1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-24 16:02:59 +01:00

move NormalizePath type to separate module

This commit is contained in:
Nikolay Kim 2018-03-29 11:39:21 -07:00
parent f5636f321b
commit dfd8f1058e
5 changed files with 329 additions and 327 deletions

View File

@ -4,6 +4,10 @@
* Type-safe path/query parameter handling, using serde #70 * Type-safe path/query parameter handling, using serde #70
* Use more ergonomic `actix_web::Error` instead of `http::Error` for `HttpResponseBuilder::body()`
* Use more ergonomic `actix_web::Error` instead of `http::Error` for `ClientRequestBuilder::body()`
* Router cannot parse Non-ASCII characters in URL #137 * Router cannot parse Non-ASCII characters in URL #137
* Fix long client urls #129 * Fix long client urls #129
@ -12,10 +16,6 @@
* Fix client connection pooling * Fix client connection pooling
* Use more ergonomic `actix_web::Error` instead of `http::Error` for `HttpResponseBuilder::body()`
* Use more ergonomic `actix_web::Error` instead of `http::Error` for `ClientRequestBuilder::body()`
## 0.4.10 (2018-03-20) ## 0.4.10 (2018-03-20)

View File

@ -437,6 +437,7 @@ This handler designed to be use as a handler for application's *default resource
# extern crate actix_web; # extern crate actix_web;
# #[macro_use] extern crate serde_derive; # #[macro_use] extern crate serde_derive;
# use actix_web::*; # use actix_web::*;
use actix_web::helpers::NormalizePath;
# #
# fn index(req: HttpRequest) -> httpcodes::StaticResponse { # fn index(req: HttpRequest) -> httpcodes::StaticResponse {
# httpcodes::HttpOk # httpcodes::HttpOk
@ -462,6 +463,7 @@ It is possible to register path normalization only for *GET* requests only:
# extern crate actix_web; # extern crate actix_web;
# #[macro_use] extern crate serde_derive; # #[macro_use] extern crate serde_derive;
# use actix_web::*; # use actix_web::*;
use actix_web::helpers::NormalizePath;
# #
# fn index(req: HttpRequest) -> httpcodes::StaticResponse { # fn index(req: HttpRequest) -> httpcodes::StaticResponse {
# httpcodes::HttpOk # httpcodes::HttpOk

View File

