1
0
mirror of https://github.com/actix/actix-website synced 2025-01-22 16:15:56 +01:00

URL-Dispatch is done-ish.

This commit is contained in:
Cameron Dershem 2019-06-25 18:20:36 -04:00
parent b9c78c1a58
commit be68e418b0
18 changed files with 264 additions and 164 deletions

View File

@ -10,9 +10,10 @@ URL dispatch provides a simple way for mapping URLs to handler code using a simp
matching language. If one of the patterns matches the path information associated with a request,
a particular handler object is invoked.
> A handler is a specific object that implements the `Handler` trait, defined in your
> application, that receives the request and returns a response object. More information
> is available in the [handler section][sec4handler].
> A request handler is a function that accepts zero or more parameters that can be extracted
> from a request (ie, [*impl FromRequest*][implfromrequest]) and returns a type that can
> be converted into an HttpResponse (ie, [*impl Responder*][implresponder]). More information
> is available in the [handler section][handlersection].
# Resource configuration
@ -32,40 +33,33 @@ for the same path, in that case, multiple routes register for the same resource
{{< include-example example="url-dispatch" section="main" >}}
While *App::route()* provides simple way of registering routes, to access complete resource
configuration, a different method has to be used. The [*App::resource()*][appresource] method
adds a single resource to application routing table. This method accepts a *path pattern*
and a resource configuration function.
configuration, a different method has to be used. The [*App::service()*][appservice] method
adds a single [resource][webresource] to application routing table. This method accepts a
*path pattern*, guards, and one or more routes.
{{< include-example example="url-dispatch" file="resource.rs" section="resource" >}}
The *Configuration function* has the following type:
```rust
FnOnce(&mut Resource<_>) -> ()
```
The *Configuration function* can set a name and register specific routes.
If a resource does not contain any route or does not have any matching routes, it
returns *NOT FOUND* http response.
## Configuring a Route
Resource contains a set of routes. Each route in turn has a set of predicates and a handler.
Resource contains a set of routes. Each route in turn has a set of `guards` and a handler.
New routes can be created with `Resource::route()` method which returns a reference
to new *Route* instance. By default the *route* does not contain any predicates, so matches
to new *Route* instance. By default the *route* does not contain any guards, so matches
all requests and the default handler is `HttpNotFound`.
The application routes incoming requests based on route criteria which are defined during
resource registration and route registration. Resource matches all routes it contains in
the order the routes were registered via `Resource::route()`.
> A *Route* can contain any number of *predicates* but only one handler.
> A *Route* can contain any number of *guards* but only one handler.
{{< include-example example="url-dispatch" file="cfg.rs" section="cfg" >}}
In this example, `HttpResponse::Ok()` is returned for *GET* requests.
If a request contains `Content-Type` header, the value of this header is *text/plain*,
and path equals to `/path`, Resource calls handle of the first matching route.
In this example, `HttpResponse::Ok()` is returned for *GET* requests if the request
contains `Content-Type` header, the value of this header is *text/plain*, and path
equals to `/path`.
If a resource can not match any route, a "NOT FOUND" response is returned.
@ -77,19 +71,16 @@ can be configured with a builder-like pattern. Following configuration methods a
* [*Route::method()*][routemethod] registers a method guard. Any number of guards can be
registered for each route.
* [*Route::to()*][routeto] registers handler function for this route. Only one handler
can be registered. Usually handler registration is the last config operation. Handler
function can be a function or closure and has the type `Fn(HttpRequest<S>) -> R + 'static`
can be registered. Usually handler registration is the last config operation.
* [*Route::to_async()*][routetoasync] registers an async handler function for this route.
Only one handler can be registered. Handler registration is the last config operation.
Handler function can be a function or closure and has the type
`Fn(HttpRequest<S>) -> Future<Item = HttpResponse, Error = Error> + 'static`
# Route matching
The main purpose of route configuration is to match (or not match) the request's `path`
against a URL path pattern. `path` represents the path portion of the URL that was requested.
The way that *actix* does this is very simple. When a request enters the system,
The way that *actix-web* does this is very simple. When a request enters the system,
for each resource configuration declaration present in the system, actix checks
the request's path against the pattern declared. This checking happens in the order that
the routes were declared via `App::resource()` method. If resource can not be found,
@ -261,12 +252,12 @@ A *scoped* path can contain variable path segments as resources. Consistent with
unscoped paths.
You can get variable path segments from `HttpRequest::match_info()`.
`Path` extractor also is able to extract scope level variable segments.
[`Path` extractor][pathextractor] also is able to extract scope level variable segments.
# Match information
All values representing matched path segments are available in [`HttpRequest::match_info`][matchinfo].
Specific values can be retrieved with [`Params::get()`][paramsget].
Specific values can be retrieved with [`Path::get()`][pathget].
{{< include-example example="url-dispatch" file="minfo.rs" section="minfo" >}}
@ -339,16 +330,9 @@ normalization conditions, if all are enabled, is 1) merge, 2) both merge and app
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 doesn't have one, it will be appended automatically.
If *merge* is *true*, merge multiple consecutive slashes in the path into one.
This handler designed to be used as a handler for application's *default resource*.
{{< include-example example="url-dispatch" file="norm.rs" section="norm" >}}
In this example `/resource`, `//resource///` will be redirected to `/resource/`.
In this example `//resource///` will be redirected to `/resource/`.
In this example, the path normalization handler is registered for all methods,
but you should not rely on this mechanism to redirect *POST* requests. The redirect of the
@ -385,12 +369,12 @@ and returns *true* or *false*. Formally, a guard is any object that implements t
Here is a simple guard that check that a request contains a specific *header*:
{{< include-example example="url-dispatch" file="pred.rs" section="pred" >}}
{{< include-example example="url-dispatch" file="guard.rs" section="guard" >}}
In this example, *index* handler will be called only if request contains *CONTENT-TYPE* header.
Guards have access to the application's state via `HttpRequest::data()`. Also predicates
can store extra information in [request extensions][httprequest].
Guards can not access or modify the request object, but it is possible to store extra
information in [request extensions][requestextensions].
## Modifying guard values
@ -398,7 +382,7 @@ You can invert the meaning of any predicate value by wrapping it in a `Not` pred
For example, if you want to return "METHOD NOT ALLOWED" response for all methods
except "GET":
{{< include-example example="url-dispatch" file="pred2.rs" section="pred" >}}
{{< include-example example="url-dispatch" file="guard2.rs" section="guard2" >}}
The `Any` guard accepts a list of guards and matches if any of the supplied
guards match. i.e:
@ -424,21 +408,25 @@ with `App::service()` method.
{{< include-example example="url-dispatch" file="dhandler.rs" section="default" >}}
[sec4handler]: sec-4-handler.html
[approute]: ../../actix-web/actix_web/struct.App.html#method.route
[appresource]: ../../actix-web/actix_web/struct.App.html#method.resource
[resourcehandler]: ../../actix-web/actix_web/dev/struct.ResourceHandler.html#method.route
[route]: ../../actix-web/actix_web/dev/struct.Route.html
[routeguard]: ../../actix-web/actix_web/dev/struct.Route.html#method.guard
[routemethod]: ../../actix-web/actix_web/dev/struct.Route.html#method.method
[routeto]: ../../actix-web/actix_web/dev/struct.Route.html#method.to
[routetoasync]: ../../actix-web/actix_web/dev/struct.Route.html#method.to_async
[matchinfo]: ../actix_web/struct.HttpRequest.html#method.match_info
[paramsget]: ../actix_web/dev/struct.Params.html#method.get
[pathstruct]: ../../actix-web/actix_web/struct.Path.html
[query]: ../../actix-web/actix_web/struct.Query.html
[urlfor]: ../../actix-web/actix_web/struct.HttpRequest.html#method.url_for
[urlobj]: https://docs.rs/url/1.7.0/url/struct.Url.html
[guardtrait]: ../actix_web/guard/trait.Guard.html
[guardfuncs]: ../../actix-web/actix_web/guard/index.html#functions
[httprequest]: ../../actix-web/actix_web/struct.HttpRequest.html#method.extensions
[handlersection]: ../handlers/
[approute]: https://docs.rs/actix-web/1.0.2/actix_web/struct.App.html#method.route
[appservice]: https://docs.rs/actix-web/1.0.2/actix_web/struct.App.html?search=#method.service
[webresource]: https://docs.rs/actix-web/1.0.2/actix_web/struct.Resource.html
[resourcehandler]: https://docs.rs/actix-web/1.0.2/actix_web/struct.Resource.html#method.route
[route]: https://docs.rs/actix-web/1.0.2/actix_web/struct.Route.html
[routeguard]: https://docs.rs/actix-web/1.0.2/actix_web/struct.Route.html#method.guard
[routemethod]: https://docs.rs/actix-web/1.0.2/actix_web/struct.Route.html#method.method
[routeto]: https://docs.rs/actix-web/1.0.2/actix_web/struct.Route.html#method.to
[routetoasync]: https://docs.rs/actix-web/1.0.2/actix_web/struct.Route.html#method.to_async
[matchinfo]: https://docs.rs/actix-web/1.0.2/actix_web/struct.HttpRequest.html#method.match_info
[pathget]: https://docs.rs/actix-web/1.0.2/actix_web/dev/struct.Path.html#method.get
[pathstruct]: https://docs.rs/actix-web/1.0.2/actix_web/dev/struct.Path.html
[query]: https://docs.rs/actix-web/1.0.2/actix_web/web/struct.Query.html
[urlfor]: https://docs.rs/actix-web/1.0.2/actix_web/struct.HttpRequest.html#method.url_for
[urlobj]: https://docs.rs/url/1.7.2/url/struct.Url.html
[guardtrait]: https://docs.rs/actix-web/1.0.2/actix_web/guard/trait.Guard.html
[guardfuncs]: https://docs.rs/actix-web/1.0.2/actix_web/guard/index.html#functions
[requestextensions]: https://docs.rs/actix-web/1.0.2/actix_web/struct.HttpRequest.html#method.extensions
[implfromrequest]: https://docs.rs/actix-web/1.0.2/actix_web/trait.FromRequest.html
[implresponder]: https://docs.rs/actix-web/1.0.2/actix_web/trait.Responder.html
[pathextractor]: ../extractors

