[uhttp] echo server example

This commit is contained in:
soup 2024-10-14 15:04:36 -04:00
parent fd85c12da3
commit f548be3ea3
No known key found for this signature in database
14 changed files with 619 additions and 235 deletions

11
Cargo.lock generated
View file

@ -7,8 +7,19 @@ name = "shelves"
version = "0.1.0"
dependencies = [
"uhttp",
"uhttp-ext",
]
[[package]]
name = "uhttp"
version = "0.1.0"
dependencies = [
"uhttp-ext",
]
[[package]]
name = "uhttp-ext"
version = "0.1.0"
dependencies = [
"uhttp",
]

View file

@ -1,6 +1,7 @@
[workspace]
members = [
"crates/uhttp"
"crates/uhttp",
"crates/uhttp/crates/uhttp-ext",
]
resolver = "2"
@ -11,3 +12,4 @@ edition = "2021"
[dependencies]
uhttp = { path = "crates/uhttp" }
uhttp-ext = { path = "crates/uhttp/crates/uhttp-ext" }

View file

@ -4,3 +4,6 @@ version = "0.1.0"
edition = "2021"
[dependencies]
[dev-dependencies]
uhttp-ext = { path = "./crates/uhttp-ext" }

View file

@ -0,0 +1,7 @@
[package]
name = "uhttp-ext"
version = "0.1.0"
edition = "2021"
[dependencies]
uhttp = { path = "../.." }

View file

@ -0,0 +1,99 @@
use std::io::{Read, Result as IOResult};
pub struct NotEnoughSpace;
pub struct Buf<T, U> {
written_len: usize,
data: T,
_dt: core::marker::PhantomData<U>,
}
impl<T, U> Buf<T, U> {
pub fn new(data: T) -> Self {
Self {
data,
written_len: 0,
_dt: Default::default(),
}
}
}
impl<T, U> Buf<T, U>
where
T: AsRef<[U]>,
{
pub fn remaining(&self) -> &[U] {
&self.data.as_ref()[self.written_len..]
}
pub fn filled(&self) -> &[U] {
&self.data.as_ref()[..self.written_len]
}
}
impl<T, U> Buf<T, U>
where
T: AsRef<[U]>,
T: AsMut<[U]>,
{
pub fn remaining_mut(&mut self) -> &mut [U] {
&mut self.data.as_mut()[self.written_len..]
}
pub fn extend_from_slice(&mut self, s: &[U]) -> Result<(), NotEnoughSpace>
where
U: Copy,
{
if self.remaining().len() < s.len() {
return Err(NotEnoughSpace);
}
self.remaining_mut()[..s.len()].copy_from_slice(s);
Ok(())
}
pub fn pop_front(&mut self, amt: usize)
where
U: Copy,
{
if amt > self.filled().len() {
panic!("Filled not big enough");
}
let data = self.data.as_mut();
let src = &data[amt..];
let count = data.len() - amt;
// SAFETY:
// - src comes from data
// - U is copy
// - count is within bounds of data
unsafe { core::ptr::copy(src.as_ptr(), data.as_mut_ptr(), count) }
self.written_len -= amt;
}
}
impl<T> Buf<T, u8>
where
T: AsRef<[u8]> + AsMut<[u8]>,
{
pub fn read_from(&mut self, mut r: impl Read) -> IOResult<usize> {
let remaining = self.remaining_mut();
let amt = r.read(remaining)?;
self.written_len += amt;
Ok(amt)
}
}
impl<T> uhttp::Write for Buf<T, u8>
where
T: AsRef<[u8]> + AsMut<[u8]>,
{
fn write(&mut self, buf: &[u8]) -> uhttp::Written {
self.extend_from_slice(buf)
.map_err(|_| uhttp::ErrorKind::BufNotBigEnough.into())
}
}

View file

