diff --git a/build.rs b/build.rs index f6ff9cb29..6a8a3bd01 100644 --- a/build.rs +++ b/build.rs @@ -21,7 +21,6 @@ fn main() { "guide/src/qs_7.md", "guide/src/qs_9.md", "guide/src/qs_10.md", - "guide/src/qs_11.md", "guide/src/qs_12.md", "guide/src/qs_13.md", ]); diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 4a820e160..1828400d3 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -8,7 +8,6 @@ - [Application state](./qs_6.md) - [Request & Response](./qs_7.md) - [WebSockets](./qs_9.md) -- [User sessions](./qs_10.md) -- [Logging](./qs_11.md) +- [Middlewares](./qs_10.md) - [Static file handling](./qs_12.md) - [HTTP/2](./qs_13.md) diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md index 0c28628eb..422e0812d 100644 --- a/guide/src/qs_10.md +++ b/guide/src/qs_10.md @@ -1 +1,87 @@ -# User sessions +# Middlewares + +## Logging + +Logging is implemented as middleware. Middlewares get executed in same order as registraton order. +It is common to register logging middleware as first middleware for application. +Logging middleware has to be registered for each application. + +### Usage + +Create `Logger` middlewares with the specified `format`. +Default `Logger` could be created with `default` method, it uses the default format: + +```ignore + %a %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i" %T +``` +```rust +extern crate actix_web; +use actix_web::Application; +use actix_web::middlewares::Logger; + +fn main() { + Application::default("/") + .middleware(Logger::default()) + .middleware(Logger::new("%a %{User-Agent}i")) + .finish(); +} +``` + +Here is example of default logging format: + +``` +INFO:actix_web::middlewares::logger: 127.0.0.1:59934 [02/Dec/2017:00:21:43 -0800] "GET / HTTP/1.1" 302 0 "-" "curl/7.54.0" 0.000397 +INFO:actix_web::middlewares::logger: 127.0.0.1:59947 [02/Dec/2017:00:22:40 -0800] "GET /index.html HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:57.0) Gecko/20100101 Firefox/57.0" 0.000646 +``` + +### Format + + `%%` The percent sign + + `%a` Remote IP-address (IP-address of proxy if using reverse proxy) + + `%t` Time when the request was started to process + + `%P` The process ID of the child that serviced the request + + `%r` First line of request + + `%s` Response status code + + `%b` Size of response in bytes, including HTTP headers + + `%T` Time taken to serve the request, in seconds with floating fraction in .06f format + + `%D` Time taken to serve the request, in milliseconds + + `%{FOO}i` request.headers['FOO'] + + `%{FOO}o` response.headers['FOO'] + + `%{FOO}e` os.environ['FOO'] + + +## Default headers + +It is possible to set default response headers with `DefaultHeaders` middleware. +*DefaultHeaders* middleware does not set header if response headers already contains it. + +```rust +extern crate actix_web; +use actix_web::*; + +fn main() { + let app = Application::default("/") + .middleware( + middlewares::DefaultHeaders::build() + .header("X-Version", "0.2") + .finish()) + .resource("/test", |r| { + r.get(|req| httpcodes::HTTPOk); + r.handler(Method::HEAD, |req| httpcodes::HTTPMethodNotAllowed); + }) + .finish(); +} +``` + +## User sessions diff --git a/guide/src/qs_11.md b/guide/src/qs_11.md deleted file mode 100644 index 5aed847ce..000000000 --- a/guide/src/qs_11.md +++ /dev/null @@ -1,59 +0,0 @@ -# Logging - -Logging is implemented as middleware. Middlewares get executed in same order as registraton order. -It is common to register logging middleware as first middleware for application. -Logging middleware has to be registered for each application. - -## Usage - -Create `Logger` middlewares with the specified `format`. -Default `Logger` could be created with `default` method, it uses the default format: - -```ignore - %a %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i" %T -``` -```rust -extern crate actix_web; -use actix_web::Application; -use actix_web::middlewares::Logger; - -fn main() { - Application::default("/") - .middleware(Logger::default()) - .middleware(Logger::new("%a %{User-Agent}i")) - .finish(); -} -``` - -Here is example of default logging format: - -``` -INFO:actix_web::middlewares::logger: 127.0.0.1:59934 [02/Dec/2017:00:21:43 -0800] "GET / HTTP/1.1" 302 0 "-" "curl/7.54.0" 0.000397 -INFO:actix_web::middlewares::logger: 127.0.0.1:59947 [02/Dec/2017:00:22:40 -0800] "GET /index.html HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:57.0) Gecko/20100101 Firefox/57.0" 0.000646 -``` - -## Format - - `%%` The percent sign - - `%a` Remote IP-address (IP-address of proxy if using reverse proxy) - - `%t` Time when the request was started to process - - `%P` The process ID of the child that serviced the request - - `%r` First line of request - - `%s` Response status code - - `%b` Size of response in bytes, including HTTP headers - - `%T` Time taken to serve the request, in seconds with floating fraction in .06f format - - `%D` Time taken to serve the request, in milliseconds - - `%{FOO}i` request.headers['FOO'] - - `%{FOO}o` response.headers['FOO'] - - `%{FOO}e` os.environ['FOO'] diff --git a/src/httpresponse.rs b/src/httpresponse.rs index ef757e1a8..cfb83fa0b 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -303,6 +303,7 @@ impl HttpResponseBuilder { /// By default `ContentEncoding::Auto` is used, which automatically /// negotiates content encoding based on request's `Accept-Encoding` headers. /// To enforce specific encoding, use specific ContentEncoding` value. + #[inline] pub fn content_encoding(&mut self, enc: ContentEncoding) -> &mut Self { if let Some(parts) = parts(&mut self.parts, &self.err) { parts.encoding = enc; @@ -311,6 +312,7 @@ impl HttpResponseBuilder { } /// Set connection type + #[inline] pub fn connection_type(&mut self, conn: ConnectionType) -> &mut Self { if let Some(parts) = parts(&mut self.parts, &self.err) { parts.connection_type = Some(conn); @@ -319,16 +321,19 @@ impl HttpResponseBuilder { } /// Set connection type to Upgrade + #[inline] pub fn upgrade(&mut self) -> &mut Self { self.connection_type(ConnectionType::Upgrade) } /// Force close connection, even if it is marked as keep-alive + #[inline] pub fn force_close(&mut self) -> &mut Self { self.connection_type(ConnectionType::Close) } /// Enables automatic chunked transfer encoding + #[inline] pub fn enable_chunked(&mut self) -> &mut Self { if let Some(parts) = parts(&mut self.parts, &self.err) { parts.chunked = true; @@ -337,6 +342,7 @@ impl HttpResponseBuilder { } /// Set response content type + #[inline] pub fn content_type(&mut self, value: V) -> &mut Self where HeaderValue: HttpTryFrom { diff --git a/src/middlewares/defaultheaders.rs b/src/middlewares/defaultheaders.rs new file mode 100644 index 000000000..ae52e684f --- /dev/null +++ b/src/middlewares/defaultheaders.rs @@ -0,0 +1,113 @@ +//! Default response headers +use http::{HeaderMap, HttpTryFrom}; +use http::header::{HeaderName, HeaderValue}; + +use httprequest::HttpRequest; +use httpresponse::HttpResponse; +use middlewares::{Response, Middleware}; + +/// `Middleware` for setting default response headers. +/// +/// This middleware does not set header if response headers already contains it. +/// +/// ```rust +/// extern crate actix_web; +/// use actix_web::*; +/// +/// fn main() { +/// let app = Application::default("/") +/// .middleware( +/// middlewares::DefaultHeaders::build() +/// .header("X-Version", "0.2") +/// .finish()) +/// .resource("/test", |r| { +/// r.get(|req| httpcodes::HTTPOk); +/// r.handler(Method::HEAD, |req| httpcodes::HTTPMethodNotAllowed); +/// }) +/// .finish(); +/// } +/// ``` +pub struct DefaultHeaders(HeaderMap); + +impl DefaultHeaders { + pub fn build() -> DefaultHeadersBuilder { + DefaultHeadersBuilder{headers: Some(HeaderMap::new())} + } +} + +impl Middleware for DefaultHeaders { + + fn response(&self, _: &mut HttpRequest, mut resp: HttpResponse) -> Response { + for (key, value) in self.0.iter() { + if !resp.headers().contains_key(key) { + resp.headers_mut().insert(key, value.clone()); + } + } + Response::Done(resp) + } +} + +/// Structure that follows the builder pattern for building `DefaultHeaders` middleware. +#[derive(Debug)] +pub struct DefaultHeadersBuilder { + headers: Option, +} + +impl DefaultHeadersBuilder { + + /// Set a header. + #[inline] + #[cfg_attr(feature = "cargo-clippy", allow(match_wild_err_arm))] + pub fn header(&mut self, key: K, value: V) -> &mut Self + where HeaderName: HttpTryFrom, + HeaderValue: HttpTryFrom + { + if let Some(ref mut headers) = self.headers { + match HeaderName::try_from(key) { + Ok(key) => { + match HeaderValue::try_from(value) { + Ok(value) => { headers.append(key, value); } + Err(_) => panic!("Can not create header value"), + } + }, + Err(_) => panic!("Can not create header name"), + }; + } + self + } + + /// Finishes building and returns the built `DefaultHeaders` middleware. + pub fn finish(&mut self) -> DefaultHeaders { + let headers = self.headers.take().expect("cannot reuse middleware builder"); + DefaultHeaders(headers) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use http::header::CONTENT_TYPE; + + #[test] + fn test_default_headers() { + let mw = DefaultHeaders::build() + .header(CONTENT_TYPE, "0001") + .finish(); + + let mut req = HttpRequest::default(); + + let resp = HttpResponse::Ok().finish().unwrap(); + let resp = match mw.response(&mut req, resp) { + Response::Done(resp) => resp, + _ => panic!(), + }; + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); + + let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish().unwrap(); + let resp = match mw.response(&mut req, resp) { + Response::Done(resp) => resp, + _ => panic!(), + }; + assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002"); + } +} diff --git a/src/middlewares/mod.rs b/src/middlewares/mod.rs index abb25800a..ebe405319 100644 --- a/src/middlewares/mod.rs +++ b/src/middlewares/mod.rs @@ -7,7 +7,9 @@ use httpresponse::HttpResponse; mod logger; mod session; +mod defaultheaders; pub use self::logger::Logger; +pub use self::defaultheaders::{DefaultHeaders, DefaultHeadersBuilder}; pub use self::session::{RequestSession, Session, SessionImpl, SessionBackend, SessionStorage, CookieSessionError, CookieSessionBackend, CookieSessionBackendBuilder};