1
0
Fork 0
mirror of https://github.com/actix/actix-web.git synced 2024-11-30 05:21:09 +00:00

Merge branch 'master' into master

This commit is contained in:
Nikolay Kim 2018-01-07 08:31:31 -08:00 committed by GitHub
commit 3074071d03
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
115 changed files with 11398 additions and 4106 deletions

View file

@ -3,6 +3,15 @@ environment:
PROJECT_NAME: actix
matrix:
# 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
CHANNEL: stable
- TARGET: i686-pc-windows-msvc
@ -22,13 +31,13 @@ environment:
CHANNEL: beta
# Nightly channel
- TARGET: i686-pc-windows-gnu
CHANNEL: nightly
CHANNEL: nightly-2017-12-21
- TARGET: i686-pc-windows-msvc
CHANNEL: nightly
CHANNEL: nightly-2017-12-21
- TARGET: x86_64-pc-windows-gnu
CHANNEL: nightly
CHANNEL: nightly-2017-12-21
- TARGET: x86_64-pc-windows-msvc
CHANNEL: nightly
CHANNEL: nightly-2017-12-21
# Install Rust and Cargo
# (Based on from https://github.com/rust-lang/libc/blob/master/appveyor.yml)

View file

@ -4,7 +4,7 @@ rust:
- 1.20.0
- stable
- beta
- nightly
- nightly-2018-01-03
sudo: required
dist: trusty
@ -28,7 +28,28 @@ before_script:
- export PATH=$PATH:~/.cargo/bin
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
cargo clippy
@ -37,7 +58,7 @@ script:
# Upload docs
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 &&
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
cargo install mdbook &&
@ -49,18 +70,8 @@ after_success:
- |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz &&
tar xzf master.tar.gz &&
cd kcov-master &&
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) &&
bash <(curl https://raw.githubusercontent.com/xd009642/tarpaulin/master/travis-install.sh)
USE_SKEPTIC=1 cargo tarpaulin --out Xml
bash <(curl -s https://codecov.io/bash)
echo "Uploaded code coverage"
fi

View file

@ -15,6 +15,10 @@
* Content compression/decompression (br, gzip, deflate)
* Server multi-threading
* Gracefull shutdown support
## 0.2.1 (2017-11-03)

View file

@ -4,13 +4,13 @@ version = "0.3.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web framework"
readme = "README.md"
keywords = ["http", "http2", "web", "async", "futures"]
keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://github.com/actix/actix-web"
repository = "https://github.com/actix/actix-web.git"
documentation = "https://docs.rs/actix-web/"
categories = ["network-programming", "asynchronous",
"web-programming::http-server", "web-programming::websocket"]
license = "Apache-2.0"
license = "MIT/Apache-2.0"
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
build = "build.rs"
@ -42,7 +42,6 @@ httparse = "0.1"
http-range = "0.1"
mime = "0.3"
mime_guess = "1.8"
cookie = { version="0.10", features=["percent-encode", "secure"] }
regex = "0.2"
sha1 = "0.2"
url = "1.5"
@ -52,10 +51,17 @@ serde_json = "1.0"
flate2 = "0.2"
brotli2 = "^0.3.2"
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"
futures = "0.1"
tokio-io = "0.1"
@ -71,11 +77,8 @@ tokio-tls = { version="0.1", optional = true }
tokio-openssl = { version="0.1", optional = true }
[dependencies.actix]
version = "^0.3.1"
#path = "../actix"
#git = "https://github.com/actix/actix.git"
default-features = false
features = []
#version = "^0.4.2"
git = "https://github.com/actix/actix.git"
[dependencies.openssl]
version = "0.9"
@ -89,10 +92,24 @@ serde_derive = "1.0"
[build-dependencies]
skeptic = "0.13"
serde_derive = "1.0"
version_check = "0.1"
[profile.release]
lto = true
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
View 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.

View file

@ -1,6 +1,6 @@
.PHONY: default build test doc book clean
CARGO_FLAGS := --features "$(FEATURES)"
CARGO_FLAGS := --features "$(FEATURES) alpn"
default: test

149
README.md
View file

@ -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/)
* [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)
* 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
* Supported HTTP/1 and HTTP/2 protocols
* Streaming and pipelining
* Keep-alive and slow requests handling
* [WebSockets](https://actix.github.io/actix-web/actix_web/ws/index.html)
* Transparent content compression/decompression (br, gzip, deflate)
* Configurable request routing
* Multipart streams
* Middlewares (Logger, Session included)
* Supported *HTTP/1.x* and *HTTP/2.0* protocols
* Streaming and pipelining
* Keep-alive and slow requests handling
* [WebSockets](https://actix.github.io/actix-web/actix_web/ws/index.html)
* Transparent content compression/decompression (br, gzip, deflate)
* Configurable request routing
* Graceful server shutdown
* 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
[dependencies]
actix-web = { git = "https://github.com/actix/actix-web" }
```
## Examples
## HTTP/2
Actix web automatically upgrades connection to `http/2` if possible.
### Negotiation
`HTTP/2` protocol over tls without prior knowlage requires
[tls alpn](https://tools.ietf.org/html/rfc7301). At the moment only
`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)
* [Basic](https://github.com/actix/actix-web/tree/master/examples/basic/)
* [Stateful](https://github.com/actix/actix-web/tree/master/examples/state/)
* [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/)
* [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/)
* [SSL / HTTP/2.0](https://github.com/actix/actix-web/tree/master/examples/tls/)
* [Tcp/Websocket chat](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/)
* [SockJS Server](https://github.com/actix/actix-sockjs)
* [Json](https://github.com/actix/actix-web/tree/master/examples/json/)
## License
```rust
extern crate actix;
extern crate actix_web;
extern crate env_logger;
This project is licensed under either of
use actix::*;
use actix_web::*;
* 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))
* 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
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();
}
```
[![Analytics](https://ga-beacon.appspot.com/UA-110322332-2/actix-web/readme?flat&useReferer)](https://github.com/igrigorik/ga-beacon)

View file

@ -12,8 +12,19 @@ fn main() {
// generates doc tests for `README.md`.
skeptic::generate_doc_tests(
&["README.md",
"guide/src/qs_1.md",
"guide/src/qs_2.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 {
let _ = fs::File::create(f);

View file

@ -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();
}

View 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
View 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
View 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
View file

@ -0,0 +1 @@
DATABASE_URL=file:test.db

View 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
View 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)

View file

@ -0,0 +1 @@
DROP TABLE users

View file

@ -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
View 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())
}
}

View 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();
}

View 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,
}

View file

@ -0,0 +1,6 @@
table! {
users (id) {
id -> Text,
name -> Text,
}
}

BIN
examples/diesel/test.db Normal file

Binary file not shown.

View 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 = "../../" }

View 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
View 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
View 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
View 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
View 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();
}

View file

@ -2,6 +2,7 @@
name = "multipart-example"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[[bin]]
name = "multipart"
@ -9,5 +10,6 @@ path = "src/main.rs"
[dependencies]
env_logger = "*"
actix = "^0.3.1"
actix-web = { git = "https://github.com/actix/actix-web.git" }
futures = "0.1"
actix = "0.4"
actix-web = { git = "https://github.com/actix/actix-web" }

View 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``

View file

@ -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 aiohttp
def req1():
async def req1():
with aiohttp.MultipartWriter() as writer:
writer.append('test')
writer.append_json({'passed': True})
resp = yield from aiohttp.request(
resp = await aiohttp.ClientSession().request(
"post", 'http://localhost:8080/multipart',
data=writer, headers=writer.headers)
print(resp)
assert 200 == resp.status
def req2():
async def req2():
with aiohttp.MultipartWriter() as writer:
writer.append('test')
writer.append_json({'passed': True})
writer.append(open('src/main.rs'))
resp = yield from aiohttp.request(
resp = await aiohttp.ClientSession().request(
"post", 'http://localhost:8080/multipart',
data=writer, headers=writer.headers)
print(resp)

View file

@ -2,76 +2,58 @@
extern crate actix;
extern crate actix_web;
extern crate env_logger;
extern crate futures;
use actix::*;
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 {
type State = ();
fn index(mut req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>>
{
println!("{:?}", req);
fn request(mut req: HttpRequest, ctx: &mut HttpContext<Self>) -> RouteResult<Self> {
println!("{:?}", req);
req.multipart() // <- get multipart stream for current request
.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()?;
// get Multipart stream
WrapStream::<MyRoute>::actstream(multipart)
.and_then(|item, act, ctx| {
// Multipart stream is a stream of Fields and nested Multiparts
match item {
multipart::MultipartItem::Field(field) => {
println!("==== FIELD ==== {:?}", field);
// Read field's stream
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(()))
}
// Field in turn is stream of *Bytes* object
Either::A(
field.map_err(Error::from)
.map(|chunk| {
println!("-- CHUNK: \n{}",
std::str::from_utf8(&chunk).unwrap());})
.finish())
},
multipart::MultipartItem::Nested(mp) => {
// Or item could be nested Multipart stream
Either::B(result(Ok(())))
}
})
// wait until stream finish
.finish()
.map_err(|e, act, ctx| {
ctx.start(httpcodes::HTTPBadRequest);
ctx.write_eof();
})
.map(|_, act, ctx| {
ctx.start(httpcodes::HTTPOk);
ctx.write_eof();
})
.spawn(ctx);
Reply::async(MyRoute)
}
}
})
.finish() // <- Stream::finish() combinator from actix
.map(|_| httpcodes::HTTPOk.into())
.responder()
}
fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
let _ = env_logger::init();
let sys = actix::System::new("multipart-example");
HttpServer::new(
vec![
Application::default("/")
.resource("/multipart", |r| {
r.post::<MyRoute>();
}).finish()
])
.serve::<_, ()>("127.0.0.1:8080").unwrap();
let addr = HttpServer::new(
|| Application::new()
.middleware(middleware::Logger::default()) // <- logger
.resource("/multipart", |r| r.method(Method::POST).a(index)))
.bind("127.0.0.1:8080").unwrap()
.start();
println!("Starting http server: 127.0.0.1:8080");
let _ = sys.run();
}

11
examples/state/Cargo.toml Normal file
View 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
View 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/)

View file

@ -7,10 +7,12 @@ extern crate actix;
extern crate actix_web;
extern crate env_logger;
use actix::*;
use actix_web::*;
use std::cell::Cell;
use actix::*;
use actix_web::*;
/// Application state
struct AppState {
counter: Cell<usize>,
}
@ -34,11 +36,10 @@ impl Actor for MyWebSocket {
type Context = HttpContext<Self, AppState>;
}
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>
{
type Result = ();
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
self.counter += 1;
println!("WS({}): {:?}", self.counter, 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 sys = actix::System::new("ws-example");
HttpServer::new(
Application::build("/", AppState{counter: Cell::new(0)})
let addr = HttpServer::new(
|| Application::with_state(AppState{counter: Cell::new(0)})
// enable logger
.middleware(middlewares::Logger::default())
.middleware(middleware::Logger::default())
// 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
.handler("/", index))
.serve::<_, ()>("127.0.0.1:8080").unwrap();
.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();

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

BIN
examples/static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View 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 = "*"

View 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)

View 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();
}

View 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>

View 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>

View file

@ -2,6 +2,7 @@
name = "tls-example"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[[bin]]
name = "server"
@ -9,5 +10,5 @@ path = "src/main.rs"
[dependencies]
env_logger = "0.4"
actix = "^0.3.1"
actix-web = { git = "https://github.com/actix/actix-web.git", features=["alpn"] }
actix = "0.4"
actix-web = { git = "https://github.com/actix/actix-web", features=["alpn"] }

View file

@ -1,5 +1,16 @@
# 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)

View file

@ -8,6 +8,7 @@ use std::io::Read;
use actix_web::*;
/// somple handle
fn index(req: HttpRequest) -> Result<HttpResponse> {
println!("{:?}", req);
@ -29,21 +30,22 @@ fn main() {
file.read_to_end(&mut pkcs12).unwrap();
let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap().parse("12345").unwrap();
HttpServer::new(
Application::default("/")
let addr = HttpServer::new(
|| Application::new()
// enable logger
.middleware(middlewares::Logger::default())
.middleware(middleware::Logger::default())
// register simple handler, handle all methods
.handler("/index.html", index)
.resource("/index.html", |r| r.f(index))
// with path parameters
.resource("/", |r| r.handler(Method::GET, |req| {
.resource("/", |r| r.method(Method::GET).f(|req| {
httpcodes::HTTPFound
.build()
.header("LOCATION", "/index.html")
.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();
}

View file

@ -2,6 +2,7 @@
name = "websocket-example"
version = "0.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
workspace = "../.."
[[bin]]
name = "server"
@ -24,5 +25,6 @@ serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
actix = "^0.3.1"
actix-web = { git = "https://github.com/actix/actix-web.git" }
#actix = "0.4"
actix = { git = "https://github.com/actix/actix" }
actix-web = { git = "https://github.com/actix/actix-web" }

View file

@ -1,6 +1,6 @@
# Websocket chat example
This is extension of the
This is extension of the
[actix chat example](https://github.com/actix/actix/tree/master/examples/chat)
Added features:
@ -9,18 +9,16 @@ Added features:
* Chat server runs in separate thread
* Tcp listener runs in separate thread
## Server
Chat server listens for incoming tcp connections. Server can access several types of message:
* `\list` - list all available rooms
* `\join name` - join room, if room does not exist, create new one
* `\name name` - set session name
* `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
message for 10 seconds connection gets droppped
* `\list` - list all available rooms
* `\join name` - join room, if room does not exist, create new one
* `\name name` - set session name
* `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 message for 10 seconds connection gets droppped
To start server use command: `cargo run --bin server`
## Client
@ -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`
## WebSocket Browser Client
Open url: http://localhost:8080/
Open url: [http://localhost:8080/](http://localhost:8080/)

View file

@ -1,4 +1,4 @@
extern crate actix;
#[macro_use] extern crate actix;
extern crate bytes;
extern crate byteorder;
extern crate futures;
@ -56,13 +56,9 @@ fn main() {
struct ChatClient;
#[derive(Message)]
struct ClientCommand(String);
impl ResponseType for ClientCommand {
type Item = ();
type Error = ();
}
impl Actor for ChatClient {
type Context = FramedContext<Self>;
@ -70,6 +66,15 @@ impl Actor for ChatClient {
// start heartbeats otherwise server will disconnect after 10 seconds
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 {
@ -83,14 +88,13 @@ impl ChatClient {
}
/// Handle stdin commands
impl Handler<ClientCommand> for ChatClient
{
fn handle(&mut self, msg: ClientCommand, ctx: &mut FramedContext<Self>)
-> Response<Self, ClientCommand>
{
impl Handler<ClientCommand> for ChatClient {
type Result = ();
fn handle(&mut self, msg: ClientCommand, ctx: &mut FramedContext<Self>) {
let m = msg.0.trim();
if m.is_empty() {
return Self::empty()
return
}
// we check for /sss type of messages
@ -112,8 +116,6 @@ impl Handler<ClientCommand> for ChatClient
} else {
let _ = ctx.send(codec::ChatRequest::Message(m.to_owned()));
}
Self::empty()
}
}
@ -122,40 +124,26 @@ impl Handler<ClientCommand> for ChatClient
impl FramedActor for ChatClient {
type Io = TcpStream;
type Codec = codec::ClientChatCodec;
}
impl StreamHandler<codec::ChatResponse, io::Error> for ChatClient {
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>
{
fn handle(&mut self, msg: io::Result<codec::ChatResponse>, ctx: &mut FramedContext<Self>) {
match msg {
codec::ChatResponse::Message(ref msg) => {
println!("message: {}", msg);
}
codec::ChatResponse::Joined(ref msg) => {
println!("!!! joined: {}", msg);
}
codec::ChatResponse::Rooms(rooms) => {
println!("\n!!! Available rooms:");
for room in rooms {
println!("{}", room);
Err(_) => ctx.stop(),
Ok(msg) => match msg {
codec::ChatResponse::Message(ref msg) => {
println!("message: {}", msg);
}
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()
}
}

View file

@ -10,6 +10,7 @@ extern crate serde;
extern crate serde_json;
#[macro_use] extern crate serde_derive;
#[macro_use]
extern crate actix;
extern crate actix_web;
@ -29,7 +30,7 @@ struct WsChatSessionState {
}
/// Entry point for our route
fn chat_route(req: HttpRequest<WsChatSessionState>) -> Result<Reply> {
fn chat_route(req: HttpRequest<WsChatSessionState>) -> Result<HttpResponse> {
ws::start(
req,
WsChatSession {
@ -52,23 +53,49 @@ struct WsChatSession {
impl Actor for WsChatSession {
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
impl Handler<session::Message> for WsChatSession {
fn handle(&mut self, msg: session::Message, ctx: &mut Self::Context)
-> Response<Self, session::Message>
{
type Result = ();
fn handle(&mut self, msg: session::Message, ctx: &mut Self::Context) {
ws::WsWriter::text(ctx, &msg.0);
Self::empty()
}
}
/// WebSocket message handler
impl Handler<ws::Message> for WsChatSession {
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context)
-> Response<Self, ws::Message>
{
type Result = ();
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
println!("WEBSOCKET MESSAGE: {:?}", msg);
match 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() {
let _ = env_logger::init();
let sys = actix::System::new("websocket-example");
@ -192,24 +186,28 @@ fn main() {
Ok(())
}));
// Websocket sessions state
let state = WsChatSessionState { addr: server };
// Create Http server with websocket support
HttpServer::new(
Application::build("/", state)
// redirect to websocket.html
.resource("/", |r| r.handler(Method::GET, |req| {
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();
let addr = HttpServer::new(
move || {
// Websocket sessions state
let state = WsChatSessionState { addr: server.clone() };
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();
}

View file

@ -13,7 +13,7 @@ use session;
/// New chat session is created
pub struct Connect {
pub addr: Box<Subscriber<session::Message> + Send>,
pub addr: Box<actix::Subscriber<session::Message> + Send>,
}
/// Response type for Connect message
@ -25,16 +25,13 @@ impl ResponseType for Connect {
}
/// Session is disconnected
#[derive(Message)]
pub struct Disconnect {
pub id: usize,
}
impl ResponseType for Disconnect {
type Item = ();
type Error = ();
}
/// Send message to specific room
#[derive(Message)]
pub struct Message {
/// Id of the client session
pub id: usize,
@ -44,11 +41,6 @@ pub struct Message {
pub room: String,
}
impl ResponseType for Message {
type Item = ();
type Error = ();
}
/// List of available rooms
pub struct ListRooms;
@ -58,6 +50,7 @@ impl ResponseType for ListRooms {
}
/// Join room, if room does not exists create new one.
#[derive(Message)]
pub struct Join {
/// Client id
pub id: usize,
@ -65,15 +58,10 @@ pub struct Join {
pub name: String,
}
impl ResponseType for Join {
type Item = ();
type Error = ();
}
/// `ChatServer` manages chat rooms and responsible for coordinating chat session.
/// implementation is super primitive
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>>,
rng: RefCell<ThreadRng>,
}
@ -118,8 +106,9 @@ impl Actor for ChatServer {
///
/// Register new session and assign unique id to this session
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");
// 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);
// send id back
Self::reply(id)
Ok(id)
}
}
/// Handler for Disconnect message.
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");
let mut rooms: Vec<String> = Vec::new();
@ -158,40 +148,39 @@ impl Handler<Disconnect> for ChatServer {
for room in rooms {
self.send_message(&room, "Someone disconnected", 0);
}
Self::empty()
}
}
/// Handler for Message message.
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::empty()
}
}
/// Handler for `ListRooms` message.
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();
for key in self.rooms.keys() {
rooms.push(key.to_owned())
}
Self::reply(rooms)
Ok(rooms)
}
}
/// Join room, send disconnect message to old room
/// send join message to new room
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 mut rooms = Vec::new();
@ -211,7 +200,5 @@ impl Handler<Join> for ChatServer {
}
self.send_message(&name, "Someone connected", id);
self.rooms.get_mut(&name).unwrap().insert(id);
Self::empty()
}
}

View file

@ -6,20 +6,16 @@ use std::time::{Instant, Duration};
use futures::Stream;
use tokio_core::net::{TcpStream, TcpListener};
use actix::*;
use actix::prelude::*;
use server::{self, ChatServer};
use codec::{ChatRequest, ChatResponse, ChatCodec};
/// Chat server sends this messages to session
#[derive(Message)]
pub struct Message(pub String);
impl ResponseType for Message {
type Item = ();
type Error = ();
}
/// `ChatSession` actor is responsible for tcp peer communitions.
pub struct ChatSession {
/// unique session id
@ -36,104 +32,87 @@ impl Actor for ChatSession {
/// For tcp communication we are going to use `FramedContext`.
/// It is convinient wrapper around `Framed` object from `tokio_io`
type Context = FramedContext<Self>;
}
/// To use `FramedContext` we have to define Io type and Codec
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>) {
fn started(&mut self, ctx: &mut Self::Context) {
// we'll start heartbeat process on session start.
self.hb(ctx);
// register self in chat server. `AsyncContext::wait` register
// future within context, but context waits until this future resolves
// before processing any other events.
self.addr.call(self, server::Connect{addr: ctx.sync_subscriber()}).then(|res, act, ctx| {
match res {
Ok(Ok(res)) => act.id = res,
// something is wrong with chat server
_ => ctx.stop(),
}
fut::ok(())
}).wait(ctx);
let addr: SyncAddress<_> = ctx.address();
self.addr.call(self, server::Connect{addr: addr.subscriber()})
.then(|res, act, ctx| {
match res {
Ok(Ok(res)) => act.id = res,
// something is wrong with chat server
_ => ctx.stop(),
}
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
self.addr.send(server::Disconnect{id: self.id});
ctx.stop()
true
}
}
impl Handler<ChatRequest, io::Error> for ChatSession {
/// We'll stop chat session actor on any error, high likely it is just
/// termination of the tcp stream.
fn error(&mut self, _: io::Error, ctx: &mut FramedContext<Self>) {
ctx.stop()
}
/// To use `FramedContext` we have to define Io type and Codec
impl FramedActor for ChatSession {
type Io = TcpStream;
type Codec= ChatCodec;
/// This is main event loop for client requests
fn handle(&mut self, msg: ChatRequest, ctx: &mut FramedContext<Self>)
-> Response<Self, ChatRequest>
{
fn handle(&mut self, msg: io::Result<ChatRequest>, ctx: &mut FramedContext<Self>) {
match msg {
ChatRequest::List => {
// Send ListRooms message to chat server and wait for response
println!("List rooms");
self.addr.call(self, server::ListRooms).then(|res, _, ctx| {
match res {
Ok(Ok(rooms)) => {
let _ = ctx.send(ChatResponse::Rooms(rooms));
},
Err(_) => ctx.stop(),
Ok(msg) => match msg {
ChatRequest::List => {
// Send ListRooms message to chat server and wait for response
println!("List rooms");
self.addr.call(self, server::ListRooms).then(|res, _, ctx| {
match res {
Ok(Ok(rooms)) => {
let _ = ctx.send(ChatResponse::Rooms(rooms));
},
_ => println!("Something is wrong"),
}
fut::ok(())
}).wait(ctx)
// .wait(ctx) pauses all events in context,
// so actor wont receive any new messages until it get list of rooms back
},
ChatRequest::Join(name) => {
println!("Join to room: {}", name);
self.room = name.clone();
self.addr.send(server::Join{id: self.id, name: name.clone()});
let _ = ctx.send(ChatResponse::Joined(name));
},
ChatRequest::Message(message) => {
// send message to chat server
println!("Peer message: {}", message);
self.addr.send(
server::Message{id: self.id,
msg: message, room:
self.room.clone()})
}
actix::fut::ok(())
}).wait(ctx)
// .wait(ctx) pauses all events in context,
// so actor wont receive any new messages until it get list of rooms back
},
ChatRequest::Join(name) => {
println!("Join to room: {}", name);
self.room = name.clone();
self.addr.send(server::Join{id: self.id, name: name.clone()});
let _ = ctx.send(ChatResponse::Joined(name));
},
ChatRequest::Message(message) => {
// send message to chat server
println!("Peer message: {}", message);
self.addr.send(
server::Message{id: self.id,
msg: message, room:
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
impl Handler<Message> for ChatSession {
type Result = ();
fn handle(&mut self, msg: Message, ctx: &mut FramedContext<Self>)
-> Response<Self, Message>
{
fn handle(&mut self, msg: Message, ctx: &mut FramedContext<Self>) {
// send message to peer
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
// stream handler `StreamHandler<(TcpStream, net::SocketAddr), io::Error>`
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}
});
}
@ -200,27 +181,17 @@ impl Actor for TcpServer {
type Context = Context<Self>;
}
#[derive(Message)]
struct TcpConnect(TcpStream, net::SocketAddr);
impl ResponseType for TcpConnect {
type Item = ();
type Error = ();
}
/// 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>) -> Response<Self, TcpConnect>
{
fn handle(&mut self, msg: TcpConnect, _: &mut Context<Self>) {
// For each incoming connection we create `ChatSession` actor
// with out chat server address.
let server = self.chat.clone();
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()
}
}

View 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" }

View 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``

View file

@ -11,10 +11,9 @@ extern crate env_logger;
use actix::*;
use actix_web::*;
/// do websocket handshake and start `MyWebSocket` actor
fn ws_index(r: HttpRequest) -> Reply {
ws::start(r, MyWebSocket).into()
fn ws_index(r: HttpRequest) -> Result<HttpResponse> {
ws::start(r, MyWebSocket)
}
/// websocket connection is long running connection, it easier
@ -25,21 +24,11 @@ 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 {
fn started(&mut self, ctx: &mut Self::Context) {
println!("WebSocket session openned");
}
fn finished(&mut self, ctx: &mut Self::Context) {
println!("WebSocket session closed");
}
}
/// Handler for `ws::Message`
impl Handler<ws::Message> for MyWebSocket {
fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext<Self>)
-> Response<Self, ws::Message>
{
type Result = ();
fn handle(&mut self, msg: ws::Message, ctx: &mut HttpContext<Self>) {
// process websocket messages
println!("WS: {:?}", 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 sys = actix::System::new("ws-example");
HttpServer::new(
Application::default("/")
let _addr = HttpServer::new(
|| Application::new()
// enable logger
.middleware(middlewares::Logger::default())
.middleware(middleware::Logger::default())
// websocket route
.resource("/ws/", |r| r.get(ws_index))
.resource("/ws/", |r| r.method(Method::GET).f(ws_index))
// static files
.route("/", StaticFiles::new("examples/static/", true)))
.handler("/", fs::StaticFiles::new("../static/", true)))
// 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");
let _ = sys.run();

View file

@ -3,13 +3,14 @@
[Quickstart](./qs_1.md)
- [Getting Started](./qs_2.md)
- [Application](./qs_3.md)
- [Server](./qs_3_5.md)
- [Handler](./qs_4.md)
- [Resources and Routes](./qs_5.md)
- [Application state](./qs_6.md)
- [Request](./qs_7.md)
- [Response](./qs_8.md)
- [WebSockets](./qs_9.md)
- [User sessions](./qs_10.md)
- [Logging](./qs_11.md)
- [Errors](./qs_4_5.md)
- [URL Dispatch](./qs_5.md)
- [Request & Response](./qs_7.md)
- [Testing](./qs_8.md)
- [Middlewares](./qs_10.md)
- [Static file handling](./qs_12.md)
- [WebSockets](./qs_9.md)
- [HTTP/2](./qs_13.md)
- [Database integration](./qs_14.md)

View file

@ -1,6 +1,6 @@
# Quickstart
# Quick start
Before you can start writing a actix web application, youll need a version of Rust installed.
Before you can start writing a actix web application, youll need a version of Rust installed.
We recommend you use rustup to install or configure such a version.
## Install Rust
@ -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
and run the included examples in the examples/ directory. The following set of
commands runs the `basic` example:
commands runs the `basics` example:
```bash
git clone https://github.com/actix/actix-web
cd actix-web
cargo run --example basic
cd actix-web/examples/basics
cargo run
```
Check `examples/` directory for more examples.
Check [examples/](https://github.com/actix/actix-web/tree/master/examples) directory for more examples.

View file

@ -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();
}
```

View file

@ -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']

View file

@ -1 +1,44 @@
# 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.

View file

@ -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
`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
`rust-openssl` has support. Turn on `alpn` feature to enable `alpn` negotiation.
With enable `alpn` feature `HttpServer` provides
@ -26,15 +26,16 @@ fn main() {
let pkcs12 = Pkcs12::from_der(&pkcs12).unwrap().parse("12345").unwrap();
HttpServer::new(
Application::default("/")
.handler("/index.html", index)
.serve_tls::<_, ()>("127.0.0.1:8080", pkcs12).unwrap();
|| Application::new()
.resource("/index.html", |r| r.f(index)))
.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.
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)
Please check [example](https://github.com/actix/actix-web/tree/master/examples/tls)

124
guide/src/qs_14.md Normal file
View 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).

View file

@ -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
and returns a type that can be converted into `HttpResponse`:
```rust,ignore
extern crate actix_web;
use actix_web::*;
fn index(req: HttpRequest) -> &'static str {
"Hello world!"
}
```rust
# extern crate actix_web;
# use actix_web::*;
fn index(req: HttpRequest) -> &'static str {
"Hello world!"
}
# fn main() {}
```
Next, create an `Application` instance and register the
request handler with the application's `resource` on a particular *HTTP method* and *path*::
```rust,ignore
let app = Application::default("/")
.resource("/", |r| r.get(index))
.finish()
```rust
# extern crate actix_web;
# use actix_web::*;
# 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
connections:
connections. Server accepts function that should return `HttpHandler` instance:
```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.
@ -59,9 +69,8 @@ Head over to ``http://localhost:8088/`` to see the results.
Here is full source of main.rs file:
```rust
extern crate actix;
extern crate actix_web;
```rust,ignore
# extern crate actix_web;
use actix_web::*;
fn index(req: HttpRequest) -> &'static str {
@ -69,22 +78,15 @@ fn index(req: HttpRequest) -> &'static str {
}
fn main() {
let sys = actix::System::new("example");
HttpServer::new(
Application::default("/")
.resource("/", |r| r.get(index)))
.serve::<_, ()>("127.0.0.1:8088").unwrap();
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();
|| Application::new()
.resource("/", |r| r.f(index)))
.bind("127.0.0.1:8088").expect("Can not bind to 127.0.0.1:8088")
.run();
}
```
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
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/)

View file

@ -5,44 +5,105 @@ It provides routing, middlewares, pre-processing of requests, and post-processin
websocket protcol handling, multipart streams, etc.
All actix web server is built around `Application` instance.
It is used for registering handlers for routes and resources, middlewares.
Also it stores applicationspecific state that is shared accross all handlers
It is used for registering routes for resources, middlewares.
Also it stores application specific state that is shared across all handlers
within same 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
let app = Application::default("/prefix")
.resource("/index.html", |r| r.handler(Method::GET, index)
# extern crate actix_web;
# 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()
# }
```
In this example application with `/prefix` prefix and `index.html` resource
get created. This resource is available as on `/prefix/index.html` url.
In this example application with `/app` prefix and `index.html` resource
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:
```rust
extern crate actix_web;
extern crate tokio_core;
use std::net::SocketAddr;
# extern crate actix_web;
# extern crate tokio_core;
# use tokio_core::net::TcpStream;
# use std::net::SocketAddr;
use actix_web::*;
use tokio_core::net::TcpStream;
fn main() {
HttpServer::<TcpStream, SocketAddr, _>::new(vec![
Application::default("/app1")
.resource("/", |r| r.get(|r| httpcodes::HTTPOk))
.finish(),
Application::default("/app2")
.resource("/", |r| r.get(|r| httpcodes::HTTPOk))
.finish(),
Application::default("/")
.resource("/", |r| r.get(|r| httpcodes::HTTPOk))
.finish(),
HttpServer::<TcpStream, SocketAddr, _, _>::new(|| vec![
Application::new()
.prefix("/app1")
.resource("/", |r| r.f(|r| httpcodes::HTTPOk)),
Application::new()
.prefix("/app2")
.resource("/", |r| r.f(|r| httpcodes::HTTPOk)),
Application::new()
.resource("/", |r| r.f(|r| httpcodes::HTTPOk)),
]);
}
```
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
View 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.

View file

@ -1,21 +1,19 @@
# Handler
A request handler can by any object that implements
[`Handler` trait](../actix_web/struct.HttpResponse.html#implementations).
A request handler can by any object that implements
[*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:
* 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,
By default actix provides `Responder` implementations for some standard types,
like `&'static str`, `String`, etc.
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
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:
```rust
extern crate actix;
extern crate actix_web;
# extern crate actix;
# extern crate actix_web;
extern crate serde;
extern crate serde_json;
#[macro_use] extern crate serde_derive;
@ -55,87 +135,101 @@ use actix_web::*;
#[derive(Serialize)]
struct MyObj {
name: String,
name: &'static str,
}
/// we have to convert Error into HttpResponse as well, but with
/// specialization this could be handled genericly.
impl Into<HttpResponse> for MyObj {
fn into(self) -> HttpResponse {
let body = match serde_json::to_string(&self) {
Err(err) => return Error::from(err).into(),
Ok(body) => body,
};
/// Responder
impl Responder for MyObj {
type Item = HttpResponse;
type Error = Error;
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse> {
let body = serde_json::to_string(&self)?;
// Create response and set content type
HttpResponse::Ok()
Ok(HttpResponse::Ok()
.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() {
let sys = actix::System::new("example");
HttpServer::new(
Application::default("/")
.resource("/", |r| r.handler(
Method::GET, |req| {MyObj{name: "user".to_owned()}})))
.serve::<_, ()>("127.0.0.1:8088").unwrap();
|| Application::new()
.resource("/", |r| r.method(Method::GET).f(index)))
.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)); // <- remove this line, this code stops system during testing
# actix::Arbiter::system().send(actix::msgs::SystemExit(0));
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
There are two different types of async handlers.
Response object could be generated asynchronously. In this case handle must
return `Future` object that resolves to `HttpResponse`, i.e:
Response object could be generated asynchronously or more precisely, any type
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
fn index(req: HttpRequest) -> Box<Future<HttpResponse, Error>> {
...
```rust
# 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
must implement stream trait `Stream<Item=Bytes, Error=Error>`, i.e:
```rust,ignore
```rust
# extern crate actix_web;
# extern crate futures;
# extern crate bytes;
# use actix_web::*;
# use bytes::Bytes;
# use futures::stream::once;
fn index(req: HttpRequest) -> HttpResponse {
let body: 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")
.body(Body::Streaming(body)).unwrap()
.body(Body::Streaming(Box::new(body))).unwrap()
}
fn main() {
Application::default("/")
.async("/async", index)
Application::new()
.resource("/async", |r| r.f(index))
.finish();
}
```

136
guide/src/qs_4_5.md Normal file
View 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.

View file

@ -1,107 +1,578 @@
# Resources and Routes
# URL Dispatch
All resources and routes register for specific application.
Application routes incoming requests based on route criteria which is defined during
resource registration or path prefix for simple handlers.
Internally *router* is a list of *resources*. Resource is an entry in *route table*
which corresponds to requested URL.
URL dispatch provides a simple way to map URLs to `Handler` code using a simple pattern matching
language. *Regex* crate and it's
[*RegexSet*](https://doc.rust-lang.org/regex/regex/struct.RegexSet.html) is beeing used for
pattern matching. If one of the patterns matches the path information associated with a request,
a particular handler object is invoked. A handler is a specific object that implements
`Handler` trait, defined in your application, that receives the request and returns
a response object. More informatin is available in [handler section](../qs_4.html).
Prefix handler:
## Resource configuration
```rust,ignore
fn index(req: Httprequest) -> HttpResponse {
...
}
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::default("/")
.handler("/prefix", |req| index)
Application::new()
.resource("/prefix", |r| r.f(index))
.resource("/user/{name}",
|r| r.method(Method::GET).f(|req| HTTPOk))
.finish();
}
```
In this example `index` get called for any url which starts with `/prefix`.
Application prefix combines with handler prefix i.e
*Configuraiton function* has following type:
```rust,ignore
FnOnce(&mut Resource<_>) -> ()
```
*Configration function* can set name and register specific routes.
If resource does not contain any route or does not have any matching routes it
returns *NOT FOUND* http resources.
## Configuring a Route
Resource contains set of routes. Each route in turn has set of predicates and handler.
New route could be crearted with `Resource::route()` method which returns reference
to new *Route* instance. By default *route* does not contain any predicates, so matches
all requests and default handler is `HTTPNotFound`.
Application routes incoming requests based on route criteria which is defined during
resource registration and route registration. Resource matches all routes it contains in
the order that the routes were registered via `Resource::route()`. *Route* can contain
any number of *predicates* but only one handler.
```rust
# extern crate actix_web;
# use actix_web::*;
# use actix_web::httpcodes::*;
fn main() {
Application::default("/app")
.handler("/prefix", |req| index)
Application::new()
.resource("/path", |resource|
resource.route()
.p(pred::Get())
.p(pred::Header("content-type", "text/plain"))
.f(|req| HTTPOk)
)
.finish();
}
```
In this example `index` get called for any url which starts with`/app/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.
Resource contains set of route for same endpoint. Route corresponds to handling
*HTTP method* by calling *web handler*. Resource select route based on *http method*,
if no route could be matched default response `HTTPMethodNotAllowed` get resturned.
[*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() {
Application::default("/")
.resource("/prefix", |r| {
r.get(HTTPOk)
r.post(HTTPForbidden)
Application::new()
.resource(r"/a/{v1}/{v2}/", |r| r.f(index))
.finish();
}
```
For this example for path '/a/1/2/', values v1 and v2 will resolve to "1" and "2".
It is possible to create a `PathBuf` from a tail path parameter. The returned `PathBuf` is
percent-decoded. If a segment is equal to "..", the previous segment (if
any) is skipped.
For security purposes, if a segment meets any of the following conditions,
an `Err` is returned indicating the condition met:
* Decoded segment starts with any of: `.` (except `..`), `*`
* Decoded segment ends with any of: `:`, `>`, `<`
* Decoded segment contains any of: `/`
* On Windows, decoded segment contains any of: '\'
* Percent-encoding results in invalid UTF8.
As a result of these conditions, a `PathBuf` parsed from request path parameter is
safe to interpolate within, or use as a suffix of, a path without additional checks.
```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))
}
fn main() {
Application::new()
.resource(r"/a/{tail:.*}", |r| r.method(Method::GET).f(index))
.finish();
}
```
List of `FromParam` implementation could be found in
[api docs](../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();
}
```
[`ApplicationBuilder::resource()` method](../actix_web/dev/struct.ApplicationBuilder.html#method.resource)
accepts configuration function, resource could be configured at once.
Check [`Resource`](../actix-web/target/doc/actix_web/struct.Resource.html) documentation
for more information.
This would return something like the string *http://example.com/test/1/2/3* (at least if
the current protocol and hostname implied http://example.com).
`url_for()` method return [*Url object*](https://docs.rs/url/1.6.0/url/struct.Url.html) so you
can modify this url (add query parameters, anchor, etc).
`url_for()` could be called only for *named* resources otherwise error get returned.
## Variable resources
Resource may have *variable path*also. For instance, a resource with the
path '/a/{name}/c' would match all incoming requests with paths such
as '/a/b/c', '/a/1/c', and '/a/etc/c'.
A *variable part*is specified in the form {identifier}, where the identifier can be
used later in a request handler to access the matched value for that part. This is
done by looking up the identifier in the `HttpRequest.match_info` object:
## External resources
Resources that are valid URLs, could be registered as external resources. They are useful
for URL generation purposes only and are never considered for matching at request time.
```rust
extern crate actix;
# extern crate actix_web;
use actix_web::*;
fn index(req: Httprequest) -> String {
format!("Hello, {}", req.match_info.get('name').unwrap())
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() {
Application::default("/")
.resource("/{name}", |r| r.get(index))
let app = Application::new()
.resource("/index.html", |r| r.f(index))
.external_resource("youtube", "https://youtube.com/watch/{video_id}")
.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
fn main() {
Application::default("/")
.resource(r"{name:\d+}", |r| r.get(index))
.finish();
}
pred::Any(pred::Get()).or(pred::Post())
```
To match path tail, `{tail:*}` pattern could be used. Tail pattern has to be last
segment in path otherwise it panics.
`All` predicate accept list of predicates and matches if all of the supplied
predicates match. i.e:
```rust,ignore
fn main() {
Application::default("/")
.resource(r"/test/{tail:*}", |r| r.get(index))
.finish();
}
pred::All(pred::Get()).and(pred::Header("content-type", "plain/text"))
```
Above example would match all incoming requests with path such as
'/test/b/c', '/test/index.html', and '/test/etc/test'.
## Changing the default Not Found response
If path pattern can not be found in routing table or resource can not find matching
route, default resource is used. Default response is *NOT FOUND* response.
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();
}
```

View file

@ -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();
}
```

View file

@ -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() {}
```

View file

@ -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
}
```

View file

@ -1 +1,48 @@
# 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/)

View file

@ -1,113 +1,195 @@
use std::mem;
use std::rc::Rc;
use std::cell::RefCell;
use std::collections::HashMap;
use futures::Future;
use error::Error;
use route::{RouteHandler, Reply, Handler, WrapHandler, AsyncHandler};
use handler::Reply;
use router::{Router, Pattern};
use resource::Resource;
use recognizer::{RouteRecognizer, check_pattern};
use handler::{Handler, RouteHandler, WrapHandler};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use channel::HttpHandler;
use pipeline::Pipeline;
use middlewares::Middleware;
use channel::{HttpHandler, IntoHttpHandler, HttpHandlerTask};
use pipeline::{Pipeline, PipelineHandler};
use middleware::Middleware;
use server::ServerSettings;
/// Application
pub struct Application<S> {
pub struct HttpApplication<S=()> {
state: Rc<S>,
prefix: String,
default: Resource<S>,
handlers: HashMap<String, Box<RouteHandler<S>>>,
router: RouteRecognizer<Resource<S>>,
middlewares: Rc<Vec<Box<Middleware>>>,
router: Router,
inner: Rc<RefCell<Inner<S>>>,
middlewares: Rc<Vec<Box<Middleware<S>>>>,
}
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 {
let mut req = req.with_state(Rc::clone(&self.state));
impl<S: 'static> PipelineHandler<S> for Inner<S> {
if let Some((params, h)) = self.router.recognize(req.path()) {
if let Some(params) = params {
req.set_match_info(params);
}
h.handle(req)
fn handle(&mut self, mut req: HttpRequest<S>) -> Reply {
if let Some(idx) = self.router.recognize(&mut req) {
self.resources[idx].handle(req.clone(), Some(&mut self.default))
} else {
for (prefix, handler) in &self.handlers {
if req.path().starts_with(prefix) {
req.set_prefix(prefix.len());
for &mut (ref prefix, ref mut handler) in &mut self.handlers {
let m = {
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)
}
}
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> {
if req.path().starts_with(&self.prefix) {
Ok(Pipeline::new(req, Rc::clone(&self.middlewares),
&|req: HttpRequest| self.run(req)))
impl<S: 'static> HttpHandler for HttpApplication<S> {
fn handle(&mut self, req: HttpRequest) -> Result<Box<HttpHandlerTask>, HttpRequest> {
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 {
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<()> {
/// Create default `ApplicationBuilder` with no state
pub fn default<T: Into<String>>(prefix: T) -> ApplicationBuilder<()> {
ApplicationBuilder {
parts: Some(ApplicationBuilderParts {
/// Create application with empty state. Application can
/// be configured with builder-like pattern.
pub fn new() -> Application<()> {
Application {
parts: Some(ApplicationParts {
state: (),
prefix: prefix.into(),
prefix: "/".to_owned(),
settings: ServerSettings::default(),
default: Resource::default_not_found(),
handlers: HashMap::new(),
resources: HashMap::new(),
handlers: Vec::new(),
external: HashMap::new(),
middlewares: Vec::new(),
})
}
}
}
impl Default for Application<()> {
fn default() -> Self {
Application::new()
}
}
impl<S> Application<S> where S: 'static {
/// Create application builder with specific state. State is shared with all
/// routes within same application and could be
/// accessed with `HttpContext::state()` method.
pub fn build<T: Into<String>>(prefix: T, state: S) -> ApplicationBuilder<S> {
ApplicationBuilder {
parts: Some(ApplicationBuilderParts {
/// Create application with specific state. Application can be
/// configured with builder-like pattern.
///
/// State is shared with all reousrces within same application and could be
/// accessed with `HttpRequest::state()` method.
pub fn with_state(state: S) -> Application<S> {
Application {
parts: Some(ApplicationParts {
state: state,
prefix: prefix.into(),
prefix: "/".to_owned(),
settings: ServerSettings::default(),
default: Resource::default_not_found(),
handlers: HashMap::new(),
resources: HashMap::new(),
handlers: Vec::new(),
external: HashMap::new(),
middlewares: Vec::new(),
})
}
}
}
struct ApplicationBuilderParts<S> {
state: S,
prefix: String,
default: Resource<S>,
handlers: HashMap<String, Box<RouteHandler<S>>>,
resources: HashMap<String, Resource<S>>,
middlewares: Vec<Box<Middleware>>,
}
/// Structure that follows the builder pattern for building `Application` structs.
pub struct ApplicationBuilder<S=()> {
parts: Option<ApplicationBuilderParts<S>>,
}
impl<S> ApplicationBuilder<S> where S: 'static {
/// Set application prefix
///
/// Only requests that matches application's prefix get processed by this application.
/// Application prefix always contains laading "/" slash. If supplied prefix
/// does not contain leading slash, it get inserted. Prefix should
/// consists valid 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.
///
/// In the following example only requests with "/app/" path prefix
/// get handled. Request with path "/app/test/" would be handled,
/// but request with path "/application" or "/other/..." would return *NOT FOUND*
///
/// ```rust
/// # extern crate actix_web;
/// use actix_web::*;
///
/// fn main() {
/// let app = Application::new()
/// .prefix("/app")
/// .resource("/test", |r| {
/// r.method(Method::GET).f(|_| httpcodes::HTTPOk);
/// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed);
/// })
/// .finish();
/// }
/// ```
pub fn prefix<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.
///
@ -118,7 +200,7 @@ impl<S> ApplicationBuilder<S> where S: 'static {
/// 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 `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 `[^{}/]+`.
///
@ -128,37 +210,39 @@ impl<S> ApplicationBuilder<S> where S: 'static {
/// store userid and friend in the exposed Params object:
///
/// ```rust
/// extern crate actix_web;
/// # extern crate actix_web;
/// use actix_web::*;
///
/// fn main() {
/// let app = Application::default("/")
/// let app = Application::new()
/// .resource("/test", |r| {
/// r.get(|req| httpcodes::HTTPOk);
/// r.handler(Method::HEAD, |req| httpcodes::HTTPMethodNotAllowed);
/// })
/// .finish();
/// r.method(Method::GET).f(|_| httpcodes::HTTPOk);
/// r.method(Method::HEAD).f(|_| httpcodes::HTTPMethodNotAllowed);
/// });
/// }
/// ```
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
{
{
let parts = self.parts.as_mut().expect("Use after finish");
// add resource
let path = path.into();
if !parts.resources.contains_key(&path) {
check_pattern(&path);
parts.resources.insert(path.clone(), Resource::default());
let mut resource = Resource::default();
f(&mut resource);
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
}
/// Default resource is used if no match route could be found.
pub fn default_resource<F>(&mut self, f: F) -> &mut Self
/// Default resource is used if no matched route could be found.
pub fn default_resource<F>(mut self, f: F) -> Application<S>
where F: FnOnce(&mut Resource<S>) + 'static
{
{
@ -168,105 +252,142 @@ impl<S> ApplicationBuilder<S> where S: 'static {
self
}
/// This method register handler for specified path prefix.
/// Any path that starts with this prefix matches handler.
/// Register external resource.
///
/// 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
/// extern crate actix_web;
/// # extern crate actix_web;
/// use actix_web::*;
///
/// fn index(mut req: HttpRequest) -> Result<HttpResponse> {
/// let url = req.url_for("youtube", &["oHg5SJYRHA0"])?;
/// assert_eq!(url.as_str(), "https://youtube.com/watch/oHg5SJYRHA0");
/// Ok(httpcodes::HTTPOk.into())
/// }
///
/// fn main() {
/// let app = Application::default("/")
/// .handler("/test", |req| {
/// match *req.method() {
/// Method::GET => httpcodes::HTTPOk,
/// Method::POST => httpcodes::HTTPMethodNotAllowed,
/// _ => httpcodes::HTTPNotFound,
/// }
/// })
/// let app = Application::new()
/// .resource("/index.html", |r| r.f(index))
/// .external_resource("youtube", "https://youtube.com/watch/{video_id}")
/// .finish();
/// }
/// ```
pub fn handler<P, F, R>(&mut self, path: P, handler: F) -> &mut Self
where P: Into<String>,
F: Fn(HttpRequest<S>) -> R + 'static,
R: Into<Reply> + 'static
pub fn external_resource<T, U>(mut self, name: T, url: U) -> Application<S>
where T: AsRef<str>, U: AsRef<str>
{
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
}
/// This method register handler for specified path prefix.
/// Any path that starts with this prefix matches handler.
pub fn route<P, H>(&mut self, path: P, handler: H) -> &mut Self
where P: Into<String>, H: Handler<S>
/// Configure handler for specific path prefix.
///
/// Path prefix consists valid path segments. i.e for prefix `/app`
/// 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
}
/// This method register async handler for specified path prefix.
/// Any path that starts with this prefix matches handler.
pub fn async<P, F, R>(&mut self, path: P, handler: F) -> &mut Self
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
/// Register a middleware
pub fn middleware<T>(mut self, mw: T) -> Application<S>
where T: Middleware<S> + 'static
{
self.parts.as_mut().expect("Use after finish")
.middlewares.push(Box::new(mw));
self
}
/// Construct application
pub fn finish(&mut self) -> Application<S> {
/// Finish application configuration and create HttpHandler object
pub fn finish(&mut self) -> HttpApplication<S> {
let parts = self.parts.take().expect("Use after finish");
let prefix = parts.prefix.trim().trim_right_matches('/');
let mut handlers = HashMap::new();
let prefix = if parts.prefix.ends_with('/') {
parts.prefix
} else {
parts.prefix + "/"
};
let mut routes = Vec::new();
for (path, handler) in parts.resources {
routes.push((path, handler))
let mut resources = parts.resources;
for (_, pattern) in parts.external {
resources.insert(pattern, None);
}
for (path, mut handler) in parts.handlers {
let path = prefix.clone() + path.trim_left_matches('/');
handlers.insert(path, handler);
}
Application {
let (router, resources) = Router::new(prefix, parts.settings, resources);
let inner = Rc::new(RefCell::new(
Inner {
prefix: prefix.len(),
default: parts.default,
router: router.clone(),
resources: resources,
handlers: parts.handlers,
}
));
HttpApplication {
state: Rc::new(parts.state),
prefix: prefix.clone(),
default: parts.default,
handlers: handlers,
router: RouteRecognizer::new(prefix, routes),
prefix: prefix.to_owned(),
inner: inner,
router: router.clone(),
middlewares: Rc::new(parts.middlewares),
}
}
}
impl<S: 'static> From<ApplicationBuilder<S>> for Application<S> {
fn from(mut builder: ApplicationBuilder<S>) -> Application<S> {
builder.finish()
impl<S: 'static> IntoHttpHandler for Application<S> {
type Handler = HttpApplication<S>;
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> {
type Item = Application<S>;
impl<'a, S: 'static> IntoHttpHandler for &'a mut 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> {
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);
}
}

View file

@ -5,9 +5,10 @@ use bytes::{Bytes, BytesMut};
use futures::Stream;
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.
pub enum Body {
@ -18,12 +19,8 @@ pub enum Body {
/// Unspecified streaming response. Developer is responsible for setting
/// right `Content-Length` or `Transfer-Encoding` headers.
Streaming(BodyStream),
/// Upgrade connection.
Upgrade(BodyStream),
/// Special body type for actor streaming response.
StreamingContext,
/// Special body type for actor upgrade response.
UpgradeContext,
/// Special body type for actor response.
Actor(Box<ActorHttpContext>),
}
/// Represents various types of binary body.
@ -48,15 +45,16 @@ pub enum Binary {
impl Body {
/// Does this body streaming.
#[inline]
pub fn is_streaming(&self) -> bool {
match *self {
Body::Streaming(_) | Body::StreamingContext
| Body::Upgrade(_) | Body::UpgradeContext => true,
Body::Streaming(_) | Body::Actor(_) => true,
_ => false
}
}
/// Is this binary body.
#[inline]
pub fn is_binary(&self) -> bool {
match *self {
Body::Binary(_) => true,
@ -81,15 +79,7 @@ impl PartialEq for Body {
Body::Binary(ref b2) => b == b2,
_ => false,
},
Body::StreamingContext => match *other {
Body::StreamingContext => true,
_ => false,
},
Body::UpgradeContext => match *other {
Body::UpgradeContext => true,
_ => false,
},
Body::Streaming(_) | Body::Upgrade(_) => false,
Body::Streaming(_) | Body::Actor(_) => false,
}
}
}
@ -100,9 +90,7 @@ impl fmt::Debug for Body {
Body::Empty => write!(f, "Body::Empty"),
Body::Binary(ref b) => write!(f, "Body::Binary({:?})", b),
Body::Streaming(_) => write!(f, "Body::Streaming(_)"),
Body::Upgrade(_) => write!(f, "Body::Upgrade(_)"),
Body::StreamingContext => write!(f, "Body::StreamingContext"),
Body::UpgradeContext => write!(f, "Body::UpgradeContext"),
Body::Actor(_) => write!(f, "Body::Actor(_)"),
}
}
}
@ -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 {
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[inline]
pub fn len(&self) -> usize {
match *self {
Binary::Bytes(ref bytes) => bytes.len(),

View file

@ -1,24 +1,54 @@
use std::{ptr, mem, time, io};
use std::rc::Rc;
use std::net::SocketAddr;
use std::net::{SocketAddr, Shutdown};
use actix::dev::*;
use bytes::Bytes;
use bytes::{Bytes, Buf, BufMut};
use futures::{Future, Poll, Async};
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_core::net::TcpStream;
use h1;
use h2;
use pipeline::Pipeline;
use {h1, h2};
use error::Error;
use h1writer::Writer;
use httprequest::HttpRequest;
use server::ServerSettings;
use worker::WorkerSettings;
/// Low level http request handler
#[allow(unused_variables)]
pub trait HttpHandler: 'static {
/// Handle request
fn handle(&self, req: HttpRequest) -> Result<Pipeline, HttpRequest>;
fn handle(&mut self, req: HttpRequest) -> Result<Box<HttpHandlerTask>, HttpRequest>;
}
enum HttpProtocol<T, H>
where T: AsyncRead + AsyncWrite + 'static, H: 'static
pub trait HttpHandlerTask {
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>),
H2(h2::Http2<T, H>),
@ -26,61 +56,104 @@ enum HttpProtocol<T, H>
#[doc(hidden)]
pub struct HttpChannel<T, H>
where T: AsyncRead + AsyncWrite + 'static, H: 'static
where T: IoStream, H: HttpHandler + 'static
{
proto: Option<HttpProtocol<T, H>>,
node: Option<Node<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)
-> HttpChannel<T, H> {
pub(crate) fn new(h: Rc<WorkerSettings<H>>,
io: T, peer: Option<SocketAddr>, http2: bool) -> HttpChannel<T, H>
{
h.add_channel();
if http2 {
HttpChannel {
node: None,
proto: Some(HttpProtocol::H2(
h2::Http2::new(stream, addr, router, Bytes::new()))) }
h2::Http2::new(h, io, peer, Bytes::new()))) }
} else {
HttpChannel {
node: None,
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) {
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>
where T: AsyncRead + AsyncWrite + 'static, H: HttpHandler + 'static
where T: IoStream, H: HttpHandler + 'static
{
type Item = ();
type 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 {
Some(HttpProtocol::H1(ref mut h1)) => {
match h1.poll() {
Ok(Async::Ready(h1::Http1Result::Done)) =>
return Ok(Async::Ready(())),
Ok(Async::Ready(h1::Http1Result::Done)) => {
h1.settings().remove_channel();
self.node.as_ref().unwrap().remove();
return Ok(Async::Ready(()))
}
Ok(Async::Ready(h1::Http1Result::Switch)) => (),
Ok(Async::NotReady) =>
return Ok(Async::NotReady),
Err(_) =>
return Err(()),
Err(_) => {
h1.settings().remove_channel();
self.node.as_ref().unwrap().remove();
return Err(())
}
}
}
Some(HttpProtocol::H2(ref mut h2)) =>
return h2.poll(),
Some(HttpProtocol::H2(ref mut h2)) => {
let result = h2.poll();
match result {
Ok(Async::Ready(())) | Err(_) => {
h2.settings().remove_channel();
self.node.as_ref().unwrap().remove();
}
_ => (),
}
return result
}
None => unreachable!(),
}
@ -88,11 +161,233 @@ impl<T, H> Future for HttpChannel<T, H>
let proto = self.proto.take().unwrap();
match proto {
HttpProtocol::H1(h1) => {
let (stream, addr, router, buf) = h1.into_inner();
self.proto = Some(HttpProtocol::H2(h2::Http2::new(stream, addr, router, buf)));
let (h, io, addr, buf) = h1.into_inner();
self.proto = Some(
HttpProtocol::H2(h2::Http2::new(h, io, addr, buf)));
self.poll()
}
_ => 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)
}
}

View file

@ -1,47 +1,38 @@
use std;
use std::rc::Rc;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::marker::PhantomData;
use std::collections::VecDeque;
use futures::{Async, Future, Poll};
use futures::sync::oneshot::Sender;
use futures::unsync::oneshot;
use actix::{Actor, ActorState, ActorContext, AsyncContext,
Handler, Subscriber, ResponseType};
Handler, Subscriber, ResponseType, SpawnHandle};
use actix::fut::ActorFuture;
use actix::dev::{AsyncContextApi, ActorAddressCell, ActorItemsCell, ActorWaitCell, SpawnHandle,
Envelope, ToEnvelope, RemoteEnvelope};
use actix::dev::{AsyncContextApi, ActorAddressCell,
ContextImpl, Envelope, ToEnvelope, RemoteEnvelope};
use body::{Body, Binary};
use error::Error;
use error::{Error, Result, ErrorInternalServerError};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use pipeline::DrainFut;
pub(crate) trait IoContext: 'static {
pub trait ActorHttpContext: 'static {
fn disconnected(&mut self);
fn poll(&mut self) -> Poll<Option<Frame>, Error>;
}
#[derive(Debug)]
pub(crate) enum Frame {
Message(HttpResponse),
pub enum Frame {
Payload(Option<Binary>),
Drain(Rc<RefCell<DrainFut>>),
Drain(oneshot::Sender<()>),
}
/// Http actor execution context
pub struct HttpContext<A, S=()> where A: Actor<Context=HttpContext<A, S>>,
{
act: A,
state: ActorState,
modified: bool,
items: ActorItemsCell<A>,
address: ActorAddressCell<A>,
inner: ContextImpl<A>,
stream: VecDeque<Frame>,
wait: ActorWaitCell<A>,
request: HttpRequest<S>,
streaming: bool,
disconnected: bool,
}
@ -50,23 +41,17 @@ impl<A, S> ActorContext for HttpContext<A, S> where A: Actor<Context=Self>
/// Stop actor execution
fn stop(&mut self) {
self.stream.push_back(Frame::Payload(None));
self.items.stop();
self.address.close();
if self.state == ActorState::Running {
self.state = ActorState::Stopping;
}
self.inner.stop();
}
/// Terminate actor execution
fn terminate(&mut self) {
self.address.close();
self.items.close();
self.state = ActorState::Stopped;
self.inner.terminate()
}
/// Actor execution state
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
where F: ActorFuture<Item=(), Error=(), Actor=A> + 'static
{
self.modified = true;
self.items.spawn(fut)
self.inner.spawn(fut)
}
fn wait<F>(&mut self, fut: F)
where F: ActorFuture<Item=(), Error=(), Actor=A> + 'static
{
self.modified = true;
self.wait.add(fut);
self.inner.wait(fut)
}
fn cancel_future(&mut self, handle: SpawnHandle) -> bool {
self.modified = true;
self.items.cancel_future(handle)
}
fn cancel_future_on_stop(&mut self, handle: SpawnHandle) {
self.items.cancel_future_on_stop(handle)
self.inner.cancel_future(handle)
}
}
#[doc(hidden)]
impl<A, S> AsyncContextApi<A> for HttpContext<A, S> where A: Actor<Context=Self> {
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 {
act: actor,
state: ActorState::Started,
modified: false,
items: ActorItemsCell::default(),
address: ActorAddressCell::default(),
wait: ActorWaitCell::default(),
inner: ContextImpl::new(None),
stream: VecDeque::new(),
request: req,
streaming: 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> {
@ -134,24 +114,12 @@ impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
&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
pub fn write<B: Into<Binary>>(&mut self, data: B) {
if self.streaming {
if !self.disconnected {
self.stream.push_back(Frame::Payload(Some(data.into())))
}
if !self.disconnected {
self.stream.push_back(Frame::Payload(Some(data.into())));
} 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
pub fn drain(&mut self) -> Drain<A> {
let fut = Rc::new(RefCell::new(DrainFut::default()));
self.stream.push_back(Frame::Drain(Rc::clone(&fut)));
self.modified = true;
Drain{ a: PhantomData, inner: fut }
let (tx, rx) = oneshot::channel();
self.inner.modify();
self.stream.push_back(Frame::Drain(tx));
Drain::new(rx)
}
/// Check if connection still open
@ -178,117 +146,43 @@ impl<A, S> HttpContext<A, S> where A: Actor<Context=Self> {
#[doc(hidden)]
pub fn subscriber<M>(&mut self) -> Box<Subscriber<M>>
where A: Handler<M>,
M: ResponseType + 'static,
where A: Handler<M>, M: ResponseType + 'static
{
Box::new(self.address.unsync_address())
self.inner.subscriber()
}
#[doc(hidden)]
pub fn sync_subscriber<M>(&mut self) -> Box<Subscriber<M> + Send>
where A: Handler<M>,
M: ResponseType + Send + 'static,
M::Item: Send,
M::Error: Send,
M: ResponseType + Send + 'static, 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) {
self.items.stop();
self.disconnected = true;
if self.state == ActorState::Running {
self.state = ActorState::Stopping;
}
self.stop();
}
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 {
std::mem::transmute(self as &mut HttpContext<A, S>)
};
// update state
match self.state {
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) {
match self.inner.poll(ctx) {
Ok(Async::NotReady) => {
// get frame
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)
}
// incoming messages
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)
Ok(Async::Ready(())) => Ok(Async::Ready(None)),
Err(_) => Err(ErrorInternalServerError("error").into()),
}
}
}
@ -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>
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>,
M: ResponseType + Send + 'static,
M::Item: Send,
M::Error: Send
{
RemoteEnvelope::new(msg, tx).into()
RemoteEnvelope::new(msg, tx, channel_on_drop).into()
}
}
pub struct Drain<A> {
a: PhantomData<A>,
inner: Rc<RefCell<DrainFut>>
impl<A, S> From<HttpContext<A, S>> for Body
where A: Actor<Context=HttpContext<A, S>>,
S: 'static
{
fn from(ctx: HttpContext<A, S>) -> Body {
Body::Actor(Box::new(ctx))
}
}
impl<A> ActorFuture for Drain<A>
where A: Actor
{
pub struct Drain<A> {
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 Error = ();
type Actor = A;
fn poll(&mut self, _: &mut A, _: &mut <Self::Actor as Actor>::Context) -> Poll<(), ()> {
self.inner.borrow_mut().poll()
fn poll(&mut self,
_: &mut A,
_: &mut <Self::Actor as Actor>::Context) -> Poll<Self::Item, Self::Error>
{
self.fut.poll().map_err(|_| ())
}
}

View file

@ -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);
}

View file

@ -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;

View file

@ -15,7 +15,8 @@ use bytes::{Bytes, BytesMut, BufMut, Writer};
use body::{Body, Binary};
use error::PayloadError;
use httprequest::HttpRequest;
use helpers::SharedBytes;
use httprequest::HttpMessage;
use httpresponse::HttpResponse;
use payload::{PayloadSender, PayloadWriter};
@ -35,6 +36,14 @@ pub enum ContentEncoding {
}
impl ContentEncoding {
fn is_compression(&self) -> bool {
match *self {
ContentEncoding::Identity | ContentEncoding::Auto => false,
_ => true
}
}
fn as_str(&self) -> &'static str {
match *self {
ContentEncoding::Br => "br",
@ -125,9 +134,9 @@ impl PayloadWriter for PayloadType {
}
enum Decoder {
Deflate(DeflateDecoder<Writer<BytesMut>>),
Gzip(Option<GzDecoder<Wrapper>>),
Br(BrotliDecoder<Writer<BytesMut>>),
Deflate(Box<DeflateDecoder<Writer<BytesMut>>>),
Gzip(Box<Option<GzDecoder<Wrapper>>>),
Br(Box<BrotliDecoder<Writer<BytesMut>>>),
Identity,
}
@ -158,10 +167,10 @@ impl EncodedPayload {
pub fn new(inner: PayloadSender, enc: ContentEncoding) -> EncodedPayload {
let dec = match enc {
ContentEncoding::Br => Decoder::Br(
BrotliDecoder::new(BytesMut::with_capacity(8192).writer())),
Box::new(BrotliDecoder::new(BytesMut::with_capacity(8192).writer()))),
ContentEncoding::Deflate => Decoder::Deflate(
DeflateDecoder::new(BytesMut::with_capacity(8192).writer())),
ContentEncoding::Gzip => Decoder::Gzip(None),
Box::new(DeflateDecoder::new(BytesMut::with_capacity(8192).writer()))),
ContentEncoding::Gzip => Decoder::Gzip(Box::new(None)),
_ => Decoder::Identity,
};
EncodedPayload {
@ -204,13 +213,13 @@ impl PayloadWriter for EncodedPayload {
}
loop {
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 {
self.dst.get_mut().reserve(len_buf * 2 - len);
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) => {
if n == 0 {
self.inner.feed_eof();
@ -270,14 +279,14 @@ impl PayloadWriter for EncodedPayload {
Decoder::Gzip(ref mut decoder) => {
if decoder.is_none() {
let mut buf = BytesMut::new();
buf.extend(data);
*decoder = Some(GzDecoder::new(Wrapper{buf: buf}).unwrap());
buf.extend_from_slice(&data);
*(decoder.as_mut()) = Some(GzDecoder::new(Wrapper{buf: buf}).unwrap());
} else {
decoder.as_mut().unwrap().get_mut().buf.extend(data);
decoder.as_mut().as_mut().unwrap().get_mut().buf.extend_from_slice(&data);
}
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 {
return
}
@ -287,7 +296,7 @@ impl PayloadWriter for EncodedPayload {
self.dst.get_mut().reserve(len_buf * 2 - len);
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) => {
if n == 0 {
return
@ -328,16 +337,16 @@ impl PayloadWriter for EncodedPayload {
pub(crate) struct PayloadEncoder(ContentEncoder);
impl Default for PayloadEncoder {
fn default() -> PayloadEncoder {
PayloadEncoder(ContentEncoder::Identity(TransferEncoding::eof()))
}
}
impl PayloadEncoder {
pub fn new(req: &HttpRequest, resp: &mut HttpResponse) -> PayloadEncoder {
let version = resp.version().unwrap_or_else(|| req.version());
pub fn empty(bytes: SharedBytes) -> PayloadEncoder {
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 has_body = match body {
Body::Empty => false,
@ -346,11 +355,11 @@ impl PayloadEncoder {
};
// 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() {
ContentEncoding::Auto => {
// 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() {
AcceptEncoding::parse(enc)
} else {
@ -362,7 +371,10 @@ impl PayloadEncoder {
}
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
} else {
ContentEncoding::Identity
@ -377,13 +389,13 @@ impl PayloadEncoder {
if resp.chunked() {
error!("Chunked transfer is enabled but body is set to Empty");
}
resp.headers.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
resp.headers.remove(TRANSFER_ENCODING);
TransferEncoding::length(0)
resp.headers_mut().remove(CONTENT_LENGTH);
TransferEncoding::eof(buf)
},
Body::Binary(ref mut bytes) => {
if compression {
let transfer = TransferEncoding::eof();
let buf = SharedBytes::default();
let transfer = TransferEncoding::eof(buf.clone());
let mut enc = match encoding {
ContentEncoding::Deflate => ContentEncoder::Deflate(
DeflateEncoder::new(transfer, Compression::Default)),
@ -397,64 +409,55 @@ impl PayloadEncoder {
// TODO return error!
let _ = enc.write(bytes.as_ref());
let _ = enc.write_eof();
let b = enc.get_mut().take();
resp.headers.insert(
CONTENT_LENGTH,
HeaderValue::from_str(format!("{}", b.len()).as_str()).unwrap());
*bytes = Binary::from(b);
*bytes = Binary::from(buf.get_mut().take());
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 => {
if resp.chunked() {
resp.headers.remove(CONTENT_LENGTH);
Body::Streaming(_) | Body::Actor(_) => {
if resp.upgrade() {
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 {
error!("Chunked transfer encoding is forbidden for {:?}", version);
}
if version == Version::HTTP_2 {
resp.headers.remove(TRANSFER_ENCODING);
TransferEncoding::eof()
resp.headers_mut().remove(TRANSFER_ENCODING);
TransferEncoding::eof(buf)
} else {
resp.headers.insert(
resp.headers_mut().insert(
TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
TransferEncoding::chunked()
TransferEncoding::chunked(buf)
}
} else if let Some(len) = resp.headers().get(CONTENT_LENGTH) {
// Content-Length
if let Ok(s) = len.to_str() {
if let Ok(len) = s.parse::<u64>() {
TransferEncoding::length(len)
TransferEncoding::length(len, buf)
} else {
debug!("illegal Content-Length: {:?}", len);
TransferEncoding::eof()
TransferEncoding::eof(buf)
}
} else {
TransferEncoding::eof()
TransferEncoding::eof(buf)
}
} 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);
@ -476,22 +479,29 @@ impl PayloadEncoder {
impl PayloadEncoder {
#[inline]
pub fn len(&self) -> usize {
self.0.get_ref().len()
}
#[inline]
pub fn get_mut(&mut self) -> &mut BytesMut {
self.0.get_mut()
}
#[inline]
pub fn is_eof(&self) -> bool {
self.0.is_eof()
}
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline(always)]
pub fn write(&mut self, payload: &[u8]) -> Result<(), io::Error> {
self.0.write(payload)
}
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
#[inline(always)]
pub fn write_eof(&mut self) -> Result<(), io::Error> {
self.0.write_eof()
}
@ -506,6 +516,7 @@ enum ContentEncoder {
impl ContentEncoder {
#[inline]
pub fn is_eof(&self) -> bool {
match *self {
ContentEncoder::Br(ref encoder) =>
@ -519,34 +530,39 @@ impl ContentEncoder {
}
}
#[inline]
pub fn get_ref(&self) -> &BytesMut {
match *self {
ContentEncoder::Br(ref encoder) =>
&encoder.get_ref().buffer,
encoder.get_ref().buffer.get_ref(),
ContentEncoder::Deflate(ref encoder) =>
&encoder.get_ref().buffer,
encoder.get_ref().buffer.get_ref(),
ContentEncoder::Gzip(ref encoder) =>
&encoder.get_ref().buffer,
encoder.get_ref().buffer.get_ref(),
ContentEncoder::Identity(ref encoder) =>
&encoder.buffer,
encoder.buffer.get_ref(),
}
}
#[inline]
pub fn get_mut(&mut self) -> &mut BytesMut {
match *self {
ContentEncoder::Br(ref mut encoder) =>
&mut encoder.get_mut().buffer,
encoder.get_mut().buffer.get_mut(),
ContentEncoder::Deflate(ref mut encoder) =>
&mut encoder.get_mut().buffer,
encoder.get_mut().buffer.get_mut(),
ContentEncoder::Gzip(ref mut encoder) =>
&mut encoder.get_mut().buffer,
encoder.get_mut().buffer.get_mut(),
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> {
let encoder = mem::replace(self, ContentEncoder::Identity(TransferEncoding::eof()));
let encoder = mem::replace(
self, ContentEncoder::Identity(TransferEncoding::eof(SharedBytes::empty())));
match 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> {
match *self {
ContentEncoder::Br(ref mut encoder) => {
match encoder.write(data) {
Ok(_) => {
encoder.flush()
},
Ok(_) =>
encoder.flush(),
Err(err) => {
trace!("Error decoding br encoding: {}", err);
Err(err)
@ -602,20 +619,18 @@ impl ContentEncoder {
},
ContentEncoder::Gzip(ref mut encoder) => {
match encoder.write(data) {
Ok(_) => {
encoder.flush()
},
Ok(_) =>
encoder.flush(),
Err(err) => {
trace!("Error decoding br encoding: {}", err);
trace!("Error decoding gzip encoding: {}", err);
Err(err)
},
}
}
ContentEncoder::Deflate(ref mut encoder) => {
match encoder.write(data) {
Ok(_) => {
encoder.flush()
},
Ok(_) =>
encoder.flush(),
Err(err) => {
trace!("Error decoding deflate encoding: {}", err);
Err(err)
@ -623,7 +638,7 @@ impl ContentEncoder {
}
}
ContentEncoder::Identity(ref mut encoder) => {
encoder.write_all(data)?;
encoder.encode(data)?;
Ok(())
}
}
@ -634,7 +649,7 @@ impl ContentEncoder {
#[derive(Debug, Clone)]
pub(crate) struct TransferEncoding {
kind: TransferEncodingKind,
buffer: BytesMut,
buffer: SharedBytes,
}
#[derive(Debug, PartialEq, Clone)]
@ -653,27 +668,31 @@ enum TransferEncodingKind {
impl TransferEncoding {
pub fn eof() -> TransferEncoding {
#[inline]
pub fn eof(bytes: SharedBytes) -> TransferEncoding {
TransferEncoding {
kind: TransferEncodingKind::Eof,
buffer: BytesMut::new(),
buffer: bytes,
}
}
pub fn chunked() -> TransferEncoding {
#[inline]
pub fn chunked(bytes: SharedBytes) -> TransferEncoding {
TransferEncoding {
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 {
kind: TransferEncodingKind::Length(len),
buffer: BytesMut::new(),
buffer: bytes,
}
}
#[inline]
pub fn is_eof(&self) -> bool {
match self.kind {
TransferEncodingKind::Eof => true,
@ -685,50 +704,51 @@ impl TransferEncoding {
}
/// 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 {
TransferEncodingKind::Eof => {
self.buffer.extend(msg);
msg.is_empty()
self.buffer.get_mut().extend_from_slice(msg);
Ok(msg.is_empty())
},
TransferEncodingKind::Chunked(ref mut eof) => {
if *eof {
return true;
return Ok(true);
}
if msg.is_empty() {
*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 {
write!(self.buffer, "{:X}\r\n", msg.len()).unwrap();
self.buffer.extend(msg);
self.buffer.extend(b"\r\n");
write!(self.buffer.get_mut(), "{:X}\r\n", msg.len())
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
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) => {
if msg.is_empty() {
return *remaining == 0
return Ok(*remaining == 0)
}
let max = cmp::min(*remaining, msg.len() as u64);
trace!("sized write = {}", max);
self.buffer.extend(msg[..max as usize].as_ref());
self.buffer.get_mut().extend_from_slice(msg[..max as usize].as_ref());
*remaining -= max as u64;
trace!("encoded {} bytes, remaining = {}", max, remaining);
*remaining == 0
Ok(*remaining == 0)
},
}
}
/// Encode eof. Return `EOF` state of encoder
#[inline]
pub fn encode_eof(&mut self) {
match self.kind {
TransferEncodingKind::Eof | TransferEncodingKind::Length(_) => (),
TransferEncodingKind::Chunked(ref mut eof) => {
if !*eof {
*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 {
#[inline]
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.encode(buf);
self.encode(buf)?;
Ok(buf.len())
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
@ -813,3 +835,19 @@ impl AcceptEncoding {
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"));
}
}

View file

@ -1,5 +1,5 @@
//! Error and Result module
use std::{fmt, result};
use std::{io, fmt, result};
use std::str::Utf8Error;
use std::string::FromUtf8Error;
use std::io::Error as IoError;
@ -10,11 +10,13 @@ use std::error::Error as StdError;
use cookie;
use httparse;
use failure::Fail;
use futures::Canceled;
use http2::Error as Http2Error;
use http::{header, StatusCode, Error as HttpError};
use http::uri::InvalidUriBytes;
use http_range::HttpRangeParseError;
use serde_json::error::Error as JsonError;
use url::ParseError as UrlParseError;
// re-exports
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
/// 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
#[derive(Debug)]
#[derive(Fail, Debug)]
pub struct Error {
cause: Box<ErrorResponse>,
cause: Box<ResponseError>,
}
impl 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
pub fn cause(&self) -> &ErrorResponse {
pub fn cause(&self) -> &ResponseError {
self.cause.as_ref()
}
}
/// Error that can be converted to `HttpResponse`
pub trait ErrorResponse: Fail {
pub trait ResponseError: Fail {
/// Create response for error
///
@ -69,8 +71,8 @@ impl From<Error> for HttpResponse {
}
}
/// `Error` for any error that implements `ErrorResponse`
impl<T: ErrorResponse> From<T> for Error {
/// `Error` for any error that implements `ResponseError`
impl<T: ResponseError> From<T> for Error {
fn from(err: T) -> Error {
Error { cause: Box::new(err) }
}
@ -78,31 +80,39 @@ impl<T: ErrorResponse> From<T> for Error {
/// Default error is `InternalServerError`
#[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 {
HttpResponse::new(StatusCode::INTERNAL_SERVER_ERROR, Body::Empty)
}
}
/// `InternalServerError` for `JsonError`
impl ErrorResponse for JsonError {}
impl ResponseError for JsonError {}
/// Return `InternalServerError` for `HttpError`,
/// Response generation can return `HttpError`, so it is internal error
impl ErrorResponse for HttpError {}
impl ResponseError for HttpError {}
/// 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`
impl ErrorResponse for header::InvalidHeaderValue {}
impl ResponseError for header::InvalidHeaderValue {}
/// Internal error
#[derive(Fail, Debug)]
#[fail(display="Unexpected task frame")]
pub struct UnexpectedTaskFrame;
impl ErrorResponse for UnexpectedTaskFrame {}
/// `InternalServerError` for `futures::Canceled`
impl ResponseError for Canceled {}
/// A set of errors that can occur during parsing HTTP streams
#[derive(Fail, Debug)]
@ -141,7 +151,7 @@ pub enum ParseError {
}
/// Return `BadRequest` for `ParseError`
impl ErrorResponse for ParseError {
impl ResponseError for ParseError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
}
@ -168,10 +178,8 @@ impl From<FromUtf8Error> for ParseError {
impl From<httparse::Error> for ParseError {
fn from(err: httparse::Error) -> ParseError {
match err {
httparse::Error::HeaderName |
httparse::Error::HeaderValue |
httparse::Error::NewLine |
httparse::Error::Token => ParseError::Header,
httparse::Error::HeaderName | httparse::Error::HeaderValue |
httparse::Error::NewLine | httparse::Error::Token => ParseError::Header,
httparse::Error::Status => ParseError::Status,
httparse::Error::TooManyHeaders => ParseError::TooLarge,
httparse::Error::Version => ParseError::Version,
@ -188,6 +196,12 @@ pub enum PayloadError {
/// Content encoding stream corruption
#[fail(display="Can not decode content-encoding.")]
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
#[fail(display="{}", _0)]
ParseError(#[cause] IoError),
@ -202,8 +216,11 @@ impl From<IoError> for PayloadError {
}
}
/// `InternalServerError` for `PayloadError`
impl ResponseError for PayloadError {}
/// Return `BadRequest` for `cookie::ParseError`
impl ErrorResponse for cookie::ParseError {
impl ResponseError for cookie::ParseError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
}
@ -217,13 +234,13 @@ pub enum HttpRangeError {
InvalidRange,
/// Returned if first-byte-pos of all of the byte-range-spec
/// 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")]
NoOverlap,
}
/// Return `BadRequest` for `HttpRangeError`
impl ErrorResponse for HttpRangeError {
impl ResponseError for HttpRangeError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(
StatusCode::BAD_REQUEST, Body::from("Invalid Range header provided"))
@ -272,7 +289,7 @@ impl From<PayloadError> for MultipartError {
}
/// Return `BadRequest` for `MultipartError`
impl ErrorResponse for MultipartError {
impl ResponseError for MultipartError {
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
@ -290,7 +307,7 @@ pub enum ExpectError {
UnknownExpect,
}
impl ErrorResponse for ExpectError {
impl ResponseError for ExpectError {
fn error_response(&self) -> HttpResponse {
HTTPExpectationFailed.with_body("Unknown Expect")
@ -320,7 +337,7 @@ pub enum WsHandshakeError {
BadWebsocketKey,
}
impl ErrorResponse for WsHandshakeError {
impl ResponseError for WsHandshakeError {
fn error_response(&self) -> HttpResponse {
match *self {
@ -340,13 +357,13 @@ impl ErrorResponse for WsHandshakeError {
WsHandshakeError::UnsupportedVersion =>
HTTPBadRequest.with_reason("Unsupported version"),
WsHandshakeError::BadWebsocketKey =>
HTTPBadRequest.with_reason("Handshake error")
HTTPBadRequest.with_reason("Handshake error"),
}
}
}
/// A set of errors that can occur during parsing urlencoded payloads
#[derive(Fail, Debug, PartialEq)]
#[derive(Fail, Debug)]
pub enum UrlencodedError {
/// Can not decode chunked transfer encoding
#[fail(display="Can not decode chunked transfer encoding")]
@ -360,16 +377,206 @@ pub enum UrlencodedError {
/// Content type error
#[fail(display="Content type error")]
ContentType,
/// Payload error
#[fail(display="Error that occur during reading payload")]
Payload(PayloadError),
}
/// Return `BadRequest` for `UrlencodedError`
impl ErrorResponse for UrlencodedError {
impl ResponseError for UrlencodedError {
fn error_response(&self) -> HttpResponse {
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)]
mod tests {
use std::error::Error as StdError;

318
src/fs.rs Normal file
View 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"));
}
}

698
src/h1.rs

File diff suppressed because it is too large Load diff

View file

@ -1,14 +1,15 @@
use std::io;
use std::fmt::Write;
use bytes::BufMut;
use futures::{Async, Poll};
use tokio_io::AsyncWrite;
use http::{Version, StatusCode};
use http::header::{HeaderValue, CONNECTION, CONTENT_TYPE, DATE};
use http::Version;
use http::header::{HeaderValue, CONNECTION, DATE};
use date;
use helpers;
use body::Body;
use helpers::SharedBytes;
use encoding::PayloadEncoder;
use httprequest::HttpRequest;
use httprequest::HttpMessage;
use httpresponse::HttpResponse;
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)]
pub(crate) enum WriterState {
pub enum WriterState {
Done,
Pause,
}
/// Send stream
pub(crate) trait Writer {
pub trait Writer {
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>;
fn write(&mut self, payload: &[u8]) -> 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> {
stream: Option<T>,
started: bool,
flags: Flags,
stream: T,
encoder: PayloadEncoder,
upgrade: bool,
keepalive: bool,
disconnected: bool,
written: u64,
headers_size: u64,
headers_size: u32,
buffer: SharedBytes,
}
impl<T: AsyncWrite> H1Writer<T> {
pub fn new(stream: T) -> H1Writer<T> {
pub fn new(stream: T, buf: SharedBytes) -> H1Writer<T> {
H1Writer {
stream: Some(stream),
started: false,
encoder: PayloadEncoder::default(),
upgrade: false,
keepalive: false,
disconnected: false,
flags: Flags::empty(),
stream: stream,
encoder: PayloadEncoder::empty(buf.clone()),
written: 0,
headers_size: 0,
buffer: buf,
}
}
pub fn get_mut(&mut self) -> &mut T {
self.stream.as_mut().unwrap()
&mut self.stream
}
pub fn unwrap(&mut self) -> T {
self.stream.take().unwrap()
pub fn reset(&mut self) {
self.written = 0;
self.flags = Flags::empty();
}
pub fn into_inner(self) -> T {
self.stream
}
pub fn disconnected(&mut self) {
@ -75,28 +85,25 @@ impl<T: AsyncWrite> H1Writer<T> {
}
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> {
let buffer = self.encoder.get_mut();
if let Some(ref mut stream) = self.stream {
while !buffer.is_empty() {
match stream.write(buffer.as_ref()) {
Ok(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 {
return Ok(WriterState::Pause)
} else {
return Ok(WriterState::Done)
}
while !buffer.is_empty() {
match self.stream.write(buffer.as_ref()) {
Ok(n) => {
let _ = buffer.split_to(n);
},
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
if buffer.len() > MAX_WRITE_BUFFER_SIZE {
return Ok(WriterState::Pause)
} else {
return Ok(WriterState::Done)
}
Err(err) => return Err(err),
}
Err(err) => return Err(err),
}
}
Ok(WriterState::Done)
@ -106,97 +113,93 @@ impl<T: AsyncWrite> H1Writer<T> {
impl<T: AsyncWrite> Writer for H1Writer<T> {
fn written(&self) -> u64 {
if self.written > self.headers_size {
self.written - self.headers_size
} else {
0
}
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>
{
trace!("Prepare response with status: {:?}", msg.status);
// prepare task
self.started = true;
self.encoder = PayloadEncoder::new(req, msg);
self.keepalive = msg.keep_alive().unwrap_or_else(|| req.keep_alive());
self.flags.insert(Flags::STARTED);
self.encoder = PayloadEncoder::new(self.buffer.clone(), req, msg);
if msg.keep_alive().unwrap_or_else(|| req.keep_alive()) {
self.flags.insert(Flags::KEEPALIVE);
}
// Connection upgrade
let version = msg.version().unwrap_or_else(|| req.version());
let version = msg.version().unwrap_or_else(|| req.version);
if msg.upgrade() {
msg.headers.insert(CONNECTION, HeaderValue::from_static("upgrade"));
msg.headers_mut().insert(CONNECTION, HeaderValue::from_static("upgrade"));
}
// keep-alive
else if self.keepalive {
else if self.flags.contains(Flags::KEEPALIVE) {
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 {
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
{
let buffer = self.encoder.get_mut();
if let Body::Binary(ref bytes) = *msg.body() {
buffer.reserve(100 + msg.headers.len() * AVERAGE_HEADER_SIZE + bytes.len());
let mut buffer = self.encoder.get_mut();
if let Body::Binary(ref bytes) = body {
buffer.reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len());
} 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 {
buffer.extend(b"HTTP/1.1 200 OK\r\n");
// status line
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 {
let _ = write!(buffer, "{:?} {}\r\n", version, msg.status);
// msg eof
buffer.extend_from_slice(b"\r\n");
}
for (key, value) in &msg.headers {
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;
self.headers_size = buffer.len() as u32;
}
trace!("Response: {:?}", msg);
if msg.body().is_binary() {
let body = msg.replace_body(Body::Empty);
if let Body::Binary(bytes) = body {
self.encoder.write(bytes.as_ref())?;
return Ok(WriterState::Done)
}
if let Body::Binary(bytes) = body {
self.written = bytes.len() as u64;
self.encoder.write(bytes.as_ref())?;
} else {
msg.replace_body(body);
}
Ok(WriterState::Done)
}
fn write(&mut self, payload: &[u8]) -> Result<WriterState, io::Error> {
if !self.disconnected {
if self.started {
self.written += payload.len() as u64;
if !self.flags.contains(Flags::DISCONNECTED) {
if self.flags.contains(Flags::STARTED) {
// TODO: add warning, write after EOF
self.encoder.write(payload)?;
return Ok(WriterState::Done)
} else {
// might be response to EXCEPT
self.encoder.get_mut().extend_from_slice(payload)
@ -214,7 +217,6 @@ impl<T: AsyncWrite> Writer for H1Writer<T> {
self.encoder.write_eof()?;
if !self.encoder.is_eof() {
//debug!("last payload item, but it is not EOF ");
Err(io::Error::new(io::ErrorKind::Other,
"Last payload item, but eof is not reached"))
} 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() {
Ok(WriterState::Done) => Ok(Async::Ready(())),
Ok(WriterState::Done) => {
if shutdown {
self.stream.shutdown()
} else {
Ok(Async::Ready(()))
}
},
Ok(WriterState::Pause) => Ok(Async::NotReady),
Err(err) => Err(err)
}

142
src/h2.rs
View file

@ -8,7 +8,7 @@ use std::collections::VecDeque;
use actix::Arbiter;
use http::request::Parts;
use http2::{Reason, RecvStream};
use http2::server::{Server, Handshake, Respond};
use http2::server::{self, Connection, Handshake, SendResponse};
use bytes::{Buf, Bytes};
use futures::{Async, Poll, Future, Stream};
use tokio_io::{AsyncRead, AsyncWrite};
@ -16,29 +16,35 @@ use tokio_core::reactor::Timeout;
use pipeline::Pipeline;
use h2writer::H2Writer;
use channel::HttpHandler;
use worker::WorkerSettings;
use channel::{HttpHandler, HttpHandlerTask};
use error::PayloadError;
use encoding::PayloadType;
use httpcodes::HTTPNotFound;
use httprequest::HttpRequest;
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>
where T: AsyncRead + AsyncWrite + 'static, H: 'static
{
router: Rc<Vec<H>>,
flags: Flags,
settings: Rc<WorkerSettings<H>>,
addr: Option<SocketAddr>,
state: State<IoWrapper<T>>,
disconnected: bool,
tasks: VecDeque<Entry>,
keepalive_timer: Option<Timeout>,
}
enum State<T: AsyncRead + AsyncWrite> {
Handshake(Handshake<T, Bytes>),
Server(Server<T, Bytes>),
Server(Connection<T, Bytes>),
Empty,
}
@ -46,17 +52,28 @@ impl<T, H> Http2<T, H>
where T: AsyncRead + AsyncWrite + 'static,
H: HttpHandler + 'static
{
pub fn new(stream: T, addr: Option<SocketAddr>, router: Rc<Vec<H>>, buf: Bytes) -> Self {
Http2{ router: router,
pub fn new(h: Rc<WorkerSettings<H>>, io: T, addr: Option<SocketAddr>, buf: Bytes) -> Self
{
Http2{ flags: Flags::empty(),
settings: h,
addr: addr,
disconnected: false,
tasks: VecDeque::new(),
state: State::Handshake(
Server::handshake(IoWrapper{unread: Some(buf), inner: stream})),
server::handshake(IoWrapper{unread: Some(buf), inner: io})),
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<(), ()> {
// server
if let State::Server(ref mut server) = self.state {
@ -80,33 +97,33 @@ impl<T, H> Http2<T, H>
// read payload
item.poll_payload();
if !item.eof {
if !item.flags.contains(EntryFlags::EOF) {
match item.task.poll_io(&mut item.stream) {
Ok(Async::Ready(ready)) => {
item.eof = true;
item.flags.insert(EntryFlags::EOF);
if ready {
item.finished = true;
item.flags.insert(EntryFlags::FINISHED);
}
not_ready = false;
},
Ok(Async::NotReady) => (),
Err(err) => {
error!("Unhandled error: {}", err);
item.eof = true;
item.error = true;
item.flags.insert(EntryFlags::EOF);
item.flags.insert(EntryFlags::ERROR);
item.stream.reset(Reason::INTERNAL_ERROR);
}
}
} else if !item.finished {
} else if !item.flags.contains(EntryFlags::FINISHED) {
match item.task.poll() {
Ok(Async::NotReady) => (),
Ok(Async::Ready(_)) => {
not_ready = false;
item.finished = true;
item.flags.insert(EntryFlags::FINISHED);
},
Err(err) => {
item.error = true;
item.finished = true;
item.flags.insert(EntryFlags::ERROR);
item.flags.insert(EntryFlags::FINISHED);
error!("Unhandled error: {}", err);
}
}
@ -115,7 +132,10 @@ impl<T, H> Http2<T, H>
// cleanup finished tasks
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();
} else {
break
@ -123,11 +143,11 @@ impl<T, H> Http2<T, H>
}
// get request
if !self.disconnected {
if !self.flags.contains(Flags::DISCONNECTED) {
match server.poll() {
Ok(Async::Ready(None)) => {
not_ready = false;
self.disconnected = true;
self.flags.insert(Flags::DISCONNECTED);
for entry in &mut self.tasks {
entry.task.disconnected()
}
@ -140,23 +160,34 @@ impl<T, H> Http2<T, H>
self.keepalive_timer.take();
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) => {
// start keep-alive timer
if self.tasks.is_empty() && self.keepalive_timer.is_none() {
trace!("Start keep-alive timer");
let mut timeout = Timeout::new(
Duration::new(KEEPALIVE_PERIOD, 0),
Arbiter::handle()).unwrap();
// register timeout
let _ = timeout.poll();
self.keepalive_timer = Some(timeout);
if self.tasks.is_empty() {
if self.settings.keep_alive_enabled() {
let keep_alive = self.settings.keep_alive();
if keep_alive > 0 && self.keepalive_timer.is_none() {
trace!("Start keep-alive timer");
let mut timeout = Timeout::new(
Duration::new(keep_alive, 0),
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) => {
trace!("Connection error: {}", err);
self.disconnected = true;
self.flags.insert(Flags::DISCONNECTED);
for entry in &mut self.tasks {
entry.task.disconnected()
}
@ -166,7 +197,7 @@ impl<T, H> Http2<T, H>
}
if not_ready {
if self.tasks.is_empty() && self.disconnected {
if self.tasks.is_empty() && self.flags.contains(Flags::DISCONNECTED) {
return Ok(Async::Ready(()))
} else {
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 {
task: Pipeline,
task: Box<HttpHandlerTask>,
payload: PayloadType,
recv: RecvStream,
stream: H2Writer,
eof: bool,
error: bool,
finished: bool,
reof: bool,
capacity: usize,
flags: EntryFlags,
}
impl Entry {
fn new<H>(parts: Parts,
recv: RecvStream,
resp: Respond<Bytes>,
resp: SendResponse<Bytes>,
addr: Option<SocketAddr>,
router: &Rc<Vec<H>>) -> Entry
settings: &Rc<WorkerSettings<H>>) -> Entry
where H: HttpHandler + 'static
{
// Payload and Content-Encoding
let (psender, payload) = Payload::new(false);
let mut req = HttpRequest::new(
parts.method, parts.uri, parts.version, parts.headers, payload);
let msg = settings.get_http_message();
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
req.set_remove_addr(addr);
let mut req = HttpRequest::from_message(msg);
// Payload sender
let psender = PayloadType::new(req.headers(), psender);
// start request processing
let mut task = None;
for h in router.iter() {
for h in settings.handlers().iter_mut() {
req = match h.handle(req) {
Ok(t) => {
task = Some(t);
@ -243,23 +284,20 @@ impl Entry {
Entry {task: task.unwrap_or_else(|| Pipeline::error(HTTPNotFound)),
payload: psender,
recv: recv,
stream: H2Writer::new(resp),
eof: false,
error: false,
finished: false,
reof: false,
stream: H2Writer::new(resp, settings.get_shared_bytes()),
flags: EntryFlags::empty(),
capacity: 0,
}
}
fn poll_payload(&mut self) {
if !self.reof {
if !self.flags.contains(EntryFlags::REOF) {
match self.recv.poll() {
Ok(Async::Ready(Some(chunk))) => {
self.payload.feed_data(chunk);
},
Ok(Async::Ready(None)) => {
self.reof = true;
self.flags.insert(EntryFlags::REOF);
},
Ok(Async::NotReady) => (),
Err(err) => {

View file

@ -1,43 +1,49 @@
use std::{io, cmp};
use bytes::Bytes;
use bytes::{Bytes, BytesMut};
use futures::{Async, Poll};
use http2::{Reason, SendStream};
use http2::server::Respond;
use http2::server::SendResponse;
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 helpers::SharedBytes;
use encoding::PayloadEncoder;
use httprequest::HttpRequest;
use httprequest::HttpMessage;
use httpresponse::HttpResponse;
use h1writer::{Writer, WriterState};
const CHUNK_SIZE: usize = 16_384;
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 {
respond: Respond<Bytes>,
respond: SendResponse<Bytes>,
stream: Option<SendStream<Bytes>>,
started: bool,
encoder: PayloadEncoder,
disconnected: bool,
eof: bool,
flags: Flags,
written: u64,
buffer: SharedBytes,
}
impl H2Writer {
pub fn new(respond: Respond<Bytes>) -> H2Writer {
pub fn new(respond: SendResponse<Bytes>, buf: SharedBytes) -> H2Writer {
H2Writer {
respond: respond,
stream: None,
started: false,
encoder: PayloadEncoder::default(),
disconnected: false,
eof: true,
encoder: PayloadEncoder::empty(buf.clone()),
flags: Flags::empty(),
written: 0,
buffer: buf,
}
}
@ -48,7 +54,7 @@ impl H2Writer {
}
fn write_to_stream(&mut self) -> Result<WriterState, io::Error> {
if !self.started {
if !self.flags.contains(Flags::STARTED) {
return Ok(WriterState::Done)
}
@ -56,7 +62,7 @@ impl H2Writer {
let buffer = self.encoder.get_mut();
if buffer.is_empty() {
if self.eof {
if self.flags.contains(Flags::EOF) {
let _ = stream.send_data(Bytes::new(), true);
}
return Ok(WriterState::Done)
@ -77,7 +83,7 @@ impl H2Writer {
Ok(Async::Ready(Some(cap))) => {
let len = buffer.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;
if let Err(err) = stream.send_data(bytes.freeze(), eof) {
@ -86,7 +92,7 @@ impl H2Writer {
let cap = cmp::min(buffer.len(), CHUNK_SIZE);
stream.reserve_capacity(cap);
} else {
return Ok(WriterState::Done)
return Ok(WriterState::Pause)
}
}
Err(_) => {
@ -105,42 +111,52 @@ impl Writer for H2Writer {
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>
{
trace!("Prepare response with status: {:?}", msg.status);
// trace!("Prepare response with status: {:?}", msg.status());
// prepare response
self.started = true;
self.encoder = PayloadEncoder::new(req, msg);
self.eof = if let Body::Empty = *msg.body() { true } else { false };
// 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());
self.flags.insert(Flags::STARTED);
self.encoder = PayloadEncoder::new(self.buffer.clone(), req, msg);
if let Body::Empty = *msg.body() {
self.flags.insert(Flags::EOF);
}
// default content-type
if !msg.headers.contains_key(CONTENT_TYPE) {
msg.headers.insert(
CONTENT_TYPE, HeaderValue::from_static("application/octet-stream"));
// http2 specific
msg.headers_mut().remove(CONNECTION);
msg.headers_mut().remove(TRANSFER_ENCODING);
// 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(());
*resp.status_mut() = msg.status;
*resp.status_mut() = msg.status();
*resp.version_mut() = Version::HTTP_2;
for (key, value) in msg.headers().iter() {
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) =>
self.stream = Some(stream),
Err(_) =>
@ -149,23 +165,25 @@ impl Writer for H2Writer {
trace!("Response: {:?}", msg);
if msg.body().is_binary() {
if let Body::Binary(bytes) = msg.replace_body(Body::Empty) {
self.eof = true;
self.encoder.write(bytes.as_ref())?;
if let Some(ref mut stream) = self.stream {
stream.reserve_capacity(cmp::min(self.encoder.len(), CHUNK_SIZE));
}
return Ok(WriterState::Done)
if let Body::Binary(bytes) = body {
self.flags.insert(Flags::EOF);
self.written = bytes.len() as u64;
self.encoder.write(bytes.as_ref())?;
if let Some(ref mut stream) = self.stream {
stream.reserve_capacity(cmp::min(self.encoder.len(), CHUNK_SIZE));
}
Ok(WriterState::Pause)
} else {
msg.replace_body(body);
Ok(WriterState::Done)
}
Ok(WriterState::Done)
}
fn write(&mut self, payload: &[u8]) -> Result<WriterState, io::Error> {
if !self.disconnected {
if self.started {
self.written = payload.len() as u64;
if !self.flags.contains(Flags::DISCONNECTED) {
if self.flags.contains(Flags::STARTED) {
// TODO: add warning, write after EOF
self.encoder.write(payload)?;
} else {
@ -184,7 +202,7 @@ impl Writer for H2Writer {
fn write_eof(&mut self) -> Result<WriterState, io::Error> {
self.encoder.write_eof()?;
self.eof = true;
self.flags.insert(Flags::EOF);
if !self.encoder.is_eof() {
Err(io::Error::new(io::ErrorKind::Other,
"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() {
Ok(WriterState::Done) => Ok(Async::Ready(())),
Ok(WriterState::Pause) => Ok(Async::NotReady),

562
src/handler.rs Normal file
View 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
View 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"[..]);
}
}

View file

@ -1,10 +1,9 @@
//! Basic http responses
#![allow(non_upper_case_globals)]
use http::StatusCode;
use http::{StatusCode, Error as HttpError};
use body::Body;
use route::Reply;
use route::RouteHandler;
use handler::{Reply, Handler, RouteHandler, Responder};
use httprequest::HttpRequest;
use httpresponse::{HttpResponse, HttpResponseBuilder};
@ -55,9 +54,6 @@ impl StaticResponse {
pub fn build(&self) -> HttpResponseBuilder {
HttpResponse::build(self.0)
}
pub fn response(&self) -> HttpResponse {
HttpResponse::new(self.0, Body::Empty)
}
pub fn with_reason(self, reason: &'static str) -> HttpResponse {
let mut resp = HttpResponse::new(self.0, Body::Empty);
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 {
fn handle(&self, _: HttpRequest<S>) -> Reply {
fn handle(&mut self, _: HttpRequest<S>) -> Reply {
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 {
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]
fn test_response() {
let resp = HTTPOk.response();
let resp: HttpResponse = HTTPOk.into();
assert_eq!(resp.status(), StatusCode::OK);
}
@ -149,8 +168,8 @@ mod tests {
#[test]
fn test_with_reason() {
let resp = HTTPOk.response();
assert_eq!(resp.reason(), "");
let resp: HttpResponse = HTTPOk.into();
assert_eq!(resp.reason(), "OK");
let resp = HTTPBadRequest.with_reason("test");
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,8 @@
//! 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::collections::VecDeque;
use cookie::CookieJar;
use bytes::{Bytes, BytesMut};
@ -8,11 +10,13 @@ use http::{StatusCode, Version, HeaderMap, HttpTryFrom, Error as HttpError};
use http::header::{self, HeaderName, HeaderValue};
use serde_json;
use serde::Serialize;
use cookie::Cookie;
use Cookie;
use body::Body;
use error::Error;
use handler::Responder;
use encoding::ContentEncoding;
use httprequest::HttpRequest;
/// Represents various types of connection
#[derive(Copy, Clone, PartialEq, Debug)]
@ -26,122 +30,122 @@ pub enum ConnectionType {
}
/// An HTTP Response
pub struct HttpResponse {
pub version: Option<Version>,
pub headers: HeaderMap,
pub status: StatusCode,
reason: Option<&'static str>,
body: Body,
chunked: bool,
encoding: ContentEncoding,
connection_type: Option<ConnectionType>,
response_size: u64,
error: Option<Error>,
pub struct HttpResponse(Option<Box<InnerHttpResponse>>);
impl Drop for HttpResponse {
fn drop(&mut self) {
if let Some(inner) = self.0.take() {
Pool::release(inner)
}
}
}
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.
#[inline]
pub fn build(status: StatusCode) -> HttpResponseBuilder {
HttpResponseBuilder {
parts: Some(Parts::new(status)),
response: Some(Pool::get(status)),
err: None,
cookies: None,
}
}
/// Constructs a response
#[inline]
pub fn new(status: StatusCode, body: Body) -> HttpResponse {
HttpResponse {
version: None,
headers: Default::default(),
status: status,
reason: None,
body: body,
chunked: false,
encoding: ContentEncoding::Auto,
connection_type: None,
response_size: 0,
error: None,
}
HttpResponse(Some(Pool::with_body(status, body)))
}
/// Constructs a error response
#[inline]
pub fn from_error(error: Error) -> HttpResponse {
let mut resp = error.cause().error_response();
resp.error = Some(error);
resp.get_mut().error = Some(error);
resp
}
/// The source `error` for this response
#[inline]
pub fn error(&self) -> Option<&Error> {
self.error.as_ref()
self.get_ref().error.as_ref()
}
/// Get the HTTP version of this response.
#[inline]
pub fn version(&self) -> Option<Version> {
self.version
self.get_ref().version
}
/// Get the headers from the response.
#[inline]
pub fn headers(&self) -> &HeaderMap {
&self.headers
&self.get_ref().headers
}
/// Get a mutable reference to the headers.
#[inline]
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.headers
&mut self.get_mut().headers
}
/// Get the status from the server.
#[inline]
pub fn status(&self) -> StatusCode {
self.status
self.get_ref().status
}
/// Set the `StatusCode` for this response.
#[inline]
pub fn status_mut(&mut self) -> &mut StatusCode {
&mut self.status
&mut self.get_mut().status
}
/// Get custom reason for the response.
#[inline]
pub fn reason(&self) -> &str {
if let Some(reason) = self.reason {
if let Some(reason) = self.get_ref().reason {
reason
} else {
""
self.get_ref().status.canonical_reason().unwrap_or("<unknown status code>")
}
}
/// Set the custom reason for the response.
#[inline]
pub fn set_reason(&mut self, reason: &'static str) -> &mut Self {
self.reason = Some(reason);
self.get_mut().reason = Some(reason);
self
}
/// Set connection type
pub fn set_connection_type(&mut self, conn: ConnectionType) -> &mut Self {
self.connection_type = Some(conn);
self.get_mut().connection_type = Some(conn);
self
}
/// Connection upgrade status
#[inline]
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
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 {
ConnectionType::KeepAlive => Some(true),
ConnectionType::Close | ConnectionType::Upgrade => Some(false),
@ -152,66 +156,59 @@ impl HttpResponse {
}
/// is chunked encoding enabled
#[inline]
pub fn chunked(&self) -> bool {
self.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(())
}
self.get_ref().chunked
}
/// Content encoding
#[inline]
pub fn content_encoding(&self) -> &ContentEncoding {
&self.encoding
&self.get_ref().encoding
}
/// Set content encoding
pub fn set_content_encoding(&mut self, enc: ContentEncoding) -> &mut Self {
self.encoding = enc;
self.get_mut().encoding = enc;
self
}
/// Get body os this response
#[inline]
pub fn body(&self) -> &Body {
&self.body
&self.get_ref().body
}
/// Set a body
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
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
pub fn response_size(&self) -> u64 {
self.response_size
self.get_ref().response_size
}
/// Set content encoding
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 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let res = write!(f, "\nHttpResponse {:?} {}{}\n",
self.version, self.status, self.reason.unwrap_or(""));
let _ = write!(f, " encoding: {:?}\n", self.encoding);
self.get_ref().version, self.get_ref().status,
self.get_ref().reason.unwrap_or(""));
let _ = write!(f, " encoding: {:?}\n", self.get_ref().encoding);
let _ = write!(f, " headers:\n");
for key in self.headers.keys() {
let vals: Vec<_> = self.headers.get_all(key).iter().collect();
for key in self.get_ref().headers.keys() {
let vals: Vec<_> = self.get_ref().headers.get_all(key).iter().collect();
if vals.len() > 1 {
let _ = write!(f, " {:?}: {:?}\n", key, vals);
} 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
///
/// This type can be used to construct an instance of `HttpResponse` through a
/// builder-like pattern.
#[derive(Debug)]
pub struct HttpResponseBuilder {
parts: Option<Parts>,
response: Option<Box<InnerHttpResponse>>,
err: Option<HttpError>,
cookies: Option<CookieJar>,
}
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]
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);
}
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.
///
/// ```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]
pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
where HeaderName: HttpTryFrom<K>,
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) {
Ok(key) => {
match HeaderValue::try_from(value) {
@ -302,7 +282,7 @@ impl HttpResponseBuilder {
/// Set the custom reason for the response.
#[inline]
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);
}
self
@ -313,44 +293,52 @@ impl HttpResponseBuilder {
/// By default `ContentEncoding::Auto` is used, which automatically
/// negotiates content encoding based on request's `Accept-Encoding` headers.
/// To enforce specific encoding, use specific ContentEncoding` value.
#[inline]
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;
}
self
}
/// Set connection type
#[inline]
#[doc(hidden)]
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);
}
self
}
/// Set connection type to Upgrade
#[inline]
#[doc(hidden)]
pub fn upgrade(&mut self) -> &mut Self {
self.connection_type(ConnectionType::Upgrade)
}
/// Force close connection, even if it is marked as keep-alive
#[inline]
pub fn force_close(&mut self) -> &mut Self {
self.connection_type(ConnectionType::Close)
}
/// Enables automatic chunked transfer encoding
pub fn enable_chunked(&mut self) -> &mut Self {
if let Some(parts) = parts(&mut self.parts, &self.err) {
#[inline]
pub fn chunked(&mut self) -> &mut Self {
if let Some(parts) = parts(&mut self.response, &self.err) {
parts.chunked = true;
}
self
}
/// Set response content type
#[inline]
pub fn content_type<V>(&mut self, value: V) -> &mut Self
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) {
Ok(value) => { parts.headers.insert(header::CONTENT_TYPE, value); },
Err(e) => self.err = Some(e.into()),
@ -360,23 +348,53 @@ impl HttpResponseBuilder {
}
/// 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 {
if let Some(parts) = parts(&mut self.parts, &self.err) {
parts.cookies.add(cookie.into_owned());
if self.cookies.is_none() {
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
}
/// 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 {
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();
parts.cookies.add_original(cookie.clone());
parts.cookies.remove(cookie);
jar.add_original(cookie.clone());
jar.remove(cookie);
}
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
where F: Fn(&mut HttpResponseBuilder) + 'static
{
@ -387,36 +405,31 @@ impl HttpResponseBuilder {
}
/// Set a body and generate `HttpResponse`.
///
/// `HttpResponseBuilder` can not be used after this call.
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() {
return Err(e)
}
for cookie in parts.cookies.delta() {
parts.headers.append(
header::SET_COOKIE,
HeaderValue::from_str(&cookie.to_string())?);
let mut response = self.response.take().expect("cannot reuse response builder");
if let Some(ref jar) = self.cookies {
for cookie in jar.delta() {
response.headers.append(
header::SET_COOKIE,
HeaderValue::from_str(&cookie.to_string())?);
}
}
Ok(HttpResponse {
version: parts.version,
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,
})
response.body = body.into();
Ok(HttpResponse(Some(response)))
}
/// 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> {
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)
} else {
true
@ -429,12 +442,26 @@ impl HttpResponseBuilder {
}
/// Set an empty body and generate `HttpResponse`
///
/// `HttpResponseBuilder` can not be used after this call.
pub fn finish(&mut self) -> Result<HttpResponse, HttpError> {
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() {
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 {
fn from(val: &'static str) -> Self {
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 {
fn from(val: &'static [u8]) -> Self {
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 {
fn from(val: String) -> Self {
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 {
fn from(val: &'a String) -> Self {
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 {
fn from(val: Bytes) -> Self {
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 {
fn from(val: BytesMut) -> Self {
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)]
mod tests {
use super::*;
use std::str::FromStr;
use time::Duration;
use http::{Method, Uri};
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]
fn test_basic_builder() {
let resp = HttpResponse::Ok()
.status(StatusCode::NO_CONTENT)
.header("X-TEST", "value")
.version(Version::HTTP_10)
.finish().unwrap();
assert_eq!(resp.version(), Some(Version::HTTP_10));
assert_eq!(resp.status(), StatusCode::NO_CONTENT)
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
@ -588,6 +821,8 @@ mod tests {
#[test]
fn test_into_response() {
let req = HttpRequest::default();
let resp: HttpResponse = "test".into();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
@ -595,6 +830,13 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK);
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();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
@ -602,6 +844,13 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK);
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();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
@ -609,6 +858,13 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK);
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();
assert_eq!(resp.status(), StatusCode::OK);
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(),
@ -616,6 +872,13 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK);
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 resp: HttpResponse = b.into();
assert_eq!(resp.status(), StatusCode::OK);
@ -624,6 +887,14 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK);
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 resp: HttpResponse = b.into();
assert_eq!(resp.status(), StatusCode::OK);
@ -631,5 +902,13 @@ mod tests {
header::HeaderValue::from_static("application/octet-stream"));
assert_eq!(resp.status(), StatusCode::OK);
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
View 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
View 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()}));
}
}

View file

@ -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(
specialization, // for impl ErrorResponse for std::error::Error
@ -11,13 +46,15 @@ extern crate bytes;
extern crate sha1;
extern crate regex;
#[macro_use]
extern crate bitflags;
#[macro_use]
extern crate failure;
#[macro_use]
extern crate futures;
extern crate tokio_io;
extern crate tokio_core;
extern crate failure;
#[macro_use] extern crate failure_derive;
extern crate mio;
extern crate net2;
extern crate cookie;
extern crate http;
extern crate httparse;
@ -31,10 +68,13 @@ extern crate serde_json;
extern crate flate2;
extern crate brotli2;
extern crate percent_encoding;
extern crate actix;
extern crate smallvec;
extern crate num_cpus;
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")]
extern crate native_tls;
@ -49,18 +89,20 @@ extern crate tokio_openssl;
mod application;
mod body;
mod context;
mod date;
mod helpers;
mod encoding;
mod httprequest;
mod httpresponse;
mod payload;
mod resource;
mod recognizer;
mod info;
mod json;
mod route;
//mod task;
mod router;
mod param;
mod resource;
mod handler;
mod pipeline;
mod staticfiles;
mod server;
mod worker;
mod channel;
mod wsframe;
mod wsproto;
@ -69,33 +111,69 @@ mod h2;
mod h1writer;
mod h2writer;
pub mod fs;
pub mod ws;
pub mod dev;
pub mod error;
pub mod httpcodes;
pub mod multipart;
pub mod middlewares;
pub use error::{Error, Result};
pub use encoding::ContentEncoding;
pub mod middleware;
pub mod pred;
pub mod test;
pub mod payload;
pub use error::{Error, Result, ResponseError};
pub use body::{Body, Binary};
pub use json::{Json};
pub use application::Application;
pub use httprequest::{HttpRequest, UrlEncoded};
pub use httprequest::HttpRequest;
pub use httpresponse::HttpResponse;
pub use payload::{Payload, PayloadItem};
pub use route::Reply;
pub use handler::{Reply, Responder, NormalizePath, AsyncResponder};
pub use route::Route;
pub use resource::Resource;
pub use recognizer::Params;
pub use server::HttpServer;
pub use context::HttpContext;
pub use staticfiles::StaticFiles;
// re-exports
pub use http::{Method, StatusCode, Version};
pub use cookie::Cookie;
pub use http_range::HttpRange;
#[doc(hidden)]
#[cfg(feature="tls")]
pub use native_tls::Pkcs12;
#[doc(hidden)]
#[cfg(feature="openssl")]
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};
}

View 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");
}
}

View file

@ -9,25 +9,25 @@ use regex::Regex;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middlewares::{Middleware, Started, Finished};
use middleware::{Middleware, Started, Finished};
/// `Middleware` for logging request and response info to the terminal.
///
/// ## 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:
///
/// ```ignore
/// %a %t "%r" %s %b "%{Referrer}i" "%{User-Agent}i" %T
/// %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
/// ```
/// ```rust
/// extern crate actix_web;
/// # extern crate actix_web;
/// use actix_web::Application;
/// use actix_web::middlewares::Logger;
/// use actix_web::middleware::Logger;
///
/// fn main() {
/// let app = Application::default("/")
/// let app = Application::new()
/// .middleware(Logger::default())
/// .middleware(Logger::new("%a %{User-Agent}i"))
/// .finish();
@ -75,7 +75,7 @@ impl Default for Logger {
/// Create `Logger` middleware with format:
///
/// ```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 {
Logger { format: Format::default() }
@ -86,7 +86,7 @@ struct StartTime(time::Tm);
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 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()));
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);
Finished::Done
}
}
/// A formatting style for the `Logger`, consisting of multiple
/// `FormatText`s concatenated into one line.
#[derive(Clone)]
@ -122,7 +121,7 @@ struct Format(Vec<FormatText>);
impl Default for Format {
/// Return the default formatting style for the `Logger`:
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 {
fn render(&self, fmt: &mut Formatter,
req: &HttpRequest,
resp: &HttpResponse,
entry_time: time::Tm) -> Result<(), fmt::Error>
fn render<S>(&self, fmt: &mut Formatter,
req: &HttpRequest<S>,
resp: &HttpResponse,
entry_time: time::Tm) -> Result<(), fmt::Error>
{
match *self {
FormatText::Str(ref string) => fmt.write_str(string),
@ -224,7 +223,7 @@ impl FormatText {
FormatText::Pid => unsafe{libc::getpid().fmt(fmt)},
FormatText::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;
fmt.write_fmt(format_args!("{:.6}", response_time))
@ -237,8 +236,8 @@ impl FormatText {
fmt.write_fmt(format_args!("{:.6}", response_time_ms))
},
FormatText::RemoteAddr => {
if let Some(addr) = req.remote() {
addr.fmt(fmt)
if let Some(remote) = req.connection_info().remote() {
return remote.fmt(fmt);
} else {
"-".fmt(fmt)
}
@ -292,7 +291,6 @@ mod tests {
use time;
use http::{Method, Version, StatusCode, Uri};
use http::header::{self, HeaderMap};
use payload::Payload;
#[test]
fn test_logger() {
@ -301,8 +299,7 @@ mod tests {
let mut headers = HeaderMap::new();
headers.insert(header::USER_AGENT, header::HeaderValue::from_static("ACTIX-WEB"));
let mut req = HttpRequest::new(
Method::GET, Uri::from_str("/").unwrap(),
Version::HTTP_11, headers, Payload::empty());
Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None);
let resp = HttpResponse::build(StatusCode::OK)
.header("X-Test", "ttt")
.force_close().body(Body::Empty).unwrap();
@ -333,8 +330,7 @@ mod tests {
let mut headers = HeaderMap::new();
headers.insert(header::USER_AGENT, header::HeaderValue::from_static("ACTIX-WEB"));
let req = HttpRequest::new(
Method::GET, Uri::from_str("/").unwrap(),
Version::HTTP_11, headers, Payload::empty());
Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None);
let resp = HttpResponse::build(StatusCode::OK)
.force_close().body(Body::Empty).unwrap();
let entry_time = time::now();
@ -352,7 +348,7 @@ mod tests {
let req = HttpRequest::new(
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)
.force_close().body(Body::Empty).unwrap();
let entry_time = time::now();

View file

@ -7,7 +7,9 @@ use httpresponse::HttpResponse;
mod logger;
mod session;
mod defaultheaders;
pub use self::logger::Logger;
pub use self::defaultheaders::{DefaultHeaders, DefaultHeadersBuilder};
pub use self::session::{RequestSession, Session, SessionImpl, SessionBackend, SessionStorage,
CookieSessionError, CookieSessionBackend, CookieSessionBackendBuilder};
@ -44,22 +46,22 @@ pub enum Finished {
/// Middleware definition
#[allow(unused_variables)]
pub trait Middleware {
pub trait Middleware<S> {
/// 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) -> Started {
fn start(&self, req: &mut HttpRequest<S>) -> Started {
Started::Done
}
/// Method is called when handler returns response,
/// 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)
}
/// 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
}
}

View file

@ -3,6 +3,7 @@
use std::any::Any;
use std::rc::Rc;
use std::sync::Arc;
use std::marker::PhantomData;
use std::collections::HashMap;
use serde_json;
@ -13,17 +14,34 @@ use cookie::{CookieJar, Cookie, Key};
use futures::Future;
use futures::future::{FutureResult, ok as FutOk, err as FutErr};
use error::{Result, Error, ErrorResponse};
use error::{Result, Error, ResponseError};
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
use middlewares::{Middleware, Started, Response};
use middleware::{Middleware, Started, Response};
/// 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 {
fn session(&mut self) -> Session;
}
impl RequestSession for HttpRequest {
impl<S> RequestSession for HttpRequest<S> {
fn session(&mut self) -> Session {
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
/// [`RequestSession::session`](trait.RequestSession.html#tymethod.session)
/// 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);
impl<'a> Session<'a> {
@ -79,18 +114,34 @@ unsafe impl Send for SessionImplBox {}
unsafe impl Sync for SessionImplBox {}
/// 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
pub fn new(backend: T) -> SessionStorage<T> {
SessionStorage(backend)
pub fn new(backend: T) -> SessionStorage<T, S> {
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 fut = self.0.from_request(&mut req)
@ -106,7 +157,7 @@ impl<T: SessionBackend> Middleware for SessionStorage<T> {
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>>() {
s_box.0.write(resp)
} else {
@ -133,12 +184,12 @@ pub trait SessionImpl: 'static {
/// Session's storage backend trait definition.
#[doc(hidden)]
pub trait SessionBackend: Sized + 'static {
pub trait SessionBackend<S>: Sized + 'static {
type Session: SessionImpl;
type ReadFuture: Future<Item=Self::Session, Error=Error>;
/// 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
@ -177,7 +228,7 @@ pub enum CookieSessionError {
Serialize(JsonError),
}
impl ErrorResponse for CookieSessionError {}
impl ResponseError for CookieSessionError {}
impl SessionImpl for CookieSession {
@ -225,7 +276,7 @@ impl CookieSessionInner {
fn new(key: &[u8]) -> CookieSessionInner {
CookieSessionInner {
key: Key::from_master(key),
name: "actix_session".to_owned(),
name: "actix-session".to_owned(),
path: "/".to_owned(),
domain: None,
secure: true }
@ -258,8 +309,8 @@ impl CookieSessionInner {
Ok(())
}
fn load(&self, req: &mut HttpRequest) -> HashMap<String, String> {
if let Ok(cookies) = req.load_cookies() {
fn load<S>(&self, req: &mut HttpRequest<S>) -> HashMap<String, String> {
if let Ok(cookies) = req.cookies() {
for cookie in cookies {
if cookie.name() == self.name {
let mut jar = CookieJar::new();
@ -307,7 +358,7 @@ impl CookieSessionBackend {
/// # Example
///
/// ```
/// use actix_web::middlewares::CookieSessionBackend;
/// use actix_web::middleware::CookieSessionBackend;
///
/// 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 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);
FutOk(
CookieSession {
@ -345,11 +396,12 @@ impl SessionBackend for CookieSessionBackend {
/// ```rust
/// # extern crate actix_web;
///
/// use actix_web::middlewares::CookieSessionBackend;
/// use actix_web::middleware::CookieSessionBackend;
///
/// # fn main() {
/// let backend: CookieSessionBackend = CookieSessionBackend::build(&[0; 32])
/// .domain("www.rust-lang.org")
/// .name("actix_session")
/// .path("/")
/// .secure(true)
/// .finish();
@ -369,6 +421,12 @@ impl CookieSessionBackendBuilder {
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.
pub fn domain<S: Into<String>>(mut self, value: S) -> CookieSessionBackendBuilder {
self.0.domain = Some(value.into());

View file

@ -9,11 +9,12 @@ use httparse;
use bytes::Bytes;
use http::HttpTryFrom;
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 error::{ParseError, PayloadError, MultipartError};
use payload::Payload;
use httprequest::HttpRequest;
const MAX_HEADERS: usize = 32;
@ -26,7 +27,8 @@ const MAX_HEADERS: usize = 32;
#[derive(Debug)]
pub struct Multipart {
safety: Safety,
inner: Rc<RefCell<InnerMultipart>>,
error: Option<MultipartError>,
inner: Option<Rc<RefCell<InnerMultipart>>>,
}
///
@ -66,17 +68,32 @@ struct InnerMultipart {
}
impl Multipart {
/// Create multipart instance for boundary.
pub fn new(boundary: String, payload: Payload) -> Multipart {
Multipart {
error: None,
safety: Safety::new(),
inner: Rc::new(RefCell::new(
inner: Some(Rc::new(RefCell::new(
InnerMultipart {
payload: PayloadRef::new(payload),
boundary: boundary,
state: InnerState::FirstBoundary,
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;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
if self.safety.current() {
self.inner.borrow_mut().poll(&self.safety)
if let Some(err) = self.error.take() {
Err(err)
} else if self.safety.current() {
self.inner.as_mut().unwrap().borrow_mut().poll(&self.safety)
} else {
Ok(Async::NotReady)
}
@ -119,7 +138,7 @@ impl InnerMultipart {
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::Ready(bytes) => {
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>
{
// TODO: need to read epilogue
match payload.readline()? {
match payload.readline().poll()? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(chunk) => {
if chunk.len() == boundary.len() + 4 &&
@ -175,7 +194,7 @@ impl InnerMultipart {
{
let mut eof = false;
loop {
if let Async::Ready(chunk) = payload.readline()? {
if let Async::Ready(chunk) = payload.readline().poll()? {
if chunk.is_empty() {
//ValueError("Could not find starting boundary %r"
//% (self._boundary))
@ -327,7 +346,9 @@ impl InnerMultipart {
Ok(Async::Ready(Some(
MultipartItem::Nested(
Multipart{safety: safety.clone(), inner: inner}))))
Multipart{safety: safety.clone(),
error: None,
inner: Some(inner)}))))
} else {
let field = Rc::new(RefCell::new(InnerField::new(
self.payload.clone(), self.boundary.clone(), &headers)?));
@ -356,10 +377,6 @@ pub struct Field {
safety: Safety,
}
/// A field's chunk
#[derive(PartialEq, Debug)]
pub struct FieldChunk(pub Bytes);
impl Field {
fn new(safety: Safety, headers: HeaderMap,
@ -382,7 +399,7 @@ impl Field {
}
impl Stream for Field {
type Item = FieldChunk;
type Item = Bytes;
type Error = MultipartError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
@ -452,15 +469,15 @@ impl InnerField {
if *size == 0 {
Ok(Async::Ready(None))
} else {
match payload.readany() {
match payload.readany().poll() {
Ok(Async::NotReady) => Ok(Async::NotReady),
Ok(Async::Ready(None)) => Ok(Async::Ready(None)),
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;
let ch = chunk.0.split_to(len as usize);
if !chunk.0.is_empty() {
payload.unread_data(chunk.0);
let ch = chunk.split_to(len as usize);
if !chunk.is_empty() {
payload.unread_data(chunk);
}
Ok(Async::Ready(Some(ch)))
},
@ -473,12 +490,12 @@ impl InnerField {
/// The `Content-Length` header for body part is not necessary.
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::Ready(mut chunk) => {
if chunk.len() == 1 {
payload.unread_data(chunk);
match payload.readexactly(boundary.len() + 4)? {
match payload.readexactly(boundary.len() + 4).poll()? {
Async::NotReady => Ok(Async::NotReady),
Async::Ready(chunk) => {
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() {
return Ok(Async::Ready(None))
}
if self.eof {
if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) {
match payload.readline()? {
match payload.readline().poll()? {
Async::NotReady =>
return Ok(Async::NotReady),
Async::Ready(chunk) => {
@ -533,10 +550,10 @@ impl InnerField {
match res {
Async::NotReady => Async::NotReady,
Async::Ready(Some(bytes)) => Async::Ready(Some(FieldChunk(bytes))),
Async::Ready(Some(bytes)) => Async::Ready(Some(bytes)),
Async::Ready(None) => {
self.eof = true;
match payload.readline()? {
match payload.readline().poll()? {
Async::NotReady => Async::NotReady,
Async::Ready(chunk) => {
assert_eq!(
@ -713,7 +730,7 @@ mod tests {
match field.poll() {
Ok(Async::Ready(Some(chunk))) =>
assert_eq!(chunk.0, "test"),
assert_eq!(chunk, "test"),
_ => unreachable!()
}
match field.poll() {
@ -736,7 +753,7 @@ mod tests {
match field.poll() {
Ok(Async::Ready(Some(chunk))) =>
assert_eq!(chunk.0, "data"),
assert_eq!(chunk, "data"),
_ => unreachable!()
}
match field.poll() {

190
src/param.rs Normal file
View 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