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

feat(grpc): grpc support #45

Merged
merged 10 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/exception_handler/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ edition = "2021"

[dependencies]
silent = { path = "../../silent", features = ["full"] }
serde = { version = "1.0.198", features = ["derive"] }
serde = { version = "1.0.200", features = ["derive"] }
2 changes: 1 addition & 1 deletion examples/form/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ edition = "2021"

[dependencies]
silent = { path = "../../silent" }
serde = { version = "1.0.198", features = ["derive"] }
serde = { version = "1.0.200", features = ["derive"] }
32 changes: 32 additions & 0 deletions examples/grpc/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[package]
name = "example-grpc"
edition.workspace = true
authors.workspace = true
homepage.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
version.workspace = true

[[bin]]
name = "example-grpc-client"
path = "src/client.rs"

[dependencies]
tonic = { git = "https://github.com/alexrudy/tonic", branch = "hyper-1.0" }
tonic-reflection = { git = "https://github.com/alexrudy/tonic", branch = "hyper-1.0" }
prost = "0.12"
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
silent = { path = "../../silent", features = ["grpc"] }
axum = "0.7"
async-trait = "0.1.80"
hyper = "1.3.1"
hyper-util = "0.1.3"
bytes = "1.6.0"
pin-project-lite = "0.2.13"
http-body = "1.0.0"
http = "1.1.0"
http-body-util = "0.1.1"

[build-dependencies]
tonic-build = { git = "https://github.com/alexrudy/tonic", branch = "hyper-1.0" }
11 changes: 11 additions & 0 deletions examples/grpc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
### Run server

```bash
cargo run -p example-grpc --bin example-grpc
```

### Run client

```bash
cargo run -p example-grpc --bin example-grpc-server
```
5 changes: 5 additions & 0 deletions examples/grpc/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
fn main() {
tonic_build::configure()
.compile(&["proto/helloworld.proto"], &["/proto"])
.unwrap();
}
37 changes: 37 additions & 0 deletions examples/grpc/proto/helloworld.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2015 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";

package helloworld;

// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
string name = 1;
}

// The response message containing the greetings
message HelloReply {
string message = 1;
}
33 changes: 33 additions & 0 deletions examples/grpc/src/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use hello_world::greeter_client::GreeterClient;
use hello_world::HelloRequest;

pub mod hello_world {
tonic::include_proto!("helloworld");
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = GreeterClient::connect("http://0.0.0.0:50051").await?;

let request = tonic::Request::new(HelloRequest {
name: "Tonic".into(),
});

let response = client.say_hello(request).await?;

println!("RESPONSE={:?}", response);

println!("MESSAGE={:?}", response.into_inner());

let request = tonic::Request::new(HelloRequest {
name: "Tonic".into(),
});

let response = client.say_hello(request).await?;

println!("RESPONSE={:?}", response);

println!("MESSAGE={:?}", response.into_inner());

Ok(())
}
49 changes: 49 additions & 0 deletions examples/grpc/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use async_trait::async_trait;
use hello_world::greeter_server::{Greeter, GreeterServer};
use hello_world::{HelloReply, HelloRequest};
use silent::prelude::{logger, HandlerAppend, Level, Route, RouteService, Server};
use tonic::{transport::Server as TonicServer, Request, Response, Status};

mod client;

pub mod hello_world {
tonic::include_proto!("helloworld"); // The string specified here must match the proto package name
}

#[derive(Debug, Default)]
pub struct MyGreeter {}

#[async_trait]
impl Greeter for MyGreeter {
async fn say_hello(
&self,
request: Request<HelloRequest>, // Accept request of type HelloRequest
) -> Result<Response<HelloReply>, Status> {
// Return an instance of type HelloReply
println!("Got a request: {:?}", request);

let reply = HelloReply {
message: format!("Hello {}!", request.into_inner().name), // We must use .into_inner() as the fields of gRPC requests and responses are private
};

Ok(Response::new(reply)) // Send back our formatted greeting
}
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let greeter = MyGreeter::default();
logger::fmt().with_max_level(Level::INFO).init();
let greeter_server = GreeterServer::new(greeter);
let grpc = TonicServer::builder()
// Wrap all services in the middleware stack
.add_service(greeter_server)
.into_router();
let route = Route::new("").get(|_req| async { Ok("hello world") });
let root = route.route().with_grpc(grpc.into());
Server::new()
.bind("0.0.0.0:50051".parse().unwrap())
.serve(root)
.await;
Ok(())
}
33 changes: 33 additions & 0 deletions examples/grpc_h2c/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[package]
name = "example-grpc-h2c"
edition.workspace = true
authors.workspace = true
homepage.workspace = true
license.workspace = true
readme.workspace = true
repository.workspace = true
version.workspace = true

