Skip to main content

giong_http/
request.rs

1//! The request handed to a handler.
2
3use std::fmt;
4use std::ops::Range;
5use std::rc::Rc;
6
7use crate::parse::{header_lines, Head};
8
9pub(crate) enum BodyBytes {
10    /// A range of the connection buffer (`Content-Length` bodies: no copy).
11    InBuffer(Range<usize>),
12    /// A decoded chunked body.
13    Decoded(Vec<u8>),
14}
15
16/// An HTTP request with its body fully received.
17///
18/// A request shares the connection's receive buffer instead of copying out of
19/// it, so creating one allocates nothing. It can be kept for as long as needed;
20/// the connection only reuses the buffer once the request has been dropped.
21pub struct Request {
22    pub(crate) buf: Rc<Vec<u8>>,
23    /// Where the head starts in `buf`.
24    pub(crate) start: usize,
25    pub(crate) head: Head,
26    pub(crate) body: BodyBytes,
27}
28
29impl Request {
30    fn head_bytes(&self) -> &[u8] {
31        &self.buf[self.start..self.start + self.head.len]
32    }
33
34    fn head_str(&self, range: Range<usize>) -> &str {
35        // The parser only accepts ASCII in the method and the target.
36        std::str::from_utf8(&self.head_bytes()[range]).unwrap_or("")
37    }
38
39    /// The request method, e.g. `GET`. Case-sensitive, as sent.
40    pub fn method(&self) -> &str {
41        self.head_str(self.head.method.clone())
42    }
43
44    /// The request target exactly as sent, e.g. `/search?q=rust`.
45    pub fn target(&self) -> &str {
46        self.head_str(self.head.target.clone())
47    }
48
49    /// The path part of the target, without the query string. For a target in
50    /// absolute form (`http://host/path`), the scheme and host are left out.
51    pub fn path(&self) -> &str {
52        let target = self.target();
53        let target = match target.find("://") {
54            Some(i) if !target.starts_with('/') => {
55                let rest = &target[i + 3..];
56                rest.find('/').map_or("/", |slash| &rest[slash..])
57            }
58            _ => target,
59        };
60        target.split('?').next().unwrap_or(target)
61    }
62
63    /// The query string, without the leading `?`.
64    pub fn query(&self) -> Option<&str> {
65        self.target().split_once('?').map(|(_, query)| query)
66    }
67
68    /// `1` for HTTP/1.1, `0` for HTTP/1.0.
69    pub fn version_minor(&self) -> u8 {
70        self.head.minor
71    }
72
73    /// All header fields in the order they were sent, values trimmed.
74    pub fn headers(&self) -> impl Iterator<Item = (&str, &[u8])> {
75        header_lines(&self.head_bytes()[self.head.headers.clone()])
76            .map(|(name, value)| (std::str::from_utf8(name).unwrap_or(""), value))
77    }
78
79    /// The first value of the header `name` (case-insensitive).
80    pub fn header(&self, name: &str) -> Option<&[u8]> {
81        self.headers()
82            .find(|(n, _)| n.eq_ignore_ascii_case(name))
83            .map(|(_, value)| value)
84    }
85
86    /// Like [`header`](Self::header), for values that are valid UTF-8.
87    pub fn header_str(&self, name: &str) -> Option<&str> {
88        self.header(name).and_then(|v| std::str::from_utf8(v).ok())
89    }
90
91    /// The request body. Empty if the request has none.
92    pub fn body(&self) -> &[u8] {
93        match &self.body {
94            BodyBytes::InBuffer(range) => &self.buf[range.clone()],
95            BodyBytes::Decoded(body) => body,
96        }
97    }
98}
99
100impl fmt::Debug for Request {
101    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102        f.debug_struct("Request")
103            .field("method", &self.method())
104            .field("target", &self.target())
105            .field("version_minor", &self.version_minor())
106            .field("body_len", &self.body().len())
107            .finish()
108    }
109}