1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
//! HTTP Redirect middleware. //! //! # Examples //! //! ```no_run //! # #[async_std::main] //! # async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> { //! let req = surf::get("https://httpbin.org/redirect/2"); //! let client = surf::client().with(surf::middleware::Redirect::new(5)); //! let mut res = client.send(req).await?; //! dbg!(res.body_string().await?); //! # Ok(()) } //! ``` use crate::http::{headers, StatusCode, Url}; use crate::middleware::{Middleware, Next, Request, Response}; use crate::{Client, Result}; // List of acceptible 300-series redirect codes. const REDIRECT_CODES: &[StatusCode] = &[ StatusCode::MovedPermanently, StatusCode::Found, StatusCode::SeeOther, StatusCode::TemporaryRedirect, StatusCode::PermanentRedirect, ]; /// A middleware which attempts to follow HTTP redirects. #[derive(Debug)] pub struct Redirect { attempts: u8, } impl Redirect { /// Create a new instance of the Redirect middleware, which attempts to follow redirects /// up to as many times as specified. /// /// Consider using `Redirect::default()` for the default number of redirect attempts. /// /// This middleware will follow redirects from the `Location` header if the server returns /// any of the following http response codes: /// - 301 Moved Permanently /// - 302 Found /// - 303 See other /// - 307 Temporary Redirect /// - 308 Permanent Redirect /// /// # Errors /// /// An error will be passed through the middleware stack if the value of the `Location` /// header is not a validly parsing url. /// /// # Caveats /// /// This will presently make at least one additional HTTP request before the actual request to /// determine if there is a redirect that should be followed, so as to preserve any request body. /// /// # Examples /// /// ```no_run /// # #[async_std::main] /// # async fn main() -> surf::Result<()> { /// let req = surf::get("https://httpbin.org/redirect/2"); /// let client = surf::client().with(surf::middleware::Redirect::new(5)); /// let mut res = client.send(req).await?; /// dbg!(res.body_string().await?); /// # Ok(()) } /// ``` pub fn new(attempts: u8) -> Self { Redirect { attempts } } } #[async_trait::async_trait] impl Middleware for Redirect { #[allow(missing_doc_code_examples)] async fn handle(&self, mut req: Request, client: Client, next: Next<'_>) -> Result<Response> { let mut redirect_count: u8 = 0; // Note(Jeremiah): This is not ideal. // // HttpClient is currently too limiting for efficient redirects. // We do not want to make unnecessary full requests, but it is // presently required due to how Body streams work. // // Ideally we'd have methods to send a partial request stream, // without sending the body, that would potnetially be able to // get a server status before we attempt to send the body. // // As a work around we clone the request first (without the body), // and try sending it until we get some status back that is not a // redirect. while redirect_count < self.attempts { redirect_count += 1; let r: Request = req.clone(); let res: Response = client.send(r).await?; if REDIRECT_CODES.contains(&res.status()) { if let Some(location) = res.header(headers::LOCATION) { *req.as_mut().url_mut() = Url::parse(location.last().as_str())?; } } else { break; } } Ok(next.run(req, client).await?) } } impl Default for Redirect { /// Create a new instance of the Redirect middleware, which attempts to follow up to /// 3 redirects (not including the actual request). fn default() -> Self { Self { attempts: 3 } } }