@ -1,10 +1,6 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use regex::Regex;
use futures::future::{Future, ok, err}; use futures::future::{Future, ok, err};
use http::{header, StatusCode};
use body::Body;
use error::Error; use error::Error;
use httprequest::HttpRequest; use httprequest::HttpRequest;
use httpresponse::HttpResponse; use httpresponse::HttpResponse;
@ -341,316 +337,3 @@ impl<S, H, F, R, E> RouteHandler<S> for AsyncHandler<S, H, F, R, E>
Reply::async(fut) Reply::async(fut)
} }
} }
/// Path normalization helper
///
/// By normalizing it means:
///
/// - Add a trailing slash to the path.
/// - Remove a trailing slash from the path.
/// - Double slashes are replaced by one.
///
/// The handler returns as soon as it finds a path that resolves
/// correctly. The order if all enable is 1) merge, 3) both merge and append
/// and 3) append. If the path resolves with
/// at least one of those conditions, it will redirect to the new path.
///
/// If *append* is *true* append slash when needed. If a resource is
/// defined with trailing slash and the request comes without it, it will
/// append it automatically.
///
/// If *merge* is *true*, merge multiple consecutive slashes in the path into one.
///
/// This handler designed to be use as a handler for application's *default resource*.
///
/// ```rust
/// # extern crate actix_web;
/// # #[macro_use] extern crate serde_derive;
/// # use actix_web::*;
/// #
/// # fn index(req: HttpRequest) -> httpcodes::StaticResponse {
/// # httpcodes::HttpOk
/// # }
/// fn main() {
/// let app = Application::new()
/// .resource("/test/", |r| r.f(index))
/// .default_resource(|r| r.h(NormalizePath::default()))
/// .finish();
/// }
/// ```
/// In this example `/test`, `/test///` will be redirected to `/test/` url.
pub struct NormalizePath {
append: bool,
merge: bool,
re_merge: Regex,
redirect: StatusCode,
not_found: StatusCode,
}
impl Default for NormalizePath {
/// Create default `NormalizePath` instance, *append* is set to *true*,
/// *merge* is set to *true* and *redirect* is set to `StatusCode::MOVED_PERMANENTLY`
fn default() -> NormalizePath {
NormalizePath {
append: true,
merge: true,
re_merge: Regex::new("//+").unwrap(),
redirect: StatusCode::MOVED_PERMANENTLY,
not_found: StatusCode::NOT_FOUND,
}
}
}
impl NormalizePath {
/// Create new `NormalizePath` instance
pub fn new(append: bool, merge: bool, redirect: StatusCode) -> NormalizePath {
NormalizePath {
append,
merge,
redirect,
re_merge: Regex::new("//+").unwrap(),
not_found: StatusCode::NOT_FOUND,
}
}
}
impl<S> Handler<S> for NormalizePath {
type Result = Result<HttpResponse, Error>;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
if let Some(router) = req.router() {
let query = req.query_string();
if self.merge {
// merge slashes
let p = self.re_merge.replace_all(req.path(), "/");
if p.len() != req.path().len() {
if router.has_route(p.as_ref()) {
let p = if !query.is_empty() { p + "?" + query } else { p };
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_ref())
.body(Body::Empty);
}
// merge slashes and append trailing slash
if self.append && !p.ends_with('/') {
let p = p.as_ref().to_owned() + "/";
if router.has_route(&p) {
let p = if !query.is_empty() { p + "?" + query } else { p };
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_str())
.body(Body::Empty);
}
}
// try to remove trailing slash
if p.ends_with('/') {
let p = p.as_ref().trim_right_matches('/');
if router.has_route(p) {
let mut req = HttpResponse::build(self.redirect);
return if !query.is_empty() {
req.header(header::LOCATION, (p.to_owned() + "?" + query).as_str())
} else {
req.header(header::LOCATION, p)
}
.body(Body::Empty);
}
}
} else if p.ends_with('/') {
// try to remove trailing slash
let p = p.as_ref().trim_right_matches('/');
if router.has_route(p) {
let mut req = HttpResponse::build(self.redirect);
return if !query.is_empty() {
req.header(header::LOCATION, (p.to_owned() + "?" + query).as_str())
} else {
req.header(header::LOCATION, p)
}
.body(Body::Empty);
}
}
}
// append trailing slash
if self.append && !req.path().ends_with('/') {
let p = req.path().to_owned() + "/";
if router.has_route(&p) {
let p = if !query.is_empty() { p + "?" + query } else { p };
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_str())
.body(Body::Empty);
}
}
}
Ok(HttpResponse::new(self.not_found, Body::Empty))
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::{header, Method};
use test::TestRequest;
use application::Application;
fn index(_req: HttpRequest) -> HttpResponse {
HttpResponse::new(StatusCode::OK, Body::Empty)
}
#[test]
fn test_normalize_path_trailing_slashes() {
let mut app = Application::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
.finish();
// trailing slashes
let params =
vec![("/resource1", "", StatusCode::OK),
("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY),
("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY),
("/resource2/", "", StatusCode::OK),
("/resource1?p1=1&p2=2", "", StatusCode::OK),
("/resource1/?p1=1&p2=2", "/resource1?p1=1&p2=2", StatusCode::MOVED_PERMANENTLY),
("/resource2?p1=1&p2=2", "/resource2/?p1=1&p2=2",
StatusCode::MOVED_PERMANENTLY),
("/resource2/?p1=1&p2=2", "", StatusCode::OK)
];
for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target,
r.headers().get(header::LOCATION).unwrap().to_str().unwrap());
}
}
}
#[test]
fn test_normalize_path_trailing_slashes_disabled() {
let mut app = Application::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(
NormalizePath::new(false, true, StatusCode::MOVED_PERMANENTLY)))
.finish();
// trailing slashes
let params = vec![("/resource1", StatusCode::OK),
("/resource1/", StatusCode::MOVED_PERMANENTLY),
("/resource2", StatusCode::NOT_FOUND),
("/resource2/", StatusCode::OK),
("/resource1?p1=1&p2=2", StatusCode::OK),
("/resource1/?p1=1&p2=2", StatusCode::MOVED_PERMANENTLY),
("/resource2?p1=1&p2=2", StatusCode::NOT_FOUND),
("/resource2/?p1=1&p2=2", StatusCode::OK)
];
for (path, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
assert_eq!(r.status(), code);
}
}
#[test]
fn test_normalize_path_merge_slashes() {
let mut app = Application::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource1/a/b", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
.finish();
// trailing slashes
let params = vec![
("/resource1/a/b", "", StatusCode::OK),
("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY),
("/resource1//", "/resource1", StatusCode::MOVED_PERMANENTLY),
("//resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("//resource1//a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("//resource1//a//b//", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/resource1/a/b?p=1", "", StatusCode::OK),
("//resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource1//a//b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a//b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a//b//?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
];
for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target,
r.headers().get(header::LOCATION).unwrap().to_str().unwrap());
}
}
}
#[test]
fn test_normalize_path_merge_and_append_slashes() {
let mut app = Application::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.resource("/resource1/a/b", |r| r.method(Method::GET).f(index))
.resource("/resource2/a/b/", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
.finish();
// trailing slashes
let params = vec![
("/resource1/a/b", "", StatusCode::OK),
("/resource1/a/b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b//", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/resource2/a/b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/resource2/a/b/", "", StatusCode::OK),
("//resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("///resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("///resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/////resource2/a///b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/////resource2/a///b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/resource1/a/b?p=1", "", StatusCode::OK),
("/resource1/a/b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b//?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/resource2/a/b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource2/a///b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource2/a///b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
];
for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target, r.headers().get(header::LOCATION).unwrap().to_str().unwrap());
}
}
}
}

