mirror of
https://github.com/tokio-rs/axum.git
synced 2025-03-13 19:27:53 +01:00
Add IntoResponseParts
(#797)
* Add `IntoResponseParts` * docs * Add test * don't allow overriding body or response * macroify impls * re-order things a bit * Fix tests * Also allow overriding version * Move things into separate modules * docs * clean up * fix trybuild test * remove churn * simplify buliding response * fixup test * fix docs typo * Use `HeaderValue::from_static`, might be faster * Bring back `impl IntoResponse` in example * Remove blanket impl to improve error message * don't need to set `content-type` * Apply suggestions from code review Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com> * changelog Co-authored-by: Jonas Platte <jplatte@users.noreply.github.com>
This commit is contained in:
parent
da74084146
commit
f12ab072c5
23 changed files with 1117 additions and 930 deletions
|
@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
# Unreleased
|
||||
|
||||
- **added:** Add `IntoResponseHeaders` trait ([#644])
|
||||
- **added:** Add `IntoResponseParts` trait ([#797])
|
||||
- **added:** Implement `IntoResponse` for `bytes::BytesMut` and `bytes::Chain<T, U>` ([#767])
|
||||
- **breaking:** Using `HeaderMap` as an extractor will no longer remove the headers and thus
|
||||
they'll still be accessible to other extractors, such as `axum::extract::Json`. Instead
|
||||
|
@ -37,10 +37,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- `<http::request::Parts as FromRequest>::Rejection` is now `Infallible`.
|
||||
- `ExtensionsAlreadyExtracted` has been removed.
|
||||
|
||||
[#644]: https://github.com/tokio-rs/axum/pull/644
|
||||
[#698]: https://github.com/tokio-rs/axum/pull/698
|
||||
[#699]: https://github.com/tokio-rs/axum/pull/699
|
||||
[#767]: https://github.com/tokio-rs/axum/pull/767
|
||||
[#797]: https://github.com/tokio-rs/axum/pull/797
|
||||
|
||||
# 0.1.1 (06. December, 2021)
|
||||
|
||||
|
|
|
@ -22,3 +22,4 @@ mime = "0.3.16"
|
|||
futures-util = "0.3"
|
||||
axum = { path = "../axum", version = "0.4" }
|
||||
hyper = "0.14"
|
||||
tokio = { version = "1.0", features = ["macros"] }
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
//! Rejection response types.
|
||||
|
||||
use crate::body;
|
||||
use http::{Response, StatusCode};
|
||||
use http_body::Full;
|
||||
use crate::response::{IntoResponse, Response};
|
||||
use http::StatusCode;
|
||||
use std::fmt;
|
||||
|
||||
/// Rejection type used if you try and extract the request body more than
|
||||
|
@ -15,11 +14,9 @@ impl BodyAlreadyExtracted {
|
|||
const BODY: &'static str = "Cannot have two request body extractors for a single handler";
|
||||
}
|
||||
|
||||
impl crate::response::IntoResponse for BodyAlreadyExtracted {
|
||||
fn into_response(self) -> crate::response::Response {
|
||||
let mut res = Response::new(body::boxed(Full::from(Self::BODY)));
|
||||
*res.status_mut() = StatusCode::INTERNAL_SERVER_ERROR;
|
||||
res
|
||||
impl IntoResponse for BodyAlreadyExtracted {
|
||||
fn into_response(self) -> Response {
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, Self::BODY).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,12 +20,10 @@ macro_rules! define_rejection {
|
|||
|
||||
impl crate::response::IntoResponse for $name {
|
||||
fn into_response(self) -> $crate::response::Response {
|
||||
let body = http_body::Full::from(format!(concat!($body, ": {}"), self.0));
|
||||
let body = $crate::body::boxed(body);
|
||||
let mut res =
|
||||
http::Response::new(body);
|
||||
*res.status_mut() = http::StatusCode::$status;
|
||||
res
|
||||
(
|
||||
http::StatusCode::$status,
|
||||
format!(concat!($body, ": {}"), self.0)
|
||||
).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
531
axum-core/src/response/into_response.rs
Normal file
531
axum-core/src/response/into_response.rs
Normal file
|
@ -0,0 +1,531 @@
|
|||
use super::{IntoResponseParts, Response, ResponseParts};
|
||||
use crate::{body, BoxError};
|
||||
use bytes::{buf::Chain, Buf, Bytes, BytesMut};
|
||||
use http::{
|
||||
header::{self, HeaderMap, HeaderName, HeaderValue},
|
||||
StatusCode, Version,
|
||||
};
|
||||
use http_body::{
|
||||
combinators::{MapData, MapErr},
|
||||
Empty, Full, SizeHint,
|
||||
};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
convert::{Infallible, TryInto},
|
||||
fmt,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
/// Trait for generating responses.
|
||||
///
|
||||
/// Types that implement `IntoResponse` can be returned from handlers.
|
||||
///
|
||||
/// # Implementing `IntoResponse`
|
||||
///
|
||||
/// You generally shouldn't have to implement `IntoResponse` manually, as axum
|
||||
/// provides implementations for many common types.
|
||||
///
|
||||
/// However it might be necessary if you have a custom error type that you want
|
||||
/// to return from handlers:
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// Router,
|
||||
/// body::{self, Bytes},
|
||||
/// routing::get,
|
||||
/// http::StatusCode,
|
||||
/// response::{IntoResponse, Response},
|
||||
/// };
|
||||
///
|
||||
/// enum MyError {
|
||||
/// SomethingWentWrong,
|
||||
/// SomethingElseWentWrong,
|
||||
/// }
|
||||
///
|
||||
/// impl IntoResponse for MyError {
|
||||
/// fn into_response(self) -> Response {
|
||||
/// let body = match self {
|
||||
/// MyError::SomethingWentWrong => "something went wrong",
|
||||
/// MyError::SomethingElseWentWrong => "something else went wrong",
|
||||
/// };
|
||||
///
|
||||
/// // its often easiest to implement `IntoResponse` by calling other implementations
|
||||
/// (StatusCode::INTERNAL_SERVER_ERROR, body).into_response()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // `Result<impl IntoResponse, MyError>` can now be returned from handlers
|
||||
/// let app = Router::new().route("/", get(handler));
|
||||
///
|
||||
/// async fn handler() -> Result<(), MyError> {
|
||||
/// Err(MyError::SomethingWentWrong)
|
||||
/// }
|
||||
/// # async {
|
||||
/// # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// Or if you have a custom body type you'll also need to implement
|
||||
/// `IntoResponse` for it:
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// body,
|
||||
/// routing::get,
|
||||
/// response::{IntoResponse, Response},
|
||||
/// Router,
|
||||
/// };
|
||||
/// use http_body::Body;
|
||||
/// use http::HeaderMap;
|
||||
/// use bytes::Bytes;
|
||||
/// use std::{
|
||||
/// convert::Infallible,
|
||||
/// task::{Poll, Context},
|
||||
/// pin::Pin,
|
||||
/// };
|
||||
///
|
||||
/// struct MyBody;
|
||||
///
|
||||
/// // First implement `Body` for `MyBody`. This could for example use
|
||||
/// // some custom streaming protocol.
|
||||
/// impl Body for MyBody {
|
||||
/// type Data = Bytes;
|
||||
/// type Error = Infallible;
|
||||
///
|
||||
/// fn poll_data(
|
||||
/// self: Pin<&mut Self>,
|
||||
/// cx: &mut Context<'_>
|
||||
/// ) -> Poll<Option<Result<Self::Data, Self::Error>>> {
|
||||
/// # unimplemented!()
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// fn poll_trailers(
|
||||
/// self: Pin<&mut Self>,
|
||||
/// cx: &mut Context<'_>
|
||||
/// ) -> Poll<Result<Option<HeaderMap>, Self::Error>> {
|
||||
/// # unimplemented!()
|
||||
/// // ...
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Now we can implement `IntoResponse` directly for `MyBody`
|
||||
/// impl IntoResponse for MyBody {
|
||||
/// fn into_response(self) -> Response {
|
||||
/// Response::new(body::boxed(self))
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // `MyBody` can now be returned from handlers.
|
||||
/// let app = Router::new().route("/", get(|| async { MyBody }));
|
||||
/// # async {
|
||||
/// # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
pub trait IntoResponse {
|
||||
/// Create a response.
|
||||
fn into_response(self) -> Response;
|
||||
}
|
||||
|
||||
impl IntoResponse for StatusCode {
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = ().into_response();
|
||||
*res.status_mut() = self;
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Version {
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = ().into_response();
|
||||
*res.version_mut() = self;
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for () {
|
||||
fn into_response(self) -> Response {
|
||||
Empty::new().into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Infallible {
|
||||
fn into_response(self) -> Response {
|
||||
match self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> IntoResponse for Result<T, E>
|
||||
where
|
||||
T: IntoResponse,
|
||||
E: IntoResponse,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
match self {
|
||||
Ok(value) => value.into_response(),
|
||||
Err(err) => err.into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> IntoResponse for Response<B>
|
||||
where
|
||||
B: http_body::Body<Data = Bytes> + Send + 'static,
|
||||
B::Error: Into<BoxError>,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
self.map(body::boxed)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for http::response::Parts {
|
||||
fn into_response(self) -> Response {
|
||||
Response::from_parts(self, body::boxed(Empty::new()))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Full<Bytes> {
|
||||
fn into_response(self) -> Response {
|
||||
Response::new(body::boxed(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Empty<Bytes> {
|
||||
fn into_response(self) -> Response {
|
||||
Response::new(body::boxed(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> IntoResponse for http_body::combinators::BoxBody<Bytes, E>
|
||||
where
|
||||
E: Into<BoxError> + 'static,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
Response::new(body::boxed(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> IntoResponse for http_body::combinators::UnsyncBoxBody<Bytes, E>
|
||||
where
|
||||
E: Into<BoxError> + 'static,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
Response::new(body::boxed(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, F> IntoResponse for MapData<B, F>
|
||||
where
|
||||
B: http_body::Body + Send + 'static,
|
||||
F: FnMut(B::Data) -> Bytes + Send + 'static,
|
||||
B::Error: Into<BoxError>,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
Response::new(body::boxed(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, F, E> IntoResponse for MapErr<B, F>
|
||||
where
|
||||
B: http_body::Body<Data = Bytes> + Send + 'static,
|
||||
F: FnMut(B::Error) -> E + Send + 'static,
|
||||
E: Into<BoxError>,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
Response::new(body::boxed(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for &'static str {
|
||||
fn into_response(self) -> Response {
|
||||
Cow::Borrowed(self).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for String {
|
||||
fn into_response(self) -> Response {
|
||||
Cow::<'static, str>::Owned(self).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Cow<'static, str> {
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = Full::from(self).into_response();
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
|
||||
);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Bytes {
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = Full::from(self).into_response();
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::APPLICATION_OCTET_STREAM.as_ref()),
|
||||
);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for BytesMut {
|
||||
fn into_response(self) -> Response {
|
||||
self.freeze().into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> IntoResponse for Chain<T, U>
|
||||
where
|
||||
T: Buf + Unpin + Send + 'static,
|
||||
U: Buf + Unpin + Send + 'static,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
let (first, second) = self.into_inner();
|
||||
let mut res = Response::new(body::boxed(BytesChainBody {
|
||||
first: Some(first),
|
||||
second: Some(second),
|
||||
}));
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::APPLICATION_OCTET_STREAM.as_ref()),
|
||||
);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
struct BytesChainBody<T, U> {
|
||||
first: Option<T>,
|
||||
second: Option<U>,
|
||||
}
|
||||
|
||||
impl<T, U> http_body::Body for BytesChainBody<T, U>
|
||||
where
|
||||
T: Buf + Unpin,
|
||||
U: Buf + Unpin,
|
||||
{
|
||||
type Data = Bytes;
|
||||
type Error = Infallible;
|
||||
|
||||
fn poll_data(
|
||||
mut self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Self::Data, Self::Error>>> {
|
||||
if let Some(mut buf) = self.first.take() {
|
||||
let bytes = buf.copy_to_bytes(buf.remaining());
|
||||
return Poll::Ready(Some(Ok(bytes)));
|
||||
}
|
||||
|
||||
if let Some(mut buf) = self.second.take() {
|
||||
let bytes = buf.copy_to_bytes(buf.remaining());
|
||||
return Poll::Ready(Some(Ok(bytes)));
|
||||
}
|
||||
|
||||
Poll::Ready(None)
|
||||
}
|
||||
|
||||
fn poll_trailers(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Result<Option<HeaderMap>, Self::Error>> {
|
||||
Poll::Ready(Ok(None))
|
||||
}
|
||||
|
||||
fn is_end_stream(&self) -> bool {
|
||||
self.first.is_none() && self.second.is_none()
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> SizeHint {
|
||||
match (self.first.as_ref(), self.second.as_ref()) {
|
||||
(Some(first), Some(second)) => {
|
||||
let total_size = first.remaining() + second.remaining();
|
||||
SizeHint::with_exact(total_size as u64)
|
||||
}
|
||||
(Some(buf), None) => SizeHint::with_exact(buf.remaining() as u64),
|
||||
(None, Some(buf)) => SizeHint::with_exact(buf.remaining() as u64),
|
||||
(None, None) => SizeHint::with_exact(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for &'static [u8] {
|
||||
fn into_response(self) -> Response {
|
||||
Cow::Borrowed(self).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Vec<u8> {
|
||||
fn into_response(self) -> Response {
|
||||
Cow::<'static, [u8]>::Owned(self).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Cow<'static, [u8]> {
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = Full::from(self).into_response();
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::APPLICATION_OCTET_STREAM.as_ref()),
|
||||
);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> IntoResponse for (StatusCode, R)
|
||||
where
|
||||
R: IntoResponse,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = self.1.into_response();
|
||||
*res.status_mut() = self.0;
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> IntoResponse for (Version, R)
|
||||
where
|
||||
R: IntoResponse,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = self.1.into_response();
|
||||
*res.version_mut() = self.0;
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<R> IntoResponse for (Version, StatusCode, R)
|
||||
where
|
||||
R: IntoResponse,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
let (version, status, res) = self;
|
||||
let mut res = res.into_response();
|
||||
*res.version_mut() = version;
|
||||
*res.status_mut() = status;
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for HeaderMap {
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = ().into_response();
|
||||
*res.headers_mut() = self;
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V, const N: usize> IntoResponse for [(K, V); N]
|
||||
where
|
||||
K: TryInto<HeaderName>,
|
||||
K::Error: fmt::Display,
|
||||
V: TryInto<HeaderValue>,
|
||||
V::Error: fmt::Display,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = ().into_response();
|
||||
|
||||
for (key, value) in self {
|
||||
let key = match key.try_into() {
|
||||
Ok(key) => key,
|
||||
Err(err) => {
|
||||
return (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response()
|
||||
}
|
||||
};
|
||||
|
||||
let value = match value.try_into() {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
return (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response()
|
||||
}
|
||||
};
|
||||
|
||||
res.headers_mut().insert(key, value);
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_into_response {
|
||||
( $($ty:ident),* $(,)? ) => {
|
||||
#[allow(non_snake_case)]
|
||||
impl<R, $($ty,)*> IntoResponse for ($($ty),*, R)
|
||||
where
|
||||
$( $ty: IntoResponseParts, )*
|
||||
R: IntoResponse,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
let ($($ty),*, res) = self;
|
||||
|
||||
let res = res.into_response();
|
||||
let mut parts = ResponseParts { res: Ok(res) };
|
||||
|
||||
$(
|
||||
$ty.into_response_parts(&mut parts);
|
||||
)*
|
||||
|
||||
match parts.res {
|
||||
Ok(res) => res,
|
||||
Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err).into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
impl<R, $($ty,)*> IntoResponse for (StatusCode, $($ty),*, R)
|
||||
where
|
||||
$( $ty: IntoResponseParts, )*
|
||||
R: IntoResponse,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
let (status, $($ty),*, res) = self;
|
||||
|
||||
let res = res.into_response();
|
||||
let mut parts = ResponseParts { res: Ok(res) };
|
||||
|
||||
$(
|
||||
$ty.into_response_parts(&mut parts);
|
||||
)*
|
||||
|
||||
match parts.res {
|
||||
Ok(mut res) => {
|
||||
*res.status_mut() = status;
|
||||
res
|
||||
}
|
||||
Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err).into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
impl<R, $($ty,)*> IntoResponse for (Version, StatusCode, $($ty),*, R)
|
||||
where
|
||||
$( $ty: IntoResponseParts, )*
|
||||
R: IntoResponse,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
let (version, status, $($ty),*, res) = self;
|
||||
|
||||
let res = res.into_response();
|
||||
let mut parts = ResponseParts { res: Ok(res) };
|
||||
|
||||
$(
|
||||
$ty.into_response_parts(&mut parts);
|
||||
)*
|
||||
|
||||
match parts.res {
|
||||
Ok(mut res) => {
|
||||
*res.version_mut() = version;
|
||||
*res.status_mut() = status;
|
||||
res
|
||||
}
|
||||
Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err).into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
all_the_tuples!(impl_into_response);
|
124
axum-core/src/response/into_response_parts.rs
Normal file
124
axum-core/src/response/into_response_parts.rs
Normal file
|
@ -0,0 +1,124 @@
|
|||
use super::Response;
|
||||
use http::header::{HeaderMap, HeaderName, HeaderValue};
|
||||
use std::{convert::TryInto, fmt};
|
||||
|
||||
/// Trait for adding headers and extensions to a response.
|
||||
///
|
||||
/// You generally don't need to implement this trait manually. It's recommended instead to rely
|
||||
/// on the implementations in axum.
|
||||
pub trait IntoResponseParts {
|
||||
/// Set parts of the response
|
||||
fn into_response_parts(self, res: &mut ResponseParts);
|
||||
}
|
||||
|
||||
/// Parts of a response.
|
||||
///
|
||||
/// Used with [`IntoResponseParts`].
|
||||
#[derive(Debug)]
|
||||
pub struct ResponseParts {
|
||||
pub(crate) res: Result<Response, String>,
|
||||
}
|
||||
|
||||
impl ResponseParts {
|
||||
/// Insert a header into the response.
|
||||
///
|
||||
/// If the header already exists, it will be overwritten.
|
||||
pub fn insert_header<K, V>(&mut self, key: K, value: V)
|
||||
where
|
||||
K: TryInto<HeaderName>,
|
||||
K::Error: fmt::Display,
|
||||
V: TryInto<HeaderValue>,
|
||||
V::Error: fmt::Display,
|
||||
{
|
||||
self.update_headers(key, value, |headers, key, value| {
|
||||
headers.insert(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
/// Append a header to the response.
|
||||
///
|
||||
/// If the header already exists it will be appended to.
|
||||
pub fn append_header<K, V>(&mut self, key: K, value: V)
|
||||
where
|
||||
K: TryInto<HeaderName>,
|
||||
K::Error: fmt::Display,
|
||||
V: TryInto<HeaderValue>,
|
||||
V::Error: fmt::Display,
|
||||
{
|
||||
self.update_headers(key, value, |headers, key, value| {
|
||||
headers.append(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
fn update_headers<K, V, F>(&mut self, key: K, value: V, f: F)
|
||||
where
|
||||
K: TryInto<HeaderName>,
|
||||
K::Error: fmt::Display,
|
||||
V: TryInto<HeaderValue>,
|
||||
V::Error: fmt::Display,
|
||||
F: FnOnce(&mut HeaderMap, HeaderName, HeaderValue),
|
||||
{
|
||||
if let Ok(response) = &mut self.res {
|
||||
let key = match key.try_into() {
|
||||
Ok(key) => key,
|
||||
Err(err) => {
|
||||
self.res = Err(err.to_string());
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let value = match value.try_into() {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
self.res = Err(err.to_string());
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
f(response.headers_mut(), key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert an extension into the response.
|
||||
///
|
||||
/// If the extension already exists it will be overwritten.
|
||||
pub fn insert_extension<T>(&mut self, extension: T)
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
if let Ok(res) = &mut self.res {
|
||||
res.extensions_mut().insert(extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<(Option<HeaderName>, HeaderValue)> for ResponseParts {
|
||||
fn extend<T>(&mut self, iter: T)
|
||||
where
|
||||
T: IntoIterator<Item = (Option<HeaderName>, HeaderValue)>,
|
||||
{
|
||||
if let Ok(res) = &mut self.res {
|
||||
res.headers_mut().extend(iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponseParts for HeaderMap {
|
||||
fn into_response_parts(self, res: &mut ResponseParts) {
|
||||
res.extend(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V, const N: usize> IntoResponseParts for [(K, V); N]
|
||||
where
|
||||
K: TryInto<HeaderName>,
|
||||
K::Error: fmt::Display,
|
||||
V: TryInto<HeaderValue>,
|
||||
V::Error: fmt::Display,
|
||||
{
|
||||
fn into_response_parts(self, res: &mut ResponseParts) {
|
||||
for (key, value) in self {
|
||||
res.insert_header(key, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,532 +4,16 @@
|
|||
//!
|
||||
//! [`axum::response`]: https://docs.rs/axum/latest/axum/response/index.html
|
||||
|
||||
use crate::{
|
||||
body::{boxed, BoxBody},
|
||||
BoxError,
|
||||
};
|
||||
use bytes::{buf::Chain, Buf, Bytes, BytesMut};
|
||||
use http::{
|
||||
header::{self, HeaderMap, HeaderName, HeaderValue},
|
||||
StatusCode,
|
||||
};
|
||||
use http_body::{
|
||||
combinators::{MapData, MapErr},
|
||||
Empty, Full, SizeHint,
|
||||
};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
convert::Infallible,
|
||||
iter,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
use crate::body::BoxBody;
|
||||
|
||||
mod into_response;
|
||||
mod into_response_parts;
|
||||
|
||||
pub use self::{
|
||||
into_response::IntoResponse,
|
||||
into_response_parts::{IntoResponseParts, ResponseParts},
|
||||
};
|
||||
|
||||
/// Type alias for [`http::Response`] whose body type defaults to [`BoxBody`], the most common body
|
||||
/// type used with axum.
|
||||
pub type Response<T = BoxBody> = http::Response<T>;
|
||||
|
||||
/// Trait for generating responses.
|
||||
///
|
||||
/// Types that implement `IntoResponse` can be returned from handlers.
|
||||
///
|
||||
/// # Implementing `IntoResponse`
|
||||
///
|
||||
/// You generally shouldn't have to implement `IntoResponse` manually, as axum
|
||||
/// provides implementations for many common types.
|
||||
///
|
||||
/// However it might be necessary if you have a custom error type that you want
|
||||
/// to return from handlers:
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// Router,
|
||||
/// body::{self, Bytes},
|
||||
/// routing::get,
|
||||
/// http::StatusCode,
|
||||
/// response::{IntoResponse, Response},
|
||||
/// };
|
||||
///
|
||||
/// enum MyError {
|
||||
/// SomethingWentWrong,
|
||||
/// SomethingElseWentWrong,
|
||||
/// }
|
||||
///
|
||||
/// impl IntoResponse for MyError {
|
||||
/// fn into_response(self) -> Response {
|
||||
/// let body = match self {
|
||||
/// MyError::SomethingWentWrong => {
|
||||
/// body::boxed(body::Full::from("something went wrong"))
|
||||
/// },
|
||||
/// MyError::SomethingElseWentWrong => {
|
||||
/// body::boxed(body::Full::from("something else went wrong"))
|
||||
/// },
|
||||
/// };
|
||||
///
|
||||
/// Response::builder()
|
||||
/// .status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
/// .body(body)
|
||||
/// .unwrap()
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // `Result<impl IntoResponse, MyError>` can now be returned from handlers
|
||||
/// let app = Router::new().route("/", get(handler));
|
||||
///
|
||||
/// async fn handler() -> Result<(), MyError> {
|
||||
/// Err(MyError::SomethingWentWrong)
|
||||
/// }
|
||||
/// # async {
|
||||
/// # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// Or if you have a custom body type you'll also need to implement
|
||||
/// `IntoResponse` for it:
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// body,
|
||||
/// routing::get,
|
||||
/// response::{IntoResponse, Response},
|
||||
/// Router,
|
||||
/// };
|
||||
/// use http_body::Body;
|
||||
/// use http::HeaderMap;
|
||||
/// use bytes::Bytes;
|
||||
/// use std::{
|
||||
/// convert::Infallible,
|
||||
/// task::{Poll, Context},
|
||||
/// pin::Pin,
|
||||
/// };
|
||||
///
|
||||
/// struct MyBody;
|
||||
///
|
||||
/// // First implement `Body` for `MyBody`. This could for example use
|
||||
/// // some custom streaming protocol.
|
||||
/// impl Body for MyBody {
|
||||
/// type Data = Bytes;
|
||||
/// type Error = Infallible;
|
||||
///
|
||||
/// fn poll_data(
|
||||
/// self: Pin<&mut Self>,
|
||||
/// cx: &mut Context<'_>
|
||||
/// ) -> Poll<Option<Result<Self::Data, Self::Error>>> {
|
||||
/// # unimplemented!()
|
||||
/// // ...
|
||||
/// }
|
||||
///
|
||||
/// fn poll_trailers(
|
||||
/// self: Pin<&mut Self>,
|
||||
/// cx: &mut Context<'_>
|
||||
/// ) -> Poll<Result<Option<HeaderMap>, Self::Error>> {
|
||||
/// # unimplemented!()
|
||||
/// // ...
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // Now we can implement `IntoResponse` directly for `MyBody`
|
||||
/// impl IntoResponse for MyBody {
|
||||
/// fn into_response(self) -> Response {
|
||||
/// Response::new(body::boxed(self))
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // We don't need to implement `IntoResponse for Response<MyBody>` as that is
|
||||
/// // covered by a blanket implementation in axum.
|
||||
///
|
||||
/// // `MyBody` can now be returned from handlers.
|
||||
/// let app = Router::new().route("/", get(|| async { MyBody }));
|
||||
/// # async {
|
||||
/// # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
pub trait IntoResponse {
|
||||
/// Create a response.
|
||||
fn into_response(self) -> Response;
|
||||
}
|
||||
|
||||
/// Trait for generating response headers.
|
||||
pub trait IntoResponseHeaders {
|
||||
/// The return type of `into_headers`.
|
||||
///
|
||||
/// The iterator item is a [`Result`] to allow the implementation to return a server error
|
||||
/// instead.
|
||||
///
|
||||
/// The header name is optional because [`HeaderMap`]s iterator doesn't yield it multiple times
|
||||
/// for headers that have multiple values, to avoid unnecessary copies.
|
||||
#[doc(hidden)]
|
||||
type IntoIter: IntoIterator<Item = Result<(Option<HeaderName>, HeaderValue), Response>>;
|
||||
|
||||
/// Attempt to turn `self` into a list of headers.
|
||||
///
|
||||
/// In practice, only the implementation for `axum::response::Headers` ever returns `Err(_)`.
|
||||
#[doc(hidden)]
|
||||
fn into_headers(self) -> Self::IntoIter;
|
||||
}
|
||||
|
||||
impl IntoResponse for () {
|
||||
fn into_response(self) -> Response {
|
||||
Response::new(boxed(Empty::new()))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Infallible {
|
||||
fn into_response(self) -> Response {
|
||||
match self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E> IntoResponse for Result<T, E>
|
||||
where
|
||||
T: IntoResponse,
|
||||
E: IntoResponse,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
match self {
|
||||
Ok(value) => value.into_response(),
|
||||
Err(err) => err.into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> IntoResponse for Response<B>
|
||||
where
|
||||
B: http_body::Body<Data = Bytes> + Send + 'static,
|
||||
B::Error: Into<BoxError>,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
self.map(boxed)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_into_response_for_body {
|
||||
($body:ty) => {
|
||||
impl IntoResponse for $body {
|
||||
fn into_response(self) -> Response {
|
||||
Response::new(boxed(self))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_into_response_for_body!(Full<Bytes>);
|
||||
impl_into_response_for_body!(Empty<Bytes>);
|
||||
|
||||
impl IntoResponse for http::response::Parts {
|
||||
fn into_response(self) -> Response {
|
||||
Response::from_parts(self, boxed(Empty::new()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> IntoResponse for http_body::combinators::BoxBody<Bytes, E>
|
||||
where
|
||||
E: Into<BoxError> + 'static,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
Response::new(boxed(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> IntoResponse for http_body::combinators::UnsyncBoxBody<Bytes, E>
|
||||
where
|
||||
E: Into<BoxError> + 'static,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
Response::new(boxed(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, F> IntoResponse for MapData<B, F>
|
||||
where
|
||||
B: http_body::Body + Send + 'static,
|
||||
F: FnMut(B::Data) -> Bytes + Send + 'static,
|
||||
B::Error: Into<BoxError>,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
Response::new(boxed(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, F, E> IntoResponse for MapErr<B, F>
|
||||
where
|
||||
B: http_body::Body<Data = Bytes> + Send + 'static,
|
||||
F: FnMut(B::Error) -> E + Send + 'static,
|
||||
E: Into<BoxError>,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
Response::new(boxed(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for &'static str {
|
||||
#[inline]
|
||||
fn into_response(self) -> Response {
|
||||
Cow::Borrowed(self).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for String {
|
||||
#[inline]
|
||||
fn into_response(self) -> Response {
|
||||
Cow::<'static, str>::Owned(self).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Cow<'static, str> {
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = Response::new(boxed(Full::from(self)));
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
|
||||
);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Bytes {
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = Response::new(boxed(Full::from(self)));
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::APPLICATION_OCTET_STREAM.as_ref()),
|
||||
);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for BytesMut {
|
||||
fn into_response(self) -> Response {
|
||||
self.freeze().into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> IntoResponse for Chain<T, U>
|
||||
where
|
||||
T: Buf + Unpin + Send + 'static,
|
||||
U: Buf + Unpin + Send + 'static,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
let (first, second) = self.into_inner();
|
||||
let mut res = Response::new(boxed(BytesChainBody {
|
||||
first: Some(first),
|
||||
second: Some(second),
|
||||
}));
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::APPLICATION_OCTET_STREAM.as_ref()),
|
||||
);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
struct BytesChainBody<T, U> {
|
||||
first: Option<T>,
|
||||
second: Option<U>,
|
||||
}
|
||||
|
||||
impl<T, U> http_body::Body for BytesChainBody<T, U>
|
||||
where
|
||||
T: Buf + Unpin,
|
||||
U: Buf + Unpin,
|
||||
{
|
||||
type Data = Bytes;
|
||||
type Error = Infallible;
|
||||
|
||||
fn poll_data(
|
||||
mut self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Self::Data, Self::Error>>> {
|
||||
if let Some(mut buf) = self.first.take() {
|
||||
let bytes = buf.copy_to_bytes(buf.remaining());
|
||||
return Poll::Ready(Some(Ok(bytes)));
|
||||
}
|
||||
|
||||
if let Some(mut buf) = self.second.take() {
|
||||
let bytes = buf.copy_to_bytes(buf.remaining());
|
||||
return Poll::Ready(Some(Ok(bytes)));
|
||||
}
|
||||
|
||||
Poll::Ready(None)
|
||||
}
|
||||
|
||||
fn poll_trailers(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Result<Option<HeaderMap>, Self::Error>> {
|
||||
Poll::Ready(Ok(None))
|
||||
}
|
||||
|
||||
fn is_end_stream(&self) -> bool {
|
||||
self.first.is_none() && self.second.is_none()
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> SizeHint {
|
||||
match (self.first.as_ref(), self.second.as_ref()) {
|
||||
(Some(first), Some(second)) => {
|
||||
let total_size = first.remaining() + second.remaining();
|
||||
SizeHint::with_exact(total_size as u64)
|
||||
}
|
||||
(Some(buf), None) => SizeHint::with_exact(buf.remaining() as u64),
|
||||
(None, Some(buf)) => SizeHint::with_exact(buf.remaining() as u64),
|
||||
(None, None) => SizeHint::with_exact(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for &'static [u8] {
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = Response::new(boxed(Full::from(self)));
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::APPLICATION_OCTET_STREAM.as_ref()),
|
||||
);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Vec<u8> {
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = Response::new(boxed(Full::from(self)));
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::APPLICATION_OCTET_STREAM.as_ref()),
|
||||
);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for Cow<'static, [u8]> {
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = Response::new(boxed(Full::from(self)));
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::APPLICATION_OCTET_STREAM.as_ref()),
|
||||
);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for StatusCode {
|
||||
fn into_response(self) -> Response {
|
||||
Response::builder()
|
||||
.status(self)
|
||||
.body(boxed(Empty::new()))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for HeaderMap {
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = Response::new(boxed(Empty::new()));
|
||||
*res.headers_mut() = self;
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoResponse for (StatusCode, T)
|
||||
where
|
||||
T: IntoResponse,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = self.1.into_response();
|
||||
*res.status_mut() = self.0;
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, T> IntoResponse for (H, T)
|
||||
where
|
||||
H: IntoResponseHeaders,
|
||||
T: IntoResponse,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = self.1.into_response();
|
||||
|
||||
if let Err(e) = try_extend_headers(res.headers_mut(), self.0.into_headers()) {
|
||||
return e;
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, T> IntoResponse for (StatusCode, H, T)
|
||||
where
|
||||
H: IntoResponseHeaders,
|
||||
T: IntoResponse,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = self.2.into_response();
|
||||
*res.status_mut() = self.0;
|
||||
|
||||
if let Err(e) = try_extend_headers(res.headers_mut(), self.1.into_headers()) {
|
||||
return e;
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponseHeaders for HeaderMap {
|
||||
// FIXME: Use type_alias_impl_trait when available
|
||||
type IntoIter = iter::Map<
|
||||
http::header::IntoIter<HeaderValue>,
|
||||
fn(
|
||||
(Option<HeaderName>, HeaderValue),
|
||||
) -> Result<(Option<HeaderName>, HeaderValue), Response>,
|
||||
>;
|
||||
|
||||
fn into_headers(self) -> Self::IntoIter {
|
||||
self.into_iter().map(Ok)
|
||||
}
|
||||
}
|
||||
|
||||
// Slightly adjusted version of `impl<T> Extend<(Option<HeaderName>, T)> for HeaderMap<T>`.
|
||||
// Accepts an iterator that returns Results and short-circuits on an `Err`.
|
||||
fn try_extend_headers(
|
||||
headers: &mut HeaderMap,
|
||||
iter: impl IntoIterator<Item = Result<(Option<HeaderName>, HeaderValue), Response>>,
|
||||
) -> Result<(), Response> {
|
||||
use http::header::Entry;
|
||||
|
||||
let mut iter = iter.into_iter();
|
||||
|
||||
// The structure of this is a bit weird, but it is mostly to make the
|
||||
// borrow checker happy.
|
||||
let (mut key, mut val) = match iter.next().transpose()? {
|
||||
Some((Some(key), val)) => (key, val),
|
||||
Some((None, _)) => panic!("expected a header name, but got None"),
|
||||
None => return Ok(()),
|
||||
};
|
||||
|
||||
'outer: loop {
|
||||
let mut entry = match headers.entry(key) {
|
||||
Entry::Occupied(mut e) => {
|
||||
// Replace all previous values while maintaining a handle to
|
||||
// the entry.
|
||||
e.insert(val);
|
||||
e
|
||||
}
|
||||
Entry::Vacant(e) => e.insert_entry(val),
|
||||
};
|
||||
|
||||
// As long as `HeaderName` is none, keep inserting the value into
|
||||
// the current entry
|
||||
loop {
|
||||
match iter.next().transpose()? {
|
||||
Some((Some(k), v)) => {
|
||||
key = k;
|
||||
val = v;
|
||||
continue 'outer;
|
||||
}
|
||||
Some((None, v)) => {
|
||||
entry.append(v);
|
||||
}
|
||||
None => {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use axum::{
|
||||
body::{self, Full},
|
||||
http::{header, HeaderValue, StatusCode},
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
|
@ -49,22 +48,16 @@ impl ErasedJson {
|
|||
|
||||
impl IntoResponse for ErasedJson {
|
||||
fn into_response(self) -> Response {
|
||||
let bytes = match self.0 {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
return Response::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.header(header::CONTENT_TYPE, mime::TEXT_PLAIN_UTF_8.as_ref())
|
||||
.body(body::boxed(Full::from(err.to_string())))
|
||||
.unwrap();
|
||||
}
|
||||
};
|
||||
|
||||
let mut res = Response::new(body::boxed(Full::new(bytes)));
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
|
||||
);
|
||||
res
|
||||
match self.0 {
|
||||
Ok(bytes) => (
|
||||
[(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
|
||||
)],
|
||||
bytes,
|
||||
)
|
||||
.into_response(),
|
||||
Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- **added:** `middleware::from_fn` for creating middleware from async functions.
|
||||
This previously lived in axum-extra but has been moved to axum ([#719])
|
||||
- **added:** Document sharing state between handler and middleware (#783])
|
||||
- **added:** `Extension<_>` can now be used in tuples for building responses, and will set an
|
||||
extension on the response ([#797])
|
||||
- **breaking:** `sse::Event` now accepts types implementing `AsRef<str>` instead of `Into<String>`
|
||||
as field values.
|
||||
- **breaking:** `sse::Event` now panics if a setter method is called twice instead of silently
|
||||
|
@ -69,6 +71,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
[#755]: https://github.com/tokio-rs/axum/pull/755
|
||||
[#783]: https://github.com/tokio-rs/axum/pull/783
|
||||
[#791]: https://github.com/tokio-rs/axum/pull/791
|
||||
[#797]: https://github.com/tokio-rs/axum/pull/797
|
||||
|
||||
# 0.4.4 (13. January, 2022)
|
||||
|
||||
|
|
|
@ -2,27 +2,21 @@ Types and traits for generating responses.
|
|||
|
||||
# Building responses
|
||||
|
||||
Anything that implements [`IntoResponse`] can be returned from a handler:
|
||||
Anything that implements [`IntoResponse`] can be returned from a handler. axum
|
||||
provides implementations for common types:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
body::Body,
|
||||
routing::get,
|
||||
handler::Handler,
|
||||
http::{Request, header::{HeaderMap, HeaderName, HeaderValue}},
|
||||
response::{IntoResponse, Html, Json, Headers},
|
||||
Router,
|
||||
Json,
|
||||
response::{Html, IntoResponse},
|
||||
http::{StatusCode, Uri, header::{self, HeaderMap, HeaderName}},
|
||||
};
|
||||
use http::{StatusCode, Response, Uri};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
// We've already seen returning &'static str
|
||||
async fn plain_text() -> &'static str {
|
||||
"foo"
|
||||
}
|
||||
// `()` gives an empty response
|
||||
async fn empty() {}
|
||||
|
||||
// String works too and will get a `text/plain; charset=utf-8` content-type
|
||||
async fn plain_text_string(uri: Uri) -> String {
|
||||
// String will get a `text/plain; charset=utf-8` content-type
|
||||
async fn plain_text(uri: Uri) -> String {
|
||||
format!("Hi from {}", uri.path())
|
||||
}
|
||||
|
||||
|
@ -31,84 +25,158 @@ async fn bytes() -> Vec<u8> {
|
|||
vec![1, 2, 3, 4]
|
||||
}
|
||||
|
||||
// `()` gives an empty response
|
||||
async fn empty() {}
|
||||
// `Json` will get a `application/json` content-type and work with anything that
|
||||
// implements `serde::Serialize`
|
||||
async fn json() -> Json<Vec<String>> {
|
||||
Json(vec!["foo".to_owned(), "bar".to_owned()])
|
||||
}
|
||||
|
||||
// `Html` will get a `text/html` content-type
|
||||
async fn html() -> Html<&'static str> {
|
||||
Html("<p>Hello, World!</p>")
|
||||
}
|
||||
|
||||
// `StatusCode` gives an empty response with that status code
|
||||
async fn empty_with_status() -> StatusCode {
|
||||
async fn status() -> StatusCode {
|
||||
StatusCode::NOT_FOUND
|
||||
}
|
||||
|
||||
// A tuple of `StatusCode` and something that implements `IntoResponse` can
|
||||
// be used to override the status code
|
||||
async fn with_status() -> (StatusCode, &'static str) {
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, "Something went wrong")
|
||||
// `HeaderMap` gives an empty response with some headers
|
||||
async fn headers() -> HeaderMap {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(header::SERVER, "axum".parse().unwrap());
|
||||
headers
|
||||
}
|
||||
|
||||
// A tuple of `HeaderMap` and something that implements `IntoResponse` can
|
||||
// be used to override the headers
|
||||
async fn with_headers() -> (HeaderMap, &'static str) {
|
||||
// An array of tuples also gives headers
|
||||
async fn array_headers() -> [(HeaderName, &'static str); 2] {
|
||||
[
|
||||
(header::SERVER, "axum"),
|
||||
(header::CONTENT_TYPE, "text/plain")
|
||||
]
|
||||
}
|
||||
|
||||
// Use `impl IntoResponse` to avoid writing the whole type
|
||||
async fn impl_trait() -> impl IntoResponse {
|
||||
[
|
||||
(header::SERVER, "axum"),
|
||||
(header::CONTENT_TYPE, "text/plain")
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Additionally you can return tuples to build more complex responses from
|
||||
individual parts.
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
Json,
|
||||
response::IntoResponse,
|
||||
http::{StatusCode, HeaderMap, Uri, header},
|
||||
extract::Extension,
|
||||
};
|
||||
|
||||
// `(StatusCode, impl IntoResponse)` will override the status code of the response
|
||||
async fn with_status(uri: Uri) -> (StatusCode, String) {
|
||||
(StatusCode::NOT_FOUND, format!("Not Found: {}", uri.path()))
|
||||
}
|
||||
|
||||
// Use `impl IntoResponse` to avoid having to type the whole type
|
||||
async fn impl_trait(uri: Uri) -> impl IntoResponse {
|
||||
(StatusCode::NOT_FOUND, format!("Not Found: {}", uri.path()))
|
||||
}
|
||||
|
||||
// `(HeaderMap, impl IntoResponse)` to add additional headers
|
||||
async fn with_headers() -> impl IntoResponse {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
HeaderName::from_static("x-foo"),
|
||||
HeaderValue::from_static("foo"),
|
||||
);
|
||||
headers.insert(header::CONTENT_TYPE, "text/plain".parse().unwrap());
|
||||
(headers, "foo")
|
||||
}
|
||||
|
||||
// You can also override both status and headers at the same time
|
||||
async fn with_headers_and_status() -> (StatusCode, HeaderMap, &'static str) {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
HeaderName::from_static("x-foo"),
|
||||
HeaderValue::from_static("foo"),
|
||||
);
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, headers, "foo")
|
||||
// Or an array of tuples to more easily build the headers
|
||||
async fn with_array_headers() -> impl IntoResponse {
|
||||
([(header::CONTENT_TYPE, "text/plain")], "foo")
|
||||
}
|
||||
|
||||
// `Headers` makes building the header map easier and `impl Trait` is easier
|
||||
// so you don't have to write the whole type
|
||||
async fn with_easy_headers() -> impl IntoResponse {
|
||||
Headers(vec![("x-foo", "foo")])
|
||||
// Use string keys for custom headers
|
||||
async fn with_array_headers_custom() -> impl IntoResponse {
|
||||
([("x-custom", "custom")], "foo")
|
||||
}
|
||||
|
||||
// `Html` gives a content-type of `text/html`
|
||||
async fn html() -> Html<&'static str> {
|
||||
Html("<h1>Hello, World!</h1>")
|
||||
// `(StatusCode, headers, impl IntoResponse)` to set status and add headers
|
||||
// `headers` can be either a `HeaderMap` or an array of tuples
|
||||
async fn with_status_and_array_headers() -> impl IntoResponse {
|
||||
(
|
||||
StatusCode::NOT_FOUND,
|
||||
[(header::CONTENT_TYPE, "text/plain")],
|
||||
"foo",
|
||||
)
|
||||
}
|
||||
|
||||
// `Json` gives a content-type of `application/json` and works with any type
|
||||
// that implements `serde::Serialize`
|
||||
async fn json() -> Json<Value> {
|
||||
Json(json!({ "data": 42 }))
|
||||
// `(Extension<_>, impl IntoResponse)` to set response extensions
|
||||
async fn with_status_extensions() -> impl IntoResponse {
|
||||
(
|
||||
Extension(Foo("foo")),
|
||||
"foo",
|
||||
)
|
||||
}
|
||||
|
||||
// `Result<T, E>` where `T` and `E` implement `IntoResponse` is useful for
|
||||
// returning errors
|
||||
async fn result() -> Result<&'static str, StatusCode> {
|
||||
Ok("all good")
|
||||
}
|
||||
struct Foo(&'static str);
|
||||
|
||||
// `Response` gives full control
|
||||
async fn response() -> Response<Body> {
|
||||
Response::builder().body(Body::empty()).unwrap()
|
||||
}
|
||||
// Or mix and match all the things
|
||||
async fn all_the_things(uri: Uri) -> impl IntoResponse {
|
||||
let mut header_map = HeaderMap::new();
|
||||
if uri.path() == "/" {
|
||||
header_map.insert(header::SERVER, "axum".parse().unwrap());
|
||||
}
|
||||
|
||||
let app = Router::new()
|
||||
.route("/plain_text", get(plain_text))
|
||||
.route("/plain_text_string", get(plain_text_string))
|
||||
.route("/bytes", get(bytes))
|
||||
.route("/empty", get(empty))
|
||||
.route("/empty_with_status", get(empty_with_status))
|
||||
.route("/with_status", get(with_status))
|
||||
.route("/with_headers", get(with_headers))
|
||||
.route("/with_headers_and_status", get(with_headers_and_status))
|
||||
.route("/with_easy_headers", get(with_easy_headers))
|
||||
.route("/html", get(html))
|
||||
.route("/json", get(json))
|
||||
.route("/result", get(result))
|
||||
.route("/response", get(response));
|
||||
# async {
|
||||
# axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
# };
|
||||
(
|
||||
// set status code
|
||||
StatusCode::NOT_FOUND,
|
||||
// headers ith an array
|
||||
[("x-custom", "custom")],
|
||||
// some extensions
|
||||
Extension(Foo("foo")),
|
||||
Extension(Foo("bar")),
|
||||
// more headers, built dynamically
|
||||
header_map,
|
||||
// and finally the body
|
||||
"foo",
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
In general you can return tuples like:
|
||||
|
||||
- `(StatusCode, impl IntoResponse)`
|
||||
- `(Version, impl IntoResponse)`
|
||||
- `(StatusCode, Version, impl IntoResponse)`
|
||||
- `(T1, .., Tn, impl IntoResponse)` where `T1` to `Tn` all implement [`IntoResponseParts`].
|
||||
- `(StatusCode, T1, .., Tn, impl IntoResponse)` where `T1` to `Tn` all implement [`IntoResponseParts`].
|
||||
- `(StatusCode, Version, T1, .., Tn, impl IntoResponse)` where `T1` to `Tn` all implement [`IntoResponseParts`].
|
||||
|
||||
This means you cannot accidentally override the status, version, or body, as [`IntoResponseParts`] only allows
|
||||
setting headers and extensions.
|
||||
|
||||
Use [`Response`](crate::response::Response) for more low level control:
|
||||
|
||||
```rust,no_run
|
||||
use axum::{
|
||||
Json,
|
||||
response::{IntoResponse, Response},
|
||||
body::Full,
|
||||
http::StatusCode,
|
||||
};
|
||||
|
||||
async fn response() -> impl IntoResponse {
|
||||
Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.header("x-foo", "custom header")
|
||||
.body(Full::from("not found"))
|
||||
.unwrap()
|
||||
}
|
||||
```
|
||||
|
||||
[`IntoResponse`]: crate::response::IntoResponse
|
||||
[`IntoResponseParts`]: crate::response::IntoResponseParts
|
||||
[`StatusCode`]: http::StatusCode
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use crate::response::IntoResponse;
|
||||
|
||||
use super::{rejection::*, FromRequest, RequestParts};
|
||||
use async_trait::async_trait;
|
||||
use axum_core::response::IntoResponse;
|
||||
use std::ops::Deref;
|
||||
|
||||
/// Extractor that will reject requests with a body larger than some size.
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use super::{rejection::*, FromRequest, RequestParts};
|
||||
use crate::response::IntoResponseParts;
|
||||
use async_trait::async_trait;
|
||||
use axum_core::response::{IntoResponse, Response, ResponseParts};
|
||||
use std::ops::Deref;
|
||||
|
||||
/// Extractor that gets a value from request extensions.
|
||||
|
@ -73,3 +75,23 @@ impl<T> Deref for Extension<T> {
|
|||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoResponseParts for Extension<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
fn into_response_parts(self, res: &mut ResponseParts) {
|
||||
res.insert_extension(self.0);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoResponse for Extension<T>
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = ().into_response();
|
||||
res.extensions_mut().insert(self.0);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,12 +4,11 @@
|
|||
mod de;
|
||||
|
||||
use crate::{
|
||||
body::{boxed, Full},
|
||||
extract::{rejection::*, FromRequest, RequestParts},
|
||||
response::{IntoResponse, Response},
|
||||
routing::{InvalidUtf8InPathParam, UrlParams},
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use axum_core::response::{IntoResponse, Response};
|
||||
use http::StatusCode;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::{
|
||||
|
@ -382,9 +381,7 @@ impl IntoResponse for FailedToDeserializePathParams {
|
|||
(StatusCode::INTERNAL_SERVER_ERROR, self.0.kind.to_string())
|
||||
}
|
||||
};
|
||||
let mut res = Response::new(boxed(Full::from(body)));
|
||||
*res.status_mut() = status;
|
||||
res
|
||||
(status, body).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
//! Rejection response types.
|
||||
|
||||
use crate::{
|
||||
body::{boxed, Full},
|
||||
response::{IntoResponse, Response},
|
||||
BoxError, Error,
|
||||
};
|
||||
use crate::{BoxError, Error};
|
||||
use axum_core::response::{IntoResponse, Response};
|
||||
|
||||
pub use crate::extract::path::FailedToDeserializePathParams;
|
||||
pub use axum_core::extract::rejection::*;
|
||||
|
@ -90,9 +87,7 @@ impl FailedToDeserializeQueryString {
|
|||
|
||||
impl IntoResponse for FailedToDeserializeQueryString {
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = Response::new(boxed(Full::from(self.to_string())));
|
||||
*res.status_mut() = http::StatusCode::UNPROCESSABLE_ENTITY;
|
||||
res
|
||||
(http::StatusCode::UNPROCESSABLE_ENTITY, self.to_string()).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use super::{FromRequest, RequestParts};
|
||||
use crate::response::{IntoResponse, Response};
|
||||
use async_trait::async_trait;
|
||||
use axum_core::response::{IntoResponse, IntoResponseParts, Response, ResponseParts};
|
||||
use headers::HeaderMapExt;
|
||||
use http::header::{HeaderName, HeaderValue};
|
||||
use std::ops::Deref;
|
||||
|
||||
/// Extractor that extracts a typed header value from [`headers`].
|
||||
|
@ -65,6 +66,47 @@ impl<T> Deref for TypedHeader<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T> IntoResponseParts for TypedHeader<T>
|
||||
where
|
||||
T: headers::Header,
|
||||
{
|
||||
fn into_response_parts(self, res: &mut ResponseParts) {
|
||||
struct ExtendHeaders<'a> {
|
||||
res: &'a mut ResponseParts,
|
||||
key: &'static HeaderName,
|
||||
}
|
||||
|
||||
impl<'a> Extend<HeaderValue> for ExtendHeaders<'a> {
|
||||
fn extend<T>(&mut self, iter: T)
|
||||
where
|
||||
T: IntoIterator<Item = HeaderValue>,
|
||||
{
|
||||
for value in iter {
|
||||
self.res.append_header(self.key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut extend = ExtendHeaders {
|
||||
res,
|
||||
key: T::name(),
|
||||
};
|
||||
|
||||
self.0.encode(&mut extend);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IntoResponse for TypedHeader<T>
|
||||
where
|
||||
T: headers::Header,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = ().into_response();
|
||||
res.headers_mut().typed_insert(self.0);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
/// Rejection used for [`TypedHeader`](super::TypedHeader).
|
||||
#[cfg(feature = "headers")]
|
||||
#[derive(Debug)]
|
||||
|
@ -97,9 +139,7 @@ pub enum TypedHeaderRejectionReason {
|
|||
|
||||
impl IntoResponse for TypedHeaderRejection {
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = self.to_string().into_response();
|
||||
*res.status_mut() = http::StatusCode::BAD_REQUEST;
|
||||
res
|
||||
(http::StatusCode::BAD_REQUEST, self.to_string()).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use crate::{
|
||||
body::{self, Bytes, Full, HttpBody},
|
||||
body::{Bytes, HttpBody},
|
||||
extract::{rejection::*, FromRequest, RequestParts},
|
||||
response::{IntoResponse, Response},
|
||||
BoxError,
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use axum_core::response::{IntoResponse, Response};
|
||||
use http::{
|
||||
header::{self, HeaderValue},
|
||||
StatusCode,
|
||||
|
@ -158,26 +158,25 @@ where
|
|||
T: Serialize,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
let bytes = match serde_json::to_vec(&self.0) {
|
||||
Ok(res) => res,
|
||||
Err(err) => {
|
||||
return Response::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.header(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
|
||||
)
|
||||
.body(body::boxed(Full::from(err.to_string())))
|
||||
.unwrap();
|
||||
}
|
||||
};
|
||||
|
||||
let mut res = Response::new(body::boxed(Full::from(bytes)));
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
|
||||
);
|
||||
res
|
||||
match serde_json::to_vec(&self.0) {
|
||||
Ok(bytes) => (
|
||||
[(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
|
||||
)],
|
||||
bytes,
|
||||
)
|
||||
.into_response(),
|
||||
Err(err) => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
[(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
|
||||
)],
|
||||
err.to_string(),
|
||||
)
|
||||
.into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -60,9 +60,7 @@ macro_rules! define_rejection {
|
|||
#[allow(deprecated)]
|
||||
impl $crate::response::IntoResponse for $name {
|
||||
fn into_response(self) -> $crate::response::Response {
|
||||
let mut res = http::Response::new($crate::body::boxed(crate::body::Full::from($body)));
|
||||
*res.status_mut() = http::StatusCode::$status;
|
||||
res
|
||||
(http::StatusCode::$status, $body).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,12 +98,12 @@ macro_rules! define_rejection {
|
|||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for $name {
|
||||
impl crate::response::IntoResponse for $name {
|
||||
fn into_response(self) -> $crate::response::Response {
|
||||
let mut res =
|
||||
http::Response::new($crate::body::boxed(crate::body::Full::from(format!(concat!($body, ": {}"), self.0))));
|
||||
*res.status_mut() = http::StatusCode::$status;
|
||||
res
|
||||
(
|
||||
http::StatusCode::$status,
|
||||
format!(concat!($body, ": {}"), self.0),
|
||||
).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,181 +0,0 @@
|
|||
use super::{IntoResponse, IntoResponseHeaders, Response};
|
||||
use http::{
|
||||
header::{HeaderName, HeaderValue},
|
||||
StatusCode,
|
||||
};
|
||||
use std::{convert::TryInto, fmt};
|
||||
|
||||
/// A response with headers.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// use axum::{
|
||||
/// Router,
|
||||
/// response::{IntoResponse, Headers},
|
||||
/// routing::get,
|
||||
/// };
|
||||
/// use http::header::{HeaderName, HeaderValue};
|
||||
///
|
||||
/// // It works with any `IntoIterator<Item = (Key, Value)>` where `Key` can be
|
||||
/// // turned into a `HeaderName` and `Value` can be turned into a `HeaderValue`
|
||||
/// //
|
||||
/// // Such as `Vec<(HeaderName, HeaderValue)>`
|
||||
/// async fn just_headers() -> impl IntoResponse {
|
||||
/// Headers(vec![
|
||||
/// (HeaderName::from_static("X-Foo"), HeaderValue::from_static("foo")),
|
||||
/// ])
|
||||
/// }
|
||||
///
|
||||
/// // Or `Vec<(&str, &str)>`
|
||||
/// async fn from_strings() -> impl IntoResponse {
|
||||
/// Headers(vec![("X-Foo", "foo")])
|
||||
/// }
|
||||
///
|
||||
/// // Or `[(&str, &str)]` if you're on Rust 1.53+
|
||||
///
|
||||
/// let app = Router::new()
|
||||
/// .route("/just-headers", get(just_headers))
|
||||
/// .route("/from-strings", get(from_strings));
|
||||
/// # async {
|
||||
/// # axum::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||
/// # };
|
||||
/// ```
|
||||
///
|
||||
/// If a conversion to `HeaderName` or `HeaderValue` fails a `500 Internal
|
||||
/// Server Error` response will be returned.
|
||||
///
|
||||
/// You can also return `(Headers, impl IntoResponse)` to customize the headers
|
||||
/// of a response, or `(StatusCode, Headers, impl IntoResponse)` to customize
|
||||
/// the status code and headers.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Headers<H>(pub H);
|
||||
|
||||
impl<H, K, V> IntoResponseHeaders for Headers<H>
|
||||
where
|
||||
H: IntoIterator<Item = (K, V)>,
|
||||
K: TryInto<HeaderName>,
|
||||
K::Error: fmt::Display,
|
||||
V: TryInto<HeaderValue>,
|
||||
V::Error: fmt::Display,
|
||||
{
|
||||
type IntoIter = IntoIter<H::IntoIter>;
|
||||
|
||||
fn into_headers(self) -> Self::IntoIter {
|
||||
IntoIter {
|
||||
inner: self.0.into_iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, K, V> IntoResponse for Headers<H>
|
||||
where
|
||||
H: IntoIterator<Item = (K, V)>,
|
||||
K: TryInto<HeaderName>,
|
||||
K::Error: fmt::Display,
|
||||
V: TryInto<HeaderValue>,
|
||||
V::Error: fmt::Display,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
(self, ()).into_response()
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Debug)]
|
||||
pub struct IntoIter<H> {
|
||||
inner: H,
|
||||
}
|
||||
|
||||
impl<H, K, V> Iterator for IntoIter<H>
|
||||
where
|
||||
H: Iterator<Item = (K, V)>,
|
||||
K: TryInto<HeaderName>,
|
||||
K::Error: fmt::Display,
|
||||
V: TryInto<HeaderValue>,
|
||||
V::Error: fmt::Display,
|
||||
{
|
||||
type Item = Result<(Option<HeaderName>, HeaderValue), Response>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next().map(|(key, value)| {
|
||||
let key = key
|
||||
.try_into()
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response())?;
|
||||
let value = value
|
||||
.try_into()
|
||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response())?;
|
||||
|
||||
Ok((Some(key), value))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use futures_util::FutureExt;
|
||||
use http::header::USER_AGENT;
|
||||
|
||||
#[test]
|
||||
fn vec_of_header_name_and_value() {
|
||||
let res = Headers(vec![(USER_AGENT, HeaderValue::from_static("axum"))]).into_response();
|
||||
|
||||
assert_eq!(res.headers()["user-agent"], "axum");
|
||||
assert_eq!(res.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vec_of_strings() {
|
||||
let res = Headers(vec![("user-agent", "axum")]).into_response();
|
||||
|
||||
assert_eq!(res.headers()["user-agent"], "axum");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_body() {
|
||||
let res = (Headers(vec![("user-agent", "axum")]), "foo").into_response();
|
||||
|
||||
assert_eq!(res.headers()["user-agent"], "axum");
|
||||
let body = hyper::body::to_bytes(res.into_body())
|
||||
.now_or_never()
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(&body[..], b"foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_status_and_body() {
|
||||
let res = (
|
||||
StatusCode::NOT_FOUND,
|
||||
Headers(vec![("user-agent", "axum")]),
|
||||
"foo",
|
||||
)
|
||||
.into_response();
|
||||
|
||||
assert_eq!(res.headers()["user-agent"], "axum");
|
||||
assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||
let body = hyper::body::to_bytes(res.into_body())
|
||||
.now_or_never()
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(&body[..], b"foo");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_header_name() {
|
||||
let bytes: &[u8] = &[0, 159, 146, 150]; // invalid utf-8
|
||||
let res = Headers(vec![(bytes, "axum")]).into_response();
|
||||
|
||||
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_header_value() {
|
||||
let bytes: &[u8] = &[0, 159, 146, 150]; // invalid utf-8
|
||||
let res = Headers(vec![("user-agent", bytes)]).into_response();
|
||||
|
||||
assert!(res.headers().get("user-agent").is_none());
|
||||
assert_eq!(res.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
|
@ -1,10 +1,8 @@
|
|||
#![doc = include_str!("../docs/response.md")]
|
||||
|
||||
use crate::body::{Bytes, Full};
|
||||
use axum_core::body::boxed;
|
||||
use http::{header, HeaderValue};
|
||||
|
||||
mod headers;
|
||||
mod redirect;
|
||||
|
||||
pub mod sse;
|
||||
|
@ -14,10 +12,10 @@ pub mod sse;
|
|||
pub use crate::Json;
|
||||
|
||||
#[doc(inline)]
|
||||
pub use axum_core::response::{IntoResponse, IntoResponseHeaders, Response};
|
||||
pub use axum_core::response::{IntoResponse, IntoResponseParts, Response, ResponseParts};
|
||||
|
||||
#[doc(inline)]
|
||||
pub use self::{headers::Headers, redirect::Redirect, sse::Sse};
|
||||
pub use self::{redirect::Redirect, sse::Sse};
|
||||
|
||||
/// An HTML response.
|
||||
///
|
||||
|
@ -30,12 +28,14 @@ where
|
|||
T: Into<Full<Bytes>>,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = Response::new(boxed(self.0.into()));
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::TEXT_HTML_UTF_8.as_ref()),
|
||||
);
|
||||
res
|
||||
(
|
||||
[(
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(mime::TEXT_HTML_UTF_8.as_ref()),
|
||||
)],
|
||||
self.0.into(),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,43 +47,166 @@ impl<T> From<T> for Html<T> {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::body::Empty;
|
||||
use http::{
|
||||
header::{HeaderMap, HeaderName},
|
||||
StatusCode,
|
||||
};
|
||||
use crate::extract::Extension;
|
||||
use crate::{body::Body, routing::get, Router};
|
||||
use axum_core::response::IntoResponse;
|
||||
use http::HeaderMap;
|
||||
use http::{StatusCode, Uri};
|
||||
|
||||
#[test]
|
||||
fn test_merge_headers() {
|
||||
struct MyResponse;
|
||||
// just needs to compile
|
||||
#[allow(dead_code)]
|
||||
fn impl_trait_result_works() {
|
||||
async fn impl_trait_ok() -> Result<impl IntoResponse, ()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl IntoResponse for MyResponse {
|
||||
fn into_response(self) -> Response {
|
||||
let mut resp = Response::new(boxed(Empty::new()));
|
||||
resp.headers_mut()
|
||||
.insert(HeaderName::from_static("a"), HeaderValue::from_static("1"));
|
||||
resp
|
||||
async fn impl_trait_err() -> Result<(), impl IntoResponse> {
|
||||
Err(())
|
||||
}
|
||||
|
||||
async fn impl_trait_both(uri: Uri) -> Result<impl IntoResponse, impl IntoResponse> {
|
||||
if uri.path() == "/" {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
fn check(resp: impl IntoResponse) {
|
||||
let resp = resp.into_response();
|
||||
assert_eq!(
|
||||
resp.headers().get(HeaderName::from_static("a")).unwrap(),
|
||||
&HeaderValue::from_static("1")
|
||||
);
|
||||
assert_eq!(
|
||||
resp.headers().get(HeaderName::from_static("b")).unwrap(),
|
||||
&HeaderValue::from_static("2")
|
||||
);
|
||||
async fn impl_trait(uri: Uri) -> impl IntoResponse {
|
||||
if uri.path() == "/" {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
}
|
||||
|
||||
let headers: HeaderMap =
|
||||
std::iter::once((HeaderName::from_static("b"), HeaderValue::from_static("2")))
|
||||
.collect();
|
||||
Router::<Body>::new()
|
||||
.route("/", get(impl_trait_ok))
|
||||
.route("/", get(impl_trait_err))
|
||||
.route("/", get(impl_trait_both))
|
||||
.route("/", get(impl_trait));
|
||||
}
|
||||
|
||||
check((headers.clone(), MyResponse));
|
||||
check((StatusCode::OK, headers, MyResponse));
|
||||
// just needs to compile
|
||||
#[allow(dead_code)]
|
||||
fn tuple_responses() {
|
||||
async fn status() -> impl IntoResponse {
|
||||
StatusCode::OK
|
||||
}
|
||||
|
||||
async fn status_headermap() -> impl IntoResponse {
|
||||
(StatusCode::OK, HeaderMap::new())
|
||||
}
|
||||
|
||||
async fn status_header_array() -> impl IntoResponse {
|
||||
(StatusCode::OK, [("content-type", "text/plain")])
|
||||
}
|
||||
|
||||
async fn status_headermap_body() -> impl IntoResponse {
|
||||
(StatusCode::OK, HeaderMap::new(), String::new())
|
||||
}
|
||||
|
||||
async fn status_header_array_body() -> impl IntoResponse {
|
||||
(
|
||||
StatusCode::OK,
|
||||
[("content-type", "text/plain")],
|
||||
String::new(),
|
||||
)
|
||||
}
|
||||
|
||||
async fn status_headermap_impl_into_response() -> impl IntoResponse {
|
||||
(StatusCode::OK, HeaderMap::new(), impl_into_response())
|
||||
}
|
||||
|
||||
async fn status_header_array_impl_into_response() -> impl IntoResponse {
|
||||
(
|
||||
StatusCode::OK,
|
||||
[("content-type", "text/plain")],
|
||||
impl_into_response(),
|
||||
)
|
||||
}
|
||||
|
||||
fn impl_into_response() -> impl IntoResponse {}
|
||||
|
||||
async fn status_header_array_extension_body() -> impl IntoResponse {
|
||||
(
|
||||
StatusCode::OK,
|
||||
[("content-type", "text/plain")],
|
||||
Extension(1),
|
||||
String::new(),
|
||||
)
|
||||
}
|
||||
|
||||
async fn status_header_array_extension_mixed_body() -> impl IntoResponse {
|
||||
(
|
||||
StatusCode::OK,
|
||||
[("content-type", "text/plain")],
|
||||
Extension(1),
|
||||
HeaderMap::new(),
|
||||
String::new(),
|
||||
)
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
async fn headermap() -> impl IntoResponse {
|
||||
HeaderMap::new()
|
||||
}
|
||||
|
||||
async fn header_array() -> impl IntoResponse {
|
||||
[("content-type", "text/plain")]
|
||||
}
|
||||
|
||||
async fn headermap_body() -> impl IntoResponse {
|
||||
(HeaderMap::new(), String::new())
|
||||
}
|
||||
|
||||
async fn header_array_body() -> impl IntoResponse {
|
||||
([("content-type", "text/plain")], String::new())
|
||||
}
|
||||
|
||||
async fn headermap_impl_into_response() -> impl IntoResponse {
|
||||
(HeaderMap::new(), impl_into_response())
|
||||
}
|
||||
|
||||
async fn header_array_impl_into_response() -> impl IntoResponse {
|
||||
([("content-type", "text/plain")], impl_into_response())
|
||||
}
|
||||
|
||||
async fn header_array_extension_body() -> impl IntoResponse {
|
||||
(
|
||||
[("content-type", "text/plain")],
|
||||
Extension(1),
|
||||
String::new(),
|
||||
)
|
||||
}
|
||||
|
||||
async fn header_array_extension_mixed_body() -> impl IntoResponse {
|
||||
(
|
||||
[("content-type", "text/plain")],
|
||||
Extension(1),
|
||||
HeaderMap::new(),
|
||||
String::new(),
|
||||
)
|
||||
}
|
||||
|
||||
Router::<Body>::new()
|
||||
.route("/", get(status))
|
||||
.route("/", get(status_headermap))
|
||||
.route("/", get(status_header_array))
|
||||
.route("/", get(status_headermap_body))
|
||||
.route("/", get(status_header_array_body))
|
||||
.route("/", get(status_headermap_impl_into_response))
|
||||
.route("/", get(status_header_array_impl_into_response))
|
||||
.route("/", get(status_header_array_extension_body))
|
||||
.route("/", get(status_header_array_extension_mixed_body))
|
||||
.route("/", get(headermap))
|
||||
.route("/", get(header_array))
|
||||
.route("/", get(headermap_body))
|
||||
.route("/", get(header_array_body))
|
||||
.route("/", get(headermap_impl_into_response))
|
||||
.route("/", get(header_array_impl_into_response))
|
||||
.route("/", get(header_array_extension_body))
|
||||
.route("/", get(header_array_extension_mixed_body));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use super::{IntoResponse, Response};
|
||||
use crate::body::{boxed, Empty};
|
||||
use axum_core::response::{IntoResponse, Response};
|
||||
use http::{header::LOCATION, HeaderValue, StatusCode, Uri};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
|
@ -111,9 +110,6 @@ impl Redirect {
|
|||
|
||||
impl IntoResponse for Redirect {
|
||||
fn into_response(self) -> Response {
|
||||
let mut res = Response::new(boxed(Empty::new()));
|
||||
*res.status_mut() = self.status_code;
|
||||
res.headers_mut().insert(LOCATION, self.location);
|
||||
res
|
||||
(self.status_code, [(LOCATION, self.location)]).into_response()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,10 +28,13 @@
|
|||
//! ```
|
||||
|
||||
use crate::{
|
||||
body::{self, Bytes, HttpBody},
|
||||
response::{IntoResponse, Response},
|
||||
body::{Bytes, HttpBody},
|
||||
BoxError,
|
||||
};
|
||||
use axum_core::{
|
||||
body,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use bytes::{BufMut, BytesMut};
|
||||
use futures_util::{
|
||||
ready,
|
||||
|
@ -95,16 +98,17 @@ where
|
|||
E: Into<BoxError>,
|
||||
{
|
||||
fn into_response(self) -> Response {
|
||||
let body = body::boxed(Body {
|
||||
event_stream: SyncWrapper::new(self.stream),
|
||||
keep_alive: self.keep_alive.map(KeepAliveStream::new),
|
||||
});
|
||||
|
||||
Response::builder()
|
||||
.header(http::header::CONTENT_TYPE, mime::TEXT_EVENT_STREAM.as_ref())
|
||||
.header(http::header::CACHE_CONTROL, "no-cache")
|
||||
.body(body)
|
||||
.unwrap()
|
||||
(
|
||||
[
|
||||
(http::header::CONTENT_TYPE, mime::TEXT_EVENT_STREAM.as_ref()),
|
||||
(http::header::CACHE_CONTROL, "no-cache"),
|
||||
],
|
||||
body::boxed(Body {
|
||||
event_stream: SyncWrapper::new(self.stream),
|
||||
keep_alive: self.keep_alive.map(KeepAliveStream::new),
|
||||
}),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1202,10 +1202,9 @@ mod tests {
|
|||
if method == Method::POST {
|
||||
"OK".into_response()
|
||||
} else {
|
||||
let headers = crate::response::Headers([(ALLOW, "GET,POST")]);
|
||||
(
|
||||
StatusCode::METHOD_NOT_ALLOWED,
|
||||
headers,
|
||||
[(ALLOW, "GET,POST")],
|
||||
"Method not allowed",
|
||||
)
|
||||
.into_response()
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
use askama::Template;
|
||||
use axum::{
|
||||
body::{self, Full},
|
||||
extract,
|
||||
http::StatusCode,
|
||||
response::{Html, IntoResponse, Response},
|
||||
|
@ -55,13 +54,11 @@ where
|
|||
fn into_response(self) -> Response {
|
||||
match self.0.render() {
|
||||
Ok(html) => Html(html).into_response(),
|
||||
Err(err) => Response::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(body::boxed(Full::from(format!(
|
||||
"Failed to render template. Error: {}",
|
||||
err
|
||||
))))
|
||||
.unwrap(),
|
||||
Err(err) => (
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Failed to render template. Error: {}", err),
|
||||
)
|
||||
.into_response(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue