New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
how to implement A vec of layers, something like this: Vec<Box<dyn Layer>>
, configurable layers
#496
Comments
Do you actually need a totally dynamic E.g. use tower::layer::{Identity, Stack, Layer};
struct MyStruct<L> {
layers: L,
}
impl Default for MyStruct<Identity> { ... }
impl<L> MyStuct<L> {
pub fn push<M>(self, outer: M) -> MyStruct<Stack<L, M>> {
MyStruct {
layers: Stack::new(self.layers, outer),
}
}
pub fn layer(&self) -> &L {
&self.layers
}
} If you really do need this to be dynamic, you probably want to look at implementing a BoxLayer helper akin to |
Thanks for the answer~ I have tried Then I follow the implementation of use tower::{
Service,
layer::Layer,
timeout::TimeoutLayer,
limit::RateLimitLayer
};
use std::{ pin::Pin, future::Future, time::Duration };
type BoxFuture<T, E> = Pin<Box<dyn Future<Output = Result<T, E>> + Send>>;
type BoxService<T, U, E> = Box<dyn Service<T, Response = U, Error = E, Future = BoxFuture<U, E>> + Send>;
type StdError =
std::boxed::Box<(dyn std::error::Error + std::marker::Send + std::marker::Sync + 'static)>;
type BoxLayer<S, T, U, E> = Box<dyn Layer<S, Service = BoxService<T, U, E>>>;
struct Layers<S, T, U, E> {
layers: Vec<BoxLayer<S, T, U, E>>
}
impl<S, T, U, E> Layers<S, T, U, E> {
fn new() -> Self {
Self {
layers: Vec::new()
}
}
fn push<L: 'static>(&mut self, layer: L)
where L: Layer<S, Service = BoxService<T, U, E>>
{
self.layers.push(Box::new(layer));
}
}
fn test() {
let timeout_layer = TimeoutLayer::new(Duration::from_secs(5));
let rate_limit_layer = RateLimitLayer::new(1000, Duration::from_secs(1));
let layers = Layers::new();
layers.push(timeout_layer);
} And this won't compile 🤦♂️ Maybe it's not a proper pattern/usage(Vec of |
@kingrongH What causes you to want a vec of layers? Are you able to use a let timeout_layer = TimeoutLayer::new(Duration::from_secs(5));
let rate_limit_layer = RateLimitLayer::new(1000, Duration::from_secs(1));
let builder = tower::ServiceBuilder::new()
.layer(timeout_layer)
.layer(rate_limit_layer);
let some_service =
tower::service_fn(|req: String| async move { Ok::<_, Infallible>(req.to_uppercase()) });
let wrapped_service = builder.service(some_service); I think the type gymnastics required to do that might not be worth the hassle if even possible. |
Yes it's not easy to do so. But somehow in my situation, I still want to dynamically add/remove the layers in
let builder = if config_timeout {
tower::ServiceBuilder::new().layer(timeout_layer)
} else if config_rate_limit {
tower::ServiceBuilder::new().layer(rate_limit_layer)
} else if config_timeout && config_rate_limit {
tower::ServiceBuilder::new().layer(timeout_layer).layer(rate_limit_layer)
} else {
tower::ServiceBuilder::new()
}; but actually this code won't compile, cuz And I'm fascinated by the impl of configurable layers. |
I think I understand a little now. I have to create a new |
Vec<Box<dyn Layer>>
Vec<Box<dyn Layer>>
, configurable layers
After some fight with the compiler, I think maybe we just can't have this kind of impl, because the request and response of I think implementing configurable layers by using |
Finally, I have achieve some thing that partly solve my problem. The idea is enum OptionLayer<L> {
Layer(L),
NoneLayer,
}
impl<L> OptionLayer<L> {
pub fn from_option(option: Option<L>) -> OptionLayer<L>
{
option.map_or_else(|| OptionLayer::NoneLayer, |v| OptionLayer::Layer(v))
}
}
impl<S, L: Layer<S>> Layer<S> for OptionLayer<L> {
type Service = Either<L::Service, S>;
fn layer(&self, inner: S) -> Self::Service {
match self {
OptionLayer::Layer(l) => Either::A(l.layer(inner)),
OptionLayer::NoneLayer => Either::B(inner)
}
}
}
struct MyConfig {
timeout: Option<Duration>,
concurrency_limit: Option<usize>,
}
impl MyConfig {
pub fn layer(&self) -> Stack<OptionLayer<TimeoutLayer>, OptionLayer<ConcurrencyLimitLayer>> {
let timeout_layer = OptionLayer::from_option(self.timeout.map(TimeoutLayer::new));
let concurrency_limit_layer = OptionLayer::from_option(self.concurrency_limit.map(ConcurrencyLimitLayer::new));
Stack::new(timeout_layer, concurrency_limit_layer)
}
}
good:
bad:
|
You could also consider dispatching to one of two services based on some config/env var at runtime. That way the types don't change. Something like use futures::future::Either;
use std::task::{Context, Poll};
use tower::layer::Layer;
use tower::Service;
pub struct EnvDispatchLayer<L> {
var: String,
toggle_layer: L,
}
impl<L> EnvDispatchLayer<L> {
pub fn new(var: impl Into<String>, toggle_layer: L) -> Self {
Self {
var: var.into(),
toggle_layer,
}
}
}
impl<L, S> Layer<S> for EnvDispatchLayer<L>
where
L: Layer<S>,
S: Clone,
{
type Service = EnvDispatch<L::Service, S>;
fn layer(&self, inner: S) -> Self::Service {
EnvDispatch {
layered: self.toggle_layer.layer(inner.clone()),
fallback: inner,
var: self.var.clone(),
env_value: None,
}
}
}
#[derive(Clone, Debug)]
pub struct EnvDispatch<Layered, Fallback> {
layered: Layered,
fallback: Fallback,
var: String,
env_value: Option<String>,
}
impl<T, K> EnvDispatch<T, K> {
fn update_value(&mut self) {
self.env_value = std::env::var(&self.var).ok();
}
fn enabled(&self) -> bool {
self.env_value
.as_ref()
.map(|value| value == "1")
.unwrap_or(false)
}
}
impl<R, Layered, Fallback> Service<R> for EnvDispatch<Layered, Fallback>
where
Layered: Service<R>,
Fallback: Service<R, Response = Layered::Response, Error = Layered::Error>,
{
type Response = Layered::Response;
type Error = Layered::Error;
type Future = Either<Layered::Future, Fallback::Future>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
// we don't want the env var to change between `poll_ready` and `call` so we cache it
// between calls
self.update_value();
if self.enabled() {
self.layered.poll_ready(cx)
} else {
self.fallback.poll_ready(cx)
}
}
fn call(&mut self, req: R) -> Self::Future {
if self.enabled() {
Either::Left(self.layered.call(req))
} else {
Either::Right(self.fallback.call(req))
}
}
}
#[allow(warnings)]
fn foo() {
use std::convert::Infallible;
use std::time::Duration;
use tower::builder::ServiceBuilder;
use tower::limit::concurrency::ConcurrencyLimitLayer;
use tower::timeout::TimeoutLayer;
let base_svc = tower::service_fn(|req: String| async move { Ok::<_, Infallible>(req) });
let final_svc = ServiceBuilder::new()
// call service wrapped in concurrency limit layer if ENABLE_RATE_LIMIT == 1
// otherwise call the base service
.layer(EnvDispatchLayer::new(
"ENABLE_RATE_LIMIT",
ConcurrencyLimitLayer::new(10),
))
// call service wrapped in timeout layer if ENABLE_TIMEOUT == 1
// otherwise call the base service
.layer(EnvDispatchLayer::new(
"ENABLE_TIMEOUT",
TimeoutLayer::new(Duration::from_secs(30)),
))
.service(base_svc);
} |
This And I'd like to discuss more. From your code: impl<R, Layered, Fallback> Service<R> for EnvDispatch<Layered, Fallback>
where
Layered: Service<R>,
Fallback: Service<R, Response = Layered::Response, Error = Layered::Error> The I am just thinking about that would I'd like to have a try later! Thanks again. |
YES, tried, let dispatched_map_response = EnvDispatchLayer::new("MAP_RES", MapResponseLayer::new(base_svc, |_: String| 42));
let result_svc = dispatched_map_response.layer(base_svc);
result_svc.call("string".into()); And also, checked, My problem here is sovled. Gonna close this one. Thanks. |
I have been running into this situation recently. I want a store a
Vec<Box<dyn Layer>>
stuff in a struct, and then I can chain them all together while using this struct . This would be something like this:but since
Layer
is a trait with generic argument and associated type, it's difficult for me to implementVec<Box<dyn Layer>>
with layers stored, I can build a service with these layers and dynamically add/remove the layer.Help needed. tks first
Edit:
In my situation, I want to dynamically add/remove the layers in MyStruct(even in the runtime!) according to some kind config.
The text was updated successfully, but these errors were encountered: