Skip to main content

rivet_envoy_client/
http.rs

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};
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	queue: Arc<HttpRequestBodyQueue>,
31	event_tx: Option<mpsc::UnboundedSender<RequestBodyEvent>>,
32	terminal: bool,
33}
34
35#[derive(Debug)]
36struct HttpRequestBodyQueueState {
37	chunks: VecDeque<Vec<u8>>,
38	finished: bool,
39	abort: Option<HttpRequestBodyError>,
40	receiver_closed: bool,
41}
42
43/// Byte-windowed request bodies use a coalescing queue instead of a frame-count bounded channel.
44/// The producer is the synchronous actor inbox, so this is a deliberately short forced-sync lock;
45/// it is never held across an await. Byte credit separately caps the total queued payload.
46#[derive(Debug)]
47pub struct HttpRequestBodyQueue {
48	state: SyncMutex<HttpRequestBodyQueueState>,
49	ready: Notify,
50}
51
52impl HttpRequestBodyQueue {
53	pub fn new() -> Arc<Self> {
54		Arc::new(Self {
55			state: SyncMutex::new(HttpRequestBodyQueueState {
56				chunks: VecDeque::new(),
57				finished: false,
58				abort: None,
59				receiver_closed: false,
60			}),
61			ready: Notify::new(),
62		})
63	}
64
65	/// Queues `data`, copying it once into the coalesced chunks.
66	pub fn push(&self, data: impl AsRef<[u8]>) -> bool {
67		let mut data = data.as_ref();
68		let mut state = self.state.lock();
69		if state.finished || state.abort.is_some() || state.receiver_closed {
70			return false;
71		}
72
73		if let Some(last) = state.chunks.back_mut() {
74			let available = HTTP_BODY_MAX_CHUNK_SIZE.saturating_sub(last.len());
75			let append = available.min(data.len());
76			last.extend_from_slice(&data[..append]);
77			data = &data[append..];
78		}
79		for chunk in data.chunks(HTTP_BODY_MAX_CHUNK_SIZE) {
80			state.chunks.push_back(chunk.to_vec());
81		}
82		drop(state);
83		self.ready.notify_one();
84		true
85	}
86
87	pub fn finish(&self) {
88		let mut state = self.state.lock();
89		if state.abort.is_none() {
90			state.finished = true;
91		}
92		drop(state);
93		self.ready.notify_waiters();
94	}
95
96	pub fn abort(&self, error: HttpRequestBodyError) {
97		let mut state = self.state.lock();
98		if state.abort.is_none() && !state.finished {
99			state.chunks.clear();
100			state.abort = Some(error);
101		}
102		drop(state);
103		self.ready.notify_waiters();
104	}
105
106	fn close_receiver(&self) {
107		let mut state = self.state.lock();
108		state.receiver_closed = true;
109		state.chunks.clear();
110		drop(state);
111		self.ready.notify_waiters();
112	}
113
114	async fn recv(&self) -> Result<Option<Vec<u8>>, HttpRequestBodyError> {
115		loop {
116			let notified = self.ready.notified();
117			{
118				let mut state = self.state.lock();
119				if let Some(error) = &state.abort {
120					return Err(error.clone());
121				}
122				if let Some(chunk) = state.chunks.pop_front() {
123					return Ok(Some(chunk));
124				}
125				if state.finished || state.receiver_closed {
126					return Ok(None);
127				}
128			}
129			notified.await;
130		}
131	}
132}
133
134#[derive(Debug)]
135pub(crate) enum RequestBodyEvent {
136	Consumed(u64),
137	Cancelled,
138}
139
140impl HttpRequestBodyStream {
141	fn handle_chunk(
142		&mut self,
143		chunk: Option<Vec<u8>>,
144	) -> Result<Option<Vec<u8>>, HttpRequestBodyError> {
145		match chunk {
146			Some(chunk) => {
147				if let Some(event_tx) = &self.event_tx {
148					let _ = event_tx.send(RequestBodyEvent::Consumed(chunk.len() as u64));
149				}
150				Ok(Some(chunk))
151			}
152			None => {
153				self.terminal = true;
154				Ok(None)
155			}
156		}
157	}
158
159	/// A body read from `queue` without reporting consumption to a flow-control window.
160	pub fn new(queue: Arc<HttpRequestBodyQueue>) -> Self {
161		Self {
162			queue,
163			event_tx: None,
164			terminal: false,
165		}
166	}
167
168	pub(crate) fn new_with_flow_control(
169		queue: Arc<HttpRequestBodyQueue>,
170		event_tx: mpsc::UnboundedSender<RequestBodyEvent>,
171	) -> Self {
172		Self {
173			queue,
174			event_tx: Some(event_tx),
175			terminal: false,
176		}
177	}
178
179	pub async fn recv(&mut self) -> Result<Option<Vec<u8>>, HttpRequestBodyError> {
180		let chunk = match self.queue.recv().await {
181			Ok(chunk) => chunk,
182			Err(error) => {
183				self.terminal = true;
184				return Err(error);
185			}
186		};
187		self.handle_chunk(chunk)
188	}
189}
190
191impl Drop for HttpRequestBodyStream {
192	fn drop(&mut self) {
193		self.queue.close_receiver();
194		if !self.terminal
195			&& let Some(event_tx) = &self.event_tx
196		{
197			let _ = event_tx.send(RequestBodyEvent::Cancelled);
198		}
199	}
200}
201
202#[derive(Debug)]
203struct HttpBodySendWindowState {
204	sent_bytes: u64,
205	consumed_bytes: u64,
206}
207
208#[derive(Debug)]
209pub(crate) struct HttpBodySendWindow {
210	state: Mutex<HttpBodySendWindowState>,
211	credit_available: Notify,
212}
213
214impl HttpBodySendWindow {
215	pub(crate) fn new() -> Arc<Self> {
216		Arc::new(Self {
217			state: Mutex::new(HttpBodySendWindowState {
218				sent_bytes: 0,
219				consumed_bytes: 0,
220			}),
221			credit_available: Notify::new(),
222		})
223	}
224
225	pub(crate) async fn reserve(&self, bytes: u64) -> AnyhowResult<()> {
226		ensure!(
227			bytes <= protocol::HTTP_STREAM_INITIAL_WINDOW_BYTES,
228			"HTTP body frame exceeds the flow-control window"
229		);
230		if bytes == 0 {
231			return Ok(());
232		}
233
234		loop {
235			let notified = self.credit_available.notified();
236			{
237				let mut state = self.state.lock().await;
238				let outstanding = state
239					.sent_bytes
240					.checked_sub(state.consumed_bytes)
241					.context("HTTP body flow-control accounting underflow")?;
242				let available = protocol::HTTP_STREAM_INITIAL_WINDOW_BYTES
243					.checked_sub(outstanding)
244					.context("HTTP body flow-control window exceeded")?;
245				if bytes <= available {
246					state.sent_bytes = state
247						.sent_bytes
248						.checked_add(bytes)
249						.context("HTTP body sent-byte counter overflow")?;
250					return Ok(());
251				}
252			}
253			notified.await;
254		}
255	}
256
257	pub(crate) async fn update_consumed(&self, consumed_bytes: u64) -> AnyhowResult<()> {
258		let mut state = self.state.lock().await;
259		ensure!(
260			consumed_bytes >= state.consumed_bytes,
261			"HTTP body consumed-byte counter moved backwards"
262		);
263		ensure!(
264			consumed_bytes <= state.sent_bytes,
265			"HTTP body consumed-byte counter exceeds sent bytes"
266		);
267		if consumed_bytes == state.consumed_bytes {
268			return Ok(());
269		}
270		state.consumed_bytes = consumed_bytes;
271		drop(state);
272		self.credit_available.notify_waiters();
273		Ok(())
274	}
275}
276
277/// HTTP request/response types used by the envoy client.
278pub struct HttpRequest {
279	pub method: String,
280	pub path: String,
281	pub headers: HashMap<String, String>,
282	pub body: Option<Vec<u8>>,
283	/// If the request is streamed, body chunks arrive on this channel.
284	pub body_stream: Option<HttpRequestBodyStream>,
285}
286
287pub struct HttpResponse {
288	pub status: u16,
289	pub headers: HashMap<String, String>,
290	pub body: Option<Vec<u8>>,
291	/// If set, the response is streamed. The envoy client reads chunks and sends
292	/// `ToRivetResponseChunk` for each one.
293	pub body_stream: Option<HttpResponseBodyStream>,
294}
295
296/// A chunk in a streaming HTTP response.
297pub enum ResponseChunk {
298	Data { data: Vec<u8>, finish: bool },
299	Error(String),
300}
301
302pub struct HttpResponseBodyStream {
303	rx: mpsc::Receiver<ResponseChunk>,
304	on_drop: Option<Box<dyn FnOnce() + Send>>,
305}
306
307impl HttpResponseBodyStream {
308	pub fn set_on_drop(&mut self, on_drop: impl FnOnce() + Send + 'static) {
309		self.on_drop = Some(Box::new(on_drop));
310	}
311
312	pub async fn recv(&mut self) -> Option<ResponseChunk> {
313		self.rx.recv().await
314	}
315}
316
317impl From<mpsc::Receiver<ResponseChunk>> for HttpResponseBodyStream {
318	fn from(rx: mpsc::Receiver<ResponseChunk>) -> Self {
319		Self { rx, on_drop: None }
320	}
321}
322
323impl Drop for HttpResponseBodyStream {
324	fn drop(&mut self) {
325		if let Some(on_drop) = self.on_drop.take() {
326			on_drop();
327		}
328	}
329}
330
331