refactor + docs
This commit is contained in:
parent
bda82c9c7c
commit
38d4f30f55
@ -4,9 +4,14 @@ use async_trait::async_trait;
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use tokio::{fs, io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}};
|
use tokio::{fs, io::{AsyncReadExt, AsyncWrite, AsyncWriteExt}};
|
||||||
|
|
||||||
use crate::ezhttp::{split_bytes, split_bytes_once};
|
use super::{
|
||||||
|
split_bytes,
|
||||||
use super::{error::HttpError, headers::Headers, read_line_crlf, Sendable};
|
split_bytes_once,
|
||||||
|
error::HttpError,
|
||||||
|
headers::Headers,
|
||||||
|
read_line_crlf,
|
||||||
|
Sendable
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Body {
|
pub struct Body {
|
@ -2,12 +2,14 @@ use crate::{error::HttpError, headers::Headers, prelude::HttpResponse, request::
|
|||||||
|
|
||||||
use super::{send_request, Proxy};
|
use super::{send_request, Proxy};
|
||||||
|
|
||||||
|
/// Client that sends http requests
|
||||||
pub struct HttpClient {
|
pub struct HttpClient {
|
||||||
proxy: Proxy,
|
proxy: Proxy,
|
||||||
verify: bool,
|
verify: bool,
|
||||||
headers: Headers
|
headers: Headers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [`HttpClient`](HttpClient) builder
|
||||||
pub struct ClientBuilder {
|
pub struct ClientBuilder {
|
||||||
proxy: Proxy,
|
proxy: Proxy,
|
||||||
verify: bool,
|
verify: bool,
|
||||||
@ -15,6 +17,7 @@ pub struct ClientBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ClientBuilder {
|
impl ClientBuilder {
|
||||||
|
/// Create a client builder
|
||||||
pub fn new() -> ClientBuilder {
|
pub fn new() -> ClientBuilder {
|
||||||
ClientBuilder {
|
ClientBuilder {
|
||||||
proxy: Proxy::None,
|
proxy: Proxy::None,
|
||||||
@ -23,6 +26,7 @@ impl ClientBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build a client
|
||||||
pub fn build(self) -> HttpClient {
|
pub fn build(self) -> HttpClient {
|
||||||
HttpClient {
|
HttpClient {
|
||||||
proxy: self.proxy,
|
proxy: self.proxy,
|
||||||
@ -31,22 +35,25 @@ impl ClientBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set client proxy
|
||||||
pub fn proxy(mut self, proxy: Proxy) -> Self {
|
pub fn proxy(mut self, proxy: Proxy) -> Self {
|
||||||
self.proxy = proxy;
|
self.proxy = proxy;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set is client have to verify ssl certificate
|
||||||
pub fn verify(mut self, verify: bool) -> Self {
|
pub fn verify(mut self, verify: bool) -> Self {
|
||||||
self.verify = verify;
|
self.verify = verify;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set default headers
|
||||||
pub fn headers(mut self, headers: Headers) -> Self {
|
pub fn headers(mut self, headers: Headers) -> Self {
|
||||||
self.headers = headers;
|
self.headers = headers;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set default header
|
||||||
pub fn header(mut self, name: impl ToString, value: impl ToString) -> Self {
|
pub fn header(mut self, name: impl ToString, value: impl ToString) -> Self {
|
||||||
self.headers.put(name, value.to_string());
|
self.headers.put(name, value.to_string());
|
||||||
self
|
self
|
||||||
@ -54,16 +61,19 @@ impl ClientBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl HttpClient {
|
impl HttpClient {
|
||||||
|
/// Get new HttpClient builder
|
||||||
pub fn builder() -> ClientBuilder {
|
pub fn builder() -> ClientBuilder {
|
||||||
ClientBuilder::new()
|
ClientBuilder::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends a request and receives a response
|
||||||
pub async fn send(&self, request: HttpRequest) -> Result<HttpResponse, HttpError> {
|
pub async fn send(&self, request: HttpRequest) -> Result<HttpResponse, HttpError> {
|
||||||
send_request(request, self.verify, self.proxy.clone(), self.headers.clone()).await
|
send_request(request, self.verify, self.proxy.clone(), self.headers.clone()).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for HttpClient {
|
impl Default for HttpClient {
|
||||||
|
/// Create default HttpClient
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
ClientBuilder::new().build()
|
ClientBuilder::new().build()
|
||||||
}
|
}
|
@ -18,8 +18,6 @@ pub use req_builder::*;
|
|||||||
pub use client::*;
|
pub use client::*;
|
||||||
pub use proxy::*;
|
pub use proxy::*;
|
||||||
|
|
||||||
|
|
||||||
// TODO: proxy support
|
|
||||||
async fn send_request(request: HttpRequest, ssl_verify: bool, proxy: Proxy, headers: Headers) -> Result<HttpResponse, HttpError> {
|
async fn send_request(request: HttpRequest, ssl_verify: bool, proxy: Proxy, headers: Headers) -> Result<HttpResponse, HttpError> {
|
||||||
let mut request = request.clone();
|
let mut request = request.clone();
|
||||||
|
|
@ -4,6 +4,7 @@ use serde_json::Value;
|
|||||||
|
|
||||||
use super::{super::body::{Body, Part}, gen_multipart_boundary, super::headers::Headers, super::request::{HttpRequest, URL}};
|
use super::{super::body::{Body, Part}, gen_multipart_boundary, super::headers::Headers, super::request::{HttpRequest, URL}};
|
||||||
|
|
||||||
|
/// Builder for [`HttpRequest`](HttpRequest)
|
||||||
pub struct RequestBuilder {
|
pub struct RequestBuilder {
|
||||||
method: String,
|
method: String,
|
||||||
url: URL,
|
url: URL,
|
||||||
@ -12,6 +13,7 @@ pub struct RequestBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RequestBuilder {
|
impl RequestBuilder {
|
||||||
|
/// Create builder with a custom method
|
||||||
pub fn new(method: String, url: URL) -> Self {
|
pub fn new(method: String, url: URL) -> Self {
|
||||||
RequestBuilder {
|
RequestBuilder {
|
||||||
method,
|
method,
|
||||||
@ -21,56 +23,82 @@ impl RequestBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create builder for a GET request
|
||||||
pub fn get(url: URL) -> Self { Self::new("GET".to_string(), url) }
|
pub fn get(url: URL) -> Self { Self::new("GET".to_string(), url) }
|
||||||
|
|
||||||
|
/// Create builder for a HEAD request
|
||||||
pub fn head(url: URL) -> Self { Self::new("HEAD".to_string(), url) }
|
pub fn head(url: URL) -> Self { Self::new("HEAD".to_string(), url) }
|
||||||
|
|
||||||
|
/// Create builder for a POST request
|
||||||
pub fn post(url: URL) -> Self { Self::new("POST".to_string(), url) }
|
pub fn post(url: URL) -> Self { Self::new("POST".to_string(), url) }
|
||||||
|
|
||||||
|
/// Create builder for a PUT request
|
||||||
pub fn put(url: URL) -> Self { Self::new("PUT".to_string(), url) }
|
pub fn put(url: URL) -> Self { Self::new("PUT".to_string(), url) }
|
||||||
|
|
||||||
|
/// Create builder for a DELETE request
|
||||||
pub fn delete(url: URL) -> Self { Self::new("DELETE".to_string(), url) }
|
pub fn delete(url: URL) -> Self { Self::new("DELETE".to_string(), url) }
|
||||||
|
|
||||||
|
/// Create builder for a CONNECT request
|
||||||
pub fn connect(url: URL) -> Self { Self::new("CONNECT".to_string(), url) }
|
pub fn connect(url: URL) -> Self { Self::new("CONNECT".to_string(), url) }
|
||||||
|
|
||||||
|
/// Create builder for a OPTIONS request
|
||||||
pub fn options(url: URL) -> Self { Self::new("OPTIONS".to_string(), url) }
|
pub fn options(url: URL) -> Self { Self::new("OPTIONS".to_string(), url) }
|
||||||
|
|
||||||
|
/// Create builder for a TRACE request
|
||||||
pub fn trace(url: URL) -> Self { Self::new("TRACE".to_string(), url) }
|
pub fn trace(url: URL) -> Self { Self::new("TRACE".to_string(), url) }
|
||||||
|
|
||||||
|
/// Create builder for a PATCH request
|
||||||
pub fn patch(url: URL) -> Self { Self::new("PATCH".to_string(), url) }
|
pub fn patch(url: URL) -> Self { Self::new("PATCH".to_string(), url) }
|
||||||
|
|
||||||
|
/// Set request url
|
||||||
pub fn url(mut self, url: URL) -> Self {
|
pub fn url(mut self, url: URL) -> Self {
|
||||||
self.url = url;
|
self.url = url;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set request method
|
||||||
pub fn method(mut self, method: String) -> Self {
|
pub fn method(mut self, method: String) -> Self {
|
||||||
self.method = method;
|
self.method = method;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set headers
|
||||||
pub fn headers(mut self, headers: Headers) -> Self {
|
pub fn headers(mut self, headers: Headers) -> Self {
|
||||||
self.headers = headers;
|
self.headers = headers;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set header
|
||||||
pub fn header(mut self, name: impl ToString, value: impl ToString) -> Self {
|
pub fn header(mut self, name: impl ToString, value: impl ToString) -> Self {
|
||||||
self.headers.put(name, value.to_string());
|
self.headers.put(name, value.to_string());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set body
|
||||||
pub fn body(mut self, body: Body) -> Self {
|
pub fn body(mut self, body: Body) -> Self {
|
||||||
self.body = Some(body);
|
self.body = Some(body);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set text as body
|
||||||
pub fn text(mut self, text: impl ToString) -> Self {
|
pub fn text(mut self, text: impl ToString) -> Self {
|
||||||
self.body = Some(Body::from_text(text.to_string().as_str()));
|
self.body = Some(Body::from_text(text.to_string().as_str()));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set json as body
|
||||||
pub fn json(mut self, json: Value) -> Self {
|
pub fn json(mut self, json: Value) -> Self {
|
||||||
self.body = Some(Body::from_json(json));
|
self.body = Some(Body::from_json(json));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set raw bytes as body
|
||||||
pub fn bytes(mut self, bytes: &[u8]) -> Self {
|
pub fn bytes(mut self, bytes: &[u8]) -> Self {
|
||||||
self.body = Some(Body::from_bytes(bytes));
|
self.body = Some(Body::from_bytes(bytes));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set multipart as body
|
||||||
pub fn multipart(mut self, parts: &[Part]) -> Self {
|
pub fn multipart(mut self, parts: &[Part]) -> Self {
|
||||||
let boundary = gen_multipart_boundary();
|
let boundary = gen_multipart_boundary();
|
||||||
self.headers.put("Content-Type", format!("multipart/form-data; boundary={}", boundary.clone()));
|
self.headers.put("Content-Type", format!("multipart/form-data; boundary={}", boundary.clone()));
|
||||||
@ -78,16 +106,19 @@ impl RequestBuilder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set query in url
|
||||||
pub fn url_query(mut self, query: &[(impl ToString, impl ToString)]) -> Self {
|
pub fn url_query(mut self, query: &[(impl ToString, impl ToString)]) -> Self {
|
||||||
self.url.query = HashMap::from_iter(query.iter().map(|o| (o.0.to_string(), o.1.to_string())));
|
self.url.query = HashMap::from_iter(query.iter().map(|o| (o.0.to_string(), o.1.to_string())));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set query as body
|
||||||
pub fn body_query(mut self, query: &[(impl ToString, impl ToString)]) -> Self {
|
pub fn body_query(mut self, query: &[(impl ToString, impl ToString)]) -> Self {
|
||||||
self.body = Some(Body::from_query(HashMap::from_iter(query.iter().map(|o| (o.0.to_string(), o.1.to_string())))));
|
self.body = Some(Body::from_query(HashMap::from_iter(query.iter().map(|o| (o.0.to_string(), o.1.to_string())))));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build request
|
||||||
pub fn build(self) -> HttpRequest {
|
pub fn build(self) -> HttpRequest {
|
||||||
HttpRequest {
|
HttpRequest {
|
||||||
url: self.url,
|
url: self.url,
|
@ -1,6 +1,6 @@
|
|||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
/// Http error
|
/// Http library errors
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum HttpError {
|
pub enum HttpError {
|
||||||
ReadLineEof,
|
ReadLineEof,
|
@ -1,120 +0,0 @@
|
|||||||
pub mod error;
|
|
||||||
pub mod headers;
|
|
||||||
pub mod request;
|
|
||||||
pub mod response;
|
|
||||||
pub mod body;
|
|
||||||
pub mod server;
|
|
||||||
pub mod client;
|
|
||||||
|
|
||||||
pub mod prelude {
|
|
||||||
pub use super::error::*;
|
|
||||||
pub use super::headers::*;
|
|
||||||
pub use super::request::*;
|
|
||||||
pub use super::response::*;
|
|
||||||
pub use super::response::status_code::*;
|
|
||||||
pub use super::body::*;
|
|
||||||
pub use super::server::*;
|
|
||||||
pub use super::server::handler::*;
|
|
||||||
pub use super::server::starter::*;
|
|
||||||
// pub use super::client::*;
|
|
||||||
pub use super::*;
|
|
||||||
}
|
|
||||||
|
|
||||||
use error::HttpError;
|
|
||||||
use rand::Rng;
|
|
||||||
use tokio::{io::{AsyncReadExt, AsyncWrite}, net::TcpStream};
|
|
||||||
use tokio_io_timeout::TimeoutStream;
|
|
||||||
use async_trait::async_trait;
|
|
||||||
|
|
||||||
const CHARS: &str = "qwertyuiopasdfghjklzxcvbnm0123456789QWERTYUIOPASDFGHJKLZXCVBNM'()+_,-./:=?";
|
|
||||||
|
|
||||||
pub fn gen_multipart_boundary() -> String {
|
|
||||||
let range = 20..40;
|
|
||||||
let length: usize = rand::thread_rng().gen_range(range);
|
|
||||||
[0..length].iter().map(|_|
|
|
||||||
String::from(CHARS.chars()
|
|
||||||
.collect::<Vec<char>>()
|
|
||||||
.get(rand::thread_rng()
|
|
||||||
.gen_range(0..CHARS.len())
|
|
||||||
).unwrap().clone()
|
|
||||||
)
|
|
||||||
).collect::<Vec<String>>().join("")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn split_bytes_once(bytes: &[u8], sep: &[u8]) -> (Vec<u8>, Vec<u8>) {
|
|
||||||
if let Some(index) = bytes.windows(sep.len())
|
|
||||||
.enumerate()
|
|
||||||
.filter(|o| o.1 == sep)
|
|
||||||
.map(|o| o.0)
|
|
||||||
.next() {
|
|
||||||
let t = bytes.split_at(index);
|
|
||||||
(t.0.to_vec(), t.1.split_at(sep.len()).1.to_vec())
|
|
||||||
} else {
|
|
||||||
(Vec::from(bytes), Vec::new())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn split_bytes(bytes: &[u8], sep: &[u8]) -> Vec<Vec<u8>> {
|
|
||||||
if bytes.len() >= sep.len() {
|
|
||||||
let indexes: Vec<usize> = bytes.windows(sep.len())
|
|
||||||
.enumerate()
|
|
||||||
.filter(|o| o.1 == sep)
|
|
||||||
.map(|o| o.0)
|
|
||||||
.collect();
|
|
||||||
let mut parts: Vec<Vec<u8>> = Vec::new();
|
|
||||||
let mut now_part: Vec<u8> = Vec::new();
|
|
||||||
let mut i = 0usize;
|
|
||||||
loop {
|
|
||||||
if i >= bytes.len() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if indexes.contains(&i) {
|
|
||||||
parts.push(now_part.clone());
|
|
||||||
now_part.clear();
|
|
||||||
i += sep.len();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
now_part.push(bytes[i]);
|
|
||||||
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
parts.push(now_part);
|
|
||||||
parts
|
|
||||||
} else {
|
|
||||||
vec![Vec::from(bytes)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn read_line(data: &mut (impl AsyncReadExt + Unpin)) -> Result<String, HttpError> {
|
|
||||||
let mut line = Vec::new();
|
|
||||||
loop {
|
|
||||||
let mut buffer = vec![0;1];
|
|
||||||
data.read_exact(&mut buffer).await.or(Err(HttpError::ReadLineEof))?;
|
|
||||||
let char = buffer[0];
|
|
||||||
line.push(char);
|
|
||||||
if char == 0x0a {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
String::from_utf8(line).or(Err(HttpError::ReadLineUnknown))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn read_line_crlf(data: &mut (impl AsyncReadExt + Unpin)) -> Result<String, HttpError> {
|
|
||||||
match read_line(data).await {
|
|
||||||
Ok(i) => Ok(i[..i.len() - 2].to_string()),
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait Sendable: Send + Sync {
|
|
||||||
async fn send(
|
|
||||||
&self,
|
|
||||||
stream: &mut (dyn AsyncWrite + Unpin + Send + Sync),
|
|
||||||
) -> Result<(), HttpError>;
|
|
||||||
fn as_box(self) -> Box<dyn Sendable>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Stream = TimeoutStream<TcpStream>;
|
|
121
src/lib.rs
121
src/lib.rs
@ -1,3 +1,120 @@
|
|||||||
pub mod ezhttp;
|
pub mod error;
|
||||||
|
pub mod headers;
|
||||||
|
pub mod request;
|
||||||
|
pub mod response;
|
||||||
|
pub mod body;
|
||||||
|
pub mod server;
|
||||||
|
pub mod client;
|
||||||
|
|
||||||
pub use ezhttp::*;
|
pub mod prelude {
|
||||||
|
pub use super::error::*;
|
||||||
|
pub use super::headers::*;
|
||||||
|
pub use super::request::*;
|
||||||
|
pub use super::response::*;
|
||||||
|
pub use super::response::status_code::*;
|
||||||
|
pub use super::body::*;
|
||||||
|
pub use super::server::*;
|
||||||
|
pub use super::server::handler::*;
|
||||||
|
pub use super::server::starter::*;
|
||||||
|
pub use super::client::*;
|
||||||
|
pub use super::*;
|
||||||
|
}
|
||||||
|
|
||||||
|
use error::HttpError;
|
||||||
|
use rand::Rng;
|
||||||
|
use tokio::{io::{AsyncReadExt, AsyncWrite}, net::TcpStream};
|
||||||
|
use tokio_io_timeout::TimeoutStream;
|
||||||
|
use async_trait::async_trait;
|
||||||
|
|
||||||
|
const CHARS: &str = "qwertyuiopasdfghjklzxcvbnm0123456789QWERTYUIOPASDFGHJKLZXCVBNM'()+_,-./:=?";
|
||||||
|
|
||||||
|
pub fn gen_multipart_boundary() -> String {
|
||||||
|
let range = 20..40;
|
||||||
|
let length: usize = rand::thread_rng().gen_range(range);
|
||||||
|
[0..length].iter().map(|_|
|
||||||
|
String::from(CHARS.chars()
|
||||||
|
.collect::<Vec<char>>()
|
||||||
|
.get(rand::thread_rng()
|
||||||
|
.gen_range(0..CHARS.len())
|
||||||
|
).unwrap().clone()
|
||||||
|
)
|
||||||
|
).collect::<Vec<String>>().join("")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_bytes_once(bytes: &[u8], sep: &[u8]) -> (Vec<u8>, Vec<u8>) {
|
||||||
|
if let Some(index) = bytes.windows(sep.len())
|
||||||
|
.enumerate()
|
||||||
|
.filter(|o| o.1 == sep)
|
||||||
|
.map(|o| o.0)
|
||||||
|
.next() {
|
||||||
|
let t = bytes.split_at(index);
|
||||||
|
(t.0.to_vec(), t.1.split_at(sep.len()).1.to_vec())
|
||||||
|
} else {
|
||||||
|
(Vec::from(bytes), Vec::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_bytes(bytes: &[u8], sep: &[u8]) -> Vec<Vec<u8>> {
|
||||||
|
if bytes.len() >= sep.len() {
|
||||||
|
let indexes: Vec<usize> = bytes.windows(sep.len())
|
||||||
|
.enumerate()
|
||||||
|
.filter(|o| o.1 == sep)
|
||||||
|
.map(|o| o.0)
|
||||||
|
.collect();
|
||||||
|
let mut parts: Vec<Vec<u8>> = Vec::new();
|
||||||
|
let mut now_part: Vec<u8> = Vec::new();
|
||||||
|
let mut i = 0usize;
|
||||||
|
loop {
|
||||||
|
if i >= bytes.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if indexes.contains(&i) {
|
||||||
|
parts.push(now_part.clone());
|
||||||
|
now_part.clear();
|
||||||
|
i += sep.len();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
now_part.push(bytes[i]);
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
parts.push(now_part);
|
||||||
|
parts
|
||||||
|
} else {
|
||||||
|
vec![Vec::from(bytes)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_line(data: &mut (impl AsyncReadExt + Unpin)) -> Result<String, HttpError> {
|
||||||
|
let mut line = Vec::new();
|
||||||
|
loop {
|
||||||
|
let mut buffer = vec![0;1];
|
||||||
|
data.read_exact(&mut buffer).await.or(Err(HttpError::ReadLineEof))?;
|
||||||
|
let char = buffer[0];
|
||||||
|
line.push(char);
|
||||||
|
if char == 0x0a {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String::from_utf8(line).or(Err(HttpError::ReadLineUnknown))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_line_crlf(data: &mut (impl AsyncReadExt + Unpin)) -> Result<String, HttpError> {
|
||||||
|
match read_line(data).await {
|
||||||
|
Ok(i) => Ok(i[..i.len() - 2].to_string()),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait Sendable: Send + Sync {
|
||||||
|
async fn send(
|
||||||
|
&self,
|
||||||
|
stream: &mut (dyn AsyncWrite + Unpin + Send + Sync),
|
||||||
|
) -> Result<(), HttpError>;
|
||||||
|
fn as_box(self) -> Box<dyn Sendable>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Stream = TimeoutStream<TcpStream>;
|
@ -6,6 +6,7 @@ use std::{
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
use tokio::io::{AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||||
|
|
||||||
|
/// Request URL
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct URL {
|
pub struct URL {
|
||||||
pub path: String,
|
pub path: String,
|
||||||
@ -35,6 +36,8 @@ impl URL {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Turns URL object to url string without scheme, domain, port
|
||||||
|
/// Example: /123.html?k=v#anc
|
||||||
pub fn to_path_string(&self) -> String {
|
pub fn to_path_string(&self) -> String {
|
||||||
format!("{}{}{}", self.path, if self.query.is_empty() {
|
format!("{}{}{}", self.path, if self.query.is_empty() {
|
||||||
String::new()
|
String::new()
|
||||||
@ -49,6 +52,8 @@ impl URL {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Turns string without scheme, domain, port to URL object
|
||||||
|
/// Example of string: /123.html?k=v#anc
|
||||||
pub fn from_path_string(s: &str, scheme: String, domain: String, port: u16) -> Option<Self> {
|
pub fn from_path_string(s: &str, scheme: String, domain: String, port: u16) -> Option<Self> {
|
||||||
let (s, anchor) = s.split_once("#").unwrap_or((s, ""));
|
let (s, anchor) = s.split_once("#").unwrap_or((s, ""));
|
||||||
let (path, query) = s.split_once("?").unwrap_or((s, ""));
|
let (path, query) = s.split_once("?").unwrap_or((s, ""));
|
||||||
@ -69,6 +74,9 @@ impl URL {
|
|||||||
impl FromStr for URL {
|
impl FromStr for URL {
|
||||||
type Err = HttpError;
|
type Err = HttpError;
|
||||||
|
|
||||||
|
/// Turns url string to URL object
|
||||||
|
/// Example: https://domain.com:999/123.html?k=v#anc
|
||||||
|
/// Example 2: http://exampl.eu/sing
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
let (scheme, s) = s.split_once("://").ok_or(HttpError::UrlError)?;
|
let (scheme, s) = s.split_once("://").ok_or(HttpError::UrlError)?;
|
||||||
let (host, s) = s.split_once("/").unwrap_or((s, ""));
|
let (host, s) = s.split_once("/").unwrap_or((s, ""));
|
||||||
@ -96,6 +104,9 @@ impl FromStr for URL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ToString for URL {
|
impl ToString for URL {
|
||||||
|
/// Turns URL object to string
|
||||||
|
/// Example: https://domain.com:999/123.html?k=v#anc
|
||||||
|
/// Example 2: http://exampl.eu/sing
|
||||||
fn to_string(&self) -> String {
|
fn to_string(&self) -> String {
|
||||||
format!("{}://{}{}", self.scheme, {
|
format!("{}://{}{}", self.scheme, {
|
||||||
if (self.scheme == "http" && self.port != 80) || (self.scheme == "https" && self.port != 443) {
|
if (self.scheme == "http" && self.port != 80) || (self.scheme == "https" && self.port != 443) {
|
||||||
@ -173,6 +184,7 @@ impl HttpRequest {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get multipart parts (requires Content-Type header)
|
||||||
pub fn get_multipart(&self) -> Option<Vec<Part>> {
|
pub fn get_multipart(&self) -> Option<Vec<Part>> {
|
||||||
let boundary = self.headers.get("content-type")?
|
let boundary = self.headers.get("content-type")?
|
||||||
.split(";")
|
.split(";")
|
||||||
@ -182,6 +194,7 @@ impl HttpRequest {
|
|||||||
Some(self.body.as_multipart(boundary))
|
Some(self.body.as_multipart(boundary))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set multipart parts (modifies Content-Type header)
|
||||||
pub fn set_multipart(&mut self, parts: Vec<Part>) -> Option<()> {
|
pub fn set_multipart(&mut self, parts: Vec<Part>) -> Option<()> {
|
||||||
let boundary = gen_multipart_boundary();
|
let boundary = gen_multipart_boundary();
|
||||||
self.headers.put("Content-Type", format!("multipart/form-data; boundary={}", boundary.clone()));
|
self.headers.put("Content-Type", format!("multipart/form-data; boundary={}", boundary.clone()));
|
||||||
@ -189,6 +202,7 @@ impl HttpRequest {
|
|||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create new request builder
|
||||||
pub fn builder(method: String, url: URL) -> RequestBuilder {
|
pub fn builder(method: String, url: URL) -> RequestBuilder {
|
||||||
RequestBuilder::new(method, url)
|
RequestBuilder::new(method, url)
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user