tokio
This commit is contained in:
parent
772cb421cb
commit
e2e5138faf
8 changed files with 474 additions and 75 deletions
125
src/main.rs
125
src/main.rs
|
|
@ -1,54 +1,97 @@
|
|||
mod request;
|
||||
mod response;
|
||||
mod shared_enums;
|
||||
mod websoket_connection;
|
||||
|
||||
use std::{
|
||||
io::{BufReader, Write},
|
||||
net::TcpListener,
|
||||
time::Duration,
|
||||
};
|
||||
use std::time::Duration;
|
||||
use std::{path::Path, str::FromStr};
|
||||
|
||||
use tokio::io::AsyncWriteExt;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::time;
|
||||
|
||||
use crate::websoket_connection::WebsocketConnection;
|
||||
use crate::{
|
||||
request::Connection,
|
||||
request::{Connection, ServerPath},
|
||||
response::{Response, ResponseCode, ResponseHeader},
|
||||
shared_enums::{Content, ContentType},
|
||||
};
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
let listener = TcpListener::bind("127.0.0.1:8080")?;
|
||||
for incoming_stream in listener.incoming() {
|
||||
let mut stream = incoming_stream?;
|
||||
stream.set_read_timeout(Some(Duration::from_millis(500)))?;
|
||||
#[tokio::main]
|
||||
async fn main() -> tokio::io::Result<()> {
|
||||
let listener = TcpListener::bind("127.0.0.1:8080").await?;
|
||||
loop {
|
||||
let (stream, _) = listener.accept().await?;
|
||||
|
||||
loop {
|
||||
let reader = BufReader::new(&stream);
|
||||
let req = match request::Request::from_bufreader(reader) {
|
||||
Ok(r) => r,
|
||||
Err(_) => break,
|
||||
};
|
||||
|
||||
println!("{req:?}");
|
||||
|
||||
let response = match req.path.to_matchable().as_slice(){
|
||||
["css.css"] => Response::new().with_code(ResponseCode::Ok).with_data(b"body{background-color: #121212;} h1{color: #ffffff;} p{color: #025da9;}".to_vec()).with_header(ResponseHeader::ContentType(Content::new(ContentType::Text(shared_enums::TextType::Css)))),
|
||||
|
||||
_ => Response::new()
|
||||
.with_code(ResponseCode::Ok)
|
||||
.with_data(b"<!doctype html><html lang=\"en\"><head><link rel=\"stylesheet\" href=\"css.css\"><meta charset=\"UTF-8\"/><title>Hello World!</title></head><body><h1>HTTP server testing</h1><p>Lorem ipsum</p></body></html>".to_vec())
|
||||
.with_header(ResponseHeader::ContentType(Content::html_utf8())).with_header(ResponseHeader::Connection(Connection::KeepAlive)),
|
||||
};
|
||||
|
||||
response.respond(&mut stream)?;
|
||||
|
||||
stream.flush()?;
|
||||
|
||||
if req.headers.contains(&request::RequestHeader::Connection(
|
||||
request::Connection::Close,
|
||||
)) {
|
||||
println!("Connection closed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
tokio::spawn(handle_connection(stream));
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_connection(stream: TcpStream) -> tokio::io::Result<()> {
|
||||
if let Some(ws) = handle_http_connection(stream).await? {
|
||||
handle_websocket(ws).await?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_http_connection(
|
||||
mut stream: TcpStream,
|
||||
) -> tokio::io::Result<Option<websoket_connection::WebsocketConnection>> {
|
||||
loop {
|
||||
let req = match time::timeout(
|
||||
Duration::from_millis(500),
|
||||
request::Request::from_bufreader(&mut stream),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(Ok(r)) => r,
|
||||
Ok(Err(_)) => {
|
||||
println!("Wrong request");
|
||||
break;
|
||||
}
|
||||
Err(_) => {
|
||||
println!("Timed out");
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
println!("{req:?}");
|
||||
|
||||
let response = match req.path.to_matchable().as_slice() {
|
||||
["public", file] => {
|
||||
match Response::from_file(Path::new(format!("./public/{file}").as_str())) {
|
||||
Ok(resp) => resp,
|
||||
Err(_) => Response::new().with_code(ResponseCode::NotFound),
|
||||
}
|
||||
}
|
||||
["websocket"] => {
|
||||
return Ok(Some(WebsocketConnection::initialize_connection(req)?));
|
||||
}
|
||||
[] => Response::new()
|
||||
.with_code(ResponseCode::PermanentRedirect)
|
||||
.with_header(ResponseHeader::Connection(Connection::KeepAlive))
|
||||
.with_header(ResponseHeader::Location(
|
||||
ServerPath::from_str("/public/index.html").unwrap(),
|
||||
)),
|
||||
|
||||
_ => Response::new().with_code(ResponseCode::NotFound),
|
||||
};
|
||||
|
||||
response.respond(&mut stream).await?;
|
||||
|
||||
stream.flush().await?;
|
||||
|
||||
if req.headers.contains(&request::RequestHeader::Connection(
|
||||
request::Connection::Close,
|
||||
)) {
|
||||
println!("Connection closed");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
async fn handle_websocket(mut web_socket: WebsocketConnection) -> tokio::io::Result<()> {
|
||||
todo!()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
use std::{
|
||||
io::{self, BufRead, BufReader, Read, Take},
|
||||
net::TcpStream,
|
||||
str::FromStr,
|
||||
};
|
||||
use std::str::FromStr;
|
||||
|
||||
use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader, Take};
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
use crate::shared_enums::Content;
|
||||
|
||||
|
|
@ -56,8 +55,8 @@ pub struct Upgrade {
|
|||
}
|
||||
|
||||
impl FromStr for Upgrade {
|
||||
type Err = io::Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
type Err = tokio::io::Error;
|
||||
fn from_str(_s: &str) -> Result<Self, Self::Err> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
@ -69,7 +68,7 @@ pub enum Protocol {
|
|||
}
|
||||
|
||||
impl FromStr for RequestHeader {
|
||||
type Err = io::Error;
|
||||
type Err = tokio::io::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let header_split: Vec<&str> = s.split(": ").collect();
|
||||
|
|
@ -83,7 +82,7 @@ impl FromStr for RequestHeader {
|
|||
value
|
||||
.split(',')
|
||||
.map(Content::from_str)
|
||||
.collect::<Result<Vec<Content>, io::Error>>()?,
|
||||
.collect::<Result<Vec<Content>, tokio::io::Error>>()?,
|
||||
)),
|
||||
"Connection" => Ok(RequestHeader::Connection(match *value {
|
||||
"close" => Connection::Close,
|
||||
|
|
@ -95,15 +94,15 @@ impl FromStr for RequestHeader {
|
|||
value
|
||||
.split(',')
|
||||
.map(Upgrade::from_str)
|
||||
.collect::<Result<Vec<Upgrade>, io::Error>>()?,
|
||||
.collect::<Result<Vec<Upgrade>, tokio::io::Error>>()?,
|
||||
)),
|
||||
_ => Ok(RequestHeader::Other {
|
||||
name: (*header_type).into(),
|
||||
value: (*value).into(),
|
||||
}),
|
||||
},
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
_ => Err(tokio::io::Error::new(
|
||||
tokio::io::ErrorKind::InvalidData,
|
||||
"Invalid header-type",
|
||||
)),
|
||||
}
|
||||
|
|
@ -111,10 +110,12 @@ impl FromStr for RequestHeader {
|
|||
}
|
||||
|
||||
impl Request {
|
||||
pub fn from_bufreader(buffer: BufReader<&TcpStream>) -> io::Result<Self> {
|
||||
pub async fn from_bufreader(buffer: &mut TcpStream) -> tokio::io::Result<Self> {
|
||||
let buffer = BufReader::new(buffer);
|
||||
|
||||
let mut limited_buffer = buffer.take(MAX_LINE_WIDTH);
|
||||
|
||||
let first_line = Self::read_line(&mut limited_buffer)?;
|
||||
let first_line = Self::read_line(&mut limited_buffer).await?;
|
||||
let parsed_first_line = Self::parse_first_line(first_line)?;
|
||||
|
||||
use std::collections::hash_set::HashSet;
|
||||
|
|
@ -124,7 +125,7 @@ impl Request {
|
|||
let mut headers = vec![];
|
||||
|
||||
for _ in 0..MAX_HEADER_COUNT {
|
||||
let current_line = Self::read_line(&mut limited_buffer)?;
|
||||
let current_line = Self::read_line(&mut limited_buffer).await?;
|
||||
|
||||
if current_line.is_empty() || current_line == "\r\n" {
|
||||
break;
|
||||
|
|
@ -138,8 +139,8 @@ impl Request {
|
|||
}
|
||||
|
||||
if !header_set.insert(discriminant(headers.last().unwrap())) {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
return Err(tokio::io::Error::new(
|
||||
tokio::io::ErrorKind::InvalidData,
|
||||
"Multiple headers of the same type",
|
||||
));
|
||||
}
|
||||
|
|
@ -154,13 +155,16 @@ impl Request {
|
|||
})
|
||||
}
|
||||
|
||||
fn read_line(buffer: &mut Take<BufReader<&TcpStream>>) -> io::Result<String> {
|
||||
async fn read_line(buffer: &mut Take<BufReader<&mut TcpStream>>) -> tokio::io::Result<String> {
|
||||
let mut read_buffer = vec![];
|
||||
buffer.set_limit(MAX_LINE_WIDTH);
|
||||
buffer.read_until(b'\n', &mut read_buffer)?;
|
||||
buffer.read_until(b'\n', &mut read_buffer).await?;
|
||||
|
||||
if read_buffer.len() < 2 {
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid line"));
|
||||
return Err(tokio::io::Error::new(
|
||||
tokio::io::ErrorKind::InvalidData,
|
||||
"Invalid line",
|
||||
));
|
||||
}
|
||||
|
||||
read_buffer.remove(read_buffer.len() - 1);
|
||||
|
|
@ -169,7 +173,7 @@ impl Request {
|
|||
Ok(String::from_utf8_lossy(&read_buffer).to_string())
|
||||
}
|
||||
|
||||
fn parse_first_line(line: String) -> io::Result<(Method, ServerPath, String)> {
|
||||
fn parse_first_line(line: String) -> tokio::io::Result<(Method, ServerPath, String)> {
|
||||
let splitted_line: Vec<&str> = line.split_whitespace().collect();
|
||||
|
||||
match splitted_line.as_slice() {
|
||||
|
|
@ -178,8 +182,8 @@ impl Request {
|
|||
ServerPath::from_str(path)?,
|
||||
"HTTP/1.1".to_string(),
|
||||
)),
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
_ => Err(tokio::io::Error::new(
|
||||
tokio::io::ErrorKind::InvalidData,
|
||||
"Incorrect HTTP first line",
|
||||
)),
|
||||
}
|
||||
|
|
@ -199,7 +203,7 @@ impl ServerPath {
|
|||
}
|
||||
|
||||
impl FromStr for ServerPath {
|
||||
type Err = io::Error;
|
||||
type Err = tokio::io::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let get_path = |path: &&str| {
|
||||
|
|
@ -239,7 +243,10 @@ impl FromStr for ServerPath {
|
|||
query: Some(query_parsed.into_boxed_slice()),
|
||||
})
|
||||
}
|
||||
_ => Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid path")),
|
||||
_ => Err(tokio::io::Error::new(
|
||||
tokio::io::ErrorKind::InvalidData,
|
||||
"Invalid path",
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -258,7 +265,7 @@ pub enum Method {
|
|||
}
|
||||
|
||||
impl FromStr for Method {
|
||||
type Err = io::Error;
|
||||
type Err = tokio::io::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
|
|
@ -271,8 +278,8 @@ impl FromStr for Method {
|
|||
"PATCH" => Ok(Self::Patch),
|
||||
"PUT" => Ok(Self::Put),
|
||||
"TRACE" => Ok(Self::Trace),
|
||||
_ => Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
_ => Err(tokio::io::Error::new(
|
||||
tokio::io::ErrorKind::InvalidData,
|
||||
"Invalid HTTP method",
|
||||
)),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
use std::{
|
||||
io::{self, Write},
|
||||
net::TcpStream,
|
||||
use std::{ffi::OsStr, path::Path};
|
||||
|
||||
use crate::{
|
||||
request::{Connection, ServerPath},
|
||||
shared_enums::{Content, ContentType},
|
||||
};
|
||||
|
||||
use crate::{request::Connection, shared_enums::Content};
|
||||
use tokio::io::{self, AsyncWriteExt};
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
pub struct Response {
|
||||
http_version: Box<str>,
|
||||
|
|
@ -13,7 +16,7 @@ pub struct Response {
|
|||
}
|
||||
|
||||
impl Response {
|
||||
pub fn respond(self, stream: &mut TcpStream) -> Result<(), io::Error> {
|
||||
pub async fn respond(self, stream: &mut TcpStream) -> Result<(), io::Error> {
|
||||
let binding = self.to_str();
|
||||
let mut output = binding.as_bytes().to_vec();
|
||||
output.extend_from_slice(b"\r\n");
|
||||
|
|
@ -24,7 +27,7 @@ impl Response {
|
|||
output.extend_from_slice(&self.data);
|
||||
}
|
||||
|
||||
stream.write_all(output.as_slice())?;
|
||||
stream.write_all(output.as_slice()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -80,6 +83,32 @@ impl Response {
|
|||
data: self.data,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_file(path: &Path) -> io::Result<Self> {
|
||||
let bytes = std::fs::read(path)?;
|
||||
|
||||
let content_type = match path.extension() {
|
||||
Some(a) if a == OsStr::new("html") => {
|
||||
ContentType::Text(crate::shared_enums::TextType::Html)
|
||||
}
|
||||
Some(a) if a == OsStr::new("css") => {
|
||||
ContentType::Text(crate::shared_enums::TextType::Css)
|
||||
}
|
||||
Some(_) | None => {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"Looking for a wrong file",
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
http_version: "HTTP/1.1".into(),
|
||||
code: ResponseCode::Ok,
|
||||
headers: vec![ResponseHeader::ContentType(Content::new(content_type))],
|
||||
data: bytes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub enum ResponseCode {
|
||||
|
|
@ -219,6 +248,7 @@ pub enum ResponseHeader {
|
|||
ContentType(Content),
|
||||
CacheControl(CacheControl),
|
||||
Connection(Connection),
|
||||
Location(ServerPath),
|
||||
}
|
||||
|
||||
impl ResponseHeader {
|
||||
|
|
@ -228,6 +258,7 @@ impl ResponseHeader {
|
|||
R::ContentType(content) => format!("Content-Type: {}", content.to_str()).into(),
|
||||
R::CacheControl(c) => format!("Cache-Control: {}", c.to_str()).into(),
|
||||
R::Connection(c) => format!("Connection: {}", c.to_str()).into(),
|
||||
R::Location(l) => format!("Location: /{}", l.path.join("/")).into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
13
src/websoket_connection.rs
Normal file
13
src/websoket_connection.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
use crate::request::Request;
|
||||
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
pub struct WebsocketConnection {
|
||||
steam: TcpStream,
|
||||
}
|
||||
|
||||
impl WebsocketConnection {
|
||||
pub fn initialize_connection(req: Request) -> tokio::io::Result<Self> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue