From 57fd35ffc1e929e1d5ec5ee2299833f95cc3f8c7 Mon Sep 17 00:00:00 2001
From: Nikolay Kim <fafhrd91@gmail.com>
Date: Sun, 3 Dec 2017 20:47:15 -0800
Subject: [PATCH] added default headers middleware

---
 build.rs                          |   1 -
 guide/src/SUMMARY.md              |   3 +-
 guide/src/qs_10.md                |  88 ++++++++++++++++++++++-
 guide/src/qs_11.md                |  59 ----------------
 src/httpresponse.rs               |   6 ++
 src/middlewares/defaultheaders.rs | 113 ++++++++++++++++++++++++++++++
 src/middlewares/mod.rs            |   2 +
 7 files changed, 209 insertions(+), 63 deletions(-)
 delete mode 100644 guide/src/qs_11.md
 create mode 100644 src/middlewares/defaultheaders.rs

diff --git a/build.rs b/build.rs
index f6ff9cb2..6a8a3bd0 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 4a820e16..1828400d 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 0c28628e..422e0812 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 5aed847c..00000000
--- 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 ef757e1a..cfb83fa0 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<V>(&mut self, value: V) -> &mut Self
         where HeaderValue: HttpTryFrom<V>
     {
diff --git a/src/middlewares/defaultheaders.rs b/src/middlewares/defaultheaders.rs
new file mode 100644
index 00000000..ae52e684
--- /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<HeaderMap>,
+}
+
+impl DefaultHeadersBuilder {
+
+    /// Set a header.
+    #[inline]
+    #[cfg_attr(feature = "cargo-clippy", allow(match_wild_err_arm))]
+    pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
+        where HeaderName: HttpTryFrom<K>,
+              HeaderValue: HttpTryFrom<V>
+    {
+        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 abb25800..ebe40531 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};