1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-04 18:06:23 +02:00

Compare commits

...

33 Commits

Author SHA1 Message Date
1ca9d87f0a prep actix-web-codegen release 2019-10-14 21:35:53 +06:00
967f965405 Update syn & quote to 1.0 (#1133)
* chore(actix-web-codegen): Upgrade syn and quote to 1.0

* feat(actix-web-codegen): Generate better error message

* doc(actix-web-codegen): Update CHANGES.md

* fix: Build with stable rust
2019-10-14 21:34:17 +06:00
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
60 changed files with 1369 additions and 888 deletions

View File

@ -1,13 +1,31 @@
# Changes # Changes
## not released yet
## [1.0.9] - 2019-xx-xx
### Added ### Added
* Add `middleware::Conditon` that conditionally enables another middleware * Add `Payload::into_inner` method and make stored `def::Payload` public. (#1110)
### Fixed ## [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
* h2 will use error response #1080
## [1.0.7] - 2019-08-29 ## [1.0.7] - 2019-08-29

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "1.0.7" version = "1.0.8"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust."
readme = "README.md" readme = "README.md"
@ -79,10 +79,11 @@ actix-router = "0.1.5"
actix-rt = "0.2.4" actix-rt = "0.2.4"
actix-web-codegen = "0.1.2" actix-web-codegen = "0.1.2"
actix-http = "0.2.9" actix-http = "0.2.9"
actix-server = "0.6.0" actix-server = "0.6.1"
actix-server-config = "0.1.2" actix-server-config = "0.1.2"
actix-testing = "0.1.0"
actix-threadpool = "0.1.1" actix-threadpool = "0.1.1"
awc = { version = "0.2.4", optional = true } awc = { version = "0.2.7", optional = true }
bytes = "0.4" bytes = "0.4"
derive_more = "0.15.0" derive_more = "0.15.0"

View File

@ -1,21 +1,25 @@
# Changes # 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 `mime_guess` crate version to 2.0.1
* Bump up `percent-encoding` crate version to 2.1 * Bump up `percent-encoding` crate version to 2.1
* Allow user defined request guards for `Files` #1113
## [0.1.4] - 2019-07-20 ## [0.1.4] - 2019-07-20
* Allow to disable `Content-Disposition` header #686 * Allow to disable `Content-Disposition` header #686
## [0.1.3] - 2019-06-28 ## [0.1.3] - 2019-06-28
* Do not set `Content-Length` header, let actix-http set it #930 * Do not set `Content-Length` header, let actix-http set it #930
## [0.1.2] - 2019-06-13 ## [0.1.2] - 2019-06-13
* Content-Length is 0 for NamedFile HEAD request #914 * Content-Length is 0 for NamedFile HEAD request #914

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-files" name = "actix-files"
version = "0.1.4" version = "0.1.6"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Static files support for actix web." description = "Static files support for actix web."
readme = "README.md" readme = "README.md"
@ -18,7 +18,7 @@ name = "actix_files"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-web = { version = "1.0.2", default-features = false } actix-web = { version = "1.0.8", default-features = false }
actix-http = "0.2.9" actix-http = "0.2.9"
actix-service = "0.4.1" actix-service = "0.4.1"
bitflags = "1" bitflags = "1"
@ -32,4 +32,4 @@ percent-encoding = "2.1"
v_htmlescape = "0.4" v_htmlescape = "0.4"
[dev-dependencies] [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, ServiceResponse,
}; };
use actix_web::error::{BlockingError, Error, ErrorInternalServerError}; 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 actix_web::{web, FromRequest, HttpRequest, HttpResponse, Responder};
use bytes::Bytes; use bytes::Bytes;
use futures::future::{ok, Either, FutureResult}; use futures::future::{ok, Either, FutureResult};
@ -231,10 +233,12 @@ pub struct Files {
directory: PathBuf, directory: PathBuf,
index: Option<String>, index: Option<String>,
show_index: bool, show_index: bool,
redirect_to_slash: bool,
default: Rc<RefCell<Option<Rc<HttpNewService>>>>, default: Rc<RefCell<Option<Rc<HttpNewService>>>>,
renderer: Rc<DirectoryRenderer>, renderer: Rc<DirectoryRenderer>,
mime_override: Option<Rc<MimeOverride>>, mime_override: Option<Rc<MimeOverride>>,
file_flags: named::Flags, file_flags: named::Flags,
guards: Option<Rc<Box<dyn Guard>>>,
} }
impl Clone for Files { impl Clone for Files {
@ -243,11 +247,13 @@ impl Clone for Files {
directory: self.directory.clone(), directory: self.directory.clone(),
index: self.index.clone(), index: self.index.clone(),
show_index: self.show_index, show_index: self.show_index,
redirect_to_slash: self.redirect_to_slash,
default: self.default.clone(), default: self.default.clone(),
renderer: self.renderer.clone(), renderer: self.renderer.clone(),
file_flags: self.file_flags, file_flags: self.file_flags,
path: self.path.clone(), path: self.path.clone(),
mime_override: self.mime_override.clone(), mime_override: self.mime_override.clone(),
guards: self.guards.clone(),
} }
} }
} }
@ -269,10 +275,12 @@ impl Files {
directory: dir, directory: dir,
index: None, index: None,
show_index: false, show_index: false,
redirect_to_slash: false,
default: Rc::new(RefCell::new(None)), default: Rc::new(RefCell::new(None)),
renderer: Rc::new(directory_listing), renderer: Rc::new(directory_listing),
mime_override: None, mime_override: None,
file_flags: named::Flags::default(), file_flags: named::Flags::default(),
guards: None,
} }
} }
@ -284,6 +292,14 @@ impl Files {
self 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 /// Set custom directory renderer
pub fn files_listing_renderer<F>(mut self, f: F) -> Self pub fn files_listing_renderer<F>(mut self, f: F) -> Self
where where
@ -331,6 +347,15 @@ impl Files {
self 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. /// Disable `Content-Disposition` header.
/// ///
/// By default Content-Disposition` header is enabled. /// By default Content-Disposition` header is enabled.
@ -375,10 +400,10 @@ impl HttpServiceFactory for Files {
} }
impl NewService for Files { impl NewService for Files {
type Config = ();
type Request = ServiceRequest; type Request = ServiceRequest;
type Response = ServiceResponse; type Response = ServiceResponse;
type Error = Error; type Error = Error;
type Config = ();
type Service = FilesService; type Service = FilesService;
type InitError = (); type InitError = ();
type Future = Box<dyn Future<Item = Self::Service, Error = Self::InitError>>; type Future = Box<dyn Future<Item = Self::Service, Error = Self::InitError>>;
@ -388,10 +413,12 @@ impl NewService for Files {
directory: self.directory.clone(), directory: self.directory.clone(),
index: self.index.clone(), index: self.index.clone(),
show_index: self.show_index, show_index: self.show_index,
redirect_to_slash: self.redirect_to_slash,
default: None, default: None,
renderer: self.renderer.clone(), renderer: self.renderer.clone(),
mime_override: self.mime_override.clone(), mime_override: self.mime_override.clone(),
file_flags: self.file_flags, file_flags: self.file_flags,
guards: self.guards.clone(),
}; };
if let Some(ref default) = *self.default.borrow() { if let Some(ref default) = *self.default.borrow() {
@ -414,10 +441,12 @@ pub struct FilesService {
directory: PathBuf, directory: PathBuf,
index: Option<String>, index: Option<String>,
show_index: bool, show_index: bool,
redirect_to_slash: bool,
default: Option<HttpService>, default: Option<HttpService>,
renderer: Rc<DirectoryRenderer>, renderer: Rc<DirectoryRenderer>,
mime_override: Option<Rc<MimeOverride>>, mime_override: Option<Rc<MimeOverride>>,
file_flags: named::Flags, file_flags: named::Flags,
guards: Option<Rc<Box<dyn Guard>>>,
} }
impl FilesService { impl FilesService {
@ -454,6 +483,25 @@ impl Service for FilesService {
fn call(&mut self, req: ServiceRequest) -> Self::Future { fn call(&mut self, req: ServiceRequest) -> Self::Future {
// let (req, pl) = req.into_parts(); // 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()) { let real_path = match PathBufWrp::get_pathbuf(req.match_info().path()) {
Ok(item) => item, Ok(item) => item,
Err(e) => return Either::A(ok(req.error_response(e))), Err(e) => return Either::A(ok(req.error_response(e))),
@ -467,6 +515,16 @@ impl Service for FilesService {
if path.is_dir() { if path.is_dir() {
if let Some(ref redir_index) = self.index { 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); let path = path.join(redir_index);
match NamedFile::open(path) { match NamedFile::open(path) {
@ -576,6 +634,7 @@ mod tests {
use bytes::BytesMut; use bytes::BytesMut;
use super::*; use super::*;
use actix_web::guard;
use actix_web::http::header::{ use actix_web::http::header::{
self, ContentDisposition, DispositionParam, DispositionType, self, ContentDisposition, DispositionParam, DispositionType,
}; };
@ -1010,20 +1069,41 @@ mod tests {
} }
#[test] #[test]
fn test_named_file_not_allowed() { fn test_files_not_allowed() {
let file = NamedFile::open("Cargo.toml").unwrap(); let mut srv = test::init_service(App::new().service(Files::new("/", ".")));
let req = TestRequest::default() let req = TestRequest::default()
.uri("/Cargo.toml")
.method(Method::POST) .method(Method::POST)
.to_http_request(); .to_request();
let resp = file.respond_to(&req).unwrap();
let resp = test::call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED);
let file = NamedFile::open("Cargo.toml").unwrap(); let mut srv = test::init_service(App::new().service(Files::new("/", ".")));
let req = TestRequest::default().method(Method::PUT).to_http_request(); let req = TestRequest::default()
let resp = file.respond_to(&req).unwrap(); .method(Method::PUT)
.uri("/Cargo.toml")
.to_request();
let resp = test::call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); 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] #[test]
fn test_named_file_content_encoding() { fn test_named_file_content_encoding() {
let mut srv = test::init_service(App::new().wrap(Compress::default()).service( 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")); 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] #[test]
fn test_static_files_bad_directory() { fn test_static_files_bad_directory() {
let _st: Files = Files::new("/", "missing"); let _st: Files = Files::new("/", "missing");

View File

@ -15,7 +15,7 @@ use actix_http::body::SizedStream;
use actix_web::http::header::{ use actix_web::http::header::{
self, ContentDisposition, DispositionParam, DispositionType, 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::middleware::BodyEncoding;
use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder}; use actix_web::{Error, HttpMessage, HttpRequest, HttpResponse, Responder};
@ -324,16 +324,6 @@ impl Responder for NamedFile {
return Ok(resp.streaming(reader)); 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) { let etag = if self.flags.contains(Flags::ETAG) {
self.etag() self.etag()
} else { } else {

View File

@ -1,13 +1,29 @@
# Changes # Changes
## [0.2.11] - 2019-09-11 ## 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 ### Added
* Add support for sending HTTP requests with `Rc<RequestHead>` in addition to sending HTTP requests with `RequestHead` * 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 ### Fixed
* h2 will use error response #1080
* on_connect result isn't added to request extensions for http2 requests #1009 * on_connect result isn't added to request extensions for http2 requests #1009

View File

@ -66,7 +66,7 @@ hashbrown = "0.5.0"
h2 = "0.1.16" h2 = "0.1.16"
http = "0.1.17" http = "0.1.17"
httparse = "1.3" httparse = "1.3"
indexmap = "1.0" indexmap = "1.2"
lazy_static = "1.0" lazy_static = "1.0"
language-tags = "0.2" language-tags = "0.2"
log = "0.4" 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 impl<S> From<SizedStream<S>> for Body
where where
S: Stream<Item = Bytes, Error = Error> + 'static, S: Stream<Item = Bytes, Error = Error> + 'static,
@ -548,4 +554,17 @@ mod tests {
assert!(format!("{:?}", Body::Empty).contains("Body::Empty")); assert!(format!("{:?}", Body::Empty).contains("Body::Empty"));
assert!(format!("{:?}", Body::Bytes(Bytes::from_static(b"1"))).contains("1")); 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

@ -8,10 +8,10 @@ use futures::{Async, Future, Poll, Sink, Stream};
use crate::error::PayloadError; use crate::error::PayloadError;
use crate::h1; use crate::h1;
use crate::header::HeaderMap;
use crate::http::header::{IntoHeaderValue, HOST}; use crate::http::header::{IntoHeaderValue, HOST};
use crate::message::{RequestHeadType, ResponseHead}; use crate::message::{RequestHeadType, ResponseHead};
use crate::payload::{Payload, PayloadStream}; use crate::payload::{Payload, PayloadStream};
use crate::header::HeaderMap;
use super::connection::{ConnectionLifetime, ConnectionType, IoConnection}; use super::connection::{ConnectionLifetime, ConnectionType, IoConnection};
use super::error::{ConnectError, SendRequestError}; use super::error::{ConnectError, SendRequestError};
@ -30,7 +30,9 @@ where
B: MessageBody, B: MessageBody,
{ {
// set request host header // set request host header
if !head.as_ref().headers.contains_key(HOST) && !head.extra_headers().iter().any(|h| h.contains_key(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() { if let Some(host) = head.as_ref().uri.host() {
let mut wrt = BytesMut::with_capacity(host.len() + 5).writer(); let mut wrt = BytesMut::with_capacity(host.len() + 5).writer();
@ -40,20 +42,16 @@ where
}; };
match wrt.get_mut().take().freeze().try_into() { match wrt.get_mut().take().freeze().try_into() {
Ok(value) => { Ok(value) => match head {
match head {
RequestHeadType::Owned(ref mut head) => { RequestHeadType::Owned(ref mut head) => {
head.headers.insert(HOST, value) head.headers.insert(HOST, value)
}, }
RequestHeadType::Rc(_, ref mut extra_headers) => { RequestHeadType::Rc(_, ref mut extra_headers) => {
let headers = extra_headers.get_or_insert(HeaderMap::new()); let headers = extra_headers.get_or_insert(HeaderMap::new());
headers.insert(HOST, value) headers.insert(HOST, value)
}
}, },
} Err(e) => log::error!("Can not set HOST header {}", e),
}
Err(e) => {
log::error!("Can not set HOST header {}", e)
}
} }
} }
} }

View File

@ -9,9 +9,9 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, TRANSFER_ENCODING};
use http::{request::Request, HttpTryFrom, Method, Version}; use http::{request::Request, HttpTryFrom, Method, Version};
use crate::body::{BodySize, MessageBody}; use crate::body::{BodySize, MessageBody};
use crate::header::HeaderMap;
use crate::message::{RequestHeadType, ResponseHead}; use crate::message::{RequestHeadType, ResponseHead};
use crate::payload::Payload; use crate::payload::Payload;
use crate::header::HeaderMap;
use super::connection::{ConnectionType, IoConnection}; use super::connection::{ConnectionType, IoConnection};
use super::error::SendRequestError; use super::error::SendRequestError;
@ -69,15 +69,21 @@ where
// Extracting extra headers from RequestHeadType. HeaderMap::new() does not allocate. // Extracting extra headers from RequestHeadType. HeaderMap::new() does not allocate.
let (head, extra_headers) = match head { let (head, extra_headers) = match head {
RequestHeadType::Owned(head) => (RequestHeadType::Owned(head), HeaderMap::new()), RequestHeadType::Owned(head) => {
RequestHeadType::Rc(head, extra_headers) => (RequestHeadType::Rc(head, None), extra_headers.unwrap_or(HeaderMap::new())), (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. // merging headers from head and extra headers.
let headers = head.as_ref().headers.iter() let headers = head
.filter(|(name, _)| { .as_ref()
!extra_headers.contains_key(*name) .headers
}) .iter()
.filter(|(name, _)| !extra_headers.contains_key(*name))
.chain(extra_headers.iter()); .chain(extra_headers.iter());
// copy headers // copy headers

View File

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

View File

@ -326,7 +326,7 @@ impl<Io> Inner<Io> {
fn release_waiter(&mut self, key: &Key, token: usize) { fn release_waiter(&mut self, key: &Key, token: usize) {
self.waiters.remove(token); 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); let _ = write!(Writer(&mut buf), "{}", self);
resp.headers_mut().insert( resp.headers_mut().insert(
header::CONTENT_TYPE, header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain"), header::HeaderValue::from_static("text/plain; charset=utf-8"),
); );
resp.set_body(Body::from(buf)) 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 /// Convert `Error` to a `Response` instance
impl From<Error> for Response { impl From<Error> for Response {
fn from(err: Error) -> Self { fn from(err: Error) -> Self {
@ -528,7 +536,7 @@ where
let _ = write!(Writer(&mut buf), "{}", self); let _ = write!(Writer(&mut buf), "{}", self);
res.headers_mut().insert( res.headers_mut().insert(
header::CONTENT_TYPE, header::CONTENT_TYPE,
header::HeaderValue::from_static("text/plain"), header::HeaderValue::from_static("text/plain; charset=utf-8"),
); );
res.set_body(Body::from(buf)) res.set_body(Body::from(buf))
} }

View File

@ -16,9 +16,11 @@ use super::{Message, MessageType};
use crate::body::BodySize; use crate::body::BodySize;
use crate::config::ServiceConfig; use crate::config::ServiceConfig;
use crate::error::{ParseError, PayloadError}; use crate::error::{ParseError, PayloadError};
use crate::helpers;
use crate::message::{ConnectionType, Head, MessagePool, RequestHead, RequestHeadType, ResponseHead};
use crate::header::HeaderMap; use crate::header::HeaderMap;
use crate::helpers;
use crate::message::{
ConnectionType, Head, MessagePool, RequestHead, RequestHeadType, ResponseHead,
};
bitflags! { bitflags! {
struct Flags: u8 { struct Flags: u8 {
@ -197,7 +199,9 @@ impl Encoder for ClientCodec {
Message::Item((mut head, length)) => { Message::Item((mut head, length)) => {
let inner = &mut self.inner; let inner = &mut self.inner;
inner.version = head.as_ref().version; inner.version = head.as_ref().version;
inner.flags.set(Flags::HEAD, head.as_ref().method == Method::HEAD); inner
.flags
.set(Flags::HEAD, head.as_ref().method == Method::HEAD);
// connection status // connection status
inner.ctype = match head.as_ref().connection_type() { inner.ctype = match head.as_ref().connection_type() {

View File

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

View File

@ -2,9 +2,9 @@
use std::fmt::Write as FmtWrite; use std::fmt::Write as FmtWrite;
use std::io::Write; use std::io::Write;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::rc::Rc;
use std::str::FromStr; use std::str::FromStr;
use std::{cmp, fmt, io, mem}; use std::{cmp, fmt, io, mem};
use std::rc::Rc;
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{BufMut, Bytes, BytesMut};
@ -16,7 +16,7 @@ use crate::http::header::{
HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING, HeaderValue, ACCEPT_ENCODING, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING,
}; };
use crate::http::{HeaderMap, Method, StatusCode, Version}; use crate::http::{HeaderMap, Method, StatusCode, Version};
use crate::message::{ConnectionType, Head, RequestHead, ResponseHead, RequestHeadType}; use crate::message::{ConnectionType, Head, RequestHead, RequestHeadType, ResponseHead};
use crate::request::Request; use crate::request::Request;
use crate::response::Response; use crate::response::Response;
@ -134,10 +134,11 @@ pub(crate) trait MessageType: Sized {
// merging headers from head and extra headers. HeaderMap::new() does not allocate. // merging headers from head and extra headers. HeaderMap::new() does not allocate.
let empty_headers = HeaderMap::new(); let empty_headers = HeaderMap::new();
let extra_headers = self.extra_headers().unwrap_or(&empty_headers); let extra_headers = self.extra_headers().unwrap_or(&empty_headers);
let headers = self.headers().inner.iter() let headers = self
.filter(|(name, _)| { .headers()
!extra_headers.contains_key(*name) .inner
}) .iter()
.filter(|(name, _)| !extra_headers.contains_key(*name))
.chain(extra_headers.inner.iter()); .chain(extra_headers.inner.iter());
// write headers // write headers
@ -604,10 +605,16 @@ mod tests {
let mut bytes = BytesMut::with_capacity(2048); let mut bytes = BytesMut::with_capacity(2048);
let mut head = RequestHead::default(); let mut head = RequestHead::default();
head.headers.insert(AUTHORIZATION, HeaderValue::from_static("some authorization")); head.headers.insert(
AUTHORIZATION,
HeaderValue::from_static("some authorization"),
);
let mut extra_headers = HeaderMap::new(); let mut extra_headers = HeaderMap::new();
extra_headers.insert(AUTHORIZATION,HeaderValue::from_static("another authorization")); extra_headers.insert(
AUTHORIZATION,
HeaderValue::from_static("another authorization"),
);
extra_headers.insert(DATE, HeaderValue::from_static("date")); extra_headers.insert(DATE, HeaderValue::from_static("date"));
let mut head = RequestHeadType::Rc(Rc::new(head), Some(extra_headers)); let mut head = RequestHeadType::Rc(Rc::new(head), Some(extra_headers));

View File

@ -16,7 +16,6 @@ use crate::httpmessage::HttpMessage;
mod common; mod common;
pub(crate) mod map; pub(crate) mod map;
mod shared; mod shared;
#[doc(hidden)]
pub use self::common::*; pub use self::common::*;
#[doc(hidden)] #[doc(hidden)]
pub use self::shared::*; 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) { pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) {
let mut curr: isize = 39; 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[39] = b'\r';
buf[40] = b'\n'; buf[40] = b'\n';
let buf_ptr = buf.as_mut_ptr(); let buf_ptr = buf.as_mut_ptr();

View File

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

View File

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

View File

@ -150,7 +150,7 @@ impl TestRequest {
/// Complete request creation and generate `Request` instance /// Complete request creation and generate `Request` instance
pub fn finish(&mut self) -> Request { 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 { let mut req = if let Some(pl) = inner.payload {
Request::with_payload(pl) Request::with_payload(pl)

View File

@ -1,5 +1,6 @@
# Changes # Changes
## [0.1.4] - 2019-xx-xx
## [0.1.4] - 2019-09-12
* Multipart handling now parses requests which do not end in CRLF #1038 * Multipart handling now parses requests which do not end in CRLF #1038

View File

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

View File

@ -178,13 +178,15 @@ impl InnerMultipart {
Some(chunk) => { Some(chunk) => {
if chunk.len() < boundary.len() + 4 if chunk.len() < boundary.len() + 4
|| &chunk[..2] != b"--" || &chunk[..2] != b"--"
|| &chunk[2..boundary.len() + 2] != boundary.as_bytes() { || &chunk[2..boundary.len() + 2] != boundary.as_bytes()
{
Err(MultipartError::Boundary) Err(MultipartError::Boundary)
} else if &chunk[boundary.len() + 2..] == b"\r\n" { } else if &chunk[boundary.len() + 2..] == b"\r\n" {
Ok(Some(false)) Ok(Some(false))
} else if &chunk[boundary.len() + 2..boundary.len() + 4] == b"--" } else if &chunk[boundary.len() + 2..boundary.len() + 4] == b"--"
&& (chunk.len() == boundary.len() + 4 && (chunk.len() == boundary.len() + 4
|| &chunk[boundary.len() + 4..] == b"\r\n") { || &chunk[boundary.len() + 4..] == b"\r\n")
{
Ok(Some(true)) Ok(Some(true))
} else { } else {
Err(MultipartError::Boundary) Err(MultipartError::Boundary)
@ -781,8 +783,10 @@ impl PayloadBuffer {
/// Read bytes until new line delimiter or eof /// Read bytes until new line delimiter or eof
pub fn readline_or_eof(&mut self) -> Result<Option<Bytes>, MultipartError> { pub fn readline_or_eof(&mut self) -> Result<Option<Bytes>, MultipartError> {
match self.readline() { match self.readline() {
Err(MultipartError::Incomplete) if self.eof => Ok(Some(self.buf.take().freeze())), Err(MultipartError::Incomplete) if self.eof => {
line => line Ok(Some(self.buf.take().freeze()))
}
line => line,
} }
} }
@ -866,7 +870,7 @@ mod tests {
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\ --abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\ Content-Type: text/plain; charset=utf-8\r\nContent-Length: 4\r\n\r\n\
data\r\n\ data\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0--\r\n" --abbc761f78ff4d7cb7573b5a23f96ef0--\r\n",
); );
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.insert( headers.insert(

View File

@ -1,5 +1,11 @@
# Changes # Changes
## [0.1.3] - 2019-10-14
* Bump up `syn` & `quote` to 1.0
* Provide better error message
## [0.1.2] - 2019-06-04 ## [0.1.2] - 2019-06-04
* Add macros for head, options, trace, connect and patch http methods * Add macros for head, options, trace, connect and patch http methods

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-web-codegen" name = "actix-web-codegen"
version = "0.1.2" version = "0.1.3"
description = "Actix web proc macros" description = "Actix web proc macros"
readme = "README.md" readme = "README.md"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
@ -12,8 +12,9 @@ workspace = ".."
proc-macro = true proc-macro = true
[dependencies] [dependencies]
quote = "0.6.12" quote = "1"
syn = { version = "0.15.34", features = ["full", "parsing", "extra-traits"] } syn = { version = "1", features = ["full", "parsing"] }
proc-macro2 = "1"
[dev-dependencies] [dev-dependencies]
actix-web = { version = "1.0.0" } actix-web = { version = "1.0.0" }

View File

@ -58,7 +58,10 @@ use syn::parse_macro_input;
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn get(args: TokenStream, input: TokenStream) -> TokenStream { pub fn get(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Get); let gen = match route::Route::new(args, input, route::GuardType::Get) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@ -70,7 +73,10 @@ pub fn get(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn post(args: TokenStream, input: TokenStream) -> TokenStream { pub fn post(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Post); let gen = match route::Route::new(args, input, route::GuardType::Post) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@ -82,7 +88,10 @@ pub fn post(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn put(args: TokenStream, input: TokenStream) -> TokenStream { pub fn put(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Put); let gen = match route::Route::new(args, input, route::GuardType::Put) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@ -94,7 +103,10 @@ pub fn put(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream { pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Delete); let gen = match route::Route::new(args, input, route::GuardType::Delete) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@ -106,7 +118,10 @@ pub fn delete(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn head(args: TokenStream, input: TokenStream) -> TokenStream { pub fn head(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Head); let gen = match route::Route::new(args, input, route::GuardType::Head) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@ -118,7 +133,10 @@ pub fn head(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn connect(args: TokenStream, input: TokenStream) -> TokenStream { pub fn connect(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Connect); let gen = match route::Route::new(args, input, route::GuardType::Connect) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@ -130,7 +148,10 @@ pub fn connect(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn options(args: TokenStream, input: TokenStream) -> TokenStream { pub fn options(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Options); let gen = match route::Route::new(args, input, route::GuardType::Options) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@ -142,7 +163,10 @@ pub fn options(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn trace(args: TokenStream, input: TokenStream) -> TokenStream { pub fn trace(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Trace); let gen = match route::Route::new(args, input, route::GuardType::Trace) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }
@ -154,6 +178,9 @@ pub fn trace(args: TokenStream, input: TokenStream) -> TokenStream {
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn patch(args: TokenStream, input: TokenStream) -> TokenStream { pub fn patch(args: TokenStream, input: TokenStream) -> TokenStream {
let args = parse_macro_input!(args as syn::AttributeArgs); let args = parse_macro_input!(args as syn::AttributeArgs);
let gen = route::Args::new(&args, input, route::GuardType::Patch); let gen = match route::Route::new(args, input, route::GuardType::Patch) {
Ok(gen) => gen,
Err(err) => return err.to_compile_error().into(),
};
gen.generate() gen.generate()
} }

View File

@ -1,21 +1,23 @@
extern crate proc_macro; extern crate proc_macro;
use std::fmt;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use quote::quote; use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{AttributeArgs, Ident, NestedMeta};
enum ResourceType { enum ResourceType {
Async, Async,
Sync, Sync,
} }
impl fmt::Display for ResourceType { impl ToTokens for ResourceType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn to_tokens(&self, stream: &mut TokenStream2) {
match *self { let ident = match self {
ResourceType::Async => write!(f, "to_async"), ResourceType::Async => "to_async",
ResourceType::Sync => write!(f, "to"), ResourceType::Sync => "to",
} };
let ident = Ident::new(ident, Span::call_site());
stream.append(ident);
} }
} }
@ -32,61 +34,87 @@ pub enum GuardType {
Patch, Patch,
} }
impl fmt::Display for GuardType { impl GuardType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn as_str(&self) -> &'static str {
match *self { match self {
GuardType::Get => write!(f, "Get"), GuardType::Get => "Get",
GuardType::Post => write!(f, "Post"), GuardType::Post => "Post",
GuardType::Put => write!(f, "Put"), GuardType::Put => "Put",
GuardType::Delete => write!(f, "Delete"), GuardType::Delete => "Delete",
GuardType::Head => write!(f, "Head"), GuardType::Head => "Head",
GuardType::Connect => write!(f, "Connect"), GuardType::Connect => "Connect",
GuardType::Options => write!(f, "Options"), GuardType::Options => "Options",
GuardType::Trace => write!(f, "Trace"), GuardType::Trace => "Trace",
GuardType::Patch => write!(f, "Patch"), GuardType::Patch => "Patch",
} }
} }
} }
pub struct Args { impl ToTokens for GuardType {
fn to_tokens(&self, stream: &mut TokenStream2) {
let ident = self.as_str();
let ident = Ident::new(ident, Span::call_site());
stream.append(ident);
}
}
struct Args {
path: syn::LitStr,
guards: Vec<Ident>,
}
impl Args {
fn new(args: AttributeArgs) -> syn::Result<Self> {
let mut path = None;
let mut guards = Vec::new();
for arg in args {
match arg {
NestedMeta::Lit(syn::Lit::Str(lit)) => match path {
None => {
path = Some(lit);
}
_ => {
return Err(syn::Error::new_spanned(
lit,
"Multiple paths specified! Should be only one!",
));
}
},
NestedMeta::Meta(syn::Meta::NameValue(nv)) => {
if nv.path.is_ident("guard") {
if let syn::Lit::Str(lit) = nv.lit {
guards.push(Ident::new(&lit.value(), Span::call_site()));
} else {
return Err(syn::Error::new_spanned(
nv.lit,
"Attribute guard expects literal string!",
));
}
} else {
return Err(syn::Error::new_spanned(
nv.path,
"Unknown attribute key is specified. Allowed: guard",
));
}
}
arg => {
return Err(syn::Error::new_spanned(arg, "Unknown attribute"));
}
}
}
Ok(Args {
path: path.unwrap(),
guards,
})
}
}
pub struct Route {
name: syn::Ident, name: syn::Ident,
path: String, args: Args,
ast: syn::ItemFn, ast: syn::ItemFn,
resource_type: ResourceType, resource_type: ResourceType,
pub guard: GuardType, guard: GuardType,
pub extra_guards: Vec<String>,
}
impl fmt::Display for Args {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let ast = &self.ast;
let guards = format!(".guard(actix_web::guard::{}())", self.guard);
let guards = self.extra_guards.iter().fold(guards, |acc, val| {
format!("{}.guard(actix_web::guard::fn_guard({}))", acc, val)
});
write!(
f,
"
#[allow(non_camel_case_types)]
pub struct {name};
impl actix_web::dev::HttpServiceFactory for {name} {{
fn register(self, config: &mut actix_web::dev::AppService) {{
{ast}
let resource = actix_web::Resource::new(\"{path}\"){guards}.{to}({name});
actix_web::dev::HttpServiceFactory::register(resource, config)
}}
}}",
name = self.name,
ast = quote!(#ast),
path = self.path,
guards = guards,
to = self.resource_type
)
}
} }
fn guess_resource_type(typ: &syn::Type) -> ResourceType { fn guess_resource_type(typ: &syn::Type) -> ResourceType {
@ -111,75 +139,73 @@ fn guess_resource_type(typ: &syn::Type) -> ResourceType {
guess guess
} }
impl Args { impl Route {
pub fn new(args: &[syn::NestedMeta], input: TokenStream, guard: GuardType) -> Self { pub fn new(
args: AttributeArgs,
input: TokenStream,
guard: GuardType,
) -> syn::Result<Self> {
if args.is_empty() { if args.is_empty() {
panic!( return Err(syn::Error::new(
"invalid server definition, expected: #[{}(\"some path\")]", Span::call_site(),
guard format!(
); r#"invalid server definition, expected #[{}("<some path>")]"#,
} guard.as_str().to_ascii_lowercase()
let ast: syn::ItemFn = syn::parse(input).expect("Parse input as function");
let name = ast.ident.clone();
let mut extra_guards = Vec::new();
let mut path = None;
for arg in args {
match arg {
syn::NestedMeta::Literal(syn::Lit::Str(ref fname)) => {
if path.is_some() {
panic!("Multiple paths specified! Should be only one!")
}
let fname = quote!(#fname).to_string();
path = Some(fname.as_str()[1..fname.len() - 1].to_owned())
}
syn::NestedMeta::Meta(syn::Meta::NameValue(ident)) => {
match ident.ident.to_string().to_lowercase().as_str() {
"guard" => match ident.lit {
syn::Lit::Str(ref text) => extra_guards.push(text.value()),
_ => panic!("Attribute guard expects literal string!"),
},
attr => panic!(
"Unknown attribute key is specified: {}. Allowed: guard",
attr
), ),
));
} }
} let ast: syn::ItemFn = syn::parse(input)?;
attr => panic!("Unknown attribute{:?}", attr), let name = ast.sig.ident.clone();
}
}
let resource_type = if ast.asyncness.is_some() { let args = Args::new(args)?;
let resource_type = if ast.sig.asyncness.is_some() {
ResourceType::Async ResourceType::Async
} else { } else {
match ast.decl.output { match ast.sig.output {
syn::ReturnType::Default => panic!( syn::ReturnType::Default => {
"Function {} has no return type. Cannot be used as handler", return Err(syn::Error::new_spanned(
name ast,
), "Function has no return type. Cannot be used as handler",
));
}
syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()), syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()),
} }
}; };
let path = path.unwrap(); Ok(Self {
Self {
name, name,
path, args,
ast, ast,
resource_type, resource_type,
guard, guard,
extra_guards, })
}
} }
pub fn generate(&self) -> TokenStream { pub fn generate(&self) -> TokenStream {
let text = self.to_string(); let name = &self.name;
let guard = &self.guard;
let ast = &self.ast;
let path = &self.args.path;
let extra_guards = &self.args.guards;
let resource_type = &self.resource_type;
let stream = quote! {
#[allow(non_camel_case_types)]
pub struct #name;
match text.parse() { impl actix_web::dev::HttpServiceFactory for #name {
Ok(res) => res, fn register(self, config: &mut actix_web::dev::AppService) {
Err(error) => panic!("Error: {:?}\nGenerated code: {}", error, text), #ast
let resource = actix_web::Resource::new(#path)
.guard(actix_web::guard::#guard())
#(.guard(actix_web::guard::fn_guard(#extra_guards)))*
.#resource_type(#name);
actix_web::dev::HttpServiceFactory::register(resource, config)
} }
} }
};
stream.into()
}
} }

View File

@ -1,14 +1,26 @@
# Changes # Changes
##
## [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 ### Added
* Add `FrozenClientRequest` to support retries for sending HTTP requests * Add `FrozenClientRequest` to support retries for sending HTTP requests
## [0.2.5] - 2019-09-06
### Changed ### Changed
* Ensure that the `Host` header is set when initiating a WebSocket client connection. * Ensure that the `Host` header is set when initiating a WebSocket client connection.

View File

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

View File

@ -1,5 +1,5 @@
use std::{fmt, io, net};
use std::rc::Rc; use std::rc::Rc;
use std::{fmt, io, net};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
use actix_http::body::Body; use actix_http::body::Body;
@ -7,8 +7,8 @@ use actix_http::client::{
Connect as ClientConnect, ConnectError, Connection, SendRequestError, Connect as ClientConnect, ConnectError, Connection, SendRequestError,
}; };
use actix_http::h1::ClientCodec; use actix_http::h1::ClientCodec;
use actix_http::{RequestHead, RequestHeadType, ResponseHead};
use actix_http::http::HeaderMap; use actix_http::http::HeaderMap;
use actix_http::{RequestHead, RequestHeadType, ResponseHead};
use actix_service::Service; use actix_service::Service;
use futures::{Future, Poll}; use futures::{Future, Poll};
@ -82,7 +82,9 @@ where
}) })
.from_err() .from_err()
// send request // send request
.and_then(move |connection| connection.send_request(RequestHeadType::from(head), body)) .and_then(move |connection| {
connection.send_request(RequestHeadType::from(head), body)
})
.map(|(head, payload)| ClientResponse::new(head, payload)), .map(|(head, payload)| ClientResponse::new(head, payload)),
) )
} }
@ -103,7 +105,10 @@ where
}) })
.from_err() .from_err()
// send request // send request
.and_then(move |connection| connection.send_request(RequestHeadType::Rc(head, extra_headers), body)) .and_then(move |connection| {
connection
.send_request(RequestHeadType::Rc(head, extra_headers), body)
})
.map(|(head, payload)| ClientResponse::new(head, payload)), .map(|(head, payload)| ClientResponse::new(head, payload)),
) )
} }
@ -127,7 +132,9 @@ where
}) })
.from_err() .from_err()
// send request // send request
.and_then(move |connection| connection.open_tunnel(RequestHeadType::from(head))) .and_then(move |connection| {
connection.open_tunnel(RequestHeadType::from(head))
})
.map(|(head, framed)| { .map(|(head, framed)| {
let framed = framed.map_io(|io| BoxedSocket(Box::new(Socket(io)))); let framed = framed.map_io(|io| BoxedSocket(Box::new(Socket(io))));
(head, framed) (head, framed)
@ -155,7 +162,9 @@ where
}) })
.from_err() .from_err()
// send request // send request
.and_then(move |connection| connection.open_tunnel(RequestHeadType::Rc(head, extra_headers))) .and_then(move |connection| {
connection.open_tunnel(RequestHeadType::Rc(head, extra_headers))
})
.map(|(head, framed)| { .map(|(head, framed)| {
let framed = framed.map_io(|io| BoxedSocket(Box::new(Socket(io)))); let framed = framed.map_io(|io| BoxedSocket(Box::new(Socket(io))));
(head, framed) (head, framed)

View File

@ -1,5 +1,7 @@
//! Http client errors //! Http client errors
pub use actix_http::client::{ConnectError, InvalidUrl, SendRequestError, FreezeRequestError}; pub use actix_http::client::{
ConnectError, FreezeRequestError, InvalidUrl, SendRequestError,
};
pub use actix_http::error::PayloadError; pub use actix_http::error::PayloadError;
pub use actix_http::ws::HandshakeError as WsHandshakeError; pub use actix_http::ws::HandshakeError as WsHandshakeError;
pub use actix_http::ws::ProtocolError as WsProtocolError; 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 builder;
mod connect; mod connect;
pub mod error; pub mod error;
mod frozen;
mod request; mod request;
mod response; mod response;
mod sender;
pub mod test; pub mod test;
pub mod ws; pub mod ws;
pub use self::builder::ClientBuilder; pub use self::builder::ClientBuilder;
pub use self::connect::BoxedSocket; pub use self::connect::BoxedSocket;
pub use self::frozen::{FrozenClientRequest, FrozenSendBuilder};
pub use self::request::ClientRequest; pub use self::request::ClientRequest;
pub use self::response::{ClientResponse, JsonBody, MessageBody}; pub use self::response::{ClientResponse, JsonBody, MessageBody};
pub use self::sender::SendClientRequest;
use self::connect::{Connect, ConnectorWrapper}; use self::connect::{Connect, ConnectorWrapper};

View File

@ -1,29 +1,26 @@
use std::fmt::Write as FmtWrite; use std::fmt::Write as FmtWrite;
use std::io::Write; use std::io::Write;
use std::rc::Rc; use std::rc::Rc;
use std::time::{Duration, Instant}; use std::time::Duration;
use std::{fmt, net}; use std::{fmt, net};
use bytes::{BufMut, Bytes, BytesMut}; use bytes::{BufMut, Bytes, BytesMut};
use futures::{Async, Future, Poll, Stream, try_ready}; use futures::Stream;
use percent_encoding::percent_encode; use percent_encoding::percent_encode;
use serde::Serialize; use serde::Serialize;
use serde_json;
use tokio_timer::Delay;
use derive_more::From;
use actix_http::body::{Body, BodyStream}; use actix_http::body::Body;
use actix_http::cookie::{Cookie, CookieJar, USERINFO}; use actix_http::cookie::{Cookie, CookieJar, USERINFO};
use actix_http::encoding::Decoder; use actix_http::http::header::{self, Header, IntoHeaderValue};
use actix_http::http::header::{self, ContentEncoding, Header, IntoHeaderValue};
use actix_http::http::{ use actix_http::http::{
uri, ConnectionType, Error as HttpError, HeaderMap, HeaderName, HeaderValue, uri, ConnectionType, Error as HttpError, HeaderMap, HeaderName, HeaderValue,
HttpTryFrom, Method, Uri, Version, HttpTryFrom, Method, Uri, Version,
}; };
use actix_http::{Error, Payload, PayloadStream, RequestHead}; use actix_http::{Error, RequestHead};
use crate::error::{InvalidUrl, SendRequestError, FreezeRequestError}; use crate::error::{FreezeRequestError, InvalidUrl};
use crate::response::ClientResponse; use crate::frozen::FrozenClientRequest;
use crate::sender::{PrepForSendingError, RequestSender, SendClientRequest};
use crate::ClientConfig; use crate::ClientConfig;
#[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))] #[cfg(any(feature = "brotli", feature = "flate2-zlib", feature = "flate2-rust"))]
@ -99,7 +96,7 @@ impl ClientRequest {
self self
} }
/// Get HTTP URI of request /// Get HTTP URI of request.
pub fn get_uri(&self) -> &Uri { pub fn get_uri(&self) -> &Uri {
&self.head.uri &self.head.uri
} }
@ -135,6 +132,16 @@ impl ClientRequest {
self 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] #[inline]
/// Returns request's headers. /// Returns request's headers.
pub fn headers(&self) -> &HeaderMap { pub fn headers(&self) -> &HeaderMap {
@ -375,6 +382,8 @@ 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> { pub fn freeze(self) -> Result<FrozenClientRequest, FreezeRequestError> {
let slf = match self.prep_for_sending() { let slf = match self.prep_for_sending() {
Ok(slf) => slf, Ok(slf) => slf,
@ -393,10 +402,7 @@ impl ClientRequest {
} }
/// Complete request construction and send body. /// Complete request construction and send body.
pub fn send_body<B>( pub fn send_body<B>(self, body: B) -> SendClientRequest
self,
body: B,
) -> SendBody
where where
B: Into<Body>, B: Into<Body>,
{ {
@ -405,47 +411,51 @@ impl ClientRequest {
Err(e) => return e.into(), Err(e) => return e.into(),
}; };
RequestSender::Owned(slf.head) RequestSender::Owned(slf.head).send_body(
.send_body(slf.addr, slf.response_decompress, slf.timeout, slf.config.as_ref(), body) slf.addr,
slf.response_decompress,
slf.timeout,
slf.config.as_ref(),
body,
)
} }
/// Set a JSON body and generate `ClientRequest` /// Set a JSON body and generate `ClientRequest`
pub fn send_json<T: Serialize>( pub fn send_json<T: Serialize>(self, value: &T) -> SendClientRequest {
self,
value: &T,
) -> SendBody
{
let slf = match self.prep_for_sending() { let slf = match self.prep_for_sending() {
Ok(slf) => slf, Ok(slf) => slf,
Err(e) => return e.into(), Err(e) => return e.into(),
}; };
RequestSender::Owned(slf.head) RequestSender::Owned(slf.head).send_json(
.send_json(slf.addr, slf.response_decompress, slf.timeout, slf.config.as_ref(), value) slf.addr,
slf.response_decompress,
slf.timeout,
slf.config.as_ref(),
value,
)
} }
/// Set a urlencoded body and generate `ClientRequest` /// Set a urlencoded body and generate `ClientRequest`
/// ///
/// `ClientRequestBuilder` can not be used after this call. /// `ClientRequestBuilder` can not be used after this call.
pub fn send_form<T: Serialize>( pub fn send_form<T: Serialize>(self, value: &T) -> SendClientRequest {
self,
value: &T,
) -> SendBody
{
let slf = match self.prep_for_sending() { let slf = match self.prep_for_sending() {
Ok(slf) => slf, Ok(slf) => slf,
Err(e) => return e.into(), Err(e) => return e.into(),
}; };
RequestSender::Owned(slf.head) RequestSender::Owned(slf.head).send_form(
.send_form(slf.addr, slf.response_decompress, slf.timeout, slf.config.as_ref(), value) slf.addr,
slf.response_decompress,
slf.timeout,
slf.config.as_ref(),
value,
)
} }
/// Set an streaming body and generate `ClientRequest`. /// Set an streaming body and generate `ClientRequest`.
pub fn send_stream<S, E>( pub fn send_stream<S, E>(self, stream: S) -> SendClientRequest
self,
stream: S,
) -> SendBody
where where
S: Stream<Item = Bytes, Error = E> + 'static, S: Stream<Item = Bytes, Error = E> + 'static,
E: Into<Error> + 'static, E: Into<Error> + 'static,
@ -455,22 +465,28 @@ impl ClientRequest {
Err(e) => return e.into(), Err(e) => return e.into(),
}; };
RequestSender::Owned(slf.head) RequestSender::Owned(slf.head).send_stream(
.send_stream(slf.addr, slf.response_decompress, slf.timeout, slf.config.as_ref(), stream) slf.addr,
slf.response_decompress,
slf.timeout,
slf.config.as_ref(),
stream,
)
} }
/// Set an empty body and generate `ClientRequest`. /// Set an empty body and generate `ClientRequest`.
pub fn send( pub fn send(self) -> SendClientRequest {
self,
) -> SendBody
{
let slf = match self.prep_for_sending() { let slf = match self.prep_for_sending() {
Ok(slf) => slf, Ok(slf) => slf,
Err(e) => return e.into(), Err(e) => return e.into(),
}; };
RequestSender::Owned(slf.head) RequestSender::Owned(slf.head).send(
.send(slf.addr, slf.response_decompress, slf.timeout, slf.config.as_ref()) slf.addr,
slf.response_decompress,
slf.timeout,
slf.config.as_ref(),
)
} }
fn prep_for_sending(mut self) -> Result<Self, PrepForSendingError> { fn prep_for_sending(mut self) -> Result<Self, PrepForSendingError> {
@ -555,441 +571,6 @@ impl fmt::Debug for ClientRequest {
} }
} }
#[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,
) -> SendBody
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,
) -> SendBody
{
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,
) -> SendBody
{
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,
) -> SendBody
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,
) -> SendBody
{
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)
}
}
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,
) -> SendBody
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,
) -> SendBody
{
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,
) -> SendBody
{
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,
) -> SendBody
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,
) -> SendBody
{
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())
}
}
#[derive(Debug, From)]
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),
}
}
}
pub enum SendBody
{
Fut(Box<dyn Future<Item = ClientResponse, Error = SendRequestError>>, Option<Delay>, bool),
Err(Option<SendRequestError>),
}
impl SendBody
{
pub fn new(
send: Box<dyn Future<Item = ClientResponse, Error = SendRequestError>>,
response_decompress: bool,
timeout: Option<Duration>,
) -> SendBody
{
let delay = timeout.map(|t| Delay::new(Instant::now() + t));
SendBody::Fut(send, delay, response_decompress)
}
}
impl Future for SendBody
{
type Item = ClientResponse<Decoder<Payload<PayloadStream>>>;
type Error = SendRequestError;
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
match self {
SendBody::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))
},
SendBody::Err(ref mut e) => {
match e.take() {
Some(e) => Err(e.into()),
None => panic!("Attempting to call completed future"),
}
}
}
}
}
impl From<SendRequestError> for SendBody
{
fn from(e: SendRequestError) -> Self {
SendBody::Err(Some(e))
}
}
impl From<Error> for SendBody
{
fn from(e: Error) -> Self {
SendBody::Err(Some(e.into()))
}
}
impl From<HttpError> for SendBody
{
fn from(e: HttpError) -> Self {
SendBody::Err(Some(e.into()))
}
}
impl From<PrepForSendingError> for SendBody
{
fn from(e: PrepForSendingError) -> Self {
SendBody::Err(Some(e.into()))
}
}
#[derive(Debug)]
enum RequestSender {
Owned(RequestHead),
Rc(Rc<RequestHead>, Option<HeaderMap>),
}
impl RequestSender {
pub fn send_body<B>(
self,
addr: Option<net::SocketAddr>,
response_decompress: bool,
timeout: Option<Duration>,
config: &ClientConfig,
body: B,
) -> SendBody
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),
};
SendBody::new(fut, response_decompress, timeout.or_else(|| config.timeout.clone()))
}
pub fn send_json<T: Serialize>(
mut self,
addr: Option<net::SocketAddr>,
response_decompress: bool,
timeout: Option<Duration>,
config: &ClientConfig,
value: &T,
) -> SendBody
{
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 fn send_form<T: Serialize>(
mut self,
addr: Option<net::SocketAddr>,
response_decompress: bool,
timeout: Option<Duration>,
config: &ClientConfig,
value: &T,
) -> SendBody
{
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 fn send_stream<S, E>(
self,
addr: Option<net::SocketAddr>,
response_decompress: bool,
timeout: Option<Duration>,
config: &ClientConfig,
stream: S,
) -> SendBody
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 fn send(
self,
addr: Option<net::SocketAddr>,
response_decompress: bool,
timeout: Option<Duration>,
config: &ClientConfig,
) -> SendBody
{
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(())
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::time::SystemTime; use std::time::SystemTime;

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

@ -234,7 +234,10 @@ impl WebsocketsRequest {
} }
if !self.head.headers.contains_key(header::HOST) { if !self.head.headers.contains_key(header::HOST) {
self.head.headers.insert(header::HOST, HeaderValue::from_str(uri.host().unwrap()).unwrap()); self.head.headers.insert(
header::HOST,
HeaderValue::from_str(uri.host().unwrap()).unwrap(),
);
} }
// set cookies // set cookies

View File

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

View File

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

View File

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

View File

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

View File

@ -155,9 +155,9 @@ impl ConnectionInfo {
&self.host &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 /// - Forwarded
/// - X-Forwarded-For /// - X-Forwarded-For

View File

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

View File

@ -271,8 +271,8 @@ impl Drop for HttpRequest {
/// ## Example /// ## Example
/// ///
/// ```rust /// ```rust
/// # #[macro_use] extern crate serde_derive;
/// use actix_web::{web, App, HttpRequest}; /// use actix_web::{web, App, HttpRequest};
/// use serde_derive::Deserialize;
/// ///
/// /// extract `Thing` from request /// /// extract `Thing` from request
/// fn index(req: HttpRequest) -> String { /// fn index(req: HttpRequest) -> String {

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() { if self.data.is_none() {
self.data = Some(Extensions::new()); self.data = Some(Extensions::new());
} }
self.data.as_mut().unwrap().insert(Data::new(data)); self.data.as_mut().unwrap().insert(data);
self self
} }
@ -763,4 +773,34 @@ mod tests {
let resp = call_service(&mut srv, req); let resp = call_service(&mut srv, req);
assert_eq!(resp.status(), StatusCode::NO_CONTENT); 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. /// Set handler function, use request extractors for parameters.
/// ///
/// ```rust /// ```rust
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{web, http, App}; /// use actix_web::{web, http, App};
/// use serde_derive::Deserialize;
/// ///
/// #[derive(Deserialize)] /// #[derive(Deserialize)]
/// struct Info { /// struct Info {
@ -239,9 +239,9 @@ impl Route {
/// ///
/// ```rust /// ```rust
/// # use futures::future::ok; /// # use futures::future::ok;
/// #[macro_use] extern crate serde_derive;
/// use actix_web::{web, App, Error}; /// use actix_web::{web, App, Error};
/// use futures::Future; /// use futures::Future;
/// use serde_derive::Deserialize;
/// ///
/// #[derive(Deserialize)] /// #[derive(Deserialize)]
/// struct Info { /// 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() { if self.data.is_none() {
self.data = Some(Extensions::new()); self.data = Some(Extensions::new());
} }
self.data.as_mut().unwrap().insert(Data::new(data)); self.data.as_mut().unwrap().insert(data);
self self
} }
@ -1082,6 +1091,28 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK); 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] #[test]
fn test_scope_config() { fn test_scope_config() {
let mut srv = let mut srv =

View File

@ -435,6 +435,37 @@ where
Ok(self) 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")] #[cfg(feature = "uds")]
/// Start listening for incoming unix domain connections. /// Start listening for incoming unix domain connections.
/// ///

View File

@ -68,6 +68,34 @@ impl ServiceRequest {
(self.0, pl) (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 /// Create service response
#[inline] #[inline]
pub fn into_response<B, R: Into<Response<B>>>(self, res: R) -> ServiceResponse<B> { 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::test::{call_service, init_service, TestRequest};
use crate::{guard, http, web, App, HttpResponse}; 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] #[test]
fn test_service() { fn test_service() {
let mut srv = init_service( let mut srv = init_service(

View File

@ -1,5 +1,4 @@
//! Various helpers for Actix applications to use during testing. //! Various helpers for Actix applications to use during testing.
use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use actix_http::http::header::{ContentType, Header, HeaderName, IntoHeaderValue}; 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::test::TestRequest as HttpTestRequest;
use actix_http::{cookie::Cookie, Extensions, Request}; use actix_http::{cookie::Cookie, Extensions, Request};
use actix_router::{Path, ResourceDef, Url}; use actix_router::{Path, ResourceDef, Url};
use actix_rt::{System, SystemRunner};
use actix_server_config::ServerConfig; use actix_server_config::ServerConfig;
use actix_service::{IntoNewService, IntoService, NewService, Service}; use actix_service::{IntoNewService, IntoService, NewService, Service};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::future::{lazy, ok, Future, IntoFuture}; use futures::future::{ok, Future};
use futures::Stream; use futures::Stream;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::Serialize; use serde::Serialize;
use serde_json; use serde_json;
pub use actix_http::test::TestBuffer; pub use actix_http::test::TestBuffer;
pub use actix_testing::{block_fn, block_on, run_on};
use crate::config::{AppConfig, AppConfigInner}; use crate::config::{AppConfig, AppConfigInner};
use crate::data::Data; use crate::data::Data;
@ -27,78 +26,6 @@ use crate::rmap::ResourceMap;
use crate::service::{ServiceRequest, ServiceResponse}; use crate::service::{ServiceRequest, ServiceResponse};
use crate::{Error, HttpRequest, HttpResponse}; 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()` /// Create service that always responds with `HttpResponse::Ok()`
pub fn ok_service( pub fn ok_service(
) -> impl Service<Request = ServiceRequest, Response = ServiceResponse<Body>, Error = Error> ) -> impl Service<Request = ServiceRequest, Response = ServiceResponse<Body>, Error = Error>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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