[uhttp] recv working
This commit is contained in:
commit
fd85c12da3
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/target
|
||||
14
Cargo.lock
generated
Normal file
14
Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "shelves"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"uhttp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uhttp"
|
||||
version = "0.1.0"
|
||||
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"crates/uhttp"
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[package]
|
||||
name = "shelves"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
uhttp = { path = "crates/uhttp" }
|
||||
6
crates/uhttp/Cargo.toml
Normal file
6
crates/uhttp/Cargo.toml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[package]
|
||||
name = "uhttp"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
27
crates/uhttp/README.md
Normal file
27
crates/uhttp/README.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# uhttp
|
||||
|
||||
A small allocation-free, sans-IO HTTP implementation
|
||||
|
||||
## Goals
|
||||
|
||||
1. Enable development of HTTP-speaking applications
|
||||
2. Simple to understand codebase
|
||||
3. Fast to compile
|
||||
|
||||
## Non-goals
|
||||
|
||||
1. Ultimate speed
|
||||
|
||||
## Scope
|
||||
### HTTP/1.1
|
||||
???
|
||||
|
||||
### HTTP/2
|
||||
???
|
||||
|
||||
## Non-scope
|
||||
### HTTP/1.1
|
||||
???
|
||||
|
||||
### HTTP/2
|
||||
???
|
||||
41
crates/uhttp/src/common.rs
Normal file
41
crates/uhttp/src/common.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
use crate::Error;
|
||||
|
||||
pub enum Eat<T> {
|
||||
NeedMoreData,
|
||||
Consumed {
|
||||
amt: usize,
|
||||
result: Result<T, Error>,
|
||||
},
|
||||
}
|
||||
impl<T> Eat<T> {
|
||||
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Eat<U> {
|
||||
match self {
|
||||
Eat::NeedMoreData => Eat::NeedMoreData,
|
||||
Eat::Consumed {
|
||||
amt,
|
||||
result: Err(e),
|
||||
} => Eat::Consumed {
|
||||
amt,
|
||||
result: Err(e),
|
||||
},
|
||||
Eat::Consumed { amt, result: Ok(v) } => Eat::Consumed {
|
||||
amt,
|
||||
result: Ok(f(v)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn and_then<U>(self, f: impl FnOnce(usize, T) -> Eat<U>) -> Eat<U> {
|
||||
match self {
|
||||
Eat::NeedMoreData => Eat::NeedMoreData,
|
||||
Eat::Consumed {
|
||||
amt,
|
||||
result: Err(e),
|
||||
} => Eat::Consumed {
|
||||
amt,
|
||||
result: Err(e),
|
||||
},
|
||||
Eat::Consumed { amt, result: Ok(v) } => f(amt, v),
|
||||
}
|
||||
}
|
||||
}
|
||||
182
crates/uhttp/src/lib.rs
Normal file
182
crates/uhttp/src/lib.rs
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
#![no_std]
|
||||
pub mod common;
|
||||
pub mod parse;
|
||||
pub mod parts;
|
||||
|
||||
pub use common::*;
|
||||
pub use parts::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
InvalidConnectionState,
|
||||
Parse(&'static str),
|
||||
TrailingBytes,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub enum Event<'a> {
|
||||
#[default]
|
||||
Empty,
|
||||
RequestLine(RequestLine<'a>),
|
||||
Header(Header<'a>),
|
||||
HeadersEnd,
|
||||
BodyChunk(&'a [u8]),
|
||||
Done,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Role {
|
||||
Client,
|
||||
Server,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum BodyState {
|
||||
ContentLength(usize),
|
||||
Chunked,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum StateRecv {
|
||||
RequestLine,
|
||||
Headers(Option<BodyState>),
|
||||
Body(BodyState),
|
||||
ValidateDone,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum StateSend {
|
||||
TODO,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
enum StateConnection {
|
||||
Recv(StateRecv),
|
||||
Send(StateSend),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Connection {
|
||||
role: Role,
|
||||
state: StateConnection,
|
||||
}
|
||||
impl Connection {
|
||||
pub fn new(role: Role) -> Self {
|
||||
let state = match role {
|
||||
Role::Server => StateConnection::Recv(StateRecv::RequestLine),
|
||||
Role::Client => StateConnection::Send(StateSend::TODO),
|
||||
};
|
||||
|
||||
Self { state, role }
|
||||
}
|
||||
|
||||
pub fn is_sending(&self) -> bool {
|
||||
matches!(self.state, StateConnection::Send(_))
|
||||
}
|
||||
|
||||
pub fn is_recving(&self) -> bool {
|
||||
matches!(self.state, StateConnection::Recv(_))
|
||||
}
|
||||
|
||||
pub fn handle_recv<'a>(&mut self, bytes: &'a [u8]) -> Eat<Event<'a>> {
|
||||
let recv = match &self.state {
|
||||
StateConnection::Recv(r) => r,
|
||||
_ => {
|
||||
return Eat::Consumed {
|
||||
amt: 0,
|
||||
result: Err(Error::InvalidConnectionState),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let n = match recv {
|
||||
StateRecv::RequestLine => parse::request_line(bytes).map(|rl| {
|
||||
(
|
||||
Event::RequestLine(rl),
|
||||
StateConnection::Recv(StateRecv::Headers(None)),
|
||||
)
|
||||
}),
|
||||
StateRecv::Headers(body_state) => {
|
||||
if bytes.starts_with(b"\r\n") {
|
||||
Eat::Consumed {
|
||||
amt: 2,
|
||||
result: Ok((
|
||||
Event::HeadersEnd,
|
||||
match body_state {
|
||||
None => StateConnection::Send(StateSend::TODO),
|
||||
Some(b) => {
|
||||
StateConnection::Recv(StateRecv::Body(*b))
|
||||
},
|
||||
},
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
parse::header(bytes).map(|h| {
|
||||
let b = match h {
|
||||
Header::Special(
|
||||
HeaderSpecial::TransferEncodingChunked,
|
||||
) => Some(BodyState::Chunked),
|
||||
Header::Special(HeaderSpecial::ContentLength(
|
||||
c,
|
||||
)) => Some(BodyState::ContentLength(c)),
|
||||
_ => *body_state,
|
||||
};
|
||||
|
||||
(
|
||||
Event::Header(h),
|
||||
StateConnection::Recv(StateRecv::Headers(b)),
|
||||
)
|
||||
})
|
||||
}
|
||||
},
|
||||
StateRecv::Body(body_state) => match body_state {
|
||||
BodyState::ContentLength(remaining) => {
|
||||
if bytes.len() < *remaining {
|
||||
Eat::Consumed {
|
||||
amt: bytes.len(),
|
||||
result: Ok((
|
||||
Event::BodyChunk(bytes),
|
||||
StateConnection::Recv(StateRecv::Body(
|
||||
BodyState::ContentLength(
|
||||
remaining - bytes.len(),
|
||||
),
|
||||
)),
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Eat::Consumed {
|
||||
amt: *remaining,
|
||||
result: Ok((
|
||||
Event::BodyChunk(bytes),
|
||||
StateConnection::Recv(StateRecv::ValidateDone),
|
||||
)),
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => todo!(),
|
||||
},
|
||||
StateRecv::ValidateDone => {
|
||||
if bytes.is_empty() {
|
||||
Eat::Consumed {
|
||||
amt: 0,
|
||||
result: Ok((
|
||||
Event::Done,
|
||||
StateConnection::Send(StateSend::TODO),
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
return Eat::Consumed {
|
||||
amt: 0,
|
||||
result: Err(Error::TrailingBytes),
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
n.map(|(ev, next_state)| {
|
||||
self.state = next_state;
|
||||
|
||||
ev
|
||||
})
|
||||
}
|
||||
}
|
||||
133
crates/uhttp/src/parse.rs
Normal file
133
crates/uhttp/src/parse.rs
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
use crate::{
|
||||
common::Eat,
|
||||
parts::{RequestLine, Version},
|
||||
Error, Header, HeaderOther, HeaderSpecial,
|
||||
};
|
||||
|
||||
pub type Parse<T> = Eat<T>;
|
||||
|
||||
pub fn split_crlf(d: &[u8]) -> Option<(&[u8], usize)> {
|
||||
let p = d.windows(2).position(|w| w == b"\r\n")?;
|
||||
|
||||
Some((&d[..p], p + 2))
|
||||
}
|
||||
|
||||
pub fn request_line(d: &[u8]) -> Parse<RequestLine> {
|
||||
let (line, amt) = match split_crlf(d) {
|
||||
Some(l) => l,
|
||||
None => {
|
||||
return Parse::NeedMoreData;
|
||||
},
|
||||
};
|
||||
|
||||
let mut it = line
|
||||
.split(|b| b.is_ascii_whitespace())
|
||||
.filter(|bs| !bs.is_empty());
|
||||
|
||||
let (method, target, version) = match (it.next(), it.next(), it.next()) {
|
||||
(Some(m), Some(t), Some(v)) => (m, t, v),
|
||||
_ => {
|
||||
return Parse::Consumed {
|
||||
amt,
|
||||
result: Err(Error::Parse(
|
||||
"request line doesn't have required number of elements",
|
||||
)),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let method = match core::str::from_utf8(method) {
|
||||
Ok(m) => m,
|
||||
_ => {
|
||||
return Parse::Consumed {
|
||||
amt,
|
||||
result: Err(Error::Parse("expected method to be ascii")),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let target = match core::str::from_utf8(target) {
|
||||
Ok(m) => m,
|
||||
_ => {
|
||||
return Parse::Consumed {
|
||||
amt,
|
||||
result: Err(Error::Parse("expected target to be ascii")),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let version = match () {
|
||||
_ if version.eq_ignore_ascii_case(b"http/1.1") => Version::HTTP1_1,
|
||||
_ => {
|
||||
return Parse::Consumed {
|
||||
amt,
|
||||
result: Err(Error::Parse("unknown http version")),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Parse::Consumed {
|
||||
amt,
|
||||
result: Ok(RequestLine {
|
||||
method,
|
||||
target,
|
||||
version,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn header(d: &[u8]) -> Parse<Header> {
|
||||
let (line, amt) = match split_crlf(d) {
|
||||
Some(l) => l,
|
||||
None => {
|
||||
return Parse::NeedMoreData;
|
||||
},
|
||||
};
|
||||
|
||||
let mut it = line
|
||||
.split(|b| b.is_ascii_whitespace())
|
||||
.filter(|bs| !bs.is_empty());
|
||||
|
||||
let (name, value) = match (it.next(), it.next()) {
|
||||
(Some(n), Some(v)) => (n, v),
|
||||
_ => {
|
||||
return Parse::Consumed {
|
||||
amt,
|
||||
result: Err(Error::Parse(
|
||||
"header doesn't have required number of elements",
|
||||
)),
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let name = match core::str::from_utf8(name) {
|
||||
Ok(m) => m,
|
||||
_ => {
|
||||
return Parse::Consumed {
|
||||
amt,
|
||||
result: Err(Error::Parse("expected target to be ascii")),
|
||||
}
|
||||
},
|
||||
};
|
||||
let name = name.split_once(":").map(|(f, _)| f).unwrap_or(name);
|
||||
|
||||
let h = match () {
|
||||
_ if name.eq_ignore_ascii_case("transfer-encoding")
|
||||
&& value.eq_ignore_ascii_case(b"chunked") =>
|
||||
{
|
||||
Header::Special(HeaderSpecial::TransferEncodingChunked)
|
||||
},
|
||||
_ if name.eq_ignore_ascii_case("content-length") => {
|
||||
match core::str::from_utf8(value)
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
{
|
||||
Some(v) => Header::Special(HeaderSpecial::ContentLength(v)),
|
||||
_ => Header::Other(HeaderOther { name, value }),
|
||||
}
|
||||
},
|
||||
_ => Header::Other(HeaderOther { name, value }),
|
||||
};
|
||||
|
||||
Parse::Consumed { amt, result: Ok(h) }
|
||||
}
|
||||
30
crates/uhttp/src/parts.rs
Normal file
30
crates/uhttp/src/parts.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#[derive(Debug)]
|
||||
pub enum Version {
|
||||
HTTP1_1,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RequestLine<'a> {
|
||||
pub method: &'a str,
|
||||
pub target: &'a str,
|
||||
pub version: Version,
|
||||
}
|
||||
|
||||
/// Headers that impact the state of the connection
|
||||
#[derive(Debug)]
|
||||
pub enum HeaderSpecial {
|
||||
TransferEncodingChunked,
|
||||
ContentLength(usize),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HeaderOther<'a> {
|
||||
pub name: &'a str,
|
||||
pub value: &'a [u8],
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Header<'a> {
|
||||
Special(HeaderSpecial),
|
||||
Other(HeaderOther<'a>),
|
||||
}
|
||||
165
flake.lock
Normal file
165
flake.lock
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
{
|
||||
"nodes": {
|
||||
"crane": {
|
||||
"locked": {
|
||||
"lastModified": 1728344376,
|
||||
"narHash": "sha256-lxTce2XE6mfJH8Zk6yBbqsbu9/jpwdymbSH5cCbiVOA=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "fd86b78f5f35f712c72147427b1eb81a9bd55d0b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"deno-flake": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 0,
|
||||
"narHash": "sha256-RsFYFS4k28JneAcEQO8ITXqIXKyBIa8FIKEPfz3NJHA=",
|
||||
"type": "git",
|
||||
"url": "file:///home/n/src/deno-flake"
|
||||
},
|
||||
"original": {
|
||||
"type": "git",
|
||||
"url": "file:///home/n/src/deno-flake"
|
||||
}
|
||||
},
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1728455642,
|
||||
"narHash": "sha256-abYGwrL6ak5sBRqwPh+V3CPJ6Pa89p378t51b7BO1lE=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "3b47535a5c782e4f4ad59cd4bdb23636b6926e03",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1726560853,
|
||||
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1726560853,
|
||||
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1725930920,
|
||||
"narHash": "sha256-RVhD9hnlTT2nJzPHlAqrWqCkA7T6CYrP41IoVRkciZM=",
|
||||
"path": "/nix/store/20yis5w6g397plssim663hqxdiiah2wr-source",
|
||||
"rev": "44a71ff39c182edaf25a7ace5c9454e7cba2c658",
|
||||
"type": "path"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"crane": "crane",
|
||||
"deno-flake": "deno-flake",
|
||||
"fenix": "fenix",
|
||||
"flake-utils": "flake-utils_2",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1728386838,
|
||||
"narHash": "sha256-Lk64EoJkvp3WMGVJK3CR1TYcNghX0/BqHPLW5zdvmLE=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "efaf8bd5de34e2f47bd57425b83e0c7974902176",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "rust-lang",
|
||||
"ref": "nightly",
|
||||
"repo": "rust-analyzer",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
46
flake.nix
Normal file
46
flake.nix
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
inputs.nixpkgs.url = "nixpkgs";
|
||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||
inputs.deno-flake = {
|
||||
url = "git+file:///home/n/src/deno-flake";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
inputs.fenix = {
|
||||
url = "github:nix-community/fenix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
inputs.crane = {
|
||||
url = "github:ipetkov/crane";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
|
||||
outputs = i:
|
||||
i.flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = i.nixpkgs.legacyPackages.${system};
|
||||
pkgsDeno = i.deno-flake.packages.${system};
|
||||
pkgsFenix = i.fenix.packages.${system};
|
||||
nightly = pkgsFenix.default;
|
||||
stable = pkgsFenix.stable;
|
||||
in {
|
||||
devShells.default = pkgs.mkShell {
|
||||
packages = [
|
||||
pkgsDeno.deno-latest
|
||||
(pkgs.sqlite.override { interactive = true; })
|
||||
pkgs.unzip
|
||||
pkgs.entr
|
||||
pkgs.gnumake
|
||||
pkgs.fd
|
||||
|
||||
(pkgsFenix.combine [
|
||||
(stable.withComponents [
|
||||
"cargo" "rustc" "rust-src" "rust-analyzer" "clippy"
|
||||
])
|
||||
nightly.rustfmt
|
||||
])
|
||||
pkgs.cargo-watch
|
||||
];
|
||||
};
|
||||
});
|
||||
}
|
||||
5
rustfmt.toml
Normal file
5
rustfmt.toml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
edition = "2021"
|
||||
hard_tabs = true
|
||||
match_block_trailing_comma = true
|
||||
max_width = 80
|
||||
empty_item_single_line = false
|
||||
34
src/main.rs
Normal file
34
src/main.rs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
use std::{error::Error, io::Read, net::TcpListener};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let listener = TcpListener::bind("127.0.0.1:8089")?;
|
||||
loop {
|
||||
let (mut stream, _) = listener.accept()?;
|
||||
let mut buf = vec![0; 1024];
|
||||
|
||||
let mut conn = uhttp::Connection::new(uhttp::Role::Server);
|
||||
|
||||
let end = stream.read(&mut buf)?;
|
||||
let mut data = &buf[..end];
|
||||
|
||||
loop {
|
||||
dbg!(&conn, data);
|
||||
match conn.handle_recv(data) {
|
||||
uhttp::Eat::NeedMoreData => todo!(),
|
||||
uhttp::Eat::Consumed { amt, result } => match result {
|
||||
Ok(ev) => match ev {
|
||||
uhttp::Event::Done => break,
|
||||
_ => {
|
||||
dbg!(ev);
|
||||
data = &data[amt..];
|
||||
},
|
||||
},
|
||||
|
||||
Err(e) => {
|
||||
dbg!(e);
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue