mirror of
https://github.com/actix/actix-web.git
synced 2024-11-13 04:11:09 +00:00
parent
34e5c7c799
commit
adf9935841
5 changed files with 109 additions and 117 deletions
|
@ -29,26 +29,25 @@ const REGEX_FLAGS: &str = "(?s-m)";
|
|||
///
|
||||
///
|
||||
/// # Pattern Format and Matching Behavior
|
||||
///
|
||||
/// Resource pattern is defined as a string of zero or more _segments_ where each segment is
|
||||
/// preceded by a slash `/`.
|
||||
///
|
||||
/// This means that pattern string __must__ either be empty or begin with a slash (`/`).
|
||||
/// This also implies that a trailing slash in pattern defines an empty segment.
|
||||
/// For example, the pattern `"/user/"` has two segments: `["user", ""]`
|
||||
/// This means that pattern string __must__ either be empty or begin with a slash (`/`). This also
|
||||
/// implies that a trailing slash in pattern defines an empty segment. For example, the pattern
|
||||
/// `"/user/"` has two segments: `["user", ""]`
|
||||
///
|
||||
/// A key point to underhand is that `ResourceDef` matches segments, not strings.
|
||||
/// It matches segments individually.
|
||||
/// For example, the pattern `/user/` is not considered a prefix for the path `/user/123/456`,
|
||||
/// because the second segment doesn't match: `["user", ""]` vs `["user", "123", "456"]`.
|
||||
/// A key point to understand is that `ResourceDef` matches segments, not strings. Segments are
|
||||
/// matched individually. For example, the pattern `/user/` is not considered a prefix for the path
|
||||
/// `/user/123/456`, because the second segment doesn't match: `["user", ""]`
|
||||
/// vs `["user", "123", "456"]`.
|
||||
///
|
||||
/// This definition is consistent with the definition of absolute URL path in
|
||||
/// [RFC 3986 (section 3.3)](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3)
|
||||
/// [RFC 3986 §3.3](https://datatracker.ietf.org/doc/html/rfc3986#section-3.3)
|
||||
///
|
||||
///
|
||||
/// # Static Resources
|
||||
/// A static resource is the most basic type of definition. Pass a pattern to
|
||||
/// [new][Self::new]. Conforming paths must match the pattern exactly.
|
||||
/// A static resource is the most basic type of definition. Pass a pattern to [new][Self::new].
|
||||
/// Conforming paths must match the pattern exactly.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
|
@ -63,7 +62,6 @@ const REGEX_FLAGS: &str = "(?s-m)";
|
|||
/// assert!(!resource.is_match("/search"));
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// # Dynamic Segments
|
||||
/// Also known as "path parameters". Resources can define sections of a pattern that be extracted
|
||||
/// from a conforming path, if it conforms to (one of) the resource pattern(s).
|
||||
|
@ -102,15 +100,15 @@ const REGEX_FLAGS: &str = "(?s-m)";
|
|||
/// assert_eq!(path.get("id").unwrap(), "123");
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// # Prefix Resources
|
||||
/// A prefix resource is defined as pattern that can match just the start of a path, up to a
|
||||
/// segment boundary.
|
||||
///
|
||||
/// Prefix patterns with a trailing slash may have an unexpected, though correct, behavior.
|
||||
/// They define and therefore require an empty segment in order to match. Examples are given below.
|
||||
/// They define and therefore require an empty segment in order to match. It is easier to understand
|
||||
/// this behavior after reading the [matching behavior section]. Examples are given below.
|
||||
///
|
||||
/// Empty pattern matches any path as a prefix.
|
||||
/// The empty pattern (`""`), as a prefix, matches any path.
|
||||
///
|
||||
/// Prefix resources can contain dynamic segments.
|
||||
///
|
||||
|
@ -130,7 +128,6 @@ const REGEX_FLAGS: &str = "(?s-m)";
|
|||
/// assert!(!resource.is_match("/user/123"));
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// # Custom Regex Segments
|
||||
/// Dynamic segments can be customised to only match a specific regular expression. It can be
|
||||
/// helpful to do this if resource definitions would otherwise conflict and cause one to
|
||||
|
@ -158,7 +155,6 @@ const REGEX_FLAGS: &str = "(?s-m)";
|
|||
/// assert!(!resource.is_match("/user/abc"));
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// # Tail Segments
|
||||
/// As a shortcut to defining a custom regex for matching _all_ remaining characters (not just those
|
||||
/// up until a `/` character), there is a special pattern to match (and capture) the remaining
|
||||
|
@ -179,7 +175,6 @@ const REGEX_FLAGS: &str = "(?s-m)";
|
|||
/// assert_eq!(path.get("tail").unwrap(), "main/LICENSE");
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// # Multi-Pattern Resources
|
||||
/// For resources that can map to multiple distinct paths, it may be suitable to use
|
||||
/// multi-pattern resources by passing an array/vec to [`new`][Self::new]. They will be combined
|
||||
|
@ -198,7 +193,6 @@ const REGEX_FLAGS: &str = "(?s-m)";
|
|||
/// assert!(resource.is_match("/index"));
|
||||
/// ```
|
||||
///
|
||||
///
|
||||
/// # Trailing Slashes
|
||||
/// It should be noted that this library takes no steps to normalize intra-path or trailing slashes.
|
||||
/// As such, all resource definitions implicitly expect a pre-processing step to normalize paths if
|
||||
|
@ -212,6 +206,8 @@ const REGEX_FLAGS: &str = "(?s-m)";
|
|||
/// assert!(!ResourceDef::new("/root/").is_match("/root"));
|
||||
/// assert!(!ResourceDef::prefix("/root/").is_match("/root"));
|
||||
/// ```
|
||||
///
|
||||
/// [matching behavior section]: #pattern-format-and-matching-behavior
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ResourceDef {
|
||||
id: u16,
|
||||
|
@ -279,7 +275,7 @@ impl ResourceDef {
|
|||
/// ```
|
||||
pub fn new<T: IntoPatterns>(paths: T) -> Self {
|
||||
profile_method!(new);
|
||||
Self::new2(paths, false)
|
||||
Self::construct(paths, false)
|
||||
}
|
||||
|
||||
/// Constructs a new resource definition using a pattern that performs prefix matching.
|
||||
|
@ -292,7 +288,7 @@ impl ResourceDef {
|
|||
/// resource definition with a tail segment; use [`new`][Self::new] in this case.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if path regex pattern is malformed.
|
||||
/// Panics if path pattern is malformed.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
|
@ -307,14 +303,14 @@ impl ResourceDef {
|
|||
/// ```
|
||||
pub fn prefix<T: IntoPatterns>(paths: T) -> Self {
|
||||
profile_method!(prefix);
|
||||
ResourceDef::new2(paths, true)
|
||||
ResourceDef::construct(paths, true)
|
||||
}
|
||||
|
||||
/// Constructs a new resource definition using a string pattern that performs prefix matching,
|
||||
/// inserting a `/` to beginning of the pattern if absent and pattern is not empty.
|
||||
/// ensuring a leading `/` if pattern is not empty.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if path regex pattern is malformed.
|
||||
/// Panics if path pattern is malformed.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
|
@ -515,8 +511,8 @@ impl ResourceDef {
|
|||
.collect::<Vec<_>>();
|
||||
|
||||
match patterns.len() {
|
||||
1 => ResourceDef::new2(&patterns[0], other.is_prefix()),
|
||||
_ => ResourceDef::new2(patterns, other.is_prefix()),
|
||||
1 => ResourceDef::construct(&patterns[0], other.is_prefix()),
|
||||
_ => ResourceDef::construct(patterns, other.is_prefix()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -881,8 +877,8 @@ impl ResourceDef {
|
|||
}
|
||||
}
|
||||
|
||||
fn new2<T: IntoPatterns>(paths: T, is_prefix: bool) -> Self {
|
||||
profile_method!(new2);
|
||||
fn construct<T: IntoPatterns>(paths: T, is_prefix: bool) -> Self {
|
||||
profile_method!(construct);
|
||||
|
||||
let patterns = paths.patterns();
|
||||
let (pat_type, segments) = match &patterns {
|
||||
|
@ -1814,7 +1810,7 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn prefix_plus_tail_match_is_allowed() {
|
||||
fn prefix_plus_tail_match_disallowed() {
|
||||
ResourceDef::prefix("/user/{id}*");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,28 +23,25 @@ use crate::{
|
|||
BoxError, Error, FromRequest, HttpResponse, Responder,
|
||||
};
|
||||
|
||||
/// *Resource* is an entry in resources table which corresponds to requested URL.
|
||||
/// A collection of [`Route`]s that respond to the same path pattern.
|
||||
///
|
||||
/// Resource in turn has at least one route.
|
||||
/// Route consists of an handlers objects and list of guards
|
||||
/// (objects that implement `Guard` trait).
|
||||
/// Resources and routes uses builder-like pattern for configuration.
|
||||
/// During request handling, resource object iterate through all routes
|
||||
/// and check guards for specific route, if request matches all
|
||||
/// guards, route considered matched and route handler get called.
|
||||
/// Resource in turn has at least one route. Route consists of an handlers objects and list of
|
||||
/// guards (objects that implement `Guard` trait). Resources and routes uses builder-like pattern
|
||||
/// for configuration. During request handling, resource object iterate through all routes and check
|
||||
/// guards for specific route, if request matches all guards, route considered matched and route
|
||||
/// handler get called.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_web::{web, App, HttpResponse};
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/")
|
||||
/// .route(web::get().to(|| HttpResponse::Ok())));
|
||||
/// }
|
||||
/// let app = App::new().service(
|
||||
/// web::resource("/")
|
||||
/// .route(web::get().to(|| HttpResponse::Ok())));
|
||||
/// ```
|
||||
///
|
||||
/// If no matching route could be found, *405* response code get returned.
|
||||
/// Default behavior could be overridden with `default_resource()` method.
|
||||
/// If no matching route could be found, *405* response code get returned. Default behavior could be
|
||||
/// overridden with `default_resource()` method.
|
||||
pub struct Resource<T = ResourceEndpoint, B = BoxBody> {
|
||||
endpoint: T,
|
||||
rdef: Patterns,
|
||||
|
|
|
@ -15,10 +15,10 @@ use crate::{
|
|||
BoxError, Error, FromRequest, HttpResponse, Responder,
|
||||
};
|
||||
|
||||
/// Resource route definition
|
||||
/// A request handler with [guards](guard).
|
||||
///
|
||||
/// Route uses builder-like pattern for configuration.
|
||||
/// If handler is not explicitly set, default *404 Not Found* handler is used.
|
||||
/// Route uses a builder-like pattern for configuration. If handler is not set, a `404 Not Found`
|
||||
/// handler is used.
|
||||
pub struct Route {
|
||||
service: BoxedHttpServiceFactory,
|
||||
guards: Rc<Vec<Box<dyn Guard>>>,
|
||||
|
|
126
src/scope.rs
126
src/scope.rs
|
@ -27,34 +27,36 @@ use crate::{
|
|||
|
||||
type Guards = Vec<Box<dyn Guard>>;
|
||||
|
||||
/// Resources scope.
|
||||
/// A collection of [`Route`]s, [`Resource`]s, or other services that share a common path prefix.
|
||||
///
|
||||
/// Scope is a set of resources with common root path.
|
||||
/// Scopes collect multiple paths under a common path prefix.
|
||||
/// Scope path can contain variable path segments as resources.
|
||||
/// Scope prefix is always complete path segment, i.e `/app` would
|
||||
/// be converted to a `/app/` and it would not match `/app` path.
|
||||
/// The `Scope`'s path can contain [dynamic segments]. The dynamic segments can be extracted from
|
||||
/// requests using the [`Path`](crate::web::Path) extractor or
|
||||
/// with [`HttpRequest::match_info()`](crate::HttpRequest::match_info).
|
||||
///
|
||||
/// You can get variable path segments from `HttpRequest::match_info()`.
|
||||
/// `Path` extractor also is able to extract scope level variable segments.
|
||||
/// # Avoid Trailing Slashes
|
||||
/// Avoid using trailing slashes in the scope prefix (e.g., `web::scope("/scope/")`). It will almost
|
||||
/// certainly not have the expected behavior. See the [documentation on resource definitions][pat]
|
||||
/// to understand why this is the case and how to correctly construct scope/prefix definitions.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_web::{web, App, HttpResponse};
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::scope("/{project_id}/")
|
||||
/// .service(web::resource("/path1").to(|| async { "OK" }))
|
||||
/// .service(web::resource("/path2").route(web::get().to(|| HttpResponse::Ok())))
|
||||
/// .service(web::resource("/path3").route(web::head().to(HttpResponse::MethodNotAllowed)))
|
||||
/// );
|
||||
/// }
|
||||
/// let app = App::new().service(
|
||||
/// web::scope("/{project_id}/")
|
||||
/// .service(web::resource("/path1").to(|| async { "OK" }))
|
||||
/// .service(web::resource("/path2").route(web::get().to(|| HttpResponse::Ok())))
|
||||
/// .service(web::resource("/path3").route(web::head().to(HttpResponse::MethodNotAllowed)))
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// In the above example three routes get registered:
|
||||
/// * /{project_id}/path1 - responds to all http method
|
||||
/// * /{project_id}/path2 - `GET` requests
|
||||
/// * /{project_id}/path3 - `HEAD` requests
|
||||
/// - /{project_id}/path1 - responds to all HTTP methods
|
||||
/// - /{project_id}/path2 - responds to `GET` requests
|
||||
/// - /{project_id}/path3 - responds to `HEAD` requests
|
||||
///
|
||||
/// [pat]: crate::dev::ResourceDef#prefix-resources
|
||||
/// [dynamic segments]: crate::dev::ResourceDef#dynamic-segments
|
||||
pub struct Scope<T = ScopeEndpoint, B = BoxBody> {
|
||||
endpoint: T,
|
||||
rdef: String,
|
||||
|
@ -106,16 +108,14 @@ where
|
|||
/// "Welcome!"
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::scope("/app")
|
||||
/// .guard(guard::Header("content-type", "text/plain"))
|
||||
/// .route("/test1", web::get().to(index))
|
||||
/// .route("/test2", web::post().to(|r: HttpRequest| {
|
||||
/// HttpResponse::MethodNotAllowed()
|
||||
/// }))
|
||||
/// );
|
||||
/// }
|
||||
/// let app = App::new().service(
|
||||
/// web::scope("/app")
|
||||
/// .guard(guard::Header("content-type", "text/plain"))
|
||||
/// .route("/test1", web::get().to(index))
|
||||
/// .route("/test2", web::post().to(|r: HttpRequest| {
|
||||
/// HttpResponse::MethodNotAllowed()
|
||||
/// }))
|
||||
/// );
|
||||
/// ```
|
||||
pub fn guard<G: Guard + 'static>(mut self, guard: G) -> Self {
|
||||
self.guards.push(Box::new(guard));
|
||||
|
@ -186,15 +186,13 @@ where
|
|||
/// );
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .wrap(middleware::Logger::default())
|
||||
/// .service(
|
||||
/// web::scope("/api")
|
||||
/// .configure(config)
|
||||
/// )
|
||||
/// .route("/index.html", web::get().to(|| HttpResponse::Ok()));
|
||||
/// }
|
||||
/// let app = App::new()
|
||||
/// .wrap(middleware::Logger::default())
|
||||
/// .service(
|
||||
/// web::scope("/api")
|
||||
/// .configure(config)
|
||||
/// )
|
||||
/// .route("/index.html", web::get().to(|| HttpResponse::Ok()));
|
||||
/// ```
|
||||
pub fn configure<F>(mut self, cfg_fn: F) -> Self
|
||||
where
|
||||
|
@ -233,13 +231,11 @@ where
|
|||
/// "Welcome!"
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::scope("/app").service(
|
||||
/// web::scope("/v1")
|
||||
/// .service(web::resource("/test1").to(index)))
|
||||
/// );
|
||||
/// }
|
||||
/// let app = App::new().service(
|
||||
/// web::scope("/app").service(
|
||||
/// web::scope("/v1")
|
||||
/// .service(web::resource("/test1").to(index)))
|
||||
/// );
|
||||
/// ```
|
||||
pub fn service<F>(mut self, factory: F) -> Self
|
||||
where
|
||||
|
@ -263,13 +259,11 @@ where
|
|||
/// "Welcome!"
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::scope("/app")
|
||||
/// .route("/test1", web::get().to(index))
|
||||
/// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed()))
|
||||
/// );
|
||||
/// }
|
||||
/// let app = App::new().service(
|
||||
/// web::scope("/app")
|
||||
/// .route("/test1", web::get().to(index))
|
||||
/// .route("/test2", web::post().to(|| HttpResponse::MethodNotAllowed()))
|
||||
/// );
|
||||
/// ```
|
||||
pub fn route(self, path: &str, mut route: Route) -> Self {
|
||||
self.service(
|
||||
|
@ -355,21 +349,19 @@ where
|
|||
/// "Welcome!"
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new().service(
|
||||
/// web::scope("/app")
|
||||
/// .wrap_fn(|req, srv| {
|
||||
/// let fut = srv.call(req);
|
||||
/// async {
|
||||
/// let mut res = fut.await?;
|
||||
/// res.headers_mut().insert(
|
||||
/// CONTENT_TYPE, HeaderValue::from_static("text/plain"),
|
||||
/// );
|
||||
/// Ok(res)
|
||||
/// }
|
||||
/// })
|
||||
/// .route("/index.html", web::get().to(index)));
|
||||
/// }
|
||||
/// let app = App::new().service(
|
||||
/// web::scope("/app")
|
||||
/// .wrap_fn(|req, srv| {
|
||||
/// let fut = srv.call(req);
|
||||
/// async {
|
||||
/// let mut res = fut.await?;
|
||||
/// res.headers_mut().insert(
|
||||
/// CONTENT_TYPE, HeaderValue::from_static("text/plain"),
|
||||
/// );
|
||||
/// Ok(res)
|
||||
/// }
|
||||
/// })
|
||||
/// .route("/index.html", web::get().to(index)));
|
||||
/// ```
|
||||
pub fn wrap_fn<F, R, B1>(
|
||||
self,
|
||||
|
|
13
src/web.rs
13
src/web.rs
|
@ -52,11 +52,16 @@ pub fn resource<T: IntoPatterns>(path: T) -> Resource {
|
|||
/// Scopes collect multiple paths under a common path prefix. The scope's path can contain dynamic
|
||||
/// path segments.
|
||||
///
|
||||
/// # Avoid Trailing Slashes
|
||||
/// Avoid using trailing slashes in the scope prefix (e.g., `web::scope("/scope/")`). It will almost
|
||||
/// certainly not have the expected behavior. See the [documentation on resource definitions][pat]
|
||||
/// to understand why this is the case and how to correctly construct scope/prefix definitions.
|
||||
///
|
||||
/// # Examples
|
||||
/// In this example, three routes are set up (and will handle any method):
|
||||
/// * `/{project_id}/path1`
|
||||
/// * `/{project_id}/path2`
|
||||
/// * `/{project_id}/path3`
|
||||
/// - `/{project_id}/path1`
|
||||
/// - `/{project_id}/path2`
|
||||
/// - `/{project_id}/path3`
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::{web, App, HttpResponse};
|
||||
|
@ -68,6 +73,8 @@ pub fn resource<T: IntoPatterns>(path: T) -> Resource {
|
|||
/// .service(web::resource("/path3").to(|| HttpResponse::MethodNotAllowed()))
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// [pat]: crate::dev::ResourceDef#prefix-resources
|
||||
pub fn scope(path: &str) -> Scope {
|
||||
Scope::new(path)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue