1use crate::proxy_common::{DownstreamStateMachine, ResponseStateMachine};
29use crate::subrequest::*;
30use crate::{PreparedSubrequest, Session};
31use bytes::Bytes;
32use futures::FutureExt;
33use log::{debug, warn};
34use pingora_core::protocols::http::{subrequest::server::SubrequestHandle, HttpTask};
35use pingora_error::{Error, ErrorType::*, OrErr, Result};
36use tokio::sync::mpsc;
37
38pub enum InputBodyType {
39 Preset(InputBody),
41 SaveBody(usize),
43}
44
45#[derive(Debug, Default)]
47pub struct PipeSubrequestState {
48 pub saved_body: Option<SavedBody>,
50 pub header_received: bool,
53 pub join_handle: Option<tokio::task::JoinHandle<()>>,
56 pub pipe_rx: Option<mpsc::Receiver<HttpTask>>,
62}
63
64impl PipeSubrequestState {
65 pub fn snapshot_for_error(&mut self) -> Self {
70 PipeSubrequestState {
71 saved_body: self.saved_body.clone(),
72 header_received: self.header_received,
73 join_handle: None,
74 pipe_rx: self.pipe_rx.take(),
75 }
76 }
77}
78
79pub struct PipeSubrequestError {
80 pub state: PipeSubrequestState,
81 pub from_subreq: bool,
84 pub error: Box<Error>,
85}
86impl PipeSubrequestError {
87 pub fn new(
88 error: impl Into<Box<Error>>,
89 from_subreq: bool,
90 state: PipeSubrequestState,
91 ) -> Self {
92 PipeSubrequestError {
93 error: error.into(),
94 from_subreq,
95 state,
96 }
97 }
98}
99
100fn map_pipe_err<T, E: Into<Box<Error>>>(
101 result: Result<T, E>,
102 from_subreq: bool,
103 state: &mut PipeSubrequestState,
104) -> Result<T, PipeSubrequestError> {
105 result.map_err(|e| PipeSubrequestError::new(e, from_subreq, state.snapshot_for_error()))
106}
107
108#[derive(Debug, Clone)]
109pub struct SavedBody {
110 body: Vec<Bytes>,
111 complete: bool,
112 truncated: bool,
113 length: usize,
114 max_length: usize,
115}
116
117impl SavedBody {
118 pub fn new(max_length: usize) -> Self {
119 SavedBody {
120 body: vec![],
121 complete: false,
122 truncated: false,
123 length: 0,
124 max_length,
125 }
126 }
127
128 pub fn save_body_bytes(&mut self, body_bytes: Bytes) -> bool {
129 let len = body_bytes.len();
130 if self.length + len > self.max_length {
131 self.truncated = true;
132 return false;
133 }
134 self.length += len;
135 self.body.push(body_bytes);
136 true
137 }
138
139 pub fn is_body_complete(&self) -> bool {
140 self.complete && !self.truncated
141 }
142
143 pub fn set_body_complete(&mut self) {
144 self.complete = true;
145 }
146}
147
148#[derive(Debug, Clone)]
149pub enum InputBody {
150 NoBody,
151 Bytes(Vec<Bytes>),
152 }
154
155impl InputBody {
156 pub(crate) fn into_reader(self) -> InputBodyReader {
157 InputBodyReader(match self {
158 InputBody::NoBody => vec![].into_iter(),
159 InputBody::Bytes(v) => v.into_iter(),
160 })
161 }
162
163 pub fn is_body_empty(&self) -> bool {
164 match self {
165 InputBody::NoBody => true,
166 InputBody::Bytes(v) => v.is_empty(),
167 }
168 }
169}
170
171impl std::convert::From<SavedBody> for InputBody {
172 fn from(body: SavedBody) -> Self {
173 if body.body.is_empty() {
174 InputBody::NoBody
175 } else {
176 InputBody::Bytes(body.body)
177 }
178 }
179}
180
181pub async fn pipe_subrequest<F>(
182 session: &mut Session,
183 mut subrequest: PreparedSubrequest,
184 subrequest_handle: SubrequestHandle,
185 mut task_filter: F,
186 input_body: InputBodyType,
187) -> std::result::Result<PipeSubrequestState, PipeSubrequestError>
188where
189 F: FnMut(HttpTask) -> Result<Option<HttpTask>>,
190{
191 let (maybe_preset_body, saved_body) = match input_body {
192 InputBodyType::Preset(body) => (Some(body), None),
193 InputBodyType::SaveBody(limit) => (None, Some(SavedBody::new(limit))),
194 };
195 let use_preset_body = maybe_preset_body.is_some();
196
197 let mut response_state = ResponseStateMachine::new();
198 let (no_body_input, mut maybe_preset_reader) = if use_preset_body {
199 let preset_body = maybe_preset_body.expect("checked above");
200 (preset_body.is_body_empty(), Some(preset_body.into_reader()))
201 } else {
202 (session.as_mut().is_body_done(), None)
203 };
204 let mut downstream_state = DownstreamStateMachine::new(no_body_input);
205
206 let mut state = PipeSubrequestState {
207 saved_body,
208 ..Default::default()
209 };
210
211 let join_handle = tokio::spawn(async move {
213 if no_body_input {
214 subrequest
215 .session_mut()
216 .as_subrequest_mut()
217 .expect("PreparedSubrequest must be subrequest")
218 .clear_request_body_headers();
219 }
220 let _ = subrequest.run().await;
221 });
222 state.join_handle = Some(join_handle);
223 let tx = subrequest_handle.tx;
224 state.pipe_rx = Some(subrequest_handle.rx);
228
229 let mut wants_body = false;
230 let mut wants_body_rx_err = false;
231 let mut wants_body_rx = subrequest_handle.subreq_wants_body;
232
233 let mut proxy_error_rx_err = false;
234 let mut proxy_error_rx = subrequest_handle.subreq_proxy_error;
235
236 while !downstream_state.is_done() || !response_state.is_done() {
239 let send_permit = tx
240 .try_reserve()
241 .or_err(InternalError, "try_reserve() body pipe for subrequest");
242
243 tokio::select! {
244 task = state.pipe_rx.as_mut().expect("pipe_rx always set after spawn").recv(), if !response_state.upstream_done() => {
245 debug!("upstream event: {:?}", task);
246 if let Some(t) = task {
247 if matches!(&t, HttpTask::Header(..)) {
249 state.header_received = true;
250 }
251 const TASK_BUFFER_SIZE: usize = 4;
253 let mut tasks = Vec::with_capacity(TASK_BUFFER_SIZE);
254 let task = map_pipe_err(task_filter(t), false, &mut state)?;
255 if let Some(filtered) = task {
256 tasks.push(filtered);
257 }
258 while let Some(maybe_task) = tokio::task::unconstrained(state.pipe_rx.as_mut().expect("pipe_rx always set after spawn").recv()).now_or_never() {
260 if let Some(t) = maybe_task {
261 if matches!(&t, HttpTask::Header(..)) {
262 state.header_received = true;
263 }
264 let task = map_pipe_err(task_filter(t), false, &mut state)?;
265 if let Some(filtered) = task {
266 tasks.push(filtered);
267 }
268 } else {
269 break
270 }
271 }
272 let response_done = map_pipe_err(session.write_response_tasks(tasks).await, false, &mut state)?;
275
276 response_state.maybe_set_upstream_done(response_done);
279 downstream_state.maybe_finished(!use_preset_body && session.is_body_done());
282 } else {
283 debug!("upstream channel closed early");
284 response_state.maybe_set_upstream_done(true);
285 }
286 },
287
288 res = &mut wants_body_rx, if !wants_body && !wants_body_rx_err => {
289 if res.is_err() {
292 wants_body_rx_err = true;
293 } else {
294 wants_body = true;
295 }
296 }
297
298 res = &mut proxy_error_rx, if !proxy_error_rx_err => {
299 if let Ok(e) = res {
300 return Err(PipeSubrequestError::new(e, true, state));
302 } else {
303 proxy_error_rx_err = true;
305 }
306 }
307
308 _ = tx.reserve(), if downstream_state.is_reading() && send_permit.is_err() => {
309 downstream_state.maybe_finished(tx.is_closed());
311 debug!("waiting for permit {send_permit:?}, upstream closed {}", tx.is_closed());
312 },
318
319 body = session.downstream_session.read_body_or_idle(downstream_state.is_done()),
320 if wants_body && !use_preset_body && downstream_state.can_poll() && send_permit.is_ok() => {
321 debug!("downstream event: main body for subrequest");
324 let body = map_pipe_err(body.map_err(|e| e.into_down()), false, &mut state)?;
325
326 if body.is_none() && session.is_upgrade_req() {
329 response_state.maybe_set_upstream_done(true);
330 }
331
332 let is_body_done = session.is_body_done();
333 let request_done = map_pipe_err(send_body_to_pipe(
334 session,
335 body,
336 is_body_done,
337 state.saved_body.as_mut(),
338 send_permit.expect("checked is_ok()"),
339 )
340 .await, false, &mut state)?;
341
342 downstream_state.maybe_finished(request_done);
343
344 },
345
346 body = async { maybe_preset_reader.as_mut().expect("preset body set").read_body() },
348 if wants_body && use_preset_body && !downstream_state.is_done() && downstream_state.can_poll() && send_permit.is_ok() => {
349 debug!("downstream event: preset body for subrequest");
350
351 let is_body_done = body.is_none();
355 let request_done = map_pipe_err(do_send_body_to_pipe(
357 body,
358 is_body_done,
359 None,
360 send_permit.expect("checked is_ok()"),
361 ), false, &mut state)?;
362 downstream_state.maybe_finished(request_done);
363
364 },
365
366 else => break,
367 }
368 }
369 if let Ok(e) = proxy_error_rx.try_recv() {
372 return Err(PipeSubrequestError::new(e, true, state));
373 }
374 Ok(state)
375}
376
377async fn send_body_to_pipe(
379 session: &mut Session,
380 mut data: Option<Bytes>,
381 end_of_body: bool,
382 saved_body: Option<&mut SavedBody>,
383 tx: mpsc::Permit<'_, HttpTask>,
384) -> Result<bool> {
385 let end_of_body = end_of_body || data.is_none();
389
390 session
391 .downstream_modules_ctx
392 .request_body_filter(&mut data, end_of_body)
393 .await?;
394
395 do_send_body_to_pipe(data, end_of_body, saved_body, tx)
396}
397
398fn do_send_body_to_pipe(
399 data: Option<Bytes>,
400 end_of_body: bool,
401 mut saved_body: Option<&mut SavedBody>,
402 tx: mpsc::Permit<'_, HttpTask>,
403) -> Result<bool> {
404 let upstream_end_of_body = end_of_body || data.is_none();
406
407 if !upstream_end_of_body && data.as_ref().is_some_and(|d| d.is_empty()) {
412 return Ok(false);
413 }
414
415 debug!(
416 "Read {} bytes body from downstream",
417 data.as_ref().map_or(-1, |d| d.len() as isize)
418 );
419
420 if let Some(capture) = saved_body.as_mut() {
421 if capture.is_body_complete() {
422 warn!("subrequest trying to save body after body is complete");
423 } else if let Some(d) = data.as_ref() {
424 capture.save_body_bytes(d.clone());
425 }
426 if end_of_body {
427 capture.set_body_complete();
428 }
429 }
430
431 tx.send(HttpTask::Body(data, upstream_end_of_body));
432
433 Ok(end_of_body)
434}
435
436#[cfg(test)]
437mod tests {
438 use super::*;
439 use crate::subrequest::Ctx as SubrequestCtx;
440 use crate::{Session, Subrequest, SubrequestSpawner};
441 use async_trait::async_trait;
442 use pingora_core::protocols::http::ServerSession as HttpSession;
443 use pingora_http::ResponseHeader;
444 use std::sync::{Arc, Barrier};
445 use tokio::io::{AsyncReadExt, AsyncWriteExt};
446
447 struct NoopApp;
449
450 #[async_trait]
451 impl Subrequest for NoopApp {
452 async fn process_subrequest(
453 self: Arc<Self>,
454 _session: Box<HttpSession>,
455 _ctx: Box<SubrequestCtx>,
456 ) {
457 }
458 }
459
460 struct HeaderThenErrorApp {
461 header_selected: Arc<Barrier>,
462 }
463
464 #[async_trait]
465 impl Subrequest for HeaderThenErrorApp {
466 async fn process_subrequest(
467 self: Arc<Self>,
468 mut session: Box<HttpSession>,
469 _ctx: Box<SubrequestCtx>,
470 ) {
471 let mut header =
472 ResponseHeader::build(200, Some(1)).expect("test response header should build");
473 header
474 .insert_header(http::header::CONTENT_LENGTH, "0")
475 .expect("test content-length should be valid");
476 session
477 .write_response_header(Box::new(header))
478 .await
479 .expect("test response header should be written");
480
481 self.header_selected.wait();
482 session.on_proxy_failure(Error::new(FileReadError));
483 self.header_selected.wait();
484 }
485 }
486
487 async fn mock_session() -> Session {
488 let input = b"GET / HTTP/1.1\r\nHost: test\r\n\r\n";
489 let mock_io = tokio_test::io::Builder::new().read(&input[..]).build();
490 let mut session = Session::new_h1(Box::new(mock_io) as pingora_core::protocols::Stream);
491 session
492 .downstream_session
493 .read_request()
494 .await
495 .expect("mock request should parse");
496 session
497 }
498
499 async fn writable_mock_session() -> Session {
500 let input = b"GET / HTTP/1.1\r\nHost: test\r\n\r\n";
501 let (mut client, server) = tokio::io::duplex(1024);
502 client
503 .write_all(input)
504 .await
505 .expect("mock request should be written");
506 tokio::spawn(async move {
507 let mut response = Vec::new();
508 let _ = client.read_to_end(&mut response).await;
509 });
510
511 let mut session = Session::new_h1(Box::new(server) as pingora_core::protocols::Stream);
512 session
513 .downstream_session
514 .read_request()
515 .await
516 .expect("mock request should parse");
517 session
518 }
519
520 fn hold_header_until_error(
521 task: HttpTask,
522 header_selected: &Barrier,
523 ) -> Result<Option<HttpTask>> {
524 if matches!(&task, HttpTask::Header(..)) {
525 header_selected.wait();
526 header_selected.wait();
527 }
528 Ok(Some(task))
529 }
530
531 async fn pipe_header_then_error(
532 ) -> std::result::Result<PipeSubrequestState, PipeSubrequestError> {
533 let header_selected = Arc::new(Barrier::new(2));
534 let mut session = writable_mock_session().await;
535 let app = HeaderThenErrorApp {
536 header_selected: Arc::clone(&header_selected),
537 };
538 let spawner = SubrequestSpawner::new(Arc::new(app));
539 let ctx = SubrequestCtx::builder().body_mode(BodyMode::NoBody).build();
540 let (subrequest, handle) = spawner.create_subrequest(session.as_downstream(), ctx);
541 pipe_subrequest(
542 &mut session,
543 subrequest,
544 handle,
545 move |task| hold_header_until_error(task, &header_selected),
546 InputBodyType::Preset(InputBody::NoBody),
547 )
548 .await
549 }
550
551 #[tokio::test]
552 async fn no_header_received_when_subrequest_exits_silently() {
553 let mut session = mock_session().await;
554
555 let spawner = SubrequestSpawner::new(Arc::new(NoopApp));
556 let ctx = SubrequestCtx::builder().body_mode(BodyMode::NoBody).build();
557 let (subrequest, handle) = spawner.create_subrequest(session.as_downstream(), ctx);
558
559 let result = pipe_subrequest(
560 &mut session,
561 subrequest,
562 handle,
563 |task| Ok(Some(task)),
564 InputBodyType::Preset(InputBody::NoBody),
565 )
566 .await;
567
568 let state =
569 result.unwrap_or_else(|e| panic!("pipe should return Ok, not Err: {:?}", e.error));
570 assert!(
571 !state.header_received,
572 "no header should have been received from the no-op subrequest"
573 );
574 assert!(state.join_handle.is_some(), "task handle should be set");
575 }
576
577 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
578 async fn preserves_proxy_error_queued_before_pipe_completion() {
579 let error = pipe_header_then_error()
580 .await
581 .expect_err("subrequest proxy error should be preserved");
582 assert!(error.from_subreq);
583 assert_eq!(error.error.root_etype(), &FileReadError);
584 assert!(error.state.header_received);
585 }
586}