Project homepage Mailing List  Warmcat.com  API Docs  Github Mirror 
    npro  
 Modern all-safe Rust Network Protocol library supporting h1, h2, h3, ws, wt sans-IO and with socket IO + tls
git clone https://npro.rs/repo/npro
 
root / crates / npro-test / h1 / requests / headers-cr-cr.http
Author[]Andy Green <andy@warmcat.com> 2026-10-05 12:38 UTC
Committer[]Andy Green <andy@warmcat.com> 2026-10-05 13:01 UTC
Treee985e1de15e8d583032073185c651c25ce0c9fba   Raw Patch
 
npro-h1: the head a connection writes, shared by server and client
npro-h1: the head a connection writes, shared by server and client

The buffer the server composed its own status line and headers in, and
the error for a head that does not fit, move to their own module, for
the client to compose its request in.  The server is as it was:
RespondError and MAX_OWN are still npro_h1::server's.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019kg5Eemy68ZaqDBcUJQG6J
diff --git a/crates/npro-h1/src/lib.rs b/crates/npro-h1/src/lib.rs index 875505e..26a7fe9 100644 --- a/crates/npro-h1/src/lib.rs +++ b/crates/npro-h1/src/lib.rs @@ -20,6 +20,7 @@ pub mod chunked; pub mod fields; pub mod head; +mod own; pub mod server; pub mod table; pub mod token; diff --git a/crates/npro-h1/src/own.rs b/crates/npro-h1/src/own.rs new file mode 100644 index 0000000..a79bcc8 --- /dev/null +++ b/crates/npro-h1/src/own.rs @@ -0,0 +1,89 @@ +//! Bytes a connection writes itself, a status line and headers, held until +//! they have gone. + +/// The most bytes a head the connection writes may have: its first line +/// and headers, or a status page with them. +pub const MAX_OWN: usize = 512; + +/// Why a head cannot be composed. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RespondError { + /// It is not the time for one: there is nothing to answer, or it is + /// answered already. + NotNow, + /// The head does not fit in [`MAX_OWN`]. + TooLong, +} + +impl core::fmt::Display for RespondError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str(match self { + Self::NotNow => "not the time for a head", + Self::TooLong => "the head is too long", + }) + } +} + +impl core::error::Error for RespondError {} + +/// Bytes the connection writes itself, and how many of them have gone. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) struct Own { + pub(crate) buf: [u8; MAX_OWN], + pub(crate) len: usize, + sent: usize, +} + +impl Own { + pub(crate) const fn new() -> Self { + Self { + buf: [0; MAX_OWN], + len: 0, + sent: 0, + } + } + + pub(crate) fn push(&mut self, b: &[u8]) -> Result<(), RespondError> { + let end = self.len.checked_add(b.len()).ok_or(RespondError::TooLong)?; + let dst = self + .buf + .get_mut(self.len..end) + .ok_or(RespondError::TooLong)?; + dst.copy_from_slice(b); + self.len = end; + Ok(()) + } + + pub(crate) fn push_u64(&mut self, v: u64) -> Result<(), RespondError> { + let mut d = [0u8; 20]; + let mut n = d.len(); + let mut v = v; + loop { + n = n.saturating_sub(1); + if let Some(slot) = d.get_mut(n) { + // v % 10 < 10 + *slot = b'0'.saturating_add(u8::try_from(v % 10).unwrap_or(0)); + } + v /= 10; + if v == 0 { + break; + } + } + self.push(d.get(n..).unwrap_or_default()) + } + + pub(crate) const fn pending(&self) -> bool { + self.sent < self.len + } + + /// Writes what is left into `out`, returning how much. + pub(crate) fn drain(&mut self, out: &mut [u8]) -> usize { + let rest = self.buf.get(self.sent..self.len).unwrap_or_default(); + let n = rest.len().min(out.len()); + if let (Some(d), Some(s)) = (out.get_mut(..n), rest.get(..n)) { + d.copy_from_slice(s); + } + self.sent = self.sent.saturating_add(n); + n + } +} diff --git a/crates/npro-h1/src/server.rs b/crates/npro-h1/src/server.rs index 0e8f7e5..89f4ac0 100644 --- a/crates/npro-h1/src/server.rs +++ b/crates/npro-h1/src/server.rs @@ -25,6 +25,7 @@ use crate::chunked::{Chunk, Dechunk}; use crate::fields::{content_length, transfer_encoding_is_chunked}; use crate::head::{self, Answer, Head, Progress, Refused, Side, Version}; +use crate::own::Own; use crate::table::{CapacityTooLarge, HeaderTable}; use crate::token::Token; @@ -32,10 +33,6 @@ use crate::token::Token; /// `max_http_body_size`. pub const DEFAULT_MAX_BODY: u64 = 100 * 1024 * 1024; -/// The most bytes a response head the connection writes may have, its -/// status line and headers, or a status page with them. -pub const MAX_OWN: usize = 512; - /// How a server takes its requests. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct Config { @@ -130,87 +127,7 @@ pub struct Response<'a> { pub content_length: Option<u64>, } -/// Why an answer cannot be given. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum RespondError { - /// There is no request to answer, or it is answered already. - NotNow, - /// The head does not fit in [`MAX_OWN`]. - TooLong, -} - -impl core::fmt::Display for RespondError { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - f.write_str(match self { - Self::NotNow => "no request to answer", - Self::TooLong => "the response head is too long", - }) - } -} - -impl core::error::Error for RespondError {} - -/// Bytes the connection writes itself, and how many of them have gone. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -struct Own { - buf: [u8; MAX_OWN], - len: usize, - sent: usize, -} - -impl Own { - const fn new() -> Self { - Self { - buf: [0; MAX_OWN], - len: 0, - sent: 0, - } - } - - fn push(&mut self, b: &[u8]) -> Result<(), RespondError> { - let end = self.len.checked_add(b.len()).ok_or(RespondError::TooLong)?; - let dst = self - .buf - .get_mut(self.len..end) - .ok_or(RespondError::TooLong)?; - dst.copy_from_slice(b); - self.len = end; - Ok(()) - } - - fn push_u64(&mut self, v: u64) -> Result<(), RespondError> { - let mut d = [0u8; 20]; - let mut n = d.len(); - let mut v = v; - loop { - n = n.saturating_sub(1); - if let Some(slot) = d.get_mut(n) { - // v % 10 < 10 - *slot = b'0'.saturating_add(u8::try_from(v % 10).unwrap_or(0)); - } - v /= 10; - if v == 0 { - break; - } - } - self.push(d.get(n..).unwrap_or_default()) - } - - const fn pending(&self) -> bool { - self.sent < self.len - } - - /// Writes what is left into `out`, returning how much. - fn drain(&mut self, out: &mut [u8]) -> usize { - let rest = self.buf.get(self.sent..self.len).unwrap_or_default(); - let n = rest.len().min(out.len()); - if let (Some(d), Some(s)) = (out.get_mut(..n), rest.get(..n)) { - d.copy_from_slice(s); - } - self.sent = self.sent.saturating_add(n); - n - } -} +pub use crate::own::{MAX_OWN, RespondError}; /// How the request's body is framed, and how far it has come. #[derive(Clone, Copy, Debug, PartialEq, Eq)]
Page fetched 0s ago, creation time: 2ms (vhost etag hits: 0%, cache hits: 0%)