mirror of
https://github.com/tokio-rs/axum.git
synced 2024-11-28 19:22:56 +01:00
Support returning any http_body::Body
from IntoResponse
(#86)
Adds associated `Body` and `BodyError` types to `IntoResponse`. This is required for returning responses with bodies other than `hyper::Body` from handlers. That wasn't previously possible. This is a breaking change so should be shipped in 0.2.
This commit is contained in:
parent
4194cf70da
commit
ab927033b3
13 changed files with 347 additions and 83 deletions
|
@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## Breaking changes
|
## Breaking changes
|
||||||
|
|
||||||
|
- Add associated `Body` and `BodyError` types to `IntoResponse`. This is
|
||||||
|
required for returning responses with bodies other than `hyper::Body` from
|
||||||
|
handlers. See the docs for advice on how to implement `IntoResponse` ([#86](https://github.com/tokio-rs/axum/pull/86))
|
||||||
- Change WebSocket API to use an extractor ([#121](https://github.com/tokio-rs/axum/pull/121))
|
- Change WebSocket API to use an extractor ([#121](https://github.com/tokio-rs/axum/pull/121))
|
||||||
- Add `RoutingDsl::or` for combining routes. ([#108](https://github.com/tokio-rs/axum/pull/108))
|
- Add `RoutingDsl::or` for combining routes. ([#108](https://github.com/tokio-rs/axum/pull/108))
|
||||||
- Ensure a `HandleError` service created from `axum::ServiceExt::handle_error`
|
- Ensure a `HandleError` service created from `axum::ServiceExt::handle_error`
|
||||||
|
|
|
@ -16,10 +16,12 @@ use axum::{
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
AddExtensionLayer,
|
AddExtensionLayer,
|
||||||
};
|
};
|
||||||
use http::StatusCode;
|
use bytes::Bytes;
|
||||||
|
use http::{Response, StatusCode};
|
||||||
|
use http_body::Full;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::{net::SocketAddr, sync::Arc};
|
use std::{convert::Infallible, net::SocketAddr, sync::Arc};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
@ -89,7 +91,10 @@ impl From<UserRepoError> for AppError {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for AppError {
|
impl IntoResponse for AppError {
|
||||||
fn into_response(self) -> http::Response<Body> {
|
type Body = Full<Bytes>;
|
||||||
|
type BodyError = Infallible;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<Self::Body> {
|
||||||
let (status, error_json) = match self {
|
let (status, error_json) = match self {
|
||||||
AppError::UserRepo(UserRepoError::NotFound) => {
|
AppError::UserRepo(UserRepoError::NotFound) => {
|
||||||
(StatusCode::NOT_FOUND, json!("User not found"))
|
(StatusCode::NOT_FOUND, json!("User not found"))
|
||||||
|
|
|
@ -6,8 +6,10 @@
|
||||||
|
|
||||||
use askama::Template;
|
use askama::Template;
|
||||||
use axum::{prelude::*, response::IntoResponse};
|
use axum::{prelude::*, response::IntoResponse};
|
||||||
|
use bytes::Bytes;
|
||||||
use http::{Response, StatusCode};
|
use http::{Response, StatusCode};
|
||||||
use std::net::SocketAddr;
|
use http_body::Full;
|
||||||
|
use std::{convert::Infallible, net::SocketAddr};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
@ -46,12 +48,15 @@ impl<T> IntoResponse for HtmlTemplate<T>
|
||||||
where
|
where
|
||||||
T: Template,
|
T: Template,
|
||||||
{
|
{
|
||||||
fn into_response(self) -> http::Response<Body> {
|
type Body = Full<Bytes>;
|
||||||
|
type BodyError = Infallible;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<Self::Body> {
|
||||||
match self.0.render() {
|
match self.0.render() {
|
||||||
Ok(html) => response::Html(html).into_response(),
|
Ok(html) => response::Html(html).into_response(),
|
||||||
Err(err) => Response::builder()
|
Err(err) => Response::builder()
|
||||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
.body(Body::from(format!(
|
.body(Full::from(format!(
|
||||||
"Failed to render template. Error: {}",
|
"Failed to render template. Error: {}",
|
||||||
err
|
err
|
||||||
)))
|
)))
|
||||||
|
|
|
@ -10,8 +10,10 @@ use axum::{
|
||||||
extract::{FromRequest, RequestParts},
|
extract::{FromRequest, RequestParts},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
use bytes::Bytes;
|
||||||
use http::Response;
|
use http::Response;
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
|
use http_body::Full;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
@ -51,7 +53,7 @@ impl<B> FromRequest<B> for Version
|
||||||
where
|
where
|
||||||
B: Send,
|
B: Send,
|
||||||
{
|
{
|
||||||
type Rejection = Response<Body>;
|
type Rejection = Response<Full<Bytes>>;
|
||||||
|
|
||||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||||
let params = extract::Path::<HashMap<String, String>>::from_request(req)
|
let params = extract::Path::<HashMap<String, String>>::from_request(req)
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use crate::response::IntoResponse;
|
||||||
|
|
||||||
use super::{rejection::*, FromRequest, RequestParts};
|
use super::{rejection::*, FromRequest, RequestParts};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
@ -27,6 +29,7 @@ pub struct ContentLengthLimit<T, const N: u64>(pub T);
|
||||||
impl<T, B, const N: u64> FromRequest<B> for ContentLengthLimit<T, N>
|
impl<T, B, const N: u64> FromRequest<B> for ContentLengthLimit<T, N>
|
||||||
where
|
where
|
||||||
T: FromRequest<B>,
|
T: FromRequest<B>,
|
||||||
|
T::Rejection: IntoResponse,
|
||||||
B: Send,
|
B: Send,
|
||||||
{
|
{
|
||||||
type Rejection = ContentLengthLimitRejection<T::Rejection>;
|
type Rejection = ContentLengthLimitRejection<T::Rejection>;
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
//! Rejection response types.
|
//! Rejection response types.
|
||||||
|
|
||||||
use super::IntoResponse;
|
use super::IntoResponse;
|
||||||
use crate::body::Body;
|
use crate::body::{box_body, BoxBody, BoxStdError};
|
||||||
|
use bytes::Bytes;
|
||||||
|
use http_body::Full;
|
||||||
|
use std::convert::Infallible;
|
||||||
use tower::BoxError;
|
use tower::BoxError;
|
||||||
|
|
||||||
define_rejection! {
|
define_rejection! {
|
||||||
|
@ -141,8 +144,11 @@ impl InvalidUrlParam {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for InvalidUrlParam {
|
impl IntoResponse for InvalidUrlParam {
|
||||||
fn into_response(self) -> http::Response<Body> {
|
type Body = Full<Bytes>;
|
||||||
let mut res = http::Response::new(Body::from(format!(
|
type BodyError = Infallible;
|
||||||
|
|
||||||
|
fn into_response(self) -> http::Response<Self::Body> {
|
||||||
|
let mut res = http::Response::new(Full::from(format!(
|
||||||
"Invalid URL param. Expected something of type `{}`",
|
"Invalid URL param. Expected something of type `{}`",
|
||||||
self.type_name
|
self.type_name
|
||||||
)));
|
)));
|
||||||
|
@ -163,8 +169,11 @@ impl InvalidPathParam {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for InvalidPathParam {
|
impl IntoResponse for InvalidPathParam {
|
||||||
fn into_response(self) -> http::Response<Body> {
|
type Body = Full<Bytes>;
|
||||||
let mut res = http::Response::new(Body::from(format!("Invalid URL param. {}", self.0)));
|
type BodyError = Infallible;
|
||||||
|
|
||||||
|
fn into_response(self) -> http::Response<Self::Body> {
|
||||||
|
let mut res = http::Response::new(Full::from(format!("Invalid URL param. {}", self.0)));
|
||||||
*res.status_mut() = http::StatusCode::BAD_REQUEST;
|
*res.status_mut() = http::StatusCode::BAD_REQUEST;
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
@ -191,8 +200,11 @@ impl FailedToDeserializeQueryString {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for FailedToDeserializeQueryString {
|
impl IntoResponse for FailedToDeserializeQueryString {
|
||||||
fn into_response(self) -> http::Response<Body> {
|
type Body = Full<Bytes>;
|
||||||
let mut res = http::Response::new(Body::from(format!(
|
type BodyError = Infallible;
|
||||||
|
|
||||||
|
fn into_response(self) -> http::Response<Self::Body> {
|
||||||
|
let mut res = http::Response::new(Full::from(format!(
|
||||||
"Failed to deserialize query string. Expected something of type `{}`. Error: {}",
|
"Failed to deserialize query string. Expected something of type `{}`. Error: {}",
|
||||||
self.type_name, self.error,
|
self.type_name, self.error,
|
||||||
)));
|
)));
|
||||||
|
@ -317,12 +329,15 @@ impl<T> IntoResponse for ContentLengthLimitRejection<T>
|
||||||
where
|
where
|
||||||
T: IntoResponse,
|
T: IntoResponse,
|
||||||
{
|
{
|
||||||
fn into_response(self) -> http::Response<Body> {
|
type Body = BoxBody;
|
||||||
|
type BodyError = BoxStdError;
|
||||||
|
|
||||||
|
fn into_response(self) -> http::Response<Self::Body> {
|
||||||
match self {
|
match self {
|
||||||
Self::PayloadTooLarge(inner) => inner.into_response(),
|
Self::PayloadTooLarge(inner) => inner.into_response().map(box_body),
|
||||||
Self::LengthRequired(inner) => inner.into_response(),
|
Self::LengthRequired(inner) => inner.into_response().map(box_body),
|
||||||
Self::HeadersAlreadyExtracted(inner) => inner.into_response(),
|
Self::HeadersAlreadyExtracted(inner) => inner.into_response().map(box_body),
|
||||||
Self::Inner(inner) => inner.into_response(),
|
Self::Inner(inner) => inner.into_response().map(box_body),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -339,7 +354,10 @@ pub struct TypedHeaderRejection {
|
||||||
#[cfg(feature = "headers")]
|
#[cfg(feature = "headers")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "headers")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "headers")))]
|
||||||
impl IntoResponse for TypedHeaderRejection {
|
impl IntoResponse for TypedHeaderRejection {
|
||||||
fn into_response(self) -> http::Response<crate::Body> {
|
type Body = Full<Bytes>;
|
||||||
|
type BodyError = Infallible;
|
||||||
|
|
||||||
|
fn into_response(self) -> http::Response<Self::Body> {
|
||||||
let mut res = format!("{} ({})", self.err, self.name).into_response();
|
let mut res = format!("{} ({})", self.err, self.name).into_response();
|
||||||
*res.status_mut() = http::StatusCode::BAD_REQUEST;
|
*res.status_mut() = http::StatusCode::BAD_REQUEST;
|
||||||
res
|
res
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
use super::{FromRequest, RequestParts};
|
use super::{FromRequest, RequestParts};
|
||||||
use crate::response::IntoResponse;
|
use crate::{
|
||||||
|
body::{box_body, BoxBody},
|
||||||
|
response::IntoResponse,
|
||||||
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use http::Response;
|
use http::Response;
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
|
@ -29,11 +32,11 @@ macro_rules! impl_from_request {
|
||||||
$( $tail: FromRequest<B> + Send, )*
|
$( $tail: FromRequest<B> + Send, )*
|
||||||
B: Send,
|
B: Send,
|
||||||
{
|
{
|
||||||
type Rejection = Response<crate::body::Body>;
|
type Rejection = Response<BoxBody>;
|
||||||
|
|
||||||
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
|
||||||
let $head = $head::from_request(req).await.map_err(IntoResponse::into_response)?;
|
let $head = $head::from_request(req).await.map_err(|err| err.into_response().map(box_body))?;
|
||||||
$( let $tail = $tail::from_request(req).await.map_err(IntoResponse::into_response)?; )*
|
$( let $tail = $tail::from_request(req).await.map_err(|err| err.into_response().map(box_body))?; )*
|
||||||
Ok(($head, $($tail,)*))
|
Ok(($head, $($tail,)*))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,10 +48,8 @@ use http::{
|
||||||
header::{self, HeaderName, HeaderValue},
|
header::{self, HeaderName, HeaderValue},
|
||||||
Method, Response, StatusCode,
|
Method, Response, StatusCode,
|
||||||
};
|
};
|
||||||
use hyper::{
|
use http_body::Full;
|
||||||
upgrade::{OnUpgrade, Upgraded},
|
use hyper::upgrade::{OnUpgrade, Upgraded};
|
||||||
Body,
|
|
||||||
};
|
|
||||||
use sha1::{Digest, Sha1};
|
use sha1::{Digest, Sha1};
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
|
@ -256,7 +254,10 @@ where
|
||||||
F: FnOnce(WebSocket) -> Fut + Send + 'static,
|
F: FnOnce(WebSocket) -> Fut + Send + 'static,
|
||||||
Fut: Future + Send + 'static,
|
Fut: Future + Send + 'static,
|
||||||
{
|
{
|
||||||
fn into_response(self) -> Response<Body> {
|
type Body = Full<Bytes>;
|
||||||
|
type BodyError = <Self::Body as http_body::Body>::Error;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<Self::Body> {
|
||||||
// check requested protocols
|
// check requested protocols
|
||||||
let protocol = self
|
let protocol = self
|
||||||
.extractor
|
.extractor
|
||||||
|
@ -315,7 +316,7 @@ where
|
||||||
builder = builder.header(header::SEC_WEBSOCKET_PROTOCOL, protocol);
|
builder = builder.header(header::SEC_WEBSOCKET_PROTOCOL, protocol);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.body(Body::empty()).unwrap()
|
builder.body(Full::default()).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -268,6 +268,7 @@ macro_rules! impl_handler {
|
||||||
Fut: Future<Output = Res> + Send,
|
Fut: Future<Output = Res> + Send,
|
||||||
B: Send + 'static,
|
B: Send + 'static,
|
||||||
Res: IntoResponse,
|
Res: IntoResponse,
|
||||||
|
B: Send + 'static,
|
||||||
$head: FromRequest<B> + Send,
|
$head: FromRequest<B> + Send,
|
||||||
$( $tail: FromRequest<B> + Send,)*
|
$( $tail: FromRequest<B> + Send,)*
|
||||||
{
|
{
|
||||||
|
@ -278,13 +279,13 @@ macro_rules! impl_handler {
|
||||||
|
|
||||||
let $head = match $head::from_request(&mut req).await {
|
let $head = match $head::from_request(&mut req).await {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(rejection) => return rejection.into_response().map(crate::body::box_body),
|
Err(rejection) => return rejection.into_response().map(box_body),
|
||||||
};
|
};
|
||||||
|
|
||||||
$(
|
$(
|
||||||
let $tail = match $tail::from_request(&mut req).await {
|
let $tail = match $tail::from_request(&mut req).await {
|
||||||
Ok(value) => value,
|
Ok(value) => value,
|
||||||
Err(rejection) => return rejection.into_response().map(crate::body::box_body),
|
Err(rejection) => return rejection.into_response().map(box_body),
|
||||||
};
|
};
|
||||||
)*
|
)*
|
||||||
|
|
||||||
|
|
17
src/json.rs
17
src/json.rs
|
@ -1,16 +1,20 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
extract::{has_content_type, rejection::*, take_body, FromRequest, RequestParts},
|
extract::{has_content_type, rejection::*, take_body, FromRequest, RequestParts},
|
||||||
prelude::response::IntoResponse,
|
prelude::response::IntoResponse,
|
||||||
Body,
|
|
||||||
};
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use bytes::Bytes;
|
||||||
use http::{
|
use http::{
|
||||||
header::{self, HeaderValue},
|
header::{self, HeaderValue},
|
||||||
StatusCode,
|
StatusCode,
|
||||||
};
|
};
|
||||||
|
use http_body::Full;
|
||||||
use hyper::Response;
|
use hyper::Response;
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::{
|
||||||
|
convert::Infallible,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
};
|
||||||
|
|
||||||
/// JSON Extractor/Response
|
/// JSON Extractor/Response
|
||||||
///
|
///
|
||||||
|
@ -132,19 +136,22 @@ impl<T> IntoResponse for Json<T>
|
||||||
where
|
where
|
||||||
T: Serialize,
|
T: Serialize,
|
||||||
{
|
{
|
||||||
fn into_response(self) -> Response<Body> {
|
type Body = Full<Bytes>;
|
||||||
|
type BodyError = Infallible;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<Self::Body> {
|
||||||
let bytes = match serde_json::to_vec(&self.0) {
|
let bytes = match serde_json::to_vec(&self.0) {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return Response::builder()
|
return Response::builder()
|
||||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
.header(header::CONTENT_TYPE, "text/plain")
|
.header(header::CONTENT_TYPE, "text/plain")
|
||||||
.body(Body::from(err.to_string()))
|
.body(Full::from(err.to_string()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut res = Response::new(Body::from(bytes));
|
let mut res = Response::new(Full::from(bytes));
|
||||||
res.headers_mut().insert(
|
res.headers_mut().insert(
|
||||||
header::CONTENT_TYPE,
|
header::CONTENT_TYPE,
|
||||||
HeaderValue::from_static("application/json"),
|
HeaderValue::from_static("application/json"),
|
||||||
|
|
|
@ -707,7 +707,6 @@
|
||||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
#![cfg_attr(test, allow(clippy::float_cmp))]
|
#![cfg_attr(test, allow(clippy::float_cmp))]
|
||||||
|
|
||||||
use self::body::Body;
|
|
||||||
use http::Request;
|
use http::Request;
|
||||||
use routing::{EmptyRouter, Route};
|
use routing::{EmptyRouter, Route};
|
||||||
use tower::Service;
|
use tower::Service;
|
||||||
|
|
|
@ -49,8 +49,11 @@ macro_rules! define_rejection {
|
||||||
|
|
||||||
#[allow(deprecated)]
|
#[allow(deprecated)]
|
||||||
impl $crate::response::IntoResponse for $name {
|
impl $crate::response::IntoResponse for $name {
|
||||||
fn into_response(self) -> http::Response<$crate::body::Body> {
|
type Body = http_body::Full<bytes::Bytes>;
|
||||||
let mut res = http::Response::new($crate::body::Body::from($body));
|
type BodyError = std::convert::Infallible;
|
||||||
|
|
||||||
|
fn into_response(self) -> http::Response<Self::Body> {
|
||||||
|
let mut res = http::Response::new(http_body::Full::from($body));
|
||||||
*res.status_mut() = http::StatusCode::$status;
|
*res.status_mut() = http::StatusCode::$status;
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
@ -77,9 +80,12 @@ macro_rules! define_rejection {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for $name {
|
impl IntoResponse for $name {
|
||||||
fn into_response(self) -> http::Response<Body> {
|
type Body = http_body::Full<bytes::Bytes>;
|
||||||
|
type BodyError = std::convert::Infallible;
|
||||||
|
|
||||||
|
fn into_response(self) -> http::Response<Self::Body> {
|
||||||
let mut res =
|
let mut res =
|
||||||
http::Response::new(Body::from(format!(concat!($body, ": {}"), self.0)));
|
http::Response::new(http_body::Full::from(format!(concat!($body, ": {}"), self.0)));
|
||||||
*res.status_mut() = http::StatusCode::$status;
|
*res.status_mut() = http::StatusCode::$status;
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
@ -106,7 +112,10 @@ macro_rules! composite_rejection {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl $crate::response::IntoResponse for $name {
|
impl $crate::response::IntoResponse for $name {
|
||||||
fn into_response(self) -> http::Response<$crate::body::Body> {
|
type Body = http_body::Full<bytes::Bytes>;
|
||||||
|
type BodyError = std::convert::Infallible;
|
||||||
|
|
||||||
|
fn into_response(self) -> http::Response<Self::Body> {
|
||||||
match self {
|
match self {
|
||||||
$(
|
$(
|
||||||
Self::$variant(inner) => inner.into_response(),
|
Self::$variant(inner) => inner.into_response(),
|
||||||
|
|
286
src/response.rs
286
src/response.rs
|
@ -1,10 +1,14 @@
|
||||||
//! Types and traits for generating responses.
|
//! Types and traits for generating responses.
|
||||||
|
|
||||||
use crate::Body;
|
use crate::body::{box_body, BoxBody, BoxStdError};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use http::{header, HeaderMap, HeaderValue, Response, StatusCode};
|
use http::{header, HeaderMap, HeaderValue, Response, StatusCode};
|
||||||
|
use http_body::{
|
||||||
|
combinators::{MapData, MapErr},
|
||||||
|
Empty, Full,
|
||||||
|
};
|
||||||
use std::{borrow::Cow, convert::Infallible};
|
use std::{borrow::Cow, convert::Infallible};
|
||||||
use tower::util::Either;
|
use tower::{util::Either, BoxError};
|
||||||
|
|
||||||
#[doc(no_inline)]
|
#[doc(no_inline)]
|
||||||
pub use crate::Json;
|
pub use crate::Json;
|
||||||
|
@ -12,19 +16,119 @@ pub use crate::Json;
|
||||||
/// Trait for generating responses.
|
/// Trait for generating responses.
|
||||||
///
|
///
|
||||||
/// Types that implement `IntoResponse` can be returned from handlers.
|
/// 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.
|
||||||
|
///
|
||||||
|
/// A manual implementation should only be necessary if you have a custom
|
||||||
|
/// response body type:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use axum::{prelude::*, response::IntoResponse};
|
||||||
|
/// use http_body::Body;
|
||||||
|
/// use http::{Response, 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 {
|
||||||
|
/// type Body = Self;
|
||||||
|
/// type BodyError = <Self as Body>::Error;
|
||||||
|
///
|
||||||
|
/// fn into_response(self) -> Response<Self::Body> {
|
||||||
|
/// Response::new(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 = route("/", get(|| async { MyBody }));
|
||||||
|
/// # async {
|
||||||
|
/// # hyper::Server::bind(&"".parse().unwrap()).serve(app.into_make_service()).await.unwrap();
|
||||||
|
/// # };
|
||||||
|
/// ```
|
||||||
pub trait IntoResponse {
|
pub trait IntoResponse {
|
||||||
|
/// The body type of the response.
|
||||||
|
///
|
||||||
|
/// Unless you're implementing this trait for a custom body type, these are
|
||||||
|
/// some common types you can use:
|
||||||
|
///
|
||||||
|
/// - [`hyper::Body`]: A good default that supports most use cases.
|
||||||
|
/// - [`http_body::Empty<Bytes>`]: When you know your response is always
|
||||||
|
/// empty.
|
||||||
|
/// - [`http_body::Full<Bytes>`]: When you know your response always
|
||||||
|
/// contains exactly one chunk.
|
||||||
|
/// - [`BoxBody`]: If you need to unify multiple body types into one, or
|
||||||
|
/// return a body type that cannot be named. Can be created with
|
||||||
|
/// [`box_body`].
|
||||||
|
///
|
||||||
|
/// [`http_body::Empty<Bytes>`]: http_body::Empty
|
||||||
|
/// [`http_body::Full<Bytes>`]: http_body::Full
|
||||||
|
type Body: http_body::Body<Data = Bytes, Error = Self::BodyError> + Send + Sync + 'static;
|
||||||
|
|
||||||
|
/// The error type `Self::Body` might generate.
|
||||||
|
///
|
||||||
|
/// Generally it should be possible to set this to:
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// type BodyError = <Self::Body as http_body::Body>::Error;
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This associated type exists mainly to make returning `impl IntoResponse`
|
||||||
|
/// possible and to simplify trait bounds internally in axum.
|
||||||
|
type BodyError: Into<BoxError>;
|
||||||
|
|
||||||
/// Create a response.
|
/// Create a response.
|
||||||
fn into_response(self) -> Response<Body>;
|
fn into_response(self) -> Response<Self::Body>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for () {
|
impl IntoResponse for () {
|
||||||
fn into_response(self) -> Response<Body> {
|
type Body = Empty<Bytes>;
|
||||||
Response::new(Body::empty())
|
type BodyError = Infallible;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<Self::Body> {
|
||||||
|
Response::new(Empty::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for Infallible {
|
impl IntoResponse for Infallible {
|
||||||
fn into_response(self) -> Response<Body> {
|
type Body = Empty<Bytes>;
|
||||||
|
type BodyError = Infallible;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<Self::Body> {
|
||||||
match self {}
|
match self {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,10 +138,13 @@ where
|
||||||
T: IntoResponse,
|
T: IntoResponse,
|
||||||
K: IntoResponse,
|
K: IntoResponse,
|
||||||
{
|
{
|
||||||
fn into_response(self) -> Response<Body> {
|
type Body = BoxBody;
|
||||||
|
type BodyError = BoxStdError;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<Self::Body> {
|
||||||
match self {
|
match self {
|
||||||
Either::A(inner) => inner.into_response(),
|
Either::A(inner) => inner.into_response().map(box_body),
|
||||||
Either::B(inner) => inner.into_response(),
|
Either::B(inner) => inner.into_response().map(box_body),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,43 +154,113 @@ where
|
||||||
T: IntoResponse,
|
T: IntoResponse,
|
||||||
E: IntoResponse,
|
E: IntoResponse,
|
||||||
{
|
{
|
||||||
fn into_response(self) -> Response<Body> {
|
type Body = BoxBody;
|
||||||
|
type BodyError = BoxStdError;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<Self::Body> {
|
||||||
match self {
|
match self {
|
||||||
Ok(value) => value.into_response(),
|
Ok(value) => value.into_response().map(box_body),
|
||||||
Err(err) => err.into_response(),
|
Err(err) => err.into_response().map(box_body),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for Response<Body> {
|
impl<B> IntoResponse for Response<B>
|
||||||
|
where
|
||||||
|
B: http_body::Body<Data = Bytes> + Send + Sync + 'static,
|
||||||
|
B::Error: Into<BoxError>,
|
||||||
|
{
|
||||||
|
type Body = B;
|
||||||
|
type BodyError = <B as http_body::Body>::Error;
|
||||||
|
|
||||||
fn into_response(self) -> Self {
|
fn into_response(self) -> Self {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for Body {
|
macro_rules! impl_into_response_for_body {
|
||||||
fn into_response(self) -> Response<Body> {
|
($body:ty) => {
|
||||||
|
impl IntoResponse for $body {
|
||||||
|
type Body = $body;
|
||||||
|
type BodyError = <$body as http_body::Body>::Error;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<Self> {
|
||||||
|
Response::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_into_response_for_body!(hyper::Body);
|
||||||
|
impl_into_response_for_body!(Full<Bytes>);
|
||||||
|
impl_into_response_for_body!(Empty<Bytes>);
|
||||||
|
|
||||||
|
impl<E> IntoResponse for http_body::combinators::BoxBody<Bytes, E>
|
||||||
|
where
|
||||||
|
E: Into<BoxError> + 'static,
|
||||||
|
{
|
||||||
|
type Body = Self;
|
||||||
|
type BodyError = E;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<Self> {
|
||||||
|
Response::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B, F> IntoResponse for MapData<B, F>
|
||||||
|
where
|
||||||
|
B: http_body::Body + Send + Sync + 'static,
|
||||||
|
F: FnMut(B::Data) -> Bytes + Send + Sync + 'static,
|
||||||
|
B::Error: Into<BoxError>,
|
||||||
|
{
|
||||||
|
type Body = Self;
|
||||||
|
type BodyError = <B as http_body::Body>::Error;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<Self::Body> {
|
||||||
|
Response::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B, F, E> IntoResponse for MapErr<B, F>
|
||||||
|
where
|
||||||
|
B: http_body::Body<Data = Bytes> + Send + Sync + 'static,
|
||||||
|
F: FnMut(B::Error) -> E + Send + Sync + 'static,
|
||||||
|
E: Into<BoxError>,
|
||||||
|
{
|
||||||
|
type Body = Self;
|
||||||
|
type BodyError = E;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<Self::Body> {
|
||||||
Response::new(self)
|
Response::new(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for &'static str {
|
impl IntoResponse for &'static str {
|
||||||
|
type Body = Full<Bytes>;
|
||||||
|
type BodyError = Infallible;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into_response(self) -> Response<Body> {
|
fn into_response(self) -> Response<Self::Body> {
|
||||||
Cow::Borrowed(self).into_response()
|
Cow::Borrowed(self).into_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for String {
|
impl IntoResponse for String {
|
||||||
|
type Body = Full<Bytes>;
|
||||||
|
type BodyError = Infallible;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into_response(self) -> Response<Body> {
|
fn into_response(self) -> Response<Self::Body> {
|
||||||
Cow::<'static, str>::Owned(self).into_response()
|
Cow::<'static, str>::Owned(self).into_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for std::borrow::Cow<'static, str> {
|
impl IntoResponse for std::borrow::Cow<'static, str> {
|
||||||
fn into_response(self) -> Response<Body> {
|
type Body = Full<Bytes>;
|
||||||
let mut res = Response::new(Body::from(self));
|
type BodyError = Infallible;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<Self::Body> {
|
||||||
|
let mut res = Response::new(Full::from(self));
|
||||||
res.headers_mut()
|
res.headers_mut()
|
||||||
.insert(header::CONTENT_TYPE, HeaderValue::from_static("text/plain"));
|
.insert(header::CONTENT_TYPE, HeaderValue::from_static("text/plain"));
|
||||||
res
|
res
|
||||||
|
@ -91,8 +268,11 @@ impl IntoResponse for std::borrow::Cow<'static, str> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for Bytes {
|
impl IntoResponse for Bytes {
|
||||||
fn into_response(self) -> Response<Body> {
|
type Body = Full<Bytes>;
|
||||||
let mut res = Response::new(Body::from(self));
|
type BodyError = Infallible;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<Self::Body> {
|
||||||
|
let mut res = Response::new(Full::from(self));
|
||||||
res.headers_mut().insert(
|
res.headers_mut().insert(
|
||||||
header::CONTENT_TYPE,
|
header::CONTENT_TYPE,
|
||||||
HeaderValue::from_static("application/octet-stream"),
|
HeaderValue::from_static("application/octet-stream"),
|
||||||
|
@ -102,8 +282,11 @@ impl IntoResponse for Bytes {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for &'static [u8] {
|
impl IntoResponse for &'static [u8] {
|
||||||
fn into_response(self) -> Response<Body> {
|
type Body = Full<Bytes>;
|
||||||
let mut res = Response::new(Body::from(self));
|
type BodyError = Infallible;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<Self::Body> {
|
||||||
|
let mut res = Response::new(Full::from(self));
|
||||||
res.headers_mut().insert(
|
res.headers_mut().insert(
|
||||||
header::CONTENT_TYPE,
|
header::CONTENT_TYPE,
|
||||||
HeaderValue::from_static("application/octet-stream"),
|
HeaderValue::from_static("application/octet-stream"),
|
||||||
|
@ -113,8 +296,11 @@ impl IntoResponse for &'static [u8] {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for Vec<u8> {
|
impl IntoResponse for Vec<u8> {
|
||||||
fn into_response(self) -> Response<Body> {
|
type Body = Full<Bytes>;
|
||||||
let mut res = Response::new(Body::from(self));
|
type BodyError = Infallible;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<Self::Body> {
|
||||||
|
let mut res = Response::new(Full::from(self));
|
||||||
res.headers_mut().insert(
|
res.headers_mut().insert(
|
||||||
header::CONTENT_TYPE,
|
header::CONTENT_TYPE,
|
||||||
HeaderValue::from_static("application/octet-stream"),
|
HeaderValue::from_static("application/octet-stream"),
|
||||||
|
@ -124,8 +310,11 @@ impl IntoResponse for Vec<u8> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for std::borrow::Cow<'static, [u8]> {
|
impl IntoResponse for std::borrow::Cow<'static, [u8]> {
|
||||||
fn into_response(self) -> Response<Body> {
|
type Body = Full<Bytes>;
|
||||||
let mut res = Response::new(Body::from(self));
|
type BodyError = Infallible;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<Self::Body> {
|
||||||
|
let mut res = Response::new(Full::from(self));
|
||||||
res.headers_mut().insert(
|
res.headers_mut().insert(
|
||||||
header::CONTENT_TYPE,
|
header::CONTENT_TYPE,
|
||||||
HeaderValue::from_static("application/octet-stream"),
|
HeaderValue::from_static("application/octet-stream"),
|
||||||
|
@ -135,11 +324,11 @@ impl IntoResponse for std::borrow::Cow<'static, [u8]> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for StatusCode {
|
impl IntoResponse for StatusCode {
|
||||||
fn into_response(self) -> Response<Body> {
|
type Body = Empty<Bytes>;
|
||||||
Response::builder()
|
type BodyError = Infallible;
|
||||||
.status(self)
|
|
||||||
.body(Body::empty())
|
fn into_response(self) -> Response<Self::Body> {
|
||||||
.unwrap()
|
Response::builder().status(self).body(Empty::new()).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +336,10 @@ impl<T> IntoResponse for (StatusCode, T)
|
||||||
where
|
where
|
||||||
T: IntoResponse,
|
T: IntoResponse,
|
||||||
{
|
{
|
||||||
fn into_response(self) -> Response<Body> {
|
type Body = T::Body;
|
||||||
|
type BodyError = T::BodyError;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<T::Body> {
|
||||||
let mut res = self.1.into_response();
|
let mut res = self.1.into_response();
|
||||||
*res.status_mut() = self.0;
|
*res.status_mut() = self.0;
|
||||||
res
|
res
|
||||||
|
@ -158,7 +350,10 @@ impl<T> IntoResponse for (HeaderMap, T)
|
||||||
where
|
where
|
||||||
T: IntoResponse,
|
T: IntoResponse,
|
||||||
{
|
{
|
||||||
fn into_response(self) -> Response<Body> {
|
type Body = T::Body;
|
||||||
|
type BodyError = T::BodyError;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<T::Body> {
|
||||||
let mut res = self.1.into_response();
|
let mut res = self.1.into_response();
|
||||||
res.headers_mut().extend(self.0);
|
res.headers_mut().extend(self.0);
|
||||||
res
|
res
|
||||||
|
@ -169,7 +364,10 @@ impl<T> IntoResponse for (StatusCode, HeaderMap, T)
|
||||||
where
|
where
|
||||||
T: IntoResponse,
|
T: IntoResponse,
|
||||||
{
|
{
|
||||||
fn into_response(self) -> Response<Body> {
|
type Body = T::Body;
|
||||||
|
type BodyError = T::BodyError;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<T::Body> {
|
||||||
let mut res = self.2.into_response();
|
let mut res = self.2.into_response();
|
||||||
*res.status_mut() = self.0;
|
*res.status_mut() = self.0;
|
||||||
res.headers_mut().extend(self.1);
|
res.headers_mut().extend(self.1);
|
||||||
|
@ -178,8 +376,11 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoResponse for HeaderMap {
|
impl IntoResponse for HeaderMap {
|
||||||
fn into_response(self) -> Response<Body> {
|
type Body = Empty<Bytes>;
|
||||||
let mut res = Response::new(Body::empty());
|
type BodyError = Infallible;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<Self::Body> {
|
||||||
|
let mut res = Response::new(Empty::new());
|
||||||
*res.headers_mut() = self;
|
*res.headers_mut() = self;
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
@ -193,9 +394,12 @@ pub struct Html<T>(pub T);
|
||||||
|
|
||||||
impl<T> IntoResponse for Html<T>
|
impl<T> IntoResponse for Html<T>
|
||||||
where
|
where
|
||||||
T: Into<Body>,
|
T: Into<Full<Bytes>>,
|
||||||
{
|
{
|
||||||
fn into_response(self) -> Response<Body> {
|
type Body = Full<Bytes>;
|
||||||
|
type BodyError = Infallible;
|
||||||
|
|
||||||
|
fn into_response(self) -> Response<Self::Body> {
|
||||||
let mut res = Response::new(self.0.into());
|
let mut res = Response::new(self.0.into());
|
||||||
res.headers_mut()
|
res.headers_mut()
|
||||||
.insert(header::CONTENT_TYPE, HeaderValue::from_static("text/html"));
|
.insert(header::CONTENT_TYPE, HeaderValue::from_static("text/html"));
|
||||||
|
@ -212,6 +416,7 @@ impl<T> From<T> for Html<T> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::body::Body;
|
||||||
use http::header::{HeaderMap, HeaderName};
|
use http::header::{HeaderMap, HeaderName};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -219,6 +424,9 @@ mod tests {
|
||||||
struct MyResponse;
|
struct MyResponse;
|
||||||
|
|
||||||
impl IntoResponse for MyResponse {
|
impl IntoResponse for MyResponse {
|
||||||
|
type Body = Body;
|
||||||
|
type BodyError = <Self::Body as http_body::Body>::Error;
|
||||||
|
|
||||||
fn into_response(self) -> Response<Body> {
|
fn into_response(self) -> Response<Body> {
|
||||||
let mut resp = Response::new(String::new().into());
|
let mut resp = Response::new(String::new().into());
|
||||||
resp.headers_mut()
|
resp.headers_mut()
|
||||||
|
|
Loading…
Reference in a new issue