From db1d6b7963ba5a4ddc2e54f10bfe04a42e225e7d Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Thu, 12 Dec 2019 22:28:47 +0600 Subject: [PATCH] refactor test server impl --- Cargo.toml | 20 +- src/lib.rs | 5 - src/test.rs | 409 ++++++++++++++++++++++++++++++- tests/test_server.rs | 558 ++++++++++++++++--------------------------- 4 files changed, 624 insertions(+), 368 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f75e4a316..0159a21dd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,10 +43,7 @@ members = [ ] [features] -default = ["compress", "client", "fail"] - -# http client -client = ["awc"] +default = ["compress", "fail"] # content-encoding support compress = ["actix-http/compress", "awc/compress"] @@ -57,10 +54,10 @@ secure-cookies = ["actix-http/secure-cookies"] fail = ["actix-http/fail"] # openssl -openssl = ["actix-tls/openssl", "awc/openssl"] +openssl = ["actix-tls/openssl", "awc/openssl", "open-ssl"] # rustls -rustls = ["actix-tls/rustls", "awc/rustls"] +rustls = ["actix-tls/rustls", "awc/rustls", "rust-tls"] [dependencies] actix-codec = "0.2.0" @@ -70,12 +67,13 @@ actix-router = "0.2.0" actix-rt = "1.0.0" actix-server = "1.0.0" actix-testing = "1.0.0" +actix-macros = "0.1.0" actix-threadpool = "0.3.0" actix-tls = "1.0.0" actix-web-codegen = "0.2.0-alpha.2" actix-http = "1.0.0" -awc = { version = "1.0.0", default-features = false, optional = true } +awc = { version = "1.0.0", default-features = false } bytes = "0.5.2" derive_more = "0.99.2" @@ -92,18 +90,16 @@ serde_json = "1.0" serde_urlencoded = "0.6.1" time = "0.1.42" url = "2.1" +open-ssl = { version="0.10", package = "openssl", optional = true } +rust-tls = { version = "0.16.0", package = "rustls", optional = true } [dev-dependencies] # actix = "0.8.3" -actix-connect = "1.0.0" -actix-http-test = "1.0.0-alpha.3" rand = "0.7" env_logger = "0.6" serde_derive = "1.0" brotli = "3.3.0" flate2 = "1.0.13" -open-ssl = { version="0.10", package = "openssl" } -rust_tls = { version = "0.16.0", package = "rustls" } [profile.release] lto = true @@ -120,4 +116,4 @@ actix-identity = { path = "actix-identity" } actix-session = { path = "actix-session" } actix-files = { path = "actix-files" } actix-multipart = { path = "actix-multipart" } -awc = { path = "awc" } \ No newline at end of file +awc = { path = "awc" } diff --git a/src/lib.rs b/src/lib.rs index 8d46cd801..7a1dbec0f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -100,10 +100,6 @@ pub mod test; mod types; pub mod web; -#[allow(unused_imports)] -#[macro_use] -extern crate actix_web_codegen; - #[doc(hidden)] pub use actix_web_codegen::*; @@ -163,7 +159,6 @@ pub mod dev { } } -#[cfg(feature = "client")] pub mod client { //! An HTTP Client //! diff --git a/src/test.rs b/src/test.rs index 419ea2d36..6d546702a 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,16 +1,24 @@ //! Various helpers for Actix applications to use during testing. use std::convert::TryFrom; use std::rc::Rc; +use std::sync::mpsc; +use std::{fmt, net, thread, time}; +use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_http::http::header::{ContentType, Header, HeaderName, IntoHeaderValue}; use actix_http::http::{Error as HttpError, Method, StatusCode, Uri, Version}; use actix_http::test::TestRequest as HttpTestRequest; -use actix_http::{cookie::Cookie, Extensions, Request}; +use actix_http::{cookie::Cookie, ws, Extensions, HttpService, Request}; use actix_router::{Path, ResourceDef, Url}; +use actix_rt::System; +use actix_server::Server; use actix_service::{IntoService, IntoServiceFactory, Service, ServiceFactory}; +use awc::error::PayloadError; +use awc::{Client, ClientRequest, ClientResponse, Connector}; use bytes::{Bytes, BytesMut}; use futures::future::ok; use futures::stream::{Stream, StreamExt}; +use net2::TcpBuilder; use serde::de::DeserializeOwned; use serde::Serialize; use serde_json; @@ -500,6 +508,405 @@ impl TestRequest { } } +/// Start test server with default configuration +/// +/// Test server is very simple server that simplify process of writing +/// integration tests cases for actix web applications. +/// +/// # Examples +/// +/// ```rust +/// use actix_web::{web, test, App, HttpResponse, Error}; +/// +/// async fn my_handler() -> Result { +/// Ok(HttpResponse::Ok().into()) +/// } +/// +/// #[actix_rt::test] +/// async fn test_example() { +/// let mut srv = test::start( +/// || App::new().service( +/// web::resource("/").to(my_handler)) +/// ); +/// +/// let req = srv.get("/"); +/// let response = req.send().await.unwrap(); +/// assert!(response.status().is_success()); +/// } +/// ``` +pub fn start(factory: F) -> TestServer +where + F: Fn() -> I + Send + Clone + 'static, + I: IntoServiceFactory, + S: ServiceFactory + 'static, + S::Error: Into + 'static, + S::InitError: fmt::Debug, + S::Response: Into> + 'static, + ::Future: 'static, + B: MessageBody + 'static, +{ + start_with(TestServerConfig::default(), factory) +} + +/// Start test server with custom configuration +/// +/// Test server could be configured in different ways, for details check +/// `TestServerConfig` docs. +/// +/// # Examples +/// +/// ```rust +/// use actix_web::{web, test, App, HttpResponse, Error}; +/// +/// async fn my_handler() -> Result { +/// Ok(HttpResponse::Ok().into()) +/// } +/// +/// #[actix_rt::test] +/// async fn test_example() { +/// let mut srv = test::start_with(test::config().h1(), || +/// App::new().service(web::resource("/").to(my_handler)) +/// ); +/// +/// let req = srv.get("/"); +/// let response = req.send().await.unwrap(); +/// assert!(response.status().is_success()); +/// } +/// ``` +pub fn start_with(cfg: TestServerConfig, factory: F) -> TestServer +where + F: Fn() -> I + Send + Clone + 'static, + I: IntoServiceFactory, + S: ServiceFactory + 'static, + S::Error: Into + 'static, + S::InitError: fmt::Debug, + S::Response: Into> + 'static, + ::Future: 'static, + B: MessageBody + 'static, +{ + let (tx, rx) = mpsc::channel(); + + let ssl = match cfg.stream { + StreamType::Tcp => false, + #[cfg(feature = "openssl")] + StreamType::Openssl(_) => true, + #[cfg(feature = "rustls")] + StreamType::Rustls(_) => true, + }; + + // run server in separate thread + thread::spawn(move || { + let sys = System::new("actix-test-server"); + let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); + let local_addr = tcp.local_addr().unwrap(); + let factory = factory.clone(); + let cfg = cfg.clone(); + let ctimeout = cfg.client_timeout; + let builder = Server::build().workers(1).disable_signals(); + + match cfg.stream { + StreamType::Tcp => match cfg.tp { + HttpVer::Http1 => builder.listen("test", tcp, move || { + HttpService::build() + .client_timeout(ctimeout) + .h1(factory()) + .tcp() + }), + HttpVer::Http2 => builder.listen("test", tcp, move || { + HttpService::build() + .client_timeout(ctimeout) + .h2(factory()) + .tcp() + }), + HttpVer::Both => builder.listen("test", tcp, move || { + HttpService::build() + .client_timeout(ctimeout) + .finish(factory()) + .tcp() + }), + }, + #[cfg(feature = "openssl")] + StreamType::Openssl(acceptor) => match cfg.tp { + HttpVer::Http1 => builder.listen("test", tcp, move || { + HttpService::build() + .client_timeout(ctimeout) + .h1(factory()) + .openssl(acceptor.clone()) + }), + HttpVer::Http2 => builder.listen("test", tcp, move || { + HttpService::build() + .client_timeout(ctimeout) + .h2(factory()) + .openssl(acceptor.clone()) + }), + HttpVer::Both => builder.listen("test", tcp, move || { + HttpService::build() + .client_timeout(ctimeout) + .finish(factory()) + .openssl(acceptor.clone()) + }), + }, + #[cfg(feature = "rustls")] + StreamType::Rustls(config) => match cfg.tp { + HttpVer::Http1 => builder.listen("test", tcp, move || { + HttpService::build() + .client_timeout(ctimeout) + .h1(factory()) + .rustls(config.clone()) + }), + HttpVer::Http2 => builder.listen("test", tcp, move || { + HttpService::build() + .client_timeout(ctimeout) + .h2(factory()) + .rustls(config.clone()) + }), + HttpVer::Both => builder.listen("test", tcp, move || { + HttpService::build() + .client_timeout(ctimeout) + .finish(factory()) + .rustls(config.clone()) + }), + }, + } + .unwrap() + .start(); + + tx.send((System::current(), local_addr)).unwrap(); + sys.run() + }); + + let (system, addr) = rx.recv().unwrap(); + + let client = { + let connector = { + #[cfg(feature = "openssl")] + { + use open_ssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + let _ = builder + .set_alpn_protos(b"\x02h2\x08http/1.1") + .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); + Connector::new() + .conn_lifetime(time::Duration::from_secs(0)) + .timeout(time::Duration::from_millis(3000)) + .ssl(builder.build()) + .finish() + } + #[cfg(not(feature = "openssl"))] + { + Connector::new() + .conn_lifetime(time::Duration::from_secs(0)) + .timeout(time::Duration::from_millis(3000)) + .finish() + } + }; + + Client::build().connector(connector).finish() + }; + + TestServer { + ssl, + addr, + client, + system, + } +} + +#[derive(Clone)] +pub struct TestServerConfig { + tp: HttpVer, + stream: StreamType, + client_timeout: u64, +} + +#[derive(Clone)] +enum HttpVer { + Http1, + Http2, + Both, +} + +#[derive(Clone)] +enum StreamType { + Tcp, + #[cfg(feature = "openssl")] + Openssl(open_ssl::ssl::SslAcceptor), + #[cfg(feature = "rustls")] + Rustls(rust_tls::ServerConfig), +} + +impl Default for TestServerConfig { + fn default() -> Self { + TestServerConfig::new() + } +} + +/// Create default test server config +pub fn config() -> TestServerConfig { + TestServerConfig::new() +} + +impl TestServerConfig { + /// Create default server configuration + pub(crate) fn new() -> TestServerConfig { + TestServerConfig { + tp: HttpVer::Both, + stream: StreamType::Tcp, + client_timeout: 5000, + } + } + + /// Start http/1.1 server only + pub fn h1(mut self) -> Self { + self.tp = HttpVer::Http1; + self + } + + /// Start http/2 server only + pub fn h2(mut self) -> Self { + self.tp = HttpVer::Http2; + self + } + + /// Start openssl server + #[cfg(feature = "openssl")] + pub fn openssl(mut self, acceptor: open_ssl::ssl::SslAcceptor) -> Self { + self.stream = StreamType::Openssl(acceptor); + self + } + + /// Start rustls server + #[cfg(feature = "rustls")] + pub fn rustls(mut self, config: rust_tls::ServerConfig) -> Self { + self.stream = StreamType::Rustls(config); + self + } + + /// Set server client timeout in milliseconds for first request. + pub fn client_timeout(mut self, val: u64) -> Self { + self.client_timeout = val; + self + } +} + +/// Get first available unused address +pub fn unused_addr() -> net::SocketAddr { + let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); + let socket = TcpBuilder::new_v4().unwrap(); + socket.bind(&addr).unwrap(); + socket.reuse_address(true).unwrap(); + let tcp = socket.to_tcp_listener().unwrap(); + tcp.local_addr().unwrap() +} + +/// Test server controller +pub struct TestServer { + addr: net::SocketAddr, + client: awc::Client, + system: actix_rt::System, + ssl: bool, +} + +impl TestServer { + /// Construct test server url + pub fn addr(&self) -> net::SocketAddr { + self.addr + } + + /// Construct test server url + pub fn url(&self, uri: &str) -> String { + let scheme = if self.ssl { "https" } else { "http" }; + + if uri.starts_with('/') { + format!("{}://localhost:{}{}", scheme, self.addr.port(), uri) + } else { + format!("{}://localhost:{}/{}", scheme, self.addr.port(), uri) + } + } + + /// Create `GET` request + pub fn get>(&self, path: S) -> ClientRequest { + self.client.get(self.url(path.as_ref()).as_str()) + } + + /// Create `POST` request + pub fn post>(&self, path: S) -> ClientRequest { + self.client.post(self.url(path.as_ref()).as_str()) + } + + /// Create `HEAD` request + pub fn head>(&self, path: S) -> ClientRequest { + self.client.head(self.url(path.as_ref()).as_str()) + } + + /// Create `PUT` request + pub fn put>(&self, path: S) -> ClientRequest { + self.client.put(self.url(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 `DELETE` request + pub fn delete>(&self, path: S) -> ClientRequest { + self.client.delete(self.url(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()) + } + + /// Connect to test http server + pub fn request>(&self, method: Method, path: S) -> ClientRequest { + self.client.request(method, path.as_ref()) + } + + pub async fn load_body( + &mut self, + mut response: ClientResponse, + ) -> Result + where + S: Stream> + Unpin + 'static, + { + response.body().limit(10_485_760).await + } + + /// Connect to websocket server at a given path + pub async fn ws_at( + &mut self, + path: &str, + ) -> Result, awc::error::WsClientError> + { + let url = self.url(path); + let connect = self.client.ws(url).connect(); + connect.await.map(|(_, framed)| framed) + } + + /// Connect to a websocket server + pub async fn ws( + &mut self, + ) -> Result, awc::error::WsClientError> + { + self.ws_at("/").await + } + + /// Stop http server + fn stop(&mut self) { + self.system.stop(); + } +} + +impl Drop for TestServer { + fn drop(&mut self) { + self.stop() + } +} + #[cfg(test)] mod tests { use actix_http::httpmessage::HttpMessage; diff --git a/tests/test_server.rs b/tests/test_server.rs index 4f9e2c7ee..ca4aff638 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -4,9 +4,7 @@ use actix_http::http::header::{ ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING, }; -use actix_http::{Error, HttpService, Response}; -use actix_http_test::TestServer; -use brotli::DecompressorWriter; +use brotli::{CompressorWriter, DecompressorWriter}; use bytes::Bytes; use flate2::read::GzDecoder; use flate2::write::{GzEncoder, ZlibDecoder, ZlibEncoder}; @@ -15,7 +13,7 @@ use futures::{future::ok, stream::once}; use rand::{distributions::Alphanumeric, Rng}; use actix_web::middleware::{BodyEncoding, Compress}; -use actix_web::{dev, http, web, App, HttpResponse, HttpServer}; +use actix_web::{dev, http, test, web, App, Error, HttpResponse}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -41,11 +39,9 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[actix_rt::test] async fn test_body() { - let srv = TestServer::start(|| { - HttpService::build() - .h1(App::new() - .service(web::resource("/").route(web::to(|| Response::Ok().body(STR))))) - .tcp() + let srv = test::start(|| { + App::new() + .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) }); let mut response = srv.get("/").send().await.unwrap(); @@ -58,12 +54,10 @@ async fn test_body() { #[actix_rt::test] async fn test_body_gzip() { - let srv = TestServer::start(|| { - HttpService::build() - .h1(App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service(web::resource("/").route(web::to(|| Response::Ok().body(STR))))) - .tcp() + let srv = test::start_with(test::config().h1(), || { + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) }); let mut response = srv @@ -87,14 +81,12 @@ async fn test_body_gzip() { #[actix_rt::test] async fn test_body_gzip2() { - let srv = TestServer::start(|| { - HttpService::build() - .h1(App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service(web::resource("/").route(web::to(|| { - Response::Ok().body(STR).into_body::() - })))) - .tcp() + let srv = test::start_with(test::config().h1(), || { + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service(web::resource("/").route(web::to(|| { + HttpResponse::Ok().body(STR).into_body::() + }))) }); let mut response = srv @@ -118,23 +110,23 @@ async fn test_body_gzip2() { #[actix_rt::test] async fn test_body_encoding_override() { - let srv = TestServer::start(|| { - HttpService::build() - .h1(App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service(web::resource("/").route(web::to(|| { - Response::Ok().encoding(ContentEncoding::Deflate).body(STR) - }))) - .service(web::resource("/raw").route(web::to(|| { - let body = actix_web::dev::Body::Bytes(STR.into()); - let mut response = - Response::with_body(actix_web::http::StatusCode::OK, body); + let srv = test::start_with(test::config().h1(), || { + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service(web::resource("/").route(web::to(|| { + HttpResponse::Ok() + .encoding(ContentEncoding::Deflate) + .body(STR) + }))) + .service(web::resource("/raw").route(web::to(|| { + let body = actix_web::dev::Body::Bytes(STR.into()); + let mut response = + HttpResponse::with_body(actix_web::http::StatusCode::OK, body); - response.encoding(ContentEncoding::Deflate); + response.encoding(ContentEncoding::Deflate); - response - })))) - .tcp() + response + }))) }); // Builder @@ -181,16 +173,14 @@ async fn test_body_gzip_large() { let data = STR.repeat(10); let srv_data = data.clone(); - let srv = TestServer::start(move || { + let srv = test::start_with(test::config().h1(), move || { let data = srv_data.clone(); - HttpService::build() - .h1(App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service( - web::resource("/") - .route(web::to(move || Response::Ok().body(data.clone()))), - )) - .tcp() + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service( + web::resource("/") + .route(web::to(move || HttpResponse::Ok().body(data.clone()))), + ) }); let mut response = srv @@ -220,16 +210,14 @@ async fn test_body_gzip_large_random() { .collect::(); let srv_data = data.clone(); - let srv = TestServer::start(move || { + let srv = test::start_with(test::config().h1(), move || { let data = srv_data.clone(); - HttpService::build() - .h1(App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service( - web::resource("/") - .route(web::to(move || Response::Ok().body(data.clone()))), - )) - .tcp() + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service( + web::resource("/") + .route(web::to(move || HttpResponse::Ok().body(data.clone()))), + ) }); let mut response = srv @@ -254,16 +242,13 @@ async fn test_body_gzip_large_random() { #[actix_rt::test] async fn test_body_chunked_implicit() { - let srv = TestServer::start(move || { - HttpService::build() - .h1(App::new() - .wrap(Compress::new(ContentEncoding::Gzip)) - .service(web::resource("/").route(web::get().to(move || { - Response::Ok().streaming(once(ok::<_, Error>(Bytes::from_static( - STR.as_ref(), - )))) - })))) - .tcp() + let srv = test::start_with(test::config().h1(), || { + App::new() + .wrap(Compress::new(ContentEncoding::Gzip)) + .service(web::resource("/").route(web::get().to(move || { + HttpResponse::Ok() + .streaming(once(ok::<_, Error>(Bytes::from_static(STR.as_ref())))) + }))) }); let mut response = srv @@ -291,16 +276,13 @@ async fn test_body_chunked_implicit() { #[actix_rt::test] async fn test_body_br_streaming() { - let srv = TestServer::start(move || { - HttpService::build() - .h1(App::new().wrap(Compress::new(ContentEncoding::Br)).service( - web::resource("/").route(web::to(move || { - Response::Ok().streaming(once(ok::<_, Error>(Bytes::from_static( - STR.as_ref(), - )))) - })), - )) - .tcp() + let srv = test::start_with(test::config().h1(), || { + App::new().wrap(Compress::new(ContentEncoding::Br)).service( + web::resource("/").route(web::to(move || { + HttpResponse::Ok() + .streaming(once(ok::<_, Error>(Bytes::from_static(STR.as_ref())))) + })), + ) }); let mut response = srv @@ -324,12 +306,10 @@ async fn test_body_br_streaming() { #[actix_rt::test] async fn test_head_binary() { - let srv = TestServer::start(move || { - HttpService::build() - .h1(App::new().service(web::resource("/").route( - web::head().to(move || Response::Ok().content_length(100).body(STR)), - ))) - .tcp() + let srv = test::start_with(test::config().h1(), || { + App::new().service(web::resource("/").route( + web::head().to(move || HttpResponse::Ok().content_length(100).body(STR)), + )) }); let mut response = srv.head("/").send().await.unwrap(); @@ -347,19 +327,13 @@ async fn test_head_binary() { #[actix_rt::test] async fn test_no_chunking() { - let srv = TestServer::start(move || { - HttpService::build() - .h1( - App::new().service(web::resource("/").route(web::to(move || { - Response::Ok() - .no_chunking() - .content_length(STR.len() as u64) - .streaming(once(ok::<_, Error>(Bytes::from_static( - STR.as_ref(), - )))) - }))), - ) - .tcp() + let srv = test::start_with(test::config().h1(), || { + App::new().service(web::resource("/").route(web::to(move || { + HttpResponse::Ok() + .no_chunking() + .content_length(STR.len() as u64) + .streaming(once(ok::<_, Error>(Bytes::from_static(STR.as_ref())))) + }))) }); let mut response = srv.get("/").send().await.unwrap(); @@ -373,14 +347,12 @@ async fn test_no_chunking() { #[actix_rt::test] async fn test_body_deflate() { - let srv = TestServer::start(move || { - HttpService::build() - .h1(App::new() - .wrap(Compress::new(ContentEncoding::Deflate)) - .service( - web::resource("/").route(web::to(move || Response::Ok().body(STR))), - )) - .tcp() + let srv = test::start_with(test::config().h1(), || { + App::new() + .wrap(Compress::new(ContentEncoding::Deflate)) + .service( + web::resource("/").route(web::to(move || HttpResponse::Ok().body(STR))), + ) }); // client request @@ -404,12 +376,10 @@ async fn test_body_deflate() { #[actix_rt::test] async fn test_body_brotli() { - let srv = TestServer::start(move || { - HttpService::build() - .h1(App::new().wrap(Compress::new(ContentEncoding::Br)).service( - web::resource("/").route(web::to(move || Response::Ok().body(STR))), - )) - .tcp() + let srv = test::start_with(test::config().h1(), || { + App::new().wrap(Compress::new(ContentEncoding::Br)).service( + web::resource("/").route(web::to(move || HttpResponse::Ok().body(STR))), + ) }); // client request @@ -434,13 +404,11 @@ async fn test_body_brotli() { #[actix_rt::test] async fn test_encoding() { - let srv = TestServer::start(move || { - HttpService::build() - .h1(App::new().wrap(Compress::default()).service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - )) - .tcp() + let srv = test::start_with(test::config().h1(), || { + App::new().wrap(Compress::default()).service( + web::resource("/") + .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + ) }); // client request @@ -462,13 +430,11 @@ async fn test_encoding() { #[actix_rt::test] async fn test_gzip_encoding() { - let srv = TestServer::start(move || { - HttpService::build() - .h1(App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - )) - .tcp() + let srv = test::start_with(test::config().h1(), || { + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + ) }); // client request @@ -491,13 +457,11 @@ async fn test_gzip_encoding() { #[actix_rt::test] async fn test_gzip_encoding_large() { let data = STR.repeat(10); - let srv = TestServer::start(move || { - HttpService::build() - .h1(App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - )) - .tcp() + let srv = test::start_with(test::config().h1(), || { + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + ) }); // client request @@ -524,13 +488,11 @@ async fn test_reading_gzip_encoding_large_random() { .take(60_000) .collect::(); - let srv = TestServer::start(move || { - HttpService::build() - .h1(App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - )) - .tcp() + let srv = test::start_with(test::config().h1(), || { + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + ) }); // client request @@ -553,13 +515,11 @@ async fn test_reading_gzip_encoding_large_random() { #[actix_rt::test] async fn test_reading_deflate_encoding() { - let srv = TestServer::start(move || { - HttpService::build() - .h1(App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - )) - .tcp() + let srv = test::start_with(test::config().h1(), || { + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + ) }); let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); @@ -582,13 +542,11 @@ async fn test_reading_deflate_encoding() { #[actix_rt::test] async fn test_reading_deflate_encoding_large() { let data = STR.repeat(10); - let srv = TestServer::start(move || { - HttpService::build() - .h1(App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - )) - .tcp() + let srv = test::start_with(test::config().h1(), || { + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + ) }); let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); @@ -615,13 +573,11 @@ async fn test_reading_deflate_encoding_large_random() { .take(160_000) .collect::(); - let srv = TestServer::start(move || { - HttpService::build() - .h1(App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - )) - .tcp() + let srv = test::start_with(test::config().h1(), || { + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + ) }); let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); @@ -643,20 +599,17 @@ async fn test_reading_deflate_encoding_large_random() { } #[actix_rt::test] -#[cfg(feature = "brotli")] async fn test_brotli_encoding() { - let srv = TestServer::start(move || { - HttpService::build() - .h1(App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - )) - .tcp() + let srv = test::start_with(test::config().h1(), || { + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + ) }); - let mut e = BrotliEncoder::new(Vec::new(), 5); + let mut e = CompressorWriter::new(Vec::new(), 0, 3, 0); e.write_all(STR.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + let enc = e.into_inner(); // client request let request = srv @@ -671,22 +624,19 @@ async fn test_brotli_encoding() { assert_eq!(bytes, Bytes::from_static(STR.as_ref())); } -#[cfg(feature = "brotli")] #[actix_rt::test] async fn test_brotli_encoding_large() { let data = STR.repeat(10); - let srv = TestServer::start(move || { - HttpService::build() - .h1(App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| Response::Ok().body(body))), - )) - .tcp() + let srv = test::start_with(test::config().h1(), || { + App::new().service( + web::resource("/") + .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + ) }); - let mut e = BrotliEncoder::new(Vec::new(), 5); + let mut e = CompressorWriter::new(Vec::new(), 0, 3, 0); e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + let enc = e.into_inner(); // client request let request = srv @@ -701,124 +651,75 @@ async fn test_brotli_encoding_large() { assert_eq!(bytes, Bytes::from(data)); } -// #[cfg(feature = "ssl")] -// #[actix_rt::test] -// async fn test_brotli_encoding_large_ssl() { -// use actix::{Actor, System}; -// use openssl::ssl::{ -// SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode, -// }; -// // 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(); +#[cfg(feature = "openssl")] +#[actix_rt::test] +async fn test_brotli_encoding_large_openssl() { + // load ssl keys + use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); + builder + .set_private_key_file("tests/key.pem", SslFiletype::PEM) + .unwrap(); + builder + .set_certificate_chain_file("tests/cert.pem") + .unwrap(); -// let data = STR.repeat(10); -// let srv = test::TestServer::build().ssl(builder).start(|app| { -// app.handler(|req: &HttpRequest| { -// req.body() -// .and_then(|bytes: Bytes| { -// Ok(HttpResponse::Ok() -// .content_encoding(http::ContentEncoding::Identity) -// .body(bytes)) -// }) -// .responder() -// }) -// }); -// let mut rt = System::new("test"); + let data = STR.repeat(10); + let srv = test::start_with(test::config().openssl(builder.build()), move || { + App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { + HttpResponse::Ok() + .encoding(http::ContentEncoding::Identity) + .body(bytes) + }))) + }); -// // client connector -// let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); -// builder.set_verify(SslVerifyMode::NONE); -// let conn = client::ClientConnector::with_connector(builder.build()).start(); + // body + let mut e = CompressorWriter::new(Vec::new(), 0, 3, 0); + e.write_all(data.as_ref()).unwrap(); + let enc = e.into_inner(); -// // body -// let mut e = BrotliEncoder::new(Vec::new(), 5); -// e.write_all(data.as_ref()).unwrap(); -// let enc = e.finish().unwrap(); + // client request + let mut response = srv + .post("/") + .header(http::header::CONTENT_ENCODING, "br") + .send_body(enc) + .await + .unwrap(); + assert!(response.status().is_success()); -// // client request -// let request = client::ClientRequest::build() -// .uri(srv.url("/")) -// .method(http::Method::POST) -// .header(http::header::CONTENT_ENCODING, "br") -// .with_connector(conn) -// .body(enc) -// .unwrap(); -// let response = rt.block_on(request.send()).unwrap(); -// assert!(response.status().is_success()); - -// // read response -// let bytes = rt.block_on(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from(data)); -// } + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes, Bytes::from(data)); +} #[cfg(all(feature = "rustls", feature = "openssl"))] #[actix_rt::test] -async fn test_reading_deflate_encoding_large_random_ssl() { - use open_ssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; +async fn test_reading_deflate_encoding_large_random_rustls() { use rust_tls::internal::pemfile::{certs, pkcs8_private_keys}; use rust_tls::{NoClientAuth, ServerConfig}; use std::fs::File; use std::io::BufReader; - use std::sync::mpsc; - - let addr = TestServer::unused_addr(); - let (tx, rx) = mpsc::channel(); let data = rand::thread_rng() .sample_iter(&Alphanumeric) .take(160_000) .collect::(); - std::thread::spawn(move || { - let sys = actix_rt::System::new("test"); + // 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(); - // 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 srv = HttpServer::new(|| { - App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { - async move { - Ok::<_, Error>( - HttpResponse::Ok() - .encoding(http::ContentEncoding::Identity) - .body(bytes), - ) - } - }))) - }) - .bind_rustls(addr, config) - .unwrap() - .start(); - - let _ = tx.send((srv, actix_rt::System::current())); - let _ = sys.run(); + let srv = test::start_with(test::config().rustls(config), || { + App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { + HttpResponse::Ok() + .encoding(http::ContentEncoding::Identity) + .body(bytes) + }))) }); - let (srv, _sys) = rx.recv().unwrap(); - let client = { - let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); - builder.set_verify(SslVerifyMode::NONE); - let _ = builder.set_alpn_protos(b"\x02h2\x08http/1.1").unwrap(); - - awc::Client::build() - .connector( - awc::Connector::new() - .timeout(std::time::Duration::from_millis(500)) - .ssl(builder.build()) - .finish(), - ) - .finish() - }; // encode data let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); @@ -826,8 +727,8 @@ async fn test_reading_deflate_encoding_large_random_ssl() { let enc = e.finish().unwrap(); // client request - let req = client - .post(format!("https://localhost:{}/", addr.port())) + let req = srv + .post("/") .header(http::header::CONTENT_ENCODING, "deflate") .send_body(enc); @@ -838,14 +739,11 @@ async fn test_reading_deflate_encoding_large_random_ssl() { let bytes = response.body().await.unwrap(); assert_eq!(bytes.len(), data.len()); assert_eq!(bytes, Bytes::from(data)); - - // stop - let _ = srv.stop(false); } // #[cfg(all(feature = "tls", feature = "ssl"))] // #[test] -// fn test_reading_deflate_encoding_large_random_tls() { +// fn test_reading_deflate_encoding_large_random_nativetls() { // use native_tls::{Identity, TlsAcceptor}; // use openssl::ssl::{ // SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode, @@ -986,55 +884,31 @@ async fn test_reading_deflate_encoding_large_random_ssl() { // } // } -// #[test] -// fn test_slow_request() { -// use actix::System; +#[actix_rt::test] +async fn test_slow_request() { + use std::net; + + let srv = test::start_with(test::config().client_timeout(200), || { + App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok()))) + }); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); + + let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); + let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n"); + let mut data = String::new(); + let _ = stream.read_to_string(&mut data); + assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); +} + +// #[cfg(feature = "openssl")] +// #[actix_rt::test] +// async fn test_ssl_handshake_timeout() { +// use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; // use std::net; -// use std::sync::mpsc; -// let (tx, rx) = mpsc::channel(); - -// let addr = test::TestServer::unused_addr(); -// thread::spawn(move || { -// System::run(move || { -// let srv = server::new(|| { -// vec![App::new().resource("/", |r| { -// r.method(http::Method::GET).f(|_| HttpResponse::Ok()) -// })] -// }); - -// let srv = srv.bind(addr).unwrap(); -// srv.client_timeout(200).start(); -// let _ = tx.send(System::current()); -// }); -// }); -// let sys = rx.recv().unwrap(); - -// thread::sleep(time::Duration::from_millis(200)); - -// let mut stream = net::TcpStream::connect(addr).unwrap(); -// let mut data = String::new(); -// let _ = stream.read_to_string(&mut data); -// assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); - -// let mut stream = net::TcpStream::connect(addr).unwrap(); -// let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\n"); -// let mut data = String::new(); -// let _ = stream.read_to_string(&mut data); -// assert!(data.starts_with("HTTP/1.1 408 Request Timeout")); - -// sys.stop(); -// } - -// #[test] -// #[cfg(feature = "ssl")] -// fn test_ssl_handshake_timeout() { -// use actix::System; -// use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; -// use std::net; -// use std::sync::mpsc; - -// let (tx, rx) = mpsc::channel(); -// let addr = test::TestServer::unused_addr(); // // load ssl keys // let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); @@ -1045,28 +919,12 @@ async fn test_reading_deflate_encoding_large_random_ssl() { // .set_certificate_chain_file("tests/cert.pem") // .unwrap(); -// thread::spawn(move || { -// System::run(move || { -// let srv = server::new(|| { -// App::new().resource("/", |r| { -// r.method(http::Method::GET).f(|_| HttpResponse::Ok()) -// }) -// }); - -// srv.bind_ssl(addr, builder) -// .unwrap() -// .workers(1) -// .client_timeout(200) -// .start(); -// let _ = tx.send(System::current()); -// }); +// let srv = test::start_with(test::config().openssl(builder.build()), || { +// App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok()))) // }); -// let sys = rx.recv().unwrap(); -// let mut stream = net::TcpStream::connect(addr).unwrap(); +// let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); // let mut data = String::new(); // let _ = stream.read_to_string(&mut data); // assert!(data.is_empty()); - -// let _ = sys.stop(); // }