mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-29 19:37:34 +02:00
Compare commits
96 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
ce6d237cc1 | ||
|
70caa2552b | ||
|
ee7d58dd7f | ||
|
c4f4cadb43 | ||
|
978091cedb | ||
|
8198f5e10a | ||
|
6cd40df387 | ||
|
35ee5d36d8 | ||
|
e7ec0f9fd7 | ||
|
f4a47ef71e | ||
|
6b1a79fab8 | ||
|
ab73da4a1a | ||
|
e0c8da567c | ||
|
c10dedf7e4 | ||
|
ec192e0ab1 | ||
|
6d792d9948 | ||
|
1fe4315c94 | ||
|
381b90e9a1 | ||
|
2d18dba40a | ||
|
d2693d58a8 | ||
|
84bf282c17 | ||
|
b15b5e5246 | ||
|
52b3b0c362 | ||
|
64c4cefa8f | ||
|
7e8b231f57 | ||
|
8a344d0c94 | ||
|
4096089a3f | ||
|
b16f2d5f05 | ||
|
5baf15822a | ||
|
5368ce823e | ||
|
4effdf065b | ||
|
61970ab190 | ||
|
484b00a0f9 | ||
|
73bf2068aa | ||
|
1cda949204 | ||
|
ad6b823255 | ||
|
0f064db31d | ||
|
fd0bb54469 | ||
|
e27bbaa55c | ||
|
8a50eae1e2 | ||
|
38080f67b3 | ||
|
08504e0892 | ||
|
401c0ad809 | ||
|
b4b0deb7fa | ||
|
05ff35d383 | ||
|
29c3e8f7ea | ||
|
6657446433 | ||
|
46b9a9c887 | ||
|
b3cdb472d0 | ||
|
31e1aab9a4 | ||
|
67f383f346 | ||
|
49f5c335f6 | ||
|
692e11a584 | ||
|
208117ca6f | ||
|
3e276ac921 | ||
|
4af115a19c | ||
|
051703eb2c | ||
|
31fbbd3168 | ||
|
fee1e255ac | ||
|
a4c933e56e | ||
|
9ddf5a3550 | ||
|
9ab0fa604d | ||
|
6c709b33cc | ||
|
71b4c07ea4 | ||
|
ac9eba8261 | ||
|
cad55f9c80 | ||
|
4263574a58 | ||
|
84ef5ee410 | ||
|
598fb9190d | ||
|
9a404a0c03 | ||
|
3dd8fdf450 | ||
|
05f5ba0084 | ||
|
8169149554 | ||
|
8d1de6c497 | ||
|
caaace82e3 | ||
|
02dd5375a9 | ||
|
717602472a | ||
|
b56be8e571 | ||
|
2853086463 | ||
|
e2107ec6f4 | ||
|
c33caddf57 | ||
|
db1e04e418 | ||
|
f8b8fe3865 | ||
|
1c6ddfd34c | ||
|
49e007ff2a | ||
|
2068eee669 | ||
|
f3c63e631a | ||
|
3f0803a7d3 | ||
|
f12b613211 | ||
|
695c052c58 | ||
|
63634be542 | ||
|
f88f1c65b6 | ||
|
a0b589eb96 | ||
|
ebdc983dfe | ||
|
395243a539 | ||
|
1ab676d7eb |
@@ -12,9 +12,6 @@ matrix:
|
||||
- rust: stable
|
||||
- rust: beta
|
||||
- rust: nightly
|
||||
allow_failures:
|
||||
- rust: nightly
|
||||
- rust: beta
|
||||
|
||||
#rust:
|
||||
# - 1.21.0
|
||||
@@ -59,6 +56,7 @@ script:
|
||||
cd examples/multipart && cargo check && cd ../..
|
||||
cd examples/json && cargo check && cd ../..
|
||||
cd examples/juniper && cargo check && cd ../..
|
||||
cd examples/protobuf && cargo check && cd ../..
|
||||
cd examples/state && cargo check && cd ../..
|
||||
cd examples/template_tera && cargo check && cd ../..
|
||||
cd examples/diesel && cargo check && cd ../..
|
||||
@@ -77,7 +75,7 @@ script:
|
||||
after_success:
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "nightly" ]]; then
|
||||
cargo doc --features "alpn, tls" --no-deps &&
|
||||
cargo doc --features "alpn, tls, session" --no-deps &&
|
||||
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
|
||||
cargo install mdbook &&
|
||||
cd guide && mdbook build -d ../target/doc/guide && cd .. &&
|
||||
|
66
CHANGES.md
66
CHANGES.md
@@ -1,5 +1,66 @@
|
||||
# Changes
|
||||
|
||||
## 0.4.10 (2018-03-20)
|
||||
|
||||
* Use `Error` instead of `InternalError` for `error::ErrorXXXX` methods
|
||||
|
||||
* Allow to set client request timeout
|
||||
|
||||
* Allow to set client websocket handshake timeout
|
||||
|
||||
* Refactor `TestServer` configuration
|
||||
|
||||
* Fix server websockets big payloads support
|
||||
|
||||
* Fix http/2 date header generation
|
||||
|
||||
|
||||
## 0.4.9 (2018-03-16)
|
||||
|
||||
* Allow to disable http/2 support
|
||||
|
||||
* Wake payload reading task when data is available
|
||||
|
||||
* Fix server keep-alive handling
|
||||
|
||||
* Send Query Parameters in client requests #120
|
||||
|
||||
* Move brotli encoding to a feature
|
||||
|
||||
* Add option of default handler for `StaticFiles` handler #57
|
||||
|
||||
* Add basic client connection pooling
|
||||
|
||||
|
||||
## 0.4.8 (2018-03-12)
|
||||
|
||||
* Allow to set read buffer capacity for server request
|
||||
|
||||
* Handle WouldBlock error for socket accept call
|
||||
|
||||
|
||||
## 0.4.7 (2018-03-11)
|
||||
|
||||
* Fix panic on unknown content encoding
|
||||
|
||||
* Fix connection get closed too early
|
||||
|
||||
* Fix streaming response handling for http/2
|
||||
|
||||
* Better sleep on error support
|
||||
|
||||
|
||||
## 0.4.6 (2018-03-10)
|
||||
|
||||
* Fix client cookie handling
|
||||
|
||||
* Fix json content type detection
|
||||
|
||||
* Fix CORS middleware #117
|
||||
|
||||
* Optimize websockets stream support
|
||||
|
||||
|
||||
## 0.4.5 (2018-03-07)
|
||||
|
||||
* Fix compression #103 and #104
|
||||
@@ -12,9 +73,8 @@
|
||||
|
||||
* Better support for `NamedFile` type
|
||||
|
||||
* Add `ResponseError` impl for `SendRequestError`.
|
||||
This improves ergonomics of http client.
|
||||
|
||||
* Add `ResponseError` impl for `SendRequestError`. This improves ergonomics of the client.
|
||||
|
||||
* Add native-tls support for client
|
||||
|
||||
* Allow client connection timeout to be set #108
|
||||
|
12
Cargo.toml
12
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "0.4.5"
|
||||
version = "0.4.10"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix web is a simple, pragmatic, extremely fast, web framework for Rust."
|
||||
readme = "README.md"
|
||||
@@ -27,7 +27,7 @@ name = "actix_web"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = ["session"]
|
||||
default = ["session", "brotli"]
|
||||
|
||||
# tls
|
||||
tls = ["native-tls", "tokio-tls"]
|
||||
@@ -38,12 +38,14 @@ alpn = ["openssl", "openssl/v102", "openssl/v110", "tokio-openssl"]
|
||||
# sessions
|
||||
session = ["cookie/secure"]
|
||||
|
||||
# brotli encoding
|
||||
brotli = ["brotli2"]
|
||||
|
||||
[dependencies]
|
||||
actix = "^0.5.2"
|
||||
actix = "^0.5.5"
|
||||
|
||||
base64 = "0.9"
|
||||
bitflags = "1.0"
|
||||
brotli2 = "^0.3.2"
|
||||
failure = "0.1.1"
|
||||
flate2 = "1.0"
|
||||
h2 = "0.1"
|
||||
@@ -67,6 +69,7 @@ encoding = "0.2"
|
||||
language-tags = "0.2"
|
||||
url = { version="1.7", features=["query_encoding"] }
|
||||
cookie = { version="0.10", features=["percent-encode"] }
|
||||
brotli2 = { version="^0.3.2", optional = true }
|
||||
|
||||
# io
|
||||
mio = "^0.6.13"
|
||||
@@ -109,6 +112,7 @@ members = [
|
||||
"examples/diesel",
|
||||
"examples/r2d2",
|
||||
"examples/json",
|
||||
"examples/protobuf",
|
||||
"examples/hello-world",
|
||||
"examples/http-proxy",
|
||||
"examples/multipart",
|
||||
|
@@ -10,6 +10,7 @@ Actix web is a simple, pragmatic, extremely fast, web framework for Rust.
|
||||
* Configurable [request routing](https://actix.github.io/actix-web/guide/qs_5.html)
|
||||
* Graceful server shutdown
|
||||
* Multipart streams
|
||||
* Static assets
|
||||
* SSL support with openssl or native-tls
|
||||
* Middlewares ([Logger](https://actix.github.io/actix-web/guide/qs_10.html#logging),
|
||||
[Session](https://actix.github.io/actix-web/guide/qs_10.html#user-sessions),
|
||||
@@ -30,7 +31,7 @@ Actix web is a simple, pragmatic, extremely fast, web framework for Rust.
|
||||
|
||||
## Example
|
||||
|
||||
```rust,ignore
|
||||
```rust
|
||||
extern crate actix_web;
|
||||
use actix_web::*;
|
||||
|
||||
@@ -51,13 +52,13 @@ fn main() {
|
||||
|
||||
* [Basics](https://github.com/actix/actix-web/tree/master/examples/basics/)
|
||||
* [Stateful](https://github.com/actix/actix-web/tree/master/examples/state/)
|
||||
* [Protobuf support](https://github.com/actix/actix-web/tree/master/examples/protobuf/)
|
||||
* [Multipart streams](https://github.com/actix/actix-web/tree/master/examples/multipart/)
|
||||
* [Simple websocket session](https://github.com/actix/actix-web/tree/master/examples/websocket/)
|
||||
* [Tera templates](https://github.com/actix/actix-web/tree/master/examples/template_tera/)
|
||||
* [Diesel integration](https://github.com/actix/actix-web/tree/master/examples/diesel/)
|
||||
* [SSL / HTTP/2.0](https://github.com/actix/actix-web/tree/master/examples/tls/)
|
||||
* [Tcp/Websocket chat](https://github.com/actix/actix-web/tree/master/examples/websocket-chat/)
|
||||
* [SockJS Server](https://github.com/actix/actix-sockjs)
|
||||
* [Json](https://github.com/actix/actix-web/tree/master/examples/json/)
|
||||
|
||||
You may consider checking out
|
||||
|
3
build.rs
3
build.rs
@@ -6,12 +6,13 @@ use std::{env, fs};
|
||||
|
||||
#[cfg(unix)]
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-env-changed=USE_SKEPTIC");
|
||||
let f = env::var("OUT_DIR").unwrap() + "/skeptic-tests.rs";
|
||||
if env::var("USE_SKEPTIC").is_ok() {
|
||||
let _ = fs::remove_file(f);
|
||||
// generates doc tests for `README.md`.
|
||||
skeptic::generate_doc_tests(
|
||||
&["README.md",
|
||||
&[// "README.md",
|
||||
"guide/src/qs_1.md",
|
||||
"guide/src/qs_2.md",
|
||||
"guide/src/qs_3.md",
|
||||
|
@@ -124,7 +124,8 @@ fn main() {
|
||||
}
|
||||
}))
|
||||
.resource("/error.html", |r| r.f(|req| {
|
||||
error::ErrorBadRequest(io::Error::new(io::ErrorKind::Other, "test"))
|
||||
error::InternalError::new(
|
||||
io::Error::new(io::ErrorKind::Other, "test"), StatusCode::OK)
|
||||
}))
|
||||
// static files
|
||||
.handler("/static/", fs::StaticFiles::new("../static/", true))
|
||||
|
@@ -1,8 +1,8 @@
|
||||
//! Actix web diesel example
|
||||
//!
|
||||
//! Diesel does not support tokio, so we have to run it in separate threads.
|
||||
//! Actix supports sync actors by default, so we going to create sync actor that will
|
||||
//! use diesel. Technically sync actors are worker style actors, multiple of them
|
||||
//! Actix supports sync actors by default, so we going to create sync actor that use diesel.
|
||||
//! Technically sync actors are worker style actors, multiple of them
|
||||
//! can run in parallel and process messages from same queue.
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
@@ -38,6 +38,7 @@ struct State {
|
||||
fn index(req: HttpRequest<State>) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||
let name = &req.match_info()["name"];
|
||||
|
||||
// send async `CreateUser` message to a `DbExecutor`
|
||||
req.state().db.send(CreateUser{name: name.to_owned()})
|
||||
.from_err()
|
||||
.and_then(|res| {
|
||||
@@ -54,7 +55,7 @@ fn main() {
|
||||
let _ = env_logger::init();
|
||||
let sys = actix::System::new("diesel-example");
|
||||
|
||||
// Start db executor actors
|
||||
// Start 3 db executor actors
|
||||
let addr = SyncArbiter::start(3, || {
|
||||
DbExecutor(SqliteConnection::establish("test.db").unwrap())
|
||||
});
|
||||
|
16
examples/protobuf/Cargo.toml
Normal file
16
examples/protobuf/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "protobuf-example"
|
||||
version = "0.1.0"
|
||||
authors = ["kingxsp <jin_hb_zh@126.com>"]
|
||||
|
||||
[dependencies]
|
||||
bytes = "0.4"
|
||||
futures = "0.1"
|
||||
failure = "0.1"
|
||||
env_logger = "*"
|
||||
|
||||
prost = "0.2.0"
|
||||
prost-derive = "0.2.0"
|
||||
|
||||
actix = "0.5"
|
||||
actix-web = { path="../../" }
|
66
examples/protobuf/client.py
Normal file
66
examples/protobuf/client.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# just start server and run client.py
|
||||
|
||||
# wget https://github.com/google/protobuf/releases/download/v3.5.1/protobuf-python-3.5.1.zip
|
||||
# unzip protobuf-python-3.5.1.zip.1
|
||||
# cd protobuf-3.5.1/python/
|
||||
# python3.6 setup.py install
|
||||
|
||||
# pip3.6 install --upgrade pip
|
||||
# pip3.6 install aiohttp
|
||||
|
||||
#!/usr/bin/env python
|
||||
import test_pb2
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
import asyncio
|
||||
import aiohttp
|
||||
|
||||
def op():
|
||||
try:
|
||||
obj = test_pb2.MyObj()
|
||||
obj.number = 9
|
||||
obj.name = 'USB'
|
||||
|
||||
#Serialize
|
||||
sendDataStr = obj.SerializeToString()
|
||||
#print serialized string value
|
||||
print('serialized string:', sendDataStr)
|
||||
#------------------------#
|
||||
# message transmission #
|
||||
#------------------------#
|
||||
receiveDataStr = sendDataStr
|
||||
receiveData = test_pb2.MyObj()
|
||||
|
||||
#Deserialize
|
||||
receiveData.ParseFromString(receiveDataStr)
|
||||
print('pares serialize string, return: devId = ', receiveData.number, ', name = ', receiveData.name)
|
||||
except(Exception, e):
|
||||
print(Exception, ':', e)
|
||||
print(traceback.print_exc())
|
||||
errInfo = sys.exc_info()
|
||||
print(errInfo[0], ':', errInfo[1])
|
||||
|
||||
|
||||
async def fetch(session):
|
||||
obj = test_pb2.MyObj()
|
||||
obj.number = 9
|
||||
obj.name = 'USB'
|
||||
async with session.post('http://localhost:8080/', data=obj.SerializeToString(),
|
||||
headers={"content-type": "application/protobuf"}) as resp:
|
||||
print(resp.status)
|
||||
data = await resp.read()
|
||||
receiveObj = test_pb2.MyObj()
|
||||
receiveObj.ParseFromString(data)
|
||||
print(receiveObj)
|
||||
|
||||
async def go(loop):
|
||||
obj = test_pb2.MyObj()
|
||||
obj.number = 9
|
||||
obj.name = 'USB'
|
||||
async with aiohttp.ClientSession(loop=loop) as session:
|
||||
await fetch(session)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(go(loop))
|
||||
loop.close()
|
55
examples/protobuf/src/main.rs
Normal file
55
examples/protobuf/src/main.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
extern crate actix;
|
||||
extern crate actix_web;
|
||||
extern crate bytes;
|
||||
extern crate futures;
|
||||
#[macro_use]
|
||||
extern crate failure;
|
||||
extern crate env_logger;
|
||||
extern crate prost;
|
||||
#[macro_use]
|
||||
extern crate prost_derive;
|
||||
|
||||
use actix_web::*;
|
||||
use futures::Future;
|
||||
|
||||
mod protobuf;
|
||||
use protobuf::ProtoBufResponseBuilder;
|
||||
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Message)]
|
||||
pub struct MyObj {
|
||||
#[prost(int32, tag="1")]
|
||||
pub number: i32,
|
||||
#[prost(string, tag="2")]
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
|
||||
/// This handler uses `ProtoBufMessage` for loading protobuf object.
|
||||
fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
|
||||
protobuf::ProtoBufMessage::new(req)
|
||||
.from_err() // convert all errors into `Error`
|
||||
.and_then(|val: MyObj| {
|
||||
println!("model: {:?}", val);
|
||||
Ok(httpcodes::HTTPOk.build().protobuf(val)?) // <- send response
|
||||
})
|
||||
.responder()
|
||||
}
|
||||
|
||||
|
||||
fn main() {
|
||||
::std::env::set_var("RUST_LOG", "actix_web=info");
|
||||
let _ = env_logger::init();
|
||||
let sys = actix::System::new("protobuf-example");
|
||||
|
||||
let addr = HttpServer::new(|| {
|
||||
Application::new()
|
||||
.middleware(middleware::Logger::default())
|
||||
.resource("/", |r| r.method(Method::POST).f(index))})
|
||||
.bind("127.0.0.1:8080").unwrap()
|
||||
.shutdown_timeout(1)
|
||||
.start();
|
||||
|
||||
println!("Started http server: 127.0.0.1:8080");
|
||||
let _ = sys.run();
|
||||
}
|
169
examples/protobuf/src/protobuf.rs
Normal file
169
examples/protobuf/src/protobuf.rs
Normal file
@@ -0,0 +1,169 @@
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::{Poll, Future, Stream};
|
||||
|
||||
use bytes::IntoBuf;
|
||||
use prost::Message;
|
||||
use prost::DecodeError as ProtoBufDecodeError;
|
||||
use prost::EncodeError as ProtoBufEncodeError;
|
||||
|
||||
use actix_web::header::http::{CONTENT_TYPE, CONTENT_LENGTH};
|
||||
use actix_web::{Responder, HttpMessage, HttpRequest, HttpResponse};
|
||||
use actix_web::dev::HttpResponseBuilder;
|
||||
use actix_web::error::{Error, PayloadError, ResponseError};
|
||||
use actix_web::httpcodes::{HttpBadRequest, HttpPayloadTooLarge};
|
||||
|
||||
|
||||
#[derive(Fail, Debug)]
|
||||
pub enum ProtoBufPayloadError {
|
||||
/// Payload size is bigger than 256k
|
||||
#[fail(display="Payload size is bigger than 256k")]
|
||||
Overflow,
|
||||
/// Content type error
|
||||
#[fail(display="Content type error")]
|
||||
ContentType,
|
||||
/// Serialize error
|
||||
#[fail(display="ProtoBud serialize error: {}", _0)]
|
||||
Serialize(#[cause] ProtoBufEncodeError),
|
||||
/// Deserialize error
|
||||
#[fail(display="ProtoBud deserialize error: {}", _0)]
|
||||
Deserialize(#[cause] ProtoBufDecodeError),
|
||||
/// Payload error
|
||||
#[fail(display="Error that occur during reading payload: {}", _0)]
|
||||
Payload(#[cause] PayloadError),
|
||||
}
|
||||
|
||||
impl ResponseError for ProtoBufPayloadError {
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
match *self {
|
||||
ProtoBufPayloadError::Overflow => HttpPayloadTooLarge.into(),
|
||||
_ => HttpBadRequest.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<PayloadError> for ProtoBufPayloadError {
|
||||
fn from(err: PayloadError) -> ProtoBufPayloadError {
|
||||
ProtoBufPayloadError::Payload(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ProtoBufDecodeError> for ProtoBufPayloadError {
|
||||
fn from(err: ProtoBufDecodeError) -> ProtoBufPayloadError {
|
||||
ProtoBufPayloadError::Deserialize(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ProtoBuf<T: Message>(pub T);
|
||||
|
||||
impl<T: Message> Responder for ProtoBuf<T> {
|
||||
type Item = HttpResponse;
|
||||
type Error = Error;
|
||||
|
||||
fn respond_to(self, _: HttpRequest) -> Result<HttpResponse, Error> {
|
||||
let mut buf = Vec::new();
|
||||
self.0.encode(&mut buf)
|
||||
.map_err(|e| Error::from(ProtoBufPayloadError::Serialize(e)))
|
||||
.and_then(|()| {
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("application/protobuf")
|
||||
.body(buf)
|
||||
.into())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProtoBufMessage<T, U: Message + Default>{
|
||||
limit: usize,
|
||||
ct: &'static str,
|
||||
req: Option<T>,
|
||||
fut: Option<Box<Future<Item=U, Error=ProtoBufPayloadError>>>,
|
||||
}
|
||||
|
||||
impl<T, U: Message + Default> ProtoBufMessage<T, U> {
|
||||
|
||||
/// Create `ProtoBufMessage` for request.
|
||||
pub fn new(req: T) -> Self {
|
||||
ProtoBufMessage{
|
||||
limit: 262_144,
|
||||
req: Some(req),
|
||||
fut: None,
|
||||
ct: "application/protobuf",
|
||||
}
|
||||
}
|
||||
|
||||
/// Change max size of payload. By default max size is 256Kb
|
||||
pub fn limit(mut self, limit: usize) -> Self {
|
||||
self.limit = limit;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set allowed content type.
|
||||
///
|
||||
/// By default *application/protobuf* content type is used. Set content type
|
||||
/// to empty string if you want to disable content type check.
|
||||
pub fn content_type(mut self, ct: &'static str) -> Self {
|
||||
self.ct = ct;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U: Message + Default + 'static> Future for ProtoBufMessage<T, U>
|
||||
where T: HttpMessage + Stream<Item=Bytes, Error=PayloadError> + 'static
|
||||
{
|
||||
type Item = U;
|
||||
type Error = ProtoBufPayloadError;
|
||||
|
||||
fn poll(&mut self) -> Poll<U, ProtoBufPayloadError> {
|
||||
if let Some(req) = self.req.take() {
|
||||
if let Some(len) = req.headers().get(CONTENT_LENGTH) {
|
||||
if let Ok(s) = len.to_str() {
|
||||
if let Ok(len) = s.parse::<usize>() {
|
||||
if len > self.limit {
|
||||
return Err(ProtoBufPayloadError::Overflow);
|
||||
}
|
||||
} else {
|
||||
return Err(ProtoBufPayloadError::Overflow);
|
||||
}
|
||||
}
|
||||
}
|
||||
// check content-type
|
||||
if !self.ct.is_empty() && req.content_type() != self.ct {
|
||||
return Err(ProtoBufPayloadError::ContentType)
|
||||
}
|
||||
|
||||
let limit = self.limit;
|
||||
let fut = req.from_err()
|
||||
.fold(BytesMut::new(), move |mut body, chunk| {
|
||||
if (body.len() + chunk.len()) > limit {
|
||||
Err(ProtoBufPayloadError::Overflow)
|
||||
} else {
|
||||
body.extend_from_slice(&chunk);
|
||||
Ok(body)
|
||||
}
|
||||
})
|
||||
.and_then(|body| Ok(<U>::decode(&mut body.into_buf())?));
|
||||
self.fut = Some(Box::new(fut));
|
||||
}
|
||||
|
||||
self.fut.as_mut().expect("ProtoBufBody could not be used second time").poll()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub trait ProtoBufResponseBuilder {
|
||||
|
||||
fn protobuf<T: Message>(&mut self, value: T) -> Result<HttpResponse, Error>;
|
||||
}
|
||||
|
||||
impl ProtoBufResponseBuilder for HttpResponseBuilder {
|
||||
|
||||
fn protobuf<T: Message>(&mut self, value: T) -> Result<HttpResponse, Error> {
|
||||
self.header(CONTENT_TYPE, "application/protobuf");
|
||||
|
||||
let mut body = Vec::new();
|
||||
value.encode(&mut body).map_err(|e| ProtoBufPayloadError::Serialize(e))?;
|
||||
Ok(self.body(body)?)
|
||||
}
|
||||
}
|
6
examples/protobuf/test.proto
Normal file
6
examples/protobuf/test.proto
Normal file
@@ -0,0 +1,6 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message MyObj {
|
||||
int32 number = 1;
|
||||
string name = 2;
|
||||
}
|
76
examples/protobuf/test_pb2.py
Normal file
76
examples/protobuf/test_pb2.py
Normal file
@@ -0,0 +1,76 @@
|
||||
# Generated by the protocol buffer compiler. DO NOT EDIT!
|
||||
# source: test.proto
|
||||
|
||||
import sys
|
||||
_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
|
||||
from google.protobuf import descriptor as _descriptor
|
||||
from google.protobuf import message as _message
|
||||
from google.protobuf import reflection as _reflection
|
||||
from google.protobuf import symbol_database as _symbol_database
|
||||
from google.protobuf import descriptor_pb2
|
||||
# @@protoc_insertion_point(imports)
|
||||
|
||||
_sym_db = _symbol_database.Default()
|
||||
|
||||
|
||||
|
||||
|
||||
DESCRIPTOR = _descriptor.FileDescriptor(
|
||||
name='test.proto',
|
||||
package='',
|
||||
syntax='proto3',
|
||||
serialized_pb=_b('\n\ntest.proto\"%\n\x05MyObj\x12\x0e\n\x06number\x18\x01 \x01(\x05\x12\x0c\n\x04name\x18\x02 \x01(\tb\x06proto3')
|
||||
)
|
||||
_sym_db.RegisterFileDescriptor(DESCRIPTOR)
|
||||
|
||||
|
||||
|
||||
|
||||
_MYOBJ = _descriptor.Descriptor(
|
||||
name='MyObj',
|
||||
full_name='MyObj',
|
||||
filename=None,
|
||||
file=DESCRIPTOR,
|
||||
containing_type=None,
|
||||
fields=[
|
||||
_descriptor.FieldDescriptor(
|
||||
name='number', full_name='MyObj.number', index=0,
|
||||
number=1, type=5, cpp_type=1, label=1,
|
||||
has_default_value=False, default_value=0,
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
options=None),
|
||||
_descriptor.FieldDescriptor(
|
||||
name='name', full_name='MyObj.name', index=1,
|
||||
number=2, type=9, cpp_type=9, label=1,
|
||||
has_default_value=False, default_value=_b("").decode('utf-8'),
|
||||
message_type=None, enum_type=None, containing_type=None,
|
||||
is_extension=False, extension_scope=None,
|
||||
options=None),
|
||||
],
|
||||
extensions=[
|
||||
],
|
||||
nested_types=[],
|
||||
enum_types=[
|
||||
],
|
||||
options=None,
|
||||
is_extendable=False,
|
||||
syntax='proto3',
|
||||
extension_ranges=[],
|
||||
oneofs=[
|
||||
],
|
||||
serialized_start=14,
|
||||
serialized_end=51,
|
||||
)
|
||||
|
||||
DESCRIPTOR.message_types_by_name['MyObj'] = _MYOBJ
|
||||
|
||||
MyObj = _reflection.GeneratedProtocolMessageType('MyObj', (_message.Message,), dict(
|
||||
DESCRIPTOR = _MYOBJ,
|
||||
__module__ = 'test_pb2'
|
||||
# @@protoc_insertion_point(class_scope:MyObj)
|
||||
))
|
||||
_sym_db.RegisterMessage(MyObj)
|
||||
|
||||
|
||||
# @@protoc_insertion_point(module_scope)
|
@@ -1,4 +1,4 @@
|
||||
# websockect
|
||||
# websocket
|
||||
|
||||
Simple echo websocket server.
|
||||
|
||||
|
@@ -134,9 +134,10 @@ for full example.
|
||||
Actix can wait for requests on a keep-alive connection. *Keep alive*
|
||||
connection behavior is defined by server settings.
|
||||
|
||||
* `Some(75)` - enable 75 sec *keep alive* timer according request and response settings.
|
||||
* `Some(0)` - disable *keep alive*.
|
||||
* `None` - Use `SO_KEEPALIVE` socket option.
|
||||
* `75` or `Some(75)` or `KeepAlive::Timeout(75)` - enable 75 sec *keep alive* timer according
|
||||
request and response settings.
|
||||
* `None` or `KeepAlive::Disabled` - disable *keep alive*.
|
||||
* `KeepAlive::Tcp(75)` - Use `SO_KEEPALIVE` socket option.
|
||||
|
||||
```rust
|
||||
# extern crate actix_web;
|
||||
@@ -147,7 +148,17 @@ fn main() {
|
||||
HttpServer::new(||
|
||||
Application::new()
|
||||
.resource("/", |r| r.h(httpcodes::HttpOk)))
|
||||
.keep_alive(None); // <- Use `SO_KEEPALIVE` socket option.
|
||||
.keep_alive(75); // <- Set keep-alive to 75 seconds
|
||||
|
||||
HttpServer::new(||
|
||||
Application::new()
|
||||
.resource("/", |r| r.h(httpcodes::HttpOk)))
|
||||
.keep_alive(server::KeepAlive::Tcp(75)); // <- Use `SO_KEEPALIVE` socket option.
|
||||
|
||||
HttpServer::new(||
|
||||
Application::new()
|
||||
.resource("/", |r| r.h(httpcodes::HttpOk)))
|
||||
.keep_alive(None); // <- Disable keep-alive
|
||||
}
|
||||
```
|
||||
|
||||
|
@@ -235,6 +235,44 @@ fn main() {
|
||||
|
||||
Both methods could be combined. (i.e Async response with streaming body)
|
||||
|
||||
## Different return types (Either)
|
||||
|
||||
Sometimes you need to return different types of responses. For example
|
||||
you can do error check and return error and return async response otherwise.
|
||||
Or any result that requires two different types.
|
||||
For this case [*Either*](../actix_web/enum.Either.html) type can be used.
|
||||
*Either* allows to combine two different responder types into a single type.
|
||||
|
||||
```rust
|
||||
# extern crate actix_web;
|
||||
# extern crate futures;
|
||||
# use actix_web::*;
|
||||
# use futures::future::Future;
|
||||
use futures::future::result;
|
||||
use actix_web::{Either, Error, HttpResponse, httpcodes};
|
||||
|
||||
type RegisterResult = Either<HttpResponse, Box<Future<Item=HttpResponse, Error=Error>>>;
|
||||
|
||||
fn index(req: HttpRequest) -> RegisterResult {
|
||||
if is_a_variant() { // <- choose variant A
|
||||
Either::A(
|
||||
httpcodes::HttpBadRequest.with_body("Bad data"))
|
||||
} else {
|
||||
Either::B( // <- variant B
|
||||
result(HttpResponse::Ok()
|
||||
.content_type("text/html")
|
||||
.body(format!("Hello!"))
|
||||
.map_err(|e| e.into())).responder())
|
||||
}
|
||||
}
|
||||
# fn is_a_variant() -> bool { true }
|
||||
# fn main() {
|
||||
# Application::new()
|
||||
# .resource("/register", |r| r.f(index))
|
||||
# .finish();
|
||||
# }
|
||||
```
|
||||
|
||||
## Tokio core handle
|
||||
|
||||
Any actix web handler runs within properly configured
|
||||
|
@@ -235,6 +235,7 @@ impl<'a> From<&'a Arc<Vec<u8>>> for Binary {
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for Binary {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
match *self {
|
||||
Binary::Bytes(ref bytes) => bytes.as_ref(),
|
||||
|
@@ -1,15 +1,18 @@
|
||||
use std::{io, time};
|
||||
use std::{fmt, io, time};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::net::Shutdown;
|
||||
use std::time::Duration;
|
||||
use std::time::{Duration, Instant};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
use actix::{fut, Actor, ActorFuture, Context,
|
||||
use actix::{fut, Actor, ActorFuture, Context, AsyncContext,
|
||||
Handler, Message, ActorResponse, Supervised};
|
||||
use actix::registry::ArbiterService;
|
||||
use actix::fut::WrapFuture;
|
||||
use actix::actors::{Connector, ConnectorError, Connect as ResolveConnect};
|
||||
|
||||
use http::{Uri, HttpTryFrom, Error as HttpError};
|
||||
use futures::Poll;
|
||||
use futures::{Async, Poll};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
||||
#[cfg(feature="alpn")]
|
||||
@@ -77,7 +80,7 @@ pub enum ClientConnectorError {
|
||||
#[fail(display = "{}", _0)]
|
||||
Connector(#[cause] ConnectorError),
|
||||
|
||||
/// Connecting took too long
|
||||
/// Connection took too long
|
||||
#[fail(display = "Timeout out while establishing connection")]
|
||||
Timeout,
|
||||
|
||||
@@ -104,10 +107,15 @@ pub struct ClientConnector {
|
||||
connector: SslConnector,
|
||||
#[cfg(all(feature="tls", not(feature="alpn")))]
|
||||
connector: TlsConnector,
|
||||
pool: Rc<Pool>,
|
||||
}
|
||||
|
||||
impl Actor for ClientConnector {
|
||||
type Context = Context<ClientConnector>;
|
||||
|
||||
fn started(&mut self, ctx: &mut Self::Context) {
|
||||
self.collect(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
impl Supervised for ClientConnector {}
|
||||
@@ -120,19 +128,21 @@ impl Default for ClientConnector {
|
||||
{
|
||||
let builder = SslConnector::builder(SslMethod::tls()).unwrap();
|
||||
ClientConnector {
|
||||
connector: builder.build()
|
||||
connector: builder.build(),
|
||||
pool: Rc::new(Pool::new()),
|
||||
}
|
||||
}
|
||||
#[cfg(all(feature="tls", not(feature="alpn")))]
|
||||
{
|
||||
let builder = TlsConnector::builder().unwrap();
|
||||
ClientConnector {
|
||||
connector: builder.build().unwrap()
|
||||
connector: builder.build().unwrap(),
|
||||
pool: Rc::new(Pool::new()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature="alpn", feature="tls")))]
|
||||
ClientConnector {}
|
||||
ClientConnector {pool: Rc::new(Pool::new())}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +192,12 @@ impl ClientConnector {
|
||||
/// }
|
||||
/// ```
|
||||
pub fn with_connector(connector: SslConnector) -> ClientConnector {
|
||||
ClientConnector { connector }
|
||||
ClientConnector { connector, pool: Rc::new(Pool::new()) }
|
||||
}
|
||||
|
||||
fn collect(&mut self, ctx: &mut Context<Self>) {
|
||||
self.pool.collect();
|
||||
ctx.run_later(Duration::from_secs(1), |act, ctx| act.collect(ctx));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,10 +229,21 @@ impl Handler<Connect> for ClientConnector {
|
||||
|
||||
let host = uri.host().unwrap().to_owned();
|
||||
let port = uri.port().unwrap_or_else(|| proto.port());
|
||||
let key = Key {host, port, ssl: proto.is_secure()};
|
||||
|
||||
let pool = if proto.is_http() {
|
||||
if let Some(conn) = self.pool.query(&key) {
|
||||
return ActorResponse::async(fut::ok(conn))
|
||||
} else {
|
||||
Some(Rc::clone(&self.pool))
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
ActorResponse::async(
|
||||
Connector::from_registry()
|
||||
.send(ResolveConnect::host_and_port(&host, port)
|
||||
.send(ResolveConnect::host_and_port(&key.host, port)
|
||||
.timeout(conn_timeout))
|
||||
.into_actor(self)
|
||||
.map_err(|_, _, _| ClientConnectorError::Disconnected)
|
||||
@@ -228,12 +254,14 @@ impl Handler<Connect> for ClientConnector {
|
||||
Ok(stream) => {
|
||||
if proto.is_secure() {
|
||||
fut::Either::A(
|
||||
_act.connector.connect_async(&host, stream)
|
||||
_act.connector.connect_async(&key.host, stream)
|
||||
.map_err(ClientConnectorError::SslError)
|
||||
.map(|stream| Connection{stream: Box::new(stream)})
|
||||
.map(|stream| Connection::new(
|
||||
key, pool, Box::new(stream)))
|
||||
.into_actor(_act))
|
||||
} else {
|
||||
fut::Either::B(fut::ok(Connection{stream: Box::new(stream)}))
|
||||
fut::Either::B(fut::ok(
|
||||
Connection::new(key, pool, Box::new(stream))))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -244,12 +272,14 @@ impl Handler<Connect> for ClientConnector {
|
||||
Ok(stream) => {
|
||||
if proto.is_secure() {
|
||||
fut::Either::A(
|
||||
_act.connector.connect_async(&host, stream)
|
||||
_act.connector.connect_async(&key.host, stream)
|
||||
.map_err(ClientConnectorError::SslError)
|
||||
.map(|stream| Connection{stream: Box::new(stream)})
|
||||
.map(|stream| Connection::new(
|
||||
key, pool, Box::new(stream)))
|
||||
.into_actor(_act))
|
||||
} else {
|
||||
fut::Either::B(fut::ok(Connection{stream: Box::new(stream)}))
|
||||
fut::Either::B(fut::ok(
|
||||
Connection::new(key, pool, Box::new(stream))))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,7 +291,7 @@ impl Handler<Connect> for ClientConnector {
|
||||
if proto.is_secure() {
|
||||
fut::err(ClientConnectorError::SslIsNotSupported)
|
||||
} else {
|
||||
fut::ok(Connection{stream: Box::new(stream)})
|
||||
fut::ok(Connection::new(key, pool, Box::new(stream)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -288,6 +318,13 @@ impl Protocol {
|
||||
}
|
||||
}
|
||||
|
||||
fn is_http(&self) -> bool {
|
||||
match *self {
|
||||
Protocol::Https | Protocol::Http => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_secure(&self) -> bool {
|
||||
match *self {
|
||||
Protocol::Https | Protocol::Wss => true,
|
||||
@@ -303,18 +340,156 @@ impl Protocol {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Hash, Eq, PartialEq, Clone, Debug)]
|
||||
struct Key {
|
||||
host: String,
|
||||
port: u16,
|
||||
ssl: bool,
|
||||
}
|
||||
|
||||
impl Key {
|
||||
fn empty() -> Key {
|
||||
Key{host: String::new(), port: 0, ssl: false}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Conn(Instant, Connection);
|
||||
|
||||
pub struct Pool {
|
||||
max_size: usize,
|
||||
keep_alive: Duration,
|
||||
max_lifetime: Duration,
|
||||
pool: RefCell<HashMap<Key, VecDeque<Conn>>>,
|
||||
to_close: RefCell<Vec<Connection>>,
|
||||
}
|
||||
|
||||
impl Pool {
|
||||
fn new() -> Pool {
|
||||
Pool {
|
||||
max_size: 128,
|
||||
keep_alive: Duration::from_secs(15),
|
||||
max_lifetime: Duration::from_secs(75),
|
||||
pool: RefCell::new(HashMap::new()),
|
||||
to_close: RefCell::new(Vec::new()),
|
||||
}
|
||||
}
|
||||
|
||||
fn collect(&self) {
|
||||
let mut pool = self.pool.borrow_mut();
|
||||
let mut to_close = self.to_close.borrow_mut();
|
||||
|
||||
// check keep-alive
|
||||
let now = Instant::now();
|
||||
for conns in pool.values_mut() {
|
||||
while !conns.is_empty() {
|
||||
if (now - conns[0].0) > self.keep_alive
|
||||
|| (now - conns[0].1.ts) > self.max_lifetime
|
||||
{
|
||||
let conn = conns.pop_front().unwrap().1;
|
||||
to_close.push(conn);
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check connections for shutdown
|
||||
let mut idx = 0;
|
||||
while idx < to_close.len() {
|
||||
match AsyncWrite::shutdown(&mut to_close[idx]) {
|
||||
Ok(Async::NotReady) => idx += 1,
|
||||
_ => {
|
||||
to_close.swap_remove(idx);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn query(&self, key: &Key) -> Option<Connection> {
|
||||
let mut pool = self.pool.borrow_mut();
|
||||
let mut to_close = self.to_close.borrow_mut();
|
||||
|
||||
if let Some(ref mut connections) = pool.get_mut(key) {
|
||||
let now = Instant::now();
|
||||
while let Some(conn) = connections.pop_back() {
|
||||
// check if it still usable
|
||||
if (now - conn.0) > self.keep_alive
|
||||
|| (now - conn.1.ts) > self.max_lifetime
|
||||
{
|
||||
to_close.push(conn.1);
|
||||
} else {
|
||||
let mut conn = conn.1;
|
||||
let mut buf = [0; 2];
|
||||
match conn.stream().read(&mut buf) {
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => (),
|
||||
Ok(n) if n > 0 => {
|
||||
to_close.push(conn);
|
||||
continue
|
||||
},
|
||||
Ok(_) | Err(_) => continue,
|
||||
}
|
||||
return Some(conn)
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn release(&self, conn: Connection) {
|
||||
if (Instant::now() - conn.ts) < self.max_lifetime {
|
||||
let mut pool = self.pool.borrow_mut();
|
||||
if !pool.contains_key(&conn.key) {
|
||||
let key = conn.key.clone();
|
||||
let mut vec = VecDeque::new();
|
||||
vec.push_back(Conn(Instant::now(), conn));
|
||||
pool.insert(key, vec);
|
||||
} else {
|
||||
let vec = pool.get_mut(&conn.key).unwrap();
|
||||
vec.push_back(Conn(Instant::now(), conn));
|
||||
if vec.len() > self.max_size {
|
||||
let conn = vec.pop_front().unwrap();
|
||||
self.to_close.borrow_mut().push(conn.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct Connection {
|
||||
key: Key,
|
||||
stream: Box<IoStream>,
|
||||
pool: Option<Rc<Pool>>,
|
||||
ts: Instant,
|
||||
}
|
||||
|
||||
impl fmt::Debug for Connection {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Connection {}:{}", self.key.host, self.key.port)
|
||||
}
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
fn new(key: Key, pool: Option<Rc<Pool>>, stream: Box<IoStream>) -> Self {
|
||||
Connection {
|
||||
key, pool, stream,
|
||||
ts: Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn stream(&mut self) -> &mut IoStream {
|
||||
&mut *self.stream
|
||||
}
|
||||
|
||||
pub fn from_stream<T: IoStream>(io: T) -> Connection {
|
||||
Connection{stream: Box::new(io)}
|
||||
Connection::new(Key::empty(), None, Box::new(io))
|
||||
}
|
||||
|
||||
pub fn release(mut self) {
|
||||
if let Some(pool) = self.pool.take() {
|
||||
pool.release(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -78,7 +78,6 @@ impl HttpResponseParser {
|
||||
-> Poll<Option<Bytes>, PayloadError>
|
||||
where T: IoStream
|
||||
{
|
||||
println!("PARSE payload, {:?}", self.decoder.is_some());
|
||||
if self.decoder.is_some() {
|
||||
loop {
|
||||
// read payload
|
||||
|
@@ -171,7 +171,8 @@ impl Future for SendRequest {
|
||||
};
|
||||
|
||||
let pl = Box::new(Pipeline {
|
||||
body, conn, writer,
|
||||
body, writer,
|
||||
conn: Some(conn),
|
||||
parser: Some(HttpResponseParser::default()),
|
||||
parser_buf: BytesMut::new(),
|
||||
disconnected: false,
|
||||
@@ -208,7 +209,7 @@ impl Future for SendRequest {
|
||||
|
||||
pub(crate) struct Pipeline {
|
||||
body: IoBody,
|
||||
conn: Connection,
|
||||
conn: Option<Connection>,
|
||||
writer: HttpClientWriter,
|
||||
parser: Option<HttpResponseParser>,
|
||||
parser_buf: BytesMut,
|
||||
@@ -249,30 +250,45 @@ impl RunningState {
|
||||
|
||||
impl Pipeline {
|
||||
|
||||
fn release_conn(&mut self) {
|
||||
if let Some(conn) = self.conn.take() {
|
||||
conn.release()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn parse(&mut self) -> Poll<ClientResponse, HttpResponseParserError> {
|
||||
match self.parser.as_mut().unwrap().parse(&mut self.conn, &mut self.parser_buf) {
|
||||
Ok(Async::Ready(resp)) => {
|
||||
// check content-encoding
|
||||
if self.should_decompress {
|
||||
if let Some(enc) = resp.headers().get(CONTENT_ENCODING) {
|
||||
if let Ok(enc) = enc.to_str() {
|
||||
match ContentEncoding::from(enc) {
|
||||
ContentEncoding::Auto | ContentEncoding::Identity => (),
|
||||
enc => self.decompress = Some(PayloadStream::new(enc)),
|
||||
fn parse(&mut self) -> Poll<ClientResponse, HttpResponseParserError> {
|
||||
if let Some(ref mut conn) = self.conn {
|
||||
match self.parser.as_mut().unwrap().parse(conn, &mut self.parser_buf) {
|
||||
Ok(Async::Ready(resp)) => {
|
||||
// check content-encoding
|
||||
if self.should_decompress {
|
||||
if let Some(enc) = resp.headers().get(CONTENT_ENCODING) {
|
||||
if let Ok(enc) = enc.to_str() {
|
||||
match ContentEncoding::from(enc) {
|
||||
ContentEncoding::Auto | ContentEncoding::Identity => (),
|
||||
enc => self.decompress = Some(PayloadStream::new(enc)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Async::Ready(resp))
|
||||
Ok(Async::Ready(resp))
|
||||
}
|
||||
val => val,
|
||||
}
|
||||
val => val,
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn poll(&mut self) -> Poll<Option<Bytes>, PayloadError> {
|
||||
if self.conn.is_none() {
|
||||
return Ok(Async::Ready(None))
|
||||
}
|
||||
let conn: &mut Connection = unsafe{ mem::transmute(self.conn.as_mut().unwrap())};
|
||||
|
||||
let mut need_run = false;
|
||||
|
||||
// need write?
|
||||
@@ -286,7 +302,7 @@ impl Pipeline {
|
||||
if self.parser.is_some() {
|
||||
loop {
|
||||
match self.parser.as_mut().unwrap()
|
||||
.parse_payload(&mut self.conn, &mut self.parser_buf)?
|
||||
.parse_payload(conn, &mut self.parser_buf)?
|
||||
{
|
||||
Async::Ready(Some(b)) => {
|
||||
if let Some(ref mut decompress) = self.decompress {
|
||||
@@ -314,6 +330,7 @@ impl Pipeline {
|
||||
if let Some(mut decompress) = self.decompress.take() {
|
||||
let res = decompress.feed_eof();
|
||||
if let Some(b) = res? {
|
||||
self.release_conn();
|
||||
return Ok(Async::Ready(Some(b)))
|
||||
}
|
||||
}
|
||||
@@ -321,13 +338,14 @@ impl Pipeline {
|
||||
if need_run {
|
||||
Ok(Async::NotReady)
|
||||
} else {
|
||||
self.release_conn();
|
||||
Ok(Async::Ready(None))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn poll_write(&mut self) -> Poll<(), Error> {
|
||||
if self.write_state == RunningState::Done {
|
||||
fn poll_write(&mut self) -> Poll<(), Error> {
|
||||
if self.write_state == RunningState::Done || self.conn.is_none() {
|
||||
return Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
@@ -416,7 +434,7 @@ impl Pipeline {
|
||||
}
|
||||
|
||||
// flush io but only if we need to
|
||||
match self.writer.poll_completed(&mut self.conn, false) {
|
||||
match self.writer.poll_completed(self.conn.as_mut().unwrap(), false) {
|
||||
Ok(Async::Ready(_)) => {
|
||||
if self.disconnected {
|
||||
self.write_state = RunningState::Done;
|
||||
|
@@ -1,5 +1,7 @@
|
||||
use std::{fmt, mem};
|
||||
use std::fmt::Write as FmtWrite;
|
||||
use std::io::Write;
|
||||
use std::time::Duration;
|
||||
|
||||
use actix::{Addr, Unsync};
|
||||
use cookie::{Cookie, CookieJar};
|
||||
@@ -8,6 +10,7 @@ use http::{uri, HeaderMap, Method, Version, Uri, HttpTryFrom, Error as HttpError
|
||||
use http::header::{self, HeaderName, HeaderValue};
|
||||
use serde_json;
|
||||
use serde::Serialize;
|
||||
use percent_encoding::{USERINFO_ENCODE_SET, percent_encode};
|
||||
|
||||
use body::Body;
|
||||
use error::Error;
|
||||
@@ -24,9 +27,10 @@ pub struct ClientRequest {
|
||||
body: Body,
|
||||
chunked: bool,
|
||||
upgrade: bool,
|
||||
timeout: Option<Duration>,
|
||||
encoding: ContentEncoding,
|
||||
response_decompress: bool,
|
||||
buffer_capacity: Option<(usize, usize)>,
|
||||
buffer_capacity: usize,
|
||||
conn: ConnectionType,
|
||||
}
|
||||
|
||||
@@ -47,9 +51,10 @@ impl Default for ClientRequest {
|
||||
body: Body::Empty,
|
||||
chunked: false,
|
||||
upgrade: false,
|
||||
timeout: None,
|
||||
encoding: ContentEncoding::Auto,
|
||||
response_decompress: true,
|
||||
buffer_capacity: None,
|
||||
buffer_capacity: 32_768,
|
||||
conn: ConnectionType::Default,
|
||||
}
|
||||
}
|
||||
@@ -177,7 +182,8 @@ impl ClientRequest {
|
||||
self.response_decompress
|
||||
}
|
||||
|
||||
pub fn buffer_capacity(&self) -> Option<(usize, usize)> {
|
||||
/// Requested write buffer capacity
|
||||
pub fn write_buffer_capacity(&self) -> usize {
|
||||
self.buffer_capacity
|
||||
}
|
||||
|
||||
@@ -201,10 +207,16 @@ impl ClientRequest {
|
||||
///
|
||||
/// This method returns future that resolves to a ClientResponse
|
||||
pub fn send(mut self) -> SendRequest {
|
||||
match mem::replace(&mut self.conn, ConnectionType::Default) {
|
||||
let timeout = self.timeout.take();
|
||||
let send = match mem::replace(&mut self.conn, ConnectionType::Default) {
|
||||
ConnectionType::Default => SendRequest::new(self),
|
||||
ConnectionType::Connector(conn) => SendRequest::with_connector(self, conn),
|
||||
ConnectionType::Connection(conn) => SendRequest::with_connection(self, conn),
|
||||
};
|
||||
if let Some(timeout) = timeout {
|
||||
send.timeout(timeout)
|
||||
} else {
|
||||
send
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -221,7 +233,6 @@ impl fmt::Debug for ClientRequest {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// An HTTP Client request builder
|
||||
///
|
||||
/// This type can be used to construct an instance of `ClientRequest` through a
|
||||
@@ -464,12 +475,22 @@ impl ClientRequestBuilder {
|
||||
}
|
||||
|
||||
/// Set write buffer capacity
|
||||
pub fn buffer_capacity(&mut self,
|
||||
low_watermark: usize,
|
||||
high_watermark: usize) -> &mut Self
|
||||
{
|
||||
///
|
||||
/// Default buffer capacity is 32kb
|
||||
pub fn write_buffer_capacity(&mut self, cap: usize) -> &mut Self {
|
||||
if let Some(parts) = parts(&mut self.request, &self.err) {
|
||||
parts.buffer_capacity = Some((low_watermark, high_watermark));
|
||||
parts.buffer_capacity = cap;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set request timeout
|
||||
///
|
||||
/// Request timeout is a total time before response should be received.
|
||||
/// Default value is 5 seconds.
|
||||
pub fn timeout(&mut self, timeout: Duration) -> &mut Self {
|
||||
if let Some(parts) = parts(&mut self.request, &self.err) {
|
||||
parts.timeout = Some(timeout);
|
||||
}
|
||||
self
|
||||
}
|
||||
@@ -539,10 +560,14 @@ impl ClientRequestBuilder {
|
||||
|
||||
// set cookies
|
||||
if let Some(ref mut jar) = self.cookies {
|
||||
for cookie in jar.delta() {
|
||||
request.headers.append(
|
||||
header::COOKIE, HeaderValue::from_str(&cookie.to_string()).unwrap());
|
||||
let mut cookie = String::new();
|
||||
for c in jar.delta() {
|
||||
let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET);
|
||||
let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET);
|
||||
let _ = write!(&mut cookie, "; {}={}", name, value);
|
||||
}
|
||||
request.headers.insert(
|
||||
header::COOKIE, HeaderValue::from_str(&cookie.as_str()[2..]).unwrap());
|
||||
}
|
||||
request.body = body.into();
|
||||
Ok(request)
|
||||
@@ -593,3 +618,19 @@ fn parts<'a>(parts: &'a mut Option<ClientRequest>, err: &Option<HttpError>)
|
||||
}
|
||||
parts.as_mut()
|
||||
}
|
||||
|
||||
impl fmt::Debug for ClientRequestBuilder {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if let Some(ref parts) = self.request {
|
||||
let res = write!(f, "\nClientRequestBuilder {:?} {}:{}\n",
|
||||
parts.version, parts.method, parts.uri);
|
||||
let _ = write!(f, " headers:\n");
|
||||
for (key, val) in parts.headers.iter() {
|
||||
let _ = write!(f, " {:?}: {:?}\n", key, val);
|
||||
}
|
||||
res
|
||||
} else {
|
||||
write!(f, "ClientRequestBuilder(Consumed)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -129,3 +129,20 @@ impl Stream for ClientResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
let resp = ClientResponse::new(ClientMessage::default());
|
||||
resp.as_mut().headers.insert(
|
||||
header::COOKIE, HeaderValue::from_static("cookie1=value1"));
|
||||
resp.as_mut().headers.insert(
|
||||
header::COOKIE, HeaderValue::from_static("cookie2=value2"));
|
||||
|
||||
let dbg = format!("{:?}", resp);
|
||||
assert!(dbg.contains("ClientResponse"));
|
||||
}
|
||||
}
|
||||
|
@@ -13,10 +13,11 @@ use http::header::{HeaderValue, DATE,
|
||||
CONNECTION, CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING};
|
||||
use flate2::Compression;
|
||||
use flate2::write::{GzEncoder, DeflateEncoder};
|
||||
#[cfg(feature="brotli")]
|
||||
use brotli2::write::BrotliEncoder;
|
||||
|
||||
use body::{Body, Binary};
|
||||
use headers::ContentEncoding;
|
||||
use header::ContentEncoding;
|
||||
use server::WriterState;
|
||||
use server::shared::SharedBytes;
|
||||
use server::encoding::{ContentEncoder, TransferEncoding};
|
||||
@@ -24,8 +25,6 @@ use server::encoding::{ContentEncoder, TransferEncoding};
|
||||
use client::ClientRequest;
|
||||
|
||||
|
||||
const LOW_WATERMARK: usize = 1024;
|
||||
const HIGH_WATERMARK: usize = 8 * LOW_WATERMARK;
|
||||
const AVERAGE_HEADER_SIZE: usize = 30;
|
||||
|
||||
bitflags! {
|
||||
@@ -42,9 +41,8 @@ pub(crate) struct HttpClientWriter {
|
||||
written: u64,
|
||||
headers_size: u32,
|
||||
buffer: SharedBytes,
|
||||
buffer_capacity: usize,
|
||||
encoder: ContentEncoder,
|
||||
low: usize,
|
||||
high: usize,
|
||||
}
|
||||
|
||||
impl HttpClientWriter {
|
||||
@@ -55,10 +53,9 @@ impl HttpClientWriter {
|
||||
flags: Flags::empty(),
|
||||
written: 0,
|
||||
headers_size: 0,
|
||||
buffer_capacity: 0,
|
||||
buffer,
|
||||
encoder,
|
||||
low: LOW_WATERMARK,
|
||||
high: HIGH_WATERMARK,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,12 +67,6 @@ impl HttpClientWriter {
|
||||
// self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE)
|
||||
// }
|
||||
|
||||
/// Set write buffer capacity
|
||||
pub fn set_buffer_capacity(&mut self, low_watermark: usize, high_watermark: usize) {
|
||||
self.low = low_watermark;
|
||||
self.high = high_watermark;
|
||||
}
|
||||
|
||||
fn write_to_stream<T: AsyncWrite>(&mut self, stream: &mut T) -> io::Result<WriterState> {
|
||||
while !self.buffer.is_empty() {
|
||||
match stream.write(self.buffer.as_ref()) {
|
||||
@@ -87,7 +78,7 @@ impl HttpClientWriter {
|
||||
let _ = self.buffer.split_to(n);
|
||||
},
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
if self.buffer.len() > self.high {
|
||||
if self.buffer.len() > self.buffer_capacity {
|
||||
return Ok(WriterState::Pause)
|
||||
} else {
|
||||
return Ok(WriterState::Done)
|
||||
@@ -106,9 +97,6 @@ impl HttpClientWriter {
|
||||
// prepare task
|
||||
self.flags.insert(Flags::STARTED);
|
||||
self.encoder = content_encoder(self.buffer.clone(), msg);
|
||||
if let Some(capacity) = msg.buffer_capacity() {
|
||||
self.set_buffer_capacity(capacity.0, capacity.1);
|
||||
}
|
||||
|
||||
// render message
|
||||
{
|
||||
@@ -125,7 +113,9 @@ impl HttpClientWriter {
|
||||
|
||||
// status line
|
||||
let _ = write!(buffer, "{} {} {:?}\r\n",
|
||||
msg.method(), msg.uri().path(), msg.version());
|
||||
msg.method(),
|
||||
msg.uri().path_and_query().map(|u| u.as_str()).unwrap_or("/"),
|
||||
msg.version());
|
||||
|
||||
// write headers
|
||||
for (key, value) in msg.headers() {
|
||||
@@ -153,6 +143,8 @@ impl HttpClientWriter {
|
||||
self.written += bytes.len() as u64;
|
||||
self.encoder.write(bytes)?;
|
||||
}
|
||||
} else {
|
||||
self.buffer_capacity = msg.write_buffer_capacity();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -168,7 +160,7 @@ impl HttpClientWriter {
|
||||
}
|
||||
}
|
||||
|
||||
if self.buffer.len() > self.high {
|
||||
if self.buffer.len() > self.buffer_capacity {
|
||||
Ok(WriterState::Pause)
|
||||
} else {
|
||||
Ok(WriterState::Done)
|
||||
@@ -224,6 +216,7 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
|
||||
DeflateEncoder::new(transfer, Compression::default())),
|
||||
ContentEncoding::Gzip => ContentEncoder::Gzip(
|
||||
GzEncoder::new(transfer, Compression::default())),
|
||||
#[cfg(feature="brotli")]
|
||||
ContentEncoding::Br => ContentEncoder::Br(
|
||||
BrotliEncoder::new(transfer, 5)),
|
||||
ContentEncoding::Identity => ContentEncoder::Identity(transfer),
|
||||
@@ -273,6 +266,7 @@ fn content_encoder(buf: SharedBytes, req: &mut ClientRequest) -> ContentEncoder
|
||||
DeflateEncoder::new(transfer, Compression::default())),
|
||||
ContentEncoding::Gzip => ContentEncoder::Gzip(
|
||||
GzEncoder::new(transfer, Compression::default())),
|
||||
#[cfg(feature="brotli")]
|
||||
ContentEncoding::Br => ContentEncoder::Br(
|
||||
BrotliEncoder::new(transfer, 5)),
|
||||
ContentEncoding::Identity | ContentEncoding::Auto => ContentEncoder::Identity(transfer),
|
||||
|
@@ -191,7 +191,7 @@ impl<A, S> ActorHttpContext for HttpContext<A, S> where A: Actor<Context=Self>,
|
||||
if self.inner.alive() {
|
||||
match self.inner.poll(ctx) {
|
||||
Ok(Async::NotReady) | Ok(Async::Ready(())) => (),
|
||||
Err(_) => return Err(ErrorInternalServerError("error").into()),
|
||||
Err(_) => return Err(ErrorInternalServerError("error")),
|
||||
}
|
||||
}
|
||||
|
||||
|
69
src/error.rs
69
src/error.rs
@@ -575,68 +575,91 @@ impl<T> Responder for InternalError<T>
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate *BAD REQUEST* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorBadRequest<T>(err: T) -> InternalError<T> {
|
||||
InternalError::new(err, StatusCode::BAD_REQUEST)
|
||||
pub fn ErrorBadRequest<T>(err: T) -> Error
|
||||
where T: Send + Sync + fmt::Debug + 'static
|
||||
{
|
||||
InternalError::new(err, StatusCode::BAD_REQUEST).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate *UNAUTHORIZED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorUnauthorized<T>(err: T) -> InternalError<T> {
|
||||
InternalError::new(err, StatusCode::UNAUTHORIZED)
|
||||
pub fn ErrorUnauthorized<T>(err: T) -> Error
|
||||
where T: Send + Sync + fmt::Debug + 'static
|
||||
{
|
||||
InternalError::new(err, StatusCode::UNAUTHORIZED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate *FORBIDDEN* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorForbidden<T>(err: T) -> InternalError<T> {
|
||||
InternalError::new(err, StatusCode::FORBIDDEN)
|
||||
pub fn ErrorForbidden<T>(err: T) -> Error
|
||||
where T: Send + Sync + fmt::Debug + 'static
|
||||
{
|
||||
InternalError::new(err, StatusCode::FORBIDDEN).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate *NOT FOUND* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorNotFound<T>(err: T) -> InternalError<T> {
|
||||
InternalError::new(err, StatusCode::NOT_FOUND)
|
||||
pub fn ErrorNotFound<T>(err: T) -> Error
|
||||
where T: Send + Sync + fmt::Debug + 'static
|
||||
{
|
||||
InternalError::new(err, StatusCode::NOT_FOUND).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate *METHOD NOT ALLOWED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorMethodNotAllowed<T>(err: T) -> InternalError<T> {
|
||||
InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED)
|
||||
pub fn ErrorMethodNotAllowed<T>(err: T) -> Error
|
||||
where T: Send + Sync + fmt::Debug + 'static
|
||||
{
|
||||
InternalError::new(err, StatusCode::METHOD_NOT_ALLOWED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate *REQUEST TIMEOUT* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorRequestTimeout<T>(err: T) -> InternalError<T> {
|
||||
InternalError::new(err, StatusCode::REQUEST_TIMEOUT)
|
||||
pub fn ErrorRequestTimeout<T>(err: T) -> Error
|
||||
where T: Send + Sync + fmt::Debug + 'static
|
||||
{
|
||||
InternalError::new(err, StatusCode::REQUEST_TIMEOUT).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate *CONFLICT* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorConflict<T>(err: T) -> InternalError<T> {
|
||||
InternalError::new(err, StatusCode::CONFLICT)
|
||||
pub fn ErrorConflict<T>(err: T) -> Error
|
||||
where T: Send + Sync + fmt::Debug + 'static
|
||||
{
|
||||
InternalError::new(err, StatusCode::CONFLICT).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate *GONE* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorGone<T>(err: T) -> InternalError<T> {
|
||||
InternalError::new(err, StatusCode::GONE)
|
||||
pub fn ErrorGone<T>(err: T) -> Error
|
||||
where T: Send + Sync + fmt::Debug + 'static
|
||||
{
|
||||
InternalError::new(err, StatusCode::GONE).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate *PRECONDITION FAILED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorPreconditionFailed<T>(err: T) -> InternalError<T> {
|
||||
InternalError::new(err, StatusCode::PRECONDITION_FAILED)
|
||||
pub fn ErrorPreconditionFailed<T>(err: T) -> Error
|
||||
where T: Send + Sync + fmt::Debug + 'static
|
||||
{
|
||||
InternalError::new(err, StatusCode::PRECONDITION_FAILED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate *EXPECTATION FAILED* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorExpectationFailed<T>(err: T) -> InternalError<T> {
|
||||
InternalError::new(err, StatusCode::EXPECTATION_FAILED)
|
||||
pub fn ErrorExpectationFailed<T>(err: T) -> Error
|
||||
where T: Send + Sync + fmt::Debug + 'static
|
||||
{
|
||||
InternalError::new(err, StatusCode::EXPECTATION_FAILED).into()
|
||||
}
|
||||
|
||||
/// Helper function that creates wrapper of any error and generate *INTERNAL SERVER ERROR* response.
|
||||
/// Helper function that creates wrapper of any error and
|
||||
/// generate *INTERNAL SERVER ERROR* response.
|
||||
#[allow(non_snake_case)]
|
||||
pub fn ErrorInternalServerError<T>(err: T) -> InternalError<T> {
|
||||
InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR)
|
||||
pub fn ErrorInternalServerError<T>(err: T) -> Error
|
||||
where T: Send + Sync + fmt::Debug + 'static
|
||||
{
|
||||
InternalError::new(err, StatusCode::INTERNAL_SERVER_ERROR).into()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
118
src/fs.rs
118
src/fs.rs
@@ -21,11 +21,11 @@ use mime_guess::get_mime_type;
|
||||
use header;
|
||||
use error::Error;
|
||||
use param::FromParam;
|
||||
use handler::{Handler, Responder};
|
||||
use handler::{Handler, RouteHandler, WrapHandler, Responder, Reply};
|
||||
use httpmessage::HttpMessage;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use httpcodes::{HttpOk, HttpFound, HttpMethodNotAllowed};
|
||||
use httpcodes::{HttpOk, HttpFound, HttpNotFound, HttpMethodNotAllowed};
|
||||
|
||||
/// A file with an associated name; responds with the Content-Type based on the
|
||||
/// file extension.
|
||||
@@ -36,6 +36,7 @@ pub struct NamedFile {
|
||||
md: Metadata,
|
||||
modified: Option<SystemTime>,
|
||||
cpu_pool: Option<CpuPool>,
|
||||
only_get: bool,
|
||||
}
|
||||
|
||||
impl NamedFile {
|
||||
@@ -54,7 +55,14 @@ impl NamedFile {
|
||||
let path = path.as_ref().to_path_buf();
|
||||
let modified = md.modified().ok();
|
||||
let cpu_pool = None;
|
||||
Ok(NamedFile{path, file, md, modified, cpu_pool})
|
||||
Ok(NamedFile{path, file, md, modified, cpu_pool, only_get: false})
|
||||
}
|
||||
|
||||
/// Allow only GET and HEAD methods
|
||||
#[inline]
|
||||
pub fn only_get(mut self) -> Self {
|
||||
self.only_get = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Returns reference to the underlying `File` object.
|
||||
@@ -168,7 +176,7 @@ impl Responder for NamedFile {
|
||||
type Error = io::Error;
|
||||
|
||||
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, io::Error> {
|
||||
if *req.method() != Method::GET && *req.method() != Method::HEAD {
|
||||
if self.only_get && *req.method() != Method::GET && *req.method() != Method::HEAD {
|
||||
return Ok(HttpMethodNotAllowed.build()
|
||||
.header(header::http::CONTENT_TYPE, "text/plain")
|
||||
.header(header::http::ALLOW, "GET, HEAD")
|
||||
@@ -215,7 +223,9 @@ impl Responder for NamedFile {
|
||||
return Ok(resp.status(StatusCode::NOT_MODIFIED).finish().unwrap())
|
||||
}
|
||||
|
||||
if *req.method() == Method::GET {
|
||||
if *req.method() == Method::HEAD {
|
||||
Ok(resp.finish().unwrap())
|
||||
} else {
|
||||
let reader = ChunkedReadFile {
|
||||
size: self.md.len(),
|
||||
offset: 0,
|
||||
@@ -224,8 +234,6 @@ impl Responder for NamedFile {
|
||||
fut: None,
|
||||
};
|
||||
Ok(resp.streaming(reader).unwrap())
|
||||
} else {
|
||||
Ok(resp.finish().unwrap())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -354,27 +362,6 @@ impl Responder for Directory {
|
||||
}
|
||||
}
|
||||
|
||||
/// This enum represents all filesystem elements.
|
||||
pub enum FilesystemElement {
|
||||
File(NamedFile),
|
||||
Directory(Directory),
|
||||
Redirect(HttpResponse),
|
||||
}
|
||||
|
||||
impl Responder for FilesystemElement {
|
||||
type Item = HttpResponse;
|
||||
type Error = io::Error;
|
||||
|
||||
fn respond_to(self, req: HttpRequest) -> Result<HttpResponse, io::Error> {
|
||||
match self {
|
||||
FilesystemElement::File(file) => file.respond_to(req),
|
||||
FilesystemElement::Directory(dir) => dir.respond_to(req),
|
||||
FilesystemElement::Redirect(resp) => Ok(resp),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Static files handling
|
||||
///
|
||||
/// `StaticFile` handler must be registered with `Application::handler()` method,
|
||||
@@ -390,23 +377,24 @@ impl Responder for FilesystemElement {
|
||||
/// .finish();
|
||||
/// }
|
||||
/// ```
|
||||
pub struct StaticFiles {
|
||||
pub struct StaticFiles<S> {
|
||||
directory: PathBuf,
|
||||
accessible: bool,
|
||||
index: Option<String>,
|
||||
show_index: bool,
|
||||
cpu_pool: CpuPool,
|
||||
default: Box<RouteHandler<S>>,
|
||||
_chunk_size: usize,
|
||||
_follow_symlinks: bool,
|
||||
}
|
||||
|
||||
impl StaticFiles {
|
||||
impl<S: 'static> StaticFiles<S> {
|
||||
/// Create new `StaticFiles` instance
|
||||
///
|
||||
/// `dir` - base directory
|
||||
///
|
||||
/// `index` - show index for directory
|
||||
pub fn new<T: Into<PathBuf>>(dir: T, index: bool) -> StaticFiles {
|
||||
pub fn new<T: Into<PathBuf>>(dir: T, index: bool) -> StaticFiles<S> {
|
||||
let dir = dir.into();
|
||||
|
||||
let (dir, access) = match dir.canonicalize() {
|
||||
@@ -430,6 +418,7 @@ impl StaticFiles {
|
||||
index: None,
|
||||
show_index: index,
|
||||
cpu_pool: CpuPool::new(40),
|
||||
default: Box::new(WrapHandler::new(|_| HttpNotFound)),
|
||||
_chunk_size: 0,
|
||||
_follow_symlinks: false,
|
||||
}
|
||||
@@ -439,28 +428,30 @@ impl StaticFiles {
|
||||
///
|
||||
/// Redirects to specific index file for directory "/" instead of
|
||||
/// showing files listing.
|
||||
pub fn index_file<T: Into<String>>(mut self, index: T) -> StaticFiles {
|
||||
pub fn index_file<T: Into<String>>(mut self, index: T) -> StaticFiles<S> {
|
||||
self.index = Some(index.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets default resource which is used when no matched file could be found.
|
||||
pub fn default_handler<H: Handler<S>>(mut self, handler: H) -> StaticFiles<S> {
|
||||
self.default = Box::new(WrapHandler::new(handler));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<S> Handler<S> for StaticFiles {
|
||||
type Result = Result<FilesystemElement, io::Error>;
|
||||
impl<S: 'static> Handler<S> for StaticFiles<S> {
|
||||
type Result = Result<Reply, Error>;
|
||||
|
||||
fn handle(&mut self, req: HttpRequest<S>) -> Self::Result {
|
||||
if !self.accessible {
|
||||
Err(io::Error::new(io::ErrorKind::NotFound, "not found"))
|
||||
Ok(self.default.handle(req))
|
||||
} else {
|
||||
let path = if let Some(path) = req.match_info().get("tail") {
|
||||
path
|
||||
} else {
|
||||
return Err(io::Error::new(io::ErrorKind::NotFound, "not found"))
|
||||
let relpath = match req.match_info().get("tail").map(PathBuf::from_param) {
|
||||
Some(Ok(path)) => path,
|
||||
_ => return Ok(self.default.handle(req))
|
||||
};
|
||||
|
||||
let relpath = PathBuf::from_param(path)
|
||||
.map_err(|_| io::Error::new(io::ErrorKind::NotFound, "not found"))?;
|
||||
|
||||
// full filepath
|
||||
let path = self.directory.join(&relpath).canonicalize()?;
|
||||
|
||||
@@ -474,20 +465,21 @@ impl<S> Handler<S> for StaticFiles {
|
||||
new_path.push('/');
|
||||
}
|
||||
new_path.push_str(redir_index);
|
||||
Ok(FilesystemElement::Redirect(
|
||||
HttpFound
|
||||
.build()
|
||||
.header(header::http::LOCATION, new_path.as_str())
|
||||
.finish().unwrap()))
|
||||
HttpFound.build()
|
||||
.header(header::http::LOCATION, new_path.as_str())
|
||||
.finish().unwrap()
|
||||
.respond_to(req.without_state())
|
||||
} else if self.show_index {
|
||||
Ok(FilesystemElement::Directory(
|
||||
Directory::new(self.directory.clone(), path)))
|
||||
Directory::new(self.directory.clone(), path)
|
||||
.respond_to(req.without_state())?
|
||||
.respond_to(req.without_state())
|
||||
} else {
|
||||
Err(io::Error::new(io::ErrorKind::NotFound, "not found"))
|
||||
Ok(self.default.handle(req))
|
||||
}
|
||||
} else {
|
||||
Ok(FilesystemElement::File(
|
||||
NamedFile::open(path)?.set_cpu_pool(self.cpu_pool.clone())))
|
||||
NamedFile::open(path)?.set_cpu_pool(self.cpu_pool.clone())
|
||||
.respond_to(req.without_state())?
|
||||
.respond_to(req.without_state())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -517,25 +509,38 @@ mod tests {
|
||||
let req = TestRequest::default().method(Method::POST).finish();
|
||||
let file = NamedFile::open("Cargo.toml").unwrap();
|
||||
|
||||
let resp = file.respond_to(req).unwrap();
|
||||
let resp = file.only_get().respond_to(req).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_named_file_any_method() {
|
||||
let req = TestRequest::default().method(Method::POST).finish();
|
||||
let file = NamedFile::open("Cargo.toml").unwrap();
|
||||
let resp = file.respond_to(req).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_static_files() {
|
||||
let mut st = StaticFiles::new(".", true);
|
||||
st.accessible = false;
|
||||
assert!(st.handle(HttpRequest::default()).is_err());
|
||||
let resp = st.handle(HttpRequest::default()).respond_to(HttpRequest::default()).unwrap();
|
||||
let resp = resp.as_response().expect("HTTP Response");
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
|
||||
st.accessible = true;
|
||||
st.show_index = false;
|
||||
assert!(st.handle(HttpRequest::default()).is_err());
|
||||
let resp = st.handle(HttpRequest::default()).respond_to(HttpRequest::default()).unwrap();
|
||||
let resp = resp.as_response().expect("HTTP Response");
|
||||
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
req.match_info_mut().add("tail", "");
|
||||
|
||||
st.show_index = true;
|
||||
let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap();
|
||||
let resp = resp.as_response().expect("HTTP Response");
|
||||
assert_eq!(resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/html; charset=utf-8");
|
||||
assert!(resp.body().is_binary());
|
||||
assert!(format!("{:?}", resp.body()).contains("README.md"));
|
||||
@@ -548,6 +553,7 @@ mod tests {
|
||||
req.match_info_mut().add("tail", "guide");
|
||||
|
||||
let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap();
|
||||
let resp = resp.as_response().expect("HTTP Response");
|
||||
assert_eq!(resp.status(), StatusCode::FOUND);
|
||||
assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/guide/index.html");
|
||||
|
||||
@@ -555,6 +561,7 @@ mod tests {
|
||||
req.match_info_mut().add("tail", "guide/");
|
||||
|
||||
let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap();
|
||||
let resp = resp.as_response().expect("HTTP Response");
|
||||
assert_eq!(resp.status(), StatusCode::FOUND);
|
||||
assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/guide/index.html");
|
||||
}
|
||||
@@ -566,6 +573,7 @@ mod tests {
|
||||
req.match_info_mut().add("tail", "examples/basics");
|
||||
|
||||
let resp = st.handle(req).respond_to(HttpRequest::default()).unwrap();
|
||||
let resp = resp.as_response().expect("HTTP Response");
|
||||
assert_eq!(resp.status(), StatusCode::FOUND);
|
||||
assert_eq!(resp.headers().get(header::LOCATION).unwrap(), "/examples/basics/Cargo.toml");
|
||||
}
|
||||
|
@@ -34,6 +34,63 @@ pub trait Responder {
|
||||
fn respond_to(self, req: HttpRequest) -> Result<Self::Item, Self::Error>;
|
||||
}
|
||||
|
||||
/// Combines two different responder types into a single type
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// # extern crate futures;
|
||||
/// # use futures::future::Future;
|
||||
/// use actix_web::AsyncResponder;
|
||||
/// use futures::future::result;
|
||||
/// use actix_web::{Either, Error, HttpRequest, HttpResponse, httpcodes};
|
||||
///
|
||||
/// type RegisterResult = Either<HttpResponse, Box<Future<Item=HttpResponse, Error=Error>>>;
|
||||
///
|
||||
///
|
||||
/// fn index(req: HttpRequest) -> RegisterResult {
|
||||
/// if is_a_variant() { // <- choose variant A
|
||||
/// Either::A(
|
||||
/// httpcodes::HttpBadRequest.with_body("Bad data"))
|
||||
/// } else {
|
||||
/// Either::B( // <- variant B
|
||||
/// result(HttpResponse::Ok()
|
||||
/// .content_type("text/html")
|
||||
/// .body(format!("Hello!"))
|
||||
/// .map_err(|e| e.into())).responder())
|
||||
/// }
|
||||
/// }
|
||||
/// # fn is_a_variant() -> bool { true }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub enum Either<A, B> {
|
||||
/// First branch of the type
|
||||
A(A),
|
||||
/// Second branch of the type
|
||||
B(B),
|
||||
}
|
||||
|
||||
impl<A, B> Responder for Either<A, B>
|
||||
where A: Responder, B: Responder
|
||||
{
|
||||
type Item = Reply;
|
||||
type Error = Error;
|
||||
|
||||
fn respond_to(self, req: HttpRequest) -> Result<Reply, Error> {
|
||||
match self {
|
||||
Either::A(a) => match a.respond_to(req) {
|
||||
Ok(val) => Ok(val.into()),
|
||||
Err(err) => Err(err.into()),
|
||||
},
|
||||
Either::B(b) => match b.respond_to(req) {
|
||||
Ok(val) => Ok(val.into()),
|
||||
Err(err) => Err(err.into()),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Convenience trait that convert `Future` object into `Boxed` future
|
||||
pub trait AsyncResponder<I, E>: Sized {
|
||||
|
@@ -119,6 +119,7 @@ pub enum ContentEncoding {
|
||||
/// Automatically select encoding based on encoding negotiation
|
||||
Auto,
|
||||
/// A format using the Brotli algorithm
|
||||
#[cfg(feature="brotli")]
|
||||
Br,
|
||||
/// A format using the zlib structure with deflate algorithm
|
||||
Deflate,
|
||||
@@ -141,15 +142,19 @@ impl ContentEncoding {
|
||||
#[inline]
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match *self {
|
||||
#[cfg(feature="brotli")]
|
||||
ContentEncoding::Br => "br",
|
||||
ContentEncoding::Gzip => "gzip",
|
||||
ContentEncoding::Deflate => "deflate",
|
||||
ContentEncoding::Identity | ContentEncoding::Auto => "identity",
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// default quality value
|
||||
pub fn quality(&self) -> f64 {
|
||||
match *self {
|
||||
#[cfg(feature="brotli")]
|
||||
ContentEncoding::Br => 1.1,
|
||||
ContentEncoding::Gzip => 1.0,
|
||||
ContentEncoding::Deflate => 0.9,
|
||||
@@ -162,11 +167,11 @@ impl ContentEncoding {
|
||||
impl<'a> From<&'a str> for ContentEncoding {
|
||||
fn from(s: &'a str) -> ContentEncoding {
|
||||
match s.trim().to_lowercase().as_ref() {
|
||||
#[cfg(feature="brotli")]
|
||||
"br" => ContentEncoding::Br,
|
||||
"gzip" => ContentEncoding::Gzip,
|
||||
"deflate" => ContentEncoding::Deflate,
|
||||
"identity" => ContentEncoding::Identity,
|
||||
_ => ContentEncoding::Auto,
|
||||
_ => ContentEncoding::Identity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,71 +1,13 @@
|
||||
use std::{str, mem, ptr, slice};
|
||||
use std::{mem, ptr, slice};
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::{self, Write};
|
||||
use std::rc::Rc;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
use std::collections::VecDeque;
|
||||
use time;
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use http::Version;
|
||||
|
||||
use httprequest::HttpInnerMessage;
|
||||
|
||||
// "Sun, 06 Nov 1994 08:49:37 GMT".len()
|
||||
pub(crate) const DATE_VALUE_LENGTH: usize = 29;
|
||||
|
||||
pub(crate) fn date(dst: &mut BytesMut) {
|
||||
CACHED.with(|cache| {
|
||||
let mut buf: [u8; 39] = unsafe { mem::uninitialized() };
|
||||
buf[..6].copy_from_slice(b"date: ");
|
||||
buf[6..35].copy_from_slice(cache.borrow().buffer());
|
||||
buf[35..].copy_from_slice(b"\r\n\r\n");
|
||||
dst.extend_from_slice(&buf);
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn date_value(dst: &mut BytesMut) {
|
||||
CACHED.with(|cache| {
|
||||
dst.extend_from_slice(cache.borrow().buffer());
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn update_date() {
|
||||
CACHED.with(|cache| {
|
||||
cache.borrow_mut().update();
|
||||
});
|
||||
}
|
||||
|
||||
struct CachedDate {
|
||||
bytes: [u8; DATE_VALUE_LENGTH],
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
thread_local!(static CACHED: RefCell<CachedDate> = RefCell::new(CachedDate {
|
||||
bytes: [0; DATE_VALUE_LENGTH],
|
||||
pos: 0,
|
||||
}));
|
||||
|
||||
impl CachedDate {
|
||||
fn buffer(&self) -> &[u8] {
|
||||
&self.bytes[..]
|
||||
}
|
||||
|
||||
fn update(&mut self) {
|
||||
self.pos = 0;
|
||||
write!(self, "{}", time::at_utc(time::get_time()).rfc822()).unwrap();
|
||||
assert_eq!(self.pos, DATE_VALUE_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Write for CachedDate {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
let len = s.len();
|
||||
self.bytes[self.pos..self.pos + len].copy_from_slice(s.as_bytes());
|
||||
self.pos += len;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal use only! unsafe
|
||||
pub(crate) struct SharedMessagePool(RefCell<VecDeque<Rc<HttpInnerMessage>>>);
|
||||
|
||||
@@ -202,7 +144,7 @@ pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesM
|
||||
}
|
||||
}
|
||||
|
||||
bytes.extend_from_slice(&buf);
|
||||
bytes.put_slice(&buf);
|
||||
if four {
|
||||
bytes.put(b' ');
|
||||
}
|
||||
@@ -214,7 +156,7 @@ pub(crate) fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
|
||||
b'n',b't',b'-',b'l',b'e',b'n',b'g',
|
||||
b't',b'h',b':',b' ',b'0',b'\r',b'\n'];
|
||||
buf[18] = (n as u8) + b'0';
|
||||
bytes.extend_from_slice(&buf);
|
||||
bytes.put_slice(&buf);
|
||||
} else if n < 100 {
|
||||
let mut buf: [u8; 22] = [b'\r',b'\n',b'c',b'o',b'n',b't',b'e',
|
||||
b'n',b't',b'-',b'l',b'e',b'n',b'g',
|
||||
@@ -224,7 +166,7 @@ pub(crate) fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
|
||||
ptr::copy_nonoverlapping(
|
||||
DEC_DIGITS_LUT.as_ptr().offset(d1 as isize), buf.as_mut_ptr().offset(18), 2);
|
||||
}
|
||||
bytes.extend_from_slice(&buf);
|
||||
bytes.put_slice(&buf);
|
||||
} else if n < 1000 {
|
||||
let mut buf: [u8; 23] = [b'\r',b'\n',b'c',b'o',b'n',b't',b'e',
|
||||
b'n',b't',b'-',b'l',b'e',b'n',b'g',
|
||||
@@ -238,9 +180,9 @@ pub(crate) fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
|
||||
// decode last 1
|
||||
buf[18] = (n as u8) + b'0';
|
||||
|
||||
bytes.extend_from_slice(&buf);
|
||||
bytes.put_slice(&buf);
|
||||
} else {
|
||||
bytes.extend_from_slice(b"\r\ncontent-length: ");
|
||||
bytes.put_slice(b"\r\ncontent-length: ");
|
||||
convert_usize(n, bytes);
|
||||
}
|
||||
}
|
||||
@@ -299,20 +241,6 @@ pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_date_len() {
|
||||
assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date() {
|
||||
let mut buf1 = BytesMut::new();
|
||||
date(&mut buf1);
|
||||
let mut buf2 = BytesMut::new();
|
||||
date(&mut buf2);
|
||||
assert_eq!(buf1, buf2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_write_content_length() {
|
||||
let mut bytes = BytesMut::new();
|
||||
|
@@ -97,7 +97,8 @@ impl HttpRequest<()> {
|
||||
/// Construct a new Request.
|
||||
#[inline]
|
||||
pub fn new(method: Method, uri: Uri,
|
||||
version: Version, headers: HeaderMap, payload: Option<Payload>) -> HttpRequest
|
||||
version: Version, headers: HeaderMap, payload: Option<Payload>)
|
||||
-> HttpRequest
|
||||
{
|
||||
HttpRequest(
|
||||
SharedHttpInnerMessage::from_message(HttpInnerMessage {
|
||||
@@ -120,7 +121,7 @@ impl HttpRequest<()> {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(inline_always))]
|
||||
#[cfg_attr(feature="cargo-clippy", allow(inline_always))]
|
||||
pub(crate) fn from_message(msg: SharedHttpInnerMessage) -> HttpRequest {
|
||||
HttpRequest(msg, None, None)
|
||||
}
|
||||
@@ -202,11 +203,10 @@ impl<S> HttpRequest<S> {
|
||||
#[inline]
|
||||
pub fn uri(&self) -> &Uri { &self.as_ref().uri }
|
||||
|
||||
#[doc(hidden)]
|
||||
#[inline]
|
||||
/// Modify the Request Uri.
|
||||
/// Returns mutable the Request Uri.
|
||||
///
|
||||
/// This might be useful for middlewares, i.e. path normalization
|
||||
/// This might be useful for middlewares, e.g. path normalization.
|
||||
#[inline]
|
||||
pub fn uri_mut(&mut self) -> &mut Uri {
|
||||
&mut self.as_mut().uri
|
||||
}
|
||||
@@ -221,7 +221,9 @@ impl<S> HttpRequest<S> {
|
||||
self.as_ref().version
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
///Returns mutable Request's headers.
|
||||
///
|
||||
///This is intended to be used by middleware.
|
||||
#[inline]
|
||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||
&mut self.as_mut().headers
|
||||
@@ -335,7 +337,11 @@ impl<S> HttpRequest<S> {
|
||||
let mut cookies = Vec::new();
|
||||
for hdr in msg.headers.get_all(header::COOKIE) {
|
||||
let s = str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?;
|
||||
cookies.push(Cookie::parse_encoded(s)?.into_owned());
|
||||
for cookie_str in s.split(';').map(|s| s.trim()) {
|
||||
if !cookie_str.is_empty() {
|
||||
cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned());
|
||||
}
|
||||
}
|
||||
}
|
||||
msg.cookies = Some(cookies)
|
||||
}
|
||||
@@ -384,6 +390,15 @@ impl<S> HttpRequest<S> {
|
||||
self.as_ref().method == Method::CONNECT
|
||||
}
|
||||
|
||||
/// Set read buffer capacity
|
||||
///
|
||||
/// Default buffer capacity is 32Kb.
|
||||
pub fn set_read_buffer_capacity(&mut self, cap: usize) {
|
||||
if let Some(ref mut payload) = self.as_mut().payload {
|
||||
payload.set_read_buffer_capacity(cap)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn payload(&self) -> &Payload {
|
||||
let msg = self.as_mut();
|
||||
|
@@ -1,7 +1,8 @@
|
||||
//! Http response
|
||||
use std::{mem, str, fmt};
|
||||
use std::rc::Rc;
|
||||
use std::io::Write;
|
||||
use std::cell::RefCell;
|
||||
use std::cell::UnsafeCell;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use cookie::{Cookie, CookieJar};
|
||||
@@ -18,6 +19,10 @@ use handler::Responder;
|
||||
use header::{Header, IntoHeaderValue, ContentEncoding};
|
||||
use httprequest::HttpRequest;
|
||||
|
||||
/// max write buffer size 64k
|
||||
pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536;
|
||||
|
||||
|
||||
/// Represents various types of connection
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
pub enum ConnectionType {
|
||||
@@ -30,12 +35,12 @@ pub enum ConnectionType {
|
||||
}
|
||||
|
||||
/// An HTTP Response
|
||||
pub struct HttpResponse(Option<Box<InnerHttpResponse>>);
|
||||
pub struct HttpResponse(Option<Box<InnerHttpResponse>>, Rc<UnsafeCell<Pool>>);
|
||||
|
||||
impl Drop for HttpResponse {
|
||||
fn drop(&mut self) {
|
||||
if let Some(inner) = self.0.take() {
|
||||
Pool::release(inner)
|
||||
Pool::release(&self.1, inner)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -57,8 +62,10 @@ impl HttpResponse {
|
||||
/// Create http response builder with specific status.
|
||||
#[inline]
|
||||
pub fn build(status: StatusCode) -> HttpResponseBuilder {
|
||||
let (msg, pool) = Pool::get(status);
|
||||
HttpResponseBuilder {
|
||||
response: Some(Pool::get(status)),
|
||||
response: Some(msg),
|
||||
pool: Some(pool),
|
||||
err: None,
|
||||
cookies: None,
|
||||
}
|
||||
@@ -67,7 +74,8 @@ impl HttpResponse {
|
||||
/// Constructs a response
|
||||
#[inline]
|
||||
pub fn new(status: StatusCode, body: Body) -> HttpResponse {
|
||||
HttpResponse(Some(Pool::with_body(status, body)))
|
||||
let (msg, pool) = Pool::with_body(status, body);
|
||||
HttpResponse(Some(msg), pool)
|
||||
}
|
||||
|
||||
/// Constructs a error response
|
||||
@@ -198,6 +206,16 @@ impl HttpResponse {
|
||||
pub(crate) fn set_response_size(&mut self, size: u64) {
|
||||
self.get_mut().response_size = size;
|
||||
}
|
||||
|
||||
/// Set write buffer capacity
|
||||
pub fn write_buffer_capacity(&self) -> usize {
|
||||
self.get_ref().write_capacity
|
||||
}
|
||||
|
||||
/// Set write buffer capacity
|
||||
pub fn set_write_buffer_capacity(&mut self, cap: usize) {
|
||||
self.get_mut().write_capacity = cap;
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for HttpResponse {
|
||||
@@ -218,9 +236,9 @@ impl fmt::Debug for HttpResponse {
|
||||
///
|
||||
/// This type can be used to construct an instance of `HttpResponse` through a
|
||||
/// builder-like pattern.
|
||||
#[derive(Debug)]
|
||||
pub struct HttpResponseBuilder {
|
||||
response: Option<Box<InnerHttpResponse>>,
|
||||
pool: Option<Rc<UnsafeCell<Pool>>>,
|
||||
err: Option<HttpError>,
|
||||
cookies: Option<CookieJar>,
|
||||
}
|
||||
@@ -462,6 +480,20 @@ impl HttpResponseBuilder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set write buffer capacity
|
||||
///
|
||||
/// This parameter makes sense only for streaming response
|
||||
/// or actor. If write buffer reaches specified capacity, stream or actor get
|
||||
/// paused.
|
||||
///
|
||||
/// Default write buffer capacity is 64kb
|
||||
pub fn write_buffer_capacity(&mut self, cap: usize) -> &mut Self {
|
||||
if let Some(parts) = parts(&mut self.response, &self.err) {
|
||||
parts.write_capacity = cap;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a body and generate `HttpResponse`.
|
||||
///
|
||||
/// `HttpResponseBuilder` can not be used after this call.
|
||||
@@ -478,7 +510,7 @@ impl HttpResponseBuilder {
|
||||
}
|
||||
}
|
||||
response.body = body.into();
|
||||
Ok(HttpResponse(Some(response)))
|
||||
Ok(HttpResponse(Some(response), self.pool.take().unwrap()))
|
||||
}
|
||||
|
||||
/// Set a streaming body and generate `HttpResponse`.
|
||||
@@ -519,6 +551,7 @@ impl HttpResponseBuilder {
|
||||
pub fn take(&mut self) -> HttpResponseBuilder {
|
||||
HttpResponseBuilder {
|
||||
response: self.response.take(),
|
||||
pool: self.pool.take(),
|
||||
err: self.err.take(),
|
||||
cookies: self.cookies.take(),
|
||||
}
|
||||
@@ -692,6 +725,7 @@ struct InnerHttpResponse {
|
||||
chunked: Option<bool>,
|
||||
encoding: Option<ContentEncoding>,
|
||||
connection_type: Option<ConnectionType>,
|
||||
write_capacity: usize,
|
||||
response_size: u64,
|
||||
error: Option<Error>,
|
||||
}
|
||||
@@ -710,6 +744,7 @@ impl InnerHttpResponse {
|
||||
encoding: None,
|
||||
connection_type: None,
|
||||
response_size: 0,
|
||||
write_capacity: MAX_WRITE_BUFFER_SIZE,
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
@@ -718,54 +753,56 @@ impl InnerHttpResponse {
|
||||
/// Internal use only! unsafe
|
||||
struct Pool(VecDeque<Box<InnerHttpResponse>>);
|
||||
|
||||
thread_local!(static POOL: RefCell<Pool> =
|
||||
RefCell::new(Pool(VecDeque::with_capacity(128))));
|
||||
thread_local!(static POOL: Rc<UnsafeCell<Pool>> =
|
||||
Rc::new(UnsafeCell::new(Pool(VecDeque::with_capacity(128)))));
|
||||
|
||||
impl Pool {
|
||||
|
||||
#[inline]
|
||||
fn get(status: StatusCode) -> Box<InnerHttpResponse> {
|
||||
fn get(status: StatusCode) -> (Box<InnerHttpResponse>, Rc<UnsafeCell<Pool>>) {
|
||||
POOL.with(|pool| {
|
||||
if let Some(mut resp) = pool.borrow_mut().0.pop_front() {
|
||||
let p = unsafe{&mut *pool.as_ref().get()};
|
||||
if let Some(mut resp) = p.0.pop_front() {
|
||||
resp.body = Body::Empty;
|
||||
resp.status = status;
|
||||
resp
|
||||
(resp, Rc::clone(pool))
|
||||
} else {
|
||||
Box::new(InnerHttpResponse::new(status, Body::Empty))
|
||||
(Box::new(InnerHttpResponse::new(status, Body::Empty)), Rc::clone(pool))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_body(status: StatusCode, body: Body) -> Box<InnerHttpResponse> {
|
||||
fn with_body(status: StatusCode, body: Body)
|
||||
-> (Box<InnerHttpResponse>, Rc<UnsafeCell<Pool>>) {
|
||||
POOL.with(|pool| {
|
||||
if let Some(mut resp) = pool.borrow_mut().0.pop_front() {
|
||||
let p = unsafe{&mut *pool.as_ref().get()};
|
||||
if let Some(mut resp) = p.0.pop_front() {
|
||||
resp.status = status;
|
||||
resp.body = body;
|
||||
resp
|
||||
(resp, Rc::clone(pool))
|
||||
} else {
|
||||
Box::new(InnerHttpResponse::new(status, body))
|
||||
(Box::new(InnerHttpResponse::new(status, body)), Rc::clone(pool))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(boxed_local, inline_always))]
|
||||
fn release(mut inner: Box<InnerHttpResponse>) {
|
||||
POOL.with(|pool| {
|
||||
let v = &mut pool.borrow_mut().0;
|
||||
if v.len() < 128 {
|
||||
inner.headers.clear();
|
||||
inner.version = None;
|
||||
inner.chunked = None;
|
||||
inner.reason = None;
|
||||
inner.encoding = None;
|
||||
inner.connection_type = None;
|
||||
inner.response_size = 0;
|
||||
inner.error = None;
|
||||
v.push_front(inner);
|
||||
}
|
||||
})
|
||||
fn release(pool: &Rc<UnsafeCell<Pool>>, mut inner: Box<InnerHttpResponse>) {
|
||||
let pool = unsafe{&mut *pool.as_ref().get()};
|
||||
if pool.0.len() < 128 {
|
||||
inner.headers.clear();
|
||||
inner.version = None;
|
||||
inner.chunked = None;
|
||||
inner.reason = None;
|
||||
inner.encoding = None;
|
||||
inner.connection_type = None;
|
||||
inner.response_size = 0;
|
||||
inner.error = None;
|
||||
inner.write_capacity = MAX_WRITE_BUFFER_SIZE;
|
||||
pool.0.push_front(inner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -781,7 +818,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
let resp = HttpResponse::Ok().finish().unwrap();
|
||||
let resp = HttpResponse::Ok()
|
||||
.header(COOKIE, HeaderValue::from_static("cookie1=value1; "))
|
||||
.header(COOKIE, HeaderValue::from_static("cookie2=value2; "))
|
||||
.finish().unwrap();
|
||||
let dbg = format!("{:?}", resp);
|
||||
assert!(dbg.contains("HttpResponse"));
|
||||
}
|
||||
@@ -789,8 +829,8 @@ mod tests {
|
||||
#[test]
|
||||
fn test_response_cookies() {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(COOKIE,
|
||||
HeaderValue::from_static("cookie1=value1; cookie2=value2"));
|
||||
headers.insert(COOKIE, HeaderValue::from_static("cookie1=value1"));
|
||||
headers.insert(COOKIE, HeaderValue::from_static("cookie2=value2"));
|
||||
|
||||
let req = HttpRequest::new(
|
||||
Method::GET, Uri::from_str("/").unwrap(), Version::HTTP_11, headers, None);
|
||||
@@ -813,7 +853,7 @@ mod tests {
|
||||
let mut val: Vec<_> = resp.headers().get_all("Set-Cookie")
|
||||
.iter().map(|v| v.to_str().unwrap().to_owned()).collect();
|
||||
val.sort();
|
||||
assert!(val[0].starts_with("cookie1=; Max-Age=0;"));
|
||||
assert!(val[0].starts_with("cookie2=; Max-Age=0;"));
|
||||
assert_eq!(
|
||||
val[1],"name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400");
|
||||
}
|
||||
|
24
src/json.rs
24
src/json.rs
@@ -2,6 +2,7 @@ use bytes::{Bytes, BytesMut};
|
||||
use futures::{Poll, Future, Stream};
|
||||
use http::header::CONTENT_LENGTH;
|
||||
|
||||
use mime;
|
||||
use serde_json;
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
@@ -82,7 +83,6 @@ impl<T: Serialize> Responder for Json<T> {
|
||||
/// ```
|
||||
pub struct JsonBody<T, U: DeserializeOwned>{
|
||||
limit: usize,
|
||||
ct: &'static str,
|
||||
req: Option<T>,
|
||||
fut: Option<Box<Future<Item=U, Error=JsonPayloadError>>>,
|
||||
}
|
||||
@@ -95,7 +95,6 @@ impl<T, U: DeserializeOwned> JsonBody<T, U> {
|
||||
limit: 262_144,
|
||||
req: Some(req),
|
||||
fut: None,
|
||||
ct: "application/json",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,15 +103,6 @@ impl<T, U: DeserializeOwned> JsonBody<T, U> {
|
||||
self.limit = limit;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set allowed content type.
|
||||
///
|
||||
/// By default *application/json* content type is used. Set content type
|
||||
/// to empty string if you want to disable content type check.
|
||||
pub fn content_type(mut self, ct: &'static str) -> Self {
|
||||
self.ct = ct;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U: DeserializeOwned + 'static> Future for JsonBody<T, U>
|
||||
@@ -135,7 +125,13 @@ impl<T, U: DeserializeOwned + 'static> Future for JsonBody<T, U>
|
||||
}
|
||||
}
|
||||
// check content-type
|
||||
if !self.ct.is_empty() && req.content_type() != self.ct {
|
||||
|
||||
let json = if let Ok(Some(mime)) = req.mime_type() {
|
||||
mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON)
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !json {
|
||||
return Err(JsonPayloadError::ContentType)
|
||||
}
|
||||
|
||||
@@ -200,8 +196,8 @@ mod tests {
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
req.headers_mut().insert(header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"));
|
||||
let mut json = req.json::<MyObject>().content_type("text/json");
|
||||
header::HeaderValue::from_static("application/text"));
|
||||
let mut json = req.json::<MyObject>();
|
||||
assert_eq!(json.poll().err().unwrap(), JsonPayloadError::ContentType);
|
||||
|
||||
let mut req = HttpRequest::default();
|
||||
|
@@ -79,6 +79,7 @@ extern crate libc;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
extern crate flate2;
|
||||
#[cfg(feature="brotli")]
|
||||
extern crate brotli2;
|
||||
extern crate encoding;
|
||||
extern crate percent_encoding;
|
||||
@@ -136,7 +137,7 @@ pub use application::Application;
|
||||
pub use httpmessage::HttpMessage;
|
||||
pub use httprequest::HttpRequest;
|
||||
pub use httpresponse::HttpResponse;
|
||||
pub use handler::{Reply, Responder, NormalizePath, AsyncResponder};
|
||||
pub use handler::{Either, Reply, Responder, NormalizePath, AsyncResponder};
|
||||
pub use route::Route;
|
||||
pub use resource::Resource;
|
||||
pub use context::HttpContext;
|
||||
|
@@ -349,8 +349,7 @@ impl<S> Middleware<S> for Cors {
|
||||
if self.send_wildcard {
|
||||
resp.headers_mut().insert(
|
||||
header::ACCESS_CONTROL_ALLOW_ORIGIN, HeaderValue::from_static("*"));
|
||||
} else {
|
||||
let origin = req.headers().get(header::ORIGIN).unwrap();
|
||||
} else if let Some(origin) = req.headers().get(header::ORIGIN) {
|
||||
resp.headers_mut().insert(
|
||||
header::ACCESS_CONTROL_ALLOW_ORIGIN, origin.clone());
|
||||
}
|
||||
@@ -807,6 +806,25 @@ mod tests {
|
||||
assert!(cors.start(&mut req).unwrap().is_done());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_origin_response() {
|
||||
let cors = Cors::build().finish().unwrap();
|
||||
|
||||
let mut req = TestRequest::default().method(Method::GET).finish();
|
||||
let resp: HttpResponse = HttpOk.into();
|
||||
let resp = cors.response(&mut req, resp).unwrap().response();
|
||||
assert!(resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).is_none());
|
||||
|
||||
let mut req = TestRequest::with_header(
|
||||
"Origin", "https://www.example.com")
|
||||
.method(Method::OPTIONS)
|
||||
.finish();
|
||||
let resp = cors.response(&mut req, resp).unwrap().response();
|
||||
assert_eq!(
|
||||
&b"https://www.example.com"[..],
|
||||
resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_response() {
|
||||
let cors = Cors::build()
|
||||
|
@@ -130,7 +130,7 @@ impl<S> InnerMultipart<S> where S: Stream<Item=Bytes, Error=PayloadError> {
|
||||
|
||||
fn read_headers(payload: &mut PayloadHelper<S>) -> Poll<HeaderMap, MultipartError>
|
||||
{
|
||||
match payload.readuntil(b"\r\n\r\n")? {
|
||||
match payload.read_until(b"\r\n\r\n")? {
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
Async::Ready(None) => Err(MultipartError::Incomplete),
|
||||
Async::Ready(Some(bytes)) => {
|
||||
@@ -469,23 +469,23 @@ impl<S> InnerField<S> where S: Stream<Item=Bytes, Error=PayloadError> {
|
||||
fn read_stream(payload: &mut PayloadHelper<S>, boundary: &str)
|
||||
-> Poll<Option<Bytes>, MultipartError>
|
||||
{
|
||||
match payload.readuntil(b"\r")? {
|
||||
match payload.read_until(b"\r")? {
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
Async::Ready(None) => Err(MultipartError::Incomplete),
|
||||
Async::Ready(Some(mut chunk)) => {
|
||||
if chunk.len() == 1 {
|
||||
payload.unread_data(chunk);
|
||||
match payload.readexactly(boundary.len() + 4)? {
|
||||
match payload.read_exact(boundary.len() + 4)? {
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
Async::Ready(None) => Err(MultipartError::Incomplete),
|
||||
Async::Ready(Some(chunk)) => {
|
||||
if &chunk[..2] == b"\r\n" && &chunk[2..4] == b"--" &&
|
||||
&chunk[4..] == boundary.as_bytes()
|
||||
{
|
||||
payload.unread_data(chunk.freeze());
|
||||
payload.unread_data(chunk);
|
||||
Ok(Async::Ready(None))
|
||||
} else {
|
||||
Ok(Async::Ready(Some(chunk.freeze())))
|
||||
Ok(Async::Ready(Some(chunk)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,9 +4,10 @@ use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::slice::Iter;
|
||||
use std::borrow::Cow;
|
||||
use http::StatusCode;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use error::{ResponseError, UriSegmentError, InternalError, ErrorBadRequest};
|
||||
use error::{ResponseError, UriSegmentError, InternalError};
|
||||
|
||||
|
||||
/// A trait to abstract the idea of creating a new instance of a type from a path parameter.
|
||||
@@ -144,7 +145,8 @@ macro_rules! FROM_STR {
|
||||
type Err = InternalError<<$type as FromStr>::Err>;
|
||||
|
||||
fn from_param(val: &str) -> Result<Self, Self::Err> {
|
||||
<$type as FromStr>::from_str(val).map_err(ErrorBadRequest)
|
||||
<$type as FromStr>::from_str(val)
|
||||
.map_err(|e| InternalError::new(e, StatusCode::BAD_REQUEST))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
173
src/payload.rs
173
src/payload.rs
@@ -5,9 +5,14 @@ use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::{Async, Poll, Stream};
|
||||
use futures::task::{Task, current as current_task};
|
||||
|
||||
use error::PayloadError;
|
||||
|
||||
/// max buffer size 32k
|
||||
pub(crate) const MAX_BUFFER_SIZE: usize = 32_768;
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) enum PayloadStatus {
|
||||
Read,
|
||||
@@ -76,6 +81,14 @@ impl Payload {
|
||||
pub(crate) fn readall(&self) -> Option<Bytes> {
|
||||
self.inner.borrow_mut().readall()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Set read buffer capacity
|
||||
///
|
||||
/// Default buffer capacity is 32Kb.
|
||||
pub fn set_read_buffer_capacity(&mut self, cap: usize) {
|
||||
self.inner.borrow_mut().capacity = cap;
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for Payload {
|
||||
@@ -117,18 +130,21 @@ pub struct PayloadSender {
|
||||
|
||||
impl PayloadWriter for PayloadSender {
|
||||
|
||||
#[inline]
|
||||
fn set_error(&mut self, err: PayloadError) {
|
||||
if let Some(shared) = self.inner.upgrade() {
|
||||
shared.borrow_mut().set_error(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn feed_eof(&mut self) {
|
||||
if let Some(shared) = self.inner.upgrade() {
|
||||
shared.borrow_mut().feed_eof()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn feed_data(&mut self, data: Bytes) {
|
||||
if let Some(shared) = self.inner.upgrade() {
|
||||
shared.borrow_mut().feed_data(data)
|
||||
@@ -143,6 +159,12 @@ impl PayloadWriter for PayloadSender {
|
||||
if shared.borrow().need_read {
|
||||
PayloadStatus::Read
|
||||
} else {
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
if shared.borrow_mut().io_task.is_none() {
|
||||
shared.borrow_mut().io_task = Some(current_task());
|
||||
}
|
||||
}
|
||||
PayloadStatus::Pause
|
||||
}
|
||||
} else {
|
||||
@@ -158,6 +180,9 @@ struct Inner {
|
||||
err: Option<PayloadError>,
|
||||
need_read: bool,
|
||||
items: VecDeque<Bytes>,
|
||||
capacity: usize,
|
||||
task: Option<Task>,
|
||||
io_task: Option<Task>,
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
@@ -169,27 +194,38 @@ impl Inner {
|
||||
err: None,
|
||||
items: VecDeque::new(),
|
||||
need_read: true,
|
||||
capacity: MAX_BUFFER_SIZE,
|
||||
task: None,
|
||||
io_task: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_error(&mut self, err: PayloadError) {
|
||||
self.err = Some(err);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn feed_eof(&mut self) {
|
||||
self.eof = true;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn feed_data(&mut self, data: Bytes) {
|
||||
self.len += data.len();
|
||||
self.need_read = false;
|
||||
self.items.push_back(data);
|
||||
self.need_read = self.len < self.capacity;
|
||||
if let Some(task) = self.task.take() {
|
||||
task.notify()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn eof(&self) -> bool {
|
||||
self.items.is_empty() && self.eof
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
@@ -214,6 +250,16 @@ impl Inner {
|
||||
fn readany(&mut self) -> Poll<Option<Bytes>, PayloadError> {
|
||||
if let Some(data) = self.items.pop_front() {
|
||||
self.len -= data.len();
|
||||
self.need_read = self.len < self.capacity;
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
if self.need_read && self.task.is_none() {
|
||||
self.task = Some(current_task());
|
||||
}
|
||||
if let Some(task) = self.io_task.take() {
|
||||
task.notify()
|
||||
}
|
||||
}
|
||||
Ok(Async::Ready(Some(data)))
|
||||
} else if let Some(err) = self.err.take() {
|
||||
Err(err)
|
||||
@@ -221,6 +267,15 @@ impl Inner {
|
||||
Ok(Async::Ready(None))
|
||||
} else {
|
||||
self.need_read = true;
|
||||
#[cfg(not(test))]
|
||||
{
|
||||
if self.task.is_none() {
|
||||
self.task = Some(current_task());
|
||||
}
|
||||
if let Some(task) = self.io_task.take() {
|
||||
task.notify()
|
||||
}
|
||||
}
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
@@ -247,6 +302,7 @@ impl<S> PayloadHelper<S> where S: Stream<Item=Bytes, Error=PayloadError> {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_stream(&mut self) -> Poll<bool, PayloadError> {
|
||||
self.stream.poll().map(|res| {
|
||||
match res {
|
||||
@@ -261,6 +317,7 @@ impl<S> PayloadHelper<S> where S: Stream<Item=Bytes, Error=PayloadError> {
|
||||
})
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn readany(&mut self) -> Poll<Option<Bytes>, PayloadError> {
|
||||
if let Some(data) = self.items.pop_front() {
|
||||
self.len -= data.len();
|
||||
@@ -274,25 +331,85 @@ impl<S> PayloadHelper<S> where S: Stream<Item=Bytes, Error=PayloadError> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn readexactly(&mut self, size: usize) -> Poll<Option<BytesMut>, PayloadError> {
|
||||
#[inline]
|
||||
pub fn can_read(&mut self, size: usize) -> Poll<Option<bool>, PayloadError> {
|
||||
if size <= self.len {
|
||||
let mut buf = BytesMut::with_capacity(size);
|
||||
while buf.len() < size {
|
||||
Ok(Async::Ready(Some(true)))
|
||||
} else {
|
||||
match self.poll_stream()? {
|
||||
Async::Ready(true) => self.can_read(size),
|
||||
Async::Ready(false) => Ok(Async::Ready(None)),
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_chunk(&mut self) -> Poll<Option<&[u8]>, PayloadError> {
|
||||
if self.items.is_empty() {
|
||||
match self.poll_stream()? {
|
||||
Async::Ready(true) => (),
|
||||
Async::Ready(false) => return Ok(Async::Ready(None)),
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
match self.items.front().map(|c| c.as_ref()) {
|
||||
Some(chunk) => Ok(Async::Ready(Some(chunk))),
|
||||
None => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn read_exact(&mut self, size: usize) -> Poll<Option<Bytes>, PayloadError> {
|
||||
if size <= self.len {
|
||||
self.len -= size;
|
||||
let mut chunk = self.items.pop_front().unwrap();
|
||||
if size < chunk.len() {
|
||||
let buf = chunk.split_to(size);
|
||||
self.items.push_front(chunk);
|
||||
Ok(Async::Ready(Some(buf)))
|
||||
}
|
||||
else if size == chunk.len() {
|
||||
Ok(Async::Ready(Some(chunk)))
|
||||
}
|
||||
else {
|
||||
let mut buf = BytesMut::with_capacity(size);
|
||||
buf.extend_from_slice(&chunk);
|
||||
|
||||
while buf.len() < size {
|
||||
let mut chunk = self.items.pop_front().unwrap();
|
||||
let rem = cmp::min(size - buf.len(), chunk.len());
|
||||
buf.extend_from_slice(&chunk.split_to(rem));
|
||||
if !chunk.is_empty() {
|
||||
self.items.push_front(chunk);
|
||||
}
|
||||
}
|
||||
Ok(Async::Ready(Some(buf.freeze())))
|
||||
}
|
||||
} else {
|
||||
match self.poll_stream()? {
|
||||
Async::Ready(true) => self.read_exact(size),
|
||||
Async::Ready(false) => Ok(Async::Ready(None)),
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn drop_payload(&mut self, size: usize) {
|
||||
if size <= self.len {
|
||||
self.len -= size;
|
||||
|
||||
let mut len = 0;
|
||||
while len < size {
|
||||
let mut chunk = self.items.pop_front().unwrap();
|
||||
let rem = cmp::min(size - buf.len(), chunk.len());
|
||||
self.len -= rem;
|
||||
buf.extend_from_slice(&chunk.split_to(rem));
|
||||
if !chunk.is_empty() {
|
||||
let rem = cmp::min(size-len, chunk.len());
|
||||
len += rem;
|
||||
if rem < chunk.len() {
|
||||
chunk.split_to(rem);
|
||||
self.items.push_front(chunk);
|
||||
}
|
||||
}
|
||||
return Ok(Async::Ready(Some(buf)))
|
||||
}
|
||||
|
||||
match self.poll_stream()? {
|
||||
Async::Ready(true) => self.readexactly(size),
|
||||
Async::Ready(false) => Ok(Async::Ready(None)),
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,7 +434,7 @@ impl<S> PayloadHelper<S> where S: Stream<Item=Bytes, Error=PayloadError> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn readuntil(&mut self, line: &[u8]) -> Poll<Option<Bytes>, PayloadError> {
|
||||
pub fn read_until(&mut self, line: &[u8]) -> Poll<Option<Bytes>, PayloadError> {
|
||||
let mut idx = 0;
|
||||
let mut num = 0;
|
||||
let mut offset = 0;
|
||||
@@ -366,14 +483,14 @@ impl<S> PayloadHelper<S> where S: Stream<Item=Bytes, Error=PayloadError> {
|
||||
}
|
||||
|
||||
match self.poll_stream()? {
|
||||
Async::Ready(true) => self.readuntil(line),
|
||||
Async::Ready(true) => self.read_until(line),
|
||||
Async::Ready(false) => Ok(Async::Ready(None)),
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn readline(&mut self) -> Poll<Option<Bytes>, PayloadError> {
|
||||
self.readuntil(b"\n")
|
||||
self.read_until(b"\n")
|
||||
}
|
||||
|
||||
pub fn unread_data(&mut self, data: Bytes) {
|
||||
@@ -486,21 +603,21 @@ mod tests {
|
||||
let (mut sender, payload) = Payload::new(false);
|
||||
let mut payload = PayloadHelper::new(payload);
|
||||
|
||||
assert_eq!(Async::NotReady, payload.readexactly(2).ok().unwrap());
|
||||
assert_eq!(Async::NotReady, payload.read_exact(2).ok().unwrap());
|
||||
|
||||
sender.feed_data(Bytes::from("line1"));
|
||||
sender.feed_data(Bytes::from("line2"));
|
||||
|
||||
assert_eq!(Async::Ready(Some(BytesMut::from("li"))),
|
||||
payload.readexactly(2).ok().unwrap());
|
||||
assert_eq!(Async::Ready(Some(Bytes::from_static(b"li"))),
|
||||
payload.read_exact(2).ok().unwrap());
|
||||
assert_eq!(payload.len, 3);
|
||||
|
||||
assert_eq!(Async::Ready(Some(BytesMut::from("ne1l"))),
|
||||
payload.readexactly(4).ok().unwrap());
|
||||
assert_eq!(Async::Ready(Some(Bytes::from_static(b"ne1l"))),
|
||||
payload.read_exact(4).ok().unwrap());
|
||||
assert_eq!(payload.len, 4);
|
||||
|
||||
sender.set_error(PayloadError::Incomplete);
|
||||
payload.readexactly(10).err().unwrap();
|
||||
payload.read_exact(10).err().unwrap();
|
||||
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
@@ -513,21 +630,21 @@ mod tests {
|
||||
let (mut sender, payload) = Payload::new(false);
|
||||
let mut payload = PayloadHelper::new(payload);
|
||||
|
||||
assert_eq!(Async::NotReady, payload.readuntil(b"ne").ok().unwrap());
|
||||
assert_eq!(Async::NotReady, payload.read_until(b"ne").ok().unwrap());
|
||||
|
||||
sender.feed_data(Bytes::from("line1"));
|
||||
sender.feed_data(Bytes::from("line2"));
|
||||
|
||||
assert_eq!(Async::Ready(Some(Bytes::from("line"))),
|
||||
payload.readuntil(b"ne").ok().unwrap());
|
||||
payload.read_until(b"ne").ok().unwrap());
|
||||
assert_eq!(payload.len, 1);
|
||||
|
||||
assert_eq!(Async::Ready(Some(Bytes::from("1line2"))),
|
||||
payload.readuntil(b"2").ok().unwrap());
|
||||
payload.read_until(b"2").ok().unwrap());
|
||||
assert_eq!(payload.len, 0);
|
||||
|
||||
sender.set_error(PayloadError::Incomplete);
|
||||
payload.readuntil(b"b").err().unwrap();
|
||||
payload.read_until(b"b").err().unwrap();
|
||||
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
|
276
src/pipeline.rs
276
src/pipeline.rs
@@ -453,163 +453,171 @@ impl<S: 'static, H> ProcessResponse<S, H> {
|
||||
fn poll_io(mut self, io: &mut Writer, info: &mut PipelineInfo<S>)
|
||||
-> Result<PipelineState<S, H>, PipelineState<S, H>>
|
||||
{
|
||||
if self.drain.is_none() && self.running != RunningState::Paused {
|
||||
// if task is paused, write buffer is probably full
|
||||
'outter: loop {
|
||||
let result = match mem::replace(&mut self.iostate, IOState::Done) {
|
||||
IOState::Response => {
|
||||
let encoding = self.resp.content_encoding().unwrap_or(info.encoding);
|
||||
loop {
|
||||
if self.drain.is_none() && self.running != RunningState::Paused {
|
||||
// if task is paused, write buffer is probably full
|
||||
'inner: loop {
|
||||
let result = match mem::replace(&mut self.iostate, IOState::Done) {
|
||||
IOState::Response => {
|
||||
let encoding = self.resp.content_encoding().unwrap_or(info.encoding);
|
||||
|
||||
let result = match io.start(info.req_mut().get_inner(),
|
||||
&mut self.resp, encoding)
|
||||
{
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
info.error = Some(err.into());
|
||||
return Ok(FinishingMiddlewares::init(info, self.resp))
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(err) = self.resp.error() {
|
||||
if self.resp.status().is_server_error() {
|
||||
error!("Error occured during request handling: {}", err);
|
||||
} else {
|
||||
warn!("Error occured during request handling: {}", err);
|
||||
}
|
||||
if log_enabled!(Debug) {
|
||||
debug!("{:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
match self.resp.replace_body(Body::Empty) {
|
||||
Body::Streaming(stream) =>
|
||||
self.iostate = IOState::Payload(stream),
|
||||
Body::Actor(ctx) =>
|
||||
self.iostate = IOState::Actor(ctx),
|
||||
_ => (),
|
||||
}
|
||||
|
||||
result
|
||||
},
|
||||
IOState::Payload(mut body) => {
|
||||
match body.poll() {
|
||||
Ok(Async::Ready(None)) => {
|
||||
if let Err(err) = io.write_eof() {
|
||||
let result = match io.start(info.req_mut().get_inner(),
|
||||
&mut self.resp, encoding)
|
||||
{
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
info.error = Some(err.into());
|
||||
return Ok(FinishingMiddlewares::init(info, self.resp))
|
||||
}
|
||||
break
|
||||
};
|
||||
|
||||
if let Some(err) = self.resp.error() {
|
||||
if self.resp.status().is_server_error() {
|
||||
error!("Error occured during request handling: {}", err);
|
||||
} else {
|
||||
warn!("Error occured during request handling: {}", err);
|
||||
}
|
||||
if log_enabled!(Debug) {
|
||||
debug!("{:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
// always poll stream or actor for the first time
|
||||
match self.resp.replace_body(Body::Empty) {
|
||||
Body::Streaming(stream) => {
|
||||
self.iostate = IOState::Payload(stream);
|
||||
continue 'inner
|
||||
},
|
||||
Body::Actor(ctx) => {
|
||||
self.iostate = IOState::Actor(ctx);
|
||||
continue 'inner
|
||||
},
|
||||
Ok(Async::Ready(Some(chunk))) => {
|
||||
self.iostate = IOState::Payload(body);
|
||||
match io.write(chunk.into()) {
|
||||
Err(err) => {
|
||||
_ => (),
|
||||
}
|
||||
|
||||
result
|
||||
},
|
||||
IOState::Payload(mut body) => {
|
||||
match body.poll() {
|
||||
Ok(Async::Ready(None)) => {
|
||||
if let Err(err) = io.write_eof() {
|
||||
info.error = Some(err.into());
|
||||
return Ok(FinishingMiddlewares::init(info, self.resp))
|
||||
},
|
||||
Ok(result) => result
|
||||
}
|
||||
}
|
||||
Ok(Async::NotReady) => {
|
||||
self.iostate = IOState::Payload(body);
|
||||
break
|
||||
},
|
||||
Err(err) => {
|
||||
info.error = Some(err);
|
||||
return Ok(FinishingMiddlewares::init(info, self.resp))
|
||||
}
|
||||
}
|
||||
},
|
||||
IOState::Actor(mut ctx) => {
|
||||
if info.disconnected.take().is_some() {
|
||||
ctx.disconnected();
|
||||
}
|
||||
match ctx.poll() {
|
||||
Ok(Async::Ready(Some(vec))) => {
|
||||
if vec.is_empty() {
|
||||
self.iostate = IOState::Actor(ctx);
|
||||
}
|
||||
break
|
||||
}
|
||||
let mut res = None;
|
||||
for frame in vec {
|
||||
match frame {
|
||||
Frame::Chunk(None) => {
|
||||
info.context = Some(ctx);
|
||||
if let Err(err) = io.write_eof() {
|
||||
info.error = Some(err.into());
|
||||
return Ok(
|
||||
FinishingMiddlewares::init(info, self.resp))
|
||||
}
|
||||
break 'outter
|
||||
},
|
||||
Ok(Async::Ready(Some(chunk))) => {
|
||||
self.iostate = IOState::Payload(body);
|
||||
match io.write(chunk.into()) {
|
||||
Err(err) => {
|
||||
info.error = Some(err.into());
|
||||
return Ok(FinishingMiddlewares::init(info, self.resp))
|
||||
},
|
||||
Frame::Chunk(Some(chunk)) => {
|
||||
match io.write(chunk) {
|
||||
Err(err) => {
|
||||
Ok(result) => result
|
||||
}
|
||||
}
|
||||
Ok(Async::NotReady) => {
|
||||
self.iostate = IOState::Payload(body);
|
||||
break
|
||||
},
|
||||
Err(err) => {
|
||||
info.error = Some(err);
|
||||
return Ok(FinishingMiddlewares::init(info, self.resp))
|
||||
}
|
||||
}
|
||||
},
|
||||
IOState::Actor(mut ctx) => {
|
||||
if info.disconnected.take().is_some() {
|
||||
ctx.disconnected();
|
||||
}
|
||||
match ctx.poll() {
|
||||
Ok(Async::Ready(Some(vec))) => {
|
||||
if vec.is_empty() {
|
||||
self.iostate = IOState::Actor(ctx);
|
||||
break
|
||||
}
|
||||
let mut res = None;
|
||||
for frame in vec {
|
||||
match frame {
|
||||
Frame::Chunk(None) => {
|
||||
info.context = Some(ctx);
|
||||
if let Err(err) = io.write_eof() {
|
||||
info.error = Some(err.into());
|
||||
return Ok(
|
||||
FinishingMiddlewares::init(info, self.resp))
|
||||
},
|
||||
Ok(result) => res = Some(result),
|
||||
}
|
||||
},
|
||||
Frame::Drain(fut) =>
|
||||
self.drain = Some(fut),
|
||||
}
|
||||
break 'inner
|
||||
},
|
||||
Frame::Chunk(Some(chunk)) => {
|
||||
match io.write(chunk) {
|
||||
Err(err) => {
|
||||
info.error = Some(err.into());
|
||||
return Ok(
|
||||
FinishingMiddlewares::init(info, self.resp))
|
||||
},
|
||||
Ok(result) => res = Some(result),
|
||||
}
|
||||
},
|
||||
Frame::Drain(fut) => self.drain = Some(fut),
|
||||
}
|
||||
}
|
||||
self.iostate = IOState::Actor(ctx);
|
||||
if self.drain.is_some() {
|
||||
self.running.resume();
|
||||
break 'inner
|
||||
}
|
||||
res.unwrap()
|
||||
},
|
||||
Ok(Async::Ready(None)) => {
|
||||
break
|
||||
}
|
||||
self.iostate = IOState::Actor(ctx);
|
||||
if self.drain.is_some() {
|
||||
self.running.resume();
|
||||
break 'outter
|
||||
Ok(Async::NotReady) => {
|
||||
self.iostate = IOState::Actor(ctx);
|
||||
break
|
||||
}
|
||||
Err(err) => {
|
||||
info.error = Some(err);
|
||||
return Ok(FinishingMiddlewares::init(info, self.resp))
|
||||
}
|
||||
res.unwrap()
|
||||
},
|
||||
Ok(Async::Ready(None)) => {
|
||||
break
|
||||
}
|
||||
Ok(Async::NotReady) => {
|
||||
self.iostate = IOState::Actor(ctx);
|
||||
break
|
||||
}
|
||||
Err(err) => {
|
||||
info.error = Some(err);
|
||||
return Ok(FinishingMiddlewares::init(info, self.resp))
|
||||
}
|
||||
}
|
||||
}
|
||||
IOState::Done => break,
|
||||
};
|
||||
IOState::Done => break,
|
||||
};
|
||||
|
||||
match result {
|
||||
WriterState::Pause => {
|
||||
self.running.pause();
|
||||
break
|
||||
match result {
|
||||
WriterState::Pause => {
|
||||
self.running.pause();
|
||||
break
|
||||
}
|
||||
WriterState::Done => {
|
||||
self.running.resume()
|
||||
},
|
||||
}
|
||||
WriterState::Done => {
|
||||
self.running.resume()
|
||||
}
|
||||
}
|
||||
|
||||
// flush io but only if we need to
|
||||
if self.running == RunningState::Paused || self.drain.is_some() {
|
||||
match io.poll_completed(false) {
|
||||
Ok(Async::Ready(_)) => {
|
||||
self.running.resume();
|
||||
|
||||
// resolve drain futures
|
||||
if let Some(tx) = self.drain.take() {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
// restart io processing
|
||||
continue
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// flush io but only if we need to
|
||||
if self.running == RunningState::Paused || self.drain.is_some() {
|
||||
match io.poll_completed(false) {
|
||||
Ok(Async::Ready(_)) => {
|
||||
self.running.resume();
|
||||
|
||||
// resolve drain futures
|
||||
if let Some(tx) = self.drain.take() {
|
||||
let _ = tx.send(());
|
||||
Ok(Async::NotReady) =>
|
||||
return Err(PipelineState::Response(self)),
|
||||
Err(err) => {
|
||||
info.error = Some(err.into());
|
||||
return Ok(FinishingMiddlewares::init(info, self.resp))
|
||||
}
|
||||
// restart io processing
|
||||
return self.poll_io(io, info);
|
||||
},
|
||||
Ok(Async::NotReady) => return Err(PipelineState::Response(self)),
|
||||
Err(err) => {
|
||||
info.error = Some(err.into());
|
||||
return Ok(FinishingMiddlewares::init(info, self.resp))
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// response is completed
|
||||
|
@@ -1,6 +1,7 @@
|
||||
use std::rc::Rc;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use smallvec::SmallVec;
|
||||
use http::{Method, StatusCode};
|
||||
|
||||
use pred;
|
||||
@@ -34,7 +35,7 @@ use httpresponse::HttpResponse;
|
||||
pub struct Resource<S=()> {
|
||||
name: String,
|
||||
state: PhantomData<S>,
|
||||
routes: Vec<Route<S>>,
|
||||
routes: SmallVec<[Route<S>; 3]>,
|
||||
middlewares: Rc<Vec<Box<Middleware<S>>>>,
|
||||
}
|
||||
|
||||
@@ -43,7 +44,7 @@ impl<S> Default for Resource<S> {
|
||||
Resource {
|
||||
name: String::new(),
|
||||
state: PhantomData,
|
||||
routes: Vec::new(),
|
||||
routes: SmallVec::new(),
|
||||
middlewares: Rc::new(Vec::new()) }
|
||||
}
|
||||
}
|
||||
@@ -54,7 +55,7 @@ impl<S> Resource<S> {
|
||||
Resource {
|
||||
name: String::new(),
|
||||
state: PhantomData,
|
||||
routes: Vec::new(),
|
||||
routes: SmallVec::new(),
|
||||
middlewares: Rc::new(Vec::new()) }
|
||||
}
|
||||
|
||||
|
@@ -3,6 +3,7 @@ use std::io::{Read, Write};
|
||||
use std::fmt::Write as FmtWrite;
|
||||
use std::str::FromStr;
|
||||
|
||||
use bytes::{Bytes, BytesMut, BufMut};
|
||||
use http::{Version, Method, HttpTryFrom};
|
||||
use http::header::{HeaderMap, HeaderValue,
|
||||
ACCEPT_ENCODING, CONNECTION,
|
||||
@@ -10,8 +11,8 @@ use http::header::{HeaderMap, HeaderValue,
|
||||
use flate2::Compression;
|
||||
use flate2::read::GzDecoder;
|
||||
use flate2::write::{GzEncoder, DeflateDecoder, DeflateEncoder};
|
||||
#[cfg(feature="brotli")]
|
||||
use brotli2::write::{BrotliDecoder, BrotliEncoder};
|
||||
use bytes::{Bytes, BytesMut, BufMut};
|
||||
|
||||
use header::ContentEncoding;
|
||||
use body::{Body, Binary};
|
||||
@@ -144,6 +145,7 @@ impl PayloadWriter for EncodedPayload {
|
||||
pub(crate) enum Decoder {
|
||||
Deflate(Box<DeflateDecoder<Writer>>),
|
||||
Gzip(Option<Box<GzDecoder<Wrapper>>>),
|
||||
#[cfg(feature="brotli")]
|
||||
Br(Box<BrotliDecoder<Writer>>),
|
||||
Identity,
|
||||
}
|
||||
@@ -214,6 +216,7 @@ pub(crate) struct PayloadStream {
|
||||
impl PayloadStream {
|
||||
pub fn new(enc: ContentEncoding) -> PayloadStream {
|
||||
let dec = match enc {
|
||||
#[cfg(feature="brotli")]
|
||||
ContentEncoding::Br => Decoder::Br(
|
||||
Box::new(BrotliDecoder::new(Writer::new()))),
|
||||
ContentEncoding::Deflate => Decoder::Deflate(
|
||||
@@ -229,6 +232,7 @@ impl PayloadStream {
|
||||
|
||||
pub fn feed_eof(&mut self) -> io::Result<Option<Bytes>> {
|
||||
match self.decoder {
|
||||
#[cfg(feature="brotli")]
|
||||
Decoder::Br(ref mut decoder) => {
|
||||
match decoder.finish() {
|
||||
Ok(mut writer) => {
|
||||
@@ -278,6 +282,7 @@ impl PayloadStream {
|
||||
|
||||
pub fn feed_data(&mut self, data: Bytes) -> io::Result<Option<Bytes>> {
|
||||
match self.decoder {
|
||||
#[cfg(feature="brotli")]
|
||||
Decoder::Br(ref mut decoder) => {
|
||||
match decoder.write_all(&data) {
|
||||
Ok(_) => {
|
||||
@@ -346,6 +351,7 @@ impl PayloadStream {
|
||||
pub(crate) enum ContentEncoder {
|
||||
Deflate(DeflateEncoder<TransferEncoding>),
|
||||
Gzip(GzEncoder<TransferEncoding>),
|
||||
#[cfg(feature="brotli")]
|
||||
Br(BrotliEncoder<TransferEncoding>),
|
||||
Identity(TransferEncoding),
|
||||
}
|
||||
@@ -362,6 +368,7 @@ impl ContentEncoder {
|
||||
response_encoding: ContentEncoding) -> ContentEncoder
|
||||
{
|
||||
let version = resp.version().unwrap_or_else(|| req.version);
|
||||
let is_head = req.method == Method::HEAD;
|
||||
let mut body = resp.replace_body(Body::Empty);
|
||||
let has_body = match body {
|
||||
Body::Empty => false,
|
||||
@@ -404,7 +411,9 @@ impl ContentEncoder {
|
||||
TransferEncoding::length(0, buf)
|
||||
},
|
||||
Body::Binary(ref mut bytes) => {
|
||||
if encoding.is_compression() {
|
||||
if !(encoding == ContentEncoding::Identity
|
||||
|| encoding == ContentEncoding::Auto)
|
||||
{
|
||||
let tmp = SharedBytes::default();
|
||||
let transfer = TransferEncoding::eof(tmp.clone());
|
||||
let mut enc = match encoding {
|
||||
@@ -412,6 +421,7 @@ impl ContentEncoder {
|
||||
DeflateEncoder::new(transfer, Compression::default())),
|
||||
ContentEncoding::Gzip => ContentEncoder::Gzip(
|
||||
GzEncoder::new(transfer, Compression::default())),
|
||||
#[cfg(feature="brotli")]
|
||||
ContentEncoding::Br => ContentEncoder::Br(
|
||||
BrotliEncoder::new(transfer, 5)),
|
||||
ContentEncoding::Identity => ContentEncoder::Identity(transfer),
|
||||
@@ -424,13 +434,13 @@ impl ContentEncoder {
|
||||
*bytes = Binary::from(tmp.take());
|
||||
encoding = ContentEncoding::Identity;
|
||||
}
|
||||
if req.method == Method::HEAD {
|
||||
if is_head {
|
||||
let mut b = BytesMut::new();
|
||||
let _ = write!(b, "{}", bytes.len());
|
||||
resp.headers_mut().insert(
|
||||
CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap());
|
||||
} else {
|
||||
resp.headers_mut().remove(CONTENT_LENGTH);
|
||||
// resp.headers_mut().remove(CONTENT_LENGTH);
|
||||
}
|
||||
TransferEncoding::eof(buf)
|
||||
}
|
||||
@@ -453,7 +463,7 @@ impl ContentEncoder {
|
||||
}
|
||||
};
|
||||
//
|
||||
if req.method == Method::HEAD {
|
||||
if is_head {
|
||||
transfer.kind = TransferEncodingKind::Length(0);
|
||||
} else {
|
||||
resp.replace_body(body);
|
||||
@@ -464,10 +474,11 @@ impl ContentEncoder {
|
||||
DeflateEncoder::new(transfer, Compression::default())),
|
||||
ContentEncoding::Gzip => ContentEncoder::Gzip(
|
||||
GzEncoder::new(transfer, Compression::default())),
|
||||
#[cfg(feature="brotli")]
|
||||
ContentEncoding::Br => ContentEncoder::Br(
|
||||
BrotliEncoder::new(transfer, 5)),
|
||||
ContentEncoding::Identity => ContentEncoder::Identity(transfer),
|
||||
ContentEncoding::Auto => unreachable!()
|
||||
ContentEncoding::Identity | ContentEncoding::Auto =>
|
||||
ContentEncoder::Identity(transfer),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -538,6 +549,7 @@ impl ContentEncoder {
|
||||
#[inline]
|
||||
pub fn is_eof(&self) -> bool {
|
||||
match *self {
|
||||
#[cfg(feature="brotli")]
|
||||
ContentEncoder::Br(ref encoder) => encoder.get_ref().is_eof(),
|
||||
ContentEncoder::Deflate(ref encoder) => encoder.get_ref().is_eof(),
|
||||
ContentEncoder::Gzip(ref encoder) => encoder.get_ref().is_eof(),
|
||||
@@ -552,6 +564,7 @@ impl ContentEncoder {
|
||||
self, ContentEncoder::Identity(TransferEncoding::eof(SharedBytes::empty())));
|
||||
|
||||
match encoder {
|
||||
#[cfg(feature="brotli")]
|
||||
ContentEncoder::Br(encoder) => {
|
||||
match encoder.finish() {
|
||||
Ok(mut writer) => {
|
||||
@@ -594,6 +607,7 @@ impl ContentEncoder {
|
||||
#[inline(always)]
|
||||
pub fn write(&mut self, data: Binary) -> Result<(), io::Error> {
|
||||
match *self {
|
||||
#[cfg(feature="brotli")]
|
||||
ContentEncoder::Br(ref mut encoder) => {
|
||||
match encoder.write_all(data.as_ref()) {
|
||||
Ok(_) => Ok(()),
|
||||
|
349
src/server/h1.rs
349
src/server/h1.rs
@@ -32,8 +32,10 @@ const MAX_PIPELINED_MESSAGES: usize = 16;
|
||||
|
||||
bitflags! {
|
||||
struct Flags: u8 {
|
||||
const STARTED = 0b0000_0001;
|
||||
const ERROR = 0b0000_0010;
|
||||
const KEEPALIVE = 0b0000_0100;
|
||||
const SHUTDOWN = 0b0000_1000;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +51,7 @@ pub(crate) struct Http1<T: IoStream, H: 'static> {
|
||||
flags: Flags,
|
||||
settings: Rc<WorkerSettings<H>>,
|
||||
addr: Option<SocketAddr>,
|
||||
stream: H1Writer<T>,
|
||||
stream: H1Writer<T, H>,
|
||||
reader: Reader,
|
||||
read_buf: BytesMut,
|
||||
tasks: VecDeque<Entry>,
|
||||
@@ -70,7 +72,7 @@ impl<T, H> Http1<T, H>
|
||||
{
|
||||
let bytes = settings.get_shared_bytes();
|
||||
Http1{ flags: Flags::KEEPALIVE,
|
||||
stream: H1Writer::new(stream, bytes),
|
||||
stream: H1Writer::new(stream, bytes, Rc::clone(&settings)),
|
||||
reader: Reader::new(),
|
||||
tasks: VecDeque::new(),
|
||||
keepalive_timer: None,
|
||||
@@ -94,34 +96,37 @@ impl<T, H> Http1<T, H>
|
||||
match timer.poll() {
|
||||
Ok(Async::Ready(_)) => {
|
||||
trace!("Keep-alive timeout, close connection");
|
||||
return Ok(Async::Ready(()))
|
||||
self.flags.insert(Flags::SHUTDOWN);
|
||||
}
|
||||
Ok(Async::NotReady) => (),
|
||||
Err(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
// shutdown
|
||||
if self.flags.contains(Flags::SHUTDOWN) {
|
||||
match self.stream.poll_completed(true) {
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Ok(Async::Ready(_)) => return Ok(Async::Ready(())),
|
||||
Err(err) => {
|
||||
debug!("Error sending data: {}", err);
|
||||
return Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loop {
|
||||
match self.poll_io()? {
|
||||
Async::Ready(true) => (),
|
||||
Async::Ready(false) => return Ok(Async::Ready(())),
|
||||
Async::Ready(false) => {
|
||||
self.flags.insert(Flags::SHUTDOWN);
|
||||
return self.poll()
|
||||
},
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_completed(&mut self, shutdown: bool) -> Result<bool, ()> {
|
||||
// check stream state
|
||||
match self.stream.poll_completed(shutdown) {
|
||||
Ok(Async::Ready(_)) => Ok(true),
|
||||
Ok(Async::NotReady) => Ok(false),
|
||||
Err(err) => {
|
||||
debug!("Error sending data: {}", err);
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: refactor
|
||||
pub fn poll_io(&mut self) -> Poll<bool, ()> {
|
||||
// read incoming data
|
||||
@@ -132,6 +137,8 @@ impl<T, H> Http1<T, H>
|
||||
match self.reader.parse(self.stream.get_mut(),
|
||||
&mut self.read_buf, &self.settings) {
|
||||
Ok(Async::Ready(mut req)) => {
|
||||
self.flags.insert(Flags::STARTED);
|
||||
|
||||
// set remote addr
|
||||
req.set_peer_addr(self.addr);
|
||||
|
||||
@@ -192,134 +199,120 @@ impl<T, H> Http1<T, H>
|
||||
|
||||
let retry = self.reader.need_read() == PayloadStatus::Read;
|
||||
|
||||
loop {
|
||||
// check in-flight messages
|
||||
let mut io = false;
|
||||
let mut idx = 0;
|
||||
while idx < self.tasks.len() {
|
||||
let item = &mut self.tasks[idx];
|
||||
// check in-flight messages
|
||||
let mut io = false;
|
||||
let mut idx = 0;
|
||||
while idx < self.tasks.len() {
|
||||
let item = &mut self.tasks[idx];
|
||||
|
||||
if !io && !item.flags.contains(EntryFlags::EOF) {
|
||||
// io is corrupted, send buffer
|
||||
if item.flags.contains(EntryFlags::ERROR) {
|
||||
if !io && !item.flags.contains(EntryFlags::EOF) {
|
||||
// io is corrupted, send buffer
|
||||
if item.flags.contains(EntryFlags::ERROR) {
|
||||
if let Ok(Async::NotReady) = self.stream.poll_completed(true) {
|
||||
return Ok(Async::NotReady)
|
||||
}
|
||||
return Err(())
|
||||
}
|
||||
|
||||
match item.pipe.poll_io(&mut self.stream) {
|
||||
Ok(Async::Ready(ready)) => {
|
||||
// override keep-alive state
|
||||
if self.stream.keepalive() {
|
||||
self.flags.insert(Flags::KEEPALIVE);
|
||||
} else {
|
||||
self.flags.remove(Flags::KEEPALIVE);
|
||||
}
|
||||
// prepare stream for next response
|
||||
self.stream.reset();
|
||||
|
||||
if ready {
|
||||
item.flags.insert(EntryFlags::EOF | EntryFlags::FINISHED);
|
||||
} else {
|
||||
item.flags.insert(EntryFlags::FINISHED);
|
||||
}
|
||||
},
|
||||
// no more IO for this iteration
|
||||
Ok(Async::NotReady) => {
|
||||
if self.reader.need_read() == PayloadStatus::Read && !retry {
|
||||
return Ok(Async::Ready(true));
|
||||
}
|
||||
io = true;
|
||||
}
|
||||
Err(err) => {
|
||||
// it is not possible to recover from error
|
||||
// during pipe handling, so just drop connection
|
||||
error!("Unhandled error: {}", err);
|
||||
item.flags.insert(EntryFlags::ERROR);
|
||||
|
||||
// check stream state, we still can have valid data in buffer
|
||||
if let Ok(Async::NotReady) = self.stream.poll_completed(true) {
|
||||
return Ok(Async::NotReady)
|
||||
}
|
||||
return Err(())
|
||||
}
|
||||
|
||||
match item.pipe.poll_io(&mut self.stream) {
|
||||
Ok(Async::Ready(ready)) => {
|
||||
// override keep-alive state
|
||||
if self.stream.keepalive() {
|
||||
self.flags.insert(Flags::KEEPALIVE);
|
||||
} else {
|
||||
self.flags.remove(Flags::KEEPALIVE);
|
||||
}
|
||||
// prepare stream for next response
|
||||
self.stream.reset();
|
||||
|
||||
if ready {
|
||||
item.flags.insert(EntryFlags::EOF | EntryFlags::FINISHED);
|
||||
} else {
|
||||
item.flags.insert(EntryFlags::FINISHED);
|
||||
}
|
||||
},
|
||||
// no more IO for this iteration
|
||||
Ok(Async::NotReady) => {
|
||||
if self.reader.need_read() == PayloadStatus::Read && !retry {
|
||||
return Ok(Async::Ready(true));
|
||||
}
|
||||
io = true;
|
||||
}
|
||||
Err(err) => {
|
||||
// it is not possible to recover from error
|
||||
// during pipe handling, so just drop connection
|
||||
error!("Unhandled error: {}", err);
|
||||
item.flags.insert(EntryFlags::ERROR);
|
||||
|
||||
// check stream state, we still can have valid data in buffer
|
||||
if let Ok(Async::NotReady) = self.stream.poll_completed(true) {
|
||||
return Ok(Async::NotReady)
|
||||
}
|
||||
return Err(())
|
||||
}
|
||||
}
|
||||
} else if !item.flags.contains(EntryFlags::FINISHED) {
|
||||
match item.pipe.poll() {
|
||||
Ok(Async::NotReady) => (),
|
||||
Ok(Async::Ready(_)) => item.flags.insert(EntryFlags::FINISHED),
|
||||
Err(err) => {
|
||||
item.flags.insert(EntryFlags::ERROR);
|
||||
error!("Unhandled error: {}", err);
|
||||
}
|
||||
}
|
||||
} else if !item.flags.contains(EntryFlags::FINISHED) {
|
||||
match item.pipe.poll() {
|
||||
Ok(Async::NotReady) => (),
|
||||
Ok(Async::Ready(_)) => item.flags.insert(EntryFlags::FINISHED),
|
||||
Err(err) => {
|
||||
item.flags.insert(EntryFlags::ERROR);
|
||||
error!("Unhandled error: {}", err);
|
||||
}
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
// cleanup finished tasks
|
||||
let mut popped = false;
|
||||
while !self.tasks.is_empty() {
|
||||
if self.tasks[0].flags.contains(EntryFlags::EOF | EntryFlags::FINISHED) {
|
||||
popped = true;
|
||||
self.tasks.pop_front();
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if need_read && popped {
|
||||
return self.poll_io()
|
||||
// cleanup finished tasks
|
||||
let mut popped = false;
|
||||
while !self.tasks.is_empty() {
|
||||
if self.tasks[0].flags.contains(EntryFlags::EOF | EntryFlags::FINISHED) {
|
||||
popped = true;
|
||||
self.tasks.pop_front();
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if need_read && popped {
|
||||
return self.poll_io()
|
||||
}
|
||||
|
||||
// no keep-alive
|
||||
if !self.flags.contains(Flags::KEEPALIVE) && self.tasks.is_empty() {
|
||||
// check stream state
|
||||
if !self.poll_completed(true)? {
|
||||
return Ok(Async::NotReady)
|
||||
// check stream state
|
||||
if self.flags.contains(Flags::STARTED) {
|
||||
match self.stream.poll_completed(false) {
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(err) => {
|
||||
debug!("Error sending data: {}", err);
|
||||
return Err(())
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
// deal with keep-alive
|
||||
if self.tasks.is_empty() {
|
||||
// no keep-alive situations
|
||||
if (self.flags.contains(Flags::ERROR)
|
||||
|| !self.flags.contains(Flags::KEEPALIVE)
|
||||
|| !self.settings.keep_alive_enabled()) &&
|
||||
self.flags.contains(Flags::STARTED)
|
||||
{
|
||||
return Ok(Async::Ready(false))
|
||||
}
|
||||
|
||||
// start keep-alive timer, this also is slow request timeout
|
||||
if self.tasks.is_empty() {
|
||||
// check stream state
|
||||
if self.flags.contains(Flags::ERROR) {
|
||||
return Ok(Async::Ready(false))
|
||||
}
|
||||
|
||||
if self.settings.keep_alive_enabled() {
|
||||
let keep_alive = self.settings.keep_alive();
|
||||
if keep_alive > 0 && self.flags.contains(Flags::KEEPALIVE) {
|
||||
if self.keepalive_timer.is_none() {
|
||||
trace!("Start keep-alive timer");
|
||||
let mut to = Timeout::new(
|
||||
Duration::new(keep_alive, 0), Arbiter::handle()).unwrap();
|
||||
// register timeout
|
||||
let _ = to.poll();
|
||||
self.keepalive_timer = Some(to);
|
||||
}
|
||||
} else {
|
||||
// check stream state
|
||||
if !self.poll_completed(true)? {
|
||||
return Ok(Async::NotReady)
|
||||
}
|
||||
// keep-alive is disabled, drop connection
|
||||
return Ok(Async::Ready(false))
|
||||
}
|
||||
} else if !self.poll_completed(false)? ||
|
||||
self.flags.contains(Flags::KEEPALIVE) {
|
||||
// check stream state or
|
||||
// if keep-alive unset, rely on operating system
|
||||
return Ok(Async::NotReady)
|
||||
} else {
|
||||
return Ok(Async::Ready(false))
|
||||
}
|
||||
} else {
|
||||
self.poll_completed(false)?;
|
||||
return Ok(Async::NotReady)
|
||||
// start keep-alive timer
|
||||
let keep_alive = self.settings.keep_alive();
|
||||
if self.keepalive_timer.is_none() && keep_alive > 0 {
|
||||
trace!("Start keep-alive timer");
|
||||
let mut timer = Timeout::new(
|
||||
Duration::new(keep_alive, 0), Arbiter::handle()).unwrap();
|
||||
// register timer
|
||||
let _ = timer.poll();
|
||||
self.keepalive_timer = Some(timer);
|
||||
}
|
||||
}
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,7 +353,7 @@ impl Reader {
|
||||
PayloadStatus::Read
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[inline]
|
||||
fn decode(&mut self, buf: &mut BytesMut, payload: &mut PayloadInfo)
|
||||
-> Result<Decoding, ReaderError>
|
||||
@@ -497,6 +490,8 @@ impl Reader {
|
||||
fn parse_message<H>(buf: &mut BytesMut, settings: &WorkerSettings<H>)
|
||||
-> Poll<(HttpRequest, Option<PayloadInfo>), ParseError> {
|
||||
// Parse http message
|
||||
let mut has_te = false;
|
||||
let mut has_upgrade = false;
|
||||
let msg = {
|
||||
let bytes_ptr = buf.as_ref().as_ptr() as usize;
|
||||
let mut headers: [httparse::Header; MAX_HEADERS] =
|
||||
@@ -507,13 +502,9 @@ impl Reader {
|
||||
let mut req = httparse::Request::new(&mut headers);
|
||||
match req.parse(b)? {
|
||||
httparse::Status::Complete(len) => {
|
||||
let method = Method::try_from(req.method.unwrap())
|
||||
let method = Method::from_bytes(req.method.unwrap().as_bytes())
|
||||
.map_err(|_| ParseError::Method)?;
|
||||
let path = req.path.unwrap();
|
||||
let path_start = path.as_ptr() as usize - bytes_ptr;
|
||||
let path_end = path_start + path.len();
|
||||
let path = (path_start, path_end);
|
||||
|
||||
let path = Uri::try_from(req.path.unwrap()).unwrap();
|
||||
let version = if req.version.unwrap() == 1 {
|
||||
Version::HTTP_11
|
||||
} else {
|
||||
@@ -529,28 +520,33 @@ impl Reader {
|
||||
|
||||
// convert headers
|
||||
let msg = settings.get_http_message();
|
||||
for header in headers[..headers_len].iter() {
|
||||
if let Ok(name) = HeaderName::try_from(header.name) {
|
||||
let v_start = header.value.as_ptr() as usize - bytes_ptr;
|
||||
let v_end = v_start + header.value.len();
|
||||
let value = unsafe {
|
||||
HeaderValue::from_shared_unchecked(slice.slice(v_start, v_end)) };
|
||||
msg.get_mut().headers.append(name, value);
|
||||
} else {
|
||||
return Err(ParseError::Header)
|
||||
{
|
||||
let msg_mut = msg.get_mut();
|
||||
for header in headers[..headers_len].iter() {
|
||||
if let Ok(name) = HeaderName::from_bytes(header.name.as_bytes()) {
|
||||
has_te = has_te || name == header::TRANSFER_ENCODING;
|
||||
has_upgrade = has_upgrade || name == header::UPGRADE;
|
||||
let v_start = header.value.as_ptr() as usize - bytes_ptr;
|
||||
let v_end = v_start + header.value.len();
|
||||
let value = unsafe {
|
||||
HeaderValue::from_shared_unchecked(
|
||||
slice.slice(v_start, v_end)) };
|
||||
msg_mut.headers.append(name, value);
|
||||
} else {
|
||||
return Err(ParseError::Header)
|
||||
}
|
||||
}
|
||||
|
||||
msg_mut.uri = path;
|
||||
msg_mut.method = method;
|
||||
msg_mut.version = version;
|
||||
}
|
||||
|
||||
let path = slice.slice(path.0, path.1);
|
||||
let uri = Uri::from_shared(path).map_err(ParseError::Uri)?;
|
||||
|
||||
msg.get_mut().uri = uri;
|
||||
msg.get_mut().method = method;
|
||||
msg.get_mut().version = version;
|
||||
msg
|
||||
};
|
||||
|
||||
let decoder = if let Some(len) = msg.get_ref().headers.get(header::CONTENT_LENGTH) {
|
||||
let decoder = if let Some(len) =
|
||||
msg.get_ref().headers.get(header::CONTENT_LENGTH)
|
||||
{
|
||||
// Content-Length
|
||||
if let Ok(s) = len.to_str() {
|
||||
if let Ok(len) = s.parse::<u64>() {
|
||||
@@ -563,12 +559,10 @@ impl Reader {
|
||||
debug!("illegal Content-Length: {:?}", len);
|
||||
return Err(ParseError::Header)
|
||||
}
|
||||
} else if chunked(&msg.get_mut().headers)? {
|
||||
} else if has_te && chunked(&msg.get_mut().headers)? {
|
||||
// Chunked encoding
|
||||
Some(Decoder::chunked())
|
||||
} else if msg.get_ref().headers.contains_key(header::UPGRADE) ||
|
||||
msg.get_ref().method == Method::CONNECT
|
||||
{
|
||||
} else if has_upgrade || msg.get_ref().method == Method::CONNECT {
|
||||
Some(Decoder::eof())
|
||||
} else {
|
||||
None
|
||||
@@ -577,7 +571,7 @@ impl Reader {
|
||||
if let Some(decoder) = decoder {
|
||||
let (psender, payload) = Payload::new(false);
|
||||
let info = PayloadInfo {
|
||||
tx: PayloadType::new(&msg.get_mut().headers, psender),
|
||||
tx: PayloadType::new(&msg.get_ref().headers, psender),
|
||||
decoder,
|
||||
};
|
||||
msg.get_mut().payload = Some(payload);
|
||||
@@ -868,7 +862,7 @@ mod tests {
|
||||
use httpmessage::HttpMessage;
|
||||
use application::HttpApplication;
|
||||
use server::settings::WorkerSettings;
|
||||
use server::IoStream;
|
||||
use server::{IoStream, KeepAlive};
|
||||
|
||||
struct Buffer {
|
||||
buf: Bytes,
|
||||
@@ -939,7 +933,8 @@ mod tests {
|
||||
|
||||
macro_rules! parse_ready {
|
||||
($e:expr) => ({
|
||||
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), None);
|
||||
let settings = WorkerSettings::<HttpApplication>::new(
|
||||
Vec::new(), KeepAlive::Os);
|
||||
match Reader::new().parse($e, &mut BytesMut::new(), &settings) {
|
||||
Ok(Async::Ready(req)) => req,
|
||||
Ok(_) => panic!("Eof during parsing http request"),
|
||||
@@ -961,7 +956,8 @@ mod tests {
|
||||
macro_rules! expect_parse_err {
|
||||
($e:expr) => ({
|
||||
let mut buf = BytesMut::new();
|
||||
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), None);
|
||||
let settings = WorkerSettings::<HttpApplication>::new(
|
||||
Vec::new(), KeepAlive::Os);
|
||||
|
||||
match Reader::new().parse($e, &mut buf, &settings) {
|
||||
Err(err) => match err {
|
||||
@@ -979,7 +975,8 @@ mod tests {
|
||||
fn test_parse() {
|
||||
let mut buf = Buffer::new("GET /test HTTP/1.1\r\n\r\n");
|
||||
let mut readbuf = BytesMut::new();
|
||||
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), None);
|
||||
let settings = WorkerSettings::<HttpApplication>::new(
|
||||
Vec::new(), KeepAlive::Os);
|
||||
|
||||
let mut reader = Reader::new();
|
||||
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
||||
@@ -996,7 +993,8 @@ mod tests {
|
||||
fn test_parse_partial() {
|
||||
let mut buf = Buffer::new("PUT /test HTTP/1");
|
||||
let mut readbuf = BytesMut::new();
|
||||
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), None);
|
||||
let settings = WorkerSettings::<HttpApplication>::new(
|
||||
Vec::new(), KeepAlive::Os);
|
||||
|
||||
let mut reader = Reader::new();
|
||||
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
||||
@@ -1019,7 +1017,8 @@ mod tests {
|
||||
fn test_parse_post() {
|
||||
let mut buf = Buffer::new("POST /test2 HTTP/1.0\r\n\r\n");
|
||||
let mut readbuf = BytesMut::new();
|
||||
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), None);
|
||||
let settings = WorkerSettings::<HttpApplication>::new(
|
||||
Vec::new(), KeepAlive::Os);
|
||||
|
||||
let mut reader = Reader::new();
|
||||
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
||||
@@ -1036,7 +1035,8 @@ mod tests {
|
||||
fn test_parse_body() {
|
||||
let mut buf = Buffer::new("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody");
|
||||
let mut readbuf = BytesMut::new();
|
||||
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), None);
|
||||
let settings = WorkerSettings::<HttpApplication>::new(
|
||||
Vec::new(), KeepAlive::Os);
|
||||
|
||||
let mut reader = Reader::new();
|
||||
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
||||
@@ -1055,7 +1055,8 @@ mod tests {
|
||||
let mut buf = Buffer::new(
|
||||
"\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody");
|
||||
let mut readbuf = BytesMut::new();
|
||||
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), None);
|
||||
let settings = WorkerSettings::<HttpApplication>::new(
|
||||
Vec::new(), KeepAlive::Os);
|
||||
|
||||
let mut reader = Reader::new();
|
||||
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
||||
@@ -1073,7 +1074,8 @@ mod tests {
|
||||
fn test_parse_partial_eof() {
|
||||
let mut buf = Buffer::new("GET /test HTTP/1.1\r\n");
|
||||
let mut readbuf = BytesMut::new();
|
||||
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), None);
|
||||
let settings = WorkerSettings::<HttpApplication>::new(
|
||||
Vec::new(), KeepAlive::Os);
|
||||
|
||||
let mut reader = Reader::new();
|
||||
not_ready!{ reader.parse(&mut buf, &mut readbuf, &settings) }
|
||||
@@ -1093,7 +1095,8 @@ mod tests {
|
||||
fn test_headers_split_field() {
|
||||
let mut buf = Buffer::new("GET /test HTTP/1.1\r\n");
|
||||
let mut readbuf = BytesMut::new();
|
||||
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), None);
|
||||
let settings = WorkerSettings::<HttpApplication>::new(
|
||||
Vec::new(), KeepAlive::Os);
|
||||
|
||||
let mut reader = Reader::new();
|
||||
not_ready!{ reader.parse(&mut buf, &mut readbuf, &settings) }
|
||||
@@ -1123,7 +1126,8 @@ mod tests {
|
||||
Set-Cookie: c1=cookie1\r\n\
|
||||
Set-Cookie: c2=cookie2\r\n\r\n");
|
||||
let mut readbuf = BytesMut::new();
|
||||
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), None);
|
||||
let settings = WorkerSettings::<HttpApplication>::new(
|
||||
Vec::new(), KeepAlive::Os);
|
||||
|
||||
let mut reader = Reader::new();
|
||||
match reader.parse(&mut buf, &mut readbuf, &settings) {
|
||||
@@ -1358,7 +1362,8 @@ mod tests {
|
||||
"GET /test HTTP/1.1\r\n\
|
||||
transfer-encoding: chunked\r\n\r\n");
|
||||
let mut readbuf = BytesMut::new();
|
||||
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), None);
|
||||
let settings = WorkerSettings::<HttpApplication>::new(
|
||||
Vec::new(), KeepAlive::Os);
|
||||
|
||||
let mut reader = Reader::new();
|
||||
let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings));
|
||||
@@ -1379,7 +1384,8 @@ mod tests {
|
||||
"GET /test HTTP/1.1\r\n\
|
||||
transfer-encoding: chunked\r\n\r\n");
|
||||
let mut readbuf = BytesMut::new();
|
||||
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), None);
|
||||
let settings = WorkerSettings::<HttpApplication>::new(
|
||||
Vec::new(), KeepAlive::Os);
|
||||
|
||||
let mut reader = Reader::new();
|
||||
|
||||
@@ -1408,10 +1414,12 @@ mod tests {
|
||||
"GET /test HTTP/1.1\r\n\
|
||||
transfer-encoding: chunked\r\n\r\n");
|
||||
let mut readbuf = BytesMut::new();
|
||||
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), None);
|
||||
let settings = WorkerSettings::<HttpApplication>::new(
|
||||
Vec::new(), KeepAlive::Os);
|
||||
|
||||
let mut reader = Reader::new();
|
||||
let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings));
|
||||
let _ = req.payload_mut().set_read_buffer_capacity(0);
|
||||
assert!(req.chunked().unwrap());
|
||||
assert!(!req.payload().eof());
|
||||
|
||||
@@ -1458,7 +1466,8 @@ mod tests {
|
||||
"GET /test HTTP/1.1\r\n\
|
||||
transfer-encoding: chunked\r\n\r\n");
|
||||
let mut readbuf = BytesMut::new();
|
||||
let settings = WorkerSettings::<HttpApplication>::new(Vec::new(), None);
|
||||
let settings = WorkerSettings::<HttpApplication>::new(
|
||||
Vec::new(), KeepAlive::Os);
|
||||
|
||||
let mut reader = Reader::new();
|
||||
let mut req = reader_parse_ready!(reader.parse(&mut buf, &mut readbuf, &settings));
|
||||
|
@@ -1,11 +1,12 @@
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
|
||||
|
||||
use std::{io, mem};
|
||||
use std::rc::Rc;
|
||||
use bytes::BufMut;
|
||||
use futures::{Async, Poll};
|
||||
use tokio_io::AsyncWrite;
|
||||
use http::{Method, Version};
|
||||
use http::header::{HeaderValue, CONNECTION, DATE};
|
||||
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE};
|
||||
|
||||
use helpers;
|
||||
use body::{Body, Binary};
|
||||
@@ -15,6 +16,7 @@ use httpresponse::HttpResponse;
|
||||
use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE};
|
||||
use super::shared::SharedBytes;
|
||||
use super::encoding::ContentEncoder;
|
||||
use super::settings::WorkerSettings;
|
||||
|
||||
const AVERAGE_HEADER_SIZE: usize = 30; // totally scientific
|
||||
|
||||
@@ -27,25 +29,31 @@ bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct H1Writer<T: AsyncWrite> {
|
||||
pub(crate) struct H1Writer<T: AsyncWrite, H: 'static> {
|
||||
flags: Flags,
|
||||
stream: T,
|
||||
encoder: ContentEncoder,
|
||||
written: u64,
|
||||
headers_size: u32,
|
||||
buffer: SharedBytes,
|
||||
buffer_capacity: usize,
|
||||
settings: Rc<WorkerSettings<H>>,
|
||||
}
|
||||
|
||||
impl<T: AsyncWrite> H1Writer<T> {
|
||||
impl<T: AsyncWrite, H: 'static> H1Writer<T, H> {
|
||||
|
||||
pub fn new(stream: T, buf: SharedBytes) -> H1Writer<T> {
|
||||
pub fn new(stream: T, buf: SharedBytes, settings: Rc<WorkerSettings<H>>)
|
||||
-> H1Writer<T, H>
|
||||
{
|
||||
H1Writer {
|
||||
flags: Flags::empty(),
|
||||
encoder: ContentEncoder::empty(buf.clone()),
|
||||
written: 0,
|
||||
headers_size: 0,
|
||||
buffer: buf,
|
||||
buffer_capacity: 0,
|
||||
stream,
|
||||
settings,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,31 +74,28 @@ impl<T: AsyncWrite> H1Writer<T> {
|
||||
self.flags.contains(Flags::KEEPALIVE) && !self.flags.contains(Flags::UPGRADE)
|
||||
}
|
||||
|
||||
fn write_to_stream(&mut self) -> io::Result<WriterState> {
|
||||
while !self.buffer.is_empty() {
|
||||
match self.stream.write(self.buffer.as_ref()) {
|
||||
fn write_data(&mut self, data: &[u8]) -> io::Result<usize> {
|
||||
let mut written = 0;
|
||||
while written < data.len() {
|
||||
match self.stream.write(&data[written..]) {
|
||||
Ok(0) => {
|
||||
self.disconnected();
|
||||
return Ok(WriterState::Done);
|
||||
return Err(io::Error::new(io::ErrorKind::WriteZero, ""))
|
||||
},
|
||||
Ok(n) => {
|
||||
let _ = self.buffer.split_to(n);
|
||||
written += n;
|
||||
},
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
if self.buffer.len() > MAX_WRITE_BUFFER_SIZE {
|
||||
return Ok(WriterState::Pause)
|
||||
} else {
|
||||
return Ok(WriterState::Done)
|
||||
}
|
||||
return Ok(written)
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
}
|
||||
Ok(WriterState::Done)
|
||||
Ok(written)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncWrite> Writer for H1Writer<T> {
|
||||
impl<T: AsyncWrite, H: 'static> Writer for H1Writer<T, H> {
|
||||
|
||||
#[inline]
|
||||
fn written(&self) -> u64 {
|
||||
@@ -129,11 +134,14 @@ impl<T: AsyncWrite> Writer for H1Writer<T> {
|
||||
// render message
|
||||
{
|
||||
let mut buffer = self.buffer.get_mut();
|
||||
if let Body::Binary(ref bytes) = body {
|
||||
buffer.reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len());
|
||||
let mut is_bin = if let Body::Binary(ref bytes) = body {
|
||||
buffer.reserve(
|
||||
256 + msg.headers().len() * AVERAGE_HEADER_SIZE + bytes.len());
|
||||
true
|
||||
} else {
|
||||
buffer.reserve(256 + msg.headers().len() * AVERAGE_HEADER_SIZE);
|
||||
}
|
||||
false
|
||||
};
|
||||
|
||||
// status line
|
||||
helpers::write_status_line(version, msg.status().as_u16(), &mut buffer);
|
||||
@@ -142,21 +150,28 @@ impl<T: AsyncWrite> Writer for H1Writer<T> {
|
||||
match body {
|
||||
Body::Empty =>
|
||||
if req.method != Method::HEAD {
|
||||
SharedBytes::extend_from_slice_(buffer, b"\r\ncontent-length: 0\r\n");
|
||||
SharedBytes::put_slice(
|
||||
buffer, b"\r\ncontent-length: 0\r\n");
|
||||
} else {
|
||||
SharedBytes::extend_from_slice_(buffer, b"\r\n");
|
||||
SharedBytes::put_slice(buffer, b"\r\n");
|
||||
},
|
||||
Body::Binary(ref bytes) =>
|
||||
helpers::write_content_length(bytes.len(), &mut buffer),
|
||||
_ =>
|
||||
SharedBytes::extend_from_slice_(buffer, b"\r\n"),
|
||||
SharedBytes::put_slice(buffer, b"\r\n"),
|
||||
}
|
||||
|
||||
// write headers
|
||||
let mut pos = 0;
|
||||
let mut has_date = false;
|
||||
let mut remaining = buffer.remaining_mut();
|
||||
let mut buf: &mut [u8] = unsafe{ mem::transmute(buffer.bytes_mut()) };
|
||||
for (key, value) in msg.headers() {
|
||||
if is_bin && key == CONTENT_LENGTH {
|
||||
is_bin = false;
|
||||
continue
|
||||
}
|
||||
has_date = has_date || key == DATE;
|
||||
let v = value.as_ref();
|
||||
let k = key.as_str().as_bytes();
|
||||
let len = k.len() + v.len() + 4;
|
||||
@@ -185,9 +200,9 @@ impl<T: AsyncWrite> Writer for H1Writer<T> {
|
||||
}
|
||||
unsafe{buffer.advance_mut(pos)};
|
||||
|
||||
// using helpers::date is quite a lot faster
|
||||
if !msg.headers().contains_key(DATE) {
|
||||
helpers::date(&mut buffer);
|
||||
// optimized date header
|
||||
if !has_date {
|
||||
self.settings.set_date(&mut buffer);
|
||||
} else {
|
||||
// msg eof
|
||||
SharedBytes::extend_from_slice_(buffer, b"\r\n");
|
||||
@@ -199,6 +214,9 @@ impl<T: AsyncWrite> Writer for H1Writer<T> {
|
||||
self.written = bytes.len() as u64;
|
||||
self.encoder.write(bytes)?;
|
||||
} else {
|
||||
// capacity, makes sense only for streaming or actor
|
||||
self.buffer_capacity = msg.write_buffer_capacity();
|
||||
|
||||
msg.replace_body(body);
|
||||
}
|
||||
Ok(WriterState::Done)
|
||||
@@ -211,18 +229,11 @@ impl<T: AsyncWrite> Writer for H1Writer<T> {
|
||||
// shortcut for upgraded connection
|
||||
if self.flags.contains(Flags::UPGRADE) {
|
||||
if self.buffer.is_empty() {
|
||||
match self.stream.write(payload.as_ref()) {
|
||||
Ok(0) => {
|
||||
self.disconnected();
|
||||
return Ok(WriterState::Done);
|
||||
},
|
||||
Ok(n) => if payload.len() < n {
|
||||
self.buffer.extend_from_slice(&payload.as_ref()[n..])
|
||||
},
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
return Ok(WriterState::Done)
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
let pl: &[u8] = payload.as_ref();
|
||||
let n = self.write_data(pl)?;
|
||||
if n < pl.len() {
|
||||
self.buffer.extend_from_slice(&pl[n..]);
|
||||
return Ok(WriterState::Done);
|
||||
}
|
||||
} else {
|
||||
self.buffer.extend(payload);
|
||||
@@ -232,12 +243,12 @@ impl<T: AsyncWrite> Writer for H1Writer<T> {
|
||||
self.encoder.write(payload)?;
|
||||
}
|
||||
} else {
|
||||
// might be response to EXCEPT
|
||||
// could be response to EXCEPT header
|
||||
self.buffer.extend_from_slice(payload.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
if self.buffer.len() > MAX_WRITE_BUFFER_SIZE {
|
||||
if self.buffer.len() > self.buffer_capacity {
|
||||
Ok(WriterState::Pause)
|
||||
} else {
|
||||
Ok(WriterState::Done)
|
||||
@@ -259,16 +270,18 @@ impl<T: AsyncWrite> Writer for H1Writer<T> {
|
||||
|
||||
#[inline]
|
||||
fn poll_completed(&mut self, shutdown: bool) -> Poll<(), io::Error> {
|
||||
match self.write_to_stream() {
|
||||
Ok(WriterState::Done) => {
|
||||
if shutdown {
|
||||
self.stream.shutdown()
|
||||
} else {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
},
|
||||
Ok(WriterState::Pause) => Ok(Async::NotReady),
|
||||
Err(err) => Err(err)
|
||||
if !self.buffer.is_empty() {
|
||||
let buf: &[u8] = unsafe{mem::transmute(self.buffer.as_ref())};
|
||||
let written = self.write_data(buf)?;
|
||||
let _ = self.buffer.split_to(written);
|
||||
if self.buffer.len() > self.buffer_capacity {
|
||||
return Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
if shutdown {
|
||||
self.stream.shutdown()
|
||||
} else {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -26,7 +26,7 @@ use payload::{Payload, PayloadWriter, PayloadStatus};
|
||||
use super::h2writer::H2Writer;
|
||||
use super::encoding::PayloadType;
|
||||
use super::settings::WorkerSettings;
|
||||
use super::{HttpHandler, HttpHandlerTask};
|
||||
use super::{HttpHandler, HttpHandlerTask, Writer};
|
||||
|
||||
bitflags! {
|
||||
struct Flags: u8 {
|
||||
@@ -43,7 +43,7 @@ struct Http2<T, H>
|
||||
settings: Rc<WorkerSettings<H>>,
|
||||
addr: Option<SocketAddr>,
|
||||
state: State<IoWrapper<T>>,
|
||||
tasks: VecDeque<Entry>,
|
||||
tasks: VecDeque<Entry<H>>,
|
||||
keepalive_timer: Option<Timeout>,
|
||||
}
|
||||
|
||||
@@ -109,22 +109,27 @@ impl<T, H> Http2<T, H>
|
||||
loop {
|
||||
match item.task.poll_io(&mut item.stream) {
|
||||
Ok(Async::Ready(ready)) => {
|
||||
item.flags.insert(EntryFlags::EOF);
|
||||
if ready {
|
||||
item.flags.insert(EntryFlags::FINISHED);
|
||||
item.flags.insert(
|
||||
EntryFlags::EOF | EntryFlags::FINISHED);
|
||||
} else {
|
||||
item.flags.insert(EntryFlags::EOF);
|
||||
}
|
||||
not_ready = false;
|
||||
},
|
||||
Ok(Async::NotReady) => {
|
||||
if item.payload.need_read() == PayloadStatus::Read && !retry
|
||||
if item.payload.need_read() == PayloadStatus::Read
|
||||
&& !retry
|
||||
{
|
||||
continue
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
error!("Unhandled error: {}", err);
|
||||
item.flags.insert(EntryFlags::EOF);
|
||||
item.flags.insert(EntryFlags::ERROR);
|
||||
item.flags.insert(
|
||||
EntryFlags::EOF |
|
||||
EntryFlags::ERROR |
|
||||
EntryFlags::WRITE_DONE);
|
||||
item.stream.reset(Reason::INTERNAL_ERROR);
|
||||
}
|
||||
}
|
||||
@@ -138,18 +143,32 @@ impl<T, H> Http2<T, H>
|
||||
item.flags.insert(EntryFlags::FINISHED);
|
||||
},
|
||||
Err(err) => {
|
||||
item.flags.insert(EntryFlags::ERROR);
|
||||
item.flags.insert(EntryFlags::FINISHED);
|
||||
item.flags.insert(
|
||||
EntryFlags::ERROR | EntryFlags::WRITE_DONE |
|
||||
EntryFlags::FINISHED);
|
||||
error!("Unhandled error: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !item.flags.contains(EntryFlags::WRITE_DONE) {
|
||||
match item.stream.poll_completed(false) {
|
||||
Ok(Async::NotReady) => (),
|
||||
Ok(Async::Ready(_)) => {
|
||||
not_ready = false;
|
||||
item.flags.insert(EntryFlags::WRITE_DONE);
|
||||
}
|
||||
Err(_err) => {
|
||||
item.flags.insert(EntryFlags::ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup finished tasks
|
||||
while !self.tasks.is_empty() {
|
||||
if self.tasks[0].flags.contains(EntryFlags::EOF) &&
|
||||
self.tasks[0].flags.contains(EntryFlags::FINISHED) ||
|
||||
self.tasks[0].flags.contains(EntryFlags::WRITE_DONE) ||
|
||||
self.tasks[0].flags.contains(EntryFlags::ERROR)
|
||||
{
|
||||
self.tasks.pop_front();
|
||||
@@ -251,23 +270,24 @@ bitflags! {
|
||||
const REOF = 0b0000_0010;
|
||||
const ERROR = 0b0000_0100;
|
||||
const FINISHED = 0b0000_1000;
|
||||
const WRITE_DONE = 0b0001_0000;
|
||||
}
|
||||
}
|
||||
|
||||
struct Entry {
|
||||
struct Entry<H: 'static> {
|
||||
task: Box<HttpHandlerTask>,
|
||||
payload: PayloadType,
|
||||
recv: RecvStream,
|
||||
stream: H2Writer,
|
||||
stream: H2Writer<H>,
|
||||
flags: EntryFlags,
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
fn new<H>(parts: Parts,
|
||||
recv: RecvStream,
|
||||
resp: SendResponse<Bytes>,
|
||||
addr: Option<SocketAddr>,
|
||||
settings: &Rc<WorkerSettings<H>>) -> Entry
|
||||
impl<H: 'static> Entry<H> {
|
||||
fn new(parts: Parts,
|
||||
recv: RecvStream,
|
||||
resp: SendResponse<Bytes>,
|
||||
addr: Option<SocketAddr>,
|
||||
settings: &Rc<WorkerSettings<H>>) -> Entry<H>
|
||||
where H: HttpHandler + 'static
|
||||
{
|
||||
// Payload and Content-Encoding
|
||||
@@ -300,7 +320,8 @@ impl Entry {
|
||||
|
||||
Entry {task: task.unwrap_or_else(|| Pipeline::error(HttpNotFound)),
|
||||
payload: psender,
|
||||
stream: H2Writer::new(resp, settings.get_shared_bytes()),
|
||||
stream: H2Writer::new(
|
||||
resp, settings.get_shared_bytes(), Rc::clone(settings)),
|
||||
flags: EntryFlags::empty(),
|
||||
recv,
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(redundant_field_names))]
|
||||
|
||||
use std::{io, cmp};
|
||||
use std::rc::Rc;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::{Async, Poll};
|
||||
use http2::{Reason, SendStream};
|
||||
@@ -15,6 +16,7 @@ use httprequest::HttpInnerMessage;
|
||||
use httpresponse::HttpResponse;
|
||||
use super::encoding::ContentEncoder;
|
||||
use super::shared::SharedBytes;
|
||||
use super::settings::WorkerSettings;
|
||||
use super::{Writer, WriterState, MAX_WRITE_BUFFER_SIZE};
|
||||
|
||||
const CHUNK_SIZE: usize = 16_384;
|
||||
@@ -24,28 +26,35 @@ bitflags! {
|
||||
const STARTED = 0b0000_0001;
|
||||
const DISCONNECTED = 0b0000_0010;
|
||||
const EOF = 0b0000_0100;
|
||||
const RESERVED = 0b0000_1000;
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct H2Writer {
|
||||
pub(crate) struct H2Writer<H: 'static> {
|
||||
respond: SendResponse<Bytes>,
|
||||
stream: Option<SendStream<Bytes>>,
|
||||
encoder: ContentEncoder,
|
||||
flags: Flags,
|
||||
written: u64,
|
||||
buffer: SharedBytes,
|
||||
buffer_capacity: usize,
|
||||
settings: Rc<WorkerSettings<H>>,
|
||||
}
|
||||
|
||||
impl H2Writer {
|
||||
impl<H: 'static> H2Writer<H> {
|
||||
|
||||
pub fn new(respond: SendResponse<Bytes>, buf: SharedBytes) -> H2Writer {
|
||||
pub fn new(respond: SendResponse<Bytes>,
|
||||
buf: SharedBytes, settings: Rc<WorkerSettings<H>>) -> H2Writer<H>
|
||||
{
|
||||
H2Writer {
|
||||
respond,
|
||||
settings,
|
||||
stream: None,
|
||||
encoder: ContentEncoder::empty(buf.clone()),
|
||||
flags: Flags::empty(),
|
||||
written: 0,
|
||||
buffer: buf,
|
||||
buffer_capacity: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,65 +63,19 @@ impl H2Writer {
|
||||
stream.send_reset(reason)
|
||||
}
|
||||
}
|
||||
|
||||
fn write_to_stream(&mut self) -> io::Result<WriterState> {
|
||||
if !self.flags.contains(Flags::STARTED) {
|
||||
return Ok(WriterState::Done)
|
||||
}
|
||||
|
||||
if let Some(ref mut stream) = self.stream {
|
||||
if self.buffer.is_empty() {
|
||||
if self.flags.contains(Flags::EOF) {
|
||||
let _ = stream.send_data(Bytes::new(), true);
|
||||
}
|
||||
return Ok(WriterState::Done)
|
||||
}
|
||||
|
||||
loop {
|
||||
match stream.poll_capacity() {
|
||||
Ok(Async::NotReady) => {
|
||||
if self.buffer.len() > MAX_WRITE_BUFFER_SIZE {
|
||||
return Ok(WriterState::Pause)
|
||||
} else {
|
||||
return Ok(WriterState::Done)
|
||||
}
|
||||
}
|
||||
Ok(Async::Ready(None)) => {
|
||||
return Ok(WriterState::Done)
|
||||
}
|
||||
Ok(Async::Ready(Some(cap))) => {
|
||||
let len = self.buffer.len();
|
||||
let bytes = self.buffer.split_to(cmp::min(cap, len));
|
||||
let eof = self.buffer.is_empty() && self.flags.contains(Flags::EOF);
|
||||
self.written += bytes.len() as u64;
|
||||
|
||||
if let Err(err) = stream.send_data(bytes.freeze(), eof) {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, err))
|
||||
} else if !self.buffer.is_empty() {
|
||||
let cap = cmp::min(self.buffer.len(), CHUNK_SIZE);
|
||||
stream.reserve_capacity(cap);
|
||||
} else {
|
||||
return Ok(WriterState::Pause)
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(WriterState::Done)
|
||||
}
|
||||
}
|
||||
|
||||
impl Writer for H2Writer {
|
||||
impl<H: 'static> Writer for H2Writer<H> {
|
||||
|
||||
fn written(&self) -> u64 {
|
||||
self.written
|
||||
}
|
||||
|
||||
fn start(&mut self, req: &mut HttpInnerMessage, msg: &mut HttpResponse, encoding: ContentEncoding)
|
||||
-> io::Result<WriterState> {
|
||||
fn start(&mut self,
|
||||
req: &mut HttpInnerMessage,
|
||||
msg: &mut HttpResponse,
|
||||
encoding: ContentEncoding) -> io::Result<WriterState>
|
||||
{
|
||||
// prepare response
|
||||
self.flags.insert(Flags::STARTED);
|
||||
self.encoder = ContentEncoder::for_server(self.buffer.clone(), req, msg, encoding);
|
||||
@@ -127,7 +90,7 @@ impl Writer for H2Writer {
|
||||
// using helpers::date is quite a lot faster
|
||||
if !msg.headers().contains_key(DATE) {
|
||||
let mut bytes = BytesMut::with_capacity(29);
|
||||
helpers::date_value(&mut bytes);
|
||||
self.settings.set_date_simple(&mut bytes);
|
||||
msg.headers_mut().insert(DATE, HeaderValue::try_from(bytes.freeze()).unwrap());
|
||||
}
|
||||
|
||||
@@ -138,7 +101,8 @@ impl Writer for H2Writer {
|
||||
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());
|
||||
CONTENT_LENGTH,
|
||||
HeaderValue::try_from(val.split_to(l-2).freeze()).unwrap());
|
||||
}
|
||||
Body::Empty => {
|
||||
msg.headers_mut().insert(CONTENT_LENGTH, HeaderValue::from_static("0"));
|
||||
@@ -167,11 +131,13 @@ impl Writer for H2Writer {
|
||||
self.written = bytes.len() as u64;
|
||||
self.encoder.write(bytes)?;
|
||||
if let Some(ref mut stream) = self.stream {
|
||||
self.flags.insert(Flags::RESERVED);
|
||||
stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE));
|
||||
}
|
||||
Ok(WriterState::Pause)
|
||||
} else {
|
||||
msg.replace_body(body);
|
||||
self.buffer_capacity = msg.write_buffer_capacity();
|
||||
Ok(WriterState::Done)
|
||||
}
|
||||
}
|
||||
@@ -189,7 +155,7 @@ impl Writer for H2Writer {
|
||||
}
|
||||
}
|
||||
|
||||
if self.buffer.len() > MAX_WRITE_BUFFER_SIZE {
|
||||
if self.buffer.len() > self.buffer_capacity {
|
||||
Ok(WriterState::Pause)
|
||||
} else {
|
||||
Ok(WriterState::Done)
|
||||
@@ -211,10 +177,41 @@ impl Writer for H2Writer {
|
||||
}
|
||||
|
||||
fn poll_completed(&mut self, _shutdown: bool) -> Poll<(), io::Error> {
|
||||
match self.write_to_stream() {
|
||||
Ok(WriterState::Done) => Ok(Async::Ready(())),
|
||||
Ok(WriterState::Pause) => Ok(Async::NotReady),
|
||||
Err(err) => Err(err)
|
||||
if !self.flags.contains(Flags::STARTED) {
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
|
||||
if let Some(ref mut stream) = self.stream {
|
||||
// reserve capacity
|
||||
if !self.flags.contains(Flags::RESERVED) && !self.buffer.is_empty() {
|
||||
self.flags.insert(Flags::RESERVED);
|
||||
stream.reserve_capacity(cmp::min(self.buffer.len(), CHUNK_SIZE));
|
||||
}
|
||||
|
||||
loop {
|
||||
match stream.poll_capacity() {
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Ok(Async::Ready(None)) => return Ok(Async::Ready(())),
|
||||
Ok(Async::Ready(Some(cap))) => {
|
||||
let len = self.buffer.len();
|
||||
let bytes = self.buffer.split_to(cmp::min(cap, len));
|
||||
let eof = self.buffer.is_empty() && self.flags.contains(Flags::EOF);
|
||||
self.written += bytes.len() as u64;
|
||||
|
||||
if let Err(e) = stream.send_data(bytes.freeze(), eof) {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, e))
|
||||
} else if !self.buffer.is_empty() {
|
||||
let cap = cmp::min(self.buffer.len(), CHUNK_SIZE);
|
||||
stream.reserve_capacity(cap);
|
||||
} else {
|
||||
self.flags.remove(Flags::RESERVED);
|
||||
return Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
@@ -31,6 +31,35 @@ use httpresponse::HttpResponse;
|
||||
/// max buffer size 64k
|
||||
pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
/// Server keep-alive setting
|
||||
pub enum KeepAlive {
|
||||
/// Keep alive in seconds
|
||||
Timeout(usize),
|
||||
/// Use `SO_KEEPALIVE` socket option, value in seconds
|
||||
Tcp(usize),
|
||||
/// Relay on OS to shutdown tcp connection
|
||||
Os,
|
||||
/// Disabled
|
||||
Disabled,
|
||||
}
|
||||
|
||||
impl From<usize> for KeepAlive {
|
||||
fn from(keepalive: usize) -> Self {
|
||||
KeepAlive::Timeout(keepalive)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<usize>> for KeepAlive {
|
||||
fn from(keepalive: Option<usize>) -> Self {
|
||||
if let Some(keepalive) = keepalive {
|
||||
KeepAlive::Timeout(keepalive)
|
||||
} else {
|
||||
KeepAlive::Disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pause accepting incoming connections
|
||||
///
|
||||
/// If socket contains some pending connection, they might be dropped.
|
||||
|
@@ -1,10 +1,14 @@
|
||||
use std::{fmt, net};
|
||||
use std::{fmt, mem, net};
|
||||
use std::fmt::Write;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::cell::{Cell, RefCell, RefMut, UnsafeCell};
|
||||
use time;
|
||||
use bytes::BytesMut;
|
||||
use futures_cpupool::{Builder, CpuPool};
|
||||
|
||||
use helpers;
|
||||
use super::KeepAlive;
|
||||
use super::channel::Node;
|
||||
use super::shared::{SharedBytes, SharedBytesPool};
|
||||
|
||||
@@ -94,27 +98,36 @@ impl ServerSettings {
|
||||
}
|
||||
}
|
||||
|
||||
// "Sun, 06 Nov 1994 08:49:37 GMT".len()
|
||||
const DATE_VALUE_LENGTH: usize = 29;
|
||||
|
||||
pub(crate) struct WorkerSettings<H> {
|
||||
h: RefCell<Vec<H>>,
|
||||
enabled: bool,
|
||||
keep_alive: u64,
|
||||
ka_enabled: bool,
|
||||
bytes: Rc<SharedBytesPool>,
|
||||
messages: Rc<helpers::SharedMessagePool>,
|
||||
channels: Cell<usize>,
|
||||
node: Box<Node<()>>,
|
||||
date: UnsafeCell<Date>,
|
||||
}
|
||||
|
||||
impl<H> WorkerSettings<H> {
|
||||
pub(crate) fn new(h: Vec<H>, keep_alive: Option<u64>) -> WorkerSettings<H> {
|
||||
pub(crate) fn new(h: Vec<H>, keep_alive: KeepAlive) -> WorkerSettings<H> {
|
||||
let (keep_alive, ka_enabled) = match keep_alive {
|
||||
KeepAlive::Timeout(val) => (val as u64, true),
|
||||
KeepAlive::Os | KeepAlive::Tcp(_) => (0, true),
|
||||
KeepAlive::Disabled => (0, false),
|
||||
};
|
||||
|
||||
WorkerSettings {
|
||||
keep_alive, ka_enabled,
|
||||
h: RefCell::new(h),
|
||||
enabled: if let Some(ka) = keep_alive { ka > 0 } else { false },
|
||||
keep_alive: keep_alive.unwrap_or(0),
|
||||
bytes: Rc::new(SharedBytesPool::new()),
|
||||
messages: Rc::new(helpers::SharedMessagePool::new()),
|
||||
channels: Cell::new(0),
|
||||
node: Box::new(Node::head()),
|
||||
date: UnsafeCell::new(Date::new()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,7 +148,7 @@ impl<H> WorkerSettings<H> {
|
||||
}
|
||||
|
||||
pub fn keep_alive_enabled(&self) -> bool {
|
||||
self.enabled
|
||||
self.ka_enabled
|
||||
}
|
||||
|
||||
pub fn get_shared_bytes(&self) -> SharedBytes {
|
||||
@@ -158,4 +171,67 @@ impl<H> WorkerSettings<H> {
|
||||
error!("Number of removed channels is bigger than added channel. Bug in actix-web");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_date(&self) {
|
||||
unsafe{&mut *self.date.get()}.update();
|
||||
}
|
||||
|
||||
pub fn set_date(&self, dst: &mut BytesMut) {
|
||||
let mut buf: [u8; 39] = unsafe { mem::uninitialized() };
|
||||
buf[..6].copy_from_slice(b"date: ");
|
||||
buf[6..35].copy_from_slice(&(unsafe{&*self.date.get()}.bytes));
|
||||
buf[35..].copy_from_slice(b"\r\n\r\n");
|
||||
dst.extend_from_slice(&buf);
|
||||
}
|
||||
|
||||
pub fn set_date_simple(&self, dst: &mut BytesMut) {
|
||||
dst.extend_from_slice(&(unsafe{&*self.date.get()}.bytes));
|
||||
}
|
||||
}
|
||||
|
||||
struct Date {
|
||||
bytes: [u8; DATE_VALUE_LENGTH],
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl Date {
|
||||
fn new() -> Date {
|
||||
let mut date = Date{bytes: [0; DATE_VALUE_LENGTH], pos: 0};
|
||||
date.update();
|
||||
date
|
||||
}
|
||||
fn update(&mut self) {
|
||||
self.pos = 0;
|
||||
write!(self, "{}", time::at_utc(time::get_time()).rfc822()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Write for Date {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
let len = s.len();
|
||||
self.bytes[self.pos..self.pos + len].copy_from_slice(s.as_bytes());
|
||||
self.pos += len;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_date_len() {
|
||||
assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_date() {
|
||||
let settings = WorkerSettings::<()>::new(Vec::new(), KeepAlive::Os);
|
||||
let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
|
||||
settings.set_date(&mut buf1);
|
||||
let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10);
|
||||
settings.set_date(&mut buf2);
|
||||
assert_eq!(buf1, buf2);
|
||||
}
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ impl SharedBytesPool {
|
||||
pub fn release_bytes(&self, mut bytes: Rc<BytesMut>) {
|
||||
let v = &mut self.0.borrow_mut();
|
||||
if v.len() < 128 {
|
||||
Rc::get_mut(&mut bytes).unwrap().take();
|
||||
Rc::get_mut(&mut bytes).unwrap().clear();
|
||||
v.push_front(bytes);
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ impl SharedBytes {
|
||||
#[inline(always)]
|
||||
#[allow(mutable_transmutes)]
|
||||
#[cfg_attr(feature = "cargo-clippy", allow(mut_from_ref, inline_always))]
|
||||
pub fn get_mut(&self) -> &mut BytesMut {
|
||||
pub(crate) fn get_mut(&self) -> &mut BytesMut {
|
||||
let r: &BytesMut = self.0.as_ref().unwrap().as_ref();
|
||||
unsafe{mem::transmute(r)}
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ use std::{io, net, thread};
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, mpsc as sync_mpsc};
|
||||
use std::time::Duration;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use actix::prelude::*;
|
||||
use actix::actors::signal;
|
||||
@@ -19,14 +18,12 @@ use native_tls::TlsAcceptor;
|
||||
#[cfg(feature="alpn")]
|
||||
use openssl::ssl::{AlpnError, SslAcceptorBuilder};
|
||||
|
||||
use helpers;
|
||||
use super::{IntoHttpHandler, IoStream};
|
||||
use super::{IntoHttpHandler, IoStream, KeepAlive};
|
||||
use super::{PauseServer, ResumeServer, StopServer};
|
||||
use super::channel::{HttpChannel, WrapperStream};
|
||||
use super::worker::{Conn, Worker, StreamHandlerType, StopWorker};
|
||||
use super::settings::{ServerSettings, WorkerSettings};
|
||||
|
||||
|
||||
/// An HTTP Server
|
||||
pub struct HttpServer<H> where H: IntoHttpHandler + 'static
|
||||
{
|
||||
@@ -34,15 +31,16 @@ pub struct HttpServer<H> where H: IntoHttpHandler + 'static
|
||||
threads: usize,
|
||||
backlog: i32,
|
||||
host: Option<String>,
|
||||
keep_alive: Option<u64>,
|
||||
keep_alive: KeepAlive,
|
||||
factory: Arc<Fn() -> Vec<H> + Send + Sync>,
|
||||
#[cfg_attr(feature="cargo-clippy", allow(type_complexity))]
|
||||
workers: Vec<(usize, Addr<Syn, Worker<H::Handler>>)>,
|
||||
sockets: HashMap<net::SocketAddr, net::TcpListener>,
|
||||
sockets: Vec<(net::SocketAddr, net::TcpListener)>,
|
||||
accept: Vec<(mio::SetReadiness, sync_mpsc::Sender<Command>)>,
|
||||
exit: bool,
|
||||
shutdown_timeout: u16,
|
||||
signals: Option<Addr<Syn, signal::ProcessSignals>>,
|
||||
no_http2: bool,
|
||||
no_signals: bool,
|
||||
}
|
||||
|
||||
@@ -59,13 +57,8 @@ enum ServerCommand {
|
||||
WorkerDied(usize, Info),
|
||||
}
|
||||
|
||||
impl<H> Actor for HttpServer<H> where H: IntoHttpHandler
|
||||
{
|
||||
impl<H> Actor for HttpServer<H> where H: IntoHttpHandler {
|
||||
type Context = Context<Self>;
|
||||
|
||||
fn started(&mut self, ctx: &mut Self::Context) {
|
||||
self.update_time(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
impl<H> HttpServer<H> where H: IntoHttpHandler + 'static
|
||||
@@ -78,28 +71,24 @@ impl<H> HttpServer<H> where H: IntoHttpHandler + 'static
|
||||
let f = move || {
|
||||
(factory)().into_iter().collect()
|
||||
};
|
||||
|
||||
|
||||
HttpServer{ h: None,
|
||||
threads: num_cpus::get(),
|
||||
backlog: 2048,
|
||||
host: None,
|
||||
keep_alive: None,
|
||||
keep_alive: KeepAlive::Os,
|
||||
factory: Arc::new(f),
|
||||
workers: Vec::new(),
|
||||
sockets: HashMap::new(),
|
||||
sockets: Vec::new(),
|
||||
accept: Vec::new(),
|
||||
exit: false,
|
||||
shutdown_timeout: 30,
|
||||
signals: None,
|
||||
no_http2: false,
|
||||
no_signals: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_time(&self, ctx: &mut Context<Self>) {
|
||||
helpers::update_date();
|
||||
ctx.run_later(Duration::new(1, 0), |slf, ctx| slf.update_time(ctx));
|
||||
}
|
||||
|
||||
/// Set number of workers to start.
|
||||
///
|
||||
/// By default http server uses number of available logical cpu as threads count.
|
||||
@@ -124,15 +113,9 @@ impl<H> HttpServer<H> where H: IntoHttpHandler + 'static
|
||||
|
||||
/// Set server keep-alive setting.
|
||||
///
|
||||
/// By default keep alive is enabled.
|
||||
///
|
||||
/// - `Some(75)` - enable
|
||||
///
|
||||
/// - `Some(0)` - disable
|
||||
///
|
||||
/// - `None` - use `SO_KEEPALIVE` socket option
|
||||
pub fn keep_alive(mut self, val: Option<u64>) -> Self {
|
||||
self.keep_alive = val;
|
||||
/// By default keep alive is set to a `Os`.
|
||||
pub fn keep_alive<T: Into<KeepAlive>>(mut self, val: T) -> Self {
|
||||
self.keep_alive = val.into();
|
||||
self
|
||||
}
|
||||
|
||||
@@ -178,9 +161,15 @@ impl<H> HttpServer<H> where H: IntoHttpHandler + 'static
|
||||
self
|
||||
}
|
||||
|
||||
/// Disable `HTTP/2` support
|
||||
pub fn no_http2(mut self) -> Self {
|
||||
self.no_http2 = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Get addresses of bound sockets.
|
||||
pub fn addrs(&self) -> Vec<net::SocketAddr> {
|
||||
self.sockets.keys().cloned().collect()
|
||||
self.sockets.iter().map(|s| s.0).collect()
|
||||
}
|
||||
|
||||
/// Use listener for accepting incoming connection requests
|
||||
@@ -188,7 +177,7 @@ impl<H> HttpServer<H> where H: IntoHttpHandler + 'static
|
||||
/// HttpServer does not change any configuration for TcpListener,
|
||||
/// it needs to be configured before passing it to listen() method.
|
||||
pub fn listen(mut self, lst: net::TcpListener) -> Self {
|
||||
self.sockets.insert(lst.local_addr().unwrap(), lst);
|
||||
self.sockets.push((lst.local_addr().unwrap(), lst));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -202,7 +191,7 @@ impl<H> HttpServer<H> where H: IntoHttpHandler + 'static
|
||||
match create_tcp_listener(addr, self.backlog) {
|
||||
Ok(lst) => {
|
||||
succ = true;
|
||||
self.sockets.insert(lst.local_addr().unwrap(), lst);
|
||||
self.sockets.push((lst.local_addr().unwrap(), lst));
|
||||
},
|
||||
Err(e) => err = Some(e),
|
||||
}
|
||||
@@ -295,7 +284,7 @@ impl<H: IntoHttpHandler> HttpServer<H>
|
||||
} else {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
let addrs: Vec<(net::SocketAddr, net::TcpListener)> =
|
||||
self.sockets.drain().collect();
|
||||
self.sockets.drain(..).collect();
|
||||
let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false);
|
||||
let workers = self.start_workers(&settings, &StreamHandlerType::Normal);
|
||||
let info = Info{addr: addrs[0].0, handler: StreamHandlerType::Normal};
|
||||
@@ -364,7 +353,7 @@ impl<H: IntoHttpHandler> HttpServer<H>
|
||||
Err(io::Error::new(io::ErrorKind::Other, "No socket addresses are bound"))
|
||||
} else {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
let addrs: Vec<(net::SocketAddr, net::TcpListener)> = self.sockets.drain().collect();
|
||||
let addrs: Vec<(net::SocketAddr, net::TcpListener)> = self.sockets.drain(..).collect();
|
||||
let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false);
|
||||
let workers = self.start_workers(
|
||||
&settings, &StreamHandlerType::Tls(acceptor.clone()));
|
||||
@@ -404,19 +393,21 @@ impl<H: IntoHttpHandler> HttpServer<H>
|
||||
Err(io::Error::new(io::ErrorKind::Other, "No socket addresses are bound"))
|
||||
} else {
|
||||
// alpn support
|
||||
builder.set_alpn_protos(b"\x02h2\x08http/1.1")?;
|
||||
builder.set_alpn_select_callback(|_, protos| {
|
||||
const H2: &[u8] = b"\x02h2";
|
||||
if protos.windows(3).any(|window| window == H2) {
|
||||
Ok(b"h2")
|
||||
} else {
|
||||
Err(AlpnError::NOACK)
|
||||
}
|
||||
});
|
||||
if !self.no_http2 {
|
||||
builder.set_alpn_protos(b"\x02h2\x08http/1.1")?;
|
||||
builder.set_alpn_select_callback(|_, protos| {
|
||||
const H2: &[u8] = b"\x02h2";
|
||||
if protos.windows(3).any(|window| window == H2) {
|
||||
Ok(b"h2")
|
||||
} else {
|
||||
Err(AlpnError::NOACK)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
let acceptor = builder.build();
|
||||
let addrs: Vec<(net::SocketAddr, net::TcpListener)> = self.sockets.drain().collect();
|
||||
let addrs: Vec<(net::SocketAddr, net::TcpListener)> = self.sockets.drain(..).collect();
|
||||
let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false);
|
||||
let workers = self.start_workers(
|
||||
&settings, &StreamHandlerType::Alpn(acceptor.clone()));
|
||||
@@ -458,7 +449,7 @@ impl<H: IntoHttpHandler> HttpServer<H>
|
||||
|
||||
if !self.sockets.is_empty() {
|
||||
let addrs: Vec<(net::SocketAddr, net::TcpListener)> =
|
||||
self.sockets.drain().collect();
|
||||
self.sockets.drain(..).collect();
|
||||
let settings = ServerSettings::new(Some(addrs[0].0), &self.host, false);
|
||||
let workers = self.start_workers(&settings, &StreamHandlerType::Normal);
|
||||
let info = Info{addr: addrs[0].0, handler: StreamHandlerType::Normal};
|
||||
@@ -742,6 +733,13 @@ fn start_accept_thread(
|
||||
workers[next].0, info.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
|
||||
}
|
||||
}
|
||||
@@ -749,10 +747,12 @@ fn start_accept_thread(
|
||||
break
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
if err.kind() != io::ErrorKind::WouldBlock {
|
||||
error!("Error accepting connection: {:?}", err);
|
||||
}
|
||||
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
|
||||
@@ -826,3 +826,16 @@ fn create_tcp_listener(addr: net::SocketAddr, backlog: i32) -> io::Result<net::T
|
||||
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
|
||||
}
|
||||
|
@@ -22,8 +22,7 @@ use tokio_openssl::SslAcceptorExt;
|
||||
use actix::*;
|
||||
use actix::msgs::StopArbiter;
|
||||
|
||||
use helpers;
|
||||
use server::HttpHandler;
|
||||
use server::{HttpHandler, KeepAlive};
|
||||
use server::channel::HttpChannel;
|
||||
use server::settings::WorkerSettings;
|
||||
|
||||
@@ -48,26 +47,35 @@ impl Message for StopWorker {
|
||||
/// Http worker
|
||||
///
|
||||
/// Worker accepts Socket objects via unbounded channel and start requests processing.
|
||||
pub(crate) struct Worker<H> where H: HttpHandler + 'static {
|
||||
pub(crate)
|
||||
struct Worker<H> where H: HttpHandler + 'static {
|
||||
settings: Rc<WorkerSettings<H>>,
|
||||
hnd: Handle,
|
||||
handler: StreamHandlerType,
|
||||
tcp_ka: Option<time::Duration>,
|
||||
}
|
||||
|
||||
impl<H: HttpHandler + 'static> Worker<H> {
|
||||
|
||||
pub(crate) fn new(h: Vec<H>, handler: StreamHandlerType, keep_alive: Option<u64>)
|
||||
pub(crate) fn new(h: Vec<H>, handler: StreamHandlerType, keep_alive: KeepAlive)
|
||||
-> Worker<H>
|
||||
{
|
||||
let tcp_ka = if let KeepAlive::Tcp(val) = keep_alive {
|
||||
Some(time::Duration::new(val as u64, 0))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Worker {
|
||||
settings: Rc::new(WorkerSettings::new(h, keep_alive)),
|
||||
hnd: Arbiter::handle().clone(),
|
||||
handler,
|
||||
tcp_ka,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_time(&self, ctx: &mut Context<Self>) {
|
||||
helpers::update_date();
|
||||
self.settings.update_date();
|
||||
ctx.run_later(time::Duration::new(1, 0), |slf, ctx| slf.update_time(ctx));
|
||||
}
|
||||
|
||||
@@ -106,9 +114,7 @@ impl<H> Handler<Conn<net::TcpStream>> for Worker<H>
|
||||
|
||||
fn handle(&mut self, msg: Conn<net::TcpStream>, _: &mut Context<Self>)
|
||||
{
|
||||
if !self.settings.keep_alive_enabled() &&
|
||||
msg.io.set_keepalive(Some(time::Duration::new(75, 0))).is_err()
|
||||
{
|
||||
if self.tcp_ka.is_some() && msg.io.set_keepalive(self.tcp_ka).is_err() {
|
||||
error!("Can not set socket keep-alive option");
|
||||
}
|
||||
self.handler.handle(Rc::clone(&self.settings), &self.hnd, msg);
|
||||
@@ -197,7 +203,8 @@ impl StreamHandlerType {
|
||||
} else {
|
||||
false
|
||||
};
|
||||
Arbiter::handle().spawn(HttpChannel::new(h, io, peer, http2));
|
||||
Arbiter::handle().spawn(
|
||||
HttpChannel::new(h, io, peer, http2));
|
||||
},
|
||||
Err(err) =>
|
||||
trace!("Error during handling tls connection: {}", err),
|
||||
|
172
src/test.rs
172
src/test.rs
@@ -5,7 +5,7 @@ use std::rc::Rc;
|
||||
use std::sync::mpsc;
|
||||
use std::str::FromStr;
|
||||
|
||||
use actix::{Arbiter, Addr, Syn, System, SystemRunner, msgs};
|
||||
use actix::{Actor, Arbiter, Addr, Syn, System, SystemRunner, Unsync, msgs};
|
||||
use cookie::Cookie;
|
||||
use http::{Uri, Method, Version, HeaderMap, HttpTryFrom};
|
||||
use http::header::HeaderName;
|
||||
@@ -14,6 +14,9 @@ use tokio_core::net::TcpListener;
|
||||
use tokio_core::reactor::Core;
|
||||
use net2::TcpBuilder;
|
||||
|
||||
#[cfg(feature="alpn")]
|
||||
use openssl::ssl::SslAcceptor;
|
||||
|
||||
use ws;
|
||||
use body::Binary;
|
||||
use error::Error;
|
||||
@@ -27,7 +30,7 @@ use payload::Payload;
|
||||
use httprequest::HttpRequest;
|
||||
use httpresponse::HttpResponse;
|
||||
use server::{HttpServer, IntoHttpHandler, ServerSettings};
|
||||
use client::{ClientRequest, ClientRequestBuilder};
|
||||
use client::{ClientRequest, ClientRequestBuilder, ClientConnector};
|
||||
|
||||
/// The `TestServer` type.
|
||||
///
|
||||
@@ -60,6 +63,8 @@ pub struct TestServer {
|
||||
thread: Option<thread::JoinHandle<()>>,
|
||||
system: SystemRunner,
|
||||
server_sys: Addr<Syn, System>,
|
||||
ssl: bool,
|
||||
conn: Addr<Unsync, ClientConnector>,
|
||||
}
|
||||
|
||||
impl TestServer {
|
||||
@@ -69,9 +74,26 @@ impl TestServer {
|
||||
/// This method accepts configuration method. You can add
|
||||
/// middlewares or set handlers for test application.
|
||||
pub fn new<F>(config: F) -> Self
|
||||
where F: Sync + Send + 'static + Fn(&mut TestApp<()>),
|
||||
where F: Sync + Send + 'static + Fn(&mut TestApp<()>)
|
||||
{
|
||||
TestServer::with_state(||(), config)
|
||||
TestServerBuilder::new(||()).start(config)
|
||||
}
|
||||
|
||||
/// Create test server builder
|
||||
pub fn build() -> TestServerBuilder<()> {
|
||||
TestServerBuilder::new(||())
|
||||
}
|
||||
|
||||
/// Create test server builder with specific state factory
|
||||
///
|
||||
/// This method can be used for constructing application state.
|
||||
/// Also it can be used for external dependecy initialization,
|
||||
/// like creating sync actors for diesel integration.
|
||||
pub fn build_with_state<F, S>(state: F) -> TestServerBuilder<S>
|
||||
where F: Fn() -> S + Sync + Send + 'static,
|
||||
S: 'static,
|
||||
{
|
||||
TestServerBuilder::new(state)
|
||||
}
|
||||
|
||||
/// Start new test server with application factory
|
||||
@@ -87,23 +109,31 @@ impl TestServer {
|
||||
let sys = System::new("actix-test-server");
|
||||
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let local_addr = tcp.local_addr().unwrap();
|
||||
let tcp = TcpListener::from_listener(tcp, &local_addr, Arbiter::handle()).unwrap();
|
||||
let tcp = TcpListener::from_listener(
|
||||
tcp, &local_addr, Arbiter::handle()).unwrap();
|
||||
|
||||
HttpServer::new(factory).disable_signals().start_incoming(tcp.incoming(), false);
|
||||
HttpServer::new(factory)
|
||||
.disable_signals()
|
||||
.start_incoming(tcp.incoming(), false);
|
||||
|
||||
tx.send((Arbiter::system(), local_addr)).unwrap();
|
||||
let _ = sys.run();
|
||||
});
|
||||
|
||||
let sys = System::new("actix-test");
|
||||
let (server_sys, addr) = rx.recv().unwrap();
|
||||
TestServer {
|
||||
addr,
|
||||
thread: Some(join),
|
||||
system: System::new("actix-test"),
|
||||
server_sys,
|
||||
ssl: false,
|
||||
conn: TestServer::get_conn(),
|
||||
thread: Some(join),
|
||||
system: sys,
|
||||
}
|
||||
}
|
||||
|
||||
#[deprecated(since="0.4.10",
|
||||
note="please use `TestServer::build_with_state()` instead")]
|
||||
/// Start new test server with custom application state
|
||||
///
|
||||
/// This method accepts state factory and configuration method.
|
||||
@@ -132,12 +162,30 @@ impl TestServer {
|
||||
let _ = sys.run();
|
||||
});
|
||||
|
||||
let system = System::new("actix-test");
|
||||
let (server_sys, addr) = rx.recv().unwrap();
|
||||
TestServer {
|
||||
addr,
|
||||
server_sys,
|
||||
system,
|
||||
ssl: false,
|
||||
conn: TestServer::get_conn(),
|
||||
thread: Some(join),
|
||||
system: System::new("actix-test"),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_conn() -> Addr<Unsync, ClientConnector> {
|
||||
#[cfg(feature="alpn")]
|
||||
{
|
||||
use openssl::ssl::{SslMethod, SslConnector, SslVerifyMode};
|
||||
|
||||
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
|
||||
builder.set_verify(SslVerifyMode::NONE);
|
||||
ClientConnector::with_connector(builder.build()).start()
|
||||
}
|
||||
#[cfg(not(feature="alpn"))]
|
||||
{
|
||||
ClientConnector::default().start()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,9 +207,9 @@ impl TestServer {
|
||||
/// Construct test server url
|
||||
pub fn url(&self, uri: &str) -> String {
|
||||
if uri.starts_with('/') {
|
||||
format!("http://{}{}", self.addr, uri)
|
||||
format!("{}://{}{}", if self.ssl {"https"} else {"http"}, self.addr, uri)
|
||||
} else {
|
||||
format!("http://{}/{}", self.addr, uri)
|
||||
format!("{}://{}/{}", if self.ssl {"https"} else {"http"}, self.addr, uri)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +231,8 @@ impl TestServer {
|
||||
/// Connect to websocket server
|
||||
pub fn ws(&mut self) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> {
|
||||
let url = self.url("/");
|
||||
self.system.run_until_complete(ws::Client::new(url).connect())
|
||||
self.system.run_until_complete(
|
||||
ws::Client::with_connector(url, self.conn.clone()).connect())
|
||||
}
|
||||
|
||||
/// Create `GET` request
|
||||
@@ -205,7 +254,9 @@ impl TestServer {
|
||||
pub fn client(&self, meth: Method, path: &str) -> ClientRequestBuilder {
|
||||
ClientRequest::build()
|
||||
.method(meth)
|
||||
.uri(self.url(path).as_str()).take()
|
||||
.uri(self.url(path).as_str())
|
||||
.with_connector(self.conn.clone())
|
||||
.take()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,6 +266,101 @@ impl Drop for TestServer {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TestServerBuilder<S> {
|
||||
state: Box<Fn() -> S + Sync + Send + 'static>,
|
||||
#[cfg(feature="alpn")]
|
||||
ssl: Option<SslAcceptor>,
|
||||
}
|
||||
|
||||
impl<S: 'static> TestServerBuilder<S> {
|
||||
|
||||
pub fn new<F>(state: F) -> TestServerBuilder<S>
|
||||
where F: Fn() -> S + Sync + Send + 'static
|
||||
{
|
||||
TestServerBuilder {
|
||||
state: Box::new(state),
|
||||
#[cfg(feature="alpn")]
|
||||
ssl: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature="alpn")]
|
||||
/// Create ssl server
|
||||
pub fn ssl(mut self, ssl: SslAcceptor) -> Self {
|
||||
self.ssl = Some(ssl);
|
||||
self
|
||||
}
|
||||
|
||||
#[allow(unused_mut)]
|
||||
/// Configure test application and run test server
|
||||
pub fn start<F>(mut self, config: F) -> TestServer
|
||||
where F: Sync + Send + 'static + Fn(&mut TestApp<S>),
|
||||
{
|
||||
let (tx, rx) = mpsc::channel();
|
||||
|
||||
#[cfg(feature="alpn")]
|
||||
let ssl = self.ssl.is_some();
|
||||
#[cfg(not(feature="alpn"))]
|
||||
let ssl = false;
|
||||
|
||||
// run server in separate thread
|
||||
let join = thread::spawn(move || {
|
||||
let sys = System::new("actix-test-server");
|
||||
|
||||
let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
|
||||
let local_addr = tcp.local_addr().unwrap();
|
||||
let tcp = TcpListener::from_listener(
|
||||
tcp, &local_addr, Arbiter::handle()).unwrap();
|
||||
|
||||
let state = self.state;
|
||||
|
||||
let srv = HttpServer::new(move || {
|
||||
let mut app = TestApp::new(state());
|
||||
config(&mut app);
|
||||
vec![app]})
|
||||
.disable_signals();
|
||||
|
||||
#[cfg(feature="alpn")]
|
||||
{
|
||||
use std::io;
|
||||
use futures::Stream;
|
||||
use tokio_openssl::SslAcceptorExt;
|
||||
|
||||
let ssl = self.ssl.take();
|
||||
if let Some(ssl) = ssl {
|
||||
srv.start_incoming(
|
||||
tcp.incoming()
|
||||
.and_then(move |(sock, addr)| {
|
||||
ssl.accept_async(sock)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
||||
.map(move |s| (s, addr))
|
||||
}),
|
||||
false);
|
||||
} else {
|
||||
srv.start_incoming(tcp.incoming(), false);
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature="alpn"))]
|
||||
{
|
||||
srv.start_incoming(tcp.incoming(), false);
|
||||
}
|
||||
|
||||
tx.send((Arbiter::system(), local_addr)).unwrap();
|
||||
let _ = sys.run();
|
||||
});
|
||||
|
||||
let system = System::new("actix-test");
|
||||
let (server_sys, addr) = rx.recv().unwrap();
|
||||
TestServer {
|
||||
addr,
|
||||
server_sys,
|
||||
ssl,
|
||||
system,
|
||||
conn: TestServer::get_conn(),
|
||||
thread: Some(join),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Test application helper for testing request handlers.
|
||||
pub struct TestApp<S=()> {
|
||||
|
@@ -2,17 +2,18 @@
|
||||
use std::{fmt, io, str};
|
||||
use std::rc::Rc;
|
||||
use std::cell::UnsafeCell;
|
||||
use std::time::Duration;
|
||||
|
||||
use base64;
|
||||
use rand;
|
||||
use bytes::Bytes;
|
||||
use cookie::Cookie;
|
||||
use byteorder::{ByteOrder, NetworkEndian};
|
||||
use http::{HttpTryFrom, StatusCode, Error as HttpError};
|
||||
use http::header::{self, HeaderName, HeaderValue};
|
||||
use sha1::Sha1;
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use futures::unsync::mpsc::{unbounded, UnboundedSender};
|
||||
use byteorder::{ByteOrder, NetworkEndian};
|
||||
|
||||
use actix::prelude::*;
|
||||
|
||||
@@ -192,6 +193,14 @@ impl Client {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set write buffer capacity
|
||||
///
|
||||
/// Default buffer capacity is 32kb
|
||||
pub fn write_buffer_capacity(mut self, cap: usize) -> Self {
|
||||
self.request.write_buffer_capacity(cap);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set request header
|
||||
pub fn header<K, V>(mut self, key: K, value: V) -> Self
|
||||
where HeaderName: HttpTryFrom<K>, V: IntoHeaderValue
|
||||
@@ -200,6 +209,15 @@ impl Client {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set websocket handshake timeout
|
||||
///
|
||||
/// Handshake timeout is a total time for successful handshake.
|
||||
/// Default value is 5 seconds.
|
||||
pub fn timeout(mut self, timeout: Duration) -> Self {
|
||||
self.request.timeout(timeout);
|
||||
self
|
||||
}
|
||||
|
||||
/// Connect to websocket server and do ws handshake
|
||||
pub fn connect(&mut self) -> ClientHandshake {
|
||||
if let Some(e) = self.err.take() {
|
||||
@@ -291,9 +309,32 @@ impl ClientHandshake {
|
||||
request: None,
|
||||
tx: None,
|
||||
error: Some(err),
|
||||
max_size: 0
|
||||
max_size: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set handshake timeout
|
||||
///
|
||||
/// Handshake timeout is a total time before handshake should be completed.
|
||||
/// Default value is 5 seconds.
|
||||
pub fn timeout(mut self, timeout: Duration) -> Self {
|
||||
if let Some(request) = self.request.take() {
|
||||
self.request = Some(request.timeout(timeout));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Set connection timeout
|
||||
///
|
||||
/// Connection timeout includes resolving hostname and actual connection to
|
||||
/// the host.
|
||||
/// Default value is 1 second.
|
||||
pub fn conn_timeout(mut self, timeout: Duration) -> Self {
|
||||
if let Some(request) = self.request.take() {
|
||||
self.request = Some(request.conn_timeout(timeout));
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for ClientHandshake {
|
||||
@@ -413,16 +454,14 @@ impl Stream for ClientReader {
|
||||
// read
|
||||
match Frame::parse(&mut inner.rx, false, max_size) {
|
||||
Ok(Async::Ready(Some(frame))) => {
|
||||
let (finished, opcode, payload) = frame.unpack();
|
||||
|
||||
// continuation is not supported
|
||||
if !finished {
|
||||
inner.closed = true;
|
||||
return Err(ProtocolError::NoContinuation)
|
||||
}
|
||||
let (_finished, opcode, payload) = frame.unpack();
|
||||
|
||||
match opcode {
|
||||
OpCode::Continue => unimplemented!(),
|
||||
// continuation is not supported
|
||||
OpCode::Continue => {
|
||||
inner.closed = true;
|
||||
Err(ProtocolError::NoContinuation)
|
||||
},
|
||||
OpCode::Bad => {
|
||||
inner.closed = true;
|
||||
Err(ProtocolError::BadOpCode)
|
||||
|
@@ -205,7 +205,7 @@ impl<A, S> ActorHttpContext for WebsocketContext<A, S> where A: Actor<Context=Se
|
||||
};
|
||||
|
||||
if self.inner.alive() && self.inner.poll(ctx).is_err() {
|
||||
return Err(ErrorInternalServerError("error").into())
|
||||
return Err(ErrorInternalServerError("error"))
|
||||
}
|
||||
|
||||
// frames
|
||||
|
170
src/ws/frame.rs
170
src/ws/frame.rs
@@ -1,4 +1,4 @@
|
||||
use std::{fmt, mem};
|
||||
use std::{fmt, mem, ptr};
|
||||
use std::iter::FromIterator;
|
||||
use bytes::{Bytes, BytesMut, BufMut};
|
||||
use byteorder::{ByteOrder, BigEndian, NetworkEndian};
|
||||
@@ -15,11 +15,8 @@ use ws::mask::apply_mask;
|
||||
|
||||
/// A struct representing a `WebSocket` frame.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Frame {
|
||||
pub struct Frame {
|
||||
finished: bool,
|
||||
rsv1: bool,
|
||||
rsv2: bool,
|
||||
rsv3: bool,
|
||||
opcode: OpCode,
|
||||
payload: Binary,
|
||||
}
|
||||
@@ -51,9 +48,11 @@ impl Frame {
|
||||
Frame::message(payload, OpCode::Close, true, genmask)
|
||||
}
|
||||
|
||||
/// Parse the input stream into a frame.
|
||||
pub fn parse<S>(pl: &mut PayloadHelper<S>, server: bool, max_size: usize)
|
||||
-> Poll<Option<Frame>, ProtocolError>
|
||||
#[cfg_attr(feature="cargo-clippy", allow(type_complexity))]
|
||||
fn read_copy_md<S>(pl: &mut PayloadHelper<S>,
|
||||
server: bool,
|
||||
max_size: usize
|
||||
) -> Poll<Option<(usize, bool, OpCode, usize, Option<u32>)>, ProtocolError>
|
||||
where S: Stream<Item=Bytes, Error=PayloadError>
|
||||
{
|
||||
let mut idx = 2;
|
||||
@@ -74,12 +73,14 @@ impl Frame {
|
||||
return Err(ProtocolError::MaskedFrame)
|
||||
}
|
||||
|
||||
let rsv1 = first & 0x40 != 0;
|
||||
let rsv2 = first & 0x20 != 0;
|
||||
let rsv3 = first & 0x10 != 0;
|
||||
// Op code
|
||||
let opcode = OpCode::from(first & 0x0F);
|
||||
let len = second & 0x7F;
|
||||
|
||||
if let OpCode::Bad = opcode {
|
||||
return Err(ProtocolError::InvalidOpcode(first & 0x0F))
|
||||
}
|
||||
|
||||
let len = second & 0x7F;
|
||||
let length = if len == 126 {
|
||||
let buf = match pl.copy(4)? {
|
||||
Async::Ready(Some(buf)) => buf,
|
||||
@@ -114,28 +115,129 @@ impl Frame {
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
};
|
||||
|
||||
let mut mask_bytes = [0u8; 4];
|
||||
mask_bytes.copy_from_slice(&buf[idx..idx+4]);
|
||||
let mask: &[u8] = &buf[idx..idx+4];
|
||||
let mask_u32: u32 = unsafe {ptr::read_unaligned(mask.as_ptr() as *const u32)};
|
||||
idx += 4;
|
||||
Some(mask_bytes)
|
||||
Some(mask_u32)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut data = match pl.readexactly(idx + length)? {
|
||||
Async::Ready(Some(buf)) => buf,
|
||||
Async::Ready(None) => return Ok(Async::Ready(None)),
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
};
|
||||
Ok(Async::Ready(Some((idx, finished, opcode, length, mask))))
|
||||
}
|
||||
|
||||
// get body
|
||||
data.split_to(idx);
|
||||
fn read_chunk_md(chunk: &[u8], server: bool, max_size: usize)
|
||||
-> Poll<(usize, bool, OpCode, usize, Option<u32>), ProtocolError>
|
||||
{
|
||||
let chunk_len = chunk.len();
|
||||
|
||||
let mut idx = 2;
|
||||
if chunk_len < 2 {
|
||||
return Ok(Async::NotReady)
|
||||
}
|
||||
|
||||
let first = chunk[0];
|
||||
let second = chunk[1];
|
||||
let finished = first & 0x80 != 0;
|
||||
|
||||
// check masking
|
||||
let masked = second & 0x80 != 0;
|
||||
if !masked && server {
|
||||
return Err(ProtocolError::UnmaskedFrame)
|
||||
} else if masked && !server {
|
||||
return Err(ProtocolError::MaskedFrame)
|
||||
}
|
||||
|
||||
// Op code
|
||||
let opcode = OpCode::from(first & 0x0F);
|
||||
|
||||
// Disallow bad opcode
|
||||
if let OpCode::Bad = opcode {
|
||||
return Err(ProtocolError::InvalidOpcode(first & 0x0F))
|
||||
}
|
||||
|
||||
let len = second & 0x7F;
|
||||
let length = if len == 126 {
|
||||
if chunk_len < 4 {
|
||||
return Ok(Async::NotReady)
|
||||
}
|
||||
let len = NetworkEndian::read_uint(&chunk[idx..], 2) as usize;
|
||||
idx += 2;
|
||||
len
|
||||
} else if len == 127 {
|
||||
if chunk_len < 10 {
|
||||
return Ok(Async::NotReady)
|
||||
}
|
||||
let len = NetworkEndian::read_uint(&chunk[idx..], 8) as usize;
|
||||
idx += 8;
|
||||
len
|
||||
} else {
|
||||
len as usize
|
||||
};
|
||||
|
||||
// check for max allowed size
|
||||
if length > max_size {
|
||||
return Err(ProtocolError::Overflow)
|
||||
}
|
||||
|
||||
let mask = if server {
|
||||
if chunk_len < idx + 4 {
|
||||
return Ok(Async::NotReady)
|
||||
}
|
||||
|
||||
let mask: &[u8] = &chunk[idx..idx+4];
|
||||
let mask_u32: u32 = unsafe {ptr::read_unaligned(mask.as_ptr() as *const u32)};
|
||||
idx += 4;
|
||||
Some(mask_u32)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(Async::Ready((idx, finished, opcode, length, mask)))
|
||||
}
|
||||
|
||||
/// Parse the input stream into a frame.
|
||||
pub fn parse<S>(pl: &mut PayloadHelper<S>, server: bool, max_size: usize)
|
||||
-> Poll<Option<Frame>, ProtocolError>
|
||||
where S: Stream<Item=Bytes, Error=PayloadError>
|
||||
{
|
||||
// try to parse ws frame md from one chunk
|
||||
let result = match pl.get_chunk()? {
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
Async::Ready(None) => return Ok(Async::Ready(None)),
|
||||
Async::Ready(Some(chunk)) => Frame::read_chunk_md(chunk, server, max_size)?,
|
||||
};
|
||||
|
||||
let (idx, finished, opcode, length, mask) = match result {
|
||||
// we may need to join several chunks
|
||||
Async::NotReady => match Frame::read_copy_md(pl, server, max_size)? {
|
||||
Async::Ready(Some(item)) => item,
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
Async::Ready(None) => return Ok(Async::Ready(None)),
|
||||
},
|
||||
Async::Ready(item) => item,
|
||||
};
|
||||
|
||||
match pl.can_read(idx + length)? {
|
||||
Async::Ready(Some(true)) => (),
|
||||
Async::Ready(None) => return Ok(Async::Ready(None)),
|
||||
Async::Ready(Some(false)) | Async::NotReady => return Ok(Async::NotReady),
|
||||
}
|
||||
|
||||
// remove prefix
|
||||
pl.drop_payload(idx);
|
||||
|
||||
// no need for body
|
||||
if length == 0 {
|
||||
return Ok(Async::Ready(Some(Frame {
|
||||
finished, opcode, payload: Binary::from("") })));
|
||||
}
|
||||
|
||||
let data = match pl.read_exact(length)? {
|
||||
Async::Ready(Some(buf)) => buf,
|
||||
Async::Ready(None) => return Ok(Async::Ready(None)),
|
||||
Async::NotReady => panic!(),
|
||||
};
|
||||
|
||||
// control frames must have length <= 125
|
||||
match opcode {
|
||||
OpCode::Ping | OpCode::Pong if length > 125 => {
|
||||
@@ -149,12 +251,14 @@ impl Frame {
|
||||
}
|
||||
|
||||
// unmask
|
||||
if let Some(ref mask) = mask {
|
||||
apply_mask(&mut data, mask);
|
||||
if let Some(mask) = mask {
|
||||
#[allow(mutable_transmutes)]
|
||||
let p: &mut [u8] = unsafe{let ptr: &[u8] = &data; mem::transmute(ptr)};
|
||||
apply_mask(p, mask);
|
||||
}
|
||||
|
||||
Ok(Async::Ready(Some(Frame {
|
||||
finished, rsv1, rsv2, rsv3, opcode, payload: data.into() })))
|
||||
finished, opcode, payload: data.into() })))
|
||||
}
|
||||
|
||||
/// Generate binary representation
|
||||
@@ -199,13 +303,13 @@ impl Frame {
|
||||
};
|
||||
|
||||
if genmask {
|
||||
let mask: [u8; 4] = rand::random();
|
||||
let mask = rand::random::<u32>();
|
||||
unsafe {
|
||||
{
|
||||
let buf_mut = buf.bytes_mut();
|
||||
buf_mut[..4].copy_from_slice(&mask);
|
||||
*(buf_mut as *mut _ as *mut u32) = mask;
|
||||
buf_mut[4..payload_len+4].copy_from_slice(payload.as_ref());
|
||||
apply_mask(&mut buf_mut[4..], &mask);
|
||||
apply_mask(&mut buf_mut[4..], mask);
|
||||
}
|
||||
buf.advance_mut(payload_len + 4);
|
||||
}
|
||||
@@ -221,9 +325,6 @@ impl Default for Frame {
|
||||
fn default() -> Frame {
|
||||
Frame {
|
||||
finished: true,
|
||||
rsv1: false,
|
||||
rsv2: false,
|
||||
rsv3: false,
|
||||
opcode: OpCode::Close,
|
||||
payload: Binary::from(&b""[..]),
|
||||
}
|
||||
@@ -236,15 +337,11 @@ impl fmt::Display for Frame {
|
||||
"
|
||||
<FRAME>
|
||||
final: {}
|
||||
reserved: {} {} {}
|
||||
opcode: {}
|
||||
payload length: {}
|
||||
payload: 0x{}
|
||||
</FRAME>",
|
||||
self.finished,
|
||||
self.rsv1,
|
||||
self.rsv2,
|
||||
self.rsv3,
|
||||
self.opcode,
|
||||
self.payload.len(),
|
||||
self.payload.as_ref().iter().map(
|
||||
@@ -282,7 +379,6 @@ mod tests {
|
||||
let mut buf = PayloadHelper::new(once(Ok(buf.freeze())));
|
||||
|
||||
let frame = extract(Frame::parse(&mut buf, false, 1024));
|
||||
println!("FRAME: {}", frame);
|
||||
assert!(!frame.finished);
|
||||
assert_eq!(frame.opcode, OpCode::Text);
|
||||
assert_eq!(frame.payload.as_ref(), &b"1"[..]);
|
||||
|
@@ -5,7 +5,7 @@ use std::ptr::copy_nonoverlapping;
|
||||
|
||||
/// Mask/unmask a frame.
|
||||
#[inline]
|
||||
pub fn apply_mask(buf: &mut [u8], mask: &[u8; 4]) {
|
||||
pub fn apply_mask(buf: &mut [u8], mask: u32) {
|
||||
apply_mask_fast32(buf, mask)
|
||||
}
|
||||
|
||||
@@ -18,34 +18,41 @@ fn apply_mask_fallback(buf: &mut [u8], mask: &[u8; 4]) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Faster version of `apply_mask()` which operates on 4-byte blocks.
|
||||
/// Faster version of `apply_mask()` which operates on 8-byte blocks.
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
fn apply_mask_fast32(buf: &mut [u8], mask: &[u8; 4]) {
|
||||
// TODO replace this with read_unaligned() as it stabilizes.
|
||||
let mask_u32 = unsafe {
|
||||
let mut m: u32 = uninitialized();
|
||||
#[allow(trivial_casts)]
|
||||
copy_nonoverlapping(mask.as_ptr(), &mut m as *mut _ as *mut u8, 4);
|
||||
m
|
||||
};
|
||||
|
||||
#[cfg_attr(feature="cargo-clippy", allow(cast_lossless))]
|
||||
fn apply_mask_fast32(buf: &mut [u8], mask_u32: u32) {
|
||||
let mut ptr = buf.as_mut_ptr();
|
||||
let mut len = buf.len();
|
||||
|
||||
// Possible first unaligned block.
|
||||
let head = min(len, (4 - (ptr as usize & 3)) & 3);
|
||||
let head = min(len, (8 - (ptr as usize & 0x7)) & 0x3);
|
||||
let mask_u32 = if head > 0 {
|
||||
unsafe {
|
||||
xor_mem(ptr, mask_u32, head);
|
||||
ptr = ptr.offset(head as isize);
|
||||
}
|
||||
len -= head;
|
||||
if cfg!(target_endian = "big") {
|
||||
mask_u32.rotate_left(8 * head as u32)
|
||||
let n = if head > 4 { head - 4 } else { head };
|
||||
|
||||
let mask_u32 = if n > 0 {
|
||||
unsafe {
|
||||
xor_mem(ptr, mask_u32, n);
|
||||
ptr = ptr.offset(head as isize);
|
||||
}
|
||||
len -= n;
|
||||
if cfg!(target_endian = "big") {
|
||||
mask_u32.rotate_left(8 * n as u32)
|
||||
} else {
|
||||
mask_u32.rotate_right(8 * n as u32)
|
||||
}
|
||||
} else {
|
||||
mask_u32.rotate_right(8 * head as u32)
|
||||
mask_u32
|
||||
};
|
||||
|
||||
if head > 4 {
|
||||
unsafe {
|
||||
*(ptr as *mut u32) ^= mask_u32;
|
||||
ptr = ptr.offset(4);
|
||||
len -= 4;
|
||||
}
|
||||
}
|
||||
mask_u32
|
||||
} else {
|
||||
mask_u32
|
||||
};
|
||||
@@ -55,7 +62,20 @@ fn apply_mask_fast32(buf: &mut [u8], mask: &[u8; 4]) {
|
||||
}
|
||||
|
||||
// Properly aligned middle of the data.
|
||||
while len > 4 {
|
||||
if len >= 8 {
|
||||
let mut mask_u64 = mask_u32 as u64;
|
||||
mask_u64 = mask_u64 << 32 | mask_u32 as u64;
|
||||
|
||||
while len >= 8 {
|
||||
unsafe {
|
||||
*(ptr as *mut u64) ^= mask_u64;
|
||||
ptr = ptr.offset(8);
|
||||
len -= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while len >= 4 {
|
||||
unsafe {
|
||||
*(ptr as *mut u32) ^= mask_u32;
|
||||
ptr = ptr.offset(4);
|
||||
@@ -83,6 +103,7 @@ unsafe fn xor_mem(ptr: *mut u8, mask: u32, len: usize) {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ptr;
|
||||
use super::{apply_mask_fallback, apply_mask_fast32};
|
||||
|
||||
#[test]
|
||||
@@ -90,6 +111,8 @@ mod tests {
|
||||
let mask = [
|
||||
0x6d, 0xb6, 0xb2, 0x80,
|
||||
];
|
||||
let mask_u32: u32 = unsafe {ptr::read_unaligned(mask.as_ptr() as *const u32)};
|
||||
|
||||
let unmasked = vec![
|
||||
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82,
|
||||
0xff, 0xfe, 0x00, 0x17, 0x74, 0xf9, 0x12, 0x03,
|
||||
@@ -101,7 +124,7 @@ mod tests {
|
||||
apply_mask_fallback(&mut masked, &mask);
|
||||
|
||||
let mut masked_fast = unmasked.clone();
|
||||
apply_mask_fast32(&mut masked_fast, &mask);
|
||||
apply_mask_fast32(&mut masked_fast, mask_u32);
|
||||
|
||||
assert_eq!(masked, masked_fast);
|
||||
}
|
||||
@@ -112,7 +135,7 @@ mod tests {
|
||||
apply_mask_fallback(&mut masked[1..], &mask);
|
||||
|
||||
let mut masked_fast = unmasked.clone();
|
||||
apply_mask_fast32(&mut masked_fast[1..], &mask);
|
||||
apply_mask_fast32(&mut masked_fast[1..], mask_u32);
|
||||
|
||||
assert_eq!(masked, masked_fast);
|
||||
}
|
||||
|
@@ -63,8 +63,8 @@ mod context;
|
||||
mod mask;
|
||||
mod client;
|
||||
|
||||
use self::frame::Frame;
|
||||
use self::proto::{hash_key, OpCode};
|
||||
pub use self::frame::Frame;
|
||||
pub use self::proto::OpCode;
|
||||
pub use self::proto::CloseCode;
|
||||
pub use self::context::WebsocketContext;
|
||||
pub use self::client::{Client, ClientError,
|
||||
@@ -248,7 +248,7 @@ pub fn handshake<S>(req: &HttpRequest<S>) -> Result<HttpResponseBuilder, Handsha
|
||||
}
|
||||
let key = {
|
||||
let key = req.headers().get(header::SEC_WEBSOCKET_KEY).unwrap();
|
||||
hash_key(key.as_ref())
|
||||
proto::hash_key(key.as_ref())
|
||||
};
|
||||
|
||||
Ok(HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS)
|
||||
@@ -304,7 +304,7 @@ impl<S> Stream for WsStream<S> where S: Stream<Item=Bytes, Error=PayloadError> {
|
||||
}
|
||||
|
||||
match opcode {
|
||||
OpCode::Continue => unimplemented!(),
|
||||
OpCode::Continue => Err(ProtocolError::NoContinuation),
|
||||
OpCode::Bad => {
|
||||
self.closed = true;
|
||||
Err(ProtocolError::BadOpCode)
|
||||
|
@@ -6,7 +6,7 @@ use base64;
|
||||
use self::OpCode::*;
|
||||
/// Operation codes as part of rfc6455.
|
||||
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
|
||||
pub(crate) enum OpCode {
|
||||
pub enum OpCode {
|
||||
/// Indicates a continuation frame of a fragmented message.
|
||||
Continue,
|
||||
/// Indicates a text data frame.
|
||||
|
31
tests/cert.pem
Normal file
31
tests/cert.pem
Normal file
@@ -0,0 +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=
|
||||
-----END CERTIFICATE-----
|
51
tests/key.pem
Normal file
51
tests/key.pem
Normal file
@@ -0,0 +1,51 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIJKAIBAAKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEP
|
||||
n8k1sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+M
|
||||
IK5UNLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM5
|
||||
4jXyvoLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZ
|
||||
WLWrodGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAk
|
||||
oqNDxdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNli
|
||||
JDmACRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6
|
||||
/stIyFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuD
|
||||
YX2UUuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nP
|
||||
wPTOvRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA
|
||||
69unCEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEA
|
||||
AQKCAgAME3aoeXNCPxMrSri7u4Xnnk71YXl0Tm9vwvjRQlMusXZggP8VKN/KjP0/
|
||||
9AE/GhmoxqPLrLCZ9ZE1EIjgmZ9Xgde9+C8rTtfCG2RFUL7/5J2p6NonlocmxoJm
|
||||
YkxYwjP6ce86RTjQWL3RF3s09u0inz9/efJk5O7M6bOWMQ9VZXDlBiRY5BYvbqUR
|
||||
6FeSzD4MnMbdyMRoVBeXE88gTvZk8xhB6DJnLzYgc0tKiRoeKT0iYv5JZw25VyRM
|
||||
ycLzfTrFmXCPfB1ylb483d9Ly4fBlM8nkx37PzEnAuukIawDxsPOb9yZC+hfvNJI
|
||||
7NFiMN+3maEqG2iC00w4Lep4skHY7eHUEUMl+Wjr+koAy2YGLWAwHZQTm7iXn9Ab
|
||||
L6adL53zyCKelRuEQOzbeosJAqS+5fpMK0ekXyoFIuskj7bWuIoCX7K/kg6q5IW+
|
||||
vC2FrlsrbQ79GztWLVmHFO1I4J9M5r666YS0qdh8c+2yyRl4FmSiHfGxb3eOKpxQ
|
||||
b6uI97iZlkxPF9LYUCSc7wq0V2gGz+6LnGvTHlHrOfVXqw/5pLAKhXqxvnroDTwz
|
||||
0Ay/xFF6ei/NSxBY5t8ztGCBm45wCU3l8pW0X6dXqwUipw5b4MRy1VFRu6rqlmbL
|
||||
OPSCuLxqyqsigiEYsBgS/icvXz9DWmCQMPd2XM9YhsHvUq+R4QKCAQEA98EuMMXI
|
||||
6UKIt1kK2t/3OeJRyDd4iv/fCMUAnuPjLBvFE4cXD/SbqCxcQYqb+pue3PYkiTIC
|
||||
71rN8OQAc5yKhzmmnCE5N26br/0pG4pwEjIr6mt8kZHmemOCNEzvhhT83nfKmV0g
|
||||
9lNtuGEQMiwmZrpUOF51JOMC39bzcVjYX2Cmvb7cFbIq3lR0zwM+aZpQ4P8LHCIu
|
||||
bgHmwbdlkLyIULJcQmHIbo6nPFB3ZZE4mqmjwY+rA6Fh9rgBa8OFCfTtrgeYXrNb
|
||||
IgZQ5U8GoYRPNC2ot0vpTinraboa/cgm6oG4M7FW1POCJTl+/ktHEnKuO5oroSga
|
||||
/BSg7hCNFVaOhwKCAQEA4Kkys0HtwEbV5mY/NnvUD5KwfXX7BxoXc9lZ6seVoLEc
|
||||
KjgPYxqYRVrC7dB2YDwwp3qcRTi/uBAgFNm3iYlDzI4xS5SeaudUWjglj7BSgXE2
|
||||
iOEa7EwcvVPluLaTgiWjlzUKeUCNNHWSeQOt+paBOT+IgwRVemGVpAgkqQzNh/nP
|
||||
tl3p9aNtgzEm1qVlPclY/XUCtf3bcOR+z1f1b4jBdn0leu5OhnxkC+Htik+2fTXD
|
||||
jt6JGrMkanN25YzsjnD3Sn+v6SO26H99wnYx5oMSdmb8SlWRrKtfJHnihphjG/YY
|
||||
l1cyorV6M/asSgXNQfGJm4OuJi0I4/FL2wLUHnU+JwKCAQEAzh4WipcRthYXXcoj
|
||||
gMKRkMOb3GFh1OpYqJgVExtudNTJmZxq8GhFU51MR27Eo7LycMwKy2UjEfTOnplh
|
||||
Us2qZiPtW7k8O8S2m6yXlYUQBeNdq9IuuYDTaYD94vsazscJNSAeGodjE+uGvb1q
|
||||
1wLqE87yoE7dUInYa1cOA3+xy2/CaNuviBFJHtzOrSb6tqqenQEyQf6h9/12+DTW
|
||||
t5pSIiixHrzxHiFqOoCLRKGToQB+71rSINwTf0nITNpGBWmSj5VcC3VV3TG5/XxI
|
||||
fPlxV2yhD5WFDPVNGBGvwPDSh4jSMZdZMSNBZCy4XWFNSKjGEWoK4DFYed3DoSt9
|
||||
5IG1YwKCAQA63ntHl64KJUWlkwNbboU583FF3uWBjee5VqoGKHhf3CkKMxhtGqnt
|
||||
+oN7t5VdUEhbinhqdx1dyPPvIsHCS3K1pkjqii4cyzNCVNYa2dQ00Qq+QWZBpwwc
|
||||
3GAkz8rFXsGIPMDa1vxpU6mnBjzPniKMcsZ9tmQDppCEpBGfLpio2eAA5IkK8eEf
|
||||
cIDB3CM0Vo94EvI76CJZabaE9IJ+0HIJb2+jz9BJ00yQBIqvJIYoNy9gP5Xjpi+T
|
||||
qV/tdMkD5jwWjHD3AYHLWKUGkNwwkAYFeqT/gX6jpWBP+ZRPOp011X3KInJFSpKU
|
||||
DT5GQ1Dux7EMTCwVGtXqjO8Ym5wjwwsfAoIBAEcxlhIW1G6BiNfnWbNPWBdh3v/K
|
||||
5Ln98Rcrz8UIbWyl7qNPjYb13C1KmifVG1Rym9vWMO3KuG5atK3Mz2yLVRtmWAVc
|
||||
fxzR57zz9MZFDun66xo+Z1wN3fVxQB4CYpOEI4Lb9ioX4v85hm3D6RpFukNtRQEc
|
||||
Gfr4scTjJX4jFWDp0h6ffMb8mY+quvZoJ0TJqV9L9Yj6Ksdvqez/bdSraev97bHQ
|
||||
4gbQxaTZ6WjaD4HjpPQefMdWp97Metg0ZQSS8b8EzmNFgyJ3XcjirzwliKTAQtn6
|
||||
I2sd0NCIooelrKRD8EJoDUwxoOctY7R97wpZ7/wEHU45cBCbRV3H4JILS5c=
|
||||
-----END RSA PRIVATE KEY-----
|
@@ -66,6 +66,21 @@ fn test_simple() {
|
||||
assert_eq!(bytes, Bytes::from_static(STR.as_ref()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_query_parameter() {
|
||||
let mut srv = test::TestServer::new(
|
||||
|app| app.handler(|req: HttpRequest| match req.query().get("qp") {
|
||||
Some(_) => httpcodes::HTTPOk.build().finish(),
|
||||
None => httpcodes::HTTPBadRequest.build().finish(),
|
||||
}));
|
||||
|
||||
let request = srv.get().uri(srv.url("/?qp=5").as_str()).finish().unwrap();
|
||||
|
||||
let response = srv.execute(request.send()).unwrap();
|
||||
assert!(response.status().is_success());
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_no_decompress() {
|
||||
let mut srv = test::TestServer::new(
|
||||
|
@@ -743,7 +743,7 @@ fn test_h2() {
|
||||
})
|
||||
});
|
||||
let _res = core.run(tcp);
|
||||
// assert_eq!(_res.unwrap(), Bytes::from_static(STR.as_ref()));
|
||||
// assert_eq!(res.unwrap(), Bytes::from_static(STR.as_ref()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
140
tests/test_ws.rs
140
tests/test_ws.rs
@@ -3,9 +3,14 @@ extern crate actix_web;
|
||||
extern crate futures;
|
||||
extern crate http;
|
||||
extern crate bytes;
|
||||
extern crate rand;
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::Stream;
|
||||
use rand::Rng;
|
||||
|
||||
#[cfg(feature="alpn")]
|
||||
extern crate openssl;
|
||||
|
||||
use actix_web::*;
|
||||
use actix::prelude::*;
|
||||
@@ -51,3 +56,138 @@ fn test_simple() {
|
||||
let (item, _) = srv.execute(reader.into_future()).unwrap();
|
||||
assert_eq!(item, Some(ws::Message::Close(ws::CloseCode::Normal)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_large_text() {
|
||||
let data = rand::thread_rng()
|
||||
.gen_ascii_chars()
|
||||
.take(65_536)
|
||||
.collect::<String>();
|
||||
|
||||
let mut srv = test::TestServer::new(
|
||||
|app| app.handler(|req| ws::start(req, Ws)));
|
||||
let (mut reader, mut writer) = srv.ws().unwrap();
|
||||
|
||||
for _ in 0..100 {
|
||||
writer.text(data.clone());
|
||||
let (item, r) = srv.execute(reader.into_future()).unwrap();
|
||||
reader = r;
|
||||
assert_eq!(item, Some(ws::Message::Text(data.clone())));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_large_bin() {
|
||||
let data = rand::thread_rng()
|
||||
.gen_ascii_chars()
|
||||
.take(65_536)
|
||||
.collect::<String>();
|
||||
|
||||
let mut srv = test::TestServer::new(
|
||||
|app| app.handler(|req| ws::start(req, Ws)));
|
||||
let (mut reader, mut writer) = srv.ws().unwrap();
|
||||
|
||||
for _ in 0..100 {
|
||||
writer.binary(data.clone());
|
||||
let (item, r) = srv.execute(reader.into_future()).unwrap();
|
||||
reader = r;
|
||||
assert_eq!(item, Some(ws::Message::Binary(Binary::from(data.clone()))));
|
||||
}
|
||||
}
|
||||
|
||||
struct Ws2 {
|
||||
count: usize,
|
||||
bin: bool,
|
||||
}
|
||||
|
||||
impl Actor for Ws2 {
|
||||
type Context = ws::WebsocketContext<Self>;
|
||||
|
||||
fn started(&mut self, ctx: &mut Self::Context) {
|
||||
self.send(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
impl Ws2 {
|
||||
fn send(&mut self, ctx: &mut ws::WebsocketContext<Self>) {
|
||||
if self.bin {
|
||||
ctx.binary(Vec::from("0".repeat(65_536)));
|
||||
} else {
|
||||
ctx.text("0".repeat(65_536));
|
||||
}
|
||||
ctx.drain().and_then(|_, act, ctx| {
|
||||
act.count += 1;
|
||||
if act.count != 10_000 {
|
||||
act.send(ctx);
|
||||
}
|
||||
actix::fut::ok(())
|
||||
}).wait(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamHandler<ws::Message, ws::ProtocolError> for Ws2 {
|
||||
|
||||
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
||||
match msg {
|
||||
ws::Message::Ping(msg) => ctx.pong(&msg),
|
||||
ws::Message::Text(text) => ctx.text(text),
|
||||
ws::Message::Binary(bin) => ctx.binary(bin),
|
||||
ws::Message::Close(reason) => ctx.close(reason, ""),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_server_send_text() {
|
||||
let data = Some(ws::Message::Text("0".repeat(65_536)));
|
||||
|
||||
let mut srv = test::TestServer::new(
|
||||
|app| app.handler(|req| ws::start(req, Ws2{count:0, bin: false})));
|
||||
let (mut reader, _writer) = srv.ws().unwrap();
|
||||
|
||||
for _ in 0..10_000 {
|
||||
let (item, r) = srv.execute(reader.into_future()).unwrap();
|
||||
reader = r;
|
||||
assert_eq!(item, data);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_server_send_bin() {
|
||||
let data = Some(ws::Message::Binary(Binary::from("0".repeat(65_536))));
|
||||
|
||||
let mut srv = test::TestServer::new(
|
||||
|app| app.handler(|req| ws::start(req, Ws2{count:0, bin: true})));
|
||||
let (mut reader, _writer) = srv.ws().unwrap();
|
||||
|
||||
for _ in 0..10_000 {
|
||||
let (item, r) = srv.execute(reader.into_future()).unwrap();
|
||||
reader = r;
|
||||
assert_eq!(item, data);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature="alpn")]
|
||||
fn test_ws_server_ssl() {
|
||||
extern crate openssl;
|
||||
use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype};
|
||||
|
||||
// load ssl keys
|
||||
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
|
||||
builder.set_private_key_file("tests/key.pem", SslFiletype::PEM).unwrap();
|
||||
builder.set_certificate_chain_file("tests/cert.pem").unwrap();
|
||||
|
||||
let mut srv = test::TestServer::build()
|
||||
.ssl(builder.build())
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
@@ -35,8 +35,9 @@ fn main() {
|
||||
-s, --size=[NUMBER] 'size of PUBLISH packet payload to send in KB'
|
||||
-w, --warm-up=[SECONDS] 'seconds before counter values are considered for reporting'
|
||||
-r, --sample-rate=[SECONDS] 'seconds between average reports'
|
||||
-c, --concurrency=[NUMBER] 'number of websockt connections to open and use concurrently for sending'
|
||||
-t, --threads=[NUMBER] 'number of threads to use'",
|
||||
-c, --concurrency=[NUMBER] 'number of websocket connections to open and use concurrently for sending'
|
||||
-t, --threads=[NUMBER] 'number of threads to use'
|
||||
--max-payload=[NUMBER] 'max size of payload before reconnect KB'",
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
@@ -50,9 +51,13 @@ fn main() {
|
||||
let threads = parse_u64_default(matches.value_of("threads"), num_cpus::get() as u64);
|
||||
let concurrency = parse_u64_default(matches.value_of("concurrency"), 1);
|
||||
let payload_size: usize = match matches.value_of("size") {
|
||||
Some(s) => parse_u64_default(Some(s), 0) as usize * 1024,
|
||||
Some(s) => parse_u64_default(Some(s), 1) as usize * 1024,
|
||||
None => 1024,
|
||||
};
|
||||
let max_payload_size: usize = match matches.value_of("max-payload") {
|
||||
Some(s) => parse_u64_default(Some(s), 0) as usize * 1024,
|
||||
None => 0,
|
||||
};
|
||||
let warmup_seconds = parse_u64_default(matches.value_of("warm-up"), 2) as u64;
|
||||
let sample_rate = parse_u64_default(matches.value_of("sample-rate"), 1) as usize;
|
||||
|
||||
@@ -64,7 +69,10 @@ fn main() {
|
||||
|
||||
let sys = actix::System::new("ws-client");
|
||||
|
||||
let mut report = true;
|
||||
let _: () = Perf{counters: perf_counters.clone(),
|
||||
payload: payload.len(),
|
||||
sample_rate_secs: sample_rate}.start();
|
||||
|
||||
for t in 0..threads {
|
||||
let pl = payload.clone();
|
||||
let ws = ws_url.clone();
|
||||
@@ -72,40 +80,41 @@ fn main() {
|
||||
let addr = Arbiter::new(format!("test {}", t));
|
||||
|
||||
addr.do_send(actix::msgs::Execute::new(move || -> Result<(), ()> {
|
||||
let mut reps = report;
|
||||
for _ in 0..concurrency {
|
||||
let pl2 = pl.clone();
|
||||
let perf2 = perf.clone();
|
||||
let ws2 = ws.clone();
|
||||
|
||||
Arbiter::handle().spawn(
|
||||
ws::Client::new(&ws).connect()
|
||||
ws::Client::new(&ws)
|
||||
.write_buffer_capacity(0)
|
||||
.connect()
|
||||
.map_err(|e| {
|
||||
println!("Error: {}", e);
|
||||
Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
||||
//Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
||||
()
|
||||
})
|
||||
.map(move |(reader, writer)| {
|
||||
let addr: Addr<Syn, _> = ChatClient::create(move |ctx| {
|
||||
ChatClient::add_stream(reader, ctx);
|
||||
ChatClient{conn: writer,
|
||||
ChatClient{url: ws2,
|
||||
conn: writer,
|
||||
payload: pl2,
|
||||
report: reps,
|
||||
bin: bin,
|
||||
ts: time::precise_time_ns(),
|
||||
perf_counters: perf2,
|
||||
sample_rate_secs: sample_rate,
|
||||
sent: 0,
|
||||
max_payload_size: max_payload_size,
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
reps = false;
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
report = false;
|
||||
}
|
||||
|
||||
let _ = sys.run();
|
||||
let res = sys.run();
|
||||
}
|
||||
|
||||
fn parse_u64_default(input: Option<&str>, default: u64) -> u64 {
|
||||
@@ -113,43 +122,33 @@ fn parse_u64_default(input: Option<&str>, default: u64) -> u64 {
|
||||
.unwrap_or(default)
|
||||
}
|
||||
|
||||
struct ChatClient{
|
||||
conn: ws::ClientWriter,
|
||||
payload: Arc<String>,
|
||||
ts: u64,
|
||||
bin: bool,
|
||||
report: bool,
|
||||
perf_counters: Arc<PerfCounters>,
|
||||
struct Perf {
|
||||
counters: Arc<PerfCounters>,
|
||||
payload: usize,
|
||||
sample_rate_secs: usize,
|
||||
}
|
||||
|
||||
impl Actor for ChatClient {
|
||||
impl Actor for Perf {
|
||||
type Context = Context<Self>;
|
||||
|
||||
fn started(&mut self, ctx: &mut Context<Self>) {
|
||||
self.send_text();
|
||||
if self.report {
|
||||
self.sample_rate(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
fn stopping(&mut self, _: &mut Context<Self>) -> Running {
|
||||
Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
||||
Running::Stop
|
||||
self.sample_rate(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
impl ChatClient {
|
||||
impl Perf {
|
||||
fn sample_rate(&self, ctx: &mut Context<Self>) {
|
||||
ctx.run_later(Duration::new(self.sample_rate_secs as u64, 0), |act, ctx| {
|
||||
let req_count = act.perf_counters.pull_request_count();
|
||||
let req_count = act.counters.pull_request_count();
|
||||
if req_count != 0 {
|
||||
let latency = act.perf_counters.pull_latency_ns();
|
||||
let latency_max = act.perf_counters.pull_latency_max_ns();
|
||||
let conns = act.counters.pull_connections_count();
|
||||
let latency = act.counters.pull_latency_ns();
|
||||
let latency_max = act.counters.pull_latency_max_ns();
|
||||
println!(
|
||||
"rate: {}, throughput: {:?} kb, latency: {}, latency max: {}",
|
||||
"rate: {}, conns: {}, throughput: {:?} kb, latency: {}, latency max: {}",
|
||||
req_count / act.sample_rate_secs,
|
||||
(((req_count * act.payload.len()) as f64) / 1024.0) /
|
||||
conns / act.sample_rate_secs,
|
||||
(((req_count * act.payload) as f64) / 1024.0) /
|
||||
act.sample_rate_secs as f64,
|
||||
time::Duration::nanoseconds((latency / req_count as u64) as i64),
|
||||
time::Duration::nanoseconds(latency_max as i64)
|
||||
@@ -159,13 +158,71 @@ impl ChatClient {
|
||||
act.sample_rate(ctx);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn send_text(&mut self) {
|
||||
self.ts = time::precise_time_ns();
|
||||
if self.bin {
|
||||
self.conn.binary(&self.payload);
|
||||
struct ChatClient{
|
||||
url: String,
|
||||
conn: ws::ClientWriter,
|
||||
payload: Arc<String>,
|
||||
ts: u64,
|
||||
bin: bool,
|
||||
perf_counters: Arc<PerfCounters>,
|
||||
sent: usize,
|
||||
max_payload_size: usize,
|
||||
}
|
||||
|
||||
impl Actor for ChatClient {
|
||||
type Context = Context<Self>;
|
||||
|
||||
fn started(&mut self, ctx: &mut Context<Self>) {
|
||||
self.send_text();
|
||||
self.perf_counters.register_connection();
|
||||
}
|
||||
}
|
||||
|
||||
impl ChatClient {
|
||||
|
||||
fn send_text(&mut self) -> bool {
|
||||
self.sent += self.payload.len();
|
||||
|
||||
if self.max_payload_size > 0 && self.sent > self.max_payload_size {
|
||||
let ws = self.url.clone();
|
||||
let pl = self.payload.clone();
|
||||
let bin = self.bin;
|
||||
let perf_counters = self.perf_counters.clone();
|
||||
let max_payload_size = self.max_payload_size;
|
||||
|
||||
Arbiter::handle().spawn(
|
||||
ws::Client::new(&self.url).connect()
|
||||
.map_err(|e| {
|
||||
println!("Error: {}", e);
|
||||
Arbiter::system().do_send(actix::msgs::SystemExit(0));
|
||||
()
|
||||
})
|
||||
.map(move |(reader, writer)| {
|
||||
let addr: Addr<Syn, _> = ChatClient::create(move |ctx| {
|
||||
ChatClient::add_stream(reader, ctx);
|
||||
ChatClient{url: ws,
|
||||
conn: writer,
|
||||
payload: pl,
|
||||
bin: bin,
|
||||
ts: time::precise_time_ns(),
|
||||
perf_counters: perf_counters,
|
||||
sent: 0,
|
||||
max_payload_size: max_payload_size,
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
false
|
||||
} else {
|
||||
self.conn.text(&self.payload);
|
||||
self.ts = time::precise_time_ns();
|
||||
if self.bin {
|
||||
self.conn.binary(&self.payload);
|
||||
} else {
|
||||
self.conn.text(&self.payload);
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,7 +240,9 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for ChatClient {
|
||||
if txt == self.payload.as_ref().as_str() {
|
||||
self.perf_counters.register_request();
|
||||
self.perf_counters.register_latency(time::precise_time_ns() - self.ts);
|
||||
self.send_text();
|
||||
if !self.send_text() {
|
||||
ctx.stop();
|
||||
}
|
||||
} else {
|
||||
println!("not eaqual");
|
||||
}
|
||||
@@ -196,6 +255,7 @@ impl StreamHandler<ws::Message, ws::ProtocolError> for ChatClient {
|
||||
|
||||
pub struct PerfCounters {
|
||||
req: AtomicUsize,
|
||||
conn: AtomicUsize,
|
||||
lat: AtomicUsize,
|
||||
lat_max: AtomicUsize
|
||||
}
|
||||
@@ -204,6 +264,7 @@ impl PerfCounters {
|
||||
pub fn new() -> PerfCounters {
|
||||
PerfCounters {
|
||||
req: AtomicUsize::new(0),
|
||||
conn: AtomicUsize::new(0),
|
||||
lat: AtomicUsize::new(0),
|
||||
lat_max: AtomicUsize::new(0),
|
||||
}
|
||||
@@ -213,6 +274,10 @@ impl PerfCounters {
|
||||
self.req.swap(0, Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub fn pull_connections_count(&self) -> usize {
|
||||
self.conn.swap(0, Ordering::SeqCst)
|
||||
}
|
||||
|
||||
pub fn pull_latency_ns(&self) -> u64 {
|
||||
self.lat.swap(0, Ordering::SeqCst) as u64
|
||||
}
|
||||
@@ -225,6 +290,10 @@ impl PerfCounters {
|
||||
self.req.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub fn register_connection(&self) {
|
||||
self.conn.fetch_add(1, Ordering::SeqCst);
|
||||
}
|
||||
|
||||
pub fn register_latency(&self, nanos: u64) {
|
||||
let nanos = nanos as usize;
|
||||
self.lat.fetch_add(nanos, Ordering::SeqCst);
|
||||
|
Reference in New Issue
Block a user