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