1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-03 09:36:36 +02:00

Compare commits

...

50 Commits

Author SHA1 Message Date
062e51e8ce prep actix-file release 2019-10-14 21:26:26 +06:00
a48e616def feat(files): add possibility to redirect to slash-ended path (#1134)
When accessing to a folder without a final slash, the index file will be loaded ok, but if it has
references (like a css or an image in an html file) these resources won't be loaded correctly if
they are using relative paths. In order to solve this, this PR adds the possibility to detect
folders without a final slash and make a 302 redirect to mitigate this issue. The behavior is off by
default. We're adding a new method called `redirect_to_slash_directory` which can be used to enable
this behavior.
2019-10-14 21:23:15 +06:00
effa96f5e4 Removed httpcode 'MovedPermanenty'. (#1128) 2019-10-12 06:45:12 +06:00
cc0b4be5b7 Fix typo in response.rs body() comment (#1126)
Fixes https://github.com/actix/actix-web/issues/1125
2019-10-09 19:11:55 +06:00
a464ffc23d prepare actix-files release 2019-10-08 10:13:16 +06:00
4de2e8a898 [actix-files] Allow user defined guards for NamedFile (actix#1113) (#1115)
* [actix-files] remove request method checks from NamedFile

* [actix-files] added custom guard checks to FilesService

* [actix-files] modify method check tests (NamedFile -> Files)

* [actix-files] add test for custom guards in Files

* [actix-files] update changelog
2019-10-08 10:09:40 +06:00
0f09415469 Convert documentation examples to Rust 2018 edition (#1120)
* Convert types::query examples to rust-2018 edition

* Convert types::json examples to rust-2018 edition

* Convert types::path examples to rust-2018 edition

* Convert types::form examples to rust-2018 edition

* Convert rest of the examples to rust-2018 edition.
2019-10-07 11:29:11 +06:00
f089cf185b Let ResponseError render w/ 'text/plain; charset=utf-8' header (#1118) (#1119)
* Let ResponseError render w/ 'text/plain; charset=utf-8' header (#1118)

Trait ResponseError originally render Error messages with header
`text/plain` , which causes browsers (i.e. Firefox 70.0) with
Non-English locale unable to render UTF-8 responses with non-English
characters correctly. i.e. emoji.

This fix solved this problem by specifying the charset of `text/plain`
as utf-8, which is the default charset in rust.

Before actix-web consider to support other charsets, this hotfix is
 enough.

Test case:

fn test() -> Result<String, actix_web::Error> {
    Err(actix_web::error::ErrorForbidden("😋test"))
}

* Update actix-http/CHANGES.md for #1118
2019-10-07 10:56:24 +06:00
15d3c1ae81 Update docs of guard.rs (#1116)
* Update guard.rs
2019-10-07 12:05:17 +09:00
fba31d4e0a Expose ContentDisposition in actix-multipart to fix broken doc link (#1114)
* Expose ContentDisposition in actix-multipart to fix broken doc link

* Revert "Expose ContentDisposition in actix-multipart to fix broken doc link"

This reverts commit e90d71d16c.

* Unhide actix-http::header::common docs

These types are used in other exported documented interfaces and create
broken links if not documented.
See `actix_multipart::Field.content_disposition`
2019-10-02 09:48:25 +06:00
f81ae37677 Add From<Payload> for crate::dev::Payload (#1110)
* Add From<Payload> for crate::dev::Payload

* Make dev::Payload field of Payload public and add into_inner method

* Add changelog entry
2019-10-01 14:05:38 +06:00
5169d306ae update ConnectionInfo.remote() doc string 2019-09-27 07:03:12 +06:00
4f3e97fff8 prepare actix-web release 2019-09-25 15:39:09 +06:00
3ff01a9fc4 Add changelog entry for #1101 (#1102) 2019-09-25 15:35:28 +06:00
3d4e45a0e5 prepare release 2019-09-25 15:30:20 +06:00
c659c33919 Feature uds: Add listen_uds to ServerBuilder (#1085)
Allows using an existing Unix Listener instead of binding to a path.
Useful for when running as a daemon under systemd.

Change-Id: I54a0e78c321d8b7a9ded381083217af590e9a7fa
2019-09-25 15:16:51 +06:00
959f7754b2 Merge pull request #1101 from actix/add-awc-get-head-methods
Add remaining getter methods from private head field
2019-09-25 10:23:23 +02:00
23f04c4f38 Add remaining getter methods from private head field 2019-09-25 08:50:45 +02:00
d9af8f66ba Use actix-testing for testing utils 2019-09-25 10:28:41 +06:00
aa39b8ca6f Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() (#1096)
* Add support for serde_json::Value to be passed as argument to ResponseBuilder.body()

* Update actix-http/CHANGES.md
2019-09-25 09:33:52 +06:00
58c7065f08 Implement register_data method on Resource and Scope. (#1094)
* Implement `register_data` method on `Resource` and `Scope`.

* Split Scope::register_data tests out from Scope::data tests.

* CHANGES.md: Mention {Scope,Resource}::register_data.
2019-09-18 06:36:39 +06:00
b3783b403e Merge branch 'master' of github.com:actix/actix-web 2019-09-17 21:46:45 +06:00
e4503046de Do not override current System 2019-09-17 21:45:06 +06:00
32a1c36597 Make UrlencodedError::Overflow more informative (#1089) 2019-09-17 06:58:04 +06:00
7c9f9afc46 Add ability to use Infallible as HttpResponse error type (#1093)
* Add `std::convert::Infallible` implementantion for `ResponseError`

* Add from `std::convert::Infallible` to `Error`

* Remove `ResponseError` implementantion for `Infallible`

* Remove useless docs

* Better comment

* Update changelog

* Update actix_http::changelog
2019-09-17 06:57:38 +06:00
c1f99e0775 Remove mem::uninitialized() (#1090) 2019-09-16 07:52:23 +09:00
a32573bb58 Allow to re-construct ServiceRequest from HttpRequest and Payload #1088 2019-09-13 11:56:24 +06:00
e35d930ef9 prepare releases 2019-09-12 21:58:08 +06:00
60b7aebd0a fmt & clippy 2019-09-12 21:52:46 +06:00
45d2fd4299 export frozen request related types; refactor code layout 2019-09-12 10:40:56 +06:00
71f8577713 prepare awc release 2019-09-11 20:13:28 +06:00
043f763c51 prepare actix-http release 2019-09-11 20:07:39 +06:00
8873e9b39e Added FrozenClientRequest for easier retrying HTTP calls (#1064)
* Initial commit

* Added extra_headers

* Added freeze() method to ClientRequest which produces a 'read-only' copy of a request suitable for retrying the send operation

* Additional methods for FrozenClientRequest

* Fix

* Increased crates versions

* Fixed a unit test. Added one more unit test.

* Added RequestHeaderWrapper

* Small fixes

* Renamed RequestHeadWrapper->RequestHeadType

* Updated CHANGES.md files

* Small fix

* Small changes

* Removed *_extra methods from Connection trait

* Added FrozenSendBuilder

* Added FrozenSendBuilder

* Minor fix

* Replaced impl Future with concrete Future implementation

* Small renaming

* Renamed Send->SendBody
2019-09-10 10:29:32 +06:00
5e8f1c338c fix h2 not using error response (#1080)
* fix h2 not using error response

* add fix change log

* fix h2 service error tests
2019-09-09 16:24:57 +06:00
1d96ae9bc3 actix-multipart: Correctly parse multipart body which does not end in CRLF (#1042)
* Correctly parse multipart body which does not end in CRLF

* Add in an eof guard for extra safety
2019-09-09 13:58:00 +06:00
8d61fe0925 Ensure that awc::ws::WebsocketsRequest sets the Host header (#1070)
* Ensure that awc::ws::WebsocketsRequest sets the Host header before connecting.

* Make sure to check if headers already have a HOST value before setting

* Update CHANGES.md to reflect WebSocket client update.
2019-09-09 12:27:13 +06:00
8a9fcddb3c Condition middleware (#1075)
* add condition middleware

* write tests

* update changes

* Update src/middleware/condition.rs

Co-Authored-By: Yuki Okushi <huyuumi.dev@gmail.com>

* Update src/middleware/condition.rs

Co-Authored-By: Yuki Okushi <huyuumi.dev@gmail.com>

* Update src/middleware/condition.rs

Co-Authored-By: Yuki Okushi <huyuumi.dev@gmail.com>

* Update src/middleware/condition.rs

Co-Authored-By: Yuki Okushi <huyuumi.dev@gmail.com>
2019-09-09 12:26:38 +06:00
c9400456f6 update actix-connect ver 2019-09-02 15:20:28 -07:00
63ddd30ee4 on_connect result isnt added to request extensions for http2 requests #1009 2019-09-01 13:15:02 +06:00
bae29897d6 prep actix-web release 2019-08-29 09:36:16 +06:00
616981ecf9 clear extensions before reclaiming HttpRequests in their pool (#1063)
Issue #1062
2019-08-29 09:35:05 +06:00
98bf8ab098 enable rust-tls feature for actix_web::client #1045 2019-08-28 21:40:24 +06:00
c193137905 actix_web::test::TestRequest::set_form (#1058) 2019-08-28 21:32:17 +06:00
a07cdd6533 Data::into_inner 2019-08-27 17:25:25 +01:00
61e492e7e3 Prepare actix-multipart 0.1.3 release 2019-08-18 10:39:22 +09:00
23d768a77b Add explicit dyns (#1041)
* Add explicit `dyn`s

* Remove unnecessary lines
2019-08-17 02:45:44 +09:00
87b7162473 chore(readme): fix copy paste error (#1040)
Fix actix-cors README
2019-08-16 09:21:30 +09:00
979c4d44f4 update awc dep 2019-08-13 12:41:26 -07:00
5d248cad89 prep release 2019-08-13 12:28:05 -07:00
b1cb72d088 update url crate 2019-08-13 11:03:24 -07:00
70 changed files with 1878 additions and 483 deletions

View File

@ -1,6 +1,40 @@
# Changes
## [1.0.6] - 2019-xx-xx
## [1.0.9] - 2019-xx-xx
### Added
* Add `Payload::into_inner` method and make stored `def::Payload` public. (#1110)
## [1.0.8] - 2019-09-25
### Added
* Add `Scope::register_data` and `Resource::register_data` methods, parallel to
`App::register_data`.
* Add `middleware::Condition` that conditionally enables another middleware
* Allow to re-construct `ServiceRequest` from `HttpRequest` and `Payload`
* Add `HttpServer::listen_uds` for ability to listen on UDS FD rather than path,
which is useful for example with systemd.
### Changed
* Make UrlEncodedError::Overflow more informativve
* Use actix-testing for testing utils
## [1.0.7] - 2019-08-29
### Fixed
* Request Extensions leak #1062
## [1.0.6] - 2019-08-28
### Added
@ -8,11 +42,20 @@
* Form immplements Responder, returning a `application/x-www-form-urlencoded` response
* Add `into_inner` to `Data`
* Add `test::TestRequest::set_form()` convenience method to automatically serialize data and set
the header in test requests.
### Changed
* `Query` payload made `pub`. Allows user to pattern-match the payload.
* Update serde_urlencoded to "0.6.1"
* Enable `rust-tls` feature for client #1045
* Update serde_urlencoded to 0.6.1
* Update url to 2.1
## [1.0.5] - 2019-07-18

View File

@ -1,6 +1,6 @@
[package]
name = "actix-web"
version = "1.0.5"
version = "1.0.8"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md"
@ -66,7 +66,7 @@ fail = ["actix-http/fail"]
ssl = ["openssl", "actix-server/ssl", "awc/ssl"]
# rustls
rust-tls = ["rustls", "actix-server/rust-tls"]
rust-tls = ["rustls", "actix-server/rust-tls", "awc/rust-tls"]
# unix domain sockets support
uds = ["actix-server/uds"]
@ -78,11 +78,12 @@ actix-utils = "0.4.4"
actix-router = "0.1.5"
actix-rt = "0.2.4"
actix-web-codegen = "0.1.2"
actix-http = "0.2.7"
actix-server = "0.6.0"
actix-http = "0.2.9"
actix-server = "0.6.1"
actix-server-config = "0.1.2"
actix-testing = "0.1.0"
actix-threadpool = "0.1.1"
awc = { version = "0.2.2", optional = true }
awc = { version = "0.2.7", optional = true }
bytes = "0.4"
derive_more = "0.15.0"
@ -98,7 +99,7 @@ serde = { version = "1.0", features=["derive"] }
serde_json = "1.0"
serde_urlencoded = "0.6.1"
time = "0.1.42"
url = { version="1.7", features=["query_encoding"] }
url = "2.1"
# ssl support
openssl = { version="0.10", optional = true }

View File

@ -1,4 +1,4 @@
# Identity service for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-identity)](https://crates.io/crates/actix-identity) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
# Cors Middleware for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-cors)](https://crates.io/crates/actix-cors) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Documentation & community resources

View File

@ -1,21 +1,25 @@
# Changes
## [0.1.5] - unreleased
## [0.1.6] - 2019-10-14
* Add option to redirect to a slash-ended path `Files` #1132
## [0.1.5] - 2019-10-08
* Bump up `mime_guess` crate version to 2.0.1
* Bump up `percent-encoding` crate version to 2.1
* Allow user defined request guards for `Files` #1113
## [0.1.4] - 2019-07-20
* Allow to disable `Content-Disposition` header #686
## [0.1.3] - 2019-06-28
* Do not set `Content-Length` header, let actix-http set it #930
## [0.1.2] - 2019-06-13
* Content-Length is 0 for NamedFile HEAD request #914

View File

@ -1,6 +1,6 @@
[package]
name = "actix-files"
version = "0.1.4"
version = "0.1.6"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Static files support for actix web."
readme = "README.md"
@ -18,8 +18,8 @@ name = "actix_files"
path = "src/lib.rs"
[dependencies]
actix-web = { version = "1.0.2", default-features = false }
actix-http = "0.2.4"
actix-web = { version = "1.0.8", default-features = false }
actix-http = "0.2.9"
actix-service = "0.4.1"
bitflags = "1"
bytes = "0.4"
@ -32,4 +32,4 @@ percent-encoding = "2.1"
v_htmlescape = "0.4"
[dev-dependencies]
actix-web = { version = "1.0.2", features=["ssl"] }
actix-web = { version = "1.0.8", features=["ssl"] }

View File

@ -16,7 +16,9 @@ use actix_web::dev::{
ServiceResponse,
};
use actix_web::error::{BlockingError, Error, ErrorInternalServerError};
use actix_web::http::header::DispositionType;
use actix_web::guard::Guard;
use actix_web::http::header::{self, DispositionType};
use actix_web::http::Method;
use actix_web::{web, FromRequest, HttpRequest, HttpResponse, Responder};
use bytes::Bytes;
use futures::future::{ok, Either, FutureResult};
@ -231,10 +233,12 @@ pub struct Files {
directory: PathBuf,
index: Option<String>,
show_index: bool,
redirect_to_slash: bool,
default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
renderer: Rc<DirectoryRenderer>,
mime_override: Option<Rc<MimeOverride>>,
file_flags: named::Flags,
guards: Option<Rc<Box<dyn Guard>>>,
}
impl Clone for Files {
@ -243,11 +247,13 @@ impl Clone for Files {
directory: self.directory.clone(),
index: self.index.clone(),
show_index: self.show_index,
redirect_to_slash: self.redirect_to_slash,
default: self.default.clone(),
renderer: self.renderer.clone(),
file_flags: self.file_flags,
path: self.path.clone(),
mime_override: self.mime_override.clone(),
guards: self.guards.clone(),
}
}
}
@ -269,10 +275,12 @@ impl Files {
directory: dir,
index: None,
show_index: false,
redirect_to_slash: false,
default: Rc::new(RefCell::new(None)),
renderer: Rc::new(directory_listing),
mime_override: None,
file_flags: named::Flags::default(),
guards: None,
}
}
@ -284,6 +292,14 @@ impl Files {
self
}
/// Redirects to a slash-ended path when browsing a directory.
///
/// By default never redirect.
pub fn redirect_to_slash_directory(mut self) -> Self {
self.redirect_to_slash = true;
self
}
/// Set custom directory renderer
pub fn files_listing_renderer<F>(mut self, f: F) -> Self
where
@ -331,6 +347,15 @@ impl Files {
self
}
/// Specifies custom guards to use for directory listings and files.
///
/// Default behaviour allows GET and HEAD.
#[inline]
pub fn use_guards<G: Guard + 'static>(mut self, guards: G) -> Self {
self.guards = Some(Rc::new(Box::new(guards)));
self
}
/// Disable `Content-Disposition` header.
///
/// By default Content-Disposition` header is enabled.
@ -375,10 +400,10 @@ impl HttpServiceFactory for Files {
}
impl NewService for Files {
type Config = ();
type Request = ServiceRequest;
type Response = ServiceResponse;
type Error = Error;
type Config = ();
type Service = FilesService;
type InitError = ();
type Future = Box<dyn Future<Item = Self::Service, Error = Self::InitError>>;
@ -388,10 +413,12 @@ impl NewService for Files {
directory: self.directory.clone(),
index: self.index.clone(),
show_index: self.show_index,
redirect_to_slash: self.redirect_to_slash,
default: None,
renderer: self.renderer.clone(),
mime_override: self.mime_override.clone(),
file_flags: self.file_flags,
guards: self.guards.clone(),
};
if let Some(ref default) = *self.default.borrow() {
@ -414,10 +441,12 @@ pub struct FilesService {
directory: PathBuf,
index: Option<String>,
show_index: bool,
redirect_to_slash: bool,
default: Option<HttpService>,
renderer: Rc<DirectoryRenderer>,
mime_override: Option<Rc<MimeOverride>>,
file_flags: named::Flags,
guards: Option<Rc<Box<dyn Guard>>>,
}
impl FilesService {
@ -454,6 +483,25 @@ impl Service for FilesService {
fn call(&mut self, req: ServiceRequest) -> Self::Future {
// let (req, pl) = req.into_parts();
let is_method_valid = if let Some(guard) = &self.guards {
// execute user defined guards
(**guard).check(req.head())
} else {
// default behaviour
match *req.method() {
Method::HEAD | Method::GET => true,
_ => false,
}
};
if !is_method_valid {
return Either::A(ok(req.into_response(
actix_web::HttpResponse::MethodNotAllowed()
.header(header::CONTENT_TYPE, "text/plain")
.body("Request did not meet this resource's requirements."),
)));
}
let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) {
Ok(item) => item,
Err(e) => return Either::A(ok(req.error_response(e))),
@ -467,6 +515,16 @@ impl Service for FilesService {
if path.is_dir() {
if let Some(ref redir_index) = self.index {
if self.redirect_to_slash && !req.path().ends_with('/') {
let redirect_to = format!("{}/", req.path());
return Either::A(ok(req.into_response(
HttpResponse::Found()
.header(header::LOCATION, redirect_to)
.body("")
.into_body(),
)));
}
let path = path.join(redir_index);
match NamedFile::open(path) {
@ -576,6 +634,7 @@ mod tests {
use bytes::BytesMut;
use super::*;
use actix_web::guard;
use actix_web::http::header::{
self, ContentDisposition, DispositionParam, DispositionType,
};
@ -1010,20 +1069,41 @@ mod tests {
}
#[test]
fn test_named_file_not_allowed() {
let file = NamedFile::open("Cargo.toml").unwrap();
fn test_files_not_allowed() {
let mut srv = test::init_service(App::new().service(Files::new("/", ".")));
let req = TestRequest::default()
.uri("/Cargo.toml")
.method(Method::POST)
.to_http_request();
let resp = file.respond_to(&req).unwrap();
.to_request();
let resp = test::call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let file = NamedFile::open("Cargo.toml").unwrap();
let req = TestRequest::default().method(Method::PUT).to_http_request();
let resp = file.respond_to(&req).unwrap();
let mut srv = test::init_service(App::new().service(Files::new("/", ".")));
let req = TestRequest::default()
.method(Method::PUT)
.uri("/Cargo.toml")
.to_request();
let resp = test::call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
}
#[test]
fn test_files_guards() {
let mut srv = test::init_service(
App::new().service(Files::new("/", ".").use_guards(guard::Post())),
);
let req = TestRequest::default()
.uri("/Cargo.toml")
.method(Method::POST)
.to_request();
let resp = test::call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn test_named_file_content_encoding() {
let mut srv = test::init_service(App::new().wrap(Compress::default()).service(
@ -1112,6 +1192,34 @@ mod tests {
assert!(format!("{:?}", bytes).contains("/tests/test.png"));
}
#[test]
fn test_redirect_to_slash_directory() {
// should not redirect if no index
let mut srv = test::init_service(
App::new().service(Files::new("/", ".").redirect_to_slash_directory()),
);
let req = TestRequest::with_uri("/tests").to_request();
let resp = test::call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
// should redirect if index present
let mut srv = test::init_service(
App::new().service(
Files::new("/", ".")
.index_file("test.png")
.redirect_to_slash_directory(),
),
);
let req = TestRequest::with_uri("/tests").to_request();
let resp = test::call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::FOUND);
// should not redirect if the path is wrong
let req = TestRequest::with_uri("/not_existing").to_request();
let resp = test::call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
}
#[test]
fn test_static_files_bad_directory() {
let _st: Files = Files::new("/", "missing");

View File

@ -15,7 +15,7 @@ use actix_http::body::SizedStream;
use actix_web::http::header::{
self, ContentDisposition, DispositionParam, DispositionType,
};
use actix_web::http::{ContentEncoding, Method, StatusCode};
use actix_web::http::{ContentEncoding, StatusCode};
use actix_web::middleware::BodyEncoding;
use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder};
@ -324,16 +324,6 @@ impl Responder for NamedFile {
return Ok(resp.streaming(reader));
}
match *req.method() {
Method::HEAD | Method::GET => (),
_ => {
return Ok(HttpResponse::MethodNotAllowed()
.header(header::CONTENT_TYPE, "text/plain")
.header(header::ALLOW, "GET, HEAD")
.body("This resource only supports GET and HEAD."));
}
}
let etag = if self.flags.contains(Flags::ETAG) {
self.etag()
} else {

View File

@ -1,5 +1,32 @@
# Changes
## Not released yet
### Added
* Add support for serde_json::Value to be passed as argument to ResponseBuilder.body()
### Fixed
* To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; charset=utf-8` header #1118
## [0.2.10] - 2019-09-11
### Added
* Add support for sending HTTP requests with `Rc<RequestHead>` in addition to sending HTTP requests with `RequestHead`
* Allow to use `std::convert::Infallible` as `actix_http::error::Error`
### Fixed
* h2 will use error response #1080
* on_connect result isn't added to request extensions for http2 requests #1009
## [0.2.9] - 2019-08-13
### Changed

View File

@ -1,6 +1,6 @@
[package]
name = "actix-http"
version = "0.2.9"
version = "0.2.10"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix http primitives"
readme = "README.md"
@ -49,7 +49,7 @@ secure-cookies = ["ring"]
[dependencies]
actix-service = "0.4.1"
actix-codec = "0.1.2"
actix-connect = "0.2.2"
actix-connect = "0.2.4"
actix-utils = "0.4.4"
actix-server-config = "0.1.2"
actix-threadpool = "0.1.1"
@ -66,7 +66,7 @@ hashbrown = "0.5.0"
h2 = "0.1.16"
http = "0.1.17"
httparse = "1.3"
indexmap = "1.0"
indexmap = "1.2"
lazy_static = "1.0"
language-tags = "0.2"
log = "0.4"

View File

@ -234,6 +234,12 @@ impl From<BytesMut> for Body {
}
}
impl From<serde_json::Value> for Body {
fn from(v: serde_json::Value) -> Body {
Body::Bytes(v.to_string().into())
}
}
impl<S> From<SizedStream<S>> for Body
where
S: Stream<Item = Bytes, Error = Error> + 'static,
@ -548,4 +554,17 @@ mod tests {
assert!(format!("{:?}", Body::Empty).contains("Body::Empty"));
assert!(format!("{:?}", Body::Bytes(Bytes::from_static(b"1"))).contains("1"));
}
#[test]
fn test_serde_json() {
use serde_json::json;
assert_eq!(
Body::from(serde_json::Value::String("test".into())).size(),
BodySize::Sized(6)
);
assert_eq!(
Body::from(json!({"test-key":"test-value"})).size(),
BodySize::Sized(25)
);
}
}

View File

@ -199,6 +199,7 @@ where
self.client_disconnect,
);
H2Service::with_config(cfg, service.into_new_service())
.on_connect(self.on_connect)
}
/// Finish service configuration and create `HttpService` instance.

View File

@ -8,7 +8,7 @@ use h2::client::SendRequest;
use crate::body::MessageBody;
use crate::h1::ClientCodec;
use crate::message::{RequestHead, ResponseHead};
use crate::message::{RequestHeadType, ResponseHead};
use crate::payload::Payload;
use super::error::SendRequestError;
@ -27,9 +27,9 @@ pub trait Connection {
fn protocol(&self) -> Protocol;
/// Send request and body
fn send_request<B: MessageBody + 'static>(
fn send_request<B: MessageBody + 'static, H: Into<RequestHeadType>>(
self,
head: RequestHead,
head: H,
body: B,
) -> Self::Future;
@ -39,7 +39,7 @@ pub trait Connection {
>;
/// Send request, returns Response and Framed
fn open_tunnel(self, head: RequestHead) -> Self::TunnelFuture;
fn open_tunnel<H: Into<RequestHeadType>>(self, head: H) -> Self::TunnelFuture;
}
pub(crate) trait ConnectionLifetime: AsyncRead + AsyncWrite + 'static {
@ -105,22 +105,22 @@ where
}
}
fn send_request<B: MessageBody + 'static>(
fn send_request<B: MessageBody + 'static, H: Into<RequestHeadType>>(
mut self,
head: RequestHead,
head: H,
body: B,
) -> Self::Future {
match self.io.take().unwrap() {
ConnectionType::H1(io) => Box::new(h1proto::send_request(
io,
head,
head.into(),
body,
self.created,
self.pool,
)),
ConnectionType::H2(io) => Box::new(h2proto::send_request(
io,
head,
head.into(),
body,
self.created,
self.pool,
@ -139,10 +139,10 @@ where
>;
/// Send request, returns Response and Framed
fn open_tunnel(mut self, head: RequestHead) -> Self::TunnelFuture {
fn open_tunnel<H: Into<RequestHeadType>>(mut self, head: H) -> Self::TunnelFuture {
match self.io.take().unwrap() {
ConnectionType::H1(io) => {
Either::A(Box::new(h1proto::open_tunnel(io, head)))
Either::A(Box::new(h1proto::open_tunnel(io, head.into())))
}
ConnectionType::H2(io) => {
if let Some(mut pool) = self.pool.take() {
@ -180,9 +180,9 @@ where
}
}
fn send_request<RB: MessageBody + 'static>(
fn send_request<RB: MessageBody + 'static, H: Into<RequestHeadType>>(
self,
head: RequestHead,
head: H,
body: RB,
) -> Self::Future {
match self {
@ -199,7 +199,7 @@ where
>;
/// Send request, returns Response and Framed
fn open_tunnel(self, head: RequestHead) -> Self::TunnelFuture {
fn open_tunnel<H: Into<RequestHeadType>>(self, head: H) -> Self::TunnelFuture {
match self {
EitherConnection::A(con) => Box::new(
con.open_tunnel(head)

View File

@ -269,9 +269,9 @@ where
.map(|protos| protos.windows(2).any(|w| w == H2))
.unwrap_or(false);
if h2 {
(Box::new(sock) as Box<Io>, Protocol::Http2)
(Box::new(sock) as Box<dyn Io>, Protocol::Http2)
} else {
(Box::new(sock) as Box<Io>, Protocol::Http1)
(Box::new(sock) as Box<dyn Io>, Protocol::Http1)
}
}),
),
@ -288,9 +288,9 @@ where
.map(|protos| protos.windows(2).any(|w| w == H2))
.unwrap_or(false);
if h2 {
(Box::new(sock) as Box<Io>, Protocol::Http2)
(Box::new(sock) as Box<dyn Io>, Protocol::Http2)
} else {
(Box::new(sock) as Box<Io>, Protocol::Http1)
(Box::new(sock) as Box<dyn Io>, Protocol::Http1)
}
}),
),

View File

@ -128,3 +128,23 @@ impl ResponseError for SendRequestError {
.into()
}
}
/// A set of errors that can occur during freezing a request
#[derive(Debug, Display, From)]
pub enum FreezeRequestError {
/// Invalid URL
#[display(fmt = "Invalid URL: {}", _0)]
Url(InvalidUrl),
/// Http error
#[display(fmt = "{}", _0)]
Http(HttpError),
}
impl From<FreezeRequestError> for SendRequestError {
fn from(e: FreezeRequestError) -> Self {
match e {
FreezeRequestError::Url(e) => e.into(),
FreezeRequestError::Http(e) => e.into(),
}
}
}

View File

@ -8,8 +8,9 @@ use futures::{Async, Future, Poll, Sink, Stream};
use crate::error::PayloadError;
use crate::h1;
use crate::header::HeaderMap;
use crate::http::header::{IntoHeaderValue, HOST};
use crate::message::{RequestHead, ResponseHead};
use crate::message::{RequestHeadType, ResponseHead};
use crate::payload::{Payload, PayloadStream};
use super::connection::{ConnectionLifetime, ConnectionType, IoConnection};
@ -19,7 +20,7 @@ use crate::body::{BodySize, MessageBody};
pub(crate) fn send_request<T, B>(
io: T,
mut head: RequestHead,
mut head: RequestHeadType,
body: B,
created: time::Instant,
pool: Option<Acquired<T>>,
@ -29,22 +30,28 @@ where
B: MessageBody,
{
// set request host header
if !head.headers.contains_key(HOST) {
if let Some(host) = head.uri.host() {
if !head.as_ref().headers.contains_key(HOST)
&& !head.extra_headers().iter().any(|h| h.contains_key(HOST))
{
if let Some(host) = head.as_ref().uri.host() {
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
let _ = match head.uri.port_u16() {
let _ = match head.as_ref().uri.port_u16() {
None | Some(80) | Some(443) => write!(wrt, "{}", host),
Some(port) => write!(wrt, "{}:{}", host, port),
};
match wrt.get_mut().take().freeze().try_into() {
Ok(value) => {
head.headers.insert(HOST, value);
}
Err(e) => {
log::error!("Can not set HOST header {}", e);
}
Ok(value) => match head {
RequestHeadType::Owned(ref mut head) => {
head.headers.insert(HOST, value)
}
RequestHeadType::Rc(_, ref mut extra_headers) => {
let headers = extra_headers.get_or_insert(HeaderMap::new());
headers.insert(HOST, value)
}
},
Err(e) => log::error!("Can not set HOST header {}", e),
}
}
}
@ -57,7 +64,7 @@ where
let len = body.size();
// create Framed and send reqest
// create Framed and send request
Framed::new(io, h1::ClientCodec::default())
.send((head, len).into())
.from_err()
@ -95,12 +102,12 @@ where
pub(crate) fn open_tunnel<T>(
io: T,
head: RequestHead,
head: RequestHeadType,
) -> impl Future<Item = (ResponseHead, Framed<T, h1::ClientCodec>), Error = SendRequestError>
where
T: AsyncRead + AsyncWrite + 'static,
{
// create Framed and send reqest
// create Framed and send request
Framed::new(io, h1::ClientCodec::default())
.send((head, BodySize::None).into())
.from_err()

View File

@ -9,7 +9,8 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, TRANSFER_ENCODING};
use http::{request::Request, HttpTryFrom, Method, Version};
use crate::body::{BodySize, MessageBody};
use crate::message::{RequestHead, ResponseHead};
use crate::header::HeaderMap;
use crate::message::{RequestHeadType, ResponseHead};
use crate::payload::Payload;
use super::connection::{ConnectionType, IoConnection};
@ -18,7 +19,7 @@ use super::pool::Acquired;
pub(crate) fn send_request<T, B>(
io: SendRequest<Bytes>,
head: RequestHead,
head: RequestHeadType,
body: B,
created: time::Instant,
pool: Option<Acquired<T>>,
@ -28,7 +29,7 @@ where
B: MessageBody,
{
trace!("Sending client request: {:?} {:?}", head, body.size());
let head_req = head.method == Method::HEAD;
let head_req = head.as_ref().method == Method::HEAD;
let length = body.size();
let eof = match length {
BodySize::None | BodySize::Empty | BodySize::Sized(0) => true,
@ -39,8 +40,8 @@ where
.map_err(SendRequestError::from)
.and_then(move |mut io| {
let mut req = Request::new(());
*req.uri_mut() = head.uri;
*req.method_mut() = head.method;
*req.uri_mut() = head.as_ref().uri.clone();
*req.method_mut() = head.as_ref().method.clone();
*req.version_mut() = Version::HTTP_2;
let mut skip_len = true;
@ -66,8 +67,27 @@ where
),
};
// Extracting extra headers from RequestHeadType. HeaderMap::new() does not allocate.
let (head, extra_headers) = match head {
RequestHeadType::Owned(head) => {
(RequestHeadType::Owned(head), HeaderMap::new())
}
RequestHeadType::Rc(head, extra_headers) => (
RequestHeadType::Rc(head, None),
extra_headers.unwrap_or_else(HeaderMap::new),
),
};
// merging headers from head and extra headers.
let headers = head
.as_ref()
.headers
.iter()
.filter(|(name, _)| !extra_headers.contains_key(*name))
.chain(extra_headers.iter());
// copy headers
for (key, value) in head.headers.iter() {
for (key, value) in headers {
match *key {
CONNECTION | TRANSFER_ENCODING => continue, // http2 specific
CONTENT_LENGTH if skip_len => continue,

View File

@ -10,7 +10,7 @@ mod pool;
pub use self::connection::Connection;
pub use self::connector::Connector;
pub use self::error::{ConnectError, InvalidUrl, SendRequestError};
pub use self::error::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError};
pub use self::pool::Protocol;
#[derive(Clone)]

View File

@ -326,7 +326,7 @@ impl<Io> Inner<Io> {
fn release_waiter(&mut self, key: &Key, token: usize) {
self.waiters.remove(token);
self.waiters_queue.remove(&(key.clone(), token));
let _ = self.waiters_queue.shift_remove(&(key.clone(), token));
}
}

View File

@ -75,7 +75,7 @@ pub trait ResponseError: fmt::Debug + fmt::Display {
let _ = write!(Writer(&mut buf), "{}", self);
resp.headers_mut().insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain"),
header::HeaderValue::from_static("text/plain; charset=utf-8"),
);
resp.set_body(Body::from(buf))
}
@ -132,6 +132,14 @@ impl std::error::Error for Error {
}
}
impl From<std::convert::Infallible> for Error {
fn from(_: std::convert::Infallible) -> Self {
// `std::convert::Infallible` indicates an error
// that will never happen
unreachable!()
}
}
/// Convert `Error` to a `Response` instance
impl From<Error> for Response {
fn from(err: Error) -> Self {
@ -528,7 +536,7 @@ where
let _ = write!(Writer(&mut buf), "{}", self);
res.headers_mut().insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain"),
header::HeaderValue::from_static("text/plain; charset=utf-8"),
);
res.set_body(Body::from(buf))
}
@ -1072,7 +1080,7 @@ mod tests {
#[test]
fn test_error_casting() {
let err = PayloadError::Overflow;
let resp_err: &ResponseError = &err;
let resp_err: &dyn ResponseError = &err;
let err = resp_err.downcast_ref::<PayloadError>().unwrap();
assert_eq!(err.to_string(), "A payload reached size limit.");
let not_err = resp_err.downcast_ref::<ContentTypeError>();

View File

@ -1,5 +1,6 @@
#![allow(unused_imports, unused_variables, dead_code)]
use std::io::{self, Write};
use std::rc::Rc;
use actix_codec::{Decoder, Encoder};
use bitflags::bitflags;
@ -15,8 +16,11 @@ use super::{Message, MessageType};
use crate::body::BodySize;
use crate::config::ServiceConfig;
use crate::error::{ParseError, PayloadError};
use crate::header::HeaderMap;
use crate::helpers;
use crate::message::{ConnectionType, Head, MessagePool, RequestHead, ResponseHead};
use crate::message::{
ConnectionType, Head, MessagePool, RequestHead, RequestHeadType, ResponseHead,
};
bitflags! {
struct Flags: u8 {
@ -48,7 +52,7 @@ struct ClientCodecInner {
// encoder part
flags: Flags,
headers_size: u32,
encoder: encoder::MessageEncoder<RequestHead>,
encoder: encoder::MessageEncoder<RequestHeadType>,
}
impl Default for ClientCodec {
@ -183,7 +187,7 @@ impl Decoder for ClientPayloadCodec {
}
impl Encoder for ClientCodec {
type Item = Message<(RequestHead, BodySize)>;
type Item = Message<(RequestHeadType, BodySize)>;
type Error = io::Error;
fn encode(
@ -192,13 +196,15 @@ impl Encoder for ClientCodec {
dst: &mut BytesMut,
) -> Result<(), Self::Error> {
match item {
Message::Item((mut msg, length)) => {
Message::Item((mut head, length)) => {
let inner = &mut self.inner;
inner.version = msg.version;
inner.flags.set(Flags::HEAD, msg.method == Method::HEAD);
inner.version = head.as_ref().version;
inner
.flags
.set(Flags::HEAD, head.as_ref().method == Method::HEAD);
// connection status
inner.ctype = match msg.connection_type() {
inner.ctype = match head.as_ref().connection_type() {
ConnectionType::KeepAlive => {
if inner.flags.contains(Flags::KEEPALIVE_ENABLED) {
ConnectionType::KeepAlive
@ -212,7 +218,7 @@ impl Encoder for ClientCodec {
inner.encoder.encode(
dst,
&mut msg,
&mut head,
false,
false,
inner.version,

View File

@ -1,5 +1,6 @@
use std::io;
use std::marker::PhantomData;
use std::{io, mem};
use std::mem::MaybeUninit;
use actix_codec::Decoder;
use bytes::{Bytes, BytesMut};
@ -186,11 +187,12 @@ impl MessageType for Request {
fn decode(src: &mut BytesMut) -> Result<Option<(Self, PayloadType)>, ParseError> {
// Unsafe: we read only this data only after httparse parses headers into.
// performance bump for pipeline benchmarks.
let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() };
let mut headers: [HeaderIndex; MAX_HEADERS] =
unsafe { MaybeUninit::uninit().assume_init() };
let (len, method, uri, ver, h_len) = {
let mut parsed: [httparse::Header; MAX_HEADERS] =
unsafe { mem::uninitialized() };
unsafe { MaybeUninit::uninit().assume_init() };
let mut req = httparse::Request::new(&mut parsed);
match req.parse(src)? {
@ -260,11 +262,12 @@ impl MessageType for ResponseHead {
fn decode(src: &mut BytesMut) -> Result<Option<(Self, PayloadType)>, ParseError> {
// Unsafe: we read only this data only after httparse parses headers into.
// performance bump for pipeline benchmarks.
let mut headers: [HeaderIndex; MAX_HEADERS] = unsafe { mem::uninitialized() };
let mut headers: [HeaderIndex; MAX_HEADERS] =
unsafe { MaybeUninit::uninit().assume_init() };
let (len, ver, status, h_len) = {
let mut parsed: [httparse::Header; MAX_HEADERS] =
unsafe { mem::uninitialized() };
unsafe { MaybeUninit::uninit().assume_init() };
let mut res = httparse::Response::new(&mut parsed);
match res.parse(src)? {

View File

@ -502,7 +502,7 @@ where
let pl = self.codec.message_type();
req.head_mut().peer_addr = self.peer_addr;
// on_connect data
// set on_connect data
if let Some(ref on_connect) = self.on_connect {
on_connect.set(&mut req.extensions_mut());
}

View File

@ -2,6 +2,7 @@
use std::fmt::Write as FmtWrite;
use std::io::Write;
use std::marker::PhantomData;
use std::rc::Rc;
use std::str::FromStr;
use std::{cmp, fmt, io, mem};
@ -15,7 +16,7 @@ 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::message::{ConnectionType, Head, RequestHead, RequestHeadType, ResponseHead};
use crate::request::Request;
use crate::response::Response;
@ -43,6 +44,8 @@ pub(crate) trait MessageType: Sized {
fn headers(&self) -> &HeaderMap;
fn extra_headers(&self) -> Option<&HeaderMap>;
fn camel_case(&self) -> bool {
false
}
@ -128,12 +131,22 @@ pub(crate) trait MessageType: Sized {
_ => (),
}
// merging headers from head and extra headers. HeaderMap::new() does not allocate.
let empty_headers = HeaderMap::new();
let extra_headers = self.extra_headers().unwrap_or(&empty_headers);
let headers = self
.headers()
.inner
.iter()
.filter(|(name, _)| !extra_headers.contains_key(*name))
.chain(extra_headers.inner.iter());
// write headers
let mut pos = 0;
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().inner.iter() {
for (key, value) in headers {
match *key {
CONNECTION => continue,
TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => continue,
@ -235,6 +248,10 @@ impl MessageType for Response<()> {
&self.head().headers
}
fn extra_headers(&self) -> Option<&HeaderMap> {
None
}
fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> {
let head = self.head();
let reason = head.reason().as_bytes();
@ -247,31 +264,36 @@ impl MessageType for Response<()> {
}
}
impl MessageType for RequestHead {
impl MessageType for RequestHeadType {
fn status(&self) -> Option<StatusCode> {
None
}
fn chunked(&self) -> bool {
self.chunked()
self.as_ref().chunked()
}
fn camel_case(&self) -> bool {
RequestHead::camel_case_headers(self)
self.as_ref().camel_case_headers()
}
fn headers(&self) -> &HeaderMap {
&self.headers
self.as_ref().headers()
}
fn extra_headers(&self) -> Option<&HeaderMap> {
self.extra_headers()
}
fn encode_status(&mut self, dst: &mut BytesMut) -> io::Result<()> {
dst.reserve(256 + self.headers.len() * AVERAGE_HEADER_SIZE);
let head = self.as_ref();
dst.reserve(256 + head.headers.len() * AVERAGE_HEADER_SIZE);
write!(
Writer(dst),
"{} {} {}",
self.method,
self.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"),
match self.version {
head.method,
head.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"),
match head.version {
Version::HTTP_09 => "HTTP/0.9",
Version::HTTP_10 => "HTTP/1.0",
Version::HTTP_11 => "HTTP/1.1",
@ -488,9 +510,11 @@ fn write_camel_case(value: &[u8], buffer: &mut [u8]) {
#[cfg(test)]
mod tests {
use bytes::Bytes;
//use std::rc::Rc;
use super::*;
use crate::http::header::{HeaderValue, CONTENT_TYPE};
use http::header::AUTHORIZATION;
#[test]
fn test_chunked_te() {
@ -515,6 +539,8 @@ mod tests {
head.headers
.insert(CONTENT_TYPE, HeaderValue::from_static("plain/text"));
let mut head = RequestHeadType::Owned(head);
let _ = head.encode_headers(
&mut bytes,
Version::HTTP_11,
@ -551,21 +577,16 @@ mod tests {
Bytes::from_static(b"\r\nContent-Length: 100\r\nDate: date\r\nContent-Type: plain/text\r\n\r\n")
);
let mut head = RequestHead::default();
head.set_camel_case_headers(false);
head.headers.insert(DATE, HeaderValue::from_static("date"));
head.headers
.insert(CONTENT_TYPE, HeaderValue::from_static("plain/text"));
head.headers
.append(CONTENT_TYPE, HeaderValue::from_static("xml"));
let _ = head.encode_headers(
&mut bytes,
Version::HTTP_11,
BodySize::Stream,
ConnectionType::KeepAlive,
&ServiceConfig::default(),
);
assert_eq!(
bytes.take().freeze(),
Bytes::from_static(b"\r\nTransfer-Encoding: chunked\r\nDate: date\r\nContent-Type: xml\r\nContent-Type: plain/text\r\n\r\n")
);
head.set_camel_case_headers(false);
let mut head = RequestHeadType::Owned(head);
let _ = head.encode_headers(
&mut bytes,
Version::HTTP_11,
@ -578,4 +599,36 @@ mod tests {
Bytes::from_static(b"\r\ntransfer-encoding: chunked\r\ndate: date\r\ncontent-type: xml\r\ncontent-type: plain/text\r\n\r\n")
);
}
#[test]
fn test_extra_headers() {
let mut bytes = BytesMut::with_capacity(2048);
let mut head = RequestHead::default();
head.headers.insert(
AUTHORIZATION,
HeaderValue::from_static("some authorization"),
);
let mut extra_headers = HeaderMap::new();
extra_headers.insert(
AUTHORIZATION,
HeaderValue::from_static("another authorization"),
);
extra_headers.insert(DATE, HeaderValue::from_static("date"));
let mut head = RequestHeadType::Rc(Rc::new(head), Some(extra_headers));
let _ = head.encode_headers(
&mut bytes,
Version::HTTP_11,
BodySize::Empty,
ConnectionType::Close,
&ServiceConfig::default(),
);
assert_eq!(
bytes.take().freeze(),
Bytes::from_static(b"\r\ncontent-length: 0\r\nconnection: close\r\nauthorization: another authorization\r\ndate: date\r\n\r\n")
);
}
}

View File

@ -23,6 +23,7 @@ use crate::cloneable::CloneableService;
use crate::config::ServiceConfig;
use crate::error::{DispatchError, Error, ParseError, PayloadError, ResponseError};
use crate::helpers::DataFactory;
use crate::httpmessage::HttpMessage;
use crate::message::ResponseHead;
use crate::payload::Payload;
use crate::request::Request;
@ -122,6 +123,12 @@ where
head.version = parts.version;
head.headers = parts.headers.into();
head.peer_addr = self.peer_addr;
// set on_connect data
if let Some(ref on_connect) = self.on_connect {
on_connect.set(&mut req.extensions_mut());
}
tokio_current_thread::spawn(ServiceResponse::<S::Future, B> {
state: ServiceResponseState::ServiceCall(
self.service.call(req),
@ -250,8 +257,8 @@ where
}
}
Ok(Async::NotReady) => Ok(Async::NotReady),
Err(_e) => {
let res: Response = Response::InternalServerError().finish();
Err(e) => {
let res: Response = e.into().into();
let (res, body) = res.replace_body(());
let mut send = send.take().unwrap();

View File

@ -16,7 +16,6 @@ use crate::httpmessage::HttpMessage;
mod common;
pub(crate) mod map;
mod shared;
#[doc(hidden)]
pub use self::common::*;
#[doc(hidden)]
pub use self::shared::*;

View File

@ -115,7 +115,7 @@ pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) {
pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) {
let mut curr: isize = 39;
let mut buf: [u8; 41] = unsafe { mem::uninitialized() };
let mut buf: [u8; 41] = unsafe { mem::MaybeUninit::uninit().assume_init() };
buf[39] = b'\r';
buf[40] = b'\n';
let buf_ptr = buf.as_mut_ptr();

View File

@ -29,7 +29,6 @@ impl Response {
STATIC_RESP!(AlreadyReported, StatusCode::ALREADY_REPORTED);
STATIC_RESP!(MultipleChoices, StatusCode::MULTIPLE_CHOICES);
STATIC_RESP!(MovedPermanenty, StatusCode::MOVED_PERMANENTLY);
STATIC_RESP!(MovedPermanently, StatusCode::MOVED_PERMANENTLY);
STATIC_RESP!(Found, StatusCode::FOUND);
STATIC_RESP!(SeeOther, StatusCode::SEE_OTHER);

View File

@ -39,7 +39,7 @@ pub use self::config::{KeepAlive, ServiceConfig};
pub use self::error::{Error, ResponseError, Result};
pub use self::extensions::Extensions;
pub use self::httpmessage::HttpMessage;
pub use self::message::{Message, RequestHead, ResponseHead};
pub use self::message::{Message, RequestHead, RequestHeadType, ResponseHead};
pub use self::payload::{Payload, PayloadStream};
pub use self::request::Request;
pub use self::response::{Response, ResponseBuilder};

View File

@ -181,6 +181,36 @@ impl RequestHead {
}
}
#[derive(Debug)]
pub enum RequestHeadType {
Owned(RequestHead),
Rc(Rc<RequestHead>, Option<HeaderMap>),
}
impl RequestHeadType {
pub fn extra_headers(&self) -> Option<&HeaderMap> {
match self {
RequestHeadType::Owned(_) => None,
RequestHeadType::Rc(_, headers) => headers.as_ref(),
}
}
}
impl AsRef<RequestHead> for RequestHeadType {
fn as_ref(&self) -> &RequestHead {
match self {
RequestHeadType::Owned(head) => &head,
RequestHeadType::Rc(head, _) => head.as_ref(),
}
}
}
impl From<RequestHead> for RequestHeadType {
fn from(head: RequestHead) -> Self {
RequestHeadType::Owned(head)
}
}
#[derive(Debug)]
pub struct ResponseHead {
pub version: Version,

View File

@ -194,7 +194,7 @@ impl<B> Response<B> {
self.head.extensions.borrow_mut()
}
/// Get body os this response
/// Get body of this response
#[inline]
pub fn body(&self) -> &ResponseBody<B> {
&self.body
@ -992,6 +992,14 @@ mod tests {
assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]");
}
#[test]
fn test_serde_json_in_body() {
use serde_json::json;
let resp =
Response::build(StatusCode::OK).body(json!({"test-key":"test-value"}));
assert_eq!(resp.body().get_ref(), br#"{"test-key":"test-value"}"#);
}
#[test]
fn test_into_response() {
let resp: Response = "test".into();

View File

@ -150,7 +150,7 @@ impl TestRequest {
/// Complete request creation and generate `Request` instance
pub fn finish(&mut self) -> Request {
let inner = self.0.take().expect("cannot reuse test request builder");;
let inner = self.0.take().expect("cannot reuse test request builder");
let mut req = if let Some(pl) = inner.payload {
Request::with_payload(pl)

View File

@ -454,9 +454,9 @@ fn test_h2_service_error() {
});
let response = srv.block_on(srv.sget("/").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"));
}

View File

@ -11,6 +11,7 @@ use futures::stream::{once, Stream};
use regex::Regex;
use tokio_timer::sleep;
use actix_http::httpmessage::HttpMessage;
use actix_http::{
body, error, http, http::header, Error, HttpService, KeepAlive, Request, Response,
};
@ -602,3 +603,18 @@ fn test_h1_service_error() {
let bytes = srv.load_body(response).unwrap();
assert_eq!(bytes, Bytes::from_static(b"error"));
}
#[test]
fn test_h1_on_connect() {
let mut srv = TestServer::new(|| {
HttpService::build()
.on_connect(|_| 10usize)
.h1(|req: Request| {
assert!(req.extensions().contains::<usize>());
future::ok::<_, ()>(Response::Ok().finish())
})
});
let response = srv.block_on(srv.get("/").send()).unwrap();
assert!(response.status().is_success());
}

View File

@ -1,9 +1,5 @@
#![cfg(feature = "ssl")]
use actix_codec::{AsyncRead, AsyncWrite};
use actix_http::error::{ErrorBadRequest, PayloadError};
use actix_http::http::header::{self, HeaderName, HeaderValue};
use actix_http::http::{Method, StatusCode, Version};
use actix_http::{body, Error, HttpService, Request, Response};
use actix_http_test::TestServer;
use actix_server::ssl::OpensslAcceptor;
use actix_server_config::ServerConfig;
@ -15,6 +11,12 @@ use futures::stream::{once, Stream};
use openssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod};
use std::io::Result;
use actix_http::error::{ErrorBadRequest, PayloadError};
use actix_http::http::header::{self, HeaderName, HeaderValue};
use actix_http::http::{Method, StatusCode, Version};
use actix_http::httpmessage::HttpMessage;
use actix_http::{body, Error, HttpService, Request, Response};
fn load_body<S>(stream: S) -> impl Future<Item = BytesMut, Error = PayloadError>
where
S: Stream<Item = Bytes, Error = PayloadError>,
@ -447,9 +449,32 @@ fn test_h2_service_error() {
});
let response = srv.block_on(srv.sget("/").send()).unwrap();
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
// read response
let bytes = srv.load_body(response).unwrap();
assert!(bytes.is_empty());
assert_eq!(bytes, Bytes::from_static(b"error"));
}
#[test]
fn test_h2_on_connect() {
let openssl = ssl_acceptor().unwrap();
let mut srv = TestServer::new(move || {
openssl
.clone()
.map_err(|e| println!("Openssl error: {}", e))
.and_then(
HttpService::build()
.on_connect(|_| 10usize)
.h2(|req: Request| {
assert!(req.extensions().contains::<usize>());
ok::<_, ()>(Response::Ok().finish())
})
.map_err(|_| ()),
)
});
let response = srv.block_on(srv.sget("/").send()).unwrap();
assert!(response.status().is_success());
}

View File

@ -1,6 +1,10 @@
# Changes
## [0.1.3] - 2019-06-06
## [0.1.4] - 2019-09-12
* Multipart handling now parses requests which do not end in CRLF #1038
## [0.1.3] - 2019-08-18
* Fix ring dependency from actix-web default features for #741.

View File

@ -1,6 +1,6 @@
[package]
name = "actix-multipart"
version = "0.1.3"
version = "0.1.4"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Multipart support for actix web framework."
readme = "README.md"

View File

@ -167,7 +167,7 @@ impl InnerMultipart {
boundary: &str,
) -> Result<Option<bool>, MultipartError> {
// TODO: need to read epilogue
match payload.readline()? {
match payload.readline_or_eof()? {
None => {
if payload.eof {
Ok(Some(true))
@ -176,15 +176,16 @@ impl InnerMultipart {
}
}
Some(chunk) => {
if chunk.len() == boundary.len() + 4
&& &chunk[..2] == b"--"
&& &chunk[2..boundary.len() + 2] == boundary.as_bytes()
if chunk.len() < boundary.len() + 4
|| &chunk[..2] != b"--"
|| &chunk[2..boundary.len() + 2] != boundary.as_bytes()
{
Err(MultipartError::Boundary)
} else if &chunk[boundary.len() + 2..] == b"\r\n" {
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"--"
} else if &chunk[boundary.len() + 2..boundary.len() + 4] == b"--"
&& (chunk.len() == boundary.len() + 4
|| &chunk[boundary.len() + 4..] == b"\r\n")
{
Ok(Some(true))
} else {
@ -779,6 +780,16 @@ impl PayloadBuffer {
self.read_until(b"\n")
}
/// Read bytes until new line delimiter or eof
pub fn readline_or_eof(&mut self) -> Result<Option<Bytes>, MultipartError> {
match self.readline() {
Err(MultipartError::Incomplete) if self.eof => {
Ok(Some(self.buf.take().freeze()))
}
line => line,
}
}
/// Put unprocessed data back to the buffer
pub fn unprocessed(&mut self, data: Bytes) {
let buf = BytesMut::from(data);
@ -849,32 +860,65 @@ mod tests {
(tx, rx.map_err(|_| panic!()).and_then(|res| res))
}
fn create_simple_request_with_header() -> (Bytes, HeaderMap) {
let bytes = Bytes::from(
"testasdadsad\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
test\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
data\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n",
);
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static(
"multipart/mixed; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"",
),
);
(bytes, headers)
}
#[test]
fn test_multipart_no_end_crlf() {
run_on(|| {
let (sender, payload) = create_stream();
let (bytes, headers) = create_simple_request_with_header();
let bytes_stripped = bytes.slice_to(bytes.len()); // strip crlf
sender.unbounded_send(Ok(bytes_stripped)).unwrap();
drop(sender); // eof
let mut multipart = Multipart::new(&headers, payload);
match multipart.poll().unwrap() {
Async::Ready(Some(_)) => (),
_ => unreachable!(),
}
match multipart.poll().unwrap() {
Async::Ready(Some(_)) => (),
_ => unreachable!(),
}
match multipart.poll().unwrap() {
Async::Ready(None) => (),
_ => unreachable!(),
}
})
}
#[test]
fn test_multipart() {
run_on(|| {
let (sender, payload) = create_stream();
let (bytes, headers) = create_simple_request_with_header();
let bytes = Bytes::from(
"testasdadsad\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
test\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
data\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n",
);
sender.unbounded_send(Ok(bytes)).unwrap();
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static(
"multipart/mixed; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"",
),
);
let mut multipart = Multipart::new(&headers, payload);
match multipart.poll().unwrap() {
Async::Ready(Some(mut field)) => {
@ -925,28 +969,10 @@ mod tests {
fn test_stream() {
run_on(|| {
let (sender, payload) = create_stream();
let (bytes, headers) = create_simple_request_with_header();
let bytes = Bytes::from(
"testasdadsad\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Disposition: form-data; name=\"file\"; filename=\"fn.txt\"\r\n\
Content-Type: text/plain; charset=utf-8\r\n\r\n\
test\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Type: text/plain; charset=utf-8\r\n\r\n\
data\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n",
);
sender.unbounded_send(Ok(bytes)).unwrap();
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
header::HeaderValue::from_static(
"multipart/mixed; boundary=\"abbc761f78ff4d7cb7573b5a23f96ef0\"",
),
);
let mut multipart = Multipart::new(&headers, payload);
match multipart.poll().unwrap() {
Async::Ready(Some(mut field)) => {

View File

@ -1,6 +1,32 @@
# Changes
## [0.2.4] - 2019-xx-xx
## [0.2.7] - 2019-09-25
### Added
* Remaining getter methods for `ClientRequest`'s private `head` field #1101
## [0.2.6] - 2019-09-12
### Added
* Export frozen request related types.
## [0.2.5] - 2019-09-11
### Added
* Add `FrozenClientRequest` to support retries for sending HTTP requests
### Changed
* Ensure that the `Host` header is set when initiating a WebSocket client connection.
## [0.2.4] - 2019-08-13
### Changed

View File

@ -1,6 +1,6 @@
[package]
name = "awc"
version = "0.2.3"
version = "0.2.7"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix http client."
readme = "README.md"
@ -44,7 +44,7 @@ flate2-rust = ["actix-http/flate2-rust"]
[dependencies]
actix-codec = "0.1.2"
actix-service = "0.4.1"
actix-http = "0.2.8"
actix-http = "0.2.10"
base64 = "0.10.1"
bytes = "0.4"
derive_more = "0.15.0"
@ -63,7 +63,7 @@ rustls = { version = "0.15.2", optional = true }
[dev-dependencies]
actix-rt = "0.2.2"
actix-web = { version = "1.0.0", features=["ssl"] }
actix-http = { version = "0.2.4", features=["ssl"] }
actix-http = { version = "0.2.10", features=["ssl"] }
actix-http-test = { version = "0.2.0", features=["ssl"] }
actix-utils = "0.4.1"
actix-server = { version = "0.6.0", features=["ssl", "rust-tls"] }

View File

@ -1,3 +1,4 @@
use std::rc::Rc;
use std::{fmt, io, net};
use actix_codec::{AsyncRead, AsyncWrite, Framed};
@ -6,7 +7,8 @@ use actix_http::client::{
Connect as ClientConnect, ConnectError, Connection, SendRequestError,
};
use actix_http::h1::ClientCodec;
use actix_http::{RequestHead, ResponseHead};
use actix_http::http::HeaderMap;
use actix_http::{RequestHead, RequestHeadType, ResponseHead};
use actix_service::Service;
use futures::{Future, Poll};
@ -22,6 +24,14 @@ pub(crate) trait Connect {
addr: Option<net::SocketAddr>,
) -> Box<dyn Future<Item = ClientResponse, Error = SendRequestError>>;
fn send_request_extra(
&mut self,
head: Rc<RequestHead>,
extra_headers: Option<HeaderMap>,
body: Body,
addr: Option<net::SocketAddr>,
) -> Box<dyn Future<Item = ClientResponse, Error = SendRequestError>>;
/// Send request, returns Response and Framed
fn open_tunnel(
&mut self,
@ -33,6 +43,19 @@ pub(crate) trait Connect {
Error = SendRequestError,
>,
>;
/// Send request and extra headers, returns Response and Framed
fn open_tunnel_extra(
&mut self,
head: Rc<RequestHead>,
extra_headers: Option<HeaderMap>,
addr: Option<net::SocketAddr>,
) -> Box<
dyn Future<
Item = (ResponseHead, Framed<BoxedSocket, ClientCodec>),
Error = SendRequestError,
>,
>;
}
impl<T> Connect for ConnectorWrapper<T>
@ -59,7 +82,33 @@ where
})
.from_err()
// send request
.and_then(move |connection| connection.send_request(head, body))
.and_then(move |connection| {
connection.send_request(RequestHeadType::from(head), body)
})
.map(|(head, payload)| ClientResponse::new(head, payload)),
)
}
fn send_request_extra(
&mut self,
head: Rc<RequestHead>,
extra_headers: Option<HeaderMap>,
body: Body,
addr: Option<net::SocketAddr>,
) -> Box<dyn Future<Item = ClientResponse, Error = SendRequestError>> {
Box::new(
self.0
// connect to the host
.call(ClientConnect {
uri: head.uri.clone(),
addr,
})
.from_err()
// send request
.and_then(move |connection| {
connection
.send_request(RequestHeadType::Rc(head, extra_headers), body)
})
.map(|(head, payload)| ClientResponse::new(head, payload)),
)
}
@ -83,7 +132,39 @@ where
})
.from_err()
// send request
.and_then(move |connection| connection.open_tunnel(head))
.and_then(move |connection| {
connection.open_tunnel(RequestHeadType::from(head))
})
.map(|(head, framed)| {
let framed = framed.map_io(|io| BoxedSocket(Box::new(Socket(io))));
(head, framed)
}),
)
}
fn open_tunnel_extra(
&mut self,
head: Rc<RequestHead>,
extra_headers: Option<HeaderMap>,
addr: Option<net::SocketAddr>,
) -> Box<
dyn Future<
Item = (ResponseHead, Framed<BoxedSocket, ClientCodec>),
Error = SendRequestError,
>,
> {
Box::new(
self.0
// connect to the host
.call(ClientConnect {
uri: head.uri.clone(),
addr,
})
.from_err()
// send request
.and_then(move |connection| {
connection.open_tunnel(RequestHeadType::Rc(head, extra_headers))
})
.map(|(head, framed)| {
let framed = framed.map_io(|io| BoxedSocket(Box::new(Socket(io))));
(head, framed)

View File

@ -1,5 +1,7 @@
//! Http client errors
pub use actix_http::client::{ConnectError, InvalidUrl, SendRequestError};
pub use actix_http::client::{
ConnectError, FreezeRequestError, InvalidUrl, SendRequestError,
};
pub use actix_http::error::PayloadError;
pub use actix_http::ws::HandshakeError as WsHandshakeError;
pub use actix_http::ws::ProtocolError as WsProtocolError;

235
awc/src/frozen.rs Normal file
View File

@ -0,0 +1,235 @@
use std::net;
use std::rc::Rc;
use std::time::Duration;
use bytes::Bytes;
use futures::Stream;
use serde::Serialize;
use actix_http::body::Body;
use actix_http::http::header::IntoHeaderValue;
use actix_http::http::{
Error as HttpError, HeaderMap, HeaderName, HttpTryFrom, Method, Uri,
};
use actix_http::{Error, RequestHead};
use crate::sender::{RequestSender, SendClientRequest};
use crate::ClientConfig;
/// `FrozenClientRequest` struct represents clonable client request.
/// It could be used to send same request multiple times.
#[derive(Clone)]
pub struct FrozenClientRequest {
pub(crate) head: Rc<RequestHead>,
pub(crate) addr: Option<net::SocketAddr>,
pub(crate) response_decompress: bool,
pub(crate) timeout: Option<Duration>,
pub(crate) config: Rc<ClientConfig>,
}
impl FrozenClientRequest {
/// Get HTTP URI of request
pub fn get_uri(&self) -> &Uri {
&self.head.uri
}
/// Get HTTP method of this request
pub fn get_method(&self) -> &Method {
&self.head.method
}
/// Returns request's headers.
pub fn headers(&self) -> &HeaderMap {
&self.head.headers
}
/// Send a body.
pub fn send_body<B>(&self, body: B) -> SendClientRequest
where
B: Into<Body>,
{
RequestSender::Rc(self.head.clone(), None).send_body(
self.addr,
self.response_decompress,
self.timeout,
self.config.as_ref(),
body,
)
}
/// Send a json body.
pub fn send_json<T: Serialize>(&self, value: &T) -> SendClientRequest {
RequestSender::Rc(self.head.clone(), None).send_json(
self.addr,
self.response_decompress,
self.timeout,
self.config.as_ref(),
value,
)
}
/// Send an urlencoded body.
pub fn send_form<T: Serialize>(&self, value: &T) -> SendClientRequest {
RequestSender::Rc(self.head.clone(), None).send_form(
self.addr,
self.response_decompress,
self.timeout,
self.config.as_ref(),
value,
)
}
/// Send a streaming body.
pub fn send_stream<S, E>(&self, stream: S) -> SendClientRequest
where
S: Stream<Item = Bytes, Error = E> + 'static,
E: Into<Error> + 'static,
{
RequestSender::Rc(self.head.clone(), None).send_stream(
self.addr,
self.response_decompress,
self.timeout,
self.config.as_ref(),
stream,
)
}
/// Send an empty body.
pub fn send(&self) -> SendClientRequest {
RequestSender::Rc(self.head.clone(), None).send(
self.addr,
self.response_decompress,
self.timeout,
self.config.as_ref(),
)
}
/// Create a `FrozenSendBuilder` with extra headers
pub fn extra_headers(&self, extra_headers: HeaderMap) -> FrozenSendBuilder {
FrozenSendBuilder::new(self.clone(), extra_headers)
}
/// Create a `FrozenSendBuilder` with an extra header
pub fn extra_header<K, V>(&self, key: K, value: V) -> FrozenSendBuilder
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
self.extra_headers(HeaderMap::new())
.extra_header(key, value)
}
}
/// Builder that allows to modify extra headers.
pub struct FrozenSendBuilder {
req: FrozenClientRequest,
extra_headers: HeaderMap,
err: Option<HttpError>,
}
impl FrozenSendBuilder {
pub(crate) fn new(req: FrozenClientRequest, extra_headers: HeaderMap) -> Self {
Self {
req,
extra_headers,
err: None,
}
}
/// Insert a header, it overrides existing header in `FrozenClientRequest`.
pub fn extra_header<K, V>(mut self, key: K, value: V) -> Self
where
HeaderName: HttpTryFrom<K>,
V: IntoHeaderValue,
{
match HeaderName::try_from(key) {
Ok(key) => match value.try_into() {
Ok(value) => self.extra_headers.insert(key, value),
Err(e) => self.err = Some(e.into()),
},
Err(e) => self.err = Some(e.into()),
}
self
}
/// Complete request construction and send a body.
pub fn send_body<B>(self, body: B) -> SendClientRequest
where
B: Into<Body>,
{
if let Some(e) = self.err {
return e.into();
}
RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_body(
self.req.addr,
self.req.response_decompress,
self.req.timeout,
self.req.config.as_ref(),
body,
)
}
/// Complete request construction and send a json body.
pub fn send_json<T: Serialize>(self, value: &T) -> SendClientRequest {
if let Some(e) = self.err {
return e.into();
}
RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_json(
self.req.addr,
self.req.response_decompress,
self.req.timeout,
self.req.config.as_ref(),
value,
)
}
/// Complete request construction and send an urlencoded body.
pub fn send_form<T: Serialize>(self, value: &T) -> SendClientRequest {
if let Some(e) = self.err {
return e.into();
}
RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_form(
self.req.addr,
self.req.response_decompress,
self.req.timeout,
self.req.config.as_ref(),
value,
)
}
/// Complete request construction and send a streaming body.
pub fn send_stream<S, E>(self, stream: S) -> SendClientRequest
where
S: Stream<Item = Bytes, Error = E> + 'static,
E: Into<Error> + 'static,
{
if let Some(e) = self.err {
return e.into();
}
RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_stream(
self.req.addr,
self.req.response_decompress,
self.req.timeout,
self.req.config.as_ref(),
stream,
)
}
/// Complete request construction and send an empty body.
pub fn send(self) -> SendClientRequest {
if let Some(e) = self.err {
return e.into();
}
RequestSender::Rc(self.req.head, Some(self.extra_headers)).send(
self.req.addr,
self.req.response_decompress,
self.req.timeout,
self.req.config.as_ref(),
)
}
}

View File

@ -33,15 +33,19 @@ use actix_http::RequestHead;
mod builder;
mod connect;
pub mod error;
mod frozen;
mod request;
mod response;
mod sender;
pub mod test;
pub mod ws;
pub use self::builder::ClientBuilder;
pub use self::connect::BoxedSocket;
pub use self::frozen::{FrozenClientRequest, FrozenSendBuilder};
pub use self::request::ClientRequest;
pub use self::response::{ClientResponse, JsonBody, MessageBody};
pub use self::sender::SendClientRequest;
use self::connect::{Connect, ConnectorWrapper};

View File

@ -5,25 +5,22 @@ use std::time::Duration;
use std::{fmt, net};
use bytes::{BufMut, Bytes, BytesMut};
use futures::future::{err, Either};
use futures::{Future, Stream};
use futures::Stream;
use percent_encoding::percent_encode;
use serde::Serialize;
use serde_json;
use tokio_timer::Timeout;
use actix_http::body::{Body, BodyStream};
use actix_http::body::Body;
use actix_http::cookie::{Cookie, CookieJar, USERINFO};
use actix_http::encoding::Decoder;
use actix_http::http::header::{self, ContentEncoding, Header, IntoHeaderValue};
use actix_http::http::header::{self, Header, IntoHeaderValue};
use actix_http::http::{
uri, ConnectionType, Error as HttpError, HeaderMap, HeaderName, HeaderValue,
HttpTryFrom, Method, Uri, Version,
};
use actix_http::{Error, Payload, RequestHead};
use actix_http::{Error, RequestHead};
use crate::error::{InvalidUrl, PayloadError, SendRequestError};
use crate::response::ClientResponse;
use crate::error::{FreezeRequestError, InvalidUrl};
use crate::frozen::FrozenClientRequest;
use crate::sender::{PrepForSendingError, RequestSender, SendClientRequest};
use crate::ClientConfig;
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))]
@ -99,6 +96,11 @@ impl ClientRequest {
self
}
/// Get HTTP URI of request.
pub fn get_uri(&self) -> &Uri {
&self.head.uri
}
/// Set socket address of the server.
///
/// This address is used for connection. If address is not
@ -115,6 +117,11 @@ impl ClientRequest {
self
}
/// Get HTTP method of this request
pub fn get_method(&self) -> &Method {
&self.head.method
}
#[doc(hidden)]
/// Set HTTP version of this request.
///
@ -125,6 +132,16 @@ impl ClientRequest {
self
}
/// Get HTTP version of this request.
pub fn get_version(&self) -> &Version {
&self.head.version
}
/// Get peer address of this request.
pub fn get_peer_addr(&self) -> &Option<net::SocketAddr> {
&self.head.peer_addr
}
#[inline]
/// Returns request's headers.
pub fn headers(&self) -> &HeaderMap {
@ -365,34 +382,131 @@ impl ClientRequest {
}
}
/// Freeze request builder and construct `FrozenClientRequest`,
/// which could be used for sending same request multiple times.
pub fn freeze(self) -> Result<FrozenClientRequest, FreezeRequestError> {
let slf = match self.prep_for_sending() {
Ok(slf) => slf,
Err(e) => return Err(e.into()),
};
let request = FrozenClientRequest {
head: Rc::new(slf.head),
addr: slf.addr,
response_decompress: slf.response_decompress,
timeout: slf.timeout,
config: slf.config,
};
Ok(request)
}
/// Complete request construction and send body.
pub fn send_body<B>(
mut self,
body: B,
) -> impl Future<
Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
Error = SendRequestError,
>
pub fn send_body<B>(self, body: B) -> SendClientRequest
where
B: Into<Body>,
{
if let Some(e) = self.err.take() {
return Either::A(err(e.into()));
let slf = match self.prep_for_sending() {
Ok(slf) => slf,
Err(e) => return e.into(),
};
RequestSender::Owned(slf.head).send_body(
slf.addr,
slf.response_decompress,
slf.timeout,
slf.config.as_ref(),
body,
)
}
/// Set a JSON body and generate `ClientRequest`
pub fn send_json<T: Serialize>(self, value: &T) -> SendClientRequest {
let slf = match self.prep_for_sending() {
Ok(slf) => slf,
Err(e) => return e.into(),
};
RequestSender::Owned(slf.head).send_json(
slf.addr,
slf.response_decompress,
slf.timeout,
slf.config.as_ref(),
value,
)
}
/// Set a urlencoded body and generate `ClientRequest`
///
/// `ClientRequestBuilder` can not be used after this call.
pub fn send_form<T: Serialize>(self, value: &T) -> SendClientRequest {
let slf = match self.prep_for_sending() {
Ok(slf) => slf,
Err(e) => return e.into(),
};
RequestSender::Owned(slf.head).send_form(
slf.addr,
slf.response_decompress,
slf.timeout,
slf.config.as_ref(),
value,
)
}
/// Set an streaming body and generate `ClientRequest`.
pub fn send_stream<S, E>(self, stream: S) -> SendClientRequest
where
S: Stream<Item = Bytes, Error = E> + 'static,
E: Into<Error> + 'static,
{
let slf = match self.prep_for_sending() {
Ok(slf) => slf,
Err(e) => return e.into(),
};
RequestSender::Owned(slf.head).send_stream(
slf.addr,
slf.response_decompress,
slf.timeout,
slf.config.as_ref(),
stream,
)
}
/// Set an empty body and generate `ClientRequest`.
pub fn send(self) -> SendClientRequest {
let slf = match self.prep_for_sending() {
Ok(slf) => slf,
Err(e) => return e.into(),
};
RequestSender::Owned(slf.head).send(
slf.addr,
slf.response_decompress,
slf.timeout,
slf.config.as_ref(),
)
}
fn prep_for_sending(mut self) -> Result<Self, PrepForSendingError> {
if let Some(e) = self.err {
return Err(e.into());
}
// validate uri
let uri = &self.head.uri;
if uri.host().is_none() {
return Either::A(err(InvalidUrl::MissingHost.into()));
return Err(InvalidUrl::MissingHost.into());
} else if uri.scheme_part().is_none() {
return Either::A(err(InvalidUrl::MissingScheme.into()));
return Err(InvalidUrl::MissingScheme.into());
} else if let Some(scheme) = uri.scheme_part() {
match scheme.as_str() {
"http" | "ws" | "https" | "wss" => (),
_ => return Either::A(err(InvalidUrl::UnknownScheme.into())),
_ => return Err(InvalidUrl::UnknownScheme.into()),
}
} else {
return Either::A(err(InvalidUrl::UnknownScheme.into()));
return Err(InvalidUrl::UnknownScheme.into());
}
// set cookies
@ -438,104 +552,7 @@ impl ClientRequest {
}
}
let head = slf.head;
let config = slf.config.as_ref();
let response_decompress = slf.response_decompress;
let fut = config
.connector
.borrow_mut()
.send_request(head, body.into(), slf.addr)
.map(move |res| {
res.map_body(|head, payload| {
if response_decompress {
Payload::Stream(Decoder::from_headers(payload, &head.headers))
} else {
Payload::Stream(Decoder::new(payload, ContentEncoding::Identity))
}
})
});
// set request timeout
if let Some(timeout) = slf.timeout.or_else(|| config.timeout) {
Either::B(Either::A(Timeout::new(fut, timeout).map_err(|e| {
if let Some(e) = e.into_inner() {
e
} else {
SendRequestError::Timeout
}
})))
} else {
Either::B(Either::B(fut))
}
}
/// Set a JSON body and generate `ClientRequest`
pub fn send_json<T: Serialize>(
self,
value: &T,
) -> impl Future<
Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
Error = SendRequestError,
> {
let body = match serde_json::to_string(value) {
Ok(body) => body,
Err(e) => return Either::A(err(Error::from(e).into())),
};
// set content-type
let slf = self.set_header_if_none(header::CONTENT_TYPE, "application/json");
Either::B(slf.send_body(Body::Bytes(Bytes::from(body))))
}
/// Set a urlencoded body and generate `ClientRequest`
///
/// `ClientRequestBuilder` can not be used after this call.
pub fn send_form<T: Serialize>(
self,
value: &T,
) -> impl Future<
Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
Error = SendRequestError,
> {
let body = match serde_urlencoded::to_string(value) {
Ok(body) => body,
Err(e) => return Either::A(err(Error::from(e).into())),
};
// set content-type
let slf = self.set_header_if_none(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
);
Either::B(slf.send_body(Body::Bytes(Bytes::from(body))))
}
/// Set an streaming body and generate `ClientRequest`.
pub fn send_stream<S, E>(
self,
stream: S,
) -> impl Future<
Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
Error = SendRequestError,
>
where
S: Stream<Item = Bytes, Error = E> + 'static,
E: Into<Error> + 'static,
{
self.send_body(Body::from_message(BodyStream::new(stream)))
}
/// Set an empty body and generate `ClientRequest`.
pub fn send(
self,
) -> impl Future<
Item = ClientResponse<impl Stream<Item = Bytes, Error = PayloadError>>,
Error = SendRequestError,
> {
self.send_body(Body::Empty)
Ok(slf)
}
}

282
awc/src/sender.rs Normal file
View File

@ -0,0 +1,282 @@
use std::net;
use std::rc::Rc;
use std::time::{Duration, Instant};
use bytes::Bytes;
use derive_more::From;
use futures::{try_ready, Async, Future, Poll, Stream};
use serde::Serialize;
use serde_json;
use tokio_timer::Delay;
use actix_http::body::{Body, BodyStream};
use actix_http::encoding::Decoder;
use actix_http::http::header::{self, ContentEncoding, IntoHeaderValue};
use actix_http::http::{Error as HttpError, HeaderMap, HeaderName};
use actix_http::{Error, Payload, PayloadStream, RequestHead};
use crate::error::{FreezeRequestError, InvalidUrl, SendRequestError};
use crate::response::ClientResponse;
use crate::ClientConfig;
#[derive(Debug, From)]
pub(crate) enum PrepForSendingError {
Url(InvalidUrl),
Http(HttpError),
}
impl Into<FreezeRequestError> for PrepForSendingError {
fn into(self) -> FreezeRequestError {
match self {
PrepForSendingError::Url(e) => FreezeRequestError::Url(e),
PrepForSendingError::Http(e) => FreezeRequestError::Http(e),
}
}
}
impl Into<SendRequestError> for PrepForSendingError {
fn into(self) -> SendRequestError {
match self {
PrepForSendingError::Url(e) => SendRequestError::Url(e),
PrepForSendingError::Http(e) => SendRequestError::Http(e),
}
}
}
/// Future that sends request's payload and resolves to a server response.
#[must_use = "futures do nothing unless polled"]
pub enum SendClientRequest {
Fut(
Box<dyn Future<Item = ClientResponse, Error = SendRequestError>>,
Option<Delay>,
bool,
),
Err(Option<SendRequestError>),
}
impl SendClientRequest {
pub(crate) fn new(
send: Box<dyn Future<Item = ClientResponse, Error = SendRequestError>>,
response_decompress: bool,
timeout: Option<Duration>,
) -> SendClientRequest {
let delay = timeout.map(|t| Delay::new(Instant::now() + t));
SendClientRequest::Fut(send, delay, response_decompress)
}
}
impl Future for SendClientRequest {
type Item = ClientResponse<Decoder<Payload<PayloadStream>>>;
type Error = SendRequestError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self {
SendClientRequest::Fut(send, delay, response_decompress) => {
if delay.is_some() {
match delay.poll() {
Ok(Async::NotReady) => (),
_ => return Err(SendRequestError::Timeout),
}
}
let res = try_ready!(send.poll()).map_body(|head, payload| {
if *response_decompress {
Payload::Stream(Decoder::from_headers(payload, &head.headers))
} else {
Payload::Stream(Decoder::new(payload, ContentEncoding::Identity))
}
});
Ok(Async::Ready(res))
}
SendClientRequest::Err(ref mut e) => match e.take() {
Some(e) => Err(e),
None => panic!("Attempting to call completed future"),
},
}
}
}
impl From<SendRequestError> for SendClientRequest {
fn from(e: SendRequestError) -> Self {
SendClientRequest::Err(Some(e))
}
}
impl From<Error> for SendClientRequest {
fn from(e: Error) -> Self {
SendClientRequest::Err(Some(e.into()))
}
}
impl From<HttpError> for SendClientRequest {
fn from(e: HttpError) -> Self {
SendClientRequest::Err(Some(e.into()))
}
}
impl From<PrepForSendingError> for SendClientRequest {
fn from(e: PrepForSendingError) -> Self {
SendClientRequest::Err(Some(e.into()))
}
}
#[derive(Debug)]
pub(crate) enum RequestSender {
Owned(RequestHead),
Rc(Rc<RequestHead>, Option<HeaderMap>),
}
impl RequestSender {
pub(crate) fn send_body<B>(
self,
addr: Option<net::SocketAddr>,
response_decompress: bool,
timeout: Option<Duration>,
config: &ClientConfig,
body: B,
) -> SendClientRequest
where
B: Into<Body>,
{
let mut connector = config.connector.borrow_mut();
let fut = match self {
RequestSender::Owned(head) => {
connector.send_request(head, body.into(), addr)
}
RequestSender::Rc(head, extra_headers) => {
connector.send_request_extra(head, extra_headers, body.into(), addr)
}
};
SendClientRequest::new(
fut,
response_decompress,
timeout.or_else(|| config.timeout),
)
}
pub(crate) fn send_json<T: Serialize>(
mut self,
addr: Option<net::SocketAddr>,
response_decompress: bool,
timeout: Option<Duration>,
config: &ClientConfig,
value: &T,
) -> SendClientRequest {
let body = match serde_json::to_string(value) {
Ok(body) => body,
Err(e) => return Error::from(e).into(),
};
if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/json")
{
return e.into();
}
self.send_body(
addr,
response_decompress,
timeout,
config,
Body::Bytes(Bytes::from(body)),
)
}
pub(crate) fn send_form<T: Serialize>(
mut self,
addr: Option<net::SocketAddr>,
response_decompress: bool,
timeout: Option<Duration>,
config: &ClientConfig,
value: &T,
) -> SendClientRequest {
let body = match serde_urlencoded::to_string(value) {
Ok(body) => body,
Err(e) => return Error::from(e).into(),
};
// set content-type
if let Err(e) = self.set_header_if_none(
header::CONTENT_TYPE,
"application/x-www-form-urlencoded",
) {
return e.into();
}
self.send_body(
addr,
response_decompress,
timeout,
config,
Body::Bytes(Bytes::from(body)),
)
}
pub(crate) fn send_stream<S, E>(
self,
addr: Option<net::SocketAddr>,
response_decompress: bool,
timeout: Option<Duration>,
config: &ClientConfig,
stream: S,
) -> SendClientRequest
where
S: Stream<Item = Bytes, Error = E> + 'static,
E: Into<Error> + 'static,
{
self.send_body(
addr,
response_decompress,
timeout,
config,
Body::from_message(BodyStream::new(stream)),
)
}
pub(crate) fn send(
self,
addr: Option<net::SocketAddr>,
response_decompress: bool,
timeout: Option<Duration>,
config: &ClientConfig,
) -> SendClientRequest {
self.send_body(addr, response_decompress, timeout, config, Body::Empty)
}
fn set_header_if_none<V>(
&mut self,
key: HeaderName,
value: V,
) -> Result<(), HttpError>
where
V: IntoHeaderValue,
{
match self {
RequestSender::Owned(head) => {
if !head.headers.contains_key(&key) {
match value.try_into() {
Ok(value) => head.headers.insert(key, value),
Err(e) => return Err(e.into()),
}
}
}
RequestSender::Rc(head, extra_headers) => {
if !head.headers.contains_key(&key)
&& !extra_headers.iter().any(|h| h.contains_key(&key))
{
match value.try_into() {
Ok(v) => {
let h = extra_headers.get_or_insert(HeaderMap::new());
h.insert(key, v)
}
Err(e) => return Err(e.into()),
};
}
}
}
Ok(())
}
}

View File

@ -233,6 +233,13 @@ impl WebsocketsRequest {
return Either::A(err(InvalidUrl::UnknownScheme.into()));
}
if !self.head.headers.contains_key(header::HOST) {
self.head.headers.insert(
header::HOST,
HeaderValue::from_str(uri.host().unwrap()).unwrap(),
);
}
// set cookies
if let Some(ref mut jar) = self.cookies {
let mut cookie = String::new();

View File

@ -169,7 +169,7 @@ where
match self.data_factories_fut[idx].poll()? {
Async::Ready(f) => {
self.data_factories.push(f);
self.data_factories_fut.remove(idx);
let _ = self.data_factories_fut.remove(idx);
}
Async::NotReady => idx += 1,
}

View File

@ -77,6 +77,11 @@ impl<T> Data<T> {
pub fn get_ref(&self) -> &T {
self.0.as_ref()
}
/// Convert to the internal Arc<T>
pub fn into_inner(self) -> Arc<T> {
self.0
}
}
impl<T> Deref for Data<T> {

View File

@ -32,8 +32,12 @@ pub enum UrlencodedError {
#[display(fmt = "Can not decode chunked transfer encoding")]
Chunked,
/// Payload size is bigger than allowed. (default: 256kB)
#[display(fmt = "Urlencoded payload size is bigger than allowed (default: 256kB)")]
Overflow,
#[display(
fmt = "Urlencoded payload size is bigger ({} bytes) than allowed (default: {} bytes)",
size,
limit
)]
Overflow { size: usize, limit: usize },
/// Payload size is now known
#[display(fmt = "Payload size is now known")]
UnknownLength,
@ -52,7 +56,7 @@ pub enum UrlencodedError {
impl ResponseError for UrlencodedError {
fn error_response(&self) -> HttpResponse {
match *self {
UrlencodedError::Overflow => {
UrlencodedError::Overflow { .. } => {
HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE)
}
UrlencodedError::UnknownLength => {
@ -164,7 +168,8 @@ mod tests {
#[test]
fn test_urlencoded_error() {
let resp: HttpResponse = UrlencodedError::Overflow.error_response();
let resp: HttpResponse =
UrlencodedError::Overflow { size: 0, limit: 0 }.error_response();
assert_eq!(resp.status(), StatusCode::PAYLOAD_TOO_LARGE);
let resp: HttpResponse = UrlencodedError::UnknownLength.error_response();
assert_eq!(resp.status(), StatusCode::LENGTH_REQUIRED);

View File

@ -46,9 +46,9 @@ pub trait FromRequest: Sized {
/// ## Example
///
/// ```rust
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::{web, dev, App, Error, HttpRequest, FromRequest};
/// use actix_web::error::ErrorBadRequest;
/// use serde_derive::Deserialize;
/// use rand;
///
/// #[derive(Debug, Deserialize)]
@ -119,9 +119,9 @@ where
/// ## Example
///
/// ```rust
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::{web, dev, App, Result, Error, HttpRequest, FromRequest};
/// use actix_web::error::ErrorBadRequest;
/// use serde_derive::Deserialize;
/// use rand;
///
/// #[derive(Debug, Deserialize)]

View File

@ -1,16 +1,16 @@
//! Route match guards.
//!
//! Guards are one of the way how actix-web router chooses
//! handler service. In essence it just function that accepts
//! reference to a `RequestHead` instance and returns boolean.
//! Guards are one of the ways how actix-web router chooses a
//! handler service. In essence it is just a function that accepts a
//! reference to a `RequestHead` instance and returns a boolean.
//! It is possible to add guards to *scopes*, *resources*
//! and *routes*. Actix provide several guards by default, like various
//! http methods, header, etc. To become a guard, type must implement `Guard`
//! trait. Simple functions coulds guards as well.
//!
//! Guard can not modify request object. But it is possible to
//! to store extra attributes on a request by using `Extensions` container.
//! Extensions container available via `RequestHead::extensions()` method.
//! Guards can not modify the request object. But it is possible
//! to store extra attributes on a request by using the `Extensions` container.
//! Extensions containers are available via the `RequestHead::extensions()` method.
//!
//! ```rust
//! use actix_web::{web, http, dev, guard, App, HttpResponse};
@ -29,11 +29,11 @@
use actix_http::http::{self, header, uri::Uri, HttpTryFrom};
use actix_http::RequestHead;
/// Trait defines resource guards. Guards are used for routes selection.
/// Trait defines resource guards. Guards are used for route selection.
///
/// Guard can not modify request object. But it is possible to
/// to store extra attributes on request by using `Extensions` container,
/// Extensions container available via `RequestHead::extensions()` method.
/// Guards can not modify the request object. But it is possible
/// to store extra attributes on a request by using the `Extensions` container.
/// Extensions containers are available via the `RequestHead::extensions()` method.
pub trait Guard {
/// Check if request matches predicate
fn check(&self, request: &RequestHead) -> bool;

View File

@ -155,9 +155,9 @@ impl ConnectionInfo {
&self.host
}
/// Remote IP of client initiated HTTP request.
/// Remote socket addr of client initiated HTTP request.
///
/// The IP is resolved through the following headers, in this order:
/// The addr is resolved through the following headers, in this order:
///
/// - Forwarded
/// - X-Forwarded-For

143
src/middleware/condition.rs Normal file
View File

@ -0,0 +1,143 @@
//! `Middleware` for conditionally enables another middleware.
use actix_service::{Service, Transform};
use futures::future::{ok, Either, FutureResult, Map};
use futures::{Future, Poll};
/// `Middleware` for conditionally enables another middleware.
/// The controled middleware must not change the `Service` interfaces.
/// This means you cannot control such middlewares like `Logger` or `Compress`.
///
/// ## Usage
///
/// ```rust
/// use actix_web::middleware::{Condition, NormalizePath};
/// use actix_web::App;
///
/// fn main() {
/// let enable_normalize = std::env::var("NORMALIZE_PATH") == Ok("true".into());
/// let app = App::new()
/// .wrap(Condition::new(enable_normalize, NormalizePath));
/// }
/// ```
pub struct Condition<T> {
trans: T,
enable: bool,
}
impl<T> Condition<T> {
pub fn new(enable: bool, trans: T) -> Self {
Self { trans, enable }
}
}
impl<S, T> Transform<S> for Condition<T>
where
S: Service,
T: Transform<S, Request = S::Request, Response = S::Response, Error = S::Error>,
{
type Request = S::Request;
type Response = S::Response;
type Error = S::Error;
type InitError = T::InitError;
type Transform = ConditionMiddleware<T::Transform, S>;
type Future = Either<
Map<T::Future, fn(T::Transform) -> Self::Transform>,
FutureResult<Self::Transform, Self::InitError>,
>;
fn new_transform(&self, service: S) -> Self::Future {
if self.enable {
let f = self
.trans
.new_transform(service)
.map(ConditionMiddleware::Enable as fn(T::Transform) -> Self::Transform);
Either::A(f)
} else {
Either::B(ok(ConditionMiddleware::Disable(service)))
}
}
}
pub enum ConditionMiddleware<E, D> {
Enable(E),
Disable(D),
}
impl<E, D> Service for ConditionMiddleware<E, D>
where
E: Service,
D: Service<Request = E::Request, Response = E::Response, Error = E::Error>,
{
type Request = E::Request;
type Response = E::Response;
type Error = E::Error;
type Future = Either<E::Future, D::Future>;
fn poll_ready(&mut self) -> Poll<(), Self::Error> {
use ConditionMiddleware::*;
match self {
Enable(service) => service.poll_ready(),
Disable(service) => service.poll_ready(),
}
}
fn call(&mut self, req: E::Request) -> Self::Future {
use ConditionMiddleware::*;
match self {
Enable(service) => Either::A(service.call(req)),
Disable(service) => Either::B(service.call(req)),
}
}
}
#[cfg(test)]
mod tests {
use actix_service::IntoService;
use super::*;
use crate::dev::{ServiceRequest, ServiceResponse};
use crate::error::Result;
use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode};
use crate::middleware::errhandlers::*;
use crate::test::{self, TestRequest};
use crate::HttpResponse;
fn render_500<B>(mut res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
res.response_mut()
.headers_mut()
.insert(CONTENT_TYPE, HeaderValue::from_static("0001"));
Ok(ErrorHandlerResponse::Response(res))
}
#[test]
fn test_handler_enabled() {
let srv = |req: ServiceRequest| {
req.into_response(HttpResponse::InternalServerError().finish())
};
let mw =
ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500);
let mut mw =
test::block_on(Condition::new(true, mw).new_transform(srv.into_service()))
.unwrap();
let resp = test::call_service(&mut mw, TestRequest::default().to_srv_request());
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
}
#[test]
fn test_handler_disabled() {
let srv = |req: ServiceRequest| {
req.into_response(HttpResponse::InternalServerError().finish())
};
let mw =
ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500);
let mut mw =
test::block_on(Condition::new(false, mw).new_transform(srv.into_service()))
.unwrap();
let resp = test::call_service(&mut mw, TestRequest::default().to_srv_request());
assert_eq!(resp.headers().get(CONTENT_TYPE), None);
}
}

View File

@ -2,11 +2,13 @@
mod compress;
pub use self::compress::{BodyEncoding, Compress};
mod condition;
mod defaultheaders;
pub mod errhandlers;
mod logger;
mod normalize;
pub use self::condition::Condition;
pub use self::defaultheaders::DefaultHeaders;
pub use self::logger::Logger;
pub use self::normalize::NormalizePath;

View File

@ -151,5 +151,4 @@ mod tests {
let res = block_on(normalize.call(req)).unwrap();
assert!(res.status().is_success());
}
}

View File

@ -259,6 +259,7 @@ impl Drop for HttpRequest {
if Rc::strong_count(&self.0) == 1 {
let v = &mut self.0.pool.0.borrow_mut();
if v.len() < 128 {
self.extensions_mut().clear();
v.push(self.0.clone());
}
}
@ -270,8 +271,8 @@ impl Drop for HttpRequest {
/// ## Example
///
/// ```rust
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::{web, App, HttpRequest};
/// use serde_derive::Deserialize;
///
/// /// extract `Thing` from request
/// fn index(req: HttpRequest) -> String {
@ -494,4 +495,38 @@ mod tests {
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
#[test]
fn test_extensions_dropped() {
struct Tracker {
pub dropped: bool,
}
struct Foo {
tracker: Rc<RefCell<Tracker>>,
}
impl Drop for Foo {
fn drop(&mut self) {
self.tracker.borrow_mut().dropped = true;
}
}
let tracker = Rc::new(RefCell::new(Tracker { dropped: false }));
{
let tracker2 = Rc::clone(&tracker);
let mut srv = init_service(App::new().data(10u32).service(
web::resource("/").to(move |req: HttpRequest| {
req.extensions_mut().insert(Foo {
tracker: Rc::clone(&tracker2),
});
HttpResponse::Ok()
}),
));
let req = TestRequest::default().to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
}
assert!(tracker.borrow().dropped);
}
}

View File

@ -189,11 +189,21 @@ where
/// ));
/// }
/// ```
pub fn data<U: 'static>(mut self, data: U) -> Self {
pub fn data<U: 'static>(self, data: U) -> Self {
self.register_data(Data::new(data))
}
/// Set or override application data.
///
/// This method has the same effect as [`Resource::data`](#method.data),
/// except that instead of taking a value of some type `T`, it expects a
/// value of type `Data<T>`. Use a `Data<T>` extractor to retrieve its
/// value.
pub fn register_data<U: 'static>(mut self, data: Data<U>) -> Self {
if self.data.is_none() {
self.data = Some(Extensions::new());
}
self.data.as_mut().unwrap().insert(Data::new(data));
self.data.as_mut().unwrap().insert(data);
self
}
@ -764,4 +774,33 @@ mod tests {
assert_eq!(resp.status(), StatusCode::NO_CONTENT);
}
#[test]
fn test_data() {
let mut srv = init_service(
App::new()
.data(1.0f64)
.data(1usize)
.register_data(web::Data::new('-'))
.service(
web::resource("/test")
.data(10usize)
.register_data(web::Data::new('*'))
.guard(guard::Get())
.to(
|data1: web::Data<usize>,
data2: web::Data<char>,
data3: web::Data<f64>| {
assert_eq!(*data1, 10);
assert_eq!(*data2, '*');
assert_eq!(*data3, 1.0);
HttpResponse::Ok()
},
),
),
);
let req = TestRequest::get().uri("/test").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
}
}

View File

@ -178,8 +178,8 @@ impl Route {
/// Set handler function, use request extractors for parameters.
///
/// ```rust
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{web, http, App};
/// use serde_derive::Deserialize;
///
/// #[derive(Deserialize)]
/// struct Info {
@ -239,9 +239,9 @@ impl Route {
///
/// ```rust
/// # use futures::future::ok;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{web, App, Error};
/// use futures::Future;
/// use serde_derive::Deserialize;
///
/// #[derive(Deserialize)]
/// struct Info {

View File

@ -148,11 +148,20 @@ where
/// );
/// }
/// ```
pub fn data<U: 'static>(mut self, data: U) -> Self {
pub fn data<U: 'static>(self, data: U) -> Self {
self.register_data(Data::new(data))
}
/// Set or override application data.
///
/// This method has the same effect as [`Scope::data`](#method.data), except
/// that instead of taking a value of some type `T`, it expects a value of
/// type `Data<T>`. Use a `Data<T>` extractor to retrieve its value.
pub fn register_data<U: 'static>(mut self, data: Data<U>) -> Self {
if self.data.is_none() {
self.data = Some(Extensions::new());
}
self.data.as_mut().unwrap().insert(Data::new(data));
self.data.as_mut().unwrap().insert(data);
self
}
@ -1082,6 +1091,28 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn test_override_register_data() {
let mut srv = init_service(
App::new().register_data(web::Data::new(1usize)).service(
web::scope("app")
.register_data(web::Data::new(10usize))
.route(
"/t",
web::get().to(|data: web::Data<usize>| {
assert_eq!(*data, 10);
let _ = data.clone();
HttpResponse::Ok()
}),
),
),
);
let req = TestRequest::with_uri("/app/t").to_request();
let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn test_scope_config() {
let mut srv =

View File

@ -435,6 +435,37 @@ where
Ok(self)
}
#[cfg(feature = "uds")]
/// Start listening for unix domain connections on existing listener.
///
/// This method is available with `uds` feature.
pub fn listen_uds(
mut self,
lst: std::os::unix::net::UnixListener,
) -> io::Result<Self> {
let cfg = self.config.clone();
let factory = self.factory.clone();
// todo duplicated:
self.sockets.push(Socket {
scheme: "http",
addr: net::SocketAddr::new(
net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)),
8080,
),
});
let addr = format!("actix-web-service-{:?}", lst.local_addr()?);
self.builder = self.builder.listen_uds(addr, lst, move || {
let c = cfg.lock();
HttpService::build()
.keep_alive(c.keep_alive)
.client_timeout(c.client_timeout)
.finish(factory())
})?;
Ok(self)
}
#[cfg(feature = "uds")]
/// Start listening for incoming unix domain connections.
///

View File

@ -68,6 +68,34 @@ impl ServiceRequest {
(self.0, pl)
}
/// Construct request from parts.
///
/// `ServiceRequest` can be re-constructed only if `req` hasnt been cloned.
pub fn from_parts(
mut req: HttpRequest,
pl: Payload,
) -> Result<Self, (HttpRequest, Payload)> {
if Rc::strong_count(&req.0) == 1 && Rc::weak_count(&req.0) == 0 {
Rc::get_mut(&mut req.0).unwrap().payload = pl;
Ok(ServiceRequest(req))
} else {
Err((req, pl))
}
}
/// Construct request from request.
///
/// `HttpRequest` implements `Clone` trait via `Rc` type. `ServiceRequest`
/// can be re-constructed only if rc's strong pointers count eq 1 and
/// weak pointers count is 0.
pub fn from_request(req: HttpRequest) -> Result<Self, HttpRequest> {
if Rc::strong_count(&req.0) == 1 && Rc::weak_count(&req.0) == 0 {
Ok(ServiceRequest(req))
} else {
Err(req)
}
}
/// Create service response
#[inline]
pub fn into_response<B, R: Into<Response<B>>>(self, res: R) -> ServiceResponse<B> {
@ -514,6 +542,27 @@ mod tests {
use crate::test::{call_service, init_service, TestRequest};
use crate::{guard, http, web, App, HttpResponse};
#[test]
fn test_service_request() {
let req = TestRequest::default().to_srv_request();
let (r, pl) = req.into_parts();
assert!(ServiceRequest::from_parts(r, pl).is_ok());
let req = TestRequest::default().to_srv_request();
let (r, pl) = req.into_parts();
let _r2 = r.clone();
assert!(ServiceRequest::from_parts(r, pl).is_err());
let req = TestRequest::default().to_srv_request();
let (r, _pl) = req.into_parts();
assert!(ServiceRequest::from_request(r).is_ok());
let req = TestRequest::default().to_srv_request();
let (r, _pl) = req.into_parts();
let _r2 = r.clone();
assert!(ServiceRequest::from_request(r).is_err());
}
#[test]
fn test_service() {
let mut srv = init_service(

View File

@ -1,5 +1,4 @@
//! Various helpers for Actix applications to use during testing.
use std::cell::RefCell;
use std::rc::Rc;
use actix_http::http::header::{ContentType, Header, HeaderName, IntoHeaderValue};
@ -7,17 +6,17 @@ use actix_http::http::{HttpTryFrom, Method, StatusCode, Uri, Version};
use actix_http::test::TestRequest as HttpTestRequest;
use actix_http::{cookie::Cookie, Extensions, Request};
use actix_router::{Path, ResourceDef, Url};
use actix_rt::{System, SystemRunner};
use actix_server_config::ServerConfig;
use actix_service::{IntoNewService, IntoService, NewService, Service};
use bytes::{Bytes, BytesMut};
use futures::future::{lazy, ok, Future, IntoFuture};
use futures::future::{ok, Future};
use futures::Stream;
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde_json;
pub use actix_http::test::TestBuffer;
pub use actix_testing::{block_fn, block_on, run_on};
use crate::config::{AppConfig, AppConfigInner};
use crate::data::Data;
@ -27,78 +26,6 @@ use crate::rmap::ResourceMap;
use crate::service::{ServiceRequest, ServiceResponse};
use crate::{Error, HttpRequest, HttpResponse};
thread_local! {
static RT: RefCell<Inner> = {
RefCell::new(Inner(Some(System::builder().build())))
};
}
struct Inner(Option<SystemRunner>);
impl Inner {
fn get_mut(&mut self) -> &mut SystemRunner {
self.0.as_mut().unwrap()
}
}
impl Drop for Inner {
fn drop(&mut self) {
std::mem::forget(self.0.take().unwrap())
}
}
/// Runs the provided future, blocking the current thread until the future
/// completes.
///
/// This function can be used to synchronously block the current thread
/// until the provided `future` has resolved either successfully or with an
/// error. The result of the future is then returned from this function
/// call.
///
/// Note that this function is intended to be used only for testing purpose.
/// This function panics on nested call.
pub fn block_on<F>(f: F) -> Result<F::Item, F::Error>
where
F: IntoFuture,
{
RT.with(move |rt| rt.borrow_mut().get_mut().block_on(f.into_future()))
}
/// Runs the provided function, blocking the current thread until the result
/// future completes.
///
/// This function can be used to synchronously block the current thread
/// until the provided `future` has resolved either successfully or with an
/// error. The result of the future is then returned from this function
/// call.
///
/// Note that this function is intended to be used only for testing purpose.
/// This function panics on nested call.
pub fn block_fn<F, R>(f: F) -> Result<R::Item, R::Error>
where
F: FnOnce() -> R,
R: IntoFuture,
{
RT.with(move |rt| rt.borrow_mut().get_mut().block_on(lazy(f)))
}
#[doc(hidden)]
/// Runs the provided function, with runtime enabled.
///
/// Note that this function is intended to be used only for testing purpose.
/// This function panics on nested call.
pub fn run_on<F, R>(f: F) -> R
where
F: FnOnce() -> R,
{
RT.with(move |rt| {
rt.borrow_mut()
.get_mut()
.block_on(lazy(|| Ok::<_, ()>(f())))
})
.unwrap()
}
/// Create service that always responds with `HttpResponse::Ok()`
pub fn ok_service(
) -> impl Service<Request = ServiceRequest, Response = ServiceResponse<Body>, Error = Error>
@ -478,6 +405,16 @@ impl TestRequest {
self
}
/// Serialize `data` to a URL encoded form and set it as the request payload. The `Content-Type`
/// header is set to `application/x-www-form-urlencoded`.
pub fn set_form<T: Serialize>(mut self, data: &T) -> Self {
let bytes = serde_urlencoded::to_string(data)
.expect("Failed to serialize test data as a urlencoded form");
self.req.set_payload(bytes);
self.req.set(ContentType::form_url_encoded());
self
}
/// Serialize `data` to JSON and set it as the request payload. The `Content-Type` header is
/// set to `application/json`.
pub fn set_json<T: Serialize>(mut self, data: &T) -> Self {
@ -670,6 +607,31 @@ mod tests {
assert_eq!(&result.id, "12345");
}
#[test]
fn test_request_response_form() {
let mut app = init_service(App::new().service(web::resource("/people").route(
web::post().to(|person: web::Form<Person>| {
HttpResponse::Ok().json(person.into_inner())
}),
)));
let payload = Person {
id: "12345".to_string(),
name: "User name".to_string(),
};
let req = TestRequest::post()
.uri("/people")
.set_form(&payload)
.to_request();
assert_eq!(req.content_type(), "application/x-www-form-urlencoded");
let result: Person = read_response_json(&mut app, req);
assert_eq!(&result.id, "12345");
assert_eq!(&result.name, "User name");
}
#[test]
fn test_request_response_json() {
let mut app = init_service(App::new().service(web::resource("/people").route(

View File

@ -35,9 +35,8 @@ use crate::responder::Responder;
///
/// ### Example
/// ```rust
/// # extern crate actix_web;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{web, App};
/// use actix_web::web;
/// use serde_derive::Deserialize;
///
/// #[derive(Deserialize)]
/// struct FormData {
@ -61,9 +60,9 @@ use crate::responder::Responder;
///
/// ### Example
/// ```rust
/// # #[macro_use] extern crate serde_derive;
/// # use actix_web::*;
/// #
/// use actix_web::*;
/// use serde_derive::Serialize;
///
/// #[derive(Serialize)]
/// struct SomeForm {
/// name: String,
@ -167,8 +166,8 @@ impl<T: Serialize> Responder for Form<T> {
/// Form extractor configuration
///
/// ```rust
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{web, App, FromRequest, Result};
/// use serde_derive::Deserialize;
///
/// #[derive(Deserialize)]
/// struct FormData {
@ -318,7 +317,7 @@ where
let limit = self.limit;
if let Some(len) = self.length.take() {
if len > limit {
return Err(UrlencodedError::Overflow);
return Err(UrlencodedError::Overflow { size: len, limit });
}
}
@ -331,7 +330,10 @@ where
.from_err()
.fold(BytesMut::with_capacity(8192), move |mut body, chunk| {
if (body.len() + chunk.len()) > limit {
Err(UrlencodedError::Overflow)
Err(UrlencodedError::Overflow {
size: body.len() + chunk.len(),
limit,
})
} else {
body.extend_from_slice(&chunk);
Ok(body)
@ -390,8 +392,8 @@ mod tests {
fn eq(err: UrlencodedError, other: UrlencodedError) -> bool {
match err {
UrlencodedError::Overflow => match other {
UrlencodedError::Overflow => true,
UrlencodedError::Overflow { .. } => match other {
UrlencodedError::Overflow { .. } => true,
_ => false,
},
UrlencodedError::UnknownLength => match other {
@ -420,7 +422,10 @@ mod tests {
.header(CONTENT_LENGTH, "1000000")
.to_http_parts();
let info = block_on(UrlEncoded::<Info>::new(&req, &mut pl));
assert!(eq(info.err().unwrap(), UrlencodedError::Overflow));
assert!(eq(
info.err().unwrap(),
UrlencodedError::Overflow { size: 0, limit: 0 }
));
let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "text/plain")
.header(CONTENT_LENGTH, "10")
@ -482,5 +487,4 @@ mod tests {
use crate::responder::tests::BodyTest;
assert_eq!(resp.body().bin_ref(), b"hello=world&counter=123");
}
}

View File

@ -33,8 +33,8 @@ use crate::responder::Responder;
/// ## Example
///
/// ```rust
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{web, App};
/// use serde_derive::Deserialize;
///
/// #[derive(Deserialize)]
/// struct Info {
@ -60,9 +60,9 @@ use crate::responder::Responder;
/// trait from *serde*.
///
/// ```rust
/// # #[macro_use] extern crate serde_derive;
/// # use actix_web::*;
/// #
/// use actix_web::*;
/// use serde_derive::Serialize;
///
/// #[derive(Serialize)]
/// struct MyObj {
/// name: String,
@ -144,8 +144,8 @@ impl<T: Serialize> Responder for Json<T> {
/// ## Example
///
/// ```rust
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{web, App};
/// use serde_derive::Deserialize;
///
/// #[derive(Deserialize)]
/// struct Info {
@ -203,8 +203,8 @@ where
/// Json extractor configuration
///
/// ```rust
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{error, web, App, FromRequest, HttpResponse};
/// use serde_derive::Deserialize;
///
/// #[derive(Deserialize)]
/// struct Info {

View File

@ -39,8 +39,8 @@ use crate::FromRequest;
/// implements `Deserialize` trait from *serde*.
///
/// ```rust
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{web, App, Error};
/// use serde_derive::Deserialize;
///
/// #[derive(Deserialize)]
/// struct Info {
@ -134,8 +134,8 @@ impl<T: fmt::Display> fmt::Display for Path<T> {
/// implements `Deserialize` trait from *serde*.
///
/// ```rust
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{web, App, Error};
/// use serde_derive::Deserialize;
///
/// #[derive(Deserialize)]
/// struct Info {
@ -190,10 +190,9 @@ where
/// Path extractor configuration
///
/// ```rust
/// # #[macro_use]
/// # extern crate serde_derive;
/// use actix_web::web::PathConfig;
/// use actix_web::{error, web, App, FromRequest, HttpResponse};
/// use serde_derive::Deserialize;
///
/// #[derive(Deserialize, Debug)]
/// enum Folder {

View File

@ -43,7 +43,14 @@ use crate::request::HttpRequest;
/// );
/// }
/// ```
pub struct Payload(crate::dev::Payload);
pub struct Payload(pub crate::dev::Payload);
impl Payload {
/// Deconstruct to a inner value
pub fn into_inner(self) -> crate::dev::Payload {
self.0
}
}
impl Stream for Payload {
type Item = Bytes;

View File

@ -21,8 +21,8 @@ use crate::request::HttpRequest;
/// ## Example
///
/// ```rust
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{web, App};
/// use serde_derive::Deserialize;
///
/// #[derive(Debug, Deserialize)]
/// pub enum ResponseType {
@ -99,8 +99,8 @@ impl<T: fmt::Display> fmt::Display for Query<T> {
/// ## Example
///
/// ```rust
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{web, App};
/// use serde_derive::Deserialize;
///
/// #[derive(Debug, Deserialize)]
/// pub enum ResponseType {
@ -169,8 +169,8 @@ where
/// ## Example
///
/// ```rust
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{error, web, App, FromRequest, HttpResponse};
/// use serde_derive::Deserialize;
///
/// #[derive(Deserialize)]
/// struct Info {

View File

@ -1,10 +1,15 @@
# Changes
## [0.2.5] - 2019-0917
### Changed
* Update serde_urlencoded to "0.6.1"
### Fixed
* Do not override current `System`
## [0.2.4] - 2019-07-18

View File

@ -1,6 +1,6 @@
[package]
name = "actix-http-test"
version = "0.2.4"
version = "0.2.5"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix http test server"
readme = "README.md"
@ -35,7 +35,7 @@ actix-rt = "0.2.2"
actix-service = "0.4.1"
actix-server = "0.6.0"
actix-utils = "0.4.1"
awc = "0.2.2"
awc = "0.2.6"
actix-connect = "0.2.2"
base64 = "0.10"
@ -56,5 +56,5 @@ tokio-timer = "0.2"
openssl = { version="0.10", optional = true }
[dev-dependencies]
actix-web = "1.0.0"
actix-http = "0.2.4"
actix-web = "1.0.7"
actix-http = "0.2.9"

View File

@ -103,8 +103,8 @@ pub struct TestServer;
/// Test server controller
pub struct TestServerRuntime {
addr: net::SocketAddr,
rt: Runtime,
client: Client,
system: System,
}
impl TestServer {
@ -130,45 +130,47 @@ impl TestServer {
});
let (system, addr) = rx.recv().unwrap();
let mut rt = Runtime::new().unwrap();
let client = rt
.block_on(lazy(move || {
let connector = {
#[cfg(feature = "ssl")]
{
use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
let client = block_on(lazy(move || {
let connector = {
#[cfg(feature = "ssl")]
{
use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
let mut builder =
SslConnector::builder(SslMethod::tls()).unwrap();
builder.set_verify(SslVerifyMode::NONE);
let _ = builder.set_alpn_protos(b"\x02h2\x08http/1.1").map_err(
|e| log::error!("Can not set alpn protocol: {:?}", e),
);
Connector::new()
.conn_lifetime(time::Duration::from_secs(0))
.timeout(time::Duration::from_millis(500))
.ssl(builder.build())
.finish()
}
#[cfg(not(feature = "ssl"))]
{
Connector::new()
.conn_lifetime(time::Duration::from_secs(0))
.timeout(time::Duration::from_millis(500))
.finish()
}
};
let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
builder.set_verify(SslVerifyMode::NONE);
let _ = builder
.set_alpn_protos(b"\x02h2\x08http/1.1")
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
Connector::new()
.conn_lifetime(time::Duration::from_secs(0))
.timeout(time::Duration::from_millis(500))
.ssl(builder.build())
.finish()
}
#[cfg(not(feature = "ssl"))]
{
Connector::new()
.conn_lifetime(time::Duration::from_secs(0))
.timeout(time::Duration::from_millis(500))
.finish()
}
};
Ok::<Client, ()>(Client::build().connector(connector).finish())
}))
.unwrap();
rt.block_on(lazy(
Ok::<Client, ()>(Client::build().connector(connector).finish())
}))
.unwrap();
block_on(lazy(
|| Ok::<_, ()>(actix_connect::start_default_resolver()),
))
.unwrap();
System::set_current(system);
TestServerRuntime { addr, rt, client }
TestServerRuntime {
addr,
client,
system,
}
}
/// Get first available unused address
@ -188,7 +190,7 @@ impl TestServerRuntime {
where
F: Future<Item = I, Error = E>,
{
self.rt.block_on(fut)
block_on(fut)
}
/// Execute future on current core
@ -197,7 +199,7 @@ impl TestServerRuntime {
F: FnOnce() -> R,
R: Future,
{
self.rt.block_on(lazy(f))
block_on(lazy(f))
}
/// Execute function on current core
@ -205,7 +207,7 @@ impl TestServerRuntime {
where
F: FnOnce() -> R,
{
self.rt.block_on(lazy(|| Ok::<_, ()>(fut()))).unwrap()
block_on(lazy(|| Ok::<_, ()>(fut()))).unwrap()
}
/// Construct test server url
@ -324,8 +326,7 @@ impl TestServerRuntime {
{
let url = self.url(path);
let connect = self.client.ws(url).connect();
self.rt
.block_on(lazy(move || connect.map(|(_, framed)| framed)))
block_on(lazy(move || connect.map(|(_, framed)| framed)))
}
/// Connect to a websocket server
@ -338,7 +339,7 @@ impl TestServerRuntime {
/// Stop http server
fn stop(&mut self) {
System::current().stop();
self.system.stop();
}
}