diff --git a/examples/basic.rs b/examples/basic.rs index c77d4adf6..53dda877d 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -57,7 +57,7 @@ fn main() { let sys = actix::System::new("ws-example"); HttpServer::new( - Application::new("/") + Application::new() // enable logger .middleware(middlewares::Logger::default()) // cookie session middleware diff --git a/examples/state.rs b/examples/state.rs index c199dd5e5..8d489dcf5 100644 --- a/examples/state.rs +++ b/examples/state.rs @@ -60,7 +60,7 @@ fn main() { let sys = actix::System::new("ws-example"); HttpServer::new( - Application::with_state("/", AppState{counter: Cell::new(0)}) + Application::with_state(AppState{counter: Cell::new(0)}) // enable logger .middleware(middlewares::Logger::default()) // websocket route diff --git a/examples/websocket.rs b/examples/websocket.rs index 124e4526e..cb644753c 100644 --- a/examples/websocket.rs +++ b/examples/websocket.rs @@ -61,7 +61,7 @@ fn main() { let sys = actix::System::new("ws-example"); HttpServer::new( - Application::new("/") + Application::new() // enable logger .middleware(middlewares::Logger::default()) // websocket route diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 17ce202e9..a9befac89 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -6,7 +6,7 @@ - [Handler](./qs_4.md) - [Errors](./qs_4_5.md) - [State](./qs_6.md) -- [Resources and Routes](./qs_5.md) +- [URL Dispatch](./qs_5.md) - [Request & Response](./qs_7.md) - [WebSockets](./qs_9.md) - [Middlewares](./qs_10.md) diff --git a/guide/src/qs_10.md b/guide/src/qs_10.md index 664106f99..951051c4d 100644 --- a/guide/src/qs_10.md +++ b/guide/src/qs_10.md @@ -20,7 +20,7 @@ use actix_web::Application; use actix_web::middlewares::Logger; fn main() { - Application::new("/") + Application::new() .middleware(Logger::default()) .middleware(Logger::new("%a %{User-Agent}i")) .finish(); @@ -71,7 +71,7 @@ Tto set default response headers `DefaultHeaders` middleware could be used. use actix_web::*; fn main() { - let app = Application::new("/") + let app = Application::new() .middleware( middlewares::DefaultHeaders::build() .header("X-Version", "0.2") diff --git a/guide/src/qs_12.md b/guide/src/qs_12.md index 4d415baf5..5900e4172 100644 --- a/guide/src/qs_12.md +++ b/guide/src/qs_12.md @@ -16,7 +16,7 @@ fn index(req: HttpRequest) -> Result { } fn main() { - Application::new("/") + Application::new() .resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index)) .finish(); } @@ -34,7 +34,7 @@ And this name has to be used in `StaticFile` constructor. use actix_web::*; fn main() { - Application::new("/") + Application::new() .resource("/static/{tail:.*}", |r| r.h(fs::StaticFiles::new("tail", ".", true))) .finish(); } diff --git a/guide/src/qs_2.md b/guide/src/qs_2.md index cd32a87be..719f24cc0 100644 --- a/guide/src/qs_2.md +++ b/guide/src/qs_2.md @@ -48,7 +48,7 @@ request handler with the application's `resource` on a particular *HTTP method* # "Hello world!" # } # fn main() { - let app = Application::new("/") + let app = Application::new() .resource("/", |r| r.method(Method::GET).f(index)) .finish(); # } @@ -79,7 +79,7 @@ fn main() { let sys = actix::System::new("example"); HttpServer::new( - Application::new("/") + Application::new() .resource("/", |r| r.f(index))) .serve::<_, ()>("127.0.0.1:8088").unwrap(); diff --git a/guide/src/qs_3.md b/guide/src/qs_3.md index 2b954045e..d826998c1 100644 --- a/guide/src/qs_3.md +++ b/guide/src/qs_3.md @@ -5,8 +5,8 @@ It provides routing, middlewares, pre-processing of requests, and post-processin websocket protcol handling, multipart streams, etc. All actix web server is built around `Application` instance. -It is used for registering handlers for routes and resources, middlewares. -Also it stores applicationspecific state that is shared accross all handlers +It is used for registering routes for resources, middlewares. +Also it stores application specific state that is shared accross all handlers within same application. Application acts as namespace for all routes, i.e all routes for specific application @@ -20,7 +20,8 @@ has same url path prefix: # "Hello world!" # } # fn main() { - let app = Application::new("/prefix") + let app = Application::new() + .prefix("/prefix") .resource("/index.html", |r| r.method(Method::GET).f(index)) .finish() # } @@ -28,23 +29,27 @@ has same url path prefix: In this example application with `/prefix` prefix and `index.html` resource get created. This resource is available as on `/prefix/index.html` url. +For more information check +[*URL Matching*](./qs_5.html#using-a-application-prefix-to-compose-applications) section. Multiple applications could be served with one server: ```rust # extern crate actix_web; # extern crate tokio_core; -use std::net::SocketAddr; +# use tokio_core::net::TcpStream; +# use std::net::SocketAddr; use actix_web::*; -use tokio_core::net::TcpStream; fn main() { HttpServer::::new(vec![ - Application::new("/app1") + Application::new() + .prefix("/app1") .resource("/", |r| r.f(|r| httpcodes::HTTPOk)), - Application::new("/app2") + Application::new() + .prefix("/app2") .resource("/", |r| r.f(|r| httpcodes::HTTPOk)), - Application::new("/") + Application::new() .resource("/", |r| r.f(|r| httpcodes::HTTPOk)), ]); } diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index acab60d71..354cac122 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -81,7 +81,7 @@ fn main() { let sys = actix::System::new("example"); HttpServer::new( - Application::new("/") + Application::new() .resource("/", |r| r.method(Method::GET).f(index))) .serve::<_, ()>("127.0.0.1:8088").unwrap(); @@ -115,7 +115,7 @@ fn index(req: HttpRequest) -> FutureResult { } fn main() { - Application::new("/") + Application::new() .resource("/async", |r| r.route().a(index)) .finish(); } @@ -140,7 +140,7 @@ fn index(req: HttpRequest) -> HttpResponse { } fn main() { - Application::new("/") + Application::new() .resource("/async", |r| r.f(index)) .finish(); } diff --git a/guide/src/qs_4_5.md b/guide/src/qs_4_5.md index ad8154366..6c2863e4c 100644 --- a/guide/src/qs_4_5.md +++ b/guide/src/qs_4_5.md @@ -27,7 +27,7 @@ fn index(req: HttpRequest) -> io::Result { } # # fn main() { -# Application::new("/") +# Application::new() # .resource(r"/a/index.html", |r| r.f(index)) # .finish(); # } @@ -57,7 +57,7 @@ fn index(req: HttpRequest) -> Result<&'static str, MyError> { } # # fn main() { -# Application::new("/") +# Application::new() # .resource(r"/a/index.html", |r| r.f(index)) # .finish(); # } @@ -99,7 +99,7 @@ fn index(req: HttpRequest) -> Result<&'static str, MyError> { } # # fn main() { -# Application::new("/") +# Application::new() # .resource(r"/a/index.html", |r| r.f(index)) # .finish(); # } @@ -126,7 +126,7 @@ fn index(req: HttpRequest) -> Result<&'static str> { Ok(result.map_err(error::ErrorBadRequest)?) } # fn main() { -# Application::new("/") +# Application::new() # .resource(r"/a/index.html", |r| r.f(index)) # .finish(); # } diff --git a/guide/src/qs_5.md b/guide/src/qs_5.md index b5a9b4d01..33b107099 100644 --- a/guide/src/qs_5.md +++ b/guide/src/qs_5.md @@ -1,115 +1,251 @@ -# Resources and Routes +# URL Dispatch -All resources and routes register for specific application. -Application routes incoming requests based on route criteria which is defined during -resource registration or path prefix for simple handlers. -Internally *router* is a list of *resources*. Resource is an entry in *route table* -which corresponds to requested URL. +URL dispatch provides a simple way to map URLs to `Handler` code using a simple pattern matching +language. *Regex* crate and it's +[*RegexSet*](https://doc.rust-lang.org/regex/regex/struct.RegexSet.html) 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](../qs_4.html). -Prefix handler: +## 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](../actix_web/struct.Application.html#method.resource) methods +add a single resource to application routing table. This method accepts *path pattern* +and resource configuration funnction. ```rust # 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)) - .finish(); -} -``` - -In this example `index` get called for any url which starts with `/prefix`. - -Application prefix combines with handler prefix i.e - -```rust -# extern crate actix_web; -# use actix_web::*; -# -fn index(req: HttpRequest) -> HttpResponse { - unimplemented!() -} - -fn main() { - Application::new("/app") - .resource("/prefix", |r| r.f(index)) - .finish(); -} -``` - -In this example `index` get called for any url which starts with`/app/prefix`. - -Resource contains set of route for same endpoint. Route corresponds to handling -*HTTP method* by calling *web handler*. Resource select route based on *http method*, -if no route could be matched default response `HTTPMethodNotAllowed` get resturned. - -```rust -# extern crate actix_web; -# use actix_web::*; -# -fn main() { - Application::new("/") - .resource("/prefix", |r| { - r.method(Method::GET).h(httpcodes::HTTPOk); - r.method(Method::POST).h(httpcodes::HTTPForbidden); - }) - .finish(); -} -``` - -[`ApplicationBuilder::resource()` method](../actix_web/dev/struct.ApplicationBuilder.html#method.resource) -accepts configuration function, resource could be configured at once. -Check [`Resource`](../actix-web/target/doc/actix_web/struct.Resource.html) documentation -for more information. - -## Variable resources - -Resource may have *variable path*also. For instance, a resource with the -path '/a/{name}/c' would match all incoming requests with paths such -as '/a/b/c', '/a/1/c', and '/a/etc/c'. - -A *variable part* is specified in the form {identifier}, where the identifier can be -used later in a request handler to access the matched value for that part. This is -done by looking up the identifier in the `HttpRequest.match_info` object: - -```rust -# extern crate actix_web; -use actix_web::*; - -fn index(req: HttpRequest) -> String { - format!("Hello, {}", &req.match_info()["name"]) -} - -fn main() { - Application::new("/") - .resource("/{name}", |r| r.method(Method::GET).f(index)) - .finish(); -} -``` - -By default, each part matches the regular expression `[^{}/]+`. - -You can also specify a custom regex in the form `{identifier:regex}`: - -```rust -# extern crate actix_web; -# use actix_web::*; -# fn index(req: HttpRequest) -> String { -# format!("Hello, {}", &req.match_info()["name"]) +# fn index(req: HttpRequest) -> HttpResponse { +# unimplemented!() # } # fn main() { - Application::new("/") - .resource(r"{name:\d+}", |r| r.method(Method::GET).f(index)) + 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: + +```rust,ignore + 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. + +```rust +# 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*](https://doc.rust-lang.org/regex/regex/struct.RegexSet.html) 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`](../actix_web/struct.HttpRequest.html#method.match_info). +Specific value can be received with +[`Params::get()`](../actix_web/dev/struct.Params.html#method.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.: @@ -125,7 +261,7 @@ fn index(req: HttpRequest) -> Result { } fn main() { - Application::new("/") + Application::new() .resource(r"/a/{v1}/{v2}/", |r| r.f(index)) .finish(); } @@ -133,25 +269,6 @@ fn main() { For this example for path '/a/1/2/', values v1 and v2 will resolve to "1" and "2". -It is possible to match path tail with custom `.*` regex. - -```rust -# extern crate actix_web; -# use actix_web::*; -# -# fn index(req: HttpRequest) -> HttpResponse { -# unimplemented!() -# } -fn main() { - Application::new("/") - .resource(r"/test/{tail:.*}", |r| r.method(Method::GET).f(index)) - .finish(); -} -``` - -Above example would match all incoming requests with path such as -'/test/b/c', '/test/index.html', and '/test/etc/test'. - 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. @@ -179,13 +296,63 @@ fn index(req: HttpRequest) -> Result { } fn main() { - Application::new("/") + Application::new() .resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index)) .finish(); } ``` -### Path normalization +List of `FromParam` implementation could be found in +[api docs](../actix_web/dev/trait.FromParam.html#foreign-impls) + +## Generating resource URLs + +Use the [HttpRequest.url_for()](../actix_web/struct.HttpRequest.html#method.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. + +```rust +# 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*](https://docs.rs/url/1.6.0/url/struct.Url.html) 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. + +```rust +# extern crate actix_web; +use actix_web::*; + +fn index(mut req: HttpRequest) -> Result { + 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: @@ -214,7 +381,7 @@ This handler designed to be use as a handler for application's *default resource # httpcodes::HTTPOk # } fn main() { - let app = Application::new("/") + let app = Application::new() .resource("/resource/", |r| r.f(index)) .default_resource(|r| r.h(NormalizePath::default())) .finish(); @@ -239,9 +406,123 @@ It is possible to register path normalization only for *GET* requests only # httpcodes::HTTPOk # } fn main() { - let app = Application::new("/") + 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: + +```rust +# 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`](../actix_web/pred/trait.Predicate.html) trait. Actix provides +several predicates, you can check [functions section](../actix_web/pred/index.html#functions) +of api docs. + +Here is simple predicates that check that request contains specific *header* and predicate +usage: + +```rust +# 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 Predicate for ContentTypeHeader { + + fn check(&self, req: &mut HttpRequest) -> 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](../actix_web/struct.HttpRequest.html#method.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": + +```rust +# 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: + +```rust,ignore + pred::Any(vec![pred::Get(), pred::Post()]) +``` + +`All` predicate accept list of predicates and matches if all of the supplied +predicates match. i.e: + +```rust,ignore + pred::All(vec![pred::Get(), pred::Header("content-type", "plain/text")]) +``` diff --git a/guide/src/qs_6.md b/guide/src/qs_6.md index 73a342ca3..f7c889464 100644 --- a/guide/src/qs_6.md +++ b/guide/src/qs_6.md @@ -30,7 +30,7 @@ fn index(req: HttpRequest) -> String { } fn main() { - Application::with_state("/", AppState{counter: Cell::new(0)}) + Application::with_state(AppState{counter: Cell::new(0)}) .resource("/", |r| r.method(Method::GET).f(index)) .finish(); } diff --git a/guide/src/qs_7.md b/guide/src/qs_7.md index e65905f86..3e3fd8f7c 100644 --- a/guide/src/qs_7.md +++ b/guide/src/qs_7.md @@ -1,4 +1,4 @@ -# HttpRequest & HttpResponse +# Request & Response ## Response @@ -77,7 +77,7 @@ fn index(req: HttpRequest) -> Result> { } fn main() { - Application::new("/") + Application::new() .resource(r"/a/{name}", |r| r.method(Method::GET).f(index)) .finish(); } diff --git a/src/application.rs b/src/application.rs index 4d18cb6fa..b094292b3 100644 --- a/src/application.rs +++ b/src/application.rs @@ -69,13 +69,11 @@ impl Application<()> { /// Create application with empty state. Application can /// be configured with builder-like pattern. - /// - /// This method accepts path prefix for which it should serve requests. - pub fn new>(prefix: T) -> Application<()> { + pub fn new() -> Application<()> { Application { parts: Some(ApplicationParts { state: (), - prefix: prefix.into(), + prefix: "/".to_owned(), default: Resource::default_not_found(), resources: HashMap::new(), external: HashMap::new(), @@ -85,6 +83,12 @@ impl Application<()> { } } +impl Default for Application<()> { + fn default() -> Self { + Application::new() + } +} + impl Application where S: 'static { /// Create application with specific state. Application can be @@ -92,11 +96,11 @@ impl Application where S: 'static { /// /// State is shared with all reousrces within same application and could be /// accessed with `HttpRequest::state()` method. - pub fn with_state>(prefix: T, state: S) -> Application { + pub fn with_state(state: S) -> Application { Application { parts: Some(ApplicationParts { state: state, - prefix: prefix.into(), + prefix: "/".to_owned(), default: Resource::default_not_found(), resources: HashMap::new(), external: HashMap::new(), @@ -105,6 +109,42 @@ impl Application where S: 'static { } } + /// Set application prefix. + /// + /// Only requests that matches application's prefix get processed by this application. + /// Application prefix always contains laading "/" slash. If supplied prefix + /// does not contain leading slash, it get inserted. + /// + /// Inthe following example only requests with "/app/" path prefix + /// get handled. Request with path "/app/test/" will be handled, + /// but request with path "/other/..." will return *NOT FOUND* + /// + /// ```rust + /// # extern crate actix_web; + /// use actix_web::*; + /// + /// fn main() { + /// let app = Application::new() + /// .prefix("/app") + /// .resource("/test", |r| { + /// r.method(Method::GET).f(|_| httpcodes::HTTPOk); + /// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed); + /// }) + /// .finish(); + /// } + /// ``` + pub fn prefix>(&mut self, prefix: P) -> &mut Self { + { + let parts = self.parts.as_mut().expect("Use after finish"); + let mut prefix = prefix.into(); + if !prefix.starts_with('/') { + prefix.insert(0, '/') + } + parts.prefix = prefix; + } + self + } + /// Configure resource for specific path. /// /// Resource may have variable path also. For instance, a resource with @@ -128,7 +168,7 @@ impl Application where S: 'static { /// use actix_web::*; /// /// fn main() { - /// let app = Application::new("/") + /// let app = Application::new() /// .resource("/test", |r| { /// r.method(Method::GET).f(|_| httpcodes::HTTPOk); /// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed); @@ -184,7 +224,7 @@ impl Application where S: 'static { /// } /// /// fn main() { - /// let app = Application::new("/") + /// let app = Application::new() /// .resource("/index.html", |r| r.f(index)) /// .external_resource("youtube", "https://youtube.com/watch/{video_id}") /// .finish(); @@ -275,7 +315,7 @@ mod tests { #[test] fn test_default_resource() { - let app = Application::new("/") + let app = Application::new() .resource("/test", |r| r.h(httpcodes::HTTPOk)) .finish(); @@ -291,7 +331,7 @@ mod tests { let resp = app.run(req); assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND); - let app = Application::new("/") + let app = Application::new() .default_resource(|r| r.h(httpcodes::HTTPMethodNotAllowed)) .finish(); let req = HttpRequest::new( @@ -303,7 +343,8 @@ mod tests { #[test] fn test_unhandled_prefix() { - let app = Application::new("/test") + let app = Application::new() + .prefix("/test") .resource("/test", |r| r.h(httpcodes::HTTPOk)) .finish(); assert!(app.handle(HttpRequest::default()).is_err()); @@ -311,7 +352,7 @@ mod tests { #[test] fn test_state() { - let app = Application::with_state("/", 10) + let app = Application::with_state(10) .resource("/", |r| r.h(httpcodes::HTTPOk)) .finish(); let req = HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone()); diff --git a/src/fs.rs b/src/fs.rs index 963cf2fc8..736d9910e 100644 --- a/src/fs.rs +++ b/src/fs.rs @@ -199,7 +199,7 @@ impl FromRequest for FilesystemElement { /// use actix_web::{fs, Application}; /// /// fn main() { -/// let app = Application::new("/") +/// let app = Application::new() /// .resource("/static/{tail:.*}", |r| r.h(fs::StaticFiles::new("tail", ".", true))) /// .finish(); /// } diff --git a/src/handler.rs b/src/handler.rs index 24cb15a7c..241eabf3c 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -303,7 +303,7 @@ impl FromRequest for Json { /// # httpcodes::HTTPOk /// # } /// fn main() { -/// let app = Application::new("/") +/// let app = Application::new() /// .resource("/test/", |r| r.f(index)) /// .default_resource(|r| r.h(NormalizePath::default())) /// .finish(); @@ -412,7 +412,7 @@ mod tests { #[test] fn test_normalize_path_trailing_slashes() { - let app = Application::new("/") + let 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())) @@ -444,7 +444,7 @@ mod tests { #[test] fn test_normalize_path_trailing_slashes_disabled() { - let app = Application::new("/") + let 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( @@ -471,7 +471,7 @@ mod tests { #[test] fn test_normalize_path_merge_slashes() { - let app = Application::new("/") + let 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())) @@ -507,7 +507,7 @@ mod tests { #[test] fn test_normalize_path_merge_and_append_slashes() { - let app = Application::new("/") + let 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)) diff --git a/src/info.rs b/src/info.rs index 190ce0c5c..76cf678ea 100644 --- a/src/info.rs +++ b/src/info.rs @@ -21,6 +21,7 @@ pub struct ConnectionInfo<'a> { impl<'a> ConnectionInfo<'a> { /// Create *ConnectionInfo* instance for a request. + #[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))] pub fn new(req: &'a HttpRequest) -> ConnectionInfo<'a> { let mut host = None; let mut scheme = None; diff --git a/src/middlewares/defaultheaders.rs b/src/middlewares/defaultheaders.rs index e1a797a23..a0b772e90 100644 --- a/src/middlewares/defaultheaders.rs +++ b/src/middlewares/defaultheaders.rs @@ -15,7 +15,7 @@ use middlewares::{Response, Middleware}; /// use actix_web::*; /// /// fn main() { -/// let app = Application::new("/") +/// let app = Application::new() /// .middleware( /// middlewares::DefaultHeaders::build() /// .header("X-Version", "0.2") diff --git a/src/middlewares/logger.rs b/src/middlewares/logger.rs index 67e5c3ffa..57b12cf81 100644 --- a/src/middlewares/logger.rs +++ b/src/middlewares/logger.rs @@ -27,7 +27,7 @@ use middlewares::{Middleware, Started, Finished}; /// use actix_web::middlewares::Logger; /// /// fn main() { -/// let app = Application::new("/") +/// let app = Application::new() /// .middleware(Logger::default()) /// .middleware(Logger::new("%a %{User-Agent}i")) /// .finish(); diff --git a/src/pred.rs b/src/pred.rs index b3fc6f882..c907d2793 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -170,7 +170,7 @@ mod tests { let pred = Header("transfer-encoding", "other"); assert!(!pred.check(&mut req)); - let pred = Header("content-tye", "other"); + let pred = Header("content-type", "other"); assert!(!pred.check(&mut req)); } diff --git a/src/resource.rs b/src/resource.rs index f4677ad08..0053b84ed 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -2,8 +2,9 @@ use std::marker::PhantomData; use http::Method; +use pred; use route::Route; -use handler::{Reply, Handler, FromRequest, RouteHandler, WrapHandler}; +use handler::{Reply, Handler, FromRequest, RouteHandler}; use httpcodes::HTTPNotFound; use httprequest::HttpRequest; @@ -22,7 +23,7 @@ use httprequest::HttpRequest; /// use actix_web::*; /// /// fn main() { -/// let app = Application::new("/") +/// let app = Application::new() /// .resource( /// "/", |r| r.method(Method::GET).f(|r| HttpResponse::Ok())) /// .finish(); @@ -31,7 +32,6 @@ pub struct Resource { name: String, state: PhantomData, routes: Vec>, - default: Box>, } impl Default for Resource { @@ -39,8 +39,7 @@ impl Default for Resource { Resource { name: String::new(), state: PhantomData, - routes: Vec::new(), - default: Box::new(HTTPNotFound)} + routes: Vec::new() } } } @@ -50,8 +49,7 @@ impl Resource { Resource { name: String::new(), state: PhantomData, - routes: Vec::new(), - default: Box::new(HTTPNotFound)} + routes: Vec::new() } } /// Set resource name @@ -74,7 +72,7 @@ impl Resource { /// use actix_web::*; /// /// fn main() { - /// let app = Application::new("/") + /// let app = Application::new() /// .resource( /// "/", |r| r.route() /// .p(pred::Any(vec![pred::Get(), pred::Put()])) @@ -97,7 +95,7 @@ impl Resource { /// ``` pub fn method(&mut self, method: Method) -> &mut Route { self.routes.push(Route::default()); - self.routes.last_mut().unwrap().method(method) + self.routes.last_mut().unwrap().p(pred::Method(method)) } /// Register a new route and add handler object. @@ -126,12 +124,6 @@ impl Resource { self.routes.push(Route::default()); self.routes.last_mut().unwrap().f(handler) } - - /// Default handler is used if no matched route found. - /// By default `HTTPNotFound` is used. - pub fn default_handler(&mut self, handler: H) where H: Handler { - self.default = Box::new(WrapHandler::new(handler)); - } } impl RouteHandler for Resource { @@ -142,6 +134,6 @@ impl RouteHandler for Resource { return route.handle(req) } } - self.default.handle(req) + Reply::response(HTTPNotFound) } } diff --git a/src/route.rs b/src/route.rs index 64f037d8d..daea3cb32 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,8 +1,7 @@ -use http::Method; use futures::Future; use error::Error; -use pred::{self, Predicate}; +use pred::Predicate; use handler::{Reply, Handler, FromRequest, RouteHandler, AsyncHandler, WrapHandler}; use httpcodes::HTTPNotFound; use httprequest::HttpRequest; @@ -43,20 +42,6 @@ impl Route { self.handler.handle(req) } - /// Add method check to route. This method could be called multiple times. - pub fn method(&mut self, method: Method) -> &mut Self { - self.preds.push(pred::Method(method)); - self - } - - /// Add predicates to route. - pub fn predicates

(&mut self, preds: P) -> &mut Self - where P: IntoIterator>> - { - self.preds.extend(preds.into_iter()); - self - } - /// Add match predicate to route. pub fn p(&mut self, p: Box>) -> &mut Self { self.preds.push(p); diff --git a/src/ws.rs b/src/ws.rs index 551dee622..2578cdc0d 100644 --- a/src/ws.rs +++ b/src/ws.rs @@ -42,7 +42,7 @@ //! } //! //! fn main() { -//! Application::new("/") +//! Application::new() //! .resource("/ws/", |r| r.method(Method::GET).f(ws_index)) // <- register websocket route //! .finish(); //! } diff --git a/tests/test_server.rs b/tests/test_server.rs index 1cc955867..35a3d76cc 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -16,7 +16,7 @@ fn test_serve() { thread::spawn(|| { let sys = System::new("test"); let srv = HttpServer::new( - vec![Application::new("/") + vec![Application::new() .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))]); srv.serve::<_, ()>("127.0.0.1:58902").unwrap(); sys.run(); @@ -36,7 +36,7 @@ fn test_serve_incoming() { let sys = System::new("test"); let srv = HttpServer::new( - Application::new("/") + Application::new() .resource("/", |r| r.method(Method::GET).h(httpcodes::HTTPOk))); let tcp = TcpListener::from_listener(tcp, &addr2, Arbiter::handle()).unwrap(); srv.serve_incoming::<_, ()>(tcp.incoming(), false).unwrap(); @@ -84,7 +84,7 @@ fn test_middlewares() { let sys = System::new("test"); HttpServer::new( - vec![Application::new("/") + vec![Application::new() .middleware(MiddlewareTest{start: act_num1, response: act_num2, finish: act_num3})