follow_redirects/lib.rs
1//! Extension for `hyper` to follow HTTP redirects.
2//!
3//! # Behaviour and Limitations
4//!
5//! ## Supported Redirects
6//!
7//! This crate supports all 5 redirect styles that are defined by the HTTP/1.1 spec.
8//! These include both "permanent" redirects (status codes `301` and `308`) as well as
9//! "temporary" redirects (status codes `302`, `303` and `307`).
10//!
11//! Encountering a `303 See Other` response from the server will cause the client to change
12//! the request method to `GET`, and discard the request body before following the redirect.
13//!
14//! ## Location Header
15//!
16//! The client uses the `Location` HTTP header to determine the next url to follow the redirect to.
17//! Both relative (without a hostname) as well as absolute URLs (including schema and hostname) urls
18//! are supported.
19//!
20//! When the `Location` header is missing in a response from the server, but the status code indicates
21//! a redirect, the client will return the response to the caller. You can detect this situation by
22//! checking the status code and headers of the returned response.
23//!
24//! ## Buffering
25//!
26//! In order to be able to replay the request body when following redirects other than `303 See Other`,
27//! the client will buffer the request body in-memory before making the first request to the
28//! server.
29//!
30//! ## Redirect Limit
31//!
32//! To avoid following an endless chain of redirects, the client has a limit for the maximum number
33//! of redirects it will follow until giving up.
34//!
35//! When the maximum number of redirects is reached, the client will simply return the last response
36//! to the caller. You can detect this situation by checking the status code of the returned response.
37//!
38//! ## Security Considerations
39//!
40//! In order to protect the confidentiality of authentication or session information passed along in
41//! a request, the client will strip authentication and cookie headers when following a redirect to
42//! a different host and port.
43//!
44//! Redirects to the same host and port, but different paths will retain session information.
45//!
46//! # Example
47//!
48//! ```rust
49//! extern crate hyper;
50//! extern crate follow_redirects;
51//! # extern crate tokio_core;
52//!
53//! // 1. import the extension trait
54//! use follow_redirects::ClientExt;
55//!
56//! # fn main() {
57//! # let core = tokio_core::reactor::Core::new().unwrap();
58//! # let handle = core.handle();
59//! // ...
60//! // 2. create a standard hyper client
61//! let client = hyper::Client::new(&handle);
62//!
63//! // ...
64//! // 3. make a request that will follow redirects
65//! let url = "http://docs.rs/hyper".parse().unwrap();
66//! let future = client.follow_redirects().get(url);
67//! # drop(future);
68//! # }
69//! ```
70
71#![deny(missing_docs)]
72#![deny(warnings)]
73#![deny(missing_debug_implementations)]
74
75extern crate bytes;
76#[macro_use] extern crate futures;
77extern crate http;
78extern crate hyper;
79
80use std::fmt;
81
82use bytes::Bytes;
83use futures::{Future, Poll, Stream};
84use hyper::{Error, Method, Request, Response, Uri};
85use hyper::client::{Connect, Service};
86
87mod uri;
88mod buffer;
89mod machine;
90mod future;
91
92use future::FutureInner;
93
94/// Extension trait for adding follow-redirect features to `hyper::Client`.
95pub trait ClientExt<C, B> {
96 /// Wrap the `hyper::Client` in a new client that follows redirects.
97 fn follow_redirects(&self) -> Client<C, B>;
98
99 /// Wrap the `hyper::Client` in a new client that follows redirects,
100 /// and set the maximum number of redirects to follow.
101 fn follow_redirects_max(&self, max_redirects: usize) -> Client<C, B> {
102 let mut client = self.follow_redirects();
103 client.set_max_redirects(max_redirects);
104 client
105 }
106}
107
108impl<C, B> ClientExt<C, B> for hyper::Client<C, B> {
109 fn follow_redirects(&self) -> Client<C, B> {
110 Client {
111 inner: self.clone(),
112 max_redirects: 10
113 }
114 }
115}
116
117/// A client to make outgoing HTTP requests, and which follows redirects.
118///
119/// By default, the client will follow up to 10 redirects. This limit can be configured
120/// via the `set_max_redirects` method.
121pub struct Client<C, B> {
122 inner: hyper::Client<C, B>,
123 max_redirects: usize
124}
125
126impl<C, B> Clone for Client<C, B> {
127 fn clone(&self) -> Client<C, B> {
128 Client {
129 inner: self.inner.clone(),
130 max_redirects: self.max_redirects
131 }
132 }
133}
134
135impl<C, B> fmt::Debug for Client<C, B> {
136 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
137 f.debug_struct("Client")
138 .field("max_redirects", &self.max_redirects)
139 .finish()
140 }
141}
142
143impl<C, B> Client<C, B>
144 where C: Connect,
145 B: Stream<Error = Error> + From<Bytes> + 'static,
146 B::Item: AsRef<[u8]>
147{
148 /// Send a GET Request using this client.
149 pub fn get(&self, url: Uri) -> FutureResponse {
150 self.request(Request::new(Method::Get, url))
151 }
152
153 /// Send a constructed Request using this client.
154 pub fn request(&self, req: Request<B>) -> FutureResponse {
155 FutureResponse(Box::new(FutureInner::new(self.inner.clone(), req, self.max_redirects)))
156 }
157}
158
159impl<C, B> Client<C, B> {
160 /// Set the maximum number of redirects the client will follow before giving up.
161 ///
162 /// By default, this limit is set to 10.
163 pub fn set_max_redirects(&mut self, max_redirects: usize) {
164 self.max_redirects = max_redirects;
165 }
166}
167
168impl<C, B> Service for Client<C, B>
169 where C: Connect,
170 B: Stream<Error = Error> + From<Bytes> + 'static,
171 B::Item: AsRef<[u8]>
172{
173 type Request = Request<B>;
174 type Response = Response;
175 type Error = Error;
176 type Future = FutureResponse;
177
178 fn call(&self, req: Request<B>) -> FutureResponse {
179 self.request(req)
180 }
181}
182
183/// A `Future` that will resolve to an HTTP Response.
184pub struct FutureResponse(Box<Future<Item=Response, Error=Error>>);
185
186impl fmt::Debug for FutureResponse {
187 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
188 f.pad("Future<Response>")
189 }
190}
191
192impl Future for FutureResponse {
193 type Item = Response;
194 type Error = Error;
195
196 fn poll(&mut self) -> Poll<Response, Error> {
197 self.0.poll()
198 }
199}