Skip to main content

ntex_remove_trailing_slash/
lib.rs

1use ntex::http::uri::{PathAndQuery, Uri};
2use ntex::util::Bytes;
3use ntex::{forward_poll, forward_ready, forward_shutdown, Service, ServiceCtx};
4use ntex::web::{WebRequest, WebResponse};
5use regex::Regex;
6
7#[derive(Debug, Default)]
8pub struct RemoveTrailingSlash;
9
10impl<S> ntex::Middleware<S> for RemoveTrailingSlash
11{
12    type Service = RemoveTrailingSlashMiddleware<S>;
13
14    fn create(&self, service: S) -> Self::Service {
15        RemoveTrailingSlashMiddleware {
16            service,
17            merge_slash: Regex::new("//+").unwrap()
18        }
19    }
20}
21
22#[derive(Debug)]
23pub struct RemoveTrailingSlashMiddleware<S> {
24    service: S,
25    merge_slash:Regex
26}
27
28impl<S, E> Service<WebRequest<E>> for RemoveTrailingSlashMiddleware<S>
29where
30    S: Service<WebRequest<E>, Response = WebResponse>
31{
32    type Response = WebResponse;
33    type Error = S::Error;
34
35    forward_poll!(service);
36    forward_ready!(service);
37    forward_shutdown!(service);
38
39    async fn call(&self, mut req: WebRequest<E>, ctx: ServiceCtx<'_, Self>) -> Result<Self::Response, Self::Error> {
40        let head = req.head_mut();
41        let original_path = head.uri.path();
42
43        if !original_path.is_empty() {
44            let path=original_path.trim_end_matches('/').to_string();
45            let path = self.merge_slash.replace_all(&path, "/");
46            let path = if path.is_empty() { "/" } else { path.as_ref() };
47
48            if path != original_path {
49                let mut parts = head.uri.clone().into_parts();
50                let query = parts.path_and_query.as_ref().and_then(|pq| pq.query());
51
52                let path = match query {
53                    Some(q) => Bytes::from(format!("{}?{}", path, q)),
54                    None => Bytes::copy_from_slice(path.as_bytes()),
55                };
56
57                parts.path_and_query = Some(PathAndQuery::from_maybe_shared(path).unwrap());
58
59                let uri = Uri::from_parts(parts).unwrap();
60                req.match_info_mut().set(uri.clone());
61                req.head_mut().uri = uri;
62            }
63        }
64
65        ctx.call(&self.service, req).await
66    }
67}