1use std::convert::From;
2use std::fmt::Display;
3use std::io::{prelude::*, BufWriter};
4use std::str;
5
6#[cfg(feature = "compress")]
7use http::header::ACCEPT_ENCODING;
8use http::{
9 header::{HeaderValue, IntoHeaderName, CONNECTION, CONTENT_LENGTH, HOST},
10 status::StatusCode,
11 HeaderMap, HttpTryFrom, Method, Version,
12};
13use url::Url;
14
15#[cfg(feature = "charsets")]
16use crate::charsets::Charset;
17use crate::error::{HttpError, HttpResult};
18use crate::parsing::{parse_response, ResponseReader};
19use crate::streams::BaseStream;
20
21pub trait HttpTryInto<T> {
22 fn try_into(self) -> Result<T, http::Error>;
23}
24
25impl<T, U> HttpTryInto<U> for T
26where
27 U: HttpTryFrom<T>,
28 http::Error: From<<U as http::HttpTryFrom<T>>::Error>,
29{
30 fn try_into(self) -> Result<U, http::Error> {
31 let val = U::try_from(self)?;
32 Ok(val)
33 }
34}
35
36fn header_insert<H, V>(headers: &mut HeaderMap, header: H, value: V) -> HttpResult
37where
38 H: IntoHeaderName,
39 V: HttpTryInto<HeaderValue>,
40{
41 let value = value.try_into()?;
42 headers.insert(header, value);
43 Ok(())
44}
45
46fn header_append<H, V>(headers: &mut HeaderMap, header: H, value: V) -> HttpResult
47where
48 H: IntoHeaderName,
49 V: HttpTryInto<HeaderValue>,
50{
51 let value = value.try_into()?;
52 headers.append(header, value);
53 Ok(())
54}
55
56pub struct Request {
61 url: Url,
62 method: Method,
63 headers: HeaderMap,
64 body: Vec<u8>,
65 follow_redirects: bool,
66 #[cfg(feature = "charsets")]
67 pub(crate) default_charset: Option<Charset>,
68 #[cfg(feature = "compress")]
69 allow_compression: bool,
70}
71
72impl Request {
73 pub fn new(base_url: &str, method: Method) -> Request {
75 let url = Url::parse(base_url).expect("invalid url");
76
77 match method {
78 Method::CONNECT => panic!("CONNECT is not yet supported"),
79 _ => {}
80 }
81
82 Request {
83 url,
84 method: method,
85 headers: HeaderMap::new(),
86 body: Vec::new(),
87 follow_redirects: true,
88 #[cfg(feature = "charsets")]
89 default_charset: None,
90 #[cfg(feature = "compress")]
91 allow_compression: true,
92 }
93 }
94
95 pub fn get(base_url: &str) -> Request {
97 Request::new(base_url, Method::GET)
98 }
99
100 pub fn post(base_url: &str) -> Request {
102 Request::new(base_url, Method::POST)
103 }
104
105 pub fn put(base_url: &str) -> Request {
107 Request::new(base_url, Method::PUT)
108 }
109
110 pub fn delete(base_url: &str) -> Request {
112 Request::new(base_url, Method::DELETE)
113 }
114
115 pub fn head(base_url: &str) -> Request {
117 Request::new(base_url, Method::HEAD)
118 }
119
120 pub fn options(base_url: &str) -> Request {
122 Request::new(base_url, Method::OPTIONS)
123 }
124
125 pub fn patch(base_url: &str) -> Request {
127 Request::new(base_url, Method::PATCH)
128 }
129
130 pub fn trace(base_url: &str) -> Request {
132 Request::new(base_url, Method::TRACE)
133 }
134
135 pub fn param<V>(&mut self, key: &str, value: V)
139 where
140 V: Display,
141 {
142 self.url.query_pairs_mut().append_pair(key, &format!("{}", value));
143 }
144
145 pub fn header<H, V>(&mut self, header: H, value: V) -> HttpResult
150 where
151 H: IntoHeaderName,
152 V: HttpTryInto<HeaderValue>,
153 {
154 header_insert(&mut self.headers, header, value)
155 }
156
157 pub fn header_append<H, V>(&mut self, header: H, value: V) -> HttpResult
161 where
162 H: IntoHeaderName,
163 V: HttpTryInto<HeaderValue>,
164 {
165 header_append(&mut self.headers, header, value)
166 }
167
168 pub fn body(&mut self, body: impl AsRef<[u8]>) {
172 self.body = body.as_ref().to_owned();
173 }
174
175 pub fn follow_redirects(&mut self, follow_redirects: bool) {
179 self.follow_redirects = follow_redirects;
180 }
181
182 #[cfg(feature = "charsets")]
187 pub fn default_charset(&mut self, default_charset: Option<Charset>) {
188 self.default_charset = default_charset;
189 }
190
191 #[cfg(feature = "compress")]
196 pub fn allow_compression(&mut self, allow_compression: bool) {
197 self.allow_compression = allow_compression;
198 }
199
200 fn base_redirect_url(&self, location: &str, previous_url: &Url) -> HttpResult<Url> {
201 Ok(match Url::parse(location) {
202 Ok(url) => url,
203 Err(url::ParseError::RelativeUrlWithoutBase) => previous_url
204 .join(location)
205 .map_err(|_| HttpError::InvalidUrl("cannot join location with new url"))?,
206 Err(_) => Err(HttpError::InvalidUrl("invalid redirection url"))?,
207 })
208 }
209
210 pub fn send(mut self) -> HttpResult<(StatusCode, HeaderMap, ResponseReader)> {
214 let mut url = self.url.clone();
215 loop {
216 let mut stream = BaseStream::connect(&url)?;
217 self.write_request(&mut stream, &url)?;
218 let (status, headers, resp) = parse_response(stream, &self)?;
219
220 debug!("status code {}", status.as_u16());
221
222 if !self.follow_redirects || !status.is_redirection() {
223 return Ok((status, headers, resp));
224 }
225
226 let location = headers
228 .get(http::header::LOCATION)
229 .ok_or(HttpError::InvalidResponse("redirect has no location header"))?;
230 let location = location
231 .to_str()
232 .map_err(|_| HttpError::InvalidResponse("location to str error"))?;
233
234 let new_url = self.base_redirect_url(location, &url)?;
235 url = new_url;
236
237 debug!("redirected to {} giving url {}", location, url,);
238 }
239 }
240
241 fn write_request<W>(&mut self, writer: W, url: &Url) -> HttpResult
242 where
243 W: Write,
244 {
245 let mut writer = BufWriter::new(writer);
246 let version = Version::HTTP_11;
247 let has_body = !self.body.is_empty() && self.method != Method::TRACE;
248
249 if let Some(query) = url.query() {
250 debug!("{} {}?{} {:?}", self.method.as_str(), url.path(), query, version,);
251
252 write!(
253 writer,
254 "{} {}?{} {:?}\r\n",
255 self.method.as_str(),
256 url.path(),
257 query,
258 version,
259 )?;
260 } else {
261 debug!("{} {} {:?}", self.method.as_str(), url.path(), version);
262
263 write!(writer, "{} {} {:?}\r\n", self.method.as_str(), url.path(), version,)?;
264 }
265
266 header_insert(&mut self.headers, CONNECTION, "close")?;
267
268 let host = url.host_str().ok_or(HttpError::InvalidUrl("url has no host"))?;
269 if let Some(port) = url.port() {
270 header_insert(&mut self.headers, HOST, format!("{}:{}", host, port))?;
271 } else {
272 header_insert(&mut self.headers, HOST, host)?;
273 }
274
275 if has_body {
276 header_insert(&mut self.headers, CONTENT_LENGTH, format!("{}", self.body.len()))?;
277 }
278
279 self.compression_header()?;
280
281 self.write_headers(&mut writer)?;
282
283 if has_body {
284 debug!("writing out body of length {}", self.body.len());
285 writer.write_all(&self.body)?;
286 }
287
288 writer.flush()?;
289
290 Ok(())
291 }
292
293 fn write_headers<W>(&self, writer: &mut W) -> HttpResult
294 where
295 W: Write,
296 {
297 for (key, value) in self.headers.iter() {
298 write!(writer, "{}: ", key.as_str())?;
299 writer.write_all(value.as_bytes())?;
300 write!(writer, "\r\n")?;
301 }
302 write!(writer, "\r\n")?;
303 Ok(())
304 }
305
306 #[cfg(feature = "compress")]
307 fn compression_header(&mut self) -> HttpResult {
308 if self.allow_compression {
309 header_insert(&mut self.headers, ACCEPT_ENCODING, "gzip, deflate")?;
310 }
311 Ok(())
312 }
313
314 #[cfg(not(feature = "compress"))]
315 fn compression_header(&mut self) -> HttpResult {
316 Ok(())
317 }
318}