mirror of
https://github.com/fafhrd91/actix-web
synced 2024-11-24 08:22:59 +01:00
Merge branch 'master' into master
This commit is contained in:
commit
3074071d03
@ -3,6 +3,15 @@ environment:
|
|||||||
PROJECT_NAME: actix
|
PROJECT_NAME: actix
|
||||||
matrix:
|
matrix:
|
||||||
# Stable channel
|
# Stable channel
|
||||||
|
- TARGET: i686-pc-windows-gnu
|
||||||
|
CHANNEL: 1.20.0
|
||||||
|
- TARGET: i686-pc-windows-msvc
|
||||||
|
CHANNEL: 1.20.0
|
||||||
|
- TARGET: x86_64-pc-windows-gnu
|
||||||
|
CHANNEL: 1.20.0
|
||||||
|
- TARGET: x86_64-pc-windows-msvc
|
||||||
|
CHANNEL: 1.20.0
|
||||||
|
# Stable channel
|
||||||
- TARGET: i686-pc-windows-gnu
|
- TARGET: i686-pc-windows-gnu
|
||||||
CHANNEL: stable
|
CHANNEL: stable
|
||||||
- TARGET: i686-pc-windows-msvc
|
- TARGET: i686-pc-windows-msvc
|
||||||
@ -22,13 +31,13 @@ environment:
|
|||||||
CHANNEL: beta
|
CHANNEL: beta
|
||||||
# Nightly channel
|
# Nightly channel
|
||||||
- TARGET: i686-pc-windows-gnu
|
- TARGET: i686-pc-windows-gnu
|
||||||
CHANNEL: nightly
|
CHANNEL: nightly-2017-12-21
|
||||||
- TARGET: i686-pc-windows-msvc
|
- TARGET: i686-pc-windows-msvc
|
||||||
CHANNEL: nightly
|
CHANNEL: nightly-2017-12-21
|
||||||
- TARGET: x86_64-pc-windows-gnu
|
- TARGET: x86_64-pc-windows-gnu
|
||||||
CHANNEL: nightly
|
CHANNEL: nightly-2017-12-21
|
||||||
- TARGET: x86_64-pc-windows-msvc
|
- TARGET: x86_64-pc-windows-msvc
|
||||||
CHANNEL: nightly
|
CHANNEL: nightly-2017-12-21
|
||||||
|
|
||||||
# Install Rust and Cargo
|
# Install Rust and Cargo
|
||||||
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
|
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)
|
||||||
|
43
.travis.yml
43
.travis.yml
@ -4,7 +4,7 @@ rust:
|
|||||||
- 1.20.0
|
- 1.20.0
|
||||||
- stable
|
- stable
|
||||||
- beta
|
- beta
|
||||||
- nightly
|
- nightly-2018-01-03
|
||||||
|
|
||||||
sudo: required
|
sudo: required
|
||||||
dist: trusty
|
dist: trusty
|
||||||
@ -28,7 +28,28 @@ before_script:
|
|||||||
- export PATH=$PATH:~/.cargo/bin
|
- export PATH=$PATH:~/.cargo/bin
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- USE_SKEPTIC=1 cargo test --features=alpn
|
- |
|
||||||
|
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then
|
||||||
|
USE_SKEPTIC=1 cargo test --features=alpn
|
||||||
|
else
|
||||||
|
cargo test --features=alpn
|
||||||
|
fi
|
||||||
|
|
||||||
|
- |
|
||||||
|
if [[ "$TRAVIS_RUST_VERSION" == "1.20.0" ]]; then
|
||||||
|
cd examples/basics && cargo check && cd ../..
|
||||||
|
cd examples/hello-world && cargo check && cd ../..
|
||||||
|
cd examples/multipart && cargo check && cd ../..
|
||||||
|
cd examples/json && cargo check && cd ../..
|
||||||
|
cd examples/template_tera && cargo check && cd ../..
|
||||||
|
fi
|
||||||
|
- |
|
||||||
|
if [[ "$TRAVIS_RUST_VERSION" == "beta" ]]; then
|
||||||
|
cd examples/diesel && cargo check && cd ../..
|
||||||
|
cd examples/tls && cargo check && cd ../..
|
||||||
|
cd examples/websocket-chat && cargo check && cd ../..
|
||||||
|
cd examples/websocket && cargo check && cd ../..
|
||||||
|
fi
|
||||||
- |
|
- |
|
||||||
if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then
|
if [[ "$TRAVIS_RUST_VERSION" == "nightly" && $CLIPPY ]]; then
|
||||||
cargo clippy
|
cargo clippy
|
||||||
@ -37,7 +58,7 @@ script:
|
|||||||
# Upload docs
|
# Upload docs
|
||||||
after_success:
|
after_success:
|
||||||
- |
|
- |
|
||||||
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
|
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "nightly-2018-01-03" ]]; then
|
||||||
cargo doc --features alpn --no-deps &&
|
cargo doc --features alpn --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 &&
|
||||||
cargo install mdbook &&
|
cargo install mdbook &&
|
||||||
@ -49,18 +70,8 @@ after_success:
|
|||||||
|
|
||||||
- |
|
- |
|
||||||
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
|
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
|
||||||
wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz &&
|
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
|
||||||
tar xzf master.tar.gz &&
|
USE_SKEPTIC=1 cargo tarpaulin --out Xml
|
||||||
cd kcov-master &&
|
bash <(curl -s https://codecov.io/bash)
|
||||||
mkdir build &&
|
|
||||||
cd build &&
|
|
||||||
cmake .. &&
|
|
||||||
make &&
|
|
||||||
make install DESTDIR=../../kcov-build &&
|
|
||||||
cd ../.. &&
|
|
||||||
rm -rf kcov-master &&
|
|
||||||
for file in target/debug/actix_web-*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done &&
|
|
||||||
for file in target/debug/test_*[^\.d]; do mkdir -p "target/cov/$(basename $file)"; ./kcov-build/usr/local/bin/kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done &&
|
|
||||||
bash <(curl -s https://codecov.io/bash) &&
|
|
||||||
echo "Uploaded code coverage"
|
echo "Uploaded code coverage"
|
||||||
fi
|
fi
|
||||||
|
@ -15,6 +15,10 @@
|
|||||||
|
|
||||||
* Content compression/decompression (br, gzip, deflate)
|
* Content compression/decompression (br, gzip, deflate)
|
||||||
|
|
||||||
|
* Server multi-threading
|
||||||
|
|
||||||
|
* Gracefull shutdown support
|
||||||
|
|
||||||
|
|
||||||
## 0.2.1 (2017-11-03)
|
## 0.2.1 (2017-11-03)
|
||||||
|
|
||||||
|
41
Cargo.toml
41
Cargo.toml
@ -4,13 +4,13 @@ version = "0.3.0"
|
|||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
description = "Actix web framework"
|
description = "Actix web framework"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
keywords = ["http", "http2", "web", "async", "futures"]
|
keywords = ["http", "web", "framework", "async", "futures"]
|
||||||
homepage = "https://github.com/actix/actix-web"
|
homepage = "https://github.com/actix/actix-web"
|
||||||
repository = "https://github.com/actix/actix-web.git"
|
repository = "https://github.com/actix/actix-web.git"
|
||||||
documentation = "https://docs.rs/actix-web/"
|
documentation = "https://docs.rs/actix-web/"
|
||||||
categories = ["network-programming", "asynchronous",
|
categories = ["network-programming", "asynchronous",
|
||||||
"web-programming::http-server", "web-programming::websocket"]
|
"web-programming::http-server", "web-programming::websocket"]
|
||||||
license = "Apache-2.0"
|
license = "MIT/Apache-2.0"
|
||||||
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
|
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
@ -42,7 +42,6 @@ httparse = "0.1"
|
|||||||
http-range = "0.1"
|
http-range = "0.1"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
mime_guess = "1.8"
|
mime_guess = "1.8"
|
||||||
cookie = { version="0.10", features=["percent-encode", "secure"] }
|
|
||||||
regex = "0.2"
|
regex = "0.2"
|
||||||
sha1 = "0.2"
|
sha1 = "0.2"
|
||||||
url = "1.5"
|
url = "1.5"
|
||||||
@ -52,10 +51,17 @@ serde_json = "1.0"
|
|||||||
flate2 = "0.2"
|
flate2 = "0.2"
|
||||||
brotli2 = "^0.3.2"
|
brotli2 = "^0.3.2"
|
||||||
percent-encoding = "1.0"
|
percent-encoding = "1.0"
|
||||||
|
smallvec = "0.6"
|
||||||
|
bitflags = "1.0"
|
||||||
|
num_cpus = "1.0"
|
||||||
|
|
||||||
# redis-async = { git="https://github.com/benashford/redis-async-rs" }
|
# temp solution
|
||||||
|
# cookie = { version="0.10", features=["percent-encode", "secure"] }
|
||||||
|
cookie = { git="https://github.com/alexcrichton/cookie-rs.git", features=["percent-encode", "secure"] }
|
||||||
|
|
||||||
# tokio
|
# io
|
||||||
|
mio = "0.6"
|
||||||
|
net2 = "0.2"
|
||||||
bytes = "0.4"
|
bytes = "0.4"
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
tokio-io = "0.1"
|
tokio-io = "0.1"
|
||||||
@ -71,11 +77,8 @@ tokio-tls = { version="0.1", optional = true }
|
|||||||
tokio-openssl = { version="0.1", optional = true }
|
tokio-openssl = { version="0.1", optional = true }
|
||||||
|
|
||||||
[dependencies.actix]
|
[dependencies.actix]
|
||||||
version = "^0.3.1"
|
#version = "^0.4.2"
|
||||||
#path = "../actix"
|
git = "https://github.com/actix/actix.git"
|
||||||
#git = "https://github.com/actix/actix.git"
|
|
||||||
default-features = false
|
|
||||||
features = []
|
|
||||||
|
|
||||||
[dependencies.openssl]
|
[dependencies.openssl]
|
||||||
version = "0.9"
|
version = "0.9"
|
||||||
@ -89,10 +92,24 @@ serde_derive = "1.0"
|
|||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
skeptic = "0.13"
|
skeptic = "0.13"
|
||||||
serde_derive = "1.0"
|
|
||||||
version_check = "0.1"
|
version_check = "0.1"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
debug = true
|
# debug = true
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"./",
|
||||||
|
"examples/basics",
|
||||||
|
"examples/diesel",
|
||||||
|
"examples/json",
|
||||||
|
"examples/hello-world",
|
||||||
|
"examples/multipart",
|
||||||
|
"examples/state",
|
||||||
|
"examples/template_tera",
|
||||||
|
"examples/tls",
|
||||||
|
"examples/websocket",
|
||||||
|
"examples/websocket-chat",
|
||||||
|
]
|
||||||
|
25
LICENSE-MIT
Normal file
25
LICENSE-MIT
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
Copyright (c) 2017 Nikilay Kim
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
2
Makefile
2
Makefile
@ -1,6 +1,6 @@
|
|||||||
.PHONY: default build test doc book clean
|
.PHONY: default build test doc book clean
|
||||||
|
|
||||||
CARGO_FLAGS := --features "$(FEATURES)"
|
CARGO_FLAGS := --features "$(FEATURES) alpn"
|
||||||
|
|
||||||
default: test
|
default: test
|
||||||
|
|
||||||
|
149
README.md
149
README.md
@ -1,6 +1,25 @@
|
|||||||
# Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](http://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web)
|
# Actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![Build status](https://ci.appveyor.com/api/projects/status/kkdb4yce7qhm5w85/branch/master?svg=true)](https://ci.appveyor.com/project/fafhrd91/actix-web-hdy9d/branch/master) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](http://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
Actix web is a fast, down-to-earth, open source rust web framework.
|
Actix web is a small, fast, down-to-earth, open source rust web framework.
|
||||||
|
|
||||||
|
```rust,ignore
|
||||||
|
extern crate actix_web;
|
||||||
|
use actix_web::*;
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> String {
|
||||||
|
format!("Hello {}!", &req.match_info()["name"])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
HttpServer::new(
|
||||||
|
|| Application::new()
|
||||||
|
.resource("/{name}", |r| r.f(index)))
|
||||||
|
.bind("127.0.0.1:8080").unwrap()
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
* [User Guide](http://actix.github.io/actix-web/guide/)
|
* [User Guide](http://actix.github.io/actix-web/guide/)
|
||||||
* [API Documentation (Development)](http://actix.github.io/actix-web/actix_web/)
|
* [API Documentation (Development)](http://actix.github.io/actix-web/actix_web/)
|
||||||
@ -8,111 +27,45 @@ Actix web is a fast, down-to-earth, open source rust web framework.
|
|||||||
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
|
* Cargo package: [actix-web](https://crates.io/crates/actix-web)
|
||||||
* Minimum supported Rust version: 1.20 or later
|
* Minimum supported Rust version: 1.20 or later
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Actix web is licensed under the [Apache-2.0 license](http://opensource.org/licenses/APACHE-2.0).
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Supported HTTP/1 and HTTP/2 protocols
|
* Supported *HTTP/1.x* and *HTTP/2.0* protocols
|
||||||
* Streaming and pipelining
|
* Streaming and pipelining
|
||||||
* Keep-alive and slow requests handling
|
* Keep-alive and slow requests handling
|
||||||
* [WebSockets](https://actix.github.io/actix-web/actix_web/ws/index.html)
|
* [WebSockets](https://actix.github.io/actix-web/actix_web/ws/index.html)
|
||||||
* Transparent content compression/decompression (br, gzip, deflate)
|
* Transparent content compression/decompression (br, gzip, deflate)
|
||||||
* Configurable request routing
|
* Configurable request routing
|
||||||
* Multipart streams
|
* Graceful server shutdown
|
||||||
* Middlewares (Logger, Session included)
|
* Multipart streams
|
||||||
|
* Middlewares ([Logger](https://actix.github.io/actix-web/guide/qs_10.html#logging),
|
||||||
|
[Session](https://actix.github.io/actix-web/guide/qs_10.html#user-sessions),
|
||||||
|
[DefaultHeaders](https://actix.github.io/actix-web/guide/qs_10.html#default-headers))
|
||||||
|
* Built on top of [Actix](https://github.com/actix/actix).
|
||||||
|
|
||||||
## Usage
|
## Benchmarks
|
||||||
|
|
||||||
To use `actix-web`, add this to your `Cargo.toml`:
|
Some basic benchmarks could be found in this [respository](https://github.com/fafhrd91/benchmarks).
|
||||||
|
|
||||||
```toml
|
## Examples
|
||||||
[dependencies]
|
|
||||||
actix-web = { git = "https://github.com/actix/actix-web" }
|
|
||||||
```
|
|
||||||
|
|
||||||
## HTTP/2
|
* [Basic](https://github.com/actix/actix-web/tree/master/examples/basic/)
|
||||||
|
* [Stateful](https://github.com/actix/actix-web/tree/master/examples/state/)
|
||||||
Actix web automatically upgrades connection to `http/2` if possible.
|
* [Mulitpart streams](https://github.com/actix/actix-web/tree/master/examples/multipart/)
|
||||||
|
* [Simple websocket session](https://github.com/actix/actix-web/tree/master/examples/websocket/)
|
||||||
### Negotiation
|
* [Tera templates](https://github.com/actix/actix-web/tree/master/examples/template_tera/)
|
||||||
|
* [Diesel integration](https://github.com/actix/actix-web/tree/master/examples/diesel/)
|
||||||
`HTTP/2` protocol over tls without prior knowlage requires
|
* [SSL / HTTP/2.0](https://github.com/actix/actix-web/tree/master/examples/tls/)
|
||||||
[tls alpn](https://tools.ietf.org/html/rfc7301). At the moment only
|
* [Tcp/Websocket chat](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/)
|
||||||
`rust-openssl` supports alpn.
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[dependencies]
|
|
||||||
actix-web = { git = "https://github.com/actix/actix-web", features=["alpn"] }
|
|
||||||
```
|
|
||||||
|
|
||||||
Upgrade to `http/2` 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)
|
|
||||||
|
|
||||||
[tls example](https://github.com/actix/actix-web/tree/master/examples/tls)
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
* [Basic](https://github.com/actix/actix-web/tree/master/examples/basic.rs)
|
|
||||||
* [Stateful](https://github.com/actix/actix-web/tree/master/examples/state.rs)
|
|
||||||
* [Mulitpart streams](https://github.com/actix/actix-web/tree/master/examples/multipart)
|
|
||||||
* [Simple websocket session](https://github.com/actix/actix-web/tree/master/examples/websocket.rs)
|
|
||||||
* [Tcp/Websocket chat](https://github.com/actix/actix-web/tree/master/examples/websocket-chat)
|
|
||||||
* [SockJS Server](https://github.com/actix/actix-sockjs)
|
* [SockJS Server](https://github.com/actix/actix-sockjs)
|
||||||
|
* [Json](https://github.com/actix/actix-web/tree/master/examples/json/)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
```rust
|
This project is licensed under either of
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
|
|
||||||
use actix::*;
|
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0))
|
||||||
use actix_web::*;
|
* MIT license ([LICENSE-MIT](LICENSE-MIT) or [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT))
|
||||||
|
|
||||||
struct MyWebSocket;
|
at your option.
|
||||||
|
|
||||||
/// Actor with http context
|
[![Analytics](https://ga-beacon.appspot.com/UA-110322332-2/actix-web/readme?flat&useReferer)](https://github.com/igrigorik/ga-beacon)
|
||||||
impl Actor for MyWebSocket {
|
|
||||||
type Context = HttpContext<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Standard actix's stream handler for a stream of `ws::Message`
|
|
||||||
impl StreamHandler<ws::Message> for MyWebSocket {}
|
|
||||||
impl Handler<ws::Message> for MyWebSocket {
|
|
||||||
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context)
|
|
||||||
-> Response<Self, ws::Message>
|
|
||||||
{
|
|
||||||
// process websocket messages
|
|
||||||
println!("WS: {:?}", msg);
|
|
||||||
match msg {
|
|
||||||
ws::Message::Ping(msg) => ws::WsWriter::pong(ctx, &msg),
|
|
||||||
ws::Message::Text(text) => ws::WsWriter::text(ctx, &text),
|
|
||||||
ws::Message::Binary(bin) => ws::WsWriter::binary(ctx, bin),
|
|
||||||
ws::Message::Closed | ws::Message::Error => {
|
|
||||||
ctx.stop();
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
Self::empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("ws-example");
|
|
||||||
|
|
||||||
HttpServer::new(
|
|
||||||
Application::default("/")
|
|
||||||
.middleware(middlewares::Logger::default()) // <- register logger middleware
|
|
||||||
.resource("/ws/", |r| r.get(|req| ws::start(req, MyWebSocket))) // <- websocket route
|
|
||||||
.route("/", StaticFiles::new("examples/static/", true))) // <- server static files
|
|
||||||
.serve::<_, ()>("127.0.0.1:8080").unwrap();
|
|
||||||
|
|
||||||
Arbiter::system().send(msgs::SystemExit(0));
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
11
build.rs
11
build.rs
@ -12,8 +12,19 @@ fn main() {
|
|||||||
// generates doc tests for `README.md`.
|
// generates doc tests for `README.md`.
|
||||||
skeptic::generate_doc_tests(
|
skeptic::generate_doc_tests(
|
||||||
&["README.md",
|
&["README.md",
|
||||||
|
"guide/src/qs_1.md",
|
||||||
"guide/src/qs_2.md",
|
"guide/src/qs_2.md",
|
||||||
"guide/src/qs_3.md",
|
"guide/src/qs_3.md",
|
||||||
|
"guide/src/qs_3_5.md",
|
||||||
|
"guide/src/qs_4.md",
|
||||||
|
"guide/src/qs_4_5.md",
|
||||||
|
"guide/src/qs_5.md",
|
||||||
|
"guide/src/qs_7.md",
|
||||||
|
"guide/src/qs_8.md",
|
||||||
|
"guide/src/qs_9.md",
|
||||||
|
"guide/src/qs_10.md",
|
||||||
|
"guide/src/qs_12.md",
|
||||||
|
"guide/src/qs_13.md",
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
let _ = fs::File::create(f);
|
let _ = fs::File::create(f);
|
||||||
|
@ -1,97 +0,0 @@
|
|||||||
#![allow(unused_variables)]
|
|
||||||
#![cfg_attr(feature="cargo-clippy", allow(needless_pass_by_value))]
|
|
||||||
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate env_logger;
|
|
||||||
extern crate futures;
|
|
||||||
|
|
||||||
use actix_web::*;
|
|
||||||
use actix_web::middlewares::RequestSession;
|
|
||||||
use futures::future::{FutureResult, result};
|
|
||||||
|
|
||||||
/// simple handler
|
|
||||||
fn index(mut req: HttpRequest) -> Result<HttpResponse> {
|
|
||||||
println!("{:?}", req);
|
|
||||||
if let Ok(ch) = req.payload_mut().readany() {
|
|
||||||
if let futures::Async::Ready(Some(d)) = ch {
|
|
||||||
println!("{}", String::from_utf8_lossy(d.0.as_ref()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// session
|
|
||||||
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(HttpResponse::Ok().into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// async handler
|
|
||||||
fn index_async(req: HttpRequest) -> FutureResult<HttpResponse, Error>
|
|
||||||
{
|
|
||||||
println!("{:?}", req);
|
|
||||||
|
|
||||||
result(HttpResponse::Ok()
|
|
||||||
.content_type("text/html")
|
|
||||||
.body(format!("Hello {}!", req.match_info().get("name").unwrap()))
|
|
||||||
.map_err(|e| e.into()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// handler with path parameters like `/user/{name}/`
|
|
||||||
fn with_param(req: HttpRequest) -> Result<HttpResponse>
|
|
||||||
{
|
|
||||||
println!("{:?}", req);
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
|
||||||
.content_type("test/plain")
|
|
||||||
.body(format!("Hello {}!", req.match_info().get("name").unwrap()))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
|
||||||
let _ = env_logger::init();
|
|
||||||
let sys = actix::System::new("ws-example");
|
|
||||||
|
|
||||||
HttpServer::new(
|
|
||||||
Application::default("/")
|
|
||||||
// enable logger
|
|
||||||
.middleware(middlewares::Logger::default())
|
|
||||||
// cookie session middleware
|
|
||||||
.middleware(middlewares::SessionStorage::new(
|
|
||||||
middlewares::CookieSessionBackend::build(&[0; 32])
|
|
||||||
.secure(false)
|
|
||||||
.finish()
|
|
||||||
))
|
|
||||||
// register simple handle r, handle all methods
|
|
||||||
.handler("/index.html", index)
|
|
||||||
// with path parameters
|
|
||||||
.resource("/user/{name}/", |r| r.handler(Method::GET, with_param))
|
|
||||||
// async handler
|
|
||||||
.resource("/async/{name}", |r| r.async(Method::GET, index_async))
|
|
||||||
// redirect
|
|
||||||
.resource("/", |r| r.handler(Method::GET, |req| {
|
|
||||||
println!("{:?}", req);
|
|
||||||
|
|
||||||
httpcodes::HTTPFound
|
|
||||||
.build()
|
|
||||||
.header("LOCATION", "/index.html")
|
|
||||||
.body(Body::Empty)
|
|
||||||
}))
|
|
||||||
.handler("/test", |req| {
|
|
||||||
match *req.method() {
|
|
||||||
Method::GET => httpcodes::HTTPOk,
|
|
||||||
Method::POST => httpcodes::HTTPMethodNotAllowed,
|
|
||||||
_ => httpcodes::HTTPNotFound,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// static files
|
|
||||||
.route("/static", StaticFiles::new("examples/static/", true)))
|
|
||||||
.serve::<_, ()>("127.0.0.1:8080").unwrap();
|
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
|
11
examples/basics/Cargo.toml
Normal file
11
examples/basics/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "basics"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
|
workspace = "../.."
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
futures = "*"
|
||||||
|
env_logger = "0.4"
|
||||||
|
actix = "0.4"
|
||||||
|
actix-web = { git = "https://github.com/actix/actix-web" }
|
19
examples/basics/README.md
Normal file
19
examples/basics/README.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# basics
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd actix-web/examples/basics
|
||||||
|
cargo run
|
||||||
|
# Started http server: 127.0.0.1:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
### web client
|
||||||
|
|
||||||
|
- [http://localhost:8080/index.html](http://localhost:8080/index.html)
|
||||||
|
- [http://localhost:8080/async/bob](http://localhost:8080/async/bob)
|
||||||
|
- [http://localhost:8080/user/bob/](http://localhost:8080/user/bob/) plain/text download
|
||||||
|
- [http://localhost:8080/test](http://localhost:8080/test) (return status switch GET or POST or other)
|
||||||
|
- [http://localhost:8080/static/index.html](http://localhost:8080/static/index.html)
|
146
examples/basics/src/main.rs
Normal file
146
examples/basics/src/main.rs
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
#![allow(unused_variables)]
|
||||||
|
#![cfg_attr(feature="cargo-clippy", allow(needless_pass_by_value))]
|
||||||
|
|
||||||
|
extern crate actix;
|
||||||
|
extern crate actix_web;
|
||||||
|
extern crate env_logger;
|
||||||
|
extern crate futures;
|
||||||
|
use futures::Stream;
|
||||||
|
|
||||||
|
use actix_web::*;
|
||||||
|
use actix_web::middleware::RequestSession;
|
||||||
|
use futures::future::{FutureResult, result};
|
||||||
|
|
||||||
|
/// favicon handler
|
||||||
|
fn favicon(req: HttpRequest) -> Result<fs::NamedFile> {
|
||||||
|
Ok(fs::NamedFile::open("../static/favicon.ico")?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// simple index handler
|
||||||
|
fn index(mut req: HttpRequest) -> Result<HttpResponse> {
|
||||||
|
println!("{:?}", req);
|
||||||
|
|
||||||
|
// example of ...
|
||||||
|
if let Ok(ch) = req.payload_mut().readany().poll() {
|
||||||
|
if let futures::Async::Ready(Some(d)) = ch {
|
||||||
|
println!("{}", String::from_utf8_lossy(d.as_ref()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// session
|
||||||
|
let mut counter = 1;
|
||||||
|
if let Some(count) = req.session().get::<i32>("counter")? {
|
||||||
|
println!("SESSION value: {}", count);
|
||||||
|
counter = count + 1;
|
||||||
|
req.session().set("counter", counter)?;
|
||||||
|
} else {
|
||||||
|
req.session().set("counter", counter)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// html
|
||||||
|
let html = format!(r#"<!DOCTYPE html><html><head><title>actix - basics</title><link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" /></head>
|
||||||
|
<body>
|
||||||
|
<h1>Welcome <img width="30px" height="30px" src="/static/actixLogo.png" /></h1>
|
||||||
|
session counter = {}
|
||||||
|
</body>
|
||||||
|
</html>"#, counter);
|
||||||
|
|
||||||
|
// response
|
||||||
|
Ok(HttpResponse::build(StatusCode::OK)
|
||||||
|
.content_type("text/html; charset=utf-8")
|
||||||
|
.body(&html).unwrap())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 404 handler
|
||||||
|
fn p404(req: HttpRequest) -> Result<HttpResponse> {
|
||||||
|
|
||||||
|
// html
|
||||||
|
let html = format!(r#"<!DOCTYPE html><html><head><title>actix - basics</title><link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" /></head>
|
||||||
|
<body>
|
||||||
|
<a href="index.html">back to home</a>
|
||||||
|
<h1>404</h1>
|
||||||
|
</body>
|
||||||
|
</html>"#);
|
||||||
|
|
||||||
|
// response
|
||||||
|
Ok(HttpResponse::build(StatusCode::NOT_FOUND)
|
||||||
|
.content_type("text/html; charset=utf-8")
|
||||||
|
.body(&html).unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// async handler
|
||||||
|
fn index_async(req: HttpRequest) -> FutureResult<HttpResponse, Error>
|
||||||
|
{
|
||||||
|
println!("{:?}", req);
|
||||||
|
|
||||||
|
result(HttpResponse::Ok()
|
||||||
|
.content_type("text/html")
|
||||||
|
.body(format!("Hello {}!", req.match_info().get("name").unwrap()))
|
||||||
|
.map_err(|e| e.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// handler with path parameters like `/user/{name}/`
|
||||||
|
fn with_param(req: HttpRequest) -> Result<HttpResponse>
|
||||||
|
{
|
||||||
|
println!("{:?}", req);
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok()
|
||||||
|
.content_type("test/plain")
|
||||||
|
.body(format!("Hello {}!", req.match_info().get("name").unwrap()))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||||
|
let _ = env_logger::init();
|
||||||
|
let sys = actix::System::new("basic-example");
|
||||||
|
|
||||||
|
let addr = HttpServer::new(
|
||||||
|
|| Application::new()
|
||||||
|
// enable logger
|
||||||
|
.middleware(middleware::Logger::default())
|
||||||
|
// cookie session middleware
|
||||||
|
.middleware(middleware::SessionStorage::new(
|
||||||
|
middleware::CookieSessionBackend::build(&[0; 32])
|
||||||
|
.secure(false)
|
||||||
|
.finish()
|
||||||
|
))
|
||||||
|
// register favicon
|
||||||
|
.resource("/favicon.ico", |r| r.f(favicon))
|
||||||
|
// register simple route, handle all methods
|
||||||
|
.resource("/index.html", |r| r.f(index))
|
||||||
|
// with path parameters
|
||||||
|
.resource("/user/{name}/", |r| r.method(Method::GET).f(with_param))
|
||||||
|
// async handler
|
||||||
|
.resource("/async/{name}", |r| r.method(Method::GET).a(index_async))
|
||||||
|
.resource("/test", |r| r.f(|req| {
|
||||||
|
match *req.method() {
|
||||||
|
Method::GET => httpcodes::HTTPOk,
|
||||||
|
Method::POST => httpcodes::HTTPMethodNotAllowed,
|
||||||
|
_ => httpcodes::HTTPNotFound,
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
// static files
|
||||||
|
.handler("/static/", fs::StaticFiles::new("../static/", true))
|
||||||
|
// redirect
|
||||||
|
.resource("/", |r| r.method(Method::GET).f(|req| {
|
||||||
|
println!("{:?}", req);
|
||||||
|
|
||||||
|
HttpResponse::Found()
|
||||||
|
.header("LOCATION", "/index.html")
|
||||||
|
.finish()
|
||||||
|
}))
|
||||||
|
// default
|
||||||
|
.default_resource(|r| {
|
||||||
|
r.method(Method::GET).f(p404);
|
||||||
|
r.route().p(pred::Not(pred::Get())).f(|req| httpcodes::HTTPMethodNotAllowed);
|
||||||
|
}))
|
||||||
|
|
||||||
|
.bind("127.0.0.1:8080").expect("Can not bind to 127.0.0.1:8080")
|
||||||
|
.shutdown_timeout(0) // <- Set shutdown timeout to 0 seconds (default 60s)
|
||||||
|
.start();
|
||||||
|
|
||||||
|
println!("Starting http server: 127.0.0.1:8080");
|
||||||
|
let _ = sys.run();
|
||||||
|
}
|
1
examples/diesel/.env
Normal file
1
examples/diesel/.env
Normal file
@ -0,0 +1 @@
|
|||||||
|
DATABASE_URL=file:test.db
|
19
examples/diesel/Cargo.toml
Normal file
19
examples/diesel/Cargo.toml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "diesel-example"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
|
workspace = "../.."
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
env_logger = "0.4"
|
||||||
|
actix = "0.4"
|
||||||
|
actix-web = { git = "https://github.com/actix/actix-web" }
|
||||||
|
|
||||||
|
futures = "0.1"
|
||||||
|
uuid = { version = "0.5", features = ["serde", "v4"] }
|
||||||
|
serde = "1.0"
|
||||||
|
serde_json = "1.0"
|
||||||
|
serde_derive = "1.0"
|
||||||
|
|
||||||
|
diesel = { version = "1.0.0-beta1", features = ["sqlite"] }
|
||||||
|
dotenv = "0.10"
|
43
examples/diesel/README.md
Normal file
43
examples/diesel/README.md
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# diesel
|
||||||
|
|
||||||
|
Diesel's `Getting Started` guide using SQLite for Actix web
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### init database sqlite
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo install diesel_cli --no-default-features --features sqlite
|
||||||
|
cd actix-web/examples/diesel
|
||||||
|
echo "DATABASE_URL=file:test.db" > .env
|
||||||
|
diesel migration run
|
||||||
|
```
|
||||||
|
|
||||||
|
### server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# if ubuntu : sudo apt-get install libsqlite3-dev
|
||||||
|
# if fedora : sudo dnf install libsqlite3x-devel
|
||||||
|
cd actix-web/examples/diesel
|
||||||
|
cargo run (or ``cargo watch -x run``)
|
||||||
|
# Started http server: 127.0.0.1:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
### web client
|
||||||
|
|
||||||
|
[http://127.0.0.1:8080/NAME](http://127.0.0.1:8080/NAME)
|
||||||
|
|
||||||
|
### sqlite client
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# if ubuntu : sudo apt-get install sqlite3
|
||||||
|
# if fedora : sudo dnf install sqlite3x
|
||||||
|
sqlite3 test.db
|
||||||
|
sqlite> .tables
|
||||||
|
sqlite> select * from users;
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Postgresql
|
||||||
|
|
||||||
|
You will also find another complete example of diesel+postgresql on [https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Rust/actix](https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Rust/actix)
|
@ -0,0 +1 @@
|
|||||||
|
DROP TABLE users
|
@ -0,0 +1,4 @@
|
|||||||
|
CREATE TABLE users (
|
||||||
|
id VARCHAR NOT NULL PRIMARY KEY,
|
||||||
|
name VARCHAR NOT NULL
|
||||||
|
)
|
53
examples/diesel/src/db.rs
Normal file
53
examples/diesel/src/db.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
//! Db executor actor
|
||||||
|
use uuid;
|
||||||
|
use diesel;
|
||||||
|
use actix_web::*;
|
||||||
|
use actix::prelude::*;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
|
||||||
|
use models;
|
||||||
|
use schema;
|
||||||
|
|
||||||
|
/// This is db executor actor. We are going to run 3 of them in parallele.
|
||||||
|
pub struct DbExecutor(pub SqliteConnection);
|
||||||
|
|
||||||
|
/// This is only message that this actor can handle, but it is easy to extend number of
|
||||||
|
/// messages.
|
||||||
|
pub struct CreateUser {
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseType for CreateUser {
|
||||||
|
type Item = models::User;
|
||||||
|
type Error = Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Actor for DbExecutor {
|
||||||
|
type Context = SyncContext<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handler<CreateUser> for DbExecutor {
|
||||||
|
type Result = MessageResult<CreateUser>;
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Self::Result {
|
||||||
|
use self::schema::users::dsl::*;
|
||||||
|
|
||||||
|
let uuid = format!("{}", uuid::Uuid::new_v4());
|
||||||
|
let new_user = models::NewUser {
|
||||||
|
id: &uuid,
|
||||||
|
name: &msg.name,
|
||||||
|
};
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
73
examples/diesel/src/main.rs
Normal file
73
examples/diesel/src/main.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
//! Actix web diesel example
|
||||||
|
//!
|
||||||
|
//! Diesel does not support tokio, so we have to run it in separate threads.
|
||||||
|
//! Actix supports sync actors by default, so we going to create sync actor that will
|
||||||
|
//! use diesel. Technically sync actors are worker style actors, multiple of them
|
||||||
|
//! can run in parallel and process messages from same queue.
|
||||||
|
extern crate serde;
|
||||||
|
extern crate serde_json;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate diesel;
|
||||||
|
extern crate uuid;
|
||||||
|
extern crate futures;
|
||||||
|
extern crate actix;
|
||||||
|
extern crate actix_web;
|
||||||
|
extern crate env_logger;
|
||||||
|
|
||||||
|
use actix::*;
|
||||||
|
use actix_web::*;
|
||||||
|
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use futures::future::Future;
|
||||||
|
|
||||||
|
mod db;
|
||||||
|
mod models;
|
||||||
|
mod schema;
|
||||||
|
|
||||||
|
use db::{CreateUser, DbExecutor};
|
||||||
|
|
||||||
|
|
||||||
|
/// State with DbExecutor address
|
||||||
|
struct State {
|
||||||
|
db: SyncAddress<DbExecutor>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Async request handler
|
||||||
|
fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||||
|
let name = &req.match_info()["name"];
|
||||||
|
|
||||||
|
req.state().db.call_fut(CreateUser{name: name.to_owned()})
|
||||||
|
.from_err()
|
||||||
|
.and_then(|res| {
|
||||||
|
match res {
|
||||||
|
Ok(user) => Ok(httpcodes::HTTPOk.build().json(user)?),
|
||||||
|
Err(_) => Ok(httpcodes::HTTPInternalServerError.into())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||||
|
let _ = env_logger::init();
|
||||||
|
let sys = actix::System::new("diesel-example");
|
||||||
|
|
||||||
|
// Start db executor actors
|
||||||
|
let addr = SyncArbiter::start(3, || {
|
||||||
|
DbExecutor(SqliteConnection::establish("test.db").unwrap())
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start http server
|
||||||
|
let _addr = HttpServer::new(move || {
|
||||||
|
Application::with_state(State{db: addr.clone()})
|
||||||
|
// enable logger
|
||||||
|
.middleware(middleware::Logger::default())
|
||||||
|
.resource("/{name}", |r| r.method(Method::GET).a(index))})
|
||||||
|
.bind("127.0.0.1:8080").unwrap()
|
||||||
|
.start();
|
||||||
|
|
||||||
|
println!("Started http server: 127.0.0.1:8080");
|
||||||
|
let _ = sys.run();
|
||||||
|
}
|
14
examples/diesel/src/models.rs
Normal file
14
examples/diesel/src/models.rs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
use super::schema::users;
|
||||||
|
|
||||||
|
#[derive(Serialize, Queryable)]
|
||||||
|
pub struct User {
|
||||||
|
pub id: String,
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Insertable)]
|
||||||
|
#[table_name = "users"]
|
||||||
|
pub struct NewUser<'a> {
|
||||||
|
pub id: &'a str,
|
||||||
|
pub name: &'a str,
|
||||||
|
}
|
6
examples/diesel/src/schema.rs
Normal file
6
examples/diesel/src/schema.rs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
table! {
|
||||||
|
users (id) {
|
||||||
|
id -> Text,
|
||||||
|
name -> Text,
|
||||||
|
}
|
||||||
|
}
|
BIN
examples/diesel/test.db
Normal file
BIN
examples/diesel/test.db
Normal file
Binary file not shown.
10
examples/hello-world/Cargo.toml
Normal file
10
examples/hello-world/Cargo.toml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "hello-world"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
|
workspace = "../.."
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
env_logger = "0.4"
|
||||||
|
actix = "0.4"
|
||||||
|
actix-web = { path = "../../" }
|
28
examples/hello-world/src/main.rs
Normal file
28
examples/hello-world/src/main.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
extern crate actix;
|
||||||
|
extern crate actix_web;
|
||||||
|
extern crate env_logger;
|
||||||
|
|
||||||
|
use actix_web::*;
|
||||||
|
|
||||||
|
|
||||||
|
fn index(_req: HttpRequest) -> &'static str {
|
||||||
|
"Hello world!"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||||
|
let _ = env_logger::init();
|
||||||
|
let sys = actix::System::new("ws-example");
|
||||||
|
|
||||||
|
let _addr = HttpServer::new(
|
||||||
|
|| Application::new()
|
||||||
|
// enable logger
|
||||||
|
.middleware(middleware::Logger::default())
|
||||||
|
.resource("/index.html", |r| r.f(|_| "Hello world!"))
|
||||||
|
.resource("/", |r| r.f(index)))
|
||||||
|
.bind("127.0.0.1:8080").unwrap()
|
||||||
|
.start();
|
||||||
|
|
||||||
|
println!("Started http server: 127.0.0.1:8080");
|
||||||
|
let _ = sys.run();
|
||||||
|
}
|
18
examples/json/Cargo.toml
Normal file
18
examples/json/Cargo.toml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
[package]
|
||||||
|
name = "json-example"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
|
workspace = "../.."
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bytes = "0.4"
|
||||||
|
futures = "0.1"
|
||||||
|
env_logger = "*"
|
||||||
|
|
||||||
|
serde = "1.0"
|
||||||
|
serde_json = "1.0"
|
||||||
|
serde_derive = "1.0"
|
||||||
|
json = "*"
|
||||||
|
|
||||||
|
actix = "0.4"
|
||||||
|
actix-web = { git = "https://github.com/actix/actix-web" }
|
48
examples/json/README.md
Normal file
48
examples/json/README.md
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# json
|
||||||
|
|
||||||
|
Json's `Getting Started` guide using json (serde-json or json-rust) for Actix web
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd actix-web/examples/json
|
||||||
|
cargo run
|
||||||
|
# Started http server: 127.0.0.1:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
### web client
|
||||||
|
|
||||||
|
With [Postman](https://www.getpostman.com/) or [Rested](moz-extension://60daeb1c-5b1b-4afd-9842-0579ed34dfcb/dist/index.html)
|
||||||
|
|
||||||
|
- POST / (embed serde-json):
|
||||||
|
|
||||||
|
- method : ``POST``
|
||||||
|
- url : ``http://127.0.0.1:8080/``
|
||||||
|
- header : ``Content-Type`` = ``application/json``
|
||||||
|
- body (raw) : ``{"name": "Test user", "number": 100}``
|
||||||
|
|
||||||
|
- POST /manual (manual serde-json):
|
||||||
|
|
||||||
|
- method : ``POST``
|
||||||
|
- url : ``http://127.0.0.1:8080/manual``
|
||||||
|
- header : ``Content-Type`` = ``application/json``
|
||||||
|
- body (raw) : ``{"name": "Test user", "number": 100}``
|
||||||
|
|
||||||
|
- POST /mjsonrust (manual json-rust):
|
||||||
|
|
||||||
|
- method : ``POST``
|
||||||
|
- url : ``http://127.0.0.1:8080/mjsonrust``
|
||||||
|
- header : ``Content-Type`` = ``application/json``
|
||||||
|
- body (raw) : ``{"name": "Test user", "number": 100}`` (you can also test ``{notjson}``)
|
||||||
|
|
||||||
|
### python client
|
||||||
|
|
||||||
|
- ``pip install aiohttp``
|
||||||
|
- ``python client.py``
|
||||||
|
|
||||||
|
if ubuntu :
|
||||||
|
|
||||||
|
- ``pip3 install aiohttp``
|
||||||
|
- ``python3 client.py``
|
18
examples/json/client.py
Normal file
18
examples/json/client.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# This script could be used for actix-web multipart example test
|
||||||
|
# just start server and run client.py
|
||||||
|
|
||||||
|
import json
|
||||||
|
import asyncio
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
|
async def req():
|
||||||
|
resp = await aiohttp.ClientSession().request(
|
||||||
|
"post", 'http://localhost:8080/',
|
||||||
|
data=json.dumps({"name": "Test user", "number": 100}),
|
||||||
|
headers={"content-type": "application/json"})
|
||||||
|
print(str(resp))
|
||||||
|
print(await resp.text())
|
||||||
|
assert 200 == resp.status
|
||||||
|
|
||||||
|
|
||||||
|
asyncio.get_event_loop().run_until_complete(req())
|
99
examples/json/src/main.rs
Normal file
99
examples/json/src/main.rs
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
extern crate actix;
|
||||||
|
extern crate actix_web;
|
||||||
|
extern crate bytes;
|
||||||
|
extern crate futures;
|
||||||
|
extern crate env_logger;
|
||||||
|
extern crate serde_json;
|
||||||
|
#[macro_use] extern crate serde_derive;
|
||||||
|
#[macro_use] extern crate json;
|
||||||
|
|
||||||
|
use actix_web::*;
|
||||||
|
|
||||||
|
use bytes::BytesMut;
|
||||||
|
use futures::{Future, Stream};
|
||||||
|
use json::JsonValue;
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct MyObj {
|
||||||
|
name: String,
|
||||||
|
number: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This handler uses `HttpRequest::json()` for loading serde json object.
|
||||||
|
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||||
|
req.json()
|
||||||
|
.from_err() // convert all errors into `Error`
|
||||||
|
.and_then(|val: MyObj| {
|
||||||
|
println!("model: {:?}", val);
|
||||||
|
Ok(httpcodes::HTTPOk.build().json(val)?) // <- send response
|
||||||
|
})
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const MAX_SIZE: usize = 262_144; // max payload size is 256k
|
||||||
|
|
||||||
|
/// This handler manually load request payload and parse serde json
|
||||||
|
fn index_manual(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||||
|
// readany() returns asynchronous stream of Bytes objects
|
||||||
|
req.payload_mut().readany()
|
||||||
|
// `Future::from_err` acts like `?` in that it coerces the error type from
|
||||||
|
// the future into the final error type
|
||||||
|
.from_err()
|
||||||
|
|
||||||
|
// `fold` will asynchronously read each chunk of the request body and
|
||||||
|
// call supplied closure, then it resolves to result of closure
|
||||||
|
.fold(BytesMut::new(), move |mut body, chunk| {
|
||||||
|
// limit max size of in-memory payload
|
||||||
|
if (body.len() + chunk.len()) > MAX_SIZE {
|
||||||
|
Err(error::ErrorBadRequest("overflow"))
|
||||||
|
} else {
|
||||||
|
body.extend_from_slice(&chunk);
|
||||||
|
Ok(body)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// `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 serde-json
|
||||||
|
let obj = serde_json::from_slice::<MyObj>(&body)?;
|
||||||
|
Ok(httpcodes::HTTPOk.build().json(obj)?) // <- send response
|
||||||
|
})
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This handler manually load request payload and parse json-rust
|
||||||
|
fn index_mjsonrust(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||||
|
req.payload_mut().readany().concat2()
|
||||||
|
.from_err()
|
||||||
|
.and_then(|body| {
|
||||||
|
// body is loaded, now we can deserialize json-rust
|
||||||
|
let result = json::parse(std::str::from_utf8(&body).unwrap()); // return Result
|
||||||
|
let injson: JsonValue = match result { Ok(v) => v, Err(e) => object!{"err" => e.to_string() } };
|
||||||
|
Ok(HttpResponse::build(StatusCode::OK)
|
||||||
|
.content_type("application/json")
|
||||||
|
.body(injson.dump()).unwrap())
|
||||||
|
|
||||||
|
})
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||||
|
let _ = env_logger::init();
|
||||||
|
let sys = actix::System::new("json-example");
|
||||||
|
|
||||||
|
let addr = HttpServer::new(|| {
|
||||||
|
Application::new()
|
||||||
|
// enable logger
|
||||||
|
.middleware(middleware::Logger::default())
|
||||||
|
.resource("/manual", |r| r.method(Method::POST).f(index_manual))
|
||||||
|
.resource("/mjsonrust", |r| r.method(Method::POST).f(index_mjsonrust))
|
||||||
|
.resource("/", |r| r.method(Method::POST).f(index))})
|
||||||
|
.bind("127.0.0.1:8080").unwrap()
|
||||||
|
.shutdown_timeout(1)
|
||||||
|
.start();
|
||||||
|
|
||||||
|
println!("Started http server: 127.0.0.1:8080");
|
||||||
|
let _ = sys.run();
|
||||||
|
}
|
@ -2,6 +2,7 @@
|
|||||||
name = "multipart-example"
|
name = "multipart-example"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
|
workspace = "../.."
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "multipart"
|
name = "multipart"
|
||||||
@ -9,5 +10,6 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
env_logger = "*"
|
env_logger = "*"
|
||||||
actix = "^0.3.1"
|
futures = "0.1"
|
||||||
actix-web = { git = "https://github.com/actix/actix-web.git" }
|
actix = "0.4"
|
||||||
|
actix-web = { git = "https://github.com/actix/actix-web" }
|
||||||
|
24
examples/multipart/README.md
Normal file
24
examples/multipart/README.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# multipart
|
||||||
|
|
||||||
|
Multipart's `Getting Started` guide for Actix web
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd actix-web/examples/multipart
|
||||||
|
cargo run (or ``cargo watch -x run``)
|
||||||
|
# Started http server: 127.0.0.1:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
### client
|
||||||
|
|
||||||
|
- ``pip install aiohttp``
|
||||||
|
- ``python client.py``
|
||||||
|
- you must see in server console multipart fields
|
||||||
|
|
||||||
|
if ubuntu :
|
||||||
|
|
||||||
|
- ``pip3 install aiohttp``
|
||||||
|
- ``python3 client.py``
|
@ -1,26 +1,28 @@
|
|||||||
|
# This script could be used for actix-web multipart example test
|
||||||
|
# just start server and run client.py
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
|
async def req1():
|
||||||
def req1():
|
|
||||||
with aiohttp.MultipartWriter() as writer:
|
with aiohttp.MultipartWriter() as writer:
|
||||||
writer.append('test')
|
writer.append('test')
|
||||||
writer.append_json({'passed': True})
|
writer.append_json({'passed': True})
|
||||||
|
|
||||||
resp = yield from aiohttp.request(
|
resp = await aiohttp.ClientSession().request(
|
||||||
"post", 'http://localhost:8080/multipart',
|
"post", 'http://localhost:8080/multipart',
|
||||||
data=writer, headers=writer.headers)
|
data=writer, headers=writer.headers)
|
||||||
print(resp)
|
print(resp)
|
||||||
assert 200 == resp.status
|
assert 200 == resp.status
|
||||||
|
|
||||||
|
|
||||||
def req2():
|
async def req2():
|
||||||
with aiohttp.MultipartWriter() as writer:
|
with aiohttp.MultipartWriter() as writer:
|
||||||
writer.append('test')
|
writer.append('test')
|
||||||
writer.append_json({'passed': True})
|
writer.append_json({'passed': True})
|
||||||
writer.append(open('src/main.rs'))
|
writer.append(open('src/main.rs'))
|
||||||
|
|
||||||
resp = yield from aiohttp.request(
|
resp = await aiohttp.ClientSession().request(
|
||||||
"post", 'http://localhost:8080/multipart',
|
"post", 'http://localhost:8080/multipart',
|
||||||
data=writer, headers=writer.headers)
|
data=writer, headers=writer.headers)
|
||||||
print(resp)
|
print(resp)
|
||||||
|
@ -2,76 +2,58 @@
|
|||||||
extern crate actix;
|
extern crate actix;
|
||||||
extern crate actix_web;
|
extern crate actix_web;
|
||||||
extern crate env_logger;
|
extern crate env_logger;
|
||||||
|
extern crate futures;
|
||||||
|
|
||||||
use actix::*;
|
use actix::*;
|
||||||
use actix_web::*;
|
use actix_web::*;
|
||||||
|
|
||||||
struct MyRoute;
|
use futures::{Future, Stream};
|
||||||
|
use futures::future::{result, Either};
|
||||||
|
|
||||||
impl Actor for MyRoute {
|
|
||||||
type Context = HttpContext<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Route for MyRoute {
|
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>>
|
||||||
type State = ();
|
{
|
||||||
|
println!("{:?}", req);
|
||||||
|
|
||||||
fn request(mut req: HttpRequest, ctx: &mut HttpContext<Self>) -> RouteResult<Self> {
|
req.multipart() // <- get multipart stream for current request
|
||||||
println!("{:?}", req);
|
.from_err() // <- convert multipart errors
|
||||||
|
.and_then(|item| { // <- iterate over multipart items
|
||||||
|
match item {
|
||||||
|
// Handle multipart Field
|
||||||
|
multipart::MultipartItem::Field(field) => {
|
||||||
|
println!("==== FIELD ==== {:?}", field);
|
||||||
|
|
||||||
let multipart = req.multipart()?;
|
// Field in turn is stream of *Bytes* object
|
||||||
|
Either::A(
|
||||||
// get Multipart stream
|
field.map_err(Error::from)
|
||||||
WrapStream::<MyRoute>::actstream(multipart)
|
.map(|chunk| {
|
||||||
.and_then(|item, act, ctx| {
|
println!("-- CHUNK: \n{}",
|
||||||
// Multipart stream is a stream of Fields and nested Multiparts
|
std::str::from_utf8(&chunk).unwrap());})
|
||||||
match item {
|
.finish())
|
||||||
multipart::MultipartItem::Field(field) => {
|
},
|
||||||
println!("==== FIELD ==== {:?}", field);
|
multipart::MultipartItem::Nested(mp) => {
|
||||||
|
// Or item could be nested Multipart stream
|
||||||
// Read field's stream
|
Either::B(result(Ok(())))
|
||||||
fut::Either::A(
|
|
||||||
field.actstream()
|
|
||||||
.map(|chunk, act, ctx| {
|
|
||||||
println!(
|
|
||||||
"-- CHUNK: \n{}",
|
|
||||||
std::str::from_utf8(&chunk.0).unwrap());
|
|
||||||
})
|
|
||||||
.finish())
|
|
||||||
},
|
|
||||||
multipart::MultipartItem::Nested(mp) => {
|
|
||||||
// Do nothing for nested multipart stream
|
|
||||||
fut::Either::B(fut::ok(()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
// wait until stream finish
|
})
|
||||||
.finish()
|
.finish() // <- Stream::finish() combinator from actix
|
||||||
.map_err(|e, act, ctx| {
|
.map(|_| httpcodes::HTTPOk.into())
|
||||||
ctx.start(httpcodes::HTTPBadRequest);
|
.responder()
|
||||||
ctx.write_eof();
|
|
||||||
})
|
|
||||||
.map(|_, act, ctx| {
|
|
||||||
ctx.start(httpcodes::HTTPOk);
|
|
||||||
ctx.write_eof();
|
|
||||||
})
|
|
||||||
.spawn(ctx);
|
|
||||||
|
|
||||||
Reply::async(MyRoute)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||||
let _ = env_logger::init();
|
let _ = env_logger::init();
|
||||||
let sys = actix::System::new("multipart-example");
|
let sys = actix::System::new("multipart-example");
|
||||||
|
|
||||||
HttpServer::new(
|
let addr = HttpServer::new(
|
||||||
vec![
|
|| Application::new()
|
||||||
Application::default("/")
|
.middleware(middleware::Logger::default()) // <- logger
|
||||||
.resource("/multipart", |r| {
|
.resource("/multipart", |r| r.method(Method::POST).a(index)))
|
||||||
r.post::<MyRoute>();
|
.bind("127.0.0.1:8080").unwrap()
|
||||||
}).finish()
|
.start();
|
||||||
])
|
|
||||||
.serve::<_, ()>("127.0.0.1:8080").unwrap();
|
|
||||||
|
|
||||||
|
println!("Starting http server: 127.0.0.1:8080");
|
||||||
let _ = sys.run();
|
let _ = sys.run();
|
||||||
}
|
}
|
||||||
|
11
examples/state/Cargo.toml
Normal file
11
examples/state/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "state"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
|
workspace = "../.."
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
futures = "*"
|
||||||
|
env_logger = "0.4"
|
||||||
|
actix = "0.4"
|
||||||
|
actix-web = { git = "https://github.com/actix/actix-web" }
|
15
examples/state/README.md
Normal file
15
examples/state/README.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# state
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd actix-web/examples/state
|
||||||
|
cargo run
|
||||||
|
# Started http server: 127.0.0.1:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
### web client
|
||||||
|
|
||||||
|
- [http://localhost:8080/](http://localhost:8080/)
|
@ -7,10 +7,12 @@ extern crate actix;
|
|||||||
extern crate actix_web;
|
extern crate actix_web;
|
||||||
extern crate env_logger;
|
extern crate env_logger;
|
||||||
|
|
||||||
use actix::*;
|
|
||||||
use actix_web::*;
|
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
|
|
||||||
|
use actix::*;
|
||||||
|
use actix_web::*;
|
||||||
|
|
||||||
|
/// Application state
|
||||||
struct AppState {
|
struct AppState {
|
||||||
counter: Cell<usize>,
|
counter: Cell<usize>,
|
||||||
}
|
}
|
||||||
@ -34,11 +36,10 @@ impl Actor for MyWebSocket {
|
|||||||
type Context = HttpContext<Self, AppState>;
|
type Context = HttpContext<Self, AppState>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StreamHandler<ws::Message> for MyWebSocket {}
|
|
||||||
impl Handler<ws::Message> for MyWebSocket {
|
impl Handler<ws::Message> for MyWebSocket {
|
||||||
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context)
|
type Result = ();
|
||||||
-> Response<Self, ws::Message>
|
|
||||||
{
|
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
||||||
self.counter += 1;
|
self.counter += 1;
|
||||||
println!("WS({}): {:?}", self.counter, msg);
|
println!("WS({}): {:?}", self.counter, msg);
|
||||||
match msg {
|
match msg {
|
||||||
@ -50,7 +51,6 @@ impl Handler<ws::Message> for MyWebSocket {
|
|||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
Self::empty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,15 +59,18 @@ fn main() {
|
|||||||
let _ = env_logger::init();
|
let _ = env_logger::init();
|
||||||
let sys = actix::System::new("ws-example");
|
let sys = actix::System::new("ws-example");
|
||||||
|
|
||||||
HttpServer::new(
|
let addr = HttpServer::new(
|
||||||
Application::build("/", AppState{counter: Cell::new(0)})
|
|| Application::with_state(AppState{counter: Cell::new(0)})
|
||||||
// enable logger
|
// enable logger
|
||||||
.middleware(middlewares::Logger::default())
|
.middleware(middleware::Logger::default())
|
||||||
// websocket route
|
// websocket route
|
||||||
.resource("/ws/", |r| r.get(|r| ws::start(r, MyWebSocket{counter: 0})))
|
.resource(
|
||||||
|
"/ws/", |r|
|
||||||
|
r.method(Method::GET).f(|req| ws::start(req, MyWebSocket{counter: 0})))
|
||||||
// register simple handler, handle all methods
|
// register simple handler, handle all methods
|
||||||
.handler("/", index))
|
.resource("/", |r| r.f(index)))
|
||||||
.serve::<_, ()>("127.0.0.1:8080").unwrap();
|
.bind("127.0.0.1:8080").unwrap()
|
||||||
|
.start();
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
println!("Started http server: 127.0.0.1:8080");
|
||||||
let _ = sys.run();
|
let _ = sys.run();
|
BIN
examples/static/actixLogo.png
Normal file
BIN
examples/static/actixLogo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.7 KiB |
BIN
examples/static/favicon.ico
Normal file
BIN
examples/static/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
11
examples/template_tera/Cargo.toml
Normal file
11
examples/template_tera/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
[package]
|
||||||
|
name = "template-tera"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
|
workspace = "../.."
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
env_logger = "0.4"
|
||||||
|
actix = "0.4"
|
||||||
|
actix-web = { git = "https://github.com/actix/actix-web" }
|
||||||
|
tera = "*"
|
17
examples/template_tera/README.md
Normal file
17
examples/template_tera/README.md
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# template_tera
|
||||||
|
|
||||||
|
Minimal example of using the template [tera](https://github.com/Keats/tera) that displays a form.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd actix-web/examples/template_tera
|
||||||
|
cargo run (or ``cargo watch -x run``)
|
||||||
|
# Started http server: 127.0.0.1:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
### web client
|
||||||
|
|
||||||
|
- [http://localhost:8080](http://localhost:8080)
|
47
examples/template_tera/src/main.rs
Normal file
47
examples/template_tera/src/main.rs
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
extern crate actix;
|
||||||
|
extern crate actix_web;
|
||||||
|
extern crate env_logger;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate tera;
|
||||||
|
|
||||||
|
use actix_web::*;
|
||||||
|
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
template: tera::Tera, // <- store tera template in application state
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index(req: HttpRequest<State>) -> Result<HttpResponse> {
|
||||||
|
let s = if let Some(name) = req.query().get("name") { // <- submitted form
|
||||||
|
let mut ctx = tera::Context::new();
|
||||||
|
ctx.add("name", &name.to_owned());
|
||||||
|
ctx.add("text", &"Welcome!".to_owned());
|
||||||
|
req.state().template.render("user.html", &ctx)
|
||||||
|
.map_err(|_| error::ErrorInternalServerError("Template error"))?
|
||||||
|
} else {
|
||||||
|
req.state().template.render("index.html", &tera::Context::new())
|
||||||
|
.map_err(|_| error::ErrorInternalServerError("Template error"))?
|
||||||
|
};
|
||||||
|
Ok(httpcodes::HTTPOk.build()
|
||||||
|
.content_type("text/html")
|
||||||
|
.body(s)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||||
|
let _ = env_logger::init();
|
||||||
|
let sys = actix::System::new("tera-example");
|
||||||
|
|
||||||
|
let addr = HttpServer::new(|| {
|
||||||
|
let tera = compile_templates!(concat!(env!("CARGO_MANIFEST_DIR"), "/templates/**/*"));
|
||||||
|
|
||||||
|
Application::with_state(State{template: tera})
|
||||||
|
// enable logger
|
||||||
|
.middleware(middleware::Logger::default())
|
||||||
|
.resource("/", |r| r.method(Method::GET).f(index))})
|
||||||
|
.bind("127.0.0.1:8080").unwrap()
|
||||||
|
.start();
|
||||||
|
|
||||||
|
println!("Started http server: 127.0.0.1:8080");
|
||||||
|
let _ = sys.run();
|
||||||
|
}
|
17
examples/template_tera/templates/index.html
Normal file
17
examples/template_tera/templates/index.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Actix web</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Welcome!</h1>
|
||||||
|
<p>
|
||||||
|
<h3>What is your name?</h3>
|
||||||
|
<form>
|
||||||
|
<input type="text" name="name" /><br/>
|
||||||
|
<p><input type="submit"></p>
|
||||||
|
</form>
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
13
examples/template_tera/templates/user.html
Normal file
13
examples/template_tera/templates/user.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Actix web</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Hi, {{ name }}!</h1>
|
||||||
|
<p>
|
||||||
|
{{ text }}
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -2,6 +2,7 @@
|
|||||||
name = "tls-example"
|
name = "tls-example"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
|
workspace = "../.."
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "server"
|
name = "server"
|
||||||
@ -9,5 +10,5 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
env_logger = "0.4"
|
env_logger = "0.4"
|
||||||
actix = "^0.3.1"
|
actix = "0.4"
|
||||||
actix-web = { git = "https://github.com/actix/actix-web.git", features=["alpn"] }
|
actix-web = { git = "https://github.com/actix/actix-web", features=["alpn"] }
|
||||||
|
@ -1,5 +1,16 @@
|
|||||||
# tls example
|
# tls example
|
||||||
|
|
||||||
To start server use command: `cargo run`
|
## Usage
|
||||||
|
|
||||||
Test command: `curl -v https://127.0.0.1:8080/index.html --compress -k`
|
### server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd actix-web/examples/tls
|
||||||
|
cargo run (or ``cargo watch -x run``)
|
||||||
|
# Started http server: 127.0.0.1:8443
|
||||||
|
```
|
||||||
|
|
||||||
|
### web client
|
||||||
|
|
||||||
|
- curl: ``curl -v https://127.0.0.1:8443/index.html --compress -k``
|
||||||
|
- browser: [https://127.0.0.1:8443/index.html](https://127.0.0.1:8080/index.html)
|
||||||
|
@ -8,6 +8,7 @@ use std::io::Read;
|
|||||||
|
|
||||||
use actix_web::*;
|
use actix_web::*;
|
||||||
|
|
||||||
|
|
||||||
/// somple handle
|
/// somple handle
|
||||||
fn index(req: HttpRequest) -> Result<HttpResponse> {
|
fn index(req: HttpRequest) -> Result<HttpResponse> {
|
||||||
println!("{:?}", req);
|
println!("{:?}", req);
|
||||||
@ -29,21 +30,22 @@ fn main() {
|
|||||||
file.read_to_end(&mut pkcs12).unwrap();
|
file.read_to_end(&mut pkcs12).unwrap();
|
||||||
let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap().parse("12345").unwrap();
|
let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap().parse("12345").unwrap();
|
||||||
|
|
||||||
HttpServer::new(
|
let addr = HttpServer::new(
|
||||||
Application::default("/")
|
|| Application::new()
|
||||||
// enable logger
|
// enable logger
|
||||||
.middleware(middlewares::Logger::default())
|
.middleware(middleware::Logger::default())
|
||||||
// register simple handler, handle all methods
|
// register simple handler, handle all methods
|
||||||
.handler("/index.html", index)
|
.resource("/index.html", |r| r.f(index))
|
||||||
// with path parameters
|
// with path parameters
|
||||||
.resource("/", |r| r.handler(Method::GET, |req| {
|
.resource("/", |r| r.method(Method::GET).f(|req| {
|
||||||
httpcodes::HTTPFound
|
httpcodes::HTTPFound
|
||||||
.build()
|
.build()
|
||||||
.header("LOCATION", "/index.html")
|
.header("LOCATION", "/index.html")
|
||||||
.body(Body::Empty)
|
.body(Body::Empty)
|
||||||
})))
|
})))
|
||||||
.serve_tls::<_, ()>("127.0.0.1:8080", pkcs12).unwrap();
|
.bind("127.0.0.1:8443").unwrap()
|
||||||
|
.start_ssl(&pkcs12).unwrap();
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
println!("Started http server: 127.0.0.1:8443");
|
||||||
let _ = sys.run();
|
let _ = sys.run();
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
name = "websocket-example"
|
name = "websocket-example"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
|
workspace = "../.."
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "server"
|
name = "server"
|
||||||
@ -24,5 +25,6 @@ serde = "1.0"
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
|
|
||||||
actix = "^0.3.1"
|
#actix = "0.4"
|
||||||
actix-web = { git = "https://github.com/actix/actix-web.git" }
|
actix = { git = "https://github.com/actix/actix" }
|
||||||
|
actix-web = { git = "https://github.com/actix/actix-web" }
|
||||||
|
@ -9,17 +9,15 @@ Added features:
|
|||||||
* Chat server runs in separate thread
|
* Chat server runs in separate thread
|
||||||
* Tcp listener runs in separate thread
|
* Tcp listener runs in separate thread
|
||||||
|
|
||||||
|
|
||||||
## Server
|
## Server
|
||||||
|
|
||||||
Chat server listens for incoming tcp connections. Server can access several types of message:
|
Chat server listens for incoming tcp connections. Server can access several types of message:
|
||||||
|
|
||||||
* `\list` - list all available rooms
|
* `\list` - list all available rooms
|
||||||
* `\join name` - join room, if room does not exist, create new one
|
* `\join name` - join room, if room does not exist, create new one
|
||||||
* `\name name` - set session name
|
* `\name name` - set session name
|
||||||
* `some message` - just string, send messsage to all peers in same room
|
* `some message` - just string, send messsage to all peers in same room
|
||||||
* client has to send heartbeat `Ping` messages, if server does not receive a heartbeat
|
* client has to send heartbeat `Ping` messages, if server does not receive a heartbeat message for 10 seconds connection gets droppped
|
||||||
message for 10 seconds connection gets droppped
|
|
||||||
|
|
||||||
To start server use command: `cargo run --bin server`
|
To start server use command: `cargo run --bin server`
|
||||||
|
|
||||||
@ -29,7 +27,6 @@ Client connects to server. Reads input from stdin and sends to server.
|
|||||||
|
|
||||||
To run client use command: `cargo run --bin client`
|
To run client use command: `cargo run --bin client`
|
||||||
|
|
||||||
|
|
||||||
## WebSocket Browser Client
|
## WebSocket Browser Client
|
||||||
|
|
||||||
Open url: http://localhost:8080/
|
Open url: [http://localhost:8080/](http://localhost:8080/)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
extern crate actix;
|
#[macro_use] extern crate actix;
|
||||||
extern crate bytes;
|
extern crate bytes;
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
@ -56,13 +56,9 @@ fn main() {
|
|||||||
|
|
||||||
struct ChatClient;
|
struct ChatClient;
|
||||||
|
|
||||||
|
#[derive(Message)]
|
||||||
struct ClientCommand(String);
|
struct ClientCommand(String);
|
||||||
|
|
||||||
impl ResponseType for ClientCommand {
|
|
||||||
type Item = ();
|
|
||||||
type Error = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Actor for ChatClient {
|
impl Actor for ChatClient {
|
||||||
type Context = FramedContext<Self>;
|
type Context = FramedContext<Self>;
|
||||||
|
|
||||||
@ -70,6 +66,15 @@ impl Actor for ChatClient {
|
|||||||
// start heartbeats otherwise server will disconnect after 10 seconds
|
// start heartbeats otherwise server will disconnect after 10 seconds
|
||||||
self.hb(ctx)
|
self.hb(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn stopping(&mut self, _: &mut FramedContext<Self>) -> bool {
|
||||||
|
println!("Disconnected");
|
||||||
|
|
||||||
|
// Stop application on disconnect
|
||||||
|
Arbiter::system().send(actix::msgs::SystemExit(0));
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChatClient {
|
impl ChatClient {
|
||||||
@ -83,14 +88,13 @@ impl ChatClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Handle stdin commands
|
/// Handle stdin commands
|
||||||
impl Handler<ClientCommand> for ChatClient
|
impl Handler<ClientCommand> for ChatClient {
|
||||||
{
|
type Result = ();
|
||||||
fn handle(&mut self, msg: ClientCommand, ctx: &mut FramedContext<Self>)
|
|
||||||
-> Response<Self, ClientCommand>
|
fn handle(&mut self, msg: ClientCommand, ctx: &mut FramedContext<Self>) {
|
||||||
{
|
|
||||||
let m = msg.0.trim();
|
let m = msg.0.trim();
|
||||||
if m.is_empty() {
|
if m.is_empty() {
|
||||||
return Self::empty()
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// we check for /sss type of messages
|
// we check for /sss type of messages
|
||||||
@ -112,8 +116,6 @@ impl Handler<ClientCommand> for ChatClient
|
|||||||
} else {
|
} else {
|
||||||
let _ = ctx.send(codec::ChatRequest::Message(m.to_owned()));
|
let _ = ctx.send(codec::ChatRequest::Message(m.to_owned()));
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::empty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,40 +124,26 @@ impl Handler<ClientCommand> for ChatClient
|
|||||||
impl FramedActor for ChatClient {
|
impl FramedActor for ChatClient {
|
||||||
type Io = TcpStream;
|
type Io = TcpStream;
|
||||||
type Codec = codec::ClientChatCodec;
|
type Codec = codec::ClientChatCodec;
|
||||||
}
|
|
||||||
|
|
||||||
impl StreamHandler<codec::ChatResponse, io::Error> for ChatClient {
|
fn handle(&mut self, msg: io::Result<codec::ChatResponse>, ctx: &mut FramedContext<Self>) {
|
||||||
|
|
||||||
fn finished(&mut self, _: &mut FramedContext<Self>) {
|
|
||||||
println!("Disconnected");
|
|
||||||
|
|
||||||
// Stop application on disconnect
|
|
||||||
Arbiter::system().send(msgs::SystemExit(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Handler<codec::ChatResponse, io::Error> for ChatClient {
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: codec::ChatResponse, _: &mut FramedContext<Self>)
|
|
||||||
-> Response<Self, codec::ChatResponse>
|
|
||||||
{
|
|
||||||
match msg {
|
match msg {
|
||||||
codec::ChatResponse::Message(ref msg) => {
|
Err(_) => ctx.stop(),
|
||||||
println!("message: {}", msg);
|
Ok(msg) => match msg {
|
||||||
}
|
codec::ChatResponse::Message(ref msg) => {
|
||||||
codec::ChatResponse::Joined(ref msg) => {
|
println!("message: {}", msg);
|
||||||
println!("!!! joined: {}", msg);
|
|
||||||
}
|
|
||||||
codec::ChatResponse::Rooms(rooms) => {
|
|
||||||
println!("\n!!! Available rooms:");
|
|
||||||
for room in rooms {
|
|
||||||
println!("{}", room);
|
|
||||||
}
|
}
|
||||||
println!("");
|
codec::ChatResponse::Joined(ref msg) => {
|
||||||
|
println!("!!! joined: {}", msg);
|
||||||
|
}
|
||||||
|
codec::ChatResponse::Rooms(rooms) => {
|
||||||
|
println!("\n!!! Available rooms:");
|
||||||
|
for room in rooms {
|
||||||
|
println!("{}", room);
|
||||||
|
}
|
||||||
|
println!("");
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::empty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ extern crate serde;
|
|||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
#[macro_use] extern crate serde_derive;
|
#[macro_use] extern crate serde_derive;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
extern crate actix;
|
extern crate actix;
|
||||||
extern crate actix_web;
|
extern crate actix_web;
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ struct WsChatSessionState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Entry point for our route
|
/// Entry point for our route
|
||||||
fn chat_route(req: HttpRequest<WsChatSessionState>) -> Result<Reply> {
|
fn chat_route(req: HttpRequest<WsChatSessionState>) -> Result<HttpResponse> {
|
||||||
ws::start(
|
ws::start(
|
||||||
req,
|
req,
|
||||||
WsChatSession {
|
WsChatSession {
|
||||||
@ -52,23 +53,49 @@ struct WsChatSession {
|
|||||||
|
|
||||||
impl Actor for WsChatSession {
|
impl Actor for WsChatSession {
|
||||||
type Context = HttpContext<Self, WsChatSessionState>;
|
type Context = HttpContext<Self, WsChatSessionState>;
|
||||||
|
|
||||||
|
/// Method is called on actor start.
|
||||||
|
/// We register ws session with ChatServer
|
||||||
|
fn started(&mut self, ctx: &mut Self::Context) {
|
||||||
|
// register self in chat server. `AsyncContext::wait` register
|
||||||
|
// future within context, but context waits until this future resolves
|
||||||
|
// before processing any other events.
|
||||||
|
// HttpContext::state() is instance of WsChatSessionState, state is shared across all
|
||||||
|
// routes within application
|
||||||
|
let subs = ctx.sync_subscriber();
|
||||||
|
ctx.state().addr.call(
|
||||||
|
self, server::Connect{addr: subs}).then(
|
||||||
|
|res, act, ctx| {
|
||||||
|
match res {
|
||||||
|
Ok(Ok(res)) => act.id = res,
|
||||||
|
// something is wrong with chat server
|
||||||
|
_ => ctx.stop(),
|
||||||
|
}
|
||||||
|
fut::ok(())
|
||||||
|
}).wait(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stopping(&mut self, ctx: &mut Self::Context) -> bool {
|
||||||
|
// notify chat server
|
||||||
|
ctx.state().addr.send(server::Disconnect{id: self.id});
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handle messages from chat server, we simply send it to peer websocket
|
/// Handle messages from chat server, we simply send it to peer websocket
|
||||||
impl Handler<session::Message> for WsChatSession {
|
impl Handler<session::Message> for WsChatSession {
|
||||||
fn handle(&mut self, msg: session::Message, ctx: &mut Self::Context)
|
type Result = ();
|
||||||
-> Response<Self, session::Message>
|
|
||||||
{
|
fn handle(&mut self, msg: session::Message, ctx: &mut Self::Context) {
|
||||||
ws::WsWriter::text(ctx, &msg.0);
|
ws::WsWriter::text(ctx, &msg.0);
|
||||||
Self::empty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// WebSocket message handler
|
/// WebSocket message handler
|
||||||
impl Handler<ws::Message> for WsChatSession {
|
impl Handler<ws::Message> for WsChatSession {
|
||||||
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context)
|
type Result = ();
|
||||||
-> Response<Self, ws::Message>
|
|
||||||
{
|
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
||||||
println!("WEBSOCKET MESSAGE: {:?}", msg);
|
println!("WEBSOCKET MESSAGE: {:?}", msg);
|
||||||
match msg {
|
match msg {
|
||||||
ws::Message::Ping(msg) =>
|
ws::Message::Ping(msg) =>
|
||||||
@ -140,42 +167,9 @@ impl Handler<ws::Message> for WsChatSession {
|
|||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
Self::empty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StreamHandler<ws::Message> for WsChatSession
|
|
||||||
{
|
|
||||||
/// Method is called when stream get polled first time.
|
|
||||||
/// We register ws session with ChatServer
|
|
||||||
fn started(&mut self, ctx: &mut Self::Context) {
|
|
||||||
// register self in chat server. `AsyncContext::wait` register
|
|
||||||
// future within context, but context waits until this future resolves
|
|
||||||
// before processing any other events.
|
|
||||||
// HttpContext::state() is instance of WsChatSessionState, state is shared across all
|
|
||||||
// routes within application
|
|
||||||
let subs = ctx.sync_subscriber();
|
|
||||||
ctx.state().addr.call(
|
|
||||||
self, server::Connect{addr: subs}).then(
|
|
||||||
|res, act, ctx| {
|
|
||||||
match res {
|
|
||||||
Ok(Ok(res)) => act.id = res,
|
|
||||||
// something is wrong with chat server
|
|
||||||
_ => ctx.stop(),
|
|
||||||
}
|
|
||||||
fut::ok(())
|
|
||||||
}).wait(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Method is called when stream finishes, even if stream finishes with error.
|
|
||||||
fn finished(&mut self, ctx: &mut Self::Context) {
|
|
||||||
// notify chat server
|
|
||||||
ctx.state().addr.send(server::Disconnect{id: self.id});
|
|
||||||
ctx.stop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let _ = env_logger::init();
|
let _ = env_logger::init();
|
||||||
let sys = actix::System::new("websocket-example");
|
let sys = actix::System::new("websocket-example");
|
||||||
@ -192,24 +186,28 @@ fn main() {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Websocket sessions state
|
|
||||||
let state = WsChatSessionState { addr: server };
|
|
||||||
|
|
||||||
// Create Http server with websocket support
|
// Create Http server with websocket support
|
||||||
HttpServer::new(
|
let addr = HttpServer::new(
|
||||||
Application::build("/", state)
|
move || {
|
||||||
// redirect to websocket.html
|
// Websocket sessions state
|
||||||
.resource("/", |r| r.handler(Method::GET, |req| {
|
let state = WsChatSessionState { addr: server.clone() };
|
||||||
httpcodes::HTTPFound
|
|
||||||
.build()
|
|
||||||
.header("LOCATION", "/static/websocket.html")
|
|
||||||
.body(Body::Empty)
|
|
||||||
}))
|
|
||||||
// websocket
|
|
||||||
.resource("/ws/", |r| r.get(chat_route))
|
|
||||||
// static resources
|
|
||||||
.route("/static", StaticFiles::new("static/", true)))
|
|
||||||
.serve::<_, ()>("127.0.0.1:8080").unwrap();
|
|
||||||
|
|
||||||
|
Application::with_state(state)
|
||||||
|
// redirect to websocket.html
|
||||||
|
.resource("/", |r| r.method(Method::GET).f(|_| {
|
||||||
|
httpcodes::HTTPFound
|
||||||
|
.build()
|
||||||
|
.header("LOCATION", "/static/websocket.html")
|
||||||
|
.finish()
|
||||||
|
}))
|
||||||
|
// websocket
|
||||||
|
.resource("/ws/", |r| r.route().f(chat_route))
|
||||||
|
// static resources
|
||||||
|
.handler("/static/", fs::StaticFiles::new("static/", true))
|
||||||
|
})
|
||||||
|
.bind("127.0.0.1:8080").unwrap()
|
||||||
|
.start();
|
||||||
|
|
||||||
|
println!("Started http server: 127.0.0.1:8080");
|
||||||
let _ = sys.run();
|
let _ = sys.run();
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ use session;
|
|||||||
|
|
||||||
/// New chat session is created
|
/// New chat session is created
|
||||||
pub struct Connect {
|
pub struct Connect {
|
||||||
pub addr: Box<Subscriber<session::Message> + Send>,
|
pub addr: Box<actix::Subscriber<session::Message> + Send>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Response type for Connect message
|
/// Response type for Connect message
|
||||||
@ -25,16 +25,13 @@ impl ResponseType for Connect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Session is disconnected
|
/// Session is disconnected
|
||||||
|
#[derive(Message)]
|
||||||
pub struct Disconnect {
|
pub struct Disconnect {
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseType for Disconnect {
|
|
||||||
type Item = ();
|
|
||||||
type Error = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Send message to specific room
|
/// Send message to specific room
|
||||||
|
#[derive(Message)]
|
||||||
pub struct Message {
|
pub struct Message {
|
||||||
/// Id of the client session
|
/// Id of the client session
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
@ -44,11 +41,6 @@ pub struct Message {
|
|||||||
pub room: String,
|
pub room: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseType for Message {
|
|
||||||
type Item = ();
|
|
||||||
type Error = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List of available rooms
|
/// List of available rooms
|
||||||
pub struct ListRooms;
|
pub struct ListRooms;
|
||||||
|
|
||||||
@ -58,6 +50,7 @@ impl ResponseType for ListRooms {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Join room, if room does not exists create new one.
|
/// Join room, if room does not exists create new one.
|
||||||
|
#[derive(Message)]
|
||||||
pub struct Join {
|
pub struct Join {
|
||||||
/// Client id
|
/// Client id
|
||||||
pub id: usize,
|
pub id: usize,
|
||||||
@ -65,15 +58,10 @@ pub struct Join {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResponseType for Join {
|
|
||||||
type Item = ();
|
|
||||||
type Error = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `ChatServer` manages chat rooms and responsible for coordinating chat session.
|
/// `ChatServer` manages chat rooms and responsible for coordinating chat session.
|
||||||
/// implementation is super primitive
|
/// implementation is super primitive
|
||||||
pub struct ChatServer {
|
pub struct ChatServer {
|
||||||
sessions: HashMap<usize, Box<Subscriber<session::Message> + Send>>,
|
sessions: HashMap<usize, Box<actix::Subscriber<session::Message> + Send>>,
|
||||||
rooms: HashMap<String, HashSet<usize>>,
|
rooms: HashMap<String, HashSet<usize>>,
|
||||||
rng: RefCell<ThreadRng>,
|
rng: RefCell<ThreadRng>,
|
||||||
}
|
}
|
||||||
@ -118,8 +106,9 @@ impl Actor for ChatServer {
|
|||||||
///
|
///
|
||||||
/// Register new session and assign unique id to this session
|
/// Register new session and assign unique id to this session
|
||||||
impl Handler<Connect> for ChatServer {
|
impl Handler<Connect> for ChatServer {
|
||||||
|
type Result = MessageResult<Connect>;
|
||||||
|
|
||||||
fn handle(&mut self, msg: Connect, _: &mut Context<Self>) -> Response<Self, Connect> {
|
fn handle(&mut self, msg: Connect, _: &mut Context<Self>) -> Self::Result {
|
||||||
println!("Someone joined");
|
println!("Someone joined");
|
||||||
|
|
||||||
// notify all users in same room
|
// notify all users in same room
|
||||||
@ -133,14 +122,15 @@ impl Handler<Connect> for ChatServer {
|
|||||||
self.rooms.get_mut(&"Main".to_owned()).unwrap().insert(id);
|
self.rooms.get_mut(&"Main".to_owned()).unwrap().insert(id);
|
||||||
|
|
||||||
// send id back
|
// send id back
|
||||||
Self::reply(id)
|
Ok(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handler for Disconnect message.
|
/// Handler for Disconnect message.
|
||||||
impl Handler<Disconnect> for ChatServer {
|
impl Handler<Disconnect> for ChatServer {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
fn handle(&mut self, msg: Disconnect, _: &mut Context<Self>) -> Response<Self, Disconnect> {
|
fn handle(&mut self, msg: Disconnect, _: &mut Context<Self>) {
|
||||||
println!("Someone disconnected");
|
println!("Someone disconnected");
|
||||||
|
|
||||||
let mut rooms: Vec<String> = Vec::new();
|
let mut rooms: Vec<String> = Vec::new();
|
||||||
@ -158,40 +148,39 @@ impl Handler<Disconnect> for ChatServer {
|
|||||||
for room in rooms {
|
for room in rooms {
|
||||||
self.send_message(&room, "Someone disconnected", 0);
|
self.send_message(&room, "Someone disconnected", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::empty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handler for Message message.
|
/// Handler for Message message.
|
||||||
impl Handler<Message> for ChatServer {
|
impl Handler<Message> for ChatServer {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
fn handle(&mut self, msg: Message, _: &mut Context<Self>) -> Response<Self, Message> {
|
fn handle(&mut self, msg: Message, _: &mut Context<Self>) {
|
||||||
self.send_message(&msg.room, msg.msg.as_str(), msg.id);
|
self.send_message(&msg.room, msg.msg.as_str(), msg.id);
|
||||||
|
|
||||||
Self::empty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handler for `ListRooms` message.
|
/// Handler for `ListRooms` message.
|
||||||
impl Handler<ListRooms> for ChatServer {
|
impl Handler<ListRooms> for ChatServer {
|
||||||
|
type Result = MessageResult<ListRooms>;
|
||||||
|
|
||||||
fn handle(&mut self, _: ListRooms, _: &mut Context<Self>) -> Response<Self, ListRooms> {
|
fn handle(&mut self, _: ListRooms, _: &mut Context<Self>) -> Self::Result {
|
||||||
let mut rooms = Vec::new();
|
let mut rooms = Vec::new();
|
||||||
|
|
||||||
for key in self.rooms.keys() {
|
for key in self.rooms.keys() {
|
||||||
rooms.push(key.to_owned())
|
rooms.push(key.to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::reply(rooms)
|
Ok(rooms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Join room, send disconnect message to old room
|
/// Join room, send disconnect message to old room
|
||||||
/// send join message to new room
|
/// send join message to new room
|
||||||
impl Handler<Join> for ChatServer {
|
impl Handler<Join> for ChatServer {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
fn handle(&mut self, msg: Join, _: &mut Context<Self>) -> Response<Self, Join> {
|
fn handle(&mut self, msg: Join, _: &mut Context<Self>) {
|
||||||
let Join {id, name} = msg;
|
let Join {id, name} = msg;
|
||||||
let mut rooms = Vec::new();
|
let mut rooms = Vec::new();
|
||||||
|
|
||||||
@ -211,7 +200,5 @@ impl Handler<Join> for ChatServer {
|
|||||||
}
|
}
|
||||||
self.send_message(&name, "Someone connected", id);
|
self.send_message(&name, "Someone connected", id);
|
||||||
self.rooms.get_mut(&name).unwrap().insert(id);
|
self.rooms.get_mut(&name).unwrap().insert(id);
|
||||||
|
|
||||||
Self::empty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,20 +6,16 @@ use std::time::{Instant, Duration};
|
|||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use tokio_core::net::{TcpStream, TcpListener};
|
use tokio_core::net::{TcpStream, TcpListener};
|
||||||
|
|
||||||
use actix::*;
|
use actix::prelude::*;
|
||||||
|
|
||||||
use server::{self, ChatServer};
|
use server::{self, ChatServer};
|
||||||
use codec::{ChatRequest, ChatResponse, ChatCodec};
|
use codec::{ChatRequest, ChatResponse, ChatCodec};
|
||||||
|
|
||||||
|
|
||||||
/// Chat server sends this messages to session
|
/// Chat server sends this messages to session
|
||||||
|
#[derive(Message)]
|
||||||
pub struct Message(pub String);
|
pub struct Message(pub String);
|
||||||
|
|
||||||
impl ResponseType for Message {
|
|
||||||
type Item = ();
|
|
||||||
type Error = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `ChatSession` actor is responsible for tcp peer communitions.
|
/// `ChatSession` actor is responsible for tcp peer communitions.
|
||||||
pub struct ChatSession {
|
pub struct ChatSession {
|
||||||
/// unique session id
|
/// unique session id
|
||||||
@ -36,104 +32,87 @@ impl Actor for ChatSession {
|
|||||||
/// For tcp communication we are going to use `FramedContext`.
|
/// For tcp communication we are going to use `FramedContext`.
|
||||||
/// It is convinient wrapper around `Framed` object from `tokio_io`
|
/// It is convinient wrapper around `Framed` object from `tokio_io`
|
||||||
type Context = FramedContext<Self>;
|
type Context = FramedContext<Self>;
|
||||||
}
|
|
||||||
|
|
||||||
/// To use `FramedContext` we have to define Io type and Codec
|
fn started(&mut self, ctx: &mut Self::Context) {
|
||||||
impl FramedActor for ChatSession {
|
|
||||||
type Io = TcpStream;
|
|
||||||
type Codec= ChatCodec;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Also `FramedContext` requires Actor which is able to handle stream
|
|
||||||
/// of `<Codec as Decoder>::Item` items.
|
|
||||||
impl StreamHandler<ChatRequest, io::Error> for ChatSession {
|
|
||||||
|
|
||||||
fn started(&mut self, ctx: &mut FramedContext<Self>) {
|
|
||||||
// we'll start heartbeat process on session start.
|
// we'll start heartbeat process on session start.
|
||||||
self.hb(ctx);
|
self.hb(ctx);
|
||||||
|
|
||||||
// register self in chat server. `AsyncContext::wait` register
|
// register self in chat server. `AsyncContext::wait` register
|
||||||
// future within context, but context waits until this future resolves
|
// future within context, but context waits until this future resolves
|
||||||
// before processing any other events.
|
// before processing any other events.
|
||||||
self.addr.call(self, server::Connect{addr: ctx.sync_subscriber()}).then(|res, act, ctx| {
|
let addr: SyncAddress<_> = ctx.address();
|
||||||
match res {
|
self.addr.call(self, server::Connect{addr: addr.subscriber()})
|
||||||
Ok(Ok(res)) => act.id = res,
|
.then(|res, act, ctx| {
|
||||||
// something is wrong with chat server
|
match res {
|
||||||
_ => ctx.stop(),
|
Ok(Ok(res)) => act.id = res,
|
||||||
}
|
// something is wrong with chat server
|
||||||
fut::ok(())
|
_ => ctx.stop(),
|
||||||
}).wait(ctx);
|
}
|
||||||
|
actix::fut::ok(())
|
||||||
|
}).wait(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finished(&mut self, ctx: &mut FramedContext<Self>) {
|
fn stopping(&mut self, ctx: &mut Self::Context) -> bool {
|
||||||
// notify chat server
|
// notify chat server
|
||||||
self.addr.send(server::Disconnect{id: self.id});
|
self.addr.send(server::Disconnect{id: self.id});
|
||||||
|
true
|
||||||
ctx.stop()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Handler<ChatRequest, io::Error> for ChatSession {
|
/// To use `FramedContext` we have to define Io type and Codec
|
||||||
|
impl FramedActor for ChatSession {
|
||||||
/// We'll stop chat session actor on any error, high likely it is just
|
type Io = TcpStream;
|
||||||
/// termination of the tcp stream.
|
type Codec= ChatCodec;
|
||||||
fn error(&mut self, _: io::Error, ctx: &mut FramedContext<Self>) {
|
|
||||||
ctx.stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This is main event loop for client requests
|
/// This is main event loop for client requests
|
||||||
fn handle(&mut self, msg: ChatRequest, ctx: &mut FramedContext<Self>)
|
fn handle(&mut self, msg: io::Result<ChatRequest>, ctx: &mut FramedContext<Self>) {
|
||||||
-> Response<Self, ChatRequest>
|
|
||||||
{
|
|
||||||
match msg {
|
match msg {
|
||||||
ChatRequest::List => {
|
Err(_) => ctx.stop(),
|
||||||
// Send ListRooms message to chat server and wait for response
|
Ok(msg) => match msg {
|
||||||
println!("List rooms");
|
ChatRequest::List => {
|
||||||
self.addr.call(self, server::ListRooms).then(|res, _, ctx| {
|
// Send ListRooms message to chat server and wait for response
|
||||||
match res {
|
println!("List rooms");
|
||||||
Ok(Ok(rooms)) => {
|
self.addr.call(self, server::ListRooms).then(|res, _, ctx| {
|
||||||
let _ = ctx.send(ChatResponse::Rooms(rooms));
|
match res {
|
||||||
},
|
Ok(Ok(rooms)) => {
|
||||||
|
let _ = ctx.send(ChatResponse::Rooms(rooms));
|
||||||
|
},
|
||||||
_ => println!("Something is wrong"),
|
_ => println!("Something is wrong"),
|
||||||
}
|
}
|
||||||
fut::ok(())
|
actix::fut::ok(())
|
||||||
}).wait(ctx)
|
}).wait(ctx)
|
||||||
// .wait(ctx) pauses all events in context,
|
// .wait(ctx) pauses all events in context,
|
||||||
// so actor wont receive any new messages until it get list of rooms back
|
// so actor wont receive any new messages until it get list of rooms back
|
||||||
},
|
},
|
||||||
ChatRequest::Join(name) => {
|
ChatRequest::Join(name) => {
|
||||||
println!("Join to room: {}", name);
|
println!("Join to room: {}", name);
|
||||||
self.room = name.clone();
|
self.room = name.clone();
|
||||||
self.addr.send(server::Join{id: self.id, name: name.clone()});
|
self.addr.send(server::Join{id: self.id, name: name.clone()});
|
||||||
let _ = ctx.send(ChatResponse::Joined(name));
|
let _ = ctx.send(ChatResponse::Joined(name));
|
||||||
},
|
},
|
||||||
ChatRequest::Message(message) => {
|
ChatRequest::Message(message) => {
|
||||||
// send message to chat server
|
// send message to chat server
|
||||||
println!("Peer message: {}", message);
|
println!("Peer message: {}", message);
|
||||||
self.addr.send(
|
self.addr.send(
|
||||||
server::Message{id: self.id,
|
server::Message{id: self.id,
|
||||||
msg: message, room:
|
msg: message, room:
|
||||||
self.room.clone()})
|
self.room.clone()})
|
||||||
|
}
|
||||||
|
// we update heartbeat time on ping from peer
|
||||||
|
ChatRequest::Ping =>
|
||||||
|
self.hb = Instant::now(),
|
||||||
}
|
}
|
||||||
// we update heartbeat time on ping from peer
|
|
||||||
ChatRequest::Ping =>
|
|
||||||
self.hb = Instant::now(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::empty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handler for Message, chat server sends this message, we just send string to peer
|
/// Handler for Message, chat server sends this message, we just send string to peer
|
||||||
impl Handler<Message> for ChatSession {
|
impl Handler<Message> for ChatSession {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
fn handle(&mut self, msg: Message, ctx: &mut FramedContext<Self>)
|
fn handle(&mut self, msg: Message, ctx: &mut FramedContext<Self>) {
|
||||||
-> Response<Self, Message>
|
|
||||||
{
|
|
||||||
// send message to peer
|
// send message to peer
|
||||||
let _ = ctx.send(ChatResponse::Message(msg.0));
|
let _ = ctx.send(ChatResponse::Message(msg.0));
|
||||||
|
|
||||||
Self::empty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,7 +167,9 @@ impl TcpServer {
|
|||||||
// So to be able to handle this events `Server` actor has to implement
|
// So to be able to handle this events `Server` actor has to implement
|
||||||
// stream handler `StreamHandler<(TcpStream, net::SocketAddr), io::Error>`
|
// stream handler `StreamHandler<(TcpStream, net::SocketAddr), io::Error>`
|
||||||
let _: () = TcpServer::create(|ctx| {
|
let _: () = TcpServer::create(|ctx| {
|
||||||
ctx.add_stream(listener.incoming().map(|(t, a)| TcpConnect(t, a)));
|
ctx.add_message_stream(listener.incoming()
|
||||||
|
.map_err(|_| ())
|
||||||
|
.map(|(t, a)| TcpConnect(t, a)));
|
||||||
TcpServer{chat: chat}
|
TcpServer{chat: chat}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -200,27 +181,17 @@ impl Actor for TcpServer {
|
|||||||
type Context = Context<Self>;
|
type Context = Context<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Message)]
|
||||||
struct TcpConnect(TcpStream, net::SocketAddr);
|
struct TcpConnect(TcpStream, net::SocketAddr);
|
||||||
|
|
||||||
impl ResponseType for TcpConnect {
|
|
||||||
type Item = ();
|
|
||||||
type Error = ();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle stream of TcpStream's
|
/// Handle stream of TcpStream's
|
||||||
impl StreamHandler<TcpConnect, io::Error> for TcpServer {}
|
impl Handler<TcpConnect> for TcpServer {
|
||||||
|
type Result = ();
|
||||||
|
|
||||||
impl Handler<TcpConnect, io::Error> for TcpServer {
|
fn handle(&mut self, msg: TcpConnect, _: &mut Context<Self>) {
|
||||||
|
|
||||||
fn handle(&mut self, msg: TcpConnect, _: &mut Context<Self>) -> Response<Self, TcpConnect>
|
|
||||||
{
|
|
||||||
// For each incoming connection we create `ChatSession` actor
|
// For each incoming connection we create `ChatSession` actor
|
||||||
// with out chat server address.
|
// with out chat server address.
|
||||||
let server = self.chat.clone();
|
let server = self.chat.clone();
|
||||||
let _: () = ChatSession::new(server).framed(msg.0, ChatCodec);
|
let _: () = ChatSession::new(server).framed(msg.0, ChatCodec);
|
||||||
|
|
||||||
// this is response for message, which is defined by `ResponseType` trait
|
|
||||||
// in this case we just return unit.
|
|
||||||
Self::empty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
15
examples/websocket/Cargo.toml
Normal file
15
examples/websocket/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "websocket"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "server"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
env_logger = "*"
|
||||||
|
futures = "0.1"
|
||||||
|
#actix = "0.4"
|
||||||
|
actix = { git = "https://github.com/actix/actix.git" }
|
||||||
|
actix-web = { git = "https://github.com/actix/actix-web.git" }
|
27
examples/websocket/README.md
Normal file
27
examples/websocket/README.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# websockect
|
||||||
|
|
||||||
|
Simple echo websocket server.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd actix-web/examples/websocket
|
||||||
|
cargo run
|
||||||
|
# Started http server: 127.0.0.1:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
### web client
|
||||||
|
|
||||||
|
- [http://localhost:8080/ws/index.html](http://localhost:8080/ws/index.html)
|
||||||
|
|
||||||
|
### python client
|
||||||
|
|
||||||
|
- ``pip install aiohttp``
|
||||||
|
- ``python websocket-client.py``
|
||||||
|
|
||||||
|
if ubuntu :
|
||||||
|
|
||||||
|
- ``pip3 install aiohttp``
|
||||||
|
- ``python3 websocket-client.py``
|
@ -11,10 +11,9 @@ extern crate env_logger;
|
|||||||
use actix::*;
|
use actix::*;
|
||||||
use actix_web::*;
|
use actix_web::*;
|
||||||
|
|
||||||
|
|
||||||
/// do websocket handshake and start `MyWebSocket` actor
|
/// do websocket handshake and start `MyWebSocket` actor
|
||||||
fn ws_index(r: HttpRequest) -> Reply {
|
fn ws_index(r: HttpRequest) -> Result<HttpResponse> {
|
||||||
ws::start(r, MyWebSocket).into()
|
ws::start(r, MyWebSocket)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// websocket connection is long running connection, it easier
|
/// websocket connection is long running connection, it easier
|
||||||
@ -25,21 +24,11 @@ impl Actor for MyWebSocket {
|
|||||||
type Context = HttpContext<Self>;
|
type Context = HttpContext<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Standard actix's stream handler for a stream of `ws::Message`
|
/// Handler for `ws::Message`
|
||||||
impl StreamHandler<ws::Message> for MyWebSocket {
|
|
||||||
fn started(&mut self, ctx: &mut Self::Context) {
|
|
||||||
println!("WebSocket session openned");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finished(&mut self, ctx: &mut Self::Context) {
|
|
||||||
println!("WebSocket session closed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Handler<ws::Message> for MyWebSocket {
|
impl Handler<ws::Message> for MyWebSocket {
|
||||||
fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext<Self>)
|
type Result = ();
|
||||||
-> Response<Self, ws::Message>
|
|
||||||
{
|
fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext<Self>) {
|
||||||
// process websocket messages
|
// process websocket messages
|
||||||
println!("WS: {:?}", msg);
|
println!("WS: {:?}", msg);
|
||||||
match msg {
|
match msg {
|
||||||
@ -51,7 +40,6 @@ impl Handler<ws::Message> for MyWebSocket {
|
|||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
Self::empty()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,16 +48,17 @@ fn main() {
|
|||||||
let _ = env_logger::init();
|
let _ = env_logger::init();
|
||||||
let sys = actix::System::new("ws-example");
|
let sys = actix::System::new("ws-example");
|
||||||
|
|
||||||
HttpServer::new(
|
let _addr = HttpServer::new(
|
||||||
Application::default("/")
|
|| Application::new()
|
||||||
// enable logger
|
// enable logger
|
||||||
.middleware(middlewares::Logger::default())
|
.middleware(middleware::Logger::default())
|
||||||
// websocket route
|
// websocket route
|
||||||
.resource("/ws/", |r| r.get(ws_index))
|
.resource("/ws/", |r| r.method(Method::GET).f(ws_index))
|
||||||
// static files
|
// static files
|
||||||
.route("/", StaticFiles::new("examples/static/", true)))
|
.handler("/", fs::StaticFiles::new("../static/", true)))
|
||||||
// start http server on 127.0.0.1:8080
|
// start http server on 127.0.0.1:8080
|
||||||
.serve::<_, ()>("127.0.0.1:8080").unwrap();
|
.bind("127.0.0.1:8080").unwrap()
|
||||||
|
.start();
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8080");
|
println!("Started http server: 127.0.0.1:8080");
|
||||||
let _ = sys.run();
|
let _ = sys.run();
|
@ -3,13 +3,14 @@
|
|||||||
[Quickstart](./qs_1.md)
|
[Quickstart](./qs_1.md)
|
||||||
- [Getting Started](./qs_2.md)
|
- [Getting Started](./qs_2.md)
|
||||||
- [Application](./qs_3.md)
|
- [Application](./qs_3.md)
|
||||||
|
- [Server](./qs_3_5.md)
|
||||||
- [Handler](./qs_4.md)
|
- [Handler](./qs_4.md)
|
||||||
- [Resources and Routes](./qs_5.md)
|
- [Errors](./qs_4_5.md)
|
||||||
- [Application state](./qs_6.md)
|
- [URL Dispatch](./qs_5.md)
|
||||||
- [Request](./qs_7.md)
|
- [Request & Response](./qs_7.md)
|
||||||
- [Response](./qs_8.md)
|
- [Testing](./qs_8.md)
|
||||||
- [WebSockets](./qs_9.md)
|
- [Middlewares](./qs_10.md)
|
||||||
- [User sessions](./qs_10.md)
|
|
||||||
- [Logging](./qs_11.md)
|
|
||||||
- [Static file handling](./qs_12.md)
|
- [Static file handling](./qs_12.md)
|
||||||
|
- [WebSockets](./qs_9.md)
|
||||||
- [HTTP/2](./qs_13.md)
|
- [HTTP/2](./qs_13.md)
|
||||||
|
- [Database integration](./qs_14.md)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Quickstart
|
# Quick start
|
||||||
|
|
||||||
Before you can start writing a actix web application, you’ll need a version of Rust installed.
|
Before you can start writing a actix web application, you’ll need a version of Rust installed.
|
||||||
We recommend you use rustup to install or configure such a version.
|
We recommend you use rustup to install or configure such a version.
|
||||||
@ -23,12 +23,12 @@ Actix web framework requies rust version 1.20 and up.
|
|||||||
|
|
||||||
The fastest way to start experimenting with actix web is to clone the actix web repository
|
The fastest way to start experimenting with actix web is to clone the actix web repository
|
||||||
and run the included examples in the examples/ directory. The following set of
|
and run the included examples in the examples/ directory. The following set of
|
||||||
commands runs the `basic` example:
|
commands runs the `basics` example:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/actix/actix-web
|
git clone https://github.com/actix/actix-web
|
||||||
cd actix-web
|
cd actix-web/examples/basics
|
||||||
cargo run --example basic
|
cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
Check `examples/` directory for more examples.
|
Check [examples/](https://github.com/actix/actix-web/tree/master/examples) directory for more examples.
|
||||||
|
@ -1 +1,206 @@
|
|||||||
# User sessions
|
# Middlewares
|
||||||
|
|
||||||
|
Actix middlewares system allows to add additional behaviour to request/response processing.
|
||||||
|
Middleware can hook into incomnig request process and modify request or halt request
|
||||||
|
processing and return response early. Also it can hook into response processing.
|
||||||
|
|
||||||
|
Typically middlewares involves in following actions:
|
||||||
|
|
||||||
|
* Pre-process the Request
|
||||||
|
* Post-process a Response
|
||||||
|
* Modify application state
|
||||||
|
* Access external services (redis, logging, sessions)
|
||||||
|
|
||||||
|
Middlewares are registered for each application and get executed in same order as
|
||||||
|
registraton order. In general, *middleware* is a type that implements
|
||||||
|
[*Middleware trait*](../actix_web/middlewares/trait.Middleware.html). Each method
|
||||||
|
in this trait has default implementation. Each method can return result immidietly
|
||||||
|
or *future* object.
|
||||||
|
|
||||||
|
Here is example of simple middleware that adds request and response headers:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate http;
|
||||||
|
# extern crate actix_web;
|
||||||
|
use http::{header, HttpTryFrom};
|
||||||
|
use actix_web::*;
|
||||||
|
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>) -> Started {
|
||||||
|
req.headers_mut().insert(
|
||||||
|
header::CONTENT_TYPE, header::HeaderValue::from_static("text/plain"));
|
||||||
|
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) -> Response {
|
||||||
|
resp.headers_mut().insert(
|
||||||
|
header::HeaderName::try_from("X-VERSION").unwrap(),
|
||||||
|
header::HeaderValue::from_static("0.2"));
|
||||||
|
Response::Done(resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
Application::new()
|
||||||
|
.middleware(Headers) // <- Register middleware, this method could be called multiple times
|
||||||
|
.resource("/", |r| r.h(httpcodes::HTTPOk));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Active provides several useful middlewares, like *logging*, *user sessions*, etc.
|
||||||
|
|
||||||
|
|
||||||
|
## Logging
|
||||||
|
|
||||||
|
Logging is implemented as middleware.
|
||||||
|
It is common to register logging middleware as first middleware for application.
|
||||||
|
Logging middleware has to be registered for each application.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
Create `Logger` middleware with the specified `format`.
|
||||||
|
Default `Logger` could 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;
|
||||||
|
use actix_web::Application;
|
||||||
|
use actix_web::middleware::Logger;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
Application::new()
|
||||||
|
.middleware(Logger::default())
|
||||||
|
.middleware(Logger::new("%a %{User-Agent}i"))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is example of 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 `DefaultHeaders` middleware could be used.
|
||||||
|
*DefaultHeaders* middleware does not set header if response headers already contains
|
||||||
|
specified header.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
|
use actix_web::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = Application::new()
|
||||||
|
.middleware(
|
||||||
|
middleware::DefaultHeaders::build()
|
||||||
|
.header("X-Version", "0.2")
|
||||||
|
.finish())
|
||||||
|
.resource("/test", |r| {
|
||||||
|
r.method(Method::GET).f(|req| httpcodes::HTTPOk);
|
||||||
|
r.method(Method::HEAD).f(|req| httpcodes::HTTPMethodNotAllowed);
|
||||||
|
})
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## User sessions
|
||||||
|
|
||||||
|
Actix provides general solution for session management.
|
||||||
|
[*Session storage*](../actix_web/middleware/struct.SessionStorage.html) middleare can be
|
||||||
|
use with different backend types to store session data in different backends.
|
||||||
|
By default only cookie session backend is implemented. Other backend implementations
|
||||||
|
could be added later.
|
||||||
|
|
||||||
|
[*Cookie session backend*](../actix_web/middleware/struct.CookieSessionBackend.html)
|
||||||
|
uses signed cookies as session storage. *Cookie session backend* creates sessions which
|
||||||
|
are limited to storing fewer than 4000 bytes of data (as the payload must fit into a
|
||||||
|
single cookie). Internal server error get generated if session contains more than 4000 bytes.
|
||||||
|
|
||||||
|
You need to pass a random value to the constructor of *CookieSessionBackend*.
|
||||||
|
This is private key for cookie session. When this value is changed, all session data is lost.
|
||||||
|
Note that whatever you write into your session is visible by the user (but not modifiable).
|
||||||
|
|
||||||
|
In general case, you cretate
|
||||||
|
[*Session storage*](../actix_web/middleware/struct.SessionStorage.html) middleware
|
||||||
|
and initializes it with specific backend implementation, like *CookieSessionBackend*.
|
||||||
|
To access session data
|
||||||
|
[*HttpRequest::session()*](../actix_web/middleware/trait.RequestSession.html#tymethod.session)
|
||||||
|
method has to be used. This method returns
|
||||||
|
[*Session*](../actix_web/middleware/struct.Session.html) object, which allows to get or set
|
||||||
|
session data.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix;
|
||||||
|
# extern crate actix_web;
|
||||||
|
use actix_web::*;
|
||||||
|
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");
|
||||||
|
HttpServer::new(
|
||||||
|
|| Application::new()
|
||||||
|
.middleware(SessionStorage::new( // <- create session middleware
|
||||||
|
CookieSessionBackend::build(&[0; 32]) // <- create cookie session backend
|
||||||
|
.secure(false)
|
||||||
|
.finish()
|
||||||
|
)))
|
||||||
|
.bind("127.0.0.1:59880").unwrap()
|
||||||
|
.start();
|
||||||
|
# actix::Arbiter::system().send(actix::msgs::SystemExit(0));
|
||||||
|
# let _ = sys.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
# Logging
|
|
||||||
|
|
||||||
Logging is implemented as middleware. Middlewares get executed in same order as registraton order.
|
|
||||||
It is common to register logging middleware as first middleware for application.
|
|
||||||
Logging middleware has to be registered for each application.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Create `Logger` middlewares with the specified `format`.
|
|
||||||
Default `Logger` could be created with `default` method, it uses the default format:
|
|
||||||
|
|
||||||
```ignore
|
|
||||||
%a %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i" %T
|
|
||||||
```
|
|
||||||
```rust
|
|
||||||
extern crate actix_web;
|
|
||||||
use actix_web::Application;
|
|
||||||
use actix_web::middlewares::Logger;
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
Application::default("/")
|
|
||||||
.middleware(Logger::default())
|
|
||||||
.middleware(Logger::new("%a %{User-Agent}i"))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Here is example of default logging format:
|
|
||||||
|
|
||||||
```
|
|
||||||
INFO:actix_web::middlewares::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::middlewares::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']
|
|
@ -1 +1,44 @@
|
|||||||
# Static file handling
|
# Static file handling
|
||||||
|
|
||||||
|
## Individual file
|
||||||
|
|
||||||
|
It is possible to serve static files with custom path pattern and `NamedFile`. To
|
||||||
|
match path tail we can use `[.*]` regex.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
|
use actix_web::*;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> Result<fs::NamedFile> {
|
||||||
|
let path: PathBuf = req.match_info().query("tail")?;
|
||||||
|
Ok(fs::NamedFile::open(path)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
Application::new()
|
||||||
|
.resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Directory
|
||||||
|
|
||||||
|
To serve files from specific directory and sub-directories `StaticFiles` could be used.
|
||||||
|
`StaticFiles` must be registered with `Application::handler()` method otherwise
|
||||||
|
it won't be able to server sub-paths.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
|
use actix_web::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
Application::new()
|
||||||
|
.handler("/static", fs::StaticFiles::new(".", true))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
First parameter is a base directory. Second parameter is *show_index*, if it is set to *true*
|
||||||
|
directory listing would be returned for directories, if it is set to *false*
|
||||||
|
then *404 Not Found* would be returned instead of directory listing.
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
# HTTP/2
|
# HTTP/2.0
|
||||||
|
|
||||||
Actix web automatically upgrades connection to `http/2` if possible.
|
Actix web automatically upgrades connection to *HTTP/2.0* if possible.
|
||||||
|
|
||||||
## Negotiation
|
## Negotiation
|
||||||
|
|
||||||
`HTTP/2` protocol over tls without prior knowlage requires
|
*HTTP/2.0* protocol over tls without prior knowlage requires
|
||||||
[tls alpn](https://tools.ietf.org/html/rfc7301). At the moment only
|
[tls alpn](https://tools.ietf.org/html/rfc7301). At the moment only
|
||||||
`rust-openssl` has support. Turn on `alpn` feature to enable `alpn` negotiation.
|
`rust-openssl` has support. Turn on `alpn` feature to enable `alpn` negotiation.
|
||||||
With enable `alpn` feature `HttpServer` provides
|
With enable `alpn` feature `HttpServer` provides
|
||||||
@ -26,15 +26,16 @@ fn main() {
|
|||||||
let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap().parse("12345").unwrap();
|
let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap().parse("12345").unwrap();
|
||||||
|
|
||||||
HttpServer::new(
|
HttpServer::new(
|
||||||
Application::default("/")
|
|| Application::new()
|
||||||
.handler("/index.html", index)
|
.resource("/index.html", |r| r.f(index)))
|
||||||
.serve_tls::<_, ()>("127.0.0.1:8080", pkcs12).unwrap();
|
.bind("127.0.0.1:8080").unwrap();
|
||||||
|
.serve_ssl(pkcs12).unwrap();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Upgrade to `http/2` schema described in
|
Upgrade to *HTTP/2.0* schema described in
|
||||||
[rfc section 3.2](https://http2.github.io/http2-spec/#rfc.section.3.2) is not supported.
|
[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
|
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)
|
and tls connection. [rfc section 3.4](https://http2.github.io/http2-spec/#rfc.section.3.4)
|
||||||
|
|
||||||
Please check [example](https://github.com/actix/actix-web/tree/master/examples/tls)
|
Please check [example](https://github.com/actix/actix-web/tree/master/examples/tls)
|
||||||
|
124
guide/src/qs_14.md
Normal file
124
guide/src/qs_14.md
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
# Database integration
|
||||||
|
|
||||||
|
## Diesel
|
||||||
|
|
||||||
|
At the moment of 1.0 release Diesel does not support asynchronous operations.
|
||||||
|
But it possible to use `actix` synchronous actor system as a db interface api.
|
||||||
|
Technically sync actors are worker style actors, multiple of them
|
||||||
|
can be run in parallel and process messages from same queue (sync actors work in mpmc mode).
|
||||||
|
|
||||||
|
Let's create simple db api that can insert new user row into sqlite table.
|
||||||
|
We have to define sync actor and connection that this actor will use. Same approach
|
||||||
|
could used for other databases.
|
||||||
|
|
||||||
|
```rust,ignore
|
||||||
|
use actix::prelude::*;*
|
||||||
|
|
||||||
|
struct DbExecutor(SqliteConnection);
|
||||||
|
|
||||||
|
impl Actor for DbExecutor {
|
||||||
|
type Context = SyncContext<Self>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This is definition of our actor. Now we need to define *create user* message and response.
|
||||||
|
|
||||||
|
```rust,ignore
|
||||||
|
#[derive(Message)]
|
||||||
|
#[rtype(User, Error)]
|
||||||
|
struct CreateUser {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We can send `CreateUser` message to `DbExecutor` actor, and as result we get
|
||||||
|
`User` model. Now we need to define actual handler implementation for this message.
|
||||||
|
|
||||||
|
```rust,ignore
|
||||||
|
impl Handler<CreateUser> for DbExecutor {
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: CreateUser, _: &mut Self::Context) -> Response<Self, CreateUser>
|
||||||
|
{
|
||||||
|
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 diesl 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");
|
||||||
|
|
||||||
|
Self::reply(items.pop().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
That is it. Now we can use *DbExecutor* actor from any http handler or middleware.
|
||||||
|
All we need is to start *DbExecutor* actors and store address in a state where http handler
|
||||||
|
can access it.
|
||||||
|
|
||||||
|
```rust,ignore
|
||||||
|
/// This is state where we will store *DbExecutor* address.
|
||||||
|
struct State {
|
||||||
|
db: SyncAddress<DbExecutor>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let sys = actix::System::new("diesel-example");
|
||||||
|
|
||||||
|
// Start 3 parallele db executors
|
||||||
|
let addr = SyncArbiter::start(3, || {
|
||||||
|
DbExecutor(SqliteConnection::establish("test.db").unwrap())
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start http server
|
||||||
|
HttpServer::new(move || {
|
||||||
|
Application::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();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And finally we can use address in a requst handler. We get message response
|
||||||
|
asynchronously, so handler needs to return future object, also `Route::a()` needs to 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.call_fut(CreateUser{name: name.to_owned()})
|
||||||
|
.from_err()
|
||||||
|
.and_then(|res| {
|
||||||
|
match res {
|
||||||
|
Ok(user) => Ok(httpcodes::HTTPOk.build().json(user)?),
|
||||||
|
Err(_) => Ok(httpcodes::HTTPInternalServerError.into())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Full example is available in
|
||||||
|
[examples directory](https://github.com/actix/actix-web/tree/master/examples/diesel/).
|
||||||
|
|
||||||
|
More information on sync actors could be found in
|
||||||
|
[actix documentation](https://docs.rs/actix/0.3.3/actix/sync/index.html).
|
@ -29,29 +29,39 @@ In order to implement a web server, first we need to create a request handler.
|
|||||||
A request handler is a function that accepts a `HttpRequest` instance as its only parameter
|
A request handler is a function that accepts a `HttpRequest` instance as its only parameter
|
||||||
and returns a type that can be converted into `HttpResponse`:
|
and returns a type that can be converted into `HttpResponse`:
|
||||||
|
|
||||||
```rust,ignore
|
```rust
|
||||||
extern crate actix_web;
|
# extern crate actix_web;
|
||||||
use actix_web::*;
|
# use actix_web::*;
|
||||||
|
fn index(req: HttpRequest) -> &'static str {
|
||||||
fn index(req: HttpRequest) -> &'static str {
|
"Hello world!"
|
||||||
"Hello world!"
|
}
|
||||||
}
|
# fn main() {}
|
||||||
```
|
```
|
||||||
|
|
||||||
Next, create an `Application` instance and register the
|
Next, create an `Application` instance and register the
|
||||||
request handler with the application's `resource` on a particular *HTTP method* and *path*::
|
request handler with the application's `resource` on a particular *HTTP method* and *path*::
|
||||||
|
|
||||||
```rust,ignore
|
```rust
|
||||||
let app = Application::default("/")
|
# extern crate actix_web;
|
||||||
.resource("/", |r| r.get(index))
|
# use actix_web::*;
|
||||||
.finish()
|
# fn index(req: HttpRequest) -> &'static str {
|
||||||
|
# "Hello world!"
|
||||||
|
# }
|
||||||
|
# fn main() {
|
||||||
|
Application::new()
|
||||||
|
.resource("/", |r| r.f(index));
|
||||||
|
# }
|
||||||
```
|
```
|
||||||
|
|
||||||
After that, application instance can be used with `HttpServer` to listen for incoming
|
After that, application instance can be used with `HttpServer` to listen for incoming
|
||||||
connections:
|
connections. Server accepts function that should return `HttpHandler` instance:
|
||||||
|
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
HttpServer::new(app).serve::<_, ()>("127.0.0.1:8088");
|
HttpServer::new(
|
||||||
|
|| Application::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.
|
That's it. Now, compile and run the program with cargo run.
|
||||||
@ -59,9 +69,8 @@ Head over to ``http://localhost:8088/`` to see the results.
|
|||||||
|
|
||||||
Here is full source of main.rs file:
|
Here is full source of main.rs file:
|
||||||
|
|
||||||
```rust
|
```rust,ignore
|
||||||
extern crate actix;
|
# extern crate actix_web;
|
||||||
extern crate actix_web;
|
|
||||||
use actix_web::*;
|
use actix_web::*;
|
||||||
|
|
||||||
fn index(req: HttpRequest) -> &'static str {
|
fn index(req: HttpRequest) -> &'static str {
|
||||||
@ -69,22 +78,15 @@ fn index(req: HttpRequest) -> &'static str {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let sys = actix::System::new("example");
|
|
||||||
|
|
||||||
HttpServer::new(
|
HttpServer::new(
|
||||||
Application::default("/")
|
|| Application::new()
|
||||||
.resource("/", |r| r.get(index)))
|
.resource("/", |r| r.f(index)))
|
||||||
.serve::<_, ()>("127.0.0.1:8088").unwrap();
|
.bind("127.0.0.1:8088").expect("Can not bind to 127.0.0.1:8088")
|
||||||
|
.run();
|
||||||
println!("Started http server: 127.0.0.1:8088");
|
|
||||||
// do not copy this line
|
|
||||||
actix::Arbiter::system().send(actix::msgs::SystemExit(0));
|
|
||||||
|
|
||||||
let _ = sys.run();
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note on `actix` crate. Actix web framework is built on top of actix actor library.
|
Note on `actix` crate. Actix web framework is built on top of actix actor library.
|
||||||
`actix::System` initializes actor system, `HttpServer` is an actor and must run within
|
`actix::System` initializes actor system, `HttpServer` is an actor and must run within
|
||||||
proper configured actix system. For more information please check
|
properly configured actix system. For more information please check
|
||||||
[actix documentation](https://actix.github.io/actix/actix/)
|
[actix documentation](https://actix.github.io/actix/actix/)
|
||||||
|
@ -5,44 +5,105 @@ It provides routing, middlewares, pre-processing of requests, and post-processin
|
|||||||
websocket protcol handling, multipart streams, etc.
|
websocket protcol handling, multipart streams, etc.
|
||||||
|
|
||||||
All actix web server is built around `Application` instance.
|
All actix web server is built around `Application` instance.
|
||||||
It is used for registering handlers for routes and resources, middlewares.
|
It is used for registering routes for resources, middlewares.
|
||||||
Also it stores applicationspecific state that is shared accross all handlers
|
Also it stores application specific state that is shared across all handlers
|
||||||
within same application.
|
within same application.
|
||||||
|
|
||||||
Application acts as namespace for all routes, i.e all routes for specific application
|
Application acts as namespace for all routes, i.e all routes for specific application
|
||||||
has same url path prefix:
|
has same url path prefix. Application prefix always contains laading "/" slash.
|
||||||
|
If supplied prefix does not contain leading slash, it get inserted.
|
||||||
|
Prefix should consists of valud path segments. i.e for application with prefix `/app`
|
||||||
|
any request with following paths `/app`, `/app/` or `/app/test` would match,
|
||||||
|
but path `/application` would not match.
|
||||||
|
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
let app = Application::default("/prefix")
|
# extern crate actix_web;
|
||||||
.resource("/index.html", |r| r.handler(Method::GET, index)
|
# extern crate tokio_core;
|
||||||
|
# use actix_web::*;
|
||||||
|
# fn index(req: HttpRequest) -> &'static str {
|
||||||
|
# "Hello world!"
|
||||||
|
# }
|
||||||
|
# fn main() {
|
||||||
|
let app = Application::new()
|
||||||
|
.prefix("/app")
|
||||||
|
.resource("/index.html", |r| r.method(Method::GET).f(index))
|
||||||
.finish()
|
.finish()
|
||||||
|
# }
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example application with `/prefix` prefix and `index.html` resource
|
In this example application with `/app` prefix and `index.html` resource
|
||||||
get created. This resource is available as on `/prefix/index.html` url.
|
get created. This resource is available as on `/app/index.html` url.
|
||||||
|
For more information check
|
||||||
|
[*URL Matching*](./qs_5.html#using-a-application-prefix-to-compose-applications) section.
|
||||||
|
|
||||||
Multiple applications could be served with one server:
|
Multiple applications could be served with one server:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
extern crate actix_web;
|
# extern crate actix_web;
|
||||||
extern crate tokio_core;
|
# extern crate tokio_core;
|
||||||
use std::net::SocketAddr;
|
# use tokio_core::net::TcpStream;
|
||||||
|
# use std::net::SocketAddr;
|
||||||
use actix_web::*;
|
use actix_web::*;
|
||||||
use tokio_core::net::TcpStream;
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
HttpServer::<TcpStream, SocketAddr, _>::new(vec![
|
HttpServer::<TcpStream, SocketAddr, _, _>::new(|| vec![
|
||||||
Application::default("/app1")
|
Application::new()
|
||||||
.resource("/", |r| r.get(|r| httpcodes::HTTPOk))
|
.prefix("/app1")
|
||||||
.finish(),
|
.resource("/", |r| r.f(|r| httpcodes::HTTPOk)),
|
||||||
Application::default("/app2")
|
Application::new()
|
||||||
.resource("/", |r| r.get(|r| httpcodes::HTTPOk))
|
.prefix("/app2")
|
||||||
.finish(),
|
.resource("/", |r| r.f(|r| httpcodes::HTTPOk)),
|
||||||
Application::default("/")
|
Application::new()
|
||||||
.resource("/", |r| r.get(|r| httpcodes::HTTPOk))
|
.resource("/", |r| r.f(|r| httpcodes::HTTPOk)),
|
||||||
.finish(),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
All `/app1` requests route to first application, `/app2` to second and then all other to third.
|
All `/app1` requests route to first application, `/app2` to second and then all other to third.
|
||||||
|
Applications get matched based on registration order, if application with more general
|
||||||
|
prefix is registered before less generic, that would effectively block less generic
|
||||||
|
application to get matched. For example if *application* with prefix "/" get registered
|
||||||
|
as first application, it would match all incoming requests.
|
||||||
|
|
||||||
|
## State
|
||||||
|
|
||||||
|
Application state is shared with all routes and resources within same application.
|
||||||
|
State could be accessed with `HttpRequest::state()` method as a read-only item
|
||||||
|
but interior mutability pattern with `RefCell` could be used to archive state mutability.
|
||||||
|
State could be accessed with `HttpContext::state()` in case of http actor.
|
||||||
|
State also available to route matching predicates and middlewares.
|
||||||
|
|
||||||
|
Let's write simple application that uses shared state. We are going to store requests count
|
||||||
|
in the state:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix;
|
||||||
|
# extern crate actix_web;
|
||||||
|
#
|
||||||
|
use actix_web::*;
|
||||||
|
use std::cell::Cell;
|
||||||
|
|
||||||
|
// 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() {
|
||||||
|
Application::with_state(AppState{counter: Cell::new(0)})
|
||||||
|
.resource("/", |r| r.method(Method::GET).f(index))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note on application state, http server accepts application factory rather than application
|
||||||
|
instance. Http server construct application instance for each thread, so application state
|
||||||
|
must be constructed multiple times. If you want to share state between different thread
|
||||||
|
shared object should be used, like `Arc`. Application state does not need to be `Send` and `Sync`
|
||||||
|
but application factory must be `Send` + `Sync`.
|
||||||
|
197
guide/src/qs_3_5.md
Normal file
197
guide/src/qs_3_5.md
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
# Server
|
||||||
|
|
||||||
|
[*HttpServer*](../actix_web/struct.HttpServer.html) type is responsible for
|
||||||
|
serving http requests. *HttpServer* accept application factory as a parameter,
|
||||||
|
Application factory must have `Send` + `Sync` bounderies. More about that in
|
||||||
|
*multi-threading* section. To bind to specific socket address `bind()` must be used.
|
||||||
|
This method could be called multiple times. To start http server one of the *start*
|
||||||
|
methods could be used. `start()` method start simple server, `start_tls()` or `start_ssl()`
|
||||||
|
starts ssl server. *HttpServer* is an actix actor, it has to be initialized
|
||||||
|
within properly configured actix system:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix;
|
||||||
|
# extern crate actix_web;
|
||||||
|
use actix::*;
|
||||||
|
use actix_web::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let sys = actix::System::new("guide");
|
||||||
|
|
||||||
|
HttpServer::new(
|
||||||
|
|| Application::new()
|
||||||
|
.resource("/", |r| r.h(httpcodes::HTTPOk)))
|
||||||
|
.bind("127.0.0.1:59080").unwrap()
|
||||||
|
.start();
|
||||||
|
|
||||||
|
# actix::Arbiter::system().send(actix::msgs::SystemExit(0));
|
||||||
|
let _ = sys.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It is possible to start server in separate thread with *spawn()* method. In that
|
||||||
|
case server spawns new thread and create new actix system in it. To stop
|
||||||
|
this server send `StopServer` message.
|
||||||
|
|
||||||
|
Http server is implemented as an actix actor. It is possible to communicate with server
|
||||||
|
via messaging system. All start methods like `start()`, `start_ssl()`, etc returns
|
||||||
|
address of the started http server. Actix http server accept 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 actix_web::*;
|
||||||
|
use std::thread;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
let sys = actix::System::new("http-server");
|
||||||
|
let addr = HttpServer::new(
|
||||||
|
|| Application::new()
|
||||||
|
.resource("/", |r| r.h(httpcodes::HTTPOk)))
|
||||||
|
.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.call_fut(
|
||||||
|
dev::StopServer{graceful:true}).wait(); // <- Send `StopServer` message to server.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Multi-threading
|
||||||
|
|
||||||
|
Http server automatically starts number of http workers, by default
|
||||||
|
this number is equal to number of logical cpu in the system. This number
|
||||||
|
could be overridden with `HttpServer::threads()` method.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
|
# extern crate tokio_core;
|
||||||
|
# use tokio_core::net::TcpStream;
|
||||||
|
# use std::net::SocketAddr;
|
||||||
|
use actix_web::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
HttpServer::<TcpStream, SocketAddr, _, _>::new(
|
||||||
|
|| Application::new()
|
||||||
|
.resource("/", |r| r.h(httpcodes::HTTPOk)))
|
||||||
|
.threads(4); // <- Start 4 workers
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Server create 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 application factory must be `Send` + `Sync`.
|
||||||
|
|
||||||
|
## SSL
|
||||||
|
|
||||||
|
There are two `tls` and `alpn` features for ssl server. `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() {
|
||||||
|
let mut file = File::open("identity.pfx").unwrap();
|
||||||
|
let mut pkcs12 = vec![];
|
||||||
|
file.read_to_end(&mut pkcs12).unwrap();
|
||||||
|
let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap().parse("12345").unwrap();
|
||||||
|
|
||||||
|
HttpServer::new(
|
||||||
|
|| Application::new()
|
||||||
|
.resource("/index.html", |r| r.f(index)))
|
||||||
|
.bind("127.0.0.1:8080").unwrap()
|
||||||
|
.serve_ssl(pkcs12).unwrap();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Note on *HTTP/2.0* protocol over tls without prior knowledge, it requires
|
||||||
|
[tls alpn](https://tools.ietf.org/html/rfc7301). At the moment only
|
||||||
|
`openssl` has `alpn ` support.
|
||||||
|
|
||||||
|
Please check [example](https://github.com/actix/actix-web/tree/master/examples/tls)
|
||||||
|
for full example.
|
||||||
|
|
||||||
|
## Keep-Alive
|
||||||
|
|
||||||
|
Actix can wait for requests on a keep-alive connection. *Keep alive*
|
||||||
|
connection behavior is defined by server settings.
|
||||||
|
|
||||||
|
* `Some(75)` - enable 75 sec *keep alive* timer according request and response settings.
|
||||||
|
* `Some(0)` - disable *keep alive*.
|
||||||
|
* `None` - Use `SO_KEEPALIVE` socket option.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
|
# extern crate tokio_core;
|
||||||
|
# use tokio_core::net::TcpStream;
|
||||||
|
# use std::net::SocketAddr;
|
||||||
|
use actix_web::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
HttpServer::<TcpStream, SocketAddr, _, _>::new(||
|
||||||
|
Application::new()
|
||||||
|
.resource("/", |r| r.h(httpcodes::HTTPOk)))
|
||||||
|
.keep_alive(None); // <- Use `SO_KEEPALIVE` socket option.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If first option is selected then *keep alive* state
|
||||||
|
calculated based on response's *connection-type*. By default
|
||||||
|
`HttpResponse::connection_type` is not defined in that case *keep alive*
|
||||||
|
defined by 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* could be change with `HttpResponseBuilder::connection_type()` method.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
|
# use actix_web::httpcodes::*;
|
||||||
|
use actix_web::*;
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> HttpResponse {
|
||||||
|
HTTPOk.build()
|
||||||
|
.connection_type(headers::ConnectionType::Close) // <- Close connection
|
||||||
|
.force_close() // <- Alternative method
|
||||||
|
.finish().unwrap()
|
||||||
|
}
|
||||||
|
# fn main() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Graceful shutdown
|
||||||
|
|
||||||
|
Actix http server support graceful shutdown. After receiving a stop signal, workers
|
||||||
|
have specific amount of time to finish serving requests. Workers still alive after the
|
||||||
|
timeout are force dropped. By default shutdown timeout sets to 30 seconds.
|
||||||
|
You can change this parameter with `HttpServer::shutdown_timeout()` method.
|
||||||
|
|
||||||
|
You can send stop message to server with server address and specify if you what
|
||||||
|
graceful shutdown or not. `start()` methods return address of the server.
|
||||||
|
|
||||||
|
Http server 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 signals handling with `HttpServer::disable_signals()` method.
|
@ -1,21 +1,19 @@
|
|||||||
# Handler
|
# Handler
|
||||||
|
|
||||||
A request handler can by any object that implements
|
A request handler can by any object that implements
|
||||||
[`Handler` trait](../actix_web/struct.HttpResponse.html#implementations).
|
[*Handler trait*](../actix_web/dev/trait.Handler.html).
|
||||||
|
Request handling happen in two stages. First handler object get called.
|
||||||
|
Handle can return any object that implements
|
||||||
|
[*Responder trait*](../actix_web/trait.Responder.html#foreign-impls).
|
||||||
|
Then `respond_to()` get called on returned object. And finally
|
||||||
|
result of the `respond_to()` call get converted to `Reply` object.
|
||||||
|
|
||||||
By default actix provdes several `Handler` implementations:
|
By default actix provides `Responder` implementations for some standard types,
|
||||||
|
|
||||||
* Simple function that accepts `HttpRequest` and returns any object that
|
|
||||||
can be converted to `HttpResponse`
|
|
||||||
* Function that accepts `HttpRequest` and returns `Result<Reply, Into<Error>>` object.
|
|
||||||
* Function that accepts `HttpRequest` and return actor that has `HttpContext<A>`as a context.
|
|
||||||
|
|
||||||
Actix provides response conversion into `HttpResponse` for some standard types,
|
|
||||||
like `&'static str`, `String`, etc.
|
like `&'static str`, `String`, etc.
|
||||||
For complete list of implementations check
|
For complete list of implementations check
|
||||||
[HttpResponse documentation](../actix_web/struct.HttpResponse.html#implementations).
|
[*Responder documentation*](../actix_web/trait.Responder.html#foreign-impls).
|
||||||
|
|
||||||
Examples:
|
Examples of valid handlers:
|
||||||
|
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
fn index(req: HttpRequest) -> &'static str {
|
fn index(req: HttpRequest) -> &'static str {
|
||||||
@ -41,13 +39,95 @@ fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Custom conversion
|
Some notes on shared application state and handler state. If you noticed
|
||||||
|
*Handler* trait is generic over *S*, which defines application state type. So
|
||||||
|
application state is accessible from handler with `HttpRequest::state()` method.
|
||||||
|
But state is accessible as a read-only reference, if you need mutable access to state
|
||||||
|
you have to implement it yourself. On other hand handler can mutable access it's own state
|
||||||
|
as `handle` method takes mutable reference to *self*. Beware, actix creates multiple copies
|
||||||
|
of application state and handlers, unique for each thread, so if you run your
|
||||||
|
application in several threads actix will create same amount as number of threads
|
||||||
|
of application state objects and handler objects.
|
||||||
|
|
||||||
|
Here is example of handler that stores number of processed requests:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix;
|
||||||
|
# extern crate actix_web;
|
||||||
|
use actix_web::*;
|
||||||
|
use actix_web::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;
|
||||||
|
httpcodes::HTTPOk.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# fn main() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
This handler will work, but `self.0` value will be different depends on number of threads and
|
||||||
|
number of requests processed per thread. Proper implementation would use `Arc` and `AtomicUsize`
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix;
|
||||||
|
# extern crate actix_web;
|
||||||
|
use actix_web::*;
|
||||||
|
use actix_web::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 {
|
||||||
|
let num = self.0.load(Ordering::Relaxed) + 1;
|
||||||
|
self.0.store(num, Ordering::Relaxed);
|
||||||
|
httpcodes::HTTPOk.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let sys = actix::System::new("example");
|
||||||
|
|
||||||
|
let inc = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
|
HttpServer::new(
|
||||||
|
move || {
|
||||||
|
let cloned = inc.clone();
|
||||||
|
Application::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().send(actix::msgs::SystemExit(0));
|
||||||
|
let _ = sys.run();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Be careful with synchronization primitives like *Mutex* or *RwLock*. Actix web framework
|
||||||
|
handles request 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 [actix](https://actix.github.io/actix/actix/) actor system.
|
||||||
|
|
||||||
|
## Response with custom type
|
||||||
|
|
||||||
|
To return custom type directly from handler function, type needs to implement `Responder` trait.
|
||||||
Let's create response for custom type that serializes to `application/json` response:
|
Let's create response for custom type that serializes to `application/json` response:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
extern crate actix;
|
# extern crate actix;
|
||||||
extern crate actix_web;
|
# extern crate actix_web;
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
#[macro_use] extern crate serde_derive;
|
#[macro_use] extern crate serde_derive;
|
||||||
@ -55,87 +135,101 @@ use actix_web::*;
|
|||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct MyObj {
|
struct MyObj {
|
||||||
name: String,
|
name: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// we have to convert Error into HttpResponse as well, but with
|
/// Responder
|
||||||
/// specialization this could be handled genericly.
|
impl Responder for MyObj {
|
||||||
impl Into<HttpResponse> for MyObj {
|
type Item = HttpResponse;
|
||||||
fn into(self) -> HttpResponse {
|
type Error = Error;
|
||||||
let body = match serde_json::to_string(&self) {
|
|
||||||
Err(err) => return Error::from(err).into(),
|
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse> {
|
||||||
Ok(body) => body,
|
let body = serde_json::to_string(&self)?;
|
||||||
};
|
|
||||||
|
|
||||||
// Create response and set content type
|
// Create response and set content type
|
||||||
HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
.content_type("application/json")
|
.content_type("application/json")
|
||||||
.body(body).unwrap()
|
.body(body)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Because `MyObj` implements `Responder`, it is possible to return it directly
|
||||||
|
fn index(req: HttpRequest) -> MyObj {
|
||||||
|
MyObj{name: "user"}
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let sys = actix::System::new("example");
|
let sys = actix::System::new("example");
|
||||||
|
|
||||||
HttpServer::new(
|
HttpServer::new(
|
||||||
Application::default("/")
|
|| Application::new()
|
||||||
.resource("/", |r| r.handler(
|
.resource("/", |r| r.method(Method::GET).f(index)))
|
||||||
Method::GET, |req| {MyObj{name: "user".to_owned()}})))
|
.bind("127.0.0.1:8088").unwrap()
|
||||||
.serve::<_, ()>("127.0.0.1:8088").unwrap();
|
.start();
|
||||||
|
|
||||||
println!("Started http server: 127.0.0.1:8088");
|
println!("Started http server: 127.0.0.1:8088");
|
||||||
actix::Arbiter::system().send(actix::msgs::SystemExit(0)); // <- remove this line, this code stops system during testing
|
# actix::Arbiter::system().send(actix::msgs::SystemExit(0));
|
||||||
|
|
||||||
let _ = sys.run();
|
let _ = sys.run();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
If `specialization` is enabled, conversion could be simplier:
|
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
impl Into<Result<HttpResponse>> for MyObj {
|
|
||||||
fn into(self) -> Result<HttpResponse> {
|
|
||||||
let body = serde_json::to_string(&self)?;
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
|
||||||
.content_type("application/json")
|
|
||||||
.body(body)?)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Async handlers
|
## Async handlers
|
||||||
|
|
||||||
There are two different types of async handlers.
|
There are two different types of async handlers.
|
||||||
|
|
||||||
Response object could be generated asynchronously. In this case handle must
|
Response object could be generated asynchronously or more precisely, any type
|
||||||
return `Future` object that resolves to `HttpResponse`, i.e:
|
that implements [*Responder*](../actix_web/trait.Responder.html) trait. In this case handle must
|
||||||
|
return `Future` object that resolves to *Responder* type, i.e:
|
||||||
|
|
||||||
```rust,ignore
|
```rust
|
||||||
fn index(req: HttpRequest) -> Box<Future<HttpResponse, Error>> {
|
# extern crate actix_web;
|
||||||
...
|
# extern crate futures;
|
||||||
|
# extern crate bytes;
|
||||||
|
# use actix_web::*;
|
||||||
|
# use bytes::Bytes;
|
||||||
|
# use futures::stream::once;
|
||||||
|
# use futures::future::{FutureResult, result};
|
||||||
|
fn index(req: HttpRequest) -> FutureResult<HttpResponse, Error> {
|
||||||
|
|
||||||
|
result(HttpResponse::Ok()
|
||||||
|
.content_type("text/html")
|
||||||
|
.body(format!("Hello!"))
|
||||||
|
.map_err(|e| e.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index2(req: HttpRequest) -> FutureResult<&'static str, Error> {
|
||||||
|
result(Ok("Welcome!"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
Application::new()
|
||||||
|
.resource("/async", |r| r.route().a(index))
|
||||||
|
.resource("/", |r| r.route().a(index2))
|
||||||
|
.finish();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This handler can be registered with `ApplicationBuilder::async()` and
|
|
||||||
`Resource::async()` methods.
|
|
||||||
|
|
||||||
Or response body can be generated asynchronously. In this case body
|
Or response body can be generated asynchronously. In this case body
|
||||||
must implement stream trait `Stream<Item=Bytes, Error=Error>`, i.e:
|
must implement stream trait `Stream<Item=Bytes, Error=Error>`, i.e:
|
||||||
|
|
||||||
|
```rust
|
||||||
```rust,ignore
|
# 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 {
|
fn index(req: HttpRequest) -> HttpResponse {
|
||||||
let body: Box<Stream<Item=Bytes, Error=Error>> = Box::new(SomeStream::new());
|
let body = once(Ok(Bytes::from_static(b"test")));
|
||||||
|
|
||||||
HttpResponse::Ok().
|
HttpResponse::Ok()
|
||||||
.content_type("application/json")
|
.content_type("application/json")
|
||||||
.body(Body::Streaming(body)).unwrap()
|
.body(Body::Streaming(Box::new(body))).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
Application::default("/")
|
Application::new()
|
||||||
.async("/async", index)
|
.resource("/async", |r| r.f(index))
|
||||||
.finish();
|
.finish();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
136
guide/src/qs_4_5.md
Normal file
136
guide/src/qs_4_5.md
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
# Errors
|
||||||
|
|
||||||
|
Actix uses [`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 `ResponseError` trait can be returned as error value.
|
||||||
|
*Handler* can return *Result* object, actix by default provides
|
||||||
|
`Responder` implemenation for compatible result object. Here is implementation
|
||||||
|
definition:
|
||||||
|
|
||||||
|
```rust,ignore
|
||||||
|
impl<T: Responder, E: Into<Error>> Responder for Result<T, E>
|
||||||
|
```
|
||||||
|
|
||||||
|
And any error that implements `ResponseError` can be converted into `Error` object.
|
||||||
|
For example if *handler* function returns `io::Error`, it would be converted
|
||||||
|
into `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() {
|
||||||
|
# Application::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 just implement `ResponseError` trait
|
||||||
|
for custom error. `ResponseError` trait has default implementation
|
||||||
|
for `error_response()` method, it generates *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() {
|
||||||
|
# Application::new()
|
||||||
|
# .resource(r"/a/index.html", |r| r.f(index))
|
||||||
|
# .finish();
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example *index* handler will always return *500* response. But it is easy
|
||||||
|
to return different responses for different type of errors.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
|
#[macro_use] extern crate failure;
|
||||||
|
use actix_web::*;
|
||||||
|
|
||||||
|
#[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(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR, Body::Empty),
|
||||||
|
MyError::BadClientData => HttpResponse::new(
|
||||||
|
StatusCode::BAD_REQUEST, Body::Empty),
|
||||||
|
MyError::Timeout => HttpResponse::new(
|
||||||
|
StatusCode::GATEWAY_TIMEOUT, Body::Empty),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> Result<&'static str, MyError> {
|
||||||
|
Err(MyError::BadClientData)
|
||||||
|
}
|
||||||
|
#
|
||||||
|
# fn main() {
|
||||||
|
# Application::new()
|
||||||
|
# .resource(r"/a/index.html", |r| r.f(index))
|
||||||
|
# .finish();
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error helpers
|
||||||
|
|
||||||
|
Actix provides set of error helper types. It is possible to use them to generate
|
||||||
|
specific error response. We can use helper types for first example with 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(error::ErrorBadRequest)?)
|
||||||
|
}
|
||||||
|
# fn main() {
|
||||||
|
# Application::new()
|
||||||
|
# .resource(r"/a/index.html", |r| r.f(index))
|
||||||
|
# .finish();
|
||||||
|
# }
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example *BAD REQUEST* response get generated for `MyError` error.
|
@ -1,107 +1,578 @@
|
|||||||
# Resources and Routes
|
# URL Dispatch
|
||||||
|
|
||||||
|
URL dispatch provides a simple way to map URLs to `Handler` code using a simple pattern matching
|
||||||
|
language. *Regex* crate and it's
|
||||||
|
[*RegexSet*](https://doc.rust-lang.org/regex/regex/struct.RegexSet.html) is beeing used for
|
||||||
|
pattern matching. If one of the patterns matches the path information associated with a request,
|
||||||
|
a particular handler object is invoked. A handler is a specific object that implements
|
||||||
|
`Handler` trait, defined in your application, that receives the request and returns
|
||||||
|
a response object. More informatin is available in [handler section](../qs_4.html).
|
||||||
|
|
||||||
|
## Resource configuration
|
||||||
|
|
||||||
|
Resource configuraiton is the act of adding a new resource to an application.
|
||||||
|
A resource has a name, which acts as an identifier to be used for URL generation.
|
||||||
|
The name also allows developers to add routes to existing resources.
|
||||||
|
A resource also has a pattern, meant to match against the *PATH* portion of a *URL*,
|
||||||
|
it does not match against *QUERY* portion (the portion following the scheme and
|
||||||
|
port, e.g., */foo/bar* in the *URL* *http://localhost:8080/foo/bar?q=value*).
|
||||||
|
|
||||||
|
The [Application::resource](../actix_web/struct.Application.html#method.resource) methods
|
||||||
|
add a single resource to application routing table. This method accepts *path pattern*
|
||||||
|
and resource configuration funnction.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
|
# use actix_web::*;
|
||||||
|
# use actix_web::httpcodes::*;
|
||||||
|
#
|
||||||
|
# fn index(req: HttpRequest) -> HttpResponse {
|
||||||
|
# unimplemented!()
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
fn main() {
|
||||||
|
Application::new()
|
||||||
|
.resource("/prefix", |r| r.f(index))
|
||||||
|
.resource("/user/{name}",
|
||||||
|
|r| r.method(Method::GET).f(|req| HTTPOk))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
*Configuraiton function* has following type:
|
||||||
|
|
||||||
|
```rust,ignore
|
||||||
|
FnOnce(&mut Resource<_>) -> ()
|
||||||
|
```
|
||||||
|
|
||||||
|
*Configration function* can set name and register specific routes.
|
||||||
|
If resource does not contain any route or does not have any matching routes it
|
||||||
|
returns *NOT FOUND* http resources.
|
||||||
|
|
||||||
|
## Configuring a Route
|
||||||
|
|
||||||
|
Resource contains set of routes. Each route in turn has set of predicates and handler.
|
||||||
|
New route could be crearted with `Resource::route()` method which returns reference
|
||||||
|
to new *Route* instance. By default *route* does not contain any predicates, so matches
|
||||||
|
all requests and default handler is `HTTPNotFound`.
|
||||||
|
|
||||||
All resources and routes register for specific application.
|
|
||||||
Application routes incoming requests based on route criteria which is defined during
|
Application routes incoming requests based on route criteria which is defined during
|
||||||
resource registration or path prefix for simple handlers.
|
resource registration and route registration. Resource matches all routes it contains in
|
||||||
Internally *router* is a list of *resources*. Resource is an entry in *route table*
|
the order that the routes were registered via `Resource::route()`. *Route* can contain
|
||||||
which corresponds to requested URL.
|
any number of *predicates* but only one handler.
|
||||||
|
|
||||||
Prefix handler:
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
```rust,ignore
|
# use actix_web::*;
|
||||||
fn index(req: Httprequest) -> HttpResponse {
|
# use actix_web::httpcodes::*;
|
||||||
...
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
Application::default("/")
|
Application::new()
|
||||||
.handler("/prefix", |req| index)
|
.resource("/path", |resource|
|
||||||
|
resource.route()
|
||||||
|
.p(pred::Get())
|
||||||
|
.p(pred::Header("content-type", "text/plain"))
|
||||||
|
.f(|req| HTTPOk)
|
||||||
|
)
|
||||||
.finish();
|
.finish();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example `index` get called for any url which starts with `/prefix`.
|
In this example `index` get called for *GET* request,
|
||||||
|
if request contains `Content-Type` header and value of this header is *text/plain*
|
||||||
|
and path equals to `/test`. Resource calls handle of the first matches route.
|
||||||
|
If resource can not match any route "NOT FOUND" response get returned.
|
||||||
|
|
||||||
Application prefix combines with handler prefix i.e
|
[*Resource::route()*](../actix_web/struct.Resource.html#method.route) method returns
|
||||||
|
[*Route*](../actix_web/struct.Route.html) object. Route can be configured with
|
||||||
|
builder-like pattern. Following configuration methods are available:
|
||||||
|
|
||||||
|
* [*Route::p()*](../actix_web/struct.Route.html#method.p) method registers new predicate,
|
||||||
|
any number of predicates could be registered for each route.
|
||||||
|
|
||||||
|
* [*Route::f()*](../actix_web/struct.Route.html#method.f) method registers handler function
|
||||||
|
for this route. Only one handler could be registered. Usually handler registeration
|
||||||
|
is the last config operation. Handler fanction could be function or closure and has type
|
||||||
|
`Fn(HttpRequest<S>) -> R + 'static`
|
||||||
|
|
||||||
|
* [*Route::h()*](../actix_web/struct.Route.html#method.h) method registers handler object
|
||||||
|
that implements `Handler` trait. This is similar to `f()` method, only one handler could
|
||||||
|
be registered. Handler registeration is the last config operation.
|
||||||
|
|
||||||
|
* [*Route::a()*](../actix_web/struct.Route.html#method.a) method registers asynchandler
|
||||||
|
function for this route. Only one handler could be registered. Handler registeration
|
||||||
|
is the last config operation. Handler fanction could be function or closure and has 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 registration present in the system, actix checks
|
||||||
|
the request's path against the pattern declared. *Regex* crate and it's
|
||||||
|
[*RegexSet*](https://doc.rust-lang.org/regex/regex/struct.RegexSet.html) is beeing used for
|
||||||
|
pattern matching. If resource could not be found, *default resource* get used as matched
|
||||||
|
resource.
|
||||||
|
|
||||||
|
When a route configuration is declared, it may contain route predicate arguments. All route
|
||||||
|
predicates associated with a route declaration must be `true` for the route configuration to
|
||||||
|
be used for a given request during a check. If any predicate in the set of route predicate
|
||||||
|
arguments provided to a route configuration returns `false` during a check, that route is
|
||||||
|
skipped and route matching continues through the ordered set of routes.
|
||||||
|
|
||||||
|
If any route matches, the route matching process stops and the handler associated with
|
||||||
|
route get invoked.
|
||||||
|
|
||||||
|
If no route matches after all route patterns are exhausted, *NOT FOUND* response get returned.
|
||||||
|
|
||||||
|
## Resource pattern syntax
|
||||||
|
|
||||||
|
The syntax of the pattern matching language used by the actix in the pattern
|
||||||
|
argument is straightforward.
|
||||||
|
|
||||||
|
The pattern used in route configuration may start with a slash character. If the pattern
|
||||||
|
does not start with a slash character, an implicit slash will be prepended
|
||||||
|
to it at matching time. For example, the following patterns are equivalent:
|
||||||
|
|
||||||
|
```
|
||||||
|
{foo}/bar/baz
|
||||||
|
```
|
||||||
|
|
||||||
|
and:
|
||||||
|
|
||||||
|
```
|
||||||
|
/{foo}/bar/baz
|
||||||
|
```
|
||||||
|
|
||||||
|
A *variable part* (replacement marker) is specified in the form *{identifier}*,
|
||||||
|
where this means "accept any characters up to the next slash character and use this
|
||||||
|
as the name in the `HttpRequest.match_info()` object".
|
||||||
|
|
||||||
|
A replacement marker in a pattern matches the regular expression `[^{}/]+`.
|
||||||
|
|
||||||
|
A match_info is the `Params` object representing the dynamic parts extracted from a
|
||||||
|
*URL* based on the routing pattern. It is available as *request.match_info*. For example, the
|
||||||
|
following pattern defines one literal segment (foo) and two replacement markers (baz, and bar):
|
||||||
|
|
||||||
|
```
|
||||||
|
foo/{baz}/{bar}
|
||||||
|
```
|
||||||
|
|
||||||
|
The above pattern will match these URLs, generating the following match information:
|
||||||
|
|
||||||
|
```
|
||||||
|
foo/1/2 -> Params {'baz':'1', 'bar':'2'}
|
||||||
|
foo/abc/def -> Params {'baz':'abc', 'bar':'def'}
|
||||||
|
```
|
||||||
|
|
||||||
|
It will not match the following patterns however:
|
||||||
|
|
||||||
|
```
|
||||||
|
foo/1/2/ -> No match (trailing slash)
|
||||||
|
bar/abc/def -> First segment literal mismatch
|
||||||
|
```
|
||||||
|
|
||||||
|
The match for a segment replacement marker in a segment will be done only up to
|
||||||
|
the first non-alphanumeric character in the segment in the pattern. So, for instance,
|
||||||
|
if this route pattern was used:
|
||||||
|
|
||||||
|
```
|
||||||
|
foo/{name}.html
|
||||||
|
```
|
||||||
|
|
||||||
|
The literal path */foo/biz.html* will match the above route pattern, and the match result
|
||||||
|
will be `Params{'name': 'biz'}`. However, the literal path */foo/biz* will not match,
|
||||||
|
because it does not contain a literal *.html* at the end of the segment represented
|
||||||
|
by *{name}.html* (it only contains biz, not biz.html).
|
||||||
|
|
||||||
|
To capture both segments, two replacement markers can be used:
|
||||||
|
|
||||||
|
```
|
||||||
|
foo/{name}.{ext}
|
||||||
|
```
|
||||||
|
|
||||||
|
The literal path */foo/biz.html* will match the above route pattern, and the match
|
||||||
|
result will be *Params{'name': 'biz', 'ext': 'html'}*. This occurs because there is a
|
||||||
|
literal part of *.* (period) between the two replacement markers *{name}* and *{ext}*.
|
||||||
|
|
||||||
|
Replacement markers can optionally specify a regular expression which will be used to decide
|
||||||
|
whether a path segment should match the marker. To specify that a replacement marker should
|
||||||
|
match only a specific set of characters as defined by a regular expression, you must use a
|
||||||
|
slightly extended form of replacement marker syntax. Within braces, the replacement marker
|
||||||
|
name must be followed by a colon, then directly thereafter, the regular expression. The default
|
||||||
|
regular expression associated with a replacement marker *[^/]+* matches one or more characters
|
||||||
|
which are not a slash. For example, under the hood, the replacement marker *{foo}* can more
|
||||||
|
verbosely be spelled as *{foo:[^/]+}*. You can change this to be an arbitrary regular expression
|
||||||
|
to match an arbitrary sequence of characters, such as *{foo:\d+}* to match only digits.
|
||||||
|
|
||||||
|
Segments must contain at least one character in order to match a segment replacement marker.
|
||||||
|
For example, for the URL */abc/*:
|
||||||
|
|
||||||
|
* */abc/{foo}* will not match.
|
||||||
|
* */{foo}/* will match.
|
||||||
|
|
||||||
|
Note that path will be URL-unquoted and decoded into valid unicode string before
|
||||||
|
matching pattern and values representing matched path segments will be URL-unquoted too.
|
||||||
|
So for instance, the following pattern:
|
||||||
|
|
||||||
|
```
|
||||||
|
foo/{bar}
|
||||||
|
```
|
||||||
|
|
||||||
|
When matching the following URL:
|
||||||
|
|
||||||
|
```
|
||||||
|
http://example.com/foo/La%20Pe%C3%B1a
|
||||||
|
```
|
||||||
|
|
||||||
|
The matchdict will look like so (the value is URL-decoded):
|
||||||
|
|
||||||
|
```
|
||||||
|
Params{'bar': 'La Pe\xf1a'}
|
||||||
|
```
|
||||||
|
|
||||||
|
Literal strings in the path segment should represent the decoded value of the
|
||||||
|
path provided to actix. You don't want to use a URL-encoded value in the pattern.
|
||||||
|
For example, rather than this:
|
||||||
|
|
||||||
|
```
|
||||||
|
/Foo%20Bar/{baz}
|
||||||
|
```
|
||||||
|
|
||||||
|
You'll want to use something like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
/Foo Bar/{baz}
|
||||||
|
```
|
||||||
|
|
||||||
|
It is possible to get "tail match". For this purpose custom regex has to be used.
|
||||||
|
|
||||||
|
```
|
||||||
|
foo/{bar}/{tail:.*}
|
||||||
|
```
|
||||||
|
|
||||||
|
The above pattern will match these URLs, generating the following match information:
|
||||||
|
|
||||||
|
```
|
||||||
|
foo/1/2/ -> Params{'bar':'1', 'tail': '2/'}
|
||||||
|
foo/abc/def/a/b/c -> Params{'bar':u'abc', 'tail': 'def/a/b/c'}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Match information
|
||||||
|
|
||||||
|
All values representing matched path segments are available in
|
||||||
|
[`HttpRequest::match_info`](../actix_web/struct.HttpRequest.html#method.match_info).
|
||||||
|
Specific value can be received with
|
||||||
|
[`Params::get()`](../actix_web/dev/struct.Params.html#method.get) method.
|
||||||
|
|
||||||
|
Any matched parameter can be deserialized into specific type if this type
|
||||||
|
implements `FromParam` trait. For example most of standard integer types
|
||||||
|
implements `FromParam` trait. i.e.:
|
||||||
|
|
||||||
|
```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))
|
||||||
|
}
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
fn main() {
|
fn main() {
|
||||||
Application::default("/app")
|
Application::new()
|
||||||
.handler("/prefix", |req| index)
|
.resource(r"/a/{v1}/{v2}/", |r| r.f(index))
|
||||||
.finish();
|
.finish();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example `index` get called for any url which starts with`/app/prefix`.
|
For this example for path '/a/1/2/', values v1 and v2 will resolve to "1" and "2".
|
||||||
|
|
||||||
Resource contains set of route for same endpoint. Route corresponds to handling
|
It is possible to create a `PathBuf` from a tail path parameter. The returned `PathBuf` is
|
||||||
*HTTP method* by calling *web handler*. Resource select route based on *http method*,
|
percent-decoded. If a segment is equal to "..", the previous segment (if
|
||||||
if no route could be matched default response `HTTPMethodNotAllowed` get resturned.
|
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 actix_web::*;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> Result<String> {
|
||||||
|
let path: PathBuf = req.match_info().query("tail")?;
|
||||||
|
Ok(format!("Path {:?}", path))
|
||||||
|
}
|
||||||
|
|
||||||
```rust,ignore
|
|
||||||
fn main() {
|
fn main() {
|
||||||
Application::default("/")
|
Application::new()
|
||||||
.resource("/prefix", |r| {
|
.resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index))
|
||||||
r.get(HTTPOk)
|
.finish();
|
||||||
r.post(HTTPForbidden)
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
List of `FromParam` implementation could be found in
|
||||||
|
[api docs](../actix_web/dev/trait.FromParam.html#foreign-impls)
|
||||||
|
|
||||||
|
## Generating resource URLs
|
||||||
|
|
||||||
|
Use the [HttpRequest.url_for()](../actix_web/struct.HttpRequest.html#method.url_for)
|
||||||
|
method to generate URLs based on resource patterns. For example, if you've configured a
|
||||||
|
resource with the name "foo" and the pattern "{a}/{b}/{c}", you might do this.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
|
# use actix_web::*;
|
||||||
|
# use actix_web::httpcodes::*;
|
||||||
|
#
|
||||||
|
fn index(req: HttpRequest) -> HttpResponse {
|
||||||
|
let url = req.url_for("foo", &["1", "2", "3"]); // <- generate url for "foo" resource
|
||||||
|
HTTPOk.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let app = Application::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(|_| httpcodes::HTTPOk);
|
||||||
})
|
})
|
||||||
.finish();
|
.finish();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
[`ApplicationBuilder::resource()` method](../actix_web/dev/struct.ApplicationBuilder.html#method.resource)
|
This would return something like the string *http://example.com/test/1/2/3* (at least if
|
||||||
accepts configuration function, resource could be configured at once.
|
the current protocol and hostname implied http://example.com).
|
||||||
Check [`Resource`](../actix-web/target/doc/actix_web/struct.Resource.html) documentation
|
`url_for()` method return [*Url object*](https://docs.rs/url/1.6.0/url/struct.Url.html) so you
|
||||||
for more information.
|
can modify this url (add query parameters, anchor, etc).
|
||||||
|
`url_for()` could be called only for *named* resources otherwise error get returned.
|
||||||
|
|
||||||
## Variable resources
|
## External resources
|
||||||
|
|
||||||
Resource may have *variable path*also. For instance, a resource with the
|
|
||||||
path '/a/{name}/c' would match all incoming requests with paths such
|
|
||||||
as '/a/b/c', '/a/1/c', and '/a/etc/c'.
|
|
||||||
|
|
||||||
A *variable part*is specified in the form {identifier}, where the identifier can be
|
|
||||||
used later in a request handler to access the matched value for that part. This is
|
|
||||||
done by looking up the identifier in the `HttpRequest.match_info` object:
|
|
||||||
|
|
||||||
|
Resources that are valid URLs, could be registered as external resources. They are useful
|
||||||
|
for URL generation purposes only and are never considered for matching at request time.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
extern crate actix;
|
# extern crate actix_web;
|
||||||
use actix_web::*;
|
use actix_web::*;
|
||||||
|
|
||||||
fn index(req: Httprequest) -> String {
|
fn index(mut req: HttpRequest) -> Result<HttpResponse> {
|
||||||
format!("Hello, {}", req.match_info.get('name').unwrap())
|
let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
|
||||||
|
assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
|
||||||
|
Ok(httpcodes::HTTPOk.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
Application::default("/")
|
let app = Application::new()
|
||||||
.resource("/{name}", |r| r.get(index))
|
.resource("/index.html", |r| r.f(index))
|
||||||
|
.external_resource("youtube", "https://youtube.com/watch/{video_id}")
|
||||||
.finish();
|
.finish();
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, each part matches the regular expression `[^{}/]+`.
|
## Path normalization and redirecting to slash-appended routes
|
||||||
|
|
||||||
You can also specify a custom regex in the form `{identifier:regex}`:
|
By normalizing it means:
|
||||||
|
|
||||||
|
- Add a trailing slash to the path.
|
||||||
|
- Double slashes are replaced by one.
|
||||||
|
|
||||||
|
The handler returns as soon as it finds a path that resolves
|
||||||
|
correctly. The order if all enable is 1) merge, 3) both merge and append
|
||||||
|
and 3) append. If the path resolves with
|
||||||
|
at least one of those conditions, it will redirect to the new path.
|
||||||
|
|
||||||
|
If *append* is *true* append slash when needed. If a resource is
|
||||||
|
defined with trailing slash and the request comes without it, it will
|
||||||
|
append it automatically.
|
||||||
|
|
||||||
|
If *merge* is *true*, merge multiple consecutive slashes in the path into one.
|
||||||
|
|
||||||
|
This handler designed to be use as a handler for application's *default resource*.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
|
# #[macro_use] extern crate serde_derive;
|
||||||
|
# use actix_web::*;
|
||||||
|
#
|
||||||
|
# fn index(req: HttpRequest) -> httpcodes::StaticResponse {
|
||||||
|
# httpcodes::HTTPOk
|
||||||
|
# }
|
||||||
|
fn main() {
|
||||||
|
let app = Application::new()
|
||||||
|
.resource("/resource/", |r| r.f(index))
|
||||||
|
.default_resource(|r| r.h(NormalizePath::default()))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example `/resource`, `//resource///` will be redirected to `/resource/` url.
|
||||||
|
|
||||||
|
In this example path normalization handler get registered for all method,
|
||||||
|
but you should not rely on this mechanism to redirect *POST* requests. The redirect of the
|
||||||
|
slash-appending *Not Found* will turn a *POST* request into a GET, losing any
|
||||||
|
*POST* data in the original request.
|
||||||
|
|
||||||
|
It is possible to register path normalization only for *GET* requests only
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
|
# #[macro_use] extern crate serde_derive;
|
||||||
|
# use actix_web::*;
|
||||||
|
#
|
||||||
|
# fn index(req: HttpRequest) -> httpcodes::StaticResponse {
|
||||||
|
# httpcodes::HTTPOk
|
||||||
|
# }
|
||||||
|
fn main() {
|
||||||
|
let app = Application::new()
|
||||||
|
.resource("/resource/", |r| r.f(index))
|
||||||
|
.default_resource(|r| r.method(Method::GET).h(NormalizePath::default()))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using a Application Prefix to Compose Applications
|
||||||
|
|
||||||
|
The `Application::prefix()`" method allows to set 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() {
|
||||||
|
Application::new()
|
||||||
|
.prefix("/users")
|
||||||
|
.resource("/show", |r| r.f(show_users))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the above example, the *show_users* route will have an effective route pattern of
|
||||||
|
*/users/show* instead of */show* because the application's prefix argument will be prepended
|
||||||
|
to the pattern. The route will then only match if the URL path is */users/show*,
|
||||||
|
and when the `HttpRequest.url_for()` function is called with the route name show_users,
|
||||||
|
it will generate a URL with that same path.
|
||||||
|
|
||||||
|
## Custom route predicates
|
||||||
|
|
||||||
|
You can think of predicate as simple function that accept *request* object reference
|
||||||
|
and returns *true* or *false*. Formally predicate is any object that implements
|
||||||
|
[`Predicate`](../actix_web/pred/trait.Predicate.html) trait. Actix provides
|
||||||
|
several predicates, you can check [functions section](../actix_web/pred/index.html#functions)
|
||||||
|
of api docs.
|
||||||
|
|
||||||
|
Here is simple predicates that check that request contains specific *header*:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
|
# extern crate http;
|
||||||
|
# use actix_web::*;
|
||||||
|
# use actix_web::httpcodes::*;
|
||||||
|
use http::header::CONTENT_TYPE;
|
||||||
|
use actix_web::pred::Predicate;
|
||||||
|
|
||||||
|
struct ContentTypeHeader;
|
||||||
|
|
||||||
|
impl<S: 'static> Predicate<S> for ContentTypeHeader {
|
||||||
|
|
||||||
|
fn check(&self, req: &mut HttpRequest<S>) -> bool {
|
||||||
|
req.headers().contains_key(CONTENT_TYPE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
Application::new()
|
||||||
|
.resource("/index.html", |r|
|
||||||
|
r.route()
|
||||||
|
.p(ContentTypeHeader)
|
||||||
|
.h(HTTPOk));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example *index* handler will be called only if request contains *CONTENT-TYPE* header.
|
||||||
|
|
||||||
|
Predicates can have access to application's state via `HttpRequest::state()` method.
|
||||||
|
Also predicates can store extra information in
|
||||||
|
[requests`s extensions](../actix_web/struct.HttpRequest.html#method.extensions).
|
||||||
|
|
||||||
|
### Modifing predicate values
|
||||||
|
|
||||||
|
You can invert the meaning of any predicate value by wrapping it in a `Not` predicate.
|
||||||
|
For example if you want to return "METHOD NOT ALLOWED" response for all methods
|
||||||
|
except "GET":
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
|
# extern crate http;
|
||||||
|
# use actix_web::*;
|
||||||
|
# use actix_web::httpcodes::*;
|
||||||
|
use actix_web::pred;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
Application::new()
|
||||||
|
.resource("/index.html", |r|
|
||||||
|
r.route()
|
||||||
|
.p(pred::Not(pred::Get()))
|
||||||
|
.f(|req| HTTPMethodNotAllowed))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`Any` predicate accept list of predicates and matches if any of the supplied
|
||||||
|
predicates match. i.e:
|
||||||
|
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
fn main() {
|
pred::Any(pred::Get()).or(pred::Post())
|
||||||
Application::default("/")
|
|
||||||
.resource(r"{name:\d+}", |r| r.get(index))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
To match path tail, `{tail:*}` pattern could be used. Tail pattern has to be last
|
`All` predicate accept list of predicates and matches if all of the supplied
|
||||||
segment in path otherwise it panics.
|
predicates match. i.e:
|
||||||
|
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
fn main() {
|
pred::All(pred::Get()).and(pred::Header("content-type", "plain/text"))
|
||||||
Application::default("/")
|
|
||||||
.resource(r"/test/{tail:*}", |r| r.get(index))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Above example would match all incoming requests with path such as
|
## Changing the default Not Found response
|
||||||
'/test/b/c', '/test/index.html', and '/test/etc/test'.
|
|
||||||
|
If path pattern can not be found in routing table or resource can not find matching
|
||||||
|
route, default resource is used. Default response is *NOT FOUND* response.
|
||||||
|
It is possible to override *NOT FOUND* response with `Application::default_resource()` method.
|
||||||
|
This method accepts *configuration function* same as normal resource configuration
|
||||||
|
with `Application::resource()` method.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
|
# extern crate http;
|
||||||
|
use actix_web::*;
|
||||||
|
use actix_web::httpcodes::*;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
Application::new()
|
||||||
|
.default_resource(|r| {
|
||||||
|
r.method(Method::GET).f(|req| HTTPNotFound);
|
||||||
|
r.route().p(pred::Not(pred::Get())).f(|req| HTTPMethodNotAllowed);
|
||||||
|
})
|
||||||
|
# .finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
# Application state
|
|
||||||
|
|
||||||
Application state is shared with all routes within same application.
|
|
||||||
State could be accessed with `HttpRequest::state()` method. It is read-only
|
|
||||||
but interior mutability pattern with `RefCell` could be used to archive state mutability.
|
|
||||||
State could be accessed with `HttpRequest::state()` method or
|
|
||||||
`HttpContext::state()` in case of http actor.
|
|
||||||
|
|
||||||
Let's write simple application that uses shared state. We are going to store requests count
|
|
||||||
in the state:
|
|
||||||
|
|
||||||
```rust
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
|
|
||||||
use std::cell::Cell;
|
|
||||||
use actix_web::*;
|
|
||||||
|
|
||||||
// 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() {
|
|
||||||
Application::build("/", AppState{counter: Cell::new(0)})
|
|
||||||
.resource("/", |r| r.handler(Method::GET, index))
|
|
||||||
.finish();
|
|
||||||
}
|
|
||||||
```
|
|
@ -1 +1,297 @@
|
|||||||
# Request
|
# Request & Response
|
||||||
|
|
||||||
|
## Response
|
||||||
|
|
||||||
|
Builder-like patter is used to construct an instance of `HttpResponse`.
|
||||||
|
`HttpResponse` provides several method that returns `HttpResponseBuilder` instance,
|
||||||
|
which is implements various convinience methods that helps build response.
|
||||||
|
Check [documentation](../actix_web/dev/struct.HttpResponseBuilder.html)
|
||||||
|
for type description. Methods `.body`, `.finish`, `.json` finalizes response creation and
|
||||||
|
returns constructed *HttpResponse* instance. if this methods get called for the same
|
||||||
|
builder instance multiple times, builder will panic.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
|
use actix_web::*;
|
||||||
|
use actix_web::headers::ContentEncoding;
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> HttpResponse {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.content_encoding(ContentEncoding::Br)
|
||||||
|
.content_type("plain/text")
|
||||||
|
.header("X-Hdr", "sample")
|
||||||
|
.body("data").unwrap()
|
||||||
|
}
|
||||||
|
# fn main() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Content encoding
|
||||||
|
|
||||||
|
Actix automatically *compress*/*decompress* payload. Following codecs are supported:
|
||||||
|
|
||||||
|
* Brotli
|
||||||
|
* Gzip
|
||||||
|
* Deflate
|
||||||
|
* Identity
|
||||||
|
|
||||||
|
If request headers contains `Content-Encoding` header, request payload get decompressed
|
||||||
|
according to header value. Multiple codecs are not supported, i.e: `Content-Encoding: br, gzip`.
|
||||||
|
|
||||||
|
Response payload get compressed based on *content_encoding* parameter.
|
||||||
|
By default `ContentEncoding::Auto` is used. If `ContentEncoding::Auto` is selected
|
||||||
|
then compression depends on request's `Accept-Encoding` header.
|
||||||
|
`ContentEncoding::Identity` could be used to disable compression.
|
||||||
|
If other content encoding is selected the compression is enforced for this codec. For example,
|
||||||
|
to enable `brotli` response's body compression use `ContentEncoding::Br`:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
|
use actix_web::*;
|
||||||
|
use actix_web::headers::ContentEncoding;
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> HttpResponse {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.content_encoding(ContentEncoding::Br)
|
||||||
|
.body("data").unwrap()
|
||||||
|
}
|
||||||
|
# fn main() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## JSON Request
|
||||||
|
|
||||||
|
There are two options of json body deserialization.
|
||||||
|
|
||||||
|
First option is to use *HttpResponse::json()* method. This method returns
|
||||||
|
[*JsonBody*](../actix_web/dev/struct.JsonBody.html) object which resolves into
|
||||||
|
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(httpcodes::HTTPOk.build().json(val)?) // <- send response
|
||||||
|
})
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
# fn main() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
Or you can manually load payload into memory and ther deserialize it.
|
||||||
|
Here is simple example. We will deserialize *MyObj* struct. We need to load request
|
||||||
|
body first and then deserialize json into 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(mut 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.payload_mut().readany().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(httpcodes::HTTPOk.build().json(obj)?) // <- send response
|
||||||
|
})
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
# fn main() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
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 you 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::*;
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct MyObj {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> Result<Json<MyObj>> {
|
||||||
|
Ok(Json(MyObj{name: req.match_info().query("name")?}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
Application::new()
|
||||||
|
.resource(r"/a/{name}", |r| r.method(Method::GET).f(index))
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Chunked transfer encoding
|
||||||
|
|
||||||
|
Actix automatically decode *chunked* encoding. `HttpRequest::payload()` already contains
|
||||||
|
decoded bytes stream. If request payload compressed with one of supported
|
||||||
|
compression codecs (br, gzip, deflate) bytes stream get decompressed.
|
||||||
|
|
||||||
|
Chunked encoding on response could be enabled with `HttpResponseBuilder::chunked()` method.
|
||||||
|
But this takes effect only for `Body::Streaming(BodyStream)` or `Body::StreamingContext` bodies.
|
||||||
|
Also if response payload compression is enabled and streaming body is used, chunked encoding
|
||||||
|
get enabled automatically.
|
||||||
|
|
||||||
|
Enabling chunked encoding for *HTTP/2.0* responses is forbidden.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
|
use actix_web::*;
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> HttpResponse {
|
||||||
|
HttpResponse::Ok()
|
||||||
|
.chunked()
|
||||||
|
.body(Body::Streaming(payload::Payload::empty().stream())).unwrap()
|
||||||
|
}
|
||||||
|
# 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 could be
|
||||||
|
[*Field*](../actix_web/multipart/struct.Field.html) or nested *Multipart* stream.
|
||||||
|
`HttpResponse::multipart()` method returns *Multipart* stream for current request.
|
||||||
|
|
||||||
|
In simple form multipart stream handling could be implemented similar to this example
|
||||||
|
|
||||||
|
```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.heders(), 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(())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Full example is available in
|
||||||
|
[examples directory](https://github.com/actix/actix-web/tree/master/examples/multipart/).
|
||||||
|
|
||||||
|
## Urlencoded body
|
||||||
|
|
||||||
|
Actix provides support for *application/x-www-form-urlencoded* encoded body.
|
||||||
|
`HttpResponse::urlencoded()` method returns
|
||||||
|
[*UrlEncoded*](../actix_web/dev/struct.UrlEncoded.html) future, it resolves
|
||||||
|
into `HashMap<String, String>` which contains decoded parameters.
|
||||||
|
*UrlEncoded* future can resolve into a 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;
|
||||||
|
use actix_web::*;
|
||||||
|
use futures::future::{Future, ok};
|
||||||
|
|
||||||
|
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||||
|
req.urlencoded() // <- get UrlEncoded future
|
||||||
|
.from_err()
|
||||||
|
.and_then(|params| { // <- url encoded parameters
|
||||||
|
println!("==== BODY ==== {:?}", params);
|
||||||
|
ok(httpcodes::HTTPOk.into())
|
||||||
|
})
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
# fn main() {}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Streaming request
|
||||||
|
|
||||||
|
Actix uses [*Payload*](../actix_web/payload/struct.Payload.html) object as request payload stream.
|
||||||
|
*HttpRequest* provides several methods, which can be used for payload access.
|
||||||
|
At the same time *Payload* implements *Stream* trait, so it could be used with various
|
||||||
|
stream combinators. Also *Payload* provides serveral convinience methods that return
|
||||||
|
future object that resolve to Bytes object.
|
||||||
|
|
||||||
|
* *readany()* method returns *Stream* of *Bytes* objects.
|
||||||
|
|
||||||
|
* *readexactly()* method returns *Future* that resolves when specified number of bytes
|
||||||
|
get received.
|
||||||
|
|
||||||
|
* *readline()* method returns *Future* that resolves when `\n` get received.
|
||||||
|
|
||||||
|
* *readuntil()* method returns *Future* that resolves when specified bytes string
|
||||||
|
matches in input bytes stream
|
||||||
|
|
||||||
|
In this example handle reads request payload chunk by chunk and prints every 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.payload_mut()
|
||||||
|
.readany()
|
||||||
|
.from_err()
|
||||||
|
.fold((), |_, chunk| {
|
||||||
|
println!("Chunk: {:?}", chunk);
|
||||||
|
result::<_, error::PayloadError>(Ok(()))
|
||||||
|
})
|
||||||
|
.map(|_| HttpResponse::Ok().finish().unwrap())
|
||||||
|
.responder()
|
||||||
|
}
|
||||||
|
# fn main() {}
|
||||||
|
```
|
||||||
|
@ -1 +1,97 @@
|
|||||||
# Response
|
# Testing
|
||||||
|
|
||||||
|
Every application should be well tested and. Actix provides the tools to perform unit and
|
||||||
|
integration tests.
|
||||||
|
|
||||||
|
## Unit tests
|
||||||
|
|
||||||
|
For unit testing actix provides request builder type and simple handler runner.
|
||||||
|
[*TestRequest*](../actix_web/test/struct.TestRequest.html) implements builder-like pattern.
|
||||||
|
You can generate `HttpRequest` instance with `finish()` method or you can
|
||||||
|
run your handler with `run()` or `run_async()` methods.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate http;
|
||||||
|
# extern crate actix_web;
|
||||||
|
use http::{header, StatusCode};
|
||||||
|
use actix_web::*;
|
||||||
|
use actix_web::test::TestRequest;
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> HttpResponse {
|
||||||
|
if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) {
|
||||||
|
if let Ok(s) = hdr.to_str() {
|
||||||
|
return httpcodes::HTTPOk.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
httpcodes::HTTPBadRequest.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let resp = TestRequest::with_header("content-type", "text/plain")
|
||||||
|
.run(index)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let resp = TestRequest::default()
|
||||||
|
.run(index)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Integration tests
|
||||||
|
|
||||||
|
There are several methods how you can test your application. Actix provides
|
||||||
|
[*TestServer*](../actix_web/test/struct.TestServer.html)
|
||||||
|
server that could be used to run whole application of just specific handlers
|
||||||
|
in real http server. At the moment it is required to use third-party libraries
|
||||||
|
to make actual requests, libraries like [reqwest](https://crates.io/crates/reqwest).
|
||||||
|
|
||||||
|
In simple form *TestServer* could be configured to use handler. *TestServer::new* method
|
||||||
|
accepts configuration function, only argument for this function is *test application*
|
||||||
|
instance. You can check [api documentation](../actix_web/test/struct.TestApp.html)
|
||||||
|
for more information.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
|
extern crate reqwest;
|
||||||
|
use actix_web::*;
|
||||||
|
use actix_web::test::TestServer;
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> HttpResponse {
|
||||||
|
httpcodes::HTTPOk.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let srv = TestServer::new(|app| app.handler(index)); // <- Start new test server
|
||||||
|
let url = srv.url("/"); // <- get handler url
|
||||||
|
assert!(reqwest::get(&url).unwrap().status().is_success()); // <- make request
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Other option is to use application factory. In this case you need to pass factory function
|
||||||
|
same as you use for real http server configuration.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
# extern crate actix_web;
|
||||||
|
extern crate reqwest;
|
||||||
|
use actix_web::*;
|
||||||
|
use actix_web::test::TestServer;
|
||||||
|
|
||||||
|
fn index(req: HttpRequest) -> HttpResponse {
|
||||||
|
httpcodes::HTTPOk.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function get called by http server.
|
||||||
|
fn create_app() -> Application {
|
||||||
|
Application::new()
|
||||||
|
.resource("/test", |r| r.h(index))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let srv = TestServer::with_factory(create_app); // <- Start new test server
|
||||||
|
let url = srv.url("/test"); // <- get handler url
|
||||||
|
assert!(reqwest::get(&url).unwrap().status().is_success()); // <- make request
|
||||||
|
}
|
||||||
|
```
|
||||||
|
@ -1 +1,48 @@
|
|||||||
# WebSockets
|
# WebSockets
|
||||||
|
|
||||||
|
Actix supports WebSockets out-of-the-box. It is possible to convert 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 simplier to handle websocket communications
|
||||||
|
with http actor.
|
||||||
|
|
||||||
|
```rust
|
||||||
|
extern crate actix;
|
||||||
|
extern crate actix_web;
|
||||||
|
|
||||||
|
use actix::*;
|
||||||
|
use actix_web::*;
|
||||||
|
|
||||||
|
/// Define http actor
|
||||||
|
struct Ws;
|
||||||
|
|
||||||
|
impl Actor for Ws {
|
||||||
|
type Context = HttpContext<Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Define Handler for ws::Message message
|
||||||
|
impl Handler<ws::Message> for Ws {
|
||||||
|
type Result=();
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext<Self>) {
|
||||||
|
match msg {
|
||||||
|
ws::Message::Ping(msg) => ws::WsWriter::pong(ctx, &msg),
|
||||||
|
ws::Message::Text(text) => ws::WsWriter::text(ctx, &text),
|
||||||
|
ws::Message::Binary(bin) => ws::WsWriter::binary(ctx, bin),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
Application::new()
|
||||||
|
.resource("/ws/", |r| r.f(|req| ws::start(req, Ws))) // <- register websocket route
|
||||||
|
.finish();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Simple websocket echo server example is available in
|
||||||
|
[examples directory](https://github.com/actix/actix-web/blob/master/examples/websocket.rs).
|
||||||
|
|
||||||
|
Example chat server with ability to chat over websocket connection or tcp connection
|
||||||
|
is available in [websocket-chat directory](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/)
|
||||||
|
@ -1,113 +1,195 @@
|
|||||||
|
use std::mem;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use futures::Future;
|
|
||||||
|
|
||||||
use error::Error;
|
use handler::Reply;
|
||||||
use route::{RouteHandler, Reply, Handler, WrapHandler, AsyncHandler};
|
use router::{Router, Pattern};
|
||||||
use resource::Resource;
|
use resource::Resource;
|
||||||
use recognizer::{RouteRecognizer, check_pattern};
|
use handler::{Handler, RouteHandler, WrapHandler};
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use channel::{HttpHandler, IntoHttpHandler, HttpHandlerTask};
|
||||||
use channel::HttpHandler;
|
use pipeline::{Pipeline, PipelineHandler};
|
||||||
use pipeline::Pipeline;
|
use middleware::Middleware;
|
||||||
use middlewares::Middleware;
|
use server::ServerSettings;
|
||||||
|
|
||||||
|
|
||||||
/// Application
|
/// Application
|
||||||
pub struct Application<S> {
|
pub struct HttpApplication<S=()> {
|
||||||
state: Rc<S>,
|
state: Rc<S>,
|
||||||
prefix: String,
|
prefix: String,
|
||||||
default: Resource<S>,
|
router: Router,
|
||||||
handlers: HashMap<String, Box<RouteHandler<S>>>,
|
inner: Rc<RefCell<Inner<S>>>,
|
||||||
router: RouteRecognizer<Resource<S>>,
|
middlewares: Rc<Vec<Box<Middleware<S>>>>,
|
||||||
middlewares: Rc<Vec<Box<Middleware>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: 'static> Application<S> {
|
pub(crate) struct Inner<S> {
|
||||||
|
prefix: usize,
|
||||||
|
default: Resource<S>,
|
||||||
|
router: Router,
|
||||||
|
resources: Vec<Resource<S>>,
|
||||||
|
handlers: Vec<(String, Box<RouteHandler<S>>)>,
|
||||||
|
}
|
||||||
|
|
||||||
fn run(&self, req: HttpRequest) -> Reply {
|
impl<S: 'static> PipelineHandler<S> for Inner<S> {
|
||||||
let mut req = req.with_state(Rc::clone(&self.state));
|
|
||||||
|
|
||||||
if let Some((params, h)) = self.router.recognize(req.path()) {
|
fn handle(&mut self, mut req: HttpRequest<S>) -> Reply {
|
||||||
if let Some(params) = params {
|
if let Some(idx) = self.router.recognize(&mut req) {
|
||||||
req.set_match_info(params);
|
self.resources[idx].handle(req.clone(), Some(&mut self.default))
|
||||||
}
|
|
||||||
h.handle(req)
|
|
||||||
} else {
|
} else {
|
||||||
for (prefix, handler) in &self.handlers {
|
for &mut (ref prefix, ref mut handler) in &mut self.handlers {
|
||||||
if req.path().starts_with(prefix) {
|
let m = {
|
||||||
req.set_prefix(prefix.len());
|
let path = &req.path()[self.prefix..];
|
||||||
|
path.starts_with(prefix) && (path.len() == prefix.len() ||
|
||||||
|
path.split_at(prefix.len()).1.starts_with('/'))
|
||||||
|
};
|
||||||
|
if m {
|
||||||
|
let path: &'static str = unsafe{
|
||||||
|
mem::transmute(&req.path()[self.prefix+prefix.len()..])};
|
||||||
|
if path.is_empty() {
|
||||||
|
req.match_info_mut().add("tail", "");
|
||||||
|
} else {
|
||||||
|
req.match_info_mut().add("tail", path.split_at(1).1);
|
||||||
|
}
|
||||||
return handler.handle(req)
|
return handler.handle(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.default.handle(req)
|
self.default.handle(req, None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: 'static> HttpHandler for Application<S> {
|
#[cfg(test)]
|
||||||
|
impl<S: 'static> HttpApplication<S> {
|
||||||
|
pub(crate) fn run(&mut self, req: HttpRequest<S>) -> Reply {
|
||||||
|
self.inner.borrow_mut().handle(req)
|
||||||
|
}
|
||||||
|
pub(crate) fn prepare_request(&self, req: HttpRequest) -> HttpRequest<S> {
|
||||||
|
req.with_state(Rc::clone(&self.state), self.router.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle(&self, req: HttpRequest) -> Result<Pipeline, HttpRequest> {
|
impl<S: 'static> HttpHandler for HttpApplication<S> {
|
||||||
if req.path().starts_with(&self.prefix) {
|
|
||||||
Ok(Pipeline::new(req, Rc::clone(&self.middlewares),
|
fn handle(&mut self, req: HttpRequest) -> Result<Box<HttpHandlerTask>, HttpRequest> {
|
||||||
&|req: HttpRequest| self.run(req)))
|
let m = {
|
||||||
|
let path = req.path();
|
||||||
|
path.starts_with(&self.prefix) && (
|
||||||
|
path.len() == self.prefix.len() ||
|
||||||
|
path.split_at(self.prefix.len()).1.starts_with('/'))
|
||||||
|
};
|
||||||
|
if m {
|
||||||
|
let inner = Rc::clone(&self.inner);
|
||||||
|
let req = req.with_state(Rc::clone(&self.state), self.router.clone());
|
||||||
|
|
||||||
|
Ok(Box::new(Pipeline::new(req, Rc::clone(&self.middlewares), inner)))
|
||||||
} else {
|
} else {
|
||||||
Err(req)
|
Err(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct ApplicationParts<S> {
|
||||||
|
state: S,
|
||||||
|
prefix: String,
|
||||||
|
settings: ServerSettings,
|
||||||
|
default: Resource<S>,
|
||||||
|
resources: HashMap<Pattern, Option<Resource<S>>>,
|
||||||
|
handlers: Vec<(String, Box<RouteHandler<S>>)>,
|
||||||
|
external: HashMap<String, Pattern>,
|
||||||
|
middlewares: Vec<Box<Middleware<S>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Structure that follows the builder pattern for building `Application` structs.
|
||||||
|
pub struct Application<S=()> {
|
||||||
|
parts: Option<ApplicationParts<S>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Application<()> {
|
impl Application<()> {
|
||||||
|
|
||||||
/// Create default `ApplicationBuilder` with no state
|
/// Create application with empty state. Application can
|
||||||
pub fn default<T: Into<String>>(prefix: T) -> ApplicationBuilder<()> {
|
/// be configured with builder-like pattern.
|
||||||
ApplicationBuilder {
|
pub fn new() -> Application<()> {
|
||||||
parts: Some(ApplicationBuilderParts {
|
Application {
|
||||||
|
parts: Some(ApplicationParts {
|
||||||
state: (),
|
state: (),
|
||||||
prefix: prefix.into(),
|
prefix: "/".to_owned(),
|
||||||
|
settings: ServerSettings::default(),
|
||||||
default: Resource::default_not_found(),
|
default: Resource::default_not_found(),
|
||||||
handlers: HashMap::new(),
|
|
||||||
resources: HashMap::new(),
|
resources: HashMap::new(),
|
||||||
|
handlers: Vec::new(),
|
||||||
|
external: HashMap::new(),
|
||||||
middlewares: Vec::new(),
|
middlewares: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Application<()> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Application::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<S> Application<S> where S: 'static {
|
impl<S> Application<S> where S: 'static {
|
||||||
|
|
||||||
/// Create application builder with specific state. State is shared with all
|
/// Create application with specific state. Application can be
|
||||||
/// routes within same application and could be
|
/// configured with builder-like pattern.
|
||||||
/// accessed with `HttpContext::state()` method.
|
///
|
||||||
pub fn build<T: Into<String>>(prefix: T, state: S) -> ApplicationBuilder<S> {
|
/// State is shared with all reousrces within same application and could be
|
||||||
ApplicationBuilder {
|
/// accessed with `HttpRequest::state()` method.
|
||||||
parts: Some(ApplicationBuilderParts {
|
pub fn with_state(state: S) -> Application<S> {
|
||||||
|
Application {
|
||||||
|
parts: Some(ApplicationParts {
|
||||||
state: state,
|
state: state,
|
||||||
prefix: prefix.into(),
|
prefix: "/".to_owned(),
|
||||||
|
settings: ServerSettings::default(),
|
||||||
default: Resource::default_not_found(),
|
default: Resource::default_not_found(),
|
||||||
handlers: HashMap::new(),
|
|
||||||
resources: HashMap::new(),
|
resources: HashMap::new(),
|
||||||
|
handlers: Vec::new(),
|
||||||
|
external: HashMap::new(),
|
||||||
middlewares: Vec::new(),
|
middlewares: Vec::new(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
struct ApplicationBuilderParts<S> {
|
/// Set application prefix
|
||||||
state: S,
|
///
|
||||||
prefix: String,
|
/// Only requests that matches application's prefix get processed by this application.
|
||||||
default: Resource<S>,
|
/// Application prefix always contains laading "/" slash. If supplied prefix
|
||||||
handlers: HashMap<String, Box<RouteHandler<S>>>,
|
/// does not contain leading slash, it get inserted. Prefix should
|
||||||
resources: HashMap<String, Resource<S>>,
|
/// consists valid path segments. i.e for application with
|
||||||
middlewares: Vec<Box<Middleware>>,
|
/// prefix `/app` any request with following paths `/app`, `/app/` or `/app/test`
|
||||||
}
|
/// would match, but path `/application` would not match.
|
||||||
|
///
|
||||||
/// Structure that follows the builder pattern for building `Application` structs.
|
/// In the following example only requests with "/app/" path prefix
|
||||||
pub struct ApplicationBuilder<S=()> {
|
/// get handled. Request with path "/app/test/" would be handled,
|
||||||
parts: Option<ApplicationBuilderParts<S>>,
|
/// but request with path "/application" or "/other/..." would return *NOT FOUND*
|
||||||
}
|
///
|
||||||
|
/// ```rust
|
||||||
impl<S> ApplicationBuilder<S> where S: 'static {
|
/// # extern crate actix_web;
|
||||||
|
/// use actix_web::*;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let app = Application::new()
|
||||||
|
/// .prefix("/app")
|
||||||
|
/// .resource("/test", |r| {
|
||||||
|
/// r.method(Method::GET).f(|_| httpcodes::HTTPOk);
|
||||||
|
/// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed);
|
||||||
|
/// })
|
||||||
|
/// .finish();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn prefix<P: Into<String>>(mut self, prefix: P) -> Application<S> {
|
||||||
|
{
|
||||||
|
let parts = self.parts.as_mut().expect("Use after finish");
|
||||||
|
let mut prefix = prefix.into();
|
||||||
|
if !prefix.starts_with('/') {
|
||||||
|
prefix.insert(0, '/')
|
||||||
|
}
|
||||||
|
parts.prefix = prefix;
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Configure resource for specific path.
|
/// Configure resource for specific path.
|
||||||
///
|
///
|
||||||
@ -118,7 +200,7 @@ impl<S> ApplicationBuilder<S> where S: 'static {
|
|||||||
/// A variable part is specified in the form `{identifier}`, where
|
/// A variable part is specified in the form `{identifier}`, where
|
||||||
/// the identifier can be used later in a request handler to access the matched
|
/// the identifier can be used later in a request handler to access the matched
|
||||||
/// value for that part. This is done by looking up the identifier
|
/// value for that part. This is done by looking up the identifier
|
||||||
/// in the `Params` object returned by `Request.match_info()` method.
|
/// in the `Params` object returned by `HttpRequest.match_info()` method.
|
||||||
///
|
///
|
||||||
/// By default, each part matches the regular expression `[^{}/]+`.
|
/// By default, each part matches the regular expression `[^{}/]+`.
|
||||||
///
|
///
|
||||||
@ -128,37 +210,39 @@ impl<S> ApplicationBuilder<S> where S: 'static {
|
|||||||
/// store userid and friend in the exposed Params object:
|
/// store userid and friend in the exposed Params object:
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// extern crate actix_web;
|
/// # extern crate actix_web;
|
||||||
/// use actix_web::*;
|
/// use actix_web::*;
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
/// let app = Application::default("/")
|
/// let app = Application::new()
|
||||||
/// .resource("/test", |r| {
|
/// .resource("/test", |r| {
|
||||||
/// r.get(|req| httpcodes::HTTPOk);
|
/// r.method(Method::GET).f(|_| httpcodes::HTTPOk);
|
||||||
/// r.handler(Method::HEAD, |req| httpcodes::HTTPMethodNotAllowed);
|
/// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed);
|
||||||
/// })
|
/// });
|
||||||
/// .finish();
|
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn resource<F, P: Into<String>>(&mut self, path: P, f: F) -> &mut Self
|
pub fn resource<F>(mut self, path: &str, f: F) -> Application<S>
|
||||||
where F: FnOnce(&mut Resource<S>) + 'static
|
where F: FnOnce(&mut Resource<S>) + 'static
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
let parts = self.parts.as_mut().expect("Use after finish");
|
let parts = self.parts.as_mut().expect("Use after finish");
|
||||||
|
|
||||||
// add resource
|
// add resource
|
||||||
let path = path.into();
|
let mut resource = Resource::default();
|
||||||
if !parts.resources.contains_key(&path) {
|
f(&mut resource);
|
||||||
check_pattern(&path);
|
|
||||||
parts.resources.insert(path.clone(), Resource::default());
|
let pattern = Pattern::new(resource.get_name(), path, "^/");
|
||||||
|
if parts.resources.contains_key(&pattern) {
|
||||||
|
panic!("Resource {:?} is registered.", path);
|
||||||
}
|
}
|
||||||
f(parts.resources.get_mut(&path).unwrap());
|
|
||||||
|
parts.resources.insert(pattern, Some(resource));
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Default resource is used if no match route could be found.
|
/// Default resource is used if no matched route could be found.
|
||||||
pub fn default_resource<F>(&mut self, f: F) -> &mut Self
|
pub fn default_resource<F>(mut self, f: F) -> Application<S>
|
||||||
where F: FnOnce(&mut Resource<S>) + 'static
|
where F: FnOnce(&mut Resource<S>) + 'static
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
@ -168,105 +252,142 @@ impl<S> ApplicationBuilder<S> where S: 'static {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This method register handler for specified path prefix.
|
/// Register external resource.
|
||||||
/// Any path that starts with this prefix matches handler.
|
///
|
||||||
|
/// External resources are useful for URL generation purposes only and
|
||||||
|
/// are never considered for matching at request time.
|
||||||
|
/// Call to `HttpRequest::url_for()` will work as expected.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// extern crate actix_web;
|
/// # extern crate actix_web;
|
||||||
/// use actix_web::*;
|
/// use actix_web::*;
|
||||||
///
|
///
|
||||||
|
/// fn index(mut req: HttpRequest) -> Result<HttpResponse> {
|
||||||
|
/// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
|
||||||
|
/// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
|
||||||
|
/// Ok(httpcodes::HTTPOk.into())
|
||||||
|
/// }
|
||||||
|
///
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
/// let app = Application::default("/")
|
/// let app = Application::new()
|
||||||
/// .handler("/test", |req| {
|
/// .resource("/index.html", |r| r.f(index))
|
||||||
/// match *req.method() {
|
/// .external_resource("youtube", "https://youtube.com/watch/{video_id}")
|
||||||
/// Method::GET => httpcodes::HTTPOk,
|
|
||||||
/// Method::POST => httpcodes::HTTPMethodNotAllowed,
|
|
||||||
/// _ => httpcodes::HTTPNotFound,
|
|
||||||
/// }
|
|
||||||
/// })
|
|
||||||
/// .finish();
|
/// .finish();
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn handler<P, F, R>(&mut self, path: P, handler: F) -> &mut Self
|
pub fn external_resource<T, U>(mut self, name: T, url: U) -> Application<S>
|
||||||
where P: Into<String>,
|
where T: AsRef<str>, U: AsRef<str>
|
||||||
F: Fn(HttpRequest<S>) -> R + 'static,
|
|
||||||
R: Into<Reply> + 'static
|
|
||||||
{
|
{
|
||||||
self.parts.as_mut().expect("Use after finish")
|
{
|
||||||
.handlers.insert(path.into(), Box::new(WrapHandler::new(handler)));
|
let parts = self.parts.as_mut().expect("Use after finish");
|
||||||
|
|
||||||
|
if parts.external.contains_key(name.as_ref()) {
|
||||||
|
panic!("External resource {:?} is registered.", name.as_ref());
|
||||||
|
}
|
||||||
|
parts.external.insert(
|
||||||
|
String::from(name.as_ref()), Pattern::new(name.as_ref(), url.as_ref(), "^/"));
|
||||||
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This method register handler for specified path prefix.
|
/// Configure handler for specific path prefix.
|
||||||
/// Any path that starts with this prefix matches handler.
|
///
|
||||||
pub fn route<P, H>(&mut self, path: P, handler: H) -> &mut Self
|
/// Path prefix consists valid path segments. i.e for prefix `/app`
|
||||||
where P: Into<String>, H: Handler<S>
|
/// any request with following paths `/app`, `/app/` or `/app/test`
|
||||||
|
/// would match, but path `/application` would not match.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// use actix_web::*;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let app = Application::new()
|
||||||
|
/// .handler("/app", |req: HttpRequest| {
|
||||||
|
/// match *req.method() {
|
||||||
|
/// Method::GET => httpcodes::HTTPOk,
|
||||||
|
/// Method::POST => httpcodes::HTTPMethodNotAllowed,
|
||||||
|
/// _ => httpcodes::HTTPNotFound,
|
||||||
|
/// }});
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn handler<H: Handler<S>>(mut self, path: &str, handler: H) -> Application<S>
|
||||||
{
|
{
|
||||||
self.parts.as_mut().expect("Use after finish")
|
{
|
||||||
.handlers.insert(path.into(), Box::new(WrapHandler::new(handler)));
|
let path = path.trim().trim_right_matches('/').to_owned();
|
||||||
|
let parts = self.parts.as_mut().expect("Use after finish");
|
||||||
|
parts.handlers.push((path, Box::new(WrapHandler::new(handler))));
|
||||||
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This method register async handler for specified path prefix.
|
/// Register a middleware
|
||||||
/// Any path that starts with this prefix matches handler.
|
pub fn middleware<T>(mut self, mw: T) -> Application<S>
|
||||||
pub fn async<P, F, R>(&mut self, path: P, handler: F) -> &mut Self
|
where T: Middleware<S> + 'static
|
||||||
where F: Fn(HttpRequest<S>) -> R + 'static,
|
|
||||||
R: Future<Item=HttpResponse, Error=Error> + 'static,
|
|
||||||
P: Into<String>,
|
|
||||||
{
|
|
||||||
self.parts.as_mut().expect("Use after finish")
|
|
||||||
.handlers.insert(path.into(), Box::new(AsyncHandler::new(handler)));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Construct application
|
|
||||||
pub fn middleware<T>(&mut self, mw: T) -> &mut Self
|
|
||||||
where T: Middleware + 'static
|
|
||||||
{
|
{
|
||||||
self.parts.as_mut().expect("Use after finish")
|
self.parts.as_mut().expect("Use after finish")
|
||||||
.middlewares.push(Box::new(mw));
|
.middlewares.push(Box::new(mw));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct application
|
/// Finish application configuration and create HttpHandler object
|
||||||
pub fn finish(&mut self) -> Application<S> {
|
pub fn finish(&mut self) -> HttpApplication<S> {
|
||||||
let parts = self.parts.take().expect("Use after finish");
|
let parts = self.parts.take().expect("Use after finish");
|
||||||
|
let prefix = parts.prefix.trim().trim_right_matches('/');
|
||||||
|
|
||||||
let mut handlers = HashMap::new();
|
let mut resources = parts.resources;
|
||||||
let prefix = if parts.prefix.ends_with('/') {
|
for (_, pattern) in parts.external {
|
||||||
parts.prefix
|
resources.insert(pattern, None);
|
||||||
} else {
|
|
||||||
parts.prefix + "/"
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut routes = Vec::new();
|
|
||||||
for (path, handler) in parts.resources {
|
|
||||||
routes.push((path, handler))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (path, mut handler) in parts.handlers {
|
let (router, resources) = Router::new(prefix, parts.settings, resources);
|
||||||
let path = prefix.clone() + path.trim_left_matches('/');
|
|
||||||
handlers.insert(path, handler);
|
let inner = Rc::new(RefCell::new(
|
||||||
}
|
Inner {
|
||||||
Application {
|
prefix: prefix.len(),
|
||||||
|
default: parts.default,
|
||||||
|
router: router.clone(),
|
||||||
|
resources: resources,
|
||||||
|
handlers: parts.handlers,
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
HttpApplication {
|
||||||
state: Rc::new(parts.state),
|
state: Rc::new(parts.state),
|
||||||
prefix: prefix.clone(),
|
prefix: prefix.to_owned(),
|
||||||
default: parts.default,
|
inner: inner,
|
||||||
handlers: handlers,
|
router: router.clone(),
|
||||||
router: RouteRecognizer::new(prefix, routes),
|
|
||||||
middlewares: Rc::new(parts.middlewares),
|
middlewares: Rc::new(parts.middlewares),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: 'static> From<ApplicationBuilder<S>> for Application<S> {
|
impl<S: 'static> IntoHttpHandler for Application<S> {
|
||||||
fn from(mut builder: ApplicationBuilder<S>) -> Application<S> {
|
type Handler = HttpApplication<S>;
|
||||||
builder.finish()
|
|
||||||
|
fn into_handler(mut self, settings: ServerSettings) -> HttpApplication<S> {
|
||||||
|
{
|
||||||
|
let parts = self.parts.as_mut().expect("Use after finish");
|
||||||
|
parts.settings = settings;
|
||||||
|
}
|
||||||
|
self.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<S: 'static> Iterator for ApplicationBuilder<S> {
|
impl<'a, S: 'static> IntoHttpHandler for &'a mut Application<S> {
|
||||||
type Item = Application<S>;
|
type Handler = HttpApplication<S>;
|
||||||
|
|
||||||
|
fn into_handler(self, settings: ServerSettings) -> HttpApplication<S> {
|
||||||
|
{
|
||||||
|
let parts = self.parts.as_mut().expect("Use after finish");
|
||||||
|
parts.settings = settings;
|
||||||
|
}
|
||||||
|
self.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
impl<S: 'static> Iterator for Application<S> {
|
||||||
|
type Item = HttpApplication<S>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
if self.parts.is_some() {
|
if self.parts.is_some() {
|
||||||
@ -276,3 +397,138 @@ impl<S: 'static> Iterator for ApplicationBuilder<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use http::StatusCode;
|
||||||
|
use super::*;
|
||||||
|
use test::TestRequest;
|
||||||
|
use httprequest::HttpRequest;
|
||||||
|
use httpcodes;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default_resource() {
|
||||||
|
let mut app = Application::new()
|
||||||
|
.resource("/test", |r| r.h(httpcodes::HTTPOk))
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/test").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/blah").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
let mut app = Application::new()
|
||||||
|
.default_resource(|r| r.h(httpcodes::HTTPMethodNotAllowed))
|
||||||
|
.finish();
|
||||||
|
let req = TestRequest::with_uri("/blah").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_response().unwrap().status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_unhandled_prefix() {
|
||||||
|
let mut app = Application::new()
|
||||||
|
.prefix("/test")
|
||||||
|
.resource("/test", |r| r.h(httpcodes::HTTPOk))
|
||||||
|
.finish();
|
||||||
|
assert!(app.handle(HttpRequest::default()).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_state() {
|
||||||
|
let mut app = Application::with_state(10)
|
||||||
|
.resource("/", |r| r.h(httpcodes::HTTPOk))
|
||||||
|
.finish();
|
||||||
|
let req = HttpRequest::default().with_state(Rc::clone(&app.state), app.router.clone());
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_prefix() {
|
||||||
|
let mut app = Application::new()
|
||||||
|
.prefix("/test")
|
||||||
|
.resource("/blah", |r| r.h(httpcodes::HTTPOk))
|
||||||
|
.finish();
|
||||||
|
let req = TestRequest::with_uri("/test").finish();
|
||||||
|
let resp = app.handle(req);
|
||||||
|
assert!(resp.is_ok());
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/test/").finish();
|
||||||
|
let resp = app.handle(req);
|
||||||
|
assert!(resp.is_ok());
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/test/blah").finish();
|
||||||
|
let resp = app.handle(req);
|
||||||
|
assert!(resp.is_ok());
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/testing").finish();
|
||||||
|
let resp = app.handle(req);
|
||||||
|
assert!(resp.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_handler() {
|
||||||
|
let mut app = Application::new()
|
||||||
|
.handler("/test", httpcodes::HTTPOk)
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/test").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/test/").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/test/app").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/testapp").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/blah").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_handler_prefix() {
|
||||||
|
let mut app = Application::new()
|
||||||
|
.prefix("/app")
|
||||||
|
.handler("/test", httpcodes::HTTPOk)
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/test").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/test").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/test/").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/test/app").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_response().unwrap().status(), StatusCode::OK);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/testapp").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
let req = TestRequest::with_uri("/app/blah").finish();
|
||||||
|
let resp = app.run(req);
|
||||||
|
assert_eq!(resp.as_response().unwrap().status(), StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
40
src/body.rs
40
src/body.rs
@ -5,9 +5,10 @@ use bytes::{Bytes, BytesMut};
|
|||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
|
|
||||||
use error::Error;
|
use error::Error;
|
||||||
|
use context::ActorHttpContext;
|
||||||
|
|
||||||
pub(crate) type BodyStream = Box<Stream<Item=Bytes, Error=Error>>;
|
/// Type represent streaming body
|
||||||
|
pub type BodyStream = Box<Stream<Item=Bytes, Error=Error>>;
|
||||||
|
|
||||||
/// Represents various types of http message body.
|
/// Represents various types of http message body.
|
||||||
pub enum Body {
|
pub enum Body {
|
||||||
@ -18,12 +19,8 @@ pub enum Body {
|
|||||||
/// Unspecified streaming response. Developer is responsible for setting
|
/// Unspecified streaming response. Developer is responsible for setting
|
||||||
/// right `Content-Length` or `Transfer-Encoding` headers.
|
/// right `Content-Length` or `Transfer-Encoding` headers.
|
||||||
Streaming(BodyStream),
|
Streaming(BodyStream),
|
||||||
/// Upgrade connection.
|
/// Special body type for actor response.
|
||||||
Upgrade(BodyStream),
|
Actor(Box<ActorHttpContext>),
|
||||||
/// Special body type for actor streaming response.
|
|
||||||
StreamingContext,
|
|
||||||
/// Special body type for actor upgrade response.
|
|
||||||
UpgradeContext,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents various types of binary body.
|
/// Represents various types of binary body.
|
||||||
@ -48,15 +45,16 @@ pub enum Binary {
|
|||||||
|
|
||||||
impl Body {
|
impl Body {
|
||||||
/// Does this body streaming.
|
/// Does this body streaming.
|
||||||
|
#[inline]
|
||||||
pub fn is_streaming(&self) -> bool {
|
pub fn is_streaming(&self) -> bool {
|
||||||
match *self {
|
match *self {
|
||||||
Body::Streaming(_) | Body::StreamingContext
|
Body::Streaming(_) | Body::Actor(_) => true,
|
||||||
| Body::Upgrade(_) | Body::UpgradeContext => true,
|
|
||||||
_ => false
|
_ => false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Is this binary body.
|
/// Is this binary body.
|
||||||
|
#[inline]
|
||||||
pub fn is_binary(&self) -> bool {
|
pub fn is_binary(&self) -> bool {
|
||||||
match *self {
|
match *self {
|
||||||
Body::Binary(_) => true,
|
Body::Binary(_) => true,
|
||||||
@ -81,15 +79,7 @@ impl PartialEq for Body {
|
|||||||
Body::Binary(ref b2) => b == b2,
|
Body::Binary(ref b2) => b == b2,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
Body::StreamingContext => match *other {
|
Body::Streaming(_) | Body::Actor(_) => false,
|
||||||
Body::StreamingContext => true,
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
Body::UpgradeContext => match *other {
|
|
||||||
Body::UpgradeContext => true,
|
|
||||||
_ => false,
|
|
||||||
},
|
|
||||||
Body::Streaming(_) | Body::Upgrade(_) => false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,9 +90,7 @@ impl fmt::Debug for Body {
|
|||||||
Body::Empty => write!(f, "Body::Empty"),
|
Body::Empty => write!(f, "Body::Empty"),
|
||||||
Body::Binary(ref b) => write!(f, "Body::Binary({:?})", b),
|
Body::Binary(ref b) => write!(f, "Body::Binary({:?})", b),
|
||||||
Body::Streaming(_) => write!(f, "Body::Streaming(_)"),
|
Body::Streaming(_) => write!(f, "Body::Streaming(_)"),
|
||||||
Body::Upgrade(_) => write!(f, "Body::Upgrade(_)"),
|
Body::Actor(_) => write!(f, "Body::Actor(_)"),
|
||||||
Body::StreamingContext => write!(f, "Body::StreamingContext"),
|
|
||||||
Body::UpgradeContext => write!(f, "Body::UpgradeContext"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -113,11 +101,19 @@ impl<T> From<T> for Body where T: Into<Binary>{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Box<ActorHttpContext>> for Body {
|
||||||
|
fn from(ctx: Box<ActorHttpContext>) -> Body {
|
||||||
|
Body::Actor(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Binary {
|
impl Binary {
|
||||||
|
#[inline]
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.len() == 0
|
self.len() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
match *self {
|
match *self {
|
||||||
Binary::Bytes(ref bytes) => bytes.len(),
|
Binary::Bytes(ref bytes) => bytes.len(),
|
||||||
|
357
src/channel.rs
357
src/channel.rs
@ -1,24 +1,54 @@
|
|||||||
|
use std::{ptr, mem, time, io};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::net::SocketAddr;
|
use std::net::{SocketAddr, Shutdown};
|
||||||
|
|
||||||
use actix::dev::*;
|
use bytes::{Bytes, Buf, BufMut};
|
||||||
use bytes::Bytes;
|
|
||||||
use futures::{Future, Poll, Async};
|
use futures::{Future, Poll, Async};
|
||||||
use tokio_io::{AsyncRead, AsyncWrite};
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
|
use tokio_core::net::TcpStream;
|
||||||
|
|
||||||
use h1;
|
use {h1, h2};
|
||||||
use h2;
|
use error::Error;
|
||||||
use pipeline::Pipeline;
|
use h1writer::Writer;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
|
use server::ServerSettings;
|
||||||
|
use worker::WorkerSettings;
|
||||||
|
|
||||||
/// Low level http request handler
|
/// Low level http request handler
|
||||||
|
#[allow(unused_variables)]
|
||||||
pub trait HttpHandler: 'static {
|
pub trait HttpHandler: 'static {
|
||||||
|
|
||||||
/// Handle request
|
/// Handle request
|
||||||
fn handle(&self, req: HttpRequest) -> Result<Pipeline, HttpRequest>;
|
fn handle(&mut self, req: HttpRequest) -> Result<Box<HttpHandlerTask>, HttpRequest>;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum HttpProtocol<T, H>
|
pub trait HttpHandlerTask {
|
||||||
where T: AsyncRead + AsyncWrite + 'static, H: 'static
|
|
||||||
|
fn poll_io(&mut self, io: &mut Writer) -> Poll<bool, Error>;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<(), Error>;
|
||||||
|
|
||||||
|
fn disconnected(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Conversion helper trait
|
||||||
|
pub trait IntoHttpHandler {
|
||||||
|
/// The associated type which is result of conversion.
|
||||||
|
type Handler: HttpHandler;
|
||||||
|
|
||||||
|
/// Convert into `HttpHandler` object.
|
||||||
|
fn into_handler(self, settings: ServerSettings) -> Self::Handler;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: HttpHandler> IntoHttpHandler for T {
|
||||||
|
type Handler = T;
|
||||||
|
|
||||||
|
fn into_handler(self, _: ServerSettings) -> Self::Handler {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum HttpProtocol<T: IoStream, H: 'static>
|
||||||
{
|
{
|
||||||
H1(h1::Http1<T, H>),
|
H1(h1::Http1<T, H>),
|
||||||
H2(h2::Http2<T, H>),
|
H2(h2::Http2<T, H>),
|
||||||
@ -26,61 +56,104 @@ enum HttpProtocol<T, H>
|
|||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub struct HttpChannel<T, H>
|
pub struct HttpChannel<T, H>
|
||||||
where T: AsyncRead + AsyncWrite + 'static, H: 'static
|
where T: IoStream, H: HttpHandler + 'static
|
||||||
{
|
{
|
||||||
proto: Option<HttpProtocol<T, H>>,
|
proto: Option<HttpProtocol<T, H>>,
|
||||||
|
node: Option<Node<HttpChannel<T, H>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T, H> HttpChannel<T, H>
|
impl<T, H> HttpChannel<T, H>
|
||||||
where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static
|
where T: IoStream, H: HttpHandler + 'static
|
||||||
{
|
{
|
||||||
pub fn new(stream: T, addr: Option<SocketAddr>, router: Rc<Vec<H>>, http2: bool)
|
pub(crate) fn new(h: Rc<WorkerSettings<H>>,
|
||||||
-> HttpChannel<T, H> {
|
io: T, peer: Option<SocketAddr>, http2: bool) -> HttpChannel<T, H>
|
||||||
|
{
|
||||||
|
h.add_channel();
|
||||||
if http2 {
|
if http2 {
|
||||||
HttpChannel {
|
HttpChannel {
|
||||||
|
node: None,
|
||||||
proto: Some(HttpProtocol::H2(
|
proto: Some(HttpProtocol::H2(
|
||||||
h2::Http2::new(stream, addr, router, Bytes::new()))) }
|
h2::Http2::new(h, io, peer, Bytes::new()))) }
|
||||||
} else {
|
} else {
|
||||||
HttpChannel {
|
HttpChannel {
|
||||||
|
node: None,
|
||||||
proto: Some(HttpProtocol::H1(
|
proto: Some(HttpProtocol::H1(
|
||||||
h1::Http1::new(stream, addr, router))) }
|
h1::Http1::new(h, io, peer))) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shutdown(&mut self) {
|
||||||
|
match self.proto {
|
||||||
|
Some(HttpProtocol::H1(ref mut h1)) => {
|
||||||
|
let io = h1.io();
|
||||||
|
let _ = IoStream::set_linger(io, Some(time::Duration::new(0, 0)));
|
||||||
|
let _ = IoStream::shutdown(io, Shutdown::Both);
|
||||||
|
}
|
||||||
|
Some(HttpProtocol::H2(ref mut h2)) => {
|
||||||
|
h2.shutdown()
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*impl<T: 'static, A: 'static, H: 'static> Drop for HttpChannel<T, A, H> {
|
/*impl<T, H> Drop for HttpChannel<T, H>
|
||||||
|
where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static
|
||||||
|
{
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
println!("Drop http channel");
|
println!("Drop http channel");
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
impl<T, H> Actor for HttpChannel<T, H>
|
|
||||||
where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static
|
|
||||||
{
|
|
||||||
type Context = Context<Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T, H> Future for HttpChannel<T, H>
|
impl<T, H> Future for HttpChannel<T, H>
|
||||||
where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static
|
where T: IoStream, H: HttpHandler + 'static
|
||||||
{
|
{
|
||||||
type Item = ();
|
type Item = ();
|
||||||
type Error = ();
|
type Error = ();
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
if self.node.is_none() {
|
||||||
|
self.node = Some(Node::new(self));
|
||||||
|
match self.proto {
|
||||||
|
Some(HttpProtocol::H1(ref mut h1)) => {
|
||||||
|
h1.settings().head().insert(self.node.as_ref().unwrap());
|
||||||
|
}
|
||||||
|
Some(HttpProtocol::H2(ref mut h2)) => {
|
||||||
|
h2.settings().head().insert(self.node.as_ref().unwrap());
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match self.proto {
|
match self.proto {
|
||||||
Some(HttpProtocol::H1(ref mut h1)) => {
|
Some(HttpProtocol::H1(ref mut h1)) => {
|
||||||
match h1.poll() {
|
match h1.poll() {
|
||||||
Ok(Async::Ready(h1::Http1Result::Done)) =>
|
Ok(Async::Ready(h1::Http1Result::Done)) => {
|
||||||
return Ok(Async::Ready(())),
|
h1.settings().remove_channel();
|
||||||
|
self.node.as_ref().unwrap().remove();
|
||||||
|
return Ok(Async::Ready(()))
|
||||||
|
}
|
||||||
Ok(Async::Ready(h1::Http1Result::Switch)) => (),
|
Ok(Async::Ready(h1::Http1Result::Switch)) => (),
|
||||||
Ok(Async::NotReady) =>
|
Ok(Async::NotReady) =>
|
||||||
return Ok(Async::NotReady),
|
return Ok(Async::NotReady),
|
||||||
Err(_) =>
|
Err(_) => {
|
||||||
return Err(()),
|
h1.settings().remove_channel();
|
||||||
|
self.node.as_ref().unwrap().remove();
|
||||||
|
return Err(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(HttpProtocol::H2(ref mut h2)) =>
|
Some(HttpProtocol::H2(ref mut h2)) => {
|
||||||
return h2.poll(),
|
let result = h2.poll();
|
||||||
|
match result {
|
||||||
|
Ok(Async::Ready(())) | Err(_) => {
|
||||||
|
h2.settings().remove_channel();
|
||||||
|
self.node.as_ref().unwrap().remove();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
None => unreachable!(),
|
None => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,11 +161,233 @@ impl<T, H> Future for HttpChannel<T, H>
|
|||||||
let proto = self.proto.take().unwrap();
|
let proto = self.proto.take().unwrap();
|
||||||
match proto {
|
match proto {
|
||||||
HttpProtocol::H1(h1) => {
|
HttpProtocol::H1(h1) => {
|
||||||
let (stream, addr, router, buf) = h1.into_inner();
|
let (h, io, addr, buf) = h1.into_inner();
|
||||||
self.proto = Some(HttpProtocol::H2(h2::Http2::new(stream, addr, router, buf)));
|
self.proto = Some(
|
||||||
|
HttpProtocol::H2(h2::Http2::new(h, io, addr, buf)));
|
||||||
self.poll()
|
self.poll()
|
||||||
}
|
}
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Node<T>
|
||||||
|
{
|
||||||
|
next: Option<*mut Node<()>>,
|
||||||
|
prev: Option<*mut Node<()>>,
|
||||||
|
element: *mut T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Node<T>
|
||||||
|
{
|
||||||
|
fn new(el: &mut T) -> Self {
|
||||||
|
Node {
|
||||||
|
next: None,
|
||||||
|
prev: None,
|
||||||
|
element: el as *mut _,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert<I>(&self, next: &Node<I>) {
|
||||||
|
#[allow(mutable_transmutes)]
|
||||||
|
unsafe {
|
||||||
|
if let Some(ref next2) = self.next {
|
||||||
|
let n: &mut Node<()> = mem::transmute(next2.as_ref().unwrap());
|
||||||
|
n.prev = Some(next as *const _ as *mut _);
|
||||||
|
}
|
||||||
|
let slf: &mut Node<T> = mem::transmute(self);
|
||||||
|
slf.next = Some(next as *const _ as *mut _);
|
||||||
|
|
||||||
|
let next: &mut Node<T> = mem::transmute(next);
|
||||||
|
next.prev = Some(slf as *const _ as *mut _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&self) {
|
||||||
|
#[allow(mutable_transmutes)]
|
||||||
|
unsafe {
|
||||||
|
if let Some(ref prev) = self.prev {
|
||||||
|
let p: &mut Node<()> = mem::transmute(prev.as_ref().unwrap());
|
||||||
|
let slf: &mut Node<T> = mem::transmute(self);
|
||||||
|
p.next = slf.next.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Node<()> {
|
||||||
|
|
||||||
|
pub(crate) fn head() -> Self {
|
||||||
|
Node {
|
||||||
|
next: None,
|
||||||
|
prev: None,
|
||||||
|
element: ptr::null_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn traverse<T, H>(&self) where T: IoStream, H: HttpHandler + 'static {
|
||||||
|
let mut next = self.next.as_ref();
|
||||||
|
loop {
|
||||||
|
if let Some(n) = next {
|
||||||
|
unsafe {
|
||||||
|
let n: &Node<()> = mem::transmute(n.as_ref().unwrap());
|
||||||
|
next = n.next.as_ref();
|
||||||
|
|
||||||
|
if !n.element.is_null() {
|
||||||
|
let ch: &mut HttpChannel<T, H> = mem::transmute(
|
||||||
|
&mut *(n.element as *mut _));
|
||||||
|
ch.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Low-level io stream operations
|
||||||
|
pub trait IoStream: AsyncRead + AsyncWrite + 'static {
|
||||||
|
fn shutdown(&mut self, how: Shutdown) -> io::Result<()>;
|
||||||
|
|
||||||
|
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()>;
|
||||||
|
|
||||||
|
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IoStream for TcpStream {
|
||||||
|
#[inline]
|
||||||
|
fn shutdown(&mut self, how: Shutdown) -> io::Result<()> {
|
||||||
|
TcpStream::shutdown(self, how)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
|
||||||
|
TcpStream::set_nodelay(self, nodelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||||
|
TcpStream::set_linger(self, dur)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Wrapper for `AsyncRead + AsyncWrite` types
|
||||||
|
pub(crate) struct WrapperStream<T> where T: AsyncRead + AsyncWrite + 'static {
|
||||||
|
io: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> WrapperStream<T> where T: AsyncRead + AsyncWrite + 'static
|
||||||
|
{
|
||||||
|
pub fn new(io: T) -> Self {
|
||||||
|
WrapperStream{io: io}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> IoStream for WrapperStream<T>
|
||||||
|
where T: AsyncRead + AsyncWrite + 'static
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn shutdown(&mut self, _: Shutdown) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_nodelay(&mut self, _: bool) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_linger(&mut self, _: Option<time::Duration>) -> io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> io::Read for WrapperStream<T>
|
||||||
|
where T: AsyncRead + AsyncWrite + 'static
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
self.io.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> io::Write for WrapperStream<T>
|
||||||
|
where T: AsyncRead + AsyncWrite + 'static
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
self.io.write(buf)
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
self.io.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AsyncRead for WrapperStream<T>
|
||||||
|
where T: AsyncRead + AsyncWrite + 'static
|
||||||
|
{
|
||||||
|
fn read_buf<B: BufMut>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
|
||||||
|
self.io.read_buf(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AsyncWrite for WrapperStream<T>
|
||||||
|
where T: AsyncRead + AsyncWrite + 'static
|
||||||
|
{
|
||||||
|
fn shutdown(&mut self) -> Poll<(), io::Error> {
|
||||||
|
self.io.shutdown()
|
||||||
|
}
|
||||||
|
fn write_buf<B: Buf>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
|
||||||
|
self.io.write_buf(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(feature="alpn")]
|
||||||
|
use tokio_openssl::SslStream;
|
||||||
|
|
||||||
|
#[cfg(feature="alpn")]
|
||||||
|
impl IoStream for SslStream<TcpStream> {
|
||||||
|
#[inline]
|
||||||
|
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
|
||||||
|
let _ = self.get_mut().shutdown();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
|
||||||
|
self.get_mut().get_mut().set_nodelay(nodelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||||
|
self.get_mut().get_mut().set_linger(dur)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature="tls")]
|
||||||
|
use tokio_tls::TlsStream;
|
||||||
|
|
||||||
|
#[cfg(feature="tls")]
|
||||||
|
impl IoStream for TlsStream<TcpStream> {
|
||||||
|
#[inline]
|
||||||
|
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
|
||||||
|
let _ = self.get_mut().shutdown();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
|
||||||
|
self.get_mut().get_mut().set_nodelay(nodelay)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
|
||||||
|
self.get_mut().get_mut().set_linger(dur)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
247
src/context.rs
247
src/context.rs
@ -1,47 +1,38 @@
|
|||||||
use std;
|
use std;
|
||||||
use std::rc::Rc;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::VecDeque;
|
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
use std::collections::VecDeque;
|
||||||
use futures::{Async, Future, Poll};
|
use futures::{Async, Future, Poll};
|
||||||
use futures::sync::oneshot::Sender;
|
use futures::sync::oneshot::Sender;
|
||||||
|
use futures::unsync::oneshot;
|
||||||
|
|
||||||
use actix::{Actor, ActorState, ActorContext, AsyncContext,
|
use actix::{Actor, ActorState, ActorContext, AsyncContext,
|
||||||
Handler, Subscriber, ResponseType};
|
Handler, Subscriber, ResponseType, SpawnHandle};
|
||||||
use actix::fut::ActorFuture;
|
use actix::fut::ActorFuture;
|
||||||
use actix::dev::{AsyncContextApi, ActorAddressCell, ActorItemsCell, ActorWaitCell, SpawnHandle,
|
use actix::dev::{AsyncContextApi, ActorAddressCell,
|
||||||
Envelope, ToEnvelope, RemoteEnvelope};
|
ContextImpl, Envelope, ToEnvelope, RemoteEnvelope};
|
||||||
|
|
||||||
use body::{Body, Binary};
|
use body::{Body, Binary};
|
||||||
use error::Error;
|
use error::{Error, Result, ErrorInternalServerError};
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
|
||||||
use pipeline::DrainFut;
|
|
||||||
|
|
||||||
pub(crate) trait IoContext: 'static {
|
|
||||||
|
pub trait ActorHttpContext: 'static {
|
||||||
fn disconnected(&mut self);
|
fn disconnected(&mut self);
|
||||||
fn poll(&mut self) -> Poll<Option<Frame>, Error>;
|
fn poll(&mut self) -> Poll<Option<Frame>, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum Frame {
|
pub enum Frame {
|
||||||
Message(HttpResponse),
|
|
||||||
Payload(Option<Binary>),
|
Payload(Option<Binary>),
|
||||||
Drain(Rc<RefCell<DrainFut>>),
|
Drain(oneshot::Sender<()>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Http actor execution context
|
/// Http actor execution context
|
||||||
pub struct HttpContext<A, S=()> where A: Actor<Context=HttpContext<A, S>>,
|
pub struct HttpContext<A, S=()> where A: Actor<Context=HttpContext<A, S>>,
|
||||||
{
|
{
|
||||||
act: A,
|
inner: ContextImpl<A>,
|
||||||
state: ActorState,
|
|
||||||
modified: bool,
|
|
||||||
items: ActorItemsCell<A>,
|
|
||||||
address: ActorAddressCell<A>,
|
|
||||||
stream: VecDeque<Frame>,
|
stream: VecDeque<Frame>,
|
||||||
wait: ActorWaitCell<A>,
|
|
||||||
request: HttpRequest<S>,
|
request: HttpRequest<S>,
|
||||||
streaming: bool,
|
|
||||||
disconnected: bool,
|
disconnected: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,23 +41,17 @@ impl<A, S> ActorContext for HttpContext<A, S> where A: Actor<Context=Self>
|
|||||||
/// Stop actor execution
|
/// Stop actor execution
|
||||||
fn stop(&mut self) {
|
fn stop(&mut self) {
|
||||||
self.stream.push_back(Frame::Payload(None));
|
self.stream.push_back(Frame::Payload(None));
|
||||||
self.items.stop();
|
self.inner.stop();
|
||||||
self.address.close();
|
|
||||||
if self.state == ActorState::Running {
|
|
||||||
self.state = ActorState::Stopping;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Terminate actor execution
|
/// Terminate actor execution
|
||||||
fn terminate(&mut self) {
|
fn terminate(&mut self) {
|
||||||
self.address.close();
|
self.inner.terminate()
|
||||||
self.items.close();
|
|
||||||
self.state = ActorState::Stopped;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Actor execution state
|
/// Actor execution state
|
||||||
fn state(&self) -> ActorState {
|
fn state(&self) -> ActorState {
|
||||||
self.state
|
self.inner.state()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,51 +60,46 @@ impl<A, S> AsyncContext<A> for HttpContext<A, S> where A: Actor<Context=Self>
|
|||||||
fn spawn<F>(&mut self, fut: F) -> SpawnHandle
|
fn spawn<F>(&mut self, fut: F) -> SpawnHandle
|
||||||
where F: ActorFuture<Item=(), Error=(), Actor=A> + 'static
|
where F: ActorFuture<Item=(), Error=(), Actor=A> + 'static
|
||||||
{
|
{
|
||||||
self.modified = true;
|
self.inner.spawn(fut)
|
||||||
self.items.spawn(fut)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait<F>(&mut self, fut: F)
|
fn wait<F>(&mut self, fut: F)
|
||||||
where F: ActorFuture<Item=(), Error=(), Actor=A> + 'static
|
where F: ActorFuture<Item=(), Error=(), Actor=A> + 'static
|
||||||
{
|
{
|
||||||
self.modified = true;
|
self.inner.wait(fut)
|
||||||
self.wait.add(fut);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cancel_future(&mut self, handle: SpawnHandle) -> bool {
|
fn cancel_future(&mut self, handle: SpawnHandle) -> bool {
|
||||||
self.modified = true;
|
self.inner.cancel_future(handle)
|
||||||
self.items.cancel_future(handle)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cancel_future_on_stop(&mut self, handle: SpawnHandle) {
|
|
||||||
self.items.cancel_future_on_stop(handle)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
impl<A, S> AsyncContextApi<A> for HttpContext<A, S> where A: Actor<Context=Self> {
|
impl<A, S> AsyncContextApi<A> for HttpContext<A, S> where A: Actor<Context=Self> {
|
||||||
fn address_cell(&mut self) -> &mut ActorAddressCell<A> {
|
fn address_cell(&mut self) -> &mut ActorAddressCell<A> {
|
||||||
&mut self.address
|
self.inner.address_cell()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
|
impl<A, S: 'static> HttpContext<A, S> where A: Actor<Context=Self> {
|
||||||
|
|
||||||
pub fn new(req: HttpRequest<S>, actor: A) -> HttpContext<A, S>
|
pub fn new(req: HttpRequest<S>, actor: A) -> HttpContext<A, S> {
|
||||||
{
|
HttpContext::from_request(req).actor(actor)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_request(req: HttpRequest<S>) -> HttpContext<A, S> {
|
||||||
HttpContext {
|
HttpContext {
|
||||||
act: actor,
|
inner: ContextImpl::new(None),
|
||||||
state: ActorState::Started,
|
|
||||||
modified: false,
|
|
||||||
items: ActorItemsCell::default(),
|
|
||||||
address: ActorAddressCell::default(),
|
|
||||||
wait: ActorWaitCell::default(),
|
|
||||||
stream: VecDeque::new(),
|
stream: VecDeque::new(),
|
||||||
request: req,
|
request: req,
|
||||||
streaming: false,
|
|
||||||
disconnected: false,
|
disconnected: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn actor(mut self, actor: A) -> HttpContext<A, S> {
|
||||||
|
self.inner.set_actor(actor);
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
|
impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
|
||||||
@ -134,24 +114,12 @@ impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
|
|||||||
&mut self.request
|
&mut self.request
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send response to peer
|
|
||||||
pub fn start<R: Into<HttpResponse>>(&mut self, response: R) {
|
|
||||||
let resp = response.into();
|
|
||||||
match *resp.body() {
|
|
||||||
Body::StreamingContext | Body::UpgradeContext => self.streaming = true,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
self.stream.push_back(Frame::Message(resp))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write payload
|
/// Write payload
|
||||||
pub fn write<B: Into<Binary>>(&mut self, data: B) {
|
pub fn write<B: Into<Binary>>(&mut self, data: B) {
|
||||||
if self.streaming {
|
if !self.disconnected {
|
||||||
if !self.disconnected {
|
self.stream.push_back(Frame::Payload(Some(data.into())));
|
||||||
self.stream.push_back(Frame::Payload(Some(data.into())))
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
warn!("Trying to write response body for non-streaming response");
|
warn!("Trying to write to disconnected response");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -162,10 +130,10 @@ impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
|
|||||||
|
|
||||||
/// Returns drain future
|
/// Returns drain future
|
||||||
pub fn drain(&mut self) -> Drain<A> {
|
pub fn drain(&mut self) -> Drain<A> {
|
||||||
let fut = Rc::new(RefCell::new(DrainFut::default()));
|
let (tx, rx) = oneshot::channel();
|
||||||
self.stream.push_back(Frame::Drain(Rc::clone(&fut)));
|
self.inner.modify();
|
||||||
self.modified = true;
|
self.stream.push_back(Frame::Drain(tx));
|
||||||
Drain{ a: PhantomData, inner: fut }
|
Drain::new(rx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if connection still open
|
/// Check if connection still open
|
||||||
@ -178,117 +146,43 @@ impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
|
|||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn subscriber<M>(&mut self) -> Box<Subscriber<M>>
|
pub fn subscriber<M>(&mut self) -> Box<Subscriber<M>>
|
||||||
where A: Handler<M>,
|
where A: Handler<M>, M: ResponseType + 'static
|
||||||
M: ResponseType + 'static,
|
|
||||||
{
|
{
|
||||||
Box::new(self.address.unsync_address())
|
self.inner.subscriber()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn sync_subscriber<M>(&mut self) -> Box<Subscriber<M> + Send>
|
pub fn sync_subscriber<M>(&mut self) -> Box<Subscriber<M> + Send>
|
||||||
where A: Handler<M>,
|
where A: Handler<M>,
|
||||||
M: ResponseType + Send + 'static,
|
M: ResponseType + Send + 'static, M::Item: Send, M::Error: Send,
|
||||||
M::Item: Send,
|
|
||||||
M::Error: Send,
|
|
||||||
{
|
{
|
||||||
Box::new(self.address.sync_address())
|
self.inner.sync_subscriber()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A, S> IoContext for HttpContext<A, S> where A: Actor<Context=Self>, S: 'static {
|
impl<A, S> ActorHttpContext for HttpContext<A, S> where A: Actor<Context=Self>, S: 'static {
|
||||||
|
|
||||||
fn disconnected(&mut self) {
|
fn disconnected(&mut self) {
|
||||||
self.items.stop();
|
|
||||||
self.disconnected = true;
|
self.disconnected = true;
|
||||||
if self.state == ActorState::Running {
|
self.stop();
|
||||||
self.state = ActorState::Stopping;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<Option<Frame>, Error> {
|
fn poll(&mut self) -> Poll<Option<Frame>, Error> {
|
||||||
let act: &mut A = unsafe {
|
|
||||||
std::mem::transmute(&mut self.act as &mut A)
|
|
||||||
};
|
|
||||||
let ctx: &mut HttpContext<A, S> = unsafe {
|
let ctx: &mut HttpContext<A, S> = unsafe {
|
||||||
std::mem::transmute(self as &mut HttpContext<A, S>)
|
std::mem::transmute(self as &mut HttpContext<A, S>)
|
||||||
};
|
};
|
||||||
|
|
||||||
// update state
|
match self.inner.poll(ctx) {
|
||||||
match self.state {
|
Ok(Async::NotReady) => {
|
||||||
ActorState::Started => {
|
|
||||||
Actor::started(act, ctx);
|
|
||||||
self.state = ActorState::Running;
|
|
||||||
},
|
|
||||||
ActorState::Stopping => {
|
|
||||||
Actor::stopping(act, ctx);
|
|
||||||
}
|
|
||||||
_ => ()
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut prep_stop = false;
|
|
||||||
loop {
|
|
||||||
self.modified = false;
|
|
||||||
|
|
||||||
// check wait futures
|
|
||||||
if self.wait.poll(act, ctx) {
|
|
||||||
// get frame
|
// get frame
|
||||||
if let Some(frame) = self.stream.pop_front() {
|
if let Some(frame) = self.stream.pop_front() {
|
||||||
return Ok(Async::Ready(Some(frame)))
|
Ok(Async::Ready(Some(frame)))
|
||||||
|
} else {
|
||||||
|
Ok(Async::NotReady)
|
||||||
}
|
}
|
||||||
return Ok(Async::NotReady)
|
|
||||||
}
|
}
|
||||||
|
Ok(Async::Ready(())) => Ok(Async::Ready(None)),
|
||||||
// incoming messages
|
Err(_) => Err(ErrorInternalServerError("error").into()),
|
||||||
self.address.poll(act, ctx);
|
|
||||||
|
|
||||||
// spawned futures and streams
|
|
||||||
self.items.poll(act, ctx);
|
|
||||||
|
|
||||||
// are we done
|
|
||||||
if self.modified {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// get frame
|
|
||||||
if let Some(frame) = self.stream.pop_front() {
|
|
||||||
return Ok(Async::Ready(Some(frame)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// check state
|
|
||||||
match self.state {
|
|
||||||
ActorState::Stopped => {
|
|
||||||
self.state = ActorState::Stopped;
|
|
||||||
Actor::stopped(act, ctx);
|
|
||||||
return Ok(Async::Ready(None))
|
|
||||||
},
|
|
||||||
ActorState::Stopping => {
|
|
||||||
if prep_stop {
|
|
||||||
if self.address.connected() || !self.items.is_empty() {
|
|
||||||
self.state = ActorState::Running;
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
self.state = ActorState::Stopped;
|
|
||||||
Actor::stopped(act, ctx);
|
|
||||||
return Ok(Async::Ready(None))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Actor::stopping(act, ctx);
|
|
||||||
prep_stop = true;
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ActorState::Running => {
|
|
||||||
if !self.address.connected() && self.items.is_empty() {
|
|
||||||
self.state = ActorState::Stopping;
|
|
||||||
Actor::stopping(act, ctx);
|
|
||||||
prep_stop = true;
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(Async::NotReady)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -296,30 +190,49 @@ impl<A, S> IoContext for HttpContext<A, S> where A: Actor<Context=Self>, S: 'sta
|
|||||||
impl<A, S> ToEnvelope<A> for HttpContext<A, S>
|
impl<A, S> ToEnvelope<A> for HttpContext<A, S>
|
||||||
where A: Actor<Context=HttpContext<A, S>>,
|
where A: Actor<Context=HttpContext<A, S>>,
|
||||||
{
|
{
|
||||||
fn pack<M>(msg: M, tx: Option<Sender<Result<M::Item, M::Error>>>) -> Envelope<A>
|
fn pack<M>(msg: M, tx: Option<Sender<Result<M::Item, M::Error>>>,
|
||||||
|
channel_on_drop: bool) -> Envelope<A>
|
||||||
where A: Handler<M>,
|
where A: Handler<M>,
|
||||||
M: ResponseType + Send + 'static,
|
M: ResponseType + Send + 'static,
|
||||||
M::Item: Send,
|
M::Item: Send,
|
||||||
M::Error: Send
|
M::Error: Send
|
||||||
{
|
{
|
||||||
RemoteEnvelope::new(msg, tx).into()
|
RemoteEnvelope::new(msg, tx, channel_on_drop).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<A, S> From<HttpContext<A, S>> for Body
|
||||||
pub struct Drain<A> {
|
where A: Actor<Context=HttpContext<A, S>>,
|
||||||
a: PhantomData<A>,
|
S: 'static
|
||||||
inner: Rc<RefCell<DrainFut>>
|
{
|
||||||
|
fn from(ctx: HttpContext<A, S>) -> Body {
|
||||||
|
Body::Actor(Box::new(ctx))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A> ActorFuture for Drain<A>
|
pub struct Drain<A> {
|
||||||
where A: Actor
|
fut: oneshot::Receiver<()>,
|
||||||
{
|
_a: PhantomData<A>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A> Drain<A> {
|
||||||
|
fn new(fut: oneshot::Receiver<()>) -> Self {
|
||||||
|
Drain {
|
||||||
|
fut: fut,
|
||||||
|
_a: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<A: Actor> ActorFuture for Drain<A> {
|
||||||
type Item = ();
|
type Item = ();
|
||||||
type Error = ();
|
type Error = ();
|
||||||
type Actor = A;
|
type Actor = A;
|
||||||
|
|
||||||
fn poll(&mut self, _: &mut A, _: &mut <Self::Actor as Actor>::Context) -> Poll<(), ()> {
|
fn poll(&mut self,
|
||||||
self.inner.borrow_mut().poll()
|
_: &mut A,
|
||||||
|
_: &mut <Self::Actor as Actor>::Context) -> Poll<Self::Item, Self::Error>
|
||||||
|
{
|
||||||
|
self.fut.poll().map_err(|_| ())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
68
src/date.rs
68
src/date.rs
@ -1,68 +0,0 @@
|
|||||||
use std::cell::RefCell;
|
|
||||||
use std::fmt::{self, Write};
|
|
||||||
use std::str;
|
|
||||||
use time::{self, Duration};
|
|
||||||
|
|
||||||
// "Sun, 06 Nov 1994 08:49:37 GMT".len()
|
|
||||||
pub const DATE_VALUE_LENGTH: usize = 29;
|
|
||||||
|
|
||||||
pub fn extend(dst: &mut [u8]) {
|
|
||||||
CACHED.with(|cache| {
|
|
||||||
let mut cache = cache.borrow_mut();
|
|
||||||
let now = time::get_time();
|
|
||||||
if now > cache.next_update {
|
|
||||||
cache.update(now);
|
|
||||||
}
|
|
||||||
|
|
||||||
dst.copy_from_slice(cache.buffer());
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CachedDate {
|
|
||||||
bytes: [u8; DATE_VALUE_LENGTH],
|
|
||||||
pos: usize,
|
|
||||||
next_update: time::Timespec,
|
|
||||||
}
|
|
||||||
|
|
||||||
thread_local!(static CACHED: RefCell<CachedDate> = RefCell::new(CachedDate {
|
|
||||||
bytes: [0; DATE_VALUE_LENGTH],
|
|
||||||
pos: 0,
|
|
||||||
next_update: time::Timespec::new(0, 0),
|
|
||||||
}));
|
|
||||||
|
|
||||||
impl CachedDate {
|
|
||||||
fn buffer(&self) -> &[u8] {
|
|
||||||
&self.bytes[..]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, now: time::Timespec) {
|
|
||||||
self.pos = 0;
|
|
||||||
write!(self, "{}", time::at_utc(now).rfc822()).unwrap();
|
|
||||||
assert_eq!(self.pos, DATE_VALUE_LENGTH);
|
|
||||||
self.next_update = now + Duration::seconds(1);
|
|
||||||
self.next_update.nsec = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Write for CachedDate {
|
|
||||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
|
||||||
let len = s.len();
|
|
||||||
self.bytes[self.pos..self.pos + len].copy_from_slice(s.as_bytes());
|
|
||||||
self.pos += len;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_date_len() {
|
|
||||||
assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_date() {
|
|
||||||
let mut buf1 = [0u8; 29];
|
|
||||||
extend(&mut buf1);
|
|
||||||
let mut buf2 = [0u8; 29];
|
|
||||||
extend(&mut buf2);
|
|
||||||
assert_eq!(buf1, buf2);
|
|
||||||
}
|
|
19
src/dev.rs
19
src/dev.rs
@ -1,19 +0,0 @@
|
|||||||
//! The `actix-web` prelude for library developers
|
|
||||||
//!
|
|
||||||
//! The purpose of this module is to alleviate imports of many common actix traits
|
|
||||||
//! by adding a glob import to the top of actix heavy modules:
|
|
||||||
//!
|
|
||||||
//! ```
|
|
||||||
//! # #![allow(unused_imports)]
|
|
||||||
//! use actix_web::dev::*;
|
|
||||||
//! ```
|
|
||||||
|
|
||||||
// dev specific
|
|
||||||
pub use pipeline::Pipeline;
|
|
||||||
pub use route::Handler;
|
|
||||||
pub use recognizer::RouteRecognizer;
|
|
||||||
pub use channel::{HttpChannel, HttpHandler};
|
|
||||||
|
|
||||||
pub use application::ApplicationBuilder;
|
|
||||||
pub use httpresponse::HttpResponseBuilder;
|
|
||||||
pub use cookie::CookieBuilder;
|
|
252
src/encoding.rs
252
src/encoding.rs
@ -15,7 +15,8 @@ use bytes::{Bytes, BytesMut, BufMut, Writer};
|
|||||||
|
|
||||||
use body::{Body, Binary};
|
use body::{Body, Binary};
|
||||||
use error::PayloadError;
|
use error::PayloadError;
|
||||||
use httprequest::HttpRequest;
|
use helpers::SharedBytes;
|
||||||
|
use httprequest::HttpMessage;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
use payload::{PayloadSender, PayloadWriter};
|
use payload::{PayloadSender, PayloadWriter};
|
||||||
|
|
||||||
@ -35,6 +36,14 @@ pub enum ContentEncoding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ContentEncoding {
|
impl ContentEncoding {
|
||||||
|
|
||||||
|
fn is_compression(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
ContentEncoding::Identity | ContentEncoding::Auto => false,
|
||||||
|
_ => true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn as_str(&self) -> &'static str {
|
fn as_str(&self) -> &'static str {
|
||||||
match *self {
|
match *self {
|
||||||
ContentEncoding::Br => "br",
|
ContentEncoding::Br => "br",
|
||||||
@ -125,9 +134,9 @@ impl PayloadWriter for PayloadType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum Decoder {
|
enum Decoder {
|
||||||
Deflate(DeflateDecoder<Writer<BytesMut>>),
|
Deflate(Box<DeflateDecoder<Writer<BytesMut>>>),
|
||||||
Gzip(Option<GzDecoder<Wrapper>>),
|
Gzip(Box<Option<GzDecoder<Wrapper>>>),
|
||||||
Br(BrotliDecoder<Writer<BytesMut>>),
|
Br(Box<BrotliDecoder<Writer<BytesMut>>>),
|
||||||
Identity,
|
Identity,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,10 +167,10 @@ impl EncodedPayload {
|
|||||||
pub fn new(inner: PayloadSender, enc: ContentEncoding) -> EncodedPayload {
|
pub fn new(inner: PayloadSender, enc: ContentEncoding) -> EncodedPayload {
|
||||||
let dec = match enc {
|
let dec = match enc {
|
||||||
ContentEncoding::Br => Decoder::Br(
|
ContentEncoding::Br => Decoder::Br(
|
||||||
BrotliDecoder::new(BytesMut::with_capacity(8192).writer())),
|
Box::new(BrotliDecoder::new(BytesMut::with_capacity(8192).writer()))),
|
||||||
ContentEncoding::Deflate => Decoder::Deflate(
|
ContentEncoding::Deflate => Decoder::Deflate(
|
||||||
DeflateDecoder::new(BytesMut::with_capacity(8192).writer())),
|
Box::new(DeflateDecoder::new(BytesMut::with_capacity(8192).writer()))),
|
||||||
ContentEncoding::Gzip => Decoder::Gzip(None),
|
ContentEncoding::Gzip => Decoder::Gzip(Box::new(None)),
|
||||||
_ => Decoder::Identity,
|
_ => Decoder::Identity,
|
||||||
};
|
};
|
||||||
EncodedPayload {
|
EncodedPayload {
|
||||||
@ -204,13 +213,13 @@ impl PayloadWriter for EncodedPayload {
|
|||||||
}
|
}
|
||||||
loop {
|
loop {
|
||||||
let len = self.dst.get_ref().len();
|
let len = self.dst.get_ref().len();
|
||||||
let len_buf = decoder.as_mut().unwrap().get_mut().buf.len();
|
let len_buf = decoder.as_mut().as_mut().unwrap().get_mut().buf.len();
|
||||||
|
|
||||||
if len < len_buf * 2 {
|
if len < len_buf * 2 {
|
||||||
self.dst.get_mut().reserve(len_buf * 2 - len);
|
self.dst.get_mut().reserve(len_buf * 2 - len);
|
||||||
unsafe{self.dst.get_mut().set_len(len_buf * 2)};
|
unsafe{self.dst.get_mut().set_len(len_buf * 2)};
|
||||||
}
|
}
|
||||||
match decoder.as_mut().unwrap().read(&mut self.dst.get_mut()) {
|
match decoder.as_mut().as_mut().unwrap().read(&mut self.dst.get_mut()) {
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
self.inner.feed_eof();
|
self.inner.feed_eof();
|
||||||
@ -270,14 +279,14 @@ impl PayloadWriter for EncodedPayload {
|
|||||||
Decoder::Gzip(ref mut decoder) => {
|
Decoder::Gzip(ref mut decoder) => {
|
||||||
if decoder.is_none() {
|
if decoder.is_none() {
|
||||||
let mut buf = BytesMut::new();
|
let mut buf = BytesMut::new();
|
||||||
buf.extend(data);
|
buf.extend_from_slice(&data);
|
||||||
*decoder = Some(GzDecoder::new(Wrapper{buf: buf}).unwrap());
|
*(decoder.as_mut()) = Some(GzDecoder::new(Wrapper{buf: buf}).unwrap());
|
||||||
} else {
|
} else {
|
||||||
decoder.as_mut().unwrap().get_mut().buf.extend(data);
|
decoder.as_mut().as_mut().unwrap().get_mut().buf.extend_from_slice(&data);
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let len_buf = decoder.as_mut().unwrap().get_mut().buf.len();
|
let len_buf = decoder.as_mut().as_mut().unwrap().get_mut().buf.len();
|
||||||
if len_buf == 0 {
|
if len_buf == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -287,7 +296,7 @@ impl PayloadWriter for EncodedPayload {
|
|||||||
self.dst.get_mut().reserve(len_buf * 2 - len);
|
self.dst.get_mut().reserve(len_buf * 2 - len);
|
||||||
unsafe{self.dst.get_mut().set_len(len_buf * 2)};
|
unsafe{self.dst.get_mut().set_len(len_buf * 2)};
|
||||||
}
|
}
|
||||||
match decoder.as_mut().unwrap().read(&mut self.dst.get_mut()) {
|
match decoder.as_mut().as_mut().unwrap().read(&mut self.dst.get_mut()) {
|
||||||
Ok(n) => {
|
Ok(n) => {
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
return
|
return
|
||||||
@ -328,16 +337,16 @@ impl PayloadWriter for EncodedPayload {
|
|||||||
|
|
||||||
pub(crate) struct PayloadEncoder(ContentEncoder);
|
pub(crate) struct PayloadEncoder(ContentEncoder);
|
||||||
|
|
||||||
impl Default for PayloadEncoder {
|
|
||||||
fn default() -> PayloadEncoder {
|
|
||||||
PayloadEncoder(ContentEncoder::Identity(TransferEncoding::eof()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PayloadEncoder {
|
impl PayloadEncoder {
|
||||||
|
|
||||||
pub fn new(req: &HttpRequest, resp: &mut HttpResponse) -> PayloadEncoder {
|
pub fn empty(bytes: SharedBytes) -> PayloadEncoder {
|
||||||
let version = resp.version().unwrap_or_else(|| req.version());
|
PayloadEncoder(ContentEncoder::Identity(TransferEncoding::eof(bytes)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(buf: SharedBytes, req: &HttpMessage, resp: &mut HttpResponse)
|
||||||
|
-> PayloadEncoder
|
||||||
|
{
|
||||||
|
let version = resp.version().unwrap_or_else(|| req.version);
|
||||||
let mut body = resp.replace_body(Body::Empty);
|
let mut body = resp.replace_body(Body::Empty);
|
||||||
let has_body = match body {
|
let has_body = match body {
|
||||||
Body::Empty => false,
|
Body::Empty => false,
|
||||||
@ -346,11 +355,11 @@ impl PayloadEncoder {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Enable content encoding only if response does not contain Content-Encoding header
|
// Enable content encoding only if response does not contain Content-Encoding header
|
||||||
let mut encoding = if has_body && !resp.headers.contains_key(CONTENT_ENCODING) {
|
let mut encoding = if has_body && !resp.headers().contains_key(CONTENT_ENCODING) {
|
||||||
let encoding = match *resp.content_encoding() {
|
let encoding = match *resp.content_encoding() {
|
||||||
ContentEncoding::Auto => {
|
ContentEncoding::Auto => {
|
||||||
// negotiate content-encoding
|
// negotiate content-encoding
|
||||||
if let Some(val) = req.headers().get(ACCEPT_ENCODING) {
|
if let Some(val) = req.headers.get(ACCEPT_ENCODING) {
|
||||||
if let Ok(enc) = val.to_str() {
|
if let Ok(enc) = val.to_str() {
|
||||||
AcceptEncoding::parse(enc)
|
AcceptEncoding::parse(enc)
|
||||||
} else {
|
} else {
|
||||||
@ -362,7 +371,10 @@ impl PayloadEncoder {
|
|||||||
}
|
}
|
||||||
encoding => encoding,
|
encoding => encoding,
|
||||||
};
|
};
|
||||||
resp.headers.insert(CONTENT_ENCODING, HeaderValue::from_static(encoding.as_str()));
|
if encoding.is_compression() {
|
||||||
|
resp.headers_mut().insert(
|
||||||
|
CONTENT_ENCODING, HeaderValue::from_static(encoding.as_str()));
|
||||||
|
}
|
||||||
encoding
|
encoding
|
||||||
} else {
|
} else {
|
||||||
ContentEncoding::Identity
|
ContentEncoding::Identity
|
||||||
@ -377,13 +389,13 @@ impl PayloadEncoder {
|
|||||||
if resp.chunked() {
|
if resp.chunked() {
|
||||||
error!("Chunked transfer is enabled but body is set to Empty");
|
error!("Chunked transfer is enabled but body is set to Empty");
|
||||||
}
|
}
|
||||||
resp.headers.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
|
resp.headers_mut().remove(CONTENT_LENGTH);
|
||||||
resp.headers.remove(TRANSFER_ENCODING);
|
TransferEncoding::eof(buf)
|
||||||
TransferEncoding::length(0)
|
|
||||||
},
|
},
|
||||||
Body::Binary(ref mut bytes) => {
|
Body::Binary(ref mut bytes) => {
|
||||||
if compression {
|
if compression {
|
||||||
let transfer = TransferEncoding::eof();
|
let buf = SharedBytes::default();
|
||||||
|
let transfer = TransferEncoding::eof(buf.clone());
|
||||||
let mut enc = match encoding {
|
let mut enc = match encoding {
|
||||||
ContentEncoding::Deflate => ContentEncoder::Deflate(
|
ContentEncoding::Deflate => ContentEncoder::Deflate(
|
||||||
DeflateEncoder::new(transfer, Compression::Default)),
|
DeflateEncoder::new(transfer, Compression::Default)),
|
||||||
@ -397,64 +409,55 @@ impl PayloadEncoder {
|
|||||||
// TODO return error!
|
// TODO return error!
|
||||||
let _ = enc.write(bytes.as_ref());
|
let _ = enc.write(bytes.as_ref());
|
||||||
let _ = enc.write_eof();
|
let _ = enc.write_eof();
|
||||||
let b = enc.get_mut().take();
|
|
||||||
|
|
||||||
resp.headers.insert(
|
*bytes = Binary::from(buf.get_mut().take());
|
||||||
CONTENT_LENGTH,
|
|
||||||
HeaderValue::from_str(format!("{}", b.len()).as_str()).unwrap());
|
|
||||||
*bytes = Binary::from(b);
|
|
||||||
encoding = ContentEncoding::Identity;
|
encoding = ContentEncoding::Identity;
|
||||||
TransferEncoding::eof()
|
|
||||||
} else {
|
|
||||||
resp.headers.insert(
|
|
||||||
CONTENT_LENGTH,
|
|
||||||
HeaderValue::from_str(format!("{}", bytes.len()).as_str()).unwrap());
|
|
||||||
resp.headers.remove(TRANSFER_ENCODING);
|
|
||||||
TransferEncoding::length(bytes.len() as u64)
|
|
||||||
}
|
}
|
||||||
|
resp.headers_mut().remove(CONTENT_LENGTH);
|
||||||
|
TransferEncoding::eof(buf)
|
||||||
}
|
}
|
||||||
Body::Streaming(_) | Body::StreamingContext => {
|
Body::Streaming(_) | Body::Actor(_) => {
|
||||||
if resp.chunked() {
|
if resp.upgrade() {
|
||||||
resp.headers.remove(CONTENT_LENGTH);
|
if version == Version::HTTP_2 {
|
||||||
|
error!("Connection upgrade is forbidden for HTTP/2");
|
||||||
|
} else {
|
||||||
|
resp.headers_mut().insert(
|
||||||
|
CONNECTION, HeaderValue::from_static("upgrade"));
|
||||||
|
}
|
||||||
|
if encoding != ContentEncoding::Identity {
|
||||||
|
encoding = ContentEncoding::Identity;
|
||||||
|
resp.headers_mut().remove(CONTENT_ENCODING);
|
||||||
|
}
|
||||||
|
TransferEncoding::eof(buf)
|
||||||
|
} else if resp.chunked() {
|
||||||
|
resp.headers_mut().remove(CONTENT_LENGTH);
|
||||||
if version != Version::HTTP_11 {
|
if version != Version::HTTP_11 {
|
||||||
error!("Chunked transfer encoding is forbidden for {:?}", version);
|
error!("Chunked transfer encoding is forbidden for {:?}", version);
|
||||||
}
|
}
|
||||||
if version == Version::HTTP_2 {
|
if version == Version::HTTP_2 {
|
||||||
resp.headers.remove(TRANSFER_ENCODING);
|
resp.headers_mut().remove(TRANSFER_ENCODING);
|
||||||
TransferEncoding::eof()
|
TransferEncoding::eof(buf)
|
||||||
} else {
|
} else {
|
||||||
resp.headers.insert(
|
resp.headers_mut().insert(
|
||||||
TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
|
TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
|
||||||
TransferEncoding::chunked()
|
TransferEncoding::chunked(buf)
|
||||||
}
|
}
|
||||||
} else if let Some(len) = resp.headers().get(CONTENT_LENGTH) {
|
} else if let Some(len) = resp.headers().get(CONTENT_LENGTH) {
|
||||||
// Content-Length
|
// Content-Length
|
||||||
if let Ok(s) = len.to_str() {
|
if let Ok(s) = len.to_str() {
|
||||||
if let Ok(len) = s.parse::<u64>() {
|
if let Ok(len) = s.parse::<u64>() {
|
||||||
TransferEncoding::length(len)
|
TransferEncoding::length(len, buf)
|
||||||
} else {
|
} else {
|
||||||
debug!("illegal Content-Length: {:?}", len);
|
debug!("illegal Content-Length: {:?}", len);
|
||||||
TransferEncoding::eof()
|
TransferEncoding::eof(buf)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
TransferEncoding::eof()
|
TransferEncoding::eof(buf)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
TransferEncoding::eof()
|
TransferEncoding::eof(buf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Body::Upgrade(_) | Body::UpgradeContext => {
|
|
||||||
if version == Version::HTTP_2 {
|
|
||||||
error!("Connection upgrade is forbidden for HTTP/2");
|
|
||||||
} else {
|
|
||||||
resp.headers.insert(CONNECTION, HeaderValue::from_static("upgrade"));
|
|
||||||
}
|
|
||||||
if encoding != ContentEncoding::Identity {
|
|
||||||
encoding = ContentEncoding::Identity;
|
|
||||||
resp.headers.remove(CONTENT_ENCODING);
|
|
||||||
}
|
|
||||||
TransferEncoding::eof()
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
resp.replace_body(body);
|
resp.replace_body(body);
|
||||||
|
|
||||||
@ -476,22 +479,29 @@ impl PayloadEncoder {
|
|||||||
|
|
||||||
impl PayloadEncoder {
|
impl PayloadEncoder {
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.0.get_ref().len()
|
self.0.get_ref().len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn get_mut(&mut self) -> &mut BytesMut {
|
pub fn get_mut(&mut self) -> &mut BytesMut {
|
||||||
self.0.get_mut()
|
self.0.get_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn is_eof(&self) -> bool {
|
pub fn is_eof(&self) -> bool {
|
||||||
self.0.is_eof()
|
self.0.is_eof()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||||
|
#[inline(always)]
|
||||||
pub fn write(&mut self, payload: &[u8]) -> Result<(), io::Error> {
|
pub fn write(&mut self, payload: &[u8]) -> Result<(), io::Error> {
|
||||||
self.0.write(payload)
|
self.0.write(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||||
|
#[inline(always)]
|
||||||
pub fn write_eof(&mut self) -> Result<(), io::Error> {
|
pub fn write_eof(&mut self) -> Result<(), io::Error> {
|
||||||
self.0.write_eof()
|
self.0.write_eof()
|
||||||
}
|
}
|
||||||
@ -506,6 +516,7 @@ enum ContentEncoder {
|
|||||||
|
|
||||||
impl ContentEncoder {
|
impl ContentEncoder {
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn is_eof(&self) -> bool {
|
pub fn is_eof(&self) -> bool {
|
||||||
match *self {
|
match *self {
|
||||||
ContentEncoder::Br(ref encoder) =>
|
ContentEncoder::Br(ref encoder) =>
|
||||||
@ -519,34 +530,39 @@ impl ContentEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn get_ref(&self) -> &BytesMut {
|
pub fn get_ref(&self) -> &BytesMut {
|
||||||
match *self {
|
match *self {
|
||||||
ContentEncoder::Br(ref encoder) =>
|
ContentEncoder::Br(ref encoder) =>
|
||||||
&encoder.get_ref().buffer,
|
encoder.get_ref().buffer.get_ref(),
|
||||||
ContentEncoder::Deflate(ref encoder) =>
|
ContentEncoder::Deflate(ref encoder) =>
|
||||||
&encoder.get_ref().buffer,
|
encoder.get_ref().buffer.get_ref(),
|
||||||
ContentEncoder::Gzip(ref encoder) =>
|
ContentEncoder::Gzip(ref encoder) =>
|
||||||
&encoder.get_ref().buffer,
|
encoder.get_ref().buffer.get_ref(),
|
||||||
ContentEncoder::Identity(ref encoder) =>
|
ContentEncoder::Identity(ref encoder) =>
|
||||||
&encoder.buffer,
|
encoder.buffer.get_ref(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn get_mut(&mut self) -> &mut BytesMut {
|
pub fn get_mut(&mut self) -> &mut BytesMut {
|
||||||
match *self {
|
match *self {
|
||||||
ContentEncoder::Br(ref mut encoder) =>
|
ContentEncoder::Br(ref mut encoder) =>
|
||||||
&mut encoder.get_mut().buffer,
|
encoder.get_mut().buffer.get_mut(),
|
||||||
ContentEncoder::Deflate(ref mut encoder) =>
|
ContentEncoder::Deflate(ref mut encoder) =>
|
||||||
&mut encoder.get_mut().buffer,
|
encoder.get_mut().buffer.get_mut(),
|
||||||
ContentEncoder::Gzip(ref mut encoder) =>
|
ContentEncoder::Gzip(ref mut encoder) =>
|
||||||
&mut encoder.get_mut().buffer,
|
encoder.get_mut().buffer.get_mut(),
|
||||||
ContentEncoder::Identity(ref mut encoder) =>
|
ContentEncoder::Identity(ref mut encoder) =>
|
||||||
&mut encoder.buffer,
|
encoder.buffer.get_mut(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||||
|
#[inline(always)]
|
||||||
pub fn write_eof(&mut self) -> Result<(), io::Error> {
|
pub fn write_eof(&mut self) -> Result<(), io::Error> {
|
||||||
let encoder = mem::replace(self, ContentEncoder::Identity(TransferEncoding::eof()));
|
let encoder = mem::replace(
|
||||||
|
self, ContentEncoder::Identity(TransferEncoding::eof(SharedBytes::empty())));
|
||||||
|
|
||||||
match encoder {
|
match encoder {
|
||||||
ContentEncoder::Br(encoder) => {
|
ContentEncoder::Br(encoder) => {
|
||||||
@ -587,13 +603,14 @@ impl ContentEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||||
|
#[inline(always)]
|
||||||
pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> {
|
pub fn write(&mut self, data: &[u8]) -> Result<(), io::Error> {
|
||||||
match *self {
|
match *self {
|
||||||
ContentEncoder::Br(ref mut encoder) => {
|
ContentEncoder::Br(ref mut encoder) => {
|
||||||
match encoder.write(data) {
|
match encoder.write(data) {
|
||||||
Ok(_) => {
|
Ok(_) =>
|
||||||
encoder.flush()
|
encoder.flush(),
|
||||||
},
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
trace!("Error decoding br encoding: {}", err);
|
trace!("Error decoding br encoding: {}", err);
|
||||||
Err(err)
|
Err(err)
|
||||||
@ -602,20 +619,18 @@ impl ContentEncoder {
|
|||||||
},
|
},
|
||||||
ContentEncoder::Gzip(ref mut encoder) => {
|
ContentEncoder::Gzip(ref mut encoder) => {
|
||||||
match encoder.write(data) {
|
match encoder.write(data) {
|
||||||
Ok(_) => {
|
Ok(_) =>
|
||||||
encoder.flush()
|
encoder.flush(),
|
||||||
},
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
trace!("Error decoding br encoding: {}", err);
|
trace!("Error decoding gzip encoding: {}", err);
|
||||||
Err(err)
|
Err(err)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ContentEncoder::Deflate(ref mut encoder) => {
|
ContentEncoder::Deflate(ref mut encoder) => {
|
||||||
match encoder.write(data) {
|
match encoder.write(data) {
|
||||||
Ok(_) => {
|
Ok(_) =>
|
||||||
encoder.flush()
|
encoder.flush(),
|
||||||
},
|
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
trace!("Error decoding deflate encoding: {}", err);
|
trace!("Error decoding deflate encoding: {}", err);
|
||||||
Err(err)
|
Err(err)
|
||||||
@ -623,7 +638,7 @@ impl ContentEncoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ContentEncoder::Identity(ref mut encoder) => {
|
ContentEncoder::Identity(ref mut encoder) => {
|
||||||
encoder.write_all(data)?;
|
encoder.encode(data)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -634,7 +649,7 @@ impl ContentEncoder {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(crate) struct TransferEncoding {
|
pub(crate) struct TransferEncoding {
|
||||||
kind: TransferEncodingKind,
|
kind: TransferEncodingKind,
|
||||||
buffer: BytesMut,
|
buffer: SharedBytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
@ -653,27 +668,31 @@ enum TransferEncodingKind {
|
|||||||
|
|
||||||
impl TransferEncoding {
|
impl TransferEncoding {
|
||||||
|
|
||||||
pub fn eof() -> TransferEncoding {
|
#[inline]
|
||||||
|
pub fn eof(bytes: SharedBytes) -> TransferEncoding {
|
||||||
TransferEncoding {
|
TransferEncoding {
|
||||||
kind: TransferEncodingKind::Eof,
|
kind: TransferEncodingKind::Eof,
|
||||||
buffer: BytesMut::new(),
|
buffer: bytes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn chunked() -> TransferEncoding {
|
#[inline]
|
||||||
|
pub fn chunked(bytes: SharedBytes) -> TransferEncoding {
|
||||||
TransferEncoding {
|
TransferEncoding {
|
||||||
kind: TransferEncodingKind::Chunked(false),
|
kind: TransferEncodingKind::Chunked(false),
|
||||||
buffer: BytesMut::new(),
|
buffer: bytes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn length(len: u64) -> TransferEncoding {
|
#[inline]
|
||||||
|
pub fn length(len: u64, bytes: SharedBytes) -> TransferEncoding {
|
||||||
TransferEncoding {
|
TransferEncoding {
|
||||||
kind: TransferEncodingKind::Length(len),
|
kind: TransferEncodingKind::Length(len),
|
||||||
buffer: BytesMut::new(),
|
buffer: bytes,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn is_eof(&self) -> bool {
|
pub fn is_eof(&self) -> bool {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
TransferEncodingKind::Eof => true,
|
TransferEncodingKind::Eof => true,
|
||||||
@ -685,50 +704,51 @@ impl TransferEncoding {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Encode message. Return `EOF` state of encoder
|
/// Encode message. Return `EOF` state of encoder
|
||||||
pub fn encode(&mut self, msg: &[u8]) -> bool {
|
#[inline]
|
||||||
|
pub fn encode(&mut self, msg: &[u8]) -> io::Result<bool> {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
TransferEncodingKind::Eof => {
|
TransferEncodingKind::Eof => {
|
||||||
self.buffer.extend(msg);
|
self.buffer.get_mut().extend_from_slice(msg);
|
||||||
msg.is_empty()
|
Ok(msg.is_empty())
|
||||||
},
|
},
|
||||||
TransferEncodingKind::Chunked(ref mut eof) => {
|
TransferEncodingKind::Chunked(ref mut eof) => {
|
||||||
if *eof {
|
if *eof {
|
||||||
return true;
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if msg.is_empty() {
|
if msg.is_empty() {
|
||||||
*eof = true;
|
*eof = true;
|
||||||
self.buffer.extend(b"0\r\n\r\n");
|
self.buffer.get_mut().extend_from_slice(b"0\r\n\r\n");
|
||||||
} else {
|
} else {
|
||||||
write!(self.buffer, "{:X}\r\n", msg.len()).unwrap();
|
write!(self.buffer.get_mut(), "{:X}\r\n", msg.len())
|
||||||
self.buffer.extend(msg);
|
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||||
self.buffer.extend(b"\r\n");
|
self.buffer.get_mut().extend_from_slice(msg);
|
||||||
|
self.buffer.get_mut().extend_from_slice(b"\r\n");
|
||||||
}
|
}
|
||||||
*eof
|
Ok(*eof)
|
||||||
},
|
},
|
||||||
TransferEncodingKind::Length(ref mut remaining) => {
|
TransferEncodingKind::Length(ref mut remaining) => {
|
||||||
if msg.is_empty() {
|
if msg.is_empty() {
|
||||||
return *remaining == 0
|
return Ok(*remaining == 0)
|
||||||
}
|
}
|
||||||
let max = cmp::min(*remaining, msg.len() as u64);
|
let max = cmp::min(*remaining, msg.len() as u64);
|
||||||
trace!("sized write = {}", max);
|
self.buffer.get_mut().extend_from_slice(msg[..max as usize].as_ref());
|
||||||
self.buffer.extend(msg[..max as usize].as_ref());
|
|
||||||
|
|
||||||
*remaining -= max as u64;
|
*remaining -= max as u64;
|
||||||
trace!("encoded {} bytes, remaining = {}", max, remaining);
|
Ok(*remaining == 0)
|
||||||
*remaining == 0
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Encode eof. Return `EOF` state of encoder
|
/// Encode eof. Return `EOF` state of encoder
|
||||||
|
#[inline]
|
||||||
pub fn encode_eof(&mut self) {
|
pub fn encode_eof(&mut self) {
|
||||||
match self.kind {
|
match self.kind {
|
||||||
TransferEncodingKind::Eof | TransferEncodingKind::Length(_) => (),
|
TransferEncodingKind::Eof | TransferEncodingKind::Length(_) => (),
|
||||||
TransferEncodingKind::Chunked(ref mut eof) => {
|
TransferEncodingKind::Chunked(ref mut eof) => {
|
||||||
if !*eof {
|
if !*eof {
|
||||||
*eof = true;
|
*eof = true;
|
||||||
self.buffer.extend(b"0\r\n\r\n");
|
self.buffer.get_mut().extend_from_slice(b"0\r\n\r\n");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -737,11 +757,13 @@ impl TransferEncoding {
|
|||||||
|
|
||||||
impl io::Write for TransferEncoding {
|
impl io::Write for TransferEncoding {
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
self.encode(buf);
|
self.encode(buf)?;
|
||||||
Ok(buf.len())
|
Ok(buf.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn flush(&mut self) -> io::Result<()> {
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -813,3 +835,19 @@ impl AcceptEncoding {
|
|||||||
ContentEncoding::Identity
|
ContentEncoding::Identity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_chunked_te() {
|
||||||
|
let bytes = SharedBytes::default();
|
||||||
|
let mut enc = TransferEncoding::chunked(bytes.clone());
|
||||||
|
assert!(!enc.encode(b"test").ok().unwrap());
|
||||||
|
assert!(enc.encode(b"").ok().unwrap());
|
||||||
|
assert_eq!(bytes.get_mut().take().freeze(),
|
||||||
|
Bytes::from_static(b"4\r\ntest\r\n0\r\n\r\n"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
273
src/error.rs
273
src/error.rs
@ -1,5 +1,5 @@
|
|||||||
//! Error and Result module
|
//! Error and Result module
|
||||||
use std::{fmt, result};
|
use std::{io, fmt, result};
|
||||||
use std::str::Utf8Error;
|
use std::str::Utf8Error;
|
||||||
use std::string::FromUtf8Error;
|
use std::string::FromUtf8Error;
|
||||||
use std::io::Error as IoError;
|
use std::io::Error as IoError;
|
||||||
@ -10,11 +10,13 @@ use std::error::Error as StdError;
|
|||||||
use cookie;
|
use cookie;
|
||||||
use httparse;
|
use httparse;
|
||||||
use failure::Fail;
|
use failure::Fail;
|
||||||
|
use futures::Canceled;
|
||||||
use http2::Error as Http2Error;
|
use http2::Error as Http2Error;
|
||||||
use http::{header, StatusCode, Error as HttpError};
|
use http::{header, StatusCode, Error as HttpError};
|
||||||
use http::uri::InvalidUriBytes;
|
use http::uri::InvalidUriBytes;
|
||||||
use http_range::HttpRangeParseError;
|
use http_range::HttpRangeParseError;
|
||||||
use serde_json::error::Error as JsonError;
|
use serde_json::error::Error as JsonError;
|
||||||
|
use url::ParseError as UrlParseError;
|
||||||
|
|
||||||
// re-exports
|
// re-exports
|
||||||
pub use cookie::{ParseError as CookieParseError};
|
pub use cookie::{ParseError as CookieParseError};
|
||||||
@ -28,25 +30,25 @@ use httpcodes::{HTTPBadRequest, HTTPMethodNotAllowed, HTTPExpectationFailed};
|
|||||||
///
|
///
|
||||||
/// This typedef is generally used to avoid writing out `actix_web::error::Error` directly and
|
/// This typedef is generally used to avoid writing out `actix_web::error::Error` directly and
|
||||||
/// is otherwise a direct mapping to `Result`.
|
/// is otherwise a direct mapping to `Result`.
|
||||||
pub type Result<T> = result::Result<T, Error>;
|
pub type Result<T, E=Error> = result::Result<T, E>;
|
||||||
|
|
||||||
/// General purpose actix web error
|
/// General purpose actix web error
|
||||||
#[derive(Debug)]
|
#[derive(Fail, Debug)]
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
cause: Box<ErrorResponse>,
|
cause: Box<ResponseError>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
|
|
||||||
/// Returns a reference to the underlying cause of this Error.
|
/// Returns a reference to the underlying cause of this Error.
|
||||||
// this should return &Fail but needs this https://github.com/rust-lang/rust/issues/5665
|
// this should return &Fail but needs this https://github.com/rust-lang/rust/issues/5665
|
||||||
pub fn cause(&self) -> &ErrorResponse {
|
pub fn cause(&self) -> &ResponseError {
|
||||||
self.cause.as_ref()
|
self.cause.as_ref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Error that can be converted to `HttpResponse`
|
/// Error that can be converted to `HttpResponse`
|
||||||
pub trait ErrorResponse: Fail {
|
pub trait ResponseError: Fail {
|
||||||
|
|
||||||
/// Create response for error
|
/// Create response for error
|
||||||
///
|
///
|
||||||
@ -69,8 +71,8 @@ impl From<Error> for HttpResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Error` for any error that implements `ErrorResponse`
|
/// `Error` for any error that implements `ResponseError`
|
||||||
impl<T: ErrorResponse> From<T> for Error {
|
impl<T: ResponseError> From<T> for Error {
|
||||||
fn from(err: T) -> Error {
|
fn from(err: T) -> Error {
|
||||||
Error { cause: Box::new(err) }
|
Error { cause: Box::new(err) }
|
||||||
}
|
}
|
||||||
@ -78,31 +80,39 @@ impl<T: ErrorResponse> From<T> for Error {
|
|||||||
|
|
||||||
/// Default error is `InternalServerError`
|
/// Default error is `InternalServerError`
|
||||||
#[cfg(actix_nightly)]
|
#[cfg(actix_nightly)]
|
||||||
default impl<T: StdError + Sync + Send + 'static> ErrorResponse for T {
|
default impl<T: StdError + Sync + Send + 'static> ResponseError for T {
|
||||||
fn error_response(&self) -> HttpResponse {
|
fn error_response(&self) -> HttpResponse {
|
||||||
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR, Body::Empty)
|
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR, Body::Empty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `InternalServerError` for `JsonError`
|
/// `InternalServerError` for `JsonError`
|
||||||
impl ErrorResponse for JsonError {}
|
impl ResponseError for JsonError {}
|
||||||
|
|
||||||
/// Return `InternalServerError` for `HttpError`,
|
/// Return `InternalServerError` for `HttpError`,
|
||||||
/// Response generation can return `HttpError`, so it is internal error
|
/// Response generation can return `HttpError`, so it is internal error
|
||||||
impl ErrorResponse for HttpError {}
|
impl ResponseError for HttpError {}
|
||||||
|
|
||||||
/// Return `InternalServerError` for `io::Error`
|
/// Return `InternalServerError` for `io::Error`
|
||||||
impl ErrorResponse for IoError {}
|
impl ResponseError for io::Error {
|
||||||
|
|
||||||
|
fn error_response(&self) -> HttpResponse {
|
||||||
|
match self.kind() {
|
||||||
|
io::ErrorKind::NotFound =>
|
||||||
|
HttpResponse::new(StatusCode::NOT_FOUND, Body::Empty),
|
||||||
|
io::ErrorKind::PermissionDenied =>
|
||||||
|
HttpResponse::new(StatusCode::FORBIDDEN, Body::Empty),
|
||||||
|
_ =>
|
||||||
|
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR, Body::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// `InternalServerError` for `InvalidHeaderValue`
|
/// `InternalServerError` for `InvalidHeaderValue`
|
||||||
impl ErrorResponse for header::InvalidHeaderValue {}
|
impl ResponseError for header::InvalidHeaderValue {}
|
||||||
|
|
||||||
/// Internal error
|
/// `InternalServerError` for `futures::Canceled`
|
||||||
#[derive(Fail, Debug)]
|
impl ResponseError for Canceled {}
|
||||||
#[fail(display="Unexpected task frame")]
|
|
||||||
pub struct UnexpectedTaskFrame;
|
|
||||||
|
|
||||||
impl ErrorResponse for UnexpectedTaskFrame {}
|
|
||||||
|
|
||||||
/// A set of errors that can occur during parsing HTTP streams
|
/// A set of errors that can occur during parsing HTTP streams
|
||||||
#[derive(Fail, Debug)]
|
#[derive(Fail, Debug)]
|
||||||
@ -141,7 +151,7 @@ pub enum ParseError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return `BadRequest` for `ParseError`
|
/// Return `BadRequest` for `ParseError`
|
||||||
impl ErrorResponse for ParseError {
|
impl ResponseError for ParseError {
|
||||||
fn error_response(&self) -> HttpResponse {
|
fn error_response(&self) -> HttpResponse {
|
||||||
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
|
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
|
||||||
}
|
}
|
||||||
@ -168,10 +178,8 @@ impl From<FromUtf8Error> for ParseError {
|
|||||||
impl From<httparse::Error> for ParseError {
|
impl From<httparse::Error> for ParseError {
|
||||||
fn from(err: httparse::Error) -> ParseError {
|
fn from(err: httparse::Error) -> ParseError {
|
||||||
match err {
|
match err {
|
||||||
httparse::Error::HeaderName |
|
httparse::Error::HeaderName | httparse::Error::HeaderValue |
|
||||||
httparse::Error::HeaderValue |
|
httparse::Error::NewLine | httparse::Error::Token => ParseError::Header,
|
||||||
httparse::Error::NewLine |
|
|
||||||
httparse::Error::Token => ParseError::Header,
|
|
||||||
httparse::Error::Status => ParseError::Status,
|
httparse::Error::Status => ParseError::Status,
|
||||||
httparse::Error::TooManyHeaders => ParseError::TooLarge,
|
httparse::Error::TooManyHeaders => ParseError::TooLarge,
|
||||||
httparse::Error::Version => ParseError::Version,
|
httparse::Error::Version => ParseError::Version,
|
||||||
@ -188,6 +196,12 @@ pub enum PayloadError {
|
|||||||
/// Content encoding stream corruption
|
/// Content encoding stream corruption
|
||||||
#[fail(display="Can not decode content-encoding.")]
|
#[fail(display="Can not decode content-encoding.")]
|
||||||
EncodingCorrupted,
|
EncodingCorrupted,
|
||||||
|
/// A payload reached size limit.
|
||||||
|
#[fail(display="A payload reached size limit.")]
|
||||||
|
Overflow,
|
||||||
|
/// A payload length is unknown.
|
||||||
|
#[fail(display="A payload length is unknown.")]
|
||||||
|
UnknownLength,
|
||||||
/// Parse error
|
/// Parse error
|
||||||
#[fail(display="{}", _0)]
|
#[fail(display="{}", _0)]
|
||||||
ParseError(#[cause] IoError),
|
ParseError(#[cause] IoError),
|
||||||
@ -202,8 +216,11 @@ impl From<IoError> for PayloadError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `InternalServerError` for `PayloadError`
|
||||||
|
impl ResponseError for PayloadError {}
|
||||||
|
|
||||||
/// Return `BadRequest` for `cookie::ParseError`
|
/// Return `BadRequest` for `cookie::ParseError`
|
||||||
impl ErrorResponse for cookie::ParseError {
|
impl ResponseError for cookie::ParseError {
|
||||||
fn error_response(&self) -> HttpResponse {
|
fn error_response(&self) -> HttpResponse {
|
||||||
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
|
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
|
||||||
}
|
}
|
||||||
@ -217,13 +234,13 @@ pub enum HttpRangeError {
|
|||||||
InvalidRange,
|
InvalidRange,
|
||||||
/// Returned if first-byte-pos of all of the byte-range-spec
|
/// Returned if first-byte-pos of all of the byte-range-spec
|
||||||
/// values is greater than the content size.
|
/// values is greater than the content size.
|
||||||
/// See https://github.com/golang/go/commit/aa9b3d7
|
/// See `https://github.com/golang/go/commit/aa9b3d7`
|
||||||
#[fail(display="First-byte-pos of all of the byte-range-spec values is greater than the content size")]
|
#[fail(display="First-byte-pos of all of the byte-range-spec values is greater than the content size")]
|
||||||
NoOverlap,
|
NoOverlap,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `BadRequest` for `HttpRangeError`
|
/// Return `BadRequest` for `HttpRangeError`
|
||||||
impl ErrorResponse for HttpRangeError {
|
impl ResponseError for HttpRangeError {
|
||||||
fn error_response(&self) -> HttpResponse {
|
fn error_response(&self) -> HttpResponse {
|
||||||
HttpResponse::new(
|
HttpResponse::new(
|
||||||
StatusCode::BAD_REQUEST, Body::from("Invalid Range header provided"))
|
StatusCode::BAD_REQUEST, Body::from("Invalid Range header provided"))
|
||||||
@ -272,7 +289,7 @@ impl From<PayloadError> for MultipartError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return `BadRequest` for `MultipartError`
|
/// Return `BadRequest` for `MultipartError`
|
||||||
impl ErrorResponse for MultipartError {
|
impl ResponseError for MultipartError {
|
||||||
|
|
||||||
fn error_response(&self) -> HttpResponse {
|
fn error_response(&self) -> HttpResponse {
|
||||||
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
|
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
|
||||||
@ -290,7 +307,7 @@ pub enum ExpectError {
|
|||||||
UnknownExpect,
|
UnknownExpect,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ErrorResponse for ExpectError {
|
impl ResponseError for ExpectError {
|
||||||
|
|
||||||
fn error_response(&self) -> HttpResponse {
|
fn error_response(&self) -> HttpResponse {
|
||||||
HTTPExpectationFailed.with_body("Unknown Expect")
|
HTTPExpectationFailed.with_body("Unknown Expect")
|
||||||
@ -320,7 +337,7 @@ pub enum WsHandshakeError {
|
|||||||
BadWebsocketKey,
|
BadWebsocketKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ErrorResponse for WsHandshakeError {
|
impl ResponseError for WsHandshakeError {
|
||||||
|
|
||||||
fn error_response(&self) -> HttpResponse {
|
fn error_response(&self) -> HttpResponse {
|
||||||
match *self {
|
match *self {
|
||||||
@ -340,13 +357,13 @@ impl ErrorResponse for WsHandshakeError {
|
|||||||
WsHandshakeError::UnsupportedVersion =>
|
WsHandshakeError::UnsupportedVersion =>
|
||||||
HTTPBadRequest.with_reason("Unsupported version"),
|
HTTPBadRequest.with_reason("Unsupported version"),
|
||||||
WsHandshakeError::BadWebsocketKey =>
|
WsHandshakeError::BadWebsocketKey =>
|
||||||
HTTPBadRequest.with_reason("Handshake error")
|
HTTPBadRequest.with_reason("Handshake error"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A set of errors that can occur during parsing urlencoded payloads
|
/// A set of errors that can occur during parsing urlencoded payloads
|
||||||
#[derive(Fail, Debug, PartialEq)]
|
#[derive(Fail, Debug)]
|
||||||
pub enum UrlencodedError {
|
pub enum UrlencodedError {
|
||||||
/// Can not decode chunked transfer encoding
|
/// Can not decode chunked transfer encoding
|
||||||
#[fail(display="Can not decode chunked transfer encoding")]
|
#[fail(display="Can not decode chunked transfer encoding")]
|
||||||
@ -360,16 +377,206 @@ pub enum UrlencodedError {
|
|||||||
/// Content type error
|
/// Content type error
|
||||||
#[fail(display="Content type error")]
|
#[fail(display="Content type error")]
|
||||||
ContentType,
|
ContentType,
|
||||||
|
/// Payload error
|
||||||
|
#[fail(display="Error that occur during reading payload")]
|
||||||
|
Payload(PayloadError),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return `BadRequest` for `UrlencodedError`
|
/// Return `BadRequest` for `UrlencodedError`
|
||||||
impl ErrorResponse for UrlencodedError {
|
impl ResponseError for UrlencodedError {
|
||||||
|
|
||||||
fn error_response(&self) -> HttpResponse {
|
fn error_response(&self) -> HttpResponse {
|
||||||
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
|
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<PayloadError> for UrlencodedError {
|
||||||
|
fn from(err: PayloadError) -> UrlencodedError {
|
||||||
|
UrlencodedError::Payload(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A set of errors that can occur during parsing json payloads
|
||||||
|
#[derive(Fail, Debug)]
|
||||||
|
pub enum JsonPayloadError {
|
||||||
|
/// Payload size is bigger than 256k
|
||||||
|
#[fail(display="Payload size is bigger than 256k")]
|
||||||
|
Overflow,
|
||||||
|
/// Content type error
|
||||||
|
#[fail(display="Content type error")]
|
||||||
|
ContentType,
|
||||||
|
/// Deserialize error
|
||||||
|
#[fail(display="Json deserialize error")]
|
||||||
|
Deserialize(JsonError),
|
||||||
|
/// Payload error
|
||||||
|
#[fail(display="Error that occur during reading payload")]
|
||||||
|
Payload(PayloadError),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return `BadRequest` for `UrlencodedError`
|
||||||
|
impl ResponseError for JsonPayloadError {
|
||||||
|
|
||||||
|
fn error_response(&self) -> HttpResponse {
|
||||||
|
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PayloadError> for JsonPayloadError {
|
||||||
|
fn from(err: PayloadError) -> JsonPayloadError {
|
||||||
|
JsonPayloadError::Payload(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<JsonError> for JsonPayloadError {
|
||||||
|
fn from(err: JsonError) -> JsonPayloadError {
|
||||||
|
JsonPayloadError::Deserialize(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Errors which can occur when attempting to interpret a segment string as a
|
||||||
|
/// valid path segment.
|
||||||
|
#[derive(Fail, Debug, PartialEq)]
|
||||||
|
pub enum UriSegmentError {
|
||||||
|
/// The segment started with the wrapped invalid character.
|
||||||
|
#[fail(display="The segment started with the wrapped invalid character")]
|
||||||
|
BadStart(char),
|
||||||
|
/// The segment contained the wrapped invalid character.
|
||||||
|
#[fail(display="The segment contained the wrapped invalid character")]
|
||||||
|
BadChar(char),
|
||||||
|
/// The segment ended with the wrapped invalid character.
|
||||||
|
#[fail(display="The segment ended with the wrapped invalid character")]
|
||||||
|
BadEnd(char),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return `BadRequest` for `UriSegmentError`
|
||||||
|
impl ResponseError for UriSegmentError {
|
||||||
|
|
||||||
|
fn error_response(&self) -> HttpResponse {
|
||||||
|
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Errors which can occur when attempting to generate resource uri.
|
||||||
|
#[derive(Fail, Debug, PartialEq)]
|
||||||
|
pub enum UrlGenerationError {
|
||||||
|
#[fail(display="Resource not found")]
|
||||||
|
ResourceNotFound,
|
||||||
|
#[fail(display="Not all path pattern covered")]
|
||||||
|
NotEnoughElements,
|
||||||
|
#[fail(display="Router is not available")]
|
||||||
|
RouterNotAvailable,
|
||||||
|
#[fail(display="{}", _0)]
|
||||||
|
ParseError(#[cause] UrlParseError),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `InternalServerError` for `UrlGeneratorError`
|
||||||
|
impl ResponseError for UrlGenerationError {}
|
||||||
|
|
||||||
|
impl From<UrlParseError> for UrlGenerationError {
|
||||||
|
fn from(err: UrlParseError) -> Self {
|
||||||
|
UrlGenerationError::ParseError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! ERROR_WRAP {
|
||||||
|
($type:ty, $status:expr) => {
|
||||||
|
unsafe impl<T> Sync for $type {}
|
||||||
|
unsafe impl<T> Send for $type {}
|
||||||
|
|
||||||
|
impl<T> $type {
|
||||||
|
pub fn cause(&self) -> &T {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: fmt::Debug + 'static> Fail for $type {}
|
||||||
|
impl<T: fmt::Debug + 'static> fmt::Display for $type {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{:?}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> ResponseError for $type
|
||||||
|
where T: Send + Sync + fmt::Debug + 'static,
|
||||||
|
{
|
||||||
|
fn error_response(&self) -> HttpResponse {
|
||||||
|
HttpResponse::new($status, Body::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper type that can wrap any error and generate *BAD REQUEST* response.
|
||||||
|
///
|
||||||
|
/// In following example any `io::Error` will be converted into "BAD REQUEST" response
|
||||||
|
/// as oposite to *INNTERNAL SERVER ERROR* which is defined by default.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// # use actix_web::*;
|
||||||
|
/// use actix_web::fs::NamedFile;
|
||||||
|
///
|
||||||
|
/// fn index(req: HttpRequest) -> Result<fs::NamedFile> {
|
||||||
|
/// let f = NamedFile::open("test.txt").map_err(error::ErrorBadRequest)?;
|
||||||
|
/// Ok(f)
|
||||||
|
/// }
|
||||||
|
/// # fn main() {}
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ErrorBadRequest<T>(pub T);
|
||||||
|
ERROR_WRAP!(ErrorBadRequest<T>, StatusCode::BAD_REQUEST);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Helper type that can wrap any error and generate *UNAUTHORIZED* response.
|
||||||
|
pub struct ErrorUnauthorized<T>(pub T);
|
||||||
|
ERROR_WRAP!(ErrorUnauthorized<T>, StatusCode::UNAUTHORIZED);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Helper type that can wrap any error and generate *FORBIDDEN* response.
|
||||||
|
pub struct ErrorForbidden<T>(pub T);
|
||||||
|
ERROR_WRAP!(ErrorForbidden<T>, StatusCode::FORBIDDEN);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Helper type that can wrap any error and generate *NOT FOUND* response.
|
||||||
|
pub struct ErrorNotFound<T>(pub T);
|
||||||
|
ERROR_WRAP!(ErrorNotFound<T>, StatusCode::NOT_FOUND);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Helper type that can wrap any error and generate *METHOD NOT ALLOWED* response.
|
||||||
|
pub struct ErrorMethodNotAllowed<T>(pub T);
|
||||||
|
ERROR_WRAP!(ErrorMethodNotAllowed<T>, StatusCode::METHOD_NOT_ALLOWED);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Helper type that can wrap any error and generate *REQUEST TIMEOUT* response.
|
||||||
|
pub struct ErrorRequestTimeout<T>(pub T);
|
||||||
|
ERROR_WRAP!(ErrorRequestTimeout<T>, StatusCode::REQUEST_TIMEOUT);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Helper type that can wrap any error and generate *CONFLICT* response.
|
||||||
|
pub struct ErrorConflict<T>(pub T);
|
||||||
|
ERROR_WRAP!(ErrorConflict<T>, StatusCode::CONFLICT);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Helper type that can wrap any error and generate *GONE* response.
|
||||||
|
pub struct ErrorGone<T>(pub T);
|
||||||
|
ERROR_WRAP!(ErrorGone<T>, StatusCode::GONE);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Helper type that can wrap any error and generate *PRECONDITION FAILED* response.
|
||||||
|
pub struct ErrorPreconditionFailed<T>(pub T);
|
||||||
|
ERROR_WRAP!(ErrorPreconditionFailed<T>, StatusCode::PRECONDITION_FAILED);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Helper type that can wrap any error and generate *EXPECTATION FAILED* response.
|
||||||
|
pub struct ErrorExpectationFailed<T>(pub T);
|
||||||
|
ERROR_WRAP!(ErrorExpectationFailed<T>, StatusCode::EXPECTATION_FAILED);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Helper type that can wrap any error and generate *INTERNAL SERVER ERROR* response.
|
||||||
|
pub struct ErrorInternalServerError<T>(pub T);
|
||||||
|
ERROR_WRAP!(ErrorInternalServerError<T>, StatusCode::INTERNAL_SERVER_ERROR);
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
|
318
src/fs.rs
Normal file
318
src/fs.rs
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
//! Static files support.
|
||||||
|
|
||||||
|
// //! TODO: needs to re-implement actual files handling, current impl blocks
|
||||||
|
use std::io;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::fmt::Write;
|
||||||
|
use std::fs::{File, DirEntry};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
use mime_guess::get_mime_type;
|
||||||
|
use param::FromParam;
|
||||||
|
use handler::{Handler, Responder};
|
||||||
|
use httprequest::HttpRequest;
|
||||||
|
use httpresponse::HttpResponse;
|
||||||
|
use httpcodes::HTTPOk;
|
||||||
|
|
||||||
|
/// A file with an associated name; responds with the Content-Type based on the
|
||||||
|
/// file extension.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NamedFile(PathBuf, File);
|
||||||
|
|
||||||
|
impl NamedFile {
|
||||||
|
/// Attempts to open a file in read-only mode.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use actix_web::fs::NamedFile;
|
||||||
|
///
|
||||||
|
/// # #[allow(unused_variables)]
|
||||||
|
/// let file = NamedFile::open("foo.txt");
|
||||||
|
/// ```
|
||||||
|
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<NamedFile> {
|
||||||
|
let file = File::open(path.as_ref())?;
|
||||||
|
Ok(NamedFile(path.as_ref().to_path_buf(), file))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns reference to the underlying `File` object.
|
||||||
|
#[inline]
|
||||||
|
pub fn file(&self) -> &File {
|
||||||
|
&self.1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve the path of this file.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use std::io;
|
||||||
|
/// use actix_web::fs::NamedFile;
|
||||||
|
///
|
||||||
|
/// # #[allow(dead_code)]
|
||||||
|
/// # fn path() -> io::Result<()> {
|
||||||
|
/// let file = NamedFile::open("test.txt")?;
|
||||||
|
/// assert_eq!(file.path().as_os_str(), "foo.txt");
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn path(&self) -> &Path {
|
||||||
|
self.0.as_path()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for NamedFile {
|
||||||
|
type Target = File;
|
||||||
|
|
||||||
|
fn deref(&self) -> &File {
|
||||||
|
&self.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for NamedFile {
|
||||||
|
fn deref_mut(&mut self) -> &mut File {
|
||||||
|
&mut self.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Responder for NamedFile {
|
||||||
|
type Item = HttpResponse;
|
||||||
|
type Error = io::Error;
|
||||||
|
|
||||||
|
fn respond_to(mut self, _: HttpRequest) -> Result<HttpResponse, io::Error> {
|
||||||
|
let mut resp = HTTPOk.build();
|
||||||
|
if let Some(ext) = self.path().extension() {
|
||||||
|
let mime = get_mime_type(&ext.to_string_lossy());
|
||||||
|
resp.content_type(format!("{}", mime).as_str());
|
||||||
|
}
|
||||||
|
let mut data = Vec::new();
|
||||||
|
let _ = self.1.read_to_end(&mut data);
|
||||||
|
Ok(resp.body(data).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A directory; responds with the generated directory listing.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Directory{
|
||||||
|
base: PathBuf,
|
||||||
|
path: PathBuf
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Directory {
|
||||||
|
pub fn new(base: PathBuf, path: PathBuf) -> Directory {
|
||||||
|
Directory {
|
||||||
|
base: base,
|
||||||
|
path: path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_list(&self, entry: &io::Result<DirEntry>) -> bool {
|
||||||
|
if let Ok(ref entry) = *entry {
|
||||||
|
if let Some(name) = entry.file_name().to_str() {
|
||||||
|
if name.starts_with('.') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Ok(ref md) = entry.metadata() {
|
||||||
|
let ft = md.file_type();
|
||||||
|
return ft.is_dir() || ft.is_file() || ft.is_symlink()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Responder for Directory {
|
||||||
|
type Item = HttpResponse;
|
||||||
|
type Error = io::Error;
|
||||||
|
|
||||||
|
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, io::Error> {
|
||||||
|
let index_of = format!("Index of {}", req.path());
|
||||||
|
let mut body = String::new();
|
||||||
|
let base = Path::new(req.path());
|
||||||
|
|
||||||
|
for entry in self.path.read_dir()? {
|
||||||
|
if self.can_list(&entry) {
|
||||||
|
let entry = entry.unwrap();
|
||||||
|
let p = match entry.path().strip_prefix(&self.base) {
|
||||||
|
Ok(p) => base.join(p),
|
||||||
|
Err(_) => continue
|
||||||
|
};
|
||||||
|
// show file url as relative to static path
|
||||||
|
let file_url = format!("{}", p.to_string_lossy());
|
||||||
|
|
||||||
|
// if file is a directory, add '/' to the end of the name
|
||||||
|
if let Ok(metadata) = entry.metadata() {
|
||||||
|
if metadata.is_dir() {
|
||||||
|
let _ = write!(body, "<li><a href=\"{}\">{}/</a></li>",
|
||||||
|
file_url, entry.file_name().to_string_lossy());
|
||||||
|
} else {
|
||||||
|
let _ = write!(body, "<li><a href=\"{}\">{}</a></li>",
|
||||||
|
file_url, entry.file_name().to_string_lossy());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let html = format!("<html>\
|
||||||
|
<head><title>{}</title></head>\
|
||||||
|
<body><h1>{}</h1>\
|
||||||
|
<ul>\
|
||||||
|
{}\
|
||||||
|
</ul></body>\n</html>", index_of, index_of, body);
|
||||||
|
Ok(HTTPOk.build()
|
||||||
|
.content_type("text/html; charset=utf-8")
|
||||||
|
.body(html).unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This enum represents all filesystem elements.
|
||||||
|
pub enum FilesystemElement {
|
||||||
|
File(NamedFile),
|
||||||
|
Directory(Directory),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Responder for FilesystemElement {
|
||||||
|
type Item = HttpResponse;
|
||||||
|
type Error = io::Error;
|
||||||
|
|
||||||
|
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, io::Error> {
|
||||||
|
match self {
|
||||||
|
FilesystemElement::File(file) => file.respond_to(req),
|
||||||
|
FilesystemElement::Directory(dir) => dir.respond_to(req),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Static files handling
|
||||||
|
///
|
||||||
|
/// `StaticFile` handler must be registered with `Application::handler()` method,
|
||||||
|
/// because `StaticFile` handler requires access sub-path information.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// use actix_web::{fs, Application};
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let app = Application::new()
|
||||||
|
/// .handler("/static", fs::StaticFiles::new(".", true))
|
||||||
|
/// .finish();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub struct StaticFiles {
|
||||||
|
directory: PathBuf,
|
||||||
|
accessible: bool,
|
||||||
|
show_index: bool,
|
||||||
|
_chunk_size: usize,
|
||||||
|
_follow_symlinks: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StaticFiles {
|
||||||
|
/// Create new `StaticFiles` instance
|
||||||
|
///
|
||||||
|
/// `dir` - base directory
|
||||||
|
///
|
||||||
|
/// `index` - show index for directory
|
||||||
|
pub fn new<D: Into<PathBuf>>(dir: D, index: bool) -> StaticFiles {
|
||||||
|
let dir = dir.into();
|
||||||
|
|
||||||
|
let (dir, access) = match dir.canonicalize() {
|
||||||
|
Ok(dir) => {
|
||||||
|
if dir.is_dir() {
|
||||||
|
(dir, true)
|
||||||
|
} else {
|
||||||
|
warn!("Is not directory `{:?}`", dir);
|
||||||
|
(dir, false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
warn!("Static files directory `{:?}` error: {}", dir, err);
|
||||||
|
(dir, false)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
StaticFiles {
|
||||||
|
directory: dir,
|
||||||
|
accessible: access,
|
||||||
|
show_index: index,
|
||||||
|
_chunk_size: 0,
|
||||||
|
_follow_symlinks: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Handler<S> for StaticFiles {
|
||||||
|
type Result = Result<FilesystemElement, io::Error>;
|
||||||
|
|
||||||
|
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
||||||
|
if !self.accessible {
|
||||||
|
Err(io::Error::new(io::ErrorKind::NotFound, "not found"))
|
||||||
|
} else {
|
||||||
|
let path = if let Some(path) = req.match_info().get("tail") {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
return Err(io::Error::new(io::ErrorKind::NotFound, "not found"))
|
||||||
|
};
|
||||||
|
|
||||||
|
let relpath = PathBuf::from_param(path)
|
||||||
|
.map_err(|_| io::Error::new(io::ErrorKind::NotFound, "not found"))?;
|
||||||
|
|
||||||
|
// full filepath
|
||||||
|
let path = self.directory.join(&relpath).canonicalize()?;
|
||||||
|
|
||||||
|
if path.is_dir() {
|
||||||
|
if self.show_index {
|
||||||
|
Ok(FilesystemElement::Directory(Directory::new(self.directory.clone(), path)))
|
||||||
|
} else {
|
||||||
|
Err(io::Error::new(io::ErrorKind::NotFound, "not found"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(FilesystemElement::File(NamedFile::open(path)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use http::header;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_named_file() {
|
||||||
|
assert!(NamedFile::open("test--").is_err());
|
||||||
|
let mut file = NamedFile::open("Cargo.toml").unwrap();
|
||||||
|
{ file.file();
|
||||||
|
let _f: &File = &file; }
|
||||||
|
{ let _f: &mut File = &mut file; }
|
||||||
|
|
||||||
|
let resp = file.respond_to(HttpRequest::default()).unwrap();
|
||||||
|
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/x-toml")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_static_files() {
|
||||||
|
let mut st = StaticFiles::new(".", true);
|
||||||
|
st.accessible = false;
|
||||||
|
assert!(st.handle(HttpRequest::default()).is_err());
|
||||||
|
|
||||||
|
st.accessible = true;
|
||||||
|
st.show_index = false;
|
||||||
|
assert!(st.handle(HttpRequest::default()).is_err());
|
||||||
|
|
||||||
|
let mut req = HttpRequest::default();
|
||||||
|
req.match_info_mut().add("tail", "");
|
||||||
|
|
||||||
|
st.show_index = true;
|
||||||
|
let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap();
|
||||||
|
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/html; charset=utf-8");
|
||||||
|
assert!(resp.body().is_binary());
|
||||||
|
assert!(format!("{:?}", resp.body()).contains("README.md"));
|
||||||
|
}
|
||||||
|
}
|
214
src/h1writer.rs
214
src/h1writer.rs
@ -1,14 +1,15 @@
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::fmt::Write;
|
use bytes::BufMut;
|
||||||
use futures::{Async, Poll};
|
use futures::{Async, Poll};
|
||||||
use tokio_io::AsyncWrite;
|
use tokio_io::AsyncWrite;
|
||||||
use http::{Version, StatusCode};
|
use http::Version;
|
||||||
use http::header::{HeaderValue, CONNECTION, CONTENT_TYPE, DATE};
|
use http::header::{HeaderValue, CONNECTION, DATE};
|
||||||
|
|
||||||
use date;
|
use helpers;
|
||||||
use body::Body;
|
use body::Body;
|
||||||
|
use helpers::SharedBytes;
|
||||||
use encoding::PayloadEncoder;
|
use encoding::PayloadEncoder;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpMessage;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
|
|
||||||
const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific
|
const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific
|
||||||
@ -16,58 +17,67 @@ const MAX_WRITE_BUFFER_SIZE: usize = 65_536; // max buffer size 64k
|
|||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum WriterState {
|
pub enum WriterState {
|
||||||
Done,
|
Done,
|
||||||
Pause,
|
Pause,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send stream
|
/// Send stream
|
||||||
pub(crate) trait Writer {
|
pub trait Writer {
|
||||||
fn written(&self) -> u64;
|
fn written(&self) -> u64;
|
||||||
|
|
||||||
fn start(&mut self, req: &mut HttpRequest, resp: &mut HttpResponse)
|
fn start(&mut self, req: &mut HttpMessage, resp: &mut HttpResponse)
|
||||||
-> Result<WriterState, io::Error>;
|
-> Result<WriterState, io::Error>;
|
||||||
|
|
||||||
fn write(&mut self, payload: &[u8]) -> Result<WriterState, io::Error>;
|
fn write(&mut self, payload: &[u8]) -> Result<WriterState, io::Error>;
|
||||||
|
|
||||||
fn write_eof(&mut self) -> Result<WriterState, io::Error>;
|
fn write_eof(&mut self) -> Result<WriterState, io::Error>;
|
||||||
|
|
||||||
fn poll_complete(&mut self) -> Poll<(), io::Error>;
|
fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
struct Flags: u8 {
|
||||||
|
const STARTED = 0b0000_0001;
|
||||||
|
const UPGRADE = 0b0000_0010;
|
||||||
|
const KEEPALIVE = 0b0000_0100;
|
||||||
|
const DISCONNECTED = 0b0000_1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct H1Writer<T: AsyncWrite> {
|
pub(crate) struct H1Writer<T: AsyncWrite> {
|
||||||
stream: Option<T>,
|
flags: Flags,
|
||||||
started: bool,
|
stream: T,
|
||||||
encoder: PayloadEncoder,
|
encoder: PayloadEncoder,
|
||||||
upgrade: bool,
|
|
||||||
keepalive: bool,
|
|
||||||
disconnected: bool,
|
|
||||||
written: u64,
|
written: u64,
|
||||||
headers_size: u64,
|
headers_size: u32,
|
||||||
|
buffer: SharedBytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: AsyncWrite> H1Writer<T> {
|
impl<T: AsyncWrite> H1Writer<T> {
|
||||||
|
|
||||||
pub fn new(stream: T) -> H1Writer<T> {
|
pub fn new(stream: T, buf: SharedBytes) -> H1Writer<T> {
|
||||||
H1Writer {
|
H1Writer {
|
||||||
stream: Some(stream),
|
flags: Flags::empty(),
|
||||||
started: false,
|
stream: stream,
|
||||||
encoder: PayloadEncoder::default(),
|
encoder: PayloadEncoder::empty(buf.clone()),
|
||||||
upgrade: false,
|
|
||||||
keepalive: false,
|
|
||||||
disconnected: false,
|
|
||||||
written: 0,
|
written: 0,
|
||||||
headers_size: 0,
|
headers_size: 0,
|
||||||
|
buffer: buf,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mut(&mut self) -> &mut T {
|
pub fn get_mut(&mut self) -> &mut T {
|
||||||
self.stream.as_mut().unwrap()
|
&mut self.stream
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unwrap(&mut self) -> T {
|
pub fn reset(&mut self) {
|
||||||
self.stream.take().unwrap()
|
self.written = 0;
|
||||||
|
self.flags = Flags::empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_inner(self) -> T {
|
||||||
|
self.stream
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn disconnected(&mut self) {
|
pub fn disconnected(&mut self) {
|
||||||
@ -75,28 +85,25 @@ impl<T: AsyncWrite> H1Writer<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn keepalive(&self) -> bool {
|
pub fn keepalive(&self) -> bool {
|
||||||
self.keepalive && !self.upgrade
|
self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_to_stream(&mut self) -> Result<WriterState, io::Error> {
|
fn write_to_stream(&mut self) -> Result<WriterState, io::Error> {
|
||||||
let buffer = self.encoder.get_mut();
|
let buffer = self.encoder.get_mut();
|
||||||
|
|
||||||
if let Some(ref mut stream) = self.stream {
|
while !buffer.is_empty() {
|
||||||
while !buffer.is_empty() {
|
match self.stream.write(buffer.as_ref()) {
|
||||||
match stream.write(buffer.as_ref()) {
|
Ok(n) => {
|
||||||
Ok(n) => {
|
let _ = buffer.split_to(n);
|
||||||
buffer.split_to(n);
|
},
|
||||||
self.written += n as u64;
|
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||||
},
|
if buffer.len() > MAX_WRITE_BUFFER_SIZE {
|
||||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
return Ok(WriterState::Pause)
|
||||||
if buffer.len() > MAX_WRITE_BUFFER_SIZE {
|
} else {
|
||||||
return Ok(WriterState::Pause)
|
return Ok(WriterState::Done)
|
||||||
} else {
|
|
||||||
return Ok(WriterState::Done)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(err) => return Err(err),
|
|
||||||
}
|
}
|
||||||
|
Err(err) => return Err(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(WriterState::Done)
|
Ok(WriterState::Done)
|
||||||
@ -106,97 +113,93 @@ impl<T: AsyncWrite> H1Writer<T> {
|
|||||||
impl<T: AsyncWrite> Writer for H1Writer<T> {
|
impl<T: AsyncWrite> Writer for H1Writer<T> {
|
||||||
|
|
||||||
fn written(&self) -> u64 {
|
fn written(&self) -> u64 {
|
||||||
if self.written > self.headers_size {
|
self.written
|
||||||
self.written - self.headers_size
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start(&mut self, req: &mut HttpRequest, msg: &mut HttpResponse)
|
fn start(&mut self, req: &mut HttpMessage, msg: &mut HttpResponse)
|
||||||
-> Result<WriterState, io::Error>
|
-> Result<WriterState, io::Error>
|
||||||
{
|
{
|
||||||
trace!("Prepare response with status: {:?}", msg.status);
|
|
||||||
|
|
||||||
// prepare task
|
// prepare task
|
||||||
self.started = true;
|
self.flags.insert(Flags::STARTED);
|
||||||
self.encoder = PayloadEncoder::new(req, msg);
|
self.encoder = PayloadEncoder::new(self.buffer.clone(), req, msg);
|
||||||
self.keepalive = msg.keep_alive().unwrap_or_else(|| req.keep_alive());
|
if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) {
|
||||||
|
self.flags.insert(Flags::KEEPALIVE);
|
||||||
|
}
|
||||||
|
|
||||||
// Connection upgrade
|
// Connection upgrade
|
||||||
let version = msg.version().unwrap_or_else(|| req.version());
|
let version = msg.version().unwrap_or_else(|| req.version);
|
||||||
if msg.upgrade() {
|
if msg.upgrade() {
|
||||||
msg.headers.insert(CONNECTION, HeaderValue::from_static("upgrade"));
|
msg.headers_mut().insert(CONNECTION, HeaderValue::from_static("upgrade"));
|
||||||
}
|
}
|
||||||
// keep-alive
|
// keep-alive
|
||||||
else if self.keepalive {
|
else if self.flags.contains(Flags::KEEPALIVE) {
|
||||||
if version < Version::HTTP_11 {
|
if version < Version::HTTP_11 {
|
||||||
msg.headers.insert(CONNECTION, HeaderValue::from_static("keep-alive"));
|
msg.headers_mut().insert(CONNECTION, HeaderValue::from_static("keep-alive"));
|
||||||
}
|
}
|
||||||
} else if version >= Version::HTTP_11 {
|
} else if version >= Version::HTTP_11 {
|
||||||
msg.headers.insert(CONNECTION, HeaderValue::from_static("close"));
|
msg.headers_mut().insert(CONNECTION, HeaderValue::from_static("close"));
|
||||||
}
|
}
|
||||||
|
let body = msg.replace_body(Body::Empty);
|
||||||
|
|
||||||
// render message
|
// render message
|
||||||
{
|
{
|
||||||
let buffer = self.encoder.get_mut();
|
let mut buffer = self.encoder.get_mut();
|
||||||
if let Body::Binary(ref bytes) = *msg.body() {
|
if let Body::Binary(ref bytes) = body {
|
||||||
buffer.reserve(100 + msg.headers.len() * AVERAGE_HEADER_SIZE + bytes.len());
|
buffer.reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len());
|
||||||
} else {
|
} else {
|
||||||
buffer.reserve(100 + msg.headers.len() * AVERAGE_HEADER_SIZE);
|
buffer.reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if version == Version::HTTP_11 && msg.status == StatusCode::OK {
|
// status line
|
||||||
buffer.extend(b"HTTP/1.1 200 OK\r\n");
|
helpers::write_status_line(version, msg.status().as_u16(), &mut buffer);
|
||||||
|
buffer.extend_from_slice(msg.reason().as_bytes());
|
||||||
|
|
||||||
|
match body {
|
||||||
|
Body::Empty =>
|
||||||
|
buffer.extend_from_slice(b"\r\ncontent-length: 0\r\n"),
|
||||||
|
Body::Binary(ref bytes) =>
|
||||||
|
helpers::write_content_length(bytes.len(), &mut buffer),
|
||||||
|
_ =>
|
||||||
|
buffer.extend_from_slice(b"\r\n"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// write headers
|
||||||
|
for (key, value) in msg.headers() {
|
||||||
|
let v = value.as_ref();
|
||||||
|
let k = key.as_str().as_bytes();
|
||||||
|
buffer.reserve(k.len() + v.len() + 4);
|
||||||
|
buffer.put_slice(k);
|
||||||
|
buffer.put_slice(b": ");
|
||||||
|
buffer.put_slice(v);
|
||||||
|
buffer.put_slice(b"\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// using helpers::date is quite a lot faster
|
||||||
|
if !msg.headers().contains_key(DATE) {
|
||||||
|
helpers::date(&mut buffer);
|
||||||
} else {
|
} else {
|
||||||
let _ = write!(buffer, "{:?} {}\r\n", version, msg.status);
|
// msg eof
|
||||||
|
buffer.extend_from_slice(b"\r\n");
|
||||||
}
|
}
|
||||||
for (key, value) in &msg.headers {
|
self.headers_size = buffer.len() as u32;
|
||||||
let t: &[u8] = key.as_ref();
|
|
||||||
buffer.extend(t);
|
|
||||||
buffer.extend(b": ");
|
|
||||||
buffer.extend(value.as_ref());
|
|
||||||
buffer.extend(b"\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// using http::h1::date is quite a lot faster than generating
|
|
||||||
// a unique Date header each time like req/s goes up about 10%
|
|
||||||
if !msg.headers.contains_key(DATE) {
|
|
||||||
buffer.reserve(date::DATE_VALUE_LENGTH + 8);
|
|
||||||
buffer.extend(b"Date: ");
|
|
||||||
let mut bytes = [0u8; 29];
|
|
||||||
date::extend(&mut bytes[..]);
|
|
||||||
buffer.extend(&bytes);
|
|
||||||
buffer.extend(b"\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
// default content-type
|
|
||||||
if !msg.headers.contains_key(CONTENT_TYPE) {
|
|
||||||
buffer.extend(b"ContentType: application/octet-stream\r\n".as_ref());
|
|
||||||
}
|
|
||||||
|
|
||||||
// msg eof
|
|
||||||
buffer.extend(b"\r\n");
|
|
||||||
self.headers_size = buffer.len() as u64;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
trace!("Response: {:?}", msg);
|
if let Body::Binary(bytes) = body {
|
||||||
|
self.written = bytes.len() as u64;
|
||||||
if msg.body().is_binary() {
|
self.encoder.write(bytes.as_ref())?;
|
||||||
let body = msg.replace_body(Body::Empty);
|
} else {
|
||||||
if let Body::Binary(bytes) = body {
|
msg.replace_body(body);
|
||||||
self.encoder.write(bytes.as_ref())?;
|
|
||||||
return Ok(WriterState::Done)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(WriterState::Done)
|
Ok(WriterState::Done)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, payload: &[u8]) -> Result<WriterState, io::Error> {
|
fn write(&mut self, payload: &[u8]) -> Result<WriterState, io::Error> {
|
||||||
if !self.disconnected {
|
self.written += payload.len() as u64;
|
||||||
if self.started {
|
if !self.flags.contains(Flags::DISCONNECTED) {
|
||||||
|
if self.flags.contains(Flags::STARTED) {
|
||||||
// TODO: add warning, write after EOF
|
// TODO: add warning, write after EOF
|
||||||
self.encoder.write(payload)?;
|
self.encoder.write(payload)?;
|
||||||
|
return Ok(WriterState::Done)
|
||||||
} else {
|
} else {
|
||||||
// might be response to EXCEPT
|
// might be response to EXCEPT
|
||||||
self.encoder.get_mut().extend_from_slice(payload)
|
self.encoder.get_mut().extend_from_slice(payload)
|
||||||
@ -214,7 +217,6 @@ impl<T: AsyncWrite> Writer for H1Writer<T> {
|
|||||||
self.encoder.write_eof()?;
|
self.encoder.write_eof()?;
|
||||||
|
|
||||||
if !self.encoder.is_eof() {
|
if !self.encoder.is_eof() {
|
||||||
//debug!("last payload item, but it is not EOF ");
|
|
||||||
Err(io::Error::new(io::ErrorKind::Other,
|
Err(io::Error::new(io::ErrorKind::Other,
|
||||||
"Last payload item, but eof is not reached"))
|
"Last payload item, but eof is not reached"))
|
||||||
} else if self.encoder.len() > MAX_WRITE_BUFFER_SIZE {
|
} else if self.encoder.len() > MAX_WRITE_BUFFER_SIZE {
|
||||||
@ -224,9 +226,15 @@ impl<T: AsyncWrite> Writer for H1Writer<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_complete(&mut self) -> Poll<(), io::Error> {
|
fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error> {
|
||||||
match self.write_to_stream() {
|
match self.write_to_stream() {
|
||||||
Ok(WriterState::Done) => Ok(Async::Ready(())),
|
Ok(WriterState::Done) => {
|
||||||
|
if shutdown {
|
||||||
|
self.stream.shutdown()
|
||||||
|
} else {
|
||||||
|
Ok(Async::Ready(()))
|
||||||
|
}
|
||||||
|
},
|
||||||
Ok(WriterState::Pause) => Ok(Async::NotReady),
|
Ok(WriterState::Pause) => Ok(Async::NotReady),
|
||||||
Err(err) => Err(err)
|
Err(err) => Err(err)
|
||||||
}
|
}
|
||||||
|
142
src/h2.rs
142
src/h2.rs
@ -8,7 +8,7 @@ use std::collections::VecDeque;
|
|||||||
use actix::Arbiter;
|
use actix::Arbiter;
|
||||||
use http::request::Parts;
|
use http::request::Parts;
|
||||||
use http2::{Reason, RecvStream};
|
use http2::{Reason, RecvStream};
|
||||||
use http2::server::{Server, Handshake, Respond};
|
use http2::server::{self, Connection, Handshake, SendResponse};
|
||||||
use bytes::{Buf, Bytes};
|
use bytes::{Buf, Bytes};
|
||||||
use futures::{Async, Poll, Future, Stream};
|
use futures::{Async, Poll, Future, Stream};
|
||||||
use tokio_io::{AsyncRead, AsyncWrite};
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
@ -16,29 +16,35 @@ use tokio_core::reactor::Timeout;
|
|||||||
|
|
||||||
use pipeline::Pipeline;
|
use pipeline::Pipeline;
|
||||||
use h2writer::H2Writer;
|
use h2writer::H2Writer;
|
||||||
use channel::HttpHandler;
|
use worker::WorkerSettings;
|
||||||
|
use channel::{HttpHandler, HttpHandlerTask};
|
||||||
use error::PayloadError;
|
use error::PayloadError;
|
||||||
use encoding::PayloadType;
|
use encoding::PayloadType;
|
||||||
use httpcodes::HTTPNotFound;
|
use httpcodes::HTTPNotFound;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use payload::{Payload, PayloadWriter};
|
use payload::{Payload, PayloadWriter};
|
||||||
|
|
||||||
const KEEPALIVE_PERIOD: u64 = 15; // seconds
|
bitflags! {
|
||||||
|
struct Flags: u8 {
|
||||||
|
const DISCONNECTED = 0b0000_0010;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// HTTP/2 Transport
|
||||||
pub(crate) struct Http2<T, H>
|
pub(crate) struct Http2<T, H>
|
||||||
where T: AsyncRead + AsyncWrite + 'static, H: 'static
|
where T: AsyncRead + AsyncWrite + 'static, H: 'static
|
||||||
{
|
{
|
||||||
router: Rc<Vec<H>>,
|
flags: Flags,
|
||||||
|
settings: Rc<WorkerSettings<H>>,
|
||||||
addr: Option<SocketAddr>,
|
addr: Option<SocketAddr>,
|
||||||
state: State<IoWrapper<T>>,
|
state: State<IoWrapper<T>>,
|
||||||
disconnected: bool,
|
|
||||||
tasks: VecDeque<Entry>,
|
tasks: VecDeque<Entry>,
|
||||||
keepalive_timer: Option<Timeout>,
|
keepalive_timer: Option<Timeout>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum State<T: AsyncRead + AsyncWrite> {
|
enum State<T: AsyncRead + AsyncWrite> {
|
||||||
Handshake(Handshake<T, Bytes>),
|
Handshake(Handshake<T, Bytes>),
|
||||||
Server(Server<T, Bytes>),
|
Server(Connection<T, Bytes>),
|
||||||
Empty,
|
Empty,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,17 +52,28 @@ impl<T, H> Http2<T, H>
|
|||||||
where T: AsyncRead + AsyncWrite + 'static,
|
where T: AsyncRead + AsyncWrite + 'static,
|
||||||
H: HttpHandler + 'static
|
H: HttpHandler + 'static
|
||||||
{
|
{
|
||||||
pub fn new(stream: T, addr: Option<SocketAddr>, router: Rc<Vec<H>>, buf: Bytes) -> Self {
|
pub fn new(h: Rc<WorkerSettings<H>>, io: T, addr: Option<SocketAddr>, buf: Bytes) -> Self
|
||||||
Http2{ router: router,
|
{
|
||||||
|
Http2{ flags: Flags::empty(),
|
||||||
|
settings: h,
|
||||||
addr: addr,
|
addr: addr,
|
||||||
disconnected: false,
|
|
||||||
tasks: VecDeque::new(),
|
tasks: VecDeque::new(),
|
||||||
state: State::Handshake(
|
state: State::Handshake(
|
||||||
Server::handshake(IoWrapper{unread: Some(buf), inner: stream})),
|
server::handshake(IoWrapper{unread: Some(buf), inner: io})),
|
||||||
keepalive_timer: None,
|
keepalive_timer: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn shutdown(&mut self) {
|
||||||
|
self.state = State::Empty;
|
||||||
|
self.tasks.clear();
|
||||||
|
self.keepalive_timer.take();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn settings(&self) -> &WorkerSettings<H> {
|
||||||
|
self.settings.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn poll(&mut self) -> Poll<(), ()> {
|
pub fn poll(&mut self) -> Poll<(), ()> {
|
||||||
// server
|
// server
|
||||||
if let State::Server(ref mut server) = self.state {
|
if let State::Server(ref mut server) = self.state {
|
||||||
@ -80,33 +97,33 @@ impl<T, H> Http2<T, H>
|
|||||||
// read payload
|
// read payload
|
||||||
item.poll_payload();
|
item.poll_payload();
|
||||||
|
|
||||||
if !item.eof {
|
if !item.flags.contains(EntryFlags::EOF) {
|
||||||
match item.task.poll_io(&mut item.stream) {
|
match item.task.poll_io(&mut item.stream) {
|
||||||
Ok(Async::Ready(ready)) => {
|
Ok(Async::Ready(ready)) => {
|
||||||
item.eof = true;
|
item.flags.insert(EntryFlags::EOF);
|
||||||
if ready {
|
if ready {
|
||||||
item.finished = true;
|
item.flags.insert(EntryFlags::FINISHED);
|
||||||
}
|
}
|
||||||
not_ready = false;
|
not_ready = false;
|
||||||
},
|
},
|
||||||
Ok(Async::NotReady) => (),
|
Ok(Async::NotReady) => (),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Unhandled error: {}", err);
|
error!("Unhandled error: {}", err);
|
||||||
item.eof = true;
|
item.flags.insert(EntryFlags::EOF);
|
||||||
item.error = true;
|
item.flags.insert(EntryFlags::ERROR);
|
||||||
item.stream.reset(Reason::INTERNAL_ERROR);
|
item.stream.reset(Reason::INTERNAL_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if !item.finished {
|
} else if !item.flags.contains(EntryFlags::FINISHED) {
|
||||||
match item.task.poll() {
|
match item.task.poll() {
|
||||||
Ok(Async::NotReady) => (),
|
Ok(Async::NotReady) => (),
|
||||||
Ok(Async::Ready(_)) => {
|
Ok(Async::Ready(_)) => {
|
||||||
not_ready = false;
|
not_ready = false;
|
||||||
item.finished = true;
|
item.flags.insert(EntryFlags::FINISHED);
|
||||||
},
|
},
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
item.error = true;
|
item.flags.insert(EntryFlags::ERROR);
|
||||||
item.finished = true;
|
item.flags.insert(EntryFlags::FINISHED);
|
||||||
error!("Unhandled error: {}", err);
|
error!("Unhandled error: {}", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -115,7 +132,10 @@ impl<T, H> Http2<T, H>
|
|||||||
|
|
||||||
// cleanup finished tasks
|
// cleanup finished tasks
|
||||||
while !self.tasks.is_empty() {
|
while !self.tasks.is_empty() {
|
||||||
if self.tasks[0].eof && self.tasks[0].finished || self.tasks[0].error {
|
if self.tasks[0].flags.contains(EntryFlags::EOF) &&
|
||||||
|
self.tasks[0].flags.contains(EntryFlags::FINISHED) ||
|
||||||
|
self.tasks[0].flags.contains(EntryFlags::ERROR)
|
||||||
|
{
|
||||||
self.tasks.pop_front();
|
self.tasks.pop_front();
|
||||||
} else {
|
} else {
|
||||||
break
|
break
|
||||||
@ -123,11 +143,11 @@ impl<T, H> Http2<T, H>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get request
|
// get request
|
||||||
if !self.disconnected {
|
if !self.flags.contains(Flags::DISCONNECTED) {
|
||||||
match server.poll() {
|
match server.poll() {
|
||||||
Ok(Async::Ready(None)) => {
|
Ok(Async::Ready(None)) => {
|
||||||
not_ready = false;
|
not_ready = false;
|
||||||
self.disconnected = true;
|
self.flags.insert(Flags::DISCONNECTED);
|
||||||
for entry in &mut self.tasks {
|
for entry in &mut self.tasks {
|
||||||
entry.task.disconnected()
|
entry.task.disconnected()
|
||||||
}
|
}
|
||||||
@ -140,23 +160,34 @@ impl<T, H> Http2<T, H>
|
|||||||
self.keepalive_timer.take();
|
self.keepalive_timer.take();
|
||||||
|
|
||||||
self.tasks.push_back(
|
self.tasks.push_back(
|
||||||
Entry::new(parts, body, resp, self.addr, &self.router));
|
Entry::new(parts, body, resp, self.addr, &self.settings));
|
||||||
}
|
}
|
||||||
Ok(Async::NotReady) => {
|
Ok(Async::NotReady) => {
|
||||||
// start keep-alive timer
|
// start keep-alive timer
|
||||||
if self.tasks.is_empty() && self.keepalive_timer.is_none() {
|
if self.tasks.is_empty() {
|
||||||
trace!("Start keep-alive timer");
|
if self.settings.keep_alive_enabled() {
|
||||||
let mut timeout = Timeout::new(
|
let keep_alive = self.settings.keep_alive();
|
||||||
Duration::new(KEEPALIVE_PERIOD, 0),
|
if keep_alive > 0 && self.keepalive_timer.is_none() {
|
||||||
Arbiter::handle()).unwrap();
|
trace!("Start keep-alive timer");
|
||||||
// register timeout
|
let mut timeout = Timeout::new(
|
||||||
let _ = timeout.poll();
|
Duration::new(keep_alive, 0),
|
||||||
self.keepalive_timer = Some(timeout);
|
Arbiter::handle()).unwrap();
|
||||||
|
// register timeout
|
||||||
|
let _ = timeout.poll();
|
||||||
|
self.keepalive_timer = Some(timeout);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// keep-alive disable, drop connection
|
||||||
|
return Ok(Async::Ready(()))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// keep-alive unset, rely on operating system
|
||||||
|
return Ok(Async::NotReady)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
trace!("Connection error: {}", err);
|
trace!("Connection error: {}", err);
|
||||||
self.disconnected = true;
|
self.flags.insert(Flags::DISCONNECTED);
|
||||||
for entry in &mut self.tasks {
|
for entry in &mut self.tasks {
|
||||||
entry.task.disconnected()
|
entry.task.disconnected()
|
||||||
}
|
}
|
||||||
@ -166,7 +197,7 @@ impl<T, H> Http2<T, H>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if not_ready {
|
if not_ready {
|
||||||
if self.tasks.is_empty() && self.disconnected {
|
if self.tasks.is_empty() && self.flags.contains(Flags::DISCONNECTED) {
|
||||||
return Ok(Async::Ready(()))
|
return Ok(Async::Ready(()))
|
||||||
} else {
|
} else {
|
||||||
return Ok(Async::NotReady)
|
return Ok(Async::NotReady)
|
||||||
@ -196,41 +227,51 @@ impl<T, H> Http2<T, H>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
struct EntryFlags: u8 {
|
||||||
|
const EOF = 0b0000_0001;
|
||||||
|
const REOF = 0b0000_0010;
|
||||||
|
const ERROR = 0b0000_0100;
|
||||||
|
const FINISHED = 0b0000_1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct Entry {
|
struct Entry {
|
||||||
task: Pipeline,
|
task: Box<HttpHandlerTask>,
|
||||||
payload: PayloadType,
|
payload: PayloadType,
|
||||||
recv: RecvStream,
|
recv: RecvStream,
|
||||||
stream: H2Writer,
|
stream: H2Writer,
|
||||||
eof: bool,
|
|
||||||
error: bool,
|
|
||||||
finished: bool,
|
|
||||||
reof: bool,
|
|
||||||
capacity: usize,
|
capacity: usize,
|
||||||
|
flags: EntryFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entry {
|
impl Entry {
|
||||||
fn new<H>(parts: Parts,
|
fn new<H>(parts: Parts,
|
||||||
recv: RecvStream,
|
recv: RecvStream,
|
||||||
resp: Respond<Bytes>,
|
resp: SendResponse<Bytes>,
|
||||||
addr: Option<SocketAddr>,
|
addr: Option<SocketAddr>,
|
||||||
router: &Rc<Vec<H>>) -> Entry
|
settings: &Rc<WorkerSettings<H>>) -> Entry
|
||||||
where H: HttpHandler + 'static
|
where H: HttpHandler + 'static
|
||||||
{
|
{
|
||||||
// Payload and Content-Encoding
|
// Payload and Content-Encoding
|
||||||
let (psender, payload) = Payload::new(false);
|
let (psender, payload) = Payload::new(false);
|
||||||
|
|
||||||
let mut req = HttpRequest::new(
|
let msg = settings.get_http_message();
|
||||||
parts.method, parts.uri, parts.version, parts.headers, payload);
|
msg.get_mut().uri = parts.uri;
|
||||||
|
msg.get_mut().method = parts.method;
|
||||||
|
msg.get_mut().version = parts.version;
|
||||||
|
msg.get_mut().headers = parts.headers;
|
||||||
|
msg.get_mut().payload = Some(payload);
|
||||||
|
msg.get_mut().addr = addr;
|
||||||
|
|
||||||
// set remote addr
|
let mut req = HttpRequest::from_message(msg);
|
||||||
req.set_remove_addr(addr);
|
|
||||||
|
|
||||||
// Payload sender
|
// Payload sender
|
||||||
let psender = PayloadType::new(req.headers(), psender);
|
let psender = PayloadType::new(req.headers(), psender);
|
||||||
|
|
||||||
// start request processing
|
// start request processing
|
||||||
let mut task = None;
|
let mut task = None;
|
||||||
for h in router.iter() {
|
for h in settings.handlers().iter_mut() {
|
||||||
req = match h.handle(req) {
|
req = match h.handle(req) {
|
||||||
Ok(t) => {
|
Ok(t) => {
|
||||||
task = Some(t);
|
task = Some(t);
|
||||||
@ -243,23 +284,20 @@ impl Entry {
|
|||||||
Entry {task: task.unwrap_or_else(|| Pipeline::error(HTTPNotFound)),
|
Entry {task: task.unwrap_or_else(|| Pipeline::error(HTTPNotFound)),
|
||||||
payload: psender,
|
payload: psender,
|
||||||
recv: recv,
|
recv: recv,
|
||||||
stream: H2Writer::new(resp),
|
stream: H2Writer::new(resp, settings.get_shared_bytes()),
|
||||||
eof: false,
|
flags: EntryFlags::empty(),
|
||||||
error: false,
|
|
||||||
finished: false,
|
|
||||||
reof: false,
|
|
||||||
capacity: 0,
|
capacity: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_payload(&mut self) {
|
fn poll_payload(&mut self) {
|
||||||
if !self.reof {
|
if !self.flags.contains(EntryFlags::REOF) {
|
||||||
match self.recv.poll() {
|
match self.recv.poll() {
|
||||||
Ok(Async::Ready(Some(chunk))) => {
|
Ok(Async::Ready(Some(chunk))) => {
|
||||||
self.payload.feed_data(chunk);
|
self.payload.feed_data(chunk);
|
||||||
},
|
},
|
||||||
Ok(Async::Ready(None)) => {
|
Ok(Async::Ready(None)) => {
|
||||||
self.reof = true;
|
self.flags.insert(EntryFlags::REOF);
|
||||||
},
|
},
|
||||||
Ok(Async::NotReady) => (),
|
Ok(Async::NotReady) => (),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
126
src/h2writer.rs
126
src/h2writer.rs
@ -1,43 +1,49 @@
|
|||||||
use std::{io, cmp};
|
use std::{io, cmp};
|
||||||
use bytes::Bytes;
|
use bytes::{Bytes, BytesMut};
|
||||||
use futures::{Async, Poll};
|
use futures::{Async, Poll};
|
||||||
use http2::{Reason, SendStream};
|
use http2::{Reason, SendStream};
|
||||||
use http2::server::Respond;
|
use http2::server::SendResponse;
|
||||||
use http::{Version, HttpTryFrom, Response};
|
use http::{Version, HttpTryFrom, Response};
|
||||||
use http::header::{HeaderValue, CONNECTION, CONTENT_TYPE, TRANSFER_ENCODING, DATE};
|
use http::header::{HeaderValue, CONNECTION, TRANSFER_ENCODING, DATE, CONTENT_LENGTH};
|
||||||
|
|
||||||
use date;
|
use helpers;
|
||||||
use body::Body;
|
use body::Body;
|
||||||
|
use helpers::SharedBytes;
|
||||||
use encoding::PayloadEncoder;
|
use encoding::PayloadEncoder;
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpMessage;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
use h1writer::{Writer, WriterState};
|
use h1writer::{Writer, WriterState};
|
||||||
|
|
||||||
const CHUNK_SIZE: usize = 16_384;
|
const CHUNK_SIZE: usize = 16_384;
|
||||||
const MAX_WRITE_BUFFER_SIZE: usize = 65_536; // max buffer size 64k
|
const MAX_WRITE_BUFFER_SIZE: usize = 65_536; // max buffer size 64k
|
||||||
|
|
||||||
|
bitflags! {
|
||||||
|
struct Flags: u8 {
|
||||||
|
const STARTED = 0b0000_0001;
|
||||||
|
const DISCONNECTED = 0b0000_0010;
|
||||||
|
const EOF = 0b0000_0100;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct H2Writer {
|
pub(crate) struct H2Writer {
|
||||||
respond: Respond<Bytes>,
|
respond: SendResponse<Bytes>,
|
||||||
stream: Option<SendStream<Bytes>>,
|
stream: Option<SendStream<Bytes>>,
|
||||||
started: bool,
|
|
||||||
encoder: PayloadEncoder,
|
encoder: PayloadEncoder,
|
||||||
disconnected: bool,
|
flags: Flags,
|
||||||
eof: bool,
|
|
||||||
written: u64,
|
written: u64,
|
||||||
|
buffer: SharedBytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl H2Writer {
|
impl H2Writer {
|
||||||
|
|
||||||
pub fn new(respond: Respond<Bytes>) -> H2Writer {
|
pub fn new(respond: SendResponse<Bytes>, buf: SharedBytes) -> H2Writer {
|
||||||
H2Writer {
|
H2Writer {
|
||||||
respond: respond,
|
respond: respond,
|
||||||
stream: None,
|
stream: None,
|
||||||
started: false,
|
encoder: PayloadEncoder::empty(buf.clone()),
|
||||||
encoder: PayloadEncoder::default(),
|
flags: Flags::empty(),
|
||||||
disconnected: false,
|
|
||||||
eof: true,
|
|
||||||
written: 0,
|
written: 0,
|
||||||
|
buffer: buf,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +54,7 @@ impl H2Writer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn write_to_stream(&mut self) -> Result<WriterState, io::Error> {
|
fn write_to_stream(&mut self) -> Result<WriterState, io::Error> {
|
||||||
if !self.started {
|
if !self.flags.contains(Flags::STARTED) {
|
||||||
return Ok(WriterState::Done)
|
return Ok(WriterState::Done)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +62,7 @@ impl H2Writer {
|
|||||||
let buffer = self.encoder.get_mut();
|
let buffer = self.encoder.get_mut();
|
||||||
|
|
||||||
if buffer.is_empty() {
|
if buffer.is_empty() {
|
||||||
if self.eof {
|
if self.flags.contains(Flags::EOF) {
|
||||||
let _ = stream.send_data(Bytes::new(), true);
|
let _ = stream.send_data(Bytes::new(), true);
|
||||||
}
|
}
|
||||||
return Ok(WriterState::Done)
|
return Ok(WriterState::Done)
|
||||||
@ -77,7 +83,7 @@ impl H2Writer {
|
|||||||
Ok(Async::Ready(Some(cap))) => {
|
Ok(Async::Ready(Some(cap))) => {
|
||||||
let len = buffer.len();
|
let len = buffer.len();
|
||||||
let bytes = buffer.split_to(cmp::min(cap, len));
|
let bytes = buffer.split_to(cmp::min(cap, len));
|
||||||
let eof = buffer.is_empty() && self.eof;
|
let eof = buffer.is_empty() && self.flags.contains(Flags::EOF);
|
||||||
self.written += bytes.len() as u64;
|
self.written += bytes.len() as u64;
|
||||||
|
|
||||||
if let Err(err) = stream.send_data(bytes.freeze(), eof) {
|
if let Err(err) = stream.send_data(bytes.freeze(), eof) {
|
||||||
@ -86,7 +92,7 @@ impl H2Writer {
|
|||||||
let cap = cmp::min(buffer.len(), CHUNK_SIZE);
|
let cap = cmp::min(buffer.len(), CHUNK_SIZE);
|
||||||
stream.reserve_capacity(cap);
|
stream.reserve_capacity(cap);
|
||||||
} else {
|
} else {
|
||||||
return Ok(WriterState::Done)
|
return Ok(WriterState::Pause)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@ -105,42 +111,52 @@ impl Writer for H2Writer {
|
|||||||
self.written
|
self.written
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start(&mut self, req: &mut HttpRequest, msg: &mut HttpResponse)
|
fn start(&mut self, req: &mut HttpMessage, msg: &mut HttpResponse)
|
||||||
-> Result<WriterState, io::Error>
|
-> Result<WriterState, io::Error>
|
||||||
{
|
{
|
||||||
trace!("Prepare response with status: {:?}", msg.status);
|
// trace!("Prepare response with status: {:?}", msg.status());
|
||||||
|
|
||||||
// prepare response
|
// prepare response
|
||||||
self.started = true;
|
self.flags.insert(Flags::STARTED);
|
||||||
self.encoder = PayloadEncoder::new(req, msg);
|
self.encoder = PayloadEncoder::new(self.buffer.clone(), req, msg);
|
||||||
self.eof = if let Body::Empty = *msg.body() { true } else { false };
|
if let Body::Empty = *msg.body() {
|
||||||
|
self.flags.insert(Flags::EOF);
|
||||||
// http2 specific
|
|
||||||
msg.headers.remove(CONNECTION);
|
|
||||||
msg.headers.remove(TRANSFER_ENCODING);
|
|
||||||
|
|
||||||
// using http::h1::date is quite a lot faster than generating
|
|
||||||
// a unique Date header each time like req/s goes up about 10%
|
|
||||||
if !msg.headers.contains_key(DATE) {
|
|
||||||
let mut bytes = [0u8; 29];
|
|
||||||
date::extend(&mut bytes[..]);
|
|
||||||
msg.headers.insert(DATE, HeaderValue::try_from(&bytes[..]).unwrap());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// default content-type
|
// http2 specific
|
||||||
if !msg.headers.contains_key(CONTENT_TYPE) {
|
msg.headers_mut().remove(CONNECTION);
|
||||||
msg.headers.insert(
|
msg.headers_mut().remove(TRANSFER_ENCODING);
|
||||||
CONTENT_TYPE, HeaderValue::from_static("application/octet-stream"));
|
|
||||||
|
// using helpers::date is quite a lot faster
|
||||||
|
if !msg.headers().contains_key(DATE) {
|
||||||
|
let mut bytes = BytesMut::with_capacity(29);
|
||||||
|
helpers::date_value(&mut bytes);
|
||||||
|
msg.headers_mut().insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let body = msg.replace_body(Body::Empty);
|
||||||
|
match body {
|
||||||
|
Body::Binary(ref bytes) => {
|
||||||
|
let mut val = BytesMut::new();
|
||||||
|
helpers::convert_usize(bytes.len(), &mut val);
|
||||||
|
let l = val.len();
|
||||||
|
msg.headers_mut().insert(
|
||||||
|
CONTENT_LENGTH, HeaderValue::try_from(val.split_to(l-2).freeze()).unwrap());
|
||||||
|
}
|
||||||
|
Body::Empty => {
|
||||||
|
msg.headers_mut().insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut resp = Response::new(());
|
let mut resp = Response::new(());
|
||||||
*resp.status_mut() = msg.status;
|
*resp.status_mut() = msg.status();
|
||||||
*resp.version_mut() = Version::HTTP_2;
|
*resp.version_mut() = Version::HTTP_2;
|
||||||
for (key, value) in msg.headers().iter() {
|
for (key, value) in msg.headers().iter() {
|
||||||
resp.headers_mut().insert(key, value.clone());
|
resp.headers_mut().insert(key, value.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.respond.send_response(resp, self.eof) {
|
match self.respond.send_response(resp, self.flags.contains(Flags::EOF)) {
|
||||||
Ok(stream) =>
|
Ok(stream) =>
|
||||||
self.stream = Some(stream),
|
self.stream = Some(stream),
|
||||||
Err(_) =>
|
Err(_) =>
|
||||||
@ -149,23 +165,25 @@ impl Writer for H2Writer {
|
|||||||
|
|
||||||
trace!("Response: {:?}", msg);
|
trace!("Response: {:?}", msg);
|
||||||
|
|
||||||
if msg.body().is_binary() {
|
if let Body::Binary(bytes) = body {
|
||||||
if let Body::Binary(bytes) = msg.replace_body(Body::Empty) {
|
self.flags.insert(Flags::EOF);
|
||||||
self.eof = true;
|
self.written = bytes.len() as u64;
|
||||||
self.encoder.write(bytes.as_ref())?;
|
self.encoder.write(bytes.as_ref())?;
|
||||||
if let Some(ref mut stream) = self.stream {
|
if let Some(ref mut stream) = self.stream {
|
||||||
stream.reserve_capacity(cmp::min(self.encoder.len(), CHUNK_SIZE));
|
stream.reserve_capacity(cmp::min(self.encoder.len(), CHUNK_SIZE));
|
||||||
}
|
|
||||||
return Ok(WriterState::Done)
|
|
||||||
}
|
}
|
||||||
|
Ok(WriterState::Pause)
|
||||||
|
} else {
|
||||||
|
msg.replace_body(body);
|
||||||
|
Ok(WriterState::Done)
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(WriterState::Done)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, payload: &[u8]) -> Result<WriterState, io::Error> {
|
fn write(&mut self, payload: &[u8]) -> Result<WriterState, io::Error> {
|
||||||
if !self.disconnected {
|
self.written = payload.len() as u64;
|
||||||
if self.started {
|
|
||||||
|
if !self.flags.contains(Flags::DISCONNECTED) {
|
||||||
|
if self.flags.contains(Flags::STARTED) {
|
||||||
// TODO: add warning, write after EOF
|
// TODO: add warning, write after EOF
|
||||||
self.encoder.write(payload)?;
|
self.encoder.write(payload)?;
|
||||||
} else {
|
} else {
|
||||||
@ -184,7 +202,7 @@ impl Writer for H2Writer {
|
|||||||
fn write_eof(&mut self) -> Result<WriterState, io::Error> {
|
fn write_eof(&mut self) -> Result<WriterState, io::Error> {
|
||||||
self.encoder.write_eof()?;
|
self.encoder.write_eof()?;
|
||||||
|
|
||||||
self.eof = true;
|
self.flags.insert(Flags::EOF);
|
||||||
if !self.encoder.is_eof() {
|
if !self.encoder.is_eof() {
|
||||||
Err(io::Error::new(io::ErrorKind::Other,
|
Err(io::Error::new(io::ErrorKind::Other,
|
||||||
"Last payload item, but eof is not reached"))
|
"Last payload item, but eof is not reached"))
|
||||||
@ -195,7 +213,7 @@ impl Writer for H2Writer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll_complete(&mut self) -> Poll<(), io::Error> {
|
fn poll_completed(&mut self, _shutdown: bool) -> Poll<(), io::Error> {
|
||||||
match self.write_to_stream() {
|
match self.write_to_stream() {
|
||||||
Ok(WriterState::Done) => Ok(Async::Ready(())),
|
Ok(WriterState::Done) => Ok(Async::Ready(())),
|
||||||
Ok(WriterState::Pause) => Ok(Async::NotReady),
|
Ok(WriterState::Pause) => Ok(Async::NotReady),
|
||||||
|
562
src/handler.rs
Normal file
562
src/handler.rs
Normal file
@ -0,0 +1,562 @@
|
|||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use regex::Regex;
|
||||||
|
use futures::future::{Future, ok, err};
|
||||||
|
use http::{header, StatusCode, Error as HttpError};
|
||||||
|
|
||||||
|
use body::Body;
|
||||||
|
use error::Error;
|
||||||
|
use httprequest::HttpRequest;
|
||||||
|
use httpresponse::HttpResponse;
|
||||||
|
|
||||||
|
/// Trait defines object that could be regestered as route handler
|
||||||
|
#[allow(unused_variables)]
|
||||||
|
pub trait Handler<S>: 'static {
|
||||||
|
|
||||||
|
/// The type of value that handler will return.
|
||||||
|
type Result: Responder;
|
||||||
|
|
||||||
|
/// Handle request
|
||||||
|
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait implemented by types that generate responses for clients.
|
||||||
|
///
|
||||||
|
/// Types that implement this trait can be used as the return type of a handler.
|
||||||
|
pub trait Responder {
|
||||||
|
/// The associated item which can be returned.
|
||||||
|
type Item: Into<Reply>;
|
||||||
|
|
||||||
|
/// The associated error which can be returned.
|
||||||
|
type Error: Into<Error>;
|
||||||
|
|
||||||
|
/// Convert itself to `Reply` or `Error`.
|
||||||
|
fn respond_to(self, req: HttpRequest) -> Result<Self::Item, Self::Error>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
/// Convinience trait that convert `Future` object into `Boxed` future
|
||||||
|
pub trait AsyncResponder<I, E>: Sized {
|
||||||
|
fn responder(self) -> Box<Future<Item=I, Error=E>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, I, E> AsyncResponder<I, E> for F
|
||||||
|
where F: Future<Item=I, Error=E> + 'static,
|
||||||
|
I: Responder + 'static,
|
||||||
|
E: Into<Error> + 'static,
|
||||||
|
{
|
||||||
|
fn responder(self) -> Box<Future<Item=I, Error=E>> {
|
||||||
|
Box::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handler<S> for Fn()
|
||||||
|
impl<F, R, S> Handler<S> for F
|
||||||
|
where F: Fn(HttpRequest<S>) -> R + 'static,
|
||||||
|
R: Responder + 'static
|
||||||
|
{
|
||||||
|
type Result = R;
|
||||||
|
|
||||||
|
fn handle(&mut self, req: HttpRequest<S>) -> R {
|
||||||
|
(self)(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents response process.
|
||||||
|
pub struct Reply(ReplyItem);
|
||||||
|
|
||||||
|
pub(crate) enum ReplyItem {
|
||||||
|
Message(HttpResponse),
|
||||||
|
Future(Box<Future<Item=HttpResponse, Error=Error>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Reply {
|
||||||
|
|
||||||
|
/// Create async response
|
||||||
|
#[inline]
|
||||||
|
pub fn async<F>(fut: F) -> Reply
|
||||||
|
where F: Future<Item=HttpResponse, Error=Error> + 'static
|
||||||
|
{
|
||||||
|
Reply(ReplyItem::Future(Box::new(fut)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send response
|
||||||
|
#[inline]
|
||||||
|
pub fn response<R: Into<HttpResponse>>(response: R) -> Reply {
|
||||||
|
Reply(ReplyItem::Message(response.into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn into(self) -> ReplyItem {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn as_response(&self) -> Option<&HttpResponse> {
|
||||||
|
match self.0 {
|
||||||
|
ReplyItem::Message(ref resp) => Some(resp),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Responder for Reply {
|
||||||
|
type Item = Reply;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn respond_to(self, _: HttpRequest) -> Result<Reply, Error> {
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Responder for HttpResponse {
|
||||||
|
type Item = Reply;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn respond_to(self, _: HttpRequest) -> Result<Reply, Error> {
|
||||||
|
Ok(Reply(ReplyItem::Message(self)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<HttpResponse> for Reply {
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn from(resp: HttpResponse) -> Reply {
|
||||||
|
Reply(ReplyItem::Message(resp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Responder, E: Into<Error>> Responder for Result<T, E>
|
||||||
|
{
|
||||||
|
type Item = <T as Responder>::Item;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn respond_to(self, req: HttpRequest) -> Result<Self::Item, Self::Error> {
|
||||||
|
match self {
|
||||||
|
Ok(val) => match val.respond_to(req) {
|
||||||
|
Ok(val) => Ok(val),
|
||||||
|
Err(err) => Err(err.into()),
|
||||||
|
},
|
||||||
|
Err(err) => Err(err.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Into<Error>> From<Result<Reply, E>> for Reply {
|
||||||
|
#[inline]
|
||||||
|
fn from(res: Result<Reply, E>) -> Self {
|
||||||
|
match res {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(err) => Reply(ReplyItem::Message(err.into().into())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<E: Into<Error>> From<Result<HttpResponse, E>> for Reply {
|
||||||
|
#[inline]
|
||||||
|
fn from(res: Result<HttpResponse, E>) -> Self {
|
||||||
|
match res {
|
||||||
|
Ok(val) => Reply(ReplyItem::Message(val)),
|
||||||
|
Err(err) => Reply(ReplyItem::Message(err.into().into())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Box<Future<Item=HttpResponse, Error=Error>>> for Reply {
|
||||||
|
#[inline]
|
||||||
|
fn from(fut: Box<Future<Item=HttpResponse, Error=Error>>) -> Reply {
|
||||||
|
Reply(ReplyItem::Future(fut))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I, E> Responder for Box<Future<Item=I, Error=E>>
|
||||||
|
where I: Responder + 'static,
|
||||||
|
E: Into<Error> + 'static
|
||||||
|
{
|
||||||
|
type Item = Reply;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn respond_to(self, req: HttpRequest) -> Result<Reply, Error> {
|
||||||
|
let fut = self.map_err(|e| e.into())
|
||||||
|
.then(move |r| {
|
||||||
|
match r.respond_to(req) {
|
||||||
|
Ok(reply) => match reply.into().0 {
|
||||||
|
ReplyItem::Message(resp) => ok(resp),
|
||||||
|
_ => panic!("Nested async replies are not supported"),
|
||||||
|
},
|
||||||
|
Err(e) => err(e),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Ok(Reply::async(fut))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Trait defines object that could be regestered as resource route
|
||||||
|
pub(crate) trait RouteHandler<S>: 'static {
|
||||||
|
fn handle(&mut self, req: HttpRequest<S>) -> Reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Route handler wrapper for Handler
|
||||||
|
pub(crate)
|
||||||
|
struct WrapHandler<S, H, R>
|
||||||
|
where H: Handler<S, Result=R>,
|
||||||
|
R: Responder,
|
||||||
|
S: 'static,
|
||||||
|
{
|
||||||
|
h: H,
|
||||||
|
s: PhantomData<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, H, R> WrapHandler<S, H, R>
|
||||||
|
where H: Handler<S, Result=R>,
|
||||||
|
R: Responder,
|
||||||
|
S: 'static,
|
||||||
|
{
|
||||||
|
pub fn new(h: H) -> Self {
|
||||||
|
WrapHandler{h: h, s: PhantomData}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, H, R> RouteHandler<S> for WrapHandler<S, H, R>
|
||||||
|
where H: Handler<S, Result=R>,
|
||||||
|
R: Responder + 'static,
|
||||||
|
S: 'static,
|
||||||
|
{
|
||||||
|
fn handle(&mut self, req: HttpRequest<S>) -> Reply {
|
||||||
|
let req2 = req.clone_without_state();
|
||||||
|
match self.h.handle(req).respond_to(req2) {
|
||||||
|
Ok(reply) => reply.into(),
|
||||||
|
Err(err) => Reply::response(err.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Async route handler
|
||||||
|
pub(crate)
|
||||||
|
struct AsyncHandler<S, H, F, R, E>
|
||||||
|
where H: Fn(HttpRequest<S>) -> F + 'static,
|
||||||
|
F: Future<Item=R, Error=E> + 'static,
|
||||||
|
R: Responder + 'static,
|
||||||
|
E: Into<Error> + 'static,
|
||||||
|
S: 'static,
|
||||||
|
{
|
||||||
|
h: Box<H>,
|
||||||
|
s: PhantomData<S>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, H, F, R, E> AsyncHandler<S, H, F, R, E>
|
||||||
|
where H: Fn(HttpRequest<S>) -> F + 'static,
|
||||||
|
F: Future<Item=R, Error=E> + 'static,
|
||||||
|
R: Responder + 'static,
|
||||||
|
E: Into<Error> + 'static,
|
||||||
|
S: 'static,
|
||||||
|
{
|
||||||
|
pub fn new(h: H) -> Self {
|
||||||
|
AsyncHandler{h: Box::new(h), s: PhantomData}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, H, F, R, E> RouteHandler<S> for AsyncHandler<S, H, F, R, E>
|
||||||
|
where H: Fn(HttpRequest<S>) -> F + 'static,
|
||||||
|
F: Future<Item=R, Error=E> + 'static,
|
||||||
|
R: Responder + 'static,
|
||||||
|
E: Into<Error> + 'static,
|
||||||
|
S: 'static,
|
||||||
|
{
|
||||||
|
fn handle(&mut self, req: HttpRequest<S>) -> Reply {
|
||||||
|
let req2 = req.clone_without_state();
|
||||||
|
let fut = (self.h)(req)
|
||||||
|
.map_err(|e| e.into())
|
||||||
|
.then(move |r| {
|
||||||
|
match r.respond_to(req2) {
|
||||||
|
Ok(reply) => match reply.into().0 {
|
||||||
|
ReplyItem::Message(resp) => ok(resp),
|
||||||
|
_ => panic!("Nested async replies are not supported"),
|
||||||
|
},
|
||||||
|
Err(e) => err(e),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Reply::async(fut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Path normalization helper
|
||||||
|
///
|
||||||
|
/// By normalizing it means:
|
||||||
|
///
|
||||||
|
/// - Add a trailing slash to the path.
|
||||||
|
/// - Double slashes are replaced by one.
|
||||||
|
///
|
||||||
|
/// The handler returns as soon as it finds a path that resolves
|
||||||
|
/// correctly. The order if all enable is 1) merge, 3) both merge and append
|
||||||
|
/// and 3) append. If the path resolves with
|
||||||
|
/// at least one of those conditions, it will redirect to the new path.
|
||||||
|
///
|
||||||
|
/// If *append* is *true* append slash when needed. If a resource is
|
||||||
|
/// defined with trailing slash and the request comes without it, it will
|
||||||
|
/// append it automatically.
|
||||||
|
///
|
||||||
|
/// If *merge* is *true*, merge multiple consecutive slashes in the path into one.
|
||||||
|
///
|
||||||
|
/// This handler designed to be use as a handler for application's *default resource*.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// # #[macro_use] extern crate serde_derive;
|
||||||
|
/// # use actix_web::*;
|
||||||
|
/// #
|
||||||
|
/// # fn index(req: HttpRequest) -> httpcodes::StaticResponse {
|
||||||
|
/// # httpcodes::HTTPOk
|
||||||
|
/// # }
|
||||||
|
/// fn main() {
|
||||||
|
/// let app = Application::new()
|
||||||
|
/// .resource("/test/", |r| r.f(index))
|
||||||
|
/// .default_resource(|r| r.h(NormalizePath::default()))
|
||||||
|
/// .finish();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// In this example `/test`, `/test///` will be redirected to `/test/` url.
|
||||||
|
pub struct NormalizePath {
|
||||||
|
append: bool,
|
||||||
|
merge: bool,
|
||||||
|
re_merge: Regex,
|
||||||
|
redirect: StatusCode,
|
||||||
|
not_found: StatusCode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NormalizePath {
|
||||||
|
/// Create default `NormalizePath` instance, *append* is set to *true*,
|
||||||
|
/// *merge* is set to *true* and *redirect* is set to `StatusCode::MOVED_PERMANENTLY`
|
||||||
|
fn default() -> NormalizePath {
|
||||||
|
NormalizePath {
|
||||||
|
append: true,
|
||||||
|
merge: true,
|
||||||
|
re_merge: Regex::new("//+").unwrap(),
|
||||||
|
redirect: StatusCode::MOVED_PERMANENTLY,
|
||||||
|
not_found: StatusCode::NOT_FOUND,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NormalizePath {
|
||||||
|
/// Create new `NoramlizePath` instance
|
||||||
|
pub fn new(append: bool, merge: bool, redirect: StatusCode) -> NormalizePath {
|
||||||
|
NormalizePath {
|
||||||
|
append: append,
|
||||||
|
merge: merge,
|
||||||
|
re_merge: Regex::new("//+").unwrap(),
|
||||||
|
redirect: redirect,
|
||||||
|
not_found: StatusCode::NOT_FOUND,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Handler<S> for NormalizePath {
|
||||||
|
type Result = Result<HttpResponse, HttpError>;
|
||||||
|
|
||||||
|
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
||||||
|
if let Some(router) = req.router() {
|
||||||
|
let query = req.query_string();
|
||||||
|
if self.merge {
|
||||||
|
// merge slashes
|
||||||
|
let p = self.re_merge.replace_all(req.path(), "/");
|
||||||
|
if p.len() != req.path().len() {
|
||||||
|
if router.has_route(p.as_ref()) {
|
||||||
|
let p = if !query.is_empty() { p + "?" + query } else { p };
|
||||||
|
return HttpResponse::build(self.redirect)
|
||||||
|
.header(header::LOCATION, p.as_ref())
|
||||||
|
.body(Body::Empty);
|
||||||
|
}
|
||||||
|
// merge slashes and append trailing slash
|
||||||
|
if self.append && !p.ends_with('/') {
|
||||||
|
let p = p.as_ref().to_owned() + "/";
|
||||||
|
if router.has_route(&p) {
|
||||||
|
let p = if !query.is_empty() { p + "?" + query } else { p };
|
||||||
|
return HttpResponse::build(self.redirect)
|
||||||
|
.header(header::LOCATION, p.as_str())
|
||||||
|
.body(Body::Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// append trailing slash
|
||||||
|
if self.append && !req.path().ends_with('/') {
|
||||||
|
let p = req.path().to_owned() + "/";
|
||||||
|
if router.has_route(&p) {
|
||||||
|
let p = if !query.is_empty() { p + "?" + query } else { p };
|
||||||
|
return HttpResponse::build(self.redirect)
|
||||||
|
.header(header::LOCATION, p.as_str())
|
||||||
|
.body(Body::Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(HttpResponse::new(self.not_found, Body::Empty))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use http::{header, Method};
|
||||||
|
use test::TestRequest;
|
||||||
|
use application::Application;
|
||||||
|
|
||||||
|
fn index(_req: HttpRequest) -> HttpResponse {
|
||||||
|
HttpResponse::new(StatusCode::OK, Body::Empty)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_normalize_path_trailing_slashes() {
|
||||||
|
let mut app = Application::new()
|
||||||
|
.resource("/resource1", |r| r.method(Method::GET).f(index))
|
||||||
|
.resource("/resource2/", |r| r.method(Method::GET).f(index))
|
||||||
|
.default_resource(|r| r.h(NormalizePath::default()))
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
// trailing slashes
|
||||||
|
let params = vec![("/resource1", "", StatusCode::OK),
|
||||||
|
("/resource1/", "", StatusCode::NOT_FOUND),
|
||||||
|
("/resource2", "/resource2/", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("/resource2/", "", StatusCode::OK),
|
||||||
|
("/resource1?p1=1&p2=2", "", StatusCode::OK),
|
||||||
|
("/resource1/?p1=1&p2=2", "", StatusCode::NOT_FOUND),
|
||||||
|
("/resource2?p1=1&p2=2", "/resource2/?p1=1&p2=2",
|
||||||
|
StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("/resource2/?p1=1&p2=2", "", StatusCode::OK)
|
||||||
|
];
|
||||||
|
for (path, target, code) in params {
|
||||||
|
let req = app.prepare_request(TestRequest::with_uri(path).finish());
|
||||||
|
let resp = app.run(req);
|
||||||
|
let r = resp.as_response().unwrap();
|
||||||
|
assert_eq!(r.status(), code);
|
||||||
|
if !target.is_empty() {
|
||||||
|
assert_eq!(
|
||||||
|
target,
|
||||||
|
r.headers().get(header::LOCATION).unwrap().to_str().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_normalize_path_trailing_slashes_disabled() {
|
||||||
|
let mut app = Application::new()
|
||||||
|
.resource("/resource1", |r| r.method(Method::GET).f(index))
|
||||||
|
.resource("/resource2/", |r| r.method(Method::GET).f(index))
|
||||||
|
.default_resource(|r| r.h(
|
||||||
|
NormalizePath::new(false, true, StatusCode::MOVED_PERMANENTLY)))
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
// trailing slashes
|
||||||
|
let params = vec![("/resource1", StatusCode::OK),
|
||||||
|
("/resource1/", StatusCode::NOT_FOUND),
|
||||||
|
("/resource2", StatusCode::NOT_FOUND),
|
||||||
|
("/resource2/", StatusCode::OK),
|
||||||
|
("/resource1?p1=1&p2=2", StatusCode::OK),
|
||||||
|
("/resource1/?p1=1&p2=2", StatusCode::NOT_FOUND),
|
||||||
|
("/resource2?p1=1&p2=2", StatusCode::NOT_FOUND),
|
||||||
|
("/resource2/?p1=1&p2=2", StatusCode::OK)
|
||||||
|
];
|
||||||
|
for (path, code) in params {
|
||||||
|
let req = app.prepare_request(TestRequest::with_uri(path).finish());
|
||||||
|
let resp = app.run(req);
|
||||||
|
let r = resp.as_response().unwrap();
|
||||||
|
assert_eq!(r.status(), code);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_normalize_path_merge_slashes() {
|
||||||
|
let mut app = Application::new()
|
||||||
|
.resource("/resource1", |r| r.method(Method::GET).f(index))
|
||||||
|
.resource("/resource1/a/b", |r| r.method(Method::GET).f(index))
|
||||||
|
.default_resource(|r| r.h(NormalizePath::default()))
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
// trailing slashes
|
||||||
|
let params = vec![
|
||||||
|
("/resource1/a/b", "", StatusCode::OK),
|
||||||
|
("//resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("//resource1//a//b/", "", StatusCode::NOT_FOUND),
|
||||||
|
("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("/////resource1/a//b/", "", StatusCode::NOT_FOUND),
|
||||||
|
("/resource1/a/b?p=1", "", StatusCode::OK),
|
||||||
|
("//resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("//resource1//a//b/?p=1", "", StatusCode::NOT_FOUND),
|
||||||
|
("///resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("/////resource1/a///b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("/////resource1/a//b/?p=1", "", StatusCode::NOT_FOUND),
|
||||||
|
];
|
||||||
|
for (path, target, code) in params {
|
||||||
|
let req = app.prepare_request(TestRequest::with_uri(path).finish());
|
||||||
|
let resp = app.run(req);
|
||||||
|
let r = resp.as_response().unwrap();
|
||||||
|
assert_eq!(r.status(), code);
|
||||||
|
if !target.is_empty() {
|
||||||
|
assert_eq!(
|
||||||
|
target,
|
||||||
|
r.headers().get(header::LOCATION).unwrap().to_str().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_normalize_path_merge_and_append_slashes() {
|
||||||
|
let mut app = Application::new()
|
||||||
|
.resource("/resource1", |r| r.method(Method::GET).f(index))
|
||||||
|
.resource("/resource2/", |r| r.method(Method::GET).f(index))
|
||||||
|
.resource("/resource1/a/b", |r| r.method(Method::GET).f(index))
|
||||||
|
.resource("/resource2/a/b/", |r| r.method(Method::GET).f(index))
|
||||||
|
.default_resource(|r| r.h(NormalizePath::default()))
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
// trailing slashes
|
||||||
|
let params = vec![
|
||||||
|
("/resource1/a/b", "", StatusCode::OK),
|
||||||
|
("/resource1/a/b/", "", StatusCode::NOT_FOUND),
|
||||||
|
("//resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("//resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("///resource1//a//b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("///resource1//a//b/", "", StatusCode::NOT_FOUND),
|
||||||
|
("/////resource1/a///b", "/resource1/a/b", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("/////resource1/a///b/", "", StatusCode::NOT_FOUND),
|
||||||
|
("/resource2/a/b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("/resource2/a/b/", "", StatusCode::OK),
|
||||||
|
("//resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("//resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("///resource2//a//b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("///resource2//a//b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("/////resource2/a///b", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("/////resource2/a///b/", "/resource2/a/b/", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("/resource1/a/b?p=1", "", StatusCode::OK),
|
||||||
|
("/resource1/a/b/?p=1", "", StatusCode::NOT_FOUND),
|
||||||
|
("//resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("//resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("///resource1//a//b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("///resource1//a//b/?p=1", "", StatusCode::NOT_FOUND),
|
||||||
|
("/////resource1/a///b?p=1", "/resource1/a/b?p=1", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("/////resource1/a///b/?p=1", "", StatusCode::NOT_FOUND),
|
||||||
|
("/resource2/a/b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("//resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("//resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("///resource2//a//b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("///resource2//a//b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("/////resource2/a///b?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
("/////resource2/a///b/?p=1", "/resource2/a/b/?p=1", StatusCode::MOVED_PERMANENTLY),
|
||||||
|
];
|
||||||
|
for (path, target, code) in params {
|
||||||
|
let req = app.prepare_request(TestRequest::with_uri(path).finish());
|
||||||
|
let resp = app.run(req);
|
||||||
|
let r = resp.as_response().unwrap();
|
||||||
|
assert_eq!(r.status(), code);
|
||||||
|
if !target.is_empty() {
|
||||||
|
assert_eq!(
|
||||||
|
target, r.headers().get(header::LOCATION).unwrap().to_str().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
416
src/helpers.rs
Normal file
416
src/helpers.rs
Normal file
@ -0,0 +1,416 @@
|
|||||||
|
use std::{str, mem, ptr, slice};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::fmt::{self, Write};
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use time;
|
||||||
|
use bytes::{BufMut, BytesMut};
|
||||||
|
use http::Version;
|
||||||
|
|
||||||
|
use httprequest::HttpMessage;
|
||||||
|
|
||||||
|
// "Sun, 06 Nov 1994 08:49:37 GMT".len()
|
||||||
|
pub(crate) const DATE_VALUE_LENGTH: usize = 29;
|
||||||
|
|
||||||
|
pub(crate) fn date(dst: &mut BytesMut) {
|
||||||
|
CACHED.with(|cache| {
|
||||||
|
let mut buf: [u8; 39] = unsafe { mem::uninitialized() };
|
||||||
|
buf[..6].copy_from_slice(b"date: ");
|
||||||
|
buf[6..35].copy_from_slice(cache.borrow().buffer());
|
||||||
|
buf[35..].copy_from_slice(b"\r\n\r\n");
|
||||||
|
dst.extend_from_slice(&buf);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn date_value(dst: &mut BytesMut) {
|
||||||
|
CACHED.with(|cache| {
|
||||||
|
dst.extend_from_slice(cache.borrow().buffer());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn update_date() {
|
||||||
|
CACHED.with(|cache| {
|
||||||
|
cache.borrow_mut().update();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CachedDate {
|
||||||
|
bytes: [u8; DATE_VALUE_LENGTH],
|
||||||
|
pos: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_local!(static CACHED: RefCell<CachedDate> = RefCell::new(CachedDate {
|
||||||
|
bytes: [0; DATE_VALUE_LENGTH],
|
||||||
|
pos: 0,
|
||||||
|
}));
|
||||||
|
|
||||||
|
impl CachedDate {
|
||||||
|
fn buffer(&self) -> &[u8] {
|
||||||
|
&self.bytes[..]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self) {
|
||||||
|
self.pos = 0;
|
||||||
|
write!(self, "{}", time::at_utc(time::get_time()).rfc822()).unwrap();
|
||||||
|
assert_eq!(self.pos, DATE_VALUE_LENGTH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Write for CachedDate {
|
||||||
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
|
let len = s.len();
|
||||||
|
self.bytes[self.pos..self.pos + len].copy_from_slice(s.as_bytes());
|
||||||
|
self.pos += len;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal use only! unsafe
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct SharedBytesPool(RefCell<VecDeque<Rc<BytesMut>>>);
|
||||||
|
|
||||||
|
impl SharedBytesPool {
|
||||||
|
pub fn new() -> SharedBytesPool {
|
||||||
|
SharedBytesPool(RefCell::new(VecDeque::with_capacity(128)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_bytes(&self) -> Rc<BytesMut> {
|
||||||
|
if let Some(bytes) = self.0.borrow_mut().pop_front() {
|
||||||
|
bytes
|
||||||
|
} else {
|
||||||
|
Rc::new(BytesMut::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release_bytes(&self, mut bytes: Rc<BytesMut>) {
|
||||||
|
let v = &mut self.0.borrow_mut();
|
||||||
|
if v.len() < 128 {
|
||||||
|
Rc::get_mut(&mut bytes).unwrap().take();
|
||||||
|
v.push_front(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct SharedBytes(
|
||||||
|
Option<Rc<BytesMut>>, Option<Rc<SharedBytesPool>>);
|
||||||
|
|
||||||
|
impl Drop for SharedBytes {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(ref pool) = self.1 {
|
||||||
|
if let Some(bytes) = self.0.take() {
|
||||||
|
if Rc::strong_count(&bytes) == 1 {
|
||||||
|
pool.release_bytes(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SharedBytes {
|
||||||
|
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
SharedBytes(None, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(bytes: Rc<BytesMut>, pool: Rc<SharedBytesPool>) -> SharedBytes {
|
||||||
|
SharedBytes(Some(bytes), Some(pool))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
#[allow(mutable_transmutes)]
|
||||||
|
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
|
||||||
|
pub fn get_mut(&self) -> &mut BytesMut {
|
||||||
|
let r: &BytesMut = self.0.as_ref().unwrap().as_ref();
|
||||||
|
unsafe{mem::transmute(r)}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn get_ref(&self) -> &BytesMut {
|
||||||
|
self.0.as_ref().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SharedBytes {
|
||||||
|
fn default() -> Self {
|
||||||
|
SharedBytes(Some(Rc::new(BytesMut::new())), None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for SharedBytes {
|
||||||
|
fn clone(&self) -> SharedBytes {
|
||||||
|
SharedBytes(self.0.clone(), self.1.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal use only! unsafe
|
||||||
|
pub(crate) struct SharedMessagePool(RefCell<VecDeque<Rc<HttpMessage>>>);
|
||||||
|
|
||||||
|
impl SharedMessagePool {
|
||||||
|
pub fn new() -> SharedMessagePool {
|
||||||
|
SharedMessagePool(RefCell::new(VecDeque::with_capacity(128)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self) -> Rc<HttpMessage> {
|
||||||
|
if let Some(msg) = self.0.borrow_mut().pop_front() {
|
||||||
|
msg
|
||||||
|
} else {
|
||||||
|
Rc::new(HttpMessage::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release(&self, mut msg: Rc<HttpMessage>) {
|
||||||
|
let v = &mut self.0.borrow_mut();
|
||||||
|
if v.len() < 128 {
|
||||||
|
Rc::get_mut(&mut msg).unwrap().reset();
|
||||||
|
v.push_front(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct SharedHttpMessage(
|
||||||
|
Option<Rc<HttpMessage>>, Option<Rc<SharedMessagePool>>);
|
||||||
|
|
||||||
|
impl Drop for SharedHttpMessage {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(ref pool) = self.1 {
|
||||||
|
if let Some(msg) = self.0.take() {
|
||||||
|
if Rc::strong_count(&msg) == 1 {
|
||||||
|
pool.release(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for SharedHttpMessage {
|
||||||
|
type Target = HttpMessage;
|
||||||
|
|
||||||
|
fn deref(&self) -> &HttpMessage {
|
||||||
|
self.get_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DerefMut for SharedHttpMessage {
|
||||||
|
|
||||||
|
fn deref_mut(&mut self) -> &mut HttpMessage {
|
||||||
|
self.get_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for SharedHttpMessage {
|
||||||
|
|
||||||
|
fn clone(&self) -> SharedHttpMessage {
|
||||||
|
SharedHttpMessage(self.0.clone(), self.1.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SharedHttpMessage {
|
||||||
|
|
||||||
|
fn default() -> SharedHttpMessage {
|
||||||
|
SharedHttpMessage(Some(Rc::new(HttpMessage::default())), None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SharedHttpMessage {
|
||||||
|
|
||||||
|
pub fn from_message(msg: HttpMessage) -> SharedHttpMessage {
|
||||||
|
SharedHttpMessage(Some(Rc::new(msg)), None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(msg: Rc<HttpMessage>, pool: Rc<SharedMessagePool>) -> SharedHttpMessage {
|
||||||
|
SharedHttpMessage(Some(msg), Some(pool))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
#[allow(mutable_transmutes)]
|
||||||
|
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
|
||||||
|
pub fn get_mut(&self) -> &mut HttpMessage {
|
||||||
|
let r: &HttpMessage = self.0.as_ref().unwrap().as_ref();
|
||||||
|
unsafe{mem::transmute(r)}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||||
|
pub fn get_ref(&self) -> &HttpMessage {
|
||||||
|
self.0.as_ref().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEC_DIGITS_LUT: &[u8] =
|
||||||
|
b"0001020304050607080910111213141516171819\
|
||||||
|
2021222324252627282930313233343536373839\
|
||||||
|
4041424344454647484950515253545556575859\
|
||||||
|
6061626364656667686970717273747576777879\
|
||||||
|
8081828384858687888990919293949596979899";
|
||||||
|
|
||||||
|
pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesMut) {
|
||||||
|
let mut buf: [u8; 13] = [b'H', b'T', b'T', b'P', b'/', b'1', b'.', b'1',
|
||||||
|
b' ', b' ', b' ', b' ', b' '];
|
||||||
|
match version {
|
||||||
|
Version::HTTP_2 => buf[5] = b'2',
|
||||||
|
Version::HTTP_10 => buf[7] = b'0',
|
||||||
|
Version::HTTP_09 => {buf[5] = b'0'; buf[7] = b'9';},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut curr: isize = 12;
|
||||||
|
let buf_ptr = buf.as_mut_ptr();
|
||||||
|
let lut_ptr = DEC_DIGITS_LUT.as_ptr();
|
||||||
|
let four = n > 999;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// decode 2 more chars, if > 2 chars
|
||||||
|
let d1 = (n % 100) << 1;
|
||||||
|
n /= 100;
|
||||||
|
curr -= 2;
|
||||||
|
ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2);
|
||||||
|
|
||||||
|
// decode last 1 or 2 chars
|
||||||
|
if n < 10 {
|
||||||
|
curr -= 1;
|
||||||
|
*buf_ptr.offset(curr) = (n as u8) + b'0';
|
||||||
|
} else {
|
||||||
|
let d1 = n << 1;
|
||||||
|
curr -= 2;
|
||||||
|
ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes.extend_from_slice(&buf);
|
||||||
|
if four {
|
||||||
|
bytes.put(b' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
|
||||||
|
if n < 10 {
|
||||||
|
let mut buf: [u8; 21] = [b'\r',b'\n',b'c',b'o',b'n',b't',b'e',
|
||||||
|
b'n',b't',b'-',b'l',b'e',b'n',b'g',
|
||||||
|
b't',b'h',b':',b' ',b'0',b'\r',b'\n'];
|
||||||
|
buf[18] = (n as u8) + b'0';
|
||||||
|
bytes.extend_from_slice(&buf);
|
||||||
|
} else if n < 100 {
|
||||||
|
let mut buf: [u8; 22] = [b'\r',b'\n',b'c',b'o',b'n',b't',b'e',
|
||||||
|
b'n',b't',b'-',b'l',b'e',b'n',b'g',
|
||||||
|
b't',b'h',b':',b' ',b'0',b'0',b'\r',b'\n'];
|
||||||
|
let d1 = n << 1;
|
||||||
|
unsafe {
|
||||||
|
ptr::copy_nonoverlapping(
|
||||||
|
DEC_DIGITS_LUT.as_ptr().offset(d1 as isize), buf.as_mut_ptr().offset(18), 2);
|
||||||
|
}
|
||||||
|
bytes.extend_from_slice(&buf);
|
||||||
|
} else if n < 1000 {
|
||||||
|
let mut buf: [u8; 23] = [b'\r',b'\n',b'c',b'o',b'n',b't',b'e',
|
||||||
|
b'n',b't',b'-',b'l',b'e',b'n',b'g',
|
||||||
|
b't',b'h',b':',b' ',b'0',b'0',b'0',b'\r',b'\n'];
|
||||||
|
// decode 2 more chars, if > 2 chars
|
||||||
|
let d1 = (n % 100) << 1;
|
||||||
|
n /= 100;
|
||||||
|
unsafe {ptr::copy_nonoverlapping(
|
||||||
|
DEC_DIGITS_LUT.as_ptr().offset(d1 as isize), buf.as_mut_ptr().offset(19), 2)};
|
||||||
|
|
||||||
|
// decode last 1
|
||||||
|
buf[18] = (n as u8) + b'0';
|
||||||
|
|
||||||
|
bytes.extend_from_slice(&buf);
|
||||||
|
} else {
|
||||||
|
bytes.extend_from_slice(b"\r\ncontent-length: ");
|
||||||
|
convert_usize(n, bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) {
|
||||||
|
let mut curr: isize = 39;
|
||||||
|
let mut buf: [u8; 41] = unsafe { mem::uninitialized() };
|
||||||
|
buf[39] = b'\r';
|
||||||
|
buf[40] = b'\n';
|
||||||
|
let buf_ptr = buf.as_mut_ptr();
|
||||||
|
let lut_ptr = DEC_DIGITS_LUT.as_ptr();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// eagerly decode 4 characters at a time
|
||||||
|
while n >= 10_000 {
|
||||||
|
let rem = (n % 10_000) as isize;
|
||||||
|
n /= 10_000;
|
||||||
|
|
||||||
|
let d1 = (rem / 100) << 1;
|
||||||
|
let d2 = (rem % 100) << 1;
|
||||||
|
curr -= 4;
|
||||||
|
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
|
||||||
|
ptr::copy_nonoverlapping(lut_ptr.offset(d2), buf_ptr.offset(curr + 2), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we reach here numbers are <= 9999, so at most 4 chars long
|
||||||
|
let mut n = n as isize; // possibly reduce 64bit math
|
||||||
|
|
||||||
|
// decode 2 more chars, if > 2 chars
|
||||||
|
if n >= 100 {
|
||||||
|
let d1 = (n % 100) << 1;
|
||||||
|
n /= 100;
|
||||||
|
curr -= 2;
|
||||||
|
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode last 1 or 2 chars
|
||||||
|
if n < 10 {
|
||||||
|
curr -= 1;
|
||||||
|
*buf_ptr.offset(curr) = (n as u8) + b'0';
|
||||||
|
} else {
|
||||||
|
let d1 = n << 1;
|
||||||
|
curr -= 2;
|
||||||
|
ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
bytes.extend_from_slice(
|
||||||
|
slice::from_raw_parts(buf_ptr.offset(curr), 41 - curr as usize));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_date_len() {
|
||||||
|
assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_date() {
|
||||||
|
let mut buf1 = BytesMut::new();
|
||||||
|
date(&mut buf1);
|
||||||
|
let mut buf2 = BytesMut::new();
|
||||||
|
date(&mut buf2);
|
||||||
|
assert_eq!(buf1, buf2);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_write_content_length() {
|
||||||
|
let mut bytes = BytesMut::new();
|
||||||
|
write_content_length(0, &mut bytes);
|
||||||
|
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 0\r\n"[..]);
|
||||||
|
write_content_length(9, &mut bytes);
|
||||||
|
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 9\r\n"[..]);
|
||||||
|
write_content_length(10, &mut bytes);
|
||||||
|
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 10\r\n"[..]);
|
||||||
|
write_content_length(99, &mut bytes);
|
||||||
|
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 99\r\n"[..]);
|
||||||
|
write_content_length(100, &mut bytes);
|
||||||
|
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 100\r\n"[..]);
|
||||||
|
write_content_length(101, &mut bytes);
|
||||||
|
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 101\r\n"[..]);
|
||||||
|
write_content_length(998, &mut bytes);
|
||||||
|
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 998\r\n"[..]);
|
||||||
|
write_content_length(1000, &mut bytes);
|
||||||
|
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1000\r\n"[..]);
|
||||||
|
write_content_length(1001, &mut bytes);
|
||||||
|
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 1001\r\n"[..]);
|
||||||
|
write_content_length(5909, &mut bytes);
|
||||||
|
assert_eq!(bytes.take().freeze(), b"\r\ncontent-length: 5909\r\n"[..]);
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,9 @@
|
|||||||
//! Basic http responses
|
//! Basic http responses
|
||||||
#![allow(non_upper_case_globals)]
|
#![allow(non_upper_case_globals)]
|
||||||
use http::StatusCode;
|
use http::{StatusCode, Error as HttpError};
|
||||||
|
|
||||||
use body::Body;
|
use body::Body;
|
||||||
use route::Reply;
|
use handler::{Reply, Handler, RouteHandler, Responder};
|
||||||
use route::RouteHandler;
|
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::{HttpResponse, HttpResponseBuilder};
|
use httpresponse::{HttpResponse, HttpResponseBuilder};
|
||||||
|
|
||||||
@ -55,9 +54,6 @@ impl StaticResponse {
|
|||||||
pub fn build(&self) -> HttpResponseBuilder {
|
pub fn build(&self) -> HttpResponseBuilder {
|
||||||
HttpResponse::build(self.0)
|
HttpResponse::build(self.0)
|
||||||
}
|
}
|
||||||
pub fn response(&self) -> HttpResponse {
|
|
||||||
HttpResponse::new(self.0, Body::Empty)
|
|
||||||
}
|
|
||||||
pub fn with_reason(self, reason: &'static str) -> HttpResponse {
|
pub fn with_reason(self, reason: &'static str) -> HttpResponse {
|
||||||
let mut resp = HttpResponse::new(self.0, Body::Empty);
|
let mut resp = HttpResponse::new(self.0, Body::Empty);
|
||||||
resp.set_reason(reason);
|
resp.set_reason(reason);
|
||||||
@ -68,15 +64,38 @@ impl StaticResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<S> Handler<S> for StaticResponse {
|
||||||
|
type Result = HttpResponse;
|
||||||
|
|
||||||
|
fn handle(&mut self, _: HttpRequest<S>) -> HttpResponse {
|
||||||
|
HttpResponse::new(self.0, Body::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<S> RouteHandler<S> for StaticResponse {
|
impl<S> RouteHandler<S> for StaticResponse {
|
||||||
fn handle(&self, _: HttpRequest<S>) -> Reply {
|
fn handle(&mut self, _: HttpRequest<S>) -> Reply {
|
||||||
Reply::response(HttpResponse::new(self.0, Body::Empty))
|
Reply::response(HttpResponse::new(self.0, Body::Empty))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Responder for StaticResponse {
|
||||||
|
type Item = HttpResponse;
|
||||||
|
type Error = HttpError;
|
||||||
|
|
||||||
|
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, HttpError> {
|
||||||
|
self.build().body(Body::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<StaticResponse> for HttpResponse {
|
impl From<StaticResponse> for HttpResponse {
|
||||||
fn from(st: StaticResponse) -> Self {
|
fn from(st: StaticResponse) -> Self {
|
||||||
st.response()
|
HttpResponse::new(st.0, Body::Empty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StaticResponse> for Reply {
|
||||||
|
fn from(st: StaticResponse) -> Self {
|
||||||
|
HttpResponse::new(st.0, Body::Empty).into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +156,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_response() {
|
fn test_response() {
|
||||||
let resp = HTTPOk.response();
|
let resp: HttpResponse = HTTPOk.into();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,8 +168,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_with_reason() {
|
fn test_with_reason() {
|
||||||
let resp = HTTPOk.response();
|
let resp: HttpResponse = HTTPOk.into();
|
||||||
assert_eq!(resp.reason(), "");
|
assert_eq!(resp.reason(), "OK");
|
||||||
|
|
||||||
let resp = HTTPBadRequest.with_reason("test");
|
let resp = HTTPBadRequest.with_reason("test");
|
||||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,8 @@
|
|||||||
//! Pieces pertaining to the HTTP response.
|
//! Pieces pertaining to the HTTP response.
|
||||||
use std::{io, mem, str, fmt};
|
use std::{mem, str, fmt};
|
||||||
|
use std::cell::RefCell;
|
||||||
use std::convert::Into;
|
use std::convert::Into;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
use cookie::CookieJar;
|
use cookie::CookieJar;
|
||||||
use bytes::{Bytes, BytesMut};
|
use bytes::{Bytes, BytesMut};
|
||||||
@ -8,11 +10,13 @@ use http::{StatusCode, Version, HeaderMap, HttpTryFrom, Error as HttpError};
|
|||||||
use http::header::{self, HeaderName, HeaderValue};
|
use http::header::{self, HeaderName, HeaderValue};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use cookie::Cookie;
|
||||||
|
|
||||||
use Cookie;
|
|
||||||
use body::Body;
|
use body::Body;
|
||||||
use error::Error;
|
use error::Error;
|
||||||
|
use handler::Responder;
|
||||||
use encoding::ContentEncoding;
|
use encoding::ContentEncoding;
|
||||||
|
use httprequest::HttpRequest;
|
||||||
|
|
||||||
/// Represents various types of connection
|
/// Represents various types of connection
|
||||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||||
@ -26,122 +30,122 @@ pub enum ConnectionType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An HTTP Response
|
/// An HTTP Response
|
||||||
pub struct HttpResponse {
|
pub struct HttpResponse(Option<Box<InnerHttpResponse>>);
|
||||||
pub version: Option<Version>,
|
|
||||||
pub headers: HeaderMap,
|
impl Drop for HttpResponse {
|
||||||
pub status: StatusCode,
|
fn drop(&mut self) {
|
||||||
reason: Option<&'static str>,
|
if let Some(inner) = self.0.take() {
|
||||||
body: Body,
|
Pool::release(inner)
|
||||||
chunked: bool,
|
}
|
||||||
encoding: ContentEncoding,
|
}
|
||||||
connection_type: Option<ConnectionType>,
|
|
||||||
response_size: u64,
|
|
||||||
error: Option<Error>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HttpResponse {
|
impl HttpResponse {
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||||
|
fn get_ref(&self) -> &InnerHttpResponse {
|
||||||
|
self.0.as_ref().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||||
|
fn get_mut(&mut self) -> &mut InnerHttpResponse {
|
||||||
|
self.0.as_mut().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
/// Create http response builder with specific status.
|
/// Create http response builder with specific status.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn build(status: StatusCode) -> HttpResponseBuilder {
|
pub fn build(status: StatusCode) -> HttpResponseBuilder {
|
||||||
HttpResponseBuilder {
|
HttpResponseBuilder {
|
||||||
parts: Some(Parts::new(status)),
|
response: Some(Pool::get(status)),
|
||||||
err: None,
|
err: None,
|
||||||
|
cookies: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a response
|
/// Constructs a response
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new(status: StatusCode, body: Body) -> HttpResponse {
|
pub fn new(status: StatusCode, body: Body) -> HttpResponse {
|
||||||
HttpResponse {
|
HttpResponse(Some(Pool::with_body(status, body)))
|
||||||
version: None,
|
|
||||||
headers: Default::default(),
|
|
||||||
status: status,
|
|
||||||
reason: None,
|
|
||||||
body: body,
|
|
||||||
chunked: false,
|
|
||||||
encoding: ContentEncoding::Auto,
|
|
||||||
connection_type: None,
|
|
||||||
response_size: 0,
|
|
||||||
error: None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a error response
|
/// Constructs a error response
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn from_error(error: Error) -> HttpResponse {
|
pub fn from_error(error: Error) -> HttpResponse {
|
||||||
let mut resp = error.cause().error_response();
|
let mut resp = error.cause().error_response();
|
||||||
resp.error = Some(error);
|
resp.get_mut().error = Some(error);
|
||||||
resp
|
resp
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The source `error` for this response
|
/// The source `error` for this response
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn error(&self) -> Option<&Error> {
|
pub fn error(&self) -> Option<&Error> {
|
||||||
self.error.as_ref()
|
self.get_ref().error.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the HTTP version of this response.
|
/// Get the HTTP version of this response.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn version(&self) -> Option<Version> {
|
pub fn version(&self) -> Option<Version> {
|
||||||
self.version
|
self.get_ref().version
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the headers from the response.
|
/// Get the headers from the response.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn headers(&self) -> &HeaderMap {
|
pub fn headers(&self) -> &HeaderMap {
|
||||||
&self.headers
|
&self.get_ref().headers
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a mutable reference to the headers.
|
/// Get a mutable reference to the headers.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||||
&mut self.headers
|
&mut self.get_mut().headers
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the status from the server.
|
/// Get the status from the server.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn status(&self) -> StatusCode {
|
pub fn status(&self) -> StatusCode {
|
||||||
self.status
|
self.get_ref().status
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the `StatusCode` for this response.
|
/// Set the `StatusCode` for this response.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn status_mut(&mut self) -> &mut StatusCode {
|
pub fn status_mut(&mut self) -> &mut StatusCode {
|
||||||
&mut self.status
|
&mut self.get_mut().status
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get custom reason for the response.
|
/// Get custom reason for the response.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn reason(&self) -> &str {
|
pub fn reason(&self) -> &str {
|
||||||
if let Some(reason) = self.reason {
|
if let Some(reason) = self.get_ref().reason {
|
||||||
reason
|
reason
|
||||||
} else {
|
} else {
|
||||||
""
|
self.get_ref().status.canonical_reason().unwrap_or("<unknown status code>")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the custom reason for the response.
|
/// Set the custom reason for the response.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_reason(&mut self, reason: &'static str) -> &mut Self {
|
pub fn set_reason(&mut self, reason: &'static str) -> &mut Self {
|
||||||
self.reason = Some(reason);
|
self.get_mut().reason = Some(reason);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set connection type
|
/// Set connection type
|
||||||
pub fn set_connection_type(&mut self, conn: ConnectionType) -> &mut Self {
|
pub fn set_connection_type(&mut self, conn: ConnectionType) -> &mut Self {
|
||||||
self.connection_type = Some(conn);
|
self.get_mut().connection_type = Some(conn);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Connection upgrade status
|
/// Connection upgrade status
|
||||||
|
#[inline]
|
||||||
pub fn upgrade(&self) -> bool {
|
pub fn upgrade(&self) -> bool {
|
||||||
self.connection_type == Some(ConnectionType::Upgrade)
|
self.get_ref().connection_type == Some(ConnectionType::Upgrade)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Keep-alive status for this connection
|
/// Keep-alive status for this connection
|
||||||
pub fn keep_alive(&self) -> Option<bool> {
|
pub fn keep_alive(&self) -> Option<bool> {
|
||||||
if let Some(ct) = self.connection_type {
|
if let Some(ct) = self.get_ref().connection_type {
|
||||||
match ct {
|
match ct {
|
||||||
ConnectionType::KeepAlive => Some(true),
|
ConnectionType::KeepAlive => Some(true),
|
||||||
ConnectionType::Close | ConnectionType::Upgrade => Some(false),
|
ConnectionType::Close | ConnectionType::Upgrade => Some(false),
|
||||||
@ -152,66 +156,59 @@ impl HttpResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// is chunked encoding enabled
|
/// is chunked encoding enabled
|
||||||
|
#[inline]
|
||||||
pub fn chunked(&self) -> bool {
|
pub fn chunked(&self) -> bool {
|
||||||
self.chunked
|
self.get_ref().chunked
|
||||||
}
|
|
||||||
|
|
||||||
/// Enables automatic chunked transfer encoding
|
|
||||||
pub fn enable_chunked_encoding(&mut self) -> Result<(), io::Error> {
|
|
||||||
if self.headers.contains_key(header::CONTENT_LENGTH) {
|
|
||||||
Err(io::Error::new(io::ErrorKind::Other,
|
|
||||||
"You can't enable chunked encoding when a content length is set"))
|
|
||||||
} else {
|
|
||||||
self.chunked = true;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Content encoding
|
/// Content encoding
|
||||||
|
#[inline]
|
||||||
pub fn content_encoding(&self) -> &ContentEncoding {
|
pub fn content_encoding(&self) -> &ContentEncoding {
|
||||||
&self.encoding
|
&self.get_ref().encoding
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set content encoding
|
/// Set content encoding
|
||||||
pub fn set_content_encoding(&mut self, enc: ContentEncoding) -> &mut Self {
|
pub fn set_content_encoding(&mut self, enc: ContentEncoding) -> &mut Self {
|
||||||
self.encoding = enc;
|
self.get_mut().encoding = enc;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get body os this response
|
/// Get body os this response
|
||||||
|
#[inline]
|
||||||
pub fn body(&self) -> &Body {
|
pub fn body(&self) -> &Body {
|
||||||
&self.body
|
&self.get_ref().body
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a body
|
/// Set a body
|
||||||
pub fn set_body<B: Into<Body>>(&mut self, body: B) {
|
pub fn set_body<B: Into<Body>>(&mut self, body: B) {
|
||||||
self.body = body.into();
|
self.get_mut().body = body.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a body and return previous body value
|
/// Set a body and return previous body value
|
||||||
pub fn replace_body<B: Into<Body>>(&mut self, body: B) -> Body {
|
pub fn replace_body<B: Into<Body>>(&mut self, body: B) -> Body {
|
||||||
mem::replace(&mut self.body, body.into())
|
mem::replace(&mut self.get_mut().body, body.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Size of response in bytes, excluding HTTP headers
|
/// Size of response in bytes, excluding HTTP headers
|
||||||
pub fn response_size(&self) -> u64 {
|
pub fn response_size(&self) -> u64 {
|
||||||
self.response_size
|
self.get_ref().response_size
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set content encoding
|
/// Set content encoding
|
||||||
pub(crate) fn set_response_size(&mut self, size: u64) {
|
pub(crate) fn set_response_size(&mut self, size: u64) {
|
||||||
self.response_size = size;
|
self.get_mut().response_size = size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for HttpResponse {
|
impl fmt::Debug for HttpResponse {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let res = write!(f, "\nHttpResponse {:?} {}{}\n",
|
let res = write!(f, "\nHttpResponse {:?} {}{}\n",
|
||||||
self.version, self.status, self.reason.unwrap_or(""));
|
self.get_ref().version, self.get_ref().status,
|
||||||
let _ = write!(f, " encoding: {:?}\n", self.encoding);
|
self.get_ref().reason.unwrap_or(""));
|
||||||
|
let _ = write!(f, " encoding: {:?}\n", self.get_ref().encoding);
|
||||||
let _ = write!(f, " headers:\n");
|
let _ = write!(f, " headers:\n");
|
||||||
for key in self.headers.keys() {
|
for key in self.get_ref().headers.keys() {
|
||||||
let vals: Vec<_> = self.headers.get_all(key).iter().collect();
|
let vals: Vec<_> = self.get_ref().headers.get_all(key).iter().collect();
|
||||||
if vals.len() > 1 {
|
if vals.len() > 1 {
|
||||||
let _ = write!(f, " {:?}: {:?}\n", key, vals);
|
let _ = write!(f, " {:?}: {:?}\n", key, vals);
|
||||||
} else {
|
} else {
|
||||||
@ -222,70 +219,53 @@ impl fmt::Debug for HttpResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct Parts {
|
|
||||||
version: Option<Version>,
|
|
||||||
headers: HeaderMap,
|
|
||||||
status: StatusCode,
|
|
||||||
reason: Option<&'static str>,
|
|
||||||
chunked: bool,
|
|
||||||
encoding: ContentEncoding,
|
|
||||||
connection_type: Option<ConnectionType>,
|
|
||||||
cookies: CookieJar,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Parts {
|
|
||||||
fn new(status: StatusCode) -> Self {
|
|
||||||
Parts {
|
|
||||||
version: None,
|
|
||||||
headers: HeaderMap::new(),
|
|
||||||
status: status,
|
|
||||||
reason: None,
|
|
||||||
chunked: false,
|
|
||||||
encoding: ContentEncoding::Auto,
|
|
||||||
connection_type: None,
|
|
||||||
cookies: CookieJar::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// An HTTP response builder
|
/// An HTTP response builder
|
||||||
///
|
///
|
||||||
/// This type can be used to construct an instance of `HttpResponse` through a
|
/// This type can be used to construct an instance of `HttpResponse` through a
|
||||||
/// builder-like pattern.
|
/// builder-like pattern.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct HttpResponseBuilder {
|
pub struct HttpResponseBuilder {
|
||||||
parts: Option<Parts>,
|
response: Option<Box<InnerHttpResponse>>,
|
||||||
err: Option<HttpError>,
|
err: Option<HttpError>,
|
||||||
|
cookies: Option<CookieJar>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HttpResponseBuilder {
|
impl HttpResponseBuilder {
|
||||||
/// Set the HTTP version of this response.
|
/// Set HTTP version of this response.
|
||||||
|
///
|
||||||
|
/// By default response's http version depends on request's version.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn version(&mut self, version: Version) -> &mut Self {
|
pub fn version(&mut self, version: Version) -> &mut Self {
|
||||||
if let Some(parts) = parts(&mut self.parts, &self.err) {
|
if let Some(parts) = parts(&mut self.response, &self.err) {
|
||||||
parts.version = Some(version);
|
parts.version = Some(version);
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the `StatusCode` for this response.
|
|
||||||
#[inline]
|
|
||||||
pub fn status(&mut self, status: StatusCode) -> &mut Self {
|
|
||||||
if let Some(parts) = parts(&mut self.parts, &self.err) {
|
|
||||||
parts.status = status;
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a header.
|
/// Set a header.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate http;
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// # use actix_web::*;
|
||||||
|
/// # use actix_web::httpcodes::*;
|
||||||
|
/// #
|
||||||
|
/// use http::header;
|
||||||
|
///
|
||||||
|
/// fn index(req: HttpRequest) -> Result<HttpResponse> {
|
||||||
|
/// Ok(HTTPOk.build()
|
||||||
|
/// .header("X-TEST", "value")
|
||||||
|
/// .header(header::CONTENT_TYPE, "application/json")
|
||||||
|
/// .finish()?)
|
||||||
|
/// }
|
||||||
|
/// fn main() {}
|
||||||
|
/// ```
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
|
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
|
||||||
where HeaderName: HttpTryFrom<K>,
|
where HeaderName: HttpTryFrom<K>,
|
||||||
HeaderValue: HttpTryFrom<V>
|
HeaderValue: HttpTryFrom<V>
|
||||||
{
|
{
|
||||||
if let Some(parts) = parts(&mut self.parts, &self.err) {
|
if let Some(parts) = parts(&mut self.response, &self.err) {
|
||||||
match HeaderName::try_from(key) {
|
match HeaderName::try_from(key) {
|
||||||
Ok(key) => {
|
Ok(key) => {
|
||||||
match HeaderValue::try_from(value) {
|
match HeaderValue::try_from(value) {
|
||||||
@ -302,7 +282,7 @@ impl HttpResponseBuilder {
|
|||||||
/// Set the custom reason for the response.
|
/// Set the custom reason for the response.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn reason(&mut self, reason: &'static str) -> &mut Self {
|
pub fn reason(&mut self, reason: &'static str) -> &mut Self {
|
||||||
if let Some(parts) = parts(&mut self.parts, &self.err) {
|
if let Some(parts) = parts(&mut self.response, &self.err) {
|
||||||
parts.reason = Some(reason);
|
parts.reason = Some(reason);
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
@ -313,44 +293,52 @@ impl HttpResponseBuilder {
|
|||||||
/// By default `ContentEncoding::Auto` is used, which automatically
|
/// By default `ContentEncoding::Auto` is used, which automatically
|
||||||
/// negotiates content encoding based on request's `Accept-Encoding` headers.
|
/// negotiates content encoding based on request's `Accept-Encoding` headers.
|
||||||
/// To enforce specific encoding, use specific ContentEncoding` value.
|
/// To enforce specific encoding, use specific ContentEncoding` value.
|
||||||
|
#[inline]
|
||||||
pub fn content_encoding(&mut self, enc: ContentEncoding) -> &mut Self {
|
pub fn content_encoding(&mut self, enc: ContentEncoding) -> &mut Self {
|
||||||
if let Some(parts) = parts(&mut self.parts, &self.err) {
|
if let Some(parts) = parts(&mut self.response, &self.err) {
|
||||||
parts.encoding = enc;
|
parts.encoding = enc;
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set connection type
|
/// Set connection type
|
||||||
|
#[inline]
|
||||||
|
#[doc(hidden)]
|
||||||
pub fn connection_type(&mut self, conn: ConnectionType) -> &mut Self {
|
pub fn connection_type(&mut self, conn: ConnectionType) -> &mut Self {
|
||||||
if let Some(parts) = parts(&mut self.parts, &self.err) {
|
if let Some(parts) = parts(&mut self.response, &self.err) {
|
||||||
parts.connection_type = Some(conn);
|
parts.connection_type = Some(conn);
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set connection type to Upgrade
|
/// Set connection type to Upgrade
|
||||||
|
#[inline]
|
||||||
|
#[doc(hidden)]
|
||||||
pub fn upgrade(&mut self) -> &mut Self {
|
pub fn upgrade(&mut self) -> &mut Self {
|
||||||
self.connection_type(ConnectionType::Upgrade)
|
self.connection_type(ConnectionType::Upgrade)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Force close connection, even if it is marked as keep-alive
|
/// Force close connection, even if it is marked as keep-alive
|
||||||
|
#[inline]
|
||||||
pub fn force_close(&mut self) -> &mut Self {
|
pub fn force_close(&mut self) -> &mut Self {
|
||||||
self.connection_type(ConnectionType::Close)
|
self.connection_type(ConnectionType::Close)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Enables automatic chunked transfer encoding
|
/// Enables automatic chunked transfer encoding
|
||||||
pub fn enable_chunked(&mut self) -> &mut Self {
|
#[inline]
|
||||||
if let Some(parts) = parts(&mut self.parts, &self.err) {
|
pub fn chunked(&mut self) -> &mut Self {
|
||||||
|
if let Some(parts) = parts(&mut self.response, &self.err) {
|
||||||
parts.chunked = true;
|
parts.chunked = true;
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set response content type
|
/// Set response content type
|
||||||
|
#[inline]
|
||||||
pub fn content_type<V>(&mut self, value: V) -> &mut Self
|
pub fn content_type<V>(&mut self, value: V) -> &mut Self
|
||||||
where HeaderValue: HttpTryFrom<V>
|
where HeaderValue: HttpTryFrom<V>
|
||||||
{
|
{
|
||||||
if let Some(parts) = parts(&mut self.parts, &self.err) {
|
if let Some(parts) = parts(&mut self.response, &self.err) {
|
||||||
match HeaderValue::try_from(value) {
|
match HeaderValue::try_from(value) {
|
||||||
Ok(value) => { parts.headers.insert(header::CONTENT_TYPE, value); },
|
Ok(value) => { parts.headers.insert(header::CONTENT_TYPE, value); },
|
||||||
Err(e) => self.err = Some(e.into()),
|
Err(e) => self.err = Some(e.into()),
|
||||||
@ -360,23 +348,53 @@ impl HttpResponseBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set a cookie
|
/// Set a cookie
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// # use actix_web::*;
|
||||||
|
/// # use actix_web::httpcodes::*;
|
||||||
|
/// #
|
||||||
|
/// use actix_web::headers::Cookie;
|
||||||
|
///
|
||||||
|
/// fn index(req: HttpRequest) -> Result<HttpResponse> {
|
||||||
|
/// Ok(HTTPOk.build()
|
||||||
|
/// .cookie(
|
||||||
|
/// Cookie::build("name", "value")
|
||||||
|
/// .domain("www.rust-lang.org")
|
||||||
|
/// .path("/")
|
||||||
|
/// .secure(true)
|
||||||
|
/// .http_only(true)
|
||||||
|
/// .finish())
|
||||||
|
/// .finish()?)
|
||||||
|
/// }
|
||||||
|
/// fn main() {}
|
||||||
|
/// ```
|
||||||
pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self {
|
pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self {
|
||||||
if let Some(parts) = parts(&mut self.parts, &self.err) {
|
if self.cookies.is_none() {
|
||||||
parts.cookies.add(cookie.into_owned());
|
let mut jar = CookieJar::new();
|
||||||
|
jar.add(cookie.into_owned());
|
||||||
|
self.cookies = Some(jar)
|
||||||
|
} else {
|
||||||
|
self.cookies.as_mut().unwrap().add(cookie.into_owned());
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remote cookie, cookie has to be cookie from `HttpRequest::cookies()` method.
|
/// Remove cookie, cookie has to be cookie from `HttpRequest::cookies()` method.
|
||||||
pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self {
|
pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self {
|
||||||
if let Some(parts) = parts(&mut self.parts, &self.err) {
|
{
|
||||||
|
if self.cookies.is_none() {
|
||||||
|
self.cookies = Some(CookieJar::new())
|
||||||
|
}
|
||||||
|
let jar = self.cookies.as_mut().unwrap();
|
||||||
let cookie = cookie.clone().into_owned();
|
let cookie = cookie.clone().into_owned();
|
||||||
parts.cookies.add_original(cookie.clone());
|
jar.add_original(cookie.clone());
|
||||||
parts.cookies.remove(cookie);
|
jar.remove(cookie);
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This method calls provided closure with builder reference if value is true.
|
||||||
pub fn if_true<F>(&mut self, value: bool, f: F) -> &mut Self
|
pub fn if_true<F>(&mut self, value: bool, f: F) -> &mut Self
|
||||||
where F: Fn(&mut HttpResponseBuilder) + 'static
|
where F: Fn(&mut HttpResponseBuilder) + 'static
|
||||||
{
|
{
|
||||||
@ -387,36 +405,31 @@ impl HttpResponseBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set a body and generate `HttpResponse`.
|
/// Set a body and generate `HttpResponse`.
|
||||||
|
///
|
||||||
/// `HttpResponseBuilder` can not be used after this call.
|
/// `HttpResponseBuilder` can not be used after this call.
|
||||||
pub fn body<B: Into<Body>>(&mut self, body: B) -> Result<HttpResponse, HttpError> {
|
pub fn body<B: Into<Body>>(&mut self, body: B) -> Result<HttpResponse, HttpError> {
|
||||||
let mut parts = self.parts.take().expect("cannot reuse response builder");
|
|
||||||
if let Some(e) = self.err.take() {
|
if let Some(e) = self.err.take() {
|
||||||
return Err(e)
|
return Err(e)
|
||||||
}
|
}
|
||||||
for cookie in parts.cookies.delta() {
|
let mut response = self.response.take().expect("cannot reuse response builder");
|
||||||
parts.headers.append(
|
if let Some(ref jar) = self.cookies {
|
||||||
header::SET_COOKIE,
|
for cookie in jar.delta() {
|
||||||
HeaderValue::from_str(&cookie.to_string())?);
|
response.headers.append(
|
||||||
|
header::SET_COOKIE,
|
||||||
|
HeaderValue::from_str(&cookie.to_string())?);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(HttpResponse {
|
response.body = body.into();
|
||||||
version: parts.version,
|
Ok(HttpResponse(Some(response)))
|
||||||
headers: parts.headers,
|
|
||||||
status: parts.status,
|
|
||||||
reason: parts.reason,
|
|
||||||
body: body.into(),
|
|
||||||
chunked: parts.chunked,
|
|
||||||
encoding: parts.encoding,
|
|
||||||
connection_type: parts.connection_type,
|
|
||||||
response_size: 0,
|
|
||||||
error: None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set a json body and generate `HttpResponse`
|
/// Set a json body and generate `HttpResponse`
|
||||||
|
///
|
||||||
|
/// `HttpResponseBuilder` can not be used after this call.
|
||||||
pub fn json<T: Serialize>(&mut self, value: T) -> Result<HttpResponse, Error> {
|
pub fn json<T: Serialize>(&mut self, value: T) -> Result<HttpResponse, Error> {
|
||||||
let body = serde_json::to_string(&value)?;
|
let body = serde_json::to_string(&value)?;
|
||||||
|
|
||||||
let contains = if let Some(parts) = parts(&mut self.parts, &self.err) {
|
let contains = if let Some(parts) = parts(&mut self.response, &self.err) {
|
||||||
parts.headers.contains_key(header::CONTENT_TYPE)
|
parts.headers.contains_key(header::CONTENT_TYPE)
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
@ -429,12 +442,26 @@ impl HttpResponseBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set an empty body and generate `HttpResponse`
|
/// Set an empty body and generate `HttpResponse`
|
||||||
|
///
|
||||||
|
/// `HttpResponseBuilder` can not be used after this call.
|
||||||
pub fn finish(&mut self) -> Result<HttpResponse, HttpError> {
|
pub fn finish(&mut self) -> Result<HttpResponse, HttpError> {
|
||||||
self.body(Body::Empty)
|
self.body(Body::Empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This method construct new `HttpResponseBuilder`
|
||||||
|
pub fn take(&mut self) -> HttpResponseBuilder {
|
||||||
|
HttpResponseBuilder {
|
||||||
|
response: self.response.take(),
|
||||||
|
err: self.err.take(),
|
||||||
|
cookies: self.cookies.take(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parts<'a>(parts: &'a mut Option<Parts>, err: &Option<HttpError>) -> Option<&'a mut Parts>
|
#[inline]
|
||||||
|
#[cfg_attr(feature = "cargo-clippy", allow(borrowed_box))]
|
||||||
|
fn parts<'a>(parts: &'a mut Option<Box<InnerHttpResponse>>, err: &Option<HttpError>)
|
||||||
|
-> Option<&'a mut Box<InnerHttpResponse>>
|
||||||
{
|
{
|
||||||
if err.is_some() {
|
if err.is_some() {
|
||||||
return None
|
return None
|
||||||
@ -458,6 +485,16 @@ impl From<HttpResponseBuilder> for HttpResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Responder for HttpResponseBuilder {
|
||||||
|
type Item = HttpResponse;
|
||||||
|
type Error = HttpError;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn respond_to(mut self, _: HttpRequest) -> Result<HttpResponse, HttpError> {
|
||||||
|
self.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<&'static str> for HttpResponse {
|
impl From<&'static str> for HttpResponse {
|
||||||
fn from(val: &'static str) -> Self {
|
fn from(val: &'static str) -> Self {
|
||||||
HttpResponse::build(StatusCode::OK)
|
HttpResponse::build(StatusCode::OK)
|
||||||
@ -467,6 +504,17 @@ impl From<&'static str> for HttpResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Responder for &'static str {
|
||||||
|
type Item = HttpResponse;
|
||||||
|
type Error = HttpError;
|
||||||
|
|
||||||
|
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, HttpError> {
|
||||||
|
HttpResponse::build(StatusCode::OK)
|
||||||
|
.content_type("text/plain; charset=utf-8")
|
||||||
|
.body(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<&'static [u8]> for HttpResponse {
|
impl From<&'static [u8]> for HttpResponse {
|
||||||
fn from(val: &'static [u8]) -> Self {
|
fn from(val: &'static [u8]) -> Self {
|
||||||
HttpResponse::build(StatusCode::OK)
|
HttpResponse::build(StatusCode::OK)
|
||||||
@ -476,6 +524,17 @@ impl From<&'static [u8]> for HttpResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Responder for &'static [u8] {
|
||||||
|
type Item = HttpResponse;
|
||||||
|
type Error = HttpError;
|
||||||
|
|
||||||
|
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, HttpError> {
|
||||||
|
HttpResponse::build(StatusCode::OK)
|
||||||
|
.content_type("application/octet-stream")
|
||||||
|
.body(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<String> for HttpResponse {
|
impl From<String> for HttpResponse {
|
||||||
fn from(val: String) -> Self {
|
fn from(val: String) -> Self {
|
||||||
HttpResponse::build(StatusCode::OK)
|
HttpResponse::build(StatusCode::OK)
|
||||||
@ -485,6 +544,17 @@ impl From<String> for HttpResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Responder for String {
|
||||||
|
type Item = HttpResponse;
|
||||||
|
type Error = HttpError;
|
||||||
|
|
||||||
|
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, HttpError> {
|
||||||
|
HttpResponse::build(StatusCode::OK)
|
||||||
|
.content_type("text/plain; charset=utf-8")
|
||||||
|
.body(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a String> for HttpResponse {
|
impl<'a> From<&'a String> for HttpResponse {
|
||||||
fn from(val: &'a String) -> Self {
|
fn from(val: &'a String) -> Self {
|
||||||
HttpResponse::build(StatusCode::OK)
|
HttpResponse::build(StatusCode::OK)
|
||||||
@ -494,6 +564,17 @@ impl<'a> From<&'a String> for HttpResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> Responder for &'a String {
|
||||||
|
type Item = HttpResponse;
|
||||||
|
type Error = HttpError;
|
||||||
|
|
||||||
|
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, HttpError> {
|
||||||
|
HttpResponse::build(StatusCode::OK)
|
||||||
|
.content_type("text/plain; charset=utf-8")
|
||||||
|
.body(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<Bytes> for HttpResponse {
|
impl From<Bytes> for HttpResponse {
|
||||||
fn from(val: Bytes) -> Self {
|
fn from(val: Bytes) -> Self {
|
||||||
HttpResponse::build(StatusCode::OK)
|
HttpResponse::build(StatusCode::OK)
|
||||||
@ -503,6 +584,17 @@ impl From<Bytes> for HttpResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Responder for Bytes {
|
||||||
|
type Item = HttpResponse;
|
||||||
|
type Error = HttpError;
|
||||||
|
|
||||||
|
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, HttpError> {
|
||||||
|
HttpResponse::build(StatusCode::OK)
|
||||||
|
.content_type("application/octet-stream")
|
||||||
|
.body(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<BytesMut> for HttpResponse {
|
impl From<BytesMut> for HttpResponse {
|
||||||
fn from(val: BytesMut) -> Self {
|
fn from(val: BytesMut) -> Self {
|
||||||
HttpResponse::build(StatusCode::OK)
|
HttpResponse::build(StatusCode::OK)
|
||||||
@ -512,19 +604,160 @@ impl From<BytesMut> for HttpResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Responder for BytesMut {
|
||||||
|
type Item = HttpResponse;
|
||||||
|
type Error = HttpError;
|
||||||
|
|
||||||
|
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, HttpError> {
|
||||||
|
HttpResponse::build(StatusCode::OK)
|
||||||
|
.content_type("application/octet-stream")
|
||||||
|
.body(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct InnerHttpResponse {
|
||||||
|
version: Option<Version>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
status: StatusCode,
|
||||||
|
reason: Option<&'static str>,
|
||||||
|
body: Body,
|
||||||
|
chunked: bool,
|
||||||
|
encoding: ContentEncoding,
|
||||||
|
connection_type: Option<ConnectionType>,
|
||||||
|
response_size: u64,
|
||||||
|
error: Option<Error>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InnerHttpResponse {
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn new(status: StatusCode, body: Body) -> InnerHttpResponse {
|
||||||
|
InnerHttpResponse {
|
||||||
|
version: None,
|
||||||
|
headers: HeaderMap::with_capacity(16),
|
||||||
|
status: status,
|
||||||
|
reason: None,
|
||||||
|
body: body,
|
||||||
|
chunked: false,
|
||||||
|
encoding: ContentEncoding::Auto,
|
||||||
|
connection_type: None,
|
||||||
|
response_size: 0,
|
||||||
|
error: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal use only! unsafe
|
||||||
|
struct Pool(VecDeque<Box<InnerHttpResponse>>);
|
||||||
|
|
||||||
|
thread_local!(static POOL: RefCell<Pool> =
|
||||||
|
RefCell::new(Pool(VecDeque::with_capacity(128))));
|
||||||
|
|
||||||
|
impl Pool {
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn get(status: StatusCode) -> Box<InnerHttpResponse> {
|
||||||
|
POOL.with(|pool| {
|
||||||
|
if let Some(mut resp) = pool.borrow_mut().0.pop_front() {
|
||||||
|
resp.body = Body::Empty;
|
||||||
|
resp.status = status;
|
||||||
|
resp
|
||||||
|
} else {
|
||||||
|
Box::new(InnerHttpResponse::new(status, Body::Empty))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn with_body(status: StatusCode, body: Body) -> Box<InnerHttpResponse> {
|
||||||
|
POOL.with(|pool| {
|
||||||
|
if let Some(mut resp) = pool.borrow_mut().0.pop_front() {
|
||||||
|
resp.status = status;
|
||||||
|
resp.body = body;
|
||||||
|
resp
|
||||||
|
} else {
|
||||||
|
Box::new(InnerHttpResponse::new(status, body))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
#[cfg_attr(feature = "cargo-clippy", allow(boxed_local, inline_always))]
|
||||||
|
fn release(mut inner: Box<InnerHttpResponse>) {
|
||||||
|
POOL.with(|pool| {
|
||||||
|
let v = &mut pool.borrow_mut().0;
|
||||||
|
if v.len() < 128 {
|
||||||
|
inner.headers.clear();
|
||||||
|
inner.version = None;
|
||||||
|
inner.chunked = false;
|
||||||
|
inner.reason = None;
|
||||||
|
inner.encoding = ContentEncoding::Auto;
|
||||||
|
inner.connection_type = None;
|
||||||
|
inner.response_size = 0;
|
||||||
|
inner.error = None;
|
||||||
|
v.push_front(inner);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use time::Duration;
|
||||||
|
use http::{Method, Uri};
|
||||||
use body::Binary;
|
use body::Binary;
|
||||||
|
use {headers, httpcodes};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_debug() {
|
||||||
|
let resp = HttpResponse::Ok().finish().unwrap();
|
||||||
|
let dbg = format!("{:?}", resp);
|
||||||
|
assert!(dbg.contains("HttpResponse"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_response_cookies() {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
headers.insert(header::COOKIE,
|
||||||
|
header::HeaderValue::from_static("cookie1=value1; cookie2=value2"));
|
||||||
|
|
||||||
|
let req = HttpRequest::new(
|
||||||
|
Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None);
|
||||||
|
let cookies = req.cookies().unwrap();
|
||||||
|
|
||||||
|
let resp = httpcodes::HTTPOk
|
||||||
|
.build()
|
||||||
|
.cookie(headers::Cookie::build("name", "value")
|
||||||
|
.domain("www.rust-lang.org")
|
||||||
|
.path("/test")
|
||||||
|
.http_only(true)
|
||||||
|
.max_age(Duration::days(1))
|
||||||
|
.finish())
|
||||||
|
.del_cookie(&cookies[0])
|
||||||
|
.body(Body::Empty);
|
||||||
|
|
||||||
|
assert!(resp.is_ok());
|
||||||
|
let resp = resp.unwrap();
|
||||||
|
|
||||||
|
let mut val: Vec<_> = resp.headers().get_all("Set-Cookie")
|
||||||
|
.iter().map(|v| v.to_str().unwrap().to_owned()).collect();
|
||||||
|
val.sort();
|
||||||
|
assert!(val[0].starts_with("cookie1=; Max-Age=0;"));
|
||||||
|
assert_eq!(
|
||||||
|
val[1],"name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_basic_builder() {
|
fn test_basic_builder() {
|
||||||
let resp = HttpResponse::Ok()
|
let resp = HttpResponse::Ok()
|
||||||
.status(StatusCode::NO_CONTENT)
|
.header("X-TEST", "value")
|
||||||
.version(Version::HTTP_10)
|
.version(Version::HTTP_10)
|
||||||
.finish().unwrap();
|
.finish().unwrap();
|
||||||
assert_eq!(resp.version(), Some(Version::HTTP_10));
|
assert_eq!(resp.version(), Some(Version::HTTP_10));
|
||||||
assert_eq!(resp.status(), StatusCode::NO_CONTENT)
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -588,6 +821,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_into_response() {
|
fn test_into_response() {
|
||||||
|
let req = HttpRequest::default();
|
||||||
|
|
||||||
let resp: HttpResponse = "test".into();
|
let resp: HttpResponse = "test".into();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
@ -595,6 +830,13 @@ mod tests {
|
|||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().binary().unwrap(), &Binary::from("test"));
|
assert_eq!(resp.body().binary().unwrap(), &Binary::from("test"));
|
||||||
|
|
||||||
|
let resp: HttpResponse = "test".respond_to(req.clone()).ok().unwrap();
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
|
header::HeaderValue::from_static("text/plain; charset=utf-8"));
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(resp.body().binary().unwrap(), &Binary::from("test"));
|
||||||
|
|
||||||
let resp: HttpResponse = b"test".as_ref().into();
|
let resp: HttpResponse = b"test".as_ref().into();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
@ -602,6 +844,13 @@ mod tests {
|
|||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().binary().unwrap(), &Binary::from(b"test".as_ref()));
|
assert_eq!(resp.body().binary().unwrap(), &Binary::from(b"test".as_ref()));
|
||||||
|
|
||||||
|
let resp: HttpResponse = b"test".as_ref().respond_to(req.clone()).ok().unwrap();
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
|
header::HeaderValue::from_static("application/octet-stream"));
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(resp.body().binary().unwrap(), &Binary::from(b"test".as_ref()));
|
||||||
|
|
||||||
let resp: HttpResponse = "test".to_owned().into();
|
let resp: HttpResponse = "test".to_owned().into();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
@ -609,6 +858,13 @@ mod tests {
|
|||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().binary().unwrap(), &Binary::from("test".to_owned()));
|
assert_eq!(resp.body().binary().unwrap(), &Binary::from("test".to_owned()));
|
||||||
|
|
||||||
|
let resp: HttpResponse = "test".to_owned().respond_to(req.clone()).ok().unwrap();
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
|
header::HeaderValue::from_static("text/plain; charset=utf-8"));
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(resp.body().binary().unwrap(), &Binary::from("test".to_owned()));
|
||||||
|
|
||||||
let resp: HttpResponse = (&"test".to_owned()).into();
|
let resp: HttpResponse = (&"test".to_owned()).into();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
@ -616,6 +872,13 @@ mod tests {
|
|||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().binary().unwrap(), &Binary::from((&"test".to_owned())));
|
assert_eq!(resp.body().binary().unwrap(), &Binary::from((&"test".to_owned())));
|
||||||
|
|
||||||
|
let resp: HttpResponse = (&"test".to_owned()).respond_to(req.clone()).ok().unwrap();
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
|
header::HeaderValue::from_static("text/plain; charset=utf-8"));
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(resp.body().binary().unwrap(), &Binary::from((&"test".to_owned())));
|
||||||
|
|
||||||
let b = Bytes::from_static(b"test");
|
let b = Bytes::from_static(b"test");
|
||||||
let resp: HttpResponse = b.into();
|
let resp: HttpResponse = b.into();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
@ -624,6 +887,14 @@ mod tests {
|
|||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().binary().unwrap(), &Binary::from(Bytes::from_static(b"test")));
|
assert_eq!(resp.body().binary().unwrap(), &Binary::from(Bytes::from_static(b"test")));
|
||||||
|
|
||||||
|
let b = Bytes::from_static(b"test");
|
||||||
|
let resp: HttpResponse = b.respond_to(req.clone()).ok().unwrap();
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
|
header::HeaderValue::from_static("application/octet-stream"));
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(resp.body().binary().unwrap(), &Binary::from(Bytes::from_static(b"test")));
|
||||||
|
|
||||||
let b = BytesMut::from("test");
|
let b = BytesMut::from("test");
|
||||||
let resp: HttpResponse = b.into();
|
let resp: HttpResponse = b.into();
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
@ -631,5 +902,13 @@ mod tests {
|
|||||||
header::HeaderValue::from_static("application/octet-stream"));
|
header::HeaderValue::from_static("application/octet-stream"));
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
assert_eq!(resp.body().binary().unwrap(), &Binary::from(BytesMut::from("test")));
|
assert_eq!(resp.body().binary().unwrap(), &Binary::from(BytesMut::from("test")));
|
||||||
|
|
||||||
|
let b = BytesMut::from("test");
|
||||||
|
let resp: HttpResponse = b.respond_to(req.clone()).ok().unwrap();
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||||
|
header::HeaderValue::from_static("application/octet-stream"));
|
||||||
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
|
assert_eq!(resp.body().binary().unwrap(), &Binary::from(BytesMut::from("test")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
213
src/info.rs
Normal file
213
src/info.rs
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
use std::str::FromStr;
|
||||||
|
use http::header::{self, HeaderName};
|
||||||
|
use httprequest::HttpRequest;
|
||||||
|
|
||||||
|
const X_FORWARDED_FOR: &str = "X-FORWARDED-FOR";
|
||||||
|
const X_FORWARDED_HOST: &str = "X-FORWARDED-HOST";
|
||||||
|
const X_FORWARDED_PROTO: &str = "X-FORWARDED-PROTO";
|
||||||
|
|
||||||
|
|
||||||
|
/// `HttpRequest` connection information
|
||||||
|
pub struct ConnectionInfo<'a> {
|
||||||
|
scheme: &'a str,
|
||||||
|
host: &'a str,
|
||||||
|
remote: Option<&'a str>,
|
||||||
|
peer: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> ConnectionInfo<'a> {
|
||||||
|
|
||||||
|
/// Create *ConnectionInfo* instance for a request.
|
||||||
|
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
|
||||||
|
pub fn new<S>(req: &'a HttpRequest<S>) -> ConnectionInfo<'a> {
|
||||||
|
let mut host = None;
|
||||||
|
let mut scheme = None;
|
||||||
|
let mut remote = None;
|
||||||
|
let mut peer = None;
|
||||||
|
|
||||||
|
// load forwarded header
|
||||||
|
for hdr in req.headers().get_all(header::FORWARDED) {
|
||||||
|
if let Ok(val) = hdr.to_str() {
|
||||||
|
for pair in val.split(';') {
|
||||||
|
for el in pair.split(',') {
|
||||||
|
let mut items = el.trim().splitn(2, '=');
|
||||||
|
if let Some(name) = items.next() {
|
||||||
|
if let Some(val) = items.next() {
|
||||||
|
match &name.to_lowercase() as &str {
|
||||||
|
"for" => if remote.is_none() {
|
||||||
|
remote = Some(val.trim());
|
||||||
|
},
|
||||||
|
"proto" => if scheme.is_none() {
|
||||||
|
scheme = Some(val.trim());
|
||||||
|
},
|
||||||
|
"host" => if host.is_none() {
|
||||||
|
host = Some(val.trim());
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// scheme
|
||||||
|
if scheme.is_none() {
|
||||||
|
if let Some(h) = req.headers().get(
|
||||||
|
HeaderName::from_str(X_FORWARDED_PROTO).unwrap()) {
|
||||||
|
if let Ok(h) = h.to_str() {
|
||||||
|
scheme = h.split(',').next().map(|v| v.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if scheme.is_none() {
|
||||||
|
scheme = req.uri().scheme_part().map(|a| a.as_str());
|
||||||
|
if scheme.is_none() {
|
||||||
|
if let Some(router) = req.router() {
|
||||||
|
if router.server_settings().secure() {
|
||||||
|
scheme = Some("https")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// host
|
||||||
|
if host.is_none() {
|
||||||
|
if let Some(h) = req.headers().get(HeaderName::from_str(X_FORWARDED_HOST).unwrap()) {
|
||||||
|
if let Ok(h) = h.to_str() {
|
||||||
|
host = h.split(',').next().map(|v| v.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if host.is_none() {
|
||||||
|
if let Some(h) = req.headers().get(header::HOST) {
|
||||||
|
host = h.to_str().ok();
|
||||||
|
}
|
||||||
|
if host.is_none() {
|
||||||
|
host = req.uri().authority_part().map(|a| a.as_str());
|
||||||
|
if host.is_none() {
|
||||||
|
if let Some(router) = req.router() {
|
||||||
|
host = Some(router.server_settings().host());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remote addr
|
||||||
|
if remote.is_none() {
|
||||||
|
if let Some(h) = req.headers().get(
|
||||||
|
HeaderName::from_str(X_FORWARDED_FOR).unwrap()) {
|
||||||
|
if let Ok(h) = h.to_str() {
|
||||||
|
remote = h.split(',').next().map(|v| v.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if remote.is_none() { // get peeraddr from socketaddr
|
||||||
|
peer = req.peer_addr().map(|addr| format!("{}", addr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ConnectionInfo {
|
||||||
|
scheme: scheme.unwrap_or("http"),
|
||||||
|
host: host.unwrap_or("localhost"),
|
||||||
|
remote: remote,
|
||||||
|
peer: peer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Scheme of the request.
|
||||||
|
///
|
||||||
|
/// Scheme is resolved through the following headers, in this order:
|
||||||
|
///
|
||||||
|
/// - Forwarded
|
||||||
|
/// - X-Forwarded-Proto
|
||||||
|
/// - Uri
|
||||||
|
#[inline]
|
||||||
|
pub fn scheme(&self) -> &str {
|
||||||
|
self.scheme
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hostname of the request.
|
||||||
|
///
|
||||||
|
/// Hostname is resolved through the following headers, in this order:
|
||||||
|
///
|
||||||
|
/// - Forwarded
|
||||||
|
/// - X-Forwarded-Host
|
||||||
|
/// - Host
|
||||||
|
/// - Uri
|
||||||
|
/// - Server hostname
|
||||||
|
pub fn host(&self) -> &str {
|
||||||
|
self.host
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remote IP of client initiated HTTP request.
|
||||||
|
///
|
||||||
|
/// The IP is resolved through the following headers, in this order:
|
||||||
|
///
|
||||||
|
/// - Forwarded
|
||||||
|
/// - X-Forwarded-For
|
||||||
|
/// - peername of opened socket
|
||||||
|
#[inline]
|
||||||
|
pub fn remote(&self) -> Option<&str> {
|
||||||
|
if let Some(r) = self.remote {
|
||||||
|
Some(r)
|
||||||
|
} else if let Some(ref peer) = self.peer {
|
||||||
|
Some(peer)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use http::header::HeaderValue;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_forwarded() {
|
||||||
|
let req = HttpRequest::default();
|
||||||
|
let info = ConnectionInfo::new(&req);
|
||||||
|
assert_eq!(info.scheme(), "http");
|
||||||
|
assert_eq!(info.host(), "localhost");
|
||||||
|
|
||||||
|
let mut req = HttpRequest::default();
|
||||||
|
req.headers_mut().insert(
|
||||||
|
header::FORWARDED,
|
||||||
|
HeaderValue::from_static(
|
||||||
|
"for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org"));
|
||||||
|
|
||||||
|
let info = ConnectionInfo::new(&req);
|
||||||
|
assert_eq!(info.scheme(), "https");
|
||||||
|
assert_eq!(info.host(), "rust-lang.org");
|
||||||
|
assert_eq!(info.remote(), Some("192.0.2.60"));
|
||||||
|
|
||||||
|
let mut req = HttpRequest::default();
|
||||||
|
req.headers_mut().insert(
|
||||||
|
header::HOST, HeaderValue::from_static("rust-lang.org"));
|
||||||
|
|
||||||
|
let info = ConnectionInfo::new(&req);
|
||||||
|
assert_eq!(info.scheme(), "http");
|
||||||
|
assert_eq!(info.host(), "rust-lang.org");
|
||||||
|
assert_eq!(info.remote(), None);
|
||||||
|
|
||||||
|
let mut req = HttpRequest::default();
|
||||||
|
req.headers_mut().insert(
|
||||||
|
HeaderName::from_str(X_FORWARDED_FOR).unwrap(), HeaderValue::from_static("192.0.2.60"));
|
||||||
|
let info = ConnectionInfo::new(&req);
|
||||||
|
assert_eq!(info.remote(), Some("192.0.2.60"));
|
||||||
|
|
||||||
|
let mut req = HttpRequest::default();
|
||||||
|
req.headers_mut().insert(
|
||||||
|
HeaderName::from_str(X_FORWARDED_HOST).unwrap(), HeaderValue::from_static("192.0.2.60"));
|
||||||
|
let info = ConnectionInfo::new(&req);
|
||||||
|
assert_eq!(info.host(), "192.0.2.60");
|
||||||
|
assert_eq!(info.remote(), None);
|
||||||
|
|
||||||
|
let mut req = HttpRequest::default();
|
||||||
|
req.headers_mut().insert(
|
||||||
|
HeaderName::from_str(X_FORWARDED_PROTO).unwrap(), HeaderValue::from_static("https"));
|
||||||
|
let info = ConnectionInfo::new(&req);
|
||||||
|
assert_eq!(info.scheme(), "https");
|
||||||
|
}
|
||||||
|
}
|
215
src/json.rs
Normal file
215
src/json.rs
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
use bytes::BytesMut;
|
||||||
|
use futures::{Poll, Future, Stream};
|
||||||
|
use http::header::CONTENT_LENGTH;
|
||||||
|
|
||||||
|
use serde_json;
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
|
||||||
|
use error::{Error, JsonPayloadError};
|
||||||
|
use handler::Responder;
|
||||||
|
use httprequest::HttpRequest;
|
||||||
|
use httpresponse::HttpResponse;
|
||||||
|
|
||||||
|
/// Json response helper
|
||||||
|
///
|
||||||
|
/// The `Json` type allows you 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::*;
|
||||||
|
/// #
|
||||||
|
/// #[derive(Serialize)]
|
||||||
|
/// struct MyObj {
|
||||||
|
/// name: String,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn index(req: HttpRequest) -> Result<Json<MyObj>> {
|
||||||
|
/// Ok(Json(MyObj{name: req.match_info().query("name")?}))
|
||||||
|
/// }
|
||||||
|
/// # fn main() {}
|
||||||
|
/// ```
|
||||||
|
pub struct Json<T: Serialize> (pub T);
|
||||||
|
|
||||||
|
impl<T: Serialize> Responder for Json<T> {
|
||||||
|
type Item = HttpResponse;
|
||||||
|
type Error = Error;
|
||||||
|
|
||||||
|
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, Error> {
|
||||||
|
let body = serde_json::to_string(&self.0)?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok()
|
||||||
|
.content_type("application/json")
|
||||||
|
.body(body)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request payload json parser that resolves to a deserialized `T` value.
|
||||||
|
///
|
||||||
|
/// Returns error:
|
||||||
|
///
|
||||||
|
/// * content type is not `application/json`
|
||||||
|
/// * content length is greater than 256k
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// # extern crate futures;
|
||||||
|
/// # #[macro_use] extern crate serde_derive;
|
||||||
|
/// use actix_web::*;
|
||||||
|
/// use futures::future::Future;
|
||||||
|
///
|
||||||
|
/// #[derive(Deserialize, Debug)]
|
||||||
|
/// struct MyObj {
|
||||||
|
/// name: String,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||||
|
/// req.json() // <- get JsonBody future
|
||||||
|
/// .from_err()
|
||||||
|
/// .and_then(|val: MyObj| { // <- deserialized value
|
||||||
|
/// println!("==== BODY ==== {:?}", val);
|
||||||
|
/// Ok(httpcodes::HTTPOk.into())
|
||||||
|
/// }).responder()
|
||||||
|
/// }
|
||||||
|
/// # fn main() {}
|
||||||
|
/// ```
|
||||||
|
pub struct JsonBody<S, T: DeserializeOwned>{
|
||||||
|
limit: usize,
|
||||||
|
ct: &'static str,
|
||||||
|
req: Option<HttpRequest<S>>,
|
||||||
|
fut: Option<Box<Future<Item=T, Error=JsonPayloadError>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, T: DeserializeOwned> JsonBody<S, T> {
|
||||||
|
|
||||||
|
/// Create `JsonBody` for request.
|
||||||
|
pub fn from_request(req: &HttpRequest<S>) -> Self {
|
||||||
|
JsonBody{
|
||||||
|
limit: 262_144,
|
||||||
|
req: Some(req.clone()),
|
||||||
|
fut: None,
|
||||||
|
ct: "application/json",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Change max size of payload. By default max size is 256Kb
|
||||||
|
pub fn limit(mut self, limit: usize) -> Self {
|
||||||
|
self.limit = limit;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set allowed content type.
|
||||||
|
///
|
||||||
|
/// By default *application/json* content type is used. Set content type
|
||||||
|
/// to empty string if you want to disable content type check.
|
||||||
|
pub fn content_type(mut self, ct: &'static str) -> Self {
|
||||||
|
self.ct = ct;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, T: DeserializeOwned + 'static> Future for JsonBody<S, T> {
|
||||||
|
type Item = T;
|
||||||
|
type Error = JsonPayloadError;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<T, JsonPayloadError> {
|
||||||
|
if let Some(req) = self.req.take() {
|
||||||
|
if let Some(len) = req.headers().get(CONTENT_LENGTH) {
|
||||||
|
if let Ok(s) = len.to_str() {
|
||||||
|
if let Ok(len) = s.parse::<usize>() {
|
||||||
|
if len > self.limit {
|
||||||
|
return Err(JsonPayloadError::Overflow);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(JsonPayloadError::Overflow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check content-type
|
||||||
|
if !self.ct.is_empty() && req.content_type() != self.ct {
|
||||||
|
return Err(JsonPayloadError::ContentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
let limit = self.limit;
|
||||||
|
let fut = req.payload().readany()
|
||||||
|
.from_err()
|
||||||
|
.fold(BytesMut::new(), move |mut body, chunk| {
|
||||||
|
if (body.len() + chunk.len()) > limit {
|
||||||
|
Err(JsonPayloadError::Overflow)
|
||||||
|
} else {
|
||||||
|
body.extend_from_slice(&chunk);
|
||||||
|
Ok(body)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.and_then(|body| Ok(serde_json::from_slice::<T>(&body)?));
|
||||||
|
self.fut = Some(Box::new(fut));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.fut.as_mut().expect("JsonBody could not be used second time").poll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use bytes::Bytes;
|
||||||
|
use http::header;
|
||||||
|
use futures::Async;
|
||||||
|
|
||||||
|
impl PartialEq for JsonPayloadError {
|
||||||
|
fn eq(&self, other: &JsonPayloadError) -> bool {
|
||||||
|
match *self {
|
||||||
|
JsonPayloadError::Overflow => match *other {
|
||||||
|
JsonPayloadError::Overflow => true,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
JsonPayloadError::ContentType => match *other {
|
||||||
|
JsonPayloadError::ContentType => true,
|
||||||
|
_ => false,
|
||||||
|
},
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
||||||
|
struct MyObject {
|
||||||
|
name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_json() {
|
||||||
|
let json = Json(MyObject{name: "test".to_owned()});
|
||||||
|
let resp = json.respond_to(HttpRequest::default()).unwrap();
|
||||||
|
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "application/json");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_json_body() {
|
||||||
|
let mut req = HttpRequest::default();
|
||||||
|
let mut json = req.json::<MyObject>();
|
||||||
|
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
|
||||||
|
|
||||||
|
let mut json = req.json::<MyObject>().content_type("text/json");
|
||||||
|
req.headers_mut().insert(header::CONTENT_TYPE,
|
||||||
|
header::HeaderValue::from_static("application/json"));
|
||||||
|
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
|
||||||
|
|
||||||
|
let mut json = req.json::<MyObject>().limit(100);
|
||||||
|
req.headers_mut().insert(header::CONTENT_TYPE,
|
||||||
|
header::HeaderValue::from_static("application/json"));
|
||||||
|
req.headers_mut().insert(header::CONTENT_LENGTH,
|
||||||
|
header::HeaderValue::from_static("10000"));
|
||||||
|
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::Overflow);
|
||||||
|
|
||||||
|
req.headers_mut().insert(header::CONTENT_LENGTH,
|
||||||
|
header::HeaderValue::from_static("16"));
|
||||||
|
req.payload_mut().unread_data(Bytes::from_static(b"{\"name\": \"test\"}"));
|
||||||
|
let mut json = req.json::<MyObject>();
|
||||||
|
assert_eq!(json.poll().ok().unwrap(), Async::Ready(MyObject{name: "test".to_owned()}));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
126
src/lib.rs
126
src/lib.rs
@ -1,4 +1,39 @@
|
|||||||
//! Web framework for [Actix](https://github.com/actix/actix)
|
//! Actix web is a small, fast, down-to-earth, open source rust web framework.
|
||||||
|
//!
|
||||||
|
//! ```rust,ignore
|
||||||
|
//! use actix_web::*;
|
||||||
|
//!
|
||||||
|
//! fn index(req: HttpRequest) -> String {
|
||||||
|
//! format!("Hello {}!", &req.match_info()["name"])
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn main() {
|
||||||
|
//! HttpServer::new(
|
||||||
|
//! || Application::new()
|
||||||
|
//! .resource("/{name}", |r| r.f(index)))
|
||||||
|
//! .bind("127.0.0.1:8080")?
|
||||||
|
//! .start()
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ## Documentation
|
||||||
|
//!
|
||||||
|
//! * [User Guide](http://actix.github.io/actix-web/guide/)
|
||||||
|
//! * Cargo package: [actix-web](https://crates.io/crates/actix-web)
|
||||||
|
//! * Minimum supported Rust version: 1.20 or later
|
||||||
|
//!
|
||||||
|
//! ## Features
|
||||||
|
//!
|
||||||
|
//! * Supported *HTTP/1.x* and *HTTP/2.0* protocols
|
||||||
|
//! * Streaming and pipelining
|
||||||
|
//! * Keep-alive and slow requests handling
|
||||||
|
//! * `WebSockets`
|
||||||
|
//! * Transparent content compression/decompression (br, gzip, deflate)
|
||||||
|
//! * Configurable request routing
|
||||||
|
//! * Multipart streams
|
||||||
|
//! * Middlewares (`Logger`, `Session`, `DefaultHeaders`)
|
||||||
|
//! * Graceful server shutdown
|
||||||
|
//! * Built on top of [Actix](https://github.com/actix/actix).
|
||||||
|
|
||||||
#![cfg_attr(actix_nightly, feature(
|
#![cfg_attr(actix_nightly, feature(
|
||||||
specialization, // for impl ErrorResponse for std::error::Error
|
specialization, // for impl ErrorResponse for std::error::Error
|
||||||
@ -11,13 +46,15 @@ extern crate bytes;
|
|||||||
extern crate sha1;
|
extern crate sha1;
|
||||||
extern crate regex;
|
extern crate regex;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
extern crate bitflags;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate failure;
|
||||||
|
#[macro_use]
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
extern crate tokio_io;
|
extern crate tokio_io;
|
||||||
extern crate tokio_core;
|
extern crate tokio_core;
|
||||||
|
extern crate mio;
|
||||||
extern crate failure;
|
extern crate net2;
|
||||||
#[macro_use] extern crate failure_derive;
|
|
||||||
|
|
||||||
extern crate cookie;
|
extern crate cookie;
|
||||||
extern crate http;
|
extern crate http;
|
||||||
extern crate httparse;
|
extern crate httparse;
|
||||||
@ -31,10 +68,13 @@ extern crate serde_json;
|
|||||||
extern crate flate2;
|
extern crate flate2;
|
||||||
extern crate brotli2;
|
extern crate brotli2;
|
||||||
extern crate percent_encoding;
|
extern crate percent_encoding;
|
||||||
extern crate actix;
|
extern crate smallvec;
|
||||||
|
extern crate num_cpus;
|
||||||
extern crate h2 as http2;
|
extern crate h2 as http2;
|
||||||
|
#[macro_use] extern crate actix;
|
||||||
|
|
||||||
// extern crate redis_async;
|
#[cfg(test)]
|
||||||
|
#[macro_use] extern crate serde_derive;
|
||||||
|
|
||||||
#[cfg(feature="tls")]
|
#[cfg(feature="tls")]
|
||||||
extern crate native_tls;
|
extern crate native_tls;
|
||||||
@ -49,18 +89,20 @@ extern crate tokio_openssl;
|
|||||||
mod application;
|
mod application;
|
||||||
mod body;
|
mod body;
|
||||||
mod context;
|
mod context;
|
||||||
mod date;
|
mod helpers;
|
||||||
mod encoding;
|
mod encoding;
|
||||||
mod httprequest;
|
mod httprequest;
|
||||||
mod httpresponse;
|
mod httpresponse;
|
||||||
mod payload;
|
mod info;
|
||||||
mod resource;
|
mod json;
|
||||||
mod recognizer;
|
|
||||||
mod route;
|
mod route;
|
||||||
//mod task;
|
mod router;
|
||||||
|
mod param;
|
||||||
|
mod resource;
|
||||||
|
mod handler;
|
||||||
mod pipeline;
|
mod pipeline;
|
||||||
mod staticfiles;
|
|
||||||
mod server;
|
mod server;
|
||||||
|
mod worker;
|
||||||
mod channel;
|
mod channel;
|
||||||
mod wsframe;
|
mod wsframe;
|
||||||
mod wsproto;
|
mod wsproto;
|
||||||
@ -69,33 +111,69 @@ mod h2;
|
|||||||
mod h1writer;
|
mod h1writer;
|
||||||
mod h2writer;
|
mod h2writer;
|
||||||
|
|
||||||
|
pub mod fs;
|
||||||
pub mod ws;
|
pub mod ws;
|
||||||
pub mod dev;
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod httpcodes;
|
pub mod httpcodes;
|
||||||
pub mod multipart;
|
pub mod multipart;
|
||||||
pub mod middlewares;
|
pub mod middleware;
|
||||||
pub use error::{Error, Result};
|
pub mod pred;
|
||||||
pub use encoding::ContentEncoding;
|
pub mod test;
|
||||||
|
pub mod payload;
|
||||||
|
pub use error::{Error, Result, ResponseError};
|
||||||
pub use body::{Body, Binary};
|
pub use body::{Body, Binary};
|
||||||
|
pub use json::{Json};
|
||||||
pub use application::Application;
|
pub use application::Application;
|
||||||
pub use httprequest::{HttpRequest, UrlEncoded};
|
pub use httprequest::HttpRequest;
|
||||||
pub use httpresponse::HttpResponse;
|
pub use httpresponse::HttpResponse;
|
||||||
pub use payload::{Payload, PayloadItem};
|
pub use handler::{Reply, Responder, NormalizePath, AsyncResponder};
|
||||||
pub use route::Reply;
|
pub use route::Route;
|
||||||
pub use resource::Resource;
|
pub use resource::Resource;
|
||||||
pub use recognizer::Params;
|
|
||||||
pub use server::HttpServer;
|
pub use server::HttpServer;
|
||||||
pub use context::HttpContext;
|
pub use context::HttpContext;
|
||||||
pub use staticfiles::StaticFiles;
|
|
||||||
|
|
||||||
// re-exports
|
// re-exports
|
||||||
pub use http::{Method, StatusCode, Version};
|
pub use http::{Method, StatusCode, Version};
|
||||||
pub use cookie::Cookie;
|
|
||||||
pub use http_range::HttpRange;
|
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
#[cfg(feature="tls")]
|
#[cfg(feature="tls")]
|
||||||
pub use native_tls::Pkcs12;
|
pub use native_tls::Pkcs12;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
#[cfg(feature="openssl")]
|
#[cfg(feature="openssl")]
|
||||||
pub use openssl::pkcs12::Pkcs12;
|
pub use openssl::pkcs12::Pkcs12;
|
||||||
|
|
||||||
|
pub mod headers {
|
||||||
|
//! Headers implementation
|
||||||
|
|
||||||
|
pub use encoding::ContentEncoding;
|
||||||
|
pub use httpresponse::ConnectionType;
|
||||||
|
|
||||||
|
pub use cookie::Cookie;
|
||||||
|
pub use cookie::CookieBuilder;
|
||||||
|
pub use http_range::HttpRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod dev {
|
||||||
|
//! The `actix-web` prelude for library developers
|
||||||
|
//!
|
||||||
|
//! The purpose of this module is to alleviate imports of many common actix traits
|
||||||
|
//! by adding a glob import to the top of actix heavy modules:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! # #![allow(unused_imports)]
|
||||||
|
//! use actix_web::dev::*;
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
pub use body::BodyStream;
|
||||||
|
pub use info::ConnectionInfo;
|
||||||
|
pub use handler::Handler;
|
||||||
|
pub use json::JsonBody;
|
||||||
|
pub use router::{Router, Pattern};
|
||||||
|
pub use channel::{HttpChannel, HttpHandler, IntoHttpHandler};
|
||||||
|
pub use param::{FromParam, Params};
|
||||||
|
pub use httprequest::{UrlEncoded, RequestBody};
|
||||||
|
pub use httpresponse::HttpResponseBuilder;
|
||||||
|
|
||||||
|
pub use server::{ServerSettings, PauseServer, ResumeServer, StopServer};
|
||||||
|
}
|
||||||
|
128
src/middleware/defaultheaders.rs
Normal file
128
src/middleware/defaultheaders.rs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
//! Default response headers
|
||||||
|
use http::{HeaderMap, HttpTryFrom};
|
||||||
|
use http::header::{HeaderName, HeaderValue, CONTENT_TYPE};
|
||||||
|
|
||||||
|
use httprequest::HttpRequest;
|
||||||
|
use httpresponse::HttpResponse;
|
||||||
|
use middleware::{Response, Middleware};
|
||||||
|
|
||||||
|
/// `Middleware` for setting default response headers.
|
||||||
|
///
|
||||||
|
/// This middleware does not set header if response headers already contains it.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// use actix_web::*;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let app = Application::new()
|
||||||
|
/// .middleware(
|
||||||
|
/// middleware::DefaultHeaders::build()
|
||||||
|
/// .header("X-Version", "0.2")
|
||||||
|
/// .finish())
|
||||||
|
/// .resource("/test", |r| {
|
||||||
|
/// r.method(Method::GET).f(|_| httpcodes::HTTPOk);
|
||||||
|
/// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed);
|
||||||
|
/// })
|
||||||
|
/// .finish();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub struct DefaultHeaders{
|
||||||
|
ct: bool,
|
||||||
|
headers: HeaderMap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DefaultHeaders {
|
||||||
|
pub fn build() -> DefaultHeadersBuilder {
|
||||||
|
DefaultHeadersBuilder{ct: false, headers: Some(HeaderMap::new())}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Middleware<S> for DefaultHeaders {
|
||||||
|
|
||||||
|
fn response(&self, _: &mut HttpRequest<S>, mut resp: HttpResponse) -> Response {
|
||||||
|
for (key, value) in self.headers.iter() {
|
||||||
|
if !resp.headers().contains_key(key) {
|
||||||
|
resp.headers_mut().insert(key, value.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// default content-type
|
||||||
|
if self.ct && !resp.headers().contains_key(CONTENT_TYPE) {
|
||||||
|
resp.headers_mut().insert(
|
||||||
|
CONTENT_TYPE, HeaderValue::from_static("application/octet-stream"));
|
||||||
|
}
|
||||||
|
Response::Done(resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Structure that follows the builder pattern for building `DefaultHeaders` middleware.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DefaultHeadersBuilder {
|
||||||
|
ct: bool,
|
||||||
|
headers: Option<HeaderMap>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DefaultHeadersBuilder {
|
||||||
|
|
||||||
|
/// Set a header.
|
||||||
|
#[inline]
|
||||||
|
#[cfg_attr(feature = "cargo-clippy", allow(match_wild_err_arm))]
|
||||||
|
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
|
||||||
|
where HeaderName: HttpTryFrom<K>,
|
||||||
|
HeaderValue: HttpTryFrom<V>
|
||||||
|
{
|
||||||
|
if let Some(ref mut headers) = self.headers {
|
||||||
|
match HeaderName::try_from(key) {
|
||||||
|
Ok(key) => {
|
||||||
|
match HeaderValue::try_from(value) {
|
||||||
|
Ok(value) => { headers.append(key, value); }
|
||||||
|
Err(_) => panic!("Can not create header value"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(_) => panic!("Can not create header name"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set *CONTENT-TYPE* header if response does not contain this header.
|
||||||
|
pub fn content_type(&mut self) -> &mut Self {
|
||||||
|
self.ct = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finishes building and returns the built `DefaultHeaders` middleware.
|
||||||
|
pub fn finish(&mut self) -> DefaultHeaders {
|
||||||
|
let headers = self.headers.take().expect("cannot reuse middleware builder");
|
||||||
|
DefaultHeaders{ ct: self.ct, headers: headers }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use http::header::CONTENT_TYPE;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_default_headers() {
|
||||||
|
let mw = DefaultHeaders::build()
|
||||||
|
.header(CONTENT_TYPE, "0001")
|
||||||
|
.finish();
|
||||||
|
|
||||||
|
let mut req = HttpRequest::default();
|
||||||
|
|
||||||
|
let resp = HttpResponse::Ok().finish().unwrap();
|
||||||
|
let resp = match mw.response(&mut req, resp) {
|
||||||
|
Response::Done(resp) => resp,
|
||||||
|
_ => panic!(),
|
||||||
|
};
|
||||||
|
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
|
||||||
|
|
||||||
|
let resp = HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish().unwrap();
|
||||||
|
let resp = match mw.response(&mut req, resp) {
|
||||||
|
Response::Done(resp) => resp,
|
||||||
|
_ => panic!(),
|
||||||
|
};
|
||||||
|
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002");
|
||||||
|
}
|
||||||
|
}
|
@ -9,25 +9,25 @@ use regex::Regex;
|
|||||||
|
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
use middlewares::{Middleware, Started, Finished};
|
use middleware::{Middleware, Started, Finished};
|
||||||
|
|
||||||
/// `Middleware` for logging request and response info to the terminal.
|
/// `Middleware` for logging request and response info to the terminal.
|
||||||
///
|
///
|
||||||
/// ## Usage
|
/// ## Usage
|
||||||
///
|
///
|
||||||
/// Create `Logger` middlewares with the specified `format`.
|
/// Create `Logger` middleware with the specified `format`.
|
||||||
/// Default `Logger` could be created with `default` method, it uses the default format:
|
/// Default `Logger` could be created with `default` method, it uses the default format:
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// %a %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i" %T
|
/// %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
|
||||||
/// ```
|
/// ```
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// extern crate actix_web;
|
/// # extern crate actix_web;
|
||||||
/// use actix_web::Application;
|
/// use actix_web::Application;
|
||||||
/// use actix_web::middlewares::Logger;
|
/// use actix_web::middleware::Logger;
|
||||||
///
|
///
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
/// let app = Application::default("/")
|
/// let app = Application::new()
|
||||||
/// .middleware(Logger::default())
|
/// .middleware(Logger::default())
|
||||||
/// .middleware(Logger::new("%a %{User-Agent}i"))
|
/// .middleware(Logger::new("%a %{User-Agent}i"))
|
||||||
/// .finish();
|
/// .finish();
|
||||||
@ -75,7 +75,7 @@ impl Default for Logger {
|
|||||||
/// Create `Logger` middleware with format:
|
/// Create `Logger` middleware with format:
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// %a %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i" %T
|
/// %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
|
||||||
/// ```
|
/// ```
|
||||||
fn default() -> Logger {
|
fn default() -> Logger {
|
||||||
Logger { format: Format::default() }
|
Logger { format: Format::default() }
|
||||||
@ -86,7 +86,7 @@ struct StartTime(time::Tm);
|
|||||||
|
|
||||||
impl Logger {
|
impl Logger {
|
||||||
|
|
||||||
fn log(&self, req: &mut HttpRequest, resp: &HttpResponse) {
|
fn log<S>(&self, req: &mut HttpRequest<S>, resp: &HttpResponse) {
|
||||||
let entry_time = req.extensions().get::<StartTime>().unwrap().0;
|
let entry_time = req.extensions().get::<StartTime>().unwrap().0;
|
||||||
|
|
||||||
let render = |fmt: &mut Formatter| {
|
let render = |fmt: &mut Formatter| {
|
||||||
@ -99,20 +99,19 @@ impl Logger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Middleware for Logger {
|
impl<S> Middleware<S> for Logger {
|
||||||
|
|
||||||
fn start(&self, req: &mut HttpRequest) -> Started {
|
fn start(&self, req: &mut HttpRequest<S>) -> Started {
|
||||||
req.extensions().insert(StartTime(time::now()));
|
req.extensions().insert(StartTime(time::now()));
|
||||||
Started::Done
|
Started::Done
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(&self, req: &mut HttpRequest, resp: &HttpResponse) -> Finished {
|
fn finish(&self, req: &mut HttpRequest<S>, resp: &HttpResponse) -> Finished {
|
||||||
self.log(req, resp);
|
self.log(req, resp);
|
||||||
Finished::Done
|
Finished::Done
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// A formatting style for the `Logger`, consisting of multiple
|
/// A formatting style for the `Logger`, consisting of multiple
|
||||||
/// `FormatText`s concatenated into one line.
|
/// `FormatText`s concatenated into one line.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@ -122,7 +121,7 @@ struct Format(Vec<FormatText>);
|
|||||||
impl Default for Format {
|
impl Default for Format {
|
||||||
/// Return the default formatting style for the `Logger`:
|
/// Return the default formatting style for the `Logger`:
|
||||||
fn default() -> Format {
|
fn default() -> Format {
|
||||||
Format::new(r#"%a %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i" %T"#)
|
Format::new(r#"%a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T"#)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,10 +199,10 @@ pub enum FormatText {
|
|||||||
|
|
||||||
impl FormatText {
|
impl FormatText {
|
||||||
|
|
||||||
fn render(&self, fmt: &mut Formatter,
|
fn render<S>(&self, fmt: &mut Formatter,
|
||||||
req: &HttpRequest,
|
req: &HttpRequest<S>,
|
||||||
resp: &HttpResponse,
|
resp: &HttpResponse,
|
||||||
entry_time: time::Tm) -> Result<(), fmt::Error>
|
entry_time: time::Tm) -> Result<(), fmt::Error>
|
||||||
{
|
{
|
||||||
match *self {
|
match *self {
|
||||||
FormatText::Str(ref string) => fmt.write_str(string),
|
FormatText::Str(ref string) => fmt.write_str(string),
|
||||||
@ -224,7 +223,7 @@ impl FormatText {
|
|||||||
FormatText::Pid => unsafe{libc::getpid().fmt(fmt)},
|
FormatText::Pid => unsafe{libc::getpid().fmt(fmt)},
|
||||||
FormatText::Time => {
|
FormatText::Time => {
|
||||||
let response_time = time::now() - entry_time;
|
let response_time = time::now() - entry_time;
|
||||||
let response_time = (response_time.num_seconds() * 1000) as f64 +
|
let response_time = response_time.num_seconds() as f64 +
|
||||||
(response_time.num_nanoseconds().unwrap_or(0) as f64) / 1000000000.0;
|
(response_time.num_nanoseconds().unwrap_or(0) as f64) / 1000000000.0;
|
||||||
|
|
||||||
fmt.write_fmt(format_args!("{:.6}", response_time))
|
fmt.write_fmt(format_args!("{:.6}", response_time))
|
||||||
@ -237,8 +236,8 @@ impl FormatText {
|
|||||||
fmt.write_fmt(format_args!("{:.6}", response_time_ms))
|
fmt.write_fmt(format_args!("{:.6}", response_time_ms))
|
||||||
},
|
},
|
||||||
FormatText::RemoteAddr => {
|
FormatText::RemoteAddr => {
|
||||||
if let Some(addr) = req.remote() {
|
if let Some(remote) = req.connection_info().remote() {
|
||||||
addr.fmt(fmt)
|
return remote.fmt(fmt);
|
||||||
} else {
|
} else {
|
||||||
"-".fmt(fmt)
|
"-".fmt(fmt)
|
||||||
}
|
}
|
||||||
@ -292,7 +291,6 @@ mod tests {
|
|||||||
use time;
|
use time;
|
||||||
use http::{Method, Version, StatusCode, Uri};
|
use http::{Method, Version, StatusCode, Uri};
|
||||||
use http::header::{self, HeaderMap};
|
use http::header::{self, HeaderMap};
|
||||||
use payload::Payload;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_logger() {
|
fn test_logger() {
|
||||||
@ -301,8 +299,7 @@ mod tests {
|
|||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
headers.insert(header::USER_AGENT, header::HeaderValue::from_static("ACTIX-WEB"));
|
headers.insert(header::USER_AGENT, header::HeaderValue::from_static("ACTIX-WEB"));
|
||||||
let mut req = HttpRequest::new(
|
let mut req = HttpRequest::new(
|
||||||
Method::GET, Uri::from_str("/").unwrap(),
|
Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None);
|
||||||
Version::HTTP_11, headers, Payload::empty());
|
|
||||||
let resp = HttpResponse::build(StatusCode::OK)
|
let resp = HttpResponse::build(StatusCode::OK)
|
||||||
.header("X-Test", "ttt")
|
.header("X-Test", "ttt")
|
||||||
.force_close().body(Body::Empty).unwrap();
|
.force_close().body(Body::Empty).unwrap();
|
||||||
@ -333,8 +330,7 @@ mod tests {
|
|||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
headers.insert(header::USER_AGENT, header::HeaderValue::from_static("ACTIX-WEB"));
|
headers.insert(header::USER_AGENT, header::HeaderValue::from_static("ACTIX-WEB"));
|
||||||
let req = HttpRequest::new(
|
let req = HttpRequest::new(
|
||||||
Method::GET, Uri::from_str("/").unwrap(),
|
Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None);
|
||||||
Version::HTTP_11, headers, Payload::empty());
|
|
||||||
let resp = HttpResponse::build(StatusCode::OK)
|
let resp = HttpResponse::build(StatusCode::OK)
|
||||||
.force_close().body(Body::Empty).unwrap();
|
.force_close().body(Body::Empty).unwrap();
|
||||||
let entry_time = time::now();
|
let entry_time = time::now();
|
||||||
@ -352,7 +348,7 @@ mod tests {
|
|||||||
|
|
||||||
let req = HttpRequest::new(
|
let req = HttpRequest::new(
|
||||||
Method::GET, Uri::from_str("/?test").unwrap(),
|
Method::GET, Uri::from_str("/?test").unwrap(),
|
||||||
Version::HTTP_11, HeaderMap::new(), Payload::empty());
|
Version::HTTP_11, HeaderMap::new(), None);
|
||||||
let resp = HttpResponse::build(StatusCode::OK)
|
let resp = HttpResponse::build(StatusCode::OK)
|
||||||
.force_close().body(Body::Empty).unwrap();
|
.force_close().body(Body::Empty).unwrap();
|
||||||
let entry_time = time::now();
|
let entry_time = time::now();
|
@ -7,7 +7,9 @@ use httpresponse::HttpResponse;
|
|||||||
|
|
||||||
mod logger;
|
mod logger;
|
||||||
mod session;
|
mod session;
|
||||||
|
mod defaultheaders;
|
||||||
pub use self::logger::Logger;
|
pub use self::logger::Logger;
|
||||||
|
pub use self::defaultheaders::{DefaultHeaders, DefaultHeadersBuilder};
|
||||||
pub use self::session::{RequestSession, Session, SessionImpl, SessionBackend, SessionStorage,
|
pub use self::session::{RequestSession, Session, SessionImpl, SessionBackend, SessionStorage,
|
||||||
CookieSessionError, CookieSessionBackend, CookieSessionBackendBuilder};
|
CookieSessionError, CookieSessionBackend, CookieSessionBackendBuilder};
|
||||||
|
|
||||||
@ -44,22 +46,22 @@ pub enum Finished {
|
|||||||
|
|
||||||
/// Middleware definition
|
/// Middleware definition
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
pub trait Middleware {
|
pub trait Middleware<S> {
|
||||||
|
|
||||||
/// Method is called when request is ready. It may return
|
/// Method is called when request is ready. It may return
|
||||||
/// future, which should resolve before next middleware get called.
|
/// future, which should resolve before next middleware get called.
|
||||||
fn start(&self, req: &mut HttpRequest) -> Started {
|
fn start(&self, req: &mut HttpRequest<S>) -> Started {
|
||||||
Started::Done
|
Started::Done
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Method is called when handler returns response,
|
/// Method is called when handler returns response,
|
||||||
/// but before sending http message to peer.
|
/// but before sending http message to peer.
|
||||||
fn response(&self, req: &mut HttpRequest, resp: HttpResponse) -> Response {
|
fn response(&self, req: &mut HttpRequest<S>, resp: HttpResponse) -> Response {
|
||||||
Response::Done(resp)
|
Response::Done(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Method is called after body stream get sent to peer.
|
/// Method is called after body stream get sent to peer.
|
||||||
fn finish(&self, req: &mut HttpRequest, resp: &HttpResponse) -> Finished {
|
fn finish(&self, req: &mut HttpRequest<S>, resp: &HttpResponse) -> Finished {
|
||||||
Finished::Done
|
Finished::Done
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,6 +3,7 @@
|
|||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::marker::PhantomData;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use serde_json;
|
use serde_json;
|
||||||
@ -13,17 +14,34 @@ use cookie::{CookieJar, Cookie, Key};
|
|||||||
use futures::Future;
|
use futures::Future;
|
||||||
use futures::future::{FutureResult, ok as FutOk, err as FutErr};
|
use futures::future::{FutureResult, ok as FutOk, err as FutErr};
|
||||||
|
|
||||||
use error::{Result, Error, ErrorResponse};
|
use error::{Result, Error, ResponseError};
|
||||||
use httprequest::HttpRequest;
|
use httprequest::HttpRequest;
|
||||||
use httpresponse::HttpResponse;
|
use httpresponse::HttpResponse;
|
||||||
use middlewares::{Middleware, Started, Response};
|
use middleware::{Middleware, Started, Response};
|
||||||
|
|
||||||
/// The helper trait to obtain your session data from a request.
|
/// The helper trait to obtain your session data from a request.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use actix_web::*;
|
||||||
|
/// use actix_web::middleware::RequestSession;
|
||||||
|
///
|
||||||
|
/// fn index(mut req: HttpRequest) -> Result<&'static str> {
|
||||||
|
/// // access session data
|
||||||
|
/// if let Some(count) = req.session().get::<i32>("counter")? {
|
||||||
|
/// req.session().set("counter", count+1)?;
|
||||||
|
/// } else {
|
||||||
|
/// req.session().set("counter", 1)?;
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// Ok("Welcome!")
|
||||||
|
/// }
|
||||||
|
/// # fn main() {}
|
||||||
|
/// ```
|
||||||
pub trait RequestSession {
|
pub trait RequestSession {
|
||||||
fn session(&mut self) -> Session;
|
fn session(&mut self) -> Session;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequestSession for HttpRequest {
|
impl<S> RequestSession for HttpRequest<S> {
|
||||||
|
|
||||||
fn session(&mut self) -> Session {
|
fn session(&mut self) -> Session {
|
||||||
if let Some(s_impl) = self.extensions().get_mut::<Arc<SessionImplBox>>() {
|
if let Some(s_impl) = self.extensions().get_mut::<Arc<SessionImplBox>>() {
|
||||||
@ -41,6 +59,23 @@ impl RequestSession for HttpRequest {
|
|||||||
/// Session object could be obtained with
|
/// Session object could be obtained with
|
||||||
/// [`RequestSession::session`](trait.RequestSession.html#tymethod.session)
|
/// [`RequestSession::session`](trait.RequestSession.html#tymethod.session)
|
||||||
/// method. `RequestSession` trait is implemented for `HttpRequest`.
|
/// method. `RequestSession` trait is implemented for `HttpRequest`.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use actix_web::*;
|
||||||
|
/// use actix_web::middleware::RequestSession;
|
||||||
|
///
|
||||||
|
/// fn index(mut req: HttpRequest) -> Result<&'static str> {
|
||||||
|
/// // access session data
|
||||||
|
/// if let Some(count) = req.session().get::<i32>("counter")? {
|
||||||
|
/// req.session().set("counter", count+1)?;
|
||||||
|
/// } else {
|
||||||
|
/// req.session().set("counter", 1)?;
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// Ok("Welcome!")
|
||||||
|
/// }
|
||||||
|
/// # fn main() {}
|
||||||
|
/// ```
|
||||||
pub struct Session<'a>(&'a mut SessionImpl);
|
pub struct Session<'a>(&'a mut SessionImpl);
|
||||||
|
|
||||||
impl<'a> Session<'a> {
|
impl<'a> Session<'a> {
|
||||||
@ -79,18 +114,34 @@ unsafe impl Send for SessionImplBox {}
|
|||||||
unsafe impl Sync for SessionImplBox {}
|
unsafe impl Sync for SessionImplBox {}
|
||||||
|
|
||||||
/// Session storage middleware
|
/// Session storage middleware
|
||||||
pub struct SessionStorage<T>(T);
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix;
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// # use actix_web::middleware::{SessionStorage, CookieSessionBackend};
|
||||||
|
/// use actix_web::*;
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let app = Application::new().middleware(
|
||||||
|
/// SessionStorage::new( // <- create session middleware
|
||||||
|
/// CookieSessionBackend::build(&[0; 32]) // <- create cookie session backend
|
||||||
|
/// .secure(false)
|
||||||
|
/// .finish())
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub struct SessionStorage<T, S>(T, PhantomData<S>);
|
||||||
|
|
||||||
impl<T: SessionBackend> SessionStorage<T> {
|
impl<S, T: SessionBackend<S>> SessionStorage<T, S> {
|
||||||
/// Create session storage
|
/// Create session storage
|
||||||
pub fn new(backend: T) -> SessionStorage<T> {
|
pub fn new(backend: T) -> SessionStorage<T, S> {
|
||||||
SessionStorage(backend)
|
SessionStorage(backend, PhantomData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: SessionBackend> Middleware for SessionStorage<T> {
|
impl<S: 'static, T: SessionBackend<S>> Middleware<S> for SessionStorage<T, S> {
|
||||||
|
|
||||||
fn start(&self, req: &mut HttpRequest) -> Started {
|
fn start(&self, req: &mut HttpRequest<S>) -> Started {
|
||||||
let mut req = req.clone();
|
let mut req = req.clone();
|
||||||
|
|
||||||
let fut = self.0.from_request(&mut req)
|
let fut = self.0.from_request(&mut req)
|
||||||
@ -106,7 +157,7 @@ impl<T: SessionBackend> Middleware for SessionStorage<T> {
|
|||||||
Started::Future(Box::new(fut))
|
Started::Future(Box::new(fut))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn response(&self, req: &mut HttpRequest, resp: HttpResponse) -> Response {
|
fn response(&self, req: &mut HttpRequest<S>, resp: HttpResponse) -> Response {
|
||||||
if let Some(s_box) = req.extensions().remove::<Arc<SessionImplBox>>() {
|
if let Some(s_box) = req.extensions().remove::<Arc<SessionImplBox>>() {
|
||||||
s_box.0.write(resp)
|
s_box.0.write(resp)
|
||||||
} else {
|
} else {
|
||||||
@ -133,12 +184,12 @@ pub trait SessionImpl: 'static {
|
|||||||
|
|
||||||
/// Session's storage backend trait definition.
|
/// Session's storage backend trait definition.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub trait SessionBackend: Sized + 'static {
|
pub trait SessionBackend<S>: Sized + 'static {
|
||||||
type Session: SessionImpl;
|
type Session: SessionImpl;
|
||||||
type ReadFuture: Future<Item=Self::Session, Error=Error>;
|
type ReadFuture: Future<Item=Self::Session, Error=Error>;
|
||||||
|
|
||||||
/// Parse the session from request and load data from a storage backend.
|
/// Parse the session from request and load data from a storage backend.
|
||||||
fn from_request(&self, request: &mut HttpRequest) -> Self::ReadFuture;
|
fn from_request(&self, request: &mut HttpRequest<S>) -> Self::ReadFuture;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dummy session impl, does not do anything
|
/// Dummy session impl, does not do anything
|
||||||
@ -177,7 +228,7 @@ pub enum CookieSessionError {
|
|||||||
Serialize(JsonError),
|
Serialize(JsonError),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ErrorResponse for CookieSessionError {}
|
impl ResponseError for CookieSessionError {}
|
||||||
|
|
||||||
impl SessionImpl for CookieSession {
|
impl SessionImpl for CookieSession {
|
||||||
|
|
||||||
@ -225,7 +276,7 @@ impl CookieSessionInner {
|
|||||||
fn new(key: &[u8]) -> CookieSessionInner {
|
fn new(key: &[u8]) -> CookieSessionInner {
|
||||||
CookieSessionInner {
|
CookieSessionInner {
|
||||||
key: Key::from_master(key),
|
key: Key::from_master(key),
|
||||||
name: "actix_session".to_owned(),
|
name: "actix-session".to_owned(),
|
||||||
path: "/".to_owned(),
|
path: "/".to_owned(),
|
||||||
domain: None,
|
domain: None,
|
||||||
secure: true }
|
secure: true }
|
||||||
@ -258,8 +309,8 @@ impl CookieSessionInner {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load(&self, req: &mut HttpRequest) -> HashMap<String, String> {
|
fn load<S>(&self, req: &mut HttpRequest<S>) -> HashMap<String, String> {
|
||||||
if let Ok(cookies) = req.load_cookies() {
|
if let Ok(cookies) = req.cookies() {
|
||||||
for cookie in cookies {
|
for cookie in cookies {
|
||||||
if cookie.name() == self.name {
|
if cookie.name() == self.name {
|
||||||
let mut jar = CookieJar::new();
|
let mut jar = CookieJar::new();
|
||||||
@ -307,7 +358,7 @@ impl CookieSessionBackend {
|
|||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use actix_web::middlewares::CookieSessionBackend;
|
/// use actix_web::middleware::CookieSessionBackend;
|
||||||
///
|
///
|
||||||
/// let backend = CookieSessionBackend::build(&[0; 32]).finish();
|
/// let backend = CookieSessionBackend::build(&[0; 32]).finish();
|
||||||
/// ```
|
/// ```
|
||||||
@ -316,12 +367,12 @@ impl CookieSessionBackend {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SessionBackend for CookieSessionBackend {
|
impl<S> SessionBackend<S> for CookieSessionBackend {
|
||||||
|
|
||||||
type Session = CookieSession;
|
type Session = CookieSession;
|
||||||
type ReadFuture = FutureResult<CookieSession, Error>;
|
type ReadFuture = FutureResult<CookieSession, Error>;
|
||||||
|
|
||||||
fn from_request(&self, req: &mut HttpRequest) -> Self::ReadFuture {
|
fn from_request(&self, req: &mut HttpRequest<S>) -> Self::ReadFuture {
|
||||||
let state = self.0.load(req);
|
let state = self.0.load(req);
|
||||||
FutOk(
|
FutOk(
|
||||||
CookieSession {
|
CookieSession {
|
||||||
@ -345,11 +396,12 @@ impl SessionBackend for CookieSessionBackend {
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate actix_web;
|
/// # extern crate actix_web;
|
||||||
///
|
///
|
||||||
/// use actix_web::middlewares::CookieSessionBackend;
|
/// use actix_web::middleware::CookieSessionBackend;
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
/// let backend: CookieSessionBackend = CookieSessionBackend::build(&[0; 32])
|
/// let backend: CookieSessionBackend = CookieSessionBackend::build(&[0; 32])
|
||||||
/// .domain("www.rust-lang.org")
|
/// .domain("www.rust-lang.org")
|
||||||
|
/// .name("actix_session")
|
||||||
/// .path("/")
|
/// .path("/")
|
||||||
/// .secure(true)
|
/// .secure(true)
|
||||||
/// .finish();
|
/// .finish();
|
||||||
@ -369,6 +421,12 @@ impl CookieSessionBackendBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the `name` field in the session cookie being built.
|
||||||
|
pub fn name<S: Into<String>>(mut self, value: S) -> CookieSessionBackendBuilder {
|
||||||
|
self.0.name = value.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the `domain` field in the session cookie being built.
|
/// Sets the `domain` field in the session cookie being built.
|
||||||
pub fn domain<S: Into<String>>(mut self, value: S) -> CookieSessionBackendBuilder {
|
pub fn domain<S: Into<String>>(mut self, value: S) -> CookieSessionBackendBuilder {
|
||||||
self.0.domain = Some(value.into());
|
self.0.domain = Some(value.into());
|
@ -9,11 +9,12 @@ use httparse;
|
|||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use http::HttpTryFrom;
|
use http::HttpTryFrom;
|
||||||
use http::header::{self, HeaderMap, HeaderName, HeaderValue};
|
use http::header::{self, HeaderMap, HeaderName, HeaderValue};
|
||||||
use futures::{Async, Stream, Poll};
|
use futures::{Async, Future, Stream, Poll};
|
||||||
use futures::task::{Task, current as current_task};
|
use futures::task::{Task, current as current_task};
|
||||||
|
|
||||||
use error::{ParseError, PayloadError, MultipartError};
|
use error::{ParseError, PayloadError, MultipartError};
|
||||||
use payload::Payload;
|
use payload::Payload;
|
||||||
|
use httprequest::HttpRequest;
|
||||||
|
|
||||||
const MAX_HEADERS: usize = 32;
|
const MAX_HEADERS: usize = 32;
|
||||||
|
|
||||||
@ -26,7 +27,8 @@ const MAX_HEADERS: usize = 32;
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Multipart {
|
pub struct Multipart {
|
||||||
safety: Safety,
|
safety: Safety,
|
||||||
inner: Rc<RefCell<InnerMultipart>>,
|
error: Option<MultipartError>,
|
||||||
|
inner: Option<Rc<RefCell<InnerMultipart>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
@ -66,17 +68,32 @@ struct InnerMultipart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Multipart {
|
impl Multipart {
|
||||||
|
|
||||||
/// Create multipart instance for boundary.
|
/// Create multipart instance for boundary.
|
||||||
pub fn new(boundary: String, payload: Payload) -> Multipart {
|
pub fn new(boundary: String, payload: Payload) -> Multipart {
|
||||||
Multipart {
|
Multipart {
|
||||||
|
error: None,
|
||||||
safety: Safety::new(),
|
safety: Safety::new(),
|
||||||
inner: Rc::new(RefCell::new(
|
inner: Some(Rc::new(RefCell::new(
|
||||||
InnerMultipart {
|
InnerMultipart {
|
||||||
payload: PayloadRef::new(payload),
|
payload: PayloadRef::new(payload),
|
||||||
boundary: boundary,
|
boundary: boundary,
|
||||||
state: InnerState::FirstBoundary,
|
state: InnerState::FirstBoundary,
|
||||||
item: InnerMultipartItem::None,
|
item: InnerMultipartItem::None,
|
||||||
}))
|
})))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create multipart instance for request.
|
||||||
|
pub fn from_request<S>(req: &mut HttpRequest<S>) -> Multipart {
|
||||||
|
match Multipart::boundary(req.headers()) {
|
||||||
|
Ok(boundary) => Multipart::new(boundary, req.payload().clone()),
|
||||||
|
Err(err) =>
|
||||||
|
Multipart {
|
||||||
|
error: Some(err),
|
||||||
|
safety: Safety::new(),
|
||||||
|
inner: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,8 +124,10 @@ impl Stream for Multipart {
|
|||||||
type Error = MultipartError;
|
type Error = MultipartError;
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||||
if self.safety.current() {
|
if let Some(err) = self.error.take() {
|
||||||
self.inner.borrow_mut().poll(&self.safety)
|
Err(err)
|
||||||
|
} else if self.safety.current() {
|
||||||
|
self.inner.as_mut().unwrap().borrow_mut().poll(&self.safety)
|
||||||
} else {
|
} else {
|
||||||
Ok(Async::NotReady)
|
Ok(Async::NotReady)
|
||||||
}
|
}
|
||||||
@ -119,7 +138,7 @@ impl InnerMultipart {
|
|||||||
|
|
||||||
fn read_headers(payload: &mut Payload) -> Poll<HeaderMap, MultipartError>
|
fn read_headers(payload: &mut Payload) -> Poll<HeaderMap, MultipartError>
|
||||||
{
|
{
|
||||||
match payload.readuntil(b"\r\n\r\n")? {
|
match payload.readuntil(b"\r\n\r\n").poll()? {
|
||||||
Async::NotReady => Ok(Async::NotReady),
|
Async::NotReady => Ok(Async::NotReady),
|
||||||
Async::Ready(bytes) => {
|
Async::Ready(bytes) => {
|
||||||
let mut hdrs = [httparse::EMPTY_HEADER; MAX_HEADERS];
|
let mut hdrs = [httparse::EMPTY_HEADER; MAX_HEADERS];
|
||||||
@ -150,7 +169,7 @@ impl InnerMultipart {
|
|||||||
fn read_boundary(payload: &mut Payload, boundary: &str) -> Poll<bool, MultipartError>
|
fn read_boundary(payload: &mut Payload, boundary: &str) -> Poll<bool, MultipartError>
|
||||||
{
|
{
|
||||||
// TODO: need to read epilogue
|
// TODO: need to read epilogue
|
||||||
match payload.readline()? {
|
match payload.readline().poll()? {
|
||||||
Async::NotReady => Ok(Async::NotReady),
|
Async::NotReady => Ok(Async::NotReady),
|
||||||
Async::Ready(chunk) => {
|
Async::Ready(chunk) => {
|
||||||
if chunk.len() == boundary.len() + 4 &&
|
if chunk.len() == boundary.len() + 4 &&
|
||||||
@ -175,7 +194,7 @@ impl InnerMultipart {
|
|||||||
{
|
{
|
||||||
let mut eof = false;
|
let mut eof = false;
|
||||||
loop {
|
loop {
|
||||||
if let Async::Ready(chunk) = payload.readline()? {
|
if let Async::Ready(chunk) = payload.readline().poll()? {
|
||||||
if chunk.is_empty() {
|
if chunk.is_empty() {
|
||||||
//ValueError("Could not find starting boundary %r"
|
//ValueError("Could not find starting boundary %r"
|
||||||
//% (self._boundary))
|
//% (self._boundary))
|
||||||
@ -327,7 +346,9 @@ impl InnerMultipart {
|
|||||||
|
|
||||||
Ok(Async::Ready(Some(
|
Ok(Async::Ready(Some(
|
||||||
MultipartItem::Nested(
|
MultipartItem::Nested(
|
||||||
Multipart{safety: safety.clone(), inner: inner}))))
|
Multipart{safety: safety.clone(),
|
||||||
|
error: None,
|
||||||
|
inner: Some(inner)}))))
|
||||||
} else {
|
} else {
|
||||||
let field = Rc::new(RefCell::new(InnerField::new(
|
let field = Rc::new(RefCell::new(InnerField::new(
|
||||||
self.payload.clone(), self.boundary.clone(), &headers)?));
|
self.payload.clone(), self.boundary.clone(), &headers)?));
|
||||||
@ -356,10 +377,6 @@ pub struct Field {
|
|||||||
safety: Safety,
|
safety: Safety,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A field's chunk
|
|
||||||
#[derive(PartialEq, Debug)]
|
|
||||||
pub struct FieldChunk(pub Bytes);
|
|
||||||
|
|
||||||
impl Field {
|
impl Field {
|
||||||
|
|
||||||
fn new(safety: Safety, headers: HeaderMap,
|
fn new(safety: Safety, headers: HeaderMap,
|
||||||
@ -382,7 +399,7 @@ impl Field {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Stream for Field {
|
impl Stream for Field {
|
||||||
type Item = FieldChunk;
|
type Item = Bytes;
|
||||||
type Error = MultipartError;
|
type Error = MultipartError;
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||||
@ -452,15 +469,15 @@ impl InnerField {
|
|||||||
if *size == 0 {
|
if *size == 0 {
|
||||||
Ok(Async::Ready(None))
|
Ok(Async::Ready(None))
|
||||||
} else {
|
} else {
|
||||||
match payload.readany() {
|
match payload.readany().poll() {
|
||||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||||
Ok(Async::Ready(None)) => Ok(Async::Ready(None)),
|
Ok(Async::Ready(None)) => Ok(Async::Ready(None)),
|
||||||
Ok(Async::Ready(Some(mut chunk))) => {
|
Ok(Async::Ready(Some(mut chunk))) => {
|
||||||
let len = cmp::min(chunk.0.len() as u64, *size);
|
let len = cmp::min(chunk.len() as u64, *size);
|
||||||
*size -= len;
|
*size -= len;
|
||||||
let ch = chunk.0.split_to(len as usize);
|
let ch = chunk.split_to(len as usize);
|
||||||
if !chunk.0.is_empty() {
|
if !chunk.is_empty() {
|
||||||
payload.unread_data(chunk.0);
|
payload.unread_data(chunk);
|
||||||
}
|
}
|
||||||
Ok(Async::Ready(Some(ch)))
|
Ok(Async::Ready(Some(ch)))
|
||||||
},
|
},
|
||||||
@ -473,12 +490,12 @@ impl InnerField {
|
|||||||
/// The `Content-Length` header for body part is not necessary.
|
/// The `Content-Length` header for body part is not necessary.
|
||||||
fn read_stream(payload: &mut Payload, boundary: &str) -> Poll<Option<Bytes>, MultipartError>
|
fn read_stream(payload: &mut Payload, boundary: &str) -> Poll<Option<Bytes>, MultipartError>
|
||||||
{
|
{
|
||||||
match payload.readuntil(b"\r")? {
|
match payload.readuntil(b"\r").poll()? {
|
||||||
Async::NotReady => Ok(Async::NotReady),
|
Async::NotReady => Ok(Async::NotReady),
|
||||||
Async::Ready(mut chunk) => {
|
Async::Ready(mut chunk) => {
|
||||||
if chunk.len() == 1 {
|
if chunk.len() == 1 {
|
||||||
payload.unread_data(chunk);
|
payload.unread_data(chunk);
|
||||||
match payload.readexactly(boundary.len() + 4)? {
|
match payload.readexactly(boundary.len() + 4).poll()? {
|
||||||
Async::NotReady => Ok(Async::NotReady),
|
Async::NotReady => Ok(Async::NotReady),
|
||||||
Async::Ready(chunk) => {
|
Async::Ready(chunk) => {
|
||||||
if &chunk[..2] == b"\r\n" && &chunk[2..4] == b"--" &&
|
if &chunk[..2] == b"\r\n" && &chunk[2..4] == b"--" &&
|
||||||
@ -501,13 +518,13 @@ impl InnerField {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn poll(&mut self, s: &Safety) -> Poll<Option<FieldChunk>, MultipartError> {
|
fn poll(&mut self, s: &Safety) -> Poll<Option<Bytes>, MultipartError> {
|
||||||
if self.payload.is_none() {
|
if self.payload.is_none() {
|
||||||
return Ok(Async::Ready(None))
|
return Ok(Async::Ready(None))
|
||||||
}
|
}
|
||||||
if self.eof {
|
if self.eof {
|
||||||
if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) {
|
if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) {
|
||||||
match payload.readline()? {
|
match payload.readline().poll()? {
|
||||||
Async::NotReady =>
|
Async::NotReady =>
|
||||||
return Ok(Async::NotReady),
|
return Ok(Async::NotReady),
|
||||||
Async::Ready(chunk) => {
|
Async::Ready(chunk) => {
|
||||||
@ -533,10 +550,10 @@ impl InnerField {
|
|||||||
|
|
||||||
match res {
|
match res {
|
||||||
Async::NotReady => Async::NotReady,
|
Async::NotReady => Async::NotReady,
|
||||||
Async::Ready(Some(bytes)) => Async::Ready(Some(FieldChunk(bytes))),
|
Async::Ready(Some(bytes)) => Async::Ready(Some(bytes)),
|
||||||
Async::Ready(None) => {
|
Async::Ready(None) => {
|
||||||
self.eof = true;
|
self.eof = true;
|
||||||
match payload.readline()? {
|
match payload.readline().poll()? {
|
||||||
Async::NotReady => Async::NotReady,
|
Async::NotReady => Async::NotReady,
|
||||||
Async::Ready(chunk) => {
|
Async::Ready(chunk) => {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -713,7 +730,7 @@ mod tests {
|
|||||||
|
|
||||||
match field.poll() {
|
match field.poll() {
|
||||||
Ok(Async::Ready(Some(chunk))) =>
|
Ok(Async::Ready(Some(chunk))) =>
|
||||||
assert_eq!(chunk.0, "test"),
|
assert_eq!(chunk, "test"),
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
}
|
}
|
||||||
match field.poll() {
|
match field.poll() {
|
||||||
@ -736,7 +753,7 @@ mod tests {
|
|||||||
|
|
||||||
match field.poll() {
|
match field.poll() {
|
||||||
Ok(Async::Ready(Some(chunk))) =>
|
Ok(Async::Ready(Some(chunk))) =>
|
||||||
assert_eq!(chunk.0, "data"),
|
assert_eq!(chunk, "data"),
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
}
|
}
|
||||||
match field.poll() {
|
match field.poll() {
|
||||||
|
190
src/param.rs
Normal file
190
src/param.rs
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
use std;
|
||||||
|
use std::ops::Index;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::slice::Iter;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
use error::{ResponseError, UriSegmentError, ErrorBadRequest};
|
||||||
|
|
||||||
|
|
||||||
|
/// A trait to abstract the idea of creating a new instance of a type from a path parameter.
|
||||||
|
pub trait FromParam: Sized {
|
||||||
|
/// The associated error which can be returned from parsing.
|
||||||
|
type Err: ResponseError;
|
||||||
|
|
||||||
|
/// Parses a string `s` to return a value of this type.
|
||||||
|
fn from_param(s: &str) -> Result<Self, Self::Err>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Route match information
|
||||||
|
///
|
||||||
|
/// If resource path contains variable patterns, `Params` stores this variables.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Params<'a>(SmallVec<[(Cow<'a, str>, Cow<'a, str>); 3]>);
|
||||||
|
|
||||||
|
impl<'a> Params<'a> {
|
||||||
|
|
||||||
|
pub(crate) fn new() -> Params<'a> {
|
||||||
|
Params(SmallVec::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn clear(&mut self) {
|
||||||
|
self.0.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn add<N, V>(&mut self, name: N, value: V)
|
||||||
|
where N: Into<Cow<'a, str>>, V: Into<Cow<'a, str>>,
|
||||||
|
{
|
||||||
|
self.0.push((name.into(), value.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if there are any matched patterns
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.0.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get matched parameter by name without type conversion
|
||||||
|
pub fn get(&'a self, key: &str) -> Option<&'a str> {
|
||||||
|
for item in self.0.iter() {
|
||||||
|
if key == item.0 {
|
||||||
|
return Some(item.1.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get matched `FromParam` compatible parameter by name.
|
||||||
|
///
|
||||||
|
/// If keyed parameter is not available empty string is used as default value.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// # use actix_web::*;
|
||||||
|
/// fn index(req: HttpRequest) -> Result<String> {
|
||||||
|
/// let ivalue: isize = req.match_info().query("val")?;
|
||||||
|
/// Ok(format!("isuze value: {:?}", ivalue))
|
||||||
|
/// }
|
||||||
|
/// # fn main() {}
|
||||||
|
/// ```
|
||||||
|
pub fn query<T: FromParam>(&'a self, key: &str) -> Result<T, <T as FromParam>::Err>
|
||||||
|
{
|
||||||
|
if let Some(s) = self.get(key) {
|
||||||
|
T::from_param(s)
|
||||||
|
} else {
|
||||||
|
T::from_param("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return iterator to items in paramter container
|
||||||
|
pub fn iter(&self) -> Iter<(Cow<'a, str>, Cow<'a, str>)> {
|
||||||
|
self.0.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b, 'c: 'a> Index<&'b str> for &'c Params<'a> {
|
||||||
|
type Output = str;
|
||||||
|
|
||||||
|
fn index(&self, name: &'b str) -> &str {
|
||||||
|
self.get(name).expect("Value for parameter is not available")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a `PathBuf` from a 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.
|
||||||
|
impl FromParam for PathBuf {
|
||||||
|
type Err = UriSegmentError;
|
||||||
|
|
||||||
|
fn from_param(val: &str) -> Result<PathBuf, UriSegmentError> {
|
||||||
|
let mut buf = PathBuf::new();
|
||||||
|
for segment in val.split('/') {
|
||||||
|
if segment == ".." {
|
||||||
|
buf.pop();
|
||||||
|
} else if segment.starts_with('.') {
|
||||||
|
return Err(UriSegmentError::BadStart('.'))
|
||||||
|
} else if segment.starts_with('*') {
|
||||||
|
return Err(UriSegmentError::BadStart('*'))
|
||||||
|
} else if segment.ends_with(':') {
|
||||||
|
return Err(UriSegmentError::BadEnd(':'))
|
||||||
|
} else if segment.ends_with('>') {
|
||||||
|
return Err(UriSegmentError::BadEnd('>'))
|
||||||
|
} else if segment.ends_with('<') {
|
||||||
|
return Err(UriSegmentError::BadEnd('<'))
|
||||||
|
} else if segment.is_empty() {
|
||||||
|
continue
|
||||||
|
} else if cfg!(windows) && segment.contains('\\') {
|
||||||
|
return Err(UriSegmentError::BadChar('\\'))
|
||||||
|
} else {
|
||||||
|
buf.push(segment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! FROM_STR {
|
||||||
|
($type:ty) => {
|
||||||
|
impl FromParam for $type {
|
||||||
|
type Err = ErrorBadRequest<<$type as FromStr>::Err>;
|
||||||
|
|
||||||
|
fn from_param(val: &str) -> Result<Self, Self::Err> {
|
||||||
|
<$type as FromStr>::from_str(val).map_err(ErrorBadRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FROM_STR!(u8);
|
||||||
|
FROM_STR!(u16);
|
||||||
|
FROM_STR!(u32);
|
||||||
|
FROM_STR!(u64);
|
||||||
|
FROM_STR!(usize);
|
||||||
|
FROM_STR!(i8);
|
||||||
|
FROM_STR!(i16);
|
||||||
|
FROM_STR!(i32);
|
||||||
|
FROM_STR!(i64);
|
||||||
|
FROM_STR!(isize);
|
||||||
|
FROM_STR!(f32);
|
||||||
|
FROM_STR!(f64);
|
||||||
|
FROM_STR!(String);
|
||||||
|
FROM_STR!(std::net::IpAddr);
|
||||||
|
FROM_STR!(std::net::Ipv4Addr);
|
||||||
|
FROM_STR!(std::net::Ipv6Addr);
|
||||||
|
FROM_STR!(std::net::SocketAddr);
|
||||||
|
FROM_STR!(std::net::SocketAddrV4);
|
||||||
|
FROM_STR!(std::net::SocketAddrV6);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::iter::FromIterator;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_path_buf() {
|
||||||
|
assert_eq!(PathBuf::from_param("/test/.tt"), Err(UriSegmentError::BadStart('.')));
|
||||||
|
assert_eq!(PathBuf::from_param("/test/*tt"), Err(UriSegmentError::BadStart('*')));
|
||||||
|
assert_eq!(PathBuf::from_param("/test/tt:"), Err(UriSegmentError::BadEnd(':')));
|
||||||
|
assert_eq!(PathBuf::from_param("/test/tt<"), Err(UriSegmentError::BadEnd('<')));
|
||||||
|
assert_eq!(PathBuf::from_param("/test/tt>"), Err(UriSegmentError::BadEnd('>')));
|
||||||
|
assert_eq!(PathBuf::from_param("/seg1/seg2/"),
|
||||||
|
Ok(PathBuf::from_iter(vec!["seg1", "seg2"])));
|
||||||
|
assert_eq!(PathBuf::from_param("/seg1/../seg2/"),
|
||||||
|
Ok(PathBuf::from_iter(vec!["seg2"])));
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user