mirror of
https://github.com/fafhrd91/actix-web
synced 2025-08-28 15:57:47 +02:00
Compare commits
34 Commits
v1.0.0-alp
...
v1.0.0-alp
Author | SHA1 | Date | |
---|---|---|---|
|
53da55aa3c | ||
|
aa78565453 | ||
|
75b213a6f0 | ||
|
3c650ca194 | ||
|
219baf3323 | ||
|
ec09d6fbe6 | ||
|
68d2203dd6 | ||
|
748289f0ff | ||
|
4ef46e26f9 | ||
|
3872d3ba5a | ||
|
b1523ab78c | ||
|
fbedaec661 | ||
|
02fcaca3da | ||
|
18593d8476 | ||
|
b6dacaa23a | ||
|
f89321fd01 | ||
|
0d4a8e1b1c | ||
|
162cd3eecd | ||
|
a655bdac52 | ||
|
309c480782 | ||
|
9c205f9f1d | ||
|
1f5c0f50f9 | ||
|
d8bc66a18e | ||
|
bc834f6a03 | ||
|
dc7c3d37a1 | ||
|
1e2bd68e83 | ||
|
954fe21751 | ||
|
7d6085ddbd | ||
|
cef3dc3586 | ||
|
237bfba1ed | ||
|
dfa0abf5a5 | ||
|
e738361e09 | ||
|
f56072954b | ||
|
2a89b995aa |
@@ -8,7 +8,6 @@ cache:
|
||||
|
||||
matrix:
|
||||
include:
|
||||
- rust: 1.31.0
|
||||
- rust: stable
|
||||
- rust: beta
|
||||
- rust: nightly-2019-04-02
|
||||
@@ -35,7 +34,6 @@ before_script:
|
||||
- export PATH=$PATH:~/.cargo/bin
|
||||
|
||||
script:
|
||||
- cargo clean
|
||||
- cargo update
|
||||
- cargo check --all --no-default-features
|
||||
- cargo test --all-features --all -- --nocapture
|
||||
@@ -44,7 +42,7 @@ script:
|
||||
after_success:
|
||||
- |
|
||||
if [[ "$TRAVIS_OS_NAME" == "linux" && "$TRAVIS_PULL_REQUEST" = "false" && "$TRAVIS_BRANCH" == "master" && "$TRAVIS_RUST_VERSION" == "stable" ]]; then
|
||||
cargo doc --no-deps --all-features &&
|
||||
cargo doc --all-features &&
|
||||
echo "<meta http-equiv=refresh content=0;url=os_balloon/index.html>" > target/doc/index.html &&
|
||||
git clone https://github.com/davisp/ghp-import.git &&
|
||||
./ghp-import/ghp_import.py -n -p -f -m "Documentation upload" -r https://"$GH_TOKEN"@github.com/"$TRAVIS_REPO_SLUG.git" target/doc &&
|
||||
|
19
CHANGES.md
19
CHANGES.md
@@ -1,5 +1,24 @@
|
||||
# Changes
|
||||
|
||||
## [1.0.0-alpha.4] - 2019-04-08
|
||||
|
||||
### Added
|
||||
|
||||
* `App::configure()` allow to offload app configuration to different methods
|
||||
|
||||
* Added `URLPath` option for logger
|
||||
|
||||
* Added `ServiceRequest::app_data()`, returns `Data<T>`
|
||||
|
||||
* Added `ServiceFromRequest::app_data()`, returns `Data<T>`
|
||||
|
||||
### Changed
|
||||
|
||||
* `FromRequest` trait refactoring
|
||||
|
||||
* Move multipart support to actix-multipart crate
|
||||
|
||||
|
||||
## [1.0.0-alpha.3] - 2019-04-02
|
||||
|
||||
### Changed
|
||||
|
16
Cargo.toml
16
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "1.0.0-alpha.3"
|
||||
version = "1.0.0-alpha.4"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
|
||||
readme = "README.md"
|
||||
@@ -30,6 +30,7 @@ members = [
|
||||
"actix-http",
|
||||
"actix-files",
|
||||
"actix-session",
|
||||
"actix-multipart",
|
||||
"actix-web-actors",
|
||||
"actix-web-codegen",
|
||||
"test-server",
|
||||
@@ -67,23 +68,22 @@ rust-tls = ["rustls", "actix-server/rust-tls"]
|
||||
|
||||
[dependencies]
|
||||
actix-codec = "0.1.1"
|
||||
actix-service = "0.3.4"
|
||||
actix-service = "0.3.6"
|
||||
actix-utils = "0.3.4"
|
||||
actix-router = "0.1.0"
|
||||
actix-router = "0.1.2"
|
||||
actix-rt = "0.2.2"
|
||||
actix-web-codegen = "0.1.0-alpha.1"
|
||||
actix-http = { version = "0.1.0-alpha.3", features=["fail"] }
|
||||
actix-http = { version = "0.1.0-alpha.4", features=["fail"] }
|
||||
actix-server = "0.4.2"
|
||||
actix-server-config = "0.1.0"
|
||||
actix-threadpool = "0.1.0"
|
||||
awc = { version = "0.1.0-alpha.3", optional = true }
|
||||
awc = { version = "0.1.0-alpha.4", optional = true }
|
||||
|
||||
bytes = "0.4"
|
||||
derive_more = "0.14"
|
||||
encoding = "0.2"
|
||||
futures = "0.1"
|
||||
hashbrown = "0.1.8"
|
||||
httparse = "1.3"
|
||||
log = "0.4"
|
||||
mime = "0.3"
|
||||
net2 = "0.2.33"
|
||||
@@ -101,7 +101,7 @@ openssl = { version="0.10", optional = true }
|
||||
rustls = { version = "^0.15", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-http = { version = "0.1.0-alpha.3", features=["ssl", "brotli", "flate2-zlib"] }
|
||||
actix-http = { version = "0.1.0-alpha.4", features=["ssl", "brotli", "flate2-zlib"] }
|
||||
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] }
|
||||
rand = "0.6"
|
||||
env_logger = "0.6"
|
||||
@@ -124,4 +124,4 @@ actix-web-codegen = { path = "actix-web-codegen" }
|
||||
actix-web-actors = { path = "actix-web-actors" }
|
||||
actix-session = { path = "actix-session" }
|
||||
actix-files = { path = "actix-files" }
|
||||
awc = { path = "awc" }
|
||||
awc = { path = "awc" }
|
55
MIGRATION.md
55
MIGRATION.md
@@ -1,3 +1,58 @@
|
||||
## 1.0
|
||||
|
||||
* `State` is now `Data`. You register Data during the App initialization process
|
||||
and then access it from handlers either using a Data extractor or using
|
||||
HttpRequest's api.
|
||||
|
||||
instead of
|
||||
|
||||
```rust
|
||||
App.with_state(T)
|
||||
```
|
||||
|
||||
use App's `data` method
|
||||
|
||||
```rust
|
||||
App.new()
|
||||
.data(T)
|
||||
```
|
||||
|
||||
and either use the Data extractor within your handler
|
||||
|
||||
```rust
|
||||
use actix_web::web::Data;
|
||||
|
||||
fn endpoint_handler(Data<T>)){
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
.. or access your Data element from the HttpRequest
|
||||
|
||||
```rust
|
||||
fn endpoint_handler(req: HttpRequest) {
|
||||
let data: Option<Data<T>> = req.app_data::<T>();
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
* AsyncResponder is deprecated.
|
||||
|
||||
instead of
|
||||
|
||||
```rust
|
||||
use actix_web::AsyncResponder;
|
||||
|
||||
fn endpoint_handler(...) -> impl Future<Item=HttpResponse, Error=Error>{
|
||||
...
|
||||
.responder()
|
||||
}
|
||||
```
|
||||
|
||||
.. simply omit AsyncResponder and the corresponding responder() finish method
|
||||
|
||||
|
||||
|
||||
## 0.7.15
|
||||
|
||||
* The `' '` character is not percent decoded anymore before matching routes. If you need to use it in
|
||||
|
@@ -1,5 +1,10 @@
|
||||
# Changes
|
||||
|
||||
## [0.1.0-alpha.4] - 2019-04-08
|
||||
|
||||
* Update actix-web to alpha4
|
||||
|
||||
|
||||
## [0.1.0-alpha.2] - 2019-04-02
|
||||
|
||||
* Add default handler support
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-files"
|
||||
version = "0.1.0-alpha.2"
|
||||
version = "0.1.0-alpha.4"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Static files support for actix web."
|
||||
readme = "README.md"
|
||||
@@ -18,7 +18,7 @@ name = "actix_files"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-web = "1.0.0-alpha.3"
|
||||
actix-web = "1.0.0-alpha.4"
|
||||
actix-service = "0.3.4"
|
||||
bitflags = "1"
|
||||
bytes = "0.4"
|
||||
@@ -31,4 +31,4 @@ percent-encoding = "1.0"
|
||||
v_htmlescape = "0.4"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-web = { version = "1.0.0-alpha.3", features=["ssl"] }
|
||||
actix-web = { version = "1.0.0-alpha.4", features=["ssl"] }
|
||||
|
@@ -10,8 +10,8 @@ use std::{cmp, io};
|
||||
use actix_service::boxed::{self, BoxedNewService, BoxedService};
|
||||
use actix_service::{IntoNewService, NewService, Service};
|
||||
use actix_web::dev::{
|
||||
HttpServiceFactory, Payload, ResourceDef, ServiceConfig, ServiceFromRequest,
|
||||
ServiceRequest, ServiceResponse,
|
||||
HttpServiceFactory, Payload, ResourceDef, ServiceConfig, ServiceRequest,
|
||||
ServiceResponse,
|
||||
};
|
||||
use actix_web::error::{BlockingError, Error, ErrorInternalServerError};
|
||||
use actix_web::http::header::DispositionType;
|
||||
@@ -423,7 +423,7 @@ impl<P> FilesService<P> {
|
||||
> {
|
||||
log::debug!("Files: Failed to handle {}: {}", req.path(), e);
|
||||
if let Some(ref mut default) = self.default {
|
||||
Either::B(default.call(ServiceRequest::from_parts(req, payload)))
|
||||
default.call(ServiceRequest::from_parts(req, payload))
|
||||
} else {
|
||||
Either::A(ok(ServiceResponse::from_err(e, req.clone())))
|
||||
}
|
||||
@@ -551,8 +551,8 @@ impl<P> FromRequest<P> for PathBufWrp {
|
||||
type Error = UriSegmentError;
|
||||
type Future = Result<Self, Self::Error>;
|
||||
|
||||
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
|
||||
PathBufWrp::get_pathbuf(req.request().match_info().path())
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
|
||||
PathBufWrp::get_pathbuf(req.match_info().path())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -979,14 +979,32 @@ mod tests {
|
||||
.to_request();
|
||||
let res = test::call_success(&mut srv, request);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert!(!res.headers().contains_key(header::CONTENT_ENCODING));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_named_file_content_encoding_gzip() {
|
||||
let mut srv = test::init_service(App::new().enable_encoding().service(
|
||||
web::resource("/").to(|| {
|
||||
NamedFile::open("Cargo.toml")
|
||||
.unwrap()
|
||||
.set_content_encoding(header::ContentEncoding::Gzip)
|
||||
}),
|
||||
));
|
||||
|
||||
let request = TestRequest::get()
|
||||
.uri("/")
|
||||
.header(header::ACCEPT_ENCODING, "gzip")
|
||||
.to_request();
|
||||
let res = test::call_success(&mut srv, request);
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
res.headers()
|
||||
.get(header::CONTENT_ENCODING)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap(),
|
||||
"identity"
|
||||
"gzip"
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -296,10 +296,9 @@ impl Responder for NamedFile {
|
||||
header::CONTENT_DISPOSITION,
|
||||
self.content_disposition.to_string(),
|
||||
);
|
||||
// TODO blocking by compressing
|
||||
// if let Some(current_encoding) = self.encoding {
|
||||
// resp.content_encoding(current_encoding);
|
||||
// }
|
||||
if let Some(current_encoding) = self.encoding {
|
||||
resp.encoding(current_encoding);
|
||||
}
|
||||
let reader = ChunkedReadFile {
|
||||
size: self.md.len(),
|
||||
offset: 0,
|
||||
@@ -345,7 +344,7 @@ impl Responder for NamedFile {
|
||||
// check last modified
|
||||
let not_modified = if !none_match(etag.as_ref(), req) {
|
||||
true
|
||||
} else if req.headers().contains_key(header::IF_NONE_MATCH) {
|
||||
} else if req.headers().contains_key(&header::IF_NONE_MATCH) {
|
||||
false
|
||||
} else if let (Some(ref m), Some(header::IfModifiedSince(ref since))) =
|
||||
(last_modified, req.get_header())
|
||||
@@ -379,7 +378,7 @@ impl Responder for NamedFile {
|
||||
let mut offset = 0;
|
||||
|
||||
// check for range header
|
||||
if let Some(ranges) = req.headers().get(header::RANGE) {
|
||||
if let Some(ranges) = req.headers().get(&header::RANGE) {
|
||||
if let Ok(rangesheader) = ranges.to_str() {
|
||||
if let Ok(rangesvec) = HttpRange::parse(rangesheader, length) {
|
||||
length = rangesvec[0].length;
|
||||
|
@@ -1,5 +1,24 @@
|
||||
# Changes
|
||||
|
||||
## [0.1.0-alpha.4] - 2019-04-08
|
||||
|
||||
### Added
|
||||
|
||||
* Add minimal `std::error::Error` impl for `Error`
|
||||
|
||||
### Changed
|
||||
|
||||
* Export IntoHeaderValue
|
||||
|
||||
* Render error and return as response body
|
||||
|
||||
* Use thread pool for response body comression
|
||||
|
||||
### Deleted
|
||||
|
||||
* Removed PayloadBuffer
|
||||
|
||||
|
||||
## [0.1.0-alpha.3] - 2019-04-02
|
||||
|
||||
### Added
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-http"
|
||||
version = "0.1.0-alpha.3"
|
||||
version = "0.1.0-alpha.4"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix http primitives"
|
||||
readme = "README.md"
|
||||
@@ -48,10 +48,10 @@ fail = ["failure"]
|
||||
secure-cookies = ["ring"]
|
||||
|
||||
[dependencies]
|
||||
actix-service = "0.3.4"
|
||||
actix-service = "0.3.6"
|
||||
actix-codec = "0.1.2"
|
||||
actix-connect = "0.1.0"
|
||||
actix-utils = "0.3.4"
|
||||
actix-connect = "0.1.2"
|
||||
actix-utils = "0.3.5"
|
||||
actix-server-config = "0.1.0"
|
||||
actix-threadpool = "0.1.0"
|
||||
|
||||
@@ -59,7 +59,9 @@ base64 = "0.10"
|
||||
bitflags = "1.0"
|
||||
bytes = "0.4"
|
||||
byteorder = "1.2"
|
||||
copyless = "0.1.2"
|
||||
derive_more = "0.14"
|
||||
either = "1.5.2"
|
||||
encoding = "0.2"
|
||||
futures = "0.1"
|
||||
hashbrown = "0.1.8"
|
||||
|
@@ -1,28 +0,0 @@
|
||||
use std::{env, io};
|
||||
|
||||
use actix_codec::Framed;
|
||||
use actix_http::{h1, Response, SendResponse, ServiceConfig};
|
||||
use actix_server::{Io, Server};
|
||||
use actix_service::{fn_service, NewService};
|
||||
use actix_utils::framed::IntoFramed;
|
||||
use actix_utils::stream::TakeItem;
|
||||
use futures::Future;
|
||||
use tokio_tcp::TcpStream;
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
env::set_var("RUST_LOG", "framed_hello=info");
|
||||
env_logger::init();
|
||||
|
||||
Server::build()
|
||||
.bind("framed_hello", "127.0.0.1:8080", || {
|
||||
fn_service(|io: Io<TcpStream>| Ok(io.into_parts().0))
|
||||
.and_then(IntoFramed::new(|| h1::Codec::new(ServiceConfig::default())))
|
||||
.and_then(TakeItem::new().map_err(|_| ()))
|
||||
.and_then(|(_req, _framed): (_, Framed<_, _>)| {
|
||||
SendResponse::send(_framed, Response::Ok().body("Hello world!"))
|
||||
.map_err(|_| ())
|
||||
.map(|_| ())
|
||||
})
|
||||
})?
|
||||
.run()
|
||||
}
|
@@ -153,7 +153,7 @@ impl MessageBody for Body {
|
||||
if len == 0 {
|
||||
Ok(Async::Ready(None))
|
||||
} else {
|
||||
Ok(Async::Ready(Some(bin.split_to(len))))
|
||||
Ok(Async::Ready(Some(mem::replace(bin, Bytes::new()))))
|
||||
}
|
||||
}
|
||||
Body::Message(ref mut body) => body.poll_next(),
|
||||
@@ -359,7 +359,7 @@ where
|
||||
}
|
||||
|
||||
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> {
|
||||
self.stream.poll().map_err(|e| e.into())
|
||||
self.stream.poll().map_err(std::convert::Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,45 +1,57 @@
|
||||
use std::fmt::Debug;
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use actix_server_config::ServerConfig as SrvConfig;
|
||||
use actix_service::{IntoNewService, NewService};
|
||||
use actix_service::{IntoNewService, NewService, Service};
|
||||
|
||||
use crate::body::MessageBody;
|
||||
use crate::config::{KeepAlive, ServiceConfig};
|
||||
use crate::error::Error;
|
||||
use crate::h1::{ExpectHandler, H1Service};
|
||||
use crate::h2::H2Service;
|
||||
use crate::request::Request;
|
||||
use crate::response::Response;
|
||||
|
||||
use crate::h1::H1Service;
|
||||
use crate::h2::H2Service;
|
||||
use crate::service::HttpService;
|
||||
|
||||
/// A http service builder
|
||||
///
|
||||
/// This type can be used to construct an instance of `http service` through a
|
||||
/// builder-like pattern.
|
||||
pub struct HttpServiceBuilder<T, S> {
|
||||
pub struct HttpServiceBuilder<T, S, X = ExpectHandler> {
|
||||
keep_alive: KeepAlive,
|
||||
client_timeout: u64,
|
||||
client_disconnect: u64,
|
||||
expect: X,
|
||||
_t: PhantomData<(T, S)>,
|
||||
}
|
||||
|
||||
impl<T, S> HttpServiceBuilder<T, S>
|
||||
impl<T, S> HttpServiceBuilder<T, S, ExpectHandler>
|
||||
where
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Error: Debug + 'static,
|
||||
S::Service: 'static,
|
||||
S::Error: Into<Error>,
|
||||
S::InitError: fmt::Debug,
|
||||
{
|
||||
/// Create instance of `ServiceConfigBuilder`
|
||||
pub fn new() -> HttpServiceBuilder<T, S> {
|
||||
pub fn new() -> Self {
|
||||
HttpServiceBuilder {
|
||||
keep_alive: KeepAlive::Timeout(5),
|
||||
client_timeout: 5000,
|
||||
client_disconnect: 0,
|
||||
expect: ExpectHandler,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, X> HttpServiceBuilder<T, S, X>
|
||||
where
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::InitError: fmt::Debug,
|
||||
X: NewService<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
X::InitError: fmt::Debug,
|
||||
{
|
||||
/// Set server keep-alive setting.
|
||||
///
|
||||
/// By default keep alive is set to a 5 seconds.
|
||||
@@ -95,10 +107,12 @@ where
|
||||
// }
|
||||
|
||||
/// Finish service configuration and create *http service* for HTTP/1 protocol.
|
||||
pub fn h1<F, P, B>(self, service: F) -> H1Service<T, P, S, B>
|
||||
pub fn h1<F, P, B>(self, service: F) -> H1Service<T, P, S, B, X>
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
F: IntoNewService<S, SrvConfig>,
|
||||
S::Error: Into<Error>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
{
|
||||
let cfg = ServiceConfig::new(
|
||||
@@ -106,7 +120,7 @@ where
|
||||
self.client_timeout,
|
||||
self.client_disconnect,
|
||||
);
|
||||
H1Service::with_config(cfg, service.into_new_service())
|
||||
H1Service::with_config(cfg, service.into_new_service()).expect(self.expect)
|
||||
}
|
||||
|
||||
/// Finish service configuration and create *http service* for HTTP/2 protocol.
|
||||
@@ -114,7 +128,10 @@ where
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
F: IntoNewService<S, SrvConfig>,
|
||||
S::Error: Into<Error>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
<S::Service as Service>::Future: 'static,
|
||||
{
|
||||
let cfg = ServiceConfig::new(
|
||||
self.keep_alive,
|
||||
@@ -129,7 +146,10 @@ where
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
F: IntoNewService<S, SrvConfig>,
|
||||
S::Error: Into<Error>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
<S::Service as Service>::Future: 'static,
|
||||
{
|
||||
let cfg = ServiceConfig::new(
|
||||
self.keep_alive,
|
||||
|
@@ -69,7 +69,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncRead + AsyncWrite + 'static> IoConnection<T> {
|
||||
impl<T: AsyncRead + AsyncWrite> IoConnection<T> {
|
||||
pub(crate) fn new(
|
||||
io: ConnectionType<T>,
|
||||
created: time::Instant,
|
||||
|
@@ -21,8 +21,18 @@ use openssl::ssl::SslConnector;
|
||||
#[cfg(not(feature = "ssl"))]
|
||||
type SslConnector = ();
|
||||
|
||||
/// Http client connector builde instance.
|
||||
/// `Connector` type uses builder-like pattern for connector service construction.
|
||||
/// Manages http client network connectivity
|
||||
/// The `Connector` type uses a builder-like combinator pattern for service
|
||||
/// construction that finishes by calling the `.finish()` method.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// use std::time::Duration;
|
||||
/// use actix_http::client::Connector;
|
||||
///
|
||||
/// let connector = Connector::new()
|
||||
/// .timeout(Duration::from_secs(5))
|
||||
/// .finish();
|
||||
/// ```
|
||||
pub struct Connector<T, U> {
|
||||
connector: T,
|
||||
timeout: Duration,
|
||||
@@ -164,7 +174,9 @@ where
|
||||
}
|
||||
|
||||
/// Finish configuration process and create connector service.
|
||||
pub fn service(
|
||||
/// The Connector builder always concludes by calling `finish()` last in
|
||||
/// its combinator chain.
|
||||
pub fn finish(
|
||||
self,
|
||||
) -> impl Service<Request = Uri, Response = impl Connection, Error = ConnectError> + Clone
|
||||
{
|
||||
@@ -253,6 +265,15 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[deprecated(since = "0.1.0-alpha4", note = "please use `.finish()` method")]
|
||||
pub fn service(
|
||||
self,
|
||||
) -> impl Service<Request = Uri, Response = impl Connection, Error = ConnectError> + Clone
|
||||
{
|
||||
self.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ssl"))]
|
||||
|
@@ -104,10 +104,9 @@ where
|
||||
let (parts, body) = resp.into_parts();
|
||||
let payload = if head_req { Payload::None } else { body.into() };
|
||||
|
||||
let mut head = ResponseHead::default();
|
||||
let mut head = ResponseHead::new(parts.status);
|
||||
head.version = parts.version;
|
||||
head.status = parts.status;
|
||||
head.headers = parts.headers;
|
||||
head.headers = parts.headers.into();
|
||||
|
||||
Ok((head, payload))
|
||||
})
|
||||
|
@@ -21,6 +21,7 @@ use tokio_timer::{sleep, Delay};
|
||||
use super::connection::{ConnectionType, IoConnection};
|
||||
use super::error::ConnectError;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum Protocol {
|
||||
Http1,
|
||||
|
@@ -68,8 +68,8 @@ impl Key {
|
||||
encryption_key.copy_from_slice(&both_keys[SIGNED_KEY_LEN..]);
|
||||
|
||||
Key {
|
||||
signing_key: signing_key,
|
||||
encryption_key: encryption_key,
|
||||
signing_key,
|
||||
encryption_key,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -37,7 +37,7 @@ impl<'a> PrivateJar<'a> {
|
||||
let mut key_array = [0u8; KEY_LEN];
|
||||
key_array.copy_from_slice(key.encryption());
|
||||
PrivateJar {
|
||||
parent: parent,
|
||||
parent,
|
||||
key: key_array,
|
||||
}
|
||||
}
|
||||
|
@@ -31,7 +31,7 @@ impl<'a> SignedJar<'a> {
|
||||
#[doc(hidden)]
|
||||
pub fn new(parent: &'a mut CookieJar, key: &Key) -> SignedJar<'a> {
|
||||
SignedJar {
|
||||
parent: parent,
|
||||
parent,
|
||||
key: SigningKey::new(HMAC_DIGEST, key.signing()),
|
||||
}
|
||||
}
|
||||
|
@@ -55,7 +55,7 @@ where
|
||||
#[inline]
|
||||
pub fn from_headers(stream: S, headers: &HeaderMap) -> Decoder<S> {
|
||||
// check content-encoding
|
||||
let encoding = if let Some(enc) = headers.get(CONTENT_ENCODING) {
|
||||
let encoding = if let Some(enc) = headers.get(&CONTENT_ENCODING) {
|
||||
if let Ok(enc) = enc.to_str() {
|
||||
ContentEncoding::from(enc)
|
||||
} else {
|
||||
|
@@ -1,13 +1,13 @@
|
||||
//! Stream encoder
|
||||
use std::io::{self, Write};
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures::{Async, Poll};
|
||||
|
||||
use actix_threadpool::{run, CpuFuture};
|
||||
#[cfg(feature = "brotli")]
|
||||
use brotli2::write::BrotliEncoder;
|
||||
use bytes::Bytes;
|
||||
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
|
||||
use flate2::write::{GzEncoder, ZlibEncoder};
|
||||
use futures::{Async, Future, Poll};
|
||||
|
||||
use crate::body::{Body, BodySize, MessageBody, ResponseBody};
|
||||
use crate::http::header::{ContentEncoding, CONTENT_ENCODING};
|
||||
@@ -16,9 +16,13 @@ use crate::{Error, ResponseHead};
|
||||
|
||||
use super::Writer;
|
||||
|
||||
const INPLACE: usize = 2049;
|
||||
|
||||
pub struct Encoder<B> {
|
||||
eof: bool,
|
||||
body: EncoderBody<B>,
|
||||
encoder: Option<ContentEncoder>,
|
||||
fut: Option<CpuFuture<ContentEncoder, io::Error>>,
|
||||
}
|
||||
|
||||
impl<B: MessageBody> Encoder<B> {
|
||||
@@ -27,73 +31,60 @@ impl<B: MessageBody> Encoder<B> {
|
||||
head: &mut ResponseHead,
|
||||
body: ResponseBody<B>,
|
||||
) -> ResponseBody<Encoder<B>> {
|
||||
let has_ce = head.headers().contains_key(CONTENT_ENCODING);
|
||||
match body {
|
||||
ResponseBody::Other(b) => match b {
|
||||
Body::None => ResponseBody::Other(Body::None),
|
||||
Body::Empty => ResponseBody::Other(Body::Empty),
|
||||
Body::Bytes(buf) => {
|
||||
if !(has_ce
|
||||
|| encoding == ContentEncoding::Identity
|
||||
|| encoding == ContentEncoding::Auto)
|
||||
{
|
||||
let mut enc = ContentEncoder::encoder(encoding).unwrap();
|
||||
let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING)
|
||||
|| head.status == StatusCode::SWITCHING_PROTOCOLS
|
||||
|| encoding == ContentEncoding::Identity
|
||||
|| encoding == ContentEncoding::Auto);
|
||||
|
||||
// TODO return error!
|
||||
let _ = enc.write(buf.as_ref());
|
||||
let body = enc.finish().unwrap();
|
||||
update_head(encoding, head);
|
||||
ResponseBody::Other(Body::Bytes(body))
|
||||
let body = match body {
|
||||
ResponseBody::Other(b) => match b {
|
||||
Body::None => return ResponseBody::Other(Body::None),
|
||||
Body::Empty => return ResponseBody::Other(Body::Empty),
|
||||
Body::Bytes(buf) => {
|
||||
if can_encode {
|
||||
EncoderBody::Bytes(buf)
|
||||
} else {
|
||||
ResponseBody::Other(Body::Bytes(buf))
|
||||
}
|
||||
}
|
||||
Body::Message(stream) => {
|
||||
if has_ce || head.status == StatusCode::SWITCHING_PROTOCOLS {
|
||||
ResponseBody::Body(Encoder {
|
||||
body: EncoderBody::Other(stream),
|
||||
encoder: None,
|
||||
})
|
||||
} else {
|
||||
update_head(encoding, head);
|
||||
head.no_chunking(false);
|
||||
ResponseBody::Body(Encoder {
|
||||
body: EncoderBody::Other(stream),
|
||||
encoder: ContentEncoder::encoder(encoding),
|
||||
})
|
||||
return ResponseBody::Other(Body::Bytes(buf));
|
||||
}
|
||||
}
|
||||
Body::Message(stream) => EncoderBody::BoxedStream(stream),
|
||||
},
|
||||
ResponseBody::Body(stream) => {
|
||||
if has_ce || head.status == StatusCode::SWITCHING_PROTOCOLS {
|
||||
ResponseBody::Body(Encoder {
|
||||
body: EncoderBody::Body(stream),
|
||||
encoder: None,
|
||||
})
|
||||
} else {
|
||||
update_head(encoding, head);
|
||||
head.no_chunking(false);
|
||||
ResponseBody::Body(Encoder {
|
||||
body: EncoderBody::Body(stream),
|
||||
encoder: ContentEncoder::encoder(encoding),
|
||||
})
|
||||
}
|
||||
}
|
||||
ResponseBody::Body(stream) => EncoderBody::Stream(stream),
|
||||
};
|
||||
|
||||
if can_encode {
|
||||
update_head(encoding, head);
|
||||
head.no_chunking(false);
|
||||
ResponseBody::Body(Encoder {
|
||||
body,
|
||||
eof: false,
|
||||
fut: None,
|
||||
encoder: ContentEncoder::encoder(encoding),
|
||||
})
|
||||
} else {
|
||||
ResponseBody::Body(Encoder {
|
||||
body,
|
||||
eof: false,
|
||||
fut: None,
|
||||
encoder: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum EncoderBody<B> {
|
||||
Body(B),
|
||||
Other(Box<dyn MessageBody>),
|
||||
Bytes(Bytes),
|
||||
Stream(B),
|
||||
BoxedStream(Box<dyn MessageBody>),
|
||||
}
|
||||
|
||||
impl<B: MessageBody> MessageBody for Encoder<B> {
|
||||
fn length(&self) -> BodySize {
|
||||
if self.encoder.is_none() {
|
||||
match self.body {
|
||||
EncoderBody::Body(ref b) => b.length(),
|
||||
EncoderBody::Other(ref b) => b.length(),
|
||||
EncoderBody::Bytes(ref b) => b.length(),
|
||||
EncoderBody::Stream(ref b) => b.length(),
|
||||
EncoderBody::BoxedStream(ref b) => b.length(),
|
||||
}
|
||||
} else {
|
||||
BodySize::Stream
|
||||
@@ -102,16 +93,47 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
|
||||
|
||||
fn poll_next(&mut self) -> Poll<Option<Bytes>, Error> {
|
||||
loop {
|
||||
if self.eof {
|
||||
return Ok(Async::Ready(None));
|
||||
}
|
||||
|
||||
if let Some(ref mut fut) = self.fut {
|
||||
let mut encoder = futures::try_ready!(fut.poll());
|
||||
let chunk = encoder.take();
|
||||
self.encoder = Some(encoder);
|
||||
self.fut.take();
|
||||
if !chunk.is_empty() {
|
||||
return Ok(Async::Ready(Some(chunk)));
|
||||
}
|
||||
}
|
||||
|
||||
let result = match self.body {
|
||||
EncoderBody::Body(ref mut b) => b.poll_next()?,
|
||||
EncoderBody::Other(ref mut b) => b.poll_next()?,
|
||||
EncoderBody::Bytes(ref mut b) => {
|
||||
if b.is_empty() {
|
||||
Async::Ready(None)
|
||||
} else {
|
||||
Async::Ready(Some(std::mem::replace(b, Bytes::new())))
|
||||
}
|
||||
}
|
||||
EncoderBody::Stream(ref mut b) => b.poll_next()?,
|
||||
EncoderBody::BoxedStream(ref mut b) => b.poll_next()?,
|
||||
};
|
||||
match result {
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
Async::Ready(Some(chunk)) => {
|
||||
if let Some(ref mut encoder) = self.encoder {
|
||||
if encoder.write(&chunk)? {
|
||||
return Ok(Async::Ready(Some(encoder.take())));
|
||||
if let Some(mut encoder) = self.encoder.take() {
|
||||
if chunk.len() < INPLACE {
|
||||
encoder.write(&chunk)?;
|
||||
let chunk = encoder.take();
|
||||
self.encoder = Some(encoder);
|
||||
if !chunk.is_empty() {
|
||||
return Ok(Async::Ready(Some(chunk)));
|
||||
}
|
||||
} else {
|
||||
self.fut = Some(run(move || {
|
||||
encoder.write(&chunk)?;
|
||||
Ok(encoder)
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
return Ok(Async::Ready(Some(chunk)));
|
||||
@@ -123,6 +145,7 @@ impl<B: MessageBody> MessageBody for Encoder<B> {
|
||||
if chunk.is_empty() {
|
||||
return Ok(Async::Ready(None));
|
||||
} else {
|
||||
self.eof = true;
|
||||
return Ok(Async::Ready(Some(chunk)));
|
||||
}
|
||||
} else {
|
||||
@@ -203,11 +226,11 @@ impl ContentEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
fn write(&mut self, data: &[u8]) -> Result<bool, io::Error> {
|
||||
fn write(&mut self, data: &[u8]) -> Result<(), io::Error> {
|
||||
match *self {
|
||||
#[cfg(feature = "brotli")]
|
||||
ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(!encoder.get_ref().buf.is_empty()),
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
trace!("Error decoding br encoding: {}", err);
|
||||
Err(err)
|
||||
@@ -215,7 +238,7 @@ impl ContentEncoder {
|
||||
},
|
||||
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
|
||||
ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(!encoder.get_ref().buf.is_empty()),
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
trace!("Error decoding gzip encoding: {}", err);
|
||||
Err(err)
|
||||
@@ -223,7 +246,7 @@ impl ContentEncoder {
|
||||
},
|
||||
#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))]
|
||||
ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(!encoder.get_ref().buf.is_empty()),
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
trace!("Error decoding deflate encoding: {}", err);
|
||||
Err(err)
|
||||
|
@@ -18,8 +18,6 @@ use tokio_timer::Error as TimerError;
|
||||
|
||||
// re-export for convinience
|
||||
pub use crate::cookie::ParseError as CookieParseError;
|
||||
|
||||
use crate::body::Body;
|
||||
use crate::response::Response;
|
||||
|
||||
/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html)
|
||||
@@ -49,13 +47,6 @@ impl Error {
|
||||
pub fn as_response_error(&self) -> &ResponseError {
|
||||
self.cause.as_ref()
|
||||
}
|
||||
|
||||
/// Converts error to a response instance and set error message as response body
|
||||
pub fn response_with_message(self) -> Response {
|
||||
let message = format!("{}", self);
|
||||
let resp: Response = self.into();
|
||||
resp.set_body(Body::from(message))
|
||||
}
|
||||
}
|
||||
|
||||
/// Error that can be converted to `Response`
|
||||
@@ -80,6 +71,26 @@ impl fmt::Debug for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<()> for Error {
|
||||
fn from(_: ()) -> Self {
|
||||
Error::from(UnitError)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {
|
||||
fn description(&self) -> &str {
|
||||
"actix-http::Error"
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&dyn std::error::Error> {
|
||||
None
|
||||
}
|
||||
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert `Error` to a `Response` instance
|
||||
impl From<Error> for Response {
|
||||
fn from(err: Error) -> Self {
|
||||
@@ -106,6 +117,13 @@ impl<E: ResponseError> ResponseError for TimeoutError<E> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Display)]
|
||||
#[display(fmt = "UnknownError")]
|
||||
struct UnitError;
|
||||
|
||||
/// `InternalServerError` for `JsonError`
|
||||
impl ResponseError for UnitError {}
|
||||
|
||||
/// `InternalServerError` for `JsonError`
|
||||
impl ResponseError for JsonError {}
|
||||
|
||||
@@ -115,6 +133,10 @@ impl ResponseError for FormError {}
|
||||
/// `InternalServerError` for `TimerError`
|
||||
impl ResponseError for TimerError {}
|
||||
|
||||
#[cfg(feature = "ssl")]
|
||||
/// `InternalServerError` for `SslError`
|
||||
impl ResponseError for openssl::ssl::Error {}
|
||||
|
||||
/// Return `BAD_REQUEST` for `de::value::Error`
|
||||
impl ResponseError for DeError {
|
||||
fn error_response(&self) -> Response {
|
||||
@@ -326,7 +348,7 @@ impl ResponseError for crate::cookie::ParseError {
|
||||
/// A set of errors that can occur during dispatching http requests
|
||||
pub enum DispatchError {
|
||||
/// Service error
|
||||
Service,
|
||||
Service(Error),
|
||||
|
||||
/// An `io::Error` that occurred while trying to read or write to a network
|
||||
/// stream.
|
||||
|
@@ -9,7 +9,7 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCOD
|
||||
use http::{Method, StatusCode, Version};
|
||||
|
||||
use super::decoder::{PayloadDecoder, PayloadItem, PayloadType};
|
||||
use super::{decoder, encoder, reserve_readbuf};
|
||||
use super::{decoder, encoder};
|
||||
use super::{Message, MessageType};
|
||||
use crate::body::BodySize;
|
||||
use crate::config::ServiceConfig;
|
||||
@@ -22,8 +22,8 @@ use crate::response::Response;
|
||||
bitflags! {
|
||||
struct Flags: u8 {
|
||||
const HEAD = 0b0000_0001;
|
||||
const KEEPALIVE_ENABLED = 0b0000_1000;
|
||||
const STREAM = 0b0001_0000;
|
||||
const KEEPALIVE_ENABLED = 0b0000_0010;
|
||||
const STREAM = 0b0000_0100;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ const AVERAGE_HEADER_SIZE: usize = 30;
|
||||
|
||||
/// HTTP/1 Codec
|
||||
pub struct Codec {
|
||||
config: ServiceConfig,
|
||||
pub(crate) config: ServiceConfig,
|
||||
decoder: decoder::MessageDecoder<Request>,
|
||||
payload: Option<PayloadDecoder>,
|
||||
version: Version,
|
||||
@@ -39,8 +39,8 @@ pub struct Codec {
|
||||
|
||||
// encoder part
|
||||
flags: Flags,
|
||||
headers_size: u32,
|
||||
encoder: encoder::MessageEncoder<Response<()>>,
|
||||
// headers_size: u32,
|
||||
}
|
||||
|
||||
impl Default for Codec {
|
||||
@@ -73,21 +73,30 @@ impl Codec {
|
||||
ctype: ConnectionType::Close,
|
||||
|
||||
flags,
|
||||
headers_size: 0,
|
||||
// headers_size: 0,
|
||||
encoder: encoder::MessageEncoder::default(),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Check if request is upgrade
|
||||
pub fn upgrade(&self) -> bool {
|
||||
self.ctype == ConnectionType::Upgrade
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Check if last response is keep-alive
|
||||
pub fn keepalive(&self) -> bool {
|
||||
self.ctype == ConnectionType::KeepAlive
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Check if keep-alive enabled on server level
|
||||
pub fn keepalive_enabled(&self) -> bool {
|
||||
self.flags.contains(Flags::KEEPALIVE_ENABLED)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Check last request's message type
|
||||
pub fn message_type(&self) -> MessageType {
|
||||
if self.flags.contains(Flags::STREAM) {
|
||||
@@ -107,10 +116,7 @@ impl Decoder for Codec {
|
||||
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||
if self.payload.is_some() {
|
||||
Ok(match self.payload.as_mut().unwrap().decode(src)? {
|
||||
Some(PayloadItem::Chunk(chunk)) => {
|
||||
reserve_readbuf(src);
|
||||
Some(Message::Chunk(Some(chunk)))
|
||||
}
|
||||
Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))),
|
||||
Some(PayloadItem::Eof) => {
|
||||
self.payload.take();
|
||||
Some(Message::Chunk(None))
|
||||
@@ -135,7 +141,6 @@ impl Decoder for Codec {
|
||||
self.flags.insert(Flags::STREAM);
|
||||
}
|
||||
}
|
||||
reserve_readbuf(src);
|
||||
Ok(Some(Message::Item(req)))
|
||||
} else {
|
||||
Ok(None)
|
||||
@@ -180,7 +185,7 @@ impl Encoder for Codec {
|
||||
self.ctype,
|
||||
&self.config,
|
||||
)?;
|
||||
self.headers_size = (dst.len() - len) as u32;
|
||||
// self.headers_size = (dst.len() - len) as u32;
|
||||
}
|
||||
Message::Chunk(Some(bytes)) => {
|
||||
self.encoder.encode_chunk(bytes.as_ref(), dst)?;
|
||||
|
@@ -5,11 +5,12 @@ use actix_codec::Decoder;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::{Async, Poll};
|
||||
use http::header::{HeaderName, HeaderValue};
|
||||
use http::{header, HeaderMap, HttpTryFrom, Method, StatusCode, Uri, Version};
|
||||
use http::{header, HttpTryFrom, Method, StatusCode, Uri, Version};
|
||||
use httparse;
|
||||
use log::{debug, error, trace};
|
||||
|
||||
use crate::error::ParseError;
|
||||
use crate::header::HeaderMap;
|
||||
use crate::message::{ConnectionType, ResponseHead};
|
||||
use crate::request::Request;
|
||||
|
||||
@@ -51,6 +52,8 @@ pub(crate) enum PayloadLength {
|
||||
pub(crate) trait MessageType: Sized {
|
||||
fn set_connection_type(&mut self, ctype: Option<ConnectionType>);
|
||||
|
||||
fn set_expect(&mut self);
|
||||
|
||||
fn headers_mut(&mut self) -> &mut HeaderMap;
|
||||
|
||||
fn decode(src: &mut BytesMut) -> Result<Option<(Self, PayloadType)>, ParseError>;
|
||||
@@ -62,6 +65,7 @@ pub(crate) trait MessageType: Sized {
|
||||
) -> Result<PayloadLength, ParseError> {
|
||||
let mut ka = None;
|
||||
let mut has_upgrade = false;
|
||||
let mut expect = false;
|
||||
let mut chunked = false;
|
||||
let mut content_length = None;
|
||||
|
||||
@@ -69,73 +73,81 @@ pub(crate) trait MessageType: Sized {
|
||||
let headers = self.headers_mut();
|
||||
|
||||
for idx in raw_headers.iter() {
|
||||
if let Ok(name) = HeaderName::from_bytes(&slice[idx.name.0..idx.name.1])
|
||||
{
|
||||
// Unsafe: httparse check header value for valid utf-8
|
||||
let value = unsafe {
|
||||
HeaderValue::from_shared_unchecked(
|
||||
slice.slice(idx.value.0, idx.value.1),
|
||||
)
|
||||
};
|
||||
match name {
|
||||
header::CONTENT_LENGTH => {
|
||||
if let Ok(s) = value.to_str() {
|
||||
if let Ok(len) = s.parse::<u64>() {
|
||||
let name =
|
||||
HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]).unwrap();
|
||||
|
||||
// Unsafe: httparse check header value for valid utf-8
|
||||
let value = unsafe {
|
||||
HeaderValue::from_shared_unchecked(
|
||||
slice.slice(idx.value.0, idx.value.1),
|
||||
)
|
||||
};
|
||||
match name {
|
||||
header::CONTENT_LENGTH => {
|
||||
if let Ok(s) = value.to_str() {
|
||||
if let Ok(len) = s.parse::<u64>() {
|
||||
if len != 0 {
|
||||
content_length = Some(len);
|
||||
} else {
|
||||
debug!("illegal Content-Length: {:?}", s);
|
||||
return Err(ParseError::Header);
|
||||
}
|
||||
} else {
|
||||
debug!("illegal Content-Length: {:?}", value);
|
||||
debug!("illegal Content-Length: {:?}", s);
|
||||
return Err(ParseError::Header);
|
||||
}
|
||||
} else {
|
||||
debug!("illegal Content-Length: {:?}", value);
|
||||
return Err(ParseError::Header);
|
||||
}
|
||||
// transfer-encoding
|
||||
header::TRANSFER_ENCODING => {
|
||||
if let Ok(s) = value.to_str().map(|s| s.trim()) {
|
||||
chunked = s.eq_ignore_ascii_case("chunked");
|
||||
} else {
|
||||
return Err(ParseError::Header);
|
||||
}
|
||||
}
|
||||
// transfer-encoding
|
||||
header::TRANSFER_ENCODING => {
|
||||
if let Ok(s) = value.to_str().map(|s| s.trim()) {
|
||||
chunked = s.eq_ignore_ascii_case("chunked");
|
||||
} else {
|
||||
return Err(ParseError::Header);
|
||||
}
|
||||
// connection keep-alive state
|
||||
header::CONNECTION => {
|
||||
ka = if let Ok(conn) = value.to_str().map(|conn| conn.trim())
|
||||
{
|
||||
if conn.eq_ignore_ascii_case("keep-alive") {
|
||||
Some(ConnectionType::KeepAlive)
|
||||
} else if conn.eq_ignore_ascii_case("close") {
|
||||
Some(ConnectionType::Close)
|
||||
} else if conn.eq_ignore_ascii_case("upgrade") {
|
||||
Some(ConnectionType::Upgrade)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
// connection keep-alive state
|
||||
header::CONNECTION => {
|
||||
ka = if let Ok(conn) = value.to_str().map(|conn| conn.trim()) {
|
||||
if conn.eq_ignore_ascii_case("keep-alive") {
|
||||
Some(ConnectionType::KeepAlive)
|
||||
} else if conn.eq_ignore_ascii_case("close") {
|
||||
Some(ConnectionType::Close)
|
||||
} else if conn.eq_ignore_ascii_case("upgrade") {
|
||||
Some(ConnectionType::Upgrade)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
header::UPGRADE => {
|
||||
has_upgrade = true;
|
||||
// check content-length, some clients (dart)
|
||||
// sends "content-length: 0" with websocket upgrade
|
||||
if let Ok(val) = value.to_str().map(|val| val.trim()) {
|
||||
if val.eq_ignore_ascii_case("websocket") {
|
||||
content_length = None;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
}
|
||||
header::UPGRADE => {
|
||||
has_upgrade = true;
|
||||
// check content-length, some clients (dart)
|
||||
// sends "content-length: 0" with websocket upgrade
|
||||
if let Ok(val) = value.to_str().map(|val| val.trim()) {
|
||||
if val.eq_ignore_ascii_case("websocket") {
|
||||
content_length = None;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
headers.append(name, value);
|
||||
} else {
|
||||
return Err(ParseError::Header);
|
||||
header::EXPECT => {
|
||||
let bytes = value.as_bytes();
|
||||
if bytes.len() >= 4 && &bytes[0..4] == b"100-" {
|
||||
expect = true;
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
headers.append(name, value);
|
||||
}
|
||||
}
|
||||
self.set_connection_type(ka);
|
||||
if expect {
|
||||
self.set_expect()
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc7230#section-3.3.3
|
||||
if chunked {
|
||||
@@ -163,6 +175,10 @@ impl MessageType for Request {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_expect(&mut self) {
|
||||
self.head_mut().set_expect();
|
||||
}
|
||||
|
||||
fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||
&mut self.head_mut().headers
|
||||
}
|
||||
@@ -198,10 +214,10 @@ impl MessageType for Request {
|
||||
let mut msg = Request::new();
|
||||
|
||||
// convert headers
|
||||
let len = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?;
|
||||
let length = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?;
|
||||
|
||||
// payload decoder
|
||||
let decoder = match len {
|
||||
let decoder = match length {
|
||||
PayloadLength::Payload(pl) => pl,
|
||||
PayloadLength::Upgrade => {
|
||||
// upgrade(websocket)
|
||||
@@ -235,6 +251,8 @@ impl MessageType for ResponseHead {
|
||||
}
|
||||
}
|
||||
|
||||
fn set_expect(&mut self) {}
|
||||
|
||||
fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||
&mut self.headers
|
||||
}
|
||||
@@ -266,13 +284,14 @@ impl MessageType for ResponseHead {
|
||||
}
|
||||
};
|
||||
|
||||
let mut msg = ResponseHead::default();
|
||||
let mut msg = ResponseHead::new(status);
|
||||
msg.version = ver;
|
||||
|
||||
// convert headers
|
||||
let len = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?;
|
||||
let length = msg.set_headers(&src.split_to(len).freeze(), &headers[..h_len])?;
|
||||
|
||||
// message payload
|
||||
let decoder = if let PayloadLength::Payload(pl) = len {
|
||||
let decoder = if let PayloadLength::Payload(pl) = length {
|
||||
pl
|
||||
} else if status == StatusCode::SWITCHING_PROTOCOLS {
|
||||
// switching protocol or connect
|
||||
@@ -284,9 +303,6 @@ impl MessageType for ResponseHead {
|
||||
PayloadType::None
|
||||
};
|
||||
|
||||
msg.status = status;
|
||||
msg.version = ver;
|
||||
|
||||
Ok(Some((msg, decoder)))
|
||||
}
|
||||
}
|
||||
@@ -610,6 +626,7 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::error::ParseError;
|
||||
use crate::http::header::{HeaderName, SET_COOKIE};
|
||||
use crate::httpmessage::HttpMessage;
|
||||
|
||||
impl PayloadType {
|
||||
@@ -770,7 +787,13 @@ mod tests {
|
||||
assert_eq!(req.version(), Version::HTTP_11);
|
||||
assert_eq!(*req.method(), Method::GET);
|
||||
assert_eq!(req.path(), "/test");
|
||||
assert_eq!(req.headers().get("test").unwrap().as_bytes(), b"value");
|
||||
assert_eq!(
|
||||
req.headers()
|
||||
.get(HeaderName::try_from("test").unwrap())
|
||||
.unwrap()
|
||||
.as_bytes(),
|
||||
b"value"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -785,12 +808,11 @@ mod tests {
|
||||
|
||||
let val: Vec<_> = req
|
||||
.headers()
|
||||
.get_all("Set-Cookie")
|
||||
.iter()
|
||||
.get_all(SET_COOKIE)
|
||||
.map(|v| v.to_str().unwrap().to_owned())
|
||||
.collect();
|
||||
assert_eq!(val[0], "c1=cookie1");
|
||||
assert_eq!(val[1], "c2=cookie2");
|
||||
assert_eq!(val[1], "c1=cookie1");
|
||||
assert_eq!(val[0], "c2=cookie2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@@ -1,65 +1,79 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Debug;
|
||||
use std::mem;
|
||||
use std::time::Instant;
|
||||
use std::{fmt, io};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder};
|
||||
use actix_service::Service;
|
||||
use actix_utils::cloneable::CloneableService;
|
||||
use bitflags::bitflags;
|
||||
use futures::{Async, Future, Poll, Sink, Stream};
|
||||
use log::{debug, error, trace};
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use futures::{Async, Future, Poll};
|
||||
use log::{error, trace};
|
||||
use tokio_timer::Delay;
|
||||
|
||||
use crate::body::{Body, BodySize, MessageBody, ResponseBody};
|
||||
use crate::config::ServiceConfig;
|
||||
use crate::error::DispatchError;
|
||||
use crate::error::{DispatchError, Error};
|
||||
use crate::error::{ParseError, PayloadError};
|
||||
use crate::request::Request;
|
||||
use crate::response::Response;
|
||||
|
||||
use super::codec::Codec;
|
||||
use super::payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter};
|
||||
use super::payload::{Payload, PayloadSender, PayloadStatus};
|
||||
use super::{Message, MessageType};
|
||||
|
||||
const LW_BUFFER_SIZE: usize = 4096;
|
||||
const HW_BUFFER_SIZE: usize = 32_768;
|
||||
const MAX_PIPELINED_MESSAGES: usize = 16;
|
||||
|
||||
bitflags! {
|
||||
pub struct Flags: u8 {
|
||||
const STARTED = 0b0000_0001;
|
||||
const KEEPALIVE_ENABLED = 0b0000_0010;
|
||||
const KEEPALIVE = 0b0000_0100;
|
||||
const POLLED = 0b0000_1000;
|
||||
const SHUTDOWN = 0b0010_0000;
|
||||
const DISCONNECTED = 0b0100_0000;
|
||||
const DROPPING = 0b1000_0000;
|
||||
const KEEPALIVE = 0b0000_0010;
|
||||
const POLLED = 0b0000_0100;
|
||||
const SHUTDOWN = 0b0000_1000;
|
||||
const READ_DISCONNECT = 0b0001_0000;
|
||||
const WRITE_DISCONNECT = 0b0010_0000;
|
||||
const DROPPING = 0b0100_0000;
|
||||
}
|
||||
}
|
||||
|
||||
/// Dispatcher for HTTP/1.1 protocol
|
||||
pub struct Dispatcher<T, S: Service<Request = Request> + 'static, B: MessageBody>
|
||||
pub struct Dispatcher<T, S, B, X>
|
||||
where
|
||||
S::Error: Debug,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
B: MessageBody,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
{
|
||||
inner: Option<InnerDispatcher<T, S, B>>,
|
||||
inner: Option<InnerDispatcher<T, S, B, X>>,
|
||||
}
|
||||
|
||||
struct InnerDispatcher<T, S: Service<Request = Request> + 'static, B: MessageBody>
|
||||
struct InnerDispatcher<T, S, B, X>
|
||||
where
|
||||
S::Error: Debug,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
B: MessageBody,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
{
|
||||
service: CloneableService<S>,
|
||||
expect: CloneableService<X>,
|
||||
flags: Flags,
|
||||
framed: Framed<T, Codec>,
|
||||
error: Option<DispatchError>,
|
||||
config: ServiceConfig,
|
||||
|
||||
state: State<S, B>,
|
||||
state: State<S, B, X>,
|
||||
payload: Option<PayloadSender>,
|
||||
messages: VecDeque<DispatcherMessage>,
|
||||
|
||||
ka_expire: Instant,
|
||||
ka_timer: Option<Delay>,
|
||||
|
||||
io: T,
|
||||
read_buf: BytesMut,
|
||||
write_buf: BytesMut,
|
||||
codec: Codec,
|
||||
}
|
||||
|
||||
enum DispatcherMessage {
|
||||
@@ -67,13 +81,24 @@ enum DispatcherMessage {
|
||||
Error(Response<()>),
|
||||
}
|
||||
|
||||
enum State<S: Service<Request = Request>, B: MessageBody> {
|
||||
enum State<S, B, X>
|
||||
where
|
||||
S: Service<Request = Request>,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
B: MessageBody,
|
||||
{
|
||||
None,
|
||||
ExpectCall(X::Future),
|
||||
ServiceCall(S::Future),
|
||||
SendPayload(ResponseBody<B>),
|
||||
}
|
||||
|
||||
impl<S: Service<Request = Request>, B: MessageBody> State<S, B> {
|
||||
impl<S, B, X> State<S, B, X>
|
||||
where
|
||||
S: Service<Request = Request>,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
B: MessageBody,
|
||||
{
|
||||
fn is_empty(&self) -> bool {
|
||||
if let State::None = self {
|
||||
true
|
||||
@@ -81,36 +106,73 @@ impl<S: Service<Request = Request>, B: MessageBody> State<S, B> {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn is_call(&self) -> bool {
|
||||
if let State::ServiceCall(_) = self {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, B> Dispatcher<T, S, B>
|
||||
impl<S, B, X> fmt::Debug for State<S, B, X>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: Service<Request = Request> + 'static,
|
||||
S::Error: Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
S: Service<Request = Request>,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
B: MessageBody,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
State::None => write!(f, "State::None"),
|
||||
State::ExpectCall(_) => write!(f, "State::ExceptCall"),
|
||||
State::ServiceCall(_) => write!(f, "State::ServiceCall"),
|
||||
State::SendPayload(_) => write!(f, "State::SendPayload"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, B, X> Dispatcher<T, S, B, X>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Response: Into<Response<B>>,
|
||||
B: MessageBody,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
{
|
||||
/// Create http/1 dispatcher.
|
||||
pub fn new(stream: T, config: ServiceConfig, service: CloneableService<S>) -> Self {
|
||||
pub fn new(
|
||||
stream: T,
|
||||
config: ServiceConfig,
|
||||
service: CloneableService<S>,
|
||||
expect: CloneableService<X>,
|
||||
) -> Self {
|
||||
Dispatcher::with_timeout(
|
||||
Framed::new(stream, Codec::new(config.clone())),
|
||||
stream,
|
||||
Codec::new(config.clone()),
|
||||
config,
|
||||
BytesMut::with_capacity(HW_BUFFER_SIZE),
|
||||
None,
|
||||
service,
|
||||
expect,
|
||||
)
|
||||
}
|
||||
|
||||
/// Create http/1 dispatcher with slow request timeout.
|
||||
pub fn with_timeout(
|
||||
framed: Framed<T, Codec>,
|
||||
io: T,
|
||||
codec: Codec,
|
||||
config: ServiceConfig,
|
||||
read_buf: BytesMut,
|
||||
timeout: Option<Delay>,
|
||||
service: CloneableService<S>,
|
||||
expect: CloneableService<X>,
|
||||
) -> Self {
|
||||
let keepalive = config.keep_alive_enabled();
|
||||
let flags = if keepalive {
|
||||
Flags::KEEPALIVE | Flags::KEEPALIVE_ENABLED
|
||||
Flags::KEEPALIVE
|
||||
} else {
|
||||
Flags::empty()
|
||||
};
|
||||
@@ -126,14 +188,17 @@ where
|
||||
|
||||
Dispatcher {
|
||||
inner: Some(InnerDispatcher {
|
||||
framed,
|
||||
io,
|
||||
codec,
|
||||
read_buf,
|
||||
write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
|
||||
payload: None,
|
||||
state: State::None,
|
||||
error: None,
|
||||
messages: VecDeque::new(),
|
||||
service,
|
||||
expect,
|
||||
flags,
|
||||
config,
|
||||
ka_expire,
|
||||
ka_timer,
|
||||
}),
|
||||
@@ -141,20 +206,20 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, B> InnerDispatcher<T, S, B>
|
||||
impl<T, S, B, X> InnerDispatcher<T, S, B, X>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: Service<Request = Request> + 'static,
|
||||
S::Error: Debug,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Response: Into<Response<B>>,
|
||||
B: MessageBody,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
{
|
||||
fn can_read(&self) -> bool {
|
||||
if self.flags.contains(Flags::DISCONNECTED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(ref info) = self.payload {
|
||||
if self.flags.contains(Flags::READ_DISCONNECT) {
|
||||
false
|
||||
} else if let Some(ref info) = self.payload {
|
||||
info.need_read() == PayloadStatus::Read
|
||||
} else {
|
||||
true
|
||||
@@ -163,41 +228,61 @@ where
|
||||
|
||||
// if checked is set to true, delay disconnect until all tasks have finished.
|
||||
fn client_disconnected(&mut self) {
|
||||
self.flags.insert(Flags::DISCONNECTED);
|
||||
self.flags
|
||||
.insert(Flags::READ_DISCONNECT | Flags::WRITE_DISCONNECT);
|
||||
if let Some(mut payload) = self.payload.take() {
|
||||
payload.set_error(PayloadError::Incomplete(None));
|
||||
}
|
||||
}
|
||||
|
||||
/// Flush stream
|
||||
fn poll_flush(&mut self) -> Poll<bool, DispatchError> {
|
||||
if !self.framed.is_write_buf_empty() {
|
||||
match self.framed.poll_complete() {
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Err(err) => {
|
||||
debug!("Error sending data: {}", err);
|
||||
Err(err.into())
|
||||
}
|
||||
Ok(Async::Ready(_)) => {
|
||||
// if payload is not consumed we can not use connection
|
||||
if self.payload.is_some() && self.state.is_empty() {
|
||||
return Err(DispatchError::PayloadIsNotConsumed);
|
||||
}
|
||||
Ok(Async::Ready(true))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(Async::Ready(false))
|
||||
///
|
||||
/// true - got whouldblock
|
||||
/// false - didnt get whouldblock
|
||||
fn poll_flush(&mut self) -> Result<bool, DispatchError> {
|
||||
if self.write_buf.is_empty() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let len = self.write_buf.len();
|
||||
let mut written = 0;
|
||||
while written < len {
|
||||
match self.io.write(&self.write_buf[written..]) {
|
||||
Ok(0) => {
|
||||
return Err(DispatchError::Io(io::Error::new(
|
||||
io::ErrorKind::WriteZero,
|
||||
"",
|
||||
)));
|
||||
}
|
||||
Ok(n) => {
|
||||
written += n;
|
||||
}
|
||||
Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
|
||||
if written > 0 {
|
||||
let _ = self.write_buf.split_to(written);
|
||||
}
|
||||
return Ok(true);
|
||||
}
|
||||
Err(err) => return Err(DispatchError::Io(err)),
|
||||
}
|
||||
}
|
||||
if written > 0 {
|
||||
if written == self.write_buf.len() {
|
||||
unsafe { self.write_buf.set_len(0) }
|
||||
} else {
|
||||
let _ = self.write_buf.split_to(written);
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn send_response(
|
||||
&mut self,
|
||||
message: Response<()>,
|
||||
body: ResponseBody<B>,
|
||||
) -> Result<State<S, B>, DispatchError> {
|
||||
self.framed
|
||||
.force_send(Message::Item((message, body.length())))
|
||||
) -> Result<State<S, B, X>, DispatchError> {
|
||||
self.codec
|
||||
.encode(Message::Item((message, body.length())), &mut self.write_buf)
|
||||
.map_err(|err| {
|
||||
if let Some(mut payload) = self.payload.take() {
|
||||
payload.set_error(PayloadError::Incomplete(None));
|
||||
@@ -205,91 +290,133 @@ where
|
||||
DispatchError::Io(err)
|
||||
})?;
|
||||
|
||||
self.flags
|
||||
.set(Flags::KEEPALIVE, self.framed.get_codec().keepalive());
|
||||
self.flags.set(Flags::KEEPALIVE, self.codec.keepalive());
|
||||
match body.length() {
|
||||
BodySize::None | BodySize::Empty => Ok(State::None),
|
||||
_ => Ok(State::SendPayload(body)),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_response(&mut self) -> Result<(), DispatchError> {
|
||||
let mut retry = self.can_read();
|
||||
fn send_continue(&mut self) {
|
||||
self.write_buf
|
||||
.extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n");
|
||||
}
|
||||
|
||||
fn poll_response(&mut self) -> Result<bool, DispatchError> {
|
||||
loop {
|
||||
let state = match mem::replace(&mut self.state, State::None) {
|
||||
let state = match self.state {
|
||||
State::None => match self.messages.pop_front() {
|
||||
Some(DispatcherMessage::Item(req)) => {
|
||||
Some(self.handle_request(req)?)
|
||||
}
|
||||
Some(DispatcherMessage::Error(res)) => {
|
||||
self.send_response(res, ResponseBody::Other(Body::Empty))?;
|
||||
None
|
||||
Some(self.send_response(res, ResponseBody::Other(Body::Empty))?)
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
State::ServiceCall(mut fut) => match fut.poll() {
|
||||
Ok(Async::Ready(res)) => {
|
||||
let (res, body) = res.into().replace_body(());
|
||||
Some(self.send_response(res, body)?)
|
||||
State::ExpectCall(ref mut fut) => match fut.poll() {
|
||||
Ok(Async::Ready(req)) => {
|
||||
self.send_continue();
|
||||
self.state = State::ServiceCall(self.service.call(req));
|
||||
continue;
|
||||
}
|
||||
Ok(Async::NotReady) => {
|
||||
self.state = State::ServiceCall(fut);
|
||||
None
|
||||
}
|
||||
Err(_e) => {
|
||||
let res: Response = Response::InternalServerError().finish();
|
||||
Ok(Async::NotReady) => None,
|
||||
Err(e) => {
|
||||
let res: Response = e.into().into();
|
||||
let (res, body) = res.replace_body(());
|
||||
Some(self.send_response(res, body.into_body())?)
|
||||
}
|
||||
},
|
||||
State::SendPayload(mut stream) => {
|
||||
State::ServiceCall(ref mut fut) => match fut.poll() {
|
||||
Ok(Async::Ready(res)) => {
|
||||
let (res, body) = res.into().replace_body(());
|
||||
self.state = self.send_response(res, body)?;
|
||||
continue;
|
||||
}
|
||||
Ok(Async::NotReady) => None,
|
||||
Err(e) => {
|
||||
let res: Response = e.into().into();
|
||||
let (res, body) = res.replace_body(());
|
||||
Some(self.send_response(res, body.into_body())?)
|
||||
}
|
||||
},
|
||||
State::SendPayload(ref mut stream) => {
|
||||
loop {
|
||||
if !self.framed.is_write_buf_full() {
|
||||
if self.write_buf.len() < HW_BUFFER_SIZE {
|
||||
match stream
|
||||
.poll_next()
|
||||
.map_err(|_| DispatchError::Unknown)?
|
||||
{
|
||||
Async::Ready(Some(item)) => {
|
||||
self.framed
|
||||
.force_send(Message::Chunk(Some(item)))?;
|
||||
self.codec.encode(
|
||||
Message::Chunk(Some(item)),
|
||||
&mut self.write_buf,
|
||||
)?;
|
||||
continue;
|
||||
}
|
||||
Async::Ready(None) => {
|
||||
self.framed.force_send(Message::Chunk(None))?;
|
||||
}
|
||||
Async::NotReady => {
|
||||
self.state = State::SendPayload(stream);
|
||||
return Ok(());
|
||||
self.codec.encode(
|
||||
Message::Chunk(None),
|
||||
&mut self.write_buf,
|
||||
)?;
|
||||
self.state = State::None;
|
||||
}
|
||||
Async::NotReady => return Ok(false),
|
||||
}
|
||||
} else {
|
||||
self.state = State::SendPayload(stream);
|
||||
return Ok(());
|
||||
return Ok(true);
|
||||
}
|
||||
break;
|
||||
}
|
||||
None
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
match state {
|
||||
Some(state) => self.state = state,
|
||||
None => {
|
||||
// if read-backpressure is enabled and we consumed some data.
|
||||
// we may read more data and retry
|
||||
if !retry && self.can_read() && self.poll_request()? {
|
||||
retry = self.can_read();
|
||||
// set new state
|
||||
if let Some(state) = state {
|
||||
self.state = state;
|
||||
if !self.state.is_empty() {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// if read-backpressure is enabled and we consumed some data.
|
||||
// we may read more data and retry
|
||||
if self.state.is_call() {
|
||||
if self.poll_request()? {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
} else if !self.messages.is_empty() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn handle_request(&mut self, req: Request) -> Result<State<S, B>, DispatchError> {
|
||||
fn handle_request(&mut self, req: Request) -> Result<State<S, B, X>, DispatchError> {
|
||||
// Handle `EXPECT: 100-Continue` header
|
||||
let req = if req.head().expect() {
|
||||
let mut task = self.expect.call(req);
|
||||
match task.poll() {
|
||||
Ok(Async::Ready(req)) => {
|
||||
self.send_continue();
|
||||
req
|
||||
}
|
||||
Ok(Async::NotReady) => return Ok(State::ExpectCall(task)),
|
||||
Err(e) => {
|
||||
let e = e.into();
|
||||
let res: Response = e.into();
|
||||
let (res, body) = res.replace_body(());
|
||||
return self.send_response(res, body.into_body());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
req
|
||||
};
|
||||
|
||||
// Call service
|
||||
let mut task = self.service.call(req);
|
||||
match task.poll() {
|
||||
Ok(Async::Ready(res)) => {
|
||||
@@ -297,8 +424,8 @@ where
|
||||
self.send_response(res, body)
|
||||
}
|
||||
Ok(Async::NotReady) => Ok(State::ServiceCall(task)),
|
||||
Err(_e) => {
|
||||
let res: Response = Response::InternalServerError().finish();
|
||||
Err(e) => {
|
||||
let res: Response = e.into().into();
|
||||
let (res, body) = res.replace_body(());
|
||||
self.send_response(res, body.into_body())
|
||||
}
|
||||
@@ -308,20 +435,20 @@ where
|
||||
/// Process one incoming requests
|
||||
pub(self) fn poll_request(&mut self) -> Result<bool, DispatchError> {
|
||||
// limit a mount of non processed requests
|
||||
if self.messages.len() >= MAX_PIPELINED_MESSAGES {
|
||||
if self.messages.len() >= MAX_PIPELINED_MESSAGES || !self.can_read() {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let mut updated = false;
|
||||
loop {
|
||||
match self.framed.poll() {
|
||||
Ok(Async::Ready(Some(msg))) => {
|
||||
match self.codec.decode(&mut self.read_buf) {
|
||||
Ok(Some(msg)) => {
|
||||
updated = true;
|
||||
self.flags.insert(Flags::STARTED);
|
||||
|
||||
match msg {
|
||||
Message::Item(mut req) => {
|
||||
match self.framed.get_codec().message_type() {
|
||||
match self.codec.message_type() {
|
||||
MessageType::Payload | MessageType::Stream => {
|
||||
let (ps, pl) = Payload::create(false);
|
||||
let (req1, _) =
|
||||
@@ -329,10 +456,6 @@ where
|
||||
req = req1;
|
||||
self.payload = Some(ps);
|
||||
}
|
||||
//MessageType::Stream => {
|
||||
// self.unhandled = Some(req);
|
||||
// return Ok(updated);
|
||||
//}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
@@ -350,7 +473,7 @@ where
|
||||
error!(
|
||||
"Internal server error: unexpected payload chunk"
|
||||
);
|
||||
self.flags.insert(Flags::DISCONNECTED);
|
||||
self.flags.insert(Flags::READ_DISCONNECT);
|
||||
self.messages.push_back(DispatcherMessage::Error(
|
||||
Response::InternalServerError().finish().drop_body(),
|
||||
));
|
||||
@@ -363,7 +486,7 @@ where
|
||||
payload.feed_eof();
|
||||
} else {
|
||||
error!("Internal server error: unexpected eof");
|
||||
self.flags.insert(Flags::DISCONNECTED);
|
||||
self.flags.insert(Flags::READ_DISCONNECT);
|
||||
self.messages.push_back(DispatcherMessage::Error(
|
||||
Response::InternalServerError().finish().drop_body(),
|
||||
));
|
||||
@@ -373,11 +496,7 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Async::Ready(None)) => {
|
||||
self.client_disconnected();
|
||||
break;
|
||||
}
|
||||
Ok(Async::NotReady) => break,
|
||||
Ok(None) => break,
|
||||
Err(ParseError::Io(e)) => {
|
||||
self.client_disconnected();
|
||||
self.error = Some(DispatchError::Io(e));
|
||||
@@ -392,15 +511,15 @@ where
|
||||
self.messages.push_back(DispatcherMessage::Error(
|
||||
Response::BadRequest().finish().drop_body(),
|
||||
));
|
||||
self.flags.insert(Flags::DISCONNECTED);
|
||||
self.flags.insert(Flags::READ_DISCONNECT);
|
||||
self.error = Some(e.into());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.ka_timer.is_some() && updated {
|
||||
if let Some(expire) = self.config.keep_alive_expire() {
|
||||
if updated && self.ka_timer.is_some() {
|
||||
if let Some(expire) = self.codec.config.keep_alive_expire() {
|
||||
self.ka_expire = expire;
|
||||
}
|
||||
}
|
||||
@@ -412,10 +531,10 @@ where
|
||||
if self.ka_timer.is_none() {
|
||||
// shutdown timeout
|
||||
if self.flags.contains(Flags::SHUTDOWN) {
|
||||
if let Some(interval) = self.config.client_disconnect_timer() {
|
||||
if let Some(interval) = self.codec.config.client_disconnect_timer() {
|
||||
self.ka_timer = Some(Delay::new(interval));
|
||||
} else {
|
||||
self.flags.insert(Flags::DISCONNECTED);
|
||||
self.flags.insert(Flags::READ_DISCONNECT);
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
@@ -433,13 +552,14 @@ where
|
||||
return Err(DispatchError::DisconnectTimeout);
|
||||
} else if self.ka_timer.as_mut().unwrap().deadline() >= self.ka_expire {
|
||||
// check for any outstanding tasks
|
||||
if self.state.is_empty() && self.framed.is_write_buf_empty() {
|
||||
if self.state.is_empty() && self.write_buf.is_empty() {
|
||||
if self.flags.contains(Flags::STARTED) {
|
||||
trace!("Keep-alive timeout, close connection");
|
||||
self.flags.insert(Flags::SHUTDOWN);
|
||||
|
||||
// start shutdown timer
|
||||
if let Some(deadline) = self.config.client_disconnect_timer()
|
||||
if let Some(deadline) =
|
||||
self.codec.config.client_disconnect_timer()
|
||||
{
|
||||
if let Some(timer) = self.ka_timer.as_mut() {
|
||||
timer.reset(deadline);
|
||||
@@ -447,7 +567,7 @@ where
|
||||
}
|
||||
} else {
|
||||
// no shutdown timeout, drop socket
|
||||
self.flags.insert(Flags::DISCONNECTED);
|
||||
self.flags.insert(Flags::WRITE_DISCONNECT);
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
@@ -464,7 +584,8 @@ where
|
||||
self.flags.insert(Flags::STARTED | Flags::SHUTDOWN);
|
||||
self.state = State::None;
|
||||
}
|
||||
} else if let Some(deadline) = self.config.keep_alive_expire() {
|
||||
} else if let Some(deadline) = self.codec.config.keep_alive_expire()
|
||||
{
|
||||
if let Some(timer) = self.ka_timer.as_mut() {
|
||||
timer.reset(deadline);
|
||||
let _ = timer.poll();
|
||||
@@ -482,13 +603,15 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S, B> Future for Dispatcher<T, S, B>
|
||||
impl<T, S, B, X> Future for Dispatcher<T, S, B, X>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Debug,
|
||||
S::Error: Into<Error>,
|
||||
S::Response: Into<Response<B>>,
|
||||
B: MessageBody,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = DispatchError;
|
||||
@@ -496,34 +619,60 @@ where
|
||||
#[inline]
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
let inner = self.inner.as_mut().unwrap();
|
||||
inner.poll_keepalive()?;
|
||||
|
||||
if inner.flags.contains(Flags::SHUTDOWN) {
|
||||
inner.poll_keepalive()?;
|
||||
if inner.flags.contains(Flags::DISCONNECTED) {
|
||||
if inner.flags.contains(Flags::WRITE_DISCONNECT) {
|
||||
Ok(Async::Ready(()))
|
||||
} else {
|
||||
// try_ready!(inner.poll_flush());
|
||||
match inner.framed.get_mut().shutdown()? {
|
||||
Async::Ready(_) => Ok(Async::Ready(())),
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
// flush buffer
|
||||
inner.poll_flush()?;
|
||||
if !inner.write_buf.is_empty() {
|
||||
Ok(Async::NotReady)
|
||||
} else {
|
||||
match inner.io.shutdown()? {
|
||||
Async::Ready(_) => Ok(Async::Ready(())),
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
inner.poll_keepalive()?;
|
||||
// read socket into a buf
|
||||
if !inner.flags.contains(Flags::READ_DISCONNECT) {
|
||||
if let Some(true) = read_available(&mut inner.io, &mut inner.read_buf)? {
|
||||
inner.flags.insert(Flags::READ_DISCONNECT)
|
||||
}
|
||||
}
|
||||
|
||||
inner.poll_request()?;
|
||||
loop {
|
||||
inner.poll_response()?;
|
||||
if let Async::Ready(false) = inner.poll_flush()? {
|
||||
if inner.write_buf.remaining_mut() < LW_BUFFER_SIZE {
|
||||
inner.write_buf.reserve(HW_BUFFER_SIZE);
|
||||
}
|
||||
let need_write = inner.poll_response()?;
|
||||
|
||||
// we didnt get WouldBlock from write operation,
|
||||
// so data get written to kernel completely (OSX)
|
||||
// and we have to write again otherwise response can get stuck
|
||||
if inner.poll_flush()? || !need_write {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if inner.flags.contains(Flags::DISCONNECTED) {
|
||||
// client is gone
|
||||
if inner.flags.contains(Flags::WRITE_DISCONNECT) {
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
|
||||
let is_empty = inner.state.is_empty();
|
||||
|
||||
// read half is closed and we do not processing any responses
|
||||
if inner.flags.contains(Flags::READ_DISCONNECT) && is_empty {
|
||||
inner.flags.insert(Flags::SHUTDOWN);
|
||||
}
|
||||
|
||||
// keep-alive and stream errors
|
||||
if inner.state.is_empty() && inner.framed.is_write_buf_empty() {
|
||||
if is_empty && inner.write_buf.is_empty() {
|
||||
if let Some(err) = inner.error.take() {
|
||||
Err(err)
|
||||
}
|
||||
@@ -547,20 +696,61 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
fn read_available<T>(io: &mut T, buf: &mut BytesMut) -> Result<Option<bool>, io::Error>
|
||||
where
|
||||
T: io::Read,
|
||||
{
|
||||
let mut read_some = false;
|
||||
loop {
|
||||
if buf.remaining_mut() < LW_BUFFER_SIZE {
|
||||
buf.reserve(HW_BUFFER_SIZE);
|
||||
}
|
||||
|
||||
let read = unsafe { io.read(buf.bytes_mut()) };
|
||||
match read {
|
||||
Ok(n) => {
|
||||
if n == 0 {
|
||||
return Ok(Some(true));
|
||||
} else {
|
||||
read_some = true;
|
||||
unsafe {
|
||||
buf.advance_mut(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
return if e.kind() == io::ErrorKind::WouldBlock {
|
||||
if read_some {
|
||||
Ok(Some(false))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
} else if e.kind() == io::ErrorKind::ConnectionReset && read_some {
|
||||
Ok(Some(true))
|
||||
} else {
|
||||
Err(e)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{cmp, io};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite};
|
||||
use actix_service::IntoService;
|
||||
use bytes::{Buf, Bytes};
|
||||
use bytes::{Buf, Bytes, BytesMut};
|
||||
use futures::future::{lazy, ok};
|
||||
|
||||
use super::*;
|
||||
use crate::error::Error;
|
||||
use crate::h1::ExpectHandler;
|
||||
|
||||
struct Buffer {
|
||||
buf: Bytes,
|
||||
write_buf: BytesMut,
|
||||
err: Option<io::Error>,
|
||||
}
|
||||
|
||||
@@ -568,6 +758,7 @@ mod tests {
|
||||
fn new(data: &'static str) -> Buffer {
|
||||
Buffer {
|
||||
buf: Bytes::from(data),
|
||||
write_buf: BytesMut::new(),
|
||||
err: None,
|
||||
}
|
||||
}
|
||||
@@ -593,6 +784,7 @@ mod tests {
|
||||
|
||||
impl io::Write for Buffer {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.write_buf.extend(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
@@ -620,16 +812,19 @@ mod tests {
|
||||
CloneableService::new(
|
||||
(|_| ok::<_, Error>(Response::Ok().finish())).into_service(),
|
||||
),
|
||||
CloneableService::new(ExpectHandler),
|
||||
);
|
||||
assert!(h1.poll().is_ok());
|
||||
assert!(h1.poll().is_ok());
|
||||
assert!(h1.poll().is_err());
|
||||
assert!(h1
|
||||
.inner
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.flags
|
||||
.contains(Flags::DISCONNECTED));
|
||||
// assert_eq!(h1.tasks.len(), 1);
|
||||
.contains(Flags::READ_DISCONNECT));
|
||||
assert_eq!(
|
||||
&h1.inner.as_ref().unwrap().io.write_buf[..26],
|
||||
b"HTTP/1.1 400 Bad Request\r\n"
|
||||
);
|
||||
ok::<_, ()>(())
|
||||
}));
|
||||
}
|
||||
|
@@ -6,15 +6,15 @@ use std::str::FromStr;
|
||||
use std::{cmp, fmt, io, mem};
|
||||
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use http::header::{
|
||||
HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
|
||||
};
|
||||
use http::{HeaderMap, Method, StatusCode, Version};
|
||||
|
||||
use crate::body::BodySize;
|
||||
use crate::config::ServiceConfig;
|
||||
use crate::header::ContentEncoding;
|
||||
use crate::header::{map, ContentEncoding};
|
||||
use crate::helpers;
|
||||
use crate::http::header::{
|
||||
HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
|
||||
};
|
||||
use crate::http::{HeaderMap, Method, StatusCode, Version};
|
||||
use crate::message::{ConnectionType, Head, RequestHead, ResponseHead};
|
||||
use crate::request::Request;
|
||||
use crate::response::Response;
|
||||
@@ -41,8 +41,6 @@ impl<T: MessageType> Default for MessageEncoder<T> {
|
||||
pub(crate) trait MessageType: Sized {
|
||||
fn status(&self) -> Option<StatusCode>;
|
||||
|
||||
// fn connection_type(&self) -> Option<ConnectionType>;
|
||||
|
||||
fn headers(&self) -> &HeaderMap;
|
||||
|
||||
fn chunked(&self) -> bool;
|
||||
@@ -76,32 +74,31 @@ pub(crate) trait MessageType: Sized {
|
||||
match length {
|
||||
BodySize::Stream => {
|
||||
if chunked {
|
||||
dst.extend_from_slice(b"\r\ntransfer-encoding: chunked\r\n")
|
||||
dst.put_slice(b"\r\ntransfer-encoding: chunked\r\n")
|
||||
} else {
|
||||
skip_len = false;
|
||||
dst.extend_from_slice(b"\r\n");
|
||||
dst.put_slice(b"\r\n");
|
||||
}
|
||||
}
|
||||
BodySize::Empty => {
|
||||
dst.extend_from_slice(b"\r\ncontent-length: 0\r\n");
|
||||
dst.put_slice(b"\r\ncontent-length: 0\r\n");
|
||||
}
|
||||
BodySize::Sized(len) => helpers::write_content_length(len, dst),
|
||||
BodySize::Sized64(len) => {
|
||||
dst.extend_from_slice(b"\r\ncontent-length: ");
|
||||
write!(dst.writer(), "{}", len)?;
|
||||
dst.extend_from_slice(b"\r\n");
|
||||
dst.put_slice(b"\r\ncontent-length: ");
|
||||
write!(dst.writer(), "{}\r\n", len)?;
|
||||
}
|
||||
BodySize::None => dst.extend_from_slice(b"\r\n"),
|
||||
BodySize::None => dst.put_slice(b"\r\n"),
|
||||
}
|
||||
|
||||
// Connection
|
||||
match ctype {
|
||||
ConnectionType::Upgrade => dst.extend_from_slice(b"connection: upgrade\r\n"),
|
||||
ConnectionType::Upgrade => dst.put_slice(b"connection: upgrade\r\n"),
|
||||
ConnectionType::KeepAlive if version < Version::HTTP_11 => {
|
||||
dst.extend_from_slice(b"connection: keep-alive\r\n")
|
||||
dst.put_slice(b"connection: keep-alive\r\n")
|
||||
}
|
||||
ConnectionType::Close if version >= Version::HTTP_11 => {
|
||||
dst.extend_from_slice(b"connection: close\r\n")
|
||||
dst.put_slice(b"connection: close\r\n")
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
@@ -111,7 +108,7 @@ pub(crate) trait MessageType: Sized {
|
||||
let mut has_date = false;
|
||||
let mut remaining = dst.remaining_mut();
|
||||
let mut buf = unsafe { &mut *(dst.bytes_mut() as *mut [u8]) };
|
||||
for (key, value) in self.headers() {
|
||||
for (key, value) in self.headers().inner.iter() {
|
||||
match *key {
|
||||
CONNECTION => continue,
|
||||
TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => continue,
|
||||
@@ -120,31 +117,59 @@ pub(crate) trait MessageType: Sized {
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let v = value.as_ref();
|
||||
let k = key.as_str().as_bytes();
|
||||
let len = k.len() + v.len() + 4;
|
||||
if len > remaining {
|
||||
unsafe {
|
||||
dst.advance_mut(pos);
|
||||
match value {
|
||||
map::Value::One(ref val) => {
|
||||
let v = val.as_ref();
|
||||
let len = k.len() + v.len() + 4;
|
||||
if len > remaining {
|
||||
unsafe {
|
||||
dst.advance_mut(pos);
|
||||
}
|
||||
pos = 0;
|
||||
dst.reserve(len * 2);
|
||||
remaining = dst.remaining_mut();
|
||||
unsafe {
|
||||
buf = &mut *(dst.bytes_mut() as *mut _);
|
||||
}
|
||||
}
|
||||
buf[pos..pos + k.len()].copy_from_slice(k);
|
||||
pos += k.len();
|
||||
buf[pos..pos + 2].copy_from_slice(b": ");
|
||||
pos += 2;
|
||||
buf[pos..pos + v.len()].copy_from_slice(v);
|
||||
pos += v.len();
|
||||
buf[pos..pos + 2].copy_from_slice(b"\r\n");
|
||||
pos += 2;
|
||||
remaining -= len;
|
||||
}
|
||||
pos = 0;
|
||||
dst.reserve(len);
|
||||
remaining = dst.remaining_mut();
|
||||
unsafe {
|
||||
buf = &mut *(dst.bytes_mut() as *mut _);
|
||||
map::Value::Multi(ref vec) => {
|
||||
for val in vec {
|
||||
let v = val.as_ref();
|
||||
let len = k.len() + v.len() + 4;
|
||||
if len > remaining {
|
||||
unsafe {
|
||||
dst.advance_mut(pos);
|
||||
}
|
||||
pos = 0;
|
||||
dst.reserve(len * 2);
|
||||
remaining = dst.remaining_mut();
|
||||
unsafe {
|
||||
buf = &mut *(dst.bytes_mut() as *mut _);
|
||||
}
|
||||
}
|
||||
buf[pos..pos + k.len()].copy_from_slice(k);
|
||||
pos += k.len();
|
||||
buf[pos..pos + 2].copy_from_slice(b": ");
|
||||
pos += 2;
|
||||
buf[pos..pos + v.len()].copy_from_slice(v);
|
||||
pos += v.len();
|
||||
buf[pos..pos + 2].copy_from_slice(b"\r\n");
|
||||
pos += 2;
|
||||
remaining -= len;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buf[pos..pos + k.len()].copy_from_slice(k);
|
||||
pos += k.len();
|
||||
buf[pos..pos + 2].copy_from_slice(b": ");
|
||||
pos += 2;
|
||||
buf[pos..pos + v.len()].copy_from_slice(v);
|
||||
pos += v.len();
|
||||
buf[pos..pos + 2].copy_from_slice(b"\r\n");
|
||||
pos += 2;
|
||||
remaining -= len;
|
||||
}
|
||||
unsafe {
|
||||
dst.advance_mut(pos);
|
||||
@@ -171,10 +196,6 @@ impl MessageType for Response<()> {
|
||||
self.head().chunked()
|
||||
}
|
||||
|
||||
//fn connection_type(&self) -> Option<ConnectionType> {
|
||||
// self.head().ctype
|
||||
//}
|
||||
|
||||
fn headers(&self) -> &HeaderMap {
|
||||
&self.head().headers
|
||||
}
|
||||
@@ -186,7 +207,7 @@ impl MessageType for Response<()> {
|
||||
|
||||
// status line
|
||||
helpers::write_status_line(head.version, head.status.as_u16(), dst);
|
||||
dst.extend_from_slice(reason);
|
||||
dst.put_slice(reason);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -205,6 +226,7 @@ impl MessageType for RequestHead {
|
||||
}
|
||||
|
||||
fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> {
|
||||
dst.reserve(256 + self.headers.len() * AVERAGE_HEADER_SIZE);
|
||||
write!(
|
||||
Writer(dst),
|
||||
"{} {} {}",
|
||||
|
36
actix-http/src/h1/expect.rs
Normal file
36
actix-http/src/h1/expect.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use actix_service::{NewService, Service};
|
||||
use futures::future::{ok, FutureResult};
|
||||
use futures::{Async, Poll};
|
||||
|
||||
use crate::error::Error;
|
||||
use crate::request::Request;
|
||||
|
||||
pub struct ExpectHandler;
|
||||
|
||||
impl NewService for ExpectHandler {
|
||||
type Request = Request;
|
||||
type Response = Request;
|
||||
type Error = Error;
|
||||
type Service = ExpectHandler;
|
||||
type InitError = Error;
|
||||
type Future = FutureResult<Self::Service, Self::InitError>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
ok(ExpectHandler)
|
||||
}
|
||||
}
|
||||
|
||||
impl Service for ExpectHandler {
|
||||
type Request = Request;
|
||||
type Response = Request;
|
||||
type Error = Error;
|
||||
type Future = FutureResult<Self::Response, Self::Error>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Request) -> Self::Future {
|
||||
ok(req)
|
||||
}
|
||||
}
|
@@ -6,13 +6,15 @@ mod codec;
|
||||
mod decoder;
|
||||
mod dispatcher;
|
||||
mod encoder;
|
||||
mod expect;
|
||||
mod payload;
|
||||
mod service;
|
||||
|
||||
pub use self::client::{ClientCodec, ClientPayloadCodec};
|
||||
pub use self::codec::Codec;
|
||||
pub use self::dispatcher::Dispatcher;
|
||||
pub use self::payload::{Payload, PayloadBuffer};
|
||||
pub use self::expect::ExpectHandler;
|
||||
pub use self::payload::Payload;
|
||||
pub use self::service::{H1Service, H1ServiceHandler, OneRequest};
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@@ -1,10 +1,9 @@
|
||||
//! Payload stream
|
||||
use std::cell::RefCell;
|
||||
use std::cmp;
|
||||
use std::collections::VecDeque;
|
||||
use std::rc::{Rc, Weak};
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use bytes::Bytes;
|
||||
use futures::task::current as current_task;
|
||||
use futures::task::Task;
|
||||
use futures::{Async, Poll, Stream};
|
||||
@@ -15,7 +14,7 @@ use crate::error::PayloadError;
|
||||
pub(crate) const MAX_BUFFER_SIZE: usize = 32_768;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub(crate) enum PayloadStatus {
|
||||
pub enum PayloadStatus {
|
||||
Read,
|
||||
Pause,
|
||||
Dropped,
|
||||
@@ -78,14 +77,6 @@ impl Payload {
|
||||
pub fn unread_data(&mut self, data: Bytes) {
|
||||
self.inner.borrow_mut().unread_data(data);
|
||||
}
|
||||
|
||||
#[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 {
|
||||
@@ -98,58 +89,35 @@ impl Stream for Payload {
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Payload {
|
||||
fn clone(&self) -> Payload {
|
||||
Payload {
|
||||
inner: Rc::clone(&self.inner),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Payload writer interface.
|
||||
pub(crate) trait PayloadWriter {
|
||||
/// Set stream error.
|
||||
fn set_error(&mut self, err: PayloadError);
|
||||
|
||||
/// Write eof into a stream which closes reading side of a stream.
|
||||
fn feed_eof(&mut self);
|
||||
|
||||
/// Feed bytes into a payload stream
|
||||
fn feed_data(&mut self, data: Bytes);
|
||||
|
||||
/// Need read data
|
||||
fn need_read(&self) -> PayloadStatus;
|
||||
}
|
||||
|
||||
/// Sender part of the payload stream
|
||||
pub struct PayloadSender {
|
||||
inner: Weak<RefCell<Inner>>,
|
||||
}
|
||||
|
||||
impl PayloadWriter for PayloadSender {
|
||||
impl PayloadSender {
|
||||
#[inline]
|
||||
fn set_error(&mut self, err: PayloadError) {
|
||||
pub 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) {
|
||||
pub 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) {
|
||||
pub fn feed_data(&mut self, data: Bytes) {
|
||||
if let Some(shared) = self.inner.upgrade() {
|
||||
shared.borrow_mut().feed_data(data)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn need_read(&self) -> PayloadStatus {
|
||||
pub fn need_read(&self) -> PayloadStatus {
|
||||
// we check need_read only if Payload (other side) is alive,
|
||||
// otherwise always return true (consume payload)
|
||||
if let Some(shared) = self.inner.upgrade() {
|
||||
@@ -177,7 +145,6 @@ struct Inner {
|
||||
err: Option<PayloadError>,
|
||||
need_read: bool,
|
||||
items: VecDeque<Bytes>,
|
||||
capacity: usize,
|
||||
task: Option<Task>,
|
||||
io_task: Option<Task>,
|
||||
}
|
||||
@@ -190,7 +157,6 @@ impl Inner {
|
||||
err: None,
|
||||
items: VecDeque::new(),
|
||||
need_read: true,
|
||||
capacity: MAX_BUFFER_SIZE,
|
||||
task: None,
|
||||
io_task: None,
|
||||
}
|
||||
@@ -210,7 +176,7 @@ impl Inner {
|
||||
fn feed_data(&mut self, data: Bytes) {
|
||||
self.len += data.len();
|
||||
self.items.push_back(data);
|
||||
self.need_read = self.len < self.capacity;
|
||||
self.need_read = self.len < MAX_BUFFER_SIZE;
|
||||
if let Some(task) = self.task.take() {
|
||||
task.notify()
|
||||
}
|
||||
@@ -224,7 +190,7 @@ 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;
|
||||
self.need_read = self.len < MAX_BUFFER_SIZE;
|
||||
|
||||
if self.need_read && self.task.is_none() && !self.eof {
|
||||
self.task = Some(current_task());
|
||||
@@ -258,407 +224,12 @@ impl Inner {
|
||||
}
|
||||
}
|
||||
|
||||
/// Payload buffer
|
||||
pub struct PayloadBuffer<S> {
|
||||
len: usize,
|
||||
items: VecDeque<Bytes>,
|
||||
stream: S,
|
||||
}
|
||||
|
||||
impl<S> PayloadBuffer<S>
|
||||
where
|
||||
S: Stream<Item = Bytes, Error = PayloadError>,
|
||||
{
|
||||
/// Create new `PayloadBuffer` instance
|
||||
pub fn new(stream: S) -> Self {
|
||||
PayloadBuffer {
|
||||
len: 0,
|
||||
items: VecDeque::new(),
|
||||
stream,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get mutable reference to an inner stream.
|
||||
pub fn get_mut(&mut self) -> &mut S {
|
||||
&mut self.stream
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_stream(&mut self) -> Poll<bool, PayloadError> {
|
||||
self.stream.poll().map(|res| match res {
|
||||
Async::Ready(Some(data)) => {
|
||||
self.len += data.len();
|
||||
self.items.push_back(data);
|
||||
Async::Ready(true)
|
||||
}
|
||||
Async::Ready(None) => Async::Ready(false),
|
||||
Async::NotReady => Async::NotReady,
|
||||
})
|
||||
}
|
||||
|
||||
/// Read first available chunk of bytes
|
||||
#[inline]
|
||||
pub fn readany(&mut self) -> Poll<Option<Bytes>, PayloadError> {
|
||||
if let Some(data) = self.items.pop_front() {
|
||||
self.len -= data.len();
|
||||
Ok(Async::Ready(Some(data)))
|
||||
} else {
|
||||
match self.poll_stream()? {
|
||||
Async::Ready(true) => self.readany(),
|
||||
Async::Ready(false) => Ok(Async::Ready(None)),
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if buffer contains enough bytes
|
||||
#[inline]
|
||||
pub fn can_read(&mut self, size: usize) -> Poll<Option<bool>, PayloadError> {
|
||||
if size <= self.len {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Return reference to the first chunk of data
|
||||
#[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),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read exact number of bytes
|
||||
#[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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove specified amount if bytes from buffer
|
||||
#[inline]
|
||||
pub fn drop_bytes(&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 - len, chunk.len());
|
||||
len += rem;
|
||||
if rem < chunk.len() {
|
||||
chunk.split_to(rem);
|
||||
self.items.push_front(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy buffered data
|
||||
pub fn copy(&mut self, size: usize) -> Poll<Option<BytesMut>, PayloadError> {
|
||||
if size <= self.len {
|
||||
let mut buf = BytesMut::with_capacity(size);
|
||||
for chunk in &self.items {
|
||||
if buf.len() < size {
|
||||
let rem = cmp::min(size - buf.len(), chunk.len());
|
||||
buf.extend_from_slice(&chunk[..rem]);
|
||||
}
|
||||
if buf.len() == size {
|
||||
return Ok(Async::Ready(Some(buf)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match self.poll_stream()? {
|
||||
Async::Ready(true) => self.copy(size),
|
||||
Async::Ready(false) => Ok(Async::Ready(None)),
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read until specified ending
|
||||
pub fn read_until(&mut self, line: &[u8]) -> Poll<Option<Bytes>, PayloadError> {
|
||||
let mut idx = 0;
|
||||
let mut num = 0;
|
||||
let mut offset = 0;
|
||||
let mut found = false;
|
||||
let mut length = 0;
|
||||
|
||||
for no in 0..self.items.len() {
|
||||
{
|
||||
let chunk = &self.items[no];
|
||||
for (pos, ch) in chunk.iter().enumerate() {
|
||||
if *ch == line[idx] {
|
||||
idx += 1;
|
||||
if idx == line.len() {
|
||||
num = no;
|
||||
offset = pos + 1;
|
||||
length += pos + 1;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
idx = 0
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
length += chunk.len()
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
let mut buf = BytesMut::with_capacity(length);
|
||||
if num > 0 {
|
||||
for _ in 0..num {
|
||||
buf.extend_from_slice(&self.items.pop_front().unwrap());
|
||||
}
|
||||
}
|
||||
if offset > 0 {
|
||||
let mut chunk = self.items.pop_front().unwrap();
|
||||
buf.extend_from_slice(&chunk.split_to(offset));
|
||||
if !chunk.is_empty() {
|
||||
self.items.push_front(chunk)
|
||||
}
|
||||
}
|
||||
self.len -= length;
|
||||
return Ok(Async::Ready(Some(buf.freeze())));
|
||||
}
|
||||
}
|
||||
|
||||
match self.poll_stream()? {
|
||||
Async::Ready(true) => self.read_until(line),
|
||||
Async::Ready(false) => Ok(Async::Ready(None)),
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read bytes until new line delimiter
|
||||
pub fn readline(&mut self) -> Poll<Option<Bytes>, PayloadError> {
|
||||
self.read_until(b"\n")
|
||||
}
|
||||
|
||||
/// Put unprocessed data back to the buffer
|
||||
pub fn unprocessed(&mut self, data: Bytes) {
|
||||
self.len += data.len();
|
||||
self.items.push_front(data);
|
||||
}
|
||||
|
||||
/// Get remaining data from the buffer
|
||||
pub fn remaining(&mut self) -> Bytes {
|
||||
self.items
|
||||
.iter_mut()
|
||||
.fold(BytesMut::new(), |mut b, c| {
|
||||
b.extend_from_slice(c);
|
||||
b
|
||||
})
|
||||
.freeze()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use actix_rt::Runtime;
|
||||
use futures::future::{lazy, result};
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(lazy(|| {
|
||||
let (_, payload) = Payload::create(false);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
assert_eq!(payload.len, 0);
|
||||
assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
|
||||
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eof() {
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(lazy(|| {
|
||||
let (mut sender, payload) = Payload::create(false);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
|
||||
sender.feed_data(Bytes::from("data"));
|
||||
sender.feed_eof();
|
||||
|
||||
assert_eq!(
|
||||
Async::Ready(Some(Bytes::from("data"))),
|
||||
payload.readany().ok().unwrap()
|
||||
);
|
||||
assert_eq!(payload.len, 0);
|
||||
assert_eq!(Async::Ready(None), payload.readany().ok().unwrap());
|
||||
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_err() {
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(lazy(|| {
|
||||
let (mut sender, payload) = Payload::create(false);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
assert_eq!(Async::NotReady, payload.readany().ok().unwrap());
|
||||
|
||||
sender.set_error(PayloadError::Incomplete(None));
|
||||
payload.readany().err().unwrap();
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readany() {
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(lazy(|| {
|
||||
let (mut sender, payload) = Payload::create(false);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
sender.feed_data(Bytes::from("line1"));
|
||||
sender.feed_data(Bytes::from("line2"));
|
||||
|
||||
assert_eq!(
|
||||
Async::Ready(Some(Bytes::from("line1"))),
|
||||
payload.readany().ok().unwrap()
|
||||
);
|
||||
assert_eq!(payload.len, 0);
|
||||
|
||||
assert_eq!(
|
||||
Async::Ready(Some(Bytes::from("line2"))),
|
||||
payload.readany().ok().unwrap()
|
||||
);
|
||||
assert_eq!(payload.len, 0);
|
||||
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readexactly() {
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(lazy(|| {
|
||||
let (mut sender, payload) = Payload::create(false);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
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(Bytes::from_static(b"li"))),
|
||||
payload.read_exact(2).ok().unwrap()
|
||||
);
|
||||
assert_eq!(payload.len, 3);
|
||||
|
||||
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(None));
|
||||
payload.read_exact(10).err().unwrap();
|
||||
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readuntil() {
|
||||
Runtime::new()
|
||||
.unwrap()
|
||||
.block_on(lazy(|| {
|
||||
let (mut sender, payload) = Payload::create(false);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
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.read_until(b"ne").ok().unwrap()
|
||||
);
|
||||
assert_eq!(payload.len, 1);
|
||||
|
||||
assert_eq!(
|
||||
Async::Ready(Some(Bytes::from("1line2"))),
|
||||
payload.read_until(b"2").ok().unwrap()
|
||||
);
|
||||
assert_eq!(payload.len, 0);
|
||||
|
||||
sender.set_error(PayloadError::Incomplete(None));
|
||||
payload.read_until(b"b").err().unwrap();
|
||||
|
||||
let res: Result<(), ()> = Ok(());
|
||||
result(res)
|
||||
}))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unread_data() {
|
||||
Runtime::new()
|
||||
|
@@ -1,4 +1,4 @@
|
||||
use std::fmt::Debug;
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
@@ -10,27 +10,28 @@ use futures::{try_ready, Async, Future, IntoFuture, Poll, Stream};
|
||||
|
||||
use crate::body::MessageBody;
|
||||
use crate::config::{KeepAlive, ServiceConfig};
|
||||
use crate::error::{DispatchError, ParseError};
|
||||
use crate::error::{DispatchError, Error, ParseError};
|
||||
use crate::request::Request;
|
||||
use crate::response::Response;
|
||||
|
||||
use super::codec::Codec;
|
||||
use super::dispatcher::Dispatcher;
|
||||
use super::Message;
|
||||
use super::{ExpectHandler, Message};
|
||||
|
||||
/// `NewService` implementation for HTTP1 transport
|
||||
pub struct H1Service<T, P, S, B> {
|
||||
pub struct H1Service<T, P, S, B, X = ExpectHandler> {
|
||||
srv: S,
|
||||
cfg: ServiceConfig,
|
||||
expect: X,
|
||||
_t: PhantomData<(T, P, B)>,
|
||||
}
|
||||
|
||||
impl<T, P, S, B> H1Service<T, P, S, B>
|
||||
where
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Error: Debug,
|
||||
S::Error: Into<Error>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
S::Service: 'static,
|
||||
B: MessageBody,
|
||||
{
|
||||
/// Create new `HttpService` instance with default config.
|
||||
@@ -40,6 +41,7 @@ where
|
||||
H1Service {
|
||||
cfg,
|
||||
srv: service.into_new_service(),
|
||||
expect: ExpectHandler,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
@@ -52,30 +54,59 @@ where
|
||||
H1Service {
|
||||
cfg,
|
||||
srv: service.into_new_service(),
|
||||
expect: ExpectHandler,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P, S, B> NewService<SrvConfig> for H1Service<T, P, S, B>
|
||||
impl<T, P, S, B, X> H1Service<T, P, S, B, X>
|
||||
where
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Response: Into<Response<B>>,
|
||||
S::InitError: fmt::Debug,
|
||||
B: MessageBody,
|
||||
{
|
||||
pub fn expect<U>(self, expect: U) -> H1Service<T, P, S, B, U>
|
||||
where
|
||||
U: NewService<Request = Request, Response = Request>,
|
||||
U::Error: Into<Error>,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
H1Service {
|
||||
expect,
|
||||
cfg: self.cfg,
|
||||
srv: self.srv,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P, S, B, X> NewService<SrvConfig> for H1Service<T, P, S, B, X>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Error: Debug,
|
||||
S::Error: Into<Error>,
|
||||
S::Response: Into<Response<B>>,
|
||||
S::Service: 'static,
|
||||
S::InitError: fmt::Debug,
|
||||
B: MessageBody,
|
||||
X: NewService<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
X::InitError: fmt::Debug,
|
||||
{
|
||||
type Request = Io<T, P>;
|
||||
type Response = ();
|
||||
type Error = DispatchError;
|
||||
type InitError = S::InitError;
|
||||
type Service = H1ServiceHandler<T, P, S::Service, B>;
|
||||
type Future = H1ServiceResponse<T, P, S, B>;
|
||||
type InitError = ();
|
||||
type Service = H1ServiceHandler<T, P, S::Service, B, X::Service>;
|
||||
type Future = H1ServiceResponse<T, P, S, B, X>;
|
||||
|
||||
fn new_service(&self, cfg: &SrvConfig) -> Self::Future {
|
||||
H1ServiceResponse {
|
||||
fut: self.srv.new_service(cfg).into_future(),
|
||||
fut_ex: Some(self.expect.new_service(&())),
|
||||
expect: None,
|
||||
cfg: Some(self.cfg.clone()),
|
||||
_t: PhantomData,
|
||||
}
|
||||
@@ -83,78 +114,136 @@ where
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct H1ServiceResponse<T, P, S: NewService<SrvConfig, Request = Request>, B> {
|
||||
fut: <S::Future as IntoFuture>::Future,
|
||||
pub struct H1ServiceResponse<T, P, S, B, X>
|
||||
where
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::InitError: fmt::Debug,
|
||||
X: NewService<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
X::InitError: fmt::Debug,
|
||||
{
|
||||
fut: S::Future,
|
||||
fut_ex: Option<X::Future>,
|
||||
expect: Option<X::Service>,
|
||||
cfg: Option<ServiceConfig>,
|
||||
_t: PhantomData<(T, P, B)>,
|
||||
}
|
||||
|
||||
impl<T, P, S, B> Future for H1ServiceResponse<T, P, S, B>
|
||||
impl<T, P, S, B, X> Future for H1ServiceResponse<T, P, S, B, X>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Service: 'static,
|
||||
S::Error: Debug,
|
||||
S::Error: Into<Error>,
|
||||
S::Response: Into<Response<B>>,
|
||||
S::InitError: fmt::Debug,
|
||||
B: MessageBody,
|
||||
X: NewService<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
X::InitError: fmt::Debug,
|
||||
{
|
||||
type Item = H1ServiceHandler<T, P, S::Service, B>;
|
||||
type Error = S::InitError;
|
||||
type Item = H1ServiceHandler<T, P, S::Service, B, X::Service>;
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
let service = try_ready!(self.fut.poll());
|
||||
if let Some(ref mut fut) = self.fut_ex {
|
||||
let expect = try_ready!(fut
|
||||
.poll()
|
||||
.map_err(|e| log::error!("Init http service error: {:?}", e)));
|
||||
self.expect = Some(expect);
|
||||
self.fut_ex.take();
|
||||
}
|
||||
|
||||
let service = try_ready!(self
|
||||
.fut
|
||||
.poll()
|
||||
.map_err(|e| log::error!("Init http service error: {:?}", e)));
|
||||
Ok(Async::Ready(H1ServiceHandler::new(
|
||||
self.cfg.take().unwrap(),
|
||||
service,
|
||||
self.expect.take().unwrap(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// `Service` implementation for HTTP1 transport
|
||||
pub struct H1ServiceHandler<T, P, S: 'static, B> {
|
||||
pub struct H1ServiceHandler<T, P, S, B, X> {
|
||||
srv: CloneableService<S>,
|
||||
expect: CloneableService<X>,
|
||||
cfg: ServiceConfig,
|
||||
_t: PhantomData<(T, P, B)>,
|
||||
}
|
||||
|
||||
impl<T, P, S, B> H1ServiceHandler<T, P, S, B>
|
||||
impl<T, P, S, B, X> H1ServiceHandler<T, P, S, B, X>
|
||||
where
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Debug,
|
||||
S::Error: Into<Error>,
|
||||
S::Response: Into<Response<B>>,
|
||||
B: MessageBody,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
{
|
||||
fn new(cfg: ServiceConfig, srv: S) -> H1ServiceHandler<T, P, S, B> {
|
||||
fn new(cfg: ServiceConfig, srv: S, expect: X) -> H1ServiceHandler<T, P, S, B, X> {
|
||||
H1ServiceHandler {
|
||||
srv: CloneableService::new(srv),
|
||||
expect: CloneableService::new(expect),
|
||||
cfg,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P, S, B> Service for H1ServiceHandler<T, P, S, B>
|
||||
impl<T, P, S, B, X> Service for H1ServiceHandler<T, P, S, B, X>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Debug,
|
||||
S::Error: Into<Error>,
|
||||
S::Response: Into<Response<B>>,
|
||||
B: MessageBody,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
{
|
||||
type Request = Io<T, P>;
|
||||
type Response = ();
|
||||
type Error = DispatchError;
|
||||
type Future = Dispatcher<T, S, B>;
|
||||
type Future = Dispatcher<T, S, B, X>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.srv.poll_ready().map_err(|e| {
|
||||
log::error!("Http service readiness error: {:?}", e);
|
||||
DispatchError::Service
|
||||
})
|
||||
let ready = self
|
||||
.expect
|
||||
.poll_ready()
|
||||
.map_err(|e| {
|
||||
let e = e.into();
|
||||
log::error!("Http service readiness error: {:?}", e);
|
||||
DispatchError::Service(e)
|
||||
})?
|
||||
.is_ready();
|
||||
|
||||
let ready = self
|
||||
.srv
|
||||
.poll_ready()
|
||||
.map_err(|e| {
|
||||
let e = e.into();
|
||||
log::error!("Http service readiness error: {:?}", e);
|
||||
DispatchError::Service(e)
|
||||
})?
|
||||
.is_ready()
|
||||
&& ready;
|
||||
|
||||
if ready {
|
||||
Ok(Async::Ready(()))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Self::Request) -> Self::Future {
|
||||
Dispatcher::new(req.into_parts().0, self.cfg.clone(), self.srv.clone())
|
||||
Dispatcher::new(
|
||||
req.into_parts().0,
|
||||
self.cfg.clone(),
|
||||
self.srv.clone(),
|
||||
self.expect.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -31,7 +31,7 @@ const CHUNK_SIZE: usize = 16_384;
|
||||
/// Dispatcher for HTTP/2 protocol
|
||||
pub struct Dispatcher<
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: Service<Request = Request> + 'static,
|
||||
S: Service<Request = Request>,
|
||||
B: MessageBody,
|
||||
> {
|
||||
service: CloneableService<S>,
|
||||
@@ -45,8 +45,9 @@ pub struct Dispatcher<
|
||||
impl<T, S, B> Dispatcher<T, S, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: Service<Request = Request> + 'static,
|
||||
S::Error: fmt::Debug,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Future: 'static,
|
||||
S::Response: Into<Response<B>>,
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
@@ -86,8 +87,9 @@ where
|
||||
impl<T, S, B> Future for Dispatcher<T, S, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: Service<Request = Request> + 'static,
|
||||
S::Error: fmt::Debug,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Future: 'static,
|
||||
S::Response: Into<Response<B>>,
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
@@ -114,8 +116,8 @@ where
|
||||
head.uri = parts.uri;
|
||||
head.method = parts.method;
|
||||
head.version = parts.version;
|
||||
head.headers = parts.headers;
|
||||
tokio_current_thread::spawn(ServiceResponse::<S, B> {
|
||||
head.headers = parts.headers.into();
|
||||
tokio_current_thread::spawn(ServiceResponse::<S::Future, B> {
|
||||
state: ServiceResponseState::ServiceCall(
|
||||
self.service.call(req),
|
||||
Some(res),
|
||||
@@ -130,22 +132,22 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
struct ServiceResponse<S: Service, B> {
|
||||
state: ServiceResponseState<S, B>,
|
||||
struct ServiceResponse<F, B> {
|
||||
state: ServiceResponseState<F, B>,
|
||||
config: ServiceConfig,
|
||||
buffer: Option<Bytes>,
|
||||
}
|
||||
|
||||
enum ServiceResponseState<S: Service, B> {
|
||||
ServiceCall(S::Future, Option<SendResponse<Bytes>>),
|
||||
enum ServiceResponseState<F, B> {
|
||||
ServiceCall(F, Option<SendResponse<Bytes>>),
|
||||
SendPayload(SendStream<Bytes>, ResponseBody<B>),
|
||||
}
|
||||
|
||||
impl<S, B> ServiceResponse<S, B>
|
||||
impl<F, B> ServiceResponse<F, B>
|
||||
where
|
||||
S: Service<Request = Request> + 'static,
|
||||
S::Error: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
F: Future,
|
||||
F::Error: Into<Error>,
|
||||
F::Item: Into<Response<B>>,
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
fn prepare_response(
|
||||
@@ -209,11 +211,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B> Future for ServiceResponse<S, B>
|
||||
impl<F, B> Future for ServiceResponse<F, B>
|
||||
where
|
||||
S: Service<Request = Request> + 'static,
|
||||
S::Error: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
F: Future,
|
||||
F::Error: Into<Error>,
|
||||
F::Item: Into<Response<B>>,
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
type Item = ();
|
||||
|
@@ -32,9 +32,9 @@ pub struct H2Service<T, P, S, B> {
|
||||
impl<T, P, S, B> H2Service<T, P, S, B>
|
||||
where
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Service: 'static,
|
||||
S::Error: Debug + 'static,
|
||||
S::Error: Into<Error>,
|
||||
S::Response: Into<Response<B>>,
|
||||
<S::Service as Service>::Future: 'static,
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
/// Create new `HttpService` instance.
|
||||
@@ -65,9 +65,9 @@ impl<T, P, S, B> NewService<SrvConfig> for H2Service<T, P, S, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Service: 'static,
|
||||
S::Error: Debug,
|
||||
S::Error: Into<Error>,
|
||||
S::Response: Into<Response<B>>,
|
||||
<S::Service as Service>::Future: 'static,
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
type Request = Io<T, P>;
|
||||
@@ -97,9 +97,9 @@ impl<T, P, S, B> Future for H2ServiceResponse<T, P, S, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Service: 'static,
|
||||
S::Error: Into<Error>,
|
||||
S::Response: Into<Response<B>>,
|
||||
S::Error: Debug,
|
||||
<S::Service as Service>::Future: 'static,
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
type Item = H2ServiceHandler<T, P, S::Service, B>;
|
||||
@@ -115,7 +115,7 @@ where
|
||||
}
|
||||
|
||||
/// `Service` implementation for http/2 transport
|
||||
pub struct H2ServiceHandler<T, P, S: 'static, B> {
|
||||
pub struct H2ServiceHandler<T, P, S, B> {
|
||||
srv: CloneableService<S>,
|
||||
cfg: ServiceConfig,
|
||||
_t: PhantomData<(T, P, B)>,
|
||||
@@ -123,8 +123,9 @@ pub struct H2ServiceHandler<T, P, S: 'static, B> {
|
||||
|
||||
impl<T, P, S, B> H2ServiceHandler<T, P, S, B>
|
||||
where
|
||||
S: Service<Request = Request> + 'static,
|
||||
S::Error: Debug,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Future: 'static,
|
||||
S::Response: Into<Response<B>>,
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
@@ -140,8 +141,9 @@ where
|
||||
impl<T, P, S, B> Service for H2ServiceHandler<T, P, S, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: Service<Request = Request> + 'static,
|
||||
S::Error: Debug,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Future: 'static,
|
||||
S::Response: Into<Response<B>>,
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
@@ -152,8 +154,9 @@ where
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.srv.poll_ready().map_err(|e| {
|
||||
let e = e.into();
|
||||
error!("Service readiness error: {:?}", e);
|
||||
DispatchError::Service
|
||||
DispatchError::Service(e)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -168,11 +171,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
enum State<
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: Service<Request = Request> + 'static,
|
||||
B: MessageBody,
|
||||
> {
|
||||
enum State<T: AsyncRead + AsyncWrite, S: Service<Request = Request>, B: MessageBody>
|
||||
where
|
||||
S::Future: 'static,
|
||||
{
|
||||
Incoming(Dispatcher<T, S, B>),
|
||||
Handshake(
|
||||
Option<CloneableService<S>>,
|
||||
@@ -184,8 +186,9 @@ enum State<
|
||||
pub struct H2ServiceHandlerResponse<T, S, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: Service<Request = Request> + 'static,
|
||||
S::Error: Debug,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Future: 'static,
|
||||
S::Response: Into<Response<B>>,
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
@@ -195,8 +198,9 @@ where
|
||||
impl<T, S, B> Future for H2ServiceHandlerResponse<T, S, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: Service<Request = Request> + 'static,
|
||||
S::Error: Debug,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Future: 'static,
|
||||
S::Response: Into<Response<B>>,
|
||||
B: MessageBody,
|
||||
{
|
||||
|
@@ -64,7 +64,7 @@ impl Header for CacheControl {
|
||||
where
|
||||
T: crate::HttpMessage,
|
||||
{
|
||||
let directives = from_comma_delimited(msg.headers().get_all(Self::name()))?;
|
||||
let directives = from_comma_delimited(msg.headers().get_all(&Self::name()))?;
|
||||
if !directives.is_empty() {
|
||||
Ok(CacheControl(directives))
|
||||
} else {
|
||||
|
@@ -444,7 +444,7 @@ impl Header for ContentDisposition {
|
||||
}
|
||||
|
||||
fn parse<T: crate::HttpMessage>(msg: &T) -> Result<Self, crate::error::ParseError> {
|
||||
if let Some(h) = msg.headers().get(Self::name()) {
|
||||
if let Some(h) = msg.headers().get(&Self::name()) {
|
||||
Self::from_raw(&h)
|
||||
} else {
|
||||
Err(crate::error::ParseError::Header)
|
||||
|
@@ -73,12 +73,12 @@ impl Header for IfRange {
|
||||
T: HttpMessage,
|
||||
{
|
||||
let etag: Result<EntityTag, _> =
|
||||
from_one_raw_str(msg.headers().get(header::IF_RANGE));
|
||||
from_one_raw_str(msg.headers().get(&header::IF_RANGE));
|
||||
if let Ok(etag) = etag {
|
||||
return Ok(IfRange::EntityTag(etag));
|
||||
}
|
||||
let date: Result<HttpDate, _> =
|
||||
from_one_raw_str(msg.headers().get(header::IF_RANGE));
|
||||
from_one_raw_str(msg.headers().get(&header::IF_RANGE));
|
||||
if let Ok(date) = date {
|
||||
return Ok(IfRange::Date(date));
|
||||
}
|
||||
|
384
actix-http/src/header/map.rs
Normal file
384
actix-http/src/header/map.rs
Normal file
@@ -0,0 +1,384 @@
|
||||
use either::Either;
|
||||
use hashbrown::hash_map::{self, Entry};
|
||||
use hashbrown::HashMap;
|
||||
use http::header::{HeaderName, HeaderValue};
|
||||
use http::HttpTryFrom;
|
||||
|
||||
/// A set of HTTP headers
|
||||
///
|
||||
/// `HeaderMap` is an multimap of [`HeaderName`] to values.
|
||||
///
|
||||
/// [`HeaderName`]: struct.HeaderName.html
|
||||
#[derive(Debug)]
|
||||
pub struct HeaderMap {
|
||||
pub(crate) inner: HashMap<HeaderName, Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Value {
|
||||
One(HeaderValue),
|
||||
Multi(Vec<HeaderValue>),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
fn get(&self) -> &HeaderValue {
|
||||
match self {
|
||||
Value::One(ref val) => val,
|
||||
Value::Multi(ref val) => &val[0],
|
||||
}
|
||||
}
|
||||
|
||||
fn get_mut(&mut self) -> &mut HeaderValue {
|
||||
match self {
|
||||
Value::One(ref mut val) => val,
|
||||
Value::Multi(ref mut val) => &mut val[0],
|
||||
}
|
||||
}
|
||||
|
||||
fn append(&mut self, val: HeaderValue) {
|
||||
match self {
|
||||
Value::One(_) => {
|
||||
let data = std::mem::replace(self, Value::Multi(vec![val]));
|
||||
match data {
|
||||
Value::One(val) => self.append(val),
|
||||
Value::Multi(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
Value::Multi(ref mut vec) => vec.push(val),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HeaderMap {
|
||||
/// Create an empty `HeaderMap`.
|
||||
///
|
||||
/// The map will be created without any capacity. This function will not
|
||||
/// allocate.
|
||||
pub fn new() -> Self {
|
||||
HeaderMap {
|
||||
inner: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create an empty `HeaderMap` with the specified capacity.
|
||||
///
|
||||
/// The returned map will allocate internal storage in order to hold about
|
||||
/// `capacity` elements without reallocating. However, this is a "best
|
||||
/// effort" as there are usage patterns that could cause additional
|
||||
/// allocations before `capacity` headers are stored in the map.
|
||||
///
|
||||
/// More capacity than requested may be allocated.
|
||||
pub fn with_capacity(capacity: usize) -> HeaderMap {
|
||||
HeaderMap {
|
||||
inner: HashMap::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of keys stored in the map.
|
||||
///
|
||||
/// This number could be be less than or equal to actual headers stored in
|
||||
/// the map.
|
||||
pub fn len(&self) -> usize {
|
||||
self.inner.len()
|
||||
}
|
||||
|
||||
/// Returns true if the map contains no elements.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inner.len() == 0
|
||||
}
|
||||
|
||||
/// Clears the map, removing all key-value pairs. Keeps the allocated memory
|
||||
/// for reuse.
|
||||
pub fn clear(&mut self) {
|
||||
self.inner.clear();
|
||||
}
|
||||
|
||||
/// Returns the number of headers the map can hold without reallocating.
|
||||
///
|
||||
/// This number is an approximation as certain usage patterns could cause
|
||||
/// additional allocations before the returned capacity is filled.
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.inner.capacity()
|
||||
}
|
||||
|
||||
/// Reserves capacity for at least `additional` more headers to be inserted
|
||||
/// into the `HeaderMap`.
|
||||
///
|
||||
/// The header map may reserve more space to avoid frequent reallocations.
|
||||
/// Like with `with_capacity`, this will be a "best effort" to avoid
|
||||
/// allocations until `additional` more headers are inserted. Certain usage
|
||||
/// patterns could cause additional allocations before the number is
|
||||
/// reached.
|
||||
pub fn reserve(&mut self, additional: usize) {
|
||||
self.inner.reserve(additional)
|
||||
}
|
||||
|
||||
/// Returns a reference to the value associated with the key.
|
||||
///
|
||||
/// If there are multiple values associated with the key, then the first one
|
||||
/// is returned. Use `get_all` to get all values associated with a given
|
||||
/// key. Returns `None` if there are no values associated with the key.
|
||||
pub fn get<N: AsName>(&self, name: N) -> Option<&HeaderValue> {
|
||||
self.get2(name).map(|v| v.get())
|
||||
}
|
||||
|
||||
fn get2<N: AsName>(&self, name: N) -> Option<&Value> {
|
||||
match name.as_name() {
|
||||
Either::Left(name) => self.inner.get(name),
|
||||
Either::Right(s) => {
|
||||
if let Ok(name) = HeaderName::try_from(s) {
|
||||
self.inner.get(&name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a view of all values associated with a key.
|
||||
///
|
||||
/// The returned view does not incur any allocations and allows iterating
|
||||
/// the values associated with the key. See [`GetAll`] for more details.
|
||||
/// Returns `None` if there are no values associated with the key.
|
||||
///
|
||||
/// [`GetAll`]: struct.GetAll.html
|
||||
pub fn get_all<N: AsName>(&self, name: N) -> GetAll {
|
||||
GetAll {
|
||||
idx: 0,
|
||||
item: self.get2(name),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a mutable reference to the value associated with the key.
|
||||
///
|
||||
/// If there are multiple values associated with the key, then the first one
|
||||
/// is returned. Use `entry` to get all values associated with a given
|
||||
/// key. Returns `None` if there are no values associated with the key.
|
||||
pub fn get_mut<N: AsName>(&mut self, name: N) -> Option<&mut HeaderValue> {
|
||||
match name.as_name() {
|
||||
Either::Left(name) => self.inner.get_mut(name).map(|v| v.get_mut()),
|
||||
Either::Right(s) => {
|
||||
if let Ok(name) = HeaderName::try_from(s) {
|
||||
self.inner.get_mut(&name).map(|v| v.get_mut())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if the map contains a value for the specified key.
|
||||
pub fn contains_key<N: AsName>(&self, key: N) -> bool {
|
||||
match key.as_name() {
|
||||
Either::Left(name) => self.inner.contains_key(name),
|
||||
Either::Right(s) => {
|
||||
if let Ok(name) = HeaderName::try_from(s) {
|
||||
self.inner.contains_key(&name)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator visiting all key-value pairs.
|
||||
///
|
||||
/// The iteration order is arbitrary, but consistent across platforms for
|
||||
/// the same crate version. Each key will be yielded once per associated
|
||||
/// value. So, if a key has 3 associated values, it will be yielded 3 times.
|
||||
pub fn iter(&self) -> Iter {
|
||||
Iter::new(self.inner.iter())
|
||||
}
|
||||
|
||||
/// An iterator visiting all keys.
|
||||
///
|
||||
/// The iteration order is arbitrary, but consistent across platforms for
|
||||
/// the same crate version. Each key will be yielded only once even if it
|
||||
/// has multiple associated values.
|
||||
pub fn keys(&self) -> Keys {
|
||||
Keys(self.inner.keys())
|
||||
}
|
||||
|
||||
/// Inserts a key-value pair into the map.
|
||||
///
|
||||
/// If the map did not previously have this key present, then `None` is
|
||||
/// returned.
|
||||
///
|
||||
/// If the map did have this key present, the new value is associated with
|
||||
/// the key and all previous values are removed. **Note** that only a single
|
||||
/// one of the previous values is returned. If there are multiple values
|
||||
/// that have been previously associated with the key, then the first one is
|
||||
/// returned. See `insert_mult` on `OccupiedEntry` for an API that returns
|
||||
/// all values.
|
||||
///
|
||||
/// The key is not updated, though; this matters for types that can be `==`
|
||||
/// without being identical.
|
||||
pub fn insert(&mut self, key: HeaderName, val: HeaderValue) {
|
||||
let _ = self.inner.insert(key, Value::One(val));
|
||||
}
|
||||
|
||||
/// Inserts a key-value pair into the map.
|
||||
///
|
||||
/// If the map did not previously have this key present, then `false` is
|
||||
/// returned.
|
||||
///
|
||||
/// If the map did have this key present, the new value is pushed to the end
|
||||
/// of the list of values currently associated with the key. The key is not
|
||||
/// updated, though; this matters for types that can be `==` without being
|
||||
/// identical.
|
||||
pub fn append(&mut self, key: HeaderName, value: HeaderValue) {
|
||||
match self.inner.entry(key) {
|
||||
Entry::Occupied(mut entry) => entry.get_mut().append(value),
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(Value::One(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes all headers for a particular header name from the map.
|
||||
pub fn remove<N: AsName>(&mut self, key: N) {
|
||||
match key.as_name() {
|
||||
Either::Left(name) => {
|
||||
let _ = self.inner.remove(name);
|
||||
}
|
||||
Either::Right(s) => {
|
||||
if let Ok(name) = HeaderName::try_from(s) {
|
||||
let _ = self.inner.remove(&name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait AsName {
|
||||
fn as_name(&self) -> Either<&HeaderName, &str>;
|
||||
}
|
||||
|
||||
impl AsName for HeaderName {
|
||||
fn as_name(&self) -> Either<&HeaderName, &str> {
|
||||
Either::Left(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsName for &'a HeaderName {
|
||||
fn as_name(&self) -> Either<&HeaderName, &str> {
|
||||
Either::Left(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsName for &'a str {
|
||||
fn as_name(&self) -> Either<&HeaderName, &str> {
|
||||
Either::Right(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsName for String {
|
||||
fn as_name(&self) -> Either<&HeaderName, &str> {
|
||||
Either::Right(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AsName for &'a String {
|
||||
fn as_name(&self) -> Either<&HeaderName, &str> {
|
||||
Either::Right(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GetAll<'a> {
|
||||
idx: usize,
|
||||
item: Option<&'a Value>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for GetAll<'a> {
|
||||
type Item = &'a HeaderValue;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<&'a HeaderValue> {
|
||||
if let Some(ref val) = self.item {
|
||||
match val {
|
||||
Value::One(ref val) => {
|
||||
self.item.take();
|
||||
Some(val)
|
||||
}
|
||||
Value::Multi(ref vec) => {
|
||||
if self.idx < vec.len() {
|
||||
let item = Some(&vec[self.idx]);
|
||||
self.idx += 1;
|
||||
item
|
||||
} else {
|
||||
self.item.take();
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Keys<'a>(hash_map::Keys<'a, HeaderName, Value>);
|
||||
|
||||
impl<'a> Iterator for Keys<'a> {
|
||||
type Item = &'a HeaderName;
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<&'a HeaderName> {
|
||||
self.0.next()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a HeaderMap {
|
||||
type Item = (&'a HeaderName, &'a HeaderValue);
|
||||
type IntoIter = Iter<'a>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.iter()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Iter<'a> {
|
||||
idx: usize,
|
||||
current: Option<(&'a HeaderName, &'a Vec<HeaderValue>)>,
|
||||
iter: hash_map::Iter<'a, HeaderName, Value>,
|
||||
}
|
||||
|
||||
impl<'a> Iter<'a> {
|
||||
fn new(iter: hash_map::Iter<'a, HeaderName, Value>) -> Self {
|
||||
Self {
|
||||
iter,
|
||||
idx: 0,
|
||||
current: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Iter<'a> {
|
||||
type Item = (&'a HeaderName, &'a HeaderValue);
|
||||
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<(&'a HeaderName, &'a HeaderValue)> {
|
||||
if let Some(ref mut item) = self.current {
|
||||
if self.idx < item.1.len() {
|
||||
let item = (item.0, &item.1[self.idx]);
|
||||
self.idx += 1;
|
||||
return Some(item);
|
||||
} else {
|
||||
self.idx = 0;
|
||||
self.current.take();
|
||||
}
|
||||
}
|
||||
if let Some(item) = self.iter.next() {
|
||||
match item.1 {
|
||||
Value::One(ref value) => Some((item.0, value)),
|
||||
Value::Multi(ref vec) => {
|
||||
self.current = Some((item.0, vec));
|
||||
self.next()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,7 +4,6 @@
|
||||
use std::{fmt, str::FromStr};
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use http::header::GetAll;
|
||||
use http::Error as HttpError;
|
||||
use mime::Mime;
|
||||
|
||||
@@ -14,6 +13,7 @@ use crate::error::ParseError;
|
||||
use crate::httpmessage::HttpMessage;
|
||||
|
||||
mod common;
|
||||
pub(crate) mod map;
|
||||
mod shared;
|
||||
#[doc(hidden)]
|
||||
pub use self::common::*;
|
||||
@@ -21,6 +21,9 @@ pub use self::common::*;
|
||||
pub use self::shared::*;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use self::map::GetAll;
|
||||
pub use self::map::HeaderMap;
|
||||
|
||||
/// A trait for any object that will represent a header field and value.
|
||||
pub trait Header
|
||||
where
|
||||
@@ -33,7 +36,6 @@ where
|
||||
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError>;
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// A trait for any object that can be Converted to a `HeaderValue`
|
||||
pub trait IntoHeaderValue: Sized {
|
||||
/// The type returned in the event of a conversion error.
|
||||
@@ -97,6 +99,26 @@ impl IntoHeaderValue for String {
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoHeaderValue for usize {
|
||||
type Error = InvalidHeaderValueBytes;
|
||||
|
||||
#[inline]
|
||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
||||
let s = format!("{}", self);
|
||||
HeaderValue::from_shared(Bytes::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoHeaderValue for u64 {
|
||||
type Error = InvalidHeaderValueBytes;
|
||||
|
||||
#[inline]
|
||||
fn try_into(self) -> Result<HeaderValue, Self::Error> {
|
||||
let s = format!("{}", self);
|
||||
HeaderValue::from_shared(Bytes::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoHeaderValue for Mime {
|
||||
type Error = InvalidHeaderValueBytes;
|
||||
|
||||
@@ -202,8 +224,8 @@ impl fmt::Write for Writer {
|
||||
#[inline]
|
||||
#[doc(hidden)]
|
||||
/// Reads a comma-delimited raw header into a Vec.
|
||||
pub fn from_comma_delimited<T: FromStr>(
|
||||
all: GetAll<HeaderValue>,
|
||||
pub fn from_comma_delimited<'a, I: Iterator<Item = &'a HeaderValue> + 'a, T: FromStr>(
|
||||
all: I,
|
||||
) -> Result<Vec<T>, ParseError> {
|
||||
let mut result = Vec::new();
|
||||
for h in all {
|
||||
@@ -361,6 +383,17 @@ pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result
|
||||
fmt::Display::fmt(&encoded, f)
|
||||
}
|
||||
|
||||
/// Convert http::HeaderMap to a HeaderMap
|
||||
impl From<http::HeaderMap> for HeaderMap {
|
||||
fn from(map: http::HeaderMap) -> HeaderMap {
|
||||
let mut new_map = HeaderMap::with_capacity(map.capacity());
|
||||
for (h, v) in map.iter() {
|
||||
new_map.append(h.clone(), v.clone());
|
||||
}
|
||||
new_map
|
||||
}
|
||||
}
|
||||
|
||||
mod percent_encoding_http {
|
||||
use percent_encoding::{self, define_encode_set};
|
||||
|
||||
|
@@ -8,7 +8,7 @@ macro_rules! STATIC_RESP {
|
||||
($name:ident, $status:expr) => {
|
||||
#[allow(non_snake_case, missing_docs)]
|
||||
pub fn $name() -> ResponseBuilder {
|
||||
Response::build($status)
|
||||
ResponseBuilder::new($status)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@@ -4,13 +4,13 @@ use std::str;
|
||||
use encoding::all::UTF_8;
|
||||
use encoding::label::encoding_from_whatwg_label;
|
||||
use encoding::EncodingRef;
|
||||
use http::{header, HeaderMap};
|
||||
use http::header;
|
||||
use mime::Mime;
|
||||
|
||||
use crate::cookie::Cookie;
|
||||
use crate::error::{ContentTypeError, CookieParseError, ParseError};
|
||||
use crate::extensions::Extensions;
|
||||
use crate::header::Header;
|
||||
use crate::header::{Header, HeaderMap};
|
||||
use crate::payload::Payload;
|
||||
|
||||
struct Cookies(Vec<Cookie<'static>>);
|
||||
|
@@ -1,5 +1,9 @@
|
||||
//! Basic http primitives for actix-net framework.
|
||||
#![allow(clippy::type_complexity, clippy::new_without_default)]
|
||||
#![allow(
|
||||
clippy::type_complexity,
|
||||
clippy::new_without_default,
|
||||
clippy::borrow_interior_mutable_const
|
||||
)]
|
||||
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
@@ -37,7 +41,7 @@ pub use self::message::{Message, RequestHead, ResponseHead};
|
||||
pub use self::payload::{Payload, PayloadStream};
|
||||
pub use self::request::Request;
|
||||
pub use self::response::{Response, ResponseBuilder};
|
||||
pub use self::service::{HttpService, SendError, SendResponse};
|
||||
pub use self::service::HttpService;
|
||||
|
||||
pub mod http {
|
||||
//! Various HTTP related types
|
||||
@@ -45,10 +49,11 @@ pub mod http {
|
||||
// re-exports
|
||||
pub use http::header::{HeaderName, HeaderValue};
|
||||
pub use http::uri::PathAndQuery;
|
||||
pub use http::{uri, Error, HeaderMap, HttpTryFrom, Uri};
|
||||
pub use http::{uri, Error, HttpTryFrom, Uri};
|
||||
pub use http::{Method, StatusCode, Version};
|
||||
|
||||
pub use crate::cookie::{Cookie, CookieBuilder};
|
||||
pub use crate::header::HeaderMap;
|
||||
|
||||
/// Various http headers
|
||||
pub mod header {
|
||||
|
@@ -1,11 +1,12 @@
|
||||
use std::cell::{Ref, RefCell, RefMut};
|
||||
use std::collections::VecDeque;
|
||||
use std::rc::Rc;
|
||||
|
||||
use bitflags::bitflags;
|
||||
use copyless::BoxHelper;
|
||||
|
||||
use crate::extensions::Extensions;
|
||||
use crate::http::{header, HeaderMap, Method, StatusCode, Uri, Version};
|
||||
use crate::header::HeaderMap;
|
||||
use crate::http::{header, Method, StatusCode, Uri, Version};
|
||||
|
||||
/// Represents various types of connection
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
@@ -23,7 +24,8 @@ bitflags! {
|
||||
const CLOSE = 0b0000_0001;
|
||||
const KEEP_ALIVE = 0b0000_0010;
|
||||
const UPGRADE = 0b0000_0100;
|
||||
const NO_CHUNKING = 0b0000_1000;
|
||||
const EXPECT = 0b0000_1000;
|
||||
const NO_CHUNKING = 0b0001_0000;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,6 +147,17 @@ impl RequestHead {
|
||||
self.flags.remove(Flags::NO_CHUNKING);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Request contains `EXPECT` header
|
||||
pub fn expect(&self) -> bool {
|
||||
self.flags.contains(Flags::EXPECT)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub(crate) fn set_expect(&mut self) {
|
||||
self.flags.insert(Flags::EXPECT);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -157,32 +170,20 @@ pub struct ResponseHead {
|
||||
flags: Flags,
|
||||
}
|
||||
|
||||
impl Default for ResponseHead {
|
||||
fn default() -> ResponseHead {
|
||||
impl ResponseHead {
|
||||
/// Create new instance of `ResponseHead` type
|
||||
#[inline]
|
||||
pub fn new(status: StatusCode) -> ResponseHead {
|
||||
ResponseHead {
|
||||
status,
|
||||
version: Version::default(),
|
||||
status: StatusCode::OK,
|
||||
headers: HeaderMap::with_capacity(16),
|
||||
headers: HeaderMap::with_capacity(12),
|
||||
reason: None,
|
||||
flags: Flags::empty(),
|
||||
extensions: RefCell::new(Extensions::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Head for ResponseHead {
|
||||
fn clear(&mut self) {
|
||||
self.reason = None;
|
||||
self.flags = Flags::empty();
|
||||
self.headers.clear();
|
||||
}
|
||||
|
||||
fn pool() -> &'static MessagePool<Self> {
|
||||
RESPONSE_POOL.with(|p| *p)
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseHead {
|
||||
/// Message extensions
|
||||
#[inline]
|
||||
pub fn extensions(&self) -> Ref<Extensions> {
|
||||
@@ -288,7 +289,6 @@ impl ResponseHead {
|
||||
|
||||
pub struct Message<T: Head> {
|
||||
head: Rc<T>,
|
||||
pool: &'static MessagePool<T>,
|
||||
}
|
||||
|
||||
impl<T: Head> Message<T> {
|
||||
@@ -302,7 +302,6 @@ impl<T: Head> Clone for Message<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Message {
|
||||
head: self.head.clone(),
|
||||
pool: self.pool,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -324,39 +323,70 @@ impl<T: Head> std::ops::DerefMut for Message<T> {
|
||||
impl<T: Head> Drop for Message<T> {
|
||||
fn drop(&mut self) {
|
||||
if Rc::strong_count(&self.head) == 1 {
|
||||
self.pool.release(self.head.clone());
|
||||
T::pool().release(self.head.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct BoxedResponseHead {
|
||||
head: Option<Box<ResponseHead>>,
|
||||
}
|
||||
|
||||
impl BoxedResponseHead {
|
||||
/// Get new message from the pool of objects
|
||||
pub fn new(status: StatusCode) -> Self {
|
||||
RESPONSE_POOL.with(|p| p.get_message(status))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::Deref for BoxedResponseHead {
|
||||
type Target = ResponseHead;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.head.as_ref().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for BoxedResponseHead {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.head.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for BoxedResponseHead {
|
||||
fn drop(&mut self) {
|
||||
RESPONSE_POOL.with(|p| p.release(self.head.take().unwrap()))
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Request's objects pool
|
||||
pub struct MessagePool<T: Head>(RefCell<VecDeque<Rc<T>>>);
|
||||
pub struct MessagePool<T: Head>(RefCell<Vec<Rc<T>>>);
|
||||
|
||||
#[doc(hidden)]
|
||||
/// Request's objects pool
|
||||
pub struct BoxedResponsePool(RefCell<Vec<Box<ResponseHead>>>);
|
||||
|
||||
thread_local!(static REQUEST_POOL: &'static MessagePool<RequestHead> = MessagePool::<RequestHead>::create());
|
||||
thread_local!(static RESPONSE_POOL: &'static MessagePool<ResponseHead> = MessagePool::<ResponseHead>::create());
|
||||
thread_local!(static RESPONSE_POOL: &'static BoxedResponsePool = BoxedResponsePool::create());
|
||||
|
||||
impl<T: Head> MessagePool<T> {
|
||||
fn create() -> &'static MessagePool<T> {
|
||||
let pool = MessagePool(RefCell::new(VecDeque::with_capacity(128)));
|
||||
let pool = MessagePool(RefCell::new(Vec::with_capacity(128)));
|
||||
Box::leak(Box::new(pool))
|
||||
}
|
||||
|
||||
/// Get message from the pool
|
||||
#[inline]
|
||||
fn get_message(&'static self) -> Message<T> {
|
||||
if let Some(mut msg) = self.0.borrow_mut().pop_front() {
|
||||
if let Some(mut msg) = self.0.borrow_mut().pop() {
|
||||
if let Some(r) = Rc::get_mut(&mut msg) {
|
||||
r.clear();
|
||||
}
|
||||
Message {
|
||||
head: msg,
|
||||
pool: self,
|
||||
}
|
||||
Message { head: msg }
|
||||
} else {
|
||||
Message {
|
||||
head: Rc::new(T::default()),
|
||||
pool: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -366,7 +396,39 @@ impl<T: Head> MessagePool<T> {
|
||||
fn release(&self, msg: Rc<T>) {
|
||||
let v = &mut self.0.borrow_mut();
|
||||
if v.len() < 128 {
|
||||
v.push_front(msg);
|
||||
v.push(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BoxedResponsePool {
|
||||
fn create() -> &'static BoxedResponsePool {
|
||||
let pool = BoxedResponsePool(RefCell::new(Vec::with_capacity(128)));
|
||||
Box::leak(Box::new(pool))
|
||||
}
|
||||
|
||||
/// Get message from the pool
|
||||
#[inline]
|
||||
fn get_message(&'static self, status: StatusCode) -> BoxedResponseHead {
|
||||
if let Some(mut head) = self.0.borrow_mut().pop() {
|
||||
head.reason = None;
|
||||
head.status = status;
|
||||
head.headers.clear();
|
||||
head.flags = Flags::empty();
|
||||
BoxedResponseHead { head: Some(head) }
|
||||
} else {
|
||||
BoxedResponseHead {
|
||||
head: Some(Box::alloc().init(ResponseHead::new(status))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Release request instance
|
||||
fn release(&self, msg: Box<ResponseHead>) {
|
||||
let v = &mut self.0.borrow_mut();
|
||||
if v.len() < 128 {
|
||||
v.push(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,10 @@
|
||||
use std::cell::{Ref, RefMut};
|
||||
use std::fmt;
|
||||
|
||||
use http::{header, HeaderMap, Method, Uri, Version};
|
||||
use http::{header, Method, Uri, Version};
|
||||
|
||||
use crate::extensions::Extensions;
|
||||
use crate::header::HeaderMap;
|
||||
use crate::httpmessage::HttpMessage;
|
||||
use crate::message::{Message, RequestHead};
|
||||
use crate::payload::{Payload, PayloadStream};
|
||||
@@ -161,7 +162,7 @@ impl<P> fmt::Debug for Request<P> {
|
||||
writeln!(f, " query: ?{:?}", q)?;
|
||||
}
|
||||
writeln!(f, " headers:")?;
|
||||
for (key, val) in self.headers().iter() {
|
||||
for (key, val) in self.headers() {
|
||||
writeln!(f, " {:?}: {:?}", key, val)?;
|
||||
}
|
||||
Ok(())
|
||||
|
@@ -1,13 +1,11 @@
|
||||
//! Http response
|
||||
use std::cell::{Ref, RefMut};
|
||||
use std::io::Write;
|
||||
use std::{fmt, str};
|
||||
use std::{fmt, io, str};
|
||||
|
||||
use bytes::{BufMut, Bytes, BytesMut};
|
||||
use futures::future::{ok, FutureResult, IntoFuture};
|
||||
use futures::Stream;
|
||||
use http::header::{self, HeaderName, HeaderValue};
|
||||
use http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode};
|
||||
use serde::Serialize;
|
||||
use serde_json;
|
||||
|
||||
@@ -16,11 +14,13 @@ use crate::cookie::{Cookie, CookieJar};
|
||||
use crate::error::Error;
|
||||
use crate::extensions::Extensions;
|
||||
use crate::header::{Header, IntoHeaderValue};
|
||||
use crate::message::{ConnectionType, Message, ResponseHead};
|
||||
use crate::http::header::{self, HeaderName, HeaderValue};
|
||||
use crate::http::{Error as HttpError, HeaderMap, HttpTryFrom, StatusCode};
|
||||
use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead};
|
||||
|
||||
/// An HTTP Response
|
||||
pub struct Response<B = Body> {
|
||||
head: Message<ResponseHead>,
|
||||
head: BoxedResponseHead,
|
||||
body: ResponseBody<B>,
|
||||
error: Option<Error>,
|
||||
}
|
||||
@@ -41,11 +41,8 @@ impl Response<Body> {
|
||||
/// Constructs a response
|
||||
#[inline]
|
||||
pub fn new(status: StatusCode) -> Response {
|
||||
let mut head: Message<ResponseHead> = Message::new();
|
||||
head.status = status;
|
||||
|
||||
Response {
|
||||
head,
|
||||
head: BoxedResponseHead::new(status),
|
||||
body: ResponseBody::Body(Body::Empty),
|
||||
error: None,
|
||||
}
|
||||
@@ -55,8 +52,12 @@ impl Response<Body> {
|
||||
#[inline]
|
||||
pub fn from_error(error: Error) -> Response {
|
||||
let mut resp = error.as_response_error().error_response();
|
||||
let mut buf = BytesMut::new();
|
||||
let _ = write!(Writer(&mut buf), "{}", error);
|
||||
resp.headers_mut()
|
||||
.insert(header::CONTENT_TYPE, HeaderValue::from_static("text/plain"));
|
||||
resp.error = Some(error);
|
||||
resp
|
||||
resp.set_body(Body::from(buf))
|
||||
}
|
||||
|
||||
/// Convert response to response with body
|
||||
@@ -74,6 +75,16 @@ impl Response<Body> {
|
||||
}
|
||||
|
||||
impl<B> Response<B> {
|
||||
/// Constructs a response with body
|
||||
#[inline]
|
||||
pub fn with_body(status: StatusCode, body: B) -> Response<B> {
|
||||
Response {
|
||||
head: BoxedResponseHead::new(status),
|
||||
body: ResponseBody::Body(body),
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Http message part of the response
|
||||
pub fn head(&self) -> &ResponseHead {
|
||||
@@ -86,18 +97,6 @@ impl<B> Response<B> {
|
||||
&mut *self.head
|
||||
}
|
||||
|
||||
/// Constructs a response with body
|
||||
#[inline]
|
||||
pub fn with_body(status: StatusCode, body: B) -> Response<B> {
|
||||
let mut head: Message<ResponseHead> = Message::new();
|
||||
head.status = status;
|
||||
Response {
|
||||
head,
|
||||
body: ResponseBody::Body(body),
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// The source `error` for this response
|
||||
#[inline]
|
||||
pub fn error(&self) -> Option<&Error> {
|
||||
@@ -132,7 +131,7 @@ impl<B> Response<B> {
|
||||
#[inline]
|
||||
pub fn cookies(&self) -> CookieIter {
|
||||
CookieIter {
|
||||
iter: self.head.headers.get_all(header::SET_COOKIE).iter(),
|
||||
iter: self.head.headers.get_all(header::SET_COOKIE),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +153,6 @@ impl<B> Response<B> {
|
||||
let h = &mut self.head.headers;
|
||||
let vals: Vec<HeaderValue> = h
|
||||
.get_all(header::SET_COOKIE)
|
||||
.iter()
|
||||
.map(|v| v.to_owned())
|
||||
.collect();
|
||||
h.remove(header::SET_COOKIE);
|
||||
@@ -204,7 +202,7 @@ impl<B> Response<B> {
|
||||
}
|
||||
|
||||
/// Set a body
|
||||
pub(crate) fn set_body<B2>(self, body: B2) -> Response<B2> {
|
||||
pub fn set_body<B2>(self, body: B2) -> Response<B2> {
|
||||
Response {
|
||||
head: self.head,
|
||||
body: ResponseBody::Body(body),
|
||||
@@ -213,7 +211,7 @@ impl<B> Response<B> {
|
||||
}
|
||||
|
||||
/// Drop request's body
|
||||
pub(crate) fn drop_body(self) -> Response<()> {
|
||||
pub fn drop_body(self) -> Response<()> {
|
||||
Response {
|
||||
head: self.head,
|
||||
body: ResponseBody::Body(()),
|
||||
@@ -282,7 +280,7 @@ impl IntoFuture for Response {
|
||||
}
|
||||
|
||||
pub struct CookieIter<'a> {
|
||||
iter: header::ValueIter<'a, HeaderValue>,
|
||||
iter: header::GetAll<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for CookieIter<'a> {
|
||||
@@ -299,24 +297,34 @@ impl<'a> Iterator for CookieIter<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Writer<'a>(pub &'a mut BytesMut);
|
||||
|
||||
impl<'a> io::Write for Writer<'a> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.0.extend_from_slice(buf);
|
||||
Ok(buf.len())
|
||||
}
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// An HTTP response builder
|
||||
///
|
||||
/// This type can be used to construct an instance of `Response` through a
|
||||
/// builder-like pattern.
|
||||
pub struct ResponseBuilder {
|
||||
head: Option<Message<ResponseHead>>,
|
||||
head: Option<BoxedResponseHead>,
|
||||
err: Option<HttpError>,
|
||||
cookies: Option<CookieJar>,
|
||||
}
|
||||
|
||||
impl ResponseBuilder {
|
||||
#[inline]
|
||||
/// Create response builder
|
||||
pub fn new(status: StatusCode) -> Self {
|
||||
let mut head: Message<ResponseHead> = Message::new();
|
||||
head.status = status;
|
||||
|
||||
ResponseBuilder {
|
||||
head: Some(head),
|
||||
head: Some(BoxedResponseHead::new(status)),
|
||||
err: None,
|
||||
cookies: None,
|
||||
}
|
||||
@@ -540,15 +548,13 @@ impl ResponseBuilder {
|
||||
/// }
|
||||
/// ```
|
||||
pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self {
|
||||
{
|
||||
if self.cookies.is_none() {
|
||||
self.cookies = Some(CookieJar::new())
|
||||
}
|
||||
let jar = self.cookies.as_mut().unwrap();
|
||||
let cookie = cookie.clone().into_owned();
|
||||
jar.add_original(cookie.clone());
|
||||
jar.remove(cookie);
|
||||
if self.cookies.is_none() {
|
||||
self.cookies = Some(CookieJar::new())
|
||||
}
|
||||
let jar = self.cookies.as_mut().unwrap();
|
||||
let cookie = cookie.clone().into_owned();
|
||||
jar.add_original(cookie.clone());
|
||||
jar.remove(cookie);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -590,6 +596,7 @@ impl ResponseBuilder {
|
||||
head.extensions.borrow_mut()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Set a body and generate `Response`.
|
||||
///
|
||||
/// `ResponseBuilder` can not be used after this call.
|
||||
@@ -610,9 +617,7 @@ impl ResponseBuilder {
|
||||
if let Some(ref jar) = self.cookies {
|
||||
for cookie in jar.delta() {
|
||||
match HeaderValue::from_str(&cookie.to_string()) {
|
||||
Ok(val) => {
|
||||
let _ = response.headers.append(header::SET_COOKIE, val);
|
||||
}
|
||||
Ok(val) => response.headers.append(header::SET_COOKIE, val),
|
||||
Err(e) => return Response::from(Error::from(e)).into_body(),
|
||||
};
|
||||
}
|
||||
@@ -637,6 +642,7 @@ impl ResponseBuilder {
|
||||
self.body(Body::from_message(BodyStream::new(stream)))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Set a json body and generate `Response`
|
||||
///
|
||||
/// `ResponseBuilder` can not be used after this call.
|
||||
@@ -685,13 +691,13 @@ impl ResponseBuilder {
|
||||
|
||||
#[inline]
|
||||
fn parts<'a>(
|
||||
parts: &'a mut Option<Message<ResponseHead>>,
|
||||
parts: &'a mut Option<BoxedResponseHead>,
|
||||
err: &Option<HttpError>,
|
||||
) -> Option<&'a mut Message<ResponseHead>> {
|
||||
) -> Option<&'a mut ResponseHead> {
|
||||
if err.is_some() {
|
||||
return None;
|
||||
}
|
||||
parts.as_mut()
|
||||
parts.as_mut().map(|r| &mut **r)
|
||||
}
|
||||
|
||||
/// Convert `Response` to a `ResponseBuilder`. Body get dropped.
|
||||
@@ -724,7 +730,7 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder {
|
||||
let mut jar: Option<CookieJar> = None;
|
||||
|
||||
let cookies = CookieIter {
|
||||
iter: head.headers.get_all(header::SET_COOKIE).iter(),
|
||||
iter: head.headers.get_all(header::SET_COOKIE),
|
||||
};
|
||||
for c in cookies {
|
||||
if let Some(ref mut j) = jar {
|
||||
@@ -736,11 +742,12 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
let mut msg: Message<ResponseHead> = Message::new();
|
||||
let mut msg = BoxedResponseHead::new(head.status);
|
||||
msg.version = head.version;
|
||||
msg.status = head.status;
|
||||
msg.reason = head.reason;
|
||||
msg.headers = head.headers.clone();
|
||||
for (k, v) in &head.headers {
|
||||
msg.headers.append(k.clone(), v.clone());
|
||||
}
|
||||
msg.no_chunking(!head.chunked());
|
||||
|
||||
ResponseBuilder {
|
||||
@@ -829,7 +836,7 @@ impl From<BytesMut> for Response {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::body::Body;
|
||||
use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE};
|
||||
use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE, SET_COOKIE};
|
||||
|
||||
#[test]
|
||||
fn test_debug() {
|
||||
@@ -860,13 +867,12 @@ mod tests {
|
||||
.max_age(time::Duration::days(1))
|
||||
.finish(),
|
||||
)
|
||||
.del_cookie(&cookies[0])
|
||||
.del_cookie(&cookies[1])
|
||||
.finish();
|
||||
|
||||
let mut val: Vec<_> = resp
|
||||
.headers()
|
||||
.get_all("Set-Cookie")
|
||||
.iter()
|
||||
.get_all(SET_COOKIE)
|
||||
.map(|v| v.to_str().unwrap().to_owned())
|
||||
.collect();
|
||||
val.sort();
|
||||
@@ -895,9 +901,9 @@ mod tests {
|
||||
|
||||
let mut iter = r.cookies();
|
||||
let v = iter.next().unwrap();
|
||||
assert_eq!((v.name(), v.value()), ("original", "val100"));
|
||||
let v = iter.next().unwrap();
|
||||
assert_eq!((v.name(), v.value()), ("cookie3", "val300"));
|
||||
let v = iter.next().unwrap();
|
||||
assert_eq!((v.name(), v.value()), ("original", "val100"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1033,6 +1039,7 @@ mod tests {
|
||||
resp.headers().get(CONTENT_TYPE).unwrap(),
|
||||
HeaderValue::from_static("application/octet-stream")
|
||||
);
|
||||
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(resp.body().get_ref(), b"test");
|
||||
}
|
||||
|
@@ -1,37 +1,37 @@
|
||||
use std::fmt::Debug;
|
||||
use std::marker::PhantomData;
|
||||
use std::{fmt, io};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed, FramedParts};
|
||||
use actix_codec::{AsyncRead, AsyncWrite};
|
||||
use actix_server_config::{Io as ServerIo, Protocol, ServerConfig as SrvConfig};
|
||||
use actix_service::{IntoNewService, NewService, Service};
|
||||
use actix_utils::cloneable::CloneableService;
|
||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
use futures::{try_ready, Async, Future, IntoFuture, Poll};
|
||||
use h2::server::{self, Handshake};
|
||||
use log::error;
|
||||
|
||||
use crate::body::MessageBody;
|
||||
use crate::builder::HttpServiceBuilder;
|
||||
use crate::config::{KeepAlive, ServiceConfig};
|
||||
use crate::error::DispatchError;
|
||||
use crate::error::{DispatchError, Error};
|
||||
use crate::request::Request;
|
||||
use crate::response::Response;
|
||||
use crate::{h1, h2::Dispatcher};
|
||||
|
||||
/// `NewService` HTTP1.1/HTTP2 transport implementation
|
||||
pub struct HttpService<T, P, S, B> {
|
||||
pub struct HttpService<T, P, S, B, X = h1::ExpectHandler> {
|
||||
srv: S,
|
||||
cfg: ServiceConfig,
|
||||
expect: X,
|
||||
_t: PhantomData<(T, P, B)>,
|
||||
}
|
||||
|
||||
impl<T, S, B> HttpService<T, (), S, B>
|
||||
where
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Service: 'static,
|
||||
S::Error: Debug + 'static,
|
||||
S::Error: Into<Error>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
<S::Service as Service>::Future: 'static,
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
/// Create builder for `HttpService` instance.
|
||||
@@ -43,9 +43,10 @@ where
|
||||
impl<T, P, S, B> HttpService<T, P, S, B>
|
||||
where
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Service: 'static,
|
||||
S::Error: Debug + 'static,
|
||||
S::Error: Into<Error>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
<S::Service as Service>::Future: 'static,
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
/// Create new `HttpService` instance.
|
||||
@@ -55,6 +56,7 @@ where
|
||||
HttpService {
|
||||
cfg,
|
||||
srv: service.into_new_service(),
|
||||
expect: h1::ExpectHandler,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
@@ -67,30 +69,65 @@ where
|
||||
HttpService {
|
||||
cfg,
|
||||
srv: service.into_new_service(),
|
||||
expect: h1::ExpectHandler,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P, S, B> NewService<SrvConfig> for HttpService<T, P, S, B>
|
||||
impl<T, P, S, B, X> HttpService<T, P, S, B, X>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + 'static,
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Service: 'static,
|
||||
S::Error: Debug,
|
||||
S::Error: Into<Error>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
B: MessageBody,
|
||||
{
|
||||
/// Provide service for `EXPECT: 100-Continue` support.
|
||||
///
|
||||
/// Service get called with request that contains `EXPECT` header.
|
||||
/// Service must return request in case of success, in that case
|
||||
/// request will be forwarded to main service.
|
||||
pub fn expect<U>(self, expect: U) -> HttpService<T, P, S, B, U>
|
||||
where
|
||||
U: NewService<Request = Request, Response = Request>,
|
||||
U::Error: Into<Error>,
|
||||
U::InitError: fmt::Debug,
|
||||
{
|
||||
HttpService {
|
||||
expect,
|
||||
cfg: self.cfg,
|
||||
srv: self.srv,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P, S, B, X> NewService<SrvConfig> for HttpService<T, P, S, B, X>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
<S::Service as Service>::Future: 'static,
|
||||
B: MessageBody + 'static,
|
||||
X: NewService<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
X::InitError: fmt::Debug,
|
||||
{
|
||||
type Request = ServerIo<T, P>;
|
||||
type Response = ();
|
||||
type Error = DispatchError;
|
||||
type InitError = S::InitError;
|
||||
type Service = HttpServiceHandler<T, P, S::Service, B>;
|
||||
type Future = HttpServiceResponse<T, P, S, B>;
|
||||
type InitError = ();
|
||||
type Service = HttpServiceHandler<T, P, S::Service, B, X::Service>;
|
||||
type Future = HttpServiceResponse<T, P, S, B, X>;
|
||||
|
||||
fn new_service(&self, cfg: &SrvConfig) -> Self::Future {
|
||||
HttpServiceResponse {
|
||||
fut: self.srv.new_service(cfg).into_future(),
|
||||
fut_ex: Some(self.expect.new_service(&())),
|
||||
expect: None,
|
||||
cfg: Some(self.cfg.clone()),
|
||||
_t: PhantomData,
|
||||
}
|
||||
@@ -98,74 +135,122 @@ where
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct HttpServiceResponse<T, P, S: NewService<SrvConfig>, B> {
|
||||
fut: <S::Future as IntoFuture>::Future,
|
||||
pub struct HttpServiceResponse<T, P, S: NewService<SrvConfig>, B, X: NewService> {
|
||||
fut: S::Future,
|
||||
fut_ex: Option<X::Future>,
|
||||
expect: Option<X::Service>,
|
||||
cfg: Option<ServiceConfig>,
|
||||
_t: PhantomData<(T, P, B)>,
|
||||
}
|
||||
|
||||
impl<T, P, S, B> Future for HttpServiceResponse<T, P, S, B>
|
||||
impl<T, P, S, B, X> Future for HttpServiceResponse<T, P, S, B, X>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: NewService<SrvConfig, Request = Request>,
|
||||
S::Service: 'static,
|
||||
S::Error: Into<Error>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
S::Error: Debug,
|
||||
<S::Service as Service>::Future: 'static,
|
||||
B: MessageBody + 'static,
|
||||
X: NewService<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
X::InitError: fmt::Debug,
|
||||
{
|
||||
type Item = HttpServiceHandler<T, P, S::Service, B>;
|
||||
type Error = S::InitError;
|
||||
type Item = HttpServiceHandler<T, P, S::Service, B, X::Service>;
|
||||
type Error = ();
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
let service = try_ready!(self.fut.poll());
|
||||
if let Some(ref mut fut) = self.fut_ex {
|
||||
let expect = try_ready!(fut
|
||||
.poll()
|
||||
.map_err(|e| log::error!("Init http service error: {:?}", e)));
|
||||
self.expect = Some(expect);
|
||||
self.fut_ex.take();
|
||||
}
|
||||
|
||||
let service = try_ready!(self
|
||||
.fut
|
||||
.poll()
|
||||
.map_err(|e| log::error!("Init http service error: {:?}", e)));
|
||||
Ok(Async::Ready(HttpServiceHandler::new(
|
||||
self.cfg.take().unwrap(),
|
||||
service,
|
||||
self.expect.take().unwrap(),
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/// `Service` implementation for http transport
|
||||
pub struct HttpServiceHandler<T, P, S: 'static, B> {
|
||||
pub struct HttpServiceHandler<T, P, S, B, X> {
|
||||
srv: CloneableService<S>,
|
||||
expect: CloneableService<X>,
|
||||
cfg: ServiceConfig,
|
||||
_t: PhantomData<(T, P, B)>,
|
||||
_t: PhantomData<(T, P, B, X)>,
|
||||
}
|
||||
|
||||
impl<T, P, S, B> HttpServiceHandler<T, P, S, B>
|
||||
impl<T, P, S, B, X> HttpServiceHandler<T, P, S, B, X>
|
||||
where
|
||||
S: Service<Request = Request> + 'static,
|
||||
S::Error: Debug,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Future: 'static,
|
||||
S::Response: Into<Response<B>>,
|
||||
B: MessageBody + 'static,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
{
|
||||
fn new(cfg: ServiceConfig, srv: S) -> HttpServiceHandler<T, P, S, B> {
|
||||
fn new(cfg: ServiceConfig, srv: S, expect: X) -> HttpServiceHandler<T, P, S, B, X> {
|
||||
HttpServiceHandler {
|
||||
cfg,
|
||||
srv: CloneableService::new(srv),
|
||||
expect: CloneableService::new(expect),
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, P, S, B> Service for HttpServiceHandler<T, P, S, B>
|
||||
impl<T, P, S, B, X> Service for HttpServiceHandler<T, P, S, B, X>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + 'static,
|
||||
S: Service<Request = Request> + 'static,
|
||||
S::Error: Debug,
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Future: 'static,
|
||||
S::Response: Into<Response<B>>,
|
||||
B: MessageBody + 'static,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
{
|
||||
type Request = ServerIo<T, P>;
|
||||
type Response = ();
|
||||
type Error = DispatchError;
|
||||
type Future = HttpServiceHandlerResponse<T, S, B>;
|
||||
type Future = HttpServiceHandlerResponse<T, S, B, X>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.srv.poll_ready().map_err(|e| {
|
||||
error!("Service readiness error: {:?}", e);
|
||||
DispatchError::Service
|
||||
})
|
||||
let ready = self
|
||||
.expect
|
||||
.poll_ready()
|
||||
.map_err(|e| {
|
||||
let e = e.into();
|
||||
log::error!("Http service readiness error: {:?}", e);
|
||||
DispatchError::Service(e)
|
||||
})?
|
||||
.is_ready();
|
||||
|
||||
let ready = self
|
||||
.srv
|
||||
.poll_ready()
|
||||
.map_err(|e| {
|
||||
let e = e.into();
|
||||
log::error!("Http service readiness error: {:?}", e);
|
||||
DispatchError::Service(e)
|
||||
})?
|
||||
.is_ready()
|
||||
&& ready;
|
||||
|
||||
if ready {
|
||||
Ok(Async::Ready(()))
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Self::Request) -> Self::Future {
|
||||
@@ -189,6 +274,7 @@ where
|
||||
io,
|
||||
self.cfg.clone(),
|
||||
self.srv.clone(),
|
||||
self.expect.clone(),
|
||||
)),
|
||||
},
|
||||
_ => HttpServiceHandlerResponse {
|
||||
@@ -197,43 +283,63 @@ where
|
||||
BytesMut::with_capacity(14),
|
||||
self.cfg.clone(),
|
||||
self.srv.clone(),
|
||||
self.expect.clone(),
|
||||
))),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum State<T, S: Service<Request = Request> + 'static, B: MessageBody>
|
||||
enum State<T, S, B, X>
|
||||
where
|
||||
S::Error: fmt::Debug,
|
||||
T: AsyncRead + AsyncWrite + 'static,
|
||||
S: Service<Request = Request>,
|
||||
S::Future: 'static,
|
||||
S::Error: Into<Error>,
|
||||
T: AsyncRead + AsyncWrite,
|
||||
B: MessageBody,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
{
|
||||
H1(h1::Dispatcher<T, S, B>),
|
||||
H1(h1::Dispatcher<T, S, B, X>),
|
||||
H2(Dispatcher<Io<T>, S, B>),
|
||||
Unknown(Option<(T, BytesMut, ServiceConfig, CloneableService<S>)>),
|
||||
Unknown(
|
||||
Option<(
|
||||
T,
|
||||
BytesMut,
|
||||
ServiceConfig,
|
||||
CloneableService<S>,
|
||||
CloneableService<X>,
|
||||
)>,
|
||||
),
|
||||
Handshake(Option<(Handshake<Io<T>, Bytes>, ServiceConfig, CloneableService<S>)>),
|
||||
}
|
||||
|
||||
pub struct HttpServiceHandlerResponse<T, S, B>
|
||||
pub struct HttpServiceHandlerResponse<T, S, B, X>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + 'static,
|
||||
S: Service<Request = Request> + 'static,
|
||||
S::Error: Debug,
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Future: 'static,
|
||||
S::Response: Into<Response<B>>,
|
||||
B: MessageBody + 'static,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
{
|
||||
state: State<T, S, B>,
|
||||
state: State<T, S, B, X>,
|
||||
}
|
||||
|
||||
const HTTP2_PREFACE: [u8; 14] = *b"PRI * HTTP/2.0";
|
||||
|
||||
impl<T, S, B> Future for HttpServiceHandlerResponse<T, S, B>
|
||||
impl<T, S, B, X> Future for HttpServiceHandlerResponse<T, S, B, X>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
S: Service<Request = Request> + 'static,
|
||||
S::Error: Debug,
|
||||
S: Service<Request = Request>,
|
||||
S::Error: Into<Error>,
|
||||
S::Future: 'static,
|
||||
S::Response: Into<Response<B>>,
|
||||
B: MessageBody,
|
||||
X: Service<Request = Request, Response = Request>,
|
||||
X::Error: Into<Error>,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = DispatchError;
|
||||
@@ -260,7 +366,7 @@ where
|
||||
} else {
|
||||
panic!()
|
||||
}
|
||||
let (io, buf, cfg, srv) = data.take().unwrap();
|
||||
let (io, buf, cfg, srv, expect) = data.take().unwrap();
|
||||
if buf[..14] == HTTP2_PREFACE[..] {
|
||||
let io = Io {
|
||||
inner: io,
|
||||
@@ -269,13 +375,15 @@ where
|
||||
self.state =
|
||||
State::Handshake(Some((server::handshake(io), cfg, srv)));
|
||||
} else {
|
||||
let framed = Framed::from_parts(FramedParts::with_read_buf(
|
||||
self.state = State::H1(h1::Dispatcher::with_timeout(
|
||||
io,
|
||||
h1::Codec::new(cfg.clone()),
|
||||
cfg,
|
||||
buf,
|
||||
));
|
||||
self.state =
|
||||
State::H1(h1::Dispatcher::with_timeout(framed, cfg, None, srv))
|
||||
None,
|
||||
srv,
|
||||
expect,
|
||||
))
|
||||
}
|
||||
self.poll()
|
||||
}
|
||||
@@ -331,13 +439,13 @@ impl<T: io::Write> io::Write for Io<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncRead + 'static> AsyncRead for Io<T> {
|
||||
impl<T: AsyncRead> AsyncRead for Io<T> {
|
||||
unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool {
|
||||
self.inner.prepare_uninitialized_buffer(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsyncWrite + 'static> AsyncWrite for Io<T> {
|
||||
impl<T: AsyncWrite> AsyncWrite for Io<T> {
|
||||
fn shutdown(&mut self) -> Poll<(), io::Error> {
|
||||
self.inner.shutdown()
|
||||
}
|
@@ -1,5 +0,0 @@
|
||||
mod senderror;
|
||||
mod service;
|
||||
|
||||
pub use self::senderror::{SendError, SendResponse};
|
||||
pub use self::service::HttpService;
|
@@ -1,241 +0,0 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
use actix_service::{NewService, Service};
|
||||
use futures::future::{ok, Either, FutureResult};
|
||||
use futures::{Async, Future, Poll, Sink};
|
||||
|
||||
use crate::body::{BodySize, MessageBody, ResponseBody};
|
||||
use crate::error::{Error, ResponseError};
|
||||
use crate::h1::{Codec, Message};
|
||||
use crate::response::Response;
|
||||
|
||||
pub struct SendError<T, R, E>(PhantomData<(T, R, E)>);
|
||||
|
||||
impl<T, R, E> Default for SendError<T, R, E>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
E: ResponseError,
|
||||
{
|
||||
fn default() -> Self {
|
||||
SendError(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, R, E> NewService for SendError<T, R, E>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
E: ResponseError,
|
||||
{
|
||||
type Request = Result<R, (E, Framed<T, Codec>)>;
|
||||
type Response = R;
|
||||
type Error = (E, Framed<T, Codec>);
|
||||
type InitError = ();
|
||||
type Service = SendError<T, R, E>;
|
||||
type Future = FutureResult<Self::Service, Self::InitError>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
ok(SendError(PhantomData))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, R, E> Service for SendError<T, R, E>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
E: ResponseError,
|
||||
{
|
||||
type Request = Result<R, (E, Framed<T, Codec>)>;
|
||||
type Response = R;
|
||||
type Error = (E, Framed<T, Codec>);
|
||||
type Future = Either<FutureResult<R, (E, Framed<T, Codec>)>, SendErrorFut<T, R, E>>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Result<R, (E, Framed<T, Codec>)>) -> Self::Future {
|
||||
match req {
|
||||
Ok(r) => Either::A(ok(r)),
|
||||
Err((e, framed)) => {
|
||||
let res = e.error_response().set_body(format!("{}", e));
|
||||
let (res, _body) = res.replace_body(());
|
||||
Either::B(SendErrorFut {
|
||||
framed: Some(framed),
|
||||
res: Some((res, BodySize::Empty).into()),
|
||||
err: Some(e),
|
||||
_t: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SendErrorFut<T, R, E> {
|
||||
res: Option<Message<(Response<()>, BodySize)>>,
|
||||
framed: Option<Framed<T, Codec>>,
|
||||
err: Option<E>,
|
||||
_t: PhantomData<R>,
|
||||
}
|
||||
|
||||
impl<T, R, E> Future for SendErrorFut<T, R, E>
|
||||
where
|
||||
E: ResponseError,
|
||||
T: AsyncRead + AsyncWrite,
|
||||
{
|
||||
type Item = R;
|
||||
type Error = (E, Framed<T, Codec>);
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
if let Some(res) = self.res.take() {
|
||||
if self.framed.as_mut().unwrap().force_send(res).is_err() {
|
||||
return Err((self.err.take().unwrap(), self.framed.take().unwrap()));
|
||||
}
|
||||
}
|
||||
match self.framed.as_mut().unwrap().poll_complete() {
|
||||
Ok(Async::Ready(_)) => {
|
||||
Err((self.err.take().unwrap(), self.framed.take().unwrap()))
|
||||
}
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Err(_) => Err((self.err.take().unwrap(), self.framed.take().unwrap())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SendResponse<T, B>(PhantomData<(T, B)>);
|
||||
|
||||
impl<T, B> Default for SendResponse<T, B> {
|
||||
fn default() -> Self {
|
||||
SendResponse(PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, B> SendResponse<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
B: MessageBody,
|
||||
{
|
||||
pub fn send(
|
||||
framed: Framed<T, Codec>,
|
||||
res: Response<B>,
|
||||
) -> impl Future<Item = Framed<T, Codec>, Error = Error> {
|
||||
// extract body from response
|
||||
let (res, body) = res.replace_body(());
|
||||
|
||||
// write response
|
||||
SendResponseFut {
|
||||
res: Some(Message::Item((res, body.length()))),
|
||||
body: Some(body),
|
||||
framed: Some(framed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, B> NewService for SendResponse<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
B: MessageBody,
|
||||
{
|
||||
type Request = (Response<B>, Framed<T, Codec>);
|
||||
type Response = Framed<T, Codec>;
|
||||
type Error = Error;
|
||||
type InitError = ();
|
||||
type Service = SendResponse<T, B>;
|
||||
type Future = FutureResult<Self::Service, Self::InitError>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
ok(SendResponse(PhantomData))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, B> Service for SendResponse<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
B: MessageBody,
|
||||
{
|
||||
type Request = (Response<B>, Framed<T, Codec>);
|
||||
type Response = Framed<T, Codec>;
|
||||
type Error = Error;
|
||||
type Future = SendResponseFut<T, B>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, (res, framed): (Response<B>, Framed<T, Codec>)) -> Self::Future {
|
||||
let (res, body) = res.replace_body(());
|
||||
SendResponseFut {
|
||||
res: Some(Message::Item((res, body.length()))),
|
||||
body: Some(body),
|
||||
framed: Some(framed),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SendResponseFut<T, B> {
|
||||
res: Option<Message<(Response<()>, BodySize)>>,
|
||||
body: Option<ResponseBody<B>>,
|
||||
framed: Option<Framed<T, Codec>>,
|
||||
}
|
||||
|
||||
impl<T, B> Future for SendResponseFut<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
B: MessageBody,
|
||||
{
|
||||
type Item = Framed<T, Codec>;
|
||||
type Error = Error;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
loop {
|
||||
let mut body_ready = self.body.is_some();
|
||||
let framed = self.framed.as_mut().unwrap();
|
||||
|
||||
// send body
|
||||
if self.res.is_none() && self.body.is_some() {
|
||||
while body_ready && self.body.is_some() && !framed.is_write_buf_full() {
|
||||
match self.body.as_mut().unwrap().poll_next()? {
|
||||
Async::Ready(item) => {
|
||||
// body is done
|
||||
if item.is_none() {
|
||||
let _ = self.body.take();
|
||||
}
|
||||
framed.force_send(Message::Chunk(item))?;
|
||||
}
|
||||
Async::NotReady => body_ready = false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// flush write buffer
|
||||
if !framed.is_write_buf_empty() {
|
||||
match framed.poll_complete()? {
|
||||
Async::Ready(_) => {
|
||||
if body_ready {
|
||||
continue;
|
||||
} else {
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
}
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
|
||||
// send response
|
||||
if let Some(res) = self.res.take() {
|
||||
framed.force_send(res)?;
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.body.is_some() {
|
||||
if body_ready {
|
||||
continue;
|
||||
} else {
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(Async::Ready(self.framed.take().unwrap()))
|
||||
}
|
||||
}
|
@@ -4,10 +4,11 @@ use std::str::FromStr;
|
||||
|
||||
use bytes::Bytes;
|
||||
use http::header::{self, HeaderName, HeaderValue};
|
||||
use http::{HeaderMap, HttpTryFrom, Method, Uri, Version};
|
||||
use http::{HttpTryFrom, Method, Uri, Version};
|
||||
use percent_encoding::{percent_encode, USERINFO_ENCODE_SET};
|
||||
|
||||
use crate::cookie::{Cookie, CookieJar};
|
||||
use crate::header::HeaderMap;
|
||||
use crate::header::{Header, IntoHeaderValue};
|
||||
use crate::payload::Payload;
|
||||
use crate::Request;
|
||||
@@ -177,6 +178,6 @@ impl TestRequest {
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn parts<'a>(parts: &'a mut Option<Inner>) -> &'a mut Inner {
|
||||
fn parts(parts: &mut Option<Inner>) -> &mut Inner {
|
||||
parts.as_mut().expect("cannot reuse test request builder")
|
||||
}
|
||||
|
@@ -61,7 +61,9 @@ fn test_connection_close() {
|
||||
.finish(|_| ok::<_, ()>(Response::Ok().body(STR)))
|
||||
.map(|_| ())
|
||||
});
|
||||
println!("REQ: {:?}", srv.get("/").force_close());
|
||||
let response = srv.block_on(srv.get("/").force_close().send()).unwrap();
|
||||
println!("RES: {:?}", response);
|
||||
assert!(response.status().is_success());
|
||||
}
|
||||
|
||||
|
@@ -867,7 +867,7 @@ fn test_h1_response_http_error_handling() {
|
||||
|
||||
// read response
|
||||
let bytes = srv.load_body(response).unwrap();
|
||||
assert!(bytes.is_empty());
|
||||
assert_eq!(bytes, Bytes::from_static(b"failed to parse header value"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssl")]
|
||||
@@ -900,7 +900,7 @@ fn test_h2_response_http_error_handling() {
|
||||
|
||||
// read response
|
||||
let bytes = srv.load_body(response).unwrap();
|
||||
assert!(bytes.is_empty());
|
||||
assert_eq!(bytes, Bytes::from_static(b"failed to parse header value"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -911,11 +911,11 @@ fn test_h1_service_error() {
|
||||
});
|
||||
|
||||
let response = srv.block_on(srv.get("/").send()).unwrap();
|
||||
assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR);
|
||||
assert_eq!(response.status(), http::StatusCode::BAD_REQUEST);
|
||||
|
||||
// read response
|
||||
let bytes = srv.load_body(response).unwrap();
|
||||
assert!(bytes.is_empty());
|
||||
assert_eq!(bytes, Bytes::from_static(b"error"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssl")]
|
||||
|
5
actix-multipart/CHANGES.md
Normal file
5
actix-multipart/CHANGES.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Changes
|
||||
|
||||
## [0.1.0-alpha.1] - 2019-04-xx
|
||||
|
||||
* Split multipart support to separate crate
|
34
actix-multipart/Cargo.toml
Normal file
34
actix-multipart/Cargo.toml
Normal file
@@ -0,0 +1,34 @@
|
||||
[package]
|
||||
name = "actix-multipart"
|
||||
version = "0.1.0-alpha.1"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Multipart support for actix web framework."
|
||||
readme = "README.md"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
documentation = "https://docs.rs/actix-multipart/"
|
||||
license = "MIT/Apache-2.0"
|
||||
exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"]
|
||||
workspace = ".."
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
name = "actix_multipart"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-web = "1.0.0-alpha.3"
|
||||
actix-service = "0.3.4"
|
||||
bytes = "0.4"
|
||||
derive_more = "0.14"
|
||||
httparse = "1.3"
|
||||
futures = "0.1.25"
|
||||
log = "0.4"
|
||||
mime = "0.3"
|
||||
time = "0.1"
|
||||
twoway = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "0.2.2"
|
||||
actix-http = "0.1.0-alpha.3"
|
1
actix-multipart/README.md
Normal file
1
actix-multipart/README.md
Normal file
@@ -0,0 +1 @@
|
||||
# Multipart support for actix web framework [](https://travis-ci.org/actix/actix-web) [](https://codecov.io/gh/actix/actix-web) [](https://crates.io/crates/actix-multipart) [](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
46
actix-multipart/src/error.rs
Normal file
46
actix-multipart/src/error.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
//! Error and Result module
|
||||
use actix_web::error::{ParseError, PayloadError};
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::{HttpResponse, ResponseError};
|
||||
use derive_more::{Display, From};
|
||||
|
||||
/// A set of errors that can occur during parsing multipart streams
|
||||
#[derive(Debug, Display, From)]
|
||||
pub enum MultipartError {
|
||||
/// Content-Type header is not found
|
||||
#[display(fmt = "No Content-type header found")]
|
||||
NoContentType,
|
||||
/// Can not parse Content-Type header
|
||||
#[display(fmt = "Can not parse Content-Type header")]
|
||||
ParseContentType,
|
||||
/// Multipart boundary is not found
|
||||
#[display(fmt = "Multipart boundary is not found")]
|
||||
Boundary,
|
||||
/// Multipart stream is incomplete
|
||||
#[display(fmt = "Multipart stream is incomplete")]
|
||||
Incomplete,
|
||||
/// Error during field parsing
|
||||
#[display(fmt = "{}", _0)]
|
||||
Parse(ParseError),
|
||||
/// Payload error
|
||||
#[display(fmt = "{}", _0)]
|
||||
Payload(PayloadError),
|
||||
}
|
||||
|
||||
/// Return `BadRequest` for `MultipartError`
|
||||
impl ResponseError for MultipartError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
HttpResponse::new(StatusCode::BAD_REQUEST)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_multipart_error() {
|
||||
let resp: HttpResponse = MultipartError::Boundary.error_response();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
}
|
54
actix-multipart/src/extractor.rs
Normal file
54
actix-multipart/src/extractor.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
//! Multipart payload support
|
||||
use bytes::Bytes;
|
||||
use futures::Stream;
|
||||
|
||||
use actix_web::error::{Error, PayloadError};
|
||||
use actix_web::{dev::Payload, FromRequest, HttpRequest};
|
||||
|
||||
use crate::server::Multipart;
|
||||
|
||||
/// Get request's payload as multipart stream
|
||||
///
|
||||
/// Content-type: multipart/form-data;
|
||||
///
|
||||
/// ## Server example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use futures::{Future, Stream};
|
||||
/// # use futures::future::{ok, result, Either};
|
||||
/// use actix_web::{web, HttpResponse, Error};
|
||||
/// use actix_multipart as mp;
|
||||
///
|
||||
/// fn index(payload: mp::Multipart) -> impl Future<Item = HttpResponse, Error = Error> {
|
||||
/// payload.from_err() // <- get multipart stream for current request
|
||||
/// .and_then(|item| match item { // <- iterate over multipart items
|
||||
/// mp::Item::Field(field) => {
|
||||
/// // Field in turn is stream of *Bytes* object
|
||||
/// Either::A(field.from_err()
|
||||
/// .fold((), |_, chunk| {
|
||||
/// println!("-- CHUNK: \n{:?}", std::str::from_utf8(&chunk));
|
||||
/// Ok::<_, Error>(())
|
||||
/// }))
|
||||
/// },
|
||||
/// mp::Item::Nested(mp) => {
|
||||
/// // Or item could be nested Multipart stream
|
||||
/// Either::B(ok(()))
|
||||
/// }
|
||||
/// })
|
||||
/// .fold((), |_, _| Ok::<_, Error>(()))
|
||||
/// .map(|_| HttpResponse::Ok().into())
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
impl<P> FromRequest<P> for Multipart
|
||||
where
|
||||
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
{
|
||||
type Error = Error;
|
||||
type Future = Result<Multipart, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future {
|
||||
Ok(Multipart::new(req.headers(), payload.take()))
|
||||
}
|
||||
}
|
6
actix-multipart/src/lib.rs
Normal file
6
actix-multipart/src/lib.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
mod error;
|
||||
mod extractor;
|
||||
mod server;
|
||||
|
||||
pub use self::error::MultipartError;
|
||||
pub use self::server::{Field, Item, Multipart};
|
@@ -4,26 +4,22 @@ use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
use std::{cmp, fmt};
|
||||
|
||||
use bytes::Bytes;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::task::{current as current_task, Task};
|
||||
use futures::{Async, Poll, Stream};
|
||||
use httparse;
|
||||
use mime;
|
||||
|
||||
use crate::error::{Error, MultipartError, ParseError, PayloadError};
|
||||
use crate::extract::FromRequest;
|
||||
use crate::http::header::{
|
||||
use actix_web::error::{ParseError, PayloadError};
|
||||
use actix_web::http::header::{
|
||||
self, ContentDisposition, HeaderMap, HeaderName, HeaderValue,
|
||||
};
|
||||
use crate::http::HttpTryFrom;
|
||||
use crate::service::ServiceFromRequest;
|
||||
use crate::HttpMessage;
|
||||
use actix_web::http::HttpTryFrom;
|
||||
|
||||
use crate::error::MultipartError;
|
||||
|
||||
const MAX_HEADERS: usize = 32;
|
||||
|
||||
type PayloadBuffer =
|
||||
actix_http::h1::PayloadBuffer<Box<dyn Stream<Item = Bytes, Error = PayloadError>>>;
|
||||
|
||||
/// The server-side implementation of `multipart/form-data` requests.
|
||||
///
|
||||
/// This will parse the incoming stream into `MultipartItem` instances via its
|
||||
@@ -37,59 +33,13 @@ pub struct Multipart {
|
||||
}
|
||||
|
||||
/// Multipart item
|
||||
pub enum MultipartItem {
|
||||
pub enum Item {
|
||||
/// Multipart field
|
||||
Field(MultipartField),
|
||||
Field(Field),
|
||||
/// Nested multipart stream
|
||||
Nested(Multipart),
|
||||
}
|
||||
|
||||
/// Get request's payload as multipart stream
|
||||
///
|
||||
/// Content-type: multipart/form-data;
|
||||
///
|
||||
/// ## Server example
|
||||
///
|
||||
/// ```rust
|
||||
/// # use futures::{Future, Stream};
|
||||
/// # use futures::future::{ok, result, Either};
|
||||
/// use actix_web::{web, HttpResponse, Error};
|
||||
///
|
||||
/// fn index(payload: web::Multipart) -> impl Future<Item = HttpResponse, Error = Error> {
|
||||
/// payload.from_err() // <- get multipart stream for current request
|
||||
/// .and_then(|item| match item { // <- iterate over multipart items
|
||||
/// web::MultipartItem::Field(field) => {
|
||||
/// // Field in turn is stream of *Bytes* object
|
||||
/// Either::A(field.from_err()
|
||||
/// .fold((), |_, chunk| {
|
||||
/// println!("-- CHUNK: \n{:?}", std::str::from_utf8(&chunk));
|
||||
/// Ok::<_, Error>(())
|
||||
/// }))
|
||||
/// },
|
||||
/// web::MultipartItem::Nested(mp) => {
|
||||
/// // Or item could be nested Multipart stream
|
||||
/// Either::B(ok(()))
|
||||
/// }
|
||||
/// })
|
||||
/// .fold((), |_, _| Ok::<_, Error>(()))
|
||||
/// .map(|_| HttpResponse::Ok().into())
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
impl<P> FromRequest<P> for Multipart
|
||||
where
|
||||
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
{
|
||||
type Error = Error;
|
||||
type Future = Result<Multipart, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
|
||||
let pl = req.take_payload();
|
||||
Ok(Multipart::new(req.headers(), pl))
|
||||
}
|
||||
}
|
||||
|
||||
enum InnerMultipartItem {
|
||||
None,
|
||||
Field(Rc<RefCell<InnerField>>),
|
||||
@@ -142,7 +92,7 @@ impl Multipart {
|
||||
|
||||
/// Extract boundary info from headers.
|
||||
fn boundary(headers: &HeaderMap) -> Result<String, MultipartError> {
|
||||
if let Some(content_type) = headers.get(header::CONTENT_TYPE) {
|
||||
if let Some(content_type) = headers.get(&header::CONTENT_TYPE) {
|
||||
if let Ok(content_type) = content_type.to_str() {
|
||||
if let Ok(ct) = content_type.parse::<mime::Mime>() {
|
||||
if let Some(boundary) = ct.get_param(mime::BOUNDARY) {
|
||||
@@ -163,14 +113,18 @@ impl Multipart {
|
||||
}
|
||||
|
||||
impl Stream for Multipart {
|
||||
type Item = MultipartItem;
|
||||
type Item = Item;
|
||||
type Error = MultipartError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
if let Some(err) = self.error.take() {
|
||||
Err(err)
|
||||
} else if self.safety.current() {
|
||||
self.inner.as_mut().unwrap().borrow_mut().poll(&self.safety)
|
||||
let mut inner = self.inner.as_mut().unwrap().borrow_mut();
|
||||
if let Some(payload) = inner.payload.get_mut(&self.safety) {
|
||||
payload.poll_stream()?;
|
||||
}
|
||||
inner.poll(&self.safety)
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
@@ -178,11 +132,18 @@ impl Stream for Multipart {
|
||||
}
|
||||
|
||||
impl InnerMultipart {
|
||||
fn read_headers(payload: &mut PayloadBuffer) -> Poll<HeaderMap, MultipartError> {
|
||||
match payload.read_until(b"\r\n\r\n")? {
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
Async::Ready(None) => Err(MultipartError::Incomplete),
|
||||
Async::Ready(Some(bytes)) => {
|
||||
fn read_headers(
|
||||
payload: &mut PayloadBuffer,
|
||||
) -> Result<Option<HeaderMap>, MultipartError> {
|
||||
match payload.read_until(b"\r\n\r\n") {
|
||||
None => {
|
||||
if payload.eof {
|
||||
Err(MultipartError::Incomplete)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Some(bytes) => {
|
||||
let mut hdrs = [httparse::EMPTY_HEADER; MAX_HEADERS];
|
||||
match httparse::parse_headers(&bytes, &mut hdrs) {
|
||||
Ok(httparse::Status::Complete((_, hdrs))) => {
|
||||
@@ -199,7 +160,7 @@ impl InnerMultipart {
|
||||
return Err(ParseError::Header.into());
|
||||
}
|
||||
}
|
||||
Ok(Async::Ready(headers))
|
||||
Ok(Some(headers))
|
||||
}
|
||||
Ok(httparse::Status::Partial) => Err(ParseError::Header.into()),
|
||||
Err(err) => Err(ParseError::from(err).into()),
|
||||
@@ -211,23 +172,28 @@ impl InnerMultipart {
|
||||
fn read_boundary(
|
||||
payload: &mut PayloadBuffer,
|
||||
boundary: &str,
|
||||
) -> Poll<bool, MultipartError> {
|
||||
) -> Result<Option<bool>, MultipartError> {
|
||||
// TODO: need to read epilogue
|
||||
match payload.readline()? {
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
Async::Ready(None) => Err(MultipartError::Incomplete),
|
||||
Async::Ready(Some(chunk)) => {
|
||||
match payload.readline() {
|
||||
None => {
|
||||
if payload.eof {
|
||||
Err(MultipartError::Incomplete)
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
Some(chunk) => {
|
||||
if chunk.len() == boundary.len() + 4
|
||||
&& &chunk[..2] == b"--"
|
||||
&& &chunk[2..boundary.len() + 2] == boundary.as_bytes()
|
||||
{
|
||||
Ok(Async::Ready(false))
|
||||
Ok(Some(false))
|
||||
} else if chunk.len() == boundary.len() + 6
|
||||
&& &chunk[..2] == b"--"
|
||||
&& &chunk[2..boundary.len() + 2] == boundary.as_bytes()
|
||||
&& &chunk[boundary.len() + 2..boundary.len() + 4] == b"--"
|
||||
{
|
||||
Ok(Async::Ready(true))
|
||||
Ok(Some(true))
|
||||
} else {
|
||||
Err(MultipartError::Boundary)
|
||||
}
|
||||
@@ -238,11 +204,11 @@ impl InnerMultipart {
|
||||
fn skip_until_boundary(
|
||||
payload: &mut PayloadBuffer,
|
||||
boundary: &str,
|
||||
) -> Poll<bool, MultipartError> {
|
||||
) -> Result<Option<bool>, MultipartError> {
|
||||
let mut eof = false;
|
||||
loop {
|
||||
match payload.readline()? {
|
||||
Async::Ready(Some(chunk)) => {
|
||||
match payload.readline() {
|
||||
Some(chunk) => {
|
||||
if chunk.is_empty() {
|
||||
//ValueError("Could not find starting boundary %r"
|
||||
//% (self._boundary))
|
||||
@@ -267,14 +233,19 @@ impl InnerMultipart {
|
||||
}
|
||||
}
|
||||
}
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
Async::Ready(None) => return Err(MultipartError::Incomplete),
|
||||
None => {
|
||||
return if payload.eof {
|
||||
Err(MultipartError::Incomplete)
|
||||
} else {
|
||||
Ok(None)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Async::Ready(eof))
|
||||
Ok(Some(eof))
|
||||
}
|
||||
|
||||
fn poll(&mut self, safety: &Safety) -> Poll<Option<MultipartItem>, MultipartError> {
|
||||
fn poll(&mut self, safety: &Safety) -> Poll<Option<Item>, MultipartError> {
|
||||
if self.state == InnerState::Eof {
|
||||
Ok(Async::Ready(None))
|
||||
} else {
|
||||
@@ -317,7 +288,7 @@ impl InnerMultipart {
|
||||
payload,
|
||||
&self.boundary,
|
||||
)? {
|
||||
Async::Ready(eof) => {
|
||||
Some(eof) => {
|
||||
if eof {
|
||||
self.state = InnerState::Eof;
|
||||
return Ok(Async::Ready(None));
|
||||
@@ -325,14 +296,14 @@ impl InnerMultipart {
|
||||
self.state = InnerState::Headers;
|
||||
}
|
||||
}
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
None => return Ok(Async::NotReady),
|
||||
}
|
||||
}
|
||||
// read boundary
|
||||
InnerState::Boundary => {
|
||||
match InnerMultipart::read_boundary(payload, &self.boundary)? {
|
||||
Async::NotReady => return Ok(Async::NotReady),
|
||||
Async::Ready(eof) => {
|
||||
None => return Ok(Async::NotReady),
|
||||
Some(eof) => {
|
||||
if eof {
|
||||
self.state = InnerState::Eof;
|
||||
return Ok(Async::Ready(None));
|
||||
@@ -347,8 +318,7 @@ impl InnerMultipart {
|
||||
|
||||
// read field headers for next field
|
||||
if self.state == InnerState::Headers {
|
||||
if let Async::Ready(headers) = InnerMultipart::read_headers(payload)?
|
||||
{
|
||||
if let Some(headers) = InnerMultipart::read_headers(payload)? {
|
||||
self.state = InnerState::Boundary;
|
||||
headers
|
||||
} else {
|
||||
@@ -364,7 +334,7 @@ impl InnerMultipart {
|
||||
|
||||
// content type
|
||||
let mut mt = mime::APPLICATION_OCTET_STREAM;
|
||||
if let Some(content_type) = headers.get(header::CONTENT_TYPE) {
|
||||
if let Some(content_type) = headers.get(&header::CONTENT_TYPE) {
|
||||
if let Ok(content_type) = content_type.to_str() {
|
||||
if let Ok(ct) = content_type.parse::<mime::Mime>() {
|
||||
mt = ct;
|
||||
@@ -389,7 +359,7 @@ impl InnerMultipart {
|
||||
|
||||
self.item = InnerMultipartItem::Multipart(Rc::clone(&inner));
|
||||
|
||||
Ok(Async::Ready(Some(MultipartItem::Nested(Multipart {
|
||||
Ok(Async::Ready(Some(Item::Nested(Multipart {
|
||||
safety: safety.clone(),
|
||||
error: None,
|
||||
inner: Some(inner),
|
||||
@@ -402,9 +372,12 @@ impl InnerMultipart {
|
||||
)?));
|
||||
self.item = InnerMultipartItem::Field(Rc::clone(&field));
|
||||
|
||||
Ok(Async::Ready(Some(MultipartItem::Field(
|
||||
MultipartField::new(safety.clone(), headers, mt, field),
|
||||
))))
|
||||
Ok(Async::Ready(Some(Item::Field(Field::new(
|
||||
safety.clone(),
|
||||
headers,
|
||||
mt,
|
||||
field,
|
||||
)))))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -418,21 +391,21 @@ impl Drop for InnerMultipart {
|
||||
}
|
||||
|
||||
/// A single field in a multipart stream
|
||||
pub struct MultipartField {
|
||||
pub struct Field {
|
||||
ct: mime::Mime,
|
||||
headers: HeaderMap,
|
||||
inner: Rc<RefCell<InnerField>>,
|
||||
safety: Safety,
|
||||
}
|
||||
|
||||
impl MultipartField {
|
||||
impl Field {
|
||||
fn new(
|
||||
safety: Safety,
|
||||
headers: HeaderMap,
|
||||
ct: mime::Mime,
|
||||
inner: Rc<RefCell<InnerField>>,
|
||||
) -> Self {
|
||||
MultipartField {
|
||||
Field {
|
||||
ct,
|
||||
headers,
|
||||
inner,
|
||||
@@ -454,7 +427,7 @@ impl MultipartField {
|
||||
pub fn content_disposition(&self) -> Option<ContentDisposition> {
|
||||
// RFC 7578: 'Each part MUST contain a Content-Disposition header field
|
||||
// where the disposition type is "form-data".'
|
||||
if let Some(content_disposition) = self.headers.get(header::CONTENT_DISPOSITION)
|
||||
if let Some(content_disposition) = self.headers.get(&header::CONTENT_DISPOSITION)
|
||||
{
|
||||
ContentDisposition::from_raw(content_disposition).ok()
|
||||
} else {
|
||||
@@ -463,22 +436,28 @@ impl MultipartField {
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for MultipartField {
|
||||
impl Stream for Field {
|
||||
type Item = Bytes;
|
||||
type Error = MultipartError;
|
||||
|
||||
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
|
||||
if self.safety.current() {
|
||||
self.inner.borrow_mut().poll(&self.safety)
|
||||
let mut inner = self.inner.borrow_mut();
|
||||
if let Some(payload) = inner.payload.as_ref().unwrap().get_mut(&self.safety)
|
||||
{
|
||||
payload.poll_stream()?;
|
||||
}
|
||||
|
||||
inner.poll(&self.safety)
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for MultipartField {
|
||||
impl fmt::Debug for Field {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
writeln!(f, "\nMultipartField: {}", self.ct)?;
|
||||
writeln!(f, "\nField: {}", self.ct)?;
|
||||
writeln!(f, " boundary: {}", self.inner.borrow().boundary)?;
|
||||
writeln!(f, " headers:")?;
|
||||
for (key, val) in self.headers.iter() {
|
||||
@@ -501,7 +480,7 @@ impl InnerField {
|
||||
boundary: String,
|
||||
headers: &HeaderMap,
|
||||
) -> Result<InnerField, PayloadError> {
|
||||
let len = if let Some(len) = headers.get(header::CONTENT_LENGTH) {
|
||||
let len = if let Some(len) = headers.get(&header::CONTENT_LENGTH) {
|
||||
if let Ok(s) = len.to_str() {
|
||||
if let Ok(len) = s.parse::<u64>() {
|
||||
Some(len)
|
||||
@@ -532,10 +511,8 @@ impl InnerField {
|
||||
if *size == 0 {
|
||||
Ok(Async::Ready(None))
|
||||
} else {
|
||||
match payload.readany() {
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Ok(Async::Ready(None)) => Err(MultipartError::Incomplete),
|
||||
Ok(Async::Ready(Some(mut chunk))) => {
|
||||
match payload.read_max(*size) {
|
||||
Some(mut chunk) => {
|
||||
let len = cmp::min(chunk.len() as u64, *size);
|
||||
*size -= len;
|
||||
let ch = chunk.split_to(len as usize);
|
||||
@@ -544,7 +521,13 @@ impl InnerField {
|
||||
}
|
||||
Ok(Async::Ready(Some(ch)))
|
||||
}
|
||||
Err(err) => Err(err.into()),
|
||||
None => {
|
||||
if payload.eof && (*size != 0) {
|
||||
Err(MultipartError::Incomplete)
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -555,16 +538,26 @@ impl InnerField {
|
||||
payload: &mut PayloadBuffer,
|
||||
boundary: &str,
|
||||
) -> Poll<Option<Bytes>, MultipartError> {
|
||||
match payload.read_until(b"\r")? {
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
Async::Ready(None) => Err(MultipartError::Incomplete),
|
||||
Async::Ready(Some(mut chunk)) => {
|
||||
match payload.read_until(b"\r") {
|
||||
None => {
|
||||
if payload.eof {
|
||||
Err(MultipartError::Incomplete)
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
Some(mut chunk) => {
|
||||
if chunk.len() == 1 {
|
||||
payload.unprocessed(chunk);
|
||||
match payload.read_exact(boundary.len() + 4)? {
|
||||
Async::NotReady => Ok(Async::NotReady),
|
||||
Async::Ready(None) => Err(MultipartError::Incomplete),
|
||||
Async::Ready(Some(mut chunk)) => {
|
||||
match payload.read_exact(boundary.len() + 4) {
|
||||
None => {
|
||||
if payload.eof {
|
||||
Err(MultipartError::Incomplete)
|
||||
} else {
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
||||
Some(mut chunk) => {
|
||||
if &chunk[..2] == b"\r\n"
|
||||
&& &chunk[2..4] == b"--"
|
||||
&& &chunk[4..] == boundary.as_bytes()
|
||||
@@ -606,10 +599,9 @@ impl InnerField {
|
||||
Async::Ready(Some(bytes)) => Async::Ready(Some(bytes)),
|
||||
Async::Ready(None) => {
|
||||
self.eof = true;
|
||||
match payload.readline()? {
|
||||
Async::NotReady => Async::NotReady,
|
||||
Async::Ready(None) => Async::Ready(None),
|
||||
Async::Ready(Some(line)) => {
|
||||
match payload.readline() {
|
||||
None => Async::Ready(None),
|
||||
Some(line) => {
|
||||
if line.as_ref() != b"\r\n" {
|
||||
log::warn!("multipart field did not read all the data or it is malformed");
|
||||
}
|
||||
@@ -711,14 +703,86 @@ impl Drop for Safety {
|
||||
}
|
||||
}
|
||||
|
||||
/// Payload buffer
|
||||
struct PayloadBuffer {
|
||||
eof: bool,
|
||||
buf: BytesMut,
|
||||
stream: Box<dyn Stream<Item = Bytes, Error = PayloadError>>,
|
||||
}
|
||||
|
||||
impl PayloadBuffer {
|
||||
/// Create new `PayloadBuffer` instance
|
||||
fn new<S>(stream: S) -> Self
|
||||
where
|
||||
S: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
{
|
||||
PayloadBuffer {
|
||||
eof: false,
|
||||
buf: BytesMut::new(),
|
||||
stream: Box::new(stream),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_stream(&mut self) -> Result<(), PayloadError> {
|
||||
loop {
|
||||
match self.stream.poll()? {
|
||||
Async::Ready(Some(data)) => self.buf.extend_from_slice(&data),
|
||||
Async::Ready(None) => {
|
||||
self.eof = true;
|
||||
return Ok(());
|
||||
}
|
||||
Async::NotReady => return Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Read exact number of bytes
|
||||
#[inline]
|
||||
fn read_exact(&mut self, size: usize) -> Option<Bytes> {
|
||||
if size <= self.buf.len() {
|
||||
Some(self.buf.split_to(size).freeze())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn read_max(&mut self, size: u64) -> Option<Bytes> {
|
||||
if !self.buf.is_empty() {
|
||||
let size = std::cmp::min(self.buf.len() as u64, size) as usize;
|
||||
Some(self.buf.split_to(size).freeze())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Read until specified ending
|
||||
pub fn read_until(&mut self, line: &[u8]) -> Option<Bytes> {
|
||||
twoway::find_bytes(&self.buf, line)
|
||||
.map(|idx| self.buf.split_to(idx + line.len()).freeze())
|
||||
}
|
||||
|
||||
/// Read bytes until new line delimiter
|
||||
pub fn readline(&mut self) -> Option<Bytes> {
|
||||
self.read_until(b"\n")
|
||||
}
|
||||
|
||||
/// Put unprocessed data back to the buffer
|
||||
pub fn unprocessed(&mut self, data: Bytes) {
|
||||
let buf = BytesMut::from(data);
|
||||
let buf = std::mem::replace(&mut self.buf, buf);
|
||||
self.buf.extend_from_slice(&buf);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_http::h1::Payload;
|
||||
use bytes::Bytes;
|
||||
use futures::unsync::mpsc;
|
||||
|
||||
use super::*;
|
||||
use crate::http::header::{DispositionParam, DispositionType};
|
||||
use crate::test::run_on;
|
||||
use actix_web::http::header::{DispositionParam, DispositionType};
|
||||
use actix_web::test::run_on;
|
||||
|
||||
#[test]
|
||||
fn test_boundary() {
|
||||
@@ -799,9 +863,9 @@ mod tests {
|
||||
);
|
||||
|
||||
let mut multipart = Multipart::new(&headers, payload);
|
||||
match multipart.poll() {
|
||||
Ok(Async::Ready(Some(item))) => match item {
|
||||
MultipartItem::Field(mut field) => {
|
||||
match multipart.poll().unwrap() {
|
||||
Async::Ready(Some(item)) => match item {
|
||||
Item::Field(mut field) => {
|
||||
{
|
||||
let cd = field.content_disposition().unwrap();
|
||||
assert_eq!(cd.disposition, DispositionType::FormData);
|
||||
@@ -813,12 +877,12 @@ mod tests {
|
||||
assert_eq!(field.content_type().type_(), mime::TEXT);
|
||||
assert_eq!(field.content_type().subtype(), mime::PLAIN);
|
||||
|
||||
match field.poll() {
|
||||
Ok(Async::Ready(Some(chunk))) => assert_eq!(chunk, "test"),
|
||||
match field.poll().unwrap() {
|
||||
Async::Ready(Some(chunk)) => assert_eq!(chunk, "test"),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
match field.poll() {
|
||||
Ok(Async::Ready(None)) => (),
|
||||
match field.poll().unwrap() {
|
||||
Async::Ready(None) => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -827,9 +891,9 @@ mod tests {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
match multipart.poll() {
|
||||
Ok(Async::Ready(Some(item))) => match item {
|
||||
MultipartItem::Field(mut field) => {
|
||||
match multipart.poll().unwrap() {
|
||||
Async::Ready(Some(item)) => match item {
|
||||
Item::Field(mut field) => {
|
||||
assert_eq!(field.content_type().type_(), mime::TEXT);
|
||||
assert_eq!(field.content_type().subtype(), mime::PLAIN);
|
||||
|
||||
@@ -847,10 +911,110 @@ mod tests {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
match multipart.poll() {
|
||||
Ok(Async::Ready(None)) => (),
|
||||
match multipart.poll().unwrap() {
|
||||
Async::Ready(None) => (),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic() {
|
||||
run_on(|| {
|
||||
let (_, payload) = Payload::create(false);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
assert_eq!(payload.buf.len(), 0);
|
||||
payload.poll_stream().unwrap();
|
||||
assert_eq!(None, payload.read_max(1));
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eof() {
|
||||
run_on(|| {
|
||||
let (mut sender, payload) = Payload::create(false);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
assert_eq!(None, payload.read_max(4));
|
||||
sender.feed_data(Bytes::from("data"));
|
||||
sender.feed_eof();
|
||||
payload.poll_stream().unwrap();
|
||||
|
||||
assert_eq!(Some(Bytes::from("data")), payload.read_max(4));
|
||||
assert_eq!(payload.buf.len(), 0);
|
||||
assert_eq!(None, payload.read_max(1));
|
||||
assert!(payload.eof);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_err() {
|
||||
run_on(|| {
|
||||
let (mut sender, payload) = Payload::create(false);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
assert_eq!(None, payload.read_max(1));
|
||||
sender.set_error(PayloadError::Incomplete(None));
|
||||
payload.poll_stream().err().unwrap();
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readmax() {
|
||||
run_on(|| {
|
||||
let (mut sender, payload) = Payload::create(false);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
sender.feed_data(Bytes::from("line1"));
|
||||
sender.feed_data(Bytes::from("line2"));
|
||||
payload.poll_stream().unwrap();
|
||||
assert_eq!(payload.buf.len(), 10);
|
||||
|
||||
assert_eq!(Some(Bytes::from("line1")), payload.read_max(5));
|
||||
assert_eq!(payload.buf.len(), 5);
|
||||
|
||||
assert_eq!(Some(Bytes::from("line2")), payload.read_max(5));
|
||||
assert_eq!(payload.buf.len(), 0);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readexactly() {
|
||||
run_on(|| {
|
||||
let (mut sender, payload) = Payload::create(false);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
assert_eq!(None, payload.read_exact(2));
|
||||
|
||||
sender.feed_data(Bytes::from("line1"));
|
||||
sender.feed_data(Bytes::from("line2"));
|
||||
payload.poll_stream().unwrap();
|
||||
|
||||
assert_eq!(Some(Bytes::from_static(b"li")), payload.read_exact(2));
|
||||
assert_eq!(payload.buf.len(), 8);
|
||||
|
||||
assert_eq!(Some(Bytes::from_static(b"ne1l")), payload.read_exact(4));
|
||||
assert_eq!(payload.buf.len(), 4);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_readuntil() {
|
||||
run_on(|| {
|
||||
let (mut sender, payload) = Payload::create(false);
|
||||
let mut payload = PayloadBuffer::new(payload);
|
||||
|
||||
assert_eq!(None, payload.read_until(b"ne"));
|
||||
|
||||
sender.feed_data(Bytes::from("line1"));
|
||||
sender.feed_data(Bytes::from("line2"));
|
||||
payload.poll_stream().unwrap();
|
||||
|
||||
assert_eq!(Some(Bytes::from("line")), payload.read_until(b"ne"));
|
||||
assert_eq!(payload.buf.len(), 6);
|
||||
|
||||
assert_eq!(Some(Bytes::from("1line2")), payload.read_until(b"2"));
|
||||
assert_eq!(payload.buf.len(), 0);
|
||||
})
|
||||
}
|
||||
}
|
@@ -1,5 +1,9 @@
|
||||
# Changes
|
||||
|
||||
## [0.1.0-alpha.4] - 2019-04-08
|
||||
|
||||
* Update actix-web
|
||||
|
||||
## [0.1.0-alpha.3] - 2019-04-02
|
||||
|
||||
* Update actix-web
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-session"
|
||||
version = "0.1.0-alpha.3"
|
||||
version = "0.1.0-alpha.4"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Session for actix web framework."
|
||||
readme = "README.md"
|
||||
@@ -24,7 +24,7 @@ default = ["cookie-session"]
|
||||
cookie-session = ["actix-web/secure-cookies"]
|
||||
|
||||
[dependencies]
|
||||
actix-web = "1.0.0-alpha.3"
|
||||
actix-web = "1.0.0-alpha.4"
|
||||
actix-service = "0.3.4"
|
||||
bytes = "0.4"
|
||||
derive_more = "0.14"
|
||||
|
@@ -45,8 +45,8 @@
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use actix_web::dev::{ServiceFromRequest, ServiceRequest, ServiceResponse};
|
||||
use actix_web::{Error, FromRequest, HttpMessage};
|
||||
use actix_web::dev::{Extensions, Payload, ServiceRequest, ServiceResponse};
|
||||
use actix_web::{Error, FromRequest, HttpMessage, HttpRequest};
|
||||
use hashbrown::HashMap;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
@@ -123,7 +123,7 @@ impl Session {
|
||||
data: impl Iterator<Item = (String, String)>,
|
||||
req: &mut ServiceRequest<P>,
|
||||
) {
|
||||
let session = Session::get_session(req);
|
||||
let session = Session::get_session(&mut *req.extensions_mut());
|
||||
let mut inner = session.0.borrow_mut();
|
||||
inner.state.extend(data);
|
||||
}
|
||||
@@ -144,12 +144,12 @@ impl Session {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_session<R: HttpMessage>(req: R) -> Session {
|
||||
if let Some(s_impl) = req.extensions().get::<Rc<RefCell<SessionInner>>>() {
|
||||
fn get_session(extensions: &mut Extensions) -> Session {
|
||||
if let Some(s_impl) = extensions.get::<Rc<RefCell<SessionInner>>>() {
|
||||
return Session(Rc::clone(&s_impl));
|
||||
}
|
||||
let inner = Rc::new(RefCell::new(SessionInner::default()));
|
||||
req.extensions_mut().insert(inner.clone());
|
||||
extensions.insert(inner.clone());
|
||||
Session(inner)
|
||||
}
|
||||
}
|
||||
@@ -177,8 +177,8 @@ impl<P> FromRequest<P> for Session {
|
||||
type Future = Result<Session, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
|
||||
Ok(Session::get_session(req))
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
|
||||
Ok(Session::get_session(&mut *req.extensions_mut()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +196,7 @@ mod tests {
|
||||
vec![("key".to_string(), "\"value\"".to_string())].into_iter(),
|
||||
&mut req,
|
||||
);
|
||||
let session = Session::get_session(&mut req);
|
||||
let session = Session::get_session(&mut *req.extensions_mut());
|
||||
let res = session.get::<String>("key").unwrap();
|
||||
assert_eq!(res, Some("value".to_string()));
|
||||
|
||||
|
@@ -50,7 +50,7 @@ pub fn handshake(req: &HttpRequest) -> Result<HttpResponseBuilder, HandshakeErro
|
||||
}
|
||||
|
||||
// Check for "UPGRADE" to websocket header
|
||||
let has_hdr = if let Some(hdr) = req.headers().get(header::UPGRADE) {
|
||||
let has_hdr = if let Some(hdr) = req.headers().get(&header::UPGRADE) {
|
||||
if let Ok(s) = hdr.to_str() {
|
||||
s.to_ascii_lowercase().contains("websocket")
|
||||
} else {
|
||||
@@ -69,11 +69,11 @@ pub fn handshake(req: &HttpRequest) -> Result<HttpResponseBuilder, HandshakeErro
|
||||
}
|
||||
|
||||
// check supported version
|
||||
if !req.headers().contains_key(header::SEC_WEBSOCKET_VERSION) {
|
||||
if !req.headers().contains_key(&header::SEC_WEBSOCKET_VERSION) {
|
||||
return Err(HandshakeError::NoVersionHeader);
|
||||
}
|
||||
let supported_ver = {
|
||||
if let Some(hdr) = req.headers().get(header::SEC_WEBSOCKET_VERSION) {
|
||||
if let Some(hdr) = req.headers().get(&header::SEC_WEBSOCKET_VERSION) {
|
||||
hdr == "13" || hdr == "8" || hdr == "7"
|
||||
} else {
|
||||
false
|
||||
@@ -84,11 +84,11 @@ pub fn handshake(req: &HttpRequest) -> Result<HttpResponseBuilder, HandshakeErro
|
||||
}
|
||||
|
||||
// check client handshake for validity
|
||||
if !req.headers().contains_key(header::SEC_WEBSOCKET_KEY) {
|
||||
if !req.headers().contains_key(&header::SEC_WEBSOCKET_KEY) {
|
||||
return Err(HandshakeError::BadWebsocketKey);
|
||||
}
|
||||
let key = {
|
||||
let key = req.headers().get(header::SEC_WEBSOCKET_KEY).unwrap();
|
||||
let key = req.headers().get(&header::SEC_WEBSOCKET_KEY).unwrap();
|
||||
hash_key(key.as_ref())
|
||||
};
|
||||
|
||||
|
@@ -1,5 +1,12 @@
|
||||
# Changes
|
||||
|
||||
## [0.1.0-alpha.4] - 2019-04-08
|
||||
|
||||
### Changed
|
||||
|
||||
* Update actix-http dependency
|
||||
|
||||
|
||||
## [0.1.0-alpha.3] - 2019-04-02
|
||||
|
||||
### Added
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "awc"
|
||||
version = "0.1.0-alpha.3"
|
||||
version = "0.1.0-alpha.4"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix http client."
|
||||
readme = "README.md"
|
||||
@@ -38,7 +38,7 @@ flate2-rust = ["actix-http/flate2-rust"]
|
||||
[dependencies]
|
||||
actix-codec = "0.1.1"
|
||||
actix-service = "0.3.4"
|
||||
actix-http = "0.1.0-alpa.3"
|
||||
actix-http = "0.1.0-alpha.4"
|
||||
base64 = "0.10.1"
|
||||
bytes = "0.4"
|
||||
derive_more = "0.14"
|
||||
@@ -55,8 +55,8 @@ openssl = { version="0.10", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "0.2.2"
|
||||
actix-web = { version = "1.0.0-alpha.3", features=["ssl"] }
|
||||
actix-http = { version = "0.1.0-alpa.3", features=["ssl"] }
|
||||
actix-web = { version = "1.0.0-alpha.4", features=["ssl"] }
|
||||
actix-http = { version = "0.1.0-alpha.4", features=["ssl"] }
|
||||
actix-http-test = { version = "0.1.0-alpha.3", features=["ssl"] }
|
||||
actix-utils = "0.3.4"
|
||||
actix-server = { version = "0.4.1", features=["ssl"] }
|
||||
|
@@ -31,7 +31,7 @@ impl ClientBuilder {
|
||||
headers: HeaderMap::new(),
|
||||
timeout: Some(Duration::from_secs(5)),
|
||||
connector: RefCell::new(Box::new(ConnectorWrapper(
|
||||
Connector::new().service(),
|
||||
Connector::new().finish(),
|
||||
))),
|
||||
},
|
||||
}
|
||||
|
@@ -78,7 +78,7 @@ impl Default for Client {
|
||||
fn default() -> Self {
|
||||
Client(Rc::new(ClientConfig {
|
||||
connector: RefCell::new(Box::new(ConnectorWrapper(
|
||||
Connector::new().service(),
|
||||
Connector::new().finish(),
|
||||
))),
|
||||
headers: HeaderMap::new(),
|
||||
timeout: Some(Duration::from_secs(5)),
|
||||
@@ -104,7 +104,7 @@ impl Client {
|
||||
{
|
||||
let mut req = ClientRequest::new(method, url, self.0.clone());
|
||||
|
||||
for (key, value) in &self.0.headers {
|
||||
for (key, value) in self.0.headers.iter() {
|
||||
req = req.set_header_if_none(key.clone(), value.clone());
|
||||
}
|
||||
req
|
||||
@@ -119,7 +119,7 @@ impl Client {
|
||||
Uri: HttpTryFrom<U>,
|
||||
{
|
||||
let mut req = self.request(head.method.clone(), url);
|
||||
for (key, value) in &head.headers {
|
||||
for (key, value) in head.headers.iter() {
|
||||
req = req.set_header_if_none(key.clone(), value.clone());
|
||||
}
|
||||
req
|
||||
@@ -187,7 +187,7 @@ impl Client {
|
||||
Uri: HttpTryFrom<U>,
|
||||
{
|
||||
let mut req = ws::WebsocketsRequest::new(url, self.0.clone());
|
||||
for (key, value) in &self.0.headers {
|
||||
for (key, value) in self.0.headers.iter() {
|
||||
req.head.headers.insert(key.clone(), value.clone());
|
||||
}
|
||||
req
|
||||
|
@@ -396,7 +396,7 @@ impl ClientRequest {
|
||||
if self.default_headers {
|
||||
// set request host header
|
||||
if let Some(host) = self.head.uri.host() {
|
||||
if !self.head.headers.contains_key(header::HOST) {
|
||||
if !self.head.headers.contains_key(&header::HOST) {
|
||||
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
|
||||
|
||||
let _ = match self.head.uri.port_u16() {
|
||||
|
@@ -46,7 +46,7 @@ impl<S> HttpMessage for ClientResponse<S> {
|
||||
|
||||
if self.extensions().get::<Cookies>().is_none() {
|
||||
let mut cookies = Vec::new();
|
||||
for hdr in self.headers().get_all(SET_COOKIE) {
|
||||
for hdr in self.headers().get_all(&SET_COOKIE) {
|
||||
let s = std::str::from_utf8(hdr.as_bytes())
|
||||
.map_err(CookieParseError::from)?;
|
||||
cookies.push(Cookie::parse_encoded(s)?.into_owned());
|
||||
@@ -160,7 +160,7 @@ where
|
||||
/// Create `MessageBody` for request.
|
||||
pub fn new(res: &mut ClientResponse<S>) -> MessageBody<S> {
|
||||
let mut len = None;
|
||||
if let Some(l) = res.headers().get(CONTENT_LENGTH) {
|
||||
if let Some(l) = res.headers().get(&CONTENT_LENGTH) {
|
||||
if let Ok(s) = l.to_str() {
|
||||
if let Ok(l) = s.parse::<usize>() {
|
||||
len = Some(l)
|
||||
@@ -254,7 +254,7 @@ where
|
||||
}
|
||||
|
||||
let mut len = None;
|
||||
if let Some(l) = req.headers().get(CONTENT_LENGTH) {
|
||||
if let Some(l) = req.headers().get(&CONTENT_LENGTH) {
|
||||
if let Ok(s) = l.to_str() {
|
||||
if let Ok(l) = s.parse::<usize>() {
|
||||
len = Some(l)
|
||||
@@ -282,7 +282,7 @@ where
|
||||
impl<T, U> Future for JsonBody<T, U>
|
||||
where
|
||||
T: Stream<Item = Bytes, Error = PayloadError>,
|
||||
U: DeserializeOwned + 'static,
|
||||
U: DeserializeOwned,
|
||||
{
|
||||
type Item = U;
|
||||
type Error = JsonPayloadError;
|
||||
|
@@ -3,7 +3,7 @@ use std::fmt::Write as FmtWrite;
|
||||
|
||||
use actix_http::cookie::{Cookie, CookieJar};
|
||||
use actix_http::http::header::{self, Header, HeaderValue, IntoHeaderValue};
|
||||
use actix_http::http::{HeaderName, HttpTryFrom, Version};
|
||||
use actix_http::http::{HeaderName, HttpTryFrom, StatusCode, Version};
|
||||
use actix_http::{h1, Payload, ResponseHead};
|
||||
use bytes::Bytes;
|
||||
#[cfg(test)]
|
||||
@@ -49,7 +49,7 @@ pub struct TestResponse {
|
||||
impl Default for TestResponse {
|
||||
fn default() -> TestResponse {
|
||||
TestResponse {
|
||||
head: ResponseHead::default(),
|
||||
head: ResponseHead::new(StatusCode::OK),
|
||||
cookies: CookieJar::new(),
|
||||
payload: None,
|
||||
}
|
||||
|
@@ -70,7 +70,7 @@ impl WebsocketsRequest {
|
||||
/// Set supported websocket protocols
|
||||
pub fn protocols<U, V>(mut self, protos: U) -> Self
|
||||
where
|
||||
U: IntoIterator<Item = V> + 'static,
|
||||
U: IntoIterator<Item = V>,
|
||||
V: AsRef<str>,
|
||||
{
|
||||
let mut protos = protos
|
||||
@@ -236,7 +236,7 @@ impl WebsocketsRequest {
|
||||
let mut slf = if self.default_headers {
|
||||
// set request host header
|
||||
if let Some(host) = self.head.uri.host() {
|
||||
if !self.head.headers.contains_key(header::HOST) {
|
||||
if !self.head.headers.contains_key(&header::HOST) {
|
||||
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
|
||||
|
||||
let _ = match self.head.uri.port_u16() {
|
||||
@@ -324,7 +324,7 @@ impl WebsocketsRequest {
|
||||
return Err(WsClientError::InvalidResponseStatus(head.status));
|
||||
}
|
||||
// Check for "UPGRADE" to websocket header
|
||||
let has_hdr = if let Some(hdr) = head.headers.get(header::UPGRADE) {
|
||||
let has_hdr = if let Some(hdr) = head.headers.get(&header::UPGRADE) {
|
||||
if let Ok(s) = hdr.to_str() {
|
||||
s.to_ascii_lowercase().contains("websocket")
|
||||
} else {
|
||||
@@ -338,7 +338,7 @@ impl WebsocketsRequest {
|
||||
return Err(WsClientError::InvalidUpgradeHeader);
|
||||
}
|
||||
// Check for "CONNECTION" header
|
||||
if let Some(conn) = head.headers.get(header::CONNECTION) {
|
||||
if let Some(conn) = head.headers.get(&header::CONNECTION) {
|
||||
if let Ok(s) = conn.to_str() {
|
||||
if !s.to_ascii_lowercase().contains("upgrade") {
|
||||
log::trace!("Invalid connection header: {}", s);
|
||||
@@ -355,7 +355,7 @@ impl WebsocketsRequest {
|
||||
return Err(WsClientError::MissingConnectionHeader);
|
||||
}
|
||||
|
||||
if let Some(hdr_key) = head.headers.get(header::SEC_WEBSOCKET_ACCEPT) {
|
||||
if let Some(hdr_key) = head.headers.get(&header::SEC_WEBSOCKET_ACCEPT) {
|
||||
let encoded = ws::hash_key(key.as_ref());
|
||||
if hdr_key.as_bytes() != encoded.as_bytes() {
|
||||
log::trace!(
|
||||
|
@@ -11,7 +11,7 @@ use futures::future::{ok, Either};
|
||||
use futures::{Future, Sink, Stream};
|
||||
use tokio_tcp::TcpStream;
|
||||
|
||||
use actix_http::{h1, ws, ResponseError, SendResponse, ServiceConfig};
|
||||
use actix_http::{body::BodySize, h1, ws, ResponseError, ServiceConfig};
|
||||
|
||||
fn ws_service(req: ws::Frame) -> impl Future<Item = ws::Message, Error = io::Error> {
|
||||
match req {
|
||||
@@ -46,26 +46,34 @@ fn test_simple() {
|
||||
match ws::verify_handshake(&req) {
|
||||
Err(e) => {
|
||||
// validation failed
|
||||
let res = e.error_response();
|
||||
Either::A(
|
||||
SendResponse::send(framed, e.error_response())
|
||||
framed
|
||||
.send(h1::Message::Item((
|
||||
res.drop_body(),
|
||||
BodySize::Empty,
|
||||
)))
|
||||
.map_err(|_| ())
|
||||
.map(|_| ()),
|
||||
)
|
||||
}
|
||||
Ok(_) => {
|
||||
let res = ws::handshake_response(&req).finish();
|
||||
Either::B(
|
||||
// send handshake response
|
||||
SendResponse::send(
|
||||
framed,
|
||||
ws::handshake_response(&req).finish(),
|
||||
)
|
||||
.map_err(|_| ())
|
||||
.and_then(|framed| {
|
||||
// start websocket service
|
||||
let framed = framed.into_framed(ws::Codec::new());
|
||||
ws::Transport::with(framed, ws_service)
|
||||
.map_err(|_| ())
|
||||
}),
|
||||
framed
|
||||
.send(h1::Message::Item((
|
||||
res.drop_body(),
|
||||
BodySize::None,
|
||||
)))
|
||||
.map_err(|_| ())
|
||||
.and_then(|framed| {
|
||||
// start websocket service
|
||||
let framed =
|
||||
framed.into_framed(ws::Codec::new());
|
||||
ws::Transport::with(framed, ws_service)
|
||||
.map_err(|_| ())
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
90
src/app.rs
90
src/app.rs
@@ -15,7 +15,7 @@ use bytes::Bytes;
|
||||
use futures::{IntoFuture, Stream};
|
||||
|
||||
use crate::app_service::{AppChain, AppEntry, AppInit, AppRouting, AppRoutingFactory};
|
||||
use crate::config::{AppConfig, AppConfigInner};
|
||||
use crate::config::{AppConfig, AppConfigInner, RouterConfig};
|
||||
use crate::data::{Data, DataFactory};
|
||||
use crate::dev::{Payload, PayloadStream, ResourceDef};
|
||||
use crate::error::{Error, PayloadError};
|
||||
@@ -257,6 +257,55 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Run external configuration as part of the application building
|
||||
/// process
|
||||
///
|
||||
/// This function is useful for moving parts of configuration to a
|
||||
/// different module or even library. For example,
|
||||
/// some of the resource's configuration could be moved to different module.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::{web, middleware, App, HttpResponse};
|
||||
///
|
||||
/// // this function could be located in different module
|
||||
/// fn config<P>(cfg: &mut web::RouterConfig<P>) {
|
||||
/// cfg.service(web::resource("/test")
|
||||
/// .route(web::get().to(|| HttpResponse::Ok()))
|
||||
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
|
||||
/// );
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .wrap(middleware::Logger::default())
|
||||
/// .configure(config) // <- register resources
|
||||
/// .route("/index.html", web::get().to(|| HttpResponse::Ok()));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn configure<F>(mut self, f: F) -> AppRouter<T, Out, Body, AppEntry<Out>>
|
||||
where
|
||||
F: Fn(&mut RouterConfig<Out>),
|
||||
{
|
||||
let mut cfg = RouterConfig::new();
|
||||
f(&mut cfg);
|
||||
self.data.extend(cfg.data);
|
||||
|
||||
let fref = Rc::new(RefCell::new(None));
|
||||
|
||||
AppRouter {
|
||||
chain: self.chain,
|
||||
default: None,
|
||||
endpoint: AppEntry::new(fref.clone()),
|
||||
factory_ref: fref,
|
||||
data: self.data,
|
||||
config: self.config,
|
||||
services: cfg.services,
|
||||
external: cfg.external,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Configure route for a specific path.
|
||||
///
|
||||
/// This is a simplified version of the `App::service()` method.
|
||||
@@ -382,6 +431,45 @@ where
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
/// Run external configuration as part of the application building
|
||||
/// process
|
||||
///
|
||||
/// This function is useful for moving parts of configuration to a
|
||||
/// different module or even library. For example,
|
||||
/// some of the resource's configuration could be moved to different module.
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate actix_web;
|
||||
/// use actix_web::{web, middleware, App, HttpResponse};
|
||||
///
|
||||
/// // this function could be located in different module
|
||||
/// fn config<P>(cfg: &mut web::RouterConfig<P>) {
|
||||
/// cfg.service(web::resource("/test")
|
||||
/// .route(web::get().to(|| HttpResponse::Ok()))
|
||||
/// .route(web::head().to(|| HttpResponse::MethodNotAllowed()))
|
||||
/// );
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .wrap(middleware::Logger::default())
|
||||
/// .configure(config) // <- register resources
|
||||
/// .route("/index.html", web::get().to(|| HttpResponse::Ok()));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn configure<F>(mut self, f: F) -> Self
|
||||
where
|
||||
F: Fn(&mut RouterConfig<P>),
|
||||
{
|
||||
let mut cfg = RouterConfig::new();
|
||||
f(&mut cfg);
|
||||
self.data.extend(cfg.data);
|
||||
self.services.extend(cfg.services);
|
||||
self.external.extend(cfg.external);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure route for a specific path.
|
||||
///
|
||||
/// This is a simplified version of the `App::service()` method.
|
||||
|
@@ -14,6 +14,7 @@ use crate::config::{AppConfig, ServiceConfig};
|
||||
use crate::data::{DataFactory, DataFactoryResult};
|
||||
use crate::error::Error;
|
||||
use crate::guard::Guard;
|
||||
use crate::request::{HttpRequest, HttpRequestPool};
|
||||
use crate::rmap::ResourceMap;
|
||||
use crate::service::{ServiceFactory, ServiceRequest, ServiceResponse};
|
||||
|
||||
@@ -21,7 +22,10 @@ type Guards = Vec<Box<Guard>>;
|
||||
type HttpService<P> = BoxedService<ServiceRequest<P>, ServiceResponse, Error>;
|
||||
type HttpNewService<P> =
|
||||
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
|
||||
type BoxedResponse = Box<Future<Item = ServiceResponse, Error = Error>>;
|
||||
type BoxedResponse = Either<
|
||||
FutureResult<ServiceResponse, Error>,
|
||||
Box<Future<Item = ServiceResponse, Error = Error>>,
|
||||
>;
|
||||
|
||||
/// Service factory to convert `Request` to a `ServiceRequest<S>`.
|
||||
/// It also executes data factories.
|
||||
@@ -191,6 +195,7 @@ where
|
||||
chain: self.chain.take().unwrap(),
|
||||
rmap: self.rmap.clone(),
|
||||
config: self.config.clone(),
|
||||
pool: HttpRequestPool::create(),
|
||||
}
|
||||
.and_then(self.endpoint.take().unwrap()),
|
||||
))
|
||||
@@ -208,6 +213,7 @@ where
|
||||
chain: C,
|
||||
rmap: Rc<ResourceMap>,
|
||||
config: AppConfig,
|
||||
pool: &'static HttpRequestPool,
|
||||
}
|
||||
|
||||
impl<C, P> Service for AppInitService<C, P>
|
||||
@@ -224,13 +230,24 @@ where
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Request) -> Self::Future {
|
||||
let req = ServiceRequest::new(
|
||||
Path::new(Url::new(req.uri().clone())),
|
||||
req,
|
||||
self.rmap.clone(),
|
||||
self.config.clone(),
|
||||
);
|
||||
self.chain.call(req)
|
||||
let (head, payload) = req.into_parts();
|
||||
|
||||
let req = if let Some(mut req) = self.pool.get_request() {
|
||||
let inner = Rc::get_mut(&mut req.0).unwrap();
|
||||
inner.path.get_mut().update(&head.uri);
|
||||
inner.path.reset();
|
||||
inner.head = head;
|
||||
req
|
||||
} else {
|
||||
HttpRequest::new(
|
||||
Path::new(Url::new(head.uri.clone())),
|
||||
head,
|
||||
self.rmap.clone(),
|
||||
self.config.clone(),
|
||||
self.pool,
|
||||
)
|
||||
};
|
||||
self.chain.call(ServiceRequest::from_parts(req, payload))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -353,7 +370,7 @@ impl<P> Service for AppRouting<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type Future = Either<BoxedResponse, FutureResult<Self::Response, Self::Error>>;
|
||||
type Future = BoxedResponse;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
if self.ready.is_none() {
|
||||
@@ -376,12 +393,12 @@ impl<P> Service for AppRouting<P> {
|
||||
});
|
||||
|
||||
if let Some((srv, _info)) = res {
|
||||
Either::A(srv.call(req))
|
||||
srv.call(req)
|
||||
} else if let Some(ref mut default) = self.default {
|
||||
Either::A(default.call(req))
|
||||
default.call(req)
|
||||
} else {
|
||||
let req = req.into_parts().0;
|
||||
Either::B(ok(ServiceResponse::new(req, Response::NotFound().finish())))
|
||||
Either::A(ok(ServiceResponse::new(req, Response::NotFound().finish())))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
146
src/config.rs
146
src/config.rs
@@ -5,11 +5,18 @@ use std::rc::Rc;
|
||||
use actix_http::Extensions;
|
||||
use actix_router::ResourceDef;
|
||||
use actix_service::{boxed, IntoNewService, NewService};
|
||||
use futures::IntoFuture;
|
||||
|
||||
use crate::data::{Data, DataFactory};
|
||||
use crate::error::Error;
|
||||
use crate::guard::Guard;
|
||||
use crate::resource::Resource;
|
||||
use crate::rmap::ResourceMap;
|
||||
use crate::service::{ServiceRequest, ServiceResponse};
|
||||
use crate::route::Route;
|
||||
use crate::service::{
|
||||
HttpServiceFactory, ServiceFactory, ServiceFactoryWrapper, ServiceRequest,
|
||||
ServiceResponse,
|
||||
};
|
||||
|
||||
type Guards = Vec<Box<Guard>>;
|
||||
type HttpNewService<P> =
|
||||
@@ -157,3 +164,140 @@ impl Default for AppConfigInner {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Router config. It is used for external configuration.
|
||||
/// Part of application configuration could be offloaded
|
||||
/// to set of external methods. This could help with
|
||||
/// modularization of big application configuration.
|
||||
pub struct RouterConfig<P: 'static> {
|
||||
pub(crate) services: Vec<Box<ServiceFactory<P>>>,
|
||||
pub(crate) data: Vec<Box<DataFactory>>,
|
||||
pub(crate) external: Vec<ResourceDef>,
|
||||
}
|
||||
|
||||
impl<P: 'static> RouterConfig<P> {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
services: Vec::new(),
|
||||
data: Vec::new(),
|
||||
external: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set application data. Applicatin data could be accessed
|
||||
/// by using `Data<T>` extractor where `T` is data type.
|
||||
///
|
||||
/// This is same as `App::data()` method.
|
||||
pub fn data<S: 'static>(&mut self, data: S) -> &mut Self {
|
||||
self.data.push(Box::new(Data::new(data)));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set application data factory. This function is
|
||||
/// similar to `.data()` but it accepts data factory. Data object get
|
||||
/// constructed asynchronously during application initialization.
|
||||
///
|
||||
/// This is same as `App::data_dactory()` method.
|
||||
pub fn data_factory<F, R>(&mut self, data: F) -> &mut Self
|
||||
where
|
||||
F: Fn() -> R + 'static,
|
||||
R: IntoFuture + 'static,
|
||||
R::Error: std::fmt::Debug,
|
||||
{
|
||||
self.data.push(Box::new(data));
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure route for a specific path.
|
||||
///
|
||||
/// This is same as `App::route()` method.
|
||||
pub fn route(&mut self, path: &str, mut route: Route<P>) -> &mut Self {
|
||||
self.service(
|
||||
Resource::new(path)
|
||||
.add_guards(route.take_guards())
|
||||
.route(route),
|
||||
)
|
||||
}
|
||||
|
||||
/// Register http service.
|
||||
///
|
||||
/// This is same as `App::service()` method.
|
||||
pub fn service<F>(&mut self, factory: F) -> &mut Self
|
||||
where
|
||||
F: HttpServiceFactory<P> + 'static,
|
||||
{
|
||||
self.services
|
||||
.push(Box::new(ServiceFactoryWrapper::new(factory)));
|
||||
self
|
||||
}
|
||||
|
||||
/// Register an external resource.
|
||||
///
|
||||
/// External resources are useful for URL generation purposes only
|
||||
/// and are never considered for matching at request time. Calls to
|
||||
/// `HttpRequest::url_for()` will work as expected.
|
||||
///
|
||||
/// This is same as `App::external_service()` method.
|
||||
pub fn external_resource<N, U>(&mut self, name: N, url: U) -> &mut Self
|
||||
where
|
||||
N: AsRef<str>,
|
||||
U: AsRef<str>,
|
||||
{
|
||||
let mut rdef = ResourceDef::new(url.as_ref());
|
||||
*rdef.name_mut() = name.as_ref().to_string();
|
||||
self.external.push(rdef);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_service::Service;
|
||||
|
||||
use super::*;
|
||||
use crate::http::StatusCode;
|
||||
use crate::test::{block_on, init_service, TestRequest};
|
||||
use crate::{web, App, HttpResponse};
|
||||
|
||||
#[test]
|
||||
fn test_data() {
|
||||
let cfg = |cfg: &mut RouterConfig<_>| {
|
||||
cfg.data(10usize);
|
||||
};
|
||||
|
||||
let mut srv =
|
||||
init_service(App::new().configure(cfg).service(
|
||||
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
|
||||
));
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_data_factory() {
|
||||
let cfg = |cfg: &mut RouterConfig<_>| {
|
||||
cfg.data_factory(|| Ok::<_, ()>(10usize));
|
||||
};
|
||||
|
||||
let mut srv =
|
||||
init_service(App::new().configure(cfg).service(
|
||||
web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()),
|
||||
));
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let cfg2 = |cfg: &mut RouterConfig<_>| {
|
||||
cfg.data_factory(|| Ok::<_, ()>(10u32));
|
||||
};
|
||||
let mut srv = init_service(
|
||||
App::new()
|
||||
.service(web::resource("/").to(|_: web::Data<usize>| HttpResponse::Ok()))
|
||||
.configure(cfg2),
|
||||
);
|
||||
let req = TestRequest::default().to_request();
|
||||
let resp = block_on(srv.call(req)).unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
@@ -5,8 +5,9 @@ use actix_http::error::{Error, ErrorInternalServerError};
|
||||
use actix_http::Extensions;
|
||||
use futures::{Async, Future, IntoFuture, Poll};
|
||||
|
||||
use crate::dev::Payload;
|
||||
use crate::extract::FromRequest;
|
||||
use crate::service::ServiceFromRequest;
|
||||
use crate::request::HttpRequest;
|
||||
|
||||
/// Application data factory
|
||||
pub(crate) trait DataFactory {
|
||||
@@ -91,8 +92,8 @@ impl<T: 'static, P> FromRequest<P> for Data<T> {
|
||||
type Future = Result<Self, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
|
||||
if let Some(st) = req.request().config().extensions().get::<Data<T>>() {
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
|
||||
if let Some(st) = req.app_config().extensions().get::<Data<T>>() {
|
||||
Ok(st.clone())
|
||||
} else {
|
||||
Err(ErrorInternalServerError(
|
||||
@@ -230,7 +231,7 @@ impl<T: 'static, P> FromRequest<P> for RouteData<T> {
|
||||
type Future = Result<Self, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
|
||||
if let Some(st) = req.route_data::<T>() {
|
||||
Ok(st.clone())
|
||||
} else {
|
||||
|
36
src/error.rs
36
src/error.rs
@@ -121,36 +121,6 @@ impl ResponseError for ReadlinesError {
|
||||
}
|
||||
}
|
||||
|
||||
/// A set of errors that can occur during parsing multipart streams
|
||||
#[derive(Debug, Display, From)]
|
||||
pub enum MultipartError {
|
||||
/// Content-Type header is not found
|
||||
#[display(fmt = "No Content-type header found")]
|
||||
NoContentType,
|
||||
/// Can not parse Content-Type header
|
||||
#[display(fmt = "Can not parse Content-Type header")]
|
||||
ParseContentType,
|
||||
/// Multipart boundary is not found
|
||||
#[display(fmt = "Multipart boundary is not found")]
|
||||
Boundary,
|
||||
/// Multipart stream is incomplete
|
||||
#[display(fmt = "Multipart stream is incomplete")]
|
||||
Incomplete,
|
||||
/// Error during field parsing
|
||||
#[display(fmt = "{}", _0)]
|
||||
Parse(ParseError),
|
||||
/// Payload error
|
||||
#[display(fmt = "{}", _0)]
|
||||
Payload(PayloadError),
|
||||
}
|
||||
|
||||
/// Return `BadRequest` for `MultipartError`
|
||||
impl ResponseError for MultipartError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
HttpResponse::new(StatusCode::BAD_REQUEST)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -180,10 +150,4 @@ mod tests {
|
||||
let resp: HttpResponse = ReadlinesError::EncodingError.error_response();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multipart_error() {
|
||||
let resp: HttpResponse = MultipartError::Boundary.error_response();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
110
src/extract.rs
110
src/extract.rs
@@ -4,7 +4,8 @@ use actix_http::error::Error;
|
||||
use futures::future::ok;
|
||||
use futures::{future, Async, Future, IntoFuture, Poll};
|
||||
|
||||
use crate::service::ServiceFromRequest;
|
||||
use crate::dev::Payload;
|
||||
use crate::request::HttpRequest;
|
||||
|
||||
/// Trait implemented by types that can be extracted from request.
|
||||
///
|
||||
@@ -17,7 +18,14 @@ pub trait FromRequest<P>: Sized {
|
||||
type Future: IntoFuture<Item = Self, Error = Self::Error>;
|
||||
|
||||
/// Convert request to a Self
|
||||
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future;
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future;
|
||||
|
||||
/// Convert request to a Self
|
||||
///
|
||||
/// This method uses `Payload::None` as payload stream.
|
||||
fn extract(req: &HttpRequest) -> Self::Future {
|
||||
Self::from_request(req, &mut Payload::None)
|
||||
}
|
||||
}
|
||||
|
||||
/// Optionally extract a field from the request
|
||||
@@ -28,7 +36,7 @@ pub trait FromRequest<P>: Sized {
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{web, dev, App, Error, FromRequest};
|
||||
/// use actix_web::{web, dev, App, Error, HttpRequest, FromRequest};
|
||||
/// use actix_web::error::ErrorBadRequest;
|
||||
/// use rand;
|
||||
///
|
||||
@@ -41,7 +49,7 @@ pub trait FromRequest<P>: Sized {
|
||||
/// type Error = Error;
|
||||
/// type Future = Result<Self, Self::Error>;
|
||||
///
|
||||
/// fn from_request(req: &mut dev::ServiceFromRequest<P>) -> Self::Future {
|
||||
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload<P>) -> Self::Future {
|
||||
/// if rand::random() {
|
||||
/// Ok(Thing { name: "thingy".into() })
|
||||
/// } else {
|
||||
@@ -76,14 +84,18 @@ where
|
||||
type Future = Box<Future<Item = Option<T>, Error = Error>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
|
||||
Box::new(T::from_request(req).into_future().then(|r| match r {
|
||||
Ok(v) => future::ok(Some(v)),
|
||||
Err(e) => {
|
||||
log::debug!("Error for Option<T> extractor: {}", e.into());
|
||||
future::ok(None)
|
||||
}
|
||||
}))
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future {
|
||||
Box::new(
|
||||
T::from_request(req, payload)
|
||||
.into_future()
|
||||
.then(|r| match r {
|
||||
Ok(v) => future::ok(Some(v)),
|
||||
Err(e) => {
|
||||
log::debug!("Error for Option<T> extractor: {}", e.into());
|
||||
future::ok(None)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +107,7 @@ where
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate serde_derive;
|
||||
/// use actix_web::{web, dev, App, Result, Error, FromRequest};
|
||||
/// use actix_web::{web, dev, App, Result, Error, HttpRequest, FromRequest};
|
||||
/// use actix_web::error::ErrorBadRequest;
|
||||
/// use rand;
|
||||
///
|
||||
@@ -108,7 +120,7 @@ where
|
||||
/// type Error = Error;
|
||||
/// type Future = Result<Thing, Error>;
|
||||
///
|
||||
/// fn from_request(req: &mut dev::ServiceFromRequest<P>) -> Self::Future {
|
||||
/// fn from_request(req: &HttpRequest, payload: &mut dev::Payload<P>) -> Self::Future {
|
||||
/// if rand::random() {
|
||||
/// Ok(Thing { name: "thingy".into() })
|
||||
/// } else {
|
||||
@@ -141,11 +153,15 @@ where
|
||||
type Future = Box<Future<Item = Result<T, T::Error>, Error = Error>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
|
||||
Box::new(T::from_request(req).into_future().then(|res| match res {
|
||||
Ok(v) => ok(Ok(v)),
|
||||
Err(e) => ok(Err(e)),
|
||||
}))
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future {
|
||||
Box::new(
|
||||
T::from_request(req, payload)
|
||||
.into_future()
|
||||
.then(|res| match res {
|
||||
Ok(v) => ok(Ok(v)),
|
||||
Err(e) => ok(Err(e)),
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +170,7 @@ impl<P> FromRequest<P> for () {
|
||||
type Error = Error;
|
||||
type Future = Result<(), Error>;
|
||||
|
||||
fn from_request(_req: &mut ServiceFromRequest<P>) -> Self::Future {
|
||||
fn from_request(_: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -168,10 +184,10 @@ macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
|
||||
type Error = Error;
|
||||
type Future = $fut_type<P, $($T),+>;
|
||||
|
||||
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future {
|
||||
$fut_type {
|
||||
items: <($(Option<$T>,)+)>::default(),
|
||||
futs: ($($T::from_request(req).into_future(),)+),
|
||||
futs: ($($T::from_request(req, payload).into_future(),)+),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -247,25 +263,25 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_option() {
|
||||
let mut req = TestRequest::with_header(
|
||||
let (req, mut pl) = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
)
|
||||
.route_data(FormConfig::default().limit(4096))
|
||||
.to_from();
|
||||
.to_http_parts();
|
||||
|
||||
let r = block_on(Option::<Form<Info>>::from_request(&mut req)).unwrap();
|
||||
let r = block_on(Option::<Form<Info>>::from_request(&req, &mut pl)).unwrap();
|
||||
assert_eq!(r, None);
|
||||
|
||||
let mut req = TestRequest::with_header(
|
||||
let (req, mut pl) = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
)
|
||||
.header(header::CONTENT_LENGTH, "9")
|
||||
.set_payload(Bytes::from_static(b"hello=world"))
|
||||
.to_from();
|
||||
.to_http_parts();
|
||||
|
||||
let r = block_on(Option::<Form<Info>>::from_request(&mut req)).unwrap();
|
||||
let r = block_on(Option::<Form<Info>>::from_request(&req, &mut pl)).unwrap();
|
||||
assert_eq!(
|
||||
r,
|
||||
Some(Form(Info {
|
||||
@@ -273,29 +289,29 @@ mod tests {
|
||||
}))
|
||||
);
|
||||
|
||||
let mut req = TestRequest::with_header(
|
||||
let (req, mut pl) = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
)
|
||||
.header(header::CONTENT_LENGTH, "9")
|
||||
.set_payload(Bytes::from_static(b"bye=world"))
|
||||
.to_from();
|
||||
.to_http_parts();
|
||||
|
||||
let r = block_on(Option::<Form<Info>>::from_request(&mut req)).unwrap();
|
||||
let r = block_on(Option::<Form<Info>>::from_request(&req, &mut pl)).unwrap();
|
||||
assert_eq!(r, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_result() {
|
||||
let mut req = TestRequest::with_header(
|
||||
let (req, mut pl) = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
)
|
||||
.header(header::CONTENT_LENGTH, "11")
|
||||
.set_payload(Bytes::from_static(b"hello=world"))
|
||||
.to_from();
|
||||
.to_http_parts();
|
||||
|
||||
let r = block_on(Result::<Form<Info>, Error>::from_request(&mut req))
|
||||
let r = block_on(Result::<Form<Info>, Error>::from_request(&req, &mut pl))
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
@@ -305,15 +321,16 @@ mod tests {
|
||||
})
|
||||
);
|
||||
|
||||
let mut req = TestRequest::with_header(
|
||||
let (req, mut pl) = TestRequest::with_header(
|
||||
header::CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
)
|
||||
.header(header::CONTENT_LENGTH, "9")
|
||||
.set_payload(Bytes::from_static(b"bye=world"))
|
||||
.to_from();
|
||||
.to_http_parts();
|
||||
|
||||
let r = block_on(Result::<Form<Info>, Error>::from_request(&mut req)).unwrap();
|
||||
let r =
|
||||
block_on(Result::<Form<Info>, Error>::from_request(&req, &mut pl)).unwrap();
|
||||
assert!(r.is_err());
|
||||
}
|
||||
|
||||
@@ -336,37 +353,38 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_request_extract() {
|
||||
let mut req = TestRequest::with_uri("/name/user1/?id=test").to_from();
|
||||
let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
|
||||
|
||||
let resource = ResourceDef::new("/{key}/{value}/");
|
||||
resource.match_path(req.match_info_mut());
|
||||
|
||||
let s = Path::<MyStruct>::from_request(&mut req).unwrap();
|
||||
let (req, mut pl) = req.into_parts();
|
||||
let s = Path::<MyStruct>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(s.key, "name");
|
||||
assert_eq!(s.value, "user1");
|
||||
|
||||
let s = Path::<(String, String)>::from_request(&mut req).unwrap();
|
||||
let s = Path::<(String, String)>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(s.0, "name");
|
||||
assert_eq!(s.1, "user1");
|
||||
|
||||
let s = Query::<Id>::from_request(&mut req).unwrap();
|
||||
let s = Query::<Id>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(s.id, "test");
|
||||
|
||||
let mut req = TestRequest::with_uri("/name/32/").to_from();
|
||||
let mut req = TestRequest::with_uri("/name/32/").to_srv_request();
|
||||
let resource = ResourceDef::new("/{key}/{value}/");
|
||||
resource.match_path(req.match_info_mut());
|
||||
|
||||
let s = Path::<Test2>::from_request(&mut req).unwrap();
|
||||
let (req, mut pl) = req.into_parts();
|
||||
let s = Path::<Test2>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(s.as_ref().key, "name");
|
||||
assert_eq!(s.value, 32);
|
||||
|
||||
let s = Path::<(String, u8)>::from_request(&mut req).unwrap();
|
||||
let s = Path::<(String, u8)>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(s.0, "name");
|
||||
assert_eq!(s.1, 32);
|
||||
|
||||
let res = Path::<Vec<String>>::from_request(&mut req).unwrap();
|
||||
let res = Path::<Vec<String>>::from_request(&req, &mut pl).unwrap();
|
||||
assert_eq!(res[0], "name".to_owned());
|
||||
assert_eq!(res[1], "32".to_owned());
|
||||
}
|
||||
|
||||
}
|
||||
|
159
src/handler.rs
159
src/handler.rs
@@ -2,7 +2,7 @@ use std::cell::RefCell;
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
|
||||
use actix_http::{Error, Extensions, Response};
|
||||
use actix_http::{Error, Extensions, Payload, Response};
|
||||
use actix_service::{NewService, Service, Void};
|
||||
use futures::future::{ok, FutureResult};
|
||||
use futures::{try_ready, Async, Future, IntoFuture, Poll};
|
||||
@@ -10,7 +10,7 @@ use futures::{try_ready, Async, Future, IntoFuture, Poll};
|
||||
use crate::extract::FromRequest;
|
||||
use crate::request::HttpRequest;
|
||||
use crate::responder::Responder;
|
||||
use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse};
|
||||
use crate::service::{ServiceRequest, ServiceResponse};
|
||||
|
||||
/// Handler converter factory
|
||||
pub trait Factory<T, R>: Clone
|
||||
@@ -22,8 +22,8 @@ where
|
||||
|
||||
impl<F, R> Factory<(), R> for F
|
||||
where
|
||||
F: Fn() -> R + Clone + 'static,
|
||||
R: Responder + 'static,
|
||||
F: Fn() -> R + Clone,
|
||||
R: Responder,
|
||||
{
|
||||
fn call(&self, _: ()) -> R {
|
||||
(self)()
|
||||
@@ -52,40 +52,24 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<F, T, R> NewService for Handler<F, T, R>
|
||||
|
||||
impl<F, T, R> Clone for Handler<F, T, R>
|
||||
where
|
||||
F: Factory<T, R>,
|
||||
R: Responder + 'static,
|
||||
R: Responder,
|
||||
{
|
||||
type Request = (T, HttpRequest);
|
||||
type Response = ServiceResponse;
|
||||
type Error = Void;
|
||||
type InitError = ();
|
||||
type Service = HandlerService<F, T, R>;
|
||||
type Future = FutureResult<Self::Service, ()>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
ok(HandlerService {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
hnd: self.hnd.clone(),
|
||||
_t: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct HandlerService<F, T, R>
|
||||
impl<F, T, R> Service for Handler<F, T, R>
|
||||
where
|
||||
F: Factory<T, R>,
|
||||
R: Responder + 'static,
|
||||
{
|
||||
hnd: F,
|
||||
_t: PhantomData<(T, R)>,
|
||||
}
|
||||
|
||||
impl<F, T, R> Service for HandlerService<F, T, R>
|
||||
where
|
||||
F: Factory<T, R>,
|
||||
R: Responder + 'static,
|
||||
R: Responder,
|
||||
{
|
||||
type Request = (T, HttpRequest);
|
||||
type Response = ServiceResponse;
|
||||
@@ -184,41 +168,23 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<F, T, R> NewService for AsyncHandler<F, T, R>
|
||||
|
||||
impl<F, T, R> Clone for AsyncHandler<F, T, R>
|
||||
where
|
||||
F: AsyncFactory<T, R>,
|
||||
R: IntoFuture,
|
||||
R::Item: Into<Response>,
|
||||
R::Error: Into<Error>,
|
||||
{
|
||||
type Request = (T, HttpRequest);
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type InitError = ();
|
||||
type Service = AsyncHandlerService<F, T, R>;
|
||||
type Future = FutureResult<Self::Service, ()>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
ok(AsyncHandlerService {
|
||||
fn clone(&self) -> Self {
|
||||
AsyncHandler {
|
||||
hnd: self.hnd.clone(),
|
||||
_t: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct AsyncHandlerService<F, T, R>
|
||||
where
|
||||
F: AsyncFactory<T, R>,
|
||||
R: IntoFuture,
|
||||
R::Item: Into<Response>,
|
||||
R::Error: Into<Error>,
|
||||
{
|
||||
hnd: F,
|
||||
_t: PhantomData<(T, R)>,
|
||||
}
|
||||
|
||||
impl<F, T, R> Service for AsyncHandlerService<F, T, R>
|
||||
impl<F, T, R> Service for AsyncHandler<F, T, R>
|
||||
where
|
||||
F: AsyncFactory<T, R>,
|
||||
R: IntoFuture,
|
||||
@@ -227,7 +193,7 @@ where
|
||||
{
|
||||
type Request = (T, HttpRequest);
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type Error = Void;
|
||||
type Future = AsyncHandlerServiceResponse<R::Future>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
@@ -255,7 +221,7 @@ where
|
||||
T::Error: Into<Error>,
|
||||
{
|
||||
type Item = ServiceResponse;
|
||||
type Error = Error;
|
||||
type Error = Void;
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
match self.fut.poll() {
|
||||
@@ -276,87 +242,112 @@ where
|
||||
}
|
||||
|
||||
/// Extract arguments from request
|
||||
pub struct Extract<P, T: FromRequest<P>> {
|
||||
pub struct Extract<P, T: FromRequest<P>, S> {
|
||||
config: Rc<RefCell<Option<Rc<Extensions>>>>,
|
||||
service: S,
|
||||
_t: PhantomData<(P, T)>,
|
||||
}
|
||||
|
||||
impl<P, T: FromRequest<P>> Extract<P, T> {
|
||||
pub fn new(config: Rc<RefCell<Option<Rc<Extensions>>>>) -> Self {
|
||||
impl<P, T: FromRequest<P>, S> Extract<P, T, S> {
|
||||
pub fn new(config: Rc<RefCell<Option<Rc<Extensions>>>>, service: S) -> Self {
|
||||
Extract {
|
||||
config,
|
||||
service,
|
||||
_t: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P, T: FromRequest<P>> NewService for Extract<P, T> {
|
||||
impl<P, T: FromRequest<P>, S> NewService for Extract<P, T, S>
|
||||
where
|
||||
S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void>
|
||||
+ Clone,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = (T, HttpRequest);
|
||||
type Error = (Error, ServiceFromRequest<P>);
|
||||
type Response = ServiceResponse;
|
||||
type Error = (Error, ServiceRequest<P>);
|
||||
type InitError = ();
|
||||
type Service = ExtractService<P, T>;
|
||||
type Service = ExtractService<P, T, S>;
|
||||
type Future = FutureResult<Self::Service, ()>;
|
||||
|
||||
fn new_service(&self, _: &()) -> Self::Future {
|
||||
ok(ExtractService {
|
||||
_t: PhantomData,
|
||||
config: self.config.borrow().clone(),
|
||||
service: self.service.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtractService<P, T: FromRequest<P>> {
|
||||
pub struct ExtractService<P, T: FromRequest<P>, S> {
|
||||
config: Option<Rc<Extensions>>,
|
||||
service: S,
|
||||
_t: PhantomData<(P, T)>,
|
||||
}
|
||||
|
||||
impl<P, T: FromRequest<P>> Service for ExtractService<P, T> {
|
||||
impl<P, T: FromRequest<P>, S> Service for ExtractService<P, T, S>
|
||||
where
|
||||
S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void>
|
||||
+ Clone,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = (T, HttpRequest);
|
||||
type Error = (Error, ServiceFromRequest<P>);
|
||||
type Future = ExtractResponse<P, T>;
|
||||
type Response = ServiceResponse;
|
||||
type Error = (Error, ServiceRequest<P>);
|
||||
type Future = ExtractResponse<P, T, S>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
Ok(Async::Ready(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
|
||||
let mut req = ServiceFromRequest::new(req, self.config.clone());
|
||||
let (mut req, mut payload) = req.into_parts();
|
||||
req.set_route_data(self.config.clone());
|
||||
let fut = T::from_request(&req, &mut payload).into_future();
|
||||
|
||||
ExtractResponse {
|
||||
fut: T::from_request(&mut req).into_future(),
|
||||
req: Some(req),
|
||||
fut,
|
||||
fut_s: None,
|
||||
req: Some((req, payload)),
|
||||
service: self.service.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ExtractResponse<P, T: FromRequest<P>> {
|
||||
req: Option<ServiceFromRequest<P>>,
|
||||
pub struct ExtractResponse<P, T: FromRequest<P>, S: Service> {
|
||||
req: Option<(HttpRequest, Payload<P>)>,
|
||||
service: S,
|
||||
fut: <T::Future as IntoFuture>::Future,
|
||||
fut_s: Option<S::Future>,
|
||||
}
|
||||
|
||||
impl<P, T: FromRequest<P>> Future for ExtractResponse<P, T> {
|
||||
type Item = (T, HttpRequest);
|
||||
type Error = (Error, ServiceFromRequest<P>);
|
||||
impl<P, T: FromRequest<P>, S> Future for ExtractResponse<P, T, S>
|
||||
where
|
||||
S: Service<Request = (T, HttpRequest), Response = ServiceResponse, Error = Void>,
|
||||
{
|
||||
type Item = ServiceResponse;
|
||||
type Error = (Error, ServiceRequest<P>);
|
||||
|
||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||
let item = try_ready!(self
|
||||
.fut
|
||||
.poll()
|
||||
.map_err(|e| (e.into(), self.req.take().unwrap())));
|
||||
if let Some(ref mut fut) = self.fut_s {
|
||||
return fut.poll().map_err(|_| panic!());
|
||||
}
|
||||
|
||||
let req = self.req.take().unwrap();
|
||||
let req = req.into_request();
|
||||
let item = try_ready!(self.fut.poll().map_err(|e| {
|
||||
let (req, payload) = self.req.take().unwrap();
|
||||
let req = ServiceRequest::from_parts(req, payload);
|
||||
(e.into(), req)
|
||||
}));
|
||||
|
||||
Ok(Async::Ready((item, req)))
|
||||
self.fut_s = Some(self.service.call((item, self.req.take().unwrap().0)));
|
||||
self.poll()
|
||||
}
|
||||
}
|
||||
|
||||
/// FromRequest trait impl for tuples
|
||||
macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => {
|
||||
impl<Func, $($T,)+ Res> Factory<($($T,)+), Res> for Func
|
||||
where Func: Fn($($T,)+) -> Res + Clone + 'static,
|
||||
Res: Responder + 'static,
|
||||
where Func: Fn($($T,)+) -> Res + Clone,
|
||||
Res: Responder,
|
||||
{
|
||||
fn call(&self, param: ($($T,)+)) -> Res {
|
||||
(self)($(param.$n,)+)
|
||||
@@ -365,7 +356,7 @@ macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => {
|
||||
|
||||
impl<Func, $($T,)+ Res> AsyncFactory<($($T,)+), Res> for Func
|
||||
where Func: Fn($($T,)+) -> Res + Clone + 'static,
|
||||
Res: IntoFuture + 'static,
|
||||
Res: IntoFuture,
|
||||
Res::Item: Into<Response>,
|
||||
Res::Error: Into<Error>,
|
||||
{
|
||||
|
10
src/info.rs
10
src/info.rs
@@ -33,7 +33,7 @@ impl ConnectionInfo {
|
||||
let peer = None;
|
||||
|
||||
// load forwarded header
|
||||
for hdr in req.headers.get_all(header::FORWARDED) {
|
||||
for hdr in req.headers.get_all(&header::FORWARDED) {
|
||||
if let Ok(val) = hdr.to_str() {
|
||||
for pair in val.split(';') {
|
||||
for el in pair.split(',') {
|
||||
@@ -69,7 +69,7 @@ impl ConnectionInfo {
|
||||
if scheme.is_none() {
|
||||
if let Some(h) = req
|
||||
.headers
|
||||
.get(HeaderName::from_lowercase(X_FORWARDED_PROTO).unwrap())
|
||||
.get(&HeaderName::from_lowercase(X_FORWARDED_PROTO).unwrap())
|
||||
{
|
||||
if let Ok(h) = h.to_str() {
|
||||
scheme = h.split(',').next().map(|v| v.trim());
|
||||
@@ -87,14 +87,14 @@ impl ConnectionInfo {
|
||||
if host.is_none() {
|
||||
if let Some(h) = req
|
||||
.headers
|
||||
.get(HeaderName::from_lowercase(X_FORWARDED_HOST).unwrap())
|
||||
.get(&HeaderName::from_lowercase(X_FORWARDED_HOST).unwrap())
|
||||
{
|
||||
if let Ok(h) = h.to_str() {
|
||||
host = h.split(',').next().map(|v| v.trim());
|
||||
}
|
||||
}
|
||||
if host.is_none() {
|
||||
if let Some(h) = req.headers.get(header::HOST) {
|
||||
if let Some(h) = req.headers.get(&header::HOST) {
|
||||
host = h.to_str().ok();
|
||||
}
|
||||
if host.is_none() {
|
||||
@@ -110,7 +110,7 @@ impl ConnectionInfo {
|
||||
if remote.is_none() {
|
||||
if let Some(h) = req
|
||||
.headers
|
||||
.get(HeaderName::from_lowercase(X_FORWARDED_FOR).unwrap())
|
||||
.get(&HeaderName::from_lowercase(X_FORWARDED_FOR).unwrap())
|
||||
{
|
||||
if let Ok(h) = h.to_str() {
|
||||
remote = h.split(',').next().map(|v| v.trim());
|
||||
|
@@ -138,12 +138,9 @@ pub mod dev {
|
||||
pub use crate::config::{AppConfig, ServiceConfig};
|
||||
pub use crate::info::ConnectionInfo;
|
||||
pub use crate::rmap::ResourceMap;
|
||||
pub use crate::service::{
|
||||
HttpServiceFactory, ServiceFromRequest, ServiceRequest, ServiceResponse,
|
||||
};
|
||||
pub use crate::service::{HttpServiceFactory, ServiceRequest, ServiceResponse};
|
||||
pub use crate::types::form::UrlEncoded;
|
||||
pub use crate::types::json::JsonBody;
|
||||
pub use crate::types::payload::HttpMessageBody;
|
||||
pub use crate::types::readlines::Readlines;
|
||||
|
||||
pub use actix_http::body::{Body, BodySize, MessageBody, ResponseBody};
|
||||
@@ -190,5 +187,7 @@ pub mod client {
|
||||
pub use awc::error::{
|
||||
ConnectError, InvalidUrl, PayloadError, SendRequestError, WsClientError,
|
||||
};
|
||||
pub use awc::{test, Client, ClientBuilder, ClientRequest, ClientResponse};
|
||||
pub use awc::{
|
||||
test, Client, ClientBuilder, ClientRequest, ClientResponse, Connector,
|
||||
};
|
||||
}
|
||||
|
@@ -70,10 +70,8 @@ impl Default for Compress {
|
||||
|
||||
impl<S, P, B> Transform<S> for Compress
|
||||
where
|
||||
P: 'static,
|
||||
B: MessageBody,
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S::Future: 'static,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = ServiceResponse<Encoder<B>>;
|
||||
@@ -97,10 +95,8 @@ pub struct CompressMiddleware<S> {
|
||||
|
||||
impl<S, P, B> Service for CompressMiddleware<S>
|
||||
where
|
||||
P: 'static,
|
||||
B: MessageBody,
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S::Future: 'static,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = ServiceResponse<Encoder<B>>;
|
||||
@@ -113,7 +109,7 @@ where
|
||||
|
||||
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
|
||||
// negotiate content-encoding
|
||||
let encoding = if let Some(val) = req.headers().get(ACCEPT_ENCODING) {
|
||||
let encoding = if let Some(val) = req.headers().get(&ACCEPT_ENCODING) {
|
||||
if let Ok(enc) = val.to_str() {
|
||||
AcceptEncoding::parse(enc, self.encoding)
|
||||
} else {
|
||||
@@ -134,10 +130,8 @@ where
|
||||
#[doc(hidden)]
|
||||
pub struct CompressResponse<S, P, B>
|
||||
where
|
||||
P: 'static,
|
||||
B: MessageBody,
|
||||
S: Service,
|
||||
S::Future: 'static,
|
||||
B: MessageBody,
|
||||
{
|
||||
fut: S::Future,
|
||||
encoding: ContentEncoding,
|
||||
@@ -146,10 +140,8 @@ where
|
||||
|
||||
impl<S, P, B> Future for CompressResponse<S, P, B>
|
||||
where
|
||||
P: 'static,
|
||||
B: MessageBody,
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S::Future: 'static,
|
||||
{
|
||||
type Item = ServiceResponse<Encoder<B>>;
|
||||
type Error = S::Error;
|
||||
|
@@ -477,8 +477,9 @@ fn cors<'a>(
|
||||
|
||||
impl<S, P, B> IntoTransform<CorsFactory, S> for Cors
|
||||
where
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>> + 'static,
|
||||
P: 'static,
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S::Future: 'static,
|
||||
S::Error: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
fn into_transform(self) -> CorsFactory {
|
||||
@@ -541,7 +542,6 @@ where
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S::Future: 'static,
|
||||
S::Error: 'static,
|
||||
P: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
@@ -584,7 +584,7 @@ struct Inner {
|
||||
|
||||
impl Inner {
|
||||
fn validate_origin(&self, req: &RequestHead) -> Result<(), CorsError> {
|
||||
if let Some(hdr) = req.headers().get(header::ORIGIN) {
|
||||
if let Some(hdr) = req.headers().get(&header::ORIGIN) {
|
||||
if let Ok(origin) = hdr.to_str() {
|
||||
return match self.origins {
|
||||
AllOrSome::All => Ok(()),
|
||||
@@ -608,7 +608,7 @@ impl Inner {
|
||||
AllOrSome::All => {
|
||||
if self.send_wildcard {
|
||||
Some(HeaderValue::from_static("*"))
|
||||
} else if let Some(origin) = req.headers().get(header::ORIGIN) {
|
||||
} else if let Some(origin) = req.headers().get(&header::ORIGIN) {
|
||||
Some(origin.clone())
|
||||
} else {
|
||||
None
|
||||
@@ -617,7 +617,7 @@ impl Inner {
|
||||
AllOrSome::Some(ref origins) => {
|
||||
if let Some(origin) =
|
||||
req.headers()
|
||||
.get(header::ORIGIN)
|
||||
.get(&header::ORIGIN)
|
||||
.filter(|o| match o.to_str() {
|
||||
Ok(os) => origins.contains(os),
|
||||
_ => false,
|
||||
@@ -632,7 +632,7 @@ impl Inner {
|
||||
}
|
||||
|
||||
fn validate_allowed_method(&self, req: &RequestHead) -> Result<(), CorsError> {
|
||||
if let Some(hdr) = req.headers().get(header::ACCESS_CONTROL_REQUEST_METHOD) {
|
||||
if let Some(hdr) = req.headers().get(&header::ACCESS_CONTROL_REQUEST_METHOD) {
|
||||
if let Ok(meth) = hdr.to_str() {
|
||||
if let Ok(method) = Method::try_from(meth) {
|
||||
return self
|
||||
@@ -653,7 +653,7 @@ impl Inner {
|
||||
AllOrSome::All => Ok(()),
|
||||
AllOrSome::Some(ref allowed_headers) => {
|
||||
if let Some(hdr) =
|
||||
req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS)
|
||||
req.headers().get(&header::ACCESS_CONTROL_REQUEST_HEADERS)
|
||||
{
|
||||
if let Ok(headers) = hdr.to_str() {
|
||||
let mut hdrs = HashSet::new();
|
||||
@@ -683,7 +683,6 @@ where
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>>,
|
||||
S::Future: 'static,
|
||||
S::Error: 'static,
|
||||
P: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
@@ -721,7 +720,7 @@ where
|
||||
.unwrap(),
|
||||
)
|
||||
} else if let Some(hdr) =
|
||||
req.headers().get(header::ACCESS_CONTROL_REQUEST_HEADERS)
|
||||
req.headers().get(&header::ACCESS_CONTROL_REQUEST_HEADERS)
|
||||
{
|
||||
Some(hdr.clone())
|
||||
} else {
|
||||
@@ -760,7 +759,7 @@ where
|
||||
.into_body();
|
||||
|
||||
Either::A(ok(req.into_response(res)))
|
||||
} else if req.headers().contains_key(header::ORIGIN) {
|
||||
} else if req.headers().contains_key(&header::ORIGIN) {
|
||||
// Only check requests with a origin header.
|
||||
if let Err(e) = self.inner.validate_origin(req.head()) {
|
||||
return Either::A(ok(req.error_response(e)));
|
||||
@@ -791,7 +790,7 @@ where
|
||||
}
|
||||
if inner.vary_header {
|
||||
let value =
|
||||
if let Some(hdr) = res.headers_mut().get(header::VARY) {
|
||||
if let Some(hdr) = res.headers_mut().get(&header::VARY) {
|
||||
let mut val: Vec<u8> =
|
||||
Vec::with_capacity(hdr.as_bytes().len() + 8);
|
||||
val.extend(hdr.as_bytes());
|
||||
@@ -826,7 +825,6 @@ mod tests {
|
||||
+ 'static,
|
||||
S::Future: 'static,
|
||||
S::Error: 'static,
|
||||
P: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
block_on(
|
||||
@@ -895,20 +893,20 @@ mod tests {
|
||||
assert_eq!(
|
||||
&b"*"[..],
|
||||
resp.headers()
|
||||
.get(header::ACCESS_CONTROL_ALLOW_ORIGIN)
|
||||
.get(&header::ACCESS_CONTROL_ALLOW_ORIGIN)
|
||||
.unwrap()
|
||||
.as_bytes()
|
||||
);
|
||||
assert_eq!(
|
||||
&b"3600"[..],
|
||||
resp.headers()
|
||||
.get(header::ACCESS_CONTROL_MAX_AGE)
|
||||
.get(&header::ACCESS_CONTROL_MAX_AGE)
|
||||
.unwrap()
|
||||
.as_bytes()
|
||||
);
|
||||
let hdr = resp
|
||||
.headers()
|
||||
.get(header::ACCESS_CONTROL_ALLOW_HEADERS)
|
||||
.get(&header::ACCESS_CONTROL_ALLOW_HEADERS)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap();
|
||||
|
@@ -131,11 +131,11 @@ where
|
||||
// set response headers
|
||||
for (key, value) in inner.headers.iter() {
|
||||
if !res.headers().contains_key(key) {
|
||||
res.headers_mut().insert(key, value.clone());
|
||||
res.headers_mut().insert(key.clone(), value.clone());
|
||||
}
|
||||
}
|
||||
// default content-type
|
||||
if inner.ct && !res.headers().contains_key(CONTENT_TYPE) {
|
||||
if inner.ct && !res.headers().contains_key(&CONTENT_TYPE) {
|
||||
res.headers_mut().insert(
|
||||
CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/octet-stream"),
|
||||
|
@@ -58,10 +58,8 @@ use time::Duration;
|
||||
use crate::cookie::{Cookie, CookieJar, Key, SameSite};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::http::header::{self, HeaderValue};
|
||||
use crate::request::HttpRequest;
|
||||
use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse};
|
||||
use crate::FromRequest;
|
||||
use crate::HttpMessage;
|
||||
use crate::service::{ServiceRequest, ServiceResponse};
|
||||
use crate::{dev::Payload, FromRequest, HttpMessage, HttpRequest};
|
||||
|
||||
/// The extractor type to obtain your identity from a request.
|
||||
///
|
||||
@@ -147,8 +145,8 @@ impl<P> FromRequest<P> for Identity {
|
||||
type Future = Result<Identity, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
|
||||
Ok(Identity(req.request().clone()))
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
|
||||
Ok(Identity(req.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,10 +200,11 @@ impl<T> IdentityService<T> {
|
||||
|
||||
impl<S, T, P, B> Transform<S> for IdentityService<T>
|
||||
where
|
||||
P: 'static,
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>> + 'static,
|
||||
S::Future: 'static,
|
||||
S::Error: 'static,
|
||||
T: IdentityPolicy,
|
||||
P: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
@@ -235,6 +234,7 @@ where
|
||||
B: 'static,
|
||||
S: Service<Request = ServiceRequest<P>, Response = ServiceResponse<B>> + 'static,
|
||||
S::Future: 'static,
|
||||
S::Error: 'static,
|
||||
T: IdentityPolicy,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
|
@@ -14,6 +14,7 @@ use time;
|
||||
|
||||
use crate::dev::{BodySize, MessageBody, ResponseBody};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::http::{HeaderName, HttpTryFrom};
|
||||
use crate::service::{ServiceRequest, ServiceResponse};
|
||||
use crate::HttpResponse;
|
||||
|
||||
@@ -65,6 +66,8 @@ use crate::HttpResponse;
|
||||
///
|
||||
/// `%D` Time taken to serve the request, in milliseconds
|
||||
///
|
||||
/// `%U` Request URL
|
||||
///
|
||||
/// `%{FOO}i` request.headers['FOO']
|
||||
///
|
||||
/// `%{FOO}o` response.headers['FOO']
|
||||
@@ -272,7 +275,7 @@ impl Format {
|
||||
/// Returns `None` if the format string syntax is incorrect.
|
||||
pub fn new(s: &str) -> Format {
|
||||
log::trace!("Access log format: {}", s);
|
||||
let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([ioe])|[atPrsbTD]?)").unwrap();
|
||||
let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([ioe])|[atPrUsbTD]?)").unwrap();
|
||||
|
||||
let mut idx = 0;
|
||||
let mut results = Vec::new();
|
||||
@@ -286,8 +289,12 @@ impl Format {
|
||||
|
||||
if let Some(key) = cap.get(2) {
|
||||
results.push(match cap.get(3).unwrap().as_str() {
|
||||
"i" => FormatText::RequestHeader(key.as_str().to_owned()),
|
||||
"o" => FormatText::ResponseHeader(key.as_str().to_owned()),
|
||||
"i" => FormatText::RequestHeader(
|
||||
HeaderName::try_from(key.as_str()).unwrap(),
|
||||
),
|
||||
"o" => FormatText::ResponseHeader(
|
||||
HeaderName::try_from(key.as_str()).unwrap(),
|
||||
),
|
||||
"e" => FormatText::EnvironHeader(key.as_str().to_owned()),
|
||||
_ => unreachable!(),
|
||||
})
|
||||
@@ -300,6 +307,7 @@ impl Format {
|
||||
"r" => FormatText::RequestLine,
|
||||
"s" => FormatText::ResponseStatus,
|
||||
"b" => FormatText::ResponseSize,
|
||||
"U" => FormatText::UrlPath,
|
||||
"T" => FormatText::Time,
|
||||
"D" => FormatText::TimeMillis,
|
||||
_ => FormatText::Str(m.as_str().to_owned()),
|
||||
@@ -328,8 +336,9 @@ pub enum FormatText {
|
||||
Time,
|
||||
TimeMillis,
|
||||
RemoteAddr,
|
||||
RequestHeader(String),
|
||||
ResponseHeader(String),
|
||||
UrlPath,
|
||||
RequestHeader(HeaderName),
|
||||
ResponseHeader(HeaderName),
|
||||
EnvironHeader(String),
|
||||
}
|
||||
|
||||
@@ -413,6 +422,7 @@ impl FormatText {
|
||||
))
|
||||
};
|
||||
}
|
||||
FormatText::UrlPath => *self = FormatText::Str(format!("{}", req.path())),
|
||||
FormatText::RequestTime => {
|
||||
*self = FormatText::Str(format!(
|
||||
"{:?}",
|
||||
@@ -473,6 +483,37 @@ mod tests {
|
||||
let _res = block_on(srv.call(req));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_url_path() {
|
||||
let mut format = Format::new("%T %U");
|
||||
let req = TestRequest::with_header(
|
||||
header::USER_AGENT,
|
||||
header::HeaderValue::from_static("ACTIX-WEB"),
|
||||
)
|
||||
.uri("/test/route/yeah")
|
||||
.to_srv_request();
|
||||
|
||||
let now = time::now();
|
||||
for unit in &mut format.0 {
|
||||
unit.render_request(now, &req);
|
||||
}
|
||||
|
||||
let resp = HttpResponse::build(StatusCode::OK).force_close().finish();
|
||||
for unit in &mut format.0 {
|
||||
unit.render_response(&resp);
|
||||
}
|
||||
|
||||
let render = |fmt: &mut Formatter| {
|
||||
for unit in &format.0 {
|
||||
unit.render(fmt, 1024, now)?;
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
let s = format!("{}", FormatDisplay(&render));
|
||||
println!("{}", s);
|
||||
assert!(s.contains("/test/route/yeah"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_format() {
|
||||
let mut format = Format::default();
|
||||
|
131
src/request.rs
131
src/request.rs
@@ -1,4 +1,4 @@
|
||||
use std::cell::{Ref, RefMut};
|
||||
use std::cell::{Ref, RefCell, RefMut};
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
|
||||
@@ -7,36 +7,42 @@ use actix_http::{Error, Extensions, HttpMessage, Message, Payload, RequestHead};
|
||||
use actix_router::{Path, Url};
|
||||
|
||||
use crate::config::AppConfig;
|
||||
use crate::data::Data;
|
||||
use crate::data::{Data, RouteData};
|
||||
use crate::error::UrlGenerationError;
|
||||
use crate::extract::FromRequest;
|
||||
use crate::info::ConnectionInfo;
|
||||
use crate::rmap::ResourceMap;
|
||||
use crate::service::ServiceFromRequest;
|
||||
|
||||
#[derive(Clone)]
|
||||
/// An HTTP Request
|
||||
pub struct HttpRequest {
|
||||
pub struct HttpRequest(pub(crate) Rc<HttpRequestInner>);
|
||||
|
||||
pub(crate) struct HttpRequestInner {
|
||||
pub(crate) head: Message<RequestHead>,
|
||||
pub(crate) path: Path<Url>,
|
||||
rmap: Rc<ResourceMap>,
|
||||
config: AppConfig,
|
||||
route_data: Option<Rc<Extensions>>,
|
||||
pool: &'static HttpRequestPool,
|
||||
}
|
||||
|
||||
impl HttpRequest {
|
||||
#[inline]
|
||||
pub(crate) fn new(
|
||||
head: Message<RequestHead>,
|
||||
path: Path<Url>,
|
||||
head: Message<RequestHead>,
|
||||
rmap: Rc<ResourceMap>,
|
||||
config: AppConfig,
|
||||
pool: &'static HttpRequestPool,
|
||||
) -> HttpRequest {
|
||||
HttpRequest {
|
||||
HttpRequest(Rc::new(HttpRequestInner {
|
||||
head,
|
||||
path,
|
||||
rmap,
|
||||
config,
|
||||
}
|
||||
pool,
|
||||
route_data: None,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +50,14 @@ impl HttpRequest {
|
||||
/// This method returns reference to the request head
|
||||
#[inline]
|
||||
pub fn head(&self) -> &RequestHead {
|
||||
&self.head
|
||||
&self.0.head
|
||||
}
|
||||
|
||||
/// This method returns muttable reference to the request head.
|
||||
/// panics if multiple references of http request exists.
|
||||
#[inline]
|
||||
pub(crate) fn head_mut(&mut self) -> &mut RequestHead {
|
||||
&mut Rc::get_mut(&mut self.0).unwrap().head
|
||||
}
|
||||
|
||||
/// Request's uri.
|
||||
@@ -97,23 +110,12 @@ impl HttpRequest {
|
||||
/// access the matched value for that segment.
|
||||
#[inline]
|
||||
pub fn match_info(&self) -> &Path<Url> {
|
||||
&self.path
|
||||
&self.0.path
|
||||
}
|
||||
|
||||
/// App config
|
||||
#[inline]
|
||||
pub fn config(&self) -> &AppConfig {
|
||||
&self.config
|
||||
}
|
||||
|
||||
/// Get an application data stored with `App::data()` method during
|
||||
/// application configuration.
|
||||
pub fn app_data<T: 'static>(&self) -> Option<Data<T>> {
|
||||
if let Some(st) = self.config.extensions().get::<Data<T>>() {
|
||||
Some(st.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
pub(crate) fn match_info_mut(&mut self) -> &mut Path<Url> {
|
||||
&mut Rc::get_mut(&mut self.0).unwrap().path
|
||||
}
|
||||
|
||||
/// Request extensions
|
||||
@@ -156,7 +158,7 @@ impl HttpRequest {
|
||||
U: IntoIterator<Item = I>,
|
||||
I: AsRef<str>,
|
||||
{
|
||||
self.rmap.url_for(&self, name, elements)
|
||||
self.0.rmap.url_for(&self, name, elements)
|
||||
}
|
||||
|
||||
/// Generate url for named resource
|
||||
@@ -171,7 +173,37 @@ impl HttpRequest {
|
||||
/// Get *ConnectionInfo* for the current request.
|
||||
#[inline]
|
||||
pub fn connection_info(&self) -> Ref<ConnectionInfo> {
|
||||
ConnectionInfo::get(self.head(), &*self.config())
|
||||
ConnectionInfo::get(self.head(), &*self.app_config())
|
||||
}
|
||||
|
||||
/// App config
|
||||
#[inline]
|
||||
pub fn app_config(&self) -> &AppConfig {
|
||||
&self.0.config
|
||||
}
|
||||
|
||||
/// Get an application data stored with `App::data()` method during
|
||||
/// application configuration.
|
||||
pub fn app_data<T: 'static>(&self) -> Option<Data<T>> {
|
||||
if let Some(st) = self.0.config.extensions().get::<Data<T>>() {
|
||||
Some(st.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Load route data. Route data could be set during
|
||||
/// route configuration with `Route::data()` method.
|
||||
pub fn route_data<T: 'static>(&self) -> Option<&RouteData<T>> {
|
||||
if let Some(ref ext) = self.0.route_data {
|
||||
ext.get::<RouteData<T>>()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_route_data(&mut self, data: Option<Rc<Extensions>>) {
|
||||
Rc::get_mut(&mut self.0).unwrap().route_data = data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,13 +219,13 @@ impl HttpMessage for HttpRequest {
|
||||
/// Request extensions
|
||||
#[inline]
|
||||
fn extensions(&self) -> Ref<Extensions> {
|
||||
self.head.extensions()
|
||||
self.0.head.extensions()
|
||||
}
|
||||
|
||||
/// Mutable reference to a the request's extensions
|
||||
#[inline]
|
||||
fn extensions_mut(&self) -> RefMut<Extensions> {
|
||||
self.head.extensions_mut()
|
||||
self.0.head.extensions_mut()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -202,6 +234,17 @@ impl HttpMessage for HttpRequest {
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for HttpRequest {
|
||||
fn drop(&mut self) {
|
||||
if Rc::strong_count(&self.0) == 1 {
|
||||
let v = &mut self.0.pool.0.borrow_mut();
|
||||
if v.len() < 128 {
|
||||
v.push(self.0.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// It is possible to get `HttpRequest` as an extractor handler parameter
|
||||
///
|
||||
/// ## Example
|
||||
@@ -227,8 +270,8 @@ impl<P> FromRequest<P> for HttpRequest {
|
||||
type Future = Result<Self, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
|
||||
Ok(req.request().clone())
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
|
||||
Ok(req.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,8 +280,8 @@ impl fmt::Debug for HttpRequest {
|
||||
writeln!(
|
||||
f,
|
||||
"\nHttpRequest {:?} {}:{}",
|
||||
self.head.version,
|
||||
self.head.method,
|
||||
self.0.head.version,
|
||||
self.0.head.method,
|
||||
self.path()
|
||||
)?;
|
||||
if !self.query_string().is_empty() {
|
||||
@@ -255,6 +298,26 @@ impl fmt::Debug for HttpRequest {
|
||||
}
|
||||
}
|
||||
|
||||
/// Request's objects pool
|
||||
pub(crate) struct HttpRequestPool(RefCell<Vec<Rc<HttpRequestInner>>>);
|
||||
|
||||
impl HttpRequestPool {
|
||||
pub(crate) fn create() -> &'static HttpRequestPool {
|
||||
let pool = HttpRequestPool(RefCell::new(Vec::with_capacity(128)));
|
||||
Box::leak(Box::new(pool))
|
||||
}
|
||||
|
||||
/// Get message from the pool
|
||||
#[inline]
|
||||
pub(crate) fn get_request(&self) -> Option<HttpRequest> {
|
||||
if let Some(inner) = self.0.borrow_mut().pop() {
|
||||
Some(HttpRequest(inner))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -286,10 +349,10 @@ mod tests {
|
||||
{
|
||||
let cookies = req.cookies().unwrap();
|
||||
assert_eq!(cookies.len(), 2);
|
||||
assert_eq!(cookies[0].name(), "cookie1");
|
||||
assert_eq!(cookies[0].value(), "value1");
|
||||
assert_eq!(cookies[1].name(), "cookie2");
|
||||
assert_eq!(cookies[1].value(), "value2");
|
||||
assert_eq!(cookies[0].name(), "cookie2");
|
||||
assert_eq!(cookies[0].value(), "value2");
|
||||
assert_eq!(cookies[1].name(), "cookie1");
|
||||
assert_eq!(cookies[1].value(), "value1");
|
||||
}
|
||||
|
||||
let cookie = req.cookie("cookie1");
|
||||
|
@@ -487,11 +487,8 @@ impl<P> Service for ResourceService<P> {
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type Future = Either<
|
||||
FutureResult<ServiceResponse, Error>,
|
||||
Box<Future<Item = ServiceResponse, Error = Error>>,
|
||||
Either<
|
||||
Box<Future<Item = Self::Response, Error = Self::Error>>,
|
||||
FutureResult<Self::Response, Self::Error>,
|
||||
>,
|
||||
>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
@@ -501,17 +498,17 @@ impl<P> Service for ResourceService<P> {
|
||||
fn call(&mut self, mut req: ServiceRequest<P>) -> Self::Future {
|
||||
for route in self.routes.iter_mut() {
|
||||
if route.check(&mut req) {
|
||||
return Either::A(route.call(req));
|
||||
return route.call(req);
|
||||
}
|
||||
}
|
||||
if let Some(ref mut default) = self.default {
|
||||
Either::B(Either::A(default.call(req)))
|
||||
default.call(req)
|
||||
} else {
|
||||
let req = req.into_parts().0;
|
||||
Either::B(Either::B(ok(ServiceResponse::new(
|
||||
Either::A(ok(ServiceResponse::new(
|
||||
req,
|
||||
Response::MethodNotAllowed().finish(),
|
||||
))))
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
64
src/route.rs
64
src/route.rs
@@ -4,6 +4,7 @@ use std::rc::Rc;
|
||||
|
||||
use actix_http::{http::Method, Error, Extensions, Response};
|
||||
use actix_service::{NewService, Service};
|
||||
use futures::future::{ok, Either, FutureResult};
|
||||
use futures::{Async, Future, IntoFuture, Poll};
|
||||
|
||||
use crate::data::RouteData;
|
||||
@@ -11,7 +12,7 @@ use crate::extract::FromRequest;
|
||||
use crate::guard::{self, Guard};
|
||||
use crate::handler::{AsyncFactory, AsyncHandler, Extract, Factory, Handler};
|
||||
use crate::responder::Responder;
|
||||
use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse};
|
||||
use crate::service::{ServiceRequest, ServiceResponse};
|
||||
use crate::HttpResponse;
|
||||
|
||||
type BoxedRouteService<Req, Res> = Box<
|
||||
@@ -19,7 +20,10 @@ type BoxedRouteService<Req, Res> = Box<
|
||||
Request = Req,
|
||||
Response = Res,
|
||||
Error = Error,
|
||||
Future = Box<Future<Item = Res, Error = Error>>,
|
||||
Future = Either<
|
||||
FutureResult<Res, Error>,
|
||||
Box<Future<Item = Res, Error = Error>>,
|
||||
>,
|
||||
>,
|
||||
>;
|
||||
|
||||
@@ -50,11 +54,10 @@ impl<P: 'static> Route<P> {
|
||||
pub fn new() -> Route<P> {
|
||||
let data_ref = Rc::new(RefCell::new(None));
|
||||
Route {
|
||||
service: Box::new(RouteNewService::new(
|
||||
Extract::new(data_ref.clone()).and_then(
|
||||
Handler::new(HttpResponse::NotFound).map_err(|_| panic!()),
|
||||
),
|
||||
)),
|
||||
service: Box::new(RouteNewService::new(Extract::new(
|
||||
data_ref.clone(),
|
||||
Handler::new(|| HttpResponse::NotFound()),
|
||||
))),
|
||||
guards: Rc::new(Vec::new()),
|
||||
data: None,
|
||||
data_ref,
|
||||
@@ -131,7 +134,10 @@ impl<P> Service for RouteService<P> {
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
|
||||
type Future = Either<
|
||||
FutureResult<Self::Response, Self::Error>,
|
||||
Box<Future<Item = Self::Response, Error = Self::Error>>,
|
||||
>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.service.poll_ready()
|
||||
@@ -235,10 +241,10 @@ impl<P: 'static> Route<P> {
|
||||
T: FromRequest<P> + 'static,
|
||||
R: Responder + 'static,
|
||||
{
|
||||
self.service = Box::new(RouteNewService::new(
|
||||
Extract::new(self.data_ref.clone())
|
||||
.and_then(Handler::new(handler).map_err(|_| panic!())),
|
||||
));
|
||||
self.service = Box::new(RouteNewService::new(Extract::new(
|
||||
self.data_ref.clone(),
|
||||
Handler::new(handler),
|
||||
)));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -277,10 +283,10 @@ impl<P: 'static> Route<P> {
|
||||
R::Item: Into<Response>,
|
||||
R::Error: Into<Error>,
|
||||
{
|
||||
self.service = Box::new(RouteNewService::new(
|
||||
Extract::new(self.data_ref.clone())
|
||||
.and_then(AsyncHandler::new(handler).map_err(|_| panic!())),
|
||||
));
|
||||
self.service = Box::new(RouteNewService::new(Extract::new(
|
||||
self.data_ref.clone(),
|
||||
AsyncHandler::new(handler),
|
||||
)));
|
||||
self
|
||||
}
|
||||
|
||||
@@ -317,7 +323,7 @@ impl<P: 'static> Route<P> {
|
||||
|
||||
struct RouteNewService<P, T>
|
||||
where
|
||||
T: NewService<Request = ServiceRequest<P>, Error = (Error, ServiceFromRequest<P>)>,
|
||||
T: NewService<Request = ServiceRequest<P>, Error = (Error, ServiceRequest<P>)>,
|
||||
{
|
||||
service: T,
|
||||
_t: PhantomData<P>,
|
||||
@@ -328,7 +334,7 @@ where
|
||||
T: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse,
|
||||
Error = (Error, ServiceFromRequest<P>),
|
||||
Error = (Error, ServiceRequest<P>),
|
||||
>,
|
||||
T::Future: 'static,
|
||||
T::Service: 'static,
|
||||
@@ -347,7 +353,7 @@ where
|
||||
T: NewService<
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse,
|
||||
Error = (Error, ServiceFromRequest<P>),
|
||||
Error = (Error, ServiceRequest<P>),
|
||||
>,
|
||||
T::Future: 'static,
|
||||
T::Service: 'static,
|
||||
@@ -388,23 +394,31 @@ where
|
||||
T: Service<
|
||||
Request = ServiceRequest<P>,
|
||||
Response = ServiceResponse,
|
||||
Error = (Error, ServiceFromRequest<P>),
|
||||
Error = (Error, ServiceRequest<P>),
|
||||
>,
|
||||
{
|
||||
type Request = ServiceRequest<P>;
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type Future = Box<Future<Item = Self::Response, Error = Self::Error>>;
|
||||
type Future = Either<
|
||||
FutureResult<Self::Response, Self::Error>,
|
||||
Box<Future<Item = Self::Response, Error = Self::Error>>,
|
||||
>;
|
||||
|
||||
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
|
||||
self.service.poll_ready().map_err(|(e, _)| e)
|
||||
}
|
||||
|
||||
fn call(&mut self, req: ServiceRequest<P>) -> Self::Future {
|
||||
Box::new(self.service.call(req).then(|res| match res {
|
||||
Ok(res) => Ok(res),
|
||||
Err((err, req)) => Ok(req.error_response(err)),
|
||||
}))
|
||||
let mut fut = self.service.call(req);
|
||||
match fut.poll() {
|
||||
Ok(Async::Ready(res)) => Either::A(ok(res)),
|
||||
Err((e, req)) => Either::A(ok(req.error_response(e))),
|
||||
Ok(Async::NotReady) => Either::B(Box::new(fut.then(|res| match res {
|
||||
Ok(res) => Ok(res),
|
||||
Err((err, req)) => Ok(req.error_response(err)),
|
||||
}))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -24,7 +24,10 @@ type Guards = Vec<Box<Guard>>;
|
||||
type HttpService<P> = BoxedService<ServiceRequest<P>, ServiceResponse, Error>;
|
||||
type HttpNewService<P> =
|
||||
BoxedNewService<(), ServiceRequest<P>, ServiceResponse, Error, ()>;
|
||||
type BoxedResponse = Box<Future<Item = ServiceResponse, Error = Error>>;
|
||||
type BoxedResponse = Either<
|
||||
FutureResult<ServiceResponse, Error>,
|
||||
Box<Future<Item = ServiceResponse, Error = Error>>,
|
||||
>;
|
||||
|
||||
/// Resources scope.
|
||||
///
|
||||
|
@@ -2,7 +2,7 @@ use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, io, net};
|
||||
|
||||
use actix_http::{body::MessageBody, HttpService, KeepAlive, Request, Response};
|
||||
use actix_http::{body::MessageBody, Error, HttpService, KeepAlive, Request, Response};
|
||||
use actix_rt::System;
|
||||
use actix_server::{Server, ServerBuilder};
|
||||
use actix_server_config::ServerConfig;
|
||||
@@ -53,7 +53,8 @@ where
|
||||
F: Fn() -> I + Send + Clone + 'static,
|
||||
I: IntoNewService<S, ServerConfig>,
|
||||
S: NewService<ServerConfig, Request = Request>,
|
||||
S::Error: fmt::Debug,
|
||||
S::Error: Into<Error>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
S::Service: 'static,
|
||||
B: MessageBody,
|
||||
@@ -72,7 +73,8 @@ where
|
||||
F: Fn() -> I + Send + Clone + 'static,
|
||||
I: IntoNewService<S, ServerConfig>,
|
||||
S: NewService<ServerConfig, Request = Request>,
|
||||
S::Error: fmt::Debug + 'static,
|
||||
S::Error: Into<Error>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
S::Service: 'static,
|
||||
B: MessageBody + 'static,
|
||||
@@ -442,7 +444,8 @@ where
|
||||
F: Fn() -> I + Send + Clone + 'static,
|
||||
I: IntoNewService<S, ServerConfig>,
|
||||
S: NewService<ServerConfig, Request = Request>,
|
||||
S::Error: fmt::Debug,
|
||||
S::Error: Into<Error>,
|
||||
S::InitError: fmt::Debug,
|
||||
S::Response: Into<Response<B>>,
|
||||
S::Service: 'static,
|
||||
B: MessageBody,
|
||||
|
121
src/service.rs
121
src/service.rs
@@ -1,21 +1,19 @@
|
||||
use std::cell::{Ref, RefMut};
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::rc::Rc;
|
||||
|
||||
use actix_http::body::{Body, MessageBody, ResponseBody};
|
||||
use actix_http::http::{HeaderMap, Method, StatusCode, Uri, Version};
|
||||
use actix_http::{
|
||||
Error, Extensions, HttpMessage, Payload, PayloadStream, Request, RequestHead,
|
||||
Response, ResponseHead,
|
||||
Error, Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response,
|
||||
ResponseHead,
|
||||
};
|
||||
use actix_router::{Path, Resource, Url};
|
||||
use futures::future::{ok, FutureResult, IntoFuture};
|
||||
|
||||
use crate::config::{AppConfig, ServiceConfig};
|
||||
use crate::data::RouteData;
|
||||
use crate::data::Data;
|
||||
use crate::request::HttpRequest;
|
||||
use crate::rmap::ResourceMap;
|
||||
|
||||
pub trait HttpServiceFactory<P> {
|
||||
fn register(self, config: &mut ServiceConfig<P>);
|
||||
@@ -56,19 +54,6 @@ pub struct ServiceRequest<P = PayloadStream> {
|
||||
}
|
||||
|
||||
impl<P> ServiceRequest<P> {
|
||||
pub(crate) fn new(
|
||||
path: Path<Url>,
|
||||
request: Request<P>,
|
||||
rmap: Rc<ResourceMap>,
|
||||
config: AppConfig,
|
||||
) -> Self {
|
||||
let (head, payload) = request.into_parts();
|
||||
ServiceRequest {
|
||||
payload,
|
||||
req: HttpRequest::new(head, path, rmap, config),
|
||||
}
|
||||
}
|
||||
|
||||
/// Construct service request from parts
|
||||
pub fn from_parts(req: HttpRequest, payload: Payload<P>) -> Self {
|
||||
ServiceRequest { req, payload }
|
||||
@@ -95,13 +80,13 @@ impl<P> ServiceRequest<P> {
|
||||
/// This method returns reference to the request head
|
||||
#[inline]
|
||||
pub fn head(&self) -> &RequestHead {
|
||||
&self.req.head
|
||||
&self.req.head()
|
||||
}
|
||||
|
||||
/// This method returns reference to the request head
|
||||
#[inline]
|
||||
pub fn head_mut(&mut self) -> &mut RequestHead {
|
||||
&mut self.req.head
|
||||
self.req.head_mut()
|
||||
}
|
||||
|
||||
/// Request's uri.
|
||||
@@ -160,18 +145,28 @@ impl<P> ServiceRequest<P> {
|
||||
/// access the matched value for that segment.
|
||||
#[inline]
|
||||
pub fn match_info(&self) -> &Path<Url> {
|
||||
&self.req.path
|
||||
self.req.match_info()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn match_info_mut(&mut self) -> &mut Path<Url> {
|
||||
&mut self.req.path
|
||||
self.req.match_info_mut()
|
||||
}
|
||||
|
||||
/// Service configuration
|
||||
#[inline]
|
||||
pub fn app_config(&self) -> &AppConfig {
|
||||
self.req.config()
|
||||
self.req.app_config()
|
||||
}
|
||||
|
||||
/// Get an application data stored with `App::data()` method during
|
||||
/// application configuration.
|
||||
pub fn app_data<T: 'static>(&self) -> Option<Data<T>> {
|
||||
if let Some(st) = self.req.app_config().extensions().get::<Data<T>>() {
|
||||
Some(st.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,13 +188,13 @@ impl<P> HttpMessage for ServiceRequest<P> {
|
||||
/// Request extensions
|
||||
#[inline]
|
||||
fn extensions(&self) -> Ref<Extensions> {
|
||||
self.req.head.extensions()
|
||||
self.req.extensions()
|
||||
}
|
||||
|
||||
/// Mutable reference to a the request's extensions
|
||||
#[inline]
|
||||
fn extensions_mut(&self) -> RefMut<Extensions> {
|
||||
self.req.head.extensions_mut()
|
||||
self.req.extensions_mut()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
@@ -231,82 +226,6 @@ impl<P> fmt::Debug for ServiceRequest<P> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ServiceFromRequest<P> {
|
||||
req: HttpRequest,
|
||||
payload: Payload<P>,
|
||||
data: Option<Rc<Extensions>>,
|
||||
}
|
||||
|
||||
impl<P> ServiceFromRequest<P> {
|
||||
pub(crate) fn new(req: ServiceRequest<P>, data: Option<Rc<Extensions>>) -> Self {
|
||||
Self {
|
||||
req: req.req,
|
||||
payload: req.payload,
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Get reference to inner HttpRequest
|
||||
pub fn request(&self) -> &HttpRequest {
|
||||
&self.req
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Convert this request into a HttpRequest
|
||||
pub fn into_request(self) -> HttpRequest {
|
||||
self.req
|
||||
}
|
||||
|
||||
#[inline]
|
||||
/// Get match information for this request
|
||||
pub fn match_info_mut(&mut self) -> &mut Path<Url> {
|
||||
&mut self.req.path
|
||||
}
|
||||
|
||||
/// Create service response for error
|
||||
#[inline]
|
||||
pub fn error_response<E: Into<Error>>(self, err: E) -> ServiceResponse {
|
||||
ServiceResponse::new(self.req, err.into().into())
|
||||
}
|
||||
|
||||
/// Load route data. Route data could be set during
|
||||
/// route configuration with `Route::data()` method.
|
||||
pub fn route_data<T: 'static>(&self) -> Option<&RouteData<T>> {
|
||||
if let Some(ref ext) = self.data {
|
||||
ext.get::<RouteData<T>>()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> HttpMessage for ServiceFromRequest<P> {
|
||||
type Stream = P;
|
||||
|
||||
#[inline]
|
||||
fn headers(&self) -> &HeaderMap {
|
||||
self.req.headers()
|
||||
}
|
||||
|
||||
/// Request extensions
|
||||
#[inline]
|
||||
fn extensions(&self) -> Ref<Extensions> {
|
||||
self.req.head.extensions()
|
||||
}
|
||||
|
||||
/// Mutable reference to a the request's extensions
|
||||
#[inline]
|
||||
fn extensions_mut(&self) -> RefMut<Extensions> {
|
||||
self.req.head.extensions_mut()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn take_payload(&mut self) -> Payload<Self::Stream> {
|
||||
std::mem::replace(&mut self.payload, Payload::None)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ServiceResponse<B = Body> {
|
||||
request: HttpRequest,
|
||||
response: Response<B>,
|
||||
|
59
src/test.rs
59
src/test.rs
@@ -16,9 +16,10 @@ use futures::future::{lazy, Future};
|
||||
|
||||
use crate::config::{AppConfig, AppConfigInner};
|
||||
use crate::data::RouteData;
|
||||
use crate::dev::Body;
|
||||
use crate::dev::{Body, Payload};
|
||||
use crate::request::HttpRequestPool;
|
||||
use crate::rmap::ResourceMap;
|
||||
use crate::service::{ServiceFromRequest, ServiceRequest, ServiceResponse};
|
||||
use crate::service::{ServiceRequest, ServiceResponse};
|
||||
use crate::{Error, HttpRequest, HttpResponse};
|
||||
|
||||
thread_local! {
|
||||
@@ -319,16 +320,24 @@ impl TestRequest {
|
||||
self
|
||||
}
|
||||
|
||||
/// Complete request creation and generate `Request` instance
|
||||
pub fn to_request(mut self) -> Request<PayloadStream> {
|
||||
self.req.finish()
|
||||
}
|
||||
|
||||
/// Complete request creation and generate `ServiceRequest` instance
|
||||
pub fn to_srv_request(mut self) -> ServiceRequest<PayloadStream> {
|
||||
let req = self.req.finish();
|
||||
let (head, payload) = self.req.finish().into_parts();
|
||||
|
||||
ServiceRequest::new(
|
||||
Path::new(Url::new(req.uri().clone())),
|
||||
req,
|
||||
let req = HttpRequest::new(
|
||||
Path::new(Url::new(head.uri.clone())),
|
||||
head,
|
||||
Rc::new(self.rmap),
|
||||
AppConfig::new(self.config),
|
||||
)
|
||||
HttpRequestPool::create(),
|
||||
);
|
||||
|
||||
ServiceRequest::from_parts(req, payload)
|
||||
}
|
||||
|
||||
/// Complete request creation and generate `ServiceResponse` instance
|
||||
@@ -336,36 +345,34 @@ impl TestRequest {
|
||||
self.to_srv_request().into_response(res)
|
||||
}
|
||||
|
||||
/// Complete request creation and generate `Request` instance
|
||||
pub fn to_request(mut self) -> Request<PayloadStream> {
|
||||
self.req.finish()
|
||||
}
|
||||
|
||||
/// Complete request creation and generate `HttpRequest` instance
|
||||
pub fn to_http_request(mut self) -> HttpRequest {
|
||||
let req = self.req.finish();
|
||||
let (head, _) = self.req.finish().into_parts();
|
||||
|
||||
ServiceRequest::new(
|
||||
Path::new(Url::new(req.uri().clone())),
|
||||
req,
|
||||
let mut req = HttpRequest::new(
|
||||
Path::new(Url::new(head.uri.clone())),
|
||||
head,
|
||||
Rc::new(self.rmap),
|
||||
AppConfig::new(self.config),
|
||||
)
|
||||
.into_parts()
|
||||
.0
|
||||
HttpRequestPool::create(),
|
||||
);
|
||||
req.set_route_data(Some(Rc::new(self.route_data)));
|
||||
req
|
||||
}
|
||||
|
||||
/// Complete request creation and generate `ServiceFromRequest` instance
|
||||
pub fn to_from(mut self) -> ServiceFromRequest<PayloadStream> {
|
||||
let req = self.req.finish();
|
||||
/// Complete request creation and generate `HttpRequest` and `Payload` instances
|
||||
pub fn to_http_parts(mut self) -> (HttpRequest, Payload) {
|
||||
let (head, payload) = self.req.finish().into_parts();
|
||||
|
||||
let req = ServiceRequest::new(
|
||||
Path::new(Url::new(req.uri().clone())),
|
||||
req,
|
||||
let mut req = HttpRequest::new(
|
||||
Path::new(Url::new(head.uri.clone())),
|
||||
head,
|
||||
Rc::new(self.rmap),
|
||||
AppConfig::new(self.config),
|
||||
HttpRequestPool::create(),
|
||||
);
|
||||
ServiceFromRequest::new(req, Some(Rc::new(self.route_data)))
|
||||
req.set_route_data(Some(Rc::new(self.route_data)));
|
||||
(req, payload)
|
||||
}
|
||||
|
||||
/// Runs the provided future, blocking the current thread until the future
|
||||
|
@@ -16,7 +16,6 @@ use crate::error::UrlencodedError;
|
||||
use crate::extract::FromRequest;
|
||||
use crate::http::header::CONTENT_LENGTH;
|
||||
use crate::request::HttpRequest;
|
||||
use crate::service::ServiceFromRequest;
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// Extract typed information from the request's body.
|
||||
@@ -79,15 +78,15 @@ where
|
||||
type Future = Box<Future<Item = Self, Error = Error>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
|
||||
let req2 = req.request().clone();
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future {
|
||||
let req2 = req.clone();
|
||||
let (limit, err) = req
|
||||
.route_data::<FormConfig>()
|
||||
.map(|c| (c.limit, c.ehandler.clone()))
|
||||
.unwrap_or((16384, None));
|
||||
|
||||
Box::new(
|
||||
UrlEncoded::new(req)
|
||||
UrlEncoded::new(req, payload)
|
||||
.limit(limit)
|
||||
.map_err(move |e| {
|
||||
if let Some(err) = err {
|
||||
@@ -183,8 +182,8 @@ impl Default for FormConfig {
|
||||
/// * content type is not `application/x-www-form-urlencoded`
|
||||
/// * content-length is greater than 32k
|
||||
///
|
||||
pub struct UrlEncoded<T: HttpMessage, U> {
|
||||
stream: Payload<T::Stream>,
|
||||
pub struct UrlEncoded<P, U> {
|
||||
stream: Payload<P>,
|
||||
limit: usize,
|
||||
length: Option<usize>,
|
||||
encoding: EncodingRef,
|
||||
@@ -192,13 +191,12 @@ pub struct UrlEncoded<T: HttpMessage, U> {
|
||||
fut: Option<Box<Future<Item = U, Error = UrlencodedError>>>,
|
||||
}
|
||||
|
||||
impl<T, U> UrlEncoded<T, U>
|
||||
impl<P, U> UrlEncoded<P, U>
|
||||
where
|
||||
T: HttpMessage,
|
||||
T::Stream: Stream<Item = Bytes, Error = PayloadError>,
|
||||
P: Stream<Item = Bytes, Error = PayloadError>,
|
||||
{
|
||||
/// Create a new future to URL encode a request
|
||||
pub fn new(req: &mut T) -> UrlEncoded<T, U> {
|
||||
pub fn new(req: &HttpRequest, payload: &mut Payload<P>) -> UrlEncoded<P, U> {
|
||||
// check content type
|
||||
if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" {
|
||||
return Self::err(UrlencodedError::ContentType);
|
||||
@@ -209,7 +207,7 @@ where
|
||||
};
|
||||
|
||||
let mut len = None;
|
||||
if let Some(l) = req.headers().get(CONTENT_LENGTH) {
|
||||
if let Some(l) = req.headers().get(&CONTENT_LENGTH) {
|
||||
if let Ok(s) = l.to_str() {
|
||||
if let Ok(l) = s.parse::<usize>() {
|
||||
len = Some(l)
|
||||
@@ -223,7 +221,7 @@ where
|
||||
|
||||
UrlEncoded {
|
||||
encoding,
|
||||
stream: req.take_payload(),
|
||||
stream: payload.take(),
|
||||
limit: 32_768,
|
||||
length: len,
|
||||
fut: None,
|
||||
@@ -249,10 +247,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Future for UrlEncoded<T, U>
|
||||
impl<P, U> Future for UrlEncoded<P, U>
|
||||
where
|
||||
T: HttpMessage,
|
||||
T::Stream: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
U: DeserializeOwned + 'static,
|
||||
{
|
||||
type Item = U;
|
||||
@@ -320,13 +317,13 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_form() {
|
||||
let mut req =
|
||||
let (req, mut pl) =
|
||||
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
|
||||
.header(CONTENT_LENGTH, "11")
|
||||
.set_payload(Bytes::from_static(b"hello=world"))
|
||||
.to_from();
|
||||
.to_http_parts();
|
||||
|
||||
let s = block_on(Form::<Info>::from_request(&mut req)).unwrap();
|
||||
let s = block_on(Form::<Info>::from_request(&req, &mut pl)).unwrap();
|
||||
assert_eq!(s.hello, "world");
|
||||
}
|
||||
|
||||
@@ -354,36 +351,36 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_urlencoded_error() {
|
||||
let mut req =
|
||||
let (req, mut pl) =
|
||||
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
|
||||
.header(CONTENT_LENGTH, "xxxx")
|
||||
.to_request();
|
||||
let info = block_on(UrlEncoded::<_, Info>::new(&mut req));
|
||||
.to_http_parts();
|
||||
let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl));
|
||||
assert!(eq(info.err().unwrap(), UrlencodedError::UnknownLength));
|
||||
|
||||
let mut req =
|
||||
let (req, mut pl) =
|
||||
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
|
||||
.header(CONTENT_LENGTH, "1000000")
|
||||
.to_request();
|
||||
let info = block_on(UrlEncoded::<_, Info>::new(&mut req));
|
||||
.to_http_parts();
|
||||
let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl));
|
||||
assert!(eq(info.err().unwrap(), UrlencodedError::Overflow));
|
||||
|
||||
let mut req = TestRequest::with_header(CONTENT_TYPE, "text/plain")
|
||||
let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "text/plain")
|
||||
.header(CONTENT_LENGTH, "10")
|
||||
.to_request();
|
||||
let info = block_on(UrlEncoded::<_, Info>::new(&mut req));
|
||||
.to_http_parts();
|
||||
let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl));
|
||||
assert!(eq(info.err().unwrap(), UrlencodedError::ContentType));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_urlencoded() {
|
||||
let mut req =
|
||||
let (req, mut pl) =
|
||||
TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded")
|
||||
.header(CONTENT_LENGTH, "11")
|
||||
.set_payload(Bytes::from_static(b"hello=world"))
|
||||
.to_request();
|
||||
.to_http_parts();
|
||||
|
||||
let info = block_on(UrlEncoded::<_, Info>::new(&mut req)).unwrap();
|
||||
let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl)).unwrap();
|
||||
assert_eq!(
|
||||
info,
|
||||
Info {
|
||||
@@ -391,15 +388,15 @@ mod tests {
|
||||
}
|
||||
);
|
||||
|
||||
let mut req = TestRequest::with_header(
|
||||
let (req, mut pl) = TestRequest::with_header(
|
||||
CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded; charset=utf-8",
|
||||
)
|
||||
.header(CONTENT_LENGTH, "11")
|
||||
.set_payload(Bytes::from_static(b"hello=world"))
|
||||
.to_request();
|
||||
.to_http_parts();
|
||||
|
||||
let info = block_on(UrlEncoded::<_, Info>::new(&mut req)).unwrap();
|
||||
let info = block_on(UrlEncoded::<_, Info>::new(&req, &mut pl)).unwrap();
|
||||
assert_eq!(
|
||||
info,
|
||||
Info {
|
||||
|
@@ -16,7 +16,6 @@ use crate::error::{Error, JsonPayloadError, PayloadError};
|
||||
use crate::extract::FromRequest;
|
||||
use crate::request::HttpRequest;
|
||||
use crate::responder::Responder;
|
||||
use crate::service::ServiceFromRequest;
|
||||
|
||||
/// Json helper
|
||||
///
|
||||
@@ -173,15 +172,15 @@ where
|
||||
type Future = Box<Future<Item = Self, Error = Error>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
|
||||
let req2 = req.request().clone();
|
||||
fn from_request(req: &HttpRequest, payload: &mut Payload<P>) -> Self::Future {
|
||||
let req2 = req.clone();
|
||||
let (limit, err) = req
|
||||
.route_data::<JsonConfig>()
|
||||
.map(|c| (c.limit, c.ehandler.clone()))
|
||||
.unwrap_or((32768, None));
|
||||
|
||||
Box::new(
|
||||
JsonBody::new(req)
|
||||
JsonBody::new(req, payload)
|
||||
.limit(limit)
|
||||
.map_err(move |e| {
|
||||
if let Some(err) = err {
|
||||
@@ -264,22 +263,21 @@ impl Default for JsonConfig {
|
||||
///
|
||||
/// * content type is not `application/json`
|
||||
/// * content length is greater than 256k
|
||||
pub struct JsonBody<T: HttpMessage, U> {
|
||||
pub struct JsonBody<P, U> {
|
||||
limit: usize,
|
||||
length: Option<usize>,
|
||||
stream: Payload<T::Stream>,
|
||||
stream: Payload<P>,
|
||||
err: Option<JsonPayloadError>,
|
||||
fut: Option<Box<Future<Item = U, Error = JsonPayloadError>>>,
|
||||
}
|
||||
|
||||
impl<T, U> JsonBody<T, U>
|
||||
impl<P, U> JsonBody<P, U>
|
||||
where
|
||||
T: HttpMessage,
|
||||
T::Stream: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
U: DeserializeOwned + 'static,
|
||||
{
|
||||
/// Create `JsonBody` for request.
|
||||
pub fn new(req: &mut T) -> Self {
|
||||
pub fn new(req: &HttpRequest, payload: &mut Payload<P>) -> Self {
|
||||
// check content-type
|
||||
let json = if let Ok(Some(mime)) = req.mime_type() {
|
||||
mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON)
|
||||
@@ -297,7 +295,7 @@ where
|
||||
}
|
||||
|
||||
let mut len = None;
|
||||
if let Some(l) = req.headers().get(CONTENT_LENGTH) {
|
||||
if let Some(l) = req.headers().get(&CONTENT_LENGTH) {
|
||||
if let Ok(s) = l.to_str() {
|
||||
if let Ok(l) = s.parse::<usize>() {
|
||||
len = Some(l)
|
||||
@@ -308,7 +306,7 @@ where
|
||||
JsonBody {
|
||||
limit: 262_144,
|
||||
length: len,
|
||||
stream: req.take_payload(),
|
||||
stream: payload.take(),
|
||||
fut: None,
|
||||
err: None,
|
||||
}
|
||||
@@ -321,10 +319,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Future for JsonBody<T, U>
|
||||
impl<P, U> Future for JsonBody<P, U>
|
||||
where
|
||||
T: HttpMessage,
|
||||
T::Stream: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
U: DeserializeOwned + 'static,
|
||||
{
|
||||
type Item = U;
|
||||
@@ -410,7 +407,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_extract() {
|
||||
let mut req = TestRequest::default()
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
@@ -420,9 +417,9 @@ mod tests {
|
||||
header::HeaderValue::from_static("16"),
|
||||
)
|
||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.to_from();
|
||||
.to_http_parts();
|
||||
|
||||
let s = block_on(Json::<MyObject>::from_request(&mut req)).unwrap();
|
||||
let s = block_on(Json::<MyObject>::from_request(&req, &mut pl)).unwrap();
|
||||
assert_eq!(s.name, "test");
|
||||
assert_eq!(
|
||||
s.into_inner(),
|
||||
@@ -431,7 +428,7 @@ mod tests {
|
||||
}
|
||||
);
|
||||
|
||||
let mut req = TestRequest::default()
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
@@ -442,12 +439,13 @@ mod tests {
|
||||
)
|
||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.route_data(JsonConfig::default().limit(10))
|
||||
.to_from();
|
||||
let s = block_on(Json::<MyObject>::from_request(&mut req));
|
||||
.to_http_parts();
|
||||
|
||||
let s = block_on(Json::<MyObject>::from_request(&req, &mut pl));
|
||||
assert!(format!("{}", s.err().unwrap())
|
||||
.contains("Json payload size is bigger than allowed."));
|
||||
|
||||
let mut req = TestRequest::default()
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
@@ -462,27 +460,27 @@ mod tests {
|
||||
.limit(10)
|
||||
.error_handler(|_, _| JsonPayloadError::ContentType.into()),
|
||||
)
|
||||
.to_from();
|
||||
let s = block_on(Json::<MyObject>::from_request(&mut req));
|
||||
.to_http_parts();
|
||||
let s = block_on(Json::<MyObject>::from_request(&req, &mut pl));
|
||||
assert!(format!("{}", s.err().unwrap()).contains("Content type error"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json_body() {
|
||||
let mut req = TestRequest::default().to_request();
|
||||
let json = block_on(JsonBody::<_, MyObject>::new(&mut req));
|
||||
let (req, mut pl) = TestRequest::default().to_http_parts();
|
||||
let json = block_on(JsonBody::<_, MyObject>::new(&req, &mut pl));
|
||||
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
|
||||
|
||||
let mut req = TestRequest::default()
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/text"),
|
||||
)
|
||||
.to_request();
|
||||
let json = block_on(JsonBody::<_, MyObject>::new(&mut req));
|
||||
.to_http_parts();
|
||||
let json = block_on(JsonBody::<_, MyObject>::new(&req, &mut pl));
|
||||
assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType));
|
||||
|
||||
let mut req = TestRequest::default()
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
@@ -491,12 +489,12 @@ mod tests {
|
||||
header::CONTENT_LENGTH,
|
||||
header::HeaderValue::from_static("10000"),
|
||||
)
|
||||
.to_request();
|
||||
.to_http_parts();
|
||||
|
||||
let json = block_on(JsonBody::<_, MyObject>::new(&mut req).limit(100));
|
||||
let json = block_on(JsonBody::<_, MyObject>::new(&req, &mut pl).limit(100));
|
||||
assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow));
|
||||
|
||||
let mut req = TestRequest::default()
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
header::HeaderValue::from_static("application/json"),
|
||||
@@ -506,9 +504,9 @@ mod tests {
|
||||
header::HeaderValue::from_static("16"),
|
||||
)
|
||||
.set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
|
||||
.to_request();
|
||||
.to_http_parts();
|
||||
|
||||
let json = block_on(JsonBody::<_, MyObject>::new(&mut req));
|
||||
let json = block_on(JsonBody::<_, MyObject>::new(&req, &mut pl));
|
||||
assert_eq!(
|
||||
json.ok().unwrap(),
|
||||
MyObject {
|
||||
|
@@ -2,7 +2,6 @@
|
||||
|
||||
pub(crate) mod form;
|
||||
pub(crate) mod json;
|
||||
mod multipart;
|
||||
mod path;
|
||||
pub(crate) mod payload;
|
||||
mod query;
|
||||
@@ -10,7 +9,6 @@ pub(crate) mod readlines;
|
||||
|
||||
pub use self::form::{Form, FormConfig};
|
||||
pub use self::json::{Json, JsonConfig};
|
||||
pub use self::multipart::{Multipart, MultipartField, MultipartItem};
|
||||
pub use self::path::Path;
|
||||
pub use self::payload::{Payload, PayloadConfig};
|
||||
pub use self::query::Query;
|
||||
|
@@ -6,8 +6,8 @@ use actix_http::error::{Error, ErrorNotFound};
|
||||
use actix_router::PathDeserializer;
|
||||
use serde::de;
|
||||
|
||||
use crate::dev::Payload;
|
||||
use crate::request::HttpRequest;
|
||||
use crate::service::ServiceFromRequest;
|
||||
use crate::FromRequest;
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
@@ -66,15 +66,6 @@ impl<T> Path<T> {
|
||||
pub fn into_inner(self) -> T {
|
||||
self.inner
|
||||
}
|
||||
|
||||
/// Extract path information from a request
|
||||
pub fn extract(req: &HttpRequest) -> Result<Path<T>, de::value::Error>
|
||||
where
|
||||
T: de::DeserializeOwned,
|
||||
{
|
||||
de::Deserialize::deserialize(PathDeserializer::new(req.match_info()))
|
||||
.map(|inner| Path { inner })
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for Path<T> {
|
||||
@@ -169,8 +160,10 @@ where
|
||||
type Future = Result<Self, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
|
||||
Self::extract(req.request()).map_err(ErrorNotFound)
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
|
||||
de::Deserialize::deserialize(PathDeserializer::new(req.match_info()))
|
||||
.map(|inner| Path { inner })
|
||||
.map_err(ErrorNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,25 +178,30 @@ mod tests {
|
||||
fn test_extract_path_single() {
|
||||
let resource = ResourceDef::new("/{value}/");
|
||||
|
||||
let mut req = TestRequest::with_uri("/32/").to_from();
|
||||
let mut req = TestRequest::with_uri("/32/").to_srv_request();
|
||||
resource.match_path(req.match_info_mut());
|
||||
|
||||
assert_eq!(*Path::<i8>::from_request(&mut req).unwrap(), 32);
|
||||
let (req, mut pl) = req.into_parts();
|
||||
assert_eq!(*Path::<i8>::from_request(&req, &mut pl).unwrap(), 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tuple_extract() {
|
||||
let resource = ResourceDef::new("/{key}/{value}/");
|
||||
|
||||
let mut req = TestRequest::with_uri("/name/user1/?id=test").to_from();
|
||||
let mut req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
|
||||
resource.match_path(req.match_info_mut());
|
||||
|
||||
let res = block_on(<(Path<(String, String)>,)>::from_request(&mut req)).unwrap();
|
||||
let (req, mut pl) = req.into_parts();
|
||||
let res =
|
||||
block_on(<(Path<(String, String)>,)>::from_request(&req, &mut pl)).unwrap();
|
||||
assert_eq!((res.0).0, "name");
|
||||
assert_eq!((res.0).1, "user1");
|
||||
|
||||
let res = block_on(
|
||||
<(Path<(String, String)>, Path<(String, String)>)>::from_request(&mut req),
|
||||
<(Path<(String, String)>, Path<(String, String)>)>::from_request(
|
||||
&req, &mut pl,
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!((res.0).0, "name");
|
||||
@@ -211,7 +209,7 @@ mod tests {
|
||||
assert_eq!((res.1).0, "name");
|
||||
assert_eq!((res.1).1, "user1");
|
||||
|
||||
let () = <()>::from_request(&mut req).unwrap();
|
||||
let () = <()>::from_request(&req, &mut pl).unwrap();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -10,9 +10,10 @@ use futures::future::{err, Either, FutureResult};
|
||||
use futures::{Future, Poll, Stream};
|
||||
use mime::Mime;
|
||||
|
||||
use crate::dev;
|
||||
use crate::extract::FromRequest;
|
||||
use crate::http::header;
|
||||
use crate::service::ServiceFromRequest;
|
||||
use crate::request::HttpRequest;
|
||||
|
||||
/// Payload extractor returns request 's payload stream.
|
||||
///
|
||||
@@ -92,8 +93,8 @@ where
|
||||
type Future = Result<Payload, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
|
||||
let pl = match req.take_payload() {
|
||||
fn from_request(_: &HttpRequest, payload: &mut dev::Payload<P>) -> Self::Future {
|
||||
let pl = match payload.take() {
|
||||
crate::dev::Payload::Stream(s) => {
|
||||
let pl: Box<dyn Stream<Item = Bytes, Error = PayloadError>> =
|
||||
Box::new(s);
|
||||
@@ -141,7 +142,7 @@ where
|
||||
Either<Box<Future<Item = Bytes, Error = Error>>, FutureResult<Bytes, Error>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
|
||||
fn from_request(req: &HttpRequest, payload: &mut dev::Payload<P>) -> Self::Future {
|
||||
let mut tmp;
|
||||
let cfg = if let Some(cfg) = req.route_data::<PayloadConfig>() {
|
||||
cfg
|
||||
@@ -155,7 +156,9 @@ where
|
||||
}
|
||||
|
||||
let limit = cfg.limit;
|
||||
Either::A(Box::new(HttpMessageBody::new(req).limit(limit).from_err()))
|
||||
Either::A(Box::new(
|
||||
HttpMessageBody::new(req, payload).limit(limit).from_err(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +197,7 @@ where
|
||||
Either<Box<Future<Item = String, Error = Error>>, FutureResult<String, Error>>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
|
||||
fn from_request(req: &HttpRequest, payload: &mut dev::Payload<P>) -> Self::Future {
|
||||
let mut tmp;
|
||||
let cfg = if let Some(cfg) = req.route_data::<PayloadConfig>() {
|
||||
cfg
|
||||
@@ -216,7 +219,7 @@ where
|
||||
let limit = cfg.limit;
|
||||
|
||||
Either::A(Box::new(
|
||||
HttpMessageBody::new(req)
|
||||
HttpMessageBody::new(req, payload)
|
||||
.limit(limit)
|
||||
.from_err()
|
||||
.and_then(move |body| {
|
||||
@@ -260,7 +263,7 @@ impl PayloadConfig {
|
||||
self
|
||||
}
|
||||
|
||||
fn check_mimetype<P>(&self, req: &ServiceFromRequest<P>) -> Result<(), Error> {
|
||||
fn check_mimetype(&self, req: &HttpRequest) -> Result<(), Error> {
|
||||
// check content-type
|
||||
if let Some(ref mt) = self.mimetype {
|
||||
match req.mime_type() {
|
||||
@@ -297,23 +300,22 @@ impl Default for PayloadConfig {
|
||||
/// By default only 256Kb payload reads to a memory, then
|
||||
/// `PayloadError::Overflow` get returned. Use `MessageBody::limit()`
|
||||
/// method to change upper limit.
|
||||
pub struct HttpMessageBody<T: HttpMessage> {
|
||||
pub struct HttpMessageBody<P> {
|
||||
limit: usize,
|
||||
length: Option<usize>,
|
||||
stream: actix_http::Payload<T::Stream>,
|
||||
stream: dev::Payload<P>,
|
||||
err: Option<PayloadError>,
|
||||
fut: Option<Box<Future<Item = Bytes, Error = PayloadError>>>,
|
||||
}
|
||||
|
||||
impl<T> HttpMessageBody<T>
|
||||
impl<P> HttpMessageBody<P>
|
||||
where
|
||||
T: HttpMessage,
|
||||
T::Stream: Stream<Item = Bytes, Error = PayloadError>,
|
||||
P: Stream<Item = Bytes, Error = PayloadError>,
|
||||
{
|
||||
/// Create `MessageBody` for request.
|
||||
pub fn new(req: &mut T) -> HttpMessageBody<T> {
|
||||
pub fn new(req: &HttpRequest, payload: &mut dev::Payload<P>) -> HttpMessageBody<P> {
|
||||
let mut len = None;
|
||||
if let Some(l) = req.headers().get(header::CONTENT_LENGTH) {
|
||||
if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) {
|
||||
if let Ok(s) = l.to_str() {
|
||||
if let Ok(l) = s.parse::<usize>() {
|
||||
len = Some(l)
|
||||
@@ -326,7 +328,7 @@ where
|
||||
}
|
||||
|
||||
HttpMessageBody {
|
||||
stream: req.take_payload(),
|
||||
stream: payload.take(),
|
||||
limit: 262_144,
|
||||
length: len,
|
||||
fut: None,
|
||||
@@ -342,7 +344,7 @@ where
|
||||
|
||||
fn err(e: PayloadError) -> Self {
|
||||
HttpMessageBody {
|
||||
stream: actix_http::Payload::None,
|
||||
stream: dev::Payload::None,
|
||||
limit: 262_144,
|
||||
fut: None,
|
||||
err: Some(e),
|
||||
@@ -351,10 +353,9 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Future for HttpMessageBody<T>
|
||||
impl<P> Future for HttpMessageBody<P>
|
||||
where
|
||||
T: HttpMessage,
|
||||
T::Stream: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
P: Stream<Item = Bytes, Error = PayloadError> + 'static,
|
||||
{
|
||||
type Item = Bytes;
|
||||
type Error = PayloadError;
|
||||
@@ -403,7 +404,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_payload_config() {
|
||||
let req = TestRequest::default().to_from();
|
||||
let req = TestRequest::default().to_http_request();
|
||||
let cfg = PayloadConfig::default().mimetype(mime::APPLICATION_JSON);
|
||||
assert!(cfg.check_mimetype(&req).is_err());
|
||||
|
||||
@@ -411,62 +412,64 @@ mod tests {
|
||||
header::CONTENT_TYPE,
|
||||
"application/x-www-form-urlencoded",
|
||||
)
|
||||
.to_from();
|
||||
.to_http_request();
|
||||
assert!(cfg.check_mimetype(&req).is_err());
|
||||
|
||||
let req =
|
||||
TestRequest::with_header(header::CONTENT_TYPE, "application/json").to_from();
|
||||
let req = TestRequest::with_header(header::CONTENT_TYPE, "application/json")
|
||||
.to_http_request();
|
||||
assert!(cfg.check_mimetype(&req).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bytes() {
|
||||
let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11")
|
||||
let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "11")
|
||||
.set_payload(Bytes::from_static(b"hello=world"))
|
||||
.to_from();
|
||||
.to_http_parts();
|
||||
|
||||
let s = block_on(Bytes::from_request(&mut req)).unwrap();
|
||||
let s = block_on(Bytes::from_request(&req, &mut pl)).unwrap();
|
||||
assert_eq!(s, Bytes::from_static(b"hello=world"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_string() {
|
||||
let mut req = TestRequest::with_header(header::CONTENT_LENGTH, "11")
|
||||
let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "11")
|
||||
.set_payload(Bytes::from_static(b"hello=world"))
|
||||
.to_from();
|
||||
.to_http_parts();
|
||||
|
||||
let s = block_on(String::from_request(&mut req)).unwrap();
|
||||
let s = block_on(String::from_request(&req, &mut pl)).unwrap();
|
||||
assert_eq!(s, "hello=world");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_message_body() {
|
||||
let mut req =
|
||||
TestRequest::with_header(header::CONTENT_LENGTH, "xxxx").to_request();
|
||||
let res = block_on(HttpMessageBody::new(&mut req));
|
||||
let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx")
|
||||
.to_srv_request()
|
||||
.into_parts();
|
||||
let res = block_on(HttpMessageBody::new(&req, &mut pl));
|
||||
match res.err().unwrap() {
|
||||
PayloadError::UnknownLength => (),
|
||||
_ => unreachable!("error"),
|
||||
}
|
||||
|
||||
let mut req =
|
||||
TestRequest::with_header(header::CONTENT_LENGTH, "1000000").to_request();
|
||||
let res = block_on(HttpMessageBody::new(&mut req));
|
||||
let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "1000000")
|
||||
.to_srv_request()
|
||||
.into_parts();
|
||||
let res = block_on(HttpMessageBody::new(&req, &mut pl));
|
||||
match res.err().unwrap() {
|
||||
PayloadError::Overflow => (),
|
||||
_ => unreachable!("error"),
|
||||
}
|
||||
|
||||
let mut req = TestRequest::default()
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
.set_payload(Bytes::from_static(b"test"))
|
||||
.to_request();
|
||||
let res = block_on(HttpMessageBody::new(&mut req));
|
||||
.to_http_parts();
|
||||
let res = block_on(HttpMessageBody::new(&req, &mut pl));
|
||||
assert_eq!(res.ok().unwrap(), Bytes::from_static(b"test"));
|
||||
|
||||
let mut req = TestRequest::default()
|
||||
let (req, mut pl) = TestRequest::default()
|
||||
.set_payload(Bytes::from_static(b"11111111111111"))
|
||||
.to_request();
|
||||
let res = block_on(HttpMessageBody::new(&mut req).limit(5));
|
||||
.to_http_parts();
|
||||
let res = block_on(HttpMessageBody::new(&req, &mut pl).limit(5));
|
||||
match res.err().unwrap() {
|
||||
PayloadError::Overflow => (),
|
||||
_ => unreachable!("error"),
|
||||
|
@@ -6,8 +6,9 @@ use actix_http::error::Error;
|
||||
use serde::de;
|
||||
use serde_urlencoded;
|
||||
|
||||
use crate::dev::Payload;
|
||||
use crate::extract::FromRequest;
|
||||
use crate::service::ServiceFromRequest;
|
||||
use crate::request::HttpRequest;
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||
/// Extract typed information from from the request's query.
|
||||
@@ -118,8 +119,8 @@ where
|
||||
type Future = Result<Self, Error>;
|
||||
|
||||
#[inline]
|
||||
fn from_request(req: &mut ServiceFromRequest<P>) -> Self::Future {
|
||||
serde_urlencoded::from_str::<T>(req.request().query_string())
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload<P>) -> Self::Future {
|
||||
serde_urlencoded::from_str::<T>(req.query_string())
|
||||
.map(|val| Ok(Query(val)))
|
||||
.unwrap_or_else(|e| Err(e.into()))
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ use crate::responder::Responder;
|
||||
use crate::route::Route;
|
||||
use crate::scope::Scope;
|
||||
|
||||
pub use crate::config::RouterConfig;
|
||||
pub use crate::data::{Data, RouteData};
|
||||
pub use crate::request::HttpRequest;
|
||||
pub use crate::types::*;
|
||||
|
@@ -55,5 +55,5 @@ tokio-timer = "0.2"
|
||||
openssl = { version="0.10", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-web = "1.0.0-alpa.3"
|
||||
actix-http = "0.1.0-alpa.3"
|
||||
actix-web = "1.0.0-alpha.3"
|
||||
actix-http = "0.1.0-alpha.3"
|
||||
|
@@ -90,13 +90,13 @@ impl TestServer {
|
||||
Connector::new()
|
||||
.timeout(time::Duration::from_millis(500))
|
||||
.ssl(builder.build())
|
||||
.service()
|
||||
.finish()
|
||||
}
|
||||
#[cfg(not(feature = "ssl"))]
|
||||
{
|
||||
Connector::new()
|
||||
.timeout(time::Duration::from_millis(500))
|
||||
.service()
|
||||
.finish()
|
||||
}
|
||||
};
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user