1#![deny(rust_2018_idioms, nonstandard_style)]
30#![warn(future_incompatible)]
31#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
32#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
33#![cfg_attr(docsrs, feature(doc_auto_cfg))]
34
35#[cfg(feature = "openssl")]
36extern crate tls_openssl as openssl;
37
38use std::{fmt, net, thread, time::Duration};
39
40use actix_codec::{AsyncRead, AsyncWrite, Framed};
41pub use actix_http::{body::to_bytes, test::TestBuffer};
42use actix_http::{header::HeaderMap, ws, HttpService, Method, Request, Response};
43pub use actix_http_test::unused_addr;
44use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _};
45pub use actix_web::test::{
46 call_and_read_body, call_and_read_body_json, call_service, init_service, ok_service, read_body,
47 read_body_json, status_service, TestRequest,
48};
49use actix_web::{
50 body::MessageBody,
51 dev::{AppConfig, Server, ServerHandle, Service},
52 rt::{self, System},
53 web, Error,
54};
55pub use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector};
56use futures_core::Stream;
57use tokio::sync::mpsc;
58
59pub fn start<F, I, S, B>(factory: F) -> TestServer
83where
84 F: Fn() -> I + Send + Clone + 'static,
85 I: IntoServiceFactory<S, Request>,
86 S: ServiceFactory<Request, Config = AppConfig> + 'static,
87 S::Error: Into<Error> + 'static,
88 S::InitError: fmt::Debug,
89 S::Response: Into<Response<B>> + 'static,
90 <S::Service as Service<Request>>::Future: 'static,
91 B: MessageBody + 'static,
92{
93 start_with(TestServerConfig::default(), factory)
94}
95
96pub fn start_with<F, I, S, B>(cfg: TestServerConfig, factory: F) -> TestServer
122where
123 F: Fn() -> I + Send + Clone + 'static,
124 I: IntoServiceFactory<S, Request>,
125 S: ServiceFactory<Request, Config = AppConfig> + 'static,
126 S::Error: Into<Error> + 'static,
127 S::InitError: fmt::Debug,
128 S::Response: Into<Response<B>> + 'static,
129 <S::Service as Service<Request>>::Future: 'static,
130 B: MessageBody + 'static,
131{
132 let (started_tx, started_rx) = std::sync::mpsc::channel();
134
135 let (thread_stop_tx, thread_stop_rx) = mpsc::channel(1);
137
138 let tls = match cfg.stream {
139 StreamType::Tcp => false,
140 #[cfg(feature = "openssl")]
141 StreamType::Openssl(_) => true,
142 #[cfg(feature = "rustls-0_20")]
143 StreamType::Rustls020(_) => true,
144 #[cfg(feature = "rustls-0_21")]
145 StreamType::Rustls021(_) => true,
146 #[cfg(feature = "rustls-0_22")]
147 StreamType::Rustls022(_) => true,
148 #[cfg(feature = "rustls-0_23")]
149 StreamType::Rustls023(_) => true,
150 };
151
152 let client_cfg = cfg.clone();
153
154 thread::spawn(move || {
156 rt::System::new().block_on(async move {
157 let tcp = net::TcpListener::bind((cfg.listen_address.clone(), cfg.port)).unwrap();
158 let local_addr = tcp.local_addr().unwrap();
159 let factory = factory.clone();
160 let srv_cfg = cfg.clone();
161 let timeout = cfg.client_request_timeout;
162
163 let builder = Server::build()
164 .workers(cfg.workers)
165 .disable_signals()
166 .system_exit();
167
168 let srv = match srv_cfg.stream {
169 StreamType::Tcp => match srv_cfg.tp {
170 HttpVer::Http1 => builder.listen("test", tcp, move || {
171 let app_cfg =
172 AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
173
174 let fac = factory()
175 .into_factory()
176 .map_err(|err| err.into().error_response());
177
178 HttpService::build()
179 .client_request_timeout(timeout)
180 .h1(map_config(fac, move |_| app_cfg.clone()))
181 .tcp()
182 }),
183 HttpVer::Http2 => builder.listen("test", tcp, move || {
184 let app_cfg =
185 AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
186
187 let fac = factory()
188 .into_factory()
189 .map_err(|err| err.into().error_response());
190
191 HttpService::build()
192 .client_request_timeout(timeout)
193 .h2(map_config(fac, move |_| app_cfg.clone()))
194 .tcp()
195 }),
196 HttpVer::Both => builder.listen("test", tcp, move || {
197 let app_cfg =
198 AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
199
200 let fac = factory()
201 .into_factory()
202 .map_err(|err| err.into().error_response());
203
204 HttpService::build()
205 .client_request_timeout(timeout)
206 .finish(map_config(fac, move |_| app_cfg.clone()))
207 .tcp()
208 }),
209 },
210 #[cfg(feature = "openssl")]
211 StreamType::Openssl(acceptor) => match cfg.tp {
212 HttpVer::Http1 => builder.listen("test", tcp, move || {
213 let app_cfg =
214 AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
215
216 let fac = factory()
217 .into_factory()
218 .map_err(|err| err.into().error_response());
219
220 HttpService::build()
221 .client_request_timeout(timeout)
222 .h1(map_config(fac, move |_| app_cfg.clone()))
223 .openssl(acceptor.clone())
224 }),
225 HttpVer::Http2 => builder.listen("test", tcp, move || {
226 let app_cfg =
227 AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
228
229 let fac = factory()
230 .into_factory()
231 .map_err(|err| err.into().error_response());
232
233 HttpService::build()
234 .client_request_timeout(timeout)
235 .h2(map_config(fac, move |_| app_cfg.clone()))
236 .openssl(acceptor.clone())
237 }),
238 HttpVer::Both => builder.listen("test", tcp, move || {
239 let app_cfg =
240 AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
241
242 let fac = factory()
243 .into_factory()
244 .map_err(|err| err.into().error_response());
245
246 HttpService::build()
247 .client_request_timeout(timeout)
248 .finish(map_config(fac, move |_| app_cfg.clone()))
249 .openssl(acceptor.clone())
250 }),
251 },
252 #[cfg(feature = "rustls-0_20")]
253 StreamType::Rustls020(config) => match cfg.tp {
254 HttpVer::Http1 => builder.listen("test", tcp, move || {
255 let app_cfg =
256 AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
257
258 let fac = factory()
259 .into_factory()
260 .map_err(|err| err.into().error_response());
261
262 HttpService::build()
263 .client_request_timeout(timeout)
264 .h1(map_config(fac, move |_| app_cfg.clone()))
265 .rustls(config.clone())
266 }),
267 HttpVer::Http2 => builder.listen("test", tcp, move || {
268 let app_cfg =
269 AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
270
271 let fac = factory()
272 .into_factory()
273 .map_err(|err| err.into().error_response());
274
275 HttpService::build()
276 .client_request_timeout(timeout)
277 .h2(map_config(fac, move |_| app_cfg.clone()))
278 .rustls(config.clone())
279 }),
280 HttpVer::Both => builder.listen("test", tcp, move || {
281 let app_cfg =
282 AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
283
284 let fac = factory()
285 .into_factory()
286 .map_err(|err| err.into().error_response());
287
288 HttpService::build()
289 .client_request_timeout(timeout)
290 .finish(map_config(fac, move |_| app_cfg.clone()))
291 .rustls(config.clone())
292 }),
293 },
294 #[cfg(feature = "rustls-0_21")]
295 StreamType::Rustls021(config) => match cfg.tp {
296 HttpVer::Http1 => builder.listen("test", tcp, move || {
297 let app_cfg =
298 AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
299
300 let fac = factory()
301 .into_factory()
302 .map_err(|err| err.into().error_response());
303
304 HttpService::build()
305 .client_request_timeout(timeout)
306 .h1(map_config(fac, move |_| app_cfg.clone()))
307 .rustls_021(config.clone())
308 }),
309 HttpVer::Http2 => builder.listen("test", tcp, move || {
310 let app_cfg =
311 AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
312
313 let fac = factory()
314 .into_factory()
315 .map_err(|err| err.into().error_response());
316
317 HttpService::build()
318 .client_request_timeout(timeout)
319 .h2(map_config(fac, move |_| app_cfg.clone()))
320 .rustls_021(config.clone())
321 }),
322 HttpVer::Both => builder.listen("test", tcp, move || {
323 let app_cfg =
324 AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
325
326 let fac = factory()
327 .into_factory()
328 .map_err(|err| err.into().error_response());
329
330 HttpService::build()
331 .client_request_timeout(timeout)
332 .finish(map_config(fac, move |_| app_cfg.clone()))
333 .rustls_021(config.clone())
334 }),
335 },
336 #[cfg(feature = "rustls-0_22")]
337 StreamType::Rustls022(config) => match cfg.tp {
338 HttpVer::Http1 => builder.listen("test", tcp, move || {
339 let app_cfg =
340 AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
341
342 let fac = factory()
343 .into_factory()
344 .map_err(|err| err.into().error_response());
345
346 HttpService::build()
347 .client_request_timeout(timeout)
348 .h1(map_config(fac, move |_| app_cfg.clone()))
349 .rustls_0_22(config.clone())
350 }),
351 HttpVer::Http2 => builder.listen("test", tcp, move || {
352 let app_cfg =
353 AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
354
355 let fac = factory()
356 .into_factory()
357 .map_err(|err| err.into().error_response());
358
359 HttpService::build()
360 .client_request_timeout(timeout)
361 .h2(map_config(fac, move |_| app_cfg.clone()))
362 .rustls_0_22(config.clone())
363 }),
364 HttpVer::Both => builder.listen("test", tcp, move || {
365 let app_cfg =
366 AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
367
368 let fac = factory()
369 .into_factory()
370 .map_err(|err| err.into().error_response());
371
372 HttpService::build()
373 .client_request_timeout(timeout)
374 .finish(map_config(fac, move |_| app_cfg.clone()))
375 .rustls_0_22(config.clone())
376 }),
377 },
378 #[cfg(feature = "rustls-0_23")]
379 StreamType::Rustls023(config) => match cfg.tp {
380 HttpVer::Http1 => builder.listen("test", tcp, move || {
381 let app_cfg =
382 AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
383
384 let fac = factory()
385 .into_factory()
386 .map_err(|err| err.into().error_response());
387
388 HttpService::build()
389 .client_request_timeout(timeout)
390 .h1(map_config(fac, move |_| app_cfg.clone()))
391 .rustls_0_23(config.clone())
392 }),
393 HttpVer::Http2 => builder.listen("test", tcp, move || {
394 let app_cfg =
395 AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
396
397 let fac = factory()
398 .into_factory()
399 .map_err(|err| err.into().error_response());
400
401 HttpService::build()
402 .client_request_timeout(timeout)
403 .h2(map_config(fac, move |_| app_cfg.clone()))
404 .rustls_0_23(config.clone())
405 }),
406 HttpVer::Both => builder.listen("test", tcp, move || {
407 let app_cfg =
408 AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
409
410 let fac = factory()
411 .into_factory()
412 .map_err(|err| err.into().error_response());
413
414 HttpService::build()
415 .client_request_timeout(timeout)
416 .finish(map_config(fac, move |_| app_cfg.clone()))
417 .rustls_0_23(config.clone())
418 }),
419 },
420 }
421 .expect("test server could not be created");
422
423 let srv = srv.run();
424 started_tx
425 .send((System::current(), srv.handle(), local_addr))
426 .unwrap();
427
428 srv.await.unwrap();
430
431 });
434
435 #[allow(clippy::let_underscore_future)]
436 let _ = thread_stop_tx.send(());
437 });
438
439 let (system, server, addr) = started_rx.recv().unwrap();
440
441 let client = {
442 let connector = {
443 #[cfg(feature = "openssl")]
444 {
445 use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode};
446
447 let mut builder = SslConnector::builder(SslMethod::tls()).unwrap();
448 builder.set_verify(SslVerifyMode::NONE);
449 let _ = builder
450 .set_alpn_protos(b"\x02h2\x08http/1.1")
451 .map_err(|err| log::error!("Can not set alpn protocol: {err:?}"));
452 Connector::new()
453 .conn_lifetime(Duration::from_secs(0))
454 .timeout(Duration::from_millis(30000))
455 .openssl(builder.build())
456 }
457 #[cfg(not(feature = "openssl"))]
458 {
459 Connector::new()
460 .conn_lifetime(Duration::from_secs(0))
461 .timeout(Duration::from_millis(30000))
462 }
463 };
464
465 let mut client_builder = Client::builder().connector(connector);
466
467 if client_cfg.disable_redirects {
468 client_builder = client_builder.disable_redirects();
469 }
470
471 client_builder.finish()
472 };
473
474 TestServer {
475 server,
476 thread_stop_rx,
477 client,
478 system,
479 addr,
480 tls,
481 }
482}
483
484#[derive(Debug, Clone)]
485enum HttpVer {
486 Http1,
487 Http2,
488 Both,
489}
490
491#[allow(clippy::large_enum_variant)]
492#[derive(Clone)]
493enum StreamType {
494 Tcp,
495 #[cfg(feature = "openssl")]
496 Openssl(openssl::ssl::SslAcceptor),
497 #[cfg(feature = "rustls-0_20")]
498 Rustls020(tls_rustls_0_20::ServerConfig),
499 #[cfg(feature = "rustls-0_21")]
500 Rustls021(tls_rustls_0_21::ServerConfig),
501 #[cfg(feature = "rustls-0_22")]
502 Rustls022(tls_rustls_0_22::ServerConfig),
503 #[cfg(feature = "rustls-0_23")]
504 Rustls023(tls_rustls_0_23::ServerConfig),
505}
506
507pub fn config() -> TestServerConfig {
509 TestServerConfig::default()
510}
511
512#[derive(Clone)]
513pub struct TestServerConfig {
514 tp: HttpVer,
515 stream: StreamType,
516 client_request_timeout: Duration,
517 listen_address: String,
518 port: u16,
519 workers: usize,
520 disable_redirects: bool,
521}
522
523impl Default for TestServerConfig {
524 fn default() -> Self {
525 TestServerConfig::new()
526 }
527}
528
529impl TestServerConfig {
530 pub(crate) fn new() -> TestServerConfig {
532 TestServerConfig {
533 tp: HttpVer::Both,
534 stream: StreamType::Tcp,
535 client_request_timeout: Duration::from_secs(5),
536 listen_address: "127.0.0.1".to_string(),
537 port: 0,
538 workers: 1,
539 disable_redirects: false,
540 }
541 }
542
543 pub fn h1(mut self) -> Self {
545 self.tp = HttpVer::Http1;
546 self
547 }
548
549 pub fn h2(mut self) -> Self {
551 self.tp = HttpVer::Http2;
552 self
553 }
554
555 #[cfg(feature = "openssl")]
557 pub fn openssl(mut self, acceptor: openssl::ssl::SslAcceptor) -> Self {
558 self.stream = StreamType::Openssl(acceptor);
559 self
560 }
561
562 #[doc(hidden)]
563 #[deprecated(note = "Renamed to `rustls_0_20()`.")]
564 #[cfg(feature = "rustls-0_20")]
565 pub fn rustls(mut self, config: tls_rustls_0_20::ServerConfig) -> Self {
566 self.stream = StreamType::Rustls020(config);
567 self
568 }
569
570 #[cfg(feature = "rustls-0_20")]
572 pub fn rustls_0_20(mut self, config: tls_rustls_0_20::ServerConfig) -> Self {
573 self.stream = StreamType::Rustls020(config);
574 self
575 }
576
577 #[doc(hidden)]
578 #[deprecated(note = "Renamed to `rustls_0_21()`.")]
579 #[cfg(feature = "rustls-0_21")]
580 pub fn rustls_021(mut self, config: tls_rustls_0_21::ServerConfig) -> Self {
581 self.stream = StreamType::Rustls021(config);
582 self
583 }
584
585 #[cfg(feature = "rustls-0_21")]
587 pub fn rustls_0_21(mut self, config: tls_rustls_0_21::ServerConfig) -> Self {
588 self.stream = StreamType::Rustls021(config);
589 self
590 }
591
592 #[cfg(feature = "rustls-0_22")]
594 pub fn rustls_0_22(mut self, config: tls_rustls_0_22::ServerConfig) -> Self {
595 self.stream = StreamType::Rustls022(config);
596 self
597 }
598
599 #[cfg(feature = "rustls-0_23")]
601 pub fn rustls_0_23(mut self, config: tls_rustls_0_23::ServerConfig) -> Self {
602 self.stream = StreamType::Rustls023(config);
603 self
604 }
605
606 pub fn client_request_timeout(mut self, dur: Duration) -> Self {
608 self.client_request_timeout = dur;
609 self
610 }
611
612 pub fn listen_address(mut self, addr: impl Into<String>) -> Self {
616 self.listen_address = addr.into();
617 self
618 }
619
620 pub fn port(mut self, port: u16) -> Self {
624 self.port = port;
625 self
626 }
627
628 pub fn workers(mut self, workers: usize) -> Self {
632 self.workers = workers;
633 self
634 }
635
636 pub fn disable_redirects(mut self) -> Self {
641 self.disable_redirects = true;
642 self
643 }
644}
645
646pub struct TestServer {
651 server: ServerHandle,
652 thread_stop_rx: mpsc::Receiver<()>,
653 client: awc::Client,
654 system: rt::System,
655 addr: net::SocketAddr,
656 tls: bool,
657}
658
659impl TestServer {
660 pub fn addr(&self) -> net::SocketAddr {
662 self.addr
663 }
664
665 pub fn url(&self, uri: &str) -> String {
667 let scheme = if self.tls { "https" } else { "http" };
668
669 if uri.starts_with('/') {
670 format!("{}://{}{}", scheme, self.addr, uri)
671 } else {
672 format!("{}://{}/{}", scheme, self.addr, uri)
673 }
674 }
675
676 pub fn get(&self, path: impl AsRef<str>) -> ClientRequest {
678 self.client.get(self.url(path.as_ref()).as_str())
679 }
680
681 pub fn post(&self, path: impl AsRef<str>) -> ClientRequest {
683 self.client.post(self.url(path.as_ref()).as_str())
684 }
685
686 pub fn head(&self, path: impl AsRef<str>) -> ClientRequest {
688 self.client.head(self.url(path.as_ref()).as_str())
689 }
690
691 pub fn put(&self, path: impl AsRef<str>) -> ClientRequest {
693 self.client.put(self.url(path.as_ref()).as_str())
694 }
695
696 pub fn patch(&self, path: impl AsRef<str>) -> ClientRequest {
698 self.client.patch(self.url(path.as_ref()).as_str())
699 }
700
701 pub fn delete(&self, path: impl AsRef<str>) -> ClientRequest {
703 self.client.delete(self.url(path.as_ref()).as_str())
704 }
705
706 pub fn options(&self, path: impl AsRef<str>) -> ClientRequest {
708 self.client.options(self.url(path.as_ref()).as_str())
709 }
710
711 pub fn request(&self, method: Method, path: impl AsRef<str>) -> ClientRequest {
713 self.client.request(method, path.as_ref())
714 }
715
716 pub async fn load_body<S>(
717 &mut self,
718 mut response: ClientResponse<S>,
719 ) -> Result<web::Bytes, PayloadError>
720 where
721 S: Stream<Item = Result<web::Bytes, PayloadError>> + Unpin + 'static,
722 {
723 response.body().limit(10_485_760).await
724 }
725
726 pub async fn ws_at(
728 &mut self,
729 path: &str,
730 ) -> Result<Framed<impl AsyncRead + AsyncWrite, ws::Codec>, awc::error::WsClientError> {
731 let url = self.url(path);
732 let connect = self.client.ws(url).connect();
733 connect.await.map(|(_, framed)| framed)
734 }
735
736 pub async fn ws(
738 &mut self,
739 ) -> Result<Framed<impl AsyncRead + AsyncWrite, ws::Codec>, awc::error::WsClientError> {
740 self.ws_at("/").await
741 }
742
743 pub fn client_headers(&mut self) -> Option<&mut HeaderMap> {
748 self.client.headers()
749 }
750
751 pub async fn stop(mut self) {
755 self.server.stop(false).await;
757
758 self.system.stop();
761
762 let _ = self.thread_stop_rx.recv().await;
764 }
765}
766
767impl Drop for TestServer {
768 fn drop(&mut self) {
769 #[allow(clippy::let_underscore_future)]
774 let _ = self.server.stop(true);
775
776 self.system.stop();
778 }
779}