started response
This commit is contained in:
parent
e209abe7a0
commit
9a2a500eb0
4 changed files with 278 additions and 25 deletions
|
|
@ -7,6 +7,8 @@ use std::{
|
|||
net::TcpListener,
|
||||
};
|
||||
|
||||
use crate::request::RequestHeader;
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let listener = TcpListener::bind("127.0.0.1:8080")?;
|
||||
for incoming_stream in listener.incoming() {
|
||||
|
|
@ -16,7 +18,10 @@ fn main() -> std::io::Result<()> {
|
|||
|
||||
let req = request::Request::from_bufreader(reader)?;
|
||||
|
||||
println!("{req:?}");
|
||||
|
||||
let mut writer = stream;
|
||||
|
||||
writer.write_all("Ok".as_bytes())?;
|
||||
writer.flush()?;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,20 +9,22 @@ const MAX_LINE_WIDTH: u64 = 4096; // 4 KiB
|
|||
const MAX_BODY_LENGTH: u64 = 8388608; // 5 MiB
|
||||
const MAX_HEADER_COUNT: u64 = 512;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Request {
|
||||
method: Method,
|
||||
http_version: String,
|
||||
path: ServerPath,
|
||||
headers: Vec<RequestHeader>,
|
||||
data: Vec<u8>,
|
||||
pub method: Method,
|
||||
pub http_version: Box<str>,
|
||||
pub path: ServerPath,
|
||||
pub headers: Box<[RequestHeader]>,
|
||||
pub body: Option<Box<[u8]>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum RequestHeader {
|
||||
Host(String),
|
||||
UserAgent(String),
|
||||
ContentType(ContentType),
|
||||
Accept(ContentType),
|
||||
Other(String, String),
|
||||
Accept(Vec<ContentType>),
|
||||
Other(Box<str>, Box<str>),
|
||||
}
|
||||
|
||||
impl FromStr for RequestHeader {
|
||||
|
|
@ -36,11 +38,15 @@ impl FromStr for RequestHeader {
|
|||
"Host" => Ok(RequestHeader::Host(value.to_string())),
|
||||
"UserAgent" => Ok(RequestHeader::UserAgent(value.to_string())),
|
||||
"ContentType" => Ok(RequestHeader::ContentType(ContentType::from_str(value)?)),
|
||||
"Accept" => Ok(RequestHeader::Accept(ContentType::from_str(value)?)),
|
||||
|
||||
"Accept" => Ok(RequestHeader::Accept(
|
||||
value
|
||||
.split(',')
|
||||
.map(ContentType::from_str)
|
||||
.collect::<Result<Vec<ContentType>, io::Error>>()?,
|
||||
)),
|
||||
_ => Ok(RequestHeader::Other(
|
||||
header_type.to_string(),
|
||||
value.to_string(),
|
||||
header_type.to_string().into_boxed_str(),
|
||||
value.to_string().into_boxed_str(),
|
||||
)),
|
||||
},
|
||||
_ => Err(io::Error::new(
|
||||
|
|
@ -58,12 +64,40 @@ impl Request {
|
|||
let first_line = Self::read_line(&mut limited_buffer)?;
|
||||
let parsed_first_line = Self::parse_first_line(first_line)?;
|
||||
|
||||
use std::collections::hash_set::HashSet;
|
||||
use std::mem::{Discriminant, discriminant};
|
||||
|
||||
let mut header_set: HashSet<Discriminant<RequestHeader>> = HashSet::new();
|
||||
let mut headers = vec![];
|
||||
|
||||
for _ in 0..MAX_HEADER_COUNT {
|
||||
let current_line = Self::read_line(&mut limited_buffer)?;
|
||||
|
||||
if current_line.is_empty() || current_line == "\r\n" {
|
||||
break;
|
||||
}
|
||||
|
||||
let header = RequestHeader::from_str(¤t_line)?;
|
||||
headers.push(header);
|
||||
|
||||
if let RequestHeader::Other(_, _) = headers.last().unwrap() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !header_set.insert(discriminant(headers.last().unwrap())) {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"Multiple headers of the same type",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
method: parsed_first_line.0,
|
||||
path: parsed_first_line.1,
|
||||
http_version: parsed_first_line.2,
|
||||
headers: vec![],
|
||||
data: vec![],
|
||||
http_version: parsed_first_line.2.into_boxed_str(),
|
||||
headers: headers.into_boxed_slice(),
|
||||
body: None,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -72,6 +106,13 @@ impl Request {
|
|||
buffer.set_limit(MAX_LINE_WIDTH);
|
||||
buffer.read_until(b'\n', &mut read_buffer)?;
|
||||
|
||||
if read_buffer.len() < 2 {
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid line"));
|
||||
}
|
||||
|
||||
read_buffer.remove(read_buffer.len() - 1);
|
||||
read_buffer.remove(read_buffer.len() - 1);
|
||||
|
||||
Ok(String::from_utf8_lossy(&read_buffer).to_string())
|
||||
}
|
||||
|
||||
|
|
@ -92,9 +133,10 @@ impl Request {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ServerPath {
|
||||
path: Vec<String>,
|
||||
query: Vec<(String, String)>,
|
||||
pub path: Box<str>,
|
||||
pub query: Option<Box<[(Box<str>, Box<str>)]>>,
|
||||
}
|
||||
|
||||
impl FromStr for ServerPath {
|
||||
|
|
@ -102,11 +144,19 @@ impl FromStr for ServerPath {
|
|||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.split("?").collect::<Vec<&str>>().as_slice() {
|
||||
[path] => Ok(Self {
|
||||
path: path
|
||||
.split('/')
|
||||
.filter(|s| !s.is_empty() && !s.starts_with('.'))
|
||||
.map(|s| s.to_string())
|
||||
.collect(),
|
||||
query: None,
|
||||
}),
|
||||
[path, query] => {
|
||||
let mut query_hashset: std::collections::hash_set::HashSet<String> =
|
||||
std::collections::hash_set::HashSet::new();
|
||||
|
||||
let query_parsed = query
|
||||
let query_parsed: Vec<(Box<str>, Box<str>)> = query
|
||||
.split('&')
|
||||
.filter_map(|s| match s.split('=').collect::<Vec<&str>>().as_slice() {
|
||||
[parameter, value] => {
|
||||
|
|
@ -114,7 +164,10 @@ impl FromStr for ServerPath {
|
|||
None
|
||||
} else {
|
||||
query_hashset.insert(parameter.to_string());
|
||||
Some((parameter.to_string(), value.to_string()))
|
||||
Some((
|
||||
parameter.to_string().into_boxed_str(),
|
||||
value.to_string().into_boxed_str(),
|
||||
))
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
|
|
@ -127,7 +180,7 @@ impl FromStr for ServerPath {
|
|||
.filter(|s| !s.is_empty() && !s.starts_with('.'))
|
||||
.map(|s| s.to_string())
|
||||
.collect(),
|
||||
query: query_parsed,
|
||||
query: Some(query_parsed.into_boxed_slice()),
|
||||
})
|
||||
}
|
||||
_ => Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid path")),
|
||||
|
|
@ -135,6 +188,7 @@ impl FromStr for ServerPath {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Method {
|
||||
Get,
|
||||
Post,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,88 @@
|
|||
use crate::shared_enums::ContentType;
|
||||
|
||||
pub struct Response {
|
||||
first_line: String,
|
||||
http_version: String,
|
||||
code: ResponseCode,
|
||||
headers: Vec<ResponseHeader>,
|
||||
data: Vec<u8>,
|
||||
}
|
||||
|
||||
pub enum ResponseCode {
|
||||
Continue,
|
||||
SwitchingProtocols,
|
||||
Processing,
|
||||
EarlyHints,
|
||||
OK,
|
||||
Created,
|
||||
Accepted,
|
||||
NonAuthoritativeInformation,
|
||||
NoContent,
|
||||
ResetContent,
|
||||
PartialContent,
|
||||
MultiStatus,
|
||||
AlreadyReported,
|
||||
IMUsed,
|
||||
MultipleChoices,
|
||||
MovedPermanently,
|
||||
Found,
|
||||
SeeOther,
|
||||
NotModified,
|
||||
TemporaryRedirect,
|
||||
PermanentRedirect,
|
||||
BadRequest,
|
||||
Unauthorized,
|
||||
PaymentRequired,
|
||||
Forbidden,
|
||||
NotFound,
|
||||
MethodNotAllowed,
|
||||
NotAcceptable,
|
||||
ProxyAuthenticationRequired,
|
||||
RequestTimeout,
|
||||
Conflict,
|
||||
Gone,
|
||||
LengthRequired,
|
||||
PreconditionFailed,
|
||||
ContentTooLarge,
|
||||
URITooLong,
|
||||
UnsupportedMediaType,
|
||||
RangeNotSatisfiable,
|
||||
ExpectationFailed,
|
||||
IAmTeapot,
|
||||
MisdirectedRequest,
|
||||
UnprocessableContent,
|
||||
Locked,
|
||||
FailedDependency,
|
||||
TooEarly,
|
||||
UpgradeRequired,
|
||||
PreconditionRequired,
|
||||
TooManyRequests,
|
||||
RequestHeaderFieldsTooLarge,
|
||||
UnavailableForLegalReasons,
|
||||
InternalServerError,
|
||||
NotImplemented,
|
||||
BadGateway,
|
||||
ServiceUnavailable,
|
||||
GatewayTimeout,
|
||||
HTTPVersionNotSupported,
|
||||
VariantAlsoNegotiates,
|
||||
InsufficientStorage,
|
||||
LoopDetected,
|
||||
NotExtended,
|
||||
NetworkAuthenticationRequired,
|
||||
}
|
||||
|
||||
impl ResponseCode {
|
||||
fn to_code(&self) -> u32 {
|
||||
match self {
|
||||
ResponseCode::Continue => 100,
|
||||
ResponseCode::SwitchingProtocols => 101,
|
||||
ResponseCode::Processing => 102,
|
||||
ResponseCode::EarlyHints => 103,
|
||||
ResponseCode::OK => 200,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ResponseHeader {
|
||||
ContentLength(u32),
|
||||
ContentType(ContentType),
|
||||
|
|
|
|||
|
|
@ -1,22 +1,136 @@
|
|||
use std::{io, str::FromStr};
|
||||
|
||||
pub enum ContentType {
|
||||
#[derive(Debug)]
|
||||
pub enum ContentTypeType {
|
||||
Text(TextType),
|
||||
Aplication(ApplicationType),
|
||||
Image(Image),
|
||||
Any,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Parameter {
|
||||
Preference(f32),
|
||||
Charset(Charset),
|
||||
Other(Box<str>, Box<str>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Charset {
|
||||
UTF8,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ContentType {
|
||||
pub content_type: ContentTypeType,
|
||||
pub parameter: Option<Parameter>,
|
||||
}
|
||||
|
||||
pub fn InvalidDataError(error: &str) -> io::Error {
|
||||
io::Error::new(io::ErrorKind::InvalidData, error)
|
||||
}
|
||||
|
||||
impl FromStr for ContentType {
|
||||
type Err = io::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"Invalid content-type",
|
||||
))
|
||||
match s.split(';').collect::<Vec<&str>>().as_slice() {
|
||||
[val, par] => Ok(Self {
|
||||
content_type: ContentTypeType::from_str(val)?,
|
||||
parameter: Some(Parameter::from_str(par)?),
|
||||
}),
|
||||
|
||||
[val] => Ok(Self {
|
||||
content_type: ContentTypeType::from_str(val)?,
|
||||
parameter: None,
|
||||
}),
|
||||
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"Invalid content-type",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Parameter {
|
||||
type Err = io::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.split('=').collect::<Vec<&str>>().as_slice() {
|
||||
["q", value] => {
|
||||
let pref_val = match value.parse::<f32>() {
|
||||
Ok(v) => Ok(v),
|
||||
Err(_) => Err(InvalidDataError("Invalid preference")),
|
||||
}?;
|
||||
|
||||
Ok(Parameter::Preference(pref_val))
|
||||
}
|
||||
|
||||
["charset", "utf-8"] => Ok(Parameter::Charset(Charset::UTF8)),
|
||||
|
||||
[t, v] => Ok(Parameter::Other(
|
||||
t.to_string().into_boxed_str(),
|
||||
v.to_string().into_boxed_str(),
|
||||
)),
|
||||
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"Invalid parameter",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ContentTypeType {
|
||||
type Err = io::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let parts: Vec<&str> = s.split("/").collect();
|
||||
|
||||
match parts.as_slice() {
|
||||
["*", "*"] => Ok(ContentTypeType::Any),
|
||||
["text", "*"] => Ok(ContentTypeType::Text(TextType::Any)),
|
||||
["text", "html"] => Ok(ContentTypeType::Text(TextType::Html)),
|
||||
["text", "css"] => Ok(ContentTypeType::Text(TextType::Css)),
|
||||
["text", "javascript"] => Ok(ContentTypeType::Text(TextType::Javascript)),
|
||||
|
||||
["application", "json"] => Ok(ContentTypeType::Aplication(ApplicationType::Json)),
|
||||
["application", "xhtml+xml"] => {
|
||||
Ok(ContentTypeType::Aplication(ApplicationType::XhtmlXml))
|
||||
}
|
||||
["application", "xml"] => Ok(ContentTypeType::Aplication(ApplicationType::Xml)),
|
||||
["application", "*"] => Ok(ContentTypeType::Aplication(ApplicationType::Any)),
|
||||
|
||||
["image", "png"] => Ok(ContentTypeType::Image(Image::Png)),
|
||||
["image", "jpeg"] | ["image", "jpg"] => Ok(ContentTypeType::Image(Image::Jpeg)),
|
||||
["image", "avif"] => Ok(ContentTypeType::Image(Image::Avif)),
|
||||
["image", "webp"] => Ok(ContentTypeType::Image(Image::Webp)),
|
||||
["image", "svg"] | ["image", "svg+xml"] => Ok(ContentTypeType::Image(Image::Svg)),
|
||||
["image", "*"] => Ok(ContentTypeType::Image(Image::Any)),
|
||||
|
||||
_ => {
|
||||
println!("{parts:?}");
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"Invalid content-type-type",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Image {
|
||||
Png,
|
||||
Avif,
|
||||
Jpeg,
|
||||
Webp,
|
||||
Svg,
|
||||
Any,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TextType {
|
||||
Html,
|
||||
Css,
|
||||
|
|
@ -24,7 +138,10 @@ pub enum TextType {
|
|||
Any,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ApplicationType {
|
||||
Json,
|
||||
Any,
|
||||
XhtmlXml,
|
||||
Xml,
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue