Initial commit
This commit is contained in:
+328
@@ -0,0 +1,328 @@
|
||||
//! `Conn` — the value that flows through the entire plug pipeline.
|
||||
//!
|
||||
//! Carries request data, response state being built, and arbitrary user
|
||||
//! data. Owned and moved at each step. Plugs are unaware of the underlying
|
||||
//! HTTP version (v1.1 today, /2 later).
|
||||
|
||||
use std::any::{Any, TypeId};
|
||||
use std::collections::HashMap;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Method
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Method {
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Delete,
|
||||
Patch,
|
||||
Head,
|
||||
Options,
|
||||
Other(String),
|
||||
}
|
||||
|
||||
impl Method {
|
||||
pub fn parse(s: &str) -> Self {
|
||||
match s {
|
||||
"GET" => Method::Get,
|
||||
"POST" => Method::Post,
|
||||
"PUT" => Method::Put,
|
||||
"DELETE" => Method::Delete,
|
||||
"PATCH" => Method::Patch,
|
||||
"HEAD" => Method::Head,
|
||||
"OPTIONS" => Method::Options,
|
||||
_ => Method::Other(s.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Method::Get => "GET",
|
||||
Method::Post => "POST",
|
||||
Method::Put => "PUT",
|
||||
Method::Delete => "DELETE",
|
||||
Method::Patch => "PATCH",
|
||||
Method::Head => "HEAD",
|
||||
Method::Options => "OPTIONS",
|
||||
Method::Other(s) => s.as_str(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// HttpVersion
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum HttpVersion {
|
||||
Http10,
|
||||
Http11,
|
||||
}
|
||||
|
||||
impl HttpVersion {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
HttpVersion::Http10 => "HTTP/1.0",
|
||||
HttpVersion::Http11 => "HTTP/1.1",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// HeaderMap — small-vec of (name, value) string pairs.
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// Per spec: most requests have <20 headers, so a linear-scan Vec is cheaper
|
||||
// than a hash map. Names are case-insensitive (HTTP/1.1), normalised to
|
||||
// lowercase on insert.
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct HeaderMap {
|
||||
inner: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
impl HeaderMap {
|
||||
pub fn new() -> Self { Self::default() }
|
||||
|
||||
pub fn with_capacity(n: usize) -> Self {
|
||||
Self { inner: Vec::with_capacity(n) }
|
||||
}
|
||||
|
||||
/// Append a header. Does not deduplicate (HTTP allows repeats; e.g.
|
||||
/// `Set-Cookie`).
|
||||
pub fn append(&mut self, name: &str, value: impl Into<String>) {
|
||||
self.inner.push((name.to_ascii_lowercase(), value.into()));
|
||||
}
|
||||
|
||||
/// Replace any existing values for `name` with a single value.
|
||||
pub fn set(&mut self, name: &str, value: impl Into<String>) {
|
||||
let lower = name.to_ascii_lowercase();
|
||||
self.inner.retain(|(n, _)| n != &lower);
|
||||
self.inner.push((lower, value.into()));
|
||||
}
|
||||
|
||||
/// First value for `name`, if any.
|
||||
pub fn get(&self, name: &str) -> Option<&str> {
|
||||
let lower = name.to_ascii_lowercase();
|
||||
self.inner.iter()
|
||||
.find(|(n, _)| n == &lower)
|
||||
.map(|(_, v)| v.as_str())
|
||||
}
|
||||
|
||||
/// Iterate over (name, value) pairs in insertion order.
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
|
||||
self.inner.iter().map(|(n, v)| (n.as_str(), v.as_str()))
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize { self.inner.len() }
|
||||
pub fn is_empty(&self) -> bool { self.inner.is_empty() }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Body — request body, exposed as owned bytes.
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// v1: bodies are read in full by the connection actor before the pipeline
|
||||
// runs, when `Content-Length` is set. Streaming and chunked encoding will
|
||||
// come later; for now this is the simplest correct thing.
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Body {
|
||||
bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Body {
|
||||
pub fn empty() -> Self { Self::default() }
|
||||
pub fn from_bytes(b: Vec<u8>) -> Self { Self { bytes: b } }
|
||||
pub fn as_bytes(&self) -> &[u8] { &self.bytes }
|
||||
pub fn into_bytes(self) -> Vec<u8> { self.bytes }
|
||||
pub fn len(&self) -> usize { self.bytes.len() }
|
||||
pub fn is_empty(&self) -> bool { self.bytes.is_empty() }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// RespBody — what plugs put on the wire.
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// Enum, not a trait, so the connection actor can pattern-match. Leaves room
|
||||
// for future variants like Sse, Stream, Chunked without touching the plug
|
||||
// API.
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RespBody {
|
||||
Empty,
|
||||
Bytes(Vec<u8>),
|
||||
}
|
||||
|
||||
impl RespBody {
|
||||
pub fn len_hint(&self) -> usize {
|
||||
match self {
|
||||
RespBody::Empty => 0,
|
||||
RespBody::Bytes(b) => b.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RespBody {
|
||||
fn default() -> Self { RespBody::Empty }
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Params — path parameters extracted by the router.
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// Small-vec semantics: routes have a handful of params; a Vec is the right
|
||||
// shape. Empty until the router plug populates it.
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Params {
|
||||
inner: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
impl Params {
|
||||
pub fn new() -> Self { Self::default() }
|
||||
|
||||
pub fn put(&mut self, name: impl Into<String>, value: impl Into<String>) {
|
||||
self.inner.push((name.into(), value.into()));
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Option<&str> {
|
||||
self.inner.iter()
|
||||
.find(|(n, _)| n == name)
|
||||
.map(|(_, v)| v.as_str())
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&str, &str)> {
|
||||
self.inner.iter().map(|(n, v)| (n.as_str(), v.as_str()))
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Assigns — type-erased map for arbitrary user data.
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// Same pattern as Phoenix's `conn.assigns`. The `Option<HashMap>` keeps the
|
||||
// common case (no assigns) at zero allocation cost.
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Assigns {
|
||||
inner: Option<HashMap<TypeId, Box<dyn Any + Send>>>,
|
||||
}
|
||||
|
||||
impl Assigns {
|
||||
pub fn new() -> Self { Self::default() }
|
||||
|
||||
pub fn put<T: Any + Send>(&mut self, value: T) {
|
||||
self.inner
|
||||
.get_or_insert_with(HashMap::new)
|
||||
.insert(TypeId::of::<T>(), Box::new(value));
|
||||
}
|
||||
|
||||
pub fn get<T: Any + Send>(&self) -> Option<&T> {
|
||||
self.inner
|
||||
.as_ref()?
|
||||
.get(&TypeId::of::<T>())?
|
||||
.downcast_ref::<T>()
|
||||
}
|
||||
|
||||
pub fn take<T: Any + Send>(&mut self) -> Option<T> {
|
||||
let boxed = self.inner.as_mut()?.remove(&TypeId::of::<T>())?;
|
||||
boxed.downcast::<T>().ok().map(|b| *b)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Conn — the pipeline value.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Conn {
|
||||
// Request (populated before the pipeline runs).
|
||||
pub method: Method,
|
||||
pub path: String,
|
||||
pub query: Option<String>,
|
||||
pub version: HttpVersion,
|
||||
pub headers: HeaderMap,
|
||||
pub body: Body,
|
||||
|
||||
// Response (built up by plugs, written after the pipeline returns).
|
||||
pub status: Option<u16>,
|
||||
pub resp_headers: HeaderMap,
|
||||
pub resp_body: RespBody,
|
||||
|
||||
// Pipeline state.
|
||||
pub params: Params,
|
||||
pub assigns: Assigns,
|
||||
pub halted: bool,
|
||||
}
|
||||
|
||||
impl Conn {
|
||||
/// Construct an empty `Conn`. Tests use this; the connection actor uses
|
||||
/// `From<RequestParts>` (see parser.rs).
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
method: Method::Get,
|
||||
path: String::new(),
|
||||
query: None,
|
||||
version: HttpVersion::Http11,
|
||||
headers: HeaderMap::new(),
|
||||
body: Body::empty(),
|
||||
|
||||
status: None,
|
||||
resp_headers: HeaderMap::new(),
|
||||
resp_body: RespBody::Empty,
|
||||
|
||||
params: Params::new(),
|
||||
assigns: Assigns::new(),
|
||||
halted: false,
|
||||
}
|
||||
}
|
||||
|
||||
// ----- Fluent builders. Most handler code uses these. -----
|
||||
|
||||
pub fn put_status(mut self, status: u16) -> Self {
|
||||
self.status = Some(status);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn put_header(mut self, name: &str, value: impl Into<String>) -> Self {
|
||||
self.resp_headers.set(name, value);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn put_body(mut self, body: impl Into<RespBody>) -> Self {
|
||||
self.resp_body = body.into();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn put_params(mut self, params: Params) -> Self {
|
||||
self.params = params;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn halt(mut self) -> Self {
|
||||
self.halted = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Conn {
|
||||
fn default() -> Self { Self::new() }
|
||||
}
|
||||
|
||||
// ----- Ergonomic conversions into RespBody. -----
|
||||
|
||||
impl From<Vec<u8>> for RespBody {
|
||||
fn from(v: Vec<u8>) -> Self { RespBody::Bytes(v) }
|
||||
}
|
||||
impl From<String> for RespBody {
|
||||
fn from(s: String) -> Self { RespBody::Bytes(s.into_bytes()) }
|
||||
}
|
||||
impl From<&'static str> for RespBody {
|
||||
fn from(s: &'static str) -> Self { RespBody::Bytes(s.as_bytes().to_vec()) }
|
||||
}
|
||||
impl From<&[u8]> for RespBody {
|
||||
fn from(s: &[u8]) -> Self { RespBody::Bytes(s.to_vec()) }
|
||||
}
|
||||
Reference in New Issue
Block a user