mirror of
https://codeberg.org/bunbun/fluffle/
synced 2025-04-21 22:27:56 -07:00
initial
This commit is contained in:
commit
0017c77637
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
target
|
40
Cargo.lock
generated
Normal file
40
Cargo.lock
generated
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fluffle"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"http",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fnv"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http"
|
||||||
|
version = "1.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"fnv",
|
||||||
|
"itoa",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[package]
|
||||||
|
name = "fluffle"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bytes = "1.10.1"
|
||||||
|
http = "1.2.0"
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Winter Hille
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
10
shell.nix
Normal file
10
shell.nix
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
pkgs ? import <nixpkgs> { },
|
||||||
|
}:
|
||||||
|
with pkgs;
|
||||||
|
mkShell {
|
||||||
|
buildInputs = [
|
||||||
|
rustc
|
||||||
|
cargo
|
||||||
|
];
|
||||||
|
}
|
118
src/lib.rs
Normal file
118
src/lib.rs
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
use std::{
|
||||||
|
io::{Read, Write},
|
||||||
|
net::{IpAddr, SocketAddr, TcpListener},
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
thread,
|
||||||
|
};
|
||||||
|
|
||||||
|
use bytes::{Bytes, BytesMut};
|
||||||
|
use http::{Method, Request, Response, Version};
|
||||||
|
|
||||||
|
pub use http;
|
||||||
|
|
||||||
|
pub struct Server {
|
||||||
|
listener: TcpListener,
|
||||||
|
request_handler:
|
||||||
|
Arc<Mutex<Option<Box<dyn Fn(Request<Bytes>) -> Response<Bytes> + Send + Sync>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Server {
|
||||||
|
pub fn new(ip: IpAddr, port: u16) -> Self {
|
||||||
|
let addr = SocketAddr::from((ip, port));
|
||||||
|
let listener = TcpListener::bind(addr).unwrap();
|
||||||
|
|
||||||
|
Server {
|
||||||
|
listener,
|
||||||
|
request_handler: Arc::new(Mutex::new(None)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self) {
|
||||||
|
for stream in self.listener.incoming() {
|
||||||
|
let mut stream = stream.unwrap();
|
||||||
|
|
||||||
|
let request_handler = Arc::clone(&self.request_handler);
|
||||||
|
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut buffer = vec![0; 1024];
|
||||||
|
let bytes_read = stream.read(&mut buffer).unwrap();
|
||||||
|
|
||||||
|
if bytes_read > 0 {
|
||||||
|
let request_bytes = BytesMut::from(&buffer[..bytes_read]);
|
||||||
|
if let Some(request_handler) = &*request_handler
|
||||||
|
.lock()
|
||||||
|
.unwrap_or_else(|poisoned| poisoned.into_inner())
|
||||||
|
{
|
||||||
|
let request = parse_request(request_bytes).unwrap();
|
||||||
|
let response = parse_response(request_handler(request));
|
||||||
|
|
||||||
|
stream.write(&response).unwrap();
|
||||||
|
stream.flush().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_request<F>(&self, handler: F)
|
||||||
|
where
|
||||||
|
F: Fn(Request<Bytes>) -> Response<Bytes> + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
*self.request_handler.lock().unwrap() = Some(Box::new(handler))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_request(request: BytesMut) -> Option<Request<Bytes>> {
|
||||||
|
let request_str = std::str::from_utf8(&request).ok()?;
|
||||||
|
let mut lines = request_str.lines();
|
||||||
|
let request_line = lines.next()?;
|
||||||
|
let mut parts = request_line.split_whitespace();
|
||||||
|
|
||||||
|
let method = parts.next()?.parse::<Method>().ok()?;
|
||||||
|
let uri = parts.next()?;
|
||||||
|
let version = match parts.next()? {
|
||||||
|
"HTTP/0.9" => Version::HTTP_09,
|
||||||
|
"HTTP/1.0" => Version::HTTP_10,
|
||||||
|
"HTTP/1.1" => Version::HTTP_11,
|
||||||
|
"HTTP/2.0" => Version::HTTP_2,
|
||||||
|
"HTTP/3.0" => Version::HTTP_3,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut builder = Request::builder().method(method).uri(uri).version(version);
|
||||||
|
|
||||||
|
for line in lines.by_ref() {
|
||||||
|
if let Some((k, v)) = line.split_once(": ") {
|
||||||
|
builder = builder.header(k, v);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let body = lines.collect::<Vec<&str>>().join("\n");
|
||||||
|
|
||||||
|
builder.body(body.into()).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_response(response: Response<Bytes>) -> BytesMut {
|
||||||
|
let mut response_bytes = BytesMut::new();
|
||||||
|
|
||||||
|
let version = match response.version() {
|
||||||
|
Version::HTTP_09 => "HTTP/0.9",
|
||||||
|
Version::HTTP_10 => "HTTP/1.0",
|
||||||
|
Version::HTTP_11 => "HTTP/1.1",
|
||||||
|
Version::HTTP_2 => "HTTP/2.0",
|
||||||
|
Version::HTTP_3 => "HTTP/3.0",
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let status = response.status();
|
||||||
|
|
||||||
|
response_bytes.extend_from_slice(format!("{} {}\r\n", version, status).as_bytes());
|
||||||
|
|
||||||
|
for (k, v) in response.headers() {
|
||||||
|
response_bytes.extend_from_slice(format!("{}: {}\r\n", k, v.to_str().unwrap()).as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
response_bytes.extend_from_slice(b"\r\n");
|
||||||
|
response_bytes.extend_from_slice(response.body());
|
||||||
|
|
||||||
|
response_bytes
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user