18 KiB
URL Dispatch
URL dispatch provides a simple way to map URLs to Handler
code using a simple pattern matching
language. Regex crate and it's
RegexSet is beeing used for
pattern matching. 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
Handler
trait, defined in your application, that receives the request and returns
a response object. More informatin is available in handler section.
Resource configuration
Resource configuraiton is the act of adding a new resource to an application. A resource has a name, which acts as an identifier to be used for URL generation. The name also allows developers to add routes to existing resources. A resource also has a pattern, meant to match against the PATH portion of a URL (the portion following the scheme and port, e.g., /foo/bar in the URL http://localhost:8080/foo/bar).
The Application::resource methods add a single resource to application routing table. This method accepts path pattern and resource configuration funnction.
# extern crate actix_web;
# use actix_web::*;
# use actix_web::httpcodes::*;
#
# fn index(req: HttpRequest) -> HttpResponse {
# unimplemented!()
# }
#
fn main() {
Application::new()
.resource("/prefix", |r| r.f(index))
.resource("/user/{name}",
|r| r.method(Method::GET).f(|req| HTTPOk))
.finish();
}
Configuraiton function has following type:
FnOnce(&mut Resource<_>) -> ()
Configration function can set name and register specific routes. If resource does not contain any route or does not have any matching routes it returns NOT FOUND http resources.
Configuring a Route
Resource contains set of routes. Each route in turn has set of predicates and handler.
New route could be crearted with Resource::route()
method which returns reference
to new Route instance. By default route does not contain any predicates, so matches
all requests and default handler is HTTPNotFound
.
Application routes incoming requests based on route criteria which is defined during
resource registration and route registration. Resource matches all routes it contains in
the order that the routes were registered via Resource::route()
. Route can contain
any number of predicates but only one handler.
# extern crate actix_web;
# use actix_web::*;
# use actix_web::httpcodes::*;
fn main() {
Application::new()
.resource("/path", |resource|
resource.route()
.p(pred::Get())
.p(pred::Header("content-type", "text/plain"))
.f(|req| HTTPOk)
)
.finish();
}
In this example index
get called for GET request,
if request contains Content-Type
header and value of this header is text/plain
and path equals to /test
. Resource calls handle of the first matches route.
If resource can not match any route "NOT FOUND" response get returned.
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, for each resource configuration registration present in the system, actix checks the request's path against the pattern declared. Regex crate and it's RegexSet is beeing used for pattern matching. If resource could not be found, default resource get used as matched resource.
When a route configuration is declared, it may contain route predicate arguments. All route
predicates associated with a route declaration must be true
for the route configuration to
be used for a given request during a check. If any predicate in the set of route predicate
arguments provided to a route configuration returns false
during a check, that route is
skipped and route matching continues through the ordered set of routes.
If any route matches, the route matching process stops and the handler associated with route get invoked.
If no route matches after all route patterns are exhausted, NOT FOUND response get returned.
Resource pattern syntax
The syntax of the pattern matching language used by the actix in the pattern argument is straightforward.
The pattern used in route configuration may start with a slash character. If the pattern does not start with a slash character, an implicit slash will be prepended to it at matching time. For example, the following patterns are equivalent:
{foo}/bar/baz
and:
/{foo}/bar/baz
A variable part(replacement marker) is specified in the form {identifier},
where this means "accept any characters up to the next slash character and use this
as the name in the HttpRequest.match_info
object".
A replacement marker in a pattern matches the regular expression [^{}/]+
.
A match_info is the Params
object representing the dynamic parts extracted from a
URL based on the routing pattern. It is available as request.match_info. For example, the
following pattern defines one literal segment (foo) and two replacement markers (baz, and bar):
foo/{baz}/{bar}
The above pattern will match these URLs, generating the following match information:
foo/1/2 -> Params {'baz':'1', 'bar':'2'}
foo/abc/def -> Params {'baz':'abc', 'bar':'def'}
It will not match the following patterns however:
foo/1/2/ -> No match (trailing slash)
bar/abc/def -> First segment literal mismatch
The match for a segment replacement marker in a segment will be done only up to the first non-alphanumeric character in the segment in the pattern. So, for instance, if this route pattern was used:
foo/{name}.html
The literal path /foo/biz.html will match the above route pattern, and the match result
will be Params{'name': 'biz'}
. However, the literal path /foo/biz will not match,
because it does not contain a literal .html at the end of the segment represented
by {name}.html (it only contains biz, not biz.html).
To capture both segments, two replacement markers can be used:
foo/{name}.{ext}
The literal path /foo/biz.html will match the above route pattern, and the match result will be Params{'name': 'biz', 'ext': 'html'}. This occurs because there is a literal part of . (period) between the two replacement markers {name} and {ext}.
Replacement markers can optionally specify a regular expression which will be used to decide whether a path segment should match the marker. To specify that a replacement marker should match only a specific set of characters as defined by a regular expression, you must use a slightly extended form of replacement marker syntax. Within braces, the replacement marker name must be followed by a colon, then directly thereafter, the regular expression. The default regular expression associated with a replacement marker [^/]+ matches one or more characters which are not a slash. For example, under the hood, the replacement marker {foo} can more verbosely be spelled as {foo:[^/]+}. You can change this to be an arbitrary regular expression to match an arbitrary sequence of characters, such as {foo:\d+} to match only digits.
Segments must contain at least one character in order to match a segment replacement marker. For example, for the URL /abc/:
- /abc/{foo} will not match.
- /{foo}/ will match.
Note that path will be URL-unquoted and decoded into valid unicode string before matching pattern and values representing matched path segments will be URL-unquoted too. So for instance, the following pattern:
foo/{bar}
When matching the following URL:
http://example.com/foo/La%20Pe%C3%B1a
The matchdict will look like so (the value is URL-decoded):
Params{'bar': 'La Pe\xf1a'}
Literal strings in the path segment should represent the decoded value of the path provided to actix. You don't want to use a URL-encoded value in the pattern. For example, rather than this:
/Foo%20Bar/{baz}
You'll want to use something like this:
/Foo Bar/{baz}
It is possible to get "tail match". For this purpose custom regex has to be used.
foo/{bar}/{tail:.*}
The above pattern will match these URLs, generating the following match information:
foo/1/2/ -> Params{'bar':'1', 'tail': '2/'}
foo/abc/def/a/b/c -> Params{'bar':u'abc', 'tail': 'def/a/b/c'}
Match information
All values representing matched path segments are available in
HttpRequest::match_info
.
Specific value can be received with
Params::get()
method.
Any matched parameter can be deserialized into specific type if this type
implements FromParam
trait. For example most of standard integer types
implements FromParam
trait. i.e.:
# extern crate actix_web;
use actix_web::*;
fn index(req: HttpRequest) -> Result<String> {
let v1: u8 = req.match_info().query("v1")?;
let v2: u8 = req.match_info().query("v2")?;
Ok(format!("Values {} {}", v1, v2))
}
fn main() {
Application::new()
.resource(r"/a/{v1}/{v2}/", |r| r.f(index))
.finish();
}
For this example for path '/a/1/2/', values v1 and v2 will resolve to "1" and "2".
It is possible to create a PathBuf
from a tail path parameter. The returned PathBuf
is
percent-decoded. If a segment is equal to "..", the previous segment (if
any) is skipped.
For security purposes, if a segment meets any of the following conditions,
an Err
is returned indicating the condition met:
- Decoded segment starts with any of:
.
(except..
),*
- Decoded segment ends with any of:
:
,>
,<
- Decoded segment contains any of:
/
- On Windows, decoded segment contains any of: ''
- Percent-encoding results in invalid UTF8.
As a result of these conditions, a PathBuf
parsed from request path parameter is
safe to interpolate within, or use as a suffix of, a path without additional checks.
# extern crate actix_web;
use actix_web::*;
use std::path::PathBuf;
fn index(req: HttpRequest) -> Result<String> {
let path: PathBuf = req.match_info().query("tail")?;
Ok(format!("Path {:?}", path))
}
fn main() {
Application::new()
.resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index))
.finish();
}
List of FromParam
implementation could be found in
api docs
Generating resource URLs
Use the HttpRequest.url_for() method to generate URLs based on resource patterns. For example, if you've configured a resource with the name "foo" and the pattern "{a}/{b}/{c}", you might do this.
# extern crate actix_web;
# use actix_web::*;
# use actix_web::httpcodes::*;
#
fn index(req: HttpRequest) -> HttpResponse {
let url = req.url_for("foo", &["1", "2", "3"]);
HTTPOk.into()
}
# fn main() {}
This would return something like the string http://example.com/1/2/3 (at least if
the current protocol and hostname implied http://example.com).
url_for()
method return Url object so you
can modify this url (add query parameters, anchor, etc).
url_for()
could be called only for named resources otherwise error get returned.
External resources
Resources that are valid URLs, could be registered as external resources. They are useful for URL generation purposes only and are never considered for matching at request time.
# extern crate actix_web;
use actix_web::*;
fn index(mut req: HttpRequest) -> Result<HttpResponse> {
let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
Ok(httpcodes::HTTPOk.into())
}
fn main() {
let app = Application::new()
.resource("/index.html", |r| r.f(index))
.external_resource("youtube", "https://youtube.com/watch/{video_id}")
.finish();
}
Path normalization and redirecting to slash-appended routes
By normalizing it means:
- Add a trailing slash to 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.
# 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("/resource/", |r| r.f(index))
.default_resource(|r| r.h(NormalizePath::default()))
.finish();
}
In this example /resource
, //resource///
will be redirected to /resource/
url.
In this example path normalization handler get registered for all method, but you should not rely on this mechanism to redirect POST requests. The redirect of the slash-appending Not Found will turn a POST request into a GET, losing any POST data in the original request.
It is possible to register path normalization only for GET requests only
# 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("/resource/", |r| r.f(index))
.default_resource(|r| r.method(Method::GET).h(NormalizePath::default()))
.finish();
}
Using a Application Prefix to Compose Applications
The Applicaiton::prefix()
" method allows to set specific application prefix.
If route_prefix is supplied to the include method, it must be a string.
This prefix represents a resource prefix that will be prepended to all resource patterns added
by the resource configuration. This can be used to help mount a set of routes at a different
location than the included callable's author intended while still maintaining the same
resource names.
For example:
# extern crate actix_web;
# use actix_web::*;
#
fn show_users(req: HttpRequest) -> HttpResponse {
unimplemented!()
}
fn main() {
Application::new()
.prefix("/users")
.resource("/show", |r| r.f(show_users))
.finish();
}
In the above example, the show_users route will have an effective route pattern of
/users/show instead of /show because the application's prefix argument will be prepended
to the pattern. The route will then only match if the URL path is /users/show,
and when the HttpRequest.url_for()
function is called with the route name show_users,
it will generate a URL with that same path.
Custom route predicates
You can think of predicate as simple function that accept request object reference
and returns true or false. Formally predicate is any object that implements
Predicate
trait. Actix provides
several predicates, you can check functions section
of api docs.
Here is simple predicates that check that request contains specific header and predicate usage:
# extern crate actix_web;
# extern crate http;
# use actix_web::*;
# use actix_web::httpcodes::*;
use http::header::CONTENT_TYPE;
use actix_web::pred::Predicate;
struct ContentTypeHeader;
impl<S: 'static> Predicate<S> for ContentTypeHeader {
fn check(&self, req: &mut HttpRequest<S>) -> bool {
req.headers().contains_key(CONTENT_TYPE)
}
}
fn main() {
Application::new()
.resource("/index.html", |r|
r.route()
.p(Box::new(ContentTypeHeader))
.f(|req| HTTPOk))
.finish();
}
In this example index handler will be called only if request contains CONTENT-TYPE header.
Predicates can have access to application's state via HttpRequest::state()
method.
Also predicates can store extra information in
requests`s extensions.
Modifing predicate values
You can invert the meaning of any predicate value by wrapping it in a Not
predicate.
For example if you want to return "METHOD NOT ALLOWED" response for all methods
except "GET":
# extern crate actix_web;
# extern crate http;
# use actix_web::*;
# use actix_web::httpcodes::*;
use actix_web::pred;
fn main() {
Application::new()
.resource("/index.html", |r|
r.route()
.p(pred::Not(pred::Get()))
.f(|req| HTTPMethodNotAllowed))
.finish();
}
Any
predicate accept list of predicates and matches if any of the supplied
predicates match. i.e:
pred::Any(vec![pred::Get(), pred::Post()])
All
predicate accept list of predicates and matches if all of the supplied
predicates match. i.e:
pred::All(vec![pred::Get(), pred::Header("content-type", "plain/text")])
Changing the default Not Found response
If path pattern can not be found in routing table or resource can not find matching
route default resource is used. Default response is NOT FOUND response.
To override NOT FOUND resource use Application::default_resource()
method.
This method accepts configuration function same as normal resource registration
with Application::resource()
method.
# extern crate actix_web;
# extern crate http;
use actix_web::*;
use actix_web::httpcodes::*;
fn main() {
Application::new()
.default_resource(|r| {
r.method(Method::GET).f(|req| HTTPNotFound);
r.route().p(pred::Not(pred::Get())).f(|req| HTTPMethodNotAllowed);
})
.finish();
}