From 8198f5e10a8e446c082699624b2f86dfffc36442 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 20 Mar 2018 11:23:35 -0700 Subject: [PATCH] Refactor TestServer configuration --- CHANGES.md | 2 + src/client/request.rs | 1 - src/test.rs | 165 +++++++++++++++++++++++++++++++++++++++--- src/ws/mod.rs | 3 +- tests/cert.pem | 31 ++++++++ tests/key.pem | 51 +++++++++++++ tests/test_ws.rs | 27 +++++++ 7 files changed, 266 insertions(+), 14 deletions(-) create mode 100644 tests/cert.pem create mode 100644 tests/key.pem diff --git a/CHANGES.md b/CHANGES.md index ab798f06c..c4bb1ea74 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,8 @@ * Fix server websockets big payloads support +* Refactor `TestServer` configuration + ## 0.4.9 (2018-03-16) diff --git a/src/client/request.rs b/src/client/request.rs index 09f6d0c72..449bb642b 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -233,7 +233,6 @@ impl fmt::Debug for ClientRequest { } } - /// An HTTP Client request builder /// /// This type can be used to construct an instance of `ClientRequest` through a diff --git a/src/test.rs b/src/test.rs index 041ceaab8..ff358fde1 100644 --- a/src/test.rs +++ b/src/test.rs @@ -5,7 +5,7 @@ use std::rc::Rc; use std::sync::mpsc; use std::str::FromStr; -use actix::{Arbiter, Addr, Syn, System, SystemRunner, msgs}; +use actix::{Actor, Arbiter, Addr, Syn, System, SystemRunner, Unsync, msgs}; use cookie::Cookie; use http::{Uri, Method, Version, HeaderMap, HttpTryFrom}; use http::header::HeaderName; @@ -14,6 +14,9 @@ use tokio_core::net::TcpListener; use tokio_core::reactor::Core; use net2::TcpBuilder; +#[cfg(feature="alpn")] +use openssl::ssl::SslAcceptor; + use ws; use body::Binary; use error::Error; @@ -27,7 +30,7 @@ use payload::Payload; use httprequest::HttpRequest; use httpresponse::HttpResponse; use server::{HttpServer, IntoHttpHandler, ServerSettings}; -use client::{ClientRequest, ClientRequestBuilder}; +use client::{ClientRequest, ClientRequestBuilder, ClientConnector}; /// The `TestServer` type. /// @@ -60,6 +63,8 @@ pub struct TestServer { thread: Option>, system: SystemRunner, server_sys: Addr, + ssl: bool, + conn: Addr, } impl TestServer { @@ -69,9 +74,26 @@ impl TestServer { /// This method accepts configuration method. You can add /// middlewares or set handlers for test application. pub fn new(config: F) -> Self - where F: Sync + Send + 'static + Fn(&mut TestApp<()>), + where F: Sync + Send + 'static + Fn(&mut TestApp<()>) { - TestServer::with_state(||(), config) + TestServerBuilder::new(||()).start(config) + } + + /// Create test server builder + pub fn build() -> TestServerBuilder<()> { + TestServerBuilder::new(||()) + } + + /// Create test server builder with specific state factory + /// + /// This method can be used for constructing application state. + /// Also it can be used for external dependecy initialization, + /// like creating sync actors for diesel integration. + pub fn build_with_state(state: F) -> TestServerBuilder + where F: Fn() -> S + Sync + Send + 'static, + S: 'static, + { + TestServerBuilder::new(state) } /// Start new test server with application factory @@ -98,15 +120,20 @@ impl TestServer { let _ = sys.run(); }); + let sys = System::new("actix-test"); let (server_sys, addr) = rx.recv().unwrap(); TestServer { addr, - thread: Some(join), - system: System::new("actix-test"), server_sys, + ssl: false, + conn: TestServer::get_conn(), + thread: Some(join), + system: sys, } } + #[deprecated(since="0.4.10", + note="please use `TestServer::build_with_state()` instead")] /// Start new test server with custom application state /// /// This method accepts state factory and configuration method. @@ -135,12 +162,30 @@ impl TestServer { let _ = sys.run(); }); + let system = System::new("actix-test"); let (server_sys, addr) = rx.recv().unwrap(); TestServer { addr, server_sys, + system, + ssl: false, + conn: TestServer::get_conn(), thread: Some(join), - system: System::new("actix-test"), + } + } + + fn get_conn() -> Addr { + #[cfg(feature="alpn")] + { + use openssl::ssl::{SslMethod, SslConnector, SslVerifyMode}; + + let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); + builder.set_verify(SslVerifyMode::NONE); + ClientConnector::with_connector(builder.build()).start() + } + #[cfg(not(feature="alpn"))] + { + ClientConnector::default().start() } } @@ -162,9 +207,9 @@ impl TestServer { /// Construct test server url pub fn url(&self, uri: &str) -> String { if uri.starts_with('/') { - format!("http://{}{}", self.addr, uri) + format!("{}://{}{}", if self.ssl {"https"} else {"http"}, self.addr, uri) } else { - format!("http://{}/{}", self.addr, uri) + format!("{}://{}/{}", if self.ssl {"https"} else {"http"}, self.addr, uri) } } @@ -186,7 +231,8 @@ impl TestServer { /// Connect to websocket server pub fn ws(&mut self) -> Result<(ws::ClientReader, ws::ClientWriter), ws::ClientError> { let url = self.url("/"); - self.system.run_until_complete(ws::Client::new(url).connect()) + self.system.run_until_complete( + ws::Client::with_connector(url, self.conn.clone()).connect()) } /// Create `GET` request @@ -208,7 +254,9 @@ impl TestServer { pub fn client(&self, meth: Method, path: &str) -> ClientRequestBuilder { ClientRequest::build() .method(meth) - .uri(self.url(path).as_str()).take() + .uri(self.url(path).as_str()) + .with_connector(self.conn.clone()) + .take() } } @@ -218,6 +266,101 @@ impl Drop for TestServer { } } +pub struct TestServerBuilder { + state: Box S + Sync + Send + 'static>, + #[cfg(feature="alpn")] + ssl: Option, +} + +impl TestServerBuilder { + + pub fn new(state: F) -> TestServerBuilder + where F: Fn() -> S + Sync + Send + 'static + { + TestServerBuilder { + state: Box::new(state), + #[cfg(feature="alpn")] + ssl: None, + } + } + + #[cfg(feature="alpn")] + /// Create ssl server + pub fn ssl(mut self, ssl: SslAcceptor) -> Self { + self.ssl = Some(ssl); + self + } + + #[allow(unused_mut)] + /// Configure test application and run test server + pub fn start(mut self, config: F) -> TestServer + where F: Sync + Send + 'static + Fn(&mut TestApp), + { + let (tx, rx) = mpsc::channel(); + + #[cfg(feature="alpn")] + let ssl = self.ssl.is_some(); + #[cfg(not(feature="alpn"))] + let ssl = false; + + // run server in separate thread + let join = 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 tcp = TcpListener::from_listener( + tcp, &local_addr, Arbiter::handle()).unwrap(); + + let state = self.state; + + let srv = HttpServer::new(move || { + let mut app = TestApp::new(state()); + config(&mut app); + vec![app]}) + .disable_signals(); + + #[cfg(feature="alpn")] + { + use std::io; + use futures::Stream; + use tokio_openssl::SslAcceptorExt; + + let ssl = self.ssl.take(); + if let Some(ssl) = ssl { + srv.start_incoming( + tcp.incoming() + .and_then(move |(sock, addr)| { + ssl.accept_async(sock) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e)) + .map(move |s| (s, addr)) + }), + false); + } else { + srv.start_incoming(tcp.incoming(), false); + } + } + #[cfg(not(feature="alpn"))] + { + srv.start_incoming(tcp.incoming(), false); + } + + tx.send((Arbiter::system(), local_addr)).unwrap(); + let _ = sys.run(); + }); + + let system = System::new("actix-test"); + let (server_sys, addr) = rx.recv().unwrap(); + TestServer { + addr, + server_sys, + ssl, + system, + conn: TestServer::get_conn(), + thread: Some(join), + } + } +} /// Test application helper for testing request handlers. pub struct TestApp { diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 7b41cf253..12fb4d709 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -329,8 +329,7 @@ impl Stream for WsStream where S: Stream { match String::from_utf8(tmp) { Ok(s) => Ok(Async::Ready(Some(Message::Text(s)))), - Err(e) => { - println!("ENC: {:?}", e); + Err(_) => { self.closed = true; Err(ProtocolError::BadEncoding) } diff --git a/tests/cert.pem b/tests/cert.pem new file mode 100644 index 000000000..159aacea2 --- /dev/null +++ b/tests/cert.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFPjCCAyYCCQDvLYiYD+jqeTANBgkqhkiG9w0BAQsFADBhMQswCQYDVQQGEwJV +UzELMAkGA1UECAwCQ0ExCzAJBgNVBAcMAlNGMRAwDgYDVQQKDAdDb21wYW55MQww +CgYDVQQLDANPcmcxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xODAxMjUx +NzQ2MDFaFw0xOTAxMjUxNzQ2MDFaMGExCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJD +QTELMAkGA1UEBwwCU0YxEDAOBgNVBAoMB0NvbXBhbnkxDDAKBgNVBAsMA09yZzEY +MBYGA1UEAwwPd3d3LmV4YW1wbGUuY29tMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A +MIICCgKCAgEA2WzIA2IpVR9Tb9EFhITlxuhE5rY2a3S6qzYNzQVgSFggxXEPn8k1 +sQEcer5BfAP986Sck3H0FvB4Bt/I8PwOtUCmhwcc8KtB5TcGPR4fjXnrpC+MIK5U +NLkwuyBDKziYzTdBj8kUFX1WxmvEHEgqToPOZfBgsS71cJAR/zOWraDLSRM54jXy +voLZN4Ti9rQagQrvTQ44Vz5ycDQy7UxtbUGh1CVv69vNVr7/SOOh/Nw5FNOZWLWr +odGyoec5wh9iqRZgRqiTUc6Lt7V2RWc2X2gjwST2UfI+U46Ip3oaQ7ZD4eAkoqND +xdniBZAykVG3c/99ux4BAESTF8fsNch6UticBxYMuTu+ouvP0psfI9wwwNliJDmA +CRUTB9AgRynbL1AzhqQoDfsb98IZfjfNOpwnwuLwpMAPhbgd5KNdZaIJ4Hb6/stI +yFElOExxd3TAxF2Gshd/lq1JcNHAZ1DSXV5MvOWT/NWgXwbIzUgQ8eIi+HuDYX2U +UuaB6R8tbd52H7rbUv6HrfinuSlKWqjSYLkiKHkwUpoMw8y9UycRSzs1E9nPwPTO +vRXb0mNCQeBCV9FvStNVXdCUTT8LGPv87xSD2pmt7LijlE6mHLG8McfcWkzA69un +CEHIFAFDimTuN7EBljc119xWFTcHMyoZAfFF+oTqwSbBGImruCxnaJECAwEAATAN +BgkqhkiG9w0BAQsFAAOCAgEApavsgsn7SpPHfhDSN5iZs1ILZQRewJg0Bty0xPfk +3tynSW6bNH3nSaKbpsdmxxomthNSQgD2heOq1By9YzeOoNR+7Pk3s4FkASnf3ToI +JNTUasBFFfaCG96s4Yvs8KiWS/k84yaWuU8c3Wb1jXs5Rv1qE1Uvuwat1DSGXSoD +JNluuIkCsC4kWkyq5pWCGQrabWPRTWsHwC3PTcwSRBaFgYLJaR72SloHB1ot02zL +d2age9dmFRFLLCBzP+D7RojBvL37qS/HR+rQ4SoQwiVc/JzaeqSe7ZbvEH9sZYEu +ALowJzgbwro7oZflwTWunSeSGDSltkqKjvWvZI61pwfHKDahUTmZ5h2y67FuGEaC +CIOUI8dSVSPKITxaq3JL4ze2e9/0Lt7hj19YK2uUmtMAW5Tirz4Yx5lyGH9U8Wur +y/X8VPxTc4A9TMlJgkyz0hqvhbPOT/zSWB10zXh0glKAsSBryAOEDxV1UygmSir7 +YV8Qaq+oyKUTMc1MFq5vZ07M51EPaietn85t8V2Y+k/8XYltRp32NxsypxAJuyxh +g/ko6RVTrWa1sMvz/F9LFqAdKiK5eM96lh9IU4xiLg4ob8aS/GRAA8oIFkZFhLrt +tOwjIUPmEPyHWFi8dLpNuQKYalLYhuwZftG/9xV+wqhKGZO9iPrpHSYBRTap8w2y +1QU= +-----END CERTIFICATE----- diff --git a/tests/key.pem b/tests/key.pem new file mode 100644 index 000000000..aac387c64 --- /dev/null +++ b/tests/key.pem @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +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 +-----END RSA PRIVATE KEY----- diff --git a/tests/test_ws.rs b/tests/test_ws.rs index a4dd2c230..6ebb69bda 100644 --- a/tests/test_ws.rs +++ b/tests/test_ws.rs @@ -9,6 +9,9 @@ use bytes::Bytes; use futures::Stream; use rand::Rng; +#[cfg(feature="alpn")] +extern crate openssl; + use actix_web::*; use actix::prelude::*; @@ -164,3 +167,27 @@ fn test_server_send_bin() { assert_eq!(item, data); } } + +#[test] +#[cfg(feature="alpn")] +fn test_ws_server_ssl() { + extern crate openssl; + use openssl::ssl::{SslMethod, SslAcceptor, SslFiletype}; + + // 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(); + + let mut srv = test::TestServer::build() + .ssl(builder.build()) + .start(|app| app.handler(|req| ws::start(req, Ws2{count:0, bin: false}))); + let (mut reader, _writer) = srv.ws().unwrap(); + + let data = Some(ws::Message::Text("0".repeat(65_536))); + for _ in 0..10_000 { + let (item, r) = srv.execute(reader.into_future()).unwrap(); + reader = r; + assert_eq!(item, data); + } +}