mirror of
https://github.com/actix/actix-extras.git
synced 2024-11-28 01:32:57 +01:00
move guide to separate repo; update links
This commit is contained in:
parent
113f5ad1a8
commit
a8567da3e2
21
.travis.yml
21
.travis.yml
@ -33,27 +33,12 @@ before_install:
|
|||||||
|
|
||||||
# Add clippy
|
# Add clippy
|
||||||
before_script:
|
before_script:
|
||||||
- |
|
|
||||||
if [[ "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
|
|
||||||
( ( cargo install clippy && export CLIPPY=true ) || export CLIPPY=false );
|
|
||||||
fi
|
|
||||||
- export PATH=$PATH:~/.cargo/bin
|
- export PATH=$PATH:~/.cargo/bin
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- |
|
- |
|
||||||
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then
|
cargo clean
|
||||||
cargo clean
|
cargo test --features="alpn,tls" -- --nocapture
|
||||||
USE_SKEPTIC=1 cargo test --features=alpn
|
|
||||||
else
|
|
||||||
cargo clean
|
|
||||||
cargo test -- --nocapture
|
|
||||||
# --features=alpn
|
|
||||||
fi
|
|
||||||
|
|
||||||
- |
|
|
||||||
if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then
|
|
||||||
cargo clippy
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Upload docs
|
# Upload docs
|
||||||
after_success:
|
after_success:
|
||||||
@ -61,8 +46,6 @@ after_success:
|
|||||||
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then
|
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then
|
||||||
cargo doc --features "alpn, tls, session" --no-deps &&
|
cargo doc --features "alpn, tls, session" --no-deps &&
|
||||||
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
|
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
|
||||||
curl -sL https://github.com/rust-lang-nursery/mdBook/releases/download/v0.1.2/mdbook-v0.1.2-x86_64-unknown-linux-gnu.tar.gz | tar xvz -C $HOME/.cargo/bin &&
|
|
||||||
cd guide && mdbook build -d ../target/doc/guide && cd .. &&
|
|
||||||
git clone https://github.com/davisp/ghp-import.git &&
|
git clone https://github.com/davisp/ghp-import.git &&
|
||||||
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&
|
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&
|
||||||
echo "Uploaded documentation"
|
echo "Uploaded documentation"
|
||||||
|
@ -12,17 +12,17 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
|
|||||||
* Multipart streams
|
* Multipart streams
|
||||||
* Static assets
|
* Static assets
|
||||||
* SSL support with OpenSSL or `native-tls`
|
* SSL support with OpenSSL or `native-tls`
|
||||||
* Middlewares ([Logger](https://actix.rs/actix-web/guide/qs_10.html#logging),
|
* Middlewares ([Logger](https://actix.rs/book/actix-web/sec-9-middlewares.html#logging),
|
||||||
[Session](https://actix.rs/actix-web/guide/qs_10.html#user-sessions),
|
[Session](https://actix.rs/book/actix-web/sec-9-middlewares.html#user-sessions),
|
||||||
[Redis sessions](https://github.com/actix/actix-redis),
|
[Redis sessions](https://github.com/actix/actix-redis),
|
||||||
[DefaultHeaders](https://actix.rs/actix-web/guide/qs_10.html#default-headers),
|
[DefaultHeaders](https://actix.rs/book/actix-web/sec-9-middlewares.html#default-headers),
|
||||||
[CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html),
|
[CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html),
|
||||||
[CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html))
|
[CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html))
|
||||||
* Built on top of [Actix actor framework](https://github.com/actix/actix)
|
* Built on top of [Actix actor framework](https://github.com/actix/actix)
|
||||||
|
|
||||||
## Documentation & community resources
|
## Documentation & community resources
|
||||||
|
|
||||||
* [User Guide](https://actix.rs/actix-web/guide/)
|
* [User Guide](https://actix.rs/book/actix-web/)
|
||||||
* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/)
|
* [API Documentation (Development)](https://actix.rs/actix-web/actix_web/)
|
||||||
* [API Documentation (Releases)](https://docs.rs/actix-web/)
|
* [API Documentation (Releases)](https://docs.rs/actix-web/)
|
||||||
* [Chat on gitter](https://gitter.im/actix/actix)
|
* [Chat on gitter](https://gitter.im/actix/actix)
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
[book]
|
|
||||||
title = "Actix web"
|
|
||||||
description = "Actix web framework guide"
|
|
||||||
author = "Actix Project and Contributors"
|
|
||||||
|
|
||||||
[output.html]
|
|
||||||
google-analytics = "UA-110322332-1"
|
|
@ -1,16 +0,0 @@
|
|||||||
# Summary
|
|
||||||
|
|
||||||
[Quickstart](./qs_1.md)
|
|
||||||
- [Getting Started](./qs_2.md)
|
|
||||||
- [Application](./qs_3.md)
|
|
||||||
- [Server](./qs_3_5.md)
|
|
||||||
- [Handler](./qs_4.md)
|
|
||||||
- [Errors](./qs_4_5.md)
|
|
||||||
- [URL Dispatch](./qs_5.md)
|
|
||||||
- [Request & Response](./qs_7.md)
|
|
||||||
- [Testing](./qs_8.md)
|
|
||||||
- [Middlewares](./qs_10.md)
|
|
||||||
- [Static file handling](./qs_12.md)
|
|
||||||
- [WebSockets](./qs_9.md)
|
|
||||||
- [HTTP/2](./qs_13.md)
|
|
||||||
- [Database integration](./qs_14.md)
|
|
@ -1,32 +0,0 @@
|
|||||||
# Quick start
|
|
||||||
|
|
||||||
## Install Rust
|
|
||||||
|
|
||||||
Before we begin, we need to install Rust using [rustup](https://www.rustup.rs/):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl https://sh.rustup.rs -sSf | sh
|
|
||||||
```
|
|
||||||
|
|
||||||
If you already have rustup installed, run this command to ensure you have the latest version of Rust:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rustup update
|
|
||||||
```
|
|
||||||
|
|
||||||
Actix web framework requires rust version 1.21 and up.
|
|
||||||
|
|
||||||
## Running Examples
|
|
||||||
|
|
||||||
The fastest way to start experimenting with actix web is to clone the
|
|
||||||
[repository](https://github.com/actix/actix-web) and run the included examples.
|
|
||||||
|
|
||||||
The following set of commands runs the `basics` example:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/actix/example
|
|
||||||
cd examples/basics
|
|
||||||
cargo run
|
|
||||||
```
|
|
||||||
|
|
||||||
Check [examples/](https://github.com/actix/examples/tree/master/) directory for more examples.
|
|
@ -1,251 +0,0 @@
|
|||||||
# Middleware
|
|
||||||
|
|
||||||
Actix's middleware system allows us to add additional behavior to request/response processing.
|
|
||||||
Middleware can hook into an incoming request process, enabling us to modify requests
|
|
||||||
as well as halt request processing to return a response early.
|
|
||||||
|
|
||||||
Middleware can also hook into response processing.
|
|
||||||
|
|
||||||
Typically, middleware is involved in the following actions:
|
|
||||||
|
|
||||||
* Pre-process the Request
|
|
||||||
* Post-process a Response
|
|
||||||
* Modify application state
|
|
||||||
* Access external services (redis, logging, sessions)
|
|
||||||
|
|
||||||
Middleware is registered for each application and executed in same order as
|
|
||||||
registration. In general, a *middleware* is a type that implements the
|
|
||||||
[*Middleware trait*](../actix_web/middlewares/trait.Middleware.html). Each method
|
|
||||||
in this trait has a default implementation. Each method can return a result immediately
|
|
||||||
or a *future* object.
|
|
||||||
|
|
||||||
The following demonstrates using middleware to add request and response headers:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate http;
|
|
||||||
# extern crate actix_web;
|
|
||||||
use http::{header, HttpTryFrom};
|
|
||||||
use actix_web::{App, HttpRequest, HttpResponse, Result};
|
|
||||||
use actix_web::middleware::{Middleware, Started, Response};
|
|
||||||
|
|
||||||
struct Headers; // <- Our middleware
|
|
||||||
|
|
||||||
/// Middleware implementation, middlewares are generic over application state,
|
|
||||||
/// so you can access state with `HttpRequest::state()` method.
|
|
||||||
impl<S> Middleware<S> for Headers {
|
|
||||||
|
|
||||||
/// Method is called when request is ready. It may return
|
|
||||||
/// future, which should resolve before next middleware get called.
|
|
||||||
fn start(&self, req: &mut HttpRequest<S>) -> Result<Started> {
|
|
||||||
req.headers_mut().insert(
|
|
||||||
header::CONTENT_TYPE, header::HeaderValue::from_static("text/plain"));
|
|
||||||
Ok(Started::Done)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Method is called when handler returns response,
|
|
||||||
/// but before sending http message to peer.
|
|
||||||
fn response(&self, req: &mut HttpRequest<S>, mut resp: HttpResponse) -> Result<Response> {
|
|
||||||
resp.headers_mut().insert(
|
|
||||||
header::HeaderName::try_from("X-VERSION").unwrap(),
|
|
||||||
header::HeaderValue::from_static("0.2"));
|
|
||||||
Ok(Response::Done(resp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.middleware(Headers) // <- Register middleware, this method can be called multiple times
|
|
||||||
.resource("/", |r| r.f(|_| HttpResponse::Ok()));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> Actix provides several useful middlewares, such as *logging*, *user sessions*, etc.
|
|
||||||
|
|
||||||
## Logging
|
|
||||||
|
|
||||||
Logging is implemented as a middleware.
|
|
||||||
It is common to register a logging middleware as the first middleware for the application.
|
|
||||||
Logging middleware must be registered for each application.
|
|
||||||
|
|
||||||
The `Logger` middleware uses the standard log crate to log information. You should enable logger
|
|
||||||
for *actix_web* package to see access log ([env_logger](https://docs.rs/env_logger/*/env_logger/)
|
|
||||||
or similar).
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
Create `Logger` middleware with the specified `format`.
|
|
||||||
Default `Logger` can be created with `default` method, it uses the default format:
|
|
||||||
|
|
||||||
```ignore
|
|
||||||
%a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
|
|
||||||
```
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
use actix_web::App;
|
|
||||||
use actix_web::middleware::Logger;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
env_logger::init();
|
|
||||||
|
|
||||||
App::new()
|
|
||||||
.middleware(Logger::default())
|
|
||||||
.middleware(Logger::new("%a %{User-Agent}i"))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The following is an example of the default logging format:
|
|
||||||
|
|
||||||
```
|
|
||||||
INFO:actix_web::middleware::logger: 127.0.0.1:59934 [02/Dec/2017:00:21:43 -0800] "GET / HTTP/1.1" 302 0 "-" "curl/7.54.0" 0.000397
|
|
||||||
INFO:actix_web::middleware::logger: 127.0.0.1:59947 [02/Dec/2017:00:22:40 -0800] "GET /index.html HTTP/1.1" 200 0 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:57.0) Gecko/20100101 Firefox/57.0" 0.000646
|
|
||||||
```
|
|
||||||
|
|
||||||
### Format
|
|
||||||
|
|
||||||
`%%` The percent sign
|
|
||||||
|
|
||||||
`%a` Remote IP-address (IP-address of proxy if using reverse proxy)
|
|
||||||
|
|
||||||
`%t` Time when the request was started to process
|
|
||||||
|
|
||||||
`%P` The process ID of the child that serviced the request
|
|
||||||
|
|
||||||
`%r` First line of request
|
|
||||||
|
|
||||||
`%s` Response status code
|
|
||||||
|
|
||||||
`%b` Size of response in bytes, including HTTP headers
|
|
||||||
|
|
||||||
`%T` Time taken to serve the request, in seconds with floating fraction in .06f format
|
|
||||||
|
|
||||||
`%D` Time taken to serve the request, in milliseconds
|
|
||||||
|
|
||||||
`%{FOO}i` request.headers['FOO']
|
|
||||||
|
|
||||||
`%{FOO}o` response.headers['FOO']
|
|
||||||
|
|
||||||
`%{FOO}e` os.environ['FOO']
|
|
||||||
|
|
||||||
## Default headers
|
|
||||||
|
|
||||||
To set default response headers, the `DefaultHeaders` middleware can be used. The
|
|
||||||
*DefaultHeaders* middleware does not set the header if response headers already contain
|
|
||||||
a specified header.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::{http, middleware, App, HttpResponse};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let app = App::new()
|
|
||||||
.middleware(
|
|
||||||
middleware::DefaultHeaders::new()
|
|
||||||
.header("X-Version", "0.2"))
|
|
||||||
.resource("/test", |r| {
|
|
||||||
r.method(http::Method::GET).f(|req| HttpResponse::Ok());
|
|
||||||
r.method(http::Method::HEAD).f(|req| HttpResponse::MethodNotAllowed());
|
|
||||||
})
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## User sessions
|
|
||||||
|
|
||||||
Actix provides a general solution for session management. The
|
|
||||||
[**SessionStorage**](../actix_web/middleware/struct.SessionStorage.html) middleware can be
|
|
||||||
used with different backend types to store session data in different backends.
|
|
||||||
|
|
||||||
> By default, only cookie session backend is implemented. Other backend implementations
|
|
||||||
> can be added.
|
|
||||||
|
|
||||||
[**CookieSessionBackend**](../actix_web/middleware/struct.CookieSessionBackend.html)
|
|
||||||
uses cookies as session storage. `CookieSessionBackend` creates sessions which
|
|
||||||
are limited to storing fewer than 4000 bytes of data, as the payload must fit into a
|
|
||||||
single cookie. An internal server error is generated if a session contains more than 4000 bytes.
|
|
||||||
|
|
||||||
A cookie may have a security policy of *signed* or *private*. Each has a respective `CookieSessionBackend` constructor.
|
|
||||||
|
|
||||||
A *signed* cookie may be viewed but not modified by the client. A *private* cookie may neither be viewed nor modified by the client.
|
|
||||||
|
|
||||||
The constructors take a key as an argument. This is the private key for cookie session - when this value is changed, all session data is lost.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
In general, you create a
|
|
||||||
`SessionStorage` middleware and initialize it with specific backend implementation,
|
|
||||||
such as a `CookieSessionBackend`. To access session data,
|
|
||||||
[*HttpRequest::session()*](../actix_web/middleware/trait.RequestSession.html#tymethod.session)
|
|
||||||
must be used. This method returns a
|
|
||||||
[*Session*](../actix_web/middleware/struct.Session.html) object, which allows us to get or set
|
|
||||||
session data.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix;
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::{server, App, HttpRequest, Result};
|
|
||||||
use actix_web::middleware::{RequestSession, SessionStorage, CookieSessionBackend};
|
|
||||||
|
|
||||||
fn index(mut req: HttpRequest) -> Result<&'static str> {
|
|
||||||
// access session data
|
|
||||||
if let Some(count) = req.session().get::<i32>("counter")? {
|
|
||||||
println!("SESSION value: {}", count);
|
|
||||||
req.session().set("counter", count+1)?;
|
|
||||||
} else {
|
|
||||||
req.session().set("counter", 1)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok("Welcome!")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
# let sys = actix::System::new("basic-example");
|
|
||||||
server::new(
|
|
||||||
|| App::new()
|
|
||||||
.middleware(SessionStorage::new( // <- create session middleware
|
|
||||||
CookieSessionBackend::signed(&[0; 32]) // <- create signed cookie session backend
|
|
||||||
.secure(false)
|
|
||||||
)))
|
|
||||||
.bind("127.0.0.1:59880").unwrap()
|
|
||||||
.start();
|
|
||||||
# actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
|
||||||
# let _ = sys.run();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error handlers
|
|
||||||
|
|
||||||
`ErrorHandlers` middleware allows us to provide custom handlers for responses.
|
|
||||||
|
|
||||||
You can use the `ErrorHandlers::handler()` method to register a custom error handler
|
|
||||||
for a specific status code. You can modify an existing response or create a completly new
|
|
||||||
one. The error handler can return a response immediately or return a future that resolves
|
|
||||||
into a response.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::{
|
|
||||||
App, HttpRequest, HttpResponse, Result,
|
|
||||||
http, middleware::Response, middleware::ErrorHandlers};
|
|
||||||
|
|
||||||
fn render_500<S>(_: &mut HttpRequest<S>, resp: HttpResponse) -> Result<Response> {
|
|
||||||
let mut builder = resp.into_builder();
|
|
||||||
builder.header(http::header::CONTENT_TYPE, "application/json");
|
|
||||||
Ok(Response::Done(builder.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let app = App::new()
|
|
||||||
.middleware(
|
|
||||||
ErrorHandlers::new()
|
|
||||||
.handler(http::StatusCode::INTERNAL_SERVER_ERROR, render_500))
|
|
||||||
.resource("/test", |r| {
|
|
||||||
r.method(http::Method::GET).f(|_| HttpResponse::Ok());
|
|
||||||
r.method(http::Method::HEAD).f(|_| HttpResponse::MethodNotAllowed());
|
|
||||||
})
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,54 +0,0 @@
|
|||||||
# Static file handling
|
|
||||||
|
|
||||||
## Individual file
|
|
||||||
|
|
||||||
It is possible to serve static files with a custom path pattern and `NamedFile`. To
|
|
||||||
match a path tail, we can use a `[.*]` regex.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use actix_web::{App, HttpRequest, Result, http::Method, fs::NamedFile};
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> Result<NamedFile> {
|
|
||||||
let path: PathBuf = req.match_info().query("tail")?;
|
|
||||||
Ok(NamedFile::open(path)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Directory
|
|
||||||
|
|
||||||
To serve files from specific directories and sub-directories, `StaticFiles` can be used.
|
|
||||||
`StaticFiles` must be registered with an `App::handler()` method, otherwise
|
|
||||||
it will be unable to serve sub-paths.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.handler(
|
|
||||||
"/static",
|
|
||||||
fs::StaticFiles::new(".")
|
|
||||||
.show_files_listing())
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The parameter is the base directory. By default files listing for sub-directories
|
|
||||||
is disabled. Attempt to load directory listing will return *404 Not Found* response.
|
|
||||||
To enable files listing, use
|
|
||||||
[*StaticFiles::show_files_listing()*](../actix_web/s/struct.StaticFiles.html#method.show_files_listing)
|
|
||||||
method.
|
|
||||||
|
|
||||||
Instead of showing files listing for directory, it is possible to redirect
|
|
||||||
to a specific index file. Use the
|
|
||||||
[*StaticFiles::index_file()*](../actix_web/s/struct.StaticFiles.html#method.index_file)
|
|
||||||
method to configure this redirect.
|
|
@ -1,46 +0,0 @@
|
|||||||
# HTTP/2.0
|
|
||||||
|
|
||||||
Actix web automatically upgrades connections to *HTTP/2.0* if possible.
|
|
||||||
|
|
||||||
## Negotiation
|
|
||||||
|
|
||||||
*HTTP/2.0* protocol over tls without prior knowledge requires
|
|
||||||
[tls alpn](https://tools.ietf.org/html/rfc7301).
|
|
||||||
|
|
||||||
> Currently, only `rust-openssl` has support.
|
|
||||||
|
|
||||||
`alpn` negotiation requires enabling the feature. When enabled, `HttpServer` provides the
|
|
||||||
[serve_tls](../actix_web/struct.HttpServer.html#method.serve_tls) method.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[dependencies]
|
|
||||||
actix-web = { version = "0.3.3", features=["alpn"] }
|
|
||||||
openssl = { version="0.10", features = ["v110"] }
|
|
||||||
```
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
use std::fs::File;
|
|
||||||
use actix_web::*;
|
|
||||||
use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// load ssl keys
|
|
||||||
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
|
|
||||||
builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap();
|
|
||||||
builder.set_certificate_chain_file("cert.pem").unwrap();
|
|
||||||
|
|
||||||
HttpServer::new(
|
|
||||||
|| App::new()
|
|
||||||
.resource("/index.html", |r| r.f(index)))
|
|
||||||
.bind("127.0.0.1:8080").unwrap();
|
|
||||||
.serve_ssl(builder).unwrap();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Upgrades to *HTTP/2.0* schema described in
|
|
||||||
[rfc section 3.2](https://http2.github.io/http2-spec/#rfc.section.3.2) is not supported.
|
|
||||||
Starting *HTTP/2* with prior knowledge is supported for both clear text connection
|
|
||||||
and tls connection. [rfc section 3.4](https://http2.github.io/http2-spec/#rfc.section.3.4)
|
|
||||||
|
|
||||||
> Check out [examples/tls](https://github.com/actix/actix-web/tree/master/examples/tls)
|
|
||||||
> for a concrete example.
|
|
@ -1,128 +0,0 @@
|
|||||||
# Database integration
|
|
||||||
|
|
||||||
## Diesel
|
|
||||||
|
|
||||||
At the moment, Diesel 1.0 does not support asynchronous operations,
|
|
||||||
but it possible to use the `actix` synchronous actor system as a database interface api.
|
|
||||||
|
|
||||||
Technically, sync actors are worker style actors. Multiple sync actors
|
|
||||||
can be run in parallel and process messages from same queue. Sync actors work in mpsc mode.
|
|
||||||
|
|
||||||
Let's create a simple database api that can insert a new user row into a SQLite table.
|
|
||||||
We must define a sync actor and a connection that this actor will use. The same approach
|
|
||||||
can be used for other databases.
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
use actix::prelude::*;
|
|
||||||
|
|
||||||
struct DbExecutor(SqliteConnection);
|
|
||||||
|
|
||||||
impl Actor for DbExecutor {
|
|
||||||
type Context = SyncContext<Self>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This is the definition of our actor. Now, we must define the *create user* message and response.
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
struct CreateUser {
|
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Message for CreateUser {
|
|
||||||
type Result = Result<User, Error>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
We can send a `CreateUser` message to the `DbExecutor` actor, and as a result, we will receive a
|
|
||||||
`User` model instance. Next, we must define the handler implementation for this message.
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
impl Handler<CreateUser> for DbExecutor {
|
|
||||||
type Result = Result<User, Error>;
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result
|
|
||||||
{
|
|
||||||
use self::schema::users::dsl::*;
|
|
||||||
|
|
||||||
// Create insertion model
|
|
||||||
let uuid = format!("{}", uuid::Uuid::new_v4());
|
|
||||||
let new_user = models::NewUser {
|
|
||||||
id: &uuid,
|
|
||||||
name: &msg.name,
|
|
||||||
};
|
|
||||||
|
|
||||||
// normal diesel operations
|
|
||||||
diesel::insert_into(users)
|
|
||||||
.values(&new_user)
|
|
||||||
.execute(&self.0)
|
|
||||||
.expect("Error inserting person");
|
|
||||||
|
|
||||||
let mut items = users
|
|
||||||
.filter(id.eq(&uuid))
|
|
||||||
.load::<models::User>(&self.0)
|
|
||||||
.expect("Error loading person");
|
|
||||||
|
|
||||||
Ok(items.pop().unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
That's it! Now, we can use the *DbExecutor* actor from any http handler or middleware.
|
|
||||||
All we need is to start *DbExecutor* actors and store the address in a state where http handler
|
|
||||||
can access it.
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
/// This is state where we will store *DbExecutor* address.
|
|
||||||
struct State {
|
|
||||||
db: Addr<Syn, DbExecutor>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let sys = actix::System::new("diesel-example");
|
|
||||||
|
|
||||||
// Start 3 parallel db executors
|
|
||||||
let addr = SyncArbiter::start(3, || {
|
|
||||||
DbExecutor(SqliteConnection::establish("test.db").unwrap())
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start http server
|
|
||||||
HttpServer::new(move || {
|
|
||||||
App::with_state(State{db: addr.clone()})
|
|
||||||
.resource("/{name}", |r| r.method(Method::GET).a(index))})
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.start().unwrap();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
We will use the address in a request handler. The handle returns a future object;
|
|
||||||
thus, we receive the message response asynchronously.
|
|
||||||
`Route::a()` must be used for async handler registration.
|
|
||||||
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
/// Async handler
|
|
||||||
fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
let name = &req.match_info()["name"];
|
|
||||||
|
|
||||||
// Send message to `DbExecutor` actor
|
|
||||||
req.state().db.send(CreateUser{name: name.to_owned()})
|
|
||||||
.from_err()
|
|
||||||
.and_then(|res| {
|
|
||||||
match res {
|
|
||||||
Ok(user) => Ok(HttpResponse::Ok().json(user)),
|
|
||||||
Err(_) => Ok(HttpResponse::InternalServerError().into())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> A full example is available in the
|
|
||||||
> [examples directory](https://github.com/actix/actix-web/tree/master/examples/diesel/).
|
|
||||||
|
|
||||||
> More information on sync actors can be found in the
|
|
||||||
> [actix documentation](https://docs.rs/actix/0.5.0/actix/sync/index.html).
|
|
@ -1,100 +0,0 @@
|
|||||||
# Getting Started
|
|
||||||
|
|
||||||
Let’s write our first actix web application!
|
|
||||||
|
|
||||||
## Hello, world!
|
|
||||||
|
|
||||||
Start by creating a new binary-based Cargo project and changing into the new directory:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo new hello-world --bin
|
|
||||||
cd hello-world
|
|
||||||
```
|
|
||||||
|
|
||||||
Now, add actix and actix web as dependencies of your project by ensuring your Cargo.toml
|
|
||||||
contains the following:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[dependencies]
|
|
||||||
actix = "0.5"
|
|
||||||
actix-web = "0.5"
|
|
||||||
```
|
|
||||||
|
|
||||||
In order to implement a web server, we first need to create a request handler.
|
|
||||||
|
|
||||||
A request handler is a function that accepts an `HttpRequest` instance as its only parameter
|
|
||||||
and returns a type that can be converted into `HttpResponse`:
|
|
||||||
|
|
||||||
Filename: src/main.rs
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# use actix_web::*;
|
|
||||||
fn index(req: HttpRequest) -> &'static str {
|
|
||||||
"Hello world!"
|
|
||||||
}
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
Next, create an `Application` instance and register the
|
|
||||||
request handler with the application's `resource` on a particular *HTTP method* and *path*::
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# use actix_web::*;
|
|
||||||
# fn index(req: HttpRequest) -> &'static str {
|
|
||||||
# "Hello world!"
|
|
||||||
# }
|
|
||||||
# fn main() {
|
|
||||||
App::new()
|
|
||||||
.resource("/", |r| r.f(index));
|
|
||||||
# }
|
|
||||||
```
|
|
||||||
|
|
||||||
After that, the application instance can be used with `HttpServer` to listen for incoming
|
|
||||||
connections. The server accepts a function that should return an `HttpHandler` instance.
|
|
||||||
For simplicity `server::new` could be used, this function is shortcut for `HttpServer::new`:
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
server::new(
|
|
||||||
|| App::new()
|
|
||||||
.resource("/", |r| r.f(index)))
|
|
||||||
.bind("127.0.0.1:8088")?
|
|
||||||
.run();
|
|
||||||
```
|
|
||||||
|
|
||||||
That's it! Now, compile and run the program with `cargo run`.
|
|
||||||
Head over to ``http://localhost:8088/`` to see the results.
|
|
||||||
|
|
||||||
The full source of src/main.rs is listed below:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# use std::thread;
|
|
||||||
extern crate actix_web;
|
|
||||||
use actix_web::{server, App, HttpRequest, HttpResponse};
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> &'static str {
|
|
||||||
"Hello world!"
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
# // In the doctest suite we can't run blocking code - deliberately leak a thread
|
|
||||||
# // If copying this example in show-all mode, make sure you skip the thread spawn
|
|
||||||
# // call.
|
|
||||||
# thread::spawn(|| {
|
|
||||||
server::new(
|
|
||||||
|| App::new()
|
|
||||||
.resource("/", |r| r.f(index)))
|
|
||||||
.bind("127.0.0.1:8088").expect("Can not bind to 127.0.0.1:8088")
|
|
||||||
.run();
|
|
||||||
# });
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Note**: actix web is built upon [actix](https://github.com/actix/actix),
|
|
||||||
> an [actor model](https://en.wikipedia.org/wiki/Actor_model) framework in Rust.
|
|
||||||
|
|
||||||
`actix::System` initializes actor system, `HttpServer` is an actor and must run within a
|
|
||||||
properly configured actix system.
|
|
||||||
|
|
||||||
> For more information, check out the [actix documentation](https://actix.github.io/actix/actix/)
|
|
||||||
> and [actix guide](https://actix.github.io/actix/guide/).
|
|
@ -1,110 +0,0 @@
|
|||||||
# Application
|
|
||||||
|
|
||||||
Actix web provides various primitives to build web servers and applications with Rust.
|
|
||||||
It provides routing, middlewares, pre-processing of requests, post-processing of responses,
|
|
||||||
websocket protocol handling, multipart streams, etc.
|
|
||||||
|
|
||||||
All actix web servers are built around the `App` instance.
|
|
||||||
It is used for registering routes for resources and middlewares.
|
|
||||||
It also stores application state shared across all handlers within same application.
|
|
||||||
|
|
||||||
Applications act as a namespace for all routes, i.e all routes for a specific application
|
|
||||||
have the same url path prefix. The application prefix always contains a leading "/" slash.
|
|
||||||
If a supplied prefix does not contain leading slash, it is automatically inserted.
|
|
||||||
The prefix should consist of value path segments.
|
|
||||||
|
|
||||||
> For an application with prefix `/app`,
|
|
||||||
> any request with the paths `/app`, `/app/`, or `/app/test` would match;
|
|
||||||
> however, the path `/application` would not match.
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate tokio_core;
|
|
||||||
# use actix_web::{*, http::Method};
|
|
||||||
# fn index(req: HttpRequest) -> &'static str {
|
|
||||||
# "Hello world!"
|
|
||||||
# }
|
|
||||||
# fn main() {
|
|
||||||
let app = App::new()
|
|
||||||
.prefix("/app")
|
|
||||||
.resource("/index.html", |r| r.method(Method::GET).f(index))
|
|
||||||
.finish()
|
|
||||||
# }
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example, an application with the `/app` prefix and a `index.html` resource
|
|
||||||
are created. This resource is available through the `/app/index.html` url.
|
|
||||||
|
|
||||||
> For more information, check the
|
|
||||||
> [URL Dispatch](./qs_5.html#using-a-application-prefix-to-compose-applications) section.
|
|
||||||
|
|
||||||
Multiple applications can be served with one server:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate tokio_core;
|
|
||||||
# use tokio_core::net::TcpStream;
|
|
||||||
# use std::net::SocketAddr;
|
|
||||||
use actix_web::{server, App, HttpResponse};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
server::new(|| vec![
|
|
||||||
App::new()
|
|
||||||
.prefix("/app1")
|
|
||||||
.resource("/", |r| r.f(|r| HttpResponse::Ok())),
|
|
||||||
App::new()
|
|
||||||
.prefix("/app2")
|
|
||||||
.resource("/", |r| r.f(|r| HttpResponse::Ok())),
|
|
||||||
App::new()
|
|
||||||
.resource("/", |r| r.f(|r| HttpResponse::Ok())),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
All `/app1` requests route to the first application, `/app2` to the second, and all other to the third.
|
|
||||||
**Applications get matched based on registration order**. If an application with a more generic
|
|
||||||
prefix is registered before a less generic one, it would effectively block the less generic
|
|
||||||
application matching. For example, if an `App` with the prefix `"/"` was registered
|
|
||||||
as the first application, it would match all incoming requests.
|
|
||||||
|
|
||||||
## State
|
|
||||||
|
|
||||||
Application state is shared with all routes and resources within the same application.
|
|
||||||
When using an http actor,state can be accessed with the `HttpRequest::state()` as read-only,
|
|
||||||
but interior mutability with `RefCell` can be used to achieve state mutability.
|
|
||||||
State is also available for route matching predicates and middlewares.
|
|
||||||
|
|
||||||
Let's write a simple application that uses shared state. We are going to store request count
|
|
||||||
in the state:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix;
|
|
||||||
# extern crate actix_web;
|
|
||||||
#
|
|
||||||
use std::cell::Cell;
|
|
||||||
use actix_web::{App, HttpRequest, http};
|
|
||||||
|
|
||||||
// This struct represents state
|
|
||||||
struct AppState {
|
|
||||||
counter: Cell<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn index(req: HttpRequest<AppState>) -> String {
|
|
||||||
let count = req.state().counter.get() + 1; // <- get count
|
|
||||||
req.state().counter.set(count); // <- store new count in state
|
|
||||||
|
|
||||||
format!("Request number: {}", count) // <- response with count
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::with_state(AppState{counter: Cell::new(0)})
|
|
||||||
.resource("/", |r| r.method(http::Method::GET).f(index))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Note**: http server accepts an application factory rather than an application
|
|
||||||
> instance. Http server constructs an application instance for each thread, thus application state
|
|
||||||
> must be constructed multiple times. If you want to share state between different threads, a
|
|
||||||
> shared object should be used, e.g. `Arc`. Application state does not need to be `Send` and `Sync`,
|
|
||||||
> but the application factory must be `Send` + `Sync`.
|
|
@ -1,210 +0,0 @@
|
|||||||
# Server
|
|
||||||
|
|
||||||
The [**HttpServer**](../actix_web/server/struct.HttpServer.html) type is responsible for
|
|
||||||
serving http requests.
|
|
||||||
|
|
||||||
`HttpServer` accepts an application factory as a parameter, and the
|
|
||||||
application factory must have `Send` + `Sync` boundaries. More about that in the
|
|
||||||
*multi-threading* section.
|
|
||||||
|
|
||||||
To bind to a specific socket address, `bind()` must be used, and it may be called multiple times.
|
|
||||||
To start the http server, one of the start methods.
|
|
||||||
|
|
||||||
- use `start()` for a simple server
|
|
||||||
- use `start_tls()` or `start_ssl()` for a ssl server
|
|
||||||
|
|
||||||
`HttpServer` is an actix actor. It must be initialized within a properly configured actix system:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix;
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::{server::HttpServer, App, HttpResponse};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let sys = actix::System::new("guide");
|
|
||||||
|
|
||||||
HttpServer::new(
|
|
||||||
|| App::new()
|
|
||||||
.resource("/", |r| r.f(|_| HttpResponse::Ok())))
|
|
||||||
.bind("127.0.0.1:59080").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
# actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> It is possible to start a server in a separate thread with the `spawn()` method. In that
|
|
||||||
> case the server spawns a new thread and creates a new actix system in it. To stop
|
|
||||||
> this server, send a `StopServer` message.
|
|
||||||
|
|
||||||
`HttpServer` is implemented as an actix actor. It is possible to communicate with the server
|
|
||||||
via a messaging system. All start methods, e.g. `start()` and `start_ssl()`, return the
|
|
||||||
address of the started http server. It accepts several messages:
|
|
||||||
|
|
||||||
- `PauseServer` - Pause accepting incoming connections
|
|
||||||
- `ResumeServer` - Resume accepting incoming connections
|
|
||||||
- `StopServer` - Stop incoming connection processing, stop all workers and exit
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate futures;
|
|
||||||
# extern crate actix;
|
|
||||||
# extern crate actix_web;
|
|
||||||
# use futures::Future;
|
|
||||||
use std::thread;
|
|
||||||
use std::sync::mpsc;
|
|
||||||
use actix_web::{server, App, HttpResponse};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let (tx, rx) = mpsc::channel();
|
|
||||||
|
|
||||||
thread::spawn(move || {
|
|
||||||
let sys = actix::System::new("http-server");
|
|
||||||
let addr = server::new(
|
|
||||||
|| App::new()
|
|
||||||
.resource("/", |r| r.f(|_| HttpResponse::Ok())))
|
|
||||||
.bind("127.0.0.1:0").expect("Can not bind to 127.0.0.1:0")
|
|
||||||
.shutdown_timeout(60) // <- Set shutdown timeout to 60 seconds
|
|
||||||
.start();
|
|
||||||
let _ = tx.send(addr);
|
|
||||||
let _ = sys.run();
|
|
||||||
});
|
|
||||||
|
|
||||||
let addr = rx.recv().unwrap();
|
|
||||||
let _ = addr.send(
|
|
||||||
server::StopServer{graceful:true}).wait(); // <- Send `StopServer` message to server.
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Multi-threading
|
|
||||||
|
|
||||||
`HttpServer` automatically starts an number of http workers, by default
|
|
||||||
this number is equal to number of logical CPUs in the system. This number
|
|
||||||
can be overridden with the `HttpServer::threads()` method.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate tokio_core;
|
|
||||||
use actix_web::{App, HttpResponse, server::HttpServer};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
HttpServer::new(
|
|
||||||
|| App::new()
|
|
||||||
.resource("/", |r| r.f(|_| HttpResponse::Ok())))
|
|
||||||
.threads(4); // <- Start 4 workers
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The server creates a separate application instance for each created worker. Application state
|
|
||||||
is not shared between threads. To share state, `Arc` could be used.
|
|
||||||
|
|
||||||
> Application state does not need to be `Send` and `Sync`,
|
|
||||||
> but factories must be `Send` + `Sync`.
|
|
||||||
|
|
||||||
## SSL
|
|
||||||
|
|
||||||
There are two features for ssl server: `tls` and `alpn`. The `tls` feature is for `native-tls`
|
|
||||||
integration and `alpn` is for `openssl`.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[dependencies]
|
|
||||||
actix-web = { git = "https://github.com/actix/actix-web", features=["alpn"] }
|
|
||||||
```
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
use std::fs::File;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
// load ssl keys
|
|
||||||
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
|
|
||||||
builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap();
|
|
||||||
builder.set_certificate_chain_file("cert.pem").unwrap();
|
|
||||||
|
|
||||||
server::new(
|
|
||||||
|| App::new()
|
|
||||||
.resource("/index.html", |r| r.f(index)))
|
|
||||||
.bind("127.0.0.1:8080").unwrap()
|
|
||||||
.serve_ssl(builder).unwrap();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> **Note**: the *HTTP/2.0* protocol requires
|
|
||||||
> [tls alpn](https://tools.ietf.org/html/rfc7301).
|
|
||||||
> At the moment, only `openssl` has `alpn` support.
|
|
||||||
> For a full example, check out
|
|
||||||
> [examples/tls](https://github.com/actix/actix-web/tree/master/examples/tls).
|
|
||||||
|
|
||||||
## Keep-Alive
|
|
||||||
|
|
||||||
Actix can wait for requests on a keep-alive connection.
|
|
||||||
|
|
||||||
> *keep alive* connection behavior is defined by server settings.
|
|
||||||
|
|
||||||
- `75`, `Some(75)`, `KeepAlive::Timeout(75)` - enable 75 second *keep alive* timer.
|
|
||||||
- `None` or `KeepAlive::Disabled` - disable *keep alive*.
|
|
||||||
- `KeepAlive::Tcp(75)` - use `SO_KEEPALIVE` socket option.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate tokio_core;
|
|
||||||
use actix_web::{server, App, HttpResponse};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
server::new(||
|
|
||||||
App::new()
|
|
||||||
.resource("/", |r| r.f(|_| HttpResponse::Ok())))
|
|
||||||
.keep_alive(75); // <- Set keep-alive to 75 seconds
|
|
||||||
|
|
||||||
server::new(||
|
|
||||||
App::new()
|
|
||||||
.resource("/", |r| r.f(|_| HttpResponse::Ok())))
|
|
||||||
.keep_alive(server::KeepAlive::Tcp(75)); // <- Use `SO_KEEPALIVE` socket option.
|
|
||||||
|
|
||||||
server::new(||
|
|
||||||
App::new()
|
|
||||||
.resource("/", |r| r.f(|_| HttpResponse::Ok())))
|
|
||||||
.keep_alive(None); // <- Disable keep-alive
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If the first option is selected, then *keep alive* state is
|
|
||||||
calculated based on the response's *connection-type*. By default
|
|
||||||
`HttpResponse::connection_type` is not defined. In that case *keep alive* is
|
|
||||||
defined by the request's http version.
|
|
||||||
|
|
||||||
> *keep alive* is **off** for *HTTP/1.0* and is **on** for *HTTP/1.1* and *HTTP/2.0*.
|
|
||||||
|
|
||||||
*Connection type* can be change with `HttpResponseBuilder::connection_type()` method.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::{HttpRequest, HttpResponse, http};
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
HttpResponse::Ok()
|
|
||||||
.connection_type(http::ConnectionType::Close) // <- Close connection
|
|
||||||
.force_close() // <- Alternative method
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Graceful shutdown
|
|
||||||
|
|
||||||
`HttpServer` supports graceful shutdown. After receiving a stop signal, workers
|
|
||||||
have a specific amount of time to finish serving requests. Any workers still alive after the
|
|
||||||
timeout are force-dropped. By default the shutdown timeout is set to 30 seconds.
|
|
||||||
You can change this parameter with the `HttpServer::shutdown_timeout()` method.
|
|
||||||
|
|
||||||
You can send a stop message to the server with the server address and specify if you want
|
|
||||||
graceful shutdown or not. The `start()` methods returns address of the server.
|
|
||||||
|
|
||||||
`HttpServer` handles several OS signals. *CTRL-C* is available on all OSs,
|
|
||||||
other signals are available on unix systems.
|
|
||||||
|
|
||||||
- *SIGINT* - Force shutdown workers
|
|
||||||
- *SIGTERM* - Graceful shutdown workers
|
|
||||||
- *SIGQUIT* - Force shutdown workers
|
|
||||||
|
|
||||||
> It is possible to disable signal handling with `HttpServer::disable_signals()` method.
|
|
@ -1,313 +0,0 @@
|
|||||||
# Handler
|
|
||||||
|
|
||||||
A request handler can be any object that implements
|
|
||||||
[`Handler` trait](../actix_web/dev/trait.Handler.html).
|
|
||||||
|
|
||||||
Request handling happens in two stages. First the handler object is called,
|
|
||||||
returning any object that implements the
|
|
||||||
[`Responder` trait](../actix_web/trait.Responder.html#foreign-impls).
|
|
||||||
Then, `respond_to()` is called on the returned object, converting itself to a `Reply` or `Error`.
|
|
||||||
|
|
||||||
By default actix provides `Responder` implementations for some standard types,
|
|
||||||
such as `&'static str`, `String`, etc.
|
|
||||||
|
|
||||||
> For a complete list of implementations, check
|
|
||||||
> [*Responder documentation*](../actix_web/trait.Responder.html#foreign-impls).
|
|
||||||
|
|
||||||
Examples of valid handlers:
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
fn index(req: HttpRequest) -> &'static str {
|
|
||||||
"Hello world!"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
fn index(req: HttpRequest) -> String {
|
|
||||||
"Hello world!".to_owned()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
fn index(req: HttpRequest) -> Bytes {
|
|
||||||
Bytes::from_static("Hello world!")
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
*Handler* trait is generic over *S*, which defines the application state's type.
|
|
||||||
Application state is accessible from the handler with the `HttpRequest::state()` method;
|
|
||||||
however, state is accessible as a read-only reference. If you need mutable access to state,
|
|
||||||
it must be implemented.
|
|
||||||
|
|
||||||
> **Note**: Alternatively, the handler can mutably access its own state because the `handle` method takes
|
|
||||||
> mutable reference to *self*. **Beware**, actix creates multiple copies
|
|
||||||
> of the application state and the handlers, unique for each thread. If you run your
|
|
||||||
> application in several threads, actix will create the same amount as number of threads
|
|
||||||
> of application state objects and handler objects.
|
|
||||||
|
|
||||||
Here is an example of a handler that stores the number of processed requests:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::{App, HttpRequest, HttpResponse, dev::Handler};
|
|
||||||
|
|
||||||
struct MyHandler(usize);
|
|
||||||
|
|
||||||
impl<S> Handler<S> for MyHandler {
|
|
||||||
type Result = HttpResponse;
|
|
||||||
|
|
||||||
/// Handle request
|
|
||||||
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
|
||||||
self.0 += 1;
|
|
||||||
HttpResponse::Ok().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
Although this handler will work, `self.0` will be different depending on the number of threads and
|
|
||||||
number of requests processed per thread. A proper implementation would use `Arc` and `AtomicUsize`.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix;
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::{server, App, HttpRequest, HttpResponse, dev::Handler};
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
||||||
|
|
||||||
struct MyHandler(Arc<AtomicUsize>);
|
|
||||||
|
|
||||||
impl<S> Handler<S> for MyHandler {
|
|
||||||
type Result = HttpResponse;
|
|
||||||
|
|
||||||
/// Handle request
|
|
||||||
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
|
||||||
self.0.fetch_add(1, Ordering::Relaxed);
|
|
||||||
HttpResponse::Ok().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let sys = actix::System::new("example");
|
|
||||||
|
|
||||||
let inc = Arc::new(AtomicUsize::new(0));
|
|
||||||
|
|
||||||
server::new(
|
|
||||||
move || {
|
|
||||||
let cloned = inc.clone();
|
|
||||||
App::new()
|
|
||||||
.resource("/", move |r| r.h(MyHandler(cloned)))
|
|
||||||
})
|
|
||||||
.bind("127.0.0.1:8088").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8088");
|
|
||||||
# actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> Be careful with synchronization primitives like `Mutex` or `RwLock`. Actix web framework
|
|
||||||
> handles requests asynchronously. By blocking thread execution, all concurrent
|
|
||||||
> request handling processes would block. If you need to share or update some state
|
|
||||||
> from multiple threads, consider using the [actix](https://actix.github.io/actix/actix/) actor system.
|
|
||||||
|
|
||||||
## Response with custom type
|
|
||||||
|
|
||||||
To return a custom type directly from a handler function, the type needs to implement the `Responder` trait.
|
|
||||||
|
|
||||||
Let's create a response for a custom type that serializes to an `application/json` response:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix;
|
|
||||||
# extern crate actix_web;
|
|
||||||
extern crate serde;
|
|
||||||
extern crate serde_json;
|
|
||||||
#[macro_use] extern crate serde_derive;
|
|
||||||
use actix_web::{server, App, HttpRequest, HttpResponse, Error, Responder, http};
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct MyObj {
|
|
||||||
name: &'static str,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Responder
|
|
||||||
impl Responder for MyObj {
|
|
||||||
type Item = HttpResponse;
|
|
||||||
type Error = Error;
|
|
||||||
|
|
||||||
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, Error> {
|
|
||||||
let body = serde_json::to_string(&self)?;
|
|
||||||
|
|
||||||
// Create response and set content type
|
|
||||||
Ok(HttpResponse::Ok()
|
|
||||||
.content_type("application/json")
|
|
||||||
.body(body))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Because `MyObj` implements `Responder`, it is possible to return it directly
|
|
||||||
fn index(req: HttpRequest) -> MyObj {
|
|
||||||
MyObj{name: "user"}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let sys = actix::System::new("example");
|
|
||||||
|
|
||||||
server::new(
|
|
||||||
|| App::new()
|
|
||||||
.resource("/", |r| r.method(http::Method::GET).f(index)))
|
|
||||||
.bind("127.0.0.1:8088").unwrap()
|
|
||||||
.start();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8088");
|
|
||||||
# actix::Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Async handlers
|
|
||||||
|
|
||||||
There are two different types of async handlers. Response objects can be generated asynchronously
|
|
||||||
or more precisely, any type that implements the [*Responder*](../actix_web/trait.Responder.html) trait.
|
|
||||||
|
|
||||||
In this case, the handler must return a `Future` object that resolves to the *Responder* type, i.e:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate futures;
|
|
||||||
# extern crate bytes;
|
|
||||||
# use actix_web::*;
|
|
||||||
# use bytes::Bytes;
|
|
||||||
# use futures::stream::once;
|
|
||||||
# use futures::future::{Future, result};
|
|
||||||
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
|
|
||||||
result(Ok(HttpResponse::Ok()
|
|
||||||
.content_type("text/html")
|
|
||||||
.body(format!("Hello!"))))
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn index2(req: HttpRequest) -> Box<Future<Item=&'static str, Error=Error>> {
|
|
||||||
result(Ok("Welcome!"))
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.resource("/async", |r| r.route().a(index))
|
|
||||||
.resource("/", |r| r.route().a(index2))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Or the response body can be generated asynchronously. In this case, body
|
|
||||||
must implement the stream trait `Stream<Item=Bytes, Error=Error>`, i.e:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate futures;
|
|
||||||
# extern crate bytes;
|
|
||||||
# use actix_web::*;
|
|
||||||
# use bytes::Bytes;
|
|
||||||
# use futures::stream::once;
|
|
||||||
fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
let body = once(Ok(Bytes::from_static(b"test")));
|
|
||||||
|
|
||||||
HttpResponse::Ok()
|
|
||||||
.content_type("application/json")
|
|
||||||
.body(Body::Streaming(Box::new(body)))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.resource("/async", |r| r.f(index))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Both methods can be combined. (i.e Async response with streaming body)
|
|
||||||
|
|
||||||
It is possible to return a `Result` where the `Result::Item` type can be `Future`.
|
|
||||||
In this example, the `index` handler can return an error immediately or return a
|
|
||||||
future that resolves to a `HttpResponse`.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate futures;
|
|
||||||
# extern crate bytes;
|
|
||||||
# use actix_web::*;
|
|
||||||
# use bytes::Bytes;
|
|
||||||
# use futures::stream::once;
|
|
||||||
# use futures::future::{Future, result};
|
|
||||||
fn index(req: HttpRequest) -> Result<Box<Future<Item=HttpResponse, Error=Error>>, Error> {
|
|
||||||
if is_error() {
|
|
||||||
Err(error::ErrorBadRequest("bad request"))
|
|
||||||
} else {
|
|
||||||
Ok(Box::new(
|
|
||||||
result(Ok(HttpResponse::Ok()
|
|
||||||
.content_type("text/html")
|
|
||||||
.body(format!("Hello!"))))))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# fn is_error() -> bool { true }
|
|
||||||
# fn main() {
|
|
||||||
# App::new()
|
|
||||||
# .resource("/async", |r| r.route().f(index))
|
|
||||||
# .finish();
|
|
||||||
# }
|
|
||||||
```
|
|
||||||
|
|
||||||
## Different return types (Either)
|
|
||||||
|
|
||||||
Sometimes, you need to return different types of responses. For example,
|
|
||||||
you can error check and return errors, return async responses, or any result that requires two different types.
|
|
||||||
|
|
||||||
For this case, the [`Either`](../actix_web/enum.Either.html) type can be used.
|
|
||||||
`Either` allows combining two different responder types into a single type.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate futures;
|
|
||||||
# use actix_web::*;
|
|
||||||
# use futures::future::Future;
|
|
||||||
use futures::future::result;
|
|
||||||
use actix_web::{Either, Error, HttpResponse};
|
|
||||||
|
|
||||||
type RegisterResult = Either<HttpResponse, Box<Future<Item=HttpResponse, Error=Error>>>;
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> RegisterResult {
|
|
||||||
if is_a_variant() { // <- choose variant A
|
|
||||||
Either::A(
|
|
||||||
HttpResponse::BadRequest().body("Bad data"))
|
|
||||||
} else {
|
|
||||||
Either::B( // <- variant B
|
|
||||||
result(Ok(HttpResponse::Ok()
|
|
||||||
.content_type("text/html")
|
|
||||||
.body(format!("Hello!")))).responder())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# fn is_a_variant() -> bool { true }
|
|
||||||
# fn main() {
|
|
||||||
# App::new()
|
|
||||||
# .resource("/register", |r| r.f(index))
|
|
||||||
# .finish();
|
|
||||||
# }
|
|
||||||
```
|
|
||||||
|
|
||||||
## Tokio core handle
|
|
||||||
|
|
||||||
Any actix web handler runs within a properly configured
|
|
||||||
[actix system](https://actix.github.io/actix/actix/struct.System.html)
|
|
||||||
and [arbiter](https://actix.github.io/actix/actix/struct.Arbiter.html).
|
|
||||||
You can always get access to the tokio handle via the
|
|
||||||
[Arbiter::handle()](https://actix.github.io/actix/actix/struct.Arbiter.html#method.handle)
|
|
||||||
method.
|
|
@ -1,153 +0,0 @@
|
|||||||
# Errors
|
|
||||||
|
|
||||||
Actix uses the [`Error` type](../actix_web/error/struct.Error.html)
|
|
||||||
and [`ResponseError` trait](../actix_web/error/trait.ResponseError.html)
|
|
||||||
for handling handler's errors.
|
|
||||||
|
|
||||||
Any error that implements the `ResponseError` trait can be returned as an error value.
|
|
||||||
`Handler` can return an `Result` object. By default, actix provides a
|
|
||||||
`Responder` implementation for compatible result types. Here is the implementation
|
|
||||||
definition:
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
impl<T: Responder, E: Into<Error>> Responder for Result<T, E>
|
|
||||||
```
|
|
||||||
|
|
||||||
Any error that implements `ResponseError` can be converted into an `Error` object.
|
|
||||||
|
|
||||||
For example, if the *handler* function returns `io::Error`, it would be converted
|
|
||||||
into an `HttpInternalServerError` response. Implementation for `io::Error` is provided
|
|
||||||
by default.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# use actix_web::*;
|
|
||||||
use std::io;
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> io::Result<fs::NamedFile> {
|
|
||||||
Ok(fs::NamedFile::open("static/index.html")?)
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# fn main() {
|
|
||||||
# App::new()
|
|
||||||
# .resource(r"/a/index.html", |r| r.f(index))
|
|
||||||
# .finish();
|
|
||||||
# }
|
|
||||||
```
|
|
||||||
|
|
||||||
## Custom error response
|
|
||||||
|
|
||||||
To add support for custom errors, all we need to do is implement the `ResponseError` trait
|
|
||||||
for the custom error type. The `ResponseError` trait has a default implementation
|
|
||||||
for the `error_response()` method: it generates a *500* response.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
#[macro_use] extern crate failure;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
#[derive(Fail, Debug)]
|
|
||||||
#[fail(display="my error")]
|
|
||||||
struct MyError {
|
|
||||||
name: &'static str
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Use default implementation for `error_response()` method
|
|
||||||
impl error::ResponseError for MyError {}
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> Result<&'static str, MyError> {
|
|
||||||
Err(MyError{name: "test"})
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# fn main() {
|
|
||||||
# App::new()
|
|
||||||
# .resource(r"/a/index.html", |r| r.f(index))
|
|
||||||
# .finish();
|
|
||||||
# }
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example the *index* handler always returns a *500* response. But it is easy
|
|
||||||
to return different responses for different types of errors.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
#[macro_use] extern crate failure;
|
|
||||||
use actix_web::{App, HttpRequest, HttpResponse, http, error};
|
|
||||||
|
|
||||||
#[derive(Fail, Debug)]
|
|
||||||
enum MyError {
|
|
||||||
#[fail(display="internal error")]
|
|
||||||
InternalError,
|
|
||||||
#[fail(display="bad request")]
|
|
||||||
BadClientData,
|
|
||||||
#[fail(display="timeout")]
|
|
||||||
Timeout,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl error::ResponseError for MyError {
|
|
||||||
fn error_response(&self) -> HttpResponse {
|
|
||||||
match *self {
|
|
||||||
MyError::InternalError => HttpResponse::new(
|
|
||||||
http::StatusCode::INTERNAL_SERVER_ERROR),
|
|
||||||
MyError::BadClientData => HttpResponse::new(
|
|
||||||
http::StatusCode::BAD_REQUEST),
|
|
||||||
MyError::Timeout => HttpResponse::new(
|
|
||||||
http::StatusCode::GATEWAY_TIMEOUT),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> Result<&'static str, MyError> {
|
|
||||||
Err(MyError::BadClientData)
|
|
||||||
}
|
|
||||||
#
|
|
||||||
# fn main() {
|
|
||||||
# App::new()
|
|
||||||
# .resource(r"/a/index.html", |r| r.f(index))
|
|
||||||
# .finish();
|
|
||||||
# }
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error helpers
|
|
||||||
|
|
||||||
Actix provides a set of error helper types. It is possible to use them for generating
|
|
||||||
specific error responses. We can use the helper types for the first example with a custom error.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
#[macro_use] extern crate failure;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct MyError {
|
|
||||||
name: &'static str
|
|
||||||
}
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> Result<&'static str> {
|
|
||||||
let result: Result<&'static str, MyError> = Err(MyError{name: "test"});
|
|
||||||
|
|
||||||
Ok(result.map_err(|e| error::ErrorBadRequest(e))?)
|
|
||||||
}
|
|
||||||
# fn main() {
|
|
||||||
# App::new()
|
|
||||||
# .resource(r"/a/index.html", |r| r.f(index))
|
|
||||||
# .finish();
|
|
||||||
# }
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example, a *BAD REQUEST* response is generated for the `MyError` error.
|
|
||||||
|
|
||||||
## Error logging
|
|
||||||
|
|
||||||
Actix logs all errors with the log level `WARN`. If log level set to `DEBUG`
|
|
||||||
and `RUST_BACKTRACE` is enabled, the backtrace gets logged. The Error type uses
|
|
||||||
the cause's error backtrace if available. If the underlying failure does not provide
|
|
||||||
a backtrace, a new backtrace is constructed pointing to that conversion point
|
|
||||||
(rather than the origin of the error). This construction only happens if there
|
|
||||||
is no underlying backtrace; if it does have a backtrace, no new backtrace is constructed.
|
|
||||||
|
|
||||||
You can enable backtrace and debug logging with following command:
|
|
||||||
|
|
||||||
```
|
|
||||||
>> RUST_BACKTRACE=1 RUST_LOG=actix_web=debug cargo run
|
|
||||||
```
|
|
@ -1,654 +0,0 @@
|
|||||||
# URL Dispatch
|
|
||||||
|
|
||||||
URL dispatch provides a simple way for mapping URLs to `Handler` code using a simple pattern
|
|
||||||
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](../qs_4.html).
|
|
||||||
|
|
||||||
## Resource configuration
|
|
||||||
|
|
||||||
Resource configuration is the act of adding a new resources 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*.
|
|
||||||
It does not match against the *QUERY* portion (the portion following the scheme and
|
|
||||||
port, e.g., */foo/bar* in the *URL* *http://localhost:8080/foo/bar?q=value*).
|
|
||||||
|
|
||||||
The [App::route](../actix_web/struct.App.html#method.route) method provides
|
|
||||||
simple way of registering routes. This method adds a single route to application
|
|
||||||
routing table. This method accepts a *path pattern*,
|
|
||||||
*http method* and a handler function. `route()` method could be called multiple times
|
|
||||||
for the same path, in that case, multiple routes register for the same resource path.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::{App, HttpRequest, HttpResponse, http::Method};
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.route("/user/{name}", Method::GET, index)
|
|
||||||
.route("/user/{name}", Method::POST, index)
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
While *App::route()* provides simple way of registering routes, to access
|
|
||||||
complete resource configuration, different method has to be used.
|
|
||||||
The [App::resource](../actix_web/struct.App.html#method.resource) method
|
|
||||||
adds a single resource to application routing table. This method accepts a *path pattern*
|
|
||||||
and a resource configuration function.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::{App, HttpRequest, HttpResponse, http::Method};
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.resource("/prefix", |r| r.f(index))
|
|
||||||
.resource("/user/{name}",
|
|
||||||
|r| r.method(Method::GET).f(|req| HttpResponse::Ok()))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The *Configuration function* has the following type:
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
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.
|
|
||||||
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
|
|
||||||
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.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# use actix_web::*;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.resource("/path", |resource|
|
|
||||||
resource.route()
|
|
||||||
.filter(pred::Get())
|
|
||||||
.filter(pred::Header("content-type", "text/plain"))
|
|
||||||
.f(|req| HttpResponse::Ok())
|
|
||||||
)
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
If a resource can not match any route, a "NOT FOUND" response is returned.
|
|
||||||
|
|
||||||
[*Resource::route()*](../actix_web/struct.Resource.html#method.route) returns a
|
|
||||||
[*Route*](../actix_web/struct.Route.html) object. Route can be configured with a
|
|
||||||
builder-like pattern. Following configuration methods are available:
|
|
||||||
|
|
||||||
* [*Route::filter()*](../actix_web/struct.Route.html#method.filter) registers a new predicate.
|
|
||||||
Any number of predicates can be registered for each route.
|
|
||||||
|
|
||||||
* [*Route::f()*](../actix_web/struct.Route.html#method.f) 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`
|
|
||||||
|
|
||||||
* [*Route::h()*](../actix_web/struct.Route.html#method.h) registers a handler object
|
|
||||||
that implements the `Handler` trait. This is similar to `f()` method - only one handler can
|
|
||||||
be registered. Handler registration is the last config operation.
|
|
||||||
|
|
||||||
* [*Route::a()*](../actix_web/struct.Route.html#method.a) 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,
|
|
||||||
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,
|
|
||||||
the *default resource* is used as the 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
|
|
||||||
the route is invoked. If no route matches after all route patterns are exhausted, a *NOT FOUND* response get returned.
|
|
||||||
|
|
||||||
## Resource pattern syntax
|
|
||||||
|
|
||||||
The syntax of the pattern matching language used by 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**: 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 values can be retrieved with
|
|
||||||
[`Params::get()`](../actix_web/dev/struct.Params.html#method.get).
|
|
||||||
|
|
||||||
Any matched parameter can be deserialized into a specific type if the type
|
|
||||||
implements the `FromParam` trait. For example most standard integer types
|
|
||||||
the trait, i.e.:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# 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() {
|
|
||||||
App::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.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use actix_web::{App, HttpRequest, Result, http::Method};
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> Result<String> {
|
|
||||||
let path: PathBuf = req.match_info().query("tail")?;
|
|
||||||
Ok(format!("Path {:?}", path))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
List of `FromParam` implementations can be found in
|
|
||||||
[api docs](../actix_web/dev/trait.FromParam.html#foreign-impls)
|
|
||||||
|
|
||||||
## Path information extractor
|
|
||||||
|
|
||||||
Actix provides functionality for type safe path information extraction.
|
|
||||||
[Path](../actix_web/struct.Path.html) extracts information, destination type
|
|
||||||
could be defined in several different forms. Simplest approach is to use
|
|
||||||
`tuple` type. Each element in tuple must correpond to one element from
|
|
||||||
path pattern. i.e. you can match path pattern `/{id}/{username}/` against
|
|
||||||
`Pyth<(u32, String)>` type, but `Path<(String, String, String)>` type will
|
|
||||||
always fail.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::{App, Path, Result, http::Method};
|
|
||||||
|
|
||||||
// extract path info using serde
|
|
||||||
fn index(info: Path<(String, u32)>) -> Result<String> {
|
|
||||||
Ok(format!("Welcome {}! id: {}", info.0, info.1))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let app = App::new()
|
|
||||||
.resource("/{username}/{id}/index.html", // <- define path parameters
|
|
||||||
|r| r.method(Method::GET).with(index));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
It also possible to extract path pattern information to a struct. In this case,
|
|
||||||
this struct must implement *serde's *`Deserialize` trait.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
#[macro_use] extern crate serde_derive;
|
|
||||||
use actix_web::{App, Path, Result, http::Method};
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct Info {
|
|
||||||
username: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
// extract path info using serde
|
|
||||||
fn index(info: Path<Info>) -> Result<String> {
|
|
||||||
Ok(format!("Welcome {}!", info.username))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let app = App::new()
|
|
||||||
.resource("/{username}/index.html", // <- define path parameters
|
|
||||||
|r| r.method(Method::GET).with(index));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
[Query](../actix_web/struct.Query.html) provides similar functionality for
|
|
||||||
request query parameters.
|
|
||||||
|
|
||||||
|
|
||||||
## 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::{App, Result, HttpRequest, HttpResponse, http::Method, http::header};
|
|
||||||
#
|
|
||||||
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())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let app = App::new()
|
|
||||||
.resource("/test/{a}/{b}/{c}", |r| {
|
|
||||||
r.name("foo"); // <- set resource name, then it could be used in `url_for`
|
|
||||||
r.method(Method::GET).f(|_| HttpResponse::Ok());
|
|
||||||
})
|
|
||||||
.route("/test/", Method::GET, index)
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This would return something like the string *http://example.com/test/1/2/3* (at least if
|
|
||||||
the current protocol and hostname implied http://example.com).
|
|
||||||
`url_for()` method returns [*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, can 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::{App, HttpRequest, HttpResponse, Error};
|
|
||||||
|
|
||||||
fn index(mut req: HttpRequest) -> Result<HttpResponse, Error> {
|
|
||||||
let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
|
|
||||||
assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
|
|
||||||
Ok(HttpResponse::Ok().into())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let app = App::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 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*.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# #[macro_use] extern crate serde_derive;
|
|
||||||
# use actix_web::*;
|
|
||||||
use actix_web::http::NormalizePath;
|
|
||||||
#
|
|
||||||
# fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
# HttpResponse::Ok().into()
|
|
||||||
# }
|
|
||||||
fn main() {
|
|
||||||
let app = App::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/`.
|
|
||||||
|
|
||||||
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
|
|
||||||
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:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# #[macro_use] extern crate serde_derive;
|
|
||||||
use actix_web::{App, HttpRequest, http::Method, http::NormalizePath};
|
|
||||||
#
|
|
||||||
# fn index(req: HttpRequest) -> &'static str {
|
|
||||||
# "test"
|
|
||||||
# }
|
|
||||||
fn main() {
|
|
||||||
let app = App::new()
|
|
||||||
.resource("/resource/", |r| r.f(index))
|
|
||||||
.default_resource(|r| r.method(Method::GET).h(NormalizePath::default()))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using an Application Prefix to Compose Applications
|
|
||||||
|
|
||||||
The `App::prefix()` method allows to set a specific application prefix.
|
|
||||||
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() {
|
|
||||||
App::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 a predicate as a simple function that accepts a *request* object reference
|
|
||||||
and returns *true* or *false*. Formally, a predicate is any object that implements the
|
|
||||||
[`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 a simple predicate that check that a request contains a specific *header*:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# use actix_web::*;
|
|
||||||
use actix_web::{http, pred::Predicate, App, HttpRequest};
|
|
||||||
|
|
||||||
struct ContentTypeHeader;
|
|
||||||
|
|
||||||
impl<S: 'static> Predicate<S> for ContentTypeHeader {
|
|
||||||
|
|
||||||
fn check(&self, req: &mut HttpRequest<S>) -> bool {
|
|
||||||
req.headers().contains_key(http::header::CONTENT_TYPE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.resource("/index.html", |r|
|
|
||||||
r.route()
|
|
||||||
.filter(ContentTypeHeader)
|
|
||||||
.f(|_| HttpResponse::Ok()));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In this example, *index* handler will be called only if request contains *CONTENT-TYPE* header.
|
|
||||||
|
|
||||||
Predicates have access to the application's state via `HttpRequest::state()`.
|
|
||||||
Also predicates can store extra information in
|
|
||||||
[request extensions](../actix_web/struct.HttpRequest.html#method.extensions).
|
|
||||||
|
|
||||||
### Modifying 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::{pred, App, HttpResponse};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.resource("/index.html", |r|
|
|
||||||
r.route()
|
|
||||||
.filter(pred::Not(pred::Get()))
|
|
||||||
.f(|req| HttpResponse::MethodNotAllowed()))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `Any` predicate accepts a list of predicates and matches if any of the supplied
|
|
||||||
predicates match. i.e:
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
pred::Any(pred::Get()).or(pred::Post())
|
|
||||||
```
|
|
||||||
|
|
||||||
The `All` predicate accepts a list of predicates and matches if all of the supplied
|
|
||||||
predicates match. i.e:
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
pred::All(pred::Get()).and(pred::Header("content-type", "plain/text"))
|
|
||||||
```
|
|
||||||
|
|
||||||
## Changing the default Not Found response
|
|
||||||
|
|
||||||
If the path pattern can not be found in the routing table or a resource can not find matching
|
|
||||||
route, the default resource is used. The default response is *NOT FOUND*.
|
|
||||||
It is possible to override the *NOT FOUND* response with `App::default_resource()`.
|
|
||||||
This method accepts a *configuration function* same as normal resource configuration
|
|
||||||
with `App::resource()` method.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::{App, HttpResponse, http::Method, pred};
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.default_resource(|r| {
|
|
||||||
r.method(Method::GET).f(|req| HttpResponse::NotFound());
|
|
||||||
r.route().filter(pred::Not(pred::Get()))
|
|
||||||
.f(|req| HttpResponse::MethodNotAllowed());
|
|
||||||
})
|
|
||||||
# .finish();
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,357 +0,0 @@
|
|||||||
# Request & Response
|
|
||||||
|
|
||||||
## Response
|
|
||||||
|
|
||||||
A builder-like pattern is used to construct an instance of `HttpResponse`.
|
|
||||||
`HttpResponse` provides several methods that return a `HttpResponseBuilder` instance,
|
|
||||||
which implements various convenience methods for building responses.
|
|
||||||
|
|
||||||
> Check the [documentation](../actix_web/dev/struct.HttpResponseBuilder.html)
|
|
||||||
> for type descriptions.
|
|
||||||
|
|
||||||
The methods `.body`, `.finish`, and `.json` finalize response creation and
|
|
||||||
return a constructed *HttpResponse* instance. If this methods is called on the same
|
|
||||||
builder instance multiple times, the builder will panic.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::{HttpRequest, HttpResponse, http::ContentEncoding};
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
HttpResponse::Ok()
|
|
||||||
.content_encoding(ContentEncoding::Br)
|
|
||||||
.content_type("plain/text")
|
|
||||||
.header("X-Hdr", "sample")
|
|
||||||
.body("data")
|
|
||||||
}
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Content encoding
|
|
||||||
|
|
||||||
Actix automatically *compresses*/*decompresses* payloads. The following codecs are supported:
|
|
||||||
|
|
||||||
* Brotli
|
|
||||||
* Gzip
|
|
||||||
* Deflate
|
|
||||||
* Identity
|
|
||||||
|
|
||||||
If request headers contain a `Content-Encoding` header, the request payload is decompressed
|
|
||||||
according to the header value. Multiple codecs are not supported,
|
|
||||||
i.e: `Content-Encoding: br, gzip`.
|
|
||||||
|
|
||||||
Response payload is compressed based on the *content_encoding* parameter.
|
|
||||||
By default, `ContentEncoding::Auto` is used. If `ContentEncoding::Auto` is selected,
|
|
||||||
then the compression depends on the request's `Accept-Encoding` header.
|
|
||||||
|
|
||||||
> `ContentEncoding::Identity` can be used to disable compression.
|
|
||||||
> If another content encoding is selected, the compression is enforced for that codec.
|
|
||||||
|
|
||||||
For example, to enable `brotli` use `ContentEncoding::Br`:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::{HttpRequest, HttpResponse, http::ContentEncoding};
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
HttpResponse::Ok()
|
|
||||||
.content_encoding(ContentEncoding::Br)
|
|
||||||
.body("data")
|
|
||||||
}
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
In this case we explicitly disable content compression
|
|
||||||
by setting content encoding to a `Identity` value:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::{HttpRequest, HttpResponse, http::ContentEncoding};
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
HttpResponse::Ok()
|
|
||||||
.content_encoding(ContentEncoding::Identity) // <- disable compression
|
|
||||||
.body("data")
|
|
||||||
}
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
Also it is possible to set default content encoding on application level, by
|
|
||||||
default `ContentEncoding::Auto` is used, which implies automatic content compression
|
|
||||||
negotiation.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::{App, HttpRequest, HttpResponse, http::ContentEncoding};
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
HttpResponse::Ok()
|
|
||||||
.body("data")
|
|
||||||
}
|
|
||||||
fn main() {
|
|
||||||
let app = App::new()
|
|
||||||
.default_encoding(ContentEncoding::Identity) // <- disable compression for all routes
|
|
||||||
.resource("/index.html", |r| r.with(index));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## JSON Request
|
|
||||||
|
|
||||||
There are several options for json body deserialization.
|
|
||||||
|
|
||||||
The first option is to use *Json* extractor. First, you define a handler function
|
|
||||||
that accepts `Json<T>` as a parameter, then, you use the `.with()` method for registering
|
|
||||||
this handler. It is also possible to accept arbitrary valid json object by
|
|
||||||
using `serde_json::Value` as a type `T`.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
#[macro_use] extern crate serde_derive;
|
|
||||||
use actix_web::{App, Json, Result, http};
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct Info {
|
|
||||||
username: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// extract `Info` using serde
|
|
||||||
fn index(info: Json<Info>) -> Result<String> {
|
|
||||||
Ok(format!("Welcome {}!", info.username))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let app = App::new().resource(
|
|
||||||
"/index.html",
|
|
||||||
|r| r.method(http::Method::POST).with(index)); // <- use `with` extractor
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Another option is to use *HttpResponse::json()*. This method returns a
|
|
||||||
[*JsonBody*](../actix_web/dev/struct.JsonBody.html) object which resolves into
|
|
||||||
the deserialized value.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix;
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate futures;
|
|
||||||
# extern crate serde_json;
|
|
||||||
# #[macro_use] extern crate serde_derive;
|
|
||||||
# use actix_web::*;
|
|
||||||
# use futures::Future;
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
struct MyObj {
|
|
||||||
name: String,
|
|
||||||
number: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
req.json().from_err()
|
|
||||||
.and_then(|val: MyObj| {
|
|
||||||
println!("model: {:?}", val);
|
|
||||||
Ok(HttpResponse::Ok().json(val)) // <- send response
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
You may also manually load the payload into memory and then deserialize it.
|
|
||||||
|
|
||||||
In the following example, we will deserialize a *MyObj* struct. We need to load the request
|
|
||||||
body first and then deserialize the json into an object.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate futures;
|
|
||||||
# use actix_web::*;
|
|
||||||
# #[macro_use] extern crate serde_derive;
|
|
||||||
extern crate serde_json;
|
|
||||||
use futures::{Future, Stream};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct MyObj {name: String, number: i32}
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
// `concat2` will asynchronously read each chunk of the request body and
|
|
||||||
// return a single, concatenated, chunk
|
|
||||||
req.concat2()
|
|
||||||
// `Future::from_err` acts like `?` in that it coerces the error type from
|
|
||||||
// the future into the final error type
|
|
||||||
.from_err()
|
|
||||||
// `Future::and_then` can be used to merge an asynchronous workflow with a
|
|
||||||
// synchronous workflow
|
|
||||||
.and_then(|body| { // <- body is loaded, now we can deserialize json
|
|
||||||
let obj = serde_json::from_slice::<MyObj>(&body)?;
|
|
||||||
Ok(HttpResponse::Ok().json(obj)) // <- send response
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
> A complete example for both options is available in
|
|
||||||
> [examples directory](https://github.com/actix/actix-web/tree/master/examples/json/).
|
|
||||||
|
|
||||||
## JSON Response
|
|
||||||
|
|
||||||
The `Json` type allows to respond with well-formed JSON data: simply return a value of
|
|
||||||
type Json<T> where `T` is the type of a structure to serialize into *JSON*.
|
|
||||||
The type `T` must implement the `Serialize` trait from *serde*.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
#[macro_use] extern crate serde_derive;
|
|
||||||
use actix_web::{App, HttpRequest, Json, Result, http::Method};
|
|
||||||
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct MyObj {
|
|
||||||
name: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> Result<Json<MyObj>> {
|
|
||||||
Ok(Json(MyObj{name: req.match_info().query("name")?}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.resource(r"/a/{name}", |r| r.method(Method::GET).f(index))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Chunked transfer encoding
|
|
||||||
|
|
||||||
Actix automatically decodes *chunked* encoding. `HttpRequest::payload()` already contains
|
|
||||||
the decoded byte stream. If the request payload is compressed with one of the supported
|
|
||||||
compression codecs (br, gzip, deflate), then the byte stream is decompressed.
|
|
||||||
|
|
||||||
Chunked encoding on a response can be enabled with `HttpResponseBuilder::chunked()`.
|
|
||||||
This takes effect only for `Body::Streaming(BodyStream)` or `Body::StreamingContext` bodies.
|
|
||||||
If the response payload compression is enabled and a streaming body is used, chunked encoding
|
|
||||||
is enabled automatically.
|
|
||||||
|
|
||||||
> Enabling chunked encoding for *HTTP/2.0* responses is forbidden.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate bytes;
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate futures;
|
|
||||||
# use futures::Stream;
|
|
||||||
use actix_web::*;
|
|
||||||
use bytes::Bytes;
|
|
||||||
use futures::stream::once;
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
HttpResponse::Ok()
|
|
||||||
.chunked()
|
|
||||||
.body(Body::Streaming(Box::new(once(Ok(Bytes::from_static(b"data"))))))
|
|
||||||
}
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Multipart body
|
|
||||||
|
|
||||||
Actix provides multipart stream support.
|
|
||||||
[*Multipart*](../actix_web/multipart/struct.Multipart.html) is implemented as
|
|
||||||
a stream of multipart items. Each item can be a
|
|
||||||
[*Field*](../actix_web/multipart/struct.Field.html) or a nested *Multipart* stream.
|
|
||||||
`HttpResponse::multipart()` returns the *Multipart* stream for the current request.
|
|
||||||
|
|
||||||
The following demonstrates multipart stream handling for a simple form:
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> Box<Future<...>> {
|
|
||||||
req.multipart() // <- get multipart stream for current request
|
|
||||||
.and_then(|item| { // <- iterate over multipart items
|
|
||||||
match item {
|
|
||||||
// Handle multipart Field
|
|
||||||
multipart::MultipartItem::Field(field) => {
|
|
||||||
println!("==== FIELD ==== {:?} {:?}", field.headers(), field.content_type());
|
|
||||||
|
|
||||||
Either::A(
|
|
||||||
// Field in turn is a stream of *Bytes* objects
|
|
||||||
field.map(|chunk| {
|
|
||||||
println!("-- CHUNK: \n{}",
|
|
||||||
std::str::from_utf8(&chunk).unwrap());})
|
|
||||||
.fold((), |_, _| result(Ok(()))))
|
|
||||||
},
|
|
||||||
multipart::MultipartItem::Nested(mp) => {
|
|
||||||
// Or item could be nested Multipart stream
|
|
||||||
Either::B(result(Ok(())))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> A full example is available in the
|
|
||||||
> [examples directory](https://github.com/actix/actix-web/tree/master/examples/multipart/).
|
|
||||||
|
|
||||||
## Urlencoded body
|
|
||||||
|
|
||||||
Actix provides support for *application/x-www-form-urlencoded* encoded bodies.
|
|
||||||
`HttpResponse::urlencoded()` returns a
|
|
||||||
[*UrlEncoded*](../actix_web/dev/struct.UrlEncoded.html) future, which resolves
|
|
||||||
to the deserialized instance. The type of the instance must implement the
|
|
||||||
`Deserialize` trait from *serde*.
|
|
||||||
|
|
||||||
The *UrlEncoded* future can resolve into an error in several cases:
|
|
||||||
|
|
||||||
* content type is not `application/x-www-form-urlencoded`
|
|
||||||
* transfer encoding is `chunked`.
|
|
||||||
* content-length is greater than 256k
|
|
||||||
* payload terminates with error.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate futures;
|
|
||||||
#[macro_use] extern crate serde_derive;
|
|
||||||
use actix_web::*;
|
|
||||||
use futures::future::{Future, ok};
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct FormData {
|
|
||||||
username: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
req.urlencoded::<FormData>() // <- get UrlEncoded future
|
|
||||||
.from_err()
|
|
||||||
.and_then(|data| { // <- deserialized instance
|
|
||||||
println!("USERNAME: {:?}", data.username);
|
|
||||||
ok(HttpResponse::Ok().into())
|
|
||||||
})
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Streaming request
|
|
||||||
|
|
||||||
*HttpRequest* is a stream of `Bytes` objects. It can be used to read the request
|
|
||||||
body payload.
|
|
||||||
|
|
||||||
In the following example, we read and print the request payload chunk by chunk:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate futures;
|
|
||||||
# use futures::future::result;
|
|
||||||
use actix_web::*;
|
|
||||||
use futures::{Future, Stream};
|
|
||||||
|
|
||||||
|
|
||||||
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|
||||||
req.from_err()
|
|
||||||
.fold((), |_, chunk| {
|
|
||||||
println!("Chunk: {:?}", chunk);
|
|
||||||
result::<_, error::PayloadError>(Ok(()))
|
|
||||||
})
|
|
||||||
.map(|_| HttpResponse::Ok().finish())
|
|
||||||
.responder()
|
|
||||||
}
|
|
||||||
# fn main() {}
|
|
||||||
```
|
|
@ -1,176 +0,0 @@
|
|||||||
# Testing
|
|
||||||
|
|
||||||
Every application should be well tested. Actix provides tools to perform unit and
|
|
||||||
integration tests.
|
|
||||||
|
|
||||||
## Unit tests
|
|
||||||
|
|
||||||
For unit testing, actix provides a request builder type and a simple handler runner.
|
|
||||||
[*TestRequest*](../actix_web/test/struct.TestRequest.html) implements a builder-like pattern.
|
|
||||||
You can generate a `HttpRequest` instance with `finish()`, or you can
|
|
||||||
run your handler with `run()` or `run_async()`.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::{http, test, HttpRequest, HttpResponse, HttpMessage};
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
if let Some(hdr) = req.headers().get(http::header::CONTENT_TYPE) {
|
|
||||||
if let Ok(s) = hdr.to_str() {
|
|
||||||
return HttpResponse::Ok().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
HttpResponse::BadRequest().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let resp = test::TestRequest::with_header("content-type", "text/plain")
|
|
||||||
.run(index)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(resp.status(), http::StatusCode::OK);
|
|
||||||
|
|
||||||
let resp = test::TestRequest::default()
|
|
||||||
.run(index)
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(resp.status(), http::StatusCode::BAD_REQUEST);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Integration tests
|
|
||||||
|
|
||||||
There are several methods for testing your application. Actix provides
|
|
||||||
[*TestServer*](../actix_web/test/struct.TestServer.html), which can be used
|
|
||||||
to run the application with specific handlers in a real http server.
|
|
||||||
|
|
||||||
`TestServer::get()`, `TestServer::post()`, and `TestServer::client()`
|
|
||||||
methods can be used to send requests to the test server.
|
|
||||||
|
|
||||||
A simple form `TestServer` can be configured to use a handler.
|
|
||||||
`TestServer::new` method accepts a configuration function, and the only argument
|
|
||||||
for this function is a *test application* instance.
|
|
||||||
|
|
||||||
> Check the [api documentation](../actix_web/test/struct.TestApp.html) for more information.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::{HttpRequest, HttpResponse, HttpMessage};
|
|
||||||
use actix_web::test::TestServer;
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
HttpResponse::Ok().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut srv = TestServer::new(|app| app.handler(index)); // <- Start new test server
|
|
||||||
|
|
||||||
let request = srv.get().finish().unwrap(); // <- create client request
|
|
||||||
let response = srv.execute(request.send()).unwrap(); // <- send request to the server
|
|
||||||
assert!(response.status().is_success()); // <- check response
|
|
||||||
|
|
||||||
let bytes = srv.execute(response.body()).unwrap(); // <- read response body
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The other option is to use an application factory. In this case, you need to pass the factory
|
|
||||||
function the same way as you would for real http server configuration.
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix_web::{http, test, App, HttpRequest, HttpResponse};
|
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> HttpResponse {
|
|
||||||
HttpResponse::Ok().into()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This function get called by http server.
|
|
||||||
fn create_app() -> App {
|
|
||||||
App::new()
|
|
||||||
.resource("/test", |r| r.h(index))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut srv = test::TestServer::with_factory(create_app); // <- Start new test server
|
|
||||||
|
|
||||||
let request = srv.client(
|
|
||||||
http::Method::GET, "/test").finish().unwrap(); // <- create client request
|
|
||||||
let response = srv.execute(request.send()).unwrap(); // <- send request to the server
|
|
||||||
|
|
||||||
assert!(response.status().is_success()); // <- check response
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If you need more complex application configuration, use the `TestServer::build_with_state()`
|
|
||||||
method. For example, you may need to initialize application state or start `SyncActor`'s for diesel
|
|
||||||
interation. This method accepts a closure that constructs the application state,
|
|
||||||
and it runs when the actix system is configured. Thus, you can initialize any additional actors.
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
#[test]
|
|
||||||
fn test() {
|
|
||||||
let srv = TestServer::build_with_state(|| { // <- construct builder with config closure
|
|
||||||
// we can start diesel actors
|
|
||||||
let addr = SyncArbiter::start(3, || {
|
|
||||||
DbExecutor(SqliteConnection::establish("test.db").unwrap())
|
|
||||||
});
|
|
||||||
// then we can construct custom state, or it could be `()`
|
|
||||||
MyState{addr: addr}
|
|
||||||
})
|
|
||||||
.start(|app| { // <- register server handlers and start test server
|
|
||||||
app.resource(
|
|
||||||
"/{username}/index.html", |r| r.with(
|
|
||||||
|p: Path<PParam>| format!("Welcome {}!", p.username)));
|
|
||||||
});
|
|
||||||
|
|
||||||
// now we can run our test code
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
## WebSocket server tests
|
|
||||||
|
|
||||||
It is possible to register a *handler* with `TestApp::handler()`, which
|
|
||||||
initiates a web socket connection. *TestServer* provides the method `ws()`, which connects to
|
|
||||||
the websocket server and returns ws reader and writer objects. *TestServer* also
|
|
||||||
provides an `execute()` method, which runs future objects to completion and returns
|
|
||||||
result of the future computation.
|
|
||||||
|
|
||||||
The following example demonstrates how to test a websocket handler:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix;
|
|
||||||
# extern crate actix_web;
|
|
||||||
# extern crate futures;
|
|
||||||
# extern crate http;
|
|
||||||
# extern crate bytes;
|
|
||||||
|
|
||||||
use actix_web::*;
|
|
||||||
use futures::Stream;
|
|
||||||
# use actix::prelude::*;
|
|
||||||
|
|
||||||
struct Ws; // <- WebSocket actor
|
|
||||||
|
|
||||||
impl Actor for Ws {
|
|
||||||
type Context = ws::WebsocketContext<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
|
||||||
match msg {
|
|
||||||
ws::Message::Text(text) => ctx.text(text),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let mut srv = test::TestServer::new( // <- start our server with ws handler
|
|
||||||
|app| app.handler(|req| ws::start(req, Ws)));
|
|
||||||
|
|
||||||
let (reader, mut writer) = srv.ws().unwrap(); // <- connect to ws server
|
|
||||||
|
|
||||||
writer.text("text"); // <- send message to server
|
|
||||||
|
|
||||||
let (item, reader) = srv.execute(reader.into_future()).unwrap(); // <- wait for one message
|
|
||||||
assert_eq!(item, Some(ws::Message::Text("text".to_owned())));
|
|
||||||
}
|
|
||||||
```
|
|
@ -1,48 +0,0 @@
|
|||||||
# WebSockets
|
|
||||||
|
|
||||||
Actix supports WebSockets out-of-the-box. It is possible to convert a request's `Payload`
|
|
||||||
to a stream of [*ws::Message*](../actix_web/ws/enum.Message.html) with
|
|
||||||
a [*ws::WsStream*](../actix_web/ws/struct.WsStream.html) and then use stream
|
|
||||||
combinators to handle actual messages, but it is simpler to handle websocket communications
|
|
||||||
with an http actor.
|
|
||||||
|
|
||||||
The following is an example of a simple websocket echo server:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
# extern crate actix;
|
|
||||||
# extern crate actix_web;
|
|
||||||
use actix::*;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
/// Define http actor
|
|
||||||
struct Ws;
|
|
||||||
|
|
||||||
impl Actor for Ws {
|
|
||||||
type Context = ws::WebsocketContext<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler for ws::Message message
|
|
||||||
impl StreamHandler<ws::Message, ws::ProtocolError> for Ws {
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
|
||||||
match msg {
|
|
||||||
ws::Message::Ping(msg) => ctx.pong(&msg),
|
|
||||||
ws::Message::Text(text) => ctx.text(text),
|
|
||||||
ws::Message::Binary(bin) => ctx.binary(bin),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
App::new()
|
|
||||||
.resource("/ws/", |r| r.f(|req| ws::start(req, Ws))) // <- register websocket route
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
> A simple websocket echo server example is available in the
|
|
||||||
> [examples directory](https://github.com/actix/actix-web/blob/master/examples/websocket).
|
|
||||||
|
|
||||||
> An example chat server with the ability to chat over a websocket or tcp connection
|
|
||||||
> is available in [websocket-chat directory](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/)
|
|
@ -25,7 +25,7 @@
|
|||||||
//! Besides the API documentation (which you are currently looking
|
//! Besides the API documentation (which you are currently looking
|
||||||
//! at!), several other resources are available:
|
//! at!), several other resources are available:
|
||||||
//!
|
//!
|
||||||
//! * [User Guide](https://actix.rs/actix-web/guide/)
|
//! * [User Guide](https://actix.rs/book/actix-web/)
|
||||||
//! * [Chat on gitter](https://gitter.im/actix/actix)
|
//! * [Chat on gitter](https://gitter.im/actix/actix)
|
||||||
//! * [GitHub repository](https://github.com/actix/actix-web)
|
//! * [GitHub repository](https://github.com/actix/actix-web)
|
||||||
//! * [Cargo package](https://crates.io/crates/actix-web)
|
//! * [Cargo package](https://crates.io/crates/actix-web)
|
||||||
|
Loading…
Reference in New Issue
Block a user