mtp/mtp-webserver/src/stream.rs
Alex Emmet 00f0aaeeff
All checks were successful
CI / checks (push) Successful in 5m29s
[Add] TCP server to core MTP (HTTP/1.1 & HTTP/2) compatibility
2026-07-21 00:43:00 +02:00

100 lines
2.8 KiB
Rust

use bytes::Bytes;
use http::{HeaderMap, HeaderName, HeaderValue, Method, StatusCode, Uri};
use std::net::SocketAddr;
use tokio::sync::mpsc;
/// An owned HTTP request passed to a route handler.
#[derive(Clone, Debug)]
pub struct HttpRequest {
pub method: Method,
pub uri: Uri,
pub headers: HeaderMap,
pub body: Option<Bytes>,
pub remote_addr: SocketAddr,
}
/// An HTTP response returned from a route handler.
pub struct HttpResponse {
pub status: StatusCode,
pub headers: HeaderMap,
pub body: Vec<Bytes>,
pub(crate) stream: Option<mpsc::Receiver<Bytes>>,
}
impl HttpResponse {
pub fn new(status: StatusCode) -> Self {
Self {
status,
headers: HeaderMap::new(),
body: Vec::new(),
stream: None,
}
}
pub fn status(mut self, status: StatusCode) -> Self {
self.status = status;
self
}
pub fn header(mut self, key: &str, value: &str) -> Self {
match (key.parse::<HeaderName>(), value.parse::<HeaderValue>()) {
(Ok(key), Ok(value)) => {
self.headers.insert(key, value);
}
(Err(error), _) => {
tracing::warn!(%error, key, "discarding invalid HTTP response header")
}
(_, Err(error)) => {
tracing::warn!(%error, key, "discarding invalid HTTP response header")
}
}
self
}
pub fn try_header(mut self, key: &str, value: &str) -> Result<Self, String> {
let key = key.parse::<HeaderName>().map_err(|e| e.to_string())?;
let value = value.parse::<HeaderValue>().map_err(|e| e.to_string())?;
self.headers.insert(key, value);
Ok(self)
}
pub fn body(mut self, chunk: impl Into<Bytes>) -> Self {
self.body.push(chunk.into());
self
}
/// Stream response chunks as they become available instead of buffering them.
pub fn stream(mut self, chunks: mpsc::Receiver<Bytes>) -> Self {
self.stream = Some(chunks);
self
}
}
impl Default for HttpResponse {
fn default() -> Self {
Self::new(StatusCode::OK)
}
}
#[deprecated(note = "use HttpRequest")]
pub type Http3Request = HttpRequest;
#[deprecated(note = "use HttpResponse")]
pub type Http3Response = HttpResponse;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn response_collects_headers_and_body_chunks() {
let response = HttpResponse::new(StatusCode::CREATED)
.header("content-type", "text/plain")
.body("hello")
.body(" world");
assert_eq!(response.status, StatusCode::CREATED);
assert_eq!(response.headers["content-type"], "text/plain");
assert_eq!(
response.body,
vec![Bytes::from("hello"), Bytes::from(" world")]
);
}
}