-
-
Notifications
You must be signed in to change notification settings - Fork 286
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #696 from loco-rs/request-id-middleware-ordering
add: request id + test more effective middleware ordering
- Loading branch information
Showing
10 changed files
with
236 additions
and
88 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
use std::time::Duration; | ||
|
||
use tower_http::cors; | ||
|
||
use crate::{config, Result}; | ||
|
||
/// Create a CORS layer | ||
/// | ||
/// # Errors | ||
/// | ||
/// This function will return an error if parsing of header config fail | ||
pub fn cors_middleware(config: &config::CorsMiddleware) -> Result<cors::CorsLayer> { | ||
let mut cors: cors::CorsLayer = cors::CorsLayer::permissive(); | ||
|
||
if let Some(allow_origins) = &config.allow_origins { | ||
// testing CORS, assuming https://example.com in the allow list: | ||
// $ curl -v --request OPTIONS 'localhost:5150/api/_ping' -H 'Origin: https://example.com' -H 'Access-Control-Request-Method: GET' | ||
// look for '< access-control-allow-origin: https://example.com' in response. | ||
// if it doesn't appear (test with a bogus domain), it is not allowed. | ||
let mut list = vec![]; | ||
for origins in allow_origins { | ||
list.push(origins.parse()?); | ||
} | ||
cors = cors.allow_origin(list); | ||
} | ||
|
||
if let Some(allow_headers) = &config.allow_headers { | ||
let mut headers = vec![]; | ||
for header in allow_headers { | ||
headers.push(header.parse()?); | ||
} | ||
cors = cors.allow_headers(headers); | ||
} | ||
|
||
if let Some(allow_methods) = &config.allow_methods { | ||
let mut methods = vec![]; | ||
for method in allow_methods { | ||
methods.push(method.parse()?); | ||
} | ||
cors = cors.allow_methods(methods); | ||
} | ||
|
||
if let Some(max_age) = config.max_age { | ||
cors = cors.max_age(Duration::from_secs(max_age)); | ||
} | ||
|
||
Ok(cors) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
#[cfg(all(feature = "auth_jwt", feature = "with-db"))] | ||
pub mod auth; | ||
pub mod cors; | ||
pub mod etag; | ||
pub mod format; | ||
pub mod request_id; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
use axum::{extract::Request, http::HeaderValue, middleware::Next, response::Response}; | ||
use lazy_static::lazy_static; | ||
use regex::Regex; | ||
use tracing::warn; | ||
use uuid::Uuid; | ||
|
||
#[derive(Debug, Clone)] | ||
pub struct LocoRequestId(String); | ||
|
||
impl LocoRequestId { | ||
/// Get the request id | ||
#[must_use] | ||
pub fn get(&self) -> &str { | ||
self.0.as_str() | ||
} | ||
} | ||
|
||
const X_REQUEST_ID: &str = "x-request-id"; | ||
const MAX_LEN: usize = 255; | ||
lazy_static! { | ||
static ref ID_CLEANUP: Regex = Regex::new(r"[^\w\-@]").unwrap(); | ||
} | ||
|
||
pub async fn request_id_middleware(mut request: Request, next: Next) -> Response { | ||
let header_request_id = request.headers().get(X_REQUEST_ID).cloned(); | ||
let request_id = make_request_id(header_request_id); | ||
request | ||
.extensions_mut() | ||
.insert(LocoRequestId(request_id.clone())); | ||
let mut res = next.run(request).await; | ||
|
||
if let Ok(v) = HeaderValue::from_str(request_id.as_str()) { | ||
res.headers_mut().insert(X_REQUEST_ID, v); | ||
} else { | ||
warn!("could not set request ID into response headers: `{request_id}`",); | ||
} | ||
res | ||
} | ||
|
||
fn make_request_id(maybe_request_id: Option<HeaderValue>) -> String { | ||
maybe_request_id | ||
.and_then(|hdr| { | ||
// see: https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/middleware/request_id.rb#L39 | ||
let id: Option<String> = hdr.to_str().ok().map(|s| { | ||
ID_CLEANUP | ||
.replace_all(s, "") | ||
.chars() | ||
.take(MAX_LEN) | ||
.collect() | ||
}); | ||
id.filter(|s| !s.is_empty()) | ||
}) | ||
.unwrap_or_else(|| Uuid::new_v4().to_string()) | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use axum::http::HeaderValue; | ||
use insta::assert_debug_snapshot; | ||
|
||
use super::make_request_id; | ||
|
||
#[test] | ||
fn create_or_fetch_request_id() { | ||
let id = make_request_id(Some(HeaderValue::from_static("foo-bar=baz"))); | ||
assert_debug_snapshot!(id); | ||
let id = make_request_id(Some(HeaderValue::from_static(""))); | ||
assert_debug_snapshot!(id.len()); | ||
let id = make_request_id(Some(HeaderValue::from_static("=========="))); | ||
assert_debug_snapshot!(id.len()); | ||
let long_id = "x".repeat(1000); | ||
let id = make_request_id(Some(HeaderValue::from_str(&long_id).unwrap())); | ||
assert_debug_snapshot!(id.len()); | ||
let id = make_request_id(None); | ||
assert_debug_snapshot!(id.len()); | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
...ots/loco_rs__controller__middleware__request_id__tests__create_or_fetch_request_id-2.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
source: src/controller/middleware/request_id.rs | ||
expression: id.len() | ||
--- | ||
36 |
5 changes: 5 additions & 0 deletions
5
...ots/loco_rs__controller__middleware__request_id__tests__create_or_fetch_request_id-3.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
source: src/controller/middleware/request_id.rs | ||
expression: id.len() | ||
--- | ||
36 |
Oops, something went wrong.