322
src/helpers.rs Normal file
View File

@ -0,0 +1,322 @@
//! Various helpers
use regex::Regex;
use http::{header, StatusCode};
use body::Body;
use error::Error;
use handler::Handler;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
/// Path normalization helper
///
/// By normalizing it means:
///
/// - Add a trailing slash to the path.
/// - Remove a trailing slash from the path.
/// - Double slashes are replaced by one.
///
/// The handler returns as soon as it finds a path that resolves
/// correctly. The order if all enable is 1) merge, 3) both merge and append
/// and 3) append. If the path resolves with
/// at least one of those conditions, it will redirect to the new path.
///
/// If *append* is *true* append slash when needed. If a resource is
/// defined with trailing slash and the request comes without it, it will
/// append it automatically.
///
/// If *merge* is *true*, merge multiple consecutive slashes in the path into one.
///
/// This handler designed to be use as a handler for application's *default resource*.
///
/// ```rust
/// # extern crate actix_web;
/// # #[macro_use] extern crate serde_derive;
/// # use actix_web::*;
/// use actix_web::helpers::NormalizePath;
/// #
/// # fn index(req: HttpRequest) -> httpcodes::StaticResponse {
/// # httpcodes::HttpOk
/// # }
/// fn main() {
/// let app = Application::new()
/// .resource("/test/", |r| r.f(index))
/// .default_resource(|r| r.h(NormalizePath::default()))
/// .finish();
/// }
/// ```
/// In this example `/test`, `/test///` will be redirected to `/test/` url.
pub struct NormalizePath {
append: bool,
merge: bool,
re_merge: Regex,
redirect: StatusCode,
not_found: StatusCode,
}
impl Default for NormalizePath {
/// Create default `NormalizePath` instance, *append* is set to *true*,
/// *merge* is set to *true* and *redirect* is set to `StatusCode::MOVED_PERMANENTLY`
fn default() -> NormalizePath {
NormalizePath {
append: true,
merge: true,
re_merge: Regex::new("//+").unwrap(),
redirect: StatusCode::MOVED_PERMANENTLY,
not_found: StatusCode::NOT_FOUND,
}
}
}
impl NormalizePath {
/// Create new `NormalizePath` instance
pub fn new(append: bool, merge: bool, redirect: StatusCode) -> NormalizePath {
NormalizePath {
append,
merge,
redirect,
re_merge: Regex::new("//+").unwrap(),
not_found: StatusCode::NOT_FOUND,
}
}
}
impl<S> Handler<S> for NormalizePath {
type Result = Result<HttpResponse, Error>;
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
if let Some(router) = req.router() {
let query = req.query_string();
if self.merge {
// merge slashes
let p = self.re_merge.replace_all(req.path(), "/");
if p.len() != req.path().len() {
if router.has_route(p.as_ref()) {
let p = if !query.is_empty() { p + "?" + query } else { p };
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_ref())
.body(Body::Empty);
}
// merge slashes and append trailing slash
if self.append && !p.ends_with('/') {
let p = p.as_ref().to_owned() + "/";
if router.has_route(&p) {
let p = if !query.is_empty() { p + "?" + query } else { p };
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_str())
.body(Body::Empty);
}
}
// try to remove trailing slash
if p.ends_with('/') {
let p = p.as_ref().trim_right_matches('/');
if router.has_route(p) {
let mut req = HttpResponse::build(self.redirect);
return if !query.is_empty() {
req.header(header::LOCATION, (p.to_owned() + "?" + query).as_str())
} else {
req.header(header::LOCATION, p)
}
.body(Body::Empty);
}
}
} else if p.ends_with('/') {
// try to remove trailing slash
let p = p.as_ref().trim_right_matches('/');
if router.has_route(p) {
let mut req = HttpResponse::build(self.redirect);
return if !query.is_empty() {
req.header(header::LOCATION, (p.to_owned() + "?" + query).as_str())
} else {
req.header(header::LOCATION, p)
}
.body(Body::Empty);
}
}
}
// append trailing slash
if self.append && !req.path().ends_with('/') {
let p = req.path().to_owned() + "/";
if router.has_route(&p) {
let p = if !query.is_empty() { p + "?" + query } else { p };
return HttpResponse::build(self.redirect)
.header(header::LOCATION, p.as_str())
.body(Body::Empty);
}
}
}
Ok(HttpResponse::new(self.not_found, Body::Empty))
}
}
#[cfg(test)]
mod tests {
use super::*;
use http::{header, Method};
use test::TestRequest;
use application::Application;
fn index(_req: HttpRequest) -> HttpResponse {
HttpResponse::new(StatusCode::OK, Body::Empty)
}
#[test]
fn test_normalize_path_trailing_slashes() {
let mut app = Application::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
.finish();
// trailing slashes
let params =
vec![("/resource1", "", StatusCode::OK),
("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY),
("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY),
("/resource2/", "", StatusCode::OK),
("/resource1?p1=1&p2=2", "", StatusCode::OK),
("/resource1/?p1=1&p2=2", "/resource1?p1=1&p2=2", StatusCode::MOVED_PERMANENTLY),
("/resource2?p1=1&p2=2", "/resource2/?p1=1&p2=2",
StatusCode::MOVED_PERMANENTLY),
("/resource2/?p1=1&p2=2", "", StatusCode::OK)
];
for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target,
r.headers().get(header::LOCATION).unwrap().to_str().unwrap());
}
}
}
#[test]
fn test_normalize_path_trailing_slashes_disabled() {
let mut app = Application::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(
NormalizePath::new(false, true, StatusCode::MOVED_PERMANENTLY)))
.finish();
// trailing slashes
let params = vec![("/resource1", StatusCode::OK),
("/resource1/", StatusCode::MOVED_PERMANENTLY),
("/resource2", StatusCode::NOT_FOUND),
("/resource2/", StatusCode::OK),
("/resource1?p1=1&p2=2", StatusCode::OK),
("/resource1/?p1=1&p2=2", StatusCode::MOVED_PERMANENTLY),
("/resource2?p1=1&p2=2", StatusCode::NOT_FOUND),
("/resource2/?p1=1&p2=2", StatusCode::OK)
];
for (path, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
assert_eq!(r.status(), code);
}
}
#[test]
fn test_normalize_path_merge_slashes() {
let mut app = Application::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource1/a/b", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
.finish();
// trailing slashes
let params = vec![
("/resource1/a/b", "", StatusCode::OK),
("/resource1/", "/resource1", StatusCode::MOVED_PERMANENTLY),
("/resource1//", "/resource1", StatusCode::MOVED_PERMANENTLY),
("//resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("//resource1//a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("//resource1//a//b//", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/resource1/a/b?p=1", "", StatusCode::OK),
("//resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource1//a//b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a//b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a//b//?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
];
for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target,
r.headers().get(header::LOCATION).unwrap().to_str().unwrap());
}
}
}
#[test]
fn test_normalize_path_merge_and_append_slashes() {
let mut app = Application::new()
.resource("/resource1", |r| r.method(Method::GET).f(index))
.resource("/resource2/", |r| r.method(Method::GET).f(index))
.resource("/resource1/a/b", |r| r.method(Method::GET).f(index))
.resource("/resource2/a/b/", |r| r.method(Method::GET).f(index))
.default_resource(|r| r.h(NormalizePath::default()))
.finish();
// trailing slashes
let params = vec![
("/resource1/a/b", "", StatusCode::OK),
("/resource1/a/b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b//", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b/", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
("/resource2/a/b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/resource2/a/b/", "", StatusCode::OK),
("//resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("///resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("///resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/////resource2/a///b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/////resource2/a///b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
("/resource1/a/b?p=1", "", StatusCode::OK),
("/resource1/a/b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource1//a//b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b/?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource1/a///b//?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
("/resource2/a/b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("//resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("///resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource2/a///b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
("/////resource2/a///b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
];
for (path, target, code) in params {
let req = app.prepare_request(TestRequest::with_uri(path).finish());
let resp = app.run(req);
let r = resp.as_response().unwrap();
assert_eq!(r.status(), code);
if !target.is_empty() {
assert_eq!(
target, r.headers().get(header::LOCATION).unwrap().to_str().unwrap());
}
}
}
}

View File

@ -126,6 +126,7 @@ pub mod fs;
pub mod ws; pub mod ws;
pub mod error; pub mod error;
pub mod header; pub mod header;
pub mod helpers;
pub mod httpcodes; pub mod httpcodes;
pub mod multipart; pub mod multipart;
pub mod middleware; pub mod middleware;
@ -157,12 +158,6 @@ pub(crate) const HAS_TLS: bool = true;
#[cfg(not(feature="tls"))] #[cfg(not(feature="tls"))]
pub(crate) const HAS_TLS: bool = false; pub(crate) const HAS_TLS: bool = false;
pub mod helpers {
//! Various helpers
pub use handler::{NormalizePath};
}
pub mod dev { pub mod dev {
//! The `actix-web` prelude for library developers //! The `actix-web` prelude for library developers
//! //!