From cbaf050ba5f5be8728e39c8bace69fc4cfb61412 Mon Sep 17 00:00:00 2001 From: Calvin Prewitt Date: Fri, 20 Sep 2024 12:08:36 -0500 Subject: [PATCH 1/8] added `read_to_end` to `AsyncRead` trait --- .gitignore | 2 ++ src/http/client.rs | 7 ++----- src/http/fields.rs | 2 +- src/http/response.rs | 11 ++++++----- src/io/read.rs | 21 +++++++++++++++++++++ src/net/tcp_stream.rs | 20 ++++++++++++++++---- 6 files changed, 48 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index b75a144..c3bd750 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ target/ tmp/ Cargo.lock .DS_Store +*.swp +*.swo diff --git a/src/http/client.rs b/src/http/client.rs index 2afeba8..8f2510d 100644 --- a/src/http/client.rs +++ b/src/http/client.rs @@ -27,7 +27,7 @@ impl<'a> Client<'a> { let res = wasi::http::outgoing_handler::handle(wasi_req, None).unwrap(); // 2. Start sending the request body - io::copy(body, OutputStream::new(&self.reactor, body_stream)) + io::copy(body, OutputStream::new(self.reactor, body_stream)) .await .expect("io::copy broke oh no"); @@ -41,10 +41,7 @@ impl<'a> Client<'a> { // is to trap if we try and get the response more than once. The final // `?` is to raise the actual error if there is one. let res = res.get().unwrap().unwrap()?; - Ok(Response::try_from_incoming_response( - res, - self.reactor.clone(), - )?) + Response::try_from_incoming_response(res, self.reactor.clone()) } } diff --git a/src/http/fields.rs b/src/http/fields.rs index 71c1dc9..24f6c53 100644 --- a/src/http/fields.rs +++ b/src/http/fields.rs @@ -70,6 +70,6 @@ impl TryFrom for WasiFields { list.push((name.clone().into_owned(), value)); } } - Ok(WasiFields::from_list(&list)?) + WasiFields::from_list(&list) } } diff --git a/src/http/response.rs b/src/http/response.rs index 68b5d3e..62b80d5 100644 --- a/src/http/response.rs +++ b/src/http/response.rs @@ -122,12 +122,13 @@ impl AsyncRead for IncomingBody { self.reactor.wait_for(pollable).await; // Read the bytes from the body stream - let buf = self.body_stream.read(CHUNK_SIZE).map_err(|err| match err { - StreamError::LastOperationFailed(err) => { - std::io::Error::other(format!("{}", err.to_debug_string())) + let buf = match self.body_stream.read(CHUNK_SIZE) { + Ok(buf) => buf, + Err(StreamError::Closed) => return Ok(0), + Err(StreamError::LastOperationFailed(err)) => { + return Err(std::io::Error::other(err.to_debug_string())); } - StreamError::Closed => std::io::Error::other("Connection closed"), - })?; + }; self.buf.insert(buf) } }; diff --git a/src/io/read.rs b/src/io/read.rs index be54fcb..e8c3294 100644 --- a/src/io/read.rs +++ b/src/io/read.rs @@ -1,6 +1,27 @@ use crate::io; +const CHUNK_SIZE: usize = 2048; + /// Read bytes from a source. pub trait AsyncRead { async fn read(&mut self, buf: &mut [u8]) -> io::Result; + async fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { + // total bytes written to buf + let mut n = 0; + + loop { + // grow buf, if less than default chuck size + if buf.len() < n + CHUNK_SIZE { + buf.resize(n + CHUNK_SIZE, 0u8); + } + + let len = self.read(&mut buf[n..]).await?; + if len == 0 { + buf.truncate(n); + return Ok(n); + } + + n += len; + } + } } diff --git a/src/net/tcp_stream.rs b/src/net/tcp_stream.rs index 912f409..414e084 100644 --- a/src/net/tcp_stream.rs +++ b/src/net/tcp_stream.rs @@ -32,9 +32,15 @@ impl<'a> TcpStream<'a> { impl<'a> AsyncRead for TcpStream<'a> { async fn read(&mut self, buf: &mut [u8]) -> io::Result { self.reactor.wait_for(self.input.subscribe()).await; - let slice = self.input.read(buf.len() as u64).map_err(to_io_err)?; + let slice = match self.input.read(buf.len() as u64) { + Ok(slice) => slice, + Err(StreamError::Closed) => return Ok(0), + Err(StreamError::LastOperationFailed(err)) => { + return Err(Error::other(err.to_debug_string())); + } + }; let bytes_read = slice.len(); - buf[..bytes_read].clone_from_slice(&slice); + buf[..bytes_read].copy_from_slice(&slice); Ok(bytes_read) } } @@ -42,9 +48,15 @@ impl<'a> AsyncRead for TcpStream<'a> { impl<'a> AsyncRead for &TcpStream<'a> { async fn read(&mut self, buf: &mut [u8]) -> io::Result { self.reactor.wait_for(self.input.subscribe()).await; - let slice = self.input.read(buf.len() as u64).map_err(to_io_err)?; + let slice = match self.input.read(buf.len() as u64) { + Ok(slice) => slice, + Err(StreamError::Closed) => return Ok(0), + Err(StreamError::LastOperationFailed(err)) => { + return Err(Error::other(err.to_debug_string())); + } + }; let bytes_read = slice.len(); - buf[..bytes_read].clone_from_slice(&slice); + buf[..bytes_read].copy_from_slice(&slice); Ok(bytes_read) } } From 00909fe5e01f553896cf7e7f69a2504354728375 Mon Sep 17 00:00:00 2001 From: Calvin Prewitt Date: Fri, 20 Sep 2024 12:17:08 -0500 Subject: [PATCH 2/8] removed http incoming body buffer --- src/http/response.rs | 53 +++++++++++--------------------------------- 1 file changed, 13 insertions(+), 40 deletions(-) diff --git a/src/http/response.rs b/src/http/response.rs index 62b80d5..901d8ff 100644 --- a/src/http/response.rs +++ b/src/http/response.rs @@ -5,9 +5,6 @@ use super::{Body, Headers, StatusCode}; use crate::io::AsyncRead; use crate::runtime::Reactor; -/// Stream 2kb chunks at a time -const CHUNK_SIZE: u64 = 2048; - /// An HTTP response #[derive(Debug)] pub struct Response { @@ -62,8 +59,6 @@ impl Response { .expect("cannot call `stream` twice on an incoming body"); let body = IncomingBody { - buf_offset: 0, - buf: None, reactor, body_stream, _incoming_body: incoming_body, @@ -102,9 +97,6 @@ impl Response { #[derive(Debug)] pub struct IncomingBody { reactor: Reactor, - buf: Option>, - // How many bytes have we already read from the buf? - buf_offset: usize, // IMPORTANT: the order of these fields here matters. `incoming_body` must // be dropped before `body_stream`. @@ -113,39 +105,20 @@ pub struct IncomingBody { } impl AsyncRead for IncomingBody { - async fn read(&mut self, out_buf: &mut [u8]) -> crate::io::Result { - let buf = match &mut self.buf { - Some(ref mut buf) => buf, - None => { - // Wait for an event to be ready - let pollable = self.body_stream.subscribe(); - self.reactor.wait_for(pollable).await; - - // Read the bytes from the body stream - let buf = match self.body_stream.read(CHUNK_SIZE) { - Ok(buf) => buf, - Err(StreamError::Closed) => return Ok(0), - Err(StreamError::LastOperationFailed(err)) => { - return Err(std::io::Error::other(err.to_debug_string())); - } - }; - self.buf.insert(buf) + async fn read(&mut self, buf: &mut [u8]) -> crate::io::Result { + // Wait for an event to be ready + self.reactor.wait_for(self.body_stream.subscribe()).await; + + // Read the bytes from the body stream + let slice = match self.body_stream.read(buf.len() as u64) { + Ok(slice) => slice, + Err(StreamError::Closed) => return Ok(0), + Err(StreamError::LastOperationFailed(err)) => { + return Err(std::io::Error::other(err.to_debug_string())); } }; - - // copy bytes - let len = (buf.len() - self.buf_offset).min(out_buf.len()); - let max = self.buf_offset + len; - let slice = &buf[self.buf_offset..max]; - out_buf[0..len].copy_from_slice(slice); - self.buf_offset += len; - - // reset the local slice if necessary - if self.buf_offset == buf.len() { - self.buf = None; - self.buf_offset = 0; - } - - Ok(len) + let bytes_read = slice.len(); + buf[..bytes_read].copy_from_slice(&slice); + Ok(bytes_read) } } From f1fa9a3e7f71470dc461ac8d1a389035ee2cd1bc Mon Sep 17 00:00:00 2001 From: Calvin Prewitt Date: Sat, 21 Sep 2024 12:04:13 -0500 Subject: [PATCH 3/8] updates --- src/http/fields.rs | 11 ++- src/http/response.rs | 162 ++++++++++++++++++++++++++++--------------- src/io/read.rs | 4 +- 3 files changed, 115 insertions(+), 62 deletions(-) diff --git a/src/http/fields.rs b/src/http/fields.rs index 24f6c53..6ad4bf3 100644 --- a/src/http/fields.rs +++ b/src/http/fields.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, collections::HashMap, ops::Deref}; +use std::{collections::HashMap, ops::Deref}; use wasi::http::types::{Fields as WasiFields, HeaderError}; /// A type alias for [`Fields`] when used as HTTP headers. @@ -8,7 +8,7 @@ pub type Headers = Fields; pub type Trailers = Fields; /// An HTTP Field name. -pub type FieldName = Cow<'static, str>; +pub type FieldName = String; /// An HTTP Field value. pub type FieldValue = Vec; @@ -18,7 +18,7 @@ pub type FieldValue = Vec; pub struct Fields(pub(crate) HashMap>); impl Fields { - pub fn get(&self, k: &FieldName) -> Option<&[FieldValue]> { + pub fn get(&self, k: &str) -> Option<&[FieldValue]> { self.0.get(k).map(|f| f.deref()) } } @@ -52,8 +52,7 @@ impl std::fmt::Debug for Fields { impl From for Fields { fn from(wasi_fields: WasiFields) -> Self { let mut output = HashMap::new(); - for (key, value) in wasi_fields.entries() { - let field_name = key.into(); + for (field_name, value) in wasi_fields.entries() { let field_list: &mut Vec<_> = output.entry(field_name).or_default(); field_list.push(value); } @@ -67,7 +66,7 @@ impl TryFrom for WasiFields { let mut list = Vec::with_capacity(fields.0.capacity()); for (name, values) in fields.0.into_iter() { for value in values { - list.push((name.clone().into_owned(), value)); + list.push((name.clone(), value)); } } WasiFields::from_list(&list) diff --git a/src/http/response.rs b/src/http/response.rs index 901d8ff..3e37a43 100644 --- a/src/http/response.rs +++ b/src/http/response.rs @@ -1,7 +1,7 @@ use wasi::http::types::{IncomingBody as WasiIncomingBody, IncomingResponse}; use wasi::io::streams::{InputStream, StreamError}; -use super::{Body, Headers, StatusCode}; +use super::{Body, Headers, StatusCode, Trailers}; use crate::io::AsyncRead; use crate::runtime::Reactor; @@ -13,55 +13,37 @@ pub struct Response { body: B, } -// #[derive(Debug)] -// enum BodyKind { -// Fixed(u64), -// Chunked, -// } - -// impl BodyKind { -// fn from_headers(headers: &Fields) -> BodyKind { -// dbg!(&headers); -// if let Some(values) = headers.0.get("content-length") { -// let value = values -// .get(0) -// .expect("no value found for content-length; violates HTTP/1.1"); -// let content_length = String::from_utf8(value.to_owned()) -// .unwrap() -// .parse::() -// .expect("content-length should be a u64; violates HTTP/1.1"); -// BodyKind::Fixed(content_length) -// } else if let Some(values) = headers.0.get("transfer-encoding") { -// dbg!(values); -// BodyKind::Chunked -// } else { -// dbg!("Encoding neither has a content-length nor transfer-encoding"); -// BodyKind::Chunked -// } -// } -// } - impl Response { pub(crate) fn try_from_incoming_response( - incoming: IncomingResponse, + incoming_response: IncomingResponse, reactor: Reactor, ) -> super::Result { - let headers: Headers = incoming.headers().into(); - let status = incoming.status().into(); + let headers: Headers = incoming_response.headers().into(); + let status = incoming_response.status().into(); // `body_stream` is a child of `incoming_body` which means we cannot - // drop the parent before we drop the child - let incoming_body = incoming + // drop the parent before we drop the child, + // which `incoming_body` is a child of `incoming_response`. + let incoming_body = incoming_response .consume() .expect("cannot call `consume` twice on incoming response"); let body_stream = incoming_body .stream() .expect("cannot call `stream` twice on an incoming body"); + let content_length = headers + .get("content-length") + .and_then(|vals| vals.first()) + .and_then(|v| std::str::from_utf8(v).ok()) + .and_then(|s| s.parse::().ok()); + let body = IncomingBody { + content_length, reactor, - body_stream, - _incoming_body: incoming_body, + trailers: None, + body_stream: Some(body_stream), + _incoming_body: Some(incoming_body), + _incoming_response: Some(incoming_response), }; Ok(Self { @@ -70,6 +52,15 @@ impl Response { status, }) } + + /// NEEDS wasmtime PR to be released: + /// https://github.com/bytecodealliance/wasmtime/pull/9208 + pub fn trailers(&self) -> Option<&Trailers> { + self.body.trailers.as_ref() + } + pub fn content_length(&self) -> Option { + self.body.content_length + } } impl Response { @@ -97,28 +88,91 @@ impl Response { #[derive(Debug)] pub struct IncomingBody { reactor: Reactor, + content_length: Option, + trailers: Option, + + // IMPORTANT: the order of these fields here matters. + // Rust drops `struct` fields in the order that they are defined in + // the source code. + // + // `body_stream` must be dropped before `incoming_body`, which + // must be dropped before `incoming_response`. + body_stream: Option, + _incoming_body: Option, + _incoming_response: Option, +} - // IMPORTANT: the order of these fields here matters. `incoming_body` must - // be dropped before `body_stream`. - body_stream: InputStream, - _incoming_body: WasiIncomingBody, +impl IncomingBody { + /// Get the full response body as `Vec`. + pub async fn bytes(&mut self) -> crate::io::Result> { + let mut buf = Vec::with_capacity(self.content_length.unwrap_or_default() as usize); + self.read_to_end(&mut buf).await?; + Ok(buf) + } } impl AsyncRead for IncomingBody { async fn read(&mut self, buf: &mut [u8]) -> crate::io::Result { - // Wait for an event to be ready - self.reactor.wait_for(self.body_stream.subscribe()).await; - - // Read the bytes from the body stream - let slice = match self.body_stream.read(buf.len() as u64) { - Ok(slice) => slice, - Err(StreamError::Closed) => return Ok(0), - Err(StreamError::LastOperationFailed(err)) => { - return Err(std::io::Error::other(err.to_debug_string())); - } - }; - let bytes_read = slice.len(); - buf[..bytes_read].copy_from_slice(&slice); - Ok(bytes_read) + if let Some(stream) = self.body_stream.as_mut() { + // Wait for an event to be ready + self.reactor.wait_for(stream.subscribe()).await; + + // Read the bytes from the body stream + let slice = match stream.read(buf.len() as u64) { + Ok(slice) => slice, + Err(StreamError::Closed) => { + // stream is done, follow drop order and finalize with trailers + + // drop `body_stream` + let stream = self.body_stream.take(); + drop(stream); + + drop( + self._incoming_body + .take() + .expect("IncomingBody is expected to be available"), + ); + + // finish `incoming_body` and get trailers + // NEEDS wasmtime PR to be released: + // https://github.com/bytecodealliance/wasmtime/pull/9208 + + //let incoming_trailers = WasiIncomingBody::finish( + // self._incoming_body + // .take() + // .expect("IncomingBody is expected to be available"), + //); + //self.reactor.wait_for(incoming_trailers.subscribe()).await; + //self.trailers = incoming_trailers + // .get() + // .unwrap() // succeeds since pollable is ready + // .unwrap() // succeeds since first time + // .or(Err(std::io::Error::other("Error receiving trailers")))? + // .map(|trailers| trailers.into()); + //drop(incoming_trailers); + + // drop `incoming_response` + let incoming_response = self + ._incoming_response + .take() + .expect("IncomingResponse is expected to be available"); + drop(incoming_response); + + // clear `content_length` + self.content_length = None; + + return Ok(0); + } + Err(StreamError::LastOperationFailed(err)) => { + return Err(std::io::Error::other(err.to_debug_string())); + } + }; + let bytes_read = slice.len(); + buf[..bytes_read].copy_from_slice(&slice); + Ok(bytes_read) + } else { + // stream is already closed + Ok(0) + } } } diff --git a/src/io/read.rs b/src/io/read.rs index e8c3294..82ae5a0 100644 --- a/src/io/read.rs +++ b/src/io/read.rs @@ -10,8 +10,8 @@ pub trait AsyncRead { let mut n = 0; loop { - // grow buf, if less than default chuck size - if buf.len() < n + CHUNK_SIZE { + // grow buf if empty + if buf.len() == n { buf.resize(n + CHUNK_SIZE, 0u8); } From ee267c3eba3c6c78026fa4886c51fc7b0ac473d7 Mon Sep 17 00:00:00 2001 From: Calvin Prewitt Date: Sat, 21 Sep 2024 12:58:53 -0500 Subject: [PATCH 4/8] updated headers --- src/http/client.rs | 2 +- src/http/fields.rs | 88 +++++++++++++++++++++++++++++++++++++-------- src/http/request.rs | 21 +++++++---- 3 files changed, 88 insertions(+), 23 deletions(-) diff --git a/src/http/client.rs b/src/http/client.rs index 8f2510d..33f5dea 100644 --- a/src/http/client.rs +++ b/src/http/client.rs @@ -19,7 +19,7 @@ impl<'a> Client<'a> { /// Send an HTTP request. pub async fn send(&self, req: Request) -> Result> { - let (wasi_req, body) = req.into_outgoing(); + let (wasi_req, body) = req.try_into_outgoing()?; let wasi_body = wasi_req.body().unwrap(); let body_stream = wasi_body.write().unwrap(); diff --git a/src/http/fields.rs b/src/http/fields.rs index 6ad4bf3..283306e 100644 --- a/src/http/fields.rs +++ b/src/http/fields.rs @@ -1,5 +1,6 @@ +use super::Error; use std::{collections::HashMap, ops::Deref}; -use wasi::http::types::{Fields as WasiFields, HeaderError}; +use wasi::http::types::{ErrorCode, Fields as WasiFields, HeaderError}; /// A type alias for [`Fields`] when used as HTTP headers. pub type Headers = Fields; @@ -13,22 +14,68 @@ pub type FieldName = String; /// An HTTP Field value. pub type FieldValue = Vec; +/// Field entry. +#[derive(Clone, PartialEq, Eq)] +pub(crate) struct FieldEntry { + /// Field key in original case. + key: String, + /// Field values. + values: Vec, +} + /// HTTP Fields which can be used as either trailers or headers. #[derive(Clone, PartialEq, Eq)] -pub struct Fields(pub(crate) HashMap>); +pub struct Fields(pub(crate) HashMap); impl Fields { - pub fn get(&self, k: &str) -> Option<&[FieldValue]> { - self.0.get(k).map(|f| f.deref()) + pub(crate) fn new() -> Self { + Self(HashMap::new()) + } + pub fn contains(&self, key: &str) -> bool { + self.0 + .get(key) + .is_some_and(|entry| !entry.values.is_empty()) + } + pub fn get(&self, key: &str) -> Option<&[FieldValue]> { + if key.chars().any(|c| c.is_uppercase()) { + self.0 + .get(&key.to_lowercase()) + .map(|entry| entry.values.deref()) + } else { + self.0.get(key).map(|entry| entry.values.deref()) + } + } + pub fn get_mut(&mut self, key: &str) -> Option<&mut Vec> { + if key.chars().any(|c| c.is_uppercase()) { + self.0 + .get_mut(&key.to_lowercase()) + .map(|entry| entry.values.as_mut()) + } else { + self.0.get_mut(key).map(|entry| entry.values.as_mut()) + } + } + pub fn insert(&mut self, key: String, values: Vec) { + self.0 + .insert(key.to_lowercase(), FieldEntry { key, values }); + } + pub fn append(&mut self, key: String, value: FieldValue) { + let entry: &mut FieldEntry = self.0.entry(key.to_lowercase()).or_insert(FieldEntry { + key, + values: Vec::with_capacity(1), + }); + entry.values.push(value); + } + pub fn remove(&mut self, key: &str) -> Option> { + self.0.remove(key).map(|entry| entry.values) } } impl std::fmt::Debug for Fields { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut map = f.debug_map(); - let mut entries: Vec<_> = self.0.iter().collect(); - entries.sort_by_cached_key(|(k, _)| k.to_owned()); - for (key, values) in entries { + let mut entries: Vec<_> = self.0.values().collect(); + entries.sort_by_cached_key(|entry| entry.key.to_owned()); + for FieldEntry { key, values } in entries { match values.len() { 0 => { map.entry(key, &""); @@ -52,23 +99,34 @@ impl std::fmt::Debug for Fields { impl From for Fields { fn from(wasi_fields: WasiFields) -> Self { let mut output = HashMap::new(); - for (field_name, value) in wasi_fields.entries() { - let field_list: &mut Vec<_> = output.entry(field_name).or_default(); - field_list.push(value); + for (key, value) in wasi_fields.entries() { + let field_name = key.to_lowercase(); + let entry: &mut FieldEntry = output.entry(field_name).or_insert(FieldEntry { + key, + values: Vec::with_capacity(1), + }); + entry.values.push(value); } Self(output) } } impl TryFrom for WasiFields { - type Error = HeaderError; + type Error = Error; fn try_from(fields: Fields) -> Result { - let mut list = Vec::with_capacity(fields.0.capacity()); - for (name, values) in fields.0.into_iter() { + let mut list = Vec::with_capacity(fields.0.values().map(|entry| entry.values.len()).sum()); + for FieldEntry { key, values } in fields.0.into_values() { for value in values { - list.push((name.clone(), value)); + list.push((key.clone(), value)); } } - WasiFields::from_list(&list) + WasiFields::from_list(&list).map_err(|e| { + let msg = match e { + HeaderError::InvalidSyntax => "header has invalid syntax", + HeaderError::Forbidden => "header key is forbidden", + HeaderError::Immutable => "headers are immutable", + }; + ErrorCode::InternalError(Some(msg.to_string())) + }) } } diff --git a/src/http/request.rs b/src/http/request.rs index f6bd58e..e06424e 100644 --- a/src/http/request.rs +++ b/src/http/request.rs @@ -1,16 +1,16 @@ use crate::io::{empty, Empty}; -use super::{Body, IntoBody, Method}; +use super::{Body, Headers, IntoBody, Method, Result}; use url::Url; use wasi::http::outgoing_handler::OutgoingRequest; -use wasi::http::types::{Headers as WasiHeaders, Scheme}; +use wasi::http::types::Scheme; /// An HTTP request #[derive(Debug)] pub struct Request { method: Method, url: Url, - headers: WasiHeaders, + headers: Headers, body: B, } @@ -21,12 +21,19 @@ impl Request { body: empty(), method, url, - headers: WasiHeaders::new(), + headers: Headers::new(), } } } impl Request { + pub fn headers(&self) -> &Headers { + &self.headers + } + pub fn headers_mut(&mut self) -> &mut Headers { + &mut self.headers + } + /// Set an HTTP body. pub fn set_body(self, body: C) -> Request { let Self { @@ -43,8 +50,8 @@ impl Request { } } - pub fn into_outgoing(self) -> (OutgoingRequest, B) { - let wasi_req = OutgoingRequest::new(self.headers); + pub fn try_into_outgoing(self) -> Result<(OutgoingRequest, B)> { + let wasi_req = OutgoingRequest::new(self.headers.try_into()?); // Set the HTTP method wasi_req.set_method(&self.method.into()).unwrap(); @@ -69,6 +76,6 @@ impl Request { wasi_req.set_authority(Some(self.url.authority())).unwrap(); // All done; request is ready for send-off - (wasi_req, self.body) + Ok((wasi_req, self.body)) } } From cd0be1ee00c57143ea64734f85f7f5b72f1e0587 Mon Sep 17 00:00:00 2001 From: Calvin Prewitt Date: Sat, 21 Sep 2024 13:05:23 -0500 Subject: [PATCH 5/8] commented out `trailers()` method` --- src/http/response.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/http/response.rs b/src/http/response.rs index 3e37a43..f0ef849 100644 --- a/src/http/response.rs +++ b/src/http/response.rs @@ -53,11 +53,11 @@ impl Response { }) } - /// NEEDS wasmtime PR to be released: - /// https://github.com/bytecodealliance/wasmtime/pull/9208 - pub fn trailers(&self) -> Option<&Trailers> { - self.body.trailers.as_ref() - } + ///// NEEDS wasmtime PR to be released: + ///// https://github.com/bytecodealliance/wasmtime/pull/9208 + //pub fn trailers(&self) -> Option<&Trailers> { + // self.body.trailers.as_ref() + //} pub fn content_length(&self) -> Option { self.body.content_length } From 58710a65eaed0ec6bf25efeda1077742de91277e Mon Sep 17 00:00:00 2001 From: Calvin Prewitt Date: Sat, 21 Sep 2024 13:51:27 -0500 Subject: [PATCH 6/8] added IntoBody impl for Vec --- src/http/body.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/http/body.rs b/src/http/body.rs index 0f722d8..e7a2178 100644 --- a/src/http/body.rs +++ b/src/http/body.rs @@ -41,6 +41,13 @@ impl IntoBody for String { } } +impl IntoBody for Vec { + type IntoBody = BoundedBody>; + fn into_body(self) -> Self::IntoBody { + BoundedBody(Cursor::new(self)) + } +} + impl Body for T where T: AsyncRead, From 40a50f69e1d16750033a88c18368d0c61bb0d2ce Mon Sep 17 00:00:00 2001 From: Calvin Prewitt Date: Sat, 21 Sep 2024 14:27:42 -0500 Subject: [PATCH 7/8] removed pub content_length method --- src/http/response.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/http/response.rs b/src/http/response.rs index f0ef849..1182fac 100644 --- a/src/http/response.rs +++ b/src/http/response.rs @@ -58,9 +58,6 @@ impl Response { //pub fn trailers(&self) -> Option<&Trailers> { // self.body.trailers.as_ref() //} - pub fn content_length(&self) -> Option { - self.body.content_length - } } impl Response { From bafaf66d1d23f4614c335016cecc2b2e293c5c8f Mon Sep 17 00:00:00 2001 From: Calvin Prewitt Date: Sun, 22 Sep 2024 04:36:43 -0500 Subject: [PATCH 8/8] fix for fields methods --- src/http/fields.rs | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/http/fields.rs b/src/http/fields.rs index 283306e..f44ffc7 100644 --- a/src/http/fields.rs +++ b/src/http/fields.rs @@ -33,26 +33,18 @@ impl Fields { } pub fn contains(&self, key: &str) -> bool { self.0 - .get(key) + .get(&key.to_lowercase()) .is_some_and(|entry| !entry.values.is_empty()) } pub fn get(&self, key: &str) -> Option<&[FieldValue]> { - if key.chars().any(|c| c.is_uppercase()) { - self.0 - .get(&key.to_lowercase()) - .map(|entry| entry.values.deref()) - } else { - self.0.get(key).map(|entry| entry.values.deref()) - } + self.0 + .get(&key.to_lowercase()) + .map(|entry| entry.values.deref()) } pub fn get_mut(&mut self, key: &str) -> Option<&mut Vec> { - if key.chars().any(|c| c.is_uppercase()) { - self.0 - .get_mut(&key.to_lowercase()) - .map(|entry| entry.values.as_mut()) - } else { - self.0.get_mut(key).map(|entry| entry.values.as_mut()) - } + self.0 + .get_mut(&key.to_lowercase()) + .map(|entry| entry.values.as_mut()) } pub fn insert(&mut self, key: String, values: Vec) { self.0 @@ -66,7 +58,7 @@ impl Fields { entry.values.push(value); } pub fn remove(&mut self, key: &str) -> Option> { - self.0.remove(key).map(|entry| entry.values) + self.0.remove(&key.to_lowercase()).map(|entry| entry.values) } }