1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-16 06:35:46 +02:00

Compare commits

..

40 Commits

Author SHA1 Message Date
Nikolay Kim
0da3fdcb09 do not use Arc for rustls config 2018-08-01 10:59:00 -07:00
Nikolay Kim
a5f80a25ff update changes 2018-08-01 10:51:47 -07:00
Nikolay Kim
6d9a1cadad Merge pull request #433 from jrconlin/feat/432
feature: allow TestServer to open a websocket on any URL
2018-08-01 10:45:55 -07:00
jrconlin
97ada3d3d0 Merge branch 'feat/432' of github.com:jrconlin/actix-web into feat/432 2018-08-01 10:27:48 -07:00
jrconlin
115f59dd14 Merge branch 'master' of https://github.com/actix/actix-web into feat/432 2018-08-01 09:59:36 -07:00
Nikolay Kim
972b008a6e remove unsafe error transmute, upgrade failure to 0.1.2 #434 2018-08-01 09:42:12 -07:00
jrconlin
246eafb8d2 Merge branch 'master' of https://github.com/actix/actix-web into feat/432 2018-08-01 09:36:08 -07:00
jrconlin
dca4c110dd feature: allow TestServer to open a websocket on any URL
* added `TestServer::ws_at(uri_str)`
* modified `TestServer::ws()` to call `self.ws_at("/")` to preserve
behavior

Closes #432
2018-08-01 09:30:27 -07:00
Nikolay Kim
58230b15b9 use one thread for accept loop; refactor rust-tls support 2018-07-31 19:51:26 -07:00
jrconlin
aa1e75f071 feature: allow TestServer to open a websocket on any URL
* added `TestServer::ws_at(uri_str)`
* modified `TestServer::ws()` to call `self.ws_at("/")` to preserve
behavior

Closes #432
2018-07-31 16:21:18 -07:00
Nikolay Kim
2071ea0532 HttpRequest::url_for is not working with scopes #429 2018-07-31 15:40:52 -07:00
Nikolay Kim
3bd43090fb use new gzdecoder, fixes gz streaming #228 2018-07-31 09:06:05 -07:00
Nikolay Kim
4dba531bf9 do not override HOST header for client request #428 2018-07-31 08:51:24 -07:00
Nikolay Kim
2072c933ba handle error during request creation 2018-07-30 15:04:52 -07:00
Nikolay Kim
7bc0ace52d move server accept impl to seprate module 2018-07-30 13:42:42 -07:00
Nikolay Kim
4c4d0d2745 update changes 2018-07-30 10:23:28 -07:00
Nikolay Kim
28a855214b Merge pull request #427 from jeizsm/feature/rustls
add rustls
2018-07-30 10:21:37 -07:00
Marat Safin
196da6d570 add rustls 2018-07-30 08:21:12 +03:00
Nikolay Kim
b4ed564e5d update changes 2018-07-26 09:11:50 -07:00
Nikolay Kim
80fbc2e9ec Fix stream draining for http/2 connections #290 2018-07-25 15:38:02 -07:00
Nikolay Kim
f58065082e fix missing content-encoding header for h2 connections #421 2018-07-25 10:30:55 -07:00
Douman
6048817ba7 Correct flate feature names in documentation 2018-07-25 20:22:18 +03:00
Mateusz Mikuła
e408b68744 Update cookie dependency (#422) 2018-07-25 18:01:22 +03:00
Nikolay Kim
b878613e10 fix warning 2018-07-24 15:49:46 -07:00
Nikolay Kim
85b275bb2b fix warnings 2018-07-24 15:09:30 -07:00
Nikolay Kim
d6abd2fe22 allow to handle empty path for application with prefix 2018-07-24 14:51:48 -07:00
Nikolay Kim
b79a9aaec7 fix changelog 2018-07-24 14:18:04 -07:00
Nikolay Kim
b9586b3f71 Merge pull request #412 from gdamjan/master
remove the timestamp from the default logger middleware
2018-07-24 14:07:10 -07:00
Nikolay Kim
d3b12d885e Merge branch 'master' into master 2018-07-24 14:07:03 -07:00
Nikolay Kim
f21386708a Merge pull request #416 from axos88/master
Add FromRequest<S> implementation for Option<T> and Result<T> where T: FromRequest<S>
2018-07-24 14:06:08 -07:00
Akos Vandra
b48a2d4d7b add changes to CHANGES.md 2018-07-24 22:25:48 +02:00
Akos Vandra
35b754a3ab pr fixes 2018-07-24 09:42:46 +02:00
Akos Vandra
1079c5c562 Add FromRequest<S> implementation for Result<T> and Option<T> where T:FromRequest<S> 2018-07-24 09:42:46 +02:00
Akos Vandra
f4bb7efa89 add partialeq, eq, partialord and ord dervie to Path, Form and Query 2018-07-24 09:42:46 +02:00
Akos Vandra
0099091e96 remove unnecessary use 2018-07-24 09:42:46 +02:00
Nikolay Kim
c352a69d54 fix dead links 2018-07-23 13:22:16 -07:00
Nikolay Kim
f5347ec897 Merge pull request #415 from DenisKolodin/cookie-http-only
Add http_only flag to CookieSessionBackend
2018-07-23 02:54:23 -07:00
Denis Kolodin
b367f07d56 Add http_only flag to CookieSessionBackend 2018-07-23 12:49:59 +03:00
Damjan Georgievski
6a75a3d683 document the change in the default logger 2018-07-21 16:01:42 +02:00
Damjan Georgievski
56b924e155 remove the timestamp from the default logger middleware
env_logger and other logging systems will (or should) already add their
own timestamp.
2018-07-21 15:15:28 +02:00
29 changed files with 1630 additions and 565 deletions

View File

@@ -32,12 +32,12 @@ script:
- |
if [[ "$TRAVIS_RUST_VERSION" != "stable" ]]; then
cargo clean
cargo test --features="alpn,tls" -- --nocapture
cargo test --features="alpn,tls,rust-tls" -- --nocapture
fi
- |
if [[ "$TRAVIS_RUST_VERSION" == "stable" ]]; then
RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install -f cargo-tarpaulin
cargo tarpaulin --features="alpn,tls" --out Xml --no-count
cargo tarpaulin --features="alpn,tls,rust-tls" --out Xml --no-count
bash <(curl -s https://codecov.io/bash)
echo "Uploaded code coverage"
fi
@@ -46,7 +46,7 @@ script:
after_success:
- |
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "beta" ]]; then
cargo doc --features "alpn, tls, session" --no-deps &&
cargo doc --features "alpn, tls, rust-tls, session" --no-deps &&
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
git clone https://github.com/davisp/ghp-import.git &&
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&

View File

