#![allow(dead_code)]
#[macro_use]
extern crate lazy_static;
pub mod config;
pub mod cookie;
pub mod auth;
mod constants;
mod http;
use crate::auth::AuthManager;
use std::sync::Mutex;
use crate::cookie::MemCookieJar;
use crate::cookie::CookieJar;
use std::sync::Arc;
use crate::cookie::Cookie;
use crate::config::{HttpConfig, HttpConfigBuilder};
use crate::constants::{CONTENT_TYPE, CONTENT_TYPE_JSON};
use crate::http::{parse_url, EndPoint, HttpScheme, ClientConnectionFactory};
use std::str::from_utf8;
use std::io::{Error, ErrorKind};
use std::fmt;
use std::collections::HashMap;
use case_insensitive_hashmap::CaseInsensitiveHashMap;
use json::JsonValue;
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum HttpMethod {GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH}
impl fmt::Display for HttpMethod {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}",
match self {
Self::GET => "GET",
Self::HEAD => "HEAD",
Self::POST => "POST",
Self::PUT=> "PUT",
Self::DELETE=> "DELETE",
Self::CONNECT => "CONNECT",
Self::OPTIONS => "OPTIONS",
Self::TRACE => "TRACE",
Self::PATCH=> "PATCH"
}
)
}
}
pub struct Request {
pub method: HttpMethod,
pub(crate) url: String,
pub(crate) endpoint: Option<EndPoint>,
pub path: String,
pub(crate) config: HttpConfig,
pub(crate) headers: CaseInsensitiveHashMap<String>,
pub (crate) jar: Option<Arc<Mutex<dyn CookieJar>>>,
pub(crate) cookies: HashMap<String, String>,
pub(crate) params: HashMap<String, String>,
pub(crate) body: Vec<u8>,
factory: Option<Arc<Mutex<ClientConnectionFactory>>>,
auth: Option<Arc<Mutex<dyn AuthManager>>>,
init_error: Option<Error>
}
impl Request {
pub fn has_body(&self) -> bool {
self.body.len() > 0
}
pub fn url(&self) -> &str {
self.url.as_str()
}
pub (crate) fn endpoint(&self) -> Result<&EndPoint, Error> {
if self.endpoint.is_none() {
return Err(Error::new(ErrorKind::InvalidData, "URL has not got a valid endpoint"));
}
Ok(self.endpoint.as_ref().unwrap())
}
pub (crate) fn path(&self) -> &str {
self.path.as_str()
}
pub fn send(&mut self) -> Result<Response, Error> {
if let Some(ref error) = self.init_error {
return Err(Error::new(error.kind(), error.get_ref().unwrap().to_string()));
}
if self.endpoint.is_none(){
return Err(Error::new(ErrorKind::InvalidData, "Cannot get host from URL"));
}
let endpoint = self.endpoint.as_ref().unwrap();
if let Some(ref cookie_jar) = self.jar {
let active_cookies = cookie_jar.lock().as_mut().unwrap().active_cookies(endpoint.host.as_str(), &self.path, endpoint.scheme == HttpScheme::HTTPS);
for cookie in active_cookies {
if self.cookies.contains_key(&cookie.0) {
continue;
}
self.cookies.insert(cookie.0, cookie.1);
}
}
let connection = if let Some(ref factory) = &self.factory {
factory.lock().unwrap().get_connection(endpoint, &self.config)?
} else {
ClientConnectionFactory::client_connection(endpoint, &self.config)?
};
return connection.lock().unwrap().send(self);
}
}
pub struct RequestBuilder {
method: HttpMethod,
url: String,
config: HttpConfig,
headers: CaseInsensitiveHashMap<String>,
jar: Option<Arc<Mutex<dyn CookieJar>>>,
cookies: HashMap<String, String>,
params: HashMap<String, String>,
body: Vec<u8>,
factory: Option<Arc<Mutex<ClientConnectionFactory>>>,
auth: Option<Arc<Mutex<dyn AuthManager>>>
}
impl RequestBuilder {
pub fn new<S>(method: HttpMethod, url: S) -> RequestBuilder
where S : Into<String> {
RequestBuilder {
method,
url: url.into(),
config: HttpConfigBuilder::default().build(),
headers: CaseInsensitiveHashMap::new(),
jar: None,
cookies: HashMap::new(),
params: HashMap::new(),
body: Vec::new(),
factory: None,
auth: None
}
}
pub fn connect<S>(url: S) -> RequestBuilder
where S : Into<String> {
Self::new(HttpMethod::CONNECT, url)
}
pub fn delete<S>(url: S) -> RequestBuilder
where S : Into<String> {
Self::new(HttpMethod::DELETE, url)
}
pub fn get<S>(url: S) -> RequestBuilder
where S : Into<String> {
Self::new(HttpMethod::GET, url)
}
pub fn head<S>(url: S) -> RequestBuilder
where S : Into<String> {
Self::new(HttpMethod::HEAD, url)
}
pub fn options<S>(url: S) -> RequestBuilder
where S : Into<String> {
Self::new(HttpMethod::OPTIONS, url)
}
pub fn patch<S>(url: S) -> RequestBuilder
where S : Into<String> {
Self::new(HttpMethod::PATCH, url)
}
pub fn post<S>(url: S) -> RequestBuilder
where S : Into<String> {
Self::new(HttpMethod::POST, url)
}
pub fn put<S>(url: S) -> RequestBuilder
where S : Into<String> {
Self::new(HttpMethod::PUT, url)
}
pub fn trace<S>(url: S) -> RequestBuilder
where S : Into<String> {
Self::new(HttpMethod::TRACE, url)
}
pub fn config( mut self, data: &HttpConfig) -> RequestBuilder {
self.config = data.clone();
self
}
pub fn headers( mut self, data: HashMap<String, String>) -> RequestBuilder {
self.headers.extend(data);
self
}
pub fn header<S>(mut self, name: S, value: S) -> RequestBuilder
where S : Into<String> {
self.headers.insert(name.into(), value.into());
self
}
pub fn params( mut self, data: HashMap<String, String>) -> RequestBuilder {
self.params.extend(data);
self
}
pub fn param<S>(mut self, name: S, value: S) -> RequestBuilder
where S : Into<String> {
self.params.insert(name.into(), value.into());
self
}
pub fn cookies( mut self, data: HashMap<String, String>) -> RequestBuilder {
self.params.extend(data);
self
}
pub fn cookie<S>(mut self, name: S, value: S) -> RequestBuilder
where S : Into<String> {
self.cookies.insert(name.into(), value.into());
self
}
pub fn cookie_jar(mut self, jar: Arc<Mutex<dyn CookieJar>>) -> RequestBuilder {
self.jar = Some(jar);
self
}
pub fn body(mut self, data: Vec<u8>) -> RequestBuilder {
self.body = data;
self
}
pub fn json(mut self, data: &JsonValue) -> RequestBuilder {
let pretty = data.pretty(4);
self.body = pretty.into_bytes();
self.headers.insert(String::from(CONTENT_TYPE), String::from(CONTENT_TYPE_JSON));
self
}
fn session(mut self, session: &Session) -> RequestBuilder {
self.config = session.config.clone();
self.jar = Some(session.jar.clone());
self.factory = Some(Arc::clone(&session.factory));
if let Some(ref auth) = session.auth {
self.auth = Some(Arc::clone(auth));
}
self
}
pub(crate) fn factory(mut self, factory: Arc<Mutex<ClientConnectionFactory>>) -> RequestBuilder {
self.factory = Some(factory);
self
}
pub fn auth(mut self, mgr: Arc<Mutex<dyn AuthManager>>) -> RequestBuilder {
self.auth = Some(mgr);
self
}
pub fn build(self) -> Request {
let mut init_error: Option<Error> = None;
let mut endpoint_holder: Option<EndPoint> = None;
let url = parse_url(&self.url);
let path = String::from(
if url.is_ok() {
url.as_ref().unwrap().path()
} else {
"/"
}
);
if url.is_ok() {
let endpoint = EndPoint::from_url(url.as_ref().unwrap());
if endpoint.is_err() {
init_error = Some(endpoint.err().unwrap());
} else {
endpoint_holder = Some(endpoint.unwrap());
}
} else {
init_error = url.err();
}
Request {
method: self.method,
url: self.url,
endpoint: endpoint_holder,
path,
config: self.config,
headers: self.headers,
jar: self.jar,
cookies: self.cookies,
params: self.params,
body: self.body,
factory: self.factory,
auth: self.auth,
init_error
}
}
}
pub struct Session {
config: HttpConfig,
jar: Arc<Mutex<dyn CookieJar>>,
auth: Option<Arc<Mutex<dyn AuthManager>>>,
factory: Arc<Mutex<ClientConnectionFactory>>
}
impl Session {
pub fn config(&self) -> &HttpConfig {
&self.config
}
pub fn connect<S>(&self, url: S) -> RequestBuilder
where S: Into<String> {
RequestBuilder::new(HttpMethod::CONNECT, url)
.session(self)
}
pub fn delete<S>(&self, url: S) -> RequestBuilder
where S: Into<String> {
RequestBuilder::new(HttpMethod::DELETE, url)
.session(self)
}
pub fn get<S>(&self, url: S) -> RequestBuilder
where S: Into<String> {
RequestBuilder::new(HttpMethod::GET, url)
.session(self)
}
pub fn head<S>(&self, url: S) -> RequestBuilder
where S: Into<String> {
RequestBuilder::new(HttpMethod::HEAD, url)
.session(self)
}
pub fn options<S>(&self, url: S) -> RequestBuilder
where S: Into<String> {
RequestBuilder::new(HttpMethod::OPTIONS, url)
.session(self)
}
pub fn patch<S>(&self, url: S) -> RequestBuilder
where S: Into<String> {
RequestBuilder::new(HttpMethod::PATCH, url)
.session(self)
}
pub fn post<S>(&self, url: S) -> RequestBuilder
where S: Into<String> {
RequestBuilder::new(HttpMethod::POST, url)
.session(self)
}
pub fn put<S>(&self, url: S) -> RequestBuilder
where S: Into<String> {
RequestBuilder::new(HttpMethod::PUT, url)
.session(self)
}
pub fn trace<S>(&self, url: S) -> RequestBuilder
where S: Into<String> {
RequestBuilder::new(HttpMethod::TRACE, url)
.session(self)
}
}
pub struct SessionBuilder {
config: Option<HttpConfig>,
jar: Option<Arc<Mutex<dyn CookieJar>>>,
auth: Option<Arc<Mutex<dyn AuthManager>>>
}
impl SessionBuilder {
pub fn new() -> SessionBuilder {
SessionBuilder {
config: None,
jar: None,
auth: None
}
}
pub fn config( mut self, data: &HttpConfig) -> SessionBuilder {
self.config = Some(data.clone());
self
}
pub fn cookie_jar(mut self, jar: Arc<Mutex<dyn CookieJar>>) -> SessionBuilder {
self.jar = Some(jar);
self
}
pub fn auth(mut self, auth: Arc<Mutex<dyn AuthManager>>) -> SessionBuilder {
self.auth = Some(auth);
self
}
pub fn build(mut self) -> Session {
if self.jar.is_none() {
self.jar = Some(Arc::new(Mutex::new(MemCookieJar::new())));
}
if self.config.is_none() {
self.config = Some(HttpConfigBuilder::default().build());
}
Session {
config: self.config.unwrap(),
jar: self.jar.unwrap(),
auth: self.auth,
factory: Arc::new(Mutex::new(ClientConnectionFactory::new()))
}
}
}
pub type HttpStatusCode = u16;
pub const HTTP_100_CONTINUE: u16 = 100;
pub const HTTP_101_SWITCHING_PROTOCOLS: u16 = 101;
pub const HTTP_200_OK: u16 = 200;
pub const HTTP_201_CREATED: u16 = 201;
pub const HTTP_202_ACCEPTED: u16 = 202;
pub const HTTP_204_NO_CONTENT: u16 = 204;
pub const HTTP_205_RESET_CONTENT: u16 = 205;
pub const HTTP_400_BAD_REQUEST: u16 = 400;
pub const HTTP_401_UNAUTHORIZED: u16 = 401;
pub const HTTP_402_FORBIDDEN: u16 = 402;
pub const HTTP_404_NOT_FOUND: u16 = 404;
pub const HTTP_405_METHOD_NOT_ALLOWED: u16 = 405;
pub const HTTP_406_NOT_ACCEPTABLE: u16 = 406;
pub const HTTP_408_REQUEST_TIMEOUT: u16 = 408;
pub const HTTP_500_INTERNAL_SERVE_ERROR: u16 = 500;
pub const HTTP_501_NOT_IMPLEMENTED: u16 = 501;
pub type SetCookies = Vec<Cookie>;
pub struct Response {
pub(crate) status_code: HttpStatusCode,
pub(crate) headers: HashMap<String, String>,
pub(crate) cookies: SetCookies,
pub(crate) auth: Vec<String>,
pub(crate) proxy_auth: Vec<String>,
pub(crate) body: Vec<u8>
}
impl Response {
pub fn new(status: HttpStatusCode) -> Response {
Response {
headers: HashMap::new(),
cookies: SetCookies::new(),
status_code: status,
auth: Vec::new(),
proxy_auth: Vec::new(),
body: Vec::new()
}
}
pub fn status_code(&self) -> HttpStatusCode {
self.status_code
}
pub fn headers(&self) -> &HashMap<String, String> {
&self.headers
}
pub fn cookie(&self) -> &SetCookies {
&self.cookies
}
pub fn auth(&self) -> Vec<&str> {
self.auth.iter().map(AsRef::as_ref).collect()
}
pub fn proxy_auth(&self) -> Vec<&str> {
self.proxy_auth.iter().map(AsRef::as_ref).collect()
}
pub fn has_body(&self) -> bool {self.body.len() > 0}
pub fn body(& self) -> &Vec<u8> {
&self.body
}
pub fn json(&self) -> Result<JsonValue, Error> {
if self.body.is_empty() {
return Err(Error::new(ErrorKind::InvalidData, "Empty body"));
}
let str_body = from_utf8(&self.body);
if str_body.is_err() {
return Err(Error::new(ErrorKind::InvalidData, str_body.err().unwrap()));
}
let result = json::parse(str_body.unwrap());
return if result.is_ok() {
Ok(result.unwrap())
} else {
Err(Error::new(ErrorKind::InvalidData, result.err().unwrap()))
}
}
}
#[cfg(test)]
mod test;