Skip to content

Commit

Permalink
Merge pull request #45 from hubertshelley/features/grpc_support
Browse files Browse the repository at this point in the history
feat(grpc): grpc support
  • Loading branch information
hubertshelley authored May 16, 2024
2 parents 2ccabca + fabcb2f commit 9d0d827
Show file tree
Hide file tree
Showing 49 changed files with 1,266 additions and 348 deletions.
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

0 comments on commit 9d0d827

Please sign in to comment.