ntex_remove_trailing_slash/
lib.rs1use 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}