LCOV - code coverage report
Current view: top level - dashql-native-proxy/src - http_stream_manager.rs Coverage Total Hit
Test: DashQL 6e74948 Lines: 88.0 % 183 161
Test Date: 2026-11-06 17:46:50

            Line data    Source code
       1              : use http::header::{HeaderMap, HeaderValue};
       2              : use std::collections::HashMap;
       3              : use std::fmt;
       4              : use std::fmt::Display;
       5              : use std::sync::Arc;
       6              : use std::sync::RwLock;
       7              : use std::sync::atomic::AtomicUsize;
       8              : use std::time::Duration;
       9              : use std::time::Instant;
      10              : use tokio::sync::Mutex;
      11              : use tokio::time::timeout;
      12              : 
      13              : use crate::status::Status;
      14              : 
      15              : #[derive(Debug)]
      16              : pub enum HttpError {
      17              :     ErrorStatus(reqwest::StatusCode),
      18              :     ConnectError(String),
      19              :     OtherError(String),
      20              : }
      21              : 
      22              : impl Display for HttpError {
      23            1 :     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
      24            1 :         match &self {
      25            0 :             HttpError::ErrorStatus(status) => {
      26            0 :                 write!(f, "{}", &status)
      27              :             }
      28            1 :             HttpError::ConnectError(e) => {
      29            1 :                 write!(f, "{}", e)
      30              :             }
      31            0 :             HttpError::OtherError(e) => {
      32            0 :                 write!(f, "{}", e)
      33              :             },
      34              :         }
      35            1 :     }
      36              : }
      37              : 
      38              : impl From<&reqwest::Error> for HttpError {
      39            1 :     fn from(e: &reqwest::Error) -> HttpError {
      40              :         // Is a status?
      41            1 :         if let Some(s) = e.status() {
      42            0 :             return HttpError::ErrorStatus(s);
      43            1 :         }
      44              : 
      45              :         // Inpack inner error
      46            1 :         let mut inner: &dyn std::error::Error = &e;
      47            4 :         while let Some(i) = inner.source() {
      48            3 :             inner = i;
      49            3 :         }
      50              : 
      51            1 :         if e.is_connect() {
      52            1 :             return HttpError::ConnectError(inner.to_string());
      53              :         } else {
      54            0 :             return HttpError::OtherError(e.to_string());
      55              :         }
      56            1 :     }
      57              : }
      58              : 
      59              : #[derive(Debug)]
      60              : pub enum HttpServerStreamEvent {
      61              :     RequestFailed(HttpError),
      62              :     Header(reqwest::StatusCode, HeaderMap<HeaderValue>),
      63              :     BodyChunk(Vec<u8>),
      64              :     BodyEnd,
      65              :     BodyReadFailed(String),
      66              : }
      67              : 
      68              : #[derive(Debug, PartialEq)]
      69              : pub enum HttpServerStreamBatchEvent {
      70              :     StreamFailed,
      71              :     StreamFinished,
      72              :     FlushAfterClose,
      73              :     FlushAfterTimeout,
      74              :     FlushAfterBytes,
      75              : }
      76              : 
      77              : impl HttpServerStreamBatchEvent {
      78           10 :     pub fn to_str(&self) -> &'static str {
      79           10 :         match self {
      80            0 :             HttpServerStreamBatchEvent::StreamFailed => "StreamFailed",
      81            5 :             HttpServerStreamBatchEvent::StreamFinished => "StreamFinished",
      82            0 :             HttpServerStreamBatchEvent::FlushAfterClose => "FlushAfterClose",
      83            3 :             HttpServerStreamBatchEvent::FlushAfterTimeout => "FlushAfterTimeout",
      84            2 :             HttpServerStreamBatchEvent::FlushAfterBytes => "FlushAfterBytes",
      85              :         }
      86           10 :     }
      87              : }
      88              : 
      89              : /// A batch of response data read from an HTTP server stream in one read call
      90              : #[derive(Debug)]
      91              : pub struct HttpServerStreamBatch {
      92              :     /// The event that emitted the batch
      93              :     pub event: HttpServerStreamBatchEvent,
      94              :     /// The status
      95              :     pub status: Option<reqwest::StatusCode>,
      96              :     /// The headers
      97              :     pub headers: HeaderMap<HeaderValue>,
      98              :     /// The body chunks
      99              :     pub body_chunks: Vec<Vec<u8>>,
     100              :     /// The total body bytes in the batch
     101              :     pub total_body_bytes: usize,
     102              : }
     103              : 
     104              : impl Default for HttpServerStreamBatch {
     105           13 :     fn default() -> Self {
     106           13 :         Self {
     107           13 :             event: HttpServerStreamBatchEvent::StreamFailed,
     108           13 :             status: None,
     109           13 :             headers: HeaderMap::new(),
     110           13 :             body_chunks: Vec::default(),
     111           13 :             total_body_bytes: 0,
     112           13 :         }
     113           13 :     }
     114              : }
     115              : 
     116              : /// An HTTP response stream whose events are buffered by a background reader task
     117              : pub struct HttpServerStream {
     118              :     /// The response queue reading. Dropping it stops the reader task, which cancels the request.
     119              :     response_receiver: Mutex<tokio::sync::mpsc::Receiver<HttpServerStreamEvent>>,
     120              : }
     121              : 
     122              : /// The registry of open HTTP server streams, keyed by stream id
     123              : #[derive(Default)]
     124              : pub struct HttpStreamManager {
     125              :     /// The next message id
     126              :     pub next_stream_id: AtomicUsize,
     127              :     /// The server streams
     128              :     pub server_streams: RwLock<HashMap<usize, Arc<HttpServerStream>>>,
     129              : }
     130              : 
     131              : impl HttpStreamManager {
     132              :     /// Start a server stream
     133              :     #[allow(dead_code)]
     134           16 :     pub fn start_server_stream(
     135           16 :         self: &Arc<Self>,
     136           16 :         client: reqwest::Client,
     137           16 :         request: reqwest::Request,
     138           16 :     ) -> Result<usize, Status> {
     139              : 
     140              :         // Allocate an identifier for the stream
     141           16 :         let reg = self.clone();
     142           16 :         let stream_id = reg
     143           16 :             .next_stream_id
     144           16 :             .fetch_add(1, std::sync::atomic::Ordering::SeqCst);
     145              : 
     146              :         // Register the receiver
     147              :         const STREAM_CAPACITY: usize = 10;
     148           16 :         let (sender, receiver) = tokio::sync::mpsc::channel(STREAM_CAPACITY);
     149           16 :         let stream_entry = Arc::new(HttpServerStream {
     150           16 :             response_receiver: Mutex::new(receiver),
     151           16 :         });
     152           16 :         if let Ok(mut streams) = self.server_streams.write() {
     153           16 :             streams.insert(stream_id, stream_entry);
     154           16 :         }
     155              : 
     156              :         // Spawn the async reader
     157           16 :         tokio::spawn(async move {
     158           16 :             let reader = async {
     159              :                 // Execute a request
     160           16 :                 let mut response = match client.execute(request).await {
     161           10 :                     Ok(response) => response,
     162            1 :                     Err(e) => {
     163            1 :                         let mapped = HttpError::from(&e);
     164            1 :                         let queue_error = sender
     165            1 :                             .send(HttpServerStreamEvent::RequestFailed(mapped))
     166            1 :                             .await;
     167            1 :                         if let Err(e) = queue_error {
     168            0 :                             log::warn!("Failed forwarding request error: {}", e);
     169            0 :                             return;
     170            1 :                         }
     171            1 :                         return;
     172              :                     }
     173              :             };
     174              : 
     175              :             // Send the header
     176           10 :             if let Err(e) = sender
     177           10 :                 .send(HttpServerStreamEvent::Header(response.status(), std::mem::take(response.headers_mut())))
     178           10 :                 .await
     179              :             {
     180            0 :                 log::warn!("Failed writing header response to stream: {}", e);
     181            0 :                 return;
     182           10 :             }
     183              : 
     184              :             // Poll the result stream
     185           10 :             let mut stream_alive = true;
     186           35 :             while stream_alive {
     187              :                 // Read the next message from the body stream
     188           29 :                 let response = match response.chunk().await {
     189              :                     // Received bytes, forward the message as-is to receivers
     190           20 :                     Ok(Some(m)) => {
     191           20 :                         let buffer: Vec<u8> = m.into();
     192           20 :                         log::debug!(
     193              :                             "Receiving chunk from server stream {}, bytes={}",
     194              :                             stream_id,
     195            0 :                             buffer.len()
     196              :                         );
     197           20 :                         HttpServerStreamEvent::BodyChunk(buffer)
     198              :                     }
     199              :                     // Stream was closed, delete stream and return trailers
     200              :                     Ok(None) => {
     201            5 :                         stream_alive = false;
     202            5 :                         log::debug!("Server stream {} ended", stream_id);
     203            5 :                         HttpServerStreamEvent::BodyEnd
     204              :                     }
     205              :                     // Stream failed, send error to receivers and close the stream
     206            1 :                     Err(e) => {
     207            1 :                         stream_alive = false;
     208            1 :                         log::warn!("Failed reading from server stream: {}", e);
     209            1 :                         HttpServerStreamEvent::BodyReadFailed(e.to_string())
     210              :                     }
     211              :                 };
     212              : 
     213              :                 // Push the response into the queue
     214           26 :                 if let Err(e) = sender
     215           26 :                     .send(response)
     216           26 :                     .await
     217              :                 {
     218            0 :                     stream_alive = false;
     219            0 :                     log::warn!("Failed writing response to stream: {}", e);
     220           25 :                 }
     221              :             }
     222            7 :             };
     223              :             // A deleted stream drops the receiver, dropping the reader then cancels the request
     224           16 :             tokio::select! {
     225           16 :                 _ = sender.closed() => log::debug!("Server stream {} was deleted", stream_id),
     226           16 :                 _ = reader => {}
     227              :             }
     228           10 :         });
     229              : 
     230              :         // Return initial metadata
     231           16 :         Ok(stream_id)
     232           16 :     }
     233              : 
     234              :     /// Read from a server stream
     235              :     #[allow(dead_code)]
     236           17 :     pub async fn read_server_stream(
     237           17 :         self: &Arc<Self>,
     238           17 :         stream_id: usize,
     239           17 :         timeout_read_after: Duration,
     240           17 :         flush_batch_after: Duration,
     241           17 :         flush_batch_bytes: usize,
     242           17 :     ) -> Result<HttpServerStreamBatch, Status> {
     243           17 :         let started_at = Instant::now();
     244              : 
     245              :         // Try to find the stream.
     246              :         // If there is none, the request might have finished already.
     247           17 :         let reg = self.clone();
     248           17 :         let stream = if let Some(streams) = reg.server_streams.read().unwrap().get(&stream_id) {
     249           13 :             streams.clone()
     250              :         } else {
     251            4 :             log::debug!("Unknown stream id: {}", stream_id);
     252            4 :             return Err(Status::HttpStreamIsUnknown { stream_id });
     253              :         };
     254              : 
     255              :         // Acquire the receiver lock.
     256              :         // There can only be one active reader at a time.
     257           13 :         let mut receiver = stream.response_receiver.lock().await;
     258              : 
     259              :         // Receive messages and return them in batches
     260           13 :         let mut batch = HttpServerStreamBatch::default();
     261              :         loop {
     262              :             // Receive the next message from the reader
     263           30 :             let elapsed_since_start = started_at.elapsed();
     264           30 :             let response = if batch.body_chunks.len() == 0 && batch.status.is_none() {
     265              :                 // Initially, we check the `timeout_read_after` timeout.
     266           13 :                 let receive_timeout = timeout_read_after.checked_sub(elapsed_since_start).unwrap_or_default();
     267           13 :                 match timeout(receive_timeout, receiver.recv()).await {
     268           12 :                     Ok(Some(response)) =>  {
     269           12 :                         log::debug!("Http stream {} receiving event", stream_id);
     270           12 :                         response
     271              :                     },
     272              :                     Ok(None) => {
     273            0 :                         log::debug!("Http stream {} closed", stream_id);
     274            0 :                         return Err(Status::HttpStreamClosed { stream_id });
     275              :                     },
     276              :                     Err(_) => {
     277            1 :                         log::debug!("Http stream {} read timed out", stream_id);
     278            1 :                         return Err(Status::HttpStreamReadTimedOut { stream_id });
     279              :                     }
     280              :                 }
     281              :             } else {
     282              :                 // After receiving one batch or the status, we check the `flush_batch_after` timeout.
     283              :                 // A status must not get lost when the body takes longer than the read timeout.
     284           17 :                 let receive_timeout = match flush_batch_after.checked_sub(elapsed_since_start) {
     285           17 :                     Some(timeout) => {
     286           17 :                         timeout
     287              :                     },
     288              :                     None => {
     289            0 :                         batch.event = HttpServerStreamBatchEvent::FlushAfterTimeout;
     290            0 :                         return Ok(batch);
     291              :                     }
     292              :                 };
     293           17 :                 match timeout(receive_timeout, receiver.recv()).await {
     294           14 :                     Ok(Some(response)) => {
     295           14 :                         log::debug!("Http stream {} returning event", stream_id);
     296           14 :                         response
     297              :                     },
     298              :                     Ok(None) => {
     299            0 :                         log::debug!("Http stream {} closed", stream_id);
     300            0 :                         batch.event = HttpServerStreamBatchEvent::FlushAfterClose;
     301            0 :                         return Ok(batch);
     302              :                     },
     303              :                     Err(_) => {
     304            3 :                         log::debug!("Http stream {} read timed out", stream_id);
     305            3 :                         batch.event = HttpServerStreamBatchEvent::FlushAfterTimeout;
     306            3 :                         return Ok(batch);
     307              :                     }
     308              :                 }
     309              :             };
     310              : 
     311           26 :             match response {
     312              :                 // Request failed?
     313            1 :                 HttpServerStreamEvent::RequestFailed(e) => {
     314            1 :                     log::debug!("Http stream {} request failed: {}", stream_id, e);
     315            1 :                     if let Ok(mut streams) = self.server_streams.write() {
     316            1 :                         streams.remove(&stream_id);
     317            1 :                     }
     318            1 :                     return Err(Status::HttpRequestFailed { stream_id, error: e.to_string() });
     319              :                 }
     320              :                 // Received headers
     321            9 :                 HttpServerStreamEvent::Header(status, headers) => {
     322            9 :                     log::debug!("Http stream {} receiving header", stream_id);
     323            9 :                     batch.status = Some(status);
     324            9 :                     batch.headers = headers;
     325              :                 },
     326              :                 // Return the message
     327           10 :                 HttpServerStreamEvent::BodyChunk(m) => {
     328           10 :                     log::debug!("Http stream {} receiving body chunk", stream_id);
     329              :                     // Add the message to the current batch and check if we should flush
     330           10 :                     batch.total_body_bytes += m.len();
     331           10 :                     batch.body_chunks.push(m);
     332              : 
     333              :                     // Flush if we hit the message size
     334           10 :                     if batch.total_body_bytes > flush_batch_bytes {
     335            2 :                         batch.event = HttpServerStreamBatchEvent::FlushAfterBytes;
     336            2 :                         return Ok(batch);
     337            8 :                     }
     338              :                 },
     339              :                 // An error occurred,.
     340              :                 // Throw away any intermediate messages that we held back.
     341            1 :                 HttpServerStreamEvent::BodyReadFailed(e) => {
     342            1 :                     log::debug!("Http stream {} failed reading body", stream_id);
     343            1 :                     if let Ok(mut streams) = self.server_streams.write() {
     344            1 :                         streams.remove(&stream_id);
     345            1 :                     }
     346            1 :                     return Err(Status::HttpStreamReadFailed{
     347            1 :                         stream_id,
     348            1 :                         error: e
     349            1 :                     });
     350              :                 },
     351              :                 // We reached the end of the stream.
     352              :                 // Flush the current batch with trailers and an EOS marker.
     353              :                 HttpServerStreamEvent::BodyEnd => {
     354            5 :                     log::debug!("Http stream {} ended", stream_id);
     355            5 :                     if let Ok(mut streams) = self.server_streams.write() {
     356            5 :                         streams.remove(&stream_id);
     357            5 :                     }
     358            5 :                     batch.event = HttpServerStreamBatchEvent::StreamFinished;
     359            5 :                     return Ok(batch);
     360              :                 }
     361              :             }
     362              :         }
     363           17 :     }
     364              : 
     365              :     /// Cancellation is done by just dropping everything, see `start_server_stream`
     366              :     #[allow(dead_code)]
     367            5 :     pub async fn destroy_server_stream(self: &Arc<Self>, stream_id: usize) {
     368            5 :         if let Ok(mut streams) = self.server_streams.write() {
     369            5 :             streams.remove(&stream_id);
     370            5 :         }
     371            5 :     }
     372              : }
        

Generated by: LCOV version 2.0-1