moirai_http/server/
connection.rs1use std::io;
4
5use moirai_async::io::AsyncWriteExt;
6use moirai_async::net::TcpStream;
7use moirai_async::timer::timeout;
8
9use super::config::ServerConfig;
10use super::response::{HttpResponse, encode_response_head};
11use crate::request::{HttpRequestHead, read_request_head};
12
13#[derive(Debug)]
15pub struct AwaitingRequest;
16
17#[derive(Debug)]
19pub struct AwaitingResponse {
20 suppress_body: bool,
21}
22
23pub struct HttpConnection<State> {
25 pub(super) stream: TcpStream,
26 pub(super) config: ServerConfig,
27 pub(super) prefix: Vec<u8>,
28 pub(super) state: State,
29}
30
31impl HttpConnection<AwaitingRequest> {
32 pub async fn read_request(
41 mut self,
42 ) -> io::Result<(HttpRequest, HttpConnection<AwaitingResponse>)> {
43 let deadline = self.config.request_timeout;
44 match timeout(deadline, self.read_request_inner()).await {
45 Ok(Ok(request)) => {
46 let suppress_body = request.method() == "HEAD";
47 let connection = HttpConnection {
48 stream: self.stream,
49 config: self.config,
50 prefix: Vec::new(),
51 state: AwaitingResponse { suppress_body },
52 };
53 Ok((request, connection))
54 }
55 Ok(Err(error)) => Err(error),
56 Err(_) => Err(timed_out("HTTP request read")),
57 }
58 }
59
60 async fn read_request_inner(&mut self) -> io::Result<HttpRequest> {
61 let (head, prefix) = read_request_head(
62 &mut self.stream,
63 self.config.max_header_bytes,
64 self.config.max_header_count,
65 )
66 .await?;
67 if head.version() != "HTTP/1.1" {
68 return Err(invalid_data("HTTP server requires HTTP/1.1"));
69 }
70 self.prefix = prefix;
71 let body_length = request_body_length(&head, self.config.max_body_bytes)?;
72 let mut body = Vec::new();
73 body.try_reserve(body_length)
74 .map_err(|_| invalid_data("HTTP request body allocation exceeds available memory"))?;
75
76 if self.prefix.len() > body_length {
77 return Err(invalid_data(
78 "HTTP request contains bytes beyond Content-Length",
79 ));
80 }
81 if !self.prefix.is_empty() {
82 body.extend_from_slice(&self.prefix);
83 }
84
85 let mut remaining = body_length
86 .checked_sub(self.prefix.len())
87 .ok_or_else(|| invalid_data("HTTP request body length underflowed"))?;
88 while remaining != 0 {
89 let mut chunk = [0_u8; 8192];
90 let read_length = remaining.min(chunk.len());
91 let target = chunk
92 .get_mut(..read_length)
93 .ok_or_else(|| invalid_data("HTTP request read window exceeded its bound"))?;
94 let count = self.stream.read(target).await?;
95 if count == 0 {
96 return Err(io::Error::new(
97 io::ErrorKind::UnexpectedEof,
98 "HTTP request body ended before Content-Length",
99 ));
100 }
101 let bytes = chunk
102 .get(..count)
103 .ok_or_else(|| invalid_data("HTTP request read exceeded its bound"))?;
104 body.extend_from_slice(bytes);
105 remaining = remaining
106 .checked_sub(count)
107 .ok_or_else(|| invalid_data("HTTP request body length underflowed"))?;
108 }
109 self.prefix.clear();
110 Ok(HttpRequest { head, body })
111 }
112}
113
114impl HttpConnection<AwaitingResponse> {
115 pub async fn write_response(self, response: HttpResponse) -> io::Result<()> {
125 let deadline = self.config.request_timeout;
126 match timeout(deadline, self.write_response_inner(response)).await {
127 Ok(result) => result,
128 Err(_) => Err(timed_out("HTTP response write")),
129 }
130 }
131
132 async fn write_response_inner(self, response: HttpResponse) -> io::Result<()> {
133 let HttpConnection {
134 mut stream,
135 config,
136 state,
137 prefix: _,
138 } = self;
139 if !state.suppress_body
140 && matches!(response.status, 204 | 205 | 304)
141 && !response.body.is_empty()
142 {
143 return Err(invalid_data("HTTP status does not permit a response body"));
144 }
145 if response.headers.len() > config.max_header_count.saturating_sub(2) {
146 return Err(invalid_data(
147 "HTTP response header count exceeds configured bound",
148 ));
149 }
150 let head = encode_response_head(&response, config.max_response_bytes)?;
151 let total_size = head
152 .len()
153 .checked_add(response.body.len())
154 .ok_or_else(|| invalid_data("HTTP response size overflows its bound"))?;
155 if total_size > config.max_response_bytes {
156 return Err(invalid_data("HTTP response exceeds configured byte bound"));
157 }
158
159 stream.write_all(&head).await?;
160 if !state.suppress_body && !response.body.is_empty() {
161 stream.write_all(&response.body).await?;
162 }
163 stream.flush().await?;
164 stream.shutdown().await
165 }
166}
167
168#[derive(Debug, Clone, PartialEq, Eq)]
170pub struct HttpRequest {
171 head: HttpRequestHead,
172 body: Vec<u8>,
173}
174
175impl HttpRequest {
176 #[must_use]
178 pub fn method(&self) -> &str {
179 self.head.method()
180 }
181
182 #[must_use]
184 pub fn target(&self) -> &str {
185 self.head.target()
186 }
187
188 #[must_use]
190 pub fn version(&self) -> &str {
191 self.head.version()
192 }
193
194 #[must_use]
196 pub fn header(&self, name: &str) -> Option<&str> {
197 self.head.header(name)
198 }
199
200 #[must_use]
202 pub fn headers(&self) -> &[(String, String)] {
203 self.head.headers()
204 }
205
206 #[must_use]
208 pub fn body(&self) -> &[u8] {
209 &self.body
210 }
211}
212
213fn request_body_length(head: &HttpRequestHead, max_body_bytes: usize) -> io::Result<usize> {
214 if head.header("transfer-encoding").is_some() {
215 return Err(invalid_data(
216 "transfer-encoded HTTP request bodies are not supported",
217 ));
218 }
219 let Some(value) = head.header("content-length") else {
220 return Ok(0);
221 };
222 let length = value
223 .parse::<usize>()
224 .map_err(|_| invalid_data("HTTP Content-Length is not a decimal byte count"))?;
225 if length > max_body_bytes {
226 return Err(invalid_data(
227 "HTTP request body exceeds configured byte bound",
228 ));
229 }
230 Ok(length)
231}
232
233fn invalid_data(message: &str) -> io::Error {
234 io::Error::new(io::ErrorKind::InvalidData, message)
235}
236
237fn timed_out(operation: &str) -> io::Error {
238 io::Error::new(io::ErrorKind::TimedOut, operation)
239}