-
-
Notifications
You must be signed in to change notification settings - Fork 723
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
Custom with
filters
#16
Comments
Right, the In your example, I wouldn't expect Would this work for you? let my_route = req_id.map(|id| {
Response::builder()
.header("x-request-id", id)
.body("")
}) |
Totally understood. The library is excellent so far and after handlers have a lot more complexity.
Ya the composition is a little awkward since this is a filter that I'd like to be universally be a applied. But I landed on something like this: let req_id = warp::header::<String>("x-request-id").or_else(|_| {
Ok(("deadbeef".to_owned(),))
}).map(|id: String| {
let mut res = Response::builder();
res.header("x-request-id", id.as_str());
res
}).map(|mut res: response::Builder| {
res.body("bar").unwrap()
}); Which allows me to compose different body handlers after, and filters that don't pass along arguments before. |
I'm learning that writing these is kinda non-trivial: you need to name the This abstraction, or one like it, is pretty necessary though: it's the only way to put something on "both sides" of a filter, which is a common middleware requirement. |
@kamalmarhubi yep exactly! I've been working on a CORS filter, and it needs to be a |
Has there been any progress on this issue recently? Warp is awesome and adding the ability to inject middleware using custom |
I think this can now be achieved via: use std::convert::Infallible;
use warp::Filter;
use hyper::{Body, Request};
use tower_service::Service;
#[tokio::main]
async fn main() -> Result<(), hyper::error::Error> {
let route = warp::any().map(|| "Hello From Warp!");
let mut warp_svc = warp::service(route);
let make_svc = hyper::service::make_service_fn(move |_| async move {
let svc = hyper::service::service_fn(move |req: Request<Body>| async move {
println!("before request");
let resp = warp_svc.call(req).await;
println!("after request");
resp
});
Ok::<_, Infallible>(svc)
});
hyper::Server::bind(&([127, 0, 0, 1], 3030).into())
.serve(make_svc)
.await?;
Ok(())
} |
@jxs it worked for me, but my use case is a little more complex: If I change the route to be a BoxedFilter (to turn easier return the route from a function): let route = warp::any().map(|| "Hello From Warp!").boxed(); It fails to compile: $ cargo run
Compiling warp-report-1 v0.1.0 (/home/fernando/repos/sandbox/warp-report-1)
error[E0507]: cannot move out of `warp_svc`, a captured variable in an `FnMut` closure
--> src/main.rs:11:83
|
9 | let mut warp_svc = warp::service(route);
| ------------ captured outer variable
10 | let make_svc = hyper::service::make_service_fn(move |_| async move {
11 | let svc = hyper::service::service_fn(move |req: Request<Body>| async move {
| ___________________________________________________________________________________^
12 | | println!("before request");
13 | | let resp = warp_svc.call(req).await;
| | --------
| | |
| | move occurs because `warp_svc` has type `warp::filter::service::FilteredService<warp::filter::boxed::BoxedFilter<(&str,)>>`, which does not implement the `Copy` trait
| | move occurs due to use in generator
14 | | println!("after request");
15 | | resp
16 | | });
| |_________^ move out of `warp_svc` occurs here
error[E0507]: cannot move out of `warp_svc`, a captured variable in an `FnMut` closure
--> src/main.rs:10:72
|
9 | let mut warp_svc = warp::service(route);
| ------------ captured outer variable
10 | let make_svc = hyper::service::make_service_fn(move |_| async move {
| ________________________________________________________________________^
11 | | let svc = hyper::service::service_fn(move |req: Request<Body>| async move {
12 | | println!("before request");
13 | | let resp = warp_svc.call(req).await;
| | --------
| | |
| | move occurs because `warp_svc` has type `warp::filter::service::FilteredService<warp::filter::boxed::BoxedFilter<(&str,)>>`, which does not implement the `Copy` trait
| | move occurs due to use in generator
... |
17 | | Ok::<_, Infallible>(svc)
18 | | });
| |_____^ move out of `warp_svc` occurs here
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0507`.
error: could not compile `warp-report-1`.
To learn more, run the command again with --verbose. And if I don't use the boxed filter, but add "state" filter: let http_client = hyper::Client::new();
let route = warp::any()
.and(warp::any().map(move || http_client.clone()))
.map(|_http_client| "Hello From Warp!"); it will fail with a similar error: $ cargo run
Compiling warp-report-1 v0.1.0 (/home/fernando/repos/sandbox/warp-report-1)
error[E0507]: cannot move out of `warp_svc`, a captured variable in an `FnMut` closure
--> src/main.rs:20:83
|
18 | let mut warp_svc = warp::service(route);
| ------------ captured outer variable
19 | let make_svc = hyper::service::make_service_fn(move |_| async move {
20 | let svc = hyper::service::service_fn(move |req: Request<Body>| async move {
| ___________________________________________________________________________________^
21 | | println!("before request");
22 | | let resp = warp_svc.call(req).await;
| | --------
| | |
| | move occurs because `warp_svc` has type `warp::filter::service::FilteredService<warp::filter::map::Map<warp::filter::and::And<impl warp::filter::Filter+std::marker::Copy, warp::filter::map::Map<impl warp::filter::Filter+std::marker::Copy, [closure@src/main.rs:15:34: 15:61 http_client:hyper::client::Client<hyper::client::connect::http::HttpConnector>]>>, [closure@src/main.rs:16:18: 16:51]>>`, which does not implement the `Copy` trait
| | move occurs due to use in generator
23 | | println!("after request");
24 | | resp
25 | | });
| |_________^ move out of `warp_svc` occurs here
error[E0507]: cannot move out of `warp_svc`, a captured variable in an `FnMut` closure
--> src/main.rs:19:72
|
18 | let mut warp_svc = warp::service(route);
| ------------ captured outer variable
19 | let make_svc = hyper::service::make_service_fn(move |_| async move {
| ________________________________________________________________________^
20 | | let svc = hyper::service::service_fn(move |req: Request<Body>| async move {
21 | | println!("before request");
22 | | let resp = warp_svc.call(req).await;
| | --------
| | |
| | move occurs because `warp_svc` has type `warp::filter::service::FilteredService<warp::filter::map::Map<warp::filter::and::And<impl warp::filter::Filter+std::marker::Copy, warp::filter::map::Map<impl warp::filter::Filter+std::marker::Copy, [closure@src/main.rs:15:34: 15:61 http_client:hyper::client::Client<hyper::client::connect::http::HttpConnector>]>>, [closure@src/main.rs:16:18: 16:51]>>`, which does not implement the `Copy` trait
| | move occurs due to use in generator
... |
26 | | Ok::<_, Infallible>(svc)
27 | | });
| |_____^ move out of `warp_svc` occurs here
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0507`.
error: could not compile `warp-report-1`.
To learn more, run the command again with --verbose. The same seems to occur when I added the cors wrapper: let route = warp::any()
.map(|| "Hello From Warp!")
.with(warp::cors().allow_any_origin()); it failed with a similar error too: $ cargo run
Compiling warp-report-1 v0.1.0 (/home/fernando/repos/sandbox/warp-report-1)
error[E0507]: cannot move out of `warp_svc`, a captured variable in an `FnMut` closure
--> src/main.rs:18:83
|
16 | let mut warp_svc = warp::service(route);
| ------------ captured outer variable
17 | let make_svc = hyper::service::make_service_fn(move |_| async move {
18 | let svc = hyper::service::service_fn(move |req: Request<Body>| async move {
| ___________________________________________________________________________________^
19 | | println!("before request");
20 | | let resp = warp_svc.call(req).await;
| | --------
| | |
| | move occurs because `warp_svc` has type `warp::filter::service::FilteredService<warp::filters::cors::internal::CorsFilter<warp::filter::map::Map<impl warp::filter::Filter+std::marker::Copy, [closure@src/main.rs:13:14: 13:35]>>>`, which does not implement the `Copy` trait
| | move occurs due to use in generator
21 | | println!("after request");
22 | | resp
23 | | });
| |_________^ move out of `warp_svc` occurs here
error[E0507]: cannot move out of `warp_svc`, a captured variable in an `FnMut` closure
--> src/main.rs:17:72
|
16 | let mut warp_svc = warp::service(route);
| ------------ captured outer variable
17 | let make_svc = hyper::service::make_service_fn(move |_| async move {
| ________________________________________________________________________^
18 | | let svc = hyper::service::service_fn(move |req: Request<Body>| async move {
19 | | println!("before request");
20 | | let resp = warp_svc.call(req).await;
| | --------
| | |
| | move occurs because `warp_svc` has type `warp::filter::service::FilteredService<warp::filters::cors::internal::CorsFilter<warp::filter::map::Map<impl warp::filter::Filter+std::marker::Copy, [closure@src/main.rs:13:14: 13:35]>>>`, which does not implement the `Copy` trait
| | move occurs due to use in generator
... |
24 | | Ok::<_, Infallible>(svc)
25 | | });
| |_____^ move out of `warp_svc` occurs here
error: aborting due to 2 previous errors
For more information about this error, try `rustc --explain E0507`.
error: could not compile `warp-report-1`.
To learn more, run the command again with --verbose. I'm new to the language, so I don't know if I'm doing something wrong on the code, but it seems that some filters are missing the implementation of the The custom Please, let me know if I should do something in a different way, or if I can help in someway! Thank you! |
In order to avoid the problem of "move out of" errors, you can try putting It may require cloning at each step of the |
@asonix thank you! It worked! I've tried clone the route before, but not as much as I needed. I'll post an example of that here, for future reference if anyone face the same error: use std::convert::Infallible;
use warp::Filter;
use hyper::{Body, Request};
use tower_service::Service;
#[tokio::main]
async fn main() -> Result<(), hyper::error::Error> {
let http_client = hyper::Client::new();
let route = warp::any()
.and(warp::any().map(move || http_client.clone()))
.map(|_http_client| "Hello From Warp!")
.with(warp::cors().allow_any_origin())
.boxed();
let warp_svc = warp::service(route);
let make_svc = hyper::service::make_service_fn(move |_| {
let warp_svc = warp_svc.clone();
async move {
let svc = hyper::service::service_fn(move |req: Request<Body>| {
let mut warp_svc = warp_svc.clone();
async move {
println!("before request");
let resp = warp_svc.call(req).await;
println!("after request");
resp
}
});
Ok::<_, Infallible>(svc)
}
});
hyper::Server::bind(&([127, 0, 0, 1], 3030).into())
.serve(make_svc)
.await?;
Ok(())
} |
In my opinion the current advised solution of using hyper service conversion to implement "wrapping" functionality isn't good enough. As @hawkw put it in #408, this is just not a very "warpy" API. If I'm not missing something, you can't convert a hyper service back to a filter, which breaks the complete composability chain. I understood the reason to seal wrap was that @seanmonstar wasn't sure yet if it could fit all needs. This was almost 2 years ago though. The API didn't really change since and PRs like #408 and #513 show that it is useful in its current form. So to move forward with this RFC I think it would make sense to either collect problems we are seeing with the current API, or allow custom |
I must also admit to becoming a bit frustrated with the lack of easy customization with warp filters. One of the strengths of FP is the ability to mix and match and customize due to referential transparency, but warp hides all of its customizable parts behind I'm looking to make a custom authentication filter which combines a few steps (getting the auth header, checking the token with a configurable service, and returning the identity data). I can do it with a ton of snaking .ands .and_thens .or_else and .recovers, but it'd be easier if I could just have a Why is this being locked away behind private flags? |
@fahrradflucht as seen with #640 unsealing |
I personally like I do think that its a little weird in that any custom filter will look like I've realized my pet issue with getting tracing support doesn't seem to be resolved by just unsealing |
yeah that can be achieved by calling
that is unfortunately a limitation of lack of specialization, |
|
I would also like to use something like Is there any way I could help with it to move it forward? Is there any code needed to ship it? Or is it more about figuring out the best developer ergonomics? |
submitted #693 |
@jxs may we please have an example of how to implement the example in the original post using the edit: After many hours i found a workable solution. let echo_x_request_id =
warp::any()
.and(warp::header::optional::<u64>("x-request-id"))
.map(|request_id:Option<u64>| {
let resp = Response::builder();
if let Some(request_id) = request_id {
resp.header("x-request-id", format!("{}", request_id))
}else{
resp
}
}); I wanted to use use warp::{Filter, reply::Response};
pub fn echo_header_x_request_id<FilterType, ExtractType>(
filter: FilterType
) -> impl Filter<Extract = (&'static str,)> + Clone + Send + Sync + 'static
where
FilterType: Filter<
Extract = (ExtractType,),
Error = std::convert::Infallible
> + Clone + Send + Sync + 'static,
FilterType::Extract: warp::Reply,
{
warp::any()
.and(filter)
.and(warp::header::<i64>("x-request-id"))
.map(|something:warp::Reply /*???*/, id:i64|{
// somehow inject the header???
todo!()
}).recover(|something|something)
} If i remove this line |
@thehappycheese possibly something like this? use std::convert::Infallible;
use warp::{http::HeaderValue, reply::Response, Filter, Rejection, Reply};
fn add_request_id<F, T>(
filter: F,
) -> impl Filter<Extract = (Response,), Error = Rejection> + Clone + Send + Sync + 'static
where
F: Filter<Extract = (T,), Error = Infallible> + Clone + Send + Sync + 'static,
T: Reply,
{
static HEADER: &str = "x-request-id";
warp::any()
.and(warp::header::optional::<u64>(HEADER))
.and(filter)
.map(|id, res: T| {
let mut res = res.into_response();
if let Some(id) = id {
res.headers_mut().insert(HEADER, HeaderValue::from(id));
}
res
})
}
#[tokio::main]
async fn main() {
let routes = warp::any()
.map(|| "Hello, World!")
.with(warp::wrap_fn(add_request_id));
warp::serve(routes).run(([127, 0, 0, 1], 3030)).await;
} |
Hi @kjvalencik :) Thankyou so much for for that example it worked for me. I had not yet understood that headers need to be extracted prior to the wrapped filter, and I didn't know about the If you don't mind one more question, is there a reason you used This is the code I ended up withuse warp::{http::HeaderValue, reply::Response, Filter, Rejection, Reply};
/// Echoes back the `x-request-id` header from the request, if it is present and
/// can be parsed as a `u64`. Otherwise the response is not modified.
/// If the header is present and valid it will be echoed even if the response
/// from `filter` is a rejection (edit: actually no it doesn't seem to add the header to a rejection :( )
///
/// # Example
///
/// ```
/// use warp::{
/// Filter,
/// reply::Reply,
/// http::StatusCode,
/// wrap_fn,
/// };
///
/// let filter = warp::any()
/// .map(|| StatusCode::OK)
/// .with(wrap_fn(echo_x_request_id));
/// ```
pub fn echo_x_request_id<F, T>(
filter: F,
) -> impl Filter<Extract = (Response,), Error = Rejection> + Clone + Send + Sync + 'static
where
F: Filter<Extract = (T,), Error = Rejection> + Clone + Send + Sync + 'static,
T: Reply,
{
static HEADER: &str = "x-request-id";
warp::any()
.and(
warp::header::optional::<u64>(HEADER) // only echo if valid u64
.or(warp::any().map(|| None)) // prevent invalid header rejection
.unify(),
)
.and(filter)
.map(move |id: Option<u64>, reply: T| {
let mut response = reply.into_response();
// note response status is not checked; the header will be
// echo-ed even on a rejection
// edit: mmm it seems that isn't true. Could be a problem
if let Some(id) = id {
response.headers_mut().insert(HEADER, HeaderValue::from(id));
}
response
})
} |
I was using Infallible because my example code was Infallible. I wanted it to be generic over either but I couldn't figure out a way to do it. Mostly because CombineRejection is private and there's no |
Under the current api,
Filter::with
requires that the type beW: Wrap
however theWrap
trait is sealed making it difficult to write custom wrapping filters. For instance, I'm trying to write a filter that will forward a header from a request to the response. So far I've come up with the filter to generate the either grab the request id or generate a new one:But I can't figure out using the combinators provided how to store the request-id in the filter and use it on the response.
It also seems that you cannot call
map
after returning awarp::reply
in the combinator chain so I can't even make the above work with boiler plate something likeThe text was updated successfully, but these errors were encountered: