Read a response body fully into memory, rejecting anything over `max` bytes. Used by the buffered proxy path so a misbehaving upstream cannot force unbounded allocation. The `Content-Length` check is a fast early-out; the chunk loop is the real guard and bounds an absent, chunked, or under-reported length. The cap counts the bytes reqwest yields: with no decompression features enabled (see `Cargo
(
mut response: reqwest::Response,
max: usize,
)
| 688 | /// Over-cap responses fail as `UpstreamProtocol` and are never partially |
| 689 | /// returned. |
| 690 | async fn read_capped_response_body( |
| 691 | mut response: reqwest::Response, |
| 692 | max: usize, |
| 693 | ) -> Result<bytes::Bytes, RouterError> { |
| 694 | if let Some(len) = response.content_length() |
| 695 | && len > max as u64 |
| 696 | { |
| 697 | return Err(RouterError::UpstreamProtocol(format!( |
| 698 | "inference response body of {len} bytes exceeds the {max} byte cap" |
| 699 | ))); |
| 700 | } |
| 701 | |
| 702 | // Preallocate to the advertised length when it is within the cap; the loop |
| 703 | // still enforces the bound for an absent or under-reported length. |
| 704 | let mut body: Vec<u8> = match response.content_length() { |
| 705 | Some(len) if len <= max as u64 => Vec::with_capacity(usize::try_from(len).unwrap_or(max)), |
| 706 | _ => Vec::new(), |
| 707 | }; |
| 708 | while let Some(chunk) = response |
| 709 | .chunk() |
| 710 | .await |
| 711 | .map_err(|e| RouterError::UpstreamProtocol(format!("failed to read response body: {e}")))? |
| 712 | { |
| 713 | if body.len() + chunk.len() > max { |
| 714 | return Err(RouterError::UpstreamProtocol(format!( |
| 715 | "inference response body exceeds the {max} byte cap" |
| 716 | ))); |
| 717 | } |
| 718 | body.extend_from_slice(&chunk); |
| 719 | } |
| 720 | Ok(bytes::Bytes::from(body)) |
| 721 | } |
| 722 | |
| 723 | /// Forward a raw HTTP request to the backend, returning response headers |
| 724 | /// immediately without buffering the body. |
no test coverage detected