diff --git a/.travis.yml b/.travis.yml index 97a05cc96..82db86a6e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,9 +10,9 @@ matrix: include: - rust: stable - rust: beta - - rust: nightly-2019-04-02 + - rust: nightly-2019-08-10 allow_failures: - - rust: nightly-2019-04-02 + - rust: nightly-2019-08-10 env: global: @@ -25,7 +25,7 @@ before_install: - sudo apt-get install -y openssl libssl-dev libelf-dev libdw-dev cmake gcc binutils-dev libiberty-dev before_cache: | - if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-04-02" ]]; then + if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-08-10" ]]; then RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install --version 0.6.11 cargo-tarpaulin fi @@ -37,6 +37,8 @@ script: - cargo update - cargo check --all --no-default-features - cargo test --all-features --all -- --nocapture + - cd actix-http; cargo test --no-default-features --features="rust-tls" -- --nocapture; cd .. + - cd awc; cargo test --no-default-features --features="rust-tls" -- --nocapture; cd .. # Upload docs after_success: @@ -49,7 +51,7 @@ after_success: echo "Uploaded documentation" fi - | - if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-04-02" ]]; then + if [[ "$TRAVIS_RUST_VERSION" == "nightly-2019-08-10" ]]; then taskset -c 0 cargo tarpaulin --out Xml --all --all-features bash <(curl -s https://codecov.io/bash) echo "Uploaded code coverage" diff --git a/CHANGES.md b/CHANGES.md index 641e09bdd..3aadc8f1e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,11 +1,50 @@ # Changes -## [1.0.4] - TBD +## [1.0.6] - 2019-xx-xx + +### Added + +* Re-implement Host predicate (#989) + +* Form immplements Responder, returning a `application/x-www-form-urlencoded` response + +### Changed + +* `Query` payload made `pub`. Allows user to pattern-match the payload. + +* Update serde_urlencoded to 0.6.1 + +* Update url to 2.1 + + +## [1.0.5] - 2019-07-18 + +### Added + +* Unix domain sockets (HttpServer::bind_uds) #92 + +* Actix now logs errors resulting in "internal server error" responses always, with the `error` + logging level + +### Fixed + +* Restored logging of errors through the `Logger` middleware + + +## [1.0.4] - 2019-07-17 + +### Added + +* Add `Responder` impl for `(T, StatusCode) where T: Responder` + +* Allow to access app's resource map via + `ServiceRequest::resource_map()` and `HttpRequest::resource_map()` methods. ### Changed * Upgrade `rand` dependency version to 0.7 + ## [1.0.3] - 2019-06-28 ### Added diff --git a/Cargo.toml b/Cargo.toml index 57676accc..7c630cc7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "1.0.3" +version = "1.0.5" authors = ["Nikolay Kim "] description = "Actix web is a simple, pragmatic and extremely fast web framework for Rust." readme = "README.md" @@ -16,7 +16,7 @@ exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] edition = "2018" [package.metadata.docs.rs] -features = ["ssl", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls"] +features = ["ssl", "brotli", "flate2-zlib", "secure-cookies", "client", "rust-tls", "uds"] [badges] travis-ci = { repository = "actix/actix-web", branch = "master" } @@ -68,18 +68,21 @@ ssl = ["openssl", "actix-server/ssl", "awc/ssl"] # rustls rust-tls = ["rustls", "actix-server/rust-tls"] +# unix domain sockets support +uds = ["actix-server/uds"] + [dependencies] actix-codec = "0.1.2" actix-service = "0.4.1" -actix-utils = "0.4.2" +actix-utils = "0.4.4" actix-router = "0.1.5" -actix-rt = "0.2.3" +actix-rt = "0.2.4" actix-web-codegen = "0.1.2" -actix-http = "0.2.5" -actix-server = "0.5.1" -actix-server-config = "0.1.1" +actix-http = "0.2.9" +actix-server = "0.6.0" +actix-server-config = "0.1.2" actix-threadpool = "0.1.1" -awc = { version = "0.2.1", optional = true } +awc = { version = "0.2.4", optional = true } bytes = "0.4" derive_more = "0.15.0" @@ -89,22 +92,22 @@ hashbrown = "0.5.0" log = "0.4" mime = "0.3" net2 = "0.2.33" -parking_lot = "0.8" +parking_lot = "0.9" regex = "1.0" serde = { version = "1.0", features=["derive"] } serde_json = "1.0" -serde_urlencoded = "0.5.3" +serde_urlencoded = "0.6.1" time = "0.1.42" -url = { version="1.7", features=["query_encoding"] } +url = "2.1" # ssl support openssl = { version="0.10", optional = true } rustls = { version = "0.15", optional = true } [dev-dependencies] -actix = { version = "0.8.3" } -actix-http = { version = "0.2.5", features=["ssl", "brotli", "flate2-zlib"] } -actix-http-test = { version = "0.2.2", features=["ssl"] } +actix = "0.8.3" +actix-connect = "0.2.2" +actix-http-test = "0.2.4" rand = "0.7" env_logger = "0.6" serde_derive = "1.0" diff --git a/README.md b/README.md index cae737b68..99b7b1760 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Actix web is a simple, pragmatic and extremely fast web framework for Rust. * [API Documentation (0.7)](https://docs.rs/actix-web/0.7.19/actix_web/) * [Chat on gitter](https://gitter.im/actix/actix) * Cargo package: [actix-web](https://crates.io/crates/actix-web) -* Minimum supported Rust version: 1.34 or later +* Minimum supported Rust version: 1.36 or later ## Example @@ -61,7 +61,7 @@ You may consider checking out ## Benchmarks -* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r16&hw=ph&test=plaintext) +* [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r18) ## License diff --git a/actix-cors/src/lib.rs b/actix-cors/src/lib.rs index 5d0d013e3..c76bae925 100644 --- a/actix-cors/src/lib.rs +++ b/actix-cors/src/lib.rs @@ -1,3 +1,4 @@ +#![allow(clippy::borrow_interior_mutable_const, clippy::type_complexity)] //! Cross-origin resource sharing (CORS) for Actix applications //! //! CORS middleware could be used with application and with resource. @@ -162,6 +163,7 @@ impl AllOrSome { /// .max_age(3600); /// # } /// ``` +#[derive(Default)] pub struct Cors { cors: Option, methods: bool, @@ -585,10 +587,10 @@ impl Inner { } Err(CorsError::BadOrigin) } else { - return match self.origins { + match self.origins { AllOrSome::All => Ok(()), _ => Err(CorsError::MissingOrigin), - }; + } } } @@ -663,7 +665,7 @@ impl Inner { } Err(CorsError::BadRequestHeaders) } else { - return Ok(()); + Ok(()) } } } @@ -681,7 +683,7 @@ where type Error = Error; type Future = Either< FutureResult, - Either>>, + Either>>, >; fn poll_ready(&mut self) -> Poll<(), Self::Error> { diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 2f98e15c2..49ecdbffc 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,5 +1,16 @@ # Changes +## [0.1.5] - unreleased + +* Bump up `mime_guess` crate version to 2.0.1 + +* Bump up `percent-encoding` crate version to 2.1 + +## [0.1.4] - 2019-07-20 + +* Allow to disable `Content-Disposition` header #686 + + ## [0.1.3] - 2019-06-28 * Do not set `Content-Length` header, let actix-http set it #930 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index c9d9cfecb..8f36cddc3 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.1.3" +version = "0.1.4" authors = ["Nikolay Kim "] description = "Static files support for actix web." readme = "README.md" @@ -19,7 +19,7 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "1.0.2", default-features = false } -actix-http = "0.2.4" +actix-http = "0.2.9" actix-service = "0.4.1" bitflags = "1" bytes = "0.4" @@ -27,8 +27,8 @@ futures = "0.1.25" derive_more = "0.15.0" log = "0.4" mime = "0.3" -mime_guess = "2.0.0-alpha" -percent-encoding = "1.0" +mime_guess = "2.0.1" +percent-encoding = "2.1" v_htmlescape = "0.4" [dev-dependencies] diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 8e87f7d89..c99d3265f 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(clippy::borrow_interior_mutable_const, clippy::type_complexity)] + //! Static files support use std::cell::RefCell; use std::fmt::Write; @@ -20,8 +22,8 @@ use bytes::Bytes; use futures::future::{ok, Either, FutureResult}; use futures::{Async, Future, Poll, Stream}; use mime; -use mime_guess::get_mime_type; -use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; +use mime_guess::from_ext; +use percent_encoding::{utf8_percent_encode, CONTROLS}; use v_htmlescape::escape as escape_html_entity; mod error; @@ -40,7 +42,7 @@ type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error /// the type `application/octet-stream`. #[inline] pub fn file_extension_to_mime(ext: &str) -> mime::Mime { - get_mime_type(ext) + from_ext(ext).first_or_octet_stream() } #[doc(hidden)] @@ -50,14 +52,14 @@ pub struct ChunkedReadFile { size: u64, offset: u64, file: Option, - fut: Option>>>, + fut: Option>>>, counter: u64, } fn handle_error(err: BlockingError) -> Error { match err { BlockingError::Error(err) => err.into(), - BlockingError::Canceled => ErrorInternalServerError("Unexpected error").into(), + BlockingError::Canceled => ErrorInternalServerError("Unexpected error"), } } @@ -105,7 +107,7 @@ impl Stream for ChunkedReadFile { } type DirectoryRenderer = - Fn(&Directory, &HttpRequest) -> Result; + dyn Fn(&Directory, &HttpRequest) -> Result; /// A directory; responds with the generated directory listing. #[derive(Debug)] @@ -142,7 +144,7 @@ impl Directory { // show file url as relative to static path macro_rules! encode_file_url { ($path:ident) => { - utf8_percent_encode(&$path.to_string_lossy(), DEFAULT_ENCODE_SET) + utf8_percent_encode(&$path.to_string_lossy(), CONTROLS) }; } @@ -209,7 +211,7 @@ fn directory_listing( )) } -type MimeOverride = Fn(&mime::Name) -> DispositionType; +type MimeOverride = dyn Fn(&mime::Name) -> DispositionType; /// Static files handling /// @@ -259,7 +261,7 @@ impl Files { pub fn new>(path: &str, dir: T) -> Files { let dir = dir.into().canonicalize().unwrap_or_else(|_| PathBuf::new()); if !dir.is_dir() { - log::error!("Specified path is not a directory"); + log::error!("Specified path is not a directory: {:?}", dir); } Files { @@ -312,23 +314,32 @@ impl Files { } #[inline] - ///Specifies whether to use ETag or not. + /// Specifies whether to use ETag or not. /// - ///Default is true. + /// Default is true. pub fn use_etag(mut self, value: bool) -> Self { self.file_flags.set(named::Flags::ETAG, value); self } #[inline] - ///Specifies whether to use Last-Modified or not. + /// Specifies whether to use Last-Modified or not. /// - ///Default is true. + /// Default is true. pub fn use_last_modified(mut self, value: bool) -> Self { self.file_flags.set(named::Flags::LAST_MD, value); self } + /// Disable `Content-Disposition` header. + /// + /// By default Content-Disposition` header is enabled. + #[inline] + pub fn disable_content_disposition(mut self) -> Self { + self.file_flags.remove(named::Flags::CONTENT_DISPOSITION); + self + } + /// Sets default handler which is used when no matched file could be found. pub fn default_handler(mut self, f: F) -> Self where @@ -370,7 +381,7 @@ impl NewService for Files { type Error = Error; type Service = FilesService; type InitError = (); - type Future = Box>; + type Future = Box>; fn new_service(&self, _: &()) -> Self::Future { let mut srv = FilesService { @@ -416,7 +427,7 @@ impl FilesService { req: ServiceRequest, ) -> Either< FutureResult, - Box>, + Box>, > { log::debug!("Files: Failed to handle {}: {}", req.path(), e); if let Some(ref mut default) = self.default { @@ -433,7 +444,7 @@ impl Service for FilesService { type Error = Error; type Future = Either< FutureResult, - Box>, + Box>, >; fn poll_ready(&mut self) -> Poll<(), Self::Error> { @@ -473,7 +484,7 @@ impl Service for FilesService { Err(e) => ServiceResponse::from_err(e, req), })) } - Err(e) => return self.handle_err(e, req), + Err(e) => self.handle_err(e, req), } } else if self.show_index { let dir = Directory::new(self.directory.clone(), path); @@ -481,7 +492,7 @@ impl Service for FilesService { let x = (self.renderer)(&dir, &req); match x { Ok(resp) => Either::A(ok(resp)), - Err(e) => return Either::A(ok(ServiceResponse::from_err(e, req))), + Err(e) => Either::A(ok(ServiceResponse::from_err(e, req))), } } else { Either::A(ok(ServiceResponse::from_err( @@ -636,6 +647,33 @@ mod tests { ); } + #[test] + fn test_named_file_content_disposition() { + assert!(NamedFile::open("test--").is_err()); + let mut file = NamedFile::open("Cargo.toml").unwrap(); + { + file.file(); + let _f: &File = &file; + } + { + let _f: &mut File = &mut file; + } + + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).unwrap(); + assert_eq!( + resp.headers().get(header::CONTENT_DISPOSITION).unwrap(), + "inline; filename=\"Cargo.toml\"" + ); + + let file = NamedFile::open("Cargo.toml") + .unwrap() + .disable_content_disposition(); + let req = TestRequest::default().to_http_request(); + let resp = file.respond_to(&req).unwrap(); + assert!(resp.headers().get(header::CONTENT_DISPOSITION).is_none()); + } + #[test] fn test_named_file_set_content_type() { let mut file = NamedFile::open("Cargo.toml") @@ -855,7 +893,7 @@ mod tests { #[test] fn test_named_file_content_length_headers() { - use actix_web::body::{MessageBody, ResponseBody}; + // use actix_web::body::{MessageBody, ResponseBody}; let mut srv = test::init_service( App::new().service(Files::new("test", ".").index_file("tests/test.binary")), @@ -866,7 +904,7 @@ mod tests { .uri("/t%65st/tests/test.binary") .header(header::RANGE, "bytes=10-20") .to_request(); - let response = test::call_service(&mut srv, request); + let _response = test::call_service(&mut srv, request); // let contentlength = response // .headers() @@ -889,7 +927,7 @@ mod tests { .uri("/t%65st/tests/test.binary") // .no_default_headers() .to_request(); - let response = test::call_service(&mut srv, request); + let _response = test::call_service(&mut srv, request); // let contentlength = response // .headers() @@ -937,7 +975,7 @@ mod tests { .method(Method::HEAD) .uri("/t%65st/tests/test.binary") .to_request(); - let response = test::call_service(&mut srv, request); + let _response = test::call_service(&mut srv, request); // TODO: fix check // let contentlength = response diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 6b948da8e..f548a7a1b 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -9,7 +9,7 @@ use std::os::unix::fs::MetadataExt; use bitflags::bitflags; use mime; -use mime_guess::guess_mime_type; +use mime_guess::from_path; use actix_http::body::SizedStream; use actix_web::http::header::{ @@ -23,9 +23,10 @@ use crate::range::HttpRange; use crate::ChunkedReadFile; bitflags! { - pub(crate) struct Flags: u32 { - const ETAG = 0b00000001; - const LAST_MD = 0b00000010; + pub(crate) struct Flags: u8 { + const ETAG = 0b0000_0001; + const LAST_MD = 0b0000_0010; + const CONTENT_DISPOSITION = 0b0000_0100; } } @@ -40,13 +41,13 @@ impl Default for Flags { pub struct NamedFile { path: PathBuf, file: File, + modified: Option, + pub(crate) md: Metadata, + pub(crate) flags: Flags, + pub(crate) status_code: StatusCode, pub(crate) content_type: mime::Mime, pub(crate) content_disposition: header::ContentDisposition, - pub(crate) md: Metadata, - modified: Option, pub(crate) encoding: Option, - pub(crate) status_code: StatusCode, - pub(crate) flags: Flags, } impl NamedFile { @@ -87,7 +88,7 @@ impl NamedFile { } }; - let ct = guess_mime_type(&path); + let ct = from_path(&path).first_or_octet_stream(); let disposition_type = match ct.type_() { mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline, _ => DispositionType::Attachment, @@ -172,11 +173,21 @@ impl NamedFile { /// sent to the peer. By default the disposition is `inline` for text, /// image, and video content types, and `attachment` otherwise, and /// the filename is taken from the path provided in the `open` method - /// after converting it to UTF-8 using + /// after converting it to UTF-8 using. /// [to_string_lossy](https://doc.rust-lang.org/std/ffi/struct.OsStr.html#method.to_string_lossy). #[inline] pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self { self.content_disposition = cd; + self.flags.insert(Flags::CONTENT_DISPOSITION); + self + } + + /// Disable `Content-Disposition` header. + /// + /// By default Content-Disposition` header is enabled. + #[inline] + pub fn disable_content_disposition(mut self) -> Self { + self.flags.remove(Flags::CONTENT_DISPOSITION); self } @@ -294,10 +305,12 @@ impl Responder for NamedFile { if self.status_code != StatusCode::OK { let mut resp = HttpResponse::build(self.status_code); resp.set(header::ContentType(self.content_type.clone())) - .header( - header::CONTENT_DISPOSITION, - self.content_disposition.to_string(), - ); + .if_true(self.flags.contains(Flags::CONTENT_DISPOSITION), |res| { + res.header( + header::CONTENT_DISPOSITION, + self.content_disposition.to_string(), + ); + }); if let Some(current_encoding) = self.encoding { resp.encoding(current_encoding); } @@ -311,8 +324,8 @@ impl Responder for NamedFile { return Ok(resp.streaming(reader)); } - match req.method() { - &Method::HEAD | &Method::GET => (), + match *req.method() { + Method::HEAD | Method::GET => (), _ => { return Ok(HttpResponse::MethodNotAllowed() .header(header::CONTENT_TYPE, "text/plain") @@ -368,10 +381,12 @@ impl Responder for NamedFile { let mut resp = HttpResponse::build(self.status_code); resp.set(header::ContentType(self.content_type.clone())) - .header( - header::CONTENT_DISPOSITION, - self.content_disposition.to_string(), - ); + .if_true(self.flags.contains(Flags::CONTENT_DISPOSITION), |res| { + res.header( + header::CONTENT_DISPOSITION, + self.content_disposition.to_string(), + ); + }); // default compressing if let Some(current_encoding) = self.encoding { resp.encoding(current_encoding); diff --git a/actix-files/src/range.rs b/actix-files/src/range.rs index d97a35e7e..47673b0b0 100644 --- a/actix-files/src/range.rs +++ b/actix-files/src/range.rs @@ -5,7 +5,7 @@ pub struct HttpRange { pub length: u64, } -static PREFIX: &'static str = "bytes="; +static PREFIX: &str = "bytes="; const PREFIX_LEN: usize = 6; impl HttpRange { diff --git a/actix-framed/Cargo.toml b/actix-framed/Cargo.toml index c2ab26fa2..321041c7e 100644 --- a/actix-framed/Cargo.toml +++ b/actix-framed/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-framed" -version = "0.2.0" +version = "0.2.1" authors = ["Nikolay Kim "] description = "Actix framed app server" readme = "README.md" @@ -21,18 +21,18 @@ path = "src/lib.rs" [dependencies] actix-codec = "0.1.2" -actix-service = "0.4.0" -actix-utils = "0.4.0" +actix-service = "0.4.1" actix-router = "0.1.2" actix-rt = "0.2.2" -actix-http = "0.2.0" -actix-server-config = "0.1.1" +actix-http = "0.2.7" +actix-server-config = "0.1.2" bytes = "0.4" futures = "0.1.25" log = "0.4" [dev-dependencies] -actix-server = { version = "0.5.0", features=["ssl"] } +actix-server = { version = "0.6.0", features=["ssl"] } actix-connect = { version = "0.2.0", features=["ssl"] } -actix-http-test = { version = "0.2.0", features=["ssl"] } +actix-http-test = { version = "0.2.4", features=["ssl"] } +actix-utils = "0.4.4" diff --git a/actix-framed/changes.md b/actix-framed/changes.md index 9f16c790a..6e67e00d8 100644 --- a/actix-framed/changes.md +++ b/actix-framed/changes.md @@ -1,5 +1,10 @@ # Changes +## [0.2.1] - 2019-07-20 + +* Remove unneeded actix-utils dependency + + ## [0.2.0] - 2019-05-12 * Update dependencies diff --git a/actix-framed/src/app.rs b/actix-framed/src/app.rs index 297796bd1..ad5b1ec26 100644 --- a/actix-framed/src/app.rs +++ b/actix-framed/src/app.rs @@ -6,14 +6,13 @@ use actix_http::{Error, Request, Response}; use actix_router::{Path, Router, Url}; use actix_server_config::ServerConfig; use actix_service::{IntoNewService, NewService, Service}; -use actix_utils::cloneable::CloneableService; use futures::{Async, Future, Poll}; use crate::helpers::{BoxedHttpNewService, BoxedHttpService, HttpNewService}; use crate::request::FramedRequest; use crate::state::State; -type BoxedResponse = Box>; +type BoxedResponse = Box>; pub trait HttpServiceFactory { type Factory: NewService; @@ -61,7 +60,7 @@ impl FramedApp { Request = FramedRequest, Response = (), Error = Error, - Future = Box>, + Future = Box>, >, { let path = factory.path().to_string(); @@ -100,7 +99,7 @@ where type Response = (); type Error = Error; type InitError = (); - type Service = CloneableService>; + type Service = FramedAppService; type Future = CreateService; fn new_service(&self, _: &ServerConfig) -> Self::Future { @@ -129,7 +128,7 @@ pub struct CreateService { enum CreateServiceItem { Future( Option, - Box>, Error = ()>>, + Box>, Error = ()>>, ), Service(String, BoxedHttpService>), } @@ -138,7 +137,7 @@ impl Future for CreateService where T: AsyncRead + AsyncWrite, { - type Item = CloneableService>; + type Item = FramedAppService; type Error = (); fn poll(&mut self) -> Poll { @@ -177,10 +176,10 @@ where } router }); - Ok(Async::Ready(CloneableService::new(FramedAppService { + Ok(Async::Ready(FramedAppService { router: router.finish(), state: self.state.clone(), - }))) + })) } else { Ok(Async::NotReady) } diff --git a/actix-framed/src/helpers.rs b/actix-framed/src/helpers.rs index 944b729d4..b343301f3 100644 --- a/actix-framed/src/helpers.rs +++ b/actix-framed/src/helpers.rs @@ -3,23 +3,23 @@ use actix_service::{NewService, Service}; use futures::{Future, Poll}; pub(crate) type BoxedHttpService = Box< - Service< + dyn Service< Request = Req, Response = (), Error = Error, - Future = Box>, + Future = Box>, >, >; pub(crate) type BoxedHttpNewService = Box< - NewService< + dyn NewService< Config = (), Request = Req, Response = (), Error = Error, InitError = (), Service = BoxedHttpService, - Future = Box, Error = ()>>, + Future = Box, Error = ()>>, >, >; @@ -30,7 +30,7 @@ where T: NewService, T::Response: 'static, T::Future: 'static, - T::Service: Service>> + 'static, + T::Service: Service>> + 'static, ::Future: 'static, { pub fn new(service: T) -> Self { @@ -43,7 +43,7 @@ where T: NewService, T::Request: 'static, T::Future: 'static, - T::Service: Service>> + 'static, + T::Service: Service>> + 'static, ::Future: 'static, { type Config = (); @@ -52,7 +52,7 @@ where type Error = Error; type InitError = (); type Service = BoxedHttpService; - type Future = Box>; + type Future = Box>; fn new_service(&self, _: &()) -> Self::Future { Box::new(self.0.new_service(&()).map_err(|_| ()).and_then(|service| { @@ -70,7 +70,7 @@ impl Service for HttpServiceWrapper where T: Service< Response = (), - Future = Box>, + Future = Box>, Error = Error, >, T::Request: 'static, @@ -78,7 +78,7 @@ where type Request = T::Request; type Response = (); type Error = Error; - type Future = Box>; + type Future = Box>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.service.poll_ready() diff --git a/actix-framed/src/lib.rs b/actix-framed/src/lib.rs index c6405e20b..250533f39 100644 --- a/actix-framed/src/lib.rs +++ b/actix-framed/src/lib.rs @@ -1,3 +1,4 @@ +#![allow(clippy::type_complexity, clippy::new_without_default, dead_code)] mod app; mod helpers; mod request; diff --git a/actix-framed/src/route.rs b/actix-framed/src/route.rs index c50401d6d..5beb24165 100644 --- a/actix-framed/src/route.rs +++ b/actix-framed/src/route.rs @@ -140,7 +140,7 @@ where type Request = FramedRequest; type Response = (); type Error = Error; - type Future = Box>; + type Future = Box>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { Ok(Async::Ready(())) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 516793264..c8d1b2ae8 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,11 +1,51 @@ # Changes -## [0.2.6] - TBD +## [0.2.9] - 2019-08-13 ### Changed +* Dropped the `byteorder`-dependency in favor of `stdlib`-implementation + +* Update percent-encoding to 2.1 + +* Update serde_urlencoded to 0.6.1 + +### Fixed + +* Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031) + + +## [0.2.8] - 2019-08-01 + +### Added + +* Add `rustls` support + +* Add `Clone` impl for `HeaderMap` + +### Fixed + +* awc client panic #1016 + +* Invalid response with compression middleware enabled, but compression-related features disabled #997 + + +## [0.2.7] - 2019-07-18 + +### Added + +* Add support for downcasting response errors #986 + + +## [0.2.6] - 2019-07-17 + +### Changed + +* Replace `ClonableService` with local copy + * Upgrade `rand` dependency version to 0.7 + ## [0.2.5] - 2019-06-28 ### Added diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 5db7a6ca3..79d7117b4 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "0.2.5" +version = "0.2.9" authors = ["Nikolay Kim "] description = "Actix http primitives" readme = "README.md" @@ -28,6 +28,9 @@ default = [] # openssl ssl = ["openssl", "actix-connect/ssl"] +# rustls support +rust-tls = ["rustls", "webpki-roots", "actix-connect/rust-tls"] + # brotli encoding, requires c compiler brotli = ["brotli2"] @@ -46,15 +49,14 @@ secure-cookies = ["ring"] [dependencies] actix-service = "0.4.1" actix-codec = "0.1.2" -actix-connect = "0.2.0" -actix-utils = "0.4.2" -actix-server-config = "0.1.1" -actix-threadpool = "0.1.0" +actix-connect = "0.2.2" +actix-utils = "0.4.4" +actix-server-config = "0.1.2" +actix-threadpool = "0.1.1" base64 = "0.10" bitflags = "1.0" bytes = "0.4" -byteorder = "1.2" copyless = "0.1.4" derive_more = "0.15.0" either = "1.5.2" @@ -69,14 +71,14 @@ lazy_static = "1.0" language-tags = "0.2" log = "0.4" mime = "0.3" -percent-encoding = "1.0" +percent-encoding = "2.1" rand = "0.7" regex = "1.0" serde = "1.0" serde_json = "1.0" sha1 = "0.6" slab = "0.4" -serde_urlencoded = "0.5.5" +serde_urlencoded = "0.6.1" time = "0.1.42" tokio-tcp = "0.1.3" tokio-timer = "0.2.8" @@ -93,13 +95,15 @@ flate2 = { version="1.0.7", optional = true, default-features = false } # optional deps failure = { version = "0.1.5", optional = true } openssl = { version="0.10", optional = true } +rustls = { version = "0.15.2", optional = true } +webpki-roots = { version = "0.16", optional = true } chrono = "0.4.6" [dev-dependencies] actix-rt = "0.2.2" -actix-server = { version = "0.5.0", features=["ssl"] } +actix-server = { version = "0.6.0", features=["ssl", "rust-tls"] } actix-connect = { version = "0.2.0", features=["ssl"] } -actix-http-test = { version = "0.2.0", features=["ssl"] } +actix-http-test = { version = "0.2.4", features=["ssl"] } env_logger = "0.6" serde_derive = "1.0" openssl = { version="0.10" } diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index b6967d94d..bab0f5e1e 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -26,7 +26,7 @@ pub struct HttpServiceBuilder> { client_disconnect: u64, expect: X, upgrade: Option, - on_connect: Option Box>>, + on_connect: Option Box>>, _t: PhantomData<(T, S)>, } diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 2bba116b9..a5b22f065 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -100,7 +100,8 @@ where T: AsyncRead + AsyncWrite + 'static, { type Io = T; - type Future = Box>; + type Future = + Box>; fn protocol(&self) -> Protocol { match self.io { @@ -138,7 +139,7 @@ where type TunnelFuture = Either< Box< - Future< + dyn Future< Item = (ResponseHead, Framed), Error = SendRequestError, >, @@ -178,7 +179,8 @@ where B: AsyncRead + AsyncWrite + 'static, { type Io = EitherIo; - type Future = Box>; + type Future = + Box>; fn protocol(&self) -> Protocol { match self { @@ -200,7 +202,7 @@ where } type TunnelFuture = Box< - Future< + dyn Future< Item = (ResponseHead, Framed), Error = SendRequestError, >, diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 0241e8472..7d6fca25a 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -17,9 +17,21 @@ use super::pool::{ConnectionPool, Protocol}; use super::Connect; #[cfg(feature = "ssl")] -use openssl::ssl::SslConnector; +use openssl::ssl::SslConnector as OpensslConnector; -#[cfg(not(feature = "ssl"))] +#[cfg(feature = "rust-tls")] +use rustls::ClientConfig; +#[cfg(feature = "rust-tls")] +use std::sync::Arc; + +#[cfg(any(feature = "ssl", feature = "rust-tls"))] +enum SslConnector { + #[cfg(feature = "ssl")] + Openssl(OpensslConnector), + #[cfg(feature = "rust-tls")] + Rustls(Arc), +} +#[cfg(not(any(feature = "ssl", feature = "rust-tls")))] type SslConnector = (); /// Manages http client network connectivity @@ -46,7 +58,11 @@ pub struct Connector { _t: PhantomData, } +trait Io: AsyncRead + AsyncWrite {} +impl Io for T {} + impl Connector<(), ()> { + #[allow(clippy::new_ret_no_self)] pub fn new() -> Connector< impl Service< Request = TcpConnect, @@ -60,13 +76,23 @@ impl Connector<(), ()> { { use openssl::ssl::SslMethod; - let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap(); + let mut ssl = OpensslConnector::builder(SslMethod::tls()).unwrap(); let _ = ssl .set_alpn_protos(b"\x02h2\x08http/1.1") .map_err(|e| error!("Can not set alpn protocol: {:?}", e)); - ssl.build() + SslConnector::Openssl(ssl.build()) } - #[cfg(not(feature = "ssl"))] + #[cfg(all(not(feature = "ssl"), feature = "rust-tls"))] + { + let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; + let mut config = ClientConfig::new(); + config.set_protocols(&protos); + config + .root_store + .add_server_trust_anchors(&webpki_roots::TLS_SERVER_ROOTS); + SslConnector::Rustls(Arc::new(config)) + } + #[cfg(not(any(feature = "ssl", feature = "rust-tls")))] {} }; @@ -126,8 +152,14 @@ where #[cfg(feature = "ssl")] /// Use custom `SslConnector` instance. - pub fn ssl(mut self, connector: SslConnector) -> Self { - self.ssl = connector; + pub fn ssl(mut self, connector: OpensslConnector) -> Self { + self.ssl = SslConnector::Openssl(connector); + self + } + + #[cfg(feature = "rust-tls")] + pub fn rustls(mut self, connector: Arc) -> Self { + self.ssl = SslConnector::Rustls(connector); self } @@ -181,7 +213,7 @@ where self, ) -> impl Service + Clone { - #[cfg(not(feature = "ssl"))] + #[cfg(not(any(feature = "ssl", feature = "rust-tls")))] { let connector = TimeoutService::new( self.timeout, @@ -206,10 +238,16 @@ where ), } } - #[cfg(feature = "ssl")] + #[cfg(any(feature = "ssl", feature = "rust-tls"))] { const H2: &[u8] = b"h2"; + #[cfg(feature = "ssl")] use actix_connect::ssl::OpensslConnector; + #[cfg(feature = "rust-tls")] + use actix_connect::ssl::RustlsConnector; + use actix_service::boxed::service; + #[cfg(feature = "rust-tls")] + use rustls::Session; let ssl_service = TimeoutService::new( self.timeout, @@ -217,24 +255,46 @@ where srv.call(TcpConnect::new(msg.uri).set_addr(msg.addr)) }) .map_err(ConnectError::from) - .and_then( - OpensslConnector::service(self.ssl) - .map_err(ConnectError::from) - .map(|stream| { - let sock = stream.into_parts().0; - let h2 = sock - .get_ref() - .ssl() - .selected_alpn_protocol() - .map(|protos| protos.windows(2).any(|w| w == H2)) - .unwrap_or(false); - if h2 { - (sock, Protocol::Http2) - } else { - (sock, Protocol::Http1) - } - }), - ), + .and_then(match self.ssl { + #[cfg(feature = "ssl")] + SslConnector::Openssl(ssl) => service( + OpensslConnector::service(ssl) + .map_err(ConnectError::from) + .map(|stream| { + let sock = stream.into_parts().0; + let h2 = sock + .get_ref() + .ssl() + .selected_alpn_protocol() + .map(|protos| protos.windows(2).any(|w| w == H2)) + .unwrap_or(false); + if h2 { + (Box::new(sock) as Box, Protocol::Http2) + } else { + (Box::new(sock) as Box, Protocol::Http1) + } + }), + ), + #[cfg(feature = "rust-tls")] + SslConnector::Rustls(ssl) => service( + RustlsConnector::service(ssl) + .map_err(ConnectError::from) + .map(|stream| { + let sock = stream.into_parts().0; + let h2 = sock + .get_ref() + .1 + .get_alpn_protocol() + .map(|protos| protos.windows(2).any(|w| w == H2)) + .unwrap_or(false); + if h2 { + (Box::new(sock) as Box, Protocol::Http2) + } else { + (Box::new(sock) as Box, Protocol::Http1) + } + }), + ), + }), ) .map_err(|e| match e { TimeoutError::Service(e) => e, @@ -274,7 +334,7 @@ where } } -#[cfg(not(feature = "ssl"))] +#[cfg(not(any(feature = "ssl", feature = "rust-tls")))] mod connect_impl { use futures::future::{err, Either, FutureResult}; use futures::Poll; @@ -336,7 +396,7 @@ mod connect_impl { } } -#[cfg(feature = "ssl")] +#[cfg(any(feature = "ssl", feature = "rust-tls"))] mod connect_impl { use std::marker::PhantomData; diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 8dedf72f5..24a187392 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -305,10 +305,12 @@ pub(crate) struct Inner { limit: usize, acquired: usize, available: HashMap>>, - waiters: Slab<( - Connect, - oneshot::Sender, ConnectError>>, - )>, + waiters: Slab< + Option<( + Connect, + oneshot::Sender, ConnectError>>, + )>, + >, waiters_queue: IndexSet<(Key, usize)>, task: Option, } @@ -346,7 +348,7 @@ where let key: Key = connect.uri.authority_part().unwrap().clone().into(); let entry = self.waiters.vacant_entry(); let token = entry.key(); - entry.insert((connect, tx)); + entry.insert(Some((connect, tx))); assert!(self.waiters_queue.insert((key, token))); (rx, token, self.task.is_some()) @@ -427,7 +429,9 @@ where fn check_availibility(&self) { if !self.waiters_queue.is_empty() && self.acquired < self.limit { - self.task.as_ref().map(|t| t.notify()); + if let Some(t) = self.task.as_ref() { + t.notify() + } } } } @@ -497,10 +501,14 @@ where break; } }; + if inner.waiters.get(token).unwrap().is_none() { + continue; + } + match inner.acquire(&key) { Acquire::NotAvailable => break, Acquire::Acquired(io, created) => { - let (_, tx) = inner.waiters.remove(token); + let tx = inner.waiters.get_mut(token).unwrap().take().unwrap().1; if let Err(conn) = tx.send(Ok(IoConnection::new( io, created, @@ -511,7 +519,8 @@ where } } Acquire::Available => { - let (connect, tx) = inner.waiters.remove(token); + let (connect, tx) = + inner.waiters.get_mut(token).unwrap().take().unwrap(); OpenWaitingConnection::spawn( key.clone(), tx, @@ -581,6 +590,29 @@ where type Error = (); fn poll(&mut self) -> Poll { + if let Some(ref mut h2) = self.h2 { + return match h2.poll() { + Ok(Async::Ready((snd, connection))) => { + tokio_current_thread::spawn(connection.map_err(|_| ())); + let rx = self.rx.take().unwrap(); + let _ = rx.send(Ok(IoConnection::new( + ConnectionType::H2(snd), + Instant::now(), + Some(Acquired(self.key.clone(), self.inner.take())), + ))); + Ok(Async::Ready(())) + } + Ok(Async::NotReady) => Ok(Async::NotReady), + Err(err) => { + let _ = self.inner.take(); + if let Some(rx) = self.rx.take() { + let _ = rx.send(Err(ConnectError::H2(err))); + } + Err(()) + } + }; + } + match self.fut.poll() { Err(err) => { let _ = self.inner.take(); diff --git a/actix-http/src/cloneable.rs b/actix-http/src/cloneable.rs new file mode 100644 index 000000000..ffc1d0611 --- /dev/null +++ b/actix-http/src/cloneable.rs @@ -0,0 +1,42 @@ +use std::cell::UnsafeCell; +use std::rc::Rc; + +use actix_service::Service; +use futures::Poll; + +#[doc(hidden)] +/// Service that allows to turn non-clone service to a service with `Clone` impl +pub(crate) struct CloneableService(Rc>); + +impl CloneableService { + pub(crate) fn new(service: T) -> Self + where + T: Service, + { + Self(Rc::new(UnsafeCell::new(service))) + } +} + +impl Clone for CloneableService { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl Service for CloneableService +where + T: Service, +{ + type Request = T::Request; + type Response = T::Response; + type Error = T::Error; + type Future = T::Future; + + fn poll_ready(&mut self) -> Poll<(), Self::Error> { + unsafe { &mut *self.0.as_ref().get() }.poll_ready() + } + + fn call(&mut self, req: T::Request) -> Self::Future { + unsafe { &mut *self.0.as_ref().get() }.call(req) + } +} diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index aba50a814..bdfecef30 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -162,16 +162,21 @@ impl ServiceConfig { pub fn set_date(&self, dst: &mut BytesMut) { let mut buf: [u8; 39] = [0; 39]; buf[..6].copy_from_slice(b"date: "); - buf[6..35].copy_from_slice(&self.0.timer.date().bytes); + self.0 + .timer + .set_date(|date| buf[6..35].copy_from_slice(&date.bytes)); buf[35..].copy_from_slice(b"\r\n\r\n"); dst.extend_from_slice(&buf); } pub(crate) fn set_date_header(&self, dst: &mut BytesMut) { - dst.extend_from_slice(&self.0.timer.date().bytes); + self.0 + .timer + .set_date(|date| dst.extend_from_slice(&date.bytes)); } } +#[derive(Copy, Clone)] struct Date { bytes: [u8; DATE_VALUE_LENGTH], pos: usize, @@ -215,10 +220,6 @@ impl DateServiceInner { } } - fn get_ref(&self) -> &Option<(Date, Instant)> { - unsafe { &*self.current.get() } - } - fn reset(&self) { unsafe { (&mut *self.current.get()).take() }; } @@ -236,7 +237,7 @@ impl DateService { } fn check_date(&self) { - if self.0.get_ref().is_none() { + if unsafe { (&*self.0.current.get()).is_none() } { self.0.update(); // periodic date update @@ -252,14 +253,12 @@ impl DateService { fn now(&self) -> Instant { self.check_date(); - self.0.get_ref().as_ref().unwrap().1 + unsafe { (&*self.0.current.get()).as_ref().unwrap().1 } } - fn date(&self) -> &Date { + fn set_date(&self, mut f: F) { self.check_date(); - - let item = self.0.get_ref().as_ref().unwrap(); - &item.0 + f(&unsafe { (&*self.0.current.get()).as_ref().unwrap().0 }) } } diff --git a/actix-http/src/cookie/mod.rs b/actix-http/src/cookie/mod.rs index ddcb12bbf..db8211427 100644 --- a/actix-http/src/cookie/mod.rs +++ b/actix-http/src/cookie/mod.rs @@ -66,7 +66,7 @@ use std::fmt; use std::str::FromStr; use chrono::Duration; -use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; +use percent_encoding::{percent_encode, AsciiSet, CONTROLS}; use time::Tm; pub use self::builder::CookieBuilder; @@ -75,6 +75,25 @@ pub use self::jar::{CookieJar, Delta, Iter}; use self::parse::parse_cookie; pub use self::parse::ParseError; +/// https://url.spec.whatwg.org/#fragment-percent-encode-set +const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`'); + +/// https://url.spec.whatwg.org/#path-percent-encode-set +const PATH: &AsciiSet = &FRAGMENT.add(b'#').add(b'?').add(b'{').add(b'}'); + +/// https://url.spec.whatwg.org/#userinfo-percent-encode-set +pub const USERINFO: &AsciiSet = &PATH + .add(b'/') + .add(b':') + .add(b';') + .add(b'=') + .add(b'@') + .add(b'[') + .add(b'\\') + .add(b']') + .add(b'^') + .add(b'|'); + #[derive(Debug, Clone)] enum CookieStr { /// An string derived from indexes (start, end). @@ -104,6 +123,7 @@ impl CookieStr { } } + #[allow(clippy::ptr_arg)] fn to_raw_str<'s, 'c: 's>(&'s self, string: &'s Cow<'c, str>) -> Option<&'c str> { match *self { CookieStr::Indexed(i, j) => match *string { @@ -909,8 +929,8 @@ pub struct EncodedCookie<'a, 'c: 'a>(&'a Cookie<'c>); impl<'a, 'c: 'a> fmt::Display for EncodedCookie<'a, 'c> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // Percent-encode the name and value. - let name = percent_encode(self.0.name().as_bytes(), USERINFO_ENCODE_SET); - let value = percent_encode(self.0.value().as_bytes(), USERINFO_ENCODE_SET); + let name = percent_encode(self.0.name().as_bytes(), USERINFO); + let value = percent_encode(self.0.value().as_bytes(), USERINFO); // Write out the name/value pair and the cookie's parameters. write!(f, "{}={}", name, value)?; diff --git a/actix-http/src/cookie/secure/key.rs b/actix-http/src/cookie/secure/key.rs index 4e74f6e78..39575c93f 100644 --- a/actix-http/src/cookie/secure/key.rs +++ b/actix-http/src/cookie/secure/key.rs @@ -6,8 +6,8 @@ use ring::rand::{SecureRandom, SystemRandom}; use super::private::KEY_LEN as PRIVATE_KEY_LEN; use super::signed::KEY_LEN as SIGNED_KEY_LEN; -static HKDF_DIGEST: &'static Algorithm = &SHA256; -const KEYS_INFO: &'static str = "COOKIE;SIGNED:HMAC-SHA256;PRIVATE:AEAD-AES-256-GCM"; +static HKDF_DIGEST: &Algorithm = &SHA256; +const KEYS_INFO: &str = "COOKIE;SIGNED:HMAC-SHA256;PRIVATE:AEAD-AES-256-GCM"; /// A cryptographic master key for use with `Signed` and/or `Private` jars. /// diff --git a/actix-http/src/cookie/secure/private.rs b/actix-http/src/cookie/secure/private.rs index e59743767..eb8e9beb1 100644 --- a/actix-http/src/cookie/secure/private.rs +++ b/actix-http/src/cookie/secure/private.rs @@ -10,7 +10,7 @@ use crate::cookie::{Cookie, CookieJar}; // Keep these in sync, and keep the key len synced with the `private` docs as // well as the `KEYS_INFO` const in secure::Key. -static ALGO: &'static Algorithm = &AES_256_GCM; +static ALGO: &Algorithm = &AES_256_GCM; const NONCE_LEN: usize = 12; pub const KEY_LEN: usize = 32; diff --git a/actix-http/src/cookie/secure/signed.rs b/actix-http/src/cookie/secure/signed.rs index 1b1799cf4..36a277cd5 100644 --- a/actix-http/src/cookie/secure/signed.rs +++ b/actix-http/src/cookie/secure/signed.rs @@ -6,7 +6,7 @@ use crate::cookie::{Cookie, CookieJar}; // Keep these in sync, and keep the key len synced with the `signed` docs as // well as the `KEYS_INFO` const in secure::Key. -static HMAC_DIGEST: &'static Algorithm = &SHA256; +static HMAC_DIGEST: &Algorithm = &SHA256; const BASE64_DIGEST_LEN: usize = 44; pub const KEY_LEN: usize = 32; diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index fa95d798a..58d8a2d9e 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -54,22 +54,24 @@ impl Encoder { }; if can_encode { - update_head(encoding, head); - head.no_chunking(false); - ResponseBody::Body(Encoder { - body, - eof: false, - fut: None, - encoder: ContentEncoder::encoder(encoding), - }) - } else { - ResponseBody::Body(Encoder { - body, - eof: false, - fut: None, - encoder: None, - }) + // Modify response body only if encoder is not None + if let Some(enc) = ContentEncoder::encoder(encoding) { + update_head(encoding, head); + head.no_chunking(false); + return ResponseBody::Body(Encoder { + body, + eof: false, + fut: None, + encoder: Some(enc), + }); + } } + ResponseBody::Body(Encoder { + body, + eof: false, + fut: None, + encoder: None, + }) } } diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 4913c3d9d..dcbc3cc93 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -1,4 +1,5 @@ //! Error and Result module +use std::any::TypeId; use std::cell::RefCell; use std::io::Write; use std::str::Utf8Error; @@ -43,14 +44,19 @@ pub type Result = result::Result; /// if you have access to an actix `Error` you can always get a /// `ResponseError` reference from it. pub struct Error { - cause: Box, + cause: Box, } impl Error { /// Returns the reference to the underlying `ResponseError`. - pub fn as_response_error(&self) -> &ResponseError { + pub fn as_response_error(&self) -> &dyn ResponseError { self.cause.as_ref() } + + /// Similar to `as_response_error` but downcasts. + pub fn as_error(&self) -> Option<&T> { + ResponseError::downcast_ref(self.cause.as_ref()) + } } /// Error that can be converted to `Response` @@ -73,6 +79,25 @@ pub trait ResponseError: fmt::Debug + fmt::Display { ); resp.set_body(Body::from(buf)) } + + #[doc(hidden)] + fn __private_get_type_id__(&self) -> TypeId + where + Self: 'static, + { + TypeId::of::() + } +} + +impl dyn ResponseError + 'static { + /// Downcasts a response error to a specific type. + pub fn downcast_ref(&self) -> Option<&T> { + if self.__private_get_type_id__() == TypeId::of::() { + unsafe { Some(&*(self as *const dyn ResponseError as *const T)) } + } else { + None + } + } } impl fmt::Display for Error { @@ -1044,6 +1069,16 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); } + #[test] + fn test_error_casting() { + let err = PayloadError::Overflow; + let resp_err: &ResponseError = &err; + let err = resp_err.downcast_ref::().unwrap(); + assert_eq!(err.to_string(), "A payload reached size limit."); + let not_err = resp_err.downcast_ref::(); + assert!(not_err.is_none()); + } + #[test] fn test_error_helpers() { let r: Response = ErrorBadRequest("err").into(); diff --git a/actix-http/src/extensions.rs b/actix-http/src/extensions.rs index 148e4c18e..c6266f56e 100644 --- a/actix-http/src/extensions.rs +++ b/actix-http/src/extensions.rs @@ -6,7 +6,7 @@ use hashbrown::HashMap; #[derive(Default)] /// A type map of request extensions. pub struct Extensions { - map: HashMap>, + map: HashMap>, } impl Extensions { @@ -35,14 +35,14 @@ impl Extensions { pub fn get(&self) -> Option<&T> { self.map .get(&TypeId::of::()) - .and_then(|boxed| (&**boxed as &(Any + 'static)).downcast_ref()) + .and_then(|boxed| (&**boxed as &(dyn Any + 'static)).downcast_ref()) } /// Get a mutable reference to a type previously inserted on this `Extensions`. pub fn get_mut(&mut self) -> Option<&mut T> { self.map .get_mut(&TypeId::of::()) - .and_then(|boxed| (&mut **boxed as &mut (Any + 'static)).downcast_mut()) + .and_then(|boxed| (&mut **boxed as &mut (dyn Any + 'static)).downcast_mut()) } /// Remove a type from this `Extensions`. @@ -50,7 +50,7 @@ impl Extensions { /// If a extension of this type existed, it will be returned. pub fn remove(&mut self) -> Option { self.map.remove(&TypeId::of::()).and_then(|boxed| { - (boxed as Box) + (boxed as Box) .downcast() .ok() .map(|boxed| *boxed) diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 12419d665..c7ef065a5 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -502,15 +502,15 @@ impl ChunkedState { fn read_size(rdr: &mut BytesMut, size: &mut u64) -> Poll { let radix = 16; match byte!(rdr) { - b @ b'0'...b'9' => { + b @ b'0'..=b'9' => { *size *= radix; *size += u64::from(b - b'0'); } - b @ b'a'...b'f' => { + b @ b'a'..=b'f' => { *size *= radix; *size += u64::from(b + 10 - b'a'); } - b @ b'A'...b'F' => { + b @ b'A'..=b'F' => { *size *= radix; *size += u64::from(b + 10 - b'A'); } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 91990d05c..5e9c0b53d 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -5,7 +5,6 @@ use std::{fmt, io, net}; use actix_codec::{Decoder, Encoder, Framed, FramedParts}; use actix_server_config::IoStream; use actix_service::Service; -use actix_utils::cloneable::CloneableService; use bitflags::bitflags; use bytes::{BufMut, BytesMut}; use futures::{Async, Future, Poll}; @@ -13,6 +12,7 @@ use log::{error, trace}; use tokio_timer::Delay; use crate::body::{Body, BodySize, MessageBody, ResponseBody}; +use crate::cloneable::CloneableService; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; use crate::error::{ParseError, PayloadError}; diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 192d1b598..89bf08e9b 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -5,11 +5,11 @@ use std::rc::Rc; use actix_codec::Framed; use actix_server_config::{Io, IoStream, ServerConfig as SrvConfig}; use actix_service::{IntoNewService, NewService, Service}; -use actix_utils::cloneable::CloneableService; use futures::future::{ok, FutureResult}; use futures::{try_ready, Async, Future, IntoFuture, Poll, Stream}; use crate::body::MessageBody; +use crate::cloneable::CloneableService; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::{DispatchError, Error, ParseError}; use crate::helpers::DataFactory; @@ -26,7 +26,7 @@ pub struct H1Service> { cfg: ServiceConfig, expect: X, upgrade: Option, - on_connect: Option Box>>, + on_connect: Option Box>>, _t: PhantomData<(T, P, B)>, } @@ -108,7 +108,7 @@ where /// Set on connect callback. pub(crate) fn on_connect( mut self, - f: Option Box>>, + f: Option Box>>, ) -> Self { self.on_connect = f; self @@ -174,7 +174,7 @@ where fut_upg: Option, expect: Option, upgrade: Option, - on_connect: Option Box>>, + on_connect: Option Box>>, cfg: Option, _t: PhantomData<(T, P, B)>, } @@ -233,7 +233,7 @@ pub struct H1ServiceHandler { srv: CloneableService, expect: CloneableService, upgrade: Option>, - on_connect: Option Box>>, + on_connect: Option Box>>, cfg: ServiceConfig, _t: PhantomData<(T, P, B)>, } @@ -254,12 +254,12 @@ where srv: S, expect: X, upgrade: Option, - on_connect: Option Box>>, + on_connect: Option Box>>, ) -> H1ServiceHandler { H1ServiceHandler { srv: CloneableService::new(srv), expect: CloneableService::new(expect), - upgrade: upgrade.map(|s| CloneableService::new(s)), + upgrade: upgrade.map(CloneableService::new), cfg, on_connect, _t: PhantomData, diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 48d32993d..2bd7940dd 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -6,7 +6,6 @@ use std::{fmt, mem, net}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_server_config::IoStream; use actix_service::Service; -use actix_utils::cloneable::CloneableService; use bitflags::bitflags; use bytes::{Bytes, BytesMut}; use futures::{try_ready, Async, Future, Poll, Sink, Stream}; @@ -20,6 +19,7 @@ use log::{debug, error, trace}; use tokio_timer::Delay; use crate::body::{Body, BodySize, MessageBody, ResponseBody}; +use crate::cloneable::CloneableService; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error, ParseError, PayloadError, ResponseError}; use crate::helpers::DataFactory; diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index efc400da1..e894cf660 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -5,7 +5,6 @@ use std::{io, net, rc}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_server_config::{Io, IoStream, ServerConfig as SrvConfig}; use actix_service::{IntoNewService, NewService, Service}; -use actix_utils::cloneable::CloneableService; use bytes::Bytes; use futures::future::{ok, FutureResult}; use futures::{try_ready, Async, Future, IntoFuture, Poll, Stream}; @@ -14,6 +13,7 @@ use h2::RecvStream; use log::error; use crate::body::MessageBody; +use crate::cloneable::CloneableService; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::{DispatchError, Error, ParseError, ResponseError}; use crate::helpers::DataFactory; @@ -27,7 +27,7 @@ use super::dispatcher::Dispatcher; pub struct H2Service { srv: S, cfg: ServiceConfig, - on_connect: Option Box>>, + on_connect: Option Box>>, _t: PhantomData<(T, P, B)>, } @@ -64,7 +64,7 @@ where /// Set on connect callback. pub(crate) fn on_connect( mut self, - f: Option Box>>, + f: Option Box>>, ) -> Self { self.on_connect = f; self @@ -102,7 +102,7 @@ where pub struct H2ServiceResponse { fut: ::Future, cfg: Option, - on_connect: Option Box>>, + on_connect: Option Box>>, _t: PhantomData<(T, P, B)>, } @@ -132,7 +132,7 @@ where pub struct H2ServiceHandler { srv: CloneableService, cfg: ServiceConfig, - on_connect: Option Box>>, + on_connect: Option Box>>, _t: PhantomData<(T, P, B)>, } @@ -146,7 +146,7 @@ where { fn new( cfg: ServiceConfig, - on_connect: Option Box>>, + on_connect: Option Box>>, srv: S, ) -> H2ServiceHandler { H2ServiceHandler { @@ -256,7 +256,7 @@ where on_connect.take(), config.take().unwrap(), None, - peer_addr.clone(), + *peer_addr, )); self.poll() } diff --git a/actix-http/src/header/common/content_disposition.rs b/actix-http/src/header/common/content_disposition.rs index badf307a0..14fcc3517 100644 --- a/actix-http/src/header/common/content_disposition.rs +++ b/actix-http/src/header/common/content_disposition.rs @@ -70,6 +70,7 @@ impl<'a> From<&'a str> for DispositionType { /// assert_eq!(param.as_filename().unwrap(), "sample.txt"); /// ``` #[derive(Clone, Debug, PartialEq)] +#[allow(clippy::large_enum_variant)] pub enum DispositionParam { /// For [`DispositionType::FormData`] (i.e. *multipart/form-data*), the name of an field from /// the form. @@ -719,8 +720,10 @@ mod tests { }; assert_eq!(a, b); - let a = - HeaderValue::from_str("form-data; name=upload; filename=\"余固知謇謇之為患兮,忍而不能舍也.pptx\"").unwrap(); + let a = HeaderValue::from_str( + "form-data; name=upload; filename=\"余固知謇謇之為患兮,忍而不能舍也.pptx\"", + ) + .unwrap(); let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); let b = ContentDisposition { disposition: DispositionType::FormData, diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index 694aed02a..f2f1ba51c 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -9,12 +9,12 @@ use http::HttpTryFrom; /// `HeaderMap` is an multimap of [`HeaderName`] to values. /// /// [`HeaderName`]: struct.HeaderName.html -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct HeaderMap { pub(crate) inner: HashMap, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub(crate) enum Value { One(HeaderValue), Multi(Vec), diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 620183476..37cf94508 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -6,6 +6,7 @@ use std::{fmt, str::FromStr}; use bytes::{Bytes, BytesMut}; use http::Error as HttpError; use mime::Mime; +use percent_encoding::{AsciiSet, CONTROLS}; pub use http::header::*; @@ -361,10 +362,8 @@ pub fn parse_extended_value( impl fmt::Display for ExtendedValue { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let encoded_value = percent_encoding::percent_encode( - &self.value[..], - self::percent_encoding_http::HTTP_VALUE, - ); + let encoded_value = + percent_encoding::percent_encode(&self.value[..], HTTP_VALUE); if let Some(ref lang) = self.language_tag { write!(f, "{}'{}'{}", self.charset, lang, encoded_value) } else { @@ -378,8 +377,7 @@ impl fmt::Display for ExtendedValue { /// /// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result { - let encoded = - percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE); + let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE); fmt::Display::fmt(&encoded, f) } @@ -394,20 +392,29 @@ impl From for HeaderMap { } } -mod percent_encoding_http { - use percent_encoding::{self, define_encode_set}; - - // internal module because macro is hard-coded to make a public item - // but we don't want to public export this item - define_encode_set! { - // This encode set is used for HTTP header values and is defined at - // https://tools.ietf.org/html/rfc5987#section-3.2 - pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | { - ' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?', - '[', '\\', ']', '{', '}' - } - } -} +// This encode set is used for HTTP header values and is defined at +// https://tools.ietf.org/html/rfc5987#section-3.2 +pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS + .add(b' ') + .add(b'"') + .add(b'%') + .add(b'\'') + .add(b'(') + .add(b')') + .add(b'*') + .add(b',') + .add(b'/') + .add(b':') + .add(b';') + .add(b'<') + .add(b'-') + .add(b'>') + .add(b'?') + .add(b'[') + .add(b'\\') + .add(b']') + .add(b'{') + .add(b'}'); #[cfg(test)] mod tests { diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index ac085eaea..6b8874b23 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -1,8 +1,10 @@ //! Basic http primitives for actix-net framework. #![allow( clippy::type_complexity, + clippy::too_many_arguments, clippy::new_without_default, - clippy::borrow_interior_mutable_const + clippy::borrow_interior_mutable_const, + clippy::write_with_newline )] #[macro_use] @@ -11,6 +13,7 @@ extern crate log; pub mod body; mod builder; pub mod client; +mod cloneable; mod config; pub mod encoding; mod extensions; diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index f3c01a12b..cf23a401c 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -385,6 +385,7 @@ impl Drop for BoxedResponseHead { pub struct MessagePool(RefCell>>); #[doc(hidden)] +#[allow(clippy::vec_box)] /// Request's objects pool pub struct BoxedResponsePool(RefCell>>); diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index ce986a472..124bf5f92 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -52,6 +52,9 @@ impl Response { #[inline] pub fn from_error(error: Error) -> Response { let mut resp = error.as_response_error().render_response(); + if resp.head.status == StatusCode::INTERNAL_SERVER_ERROR { + error!("Internal Server Error: {:?}", error); + } resp.error = Some(error); resp } diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 1ac018803..09b8077b3 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -6,13 +6,13 @@ use actix_server_config::{ Io as ServerIo, IoStream, Protocol, ServerConfig as SrvConfig, }; use actix_service::{IntoNewService, NewService, Service}; -use actix_utils::cloneable::CloneableService; use bytes::{Buf, BufMut, Bytes, BytesMut}; use futures::{try_ready, Async, Future, IntoFuture, Poll}; use h2::server::{self, Handshake}; use crate::body::MessageBody; use crate::builder::HttpServiceBuilder; +use crate::cloneable::CloneableService; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::{DispatchError, Error}; use crate::helpers::DataFactory; @@ -26,7 +26,7 @@ pub struct HttpService, - on_connect: Option Box>>, + on_connect: Option Box>>, _t: PhantomData<(T, P, B)>, } @@ -140,7 +140,7 @@ where /// Set on connect callback. pub(crate) fn on_connect( mut self, - f: Option Box>>, + f: Option Box>>, ) -> Self { self.on_connect = f; self @@ -196,7 +196,7 @@ pub struct HttpServiceResponse, expect: Option, upgrade: Option, - on_connect: Option Box>>, + on_connect: Option Box>>, cfg: Option, _t: PhantomData<(T, P, B)>, } @@ -257,7 +257,7 @@ pub struct HttpServiceHandler { expect: CloneableService, upgrade: Option>, cfg: ServiceConfig, - on_connect: Option Box>>, + on_connect: Option Box>>, _t: PhantomData<(T, P, B, X)>, } @@ -278,14 +278,14 @@ where srv: S, expect: X, upgrade: Option, - on_connect: Option Box>>, + on_connect: Option Box>>, ) -> HttpServiceHandler { HttpServiceHandler { cfg, on_connect, srv: CloneableService::new(srv), expect: CloneableService::new(expect), - upgrade: upgrade.map(|s| CloneableService::new(s)), + upgrade: upgrade.map(CloneableService::new), _t: PhantomData, } } @@ -466,16 +466,18 @@ where State::Unknown(ref mut data) => { if let Some(ref mut item) = data { loop { - unsafe { - let b = item.1.bytes_mut(); - let n = try_ready!(item.0.poll_read(b)); - if n == 0 { - return Ok(Async::Ready(())); - } - item.1.advance_mut(n); - if item.1.len() >= HTTP2_PREFACE.len() { - break; - } + // Safety - we only write to the returned slice. + let b = unsafe { item.1.bytes_mut() }; + let n = try_ready!(item.0.poll_read(b)); + if n == 0 { + return Ok(Async::Ready(())); + } + // Safety - we know that 'n' bytes have + // been initialized via the contract of + // 'poll_read' + unsafe { item.1.advance_mut(n) }; + if item.1.len() >= HTTP2_PREFACE.len() { + break; } } } else { diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index b4344a676..ce81a54d5 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -9,9 +9,9 @@ use bytes::{Buf, Bytes, BytesMut}; use futures::{Async, Poll}; use http::header::{self, HeaderName, HeaderValue}; use http::{HttpTryFrom, Method, Uri, Version}; -use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; +use percent_encoding::percent_encode; -use crate::cookie::{Cookie, CookieJar}; +use crate::cookie::{Cookie, CookieJar, USERINFO}; use crate::header::HeaderMap; use crate::header::{Header, IntoHeaderValue}; use crate::payload::Payload; @@ -166,8 +166,8 @@ impl TestRequest { let mut cookie = String::new(); for c in inner.cookies.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let name = percent_encode(c.name().as_bytes(), USERINFO); + let value = percent_encode(c.value().as_bytes(), USERINFO); let _ = write!(&mut cookie, "; {}={}", name, value); } if !cookie.is_empty() { diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs index 652746b89..46e9f36db 100644 --- a/actix-http/src/ws/frame.rs +++ b/actix-http/src/ws/frame.rs @@ -1,4 +1,5 @@ -use byteorder::{ByteOrder, LittleEndian, NetworkEndian}; +use std::convert::TryFrom; + use bytes::{BufMut, Bytes, BytesMut}; use log::debug; use rand; @@ -48,14 +49,16 @@ impl Parser { if chunk_len < 4 { return Ok(None); } - let len = NetworkEndian::read_uint(&src[idx..], 2) as usize; + let len = usize::from(u16::from_be_bytes( + TryFrom::try_from(&src[idx..idx + 2]).unwrap(), + )); idx += 2; len } else if len == 127 { if chunk_len < 10 { return Ok(None); } - let len = NetworkEndian::read_uint(&src[idx..], 8); + let len = u64::from_be_bytes(TryFrom::try_from(&src[idx..idx + 8]).unwrap()); if len > max_size as u64 { return Err(ProtocolError::Overflow); } @@ -75,10 +78,10 @@ impl Parser { return Ok(None); } - let mask: &[u8] = &src[idx..idx + 4]; - let mask_u32 = LittleEndian::read_u32(mask); + let mask = + u32::from_le_bytes(TryFrom::try_from(&src[idx..idx + 4]).unwrap()); idx += 4; - Some(mask_u32) + Some(mask) } else { None }; @@ -137,7 +140,7 @@ impl Parser { /// Parse the payload of a close frame. pub fn parse_close_payload(payload: &[u8]) -> Option { if payload.len() >= 2 { - let raw_code = NetworkEndian::read_u16(payload); + let raw_code = u16::from_be_bytes(TryFrom::try_from(&payload[..2]).unwrap()); let code = CloseCode::from(raw_code); let description = if payload.len() > 2 { Some(String::from_utf8_lossy(&payload[2..]).into()) @@ -201,10 +204,7 @@ impl Parser { let payload = match reason { None => Vec::new(), Some(reason) => { - let mut code_bytes = [0; 2]; - NetworkEndian::write_u16(&mut code_bytes, reason.code.into()); - - let mut payload = Vec::from(&code_bytes[..]); + let mut payload = Into::::into(reason.code).to_be_bytes().to_vec(); if let Some(description) = reason.description { payload.extend(description.as_bytes()); } diff --git a/actix-http/src/ws/mask.rs b/actix-http/src/ws/mask.rs index 157375417..9f7304039 100644 --- a/actix-http/src/ws/mask.rs +++ b/actix-http/src/ws/mask.rs @@ -105,7 +105,6 @@ fn align_buf(buf: &mut [u8]) -> (ShortSlice, &mut [u64], ShortSlice) { #[cfg(test)] mod tests { use super::apply_mask; - use byteorder::{ByteOrder, LittleEndian}; /// A safe unoptimized mask application. fn apply_mask_fallback(buf: &mut [u8], mask: &[u8; 4]) { @@ -117,7 +116,7 @@ mod tests { #[test] fn test_apply_mask() { let mask = [0x6d, 0xb6, 0xb2, 0x80]; - let mask_u32: u32 = LittleEndian::read_u32(&mask); + let mask_u32 = u32::from_le_bytes(mask); let unmasked = vec![ 0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs index eef874741..e14651a56 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -47,10 +47,7 @@ impl Into for OpCode { Ping => 9, Pong => 10, Bad => { - debug_assert!( - false, - "Attempted to convert invalid opcode to u8. This is a bug." - ); + log::error!("Attempted to convert invalid opcode to u8. This is a bug."); 8 // if this somehow happens, a close frame will help us tear down quickly } } @@ -206,7 +203,7 @@ impl> From<(CloseCode, T)> for CloseReason { } } -static WS_GUID: &'static str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; +static WS_GUID: &str = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // TODO: hash is always same size, we dont need String pub fn hash_key(key: &[u8]) -> String { diff --git a/actix-http/tests/cert.pem b/actix-http/tests/cert.pem deleted file mode 100644 index eafad5245..000000000 --- a/actix-http/tests/cert.pem +++ /dev/null @@ -1,20 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDPjCCAiYCCQCmkoCBehOyYTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJV -UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMRAwDgYDVQQKDAdDb21wYW55MQww -CgYDVQQLDANPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xOTAzMjky -MzE5MDlaFw0yMDAzMjgyMzE5MDlaMGExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD -QTELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBhbnkxDDAKBgNVBAsMA09yZzEY -MBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEA2uFoWm74qumqIIsBBf/rgP3ZtZw6dRQhVoYjIwYk00T1RLmmbt8r -YNh3lehmnrQlM/YC3dzcspucGqIfvs5FEReh/vgvsqY3lfy47Q1zzdtBrKq2ZBro -AuJUe4ayMYz/L/2jAtPtGDQqWyzhKv6x/oz6N/tKqlzoGbjSGSJUqKAV+Tuo4YI4 -xw3r/RJg3I3+ruXOgM65GBdja7usI/BhseEOp9VXotoTEItGmvG2RFZ4A7cN124x -giFl2IeYuC60jteZ+bnhPiqxcdzf3K4dnZlzrYma+FxwWbaow4wlpQcZVFdZ+K/Y -p/Bbm/FDKoUHnEdn/QAanTruRxSGdai0owIDAQABMA0GCSqGSIb3DQEBCwUAA4IB -AQAEWn3WAwAbd64f5jo2w4076s2qFiCJjPWoxO6bO75FgFFtw/NNev8pxGVw1ehg -HiTO6VRYolL5S/RKOchjA83AcDEBjgf8fKtvTmE9kxZSUIo4kIvv8V9ZM72gJhDN -8D/lXduTZ9JMwLOa1NUB8/I6CbaU3VzWkfodArKKpQF3M+LLgK03i12PD0KPQ5zv -bwaNoQo6cTmPNIdsVZETRvPqONiCUaQV57G74dGtjeirCh/DO5EYRtb1thgS7TGm -+Xg8OC5vZ6g0+xsrSqDBmWNtlI7S3bsL5C3LIEOOAL1ZJHRy2KvIGQ9ipb3XjnKS -N7/wlQduRyPH7oaD/o4xf5Gt ------END CERTIFICATE----- diff --git a/actix-http/tests/key.pem b/actix-http/tests/key.pem deleted file mode 100644 index 2afbf5497..000000000 --- a/actix-http/tests/key.pem +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEA2uFoWm74qumqIIsBBf/rgP3ZtZw6dRQhVoYjIwYk00T1RLmm -bt8rYNh3lehmnrQlM/YC3dzcspucGqIfvs5FEReh/vgvsqY3lfy47Q1zzdtBrKq2 -ZBroAuJUe4ayMYz/L/2jAtPtGDQqWyzhKv6x/oz6N/tKqlzoGbjSGSJUqKAV+Tuo -4YI4xw3r/RJg3I3+ruXOgM65GBdja7usI/BhseEOp9VXotoTEItGmvG2RFZ4A7cN -124xgiFl2IeYuC60jteZ+bnhPiqxcdzf3K4dnZlzrYma+FxwWbaow4wlpQcZVFdZ -+K/Yp/Bbm/FDKoUHnEdn/QAanTruRxSGdai0owIDAQABAoIBAQC4lzyQd+ITEbi+ -dTxJuQj94hgHB1htgKqU888SLI5F9nP6n67y9hb5N9WygSp6UWbGqYTFYwxlPMKr -22p2WjL5NTsTcm+XdIKQZW/3y06Mn4qFefsT9XURaZriCjihfU2BRaCCNARSUzwd -ZH4I6n9mM7KaH71aa7v6ZVoahE9tXPR6hM+SHQEySW4pWkEu98VpNNeIt6vP7WF9 -ONGbRa+0En4xgkuaxem2ZYa/GZFFtdQRkroNMhIRlfcPpkjy8DCc8E5RAkOzKC3O -lnxQwt+tdNNkGZz02ed2hx/YHPwFYy76y6hK5dxq74iKIaOc8U5t0HjB1zVfwiR0 -5mcxMncxAoGBAP+RivwXZ4FcxDY1uyziF+rwlC/1RujQFEWXIxsXCnba5DH3yKul -iKEIZPZtGhpsnQe367lcXcn7tztuoVjpAnk5L+hQY64tLwYbHeRcOMJ75C2y8FFC -NeG5sQsrk3IU1+jhGvrbE7UgOeAuWJmv0M1vPNB/+hGoZBW5W5uU1x89AoGBANtA -AhLtAcqQ/Qh2SpVhLljh7U85Be9tbCGua09clkYFzh3bcoBolXKH18Veww0TP0yF -0748CKw1A+ITbTVFV+vKvi4jzIxS7mr4wYtVCMssbttQN7y3l30IDxJwa9j3zTJx -IUn5OMMLv1JyitLId8HdOy1AdU3MkpJzdLyi1mFfAoGBAL3kL4fGABM/kU7SN6RO -zgS0AvdrYOeljBp1BRGg2hab58g02vamxVEZgqMTR7zwjPDqOIz+03U7wda4Ccyd -PUhDNJSB/r6xNepshZZi642eLlnCRguqjYyNw72QADtYv2B6uehAlXEUY8xtw0lW -OGgcSeyF2pH6M3tswWNlgT3lAoGAQ/BttBelOnP7NKgTLH7Usc4wjyAIaszpePZn -Ykw6dLBP0oixzoCZ7seRYSOgJWkVcEz39Db+KP60mVWTvbIjMHm+vOVy+Pip0JQM -xXQwKWU3ZNZSrzPkyWW55ejYQn9nIn5T5mxH3ojBXHcJ9Y8RLQ20zKzwrI77zE3i -mqGK9NkCgYEAq3dzHI0DGAJrR19sWl2LcqI19sj5a91tHx4cl1dJXS/iApOLLieU -zyUGkwfsqjHPAZ7GacICeBojIn/7KdPdlSKAbGVAU3d4qzvFS0qmWzObplBz3niT -Xnep2XLaVXqwlFJZZ6AHeKzYmMH0d0raiou2bpEUBqYizy2fi3NI4mA= ------END RSA PRIVATE KEY----- diff --git a/actix-http/tests/test_rustls_server.rs b/actix-http/tests/test_rustls_server.rs new file mode 100644 index 000000000..32b33fce8 --- /dev/null +++ b/actix-http/tests/test_rustls_server.rs @@ -0,0 +1,462 @@ +#![cfg(feature = "rust-tls")] +use actix_codec::{AsyncRead, AsyncWrite}; +use actix_http::error::PayloadError; +use actix_http::http::header::{self, HeaderName, HeaderValue}; +use actix_http::http::{Method, StatusCode, Version}; +use actix_http::{body, error, Error, HttpService, Request, Response}; +use actix_http_test::TestServer; +use actix_server::ssl::RustlsAcceptor; +use actix_server_config::ServerConfig; +use actix_service::{new_service_cfg, NewService}; + +use bytes::{Bytes, BytesMut}; +use futures::future::{self, ok, Future}; +use futures::stream::{once, Stream}; +use rustls::{ + internal::pemfile::{certs, pkcs8_private_keys}, + NoClientAuth, ServerConfig as RustlsServerConfig, +}; + +use std::fs::File; +use std::io::{BufReader, Result}; + +fn load_body(stream: S) -> impl Future +where + S: Stream, +{ + stream.fold(BytesMut::new(), move |mut body, chunk| { + body.extend_from_slice(&chunk); + Ok::<_, PayloadError>(body) + }) +} + +fn ssl_acceptor() -> Result> { + // load ssl keys + let mut config = RustlsServerConfig::new(NoClientAuth::new()); + let cert_file = &mut BufReader::new(File::open("../tests/cert.pem").unwrap()); + let key_file = &mut BufReader::new(File::open("../tests/key.pem").unwrap()); + let cert_chain = certs(cert_file).unwrap(); + let mut keys = pkcs8_private_keys(key_file).unwrap(); + config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); + + let protos = vec![b"h2".to_vec()]; + config.set_protocols(&protos); + Ok(RustlsAcceptor::new(config)) +} + +#[test] +fn test_h2() -> Result<()> { + let rustls = ssl_acceptor()?; + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| future::ok::<_, Error>(Response::Ok().finish())) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + Ok(()) +} + +#[test] +fn test_h2_1() -> Result<()> { + let rustls = ssl_acceptor()?; + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .finish(|req: Request| { + assert!(req.peer_addr().is_some()); + assert_eq!(req.version(), Version::HTTP_2); + future::ok::<_, Error>(Response::Ok().finish()) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + Ok(()) +} + +#[test] +fn test_h2_body() -> Result<()> { + let data = "HELLOWORLD".to_owned().repeat(64 * 1024); + let rustls = ssl_acceptor()?; + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .h2(|mut req: Request<_>| { + load_body(req.take_payload()) + .and_then(|body| Ok(Response::Ok().body(body))) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send_body(data.clone())).unwrap(); + assert!(response.status().is_success()); + + let body = srv.load_body(response).unwrap(); + assert_eq!(&body, data.as_bytes()); + Ok(()) +} + +#[test] +fn test_h2_content_length() { + let rustls = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .h2(|req: Request| { + let indx: usize = req.uri().path()[1..].parse().unwrap(); + let statuses = [ + StatusCode::NO_CONTENT, + StatusCode::CONTINUE, + StatusCode::SWITCHING_PROTOCOLS, + StatusCode::PROCESSING, + StatusCode::OK, + StatusCode::NOT_FOUND, + ]; + future::ok::<_, ()>(Response::new(statuses[indx])) + }) + .map_err(|_| ()), + ) + }); + + let header = HeaderName::from_static("content-length"); + let value = HeaderValue::from_static("0"); + + { + for i in 0..4 { + let req = srv + .request(Method::GET, srv.surl(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); + assert_eq!(response.headers().get(&header), None); + + let req = srv + .request(Method::HEAD, srv.surl(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); + assert_eq!(response.headers().get(&header), None); + } + + for i in 4..6 { + let req = srv + .request(Method::GET, srv.surl(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); + assert_eq!(response.headers().get(&header), Some(&value)); + } + } +} + +#[test] +fn test_h2_headers() { + let data = STR.repeat(10); + let data2 = data.clone(); + let rustls = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + let data = data.clone(); + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build().h2(move |_| { + let mut config = Response::Ok(); + for idx in 0..90 { + config.header( + format!("X-TEST-{}", idx).as_str(), + "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", + ); + } + future::ok::<_, ()>(config.body(data.clone())) + }).map_err(|_| ())) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.load_body(response).unwrap(); + assert_eq!(bytes, Bytes::from(data2)); +} + +const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World"; + +#[test] +fn test_h2_body2() { + let rustls = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| future::ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.load_body(response).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_h2_head_empty() { + let rustls = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.shead("/").send()).unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.version(), Version::HTTP_2); + + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + + // read response + let bytes = srv.load_body(response).unwrap(); + assert!(bytes.is_empty()); +} + +#[test] +fn test_h2_head_binary() { + let rustls = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| { + ok::<_, ()>( + Response::Ok().content_length(STR.len() as u64).body(STR), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.shead("/").send()).unwrap(); + assert!(response.status().is_success()); + + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + + // read response + let bytes = srv.load_body(response).unwrap(); + assert!(bytes.is_empty()); +} + +#[test] +fn test_h2_head_binary2() { + let rustls = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.shead("/").send()).unwrap(); + assert!(response.status().is_success()); + + { + let len = response + .headers() + .get(http::header::CONTENT_LENGTH) + .unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } +} + +#[test] +fn test_h2_body_length() { + let rustls = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| { + let body = once(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .body(body::SizedStream::new(STR.len() as u64, body)), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.load_body(response).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_h2_body_chunked_explicit() { + let rustls = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| { + let body = + once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .header(header::TRANSFER_ENCODING, "chunked") + .streaming(body), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); + + // read response + let bytes = srv.load_body(response).unwrap(); + + // decode + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_h2_response_http_error_handling() { + let rustls = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .h2(new_service_cfg(|_: &ServerConfig| { + Ok::<_, ()>(|_| { + let broken_header = Bytes::from_static(b"\0\0\0"); + ok::<_, ()>( + Response::Ok() + .header(http::header::CONTENT_TYPE, broken_header) + .body(STR), + ) + }) + })) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); + + // read response + let bytes = srv.load_body(response).unwrap(); + assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); +} + +#[test] +fn test_h2_service_error() { + let rustls = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + rustls + .clone() + .map_err(|e| println!("Rustls error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| Err::(error::ErrorBadRequest("error"))) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); + + // read response + let bytes = srv.load_body(response).unwrap(); + assert!(bytes.is_empty()); +} diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 4a679f4b9..a74fbb155 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -2,32 +2,19 @@ use std::io::{Read, Write}; use std::time::Duration; use std::{net, thread}; -use actix_codec::{AsyncRead, AsyncWrite}; use actix_http_test::TestServer; use actix_server_config::ServerConfig; use actix_service::{new_service_cfg, service_fn, NewService}; -use bytes::{Bytes, BytesMut}; +use bytes::Bytes; use futures::future::{self, ok, Future}; use futures::stream::{once, Stream}; use regex::Regex; use tokio_timer::sleep; -use actix_http::error::PayloadError; use actix_http::{ body, error, http, http::header, Error, HttpService, KeepAlive, Request, Response, }; -#[cfg(feature = "ssl")] -fn load_body(stream: S) -> impl Future -where - S: Stream, -{ - stream.fold(BytesMut::new(), move |mut body, chunk| { - body.extend_from_slice(&chunk); - Ok::<_, PayloadError>(body) - }) -} - #[test] fn test_h1() { let mut srv = TestServer::new(|| { @@ -64,101 +51,6 @@ fn test_h1_2() { assert!(response.status().is_success()); } -#[cfg(feature = "ssl")] -fn ssl_acceptor( -) -> std::io::Result> { - use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); - builder.set_alpn_select_callback(|_, protos| { - const H2: &[u8] = b"\x02h2"; - if protos.windows(3).any(|window| window == H2) { - Ok(b"h2") - } else { - Err(openssl::ssl::AlpnError::NOACK) - } - }); - builder.set_alpn_protos(b"\x02h2")?; - Ok(actix_server::ssl::OpensslAcceptor::new(builder.build())) -} - -#[cfg(feature = "ssl")] -#[test] -fn test_h2() -> std::io::Result<()> { - let openssl = ssl_acceptor()?; - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| future::ok::<_, Error>(Response::Ok().finish())) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - Ok(()) -} - -#[cfg(feature = "ssl")] -#[test] -fn test_h2_1() -> std::io::Result<()> { - let openssl = ssl_acceptor()?; - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .finish(|req: Request| { - assert!(req.peer_addr().is_some()); - assert_eq!(req.version(), http::Version::HTTP_2); - future::ok::<_, Error>(Response::Ok().finish()) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - Ok(()) -} - -#[cfg(feature = "ssl")] -#[test] -fn test_h2_body() -> std::io::Result<()> { - let data = "HELLOWORLD".to_owned().repeat(64 * 1024); - let openssl = ssl_acceptor()?; - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|mut req: Request<_>| { - load_body(req.take_payload()) - .and_then(|body| Ok(Response::Ok().body(body))) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send_body(data.clone())).unwrap(); - assert!(response.status().is_success()); - - let body = srv.load_body(response).unwrap(); - assert_eq!(&body, data.as_bytes()); - Ok(()) -} - #[test] fn test_expect_continue() { let srv = TestServer::new(|| { @@ -457,65 +349,6 @@ fn test_content_length() { } } -#[cfg(feature = "ssl")] -#[test] -fn test_h2_content_length() { - use actix_http::http::{ - header::{HeaderName, HeaderValue}, - StatusCode, - }; - let openssl = ssl_acceptor().unwrap(); - - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|req: Request| { - let indx: usize = req.uri().path()[1..].parse().unwrap(); - let statuses = [ - StatusCode::NO_CONTENT, - StatusCode::CONTINUE, - StatusCode::SWITCHING_PROTOCOLS, - StatusCode::PROCESSING, - StatusCode::OK, - StatusCode::NOT_FOUND, - ]; - future::ok::<_, ()>(Response::new(statuses[indx])) - }) - .map_err(|_| ()), - ) - }); - - let header = HeaderName::from_static("content-length"); - let value = HeaderValue::from_static("0"); - - { - for i in 0..4 { - let req = srv - .request(http::Method::GET, srv.surl(&format!("/{}", i))) - .send(); - let response = srv.block_on(req).unwrap(); - assert_eq!(response.headers().get(&header), None); - - let req = srv - .request(http::Method::HEAD, srv.surl(&format!("/{}", i))) - .send(); - let response = srv.block_on(req).unwrap(); - assert_eq!(response.headers().get(&header), None); - } - - for i in 4..6 { - let req = srv - .request(http::Method::GET, srv.surl(&format!("/{}", i))) - .send(); - let response = srv.block_on(req).unwrap(); - assert_eq!(response.headers().get(&header), Some(&value)); - } - } -} - #[test] fn test_h1_headers() { let data = STR.repeat(10); @@ -555,51 +388,6 @@ fn test_h1_headers() { assert_eq!(bytes, Bytes::from(data2)); } -#[cfg(feature = "ssl")] -#[test] -fn test_h2_headers() { - let data = STR.repeat(10); - let data2 = data.clone(); - let openssl = ssl_acceptor().unwrap(); - - let mut srv = TestServer::new(move || { - let data = data.clone(); - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build().h2(move |_| { - let mut builder = Response::Ok(); - for idx in 0..90 { - builder.header( - format!("X-TEST-{}", idx).as_str(), - "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ - TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", - ); - } - future::ok::<_, ()>(builder.body(data.clone())) - }).map_err(|_| ())) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from(data2)); -} - const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -636,29 +424,6 @@ fn test_h1_body() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[cfg(feature = "ssl")] -#[test] -fn test_h2_body2() { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| future::ok::<_, ()>(Response::Ok().body(STR))) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - #[test] fn test_h1_head_empty() { let mut srv = TestServer::new(|| { @@ -681,38 +446,6 @@ fn test_h1_head_empty() { assert!(bytes.is_empty()); } -#[cfg(feature = "ssl")] -#[test] -fn test_h2_head_empty() { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.shead("/").send()).unwrap(); - assert!(response.status().is_success()); - assert_eq!(response.version(), http::Version::HTTP_2); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = srv.load_body(response).unwrap(); - assert!(bytes.is_empty()); -} - #[test] fn test_h1_head_binary() { let mut srv = TestServer::new(|| { @@ -737,41 +470,6 @@ fn test_h1_head_binary() { assert!(bytes.is_empty()); } -#[cfg(feature = "ssl")] -#[test] -fn test_h2_head_binary() { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| { - ok::<_, ()>( - Response::Ok().content_length(STR.len() as u64).body(STR), - ) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.shead("/").send()).unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } - - // read response - let bytes = srv.load_body(response).unwrap(); - assert!(bytes.is_empty()); -} - #[test] fn test_h1_head_binary2() { let mut srv = TestServer::new(|| { @@ -790,33 +488,6 @@ fn test_h1_head_binary2() { } } -#[cfg(feature = "ssl")] -#[test] -fn test_h2_head_binary2() { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.shead("/").send()).unwrap(); - assert!(response.status().is_success()); - - { - let len = response - .headers() - .get(http::header::CONTENT_LENGTH) - .unwrap(); - assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); - } -} - #[test] fn test_h1_body_length() { let mut srv = TestServer::new(|| { @@ -836,35 +507,6 @@ fn test_h1_body_length() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[cfg(feature = "ssl")] -#[test] -fn test_h2_body_length() { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| { - let body = once(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok() - .body(body::SizedStream::new(STR.len() as u64, body)), - ) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - #[test] fn test_h1_body_chunked_explicit() { let mut srv = TestServer::new(|| { @@ -897,40 +539,6 @@ fn test_h1_body_chunked_explicit() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[cfg(feature = "ssl")] -#[test] -fn test_h2_body_chunked_explicit() { - let openssl = ssl_acceptor().unwrap(); - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| { - let body = - once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); - ok::<_, ()>( - Response::Ok() - .header(header::TRANSFER_ENCODING, "chunked") - .streaming(body), - ) - }) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert!(response.status().is_success()); - assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); - - // read response - let bytes = srv.load_body(response).unwrap(); - - // decode - assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -} - #[test] fn test_h1_body_chunked_implicit() { let mut srv = TestServer::new(|| { @@ -980,39 +588,6 @@ fn test_h1_response_http_error_handling() { assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); } -#[cfg(feature = "ssl")] -#[test] -fn test_h2_response_http_error_handling() { - let openssl = ssl_acceptor().unwrap(); - - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(new_service_cfg(|_: &ServerConfig| { - Ok::<_, ()>(|_| { - let broken_header = Bytes::from_static(b"\0\0\0"); - ok::<_, ()>( - Response::Ok() - .header(http::header::CONTENT_TYPE, broken_header) - .body(STR), - ) - }) - })) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); - - // read response - let bytes = srv.load_body(response).unwrap(); - assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); -} - #[test] fn test_h1_service_error() { let mut srv = TestServer::new(|| { @@ -1027,27 +602,3 @@ fn test_h1_service_error() { let bytes = srv.load_body(response).unwrap(); assert_eq!(bytes, Bytes::from_static(b"error")); } - -#[cfg(feature = "ssl")] -#[test] -fn test_h2_service_error() { - let openssl = ssl_acceptor().unwrap(); - - let mut srv = TestServer::new(move || { - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)) - .and_then( - HttpService::build() - .h2(|_| Err::(error::ErrorBadRequest("error"))) - .map_err(|_| ()), - ) - }); - - let response = srv.block_on(srv.sget("/").send()).unwrap(); - assert_eq!(response.status(), http::StatusCode::INTERNAL_SERVER_ERROR); - - // read response - let bytes = srv.load_body(response).unwrap(); - assert!(bytes.is_empty()); -} diff --git a/actix-http/tests/test_ssl_server.rs b/actix-http/tests/test_ssl_server.rs new file mode 100644 index 000000000..0b85f33f4 --- /dev/null +++ b/actix-http/tests/test_ssl_server.rs @@ -0,0 +1,455 @@ +#![cfg(feature = "ssl")] +use actix_codec::{AsyncRead, AsyncWrite}; +use actix_http::error::{ErrorBadRequest, PayloadError}; +use actix_http::http::header::{self, HeaderName, HeaderValue}; +use actix_http::http::{Method, StatusCode, Version}; +use actix_http::{body, Error, HttpService, Request, Response}; +use actix_http_test::TestServer; +use actix_server::ssl::OpensslAcceptor; +use actix_server_config::ServerConfig; +use actix_service::{new_service_cfg, NewService}; + +use bytes::{Bytes, BytesMut}; +use futures::future::{ok, Future}; +use futures::stream::{once, Stream}; +use openssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod}; +use std::io::Result; + +fn load_body(stream: S) -> impl Future +where + S: Stream, +{ + stream.fold(BytesMut::new(), move |mut body, chunk| { + body.extend_from_slice(&chunk); + Ok::<_, PayloadError>(body) + }) +} + +fn ssl_acceptor() -> Result> { + // load ssl keys + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); + builder + .set_private_key_file("../tests/key.pem", SslFiletype::PEM) + .unwrap(); + builder + .set_certificate_chain_file("../tests/cert.pem") + .unwrap(); + builder.set_alpn_select_callback(|_, protos| { + const H2: &[u8] = b"\x02h2"; + if protos.windows(3).any(|window| window == H2) { + Ok(b"h2") + } else { + Err(AlpnError::NOACK) + } + }); + builder.set_alpn_protos(b"\x02h2")?; + Ok(OpensslAcceptor::new(builder.build())) +} + +#[test] +fn test_h2() -> Result<()> { + let openssl = ssl_acceptor()?; + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| ok::<_, Error>(Response::Ok().finish())) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + Ok(()) +} + +#[test] +fn test_h2_1() -> Result<()> { + let openssl = ssl_acceptor()?; + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .finish(|req: Request| { + assert!(req.peer_addr().is_some()); + assert_eq!(req.version(), Version::HTTP_2); + ok::<_, Error>(Response::Ok().finish()) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + Ok(()) +} + +#[test] +fn test_h2_body() -> Result<()> { + let data = "HELLOWORLD".to_owned().repeat(64 * 1024); + let openssl = ssl_acceptor()?; + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|mut req: Request<_>| { + load_body(req.take_payload()) + .and_then(|body| Ok(Response::Ok().body(body))) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send_body(data.clone())).unwrap(); + assert!(response.status().is_success()); + + let body = srv.load_body(response).unwrap(); + assert_eq!(&body, data.as_bytes()); + Ok(()) +} + +#[test] +fn test_h2_content_length() { + let openssl = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|req: Request| { + let indx: usize = req.uri().path()[1..].parse().unwrap(); + let statuses = [ + StatusCode::NO_CONTENT, + StatusCode::CONTINUE, + StatusCode::SWITCHING_PROTOCOLS, + StatusCode::PROCESSING, + StatusCode::OK, + StatusCode::NOT_FOUND, + ]; + ok::<_, ()>(Response::new(statuses[indx])) + }) + .map_err(|_| ()), + ) + }); + + let header = HeaderName::from_static("content-length"); + let value = HeaderValue::from_static("0"); + + { + for i in 0..4 { + let req = srv + .request(Method::GET, srv.surl(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); + assert_eq!(response.headers().get(&header), None); + + let req = srv + .request(Method::HEAD, srv.surl(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); + assert_eq!(response.headers().get(&header), None); + } + + for i in 4..6 { + let req = srv + .request(Method::GET, srv.surl(&format!("/{}", i))) + .send(); + let response = srv.block_on(req).unwrap(); + assert_eq!(response.headers().get(&header), Some(&value)); + } + } +} + +#[test] +fn test_h2_headers() { + let data = STR.repeat(10); + let data2 = data.clone(); + let openssl = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + let data = data.clone(); + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build().h2(move |_| { + let mut builder = Response::Ok(); + for idx in 0..90 { + builder.header( + format!("X-TEST-{}", idx).as_str(), + "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ + TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", + ); + } + ok::<_, ()>(builder.body(data.clone())) + }).map_err(|_| ())) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.load_body(response).unwrap(); + assert_eq!(bytes, Bytes::from(data2)); +} + +const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World \ + Hello World Hello World Hello World Hello World Hello World"; + +#[test] +fn test_h2_body2() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.load_body(response).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_h2_head_empty() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.shead("/").send()).unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.version(), Version::HTTP_2); + + { + let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + + // read response + let bytes = srv.load_body(response).unwrap(); + assert!(bytes.is_empty()); +} + +#[test] +fn test_h2_head_binary() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| { + ok::<_, ()>( + Response::Ok().content_length(STR.len() as u64).body(STR), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.shead("/").send()).unwrap(); + assert!(response.status().is_success()); + + { + let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } + + // read response + let bytes = srv.load_body(response).unwrap(); + assert!(bytes.is_empty()); +} + +#[test] +fn test_h2_head_binary2() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.shead("/").send()).unwrap(); + assert!(response.status().is_success()); + + { + let len = response.headers().get(header::CONTENT_LENGTH).unwrap(); + assert_eq!(format!("{}", STR.len()), len.to_str().unwrap()); + } +} + +#[test] +fn test_h2_body_length() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| { + let body = once(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .body(body::SizedStream::new(STR.len() as u64, body)), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = srv.load_body(response).unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_h2_body_chunked_explicit() { + let openssl = ssl_acceptor().unwrap(); + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| { + let body = + once::<_, Error>(Ok(Bytes::from_static(STR.as_ref()))); + ok::<_, ()>( + Response::Ok() + .header(header::TRANSFER_ENCODING, "chunked") + .streaming(body), + ) + }) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert!(response.status().is_success()); + assert!(!response.headers().contains_key(header::TRANSFER_ENCODING)); + + // read response + let bytes = srv.load_body(response).unwrap(); + + // decode + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} + +#[test] +fn test_h2_response_http_error_handling() { + let openssl = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(new_service_cfg(|_: &ServerConfig| { + Ok::<_, ()>(|_| { + let broken_header = Bytes::from_static(b"\0\0\0"); + ok::<_, ()>( + Response::Ok() + .header(header::CONTENT_TYPE, broken_header) + .body(STR), + ) + }) + })) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); + + // read response + let bytes = srv.load_body(response).unwrap(); + assert_eq!(bytes, Bytes::from_static(b"failed to parse header value")); +} + +#[test] +fn test_h2_service_error() { + let openssl = ssl_acceptor().unwrap(); + + let mut srv = TestServer::new(move || { + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)) + .and_then( + HttpService::build() + .h2(|_| Err::(ErrorBadRequest("error"))) + .map_err(|_| ()), + ) + }); + + let response = srv.block_on(srv.sget("/").send()).unwrap(); + assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR); + + // read response + let bytes = srv.load_body(response).unwrap(); + assert!(bytes.is_empty()); +} diff --git a/actix-identity/src/lib.rs b/actix-identity/src/lib.rs index 6664df676..7216104eb 100644 --- a/actix-identity/src/lib.rs +++ b/actix-identity/src/lib.rs @@ -261,7 +261,7 @@ where type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = Box>; + type Future = Box>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.service.borrow_mut().poll_ready() @@ -283,7 +283,7 @@ where res.request().extensions_mut().remove::(); if let Some(id) = id { - return Either::A( + Either::A( backend .to_response(id.id, id.changed, &mut res) .into_future() @@ -291,7 +291,7 @@ where Ok(_) => Ok(res), Err(e) => Ok(res.error_response(e)), }), - ); + ) } else { Either::B(ok(res)) } @@ -333,8 +333,7 @@ struct CookieIdentityExtention { impl CookieIdentityInner { fn new(key: &[u8]) -> CookieIdentityInner { - let key_v2: Vec = - key.iter().chain([1, 0, 0, 0].iter()).map(|e| *e).collect(); + let key_v2: Vec = key.iter().chain([1, 0, 0, 0].iter()).cloned().collect(); CookieIdentityInner { key: Key::from_master(key), key_v2: Key::from_master(&key_v2), @@ -585,13 +584,14 @@ impl IdentityPolicy for CookieIdentityPolicy { ) } else if self.0.always_update_cookie() && id.is_some() { let visit_timestamp = SystemTime::now(); - let mut login_timestamp = None; - if self.0.requires_oob_data() { + let login_timestamp = if self.0.requires_oob_data() { let CookieIdentityExtention { login_timestamp: lt, } = res.request().extensions_mut().remove().unwrap(); - login_timestamp = lt; - } + lt + } else { + None + }; self.0.set_cookie( res, Some(CookieValue { diff --git a/actix-multipart/src/lib.rs b/actix-multipart/src/lib.rs index d8f365b2c..43eb048ca 100644 --- a/actix-multipart/src/lib.rs +++ b/actix-multipart/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(clippy::borrow_interior_mutable_const)] + mod error; mod extractor; mod server; diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index e1a5543d4..e2111bb7b 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -1,5 +1,5 @@ //! Multipart payload support -use std::cell::{Cell, RefCell, UnsafeCell}; +use std::cell::{Cell, RefCell, RefMut}; use std::marker::PhantomData; use std::rc::Rc; use std::{cmp, fmt}; @@ -112,7 +112,7 @@ impl Stream for Multipart { Err(err) } else if self.safety.current() { let mut inner = self.inner.as_mut().unwrap().borrow_mut(); - if let Some(payload) = inner.payload.get_mut(&self.safety) { + if let Some(mut payload) = inner.payload.get_mut(&self.safety) { payload.poll_stream()?; } inner.poll(&self.safety) @@ -265,12 +265,12 @@ impl InnerMultipart { } } - let headers = if let Some(payload) = self.payload.get_mut(safety) { + let headers = if let Some(mut payload) = self.payload.get_mut(safety) { match self.state { // read until first boundary InnerState::FirstBoundary => { match InnerMultipart::skip_until_boundary( - payload, + &mut *payload, &self.boundary, )? { Some(eof) => { @@ -286,7 +286,10 @@ impl InnerMultipart { } // read boundary InnerState::Boundary => { - match InnerMultipart::read_boundary(payload, &self.boundary)? { + match InnerMultipart::read_boundary( + &mut *payload, + &self.boundary, + )? { None => return Ok(Async::NotReady), Some(eof) => { if eof { @@ -303,7 +306,7 @@ impl InnerMultipart { // read field headers for next field if self.state == InnerState::Headers { - if let Some(headers) = InnerMultipart::read_headers(payload)? { + if let Some(headers) = InnerMultipart::read_headers(&mut *payload)? { self.state = InnerState::Boundary; headers } else { @@ -411,14 +414,15 @@ impl Stream for Field { fn poll(&mut self) -> Poll, Self::Error> { if self.safety.current() { let mut inner = self.inner.borrow_mut(); - if let Some(payload) = inner.payload.as_ref().unwrap().get_mut(&self.safety) + if let Some(mut payload) = + inner.payload.as_ref().unwrap().get_mut(&self.safety) { payload.poll_stream()?; } inner.poll(&self.safety) } else if !self.safety.is_clean() { - return Err(MultipartError::NotConsumed); + Err(MultipartError::NotConsumed) } else { Ok(Async::NotReady) } @@ -533,11 +537,9 @@ impl InnerField { let b_size = boundary.len() + b_len; if len < b_size { return Ok(Async::NotReady); - } else { - if &payload.buf[b_len..b_size] == boundary.as_bytes() { - // found boundary - return Ok(Async::Ready(None)); - } + } else if &payload.buf[b_len..b_size] == boundary.as_bytes() { + // found boundary + return Ok(Async::Ready(None)); } } } @@ -557,7 +559,7 @@ impl InnerField { // check boundary if (&payload.buf[cur..cur + 2] == b"\r\n" && &payload.buf[cur + 2..cur + 4] == b"--") - || (&payload.buf[cur..cur + 1] == b"\r" + || (&payload.buf[cur..=cur] == b"\r" && &payload.buf[cur + 1..cur + 3] == b"--") { if cur != 0 { @@ -584,12 +586,13 @@ impl InnerField { return Ok(Async::Ready(None)); } - let result = if let Some(payload) = self.payload.as_ref().unwrap().get_mut(s) { + let result = if let Some(mut payload) = self.payload.as_ref().unwrap().get_mut(s) + { if !self.eof { let res = if let Some(ref mut len) = self.length { - InnerField::read_len(payload, len)? + InnerField::read_len(&mut *payload, len)? } else { - InnerField::read_stream(payload, &self.boundary)? + InnerField::read_stream(&mut *payload, &self.boundary)? }; match res { @@ -620,7 +623,7 @@ impl InnerField { } struct PayloadRef { - payload: Rc>, + payload: Rc>, } impl PayloadRef { @@ -630,15 +633,12 @@ impl PayloadRef { } } - fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option<&'a mut PayloadBuffer> + fn get_mut<'a, 'b>(&'a self, s: &'b Safety) -> Option> where 'a: 'b, { - // Unsafe: Invariant is inforced by Safety Safety is used as ref counter, - // only top most ref can have mutable access to payload. if s.current() { - let payload: &mut PayloadBuffer = unsafe { &mut *self.payload.get() }; - Some(payload) + Some(self.payload.borrow_mut()) } else { None } diff --git a/actix-session/src/cookie.rs b/actix-session/src/cookie.rs index 8627ce4c8..192737780 100644 --- a/actix-session/src/cookie.rs +++ b/actix-session/src/cookie.rs @@ -309,7 +309,7 @@ where type Request = ServiceRequest; type Response = ServiceResponse; type Error = S::Error; - type Future = Box>; + type Future = Box>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.service.poll_ready() @@ -342,7 +342,7 @@ where } } (SessionStatus::Purged, _) => { - inner.remove_cookie(&mut res); + let _ = inner.remove_cookie(&mut res); res } _ => res, diff --git a/actix-session/src/lib.rs b/actix-session/src/lib.rs index 27ad2b876..2e9e51714 100644 --- a/actix-session/src/lib.rs +++ b/actix-session/src/lib.rs @@ -282,7 +282,7 @@ mod tests { #[test] fn purge_session() { - let mut req = test::TestRequest::default().to_srv_request(); + let req = test::TestRequest::default().to_srv_request(); let session = Session::get_session(&mut *req.extensions_mut()); assert_eq!(session.0.borrow().status, SessionStatus::Unchanged); session.purge(); @@ -291,7 +291,7 @@ mod tests { #[test] fn renew_session() { - let mut req = test::TestRequest::default().to_srv_request(); + let req = test::TestRequest::default().to_srv_request(); let session = Session::get_session(&mut *req.extensions_mut()); assert_eq!(session.0.borrow().status, SessionStatus::Unchanged); session.renew(); diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 115af87b9..0d1df7e55 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,5 +1,12 @@ # Changes +## [1.0.2] - 2019-07-20 + +* Add `ws::start_with_addr()`, returning the address of the created actor, along + with the `HttpResponse`. + +* Add support for specifying protocols on websocket handshake #835 + ## [1.0.1] - 2019-06-28 * Allow to use custom ws codec with `WebsocketContext` #925 diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index eb5fb1115..356109da5 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "1.0.1" +version = "1.0.2" authors = ["Nikolay Kim "] description = "Actix actors support for actix web framework." readme = "README.md" @@ -27,4 +27,4 @@ futures = "0.1.25" [dev-dependencies] env_logger = "0.6" -actix-http-test = { version = "0.2.0", features=["ssl"] } +actix-http-test = { version = "0.2.4", features=["ssl"] } diff --git a/actix-web-actors/src/lib.rs b/actix-web-actors/src/lib.rs index 5b64d7e0a..6360917cd 100644 --- a/actix-web-actors/src/lib.rs +++ b/actix-web-actors/src/lib.rs @@ -1,3 +1,4 @@ +#![allow(clippy::borrow_interior_mutable_const)] //! Actix actors integration for Actix web framework mod context; pub mod ws; diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index fece826dd..e25a7e6e4 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -35,15 +35,68 @@ where Ok(res.streaming(WebsocketContext::create(actor, stream))) } +/// Do websocket handshake and start ws actor. +/// +/// `req` is an HTTP Request that should be requesting a websocket protocol +/// change. `stream` should be a `Bytes` stream (such as +/// `actix_web::web::Payload`) that contains a stream of the body request. +/// +/// If there is a problem with the handshake, an error is returned. +/// +/// If successful, returns a pair where the first item is an address for the +/// created actor and the second item is the response that should be returned +/// from the websocket request. +pub fn start_with_addr( + actor: A, + req: &HttpRequest, + stream: T, +) -> Result<(Addr, HttpResponse), Error> +where + A: Actor> + StreamHandler, + T: Stream + 'static, +{ + let mut res = handshake(req)?; + let (addr, out_stream) = WebsocketContext::create_with_addr(actor, stream); + Ok((addr, res.streaming(out_stream))) +} + +/// Do websocket handshake and start ws actor. +/// +/// `protocols` is a sequence of known protocols. +pub fn start_with_protocols( + actor: A, + protocols: &[&str], + req: &HttpRequest, + stream: T, +) -> Result +where + A: Actor> + StreamHandler, + T: Stream + 'static, +{ + let mut res = handshake_with_protocols(req, protocols)?; + Ok(res.streaming(WebsocketContext::create(actor, stream))) +} + +/// Prepare `WebSocket` handshake response. +/// +/// This function returns handshake `HttpResponse`, ready to send to peer. +/// It does not perform any IO. +pub fn handshake(req: &HttpRequest) -> Result { + handshake_with_protocols(req, &[]) +} + /// Prepare `WebSocket` handshake response. /// /// This function returns handshake `HttpResponse`, ready to send to peer. /// It does not perform any IO. /// -// /// `protocols` is a sequence of known protocols. On successful handshake, -// /// the returned response headers contain the first protocol in this list -// /// which the server also knows. -pub fn handshake(req: &HttpRequest) -> Result { +/// `protocols` is a sequence of known protocols. On successful handshake, +/// the returned response headers contain the first protocol in this list +/// which the server also knows. +pub fn handshake_with_protocols( + req: &HttpRequest, + protocols: &[&str], +) -> Result { // WebSocket accepts only GET if *req.method() != Method::GET { return Err(HandshakeError::GetMethodRequired); @@ -92,11 +145,28 @@ pub fn handshake(req: &HttpRequest) -> Result(actor: A, stream: S) -> impl Stream + where + A: StreamHandler, + S: Stream + 'static, + { + let (_, stream) = WebsocketContext::create_with_addr(actor, stream); + stream + } + + #[inline] + /// Create a new Websocket context from a request and an actor. + /// + /// Returns a pair, where the first item is an addr for the created actor, + /// and the second item is a stream intended to be set as part of the + /// response via `HttpResponseBuilder::streaming()`. + pub fn create_with_addr( + actor: A, + stream: S, + ) -> (Addr, impl Stream) where A: StreamHandler, S: Stream + 'static, @@ -179,7 +267,9 @@ where }; ctx.add_stream(WsStream::new(stream, Codec::new())); - WebsocketContextFut::new(ctx, actor, mb, Codec::new()) + let addr = ctx.address(); + + (addr, WebsocketContextFut::new(ctx, actor, mb, Codec::new())) } #[inline] @@ -435,7 +525,7 @@ where } } Frame::Binary(data) => Message::Binary( - data.map(|b| b.freeze()).unwrap_or_else(|| Bytes::new()), + data.map(|b| b.freeze()).unwrap_or_else(Bytes::new), ), Frame::Ping(s) => Message::Ping(s), Frame::Pong(s) => Message::Pong(s), @@ -564,5 +654,87 @@ mod tests { StatusCode::SWITCHING_PROTOCOLS, handshake(&req).unwrap().finish().status() ); + + let req = TestRequest::default() + .header( + header::UPGRADE, + header::HeaderValue::from_static("websocket"), + ) + .header( + header::CONNECTION, + header::HeaderValue::from_static("upgrade"), + ) + .header( + header::SEC_WEBSOCKET_VERSION, + header::HeaderValue::from_static("13"), + ) + .header( + header::SEC_WEBSOCKET_KEY, + header::HeaderValue::from_static("13"), + ) + .header( + header::SEC_WEBSOCKET_PROTOCOL, + header::HeaderValue::from_static("graphql"), + ) + .to_http_request(); + + let protocols = ["graphql"]; + + assert_eq!( + StatusCode::SWITCHING_PROTOCOLS, + handshake_with_protocols(&req, &protocols) + .unwrap() + .finish() + .status() + ); + assert_eq!( + Some(&header::HeaderValue::from_static("graphql")), + handshake_with_protocols(&req, &protocols) + .unwrap() + .finish() + .headers() + .get(&header::SEC_WEBSOCKET_PROTOCOL) + ); + + let req = TestRequest::default() + .header( + header::UPGRADE, + header::HeaderValue::from_static("websocket"), + ) + .header( + header::CONNECTION, + header::HeaderValue::from_static("upgrade"), + ) + .header( + header::SEC_WEBSOCKET_VERSION, + header::HeaderValue::from_static("13"), + ) + .header( + header::SEC_WEBSOCKET_KEY, + header::HeaderValue::from_static("13"), + ) + .header( + header::SEC_WEBSOCKET_PROTOCOL, + header::HeaderValue::from_static("p1, p2, p3"), + ) + .to_http_request(); + + let protocols = vec!["p3", "p2"]; + + assert_eq!( + StatusCode::SWITCHING_PROTOCOLS, + handshake_with_protocols(&req, &protocols) + .unwrap() + .finish() + .status() + ); + assert_eq!( + Some(&header::HeaderValue::from_static("p2")), + handshake_with_protocols(&req, &protocols) + .unwrap() + .finish() + .headers() + .get(&header::SEC_WEBSOCKET_PROTOCOL) + ); } } diff --git a/actix-web-codegen/src/route.rs b/actix-web-codegen/src/route.rs index 268adecb0..5215f60c8 100644 --- a/actix-web-codegen/src/route.rs +++ b/actix-web-codegen/src/route.rs @@ -12,9 +12,9 @@ enum ResourceType { impl fmt::Display for ResourceType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - &ResourceType::Async => write!(f, "to_async"), - &ResourceType::Sync => write!(f, "to"), + match *self { + ResourceType::Async => write!(f, "to_async"), + ResourceType::Sync => write!(f, "to"), } } } @@ -34,16 +34,16 @@ pub enum GuardType { impl fmt::Display for GuardType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - &GuardType::Get => write!(f, "Get"), - &GuardType::Post => write!(f, "Post"), - &GuardType::Put => write!(f, "Put"), - &GuardType::Delete => write!(f, "Delete"), - &GuardType::Head => write!(f, "Head"), - &GuardType::Connect => write!(f, "Connect"), - &GuardType::Options => write!(f, "Options"), - &GuardType::Trace => write!(f, "Trace"), - &GuardType::Patch => write!(f, "Patch"), + match *self { + GuardType::Get => write!(f, "Get"), + GuardType::Post => write!(f, "Post"), + GuardType::Put => write!(f, "Put"), + GuardType::Delete => write!(f, "Delete"), + GuardType::Head => write!(f, "Head"), + GuardType::Connect => write!(f, "Connect"), + GuardType::Options => write!(f, "Options"), + GuardType::Trace => write!(f, "Trace"), + GuardType::Patch => write!(f, "Patch"), } } } @@ -92,37 +92,27 @@ impl actix_web::dev::HttpServiceFactory for {name} {{ fn guess_resource_type(typ: &syn::Type) -> ResourceType { let mut guess = ResourceType::Sync; - match typ { - syn::Type::ImplTrait(typ) => { - for bound in typ.bounds.iter() { - match bound { - syn::TypeParamBound::Trait(bound) => { - for bound in bound.path.segments.iter() { - if bound.ident == "Future" { - guess = ResourceType::Async; - break; - } else if bound.ident == "Responder" { - guess = ResourceType::Sync; - break; - } - } + if let syn::Type::ImplTrait(typ) = typ { + for bound in typ.bounds.iter() { + if let syn::TypeParamBound::Trait(bound) = bound { + for bound in bound.path.segments.iter() { + if bound.ident == "Future" { + guess = ResourceType::Async; + break; + } else if bound.ident == "Responder" { + guess = ResourceType::Sync; + break; } - _ => (), } } } - _ => (), } guess } impl Args { - pub fn new( - args: &Vec, - input: TokenStream, - guard: GuardType, - ) -> Self { + pub fn new(args: &[syn::NestedMeta], input: TokenStream, guard: GuardType) -> Self { if args.is_empty() { panic!( "invalid server definition, expected: #[{}(\"some path\")]", @@ -164,9 +154,10 @@ impl Args { ResourceType::Async } else { match ast.decl.output { - syn::ReturnType::Default => { - panic!("Function {} has no return type. Cannot be used as handler") - } + syn::ReturnType::Default => panic!( + "Function {} has no return type. Cannot be used as handler", + name + ), syn::ReturnType::Type(_, ref typ) => guess_resource_type(typ.as_ref()), } }; diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index f02b82f00..8ecc81dc1 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -142,7 +142,6 @@ fn test_body() { assert!(response.status().is_success()); assert_eq!(response.status(), http::StatusCode::NO_CONTENT); - let mut srv = TestServer::new(|| HttpService::new(App::new().service(auto_sync))); let request = srv.request(http::Method::GET, srv.url("/test")); let response = srv.block_on(request.send()).unwrap(); assert!(response.status().is_success()); diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 602f9a3b6..5edfc5e38 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,5 +1,21 @@ # Changes +## [0.2.4] - 2019-08-13 + +### Changed + +* Update percent-encoding to "2.1" + +* Update serde_urlencoded to "0.6.1" + + +## [0.2.3] - 2019-08-01 + +### Added + +* Add `rustls` support + + ## [0.2.2] - 2019-07-01 ### Changed diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 234662e97..7f42501e9 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "0.2.2" +version = "0.2.4" authors = ["Nikolay Kim "] description = "Actix http client." readme = "README.md" @@ -29,6 +29,9 @@ default = ["brotli", "flate2-zlib"] # openssl ssl = ["openssl", "actix-http/ssl"] +# rustls +rust-tls = ["rustls", "actix-http/rust-tls"] + # brotli encoding, requires c compiler brotli = ["actix-http/brotli"] @@ -41,20 +44,21 @@ flate2-rust = ["actix-http/flate2-rust"] [dependencies] actix-codec = "0.1.2" actix-service = "0.4.1" -actix-http = "0.2.4" +actix-http = "0.2.9" base64 = "0.10.1" bytes = "0.4" derive_more = "0.15.0" futures = "0.1.25" log =" 0.4" mime = "0.3" -percent-encoding = "1.0" +percent-encoding = "2.1" rand = "0.7" serde = "1.0" serde_json = "1.0" -serde_urlencoded = "0.5.3" +serde_urlencoded = "0.6.1" tokio-timer = "0.2.8" openssl = { version="0.10", optional = true } +rustls = { version = "0.15.2", optional = true } [dev-dependencies] actix-rt = "0.2.2" @@ -62,9 +66,11 @@ actix-web = { version = "1.0.0", features=["ssl"] } actix-http = { version = "0.2.4", features=["ssl"] } actix-http-test = { version = "0.2.0", features=["ssl"] } actix-utils = "0.4.1" -actix-server = { version = "0.5.1", features=["ssl"] } +actix-server = { version = "0.6.0", features=["ssl", "rust-tls"] } brotli2 = { version="0.3.2" } flate2 = { version="1.0.2" } env_logger = "0.6" rand = "0.7" tokio-tcp = "0.1" +webpki = "0.19" +rustls = { version = "0.15.2", features = ["dangerous_configuration"] } diff --git a/awc/src/builder.rs b/awc/src/builder.rs index a58265c5f..463f40303 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -21,6 +21,12 @@ pub struct ClientBuilder { max_redirects: usize, } +impl Default for ClientBuilder { + fn default() -> Self { + Self::new() + } +} + impl ClientBuilder { pub fn new() -> Self { ClientBuilder { diff --git a/awc/src/connect.rs b/awc/src/connect.rs index f9081063f..77ee73bd9 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -23,7 +23,7 @@ pub(crate) trait Connect { extra_headers: Option, body: Body, addr: Option, - ) -> Box>; + ) -> Box>; /// Send request, returns Response and Framed fn open_tunnel( @@ -32,7 +32,7 @@ pub(crate) trait Connect { extra_headers: Option, addr: Option, ) -> Box< - Future< + dyn Future< Item = (ResponseHead, Framed), Error = SendRequestError, >, @@ -54,7 +54,7 @@ where extra_headers: Option, body: Body, addr: Option, - ) -> Box> { + ) -> Box> { Box::new( self.0 // connect to the host @@ -75,7 +75,7 @@ where extra_headers: Option, addr: Option, ) -> Box< - Future< + dyn Future< Item = (ResponseHead, Framed), Error = SendRequestError, >, @@ -99,21 +99,21 @@ where } trait AsyncSocket { - fn as_read(&self) -> &AsyncRead; - fn as_read_mut(&mut self) -> &mut AsyncRead; - fn as_write(&mut self) -> &mut AsyncWrite; + fn as_read(&self) -> &dyn AsyncRead; + fn as_read_mut(&mut self) -> &mut dyn AsyncRead; + fn as_write(&mut self) -> &mut dyn AsyncWrite; } struct Socket(T); impl AsyncSocket for Socket { - fn as_read(&self) -> &AsyncRead { + fn as_read(&self) -> &dyn AsyncRead { &self.0 } - fn as_read_mut(&mut self) -> &mut AsyncRead { + fn as_read_mut(&mut self) -> &mut dyn AsyncRead { &mut self.0 } - fn as_write(&mut self) -> &mut AsyncWrite { + fn as_write(&mut self) -> &mut dyn AsyncWrite { &mut self.0 } } diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 9fbda8aa8..da63bbd93 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -1,7 +1,8 @@ +#![allow(clippy::borrow_interior_mutable_const)] //! An HTTP Client //! //! ```rust -//! # use futures::future::{Future, lazy}; +//! use futures::future::{lazy, Future}; //! use actix_rt::System; //! use awc::Client; //! diff --git a/awc/src/request.rs b/awc/src/request.rs index b28912e1a..483c3970e 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -7,13 +7,13 @@ use std::{fmt, net}; use bytes::{BufMut, Bytes, BytesMut}; use futures::future::{err, Either}; use futures::{Future, Stream}; -use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; +use percent_encoding::percent_encode; use serde::Serialize; use serde_json; use tokio_timer::Timeout; use actix_http::body::{Body, BodyStream}; -use actix_http::cookie::{Cookie, CookieJar}; +use actix_http::cookie::{Cookie, CookieJar, USERINFO}; use actix_http::encoding::Decoder; use actix_http::http::header::{self, ContentEncoding, Header, IntoHeaderValue}; use actix_http::http::{ @@ -185,9 +185,7 @@ impl ClientRequest { { match HeaderName::try_from(key) { Ok(key) => match value.try_into() { - Ok(value) => { - let _ = self.head.headers.append(key, value); - } + Ok(value) => self.head.headers.append(key, value), Err(e) => self.err = Some(e.into()), }, Err(e) => self.err = Some(e.into()), @@ -203,9 +201,7 @@ impl ClientRequest { { match HeaderName::try_from(key) { Ok(key) => match value.try_into() { - Ok(value) => { - let _ = self.head.headers.insert(key, value); - } + Ok(value) => self.head.headers.insert(key, value), Err(e) => self.err = Some(e.into()), }, Err(e) => self.err = Some(e.into()), @@ -223,9 +219,7 @@ impl ClientRequest { Ok(key) => { if !self.head.headers.contains_key(&key) { match value.try_into() { - Ok(value) => { - let _ = self.head.headers.insert(key, value); - } + Ok(value) => self.head.headers.insert(key, value), Err(e) => self.err = Some(e.into()), } } @@ -257,9 +251,7 @@ impl ClientRequest { HeaderValue: HttpTryFrom, { match HeaderValue::try_from(value) { - Ok(value) => { - let _ = self.head.headers.insert(header::CONTENT_TYPE, value); - } + Ok(value) => self.head.headers.insert(header::CONTENT_TYPE, value), Err(e) => self.err = Some(e.into()), } self @@ -321,7 +313,7 @@ impl ClientRequest { /// })); /// } /// ``` - pub fn cookie<'c>(mut self, cookie: Cookie<'c>) -> Self { + pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { if self.cookies.is_none() { let mut jar = CookieJar::new(); jar.add(cookie.into_owned()); @@ -397,8 +389,8 @@ impl ClientRequest { if let Some(ref mut jar) = self.cookies { let mut cookie = String::new(); for c in jar.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let name = percent_encode(c.name().as_bytes(), USERINFO); + let value = percent_encode(c.value().as_bytes(), USERINFO); let _ = write!(&mut cookie, "; {}={}", name, value); } self.head.headers.insert( diff --git a/awc/src/test.rs b/awc/src/test.rs index f2c513bab..641ecaa88 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -1,12 +1,12 @@ //! Test helpers for actix http client to use during testing. use std::fmt::Write as FmtWrite; -use actix_http::cookie::{Cookie, CookieJar}; +use actix_http::cookie::{Cookie, CookieJar, USERINFO}; use actix_http::http::header::{self, Header, HeaderValue, IntoHeaderValue}; use actix_http::http::{HeaderName, HttpTryFrom, StatusCode, Version}; use actix_http::{h1, Payload, ResponseHead}; use bytes::Bytes; -use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; +use percent_encoding::percent_encode; use crate::ClientResponse; @@ -68,7 +68,7 @@ impl TestResponse { } /// Set cookie for this response - pub fn cookie<'a>(mut self, cookie: Cookie<'a>) -> Self { + pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { self.cookies.add(cookie.into_owned()); self } @@ -87,8 +87,8 @@ impl TestResponse { let mut cookie = String::new(); for c in self.cookies.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let name = percent_encode(c.name().as_bytes(), USERINFO); + let value = percent_encode(c.value().as_bytes(), USERINFO); let _ = write!(&mut cookie, "; {}={}", name, value); } if !cookie.is_empty() { diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 0277110c1..dbfe9e7ab 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -8,9 +8,10 @@ use actix_codec::Framed; use actix_http::cookie::{Cookie, CookieJar}; use actix_http::{ws, Payload, RequestHead}; use futures::future::{err, Either, Future}; -use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; +use percent_encoding::percent_encode; use tokio_timer::Timeout; +use actix_http::cookie::USERINFO; pub use actix_http::ws::{CloseCode, CloseReason, Codec, Frame, Message}; use crate::connect::BoxedSocket; @@ -90,7 +91,7 @@ impl WebsocketsRequest { } /// Set a cookie - pub fn cookie<'c>(mut self, cookie: Cookie<'c>) -> Self { + pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { if self.cookies.is_none() { let mut jar = CookieJar::new(); jar.add(cookie.into_owned()); @@ -236,8 +237,8 @@ impl WebsocketsRequest { if let Some(ref mut jar) = self.cookies { let mut cookie = String::new(); for c in jar.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let name = percent_encode(c.name().as_bytes(), USERINFO); + let value = percent_encode(c.value().as_bytes(), USERINFO); let _ = write!(&mut cookie, "; {}={}", name, value); } self.head.headers.insert( diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 698481e37..cb38c7315 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -12,11 +12,10 @@ use flate2::Compression; use futures::Future; use rand::Rng; -use actix_codec::{AsyncRead, AsyncWrite}; use actix_http::HttpService; use actix_http_test::TestServer; use actix_service::{service_fn, NewService}; -use actix_web::http::{Cookie, Version}; +use actix_web::http::Cookie; use actix_web::middleware::{BodyEncoding, Compress}; use actix_web::{http::header, web, App, Error, HttpMessage, HttpRequest, HttpResponse}; use awc::error::SendRequestError; @@ -43,30 +42,6 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; -#[cfg(feature = "ssl")] -fn ssl_acceptor( -) -> std::io::Result> { - use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("../tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("../tests/cert.pem") - .unwrap(); - builder.set_alpn_select_callback(|_, protos| { - const H2: &[u8] = b"\x02h2"; - if protos.windows(3).any(|window| window == H2) { - Ok(b"h2") - } else { - Err(openssl::ssl::AlpnError::NOACK) - } - }); - builder.set_alpn_protos(b"\x02h2")?; - Ok(actix_server::ssl::OpensslAcceptor::new(builder.build())) -} - #[test] fn test_simple() { let mut srv = @@ -207,60 +182,6 @@ fn test_connection_reuse() { assert_eq!(num.load(Ordering::Relaxed), 1); } -#[cfg(feature = "ssl")] -#[test] -fn test_connection_reuse_h2() { - let openssl = ssl_acceptor().unwrap(); - let num = Arc::new(AtomicUsize::new(0)); - let num2 = num.clone(); - - let mut srv = TestServer::new(move || { - let num2 = num2.clone(); - service_fn(move |io| { - num2.fetch_add(1, Ordering::Relaxed); - Ok(io) - }) - .and_then( - openssl - .clone() - .map_err(|e| println!("Openssl error: {}", e)), - ) - .and_then( - HttpService::build() - .h2(App::new() - .service(web::resource("/").route(web::to(|| HttpResponse::Ok())))) - .map_err(|_| ()), - ) - }); - - // disable ssl verification - use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let _ = builder - .set_alpn_protos(b"\x02h2\x08http/1.1") - .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); - - let client = awc::Client::build() - .connector(awc::Connector::new().ssl(builder.build()).finish()) - .finish(); - - // req 1 - let request = client.get(srv.surl("/")).send(); - let response = srv.block_on(request).unwrap(); - assert!(response.status().is_success()); - - // req 2 - let req = client.post(srv.surl("/")); - let response = srv.block_on_fn(move || req.send()).unwrap(); - assert!(response.status().is_success()); - assert_eq!(response.version(), Version::HTTP_2); - - // one connection - assert_eq!(num.load(Ordering::Relaxed), 1); -} - #[test] fn test_connection_force_close() { let num = Arc::new(AtomicUsize::new(0)); diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs new file mode 100644 index 000000000..e65e4e874 --- /dev/null +++ b/awc/tests/test_rustls_client.rs @@ -0,0 +1,96 @@ +#![cfg(feature = "rust-tls")] +use rustls::{ + internal::pemfile::{certs, pkcs8_private_keys}, + ClientConfig, NoClientAuth, +}; + +use std::fs::File; +use std::io::{BufReader, Result}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; + +use actix_codec::{AsyncRead, AsyncWrite}; +use actix_http::HttpService; +use actix_http_test::TestServer; +use actix_server::ssl::RustlsAcceptor; +use actix_service::{service_fn, NewService}; +use actix_web::http::Version; +use actix_web::{web, App, HttpResponse}; + +fn ssl_acceptor() -> Result> { + use rustls::ServerConfig; + // load ssl keys + let mut config = ServerConfig::new(NoClientAuth::new()); + let cert_file = &mut BufReader::new(File::open("../tests/cert.pem").unwrap()); + let key_file = &mut BufReader::new(File::open("../tests/key.pem").unwrap()); + let cert_chain = certs(cert_file).unwrap(); + let mut keys = pkcs8_private_keys(key_file).unwrap(); + config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); + let protos = vec![b"h2".to_vec()]; + config.set_protocols(&protos); + Ok(RustlsAcceptor::new(config)) +} + +mod danger { + pub struct NoCertificateVerification {} + + impl rustls::ServerCertVerifier for NoCertificateVerification { + fn verify_server_cert( + &self, + _roots: &rustls::RootCertStore, + _presented_certs: &[rustls::Certificate], + _dns_name: webpki::DNSNameRef<'_>, + _ocsp: &[u8], + ) -> Result { + Ok(rustls::ServerCertVerified::assertion()) + } + } +} + +#[test] +fn test_connection_reuse_h2() { + let rustls = ssl_acceptor().unwrap(); + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); + + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + service_fn(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then(rustls.clone().map_err(|e| println!("Rustls error: {}", e))) + .and_then( + HttpService::build() + .h2(App::new() + .service(web::resource("/").route(web::to(|| HttpResponse::Ok())))) + .map_err(|_| ()), + ) + }); + + // disable ssl verification + let mut config = ClientConfig::new(); + let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; + config.set_protocols(&protos); + config + .dangerous() + .set_certificate_verifier(Arc::new(danger::NoCertificateVerification {})); + + let client = awc::Client::build() + .connector(awc::Connector::new().rustls(Arc::new(config)).finish()) + .finish(); + + // req 1 + let request = client.get(srv.surl("/")).send(); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); + + // req 2 + let req = client.post(srv.surl("/")); + let response = srv.block_on_fn(move || req.send()).unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.version(), Version::HTTP_2); + + // one connection + assert_eq!(num.load(Ordering::Relaxed), 1); +} diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs new file mode 100644 index 000000000..e6b0101b2 --- /dev/null +++ b/awc/tests/test_ssl_client.rs @@ -0,0 +1,86 @@ +#![cfg(feature = "ssl")] +use openssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode}; + +use std::io::Result; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; + +use actix_codec::{AsyncRead, AsyncWrite}; +use actix_http::HttpService; +use actix_http_test::TestServer; +use actix_server::ssl::OpensslAcceptor; +use actix_service::{service_fn, NewService}; +use actix_web::http::Version; +use actix_web::{web, App, HttpResponse}; + +fn ssl_acceptor() -> Result> { + // load ssl keys + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); + builder + .set_private_key_file("../tests/key.pem", SslFiletype::PEM) + .unwrap(); + builder + .set_certificate_chain_file("../tests/cert.pem") + .unwrap(); + builder.set_alpn_select_callback(|_, protos| { + const H2: &[u8] = b"\x02h2"; + if protos.windows(3).any(|window| window == H2) { + Ok(b"h2") + } else { + Err(openssl::ssl::AlpnError::NOACK) + } + }); + builder.set_alpn_protos(b"\x02h2")?; + Ok(actix_server::ssl::OpensslAcceptor::new(builder.build())) +} + +#[test] +fn test_connection_reuse_h2() { + let openssl = ssl_acceptor().unwrap(); + let num = Arc::new(AtomicUsize::new(0)); + let num2 = num.clone(); + + let mut srv = TestServer::new(move || { + let num2 = num2.clone(); + service_fn(move |io| { + num2.fetch_add(1, Ordering::Relaxed); + Ok(io) + }) + .and_then( + openssl + .clone() + .map_err(|e| println!("Openssl error: {}", e)), + ) + .and_then( + HttpService::build() + .h2(App::new() + .service(web::resource("/").route(web::to(|| HttpResponse::Ok())))) + .map_err(|_| ()), + ) + }); + + // disable ssl verification + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + let _ = builder + .set_alpn_protos(b"\x02h2\x08http/1.1") + .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); + + let client = awc::Client::build() + .connector(awc::Connector::new().ssl(builder.build()).finish()) + .finish(); + + // req 1 + let request = client.get(srv.surl("/")).send(); + let response = srv.block_on(request).unwrap(); + assert!(response.status().is_success()); + + // req 2 + let req = client.post(srv.surl("/")); + let response = srv.block_on_fn(move || req.send()).unwrap(); + assert!(response.status().is_success()); + assert_eq!(response.version(), Version::HTTP_2); + + // one connection + assert_eq!(num.load(Ordering::Relaxed), 1); +} diff --git a/examples/uds.rs b/examples/uds.rs new file mode 100644 index 000000000..9dc82903f --- /dev/null +++ b/examples/uds.rs @@ -0,0 +1,53 @@ +use futures::IntoFuture; + +use actix_web::{ + get, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer, +}; + +#[get("/resource1/{name}/index.html")] +fn index(req: HttpRequest, name: web::Path) -> String { + println!("REQ: {:?}", req); + format!("Hello: {}!\r\n", name) +} + +fn index_async(req: HttpRequest) -> impl IntoFuture { + println!("REQ: {:?}", req); + Ok("Hello world!\r\n") +} + +#[get("/")] +fn no_params() -> &'static str { + "Hello world!\r\n" +} + +#[cfg(feature = "uds")] +fn main() -> std::io::Result<()> { + std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); + env_logger::init(); + + HttpServer::new(|| { + App::new() + .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2")) + .wrap(middleware::Compress::default()) + .wrap(middleware::Logger::default()) + .service(index) + .service(no_params) + .service( + web::resource("/resource2/index.html") + .wrap( + middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"), + ) + .default_service( + web::route().to(|| HttpResponse::MethodNotAllowed()), + ) + .route(web::get().to_async(index_async)), + ) + .service(web::resource("/test1.html").to(|| "Test\r\n")) + }) + .bind_uds("/Users/fafhrd91/uds-test")? + .workers(1) + .run() +} + +#[cfg(not(feature = "uds"))] +fn main() {} diff --git a/src/app.rs b/src/app.rs index 3b063a841..f93859c7e 100644 --- a/src/app.rs +++ b/src/app.rs @@ -23,16 +23,17 @@ use crate::service::{ }; type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; -type FnDataFactory = Box Box, Error = ()>>>; +type FnDataFactory = + Box Box, Error = ()>>>; /// Application builder - structure that follows the builder pattern /// for building application instances. pub struct App { endpoint: T, - services: Vec>, + services: Vec>, default: Option>, factory_ref: Rc>>, - data: Vec>, + data: Vec>, data_factories: Vec, config: AppConfigInner, external: Vec, diff --git a/src/app_service.rs b/src/app_service.rs index 8ab9b352a..736c35010 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -18,14 +18,15 @@ use crate::request::{HttpRequest, HttpRequestPool}; use crate::rmap::ResourceMap; use crate::service::{ServiceFactory, ServiceRequest, ServiceResponse}; -type Guards = Vec>; +type Guards = Vec>; type HttpService = BoxedService; type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; type BoxedResponse = Either< FutureResult, - Box>, + Box>, >; -type FnDataFactory = Box Box, Error = ()>>>; +type FnDataFactory = + Box Box, Error = ()>>>; /// Service factory to convert `Request` to a `ServiceRequest`. /// It also executes data factories. @@ -40,10 +41,10 @@ where >, { pub(crate) endpoint: T, - pub(crate) data: Rc>>, + pub(crate) data: Rc>>, pub(crate) data_factories: Rc>, pub(crate) config: RefCell, - pub(crate) services: Rc>>>, + pub(crate) services: Rc>>>, pub(crate) default: Option>, pub(crate) factory_ref: Rc>>, pub(crate) external: RefCell>, @@ -142,9 +143,9 @@ where endpoint_fut: T::Future, rmap: Rc, config: AppConfig, - data: Rc>>, - data_factories: Vec>, - data_factories_fut: Vec, Error = ()>>>, + data: Rc>>, + data_factories: Vec>, + data_factories_fut: Vec, Error = ()>>>, _t: PhantomData, } @@ -297,14 +298,14 @@ impl NewService for AppRoutingFactory { } } -type HttpServiceFut = Box>; +type HttpServiceFut = Box>; /// Create app service #[doc(hidden)] pub struct AppRoutingFactoryResponse { fut: Vec, default: Option, - default_fut: Option>>, + default_fut: Option>>, } enum CreateAppRoutingItem { diff --git a/src/config.rs b/src/config.rs index 8de43f36c..63fd31d27 100644 --- a/src/config.rs +++ b/src/config.rs @@ -16,7 +16,7 @@ use crate::service::{ ServiceResponse, }; -type Guards = Vec>; +type Guards = Vec>; type HttpNewService = boxed::BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; @@ -31,7 +31,7 @@ pub struct AppService { Option, Option>, )>, - service_data: Rc>>, + service_data: Rc>>, } impl AppService { @@ -39,7 +39,7 @@ impl AppService { pub(crate) fn new( config: AppConfig, default: Rc, - service_data: Rc>>, + service_data: Rc>>, ) -> Self { AppService { config, @@ -101,7 +101,7 @@ impl AppService { pub fn register_service( &mut self, rdef: ResourceDef, - guards: Option>>, + guards: Option>>, service: F, nested: Option>, ) where @@ -133,7 +133,7 @@ impl AppConfig { /// Set server host name. /// - /// Host name is used by application router aa a hostname for url + /// Host name is used by application router as a hostname for url /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. /// html#method.host) documentation for more information. /// @@ -174,8 +174,8 @@ impl Default for AppConfigInner { /// to set of external methods. This could help with /// modularization of big application configuration. pub struct ServiceConfig { - pub(crate) services: Vec>, - pub(crate) data: Vec>, + pub(crate) services: Vec>, + pub(crate) data: Vec>, pub(crate) external: Vec, } diff --git a/src/data.rs b/src/data.rs index bd166b79c..3461d24f3 100644 --- a/src/data.rs +++ b/src/data.rs @@ -118,7 +118,7 @@ impl FromRequest for Data { impl DataFactory for Data { fn create(&self, extensions: &mut Extensions) -> bool { if !extensions.contains::>() { - let _ = extensions.insert(Data(self.0.clone())); + extensions.insert(Data(self.0.clone())); true } else { false diff --git a/src/extract.rs b/src/extract.rs index 17b5cb40c..1687973ac 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -94,7 +94,7 @@ where { type Config = T::Config; type Error = Error; - type Future = Box, Error = Error>>; + type Future = Box, Error = Error>>; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { @@ -165,7 +165,7 @@ where { type Config = T::Config; type Error = Error; - type Future = Box, Error = Error>>; + type Future = Box, Error = Error>>; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { diff --git a/src/guard.rs b/src/guard.rs index 0990e876a..e0b4055ba 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -26,7 +26,7 @@ //! ``` #![allow(non_snake_case)] -use actix_http::http::{self, header, HttpTryFrom}; +use actix_http::http::{self, header, uri::Uri, HttpTryFrom}; use actix_http::RequestHead; /// Trait defines resource guards. Guards are used for routes selection. @@ -100,7 +100,7 @@ pub fn Any(guard: F) -> AnyGuard { } /// Matches if any of supplied guards matche. -pub struct AnyGuard(Vec>); +pub struct AnyGuard(Vec>); impl AnyGuard { /// Add guard to a list of guards to check @@ -140,7 +140,7 @@ pub fn All(guard: F) -> AllGuard { } /// Matches if all of supplied guards. -pub struct AllGuard(Vec>); +pub struct AllGuard(Vec>); impl AllGuard { /// Add new guard to the list of guards to check @@ -167,7 +167,7 @@ pub fn Not(guard: F) -> NotGuard { } #[doc(hidden)] -pub struct NotGuard(Box); +pub struct NotGuard(Box); impl Guard for NotGuard { fn check(&self, request: &RequestHead) -> bool { @@ -256,45 +256,68 @@ impl Guard for HeaderGuard { } } -// /// Return predicate that matches if request contains specified Host name. -// /// -// /// ```rust,ignore -// /// # extern crate actix_web; -// /// use actix_web::{pred, App, HttpResponse}; -// /// -// /// fn main() { -// /// App::new().resource("/index.html", |r| { -// /// r.route() -// /// .guard(pred::Host("www.rust-lang.org")) -// /// .f(|_| HttpResponse::MethodNotAllowed()) -// /// }); -// /// } -// /// ``` -// pub fn Host>(host: H) -> HostGuard { -// HostGuard(host.as_ref().to_string(), None) -// } +/// Return predicate that matches if request contains specified Host name. +/// +/// ```rust,ignore +/// # extern crate actix_web; +/// use actix_web::{guard::Host, App, HttpResponse}; +/// +/// fn main() { +/// App::new().resource("/index.html", |r| { +/// r.route() +/// .guard(Host("www.rust-lang.org")) +/// .f(|_| HttpResponse::MethodNotAllowed()) +/// }); +/// } +/// ``` +pub fn Host>(host: H) -> HostGuard { + HostGuard(host.as_ref().to_string(), None) +} -// #[doc(hidden)] -// pub struct HostGuard(String, Option); +fn get_host_uri(req: &RequestHead) -> Option { + use core::str::FromStr; + let host_value = req.headers.get(header::HOST)?; + let host = host_value.to_str().ok()?; + let uri = Uri::from_str(host).ok()?; + Some(uri) +} -// impl HostGuard { -// /// Set reuest scheme to match -// pub fn scheme>(&mut self, scheme: H) { -// self.1 = Some(scheme.as_ref().to_string()) -// } -// } +#[doc(hidden)] +pub struct HostGuard(String, Option); -// impl Guard for HostGuard { -// fn check(&self, _req: &RequestHead) -> bool { -// // let info = req.connection_info(); -// // if let Some(ref scheme) = self.1 { -// // self.0 == info.host() && scheme == info.scheme() -// // } else { -// // self.0 == info.host() -// // } -// false -// } -// } +impl HostGuard { + /// Set request scheme to match + pub fn scheme>(mut self, scheme: H) -> HostGuard { + self.1 = Some(scheme.as_ref().to_string()); + self + } +} + +impl Guard for HostGuard { + fn check(&self, req: &RequestHead) -> bool { + let req_host_uri = if let Some(uri) = get_host_uri(req) { + uri + } else { + return false; + }; + + if let Some(uri_host) = req_host_uri.host() { + if self.0 != uri_host { + return false; + } + } else { + return false; + } + + if let Some(ref scheme) = self.1 { + if let Some(ref req_host_uri_scheme) = req_host_uri.scheme_str() { + return scheme == req_host_uri_scheme; + } + } + + true + } +} #[cfg(test)] mod tests { @@ -318,21 +341,64 @@ mod tests { assert!(!pred.check(req.head())); } - // #[test] - // fn test_host() { - // let req = TestServiceRequest::default() - // .header( - // header::HOST, - // header::HeaderValue::from_static("www.rust-lang.org"), - // ) - // .request(); + #[test] + fn test_host() { + let req = TestRequest::default() + .header( + header::HOST, + header::HeaderValue::from_static("www.rust-lang.org"), + ) + .to_http_request(); - // let pred = Host("www.rust-lang.org"); - // assert!(pred.check(&req)); + let pred = Host("www.rust-lang.org"); + assert!(pred.check(req.head())); - // let pred = Host("localhost"); - // assert!(!pred.check(&req)); - // } + let pred = Host("www.rust-lang.org").scheme("https"); + assert!(pred.check(req.head())); + + let pred = Host("blog.rust-lang.org"); + assert!(!pred.check(req.head())); + + let pred = Host("blog.rust-lang.org").scheme("https"); + assert!(!pred.check(req.head())); + + let pred = Host("crates.io"); + assert!(!pred.check(req.head())); + + let pred = Host("localhost"); + assert!(!pred.check(req.head())); + } + + #[test] + fn test_host_scheme() { + let req = TestRequest::default() + .header( + header::HOST, + header::HeaderValue::from_static("https://www.rust-lang.org"), + ) + .to_http_request(); + + let pred = Host("www.rust-lang.org").scheme("https"); + assert!(pred.check(req.head())); + + let pred = Host("www.rust-lang.org"); + assert!(pred.check(req.head())); + + let pred = Host("www.rust-lang.org").scheme("http"); + assert!(!pred.check(req.head())); + + let pred = Host("blog.rust-lang.org"); + assert!(!pred.check(req.head())); + + let pred = Host("blog.rust-lang.org").scheme("https"); + assert!(!pred.check(req.head())); + + let pred = Host("crates.io").scheme("https"); + assert!(!pred.check(req.head())); + + let pred = Host("localhost"); + assert!(!pred.check(req.head())); + } #[test] fn test_methods() { diff --git a/src/handler.rs b/src/handler.rs index bd0b35517..078abbf1d 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -250,7 +250,7 @@ where Ok(Async::Ready(res)) => { self.fut2 = Some(res.respond_to(self.req.as_ref().unwrap()).into_future()); - return self.poll(); + self.poll() } Ok(Async::NotReady) => Ok(Async::NotReady), Err(e) => { diff --git a/src/info.rs b/src/info.rs index e9b375875..ba59605de 100644 --- a/src/info.rs +++ b/src/info.rs @@ -25,7 +25,7 @@ impl ConnectionInfo { Ref::map(req.extensions(), |e| e.get().unwrap()) } - #[allow(clippy::cyclomatic_complexity)] + #[allow(clippy::cognitive_complexity)] fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo { let mut host = None; let mut scheme = None; diff --git a/src/lib.rs b/src/lib.rs index fffbc2f5e..60c34489e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#![allow(clippy::borrow_interior_mutable_const)] //! Actix web is a small, pragmatic, and extremely fast web framework //! for Rust. //! @@ -60,9 +61,9 @@ //! * Configurable request routing //! * Multipart streams //! * SSL support with OpenSSL or `native-tls` -//! * Middlewares (`Logger`, `Session`, `CORS`, `CSRF`, `DefaultHeaders`) +//! * Middlewares (`Logger`, `Session`, `CORS`, `DefaultHeaders`) //! * Supports [Actix actor framework](https://github.com/actix/actix) -//! * Supported Rust version: 1.31 or later +//! * Supported Rust version: 1.36 or later //! //! ## Package feature //! @@ -77,6 +78,7 @@ //! `c` compiler (default enabled) //! * `flate2-rust` - experimental rust based implementation for //! `gzip`, `deflate` compression. +//! * `uds` - Unix domain support, enables `HttpServer::bind_uds()` method. //! #![allow(clippy::type_complexity, clippy::new_without_default)] @@ -134,6 +136,8 @@ pub mod dev { //! ``` pub use crate::config::{AppConfig, AppService}; + #[doc(hidden)] + pub use crate::handler::{AsyncFactory, Factory}; pub use crate::info::ConnectionInfo; pub use crate::rmap::ResourceMap; pub use crate::service::{ diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index bddcdd552..ab2d36c2c 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -119,7 +119,7 @@ where type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = Box>; + type Future = Box>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.service.poll_ready() diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index ac166e0eb..5f73d4d7e 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -15,10 +15,10 @@ pub enum ErrorHandlerResponse { /// New http response got generated Response(ServiceResponse), /// Result is a future that resolves to a new http response - Future(Box, Error = Error>>), + Future(Box, Error = Error>>), } -type ErrorHandler = Fn(ServiceResponse) -> Result>; +type ErrorHandler = dyn Fn(ServiceResponse) -> Result>; /// `Middleware` for allowing custom handlers for responses. /// @@ -117,7 +117,7 @@ where type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = Box>; + type Future = Box>; fn poll_ready(&mut self) -> Poll<(), Self::Error> { self.service.poll_ready() diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index d47e45023..f450f0481 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -9,12 +9,13 @@ use actix_service::{Service, Transform}; use bytes::Bytes; use futures::future::{ok, FutureResult}; use futures::{Async, Future, Poll}; +use log::debug; use regex::Regex; use time; use crate::dev::{BodySize, MessageBody, ResponseBody}; use crate::error::{Error, Result}; -use crate::http::{HeaderName, HttpTryFrom}; +use crate::http::{HeaderName, HttpTryFrom, StatusCode}; use crate::service::{ServiceRequest, ServiceResponse}; use crate::HttpResponse; @@ -202,6 +203,12 @@ where fn poll(&mut self) -> Poll { let res = futures::try_ready!(self.fut.poll()); + if let Some(error) = res.response().error() { + if res.response().head().status != StatusCode::INTERNAL_SERVER_ERROR { + debug!("Error in response: {:?}", error); + } + } + if let Some(ref mut format) = self.format { for unit in &mut format.0 { unit.render_response(res.response()); @@ -415,9 +422,9 @@ impl FormatText { )) }; } - FormatText::UrlPath => *self = FormatText::Str(format!("{}", req.path())), + FormatText::UrlPath => *self = FormatText::Str(req.path().to_string()), FormatText::RequestTime => { - *self = FormatText::Str(format!("{}", now.rfc3339())) + *self = FormatText::Str(now.rfc3339().to_string()) } FormatText::RequestHeader(ref name) => { let s = if let Some(val) = req.headers().get(name) { @@ -444,7 +451,9 @@ impl FormatText { } } -pub(crate) struct FormatDisplay<'a>(&'a Fn(&mut Formatter) -> Result<(), fmt::Error>); +pub(crate) struct FormatDisplay<'a>( + &'a dyn Fn(&mut Formatter) -> Result<(), fmt::Error>, +); impl<'a> fmt::Display for FormatDisplay<'a> { fn fmt(&self, fmt: &mut Formatter) -> Result<(), fmt::Error> { diff --git a/src/request.rs b/src/request.rs index 07aac8cf7..0fc0647ff 100644 --- a/src/request.rs +++ b/src/request.rs @@ -174,6 +174,12 @@ impl HttpRequest { self.url_for(name, &NO_PARAMS) } + #[inline] + /// Get a reference to a `ResourceMap` of current application. + pub fn resource_map(&self) -> &ResourceMap { + &self.0.rmap + } + /// Peer socket address /// /// Peer address is actual socket address, if proxy is used in front of @@ -186,6 +192,9 @@ impl HttpRequest { } /// Get *ConnectionInfo* for the current request. + /// + /// This method panics if request's extensions container is already + /// borrowed. #[inline] pub fn connection_info(&self) -> Ref { ConnectionInfo::get(self.head(), &*self.app_config()) diff --git a/src/resource.rs b/src/resource.rs index c2691eebe..0af43a424 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -50,7 +50,7 @@ pub struct Resource { name: Option, routes: Vec, data: Option, - guards: Vec>, + guards: Vec>, default: Rc>>>, factory_ref: Rc>>, } @@ -118,7 +118,7 @@ where self } - pub(crate) fn add_guards(mut self, guards: Vec>) -> Self { + pub(crate) fn add_guards(mut self, guards: Vec>) -> Self { self.guards.extend(guards); self } @@ -245,7 +245,7 @@ where /// ```rust /// # use actix_web::*; /// # use futures::future::Future; - /// # fn index(req: HttpRequest) -> Box> { + /// # fn index(req: HttpRequest) -> Box> { /// # unimplemented!() /// # } /// App::new().service(web::resource("/").route(web::route().to_async(index))); @@ -426,7 +426,7 @@ where fn into_new_service(self) -> T { *self.factory_ref.borrow_mut() = Some(ResourceFactory { routes: self.routes, - data: self.data.map(|data| Rc::new(data)), + data: self.data.map(Rc::new), default: self.default, }); @@ -478,7 +478,7 @@ pub struct CreateResourceService { fut: Vec, data: Option>, default: Option, - default_fut: Option>>, + default_fut: Option>>, } impl Future for CreateResourceService { @@ -542,7 +542,7 @@ impl Service for ResourceService { type Error = Error; type Future = Either< FutureResult, - Box>, + Box>, >; fn poll_ready(&mut self) -> Poll<(), Self::Error> { diff --git a/src/responder.rs b/src/responder.rs index 47a8800ef..4988ad5bc 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -137,6 +137,22 @@ impl Responder for () { } } +impl Responder for (T, StatusCode) +where + T: Responder, +{ + type Error = T::Error; + type Future = CustomResponderFut; + + fn respond_to(self, req: &HttpRequest) -> Self::Future { + CustomResponderFut { + fut: self.0.respond_to(req).into_future(), + status: Some(self.1), + headers: None, + } + } +} + impl Responder for &'static str { type Error = Error; type Future = FutureResult; @@ -321,7 +337,7 @@ impl Future for CustomResponderFut { /// use actix_web::{Either, Error, HttpResponse}; /// /// type RegisterResult = -/// Either>>; +/// Either>>; /// /// fn index() -> RegisterResult { /// if is_a_variant() { @@ -395,13 +411,13 @@ where } } -impl Responder for Box> +impl Responder for Box> where I: Responder + 'static, E: Into + 'static, { type Error = Error; - type Future = Box>; + type Future = Box>; #[inline] fn respond_to(self, req: &HttpRequest) -> Self::Future { @@ -624,4 +640,28 @@ pub(crate) mod tests { HeaderValue::from_static("json") ); } + + #[test] + fn test_tuple_responder_with_status_code() { + let req = TestRequest::default().to_http_request(); + let res = + block_on(("test".to_string(), StatusCode::BAD_REQUEST).respond_to(&req)) + .unwrap(); + assert_eq!(res.status(), StatusCode::BAD_REQUEST); + assert_eq!(res.body().bin_ref(), b"test"); + + let req = TestRequest::default().to_http_request(); + let res = block_on( + ("test".to_string(), StatusCode::OK) + .with_header("content-type", "json") + .respond_to(&req), + ) + .unwrap(); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(res.body().bin_ref(), b"test"); + assert_eq!( + res.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("json") + ); + } } diff --git a/src/rmap.rs b/src/rmap.rs index cad62dca0..42ddb1349 100644 --- a/src/rmap.rs +++ b/src/rmap.rs @@ -123,7 +123,7 @@ impl ResourceMap { I: AsRef, { if let Some(pattern) = self.named.get(name) { - if pattern.pattern().starts_with("/") { + if pattern.pattern().starts_with('/') { self.fill_root(path, elements)?; } if pattern.resource_path(path, elements) { diff --git a/src/route.rs b/src/route.rs index 660b82002..f4d303632 100644 --- a/src/route.rs +++ b/src/route.rs @@ -13,26 +13,26 @@ use crate::service::{ServiceRequest, ServiceResponse}; use crate::HttpResponse; type BoxedRouteService = Box< - Service< + dyn Service< Request = Req, Response = Res, Error = Error, Future = Either< FutureResult, - Box>, + Box>, >, >, >; type BoxedRouteNewService = Box< - NewService< + dyn NewService< Config = (), Request = Req, Response = Res, Error = Error, InitError = (), Service = BoxedRouteService, - Future = Box, Error = ()>>, + Future = Box, Error = ()>>, >, >; @@ -42,7 +42,7 @@ type BoxedRouteNewService = Box< /// If handler is not explicitly set, default *404 Not Found* handler is used. pub struct Route { service: BoxedRouteNewService, - guards: Rc>>, + guards: Rc>>, } impl Route { @@ -56,7 +56,7 @@ impl Route { } } - pub(crate) fn take_guards(&mut self) -> Vec> { + pub(crate) fn take_guards(&mut self) -> Vec> { std::mem::replace(Rc::get_mut(&mut self.guards).unwrap(), Vec::new()) } } @@ -78,12 +78,13 @@ impl NewService for Route { } } -type RouteFuture = - Box, Error = ()>>; +type RouteFuture = Box< + dyn Future, Error = ()>, +>; pub struct CreateRouteService { fut: RouteFuture, - guards: Rc>>, + guards: Rc>>, } impl Future for CreateRouteService { @@ -103,7 +104,7 @@ impl Future for CreateRouteService { pub struct RouteService { service: BoxedRouteService, - guards: Rc>>, + guards: Rc>>, } impl RouteService { @@ -123,7 +124,7 @@ impl Service for RouteService { type Error = Error; type Future = Either< FutureResult, - Box>, + Box>, >; fn poll_ready(&mut self) -> Poll<(), Self::Error> { @@ -317,7 +318,7 @@ where type Error = Error; type InitError = (); type Service = BoxedRouteService; - type Future = Box>; + type Future = Box>; fn new_service(&self, _: &()) -> Self::Future { Box::new( @@ -351,7 +352,7 @@ where type Error = Error; type Future = Either< FutureResult, - Box>, + Box>, >; fn poll_ready(&mut self) -> Poll<(), Self::Error> { diff --git a/src/scope.rs b/src/scope.rs index 400da668d..760fee478 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -23,12 +23,12 @@ use crate::service::{ ServiceFactory, ServiceFactoryWrapper, ServiceRequest, ServiceResponse, }; -type Guards = Vec>; +type Guards = Vec>; type HttpService = BoxedService; type HttpNewService = BoxedNewService<(), ServiceRequest, ServiceResponse, Error, ()>; type BoxedResponse = Either< FutureResult, - Box>, + Box>, >; /// Resources scope. @@ -64,8 +64,8 @@ pub struct Scope { endpoint: T, rdef: String, data: Option, - services: Vec>, - guards: Vec>, + services: Vec>, + guards: Vec>, default: Rc>>>, external: Vec, factory_ref: Rc>>, @@ -195,7 +195,7 @@ where self.external.extend(cfg.external); if !cfg.data.is_empty() { - let mut data = self.data.unwrap_or_else(|| Extensions::new()); + let mut data = self.data.unwrap_or_else(Extensions::new); for value in cfg.data.iter() { value.create(&mut data); @@ -425,7 +425,7 @@ where // complete scope pipeline creation *self.factory_ref.borrow_mut() = Some(ScopeFactory { - data: self.data.take().map(|data| Rc::new(data)), + data: self.data.take().map(Rc::new), default: self.default.clone(), services: Rc::new( cfg.into_services() @@ -503,10 +503,10 @@ pub struct ScopeFactoryResponse { fut: Vec, data: Option>, default: Option, - default_fut: Option>>, + default_fut: Option>>, } -type HttpServiceFut = Box>; +type HttpServiceFut = Box>; enum CreateScopeServiceItem { Future(Option, Option, HttpServiceFut), @@ -578,7 +578,7 @@ impl Future for ScopeFactoryResponse { pub struct ScopeService { data: Option>, - router: Router>>, + router: Router>>, default: Option, _ready: Option<(ServiceRequest, ResourceInfo)>, } diff --git a/src/server.rs b/src/server.rs index 353f29ba9..d1a019a19 100644 --- a/src/server.rs +++ b/src/server.rs @@ -180,7 +180,7 @@ where /// Set server host name. /// - /// Host name is used by application router aa a hostname for url + /// Host name is used by application router as a hostname for url /// generation. Check [ConnectionInfo](./dev/struct.ConnectionInfo. /// html#method.host) documentation for more information. pub fn server_hostname>(mut self, val: T) -> Self { @@ -288,13 +288,13 @@ where lst, move || { let c = cfg.lock(); - acceptor.clone().map_err(|e| SslError::Ssl(e)).and_then( + acceptor.clone().map_err(SslError::Ssl).and_then( HttpService::build() .keep_alive(c.keep_alive) .client_timeout(c.client_timeout) .client_disconnect(c.client_shutdown) .finish(factory()) - .map_err(|e| SslError::Service(e)) + .map_err(SslError::Service) .map_init_err(|_| ()), ) }, @@ -339,13 +339,13 @@ where lst, move || { let c = cfg.lock(); - acceptor.clone().map_err(|e| SslError::Ssl(e)).and_then( + acceptor.clone().map_err(SslError::Ssl).and_then( HttpService::build() .keep_alive(c.keep_alive) .client_timeout(c.client_timeout) .client_disconnect(c.client_shutdown) .finish(factory()) - .map_err(|e| SslError::Service(e)) + .map_err(SslError::Service) .map_init_err(|_| ()), ) }, @@ -434,6 +434,38 @@ where } Ok(self) } + + #[cfg(feature = "uds")] + /// Start listening for incoming unix domain connections. + /// + /// This method is available with `uds` feature. + pub fn bind_uds(mut self, addr: A) -> io::Result + where + A: AsRef, + { + let cfg = self.config.clone(); + let factory = self.factory.clone(); + self.sockets.push(Socket { + scheme: "http", + addr: net::SocketAddr::new( + net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)), + 8080, + ), + }); + + self.builder = self.builder.bind_uds( + format!("actix-web-service-{:?}", addr.as_ref()), + addr, + move || { + let c = cfg.lock(); + HttpService::build() + .keep_alive(c.keep_alive) + .client_timeout(c.client_timeout) + .finish(factory()) + }, + )?; + Ok(self) + } } impl HttpServer diff --git a/src/service.rs b/src/service.rs index 722813a9f..1d475cf15 100644 --- a/src/service.rs +++ b/src/service.rs @@ -18,6 +18,7 @@ use crate::dev::insert_slash; use crate::guard::Guard; use crate::info::ConnectionInfo; use crate::request::HttpRequest; +use crate::rmap::ResourceMap; pub trait HttpServiceFactory { fn register(self, config: &mut AppService); @@ -169,10 +170,17 @@ impl ServiceRequest { } #[inline] + /// Get a mutable reference to the Path parameters. pub fn match_info_mut(&mut self) -> &mut Path { self.0.match_info_mut() } + #[inline] + /// Get a reference to a `ResourceMap` of current application. + pub fn resource_map(&self) -> &ResourceMap { + self.0.resource_map() + } + /// Service configuration #[inline] pub fn app_config(&self) -> &AppConfig { @@ -399,7 +407,7 @@ impl fmt::Debug for ServiceResponse { pub struct WebService { rdef: String, name: Option, - guards: Vec>, + guards: Vec>, } impl WebService { @@ -468,7 +476,7 @@ struct WebServiceImpl { srv: T, rdef: String, name: Option, - guards: Vec>, + guards: Vec>, } impl HttpServiceFactory for WebServiceImpl diff --git a/src/test.rs b/src/test.rs index 208360a29..562fdf436 100644 --- a/src/test.rs +++ b/src/test.rs @@ -79,7 +79,7 @@ where F: FnOnce() -> R, R: IntoFuture, { - RT.with(move |rt| rt.borrow_mut().get_mut().block_on(lazy(|| f()))) + RT.with(move |rt| rt.borrow_mut().get_mut().block_on(lazy(f))) } #[doc(hidden)] diff --git a/src/types/form.rs b/src/types/form.rs index 32d0edb69..ec6e6cd09 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -3,20 +3,29 @@ use std::rc::Rc; use std::{fmt, ops}; -use actix_http::{Error, HttpMessage, Payload}; +use actix_http::{Error, HttpMessage, Payload, Response}; use bytes::BytesMut; use encoding_rs::{Encoding, UTF_8}; use futures::{Future, Poll, Stream}; use serde::de::DeserializeOwned; +use serde::Serialize; use crate::dev::Decompress; use crate::error::UrlencodedError; use crate::extract::FromRequest; -use crate::http::header::CONTENT_LENGTH; +use crate::http::{ + header::{ContentType, CONTENT_LENGTH}, + StatusCode, +}; use crate::request::HttpRequest; +use crate::responder::Responder; -#[derive(PartialEq, Eq, PartialOrd, Ord)] -/// Extract typed information from the request's body. +/// Form data helper (`application/x-www-form-urlencoded`) +/// +/// Can be use to extract url-encoded data from the request body, +/// or send url-encoded data as the response. +/// +/// ## Extract /// /// To extract typed information from request's body, the type `T` must /// implement the `Deserialize` trait from *serde*. @@ -24,8 +33,7 @@ use crate::request::HttpRequest; /// [**FormConfig**](struct.FormConfig.html) allows to configure extraction /// process. /// -/// ## Example -/// +/// ### Example /// ```rust /// # extern crate actix_web; /// #[macro_use] extern crate serde_derive; @@ -44,6 +52,36 @@ use crate::request::HttpRequest; /// } /// # fn main() {} /// ``` +/// +/// ## Respond +/// +/// The `Form` type also allows you to respond with well-formed url-encoded data: +/// simply return a value of type Form where T is the type to be url-encoded. +/// The type must implement `serde::Serialize`; +/// +/// ### Example +/// ```rust +/// # #[macro_use] extern crate serde_derive; +/// # use actix_web::*; +/// # +/// #[derive(Serialize)] +/// struct SomeForm { +/// name: String, +/// age: u8 +/// } +/// +/// // Will return a 200 response with header +/// // `Content-Type: application/x-www-form-urlencoded` +/// // and body "name=actix&age=123" +/// fn index() -> web::Form { +/// web::Form(SomeForm { +/// name: "actix".into(), +/// age: 123 +/// }) +/// } +/// # fn main() {} +/// ``` +#[derive(PartialEq, Eq, PartialOrd, Ord)] pub struct Form(pub T); impl Form { @@ -73,7 +111,7 @@ where { type Config = FormConfig; type Error = Error; - type Future = Box>; + type Future = Box>; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { @@ -110,6 +148,22 @@ impl fmt::Display for Form { } } +impl Responder for Form { + type Error = Error; + type Future = Result; + + fn respond_to(self, _: &HttpRequest) -> Self::Future { + let body = match serde_urlencoded::to_string(&self.0) { + Ok(body) => body, + Err(e) => return Err(e.into()), + }; + + Ok(Response::build(StatusCode::OK) + .set(ContentType::form_url_encoded()) + .body(body)) + } +} + /// Form extractor configuration /// /// ```rust @@ -141,7 +195,7 @@ impl fmt::Display for Form { #[derive(Clone)] pub struct FormConfig { limit: usize, - ehandler: Option Error>>, + ehandler: Option Error>>, } impl FormConfig { @@ -187,7 +241,7 @@ pub struct UrlEncoded { length: Option, encoding: &'static Encoding, err: Option, - fut: Option>>, + fut: Option>>, } impl UrlEncoded { @@ -304,15 +358,16 @@ where #[cfg(test)] mod tests { use bytes::Bytes; - use serde::Deserialize; + use serde::{Deserialize, Serialize}; use super::*; - use crate::http::header::CONTENT_TYPE; + use crate::http::header::{HeaderValue, CONTENT_TYPE}; use crate::test::{block_on, TestRequest}; - #[derive(Deserialize, Debug, PartialEq)] + #[derive(Deserialize, Serialize, Debug, PartialEq)] struct Info { hello: String, + counter: i64, } #[test] @@ -320,11 +375,17 @@ mod tests { let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") .header(CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) + .set_payload(Bytes::from_static(b"hello=world&counter=123")) .to_http_parts(); - let s = block_on(Form::::from_request(&req, &mut pl)).unwrap(); - assert_eq!(s.hello, "world"); + let Form(s) = block_on(Form::::from_request(&req, &mut pl)).unwrap(); + assert_eq!( + s, + Info { + hello: "world".into(), + counter: 123 + } + ); } fn eq(err: UrlencodedError, other: UrlencodedError) -> bool { @@ -373,14 +434,15 @@ mod tests { let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") .header(CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) + .set_payload(Bytes::from_static(b"hello=world&counter=123")) .to_http_parts(); let info = block_on(UrlEncoded::::new(&req, &mut pl)).unwrap(); assert_eq!( info, Info { - hello: "world".to_owned() + hello: "world".to_owned(), + counter: 123 } ); @@ -389,15 +451,36 @@ mod tests { "application/x-www-form-urlencoded; charset=utf-8", ) .header(CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) + .set_payload(Bytes::from_static(b"hello=world&counter=123")) .to_http_parts(); let info = block_on(UrlEncoded::::new(&req, &mut pl)).unwrap(); assert_eq!( info, Info { - hello: "world".to_owned() + hello: "world".to_owned(), + counter: 123 } ); } + + #[test] + fn test_responder() { + let req = TestRequest::default().to_http_request(); + + let form = Form(Info { + hello: "world".to_string(), + counter: 123, + }); + let resp = form.respond_to(&req).unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + assert_eq!( + resp.headers().get(CONTENT_TYPE).unwrap(), + HeaderValue::from_static("application/x-www-form-urlencoded") + ); + + use crate::responder::tests::BodyTest; + assert_eq!(resp.body().bin_ref(), b"hello=world&counter=123"); + } + } diff --git a/src/types/json.rs b/src/types/json.rs index de0ffb54c..f309a3c5a 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -169,7 +169,7 @@ where T: DeserializeOwned + 'static, { type Error = Error; - type Future = Box>; + type Future = Box>; type Config = JsonConfig; #[inline] @@ -290,7 +290,7 @@ pub struct JsonBody { length: Option, stream: Option>, err: Option, - fut: Option>>, + fut: Option>>, } impl JsonBody diff --git a/src/types/path.rs b/src/types/path.rs index 2a6ce2879..a46575764 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -224,7 +224,7 @@ where /// ``` #[derive(Clone)] pub struct PathConfig { - ehandler: Option Error + Send + Sync>>, + ehandler: Option Error + Send + Sync>>, } impl PathConfig { diff --git a/src/types/payload.rs b/src/types/payload.rs index a8e85e4f3..f33e2e5f1 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -124,11 +124,11 @@ impl FromRequest for Bytes { type Config = PayloadConfig; type Error = Error; type Future = - Either>, FutureResult>; + Either>, FutureResult>; #[inline] fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { - let mut tmp; + let tmp; let cfg = if let Some(cfg) = req.app_data::() { cfg } else { @@ -177,12 +177,14 @@ impl FromRequest for Bytes { impl FromRequest for String { type Config = PayloadConfig; type Error = Error; - type Future = - Either>, FutureResult>; + type Future = Either< + Box>, + FutureResult, + >; #[inline] fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { - let mut tmp; + let tmp; let cfg = if let Some(cfg) = req.app_data::() { cfg } else { @@ -291,7 +293,7 @@ pub struct HttpMessageBody { length: Option, stream: Option>, err: Option, - fut: Option>>, + fut: Option>>, } impl HttpMessageBody { diff --git a/src/types/query.rs b/src/types/query.rs index 17240c0b5..60b07085d 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -12,9 +12,12 @@ use crate::error::QueryPayloadError; use crate::extract::FromRequest; use crate::request::HttpRequest; -#[derive(PartialEq, Eq, PartialOrd, Ord)] /// Extract typed information from the request's query. /// +/// **Note**: A query string consists of unordered `key=value` pairs, therefore it cannot +/// be decoded into any type which depends upon data ordering e.g. tuples or tuple-structs. +/// Attempts to do so will *fail at runtime*. +/// /// ## Example /// /// ```rust @@ -33,10 +36,10 @@ use crate::request::HttpRequest; /// response_type: ResponseType, /// } /// -/// // Use `Query` extractor for query information. -/// // This handler get called only if request's query contains `username` field -/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"` -/// fn index(info: web::Query) -> String { +/// // Use `Query` extractor for query information (and destructure it within the signature). +/// // This handler gets called only if the request's query string contains a `username` field. +/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`. +/// fn index(web::Query(info): web::Query) -> String { /// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) /// } /// @@ -45,7 +48,8 @@ use crate::request::HttpRequest; /// web::resource("/index.html").route(web::get().to(index))); // <- use `Query` extractor /// } /// ``` -pub struct Query(T); +#[derive(PartialEq, Eq, PartialOrd, Ord)] +pub struct Query(pub T); impl Query { /// Deconstruct to a inner value @@ -162,6 +166,8 @@ where /// Query extractor configuration /// +/// ## Example +/// /// ```rust /// #[macro_use] extern crate serde_derive; /// use actix_web::{error, web, App, FromRequest, HttpResponse}; @@ -192,7 +198,8 @@ where /// ``` #[derive(Clone)] pub struct QueryConfig { - ehandler: Option Error + Send + Sync>>, + ehandler: + Option Error + Send + Sync>>, } impl QueryConfig { diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md index e7292c0ec..c3fe5b285 100644 --- a/test-server/CHANGES.md +++ b/test-server/CHANGES.md @@ -1,5 +1,19 @@ # Changes + +### Changed + +* Update serde_urlencoded to "0.6.1" + + +## [0.2.4] - 2019-07-18 + +* Update actix-server to 0.6 + +## [0.2.3] - 2019-07-16 + +* Add `delete`, `options`, `patch` methods to `TestServerRunner` + ## [0.2.2] - 2019-06-16 * Add .put() and .sput() methods diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 4231b17bf..22809c060 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "0.2.2" +version = "0.2.4" authors = ["Nikolay Kim "] description = "Actix http test server" readme = "README.md" @@ -33,9 +33,10 @@ ssl = ["openssl", "actix-server/ssl", "awc/ssl"] actix-codec = "0.1.2" actix-rt = "0.2.2" actix-service = "0.4.1" -actix-server = "0.5.1" +actix-server = "0.6.0" actix-utils = "0.4.1" -awc = "0.2.1" +awc = "0.2.2" +actix-connect = "0.2.2" base64 = "0.10" bytes = "0.4" @@ -48,7 +49,7 @@ serde = "1.0" serde_json = "1.0" sha1 = "0.6" slab = "0.4" -serde_urlencoded = "0.5.3" +serde_urlencoded = "0.6.1" time = "0.1" tokio-tcp = "0.1" tokio-timer = "0.2" diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 1fbaa6c74..aba53980c 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -12,6 +12,7 @@ use futures::future::lazy; use futures::{Future, IntoFuture, Stream}; use http::Method; use net2::TcpBuilder; +use tokio_tcp::TcpStream; thread_local! { static RT: RefCell = { @@ -65,7 +66,7 @@ where F: FnOnce() -> R, R: IntoFuture, { - RT.with(move |rt| rt.borrow_mut().get_mut().block_on(lazy(|| f()))) + RT.with(move |rt| rt.borrow_mut().get_mut().block_on(lazy(f))) } /// The `TestServer` type. @@ -107,8 +108,9 @@ pub struct TestServerRuntime { } impl TestServer { + #[allow(clippy::new_ret_no_self)] /// Start new test server with application factory - pub fn new(factory: F) -> TestServerRuntime { + pub fn new>(factory: F) -> TestServerRuntime { let (tx, rx) = mpsc::channel(); // run server in separate thread @@ -161,6 +163,10 @@ impl TestServer { Ok::(Client::build().connector(connector).finish()) })) .unwrap(); + rt.block_on(lazy( + || Ok::<_, ()>(actix_connect::start_default_resolver()), + )) + .unwrap(); System::set_current(system); TestServerRuntime { addr, rt, client } } @@ -191,7 +197,7 @@ impl TestServerRuntime { F: FnOnce() -> R, R: Future, { - self.rt.block_on(lazy(|| f())) + self.rt.block_on(lazy(f)) } /// Execute function on current core @@ -210,18 +216,18 @@ impl TestServerRuntime { /// Construct test server url pub fn url(&self, uri: &str) -> String { if uri.starts_with('/') { - format!("http://127.0.0.1:{}{}", self.addr.port(), uri) + format!("http://localhost:{}{}", self.addr.port(), uri) } else { - format!("http://127.0.0.1:{}/{}", self.addr.port(), uri) + format!("http://localhost:{}/{}", self.addr.port(), uri) } } /// Construct test https server url pub fn surl(&self, uri: &str) -> String { if uri.starts_with('/') { - format!("https://127.0.0.1:{}{}", self.addr.port(), uri) + format!("https://localhost:{}{}", self.addr.port(), uri) } else { - format!("https://127.0.0.1:{}/{}", self.addr.port(), uri) + format!("https://localhost:{}/{}", self.addr.port(), uri) } } @@ -265,6 +271,36 @@ impl TestServerRuntime { self.client.put(self.surl(path.as_ref()).as_str()) } + /// Create `PATCH` request + pub fn patch>(&self, path: S) -> ClientRequest { + self.client.patch(self.url(path.as_ref()).as_str()) + } + + /// Create https `PATCH` request + pub fn spatch>(&self, path: S) -> ClientRequest { + self.client.patch(self.surl(path.as_ref()).as_str()) + } + + /// Create `DELETE` request + pub fn delete>(&self, path: S) -> ClientRequest { + self.client.delete(self.url(path.as_ref()).as_str()) + } + + /// Create https `DELETE` request + pub fn sdelete>(&self, path: S) -> ClientRequest { + self.client.delete(self.surl(path.as_ref()).as_str()) + } + + /// Create `OPTIONS` request + pub fn options>(&self, path: S) -> ClientRequest { + self.client.options(self.url(path.as_ref()).as_str()) + } + + /// Create https `OPTIONS` request + pub fn soptions>(&self, path: S) -> ClientRequest { + self.client.options(self.surl(path.as_ref()).as_str()) + } + /// Connect to test http server pub fn request>(&self, method: Method, path: S) -> ClientRequest { self.client.request(method, path.as_ref()) diff --git a/tests/cert.pem b/tests/cert.pem index eafad5245..f9bb05081 100644 --- a/tests/cert.pem +++ b/tests/cert.pem @@ -1,20 +1,32 @@ -----BEGIN CERTIFICATE----- -MIIDPjCCAiYCCQCmkoCBehOyYTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJV -UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMRAwDgYDVQQKDAdDb21wYW55MQww -CgYDVQQLDANPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xOTAzMjky -MzE5MDlaFw0yMDAzMjgyMzE5MDlaMGExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD -QTELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBhbnkxDDAKBgNVBAsMA09yZzEY -MBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEA2uFoWm74qumqIIsBBf/rgP3ZtZw6dRQhVoYjIwYk00T1RLmmbt8r -YNh3lehmnrQlM/YC3dzcspucGqIfvs5FEReh/vgvsqY3lfy47Q1zzdtBrKq2ZBro -AuJUe4ayMYz/L/2jAtPtGDQqWyzhKv6x/oz6N/tKqlzoGbjSGSJUqKAV+Tuo4YI4 -xw3r/RJg3I3+ruXOgM65GBdja7usI/BhseEOp9VXotoTEItGmvG2RFZ4A7cN124x -giFl2IeYuC60jteZ+bnhPiqxcdzf3K4dnZlzrYma+FxwWbaow4wlpQcZVFdZ+K/Y -p/Bbm/FDKoUHnEdn/QAanTruRxSGdai0owIDAQABMA0GCSqGSIb3DQEBCwUAA4IB -AQAEWn3WAwAbd64f5jo2w4076s2qFiCJjPWoxO6bO75FgFFtw/NNev8pxGVw1ehg -HiTO6VRYolL5S/RKOchjA83AcDEBjgf8fKtvTmE9kxZSUIo4kIvv8V9ZM72gJhDN -8D/lXduTZ9JMwLOa1NUB8/I6CbaU3VzWkfodArKKpQF3M+LLgK03i12PD0KPQ5zv -bwaNoQo6cTmPNIdsVZETRvPqONiCUaQV57G74dGtjeirCh/DO5EYRtb1thgS7TGm -+Xg8OC5vZ6g0+xsrSqDBmWNtlI7S3bsL5C3LIEOOAL1ZJHRy2KvIGQ9ipb3XjnKS -N7/wlQduRyPH7oaD/o4xf5Gt +MIIFfjCCA2agAwIBAgIJAOIBvp/w68KrMA0GCSqGSIb3DQEBCwUAMGsxCzAJBgNV +BAYTAlJVMRkwFwYDVQQIDBBTYWludC1QZXRlcnNidXJnMRkwFwYDVQQHDBBTYWlu +dC1QZXRlcnNidXJnMRIwEAYDVQQKDAlLdXBpYmlsZXQxEjAQBgNVBAMMCWxvY2Fs +aG9zdDAgFw0xOTA3MjcxODIzMTJaGA8zMDE5MDcyNzE4MjMxMlowazELMAkGA1UE +BhMCUlUxGTAXBgNVBAgMEFNhaW50LVBldGVyc2J1cmcxGTAXBgNVBAcMEFNhaW50 +LVBldGVyc2J1cmcxEjAQBgNVBAoMCUt1cGliaWxldDESMBAGA1UEAwwJbG9jYWxo +b3N0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuiQZzTO3gRRPr6ZH +wcmKqkoXig9taCCqx72Qvb9tvCLhQLE1dDPZV8I/r8bx+mM4Yz3r0Hm5LxTIhCM9 +p3/abuiJAZENC/VkxgFzBGg7KGLSFmzU+A8Ft+2mrKmj5MpIPBCxDeVg80TCQOJy +hj+NU3PpBo9nxTgxWNWO6X+ZovZohdp78fYLLtns8rxjug3FVzdPrrLnBvihkGlq +gfImkh+vZxMTj1OgtxyCOhdbO4Ol4jCbn7a5yIw+iixHOEgBQfTQopRP7z1PEUV2 +WIy2VEGzvQDlj2OyzH86T1IOFV5rz5MjdZuW0qNzeS0w3Jzgp/olSbIZLhGAaIk0 +gN7y9XvSHqs7rO0wW+467ico7+uP1ScGgPgJA5fGu7ahp7F7G3ZSoAqAcS60wYsX +kStoA3RWAuqste6aChv1tlgTt+Rhk8qjGhuh0ng2qVnTGyo2T3OCHB/c47Bcsp6L +xiyTCnQIPH3fh2iO/SC7gPw3jihPMCAQZYlkC3MhMk974rn2zs9cKtJ8ubnG2m5F +VFVYmduRqS/YQS/O802jVCFdc8KDmoeAYNuHzgRZjQv9018UUeW3jtWKnopJnWs5 +ae9pbtmYeOtc7OovOxT7J2AaVfUkHRhmlqWZMzEJTcZws0fRPTZDifFJ5LFWbZsC +zW4tCKBKvYM9eAnbb+abiHXlY1MCAwEAAaMjMCEwHwYDVR0RBBgwFoIJbG9jYWxo +b3N0ggkxMjcuMC4wLjEwDQYJKoZIhvcNAQELBQADggIBAC1EU4SVCfUKc7JbhYRf +P87F+8e13bBTLxevJdnTCH3Xw2AN8UPmwQ2vv9Mv2FMulMBQ7yLnQLGtgGUua2OE +XO+EdBBEKnglo9cwXGzU6qHhaiCeXZDM8s53qOOrD42XsDsY0nOoFYqDLW3WixP9 +f1fWbcEf6+ktlvqi/1/3R6QtQR+6LS43enbsYHq8aAP60NrpXxdXxEoUwW6Z/sje +XAQluH8jzledwJcY8bXRskAHZlE4kGlOVuGgnyI3BXyLiwB4g9smFzYIs98iAGmV +7ZBaR5IIiRCtoKBG+SngM7Log0bHphvFPjDDvgqWYiWaOHboYM60Y2Z/gRbcjuMU +WZX64jw29fa8UPFdtGTupt+iuO7iXnHnm0lBBK36rVdOvsZup76p6L4BXmFsRmFK +qJ2Zd8uWNPDq80Am0mYaAqENuIANHHJXX38SesC+QO+G2JZt6vCwkGk/Qune4GIg +1GwhvsDRfTQopSxg1rdPwPM7HWeTfUGHZ34B5p/iILA3o6PfYQU8fNAWIsCDkRX2 +MrgDgCnLZxKb6pjR4DYNAdPwkxyMFACZ2T46z6WvLWFlnkK5nbZoqsOsp+GJHole +llafhrelXEzt3zFR0q4zGcqheJDI+Wy+fBy3XawgAc4eN0T2UCzL/jKxKgzlzSU3 ++xh1SDNjFLRd6sGzZHPMgXN0 -----END CERTIFICATE----- diff --git a/tests/key.pem b/tests/key.pem index 2afbf5497..70153c8ae 100644 --- a/tests/key.pem +++ b/tests/key.pem @@ -1,27 +1,52 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEA2uFoWm74qumqIIsBBf/rgP3ZtZw6dRQhVoYjIwYk00T1RLmm -bt8rYNh3lehmnrQlM/YC3dzcspucGqIfvs5FEReh/vgvsqY3lfy47Q1zzdtBrKq2 -ZBroAuJUe4ayMYz/L/2jAtPtGDQqWyzhKv6x/oz6N/tKqlzoGbjSGSJUqKAV+Tuo -4YI4xw3r/RJg3I3+ruXOgM65GBdja7usI/BhseEOp9VXotoTEItGmvG2RFZ4A7cN -124xgiFl2IeYuC60jteZ+bnhPiqxcdzf3K4dnZlzrYma+FxwWbaow4wlpQcZVFdZ -+K/Yp/Bbm/FDKoUHnEdn/QAanTruRxSGdai0owIDAQABAoIBAQC4lzyQd+ITEbi+ -dTxJuQj94hgHB1htgKqU888SLI5F9nP6n67y9hb5N9WygSp6UWbGqYTFYwxlPMKr -22p2WjL5NTsTcm+XdIKQZW/3y06Mn4qFefsT9XURaZriCjihfU2BRaCCNARSUzwd -ZH4I6n9mM7KaH71aa7v6ZVoahE9tXPR6hM+SHQEySW4pWkEu98VpNNeIt6vP7WF9 -ONGbRa+0En4xgkuaxem2ZYa/GZFFtdQRkroNMhIRlfcPpkjy8DCc8E5RAkOzKC3O -lnxQwt+tdNNkGZz02ed2hx/YHPwFYy76y6hK5dxq74iKIaOc8U5t0HjB1zVfwiR0 -5mcxMncxAoGBAP+RivwXZ4FcxDY1uyziF+rwlC/1RujQFEWXIxsXCnba5DH3yKul -iKEIZPZtGhpsnQe367lcXcn7tztuoVjpAnk5L+hQY64tLwYbHeRcOMJ75C2y8FFC -NeG5sQsrk3IU1+jhGvrbE7UgOeAuWJmv0M1vPNB/+hGoZBW5W5uU1x89AoGBANtA -AhLtAcqQ/Qh2SpVhLljh7U85Be9tbCGua09clkYFzh3bcoBolXKH18Veww0TP0yF -0748CKw1A+ITbTVFV+vKvi4jzIxS7mr4wYtVCMssbttQN7y3l30IDxJwa9j3zTJx -IUn5OMMLv1JyitLId8HdOy1AdU3MkpJzdLyi1mFfAoGBAL3kL4fGABM/kU7SN6RO -zgS0AvdrYOeljBp1BRGg2hab58g02vamxVEZgqMTR7zwjPDqOIz+03U7wda4Ccyd -PUhDNJSB/r6xNepshZZi642eLlnCRguqjYyNw72QADtYv2B6uehAlXEUY8xtw0lW -OGgcSeyF2pH6M3tswWNlgT3lAoGAQ/BttBelOnP7NKgTLH7Usc4wjyAIaszpePZn -Ykw6dLBP0oixzoCZ7seRYSOgJWkVcEz39Db+KP60mVWTvbIjMHm+vOVy+Pip0JQM -xXQwKWU3ZNZSrzPkyWW55ejYQn9nIn5T5mxH3ojBXHcJ9Y8RLQ20zKzwrI77zE3i -mqGK9NkCgYEAq3dzHI0DGAJrR19sWl2LcqI19sj5a91tHx4cl1dJXS/iApOLLieU -zyUGkwfsqjHPAZ7GacICeBojIn/7KdPdlSKAbGVAU3d4qzvFS0qmWzObplBz3niT -Xnep2XLaVXqwlFJZZ6AHeKzYmMH0d0raiou2bpEUBqYizy2fi3NI4mA= ------END RSA PRIVATE KEY----- +-----BEGIN PRIVATE KEY----- +MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQC6JBnNM7eBFE+v +pkfByYqqSheKD21oIKrHvZC9v228IuFAsTV0M9lXwj+vxvH6YzhjPevQebkvFMiE +Iz2nf9pu6IkBkQ0L9WTGAXMEaDsoYtIWbNT4DwW37aasqaPkykg8ELEN5WDzRMJA +4nKGP41Tc+kGj2fFODFY1Y7pf5mi9miF2nvx9gsu2ezyvGO6DcVXN0+usucG+KGQ +aWqB8iaSH69nExOPU6C3HII6F1s7g6XiMJuftrnIjD6KLEc4SAFB9NCilE/vPU8R +RXZYjLZUQbO9AOWPY7LMfzpPUg4VXmvPkyN1m5bSo3N5LTDcnOCn+iVJshkuEYBo +iTSA3vL1e9Ieqzus7TBb7jruJyjv64/VJwaA+AkDl8a7tqGnsXsbdlKgCoBxLrTB +ixeRK2gDdFYC6qy17poKG/W2WBO35GGTyqMaG6HSeDapWdMbKjZPc4IcH9zjsFyy +novGLJMKdAg8fd+HaI79ILuA/DeOKE8wIBBliWQLcyEyT3viufbOz1wq0ny5ucba +bkVUVViZ25GpL9hBL87zTaNUIV1zwoOah4Bg24fOBFmNC/3TXxRR5beO1Yqeikmd +azlp72lu2Zh461zs6i87FPsnYBpV9SQdGGaWpZkzMQlNxnCzR9E9NkOJ8UnksVZt +mwLNbi0IoEq9gz14Cdtv5puIdeVjUwIDAQABAoICAQCZVVezw+BsAjFKPi1qIv2J +HZOadO7pEc/czflHdUON8SWgxtmDqZpmQmt3/ugiHE284qs4hqzXbcVnpCgLrLRh +HEiP887NhQ3IVjVK8hmZQR5SvsAIv0c0ph3gqbWKqF8sq4tOKR/eBUwHawJwODXR +AvB4KPWQbqOny/P3wNbseRLNAJeNT+MSaw5XPnzgLKvdFoEbJeBNy847Sbsk5DaF +tHgm7n30WS1Q6bkU5VyP//hMBUKNJFaSL4TtCWB5qkbu8B5VbtsR9m0FizTb6L3h +VmYbUXvIzJXjAwMjiDJ1w9wHl+tj3BE33tEmhuVzNf+SH+tLc9xuKJighDWt2vpD +eTpZ1qest26ANLOmNXWVCVTGpcWvOu5yhG/P7En10EzjFruMfHAFdwLm1gMx1rlR +9fyNAk/0ROJ+5BUtuWgDiyytS5f2T9KGiOHni7UbBIkv0CV2H6VL39Twxf+3OHnx +JJ7OWZ8DRuLM/EJfN3C1+3eDsXOvcdvbo2TFBmCCl4Pa2pm4k3g2NBfxy/zSYWIh +ccGPZorFKNMUi29U0Ool6fxeVflbll570pWVBLAB31HdkLSESv9h+2j/IiEJcJXj +nzl2RtYB0Uxzk6SjO0z4WXjz/SXg5tQQkm/dx8kM8TvHICFq68AEnw8t9Hagsdxs +v5jNyOEeI1I5gPgZmKuboQKCAQEA7Hw6s8Xc3UtNaywMcK1Eb1O+kwRdztgtm0uE +uqsHWmGqbBxXN4jiVLh3dILIXFuVbvDSsSZtPLhOj1wqxgsTHg93b4BtvowyNBgo +X4tErMu7/6NRael/hfOEdtpfv2gV+0eQa+8KKqYJPbqpMz/r5L/3RaxS3iXkj3QM +6oC4+cRuwy/flPMIpxhDriH5yjfiMOdDsi3ZfMTJu/58DTrKV7WkJxQZmha4EoZn +IiXeRhzo+2ukMDWrr3GGPyDfjd/NB7rmY8QBdmhB5NSk+6B66JCNTIbKka/pichS +36bwSYFNji4NaHUUlYDUjfKoTNuQMEZknMGhc/433ADO7s17iQKCAQEAyYBYVG7/ +LE2IkvQy9Nwly5tRCNlSvSkloz7PUwRbzG5uF5mweWEa8YECJe9/vrFXvyBW+NR8 +XABFn4eG0POTR9nyb4n2nUlqiGugDIPgkrKCkJws5InifITZ/+Viocd4YZL5UwCU +R1/kMf0UjK2iJjWEeTPS6RmwRI2Iu7kym9BzphDyNYBQSbUE/f+4hNP6nUT/h09c +VH4/sUhubSgVKeK4onOci8bKitAkwVBYCYSyhuBCeCu8fTk2hVRWviRaJPVq2PMB +LHw1FCcfJLIPJG6MZpFAPkMQxpiewdggXIgi46ZlZcsNXEJ81ocT4GU2j+ArQXCf +lgEycyD3mx4k+wKCAQBGneohmKoVYtEheavVUcgnvkggOqOQirlDsE9YNo4hjRyI +4AWjTbrYNaVmI0+VVLvQvxULVUA1a4v5/zm+nbv9s/ykTSN4TQEI0VXtAfdl6gif +k7NR/ynXZBpgK2GAFKLLwFj+Agl1JtOHnV+9MA9O5Yv/QDAWqhYQSEU7GWkjHGc+ +3eLT5ablzrcXHooqunlOxSBP6qURPupGuv1sLewSOOllyfjDLJmW3o+ZgNlY8nUX +7tK+mqhD4ZCG9VgMU5I0BrmZfQQ6yXMz09PYV9mb7N5kxbNjwbXpMOqeYolKSdRQ +6quST7Pv2OKf6KAdI0txPvP4Y1HFA1rG1W71nGKRAoIBAHlDU+T8J3Rx9I77hu70 +zYoKnmnE35YW/R+Q3RQIu3X7vyVUyG9DkQNlr/VEfIw2Dahnve9hcLWtNDkdRnTZ +IPlMoCmfzVo6pHIU0uy1MKEX7Js6YYnnsPVevhLR6NmTQU73NDRPVOzfOGUc+RDw +LXTxIBgQqAy/+ORIiNDwUxSSDgcSi7DG14qD9c0l59WH/HpI276Cc/4lPA9kl4/5 +X0MlvheFm+BCcgG34Wa1A0Y3JXkl3NqU94oktDro1or3NYioaPTGyR4MYaUPJh7f +SV2TacsP/ql5ks7xahkeB9un0ddOfBcWa6PqH1a7U6rnPj63mVB4hpGvhrziSiB/ +s6ECggEAOp2P4Yd9Vm9/CptxS50HFF4adyLscAtsDd3S2hIAXhDovcPbvRek4pLQ +idPhHlRAfqrEztnhaVAmCK9HlhgthtiQGQX62YI4CS4QL2IhzDFo3M1a2snjFEdl +QuFk3XI7kQ0Yp8BLLG7T436JUrUkCXc4gQX2uRNut+ff34RIR2CjcQQjChxuHVeG +sP/3xFFj8OSs7ZoSPbmDBLrMOl64YHwezQUNAZiRYiaGbFiY0QUV6dHq8qX/qE1h +a/0Rq+gTqObDST0TqhMzI8V/i7R8SwVcD5ODHaZp5I2N2P/hV5OWY7ghQXhh89WM +o21xtGh0nP2Fq1TC6jFO+9cpbK8jNA== +-----END PRIVATE KEY----- diff --git a/tests/test_server.rs b/tests/test_server.rs index 33c18b001..1623d2ef3 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -16,6 +16,7 @@ use flate2::Compression; use futures::stream::once; use rand::{distributions::Alphanumeric, Rng}; +use actix_connect::start_default_resolver; use actix_web::middleware::{BodyEncoding, Compress}; use actix_web::{dev, http, test, web, App, HttpResponse, HttpServer}; @@ -782,7 +783,7 @@ fn test_brotli_encoding_large() { #[test] fn test_reading_deflate_encoding_large_random_ssl() { use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; - use rustls::internal::pemfile::{certs, rsa_private_keys}; + use rustls::internal::pemfile::{certs, pkcs8_private_keys}; use rustls::{NoClientAuth, ServerConfig}; use std::fs::File; use std::io::BufReader; @@ -803,7 +804,7 @@ fn test_reading_deflate_encoding_large_random_ssl() { let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); let cert_chain = certs(cert_file).unwrap(); - let mut keys = rsa_private_keys(key_file).unwrap(); + let mut keys = pkcs8_private_keys(key_file).unwrap(); config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); let srv = HttpServer::new(|| { @@ -823,6 +824,7 @@ fn test_reading_deflate_encoding_large_random_ssl() { let _ = sys.run(); }); let (srv, _sys) = rx.recv().unwrap(); + test::block_on(futures::lazy(|| Ok::<_, ()>(start_default_resolver()))).unwrap(); let client = test::run_on(|| { let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); builder.set_verify(SslVerifyMode::NONE); @@ -844,19 +846,18 @@ fn test_reading_deflate_encoding_large_random_ssl() { let enc = e.finish().unwrap(); // client request - let _req = client - .post(format!("https://{}/", addr)) + let req = client + .post(format!("https://localhost:{}/", addr.port())) .header(http::header::CONTENT_ENCODING, "deflate") .send_body(enc); - // TODO: fix - // let response = test::block_on(req).unwrap(); - // assert!(response.status().is_success()); + let mut response = test::block_on(req).unwrap(); + assert!(response.status().is_success()); // read response - // let bytes = test::block_on(response.body()).unwrap(); - // assert_eq!(bytes.len(), data.len()); - // assert_eq!(bytes, Bytes::from(data)); + let bytes = test::block_on(response.body()).unwrap(); + assert_eq!(bytes.len(), data.len()); + assert_eq!(bytes, Bytes::from(data)); // stop let _ = srv.stop(false);