From 1e7f97a111c7fb0b047253bd4b0882d2fd471fb8 Mon Sep 17 00:00:00 2001 From: Douman Date: Fri, 19 Apr 2019 23:53:49 +0300 Subject: [PATCH] Add Normalization middleware for in place (#783) --- src/middleware/mod.rs | 1 + src/middleware/normalize.rs | 109 ++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 src/middleware/normalize.rs diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 59d467c0..3df92625 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -6,6 +6,7 @@ pub mod cors; mod defaultheaders; pub mod errhandlers; mod logger; +pub mod normalize; pub use self::defaultheaders::DefaultHeaders; pub use self::logger::Logger; diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs new file mode 100644 index 00000000..4b0afe7a --- /dev/null +++ b/src/middleware/normalize.rs @@ -0,0 +1,109 @@ +//! `Middleware` to normalize request's URI + +use regex::Regex; +use actix_service::{Service, Transform}; +use futures::future::{self, FutureResult}; + +use crate::service::{ServiceRequest, ServiceResponse}; + +#[derive(Default, Clone, Copy)] +/// `Middleware` to normalize request's URI in place +/// +/// Performs following: +/// +/// - Merges multiple slashes into one. +pub struct NormalizePath; + +impl Transform for NormalizePath +where + S: Service, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = S::Error; + type InitError = (); + type Transform = NormalizePathNormalization; + type Future = FutureResult; + + fn new_transform(&self, service: S) -> Self::Future { + future::ok(NormalizePathNormalization { + service, + merge_slash: Regex::new("//+").unwrap() + }) + } +} + +pub struct NormalizePathNormalization { + service: S, + merge_slash: Regex, +} + +impl Service for NormalizePathNormalization +where + S: Service, +{ + type Request = ServiceRequest; + type Response = ServiceResponse; + type Error = S::Error; + type Future = S::Future; + + fn poll_ready(&mut self) -> futures::Poll<(), Self::Error> { + self.service.poll_ready() + } + + fn call(&mut self, mut req: ServiceRequest) -> Self::Future { + let head = req.head_mut(); + + let path = head.uri.path(); + let original_len = path.len(); + let path = self.merge_slash.replace_all(path, "/"); + + if original_len != path.len() { + head.uri = path.parse().unwrap(); + } + + self.service.call(req) + } +} + +#[cfg(test)] +mod tests { + use actix_service::FnService; + + use super::*; + use crate::dev::ServiceRequest; + use crate::http::header::CONTENT_TYPE; + use crate::test::{block_on, TestRequest}; + use crate::HttpResponse; + + #[test] + fn test_in_place_normalization() { + let srv = FnService::new(|req: ServiceRequest| { + assert_eq!("/v1/something/", req.path()); + req.into_response(HttpResponse::Ok().finish()) + }); + + let mut normalize = block_on(NormalizePath.new_transform(srv)).unwrap(); + + let req = TestRequest::with_uri("/v1//something////").to_srv_request(); + let res = block_on(normalize.call(req)).unwrap(); + assert!(res.status().is_success()); + } + + #[test] + fn should_normalize_nothing() { + const URI: &str = "/v1/something/"; + + let srv = FnService::new(|req: ServiceRequest| { + assert_eq!(URI, req.path()); + req.into_response(HttpResponse::Ok().finish()) + }); + + let mut normalize = block_on(NormalizePath.new_transform(srv)).unwrap(); + + let req = TestRequest::with_uri(URI).to_srv_request(); + let res = block_on(normalize.call(req)).unwrap(); + assert!(res.status().is_success()); + } + +}