[[bin]]
name = "example-grpc-client"
path = "src/client.rs"

[dependencies]
tonic = { git = "https://github.com/alexrudy/tonic", branch = "hyper-1.0" }
tonic-reflection = { git = "https://github.com/alexrudy/tonic", branch = "hyper-1.0" }
prost = "0.12"
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
silent = { path = "../../silent", features = ["grpc"] }
axum = "0.7"
async-trait = "0.1.80"
hyper = "1.3.1"
hyper-util = "0.1.3"
bytes = "1.6.0"
pin-project-lite = "0.2.13"
http-body = "1.0.0"
http = "1.1.0"
http-body-util = "0.1.1"
tower = "0.4.13"

[build-dependencies]
tonic-build = { git = "https://github.com/alexrudy/tonic", branch = "hyper-1.0" }
11 changes: 11 additions & 0 deletions examples/grpc_h2c/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
### Run server

```bash
cargo run -p example-grpc --bin example-grpc
```

### Run client

```bash
cargo run -p example-grpc --bin example-grpc-server
```
10 changes: 10 additions & 0 deletions examples/grpc_h2c/build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
use std::{env, path::PathBuf};

fn main() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());

tonic_build::configure()
.file_descriptor_set_path(out_dir.join("helloworld_descriptor.bin"))
.compile(&["proto/helloworld.proto"], &["/proto"])
.unwrap();
}
37 changes: 37 additions & 0 deletions examples/grpc_h2c/proto/helloworld.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2015 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

syntax = "proto3";

option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";

package helloworld;

// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
string name = 1;
}

// The response message containing the greetings
message HelloReply {
string message = 1;
}
92 changes: 92 additions & 0 deletions examples/grpc_h2c/src/client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
use hello_world::greeter_client::GreeterClient;
use hello_world::HelloRequest;
use http::Uri;
use hyper_util::client::legacy::Client;
use hyper_util::rt::TokioExecutor;

pub mod hello_world {
tonic::include_proto!("helloworld");
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let origin = Uri::from_static("http://[::1]:50051");
let h2c_client = h2c::H2cChannel {
client: Client::builder(TokioExecutor::new()).build_http(),
};

let mut client = GreeterClient::with_origin(h2c_client, origin);

let request = tonic::Request::new(HelloRequest {
name: "Tonic".into(),
});

let response = client.say_hello(request).await?;

println!("RESPONSE={:?}", response);

Ok(())
}

mod h2c {
use std::{
pin::Pin,
task::{Context, Poll},
};

use hyper::body::Incoming;
use hyper_util::{
client::legacy::{connect::HttpConnector, Client},
rt::TokioExecutor,
};
use tonic::body::{empty_body, BoxBody};
use tower::Service;

pub struct H2cChannel {
pub client: Client<HttpConnector, BoxBody>,
}

impl Service<http::Request<BoxBody>> for H2cChannel {
type Response = http::Response<Incoming>;
type Error = hyper::Error;
type Future =
Pin<Box<dyn std::future::Future<Output = Result<Self::Response, Self::Error>> + Send>>;

fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}

fn call(&mut self, request: http::Request<BoxBody>) -> Self::Future {
let client = self.client.clone();

Box::pin(async move {
let origin = request.uri();

let h2c_req = hyper::Request::builder()
.uri(origin)
.method(request.method())
.header(http::header::UPGRADE, "h2c")
.body(empty_body())
.unwrap();

let res = client.request(h2c_req).await.unwrap();

if res.status() != http::StatusCode::SWITCHING_PROTOCOLS {
panic!("Our server didn't upgrade: {}", res.status());
}

let upgraded_io = hyper::upgrade::on(res).await.unwrap();

// In an ideal world you would somehow cache this connection
let (mut h2_client, conn) =
hyper::client::conn::http2::Builder::new(TokioExecutor::new())
.handshake(upgraded_io)
.await
.unwrap();
tokio::spawn(conn);

h2_client.send_request(request).await
})
}
}
}
Loading
Loading