2019-04-19 20:53:49 +00:00
|
|
|
//! `Middleware` to normalize request's URI
|
|
|
|
|
|
|
|
use actix_service::{Service, Transform};
|
|
|
|
use futures::future::{self, FutureResult};
|
2019-04-20 00:23:17 +00:00
|
|
|
use regex::Regex;
|
2019-04-19 20:53:49 +00:00
|
|
|
|
|
|
|
use crate::service::{ServiceRequest, ServiceResponse};
|
2019-04-25 18:14:32 +00:00
|
|
|
use crate::Error;
|
2019-04-19 20:53:49 +00:00
|
|
|
|
|
|
|
#[derive(Default, Clone, Copy)]
|
|
|
|
/// `Middleware` to normalize request's URI in place
|
|
|
|
///
|
|
|
|
/// Performs following:
|
|
|
|
///
|
|
|
|
/// - Merges multiple slashes into one.
|
|
|
|
pub struct NormalizePath;
|
|
|
|
|
|
|
|
impl<S> Transform<S> for NormalizePath
|
|
|
|
where
|
2019-04-25 18:14:32 +00:00
|
|
|
S: Service<Request = ServiceRequest, Response = ServiceResponse, Error = Error>,
|
2019-04-19 20:53:49 +00:00
|
|
|
{
|
|
|
|
type Request = ServiceRequest;
|
|
|
|
type Response = ServiceResponse;
|
2019-04-25 18:14:32 +00:00
|
|
|
type Error = Error;
|
2019-04-19 20:53:49 +00:00
|
|
|
type InitError = ();
|
|
|
|
type Transform = NormalizePathNormalization<S>;
|
|
|
|
type Future = FutureResult<Self::Transform, Self::InitError>;
|
|
|
|
|
|
|
|
fn new_transform(&self, service: S) -> Self::Future {
|
|
|
|
future::ok(NormalizePathNormalization {
|
|
|
|
service,
|
2019-04-20 00:23:17 +00:00
|
|
|
merge_slash: Regex::new("//+").unwrap(),
|
2019-04-19 20:53:49 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct NormalizePathNormalization<S> {
|
|
|
|
service: S,
|
|
|
|
merge_slash: Regex,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<S> Service for NormalizePathNormalization<S>
|
|
|
|
where
|
2019-04-25 18:14:32 +00:00
|
|
|
S: Service<Request = ServiceRequest, Response = ServiceResponse, Error = Error>,
|
2019-04-19 20:53:49 +00:00
|
|
|
{
|
|
|
|
type Request = ServiceRequest;
|
|
|
|
type Response = ServiceResponse;
|
2019-04-25 18:14:32 +00:00
|
|
|
type Error = Error;
|
2019-04-19 20:53:49 +00:00
|
|
|
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::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());
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|