use std::fmt;
use std::ops::Range;
use std::rc::Rc;
use crate::parse::{header_lines, Head};
pub(crate) enum BodyBytes {
InBuffer(Range<usize>),
Decoded(Vec<u8>),
}
pub struct Request {
pub(crate) buf: Rc<Vec<u8>>,
pub(crate) start: usize,
pub(crate) head: Head,
pub(crate) body: BodyBytes,
}
impl Request {
fn head_bytes(&self) -> &[u8] {
&self.buf[self.start..self.start + self.head.len]
}
fn head_str(&self, range: Range<usize>) -> &str {
std::str::from_utf8(&self.head_bytes()[range]).unwrap_or("")
}
pub fn method(&self) -> &str {
self.head_str(self.head.method.clone())
}
pub fn target(&self) -> &str {
self.head_str(self.head.target.clone())
}
pub fn path(&self) -> &str {
let target = self.target();
let target = match target.find("://") {
Some(i) if !target.starts_with('/') => {
let rest = &target[i + 3..];
rest.find('/').map_or("/", |slash| &rest[slash..])
}
_ => target,
};
target.split('?').next().unwrap_or(target)
}
pub fn query(&self) -> Option<&str> {
self.target().split_once('?').map(|(_, query)| query)
}
pub fn version_minor(&self) -> u8 {
self.head.minor
}
pub fn headers(&self) -> impl Iterator<Item = (&str, &[u8])> {
header_lines(&self.head_bytes()[self.head.headers.clone()])
.map(|(name, value)| (std::str::from_utf8(name).unwrap_or(""), value))
}
pub fn header(&self, name: &str) -> Option<&[u8]> {
self.headers()
.find(|(n, _)| n.eq_ignore_ascii_case(name))
.map(|(_, value)| value)
}
pub fn header_str(&self, name: &str) -> Option<&str> {
self.header(name).and_then(|v| std::str::from_utf8(v).ok())
}
pub fn body(&self) -> &[u8] {
match &self.body {
BodyBytes::InBuffer(range) => &self.buf[range.clone()],
BodyBytes::Decoded(body) => body,
}
}
}
impl fmt::Debug for Request {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Request")
.field("method", &self.method())
.field("target", &self.target())
.field("version_minor", &self.version_minor())
.field("body_len", &self.body().len())
.finish()
}
}