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:
David Pedersen 2022-03-01 00:04:33 +01:00 committed by GitHub
parent da74084146
commit f12ab072c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 1117 additions and 930 deletions

View file

@ -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)

View file

@ -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"] }

View file

@ -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()
}
}

View file

@ -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()
}
}

View 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);

View 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);
}
}
}

View file

@ -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(());
}
}
}
}
}

View file

@ -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(),
}
}
}

View file

@ -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)

View file

@ -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

View file

@ -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.

View file

@ -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
}
}

View file

@ -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()
}
}

View file

@ -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()
}
}

View file

@ -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()
}
}

View file

@ -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(),
}
}
}

View file

@ -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()
}
}

View file

@ -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);
}
}

View file

@ -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));
}
}

View file

@ -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()
}
}

View file

@ -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()
}
}

View file

@ -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()

View file

@ -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(),
}
}
}