I follow https://github.com/hyperium/tonic/blob/master/examples/src/tower/server.rs to capture body content of a rpc request before calling logic request.
But I get:
type mismatch resolving `<Route as Service<Request<Body>>>::Error == Status`
--> server/src/rpc.rs:645:85
|
645 | routes.routes().into_axum_router().layer(GrpcWebLayer::new()).layer(cors_layer).layer(layer)
| ^^^^^ expected `Status`, found `Infallible`
|
note: required for `AuthMiddleware<Route>` to implement `Service<http::Request<AxumBody>>`
--> server/src/rpc.rs:666:18
|
666 | impl<S, ResBody> Service<http::Request<AxumBody>> for AuthMiddleware<S>
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^
...
671 | Error = Status,
| -------------- unsatisfied trait bound introduced here
I do:
#[derive(Debug, Clone)]
pub struct AuthMiddleware<S> {
inner: S,
}
type BoxFuture<'a, T> = Pin<Box<dyn std::future::Future<Output = T> + Send + 'a>>;
impl<S, ResBody> Service<http::Request<AxumBody>> for AuthMiddleware<S>
where
S: Service<
http::Request<AxumBody>,
Response = http::Response<ResBody>,
Error = Status,
> + Clone
+ Send
+ 'static,
S::Future: Send + 'static,
{
type Response = S::Response;
type Error = S::Error;
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: http::Request<axum::body::Body>) -> Self::Future {
let clone = self.inner.clone();
let mut inner = std::mem::replace(&mut self.inner, clone);
Box::pin(async move {
let (parts, body) = req.into_parts();
let content = body
.collect()
.await
.map_err(|_err| {
println!("error");
})
.unwrap()
.to_bytes();
let content_without_path = "";
match parts.headers.get("authorization") {
Some(t) => {
let validation = &mut Validation::new(Algorithm::ES256);
validation.validate_exp = false;
let t_string = t.to_str().unwrap().replace("Bearer ", "");
match decode::<Claims>(&t_string, decoding_key, validation) {
Ok(token_data) => {
// Compare body with scope
if token_data.claims.scope == content_without_path {
let body = AxumBody::from(content);
let response = inner
.call(http::Request::from_parts(parts, body))
.await
.map_err(|_err| {
println!("error");
})
.unwrap();
Ok(response)
} else {
Err(Status::unauthenticated("Not allowed"))
}
}
Err(err) => Err(Status::unauthenticated(err.to_string())),
}
}
None => Err(Status::unauthenticated("No token found")),
}
})
}
}
I declare return type to be Status on Error, I don't declare or use std::convert::Infallible
.
I never use Infallible, I don't know what it's for So I don't inderstand what is wrong.
The example you are following is using tower directly. However you adding layers after .into_axum_router()
which means the they have to follow axum's conventions. Specifically axum expects middleware to ultimately be infallible - errors should be converted to responses.
From axum::middleware
:
If you choose to implement a custom error type such as type
Error = BoxError
(a boxed opaque error), or any other error type that is notInfallible
, you must use aHandleErrorLayer
, here is an example using aServiceBuilder
:ServiceBuilder::new() .layer(HandleErrorLayer::new(|_: BoxError| async { // because axum uses infallible errors, you must handle your custom error type from your middleware here StatusCode::BAD_REQUEST })) .layer( // <your actual layer which DOES return an error> );
You can use the above wrapper or it'd likely be better to just change your service to be Infallible
and return your errors as Response<Body>
. If Status
is the one from tonic, then .to_http()
should be able to do that.