actix_test/
lib.rs

1//! Integration testing tools for Actix Web applications.
2//!
3//! The main integration testing tool is [`TestServer`]. It spawns a real HTTP server on an
4//! unused port and provides methods that use a real HTTP client. Therefore, it is much closer to
5//! real-world cases than using `init_service`, which skips HTTP encoding and decoding.
6//!
7//! # Examples
8//! ```
9//! use actix_web::{get, web, test, App, HttpResponse, Error, Responder};
10//!
11//! #[get("/")]
12//! async fn my_handler() -> Result<impl Responder, Error> {
13//!     Ok(HttpResponse::Ok())
14//! }
15//!
16//! #[actix_rt::test]
17//! async fn test_example() {
18//!     let srv = actix_test::start(||
19//!         App::new().service(my_handler)
20//!     );
21//!
22//!     let req = srv.get("/");
23//!     let res = req.send().await.unwrap();
24//!
25//!     assert!(res.status().is_success());
26//! }
27//! ```
28
29#![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
59/// Start default [`TestServer`].
60///
61/// # Examples
62/// ```
63/// use actix_web::{get, web, test, App, HttpResponse, Error, Responder};
64///
65/// #[get("/")]
66/// async fn my_handler() -> Result<impl Responder, Error> {
67///     Ok(HttpResponse::Ok())
68/// }
69///
70/// #[actix_web::test]
71/// async fn test_example() {
72///     let srv = actix_test::start(||
73///         App::new().service(my_handler)
74///     );
75///
76///     let req = srv.get("/");
77///     let res = req.send().await.unwrap();
78///
79///     assert!(res.status().is_success());
80/// }
81/// ```
82pub 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
96/// Start test server with custom configuration
97///
98/// Check [`TestServerConfig`] docs for configuration options.
99///
100/// # Examples
101/// ```
102/// use actix_web::{get, web, test, App, HttpResponse, Error, Responder};
103///
104/// #[get("/")]
105/// async fn my_handler() -> Result<impl Responder, Error> {
106///     Ok(HttpResponse::Ok())
107/// }
108///
109/// #[actix_web::test]
110/// async fn test_example() {
111///     let srv = actix_test::start_with(actix_test::config().h1(), ||
112///         App::new().service(my_handler)
113///     );
114///
115///     let req = srv.get("/");
116///     let res = req.send().await.unwrap();
117///
118///     assert!(res.status().is_success());
119/// }
120/// ```
121pub 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    // for sending handles and server info back from the spawned thread
133    let (started_tx, started_rx) = std::sync::mpsc::channel();
134
135    // for signaling the shutdown of spawned server and system
136    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    // run server in separate orphaned thread
155    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            // drive server loop
429            srv.await.unwrap();
430
431            // notify TestServer that server and system have shut down
432            // all thread managed resources should be dropped at this point
433        });
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
507/// Create default test server config.
508pub 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    /// Constructs default server configuration.
531    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    /// Accepts HTTP/1.1 only.
544    pub fn h1(mut self) -> Self {
545        self.tp = HttpVer::Http1;
546        self
547    }
548
549    /// Accepts HTTP/2 only.
550    pub fn h2(mut self) -> Self {
551        self.tp = HttpVer::Http2;
552        self
553    }
554
555    /// Accepts secure connections via OpenSSL.
556    #[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    /// Accepts secure connections via Rustls v0.20.
571    #[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    /// Accepts secure connections via Rustls v0.21.
586    #[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    /// Accepts secure connections via Rustls v0.22.
593    #[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    /// Accepts secure connections via Rustls v0.23.
600    #[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    /// Sets client timeout for first request.
607    pub fn client_request_timeout(mut self, dur: Duration) -> Self {
608        self.client_request_timeout = dur;
609        self
610    }
611
612    /// Sets the address the server will listen on.
613    ///
614    /// By default, only listens on `127.0.0.1`.
615    pub fn listen_address(mut self, addr: impl Into<String>) -> Self {
616        self.listen_address = addr.into();
617        self
618    }
619
620    /// Sets test server port.
621    ///
622    /// By default, a random free port is determined by the OS.
623    pub fn port(mut self, port: u16) -> Self {
624        self.port = port;
625        self
626    }
627
628    /// Sets number of workers for the test server.
629    ///
630    /// By default, the server uses 1 worker
631    pub fn workers(mut self, workers: usize) -> Self {
632        self.workers = workers;
633        self
634    }
635
636    /// Instruct the client to not follow redirects.
637    ///
638    /// By default, the client will follow up to 10 consecutive redirects
639    /// before giving up.
640    pub fn disable_redirects(mut self) -> Self {
641        self.disable_redirects = true;
642        self
643    }
644}
645
646/// A basic HTTP server controller that simplifies the process of writing integration tests for
647/// Actix Web applications.
648///
649/// See [`start`] for usage example.
650pub 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    /// Construct test server url
661    pub fn addr(&self) -> net::SocketAddr {
662        self.addr
663    }
664
665    /// Construct test server url
666    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    /// Create `GET` request.
677    pub fn get(&self, path: impl AsRef<str>) -> ClientRequest {
678        self.client.get(self.url(path.as_ref()).as_str())
679    }
680
681    /// Create `POST` request.
682    pub fn post(&self, path: impl AsRef<str>) -> ClientRequest {
683        self.client.post(self.url(path.as_ref()).as_str())
684    }
685
686    /// Create `HEAD` request.
687    pub fn head(&self, path: impl AsRef<str>) -> ClientRequest {
688        self.client.head(self.url(path.as_ref()).as_str())
689    }
690
691    /// Create `PUT` request.
692    pub fn put(&self, path: impl AsRef<str>) -> ClientRequest {
693        self.client.put(self.url(path.as_ref()).as_str())
694    }
695
696    /// Create `PATCH` request.
697    pub fn patch(&self, path: impl AsRef<str>) -> ClientRequest {
698        self.client.patch(self.url(path.as_ref()).as_str())
699    }
700
701    /// Create `DELETE` request.
702    pub fn delete(&self, path: impl AsRef<str>) -> ClientRequest {
703        self.client.delete(self.url(path.as_ref()).as_str())
704    }
705
706    /// Create `OPTIONS` request.
707    pub fn options(&self, path: impl AsRef<str>) -> ClientRequest {
708        self.client.options(self.url(path.as_ref()).as_str())
709    }
710
711    /// Connect request with given method and path.
712    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    /// Connect to WebSocket server at a given path.
727    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    /// Connect to a WebSocket server.
737    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    /// Get default HeaderMap of Client.
744    ///
745    /// Returns Some(&mut HeaderMap) when Client object is unique
746    /// (No other clone of client exists at the same time).
747    pub fn client_headers(&mut self) -> Option<&mut HeaderMap> {
748        self.client.headers()
749    }
750
751    /// Stop HTTP server.
752    ///
753    /// Waits for spawned `Server` and `System` to shutdown (force) shutdown.
754    pub async fn stop(mut self) {
755        // signal server to stop
756        self.server.stop(false).await;
757
758        // also signal system to stop
759        // though this is handled by `ServerBuilder::exit_system` too
760        self.system.stop();
761
762        // wait for thread to be stopped but don't care about result
763        let _ = self.thread_stop_rx.recv().await;
764    }
765}
766
767impl Drop for TestServer {
768    fn drop(&mut self) {
769        // calls in this Drop impl should be enough to shut down the server, system, and thread
770        // without needing to await anything
771
772        // signal server to stop
773        #[allow(clippy::let_underscore_future)]
774        let _ = self.server.stop(true);
775
776        // signal system to stop
777        self.system.stop();
778    }
779}