1use crate::connection::Connection;
2use crate::http_url::{HttpUrl, Port};
3#[cfg(feature = "proxy")]
4use crate::proxy::Proxy;
5use crate::{Error, Response, ResponseLazy};
6use std::collections::HashMap;
7use std::fmt;
8use std::fmt::Write;
9
10pub type URL = String;
12
13#[derive(Clone, PartialEq, Eq, Debug)]
15pub enum Method {
16 Get,
18 Head,
20 Post,
22 Put,
24 Delete,
26 Connect,
28 Options,
30 Trace,
32 Patch,
34 Custom(String),
37}
38
39impl fmt::Display for Method {
40 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
43 match *self {
44 Method::Get => write!(f, "GET"),
45 Method::Head => write!(f, "HEAD"),
46 Method::Post => write!(f, "POST"),
47 Method::Put => write!(f, "PUT"),
48 Method::Delete => write!(f, "DELETE"),
49 Method::Connect => write!(f, "CONNECT"),
50 Method::Options => write!(f, "OPTIONS"),
51 Method::Trace => write!(f, "TRACE"),
52 Method::Patch => write!(f, "PATCH"),
53 Method::Custom(ref s) => write!(f, "{}", s),
54 }
55 }
56}
57
58#[derive(Clone, PartialEq, Eq, Debug)]
74pub struct Request {
75 pub(crate) method: Method,
76 url: URL,
77 params: String,
78 headers: HashMap<String, String>,
79 body: Option<Vec<u8>>,
80 pub(crate) timeout: Option<u64>,
81 pub(crate) max_headers_size: Option<usize>,
82 pub(crate) max_status_line_len: Option<usize>,
83 max_redirects: usize,
84 pub(crate) follow_redirects: bool,
85 #[cfg(feature = "proxy")]
86 pub(crate) proxy: Option<Proxy>,
87}
88
89impl Request {
90 pub fn new<T: Into<URL>>(method: Method, url: T) -> Request {
102 Request {
103 method,
104 url: url.into(),
105 params: String::new(),
106 headers: HashMap::new(),
107 body: None,
108 timeout: None,
109 max_headers_size: None,
110 max_status_line_len: None,
111 max_redirects: 100,
112 follow_redirects: true,
113 #[cfg(feature = "proxy")]
114 proxy: None,
115 }
116 }
117
118 pub fn with_headers<T, K, V>(mut self, headers: T) -> Request
121 where
122 T: IntoIterator<Item = (K, V)>,
123 K: Into<String>,
124 V: Into<String>,
125 {
126 let headers = headers.into_iter().map(|(k, v)| (k.into(), v.into()));
127 self.headers.extend(headers);
128 self
129 }
130
131 pub fn with_header<T: Into<String>, U: Into<String>>(mut self, key: T, value: U) -> Request {
134 self.headers.insert(key.into(), value.into());
135 self
136 }
137
138 pub fn with_body<T: Into<Vec<u8>>>(mut self, body: T) -> Request {
140 let body = body.into();
141 let body_length = body.len();
142 self.body = Some(body);
143 self.with_header("Content-Length", format!("{}", body_length))
144 }
145
146 pub fn with_param<T: Into<String>, U: Into<String>>(mut self, key: T, value: U) -> Request {
155 let key = key.into();
156 #[cfg(feature = "urlencoding")]
157 let key = urlencoding::encode(&key);
158 let value = value.into();
159 #[cfg(feature = "urlencoding")]
160 let value = urlencoding::encode(&value);
161
162 if !self.params.is_empty() {
163 self.params.push('&');
164 }
165 self.params.push_str(&key);
166 self.params.push('=');
167 self.params.push_str(&value);
168 self
169 }
170
171 #[cfg(feature = "json-using-serde")]
180 pub fn with_json<T: serde::ser::Serialize>(mut self, body: &T) -> Result<Request, Error> {
181 self.headers.insert(
182 "Content-Type".to_string(),
183 "application/json; charset=UTF-8".to_string(),
184 );
185 match serde_json::to_string(&body) {
186 Ok(json) => Ok(self.with_body(json)),
187 Err(err) => Err(Error::SerdeJsonError(err)),
188 }
189 }
190
191 pub fn with_timeout(mut self, timeout: u64) -> Request {
193 self.timeout = Some(timeout);
194 self
195 }
196
197 pub fn with_max_redirects(mut self, max_redirects: usize) -> Request {
205 self.max_redirects = max_redirects;
206 self
207 }
208
209 pub fn with_follow_redirects(mut self, follow_redirects: bool) -> Request {
220 self.follow_redirects = follow_redirects;
221 self
222 }
223
224 pub fn with_max_headers_size<S: Into<Option<usize>>>(mut self, max_headers_size: S) -> Request {
240 self.max_headers_size = max_headers_size.into();
241 self
242 }
243
244 pub fn with_max_status_line_length<S: Into<Option<usize>>>(
259 mut self,
260 max_status_line_len: S,
261 ) -> Request {
262 self.max_status_line_len = max_status_line_len.into();
263 self
264 }
265
266 #[cfg(feature = "proxy")]
268 pub fn with_proxy(mut self, proxy: Proxy) -> Request {
269 self.proxy = Some(proxy);
270 self
271 }
272
273 pub fn send(self) -> Result<Response, Error> {
284 let parsed_request = ParsedRequest::new(self)?;
285 if parsed_request.url.https {
286 #[cfg(any(feature = "rustls", feature = "openssl", feature = "native-tls"))]
287 {
288 let is_head = parsed_request.config.method == Method::Head;
289 let response = Connection::new(parsed_request).send_https()?;
290 Response::create(response, is_head)
291 }
292 #[cfg(not(any(feature = "rustls", feature = "openssl", feature = "native-tls")))]
293 {
294 Err(Error::HttpsFeatureNotEnabled)
295 }
296 } else {
297 let is_head = parsed_request.config.method == Method::Head;
298 let response = Connection::new(parsed_request).send()?;
299 Response::create(response, is_head)
300 }
301 }
302
303 pub fn send_lazy(self) -> Result<ResponseLazy, Error> {
309 let parsed_request = ParsedRequest::new(self)?;
310 if parsed_request.url.https {
311 #[cfg(any(feature = "rustls", feature = "openssl", feature = "native-tls"))]
312 {
313 Connection::new(parsed_request).send_https()
314 }
315 #[cfg(not(any(feature = "rustls", feature = "openssl", feature = "native-tls")))]
316 {
317 Err(Error::HttpsFeatureNotEnabled)
318 }
319 } else {
320 Connection::new(parsed_request).send()
321 }
322 }
323}
324
325pub(crate) struct ParsedRequest {
326 pub(crate) url: HttpUrl,
327 pub(crate) redirects: Vec<HttpUrl>,
328 pub(crate) config: Request,
329}
330
331impl ParsedRequest {
332 #[allow(unused_mut)]
333 fn new(mut config: Request) -> Result<ParsedRequest, Error> {
334 let mut url = HttpUrl::parse(&config.url, None)?;
335
336 if !config.params.is_empty() {
337 if url.path_and_query.contains('?') {
338 url.path_and_query.push('&');
339 } else {
340 url.path_and_query.push('?');
341 }
342 url.path_and_query.push_str(&config.params);
343 }
344
345 #[cfg(feature = "proxy")]
346 if config.proxy.is_none() {
354 if url.https {
356 if let Ok(proxy) =
357 std::env::var("https_proxy").map_err(|_| std::env::var("HTTPS_PROXY"))
358 {
359 if let Ok(proxy) = Proxy::new(proxy) {
360 config.proxy = Some(proxy);
361 }
362 }
363 }
364 else if let Ok(proxy) = std::env::var("http_proxy") {
366 if let Ok(proxy) = Proxy::new(proxy) {
367 config.proxy = Some(proxy);
368 }
369 }
370 else if let Ok(proxy) =
372 std::env::var("all_proxy").map_err(|_| std::env::var("ALL_PROXY"))
373 {
374 if let Ok(proxy) = Proxy::new(proxy) {
375 config.proxy = Some(proxy);
376 }
377 }
378 }
379
380 Ok(ParsedRequest {
381 url,
382 redirects: Vec::new(),
383 config,
384 })
385 }
386
387 fn get_http_head(&self) -> String {
388 let mut http = String::with_capacity(32);
389
390 write!(
401 http,
402 "{} {} HTTP/1.1\r\nHost: {}",
403 self.config.method, self.url.path_and_query, self.url.host
404 )
405 .unwrap();
406 if let Port::Explicit(port) = self.url.port {
407 write!(http, ":{}", port).unwrap();
408 }
409 http += "\r\n";
410
411 for (k, v) in &self.config.headers {
413 write!(http, "{}: {}\r\n", k, v).unwrap();
414 }
415
416 if self.config.method == Method::Post
417 || self.config.method == Method::Put
418 || self.config.method == Method::Patch
419 {
420 let not_length = |key: &String| {
421 let key = key.to_lowercase();
422 key != "content-length" && key != "transfer-encoding"
423 };
424 if self.config.headers.keys().all(not_length) {
425 http += "Content-Length: 0\r\n";
434 }
435 }
436
437 http += "\r\n";
438 http
439 }
440
441 pub(crate) fn as_bytes(&self) -> Vec<u8> {
444 let mut head = self.get_http_head().into_bytes();
445 if let Some(body) = &self.config.body {
446 head.extend(body);
447 }
448 head
449 }
450
451 pub(crate) fn redirect_to(&mut self, url: &str) -> Result<(), Error> {
455 if url.contains("://") {
456 let mut url = HttpUrl::parse(url, Some(&self.url)).map_err(|_| {
457 Error::IoError(std::io::Error::new(
460 std::io::ErrorKind::Other,
461 "was redirected to an absolute url with an invalid protocol",
462 ))
463 })?;
464 std::mem::swap(&mut url, &mut self.url);
465 self.redirects.push(url);
466 } else {
467 let mut absolute_url = String::new();
470 self.url.write_base_url_to(&mut absolute_url).unwrap();
471 absolute_url.push_str(url);
472 let mut url = HttpUrl::parse(&absolute_url, Some(&self.url))?;
473 std::mem::swap(&mut url, &mut self.url);
474 self.redirects.push(url);
475 }
476
477 if self.redirects.len() > self.config.max_redirects {
478 Err(Error::TooManyRedirections)
479 } else if self
480 .redirects
481 .iter()
482 .any(|redirect_url| redirect_url == &self.url)
483 {
484 Err(Error::InfiniteRedirectionLoop)
485 } else {
486 Ok(())
487 }
488 }
489}
490
491pub fn get<T: Into<URL>>(url: T) -> Request {
494 Request::new(Method::Get, url)
495}
496
497pub fn head<T: Into<URL>>(url: T) -> Request {
500 Request::new(Method::Head, url)
501}
502
503pub fn post<T: Into<URL>>(url: T) -> Request {
506 Request::new(Method::Post, url)
507}
508
509pub fn put<T: Into<URL>>(url: T) -> Request {
512 Request::new(Method::Put, url)
513}
514
515pub fn delete<T: Into<URL>>(url: T) -> Request {
518 Request::new(Method::Delete, url)
519}
520
521pub fn connect<T: Into<URL>>(url: T) -> Request {
524 Request::new(Method::Connect, url)
525}
526
527pub fn options<T: Into<URL>>(url: T) -> Request {
530 Request::new(Method::Options, url)
531}
532
533pub fn trace<T: Into<URL>>(url: T) -> Request {
536 Request::new(Method::Trace, url)
537}
538
539pub fn patch<T: Into<URL>>(url: T) -> Request {
542 Request::new(Method::Patch, url)
543}
544
545#[cfg(test)]
546mod parsing_tests {
547
548 use std::collections::HashMap;
549
550 use super::{get, ParsedRequest};
551
552 #[test]
553 fn test_headers() {
554 let mut headers = HashMap::new();
555 headers.insert("foo".to_string(), "bar".to_string());
556 headers.insert("foo".to_string(), "baz".to_string());
557
558 let req = get("http://www.example.org/test/res").with_headers(headers.clone());
559
560 assert_eq!(req.headers, headers);
561 }
562
563 #[test]
564 fn test_multiple_params() {
565 let req = get("http://www.example.org/test/res")
566 .with_param("foo", "bar")
567 .with_param("asd", "qwe");
568 let req = ParsedRequest::new(req).unwrap();
569 assert_eq!(&req.url.path_and_query, "/test/res?foo=bar&asd=qwe");
570 }
571
572 #[test]
573 fn test_domain() {
574 let req = get("http://www.example.org/test/res").with_param("foo", "bar");
575 let req = ParsedRequest::new(req).unwrap();
576 assert_eq!(&req.url.host, "www.example.org");
577 }
578
579 #[test]
580 fn test_protocol() {
581 let req =
582 ParsedRequest::new(get("http://www.example.org/").with_param("foo", "bar")).unwrap();
583 assert!(!req.url.https);
584 let req =
585 ParsedRequest::new(get("https://www.example.org/").with_param("foo", "bar")).unwrap();
586 assert!(req.url.https);
587 }
588}
589
590#[cfg(all(test, feature = "urlencoding"))]
591mod encoding_tests {
592 use super::{get, ParsedRequest};
593
594 #[test]
595 fn test_with_param() {
596 let req = get("http://www.example.org").with_param("foo", "bar");
597 let req = ParsedRequest::new(req).unwrap();
598 assert_eq!(&req.url.path_and_query, "/?foo=bar");
599
600 let req = get("http://www.example.org").with_param("ówò", "what's this? 👀");
601 let req = ParsedRequest::new(req).unwrap();
602 assert_eq!(
603 &req.url.path_and_query,
604 "/?%C3%B3w%C3%B2=what%27s%20this%3F%20%F0%9F%91%80"
605 );
606 }
607
608 #[test]
609 fn test_on_creation() {
610 let req = ParsedRequest::new(get("http://www.example.org/?foo=bar#baz")).unwrap();
611 assert_eq!(&req.url.path_and_query, "/?foo=bar");
612
613 let req = ParsedRequest::new(get("http://www.example.org/?ówò=what's this? 👀")).unwrap();
614 assert_eq!(
615 &req.url.path_and_query,
616 "/?%C3%B3w%C3%B2=what%27s%20this?%20%F0%9F%91%80"
617 );
618 }
619}