1use std::collections::{HashMap, VecDeque};
2use std::sync::Arc;
3
4use anyhow::{Context, Result as AnyhowResult, ensure};
5use parking_lot::Mutex as SyncMutex;
6use rivet_envoy_protocol as protocol;
7use tokio::sync::{Mutex, Notify, mpsc, watch};
8
9pub const HTTP_BODY_STREAM_CHANNEL_CAPACITY: usize = 16;
10pub const HTTP_BODY_MAX_CHUNK_SIZE: usize = 64 * 1024;
11
12#[derive(Clone, Debug)]
13pub struct HttpRequestBodyError {
14 pub reason: protocol::HttpStreamAbortReason,
15}
16
17impl std::fmt::Display for HttpRequestBodyError {
18 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19 match &self.reason.detail {
20 Some(detail) => write!(f, "{:?}: {detail}", self.reason.kind),
21 None => write!(f, "{:?}", self.reason.kind),
22 }
23 }
24}
25
26impl std::error::Error for HttpRequestBodyError {}
27
28#[derive(Debug)]
29pub struct HttpRequestBodyStream {
30 source: HttpRequestBodySource,
31 event_tx: Option<mpsc::UnboundedSender<RequestBodyEvent>>,
32 terminal: bool,
33}
34
35#[derive(Debug)]
36enum HttpRequestBodySource {
37 Legacy {
38 rx: mpsc::Receiver<Vec<u8>>,
39 abort_rx: watch::Receiver<Option<HttpRequestBodyError>>,
40 },
41 FlowControlled(Arc<HttpRequestBodyQueue>),
42}
43
44#[derive(Debug)]
45struct HttpRequestBodyQueueState {
46 chunks: VecDeque<Vec<u8>>,
47 finished: bool,
48 abort: Option<HttpRequestBodyError>,
49 receiver_closed: bool,
50}
51
52#[derive(Debug)]
56pub(crate) struct HttpRequestBodyQueue {
57 state: SyncMutex<HttpRequestBodyQueueState>,
58 ready: Notify,
59}
60
61impl HttpRequestBodyQueue {
62 pub(crate) fn new() -> Arc<Self> {
63 Arc::new(Self {
64 state: SyncMutex::new(HttpRequestBodyQueueState {
65 chunks: VecDeque::new(),
66 finished: false,
67 abort: None,
68 receiver_closed: false,
69 }),
70 ready: Notify::new(),
71 })
72 }
73
74 pub(crate) fn push(&self, mut data: Vec<u8>) -> bool {
75 let mut state = self.state.lock();
76 if state.finished || state.abort.is_some() || state.receiver_closed {
77 return false;
78 }
79
80 if let Some(last) = state.chunks.back_mut() {
81 let available = HTTP_BODY_MAX_CHUNK_SIZE.saturating_sub(last.len());
82 let append = available.min(data.len());
83 last.extend_from_slice(&data[..append]);
84 data.drain(..append);
85 }
86 for chunk in data.chunks(HTTP_BODY_MAX_CHUNK_SIZE) {
87 state.chunks.push_back(chunk.to_vec());
88 }
89 drop(state);
90 self.ready.notify_one();
91 true
92 }
93
94 pub(crate) fn finish(&self) {
95 let mut state = self.state.lock();
96 if state.abort.is_none() {
97 state.finished = true;
98 }
99 drop(state);
100 self.ready.notify_waiters();
101 }
102
103 pub(crate) fn abort(&self, error: HttpRequestBodyError) {
104 let mut state = self.state.lock();
105 if state.abort.is_none() && !state.finished {
106 state.chunks.clear();
107 state.abort = Some(error);
108 }
109 drop(state);
110 self.ready.notify_waiters();
111 }
112
113 fn close_receiver(&self) {
114 let mut state = self.state.lock();
115 state.receiver_closed = true;
116 state.chunks.clear();
117 drop(state);
118 self.ready.notify_waiters();
119 }
120
121 async fn recv(&self) -> Result<Option<Vec<u8>>, HttpRequestBodyError> {
122 loop {
123 let notified = self.ready.notified();
124 {
125 let mut state = self.state.lock();
126 if let Some(error) = &state.abort {
127 return Err(error.clone());
128 }
129 if let Some(chunk) = state.chunks.pop_front() {
130 return Ok(Some(chunk));
131 }
132 if state.finished || state.receiver_closed {
133 return Ok(None);
134 }
135 }
136 notified.await;
137 }
138 }
139}
140
141#[derive(Debug)]
142pub(crate) enum RequestBodyEvent {
143 Consumed(u64),
144 Cancelled,
145}
146
147impl HttpRequestBodyStream {
148 fn handle_chunk(
149 &mut self,
150 chunk: Option<Vec<u8>>,
151 ) -> Result<Option<Vec<u8>>, HttpRequestBodyError> {
152 match chunk {
153 Some(chunk) => {
154 if let Some(event_tx) = &self.event_tx {
155 let _ = event_tx.send(RequestBodyEvent::Consumed(chunk.len() as u64));
156 }
157 Ok(Some(chunk))
158 }
159 None => {
160 self.terminal = true;
161 Ok(None)
162 }
163 }
164 }
165
166 pub fn new(
167 rx: mpsc::Receiver<Vec<u8>>,
168 abort_rx: watch::Receiver<Option<HttpRequestBodyError>>,
169 ) -> Self {
170 Self {
171 source: HttpRequestBodySource::Legacy { rx, abort_rx },
172 event_tx: None,
173 terminal: false,
174 }
175 }
176
177 pub(crate) fn new_with_flow_control(
178 queue: Arc<HttpRequestBodyQueue>,
179 event_tx: mpsc::UnboundedSender<RequestBodyEvent>,
180 ) -> Self {
181 Self {
182 source: HttpRequestBodySource::FlowControlled(queue),
183 event_tx: Some(event_tx),
184 terminal: false,
185 }
186 }
187
188 pub async fn recv(&mut self) -> Result<Option<Vec<u8>>, HttpRequestBodyError> {
189 let chunk = match &mut self.source {
190 HttpRequestBodySource::Legacy { rx, abort_rx } => loop {
191 if let Some(error) = abort_rx.borrow().clone() {
192 self.terminal = true;
193 return Err(error);
194 }
195
196 tokio::select! {
197 biased;
198 changed = abort_rx.changed() => {
199 if changed.is_ok() {
200 continue;
201 }
202 break rx.recv().await;
203 }
204 chunk = rx.recv() => break chunk,
205 }
206 },
207 HttpRequestBodySource::FlowControlled(queue) => match queue.recv().await {
208 Ok(chunk) => chunk,
209 Err(error) => {
210 self.terminal = true;
211 return Err(error);
212 }
213 },
214 };
215 self.handle_chunk(chunk)
216 }
217}
218
219impl Drop for HttpRequestBodyStream {
220 fn drop(&mut self) {
221 if let HttpRequestBodySource::FlowControlled(queue) = &self.source {
222 queue.close_receiver();
223 }
224 if !self.terminal
225 && let Some(event_tx) = &self.event_tx
226 {
227 let _ = event_tx.send(RequestBodyEvent::Cancelled);
228 }
229 }
230}
231
232#[derive(Debug)]
233struct HttpBodySendWindowState {
234 sent_bytes: u64,
235 consumed_bytes: u64,
236}
237
238#[derive(Debug)]
239pub(crate) struct HttpBodySendWindow {
240 state: Mutex<HttpBodySendWindowState>,
241 credit_available: Notify,
242}
243
244impl HttpBodySendWindow {
245 pub(crate) fn new() -> Arc<Self> {
246 Arc::new(Self {
247 state: Mutex::new(HttpBodySendWindowState {
248 sent_bytes: 0,
249 consumed_bytes: 0,
250 }),
251 credit_available: Notify::new(),
252 })
253 }
254
255 pub(crate) async fn reserve(&self, bytes: u64) -> AnyhowResult<()> {
256 ensure!(
257 bytes <= protocol::HTTP_STREAM_INITIAL_WINDOW_BYTES,
258 "HTTP body frame exceeds the flow-control window"
259 );
260 if bytes == 0 {
261 return Ok(());
262 }
263
264 loop {
265 let notified = self.credit_available.notified();
266 {
267 let mut state = self.state.lock().await;
268 let outstanding = state
269 .sent_bytes
270 .checked_sub(state.consumed_bytes)
271 .context("HTTP body flow-control accounting underflow")?;
272 let available = protocol::HTTP_STREAM_INITIAL_WINDOW_BYTES
273 .checked_sub(outstanding)
274 .context("HTTP body flow-control window exceeded")?;
275 if bytes <= available {
276 state.sent_bytes = state
277 .sent_bytes
278 .checked_add(bytes)
279 .context("HTTP body sent-byte counter overflow")?;
280 return Ok(());
281 }
282 }
283 notified.await;
284 }
285 }
286
287 pub(crate) async fn update_consumed(&self, consumed_bytes: u64) -> AnyhowResult<()> {
288 let mut state = self.state.lock().await;
289 ensure!(
290 consumed_bytes >= state.consumed_bytes,
291 "HTTP body consumed-byte counter moved backwards"
292 );
293 ensure!(
294 consumed_bytes <= state.sent_bytes,
295 "HTTP body consumed-byte counter exceeds sent bytes"
296 );
297 if consumed_bytes == state.consumed_bytes {
298 return Ok(());
299 }
300 state.consumed_bytes = consumed_bytes;
301 drop(state);
302 self.credit_available.notify_waiters();
303 Ok(())
304 }
305}
306
307pub struct HttpRequest {
309 pub method: String,
310 pub path: String,
311 pub headers: HashMap<String, String>,
312 pub body: Option<Vec<u8>>,
313 pub body_stream: Option<HttpRequestBodyStream>,
315}
316
317pub struct HttpResponse {
318 pub status: u16,
319 pub headers: HashMap<String, String>,
320 pub body: Option<Vec<u8>>,
321 pub body_stream: Option<HttpResponseBodyStream>,
324}
325
326pub enum ResponseChunk {
328 Data { data: Vec<u8>, finish: bool },
329 Error(String),
330}
331
332pub struct HttpResponseBodyStream {
333 rx: mpsc::Receiver<ResponseChunk>,
334 on_drop: Option<Box<dyn FnOnce() + Send>>,
335}
336
337impl HttpResponseBodyStream {
338 pub fn set_on_drop(&mut self, on_drop: impl FnOnce() + Send + 'static) {
339 self.on_drop = Some(Box::new(on_drop));
340 }
341
342 pub async fn recv(&mut self) -> Option<ResponseChunk> {
343 self.rx.recv().await
344 }
345}
346
347impl From<mpsc::Receiver<ResponseChunk>> for HttpResponseBodyStream {
348 fn from(rx: mpsc::Receiver<ResponseChunk>) -> Self {
349 Self { rx, on_drop: None }
350 }
351}
352
353impl Drop for HttpResponseBodyStream {
354 fn drop(&mut self) {
355 if let Some(on_drop) = self.on_drop.take() {
356 on_drop();
357 }
358 }
359}
360
361#[cfg(test)]
362mod tests {
363 use std::time::Duration;
364
365 use super::*;
366
367 #[tokio::test]
368 async fn request_body_returns_credit_only_when_consumed() {
369 let queue = HttpRequestBodyQueue::new();
370 let (event_tx, mut event_rx) = mpsc::unbounded_channel();
371 let mut body = HttpRequestBodyStream::new_with_flow_control(queue.clone(), event_tx);
372 assert!(queue.push(vec![1, 2, 3]));
373
374 assert!(event_rx.try_recv().is_err());
375 assert_eq!(body.recv().await.expect("read body"), Some(vec![1, 2, 3]));
376 assert!(matches!(
377 event_rx.recv().await,
378 Some(RequestBodyEvent::Consumed(3))
379 ));
380 }
381
382 #[tokio::test]
383 async fn dropping_unfinished_request_body_emits_upload_cancel() {
384 let queue = HttpRequestBodyQueue::new();
385 let (event_tx, mut event_rx) = mpsc::unbounded_channel();
386 let body = HttpRequestBodyStream::new_with_flow_control(queue, event_tx);
387
388 drop(body);
389
390 assert!(matches!(
391 event_rx.recv().await,
392 Some(RequestBodyEvent::Cancelled)
393 ));
394 }
395
396 #[tokio::test]
397 async fn request_body_coalesces_tiny_frames_within_the_byte_window() {
398 let queue = HttpRequestBodyQueue::new();
399 let (event_tx, mut event_rx) = mpsc::unbounded_channel();
400 let mut body = HttpRequestBodyStream::new_with_flow_control(queue.clone(), event_tx);
401 for byte in 0..=255u8 {
402 assert!(queue.push(vec![byte]));
403 }
404 queue.finish();
405
406 let chunk = body.recv().await.expect("read coalesced body").unwrap();
407 assert_eq!(chunk, (0..=255u8).collect::<Vec<_>>());
408 assert!(matches!(
409 event_rx.recv().await,
410 Some(RequestBodyEvent::Consumed(256))
411 ));
412 assert_eq!(body.recv().await.expect("read request eof"), None);
413 }
414
415 #[tokio::test]
416 async fn response_body_window_blocks_until_consumption_is_acknowledged() {
417 let window = HttpBodySendWindow::new();
418 window
419 .reserve(protocol::HTTP_STREAM_INITIAL_WINDOW_BYTES)
420 .await
421 .expect("reserve initial window");
422 let blocked = tokio::spawn({
423 let window = window.clone();
424 async move { window.reserve(1).await }
425 });
426 assert!(
427 tokio::time::timeout(Duration::from_millis(20), async {
428 while !blocked.is_finished() {
429 tokio::task::yield_now().await;
430 }
431 })
432 .await
433 .is_err()
434 );
435
436 window
437 .update_consumed(1)
438 .await
439 .expect("return one byte of credit");
440 blocked
441 .await
442 .expect("join blocked reservation")
443 .expect("reserve after credit");
444 }
445
446 #[tokio::test]
447 async fn response_body_window_rejects_regression_and_over_credit() {
448 let window = HttpBodySendWindow::new();
449 window.reserve(10).await.expect("reserve bytes");
450 window.update_consumed(5).await.expect("consume bytes");
451 assert!(window.update_consumed(4).await.is_err());
452 assert!(window.update_consumed(11).await.is_err());
453 window
454 .update_consumed(5)
455 .await
456 .expect("duplicate cumulative ack");
457 }
458}