Skip to content
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

Add generic JSON-over-GRPC wrapper to examples/rest-grpc-multiplex #1082

Closed
wants to merge 6 commits into from

Conversation

muusbolla
Copy link

@muusbolla muusbolla commented Jun 10, 2022

Resolves #1068 and #1069.

Add a function to examples/rest-grpc-multiplex that provides a generic wrapper to expose a HTTP/JSON frontend for any gRPC call. For the provided example code, this provides a HTTP endpoint with the following behavior:

POST localhost:3000/Hello
Header: content-type "application/json"
Body: { "name": "muusbolla" }

RESPONSE 200 OK
Header: content-type "application/json"
Body: { "message": "Hello muusbolla, my name is HAL 9000." }

Motivation

Make it easy to provide an API that is compatible with both gRPC and JSON, that uses a single backing implementation, with minimal additional boilerplate required for each new endpoint. This can be very helpful for users to create backwards/forwards-compatible APIs.

Solution

For context - the following is a specific implementation of a JSON wrapper around the GrpcServiceImpl::say_hello function that is compatible with the axum::Handler trait:

async fn json_wrap_hello(Json(body): Json<HelloRequest>) -> Result<Json<HelloReply>, GrpcErrorAsJson> {
    let handler = GRPC_SERVICE.get().unwrap();
    let r = handler.say_hello(tonic::Request::new(body)).await;
    match r {
        Ok(r) => Ok(Json(r.into_inner())),
        Err(e) => Err(GrpcErrorAsJson(e))
    }
}

#[tokio::main]
async fn main() {
    let rest = Router::new()
        .route("/Hello", axum::routing::any(json_wrap_hello);
}

The json_wrap_grpc function included in this PR is a genericization of the above code, making it possible to wrap any GRPC handler into an axum::Handler with a single function call.

It was quite a hassle to make the generic version compile (I was butting heads with rust-lang/rust#36582 and/or rust-lang/rust#41078), so I am hoping that this can be helpful to others who might want to do something similar.

Comment on lines +61 to +67
-> impl FnOnce(Json<ReqT>)
-> Pin<Box<dyn Future<Output = Result<Json<ResT>, GrpcErrorAsJson>> + Send + 'a>> + Clone + Send + Sized + 'static
where
F: FnOnce(&'r GrpcServiceImpl, tonic::Request<ReqT>)
-> Pin<Box<dyn Future<Output = Result<tonic::Response<ResT>, tonic::Status>> + Send + 'r>> + Clone + Send + Sync + 'static,
for<'de> ReqT: serde::Deserialize<'de> + Send + 'a,
ResT: serde::Serialize
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate what you're trying to do but we cannot merge something this complicated as an example. That is just gonna confuse people. So unless you're able to find a simpler solution I don't think we can merge this.

Copy link
Author

@muusbolla muusbolla Jun 15, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I understand. Now that I've made the PR at least people who are looking to do this can find it from the discussion.

@takkuumi
Copy link
Contributor

To clarify here, I integrated tonic in axum, which is mainly used to solve the internal communication of the cluster. But I think, your example is helpful for wanting to access rpc through rest interface. Before, I referred to some spring projects, which can implement an interface to receive http, grpc and websocket access at the same time through annotations, but I don't have time to study these details now. When I have time, I'd like to try if I can do it based on axum and tonic.

@muusbolla
Copy link
Author

To clarify here, I integrated tonic in axum, which is mainly used to solve the internal communication of the cluster. But I think, your example is helpful for wanting to access rpc through rest interface. Before, I referred to some spring projects, which can implement an interface to receive http, grpc and websocket access at the same time through annotations, but I don't have time to study these details now. When I have time, I'd like to try if I can do it based on axum and tonic.

If there was an implementation that was codegen-based, or somehow had access to the routing of tonic and could automatically generate routes based on that, that would be really nice.

@muusbolla muusbolla closed this Jun 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

How to implement wrapper function that implements Handler?
3 participants