@ -0,0 +1,82 @@
//! A simple echo server that listens on 127.0.0.1:8089
use std::{error::Error, io::Write, net::TcpListener};
use uhttp::Lift;
fn main() -> Result<(), Box<dyn Error>> {
let listener = TcpListener::bind("127.0.0.1:8089")?;
loop {
let (mut stream, _) = listener.accept()?;
let mut conn = uhttp::Connection::new(uhttp::Role::Server);
let mut body: Vec<u8> = Vec::new();
let mut buf = vec![0; 1024].into_boxed_slice();
let mut buf = uhttp_ext::Buf::new(&mut buf);
let mut method_not_allowed = false;
loop {
let data = buf.filled();
let (remaining, r) = conn.handle_recv(data).lift();
match r.map_err(|e| e.kind) {
Err(uhttp::ErrorKind::NeedMoreData) => {
buf.read_from(&mut stream)?;
continue;
},
Err(e) => panic!("{e:?}"),
Ok(event) => {
match event {
uhttp::Event::RequestLine(r) => {
if !r.method.eq_ignore_ascii_case("post") {
method_not_allowed = true;
}
},
uhttp::Event::RecvDone => break,
uhttp::Event::BodyChunk(b) => body.extend_from_slice(b),
_ => (),
};
},
};
let len = data.len() - remaining.len();
buf.pop_front(len);
}
let parts: &[uhttp::Event] = if method_not_allowed {
&[
uhttp::Event::StatusLine(uhttp::StatusLine {
version: uhttp::Version::HTTP1_1,
status_code: 405,
status_text: "Method not allowed",
}),
uhttp::Event::HeadersEnd,
uhttp::Event::SendDone,
]
} else {
&[
uhttp::Event::StatusLine(uhttp::StatusLine {
version: uhttp::Version::HTTP1_1,
status_code: 200,
status_text: "OK",
}),
uhttp::Event::Header(uhttp::Header::Special(
uhttp::HeaderSpecial::ContentLength(body.len()),
)),
uhttp::Event::HeadersEnd,
uhttp::Event::BodyChunk(body.as_slice()),
uhttp::Event::SendDone,
]
};
let buf = vec![0; 1024];
let mut cursor = uhttp::WriteCursor::new(buf);
for p in parts {
if let Err(e) = conn.handle_send(p, &mut cursor) {
panic!("{e:?}")
};
stream.write_all(cursor.written())?;
cursor.reset();
}
}
}

View file

@ -1,41 +1,38 @@
use crate::Error;
pub enum Eat<T> {
#[derive(Debug)]
pub enum ErrorKind {
NeedMoreData,
Consumed {
amt: usize,
result: Result<T, Error>,
},
InvalidConnectionState,
Parse,
TrailingBytes,
BufNotBigEnough,
InvalidEventForConnectionState,
BodySizeMismatch,
}
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)),
},
}
#[derive(Debug)]
pub struct Error {
pub kind: ErrorKind,
pub details: &'static str,
}
impl Error {
pub fn new(kind: ErrorKind) -> Self {
Self::with_details(kind, "")
}
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),
}
pub fn with_details(kind: ErrorKind, details: &'static str) -> Self {
Error { kind, details }
}
}
impl From<ErrorKind> for Error {
fn from(value: ErrorKind) -> Self {
Self::new(value)
}
}
pub fn fail<T>(error_kind: ErrorKind) -> Result<T, Error> {
fail_details(error_kind, "")
}
pub fn fail_details<T>(error_kind: ErrorKind, details: &'static str) -> Result<T, Error> {
Err(Error::with_details(error_kind, details))
}

View file

