Initial commit
This commit is contained in:
commit
56af313687
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "wetstring_http"
|
||||
version = "1.0.0"
|
||||
authors = ["Ben Bridle <bridle.benjamin@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
wetstring = { git = "https://tildegit.org/toast/wetstring", tag = "v1.0" }
|
||||
log = "0.4.14"
|
|
@ -0,0 +1,22 @@
|
|||
pub enum RequestError {
|
||||
StartLineMissingMethod,
|
||||
StartLineMissingTarget,
|
||||
TargetTooLong,
|
||||
TargetCouldNotParse,
|
||||
MethodNotSupported(String),
|
||||
QueryParametersCouldNotParse,
|
||||
}
|
||||
|
||||
impl RequestError {
|
||||
#[rustfmt::skip]
|
||||
pub fn description(&self) -> String {
|
||||
match self {
|
||||
Self::StartLineMissingMethod => "The request method is missing from the start line".to_string(),
|
||||
Self::StartLineMissingTarget => "The request target URI is missing from the start line".to_string(),
|
||||
Self::TargetTooLong => "The length of the request target URI in the start line exceeds the maximum length".to_string(),
|
||||
Self::TargetCouldNotParse => "The request target URI is invalid".to_string(),
|
||||
Self::MethodNotSupported(method) => format!("The '{}' method is not supported", method),
|
||||
Self::QueryParametersCouldNotParse => "QueryParametersCouldNotParse".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
mod error;
|
||||
mod method;
|
||||
mod request;
|
||||
mod request_parser;
|
||||
mod response;
|
||||
mod status;
|
||||
|
||||
pub use error::RequestError;
|
||||
pub use method::Method;
|
||||
pub use request::{HttpRequest, RequestTarget};
|
||||
pub use request_parser::HttpRequestParser;
|
||||
pub use response::HttpResponse;
|
||||
pub use status::Status;
|
||||
|
||||
pub use wetstring::RequestProcessor;
|
||||
pub type HttpServer<Proc> = wetstring::Server<HttpRequestParser, Proc>;
|
|
@ -0,0 +1,44 @@
|
|||
use crate::RequestError;
|
||||
|
||||
pub enum Method {
|
||||
GET,
|
||||
POST,
|
||||
PUT,
|
||||
PATCH,
|
||||
DELETE,
|
||||
OPTION,
|
||||
HEAD,
|
||||
}
|
||||
|
||||
impl Method {
|
||||
pub fn parse(method: &str) -> Result<Self, RequestError> {
|
||||
Ok(match method {
|
||||
"GET" => Self::GET,
|
||||
"POST" => Self::POST,
|
||||
"PUT" => Self::PUT,
|
||||
"PATCH" => Self::PATCH,
|
||||
"DELETE" => Self::DELETE,
|
||||
"OPTION" => Self::OPTION,
|
||||
"HEAD" => Self::HEAD,
|
||||
method => return Err(RequestError::MethodNotSupported(method.to_string())),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
match self {
|
||||
Self::GET => "GET",
|
||||
Self::POST => "POST",
|
||||
Self::PUT => "PUT",
|
||||
Self::PATCH => "PATCH",
|
||||
Self::DELETE => "DELETE",
|
||||
Self::OPTION => "OPTION",
|
||||
Self::HEAD => "HEAD",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Method {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||
write!(f, "{}", self.name())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
use crate::*;
|
||||
use log::info;
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use wetstring::Request;
|
||||
|
||||
pub struct HttpRequest {
|
||||
pub client_address: SocketAddr,
|
||||
pub method: Method,
|
||||
pub target: RequestTarget,
|
||||
pub headers: HashMap<String, String>,
|
||||
pub body: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl Request for HttpRequest {}
|
||||
|
||||
pub struct RequestTarget {
|
||||
/// `path` excludes host
|
||||
pub path: String,
|
||||
pub query_parameters: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl RequestTarget {
|
||||
pub fn parse(text: &str) -> Result<Self, RequestError> {
|
||||
info!("URI is '{}'", text);
|
||||
match text.split_once('?') {
|
||||
Some((path, query)) => Ok(Self {
|
||||
path: path.to_string(),
|
||||
query_parameters: Self::parse_query_parameters(query)?,
|
||||
}),
|
||||
None => Ok(Self {
|
||||
path: text.to_string(),
|
||||
query_parameters: HashMap::new(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_query_parameters(query: &str) -> Result<HashMap<String, String>, RequestError> {
|
||||
let mut query_parameters = HashMap::new();
|
||||
for pair_text in query.split('&') {
|
||||
match pair_text.split_once('=') {
|
||||
Some((key, value)) => query_parameters.insert(key.to_string(), value.to_string()),
|
||||
None => return Err(RequestError::QueryParametersCouldNotParse),
|
||||
};
|
||||
}
|
||||
return Ok(query_parameters);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
use crate::*;
|
||||
use log::info;
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use wetstring::{RequestParseResult, RequestParser};
|
||||
|
||||
pub struct HttpRequestParser {
|
||||
bytes: Vec<u8>,
|
||||
client_address: SocketAddr,
|
||||
headers: HashMap<String, String>,
|
||||
body: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl RequestParser for HttpRequestParser {
|
||||
type Req = HttpRequest;
|
||||
type Res = HttpResponse;
|
||||
|
||||
fn new(client_address: SocketAddr) -> Self {
|
||||
Self {
|
||||
bytes: Vec::new(),
|
||||
client_address,
|
||||
headers: HashMap::new(),
|
||||
body: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn push_bytes(&mut self, bytes: &[u8]) {
|
||||
self.bytes.extend(bytes);
|
||||
}
|
||||
fn try_parse(&mut self) -> RequestParseResult<HttpRequest, HttpResponse> {
|
||||
let client_error = HttpResponse::client_error();
|
||||
if self.bytes.len() > 8096 {
|
||||
return RequestParseResult::Invalid(client_error);
|
||||
}
|
||||
|
||||
let string = match String::from_utf8(self.bytes.clone()) {
|
||||
Ok(string) => string,
|
||||
Err(_) => return RequestParseResult::Incomplete,
|
||||
};
|
||||
let mut lines = string.lines();
|
||||
|
||||
// Parse request start line
|
||||
let (method, target) = match lines.next() {
|
||||
Some(line) => match parse_start_line(line) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
info!("{}", e.description());
|
||||
return RequestParseResult::Invalid(client_error);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
info!("Missing start line from request");
|
||||
return RequestParseResult::Invalid(client_error);
|
||||
}
|
||||
};
|
||||
|
||||
// Parse request headers
|
||||
// for line in lines {
|
||||
// if line == "" {
|
||||
// break;
|
||||
// };
|
||||
// match parse_header_line(line) {
|
||||
// Ok((key, value)) => self.headers.insert(key, value),
|
||||
// Err(_) => return RequestParseResult::Invalid(client_error),
|
||||
// };
|
||||
// }
|
||||
|
||||
let request = HttpRequest {
|
||||
client_address: self.client_address,
|
||||
method,
|
||||
target,
|
||||
headers: std::mem::take(&mut self.headers),
|
||||
body: std::mem::take(&mut self.body),
|
||||
};
|
||||
|
||||
RequestParseResult::Complete(request)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_start_line(line: &str) -> Result<(Method, RequestTarget), RequestError> {
|
||||
let mut split = line.split_whitespace();
|
||||
|
||||
let method_str = split.next().ok_or(RequestError::StartLineMissingMethod)?;
|
||||
let target_str = split.next().ok_or(RequestError::StartLineMissingTarget)?;
|
||||
|
||||
let method = Method::parse(method_str)?;
|
||||
let target = RequestTarget::parse(target_str)?;
|
||||
|
||||
Ok((method, target))
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
use crate::Status;
|
||||
use std::collections::HashMap;
|
||||
use wetstring::Response;
|
||||
|
||||
pub struct HttpResponse {
|
||||
pub status: Status,
|
||||
pub headers: HashMap<String, String>,
|
||||
pub body: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl HttpResponse {
|
||||
pub fn new(status: Status) -> Self {
|
||||
Self {
|
||||
status,
|
||||
headers: HashMap::new(),
|
||||
body: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_utf8_body(mut self, body: &str) -> Self {
|
||||
self.body = Some(body.as_bytes().to_vec());
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn client_error() -> Self {
|
||||
Self::new(Status::ClientError).with_utf8_body("Client error")
|
||||
}
|
||||
}
|
||||
|
||||
impl Response for HttpResponse {
|
||||
fn to_bytes(self) -> Vec<u8> {
|
||||
let mut bytes = format!("HTTP/1.1 {}\n\n", self.status.code())
|
||||
.as_bytes()
|
||||
.to_vec();
|
||||
if let Some(body) = self.body {
|
||||
bytes.extend(body);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
pub enum Status {
|
||||
Information,
|
||||
Success,
|
||||
Redirect,
|
||||
ClientError,
|
||||
ServerError,
|
||||
}
|
||||
|
||||
impl Status {
|
||||
pub fn code(&self) -> &str {
|
||||
match self {
|
||||
Self::Information => "100",
|
||||
Self::Success => "200",
|
||||
Self::Redirect => "300",
|
||||
Self::ClientError => "400",
|
||||
Self::ServerError => "500",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn description(&self) -> &str {
|
||||
match self {
|
||||
Self::Information => "Information",
|
||||
Self::Success => "Success",
|
||||
Self::Redirect => "Redirect",
|
||||
Self::ClientError => "ClientError",
|
||||
Self::ServerError => "ServerError",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for Status {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
|
||||
write!(f, "{} {}", self.code(), self.description())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue