1use crate::{
2 Body, Request, Response, StatusCode,
3 layer::error_handling::ErrorHandlerLayer,
4 matcher::HttpMatcher,
5 mime::Mime,
6 service::{
7 fs::{DirectoryServeMode, ServeDir},
8 web::{
9 IntoEndpointServiceWithState, endpoint::response::IntoResponse, response::ErrorResponse,
10 },
11 },
12};
13
14use rama_core::{
15 Layer,
16 extensions::Extensions,
17 extensions::ExtensionsRef,
18 matcher::Matcher,
19 service::{BoxService, Service, service_fn},
20 telemetry::tracing,
21};
22use rama_http_types::OriginalRouterUri;
23use rama_net::uri::PathMatchOptions;
24use rama_utils::{include_dir, str::arcstr::ArcStr};
25
26use std::{convert::Infallible, path::Path, sync::Arc};
27
28use super::{IntoEndpointService, endpoint::Endpoint};
29
30type DefaultLayer = ErrorHandlerLayer;
31
32#[derive(Debug, Clone)]
39pub struct WebService<State = ()> {
40 endpoints: Vec<Arc<Endpoint>>,
41 not_found: BoxService<Request, Response, Infallible>,
42 state: State,
43}
44
45impl WebService {
46 #[must_use]
47 pub fn new() -> Self {
49 Self {
50 endpoints: Vec::new(),
51 not_found: service_fn(async || Ok(StatusCode::NOT_FOUND.into_response())).boxed(),
52 state: (),
53 }
54 }
55}
56
57impl<State> WebService<State>
58where
59 State: Send + Sync + Clone + 'static,
60{
61 pub fn new_with_state(state: State) -> Self {
62 Self {
63 endpoints: Vec::new(),
64 not_found: service_fn(async || Ok(StatusCode::NOT_FOUND.into_response())).boxed(),
65 state,
66 }
67 }
68
69 #[must_use]
71 #[inline]
72 pub fn with_get<I, T>(self, path: &str, service: I) -> Self
73 where
74 I: IntoEndpointServiceWithState<T, State>,
75 I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
76 {
77 let matcher = HttpMatcher::method_get().and_path(path);
78 self.with_matcher(matcher, service)
79 }
80
81 #[inline]
83 pub fn set_get<I, T>(&mut self, path: &str, service: I) -> &mut Self
84 where
85 I: IntoEndpointServiceWithState<T, State>,
86 I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
87 {
88 let matcher = HttpMatcher::method_get().and_path(path);
89 self.set_matcher(matcher, service)
90 }
91
92 #[must_use]
94 #[inline]
95 pub fn with_post<I, T>(self, path: &str, service: I) -> Self
96 where
97 I: IntoEndpointServiceWithState<T, State>,
98 I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
99 {
100 let matcher = HttpMatcher::method_post().and_path(path);
101 self.with_matcher(matcher, service)
102 }
103
104 #[inline]
106 pub fn set_post<I, T>(&mut self, path: &str, service: I) -> &mut Self
107 where
108 I: IntoEndpointServiceWithState<T, State>,
109 I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
110 {
111 let matcher = HttpMatcher::method_post().and_path(path);
112 self.set_matcher(matcher, service)
113 }
114
115 #[must_use]
117 #[inline]
118 pub fn with_put<I, T>(self, path: &str, service: I) -> Self
119 where
120 I: IntoEndpointServiceWithState<T, State>,
121 I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
122 {
123 let matcher = HttpMatcher::method_put().and_path(path);
124 self.with_matcher(matcher, service)
125 }
126
127 #[inline]
129 pub fn set_put<I, T>(&mut self, path: &str, service: I) -> &mut Self
130 where
131 I: IntoEndpointServiceWithState<T, State>,
132 I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
133 {
134 let matcher = HttpMatcher::method_put().and_path(path);
135 self.set_matcher(matcher, service)
136 }
137
138 #[must_use]
140 #[inline]
141 pub fn with_delete<I, T>(self, path: &str, service: I) -> Self
142 where
143 I: IntoEndpointServiceWithState<T, State>,
144 I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
145 {
146 let matcher = HttpMatcher::method_delete().and_path(path);
147 self.with_matcher(matcher, service)
148 }
149
150 #[inline]
152 pub fn set_delete<I, T>(&mut self, path: &str, service: I) -> &mut Self
153 where
154 I: IntoEndpointServiceWithState<T, State>,
155 I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
156 {
157 let matcher = HttpMatcher::method_delete().and_path(path);
158 self.set_matcher(matcher, service)
159 }
160
161 #[must_use]
163 #[inline]
164 pub fn with_patch<I, T>(self, path: &str, service: I) -> Self
165 where
166 I: IntoEndpointServiceWithState<T, State>,
167 I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
168 {
169 let matcher = HttpMatcher::method_patch().and_path(path);
170 self.with_matcher(matcher, service)
171 }
172
173 #[inline]
175 pub fn set_patch<I, T>(&mut self, path: &str, service: I) -> &mut Self
176 where
177 I: IntoEndpointServiceWithState<T, State>,
178 I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
179 {
180 let matcher = HttpMatcher::method_patch().and_path(path);
181 self.set_matcher(matcher, service)
182 }
183
184 #[must_use]
186 #[inline]
187 pub fn with_head<I, T>(self, path: &str, service: I) -> Self
188 where
189 I: IntoEndpointServiceWithState<T, State>,
190 I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
191 {
192 let matcher = HttpMatcher::method_head().and_path(path);
193 self.with_matcher(matcher, service)
194 }
195
196 #[inline]
198 pub fn set_head<I, T>(&mut self, path: &str, service: I) -> &mut Self
199 where
200 I: IntoEndpointServiceWithState<T, State>,
201 I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
202 {
203 let matcher = HttpMatcher::method_head().and_path(path);
204 self.set_matcher(matcher, service)
205 }
206
207 #[must_use]
209 #[inline]
210 pub fn with_options<I, T>(self, path: &str, service: I) -> Self
211 where
212 I: IntoEndpointServiceWithState<T, State>,
213 I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
214 {
215 let matcher = HttpMatcher::method_options().and_path(path);
216 self.with_matcher(matcher, service)
217 }
218
219 #[inline]
221 pub fn set_options<I, T>(&mut self, path: &str, service: I) -> &mut Self
222 where
223 I: IntoEndpointServiceWithState<T, State>,
224 I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
225 {
226 let matcher = HttpMatcher::method_options().and_path(path);
227 self.set_matcher(matcher, service)
228 }
229
230 #[must_use]
232 #[inline]
233 pub fn with_trace<I, T>(self, path: &str, service: I) -> Self
234 where
235 I: IntoEndpointServiceWithState<T, State>,
236 I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
237 {
238 let matcher = HttpMatcher::method_trace().and_path(path);
239 self.with_matcher(matcher, service)
240 }
241
242 #[inline]
244 pub fn set_trace<I, T>(&mut self, path: &str, service: I) -> &mut Self
245 where
246 I: IntoEndpointServiceWithState<T, State>,
247 I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
248 {
249 let matcher = HttpMatcher::method_trace().and_path(path);
250 self.set_matcher(matcher, service)
251 }
252
253 #[must_use]
259 pub fn with_nest_make_fn(self, prefix: &str, configure_svc: impl FnOnce(Self) -> Self) -> Self {
260 let web_service = Self::new_with_state(self.state.clone());
261 let web_service = configure_svc(web_service);
262 self.with_nest_inner(prefix, web_service)
263 }
264
265 pub fn set_nest_make_fn(
271 &mut self,
272 prefix: &str,
273 configure_svc: impl FnOnce(Self) -> Self,
274 ) -> &mut Self {
275 let web_service = Self::new_with_state(self.state.clone());
276 let web_service = configure_svc(web_service);
277 self.set_nest_inner(prefix, web_service)
278 }
279
280 #[must_use]
287 #[inline(always)]
288 pub fn with_nest_service<I, T>(self, prefix: impl AsRef<str>, service: I) -> Self
289 where
290 I: IntoEndpointService<T>,
291 I::Service: Service<Request, Output = Response, Error = Infallible>,
292 {
293 self.with_nest_inner(prefix, service.into_endpoint_service())
294 }
295
296 #[inline(always)]
303 pub fn set_nest_service<I, T>(&mut self, prefix: impl AsRef<str>, service: I) -> &mut Self
304 where
305 I: IntoEndpointService<T>,
306 I::Service: Service<Request, Output = Response, Error = Infallible>,
307 {
308 self.set_nest_inner(prefix, service.into_endpoint_service())
309 }
310
311 #[inline]
312 fn with_nest_inner<S>(mut self, prefix: impl AsRef<str>, inner: S) -> Self
313 where
314 S: Service<Request, Output = Response, Error = Infallible>,
315 {
316 self.set_nest_inner(prefix, inner);
317 self
318 }
319
320 fn set_nest_inner<S>(&mut self, prefix: impl AsRef<str>, inner: S) -> &mut Self
321 where
322 S: Service<Request, Output = Response, Error = Infallible>,
323 {
324 let prefix = prefix
325 .as_ref()
326 .trim_end_matches(['/', '*'])
327 .trim_start_matches('/');
328 let matcher = HttpMatcher::path_prefix(prefix);
329 let service = NestedService {
330 inner,
331 prefix: ArcStr::from(prefix),
332 };
333 self.set_matcher(matcher, service)
334 }
335
336 #[must_use]
338 pub fn with_file(self, prefix: &str, path: impl AsRef<Path>, mime: Mime) -> Self {
339 let service = ServeDir::new_single_file(path, mime).fallback(self.not_found.clone());
340 self.with_nest_inner(prefix, service)
341 }
342
343 pub fn set_file(&mut self, prefix: &str, path: impl AsRef<Path>, mime: Mime) -> &mut Self {
345 let service = ServeDir::new_single_file(path, mime).fallback(self.not_found.clone());
346 self.set_nest_inner(prefix, service)
347 }
348
349 #[inline]
351 #[must_use]
352 pub fn with_dir(self, prefix: &str, path: impl AsRef<Path>) -> Self {
353 self.with_dir_with_serve_mode(prefix, path, Default::default())
354 }
355
356 #[inline]
358 pub fn set_dir(&mut self, prefix: &str, path: impl AsRef<Path>) -> &mut Self {
359 self.set_dir_with_serve_mode(prefix, path, Default::default())
360 }
361
362 #[must_use]
365 pub fn with_dir_with_serve_mode(
366 self,
367 prefix: &str,
368 path: impl AsRef<Path>,
369 mode: DirectoryServeMode,
370 ) -> Self {
371 let service = ServeDir::new(path)
372 .fallback(self.not_found.clone())
373 .with_directory_serve_mode(mode);
374 self.with_nest_service(prefix, service)
375 }
376
377 pub fn set_dir_with_serve_mode(
380 &mut self,
381 prefix: &str,
382 path: impl AsRef<Path>,
383 mode: DirectoryServeMode,
384 ) -> &mut Self {
385 let service = ServeDir::new(path)
386 .fallback(self.not_found.clone())
387 .with_directory_serve_mode(mode);
388 self.set_nest_service(prefix, service)
389 }
390
391 #[inline]
393 #[must_use]
394 pub fn with_dir_embed(self, prefix: &str, dir: include_dir::Dir<'static>) -> Self {
395 self.with_dir_embed_with_serve_mode(prefix, dir, Default::default())
396 }
397
398 #[inline]
400 pub fn set_dir_embed(&mut self, prefix: &str, dir: include_dir::Dir<'static>) -> &mut Self {
401 self.set_dir_embed_with_serve_mode(prefix, dir, Default::default())
402 }
403
404 #[must_use]
407 pub fn with_dir_embed_with_serve_mode(
408 self,
409 prefix: &str,
410 dir: include_dir::Dir<'static>,
411 mode: DirectoryServeMode,
412 ) -> Self {
413 let service = ServeDir::new_embedded(dir)
414 .fallback(self.not_found.clone())
415 .with_directory_serve_mode(mode);
416 self.with_nest_service(prefix, service)
417 }
418
419 pub fn set_dir_embed_with_serve_mode(
422 &mut self,
423 prefix: &str,
424 dir: include_dir::Dir<'static>,
425 mode: DirectoryServeMode,
426 ) -> &mut Self {
427 let service = ServeDir::new_embedded(dir)
428 .fallback(self.not_found.clone())
429 .with_directory_serve_mode(mode);
430 self.set_nest_service(prefix, service)
431 }
432
433 #[must_use]
435 pub fn with_matcher<I, T>(mut self, matcher: HttpMatcher<Body>, service: I) -> Self
436 where
437 I: IntoEndpointServiceWithState<T, State>,
438 I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
439 {
440 let endpoint = Endpoint {
441 matcher,
442 service: DefaultLayer::default()
443 .into_layer(service.into_endpoint_service_with_state(self.state.clone()))
444 .boxed(),
445 };
446 self.endpoints.push(Arc::new(endpoint));
447 self
448 }
449
450 pub fn set_matcher<I, T>(&mut self, matcher: HttpMatcher<Body>, service: I) -> &mut Self
452 where
453 I: IntoEndpointServiceWithState<T, State>,
454 I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
455 {
456 let endpoint = Endpoint {
457 matcher,
458 service: DefaultLayer::default()
459 .into_layer(service.into_endpoint_service_with_state(self.state.clone()))
460 .boxed(),
461 };
462 self.endpoints.push(Arc::new(endpoint));
463 self
464 }
465
466 #[must_use]
468 pub fn with_not_found<I, T>(mut self, service: I) -> Self
469 where
470 I: IntoEndpointServiceWithState<T, State>,
471 I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
472 {
473 self.not_found = DefaultLayer::default()
474 .into_layer(service.into_endpoint_service_with_state(self.state.clone()))
475 .boxed();
476 self
477 }
478
479 pub fn set_not_found<I, T>(&mut self, service: I) -> &mut Self
481 where
482 I: IntoEndpointServiceWithState<T, State>,
483 I::Service: Service<Request, Output: IntoResponse, Error: Into<ErrorResponse>>,
484 {
485 self.not_found = DefaultLayer::default()
486 .into_layer(service.into_endpoint_service_with_state(self.state.clone()))
487 .boxed();
488 self
489 }
490}
491
492#[derive(Debug, Clone)]
493struct NestedService<S> {
494 inner: S,
495 prefix: ArcStr,
496}
497
498impl<S> Service<Request> for NestedService<S>
499where
500 S: Service<Request>,
501{
502 type Output = S::Output;
503 type Error = S::Error;
504
505 fn serve(
506 &self,
507 req: Request,
508 ) -> impl Future<Output = Result<Self::Output, Self::Error>> + Send + '_ {
509 let (mut parts, body) = req.into_parts();
511
512 let original_uri = parts.uri.clone();
513 if parts.uri.path_mut().strip_prefix_with_opts(
514 self.prefix.as_str(),
515 PathMatchOptions {
516 ignore_ascii_case: true,
517 ..Default::default()
518 },
519 ) {
520 if !parts.extensions.contains::<OriginalRouterUri>() {
521 parts.extensions.insert(OriginalRouterUri(original_uri));
522 }
523 } else {
524 tracing::debug!(
525 "failed to strip prefix '{}' from Uri (bug??) preserve og uri as is",
526 self.prefix,
527 );
528 }
529
530 let req = Request::from_parts(parts, body);
531
532 self.inner.serve(req)
534 }
535}
536
537impl Default for WebService {
538 fn default() -> Self {
539 Self::new()
540 }
541}
542
543impl<State> Service<Request> for WebService<State>
544where
545 State: Send + Sync + Clone + 'static,
546{
547 type Output = Response;
548 type Error = Infallible;
549
550 async fn serve(&self, req: Request) -> Result<Self::Output, Self::Error> {
551 for endpoint in &self.endpoints {
552 let ext = Extensions::new();
553 if endpoint.matcher.matches(Some(&ext), &req) {
554 req.extensions().extend(&ext);
556 return endpoint.service.serve(req).await;
557 }
558 }
559 self.not_found.serve(req).await
560 }
561}
562
563#[doc(hidden)]
564#[macro_export]
565macro_rules! __match_service {
631 ($($M:expr_2021 => $S:expr_2021),+, _ => $F:expr $(,)?) => {{
632 use $crate::service::web::IntoEndpointService;
633 use $crate::layer::error_handling::ErrorHandler;
634 use $crate::__macro_dep::__core::matcher::MatcherRouter;
635 MatcherRouter((
636 $(($M, ErrorHandler::new($S.into_endpoint_service()))),+,
637 ErrorHandler::new($F.into_endpoint_service()))
638 )
639 }};
640}
641
642#[doc(inline)]
643pub use crate::__match_service as match_service;
644
645#[cfg(test)]
646mod test {
647 use crate::matcher::MethodMatcher;
648 use crate::service::web::extract::State;
649 use crate::{Body, body::util::BodyExt};
650
651 use super::*;
652
653 async fn get_response<S>(service: &S, uri: &str) -> Response
654 where
655 S: Service<Request, Output = Response, Error = Infallible>,
656 {
657 let req = Request::get(uri).body(Body::empty()).unwrap();
658 service.serve(req).await.unwrap()
659 }
660
661 async fn post_response<S>(service: &S, uri: &str) -> Response
662 where
663 S: Service<Request, Output = Response, Error = Infallible>,
664 {
665 let req = Request::post(uri).body(Body::empty()).unwrap();
666 service.serve(req).await.unwrap()
667 }
668
669 async fn connect_response<S>(service: &S, uri: &str) -> Response
670 where
671 S: Service<Request, Output = Response, Error = Infallible>,
672 {
673 let req = Request::connect(uri).body(Body::empty()).unwrap();
674 service.serve(req).await.unwrap()
675 }
676
677 #[tokio::test]
678 async fn test_web_service() {
679 let svc = WebService::new()
680 .with_get("/hello", "hello")
681 .with_post("/world", "world");
682
683 let res = get_response(&svc, "https://www.test.io/hello").await;
684 assert_eq!(res.status(), StatusCode::OK);
685 let body = res.into_body().collect().await.unwrap().to_bytes();
686 assert_eq!(body, "hello");
687
688 let res = post_response(&svc, "https://www.test.io/world").await;
689 assert_eq!(res.status(), StatusCode::OK);
690 let body = res.into_body().collect().await.unwrap().to_bytes();
691 assert_eq!(body, "world");
692
693 let res = get_response(&svc, "https://www.test.io/world").await;
694 assert_eq!(res.status(), StatusCode::NOT_FOUND);
695
696 let res = get_response(&svc, "https://www.test.io").await;
697 assert_eq!(res.status(), StatusCode::NOT_FOUND);
698 }
699
700 #[tokio::test]
701 async fn test_web_service_not_found() {
702 let svc = WebService::new().with_not_found("not found");
703
704 let res = get_response(&svc, "https://www.test.io/hello").await;
705 assert_eq!(res.status(), StatusCode::OK);
706 let body = res.into_body().collect().await.unwrap().to_bytes();
707 assert_eq!(body, "not found");
708 }
709
710 #[tokio::test]
711 async fn test_web_service_nest() {
712 let state = "state".to_owned();
713
714 let svc = WebService::new_with_state(state)
715 .with_get("/state", async |State(state): State<String>| state)
716 .with_nest_make_fn("/api", |web| {
717 web.with_get("/hello", "hello")
718 .with_post("/world", "world")
719 .with_get("/state", async |State(state): State<String>| state)
720 });
721
722 let res = get_response(&svc, "https://www.test.io/api/hello").await;
723 assert_eq!(res.status(), StatusCode::OK);
724 let body = res.into_body().collect().await.unwrap().to_bytes();
725 assert_eq!(body, "hello");
726
727 let res = post_response(&svc, "https://www.test.io/api/world").await;
728 assert_eq!(res.status(), StatusCode::OK);
729 let body = res.into_body().collect().await.unwrap().to_bytes();
730 assert_eq!(body, "world");
731
732 let res = get_response(&svc, "https://www.test.io/api/world").await;
733 assert_eq!(res.status(), StatusCode::NOT_FOUND);
734
735 let res = get_response(&svc, "https://www.test.io").await;
736 assert_eq!(res.status(), StatusCode::NOT_FOUND);
737
738 let res = get_response(&svc, "https://www.test.io/state").await;
739 assert_eq!(res.status(), StatusCode::OK);
740 let body = res.into_body().collect().await.unwrap().to_bytes();
741 assert_eq!(body, "state");
742
743 let res = get_response(&svc, "https://www.test.io/api/state").await;
744 assert_eq!(res.status(), StatusCode::OK);
745 let body = res.into_body().collect().await.unwrap().to_bytes();
746 assert_eq!(body, "state");
747 }
748
749 #[tokio::test]
750 async fn test_web_service_dir() {
751 let tmp_dir = tempfile::tempdir().unwrap();
752 let file_path = tmp_dir.path().join("index.html");
753 std::fs::write(&file_path, "<h1>Hello, World!</h1>").unwrap();
754 let style_dir = tmp_dir.path().join("style");
755 std::fs::create_dir(&style_dir).unwrap();
756 let file_path = style_dir.join("main.css");
757 std::fs::write(&file_path, "body { background-color: red }").unwrap();
758
759 let svc = WebService::new()
760 .with_get("/api/version", "v1")
761 .with_post("/api", StatusCode::FORBIDDEN)
762 .with_dir("/", tmp_dir.path().to_str().unwrap());
763
764 let res = get_response(&svc, "https://www.test.io/index.html").await;
765 assert_eq!(res.status(), StatusCode::OK);
766 let body = res.into_body().collect().await.unwrap().to_bytes();
767 assert_eq!(body, "<h1>Hello, World!</h1>");
768
769 let res = get_response(&svc, "https://www.test.io/style/main.css").await;
770 assert_eq!(res.status(), StatusCode::OK);
771 let body = res.into_body().collect().await.unwrap().to_bytes();
772 assert_eq!(body, "body { background-color: red }");
773
774 let res = get_response(&svc, "https://www.test.io/api/version").await;
775 assert_eq!(res.status(), StatusCode::OK);
776 let body = res.into_body().collect().await.unwrap().to_bytes();
777 assert_eq!(body, "v1");
778
779 let res = post_response(&svc, "https://www.test.io/api").await;
780 assert_eq!(res.status(), StatusCode::FORBIDDEN);
781
782 let res = get_response(&svc, "https://www.test.io/notfound.html").await;
783 assert_eq!(res.status(), StatusCode::NOT_FOUND);
784
785 let res = get_response(&svc, "https://www.test.io/").await;
786 assert_eq!(res.status(), StatusCode::OK);
787 let body = res.into_body().collect().await.unwrap().to_bytes();
788 assert_eq!(body, "<h1>Hello, World!</h1>");
789 }
790
791 #[tokio::test]
792 async fn test_matcher_service_tuples() {
793 let svc = match_service! {
794 HttpMatcher::get("/hello") => "hello",
795 HttpMatcher::post("/world") => "world",
796 MethodMatcher::CONNECT => "connect",
797 _ => StatusCode::NOT_FOUND,
798 };
799
800 let res = get_response(&svc, "https://www.test.io/hello").await;
801 assert_eq!(res.status(), StatusCode::OK);
802 let body = res.into_body().collect().await.unwrap().to_bytes();
803 assert_eq!(body, "hello");
804
805 let res = post_response(&svc, "https://www.test.io/world").await;
806 assert_eq!(res.status(), StatusCode::OK);
807 let body = res.into_body().collect().await.unwrap().to_bytes();
808 assert_eq!(body, "world");
809
810 let res = connect_response(&svc, "https://www.test.io").await;
811 assert_eq!(res.status(), StatusCode::OK);
812 let body = res.into_body().collect().await.unwrap().to_bytes();
813 assert_eq!(body, "connect");
814
815 let res = get_response(&svc, "https://www.test.io/world").await;
816 assert_eq!(res.status(), StatusCode::NOT_FOUND);
817
818 let res = get_response(&svc, "https://www.test.io").await;
819 assert_eq!(res.status(), StatusCode::NOT_FOUND);
820 }
821}