@ -2,18 +2,16 @@
pub mod common;
pub mod parse;
pub mod parts;
pub mod util;
pub mod write;
pub use common::*;
pub use parse::Parse;
pub use parts::*;
pub use util::*;
pub use write::{Write, WriteCursor, Written};
#[derive(Debug)]
pub enum Error {
InvalidConnectionState,
Parse(&'static str),
TrailingBytes,
}
#[derive(Default, Debug)]
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Event<'a> {
#[default]
Empty,
@ -21,7 +19,9 @@ pub enum Event<'a> {
Header(Header<'a>),
HeadersEnd,
BodyChunk(&'a [u8]),
Done,
RecvDone,
StatusLine(StatusLine<'a>),
SendDone,
}
#[derive(Debug)]
@ -46,7 +46,10 @@ enum StateRecv {
#[derive(Debug, Copy, Clone)]
enum StateSend {
TODO,
StatusLine,
Headers(Option<BodyState>),
Body(BodyState),
ValidateDone,
}
#[derive(Debug, Copy, Clone)]
@ -64,7 +67,7 @@ impl Connection {
pub fn new(role: Role) -> Self {
let state = match role {
Role::Server => StateConnection::Recv(StateRecv::RequestLine),
Role::Client => StateConnection::Send(StateSend::TODO),
Role::Client => StateConnection::Send(StateSend::StatusLine),
};
Self { state, role }
@ -78,47 +81,43 @@ impl Connection {
matches!(self.state, StateConnection::Recv(_))
}
pub fn handle_recv<'a>(&mut self, bytes: &'a [u8]) -> Eat<Event<'a>> {
pub fn handle_recv<'a>(&mut self, bytes: &'a [u8]) -> Parse<'a, Event<'a>> {
let recv = match &self.state {
StateConnection::Recv(r) => r,
_ => {
return Eat::Consumed {
amt: 0,
result: Err(Error::InvalidConnectionState),
}
return Err((bytes, ErrorKind::InvalidConnectionState.into()));
},
};
let n = match recv {
StateRecv::RequestLine => parse::request_line(bytes).map(|rl| {
StateRecv::RequestLine => parse::request_line(bytes).map2(|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((
Ok((
&bytes[2..],
(
Event::HeadersEnd,
match body_state {
None => StateConnection::Send(StateSend::TODO),
Some(b) => {
StateConnection::Recv(StateRecv::Body(*b))
},
None => StateConnection::Recv(StateRecv::ValidateDone),
Some(b) => StateConnection::Recv(StateRecv::Body(*b)),
},
)),
}
),
))
} else {
parse::header(bytes).map(|h| {
parse::header(bytes).map2(|h| {
let b = match h {
Header::Special(
HeaderSpecial::TransferEncodingChunked,
) => Some(BodyState::Chunked),
Header::Special(HeaderSpecial::ContentLength(
c,
)) => Some(BodyState::ContentLength(c)),
Header::Special(HeaderSpecial::TransferEncodingChunked) => {
Some(BodyState::Chunked)
},
Header::Special(HeaderSpecial::ContentLength(c)) => {
Some(BodyState::ContentLength(c))
},
_ => *body_state,
};
@ -129,54 +128,120 @@ impl Connection {
})
}
},
StateRecv::Body(body_state) => match body_state {
BodyState::ContentLength(remaining) => {
if bytes.len() < *remaining {
Eat::Consumed {
amt: bytes.len(),
result: Ok((
Ok((
&[] as &[u8],
(
Event::BodyChunk(bytes),
StateConnection::Recv(StateRecv::Body(
BodyState::ContentLength(
remaining - bytes.len(),
),
BodyState::ContentLength(remaining - bytes.len()),
)),
)),
}
),
))
} else {
Eat::Consumed {
amt: *remaining,
result: Ok((
Event::BodyChunk(bytes),
Ok((
&bytes[*remaining..],
(
Event::BodyChunk(&bytes[..*remaining]),
StateConnection::Recv(StateRecv::ValidateDone),
)),
}
),
))
}
},
_ => todo!(),
},
StateRecv::ValidateDone => {
if bytes.is_empty() {
Eat::Consumed {
amt: 0,
result: Ok((
Event::Done,
StateConnection::Send(StateSend::TODO),
)),
}
Ok((
bytes,
(
Event::RecvDone,
StateConnection::Send(StateSend::StatusLine),
),
))
} else {
return Eat::Consumed {
amt: 0,
result: Err(Error::TrailingBytes),
};
fail(ErrorKind::TrailingBytes).tup(bytes)
}
},
};
n.map(|(ev, next_state)| {
n.map2(move |(ev, next_state)| {
self.state = next_state;
ev
})
}
pub fn handle_send(&mut self, event: &Event, mut w: impl Write) -> Written {
let state = match self.state {
StateConnection::Send(s) => s,
_ => return fail(ErrorKind::InvalidConnectionState),
};
let next = match (state, event) {
(StateSend::StatusLine, Event::StatusLine(sl)) => write::status_line(sl, w)
.map(|_| StateConnection::Send(StateSend::Headers(None))),
(StateSend::Headers(body_state), Event::Header(h)) => {
write::header(h, w)?;
let bs = match h {
Header::Other(_) => body_state,
Header::Special(h) => match h {
HeaderSpecial::TransferEncodingChunked => Some(BodyState::Chunked),
HeaderSpecial::ContentLength(cl) => {
Some(BodyState::ContentLength(*cl))
},
},
};
Ok(StateConnection::Send(StateSend::Headers(bs)))
},
(StateSend::Headers(body_state), Event::HeadersEnd) => {
write!(w, "\r\n")?;
match body_state {
Some(bs) => Ok(StateConnection::Send(StateSend::Body(bs))),
None => Ok(StateConnection::Send(StateSend::ValidateDone)),
}
},
(StateSend::Body(b), Event::BodyChunk(c)) => match b {
BodyState::ContentLength(cl) => match () {
_ if c.len() < cl => {
w.write(c)?;
Ok(StateConnection::Send(StateSend::Body(
BodyState::ContentLength(cl - c.len()),
)))
},
_ if c.len() == cl => {
w.write(c)?;
Ok(StateConnection::Send(StateSend::ValidateDone))
},
_ => {
return fail(ErrorKind::BodySizeMismatch);
},
},
BodyState::Chunked => todo!(),
},
(StateSend::ValidateDone, Event::SendDone) => {
Ok(StateConnection::Recv(StateRecv::RequestLine))
},
_ => return Err(Error::from(ErrorKind::InvalidEventForConnectionState)),
}?;
self.state = next;
Ok(())
}
}

View file

@ -1,133 +1,113 @@
use crate::{
common::Eat,
fail_details,
parts::{RequestLine, Version},
Error, Header, HeaderOther, HeaderSpecial,
Error, ErrorKind, Header, HeaderOther, HeaderSpecial, Tup,
};
pub type Parse<T> = Eat<T>;
pub type Parse<'a, T> = Result<(&'a [u8], T), (&'a [u8], Error)>;
pub fn split_crlf(d: &[u8]) -> Option<(&[u8], usize)> {
pub fn split_crlf(d: &[u8]) -> Option<(&[u8], &[u8])> {
let p = d.windows(2).position(|w| w == b"\r\n")?;
Some((&d[..p], p + 2))
Some((&d[..p], &d[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 (line, rest) = split_crlf(d).ok_or((d, ErrorKind::NeedMoreData.into()))?;
let mut it = line
.split(|b| b.is_ascii_whitespace())
.filter(|bs| !bs.is_empty());
let go = || {
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(
let (method, target, version) = match (it.next(), it.next(), it.next()) {
(Some(m), Some(t), Some(v)) => (m, t, v),
_ => {
return fail_details(
ErrorKind::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 method = match core::str::from_utf8(method) {
Ok(m) => m,
_ => {
return fail_details(ErrorKind::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 target = match core::str::from_utf8(target) {
Ok(m) => m,
_ => {
return fail_details(ErrorKind::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")),
}
},
};
let version = match () {
_ if version.eq_ignore_ascii_case(b"http/1.1") => Version::HTTP1_1,
_ => {
return fail_details(ErrorKind::Parse, "unknown http version");
},
};
Parse::Consumed {
amt,
result: Ok(RequestLine {
Ok(RequestLine {
method,
target,
version,
}),
}
})
};
go().tup(rest)
}
pub fn header(d: &[u8]) -> Parse<Header> {
let (line, amt) = match split_crlf(d) {
Some(l) => l,
None => {
return Parse::NeedMoreData;
},
};
let (line, rest) = split_crlf(d).ok_or((d, ErrorKind::NeedMoreData.into()))?;
let mut it = line
.split(|b| b.is_ascii_whitespace())
.filter(|bs| !bs.is_empty());
let go = || {
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(
let (name, value) = match (it.next(), it.next()) {
(Some(n), Some(v)) => (n, v),
_ => {
return fail_details(
ErrorKind::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 name = match core::str::from_utf8(name) {
Ok(m) => m,
_ => return fail_details(ErrorKind::Parse, "expected target to be ascii"),
};
let name = name
.split_once(":")
.map(|(f, _)| f)
.ok_or(Error::with_details(ErrorKind::Parse, "invalid header 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())
let h = match () {
_ if name.eq_ignore_ascii_case("transfer-encoding")
&& value.eq_ignore_ascii_case(b"chunked") =>
{
Some(v) => Header::Special(HeaderSpecial::ContentLength(v)),
_ => Header::Other(HeaderOther { name, value }),
}
},
_ => Header::Other(HeaderOther { name, value }),
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 }),
};
Ok(h)
};
Parse::Consumed { amt, result: Ok(h) }
go().tup(rest)
}

View file

@ -1,29 +1,43 @@
#[derive(Debug)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Version {
HTTP1_1,
}
impl core::fmt::Display for Version {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Version::HTTP1_1 => write!(f, "HTTP/1.1"),
}
}
}
#[derive(Debug)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct RequestLine<'a> {
pub method: &'a str,
pub target: &'a str,
pub version: Version,
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct StatusLine<'a> {
pub version: Version,
pub status_code: u16,
pub status_text: &'a str,
}
/// Headers that impact the state of the connection
#[derive(Debug)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum HeaderSpecial {
TransferEncodingChunked,
ContentLength(usize),
}
#[derive(Debug)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct HeaderOther<'a> {
pub name: &'a str,
pub value: &'a [u8],
}
#[derive(Debug)]
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum Header<'a> {
Special(HeaderSpecial),
Other(HeaderOther<'a>),

37
crates/uhttp/src/util.rs Normal file
View file

@ -0,0 +1,37 @@
use crate::{ErrorKind, Write};
pub trait ResultTupExt<A, T, B, E> {
fn map2<U>(self, f: impl FnOnce(T) -> U) -> Result<(A, U), (B, E)>;
}
impl<A, T, B, E> ResultTupExt<A, T, B, E> for Result<(A, T), (B, E)> {
fn map2<U>(self, f: impl FnOnce(T) -> U) -> Result<(A, U), (B, E)> {
match self {
Ok((a, t)) => Ok((a, f(t))),
Err((b, e)) => Err((b, e)),
}
}
}
pub trait Tup<T, E> {
fn tup<V>(self, v: V) -> Result<(V, T), (V, E)>;
}
impl<T, E> Tup<T, E> for Result<T, E> {
fn tup<V>(self, v: V) -> Result<(V, T), (V, E)> {
match self {
Ok(t) => Ok((v, t)),
Err(e) => Err((v, e)),
}
}
}
pub trait Lift<V, T, E> {
fn lift(self) -> (V, Result<T, E>);
}
impl<V, T, E> Lift<V, T, E> for Result<(V, T), (V, E)> {
fn lift(self) -> (V, Result<T, E>) {
match self {
Ok((v, t)) => (v, Ok(t)),
Err((v, e)) => (v, Err(e)),
}
}
}

118
crates/uhttp/src/write.rs Normal file
View file

@ -0,0 +1,118 @@
use core::fmt::Arguments;
use crate::{Error, ErrorKind, Header, HeaderOther, HeaderSpecial, StatusLine};
pub struct FmtWriteAdapter<T> {
inner: T,
err: Written,
}
impl<T> FmtWriteAdapter<T> {
pub fn new(inner: T) -> Self {
Self { inner, err: Ok(()) }
}
}
impl<T> core::fmt::Write for FmtWriteAdapter<T>
where
T: Write + Sized,
{
fn write_str(&mut self, s: &str) -> core::fmt::Result {
self.inner.write(s.as_bytes()).map_err(|e| {
self.err = Err(e);
core::fmt::Error
})
}
}
pub trait Write {
fn write(&mut self, buf: &[u8]) -> Written;
fn write_fmt(&mut self, args: Arguments<'_>) -> Written
where
Self: Sized,
{
let mut adp = FmtWriteAdapter::new(self);
let _ = core::fmt::Write::write_fmt(&mut adp, args);
adp.err
}
}
impl<T> Write for &mut T
where
T: Write,
{
fn write(&mut self, buf: &[u8]) -> Written {
(**self).write(buf)
}
}
pub struct WriteCursor<T> {
at: usize,
buf: T,
}
impl<T> WriteCursor<T> {
pub fn new(buf: T) -> Self {
Self { at: 0, buf }
}
pub fn reset(&mut self) {
self.at = 0;
}
pub fn remaining_mut(&mut self) -> &mut [u8]
where
T: AsMut<[u8]>,
{
&mut self.buf.as_mut()[self.at..]
}
pub fn written(&mut self) -> &[u8]
where
T: AsRef<[u8]>,
{
&self.buf.as_ref()[..self.at]
}
}
impl<T> Write for WriteCursor<T>
where
T: AsMut<[u8]>,
{
fn write(&mut self, buf: &[u8]) -> Written {
let remaining = self.remaining_mut();
if buf.len() > remaining.len() {
return Err(Error::from(ErrorKind::BufNotBigEnough));
}
remaining[..buf.len()].copy_from_slice(buf);
self.at += buf.len();
Ok(())
}
}
pub type Written = Result<(), Error>;
pub fn status_line(sl: &StatusLine, mut w: impl Write) -> Written {
write!(
w,
"{} {} {}\r\n",
sl.version, sl.status_code, sl.status_text
)
}
pub fn header(h: &Header, mut w: impl Write) -> Written {
match h {
Header::Special(h) => match h {
HeaderSpecial::TransferEncodingChunked => write!(w, "Transfer-Encoding: Chunked"),
HeaderSpecial::ContentLength(cl) => write!(w, "Content-Length: {}", cl),
},
Header::Other(HeaderOther { name, value }) => {
write!(w, "{name}: ")?;
w.write(value)
},
}?;
write!(w, "\r\n")
}

View file

@ -1,5 +1,5 @@
edition = "2021"
hard_tabs = true
match_block_trailing_comma = true
max_width = 80
max_width = 95
empty_item_single_line = false

View file

@ -1,34 +1,3 @@
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);
},
},
}
}
}
fn main() {
println!("hello, world");
}