1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-06-12 18:29:34 +00:00

major cleanup of middleware module (#1875)

* major cleanup of middleware module

* update changelog
This commit is contained in:
Rob Ede 2021-01-05 09:51:58 +00:00 committed by GitHub
parent 4f5971d79e
commit 68117543ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 319 additions and 303 deletions

View file

@ -2,25 +2,31 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
### Added ### Added
* `Compat` middleware enabling generic response body/error type of middlewares * `Compat` middleware enabling generic response body/error type of middlewares like `Logger` and
like `Logger` and `Compress` to be used in `middleware::Condition` `Compress` to be used in `middleware::Condition` and `Resource`, `Scope` services. [#1865]
and `Resource`, `Scope` services. [#1865]
### Changed ### Changed
* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] * Update `actix-*` dependencies to tokio `1.0` based versions. [#1813]
* Bumped `rand` to `0.8`. * Bumped `rand` to `0.8`.
* Update `rust-tls` to `0.19`. [#1813] * Update `rust-tls` to `0.19`. [#1813]
* Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852] * Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852]
* The default `TrailingSlash` is now `Trim`, in line with existing documentation. See migration
guide for implications. [#1875]
* Rename `DefaultHeaders::{content_type => add_content_type}`. [#1875]
* MSRV is now 1.46.0. * MSRV is now 1.46.0.
[#1813]: https://github.com/actix/actix-web/pull/1813
[#1865]: https://github.com/actix/actix-web/pull/1865
### Fixed ### Fixed
* added the actual parsing error to `test::read_body_json` [#1812] * Added the underlying parse error to `test::read_body_json`'s panic message. [#1812]
### Removed
* Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now
exposed directly by the `middleware` module.
[#1812]: https://github.com/actix/actix-web/pull/1812 [#1812]: https://github.com/actix/actix-web/pull/1812
[#1813]: https://github.com/actix/actix-web/pull/1813
[#1852]: https://github.com/actix/actix-web/pull/1852 [#1852]: https://github.com/actix/actix-web/pull/1852
[#1865]: https://github.com/actix/actix-web/pull/1865
[#1875]: https://github.com/actix/actix-web/pull/1875
## 3.3.2 - 2020-12-01 ## 3.3.2 - 2020-12-01

View file

@ -1,5 +1,15 @@
## Unreleased ## Unreleased
* The default `NormalizePath` behavior now strips trailing slashes by default. This was
previously documented to be the case in v3 but the behavior now matches. The effect is that
routes defined with trailing slashes will become inaccessible when
using `NormalizePath::default()`.
Before: `#[get("/test/")`
After: `#[get("/test")`
Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`.
## 3.0.0 ## 3.0.0

View file

@ -18,8 +18,7 @@ async fn no_params() -> &'static str {
#[actix_web::main] #[actix_web::main]
async fn main() -> std::io::Result<()> { async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
env_logger::init();
HttpServer::new(|| { HttpServer::new(|| {
App::new() App::new()

View file

@ -1,41 +1,45 @@
//! `Middleware` for enabling any middleware to be used in `Resource`, `Scope` and `Condition`. //! For middleware documentation, see [`Compat`].
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
use actix_http::body::Body; use std::{
use actix_http::body::{MessageBody, ResponseBody}; future::Future,
pin::Pin,
task::{Context, Poll},
};
use actix_http::body::{Body, MessageBody, ResponseBody};
use actix_service::{Service, Transform}; use actix_service::{Service, Transform};
use futures_core::future::LocalBoxFuture; use futures_core::{future::LocalBoxFuture, ready};
use futures_core::ready;
use crate::error::Error; use crate::{error::Error, service::ServiceResponse};
use crate::service::ServiceResponse;
/// `Middleware` for enabling any middleware to be used in `Resource`, `Scope` and `Condition`. /// Middleware for enabling any middleware to be used in [`Resource::wrap`](crate::Resource::wrap),
/// /// [`Scope::wrap`](crate::Scope::wrap) and [`Condition`](super::Condition).
///
/// ## Usage
/// ///
/// # Usage
/// ```rust /// ```rust
/// use actix_web::middleware::{Logger, Compat}; /// use actix_web::middleware::{Logger, Compat};
/// use actix_web::{App, web}; /// use actix_web::{App, web};
/// ///
/// let logger = Logger::default(); /// let logger = Logger::default();
/// ///
/// // this would not compile /// // this would not compile because of incompatible body types
/// // let app = App::new().service(web::scope("scoped").wrap(logger)); /// // let app = App::new()
/// // .service(web::scope("scoped").wrap(logger));
/// ///
/// // by using scoped middleware we can use logger in scope. /// // by using this middleware we can use the logger on a scope
/// let app = App::new().service(web::scope("scoped").wrap(Compat::new(logger))); /// let app = App::new()
/// .service(web::scope("scoped").wrap(Compat::new(logger)));
/// ``` /// ```
pub struct Compat<T> { pub struct Compat<T> {
transform: T, transform: T,
} }
impl<T> Compat<T> { impl<T> Compat<T> {
pub fn new(transform: T) -> Self { /// Wrap a middleware to give it broader compatibility.
Self { transform } pub fn new(middleware: T) -> Self {
Self {
transform: middleware,
}
} }
} }

View file

@ -1,45 +1,47 @@
//! `Middleware` for compressing response body. //! For middleware documentation, see [`Compress`].
use std::cmp;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::str::FromStr;
use std::task::{Context, Poll};
use actix_http::body::MessageBody; use std::{
use actix_http::encoding::Encoder; cmp,
use actix_http::http::header::{ContentEncoding, ACCEPT_ENCODING}; future::Future,
use actix_http::Error; marker::PhantomData,
pin::Pin,
str::FromStr,
task::{Context, Poll},
};
use actix_http::{
body::MessageBody,
encoding::Encoder,
http::header::{ContentEncoding, ACCEPT_ENCODING},
Error,
};
use actix_service::{Service, Transform}; use actix_service::{Service, Transform};
use futures_util::future::{ok, Ready}; use futures_util::future::{ok, Ready};
use pin_project::pin_project; use pin_project::pin_project;
use crate::dev::BodyEncoding; use crate::{
use crate::service::{ServiceRequest, ServiceResponse}; dev::BodyEncoding,
service::{ServiceRequest, ServiceResponse},
};
#[derive(Debug, Clone)] /// Middleware for compressing response payloads.
/// `Middleware` for compressing response body.
/// ///
/// Use `BodyEncoding` trait for overriding response compression. /// Use `BodyEncoding` trait for overriding response compression. To disable compression set
/// To disable compression set encoding to `ContentEncoding::Identity` value. /// encoding to `ContentEncoding::Identity`.
/// ///
/// # Usage
/// ```rust /// ```rust
/// use actix_web::{web, middleware, App, HttpResponse}; /// use actix_web::{web, middleware, App, HttpResponse};
/// ///
/// fn main() { /// let app = App::new()
/// let app = App::new() /// .wrap(middleware::Compress::default())
/// .wrap(middleware::Compress::default()) /// .default_service(web::to(|| HttpResponse::NotFound()));
/// .service(
/// web::resource("/test")
/// .route(web::get().to(|| HttpResponse::Ok()))
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
/// );
/// }
/// ``` /// ```
#[derive(Debug, Clone)]
pub struct Compress(ContentEncoding); pub struct Compress(ContentEncoding);
impl Compress { impl Compress {
/// Create new `Compress` middleware with default encoding. /// Create new `Compress` middleware with the specified encoding.
pub fn new(encoding: ContentEncoding) -> Self { pub fn new(encoding: ContentEncoding) -> Self {
Compress(encoding) Compress(encoding)
} }
@ -84,9 +86,7 @@ where
type Error = Error; type Error = Error;
type Future = CompressResponse<S, B>; type Future = CompressResponse<S, B>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_service::forward_ready!(service);
self.service.poll_ready(cx)
}
#[allow(clippy::borrow_interior_mutable_const)] #[allow(clippy::borrow_interior_mutable_const)]
fn call(&mut self, req: ServiceRequest) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
@ -109,7 +109,6 @@ where
} }
} }
#[doc(hidden)]
#[pin_project] #[pin_project]
pub struct CompressResponse<S, B> pub struct CompressResponse<S, B>
where where

View file

@ -1,35 +1,36 @@
//! `Middleware` for conditionally enables another middleware. //! For middleware documentation, see [`Condition`].
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use actix_service::{Service, Transform}; use actix_service::{Service, Transform};
use futures_util::future::{ok, Either, FutureExt, LocalBoxFuture}; use futures_util::future::{Either, FutureExt, LocalBoxFuture};
/// `Middleware` for conditionally enables another middleware. /// Middleware for conditionally enabling other middleware.
/// The controlled middleware must not change the `Service` interfaces.
/// ///
/// This means you cannot control such middlewares like `Logger` or `Compress` directly. /// The controlled middleware must not change the `Service` interfaces. This means you cannot
/// *. See `Compat` middleware for alternative. /// control such middlewares like `Logger` or `Compress` directly. See the [`Compat`](super::Compat)
/// /// middleware for a workaround.
/// ## Usage
/// ///
/// # Usage
/// ```rust /// ```rust
/// use actix_web::middleware::{Condition, NormalizePath}; /// use actix_web::middleware::{Condition, NormalizePath};
/// use actix_web::App; /// use actix_web::App;
/// ///
/// # fn main() { /// let enable_normalize = std::env::var("NORMALIZE_PATH").is_ok();
/// let enable_normalize = std::env::var("NORMALIZE_PATH") == Ok("true".into());
/// let app = App::new() /// let app = App::new()
/// .wrap(Condition::new(enable_normalize, NormalizePath::default())); /// .wrap(Condition::new(enable_normalize, NormalizePath::default()));
/// # }
/// ``` /// ```
pub struct Condition<T> { pub struct Condition<T> {
trans: T, transformer: T,
enable: bool, enable: bool,
} }
impl<T> Condition<T> { impl<T> Condition<T> {
pub fn new(enable: bool, trans: T) -> Self { pub fn new(enable: bool, transformer: T) -> Self {
Self { trans, enable } Self {
transformer,
enable,
}
} }
} }
@ -49,16 +50,15 @@ where
fn new_transform(&self, service: S) -> Self::Future { fn new_transform(&self, service: S) -> Self::Future {
if self.enable { if self.enable {
let f = self.trans.new_transform(service).map(|res| { let fut = self.transformer.new_transform(service);
res.map( async move {
ConditionMiddleware::Enable as fn(T::Transform) -> Self::Transform, let wrapped_svc = fut.await?;
) Ok(ConditionMiddleware::Enable(wrapped_svc))
}); }
Either::Left(f) .boxed_local()
} else { } else {
Either::Right(ok(ConditionMiddleware::Disable(service))) async move { Ok(ConditionMiddleware::Disable(service)) }.boxed_local()
} }
.boxed_local()
} }
} }
@ -77,18 +77,16 @@ where
type Future = Either<E::Future, D::Future>; type Future = Either<E::Future, D::Future>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
use ConditionMiddleware::*;
match self { match self {
Enable(service) => service.poll_ready(cx), ConditionMiddleware::Enable(service) => service.poll_ready(cx),
Disable(service) => service.poll_ready(cx), ConditionMiddleware::Disable(service) => service.poll_ready(cx),
} }
} }
fn call(&mut self, req: Req) -> Self::Future { fn call(&mut self, req: Req) -> Self::Future {
use ConditionMiddleware::*;
match self { match self {
Enable(service) => Either::Left(service.call(req)), ConditionMiddleware::Enable(service) => Either::Left(service.call(req)),
Disable(service) => Either::Right(service.call(req)), ConditionMiddleware::Disable(service) => Either::Right(service.call(req)),
} }
} }
} }
@ -96,14 +94,17 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_service::IntoService; use actix_service::IntoService;
use futures_util::future::ok;
use super::*; use super::*;
use crate::dev::{ServiceRequest, ServiceResponse}; use crate::{
use crate::error::Result; dev::{ServiceRequest, ServiceResponse},
use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; error::Result,
use crate::middleware::errhandlers::*; http::{header::CONTENT_TYPE, HeaderValue, StatusCode},
use crate::test::{self, TestRequest}; middleware::err_handlers::*,
use crate::HttpResponse; test::{self, TestRequest},
HttpResponse,
};
#[allow(clippy::unnecessary_wraps)] #[allow(clippy::unnecessary_wraps)]
fn render_500<B>(mut res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> { fn render_500<B>(mut res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {

View file

@ -1,24 +1,34 @@
//! Middleware for setting default response headers //! For middleware documentation, see [`DefaultHeaders`].
use std::convert::TryFrom;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use actix_service::{Service, Transform}; use std::{
use futures_util::future::{ready, Ready}; convert::TryFrom,
use futures_util::ready; future::Future,
marker::PhantomData,
pin::Pin,
rc::Rc,
task::{Context, Poll},
};
use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; use futures_util::{
use crate::http::{Error as HttpError, HeaderMap}; future::{ready, Ready},
use crate::service::{ServiceRequest, ServiceResponse}; ready,
use crate::Error; };
/// `Middleware` for setting default response headers. use crate::{
dev::{Service, Transform},
http::{
header::{HeaderName, HeaderValue, CONTENT_TYPE},
Error as HttpError, HeaderMap,
},
service::{ServiceRequest, ServiceResponse},
Error,
};
/// Middleware for setting default response headers.
/// ///
/// This middleware does not set header if response headers already contains it. /// Headers with the same key that are already set in a response will *not* be overwritten.
/// ///
/// # Usage
/// ```rust /// ```rust
/// use actix_web::{web, http, middleware, App, HttpResponse}; /// use actix_web::{web, http, middleware, App, HttpResponse};
/// ///
@ -38,7 +48,6 @@ pub struct DefaultHeaders {
} }
struct Inner { struct Inner {
ct: bool,
headers: HeaderMap, headers: HeaderMap,
} }
@ -46,7 +55,6 @@ impl Default for DefaultHeaders {
fn default() -> Self { fn default() -> Self {
DefaultHeaders { DefaultHeaders {
inner: Rc::new(Inner { inner: Rc::new(Inner {
ct: false,
headers: HeaderMap::new(), headers: HeaderMap::new(),
}), }),
} }
@ -54,12 +62,12 @@ impl Default for DefaultHeaders {
} }
impl DefaultHeaders { impl DefaultHeaders {
/// Construct `DefaultHeaders` middleware. /// Constructs an empty `DefaultHeaders` middleware.
pub fn new() -> DefaultHeaders { pub fn new() -> DefaultHeaders {
DefaultHeaders::default() DefaultHeaders::default()
} }
/// Set a header. /// Adds a header to the default set.
#[inline] #[inline]
pub fn header<K, V>(mut self, key: K, value: V) -> Self pub fn header<K, V>(mut self, key: K, value: V) -> Self
where where
@ -84,11 +92,18 @@ impl DefaultHeaders {
self self
} }
/// Set *CONTENT-TYPE* header if response does not contain this header. /// Adds a default *Content-Type* header if response does not contain one.
pub fn content_type(mut self) -> Self { ///
/// Default is `application/octet-stream`.
pub fn add_content_type(mut self) -> Self {
Rc::get_mut(&mut self.inner) Rc::get_mut(&mut self.inner)
.expect("Multiple copies exist") .expect("Multiple `Inner` copies exist.")
.ct = true; .headers
.insert(
CONTENT_TYPE,
HeaderValue::from_static("application/octet-stream"),
);
self self
} }
} }
@ -126,9 +141,7 @@ where
type Error = Error; type Error = Error;
type Future = DefaultHeaderFuture<S, B>; type Future = DefaultHeaderFuture<S, B>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_service::forward_ready!(service);
self.service.poll_ready(cx)
}
fn call(&mut self, req: ServiceRequest) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
let inner = self.inner.clone(); let inner = self.inner.clone();
@ -160,19 +173,14 @@ where
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let this = self.project(); let this = self.project();
let mut res = ready!(this.fut.poll(cx))?; let mut res = ready!(this.fut.poll(cx))?;
// set response headers // set response headers
for (key, value) in this.inner.headers.iter() { for (key, value) in this.inner.headers.iter() {
if !res.headers().contains_key(key) { if !res.headers().contains_key(key) {
res.headers_mut().insert(key.clone(), value.clone()); res.headers_mut().insert(key.clone(), value.clone());
} }
} }
// default content-type
if this.inner.ct && !res.headers().contains_key(&CONTENT_TYPE) {
res.headers_mut().insert(
CONTENT_TYPE,
HeaderValue::from_static("application/octet-stream"),
);
}
Poll::Ready(Ok(res)) Poll::Ready(Ok(res))
} }
} }
@ -183,10 +191,12 @@ mod tests {
use futures_util::future::ok; use futures_util::future::ok;
use super::*; use super::*;
use crate::dev::ServiceRequest; use crate::{
use crate::http::header::CONTENT_TYPE; dev::ServiceRequest,
use crate::test::{ok_service, TestRequest}; http::header::CONTENT_TYPE,
use crate::HttpResponse; test::{ok_service, TestRequest},
HttpResponse,
};
#[actix_rt::test] #[actix_rt::test]
async fn test_default_headers() { async fn test_default_headers() {
@ -219,7 +229,7 @@ mod tests {
let srv = let srv =
|req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())); |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish()));
let mut mw = DefaultHeaders::new() let mut mw = DefaultHeaders::new()
.content_type() .add_content_type()
.new_transform(srv.into_service()) .new_transform(srv.into_service())
.await .await
.unwrap(); .unwrap();

View file

@ -1,35 +1,36 @@
//! Custom handlers service for responses. //! For middleware documentation, see [`ErrorHandlers`].
use std::rc::Rc; use std::rc::Rc;
use std::task::{Context, Poll};
use actix_service::{Service, Transform}; use actix_service::{Service, Transform};
use ahash::AHashMap; use ahash::AHashMap;
use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready}; use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready};
use crate::dev::{ServiceRequest, ServiceResponse}; use crate::{
use crate::error::{Error, Result}; dev::{ServiceRequest, ServiceResponse},
use crate::http::StatusCode; error::{Error, Result},
http::StatusCode,
};
/// Error handler response /// Return type for [`ErrorHandlers`] custom handlers.
pub enum ErrorHandlerResponse<B> { pub enum ErrorHandlerResponse<B> {
/// New http response got generated /// Immediate HTTP response.
Response(ServiceResponse<B>), Response(ServiceResponse<B>),
/// Result is a future that resolves to a new http response
/// A future that resolves to an HTTP response.
Future(LocalBoxFuture<'static, Result<ServiceResponse<B>, Error>>), Future(LocalBoxFuture<'static, Result<ServiceResponse<B>, Error>>),
} }
type ErrorHandler<B> = dyn Fn(ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>>; type ErrorHandler<B> = dyn Fn(ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>>;
/// `Middleware` for allowing custom handlers for responses. /// Middleware for registering custom status code based error handlers.
/// ///
/// You can use `ErrorHandlers::handler()` method to register a custom error /// Register handlers with the `ErrorHandlers::handler()` method to register a custom error handler
/// handler for specific status code. You can modify existing response or /// for a given status code. Handlers can modify existing responses or create completely new ones.
/// create completely new one.
///
/// ## Example
/// ///
/// # Usage
/// ```rust /// ```rust
/// use actix_web::middleware::errhandlers::{ErrorHandlers, ErrorHandlerResponse}; /// use actix_web::middleware::{ErrorHandlers, ErrorHandlerResponse};
/// use actix_web::{web, http, dev, App, HttpRequest, HttpResponse, Result}; /// use actix_web::{web, http, dev, App, HttpRequest, HttpResponse, Result};
/// ///
/// fn render_500<B>(mut res: dev::ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> { /// fn render_500<B>(mut res: dev::ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
@ -39,7 +40,6 @@ type ErrorHandler<B> = dyn Fn(ServiceResponse<B>) -> Result<ErrorHandlerResponse
/// Ok(ErrorHandlerResponse::Response(res)) /// Ok(ErrorHandlerResponse::Response(res))
/// } /// }
/// ///
/// # fn main() {
/// let app = App::new() /// let app = App::new()
/// .wrap( /// .wrap(
/// ErrorHandlers::new() /// ErrorHandlers::new()
@ -49,7 +49,6 @@ type ErrorHandler<B> = dyn Fn(ServiceResponse<B>) -> Result<ErrorHandlerResponse
/// .route(web::get().to(|| HttpResponse::Ok())) /// .route(web::get().to(|| HttpResponse::Ok()))
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()) /// .route(web::head().to(|| HttpResponse::MethodNotAllowed())
/// )); /// ));
/// # }
/// ``` /// ```
pub struct ErrorHandlers<B> { pub struct ErrorHandlers<B> {
handlers: Rc<AHashMap<StatusCode, Box<ErrorHandler<B>>>>, handlers: Rc<AHashMap<StatusCode, Box<ErrorHandler<B>>>>,
@ -64,12 +63,12 @@ impl<B> Default for ErrorHandlers<B> {
} }
impl<B> ErrorHandlers<B> { impl<B> ErrorHandlers<B> {
/// Construct new `ErrorHandlers` instance /// Construct new `ErrorHandlers` instance.
pub fn new() -> Self { pub fn new() -> Self {
ErrorHandlers::default() ErrorHandlers::default()
} }
/// Register error handler for specified status code /// Register error handler for specified status code.
pub fn handler<F>(mut self, status: StatusCode, handler: F) -> Self pub fn handler<F>(mut self, status: StatusCode, handler: F) -> Self
where where
F: Fn(ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> + 'static, F: Fn(ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> + 'static,
@ -117,9 +116,7 @@ where
type Error = Error; type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>; type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_service::forward_ready!(service);
self.service.poll_ready(cx)
}
fn call(&mut self, req: ServiceRequest) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
let handlers = self.handlers.clone(); let handlers = self.handlers.clone();

View file

@ -1,13 +1,16 @@
//! Request logging middleware //! For middleware documentation, see [`Logger`].
use std::collections::HashSet;
use std::convert::TryFrom; use std::{
use std::env; collections::HashSet,
use std::fmt::{self, Display, Formatter}; convert::TryFrom,
use std::future::Future; env,
use std::marker::PhantomData; fmt::{self, Display as _},
use std::pin::Pin; future::Future,
use std::rc::Rc; marker::PhantomData,
use std::task::{Context, Poll}; pin::Pin,
rc::Rc,
task::{Context, Poll},
};
use actix_service::{Service, Transform}; use actix_service::{Service, Transform};
use bytes::Bytes; use bytes::Bytes;
@ -16,78 +19,69 @@ use log::debug;
use regex::{Regex, RegexSet}; use regex::{Regex, RegexSet};
use time::OffsetDateTime; use time::OffsetDateTime;
use crate::dev::{BodySize, MessageBody, ResponseBody}; use crate::{
use crate::error::{Error, Result}; dev::{BodySize, MessageBody, ResponseBody},
use crate::http::{HeaderName, StatusCode}; error::{Error, Result},
use crate::service::{ServiceRequest, ServiceResponse}; http::{HeaderName, StatusCode},
use crate::HttpResponse; service::{ServiceRequest, ServiceResponse},
HttpResponse,
};
/// `Middleware` for logging request and response info to the terminal. /// Middleware for logging request and response summaries to the terminal.
/// ///
/// `Logger` middleware uses standard log crate to log information. You should /// This middleware uses the `log` crate to output information. Enable `log`'s output for the
/// enable logger for `actix_web` package to see access log. /// "actix_web" scope using [`env_logger`](https://docs.rs/env_logger) or similar crate.
/// ([`env_logger`](https://docs.rs/env_logger/*/env_logger/) or similar)
/// ///
/// ## Usage /// # Default Format
/// /// The [`default`](Logger::default) Logger uses the following format:
/// Create `Logger` middleware with the specified `format`.
/// Default `Logger` could be created with `default` method, it uses the
/// default format:
/// ///
/// ```plain /// ```plain
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T /// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
///
/// Example Output:
/// 127.0.0.1:54278 "GET /test HTTP/1.1" 404 20 "-" "HTTPie/2.2.0" 0.001074
/// ``` /// ```
/// ///
/// # Usage
/// ```rust /// ```rust
/// use actix_web::{middleware::Logger, App}; /// use actix_web::{middleware::Logger, App};
/// ///
/// std::env::set_var("RUST_LOG", "actix_web=info"); /// // access logs are printed with the INFO level so ensure it is enabled by default
/// env_logger::init(); /// env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
/// ///
/// let app = App::new() /// let app = App::new()
/// .wrap(Logger::default()) /// // .wrap(Logger::default())
/// .wrap(Logger::new("%a %{User-Agent}i")); /// .wrap(Logger::new("%a %{User-Agent}i"));
/// ``` /// ```
/// ///
/// ## Format /// # Format
/// /// Variable | Description
/// `%%` The percent sign /// -------- | -----------
/// /// `%%` | The percent sign
/// `%a` Remote IP-address (IP-address of proxy if using reverse proxy) /// `%a` | Peer IP address (or IP address of reverse proxy if used)
/// /// `%t` | Time when the request started processing (in RFC 3339 format)
/// `%t` Time when the request was started to process (in rfc3339 format) /// `%r` | First line of request (Example: `GET /test HTTP/1.1`)
/// /// `%s` | Response status code
/// `%r` First line of request /// `%b` | Size of response in bytes, including HTTP headers
/// /// `%T` | Time taken to serve the request, in seconds to 6 decimal places
/// `%s` Response status code /// `%D` | Time taken to serve the request, in milliseconds
/// /// `%U` | Request URL
/// `%b` Size of response in bytes, including HTTP headers /// `%{r}a` | "Real IP" remote address **\***
/// /// `%{FOO}i` | `request.headers["FOO"]`
/// `%T` Time taken to serve the request, in seconds with floating fraction in /// `%{FOO}o` | `response.headers["FOO"]`
/// .06f format /// `%{FOO}e` | `env_var["FOO"]`
/// /// `%{FOO}xi` | [Custom request replacement](Logger::custom_request_replace) labelled "FOO"
/// `%D` Time taken to serve the request, in milliseconds
///
/// `%U` Request URL
///
/// `%{r}a` Real IP remote address **\***
///
/// `%{FOO}i` request.headers['FOO']
///
/// `%{FOO}o` response.headers['FOO']
///
/// `%{FOO}e` os.environ['FOO']
///
/// `%{FOO}xi` [custom request replacement](Logger::custom_request_replace) labelled "FOO"
/// ///
/// # Security /// # Security
/// **\*** It is calculated using /// **\*** "Real IP" remote address is calculated using
/// [`ConnectionInfo::realip_remote_addr()`](crate::dev::ConnectionInfo::realip_remote_addr()) /// [`ConnectionInfo::realip_remote_addr()`](crate::dev::ConnectionInfo::realip_remote_addr())
/// ///
/// If you use this value ensure that all requests come from trusted hosts, since it is trivial /// If you use this value, ensure that all requests come from trusted hosts. Otherwise, it is
/// for the remote client to simulate being another client. /// trivial for the remote client to falsify their source IP address.
#[derive(Debug)]
pub struct Logger(Rc<Inner>); pub struct Logger(Rc<Inner>);
#[derive(Debug, Clone)]
struct Inner { struct Inner {
format: Format, format: Format,
exclude: HashSet<String>, exclude: HashSet<String>,
@ -113,7 +107,7 @@ impl Logger {
self self
} }
/// Ignore and do not log access info for paths that match regex /// Ignore and do not log access info for paths that match regex.
pub fn exclude_regex<T: Into<String>>(mut self, path: T) -> Self { pub fn exclude_regex<T: Into<String>>(mut self, path: T) -> Self {
let inner = Rc::get_mut(&mut self.0).unwrap(); let inner = Rc::get_mut(&mut self.0).unwrap();
let mut patterns = inner.exclude_regex.patterns().to_vec(); let mut patterns = inner.exclude_regex.patterns().to_vec();
@ -209,7 +203,7 @@ where
} }
} }
/// Logger middleware /// Logger middleware service.
pub struct LoggerMiddleware<S> { pub struct LoggerMiddleware<S> {
inner: Rc<Inner>, inner: Rc<Inner>,
service: S, service: S,
@ -224,9 +218,7 @@ where
type Error = Error; type Error = Error;
type Future = LoggerResponse<S, B>; type Future = LoggerResponse<S, B>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_service::forward_ready!(service);
self.service.poll_ready(cx)
}
fn call(&mut self, req: ServiceRequest) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
if self.inner.exclude.contains(req.path()) if self.inner.exclude.contains(req.path())
@ -255,7 +247,6 @@ where
} }
} }
#[doc(hidden)]
#[pin_project::pin_project] #[pin_project::pin_project]
pub struct LoggerResponse<S, B> pub struct LoggerResponse<S, B>
where where
@ -325,7 +316,7 @@ pub struct StreamLog<B> {
impl<B> PinnedDrop for StreamLog<B> { impl<B> PinnedDrop for StreamLog<B> {
fn drop(self: Pin<&mut Self>) { fn drop(self: Pin<&mut Self>) {
if let Some(ref format) = self.format { if let Some(ref format) = self.format {
let render = |fmt: &mut Formatter<'_>| { let render = |fmt: &mut fmt::Formatter<'_>| {
for unit in &format.0 { for unit in &format.0 {
unit.render(fmt, self.size, self.time)?; unit.render(fmt, self.size, self.time)?;
} }
@ -356,9 +347,8 @@ impl<B: MessageBody> MessageBody for StreamLog<B> {
} }
} }
/// A formatting style for the `Logger`, consisting of multiple /// A formatting style for the `Logger` consisting of multiple concatenated `FormatText` items.
/// `FormatText`s concatenated into one line. #[derive(Debug, Clone)]
#[derive(Clone)]
struct Format(Vec<FormatText>); struct Format(Vec<FormatText>);
impl Default for Format { impl Default for Format {
@ -430,13 +420,12 @@ impl Format {
} }
} }
/// A string of text to be logged. This is either one of the data /// A string of text to be logged.
/// fields supported by the `Logger`, or a custom `String`. ///
#[doc(hidden)] /// This is either one of the data fields supported by the `Logger`, or a custom `String`.
#[non_exhaustive] #[non_exhaustive]
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
// TODO: remove pub on next breaking change enum FormatText {
pub enum FormatText {
Str(String), Str(String),
Percent, Percent,
RequestLine, RequestLine,
@ -454,10 +443,8 @@ pub enum FormatText {
CustomRequest(String, Option<CustomRequestFn>), CustomRequest(String, Option<CustomRequestFn>),
} }
// TODO: remove pub on next breaking change
#[doc(hidden)]
#[derive(Clone)] #[derive(Clone)]
pub struct CustomRequestFn { struct CustomRequestFn {
inner_fn: Rc<dyn Fn(&ServiceRequest) -> String>, inner_fn: Rc<dyn Fn(&ServiceRequest) -> String>,
} }
@ -476,11 +463,11 @@ impl fmt::Debug for CustomRequestFn {
impl FormatText { impl FormatText {
fn render( fn render(
&self, &self,
fmt: &mut Formatter<'_>, fmt: &mut fmt::Formatter<'_>,
size: usize, size: usize,
entry_time: OffsetDateTime, entry_time: OffsetDateTime,
) -> Result<(), fmt::Error> { ) -> Result<(), fmt::Error> {
match *self { match self {
FormatText::Str(ref string) => fmt.write_str(string), FormatText::Str(ref string) => fmt.write_str(string),
FormatText::Percent => "%".fmt(fmt), FormatText::Percent => "%".fmt(fmt),
FormatText::ResponseSize => size.fmt(fmt), FormatText::ResponseSize => size.fmt(fmt),
@ -506,7 +493,7 @@ impl FormatText {
} }
fn render_response<B>(&mut self, res: &HttpResponse<B>) { fn render_response<B>(&mut self, res: &HttpResponse<B>) {
match *self { match self {
FormatText::ResponseStatus => { FormatText::ResponseStatus => {
*self = FormatText::Str(format!("{}", res.status().as_u16())) *self = FormatText::Str(format!("{}", res.status().as_u16()))
} }
@ -527,7 +514,7 @@ impl FormatText {
} }
fn render_request(&mut self, now: OffsetDateTime, req: &ServiceRequest) { fn render_request(&mut self, now: OffsetDateTime, req: &ServiceRequest) {
match &*self { match self {
FormatText::RequestLine => { FormatText::RequestLine => {
*self = if req.query_string().is_empty() { *self = if req.query_string().is_empty() {
FormatText::Str(format!( FormatText::Str(format!(
@ -594,11 +581,11 @@ impl FormatText {
/// Converter to get a String from something that writes to a Formatter. /// Converter to get a String from something that writes to a Formatter.
pub(crate) struct FormatDisplay<'a>( pub(crate) struct FormatDisplay<'a>(
&'a dyn Fn(&mut Formatter<'_>) -> Result<(), fmt::Error>, &'a dyn Fn(&mut fmt::Formatter<'_>) -> Result<(), fmt::Error>,
); );
impl<'a> fmt::Display for FormatDisplay<'a> { impl<'a> fmt::Display for FormatDisplay<'a> {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
(self.0)(fmt) (self.0)(fmt)
} }
} }
@ -675,7 +662,7 @@ mod tests {
unit.render_response(&resp); unit.render_response(&resp);
} }
let render = |fmt: &mut Formatter<'_>| { let render = |fmt: &mut fmt::Formatter<'_>| {
for unit in &format.0 { for unit in &format.0 {
unit.render(fmt, 1024, now)?; unit.render(fmt, 1024, now)?;
} }
@ -708,7 +695,7 @@ mod tests {
} }
let entry_time = OffsetDateTime::now_utc(); let entry_time = OffsetDateTime::now_utc();
let render = |fmt: &mut Formatter<'_>| { let render = |fmt: &mut fmt::Formatter<'_>| {
for unit in &format.0 { for unit in &format.0 {
unit.render(fmt, 1024, entry_time)?; unit.render(fmt, 1024, entry_time)?;
} }
@ -736,7 +723,7 @@ mod tests {
unit.render_response(&resp); unit.render_response(&resp);
} }
let render = |fmt: &mut Formatter<'_>| { let render = |fmt: &mut fmt::Formatter<'_>| {
for unit in &format.0 { for unit in &format.0 {
unit.render(fmt, 1024, now)?; unit.render(fmt, 1024, now)?;
} }
@ -769,7 +756,7 @@ mod tests {
} }
let entry_time = OffsetDateTime::now_utc(); let entry_time = OffsetDateTime::now_utc();
let render = |fmt: &mut Formatter<'_>| { let render = |fmt: &mut fmt::Formatter<'_>| {
for unit in &format.0 { for unit in &format.0 {
unit.render(fmt, 1024, entry_time)?; unit.render(fmt, 1024, entry_time)?;
} }
@ -800,7 +787,7 @@ mod tests {
unit.render_request(now, &req); unit.render_request(now, &req);
let render = |fmt: &mut Formatter<'_>| unit.render(fmt, 1024, now); let render = |fmt: &mut fmt::Formatter<'_>| unit.render(fmt, 1024, now);
let log_output = FormatDisplay(&render).to_string(); let log_output = FormatDisplay(&render).to_string();
assert_eq!(log_output, "custom_log"); assert_eq!(log_output, "custom_log");

View file

@ -1,19 +1,20 @@
//! Middlewares //! Commonly used middleware.
mod compat;
mod condition;
mod default_headers;
mod err_handlers;
mod logger;
mod normalize;
pub use self::compat::Compat;
pub use self::condition::Condition;
pub use self::default_headers::DefaultHeaders;
pub use self::err_handlers::{ErrorHandlerResponse, ErrorHandlers};
pub use self::logger::Logger;
pub use self::normalize::{NormalizePath, TrailingSlash};
#[cfg(feature = "compress")] #[cfg(feature = "compress")]
mod compress; mod compress;
#[cfg(feature = "compress")] #[cfg(feature = "compress")]
pub use self::compress::Compress; pub use self::compress::Compress;
mod compat;
mod condition;
mod defaultheaders;
pub mod errhandlers;
mod logger;
pub mod normalize;
pub use self::compat::Compat;
pub use self::condition::Condition;
pub use self::defaultheaders::DefaultHeaders;
pub use self::logger::Logger;
pub use self::normalize::NormalizePath;

View file

@ -1,60 +1,63 @@
//! For middleware documentation, see [`NormalizePath`]. //! For middleware documentation, see [`NormalizePath`].
use std::task::{Context, Poll};
use actix_http::http::{PathAndQuery, Uri}; use actix_http::http::{PathAndQuery, Uri};
use actix_service::{Service, Transform}; use actix_service::{Service, Transform};
use bytes::Bytes; use bytes::Bytes;
use futures_util::future::{ready, Ready}; use futures_util::future::{ready, Ready};
use regex::Regex; use regex::Regex;
use crate::service::{ServiceRequest, ServiceResponse}; use crate::{
use crate::Error; service::{ServiceRequest, ServiceResponse},
Error,
};
/// To be used when constructing `NormalizePath` to define it's behavior. /// Determines the behavior of the [`NormalizePath`] middleware.
///
/// The default is `TrailingSlash::Trim`.
#[non_exhaustive] #[non_exhaustive]
#[derive(Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum TrailingSlash { pub enum TrailingSlash {
/// Always add a trailing slash to the end of the path. /// Trim trailing slashes from the end of the path.
/// This will require all routes to end in a trailing slash for them to be accessible. ///
Always, /// Using this will require all routes to omit trailing slashes for them to be accessible.
Trim,
/// Only merge any present multiple trailing slashes. /// Only merge any present multiple trailing slashes.
/// ///
/// Note: This option provides the best compatibility with the v2 version of this middleware. /// This option provides the best compatibility with behavior in actix-web v2.0.
MergeOnly, MergeOnly,
/// Trim trailing slashes from the end of the path. /// Always add a trailing slash to the end of the path.
Trim, ///
/// Using this will require all routes have a trailing slash for them to be accessible.
Always,
} }
impl Default for TrailingSlash { impl Default for TrailingSlash {
fn default() -> Self { fn default() -> Self {
TrailingSlash::Always TrailingSlash::Trim
} }
} }
#[derive(Default, Clone, Copy)] /// Middleware for normalizing a request's path so that routes can be matched more flexibly.
/// Middleware to normalize a request's path so that routes can be matched less strictly.
/// ///
/// # Normalization Steps /// # Normalization Steps
/// - Merges multiple consecutive slashes into one. (For example, `/path//one` always /// - Merges consecutive slashes into one. (For example, `/path//one` always becomes `/path/one`.)
/// becomes `/path/one`.)
/// - Appends a trailing slash if one is not present, removes one if present, or keeps trailing /// - Appends a trailing slash if one is not present, removes one if present, or keeps trailing
/// slashes as-is, depending on which [`TrailingSlash`] variant is supplied /// slashes as-is, depending on which [`TrailingSlash`] variant is supplied
/// to [`new`](NormalizePath::new()). /// to [`new`](NormalizePath::new()).
/// ///
/// # Default Behavior /// # Default Behavior
/// The default constructor chooses to strip trailing slashes from the end /// The default constructor chooses to strip trailing slashes from the end of paths with them
/// ([`TrailingSlash::Trim`]), the effect is that route definitions should be defined without /// ([`TrailingSlash::Trim`]). The implication is that route definitions should be defined without
/// trailing slashes or else they will be inaccessible. /// trailing slashes or else they will be inaccessible (or vice versa when using the
/// `TrailingSlash::Always` behavior), as shown in the example tests below.
/// ///
/// # Example /// # Usage
/// ```rust /// ```rust
/// use actix_web::{web, middleware, App}; /// use actix_web::{web, middleware, App};
/// ///
/// # #[actix_rt::test] /// # actix_web::rt::System::new("doctest").block_on(async {
/// # async fn normalize() {
/// let app = App::new() /// let app = App::new()
/// .wrap(middleware::NormalizePath::default()) /// .wrap(middleware::NormalizePath::default())
/// .route("/test", web::get().to(|| async { "test" })) /// .route("/test", web::get().to(|| async { "test" }))
@ -80,8 +83,9 @@ impl Default for TrailingSlash {
/// let req = TestRequest::with_uri("/unmatchable/").to_request(); /// let req = TestRequest::with_uri("/unmatchable/").to_request();
/// let res = call_service(&mut app, req).await; /// let res = call_service(&mut app, req).await;
/// assert_eq!(res.status(), StatusCode::NOT_FOUND); /// assert_eq!(res.status(), StatusCode::NOT_FOUND);
/// # } /// # })
/// ``` /// ```
#[derive(Debug, Clone, Copy, Default)]
pub struct NormalizePath(TrailingSlash); pub struct NormalizePath(TrailingSlash);
impl NormalizePath { impl NormalizePath {
@ -111,7 +115,6 @@ where
} }
} }
#[doc(hidden)]
pub struct NormalizePathNormalization<S> { pub struct NormalizePathNormalization<S> {
service: S, service: S,
merge_slash: Regex, merge_slash: Regex,
@ -127,9 +130,7 @@ where
type Error = Error; type Error = Error;
type Future = S::Future; type Future = S::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { actix_service::forward_ready!(service);
self.service.poll_ready(cx)
}
fn call(&mut self, mut req: ServiceRequest) -> Self::Future { fn call(&mut self, mut req: ServiceRequest) -> Self::Future {
let head = req.head_mut(); let head = req.head_mut();
@ -198,7 +199,7 @@ mod tests {
App::new() App::new()
.wrap(NormalizePath::default()) .wrap(NormalizePath::default())
.service(web::resource("/").to(HttpResponse::Ok)) .service(web::resource("/").to(HttpResponse::Ok))
.service(web::resource("/v1/something/").to(HttpResponse::Ok)), .service(web::resource("/v1/something").to(HttpResponse::Ok)),
) )
.await; .await;
@ -306,7 +307,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn test_in_place_normalization() { async fn test_in_place_normalization() {
let srv = |req: ServiceRequest| { let srv = |req: ServiceRequest| {
assert_eq!("/v1/something/", req.path()); assert_eq!("/v1/something", req.path());
ready(Ok(req.into_response(HttpResponse::Ok().finish()))) ready(Ok(req.into_response(HttpResponse::Ok().finish())))
}; };
@ -334,7 +335,7 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn should_normalize_nothing() { async fn should_normalize_nothing() {
const URI: &str = "/v1/something/"; const URI: &str = "/v1/something";
let srv = |req: ServiceRequest| { let srv = |req: ServiceRequest| {
assert_eq!(URI, req.path()); assert_eq!(URI, req.path());
@ -353,10 +354,8 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn should_normalize_no_trail() { async fn should_normalize_no_trail() {
const URI: &str = "/v1/something";
let srv = |req: ServiceRequest| { let srv = |req: ServiceRequest| {
assert_eq!(URI.to_string() + "/", req.path()); assert_eq!("/v1/something", req.path());
ready(Ok(req.into_response(HttpResponse::Ok().finish()))) ready(Ok(req.into_response(HttpResponse::Ok().finish())))
}; };
@ -365,7 +364,7 @@ mod tests {
.await .await
.unwrap(); .unwrap();
let req = TestRequest::with_uri(URI).to_srv_request(); let req = TestRequest::with_uri("/v1/something/").to_srv_request();
let res = normalize.call(req).await.unwrap(); let res = normalize.call(req).await.unwrap();
assert!(res.status().is_success()); assert!(res.status().is_success());
} }

View file

@ -1,7 +1,9 @@
use std::future::Future; use std::{
use std::io::{Read, Write}; future::Future,
use std::pin::Pin; io::{Read, Write},
use std::task::{Context, Poll}; pin::Pin,
task::{Context, Poll},
};
use actix_http::http::header::{ use actix_http::http::header::{
ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH,
@ -9,15 +11,16 @@ use actix_http::http::header::{
}; };
use brotli2::write::{BrotliDecoder, BrotliEncoder}; use brotli2::write::{BrotliDecoder, BrotliEncoder};
use bytes::Bytes; use bytes::Bytes;
use flate2::read::GzDecoder; use flate2::{
use flate2::write::{GzEncoder, ZlibDecoder, ZlibEncoder}; read::GzDecoder,
use flate2::Compression; write::{GzEncoder, ZlibDecoder, ZlibEncoder},
Compression,
};
use futures_util::ready; use futures_util::ready;
use rand::{distributions::Alphanumeric, Rng}; use rand::{distributions::Alphanumeric, Rng};
use actix_web::dev::BodyEncoding; use actix_web::dev::BodyEncoding;
use actix_web::middleware::normalize::TrailingSlash; use actix_web::middleware::{Compress, NormalizePath, TrailingSlash};
use actix_web::middleware::{Compress, NormalizePath};
use actix_web::{dev, test, web, App, Error, HttpResponse}; use actix_web::{dev, test, web, App, Error, HttpResponse};
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \