mirror of
https://github.com/Noratrieb/101844-repro.git
synced 2026-01-14 14:25:02 +01:00
tower lol
This commit is contained in:
parent
1e415960ec
commit
40a6bd3bce
49 changed files with 4626 additions and 1 deletions
1
tower
1
tower
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 4dc34c8d57b1e448c29cccb9f91468f6105ae461
|
|
||||||
2
tower/.gitignore
vendored
Normal file
2
tower/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
target
|
||||||
|
Cargo.lock
|
||||||
8
tower/Cargo.toml
Normal file
8
tower/Cargo.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[workspace]
|
||||||
|
|
||||||
|
members = [
|
||||||
|
"tower",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tower-test",
|
||||||
|
]
|
||||||
25
tower/LICENSE
Normal file
25
tower/LICENSE
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
Copyright (c) 2019 Tower Contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
53
tower/README.md
Normal file
53
tower/README.md
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
# Tower
|
||||||
|
|
||||||
|
Tower is a library of modular and reusable components for building robust
|
||||||
|
networking clients and servers.
|
||||||
|
|
||||||
|
[![Crates.io][crates-badge]][crates-url]
|
||||||
|
[![Documentation][docs-badge]][docs-url]
|
||||||
|
[![Documentation (master)][docs-master-badge]][docs-master-url]
|
||||||
|
[![MIT licensed][mit-badge]][mit-url]
|
||||||
|
[![Build Status][actions-badge]][actions-url]
|
||||||
|
[![Discord chat][discord-badge]][discord-url]
|
||||||
|
|
||||||
|
[crates-badge]: https://img.shields.io/crates/v/tower.svg
|
||||||
|
[crates-url]: https://crates.io/crates/tower
|
||||||
|
[docs-badge]: https://docs.rs/tower/badge.svg
|
||||||
|
[docs-url]: https://docs.rs/tower
|
||||||
|
[docs-master-badge]: https://img.shields.io/badge/docs-master-blue
|
||||||
|
[docs-master-url]: https://tower-rs.github.io/tower/tower
|
||||||
|
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||||
|
[mit-url]: LICENSE
|
||||||
|
[actions-badge]: https://github.com/tower-rs/tower/workflows/CI/badge.svg
|
||||||
|
[actions-url]:https://github.com/tower-rs/tower/actions?query=workflow%3ACI
|
||||||
|
[discord-badge]: https://img.shields.io/discord/500028886025895936?logo=discord&label=discord&logoColor=white
|
||||||
|
[discord-url]: https://discord.gg/EeF3cQw
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Tower aims to make it as easy as possible to build robust networking clients and
|
||||||
|
servers. It is protocol agnostic, but is designed around a request / response
|
||||||
|
pattern. If your protocol is entirely stream based, Tower may not be a good fit.
|
||||||
|
|
||||||
|
## Supported Rust Versions
|
||||||
|
|
||||||
|
Tower will keep a rolling MSRV (minimum supported Rust version) policy of **at
|
||||||
|
least** 6 months. When increasing the MSRV, the new Rust version must have been
|
||||||
|
released at least six months ago. The current MSRV is 1.49.0.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
If you're brand new to Tower and want to start with the basics we recommend you
|
||||||
|
check out some of our [guides].
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the [MIT license](LICENSE).
|
||||||
|
|
||||||
|
### Contribution
|
||||||
|
|
||||||
|
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||||
|
for inclusion in Tower by you, shall be licensed as MIT, without any additional
|
||||||
|
terms or conditions.
|
||||||
|
|
||||||
|
[guides]: https://github.com/tower-rs/tower/tree/master/guides
|
||||||
23
tower/deny.toml
Normal file
23
tower/deny.toml
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
[advisories]
|
||||||
|
vulnerability = "deny"
|
||||||
|
unmaintained = "warn"
|
||||||
|
notice = "warn"
|
||||||
|
ignore = ["RUSTSEC-2020-0159"]
|
||||||
|
|
||||||
|
[licenses]
|
||||||
|
unlicensed = "deny"
|
||||||
|
allow = []
|
||||||
|
deny = []
|
||||||
|
copyleft = "warn"
|
||||||
|
allow-osi-fsf-free = "either"
|
||||||
|
confidence-threshold = 0.8
|
||||||
|
|
||||||
|
[bans]
|
||||||
|
multiple-versions = "deny"
|
||||||
|
highlight = "all"
|
||||||
|
skip = []
|
||||||
|
|
||||||
|
[sources]
|
||||||
|
unknown-registry = "warn"
|
||||||
|
unknown-git = "warn"
|
||||||
|
allow-git = []
|
||||||
8
tower/netlify.toml
Normal file
8
tower/netlify.toml
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
[build]
|
||||||
|
command = "rustup install nightly --profile minimal && cargo doc --features=full --no-deps && cp -r target/doc _netlify_out"
|
||||||
|
environment = { RUSTDOCFLAGS= "--cfg docsrs" }
|
||||||
|
publish = "_netlify_out"
|
||||||
|
|
||||||
|
[[redirects]]
|
||||||
|
from = "/"
|
||||||
|
to = "/tower"
|
||||||
30
tower/tower-layer/CHANGELOG.md
Normal file
30
tower/tower-layer/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# 0.3.1 (January 7, 2021)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added `layer_fn`, for constructing a `Layer` from a function taking
|
||||||
|
a `Service` and returning a different `Service` ([#491])
|
||||||
|
- Added an implementation of `Layer` for `&Layer` ([#446])
|
||||||
|
- Multiple documentation improvements ([#487], [#490])
|
||||||
|
|
||||||
|
[#491]: https://github.com/tower-rs/tower/pull/491
|
||||||
|
[#446]: https://github.com/tower-rs/tower/pull/446
|
||||||
|
[#487]: https://github.com/tower-rs/tower/pull/487
|
||||||
|
[#490]: https://github.com/tower-rs/tower/pull/490
|
||||||
|
|
||||||
|
# 0.3.0 (November 29, 2019)
|
||||||
|
|
||||||
|
- Move layer builder from `tower-util` to tower-layer.
|
||||||
|
|
||||||
|
# 0.3.0-alpha.2 (September 30, 2019)
|
||||||
|
|
||||||
|
- Move to `futures-*-preview 0.3.0-alpha.19`
|
||||||
|
- Move to `pin-project 0.4`
|
||||||
|
|
||||||
|
# 0.3.0-alpha.1 (September 11, 2019)
|
||||||
|
|
||||||
|
- Move to `std::future`
|
||||||
|
|
||||||
|
# 0.1.0 (April 26, 2019)
|
||||||
|
|
||||||
|
- Initial release
|
||||||
26
tower/tower-layer/Cargo.toml
Normal file
26
tower/tower-layer/Cargo.toml
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
[package]
|
||||||
|
name = "tower-layer"
|
||||||
|
# When releasing to crates.io:
|
||||||
|
# - Update doc url
|
||||||
|
# - Cargo.toml
|
||||||
|
# - README.md
|
||||||
|
# - Update CHANGELOG.md.
|
||||||
|
# - Create "v0.1.x" git tag.
|
||||||
|
version = "0.3.1"
|
||||||
|
authors = ["Tower Maintainers <team@tower-rs.com>"]
|
||||||
|
license = "MIT"
|
||||||
|
readme = "README.md"
|
||||||
|
repository = "https://github.com/tower-rs/tower"
|
||||||
|
homepage = "https://github.com/tower-rs/tower"
|
||||||
|
documentation = "https://docs.rs/tower-layer/0.3.0-alpha.2"
|
||||||
|
description = """
|
||||||
|
Decorates a `Service` to allow easy composition between `Service`s.
|
||||||
|
"""
|
||||||
|
categories = ["asynchronous", "network-programming"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tower-service = { version = "0.3.0", path = "../tower-service" }
|
||||||
|
tower = { version = "0.4", path = "../tower" }
|
||||||
25
tower/tower-layer/LICENSE
Normal file
25
tower/tower-layer/LICENSE
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
Copyright (c) 2019 Tower Contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
43
tower/tower-layer/README.md
Normal file
43
tower/tower-layer/README.md
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
# Tower Layer
|
||||||
|
|
||||||
|
Decorates a [Tower] `Service`, transforming either the request or the response.
|
||||||
|
|
||||||
|
[![Crates.io][crates-badge]][crates-url]
|
||||||
|
[![Documentation][docs-badge]][docs-url]
|
||||||
|
[![Documentation (master)][docs-master-badge]][docs-master-url]
|
||||||
|
[![MIT licensed][mit-badge]][mit-url]
|
||||||
|
[![Build Status][actions-badge]][actions-url]
|
||||||
|
[![Discord chat][discord-badge]][discord-url]
|
||||||
|
|
||||||
|
[crates-badge]: https://img.shields.io/crates/v/tower-layer.svg
|
||||||
|
[crates-url]: https://crates.io/crates/tower-layer
|
||||||
|
[docs-badge]: https://docs.rs/tower-layer/badge.svg
|
||||||
|
[docs-url]: https://docs.rs/tower-layer
|
||||||
|
[docs-master-badge]: https://img.shields.io/badge/docs-master-blue
|
||||||
|
[docs-master-url]: https://tower-rs.github.io/tower/tower_layer
|
||||||
|
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||||
|
[mit-url]: LICENSE
|
||||||
|
[actions-badge]: https://github.com/tower-rs/tower/workflows/CI/badge.svg
|
||||||
|
[actions-url]:https://github.com/tower-rs/tower/actions?query=workflow%3ACI
|
||||||
|
[discord-badge]: https://img.shields.io/discord/500028886025895936?logo=discord&label=discord&logoColor=white
|
||||||
|
[discord-url]: https://discord.gg/EeF3cQw
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Often, many of the pieces needed for writing network applications can be
|
||||||
|
reused across multiple services. The `Layer` trait can be used to write
|
||||||
|
reusable components that can be applied to very different kinds of services;
|
||||||
|
for example, it can be applied to services operating on different protocols,
|
||||||
|
and to both the client and server side of a network transaction.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the [MIT license](LICENSE).
|
||||||
|
|
||||||
|
### Contribution
|
||||||
|
|
||||||
|
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||||
|
for inclusion in Tower by you, shall be licensed as MIT, without any additional
|
||||||
|
terms or conditions.
|
||||||
|
|
||||||
|
[Tower]: https://crates.io/crates/tower
|
||||||
37
tower/tower-layer/src/identity.rs
Normal file
37
tower/tower-layer/src/identity.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
use super::Layer;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// A no-op middleware.
|
||||||
|
///
|
||||||
|
/// When wrapping a [`Service`], the [`Identity`] layer returns the provided
|
||||||
|
/// service without modifying it.
|
||||||
|
///
|
||||||
|
/// [`Service`]: https://docs.rs/tower-service/latest/tower_service/trait.Service.html
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct Identity {
|
||||||
|
_p: (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Identity {
|
||||||
|
/// Create a new [`Identity`] value
|
||||||
|
pub fn new() -> Identity {
|
||||||
|
Identity { _p: () }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decorates a [`Service`], transforming either the request or the response.
|
||||||
|
///
|
||||||
|
/// [`Service`]: https://docs.rs/tower-service/latest/tower_service/trait.Service.html
|
||||||
|
impl<S> Layer<S> for Identity {
|
||||||
|
type Service = S;
|
||||||
|
|
||||||
|
fn layer(&self, inner: S) -> Self::Service {
|
||||||
|
inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Identity {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Identity").finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
114
tower/tower-layer/src/layer_fn.rs
Normal file
114
tower/tower-layer/src/layer_fn.rs
Normal file
|
|
@ -0,0 +1,114 @@
|
||||||
|
use super::Layer;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// Returns a new [`LayerFn`] that implements [`Layer`] by calling the
|
||||||
|
/// given function.
|
||||||
|
///
|
||||||
|
/// The [`Layer::layer`] method takes a type implementing [`Service`] and
|
||||||
|
/// returns a different type implementing [`Service`]. In many cases, this can
|
||||||
|
/// be implemented by a function or a closure. The [`LayerFn`] helper allows
|
||||||
|
/// writing simple [`Layer`] implementations without needing the boilerplate of
|
||||||
|
/// a new struct implementing [`Layer`].
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```rust
|
||||||
|
/// # use tower::Service;
|
||||||
|
/// # use std::task::{Poll, Context};
|
||||||
|
/// # use tower_layer::{Layer, layer_fn};
|
||||||
|
/// # use std::fmt;
|
||||||
|
/// # use std::convert::Infallible;
|
||||||
|
/// #
|
||||||
|
/// // A middleware that logs requests before forwarding them to another service
|
||||||
|
/// pub struct LogService<S> {
|
||||||
|
/// target: &'static str,
|
||||||
|
/// service: S,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl<S, Request> Service<Request> for LogService<S>
|
||||||
|
/// where
|
||||||
|
/// S: Service<Request>,
|
||||||
|
/// Request: fmt::Debug,
|
||||||
|
/// {
|
||||||
|
/// type Response = S::Response;
|
||||||
|
/// type Error = S::Error;
|
||||||
|
/// type Future = S::Future;
|
||||||
|
///
|
||||||
|
/// fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
/// self.service.poll_ready(cx)
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn call(&mut self, request: Request) -> Self::Future {
|
||||||
|
/// // Log the request
|
||||||
|
/// println!("request = {:?}, target = {:?}", request, self.target);
|
||||||
|
///
|
||||||
|
/// self.service.call(request)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // A `Layer` that wraps services in `LogService`
|
||||||
|
/// let log_layer = layer_fn(|service| {
|
||||||
|
/// LogService {
|
||||||
|
/// service,
|
||||||
|
/// target: "tower-docs",
|
||||||
|
/// }
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// // An example service. This one uppercases strings
|
||||||
|
/// let uppercase_service = tower::service_fn(|request: String| async move {
|
||||||
|
/// Ok::<_, Infallible>(request.to_uppercase())
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// // Wrap our service in a `LogService` so requests are logged.
|
||||||
|
/// let wrapped_service = log_layer.layer(uppercase_service);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`Service`]: https://docs.rs/tower-service/latest/tower_service/trait.Service.html
|
||||||
|
/// [`Layer::layer`]: crate::Layer::layer
|
||||||
|
pub fn layer_fn<T>(f: T) -> LayerFn<T> {
|
||||||
|
LayerFn { f }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A `Layer` implemented by a closure. See the docs for [`layer_fn`] for more details.
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct LayerFn<F> {
|
||||||
|
f: F,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, S, Out> Layer<S> for LayerFn<F>
|
||||||
|
where
|
||||||
|
F: Fn(S) -> Out,
|
||||||
|
{
|
||||||
|
type Service = Out;
|
||||||
|
|
||||||
|
fn layer(&self, inner: S) -> Self::Service {
|
||||||
|
(self.f)(inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F> fmt::Debug for LayerFn<F> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("LayerFn")
|
||||||
|
.field("f", &format_args!("{}", std::any::type_name::<F>()))
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[test]
|
||||||
|
fn layer_fn_has_useful_debug_impl() {
|
||||||
|
struct WrappedService<S> {
|
||||||
|
inner: S,
|
||||||
|
}
|
||||||
|
let layer = layer_fn(|svc| WrappedService { inner: svc });
|
||||||
|
let _svc = layer.layer("foo");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
"LayerFn { f: tower_layer::layer_fn::tests::layer_fn_has_useful_debug_impl::{{closure}} }".to_string(),
|
||||||
|
format!("{:?}", layer),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
111
tower/tower-layer/src/lib.rs
Normal file
111
tower/tower-layer/src/lib.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
#![warn(
|
||||||
|
missing_debug_implementations,
|
||||||
|
missing_docs,
|
||||||
|
rust_2018_idioms,
|
||||||
|
unreachable_pub
|
||||||
|
)]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
// `rustdoc::broken_intra_doc_links` is checked on CI
|
||||||
|
|
||||||
|
//! Layer traits and extensions.
|
||||||
|
//!
|
||||||
|
//! A layer decorates an service and provides additional functionality. It
|
||||||
|
//! allows other services to be composed with the service that implements layer.
|
||||||
|
//!
|
||||||
|
//! A middleware implements the [`Layer`] and [`Service`] trait.
|
||||||
|
//!
|
||||||
|
//! [`Service`]: https://docs.rs/tower/latest/tower/trait.Service.html
|
||||||
|
|
||||||
|
mod identity;
|
||||||
|
mod layer_fn;
|
||||||
|
mod stack;
|
||||||
|
|
||||||
|
pub use self::{
|
||||||
|
identity::Identity,
|
||||||
|
layer_fn::{layer_fn, LayerFn},
|
||||||
|
stack::Stack,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Decorates a [`Service`], transforming either the request or the response.
|
||||||
|
///
|
||||||
|
/// Often, many of the pieces needed for writing network applications can be
|
||||||
|
/// reused across multiple services. The `Layer` trait can be used to write
|
||||||
|
/// reusable components that can be applied to very different kinds of services;
|
||||||
|
/// for example, it can be applied to services operating on different protocols,
|
||||||
|
/// and to both the client and server side of a network transaction.
|
||||||
|
///
|
||||||
|
/// # Log
|
||||||
|
///
|
||||||
|
/// Take request logging as an example:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use tower_service::Service;
|
||||||
|
/// # use std::task::{Poll, Context};
|
||||||
|
/// # use tower_layer::Layer;
|
||||||
|
/// # use std::fmt;
|
||||||
|
///
|
||||||
|
/// pub struct LogLayer {
|
||||||
|
/// target: &'static str,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl<S> Layer<S> for LogLayer {
|
||||||
|
/// type Service = LogService<S>;
|
||||||
|
///
|
||||||
|
/// fn layer(&self, service: S) -> Self::Service {
|
||||||
|
/// LogService {
|
||||||
|
/// target: self.target,
|
||||||
|
/// service
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // This service implements the Log behavior
|
||||||
|
/// pub struct LogService<S> {
|
||||||
|
/// target: &'static str,
|
||||||
|
/// service: S,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl<S, Request> Service<Request> for LogService<S>
|
||||||
|
/// where
|
||||||
|
/// S: Service<Request>,
|
||||||
|
/// Request: fmt::Debug,
|
||||||
|
/// {
|
||||||
|
/// type Response = S::Response;
|
||||||
|
/// type Error = S::Error;
|
||||||
|
/// type Future = S::Future;
|
||||||
|
///
|
||||||
|
/// fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
/// self.service.poll_ready(cx)
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn call(&mut self, request: Request) -> Self::Future {
|
||||||
|
/// // Insert log statement here or other functionality
|
||||||
|
/// println!("request = {:?}, target = {:?}", request, self.target);
|
||||||
|
/// self.service.call(request)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The above log implementation is decoupled from the underlying protocol and
|
||||||
|
/// is also decoupled from client or server concerns. In other words, the same
|
||||||
|
/// log middleware could be used in either a client or a server.
|
||||||
|
///
|
||||||
|
/// [`Service`]: https://docs.rs/tower/latest/tower/trait.Service.html
|
||||||
|
pub trait Layer<S> {
|
||||||
|
/// The wrapped service
|
||||||
|
type Service;
|
||||||
|
/// Wrap the given service with the middleware, returning a new service
|
||||||
|
/// that has been decorated with the middleware.
|
||||||
|
fn layer(&self, inner: S) -> Self::Service;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T, S> Layer<S> for &'a T
|
||||||
|
where
|
||||||
|
T: ?Sized + Layer<S>,
|
||||||
|
{
|
||||||
|
type Service = T::Service;
|
||||||
|
|
||||||
|
fn layer(&self, inner: S) -> Self::Service {
|
||||||
|
(**self).layer(inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
62
tower/tower-layer/src/stack.rs
Normal file
62
tower/tower-layer/src/stack.rs
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
use super::Layer;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// Two middlewares chained together.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Stack<Inner, Outer> {
|
||||||
|
inner: Inner,
|
||||||
|
outer: Outer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Inner, Outer> Stack<Inner, Outer> {
|
||||||
|
/// Create a new `Stack`.
|
||||||
|
pub fn new(inner: Inner, outer: Outer) -> Self {
|
||||||
|
Stack { inner, outer }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, Inner, Outer> Layer<S> for Stack<Inner, Outer>
|
||||||
|
where
|
||||||
|
Inner: Layer<S>,
|
||||||
|
Outer: Layer<Inner::Service>,
|
||||||
|
{
|
||||||
|
type Service = Outer::Service;
|
||||||
|
|
||||||
|
fn layer(&self, service: S) -> Self::Service {
|
||||||
|
let inner = self.inner.layer(service);
|
||||||
|
|
||||||
|
self.outer.layer(inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Inner, Outer> fmt::Debug for Stack<Inner, Outer>
|
||||||
|
where
|
||||||
|
Inner: fmt::Debug,
|
||||||
|
Outer: fmt::Debug,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
// The generated output of nested `Stack`s is very noisy and makes
|
||||||
|
// it harder to understand what is in a `ServiceBuilder`.
|
||||||
|
//
|
||||||
|
// Instead, this output is designed assuming that a `Stack` is
|
||||||
|
// usually quite nested, and inside a `ServiceBuilder`. Therefore,
|
||||||
|
// this skips using `f.debug_struct()`, since each one would force
|
||||||
|
// a new layer of indentation.
|
||||||
|
//
|
||||||
|
// - In compact mode, a nested stack ends up just looking like a flat
|
||||||
|
// list of layers.
|
||||||
|
//
|
||||||
|
// - In pretty mode, while a newline is inserted between each layer,
|
||||||
|
// the `DebugStruct` used in the `ServiceBuilder` will inject padding
|
||||||
|
// to that each line is at the same indentation level.
|
||||||
|
//
|
||||||
|
// Also, the order of [outer, inner] is important, since it reflects
|
||||||
|
// the order that the layers were added to the stack.
|
||||||
|
if f.alternate() {
|
||||||
|
// pretty
|
||||||
|
write!(f, "{:#?},\n{:#?}", self.outer, self.inner)
|
||||||
|
} else {
|
||||||
|
write!(f, "{:?}, {:?}", self.outer, self.inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
63
tower/tower-service/CHANGELOG.md
Normal file
63
tower/tower-service/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
# Unreleased
|
||||||
|
|
||||||
|
- None
|
||||||
|
|
||||||
|
# 0.3.2 (June 17, 2022)
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- **docs**: Clarify subtlety around cloning and readiness in the `Service` docs
|
||||||
|
([#548])
|
||||||
|
- **docs**: Clarify details around shared resource consumption in `poll_ready()`
|
||||||
|
([#662])
|
||||||
|
|
||||||
|
|
||||||
|
[#548]: https://github.com/tower-rs/tower/pull/548
|
||||||
|
[#662]: https://github.com/tower-rs/tower/pull/662
|
||||||
|
|
||||||
|
|
||||||
|
# 0.3.1 (November 29, 2019)
|
||||||
|
|
||||||
|
- Improve example in `Service` docs. ([#510])
|
||||||
|
|
||||||
|
[#510]: https://github.com/tower-rs/tower/pull/510
|
||||||
|
|
||||||
|
# 0.3.0 (November 29, 2019)
|
||||||
|
|
||||||
|
- Update to `futures 0.3`.
|
||||||
|
- Update documentation for `std::future::Future`.
|
||||||
|
|
||||||
|
# 0.3.0-alpha.2 (September 30, 2019)
|
||||||
|
|
||||||
|
- Documentation fixes.
|
||||||
|
|
||||||
|
# 0.3.0-alpha.1 (Aug 20, 2019)
|
||||||
|
|
||||||
|
* Switch to `std::future::Future`
|
||||||
|
|
||||||
|
# 0.2.0 (Dec 12, 2018)
|
||||||
|
|
||||||
|
* Change `Service`'s `Request` associated type to be a generic instead.
|
||||||
|
* Before:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl Service for Client {
|
||||||
|
type Request = HttpRequest;
|
||||||
|
type Response = HttpResponse;
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
* After:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
impl Service<HttpRequest> for Client {
|
||||||
|
type Response = HttpResponse;
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
* Remove `NewService`, use `tower_util::MakeService` instead.
|
||||||
|
* Remove `Service::ready` and `Ready`, use `tower_util::ServiceExt` instead.
|
||||||
|
|
||||||
|
# 0.1.0 (Aug 9, 2018)
|
||||||
|
|
||||||
|
* Initial release
|
||||||
28
tower/tower-service/Cargo.toml
Normal file
28
tower/tower-service/Cargo.toml
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
[package]
|
||||||
|
name = "tower-service"
|
||||||
|
# When releasing to crates.io:
|
||||||
|
# - Update doc url
|
||||||
|
# - Cargo.toml
|
||||||
|
# - README.md
|
||||||
|
# - Update CHANGELOG.md.
|
||||||
|
# - Create "v0.3.x" git tag.
|
||||||
|
version = "0.3.2"
|
||||||
|
authors = ["Tower Maintainers <team@tower-rs.com>"]
|
||||||
|
license = "MIT"
|
||||||
|
readme = "README.md"
|
||||||
|
repository = "https://github.com/tower-rs/tower"
|
||||||
|
homepage = "https://github.com/tower-rs/tower"
|
||||||
|
documentation = "https://docs.rs/tower-service/0.3.2"
|
||||||
|
description = """
|
||||||
|
Trait representing an asynchronous, request / response based, client or server.
|
||||||
|
"""
|
||||||
|
categories = ["asynchronous", "network-programming"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
http = "0.2"
|
||||||
|
tower-layer = { version = "0.3", path = "../tower-layer" }
|
||||||
|
tokio = { version = "1", features = ["macros", "time"] }
|
||||||
|
futures = "0.3"
|
||||||
25
tower/tower-service/LICENSE
Normal file
25
tower/tower-service/LICENSE
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
Copyright (c) 2019 Tower Contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
56
tower/tower-service/README.md
Normal file
56
tower/tower-service/README.md
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
# Tower Service
|
||||||
|
|
||||||
|
The foundational `Service` trait that [Tower] is based on.
|
||||||
|
|
||||||
|
[![Crates.io][crates-badge]][crates-url]
|
||||||
|
[![Documentation][docs-badge]][docs-url]
|
||||||
|
[![Documentation (master)][docs-master-badge]][docs-master-url]
|
||||||
|
[![MIT licensed][mit-badge]][mit-url]
|
||||||
|
[![Build Status][actions-badge]][actions-url]
|
||||||
|
[![Discord chat][discord-badge]][discord-url]
|
||||||
|
|
||||||
|
[crates-badge]: https://img.shields.io/crates/v/tower-service.svg
|
||||||
|
[crates-url]: https://crates.io/crates/tower-service
|
||||||
|
[docs-badge]: https://docs.rs/tower-service/badge.svg
|
||||||
|
[docs-url]: https://docs.rs/tower-service
|
||||||
|
[docs-master-badge]: https://img.shields.io/badge/docs-master-blue
|
||||||
|
[docs-master-url]: https://tower-rs.github.io/tower/tower_service
|
||||||
|
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||||
|
[mit-url]: LICENSE
|
||||||
|
[actions-badge]: https://github.com/tower-rs/tower/workflows/CI/badge.svg
|
||||||
|
[actions-url]:https://github.com/tower-rs/tower/actions?query=workflow%3ACI
|
||||||
|
[discord-badge]: https://img.shields.io/discord/500028886025895936?logo=discord&label=discord&logoColor=white
|
||||||
|
[discord-url]: https://discord.gg/EeF3cQw
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The [`Service`] trait provides the foundation upon which [Tower] is built. It is a
|
||||||
|
simple, but powerful trait. At its heart, `Service` is just an asynchronous
|
||||||
|
function of request to response.
|
||||||
|
|
||||||
|
```
|
||||||
|
async fn(Request) -> Result<Response, Error>
|
||||||
|
```
|
||||||
|
|
||||||
|
Implementations of `Service` take a request, the type of which varies per
|
||||||
|
protocol, and returns a future representing the eventual completion or failure
|
||||||
|
of the response.
|
||||||
|
|
||||||
|
Services are used to represent both clients and servers. An *instance* of
|
||||||
|
`Service` is used through a client; a server *implements* `Service`.
|
||||||
|
|
||||||
|
By using standardizing the interface, middleware can be created. Middleware
|
||||||
|
*implement* `Service` by passing the request to another `Service`. The
|
||||||
|
middleware may take actions such as modify the request.
|
||||||
|
|
||||||
|
[`Service`]: https://docs.rs/tower-service/latest/tower_service/trait.Service.html
|
||||||
|
[Tower]: https://crates.io/crates/tower
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the [MIT license](LICENSE).
|
||||||
|
|
||||||
|
### Contribution
|
||||||
|
|
||||||
|
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||||
|
for inclusion in Tower by you, shall be licensed as MIT, without any additional
|
||||||
|
terms or conditions.
|
||||||
20
tower/tower-service/src/lib.rs
Normal file
20
tower/tower-service/src/lib.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
#![warn(rust_2018_idioms, unreachable_pub)]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
|
use std::future::Future;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
pub trait Service<Request> {
|
||||||
|
/// Responses given by the service.
|
||||||
|
type Response;
|
||||||
|
|
||||||
|
/// Errors produced by the service.
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
/// The future response value.
|
||||||
|
type Future: Future<Output = Result<Self::Response, Self::Error>>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>>;
|
||||||
|
|
||||||
|
fn call(&mut self, req: Request) -> Self::Future;
|
||||||
|
}
|
||||||
418
tower/tower/CHANGELOG.md
Normal file
418
tower/tower/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,418 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
# Unreleased
|
||||||
|
|
||||||
|
- None.
|
||||||
|
|
||||||
|
# 0.4.13 (June 17, 2022)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **load_shed**: Public constructor for `Overloaded` error ([#661])
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **util**: Fix hang with `call_all` when the `Stream` of requests is pending
|
||||||
|
([#656])
|
||||||
|
- **ready_cache**: Ensure cancelation is observed by pending services ([#668],
|
||||||
|
fixes [#415])
|
||||||
|
- **docs**: Fix a missing section header due to a typo ([#646])
|
||||||
|
- **docs**: Fix broken links to `Service` trait ([#659])
|
||||||
|
|
||||||
|
|
||||||
|
[#661]: https://github.com/tower-rs/tower/pull/661
|
||||||
|
[#656]: https://github.com/tower-rs/tower/pull/656
|
||||||
|
[#668]: https://github.com/tower-rs/tower/pull/668
|
||||||
|
[#415]: https://github.com/tower-rs/tower/pull/415
|
||||||
|
[#646]: https://github.com/tower-rs/tower/pull/646
|
||||||
|
[#659]: https://github.com/tower-rs/tower/pull/659
|
||||||
|
|
||||||
|
# 0.4.12 (February 16, 2022)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **hedge**, **load**, **retry**: Fix use of `Instant` operations that can panic
|
||||||
|
on platforms where `Instant` is not monotonic ([#633])
|
||||||
|
- Disable `attributes` feature on `tracing` dependency ([#623])
|
||||||
|
- Remove unused dependencies and dependency features with some feature
|
||||||
|
combinations ([#603], [#602])
|
||||||
|
- **docs**: Fix a typo in the RustDoc for `Buffer` ([#622])
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Updated minimum supported Rust version (MSRV) to 1.49.0.
|
||||||
|
- **hedge**: Updated `hdrhistogram` dependency to v7.0 ([#602])
|
||||||
|
- Updated `tokio-util` dependency to v0.7 ([#638])
|
||||||
|
|
||||||
|
[#633]: https://github.com/tower-rs/tower/pull/633
|
||||||
|
[#623]: https://github.com/tower-rs/tower/pull/623
|
||||||
|
[#603]: https://github.com/tower-rs/tower/pull/603
|
||||||
|
[#602]: https://github.com/tower-rs/tower/pull/602
|
||||||
|
[#622]: https://github.com/tower-rs/tower/pull/622
|
||||||
|
[#638]: https://github.com/tower-rs/tower/pull/638
|
||||||
|
|
||||||
|
# 0.4.11 (November 18, 2021)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **util**: Add `BoxCloneService` which is a `Clone + Send` boxed `Service` ([#615])
|
||||||
|
- **util**: Add `ServiceExt::boxed` and `ServiceExt::boxed_clone` for applying the
|
||||||
|
`BoxService` and `BoxCloneService` middleware ([#616])
|
||||||
|
- **builder**: Add `ServiceBuilder::boxed` and `ServiceBuilder::boxed_clone` for
|
||||||
|
applying `BoxService` and `BoxCloneService` layers ([#616])
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **util**: Remove redundant `F: Clone` bound from `ServiceExt::map_request` ([#607])
|
||||||
|
- **util**: Remove unnecessary `Debug` bounds from `impl Debug for BoxService` ([#617])
|
||||||
|
- **util**: Remove unnecessary `Debug` bounds from `impl Debug for UnsyncBoxService` ([#617])
|
||||||
|
- **balance**: Remove redundant `Req: Clone` bound from `Clone` impls
|
||||||
|
for `MakeBalance`, and `MakeBalanceLayer` ([#607])
|
||||||
|
- **balance**: Remove redundant `Req: Debug` bound from `Debug` impls
|
||||||
|
for `MakeBalance`, `MakeFuture`, `Balance`, and `Pool` ([#607])
|
||||||
|
- **ready-cache**: Remove redundant `Req: Debug` bound from `Debug` impl
|
||||||
|
for `ReadyCache` ([#607])
|
||||||
|
- **steer**: Remove redundant `Req: Debug` bound from `Debug` impl
|
||||||
|
for `Steer` ([#607])
|
||||||
|
- **docs**: Fix `doc(cfg(...))` attributes
|
||||||
|
of `PeakEwmaDiscover`, and `PendingRequestsDiscover` ([#610])
|
||||||
|
|
||||||
|
[#607]: https://github.com/tower-rs/tower/pull/607
|
||||||
|
[#610]: https://github.com/tower-rs/tower/pull/610
|
||||||
|
[#615]: https://github.com/tower-rs/tower/pull/615
|
||||||
|
[#616]: https://github.com/tower-rs/tower/pull/616
|
||||||
|
[#617]: https://github.com/tower-rs/tower/pull/617
|
||||||
|
|
||||||
|
# 0.4.10 (October 19, 2021)
|
||||||
|
|
||||||
|
- Fix accidental breaking change when using the
|
||||||
|
`rustdoc::broken_intra_doc_links` lint ([#605])
|
||||||
|
- Clarify that tower's minimum supported rust version is 1.46 ([#605])
|
||||||
|
|
||||||
|
[#605]: https://github.com/tower-rs/tower/pull/605
|
||||||
|
|
||||||
|
# 0.4.9 (October 13, 2021)
|
||||||
|
|
||||||
|
- Migrate to [pin-project-lite] ([#595])
|
||||||
|
- **builder**: Implement `Layer` for `ServiceBuilder` ([#600])
|
||||||
|
- **builder**: Add `ServiceBuilder::and_then` analogous to
|
||||||
|
`ServiceExt::and_then` ([#601])
|
||||||
|
|
||||||
|
[#600]: https://github.com/tower-rs/tower/pull/600
|
||||||
|
[#601]: https://github.com/tower-rs/tower/pull/601
|
||||||
|
[#595]: https://github.com/tower-rs/tower/pull/595
|
||||||
|
[pin-project-lite]: https://crates.io/crates/pin-project-lite
|
||||||
|
|
||||||
|
# 0.4.8 (May 28, 2021)
|
||||||
|
|
||||||
|
- **builder**: Add `ServiceBuilder::map_result` analogous to
|
||||||
|
`ServiceExt::map_result` ([#583])
|
||||||
|
- **limit**: Add `GlobalConcurrencyLimitLayer` to allow reusing a concurrency
|
||||||
|
limit across multiple services ([#574])
|
||||||
|
|
||||||
|
[#574]: https://github.com/tower-rs/tower/pull/574
|
||||||
|
[#583]: https://github.com/tower-rs/tower/pull/583
|
||||||
|
|
||||||
|
# 0.4.7 (April 27, 2021)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **builder**: Add `ServiceBuilder::check_service` to check the request,
|
||||||
|
response, and error types of the output service. ([#576])
|
||||||
|
- **builder**: Add `ServiceBuilder::check_service_clone` to check the output
|
||||||
|
service can be cloned. ([#576])
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **spawn_ready**: Abort spawned background tasks when the `SpawnReady` service
|
||||||
|
is dropped, fixing a potential task/resource leak (#[581])
|
||||||
|
- Fixed broken documentation links ([#578])
|
||||||
|
|
||||||
|
[#576]: https://github.com/tower-rs/tower/pull/576
|
||||||
|
[#578]: https://github.com/tower-rs/tower/pull/578
|
||||||
|
[#581]: https://github.com/tower-rs/tower/pull/581
|
||||||
|
|
||||||
|
# 0.4.6 (February 26, 2021)
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
|
||||||
|
- **util**: Deprecated `ServiceExt::ready_and` (renamed to `ServiceExt::ready`).
|
||||||
|
([#567])
|
||||||
|
- **util**: Deprecated `ReadyAnd` future (renamed to `Ready`). ([#567])
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **builder**: Add `ServiceBuilder::layer_fn` to add a layer built from a
|
||||||
|
function. ([#560])
|
||||||
|
- **builder**: Add `ServiceBuilder::map_future` for transforming the futures
|
||||||
|
produced by a service. ([#559])
|
||||||
|
- **builder**: Add `ServiceBuilder::service_fn` for applying `Layer`s to an
|
||||||
|
async function using `util::service_fn`. ([#564])
|
||||||
|
- **util**: Add example for `service_fn`. ([#563])
|
||||||
|
- **util**: Add `BoxLayer` for creating boxed `Layer` trait objects. ([#569])
|
||||||
|
|
||||||
|
[#567]: https://github.com/tower-rs/tower/pull/567
|
||||||
|
[#560]: https://github.com/tower-rs/tower/pull/560
|
||||||
|
[#559]: https://github.com/tower-rs/tower/pull/559
|
||||||
|
[#564]: https://github.com/tower-rs/tower/pull/564
|
||||||
|
[#563]: https://github.com/tower-rs/tower/pull/563
|
||||||
|
[#569]: https://github.com/tower-rs/tower/pull/569
|
||||||
|
|
||||||
|
# 0.4.5 (February 10, 2021)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **util**: Add `ServiceExt::map_future`. ([#542])
|
||||||
|
- **builder**: Add `ServiceBuilder::option_layer` to optionally add a layer. ([#555])
|
||||||
|
- **make**: Add `Shared` which lets you implement `MakeService` by cloning a
|
||||||
|
service. ([#533])
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **util**: Make combinators that contain closures implement `Debug`. They
|
||||||
|
previously wouldn't since closures never implement `Debug`. ([#552])
|
||||||
|
- **steer**: Implement `Clone` for `Steer`. ([#554])
|
||||||
|
- **spawn-ready**: SpawnReady now propagates the current `tracing` span to
|
||||||
|
spawned tasks ([#557])
|
||||||
|
- Only pull in `tracing` for the features that need it. ([#551])
|
||||||
|
|
||||||
|
[#542]: https://github.com/tower-rs/tower/pull/542
|
||||||
|
[#555]: https://github.com/tower-rs/tower/pull/555
|
||||||
|
[#557]: https://github.com/tower-rs/tower/pull/557
|
||||||
|
[#533]: https://github.com/tower-rs/tower/pull/533
|
||||||
|
[#551]: https://github.com/tower-rs/tower/pull/551
|
||||||
|
[#554]: https://github.com/tower-rs/tower/pull/554
|
||||||
|
[#552]: https://github.com/tower-rs/tower/pull/552
|
||||||
|
|
||||||
|
# 0.4.4 (January 20, 2021)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **util**: Implement `Layer` for `Either<A, B>`. ([#531])
|
||||||
|
- **util**: Implement `Clone` for `FilterLayer`. ([#535])
|
||||||
|
- **timeout**: Implement `Clone` for `TimeoutLayer`. ([#535])
|
||||||
|
- **limit**: Implement `Clone` for `RateLimitLayer`. ([#535])
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Added "full" feature which turns on all other features. ([#532])
|
||||||
|
- **spawn-ready**: Avoid oneshot allocations. ([#538])
|
||||||
|
|
||||||
|
[#531]: https://github.com/tower-rs/tower/pull/531
|
||||||
|
[#532]: https://github.com/tower-rs/tower/pull/532
|
||||||
|
[#535]: https://github.com/tower-rs/tower/pull/535
|
||||||
|
[#538]: https://github.com/tower-rs/tower/pull/538
|
||||||
|
|
||||||
|
# 0.4.3 (January 13, 2021)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **filter**: `Filter::check` and `AsyncFilter::check` methods which check a
|
||||||
|
request against the filter's `Predicate` ([#521])
|
||||||
|
- **filter**: Added `get_ref`, `get_mut`, and `into_inner` methods to `Filter`
|
||||||
|
and `AsyncFilter`, allowing access to the wrapped service ([#522])
|
||||||
|
- **util**: Added `layer` associated function to `AndThen`, `Then`,
|
||||||
|
`MapRequest`, `MapResponse`, and `MapResult` types. These return a `Layer`
|
||||||
|
that produces middleware of that type, as a convenience to avoid having to
|
||||||
|
import the `Layer` type separately. ([#524])
|
||||||
|
- **util**: Added missing `Clone` impls to `AndThenLayer`, `MapRequestLayer`,
|
||||||
|
and `MapErrLayer`, when the mapped function implements `Clone` ([#525])
|
||||||
|
- **util**: Added `FutureService::new` constructor, with less restrictive bounds
|
||||||
|
than the `future_service` free function ([#523])
|
||||||
|
|
||||||
|
[#521]: https://github.com/tower-rs/tower/pull/521
|
||||||
|
[#522]: https://github.com/tower-rs/tower/pull/522
|
||||||
|
[#523]: https://github.com/tower-rs/tower/pull/523
|
||||||
|
[#524]: https://github.com/tower-rs/tower/pull/524
|
||||||
|
[#525]: https://github.com/tower-rs/tower/pull/525
|
||||||
|
|
||||||
|
# 0.4.2 (January 11, 2021)
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Export `layer_fn` and `LayerFn` from the `tower::layer` module. ([#516])
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix missing `Sync` implementation for `Buffer` and `ConcurrencyLimit` ([#518])
|
||||||
|
|
||||||
|
[#518]: https://github.com/tower-rs/tower/pull/518
|
||||||
|
[#516]: https://github.com/tower-rs/tower/pull/516
|
||||||
|
|
||||||
|
# 0.4.1 (January 7, 2021)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Updated `tower-layer` to 0.3.1 to fix broken re-exports.
|
||||||
|
|
||||||
|
# 0.4.0 (January 7, 2021)
|
||||||
|
|
||||||
|
This is a major breaking release including a large number of changes. In
|
||||||
|
particular, this release updates `tower` to depend on Tokio 1.0, and moves all
|
||||||
|
middleware into the `tower` crate. In addition, Tower 0.4 reworks several
|
||||||
|
middleware APIs, as well as introducing new ones.
|
||||||
|
|
||||||
|
This release does *not* change the core `Service` or `Layer` traits, so `tower`
|
||||||
|
0.4 still depends on `tower-service` 0.3 and `tower-layer` 0.3. This means that
|
||||||
|
`tower` 0.4 is still compatible with libraries that depend on those crates.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **make**: Added `MakeService::into_service` and `MakeService::as_service` for
|
||||||
|
converting `MakeService`s into `Service`s ([#492])
|
||||||
|
- **steer**: Added `steer` middleware for routing requests to one of a set of
|
||||||
|
services ([#426])
|
||||||
|
- **util**: Added `MapRequest` middleware and `ServiceExt::map_request`, for
|
||||||
|
applying a function to a request before passing it to the inner service
|
||||||
|
([#435])
|
||||||
|
- **util**: Added `MapResponse` middleware and `ServiceExt::map_response`, for
|
||||||
|
applying a function to the `Response` type of an inner service after its
|
||||||
|
future completes ([#435])
|
||||||
|
- **util**: Added `MapErr` middleware and `ServiceExt::map_err`, for
|
||||||
|
applying a function to the `Error` returned by an inner service if it fails
|
||||||
|
([#396])
|
||||||
|
- **util**: Added `MapResult` middleware and `ServiceExt::map_result`, for
|
||||||
|
applying a function to the `Result` returned by an inner service's future
|
||||||
|
regardless of whether it succeeds or fails ([#499])
|
||||||
|
- **util**: Added `Then` middleware and `ServiceExt::then`, for chaining another
|
||||||
|
future after an inner service's future completes (with a `Response` or an
|
||||||
|
`Error`) ([#500])
|
||||||
|
- **util**: Added `AndThen` middleware and `ServiceExt::and_then`, for
|
||||||
|
chaining another future after an inner service's future completes successfully
|
||||||
|
([#485])
|
||||||
|
- **util**: Added `layer_fn`, for constructing a `Layer` from a function taking
|
||||||
|
a `Service` and returning a different `Service` ([#491])
|
||||||
|
- **util**: Added `FutureService`, which implements `Service` for a
|
||||||
|
`Future` whose `Output` type is a `Service` ([#496])
|
||||||
|
- **util**: Added `BoxService::layer` and `UnsyncBoxService::layer`, to make
|
||||||
|
constructing layers more ergonomic ([#503])
|
||||||
|
- **layer**: Added `Layer` impl for `&Layer` ([#446])
|
||||||
|
- **retry**: Added `Retry::get_ref`, `Retry::get_mut`, and `Retry::into_inner`
|
||||||
|
to access the inner service ([#463])
|
||||||
|
- **timeout**: Added `Timeout::get_ref`, `Timeout::get_mut`, and
|
||||||
|
`Timeout::into_inner` to access the inner service ([#463])
|
||||||
|
- **buffer**: Added `Clone` and `Copy` impls for `BufferLayer` (#[493])
|
||||||
|
- Several documentation improvements ([#442], [#444], [#445], [#449], [#487],
|
||||||
|
[#490], [#506]])
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- All middleware `tower-*` crates were merged into `tower` and placed
|
||||||
|
behind feature flags ([#432])
|
||||||
|
- Updated Tokio dependency to 1.0 ([#489])
|
||||||
|
- **builder**: Make `ServiceBuilder::service` take `self` by reference rather
|
||||||
|
than by value ([#504])
|
||||||
|
- **reconnect**: Return errors from `MakeService` in the response future, rather than
|
||||||
|
in `poll_ready`, allowing the reconnect service to be reused when a reconnect
|
||||||
|
fails ([#386], [#437])
|
||||||
|
- **discover**: Changed `Discover` to be a sealed trait alias for a
|
||||||
|
`TryStream<Item = Change>`. `Discover` implementations are now written by
|
||||||
|
implementing `Stream`. ([#443])
|
||||||
|
- **load**: Renamed the `Instrument` trait to `TrackCompletion` ([#445])
|
||||||
|
- **load**: Renamed `NoInstrument` to `CompleteOnResponse` ([#445])
|
||||||
|
- **balance**: Renamed `BalanceLayer` to `MakeBalanceLayer` ([#449])
|
||||||
|
- **balance**: Renamed `BalanceMake` to `MakeBalance` ([#449])
|
||||||
|
- **ready-cache**: Changed `ready_cache::error::Failed`'s `fmt::Debug` impl to
|
||||||
|
require the key type to also implement `fmt::Debug` ([#467])
|
||||||
|
- **filter**: Changed `Filter` and `Predicate` to use a synchronous function as
|
||||||
|
a predicate ([#508])
|
||||||
|
- **filter**: Renamed the previous `Filter` and `Predicate` (where `Predicate`s
|
||||||
|
returned a `Future`) to `AsyncFilter` and `AsyncPredicate` ([#508])
|
||||||
|
- **filter**: `Predicate`s now take a `Request` type by value and may return a
|
||||||
|
new request, potentially of a different type ([#508])
|
||||||
|
- **filter**: `Predicate`s may now return an error of any type ([#508])
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **limit**: Fixed an issue where `RateLimit` services do not reset the remaining
|
||||||
|
count when rate limiting ([#438], [#439])
|
||||||
|
- **util**: Fixed a bug where `oneshot` futures panic if the service does not
|
||||||
|
immediately become ready ([#447])
|
||||||
|
- **ready-cache**: Fixed `ready_cache::error::Failed` not returning inner error types
|
||||||
|
via `Error::source` ([#467])
|
||||||
|
- **hedge**: Fixed an interaction with `buffer` where `buffer` slots were
|
||||||
|
eagerly reserved for hedge requests even if they were not sent ([#472])
|
||||||
|
- **hedge**: Fixed the use of a fixed 10 second bound on the hedge latency
|
||||||
|
histogram resulting on errors with longer-lived requests. The latency
|
||||||
|
histogram now automatically resizes ([#484])
|
||||||
|
- **buffer**: Fixed an issue where tasks waiting for buffer capacity were not
|
||||||
|
woken when a buffer is dropped, potentially resulting in a task leak ([#480])
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
|
||||||
|
- Remove `ServiceExt::ready`.
|
||||||
|
- **discover**: Removed `discover::stream` module, since `Discover` is now an
|
||||||
|
alias for `Stream` ([#443])
|
||||||
|
- **buffer**: Removed `MakeBalance::from_rng`, which caused all balancers to use
|
||||||
|
the same RNG ([#497])
|
||||||
|
|
||||||
|
[#432]: https://github.com/tower-rs/tower/pull/432
|
||||||
|
[#426]: https://github.com/tower-rs/tower/pull/426
|
||||||
|
[#435]: https://github.com/tower-rs/tower/pull/435
|
||||||
|
[#499]: https://github.com/tower-rs/tower/pull/499
|
||||||
|
[#386]: https://github.com/tower-rs/tower/pull/386
|
||||||
|
[#437]: https://github.com/tower-rs/tower/pull/487
|
||||||
|
[#438]: https://github.com/tower-rs/tower/pull/438
|
||||||
|
[#437]: https://github.com/tower-rs/tower/pull/439
|
||||||
|
[#443]: https://github.com/tower-rs/tower/pull/443
|
||||||
|
[#442]: https://github.com/tower-rs/tower/pull/442
|
||||||
|
[#444]: https://github.com/tower-rs/tower/pull/444
|
||||||
|
[#445]: https://github.com/tower-rs/tower/pull/445
|
||||||
|
[#446]: https://github.com/tower-rs/tower/pull/446
|
||||||
|
[#447]: https://github.com/tower-rs/tower/pull/447
|
||||||
|
[#449]: https://github.com/tower-rs/tower/pull/449
|
||||||
|
[#463]: https://github.com/tower-rs/tower/pull/463
|
||||||
|
[#396]: https://github.com/tower-rs/tower/pull/396
|
||||||
|
[#467]: https://github.com/tower-rs/tower/pull/467
|
||||||
|
[#472]: https://github.com/tower-rs/tower/pull/472
|
||||||
|
[#480]: https://github.com/tower-rs/tower/pull/480
|
||||||
|
[#484]: https://github.com/tower-rs/tower/pull/484
|
||||||
|
[#489]: https://github.com/tower-rs/tower/pull/489
|
||||||
|
[#497]: https://github.com/tower-rs/tower/pull/497
|
||||||
|
[#487]: https://github.com/tower-rs/tower/pull/487
|
||||||
|
[#493]: https://github.com/tower-rs/tower/pull/493
|
||||||
|
[#491]: https://github.com/tower-rs/tower/pull/491
|
||||||
|
[#495]: https://github.com/tower-rs/tower/pull/495
|
||||||
|
[#503]: https://github.com/tower-rs/tower/pull/503
|
||||||
|
[#504]: https://github.com/tower-rs/tower/pull/504
|
||||||
|
[#492]: https://github.com/tower-rs/tower/pull/492
|
||||||
|
[#500]: https://github.com/tower-rs/tower/pull/500
|
||||||
|
[#490]: https://github.com/tower-rs/tower/pull/490
|
||||||
|
[#506]: https://github.com/tower-rs/tower/pull/506
|
||||||
|
[#508]: https://github.com/tower-rs/tower/pull/508
|
||||||
|
[#485]: https://github.com/tower-rs/tower/pull/485
|
||||||
|
|
||||||
|
# 0.3.1 (January 17, 2020)
|
||||||
|
|
||||||
|
- Allow opting out of tracing/log (#410).
|
||||||
|
|
||||||
|
# 0.3.0 (December 19, 2019)
|
||||||
|
|
||||||
|
- Update all tower based crates to `0.3`.
|
||||||
|
- Update to `tokio 0.2`
|
||||||
|
- Update to `futures 0.3`
|
||||||
|
|
||||||
|
# 0.3.0-alpha.2 (September 30, 2019)
|
||||||
|
|
||||||
|
- Move to `futures-*-preview 0.3.0-alpha.19`
|
||||||
|
- Move to `pin-project 0.4`
|
||||||
|
|
||||||
|
# 0.3.0-alpha.1a (September 13, 2019)
|
||||||
|
|
||||||
|
- Update `tower-buffer` to `0.3.0-alpha.1b`
|
||||||
|
|
||||||
|
# 0.3.0-alpha.1 (September 11, 2019)
|
||||||
|
|
||||||
|
- Move to `std::future`
|
||||||
|
|
||||||
|
# 0.1.1 (July 19, 2019)
|
||||||
|
|
||||||
|
- Add `ServiceBuilder::into_inner`
|
||||||
|
|
||||||
|
# 0.1.0 (April 26, 2019)
|
||||||
|
|
||||||
|
- Initial release
|
||||||
107
tower/tower/Cargo.toml
Normal file
107
tower/tower/Cargo.toml
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
[package]
|
||||||
|
name = "tower"
|
||||||
|
# When releasing to crates.io:
|
||||||
|
# - Update doc url
|
||||||
|
# - Cargo.toml
|
||||||
|
# - README.md
|
||||||
|
# - Update CHANGELOG.md.
|
||||||
|
# - Create "vX.X.X" git tag.
|
||||||
|
version = "0.4.13"
|
||||||
|
authors = ["Tower Maintainers <team@tower-rs.com>"]
|
||||||
|
license = "MIT"
|
||||||
|
readme = "README.md"
|
||||||
|
repository = "https://github.com/tower-rs/tower"
|
||||||
|
homepage = "https://github.com/tower-rs/tower"
|
||||||
|
documentation = "https://docs.rs/tower/0.4.13"
|
||||||
|
description = """
|
||||||
|
Tower is a library of modular and reusable components for building robust
|
||||||
|
clients and servers.
|
||||||
|
"""
|
||||||
|
categories = ["asynchronous", "network-programming"]
|
||||||
|
keywords = ["io", "async", "non-blocking", "futures", "service"]
|
||||||
|
edition = "2018"
|
||||||
|
rust-version = "1.49.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["log"]
|
||||||
|
|
||||||
|
# Internal
|
||||||
|
__common = ["futures-core", "pin-project-lite"]
|
||||||
|
|
||||||
|
full = [
|
||||||
|
"balance",
|
||||||
|
"buffer",
|
||||||
|
"discover",
|
||||||
|
"filter",
|
||||||
|
"hedge",
|
||||||
|
"limit",
|
||||||
|
"load",
|
||||||
|
"load-shed",
|
||||||
|
"make",
|
||||||
|
"ready-cache",
|
||||||
|
"reconnect",
|
||||||
|
"retry",
|
||||||
|
"spawn-ready",
|
||||||
|
"steer",
|
||||||
|
"timeout",
|
||||||
|
"util",
|
||||||
|
]
|
||||||
|
# FIXME: Use weak dependency once available (https://github.com/rust-lang/cargo/issues/8832)
|
||||||
|
log = ["tracing/log"]
|
||||||
|
balance = ["discover", "load", "ready-cache", "make", "rand", "slab"]
|
||||||
|
buffer = ["__common", "tokio/sync", "tokio/rt", "tokio-util", "tracing"]
|
||||||
|
discover = ["__common"]
|
||||||
|
filter = ["__common", "futures-util"]
|
||||||
|
hedge = ["util", "filter", "futures-util", "hdrhistogram", "tokio/time", "tracing"]
|
||||||
|
limit = ["__common", "tokio/time", "tokio/sync", "tokio-util", "tracing"]
|
||||||
|
load = ["__common", "tokio/time", "tracing"]
|
||||||
|
load-shed = ["__common"]
|
||||||
|
make = ["futures-util", "pin-project-lite", "tokio/io-std"]
|
||||||
|
ready-cache = ["futures-core", "futures-util", "indexmap", "tokio/sync", "tracing", "pin-project-lite"]
|
||||||
|
reconnect = ["make", "tokio/io-std", "tracing"]
|
||||||
|
retry = ["__common", "tokio/time"]
|
||||||
|
spawn-ready = ["__common", "futures-util", "tokio/sync", "tokio/rt", "util", "tracing"]
|
||||||
|
steer = []
|
||||||
|
timeout = ["pin-project-lite", "tokio/time"]
|
||||||
|
util = ["__common", "futures-util", "pin-project"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tower-layer = { version = "0.3.1", path = "../tower-layer" }
|
||||||
|
tower-service = { version = "0.3.1", path = "../tower-service" }
|
||||||
|
|
||||||
|
futures-core = { version = "0.3", optional = true }
|
||||||
|
futures-util = { version = "0.3", default-features = false, features = ["alloc"], optional = true }
|
||||||
|
hdrhistogram = { version = "7.0", optional = true, default-features = false }
|
||||||
|
indexmap = { version = "1.0.2", optional = true }
|
||||||
|
rand = { version = "0.8", features = ["small_rng"], optional = true }
|
||||||
|
slab = { version = "0.4", optional = true }
|
||||||
|
tokio = { version = "1.6", optional = true, features = ["sync"] }
|
||||||
|
tokio-stream = { version = "0.1.0", optional = true }
|
||||||
|
tokio-util = { version = "0.7.0", default-features = false, optional = true }
|
||||||
|
tracing = { version = "0.1.2", default-features = false, features = ["std"], optional = true }
|
||||||
|
pin-project = { version = "1", optional = true }
|
||||||
|
pin-project-lite = { version = "0.2.7", optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
futures = "0.3"
|
||||||
|
hdrhistogram = { version = "7.0", default-features = false }
|
||||||
|
pin-project-lite = "0.2.7"
|
||||||
|
tokio = { version = "1.6.2", features = ["macros", "sync", "test-util", "rt-multi-thread"] }
|
||||||
|
tokio-stream = "0.1"
|
||||||
|
tokio-test = "0.4"
|
||||||
|
tower-test = { version = "0.4", path = "../tower-test" }
|
||||||
|
tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt", "ansi"] }
|
||||||
|
http = "0.2"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
|
[package.metadata.playground]
|
||||||
|
features = ["full"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "tower-balance"
|
||||||
|
path = "examples/tower-balance.rs"
|
||||||
|
required-features = ["full"]
|
||||||
25
tower/tower/LICENSE
Normal file
25
tower/tower/LICENSE
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
Copyright (c) 2019 Tower Contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any
|
||||||
|
person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the
|
||||||
|
Software without restriction, including without
|
||||||
|
limitation the rights to use, copy, modify, merge,
|
||||||
|
publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software
|
||||||
|
is furnished to do so, subject to the following
|
||||||
|
conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice
|
||||||
|
shall be included in all copies or substantial portions
|
||||||
|
of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||||
|
DEALINGS IN THE SOFTWARE.
|
||||||
190
tower/tower/README.md
Normal file
190
tower/tower/README.md
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
# Tower
|
||||||
|
|
||||||
|
Tower is a library of modular and reusable components for building robust
|
||||||
|
networking clients and servers.
|
||||||
|
|
||||||
|
[![Crates.io][crates-badge]][crates-url]
|
||||||
|
[![Documentation][docs-badge]][docs-url]
|
||||||
|
[![Documentation (master)][docs-master-badge]][docs-master-url]
|
||||||
|
[![MIT licensed][mit-badge]][mit-url]
|
||||||
|
[![Build Status][actions-badge]][actions-url]
|
||||||
|
[![Discord chat][discord-badge]][discord-url]
|
||||||
|
|
||||||
|
[crates-badge]: https://img.shields.io/crates/v/tower.svg
|
||||||
|
[crates-url]: https://crates.io/crates/tower
|
||||||
|
[docs-badge]: https://docs.rs/tower/badge.svg
|
||||||
|
[docs-url]: https://docs.rs/tower
|
||||||
|
[docs-master-badge]: https://img.shields.io/badge/docs-master-blue
|
||||||
|
[docs-master-url]: https://tower-rs.github.io/tower/tower
|
||||||
|
[mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg
|
||||||
|
[mit-url]: LICENSE
|
||||||
|
[actions-badge]: https://github.com/tower-rs/tower/workflows/CI/badge.svg
|
||||||
|
[actions-url]:https://github.com/tower-rs/tower/actions?query=workflow%3ACI
|
||||||
|
[discord-badge]: https://img.shields.io/discord/500028886025895936?logo=discord&label=discord&logoColor=white
|
||||||
|
[discord-url]: https://discord.gg/EeF3cQw
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Tower aims to make it as easy as possible to build robust networking clients and
|
||||||
|
servers. It is protocol agnostic, but is designed around a request / response
|
||||||
|
pattern. If your protocol is entirely stream based, Tower may not be a good fit.
|
||||||
|
|
||||||
|
Tower provides a simple core abstraction, the [`Service`] trait, which
|
||||||
|
represents an asynchronous function taking a request and returning either a
|
||||||
|
response or an error. This abstraction can be used to model both clients and
|
||||||
|
servers.
|
||||||
|
|
||||||
|
Generic components, like [timeouts], [rate limiting], and [load balancing],
|
||||||
|
can be modeled as [`Service`]s that wrap some inner service and apply
|
||||||
|
additional behavior before or after the inner service is called. This allows
|
||||||
|
implementing these components in a protocol-agnostic, composable way. Typically,
|
||||||
|
such services are referred to as _middleware_.
|
||||||
|
|
||||||
|
An additional abstraction, the [`Layer`] trait, is used to compose
|
||||||
|
middleware with [`Service`]s. If a [`Service`] can be thought of as an
|
||||||
|
asynchronous function from a request type to a response type, a [`Layer`] is
|
||||||
|
a function taking a [`Service`] of one type and returning a [`Service`] of a
|
||||||
|
different type. The [`ServiceBuilder`] type is used to add middleware to a
|
||||||
|
service by composing it with multiple multiple [`Layer`]s.
|
||||||
|
|
||||||
|
### The Tower Ecosystem
|
||||||
|
|
||||||
|
Tower is made up of the following crates:
|
||||||
|
|
||||||
|
* [`tower`] (this crate)
|
||||||
|
* [`tower-service`]
|
||||||
|
* [`tower-layer`]
|
||||||
|
* [`tower-test`]
|
||||||
|
|
||||||
|
Since the [`Service`] and [`Layer`] traits are important integration points
|
||||||
|
for all libraries using Tower, they are kept as stable as possible, and
|
||||||
|
breaking changes are made rarely. Therefore, they are defined in separate
|
||||||
|
crates, [`tower-service`] and [`tower-layer`]. This crate contains
|
||||||
|
re-exports of those core traits, implementations of commonly-used
|
||||||
|
middleware, and [utilities] for working with [`Service`]s and [`Layer`]s.
|
||||||
|
Finally, the [`tower-test`] crate provides tools for testing programs using
|
||||||
|
Tower.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Tower provides an abstraction layer, and generic implementations of various
|
||||||
|
middleware. This means that the `tower` crate on its own does *not* provide
|
||||||
|
a working implementation of a network client or server. Instead, Tower's
|
||||||
|
[`Service` trait][`Service`] provides an integration point between
|
||||||
|
application code, libraries providing middleware implementations, and
|
||||||
|
libraries that implement servers and/or clients for various network
|
||||||
|
protocols.
|
||||||
|
|
||||||
|
Depending on your particular use case, you might use Tower in several ways:
|
||||||
|
|
||||||
|
* **Implementing application logic** for a networked program. You might
|
||||||
|
use the [`Service`] trait to model your application's behavior, and use
|
||||||
|
the middleware [provided by this crate][all_layers] and by other libraries
|
||||||
|
to add functionality to clients and servers provided by one or more
|
||||||
|
protocol implementations.
|
||||||
|
* **Implementing middleware** to add custom behavior to network clients and
|
||||||
|
servers in a reusable manner. This might be general-purpose middleware
|
||||||
|
(and if it is, please consider releasing your middleware as a library for
|
||||||
|
other Tower users!) or application-specific behavior that needs to be
|
||||||
|
shared between multiple clients or servers.
|
||||||
|
* **Implementing a network protocol**. Libraries that implement network
|
||||||
|
protocols (such as HTTP) can depend on `tower-service` to use the
|
||||||
|
[`Service`] trait as an integration point between the protocol and user
|
||||||
|
code. For example, a client for some protocol might implement [`Service`],
|
||||||
|
allowing users to add arbitrary Tower middleware to those clients.
|
||||||
|
Similarly, a server might be created from a user-provided [`Service`].
|
||||||
|
|
||||||
|
Additionally, when a network protocol requires functionality already
|
||||||
|
provided by existing Tower middleware, a protocol implementation might use
|
||||||
|
Tower middleware internally, as well as as an integration point.
|
||||||
|
|
||||||
|
### Library Support
|
||||||
|
|
||||||
|
A number of third-party libraries support Tower and the [`Service`] trait.
|
||||||
|
The following is an incomplete list of such libraries:
|
||||||
|
|
||||||
|
* [`hyper`]: A fast and correct low-level HTTP implementation.
|
||||||
|
* [`tonic`]: A [gRPC-over-HTTP/2][grpc] implementation built on top of
|
||||||
|
[`hyper`]. See [here][tonic-examples] for examples of using [`tonic`] with
|
||||||
|
Tower.
|
||||||
|
* [`warp`]: A lightweight, composable web framework. See
|
||||||
|
[here][warp-service] for details on using [`warp`] with Tower.
|
||||||
|
* [`tower-lsp`] and its fork, [`lspower`]: implementations of the [Language
|
||||||
|
Server Protocol][lsp] based on Tower.
|
||||||
|
* [`kube`]: Kubernetes client and futures controller runtime. [`kube::Client`]
|
||||||
|
makes use of the Tower ecosystem: [`tower`], [`tower-http`], and
|
||||||
|
[`tower-test`]. See [here][kube-example-minimal] and
|
||||||
|
[here][kube-example-trace] for examples of using [`kube`] with Tower.
|
||||||
|
|
||||||
|
[`hyper`]: https://crates.io/crates/hyper
|
||||||
|
[`tonic`]: https://crates.io/crates/tonic
|
||||||
|
[tonic-examples]: https://github.com/hyperium/tonic/tree/master/examples/src/tower
|
||||||
|
[grpc]: https://grpc.io
|
||||||
|
[`warp`]: https://crates.io/crates/warp
|
||||||
|
[warp-service]: https://docs.rs/warp/0.2.5/warp/fn.service.html
|
||||||
|
[`tower-lsp`]: https://crates.io/crates/tower-lsp
|
||||||
|
[`lspower`]: https://crates.io/crates/lspower
|
||||||
|
[lsp]: https://microsoft.github.io/language-server-protocol/
|
||||||
|
[`kube`]: https://crates.io/crates/kube
|
||||||
|
[`kube::Client`]: https://docs.rs/kube/latest/kube/struct.Client.html
|
||||||
|
[kube-example-minimal]: https://github.com/clux/kube-rs/blob/master/examples/custom_client.rs
|
||||||
|
[kube-example-trace]: https://github.com/clux/kube-rs/blob/master/examples/custom_client_trace.rs
|
||||||
|
[`tower-http`]: https://crates.io/crates/tower-http
|
||||||
|
|
||||||
|
If you're the maintainer of a crate that supports Tower, we'd love to add
|
||||||
|
your crate to this list! Please [open a PR] adding a brief description of
|
||||||
|
your library!
|
||||||
|
|
||||||
|
### Getting Started
|
||||||
|
|
||||||
|
The various middleware implementations provided by this crate are feature
|
||||||
|
flagged, so that users can only compile the parts of Tower they need. By
|
||||||
|
default, all the optional middleware are disabled.
|
||||||
|
|
||||||
|
To get started using all of Tower's optional middleware, add this to your
|
||||||
|
`Cargo.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
tower = { version = "0.4", features = ["full"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, you can only enable some features. For example, to enable
|
||||||
|
only the [`retry`] and [`timeout`][timeouts] middleware, write:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
tower = { version = "0.4", features = ["retry", "timeout"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
See [here][all_layers] for a complete list of all middleware provided by
|
||||||
|
Tower.
|
||||||
|
|
||||||
|
[`Service`]: https://docs.rs/tower/latest/tower/trait.Service.html
|
||||||
|
[`Layer`]: https://docs.rs/tower/latest/tower/trait.Layer.html
|
||||||
|
[all_layers]: https://docs.rs/tower/latest/tower/#modules
|
||||||
|
[timeouts]: https://docs.rs/tower/latest/tower/timeout/
|
||||||
|
[rate limiting]: https://docs.rs/tower/latest/tower/limit/rate
|
||||||
|
[load balancing]: https://docs.rs/tower/latest/tower/balance/
|
||||||
|
[`ServiceBuilder`]: https://docs.rs/tower/latest/tower/struct.ServiceBuilder.html
|
||||||
|
[utilities]: https://docs.rs/tower/latest/tower/trait.ServiceExt.html
|
||||||
|
[`tower`]: https://crates.io/crates/tower
|
||||||
|
[`tower-service`]: https://crates.io/crates/tower-service
|
||||||
|
[`tower-layer`]: https://crates.io/crates/tower-layer
|
||||||
|
[`tower-test`]: https://crates.io/crates/tower-test
|
||||||
|
[`retry`]: https://docs.rs/tower/latest/tower/retry
|
||||||
|
[open a PR]: https://github.com/tower-rs/tower/compare
|
||||||
|
|
||||||
|
|
||||||
|
## Supported Rust Versions
|
||||||
|
|
||||||
|
Tower will keep a rolling MSRV (minimum supported Rust version) policy of **at
|
||||||
|
least** 6 months. When increasing the MSRV, the new Rust version must have been
|
||||||
|
released at least six months ago. The current MSRV is 1.49.0.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the [MIT license](LICENSE).
|
||||||
|
|
||||||
|
### Contribution
|
||||||
|
|
||||||
|
Unless you explicitly state otherwise, any contribution intentionally submitted
|
||||||
|
for inclusion in Tower by you, shall be licensed as MIT, without any additional
|
||||||
|
terms or conditions.
|
||||||
21
tower/tower/src/balance/error.rs
Normal file
21
tower/tower/src/balance/error.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
//! Error types for the [`tower::balance`] middleware.
|
||||||
|
//!
|
||||||
|
//! [`tower::balance`]: crate::balance
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// The balancer's endpoint discovery stream failed.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Discover(pub(crate) crate::BoxError);
|
||||||
|
|
||||||
|
impl fmt::Display for Discover {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "load balancer discovery error: {}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for Discover {
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
|
Some(&*self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
61
tower/tower/src/balance/mod.rs
Normal file
61
tower/tower/src/balance/mod.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
//! Middleware that allows balancing load among multiple services.
|
||||||
|
//!
|
||||||
|
//! In larger systems, multiple endpoints are often available for a given service. As load
|
||||||
|
//! increases, you want to ensure that that load is spread evenly across the available services.
|
||||||
|
//! Otherwise, clients could see spikes in latency if their request goes to a particularly loaded
|
||||||
|
//! service, even when spare capacity is available to handle that request elsewhere.
|
||||||
|
//!
|
||||||
|
//! This module provides two pieces of middleware that helps with this type of load balancing:
|
||||||
|
//!
|
||||||
|
//! First, [`p2c`] implements the "[Power of Two Random Choices]" algorithm, a simple but robust
|
||||||
|
//! technique for spreading load across services with only inexact load measurements. Use this if
|
||||||
|
//! the set of available services is not within your control, and you simply want to spread load
|
||||||
|
//! among that set of services.
|
||||||
|
//!
|
||||||
|
//! [Power of Two Random Choices]: http://www.eecs.harvard.edu/~michaelm/postscripts/handbook2001.pdf
|
||||||
|
//!
|
||||||
|
//! Second, [`pool`] implements a dynamically sized pool of services. It estimates the overall
|
||||||
|
//! current load by tracking successful and unsuccessful calls to [`poll_ready`], and uses an
|
||||||
|
//! exponentially weighted moving average to add (using [`MakeService`]) or remove (by dropping)
|
||||||
|
//! services in response to increases or decreases in load. Use this if you are able to
|
||||||
|
//! dynamically add more service endpoints to the system to handle added load.
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # #[cfg(feature = "util")]
|
||||||
|
//! # #[cfg(feature = "load")]
|
||||||
|
//! # fn warnings_are_errors() {
|
||||||
|
//! use tower::balance::p2c::Balance;
|
||||||
|
//! use tower::load::Load;
|
||||||
|
//! use tower::{Service, ServiceExt};
|
||||||
|
//! use futures_util::pin_mut;
|
||||||
|
//! # use futures_core::Stream;
|
||||||
|
//! # use futures_util::StreamExt;
|
||||||
|
//!
|
||||||
|
//! async fn spread<Req, S: Service<Req> + Load>(svc1: S, svc2: S, reqs: impl Stream<Item = Req>)
|
||||||
|
//! where
|
||||||
|
//! S::Error: Into<tower::BoxError>,
|
||||||
|
//! # // this bound is pretty unfortunate, and the compiler does _not_ help
|
||||||
|
//! S::Metric: std::fmt::Debug,
|
||||||
|
//! {
|
||||||
|
//! // Spread load evenly across the two services
|
||||||
|
//! let p2c = Balance::new(tower::discover::ServiceList::new(vec![svc1, svc2]));
|
||||||
|
//!
|
||||||
|
//! // Issue all the requests that come in.
|
||||||
|
//! // Some will go to svc1, some will go to svc2.
|
||||||
|
//! pin_mut!(reqs);
|
||||||
|
//! let mut responses = p2c.call_all(reqs);
|
||||||
|
//! while let Some(rsp) = responses.next().await {
|
||||||
|
//! // ...
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! [`MakeService`]: crate::MakeService
|
||||||
|
//! [`poll_ready`]: crate::Service::poll_ready
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
|
pub mod p2c;
|
||||||
|
pub mod pool;
|
||||||
60
tower/tower/src/balance/p2c/layer.rs
Normal file
60
tower/tower/src/balance/p2c/layer.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
use super::MakeBalance;
|
||||||
|
use std::{fmt, marker::PhantomData};
|
||||||
|
use tower_layer::Layer;
|
||||||
|
|
||||||
|
/// Construct load balancers ([`Balance`]) over dynamic service sets ([`Discover`]) produced by the
|
||||||
|
/// "inner" service in response to requests coming from the "outer" service.
|
||||||
|
///
|
||||||
|
/// This construction may seem a little odd at first glance. This is not a layer that takes
|
||||||
|
/// requests and produces responses in the traditional sense. Instead, it is more like
|
||||||
|
/// [`MakeService`] in that it takes service _descriptors_ (see `Target` on [`MakeService`])
|
||||||
|
/// and produces _services_. Since [`Balance`] spreads requests across a _set_ of services,
|
||||||
|
/// the inner service should produce a [`Discover`], not just a single
|
||||||
|
/// [`Service`], given a service descriptor.
|
||||||
|
///
|
||||||
|
/// See the [module-level documentation](crate::balance) for details on load balancing.
|
||||||
|
///
|
||||||
|
/// [`Balance`]: crate::balance::p2c::Balance
|
||||||
|
/// [`Discover`]: crate::discover::Discover
|
||||||
|
/// [`MakeService`]: crate::MakeService
|
||||||
|
/// [`Service`]: crate::Service
|
||||||
|
pub struct MakeBalanceLayer<D, Req> {
|
||||||
|
_marker: PhantomData<fn(D, Req)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D, Req> MakeBalanceLayer<D, Req> {
|
||||||
|
/// Build balancers using operating system entropy.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D, Req> Default for MakeBalanceLayer<D, Req> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D, Req> Clone for MakeBalanceLayer<D, Req> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, Req> Layer<S> for MakeBalanceLayer<S, Req> {
|
||||||
|
type Service = MakeBalance<S, Req>;
|
||||||
|
|
||||||
|
fn layer(&self, make_discover: S) -> Self::Service {
|
||||||
|
MakeBalance::new(make_discover)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D, Req> fmt::Debug for MakeBalanceLayer<D, Req> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.debug_struct("MakeBalanceLayer").finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
125
tower/tower/src/balance/p2c/make.rs
Normal file
125
tower/tower/src/balance/p2c/make.rs
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
use super::Balance;
|
||||||
|
use crate::discover::Discover;
|
||||||
|
use futures_core::ready;
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
use std::hash::Hash;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::{
|
||||||
|
fmt,
|
||||||
|
future::Future,
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
use tower_service::Service;
|
||||||
|
|
||||||
|
/// Constructs load balancers over dynamic service sets produced by a wrapped "inner" service.
|
||||||
|
///
|
||||||
|
/// This is effectively an implementation of [`MakeService`] except that it forwards the service
|
||||||
|
/// descriptors (`Target`) to an inner service (`S`), and expects that service to produce a
|
||||||
|
/// service set in the form of a [`Discover`]. It then wraps the service set in a [`Balance`]
|
||||||
|
/// before returning it as the "made" service.
|
||||||
|
///
|
||||||
|
/// See the [module-level documentation](crate::balance) for details on load balancing.
|
||||||
|
///
|
||||||
|
/// [`MakeService`]: crate::MakeService
|
||||||
|
/// [`Discover`]: crate::discover::Discover
|
||||||
|
/// [`Balance`]: crate::balance::p2c::Balance
|
||||||
|
pub struct MakeBalance<S, Req> {
|
||||||
|
inner: S,
|
||||||
|
_marker: PhantomData<fn(Req)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pin_project! {
|
||||||
|
/// A [`Balance`] in the making.
|
||||||
|
///
|
||||||
|
/// [`Balance`]: crate::balance::p2c::Balance
|
||||||
|
pub struct MakeFuture<F, Req> {
|
||||||
|
#[pin]
|
||||||
|
inner: F,
|
||||||
|
_marker: PhantomData<fn(Req)>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, Req> MakeBalance<S, Req> {
|
||||||
|
/// Build balancers using operating system entropy.
|
||||||
|
pub fn new(make_discover: S) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: make_discover,
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, Req> Clone for MakeBalance<S, Req>
|
||||||
|
where
|
||||||
|
S: Clone,
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: self.inner.clone(),
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, Target, Req> Service<Target> for MakeBalance<S, Req>
|
||||||
|
where
|
||||||
|
S: Service<Target>,
|
||||||
|
S::Response: Discover,
|
||||||
|
<S::Response as Discover>::Key: Hash,
|
||||||
|
<S::Response as Discover>::Service: Service<Req>,
|
||||||
|
<<S::Response as Discover>::Service as Service<Req>>::Error: Into<crate::BoxError>,
|
||||||
|
{
|
||||||
|
type Response = Balance<S::Response, Req>;
|
||||||
|
type Error = S::Error;
|
||||||
|
type Future = MakeFuture<S::Future, Req>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.inner.poll_ready(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, target: Target) -> Self::Future {
|
||||||
|
MakeFuture {
|
||||||
|
inner: self.inner.call(target),
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, Req> fmt::Debug for MakeBalance<S, Req>
|
||||||
|
where
|
||||||
|
S: fmt::Debug,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let Self { inner, _marker } = self;
|
||||||
|
f.debug_struct("MakeBalance").field("inner", inner).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, T, E, Req> Future for MakeFuture<F, Req>
|
||||||
|
where
|
||||||
|
F: Future<Output = Result<T, E>>,
|
||||||
|
T: Discover,
|
||||||
|
<T as Discover>::Key: Hash,
|
||||||
|
<T as Discover>::Service: Service<Req>,
|
||||||
|
<<T as Discover>::Service as Service<Req>>::Error: Into<crate::BoxError>,
|
||||||
|
{
|
||||||
|
type Output = Result<Balance<T, Req>, E>;
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let this = self.project();
|
||||||
|
let inner = ready!(this.inner.poll(cx))?;
|
||||||
|
let svc = Balance::new(inner);
|
||||||
|
Poll::Ready(Ok(svc))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, Req> fmt::Debug for MakeFuture<F, Req>
|
||||||
|
where
|
||||||
|
F: fmt::Debug,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let Self { inner, _marker } = self;
|
||||||
|
f.debug_struct("MakeFuture").field("inner", inner).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
41
tower/tower/src/balance/p2c/mod.rs
Normal file
41
tower/tower/src/balance/p2c/mod.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
//! This module implements the "[Power of Two Random Choices]" load balancing algorithm.
|
||||||
|
//!
|
||||||
|
//! It is a simple but robust technique for spreading load across services with only inexact load
|
||||||
|
//! measurements. As its name implies, whenever a request comes in, it samples two ready services
|
||||||
|
//! at random, and issues the request to whichever service is less loaded. How loaded a service is
|
||||||
|
//! is determined by the return value of [`Load`](crate::load::Load).
|
||||||
|
//!
|
||||||
|
//! As described in the [Finagle Guide][finagle]:
|
||||||
|
//!
|
||||||
|
//! > The algorithm randomly picks two services from the set of ready endpoints and
|
||||||
|
//! > selects the least loaded of the two. By repeatedly using this strategy, we can
|
||||||
|
//! > expect a manageable upper bound on the maximum load of any server.
|
||||||
|
//! >
|
||||||
|
//! > The maximum load variance between any two servers is bound by `ln(ln(n))` where
|
||||||
|
//! > `n` is the number of servers in the cluster.
|
||||||
|
//!
|
||||||
|
//! The balance service and layer implementations rely on _service discovery_ to provide the
|
||||||
|
//! underlying set of services to balance requests across. This happens through the
|
||||||
|
//! [`Discover`](crate::discover::Discover) trait, which is essentially a [`Stream`] that indicates
|
||||||
|
//! when services become available or go away. If you have a fixed set of services, consider using
|
||||||
|
//! [`ServiceList`](crate::discover::ServiceList).
|
||||||
|
//!
|
||||||
|
//! Since the load balancer needs to perform _random_ choices, the constructors in this module
|
||||||
|
//! usually come in two forms: one that uses randomness provided by the operating system, and one
|
||||||
|
//! that lets you specify the random seed to use. Usually the former is what you'll want, though
|
||||||
|
//! the latter may come in handy for reproducability or to reduce reliance on the operating system.
|
||||||
|
//!
|
||||||
|
//! [Power of Two Random Choices]: http://www.eecs.harvard.edu/~michaelm/postscripts/handbook2001.pdf
|
||||||
|
//! [finagle]: https://twitter.github.io/finagle/guide/Clients.html#power-of-two-choices-p2c-least-loaded
|
||||||
|
//! [`Stream`]: https://docs.rs/futures/0.3/futures/stream/trait.Stream.html
|
||||||
|
|
||||||
|
mod layer;
|
||||||
|
mod make;
|
||||||
|
mod service;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test;
|
||||||
|
|
||||||
|
pub use layer::MakeBalanceLayer;
|
||||||
|
pub use make::{MakeBalance, MakeFuture};
|
||||||
|
pub use service::Balance;
|
||||||
58
tower/tower/src/balance/p2c/service.rs
Normal file
58
tower/tower/src/balance/p2c/service.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
use super::super::error;
|
||||||
|
use crate::discover::{Change, Discover};
|
||||||
|
use crate::load::Load;
|
||||||
|
use futures_core::ready;
|
||||||
|
use futures_util::future::{self, TryFutureExt};
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
use rand::{rngs::SmallRng, Rng, SeedableRng};
|
||||||
|
use std::hash::Hash;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::{
|
||||||
|
fmt,
|
||||||
|
future::Future,
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
use tokio::sync::oneshot;
|
||||||
|
use tower_service::Service;
|
||||||
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
|
pub struct Balance<D, Req> {
|
||||||
|
_req: PhantomData<(D, Req)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D, Req> Balance<D, Req>
|
||||||
|
where
|
||||||
|
D: Discover,
|
||||||
|
D::Service: Service<Req>,
|
||||||
|
<D::Service as Service<Req>>::Error: Into<crate::BoxError>,
|
||||||
|
{
|
||||||
|
pub fn new(discover: D) -> Self {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D, Req> Service<Req> for Balance<D, Req>
|
||||||
|
where
|
||||||
|
D: Discover + Unpin,
|
||||||
|
D::Key: Hash + Clone,
|
||||||
|
D::Error: Into<crate::BoxError>,
|
||||||
|
D::Service: Service<Req> + Load,
|
||||||
|
<D::Service as Load>::Metric: std::fmt::Debug,
|
||||||
|
<D::Service as Service<Req>>::Error: Into<crate::BoxError>,
|
||||||
|
{
|
||||||
|
type Response = <D::Service as Service<Req>>::Response;
|
||||||
|
type Error = crate::BoxError;
|
||||||
|
type Future = future::MapErr<
|
||||||
|
<D::Service as Service<Req>>::Future,
|
||||||
|
fn(<D::Service as Service<Req>>::Error) -> crate::BoxError,
|
||||||
|
>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, request: Req) -> Self::Future {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
125
tower/tower/src/balance/p2c/test.rs
Normal file
125
tower/tower/src/balance/p2c/test.rs
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
use crate::discover::ServiceList;
|
||||||
|
use crate::load;
|
||||||
|
use futures_util::pin_mut;
|
||||||
|
use std::task::Poll;
|
||||||
|
use tokio_test::{assert_pending, assert_ready, assert_ready_ok, task};
|
||||||
|
use tower_test::{assert_request_eq, mock};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn empty() {
|
||||||
|
let empty: Vec<load::Constant<mock::Mock<(), &'static str>, usize>> = vec![];
|
||||||
|
let disco = ServiceList::new(empty);
|
||||||
|
let mut svc = mock::Spawn::new(Balance::new(disco));
|
||||||
|
assert_pending!(svc.poll_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn single_endpoint() {
|
||||||
|
let (mut svc, mut handle) = mock::spawn_with(|s| {
|
||||||
|
let mock = load::Constant::new(s, 0);
|
||||||
|
let disco = ServiceList::new(vec![mock].into_iter());
|
||||||
|
Balance::new(disco)
|
||||||
|
});
|
||||||
|
|
||||||
|
handle.allow(0);
|
||||||
|
assert_pending!(svc.poll_ready());
|
||||||
|
assert_eq!(
|
||||||
|
svc.get_ref().len(),
|
||||||
|
1,
|
||||||
|
"balancer must have discovered endpoint"
|
||||||
|
);
|
||||||
|
|
||||||
|
handle.allow(1);
|
||||||
|
assert_ready_ok!(svc.poll_ready());
|
||||||
|
|
||||||
|
let mut fut = task::spawn(svc.call(()));
|
||||||
|
|
||||||
|
assert_request_eq!(handle, ()).send_response(1);
|
||||||
|
|
||||||
|
assert_eq!(assert_ready_ok!(fut.poll()), 1);
|
||||||
|
handle.allow(1);
|
||||||
|
assert_ready_ok!(svc.poll_ready());
|
||||||
|
|
||||||
|
handle.send_error("endpoint lost");
|
||||||
|
assert_pending!(svc.poll_ready());
|
||||||
|
assert!(
|
||||||
|
svc.get_ref().is_empty(),
|
||||||
|
"balancer must drop failed endpoints"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn two_endpoints_with_equal_load() {
|
||||||
|
let (mock_a, handle_a) = mock::pair();
|
||||||
|
let (mock_b, handle_b) = mock::pair();
|
||||||
|
let mock_a = load::Constant::new(mock_a, 1);
|
||||||
|
let mock_b = load::Constant::new(mock_b, 1);
|
||||||
|
|
||||||
|
pin_mut!(handle_a);
|
||||||
|
pin_mut!(handle_b);
|
||||||
|
|
||||||
|
let disco = ServiceList::new(vec![mock_a, mock_b].into_iter());
|
||||||
|
let mut svc = mock::Spawn::new(Balance::new(disco));
|
||||||
|
|
||||||
|
handle_a.allow(0);
|
||||||
|
handle_b.allow(0);
|
||||||
|
assert_pending!(svc.poll_ready());
|
||||||
|
assert_eq!(
|
||||||
|
svc.get_ref().len(),
|
||||||
|
2,
|
||||||
|
"balancer must have discovered both endpoints"
|
||||||
|
);
|
||||||
|
|
||||||
|
handle_a.allow(1);
|
||||||
|
handle_b.allow(0);
|
||||||
|
assert_ready_ok!(
|
||||||
|
svc.poll_ready(),
|
||||||
|
"must be ready when one of two services is ready"
|
||||||
|
);
|
||||||
|
{
|
||||||
|
let mut fut = task::spawn(svc.call(()));
|
||||||
|
assert_request_eq!(handle_a, ()).send_response("a");
|
||||||
|
assert_eq!(assert_ready_ok!(fut.poll()), "a");
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_a.allow(0);
|
||||||
|
handle_b.allow(1);
|
||||||
|
assert_ready_ok!(
|
||||||
|
svc.poll_ready(),
|
||||||
|
"must be ready when both endpoints are ready"
|
||||||
|
);
|
||||||
|
{
|
||||||
|
let mut fut = task::spawn(svc.call(()));
|
||||||
|
assert_request_eq!(handle_b, ()).send_response("b");
|
||||||
|
assert_eq!(assert_ready_ok!(fut.poll()), "b");
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_a.allow(1);
|
||||||
|
handle_b.allow(1);
|
||||||
|
for _ in 0..2 {
|
||||||
|
assert_ready_ok!(
|
||||||
|
svc.poll_ready(),
|
||||||
|
"must be ready when both endpoints are ready"
|
||||||
|
);
|
||||||
|
let mut fut = task::spawn(svc.call(()));
|
||||||
|
|
||||||
|
for (ref mut h, c) in &mut [(&mut handle_a, "a"), (&mut handle_b, "b")] {
|
||||||
|
if let Poll::Ready(Some((_, tx))) = h.as_mut().poll_request() {
|
||||||
|
tracing::info!("using {}", c);
|
||||||
|
tx.send_response(c);
|
||||||
|
h.allow(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert_ready_ok!(fut.poll());
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_a.send_error("endpoint lost");
|
||||||
|
assert_pending!(svc.poll_ready());
|
||||||
|
assert_eq!(
|
||||||
|
svc.get_ref().len(),
|
||||||
|
1,
|
||||||
|
"balancer must drop failed endpoints",
|
||||||
|
);
|
||||||
|
}
|
||||||
142
tower/tower/src/balance/pool/mod.rs
Normal file
142
tower/tower/src/balance/pool/mod.rs
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
//! This module defines a load-balanced pool of services that adds new services when load is high.
|
||||||
|
//!
|
||||||
|
//! The pool uses `poll_ready` as a signal indicating whether additional services should be spawned
|
||||||
|
//! to handle the current level of load. Specifically, every time `poll_ready` on the inner service
|
||||||
|
//! returns `Ready`, [`Pool`] consider that a 0, and every time it returns `Pending`, [`Pool`]
|
||||||
|
//! considers it a 1. [`Pool`] then maintains an [exponential moving
|
||||||
|
//! average](https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average) over those
|
||||||
|
//! samples, which gives an estimate of how often the underlying service has been ready when it was
|
||||||
|
//! needed "recently" (see [`Builder::urgency`]). If the service is loaded (see
|
||||||
|
//! [`Builder::loaded_above`]), a new service is created and added to the underlying [`Balance`].
|
||||||
|
//! If the service is underutilized (see [`Builder::underutilized_below`]) and there are two or
|
||||||
|
//! more services, then the latest added service is removed. In either case, the load estimate is
|
||||||
|
//! reset to its initial value (see [`Builder::initial`] to prevent services from being rapidly
|
||||||
|
//! added or removed.
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
|
use super::p2c::Balance;
|
||||||
|
use crate::discover::Change;
|
||||||
|
use crate::load::Load;
|
||||||
|
use crate::make::MakeService;
|
||||||
|
use futures_core::{ready, Stream};
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
use slab::Slab;
|
||||||
|
use std::{
|
||||||
|
fmt,
|
||||||
|
future::Future,
|
||||||
|
marker::PhantomData,
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
use tower_service::Service;
|
||||||
|
|
||||||
|
/// A wrapper around `MakeService` that discovers a new service when load is high, and removes a
|
||||||
|
/// service when load is low. See [`Pool`].
|
||||||
|
pub struct PoolDiscoverer<MS, Target, Request>
|
||||||
|
where
|
||||||
|
MS: MakeService<Target, Request>,
|
||||||
|
{
|
||||||
|
_p: PhantomData<(MS, Target, Request)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<MS, Target, Request> Stream for PoolDiscoverer<MS, Target, Request>
|
||||||
|
where
|
||||||
|
MS: MakeService<Target, Request>,
|
||||||
|
{
|
||||||
|
type Item = Result<(Change<usize, DropNotifyService<MS::Service>>), MS::MakeError>;
|
||||||
|
|
||||||
|
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [builder] that lets you configure how a [`Pool`] determines whether the underlying service is
|
||||||
|
/// loaded or not. See the [module-level documentation](self) and the builder's methods for
|
||||||
|
/// details.
|
||||||
|
///
|
||||||
|
/// [builder]: https://rust-lang-nursery.github.io/api-guidelines/type-safety.html#builders-enable-construction-of-complex-values-c-builder
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct Builder {}
|
||||||
|
|
||||||
|
impl Builder {
|
||||||
|
/// See [`Pool::new`].
|
||||||
|
pub fn build<MS, Target, Request>() -> ()
|
||||||
|
where
|
||||||
|
MS: MakeService<Target, Request>,
|
||||||
|
MS::Service: Load,
|
||||||
|
<MS::Service as Load>::Metric: std::fmt::Debug,
|
||||||
|
MS::MakeError: Into<crate::BoxError>,
|
||||||
|
MS::Error: Into<crate::BoxError>,
|
||||||
|
{
|
||||||
|
let d: PoolDiscoverer<MS, Target, Request> = todo!();
|
||||||
|
|
||||||
|
let x = Balance::new(Box::pin(d));
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A dynamically sized, load-balanced pool of `Service` instances.
|
||||||
|
pub struct Pool<MS, Target, Request> {
|
||||||
|
// the Pin<Box<_>> here is needed since Balance requires the Service to be Unpin
|
||||||
|
balance: (MS, Target, Request),
|
||||||
|
}
|
||||||
|
|
||||||
|
type PinBalance<S, Request> = Balance<Pin<Box<S>>, Request>;
|
||||||
|
|
||||||
|
impl<MS, Target, Req> Service<Req> for Pool<MS, Target, Req>
|
||||||
|
where
|
||||||
|
MS: MakeService<Target, Req>,
|
||||||
|
MS::Service: Load,
|
||||||
|
<MS::Service as Load>::Metric: std::fmt::Debug,
|
||||||
|
MS::MakeError: Into<crate::BoxError>,
|
||||||
|
MS::Error: Into<crate::BoxError>,
|
||||||
|
Target: Clone,
|
||||||
|
{
|
||||||
|
type Response = <PinBalance<PoolDiscoverer<MS, Target, Req>, Req> as Service<Req>>::Response;
|
||||||
|
type Error = <PinBalance<PoolDiscoverer<MS, Target, Req>, Req> as Service<Req>>::Error;
|
||||||
|
type Future = <PinBalance<PoolDiscoverer<MS, Target, Req>, Req> as Service<Req>>::Future;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: Req) -> Self::Future {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DropNotifyService<Svc> {
|
||||||
|
svc: Svc,
|
||||||
|
id: usize,
|
||||||
|
notify: tokio::sync::mpsc::UnboundedSender<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Svc> Drop for DropNotifyService<Svc> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Svc: Load> Load for DropNotifyService<Svc> {
|
||||||
|
type Metric = Svc::Metric;
|
||||||
|
fn load(&self) -> Self::Metric {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Request, Svc: Service<Request>> Service<Request> for DropNotifyService<Svc> {
|
||||||
|
type Response = Svc::Response;
|
||||||
|
type Future = Svc::Future;
|
||||||
|
type Error = Svc::Error;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: Request) -> Self::Future {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
190
tower/tower/src/balance/pool/test.rs
Normal file
190
tower/tower/src/balance/pool/test.rs
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
use crate::load;
|
||||||
|
use futures_util::pin_mut;
|
||||||
|
use tokio_test::{assert_pending, assert_ready, assert_ready_ok, task};
|
||||||
|
use tower_test::{assert_request_eq, mock};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn basic() {
|
||||||
|
// start the pool
|
||||||
|
let (mock, handle) = mock::pair::<(), load::Constant<mock::Mock<(), &'static str>, usize>>();
|
||||||
|
pin_mut!(handle);
|
||||||
|
|
||||||
|
let mut pool = mock::Spawn::new(Builder::new().build(mock, ()));
|
||||||
|
assert_pending!(pool.poll_ready());
|
||||||
|
|
||||||
|
// give the pool a backing service
|
||||||
|
let (svc1_m, svc1) = mock::pair();
|
||||||
|
pin_mut!(svc1);
|
||||||
|
|
||||||
|
assert_request_eq!(handle, ()).send_response(load::Constant::new(svc1_m, 0));
|
||||||
|
assert_ready_ok!(pool.poll_ready());
|
||||||
|
|
||||||
|
// send a request to the one backing service
|
||||||
|
let mut fut = task::spawn(pool.call(()));
|
||||||
|
|
||||||
|
assert_pending!(fut.poll());
|
||||||
|
assert_request_eq!(svc1, ()).send_response("foobar");
|
||||||
|
assert_eq!(assert_ready_ok!(fut.poll()), "foobar");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn high_load() {
|
||||||
|
// start the pool
|
||||||
|
let (mock, handle) = mock::pair::<(), load::Constant<mock::Mock<(), &'static str>, usize>>();
|
||||||
|
pin_mut!(handle);
|
||||||
|
|
||||||
|
let pool = Builder::new()
|
||||||
|
.urgency(1.0) // so _any_ Pending will add a service
|
||||||
|
.underutilized_below(0.0) // so no Ready will remove a service
|
||||||
|
.max_services(Some(2))
|
||||||
|
.build(mock, ());
|
||||||
|
let mut pool = mock::Spawn::new(pool);
|
||||||
|
assert_pending!(pool.poll_ready());
|
||||||
|
|
||||||
|
// give the pool a backing service
|
||||||
|
let (svc1_m, svc1) = mock::pair();
|
||||||
|
pin_mut!(svc1);
|
||||||
|
|
||||||
|
svc1.allow(1);
|
||||||
|
assert_request_eq!(handle, ()).send_response(load::Constant::new(svc1_m, 0));
|
||||||
|
assert_ready_ok!(pool.poll_ready());
|
||||||
|
|
||||||
|
// make the one backing service not ready
|
||||||
|
let mut fut1 = task::spawn(pool.call(()));
|
||||||
|
|
||||||
|
// if we poll_ready again, pool should notice that load is increasing
|
||||||
|
// since urgency == 1.0, it should immediately enter high load
|
||||||
|
assert_pending!(pool.poll_ready());
|
||||||
|
// it should ask the maker for another service, so we give it one
|
||||||
|
let (svc2_m, svc2) = mock::pair();
|
||||||
|
pin_mut!(svc2);
|
||||||
|
|
||||||
|
svc2.allow(1);
|
||||||
|
assert_request_eq!(handle, ()).send_response(load::Constant::new(svc2_m, 0));
|
||||||
|
|
||||||
|
// the pool should now be ready again for one more request
|
||||||
|
assert_ready_ok!(pool.poll_ready());
|
||||||
|
let mut fut2 = task::spawn(pool.call(()));
|
||||||
|
|
||||||
|
assert_pending!(pool.poll_ready());
|
||||||
|
|
||||||
|
// the pool should _not_ try to add another service
|
||||||
|
// sicen we have max_services(2)
|
||||||
|
assert_pending!(handle.as_mut().poll_request());
|
||||||
|
|
||||||
|
// let see that each service got one request
|
||||||
|
assert_request_eq!(svc1, ()).send_response("foo");
|
||||||
|
assert_request_eq!(svc2, ()).send_response("bar");
|
||||||
|
assert_eq!(assert_ready_ok!(fut1.poll()), "foo");
|
||||||
|
assert_eq!(assert_ready_ok!(fut2.poll()), "bar");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn low_load() {
|
||||||
|
// start the pool
|
||||||
|
let (mock, handle) = mock::pair::<(), load::Constant<mock::Mock<(), &'static str>, usize>>();
|
||||||
|
pin_mut!(handle);
|
||||||
|
|
||||||
|
let pool = Builder::new()
|
||||||
|
.urgency(1.0) // so any event will change the service count
|
||||||
|
.build(mock, ());
|
||||||
|
|
||||||
|
let mut pool = mock::Spawn::new(pool);
|
||||||
|
|
||||||
|
assert_pending!(pool.poll_ready());
|
||||||
|
|
||||||
|
// give the pool a backing service
|
||||||
|
let (svc1_m, svc1) = mock::pair();
|
||||||
|
pin_mut!(svc1);
|
||||||
|
|
||||||
|
svc1.allow(1);
|
||||||
|
assert_request_eq!(handle, ()).send_response(load::Constant::new(svc1_m, 0));
|
||||||
|
assert_ready_ok!(pool.poll_ready());
|
||||||
|
|
||||||
|
// cycling a request should now work
|
||||||
|
let mut fut = task::spawn(pool.call(()));
|
||||||
|
|
||||||
|
assert_request_eq!(svc1, ()).send_response("foo");
|
||||||
|
assert_eq!(assert_ready_ok!(fut.poll()), "foo");
|
||||||
|
// and pool should now not be ready (since svc1 isn't ready)
|
||||||
|
// it should immediately try to add another service
|
||||||
|
// which we give it
|
||||||
|
assert_pending!(pool.poll_ready());
|
||||||
|
let (svc2_m, svc2) = mock::pair();
|
||||||
|
pin_mut!(svc2);
|
||||||
|
|
||||||
|
svc2.allow(1);
|
||||||
|
assert_request_eq!(handle, ()).send_response(load::Constant::new(svc2_m, 0));
|
||||||
|
// pool is now ready
|
||||||
|
// which (because of urgency == 1.0) should immediately cause it to drop a service
|
||||||
|
// it'll drop svc1, so it'll still be ready
|
||||||
|
assert_ready_ok!(pool.poll_ready());
|
||||||
|
// and even with another ready, it won't drop svc2 since its now the only service
|
||||||
|
assert_ready_ok!(pool.poll_ready());
|
||||||
|
|
||||||
|
// cycling a request should now work on svc2
|
||||||
|
let mut fut = task::spawn(pool.call(()));
|
||||||
|
|
||||||
|
assert_request_eq!(svc2, ()).send_response("foo");
|
||||||
|
assert_eq!(assert_ready_ok!(fut.poll()), "foo");
|
||||||
|
|
||||||
|
// and again (still svc2)
|
||||||
|
svc2.allow(1);
|
||||||
|
assert_ready_ok!(pool.poll_ready());
|
||||||
|
let mut fut = task::spawn(pool.call(()));
|
||||||
|
|
||||||
|
assert_request_eq!(svc2, ()).send_response("foo");
|
||||||
|
assert_eq!(assert_ready_ok!(fut.poll()), "foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn failing_service() {
|
||||||
|
// start the pool
|
||||||
|
let (mock, handle) = mock::pair::<(), load::Constant<mock::Mock<(), &'static str>, usize>>();
|
||||||
|
pin_mut!(handle);
|
||||||
|
|
||||||
|
let pool = Builder::new()
|
||||||
|
.urgency(1.0) // so _any_ Pending will add a service
|
||||||
|
.underutilized_below(0.0) // so no Ready will remove a service
|
||||||
|
.build(mock, ());
|
||||||
|
|
||||||
|
let mut pool = mock::Spawn::new(pool);
|
||||||
|
|
||||||
|
assert_pending!(pool.poll_ready());
|
||||||
|
|
||||||
|
// give the pool a backing service
|
||||||
|
let (svc1_m, svc1) = mock::pair();
|
||||||
|
pin_mut!(svc1);
|
||||||
|
|
||||||
|
svc1.allow(1);
|
||||||
|
assert_request_eq!(handle, ()).send_response(load::Constant::new(svc1_m, 0));
|
||||||
|
assert_ready_ok!(pool.poll_ready());
|
||||||
|
|
||||||
|
// one request-response cycle
|
||||||
|
let mut fut = task::spawn(pool.call(()));
|
||||||
|
|
||||||
|
assert_request_eq!(svc1, ()).send_response("foo");
|
||||||
|
assert_eq!(assert_ready_ok!(fut.poll()), "foo");
|
||||||
|
|
||||||
|
// now make svc1 fail, so it has to be removed
|
||||||
|
svc1.send_error("ouch");
|
||||||
|
// polling now should recognize the failed service,
|
||||||
|
// try to create a new one, and then realize the maker isn't ready
|
||||||
|
assert_pending!(pool.poll_ready());
|
||||||
|
// then we release another service
|
||||||
|
let (svc2_m, svc2) = mock::pair();
|
||||||
|
pin_mut!(svc2);
|
||||||
|
|
||||||
|
svc2.allow(1);
|
||||||
|
assert_request_eq!(handle, ()).send_response(load::Constant::new(svc2_m, 0));
|
||||||
|
|
||||||
|
// the pool should now be ready again
|
||||||
|
assert_ready_ok!(pool.poll_ready());
|
||||||
|
// and a cycle should work (and go through svc2)
|
||||||
|
let mut fut = task::spawn(pool.call(()));
|
||||||
|
|
||||||
|
assert_request_eq!(svc2, ()).send_response("bar");
|
||||||
|
assert_eq!(assert_ready_ok!(fut.poll()), "bar");
|
||||||
|
}
|
||||||
828
tower/tower/src/builder.rs
Normal file
828
tower/tower/src/builder.rs
Normal file
|
|
@ -0,0 +1,828 @@
|
||||||
|
//! Builder types to compose layers and services
|
||||||
|
|
||||||
|
use tower_layer::{Identity, Layer, Stack};
|
||||||
|
use tower_service::Service;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// Declaratively construct [`Service`] values.
|
||||||
|
///
|
||||||
|
/// [`ServiceBuilder`] provides a [builder-like interface][builder] for composing
|
||||||
|
/// layers to be applied to a [`Service`].
|
||||||
|
///
|
||||||
|
/// # Service
|
||||||
|
///
|
||||||
|
/// A [`Service`] is a trait representing an asynchronous function of a request
|
||||||
|
/// to a response. It is similar to `async fn(Request) -> Result<Response, Error>`.
|
||||||
|
///
|
||||||
|
/// A [`Service`] is typically bound to a single transport, such as a TCP
|
||||||
|
/// connection. It defines how _all_ inbound or outbound requests are handled
|
||||||
|
/// by that connection.
|
||||||
|
///
|
||||||
|
/// [builder]: https://doc.rust-lang.org/1.0.0/style/ownership/builders.html
|
||||||
|
///
|
||||||
|
/// # Order
|
||||||
|
///
|
||||||
|
/// The order in which layers are added impacts how requests are handled. Layers
|
||||||
|
/// that are added first will be called with the request first. The argument to
|
||||||
|
/// `service` will be last to see the request.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # // this (and other) doctest is ignored because we don't have a way
|
||||||
|
/// # // to say that it should only be run with cfg(feature = "...")
|
||||||
|
/// # use tower::Service;
|
||||||
|
/// # use tower::builder::ServiceBuilder;
|
||||||
|
/// # #[cfg(all(feature = "buffer", feature = "limit"))]
|
||||||
|
/// # async fn wrap<S>(svc: S) where S: Service<(), Error = &'static str> + 'static + Send, S::Future: Send {
|
||||||
|
/// ServiceBuilder::new()
|
||||||
|
/// .buffer(100)
|
||||||
|
/// .concurrency_limit(10)
|
||||||
|
/// .service(svc)
|
||||||
|
/// # ;
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// In the above example, the buffer layer receives the request first followed
|
||||||
|
/// by `concurrency_limit`. `buffer` enables up to 100 request to be in-flight
|
||||||
|
/// **on top of** the requests that have already been forwarded to the next
|
||||||
|
/// layer. Combined with `concurrency_limit`, this allows up to 110 requests to be
|
||||||
|
/// in-flight.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use tower::Service;
|
||||||
|
/// # use tower::builder::ServiceBuilder;
|
||||||
|
/// # #[cfg(all(feature = "buffer", feature = "limit"))]
|
||||||
|
/// # async fn wrap<S>(svc: S) where S: Service<(), Error = &'static str> + 'static + Send, S::Future: Send {
|
||||||
|
/// ServiceBuilder::new()
|
||||||
|
/// .concurrency_limit(10)
|
||||||
|
/// .buffer(100)
|
||||||
|
/// .service(svc)
|
||||||
|
/// # ;
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The above example is similar, but the order of layers is reversed. Now,
|
||||||
|
/// `concurrency_limit` applies first and only allows 10 requests to be in-flight
|
||||||
|
/// total.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// A [`Service`] stack with a single layer:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use tower::Service;
|
||||||
|
/// # use tower::builder::ServiceBuilder;
|
||||||
|
/// # #[cfg(feature = "limit")]
|
||||||
|
/// # use tower::limit::concurrency::ConcurrencyLimitLayer;
|
||||||
|
/// # #[cfg(feature = "limit")]
|
||||||
|
/// # async fn wrap<S>(svc: S) where S: Service<(), Error = &'static str> + 'static + Send, S::Future: Send {
|
||||||
|
/// ServiceBuilder::new()
|
||||||
|
/// .concurrency_limit(5)
|
||||||
|
/// .service(svc);
|
||||||
|
/// # ;
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// A [`Service`] stack with _multiple_ layers that contain rate limiting,
|
||||||
|
/// in-flight request limits, and a channel-backed, clonable [`Service`]:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use tower::Service;
|
||||||
|
/// # use tower::builder::ServiceBuilder;
|
||||||
|
/// # use std::time::Duration;
|
||||||
|
/// # #[cfg(all(feature = "buffer", feature = "limit"))]
|
||||||
|
/// # async fn wrap<S>(svc: S) where S: Service<(), Error = &'static str> + 'static + Send, S::Future: Send {
|
||||||
|
/// ServiceBuilder::new()
|
||||||
|
/// .buffer(5)
|
||||||
|
/// .concurrency_limit(5)
|
||||||
|
/// .rate_limit(5, Duration::from_secs(1))
|
||||||
|
/// .service(svc);
|
||||||
|
/// # ;
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`Service`]: crate::Service
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ServiceBuilder<L> {
|
||||||
|
layer: L,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ServiceBuilder<Identity> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServiceBuilder<Identity> {
|
||||||
|
/// Create a new [`ServiceBuilder`].
|
||||||
|
pub fn new() -> Self {
|
||||||
|
ServiceBuilder {
|
||||||
|
layer: Identity::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L> ServiceBuilder<L> {
|
||||||
|
/// Add a new layer `T` into the [`ServiceBuilder`].
|
||||||
|
///
|
||||||
|
/// This wraps the inner service with the service provided by a user-defined
|
||||||
|
/// [`Layer`]. The provided layer must implement the [`Layer`] trait.
|
||||||
|
///
|
||||||
|
/// [`Layer`]: crate::Layer
|
||||||
|
pub fn layer<T>(self, layer: T) -> ServiceBuilder<Stack<T, L>> {
|
||||||
|
ServiceBuilder {
|
||||||
|
layer: Stack::new(layer, self.layer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Optionally add a new layer `T` into the [`ServiceBuilder`].
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use std::time::Duration;
|
||||||
|
/// # use tower::Service;
|
||||||
|
/// # use tower::builder::ServiceBuilder;
|
||||||
|
/// # use tower::timeout::TimeoutLayer;
|
||||||
|
/// # async fn wrap<S>(svc: S) where S: Service<(), Error = &'static str> + 'static + Send, S::Future: Send {
|
||||||
|
/// # let timeout = Some(Duration::new(10, 0));
|
||||||
|
/// // Apply a timeout if configured
|
||||||
|
/// ServiceBuilder::new()
|
||||||
|
/// .option_layer(timeout.map(TimeoutLayer::new))
|
||||||
|
/// .service(svc)
|
||||||
|
/// # ;
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
#[cfg(feature = "util")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "util")))]
|
||||||
|
pub fn option_layer<T>(
|
||||||
|
self,
|
||||||
|
layer: Option<T>,
|
||||||
|
) -> ServiceBuilder<Stack<crate::util::Either<T, Identity>, L>> {
|
||||||
|
self.layer(crate::util::option_layer(layer))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add a [`Layer`] built from a function that accepts a service and returns another service.
|
||||||
|
///
|
||||||
|
/// See the documentation for [`layer_fn`] for more details.
|
||||||
|
///
|
||||||
|
/// [`layer_fn`]: crate::layer::layer_fn
|
||||||
|
pub fn layer_fn<F>(self, f: F) -> ServiceBuilder<Stack<crate::layer::LayerFn<F>, L>> {
|
||||||
|
self.layer(crate::layer::layer_fn(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Buffer requests when the next layer is not ready.
|
||||||
|
///
|
||||||
|
/// This wraps the inner service with an instance of the [`Buffer`]
|
||||||
|
/// middleware.
|
||||||
|
///
|
||||||
|
/// [`Buffer`]: crate::buffer
|
||||||
|
#[cfg(feature = "buffer")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "buffer")))]
|
||||||
|
pub fn buffer<Request>(
|
||||||
|
self,
|
||||||
|
bound: usize,
|
||||||
|
) -> ServiceBuilder<Stack<crate::buffer::BufferLayer<Request>, L>> {
|
||||||
|
self.layer(crate::buffer::BufferLayer::new(bound))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Limit the max number of in-flight requests.
|
||||||
|
///
|
||||||
|
/// A request is in-flight from the time the request is received until the
|
||||||
|
/// response future completes. This includes the time spent in the next
|
||||||
|
/// layers.
|
||||||
|
///
|
||||||
|
/// This wraps the inner service with an instance of the
|
||||||
|
/// [`ConcurrencyLimit`] middleware.
|
||||||
|
///
|
||||||
|
/// [`ConcurrencyLimit`]: crate::limit::concurrency
|
||||||
|
#[cfg(feature = "limit")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "limit")))]
|
||||||
|
pub fn concurrency_limit(
|
||||||
|
self,
|
||||||
|
max: usize,
|
||||||
|
) -> ServiceBuilder<Stack<crate::limit::ConcurrencyLimitLayer, L>> {
|
||||||
|
self.layer(crate::limit::ConcurrencyLimitLayer::new(max))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Drop requests when the next layer is unable to respond to requests.
|
||||||
|
///
|
||||||
|
/// Usually, when a service or middleware does not have capacity to process a
|
||||||
|
/// request (i.e., [`poll_ready`] returns [`Pending`]), the caller waits until
|
||||||
|
/// capacity becomes available.
|
||||||
|
///
|
||||||
|
/// [`LoadShed`] immediately responds with an error when the next layer is
|
||||||
|
/// out of capacity.
|
||||||
|
///
|
||||||
|
/// This wraps the inner service with an instance of the [`LoadShed`]
|
||||||
|
/// middleware.
|
||||||
|
///
|
||||||
|
/// [`LoadShed`]: crate::load_shed
|
||||||
|
/// [`poll_ready`]: crate::Service::poll_ready
|
||||||
|
/// [`Pending`]: std::task::Poll::Pending
|
||||||
|
#[cfg(feature = "load-shed")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "load-shed")))]
|
||||||
|
pub fn load_shed(self) -> ServiceBuilder<Stack<crate::load_shed::LoadShedLayer, L>> {
|
||||||
|
self.layer(crate::load_shed::LoadShedLayer::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Limit requests to at most `num` per the given duration.
|
||||||
|
///
|
||||||
|
/// This wraps the inner service with an instance of the [`RateLimit`]
|
||||||
|
/// middleware.
|
||||||
|
///
|
||||||
|
/// [`RateLimit`]: crate::limit::rate
|
||||||
|
#[cfg(feature = "limit")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "limit")))]
|
||||||
|
pub fn rate_limit(
|
||||||
|
self,
|
||||||
|
num: u64,
|
||||||
|
per: std::time::Duration,
|
||||||
|
) -> ServiceBuilder<Stack<crate::limit::RateLimitLayer, L>> {
|
||||||
|
self.layer(crate::limit::RateLimitLayer::new(num, per))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retry failed requests according to the given [retry policy][policy].
|
||||||
|
///
|
||||||
|
/// `policy` determines which failed requests will be retried. It must
|
||||||
|
/// implement the [`retry::Policy`][policy] trait.
|
||||||
|
///
|
||||||
|
/// This wraps the inner service with an instance of the [`Retry`]
|
||||||
|
/// middleware.
|
||||||
|
///
|
||||||
|
/// [`Retry`]: crate::retry
|
||||||
|
/// [policy]: crate::retry::Policy
|
||||||
|
#[cfg(feature = "retry")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "retry")))]
|
||||||
|
pub fn retry<P>(self, policy: P) -> ServiceBuilder<Stack<crate::retry::RetryLayer<P>, L>> {
|
||||||
|
self.layer(crate::retry::RetryLayer::new(policy))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fail requests that take longer than `timeout`.
|
||||||
|
///
|
||||||
|
/// If the next layer takes more than `timeout` to respond to a request,
|
||||||
|
/// processing is terminated and an error is returned.
|
||||||
|
///
|
||||||
|
/// This wraps the inner service with an instance of the [`timeout`]
|
||||||
|
/// middleware.
|
||||||
|
///
|
||||||
|
/// [`timeout`]: crate::timeout
|
||||||
|
#[cfg(feature = "timeout")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "timeout")))]
|
||||||
|
pub fn timeout(
|
||||||
|
self,
|
||||||
|
timeout: std::time::Duration,
|
||||||
|
) -> ServiceBuilder<Stack<crate::timeout::TimeoutLayer, L>> {
|
||||||
|
self.layer(crate::timeout::TimeoutLayer::new(timeout))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Conditionally reject requests based on `predicate`.
|
||||||
|
///
|
||||||
|
/// `predicate` must implement the [`Predicate`] trait.
|
||||||
|
///
|
||||||
|
/// This wraps the inner service with an instance of the [`Filter`]
|
||||||
|
/// middleware.
|
||||||
|
///
|
||||||
|
/// [`Filter`]: crate::filter
|
||||||
|
/// [`Predicate`]: crate::filter::Predicate
|
||||||
|
#[cfg(feature = "filter")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "filter")))]
|
||||||
|
pub fn filter<P>(
|
||||||
|
self,
|
||||||
|
predicate: P,
|
||||||
|
) -> ServiceBuilder<Stack<crate::filter::FilterLayer<P>, L>> {
|
||||||
|
self.layer(crate::filter::FilterLayer::new(predicate))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Conditionally reject requests based on an asynchronous `predicate`.
|
||||||
|
///
|
||||||
|
/// `predicate` must implement the [`AsyncPredicate`] trait.
|
||||||
|
///
|
||||||
|
/// This wraps the inner service with an instance of the [`AsyncFilter`]
|
||||||
|
/// middleware.
|
||||||
|
///
|
||||||
|
/// [`AsyncFilter`]: crate::filter::AsyncFilter
|
||||||
|
/// [`AsyncPredicate`]: crate::filter::AsyncPredicate
|
||||||
|
#[cfg(feature = "filter")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "filter")))]
|
||||||
|
pub fn filter_async<P>(
|
||||||
|
self,
|
||||||
|
predicate: P,
|
||||||
|
) -> ServiceBuilder<Stack<crate::filter::AsyncFilterLayer<P>, L>> {
|
||||||
|
self.layer(crate::filter::AsyncFilterLayer::new(predicate))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Map one request type to another.
|
||||||
|
///
|
||||||
|
/// This wraps the inner service with an instance of the [`MapRequest`]
|
||||||
|
/// middleware.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// Changing the type of a request:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use tower::ServiceBuilder;
|
||||||
|
/// use tower::ServiceExt;
|
||||||
|
///
|
||||||
|
/// # #[tokio::main]
|
||||||
|
/// # async fn main() -> Result<(), ()> {
|
||||||
|
/// // Suppose we have some `Service` whose request type is `String`:
|
||||||
|
/// let string_svc = tower::service_fn(|request: String| async move {
|
||||||
|
/// println!("request: {}", request);
|
||||||
|
/// Ok(())
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// // ...but we want to call that service with a `usize`. What do we do?
|
||||||
|
///
|
||||||
|
/// let usize_svc = ServiceBuilder::new()
|
||||||
|
/// // Add a middlware that converts the request type to a `String`:
|
||||||
|
/// .map_request(|request: usize| format!("{}", request))
|
||||||
|
/// // ...and wrap the string service with that middleware:
|
||||||
|
/// .service(string_svc);
|
||||||
|
///
|
||||||
|
/// // Now, we can call that service with a `usize`:
|
||||||
|
/// usize_svc.oneshot(42).await?;
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Modifying the request value:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use tower::ServiceBuilder;
|
||||||
|
/// use tower::ServiceExt;
|
||||||
|
///
|
||||||
|
/// # #[tokio::main]
|
||||||
|
/// # async fn main() -> Result<(), ()> {
|
||||||
|
/// // A service that takes a number and returns it:
|
||||||
|
/// let svc = tower::service_fn(|request: usize| async move {
|
||||||
|
/// Ok(request)
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// let svc = ServiceBuilder::new()
|
||||||
|
/// // Add a middleware that adds 1 to each request
|
||||||
|
/// .map_request(|request: usize| request + 1)
|
||||||
|
/// .service(svc);
|
||||||
|
///
|
||||||
|
/// let response = svc.oneshot(1).await?;
|
||||||
|
/// assert_eq!(response, 2);
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`MapRequest`]: crate::util::MapRequest
|
||||||
|
#[cfg(feature = "util")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "util")))]
|
||||||
|
pub fn map_request<F, R1, R2>(
|
||||||
|
self,
|
||||||
|
f: F,
|
||||||
|
) -> ServiceBuilder<Stack<crate::util::MapRequestLayer<F>, L>>
|
||||||
|
where
|
||||||
|
F: FnMut(R1) -> R2 + Clone,
|
||||||
|
{
|
||||||
|
self.layer(crate::util::MapRequestLayer::new(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Map one response type to another.
|
||||||
|
///
|
||||||
|
/// This wraps the inner service with an instance of the [`MapResponse`]
|
||||||
|
/// middleware.
|
||||||
|
///
|
||||||
|
/// See the documentation for the [`map_response` combinator] for details.
|
||||||
|
///
|
||||||
|
/// [`MapResponse`]: crate::util::MapResponse
|
||||||
|
/// [`map_response` combinator]: crate::util::ServiceExt::map_response
|
||||||
|
#[cfg(feature = "util")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "util")))]
|
||||||
|
pub fn map_response<F>(
|
||||||
|
self,
|
||||||
|
f: F,
|
||||||
|
) -> ServiceBuilder<Stack<crate::util::MapResponseLayer<F>, L>> {
|
||||||
|
self.layer(crate::util::MapResponseLayer::new(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Map one error type to another.
|
||||||
|
///
|
||||||
|
/// This wraps the inner service with an instance of the [`MapErr`]
|
||||||
|
/// middleware.
|
||||||
|
///
|
||||||
|
/// See the documentation for the [`map_err` combinator] for details.
|
||||||
|
///
|
||||||
|
/// [`MapErr`]: crate::util::MapErr
|
||||||
|
/// [`map_err` combinator]: crate::util::ServiceExt::map_err
|
||||||
|
#[cfg(feature = "util")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "util")))]
|
||||||
|
pub fn map_err<F>(self, f: F) -> ServiceBuilder<Stack<crate::util::MapErrLayer<F>, L>> {
|
||||||
|
self.layer(crate::util::MapErrLayer::new(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Composes a function that transforms futures produced by the service.
|
||||||
|
///
|
||||||
|
/// This wraps the inner service with an instance of the [`MapFutureLayer`] middleware.
|
||||||
|
///
|
||||||
|
/// See the documentation for the [`map_future`] combinator for details.
|
||||||
|
///
|
||||||
|
/// [`MapFutureLayer`]: crate::util::MapFutureLayer
|
||||||
|
/// [`map_future`]: crate::util::ServiceExt::map_future
|
||||||
|
#[cfg(feature = "util")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "util")))]
|
||||||
|
pub fn map_future<F>(self, f: F) -> ServiceBuilder<Stack<crate::util::MapFutureLayer<F>, L>> {
|
||||||
|
self.layer(crate::util::MapFutureLayer::new(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Apply an asynchronous function after the service, regardless of whether the future
|
||||||
|
/// succeeds or fails.
|
||||||
|
///
|
||||||
|
/// This wraps the inner service with an instance of the [`Then`]
|
||||||
|
/// middleware.
|
||||||
|
///
|
||||||
|
/// This is similar to the [`map_response`] and [`map_err`] functions,
|
||||||
|
/// except that the *same* function is invoked when the service's future
|
||||||
|
/// completes, whether it completes successfully or fails. This function
|
||||||
|
/// takes the [`Result`] returned by the service's future, and returns a
|
||||||
|
/// [`Result`].
|
||||||
|
///
|
||||||
|
/// See the documentation for the [`then` combinator] for details.
|
||||||
|
///
|
||||||
|
/// [`Then`]: crate::util::Then
|
||||||
|
/// [`then` combinator]: crate::util::ServiceExt::then
|
||||||
|
/// [`map_response`]: ServiceBuilder::map_response
|
||||||
|
/// [`map_err`]: ServiceBuilder::map_err
|
||||||
|
#[cfg(feature = "util")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "util")))]
|
||||||
|
pub fn then<F>(self, f: F) -> ServiceBuilder<Stack<crate::util::ThenLayer<F>, L>> {
|
||||||
|
self.layer(crate::util::ThenLayer::new(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes a new future after this service's future resolves. This does
|
||||||
|
/// not alter the behaviour of the [`poll_ready`] method.
|
||||||
|
///
|
||||||
|
/// This method can be used to change the [`Response`] type of the service
|
||||||
|
/// into a different type. You can use this method to chain along a computation once the
|
||||||
|
/// service's response has been resolved.
|
||||||
|
///
|
||||||
|
/// This wraps the inner service with an instance of the [`AndThen`]
|
||||||
|
/// middleware.
|
||||||
|
///
|
||||||
|
/// See the documentation for the [`and_then` combinator] for details.
|
||||||
|
///
|
||||||
|
/// [`Response`]: crate::Service::Response
|
||||||
|
/// [`poll_ready`]: crate::Service::poll_ready
|
||||||
|
/// [`and_then` combinator]: crate::util::ServiceExt::and_then
|
||||||
|
/// [`AndThen`]: crate::util::AndThen
|
||||||
|
#[cfg(feature = "util")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "util")))]
|
||||||
|
pub fn and_then<F>(self, f: F) -> ServiceBuilder<Stack<crate::util::AndThenLayer<F>, L>> {
|
||||||
|
self.layer(crate::util::AndThenLayer::new(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Maps this service's result type (`Result<Self::Response, Self::Error>`)
|
||||||
|
/// to a different value, regardless of whether the future succeeds or
|
||||||
|
/// fails.
|
||||||
|
///
|
||||||
|
/// This wraps the inner service with an instance of the [`MapResult`]
|
||||||
|
/// middleware.
|
||||||
|
///
|
||||||
|
/// See the documentation for the [`map_result` combinator] for details.
|
||||||
|
///
|
||||||
|
/// [`map_result` combinator]: crate::util::ServiceExt::map_result
|
||||||
|
/// [`MapResult`]: crate::util::MapResult
|
||||||
|
#[cfg(feature = "util")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "util")))]
|
||||||
|
pub fn map_result<F>(self, f: F) -> ServiceBuilder<Stack<crate::util::MapResultLayer<F>, L>> {
|
||||||
|
self.layer(crate::util::MapResultLayer::new(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the underlying `Layer` implementation.
|
||||||
|
pub fn into_inner(self) -> L {
|
||||||
|
self.layer
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrap the service `S` with the middleware provided by this
|
||||||
|
/// [`ServiceBuilder`]'s [`Layer`]'s, returning a new [`Service`].
|
||||||
|
///
|
||||||
|
/// [`Layer`]: crate::Layer
|
||||||
|
/// [`Service`]: crate::Service
|
||||||
|
pub fn service<S>(&self, service: S) -> L::Service
|
||||||
|
where
|
||||||
|
L: Layer<S>,
|
||||||
|
{
|
||||||
|
self.layer.layer(service)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wrap the async function `F` with the middleware provided by this [`ServiceBuilder`]'s
|
||||||
|
/// [`Layer`]s, returning a new [`Service`].
|
||||||
|
///
|
||||||
|
/// This is a convenience method which is equivalent to calling
|
||||||
|
/// [`ServiceBuilder::service`] with a [`service_fn`], like this:
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use tower::{ServiceBuilder, service_fn};
|
||||||
|
/// # async fn handler_fn(_: ()) -> Result<(), ()> { Ok(()) }
|
||||||
|
/// # let _ = {
|
||||||
|
/// ServiceBuilder::new()
|
||||||
|
/// // ...
|
||||||
|
/// .service(service_fn(handler_fn))
|
||||||
|
/// # };
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use std::time::Duration;
|
||||||
|
/// use tower::{ServiceBuilder, ServiceExt, BoxError, service_fn};
|
||||||
|
///
|
||||||
|
/// # #[tokio::main]
|
||||||
|
/// # async fn main() -> Result<(), BoxError> {
|
||||||
|
/// async fn handle(request: &'static str) -> Result<&'static str, BoxError> {
|
||||||
|
/// Ok(request)
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// let svc = ServiceBuilder::new()
|
||||||
|
/// .buffer(1024)
|
||||||
|
/// .timeout(Duration::from_secs(10))
|
||||||
|
/// .service_fn(handle);
|
||||||
|
///
|
||||||
|
/// let response = svc.oneshot("foo").await?;
|
||||||
|
///
|
||||||
|
/// assert_eq!(response, "foo");
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`Layer`]: crate::Layer
|
||||||
|
/// [`Service`]: crate::Service
|
||||||
|
/// [`service_fn`]: crate::service_fn
|
||||||
|
#[cfg(feature = "util")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "util")))]
|
||||||
|
pub fn service_fn<F>(self, f: F) -> L::Service
|
||||||
|
where
|
||||||
|
L: Layer<crate::util::ServiceFn<F>>,
|
||||||
|
{
|
||||||
|
self.service(crate::util::service_fn(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check that the builder implements `Clone`.
|
||||||
|
///
|
||||||
|
/// This can be useful when debugging type errors in `ServiceBuilder`s with lots of layers.
|
||||||
|
///
|
||||||
|
/// Doesn't actually change the builder but serves as a type check.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use tower::ServiceBuilder;
|
||||||
|
///
|
||||||
|
/// let builder = ServiceBuilder::new()
|
||||||
|
/// // Do something before processing the request
|
||||||
|
/// .map_request(|request: String| {
|
||||||
|
/// println!("got request!");
|
||||||
|
/// request
|
||||||
|
/// })
|
||||||
|
/// // Ensure our `ServiceBuilder` can be cloned
|
||||||
|
/// .check_clone()
|
||||||
|
/// // Do something after processing the request
|
||||||
|
/// .map_response(|response: String| {
|
||||||
|
/// println!("got response!");
|
||||||
|
/// response
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn check_clone(self) -> Self
|
||||||
|
where
|
||||||
|
Self: Clone,
|
||||||
|
{
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check that the builder when given a service of type `S` produces a service that implements
|
||||||
|
/// `Clone`.
|
||||||
|
///
|
||||||
|
/// This can be useful when debugging type errors in `ServiceBuilder`s with lots of layers.
|
||||||
|
///
|
||||||
|
/// Doesn't actually change the builder but serves as a type check.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use tower::ServiceBuilder;
|
||||||
|
///
|
||||||
|
/// # #[derive(Clone)]
|
||||||
|
/// # struct MyService;
|
||||||
|
/// #
|
||||||
|
/// let builder = ServiceBuilder::new()
|
||||||
|
/// // Do something before processing the request
|
||||||
|
/// .map_request(|request: String| {
|
||||||
|
/// println!("got request!");
|
||||||
|
/// request
|
||||||
|
/// })
|
||||||
|
/// // Ensure that the service produced when given a `MyService` implements
|
||||||
|
/// .check_service_clone::<MyService>()
|
||||||
|
/// // Do something after processing the request
|
||||||
|
/// .map_response(|response: String| {
|
||||||
|
/// println!("got response!");
|
||||||
|
/// response
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn check_service_clone<S>(self) -> Self
|
||||||
|
where
|
||||||
|
L: Layer<S>,
|
||||||
|
L::Service: Clone,
|
||||||
|
{
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check that the builder when given a service of type `S` produces a service with the given
|
||||||
|
/// request, response, and error types.
|
||||||
|
///
|
||||||
|
/// This can be useful when debugging type errors in `ServiceBuilder`s with lots of layers.
|
||||||
|
///
|
||||||
|
/// Doesn't actually change the builder but serves as a type check.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use tower::ServiceBuilder;
|
||||||
|
/// use std::task::{Poll, Context};
|
||||||
|
/// use tower::{Service, ServiceExt};
|
||||||
|
///
|
||||||
|
/// // An example service
|
||||||
|
/// struct MyService;
|
||||||
|
///
|
||||||
|
/// impl Service<Request> for MyService {
|
||||||
|
/// type Response = Response;
|
||||||
|
/// type Error = Error;
|
||||||
|
/// type Future = futures_util::future::Ready<Result<Response, Error>>;
|
||||||
|
///
|
||||||
|
/// fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
/// // ...
|
||||||
|
/// # todo!()
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn call(&mut self, request: Request) -> Self::Future {
|
||||||
|
/// // ...
|
||||||
|
/// # todo!()
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// struct Request;
|
||||||
|
/// struct Response;
|
||||||
|
/// struct Error;
|
||||||
|
///
|
||||||
|
/// struct WrappedResponse(Response);
|
||||||
|
///
|
||||||
|
/// let builder = ServiceBuilder::new()
|
||||||
|
/// // At this point in the builder if given a `MyService` it produces a service that
|
||||||
|
/// // accepts `Request`s, produces `Response`s, and fails with `Error`s
|
||||||
|
/// .check_service::<MyService, Request, Response, Error>()
|
||||||
|
/// // Wrap responses in `WrappedResponse`
|
||||||
|
/// .map_response(|response: Response| WrappedResponse(response))
|
||||||
|
/// // Now the response type will be `WrappedResponse`
|
||||||
|
/// .check_service::<MyService, _, WrappedResponse, _>();
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn check_service<S, T, U, E>(self) -> Self
|
||||||
|
where
|
||||||
|
L: Layer<S>,
|
||||||
|
L::Service: Service<T, Response = U, Error = E>,
|
||||||
|
{
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This wraps the inner service with the [`Layer`] returned by [`BoxService::layer()`].
|
||||||
|
///
|
||||||
|
/// See that method for more details.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use tower::{Service, ServiceBuilder, BoxError, util::BoxService};
|
||||||
|
/// use std::time::Duration;
|
||||||
|
/// #
|
||||||
|
/// # struct Request;
|
||||||
|
/// # struct Response;
|
||||||
|
/// # impl Response {
|
||||||
|
/// # fn new() -> Self { Self }
|
||||||
|
/// # }
|
||||||
|
///
|
||||||
|
/// let service: BoxService<Request, Response, BoxError> = ServiceBuilder::new()
|
||||||
|
/// .boxed()
|
||||||
|
/// .load_shed()
|
||||||
|
/// .concurrency_limit(64)
|
||||||
|
/// .timeout(Duration::from_secs(10))
|
||||||
|
/// .service_fn(|req: Request| async {
|
||||||
|
/// Ok::<_, BoxError>(Response::new())
|
||||||
|
/// });
|
||||||
|
/// # let service = assert_service(service);
|
||||||
|
/// # fn assert_service<S, R>(svc: S) -> S
|
||||||
|
/// # where S: Service<R> { svc }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`BoxService::layer()`]: crate::util::BoxService::layer()
|
||||||
|
#[cfg(feature = "util")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "util")))]
|
||||||
|
pub fn boxed<S, R>(
|
||||||
|
self,
|
||||||
|
) -> ServiceBuilder<
|
||||||
|
Stack<
|
||||||
|
tower_layer::LayerFn<
|
||||||
|
fn(
|
||||||
|
L::Service,
|
||||||
|
) -> crate::util::BoxService<
|
||||||
|
R,
|
||||||
|
<L::Service as Service<R>>::Response,
|
||||||
|
<L::Service as Service<R>>::Error,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
L,
|
||||||
|
>,
|
||||||
|
>
|
||||||
|
where
|
||||||
|
L: Layer<S>,
|
||||||
|
L::Service: Service<R> + Send + 'static,
|
||||||
|
<L::Service as Service<R>>::Future: Send + 'static,
|
||||||
|
{
|
||||||
|
self.layer(crate::util::BoxService::layer())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This wraps the inner service with the [`Layer`] returned by [`BoxCloneService::layer()`].
|
||||||
|
///
|
||||||
|
/// This is similar to the [`boxed`] method, but it requires that `Self` implement
|
||||||
|
/// [`Clone`], and the returned boxed service implements [`Clone`].
|
||||||
|
///
|
||||||
|
/// See [`BoxCloneService`] for more details.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use tower::{Service, ServiceBuilder, BoxError, util::BoxCloneService};
|
||||||
|
/// use std::time::Duration;
|
||||||
|
/// #
|
||||||
|
/// # struct Request;
|
||||||
|
/// # struct Response;
|
||||||
|
/// # impl Response {
|
||||||
|
/// # fn new() -> Self { Self }
|
||||||
|
/// # }
|
||||||
|
///
|
||||||
|
/// let service: BoxCloneService<Request, Response, BoxError> = ServiceBuilder::new()
|
||||||
|
/// .boxed_clone()
|
||||||
|
/// .load_shed()
|
||||||
|
/// .concurrency_limit(64)
|
||||||
|
/// .timeout(Duration::from_secs(10))
|
||||||
|
/// .service_fn(|req: Request| async {
|
||||||
|
/// Ok::<_, BoxError>(Response::new())
|
||||||
|
/// });
|
||||||
|
/// # let service = assert_service(service);
|
||||||
|
///
|
||||||
|
/// // The boxed service can still be cloned.
|
||||||
|
/// service.clone();
|
||||||
|
/// # fn assert_service<S, R>(svc: S) -> S
|
||||||
|
/// # where S: Service<R> { svc }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`BoxCloneService::layer()`]: crate::util::BoxCloneService::layer()
|
||||||
|
/// [`BoxCloneService`]: crate::util::BoxCloneService
|
||||||
|
/// [`boxed`]: Self::boxed
|
||||||
|
#[cfg(feature = "util")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "util")))]
|
||||||
|
pub fn boxed_clone<S, R>(
|
||||||
|
self,
|
||||||
|
) -> ServiceBuilder<
|
||||||
|
Stack<
|
||||||
|
tower_layer::LayerFn<
|
||||||
|
fn(
|
||||||
|
L::Service,
|
||||||
|
) -> crate::util::BoxCloneService<
|
||||||
|
R,
|
||||||
|
<L::Service as Service<R>>::Response,
|
||||||
|
<L::Service as Service<R>>::Error,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
L,
|
||||||
|
>,
|
||||||
|
>
|
||||||
|
where
|
||||||
|
L: Layer<S>,
|
||||||
|
L::Service: Service<R> + Clone + Send + 'static,
|
||||||
|
<L::Service as Service<R>>::Future: Send + 'static,
|
||||||
|
{
|
||||||
|
self.layer(crate::util::BoxCloneService::layer())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L: fmt::Debug> fmt::Debug for ServiceBuilder<L> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_tuple("ServiceBuilder").field(&self.layer).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, L> Layer<S> for ServiceBuilder<L>
|
||||||
|
where
|
||||||
|
L: Layer<S>,
|
||||||
|
{
|
||||||
|
type Service = L::Service;
|
||||||
|
|
||||||
|
fn layer(&self, inner: S) -> Self::Service {
|
||||||
|
self.layer.layer(inner)
|
||||||
|
}
|
||||||
|
}
|
||||||
12
tower/tower/src/discover/error.rs
Normal file
12
tower/tower/src/discover/error.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
use std::{error::Error, fmt};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Never {}
|
||||||
|
|
||||||
|
impl fmt::Display for Never {
|
||||||
|
fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match *self {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for Never {}
|
||||||
61
tower/tower/src/discover/list.rs
Normal file
61
tower/tower/src/discover/list.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
use super::{error::Never, Change};
|
||||||
|
use futures_core::Stream;
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
use std::iter::{Enumerate, IntoIterator};
|
||||||
|
use std::{
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
use tower_service::Service;
|
||||||
|
|
||||||
|
pin_project! {
|
||||||
|
/// Static service discovery based on a predetermined list of services.
|
||||||
|
///
|
||||||
|
/// [`ServiceList`] is created with an initial list of services. The discovery
|
||||||
|
/// process will yield this list once and do nothing after.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ServiceList<T>
|
||||||
|
where
|
||||||
|
T: IntoIterator,
|
||||||
|
{
|
||||||
|
inner: Enumerate<T::IntoIter>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> ServiceList<T>
|
||||||
|
where
|
||||||
|
T: IntoIterator<Item = U>,
|
||||||
|
{
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub fn new<Request>(services: T) -> ServiceList<T>
|
||||||
|
where
|
||||||
|
U: Service<Request>,
|
||||||
|
{
|
||||||
|
ServiceList {
|
||||||
|
inner: services.into_iter().enumerate(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> Stream for ServiceList<T>
|
||||||
|
where
|
||||||
|
T: IntoIterator<Item = U>,
|
||||||
|
{
|
||||||
|
type Item = Result<Change<usize, U>, Never>;
|
||||||
|
|
||||||
|
fn poll_next(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
|
match self.project().inner.next() {
|
||||||
|
Some((i, service)) => Poll::Ready(Some(Ok(Change::Insert(i, service)))),
|
||||||
|
None => Poll::Ready(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that List can be directly over collections
|
||||||
|
#[cfg(test)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
type ListVecTest<T> = ServiceList<Vec<T>>;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[allow(dead_code)]
|
||||||
|
type ListVecIterTest<T> = ServiceList<::std::vec::IntoIter<T>>;
|
||||||
52
tower/tower/src/discover/mod.rs
Normal file
52
tower/tower/src/discover/mod.rs
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
mod error;
|
||||||
|
mod list;
|
||||||
|
|
||||||
|
pub use self::list::ServiceList;
|
||||||
|
|
||||||
|
use crate::sealed::Sealed;
|
||||||
|
use futures_core::TryStream;
|
||||||
|
use std::{
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub trait Discover {
|
||||||
|
type Key: Eq;
|
||||||
|
type Service;
|
||||||
|
type Error;
|
||||||
|
fn poll_discover(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> (Poll<Option<Result<Change<Self::Key, Self::Service>, Self::Error>>>);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K, S, E, D: ?Sized> Sealed<Change<(), ()>> for D
|
||||||
|
where
|
||||||
|
D: TryStream<Ok = Change<K, S>, Error = E>,
|
||||||
|
K: Eq,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K, S, E, D: ?Sized> Discover for D
|
||||||
|
where
|
||||||
|
D: TryStream<Ok = Change<K, S>, Error = E>,
|
||||||
|
K: Eq,
|
||||||
|
{
|
||||||
|
type Key = K;
|
||||||
|
type Service = S;
|
||||||
|
type Error = E;
|
||||||
|
|
||||||
|
fn poll_discover(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Option<Result<D::Ok, D::Error>>> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A change in the service set.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Change<K, V> {
|
||||||
|
Insert(K, V),
|
||||||
|
Remove(K),
|
||||||
|
}
|
||||||
14
tower/tower/src/layer.rs
Normal file
14
tower/tower/src/layer.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
//! A collection of [`Layer`] based tower services
|
||||||
|
//!
|
||||||
|
//! [`Layer`]: crate::Layer
|
||||||
|
|
||||||
|
pub use tower_layer::{layer_fn, Layer, LayerFn};
|
||||||
|
|
||||||
|
/// Utilities for combining layers
|
||||||
|
///
|
||||||
|
/// [`Identity`]: crate::layer::util::Identity
|
||||||
|
/// [`Layer`]: crate::Layer
|
||||||
|
/// [`Stack`]: crate::layer::util::Stack
|
||||||
|
pub mod util {
|
||||||
|
pub use tower_layer::{Identity, Stack};
|
||||||
|
}
|
||||||
51
tower/tower/src/lib.rs
Normal file
51
tower/tower/src/lib.rs
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
#![warn(
|
||||||
|
missing_debug_implementations,
|
||||||
|
missing_docs,
|
||||||
|
rust_2018_idioms,
|
||||||
|
unreachable_pub
|
||||||
|
)]
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
#![allow(elided_lifetimes_in_paths, clippy::type_complexity)]
|
||||||
|
#![cfg_attr(test, allow(clippy::float_cmp))]
|
||||||
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
|
|
||||||
|
#![allow(warnings)]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
pub(crate) mod macros;
|
||||||
|
#[cfg(feature = "balance")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "balance")))]
|
||||||
|
pub mod balance;
|
||||||
|
|
||||||
|
#[cfg(feature = "discover")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "discover")))]
|
||||||
|
pub mod discover;
|
||||||
|
|
||||||
|
#[cfg(feature = "load")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "load")))]
|
||||||
|
pub mod load;
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(feature = "make")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "make")))]
|
||||||
|
pub mod make;
|
||||||
|
|
||||||
|
|
||||||
|
pub mod builder;
|
||||||
|
pub mod layer;
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use crate::builder::ServiceBuilder;
|
||||||
|
#[cfg(feature = "make")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "make")))]
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use crate::make::MakeService;
|
||||||
|
|
||||||
|
|
||||||
|
#[allow(unreachable_pub)]
|
||||||
|
mod sealed {
|
||||||
|
pub trait Sealed<T> {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Alias for a type-erased error type.
|
||||||
|
pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
|
||||||
95
tower/tower/src/load/completion.rs
Normal file
95
tower/tower/src/load/completion.rs
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
//! Application-specific request completion semantics.
|
||||||
|
|
||||||
|
use futures_core::ready;
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
use std::{
|
||||||
|
future::Future,
|
||||||
|
pin::Pin,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Attaches `H`-typed completion tracker to `V` typed values.
|
||||||
|
///
|
||||||
|
/// Handles (of type `H`) are intended to be RAII guards that primarily implement [`Drop`] and update
|
||||||
|
/// load metric state as they are dropped. This trait allows implementors to "forward" the handle
|
||||||
|
/// to later parts of the request-handling pipeline, so that the handle is only dropped when the
|
||||||
|
/// request has truly completed.
|
||||||
|
///
|
||||||
|
/// This utility allows load metrics to have a protocol-agnostic means to track streams past their
|
||||||
|
/// initial response future. For example, if `V` represents an HTTP response type, an
|
||||||
|
/// implementation could add `H`-typed handles to each response's extensions to detect when all the
|
||||||
|
/// response's extensions have been dropped.
|
||||||
|
///
|
||||||
|
/// A base `impl<H, V> TrackCompletion<H, V> for CompleteOnResponse` is provided to drop the handle
|
||||||
|
/// once the response future is resolved. This is appropriate when a response is discrete and
|
||||||
|
/// cannot comprise multiple messages.
|
||||||
|
///
|
||||||
|
/// In many cases, the `Output` type is simply `V`. However, [`TrackCompletion`] may alter the type
|
||||||
|
/// in order to instrument it appropriately. For example, an HTTP [`TrackCompletion`] may modify
|
||||||
|
/// the body type: so a [`TrackCompletion`] that takes values of type
|
||||||
|
/// [`http::Response<A>`][response] may output values of type [`http::Response<B>`][response].
|
||||||
|
///
|
||||||
|
/// [response]: https://docs.rs/http/latest/http/response/struct.Response.html
|
||||||
|
pub trait TrackCompletion<H, V>: Clone {
|
||||||
|
/// The instrumented value type.
|
||||||
|
type Output;
|
||||||
|
|
||||||
|
/// Attaches a `H`-typed handle to a `V`-typed value.
|
||||||
|
fn track_completion(&self, handle: H, value: V) -> Self::Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A [`TrackCompletion`] implementation that considers the request completed when the response
|
||||||
|
/// future is resolved.
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct CompleteOnResponse;
|
||||||
|
|
||||||
|
pin_project! {
|
||||||
|
/// Attaches a `C`-typed completion tracker to the result of an `F`-typed [`Future`].
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TrackCompletionFuture<F, C, H> {
|
||||||
|
#[pin]
|
||||||
|
future: F,
|
||||||
|
handle: Option<H>,
|
||||||
|
completion: C,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl InstrumentFuture =====
|
||||||
|
|
||||||
|
impl<F, C, H> TrackCompletionFuture<F, C, H> {
|
||||||
|
/// Wraps a future, propagating the tracker into its value if successful.
|
||||||
|
pub fn new(completion: C, handle: H, future: F) -> Self {
|
||||||
|
TrackCompletionFuture {
|
||||||
|
future,
|
||||||
|
completion,
|
||||||
|
handle: Some(handle),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, C, H, T, E> Future for TrackCompletionFuture<F, C, H>
|
||||||
|
where
|
||||||
|
F: Future<Output = Result<T, E>>,
|
||||||
|
C: TrackCompletion<H, T>,
|
||||||
|
{
|
||||||
|
type Output = Result<C::Output, E>;
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let this = self.project();
|
||||||
|
let rsp = ready!(this.future.poll(cx))?;
|
||||||
|
let h = this.handle.take().expect("handle");
|
||||||
|
Poll::Ready(Ok(this.completion.track_completion(h, rsp)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== CompleteOnResponse =====
|
||||||
|
|
||||||
|
impl<H, V> TrackCompletion<H, V> for CompleteOnResponse {
|
||||||
|
type Output = V;
|
||||||
|
|
||||||
|
fn track_completion(&self, handle: H, value: V) -> V {
|
||||||
|
drop(handle);
|
||||||
|
value
|
||||||
|
}
|
||||||
|
}
|
||||||
80
tower/tower/src/load/constant.rs
Normal file
80
tower/tower/src/load/constant.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
//! A constant [`Load`] implementation.
|
||||||
|
|
||||||
|
#[cfg(feature = "discover")]
|
||||||
|
use crate::discover::{Change, Discover};
|
||||||
|
#[cfg(feature = "discover")]
|
||||||
|
use futures_core::{ready, Stream};
|
||||||
|
#[cfg(feature = "discover")]
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
use super::Load;
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
use tower_service::Service;
|
||||||
|
|
||||||
|
pin_project! {
|
||||||
|
#[derive(Debug)]
|
||||||
|
/// Wraps a type so that it implements [`Load`] and returns a constant load metric.
|
||||||
|
///
|
||||||
|
/// This load estimator is primarily useful for testing.
|
||||||
|
pub struct Constant<T, M> {
|
||||||
|
inner: T,
|
||||||
|
load: M,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl Constant =====
|
||||||
|
|
||||||
|
impl<T, M: Copy> Constant<T, M> {
|
||||||
|
/// Wraps a `T`-typed service with a constant `M`-typed load metric.
|
||||||
|
pub fn new(inner: T, load: M) -> Self {
|
||||||
|
Self { inner, load }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, M: Copy + PartialOrd> Load for Constant<T, M> {
|
||||||
|
type Metric = M;
|
||||||
|
|
||||||
|
fn load(&self) -> M {
|
||||||
|
self.load
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, M, Request> Service<Request> for Constant<S, M>
|
||||||
|
where
|
||||||
|
S: Service<Request>,
|
||||||
|
M: Copy,
|
||||||
|
{
|
||||||
|
type Response = S::Response;
|
||||||
|
type Error = S::Error;
|
||||||
|
type Future = S::Future;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.inner.poll_ready(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: Request) -> Self::Future {
|
||||||
|
self.inner.call(req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Proxies [`Discover`] such that all changes are wrapped with a constant load.
|
||||||
|
#[cfg(feature = "discover")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "discover")))]
|
||||||
|
impl<D: Discover + Unpin, M: Copy> Stream for Constant<D, M> {
|
||||||
|
type Item = Result<Change<D::Key, Constant<D::Service, M>>, D::Error>;
|
||||||
|
|
||||||
|
/// Yields the next discovery change set.
|
||||||
|
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
|
use self::Change::*;
|
||||||
|
|
||||||
|
let this = self.project();
|
||||||
|
let change = match ready!(Pin::new(this.inner).poll_discover(cx)).transpose()? {
|
||||||
|
None => return Poll::Ready(None),
|
||||||
|
Some(Insert(k, svc)) => Insert(k, Constant::new(svc, *this.load)),
|
||||||
|
Some(Remove(k)) => Remove(k),
|
||||||
|
};
|
||||||
|
|
||||||
|
Poll::Ready(Some(Ok(change)))
|
||||||
|
}
|
||||||
|
}
|
||||||
89
tower/tower/src/load/mod.rs
Normal file
89
tower/tower/src/load/mod.rs
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
//! Service load measurement
|
||||||
|
//!
|
||||||
|
//! This module provides the [`Load`] trait, which allows measuring how loaded a service is.
|
||||||
|
//! It also provides several wrapper types that measure load in different ways:
|
||||||
|
//!
|
||||||
|
//! - [`Constant`] — Always returns the same constant load value for a service.
|
||||||
|
//! - [`PendingRequests`] — Measures load by tracking the number of in-flight requests.
|
||||||
|
//! - [`PeakEwma`] — Measures load using a moving average of the peak latency for the service.
|
||||||
|
//!
|
||||||
|
//! In general, you will want to use one of these when using the types in [`tower::balance`] which
|
||||||
|
//! balance services depending on their load. Which load metric to use depends on your exact
|
||||||
|
//! use-case, but the ones above should get you quite far!
|
||||||
|
//!
|
||||||
|
//! When the `discover` feature is enabled, wrapper types for [`Discover`] that
|
||||||
|
//! wrap the discovered services with the given load estimator are also provided.
|
||||||
|
//!
|
||||||
|
//! # When does a request complete?
|
||||||
|
//!
|
||||||
|
//! For many applications, the request life-cycle is relatively simple: when a service responds to
|
||||||
|
//! a request, that request is done, and the system can forget about it. However, for some
|
||||||
|
//! applications, the service may respond to the initial request while other parts of the system
|
||||||
|
//! are still acting on that request. In such an application, the system load must take these
|
||||||
|
//! requests into account as well, or risk the system underestimating its own load.
|
||||||
|
//!
|
||||||
|
//! To support these use-cases, the load estimators in this module are parameterized by the
|
||||||
|
//! [`TrackCompletion`] trait, with [`CompleteOnResponse`] as the default type. The behavior of
|
||||||
|
//! [`CompleteOnResponse`] is what you would normally expect for a request-response cycle: when the
|
||||||
|
//! response is produced, the request is considered "finished", and load goes down. This can be
|
||||||
|
//! overriden by your own user-defined type to track more complex request completion semantics. See
|
||||||
|
//! the documentation for [`completion`] for more details.
|
||||||
|
//!
|
||||||
|
//! # Examples
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! # #[cfg(feature = "util")]
|
||||||
|
//! use tower::util::ServiceExt;
|
||||||
|
//! # #[cfg(feature = "util")]
|
||||||
|
//! use tower::{load::Load, Service};
|
||||||
|
//! # #[cfg(feature = "util")]
|
||||||
|
//! async fn simple_balance<S1, S2, R>(
|
||||||
|
//! svc1: &mut S1,
|
||||||
|
//! svc2: &mut S2,
|
||||||
|
//! request: R
|
||||||
|
//! ) -> Result<S1::Response, S1::Error>
|
||||||
|
//! where
|
||||||
|
//! S1: Load + Service<R>,
|
||||||
|
//! S2: Load<Metric = S1::Metric> + Service<R, Response = S1::Response, Error = S1::Error>
|
||||||
|
//! {
|
||||||
|
//! if svc1.load() < svc2.load() {
|
||||||
|
//! svc1.ready().await?.call(request).await
|
||||||
|
//! } else {
|
||||||
|
//! svc2.ready().await?.call(request).await
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! [`tower::balance`]: crate::balance
|
||||||
|
//! [`Discover`]: crate::discover::Discover
|
||||||
|
//! [`CompleteOnResponse`]: crate::load::completion::CompleteOnResponse
|
||||||
|
// TODO: a custom completion example would be good here
|
||||||
|
|
||||||
|
pub mod completion;
|
||||||
|
mod constant;
|
||||||
|
pub mod peak_ewma;
|
||||||
|
pub mod pending_requests;
|
||||||
|
|
||||||
|
pub use self::{
|
||||||
|
completion::{CompleteOnResponse, TrackCompletion},
|
||||||
|
constant::Constant,
|
||||||
|
peak_ewma::PeakEwma,
|
||||||
|
pending_requests::PendingRequests,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "discover")]
|
||||||
|
pub use self::{peak_ewma::PeakEwmaDiscover, pending_requests::PendingRequestsDiscover};
|
||||||
|
|
||||||
|
/// Types that implement this trait can give an estimate of how loaded they are.
|
||||||
|
///
|
||||||
|
/// See the module documentation for more details.
|
||||||
|
pub trait Load {
|
||||||
|
/// A comparable load metric.
|
||||||
|
///
|
||||||
|
/// Lesser values indicate that the service is less loaded, and should be preferred for new
|
||||||
|
/// requests over another service with a higher value.
|
||||||
|
type Metric: PartialOrd;
|
||||||
|
|
||||||
|
/// Estimate the service's current load.
|
||||||
|
fn load(&self) -> Self::Metric;
|
||||||
|
}
|
||||||
407
tower/tower/src/load/peak_ewma.rs
Normal file
407
tower/tower/src/load/peak_ewma.rs
Normal file
|
|
@ -0,0 +1,407 @@
|
||||||
|
//! A `Load` implementation that measures load using the PeakEWMA response latency.
|
||||||
|
|
||||||
|
#[cfg(feature = "discover")]
|
||||||
|
use crate::discover::{Change, Discover};
|
||||||
|
#[cfg(feature = "discover")]
|
||||||
|
use futures_core::{ready, Stream};
|
||||||
|
#[cfg(feature = "discover")]
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
#[cfg(feature = "discover")]
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
use super::completion::{CompleteOnResponse, TrackCompletion, TrackCompletionFuture};
|
||||||
|
use super::Load;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
use std::{
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
|
use tokio::time::Instant;
|
||||||
|
use tower_service::Service;
|
||||||
|
use tracing::trace;
|
||||||
|
|
||||||
|
/// Measures the load of the underlying service using Peak-EWMA load measurement.
|
||||||
|
///
|
||||||
|
/// [`PeakEwma`] implements [`Load`] with the [`Cost`] metric that estimates the amount of
|
||||||
|
/// pending work to an endpoint. Work is calculated by multiplying the
|
||||||
|
/// exponentially-weighted moving average (EWMA) of response latencies by the number of
|
||||||
|
/// pending requests. The Peak-EWMA algorithm is designed to be especially sensitive to
|
||||||
|
/// worst-case latencies. Over time, the peak latency value decays towards the moving
|
||||||
|
/// average of latencies to the endpoint.
|
||||||
|
///
|
||||||
|
/// When no latency information has been measured for an endpoint, an arbitrary default
|
||||||
|
/// RTT of 1 second is used to prevent the endpoint from being overloaded before a
|
||||||
|
/// meaningful baseline can be established..
|
||||||
|
///
|
||||||
|
/// ## Note
|
||||||
|
///
|
||||||
|
/// This is derived from [Finagle][finagle], which is distributed under the Apache V2
|
||||||
|
/// license. Copyright 2017, Twitter Inc.
|
||||||
|
///
|
||||||
|
/// [finagle]:
|
||||||
|
/// https://github.com/twitter/finagle/blob/9cc08d15216497bb03a1cafda96b7266cfbbcff1/finagle-core/src/main/scala/com/twitter/finagle/loadbalancer/PeakEwma.scala
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PeakEwma<S, C = CompleteOnResponse> {
|
||||||
|
service: S,
|
||||||
|
decay_ns: f64,
|
||||||
|
rtt_estimate: Arc<Mutex<RttEstimate>>,
|
||||||
|
completion: C,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "discover")]
|
||||||
|
pin_project! {
|
||||||
|
/// Wraps a `D`-typed stream of discovered services with `PeakEwma`.
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "discover")))]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PeakEwmaDiscover<D, C = CompleteOnResponse> {
|
||||||
|
#[pin]
|
||||||
|
discover: D,
|
||||||
|
decay_ns: f64,
|
||||||
|
default_rtt: Duration,
|
||||||
|
completion: C,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the relative cost of communicating with a service.
|
||||||
|
///
|
||||||
|
/// The underlying value estimates the amount of pending work to a service: the Peak-EWMA
|
||||||
|
/// latency estimate multiplied by the number of pending requests.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
|
||||||
|
pub struct Cost(f64);
|
||||||
|
|
||||||
|
/// Tracks an in-flight request and updates the RTT-estimate on Drop.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Handle {
|
||||||
|
sent_at: Instant,
|
||||||
|
decay_ns: f64,
|
||||||
|
rtt_estimate: Arc<Mutex<RttEstimate>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Holds the current RTT estimate and the last time this value was updated.
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct RttEstimate {
|
||||||
|
update_at: Instant,
|
||||||
|
rtt_ns: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
const NANOS_PER_MILLI: f64 = 1_000_000.0;
|
||||||
|
|
||||||
|
// ===== impl PeakEwma =====
|
||||||
|
|
||||||
|
impl<S, C> PeakEwma<S, C> {
|
||||||
|
/// Wraps an `S`-typed service so that its load is tracked by the EWMA of its peak latency.
|
||||||
|
pub fn new(service: S, default_rtt: Duration, decay_ns: f64, completion: C) -> Self {
|
||||||
|
debug_assert!(decay_ns > 0.0, "decay_ns must be positive");
|
||||||
|
Self {
|
||||||
|
service,
|
||||||
|
decay_ns,
|
||||||
|
rtt_estimate: Arc::new(Mutex::new(RttEstimate::new(nanos(default_rtt)))),
|
||||||
|
completion,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle(&self) -> Handle {
|
||||||
|
Handle {
|
||||||
|
decay_ns: self.decay_ns,
|
||||||
|
sent_at: Instant::now(),
|
||||||
|
rtt_estimate: self.rtt_estimate.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, C, Request> Service<Request> for PeakEwma<S, C>
|
||||||
|
where
|
||||||
|
S: Service<Request>,
|
||||||
|
C: TrackCompletion<Handle, S::Response>,
|
||||||
|
{
|
||||||
|
type Response = C::Output;
|
||||||
|
type Error = S::Error;
|
||||||
|
type Future = TrackCompletionFuture<S::Future, C, Handle>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.service.poll_ready(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: Request) -> Self::Future {
|
||||||
|
TrackCompletionFuture::new(
|
||||||
|
self.completion.clone(),
|
||||||
|
self.handle(),
|
||||||
|
self.service.call(req),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, C> Load for PeakEwma<S, C> {
|
||||||
|
type Metric = Cost;
|
||||||
|
|
||||||
|
fn load(&self) -> Self::Metric {
|
||||||
|
let pending = Arc::strong_count(&self.rtt_estimate) as u32 - 1;
|
||||||
|
|
||||||
|
// Update the RTT estimate to account for decay since the last update.
|
||||||
|
// If an estimate has not been established, a default is provided
|
||||||
|
let estimate = self.update_estimate();
|
||||||
|
|
||||||
|
let cost = Cost(estimate * f64::from(pending + 1));
|
||||||
|
trace!(
|
||||||
|
"load estimate={:.0}ms pending={} cost={:?}",
|
||||||
|
estimate / NANOS_PER_MILLI,
|
||||||
|
pending,
|
||||||
|
cost,
|
||||||
|
);
|
||||||
|
cost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, C> PeakEwma<S, C> {
|
||||||
|
fn update_estimate(&self) -> f64 {
|
||||||
|
let mut rtt = self.rtt_estimate.lock().expect("peak ewma prior_estimate");
|
||||||
|
rtt.decay(self.decay_ns)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl PeakEwmaDiscover =====
|
||||||
|
|
||||||
|
#[cfg(feature = "discover")]
|
||||||
|
impl<D, C> PeakEwmaDiscover<D, C> {
|
||||||
|
/// Wraps a `D`-typed [`Discover`] so that services have a [`PeakEwma`] load metric.
|
||||||
|
///
|
||||||
|
/// The provided `default_rtt` is used as the default RTT estimate for newly
|
||||||
|
/// added services.
|
||||||
|
///
|
||||||
|
/// They `decay` value determines over what time period a RTT estimate should
|
||||||
|
/// decay.
|
||||||
|
pub fn new<Request>(discover: D, default_rtt: Duration, decay: Duration, completion: C) -> Self
|
||||||
|
where
|
||||||
|
D: Discover,
|
||||||
|
D::Service: Service<Request>,
|
||||||
|
C: TrackCompletion<Handle, <D::Service as Service<Request>>::Response>,
|
||||||
|
{
|
||||||
|
PeakEwmaDiscover {
|
||||||
|
discover,
|
||||||
|
decay_ns: nanos(decay),
|
||||||
|
default_rtt,
|
||||||
|
completion,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "discover")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "discover")))]
|
||||||
|
impl<D, C> Stream for PeakEwmaDiscover<D, C>
|
||||||
|
where
|
||||||
|
D: Discover,
|
||||||
|
C: Clone,
|
||||||
|
{
|
||||||
|
type Item = Result<Change<D::Key, PeakEwma<D::Service, C>>, D::Error>;
|
||||||
|
|
||||||
|
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
|
let this = self.project();
|
||||||
|
let change = match ready!(this.discover.poll_discover(cx)).transpose()? {
|
||||||
|
None => return Poll::Ready(None),
|
||||||
|
Some(Change::Remove(k)) => Change::Remove(k),
|
||||||
|
Some(Change::Insert(k, svc)) => {
|
||||||
|
let peak_ewma = PeakEwma::new(
|
||||||
|
svc,
|
||||||
|
*this.default_rtt,
|
||||||
|
*this.decay_ns,
|
||||||
|
this.completion.clone(),
|
||||||
|
);
|
||||||
|
Change::Insert(k, peak_ewma)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Poll::Ready(Some(Ok(change)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl RttEstimate =====
|
||||||
|
|
||||||
|
impl RttEstimate {
|
||||||
|
fn new(rtt_ns: f64) -> Self {
|
||||||
|
debug_assert!(0.0 < rtt_ns, "rtt must be positive");
|
||||||
|
Self {
|
||||||
|
rtt_ns,
|
||||||
|
update_at: Instant::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decays the RTT estimate with a decay period of `decay_ns`.
|
||||||
|
fn decay(&mut self, decay_ns: f64) -> f64 {
|
||||||
|
// Updates with a 0 duration so that the estimate decays towards 0.
|
||||||
|
let now = Instant::now();
|
||||||
|
self.update(now, now, decay_ns)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the Peak-EWMA RTT estimate.
|
||||||
|
///
|
||||||
|
/// The elapsed time from `sent_at` to `recv_at` is added
|
||||||
|
fn update(&mut self, sent_at: Instant, recv_at: Instant, decay_ns: f64) -> f64 {
|
||||||
|
debug_assert!(
|
||||||
|
sent_at <= recv_at,
|
||||||
|
"recv_at={:?} after sent_at={:?}",
|
||||||
|
recv_at,
|
||||||
|
sent_at
|
||||||
|
);
|
||||||
|
let rtt = nanos(recv_at.saturating_duration_since(sent_at));
|
||||||
|
|
||||||
|
let now = Instant::now();
|
||||||
|
debug_assert!(
|
||||||
|
self.update_at <= now,
|
||||||
|
"update_at={:?} in the future",
|
||||||
|
self.update_at
|
||||||
|
);
|
||||||
|
|
||||||
|
self.rtt_ns = if self.rtt_ns < rtt {
|
||||||
|
// For Peak-EWMA, always use the worst-case (peak) value as the estimate for
|
||||||
|
// subsequent requests.
|
||||||
|
trace!(
|
||||||
|
"update peak rtt={}ms prior={}ms",
|
||||||
|
rtt / NANOS_PER_MILLI,
|
||||||
|
self.rtt_ns / NANOS_PER_MILLI,
|
||||||
|
);
|
||||||
|
rtt
|
||||||
|
} else {
|
||||||
|
// When an RTT is observed that is less than the estimated RTT, we decay the
|
||||||
|
// prior estimate according to how much time has elapsed since the last
|
||||||
|
// update. The inverse of the decay is used to scale the estimate towards the
|
||||||
|
// observed RTT value.
|
||||||
|
let elapsed = nanos(now.saturating_duration_since(self.update_at));
|
||||||
|
let decay = (-elapsed / decay_ns).exp();
|
||||||
|
let recency = 1.0 - decay;
|
||||||
|
let next_estimate = (self.rtt_ns * decay) + (rtt * recency);
|
||||||
|
trace!(
|
||||||
|
"update rtt={:03.0}ms decay={:06.0}ns; next={:03.0}ms",
|
||||||
|
rtt / NANOS_PER_MILLI,
|
||||||
|
self.rtt_ns - next_estimate,
|
||||||
|
next_estimate / NANOS_PER_MILLI,
|
||||||
|
);
|
||||||
|
next_estimate
|
||||||
|
};
|
||||||
|
self.update_at = now;
|
||||||
|
|
||||||
|
self.rtt_ns
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl Handle =====
|
||||||
|
|
||||||
|
impl Drop for Handle {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let recv_at = Instant::now();
|
||||||
|
|
||||||
|
if let Ok(mut rtt) = self.rtt_estimate.lock() {
|
||||||
|
rtt.update(self.sent_at, recv_at, self.decay_ns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl Cost =====
|
||||||
|
|
||||||
|
// Utility that converts durations to nanos in f64.
|
||||||
|
//
|
||||||
|
// Due to a lossy transformation, the maximum value that can be represented is ~585 years,
|
||||||
|
// which, I hope, is more than enough to represent request latencies.
|
||||||
|
fn nanos(d: Duration) -> f64 {
|
||||||
|
const NANOS_PER_SEC: u64 = 1_000_000_000;
|
||||||
|
let n = f64::from(d.subsec_nanos());
|
||||||
|
let s = d.as_secs().saturating_mul(NANOS_PER_SEC) as f64;
|
||||||
|
n + s
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use futures_util::future;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::time;
|
||||||
|
use tokio_test::{assert_ready, assert_ready_ok, task};
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
struct Svc;
|
||||||
|
impl Service<()> for Svc {
|
||||||
|
type Response = ();
|
||||||
|
type Error = ();
|
||||||
|
type Future = future::Ready<Result<(), ()>>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), ()>> {
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, (): ()) -> Self::Future {
|
||||||
|
future::ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The default RTT estimate decays, so that new nodes are considered if the
|
||||||
|
/// default RTT is too high.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn default_decay() {
|
||||||
|
time::pause();
|
||||||
|
|
||||||
|
let svc = PeakEwma::new(
|
||||||
|
Svc,
|
||||||
|
Duration::from_millis(10),
|
||||||
|
NANOS_PER_MILLI * 1_000.0,
|
||||||
|
CompleteOnResponse,
|
||||||
|
);
|
||||||
|
let Cost(load) = svc.load();
|
||||||
|
assert_eq!(load, 10.0 * NANOS_PER_MILLI);
|
||||||
|
|
||||||
|
time::advance(Duration::from_millis(100)).await;
|
||||||
|
let Cost(load) = svc.load();
|
||||||
|
assert!(9.0 * NANOS_PER_MILLI < load && load < 10.0 * NANOS_PER_MILLI);
|
||||||
|
|
||||||
|
time::advance(Duration::from_millis(100)).await;
|
||||||
|
let Cost(load) = svc.load();
|
||||||
|
assert!(8.0 * NANOS_PER_MILLI < load && load < 9.0 * NANOS_PER_MILLI);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The default RTT estimate decays, so that new nodes are considered if the default RTT is too
|
||||||
|
// high.
|
||||||
|
#[tokio::test]
|
||||||
|
async fn compound_decay() {
|
||||||
|
time::pause();
|
||||||
|
|
||||||
|
let mut svc = PeakEwma::new(
|
||||||
|
Svc,
|
||||||
|
Duration::from_millis(20),
|
||||||
|
NANOS_PER_MILLI * 1_000.0,
|
||||||
|
CompleteOnResponse,
|
||||||
|
);
|
||||||
|
assert_eq!(svc.load(), Cost(20.0 * NANOS_PER_MILLI));
|
||||||
|
|
||||||
|
time::advance(Duration::from_millis(100)).await;
|
||||||
|
let mut rsp0 = task::spawn(svc.call(()));
|
||||||
|
assert!(svc.load() > Cost(20.0 * NANOS_PER_MILLI));
|
||||||
|
|
||||||
|
time::advance(Duration::from_millis(100)).await;
|
||||||
|
let mut rsp1 = task::spawn(svc.call(()));
|
||||||
|
assert!(svc.load() > Cost(40.0 * NANOS_PER_MILLI));
|
||||||
|
|
||||||
|
time::advance(Duration::from_millis(100)).await;
|
||||||
|
let () = assert_ready_ok!(rsp0.poll());
|
||||||
|
assert_eq!(svc.load(), Cost(400_000_000.0));
|
||||||
|
|
||||||
|
time::advance(Duration::from_millis(100)).await;
|
||||||
|
let () = assert_ready_ok!(rsp1.poll());
|
||||||
|
assert_eq!(svc.load(), Cost(200_000_000.0));
|
||||||
|
|
||||||
|
// Check that values decay as time elapses
|
||||||
|
time::advance(Duration::from_secs(1)).await;
|
||||||
|
assert!(svc.load() < Cost(100_000_000.0));
|
||||||
|
|
||||||
|
time::advance(Duration::from_secs(10)).await;
|
||||||
|
assert!(svc.load() < Cost(100_000.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn nanos() {
|
||||||
|
assert_eq!(super::nanos(Duration::new(0, 0)), 0.0);
|
||||||
|
assert_eq!(super::nanos(Duration::new(0, 123)), 123.0);
|
||||||
|
assert_eq!(super::nanos(Duration::new(1, 23)), 1_000_000_023.0);
|
||||||
|
assert_eq!(
|
||||||
|
super::nanos(Duration::new(::std::u64::MAX, 999_999_999)),
|
||||||
|
18446744074709553000.0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
216
tower/tower/src/load/pending_requests.rs
Normal file
216
tower/tower/src/load/pending_requests.rs
Normal file
|
|
@ -0,0 +1,216 @@
|
||||||
|
//! A [`Load`] implementation that measures load using the number of in-flight requests.
|
||||||
|
|
||||||
|
#[cfg(feature = "discover")]
|
||||||
|
use crate::discover::{Change, Discover};
|
||||||
|
#[cfg(feature = "discover")]
|
||||||
|
use futures_core::{ready, Stream};
|
||||||
|
#[cfg(feature = "discover")]
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
#[cfg(feature = "discover")]
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
use super::completion::{CompleteOnResponse, TrackCompletion, TrackCompletionFuture};
|
||||||
|
use super::Load;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
use tower_service::Service;
|
||||||
|
|
||||||
|
/// Measures the load of the underlying service using the number of currently-pending requests.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PendingRequests<S, C = CompleteOnResponse> {
|
||||||
|
service: S,
|
||||||
|
ref_count: RefCount,
|
||||||
|
completion: C,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Shared between instances of [`PendingRequests`] and [`Handle`] to track active references.
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
struct RefCount(Arc<()>);
|
||||||
|
|
||||||
|
#[cfg(feature = "discover")]
|
||||||
|
pin_project! {
|
||||||
|
/// Wraps a `D`-typed stream of discovered services with [`PendingRequests`].
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "discover")))]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PendingRequestsDiscover<D, C = CompleteOnResponse> {
|
||||||
|
#[pin]
|
||||||
|
discover: D,
|
||||||
|
completion: C,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents the number of currently-pending requests to a given service.
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialOrd, PartialEq, Ord, Eq)]
|
||||||
|
pub struct Count(usize);
|
||||||
|
|
||||||
|
/// Tracks an in-flight request by reference count.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Handle(RefCount);
|
||||||
|
|
||||||
|
// ===== impl PendingRequests =====
|
||||||
|
|
||||||
|
impl<S, C> PendingRequests<S, C> {
|
||||||
|
/// Wraps an `S`-typed service so that its load is tracked by the number of pending requests.
|
||||||
|
pub fn new(service: S, completion: C) -> Self {
|
||||||
|
Self {
|
||||||
|
service,
|
||||||
|
completion,
|
||||||
|
ref_count: RefCount::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle(&self) -> Handle {
|
||||||
|
Handle(self.ref_count.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, C> Load for PendingRequests<S, C> {
|
||||||
|
type Metric = Count;
|
||||||
|
|
||||||
|
fn load(&self) -> Count {
|
||||||
|
// Count the number of references that aren't `self`.
|
||||||
|
Count(self.ref_count.ref_count() - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, C, Request> Service<Request> for PendingRequests<S, C>
|
||||||
|
where
|
||||||
|
S: Service<Request>,
|
||||||
|
C: TrackCompletion<Handle, S::Response>,
|
||||||
|
{
|
||||||
|
type Response = C::Output;
|
||||||
|
type Error = S::Error;
|
||||||
|
type Future = TrackCompletionFuture<S::Future, C, Handle>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.service.poll_ready(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: Request) -> Self::Future {
|
||||||
|
TrackCompletionFuture::new(
|
||||||
|
self.completion.clone(),
|
||||||
|
self.handle(),
|
||||||
|
self.service.call(req),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl PendingRequestsDiscover =====
|
||||||
|
|
||||||
|
#[cfg(feature = "discover")]
|
||||||
|
impl<D, C> PendingRequestsDiscover<D, C> {
|
||||||
|
/// Wraps a [`Discover`], wrapping all of its services with [`PendingRequests`].
|
||||||
|
pub fn new<Request>(discover: D, completion: C) -> Self
|
||||||
|
where
|
||||||
|
D: Discover,
|
||||||
|
D::Service: Service<Request>,
|
||||||
|
C: TrackCompletion<Handle, <D::Service as Service<Request>>::Response>,
|
||||||
|
{
|
||||||
|
Self {
|
||||||
|
discover,
|
||||||
|
completion,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "discover")]
|
||||||
|
impl<D, C> Stream for PendingRequestsDiscover<D, C>
|
||||||
|
where
|
||||||
|
D: Discover,
|
||||||
|
C: Clone,
|
||||||
|
{
|
||||||
|
type Item = Result<Change<D::Key, PendingRequests<D::Service, C>>, D::Error>;
|
||||||
|
|
||||||
|
/// Yields the next discovery change set.
|
||||||
|
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
|
use self::Change::*;
|
||||||
|
|
||||||
|
let this = self.project();
|
||||||
|
let change = match ready!(this.discover.poll_discover(cx)).transpose()? {
|
||||||
|
None => return Poll::Ready(None),
|
||||||
|
Some(Insert(k, svc)) => Insert(k, PendingRequests::new(svc, this.completion.clone())),
|
||||||
|
Some(Remove(k)) => Remove(k),
|
||||||
|
};
|
||||||
|
|
||||||
|
Poll::Ready(Some(Ok(change)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==== RefCount ====
|
||||||
|
|
||||||
|
impl RefCount {
|
||||||
|
pub(crate) fn ref_count(&self) -> usize {
|
||||||
|
Arc::strong_count(&self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use futures_util::future;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
struct Svc;
|
||||||
|
impl Service<()> for Svc {
|
||||||
|
type Response = ();
|
||||||
|
type Error = ();
|
||||||
|
type Future = future::Ready<Result<(), ()>>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), ()>> {
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, (): ()) -> Self::Future {
|
||||||
|
future::ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default() {
|
||||||
|
let mut svc = PendingRequests::new(Svc, CompleteOnResponse);
|
||||||
|
assert_eq!(svc.load(), Count(0));
|
||||||
|
|
||||||
|
let rsp0 = svc.call(());
|
||||||
|
assert_eq!(svc.load(), Count(1));
|
||||||
|
|
||||||
|
let rsp1 = svc.call(());
|
||||||
|
assert_eq!(svc.load(), Count(2));
|
||||||
|
|
||||||
|
let () = tokio_test::block_on(rsp0).unwrap();
|
||||||
|
assert_eq!(svc.load(), Count(1));
|
||||||
|
|
||||||
|
let () = tokio_test::block_on(rsp1).unwrap();
|
||||||
|
assert_eq!(svc.load(), Count(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn with_completion() {
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct IntoHandle;
|
||||||
|
impl TrackCompletion<Handle, ()> for IntoHandle {
|
||||||
|
type Output = Handle;
|
||||||
|
fn track_completion(&self, i: Handle, (): ()) -> Handle {
|
||||||
|
i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut svc = PendingRequests::new(Svc, IntoHandle);
|
||||||
|
assert_eq!(svc.load(), Count(0));
|
||||||
|
|
||||||
|
let rsp = svc.call(());
|
||||||
|
assert_eq!(svc.load(), Count(1));
|
||||||
|
let i0 = tokio_test::block_on(rsp).unwrap();
|
||||||
|
assert_eq!(svc.load(), Count(1));
|
||||||
|
|
||||||
|
let rsp = svc.call(());
|
||||||
|
assert_eq!(svc.load(), Count(2));
|
||||||
|
let i1 = tokio_test::block_on(rsp).unwrap();
|
||||||
|
assert_eq!(svc.load(), Count(2));
|
||||||
|
|
||||||
|
drop(i1);
|
||||||
|
assert_eq!(svc.load(), Count(1));
|
||||||
|
|
||||||
|
drop(i0);
|
||||||
|
assert_eq!(svc.load(), Count(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
42
tower/tower/src/macros.rs
Normal file
42
tower/tower/src/macros.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
#[cfg(any(
|
||||||
|
feature = "util",
|
||||||
|
feature = "spawn-ready",
|
||||||
|
feature = "filter",
|
||||||
|
feature = "make"
|
||||||
|
))]
|
||||||
|
macro_rules! opaque_future {
|
||||||
|
($(#[$m:meta])* pub type $name:ident<$($param:ident),+> = $actual:ty;) => {
|
||||||
|
pin_project_lite::pin_project! {
|
||||||
|
$(#[$m])*
|
||||||
|
pub struct $name<$($param),+> {
|
||||||
|
#[pin]
|
||||||
|
inner: $actual
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<$($param),+> $name<$($param),+> {
|
||||||
|
pub(crate) fn new(inner: $actual) -> Self {
|
||||||
|
Self {
|
||||||
|
inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<$($param),+> std::fmt::Debug for $name<$($param),+> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_tuple(stringify!($name)).field(&format_args!("...")).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<$($param),+> std::future::Future for $name<$($param),+>
|
||||||
|
where
|
||||||
|
$actual: std::future::Future,
|
||||||
|
{
|
||||||
|
type Output = <$actual as std::future::Future>::Output;
|
||||||
|
#[inline]
|
||||||
|
fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> std::task::Poll<Self::Output> {
|
||||||
|
self.project().inner.poll(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
47
tower/tower/src/make/make_connection.rs
Normal file
47
tower/tower/src/make/make_connection.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
use crate::sealed::Sealed;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
use tower_service::Service;
|
||||||
|
|
||||||
|
/// The [`MakeConnection`] trait is used to create transports.
|
||||||
|
///
|
||||||
|
/// The goal of this service is to allow composable methods for creating
|
||||||
|
/// `AsyncRead + AsyncWrite` transports. This could mean creating a TLS
|
||||||
|
/// based connection or using some other method to authenticate the connection.
|
||||||
|
pub trait MakeConnection<Target>: Sealed<(Target,)> {
|
||||||
|
/// The transport provided by this service
|
||||||
|
type Connection: AsyncRead + AsyncWrite;
|
||||||
|
|
||||||
|
/// Errors produced by the connecting service
|
||||||
|
type Error;
|
||||||
|
|
||||||
|
/// The future that eventually produces the transport
|
||||||
|
type Future: Future<Output = Result<Self::Connection, Self::Error>>;
|
||||||
|
|
||||||
|
/// Returns `Poll::Ready(Ok(()))` when it is able to make more connections.
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>>;
|
||||||
|
|
||||||
|
/// Connect and return a transport asynchronously
|
||||||
|
fn make_connection(&mut self, target: Target) -> Self::Future;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, Target> Sealed<(Target,)> for S where S: Service<Target> {}
|
||||||
|
|
||||||
|
impl<C, Target> MakeConnection<Target> for C
|
||||||
|
where
|
||||||
|
C: Service<Target>,
|
||||||
|
C::Response: AsyncRead + AsyncWrite,
|
||||||
|
{
|
||||||
|
type Connection = C::Response;
|
||||||
|
type Error = C::Error;
|
||||||
|
type Future = C::Future;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
Service::poll_ready(self, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_connection(&mut self, target: Target) -> Self::Future {
|
||||||
|
Service::call(self, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
150
tower/tower/src/make/make_service.rs
Normal file
150
tower/tower/src/make/make_service.rs
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
//! Contains [`MakeService`] which is a trait alias for a [`Service`] of [`Service`]s.
|
||||||
|
|
||||||
|
use crate::sealed::Sealed;
|
||||||
|
use std::fmt;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
use tower_service::Service;
|
||||||
|
|
||||||
|
pub(crate) mod shared;
|
||||||
|
|
||||||
|
/// Creates new [`Service`] values.
|
||||||
|
///
|
||||||
|
/// Acts as a service factory. This is useful for cases where new [`Service`]
|
||||||
|
/// values must be produced. One case is a TCP server listener. The listener
|
||||||
|
/// accepts new TCP streams, obtains a new [`Service`] value using the
|
||||||
|
/// [`MakeService`] trait, and uses that new [`Service`] value to process inbound
|
||||||
|
/// requests on that new TCP stream.
|
||||||
|
///
|
||||||
|
/// This is essentially a trait alias for a [`Service`] of [`Service`]s.
|
||||||
|
pub trait MakeService<Target, Request> {
|
||||||
|
type Response;
|
||||||
|
type Error;
|
||||||
|
type Service: Service<Request, Response = Self::Response, Error = Self::Error>;
|
||||||
|
type MakeError;
|
||||||
|
type Future;
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::MakeError>>;
|
||||||
|
fn make_service(&mut self, target: Target) -> Self::Future;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M, S, Target, Request> Sealed<(Target, Request)> for M
|
||||||
|
where
|
||||||
|
M: Service<Target, Response = S>,
|
||||||
|
S: Service<Request>,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M, S, Target, Request> MakeService<Target, Request> for M
|
||||||
|
where
|
||||||
|
M: Service<Target, Response = S>,
|
||||||
|
S: Service<Request>,
|
||||||
|
{
|
||||||
|
type Response = S::Response;
|
||||||
|
type Error = S::Error;
|
||||||
|
type Service = S;
|
||||||
|
type MakeError = M::Error;
|
||||||
|
type Future = M::Future;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::MakeError>> {
|
||||||
|
Service::poll_ready(self, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_service(&mut self, target: Target) -> Self::Future {
|
||||||
|
Service::call(self, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Service returned by [`MakeService::into_service`][into].
|
||||||
|
///
|
||||||
|
/// See the documentation on [`into_service`][into] for details.
|
||||||
|
///
|
||||||
|
/// [into]: MakeService::into_service
|
||||||
|
pub struct IntoService<M, Request> {
|
||||||
|
make: M,
|
||||||
|
_marker: PhantomData<Request>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M, Request> Clone for IntoService<M, Request>
|
||||||
|
where
|
||||||
|
M: Clone,
|
||||||
|
{
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
make: self.make.clone(),
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M, Request> fmt::Debug for IntoService<M, Request>
|
||||||
|
where
|
||||||
|
M: fmt::Debug,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("IntoService")
|
||||||
|
.field("make", &self.make)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M, S, Target, Request> Service<Target> for IntoService<M, Request>
|
||||||
|
where
|
||||||
|
M: Service<Target, Response = S>,
|
||||||
|
S: Service<Request>,
|
||||||
|
{
|
||||||
|
type Response = M::Response;
|
||||||
|
type Error = M::Error;
|
||||||
|
type Future = M::Future;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.make.poll_ready(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn call(&mut self, target: Target) -> Self::Future {
|
||||||
|
self.make.make_service(target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Service returned by [`MakeService::as_service`][as].
|
||||||
|
///
|
||||||
|
/// See the documentation on [`as_service`][as] for details.
|
||||||
|
///
|
||||||
|
/// [as]: MakeService::as_service
|
||||||
|
pub struct AsService<'a, M, Request> {
|
||||||
|
make: &'a mut M,
|
||||||
|
_marker: PhantomData<Request>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M, Request> fmt::Debug for AsService<'_, M, Request>
|
||||||
|
where
|
||||||
|
M: fmt::Debug,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("AsService")
|
||||||
|
.field("make", &self.make)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<M, S, Target, Request> Service<Target> for AsService<'_, M, Request>
|
||||||
|
where
|
||||||
|
M: Service<Target, Response = S>,
|
||||||
|
S: Service<Request>,
|
||||||
|
{
|
||||||
|
type Response = M::Response;
|
||||||
|
type Error = M::Error;
|
||||||
|
type Future = M::Future;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.make.poll_ready(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn call(&mut self, target: Target) -> Self::Future {
|
||||||
|
self.make.make_service(target)
|
||||||
|
}
|
||||||
|
}
|
||||||
146
tower/tower/src/make/make_service/shared.rs
Normal file
146
tower/tower/src/make/make_service/shared.rs
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
use std::convert::Infallible;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
use tower_service::Service;
|
||||||
|
|
||||||
|
/// A [`MakeService`] that produces services by cloning an inner service.
|
||||||
|
///
|
||||||
|
/// [`MakeService`]: super::MakeService
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use std::task::{Context, Poll};
|
||||||
|
/// # use std::pin::Pin;
|
||||||
|
/// # use std::convert::Infallible;
|
||||||
|
/// use tower::make::{MakeService, Shared};
|
||||||
|
/// use tower::buffer::Buffer;
|
||||||
|
/// use tower::Service;
|
||||||
|
/// use futures::future::{Ready, ready};
|
||||||
|
///
|
||||||
|
/// // An example connection type
|
||||||
|
/// struct Connection {}
|
||||||
|
///
|
||||||
|
/// // An example request type
|
||||||
|
/// struct Request {}
|
||||||
|
///
|
||||||
|
/// // An example response type
|
||||||
|
/// struct Response {}
|
||||||
|
///
|
||||||
|
/// // Some service that doesn't implement `Clone`
|
||||||
|
/// struct MyService;
|
||||||
|
///
|
||||||
|
/// impl Service<Request> for MyService {
|
||||||
|
/// type Response = Response;
|
||||||
|
/// type Error = Infallible;
|
||||||
|
/// type Future = Ready<Result<Response, Infallible>>;
|
||||||
|
///
|
||||||
|
/// fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
/// Poll::Ready(Ok(()))
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn call(&mut self, req: Request) -> Self::Future {
|
||||||
|
/// ready(Ok(Response {}))
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // Example function that runs a service by accepting new connections and using
|
||||||
|
/// // `Make` to create new services that might be bound to the connection.
|
||||||
|
/// //
|
||||||
|
/// // This is similar to what you might find in hyper.
|
||||||
|
/// async fn serve_make_service<Make>(make: Make)
|
||||||
|
/// where
|
||||||
|
/// Make: MakeService<Connection, Request>
|
||||||
|
/// {
|
||||||
|
/// // ...
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # async {
|
||||||
|
/// // Our service
|
||||||
|
/// let svc = MyService;
|
||||||
|
///
|
||||||
|
/// // Make it `Clone` by putting a channel in front
|
||||||
|
/// let buffered = Buffer::new(svc, 1024);
|
||||||
|
///
|
||||||
|
/// // Convert it into a `MakeService`
|
||||||
|
/// let make = Shared::new(buffered);
|
||||||
|
///
|
||||||
|
/// // Run the service and just ignore the `Connection`s as `MyService` doesn't need them
|
||||||
|
/// serve_make_service(make).await;
|
||||||
|
/// # };
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Shared<S> {
|
||||||
|
service: S,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Shared<S> {
|
||||||
|
/// Create a new [`Shared`] from a service.
|
||||||
|
pub fn new(service: S) -> Self {
|
||||||
|
Self { service }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, T> Service<T> for Shared<S>
|
||||||
|
where
|
||||||
|
S: Clone,
|
||||||
|
{
|
||||||
|
type Response = S;
|
||||||
|
type Error = Infallible;
|
||||||
|
type Future = SharedFuture<S>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, _target: T) -> Self::Future {
|
||||||
|
SharedFuture::new(futures_util::future::ready(Ok(self.service.clone())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opaque_future! {
|
||||||
|
/// Response future from [`Shared`] services.
|
||||||
|
pub type SharedFuture<S> = futures_util::future::Ready<Result<S, Infallible>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::make::MakeService;
|
||||||
|
use crate::service_fn;
|
||||||
|
use futures::future::poll_fn;
|
||||||
|
|
||||||
|
async fn echo<R>(req: R) -> Result<R, Infallible> {
|
||||||
|
Ok(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn as_make_service() {
|
||||||
|
let mut shared = Shared::new(service_fn(echo::<&'static str>));
|
||||||
|
|
||||||
|
poll_fn(|cx| MakeService::<(), _>::poll_ready(&mut shared, cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let mut svc = shared.make_service(()).await.unwrap();
|
||||||
|
|
||||||
|
poll_fn(|cx| svc.poll_ready(cx)).await.unwrap();
|
||||||
|
let res = svc.call("foo").await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(res, "foo");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn as_make_service_into_service() {
|
||||||
|
let shared = Shared::new(service_fn(echo::<&'static str>));
|
||||||
|
let mut shared = MakeService::<(), _>::into_service(shared);
|
||||||
|
|
||||||
|
poll_fn(|cx| Service::<()>::poll_ready(&mut shared, cx))
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
let mut svc = shared.call(()).await.unwrap();
|
||||||
|
|
||||||
|
poll_fn(|cx| svc.poll_ready(cx)).await.unwrap();
|
||||||
|
let res = svc.call("foo").await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(res, "foo");
|
||||||
|
}
|
||||||
|
}
|
||||||
14
tower/tower/src/make/mod.rs
Normal file
14
tower/tower/src/make/mod.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
//! Trait aliases for Services that produce specific types of Responses.
|
||||||
|
|
||||||
|
mod make_connection;
|
||||||
|
mod make_service;
|
||||||
|
|
||||||
|
pub use self::make_connection::MakeConnection;
|
||||||
|
pub use self::make_service::shared::Shared;
|
||||||
|
pub use self::make_service::{AsService, IntoService, MakeService};
|
||||||
|
|
||||||
|
pub mod future {
|
||||||
|
//! Future types
|
||||||
|
|
||||||
|
pub use super::make_service::shared::SharedFuture;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue