use std::{
error::Error as StdError,
fmt,
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
use bytes::Bytes;
use futures_util::{Stream, StreamExt};
use http::{
self, HeaderMap, HeaderName, HeaderValue, StatusCode,
header::{self, InvalidHeaderName},
};
use http_body::{Body as HttpBodyTrait, Frame, SizeHint};
use http_body_util::Full;
use crate::{
any_map::{AnyMap, SerializableAny},
app::App,
error::Error,
};
pub type BoxError = Box<dyn StdError + Send + Sync>;
pub enum StreamKind {
Bytes(Pin<Box<dyn Stream<Item = Result<Bytes, BoxError>> + Send + Sync>>),
Frames(Pin<Box<dyn Stream<Item = Result<Frame<Bytes>, BoxError>> + Send + Sync>>),
}
#[derive(Default)]
pub enum HttpBody {
#[default]
Empty,
Full(Full<Bytes>),
Stream(StreamKind),
}
impl fmt::Debug for HttpBody {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
HttpBody::Empty => f.debug_struct("HttpBody::Empty").finish(),
HttpBody::Full(b) => f.debug_struct("HttpBody::Full").field("body", b).finish(),
HttpBody::Stream(_) => f.debug_struct("HttpBody::Stream").finish(),
}
}
}
impl HttpBody {
pub fn full(bytes: Bytes) -> Self {
HttpBody::Full(Full::new(bytes))
}
pub fn stream<S>(stream: S) -> Self
where
S: Stream<Item = Result<Bytes, BoxError>> + Send + Sync + 'static,
{
HttpBody::Stream(StreamKind::Bytes(Box::pin(stream)))
}
pub fn stream_frames<S>(stream: S) -> Self
where
S: Stream<Item = Result<Frame<Bytes>, BoxError>> + Send + Sync + 'static,
{
HttpBody::Stream(StreamKind::Frames(Box::pin(stream)))
}
}
impl HttpBodyTrait for HttpBody {
type Data = Bytes;
type Error = BoxError;
fn poll_frame(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
match self.get_mut() {
HttpBody::Empty => Poll::Ready(None),
HttpBody::Full(full) => Pin::new(full)
.poll_frame(cx)
.map(|opt| opt.map(|res| res.map_err(Into::into))),
HttpBody::Stream(kind) => match kind {
StreamKind::Bytes(stream) => match stream.as_mut().poll_next(cx) {
Poll::Ready(Some(Ok(bytes))) => Poll::Ready(Some(Ok(Frame::data(bytes)))),
Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))),
Poll::Ready(None) => Poll::Ready(None),
Poll::Pending => Poll::Pending,
},
StreamKind::Frames(stream) => stream.as_mut().poll_next(cx),
},
}
}
fn size_hint(&self) -> SizeHint {
match self {
HttpBody::Empty => SizeHint::with_exact(0),
HttpBody::Full(full) => full.size_hint(),
HttpBody::Stream(_) => SizeHint::new(),
}
}
}
pub type HttpResponse<T = HttpBody> = http::Response<T>;
pub struct Response {
pub(crate) app: Arc<App>,
pub(crate) inner: http::response::Response<HttpBody>,
pub locals: AnyMap<dyn SerializableAny>,
pub(crate) status_modified: bool,
}
impl Response {
#[inline]
pub(crate) const fn from_response(app: Arc<App>, res: HttpResponse) -> Self {
Response {
app,
inner: res,
locals: AnyMap::new(),
status_modified: false,
}
}
pub fn app(&self) -> &App {
&self.app
}
pub fn status(&mut self, status: StatusCode) -> &mut Self {
if !self.status_modified {
self.status_modified = true;
}
*self.inner.status_mut() = status;
self
}
pub fn send_status(&mut self, status: StatusCode) -> &mut Self {
self.status(status);
if self.inner.body().size_hint().exact() == Some(0) {
let text = status.canonical_reason().unwrap_or("").to_string();
*self.inner.body_mut() = HttpBody::full(Bytes::from(text));
}
self
}
#[inline]
pub fn headers(&self) -> &http::header::HeaderMap<HeaderValue> {
self.inner.headers()
}
#[inline]
pub fn headers_mut(&mut self) -> &mut http::header::HeaderMap<HeaderValue> {
self.inner.headers_mut()
}
#[inline]
pub fn header<H>(&mut self, headers: H) -> &mut Self
where
H: SetIntoHeaders,
{
if let Err(e) = headers.into_headers(self.inner.headers_mut()) {
tracing::error!("failed to set headers: {e}");
}
self
}
#[inline]
pub fn append<K, V>(&mut self, key: K, values: V) -> &mut Self
where
K: TryInto<HeaderName, Error = InvalidHeaderName>,
V: AppendIntoHeaderValues,
Error: From<K::Error>,
{
let key = match key.try_into() {
Ok(k) => k,
Err(e) => {
tracing::error!("failed to convert header name: {e}");
return self;
}
};
if let Err(e) = values.append_to_header(self.inner.headers_mut(), key) {
tracing::error!("failed to append header value: {e}");
}
self
}
#[inline]
pub fn send(&mut self, body: impl Into<Bytes>) {
*self.inner.body_mut() = HttpBody::full(body.into());
}
#[inline]
pub fn stream<S, E>(&mut self, stream: S)
where
S: Stream<Item = Result<Bytes, E>> + Send + Sync + 'static,
E: Into<BoxError> + 'static,
{
let mapped = stream.map(|result| result.map_err(|e| e.into()));
*self.inner.body_mut() = HttpBody::stream(mapped);
}
#[inline]
pub fn stream_frames<S, E>(&mut self, stream: S)
where
S: Stream<Item = Result<Frame<Bytes>, E>> + Send + Sync + 'static,
E: Into<BoxError> + 'static,
{
let mapped = stream.map(|result| result.map_err(|e| e.into()));
*self.inner.body_mut() = HttpBody::stream_frames(mapped);
}
pub fn sse<S, E>(&mut self, stream: S)
where
S: Stream<Item = Result<Bytes, E>> + Send + Sync + 'static,
E: Into<BoxError> + 'static,
{
self.header([
("Content-Type", "text/event-stream"),
("Cache-Control", "no-cache"),
("X-Accel-Buffering", "no"), ]);
let shutdown = self.app.shutdown_token().cancelled_owned();
let stream = stream
.map(|result| result.map_err(Into::into))
.take_until(shutdown);
*self.inner.body_mut() = HttpBody::stream(stream);
}
#[inline]
pub fn content_type<V>(&mut self, value: V) -> &mut Self
where
V: TryInto<HeaderValue>,
Error: From<V::Error>,
{
self.header((header::CONTENT_TYPE, value))
}
#[inline]
pub fn html(&mut self, s: &'static str) {
self.content_type("text/html; charset=utf-8").send(s);
}
#[inline]
pub fn json(&mut self, value: impl serde::Serialize) {
match serde_json::to_string(&value) {
Ok(json_str) => self
.content_type("application/json; charset=utf-8")
.send(json_str),
Err(e) => {
tracing::error!("failed to serialize JSON response: {e}");
self.status(StatusCode::INTERNAL_SERVER_ERROR)
.send("Internal Server Error")
}
}
}
#[cfg(feature = "minijinja")]
#[inline]
pub fn get_render_ctx(&self) -> minijinja::Value {
let mut ctx = std::collections::BTreeMap::new();
self.app.locals(|l| {
for (key, value) in l {
ctx.insert(key.to_string(), minijinja::Value::from_serialize(value));
}
});
for (key, value) in &self.locals {
ctx.insert(key.to_string(), minijinja::Value::from_serialize(value));
}
minijinja::Value::from(ctx)
}
#[cfg(feature = "minijinja")]
fn send_rendered(&mut self, result: Result<String, minijinja::Error>, label: &str) {
match result {
Ok(rendered) => self
.status(StatusCode::OK)
.content_type("text/html; charset=utf-8")
.send(rendered),
Err(e) => {
tracing::warn!("failed to render {label}: {e}");
self.send_status(StatusCode::INTERNAL_SERVER_ERROR);
}
}
}
#[cfg(feature = "minijinja")]
#[inline]
pub fn render(&mut self, template: &str) {
let ctx = self.get_render_ctx();
let result = self.app.jinja.render(template, &ctx);
self.send_rendered(result, template);
}
#[cfg(feature = "minijinja")]
#[inline]
pub fn render_with(&mut self, template: &str, value: minijinja::Value) {
let ctx = minijinja::context! { ..self.get_render_ctx(), ..value };
let result = self.app.jinja.render(template, &ctx);
self.send_rendered(result, template);
}
#[cfg(feature = "minijinja")]
#[inline]
pub fn render_str(&mut self, source: &str) {
let ctx = self.get_render_ctx();
let result = self.app.jinja.render_str(source, &ctx);
self.send_rendered(result, "inline template");
}
#[cfg(feature = "minijinja")]
#[inline]
pub fn render_str_with(&mut self, source: &str, value: minijinja::Value) {
let ctx = minijinja::context! { ..self.get_render_ctx(), ..value };
let result = self.app.jinja.render_str(source, &ctx);
self.send_rendered(result, "inline template");
}
pub fn redirect(&mut self, location: impl AsRef<str>, status: Option<StatusCode>) {
self.header((header::LOCATION, location.as_ref()));
let status_code = status.unwrap_or(StatusCode::FOUND);
self.status(status_code);
}
pub async fn send_file(
&mut self,
path: impl AsRef<std::path::Path>,
) -> Result<(), std::io::Error> {
let path = path.as_ref();
let file = tokio::fs::File::open(path).await?;
let meta = file.metadata().await?;
let mime = mime_guess::from_path(path).first_or_octet_stream();
self.header(("Content-Type", mime.as_ref()));
self.header(("Content-Length", meta.len().to_string()));
self.stream(tokio_util::io::ReaderStream::with_capacity(file, 64 * 1024));
Ok(())
}
}
impl fmt::Debug for Response {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Response")
.field("status_code", &self.inner.status())
.field("body", &self.inner.body())
.finish()
}
}
pub trait SetIntoHeaders {
fn into_headers(self, map: &mut HeaderMap) -> Result<(), Error>;
}
impl<K, V> SetIntoHeaders for (K, V)
where
K: TryInto<HeaderName>,
V: TryInto<HeaderValue>,
Error: From<K::Error> + From<V::Error>,
{
fn into_headers(self, map: &mut HeaderMap) -> Result<(), Error> {
let k = self.0.try_into()?;
let v = self.1.try_into()?;
map.insert(k, v);
Ok(())
}
}
impl<K, V, const N: usize> SetIntoHeaders for [(K, V); N]
where
K: TryInto<HeaderName>,
V: TryInto<HeaderValue>,
Error: From<K::Error> + From<V::Error>,
{
fn into_headers(self, map: &mut HeaderMap) -> Result<(), Error> {
for (key, value) in self {
let k = key.try_into()?;
let v = value.try_into()?;
map.insert(k, v);
}
Ok(())
}
}
impl<K, V> SetIntoHeaders for Vec<(K, V)>
where
K: TryInto<HeaderName>,
V: TryInto<HeaderValue>,
Error: From<K::Error> + From<V::Error>,
{
fn into_headers(self, map: &mut HeaderMap) -> Result<(), Error> {
for (key, value) in self {
let k = key.try_into()?;
let v = value.try_into()?;
map.insert(k, v);
}
Ok(())
}
}
pub trait AppendIntoHeaderValues {
fn append_to_header(self, map: &mut HeaderMap, key: HeaderName) -> Result<(), http::Error>;
}
impl AppendIntoHeaderValues for &str {
fn append_to_header(self, map: &mut HeaderMap, key: HeaderName) -> Result<(), http::Error> {
let value = HeaderValue::try_from(self).map_err(|e| http::Error::from(e))?;
map.append(key, value);
Ok(())
}
}
impl AppendIntoHeaderValues for String {
fn append_to_header(self, map: &mut HeaderMap, key: HeaderName) -> Result<(), http::Error> {
let value = HeaderValue::try_from(self).map_err(|e| http::Error::from(e))?;
map.append(key, value);
Ok(())
}
}
impl AppendIntoHeaderValues for HeaderValue {
fn append_to_header(self, map: &mut HeaderMap, key: HeaderName) -> Result<(), http::Error> {
map.append(key, self);
Ok(())
}
}
impl AppendIntoHeaderValues for &[&str] {
fn append_to_header(self, map: &mut HeaderMap, key: HeaderName) -> Result<(), http::Error> {
for &value in self {
let v = HeaderValue::try_from(value).map_err(|e| http::Error::from(e))?;
map.append(key.clone(), v);
}
Ok(())
}
}
impl AppendIntoHeaderValues for &[String] {
fn append_to_header(self, map: &mut HeaderMap, key: HeaderName) -> Result<(), http::Error> {
for value in self {
let v = HeaderValue::try_from(value.as_str()).map_err(|e| http::Error::from(e))?;
map.append(key.clone(), v);
}
Ok(())
}
}
impl AppendIntoHeaderValues for Vec<String> {
fn append_to_header(self, map: &mut HeaderMap, key: HeaderName) -> Result<(), http::Error> {
for value in self {
let v = HeaderValue::try_from(value).map_err(|e| http::Error::from(e))?;
map.append(key.clone(), v);
}
Ok(())
}
}
impl<const N: usize> AppendIntoHeaderValues for [String; N] {
fn append_to_header(self, map: &mut HeaderMap, key: HeaderName) -> Result<(), http::Error> {
for value in self {
let v = HeaderValue::try_from(value).map_err(|e| http::Error::from(e))?;
map.append(key.clone(), v);
}
Ok(())
}
}
impl<const N: usize> AppendIntoHeaderValues for [&str; N] {
fn append_to_header(self, map: &mut HeaderMap, key: HeaderName) -> Result<(), http::Error> {
for value in self {
let v = HeaderValue::try_from(value).map_err(|e| http::Error::from(e))?;
map.append(key.clone(), v);
}
Ok(())
}
}
impl<const N: usize> AppendIntoHeaderValues for &[String; N] {
fn append_to_header(self, map: &mut HeaderMap, key: HeaderName) -> Result<(), http::Error> {
for value in self {
let v = HeaderValue::try_from(value).map_err(|e| http::Error::from(e))?;
map.append(key.clone(), v);
}
Ok(())
}
}
impl<const N: usize> AppendIntoHeaderValues for &[&str; N] {
fn append_to_header(self, map: &mut HeaderMap, key: HeaderName) -> Result<(), http::Error> {
for value in self {
let v = HeaderValue::try_from(*value).map_err(|e| http::Error::from(e))?;
map.append(key.clone(), v);
}
Ok(())
}
}
impl AppendIntoHeaderValues for Vec<&str> {
fn append_to_header(self, map: &mut HeaderMap, key: HeaderName) -> Result<(), http::Error> {
for value in self {
let v = HeaderValue::try_from(value).map_err(|e| http::Error::from(e))?;
map.append(key.clone(), v);
}
Ok(())
}
}