Skip to content

Commit 736042e

Browse files
committed
feat(cors): make cors middleware from config
1 parent 9e3eb80 commit 736042e

File tree

6 files changed

+157
-25
lines changed

6 files changed

+157
-25
lines changed

fixtures/config.toml

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
# Server configuration is also provided by specifying a TOML file with the
22
# following values
3-
host = "0.0.0.0"
4-
port = 5800
5-
verbose = true
6-
root_dir = "~/Desktop"
73

8-
[tls]
9-
cert = "cert.pem"
10-
key = "key.pem"
4+
host = "127.0.0.1"
5+
port = 7878
6+
verbose = false
7+
root_dir = "./"
8+
9+
# [tls]
10+
# cert = "cert.pem"
11+
# key = "key.pem"
12+
13+
[cors]
14+
allow_credentials = false
15+
allow_headers = ["content-type", "authorization", "content-length"]
16+
allow_methods = ["GET", "PATCH", "POST", "PUT", "DELETE"]
17+
allow_origin = "example.com"
18+
expose_headers = ["*", "authorization"]
19+
max_age = 600
20+
request_headers = ["x-app-version"]
21+
request_method = "GET"

src/config/cors.rs

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,24 +29,24 @@ pub struct CorsConfig {
2929
/// its value to false).
3030
///
3131
/// Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
32-
allow_credentials: bool,
32+
pub(crate) allow_credentials: bool,
3333
/// The Access-Control-Allow-Headers response header is used in response to a
3434
/// preflight request which includes the Access-Control-Request-Headers to
3535
/// indicate which HTTP headers can be used during the actual request.
3636
///
3737
/// Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
38-
allow_headers: Option<Vec<String>>,
38+
pub(crate) allow_headers: Option<Vec<String>>,
3939
/// The Access-Control-Allow-Methods response header specifies the method or
4040
/// methods allowed when accessing the resource in response to a preflight
4141
/// request.
4242
///
4343
/// Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
44-
allow_methods: Option<Vec<String>>,
44+
pub(crate) allow_methods: Option<Vec<String>>,
4545
/// The Access-Control-Allow-Origin response header indicates whether the
4646
/// response can be shared with requesting code from the given origin.
4747
///
4848
/// Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
49-
allow_origin: Option<String>,
49+
pub(crate) allow_origin: Option<String>,
5050
/// The Access-Control-Expose-Headers response header allows a server to
5151
/// indicate which response headers should be made available to scripts
5252
/// running in the browser, in response to a cross-origin request.
@@ -56,28 +56,28 @@ pub struct CorsConfig {
5656
/// using the Access-Control-Expose-Headers header.
5757
///
5858
/// Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
59-
expose_headers: Option<Vec<String>>,
59+
pub(crate) expose_headers: Option<Vec<String>>,
6060
/// The Access-Control-Max-Age response header indicates how long the results
6161
/// of a preflight request (that is the information contained in the
6262
/// Access-Control-Allow-Methods and Access-Control-Allow-Headers headers)
6363
/// can be cached.
6464
///
6565
/// Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
66-
max_age: Option<Duration>,
66+
pub(crate) max_age: Option<u64>,
6767
/// The Access-Control-Request-Headers request header is used by browsers
6868
/// when issuing a preflight request, to let the server know which HTTP
6969
/// headers the client might send when the actual request is made (such as
7070
/// with setRequestHeader()). This browser side header will be answered by
7171
/// the complementary server side header of Access-Control-Allow-Headers.
7272
///
7373
/// Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers
74-
request_headers: Option<Vec<String>>,
74+
pub(crate) request_headers: Option<Vec<String>>,
7575
/// The Access-Control-Request-Method request header is used by browsers when
7676
/// issuing a preflight request, to let the server know which HTTP method will
7777
/// be used when the actual request is made. This header is necessary as the
7878
/// preflight request is always an OPTIONS and doesn't use the same method as
7979
/// the actual request.
80-
request_method: Option<String>,
80+
pub(crate) request_method: Option<String>,
8181
}
8282

8383
impl CorsConfig {
@@ -104,7 +104,7 @@ impl CorsConfig {
104104
"Content-Type".to_string(),
105105
]),
106106
allow_credentials: false,
107-
max_age: Some(Duration::from_secs(43200)),
107+
max_age: Some(43200),
108108
expose_headers: None,
109109
request_headers: None,
110110
request_method: None,
@@ -153,7 +153,7 @@ impl CorsConfigBuilder {
153153
self
154154
}
155155

156-
pub fn max_age(mut self, duration: Duration) -> Self {
156+
pub fn max_age(mut self, duration: u64) -> Self {
157157
self.config.max_age = Some(duration);
158158
self
159159
}
@@ -188,7 +188,7 @@ pub struct CorsConfigFile {
188188
pub allow_methods: Option<Vec<String>>,
189189
pub allow_origin: Option<String>,
190190
pub expose_headers: Option<Vec<String>>,
191-
pub max_age: Option<f64>,
191+
pub max_age: Option<u64>,
192192
pub request_headers: Option<Vec<String>>,
193193
pub request_method: Option<String>,
194194
}
@@ -220,7 +220,7 @@ impl TryFrom<CorsConfigFile> for CorsConfig {
220220
}
221221

222222
if let Some(max_age) = file_config.max_age {
223-
cors_config_builder = cors_config_builder.max_age(Duration::from_secs_f64(max_age));
223+
cors_config_builder = cors_config_builder.max_age(max_age);
224224
}
225225

226226
if let Some(request_headers) = file_config.request_headers {
@@ -308,7 +308,7 @@ mod tests {
308308
])
309309
);
310310
assert_eq!(cors_config.allow_credentials, false);
311-
assert_eq!(cors_config.max_age, Some(Duration::from_secs(43200)));
311+
assert_eq!(cors_config.max_age, Some(43200));
312312
assert_eq!(cors_config.expose_headers, None);
313313
assert_eq!(cors_config.request_headers, None);
314314
assert_eq!(cors_config.request_method, None);
@@ -324,7 +324,7 @@ mod tests {
324324
let allow_mehtods = vec!["GET".to_string(), "POST".to_string(), "PUT".to_string()];
325325
let allow_origin = String::from("github.com");
326326
let expose_headers = vec!["content-type".to_string(), "request-id".to_string()];
327-
let max_age = 5400.;
327+
let max_age = 5400;
328328
let request_headers = vec![
329329
"content-type".to_string(),
330330
"content-length".to_string(),
@@ -347,7 +347,7 @@ mod tests {
347347
allow_methods: Some(allow_mehtods),
348348
allow_origin: Some(allow_origin),
349349
expose_headers: Some(expose_headers),
350-
max_age: Some(Duration::from_secs_f64(max_age)),
350+
max_age: Some(max_age),
351351
request_headers: Some(request_headers),
352352
request_method: Some(request_method),
353353
};

src/config/file.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ mod tests {
208208
]),
209209
allow_origin: Some(String::from("example.com")),
210210
expose_headers: Some(vec!["*".to_string(), "authorization".to_string()]),
211-
max_age: Some(2800_f64),
211+
max_age: Some(2800),
212212
request_headers: Some(vec!["x-app-version".to_string()]),
213213
request_method: Some(String::from("GET")),
214214
};

src/server/handler/mod.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ mod file_explorer;
22

33
use anyhow::Result;
44
use hyper::{Body, Request, Response};
5-
use std::sync::Arc;
5+
use std::{convert::TryFrom, sync::Arc};
66

77
use crate::Config;
88

@@ -29,7 +29,8 @@ impl HttpHandler {
2929
impl From<Config> for HttpHandler {
3030
fn from(config: Config) -> Self {
3131
let file_explorer = Arc::new(FileExplorer::new(config.root_dir()));
32-
let middleware = Arc::new(Middleware::default());
32+
let middleware = Middleware::try_from(config).unwrap();
33+
let middleware = Arc::new(middleware);
3334

3435
HttpHandler {
3536
file_explorer,
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use hyper::header::{self, HeaderName, HeaderValue};
2+
use hyper::{Body, Response};
3+
4+
use crate::config::Config;
5+
6+
use super::MiddlewareAfter;
7+
8+
/// Creates a CORS middleware with the configuration provided and returns it.
9+
/// The configured headers will be appended to every HTTP Response before
10+
/// sending such response back to the client (After Middleware)
11+
///
12+
/// CORS headers for every response are built on server initialization and
13+
/// then are "appended" to Response headers on every response.
14+
///
15+
/// # Panics
16+
///
17+
/// Panics if a CORS config is not defined for the `Config` instance provided.
18+
/// (`Config.cors` is `None`).
19+
/// `make_cors_middlware` should only be called when a `CorsConfig` is defined.
20+
///
21+
/// Also panics if any CORS header value is not a valid UTF-8 string
22+
pub fn make_cors_middleware(config: Config) -> MiddlewareAfter {
23+
let cors_config = config.cors().unwrap();
24+
let mut cors_headers: Vec<(HeaderName, HeaderValue)> = Vec::new();
25+
26+
if cors_config.allow_credentials {
27+
cors_headers.push((
28+
header::ACCESS_CONTROL_ALLOW_CREDENTIALS,
29+
HeaderValue::from_str("true").unwrap(),
30+
));
31+
}
32+
33+
if let Some(allow_headers) = cors_config.allow_headers {
34+
let allow_headers = allow_headers.join(", ");
35+
36+
cors_headers.push((
37+
header::ACCESS_CONTROL_ALLOW_HEADERS,
38+
HeaderValue::from_str(allow_headers.as_str()).unwrap(),
39+
));
40+
}
41+
42+
if let Some(allow_methods) = cors_config.allow_methods {
43+
let allow_methods = allow_methods.join(", ");
44+
45+
cors_headers.push((
46+
header::ACCESS_CONTROL_ALLOW_METHODS,
47+
HeaderValue::from_str(allow_methods.as_str()).unwrap(),
48+
));
49+
}
50+
51+
if let Some(allow_origin) = cors_config.allow_origin {
52+
cors_headers.push((
53+
header::ACCESS_CONTROL_ALLOW_ORIGIN,
54+
HeaderValue::from_str(allow_origin.as_str()).unwrap(),
55+
));
56+
}
57+
58+
if let Some(expose_headers) = cors_config.expose_headers {
59+
let expose_headers = expose_headers.join(", ");
60+
61+
cors_headers.push((
62+
header::ACCESS_CONTROL_EXPOSE_HEADERS,
63+
HeaderValue::from_str(expose_headers.as_str()).unwrap(),
64+
));
65+
}
66+
67+
if let Some(max_age) = cors_config.max_age {
68+
cors_headers.push((
69+
header::ACCESS_CONTROL_MAX_AGE,
70+
HeaderValue::from_str(max_age.to_string().as_str()).unwrap(),
71+
));
72+
}
73+
74+
if let Some(request_headers) = cors_config.request_headers {
75+
let request_headers = request_headers.join(", ");
76+
77+
cors_headers.push((
78+
header::ACCESS_CONTROL_REQUEST_HEADERS,
79+
HeaderValue::from_str(request_headers.as_str()).unwrap(),
80+
));
81+
}
82+
83+
if let Some(request_method) = cors_config.request_method {
84+
cors_headers.push((
85+
header::ACCESS_CONTROL_REQUEST_METHOD,
86+
HeaderValue::from_str(request_method.as_str()).unwrap(),
87+
));
88+
}
89+
90+
Box::new(move |response: &mut Response<Body>| {
91+
let headers = response.headers_mut();
92+
93+
cors_headers.iter().for_each(|(header, value)| {
94+
headers.append(header, value.to_owned());
95+
});
96+
})
97+
}

src/server/middleware/mod.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1+
pub mod make_cors_middleware;
12
pub mod with_cors_allow_all;
23

4+
use anyhow::Error;
35
use futures::Future;
46
use hyper::{Body, Request, Response};
7+
use std::convert::TryFrom;
58
use std::pin::Pin;
69

10+
use crate::config::Config;
11+
12+
use self::make_cors_middleware::make_cors_middleware;
13+
714
pub type MiddlewareBefore = Box<dyn Fn(&mut Request<Body>) + Send + Sync>;
815
pub type MiddlewareAfter = Box<dyn Fn(&mut Response<Body>) + Send + Sync>;
916
pub type Handler = Box<
@@ -60,6 +67,22 @@ impl Default for Middleware {
6067
}
6168
}
6269

70+
impl TryFrom<Config> for Middleware {
71+
type Error = Error;
72+
73+
fn try_from(config: Config) -> Result<Self, Self::Error> {
74+
let mut middleware = Middleware::default();
75+
76+
if config.cors().is_some() {
77+
let func = make_cors_middleware(config.clone());
78+
79+
middleware.after(func);
80+
}
81+
82+
Ok(middleware)
83+
}
84+
}
85+
6386
// mod tests {
6487
// use std::str::FromStr;
6588

0 commit comments

Comments
 (0)