commit rust backend

This commit is contained in:
Theis Pieter Hollebeek 2023-02-09 15:08:13 +01:00
parent db6bc13a49
commit 02d5fa4b8e
7 changed files with 215 additions and 0 deletions

1
rs-backend/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target/

7
rs-backend/Cargo.lock generated Normal file
View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "rs-backend"
version = "0.1.0"

8
rs-backend/Cargo.toml Normal file
View File

@ -0,0 +1,8 @@
[package]
name = "rs-backend"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

39
rs-backend/src/main.rs Normal file
View File

@ -0,0 +1,39 @@
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
mod parse;
fn parse_client(stream_buffer: &mut [u8]) -> parse::Result<()> {
let start = parse::http_start(stream_buffer)?;
loop {
match parse::http_header(stream_buffer, start.total_length) {
Ok(header) => {
todo!();
}
Err(err) => match err {
parse::Error::InvalidHeader => {
break;
}
err => return Err(err),
},
}
}
Ok(())
}
fn handle_client(mut stream: TcpStream) -> parse::Result<()> {
let mut stream_buffer = Vec::from([0u8; 4096]);
let bytes_read = stream.read(&mut stream_buffer)?;
stream_buffer.truncate(bytes_read);
parse_client(&mut stream_buffer)
}
fn main() -> std::io::Result<()> {
let listener = TcpListener::bind("127.0.0.1:8080")?;
for stream in listener.incoming() {
if let Err(err) = handle_client(stream?) {
println!("error occurred: {err:#?}");
};
}
Ok(())
}

View File

@ -0,0 +1,136 @@
fn read_until_whitespace(
stream_buffer: &mut [u8],
stream_initial_index: usize,
) -> super::Result<String> {
let buffer = String::from_utf8(
(stream_initial_index..stream_buffer.len())
.map_while(|stream_index| {
let byte = stream_buffer[stream_index];
if byte == b' ' || byte == b'\r' && stream_buffer[stream_index + 1] == b'\n' {
return None;
}
Some(byte)
})
.collect(),
)?;
Ok(buffer)
}
pub struct HttpStart {
pub method: String,
pub path: String,
pub version: String,
pub total_length: usize,
}
pub fn http_start(stream_buffer: &mut [u8]) -> super::Result<HttpStart> {
let mut total_length = 0;
let method = read_until_whitespace(stream_buffer, total_length)?;
total_length += method.len() + 1;
let path = read_until_whitespace(stream_buffer, total_length)?;
total_length += path.len() + 1;
let version = read_until_whitespace(stream_buffer, total_length)?;
total_length += version.len() + 2; // CRLF
Ok(HttpStart {
method,
path,
version,
total_length,
})
}
pub struct HttpHeader {
pub key: String,
pub content: String,
pub total_length: usize,
}
pub fn http_header(
stream_buffer: &mut [u8],
stream_initial_index: usize,
) -> super::Result<HttpHeader> {
if read_until_whitespace(stream_buffer, stream_initial_index)?.is_empty() {
return Err(super::Error::InvalidHeader);
}
let key = String::from_utf8(
(stream_initial_index..stream_buffer.len())
.map_while(|stream_index| {
let byte = stream_buffer[stream_index];
if byte == b':' {
None
} else {
Some(byte)
}
})
.collect(),
)?;
let content = String::from_utf8(
(stream_initial_index + key.len() + 2..stream_buffer.len())
.map_while(|stream_index| {
let byte = stream_buffer[stream_index];
if byte == b'\r' && stream_buffer[stream_index + 1] == b'\n' {
None
} else {
Some(byte)
}
})
.collect(),
)?;
let total_length = key.len() + content.len() + 4; // ': ' + '\r\n'
Ok(HttpHeader {
key,
content,
total_length,
})
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn valid_http_header() {
let mut buffer = b"Content-Type: application/json\r\n".to_owned();
let len = buffer.len();
let header = http_header(&mut buffer, 0).expect("should not fail with valid input");
assert_eq!(header.total_length, len, "total length should be {len}");
assert_eq!(
&header.content, "application/json",
"header content should be application/json"
);
assert_eq!(
&header.key, "Content-Type",
"header key should be Content-Type"
);
}
#[test]
fn invalid_http_header() {
let mut buffer = b"\r\n".to_owned();
let result = http_header(&mut buffer, 0)
.err()
.expect("should fail with invalid input");
assert_eq!(
result,
crate::parse::Error::InvalidHeader,
"should return ParseError::InvalidHeader on invalid header"
);
}
#[test]
fn valid_http_start() {
let mut buffer = b"GET /resource HTTP/1.1\r\n".to_owned();
let len = buffer.len();
let start = http_start(&mut buffer).expect("should not fail with valid input");
assert_eq!(start.total_length, len, "total length should be {len}");
assert_eq!(&start.method, "GET", "method should be GET");
assert_eq!(&start.path, "/resource", "path should be /resource");
assert_eq!(&start.version, "HTTP/1.1", "version should be HTTP/1.1");
}
}

View File

@ -0,0 +1,20 @@
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
InvalidHeader,
InvalidString,
Io,
}
impl From<std::string::FromUtf8Error> for Error {
fn from(_: std::string::FromUtf8Error) -> Self {
Error::InvalidString
}
}
impl From<std::io::Error> for Error {
fn from(_: std::io::Error) -> Self {
Error::Io
}
}
pub type Result<T> = std::result::Result<T, Error>;

View File

@ -0,0 +1,4 @@
pub mod client;
pub mod error;
pub use client::*;
pub use error::*;