mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-16 06:35:46 +02:00
Compare commits
40 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
0da3fdcb09 | ||
|
a5f80a25ff | ||
|
6d9a1cadad | ||
|
97ada3d3d0 | ||
|
115f59dd14 | ||
|
972b008a6e | ||
|
246eafb8d2 | ||
|
dca4c110dd | ||
|
58230b15b9 | ||
|
aa1e75f071 | ||
|
2071ea0532 | ||
|
3bd43090fb | ||
|
4dba531bf9 | ||
|
2072c933ba | ||
|
7bc0ace52d | ||
|
4c4d0d2745 | ||
|
28a855214b | ||
|
196da6d570 | ||
|
b4ed564e5d | ||
|
80fbc2e9ec | ||
|
f58065082e | ||
|
6048817ba7 | ||
|
e408b68744 | ||
|
b878613e10 | ||
|
85b275bb2b | ||
|
d6abd2fe22 | ||
|
b79a9aaec7 | ||
|
b9586b3f71 | ||
|
d3b12d885e | ||
|
f21386708a | ||
|
b48a2d4d7b | ||
|
35b754a3ab | ||
|
1079c5c562 | ||
|
f4bb7efa89 | ||
|
0099091e96 | ||
|
c352a69d54 | ||
|
f5347ec897 | ||
|
b367f07d56 | ||
|
6a75a3d683 | ||
|
56b924e155 |
@@ -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 &&
|
||||
|
46
CHANGES.md
46
CHANGES.md
@@ -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
|
||||
|
21
Cargo.toml
21
Cargo.toml
@@ -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"
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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)
|
||||
|
@@ -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);
|
||||
|
@@ -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")),
|
||||
);
|
||||
}
|
||||
|
||||
|
17
src/error.rs
17
src/error.rs
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
225
src/extractor.rs
225
src/extractor.rs
@@ -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();
|
||||
|
@@ -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}"),
|
||||
|
18
src/lib.rs
18
src/lib.rs
@@ -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
|
||||
//!
|
||||
|
@@ -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"#)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -409,7 +409,7 @@ struct ProcessResponse<S, H> {
|
||||
_h: PhantomData<H>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
#[derive(PartialEq, Debug)]
|
||||
enum RunningState {
|
||||
Running,
|
||||
Paused,
|
||||
|
297
src/router.rs
297
src/router.rs
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
23
src/scope.rs
23
src/scope.rs
@@ -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
316
src/server/accept.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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(_)) => {
|
||||
|
@@ -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(()))
|
||||
}
|
||||
}
|
||||
|
@@ -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(_) => {
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
@@ -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(®, 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
|
||||
}
|
||||
|
@@ -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",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
91
src/test.rs
91
src/test.rs
@@ -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);
|
||||
|
@@ -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-----
|
||||
|
@@ -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"),
|
||||
"\""
|
||||
)));
|
||||
|
@@ -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()));
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user