chore: Reorganize examples and interop crates (#180)
* chore: Reorganize examples and interop crates * fix interop tests
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
[package]
|
||||
name = "tonic-examples"
|
||||
version = "0.1.0"
|
||||
authors = ["Lucio Franco <[email protected]>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
license = "MIT"
|
||||
|
||||
[[bin]]
|
||||
name = "helloworld-server"
|
||||
path = "src/helloworld/server.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "helloworld-client"
|
||||
path = "src/helloworld/client.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "blocking-client"
|
||||
path = "src/blocking/client.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "routeguide-server"
|
||||
path = "src/routeguide/server.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "routeguide-client"
|
||||
path = "src/routeguide/client.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "authentication-client"
|
||||
path = "src/authentication/client.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "authentication-server"
|
||||
path = "src/authentication/server.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "load-balance-client"
|
||||
path = "src/load_balance/client.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "load-balance-server"
|
||||
path = "src/load_balance/server.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "tls-client"
|
||||
path = "src/tls/client.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "tls-server"
|
||||
path = "src/tls/server.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "tls-client-auth-server"
|
||||
path = "src/tls_client_auth/server.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "tls-client-auth-client"
|
||||
path = "src/tls_client_auth/client.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "multiplex-server"
|
||||
path = "src/multiplex/server.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "multiplex-client"
|
||||
path = "src/multiplex/client.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "gcp-client"
|
||||
path = "src/gcp/client.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "tracing-client"
|
||||
path = "src/tracing/client.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "tracing-server"
|
||||
path = "src/tracing/server.rs"
|
||||
|
||||
[dependencies]
|
||||
tonic = { path = "../tonic", features = ["tls"] }
|
||||
bytes = "0.4"
|
||||
prost = "0.5"
|
||||
|
||||
tokio = { version = "0.2", features = ["rt-threaded", "time", "stream", "fs", "macros"] }
|
||||
futures = { version = "0.3", default-features = false, features = ["alloc"]}
|
||||
async-stream = "0.2"
|
||||
http = "0.2"
|
||||
tower = { git = "https://github.com/tower-rs/tower" }
|
||||
|
||||
# Required for routeguide
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
rand = "0.6"
|
||||
|
||||
# Tracing
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.2.0-alpha", features = ["tracing-log"] }
|
||||
tracing-attributes = "0.1"
|
||||
tracing-futures = "0.2"
|
||||
|
||||
# Required for wellknown types
|
||||
prost-types = "0.5"
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = { path = "../tonic-build" }
|
||||
@@ -0,0 +1,84 @@
|
||||
# Examples
|
||||
|
||||
Set of examples that show off the features provided by `tonic`.
|
||||
|
||||
## Helloworld
|
||||
|
||||
### Client
|
||||
|
||||
```bash
|
||||
$ cargo run --bin helloworld-client
|
||||
```
|
||||
|
||||
### Server
|
||||
|
||||
```bash
|
||||
$ cargo run --bin helloworld-server
|
||||
```
|
||||
|
||||
## RouteGuide
|
||||
|
||||
### Client
|
||||
|
||||
```bash
|
||||
$ cargo run --bin routeguide-client
|
||||
```
|
||||
|
||||
### Server
|
||||
|
||||
```bash
|
||||
$ cargo run --bin routeguide-server
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
### Client
|
||||
|
||||
```bash
|
||||
$ cargo run --bin authentication-client
|
||||
```
|
||||
|
||||
### Server
|
||||
|
||||
```bash
|
||||
$ cargo run --bin authentication-server
|
||||
```
|
||||
|
||||
## Load balance
|
||||
|
||||
### Client
|
||||
|
||||
```bash
|
||||
$ cargo run --bin load-balance-client
|
||||
```
|
||||
|
||||
### Server
|
||||
|
||||
```bash
|
||||
$ cargo run --bin load-balance-server
|
||||
```
|
||||
|
||||
## TLS (rustls)
|
||||
|
||||
### Client
|
||||
|
||||
```bash
|
||||
$ cargo run --bin tls-client
|
||||
```
|
||||
|
||||
### Server
|
||||
|
||||
```bash
|
||||
$ cargo run --bin tls-server
|
||||
```
|
||||
|
||||
|
||||
### Notes:
|
||||
|
||||
If you are using the `codegen` feature, then the following dependencies are
|
||||
**required**:
|
||||
|
||||
* [bytes](https://crates.io/crates/bytes)
|
||||
* [prost](https://crates.io/crates/prost)
|
||||
* [prost-derive](https://crates.io/crates/prost-derive)
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
fn main() {
|
||||
tonic_build::compile_protos("proto/helloworld/helloworld.proto").unwrap();
|
||||
tonic_build::compile_protos("proto/routeguide/route_guide.proto").unwrap();
|
||||
tonic_build::compile_protos("proto/echo/echo.proto").unwrap();
|
||||
tonic_build::compile_protos("proto/google/pubsub/pubsub.proto").unwrap();
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,702 @@
|
||||
[
|
||||
{
|
||||
"location": {
|
||||
"latitude": 407838351,
|
||||
"longitude": -746143763
|
||||
},
|
||||
"name": "Patriots Path, Mendham, NJ 07945, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 408122808,
|
||||
"longitude": -743999179
|
||||
},
|
||||
"name": "101 New Jersey 10, Whippany, NJ 07981, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 413628156,
|
||||
"longitude": -749015468
|
||||
},
|
||||
"name": "U.S. 6, Shohola, PA 18458, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 419999544,
|
||||
"longitude": -740371136
|
||||
},
|
||||
"name": "5 Conners Road, Kingston, NY 12401, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 414008389,
|
||||
"longitude": -743951297
|
||||
},
|
||||
"name": "Mid Hudson Psychiatric Center, New Hampton, NY 10958, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 419611318,
|
||||
"longitude": -746524769
|
||||
},
|
||||
"name": "287 Flugertown Road, Livingston Manor, NY 12758, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 406109563,
|
||||
"longitude": -742186778
|
||||
},
|
||||
"name": "4001 Tremley Point Road, Linden, NJ 07036, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 416802456,
|
||||
"longitude": -742370183
|
||||
},
|
||||
"name": "352 South Mountain Road, Wallkill, NY 12589, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 412950425,
|
||||
"longitude": -741077389
|
||||
},
|
||||
"name": "Bailey Turn Road, Harriman, NY 10926, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 412144655,
|
||||
"longitude": -743949739
|
||||
},
|
||||
"name": "193-199 Wawayanda Road, Hewitt, NJ 07421, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 415736605,
|
||||
"longitude": -742847522
|
||||
},
|
||||
"name": "406-496 Ward Avenue, Pine Bush, NY 12566, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 413843930,
|
||||
"longitude": -740501726
|
||||
},
|
||||
"name": "162 Merrill Road, Highland Mills, NY 10930, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 410873075,
|
||||
"longitude": -744459023
|
||||
},
|
||||
"name": "Clinton Road, West Milford, NJ 07480, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 412346009,
|
||||
"longitude": -744026814
|
||||
},
|
||||
"name": "16 Old Brook Lane, Warwick, NY 10990, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 402948455,
|
||||
"longitude": -747903913
|
||||
},
|
||||
"name": "3 Drake Lane, Pennington, NJ 08534, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 406337092,
|
||||
"longitude": -740122226
|
||||
},
|
||||
"name": "6324 8th Avenue, Brooklyn, NY 11220, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 406421967,
|
||||
"longitude": -747727624
|
||||
},
|
||||
"name": "1 Merck Access Road, Whitehouse Station, NJ 08889, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 416318082,
|
||||
"longitude": -749677716
|
||||
},
|
||||
"name": "78-98 Schalck Road, Narrowsburg, NY 12764, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 415301720,
|
||||
"longitude": -748416257
|
||||
},
|
||||
"name": "282 Lakeview Drive Road, Highland Lake, NY 12743, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 402647019,
|
||||
"longitude": -747071791
|
||||
},
|
||||
"name": "330 Evelyn Avenue, Hamilton Township, NJ 08619, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 412567807,
|
||||
"longitude": -741058078
|
||||
},
|
||||
"name": "New York State Reference Route 987E, Southfields, NY 10975, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 416855156,
|
||||
"longitude": -744420597
|
||||
},
|
||||
"name": "103-271 Tempaloni Road, Ellenville, NY 12428, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 404663628,
|
||||
"longitude": -744820157
|
||||
},
|
||||
"name": "1300 Airport Road, North Brunswick Township, NJ 08902, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 407113723,
|
||||
"longitude": -749746483
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 402133926,
|
||||
"longitude": -743613249
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 400273442,
|
||||
"longitude": -741220915
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 411236786,
|
||||
"longitude": -744070769
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 411633782,
|
||||
"longitude": -746784970
|
||||
},
|
||||
"name": "211-225 Plains Road, Augusta, NJ 07822, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 415830701,
|
||||
"longitude": -742952812
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 413447164,
|
||||
"longitude": -748712898
|
||||
},
|
||||
"name": "165 Pedersen Ridge Road, Milford, PA 18337, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 405047245,
|
||||
"longitude": -749800722
|
||||
},
|
||||
"name": "100-122 Locktown Road, Frenchtown, NJ 08825, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 418858923,
|
||||
"longitude": -746156790
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 417951888,
|
||||
"longitude": -748484944
|
||||
},
|
||||
"name": "650-652 Willi Hill Road, Swan Lake, NY 12783, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 407033786,
|
||||
"longitude": -743977337
|
||||
},
|
||||
"name": "26 East 3rd Street, New Providence, NJ 07974, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 417548014,
|
||||
"longitude": -740075041
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 410395868,
|
||||
"longitude": -744972325
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 404615353,
|
||||
"longitude": -745129803
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 406589790,
|
||||
"longitude": -743560121
|
||||
},
|
||||
"name": "611 Lawrence Avenue, Westfield, NJ 07090, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 414653148,
|
||||
"longitude": -740477477
|
||||
},
|
||||
"name": "18 Lannis Avenue, New Windsor, NY 12553, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 405957808,
|
||||
"longitude": -743255336
|
||||
},
|
||||
"name": "82-104 Amherst Avenue, Colonia, NJ 07067, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 411733589,
|
||||
"longitude": -741648093
|
||||
},
|
||||
"name": "170 Seven Lakes Drive, Sloatsburg, NY 10974, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 412676291,
|
||||
"longitude": -742606606
|
||||
},
|
||||
"name": "1270 Lakes Road, Monroe, NY 10950, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 409224445,
|
||||
"longitude": -748286738
|
||||
},
|
||||
"name": "509-535 Alphano Road, Great Meadows, NJ 07838, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 406523420,
|
||||
"longitude": -742135517
|
||||
},
|
||||
"name": "652 Garden Street, Elizabeth, NJ 07202, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 401827388,
|
||||
"longitude": -740294537
|
||||
},
|
||||
"name": "349 Sea Spray Court, Neptune City, NJ 07753, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 410564152,
|
||||
"longitude": -743685054
|
||||
},
|
||||
"name": "13-17 Stanley Street, West Milford, NJ 07480, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 408472324,
|
||||
"longitude": -740726046
|
||||
},
|
||||
"name": "47 Industrial Avenue, Teterboro, NJ 07608, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 412452168,
|
||||
"longitude": -740214052
|
||||
},
|
||||
"name": "5 White Oak Lane, Stony Point, NY 10980, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 409146138,
|
||||
"longitude": -746188906
|
||||
},
|
||||
"name": "Berkshire Valley Management Area Trail, Jefferson, NJ, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 404701380,
|
||||
"longitude": -744781745
|
||||
},
|
||||
"name": "1007 Jersey Avenue, New Brunswick, NJ 08901, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 409642566,
|
||||
"longitude": -746017679
|
||||
},
|
||||
"name": "6 East Emerald Isle Drive, Lake Hopatcong, NJ 07849, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 408031728,
|
||||
"longitude": -748645385
|
||||
},
|
||||
"name": "1358-1474 New Jersey 57, Port Murray, NJ 07865, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 413700272,
|
||||
"longitude": -742135189
|
||||
},
|
||||
"name": "367 Prospect Road, Chester, NY 10918, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 404310607,
|
||||
"longitude": -740282632
|
||||
},
|
||||
"name": "10 Simon Lake Drive, Atlantic Highlands, NJ 07716, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 409319800,
|
||||
"longitude": -746201391
|
||||
},
|
||||
"name": "11 Ward Street, Mount Arlington, NJ 07856, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 406685311,
|
||||
"longitude": -742108603
|
||||
},
|
||||
"name": "300-398 Jefferson Avenue, Elizabeth, NJ 07201, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 419018117,
|
||||
"longitude": -749142781
|
||||
},
|
||||
"name": "43 Dreher Road, Roscoe, NY 12776, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 412856162,
|
||||
"longitude": -745148837
|
||||
},
|
||||
"name": "Swan Street, Pine Island, NY 10969, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 416560744,
|
||||
"longitude": -746721964
|
||||
},
|
||||
"name": "66 Pleasantview Avenue, Monticello, NY 12701, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 405314270,
|
||||
"longitude": -749836354
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 414219548,
|
||||
"longitude": -743327440
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 415534177,
|
||||
"longitude": -742900616
|
||||
},
|
||||
"name": "565 Winding Hills Road, Montgomery, NY 12549, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 406898530,
|
||||
"longitude": -749127080
|
||||
},
|
||||
"name": "231 Rocky Run Road, Glen Gardner, NJ 08826, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 407586880,
|
||||
"longitude": -741670168
|
||||
},
|
||||
"name": "100 Mount Pleasant Avenue, Newark, NJ 07104, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 400106455,
|
||||
"longitude": -742870190
|
||||
},
|
||||
"name": "517-521 Huntington Drive, Manchester Township, NJ 08759, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 400066188,
|
||||
"longitude": -746793294
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 418803880,
|
||||
"longitude": -744102673
|
||||
},
|
||||
"name": "40 Mountain Road, Napanoch, NY 12458, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 414204288,
|
||||
"longitude": -747895140
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 414777405,
|
||||
"longitude": -740615601
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 415464475,
|
||||
"longitude": -747175374
|
||||
},
|
||||
"name": "48 North Road, Forestburgh, NY 12777, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 404062378,
|
||||
"longitude": -746376177
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 405688272,
|
||||
"longitude": -749285130
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 400342070,
|
||||
"longitude": -748788996
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 401809022,
|
||||
"longitude": -744157964
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 404226644,
|
||||
"longitude": -740517141
|
||||
},
|
||||
"name": "9 Thompson Avenue, Leonardo, NJ 07737, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 410322033,
|
||||
"longitude": -747871659
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 407100674,
|
||||
"longitude": -747742727
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 418811433,
|
||||
"longitude": -741718005
|
||||
},
|
||||
"name": "213 Bush Road, Stone Ridge, NY 12484, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 415034302,
|
||||
"longitude": -743850945
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 411349992,
|
||||
"longitude": -743694161
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 404839914,
|
||||
"longitude": -744759616
|
||||
},
|
||||
"name": "1-17 Bergen Court, New Brunswick, NJ 08901, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 414638017,
|
||||
"longitude": -745957854
|
||||
},
|
||||
"name": "35 Oakland Valley Road, Cuddebackville, NY 12729, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 412127800,
|
||||
"longitude": -740173578
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 401263460,
|
||||
"longitude": -747964303
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 412843391,
|
||||
"longitude": -749086026
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 418512773,
|
||||
"longitude": -743067823
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 404318328,
|
||||
"longitude": -740835638
|
||||
},
|
||||
"name": "42-102 Main Street, Belford, NJ 07718, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 419020746,
|
||||
"longitude": -741172328
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 404080723,
|
||||
"longitude": -746119569
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 401012643,
|
||||
"longitude": -744035134
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 404306372,
|
||||
"longitude": -741079661
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 403966326,
|
||||
"longitude": -748519297
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 405002031,
|
||||
"longitude": -748407866
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 409532885,
|
||||
"longitude": -742200683
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 416851321,
|
||||
"longitude": -742674555
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 406411633,
|
||||
"longitude": -741722051
|
||||
},
|
||||
"name": "3387 Richmond Terrace, Staten Island, NY 10303, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 413069058,
|
||||
"longitude": -744597778
|
||||
},
|
||||
"name": "261 Van Sickle Road, Goshen, NY 10924, USA"
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 418465462,
|
||||
"longitude": -746859398
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 411733222,
|
||||
"longitude": -744228360
|
||||
},
|
||||
"name": ""
|
||||
},
|
||||
{
|
||||
"location": {
|
||||
"latitude": 410248224,
|
||||
"longitude": -747127767
|
||||
},
|
||||
"name": "3 Hasta Way, Newton, NJ 07860, USA"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,28 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIE3DCCA0SgAwIBAgIRAObeYbJFiVQSGR8yk44dsOYwDQYJKoZIhvcNAQELBQAw
|
||||
gYUxHjAcBgNVBAoTFW1rY2VydCBkZXZlbG9wbWVudCBDQTEtMCsGA1UECwwkbHVj
|
||||
aW9ATHVjaW9zLVdvcmstTUJQIChMdWNpbyBGcmFuY28pMTQwMgYDVQQDDCtta2Nl
|
||||
cnQgbHVjaW9ATHVjaW9zLVdvcmstTUJQIChMdWNpbyBGcmFuY28pMB4XDTE5MDky
|
||||
OTIzMzUzM1oXDTI5MDkyOTIzMzUzM1owgYUxHjAcBgNVBAoTFW1rY2VydCBkZXZl
|
||||
bG9wbWVudCBDQTEtMCsGA1UECwwkbHVjaW9ATHVjaW9zLVdvcmstTUJQIChMdWNp
|
||||
byBGcmFuY28pMTQwMgYDVQQDDCtta2NlcnQgbHVjaW9ATHVjaW9zLVdvcmstTUJQ
|
||||
IChMdWNpbyBGcmFuY28pMIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA
|
||||
y/vE61ItbN/1qMYt13LMf+le1svwfkCCOPsygk7nWeRXmomgUpymqn1LnWiuB0+e
|
||||
4IdVH2f5E9DknWEpPhKIDMRTCbz4jTwQfHrxCb8EGj3I8oO73pJO5S/xCedM9OrZ
|
||||
qWcYWwN0GQ8cO/ogazaoZf1uTrRNHyzRyQsKyb412kDBTNEeldJZ2ljKgXXvh4HO
|
||||
2ZIk9K/ZAaAf6VN8K/89rlJ9/KPgRVNsyAapE+Pb8XXKtpzeFiEcUfuXVYWtkoW+
|
||||
xyn/Zu8A1L2CXMQ1sARh7P/42BTMKr5pfraYgcBGxKXLrxoySpxCO9KqeVveKy1q
|
||||
fPm5FCwFsXDr0koFLrCiR58mcIO/04Q9DKKTV4Z2a+LoqDJRY37KfBSc8sDMPhw5
|
||||
k7g3WPoa6QwXRjZTCA5fHWVgLOtcwLsnju5tBE4LDxwF6s+1wPF8NI5yUfufcEjJ
|
||||
Z6JBwgoWYosVj27Lx7KBNLU/57PX9ryee691zmtswt0tP0WVBAgalhYWg99RXoa3
|
||||
AgMBAAGjRTBDMA4GA1UdDwEB/wQEAwICBDASBgNVHRMBAf8ECDAGAQH/AgEAMB0G
|
||||
A1UdDgQWBBQdvlE4Bdcsjc9oaxjDCRu5FiuZkzANBgkqhkiG9w0BAQsFAAOCAYEA
|
||||
BP/6o1kPINksMJZSSXgNCPZskDLyGw7auUZBnQ0ocDT3W6gXQvT/27LM1Hxoj9Eh
|
||||
qU1TYdEt7ppecLQSGvzQ02MExG7H75art75oLiB+A5agDira937YbK4MCjqW481d
|
||||
bDhw6ixJnY1jIvwjEZxyH6g94YyL927aSPch51fys0kSnjkFzC2RmuzDADScc4XH
|
||||
5P1+/3dnIm3M5yfpeUzoaOrTXNmhn8p0RDIGrZ5kA5eISIGGD3Mm8FDssUNKndtO
|
||||
g4ojHUsxb14icnAYGeye1NOhGiqN6TEFcgr6MPd0XdFNZ5c0HUaBCfN6bc+JxDV5
|
||||
MKZVJdNeJsYYwilgJNHAyZgCi30JC20xeYVtTF7CEEsMrFDGJ70Kz7o/FnRiFsA1
|
||||
ZSwVVWhhkHG2VkT4vlo0O3fYeZpenYicvy+wZNTbGK83gzHWqxxNC1z3Etg5+HRJ
|
||||
F9qeMWPyfA3IHYXygiMcviyLcyNGG/SJ0EhUpYBN/Gg7wI5yFkcsxUDPPzd23O0M
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCiiWrmzpENsI+c
|
||||
Cz4aBpG+Pl8WOsrByfZx/ZnJdCZHO3MTYE6sCLhYssf0ygAEEGxvmkd4cxmfCfgf
|
||||
xuT8u+D7Y5zQSoymkbWdU6/9jbNY6Ovtc+a96I1LGXOKROQw6KR3PuqLpUqEOJiB
|
||||
l03qK+HMU0g56G1n31Od7HkJsDRvtePqy3I3LgpdcRps23sk46tCzZzhyfqIQ7Qf
|
||||
J5qZx93tA+pfy+Xtb9XIUTIWKIp1/uyfh8Fp8HA0c9zJCSZzJOX2j3GH1TYqkVgP
|
||||
egI2lhmdXhP5Q8vdhwy0UJaL28RJXA6UAg0tPZeWJe6pux9JiA81sI6My+Krrw8D
|
||||
yibkGTTbAgMBAAECggEANCQhRym9HsclSsnQgkjZOE6J8nep08EWbjsMurOoE/He
|
||||
WLjshAPIH6w6uSyUFLmwD51OkDVcYsiv8IG9s9YRtpOeGrPPqx/TQ0U1kAGFJ2CR
|
||||
Tvt/aizQJudjSVgQXCBFontsgp/j58bAJdKEDDtHlGSjJvCJKGlcSa0ypwj/yVXt
|
||||
frjROJNYzw9gMM7fN/IKF/cysdXSeLl/Q9RnHVIfC3jOFJutsILCK8+PC51dM8Fl
|
||||
IOjmPmiZ080yV8RBcMRECwl53vLOE3OOpR3ZijfNCY1KU8zWi1oELJ1o6f4+cBye
|
||||
7WPgFEoBew5XHXZ+ke8rh8cc0wth7ZTcC+xC/456AQKBgQDQr2EzBwXxYLF8qsN1
|
||||
R4zlzXILLdZN8a4bKfrS507/Gi1gDBHzfvbE7HfljeqrAkbKMdKNkbz3iS85SguH
|
||||
jsM047xUGJg0PAcwBLHUedlSn1xDDcDHW6X8ginpA2Zz1+WAlhNz6XurA1wnjZmS
|
||||
VcPxopH7QsuFCclqtt14MbBQ6QKBgQDHY3jcAVfQF+yhQ0YyM6GPLN342aTplgyJ
|
||||
yz4uWVMeXacU4QzqGbf2L2hc9M2L28Xb37RWC3Q/by0vUefiC6qxRt+GJdRsOuQj
|
||||
2F1uUibeWtAWp249fcfvxjLib276J+Eit18LI0s0mNR3ekK4GcjSe4NwSq5IrU8e
|
||||
pBreet3dIwKBgQCxVuil4WkGd+I8jC0v5A7zVsR8hYZhlGkdgm45fgHevdMjlP5I
|
||||
S3PPYxh8hj6O9o9L0k0Yq2nHfdgYujjUCNkQgBuR55iogv6kqsioRKgPE4fnH6/c
|
||||
eqCy1bZh4tbUyPqqbF65mQfUCzXsEuQXvDSYiku+F0Q2mVuGCUJpmug3yQKBgEd3
|
||||
LeCdUp4xlQ0QEd74hpXM3RrO178pmwDgqj7uoU4m/zYKnBhkc3137I406F+SvE5c
|
||||
1kRpApeh/64QS27IA7xazM9GS+cnDJKUgJiENY5JOoCELo03wiv8/EwQ6NQc6yMI
|
||||
WrahRdlqVe0lEzjtdP+MacYb3nAKPmubIk5P96nFAoGAFAyrKpFTyXbNYBTw9Rab
|
||||
TG6q7qkn+YTHN3+k4mo9NGGwZ3pXvmrKMYCIRhLMbqzsmTbFqCPPIxKsrmf8QYLh
|
||||
xHYQjrCkbZ0wZdcdeV6yFSDsF218nF/12ZPE7CBOQMfZTCKFNWGL97uIVcmR6K5G
|
||||
ojTkOvaUnwQtSFhNuzyr23I=
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -0,0 +1,19 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDCTCCAfGgAwIBAgIQYbE9d1Rft5h4ku7FSAvWdzANBgkqhkiG9w0BAQsFADAn
|
||||
MSUwIwYDVQQDExxUb25pYyBFeGFtcGxlIENsaWVudCBSb290IENBMB4XDTE5MTAx
|
||||
NDEyMzkzNloXDTI0MTAxMjEyMzkzNlowEjEQMA4GA1UEAxMHY2xpZW50MTCCASIw
|
||||
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKKJaubOkQ2wj5wLPhoGkb4+XxY6
|
||||
ysHJ9nH9mcl0Jkc7cxNgTqwIuFiyx/TKAAQQbG+aR3hzGZ8J+B/G5Py74PtjnNBK
|
||||
jKaRtZ1Tr/2Ns1jo6+1z5r3ojUsZc4pE5DDopHc+6oulSoQ4mIGXTeor4cxTSDno
|
||||
bWffU53seQmwNG+14+rLcjcuCl1xGmzbeyTjq0LNnOHJ+ohDtB8nmpnH3e0D6l/L
|
||||
5e1v1chRMhYoinX+7J+HwWnwcDRz3MkJJnMk5faPcYfVNiqRWA96AjaWGZ1eE/lD
|
||||
y92HDLRQlovbxElcDpQCDS09l5Yl7qm7H0mIDzWwjozL4quvDwPKJuQZNNsCAwEA
|
||||
AaNGMEQwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAfBgNVHSME
|
||||
GDAWgBQV1YOR+Jpl1fbujvWLSBEoRvsDhTANBgkqhkiG9w0BAQsFAAOCAQEAfTPu
|
||||
KeHXmyVTSCUrYQ1X5Mu7VzfZlRbhoytHOw7bYGgwaFwQj+ZhlPt8nFC22/bEk4IV
|
||||
AoCOli0WyPIB7Lx52dZ+v9JmYOK6ca2Aa/Dkw8Q+M3XA024FQWq3nZ6qANKC32/9
|
||||
Nk+xOcb1Qd/11stpTkRf2Oj7F7K4GnlFbY6iMyNW+RFXGKEbL5QAJDTDPIT8vw1x
|
||||
oYeNPwmC042uEboCZPNXmuctiK9Wt1TAxjZT/cwdIBGGJ+xrW72abfJGs7bUcJfc
|
||||
O4r9V0xVv+X0iKWTW0fwd9qjNfiEP1tFCcZb2XsNQPe/DlQZ+h98P073tZEsWI/G
|
||||
KJrFspGX8vOuSdIeqw==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCvpRVRx7joJVf3
|
||||
dIcDih5lY0Z+Y2MoDX83iwNF4QTfMibULyXffOnC5XcvayS/W8ToxaLweNE2bIwa
|
||||
krl3K+R/4tdI9ZguSgHFBMRCNp33ZDGxD01XHduETXXdHXxKufxLNjCFtAWwM+GF
|
||||
KbYzEdeyMmV9yYc3ihW3mg+7rzaUwU+hK7LjhTi/YW7TJF53UGIkEQuPKYUfBCha
|
||||
01CWAJTJDI7hrH7asfIG690Wm3uWNj2wsFVk2ZtyRIis2aQOE4tknG3yTKjnLGBH
|
||||
L+DQ+2UljuVyHi1pwCVN+zvoG/0RiiHWs6kW/zAompoxWhWPucKgssjC/JsWVKKp
|
||||
TpfOfEy7AgMBAAECggEAFclTQKaWT+054Q7KJG1AYfETcF/hj7lE213Z1RQZJ5ov
|
||||
6MfEWdlDoZIW24HduAKpBPpmwI8r3CVQp4cljBucpyQ68ejMrIkveQGjWlct9t6t
|
||||
rzmnrTOd4+Y7xWZ/4UD6g1XAZQU0Y2u8AGlxGRqQd6D6p8SUihYNpY1tgCk2ivLO
|
||||
znGZau0Gdt2f6Pv9C0cy5R2832a+3V1X9oCeeXVP8kM1dBKbWQTVxhYaxJxMZifn
|
||||
qfJi3sjEJZZ05NEFKwKpYx7gUEtC1d/zvvL/VSjihcyCYk728jr40uoeBpBUiUsR
|
||||
G0N1/xGNGHuRAGFjQSyrACtz1hRSPFmTyRhr6vrRGQKBgQDOFWKl6HjVB/WZ6ZC+
|
||||
BXMhUvCKz99lrOO1TbDAI8Tn26D+OSXhywXWPf++HhTZuND460I+79rPH+tJiFw4
|
||||
Y5z9JFce0MYPNcd/K7JBiV7zBBYof40UbBw8Eff7mWLaOXtI9uwItRXRBs+fIPRM
|
||||
eCgVUid7pe//oAQ8npcmQEVxjwKBgQDaMEmrTo0+RpT3sS1m9pCQc2LjiKmhWP1L
|
||||
N/IxZn5Ey9Ao+ibVIQzb69HMUBJ1EZGNZZkEcCguWICIov8rO10xIrD3m9rWgAVg
|
||||
HlVFlcSTrg0/3cVd26msJKW4pjKA/GmYVhP8o027Zy5KtZ/2jJCh2UjSLsY+acEq
|
||||
+Qr972BEFQKBgHOwAZ7NL/e27iKmwUBK4uSUIMBsDSaQtYtzv4M9ES5vVqMgBaoJ
|
||||
RI+OYmChlmban0T9HEUkdJrNelHfIJXvJZPdsKJ15JlpQUKcjwbHTOvzIVU+tT3/
|
||||
qqH2HFW7N4j1t8WwB7Sjo0miHy9fWoUK9sVxRwTclCvV8krtZEBu2Az1AoGAGZoU
|
||||
6t76v9X0YOQPWceQywJfFifRD7ercQoNhzJpmpT3xfckW1nXcm7HXVv/7nCzTY4g
|
||||
WF74uAd2fZHysxXyJ3PUpBlLomO/PboRc2rReCqyL05MfGjsDeD2+SW3Q19a3J8t
|
||||
FTXsRxMiYW3SaVGxHuyqGM+YP3aVTf+PBKD0AMkCgYEAyKtuxwBjPjO1pyOn195e
|
||||
UY2oaZq4PfaK04Eu5uCmOD2vBcgJWZ8VJNXPlRL5vlCx9LrLB9vJDyXgSurqJAH6
|
||||
M8/7tR/xlyMix+Jq47209kfOXMMdSGf55/xERmN6vPuhLtPwtj0ZduJzq6gwnlvs
|
||||
gG29TwA5xp9S4A1amFSrJBo=
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -0,0 +1,19 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDCjCCAfKgAwIBAgIRAKD72aj29NawAKPAQkcm7QcwDQYJKoZIhvcNAQELBQAw
|
||||
JzElMCMGA1UEAxMcVG9uaWMgRXhhbXBsZSBDbGllbnQgUm9vdCBDQTAeFw0xOTEw
|
||||
MTQxMjM5MzZaFw0yNDEwMTIxMjM5MzZaMBIxEDAOBgNVBAMTB2NsaWVudDIwggEi
|
||||
MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvpRVRx7joJVf3dIcDih5lY0Z+
|
||||
Y2MoDX83iwNF4QTfMibULyXffOnC5XcvayS/W8ToxaLweNE2bIwakrl3K+R/4tdI
|
||||
9ZguSgHFBMRCNp33ZDGxD01XHduETXXdHXxKufxLNjCFtAWwM+GFKbYzEdeyMmV9
|
||||
yYc3ihW3mg+7rzaUwU+hK7LjhTi/YW7TJF53UGIkEQuPKYUfBCha01CWAJTJDI7h
|
||||
rH7asfIG690Wm3uWNj2wsFVk2ZtyRIis2aQOE4tknG3yTKjnLGBHL+DQ+2UljuVy
|
||||
Hi1pwCVN+zvoG/0RiiHWs6kW/zAompoxWhWPucKgssjC/JsWVKKpTpfOfEy7AgMB
|
||||
AAGjRjBEMBMGA1UdJQQMMAoGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHwYDVR0j
|
||||
BBgwFoAUFdWDkfiaZdX27o71i0gRKEb7A4UwDQYJKoZIhvcNAQELBQADggEBAF5F
|
||||
2A3V2GGZo7AdztxWaPd6Nu/8VbQWGEeZeWFpQEloNiur96KDcndXXrS6GAyL31d9
|
||||
vfV2IA2yrB/2przFVRjfNnTj7+xNEtp23iMW8qhMIeHQs1IYu+HqHWDPEJfphTPd
|
||||
4Tu5M+ciE7KSZgPsV15piPgst2dqDvghBqxE6t3UnR1wQ5b3wWCt1O9NO+obeV9a
|
||||
1lzlYw2NjdHxmrqXLS9I5eYqEo3JtRQrOu3LUd7LqdlEO0jHMaxblxTgwJDoj/9C
|
||||
CnXp3sNewqsNHW17dsKavMJ2LFXukqLjonvz8NjxeetD09OzMEsg8Tx/1nOQPUBw
|
||||
rtVdBkQ/TxiNTlTtdZc=
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,19 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDGzCCAgOgAwIBAgIRAMNWpWRu6Q1txEYUyrkyXKEwDQYJKoZIhvcNAQELBQAw
|
||||
JzElMCMGA1UEAxMcVG9uaWMgRXhhbXBsZSBDbGllbnQgUm9vdCBDQTAeFw0xOTEw
|
||||
MTQxMjM5MzZaFw0yOTEwMTExMjM5MzZaMCcxJTAjBgNVBAMTHFRvbmljIEV4YW1w
|
||||
bGUgQ2xpZW50IFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
|
||||
AQCv8Nj4XJbMI0wWUvLbmCf7IEvJFnomodGnDurh8Y5AGMPJ8cGdZC1yo2Lgah+D
|
||||
IhXdsd72Wp7MhdntJAyPrMCDBfDrFiuj6YHDgt3OhPQSYl7EWG7QjFK3B2sp1K5D
|
||||
h16G5zfwUKDj9Jp3xuPGuqNFQHL02nwbhtDilqHvaTfOJKVjsFCoU8Z77mfwXSwn
|
||||
sPXpPB7oOO4mWfAtcwU11rTMiHFSGFlFhgbHULU/y90DcpfRQEpEiBoiK13gkyoP
|
||||
zHT9WAg3Pelwb6K7c7kJ7mp4axhbf7MkwFhDQIjbBWqus2Eu3b0mf86ALfDbAaNC
|
||||
wBi8xbNH2vWaDjiwLDY5uMZDAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwICBDAPBgNV
|
||||
HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQV1YOR+Jpl1fbujvWLSBEoRvsDhTANBgkq
|
||||
hkiG9w0BAQsFAAOCAQEAaXmM29TYkFUzZUsV7TSonAK560BjxDmbg0GJSUgLEFUJ
|
||||
wpKqa9UKOSapG45LEeR2wwAmVWDJomJplkuvTD/KOabAbZKyPEfp+VMCaBUnILQF
|
||||
Cxv5m7kQ3wmPS/rEL8FD809UGowW9cYqnZzUy5i/r263rx0k3OPjkkZN66Mh6+3H
|
||||
ibNdaxf7ITO0JVb/Ohq9vLC9qf7ujiB1atMdJwkOWsZrLJXLygpx/D0/UhBT4fFH
|
||||
OlyVOmuR27qaMbPgOs2l8DznkJY/QUfnET8iOQhFgb0Dt/Os4PYFhSDRIrgl5dJ7
|
||||
L/zZVQfZYpdxlBHJlDC1/NzVQl/1MgDnSgPGStZKPQ==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,28 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDyptbMyYWztgta
|
||||
t1MXLMzIkaQdeeVbs1Y/qCpAdwZe/Y5ZpbzjGIjCxbB6vNRSnEbYKpytKHPzYfM7
|
||||
8d8K8bPvpnqXIiTXFT0JQlw1OHLC1fr4e598GJumAmpMYFrtqv0fbmUFTuQGbHxe
|
||||
OH2vji0bvr3NKZubMfkEZP3X4sNXXoXIuW2LaS8OMGKoJaeCBvdbszEiSGj/v9Bj
|
||||
pM0yLTH89NNMX1T+FtTKnuXag5g7pr6lzJj83+MzAGy4nOjseSuUimuiyG90/C5t
|
||||
A5wC0Qh5RbDnkFYhC44Kxof/i6+jnfateIPNiIIwQV+2f6G/aK1hgjekT10m/eoR
|
||||
YDTf+e5ZAgMBAAECggEACODt7yRYjhDVLYaTtb9f5t7dYG67Y7WWLFIc6arxQryI
|
||||
XuNfm/ej2WyeXn9WTYeGWBaHERbv1zH4UnMxNBdP/C7dQXZwXqZaS2JwOUpNeK+X
|
||||
tUvgtAu6dkKUXSMRcKzXAjVp4N3YHhwOGOx8PNY49FDwZPdmyDD16aFAYIvdle6/
|
||||
PSMrj38rB1sbQQdmRob2FjJBSDZ44nsr+/nilrcOFNfNnWv7tQIWYVXNcLfdK/WJ
|
||||
ZCDFhA8lr/Yon6MEq6ApTj2ZYRRGXPd6UeASJkmTZEUIUbeDcje/MO8cHkREpuRH
|
||||
wm3pCjR7OdO4vc+/d/QmEvu5ns6wbTauelYnL616YQKBgQD414gJtpCHauNEUlFB
|
||||
v/R3DzPI5NGp9PAqovOD8nCbI49Mw61gP/ExTIPKiR5uUX/5EL04uspaNkuohXk+
|
||||
ys0G5At0NfV7W39lzhvALEaSfleybvYxppbBrc20/q8Gvi/i30NY+1LM3RdtMiEw
|
||||
hKHjU0SnFhJq0InFg3AO/iCeTQKBgQD5obkbzpOidSsa55aNsUlO2qjiUY9leq9b
|
||||
irAohIZ8YnuuixYvkOeSeSz1eIrA4tECeAFSgTZxYe1Iz+USru2Xg/0xNte11dJD
|
||||
rBoH/yMn2gDvBK7xQ6uFMPTeYtKG0vfvpXZYSWZzGntyrHTwFk6UV+xdrt9MBdd1
|
||||
XdSn7bwOPQKBgC9VQAko8uDvUf+C8PXiv2uONrl13PPJJY3WpR9qFEVOREnDxszS
|
||||
HNzVwxPZdTJiykbkCjoqPadfQJDzopZxGQLAifU29lTamKcSx3CMe3gOFDxaovXa
|
||||
zD5XAxP0hfJwZsdu1G6uj5dsTrJ0oJ+L+wc0pZBqwGIU/L/XOo9/g1DZAoGAUebL
|
||||
kuH98ik7EUK2VJq8EJERI9/ailLsQb6I+WIxtZGiPqwHhWencpkrNQZtj8dbB9JT
|
||||
rLwUHrMgZOlAoRafgTyez4zMzS3wJJ/Mkp8U67hM4h7JPwMSvUpIrMYDiJSjIA9L
|
||||
er/qSw1/Pypx22uWMHmAZWRAgvLPtAQrB0Wqk4kCgYEAr2H1PvfbwZwkSvlMt5o8
|
||||
WLnBbxcM3AKglLRbkShxxgiZYdEP71/uOtRMiL26du5XX8evItITN0DsvmXL/kcd
|
||||
h29LK7LM5uLw7efz0Qxs03G6kEyIHVkacowHi5I5Ul1qI61SoV3yMB1TjIU+bXZt
|
||||
0ZjC07totO0fqPOLQxonjQg=
|
||||
-----END PRIVATE KEY-----
|
||||
@@ -0,0 +1,27 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEmDCCAwCgAwIBAgIQVEJFCgU/CZk9JEwTucWPpzANBgkqhkiG9w0BAQsFADCB
|
||||
hTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMS0wKwYDVQQLDCRsdWNp
|
||||
b0BMdWNpb3MtV29yay1NQlAgKEx1Y2lvIEZyYW5jbykxNDAyBgNVBAMMK21rY2Vy
|
||||
dCBsdWNpb0BMdWNpb3MtV29yay1NQlAgKEx1Y2lvIEZyYW5jbykwHhcNMTkwNjAx
|
||||
MDAwMDAwWhcNMjkwOTI5MjMzNTM0WjBYMScwJQYDVQQKEx5ta2NlcnQgZGV2ZWxv
|
||||
cG1lbnQgY2VydGlmaWNhdGUxLTArBgNVBAsMJGx1Y2lvQEx1Y2lvcy1Xb3JrLU1C
|
||||
UCAoTHVjaW8gRnJhbmNvKTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
APKm1szJhbO2C1q3UxcszMiRpB155VuzVj+oKkB3Bl79jlmlvOMYiMLFsHq81FKc
|
||||
RtgqnK0oc/Nh8zvx3wrxs++mepciJNcVPQlCXDU4csLV+vh7n3wYm6YCakxgWu2q
|
||||
/R9uZQVO5AZsfF44fa+OLRu+vc0pm5sx+QRk/dfiw1dehci5bYtpLw4wYqglp4IG
|
||||
91uzMSJIaP+/0GOkzTItMfz000xfVP4W1Mqe5dqDmDumvqXMmPzf4zMAbLic6Ox5
|
||||
K5SKa6LIb3T8Lm0DnALRCHlFsOeQViELjgrGh/+Lr6Od9q14g82IgjBBX7Z/ob9o
|
||||
rWGCN6RPXSb96hFgNN/57lkCAwEAAaOBrzCBrDAOBgNVHQ8BAf8EBAMCBaAwEwYD
|
||||
VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBQdvlE4
|
||||
Bdcsjc9oaxjDCRu5FiuZkzBWBgNVHREETzBNggtleGFtcGxlLmNvbYINKi5leGFt
|
||||
cGxlLmNvbYIMZXhhbXBsZS50ZXN0gglsb2NhbGhvc3SHBH8AAAGHEAAAAAAAAAAA
|
||||
AAAAAAAAAAEwDQYJKoZIhvcNAQELBQADggGBAKb2TJ8l+e1eraNwZWizLw5fccAf
|
||||
y59J1JAWdLxZyAI/bkiTlVO3DQoPZpw7XwLhefCvILkwKAL4TtIGGVC9yTb5Q5eg
|
||||
rqGO3FC0yg1fn65Kf1VpVxxUVyoiM5PQ4pFJb4AicAv88rCOLD9FFuE0PKOKU/dm
|
||||
Tw0WgPStoh9wsJ1RXUuTJYZs1nd1kMBlfv9NbLilnL+cR2sLktS54X5XagsBYVlf
|
||||
oapRb0JtABOoQhX3U8QMq8UF8yzceRHNTN9yfLOUrW26s9nKtlWVniNhw1uPxZw9
|
||||
RHM7w9/4+a9LXtEDYg4IP/1mm0ywBoUqy1O6hA73uId+Yi/kFBks/GyYaGjKgYcO
|
||||
23B75tkPGYEdGuGZYLzZNHbXg4V0UxFQG3KA1pUiSnD3bN2Rxs+CMpzORnOeK3xi
|
||||
EooKgAPYsehItoQOMPpccI2xHdSAMWtwUgOKrefUQujkx2Op+KFlspF0+WJ6AZEe
|
||||
2D4hyWaEZsvvILXapwqHDCuN3/jSUlTIqUoE1w==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,314 @@
|
||||
# Getting Started
|
||||
|
||||
This tutorial is meant to be an introduction to Tonic and assumes that you have basic [Rust] experience as well as an understanding of what [protocol buffers] are. If you don't, feel free to read up on the pages linked in this paragraph and come back to this tutorial once you feel you are ready!
|
||||
|
||||
[rust]: https://www.rust-lang.org/
|
||||
[protocol buffers]: https://developers.google.com/protocol-buffers/docs/overview
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To run the sample code and walk through the tutorial, the only prerequisite is Rust itself.
|
||||
[rustup] is a convenient tool to install it, if you haven't already.
|
||||
|
||||
[rustup]: https://rustup.rs
|
||||
|
||||
## Project Setup
|
||||
|
||||
For this tutorial, we will start by creating a new Rust project with Cargo:
|
||||
|
||||
```shell
|
||||
$ cargo new helloworld-tonic
|
||||
$ cd helloworld-tonic
|
||||
```
|
||||
|
||||
`tonic` works on rust `1.39` and above as it requires support for the `async_await`
|
||||
feature.
|
||||
|
||||
```bash
|
||||
$ rustup update
|
||||
$ rustup component add rustfmt
|
||||
```
|
||||
|
||||
## Defining the HelloWorld service
|
||||
|
||||
Our first step is to define the gRPC _service_ and the method _request_ and _response_ types using
|
||||
[protocol buffers]. We will keep our `.proto` files in a directory in our crate's root.
|
||||
Note that Tonic does not really care where our `.proto` definitions live.
|
||||
|
||||
```shell
|
||||
$ mkdir proto
|
||||
$ touch proto/helloworld.proto
|
||||
```
|
||||
|
||||
Then you define RPC methods inside your service definition, specifying their request and response
|
||||
types. gRPC lets you define four kinds of service methods, all of which are supported by Tonic. For this tutorial we will only use a simple RPC, if you would like to see a Tonic example which uses all four kinds please read the [routeguide tutorial].
|
||||
|
||||
[routeguide tutorial]: https://github.com/hyperium/tonic/blob/master/tonic-examples/routeguide-tutorial.md
|
||||
|
||||
First we define our package name, which is what Tonic looks for when including your protos in the client and server applications. Lets give this one a name of `helloworld`.
|
||||
|
||||
```proto
|
||||
syntax = "proto3";
|
||||
package helloworld;
|
||||
```
|
||||
|
||||
Next we need to define our service. This service will contain the actual RPC calls we will be using in our application. An RPC contains an Identifier, a Request type, and returns a Response type. Here is our Greeter service, which provides the SayHello RPC method.
|
||||
|
||||
```proto
|
||||
service Greeter {
|
||||
// Our SayHello rpc accepts HelloRequests and returns HelloReplies
|
||||
rpc SayHello (HelloRequest) returns (HelloReply);
|
||||
}
|
||||
```
|
||||
|
||||
Finally, we have to actually define those types we used above in our `SayHello` RPC method. RPC types are defined as messages which contain typed fields. Here is what that will look like for our HelloWorld application:
|
||||
|
||||
```proto
|
||||
message HelloRequest {
|
||||
// Request message contains the name to be greeted
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message HelloReply {
|
||||
// Reply contains the greeting message
|
||||
string message = 1;
|
||||
}
|
||||
```
|
||||
|
||||
Great! Now our `.proto` file should be complete and ready for use in our application. Here is what it should look like completed:
|
||||
|
||||
```proto
|
||||
syntax = "proto3";
|
||||
package helloworld;
|
||||
|
||||
service Greeter {
|
||||
rpc SayHello (HelloRequest) returns (HelloReply);
|
||||
}
|
||||
|
||||
message HelloRequest {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message HelloReply {
|
||||
string message = 1;
|
||||
}
|
||||
```
|
||||
|
||||
## Application Setup
|
||||
|
||||
Now that have defined the protobuf for our application we can start writing our application with Tonic! Let's first add our required dependencies to the `Cargo.toml`.
|
||||
|
||||
```toml
|
||||
[package]
|
||||
name = "helloworld-tonic"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[[bin]] # Bin to run the HelloWorld gRPC server
|
||||
name = "helloworld-server"
|
||||
path = "src/server.rs"
|
||||
|
||||
[[bin]] # Bin to run the HelloWorld gRPC client
|
||||
name = "helloworld-client"
|
||||
path = "src/client.rs"
|
||||
|
||||
[dependencies]
|
||||
tonic = "0.1.0-alpha.5"
|
||||
bytes = "0.4"
|
||||
prost = "0.5"
|
||||
prost-derive = "0.5"
|
||||
tokio = "=0.2.0-alpha.6"
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = "0.1.0-alpha.5"
|
||||
```
|
||||
|
||||
We include `tonic-build` as a useful way to incorporate the generation of our client and server gRPC code into the build process of our application. We will setup this build process now:
|
||||
|
||||
## Generating Server and Client code
|
||||
|
||||
At the root of your crate, create a `build.rs` file and add the following code:
|
||||
|
||||
```rust
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tonic_build::compile_protos("proto/helloworld.proto")?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
This tells `tonic-build` to compile your protobufs when you build your Rust project. While you can configure this build process in a number of ways, we will not get into the details in this introductory tutorial. Please see the [tonic-build] documentation for details on configuration.
|
||||
|
||||
[tonic-build]: https://github.com/hyperium/tonic/blob/master/tonic-build/README.md
|
||||
|
||||
## Writing our Server
|
||||
|
||||
Now that the build process is written and our dependencies are all setup, we can begin writing the fun stuff! We need to import the things we will be using in our server, including the protobuf. Start by making a file called `server.rs` in your `/src` directory and writing the following code:
|
||||
|
||||
```rust
|
||||
use tonic::{transport::Server, Request, Response, Status};
|
||||
|
||||
pub mod hello_world {
|
||||
tonic::include_proto!("helloworld"); // The string specified here must match the proto package name
|
||||
}
|
||||
|
||||
use hello_world::{
|
||||
server::{Greeter, GreeterServer},
|
||||
HelloReply, HelloRequest,
|
||||
};
|
||||
```
|
||||
|
||||
Next up, let's implement the Greeter service we previously defined in our `.proto` file. Here's what that might look like:
|
||||
|
||||
```rust
|
||||
pub struct MyGreeter {}
|
||||
|
||||
#[tonic::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 = hello_world::HelloReply {
|
||||
message: format!("Hello {}!", request.into_inner().name).into(), // We must use .into_inner() as the fields of gRPC requests and responses are private
|
||||
};
|
||||
|
||||
Ok(Response::new(reply)) // Send back our formatted greeting
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Finally, let's define the Tokio runtime that our server will actually run on. This requires Tokio to be added as a dependency, so make sure you included that!
|
||||
|
||||
```rust
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let addr = "[::1]:50051".parse()?;
|
||||
let greeter = MyGreeter {};
|
||||
|
||||
Server::builder()
|
||||
.add_service(GreeterServer::new(greeter))
|
||||
.serve(addr)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
Altogether your server should look something like this once you are done:
|
||||
|
||||
```rust
|
||||
use tonic::{transport::Server, Request, Response, Status};
|
||||
|
||||
pub mod hello_world {
|
||||
tonic::include_proto!("helloworld");
|
||||
}
|
||||
|
||||
use hello_world::{
|
||||
server::{Greeter, GreeterServer},
|
||||
HelloReply, HelloRequest,
|
||||
};
|
||||
|
||||
pub struct MyGreeter {}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl Greeter for MyGreeter {
|
||||
async fn say_hello(
|
||||
&self,
|
||||
request: Request<HelloRequest>,
|
||||
) -> Result<Response<HelloReply>, Status> {
|
||||
println!("Got a request: {:?}", request);
|
||||
|
||||
let reply = hello_world::HelloReply {
|
||||
message: format!("Hello {}!", request.into_inner().name).into(),
|
||||
};
|
||||
|
||||
Ok(Response::new(reply))
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let addr = "[::1]:50051".parse()?;
|
||||
let greeter = MyGreeter {};
|
||||
|
||||
Server::builder()
|
||||
.add_service(GreeterServer::new(greeter))
|
||||
.serve(addr)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
You should now be able to run your HelloWorld gRPC server using the command `cargo run --bin helloworld-server`. This uses the [[bin]] we defined earlier in our `Cargo.toml` to run specifically the server. If have a gRPC GUI client such as [Bloom RPC] you should be able to send requests to the server and get back greetings!
|
||||
|
||||
[bloom rpc]: https://github.com/uw-labs/bloomrpc
|
||||
|
||||
## Writing our Client
|
||||
|
||||
So now we have a running gRPC server, and that's great but how can our application communicate with it? This is where our client would come in. Tonic supports both client and server implementations. Similar to the server, we will start by creating a file `client.rs` in our `/src` directory and importing everything we will need:
|
||||
|
||||
```rust
|
||||
pub mod hello_world {
|
||||
tonic::include_proto!("helloworld");
|
||||
}
|
||||
|
||||
use hello_world::{client::GreeterClient, HelloRequest};
|
||||
```
|
||||
|
||||
The client is much simpler than the server as we don't need to implement any service methods, just make requests. Here is a Tokio runtime which will make our request and print the response to your terminal:
|
||||
|
||||
```rust
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut client = GreeterClient::connect("http://[::1]:50051").await?;
|
||||
|
||||
let request = tonic::Request::new(HelloRequest {
|
||||
name: "Tonic".into(),
|
||||
});
|
||||
|
||||
let response = client.say_hello(request).await?;
|
||||
|
||||
println!("RESPONSE={:?}", response);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
That's it! Our complete client file should look something like below, if it doesn't please go back and make sure you followed along correctly:
|
||||
|
||||
```rust
|
||||
pub mod hello_world {
|
||||
tonic::include_proto!("helloworld");
|
||||
}
|
||||
|
||||
use hello_world::{client::GreeterClient, HelloRequest};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut client = GreeterClient::connect("http://[::1]:50051").await?;
|
||||
|
||||
let request = tonic::Request::new(HelloRequest {
|
||||
name: "Tonic".into(),
|
||||
});
|
||||
|
||||
let response = client.say_hello(request).await?;
|
||||
|
||||
println!("RESPONSE={:?}", response);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Putting it all together
|
||||
|
||||
At this point we have written our protobuf file, a build file to compile our protobufs, a server which implements our SayHello service, and a client which makes requests to our server. You should have a `proto/helloworld.proto` file, a `build.rs` file at the root of your project, and `src/server.rs` as well as a `src/client.rs` files.
|
||||
|
||||
To run the server, run `cargo run --bin helloworld-server`.
|
||||
To run the client, run `cargo run --bin helloworld-client` in another terminal window.
|
||||
|
||||
You should see the request logged out by the server in its terminal window, as well as the response logged out by the client in its window.
|
||||
|
||||
Congrats on making it through this introductory tutorial! We hope that this walkthrough tutorial has helped you understand the basics of Tonic, and how to get started writing high-performance, interoperable, and flexible gRPC servers in Rust. For a more in-depth tutorial which showcases an advanced gRPC server in Tonic, please see the [routeguide tutorial].
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
*
|
||||
* Copyright 2018 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";
|
||||
|
||||
package grpc.examples.echo;
|
||||
|
||||
// EchoRequest is the request for echo.
|
||||
message EchoRequest {
|
||||
string message = 1;
|
||||
}
|
||||
|
||||
// EchoResponse is the response for echo.
|
||||
message EchoResponse {
|
||||
string message = 1;
|
||||
}
|
||||
|
||||
// Echo is the echo service.
|
||||
service Echo {
|
||||
// UnaryEcho is unary echo.
|
||||
rpc UnaryEcho(EchoRequest) returns (EchoResponse) {}
|
||||
// ServerStreamingEcho is server side streaming.
|
||||
rpc ServerStreamingEcho(EchoRequest) returns (stream EchoResponse) {}
|
||||
// ClientStreamingEcho is client side streaming.
|
||||
rpc ClientStreamingEcho(stream EchoRequest) returns (EchoResponse) {}
|
||||
// BidirectionalStreamingEcho is bidi streaming.
|
||||
rpc BidirectionalStreamingEcho(stream EchoRequest) returns (stream EchoResponse) {}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) 2015, Google Inc.
|
||||
//
|
||||
// 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";
|
||||
|
||||
package google.api;
|
||||
|
||||
import "http.proto";
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations";
|
||||
option java_multiple_files = true;
|
||||
option java_outer_classname = "AnnotationsProto";
|
||||
option java_package = "com.google.api";
|
||||
option objc_class_prefix = "GAPI";
|
||||
|
||||
extend google.protobuf.MethodOptions {
|
||||
// See `HttpRule`.
|
||||
HttpRule http = 72295728;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
// Copyright 2019 Google LLC.
|
||||
//
|
||||
// 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";
|
||||
|
||||
package google.api;
|
||||
|
||||
import "google/protobuf/descriptor.proto";
|
||||
|
||||
option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations";
|
||||
option java_multiple_files = true;
|
||||
option java_outer_classname = "ClientProto";
|
||||
option java_package = "com.google.api";
|
||||
option objc_class_prefix = "GAPI";
|
||||
|
||||
extend google.protobuf.MethodOptions {
|
||||
// A definition of a client library method signature.
|
||||
//
|
||||
// In client libraries, each proto RPC corresponds to one or more methods
|
||||
// which the end user is able to call, and calls the underlying RPC.
|
||||
// Normally, this method receives a single argument (a struct or instance
|
||||
// corresponding to the RPC request object). Defining this field will
|
||||
// add one or more overloads providing flattened or simpler method signatures
|
||||
// in some languages.
|
||||
//
|
||||
// The fields on the method signature are provided as a comma-separated
|
||||
// string.
|
||||
//
|
||||
// For example, the proto RPC and annotation:
|
||||
//
|
||||
// rpc CreateSubscription(CreateSubscriptionRequest)
|
||||
// returns (Subscription) {
|
||||
// option (google.api.method_signature) = "name,topic";
|
||||
// }
|
||||
//
|
||||
// Would add the following Java overload (in addition to the method accepting
|
||||
// the request object):
|
||||
//
|
||||
// public final Subscription createSubscription(String name, String topic)
|
||||
//
|
||||
// The following backwards-compatibility guidelines apply:
|
||||
//
|
||||
// * Adding this annotation to an unannotated method is backwards
|
||||
// compatible.
|
||||
// * Adding this annotation to a method which already has existing
|
||||
// method signature annotations is backwards compatible if and only if
|
||||
// the new method signature annotation is last in the sequence.
|
||||
// * Modifying or removing an existing method signature annotation is
|
||||
// a breaking change.
|
||||
// * Re-ordering existing method signature annotations is a breaking
|
||||
// change.
|
||||
repeated string method_signature = 1051;
|
||||
}
|
||||
|
||||
extend google.protobuf.ServiceOptions {
|
||||
// The hostname for this service.
|
||||
// This should be specified with no prefix or protocol.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// service Foo {
|
||||
// option (google.api.default_host) = "foo.googleapi.com";
|
||||
// ...
|
||||
// }
|
||||
string default_host = 1049;
|
||||
|
||||
// OAuth scopes needed for the client.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// service Foo {
|
||||
// option (google.api.oauth_scopes) = \
|
||||
// "https://www.googleapis.com/auth/cloud-platform";
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
// If there is more than one scope, use a comma-separated string:
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// service Foo {
|
||||
// option (google.api.oauth_scopes) = \
|
||||
// "https://www.googleapis.com/auth/cloud-platform,"
|
||||
// "https://www.googleapis.com/auth/monitoring";
|
||||
// ...
|
||||
// }
|
||||
string oauth_scopes = 1050;
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
// Copyright 2019 Google LLC.
|
||||
//
|
||||
// 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";
|
||||
|
||||
package google.api;
|
||||
|
||||
option cc_enable_arenas = true;
|
||||
option go_package = "google.golang.org/genproto/googleapis/api/annotations;annotations";
|
||||
option java_multiple_files = true;
|
||||
option java_outer_classname = "HttpProto";
|
||||
option java_package = "com.google.api";
|
||||
option objc_class_prefix = "GAPI";
|
||||
|
||||
// Defines the HTTP configuration for an API service. It contains a list of
|
||||
// [HttpRule][google.api.HttpRule], each specifying the mapping of an RPC method
|
||||
// to one or more HTTP REST API methods.
|
||||
message Http {
|
||||
// A list of HTTP configuration rules that apply to individual API methods.
|
||||
//
|
||||
// **NOTE:** All service configuration rules follow "last one wins" order.
|
||||
repeated HttpRule rules = 1;
|
||||
|
||||
// When set to true, URL path parameters will be fully URI-decoded except in
|
||||
// cases of single segment matches in reserved expansion, where "%2F" will be
|
||||
// left encoded.
|
||||
//
|
||||
// The default behavior is to not decode RFC 6570 reserved characters in multi
|
||||
// segment matches.
|
||||
bool fully_decode_reserved_expansion = 2;
|
||||
}
|
||||
|
||||
// # gRPC Transcoding
|
||||
//
|
||||
// gRPC Transcoding is a feature for mapping between a gRPC method and one or
|
||||
// more HTTP REST endpoints. It allows developers to build a single API service
|
||||
// that supports both gRPC APIs and REST APIs. Many systems, including [Google
|
||||
// APIs](https://github.com/googleapis/googleapis),
|
||||
// [Cloud Endpoints](https://cloud.google.com/endpoints), [gRPC
|
||||
// Gateway](https://github.com/grpc-ecosystem/grpc-gateway),
|
||||
// and [Envoy](https://github.com/envoyproxy/envoy) proxy support this feature
|
||||
// and use it for large scale production services.
|
||||
//
|
||||
// `HttpRule` defines the schema of the gRPC/REST mapping. The mapping specifies
|
||||
// how different portions of the gRPC request message are mapped to the URL
|
||||
// path, URL query parameters, and HTTP request body. It also controls how the
|
||||
// gRPC response message is mapped to the HTTP response body. `HttpRule` is
|
||||
// typically specified as an `google.api.http` annotation on the gRPC method.
|
||||
//
|
||||
// Each mapping specifies a URL path template and an HTTP method. The path
|
||||
// template may refer to one or more fields in the gRPC request message, as long
|
||||
// as each field is a non-repeated field with a primitive (non-message) type.
|
||||
// The path template controls how fields of the request message are mapped to
|
||||
// the URL path.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// service Messaging {
|
||||
// rpc GetMessage(GetMessageRequest) returns (Message) {
|
||||
// option (google.api.http) = {
|
||||
// get: "/v1/{name=messages/*}"
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
// message GetMessageRequest {
|
||||
// string name = 1; // Mapped to URL path.
|
||||
// }
|
||||
// message Message {
|
||||
// string text = 1; // The resource content.
|
||||
// }
|
||||
//
|
||||
// This enables an HTTP REST to gRPC mapping as below:
|
||||
//
|
||||
// HTTP | gRPC
|
||||
// -----|-----
|
||||
// `GET /v1/messages/123456` | `GetMessage(name: "messages/123456")`
|
||||
//
|
||||
// Any fields in the request message which are not bound by the path template
|
||||
// automatically become HTTP query parameters if there is no HTTP request body.
|
||||
// For example:
|
||||
//
|
||||
// service Messaging {
|
||||
// rpc GetMessage(GetMessageRequest) returns (Message) {
|
||||
// option (google.api.http) = {
|
||||
// get:"/v1/messages/{message_id}"
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
// message GetMessageRequest {
|
||||
// message SubMessage {
|
||||
// string subfield = 1;
|
||||
// }
|
||||
// string message_id = 1; // Mapped to URL path.
|
||||
// int64 revision = 2; // Mapped to URL query parameter `revision`.
|
||||
// SubMessage sub = 3; // Mapped to URL query parameter `sub.subfield`.
|
||||
// }
|
||||
//
|
||||
// This enables a HTTP JSON to RPC mapping as below:
|
||||
//
|
||||
// HTTP | gRPC
|
||||
// -----|-----
|
||||
// `GET /v1/messages/123456?revision=2&sub.subfield=foo` |
|
||||
// `GetMessage(message_id: "123456" revision: 2 sub: SubMessage(subfield:
|
||||
// "foo"))`
|
||||
//
|
||||
// Note that fields which are mapped to URL query parameters must have a
|
||||
// primitive type or a repeated primitive type or a non-repeated message type.
|
||||
// In the case of a repeated type, the parameter can be repeated in the URL
|
||||
// as `...?param=A¶m=B`. In the case of a message type, each field of the
|
||||
// message is mapped to a separate parameter, such as
|
||||
// `...?foo.a=A&foo.b=B&foo.c=C`.
|
||||
//
|
||||
// For HTTP methods that allow a request body, the `body` field
|
||||
// specifies the mapping. Consider a REST update method on the
|
||||
// message resource collection:
|
||||
//
|
||||
// service Messaging {
|
||||
// rpc UpdateMessage(UpdateMessageRequest) returns (Message) {
|
||||
// option (google.api.http) = {
|
||||
// patch: "/v1/messages/{message_id}"
|
||||
// body: "message"
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
// message UpdateMessageRequest {
|
||||
// string message_id = 1; // mapped to the URL
|
||||
// Message message = 2; // mapped to the body
|
||||
// }
|
||||
//
|
||||
// The following HTTP JSON to RPC mapping is enabled, where the
|
||||
// representation of the JSON in the request body is determined by
|
||||
// protos JSON encoding:
|
||||
//
|
||||
// HTTP | gRPC
|
||||
// -----|-----
|
||||
// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id:
|
||||
// "123456" message { text: "Hi!" })`
|
||||
//
|
||||
// The special name `*` can be used in the body mapping to define that
|
||||
// every field not bound by the path template should be mapped to the
|
||||
// request body. This enables the following alternative definition of
|
||||
// the update method:
|
||||
//
|
||||
// service Messaging {
|
||||
// rpc UpdateMessage(Message) returns (Message) {
|
||||
// option (google.api.http) = {
|
||||
// patch: "/v1/messages/{message_id}"
|
||||
// body: "*"
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
// message Message {
|
||||
// string message_id = 1;
|
||||
// string text = 2;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// The following HTTP JSON to RPC mapping is enabled:
|
||||
//
|
||||
// HTTP | gRPC
|
||||
// -----|-----
|
||||
// `PATCH /v1/messages/123456 { "text": "Hi!" }` | `UpdateMessage(message_id:
|
||||
// "123456" text: "Hi!")`
|
||||
//
|
||||
// Note that when using `*` in the body mapping, it is not possible to
|
||||
// have HTTP parameters, as all fields not bound by the path end in
|
||||
// the body. This makes this option more rarely used in practice when
|
||||
// defining REST APIs. The common usage of `*` is in custom methods
|
||||
// which don't use the URL at all for transferring data.
|
||||
//
|
||||
// It is possible to define multiple HTTP methods for one RPC by using
|
||||
// the `additional_bindings` option. Example:
|
||||
//
|
||||
// service Messaging {
|
||||
// rpc GetMessage(GetMessageRequest) returns (Message) {
|
||||
// option (google.api.http) = {
|
||||
// get: "/v1/messages/{message_id}"
|
||||
// additional_bindings {
|
||||
// get: "/v1/users/{user_id}/messages/{message_id}"
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
// message GetMessageRequest {
|
||||
// string message_id = 1;
|
||||
// string user_id = 2;
|
||||
// }
|
||||
//
|
||||
// This enables the following two alternative HTTP JSON to RPC mappings:
|
||||
//
|
||||
// HTTP | gRPC
|
||||
// -----|-----
|
||||
// `GET /v1/messages/123456` | `GetMessage(message_id: "123456")`
|
||||
// `GET /v1/users/me/messages/123456` | `GetMessage(user_id: "me" message_id:
|
||||
// "123456")`
|
||||
//
|
||||
// ## Rules for HTTP mapping
|
||||
//
|
||||
// 1. Leaf request fields (recursive expansion nested messages in the request
|
||||
// message) are classified into three categories:
|
||||
// - Fields referred by the path template. They are passed via the URL path.
|
||||
// - Fields referred by the [HttpRule.body][google.api.HttpRule.body]. They are passed via the HTTP
|
||||
// request body.
|
||||
// - All other fields are passed via the URL query parameters, and the
|
||||
// parameter name is the field path in the request message. A repeated
|
||||
// field can be represented as multiple query parameters under the same
|
||||
// name.
|
||||
// 2. If [HttpRule.body][google.api.HttpRule.body] is "*", there is no URL query parameter, all fields
|
||||
// are passed via URL path and HTTP request body.
|
||||
// 3. If [HttpRule.body][google.api.HttpRule.body] is omitted, there is no HTTP request body, all
|
||||
// fields are passed via URL path and URL query parameters.
|
||||
//
|
||||
// ### Path template syntax
|
||||
//
|
||||
// Template = "/" Segments [ Verb ] ;
|
||||
// Segments = Segment { "/" Segment } ;
|
||||
// Segment = "*" | "**" | LITERAL | Variable ;
|
||||
// Variable = "{" FieldPath [ "=" Segments ] "}" ;
|
||||
// FieldPath = IDENT { "." IDENT } ;
|
||||
// Verb = ":" LITERAL ;
|
||||
//
|
||||
// The syntax `*` matches a single URL path segment. The syntax `**` matches
|
||||
// zero or more URL path segments, which must be the last part of the URL path
|
||||
// except the `Verb`.
|
||||
//
|
||||
// The syntax `Variable` matches part of the URL path as specified by its
|
||||
// template. A variable template must not contain other variables. If a variable
|
||||
// matches a single path segment, its template may be omitted, e.g. `{var}`
|
||||
// is equivalent to `{var=*}`.
|
||||
//
|
||||
// The syntax `LITERAL` matches literal text in the URL path. If the `LITERAL`
|
||||
// contains any reserved character, such characters should be percent-encoded
|
||||
// before the matching.
|
||||
//
|
||||
// If a variable contains exactly one path segment, such as `"{var}"` or
|
||||
// `"{var=*}"`, when such a variable is expanded into a URL path on the client
|
||||
// side, all characters except `[-_.~0-9a-zA-Z]` are percent-encoded. The
|
||||
// server side does the reverse decoding. Such variables show up in the
|
||||
// [Discovery
|
||||
// Document](https://developers.google.com/discovery/v1/reference/apis) as
|
||||
// `{var}`.
|
||||
//
|
||||
// If a variable contains multiple path segments, such as `"{var=foo/*}"`
|
||||
// or `"{var=**}"`, when such a variable is expanded into a URL path on the
|
||||
// client side, all characters except `[-_.~/0-9a-zA-Z]` are percent-encoded.
|
||||
// The server side does the reverse decoding, except "%2F" and "%2f" are left
|
||||
// unchanged. Such variables show up in the
|
||||
// [Discovery
|
||||
// Document](https://developers.google.com/discovery/v1/reference/apis) as
|
||||
// `{+var}`.
|
||||
//
|
||||
// ## Using gRPC API Service Configuration
|
||||
//
|
||||
// gRPC API Service Configuration (service config) is a configuration language
|
||||
// for configuring a gRPC service to become a user-facing product. The
|
||||
// service config is simply the YAML representation of the `google.api.Service`
|
||||
// proto message.
|
||||
//
|
||||
// As an alternative to annotating your proto file, you can configure gRPC
|
||||
// transcoding in your service config YAML files. You do this by specifying a
|
||||
// `HttpRule` that maps the gRPC method to a REST endpoint, achieving the same
|
||||
// effect as the proto annotation. This can be particularly useful if you
|
||||
// have a proto that is reused in multiple services. Note that any transcoding
|
||||
// specified in the service config will override any matching transcoding
|
||||
// configuration in the proto.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// http:
|
||||
// rules:
|
||||
// # Selects a gRPC method and applies HttpRule to it.
|
||||
// - selector: example.v1.Messaging.GetMessage
|
||||
// get: /v1/messages/{message_id}/{sub.subfield}
|
||||
//
|
||||
// ## Special notes
|
||||
//
|
||||
// When gRPC Transcoding is used to map a gRPC to JSON REST endpoints, the
|
||||
// proto to JSON conversion must follow the [proto3
|
||||
// specification](https://developers.google.com/protocol-buffers/docs/proto3#json).
|
||||
//
|
||||
// While the single segment variable follows the semantics of
|
||||
// [RFC 6570](https://tools.ietf.org/html/rfc6570) Section 3.2.2 Simple String
|
||||
// Expansion, the multi segment variable **does not** follow RFC 6570 Section
|
||||
// 3.2.3 Reserved Expansion. The reason is that the Reserved Expansion
|
||||
// does not expand special characters like `?` and `#`, which would lead
|
||||
// to invalid URLs. As the result, gRPC Transcoding uses a custom encoding
|
||||
// for multi segment variables.
|
||||
//
|
||||
// The path variables **must not** refer to any repeated or mapped field,
|
||||
// because client libraries are not capable of handling such variable expansion.
|
||||
//
|
||||
// The path variables **must not** capture the leading "/" character. The reason
|
||||
// is that the most common use case "{var}" does not capture the leading "/"
|
||||
// character. For consistency, all path variables must share the same behavior.
|
||||
//
|
||||
// Repeated message fields must not be mapped to URL query parameters, because
|
||||
// no client library can support such complicated mapping.
|
||||
//
|
||||
// If an API needs to use a JSON array for request or response body, it can map
|
||||
// the request or response body to a repeated field. However, some gRPC
|
||||
// Transcoding implementations may not support this feature.
|
||||
message HttpRule {
|
||||
// Selects a method to which this rule applies.
|
||||
//
|
||||
// Refer to [selector][google.api.DocumentationRule.selector] for syntax details.
|
||||
string selector = 1;
|
||||
|
||||
// Determines the URL pattern is matched by this rules. This pattern can be
|
||||
// used with any of the {get|put|post|delete|patch} methods. A custom method
|
||||
// can be defined using the 'custom' field.
|
||||
oneof pattern {
|
||||
// Maps to HTTP GET. Used for listing and getting information about
|
||||
// resources.
|
||||
string get = 2;
|
||||
|
||||
// Maps to HTTP PUT. Used for replacing a resource.
|
||||
string put = 3;
|
||||
|
||||
// Maps to HTTP POST. Used for creating a resource or performing an action.
|
||||
string post = 4;
|
||||
|
||||
// Maps to HTTP DELETE. Used for deleting a resource.
|
||||
string delete = 5;
|
||||
|
||||
// Maps to HTTP PATCH. Used for updating a resource.
|
||||
string patch = 6;
|
||||
|
||||
// The custom pattern is used for specifying an HTTP method that is not
|
||||
// included in the `pattern` field, such as HEAD, or "*" to leave the
|
||||
// HTTP method unspecified for this rule. The wild-card rule is useful
|
||||
// for services that provide content to Web (HTML) clients.
|
||||
CustomHttpPattern custom = 8;
|
||||
}
|
||||
|
||||
// The name of the request field whose value is mapped to the HTTP request
|
||||
// body, or `*` for mapping all request fields not captured by the path
|
||||
// pattern to the HTTP body, or omitted for not having any HTTP request body.
|
||||
//
|
||||
// NOTE: the referred field must be present at the top-level of the request
|
||||
// message type.
|
||||
string body = 7;
|
||||
|
||||
// Optional. The name of the response field whose value is mapped to the HTTP
|
||||
// response body. When omitted, the entire response message will be used
|
||||
// as the HTTP response body.
|
||||
//
|
||||
// NOTE: The referred field must be present at the top-level of the response
|
||||
// message type.
|
||||
string response_body = 12;
|
||||
|
||||
// Additional HTTP bindings for the selector. Nested bindings must
|
||||
// not contain an `additional_bindings` field themselves (that is,
|
||||
// the nesting may only be one level deep).
|
||||
repeated HttpRule additional_bindings = 11;
|
||||
}
|
||||
|
||||
// A custom pattern is used for defining custom HTTP verb.
|
||||
message CustomHttpPattern {
|
||||
// The name of this custom HTTP verb.
|
||||
string kind = 1;
|
||||
|
||||
// The path matched by this custom verb.
|
||||
string path = 2;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// 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.routeguide";
|
||||
option java_outer_classname = "RouteGuideProto";
|
||||
|
||||
package routeguide;
|
||||
|
||||
// Interface exported by the server.
|
||||
service RouteGuide {
|
||||
// A simple RPC.
|
||||
//
|
||||
// Obtains the feature at a given position.
|
||||
//
|
||||
// A feature with an empty name is returned if there's no feature at the given
|
||||
// position.
|
||||
rpc GetFeature(Point) returns (Feature) {}
|
||||
|
||||
// A server-to-client streaming RPC.
|
||||
//
|
||||
// Obtains the Features available within the given Rectangle. Results are
|
||||
// streamed rather than returned at once (e.g. in a response message with a
|
||||
// repeated field), as the rectangle may cover a large area and contain a
|
||||
// huge number of features.
|
||||
rpc ListFeatures(Rectangle) returns (stream Feature) {}
|
||||
|
||||
// A client-to-server streaming RPC.
|
||||
//
|
||||
// Accepts a stream of Points on a route being traversed, returning a
|
||||
// RouteSummary when traversal is completed.
|
||||
rpc RecordRoute(stream Point) returns (RouteSummary) {}
|
||||
|
||||
// A Bidirectional streaming RPC.
|
||||
//
|
||||
// Accepts a stream of RouteNotes sent while a route is being traversed,
|
||||
// while receiving other RouteNotes (e.g. from other users).
|
||||
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
|
||||
}
|
||||
|
||||
// Points are represented as latitude-longitude pairs in the E7 representation
|
||||
// (degrees multiplied by 10**7 and rounded to the nearest integer).
|
||||
// Latitudes should be in the range +/- 90 degrees and longitude should be in
|
||||
// the range +/- 180 degrees (inclusive).
|
||||
message Point {
|
||||
int32 latitude = 1;
|
||||
int32 longitude = 2;
|
||||
}
|
||||
|
||||
// A latitude-longitude rectangle, represented as two diagonally opposite
|
||||
// points "lo" and "hi".
|
||||
message Rectangle {
|
||||
// One corner of the rectangle.
|
||||
Point lo = 1;
|
||||
|
||||
// The other corner of the rectangle.
|
||||
Point hi = 2;
|
||||
}
|
||||
|
||||
// A feature names something at a given point.
|
||||
//
|
||||
// If a feature could not be named, the name is empty.
|
||||
message Feature {
|
||||
// The name of the feature.
|
||||
string name = 1;
|
||||
|
||||
// The point where the feature is detected.
|
||||
Point location = 2;
|
||||
}
|
||||
|
||||
// A RouteNote is a message sent while at a given point.
|
||||
message RouteNote {
|
||||
// The location from which the message is sent.
|
||||
Point location = 1;
|
||||
|
||||
// The message to be sent.
|
||||
string message = 2;
|
||||
}
|
||||
|
||||
// A RouteSummary is received in response to a RecordRoute rpc.
|
||||
//
|
||||
// It contains the number of individual points received, the number of
|
||||
// detected features, and the total distance covered as the cumulative sum of
|
||||
// the distance between each point.
|
||||
message RouteSummary {
|
||||
// The number of points received.
|
||||
int32 point_count = 1;
|
||||
|
||||
// The number of known features passed while traversing the route.
|
||||
int32 feature_count = 2;
|
||||
|
||||
// The distance covered in metres.
|
||||
int32 distance = 3;
|
||||
|
||||
// The duration of the traversal in seconds.
|
||||
int32 elapsed_time = 4;
|
||||
}
|
||||
@@ -0,0 +1,840 @@
|
||||
# gRPC Basics: Tonic
|
||||
|
||||
This tutorial, adapted from [grpc-go], provides a basic introduction to working with gRPC
|
||||
and Tonic. By walking through this example you'll learn how to:
|
||||
|
||||
- Define a service in a `.proto` file.
|
||||
- Generate server and client code.
|
||||
- Write a simple client and server for your service.
|
||||
|
||||
It assumes you are familiar with [protocol buffers] and basic Rust. Note that the example in
|
||||
this tutorial uses the proto3 version of the protocol buffers language, you can find out more in the
|
||||
[proto3 language guide][proto3].
|
||||
|
||||
[grpc-go]: https://github.com/grpc/grpc-go/blob/master/examples/gotutorial.md
|
||||
[protocol buffers]: https://developers.google.com/protocol-buffers/docs/overview
|
||||
[proto3]: https://developers.google.com/protocol-buffers/docs/proto3
|
||||
|
||||
## Why use gRPC?
|
||||
|
||||
Our example is a simple route mapping application that lets clients get information about features
|
||||
on their route, create a summary of their route, and exchange route information such as traffic
|
||||
updates with the server and other clients.
|
||||
|
||||
With gRPC we can define our service once in a `.proto` file and implement clients and servers in
|
||||
any of gRPC's supported languages, which in turn can be run in environments ranging from servers
|
||||
inside Google to your own tablet - all the complexity of communication between different languages
|
||||
and environments is handled for you by gRPC. We also get all the advantages of working with
|
||||
protocol buffers, including efficient serialization, a simple IDL, and easy interface updating.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To run the sample code and walk through the tutorial, the only prerequisite is Rust itself.
|
||||
[rustup] is a convenient tool to install it, if you haven't already.
|
||||
|
||||
[rustup]: https://rustup.rs
|
||||
|
||||
## Running the example
|
||||
|
||||
Clone or download Tonic's repository:
|
||||
|
||||
```shell
|
||||
$ git clone https://github.com/hyperium/tonic.git
|
||||
```
|
||||
|
||||
Change your current directory to Tonic's repository root:
|
||||
```shell
|
||||
$ cd tonic
|
||||
```
|
||||
|
||||
Tonic uses `rustfmt` to tidy up the code it generates, so we'll make sure it's installed.
|
||||
|
||||
```shell
|
||||
$ rustup component add rustfmt
|
||||
```
|
||||
|
||||
Run the server
|
||||
```shell
|
||||
$ cargo run --bin routeguide-server
|
||||
```
|
||||
|
||||
In a separate shell, run the client
|
||||
```shell
|
||||
$ cargo run --bin routeguide-client
|
||||
```
|
||||
|
||||
You should see some logging output flying past really quickly on both terminal windows. On the
|
||||
shell where you ran the client binary, you should see the output of the bidirectional streaming rpc,
|
||||
printing 1 line per second:
|
||||
|
||||
NOTE = RouteNote { location: Some(Point { latitude: 409146139, longitude: -746188906 }), message: "at 1.000319208s" }
|
||||
|
||||
If you scroll up you should see the output of the other 3 request types: simple rpc, server-side
|
||||
streaming and client-side streaming.
|
||||
|
||||
|
||||
[readme]: https://github.com/hyperium/tonic#getting-started
|
||||
|
||||
## Project setup
|
||||
|
||||
We will develop our example from scratch in a new crate:
|
||||
|
||||
```shell
|
||||
$ cargo new routeguide
|
||||
$ cd routeguide
|
||||
```
|
||||
|
||||
|
||||
## Defining the service
|
||||
|
||||
Our first step is to define the gRPC *service* and the method *request* and *response* types using
|
||||
[protocol buffers]. We will keep our `.proto` files in a directory in our crate's root.
|
||||
Note that Tonic does not really care where our `.proto` definitions live. We will see how to use
|
||||
different [code generation configuration](#tonic-build) later in the tutorial.
|
||||
|
||||
```shell
|
||||
$ mkdir proto && touch proto/route_guide.proto
|
||||
```
|
||||
|
||||
You can see the complete `.proto` file in
|
||||
[tonic-examples/proto/routeguide/route_guide.proto][routeguide-proto].
|
||||
|
||||
To define a service, you specify a named `service` in your `.proto` file:
|
||||
|
||||
```proto
|
||||
service RouteGuide {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Then you define `rpc` methods inside your service definition, specifying their request and response
|
||||
types. gRPC lets you define four kinds of service method, all of which are used in the `RouteGuide`
|
||||
service:
|
||||
|
||||
- A *simple RPC* where the client sends a request to the server and waits for a response to come
|
||||
back, just like a normal function call.
|
||||
```proto
|
||||
// Obtains the feature at a given position.
|
||||
rpc GetFeature(Point) returns (Feature) {}
|
||||
```
|
||||
|
||||
- A *server-side streaming RPC* where the client sends a request to the server and gets a stream
|
||||
to read a sequence of messages back. The client reads from the returned stream until there are
|
||||
no more messages. As you can see in our example, you specify a server-side streaming method by
|
||||
placing the `stream` keyword before the *response* type.
|
||||
```proto
|
||||
// Obtains the Features available within the given Rectangle. Results are
|
||||
// streamed rather than returned at once (e.g. in a response message with a
|
||||
// repeated field), as the rectangle may cover a large area and contain a
|
||||
// huge number of features.
|
||||
rpc ListFeatures(Rectangle) returns (stream Feature) {}
|
||||
```
|
||||
|
||||
- A *client-side streaming RPC* where the client writes a sequence of messages and sends them to
|
||||
the server. Once the client has finished writing the messages, it waits for the server to read them
|
||||
all and return its response. You specify a client-side streaming method by placing the `stream`
|
||||
keyword before the *request* type.
|
||||
```proto
|
||||
// Accepts a stream of Points on a route being traversed, returning a
|
||||
// RouteSummary when traversal is completed.
|
||||
rpc RecordRoute(stream Point) returns (RouteSummary) {}
|
||||
```
|
||||
|
||||
- A *bidirectional streaming RPC* where both sides send a sequence of messages. The two streams
|
||||
operate independently, so clients and servers can read and write in whatever
|
||||
order they like: for example, the server could wait to receive all the client messages before
|
||||
writing its responses, or it could alternately read a message then write a message, or some other
|
||||
combination of reads and writes. The order of messages in each stream is preserved. You specify
|
||||
this type of method by placing the `stream` keyword before both the request and the response.
|
||||
```proto
|
||||
// Accepts a stream of RouteNotes sent while a route is being traversed,
|
||||
// while receiving other RouteNotes (e.g. from other users).
|
||||
rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
|
||||
```
|
||||
|
||||
Our `.proto` file also contains protocol buffer message type definitions for all the request and
|
||||
response types used in our service methods - for example, here's the `Point` message type:
|
||||
```proto
|
||||
// Points are represented as latitude-longitude pairs in the E7 representation
|
||||
// (degrees multiplied by 10**7 and rounded to the nearest integer).
|
||||
// Latitudes should be in the range +/- 90 degrees and longitude should be in
|
||||
// the range +/- 180 degrees (inclusive).
|
||||
message Point {
|
||||
int32 latitude = 1;
|
||||
int32 longitude = 2;
|
||||
}
|
||||
```
|
||||
|
||||
[routeguide-proto]: https://github.com/hyperium/tonic/blob/master/tonic-examples/proto/routeguide/route_guide.proto
|
||||
|
||||
## Generating client and server code
|
||||
|
||||
Tonic can be configured to generate code as part cargo's normal build process. This is very
|
||||
convenient because once we've set everything up, there is no extra step to keep the generated code
|
||||
and our `.proto` definitions in sync.
|
||||
|
||||
Behind the scenes, Tonic uses [PROST!] to handle protocol buffer serialization and code
|
||||
generation.
|
||||
|
||||
Edit `Cargo.toml` and add all the dependencies we'll need for this example:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
async-stream = "0.1.2"
|
||||
bytes = "0.4"
|
||||
futures-preview = { version = "0.3.0-alpha.19", default-features = false, features = ["alloc"]}
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
prost = "0.5"
|
||||
rand = "0.7.2"
|
||||
tokio = "0.2.0-alpha.6"
|
||||
tonic = "0.1.0-alpha.5"
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = "0.1.0-alpha.5"
|
||||
```
|
||||
|
||||
Create a `build.rs` file at the root of your crate:
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
tonic_build::compile_protos("proto/route_guide.proto")
|
||||
.unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e));
|
||||
}
|
||||
```
|
||||
|
||||
```shell
|
||||
$ cargo build
|
||||
```
|
||||
|
||||
That's it. The generated code contains:
|
||||
|
||||
- Struct definitions for message types `Point`, `Rectangle`, `Feature`, `RouteNote`, `RouteSummary`.
|
||||
- A service trait we'll need to implement: `server::RouteGuide`.
|
||||
- A client type we'll use to call the server: `client::RouteGuideClient<T>`.
|
||||
|
||||
If your are curious as to where the generated files are, keep reading. The mystery will be revealed
|
||||
soon! We can now move on to the fun part.
|
||||
|
||||
[PROST!]: https://github.com/danburkert/prost
|
||||
|
||||
## Creating the server
|
||||
|
||||
First let's look at how we create a `RouteGuide` server. If you're only interested in creating gRPC
|
||||
clients, you can skip this section and go straight to [Creating the client](#client)
|
||||
(though you might find it interesting anyway!).
|
||||
|
||||
There are two parts to making our `RouteGuide` service do its job:
|
||||
|
||||
- Implementing the service trait generated from our service definition.
|
||||
- Running a gRPC server to listen for requests from clients.
|
||||
|
||||
You can find our example `RouteGuide` server in
|
||||
[tonic-examples/src/routeguide/server.rs][routeguide-server].
|
||||
|
||||
[routeguide-server]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/server.rs
|
||||
|
||||
### Implementing the server::RouteGuide trait
|
||||
|
||||
We can start by defining a struct to represent our service, we can do this on `main.rs` for now:
|
||||
|
||||
```rust
|
||||
#[derive(Debug)]
|
||||
struct RouteGuide;
|
||||
```
|
||||
|
||||
Next, we need to implement the `server::RouteGuide` trait that is generated in our build step.
|
||||
The generated code is placed inside our target directory, in a location defined by the `OUT_DIR`
|
||||
environment variable that is set by cargo. For our example, this means you can find the generated
|
||||
code in a path similar to `target/debug/build/routeguide/out/routeguide.rs`.
|
||||
|
||||
You can learn more about `build.rs` and the `OUT_DIR` environment variable in the [cargo book].
|
||||
|
||||
We can use Tonic's `include_proto` macro to bring the generated code into scope:
|
||||
|
||||
```rust
|
||||
pub mod routeguide {
|
||||
tonic::include_proto!("routeguide");
|
||||
}
|
||||
|
||||
use routeguide::{server, Feature, Point, Rectangle, RouteNote, RouteSummary};
|
||||
```
|
||||
|
||||
**Note**: The token passed to the `include_proto` macro (in our case "routeguide") is the name of
|
||||
the package declared in in our `.proto` file, not a filename, e.g "routeguide.rs".
|
||||
|
||||
With this in place, we can stub out our service implementation:
|
||||
|
||||
```rust
|
||||
use tonic::{Request, Response, Status};
|
||||
use tokio::sync::mpsc;
|
||||
use std::pin::Pin;
|
||||
use futures::Stream;
|
||||
```
|
||||
|
||||
```rust
|
||||
#[tonic::async_trait]
|
||||
impl server::RouteGuide for RouteGuide {
|
||||
async fn get_feature(&self, _request: Request<Point>) -> Result<Response<Feature>, Status> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
type ListFeaturesStream = mpsc::Receiver<Result<Feature, Status>>;
|
||||
|
||||
async fn list_features(
|
||||
&self,
|
||||
_request: Request<Rectangle>,
|
||||
) -> Result<Response<Self::ListFeaturesStream>, Status> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn record_route(
|
||||
&self,
|
||||
_request: Request<tonic::Streaming<Point>>,
|
||||
) -> Result<Response<RouteSummary>, Status> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
type RouteChatStream = Pin<Box<dyn Stream<Item = Result<RouteNote, Status>> + Send + Sync + 'static>>;
|
||||
|
||||
async fn route_chat(
|
||||
&self,
|
||||
_request: Request<tonic::Streaming<RouteNote>>,
|
||||
) -> Result<Response<Self::RouteChatStream>, Status> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Note**: The `tonic::async_trait` attribute macro adds support for async functions in traits. It
|
||||
uses [async-trait] internally. You can learn more about `async fn` in traits in the [async book].
|
||||
|
||||
|
||||
[cargo book]: https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts
|
||||
[async-trait]: https://github.com/dtolnay/async-trait
|
||||
[async book]: https://rust-lang.github.io/async-book/07_workarounds/06_async_in_traits.html
|
||||
|
||||
### Server state
|
||||
Our service needs access to an immutable list of features. When the server starts, we are going to
|
||||
deserialize them from a json file and keep them around as our only piece of shared state:
|
||||
|
||||
```rust
|
||||
#[derive(Debug)]
|
||||
pub struct RouteGuide {
|
||||
features: Arc<Vec<Feature>>,
|
||||
}
|
||||
```
|
||||
|
||||
Create the json data file and a helper module to read and deserialize our features.
|
||||
|
||||
```shell
|
||||
$ mkdir data && touch data/route_guide_db.json
|
||||
$ touch src/data.rs
|
||||
```
|
||||
|
||||
You can find our example json data in [tonic-examples/data/route_guide_db.json][route-guide-db] and
|
||||
the corresponding `data` module to load and deserialize it in
|
||||
[tonic-examples/routeguide/data.rs][data-module].
|
||||
|
||||
**Note:** If you are following along, you'll need to change the data file's path from
|
||||
`tonic-examples/data/route_guide_db.json` to `data/route_guide_db.json`.
|
||||
|
||||
Next, we need to implement `Hash` and `Eq` for `Point`, so we can use point values as map keys:
|
||||
|
||||
```rust
|
||||
use std::hash::{Hasher, Hash};
|
||||
```
|
||||
|
||||
```rust
|
||||
impl Hash for Point {
|
||||
fn hash<H>(&self, state: &mut H)
|
||||
where
|
||||
H: Hasher,
|
||||
{
|
||||
self.latitude.hash(state);
|
||||
self.longitude.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Point {}
|
||||
|
||||
```
|
||||
|
||||
Lastly, we need implement two helper functions: `in_range` and `calc_distance`. We'll use them
|
||||
when performing feature lookups. You can find them in
|
||||
[tonic-examples/src/routeguide/server.rs][in-range-fn].
|
||||
|
||||
[route-guide-db]: https://github.com/hyperium/tonic/blob/master/tonic-examples/data/route_guide_db.json
|
||||
[data-module]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/data.rs
|
||||
[in-range-fn]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/server.rs#L188
|
||||
|
||||
#### Request and Response types
|
||||
All our service methods receive a `tonic::Request<T>` and return a
|
||||
`Result<tonic::Response<T>, tonic::Status>`. The concrete type of `T` depends on how our methods
|
||||
are declared in our *service* `.proto` definition. It can be either:
|
||||
|
||||
- A single value, e.g `Point`, `Rectangle`, or even a message type that includes a repeated field.
|
||||
- A stream of values, e.g. `impl Stream<Item = Result<Feature, tonic::Status>>`.
|
||||
|
||||
#### Simple RPC
|
||||
Let's look at the simplest method first, `get_feature`, which just gets a `tonic::Request<Point>`
|
||||
from the client and tries to find a feature at the given `Point`. If no feature is found, it returns
|
||||
an empty one.
|
||||
|
||||
```rust
|
||||
async fn get_feature(&self, request: Request<Point>) -> Result<Response<Feature>, Status> {
|
||||
for feature in &self.features[..] {
|
||||
if feature.location.as_ref() == Some(request.get_ref()) {
|
||||
return Ok(Response::new(feature.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
let response = Response::new(Feature {
|
||||
name: "".to_string(),
|
||||
location: None,
|
||||
});
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### Server-side streaming RPC
|
||||
Now let's look at one of our streaming RPCs. `list_features` is a server-side streaming RPC, so we
|
||||
need to send back multiple `Feature`s to our client.
|
||||
|
||||
```rust
|
||||
type ListFeaturesStream = mpsc::Receiver<Result<Feature, Status>>;
|
||||
|
||||
async fn list_features(
|
||||
&self,
|
||||
request: Request<Rectangle>,
|
||||
) -> Result<Response<Self::ListFeaturesStream>, Status> {
|
||||
let (mut tx, rx) = mpsc::channel(4);
|
||||
let features = self.features.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
for feature in &features[..] {
|
||||
if in_range(feature.location.as_ref().unwrap(), request.get_ref()) {
|
||||
tx.send(Ok(feature.clone())).await.unwrap();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(Response::new(rx))
|
||||
}
|
||||
```
|
||||
|
||||
Like `get_feature`, `list_features`'s input is a single message, a `Rectangle` in this
|
||||
case. This time, however, we need to return a stream of values, rather than a single one.
|
||||
We create a channel and spawn a new asynchronous task where we perform a lookup, sending
|
||||
the features that satisfy our constraints into the channel.
|
||||
|
||||
The `Stream` half of the channel is returned to the caller, wrapped in a `tonic::Response`.
|
||||
|
||||
|
||||
#### Client-side streaming RPC
|
||||
Now let's look at something a little more complicated: the client-side streaming method
|
||||
`record_route`, where we get a stream of `Point`s from the client and return a single `RouteSummary`
|
||||
with information about their trip. As you can see, this time the method receives a
|
||||
`tonic::Request<tonic::Streaming<Point>>`.
|
||||
|
||||
```rust
|
||||
use std::time::Instant;
|
||||
use futures::StreamExt;
|
||||
```
|
||||
|
||||
```rust
|
||||
async fn record_route(
|
||||
&self,
|
||||
request: Request<tonic::Streaming<Point>>,
|
||||
) -> Result<Response<RouteSummary>, Status> {
|
||||
let stream = request.into_inner();
|
||||
futures::pin_mut!(stream);
|
||||
|
||||
let mut summary = RouteSummary::default();
|
||||
let mut last_point = None;
|
||||
let now = Instant::now();
|
||||
|
||||
while let Some(point) = stream.next().await {
|
||||
let point = point?;
|
||||
summary.point_count += 1;
|
||||
|
||||
for feature in &self.features[..] {
|
||||
if feature.location.as_ref() == Some(&point) {
|
||||
summary.feature_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ref last_point) = last_point {
|
||||
summary.distance += calc_distance(last_point, &point);
|
||||
}
|
||||
|
||||
last_point = Some(point);
|
||||
}
|
||||
|
||||
summary.elapsed_time = now.elapsed().as_secs() as i32;
|
||||
|
||||
Ok(Response::new(summary))
|
||||
}
|
||||
```
|
||||
|
||||
`record_route` is conceptually simple: we get a stream of `Points` and fold it into a `RouteSummary`.
|
||||
In other words, we build a summary value as we process each `Point` in our stream, one by one.
|
||||
When there are no more `Points` in our stream, we return the `RouteSummary` wrapped in a
|
||||
`tonic::Response`.
|
||||
|
||||
#### Bidirectional streaming RPC
|
||||
Finally, let's look at our bidirectional streaming RPC `route_chat`, which receives a stream
|
||||
of `RouteNote`s and returns a stream of `RouteNote`s.
|
||||
|
||||
```rust
|
||||
type RouteChatStream = Pin<Box<dyn Stream<Item = Result<RouteNote, Status>> + Send + 'static>>;
|
||||
|
||||
async fn route_chat(
|
||||
&self,
|
||||
request: Request<tonic::Streaming<RouteNote>>,
|
||||
) -> Result<Response<Self::RouteChatStream>, Status> {
|
||||
let mut notes = HashMap::new();
|
||||
let stream = request.into_inner();
|
||||
|
||||
let output = async_stream::try_stream! {
|
||||
futures::pin_mut!(stream);
|
||||
|
||||
while let Some(note) = stream.next().await {
|
||||
let note = note?;
|
||||
|
||||
let location = note.location.clone().unwrap();
|
||||
|
||||
let location_notes = notes.entry(location).or_insert(vec![]);
|
||||
location_notes.push(note);
|
||||
|
||||
for note in location_notes {
|
||||
yield note.clone();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Response::new(Box::pin(output)
|
||||
as Pin<
|
||||
Box<dyn Stream<Item = Result<RouteNote, Status>> + Send + Sync + 'static>,
|
||||
>))
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
`route_chat` uses the [async-stream] crate to perform an asynchronous transformation
|
||||
from one (input) stream to another (output) stream. As the input is processed, each value is
|
||||
inserted into the notes map, yielding a clone of the original `RouteNote`. The resulting stream
|
||||
is then returned to the caller. Neat.
|
||||
|
||||
**Note**: The funky `as` cast is needed due to a limitation in the rust compiler. This is expected
|
||||
to be fixed soon.
|
||||
|
||||
[async-stream]: https://github.com/tokio-rs/async-stream
|
||||
|
||||
### Starting the server
|
||||
|
||||
Once we've implemented all our methods, we also need to start up a gRPC server so that clients can
|
||||
actually use our service. This is how our `main` function looks like:
|
||||
|
||||
```rust
|
||||
mod data;
|
||||
use tonic::transport::Server;
|
||||
```
|
||||
|
||||
```rust
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let addr = "[::1]:10000".parse().unwrap();
|
||||
|
||||
let route_guide = RouteGuide {
|
||||
features: Arc::new(data::load()),
|
||||
};
|
||||
|
||||
let svc = server::RouteGuideServer::new(route_guide);
|
||||
|
||||
Server::builder()
|
||||
.add_service(svc)
|
||||
.serve(addr)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
To handle requests, `Tonic` uses [Tower] and [hyper] internally. What this means,
|
||||
among other things, is that we have a flexible and composable stack we can build on top of. We can,
|
||||
for example, add an [interceptor][authentication-example] or implement [routing][router-example].
|
||||
In the future, Tonic may include higher level support for routing and interceptors.
|
||||
|
||||
|
||||
[Tower]: https://github.com/tower-rs
|
||||
[hyper]: https://github.com/hyperium/hyper
|
||||
[authentication-example]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/authentication/server.rs#L54
|
||||
[router-example]: https://github.com/hyperium/tonic/blob/master/tonic-interop/src/bin/server.rs#L73
|
||||
|
||||
<a name="client"></a>
|
||||
## Creating the client
|
||||
|
||||
In this section, we'll look at creating a Tonic client for our `RouteGuide` service. You can see our
|
||||
complete example client code in [tonic-examples/src/routeguide/client.rs][routeguide-client].
|
||||
|
||||
Our crate will have two binary targets: `routeguide-client` and `routeguide-server`. We need to
|
||||
edit our `Cargo.toml` accordingly:
|
||||
|
||||
```toml
|
||||
[[bin]]
|
||||
name = "routeguide-server"
|
||||
path = "src/server.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "routeguide-client"
|
||||
path = "src/client.rs"
|
||||
```
|
||||
|
||||
Rename `main.rs` to `server.rs` and create a new file `client.rs`.
|
||||
|
||||
```shell
|
||||
$ mv src/main.rs src/server.rs
|
||||
$ touch src/client.rs
|
||||
```
|
||||
|
||||
To call service methods, we first need to create a gRPC *client* to communicate with the server.
|
||||
|
||||
```rust
|
||||
pub mod route_guide {
|
||||
tonic::include_proto!("routeguide");
|
||||
}
|
||||
|
||||
use route_guide::{client::RouteGuideClient, Point, Rectangle, RouteNote};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut client = RouteGuideClient::connect("http://[::1]:10000").await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
Same as in the server implementation, we start by bringing our generated code into scope. We then
|
||||
create a client in our main function, passing the server's full URL to `RouteGuideClient::connect`.
|
||||
Our client is now ready to make service calls. Note that `client` is mutable, this is because it
|
||||
needs to manage internal state.
|
||||
|
||||
[routeguide-client]: https://github.com/hyperium/tonic/blob/master/tonic-examples/src/routeguide/client.rs
|
||||
|
||||
|
||||
### Calling service methods
|
||||
Now let's look at how we call our service methods. Note that in Tonic, RPCs are asynchronous,
|
||||
which means that RPC calls need to be `.await`ed.
|
||||
|
||||
#### Simple RPC
|
||||
Calling the simple RPC `get_feature` is as straightforward as calling a local method:
|
||||
|
||||
```rust
|
||||
use tonic::Request;
|
||||
```
|
||||
|
||||
```rust
|
||||
let response = client
|
||||
.get_feature(Request::new(Point {
|
||||
latitude: 409146138,
|
||||
longitude: -746188906,
|
||||
}))
|
||||
.await?;
|
||||
|
||||
println!("RESPONSE = {:?}", response);
|
||||
```
|
||||
We call the `get_feature` client method, passing a single `Point` value wrapped in a
|
||||
`tonic::Request`. We get a `Result<tonic::Response<Feature>, tonic::Status>` back.
|
||||
|
||||
#### Server-side streaming RPC
|
||||
Here's where we call the server-side streaming method `list_features`, which returns a stream of
|
||||
geographical `Feature`s.
|
||||
|
||||
```rust
|
||||
use tonic::transport::Channel;
|
||||
use std::error::Error;
|
||||
```
|
||||
|
||||
```rust
|
||||
async fn print_features(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
|
||||
let rectangle = Rectangle {
|
||||
lo: Some(Point {
|
||||
latitude: 400000000,
|
||||
longitude: -750000000,
|
||||
}),
|
||||
hi: Some(Point {
|
||||
latitude: 420000000,
|
||||
longitude: -730000000,
|
||||
}),
|
||||
};
|
||||
|
||||
let mut stream = client
|
||||
.list_features(Request::new(rectangle))
|
||||
.await?
|
||||
.into_inner();
|
||||
|
||||
while let Some(feature) = stream.message().await? {
|
||||
println!("NOTE = {:?}", feature);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
As in the simple RPC, we pass a single value request. However, instead of getting a
|
||||
single value back, we get a stream of `Features`.
|
||||
|
||||
We use the the `message()` method from the `tonic::Streaming` struct to repeatedly read in the
|
||||
server's responses to a response protocol buffer object (in this case a `Feature`) until there are
|
||||
no more messages left in the stream.
|
||||
|
||||
#### Client-side streaming RPC
|
||||
The client-side streaming method `record_route` takes a stream of `Point`s and returns a single
|
||||
`RouteSummary` value.
|
||||
|
||||
```rust
|
||||
use rand::rngs::ThreadRng;
|
||||
use rand::Rng;
|
||||
use futures::stream;
|
||||
```
|
||||
|
||||
```rust
|
||||
async fn run_record_route(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
|
||||
let mut rng = rand::thread_rng();
|
||||
let point_count: i32 = rng.gen_range(2, 100);
|
||||
|
||||
let mut points = vec![];
|
||||
for _ in 0..=point_count {
|
||||
points.push(random_point(&mut rng))
|
||||
}
|
||||
|
||||
println!("Traversing {} points", points.len());
|
||||
let request = Request::new(stream::iter(points));
|
||||
|
||||
match client.record_route(request).await {
|
||||
Ok(response) => println!("SUMMARY: {:?}", response.into_inner()),
|
||||
Err(e) => println!("something went wrong: {:?}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
```rust
|
||||
fn random_point(rng: &mut ThreadRng) -> Point {
|
||||
let latitude = (rng.gen_range(0, 180) - 90) * 10_000_000;
|
||||
let longitude = (rng.gen_range(0, 360) - 180) * 10_000_000;
|
||||
Point {
|
||||
latitude,
|
||||
longitude,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
We build a vector of a random number of `Point` values (between 2 and 100) and then convert
|
||||
it into a `Stream` using the `futures::stream::iter` function. This is a cheap an easy way to get
|
||||
a stream suitable for passing into our service method. The resulting stream is then wrapped in a
|
||||
`tonic::Request`.
|
||||
|
||||
|
||||
#### Bidirectional streaming RPC
|
||||
|
||||
Finally, let's look at our bidirectional streaming RPC. The `route_chat` method takes a stream
|
||||
of `RouteNotes` and returns either another stream of `RouteNotes` or an error.
|
||||
|
||||
```rust
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::timer::Interval;
|
||||
```
|
||||
|
||||
```rust
|
||||
async fn run_route_chat(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
|
||||
let start = Instant::now();
|
||||
|
||||
let outbound = async_stream::stream! {
|
||||
let mut interval = Interval::new_interval(Duration::from_secs(1));
|
||||
|
||||
while let Some(time) = interval.next().await {
|
||||
let elapsed = time.duration_since(start);
|
||||
let note = RouteNote {
|
||||
location: Some(Point {
|
||||
latitude: 409146138 + elapsed.as_secs() as i32,
|
||||
longitude: -746188906,
|
||||
}),
|
||||
message: format!("at {:?}", elapsed),
|
||||
};
|
||||
|
||||
yield note;
|
||||
}
|
||||
};
|
||||
|
||||
let request = Request::new(outbound);
|
||||
let response = client.route_chat(request).await?;
|
||||
let mut inbound = response.into_inner();
|
||||
|
||||
while let Some(note) = inbound.message().await? {
|
||||
println!("NOTE = {:?}", note);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
In this case, we use the [async-stream] crate to generate our outbound stream, yielding
|
||||
`RouteNote` values in one second intervals. We then iterate over the stream returned by
|
||||
the server, printing each value in the stream.
|
||||
|
||||
## Try it out!
|
||||
|
||||
### Run the server
|
||||
```shell
|
||||
$ cargo run --bin routeguide-server
|
||||
```
|
||||
|
||||
### Run the client
|
||||
```shell
|
||||
$ cargo run --bin routeguide-client
|
||||
```
|
||||
|
||||
## Appendix
|
||||
|
||||
<a name="tonic-build"></a>
|
||||
### tonic_build configuration
|
||||
|
||||
Tonic's default code generation configuration is convenient for self contained examples and small
|
||||
projects. However, there are some cases when we need a slightly different workflow. For example:
|
||||
|
||||
- When building rust clients and servers in different crates.
|
||||
- When building a rust client or server (or both) as part of a larger, multi-language project.
|
||||
- When we want editor support for the generate code and our editor does not index the generated
|
||||
files in the default location.
|
||||
|
||||
More generally, whenever we want to keep our `.proto` definitions in a central place and generate
|
||||
code for different crates or different languages, the default configuration is not enough.
|
||||
|
||||
Luckily, `tonic_build` can be configured to fit whatever workflow we need. Here are just two
|
||||
possibilities:
|
||||
|
||||
1) We can keep our `.proto` definitions in a separate crate and generate our code on demand, as
|
||||
opposed to at build time, placing the resulting modules wherever we need them.
|
||||
|
||||
`main.rs`
|
||||
|
||||
```rust
|
||||
fn main() {
|
||||
tonic_build::configure()
|
||||
.build_client(false)
|
||||
.out_dir("another_crate/src/pb")
|
||||
.compile(&["path/my_proto.proto"], &["path"])
|
||||
.expect("failed to compile protos");
|
||||
}
|
||||
```
|
||||
|
||||
On `cargo run`, this will generate code for the client only, and place the resulting file in
|
||||
`another_crate/src/pb`.
|
||||
|
||||
2) Similarly, we could also keep the `.proto` definitions in a separate crate and then use that
|
||||
crate as a direct dependency wherever we need it.
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
pub mod pb {
|
||||
tonic::include_proto!("grpc.examples.echo");
|
||||
}
|
||||
|
||||
use http::header::HeaderValue;
|
||||
use pb::{echo_client::EchoClient, EchoRequest};
|
||||
use tonic::transport::Channel;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let channel = Channel::from_static("http://[::1]:50051")
|
||||
.intercept_headers(|headers| {
|
||||
headers.insert(
|
||||
"authorization",
|
||||
HeaderValue::from_static("Bearer some-secret-token"),
|
||||
);
|
||||
})
|
||||
.connect()
|
||||
.await?;
|
||||
|
||||
let mut client = EchoClient::new(channel);
|
||||
|
||||
let request = tonic::Request::new(EchoRequest {
|
||||
message: "hello".into(),
|
||||
});
|
||||
|
||||
let response = client.unary_echo(request).await?;
|
||||
|
||||
println!("RESPONSE={:?}", response);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
pub mod pb {
|
||||
tonic::include_proto!("grpc.examples.echo");
|
||||
}
|
||||
|
||||
use futures::Stream;
|
||||
use pb::{EchoRequest, EchoResponse};
|
||||
use std::pin::Pin;
|
||||
use tonic::{body::BoxBody, transport::Server, Request, Response, Status, Streaming};
|
||||
use tower::Service;
|
||||
|
||||
type EchoResult<T> = Result<Response<T>, Status>;
|
||||
type ResponseStream = Pin<Box<dyn Stream<Item = Result<EchoResponse, Status>> + Send + Sync>>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct EchoServer;
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl pb::echo_server::Echo for EchoServer {
|
||||
async fn unary_echo(&self, request: Request<EchoRequest>) -> EchoResult<EchoResponse> {
|
||||
let message = request.into_inner().message;
|
||||
Ok(Response::new(EchoResponse { message }))
|
||||
}
|
||||
|
||||
type ServerStreamingEchoStream = ResponseStream;
|
||||
|
||||
async fn server_streaming_echo(
|
||||
&self,
|
||||
_: Request<EchoRequest>,
|
||||
) -> EchoResult<Self::ServerStreamingEchoStream> {
|
||||
Err(Status::unimplemented("not implemented"))
|
||||
}
|
||||
|
||||
async fn client_streaming_echo(
|
||||
&self,
|
||||
_: Request<Streaming<EchoRequest>>,
|
||||
) -> EchoResult<EchoResponse> {
|
||||
Err(Status::unimplemented("not implemented"))
|
||||
}
|
||||
|
||||
type BidirectionalStreamingEchoStream = ResponseStream;
|
||||
|
||||
async fn bidirectional_streaming_echo(
|
||||
&self,
|
||||
_: Request<Streaming<EchoRequest>>,
|
||||
) -> EchoResult<Self::BidirectionalStreamingEchoStream> {
|
||||
Err(Status::unimplemented("not implemented"))
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let addr = "[::1]:50051".parse().unwrap();
|
||||
let server = EchoServer::default();
|
||||
|
||||
Server::builder()
|
||||
.interceptor_fn(move |svc, req| {
|
||||
let auth_header = req.headers().get("authorization").clone();
|
||||
|
||||
let authed = if let Some(auth_header) = auth_header {
|
||||
auth_header == "Bearer some-secret-token"
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let fut = svc.call(req);
|
||||
|
||||
async move {
|
||||
if authed {
|
||||
fut.await
|
||||
} else {
|
||||
// Cancel the inner future since we never await it
|
||||
// the IO never gets registered.
|
||||
drop(fut);
|
||||
let res = http::Response::builder()
|
||||
.header("grpc-status", "16")
|
||||
.body(BoxBody::empty())
|
||||
.unwrap();
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
})
|
||||
.add_service(pb::echo_server::EchoServer::new(server))
|
||||
.serve(addr)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
use tokio::runtime::{Builder, Runtime};
|
||||
|
||||
pub mod hello_world {
|
||||
tonic::include_proto!("helloworld");
|
||||
}
|
||||
|
||||
use hello_world::{greeter_client::GreeterClient, HelloReply, HelloRequest};
|
||||
|
||||
type StdError = Box<dyn std::error::Error + Send + Sync + 'static>;
|
||||
type Result<T, E = StdError> = ::std::result::Result<T, E>;
|
||||
|
||||
// The order of the fields in this struct is important. The runtime must be the first field and the
|
||||
// client must be the last field so that when `BlockingClient` is dropped the client is dropped
|
||||
// before the runtime. Not doing this will result in a deadlock when dropped.
|
||||
struct BlockingClient {
|
||||
rt: Runtime,
|
||||
client: GreeterClient<tonic::transport::Channel>,
|
||||
}
|
||||
|
||||
impl BlockingClient {
|
||||
pub fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
|
||||
where
|
||||
D: std::convert::TryInto<tonic::transport::Endpoint>,
|
||||
D::Error: Into<StdError>,
|
||||
{
|
||||
let mut rt = Builder::new()
|
||||
.basic_scheduler()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
let client = rt.block_on(GreeterClient::connect(dst))?;
|
||||
|
||||
Ok(Self { rt, client })
|
||||
}
|
||||
|
||||
pub fn say_hello(
|
||||
&mut self,
|
||||
request: impl tonic::IntoRequest<HelloRequest>,
|
||||
) -> Result<tonic::Response<HelloReply>, tonic::Status> {
|
||||
self.rt.block_on(self.client.say_hello(request))
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let mut client = BlockingClient::connect("http://[::1]:50051")?;
|
||||
|
||||
let request = tonic::Request::new(HelloRequest {
|
||||
name: "Tonic".into(),
|
||||
});
|
||||
|
||||
let response = client.say_hello(request)?;
|
||||
|
||||
println!("RESPONSE={:?}", response);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Google Cloud Pubsub example
|
||||
|
||||
This example will attempt to fetch a list of topics using the google
|
||||
gRPC protobuf specification. This will use an OAuth token and TLS to
|
||||
fetch the list of topics.
|
||||
|
||||
First, you must generate a access token via the [OAuth playground]. From here
|
||||
select the `Cloud Pub/Sub API v1` and its urls as the scope. This will start
|
||||
the OAuth flow. Then you must hit the `Exchange authorization code for tokens`
|
||||
button to generate an `access_token` which will show up in the HTTP response
|
||||
to the right under the `access_token` field in the response json.
|
||||
|
||||
Once, you have this token you must fetch your GCP project id which can be found
|
||||
from the main page on the dashboard. When you have both of these items you can
|
||||
run the example like so:
|
||||
|
||||
```shell
|
||||
GCP_AUTH_TOKEN="<access-token>" cargo run --bin gcp-client -- <project-id>
|
||||
```
|
||||
|
||||
[OAuth playground]: https://developers.google.com/oauthplayground/
|
||||
@@ -0,0 +1,55 @@
|
||||
pub mod api {
|
||||
tonic::include_proto!("google.pubsub.v1");
|
||||
}
|
||||
|
||||
use api::{publisher_client::PublisherClient, ListTopicsRequest};
|
||||
use http::header::HeaderValue;
|
||||
use tonic::{
|
||||
transport::{Certificate, Channel, ClientTlsConfig},
|
||||
Request,
|
||||
};
|
||||
|
||||
const ENDPOINT: &str = "https://pubsub.googleapis.com";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let token = std::env::var("GCP_AUTH_TOKEN").map_err(|_| {
|
||||
"Pass a valid 0Auth bearer token via `GCP_AUTH_TOKEN` environment variable.".to_string()
|
||||
})?;
|
||||
|
||||
let project = std::env::args()
|
||||
.skip(1)
|
||||
.next()
|
||||
.ok_or("Expected a project name as the first argument.".to_string())?;
|
||||
|
||||
let bearer_token = format!("Bearer {}", token);
|
||||
let header_value = HeaderValue::from_str(&bearer_token)?;
|
||||
|
||||
let certs = tokio::fs::read("tonic-examples/data/gcp/roots.pem").await?;
|
||||
|
||||
let tls_config = ClientTlsConfig::with_rustls()
|
||||
.ca_certificate(Certificate::from_pem(certs.as_slice()))
|
||||
.domain_name("pubsub.googleapis.com");
|
||||
|
||||
let channel = Channel::from_static(ENDPOINT)
|
||||
.intercept_headers(move |headers| {
|
||||
headers.insert("authorization", header_value.clone());
|
||||
})
|
||||
.tls_config(tls_config)
|
||||
.connect()
|
||||
.await?;
|
||||
|
||||
let mut service = PublisherClient::new(channel);
|
||||
|
||||
let response = service
|
||||
.list_topics(Request::new(ListTopicsRequest {
|
||||
project: format!("projects/{0}", project),
|
||||
page_size: 10,
|
||||
..Default::default()
|
||||
}))
|
||||
.await?;
|
||||
|
||||
println!("RESPONSE={:?}", response);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
pub mod hello_world {
|
||||
tonic::include_proto!("helloworld");
|
||||
}
|
||||
|
||||
use hello_world::{greeter_client::GreeterClient, HelloRequest};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut client = GreeterClient::connect("http://[::1]:50051").await?;
|
||||
|
||||
let request = tonic::Request::new(HelloRequest {
|
||||
name: "Tonic".into(),
|
||||
});
|
||||
|
||||
let response = client.say_hello(request).await?;
|
||||
|
||||
println!("RESPONSE={:?}", response);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
use tonic::{transport::Server, Request, Response, Status};
|
||||
|
||||
pub mod hello_world {
|
||||
tonic::include_proto!("helloworld");
|
||||
}
|
||||
|
||||
use hello_world::{
|
||||
greeter_server::{Greeter, GreeterServer},
|
||||
HelloReply, HelloRequest,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MyGreeter {}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl Greeter for MyGreeter {
|
||||
async fn say_hello(
|
||||
&self,
|
||||
request: Request<HelloRequest>,
|
||||
) -> Result<Response<HelloReply>, Status> {
|
||||
println!("Got a request: {:?}", request);
|
||||
|
||||
let reply = hello_world::HelloReply {
|
||||
message: format!("Hello {}!", request.into_inner().name).into(),
|
||||
};
|
||||
Ok(Response::new(reply))
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let addr = "[::1]:50051".parse().unwrap();
|
||||
let greeter = MyGreeter::default();
|
||||
|
||||
Server::builder()
|
||||
.add_service(GreeterServer::new(greeter))
|
||||
.serve(addr)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
pub mod pb {
|
||||
tonic::include_proto!("grpc.examples.echo");
|
||||
}
|
||||
|
||||
use pb::{echo_client::EchoClient, EchoRequest};
|
||||
use tonic::transport::Channel;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let endpoints = ["http://[::1]:50051", "http://[::1]:50052"]
|
||||
.into_iter()
|
||||
.map(|a| Channel::from_static(a));
|
||||
|
||||
let channel = Channel::balance_list(endpoints);
|
||||
|
||||
let mut client = EchoClient::new(channel);
|
||||
|
||||
for _ in 0..12usize {
|
||||
let request = tonic::Request::new(EchoRequest {
|
||||
message: "hello".into(),
|
||||
});
|
||||
|
||||
let response = client.unary_echo(request).await?;
|
||||
|
||||
println!("RESPONSE={:?}", response);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
pub mod pb {
|
||||
tonic::include_proto!("grpc.examples.echo");
|
||||
}
|
||||
|
||||
use futures::Stream;
|
||||
use std::net::SocketAddr;
|
||||
use std::pin::Pin;
|
||||
use tokio::sync::mpsc;
|
||||
use tonic::{transport::Server, Request, Response, Status, Streaming};
|
||||
|
||||
use pb::{EchoRequest, EchoResponse};
|
||||
|
||||
type EchoResult<T> = Result<Response<T>, Status>;
|
||||
type ResponseStream = Pin<Box<dyn Stream<Item = Result<EchoResponse, Status>> + Send + Sync>>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct EchoServer {
|
||||
addr: SocketAddr,
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl pb::echo_server::Echo for EchoServer {
|
||||
async fn unary_echo(&self, request: Request<EchoRequest>) -> EchoResult<EchoResponse> {
|
||||
let message = format!("{} (from {})", request.into_inner().message, self.addr);
|
||||
|
||||
Ok(Response::new(EchoResponse { message }))
|
||||
}
|
||||
|
||||
type ServerStreamingEchoStream = ResponseStream;
|
||||
|
||||
async fn server_streaming_echo(
|
||||
&self,
|
||||
_: Request<EchoRequest>,
|
||||
) -> EchoResult<Self::ServerStreamingEchoStream> {
|
||||
Err(Status::unimplemented("not implemented"))
|
||||
}
|
||||
|
||||
async fn client_streaming_echo(
|
||||
&self,
|
||||
_: Request<Streaming<EchoRequest>>,
|
||||
) -> EchoResult<EchoResponse> {
|
||||
Err(Status::unimplemented("not implemented"))
|
||||
}
|
||||
|
||||
type BidirectionalStreamingEchoStream = ResponseStream;
|
||||
|
||||
async fn bidirectional_streaming_echo(
|
||||
&self,
|
||||
_: Request<Streaming<EchoRequest>>,
|
||||
) -> EchoResult<Self::BidirectionalStreamingEchoStream> {
|
||||
Err(Status::unimplemented("not implemented"))
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let addrs = ["[::1]:50051", "[::1]:50052"];
|
||||
|
||||
let (tx, mut rx) = mpsc::unbounded_channel();
|
||||
|
||||
for addr in &addrs {
|
||||
let addr = addr.parse()?;
|
||||
let tx = tx.clone();
|
||||
|
||||
let server = EchoServer { addr };
|
||||
let serve = Server::builder()
|
||||
.add_service(pb::echo_server::EchoServer::new(server))
|
||||
.serve(addr);
|
||||
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = serve.await {
|
||||
eprintln!("Error = {:?}", e);
|
||||
}
|
||||
|
||||
tx.send(()).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
rx.recv().await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
pub mod hello_world {
|
||||
tonic::include_proto!("helloworld");
|
||||
}
|
||||
|
||||
pub mod echo {
|
||||
tonic::include_proto!("grpc.examples.echo");
|
||||
}
|
||||
|
||||
use echo::{echo_client::EchoClient, EchoRequest};
|
||||
use hello_world::{greeter_client::GreeterClient, HelloRequest};
|
||||
use tonic::transport::Endpoint;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let channel = Endpoint::from_static("http://[::1]:50051")
|
||||
.connect()
|
||||
.await?;
|
||||
|
||||
let mut greeter_client = GreeterClient::new(channel.clone());
|
||||
let mut echo_client = EchoClient::new(channel);
|
||||
|
||||
let request = tonic::Request::new(HelloRequest {
|
||||
name: "Tonic".into(),
|
||||
});
|
||||
|
||||
let response = greeter_client.say_hello(request).await?;
|
||||
|
||||
println!("GREETER RESPONSE={:?}", response);
|
||||
|
||||
let request = tonic::Request::new(EchoRequest {
|
||||
message: "hello".into(),
|
||||
});
|
||||
|
||||
let response = echo_client.unary_echo(request).await?;
|
||||
|
||||
println!("ECHO RESPONSE={:?}", response);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
use futures::Stream;
|
||||
use std::pin::Pin;
|
||||
use tonic::{transport::Server, Request, Response, Status};
|
||||
|
||||
pub mod hello_world {
|
||||
tonic::include_proto!("helloworld");
|
||||
}
|
||||
|
||||
pub mod echo {
|
||||
tonic::include_proto!("grpc.examples.echo");
|
||||
}
|
||||
|
||||
use hello_world::{
|
||||
greeter_server::{Greeter, GreeterServer},
|
||||
HelloReply, HelloRequest,
|
||||
};
|
||||
|
||||
use echo::{
|
||||
echo_server::{Echo, EchoServer},
|
||||
EchoRequest, EchoResponse,
|
||||
};
|
||||
|
||||
type ResponseStream = Pin<Box<dyn Stream<Item = Result<EchoResponse, Status>> + Send + Sync>>;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let addr = "[::1]:50051".parse().unwrap();
|
||||
|
||||
let greeter = GreeterServer::new(MyGreeter::default());
|
||||
let echo = EchoServer::new(MyEcho::default());
|
||||
|
||||
Server::builder()
|
||||
.add_service(greeter)
|
||||
.add_service(echo)
|
||||
.serve(addr)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MyGreeter {}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl Greeter for MyGreeter {
|
||||
async fn say_hello(
|
||||
&self,
|
||||
request: Request<HelloRequest>,
|
||||
) -> Result<Response<HelloReply>, Status> {
|
||||
let reply = hello_world::HelloReply {
|
||||
message: format!("Hello {}!", request.into_inner().name).into(),
|
||||
};
|
||||
Ok(Response::new(reply))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MyEcho;
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl Echo for MyEcho {
|
||||
async fn unary_echo(
|
||||
&self,
|
||||
request: Request<EchoRequest>,
|
||||
) -> Result<Response<EchoResponse>, Status> {
|
||||
let message = request.into_inner().message;
|
||||
Ok(Response::new(EchoResponse { message }))
|
||||
}
|
||||
|
||||
type ServerStreamingEchoStream = ResponseStream;
|
||||
type BidirectionalStreamingEchoStream = ResponseStream;
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
use futures::stream;
|
||||
use rand::rngs::ThreadRng;
|
||||
use rand::Rng;
|
||||
use route_guide::{Point, Rectangle, RouteNote};
|
||||
use std::error::Error;
|
||||
use std::time::Duration;
|
||||
use tokio::time::Instant;
|
||||
use tonic::transport::Channel;
|
||||
use tonic::Request;
|
||||
|
||||
pub mod route_guide {
|
||||
tonic::include_proto!("routeguide");
|
||||
}
|
||||
|
||||
use route_guide::routeguide_client::RouteGuideClient;
|
||||
|
||||
async fn print_features(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
|
||||
let rectangle = Rectangle {
|
||||
lo: Some(Point {
|
||||
latitude: 400000000,
|
||||
longitude: -750000000,
|
||||
}),
|
||||
hi: Some(Point {
|
||||
latitude: 420000000,
|
||||
longitude: -730000000,
|
||||
}),
|
||||
};
|
||||
|
||||
let mut stream = client
|
||||
.list_features(Request::new(rectangle))
|
||||
.await?
|
||||
.into_inner();
|
||||
|
||||
while let Some(feature) = stream.message().await? {
|
||||
println!("NOTE = {:?}", feature);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_record_route(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
|
||||
let mut rng = rand::thread_rng();
|
||||
let point_count: i32 = rng.gen_range(2, 100);
|
||||
|
||||
let mut points = vec![];
|
||||
for _ in 0..=point_count {
|
||||
points.push(random_point(&mut rng))
|
||||
}
|
||||
|
||||
println!("Traversing {} points", points.len());
|
||||
let request = Request::new(stream::iter(points));
|
||||
|
||||
match client.record_route(request).await {
|
||||
Ok(response) => println!("SUMMARY: {:?}", response.into_inner()),
|
||||
Err(e) => println!("something went wrong: {:?}", e),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run_route_chat(client: &mut RouteGuideClient<Channel>) -> Result<(), Box<dyn Error>> {
|
||||
let start = Instant::now();
|
||||
|
||||
let outbound = async_stream::stream! {
|
||||
let mut interval = tokio::time::interval(Duration::from_secs(1));
|
||||
|
||||
while let time = interval.tick().await {
|
||||
let elapsed = time.duration_since(start);
|
||||
let note = RouteNote {
|
||||
location: Some(Point {
|
||||
latitude: 409146138 + elapsed.as_secs() as i32,
|
||||
longitude: -746188906,
|
||||
}),
|
||||
message: format!("at {:?}", elapsed),
|
||||
};
|
||||
|
||||
yield note;
|
||||
}
|
||||
};
|
||||
|
||||
let request = Request::new(outbound);
|
||||
let response = client.route_chat(request).await?;
|
||||
let mut inbound = response.into_inner();
|
||||
|
||||
while let Some(note) = inbound.message().await? {
|
||||
println!("NOTE = {:?}", note);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut client = RouteGuideClient::connect("http://[::1]:10000").await?;
|
||||
|
||||
println!("*** SIMPLE RPC ***");
|
||||
let response = client
|
||||
.get_feature(Request::new(Point {
|
||||
latitude: 409146138,
|
||||
longitude: -746188906,
|
||||
}))
|
||||
.await?;
|
||||
println!("RESPONSE = {:?}", response);
|
||||
|
||||
println!("\n*** SERVER STREAMING ***");
|
||||
print_features(&mut client).await?;
|
||||
|
||||
println!("\n*** CLIENT STREAMING ***");
|
||||
run_record_route(&mut client).await?;
|
||||
|
||||
println!("\n*** BIDIRECTIONAL STREAMING ***");
|
||||
run_route_chat(&mut client).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn random_point(rng: &mut ThreadRng) -> Point {
|
||||
let latitude = (rng.gen_range(0, 180) - 90) * 10_000_000;
|
||||
let longitude = (rng.gen_range(0, 360) - 180) * 10_000_000;
|
||||
Point {
|
||||
latitude,
|
||||
longitude,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
use serde::Deserialize;
|
||||
use std::fs::File;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Feature {
|
||||
location: Location,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Location {
|
||||
latitude: i32,
|
||||
longitude: i32,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn load() -> Vec<crate::routeguide::Feature> {
|
||||
let file =
|
||||
File::open("tonic-examples/data/route_guide_db.json").expect("failed to open data file");
|
||||
|
||||
let decoded: Vec<Feature> =
|
||||
serde_json::from_reader(&file).expect("failed to deserialize features");
|
||||
|
||||
decoded
|
||||
.into_iter()
|
||||
.map(|feature| crate::routeguide::Feature {
|
||||
name: feature.name,
|
||||
location: Some(crate::routeguide::Point {
|
||||
longitude: feature.location.longitude,
|
||||
latitude: feature.location.latitude,
|
||||
}),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
mod data;
|
||||
|
||||
use futures::{Stream, StreamExt};
|
||||
use std::collections::HashMap;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::time::Instant;
|
||||
use tokio::sync::mpsc;
|
||||
use tonic::transport::Server;
|
||||
use tonic::{Request, Response, Status};
|
||||
|
||||
pub mod routeguide {
|
||||
tonic::include_proto!("routeguide");
|
||||
}
|
||||
|
||||
use routeguide::{routeguide_server, Feature, Point, Rectangle, RouteNote, RouteSummary};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RouteGuide {
|
||||
features: Arc<Vec<Feature>>,
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl routeguide_server::RouteGuide for RouteGuide {
|
||||
async fn get_feature(&self, request: Request<Point>) -> Result<Response<Feature>, Status> {
|
||||
println!("GetFeature = {:?}", request);
|
||||
|
||||
for feature in &self.features[..] {
|
||||
if feature.location.as_ref() == Some(request.get_ref()) {
|
||||
return Ok(Response::new(feature.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
let response = Response::new(Feature {
|
||||
name: "".to_string(),
|
||||
location: None,
|
||||
});
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
type ListFeaturesStream = mpsc::Receiver<Result<Feature, Status>>;
|
||||
|
||||
async fn list_features(
|
||||
&self,
|
||||
request: Request<Rectangle>,
|
||||
) -> Result<Response<Self::ListFeaturesStream>, Status> {
|
||||
println!("ListFeatures = {:?}", request);
|
||||
|
||||
let (mut tx, rx) = mpsc::channel(4);
|
||||
let features = self.features.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
for feature in &features[..] {
|
||||
if in_range(feature.location.as_ref().unwrap(), request.get_ref()) {
|
||||
println!(" => send {:?}", feature);
|
||||
tx.send(Ok(feature.clone())).await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
println!(" /// done sending");
|
||||
});
|
||||
|
||||
Ok(Response::new(rx))
|
||||
}
|
||||
|
||||
async fn record_route(
|
||||
&self,
|
||||
request: Request<tonic::Streaming<Point>>,
|
||||
) -> Result<Response<RouteSummary>, Status> {
|
||||
println!("RecordRoute");
|
||||
|
||||
let stream = request.into_inner();
|
||||
|
||||
// Pin the inbound stream to the stack so that we can call next on it
|
||||
futures::pin_mut!(stream);
|
||||
|
||||
let mut summary = RouteSummary::default();
|
||||
let mut last_point = None;
|
||||
let now = Instant::now();
|
||||
|
||||
while let Some(point) = stream.next().await {
|
||||
let point = point?;
|
||||
|
||||
println!(" ==> Point = {:?}", point);
|
||||
|
||||
// Increment the point count
|
||||
summary.point_count += 1;
|
||||
|
||||
// Find features
|
||||
for feature in &self.features[..] {
|
||||
if feature.location.as_ref() == Some(&point) {
|
||||
summary.feature_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the distance
|
||||
if let Some(ref last_point) = last_point {
|
||||
summary.distance += calc_distance(last_point, &point);
|
||||
}
|
||||
|
||||
last_point = Some(point);
|
||||
}
|
||||
|
||||
summary.elapsed_time = now.elapsed().as_secs() as i32;
|
||||
|
||||
Ok(Response::new(summary))
|
||||
}
|
||||
|
||||
type RouteChatStream =
|
||||
Pin<Box<dyn Stream<Item = Result<RouteNote, Status>> + Send + Sync + 'static>>;
|
||||
|
||||
async fn route_chat(
|
||||
&self,
|
||||
request: Request<tonic::Streaming<RouteNote>>,
|
||||
) -> Result<Response<Self::RouteChatStream>, Status> {
|
||||
println!("RouteChat");
|
||||
|
||||
let mut notes = HashMap::new();
|
||||
let stream = request.into_inner();
|
||||
|
||||
let output = async_stream::try_stream! {
|
||||
futures::pin_mut!(stream);
|
||||
|
||||
while let Some(note) = stream.next().await {
|
||||
let note = note?;
|
||||
|
||||
let location = note.location.clone().unwrap();
|
||||
|
||||
let location_notes = notes.entry(location).or_insert(vec![]);
|
||||
location_notes.push(note);
|
||||
|
||||
for note in location_notes {
|
||||
yield note.clone();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Response::new(Box::pin(output)
|
||||
as Pin<
|
||||
Box<dyn Stream<Item = Result<RouteNote, Status>> + Send + Sync + 'static>,
|
||||
>))
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let addr = "[::1]:10000".parse().unwrap();
|
||||
|
||||
println!("Listening on: {}", addr);
|
||||
|
||||
let route_guide = RouteGuide {
|
||||
features: Arc::new(data::load()),
|
||||
};
|
||||
|
||||
let svc = routeguide_server::RouteGuideServer::new(route_guide);
|
||||
|
||||
Server::builder().add_service(svc).serve(addr).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Implement hash for Point
|
||||
impl Hash for Point {
|
||||
fn hash<H>(&self, state: &mut H)
|
||||
where
|
||||
H: Hasher,
|
||||
{
|
||||
self.latitude.hash(state);
|
||||
self.longitude.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Point {}
|
||||
|
||||
fn in_range(point: &Point, rect: &Rectangle) -> bool {
|
||||
use std::cmp;
|
||||
|
||||
let lo = rect.lo.as_ref().unwrap();
|
||||
let hi = rect.hi.as_ref().unwrap();
|
||||
|
||||
let left = cmp::min(lo.longitude, hi.longitude);
|
||||
let right = cmp::max(lo.longitude, hi.longitude);
|
||||
let top = cmp::max(lo.latitude, hi.latitude);
|
||||
let bottom = cmp::min(lo.latitude, hi.latitude);
|
||||
|
||||
point.longitude >= left
|
||||
&& point.longitude <= right
|
||||
&& point.latitude >= bottom
|
||||
&& point.latitude <= top
|
||||
}
|
||||
|
||||
/// Calculates the distance between two points using the "haversine" formula.
|
||||
/// This code was taken from http://www.movable-type.co.uk/scripts/latlong.html.
|
||||
fn calc_distance(p1: &Point, p2: &Point) -> i32 {
|
||||
const CORD_FACTOR: f64 = 1e7;
|
||||
const R: f64 = 6371000.0; // meters
|
||||
|
||||
let lat1 = p1.latitude as f64 / CORD_FACTOR;
|
||||
let lat2 = p2.latitude as f64 / CORD_FACTOR;
|
||||
let lng1 = p1.longitude as f64 / CORD_FACTOR;
|
||||
let lng2 = p2.longitude as f64 / CORD_FACTOR;
|
||||
|
||||
let lat_rad1 = lat1.to_radians();
|
||||
let lat_rad2 = lat2.to_radians();
|
||||
|
||||
let delta_lat = (lat2 - lat1).to_radians();
|
||||
let delta_lng = (lng2 - lng1).to_radians();
|
||||
|
||||
let a = (delta_lat / 2f64).sin() * (delta_lat / 2f64).sin()
|
||||
+ (lat_rad1).cos() * (lat_rad2).cos() * (delta_lng / 2f64).sin() * (delta_lng / 2f64).sin();
|
||||
|
||||
let c = 2f64 * a.sqrt().atan2((1f64 - a).sqrt());
|
||||
|
||||
(R * c) as i32
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
pub mod pb {
|
||||
tonic::include_proto!("/grpc.examples.echo");
|
||||
}
|
||||
|
||||
use pb::{echo_client::EchoClient, EchoRequest};
|
||||
use tonic::transport::{Certificate, Channel, ClientTlsConfig};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let pem = tokio::fs::read("tonic-examples/data/tls/ca.pem").await?;
|
||||
let ca = Certificate::from_pem(pem);
|
||||
|
||||
let tls = ClientTlsConfig::with_rustls()
|
||||
.ca_certificate(ca)
|
||||
.domain_name("example.com");
|
||||
|
||||
let channel = Channel::from_static("http://[::1]:50051")
|
||||
.tls_config(tls)
|
||||
.connect()
|
||||
.await?;
|
||||
|
||||
let mut client = EchoClient::new(channel);
|
||||
let request = tonic::Request::new(EchoRequest {
|
||||
message: "hello".into(),
|
||||
});
|
||||
|
||||
let response = client.unary_echo(request).await?;
|
||||
|
||||
println!("RESPONSE={:?}", response);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
pub mod pb {
|
||||
tonic::include_proto!("/grpc.examples.echo");
|
||||
}
|
||||
|
||||
use futures::Stream;
|
||||
use pb::{EchoRequest, EchoResponse};
|
||||
use std::pin::Pin;
|
||||
use tonic::{
|
||||
transport::{Identity, Server, ServerTlsConfig},
|
||||
Request, Response, Status, Streaming,
|
||||
};
|
||||
|
||||
type EchoResult<T> = Result<Response<T>, Status>;
|
||||
type ResponseStream = Pin<Box<dyn Stream<Item = Result<EchoResponse, Status>> + Send + Sync>>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct EchoServer;
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl pb::echo_server::Echo for EchoServer {
|
||||
async fn unary_echo(&self, request: Request<EchoRequest>) -> EchoResult<EchoResponse> {
|
||||
let message = request.into_inner().message;
|
||||
Ok(Response::new(EchoResponse { message }))
|
||||
}
|
||||
|
||||
type ServerStreamingEchoStream = ResponseStream;
|
||||
|
||||
async fn server_streaming_echo(
|
||||
&self,
|
||||
_: Request<EchoRequest>,
|
||||
) -> EchoResult<Self::ServerStreamingEchoStream> {
|
||||
Err(Status::unimplemented("not implemented"))
|
||||
}
|
||||
|
||||
async fn client_streaming_echo(
|
||||
&self,
|
||||
_: Request<Streaming<EchoRequest>>,
|
||||
) -> EchoResult<EchoResponse> {
|
||||
Err(Status::unimplemented("not implemented"))
|
||||
}
|
||||
|
||||
type BidirectionalStreamingEchoStream = ResponseStream;
|
||||
|
||||
async fn bidirectional_streaming_echo(
|
||||
&self,
|
||||
_: Request<Streaming<EchoRequest>>,
|
||||
) -> EchoResult<Self::BidirectionalStreamingEchoStream> {
|
||||
Err(Status::unimplemented("not implemented"))
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cert = tokio::fs::read("tonic-examples/data/tls/server.pem").await?;
|
||||
let key = tokio::fs::read("tonic-examples/data/tls/server.key").await?;
|
||||
|
||||
let identity = Identity::from_pem(cert, key);
|
||||
|
||||
let addr = "[::1]:50051".parse().unwrap();
|
||||
let server = EchoServer::default();
|
||||
|
||||
Server::builder()
|
||||
.tls_config(ServerTlsConfig::with_rustls().identity(identity))
|
||||
.add_service(pb::echo_server::EchoServer::new(server))
|
||||
.serve(addr)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
pub mod pb {
|
||||
tonic::include_proto!("grpc.examples.echo");
|
||||
}
|
||||
|
||||
use pb::{echo_client::EchoClient, EchoRequest};
|
||||
use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let server_root_ca_cert = tokio::fs::read("tonic-examples/data/tls/ca.pem").await?;
|
||||
let server_root_ca_cert = Certificate::from_pem(server_root_ca_cert);
|
||||
let client_cert = tokio::fs::read("tonic-examples/data/tls/client1.pem").await?;
|
||||
let client_key = tokio::fs::read("tonic-examples/data/tls/client1.key").await?;
|
||||
let client_identity = Identity::from_pem(client_cert, client_key);
|
||||
|
||||
let tls = ClientTlsConfig::with_rustls()
|
||||
.domain_name("localhost")
|
||||
.ca_certificate(server_root_ca_cert)
|
||||
.identity(client_identity);
|
||||
|
||||
let channel = Channel::from_static("http://[::1]:50051")
|
||||
.tls_config(tls)
|
||||
.connect()
|
||||
.await?;
|
||||
|
||||
let mut client = EchoClient::new(channel);
|
||||
|
||||
let request = tonic::Request::new(EchoRequest {
|
||||
message: "hello".into(),
|
||||
});
|
||||
|
||||
let response = client.unary_echo(request).await?;
|
||||
|
||||
println!("RESPONSE={:?}", response);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
pub mod pb {
|
||||
tonic::include_proto!("grpc.examples.echo");
|
||||
}
|
||||
|
||||
use futures::Stream;
|
||||
use pb::{EchoRequest, EchoResponse};
|
||||
use std::pin::Pin;
|
||||
use tonic::transport::{Certificate, Identity, Server, ServerTlsConfig};
|
||||
use tonic::{Request, Response, Status};
|
||||
|
||||
type EchoResult<T> = Result<Response<T>, Status>;
|
||||
type ResponseStream = Pin<Box<dyn Stream<Item = Result<EchoResponse, Status>> + Send + Sync>>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct EchoServer;
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl pb::echo_server::Echo for EchoServer {
|
||||
async fn unary_echo(&self, request: Request<EchoRequest>) -> EchoResult<EchoResponse> {
|
||||
let message = request.into_inner().message;
|
||||
Ok(Response::new(EchoResponse { message }))
|
||||
}
|
||||
|
||||
type ServerStreamingEchoStream = ResponseStream;
|
||||
type BidirectionalStreamingEchoStream = ResponseStream;
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let cert = tokio::fs::read("tonic-examples/data/tls/server.pem").await?;
|
||||
let key = tokio::fs::read("tonic-examples/data/tls/server.key").await?;
|
||||
let server_identity = Identity::from_pem(cert, key);
|
||||
|
||||
let client_ca_cert = tokio::fs::read("tonic-examples/data/tls/client_ca.pem").await?;
|
||||
let client_ca_cert = Certificate::from_pem(client_ca_cert);
|
||||
|
||||
let addr = "[::1]:50051".parse().unwrap();
|
||||
let server = EchoServer::default();
|
||||
|
||||
let tls = ServerTlsConfig::with_rustls()
|
||||
.identity(server_identity)
|
||||
.client_ca_root(client_ca_cert);
|
||||
|
||||
Server::builder()
|
||||
.tls_config(tls)
|
||||
.add_service(pb::echo_server::EchoServer::new(server))
|
||||
.serve(addr)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
pub mod hello_world {
|
||||
tonic::include_proto!("helloworld");
|
||||
}
|
||||
|
||||
use hello_world::{greeter_client::GreeterClient, HelloRequest};
|
||||
use tracing_attributes::instrument;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::FmtSubscriber::builder()
|
||||
.with_max_level(tracing::Level::DEBUG)
|
||||
.init();
|
||||
|
||||
say_hi("Bob".into()).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
async fn say_hi(name: String) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut client = GreeterClient::connect("http://[::1]:50051").await?;
|
||||
|
||||
let request = tonic::Request::new(HelloRequest { name });
|
||||
|
||||
tracing::info!(
|
||||
message = "Sending request.",
|
||||
request = %request.get_ref().name
|
||||
);
|
||||
|
||||
let response = client.say_hello(request).await?;
|
||||
|
||||
tracing::info!(
|
||||
message = "Got a response.",
|
||||
response = %response.get_ref().message
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
use tonic::{transport::Server, Request, Response, Status};
|
||||
|
||||
pub mod hello_world {
|
||||
tonic::include_proto!("helloworld");
|
||||
}
|
||||
|
||||
use hello_world::{
|
||||
greeter_server::{Greeter, GreeterServer},
|
||||
HelloReply, HelloRequest,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MyGreeter {}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl Greeter for MyGreeter {
|
||||
async fn say_hello(
|
||||
&self,
|
||||
request: Request<HelloRequest>,
|
||||
) -> Result<Response<HelloReply>, Status> {
|
||||
tracing::info!(message = "Inbound request.", metadata = ?request.metadata());
|
||||
|
||||
let reply = hello_world::HelloReply {
|
||||
message: format!("Hello {}!", request.into_inner().name).into(),
|
||||
};
|
||||
|
||||
tracing::debug!(message = "Sending reply.", response = %reply.message);
|
||||
|
||||
Ok(Response::new(reply))
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tracing_subscriber::FmtSubscriber::builder()
|
||||
.with_max_level(tracing::Level::DEBUG)
|
||||
.init();
|
||||
|
||||
let addr = "[::1]:50051".parse().unwrap();
|
||||
let greeter = MyGreeter::default();
|
||||
|
||||
tracing::info!(message = "Starting server.", %addr);
|
||||
|
||||
Server::builder()
|
||||
.trace_fn(|_| tracing::info_span!("helloworld_server"))
|
||||
.add_service(GreeterServer::new(greeter))
|
||||
.serve(addr)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user