1pub mod policy;
151
152use crate::{Method, Request, Response, StatusCode, StreamingBody, header::LOCATION};
153use iri_string::types::{UriAbsoluteString, UriReferenceStr};
154use rama_core::{
155 Layer, Service,
156 extensions::{Extension, ExtensionsRef},
157};
158use rama_http_types::{
159 HeaderMap,
160 header::{CONTENT_ENCODING, CONTENT_LENGTH, CONTENT_TYPE, TRANSFER_ENCODING},
161};
162use rama_net::uri::Uri;
163use rama_utils::macros::define_inner_service_accessors;
164use std::fmt;
165
166use self::policy::{Action, Attempt, Policy, Standard};
167
168#[derive(Clone)]
172pub struct FollowRedirectLayer<P = Standard> {
173 policy: P,
174}
175
176impl FollowRedirectLayer {
177 #[must_use]
179 pub fn new() -> Self {
180 Self::default()
181 }
182}
183
184impl Default for FollowRedirectLayer {
185 fn default() -> Self {
186 Self::with_policy(Standard::default())
187 }
188}
189
190impl<P: fmt::Debug> fmt::Debug for FollowRedirectLayer<P> {
191 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
192 f.debug_struct("FollowRedirectLayer")
193 .field("policy", &self.policy)
194 .finish()
195 }
196}
197
198impl<P> FollowRedirectLayer<P> {
199 pub const fn with_policy(policy: P) -> Self {
201 Self { policy }
202 }
203}
204
205impl<S, P> Layer<S> for FollowRedirectLayer<P>
206where
207 S: Clone,
208 P: Clone,
209{
210 type Service = FollowRedirect<S, P>;
211
212 fn layer(&self, inner: S) -> Self::Service {
213 FollowRedirect {
214 inner,
215 policy: self.policy.clone(),
216 }
217 }
218
219 fn into_layer(self, inner: S) -> Self::Service {
220 FollowRedirect {
221 inner,
222 policy: self.policy,
223 }
224 }
225}
226
227#[derive(Debug, Clone)]
231pub struct FollowRedirect<S, P = Standard> {
232 inner: S,
233 policy: P,
234}
235
236impl<S> FollowRedirect<S> {
237 pub fn new(inner: S) -> Self {
239 Self::with_policy(inner, Standard::default())
240 }
241}
242
243impl<S, P> FollowRedirect<S, P> {
244 pub const fn with_policy(inner: S, policy: P) -> Self {
246 Self { inner, policy }
247 }
248
249 define_inner_service_accessors!();
250}
251
252impl<ReqBody, ResBody, S, P> Service<Request<ReqBody>> for FollowRedirect<S, P>
253where
254 S: Service<Request<ReqBody>, Output = Response<ResBody>>,
255 ReqBody: StreamingBody + Default + Send + 'static,
256 ResBody: Send + 'static,
257 P: Policy<ReqBody, S::Error> + Clone,
258{
259 type Output = Response<ResBody>;
260 type Error = S::Error;
261
262 fn serve(
263 &self,
264 mut req: Request<ReqBody>,
265 ) -> impl Future<Output = Result<Self::Output, Self::Error>> {
266 let mut method = req.method().clone();
267 let mut uri = req.uri().clone();
268 let version = req.version();
269
270 let mut policy = self.policy.clone();
271
272 let mut body = BodyRepr::None;
273 body.try_clone_from(&mut policy, req.body());
274
275 let caller_extensions = req.fork_extensions_in_place();
278
279 policy.on_request(&mut req);
280 let mut headers = req.headers().clone();
283
284 let service = &self.inner;
285
286 async move {
287 loop {
288 let res = service.serve(req).await?;
289 res.extensions().insert(RequestUri(uri.clone()));
290
291 let previous_method = method.clone();
292 let drop_payload_headers = |headers: &mut HeaderMap| {
293 for header in &[
294 CONTENT_TYPE,
295 CONTENT_LENGTH,
296 CONTENT_ENCODING,
297 TRANSFER_ENCODING,
298 ] {
299 headers.remove(header);
300 }
301 };
302
303 match res.status() {
304 StatusCode::MOVED_PERMANENTLY | StatusCode::FOUND => {
305 if method == Method::POST {
308 method = Method::GET;
309 body = BodyRepr::Empty;
310 drop_payload_headers(&mut headers);
311 }
312 }
313 StatusCode::SEE_OTHER => {
314 if method != Method::HEAD {
316 method = Method::GET;
317 }
318 body = BodyRepr::Empty;
319 drop_payload_headers(&mut headers);
320 }
321 StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT => {}
322 _ => return Ok(res),
323 };
324
325 let Some(taken_body) = body.take() else {
326 return Ok(res);
327 };
328
329 let location = res
330 .headers()
331 .get(&LOCATION)
332 .and_then(|loc| resolve_uri(std::str::from_utf8(loc.as_bytes()).ok()?, &uri));
333 let Some(location) = location else {
334 return Ok(res);
335 };
336
337 let attempt = Attempt {
338 status: res.status(),
339 method: &method,
340 location: &location,
341 previous_method: &previous_method,
342 previous: &uri,
343 };
344 match policy.redirect(&attempt)? {
345 Action::Follow => {
346 uri = location;
347 body.try_clone_from(&mut policy, &taken_body);
348
349 req = Request::new(taken_body);
350 *req.uri_mut() = uri.clone();
351 *req.method_mut() = method.clone();
352 *req.version_mut() = version;
353 *req.headers_mut() = headers.clone();
354 req.set_extensions(caller_extensions.fork());
355 policy.on_request(&mut req);
356 headers = req.headers().clone();
360 }
361 Action::Stop => return Ok(res),
362 }
363 }
364 }
365 }
366}
367
368#[derive(Debug, Clone, Extension)]
376#[extension(tags(http))]
377pub struct RequestUri(pub Uri);
378
379#[derive(Debug)]
380enum BodyRepr<B> {
381 Some(B),
382 Empty,
383 None,
384}
385
386impl<B> BodyRepr<B>
387where
388 B: StreamingBody + Default,
389{
390 fn take(&mut self) -> Option<B> {
391 match std::mem::replace(self, Self::None) {
392 Self::Some(body) => Some(body),
393 Self::Empty => {
394 *self = Self::Empty;
395 Some(B::default())
396 }
397 Self::None => None,
398 }
399 }
400
401 fn try_clone_from<P, E>(&mut self, policy: &mut P, body: &B)
402 where
403 P: Policy<B, E>,
404 {
405 match self {
406 Self::Some(_) | Self::Empty => {}
407 Self::None => {
408 if let Some(body) = clone_body(policy, body) {
409 *self = Self::Some(body);
410 }
411 }
412 }
413 }
414}
415
416fn clone_body<P, B, E>(policy: &mut P, body: &B) -> Option<B>
417where
418 P: Policy<B, E>,
419 B: StreamingBody + Default,
420{
421 if body.size_hint().exact() == Some(0) {
422 Some(B::default())
423 } else {
424 policy.clone_body(body)
425 }
426}
427
428fn resolve_uri(relative: &str, base: &Uri) -> Option<Uri> {
430 let relative = UriReferenceStr::new(relative).ok()?;
431 let base = UriAbsoluteString::try_from(base.to_string()).ok()?;
432 let uri = relative.resolve_against(&base).to_string();
433 Uri::try_from(uri).ok()
434}
435
436#[cfg(test)]
443mod tests {
444 use super::{policy::*, *};
445 use crate::{Body, header::LOCATION};
446 use parking_lot::Mutex;
447 use rama_core::Layer;
448 use rama_core::extensions::ExtensionsRef;
449 use rama_core::service::service_fn;
450 use std::{convert::Infallible, sync::Arc};
451
452 #[test]
453 fn layer_debug_includes_policy() {
454 assert_eq!(
455 format!("{:?}", FollowRedirectLayer::with_policy(Action::Follow)),
456 "FollowRedirectLayer { policy: Follow }"
457 );
458 }
459
460 #[test]
461 fn body_representation_preserves_clone_refusal_and_empty_state() {
462 let mut none = BodyRepr::<Body>::None;
463 assert!(none.take().is_none());
464
465 let mut empty = BodyRepr::<Body>::Empty;
466 assert!(empty.take().is_some());
467 assert!(empty.take().is_some());
468
469 let mut policy = Action::Follow;
470 assert!(
471 clone_body::<_, _, Infallible>(&mut policy, &Body::from("not cloneable")).is_none()
472 );
473 assert!(clone_body::<_, _, Infallible>(&mut policy, &Body::empty()).is_some());
474 }
475
476 #[tokio::test]
477 async fn follows() {
478 let svc = FollowRedirectLayer::with_policy(Action::Follow).into_layer(service_fn(handle));
479 let req = Request::builder()
480 .uri("http://example.com/42")
481 .body(Body::empty())
482 .unwrap();
483 let res = svc.serve(req).await.unwrap();
484 assert_eq!(*res.body(), 0);
485 assert_eq!(
486 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
487 "http://example.com/0"
488 );
489 }
490
491 #[tokio::test]
492 async fn stops() {
493 let svc = FollowRedirectLayer::with_policy(Action::Stop).into_layer(service_fn(handle));
494 let req = Request::builder()
495 .uri("http://example.com/42")
496 .body(Body::empty())
497 .unwrap();
498 let res = svc.serve(req).await.unwrap();
499 assert_eq!(*res.body(), 42);
500 assert_eq!(
501 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
502 "http://example.com/42"
503 );
504 }
505
506 #[tokio::test]
507 async fn limited() {
508 let svc = FollowRedirectLayer::with_policy(Limited::new(10)).into_layer(service_fn(handle));
509 let req = Request::builder()
510 .uri("http://example.com/42")
511 .body(Body::empty())
512 .unwrap();
513 let res = svc.serve(req).await.unwrap();
514 assert_eq!(*res.body(), 42 - 10);
515 assert_eq!(
516 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
517 "http://example.com/32"
518 );
519 }
520
521 async fn handle<B>(req: Request<B>) -> Result<Response<u64>, Infallible> {
524 let n: u64 = req
525 .uri()
526 .first_path_segment()
527 .and_then(|segment| segment.as_encoded_str().parse().ok())
528 .unwrap();
529 let mut res = Response::builder();
530 if n > 0 {
531 res = res
532 .status(StatusCode::MOVED_PERMANENTLY)
533 .header(LOCATION, format!("/{}", n - 1));
534 }
535 Ok::<_, Infallible>(res.body(n).unwrap())
536 }
537
538 #[derive(Clone, Debug, PartialEq, rama_core::extensions::Extension)]
539 struct Marker(u32);
540
541 async fn handle_marker<B>(req: Request<B>) -> Result<Response<u64>, Infallible> {
544 let n: u64 = req
545 .uri()
546 .first_path_segment()
547 .and_then(|segment| segment.as_encoded_str().parse().ok())
548 .unwrap();
549 let mut res = Response::builder();
550 if n > 0 {
551 res = res
552 .status(StatusCode::MOVED_PERMANENTLY)
553 .header(LOCATION, format!("/{}", n - 1));
554 }
555 let res = res.body(n).unwrap();
556 if let Some(marker) = req.extensions().get_ref::<Marker>() {
557 res.extensions().insert(marker.clone());
558 }
559 Ok::<_, Infallible>(res)
560 }
561
562 #[tokio::test]
563 async fn final_hop_reads_the_callers_extensions() {
564 let svc = FollowRedirectLayer::new().into_layer(service_fn(handle_marker));
565 let req = Request::builder()
566 .uri("http://example.com/3")
567 .body(Body::empty())
568 .unwrap();
569 req.extensions().insert(Marker(7));
570 let res = svc.serve(req).await.unwrap();
571 assert_eq!(res.extensions().get_ref::<Marker>(), Some(&Marker(7)));
573 }
574
575 #[tokio::test]
576 async fn fork_reads_the_callers_extensions_on_every_hop() {
577 let hops = Arc::new(Mutex::new(Vec::new()));
578 let svc = FollowRedirectLayer::new().into_layer(service_fn({
579 let hops = hops.clone();
580 move |req: Request<Body>| {
581 hops.lock()
582 .push(req.extensions().get_ref::<Marker>().cloned());
583 handle(req)
584 }
585 }));
586 let req = Request::builder()
587 .uri("http://example.com/2")
588 .body(Body::empty())
589 .unwrap();
590 req.extensions().insert(Marker(7));
591
592 svc.serve(req).await.unwrap();
593 assert_eq!(
594 hops.lock().as_slice(),
595 [Some(Marker(7)), Some(Marker(7)), Some(Marker(7))],
596 );
597 }
598
599 #[derive(Debug, Clone, Default)]
602 struct StampingPolicy(u32);
603
604 impl<B, E> Policy<B, E> for StampingPolicy {
605 fn redirect(&mut self, _: &Attempt<'_>) -> Result<Action, E> {
606 Ok(Action::Follow)
607 }
608
609 fn on_request(&mut self, req: &mut Request<B>) {
610 if !req.extensions().contains::<Marker>() {
611 req.extensions().insert(Marker(self.0));
612 self.0 += 1;
613 }
614 }
615 }
616
617 #[tokio::test]
618 async fn policy_on_request_inserts_stay_per_hop() {
619 let hops = Arc::new(Mutex::new(Vec::new()));
622 let svc =
623 FollowRedirectLayer::with_policy(StampingPolicy::default()).into_layer(service_fn({
624 let hops = hops.clone();
625 move |req: Request<Body>| {
626 hops.lock()
627 .push(req.extensions().get_ref::<Marker>().cloned());
628 handle(req)
629 }
630 }));
631 let req = Request::builder()
632 .uri("http://example.com/2")
633 .body(Body::empty())
634 .unwrap();
635 let caller_extensions = req.extensions().clone();
636
637 svc.serve(req).await.unwrap();
638 assert_eq!(
639 hops.lock().as_slice(),
640 [Some(Marker(0)), Some(Marker(1)), Some(Marker(2))],
641 );
642 assert!(!caller_extensions.contains::<Marker>());
643 }
644
645 #[derive(Debug, Clone, Default)]
646 struct RemoveHeaderOnce(bool);
647
648 impl<B, E> Policy<B, E> for RemoveHeaderOnce {
649 fn redirect(&mut self, _: &Attempt<'_>) -> Result<Action, E> {
650 Ok(Action::Follow)
651 }
652
653 fn on_request(&mut self, req: &mut Request<B>) {
654 if !self.0 {
655 req.headers_mut().remove("x-remove-on-first-hop");
656 self.0 = true;
657 }
658 }
659 }
660
661 #[tokio::test]
662 async fn first_hop_policy_header_changes_are_carried_forward() {
663 let seen = Arc::new(Mutex::new(Vec::new()));
664 let svc =
665 FollowRedirectLayer::with_policy(RemoveHeaderOnce::default()).into_layer(service_fn({
666 let seen = seen.clone();
667 move |req: Request<Body>| {
668 seen.lock()
669 .push(req.headers().contains_key("x-remove-on-first-hop"));
670 handle(req)
671 }
672 }));
673 let req = Request::builder()
674 .uri("http://example.com/1")
675 .header("x-remove-on-first-hop", "yes")
676 .body(Body::empty())
677 .unwrap();
678
679 svc.serve(req).await.unwrap();
680 assert_eq!(seen.lock().as_slice(), [false, false]);
681 }
682
683 async fn handle_cookie_chain<B>(req: Request<B>) -> Result<Response<u64>, Infallible> {
687 let host = req.uri().host_str();
688 let path = req.uri().path_ref_or_root();
689 let location = if host.as_deref() == Some("a.example.com") {
690 Some("http://b.example.com/second")
691 } else if host.as_deref() == Some("b.example.com") && path == "/second" {
692 Some("http://b.example.com/final")
693 } else {
694 None
695 };
696 let mut res = Response::builder();
697 if let Some(location) = location {
698 res = res
699 .status(StatusCode::MOVED_PERMANENTLY)
700 .header(LOCATION, location);
701 }
702 let mut res = res.body(0u64).unwrap();
703 if req.headers().contains_key(crate::header::COOKIE) {
704 res.headers_mut()
705 .insert("x-saw-cookie", crate::HeaderValue::from_static("1"));
706 }
707 Ok::<_, Infallible>(res)
708 }
709
710 #[tokio::test]
711 async fn credentials_do_not_resurrect_after_cross_origin() {
712 let svc = FollowRedirectLayer::default().into_layer(service_fn(handle_cookie_chain));
716 let req = Request::builder()
717 .uri("http://a.example.com/")
718 .header(crate::header::COOKIE, "session=secret")
719 .body(Body::empty())
720 .unwrap();
721 let res = svc.serve(req).await.unwrap();
722 assert!(
723 !res.headers().contains_key("x-saw-cookie"),
724 "Cookie resurrected on a same-origin hop after being dropped cross-origin",
725 );
726 assert_eq!(
727 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
728 "http://b.example.com/final"
729 );
730 }
731
732 #[derive(Debug, Clone)]
736 struct RouteDecider<S> {
737 inner: S,
738 seen: Arc<Mutex<Vec<String>>>,
739 }
740
741 #[derive(Clone, Debug, PartialEq, rama_core::extensions::Extension)]
742 struct Route(String);
743
744 impl<S, B> Service<Request<B>> for RouteDecider<S>
745 where
746 S: Service<Request<B>>,
747 B: Send + 'static,
748 {
749 type Output = S::Output;
750 type Error = S::Error;
751
752 async fn serve(&self, req: Request<B>) -> Result<Self::Output, Self::Error> {
753 if req.extensions().get_ref::<Route>().is_none() {
754 let host = req.uri().host_str().unwrap_or_default().into_owned();
755 req.extensions().insert(Route(host));
756 }
757 self.seen
758 .lock()
759 .push(req.extensions().get_ref::<Route>().unwrap().0.clone());
760 self.inner.serve(req).await
761 }
762 }
763
764 #[tokio::test]
765 async fn inner_per_hop_decision_does_not_leak_into_the_next_hop() {
766 let seen = Arc::new(Mutex::new(Vec::new()));
769 let svc = FollowRedirectLayer::new().into_layer(RouteDecider {
770 inner: service_fn(handle_cookie_chain),
771 seen: seen.clone(),
772 });
773 let req = Request::builder()
774 .uri("http://a.example.com/")
775 .body(Body::empty())
776 .unwrap();
777
778 svc.serve(req).await.unwrap();
779 assert_eq!(
780 seen.lock().as_slice(),
781 ["a.example.com", "b.example.com", "b.example.com"],
782 );
783 }
784
785 #[tokio::test]
786 async fn outer_per_hop_decision_is_inherited_by_every_hop() {
787 let seen = Arc::new(Mutex::new(Vec::new()));
790 let svc = RouteDecider {
791 inner: FollowRedirectLayer::new().into_layer(service_fn({
792 let seen = seen.clone();
793 move |req: Request<Body>| {
794 seen.lock()
795 .push(req.extensions().get_ref::<Route>().unwrap().0.clone());
796 handle_cookie_chain(req)
797 }
798 })),
799 seen: Arc::new(Mutex::new(Vec::new())),
800 };
801 let req = Request::builder()
802 .uri("http://a.example.com/")
803 .body(Body::empty())
804 .unwrap();
805
806 svc.serve(req).await.unwrap();
807 assert_eq!(
808 seen.lock().as_slice(),
809 ["a.example.com", "a.example.com", "a.example.com"],
810 );
811 }
812
813 #[tokio::test]
814 async fn hop_inserts_never_reach_the_callers_request_store() {
815 let svc = FollowRedirectLayer::new().into_layer(RouteDecider {
816 inner: service_fn(handle_cookie_chain),
817 seen: Arc::new(Mutex::new(Vec::new())),
818 });
819 let req = Request::builder()
820 .uri("http://a.example.com/")
821 .body(Body::empty())
822 .unwrap();
823 let caller_extensions = req.extensions().clone();
824
825 svc.serve(req).await.unwrap();
826 assert!(
827 !caller_extensions.contains::<Route>(),
828 "a hop's insert leaked back into the caller's request extensions",
829 );
830 }
831
832 #[tokio::test]
833 async fn response_still_exposes_the_final_hops_extensions() {
834 let svc = FollowRedirectLayer::new().into_layer(RouteDecider {
838 inner: service_fn(async |req: Request<Body>| {
839 let request_extensions = req.extensions().clone();
840 let mut res = handle_cookie_chain(req).await?;
841 res.set_extensions(request_extensions.fork());
842 Ok::<_, Infallible>(res)
843 }),
844 seen: Arc::new(Mutex::new(Vec::new())),
845 });
846 let req = Request::builder()
847 .uri("http://a.example.com/")
848 .body(Body::empty())
849 .unwrap();
850 req.extensions().insert(Marker(7));
851
852 let res = svc.serve(req).await.unwrap();
853 assert_eq!(
854 res.extensions().get_ref::<Route>(),
855 Some(&Route("b.example.com".to_owned())),
856 );
857 assert_eq!(res.extensions().get_ref::<Marker>(), Some(&Marker(7)));
858 }
859
860 #[tokio::test]
861 async fn test_301_redirects() {
862 let policy = policy::redirect_fn(|attempt| -> Result<_, Infallible> {
863 if attempt.previous_method() == Method::POST && attempt.method() == Method::GET {
864 Ok(Action::Stop)
865 } else {
866 Ok(Action::Follow)
867 }
868 });
869 let svc = FollowRedirectLayer::with_policy(policy).into_layer(service_fn(redirections));
870
871 {
874 let req = Request::builder()
875 .method(Method::POST)
876 .uri("http://example.com/301")
877 .body(Body::empty())
878 .unwrap();
879 let res = svc.clone().serve(req).await.unwrap();
880 assert_eq!(*res.body(), "/target/301");
881 assert_eq!(
882 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
883 "http://example.com/301"
884 );
885 }
886
887 {
890 let req = Request::builder()
891 .method(Method::GET)
892 .uri("http://example.com/301")
893 .body(Body::empty())
894 .unwrap();
895 let res = svc.clone().serve(req).await.unwrap();
896 assert_eq!(*res.body(), "/target/301/final");
897 assert_eq!(
898 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
899 "http://example.com/target/301"
900 );
901 }
902 }
903
904 #[tokio::test]
905 async fn test_302_redirects() {
906 let policy = policy::redirect_fn(|attempt| -> Result<_, Infallible> {
907 if attempt.previous_method() != attempt.method() {
908 Ok(Action::Stop)
909 } else {
910 Ok(Action::Follow)
911 }
912 });
913 let svc = FollowRedirectLayer::with_policy(policy).into_layer(service_fn(redirections));
914
915 {
918 let req = Request::builder()
919 .method(Method::POST)
920 .uri("http://example.com/302")
921 .body(Body::empty())
922 .unwrap();
923 let res = svc.clone().serve(req).await.unwrap();
924 assert_eq!(*res.body(), "/target/302");
925 assert_eq!(
926 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
927 "http://example.com/302"
928 );
929 }
930
931 {
934 let req = Request::builder()
935 .method(Method::PUT)
936 .uri("http://example.com/302")
937 .body(Body::empty())
938 .unwrap();
939 let res = svc.clone().serve(req).await.unwrap();
940 assert_eq!(*res.body(), "/target/302/final");
941 assert_eq!(
942 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
943 "http://example.com/target/302"
944 );
945 }
946
947 {
950 let req = Request::builder()
951 .method(Method::HEAD)
952 .uri("http://example.com/302")
953 .body(Body::empty())
954 .unwrap();
955 let res = svc.clone().serve(req).await.unwrap();
956 assert_eq!(*res.body(), "/target/302/final");
957 assert_eq!(
958 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
959 "http://example.com/target/302"
960 );
961 }
962 }
963
964 #[tokio::test]
965 async fn test_303_redirects() {
966 let policy = policy::redirect_fn(|attempt| -> Result<_, Infallible> {
967 if attempt.previous_method() != attempt.method() {
968 Ok(Action::Stop)
969 } else {
970 Ok(Action::Follow)
971 }
972 });
973 let svc = FollowRedirectLayer::with_policy(policy).into_layer(service_fn(redirections));
974
975 {
978 let req = Request::builder()
979 .method(Method::POST)
980 .uri("http://example.com/303")
981 .body(Body::empty())
982 .unwrap();
983 let res = svc.clone().serve(req).await.unwrap();
984 assert_eq!(*res.body(), "/target/303");
985 assert_eq!(
986 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
987 "http://example.com/303"
988 );
989 }
990
991 {
994 let req = Request::builder()
995 .method(Method::PUT)
996 .uri("http://example.com/303")
997 .body(Body::empty())
998 .unwrap();
999 let res = svc.clone().serve(req).await.unwrap();
1000 assert_eq!(*res.body(), "/target/303");
1001 assert_eq!(
1002 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
1003 "http://example.com/303"
1004 );
1005 }
1006
1007 {
1010 let req = Request::builder()
1011 .method(Method::HEAD)
1012 .uri("http://example.com/303")
1013 .body(Body::empty())
1014 .unwrap();
1015 let res = svc.clone().serve(req).await.unwrap();
1016 assert_eq!(*res.body(), "/target/303/final");
1017 assert_eq!(
1018 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
1019 "http://example.com/target/303"
1020 );
1021 }
1022 }
1023
1024 #[tokio::test]
1025 async fn test_307_308_redirects() {
1026 let policy = policy::redirect_fn(|attempt| -> Result<_, Infallible> {
1027 if attempt.previous_method() != Method::POST || attempt.method() != Method::POST {
1028 Ok(Action::Stop)
1029 } else {
1030 Ok(Action::Follow)
1031 }
1032 });
1033 let svc = FollowRedirectLayer::with_policy(policy).into_layer(service_fn(redirections));
1034
1035 {
1038 let req = Request::builder()
1039 .method(Method::POST)
1040 .uri("http://example.com/307")
1041 .body(Body::empty())
1042 .unwrap();
1043 let res = svc.clone().serve(req).await.unwrap();
1044 assert_eq!(*res.body(), "/target/307/final");
1045 assert_eq!(
1046 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
1047 "http://example.com/target/307"
1048 );
1049 }
1050
1051 {
1054 let req = Request::builder()
1055 .method(Method::POST)
1056 .uri("http://example.com/308")
1057 .body(Body::empty())
1058 .unwrap();
1059 let res = svc.clone().serve(req).await.unwrap();
1060 assert_eq!(*res.body(), "/target/308/final");
1061 assert_eq!(
1062 res.extensions().get_ref::<RequestUri>().unwrap().0.as_str(),
1063 "http://example.com/target/308"
1064 );
1065 }
1066 }
1067
1068 async fn redirections<B>(req: Request<B>) -> Result<Response<String>, Infallible> {
1070 let path = req.uri().path_ref_or_root();
1071 let mut res = Response::builder();
1072 let body_str;
1073 res = if path == "/301" {
1074 let case = "/target/301";
1075 body_str = case.to_owned();
1076 res.status(StatusCode::MOVED_PERMANENTLY)
1077 .header(LOCATION, case)
1078 } else if path == "/302" {
1079 let case = "/target/302";
1080 body_str = case.to_owned();
1081 res.status(StatusCode::FOUND).header(LOCATION, case)
1082 } else if path == "/303" {
1083 let case = "/target/303";
1084 body_str = case.to_owned();
1085 res.status(StatusCode::SEE_OTHER).header(LOCATION, case)
1086 } else if path == "/307" {
1087 let case = "/target/307";
1088 body_str = case.to_owned();
1089 res.status(StatusCode::TEMPORARY_REDIRECT)
1090 .header(LOCATION, case)
1091 } else if path == "/308" {
1092 let case = "/target/308";
1093 body_str = case.to_owned();
1094 res.status(StatusCode::PERMANENT_REDIRECT)
1095 .header(LOCATION, case)
1096 } else {
1097 body_str = format!("{path}/final");
1098 res.status(StatusCode::OK)
1099 };
1100 Ok::<_, Infallible>(res.body(body_str).unwrap())
1101 }
1102
1103 }