Add RequireAuthorization middleware (#70)

This commit is contained in:
David Pedersen 2021-05-20 10:14:25 +02:00 committed by GitHub
parent 526368ca31
commit dee4286c2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 682 additions and 23 deletions

View File

@ -128,7 +128,7 @@ that the example is correct and provides additional test coverage.
The trick to documentation tests is striking a balance between being succinct
for a reader to understand and actually testing the API.
All middlewares should have at least one example that demonstrates how to use
All middleware should have at least one example that demonstrates how to use
them. The example should highlight the most common use case that we expect most
users to have. If the middleware can be configured there should be at least two
examples: One that uses the default configuration (as that is probably the most

View File

@ -5,7 +5,7 @@ initial churn is over, Tower HTTP still isn't released on crates.io. We are
actively working on that and you can follow the progress towards 0.1.0
[here][milestone].
Tower middlewares and utilities for HTTP clients and servers.
Tower middleware and utilities for HTTP clients and servers.
[![Build status](https://github.com/tower-rs/tower-http/workflows/CI/badge.svg)](https://github.com/tower-rs/tower-http/actions)
[![Crates.io](https://img.shields.io/crates/v/tower-http)](https://crates.io/crates/tower-http)
@ -14,9 +14,9 @@ Tower middlewares and utilities for HTTP clients and servers.
More information about this crate can be found in the [crate documentation][docs].
## Middlewares
## Middleware
Tower HTTP contains lots of middlewares that are generally useful when building
Tower HTTP contains lots of middleware that are generally useful when building
HTTP servers and clients. Some of the highlights are:
- `Trace` adds high level logging of requests and responses. Supports both
@ -24,12 +24,12 @@ HTTP servers and clients. Some of the highlights are:
- `Compression` and `Decompression` to compress/decompress response bodies.
- `FollowRedirect` to automatically follow redirection responses.
See the [docs] for the complete list of middlewares.
See the [docs] for the complete list of middleware.
Middlewares uses the [`http`] crate as the HTTP interface so they're compatible
Middleware uses the [`http`] crate as the HTTP interface so they're compatible
with any library or framework that also uses [`http`]. For example [hyper].
The middlewares were originally extracted from one of [@EmbarkStudios] internal
The middleware were originally extracted from one of [@EmbarkStudios] internal
projects.
## Examples

View File

@ -161,7 +161,7 @@ async fn serve_forever(listener: TcpListener) -> Result<(), Box<dyn std::error::
// Build our tonic `Service`
let service = key_value_store_server::KeyValueStoreServer::new(ServerImpl { db, tx });
// Apply middlewares to our service
// Apply middleware to our service
let service = ServiceBuilder::new()
// Set a timeout
.timeout(Duration::from_secs(10))
@ -182,7 +182,7 @@ async fn serve_forever(listener: TcpListener) -> Result<(), Box<dyn std::error::
tracing::info!("Listening on {}", addr);
// We cannot use `tonic::transport::Server` directly as it requires services to implement
// `tonic::transport::NamedService` which tower-http middlewares don't
// `tonic::transport::NamedService` which tower-http middleware don't
Server::from_tcp(listener)?
// Required for gRPC
.http2_only(true)
@ -250,7 +250,7 @@ impl key_value_store_server::KeyValueStore for ServerImpl {
}
}
// Build a client with a few middlewares applied and connect to the server
// Build a client with a few middleware applied and connect to the server
async fn make_client(
addr: SocketAddr,
) -> Result<
@ -273,10 +273,10 @@ async fn make_client(
.unwrap();
// We have to use a `tonic::transport::Channel` as it implementes `Service` so we can apply
// middlewares to it
// middleware to it
let channel = Channel::builder(uri).connect().await?;
// Apply middlewares to our client
// Apply middleware to our client
let channel = ServiceBuilder::new()
// Decompress response bodies
.layer(DecompressionLayer::new())

View File

@ -66,7 +66,7 @@ async fn serve_forever(listener: TcpListener) -> Result<(), hyper::Error> {
// Convert our `Filter` into a `Service`
let warp_service = warp::service(filter);
// Apply middlewares to our service.
// Apply middleware to our service.
let service = ServiceBuilder::new()
// Add high level tracing/logging to all requests
.layer(

View File

@ -1,6 +1,6 @@
[package]
name = "tower-http"
description = "Tower middlewares and utilities for HTTP clients and servers"
description = "Tower middleware and utilities for HTTP clients and servers"
version = "0.1.0"
authors = ["Tower Maintainers <team@tower-rs.com>"]
edition = "2018"
@ -24,6 +24,7 @@ tower-service = "0.3"
# optional dependencies
async-compression = { version = "0.3", optional = true, features = ["tokio"] }
base64 = { version = "0.13", optional = true }
hyper = { version = "0.14", optional = true, default_features = false, features = ["stream"] }
iri-string = { version = "0.3", optional = true }
mime = { version = "0.3", optional = true, default_features = false }
@ -47,6 +48,7 @@ tracing-subscriber = "0.2"
default = []
full = [
"add-extension",
"auth",
"compression",
"compression-full",
"decompression-full",
@ -62,6 +64,7 @@ full = [
]
add-extension = []
auth = ["base64"]
follow-redirect = ["iri-string", "tower/util"]
fs = ["tokio/fs", "tokio-util/io", "mime_guess", "mime"]
map-request-body = []

View File

@ -0,0 +1,8 @@
//! Authorization related middleware.
pub mod require_authorization;
#[doc(inline)]
pub use self::require_authorization::{
AuthorizeRequest, RequireAuthorization, RequireAuthorizationLayer,
};

View File

@ -0,0 +1,640 @@
//! Authorize requests using the [`Authorization`] header.
//!
//! [`Authorization`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization
//!
//! # Example
//!
//! ```
//! use tower_http::auth::RequireAuthorizationLayer;
//! use hyper::{Request, Response, Body, Error};
//! use http::{StatusCode, header::AUTHORIZATION};
//! use tower::{Service, ServiceExt, ServiceBuilder, service_fn};
//!
//! async fn handle(request: Request<Body>) -> Result<Response<Body>, Error> {
//! Ok(Response::new(Body::empty()))
//! }
//!
//! # #[tokio::main]
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let mut service = ServiceBuilder::new()
//! // Require the `Authorization` header to be `Bearer passwordlol`
//! .layer(RequireAuthorizationLayer::bearer("passwordlol"))
//! .service_fn(handle);
//!
//! // Requests with the correct token are allowed through
//! let request = Request::builder()
//! .header(AUTHORIZATION, "Bearer passwordlol")
//! .body(Body::empty())
//! .unwrap();
//!
//! let response = service
//! .ready_and()
//! .await?
//! .call(request)
//! .await?;
//!
//! assert_eq!(StatusCode::OK, response.status());
//!
//! // Requests with an invalid token get a `401 Unauthorized` response
//! let request = Request::builder()
//! .body(Body::empty())
//! .unwrap();
//!
//! let response = service
//! .ready_and()
//! .await?
//! .call(request)
//! .await?;
//!
//! assert_eq!(StatusCode::UNAUTHORIZED, response.status());
//! # Ok(())
//! # }
//! ```
//!
//! Custom authorization schemes can be made by implementing [`AuthorizeRequest`]:
//!
//! ```
//! use tower_http::auth::{RequireAuthorizationLayer, AuthorizeRequest};
//! use hyper::{Request, Response, Body, Error};
//! use http::{StatusCode, header::AUTHORIZATION};
//! use tower::{Service, ServiceExt, ServiceBuilder, service_fn};
//!
//! #[derive(Clone, Copy)]
//! struct MyAuth;
//!
//! impl AuthorizeRequest for MyAuth {
//! type Output = UserId;
//! type ResponseBody = Body;
//!
//! fn authorize<B>(&mut self, request: &Request<B>) -> Option<UserId> {
//! // ...
//! # None
//! }
//!
//! fn on_authorized<B>(&mut self, request: &mut Request<B>, user_id: UserId) {
//! // Set `user_id` as a request extension so it can be accessed by other
//! // services down the stack.
//! request.extensions_mut().insert(user_id);
//! }
//!
//! fn unauthorized_response<B>(&mut self, request: &Request<B>) -> Response<Body> {
//! Response::builder()
//! .status(StatusCode::UNAUTHORIZED)
//! .body(Body::empty())
//! .unwrap()
//! }
//! }
//!
//! #[derive(Debug)]
//! struct UserId(String);
//!
//! async fn handle(request: Request<Body>) -> Result<Response<Body>, Error> {
//! // Access the `UserId` that was set in `on_authorized`. If `handle` gets called the
//! // request was authorized and `UserId` will be present.
//! let user_id = request
//! .extensions()
//! .get::<UserId>()
//! .expect("UserId will be there if request was authorized");
//!
//! println!("request from {:?}", user_id);
//!
//! Ok(Response::new(Body::empty()))
//! }
//!
//! # #[tokio::main]
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let service = ServiceBuilder::new()
//! // Authorize requests using `MyAuth`
//! .layer(RequireAuthorizationLayer::custom(MyAuth))
//! .service_fn(handle);
//! # Ok(())
//! # }
//! ```
use http::{
header::{self, HeaderValue},
Request, Response, StatusCode,
};
use http_body::Body;
use pin_project::pin_project;
use std::{
fmt,
future::Future,
marker::PhantomData,
pin::Pin,
task::{Context, Poll},
};
use tower_layer::Layer;
use tower_service::Service;
/// Layer that applies [`RequireAuthorization`] which authorizes all requests using the
/// [`Authorization`] header.
///
/// See the [module docs](crate::auth::require_authorization) for an example.
///
/// [`Authorization`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization
#[derive(Debug, Clone)]
pub struct RequireAuthorizationLayer<T> {
auth: T,
}
impl<ResBody> RequireAuthorizationLayer<Bearer<ResBody>> {
/// Authorize requests using a "bearer token". Commonly used for OAuth 2.
///
/// The `Authorization` header is required to be `Bearer {token}`.
///
/// # Panics
///
/// Panics if the token is not a valid [`HeaderValue`](http::header::HeaderValue).
pub fn bearer(token: &str) -> Self
where
ResBody: Body + Default,
{
Self::custom(Bearer::new(token))
}
}
impl<ResBody> RequireAuthorizationLayer<Basic<ResBody>> {
/// Authorize requests using a username and password pair.
///
/// The `Authorization` header is required to be `Basic {credentials}` where `credentials` is
/// `base64_encode("{username}:{password}")`.
///
/// Since the username and password is sent in clear text it is recommended to use HTTPS/TLS
/// with this method. However use of HTTPS/TLS is not enforced by this middleware.
pub fn basic(username: &str, password: &str) -> Self
where
ResBody: Body + Default,
{
Self::custom(Basic::new(username, password))
}
}
impl<T> RequireAuthorizationLayer<T>
where
T: AuthorizeRequest,
{
/// Authorize requests using a custom scheme.
pub fn custom(auth: T) -> RequireAuthorizationLayer<T> {
Self { auth }
}
}
impl<S, T> Layer<S> for RequireAuthorizationLayer<T>
where
T: Clone,
{
type Service = RequireAuthorization<S, T>;
fn layer(&self, inner: S) -> Self::Service {
RequireAuthorization::new(inner, self.auth.clone())
}
}
/// Middleware that authorizes all requests using the [`Authorization`] header.
///
/// See the [module docs](crate::auth::require_authorization) for an example.
///
/// [`Authorization`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization
#[derive(Clone, Debug)]
pub struct RequireAuthorization<S, T> {
inner: S,
auth: T,
}
impl<S, T> RequireAuthorization<S, T> {
fn new(inner: S, auth: T) -> Self {
Self { inner, auth }
}
define_inner_service_accessors!();
}
impl<S, ResBody> RequireAuthorization<S, Bearer<ResBody>> {
/// Authorize requests using a "bearer token". Commonly used for OAuth 2.
///
/// The `Authorization` header is required to be `Bearer {token}`.
///
/// # Panics
///
/// Panics if the token is not a valid [`HeaderValue`](http::header::HeaderValue).
pub fn bearer(inner: S, token: &str) -> Self
where
ResBody: Body + Default,
{
Self::custom(inner, Bearer::new(token))
}
}
impl<S, ResBody> RequireAuthorization<S, Basic<ResBody>> {
/// Authorize requests using a username and password pair.
///
/// The `Authorization` header is required to be `Basic {credentials}` where `credentials` is
/// `base64_encode("{username}:{password}")`.
///
/// Since the username and password is sent in clear text it is recommended to use HTTPS/TLS
/// with this method. However use of HTTPS/TLS is not enforced by this middleware.
pub fn basic(inner: S, username: &str, password: &str) -> Self
where
ResBody: Body + Default,
{
Self::custom(inner, Basic::new(username, password))
}
}
impl<S, T> RequireAuthorization<S, T>
where
T: AuthorizeRequest,
{
/// Authorize requests using a custom scheme.
///
/// The `Authorization` header is required to have the value provided.
pub fn custom(inner: S, auth: T) -> RequireAuthorization<S, T> {
Self { inner, auth }
}
}
impl<ReqBody, ResBody, S, T> Service<Request<ReqBody>> for RequireAuthorization<S, T>
where
S: Service<Request<ReqBody>, Response = Response<ResBody>>,
ResBody: Default,
T: AuthorizeRequest<ResponseBody = ResBody>,
{
type Response = Response<ResBody>;
type Error = S::Error;
type Future = ResponseFuture<S::Future, ResBody>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, mut req: Request<ReqBody>) -> Self::Future {
if let Some(output) = self.auth.authorize(&req) {
self.auth.on_authorized(&mut req, output);
ResponseFuture::future(self.inner.call(req))
} else {
let res = self.auth.unauthorized_response(&req);
ResponseFuture::invalid_auth(res)
}
}
}
/// Response future for [`RequireAuthorization`].
#[pin_project]
pub struct ResponseFuture<F, B> {
#[pin]
kind: Kind<F, B>,
}
impl<F, B> ResponseFuture<F, B> {
fn future(future: F) -> Self {
Self {
kind: Kind::Future(future),
}
}
fn invalid_auth(res: Response<B>) -> Self {
Self {
kind: Kind::Error(Some(res)),
}
}
}
#[pin_project(project = KindProj)]
enum Kind<F, B> {
Future(#[pin] F),
Error(Option<Response<B>>),
}
impl<F, B, E> Future for ResponseFuture<F, B>
where
F: Future<Output = Result<Response<B>, E>>,
{
type Output = F::Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.project().kind.project() {
KindProj::Future(future) => future.poll(cx),
KindProj::Error(response) => {
let response = response.take().unwrap();
Poll::Ready(Ok(response))
}
}
}
}
/// Trait for authorizing requests.
pub trait AuthorizeRequest {
/// The output type of doing the authorization.
///
/// Use `()` if authorization doesn't produce any meaningful output.
type Output;
/// The body type used for responses to unauthorized requests.
type ResponseBody: Body;
/// Authorize the request.
///
/// If `Some(_)` is returned then the request is allowed through, otherwise not.
fn authorize<B>(&mut self, request: &Request<B>) -> Option<Self::Output>;
/// Callback for when a request has been successfully authorized.
///
/// For example this allows you to save `Self::Output` in a [request extension][] to make it
/// available to services further down the stack. This could for example be the "claims" for a
/// valid [JWT].
///
/// Defaults to doing nothing.
///
/// See the [module docs](crate::auth::require_authorization) for an example.
///
/// [request extension]: https://docs.rs/http/latest/http/struct.Extensions.html
/// [JWT]: https://jwt.io
#[inline]
fn on_authorized<B>(&mut self, _request: &mut Request<B>, _output: Self::Output) {}
/// Create the response for an unauthorized request.
fn unauthorized_response<B>(&mut self, request: &Request<B>) -> Response<Self::ResponseBody>;
}
/// Type that performs "bearer token" authorization.
///
/// See [`RequireAuthorization::bearer`] for more details.
pub struct Bearer<ResBody> {
header_value: HeaderValue,
_ty: PhantomData<fn() -> ResBody>,
}
impl<ResBody> Bearer<ResBody> {
fn new(token: &str) -> Self
where
ResBody: Body + Default,
{
Self {
header_value: format!("Bearer {}", token)
.parse()
.expect("token is not a valid header value"),
_ty: PhantomData,
}
}
}
impl<ResBody> Clone for Bearer<ResBody> {
fn clone(&self) -> Self {
Self {
header_value: self.header_value.clone(),
_ty: PhantomData,
}
}
}
impl<ResBody> fmt::Debug for Bearer<ResBody> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Bearer")
.field("header_value", &self.header_value)
.finish()
}
}
impl<ResBody> AuthorizeRequest for Bearer<ResBody>
where
ResBody: Body + Default,
{
type Output = ();
type ResponseBody = ResBody;
fn authorize<B>(&mut self, request: &Request<B>) -> Option<Self::Output> {
if let Some(actual) = request.headers().get(header::AUTHORIZATION) {
(actual == self.header_value).then(|| ())
} else {
None
}
}
fn unauthorized_response<B>(&mut self, _request: &Request<B>) -> Response<Self::ResponseBody> {
let body = ResBody::default();
let mut res = Response::new(body);
*res.status_mut() = StatusCode::UNAUTHORIZED;
res
}
}
/// Type that performs basic authorization.
///
/// See [`RequireAuthorization::basic`] for more details.
pub struct Basic<ResBody> {
header_value: HeaderValue,
_ty: PhantomData<fn() -> ResBody>,
}
impl<ResBody> Basic<ResBody> {
fn new(username: &str, password: &str) -> Self
where
ResBody: Body + Default,
{
let encoded = base64::encode(format!("{}:{}", username, password));
let header_value = format!("Basic {}", encoded).parse().unwrap();
Self {
header_value,
_ty: PhantomData,
}
}
}
impl<ResBody> Clone for Basic<ResBody> {
fn clone(&self) -> Self {
Self {
header_value: self.header_value.clone(),
_ty: PhantomData,
}
}
}
impl<ResBody> fmt::Debug for Basic<ResBody> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Basic")
.field("header_value", &self.header_value)
.finish()
}
}
impl<ResBody> AuthorizeRequest for Basic<ResBody>
where
ResBody: Body + Default,
{
type Output = ();
type ResponseBody = ResBody;
fn authorize<B>(&mut self, request: &Request<B>) -> Option<Self::Output> {
if let Some(actual) = request.headers().get(header::AUTHORIZATION) {
(actual == self.header_value).then(|| ())
} else {
None
}
}
fn unauthorized_response<B>(&mut self, _request: &Request<B>) -> Response<Self::ResponseBody> {
let body = ResBody::default();
let mut res = Response::new(body);
*res.status_mut() = StatusCode::UNAUTHORIZED;
res.headers_mut()
.insert(header::WWW_AUTHENTICATE, "Basic".parse().unwrap());
res
}
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
use http::header;
use hyper::Body;
use tower::{BoxError, ServiceBuilder, ServiceExt};
#[tokio::test]
async fn valid_basic_token() {
let mut service = ServiceBuilder::new()
.layer(RequireAuthorizationLayer::basic("foo", "bar"))
.service_fn(echo);
let request = Request::get("/")
.header(
header::AUTHORIZATION,
format!("Basic {}", base64::encode("foo:bar")),
)
.body(Body::empty())
.unwrap();
let res = service.ready().await.unwrap().call(request).await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
async fn invalid_basic_token() {
let mut service = ServiceBuilder::new()
.layer(RequireAuthorizationLayer::basic("foo", "bar"))
.service_fn(echo);
let request = Request::get("/")
.header(
header::AUTHORIZATION,
format!("Basic {}", base64::encode("wrong:credentials")),
)
.body(Body::empty())
.unwrap();
let res = service.ready().await.unwrap().call(request).await.unwrap();
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
let www_authenticate = res.headers().get(header::WWW_AUTHENTICATE).unwrap();
assert_eq!(www_authenticate, "Basic");
}
#[tokio::test]
async fn valid_bearer_token() {
let mut service = ServiceBuilder::new()
.layer(RequireAuthorizationLayer::bearer("foobar"))
.service_fn(echo);
let request = Request::get("/")
.header(header::AUTHORIZATION, "Bearer foobar")
.body(Body::empty())
.unwrap();
let res = service.ready().await.unwrap().call(request).await.unwrap();
assert_eq!(res.status(), StatusCode::OK);
}
#[tokio::test]
async fn basic_auth_is_case_sensitive_in_prefix() {
let mut service = ServiceBuilder::new()
.layer(RequireAuthorizationLayer::basic("foo", "bar"))
.service_fn(echo);
let request = Request::get("/")
.header(
header::AUTHORIZATION,
format!("basic {}", base64::encode("foo:bar")),
)
.body(Body::empty())
.unwrap();
let res = service.ready().await.unwrap().call(request).await.unwrap();
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn basic_auth_is_case_sensitive_in_value() {
let mut service = ServiceBuilder::new()
.layer(RequireAuthorizationLayer::basic("foo", "bar"))
.service_fn(echo);
let request = Request::get("/")
.header(
header::AUTHORIZATION,
format!("Basic {}", base64::encode("Foo:bar")),
)
.body(Body::empty())
.unwrap();
let res = service.ready().await.unwrap().call(request).await.unwrap();
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn invalid_bearer_token() {
let mut service = ServiceBuilder::new()
.layer(RequireAuthorizationLayer::bearer("foobar"))
.service_fn(echo);
let request = Request::get("/")
.header(header::AUTHORIZATION, "Bearer wat")
.body(Body::empty())
.unwrap();
let res = service.ready().await.unwrap().call(request).await.unwrap();
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn bearer_token_is_case_sensitive_in_prefix() {
let mut service = ServiceBuilder::new()
.layer(RequireAuthorizationLayer::bearer("foobar"))
.service_fn(echo);
let request = Request::get("/")
.header(header::AUTHORIZATION, "bearer foobar")
.body(Body::empty())
.unwrap();
let res = service.ready().await.unwrap().call(request).await.unwrap();
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
}
#[tokio::test]
async fn bearer_token_is_case_sensitive_in_token() {
let mut service = ServiceBuilder::new()
.layer(RequireAuthorizationLayer::bearer("foobar"))
.service_fn(echo);
let request = Request::get("/")
.header(header::AUTHORIZATION, "Bearer Foobar")
.body(Body::empty())
.unwrap();
let res = service.ready().await.unwrap().call(request).await.unwrap();
assert_eq!(res.status(), StatusCode::UNAUTHORIZED);
}
async fn echo(req: Request<Body>) -> Result<Response<Body>, BoxError> {
Ok(Response::new(req.into_body()))
}
}

View File

@ -1,4 +1,4 @@
//! Types used by compression and decompression middlewares.
//! Types used by compression and decompression middleware.
use bytes::{Bytes, BytesMut};
use futures_core::Stream;

View File

@ -7,7 +7,7 @@
//!
//! The middleware tries to clone the original [`Request`] when making a redirected request.
//! However, since [`Extensions`][http::Extensions] are `!Clone`, any extensions set by outer
//! middlewares will be discarded. Also, the request body cannot always be cloned. When the
//! middleware will be discarded. Also, the request body cannot always be cloned. When the
//! original body is known to be empty by [`Body::size_hint`], the middleware uses `Default`
//! implementation of the body type to create a new request body. If you know that the body can be
//! cloned in some way, you can tell the middleware to clone it by configuring a [`policy`].

View File

@ -2,16 +2,16 @@
//!
//! # Overview
//!
//! `tower-http` is a library that provides HTTP-specific middlewares and utilities built on top of
//! `tower-http` is a library that provides HTTP-specific middleware and utilities built on top of
//! [`tower`].
//!
//! All middlewares uses the [`http`] and [`http-body`] crates as the HTTP abstractions. That means
//! All middleware uses the [`http`] and [`http-body`] crates as the HTTP abstractions. That means
//! they're compatible with any library or framework that also uses those crates, such as
//! [`hyper`].
//!
//! # Example server
//!
//! This example shows how to apply middlewares from `tower-http` to a [`Service`] and then run
//! This example shows how to apply middleware from `tower-http` to a [`Service`] and then run
//! that service using [`hyper`].
//!
//! ```rust,no_run
@ -19,6 +19,7 @@
//! add_extension::AddExtensionLayer,
//! compression::CompressionLayer,
//! propagate_header::PropagateHeaderLayer,
//! auth::RequireAuthorizationLayer,
//! sensitive_header::SetSensitiveRequestHeaderLayer,
//! set_header::SetResponseHeaderLayer,
//! trace::TraceLayer,
@ -67,6 +68,8 @@
//! .layer(PropagateHeaderLayer::new(HeaderName::from_static("x-request-id")))
//! // If the response has a known size set the `Content-Length` header
//! .layer(SetResponseHeaderLayer::overriding(CONTENT_TYPE, content_length_from_response))
//! // Authorize requests using a token
//! .layer(RequireAuthorizationLayer::bearer("passwordlol"))
//! // Wrap a `Service` in our middleware stack
//! .service_fn(handler);
//!
@ -84,7 +87,7 @@
//!
//! # Example client
//!
//! `tower-http` middlewares can also be applied to HTTP clients:
//! `tower-http` middleware can also be applied to HTTP clients:
//!
//! ```rust,no_run
//! use tower_http::{
@ -157,6 +160,7 @@
//! [`Service`]: https://docs.rs/tower/latest/tower/trait.Service.html
//! [chat]: https://discord.gg/tokio
//! [issue]: https://github.com/tower-rs/tower-http/issues/new
//! [`Trace`]: crate::trace::Trace
#![doc(html_root_url = "https://docs.rs/tower-http/0.1.0")]
#![warn(
@ -207,6 +211,10 @@
#[macro_use]
pub(crate) mod macros;
#[cfg(feature = "auth")]
#[cfg_attr(docsrs, doc(cfg(feature = "auth")))]
pub mod auth;
#[cfg(feature = "set-header")]
#[cfg_attr(docsrs, doc(cfg(feature = "set-header")))]
pub mod set_header;
@ -299,7 +307,7 @@ where
}
}
/// The latency unit used to report latencies by middlewares.
/// The latency unit used to report latencies by middleware.
#[non_exhaustive]
#[derive(Copy, Clone, Debug)]
pub enum LatencyUnit {

View File

@ -1,4 +1,4 @@
//! Middlewares that mark headers as [sensitive].
//! Middleware that mark headers as [sensitive].
//!
//! [sensitive]: https://docs.rs/http/latest/http/header/struct.HeaderValue.html#method.set_sensitive
//!

View File

@ -1,4 +1,4 @@
//! Middlewares for setting headers on requests and responses.
//! Middleware for setting headers on requests and responses.
//!
//! See [request] and [response] for more details.