mirror of
https://codeberg.org/bunbun/fluffle/
synced 2025-01-22 20:54:34 -08:00
version 0.1.0
This commit is contained in:
parent
e1ef48fc55
commit
9a1cd8bb50
111
Cargo.lock
generated
111
Cargo.lock
generated
@ -2,6 +2,117 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fluffle"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"lazy-regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy-regex"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d8e41c97e6bc7ecb552016274b99fbb5d035e8de288c582d9b933af6677bfda"
|
||||
dependencies = [
|
||||
"lazy-regex-proc_macros",
|
||||
"once_cell",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy-regex-proc_macros"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76e1d8b05d672c53cb9c7b920bbba8783845ae4f0b076e02a3db1d02c81b4163"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||
|
@ -4,3 +4,4 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
lazy-regex = { version = "3.3.0", features = ["std"] }
|
||||
|
41
src/client.rs
Normal file
41
src/client.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use std::{
|
||||
io::{self, Read, Write},
|
||||
net::TcpStream,
|
||||
};
|
||||
|
||||
pub struct Client {
|
||||
stream: TcpStream,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
pub(crate) fn new(stream: TcpStream) -> Self {
|
||||
Client { stream }
|
||||
}
|
||||
|
||||
// basically an alias for write (might remove)
|
||||
pub(crate) fn write_bytes(&mut self, mut bytes: Vec<u8>) -> io::Result<usize> {
|
||||
bytes.extend(b"\r\n");
|
||||
self.stream.write(&bytes)
|
||||
}
|
||||
|
||||
pub(crate) fn write_string(&mut self, mut string: String) -> io::Result<usize> {
|
||||
string.push_str("\r\n");
|
||||
self.stream.write(string.as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl Read for Client {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
self.stream.read(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl Write for Client {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.stream.write(buf)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.stream.flush()
|
||||
}
|
||||
}
|
7
src/http/mod.rs
Normal file
7
src/http/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
pub mod request;
|
||||
pub mod response;
|
||||
mod version;
|
||||
|
||||
pub use self::request::Request;
|
||||
pub use self::response::Response;
|
||||
pub use self::version::Version;
|
34
src/http/request.rs
Normal file
34
src/http/request.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use lazy_regex::regex_captures;
|
||||
|
||||
use crate::http;
|
||||
|
||||
/// HTTP request line
|
||||
///
|
||||
/// `<method> <target> HTTP/<version>`
|
||||
#[derive(Debug)]
|
||||
pub struct RequestLine<'a> {
|
||||
pub method: &'a str,
|
||||
pub target: &'a str,
|
||||
pub version: http::Version,
|
||||
}
|
||||
|
||||
impl RequestLine<'_> {
|
||||
pub(crate) fn from_str(str: &str) -> RequestLine<'_> {
|
||||
let (_, method, target, version) =
|
||||
regex_captures!(r#"([^\s]+)\s+([^\s]+)\s+HTTP\/([0-9.]+)"#, str).unwrap();
|
||||
|
||||
RequestLine {
|
||||
method,
|
||||
target,
|
||||
version: http::Version::from_str(version),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Request<'a> {
|
||||
pub request_line: RequestLine<'a>,
|
||||
pub headers: HashMap<&'a str, &'a str>,
|
||||
}
|
55
src/http/response.rs
Normal file
55
src/http/response.rs
Normal file
@ -0,0 +1,55 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::http;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Status {
|
||||
OK,
|
||||
NotFound,
|
||||
}
|
||||
|
||||
impl Status {
|
||||
fn code(&self) -> u16 {
|
||||
match self {
|
||||
Status::OK => 200,
|
||||
Status::NotFound => 404,
|
||||
}
|
||||
}
|
||||
|
||||
fn message(&self) -> &str {
|
||||
match self {
|
||||
Status::OK => "OK",
|
||||
Status::NotFound => "Not Found",
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_string(&self) -> String {
|
||||
format!("{} {}", self.code(), self.message())
|
||||
}
|
||||
}
|
||||
|
||||
/// HTTP response line
|
||||
///
|
||||
/// `HTTP/<version> <status-code> <status-message>`
|
||||
#[derive(Debug)]
|
||||
pub struct ResponseLine {
|
||||
pub version: http::Version,
|
||||
pub status: Status,
|
||||
}
|
||||
|
||||
impl ResponseLine {
|
||||
pub fn new(version: http::Version, status: Status) -> Self {
|
||||
ResponseLine { version, status }
|
||||
}
|
||||
|
||||
pub(crate) fn to_string(&self) -> String {
|
||||
format!("HTTP/{} {}", self.version.to_str(), self.status.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Response<'a> {
|
||||
pub response_line: ResponseLine,
|
||||
pub headers: Option<HashMap<&'a str, &'a str>>,
|
||||
pub content: Option<Vec<u8>>,
|
||||
}
|
37
src/http/version.rs
Normal file
37
src/http/version.rs
Normal file
@ -0,0 +1,37 @@
|
||||
#[derive(Debug)]
|
||||
pub enum Version {
|
||||
Http0_9,
|
||||
Http1_0,
|
||||
Http1_1,
|
||||
Http2,
|
||||
Http3,
|
||||
}
|
||||
|
||||
impl Version {
|
||||
pub(crate) fn from_str(str: &str) -> Self {
|
||||
match str {
|
||||
"0.9" => Version::Http0_9,
|
||||
"1.0" => Version::Http1_0,
|
||||
"1.1" => Version::Http1_1,
|
||||
"2" => Version::Http2,
|
||||
"3" => Version::Http3,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_str(&self) -> &str {
|
||||
match self {
|
||||
Version::Http0_9 => "0.9",
|
||||
Version::Http1_0 => "1.0",
|
||||
Version::Http1_1 => "1.1",
|
||||
Version::Http2 => "2",
|
||||
Version::Http3 => "3",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Version {
|
||||
fn default() -> Self {
|
||||
Version::Http1_1
|
||||
}
|
||||
}
|
@ -1 +1,7 @@
|
||||
mod client;
|
||||
pub mod http;
|
||||
mod server;
|
||||
|
||||
pub use client::Client;
|
||||
pub use http::Version as HttpVersion;
|
||||
pub use server::Server;
|
||||
|
102
src/server.rs
Normal file
102
src/server.rs
Normal file
@ -0,0 +1,102 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io::{Read, Write},
|
||||
net::TcpListener,
|
||||
str::from_utf8,
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
http::{request::RequestLine, Request, Response},
|
||||
Client,
|
||||
};
|
||||
|
||||
pub struct Server {
|
||||
listener: TcpListener,
|
||||
|
||||
on_request: Arc<Mutex<Option<Box<dyn Fn(Request) -> Response + Send + 'static>>>>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub fn new(ip: &str, port: i32) -> Self {
|
||||
let addr = format!("{}:{}", ip, port);
|
||||
let listener = TcpListener::bind(addr).unwrap();
|
||||
|
||||
Server {
|
||||
listener,
|
||||
|
||||
on_request: Arc::new(Mutex::new(None)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) {
|
||||
for stream in self.listener.incoming() {
|
||||
let stream = stream.unwrap();
|
||||
|
||||
let on_request = Arc::clone(&self.on_request);
|
||||
|
||||
thread::spawn(move || {
|
||||
let mut client = Client::new(stream);
|
||||
|
||||
let mut buffer = vec![0; 1024];
|
||||
let bytes_read = client.read(&mut buffer).unwrap();
|
||||
|
||||
if bytes_read > 0 {
|
||||
if let Ok(request_str) = from_utf8(&buffer[..bytes_read]) {
|
||||
if let Some(on_request) = &*on_request.lock().unwrap() {
|
||||
let request;
|
||||
{
|
||||
let mut lines = request_str.lines();
|
||||
|
||||
let request_line = RequestLine::from_str(lines.next().unwrap());
|
||||
|
||||
let mut headers = HashMap::new();
|
||||
for line in lines {
|
||||
if let Some((k, v)) = line.split_once(": ") {
|
||||
headers.insert(k, v);
|
||||
}
|
||||
}
|
||||
|
||||
request = Request {
|
||||
request_line,
|
||||
headers,
|
||||
};
|
||||
}
|
||||
|
||||
let response = on_request(request);
|
||||
{
|
||||
let response_line = response.response_line.to_string();
|
||||
client.write_string(response_line).unwrap();
|
||||
|
||||
if let Some(headers) = response.headers {
|
||||
for (k, v) in headers {
|
||||
client.write_string(format!("{}: {}", k, v)).unwrap();
|
||||
}
|
||||
} else {
|
||||
client.write(b"\r\n").unwrap();
|
||||
}
|
||||
|
||||
if let Some(content) = response.content {
|
||||
client.write(b"\r\n").unwrap();
|
||||
client.write(&content).unwrap();
|
||||
} else {
|
||||
client.write(b"\r\n").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
client.flush().unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_request<F>(&mut self, f: F)
|
||||
where
|
||||
F: Fn(Request) -> Response + Send + 'static,
|
||||
{
|
||||
*self.on_request.lock().unwrap() = Some(Box::new(f))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user