[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