@@ -1,5 +1,51 @@
# Changes
## [0.7.3] - 2018-08-01
### Added
* Support HTTP/2 with rustls #36
* Allow TestServer to open a websocket on any URL (TestServer::ws_at()) #433
### Fixed
* Fixed failure 0.1.2 compatibility
* Do not override HOST header for client request #428
* Gz streaming, use `flate2::write::GzDecoder` #228
* HttpRequest::url_for is not working with scopes #429
## [0.7.2] - 2018-07-26
### Added
* Add implementation of `FromRequest<S>` for `Option<T>` and `Result<T, Error>`
* Allow to handle application prefix, i.e. allow to handle `/app` path
for application with `/app` prefix.
Check [`App::prefix()`](https://actix.rs/actix-web/actix_web/struct.App.html#method.prefix)
api doc.
* Add `CookieSessionBackend::http_only` method to set `HttpOnly` directive of cookies
### Changed
* Upgrade to cookie 0.11
* Removed the timestamp from the default logger middleware
### Fixed
* Missing response header "content-encoding" #421
* Fix stream draining for http/2 connections #290
## [0.7.1] - 2018-07-21
### Fixed

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-web"
version = "0.7.1"
version = "0.7.3"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md"
@@ -17,7 +17,7 @@ exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
build = "build.rs"
[package.metadata.docs.rs]
features = ["tls", "alpn", "session", "brotli", "flate2-c"]
features = ["tls", "alpn", "rust-tls", "session", "brotli", "flate2-c"]
[badges]
travis-ci = { repository = "actix/actix-web", branch = "master" }
@@ -37,6 +37,9 @@ tls = ["native-tls", "tokio-tls"]
# openssl
alpn = ["openssl", "tokio-openssl"]
# rustls
rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots"]
# sessions feature, session require "ring" crate and c compiler
session = ["cookie/secure"]
@@ -56,7 +59,7 @@ base64 = "0.9"
bitflags = "1.0"
h2 = "0.1"
htmlescape = "0.3"
http = "^0.1.5"
http = "^0.1.8"
httparse = "1.3"
log = "0.4"
mime = "0.3"
@@ -76,11 +79,11 @@ lazy_static = "1.0"
lazycell = "1.0.0"
parking_lot = "0.6"
url = { version="1.7", features=["query_encoding"] }
cookie = { version="0.10", features=["percent-encode"] }
cookie = { version="0.11", features=["percent-encode"] }
brotli2 = { version="^0.3.2", optional = true }
flate2 = { version="1.0", optional = true, default-features = false }
flate2 = { version="^1.0.2", optional = true, default-features = false }
failure = "=0.1.1"
failure = "^0.1.2"
# io
mio = "^0.6.13"
@@ -104,6 +107,12 @@ tokio-tls = { version="0.1", optional = true }
openssl = { version="0.10", optional = true }
tokio-openssl = { version="0.2", optional = true }
#rustls
rustls = { version = "0.13", optional = true }
tokio-rustls = { version = "0.7", optional = true }
webpki = { version = "0.18", optional = true }
webpki-roots = { version = "0.15", optional = true }
# forked url_encoded
itoa = "0.4"
dtoa = "0.4"

View File

@@ -12,12 +12,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust.
* Multipart streams
* Static assets
* SSL support with OpenSSL or `native-tls`
* Middlewares ([Logger](https://actix.rs/book/actix-web/sec-9-middlewares.html#logging),
[Session](https://actix.rs/book/actix-web/sec-9-middlewares.html#user-sessions),
[Redis sessions](https://github.com/actix/actix-redis),
[DefaultHeaders](https://actix.rs/book/actix-web/sec-9-middlewares.html#default-headers),
[CORS](https://actix.rs/actix-web/actix_web/middleware/cors/index.html),
[CSRF](https://actix.rs/actix-web/actix_web/middleware/csrf/index.html))
* Middlewares ([Logger, Session, CORS, CSRF, etc](https://actix.rs/docs/middleware/))
* Includes an asynchronous [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html)
* Built on top of [Actix actor framework](https://github.com/actix/actix)

View File

@@ -140,7 +140,7 @@ where
parts: Some(ApplicationParts {
state,
prefix: "".to_owned(),
router: Router::new(),
router: Router::new(ResourceDef::prefix("")),
middlewares: Vec::new(),
filters: Vec::new(),
encoding: ContentEncoding::Auto,
@@ -171,7 +171,9 @@ where
/// In the following example only requests with an `/app/` path
/// prefix get handled. Requests with path `/app/test/` would be
/// handled, while requests with the paths `/application` or
/// `/other/...` would return `NOT FOUND`.
/// `/other/...` would return `NOT FOUND`. It is also possible to
/// handle `/app` path, to do this you can register resource for
/// empty string `""`
///
/// ```rust
/// # extern crate actix_web;
@@ -180,6 +182,8 @@ where
/// fn main() {
/// let app = App::new()
/// .prefix("/app")
/// .resource("", |r| r.f(|_| HttpResponse::Ok())) // <- handle `/app` path
/// .resource("/", |r| r.f(|_| HttpResponse::Ok())) // <- handle `/app/` path
/// .resource("/test", |r| {
/// r.get().f(|_| HttpResponse::Ok());
/// r.head().f(|_| HttpResponse::MethodNotAllowed());
@@ -194,6 +198,7 @@ where
if !prefix.starts_with('/') {
prefix.insert(0, '/')
}
parts.router.set_prefix(&prefix);
parts.prefix = prefix;
}
self
@@ -610,7 +615,6 @@ impl<S: 'static> Iterator for App<S> {
mod tests {
use super::*;
use body::{Binary, Body};
use fs;
use http::StatusCode;
use httprequest::HttpRequest;
use httpresponse::HttpResponse;
@@ -823,6 +827,23 @@ mod tests {
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_option_responder() {
let app = App::new()
.resource("/none", |r| r.f(|_| -> Option<&'static str> { None }))
.resource("/some", |r| r.f(|_| Some("some")))
.finish();
let req = TestRequest::with_uri("/none").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let req = TestRequest::with_uri("/some").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
assert_eq!(resp.as_msg().body(), &Body::Binary(Binary::Slice(b"some")));
}
#[test]
fn test_filter() {
let mut srv = TestServer::with_factory(|| {
@@ -841,19 +862,21 @@ mod tests {
}
#[test]
fn test_option_responder() {
let app = App::new()
.resource("/none", |r| r.f(|_| -> Option<&'static str> { None }))
.resource("/some", |r| r.f(|_| Some("some")))
.finish();
fn test_prefix_root() {
let mut srv = TestServer::with_factory(|| {
App::new()
.prefix("/test")
.resource("/", |r| r.f(|_| HttpResponse::Ok()))
.resource("", |r| r.f(|_| HttpResponse::Created()))
});
let req = TestRequest::with_uri("/none").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::NOT_FOUND);
let request = srv.get().uri(srv.url("/test/")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::OK);
let req = TestRequest::with_uri("/some").request();
let resp = app.run(req);
assert_eq!(resp.as_msg().status(), StatusCode::OK);
assert_eq!(resp.as_msg().body(), &Body::Binary(Binary::Slice(b"some")));
let request = srv.get().uri(srv.url("/test")).finish().unwrap();
let response = srv.execute(request.send()).unwrap();
assert_eq!(response.status(), StatusCode::CREATED);
}
}

View File

@@ -22,12 +22,55 @@ use openssl::ssl::{Error as OpensslError, SslConnector, SslMethod};
use tokio_openssl::SslConnectorExt;
#[cfg(all(feature = "tls", not(feature = "alpn")))]
use native_tls::{Error as TlsError, TlsConnector};
use native_tls::{Error as TlsError, TlsConnector, TlsStream};
#[cfg(all(feature = "tls", not(feature = "alpn")))]
use tokio_tls::TlsConnectorExt;
#[cfg(
all(
feature = "rust-tls",
not(any(feature = "alpn", feature = "tls"))
)
)]
use rustls::ClientConfig;
#[cfg(
all(
feature = "rust-tls",
not(any(feature = "alpn", feature = "tls"))
)
)]
use std::io::Error as TLSError;
#[cfg(
all(
feature = "rust-tls",
not(any(feature = "alpn", feature = "tls"))
)
)]
use std::sync::Arc;
#[cfg(
all(
feature = "rust-tls",
not(any(feature = "alpn", feature = "tls"))
)
)]
use tokio_rustls::ClientConfigExt;
#[cfg(
all(
feature = "rust-tls",
not(any(feature = "alpn", feature = "tls"))
)
)]
use webpki::DNSNameRef;
#[cfg(
all(
feature = "rust-tls",
not(any(feature = "alpn", feature = "tls"))
)
)]
use webpki_roots;
use server::IoStream;
use {HAS_OPENSSL, HAS_TLS};
use {HAS_OPENSSL, HAS_RUSTLS, HAS_TLS};
/// Client connector usage stats
#[derive(Default, Message)]
@@ -139,6 +182,16 @@ pub enum ClientConnectorError {
#[fail(display = "{}", _0)]
SslError(#[cause] TlsError),
/// SSL error
#[cfg(
all(
feature = "rust-tls",
not(any(feature = "alpn", feature = "tls"))
)
)]
#[fail(display = "{}", _0)]
SslError(#[cause] TLSError),
/// Resolver error
#[fail(display = "{}", _0)]
Resolver(#[cause] ResolverError),
@@ -193,6 +246,13 @@ pub struct ClientConnector {
connector: SslConnector,
#[cfg(all(feature = "tls", not(feature = "alpn")))]
connector: TlsConnector,
#[cfg(
all(
feature = "rust-tls",
not(any(feature = "alpn", feature = "tls"))
)
)]
connector: Arc<ClientConfig>,
stats: ClientConnectorStats,
subscriber: Option<Recipient<ClientConnectorStats>>,
@@ -262,8 +322,21 @@ impl Default for ClientConnector {
paused: Paused::No,
}
}
#[cfg(
all(
feature = "rust-tls",
not(any(feature = "alpn", feature = "tls"))
)
)]
{
let mut config = ClientConfig::new();
config
.root_store
.add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
ClientConnector::with_connector(config)
}
#[cfg(not(any(feature = "alpn", feature = "tls")))]
#[cfg(not(any(feature = "alpn", feature = "tls", feature = "rust-tls")))]
{
let (tx, rx) = mpsc::unbounded();
ClientConnector {
@@ -325,7 +398,7 @@ impl ClientConnector {
/// # actix::System::current().stop();
/// Ok(())
/// })
/// );
/// });
/// }
/// ```
pub fn with_connector(connector: SslConnector) -> ClientConnector {
@@ -352,6 +425,80 @@ impl ClientConnector {
}
}
#[cfg(
all(
feature = "rust-tls",
not(any(feature = "alpn", feature = "tls"))
)
)]
/// Create `ClientConnector` actor with custom `SslConnector` instance.
///
/// By default `ClientConnector` uses very a simple SSL configuration.
/// With `with_connector` method it is possible to use a custom
/// `SslConnector` object.
///
/// ```rust
/// # #![cfg(feature = "rust-tls")]
/// # extern crate actix_web;
/// # extern crate futures;
/// # extern crate tokio;
/// # use futures::{future, Future};
/// # use std::io::Write;
/// # use std::process;
/// # use actix_web::actix::Actor;
/// extern crate rustls;
/// extern crate webpki_roots;
/// use actix_web::{actix, client::ClientConnector, client::Connect};
///
/// use rustls::ClientConfig;
/// use std::sync::Arc;
///
/// fn main() {
/// actix::run(|| {
/// // Start `ClientConnector` with custom `ClientConfig`
/// let mut config = ClientConfig::new();
/// config
/// .root_store
/// .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS);
/// let conn = ClientConnector::with_connector(Arc::new(config)).start();
///
/// conn.send(
/// Connect::new("https://www.rust-lang.org").unwrap()) // <- connect to host
/// .map_err(|_| ())
/// .and_then(|res| {
/// if let Ok(mut stream) = res {
/// stream.write_all(b"GET / HTTP/1.0\r\n\r\n").unwrap();
/// }
/// # actix::System::current().stop();
/// Ok(())
/// })
/// });
/// }
/// ```
pub fn with_connector(connector: ClientConfig) -> ClientConnector {
let (tx, rx) = mpsc::unbounded();
ClientConnector {
connector: Arc::new(connector),
stats: ClientConnectorStats::default(),
subscriber: None,
acq_tx: tx,
acq_rx: Some(rx),
resolver: None,
conn_lifetime: Duration::from_secs(75),
conn_keep_alive: Duration::from_secs(15),
limit: 100,
limit_per_host: 0,
acquired: 0,
acquired_per_host: HashMap::new(),
available: HashMap::new(),
to_close: Vec::new(),
waiters: Some(HashMap::new()),
wait_timeout: None,
paused: Paused::No,
}
}
/// Set total number of simultaneous connections.
///
/// If limit is 0, the connector has no limit.
@@ -599,7 +746,7 @@ impl ClientConnector {
}
Acquire::Available => {
// create new connection
self.connect_waiter(key.clone(), waiter, ctx);
self.connect_waiter(&key, waiter, ctx);
}
}
}
@@ -608,7 +755,8 @@ impl ClientConnector {
self.waiters = Some(act_waiters);
}
fn connect_waiter(&mut self, key: Key, waiter: Waiter, ctx: &mut Context<Self>) {
fn connect_waiter(&mut self, key: &Key, waiter: Waiter, ctx: &mut Context<Self>) {
let key = key.clone();
let conn = AcquiredConn(key.clone(), Some(self.acq_tx.clone()));
let key2 = key.clone();
@@ -635,7 +783,7 @@ impl ClientConnector {
act.connector
.connect_async(&key.host, stream)
.into_actor(act)
.then(move |res, act, _| {
.then(move |res, _, _| {
match res {
Err(e) => {
let _ = waiter.tx.send(Err(
@@ -708,7 +856,57 @@ impl ClientConnector {
}
}
#[cfg(not(any(feature = "alpn", feature = "tls")))]
#[cfg(
all(
feature = "rust-tls",
not(any(feature = "alpn", feature = "tls"))
)
)]
match res {
Err(err) => {
let _ = waiter.tx.send(Err(err.into()));
fut::Either::B(fut::err(()))
}
Ok(stream) => {
act.stats.opened += 1;
if conn.0.ssl {
let host =
DNSNameRef::try_from_ascii_str(&key.host).unwrap();
fut::Either::A(
act.connector
.connect_async(host, stream)
.into_actor(act)
.then(move |res, _, _| {
match res {
Err(e) => {
let _ = waiter.tx.send(Err(
ClientConnectorError::SslError(e),
));
}
Ok(stream) => {
let _ =
waiter.tx.send(Ok(Connection::new(
conn.0.clone(),
Some(conn),
Box::new(stream),
)));
}
}
fut::ok(())
}),
)
} else {
let _ = waiter.tx.send(Ok(Connection::new(
conn.0.clone(),
Some(conn),
Box::new(stream),
)));
fut::Either::B(fut::ok(()))
}
}
}
#[cfg(not(any(feature = "alpn", feature = "tls", feature = "rust-tls")))]
match res {
Err(err) => {
let _ = waiter.tx.send(Err(err.into()));
@@ -783,7 +981,7 @@ impl Handler<Connect> for ClientConnector {
};
// check ssl availability
if proto.is_secure() && !HAS_OPENSSL && !HAS_TLS {
if proto.is_secure() && !HAS_OPENSSL && !HAS_TLS && !HAS_RUSTLS {
return ActorResponse::reply(Err(ClientConnectorError::SslIsNotSupported));
}
@@ -828,7 +1026,7 @@ impl Handler<Connect> for ClientConnector {
wait,
conn_timeout,
};
self.connect_waiter(key.clone(), waiter, ctx);
self.connect_waiter(&key, waiter, ctx);
return ActorResponse::async(
rx.map_err(|_| ClientConnectorError::Disconnected)
@@ -885,7 +1083,7 @@ impl Handler<Connect> for ClientConnector {
wait,
conn_timeout,
};
self.connect_waiter(key.clone(), waiter, ctx);
self.connect_waiter(&key, waiter, ctx);
ActorResponse::async(
rx.map_err(|_| ClientConnectorError::Disconnected)

View File

@@ -216,7 +216,7 @@ impl Future for SendRequest {
match pl.parse() {
Ok(Async::Ready(mut resp)) => {
if self.req.method() == &Method::HEAD {
if self.req.method() == Method::HEAD {
pl.parser.take();
}
resp.set_pipeline(pl);

View File

@@ -291,10 +291,6 @@ impl ClientRequestBuilder {
fn _uri(&mut self, url: &str) -> &mut Self {
match Uri::try_from(url) {
Ok(uri) => {
// set request host header
if let Some(host) = uri.host() {
self.set_header(header::HOST, host);
}
if let Some(parts) = parts(&mut self.request, &self.err) {
parts.uri = uri;
}
@@ -316,8 +312,7 @@ impl ClientRequestBuilder {
/// Set HTTP method of this request.
#[inline]
pub fn get_method(&mut self) -> &Method {
let parts =
parts(&mut self.request, &self.err).expect("cannot reuse request builder");
let parts = self.request.as_ref().expect("cannot reuse request builder");
&parts.method
}
@@ -630,9 +625,24 @@ impl ClientRequestBuilder {
self.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate");
}
// set request host header
if let Some(parts) = parts(&mut self.request, &self.err) {
if let Some(host) = parts.uri.host() {
if !parts.headers.contains_key(header::HOST) {
match host.try_into() {
Ok(value) => {
parts.headers.insert(header::HOST, value);
}
Err(e) => self.err = Some(e.into()),
}
}
}
}
// user agent
self.set_header_if_none(
header::USER_AGENT,
concat!("Actix-web/", env!("CARGO_PKG_VERSION")),
concat!("actix-web/", env!("CARGO_PKG_VERSION")),
);
}

View File

@@ -52,7 +52,8 @@ pub struct Error {
impl Error {
/// Deprecated way to reference the underlying response error.
#[deprecated(
since = "0.6.0", note = "please use `Error::as_response_error()` instead"
since = "0.6.0",
note = "please use `Error::as_response_error()` instead"
)]
pub fn cause(&self) -> &ResponseError {
self.cause.as_ref()
@@ -97,21 +98,9 @@ impl Error {
//
// So we first downcast into that compat, to then further downcast through
// the failure's Error downcasting system into the original failure.
//
// This currently requires a transmute. This could be avoided if failure
// provides a deref: https://github.com/rust-lang-nursery/failure/pull/213
let compat: Option<&failure::Compat<failure::Error>> =
Fail::downcast_ref(self.cause.as_fail());
if let Some(compat) = compat {
pub struct CompatWrappedError {
error: failure::Error,
}
let compat: &CompatWrappedError =
unsafe { &*(compat as *const _ as *const CompatWrappedError) };
compat.error.downcast_ref()
} else {
None
}
compat.and_then(|e| e.get_ref().downcast_ref())
}
}

View File

@@ -6,7 +6,7 @@ use std::{fmt, str};
use bytes::Bytes;
use encoding::all::UTF_8;
use encoding::types::{DecoderTrap, Encoding};
use futures::{Async, Future, Poll};
use futures::{future, Async, Future, Poll};
use mime::Mime;
use serde::de::{self, DeserializeOwned};
use serde_urlencoded;
@@ -17,6 +17,7 @@ use handler::{AsyncResult, FromRequest};
use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
use httprequest::HttpRequest;
#[derive(PartialEq, Eq, PartialOrd, Ord)]
/// Extract typed information from the request's path.
///
/// ## Example
@@ -128,6 +129,7 @@ impl<T: fmt::Display> fmt::Display for Path<T> {
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord)]
/// Extract typed information from from the request's query.
///
/// ## Example
@@ -215,6 +217,7 @@ impl<T: fmt::Display> fmt::Display for Query<T> {
}
}
#[derive(PartialEq, Eq, PartialOrd, Ord)]
/// Extract typed information from the request's body.
///
/// To extract typed information from request's body, the type `T` must
@@ -455,6 +458,126 @@ impl<S: 'static> FromRequest<S> for String {
}
}
/// Optionally extract a field from the request
///
/// If the FromRequest for T fails, return None rather than returning an error response
///
/// ## Example
///
/// ```rust
/// # extern crate actix_web;
/// extern crate rand;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest};
/// use actix_web::error::ErrorBadRequest;
///
/// #[derive(Debug, Deserialize)]
/// struct Thing { name: String }
///
/// impl<S> FromRequest<S> for Thing {
/// type Config = ();
/// type Result = Result<Thing, Error>;
///
/// #[inline]
/// fn from_request(req: &HttpRequest<S>, _cfg: &Self::Config) -> Self::Result {
/// if rand::random() {
/// Ok(Thing { name: "thingy".into() })
/// } else {
/// Err(ErrorBadRequest("no luck"))
/// }
///
/// }
/// }
///
/// /// extract text data from request
/// fn index(supplied_thing: Option<Thing>) -> Result<String> {
/// match supplied_thing {
/// // Puns not intended
/// Some(thing) => Ok(format!("Got something: {:?}", thing)),
/// None => Ok(format!("No thing!"))
/// }
/// }
///
/// fn main() {
/// let app = App::new().resource("/users/:first", |r| {
/// r.method(http::Method::POST).with(index)
/// });
/// }
/// ```
impl<T: 'static, S: 'static> FromRequest<S> for Option<T>
where
T: FromRequest<S>,
{
type Config = T::Config;
type Result = Box<Future<Item = Option<T>, Error = Error>>;
#[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
Box::new(T::from_request(req, cfg).into().then(|r| match r {
Ok(v) => future::ok(Some(v)),
Err(_) => future::ok(None),
}))
}
}
/// Optionally extract a field from the request or extract the Error if unsuccessful
///
/// If the FromRequest for T fails, inject Err into handler rather than returning an error response
///
/// ## Example
///
/// ```rust
/// # extern crate actix_web;
/// extern crate rand;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{http, App, Result, HttpRequest, Error, FromRequest};
/// use actix_web::error::ErrorBadRequest;
///
/// #[derive(Debug, Deserialize)]
/// struct Thing { name: String }
///
/// impl<S> FromRequest<S> for Thing {
/// type Config = ();
/// type Result = Result<Thing, Error>;
///
/// #[inline]
/// fn from_request(req: &HttpRequest<S>, _cfg: &Self::Config) -> Self::Result {
/// if rand::random() {
/// Ok(Thing { name: "thingy".into() })
/// } else {
/// Err(ErrorBadRequest("no luck"))
/// }
///
/// }
/// }
///
/// /// extract text data from request
/// fn index(supplied_thing: Result<Thing>) -> Result<String> {
/// match supplied_thing {
/// Ok(thing) => Ok(format!("Got thing: {:?}", thing)),
/// Err(e) => Ok(format!("Error extracting thing: {}", e))
/// }
/// }
///
/// fn main() {
/// let app = App::new().resource("/users/:first", |r| {
/// r.method(http::Method::POST).with(index)
/// });
/// }
/// ```
impl<T: 'static, S: 'static> FromRequest<S> for Result<T, Error>
where
T: FromRequest<S>,
{
type Config = T::Config;
type Result = Box<Future<Item = Result<T, Error>, Error = Error>>;
#[inline]
fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
Box::new(T::from_request(req, cfg).into().then(future::ok))
}
}
/// Payload configuration for request's payload.
pub struct PayloadConfig {
limit: usize,
@@ -680,6 +803,98 @@ mod tests {
}
}
#[test]
fn test_option() {
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).finish();
let mut cfg = FormConfig::default();
cfg.limit(4096);
match Option::<Form<Info>>::from_request(&req, &cfg)
.poll()
.unwrap()
{
Async::Ready(r) => assert_eq!(r, None),
_ => unreachable!(),
}
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "9")
.set_payload(Bytes::from_static(b"hello=world"))
.finish();
match Option::<Form<Info>>::from_request(&req, &cfg)
.poll()
.unwrap()
{
Async::Ready(r) => assert_eq!(
r,
Some(Form(Info {
hello: "world".into()
}))
),
_ => unreachable!(),
}
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "9")
.set_payload(Bytes::from_static(b"bye=world"))
.finish();
match Option::<Form<Info>>::from_request(&req, &cfg)
.poll()
.unwrap()
{
Async::Ready(r) => assert_eq!(r, None),
_ => unreachable!(),
}
}
#[test]
fn test_result() {
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "11")
.set_payload(Bytes::from_static(b"hello=world"))
.finish();
match Result::<Form<Info>, Error>::from_request(&req, &FormConfig::default())
.poll()
.unwrap()
{
Async::Ready(Ok(r)) => assert_eq!(
r,
Form(Info {
hello: "world".into()
})
),
_ => unreachable!(),
}
let req = TestRequest::with_header(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
).header(header::CONTENT_LENGTH, "9")
.set_payload(Bytes::from_static(b"bye=world"))
.finish();
match Result::<Form<Info>, Error>::from_request(&req, &FormConfig::default())
.poll()
.unwrap()
{
Async::Ready(r) => assert!(r.is_err()),
_ => unreachable!(),
}
}
#[test]
fn test_payload_config() {
let req = TestRequest::default().finish();
@@ -719,7 +934,7 @@ mod tests {
fn test_request_extract() {
let req = TestRequest::with_uri("/name/user1/?id=test").finish();
let mut router = Router::<()>::new();
let mut router = Router::<()>::default();
router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/")));
let info = router.recognize(&req, &(), 0);
let req = req.with_route_info(info);
@@ -735,7 +950,7 @@ mod tests {
let s = Query::<Id>::from_request(&req, &()).unwrap();
assert_eq!(s.id, "test");
let mut router = Router::<()>::new();
let mut router = Router::<()>::default();
router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/")));
let req = TestRequest::with_uri("/name/32/").finish();
let info = router.recognize(&req, &(), 0);
@@ -756,7 +971,7 @@ mod tests {
#[test]
fn test_extract_path_single() {
let mut router = Router::<()>::new();
let mut router = Router::<()>::default();
router.register_resource(Resource::new(ResourceDef::new("/{value}/")));
let req = TestRequest::with_uri("/32/").finish();
@@ -767,7 +982,7 @@ mod tests {
#[test]
fn test_tuple_extract() {
let mut router = Router::<()>::new();
let mut router = Router::<()>::default();
router.register_resource(Resource::new(ResourceDef::new("/{key}/{value}/")));
let req = TestRequest::with_uri("/name/user1/?id=test").finish();

View File

@@ -420,7 +420,7 @@ mod tests {
#[test]
fn test_request_match_info() {
let mut router = Router::<()>::new();
let mut router = Router::<()>::default();
router.register_resource(Resource::new(ResourceDef::new("/{key}/")));
let req = TestRequest::with_uri("/value/?id=test").finish();
@@ -430,7 +430,7 @@ mod tests {
#[test]
fn test_url_for() {
let mut router = Router::<()>::new();
let mut router = Router::<()>::default();
let mut resource = Resource::new(ResourceDef::new("/user/{name}.{ext}"));
resource.name("index");
router.register_resource(resource);
@@ -464,7 +464,8 @@ mod tests {
fn test_url_for_with_prefix() {
let mut resource = Resource::new(ResourceDef::new("/user/{name}.html"));
resource.name("index");
let mut router = Router::<()>::new();
let mut router = Router::<()>::default();
router.set_prefix("/prefix");
router.register_resource(resource);
let mut info = router.default_route_info();
@@ -490,7 +491,8 @@ mod tests {
fn test_url_for_static() {
let mut resource = Resource::new(ResourceDef::new("/index.html"));
resource.name("index");
let mut router = Router::<()>::new();
let mut router = Router::<()>::default();
router.set_prefix("/prefix");
router.register_resource(resource);
let mut info = router.default_route_info();
@@ -513,7 +515,7 @@ mod tests {
#[test]
fn test_url_for_external() {
let mut router = Router::<()>::new();
let mut router = Router::<()>::default();
router.register_external(
"youtube",
ResourceDef::external("https://youtube.com/watch/{video_id}"),

View File

@@ -70,9 +70,9 @@
//! dependency
//! * `brotli` - enables `brotli` compression support, requires `c`
//! compiler
//! * `flate-c` - enables `gzip`, `deflate` compression support, requires
//! * `flate2-c` - enables `gzip`, `deflate` compression support, requires
//! `c` compiler
//! * `flate-rust` - experimental rust based implementation for
//! * `flate2-rust` - experimental rust based implementation for
//! `gzip`, `deflate` compression.
//!
#![cfg_attr(actix_nightly, feature(
@@ -151,6 +151,15 @@ extern crate openssl;
#[cfg(feature = "openssl")]
extern crate tokio_openssl;
#[cfg(feature = "rust-tls")]
extern crate rustls;
#[cfg(feature = "rust-tls")]
extern crate tokio_rustls;
#[cfg(feature = "rust-tls")]
extern crate webpki;
#[cfg(feature = "rust-tls")]
extern crate webpki_roots;
mod application;
mod body;
mod context;
@@ -224,6 +233,11 @@ pub(crate) const HAS_TLS: bool = true;
#[cfg(not(feature = "tls"))]
pub(crate) const HAS_TLS: bool = false;
#[cfg(feature = "rust-tls")]
pub(crate) const HAS_RUSTLS: bool = true;
#[cfg(not(feature = "rust-tls"))]
pub(crate) const HAS_RUSTLS: bool = false;
pub mod dev {
//! The `actix-web` prelude for library developers
//!

View File

@@ -25,7 +25,7 @@ use middleware::{Finished, Middleware, Started};
/// default format:
///
/// ```ignore
/// %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
/// ```
/// ```rust
/// # extern crate actix_web;
@@ -94,7 +94,7 @@ impl Default for Logger {
/// Create `Logger` middleware with format:
///
/// ```ignore
/// %a %t "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T
/// ```
fn default() -> Logger {
Logger {
@@ -143,7 +143,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 "%{Referer}i" "%{User-Agent}i" %T"#)
Format::new(r#"%a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T"#)
}
}

View File

@@ -358,6 +358,7 @@ struct CookieSessionInner {
path: String,
domain: Option<String>,
secure: bool,
http_only: bool,
max_age: Option<Duration>,
same_site: Option<SameSite>,
}
@@ -371,6 +372,7 @@ impl CookieSessionInner {
path: "/".to_owned(),
domain: None,
secure: true,
http_only: true,
max_age: None,
same_site: None,
}
@@ -388,7 +390,7 @@ impl CookieSessionInner {
let mut cookie = Cookie::new(self.name.clone(), value);
cookie.set_path(self.path.clone());
cookie.set_secure(self.secure);
cookie.set_http_only(true);
cookie.set_http_only(self.http_only);
if let Some(ref domain) = self.domain {
cookie.set_domain(domain.clone());
@@ -532,6 +534,12 @@ impl CookieSessionBackend {
self
}
/// Sets the `http_only` field in the session cookie being built.
pub fn http_only(mut self, value: bool) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().http_only = value;
self
}
/// Sets the `same_site` field in the session cookie being built.
pub fn same_site(mut self, value: SameSite) -> CookieSessionBackend {
Rc::get_mut(&mut self.0).unwrap().same_site = Some(value);

View File

@@ -409,7 +409,7 @@ struct ProcessResponse<S, H> {
_h: PhantomData<H>,
}
#[derive(PartialEq)]
#[derive(PartialEq, Debug)]
enum RunningState {
Running,
Paused,

View File

@@ -1,3 +1,4 @@
use std::cell::RefCell;
use std::cmp::min;
use std::collections::HashMap;
use std::hash::{Hash, Hasher};
@@ -111,9 +112,14 @@ impl ResourceInfo {
U: IntoIterator<Item = I>,
I: AsRef<str>,
{
if let Some(pattern) = self.rmap.named.get(name) {
let path =
pattern.resource_path(elements, &req.path()[..(self.prefix as usize)])?;
let mut path = String::new();
let mut elements = elements.into_iter();
if self
.rmap
.patterns_for(name, &mut path, &mut elements)?
.is_some()
{
if path.starts_with('/') {
let conn = req.connection_info();
Ok(Url::parse(&format!(
@@ -160,12 +166,15 @@ impl ResourceInfo {
}
pub(crate) struct ResourceMap {
root: ResourceDef,
parent: RefCell<Option<Rc<ResourceMap>>>,
named: HashMap<String, ResourceDef>,
patterns: Vec<(ResourceDef, Option<Rc<ResourceMap>>)>,
nested: Vec<Rc<ResourceMap>>,
}
impl ResourceMap {
pub fn has_resource(&self, path: &str) -> bool {
fn has_resource(&self, path: &str) -> bool {
let path = if path.is_empty() { "/" } else { path };
for (pattern, rmap) in &self.patterns {
@@ -179,20 +188,91 @@ impl ResourceMap {
}
false
}
fn patterns_for<U, I>(
&self, name: &str, path: &mut String, elements: &mut U,
) -> Result<Option<()>, UrlGenerationError>
where
U: Iterator<Item = I>,
I: AsRef<str>,
{
if self.pattern_for(name, path, elements)?.is_some() {
Ok(Some(()))
} else {
self.parent_pattern_for(name, path, elements)
}
}
fn pattern_for<U, I>(
&self, name: &str, path: &mut String, elements: &mut U,
) -> Result<Option<()>, UrlGenerationError>
where
U: Iterator<Item = I>,
I: AsRef<str>,
{
if let Some(pattern) = self.named.get(name) {
self.fill_root(path, elements)?;
pattern.resource_path(path, elements)?;
Ok(Some(()))
} else {
for rmap in &self.nested {
if rmap.pattern_for(name, path, elements)?.is_some() {
return Ok(Some(()));
}
}
Ok(None)
}
}
fn fill_root<U, I>(
&self, path: &mut String, elements: &mut U,
) -> Result<(), UrlGenerationError>
where
U: Iterator<Item = I>,
I: AsRef<str>,
{
if let Some(ref parent) = *self.parent.borrow() {
parent.fill_root(path, elements)?;
}
self.root.resource_path(path, elements)
}
fn parent_pattern_for<U, I>(
&self, name: &str, path: &mut String, elements: &mut U,
) -> Result<Option<()>, UrlGenerationError>
where
U: Iterator<Item = I>,
I: AsRef<str>,
{
if let Some(ref parent) = *self.parent.borrow() {
if let Some(pattern) = parent.named.get(name) {
self.fill_root(path, elements)?;
pattern.resource_path(path, elements)?;
Ok(Some(()))
} else {
parent.parent_pattern_for(name, path, elements)
}
} else {
Ok(None)
}
}
}
impl<S: 'static> Default for Router<S> {
fn default() -> Self {
Router::new()
Router::new(ResourceDef::new(""))
}
}
impl<S: 'static> Router<S> {
pub(crate) fn new() -> Self {
pub(crate) fn new(root: ResourceDef) -> Self {
Router {
rmap: Rc::new(ResourceMap {
root,
parent: RefCell::new(None),
named: HashMap::new(),
patterns: Vec::new(),
nested: Vec::new(),
}),
resources: Vec::new(),
patterns: Vec::new(),
@@ -233,6 +313,10 @@ impl<S: 'static> Router<S> {
}
}
pub(crate) fn set_prefix(&mut self, path: &str) {
Rc::get_mut(&mut self.rmap).unwrap().root = ResourceDef::new(path);
}
pub(crate) fn register_resource(&mut self, resource: Resource<S>) {
{
let rmap = Rc::get_mut(&mut self.rmap).unwrap();
@@ -258,6 +342,11 @@ impl<S: 'static> Router<S> {
.unwrap()
.patterns
.push((scope.rdef().clone(), Some(scope.router().rmap.clone())));
Rc::get_mut(&mut self.rmap)
.unwrap()
.nested
.push(scope.router().rmap.clone());
let filters = scope.take_filters();
self.patterns
.push(ResourcePattern::Scope(scope.rdef().clone(), filters));
@@ -286,22 +375,25 @@ impl<S: 'static> Router<S> {
}
pub(crate) fn finish(&mut self) {
if let Some(ref default) = self.default {
for resource in &mut self.resources {
match resource {
ResourceItem::Resource(_) => (),
ResourceItem::Scope(scope) => {
if !scope.has_default_resource() {
for resource in &mut self.resources {
match resource {
ResourceItem::Resource(_) => (),
ResourceItem::Scope(scope) => {
if !scope.has_default_resource() {
if let Some(ref default) = self.default {
scope.default_resource(default.clone());
}
scope.finish()
}
ResourceItem::Handler(hnd) => {
if !hnd.has_default_resource() {
*scope.router().rmap.parent.borrow_mut() = Some(self.rmap.clone());
scope.finish();
}
ResourceItem::Handler(hnd) => {
if !hnd.has_default_resource() {
if let Some(ref default) = self.default {
hnd.default_resource(default.clone());
}
hnd.finish()
}
hnd.finish()
}
}
}
@@ -459,35 +551,38 @@ pub struct ResourceDef {
}
impl ResourceDef {
/// Parse path pattern and create new `Resource` instance.
/// Parse path pattern and create new `ResourceDef` instance.
///
/// Panics if path pattern is wrong.
pub fn new(path: &str) -> Self {
ResourceDef::with_prefix(path, "/", false)
ResourceDef::with_prefix(path, false, !path.is_empty())
}
/// Parse path pattern and create new `Resource` instance.
/// Parse path pattern and create new `ResourceDef` instance.
///
/// Use `prefix` type instead of `static`.
///
/// Panics if path regex pattern is wrong.
pub fn prefix(path: &str) -> Self {
ResourceDef::with_prefix(path, "/", true)
ResourceDef::with_prefix(path, true, !path.is_empty())
}
/// Construct external resource
/// Construct external resource def
///
/// Panics if path pattern is wrong.
pub fn external(path: &str) -> Self {
let mut resource = ResourceDef::with_prefix(path, "/", false);
let mut resource = ResourceDef::with_prefix(path, false, false);
resource.rtp = ResourceType::External;
resource
}
/// Parse path pattern and create new `Resource` instance with custom prefix
pub fn with_prefix(path: &str, prefix: &str, for_prefix: bool) -> Self {
let (pattern, elements, is_dynamic, len) =
ResourceDef::parse(path, prefix, for_prefix);
/// Parse path pattern and create new `ResourceDef` instance with custom prefix
pub fn with_prefix(path: &str, for_prefix: bool, slash: bool) -> Self {
let mut path = path.to_owned();
if slash && !path.starts_with('/') {
path.insert(0, '/');
}
let (pattern, elements, is_dynamic, len) = ResourceDef::parse(&path, for_prefix);
let tp = if is_dynamic {
let re = match Regex::new(&pattern) {
@@ -705,23 +800,21 @@ impl ResourceDef {
/// Build resource path.
pub fn resource_path<U, I>(
&self, elements: U, prefix: &str,
) -> Result<String, UrlGenerationError>
&self, path: &mut String, elements: &mut U,
) -> Result<(), UrlGenerationError>
where
U: IntoIterator<Item = I>,
U: Iterator<Item = I>,
I: AsRef<str>,
{
let mut path = match self.tp {
PatternType::Prefix(ref p) => p.to_owned(),
PatternType::Static(ref p) => p.to_owned(),
match self.tp {
PatternType::Prefix(ref p) => path.push_str(p),
PatternType::Static(ref p) => path.push_str(p),
PatternType::Dynamic(..) => {
let mut path = String::new();
let mut iter = elements.into_iter();
for el in &self.elements {
match *el {
PatternElement::Str(ref s) => path.push_str(s),
PatternElement::Var(_) => {
if let Some(val) = iter.next() {
if let Some(val) = elements.next() {
path.push_str(val.as_ref())
} else {
return Err(UrlGenerationError::NotEnoughElements);
@@ -729,34 +822,18 @@ impl ResourceDef {
}
}
}
path
}
};
if self.rtp != ResourceType::External {
if prefix.ends_with('/') {
if path.starts_with('/') {
path.insert_str(0, &prefix[..prefix.len() - 1]);
} else {
path.insert_str(0, prefix);
}
} else {
if !path.starts_with('/') {
path.insert(0, '/');
}
path.insert_str(0, prefix);
}
}
Ok(path)
Ok(())
}
fn parse(
pattern: &str, prefix: &str, for_prefix: bool,
pattern: &str, for_prefix: bool,
) -> (String, Vec<PatternElement>, bool, usize) {
const DEFAULT_PATTERN: &str = "[^/]+";
let mut re1 = String::from("^") + prefix;
let mut re2 = String::from(prefix);
let mut re1 = String::from("^");
let mut re2 = String::new();
let mut el = String::new();
let mut in_param = false;
let mut in_param_pattern = false;
@@ -766,12 +843,7 @@ impl ResourceDef {
let mut elems = Vec::new();
let mut len = 0;
for (index, ch) in pattern.chars().enumerate() {
// All routes must have a leading slash so its optional to have one
if index == 0 && ch == '/' {
continue;
}
for ch in pattern.chars() {
if in_param {
// In parameter segment: `{....}`
if ch == '}' {
@@ -846,7 +918,7 @@ mod tests {
#[test]
fn test_recognizer10() {
let mut router = Router::<()>::new();
let mut router = Router::<()>::default();
router.register_resource(Resource::new(ResourceDef::new("/name")));
router.register_resource(Resource::new(ResourceDef::new("/name/{val}")));
router.register_resource(Resource::new(ResourceDef::new(
@@ -858,7 +930,7 @@ mod tests {
)));
router.register_resource(Resource::new(ResourceDef::new("/v/{tail:.*}")));
router.register_resource(Resource::new(ResourceDef::new("/test2/{test}.html")));
router.register_resource(Resource::new(ResourceDef::new("{test}/index.html")));
router.register_resource(Resource::new(ResourceDef::new("/{test}/index.html")));
let req = TestRequest::with_uri("/name").finish();
let info = router.recognize(&req, &(), 0);
@@ -909,7 +981,7 @@ mod tests {
#[test]
fn test_recognizer_2() {
let mut router = Router::<()>::new();
let mut router = Router::<()>::default();
router.register_resource(Resource::new(ResourceDef::new("/index.json")));
router.register_resource(Resource::new(ResourceDef::new("/{source}.json")));
@@ -924,7 +996,7 @@ mod tests {
#[test]
fn test_recognizer_with_prefix() {
let mut router = Router::<()>::new();
let mut router = Router::<()>::default();
router.register_resource(Resource::new(ResourceDef::new("/name")));
router.register_resource(Resource::new(ResourceDef::new("/name/{val}")));
@@ -943,7 +1015,7 @@ mod tests {
assert_eq!(&info.match_info()["val"], "value");
// same patterns
let mut router = Router::<()>::new();
let mut router = Router::<()>::default();
router.register_resource(Resource::new(ResourceDef::new("/name")));
router.register_resource(Resource::new(ResourceDef::new("/name/{val}")));
@@ -1049,7 +1121,7 @@ mod tests {
#[test]
fn test_request_resource() {
let mut router = Router::<()>::new();
let mut router = Router::<()>::default();
let mut resource = Resource::new(ResourceDef::new("/index.json"));
resource.name("r1");
router.register_resource(resource);
@@ -1071,7 +1143,7 @@ mod tests {
#[test]
fn test_has_resource() {
let mut router = Router::<()>::new();
let mut router = Router::<()>::default();
let scope = Scope::new("/test").resource("/name", |_| "done");
router.register_scope(scope);
@@ -1088,4 +1160,93 @@ mod tests {
let info = router.default_route_info();
assert!(info.has_resource("/test2/test10/name"));
}
#[test]
fn test_url_for() {
let mut router = Router::<()>::new(ResourceDef::prefix(""));
let mut resource = Resource::new(ResourceDef::new("/tttt"));
resource.name("r0");
router.register_resource(resource);
let scope = Scope::new("/test").resource("/name", |r| {
r.name("r1");
});
router.register_scope(scope);
let scope = Scope::new("/test2")
.nested("/test10", |s| s.resource("/name", |r| r.name("r2")));
router.register_scope(scope);
router.finish();
let req = TestRequest::with_uri("/test").request();
{
let info = router.default_route_info();
let res = info
.url_for(&req, "r0", Vec::<&'static str>::new())
.unwrap();
assert_eq!(res.as_str(), "http://localhost:8080/tttt");
let res = info
.url_for(&req, "r1", Vec::<&'static str>::new())
.unwrap();
assert_eq!(res.as_str(), "http://localhost:8080/test/name");
let res = info
.url_for(&req, "r2", Vec::<&'static str>::new())
.unwrap();
assert_eq!(res.as_str(), "http://localhost:8080/test2/test10/name");
}
let req = TestRequest::with_uri("/test/name").request();
let info = router.recognize(&req, &(), 0);
assert_eq!(info.resource, ResourceId::Normal(1));
let res = info
.url_for(&req, "r0", Vec::<&'static str>::new())
.unwrap();
assert_eq!(res.as_str(), "http://localhost:8080/tttt");
let res = info
.url_for(&req, "r1", Vec::<&'static str>::new())
.unwrap();
assert_eq!(res.as_str(), "http://localhost:8080/test/name");
let res = info
.url_for(&req, "r2", Vec::<&'static str>::new())
.unwrap();
assert_eq!(res.as_str(), "http://localhost:8080/test2/test10/name");
}
#[test]
fn test_url_for_dynamic() {
let mut router = Router::<()>::new(ResourceDef::prefix(""));
let mut resource = Resource::new(ResourceDef::new("/{name}/test/index.{ext}"));
resource.name("r0");
router.register_resource(resource);
let scope = Scope::new("/{name1}").nested("/{name2}", |s| {
s.resource("/{name3}/test/index.{ext}", |r| r.name("r2"))
});
router.register_scope(scope);
router.finish();
let req = TestRequest::with_uri("/test").request();
{
let info = router.default_route_info();
let res = info.url_for(&req, "r0", vec!["sec1", "html"]).unwrap();
assert_eq!(res.as_str(), "http://localhost:8080/sec1/test/index.html");
let res = info
.url_for(&req, "r2", vec!["sec1", "sec2", "sec3", "html"])
.unwrap();
assert_eq!(
res.as_str(),
"http://localhost:8080/sec1/sec2/sec3/test/index.html"
);
}
}
}

View File

@@ -58,11 +58,11 @@ pub struct Scope<S> {
#[cfg_attr(feature = "cargo-clippy", allow(new_without_default_derive))]
impl<S: 'static> Scope<S> {
/// Create a new scope
// TODO: Why is this not exactly the default impl?
pub fn new(path: &str) -> Scope<S> {
let rdef = ResourceDef::prefix(path);
Scope {
rdef: ResourceDef::prefix(path),
router: Rc::new(Router::new()),
rdef: rdef.clone(),
router: Rc::new(Router::new(rdef)),
filters: Vec::new(),
middlewares: Rc::new(Vec::new()),
}
@@ -132,10 +132,11 @@ impl<S: 'static> Scope<S> {
where
F: FnOnce(Scope<T>) -> Scope<T>,
{
let rdef = ResourceDef::prefix(path);
let scope = Scope {
rdef: ResourceDef::prefix(path),
rdef: rdef.clone(),
filters: Vec::new(),
router: Rc::new(Router::new()),
router: Rc::new(Router::new(rdef)),
middlewares: Rc::new(Vec::new()),
};
let mut scope = f(scope);
@@ -178,10 +179,11 @@ impl<S: 'static> Scope<S> {
where
F: FnOnce(Scope<S>) -> Scope<S>,
{
let rdef = ResourceDef::prefix(&path);
let scope = Scope {
rdef: ResourceDef::prefix(&path),
rdef: rdef.clone(),
filters: Vec::new(),
router: Rc::new(Router::new()),
router: Rc::new(Router::new(rdef)),
middlewares: Rc::new(Vec::new()),
};
Rc::get_mut(&mut self.router)
@@ -258,12 +260,7 @@ impl<S: 'static> Scope<S> {
F: FnOnce(&mut Resource<S>) -> R + 'static,
{
// add resource
let pattern = ResourceDef::with_prefix(
path,
if path.is_empty() { "" } else { "/" },
false,
);
let mut resource = Resource::new(pattern);
let mut resource = Resource::new(ResourceDef::new(path));
f(&mut resource);
Rc::get_mut(&mut self.router)

316
src/server/accept.rs Normal file
View File

@@ -0,0 +1,316 @@
use std::sync::mpsc as sync_mpsc;
use std::time::{Duration, Instant};
use std::{io, net, thread};
use futures::{sync::mpsc, Future};
use mio;
use slab::Slab;
use tokio_timer::Delay;
use actix::{msgs::Execute, Arbiter, System};
use super::srv::{ServerCommand, Socket};
use super::worker::Conn;
pub(crate) enum Command {
Pause,
Resume,
Stop,
Worker(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>),
}
struct ServerSocketInfo {
addr: net::SocketAddr,
token: usize,
sock: mio::net::TcpListener,
timeout: Option<Instant>,
}
struct Accept {
poll: mio::Poll,
rx: sync_mpsc::Receiver<Command>,
sockets: Slab<ServerSocketInfo>,
workers: Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)>,
_reg: mio::Registration,
next: usize,
srv: mpsc::UnboundedSender<ServerCommand>,
timer: (mio::Registration, mio::SetReadiness),
}
const CMD: mio::Token = mio::Token(0);
const TIMER: mio::Token = mio::Token(1);
pub(crate) fn start_accept_thread(
socks: Vec<(usize, Socket)>, srv: mpsc::UnboundedSender<ServerCommand>,
workers: Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)>,
) -> (mio::SetReadiness, sync_mpsc::Sender<Command>) {
let (tx, rx) = sync_mpsc::channel();
let (reg, readiness) = mio::Registration::new2();
let sys = System::current();
// start accept thread
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
let _ = thread::Builder::new()
.name("actix-web accept loop".to_owned())
.spawn(move || {
System::set_current(sys);
Accept::new(reg, rx, socks, workers, srv).poll();
});
(readiness, tx)
}
/// This function defines errors that are per-connection. Which basically
/// means that if we get this error from `accept()` system call it means
/// next connection might be ready to be accepted.
///
/// All other errors will incur a timeout before next `accept()` is performed.
/// The timeout is useful to handle resource exhaustion errors like ENFILE
/// and EMFILE. Otherwise, could enter into tight loop.
fn connection_error(e: &io::Error) -> bool {
e.kind() == io::ErrorKind::ConnectionRefused
|| e.kind() == io::ErrorKind::ConnectionAborted
|| e.kind() == io::ErrorKind::ConnectionReset
}
impl Accept {
fn new(
_reg: mio::Registration, rx: sync_mpsc::Receiver<Command>,
socks: Vec<(usize, Socket)>,
workers: Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)>,
srv: mpsc::UnboundedSender<ServerCommand>,
) -> Accept {
// Create a poll instance
let poll = match mio::Poll::new() {
Ok(poll) => poll,
Err(err) => panic!("Can not create mio::Poll: {}", err),
};
// Start listening for incoming commands
if let Err(err) =
poll.register(&_reg, CMD, mio::Ready::readable(), mio::PollOpt::edge())
{
panic!("Can not register Registration: {}", err);
}
// Start accept
let mut sockets = Slab::new();
for (stoken, sock) in socks {
let server = mio::net::TcpListener::from_std(sock.lst)
.expect("Can not create mio::net::TcpListener");
let entry = sockets.vacant_entry();
let token = entry.key();
// Start listening for incoming connections
if let Err(err) = poll.register(
&server,
mio::Token(token + 1000),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
panic!("Can not register io: {}", err);
}
entry.insert(ServerSocketInfo {
token: stoken,
addr: sock.addr,
sock: server,
timeout: None,
});
}
// Timer
let (tm, tmr) = mio::Registration::new2();
if let Err(err) =
poll.register(&tm, TIMER, mio::Ready::readable(), mio::PollOpt::edge())
{
panic!("Can not register Registration: {}", err);
}
Accept {
poll,
rx,
_reg,
sockets,
workers,
srv,
next: 0,
timer: (tm, tmr),
}
}
fn poll(&mut self) {
// Create storage for events
let mut events = mio::Events::with_capacity(128);
loop {
if let Err(err) = self.poll.poll(&mut events, None) {
panic!("Poll error: {}", err);
}
for event in events.iter() {
let token = event.token();
match token {
CMD => if !self.process_cmd() {
return;
},
TIMER => self.process_timer(),
_ => self.accept(token),
}
}
}
}
fn process_timer(&mut self) {
let now = Instant::now();
for (token, info) in self.sockets.iter_mut() {
if let Some(inst) = info.timeout.take() {
if now > inst {
if let Err(err) = self.poll.register(
&info.sock,
mio::Token(token + 1000),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
error!("Can not register server socket {}", err);
} else {
info!("Resume accepting connections on {}", info.addr);
}
} else {
info.timeout = Some(inst);
}
}
}
}
fn process_cmd(&mut self) -> bool {
loop {
match self.rx.try_recv() {
Ok(cmd) => match cmd {
Command::Pause => {
for (_, info) in self.sockets.iter_mut() {
if let Err(err) = self.poll.deregister(&info.sock) {
error!("Can not deregister server socket {}", err);
} else {
info!("Paused accepting connections on {}", info.addr);
}
}
}
Command::Resume => {
for (token, info) in self.sockets.iter() {
if let Err(err) = self.poll.register(
&info.sock,
mio::Token(token + 1000),
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
error!("Can not resume socket accept process: {}", err);
} else {
info!(
"Accepting connections on {} has been resumed",
info.addr
);
}
}
}
Command::Stop => {
for (_, info) in self.sockets.iter() {
let _ = self.poll.deregister(&info.sock);
}
return false;
}
Command::Worker(idx, addr) => {
self.workers.push((idx, addr));
}
},
Err(err) => match err {
sync_mpsc::TryRecvError::Empty => break,
sync_mpsc::TryRecvError::Disconnected => {
for (_, info) in self.sockets.iter() {
let _ = self.poll.deregister(&info.sock);
}
return false;
}
},
}
}
true
}
fn accept(&mut self, token: mio::Token) {
let token = usize::from(token);
if token < 1000 {
return;
}
if let Some(info) = self.sockets.get_mut(token - 1000) {
loop {
match info.sock.accept_std() {
Ok((io, addr)) => {
let mut msg = Conn {
io,
token: info.token,
peer: Some(addr),
http2: false,
};
while !self.workers.is_empty() {
match self.workers[self.next].1.unbounded_send(msg) {
Ok(_) => (),
Err(err) => {
let _ = self.srv.unbounded_send(
ServerCommand::WorkerDied(
self.workers[self.next].0,
),
);
msg = err.into_inner();
self.workers.swap_remove(self.next);
if self.workers.is_empty() {
error!("No workers");
thread::sleep(Duration::from_millis(100));
break;
} else if self.workers.len() <= self.next {
self.next = 0;
}
continue;
}
}
self.next = (self.next + 1) % self.workers.len();
break;
}
}
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => break,
Err(ref e) if connection_error(e) => continue,
Err(e) => {
error!("Error accepting connection: {}", e);
if let Err(err) = self.poll.deregister(&info.sock) {
error!("Can not deregister server socket {}", err);
}
// sleep after error
info.timeout = Some(Instant::now() + Duration::from_millis(500));
let r = self.timer.1.clone();
System::current().arbiter().do_send(Execute::new(
move || -> Result<(), ()> {
Arbiter::spawn(
Delay::new(
Instant::now() + Duration::from_millis(510),
).map_err(|_| ())
.and_then(move |_| {
let _ =
r.set_readiness(mio::Ready::readable());
Ok(())
}),
);
Ok(())
},
));
break;
}
}
}
}
}
}

View File

@@ -155,7 +155,9 @@ where
}
}
if !item.flags.contains(EntryFlags::WRITE_DONE) {
if item.flags.contains(EntryFlags::FINISHED)
&& !item.flags.contains(EntryFlags::WRITE_DONE)
{
match item.stream.poll_completed(false) {
Ok(Async::NotReady) => (),
Ok(Async::Ready(_)) => {

View File

@@ -8,16 +8,18 @@ use modhttp::Response;
use std::rc::Rc;
use std::{cmp, io};
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
use http::{HttpTryFrom, Method, Version};
use super::helpers;
use super::message::Request;
use super::output::{Output, ResponseInfo};
use super::output::{Output, ResponseInfo, ResponseLength};
use super::settings::WorkerSettings;
use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE};
use body::{Binary, Body};
use header::ContentEncoding;
use http::header::{
HeaderValue, CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
};
use httpresponse::HttpResponse;
const CHUNK_SIZE: usize = 16_384;
@@ -92,50 +94,63 @@ impl<H: 'static> Writer for H2Writer<H> {
let mut info = ResponseInfo::new(req.inner.method == Method::HEAD);
self.buffer.for_server(&mut info, &req.inner, msg, encoding);
// 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);
self.settings.set_date(&mut bytes, false);
msg.headers_mut()
.insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap());
}
let body = msg.replace_body(Body::Empty);
match body {
Body::Binary(ref bytes) => {
if bytes.is_empty() {
msg.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
self.flags.insert(Flags::EOF);
} else {
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 => {
self.flags.insert(Flags::EOF);
msg.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
}
_ => (),
}
let mut has_date = false;
let mut resp = Response::new(());
*resp.status_mut() = msg.status();
*resp.version_mut() = Version::HTTP_2;
for (key, value) in msg.headers().iter() {
match *key {
// http2 specific
CONNECTION | TRANSFER_ENCODING => continue,
CONTENT_ENCODING => if encoding != ContentEncoding::Identity {
continue;
},
CONTENT_LENGTH => match info.length {
ResponseLength::None => (),
_ => continue,
},
DATE => has_date = true,
_ => (),
}
resp.headers_mut().insert(key, value.clone());
}
// set date header
if !has_date {
let mut bytes = BytesMut::with_capacity(29);
self.settings.set_date(&mut bytes, false);
resp.headers_mut()
.insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap());
}
// content length
match info.length {
ResponseLength::Zero => {
resp.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
self.flags.insert(Flags::EOF);
}
ResponseLength::Length(len) => {
let mut val = BytesMut::new();
helpers::convert_usize(len, &mut val);
let l = val.len();
resp.headers_mut().insert(
CONTENT_LENGTH,
HeaderValue::try_from(val.split_to(l - 2).freeze()).unwrap(),
);
}
ResponseLength::Length64(len) => {
let l = format!("{}", len);
resp.headers_mut()
.insert(CONTENT_LENGTH, HeaderValue::try_from(l.as_str()).unwrap());
}
_ => (),
}
if let Some(ce) = info.content_encoding {
resp.headers_mut()
.insert(CONTENT_ENCODING, HeaderValue::try_from(ce).unwrap());
}
match self
.respond
.send_response(resp, self.flags.contains(Flags::EOF))
@@ -146,6 +161,7 @@ impl<H: 'static> Writer for H2Writer<H> {
trace!("Response: {:?}", msg);
let body = msg.replace_body(Body::Empty);
if let Body::Binary(bytes) = body {
if bytes.is_empty() {
Ok(WriterState::Done)
@@ -229,14 +245,18 @@ impl<H: 'static> Writer for H2Writer<H> {
let cap = cmp::min(self.buffer.len(), CHUNK_SIZE);
stream.reserve_capacity(cap);
} else {
if eof {
stream.reserve_capacity(0);
continue;
}
self.flags.remove(Flags::RESERVED);
return Ok(Async::NotReady);
return Ok(Async::Ready(()));
}
}
Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)),
}
}
}
Ok(Async::NotReady)
Ok(Async::Ready(()))
}
}

View File

@@ -1,14 +1,11 @@
use std::io::{Read, Write};
use std::{cmp, io};
use std::io::{self, Write};
#[cfg(feature = "brotli")]
use brotli2::write::BrotliDecoder;
use bytes::{BufMut, Bytes, BytesMut};
use bytes::{Bytes, BytesMut};
use error::PayloadError;
#[cfg(feature = "flate2")]
use flate2::read::GzDecoder;
#[cfg(feature = "flate2")]
use flate2::write::DeflateDecoder;
use flate2::write::{DeflateDecoder, GzDecoder};
use header::ContentEncoding;
use http::header::{HeaderMap, CONTENT_ENCODING};
use payload::{PayloadSender, PayloadStatus, PayloadWriter};
@@ -144,46 +141,12 @@ pub(crate) enum Decoder {
#[cfg(feature = "flate2")]
Deflate(Box<DeflateDecoder<Writer>>),
#[cfg(feature = "flate2")]
Gzip(Option<Box<GzDecoder<Wrapper>>>),
Gzip(Box<GzDecoder<Writer>>),
#[cfg(feature = "brotli")]
Br(Box<BrotliDecoder<Writer>>),
Identity,
}
// should go after write::GzDecoder get implemented
#[derive(Debug)]
pub(crate) struct Wrapper {
pub buf: BytesMut,
pub eof: bool,
}
impl io::Read for Wrapper {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let len = cmp::min(buf.len(), self.buf.len());
buf[..len].copy_from_slice(&self.buf[..len]);
self.buf.split_to(len);
if len == 0 {
if self.eof {
Ok(0)
} else {
Err(io::Error::new(io::ErrorKind::WouldBlock, ""))
}
} else {
Ok(len)
}
}
}
impl io::Write for Wrapper {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.buf.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
pub(crate) struct Writer {
buf: BytesMut,
}
@@ -212,12 +175,11 @@ impl io::Write for Writer {
/// Payload stream with decompression support
pub(crate) struct PayloadStream {
decoder: Decoder,
dst: BytesMut,
}
impl PayloadStream {
pub fn new(enc: ContentEncoding) -> PayloadStream {
let dec = match enc {
let decoder = match enc {
#[cfg(feature = "brotli")]
ContentEncoding::Br => {
Decoder::Br(Box::new(BrotliDecoder::new(Writer::new())))
@@ -227,13 +189,12 @@ impl PayloadStream {
Decoder::Deflate(Box::new(DeflateDecoder::new(Writer::new())))
}
#[cfg(feature = "flate2")]
ContentEncoding::Gzip => Decoder::Gzip(None),
ContentEncoding::Gzip => {
Decoder::Gzip(Box::new(GzDecoder::new(Writer::new())))
}
_ => Decoder::Identity,
};
PayloadStream {
decoder: dec,
dst: BytesMut::new(),
}
PayloadStream { decoder }
}
}
@@ -253,22 +214,17 @@ impl PayloadStream {
Err(e) => Err(e),
},
#[cfg(feature = "flate2")]
Decoder::Gzip(ref mut decoder) => {
if let Some(ref mut decoder) = *decoder {
decoder.as_mut().get_mut().eof = true;
self.dst.reserve(8192);
match decoder.read(unsafe { self.dst.bytes_mut() }) {
Ok(n) => {
unsafe { self.dst.advance_mut(n) };
return Ok(Some(self.dst.take().freeze()));
}
Err(e) => return Err(e),
Decoder::Gzip(ref mut decoder) => match decoder.try_finish() {
Ok(_) => {
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
} else {
Ok(None)
}
}
Err(e) => Err(e),
},
#[cfg(feature = "flate2")]
Decoder::Deflate(ref mut decoder) => match decoder.try_finish() {
Ok(_) => {
@@ -301,43 +257,18 @@ impl PayloadStream {
Err(e) => Err(e),
},
#[cfg(feature = "flate2")]
Decoder::Gzip(ref mut decoder) => {
if decoder.is_none() {
*decoder = Some(Box::new(GzDecoder::new(Wrapper {
buf: BytesMut::from(data),
eof: false,
})));
} else {
let _ = decoder.as_mut().unwrap().write(&data);
}
loop {
self.dst.reserve(8192);
match decoder
.as_mut()
.as_mut()
.unwrap()
.read(unsafe { self.dst.bytes_mut() })
{
Ok(n) => {
if n != 0 {
unsafe { self.dst.advance_mut(n) };
}
if n == 0 {
return Ok(Some(self.dst.take().freeze()));
}
}
Err(e) => {
if e.kind() == io::ErrorKind::WouldBlock
&& !self.dst.is_empty()
{
return Ok(Some(self.dst.take().freeze()));
}
return Err(e);
}
Decoder::Gzip(ref mut decoder) => match decoder.write_all(&data) {
Ok(_) => {
decoder.flush()?;
let b = decoder.get_mut().take();
if !b.is_empty() {
Ok(Some(b))
} else {
Ok(None)
}
}
}
Err(e) => Err(e),
},
#[cfg(feature = "flate2")]
Decoder::Deflate(ref mut decoder) => match decoder.write_all(&data) {
Ok(_) => {

View File

@@ -7,6 +7,7 @@ use futures::{Async, Poll};
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_tcp::TcpStream;
pub(crate) mod accept;
mod channel;
mod error;
pub(crate) mod h1;
@@ -310,3 +311,46 @@ impl IoStream for TlsStream<TcpStream> {
self.get_mut().get_mut().set_linger(dur)
}
}
#[cfg(feature = "rust-tls")]
use rustls::{ClientSession, ServerSession};
#[cfg(feature = "rust-tls")]
use tokio_rustls::TlsStream as RustlsStream;
#[cfg(feature = "rust-tls")]
impl IoStream for RustlsStream<TcpStream, ClientSession> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = <Self as AsyncWrite>::shutdown(self);
Ok(())
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().0.set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().0.set_linger(dur)
}
}
#[cfg(feature = "rust-tls")]
impl IoStream for RustlsStream<TcpStream, ServerSession> {
#[inline]
fn shutdown(&mut self, _how: Shutdown) -> io::Result<()> {
let _ = <Self as AsyncWrite>::shutdown(self);
Ok(())
}
#[inline]
fn set_nodelay(&mut self, nodelay: bool) -> io::Result<()> {
self.get_mut().0.set_nodelay(nodelay)
}
#[inline]
fn set_linger(&mut self, dur: Option<time::Duration>) -> io::Result<()> {
self.get_mut().0.set_linger(dur)
}
}

View File

@@ -1,7 +1,7 @@
use std::rc::Rc;
use std::sync::{mpsc as sync_mpsc, Arc};
use std::time::Duration;
use std::{io, net, thread};
use std::{io, net};
use actix::{
fut, signal, Actor, ActorFuture, Addr, Arbiter, AsyncContext, Context, Handler,
@@ -22,6 +22,10 @@ use native_tls::TlsAcceptor;
#[cfg(feature = "alpn")]
use openssl::ssl::{AlpnError, SslAcceptorBuilder};
#[cfg(feature = "rust-tls")]
use rustls::ServerConfig;
use super::accept::{start_accept_thread, Command};
use super::channel::{HttpChannel, WrapperStream};
use super::settings::{ServerSettings, WorkerSettings};
use super::worker::{Conn, SocketInfo, StopWorker, StreamHandlerType, Worker};
@@ -56,7 +60,11 @@ where
#[cfg_attr(feature = "cargo-clippy", allow(type_complexity))]
workers: Vec<(usize, Addr<Worker<H::Handler>>)>,
sockets: Vec<Socket>,
accept: Vec<(mio::SetReadiness, sync_mpsc::Sender<Command>)>,
accept: Option<(
mio::SetReadiness,
sync_mpsc::Sender<Command>,
Slab<SocketInfo>,
)>,
exit: bool,
shutdown_timeout: u16,
signals: Option<Addr<signal::ProcessSignals>>,
@@ -64,8 +72,8 @@ where
no_signals: bool,
}
enum ServerCommand {
WorkerDied(usize, Slab<SocketInfo>),
pub(crate) enum ServerCommand {
WorkerDied(usize),
}
impl<H> Actor for HttpServer<H>
@@ -75,10 +83,10 @@ where
type Context = Context<Self>;
}
struct Socket {
lst: net::TcpListener,
addr: net::SocketAddr,
tp: StreamHandlerType,
pub(crate) struct Socket {
pub lst: net::TcpListener,
pub addr: net::SocketAddr,
pub tp: StreamHandlerType,
}
impl<H> HttpServer<H>
@@ -102,7 +110,7 @@ where
factory: Arc::new(f),
workers: Vec::new(),
sockets: Vec::new(),
accept: Vec::new(),
accept: None,
exit: false,
shutdown_timeout: 30,
signals: None,
@@ -121,7 +129,10 @@ where
}
#[doc(hidden)]
#[deprecated(since = "0.6.0", note = "please use `HttpServer::workers()` instead")]
#[deprecated(
since = "0.6.0",
note = "please use `HttpServer::workers()` instead"
)]
pub fn threads(self, num: usize) -> Self {
self.workers(num)
}
@@ -265,6 +276,26 @@ where
Ok(self)
}
#[cfg(feature = "rust-tls")]
/// Use listener for accepting incoming tls connection requests
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn listen_rustls(
mut self, lst: net::TcpListener, mut builder: ServerConfig,
) -> io::Result<Self> {
// alpn support
if !self.no_http2 {
builder.set_protocols(&vec!["h2".to_string(), "http/1.1".to_string()]);
}
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
addr,
lst,
tp: StreamHandlerType::Rustls(Arc::new(builder)),
});
Ok(self)
}
fn bind2<S: net::ToSocketAddrs>(&mut self, addr: S) -> io::Result<Vec<Socket>> {
let mut err = None;
let mut succ = false;
@@ -343,6 +374,27 @@ where
Ok(self)
}
#[cfg(feature = "rust-tls")]
/// Start listening for incoming tls connections.
///
/// This method sets alpn protocols to "h2" and "http/1.1"
pub fn bind_rustls<S: net::ToSocketAddrs>(
mut self, addr: S, mut builder: ServerConfig,
) -> io::Result<Self> {
// alpn support
if !self.no_http2 {
builder.set_protocols(&vec!["h2".to_string(), "http/1.1".to_string()]);
}
let builder = Arc::new(builder);
let sockets = self.bind2(addr)?;
self.sockets.extend(sockets.into_iter().map(move |mut s| {
s.tp = StreamHandlerType::Rustls(builder.clone());
s
}));
Ok(self)
}
fn start_workers(
&mut self, settings: &ServerSettings, sockets: &Slab<SocketInfo>,
) -> Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)> {
@@ -432,17 +484,12 @@ impl<H: IntoHttpHandler> HttpServer<H> {
let settings = ServerSettings::new(Some(addrs[0].1.addr), &self.host, false);
let workers = self.start_workers(&settings, &socks);
// start acceptors threads
for (token, sock) in addrs {
// start accept thread
for (_, sock) in &addrs {
info!("Starting server on http://{}", sock.addr);
self.accept.push(start_accept_thread(
token,
sock,
tx.clone(),
socks.clone(),
workers.clone(),
));
}
let (r, cmd) = start_accept_thread(addrs, tx.clone(), workers.clone());
self.accept = Some((r, cmd, socks));
// start http server actor
let signals = self.subscribe_to_signals();
@@ -487,7 +534,8 @@ impl<H: IntoHttpHandler> HttpServer<H> {
#[doc(hidden)]
#[cfg(feature = "tls")]
#[deprecated(
since = "0.6.0", note = "please use `actix_web::HttpServer::bind_tls` instead"
since = "0.6.0",
note = "please use `actix_web::HttpServer::bind_tls` instead"
)]
impl<H: IntoHttpHandler> HttpServer<H> {
/// Start listening for incoming tls connections.
@@ -506,7 +554,8 @@ impl<H: IntoHttpHandler> HttpServer<H> {
#[doc(hidden)]
#[cfg(feature = "alpn")]
#[deprecated(
since = "0.6.0", note = "please use `actix_web::HttpServer::bind_ssl` instead"
since = "0.6.0",
note = "please use `actix_web::HttpServer::bind_ssl` instead"
)]
impl<H: IntoHttpHandler> HttpServer<H> {
/// Start listening for incoming tls connections.
@@ -615,7 +664,7 @@ impl<H: IntoHttpHandler> StreamHandler<ServerCommand, ()> for HttpServer<H> {
fn handle(&mut self, msg: ServerCommand, _: &mut Context<Self>) {
match msg {
ServerCommand::WorkerDied(idx, socks) => {
ServerCommand::WorkerDied(idx) => {
let mut found = false;
for i in 0..self.workers.len() {
if self.workers[i].0 == idx {
@@ -643,6 +692,7 @@ impl<H: IntoHttpHandler> StreamHandler<ServerCommand, ()> for HttpServer<H> {
let ka = self.keep_alive;
let factory = Arc::clone(&self.factory);
let host = self.host.clone();
let socks = self.accept.as_ref().unwrap().2.clone();
let addr = socks[0].addr;
let addr = Arbiter::start(move |ctx: &mut Context<_>| {
@@ -652,7 +702,7 @@ impl<H: IntoHttpHandler> StreamHandler<ServerCommand, ()> for HttpServer<H> {
ctx.add_message_stream(rx);
Worker::new(apps, socks, ka, settings)
});
for item in &self.accept {
if let Some(ref item) = &self.accept {
let _ = item.1.send(Command::Worker(new_idx, tx.clone()));
let _ = item.0.set_readiness(mio::Ready::readable());
}
@@ -759,181 +809,6 @@ impl<H: IntoHttpHandler> Handler<StopServer> for HttpServer<H> {
}
}
enum Command {
Pause,
Resume,
Stop,
Worker(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>),
}
fn start_accept_thread(
token: usize, sock: Socket, srv: mpsc::UnboundedSender<ServerCommand>,
socks: Slab<SocketInfo>,
mut workers: Vec<(usize, mpsc::UnboundedSender<Conn<net::TcpStream>>)>,
) -> (mio::SetReadiness, sync_mpsc::Sender<Command>) {
let (tx, rx) = sync_mpsc::channel();
let (reg, readiness) = mio::Registration::new2();
// start accept thread
#[cfg_attr(feature = "cargo-clippy", allow(cyclomatic_complexity))]
let _ = thread::Builder::new()
.name(format!("Accept on {}", sock.addr))
.spawn(move || {
const SRV: mio::Token = mio::Token(0);
const CMD: mio::Token = mio::Token(1);
let addr = sock.addr;
let mut server = Some(
mio::net::TcpListener::from_std(sock.lst)
.expect("Can not create mio::net::TcpListener"),
);
// Create a poll instance
let poll = match mio::Poll::new() {
Ok(poll) => poll,
Err(err) => panic!("Can not create mio::Poll: {}", err),
};
// Start listening for incoming connections
if let Some(ref srv) = server {
if let Err(err) =
poll.register(srv, SRV, mio::Ready::readable(), mio::PollOpt::edge())
{
panic!("Can not register io: {}", err);
}
}
// Start listening for incoming commands
if let Err(err) =
poll.register(&reg, CMD, mio::Ready::readable(), mio::PollOpt::edge())
{
panic!("Can not register Registration: {}", err);
}
// Create storage for events
let mut events = mio::Events::with_capacity(128);
// Sleep on error
let sleep = Duration::from_millis(100);
let mut next = 0;
loop {
if let Err(err) = poll.poll(&mut events, None) {
panic!("Poll error: {}", err);
}
for event in events.iter() {
match event.token() {
SRV => if let Some(ref server) = server {
loop {
match server.accept_std() {
Ok((io, addr)) => {
let mut msg = Conn {
io,
token,
peer: Some(addr),
http2: false,
};
while !workers.is_empty() {
match workers[next].1.unbounded_send(msg) {
Ok(_) => (),
Err(err) => {
let _ = srv.unbounded_send(
ServerCommand::WorkerDied(
workers[next].0,
socks.clone(),
),
);
msg = err.into_inner();
workers.swap_remove(next);
if workers.is_empty() {
error!("No workers");
thread::sleep(sleep);
break;
} else if workers.len() <= next {
next = 0;
}
continue;
}
}
next = (next + 1) % workers.len();
break;
}
}
Err(ref e)
if e.kind() == io::ErrorKind::WouldBlock =>
{
break
}
Err(ref e) if connection_error(e) => continue,
Err(e) => {
error!("Error accepting connection: {}", e);
// sleep after error
thread::sleep(sleep);
break;
}
}
}
},
CMD => match rx.try_recv() {
Ok(cmd) => match cmd {
Command::Pause => if let Some(ref server) = server {
if let Err(err) = poll.deregister(server) {
error!(
"Can not deregister server socket {}",
err
);
} else {
info!(
"Paused accepting connections on {}",
addr
);
}
},
Command::Resume => {
if let Some(ref server) = server {
if let Err(err) = poll.register(
server,
SRV,
mio::Ready::readable(),
mio::PollOpt::edge(),
) {
error!("Can not resume socket accept process: {}", err);
} else {
info!("Accepting connections on {} has been resumed",
addr);
}
}
}
Command::Stop => {
if let Some(server) = server.take() {
let _ = poll.deregister(&server);
}
return;
}
Command::Worker(idx, addr) => {
workers.push((idx, addr));
}
},
Err(err) => match err {
sync_mpsc::TryRecvError::Empty => (),
sync_mpsc::TryRecvError::Disconnected => {
if let Some(server) = server.take() {
let _ = poll.deregister(&server);
}
return;
}
},
},
_ => unreachable!(),
}
}
}
});
(readiness, tx)
}
fn create_tcp_listener(
addr: net::SocketAddr, backlog: i32,
) -> io::Result<net::TcpListener> {
@@ -945,16 +820,3 @@ fn create_tcp_listener(
builder.bind(addr)?;
Ok(builder.listen(backlog)?)
}
/// This function defines errors that are per-connection. Which basically
/// means that if we get this error from `accept()` system call it means
/// next connection might be ready to be accepted.
///
/// All other errors will incur a timeout before next `accept()` is performed.
/// The timeout is useful to handle resource exhaustion errors like ENFILE
/// and EMFILE. Otherwise, could enter into tight loop.
fn connection_error(e: &io::Error) -> bool {
e.kind() == io::ErrorKind::ConnectionRefused
|| e.kind() == io::ErrorKind::ConnectionAborted
|| e.kind() == io::ErrorKind::ConnectionReset
}

View File

@@ -8,7 +8,7 @@ use tokio::executor::current_thread;
use tokio_reactor::Handle;
use tokio_tcp::TcpStream;
#[cfg(any(feature = "tls", feature = "alpn"))]
#[cfg(any(feature = "tls", feature = "alpn", feature = "rust-tls"))]
use futures::future;
#[cfg(feature = "tls")]
@@ -21,6 +21,13 @@ use openssl::ssl::SslAcceptor;
#[cfg(feature = "alpn")]
use tokio_openssl::SslAcceptorExt;
#[cfg(feature = "rust-tls")]
use rustls::{ServerConfig, Session};
#[cfg(feature = "rust-tls")]
use std::sync::Arc;
#[cfg(feature = "rust-tls")]
use tokio_rustls::ServerConfigExt;
use actix::msgs::StopArbiter;
use actix::{Actor, Arbiter, AsyncContext, Context, Handler, Message, Response};
@@ -170,6 +177,8 @@ pub(crate) enum StreamHandlerType {
Tls(TlsAcceptor),
#[cfg(feature = "alpn")]
Alpn(SslAcceptor),
#[cfg(feature = "rust-tls")]
Rustls(Arc<ServerConfig>),
}
impl StreamHandlerType {
@@ -237,6 +246,36 @@ impl StreamHandlerType {
},
));
}
#[cfg(feature = "rust-tls")]
StreamHandlerType::Rustls(ref acceptor) => {
let Conn { io, peer, .. } = msg;
let _ = io.set_nodelay(true);
let io = TcpStream::from_std(io, &Handle::default())
.expect("failed to associate TCP stream");
current_thread::spawn(ServerConfigExt::accept_async(acceptor, io).then(
move |res| {
match res {
Ok(io) => {
let http2 = if let Some(p) =
io.get_ref().1.get_alpn_protocol()
{
p.len() == 2 && &p == &"h2"
} else {
false
};
current_thread::spawn(HttpChannel::new(
h, io, peer, http2,
));
}
Err(err) => {
trace!("Error during handling tls connection: {}", err)
}
};
future::result(Ok(()))
},
));
}
}
}
@@ -247,6 +286,8 @@ impl StreamHandlerType {
StreamHandlerType::Tls(_) => "https",
#[cfg(feature = "alpn")]
StreamHandlerType::Alpn(_) => "https",
#[cfg(feature = "rust-tls")]
StreamHandlerType::Rustls(_) => "https",
}
}
}

View File

@@ -15,6 +15,8 @@ use tokio::runtime::current_thread::Runtime;
#[cfg(feature = "alpn")]
use openssl::ssl::SslAcceptorBuilder;
#[cfg(all(feature = "rust-tls"))]
use rustls::ServerConfig;
use application::{App, HttpApplication};
use body::Binary;
@@ -140,7 +142,17 @@ impl TestServer {
builder.set_verify(SslVerifyMode::NONE);
ClientConnector::with_connector(builder.build()).start()
}
#[cfg(not(feature = "alpn"))]
#[cfg(all(feature = "rust-tls", not(feature = "alpn")))]
{
use rustls::ClientConfig;
use std::fs::File;
use std::io::BufReader;
let mut config = ClientConfig::new();
let pem_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap());
config.root_store.add_pem_file(pem_file).unwrap();
ClientConnector::with_connector(config).start()
}
#[cfg(not(any(feature = "alpn", feature = "rust-tls")))]
{
ClientConnector::default().start()
}
@@ -165,16 +177,16 @@ impl TestServer {
pub fn url(&self, uri: &str) -> String {
if uri.starts_with('/') {
format!(
"{}://{}{}",
"{}://localhost:{}{}",
if self.ssl { "https" } else { "http" },
self.addr,
self.addr.port(),
uri
)
} else {
format!(
"{}://{}/{}",
"{}://localhost:{}/{}",
if self.ssl { "https" } else { "http" },
self.addr,
self.addr.port(),
uri
)
}
@@ -193,13 +205,20 @@ impl TestServer {
self.rt.block_on(fut)
}
/// Connect to websocket server
/// Connect to websocket server at a given path
pub fn ws_at(
&mut self, path: &str,
) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> {
let url = self.url(path);
self.rt
.block_on(ws::Client::with_connector(url, self.conn.clone()).connect())
}
/// Connect to a websocket server
pub fn ws(
&mut self,
) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> {
let url = self.url("/");
self.rt
.block_on(ws::Client::with_connector(url, self.conn.clone()).connect())
self.ws_at("/")
}
/// Create `GET` request
@@ -241,6 +260,8 @@ pub struct TestServerBuilder<S> {
state: Box<Fn() -> S + Sync + Send + 'static>,
#[cfg(feature = "alpn")]
ssl: Option<SslAcceptorBuilder>,
#[cfg(feature = "rust-tls")]
rust_ssl: Option<ServerConfig>,
}
impl<S: 'static> TestServerBuilder<S> {
@@ -253,6 +274,8 @@ impl<S: 'static> TestServerBuilder<S> {
state: Box::new(state),
#[cfg(feature = "alpn")]
ssl: None,
#[cfg(feature = "rust-tls")]
rust_ssl: None,
}
}
@@ -263,6 +286,13 @@ impl<S: 'static> TestServerBuilder<S> {
self
}
#[cfg(feature = "rust-tls")]
/// Create rust tls server
pub fn rustls(mut self, ssl: ServerConfig) -> Self {
self.rust_ssl = Some(ssl);
self
}
#[allow(unused_mut)]
/// Configure test application and run test server
pub fn start<F>(mut self, config: F) -> TestServer
@@ -271,41 +301,56 @@ impl<S: 'static> TestServerBuilder<S> {
{
let (tx, rx) = mpsc::channel();
let mut has_ssl = false;
#[cfg(feature = "alpn")]
let ssl = self.ssl.is_some();
#[cfg(not(feature = "alpn"))]
let ssl = false;
{
has_ssl = has_ssl || self.ssl.is_some();
}
#[cfg(feature = "rust-tls")]
{
has_ssl = has_ssl || self.rust_ssl.is_some();
}
// run server in separate thread
thread::spawn(move || {
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
let local_addr = tcp.local_addr().unwrap();
let addr = TestServer::unused_addr();
let sys = System::new("actix-test-server");
let state = self.state;
let srv = HttpServer::new(move || {
let mut srv = HttpServer::new(move || {
let mut app = TestApp::new(state());
config(&mut app);
vec![app]
}).workers(1)
.disable_signals();
tx.send((System::current(), local_addr, TestServer::get_conn()))
tx.send((System::current(), addr, TestServer::get_conn()))
.unwrap();
#[cfg(feature = "alpn")]
{
let ssl = self.ssl.take();
if let Some(ssl) = ssl {
srv.listen_ssl(tcp, ssl).unwrap().start();
} else {
srv.listen(tcp).start();
let tcp = net::TcpListener::bind(addr).unwrap();
srv = srv.listen_ssl(tcp, ssl).unwrap();
}
}
#[cfg(not(feature = "alpn"))]
#[cfg(feature = "rust-tls")]
{
srv.listen(tcp).start();
let ssl = self.rust_ssl.take();
if let Some(ssl) = ssl {
let tcp = net::TcpListener::bind(addr).unwrap();
srv = srv.listen_rustls(tcp, ssl).unwrap();
}
}
if !has_ssl {
let tcp = net::TcpListener::bind(addr).unwrap();
srv = srv.listen(tcp);
}
srv.start();
sys.run();
});
@@ -313,8 +358,8 @@ impl<S: 'static> TestServerBuilder<S> {
System::set_current(system);
TestServer {
addr,
ssl,
conn,
ssl: has_ssl,
rt: Runtime::new().unwrap(),
}
}
@@ -549,7 +594,7 @@ impl<S: 'static> TestRequest<S> {
payload,
prefix,
} = self;
let router = Router::<()>::new();
let router = Router::<()>::default();
let pool = RequestPool::pool(ServerSettings::default());
let mut req = RequestPool::get(pool);

View File

@@ -1,31 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFPjCCAyYCCQDvLYiYD+jqeTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJV
UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMRAwDgYDVQQKDAdDb21wYW55MQww
CgYDVQQLDANPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xODAxMjUx
NzQ2MDFaFw0xOTAxMjUxNzQ2MDFaMGExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD
QTELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBhbnkxDDAKBgNVBAsMA09yZzEY
MBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A
MIICCgKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEPn8k1
sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+MIK5U
NLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM54jXy
voLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZWLWr
odGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAkoqND
xdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNliJDmA
CRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6/stI
yFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuDYX2U
UuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nPwPTO
vRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA69un
CEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEAATAN
BgkqhkiG9w0BAQsFAAOCAgEApavsgsn7SpPHfhDSN5iZs1ILZQRewJg0Bty0xPfk
3tynSW6bNH3nSaKbpsdmxxomthNSQgD2heOq1By9YzeOoNR+7Pk3s4FkASnf3ToI
JNTUasBFFfaCG96s4Yvs8KiWS/k84yaWuU8c3Wb1jXs5Rv1qE1Uvuwat1DSGXSoD
JNluuIkCsC4kWkyq5pWCGQrabWPRTWsHwC3PTcwSRBaFgYLJaR72SloHB1ot02zL
d2age9dmFRFLLCBzP+D7RojBvL37qS/HR+rQ4SoQwiVc/JzaeqSe7ZbvEH9sZYEu
ALowJzgbwro7oZflwTWunSeSGDSltkqKjvWvZI61pwfHKDahUTmZ5h2y67FuGEaC
CIOUI8dSVSPKITxaq3JL4ze2e9/0Lt7hj19YK2uUmtMAW5Tirz4Yx5lyGH9U8Wur
y/X8VPxTc4A9TMlJgkyz0hqvhbPOT/zSWB10zXh0glKAsSBryAOEDxV1UygmSir7
YV8Qaq+oyKUTMc1MFq5vZ07M51EPaietn85t8V2Y+k/8XYltRp32NxsypxAJuyxh
g/ko6RVTrWa1sMvz/F9LFqAdKiK5eM96lh9IU4xiLg4ob8aS/GRAA8oIFkZFhLrt
tOwjIUPmEPyHWFi8dLpNuQKYalLYhuwZftG/9xV+wqhKGZO9iPrpHSYBRTap8w2y
1QU=
MIIFXTCCA0WgAwIBAgIJAJ3tqfd0MLLNMA0GCSqGSIb3DQEBCwUAMGExCzAJBgNV
BAYTAlVTMQswCQYDVQQIDAJDRjELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBh
bnkxDDAKBgNVBAsMA09yZzEYMBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMB4XDTE4
MDcyOTE4MDgzNFoXDTE5MDcyOTE4MDgzNFowYTELMAkGA1UEBhMCVVMxCzAJBgNV
BAgMAkNGMQswCQYDVQQHDAJTRjEQMA4GA1UECgwHQ29tcGFueTEMMAoGA1UECwwD
T3JnMRgwFgYDVQQDDA93d3cuZXhhbXBsZS5jb20wggIiMA0GCSqGSIb3DQEBAQUA
A4ICDwAwggIKAoICAQDZbMgDYilVH1Nv0QWEhOXG6ETmtjZrdLqrNg3NBWBIWCDF
cQ+fyTWxARx6vkF8A/3zpJyTcfQW8HgG38jw/A61QKaHBxzwq0HlNwY9Hh+Neeuk
L4wgrlQ0uTC7IEMrOJjNN0GPyRQVfVbGa8QcSCpOg85l8GCxLvVwkBH/M5atoMtJ
EzniNfK+gtk3hOL2tBqBCu9NDjhXPnJwNDLtTG1tQaHUJW/r281Wvv9I46H83DkU
05lYtauh0bKh5znCH2KpFmBGqJNRzou3tXZFZzZfaCPBJPZR8j5TjoinehpDtkPh
4CSio0PF2eIFkDKRUbdz/327HgEARJMXx+w1yHpS2JwHFgy5O76i68/Smx8j3DDA
2WIkOYAJFRMH0CBHKdsvUDOGpCgN+xv3whl+N806nCfC4vCkwA+FuB3ko11logng
dvr+y0jIUSU4THF3dMDEXYayF3+WrUlw0cBnUNJdXky85ZP81aBfBsjNSBDx4iL4
e4NhfZRS5oHpHy1t3nYfuttS/oet+Ke5KUpaqNJguSIoeTBSmgzDzL1TJxFLOzUT
2c/A9M69FdvSY0JB4EJX0W9K01Vd0JRNPwsY+/zvFIPama3suKOUTqYcsbwxx9xa
TMDr26cIQcgUAUOKZO43sQGWNzXX3FYVNwczKhkB8UX6hOrBJsEYiau4LGdokQID
AQABoxgwFjAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggIB
AIX+Qb4QRBxHl5X2UjRyLfWVkimtGlwI8P+eJZL3DrHBH/TpqAaCvTf0EbRC32nm
ASDMwIghaMvyrW40QN6V/CWRRi25cXUfsIZr1iHAHK0eZJV8SWooYtt4iNrcUs3g
4OTvDxhNmDyNwV9AXhJsBKf80dCW6/84jItqVAj20/OO4Rkd2tEeI8NomiYBc6a1
hgwvv02myYF5hG/xZ9YSqeroBCZHwGYoJJnSpMPqJsxbCVnx2/U9FzGwcRmNHFCe
0g7EJZd3//8Plza6nkTBjJ/V7JnLqMU+ltx4mAgZO8rfzIr84qZdt0YN33VJQhYq
seuMySxrsuaAoxAmm8IoK9cW4IPzx1JveBQiroNlq5YJGf2UW7BTc3gz6c2tINZi
7ailBVdhlMnDXAf3/9xiiVlRAHOxgZh/7sRrKU7kDEHM4fGoc0YyZBTQKndPYMwO
3Bd82rlQ4sd46XYutTrB+mBYClVrJs+OzbNedTsR61DVNKKsRG4mNPyKSAIgOfM5
XmSvCMPN5JK9U0DsNIV2/SnVsmcklQczT35FLTxl9ntx8ys7ZYK+SppD7XuLfWMq
GT9YMWhlpw0aRDg/aayeeOcnsNBhzAFMcOpQj1t6Fgv4+zbS9BM2bT0hbX86xjkr
E6wWgkuCslMgQlEJ+TM5RhYrI5/rVZQhvmgcob/9gPZv
-----END CERTIFICATE-----

View File

@@ -438,7 +438,7 @@ fn test_default_headers() {
let repr = format!("{:?}", request);
assert!(repr.contains("\"accept-encoding\": \"gzip, deflate\""));
assert!(repr.contains(concat!(
"\"user-agent\": \"Actix-web/",
"\"user-agent\": \"actix-web/",
env!("CARGO_PKG_VERSION"),
"\""
)));

View File

@@ -153,6 +153,62 @@ fn test_shutdown() {
let _ = sys.stop();
}
#[test]
#[cfg(unix)]
fn test_panic() {
let _ = test::TestServer::unused_addr();
let (tx, rx) = mpsc::channel();
thread::spawn(|| {
System::run(move || {
let srv = server::new(|| {
App::new()
.resource("/panic", |r| {
r.method(http::Method::GET).f(|_| -> &'static str {
panic!("error");
});
})
.resource("/", |r| {
r.method(http::Method::GET).f(|_| HttpResponse::Ok())
})
}).workers(1);
let srv = srv.bind("127.0.0.1:0").unwrap();
let addr = srv.addrs()[0];
srv.start();
let _ = tx.send((addr, System::current()));
});
});
let (addr, sys) = rx.recv().unwrap();
System::set_current(sys.clone());
let mut rt = Runtime::new().unwrap();
{
let req = client::ClientRequest::get(format!("http://{}/panic", addr).as_str())
.finish()
.unwrap();
let response = rt.block_on(req.send());
assert!(response.is_err());
}
{
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish()
.unwrap();
let response = rt.block_on(req.send());
assert!(response.is_err());
}
{
let req = client::ClientRequest::get(format!("http://{}/", addr).as_str())
.finish()
.unwrap();
let response = rt.block_on(req.send()).unwrap();
assert!(response.status().is_success());
}
let _ = sys.stop();
}
#[test]
fn test_simple() {
let mut srv = test::TestServer::new(|app| app.handler(|_| HttpResponse::Ok()));

View File

@@ -12,6 +12,8 @@ use rand::Rng;
#[cfg(feature = "alpn")]
extern crate openssl;
#[cfg(feature = "rust-tls")]
extern crate rustls;
use actix::prelude::*;
use actix_web::*;
@@ -62,6 +64,46 @@ fn test_simple() {
);
}
// websocket resource helper function
fn start_ws_resource(req: &HttpRequest) -> Result<HttpResponse, Error> {
ws::start(req, Ws)
}
#[test]
fn test_simple_path() {
const PATH:&str = "/v1/ws/";
// Create a websocket at a specific path.
let mut srv = test::TestServer::new(|app| {
app.resource(PATH, |r| r.route().f(start_ws_resource));
});
// fetch the sockets for the resource at a given path.
let (reader, mut writer) = srv.ws_at(PATH).unwrap();
writer.text("text");
let (item, reader) = srv.execute(reader.into_future()).unwrap();
assert_eq!(item, Some(ws::Message::Text("text".to_owned())));
writer.binary(b"text".as_ref());
let (item, reader) = srv.execute(reader.into_future()).unwrap();
assert_eq!(
item,
Some(ws::Message::Binary(Bytes::from_static(b"text").into()))
);
writer.ping("ping");
let (item, reader) = srv.execute(reader.into_future()).unwrap();
assert_eq!(item, Some(ws::Message::Pong("ping".to_owned())));
writer.close(Some(ws::CloseCode::Normal.into()));
let (item, _) = srv.execute(reader.into_future()).unwrap();
assert_eq!(
item,
Some(ws::Message::Close(Some(ws::CloseCode::Normal.into())))
);
}
#[test]
fn test_empty_close_code() {
let mut srv = test::TestServer::new(|app| app.handler(|req| ws::start(req, Ws)));
@@ -272,3 +314,42 @@ fn test_ws_server_ssl() {
assert_eq!(item, data);
}
}
#[test]
#[cfg(feature = "rust-tls")]
fn test_ws_server_rust_tls() {
extern crate rustls;
use rustls::internal::pemfile::{certs, rsa_private_keys};
use rustls::{NoClientAuth, ServerConfig};
use std::fs::File;
use std::io::BufReader;
// load ssl keys
let mut config = ServerConfig::new(NoClientAuth::new());
let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap());
let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap());
let cert_chain = certs(cert_file).unwrap();
let mut keys = rsa_private_keys(key_file).unwrap();
config.set_single_cert(cert_chain, keys.remove(0)).unwrap();
let mut srv = test::TestServer::build().rustls(config).start(|app| {
app.handler(|req| {
ws::start(
req,
Ws2 {
count: 0,
bin: false,
},
)
})
});
let (mut reader, _writer) = srv.ws().unwrap();
let data = Some(ws::Message::Text("0".repeat(65_536)));
for _ in 0..10_000 {
let (item, r) = srv.execute(reader.into_future()).unwrap();
reader = r;
assert_eq!(item, data);
}
}