use crate::connection::Connection;
#[cfg(feature = "proxy")]
use crate::proxy::Proxy;
use crate::{Error, Response, ResponseLazy};
use std::collections::HashMap;
use std::fmt;
pub type URL = String;
#[derive(Clone, PartialEq, Debug)]
pub enum Method {
Get,
Head,
Post,
Put,
Delete,
Connect,
Options,
Trace,
Patch,
Custom(String),
}
impl fmt::Display for Method {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Method::Get => write!(f, "GET"),
Method::Head => write!(f, "HEAD"),
Method::Post => write!(f, "POST"),
Method::Put => write!(f, "PUT"),
Method::Delete => write!(f, "DELETE"),
Method::Connect => write!(f, "CONNECT"),
Method::Options => write!(f, "OPTIONS"),
Method::Trace => write!(f, "TRACE"),
Method::Patch => write!(f, "PATCH"),
Method::Custom(ref s) => write!(f, "{}", s),
}
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct Request {
pub(crate) method: Method,
pub(crate) host: URL,
resource: URL,
headers: HashMap<String, String>,
body: Option<Vec<u8>>,
pub(crate) timeout: Option<u64>,
max_redirects: usize,
https: bool,
pub(crate) redirects: Vec<(bool, URL, URL)>,
#[cfg(feature = "proxy")]
pub(crate) proxy: Option<Proxy>,
}
impl Request {
pub fn new<T: Into<URL>>(method: Method, url: T) -> Request {
let (https, host, resource) = parse_url(url.into());
Request {
method,
host,
resource,
headers: HashMap::new(),
body: None,
timeout: None,
max_redirects: 100,
https,
redirects: Vec::new(),
#[cfg(feature = "proxy")]
proxy: None,
}
}
pub fn with_header<T: Into<String>, U: Into<String>>(mut self, key: T, value: U) -> Request {
self.headers.insert(key.into(), value.into());
self
}
pub fn with_body<T: Into<Vec<u8>>>(mut self, body: T) -> Request {
let body = body.into();
let body_length = body.len();
self.body = Some(body);
self.with_header("Content-Length", format!("{}", body_length))
}
#[cfg(feature = "json-using-serde")]
pub fn with_json<T: serde::ser::Serialize>(mut self, body: &T) -> Result<Request, Error> {
self.headers.insert(
"Content-Type".to_string(),
"application/json; charset=UTF-8".to_string(),
);
match serde_json::to_string(&body) {
Ok(json) => Ok(self.with_body(json)),
Err(err) => Err(Error::SerdeJsonError(err)),
}
}
pub fn with_timeout(mut self, timeout: u64) -> Request {
self.timeout = Some(timeout);
self
}
pub fn with_max_redirects(mut self, max_redirects: usize) -> Request {
self.max_redirects = max_redirects;
self
}
#[cfg(any(feature = "rustls", feature = "openssl", feature = "native-tls"))]
pub fn send(self) -> Result<Response, Error> {
if self.https {
let is_head = self.method == Method::Head;
let response = Connection::new(self).send_https()?;
Response::create(response, is_head)
} else {
let is_head = self.method == Method::Head;
let response = Connection::new(self).send()?;
Response::create(response, is_head)
}
}
#[cfg(any(feature = "rustls", feature = "openssl", feature = "native-tls"))]
pub fn send_lazy(self) -> Result<ResponseLazy, Error> {
if self.https {
Connection::new(self).send_https()
} else {
Connection::new(self).send()
}
}
#[cfg(not(any(feature = "rustls", feature = "openssl", feature = "native-tls")))]
pub fn send(self) -> Result<Response, Error> {
if self.https {
Err(Error::HttpsFeatureNotEnabled)
} else {
let is_head = self.method == Method::Head;
let response = Connection::new(self).send()?;
Response::create(response, is_head)
}
}
#[cfg(not(any(feature = "rustls", feature = "openssl", feature = "native-tls")))]
pub fn send_lazy(self) -> Result<ResponseLazy, Error> {
if self.https {
Err(Error::HttpsFeatureNotEnabled)
} else {
Connection::new(self).send()
}
}
fn get_http_head(&self) -> String {
let mut http = String::with_capacity(32);
http += &format!(
"{} {} HTTP/1.1\r\nHost: {}\r\n",
self.method, self.resource, self.host
);
for (k, v) in &self.headers {
http += &format!("{}: {}\r\n", k, v);
}
http += "\r\n";
http
}
pub(crate) fn as_bytes(&self) -> Vec<u8> {
let mut head = self.get_http_head().into_bytes();
if let Some(body) = &self.body {
head.extend(body);
}
head
}
pub(crate) fn redirect_to(mut self, url: URL) -> Result<Request, Error> {
self.redirects.push((self.https, self.host, self.resource));
let (https, host, resource) = parse_url(url);
self.host = host;
self.resource = resource;
self.https = https;
let is_this_url = |(https_, host_, resource_): &(bool, URL, URL)| {
*resource_ == self.resource && *host_ == self.host && *https_ == https
};
if self.redirects.len() > self.max_redirects {
Err(Error::TooManyRedirections)
} else if self.redirects.iter().any(is_this_url) {
Err(Error::InfiniteRedirectionLoop)
} else {
Ok(self)
}
}
#[cfg(feature = "proxy")]
pub fn with_proxy(mut self, proxy: Proxy) -> Request {
self.proxy = Some(proxy);
self
}
}
fn parse_url(url: URL) -> (bool, URL, URL) {
let mut first = URL::new();
let mut second = URL::new();
let mut slashes = 0;
for c in url.chars() {
if c == '/' {
slashes += 1;
} else if slashes == 2 {
first.push(c);
}
if slashes >= 3 {
second.push(c);
}
}
if second.is_empty() {
second += "/";
}
let https = url.starts_with("https://");
if !first.contains(':') {
first += if https { ":443" } else { ":80" };
}
(https, first, second)
}
pub fn get<T: Into<URL>>(url: T) -> Request {
Request::new(Method::Get, url)
}
pub fn head<T: Into<URL>>(url: T) -> Request {
Request::new(Method::Head, url)
}
pub fn post<T: Into<URL>>(url: T) -> Request {
Request::new(Method::Post, url)
}
pub fn put<T: Into<URL>>(url: T) -> Request {
Request::new(Method::Put, url)
}
pub fn delete<T: Into<URL>>(url: T) -> Request {
Request::new(Method::Delete, url)
}
pub fn connect<T: Into<URL>>(url: T) -> Request {
Request::new(Method::Connect, url)
}
pub fn options<T: Into<URL>>(url: T) -> Request {
Request::new(Method::Options, url)
}
pub fn trace<T: Into<URL>>(url: T) -> Request {
Request::new(Method::Trace, url)
}
pub fn patch<T: Into<URL>>(url: T) -> Request {
Request::new(Method::Patch, url)
}