Initial commit
This commit is contained in:
@@ -0,0 +1,309 @@
|
||||
//! The connection actor — one per accepted TCP connection.
|
||||
//!
|
||||
//! Runs the HTTP/1.1 request loop:
|
||||
//!
|
||||
//! loop {
|
||||
//! read bytes → parse → build Conn
|
||||
//! pipeline.run(conn) // inline; no spawn
|
||||
//! write response
|
||||
//! if !keep_alive { break }
|
||||
//! }
|
||||
//!
|
||||
//! Everything in here happens in one smarm green thread. The actor parks on
|
||||
//! `wait_readable` between bytes and `wait_writable` during slow writes;
|
||||
//! during those parks, other connection actors progress freely.
|
||||
|
||||
use crate::conn::{Body, Conn, RespBody};
|
||||
use crate::net::OwnedFd;
|
||||
use crate::parser::{self, ParseError};
|
||||
use crate::plug::Pipeline;
|
||||
|
||||
use std::io::{self, ErrorKind};
|
||||
use std::os::fd::RawFd;
|
||||
use std::time::Duration;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Limits
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// Per-connection settings the connection actor needs to honour.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ConnLimits {
|
||||
pub max_headers: usize,
|
||||
pub initial_read_buf: usize,
|
||||
/// Hard cap on the request head to bound buffer growth. 64 KiB is
|
||||
/// well over Apache's 8 KiB default; protects against pathological
|
||||
/// clients streaming headers forever.
|
||||
pub max_head_bytes: usize,
|
||||
/// Hard cap on Content-Length we'll accept. 16 MiB is enough for a CRUD
|
||||
/// example; configurable in `Config`.
|
||||
pub max_body_bytes: usize,
|
||||
pub keep_alive_timeout: Duration,
|
||||
}
|
||||
|
||||
impl Default for ConnLimits {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
max_headers: 64,
|
||||
initial_read_buf: 8 * 1024,
|
||||
max_head_bytes: 64 * 1024,
|
||||
max_body_bytes: 16 * 1024 * 1024,
|
||||
keep_alive_timeout: Duration::from_secs(60),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// run_connection — entry point spawned by the listener actor.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
pub fn run_connection(fd: OwnedFd, pipeline: Pipeline, limits: ConnLimits) {
|
||||
// The OwnedFd cleans up via Drop on any exit path (panic, error, or
|
||||
// normal close). No explicit close calls below.
|
||||
let raw = fd.as_raw();
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(limits.initial_read_buf);
|
||||
|
||||
loop {
|
||||
// ----- 1. Read until we have a full request head. -----
|
||||
let parsed = match read_head(raw, &mut buf, &limits) {
|
||||
Ok(p) => p,
|
||||
Err(ReadHeadErr::ClientClosed) => {
|
||||
// Clean EOF between requests (or before any request). Normal.
|
||||
return;
|
||||
}
|
||||
Err(ReadHeadErr::Io(_)) => {
|
||||
// Network error or timeout. Best-effort close; we're done.
|
||||
return;
|
||||
}
|
||||
Err(ReadHeadErr::Parse(e)) => {
|
||||
emit_error_response(raw, &e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// ----- 2. Read body. -----
|
||||
let body_len = parsed.content_length.unwrap_or(0);
|
||||
if body_len > limits.max_body_bytes {
|
||||
let _ = write_all(raw, b"HTTP/1.1 413 Payload Too Large\r\ncontent-length: 0\r\nconnection: close\r\n\r\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// If client sent `Expect: 100-continue`, emit it before reading the
|
||||
// body. RFC 7231 §5.1.1. We don't gate on app logic here; v1 always
|
||||
// accepts.
|
||||
if parsed.expect_100 {
|
||||
if write_all(raw, b"HTTP/1.1 100 Continue\r\n\r\n").is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let body = match read_body(raw, &mut buf, parsed.head_len, body_len) {
|
||||
Ok(b) => b,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let keep_alive = parsed.keep_alive;
|
||||
let version = parsed.version;
|
||||
let head_len = parsed.head_len;
|
||||
let conn = parser::build_conn(parsed, Body::from_bytes(body));
|
||||
|
||||
// ----- 3. Run the pipeline. -----
|
||||
// Catch panics at the actor boundary — a panicking handler should
|
||||
// not take down the whole connection silently with no response.
|
||||
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
pipeline.run(conn)
|
||||
}));
|
||||
|
||||
let mut response_conn = match result {
|
||||
Ok(c) => c,
|
||||
Err(_) => {
|
||||
// Compose a 500 manually; the original Conn was moved into
|
||||
// the closure.
|
||||
let mut c = Conn::new();
|
||||
c.version = version;
|
||||
c.put_status(500).put_header("content-length", "0")
|
||||
}
|
||||
};
|
||||
|
||||
// If no plug touched status, that's a configuration error (no router
|
||||
// matched, no default handler). Emit 404.
|
||||
if response_conn.status.is_none() {
|
||||
response_conn = response_conn.put_status(404)
|
||||
.put_body(RespBody::Empty);
|
||||
}
|
||||
|
||||
// ----- 4. Write the response. -----
|
||||
let bytes = parser::serialise_response(&response_conn, keep_alive);
|
||||
if write_all(raw, &bytes).is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
// ----- 5. Loop or close. -----
|
||||
if !keep_alive {
|
||||
return;
|
||||
}
|
||||
|
||||
// Drop the request bytes (head + body) from `buf`; anything past
|
||||
// them is the start of the next pipelined request.
|
||||
let consumed = head_len + body_len;
|
||||
buf.drain(..consumed);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// read_head
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[allow(dead_code)] // io::Error is captured for future logging
|
||||
enum ReadHeadErr {
|
||||
ClientClosed,
|
||||
Io(io::Error),
|
||||
Parse(ParseError),
|
||||
}
|
||||
|
||||
/// Read until `parse_head` succeeds or fails definitively. `buf` may already
|
||||
/// contain leftover bytes from a previous keep-alive cycle; we try to parse
|
||||
/// those before reading more from the socket.
|
||||
fn read_head(
|
||||
fd: RawFd,
|
||||
buf: &mut Vec<u8>,
|
||||
limits: &ConnLimits,
|
||||
) -> Result<parser::ParsedHead, ReadHeadErr> {
|
||||
loop {
|
||||
// Try to parse what we already have. On the first iteration of a
|
||||
// fresh keep-alive cycle, `buf` may already hold the next request.
|
||||
if !buf.is_empty() {
|
||||
match parser::parse_head(buf, limits.max_headers) {
|
||||
Ok(h) => return Ok(h),
|
||||
Err(ParseError::Incomplete) => {} // need more bytes
|
||||
Err(e) => return Err(ReadHeadErr::Parse(e)),
|
||||
}
|
||||
}
|
||||
|
||||
if buf.len() >= limits.max_head_bytes {
|
||||
return Err(ReadHeadErr::Parse(ParseError::TooManyHeaders));
|
||||
}
|
||||
|
||||
// Read more.
|
||||
match read_some(fd, buf, limits.initial_read_buf) {
|
||||
Ok(0) => return Err(ReadHeadErr::ClientClosed),
|
||||
Ok(_) => continue,
|
||||
Err(e) => return Err(ReadHeadErr::Io(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// read_body
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn read_body(
|
||||
fd: RawFd,
|
||||
buf: &mut Vec<u8>,
|
||||
head_len: usize,
|
||||
body_len: usize,
|
||||
) -> io::Result<Vec<u8>> {
|
||||
// Bytes already in `buf` past the head belong to the body.
|
||||
let already = buf.len().saturating_sub(head_len);
|
||||
let need = body_len.saturating_sub(already);
|
||||
|
||||
if need == 0 {
|
||||
// We have the full body in `buf` already. Extract a copy; `buf` is
|
||||
// drained later in the connection loop.
|
||||
return Ok(buf[head_len..head_len + body_len].to_vec());
|
||||
}
|
||||
|
||||
// Read until we have the rest.
|
||||
let mut total_read = already;
|
||||
while total_read < body_len {
|
||||
match read_some(fd, buf, 8 * 1024) {
|
||||
Ok(0) => return Err(io::Error::new(ErrorKind::UnexpectedEof, "client closed during body")),
|
||||
Ok(n) => total_read += n,
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
Ok(buf[head_len..head_len + body_len].to_vec())
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// read_some — single epoll-park + read loop.
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// Appends what it reads onto `buf`. Returns bytes read, 0 for EOF, or the
|
||||
// last io error.
|
||||
|
||||
fn read_some(fd: RawFd, buf: &mut Vec<u8>, chunk: usize) -> io::Result<usize> {
|
||||
// Loop to absorb EAGAIN: a readable wakeup followed by EAGAIN is
|
||||
// possible (signal race, etc). Re-park and retry rather than returning
|
||||
// 0 (which would be confused with EOF by callers).
|
||||
loop {
|
||||
smarm::wait_readable(fd)?;
|
||||
|
||||
let start = buf.len();
|
||||
buf.resize(start + chunk, 0);
|
||||
|
||||
let n = unsafe {
|
||||
libc::read(fd, buf.as_mut_ptr().add(start) as *mut _, chunk)
|
||||
};
|
||||
|
||||
if n < 0 {
|
||||
let err = io::Error::last_os_error();
|
||||
buf.truncate(start);
|
||||
if err.kind() == ErrorKind::WouldBlock || err.kind() == ErrorKind::Interrupted {
|
||||
continue;
|
||||
}
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
let n = n as usize;
|
||||
buf.truncate(start + n);
|
||||
return Ok(n); // n == 0 here is real EOF
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// write_all — robust write loop.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn write_all(fd: RawFd, mut buf: &[u8]) -> io::Result<()> {
|
||||
while !buf.is_empty() {
|
||||
// Park on writability before each syscall.
|
||||
smarm::wait_writable(fd)?;
|
||||
|
||||
let n = unsafe {
|
||||
libc::write(fd, buf.as_ptr() as *const _, buf.len())
|
||||
};
|
||||
if n < 0 {
|
||||
let err = io::Error::last_os_error();
|
||||
if err.kind() == ErrorKind::WouldBlock {
|
||||
continue; // spurious wake; retry
|
||||
}
|
||||
return Err(err);
|
||||
}
|
||||
if n == 0 {
|
||||
return Err(io::Error::new(ErrorKind::WriteZero, "write returned 0"));
|
||||
}
|
||||
buf = &buf[n as usize..];
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Error responses for unparseable / malformed requests.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
fn emit_error_response(fd: RawFd, err: &ParseError) {
|
||||
let resp: &[u8] = match err {
|
||||
ParseError::TooManyHeaders =>
|
||||
b"HTTP/1.1 431 Request Header Fields Too Large\r\ncontent-length: 0\r\nconnection: close\r\n\r\n",
|
||||
ParseError::BadContentLength =>
|
||||
b"HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\nconnection: close\r\n\r\n",
|
||||
ParseError::Unsupported =>
|
||||
b"HTTP/1.1 411 Length Required\r\ncontent-length: 0\r\nconnection: close\r\n\r\n",
|
||||
// Incomplete and Malformed both lead here; Incomplete shouldn't
|
||||
// appear (read_head loops on it).
|
||||
_ =>
|
||||
b"HTTP/1.1 400 Bad Request\r\ncontent-length: 0\r\nconnection: close\r\n\r\n",
|
||||
};
|
||||
let _ = write_all(fd, resp);
|
||||
}
|
||||
Reference in New Issue
Block a user