View File

@ -1,14 +1,23 @@
// <cfg>
use actix_web::{guard, web, App, HttpResponse};
#[rustfmt::skip]
pub fn main() {
App::new().service(
web::resource("/path").route(
web::route()
.guard(guard::Get())
.guard(guard::Header("content-type", "text/plain"))
.to(|| HttpResponse::Ok()),
),
);
}
use actix_web::HttpServer;
HttpServer::new(|| {
// <cfg>
App::new().service(
web::resource("/path").route(
web::route()
.guard(guard::Get())
.guard(guard::Header("content-type", "text/plain"))
.to(|| HttpResponse::Ok()),
),
)
// </cfg>
})
.bind("127.0.0.1:8088")
.unwrap()
.run()
.unwrap();
}

View File

@ -1,4 +1,4 @@
use actix_web::{guard, web, App, HttpRequest, HttpResponse, Responder};
use actix_web::{guard, web, App, HttpRequest, HttpResponse, HttpServer, Responder};
fn index(_req: HttpRequest) -> impl Responder {
"Welcome!"
@ -6,12 +6,18 @@ fn index(_req: HttpRequest) -> impl Responder {
// <default>
pub fn main() {
App::new()
.service(web::resource("/").route(web::get().to(index)))
.default_service(
web::route()
.guard(guard::Not(guard::Get()))
.to(|| HttpResponse::MethodNotAllowed()),
);
HttpServer::new(|| {
App::new()
.service(web::resource("/").route(web::get().to(index)))
.default_service(
web::route()
.guard(guard::Not(guard::Get()))
.to(|| HttpResponse::MethodNotAllowed()),
)
})
.bind("127.0.0.1:8088")
.unwrap()
.run()
.unwrap();
}
// </default>

View File

@ -0,0 +1,28 @@
// <guard>
use actix_web::{
dev::RequestHead, guard::Guard, http, web, App, HttpResponse, HttpServer,
};
struct ContentTypeHeader;
impl Guard for ContentTypeHeader {
fn check(&self, req: &RequestHead) -> bool {
req.headers().contains_key(http::header::CONTENT_TYPE)
}
}
pub fn main() {
HttpServer::new(|| {
App::new().route(
"/",
web::route()
.guard(ContentTypeHeader)
.to(|| HttpResponse::Ok()),
)
})
.bind("127.0.0.1:8088")
.unwrap()
.run()
.unwrap();
}
// </guard>

View File

@ -0,0 +1,18 @@
// <guard2>
use actix_web::{guard, web, App, HttpResponse, HttpServer};
pub fn main() {
HttpServer::new(|| {
App::new().route(
"/",
web::route()
.guard(guard::Not(guard::Get()))
.to(|| HttpResponse::MethodNotAllowed()),
)
})
.bind("127.0.0.1:8088")
.unwrap()
.run()
.unwrap();
}
// </guard2>

View File

@ -1,28 +1,34 @@
pub mod cfg;
pub mod dhandler;
pub mod guard;
pub mod guard2;
pub mod minfo;
pub mod norm;
pub mod norm2;
pub mod path;
pub mod path2;
pub mod pbuf;
pub mod pred;
pub mod pred2;
pub mod resource;
pub mod scope;
pub mod url_ext;
pub mod urls;
// <main>
use actix_web::{web, App, HttpRequest, HttpResponse};
use actix_web::{web, App, HttpResponse, HttpServer};
fn index(_req: HttpRequest) -> HttpResponse {
unimplemented!()
fn index() -> HttpResponse {
HttpResponse::Ok().body("Hello")
}
fn main() {
App::new()
.route("/user/{name}", web::get().to(index))
.route("/user/{name}", web::post().to(index));
HttpServer::new(|| {
App::new()
.route("/", web::get().to(index))
.route("/user", web::post().to(index))
})
.bind("127.0.0.1:8088")
.unwrap()
.run()
.unwrap();
}
// </main>

View File

@ -9,8 +9,16 @@ fn index(req: HttpRequest) -> Result<String> {
}
pub fn main() {
App::new()
.route("/a/{v1}/{v2}/", web::get().to(index))
.route("", web::get().to(|| actix_web::HttpResponse::Ok()));
use actix_web::HttpServer;
HttpServer::new(|| {
App::new()
.route("/a/{v1}/{v2}/", web::get().to(index))
.route("", web::get().to(|| actix_web::HttpResponse::Ok()))
})
.bind("127.0.0.1:8088")
.unwrap()
.run()
.unwrap();
}
// </minfo>

View File

@ -1,14 +1,21 @@
// <norm>
use actix_web::{middleware, web, App};
use actix_web::{middleware, web, App, HttpResponse};
fn index() -> HttpResponse {
HttpResponse::Ok().body("Hello")
}
pub fn main() {
App::new()
.wrap(middleware::NormalizePath)
.route("/", web::get().to(index));
use actix_web::HttpServer;
HttpServer::new(|| {
App::new()
.wrap(middleware::NormalizePath)
.route("/resource/", web::to(index))
})
.bind("127.0.0.1:8088")
.unwrap()
.run()
.unwrap();
}
// </norm>
use actix_web::HttpRequest;
fn index(_req: HttpRequest) -> String {
unimplemented!()
}

View File

@ -1,16 +1,22 @@
// <norm>
use actix_web::{http::Method, middleware, web, App};
use actix_web::{http::Method, middleware, web, App, HttpServer};
pub fn main() {
App::new()
.wrap(middleware::NormalizePath)
.route("/resource/", web::get().to(index))
.default_service(web::route().method(Method::GET));
HttpServer::new(|| {
App::new()
.wrap(middleware::NormalizePath)
.route("/resource/", web::get().to(index))
.default_service(web::route().method(Method::GET))
})
.bind("127.0.0.1:8088")
.unwrap()
.run()
.unwrap();
}
// </norm>
use actix_web::HttpRequest;
use actix_web::HttpResponse;
fn index(_req: HttpRequest) -> String {
unimplemented!()
fn index() -> HttpResponse {
HttpResponse::Ok().body("Hello")
}

View File

@ -1,15 +1,22 @@
// <path>
use actix_web::{web, App, Result};
// extract path info using serde
fn index(info: web::Path<(String, u32)>) -> Result<String> {
Ok(format!("Welcome {}! id: {}", info.0, info.1))
}
pub fn main() {
App::new().route(
"/{username}/{id}/index.html", // <- define path parameters
web::get().to(index),
);
use actix_web::HttpServer;
HttpServer::new(|| {
App::new().route(
"/{username}/{id}/index.html", // <- define path parameters
web::get().to(index),
)
})
.bind("127.0.0.1:8088")
.unwrap()
.run()
.unwrap();
}
// </path>

View File

@ -13,9 +13,17 @@ fn index(info: web::Path<Info>) -> Result<String> {
}
pub fn main() {
App::new().route(
"/{username}/index.html", // <- define path parameters
web::get().to(index),
);
use actix_web::HttpServer;
HttpServer::new(|| {
App::new().route(
"/{username}/index.html", // <- define path parameters
web::get().to(index),
)
})
.bind("127.0.0.1:8088")
.unwrap()
.run()
.unwrap();
}
// </path>

View File

@ -8,6 +8,12 @@ fn index(req: HttpRequest) -> Result<String> {
}
pub fn main() {
App::new().route(r"/a/{tail:.*}", web::get().to(index));
use actix_web::HttpServer;
HttpServer::new(|| App::new().route(r"/a/{tail:.*}", web::get().to(index)))
.bind("127.0.0.1:8088")
.unwrap()
.run()
.unwrap();
}
// </pbuf>

View File

@ -1,20 +0,0 @@
// <pred>
use actix_web::{dev::RequestHead, guard::Guard, http, web, App, HttpResponse};
struct ContentTypeHeader;
impl Guard for ContentTypeHeader {
fn check(&self, req: &RequestHead) -> bool {
req.headers().contains_key(http::header::CONTENT_TYPE)
}
}
pub fn main() {
App::new().route(
"",
web::route()
.guard(ContentTypeHeader)
.to(|| HttpResponse::Ok()),
);
}
// </pred>

View File

@ -1,12 +0,0 @@
// <pred>
use actix_web::{guard, web, App, HttpResponse};
pub fn main() {
App::new().route(
"/",
web::route()
.guard(guard::Not(guard::Get()))
.to(|| HttpResponse::MethodNotAllowed()),
);
}
// </pred>

View File

@ -1,15 +1,19 @@
// <resource>
use actix_web::{web, App, HttpRequest, HttpResponse};
use actix_web::{guard, web, App, HttpResponse};
fn index(_req: HttpRequest) -> HttpResponse {
unimplemented!()
fn index() -> HttpResponse {
HttpResponse::Ok().body("Hello")
}
pub fn main() {
App::new()
.service(web::resource("/prefix").to(index))
.service(
web::resource("/user/{name}").route(web::get().to(|| HttpResponse::Ok())),
web::resource("/user/{name}")
.name("user_detail")
.guard(guard::Header("content-type", "application/json"))
.route(web::get().to(|| HttpResponse::Ok()))
.route(web::put().to(|| HttpResponse::Ok())),
);
}
// </resource>

View File

@ -1,11 +1,25 @@
use actix_web::{web, App, HttpRequest, HttpResponse};
use actix_web::{web, App, HttpResponse, HttpServer};
// <scope>
fn show_users(_req: HttpRequest) -> HttpResponse {
unimplemented!()
fn show_users() -> HttpResponse {
HttpResponse::Ok().body("Show users")
}
fn user_detail(_path: web::Path<(u32,)>) -> HttpResponse {
HttpResponse::Ok().body("User detail")
}
pub fn main() {
App::new().service(web::scope("/users").route("/show", web::get().to(show_users)));
HttpServer::new(|| {
App::new().service(
web::scope("/users")
.route("/show", web::get().to(show_users))
.route("/show/{id}", web::get().to(user_detail)),
)
})
.bind("127.0.0.1:8088")
.unwrap()
.run()
.unwrap();
}
// </scope>

View File

@ -9,9 +9,17 @@ fn index(req: HttpRequest) -> impl Responder {
}
pub fn main() {
App::new()
.route("/index.html", web::get().to(index))
.external_resource("youtube", "https://youtube.com/watch/{video_id}")
.route("/", actix_web::web::get().to(index));
use actix_web::HttpServer;
HttpServer::new(|| {
App::new()
.route("/", web::get().to(index))
.external_resource("youtube", "https://youtube.com/watch/{video_id}")
.route("/", actix_web::web::get().to(index))
})
.bind("127.0.0.1:8088")
.unwrap()
.run()
.unwrap();
}
// </ext>

View File

@ -3,19 +3,28 @@ use actix_web::{guard, http::header, web, App, HttpRequest, HttpResponse, Result
fn index(req: HttpRequest) -> Result<HttpResponse> {
let url = req.url_for("foo", &["1", "2", "3"])?; // <- generate url for "foo" resource
Ok(HttpResponse::Found()
.header(header::LOCATION, url.as_str())
.finish())
}
pub fn main() {
App::new()
.service(
web::resource("/test/{a}/{b}/{c}")
.name("foo") // <- set resource name, then it could be used in `url_for`
.guard(guard::Get())
.to(|| HttpResponse::Ok()),
)
.route("/test/", web::get().to(index));
use actix_web::HttpServer;
HttpServer::new(|| {
App::new()
.service(
web::resource("/test/{a}/{b}/{c}")
.name("foo") // <- set resource name, then it could be used in `url_for`
.guard(guard::Get())
.to(|| HttpResponse::Ok()),
)
.route("/test/", web::get().to(index))
})
.bind("127.0.0.1:8088")
.unwrap()
.run()
.unwrap();
}
// </url>