.
This commit is contained in:
parent
40177f03e5
commit
523bbd2e4c
40
Cargo.lock
generated
40
Cargo.lock
generated
|
|
@ -3,15 +3,45 @@
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httplz"
|
name = "bytes"
|
||||||
version = "0.1.0"
|
version = "1.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fnv"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"httplz-ext",
|
"bytes",
|
||||||
|
"fnv",
|
||||||
|
"itoa",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httplz-ext"
|
name = "httplz"
|
||||||
version = "0.1.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"httplzx",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httplzx"
|
||||||
|
version = "0.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"http",
|
||||||
"httplz",
|
"httplz",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||||
|
|
|
||||||
14
Cargo.toml
14
Cargo.toml
|
|
@ -1,9 +1,17 @@
|
||||||
[package]
|
[package]
|
||||||
name = "httplz"
|
name = "httplz"
|
||||||
version = "0.1.0"
|
version = "0.0.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
license-file = ".gitignore"
|
||||||
|
description = "A sans-io, no-std HTTP implementation"
|
||||||
|
|
||||||
[dependencies]
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"crates/httplzx"
|
||||||
|
]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
std = []
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
httplz-ext = { path = "./crates/httplz-ext" }
|
httplzx = { path = "./crates/httplzx" }
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "httplz-ext"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
httplz = { path = "../.." }
|
|
||||||
|
|
@ -1,116 +0,0 @@
|
||||||
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);
|
|
||||||
self.written_len += s.len();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
self.written_len = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop_front_alloc(&mut self, amt: usize)
|
|
||||||
where
|
|
||||||
U: Copy + Default,
|
|
||||||
{
|
|
||||||
let to_copy = &self.filled()[amt..];
|
|
||||||
let buf_sz = to_copy.len();
|
|
||||||
let mut buf = vec![Default::default(); buf_sz].into_boxed_slice();
|
|
||||||
buf.copy_from_slice(to_copy);
|
|
||||||
self.clear();
|
|
||||||
let _ = self.extend_from_slice(&buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
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> httplz::Write for Buf<T, u8>
|
|
||||||
where
|
|
||||||
T: AsRef<[u8]> + AsMut<[u8]>,
|
|
||||||
{
|
|
||||||
fn write(&mut self, buf: &[u8]) -> httplz::Written {
|
|
||||||
self.extend_from_slice(buf)
|
|
||||||
.map_err(|_| httplz::ErrorKind::BufNotBigEnough.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
17
crates/httplzx/Cargo.toml
Normal file
17
crates/httplzx/Cargo.toml
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "httplzx"
|
||||||
|
version = "0.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
license-file = "Cargo.toml"
|
||||||
|
description = "Extensions for httplz"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
httplz = { path = "../..", version = "0.0.0" }
|
||||||
|
|
||||||
|
http = { version = "*", optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["utils", "compat-http"]
|
||||||
|
utils = ["utils-buf"]
|
||||||
|
utils-buf = []
|
||||||
|
compat-http = ["dep:http"]
|
||||||
22
crates/httplzx/src/compat_http.rs
Normal file
22
crates/httplzx/src/compat_http.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
use httplz::{Event, Header, StatusLine};
|
||||||
|
|
||||||
|
use crate::ToEvents;
|
||||||
|
|
||||||
|
impl ToEvents for http::response::Parts {
|
||||||
|
fn to_events(&self) -> Vec<Event> {
|
||||||
|
let mut out = vec![StatusLine {
|
||||||
|
version: httplz::Version::HTTP1_1,
|
||||||
|
status_code: self.status.as_u16(),
|
||||||
|
status_text: self.status.canonical_reason().unwrap_or(""),
|
||||||
|
}
|
||||||
|
.into()];
|
||||||
|
|
||||||
|
out.extend(
|
||||||
|
self.headers
|
||||||
|
.iter()
|
||||||
|
.map(|(n, v)| Event::from(Header::from((n, v)))),
|
||||||
|
);
|
||||||
|
|
||||||
|
out
|
||||||
|
}
|
||||||
|
}
|
||||||
11
crates/httplzx/src/lib.rs
Normal file
11
crates/httplzx/src/lib.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
use httplz::Event;
|
||||||
|
|
||||||
|
#[cfg(feature = "utils")]
|
||||||
|
pub mod utils;
|
||||||
|
|
||||||
|
#[cfg(feature = "compat-http")]
|
||||||
|
pub mod compat_http;
|
||||||
|
|
||||||
|
pub trait ToEvents {
|
||||||
|
fn to_events(&self) -> Vec<Event>;
|
||||||
|
}
|
||||||
1
crates/httplzx/src/utils/buf.rs
Normal file
1
crates/httplzx/src/utils/buf.rs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
2
crates/httplzx/src/utils/mod.rs
Normal file
2
crates/httplzx/src/utils/mod.rs
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
#[cfg(feature = "utils-buf")]
|
||||||
|
pub mod buf;
|
||||||
|
|
@ -44,7 +44,7 @@ pub fn main() -> Result<(), Box<dyn Error>> {
|
||||||
buf.clear();
|
buf.clear();
|
||||||
loop {
|
loop {
|
||||||
let data = buf.filled();
|
let data = buf.filled();
|
||||||
let (d, r) = conn.handle_recv(data).lift();
|
let (d, r) = conn.poll_recv(data).lift();
|
||||||
|
|
||||||
match r {
|
match r {
|
||||||
Err(httplz::Error {
|
Err(httplz::Error {
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let data = buf.filled();
|
let data = buf.filled();
|
||||||
let (remaining, r) = conn.handle_recv(data).lift();
|
let (remaining, r) = conn.poll_recv(data).lift();
|
||||||
match r.map_err(|e| e.kind) {
|
match r.map_err(|e| e.kind) {
|
||||||
Err(httplz::ErrorKind::NeedMoreData) => {
|
Err(httplz::ErrorKind::NeedMoreData) => {
|
||||||
buf.read_from(&mut stream)?;
|
buf.read_from(&mut stream)?;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||||
pub enum ErrorKind {
|
pub enum ErrorKind {
|
||||||
NeedMoreData,
|
NeedMoreData,
|
||||||
InvalidConnectionState,
|
InvalidConnectionState,
|
||||||
|
|
@ -9,7 +9,7 @@ pub enum ErrorKind {
|
||||||
BodySizeMismatch,
|
BodySizeMismatch,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||||
pub struct Error {
|
pub struct Error {
|
||||||
pub kind: ErrorKind,
|
pub kind: ErrorKind,
|
||||||
pub details: &'static str,
|
pub details: &'static str,
|
||||||
|
|
|
||||||
47
src/lib.rs
47
src/lib.rs
|
|
@ -1,4 +1,5 @@
|
||||||
#![no_std]
|
#![cfg_attr(not(feature = "std"), no_std)]
|
||||||
|
|
||||||
pub mod common;
|
pub mod common;
|
||||||
pub mod parse;
|
pub mod parse;
|
||||||
pub mod parts;
|
pub mod parts;
|
||||||
|
|
@ -6,7 +7,7 @@ pub mod util;
|
||||||
pub mod write;
|
pub mod write;
|
||||||
|
|
||||||
pub use common::*;
|
pub use common::*;
|
||||||
pub use parse::Parse;
|
pub use parse::{NeedsMoreData, Parse};
|
||||||
pub use parts::*;
|
pub use parts::*;
|
||||||
pub use util::*;
|
pub use util::*;
|
||||||
pub use write::{Write, WriteCursor, Written};
|
pub use write::{Write, WriteCursor, Written};
|
||||||
|
|
@ -24,18 +25,6 @@ pub enum Event<'a> {
|
||||||
SendDone,
|
SendDone,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<HeaderSpecial> for Event<'a> {
|
|
||||||
fn from(value: HeaderSpecial) -> Self {
|
|
||||||
Self::Header(value.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<HeaderOther<'a>> for Event<'a> {
|
|
||||||
fn from(value: HeaderOther<'a>) -> Self {
|
|
||||||
Self::Header(value.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<Header<'a>> for Event<'a> {
|
impl<'a> From<Header<'a>> for Event<'a> {
|
||||||
fn from(value: Header<'a>) -> Self {
|
fn from(value: Header<'a>) -> Self {
|
||||||
Self::Header(value)
|
Self::Header(value)
|
||||||
|
|
@ -63,7 +52,7 @@ impl<'a> core::fmt::Display for Event<'a> {
|
||||||
write!(f, "{} {} {}", s.version, s.status_code, s.status_text)
|
write!(f, "{} {} {}", s.version, s.status_code, s.status_text)
|
||||||
},
|
},
|
||||||
Event::Header(h) => match h {
|
Event::Header(h) => match h {
|
||||||
Header::Other(HeaderOther { name, value }) => {
|
Header::Other { name, value } => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"{}: {}",
|
"{}: {}",
|
||||||
|
|
@ -71,11 +60,9 @@ impl<'a> core::fmt::Display for Event<'a> {
|
||||||
core::str::from_utf8(value).unwrap_or("<binary>")
|
core::str::from_utf8(value).unwrap_or("<binary>")
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
Header::Special(h) => match h {
|
Header::ContentLength(cl) => write!(f, "Content-Length: {cl}"),
|
||||||
HeaderSpecial::ContentLength(cl) => write!(f, "Content-Length: {cl}"),
|
Header::TransferEncodingChunked => {
|
||||||
HeaderSpecial::TransferEncodingChunked => {
|
write!(f, "Transfer-Encoding: chunked")
|
||||||
write!(f, "Transfer-Encoding: chunked")
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Event::BodyChunk(b) => {
|
Event::BodyChunk(b) => {
|
||||||
|
|
@ -142,7 +129,7 @@ impl Connection {
|
||||||
matches!(self.state, StateConnection::Recv(_))
|
matches!(self.state, StateConnection::Recv(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_recv<'a>(&mut self, bytes: &'a [u8]) -> Parse<'a, Event<'a>> {
|
pub fn poll_recv<'a>(&mut self, bytes: &'a [u8]) -> Parse<'a, Event<'a>> {
|
||||||
let recv = match &self.state {
|
let recv = match &self.state {
|
||||||
StateConnection::Recv(r) => r,
|
StateConnection::Recv(r) => r,
|
||||||
_ => {
|
_ => {
|
||||||
|
|
@ -180,12 +167,8 @@ impl Connection {
|
||||||
} else {
|
} else {
|
||||||
parse::header(bytes).map2(|h| {
|
parse::header(bytes).map2(|h| {
|
||||||
let b = match h {
|
let b = match h {
|
||||||
Header::Special(HeaderSpecial::TransferEncodingChunked) => {
|
Header::TransferEncodingChunked => Some(BodyState::Chunked),
|
||||||
Some(BodyState::Chunked)
|
Header::ContentLength(c) => Some(BodyState::ContentLength(c)),
|
||||||
},
|
|
||||||
Header::Special(HeaderSpecial::ContentLength(c)) => {
|
|
||||||
Some(BodyState::ContentLength(c))
|
|
||||||
},
|
|
||||||
_ => *body_state,
|
_ => *body_state,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -265,13 +248,9 @@ impl Connection {
|
||||||
write::header(h, w)?;
|
write::header(h, w)?;
|
||||||
|
|
||||||
let bs = match h {
|
let bs = match h {
|
||||||
Header::Other(_) => body_state,
|
Header::Other { .. } => body_state,
|
||||||
Header::Special(h) => match h {
|
Header::TransferEncodingChunked => Some(BodyState::Chunked),
|
||||||
HeaderSpecial::TransferEncodingChunked => Some(BodyState::Chunked),
|
Header::ContentLength(cl) => Some(BodyState::ContentLength(*cl)),
|
||||||
HeaderSpecial::ContentLength(cl) => {
|
|
||||||
Some(BodyState::ContentLength(*cl))
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(StateConnection::Send(StateSend::Headers(bs)))
|
Ok(StateConnection::Send(StateSend::Headers(bs)))
|
||||||
|
|
|
||||||
23
src/parse.rs
23
src/parse.rs
|
|
@ -1,10 +1,30 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
fail_details,
|
fail_details,
|
||||||
parts::{RequestLine, Version},
|
parts::{RequestLine, Version},
|
||||||
Error, ErrorKind, Header, StatusLine, Tup,
|
Error, ErrorKind, Header, Method, StatusLine, Tup,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type Parse<'a, T> = Result<(&'a [u8], T), (&'a [u8], Error)>;
|
pub type Parse<'a, T> = Result<(&'a [u8], T), (&'a [u8], Error)>;
|
||||||
|
pub trait NeedsMoreData {
|
||||||
|
fn needs_more_data(&self) -> bool;
|
||||||
|
}
|
||||||
|
impl<'a, T> NeedsMoreData for Parse<'a, T> {
|
||||||
|
fn needs_more_data(&self) -> bool {
|
||||||
|
self.as_ref()
|
||||||
|
.err()
|
||||||
|
.map(|e| e.1.kind)
|
||||||
|
.is_some_and(|e| e == ErrorKind::NeedMoreData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> NeedsMoreData for Result<T, Error> {
|
||||||
|
fn needs_more_data(&self) -> bool {
|
||||||
|
self.as_ref()
|
||||||
|
.err()
|
||||||
|
.map(|e| e.kind)
|
||||||
|
.is_some_and(|e| e == ErrorKind::NeedMoreData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn split_crlf(d: &[u8]) -> Option<(&[u8], &[u8])> {
|
pub fn split_crlf(d: &[u8]) -> Option<(&[u8], &[u8])> {
|
||||||
let p = d.windows(2).position(|w| w == b"\r\n")?;
|
let p = d.windows(2).position(|w| w == b"\r\n")?;
|
||||||
|
|
@ -36,6 +56,7 @@ pub fn request_line(d: &[u8]) -> Parse<RequestLine> {
|
||||||
return fail_details(ErrorKind::Parse, "expected method to be ascii");
|
return fail_details(ErrorKind::Parse, "expected method to be ascii");
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
let method = Method::new_from_str(method);
|
||||||
|
|
||||||
let target = match core::str::from_utf8(target) {
|
let target = match core::str::from_utf8(target) {
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
|
|
|
||||||
105
src/parts.rs
105
src/parts.rs
|
|
@ -10,9 +10,60 @@ impl core::fmt::Display for Version {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
|
pub enum Method<'a> {
|
||||||
|
Get,
|
||||||
|
Head,
|
||||||
|
Options,
|
||||||
|
Trace,
|
||||||
|
Put,
|
||||||
|
Delete,
|
||||||
|
Post,
|
||||||
|
Patch,
|
||||||
|
Connect,
|
||||||
|
Other(&'a str),
|
||||||
|
}
|
||||||
|
impl<'a> Method<'a> {
|
||||||
|
pub fn new_from_str(s: &'a str) -> Self {
|
||||||
|
match s {
|
||||||
|
_ if s.eq_ignore_ascii_case("get") => Method::Get,
|
||||||
|
_ if s.eq_ignore_ascii_case("head") => Method::Head,
|
||||||
|
_ if s.eq_ignore_ascii_case("options") => Method::Options,
|
||||||
|
_ if s.eq_ignore_ascii_case("trace") => Method::Trace,
|
||||||
|
_ if s.eq_ignore_ascii_case("put") => Method::Put,
|
||||||
|
_ if s.eq_ignore_ascii_case("delete") => Method::Delete,
|
||||||
|
_ if s.eq_ignore_ascii_case("post") => Method::Post,
|
||||||
|
_ if s.eq_ignore_ascii_case("patch") => Method::Patch,
|
||||||
|
_ if s.eq_ignore_ascii_case("connect") => Method::Connect,
|
||||||
|
s => Method::Other(s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> core::fmt::Display for Method<'a> {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
use Method::*;
|
||||||
|
|
||||||
|
let s = match self {
|
||||||
|
Get => "GET",
|
||||||
|
Head => "HEAD",
|
||||||
|
Options => "OPTIONS",
|
||||||
|
Trace => "TRACE",
|
||||||
|
Put => "PUT",
|
||||||
|
Delete => "DELETE",
|
||||||
|
Post => "POST",
|
||||||
|
Patch => "PATCH",
|
||||||
|
Connect => "CONNECT",
|
||||||
|
Other(s) => s,
|
||||||
|
};
|
||||||
|
|
||||||
|
write!(f, "{s}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
pub struct RequestLine<'a> {
|
pub struct RequestLine<'a> {
|
||||||
pub method: &'a str,
|
pub method: Method<'a>,
|
||||||
pub target: &'a str,
|
pub target: &'a str,
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
}
|
}
|
||||||
|
|
@ -24,49 +75,11 @@ pub struct StatusLine<'a> {
|
||||||
pub status_text: &'a str,
|
pub status_text: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Headers that impact the state of the connection
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
|
||||||
pub enum HeaderSpecial {
|
|
||||||
TransferEncodingChunked,
|
|
||||||
ContentLength(usize),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
|
||||||
pub struct HeaderOther<'a> {
|
|
||||||
pub name: &'a str,
|
|
||||||
pub value: &'a [u8],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, Name, Value> From<(&'a Name, &'a Value)> for HeaderOther<'a>
|
|
||||||
where
|
|
||||||
Name: AsRef<str> + ?Sized,
|
|
||||||
Value: AsRef<[u8]> + ?Sized,
|
|
||||||
{
|
|
||||||
fn from(value: (&'a Name, &'a Value)) -> Self {
|
|
||||||
let (name, value) = value;
|
|
||||||
let name = name.as_ref();
|
|
||||||
let value = value.as_ref();
|
|
||||||
|
|
||||||
Self { name, value }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
pub enum Header<'a> {
|
pub enum Header<'a> {
|
||||||
Special(HeaderSpecial),
|
TransferEncodingChunked,
|
||||||
Other(HeaderOther<'a>),
|
ContentLength(usize),
|
||||||
}
|
Other { name: &'a str, value: &'a [u8] },
|
||||||
|
|
||||||
impl<'a> From<HeaderSpecial> for Header<'a> {
|
|
||||||
fn from(value: HeaderSpecial) -> Self {
|
|
||||||
Self::Special(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<HeaderOther<'a>> for Header<'a> {
|
|
||||||
fn from(value: HeaderOther<'a>) -> Self {
|
|
||||||
Self::Other(value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, Name, Value> From<(&'a Name, &'a Value)> for Header<'a>
|
impl<'a, Name, Value> From<(&'a Name, &'a Value)> for Header<'a>
|
||||||
|
|
@ -83,18 +96,18 @@ where
|
||||||
_ if name.eq_ignore_ascii_case("transfer-encoding")
|
_ if name.eq_ignore_ascii_case("transfer-encoding")
|
||||||
&& value.eq_ignore_ascii_case(b"chunked") =>
|
&& value.eq_ignore_ascii_case(b"chunked") =>
|
||||||
{
|
{
|
||||||
Header::Special(HeaderSpecial::TransferEncodingChunked)
|
Header::TransferEncodingChunked
|
||||||
},
|
},
|
||||||
_ if name.eq_ignore_ascii_case("content-length") => {
|
_ if name.eq_ignore_ascii_case("content-length") => {
|
||||||
match core::str::from_utf8(value)
|
match core::str::from_utf8(value)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|s| s.parse().ok())
|
.and_then(|s| s.parse().ok())
|
||||||
{
|
{
|
||||||
Some(v) => Header::Special(HeaderSpecial::ContentLength(v)),
|
Some(v) => Header::ContentLength(v),
|
||||||
_ => Header::Other(HeaderOther { name, value }),
|
_ => Header::Other { name, value },
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ => Header::Other(HeaderOther { name, value }),
|
_ => Header::Other { name, value },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
132
src/util.rs
132
src/util.rs
|
|
@ -1,3 +1,5 @@
|
||||||
|
use crate::{ErrorKind, Write, Written};
|
||||||
|
|
||||||
pub trait ResultTupExt<A, T, B, E> {
|
pub trait ResultTupExt<A, T, B, E> {
|
||||||
fn map2<U>(self, f: impl FnOnce(T) -> U) -> Result<(A, U), (B, E)>;
|
fn map2<U>(self, f: impl FnOnce(T) -> U) -> Result<(A, U), (B, E)>;
|
||||||
}
|
}
|
||||||
|
|
@ -33,3 +35,133 @@ impl<V, T, E> Lift<V, T, E> for Result<(V, T), (V, E)> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
self.written_len += s.len();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.written_len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
mod buf_std_impls {
|
||||||
|
use crate::Buf;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::io::Result as IOResult;
|
||||||
|
|
||||||
|
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, U> Buf<T, U>
|
||||||
|
where
|
||||||
|
T: AsRef<[U]>,
|
||||||
|
T: AsMut<[U]>,
|
||||||
|
{
|
||||||
|
pub fn pop_front_alloc(&mut self, amt: usize)
|
||||||
|
where
|
||||||
|
U: Copy + Default,
|
||||||
|
{
|
||||||
|
let to_copy = &self.filled()[amt..];
|
||||||
|
let buf_sz = to_copy.len();
|
||||||
|
let mut buf = vec![Default::default(); buf_sz].into_boxed_slice();
|
||||||
|
buf.copy_from_slice(to_copy);
|
||||||
|
self.clear();
|
||||||
|
let _ = self.extend_from_slice(&buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
pub use buf_std_impls::*;
|
||||||
|
|
||||||
|
impl<T> Write for Buf<T, u8>
|
||||||
|
where
|
||||||
|
T: AsRef<[u8]> + AsMut<[u8]>,
|
||||||
|
{
|
||||||
|
fn write(&mut self, buf: &[u8]) -> Written {
|
||||||
|
self.extend_from_slice(buf)
|
||||||
|
.map_err(|_| ErrorKind::BufNotBigEnough.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
10
src/write.rs
10
src/write.rs
|
|
@ -1,6 +1,6 @@
|
||||||
use core::fmt::Arguments;
|
use core::fmt::Arguments;
|
||||||
|
|
||||||
use crate::{Error, ErrorKind, Header, HeaderOther, HeaderSpecial, RequestLine, StatusLine};
|
use crate::{Error, ErrorKind, Header, RequestLine, StatusLine};
|
||||||
|
|
||||||
pub struct FmtWriteAdapter<T> {
|
pub struct FmtWriteAdapter<T> {
|
||||||
inner: T,
|
inner: T,
|
||||||
|
|
@ -108,11 +108,9 @@ pub fn request_line(rl: &RequestLine, mut w: impl Write) -> Written {
|
||||||
|
|
||||||
pub fn header(h: &Header, mut w: impl Write) -> Written {
|
pub fn header(h: &Header, mut w: impl Write) -> Written {
|
||||||
match h {
|
match h {
|
||||||
Header::Special(h) => match h {
|
Header::TransferEncodingChunked => write!(w, "Transfer-Encoding: Chunked"),
|
||||||
HeaderSpecial::TransferEncodingChunked => write!(w, "Transfer-Encoding: Chunked"),
|
Header::ContentLength(cl) => write!(w, "Content-Length: {}", cl),
|
||||||
HeaderSpecial::ContentLength(cl) => write!(w, "Content-Length: {}", cl),
|
Header::Other { name, value } => {
|
||||||
},
|
|
||||||
Header::Other(HeaderOther { name, value }) => {
|
|
||||||
write!(w, "{name}: ")?;
|
write!(w, "{name}: ")?;
|
||||||
w.write(value)
|
w.write(value)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue