url root and ip now optional'
This commit is contained in:
parent
ce52997bed
commit
3726a28286
@ -1,9 +1,11 @@
|
|||||||
use std::{error::Error, str::FromStr, time::Duration};
|
use std::{error::Error, time::Duration};
|
||||||
|
|
||||||
use ezhttp::{client::{ClientBuilder, RequestBuilder}, request::URL};
|
use ezhttp::{client::{ClientBuilder, RequestBuilder}, request::IntoURL};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn Error>> {
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
dbg!("https://meex.lol/dku?key=value#hex_id".to_url().unwrap().to_string());
|
||||||
|
|
||||||
let client = ClientBuilder::new()
|
let client = ClientBuilder::new()
|
||||||
.ssl_verify(false)
|
.ssl_verify(false)
|
||||||
.connect_timeout(Duration::from_secs(5))
|
.connect_timeout(Duration::from_secs(5))
|
||||||
@ -12,7 +14,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
.header("User-Agent", "EzHttp/0.1.0")
|
.header("User-Agent", "EzHttp/0.1.0")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let request = RequestBuilder::get(URL::from_str("https://meex.lol/dku?key=value#hex_id")?).build();
|
let request = RequestBuilder::get("https://meex.lol/dku?key=value#hex_id");
|
||||||
|
|
||||||
println!("request: {:?}", &request);
|
println!("request: {:?}", &request);
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@ impl EzSite {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl HttpServer for EzSite {
|
impl HttpServer for EzSite {
|
||||||
async fn on_request(&self, req: &HttpRequest) -> Option<Box<dyn Sendable>> {
|
async fn on_request(&self, req: &HttpRequest) -> Option<Box<dyn Sendable>> {
|
||||||
println!("{} > {} {}", req.addr, req.method, req.url.to_path_string());
|
println!("{} > {} {}", req.addr?, req.method, req.url.to_string());
|
||||||
|
|
||||||
if let Some(resp) = self.get_main_page(req).await {
|
if let Some(resp) = self.get_main_page(req).await {
|
||||||
Some(resp.as_box())
|
Some(resp.as_box())
|
||||||
|
@ -6,7 +6,7 @@ struct EzSite(String);
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl HttpServer for EzSite {
|
impl HttpServer for EzSite {
|
||||||
async fn on_request(&self, req: &HttpRequest) -> Option<Box<dyn Sendable>> {
|
async fn on_request(&self, req: &HttpRequest) -> Option<Box<dyn Sendable>> {
|
||||||
println!("{} > {} {}", req.addr, req.method, req.url.to_path_string());
|
println!("{} > {} {}", req.addr?, req.method, req.url.to_string());
|
||||||
|
|
||||||
if req.url.path == "/" {
|
if req.url.path == "/" {
|
||||||
Some(HttpResponse::new(
|
Some(HttpResponse::new(
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crate::{error::HttpError, headers::Headers, prelude::HttpResponse, request::HttpRequest};
|
use crate::{error::HttpError, headers::Headers, prelude::HttpResponse, request::IntoRequest};
|
||||||
|
|
||||||
use super::{send_request, Proxy};
|
use super::{send_request, Proxy};
|
||||||
|
|
||||||
@ -107,9 +107,9 @@ impl HttpClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a request and receives a response
|
/// Sends a request and receives a response
|
||||||
pub async fn send(&self, request: HttpRequest) -> Result<HttpResponse, HttpError> {
|
pub async fn send(&self, request: impl IntoRequest) -> Result<HttpResponse, HttpError> {
|
||||||
send_request(
|
send_request(
|
||||||
request,
|
request.to_request()?,
|
||||||
self.ssl_verify,
|
self.ssl_verify,
|
||||||
self.proxy.clone(),
|
self.proxy.clone(),
|
||||||
self.headers.clone(),
|
self.headers.clone(),
|
||||||
|
@ -51,11 +51,14 @@ async fn send_request(
|
|||||||
for (key, value) in headers.entries() {
|
for (key, value) in headers.entries() {
|
||||||
request.headers.put_default(key, value);
|
request.headers.put_default(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let root = request.clone().url.root.ok_or(HttpError::UrlNeedsRootError)?;
|
||||||
|
|
||||||
request.headers.put_default("Connection", "close".to_string());
|
request.headers.put_default("Connection", "close".to_string());
|
||||||
request.headers.put_default("Host", request.url.domain.to_string());
|
request.headers.put_default("Host", root.domain.to_string());
|
||||||
request.headers.put_default("Content-Length", request.body.as_bytes().len().to_string());
|
request.headers.put_default("Content-Length", request.body.as_bytes().len().to_string());
|
||||||
|
|
||||||
let site_host = format!("{}:{}", request.url.domain, request.url.port);
|
let site_host = format!("{}:{}", root.domain, root.port);
|
||||||
let stream: Box<dyn RequestStream> = match connect_timeout {
|
let stream: Box<dyn RequestStream> = match connect_timeout {
|
||||||
Some(connect_timeout) => {
|
Some(connect_timeout) => {
|
||||||
tokio::time::timeout(
|
tokio::time::timeout(
|
||||||
@ -72,8 +75,8 @@ async fn send_request(
|
|||||||
stream.set_read_timeout(read_timeout);
|
stream.set_read_timeout(read_timeout);
|
||||||
let mut stream = Box::pin(stream);
|
let mut stream = Box::pin(stream);
|
||||||
|
|
||||||
if request.url.scheme == "https" {
|
if root.scheme == "https" {
|
||||||
let mut stream = ssl_wrapper(ssl_verify, request.url.domain.clone(), stream).await?;
|
let mut stream = ssl_wrapper(ssl_verify, root.domain.clone(), stream).await?;
|
||||||
request.send(&mut stream).await?;
|
request.send(&mut stream).await?;
|
||||||
Ok(HttpResponse::recv(&mut stream).await?)
|
Ok(HttpResponse::recv(&mut stream).await?)
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,58 +1,63 @@
|
|||||||
use std::{collections::HashMap, net::ToSocketAddrs};
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use super::{super::body::{Body, Part}, gen_multipart_boundary, super::headers::Headers, super::request::{HttpRequest, URL}};
|
use crate::{error::HttpError, request::{IntoRequest, IntoURL}};
|
||||||
|
|
||||||
|
use super::{super::body::{Body, Part}, gen_multipart_boundary, super::headers::Headers, super::request::HttpRequest};
|
||||||
|
|
||||||
/// Builder for [`HttpRequest`](HttpRequest)
|
/// Builder for [`HttpRequest`](HttpRequest)
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct RequestBuilder {
|
pub struct RequestBuilder {
|
||||||
method: String,
|
method: String,
|
||||||
url: URL,
|
url: String,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
body: Option<Body>
|
body: Option<Body>,
|
||||||
|
url_query: Option<HashMap<String, String>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequestBuilder {
|
impl RequestBuilder {
|
||||||
/// Create builder with a custom method
|
/// Create builder with a custom method
|
||||||
pub fn new(method: String, url: URL) -> Self {
|
pub fn new(method: String, url: impl IntoURL) -> Self {
|
||||||
RequestBuilder {
|
RequestBuilder {
|
||||||
method,
|
method,
|
||||||
url,
|
url: url.to_string(),
|
||||||
headers: Headers::new(),
|
headers: Headers::new(),
|
||||||
body: None
|
body: None,
|
||||||
|
url_query: None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create builder for a GET request
|
/// Create builder for a GET request
|
||||||
pub fn get(url: URL) -> Self { Self::new("GET".to_string(), url) }
|
pub fn get(url: impl IntoURL) -> Self { Self::new("GET".to_string(), url) }
|
||||||
|
|
||||||
/// Create builder for a HEAD request
|
/// Create builder for a HEAD request
|
||||||
pub fn head(url: URL) -> Self { Self::new("HEAD".to_string(), url) }
|
pub fn head(url: impl IntoURL) -> Self { Self::new("HEAD".to_string(), url) }
|
||||||
|
|
||||||
/// Create builder for a POST request
|
/// Create builder for a POST request
|
||||||
pub fn post(url: URL) -> Self { Self::new("POST".to_string(), url) }
|
pub fn post(url: impl IntoURL) -> Self { Self::new("POST".to_string(), url) }
|
||||||
|
|
||||||
/// Create builder for a PUT request
|
/// Create builder for a PUT request
|
||||||
pub fn put(url: URL) -> Self { Self::new("PUT".to_string(), url) }
|
pub fn put(url: impl IntoURL) -> Self { Self::new("PUT".to_string(), url) }
|
||||||
|
|
||||||
/// Create builder for a DELETE request
|
/// Create builder for a DELETE request
|
||||||
pub fn delete(url: URL) -> Self { Self::new("DELETE".to_string(), url) }
|
pub fn delete(url: impl IntoURL) -> Self { Self::new("DELETE".to_string(), url) }
|
||||||
|
|
||||||
/// Create builder for a CONNECT request
|
/// Create builder for a CONNECT request
|
||||||
pub fn connect(url: URL) -> Self { Self::new("CONNECT".to_string(), url) }
|
pub fn connect(url: impl IntoURL) -> Self { Self::new("CONNECT".to_string(), url) }
|
||||||
|
|
||||||
/// Create builder for a OPTIONS request
|
/// Create builder for a OPTIONS request
|
||||||
pub fn options(url: URL) -> Self { Self::new("OPTIONS".to_string(), url) }
|
pub fn options(url: impl IntoURL) -> Self { Self::new("OPTIONS".to_string(), url) }
|
||||||
|
|
||||||
/// Create builder for a TRACE request
|
/// Create builder for a TRACE request
|
||||||
pub fn trace(url: URL) -> Self { Self::new("TRACE".to_string(), url) }
|
pub fn trace(url: impl IntoURL) -> Self { Self::new("TRACE".to_string(), url) }
|
||||||
|
|
||||||
/// Create builder for a PATCH request
|
/// Create builder for a PATCH request
|
||||||
pub fn patch(url: URL) -> Self { Self::new("PATCH".to_string(), url) }
|
pub fn patch(url: impl IntoURL) -> Self { Self::new("PATCH".to_string(), url) }
|
||||||
|
|
||||||
/// Set request url
|
/// Set request url
|
||||||
pub fn url(mut self, url: URL) -> Self {
|
pub fn url(mut self, url: impl IntoURL) -> Self {
|
||||||
self.url = url;
|
self.url = url.to_string();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,7 +113,7 @@ impl RequestBuilder {
|
|||||||
|
|
||||||
/// Set query in url
|
/// 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 = Some(HashMap::from_iter(query.iter().map(|o| (o.0.to_string(), o.1.to_string()))));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,13 +124,24 @@ impl RequestBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Build request
|
/// Build request
|
||||||
pub fn build(self) -> HttpRequest {
|
pub fn build(self) -> Result<HttpRequest, HttpError> {
|
||||||
HttpRequest {
|
let mut url = self.url.to_url()?;
|
||||||
url: self.url,
|
if let Some(query) = self.url_query {
|
||||||
|
url.query = query;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HttpRequest {
|
||||||
|
url,
|
||||||
method: self.method,
|
method: self.method,
|
||||||
addr: "localhost:80".to_socket_addrs().unwrap().next().unwrap(),
|
addr: None,
|
||||||
headers: self.headers,
|
headers: self.headers,
|
||||||
body: self.body.unwrap_or(Body::default())
|
body: self.body.unwrap_or(Body::default())
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoRequest for RequestBuilder {
|
||||||
|
fn to_request(self) -> Result<HttpRequest, HttpError> {
|
||||||
|
self.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,7 +18,8 @@ pub enum HttpError {
|
|||||||
ConnectError,
|
ConnectError,
|
||||||
ShutdownError,
|
ShutdownError,
|
||||||
SslError,
|
SslError,
|
||||||
UnknownScheme
|
UnknownScheme,
|
||||||
|
UrlNeedsRootError
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for HttpError {
|
impl std::fmt::Display for HttpError {
|
||||||
|
221
src/request.rs
221
src/request.rs
@ -6,39 +6,70 @@ 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
|
|
||||||
|
/// Request URL root (scheme://domain:port)
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct RootURL {
|
||||||
|
pub scheme: String,
|
||||||
|
pub domain: String,
|
||||||
|
pub port: u16
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for RootURL {
|
||||||
|
type Err = HttpError;
|
||||||
|
|
||||||
|
fn from_str(text: &str) -> Result<Self, Self::Err> {
|
||||||
|
let (scheme, host) = text.split_once("://").ok_or(HttpError::UrlError)?;
|
||||||
|
let (domain, port) = host.split_once(":").unwrap_or(match scheme {
|
||||||
|
"https" => (host, "443"),
|
||||||
|
"http" => (host, "80"),
|
||||||
|
_ => { return Err(HttpError::UrlError) }
|
||||||
|
});
|
||||||
|
let port = port.parse::<u16>().or(Err(HttpError::UrlError))?;
|
||||||
|
let scheme= scheme.to_string();
|
||||||
|
let domain= domain.to_string();
|
||||||
|
Ok(RootURL { scheme, domain, port })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for RootURL {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
format!("{}://{}", self.scheme, {
|
||||||
|
if (self.scheme == "http" && self.port == 80) ||
|
||||||
|
(self.scheme == "https" && self.port == 443) {
|
||||||
|
format!("{}", self.domain)
|
||||||
|
} else {
|
||||||
|
format!("{}:{}", self.domain, self.port)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Request URL ({root}/path?query_key=query_value#anchor)
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct URL {
|
pub struct URL {
|
||||||
|
pub root: Option<RootURL>,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
pub domain: String,
|
|
||||||
pub anchor: Option<String>,
|
pub anchor: Option<String>,
|
||||||
pub query: HashMap<String, String>,
|
pub query: HashMap<String, String>
|
||||||
pub scheme: String,
|
|
||||||
pub port: u16
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl URL {
|
impl URL {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
domain: String,
|
root: Option<RootURL>,
|
||||||
port: u16,
|
|
||||||
path: String,
|
path: String,
|
||||||
anchor: Option<String>,
|
anchor: Option<String>,
|
||||||
query: HashMap<String, String>,
|
query: HashMap<String, String>,
|
||||||
scheme: String
|
|
||||||
) -> URL {
|
) -> URL {
|
||||||
URL {
|
URL {
|
||||||
path,
|
path,
|
||||||
domain,
|
|
||||||
anchor,
|
anchor,
|
||||||
query,
|
query,
|
||||||
scheme,
|
root
|
||||||
port
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Turns URL object to url string without scheme, domain, port
|
fn to_path_str(&self) -> String {
|
||||||
/// Example: /123.html?k=v#anc
|
|
||||||
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()
|
||||||
} else {
|
} else {
|
||||||
@ -52,79 +83,109 @@ impl URL {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Turns string without scheme, domain, port to URL object
|
fn from_path_str(text: &str) -> Option<Self> {
|
||||||
/// Example of string: /123.html?k=v#anc
|
let (text, anchor) = text.split_once("#").unwrap_or((text, ""));
|
||||||
pub fn from_path_string(s: &str, scheme: String, domain: String, port: u16) -> Option<Self> {
|
let (path, query) = text.split_once("?").unwrap_or((text, ""));
|
||||||
let (s, anchor) = s.split_once("#").unwrap_or((s, ""));
|
let path = path.to_string();
|
||||||
let (path, query) = s.split_once("?").unwrap_or((s, ""));
|
|
||||||
|
|
||||||
let anchor = if anchor.is_empty() { None } else { Some(anchor.to_string()) };
|
let anchor = if anchor.is_empty() {
|
||||||
let query = if query.is_empty() { HashMap::new() } else { {
|
None
|
||||||
|
} else {
|
||||||
|
Some(anchor.to_string())
|
||||||
|
};
|
||||||
|
|
||||||
|
let query = if query.is_empty() {
|
||||||
|
HashMap::new()
|
||||||
|
} else {
|
||||||
HashMap::from_iter(query.split("&").filter_map(|entry| {
|
HashMap::from_iter(query.split("&").filter_map(|entry| {
|
||||||
let (key, value) = entry.split_once("=").unwrap_or((entry, ""));
|
let (key, value) = entry.split_once("=").unwrap_or((entry, ""));
|
||||||
Some((urlencoding::decode(key).ok()?.to_string(), urlencoding::decode(value).ok()?.to_string()))
|
Some((urlencoding::decode(key).ok()?.to_string(), urlencoding::decode(value).ok()?.to_string()))
|
||||||
}))
|
}))
|
||||||
} };
|
};
|
||||||
let path = path.to_string();
|
|
||||||
let scheme = scheme.to_string();
|
Some(URL { root: None, path, anchor, query })
|
||||||
Some(URL { path, domain, anchor, query, scheme, port })
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for URL {
|
impl FromStr for URL {
|
||||||
type Err = HttpError;
|
type Err = HttpError;
|
||||||
|
|
||||||
/// Turns url string to URL object
|
fn from_str(text: &str) -> Result<Self, Self::Err> {
|
||||||
/// Example: https://domain.com:999/123.html?k=v#anc
|
if text.starts_with("/") {
|
||||||
/// Example 2: http://exampl.eu/sing
|
return Self::from_path_str(text).ok_or(HttpError::UrlError)
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
}
|
||||||
let (scheme, s) = s.split_once("://").ok_or(HttpError::UrlError)?;
|
|
||||||
let (host, s) = s.split_once("/").unwrap_or((s, ""));
|
|
||||||
let (domain, port) = host.split_once(":").unwrap_or((host,
|
|
||||||
if scheme == "http" { "80" }
|
|
||||||
else if scheme == "https" { "443" }
|
|
||||||
else { return Err(HttpError::UrlError) }
|
|
||||||
));
|
|
||||||
let port = port.parse::<u16>().map_err(|_| HttpError::UrlError)?;
|
|
||||||
let (s, anchor) = s.split_once("#").unwrap_or((s, ""));
|
|
||||||
let (path, query) = s.split_once("?").unwrap_or((s, ""));
|
|
||||||
|
|
||||||
let anchor = if anchor.is_empty() { None } else { Some(anchor.to_string()) };
|
let (scheme_n_host, path) = match text.split_once("://") {
|
||||||
let query = if query.is_empty() { HashMap::new() } else { {
|
Some((scheme, host_n_path)) => {
|
||||||
HashMap::from_iter(query.split("&").filter_map(|entry| {
|
match host_n_path.split_once("/") {
|
||||||
let (key, value) = entry.split_once("=").unwrap_or((entry, ""));
|
Some((host, path)) => {
|
||||||
Some((urlencoding::decode(key).ok()?.to_string(), urlencoding::decode(value).ok()?.to_string()))
|
(format!("{}://{}", scheme, host), format!("/{}", path))
|
||||||
}))
|
}, None => {
|
||||||
} };
|
(format!("{}://{}", scheme, host_n_path), "/".to_string())
|
||||||
let domain = domain.to_string();
|
}
|
||||||
let path = format!("/{path}");
|
}
|
||||||
let scheme = scheme.to_string();
|
}, None => {
|
||||||
Ok(URL { path, domain, anchor, query, scheme, port })
|
return Err(HttpError::UrlError)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut url = Self::from_path_str(&path).ok_or(HttpError::UrlError)?;
|
||||||
|
url.root = Some(RootURL::from_str(&scheme_n_host)?);
|
||||||
|
|
||||||
|
Ok(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.root.clone().map(|o| o.to_string()).unwrap_or_default(), self.to_path_str())
|
||||||
if (self.scheme == "http" && self.port != 80) || (self.scheme == "https" && self.port != 443) {
|
|
||||||
format!("{}:{}", self.domain, self.port)
|
|
||||||
} else {
|
|
||||||
self.domain.clone()
|
|
||||||
}
|
|
||||||
}, self.to_path_string())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait IntoURL: ToString {
|
||||||
|
fn to_url(self) -> Result<URL, HttpError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoURL for &String {
|
||||||
|
fn to_url(self) -> Result<URL, HttpError> {
|
||||||
|
URL::from_str(&self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoURL for String {
|
||||||
|
fn to_url(self) -> Result<URL, HttpError> {
|
||||||
|
URL::from_str(&self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoURL for &str {
|
||||||
|
fn to_url(self) -> Result<URL, HttpError> {
|
||||||
|
URL::from_str(&self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoURL for URL {
|
||||||
|
fn to_url(self) -> Result<URL, HttpError> {
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait IntoRequest {
|
||||||
|
fn to_request(self) -> Result<HttpRequest, HttpError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoRequest for HttpRequest {
|
||||||
|
fn to_request(self) -> Result<HttpRequest, HttpError> {
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Http request
|
/// Http request
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct HttpRequest {
|
pub struct HttpRequest {
|
||||||
pub url: URL,
|
pub url: URL,
|
||||||
pub method: String,
|
pub method: String,
|
||||||
pub addr: SocketAddr,
|
pub addr: Option<SocketAddr>,
|
||||||
pub headers: Headers,
|
pub headers: Headers,
|
||||||
pub body: Body
|
pub body: Body
|
||||||
}
|
}
|
||||||
@ -138,19 +199,19 @@ impl Display for HttpRequest {
|
|||||||
impl HttpRequest {
|
impl HttpRequest {
|
||||||
/// Create new http request
|
/// Create new http request
|
||||||
pub fn new(
|
pub fn new(
|
||||||
url: URL,
|
url: impl IntoURL,
|
||||||
method: String,
|
method: String,
|
||||||
addr: SocketAddr,
|
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
body: Body
|
body: Body,
|
||||||
) -> Self {
|
addr: Option<SocketAddr>
|
||||||
HttpRequest {
|
) -> Result<Self, HttpError> {
|
||||||
url,
|
Ok(HttpRequest {
|
||||||
|
url: url.to_url()?,
|
||||||
method,
|
method,
|
||||||
addr,
|
|
||||||
headers,
|
headers,
|
||||||
body
|
body,
|
||||||
}
|
addr
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read http request from stream
|
/// Read http request from stream
|
||||||
@ -170,18 +231,13 @@ impl HttpRequest {
|
|||||||
let headers = Headers::recv(stream).await?;
|
let headers = Headers::recv(stream).await?;
|
||||||
let body = Body::recv(stream, &headers).await?;
|
let body = Body::recv(stream, &headers).await?;
|
||||||
|
|
||||||
Ok(HttpRequest::new(
|
HttpRequest::new(
|
||||||
URL::from_path_string(
|
page,
|
||||||
&page,
|
|
||||||
"http".to_string(),
|
|
||||||
"localhost".to_string(),
|
|
||||||
80
|
|
||||||
).ok_or(HttpError::UrlError)?,
|
|
||||||
method,
|
method,
|
||||||
addr.clone(),
|
|
||||||
headers,
|
headers,
|
||||||
body
|
body,
|
||||||
))
|
Some(addr.clone())
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get multipart parts (requires Content-Type header)
|
/// Get multipart parts (requires Content-Type header)
|
||||||
@ -214,10 +270,13 @@ impl Sendable for HttpRequest {
|
|||||||
&self,
|
&self,
|
||||||
stream: &mut (dyn AsyncWrite + Unpin + Send + Sync),
|
stream: &mut (dyn AsyncWrite + Unpin + Send + Sync),
|
||||||
) -> Result<(), HttpError> {
|
) -> Result<(), HttpError> {
|
||||||
|
let mut url = self.url.clone();
|
||||||
|
url.root = None;
|
||||||
|
|
||||||
let mut head: String = String::new();
|
let mut head: String = String::new();
|
||||||
head.push_str(&self.method);
|
head.push_str(&self.method);
|
||||||
head.push_str(" ");
|
head.push_str(" ");
|
||||||
head.push_str(&self.url.to_path_string());
|
head.push_str(&url.to_string());
|
||||||
head.push_str(" HTTP/1.1");
|
head.push_str(" HTTP/1.1");
|
||||||
head.push_str("\r\n");
|
head.push_str("\r\n");
|
||||||
stream.write_all(head.as_bytes()).await.map_err(|_| HttpError::WriteHeadError)?;
|
stream.write_all(head.as_bytes()).await.map_err(|_| HttpError::WriteHeadError)?;
|
||||||
|
Loading…
Reference in New Issue
Block a user