├── .editorconfig ├── .github ├── linters │ ├── .ecrc │ └── .markdown-lint.yml └── workflows │ └── ci.yml ├── .gitignore ├── .rustfmt.toml ├── .travis.yml-disabled ├── CHANGELOG.md ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── ci-gen ├── Cargo.toml ├── README.md └── src │ └── main.rs ├── ci ├── install-protobuf.sh └── run.sh ├── docs └── FAQ.md ├── grpc-compiler ├── Cargo.toml ├── README.md ├── src │ ├── bin │ │ └── protoc-gen-rust-grpc.rs │ ├── codegen.rs │ └── lib.rs └── test-protoc-plugin │ ├── Cargo.toml │ ├── fgfg.proto │ ├── gen.sh │ └── src │ ├── .gitignore │ └── lib.rs ├── grpc-examples ├── greeter │ ├── Cargo.toml │ ├── README.md │ ├── build.rs │ ├── get-go-greeter.sh │ ├── helloworld.proto │ └── src │ │ ├── .gitignore │ │ ├── bin │ │ ├── greeter_client.rs │ │ └── greeter_server.rs │ │ ├── foobar.com.p12 │ │ ├── gen-certs.sh │ │ ├── lib.rs │ │ └── root-ca.der └── route_guide │ ├── Cargo.toml │ ├── build.rs │ ├── get-go-route-guide.sh │ ├── route_guide.proto │ ├── src │ ├── .gitignore │ ├── bin │ │ ├── route_guide_client.rs │ │ └── route_guide_server.rs │ ├── client.rs │ ├── lib.rs │ ├── server.rs │ └── test.rs │ └── testdata │ └── route_guide_db.json ├── grpc-protobuf ├── Cargo.toml └── src │ └── lib.rs ├── grpc ├── Cargo.toml ├── src │ ├── assert_types.rs │ ├── bytesx │ │ ├── buf_eq.rs │ │ ├── buf_vec_deque.rs │ │ ├── bytes_deque.rs │ │ ├── bytes_vec_deque.rs │ │ ├── iter_buf.rs │ │ └── mod.rs │ ├── chars.rs │ ├── client │ │ ├── http_request_to_grpc_frames_typed.rs │ │ ├── http_response_to_grpc_frames.rs │ │ ├── http_response_to_grpc_frames_typed.rs │ │ ├── mod.rs │ │ ├── req_sink.rs │ │ └── types.rs │ ├── client_stub.rs │ ├── common │ │ ├── mod.rs │ │ ├── sink.rs │ │ └── types.rs │ ├── error.rs │ ├── for_test.rs │ ├── futures_grpc.rs │ ├── lib.rs │ ├── marshall.rs │ ├── method.rs │ ├── misc.rs │ ├── or_static │ │ ├── arc.rs │ │ ├── mod.rs │ │ └── string.rs │ ├── prelude.rs │ ├── proto │ │ ├── grpc_frame.rs │ │ ├── grpc_frame_parser.rs │ │ ├── grpc_status.rs │ │ ├── headers.rs │ │ ├── metadata.rs │ │ └── mod.rs │ ├── req.rs │ ├── resp.rs │ ├── result.rs │ ├── rt.rs │ ├── server │ │ ├── method.rs │ │ ├── mod.rs │ │ ├── req_handler.rs │ │ ├── req_handler_unary.rs │ │ ├── req_single.rs │ │ ├── req_stream.rs │ │ ├── resp_sink.rs │ │ ├── resp_sink_untyped.rs │ │ ├── resp_unary_sink.rs │ │ └── types.rs │ └── stream_item.rs └── tests │ ├── client.rs │ ├── server.rs │ ├── simple.rs │ └── test_misc │ ├── mod.rs │ ├── stream_thread_spawn_iter.rs │ └── test_sync.rs ├── interop ├── .gitignore ├── Cargo.toml ├── README.md ├── build.rs ├── client-test.sh ├── get-go-interop.sh ├── proto │ ├── empty.proto │ ├── messages.proto │ └── test.proto ├── server-test.sh ├── src │ ├── .gitignore │ ├── bin │ │ ├── interop_client.rs │ │ └── interop_server.rs │ ├── interop_client.rs │ └── lib.rs └── testdata │ ├── server1.key │ └── server1.pem ├── long-tests ├── README.md ├── long_tests_pb.proto ├── run-test-helper.sh ├── with-go │ ├── .editorconfig │ ├── gen-go.sh │ ├── long_tests_client │ │ └── long_tests_client.go │ ├── long_tests_pb │ │ └── long_tests_pb.pb.go │ └── long_tests_server │ │ └── long_tests_server.go └── with-rust │ ├── Cargo.toml │ ├── build-rs.sh │ ├── build.rs │ └── src │ ├── bin │ ├── long_tests_client.rs │ └── long_tests_server.rs │ └── lib.rs └── protoc-rust-grpc ├── Cargo.toml ├── README.md └── src └── lib.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = lf 3 | insert_final_newline = true 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | -------------------------------------------------------------------------------- /.github/linters/.ecrc: -------------------------------------------------------------------------------- 1 | { 2 | "Exclude" : ["expected"], 3 | "Disable": { 4 | "IndentSize": true, 5 | "Indentation": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.github/linters/.markdown-lint.yml: -------------------------------------------------------------------------------- 1 | { 2 | # Heading style 3 | "MD003": false, 4 | # Line length 5 | "MD013": false, 6 | # Headings should be surrounded by blank lines 7 | "MD022": false, 8 | # Multiple headings with the same content 9 | "MD024": false, 10 | # Lists should be surrounded by blank lines 11 | "MD032": false, 12 | # First line in file should be a top level heading 13 | "MD041": false 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # @generated by gh-actions-gen, do not edit 2 | 3 | --- 4 | "on": 5 | - push 6 | - pull_request 7 | name: Jobs 8 | jobs: 9 | linux-stable: 10 | name: linux stable 11 | runs-on: ubuntu-latest 12 | env: 13 | PROTOBUF_VERSION: 3.1.0 14 | RUST_BACKTRACE: "1" 15 | steps: 16 | - name: Checkout sources 17 | uses: actions/checkout@v2 18 | - name: Install toolchain 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | profile: minimal 22 | toolchain: stable 23 | override: true 24 | - name: install protobuf 25 | run: "./ci/install-protobuf.sh" 26 | shell: bash 27 | - name: run 28 | run: ci/run.sh 29 | shell: bash 30 | macos-stable: 31 | name: macos stable 32 | runs-on: macos-latest 33 | env: 34 | PROTOBUF_VERSION: 3.1.0 35 | RUST_BACKTRACE: "1" 36 | steps: 37 | - name: Checkout sources 38 | uses: actions/checkout@v2 39 | - name: Install toolchain 40 | uses: actions-rs/toolchain@v1 41 | with: 42 | profile: minimal 43 | toolchain: stable 44 | override: true 45 | - name: install protobuf 46 | run: "./ci/install-protobuf.sh" 47 | shell: bash 48 | - name: run 49 | run: ci/run.sh 50 | shell: bash 51 | linux-beta: 52 | name: linux beta 53 | runs-on: ubuntu-latest 54 | env: 55 | PROTOBUF_VERSION: 3.1.0 56 | RUST_BACKTRACE: "1" 57 | steps: 58 | - name: Checkout sources 59 | uses: actions/checkout@v2 60 | - name: Install toolchain 61 | uses: actions-rs/toolchain@v1 62 | with: 63 | profile: minimal 64 | toolchain: beta 65 | override: true 66 | - name: install protobuf 67 | run: "./ci/install-protobuf.sh" 68 | shell: bash 69 | - name: run 70 | run: ci/run.sh 71 | shell: bash 72 | linux-nightly: 73 | name: linux nightly 74 | runs-on: ubuntu-latest 75 | env: 76 | PROTOBUF_VERSION: 3.1.0 77 | RUST_BACKTRACE: "1" 78 | steps: 79 | - name: Checkout sources 80 | uses: actions/checkout@v2 81 | - name: Install toolchain 82 | uses: actions-rs/toolchain@v1 83 | with: 84 | profile: minimal 85 | toolchain: nightly 86 | override: true 87 | - name: install protobuf 88 | run: "./ci/install-protobuf.sh" 89 | shell: bash 90 | - name: run 91 | run: ci/run.sh 92 | shell: bash 93 | macos-nightly: 94 | name: macos nightly 95 | runs-on: macos-latest 96 | env: 97 | PROTOBUF_VERSION: 3.1.0 98 | RUST_BACKTRACE: "1" 99 | steps: 100 | - name: Checkout sources 101 | uses: actions/checkout@v2 102 | - name: Install toolchain 103 | uses: actions-rs/toolchain@v1 104 | with: 105 | profile: minimal 106 | toolchain: nightly 107 | override: true 108 | - name: install protobuf 109 | run: "./ci/install-protobuf.sh" 110 | shell: bash 111 | - name: run 112 | run: ci/run.sh 113 | shell: bash 114 | test-protoc-plugin: 115 | name: test-protoc-plugin 116 | runs-on: ubuntu-latest 117 | env: 118 | PROTOBUF_VERSION: 3.1.0 119 | RUST_BACKTRACE: "1" 120 | steps: 121 | - name: cargo cache 122 | uses: actions/cache@v2 123 | with: 124 | path: "~/.cargo/registry\n~/.cargo/git\n" 125 | key: "${{ runner.os }}-cargo-2" 126 | - name: Checkout sources 127 | uses: actions/checkout@v2 128 | - name: Install toolchain 129 | uses: actions-rs/toolchain@v1 130 | with: 131 | profile: minimal 132 | toolchain: stable 133 | override: true 134 | - name: install protobuf 135 | run: "./ci/install-protobuf.sh" 136 | shell: bash 137 | - name: install protobuf-codegen 138 | run: cargo install protobuf-codegen --version=2.23.0 139 | shell: bash 140 | - name: gen 141 | run: grpc-compiler/test-protoc-plugin/gen.sh 142 | shell: bash 143 | - name: check 144 | run: cargo check --manifest-path grpc-compiler/test-protoc-plugin/Cargo.toml 145 | shell: bash 146 | cargo-doc: 147 | name: cargo doc 148 | runs-on: ubuntu-latest 149 | env: 150 | PROTOBUF_VERSION: 3.1.0 151 | RUST_BACKTRACE: "1" 152 | steps: 153 | - name: Checkout sources 154 | uses: actions/checkout@v2 155 | - name: install protobuf 156 | run: "./ci/install-protobuf.sh" 157 | shell: bash 158 | - name: Install toolchain 159 | uses: actions-rs/toolchain@v1 160 | with: 161 | profile: minimal 162 | toolchain: stable 163 | override: true 164 | - name: cargo doc 165 | uses: actions-rs/cargo@v1 166 | with: 167 | command: doc 168 | super-linter: 169 | name: super-linter 170 | runs-on: ubuntu-latest 171 | steps: 172 | - name: Checkout sources 173 | uses: actions/checkout@v2 174 | with: 175 | fetch-depth: 0 176 | - name: super-linter 177 | uses: github/super-linter@v3 178 | env: 179 | VALIDATE_ALL_CODEBASE: "false" 180 | DEFAULT_BRANCH: master 181 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 182 | VALIDATE_JSCPD: "false" 183 | VALIDATE_PROTOBUF: "false" 184 | VALIDATE_RUST_2015: "false" 185 | VALIDATE_RUST_2018: "false" 186 | VALIDATE_RUST_CLIPPY: "false" 187 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | *.iml 4 | .idea 5 | .project 6 | .*.swp 7 | long-tests/with-go/long_tests_client/long_tests_client 8 | long-tests/with-go/long_tests_server/long_tests_server 9 | -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | imports_granularity = "Item" 2 | -------------------------------------------------------------------------------- /.travis.yml-disabled: -------------------------------------------------------------------------------- 1 | sudo: false 2 | dist: trusty 3 | 4 | language: rust 5 | 6 | matrix: 7 | include: 8 | - name: Stable 9 | rust: stable 10 | - name: Beta 11 | rust: beta 12 | - name: Nightly 13 | rust: nightly 14 | - name: test-protoc-plugin 15 | rust: stable 16 | env: ACTION=test-protoc-plugin 17 | 18 | script: 19 | - ci/run.sh 20 | 21 | notifications: 22 | email: 23 | on_success: never 24 | 25 | # vim: set ts=2 sw=2 et: 26 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [Unreleased] 2 | 3 | ## [0.8.3] - 2021-03-29 4 | 5 | - Pin protobuf version to 2.18.2 to fix compilation: incompatibility of bytes crate 6 | 7 | ## [0.8.1] - 2020-05-26 8 | 9 | - Fix compilation on Windows 10 | 11 | ## [0.8.0] - 2020-05-17 12 | 13 | - Upgrade httpbis, tls-api, rustls dependencies 14 | 15 | ## [0.7.0] 16 | 17 | - Upgrade to futures 0.3 18 | 19 | ## [0.6.2] - 2020-01-14 20 | 21 | - Pinned rust-protobuf to 2.8 and bytes to 0.4 (because bytes 0.5 is incompatible with bytes 0.4). 22 | 23 | ## [0.6.1] 24 | 25 | ## [0.6.0] 26 | 27 | ## [0.5.0] - 2018-06-16 28 | 29 | - Update rust-protobuf to version 2 30 | - Update httpbis to version 0.7 31 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "grpc", 4 | "grpc-protobuf", 5 | "grpc-examples/greeter", 6 | "grpc-examples/route_guide", 7 | "grpc-compiler", 8 | "long-tests/with-rust", 9 | "interop", 10 | "protoc-rust-grpc", 11 | "ci-gen", 12 | ] 13 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Stepan Koltsov 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | grpc-rust 2 | ========= 3 | 4 | # The project is dead; long live the new project 5 | 6 | gRPC team is [developing shiny new gRPC implementation in Rust](https://groups.google.com/g/grpc-io/c/ExbWWLaGHjI/m/TJssglLiBgAJ), 7 | `grpc` crate name is transferred to them. 8 | 9 | This code is no longer maintained. 10 | 11 | # Original README.md 12 | 13 | 14 | [![Build Status](https://img.shields.io/travis/stepancheg/grpc-rust.svg)](https://travis-ci.org/stepancheg/grpc-rust) 15 | [![License](https://img.shields.io/crates/l/grpc.svg)](https://github.com/stepancheg/grpc-rust/blob/master/LICENSE.txt) 16 | [![crates.io](https://img.shields.io/crates/v/grpc.svg)](https://crates.io/crates/grpc) 17 | 18 | Rust implementation of [gRPC](http://www.grpc.io/) protocol, under development. 19 | 20 | Some development questions in [FAQ](/docs/FAQ.md). 21 | 22 | ## Current status 23 | 24 | It basically works, but not suitable for production use. 25 | 26 | See `grpc-examples/src/bin/greeter_{client,server}.rs`. It can be tested 27 | for example with [go client](https://github.com/grpc/grpc-go/tree/master/examples/helloworld): 28 | 29 | ```shell 30 | # start greeter server implemented in rust 31 | $ cargo run --bin greeter_server 32 | 33 | # ... or start greeter server implemented in go 34 | $ go get -u google.golang.org/grpc/examples/helloworld/greeter_client 35 | $ greeter_server 36 | 37 | # start greeter client implemented in rust 38 | $ cargo run --bin greeter_client rust 39 | > message: "Hello rust" 40 | 41 | # ... or start greeter client implemented in go 42 | $ go get -u google.golang.org/grpc/examples/helloworld/greeter_client 43 | $ greeter_client rust 44 | > 2016/08/19 05:44:45 Greeting: Hello rust 45 | ``` 46 | 47 | ## Route guide 48 | 49 | [Route guide](https://github.com/grpc/grpc-go/tree/master/examples/route_guide) 50 | example implementation in grpc-rust is in 51 | [grpc-examples folder](https://github.com/stepancheg/grpc-rust/tree/master/grpc-examples/route_guide). 52 | 53 | ## How to generate rust code 54 | 55 | There are two ways to generate rust code from .proto files 56 | 57 | ### Invoke protoc programmatically with `protoc-rust-grpc` crate 58 | 59 | (Recommended) 60 | 61 | Have a look at readme in 62 | [protoc-rust-grpc crate](https://github.com/stepancheg/grpc-rust/tree/master/protoc-rust-grpc). 63 | 64 | ### With `protoc` command and `protoc-gen-rust-grpc` plugin 65 | 66 | [Readme](https://github.com/stepancheg/grpc-rust/tree/master/grpc-compiler) 67 | 68 | ### Use generated protos in your project 69 | 70 | In Cargo.toml: 71 | 72 | ```ini 73 | [dependencies] 74 | grpc = "~0.8" 75 | protobuf = "2.23" 76 | futures = "~0.3" 77 | 78 | [build-dependencies] 79 | protoc-rust-grpc = "~0.8" 80 | ``` 81 | 82 | ## TODO 83 | 84 | * Fix performance 85 | * More tests 86 | * In particular, add more compatibility tests, they live in `interop` directory 87 | * Fix all TODO in sources 88 | 89 | ## Related projects 90 | 91 | * [grpc-rs](https://github.com/pingcap/grpc-rs) — alternative implementation of gRPC in Rust, 92 | a wrapper to C++ implementation 93 | * [httpbis](https://github.com/stepancheg/rust-http2) — implementation of HTTP/2, 94 | which is used by this implementation of gRPC 95 | * [rust-protobuf](https://github.com/stepancheg/rust-protobuf/) — implementation of Google Protocol Buffers 96 | -------------------------------------------------------------------------------- /ci-gen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ci-gen" 3 | version = "0.0.0" 4 | authors = ["Stiopa Koltsov "] 5 | edition = "2018" 6 | publish = false 7 | 8 | [dependencies] 9 | gh-actions-gen = { git = "https://github.com/stepancheg/gh-actions-gen", rev = "d98061bc72c91bca5744392c91d5e59638f7f48c" } 10 | #gh-actions-gen = { path = "../../gh-actions-gen/gh-actions-gen" } 11 | -------------------------------------------------------------------------------- /ci-gen/README.md: -------------------------------------------------------------------------------- 1 | # ci-gen 2 | 3 | Generates `.github/workflow` for this repository. 4 | -------------------------------------------------------------------------------- /ci-gen/src/main.rs: -------------------------------------------------------------------------------- 1 | use gh_actions_gen::actions::cargo_cache; 2 | use gh_actions_gen::actions::cargo_doc; 3 | use gh_actions_gen::actions::checkout_sources; 4 | use gh_actions_gen::actions::rust_install_toolchain; 5 | use gh_actions_gen::actions::RustToolchain; 6 | use gh_actions_gen::ghwf::Env; 7 | use gh_actions_gen::ghwf::Job; 8 | use gh_actions_gen::ghwf::Step; 9 | use gh_actions_gen::super_linter::super_linter_job; 10 | 11 | fn steps(_os: Os, channel: RustToolchain) -> Vec { 12 | let mut steps = Vec::new(); 13 | steps.push(checkout_sources()); 14 | steps.push(rust_install_toolchain(channel)); 15 | steps.push(install_protobuf_step()); 16 | steps.push(Step::run("run", "ci/run.sh")); 17 | steps 18 | } 19 | 20 | #[derive(PartialEq, Eq, Copy, Clone)] 21 | struct Os { 22 | name: &'static str, 23 | ghwf: Env, 24 | } 25 | 26 | const LINUX: Os = Os { 27 | name: "linux", 28 | ghwf: Env::UbuntuLatest, 29 | }; 30 | const MACOS: Os = Os { 31 | name: "macos", 32 | ghwf: Env::MacosLatest, 33 | }; 34 | const _WINDOWS: Os = Os { 35 | name: "windows", 36 | ghwf: Env::WindowsLatest, 37 | }; 38 | 39 | fn install_protobuf_step() -> Step { 40 | Step::run("install protobuf", "./ci/install-protobuf.sh") 41 | } 42 | 43 | fn protobuf_version_env() -> (String, String) { 44 | ("PROTOBUF_VERSION".to_owned(), "3.1.0".to_owned()) 45 | } 46 | 47 | fn rust_backtrace_env() -> (String, String) { 48 | ("RUST_BACKTRACE".to_owned(), "1".to_owned()) 49 | } 50 | 51 | fn cargo_doc_job() -> Job { 52 | let os = LINUX; 53 | let mut steps = Vec::new(); 54 | steps.push(checkout_sources()); 55 | steps.push(install_protobuf_step()); 56 | steps.push(rust_install_toolchain(RustToolchain::Stable)); 57 | steps.push(cargo_doc("cargo doc", "")); 58 | Job { 59 | id: "cargo-doc".to_owned(), 60 | name: "cargo doc".to_owned(), 61 | runs_on: os.ghwf, 62 | env: vec![protobuf_version_env(), rust_backtrace_env()], 63 | steps, 64 | ..Default::default() 65 | } 66 | } 67 | 68 | fn test_protoc_plugin_job() -> Job { 69 | let mut steps = Vec::new(); 70 | steps.push(cargo_cache()); 71 | steps.push(checkout_sources()); 72 | steps.push(rust_install_toolchain(RustToolchain::Stable)); 73 | steps.push(install_protobuf_step()); 74 | steps.push(Step::run( 75 | "install protobuf-codegen", 76 | "cargo install protobuf-codegen --version=2.23.0", 77 | )); 78 | steps.push(Step::run("gen", "grpc-compiler/test-protoc-plugin/gen.sh")); 79 | steps.push(Step::run( 80 | "check", 81 | "cargo check --manifest-path grpc-compiler/test-protoc-plugin/Cargo.toml", 82 | )); 83 | Job { 84 | id: "test-protoc-plugin".to_owned(), 85 | name: "test-protoc-plugin".to_owned(), 86 | env: vec![protobuf_version_env(), rust_backtrace_env()], 87 | steps, 88 | ..Default::default() 89 | } 90 | } 91 | 92 | fn jobs() -> Vec { 93 | let mut r = Vec::new(); 94 | for &channel in &[ 95 | RustToolchain::Stable, 96 | RustToolchain::Beta, 97 | RustToolchain::Nightly, 98 | ] { 99 | for &os in &[LINUX, MACOS] { 100 | if channel == RustToolchain::Beta && os == MACOS { 101 | // skip some jobs because macos is expensive 102 | continue; 103 | } 104 | r.push(Job { 105 | id: format!("{}-{}", os.name, channel), 106 | name: format!("{} {}", os.name, channel), 107 | runs_on: os.ghwf, 108 | env: vec![protobuf_version_env(), rust_backtrace_env()], 109 | steps: steps(os, channel), 110 | ..Default::default() 111 | }); 112 | } 113 | } 114 | 115 | r.push(test_protoc_plugin_job()); 116 | 117 | r.push(cargo_doc_job()); 118 | 119 | // TODO: enable 120 | //r.push(rustfmt_check_job()); 121 | 122 | r.push(super_linter_job()); 123 | 124 | r 125 | } 126 | 127 | fn main() { 128 | gh_actions_gen::write(jobs()); 129 | } 130 | -------------------------------------------------------------------------------- /ci/install-protobuf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | die() { 6 | echo "$@" >&2 7 | exit 1 8 | } 9 | 10 | test -n "$PROTOBUF_VERSION" || die "PROTOBUF_VERSION env var is undefined" 11 | 12 | case "$PROTOBUF_VERSION" in 13 | 3*) 14 | basename="protobuf-cpp-$PROTOBUF_VERSION" 15 | ;; 16 | *) 17 | die "unknown protobuf version: $PROTOBUF_VERSION" 18 | ;; 19 | esac 20 | 21 | curl -sL "https://github.com/google/protobuf/releases/download/v$PROTOBUF_VERSION/$basename.tar.gz" | tar zx 22 | 23 | cd "protobuf-$PROTOBUF_VERSION" 24 | 25 | ./configure --prefix="$HOME" && make -j2 && make install 26 | 27 | test -n "$GITHUB_PATH" 28 | echo "$HOME/bin" >>"$GITHUB_PATH" 29 | -------------------------------------------------------------------------------- /ci/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | set -ex 4 | 5 | rustc --version 6 | 7 | export RUST_BACKTRACE=1 8 | 9 | cargo test --all --all-targets 10 | 11 | # `--all-targets` does not include doctests 12 | # https://github.com/rust-lang/cargo/issues/6669 13 | cargo test --doc 14 | 15 | # Check the docs 16 | cargo doc 17 | 18 | # vim: set ts=4 sw=4 et: 19 | -------------------------------------------------------------------------------- /docs/FAQ.md: -------------------------------------------------------------------------------- 1 | # grpc-rust FAQ 2 | 3 | ## Q: I get an error when I try to make mutable service 4 | 5 | Parameters in service are immutable intentionally because operations can be executed concurrently, so `self` should be protected against access from multiple threads. For example, mutable value can be wrapped in `Arc>`. 6 | 7 | ```rust 8 | ... 9 | 10 | use std::sync::{Arc, Mutex}; 11 | 12 | struct CounterServiceImpl { 13 | requests: Arc>, 14 | } 15 | 16 | impl CounterServiceImpl { 17 | fn new() -> CounterServiceImpl { 18 | CounterServiceImpl { requests: Arc::new(Mutex::new(0)) } 19 | } 20 | } 21 | 22 | impl CounterService for CounterServiceImpl { 23 | fn Count(&self, req: Request) -> grpc::Result { 24 | println!("Request url: {}", req.get_url()); 25 | *self.requests.lock().unwrap() += 1; 26 | Ok(Reply::new()) 27 | } 28 | } 29 | ``` 30 | -------------------------------------------------------------------------------- /grpc-compiler/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "grpc-compiler" 3 | version = "0.9.0-pre" 4 | description = "gRPC compiler for rust-grpc" 5 | license = "MIT/Apache-2.0" 6 | authors = ["Stepan Koltsov "] 7 | edition = "2018" 8 | 9 | [dependencies] 10 | protobuf = "2.23" 11 | protobuf-codegen = "2.23" 12 | 13 | [[bin]] 14 | 15 | name = "protoc-gen-rust-grpc" 16 | test = false 17 | 18 | [lib] 19 | 20 | doctest = false 21 | -------------------------------------------------------------------------------- /grpc-compiler/README.md: -------------------------------------------------------------------------------- 1 | # grpc compiler 2 | 3 | Stub generator for grpc. 4 | 5 | Library and `protoc-gen-rust-grpc` plugin for `protoc`. 6 | 7 | Can be installed with command `cargo install grpc-compiler`. 8 | 9 | Generate .rs files: 10 | 11 | ``` 12 | protoc --rust-grpc_out=. foo.proto 13 | ``` 14 | 15 | Generate .rs files (protobuf messages and grpc stubs): 16 | 17 | ``` 18 | protoc --rust_out=. --rust-grpc_out=. foo.proto 19 | ``` 20 | 21 | Alternatively, [protoc-grpc-rust](https://github.com/stepancheg/grpc-rust/tree/master/protoc-rust-grpc) 22 | crate can be used to invoke codegen programmatically, which only requires `protoc` command in `$PATH`. 23 | -------------------------------------------------------------------------------- /grpc-compiler/src/bin/protoc-gen-rust-grpc.rs: -------------------------------------------------------------------------------- 1 | use grpc_compiler::codegen; 2 | 3 | fn main() { 4 | codegen::protoc_gen_grpc_rust_main(); 5 | } 6 | -------------------------------------------------------------------------------- /grpc-compiler/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(broken_intra_doc_links)] 2 | 3 | pub mod codegen; 4 | -------------------------------------------------------------------------------- /grpc-compiler/test-protoc-plugin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-protoc-plugin" 3 | version = "0.0.0" 4 | authors = ["Stepan Koltsov "] 5 | publish = false 6 | edition = "2018" 7 | 8 | [dependencies] 9 | tls-api = "0.5.0" 10 | protobuf = "2.23" 11 | grpc = { path = "../../grpc" } 12 | grpc-protobuf = { path = "../../grpc-protobuf" } 13 | 14 | [lib] 15 | test = false 16 | doctest = false 17 | 18 | [workspace] 19 | -------------------------------------------------------------------------------- /grpc-compiler/test-protoc-plugin/fgfg.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message FooBar { 4 | 5 | } 6 | 7 | service Baz { 8 | rpc qux(FooBar) returns (FooBar) {} 9 | } 10 | -------------------------------------------------------------------------------- /grpc-compiler/test-protoc-plugin/gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | cd $(dirname $0) 4 | 5 | ( 6 | cd .. 7 | cargo build 8 | ) 9 | 10 | root=$(cd ../..; pwd) 11 | 12 | PATH="$root/target/debug:$PATH" 13 | 14 | protoc --rust_out=src fgfg.proto 15 | protoc --rust-grpc_out=src fgfg.proto 16 | -------------------------------------------------------------------------------- /grpc-compiler/test-protoc-plugin/src/.gitignore: -------------------------------------------------------------------------------- 1 | fgfg.rs 2 | fgfg_grpc.rs 3 | -------------------------------------------------------------------------------- /grpc-compiler/test-protoc-plugin/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod fgfg; 2 | mod fgfg_grpc; 3 | -------------------------------------------------------------------------------- /grpc-examples/greeter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "grpc_examples_greeter" 3 | version = "0.0.0" 4 | authors = ["Stepan Koltsov "] 5 | publish = false 6 | edition = "2018" 7 | 8 | [lib] 9 | doctest = false 10 | test = false 11 | 12 | [dependencies.grpc] 13 | path = "../../grpc" 14 | [dependencies.grpc-protobuf] 15 | path = "../../grpc-protobuf" 16 | 17 | [dependencies] 18 | protobuf = "2.23" 19 | futures = "0.3.*" 20 | tls-api = "0.6.0" 21 | tls-api-stub = "0.6.0" 22 | tls-api-native-tls = "0.6.0" 23 | #httpbis = "~0.9" 24 | httpbis = { git = "https://github.com/stepancheg/rust-http2", rev = "04410b1a07f937d94e075c813d05a68022f0c7c8" } 25 | #httpbis = { path = "../../../rust-http2/httpbis" } 26 | env_logger = "~0.9" 27 | 28 | [build-dependencies] 29 | protoc-rust-grpc = { path = "../../protoc-rust-grpc" } 30 | 31 | [[bin]] 32 | name = "greeter_client" 33 | test = false 34 | 35 | [[bin]] 36 | name = "greeter_server" 37 | test = false 38 | -------------------------------------------------------------------------------- /grpc-examples/greeter/README.md: -------------------------------------------------------------------------------- 1 | # Test against go client 2 | 3 | ``` 4 | # start greeter server implemented in rust 5 | $ cargo run --bin greeter_server 6 | 7 | # ... or start greeter server implemented in go 8 | $ go get -u google.golang.org/grpc/examples/helloworld/greeter_client 9 | $ greeter_server 10 | 11 | # start greeter client implemented in rust 12 | $ cargo run --bin greeter_client rust 13 | > message: "Hello rust" 14 | 15 | # ... or start greeter client implemented in go 16 | $ go get -u google.golang.org/grpc/examples/helloworld/greeter_client 17 | $ greeter_client rust 18 | > 2016/08/19 05:44:45 Greeting: Hello rust 19 | ``` 20 | 21 | # Test TLS 22 | 23 | Rust client and server can talk via TLS. 24 | 25 | ``` 26 | $ cargo run --bin greeter_server -- --tls 27 | 28 | $ cargo run --bin greeter_client -- --tls 29 | ``` 30 | -------------------------------------------------------------------------------- /grpc-examples/greeter/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | protoc_rust_grpc::Codegen::new() 3 | .out_dir("src") 4 | .input("helloworld.proto") 5 | .rust_protobuf(true) 6 | .run() 7 | .expect("protoc-rust-grpc"); 8 | } 9 | -------------------------------------------------------------------------------- /grpc-examples/greeter/get-go-greeter.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ex 2 | 3 | go get -u google.golang.org/grpc/examples/helloworld/greeter_client 4 | go get -u google.golang.org/grpc/examples/helloworld/greeter_server 5 | 6 | # vim: set ts=4 sw=4 et: 7 | -------------------------------------------------------------------------------- /grpc-examples/greeter/helloworld.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015, Google Inc. 2 | // All rights reserved. 3 | // 4 | // Redistribution and use in source and binary forms, with or without 5 | // modification, are permitted provided that the following conditions are 6 | // met: 7 | // 8 | // * Redistributions of source code must retain the above copyright 9 | // notice, this list of conditions and the following disclaimer. 10 | // * Redistributions in binary form must reproduce the above 11 | // copyright notice, this list of conditions and the following disclaimer 12 | // in the documentation and/or other materials provided with the 13 | // distribution. 14 | // * Neither the name of Google Inc. nor the names of its 15 | // contributors may be used to endorse or promote products derived from 16 | // this software without specific prior written permission. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | syntax = "proto3"; 31 | 32 | option java_multiple_files = true; 33 | option java_package = "io.grpc.examples.helloworld"; 34 | option java_outer_classname = "HelloWorldProto"; 35 | option objc_class_prefix = "HLW"; 36 | 37 | package helloworld; 38 | 39 | // The greeting service definition. 40 | service Greeter { 41 | // Sends a greeting 42 | rpc SayHello (HelloRequest) returns (HelloReply) {} 43 | } 44 | 45 | // The request message containing the user's name. 46 | message HelloRequest { 47 | string name = 1; 48 | } 49 | 50 | // The response message containing the greetings 51 | message HelloReply { 52 | string message = 1; 53 | } 54 | -------------------------------------------------------------------------------- /grpc-examples/greeter/src/.gitignore: -------------------------------------------------------------------------------- 1 | helloworld.rs 2 | helloworld_grpc.rs 3 | -------------------------------------------------------------------------------- /grpc-examples/greeter/src/bin/greeter_client.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::sync::Arc; 3 | 4 | use grpc_examples_greeter::helloworld::*; 5 | use grpc_examples_greeter::helloworld_grpc::*; 6 | 7 | use grpc::ClientStub; 8 | use grpc::ClientStubExt; 9 | 10 | use futures::executor; 11 | use tls_api::TlsConnector; 12 | use tls_api::TlsConnectorBuilder; 13 | 14 | fn test_tls_connector() -> tls_api_native_tls::TlsConnector { 15 | let root_ca = include_bytes!("../root-ca.der"); 16 | 17 | let mut builder = tls_api_native_tls::TlsConnector::builder().unwrap(); 18 | builder 19 | .add_root_certificate(root_ca) 20 | .expect("add_root_certificate"); 21 | builder.build().unwrap() 22 | } 23 | 24 | fn is_tls() -> bool { 25 | env::args().any(|a| a == "--tls") 26 | } 27 | 28 | fn main() { 29 | env_logger::init(); 30 | 31 | let tls = is_tls(); 32 | 33 | let name = env::args() 34 | .filter(|a| a != "--tls") 35 | .nth(1) 36 | .map(|s| s.to_owned()) 37 | .unwrap_or_else(|| "world".to_owned()); 38 | 39 | let port = if !tls { 50051 } else { 50052 }; 40 | 41 | let client_conf = Default::default(); 42 | 43 | let client = if tls { 44 | // This is a bit complicated, because we need to explicitly pass root CA here 45 | // because server uses self-signed certificate. 46 | // TODO: simplify it 47 | let tls_option = httpbis::ClientTlsOption::Tls( 48 | "foobar.com".to_owned(), 49 | Arc::new(test_tls_connector().into_dyn()), 50 | ); 51 | let grpc_client = Arc::new( 52 | grpc::ClientBuilder::new("::1", port) 53 | .explicit_tls(tls_option) 54 | .build() 55 | .unwrap(), 56 | ); 57 | GreeterClient::with_client(grpc_client) 58 | } else { 59 | GreeterClient::new_plain("::1", port, client_conf).unwrap() 60 | }; 61 | 62 | let mut req = HelloRequest::new(); 63 | req.set_name(name); 64 | 65 | let resp = client 66 | .say_hello(grpc::RequestOptions::new(), req) 67 | .join_metadata_result(); 68 | 69 | println!("{:?}", executor::block_on(resp)); 70 | } 71 | -------------------------------------------------------------------------------- /grpc-examples/greeter/src/bin/greeter_server.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::thread; 3 | 4 | use grpc_examples_greeter::helloworld::*; 5 | use grpc_examples_greeter::helloworld_grpc::*; 6 | 7 | use grpc::ServerRequestSingle; 8 | use grpc::ServerResponseUnarySink; 9 | use tls_api::TlsAcceptor; 10 | use tls_api::TlsAcceptorBuilder; 11 | 12 | struct GreeterImpl; 13 | 14 | impl Greeter for GreeterImpl { 15 | fn say_hello( 16 | &self, 17 | req: ServerRequestSingle, 18 | resp: ServerResponseUnarySink, 19 | ) -> grpc::Result<()> { 20 | let mut r = HelloReply::new(); 21 | let name = if req.message.get_name().is_empty() { 22 | "world" 23 | } else { 24 | req.message.get_name() 25 | }; 26 | println!("greeting request from {}", name); 27 | r.set_message(format!("Hello {}", name)); 28 | resp.finish(r) 29 | } 30 | } 31 | 32 | fn test_tls_acceptor() -> tls_api_native_tls::TlsAcceptor { 33 | let pkcs12 = include_bytes!("../foobar.com.p12"); 34 | let builder = tls_api_native_tls::TlsAcceptor::builder_from_pkcs12(pkcs12, "mypass").unwrap(); 35 | builder.build().unwrap() 36 | } 37 | 38 | fn is_tls() -> bool { 39 | env::args().any(|a| a == "--tls") 40 | } 41 | 42 | fn main() { 43 | let tls = is_tls(); 44 | 45 | let port = if !tls { 50051 } else { 50052 }; 46 | 47 | let mut server = grpc::ServerBuilder::new(); 48 | server.http.set_port(port); 49 | server.add_service(GreeterServer::new_service_def(GreeterImpl)); 50 | //server.http.set_cpu_pool_threads(4); 51 | if tls { 52 | server.http.set_tls(test_tls_acceptor()); 53 | } 54 | let _server = server.build().expect("server"); 55 | 56 | println!( 57 | "greeter server started on port {} {}", 58 | port, 59 | if tls { "with tls" } else { "without tls" } 60 | ); 61 | 62 | loop { 63 | thread::park(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /grpc-examples/greeter/src/foobar.com.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stepancheg/grpc-rust/c54120fc786b7c8d66e7f93a41d93b66d4480eae/grpc-examples/greeter/src/foobar.com.p12 -------------------------------------------------------------------------------- /grpc-examples/greeter/src/gen-certs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | cd $(dirname $0) 4 | 5 | # Generate root CA 6 | 7 | openssl genrsa -out root-ca.key 1024 8 | 9 | openssl req -x509 -new -nodes -subj '/CN=www.mydom.com/O=My Company Name LTD./C=US' -key root-ca.key -sha256 -days 1024 -out root-ca.crt 10 | 11 | openssl x509 -outform der -in root-ca.crt -out root-ca.der 12 | 13 | 14 | # Generate server certificate 15 | 16 | openssl genrsa -out foobar.com.key 1024 17 | 18 | openssl req -new -key foobar.com.key -subj '/CN=foobar.com/O=My Company Name LTD./C=US' -out foobar.com.csr 19 | 20 | openssl x509 -req -in foobar.com.csr -CA root-ca.crt -CAkey root-ca.key -CAcreateserial -out foobar.com.crt -days 500 -sha256 21 | 22 | openssl pkcs12 -export -inkey foobar.com.key -in foobar.com.crt -out foobar.com.p12 -password pass:mypass 23 | 24 | 25 | # Cleanup 26 | 27 | rm foobar.com.crt foobar.com.csr foobar.com.key root-ca.crt root-ca.key root-ca.srl 28 | 29 | 30 | # vim: set ts=4 sw=4 et: 31 | -------------------------------------------------------------------------------- /grpc-examples/greeter/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod helloworld; 2 | pub mod helloworld_grpc; 3 | -------------------------------------------------------------------------------- /grpc-examples/greeter/src/root-ca.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stepancheg/grpc-rust/c54120fc786b7c8d66e7f93a41d93b66d4480eae/grpc-examples/greeter/src/root-ca.der -------------------------------------------------------------------------------- /grpc-examples/route_guide/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "grpc_examples_route_guide" 3 | version = "0.0.0" 4 | authors = ["Stepan Koltsov "] 5 | publish = false 6 | edition = "2018" 7 | 8 | [lib] 9 | doctest = false 10 | test = false 11 | 12 | [dependencies.grpc] 13 | path = "../../grpc" 14 | [dependencies.grpc-protobuf] 15 | path = "../../grpc-protobuf" 16 | 17 | [dependencies] 18 | protobuf = "2.23" 19 | futures = "0.3.*" 20 | tls-api = "0.6.0" 21 | tls-api-stub = "0.6.0" 22 | env_logger = "0.9" 23 | rand = "0.8" 24 | json = "0.12" 25 | 26 | [build-dependencies] 27 | protoc-rust-grpc = { path = "../../protoc-rust-grpc" } 28 | 29 | [[bin]] 30 | name = "route_guide_client" 31 | test = false 32 | 33 | [[bin]] 34 | name = "route_guide_server" 35 | test = false 36 | -------------------------------------------------------------------------------- /grpc-examples/route_guide/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | protoc_rust_grpc::Codegen::new() 3 | .out_dir("src") 4 | .input("route_guide.proto") 5 | .rust_protobuf(true) 6 | .run() 7 | .expect("protoc-rust-grpc"); 8 | } 9 | -------------------------------------------------------------------------------- /grpc-examples/route_guide/get-go-route-guide.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ex 2 | 3 | go get -u google.golang.org/grpc/examples/route_guide/client 4 | go get -u google.golang.org/grpc/examples/route_guide/server 5 | 6 | # vim: set ts=4 sw=4 et: 7 | -------------------------------------------------------------------------------- /grpc-examples/route_guide/route_guide.proto: -------------------------------------------------------------------------------- 1 | // Copyright 2015 gRPC authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | syntax = "proto3"; 16 | 17 | option java_multiple_files = true; 18 | option java_package = "io.grpc.examples.routeguide"; 19 | option java_outer_classname = "RouteGuideProto"; 20 | 21 | package routeguide; 22 | 23 | // Interface exported by the server. 24 | service RouteGuide { 25 | // A simple RPC. 26 | // 27 | // Obtains the feature at a given position. 28 | // 29 | // A feature with an empty name is returned if there's no feature at the given 30 | // position. 31 | rpc GetFeature(Point) returns (Feature) {} 32 | 33 | // A server-to-client streaming RPC. 34 | // 35 | // Obtains the Features available within the given Rectangle. Results are 36 | // streamed rather than returned at once (e.g. in a response message with a 37 | // repeated field), as the rectangle may cover a large area and contain a 38 | // huge number of features. 39 | rpc ListFeatures(Rectangle) returns (stream Feature) {} 40 | 41 | // A client-to-server streaming RPC. 42 | // 43 | // Accepts a stream of Points on a route being traversed, returning a 44 | // RouteSummary when traversal is completed. 45 | rpc RecordRoute(stream Point) returns (RouteSummary) {} 46 | 47 | // A Bidirectional streaming RPC. 48 | // 49 | // Accepts a stream of RouteNotes sent while a route is being traversed, 50 | // while receiving other RouteNotes (e.g. from other users). 51 | rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} 52 | } 53 | 54 | // Points are represented as latitude-longitude pairs in the E7 representation 55 | // (degrees multiplied by 10**7 and rounded to the nearest integer). 56 | // Latitudes should be in the range +/- 90 degrees and longitude should be in 57 | // the range +/- 180 degrees (inclusive). 58 | message Point { 59 | int32 latitude = 1; 60 | int32 longitude = 2; 61 | } 62 | 63 | // A latitude-longitude rectangle, represented as two diagonally opposite 64 | // points "lo" and "hi". 65 | message Rectangle { 66 | // One corner of the rectangle. 67 | Point lo = 1; 68 | 69 | // The other corner of the rectangle. 70 | Point hi = 2; 71 | } 72 | 73 | // A feature names something at a given point. 74 | // 75 | // If a feature could not be named, the name is empty. 76 | message Feature { 77 | // The name of the feature. 78 | string name = 1; 79 | 80 | // The point where the feature is detected. 81 | Point location = 2; 82 | } 83 | 84 | // A RouteNote is a message sent while at a given point. 85 | message RouteNote { 86 | // The location from which the message is sent. 87 | Point location = 1; 88 | 89 | // The message to be sent. 90 | string message = 2; 91 | } 92 | 93 | // A RouteSummary is received in response to a RecordRoute rpc. 94 | // 95 | // It contains the number of individual points received, the number of 96 | // detected features, and the total distance covered as the cumulative sum of 97 | // the distance between each point. 98 | message RouteSummary { 99 | // The number of points received. 100 | int32 point_count = 1; 101 | 102 | // The number of known features passed while traversing the route. 103 | int32 feature_count = 2; 104 | 105 | // The distance covered in metres. 106 | int32 distance = 3; 107 | 108 | // The duration of the traversal in seconds. 109 | int32 elapsed_time = 4; 110 | } 111 | -------------------------------------------------------------------------------- /grpc-examples/route_guide/src/.gitignore: -------------------------------------------------------------------------------- 1 | route_guide.rs 2 | route_guide_grpc.rs 3 | -------------------------------------------------------------------------------- /grpc-examples/route_guide/src/bin/route_guide_client.rs: -------------------------------------------------------------------------------- 1 | use grpc::prelude::*; 2 | 3 | use futures::executor; 4 | use grpc::ClientConf; 5 | use grpc_examples_route_guide::client::run_client; 6 | use grpc_examples_route_guide::route_guide_grpc::RouteGuideClient; 7 | use grpc_examples_route_guide::DEFAULT_PORT; 8 | 9 | fn main() { 10 | env_logger::init(); 11 | 12 | let client = 13 | RouteGuideClient::new_plain("127.0.0.1", DEFAULT_PORT, ClientConf::new()).expect("client"); 14 | 15 | executor::block_on(async { run_client(&client).await }); 16 | } 17 | -------------------------------------------------------------------------------- /grpc-examples/route_guide/src/bin/route_guide_server.rs: -------------------------------------------------------------------------------- 1 | use grpc_examples_route_guide::route_guide_grpc::RouteGuideServer; 2 | use grpc_examples_route_guide::server::RouteGuideImpl; 3 | use grpc_examples_route_guide::DEFAULT_PORT; 4 | use std::thread; 5 | 6 | fn main() { 7 | let service_def = RouteGuideServer::new_service_def(RouteGuideImpl::new_and_load_db()); 8 | 9 | let mut server_builder = grpc::ServerBuilder::new_plain(); 10 | server_builder.add_service(service_def); 11 | server_builder.http.set_port(DEFAULT_PORT); 12 | let server = server_builder.build().expect("build"); 13 | 14 | println!("server stared on addr {}", server.local_addr()); 15 | 16 | loop { 17 | thread::park(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /grpc-examples/route_guide/src/client.rs: -------------------------------------------------------------------------------- 1 | //! [client.go](https://github.com/grpc/grpc-go/blob/master/examples/route_guide/client/client.go) 2 | 3 | use crate::route_guide::Point; 4 | use crate::route_guide::Rectangle; 5 | use crate::route_guide::RouteNote; 6 | use crate::route_guide_grpc::RouteGuideClient; 7 | use futures::executor; 8 | use futures::StreamExt; 9 | use protobuf::Message; 10 | use protobuf::SingularPtrField; 11 | use rand::thread_rng; 12 | use rand::Rng; 13 | use std::thread; 14 | 15 | // print_feature gets the feature for the given point. 16 | async fn print_feature(client: &RouteGuideClient, point: Point) { 17 | println!( 18 | "Getting feature for point ({}, {})", 19 | point.latitude, point.longitude 20 | ); 21 | let feature = client 22 | .get_feature(grpc::RequestOptions::new(), point) 23 | // Drop response metadata 24 | .drop_metadata() 25 | .await 26 | .expect("get_feature"); 27 | println!("feature: {:?}", feature); 28 | } 29 | 30 | // print_features lists all the features within the given bounding Rectangle. 31 | async fn print_features(client: &RouteGuideClient, rect: Rectangle) { 32 | println!("Looking for features within {:?}", rect); 33 | let resp = client.list_features(grpc::RequestOptions::new(), rect); 34 | // Stream of features without response metadata 35 | let mut stream = resp.drop_metadata(); 36 | while let Some(feature) = stream.next().await { 37 | let feature = feature.expect("feature"); 38 | println!("{:?}", feature); 39 | } 40 | } 41 | 42 | fn random_point() -> Point { 43 | let mut rng = thread_rng(); 44 | let mut point = Point::new(); 45 | point.latitude = rng.gen_range(-90..90) * 10_000_000; 46 | point.longitude = rng.gen_range(-180..180) * 10_000_000; 47 | point 48 | } 49 | 50 | // run_record_route sends a sequence of points to server and expects to get a RouteSummary from server. 51 | async fn run_record_route(client: &RouteGuideClient) { 52 | // Create a random number of random points 53 | let mut rng = thread_rng(); 54 | let point_count = rng.gen_range(2..102); // Traverse at least two points 55 | 56 | println!("Traversing {} points.", point_count); 57 | 58 | let (mut req, resp) = client 59 | .record_route(grpc::RequestOptions::new()) 60 | .await 61 | .expect("request"); 62 | 63 | for _ in 0..point_count { 64 | let point = random_point(); 65 | // Waiting for buffer space to send data. 66 | req.wait().await.expect("block_wait"); 67 | req.send_data(point).expect("send_data"); 68 | } 69 | 70 | req.finish().unwrap(); 71 | 72 | let reply = resp.drop_metadata().await.expect("resp"); 73 | println!("Route summary: {:?}", reply); 74 | } 75 | 76 | // run_route_chat receives a sequence of route notes, while sending notes for various locations. 77 | async fn run_route_chat(client: &RouteGuideClient) { 78 | fn new_note(latitude: i32, longitude: i32, message: &str) -> RouteNote { 79 | RouteNote { 80 | location: SingularPtrField::some(Point { 81 | latitude, 82 | longitude, 83 | ..Default::default() 84 | }), 85 | message: message.to_owned(), 86 | ..Default::default() 87 | } 88 | } 89 | let notes = vec![ 90 | new_note(0, 1, "First message"), 91 | new_note(0, 2, "Second message"), 92 | new_note(0, 3, "Third message"), 93 | new_note(0, 1, "Fourth message"), 94 | new_note(0, 2, "Fifth message"), 95 | new_note(0, 3, "Sixth message"), 96 | ]; 97 | 98 | let (mut req, resp) = client 99 | .route_chat(grpc::RequestOptions::new()) 100 | .await 101 | .unwrap(); 102 | 103 | let sender_thread = thread::spawn(move || { 104 | executor::block_on(async { 105 | for note in notes { 106 | req.wait().await.unwrap(); 107 | req.send_data(note).expect("send"); 108 | } 109 | req.finish().expect("finish"); 110 | }); 111 | }); 112 | 113 | let mut responses = resp.drop_metadata(); 114 | while let Some(message) = responses.next().await { 115 | let message = message.expect("message"); 116 | let location = message 117 | .location 118 | .as_ref() 119 | .unwrap_or(Point::default_instance()); 120 | println!( 121 | "Got message {} at point({}, {})", 122 | message.message, location.latitude, location.longitude 123 | ); 124 | } 125 | 126 | sender_thread.join().expect("sender_thread"); 127 | } 128 | 129 | pub async fn run_client(client: &RouteGuideClient) { 130 | // Looking for a valid feature 131 | let mut point = Point::new(); 132 | point.latitude = 409146138; 133 | point.longitude = -746188906; 134 | print_feature(&client, point).await; 135 | 136 | // Feature missing. 137 | print_feature(&client, Point::new()).await; 138 | 139 | // Looking for features between 40, -75 and 42, -73. 140 | let mut rect = Rectangle::new(); 141 | rect.hi = SingularPtrField::some({ 142 | let mut point = Point::new(); 143 | point.latitude = 400000000; 144 | point.longitude = -750000000; 145 | point 146 | }) 147 | .into(); 148 | rect.lo = SingularPtrField::some({ 149 | let mut point = Point::new(); 150 | point.latitude = 420000000; 151 | point.longitude = -730000000; 152 | point 153 | }) 154 | .into(); 155 | 156 | print_features(&client, rect).await; 157 | 158 | // RecordRoute 159 | run_record_route(&client).await; 160 | 161 | // RouteChat 162 | run_route_chat(&client).await; 163 | } 164 | -------------------------------------------------------------------------------- /grpc-examples/route_guide/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod client; 2 | pub mod route_guide; 3 | pub mod route_guide_grpc; 4 | pub mod server; 5 | mod test; 6 | 7 | pub const DEFAULT_PORT: u16 = 10000; 8 | -------------------------------------------------------------------------------- /grpc-examples/route_guide/src/test.rs: -------------------------------------------------------------------------------- 1 | #![cfg(test)] 2 | 3 | use crate::client::run_client; 4 | use crate::route_guide_grpc::RouteGuideClient; 5 | use crate::route_guide_grpc::RouteGuideServer; 6 | use crate::server::RouteGuideImpl; 7 | use futures::executor; 8 | use grpc::ClientConf; 9 | use grpc::ClientStubExt; 10 | 11 | #[test] 12 | fn test() { 13 | let service_def = RouteGuideServer::new_service_def(RouteGuideImpl::new_and_load_db()); 14 | 15 | let mut server_builder = grpc::ServerBuilder::new_plain(); 16 | server_builder.add_service(service_def); 17 | server_builder.http.set_port(0); 18 | let server = server_builder.build().expect("build"); 19 | 20 | let port = server.local_addr().port().unwrap(); 21 | 22 | // server is running now 23 | 24 | let client = RouteGuideClient::new_plain("127.0.0.1", port, ClientConf::new()).expect("client"); 25 | 26 | executor::block_on(async { run_client(&client).await }); 27 | 28 | // shutdown server 29 | drop(server); 30 | } 31 | -------------------------------------------------------------------------------- /grpc-protobuf/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "grpc-protobuf" 4 | version = "0.9.0-pre" 5 | authors = ["Stepan Koltsov "] 6 | license = "MIT/Apache-2.0" 7 | description = "Protobuf marshaller for gRPC" 8 | repository = "https://github.com/stepancheg/grpc-rust" 9 | readme = "../README.md" 10 | keywords = ["grpc"] 11 | edition = "2018" 12 | 13 | [dependencies] 14 | protobuf = { version = "2.23", features = ["with-bytes"] } 15 | grpc = { path = "../grpc", version = "=0.9.0-pre" } 16 | bytes = "1.0.1" 17 | 18 | [lib] 19 | doctest = false 20 | -------------------------------------------------------------------------------- /grpc-protobuf/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(broken_intra_doc_links)] 2 | 3 | //! Implementation of marshaller for rust-protobuf parameter types. 4 | 5 | use bytes::Bytes; 6 | 7 | use grpc::marshall::Marshaller; 8 | 9 | use protobuf::CodedInputStream; 10 | use protobuf::Message; 11 | 12 | pub struct MarshallerProtobuf; 13 | 14 | impl Marshaller for MarshallerProtobuf { 15 | fn write_size_estimate(&self, _m: &M) -> grpc::Result { 16 | // TODO: implement it 17 | Ok(0) 18 | } 19 | 20 | fn write(&self, m: &M, _size_esimate: u32, out: &mut Vec) -> grpc::Result<()> { 21 | m.write_to_vec(out) 22 | .map_err(|e| grpc::Error::Marshaller(Box::new(e))) 23 | } 24 | 25 | fn read(&self, buf: Bytes) -> grpc::Result { 26 | // TODO: make protobuf simple 27 | let mut is = CodedInputStream::from_carllerche_bytes(&buf); 28 | let mut r: M = M::new(); 29 | r.merge_from(&mut is) 30 | .map_err(|e| grpc::Error::Marshaller(Box::new(e)))?; 31 | r.check_initialized() 32 | .map_err(|e| grpc::Error::Marshaller(Box::new(e)))?; 33 | Ok(r) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /grpc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "grpc" 4 | version = "0.9.0-pre" 5 | authors = ["Stepan Koltsov "] 6 | license = "MIT/Apache-2.0" 7 | description = "Rust implementation of gRPC" 8 | repository = "https://github.com/stepancheg/grpc-rust" 9 | readme = "../README.md" 10 | keywords = ["grpc"] 11 | edition = "2018" 12 | 13 | [dependencies] 14 | log = "0.4" 15 | log-ndc = "0.3" 16 | futures = "0.3.1" 17 | tokio = { version = "1.2.0", features = ["io-util", "net", "time", "rt"] } 18 | #httpbis = "~0.9" 19 | httpbis = { git = "https://github.com/stepancheg/rust-http2", rev = "04410b1a07f937d94e075c813d05a68022f0c7c8" } 20 | #httpbis = { path = "../../rust-http2/httpbis" } 21 | tls-api = "0.6.0" 22 | tls-api-stub = "0.6.0" 23 | bytes = "1.0.1" 24 | base64 = "0.13" 25 | 26 | [dev-dependencies] 27 | log-ndc-env-logger = "~0.3" 28 | rand = "0.8.3" 29 | 30 | [lib] 31 | doctest = false 32 | -------------------------------------------------------------------------------- /grpc/src/assert_types.rs: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | pub fn assert_send() {} 3 | #[allow(dead_code)] 4 | pub fn assert_sync() {} 5 | #[allow(dead_code)] 6 | pub fn assert_unpin() {} 7 | -------------------------------------------------------------------------------- /grpc/src/bytesx/buf_eq.rs: -------------------------------------------------------------------------------- 1 | use bytes::Buf; 2 | use std::cmp; 3 | 4 | /// Test if two buffers content is the same. 5 | pub fn buf_eq(mut b1: B1, mut b2: B2) -> bool { 6 | loop { 7 | if !b1.has_remaining() || !b2.has_remaining() { 8 | return b1.has_remaining() == b2.has_remaining(); 9 | } 10 | let s1 = b1.chunk(); 11 | let s2 = b2.chunk(); 12 | let min = cmp::min(s1.len(), s2.len()); 13 | if &s1[..min] != &s2[..min] { 14 | return false; 15 | } 16 | b1.advance(min); 17 | b2.advance(min); 18 | } 19 | } 20 | 21 | #[cfg(test)] 22 | mod test { 23 | use super::*; 24 | 25 | #[test] 26 | fn test() { 27 | assert!(buf_eq(b"".as_ref(), b"".as_ref())); 28 | assert!(buf_eq(b"ab".as_ref(), b"ab".as_ref())); 29 | assert!(!buf_eq(b"a".as_ref(), b"ab".as_ref())); 30 | assert!(!buf_eq(b"abc".as_ref(), b"ab".as_ref())); 31 | 32 | assert!(buf_eq(b"".as_ref().chain(b"ab".as_ref()), b"ab".as_ref())); 33 | assert!(buf_eq(b"a".as_ref().chain(b"b".as_ref()), b"ab".as_ref())); 34 | assert!(!buf_eq(b"a".as_ref().chain(b"bc".as_ref()), b"ab".as_ref())); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /grpc/src/bytesx/buf_vec_deque.rs: -------------------------------------------------------------------------------- 1 | use std::cmp; 2 | use std::collections::vec_deque; 3 | use std::collections::VecDeque; 4 | use std::io::IoSlice; 5 | use std::mem; 6 | use std::ops::Deref; 7 | use std::ops::DerefMut; 8 | 9 | use bytes::Buf; 10 | use bytes::BufMut; 11 | use bytes::Bytes; 12 | use bytes::BytesMut; 13 | use httpbis::BufGetBytes; 14 | 15 | #[derive(Debug)] 16 | pub(crate) struct BufVecDeque { 17 | deque: VecDeque, 18 | len: usize, 19 | } 20 | 21 | impl Default for BufVecDeque { 22 | fn default() -> Self { 23 | BufVecDeque { 24 | deque: VecDeque::default(), 25 | len: 0, 26 | } 27 | } 28 | } 29 | 30 | impl>> From for BufVecDeque { 31 | fn from(deque: I) -> Self { 32 | let deque = deque.into(); 33 | let len = deque.iter().map(Buf::remaining).sum(); 34 | BufVecDeque { deque, len } 35 | } 36 | } 37 | 38 | impl BufVecDeque { 39 | pub fn _new() -> BufVecDeque { 40 | Default::default() 41 | } 42 | 43 | pub fn _len(&self) -> usize { 44 | self.len 45 | } 46 | 47 | pub fn push_back(&mut self, bytes: B) { 48 | self.len += bytes.remaining(); 49 | self.deque.push_back(bytes); 50 | } 51 | 52 | pub fn pop_back(&mut self) -> Option { 53 | match self.deque.pop_back() { 54 | Some(b) => { 55 | self.len -= b.remaining(); 56 | Some(b) 57 | } 58 | None => None, 59 | } 60 | } 61 | 62 | #[cfg(test)] 63 | pub fn back_mut(&mut self) -> Option> { 64 | match self.deque.pop_back() { 65 | Some(back) => Some(BufVecDequeBackMut { 66 | deque: self, 67 | remaining: back.remaining(), 68 | back: Some(back), 69 | }), 70 | None => None, 71 | } 72 | } 73 | } 74 | 75 | impl Buf for BufVecDeque { 76 | fn remaining(&self) -> usize { 77 | self.len 78 | } 79 | 80 | fn chunk(&self) -> &[u8] { 81 | for b in &self.deque { 82 | let bytes = b.chunk(); 83 | if !bytes.is_empty() { 84 | return &bytes; 85 | } 86 | } 87 | &[] 88 | } 89 | 90 | fn chunks_vectored<'a>(&'a self, dst: &mut [IoSlice<'a>]) -> usize { 91 | let mut n = 0; 92 | for b in &self.deque { 93 | if n == dst.len() { 94 | break; 95 | } 96 | n += b.chunks_vectored(&mut dst[n..]); 97 | } 98 | n 99 | } 100 | 101 | fn advance(&mut self, mut cnt: usize) { 102 | assert!(self.len >= cnt); 103 | self.len -= cnt; 104 | 105 | while cnt != 0 { 106 | let front = self.deque.front_mut().unwrap(); 107 | let front_remaining = front.remaining(); 108 | if cnt < front_remaining { 109 | front.advance(cnt); 110 | break; 111 | } 112 | 113 | self.deque.pop_front().unwrap(); 114 | 115 | cnt -= front_remaining; 116 | } 117 | } 118 | 119 | fn copy_to_bytes(&mut self, len: usize) -> Bytes { 120 | todo!() 121 | // if !self.has_remaining() { 122 | // Bytes::new() 123 | // } else if self.deque.len() == 1 { 124 | // mem::take(self).into_iter().next().unwrap().to_bytes() 125 | // } else { 126 | // let mut ret = BytesMut::with_capacity(self.remaining()); 127 | // ret.put(self); 128 | // ret.freeze() 129 | // } 130 | } 131 | } 132 | 133 | impl BufGetBytes for BufVecDeque { 134 | fn get_bytes(&mut self, cnt: usize) -> Bytes { 135 | if cnt == 0 { 136 | return Bytes::new(); 137 | } 138 | 139 | assert!(cnt <= self.remaining()); 140 | 141 | match self.deque.front_mut() { 142 | Some(front) if front.remaining() >= cnt => { 143 | let r = if front.remaining() == cnt { 144 | let mut front = self.deque.pop_front().unwrap(); 145 | front.get_bytes(cnt) 146 | } else { 147 | front.get_bytes(cnt) 148 | }; 149 | 150 | self.len -= cnt; 151 | return r; 152 | } 153 | _ => {} 154 | } 155 | 156 | let mut r = BytesMut::new(); 157 | while r.len() < cnt { 158 | let chunk = self.chunk(); 159 | debug_assert!(chunk.len() > 0); 160 | let copy = cmp::min(cnt - r.len(), chunk.len()); 161 | r.extend_from_slice(&chunk[..copy]); 162 | self.advance(copy); 163 | } 164 | r.freeze() 165 | } 166 | } 167 | 168 | impl<'a, B: Buf> IntoIterator for &'a BufVecDeque { 169 | type Item = &'a B; 170 | type IntoIter = vec_deque::Iter<'a, B>; 171 | 172 | fn into_iter(self) -> Self::IntoIter { 173 | self.deque.iter() 174 | } 175 | } 176 | 177 | impl IntoIterator for BufVecDeque { 178 | type Item = B; 179 | type IntoIter = vec_deque::IntoIter; 180 | 181 | fn into_iter(self) -> Self::IntoIter { 182 | self.deque.into_iter() 183 | } 184 | } 185 | 186 | pub struct BufVecDequeBackMut<'a, B: Buf> { 187 | deque: &'a mut BufVecDeque, 188 | back: Option, 189 | remaining: usize, 190 | } 191 | 192 | impl<'a, B: Buf> Deref for BufVecDequeBackMut<'a, B> { 193 | type Target = B; 194 | 195 | fn deref(&self) -> &Self::Target { 196 | self.back.as_ref().unwrap() 197 | } 198 | } 199 | 200 | impl<'a, B: Buf> DerefMut for BufVecDequeBackMut<'a, B> { 201 | fn deref_mut(&mut self) -> &mut Self::Target { 202 | self.back.as_mut().unwrap() 203 | } 204 | } 205 | 206 | impl<'a, B: Buf> Drop for BufVecDequeBackMut<'a, B> { 207 | fn drop(&mut self) { 208 | let back = mem::take(&mut self.back).unwrap(); 209 | let new_remaining = back.remaining(); 210 | if new_remaining > self.remaining { 211 | self.deque.len += new_remaining - self.remaining; 212 | } else { 213 | self.deque.len -= self.remaining - new_remaining; 214 | } 215 | self.deque.deque.push_back(back); 216 | } 217 | } 218 | 219 | #[cfg(test)] 220 | mod test { 221 | use super::*; 222 | 223 | #[test] 224 | fn back_mut() { 225 | let mut d = BufVecDeque::>::default(); 226 | d.push_back(VecDeque::from(vec![3, 4])); 227 | d.push_back(VecDeque::from(vec![4, 6])); 228 | assert_eq!(4, d.remaining()); 229 | d.back_mut().unwrap().push_back(7); 230 | assert_eq!(5, d.remaining()); 231 | d.back_mut().unwrap().pop_back(); 232 | assert_eq!(4, d.remaining()); 233 | d.back_mut().unwrap().pop_back(); 234 | d.back_mut().unwrap().pop_back(); 235 | assert_eq!(2, d.remaining()); 236 | } 237 | 238 | #[test] 239 | fn pop_back() { 240 | let mut d = BufVecDeque::>::default(); 241 | d.push_back(VecDeque::from(vec![3, 4])); 242 | d.push_back(VecDeque::from(vec![4, 6, 7])); 243 | 244 | d.pop_back().unwrap(); 245 | assert_eq!(2, d.remaining()); 246 | } 247 | 248 | #[test] 249 | fn get_bytes() { 250 | let mut d = BufVecDeque::from(vec![ 251 | Bytes::copy_from_slice(b"ab"), 252 | Bytes::copy_from_slice(b"cde"), 253 | ]); 254 | 255 | assert_eq!(Bytes::copy_from_slice(b"a"), d.get_bytes(1)); 256 | assert_eq!(4, d.remaining()); 257 | assert_eq!(Bytes::copy_from_slice(b"b"), d.get_bytes(1)); 258 | assert_eq!(3, d.remaining()); 259 | assert_eq!(Bytes::copy_from_slice(b"cde"), d.get_bytes(3)); 260 | assert_eq!(0, d.remaining()); 261 | } 262 | 263 | #[test] 264 | fn get_bytes_cross_boundary() { 265 | let mut d = BufVecDeque::from(vec![ 266 | Bytes::copy_from_slice(b"ab"), 267 | Bytes::copy_from_slice(b"cde"), 268 | ]); 269 | assert_eq!(Bytes::copy_from_slice(b"abc"), d.get_bytes(3)); 270 | assert_eq!(2, d.remaining()); 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /grpc/src/bytesx/bytes_deque.rs: -------------------------------------------------------------------------------- 1 | use crate::bytesx::buf_eq::buf_eq; 2 | use crate::bytesx::bytes_vec_deque::BytesVecDeque; 3 | use crate::bytesx::iter_buf::IterBuf; 4 | use bytes::Buf; 5 | use bytes::Bytes; 6 | use httpbis::BufGetBytes; 7 | use std::collections::vec_deque; 8 | use std::io::IoSlice; 9 | use std::mem; 10 | 11 | #[derive(Debug)] 12 | enum Inner { 13 | One(Bytes), 14 | Deque(BytesVecDeque), 15 | } 16 | 17 | impl Default for Inner { 18 | fn default() -> Self { 19 | Inner::One(Bytes::new()) 20 | } 21 | } 22 | 23 | /// `VecDeque` but slightly more efficient. 24 | #[derive(Debug, Default)] 25 | pub struct BytesDeque(Inner); 26 | 27 | impl BytesDeque { 28 | /// Empty deque. 29 | pub fn new() -> BytesDeque { 30 | Default::default() 31 | } 32 | 33 | /// Create a deque by copying from bytes slice. 34 | pub fn copy_from_slice(bytes: &[u8]) -> BytesDeque { 35 | BytesDeque::from(Bytes::copy_from_slice(bytes)) 36 | } 37 | 38 | /// Length in bytes. 39 | pub fn len(&self) -> usize { 40 | match &self.0 { 41 | Inner::One(b) => b.len(), 42 | Inner::Deque(d) => d.len(), 43 | } 44 | } 45 | 46 | /// Append [`Bytes`] to this deque. 47 | pub fn extend(&mut self, bytes: Bytes) { 48 | if bytes.is_empty() { 49 | return; 50 | } 51 | 52 | match &mut self.0 { 53 | Inner::One(one) if one.is_empty() => { 54 | self.0 = Inner::One(bytes); 55 | } 56 | Inner::One(one) => { 57 | self.0 = Inner::Deque(BytesVecDeque::from(vec![mem::take(one), bytes])); 58 | } 59 | Inner::Deque(deque) if deque.len() == 0 => { 60 | self.0 = Inner::One(bytes); 61 | } 62 | Inner::Deque(deque) => { 63 | deque.extend(bytes); 64 | } 65 | } 66 | } 67 | 68 | /// Get deque contents as [`Bytes`] object. 69 | /// 70 | /// This operation is cheap if this deque contains only single [`Bytes`] object, 71 | /// otherwise it allocates memory and copies data. 72 | pub fn get_bytes(&self) -> Bytes { 73 | match &self.0 { 74 | Inner::One(b) => b.clone(), 75 | Inner::Deque(d) => d.get_bytes(), 76 | } 77 | } 78 | 79 | /// Convert contents into [`Bytes`] object. 80 | /// 81 | /// This function tries to avoid memory allocation when possible. 82 | pub fn into_bytes(self) -> Bytes { 83 | match self.0 { 84 | Inner::One(b) => b, 85 | Inner::Deque(d) => d.into_bytes(), 86 | } 87 | } 88 | 89 | fn iter_buf<'a>(&'a self) -> IterBuf + 'a> { 90 | IterBuf::new(self.into_iter().cloned(), self.remaining()) 91 | } 92 | } 93 | 94 | impl PartialEq for BytesDeque { 95 | fn eq(&self, other: &BytesDeque) -> bool { 96 | buf_eq(self.iter_buf(), other.iter_buf()) 97 | } 98 | } 99 | 100 | impl PartialEq<[u8]> for BytesDeque { 101 | fn eq(&self, other: &[u8]) -> bool { 102 | buf_eq(self.iter_buf(), other) 103 | } 104 | } 105 | 106 | impl From for BytesDeque { 107 | fn from(b: Bytes) -> Self { 108 | BytesDeque(Inner::One(b)) 109 | } 110 | } 111 | 112 | impl From> for BytesDeque { 113 | fn from(v: Vec) -> Self { 114 | BytesDeque::from(Bytes::from(v)) 115 | } 116 | } 117 | 118 | impl<'a> From<&'a str> for BytesDeque { 119 | fn from(s: &'a str) -> Self { 120 | BytesDeque::from(Bytes::copy_from_slice(s.as_bytes())) 121 | } 122 | } 123 | 124 | impl Into for BytesDeque { 125 | fn into(self) -> Bytes { 126 | self.into_bytes() 127 | } 128 | } 129 | 130 | impl Into> for BytesDeque { 131 | fn into(self) -> Vec { 132 | match self.0 { 133 | Inner::One(b) => Vec::from(b.as_ref()), 134 | Inner::Deque(d) => d.into(), 135 | } 136 | } 137 | } 138 | 139 | impl Buf for BytesDeque { 140 | fn remaining(&self) -> usize { 141 | match &self.0 { 142 | Inner::One(b) => b.remaining(), 143 | Inner::Deque(d) => d.remaining(), 144 | } 145 | } 146 | 147 | fn chunk(&self) -> &[u8] { 148 | match &self.0 { 149 | Inner::One(b) => b.chunk(), 150 | Inner::Deque(d) => d.chunk(), 151 | } 152 | } 153 | 154 | fn chunks_vectored<'a>(&'a self, dst: &mut [IoSlice<'a>]) -> usize { 155 | match &self.0 { 156 | Inner::One(b) => b.chunks_vectored(dst), 157 | Inner::Deque(d) => d.chunks_vectored(dst), 158 | } 159 | } 160 | 161 | fn advance(&mut self, cnt: usize) { 162 | match &mut self.0 { 163 | Inner::One(b) => b.advance(cnt), 164 | Inner::Deque(d) => d.advance(cnt), 165 | } 166 | } 167 | } 168 | 169 | impl BufGetBytes for BytesDeque { 170 | fn get_bytes(&mut self, cnt: usize) -> Bytes { 171 | match &mut self.0 { 172 | Inner::One(b) => b.get_bytes(cnt), 173 | Inner::Deque(d) => d.get_bytes(cnt), 174 | } 175 | } 176 | } 177 | 178 | pub enum Iter<'a> { 179 | One(Option<&'a Bytes>), 180 | Deque(vec_deque::Iter<'a, Bytes>), 181 | } 182 | 183 | impl<'a> Iterator for Iter<'a> { 184 | type Item = &'a Bytes; 185 | 186 | fn next(&mut self) -> Option { 187 | match self { 188 | Iter::One(b) => b.take(), 189 | Iter::Deque(d) => d.next(), 190 | } 191 | } 192 | } 193 | 194 | impl<'a> IntoIterator for &'a BytesDeque { 195 | type Item = &'a Bytes; 196 | type IntoIter = Iter<'a>; 197 | 198 | fn into_iter(self) -> Self::IntoIter { 199 | match &self.0 { 200 | Inner::One(b) => Iter::One(Some(b)), 201 | Inner::Deque(d) => Iter::Deque((&d).into_iter()), 202 | } 203 | } 204 | } 205 | 206 | #[cfg(test)] 207 | mod test { 208 | use super::*; 209 | use rand::thread_rng; 210 | use rand::Rng; 211 | 212 | fn extend_iter() { 213 | let mut d = BytesDeque::new(); 214 | let mut reference = Vec::new(); 215 | 216 | for _ in 0..10 { 217 | let bytes = if thread_rng().gen_range(0..3) == 0 { 218 | Bytes::new() 219 | } else { 220 | let len = thread_rng().gen_range(0..10); 221 | let mut v = Vec::new(); 222 | for _ in 0..len { 223 | v.push(thread_rng().gen()); 224 | } 225 | Bytes::from(v) 226 | }; 227 | 228 | reference.extend_from_slice(&bytes); 229 | d.extend(bytes); 230 | } 231 | 232 | assert_eq!(reference, Into::>::into(d)); 233 | } 234 | 235 | #[test] 236 | fn extend() { 237 | for _ in 0..10000 { 238 | extend_iter(); 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /grpc/src/bytesx/bytes_vec_deque.rs: -------------------------------------------------------------------------------- 1 | use crate::bytesx::buf_vec_deque::BufVecDeque; 2 | use bytes::Buf; 3 | use bytes::Bytes; 4 | use bytes::BytesMut; 5 | use httpbis::BufGetBytes; 6 | use std::collections::vec_deque; 7 | use std::collections::VecDeque; 8 | use std::io::IoSlice; 9 | 10 | #[derive(Debug, Default)] 11 | pub(crate) struct BytesVecDeque { 12 | deque: BufVecDeque, 13 | } 14 | 15 | impl>> From for BytesVecDeque { 16 | fn from(deque: I) -> Self { 17 | BytesVecDeque { 18 | deque: deque.into().into(), 19 | } 20 | } 21 | } 22 | 23 | impl Into for BytesVecDeque { 24 | fn into(self) -> Bytes { 25 | self.into_bytes() 26 | } 27 | } 28 | 29 | impl Into> for BytesVecDeque { 30 | fn into(self) -> Vec { 31 | let mut v = Vec::with_capacity(self.remaining()); 32 | for b in self.deque { 33 | v.extend_from_slice(b.as_ref()); 34 | } 35 | v 36 | } 37 | } 38 | 39 | impl BytesVecDeque { 40 | pub fn _new() -> BytesVecDeque { 41 | Default::default() 42 | } 43 | 44 | pub fn len(&self) -> usize { 45 | self.deque.remaining() 46 | } 47 | 48 | pub fn extend(&mut self, bytes: Bytes) { 49 | if bytes.is_empty() { 50 | return; 51 | } 52 | self.deque.push_back(bytes); 53 | } 54 | 55 | pub fn get_bytes(&self) -> Bytes { 56 | let mut bytes_mut = BytesMut::with_capacity(self.remaining()); 57 | for b in &self.deque { 58 | bytes_mut.extend_from_slice(b); 59 | } 60 | bytes_mut.freeze() 61 | } 62 | 63 | pub fn into_bytes(mut self) -> Bytes { 64 | self.to_bytes() 65 | } 66 | } 67 | 68 | impl Buf for BytesVecDeque { 69 | fn remaining(&self) -> usize { 70 | self.deque.remaining() 71 | } 72 | 73 | fn chunk(&self) -> &[u8] { 74 | self.deque.chunk() 75 | } 76 | 77 | fn chunks_vectored<'a>(&'a self, dst: &mut [IoSlice<'a>]) -> usize { 78 | self.deque.chunks_vectored(dst) 79 | } 80 | 81 | fn advance(&mut self, cnt: usize) { 82 | self.deque.advance(cnt) 83 | } 84 | 85 | fn copy_to_bytes(&mut self, len: usize) -> Bytes { 86 | self.deque.copy_to_bytes(len) 87 | } 88 | } 89 | 90 | impl BufGetBytes for BytesVecDeque { 91 | fn get_bytes(&mut self, cnt: usize) -> Bytes { 92 | self.deque.get_bytes(cnt) 93 | } 94 | } 95 | 96 | impl<'a> IntoIterator for &'a BytesVecDeque { 97 | type Item = &'a Bytes; 98 | type IntoIter = vec_deque::Iter<'a, Bytes>; 99 | 100 | fn into_iter(self) -> Self::IntoIter { 101 | (&self.deque).into_iter() 102 | } 103 | } 104 | 105 | #[cfg(test)] 106 | mod test { 107 | use super::*; 108 | 109 | #[test] 110 | fn buf_empty() { 111 | let d = BytesVecDeque::default(); 112 | assert_eq!(&[0u8; 0], Buf::chunk(&d)); 113 | assert_eq!(0, Buf::remaining(&d)); 114 | assert_eq!(false, Buf::has_remaining(&d)); 115 | } 116 | 117 | #[test] 118 | fn buf_advance_full() { 119 | let mut d = BytesVecDeque::default(); 120 | d.extend(Bytes::from_static(b"ab")); 121 | d.extend(Bytes::from_static(b"cde")); 122 | 123 | assert_eq!(b"ab", Buf::chunk(&d)); 124 | Buf::advance(&mut d, 2); 125 | assert_eq!(b"cde", Buf::chunk(&d)); 126 | Buf::advance(&mut d, 3); 127 | assert_eq!(0, Buf::remaining(&d)); 128 | assert_eq!(false, Buf::has_remaining(&d)); 129 | } 130 | 131 | #[test] 132 | fn buf_advance() { 133 | let mut d = BytesVecDeque::default(); 134 | d.extend(Bytes::from_static(b"ab")); 135 | d.extend(Bytes::from_static(b"cde")); 136 | 137 | assert_eq!(b"ab", Buf::chunk(&d)); 138 | Buf::advance(&mut d, 1); 139 | assert_eq!(b"b", Buf::chunk(&d)); 140 | Buf::advance(&mut d, 3); 141 | assert_eq!(b"e", Buf::chunk(&d)); 142 | Buf::advance(&mut d, 1); 143 | assert_eq!(0, Buf::remaining(&d)); 144 | assert_eq!(false, Buf::has_remaining(&d)); 145 | } 146 | 147 | #[test] 148 | fn buf_bytes_vectored() { 149 | let mut d = BytesVecDeque::default(); 150 | d.extend(Bytes::from_static(b"ab")); 151 | d.extend(Bytes::from_static(b"cde")); 152 | 153 | let mut v = [IoSlice::new(&[])]; 154 | assert_eq!(1, d.chunks_vectored(&mut v)); 155 | assert_eq!(b"ab", &*v[0]); 156 | 157 | let mut v = [IoSlice::new(&[]), IoSlice::new(&[])]; 158 | assert_eq!(2, d.chunks_vectored(&mut v)); 159 | assert_eq!(b"ab", &*v[0]); 160 | assert_eq!(b"cde", &*v[1]); 161 | 162 | let mut v = [IoSlice::new(&[]), IoSlice::new(&[]), IoSlice::new(&[])]; 163 | assert_eq!(2, d.chunks_vectored(&mut v)); 164 | assert_eq!(b"ab", &*v[0]); 165 | assert_eq!(b"cde", &*v[1]); 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /grpc/src/bytesx/iter_buf.rs: -------------------------------------------------------------------------------- 1 | use bytes::Buf; 2 | use std::cmp; 3 | 4 | pub(crate) struct IterBuf> { 5 | iter: I, 6 | next: Option, 7 | rem: usize, 8 | } 9 | 10 | impl> IterBuf { 11 | pub fn new(iter: I, rem: usize) -> IterBuf { 12 | let mut b = IterBuf { 13 | next: None, 14 | iter, 15 | rem, 16 | }; 17 | b.fill_next(); 18 | b 19 | } 20 | 21 | fn fill_next(&mut self) { 22 | loop { 23 | if let None = self.next { 24 | self.next = self.iter.next(); 25 | } 26 | match &mut self.next { 27 | None => return, 28 | Some(b) => { 29 | if b.has_remaining() { 30 | return; 31 | } 32 | } 33 | } 34 | } 35 | } 36 | } 37 | 38 | impl> Buf for IterBuf { 39 | fn remaining(&self) -> usize { 40 | self.rem 41 | } 42 | 43 | fn chunk(&self) -> &[u8] { 44 | match &self.next { 45 | Some(buf) => buf.chunk(), 46 | None => &[], 47 | } 48 | } 49 | 50 | fn advance(&mut self, cnt: usize) { 51 | while cnt != 0 { 52 | if let Some(buf) = &mut self.next { 53 | let min = cmp::min(cnt, buf.remaining()); 54 | buf.advance(min); 55 | self.rem -= min; 56 | if !buf.has_remaining() { 57 | self.next = None; 58 | } else { 59 | return; 60 | } 61 | } else { 62 | panic!("overflow"); 63 | } 64 | debug_assert!(self.next.is_none()); 65 | self.fill_next(); 66 | } 67 | } 68 | } 69 | 70 | #[cfg(test)] 71 | mod test {} 72 | -------------------------------------------------------------------------------- /grpc/src/bytesx/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod buf_eq; 2 | pub(crate) mod buf_vec_deque; 3 | pub(crate) mod bytes_deque; 4 | pub(crate) mod bytes_vec_deque; 5 | pub(crate) mod iter_buf; 6 | -------------------------------------------------------------------------------- /grpc/src/chars.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::ops::Deref; 3 | use std::str; 4 | 5 | use bytes::Bytes; 6 | 7 | #[derive(PartialEq, Eq, Clone)] 8 | pub struct Chars(Bytes); 9 | 10 | impl fmt::Debug for Chars { 11 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 12 | self.as_ref().fmt(f) 13 | } 14 | } 15 | 16 | impl Chars { 17 | pub fn from_static(s: &'static str) -> Chars { 18 | Chars(Bytes::from_static(s.as_bytes())) 19 | } 20 | 21 | pub fn into_inner(self) -> Bytes { 22 | self.0 23 | } 24 | 25 | pub fn try_from>(b: B) -> Result { 26 | let bytes = b.into(); 27 | str::from_utf8(&bytes)?; 28 | Ok(Chars(bytes)) 29 | } 30 | 31 | pub fn is_empty(&self) -> bool { 32 | self.0.is_empty() 33 | } 34 | 35 | pub fn copy_from_str(s: &str) -> Chars { 36 | Chars(Bytes::copy_from_slice(s.as_bytes())) 37 | } 38 | } 39 | 40 | impl AsRef for Chars { 41 | fn as_ref(&self) -> &str { 42 | unsafe { str::from_utf8_unchecked(&self.0) } 43 | } 44 | } 45 | 46 | impl Deref for Chars { 47 | type Target = str; 48 | 49 | fn deref(&self) -> &Self::Target { 50 | self.as_ref() 51 | } 52 | } 53 | 54 | impl From<&'static str> for Chars { 55 | fn from(s: &'static str) -> Chars { 56 | Chars(Bytes::from(s)) 57 | } 58 | } 59 | 60 | impl From for Chars { 61 | fn from(s: String) -> Chars { 62 | Chars(Bytes::from(s)) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /grpc/src/client/http_request_to_grpc_frames_typed.rs: -------------------------------------------------------------------------------- 1 | use crate::client::req_sink::ClientRequestSinkUntyped; 2 | use crate::common::sink::SinkCommon; 3 | use crate::common::sink::SinkCommonUntyped; 4 | use crate::marshall::Marshaller; 5 | use crate::or_static::arc::ArcOrStatic; 6 | use crate::ClientRequestSink; 7 | use httpbis::SinkAfterHeadersBox; 8 | use std::marker; 9 | 10 | pub(crate) fn http_req_to_grpc_frames_typed( 11 | http_req: SinkAfterHeadersBox, 12 | req_marshaller: ArcOrStatic>, 13 | ) -> ClientRequestSink { 14 | ClientRequestSink { 15 | common: SinkCommon { 16 | marshaller: req_marshaller, 17 | sink: ClientRequestSinkUntyped { 18 | common: SinkCommonUntyped { 19 | http: http_req, 20 | _marker: marker::PhantomData, 21 | }, 22 | }, 23 | }, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /grpc/src/client/http_response_to_grpc_frames.rs: -------------------------------------------------------------------------------- 1 | ///! Convert HTTP response stream to gRPC stream 2 | use std::collections::VecDeque; 3 | 4 | use futures::future::TryFutureExt; 5 | use futures::stream::Stream; 6 | 7 | use httpbis::Headers; 8 | use httpbis::StreamAfterHeadersBox; 9 | use httpbis::TryFutureBox; 10 | 11 | use bytes::Bytes; 12 | 13 | use crate::result; 14 | 15 | use crate::error::Error; 16 | use crate::error::GrpcMessageError; 17 | 18 | use crate::proto::grpc_frame_parser::GrpcFrameParser; 19 | use crate::proto::grpc_status::GrpcStatus; 20 | use crate::proto::headers::HEADER_GRPC_MESSAGE; 21 | use crate::proto::headers::HEADER_GRPC_STATUS; 22 | use crate::proto::metadata::Metadata; 23 | use crate::resp::*; 24 | use crate::stream_item::*; 25 | use futures::task::Context; 26 | use httpbis::DataOrTrailers; 27 | use std::pin::Pin; 28 | use std::task::Poll; 29 | 30 | fn init_headers_to_metadata(headers: Headers) -> result::Result { 31 | if headers.get_opt(":status") != Some("200") { 32 | return Err(Error::Other("not 200")); 33 | } 34 | 35 | // Check gRPC status code and message 36 | // TODO: a more detailed error message. 37 | if let Some(grpc_status) = headers.get_opt_parse(HEADER_GRPC_STATUS) { 38 | if grpc_status != GrpcStatus::Ok as i32 { 39 | let message = headers 40 | .get_opt(HEADER_GRPC_MESSAGE) 41 | .unwrap_or("unknown error"); 42 | return Err(Error::GrpcMessage(GrpcMessageError { 43 | grpc_status: grpc_status, 44 | grpc_message: message.to_owned(), 45 | })); 46 | } 47 | } 48 | 49 | Ok(Metadata::from_headers(headers)?) 50 | } 51 | 52 | pub fn http_response_to_grpc_frames( 53 | response: TryFutureBox<(Headers, StreamAfterHeadersBox)>, 54 | ) -> StreamingResponse { 55 | StreamingResponse::new(response.map_err(|e| crate::Error::from(e)).and_then( 56 | |(headers, rem)| async { 57 | let metadata = init_headers_to_metadata(headers)?; 58 | let frames: GrpcStreamWithTrailingMetadata = GrpcStreamWithTrailingMetadata::new( 59 | GrpcFrameFromHttpFramesStreamResponse::new(rem), 60 | ); 61 | Ok((metadata, frames)) 62 | }, 63 | )) 64 | } 65 | 66 | struct GrpcFrameFromHttpFramesStreamResponse { 67 | http_stream_stream: StreamAfterHeadersBox, 68 | buf: GrpcFrameParser, 69 | parsed_frames: VecDeque, 70 | } 71 | 72 | impl GrpcFrameFromHttpFramesStreamResponse { 73 | pub fn new(http_stream_stream: StreamAfterHeadersBox) -> Self { 74 | GrpcFrameFromHttpFramesStreamResponse { 75 | http_stream_stream, 76 | buf: GrpcFrameParser::default(), 77 | parsed_frames: VecDeque::new(), 78 | } 79 | } 80 | } 81 | 82 | impl Stream for GrpcFrameFromHttpFramesStreamResponse { 83 | type Item = crate::Result>; 84 | 85 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 86 | loop { 87 | let frames = self.buf.next_frames()?.0; 88 | self.parsed_frames.extend(frames); 89 | 90 | if let Some(frame) = self.parsed_frames.pop_front() { 91 | return Poll::Ready(Some(Ok(ItemOrMetadata::Item(frame)))); 92 | } 93 | 94 | let part_opt = 95 | match unsafe { Pin::new_unchecked(&mut self.http_stream_stream) }.poll_next(cx)? { 96 | Poll::Pending => return Poll::Pending, 97 | Poll::Ready(part_opt) => part_opt, 98 | }; 99 | let part = match part_opt { 100 | None => { 101 | self.buf.check_empty()?; 102 | return Poll::Ready(None); 103 | } 104 | Some(part) => part, 105 | }; 106 | 107 | match part { 108 | DataOrTrailers::Trailers(headers) => { 109 | self.buf.check_empty()?; 110 | let grpc_status = headers.get_opt_parse(HEADER_GRPC_STATUS); 111 | if grpc_status == Some(GrpcStatus::Ok as i32) { 112 | return Poll::Ready(Some(Ok(ItemOrMetadata::TrailingMetadata( 113 | Metadata::from_headers(headers)?, 114 | )))); 115 | } else { 116 | return Poll::Ready(Some(Err( 117 | if let Some(message) = headers.get_opt(HEADER_GRPC_MESSAGE) { 118 | Error::GrpcMessage(GrpcMessageError { 119 | grpc_status: grpc_status.unwrap_or(GrpcStatus::Unknown as i32), 120 | grpc_message: message.to_owned(), 121 | }) 122 | } else { 123 | Error::Other("not xxx") 124 | }, 125 | ))); 126 | } 127 | } 128 | DataOrTrailers::Data(data, ..) => { 129 | self.buf.enqueue(data); 130 | } 131 | } 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /grpc/src/client/http_response_to_grpc_frames_typed.rs: -------------------------------------------------------------------------------- 1 | use crate::client::http_response_to_grpc_frames::http_response_to_grpc_frames; 2 | use crate::marshall::Marshaller; 3 | use crate::or_static::arc::ArcOrStatic; 4 | use crate::StreamingResponse; 5 | use httpbis::Headers; 6 | use httpbis::StreamAfterHeadersBox; 7 | use httpbis::TryFutureBox; 8 | 9 | pub(crate) fn http_response_to_grpc_frames_typed( 10 | resp: TryFutureBox<(Headers, StreamAfterHeadersBox)>, 11 | marshaller: ArcOrStatic>, 12 | ) -> StreamingResponse { 13 | http_response_to_grpc_frames(resp).and_then_items(move |message| marshaller.read(message)) 14 | } 15 | -------------------------------------------------------------------------------- /grpc/src/client/req_sink.rs: -------------------------------------------------------------------------------- 1 | use crate::client::types::ClientTypes; 2 | use crate::common::sink::MessageToBeSerialized; 3 | use crate::common::sink::SinkCommon; 4 | use crate::common::sink::SinkCommonUntyped; 5 | use crate::common::sink::SinkUntyped; 6 | 7 | use crate::result; 8 | use futures::task::Context; 9 | use httpbis; 10 | 11 | use futures::future; 12 | use httpbis::SinkAfterHeadersBox; 13 | use std::future::Future; 14 | use std::task::Poll; 15 | 16 | pub struct ClientRequestSinkUntyped { 17 | pub(crate) common: SinkCommonUntyped, 18 | } 19 | 20 | impl SinkUntyped for ClientRequestSinkUntyped { 21 | fn poll(&mut self, cx: &mut Context<'_>) -> Poll> { 22 | self.common.http.poll(cx) 23 | } 24 | 25 | fn send_message(&mut self, message: &dyn MessageToBeSerialized) -> result::Result<()> { 26 | self.common.send_message(message)?; 27 | Ok(()) 28 | } 29 | } 30 | 31 | impl ClientRequestSinkUntyped { 32 | pub fn finish(&mut self) -> result::Result<()> { 33 | self.common.http.close()?; 34 | Ok(()) 35 | } 36 | } 37 | 38 | /// And interface to send messages when client request is streaming. 39 | pub struct ClientRequestSink { 40 | pub(crate) common: SinkCommon, 41 | } 42 | 43 | impl ClientRequestSink { 44 | /// Poll for write availability. 45 | /// 46 | /// When request is not polled, [`send_data`](ClientRequestSink::send_data) 47 | /// will be successful anyway, but client memory can overflow. 48 | pub fn poll(&mut self, cx: &mut Context<'_>) -> Poll> { 49 | self.common.poll(cx) 50 | } 51 | 52 | /// Wait for buffer availability. 53 | /// 54 | /// When request is not polled, [`send_data`](ClientRequestSink::send_data) 55 | /// will be successful anyway, but client memory can overflow. 56 | pub fn wait<'a>(&'a mut self) -> impl Future> + 'a { 57 | future::poll_fn(move |cx| self.poll(cx)) 58 | } 59 | 60 | /// Send a message. 61 | pub fn send_data(&mut self, message: Req) -> result::Result<()> { 62 | self.common.send_data(message) 63 | } 64 | 65 | /// Finish client request. 66 | /// 67 | /// This is mandatory operation, if it is not called, client will reset the stream on drop. 68 | pub fn finish(&mut self) -> result::Result<()> { 69 | self.common.sink.finish() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /grpc/src/client/types.rs: -------------------------------------------------------------------------------- 1 | use crate::client::req_sink::ClientRequestSinkUntyped; 2 | use crate::common::types::Types; 3 | 4 | pub(crate) struct ClientTypes; 5 | 6 | impl Types for ClientTypes { 7 | type SinkUntyped = ClientRequestSinkUntyped; 8 | } 9 | 10 | unsafe impl Sync for ClientTypes {} 11 | -------------------------------------------------------------------------------- /grpc/src/client_stub.rs: -------------------------------------------------------------------------------- 1 | use crate::client::Client; 2 | use crate::client::ClientBuilder; 3 | use crate::ClientConf; 4 | use crate::Result as grpc_Result; 5 | use std::sync::Arc; 6 | 7 | /// Trait implemented by `XxxClient` structs for `Xxx` trait. 8 | pub trait ClientStub: Sized { 9 | /// Create a client stub using given `Client` object. 10 | fn with_client(grpc_client: Arc) -> Self; 11 | } 12 | 13 | /// Utilities to work with generated code clients. 14 | pub trait ClientStubExt: Sized { 15 | /// Create a plain (non-encrypted) client connected to specified host and port 16 | fn new_plain(host: &str, port: u16, conf: ClientConf) -> crate::Result; 17 | 18 | /// Create a client connected to specified host and port using TLS. 19 | fn new_tls( 20 | host: &str, 21 | port: u16, 22 | conf: ClientConf, 23 | ) -> grpc_Result; 24 | 25 | /// Create a client connected to specified unix socket. 26 | fn new_plain_unix(addr: &str, conf: ClientConf) -> grpc_Result; 27 | } 28 | 29 | impl ClientStubExt for C { 30 | fn new_plain(host: &str, port: u16, conf: ClientConf) -> grpc_Result { 31 | ClientBuilder::new(host, port) 32 | .conf(conf) 33 | .build() 34 | .map(|c| Self::with_client(Arc::new(c))) 35 | } 36 | 37 | fn new_tls( 38 | host: &str, 39 | port: u16, 40 | conf: ClientConf, 41 | ) -> grpc_Result { 42 | ClientBuilder::new(host, port) 43 | .tls::() 44 | .conf(conf) 45 | .build() 46 | .map(|c| Self::with_client(Arc::new(c))) 47 | } 48 | 49 | fn new_plain_unix(addr: &str, conf: ClientConf) -> grpc_Result { 50 | ClientBuilder::new_unix(addr) 51 | .conf(conf) 52 | .build() 53 | .map(|c| Self::with_client(Arc::new(c))) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /grpc/src/common/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod sink; 2 | pub(crate) mod types; 3 | -------------------------------------------------------------------------------- /grpc/src/common/sink.rs: -------------------------------------------------------------------------------- 1 | use crate::client::types::ClientTypes; 2 | use crate::common::types::Types; 3 | use bytes::Bytes; 4 | 5 | use crate::marshall::Marshaller; 6 | use crate::or_static::arc::ArcOrStatic; 7 | use crate::proto::grpc_frame::write_grpc_frame_cb; 8 | use crate::result; 9 | use crate::server::types::ServerTypes; 10 | use futures::task::Context; 11 | use httpbis; 12 | use httpbis::SinkAfterHeadersBox; 13 | use std::marker; 14 | use std::task::Poll; 15 | 16 | pub trait MessageToBeSerialized { 17 | fn size_estimate(&self) -> result::Result; 18 | fn write(&self, size_estimate: u32, out: &mut Vec) -> result::Result<()>; 19 | 20 | fn serialize_to_grpc_frame(&self) -> result::Result { 21 | let mut data = Vec::new(); 22 | let size_estimate = self.size_estimate()?; 23 | write_grpc_frame_cb(&mut data, size_estimate, |data| { 24 | self.write(size_estimate, data) 25 | })?; 26 | Ok(Bytes::from(data)) 27 | } 28 | } 29 | 30 | struct MessageToBeSerializedImpl<'a, T> { 31 | pub message: T, 32 | pub marshaller: &'a dyn Marshaller, 33 | } 34 | 35 | impl<'a, T: 'static> MessageToBeSerialized for MessageToBeSerializedImpl<'a, T> { 36 | fn size_estimate(&self) -> result::Result { 37 | self.marshaller.write_size_estimate(&self.message) 38 | } 39 | 40 | fn write(&self, size_estimate: u32, out: &mut Vec) -> result::Result<()> { 41 | self.marshaller.write(&self.message, size_estimate, out) 42 | } 43 | } 44 | 45 | /// Client request or server response. 46 | pub(crate) trait SinkUntyped { 47 | fn poll(&mut self, cx: &mut Context<'_>) -> Poll>; 48 | fn send_message(&mut self, message: &dyn MessageToBeSerialized) -> result::Result<()>; 49 | } 50 | 51 | pub(crate) struct SinkCommonUntyped { 52 | pub(crate) http: SinkAfterHeadersBox, 53 | pub(crate) _marker: marker::PhantomData, 54 | } 55 | 56 | impl SinkCommonUntyped { 57 | pub fn send_message(&mut self, message: &dyn MessageToBeSerialized) -> result::Result<()> { 58 | let mut data = Vec::new(); 59 | let size_estimate = message.size_estimate()?; 60 | write_grpc_frame_cb(&mut data, size_estimate, |data| { 61 | message.write(size_estimate, data) 62 | })?; 63 | self.http.send_data(Bytes::from(data))?; 64 | Ok(()) 65 | } 66 | } 67 | 68 | pub(crate) struct SinkCommon { 69 | pub marshaller: ArcOrStatic>, 70 | pub sink: T::SinkUntyped, 71 | } 72 | 73 | impl SinkCommon { 74 | pub fn poll(&mut self, cx: &mut Context<'_>) -> Poll> { 75 | self.sink.poll(cx) 76 | } 77 | 78 | pub fn send_data(&mut self, message: M) -> result::Result<()> { 79 | self.sink.send_message(&MessageToBeSerializedImpl { 80 | message, 81 | marshaller: &*self.marshaller, 82 | })?; 83 | Ok(()) 84 | } 85 | } 86 | 87 | fn _assert_types() { 88 | crate::assert_types::assert_send::>(); 89 | crate::assert_types::assert_send::>(); 90 | } 91 | -------------------------------------------------------------------------------- /grpc/src/common/types.rs: -------------------------------------------------------------------------------- 1 | use crate::common::sink::SinkUntyped; 2 | 3 | pub(crate) trait Types: Send + Sync + 'static { 4 | type SinkUntyped: SinkUntyped; 5 | } 6 | -------------------------------------------------------------------------------- /grpc/src/error.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error as std_Error; 2 | use std::fmt; 3 | use std::io; 4 | 5 | use httpbis; 6 | 7 | use crate::proto::metadata; 8 | 9 | /// Error from gRPC protocol headers. 10 | #[derive(Debug)] 11 | pub struct GrpcMessageError { 12 | /// Content of `grpc-status` header. 13 | pub grpc_status: i32, 14 | 15 | /// Content of `grpc-message` header. 16 | pub grpc_message: String, 17 | } 18 | 19 | /// All grpc crate errors. 20 | #[derive(Debug)] 21 | pub enum Error { 22 | /// I/O error. 23 | Io(io::Error), 24 | /// rust-http2 error. 25 | Http(httpbis::Error), 26 | /// Error from gRPC protocol. 27 | GrpcMessage(GrpcMessageError), 28 | /// Failed to decode megadata. 29 | MetadataDecode(metadata::MetadataDecodeError), 30 | /// Someone panicked. 31 | Panic(String), 32 | /// Marshaller error. 33 | Marshaller(Box), 34 | /// Other error. 35 | // TODO: get rid of it. 36 | Other(&'static str), 37 | } 38 | 39 | fn _assert_debug(_: &D) {} 40 | 41 | fn _assert_grpc_error_debug(e: &Error) { 42 | _assert_debug(e); 43 | } 44 | 45 | impl std_Error for Error {} 46 | 47 | impl fmt::Display for Error { 48 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 49 | match self { 50 | &Error::Io(ref err) => write!(f, "io error: {}", err), 51 | &Error::Http(ref err) => write!(f, "http error: {}", err), 52 | &Error::GrpcMessage(ref err) => write!(f, "grpc message error: {}", err.grpc_message), 53 | &Error::MetadataDecode(..) => write!(f, "metadata decode error"), 54 | &Error::Panic(ref message) => write!(f, "panic: {}", message), 55 | &Error::Other(ref message) => write!(f, "other error: {}", message), 56 | &Error::Marshaller(ref e) => write!(f, "marshaller error: {}", e), 57 | } 58 | } 59 | } 60 | 61 | impl From for Error { 62 | fn from(err: io::Error) -> Self { 63 | Error::Io(err) 64 | } 65 | } 66 | 67 | impl From for Error { 68 | fn from(err: httpbis::Error) -> Self { 69 | Error::Http(err) 70 | } 71 | } 72 | 73 | impl From for io::Error { 74 | fn from(err: Error) -> io::Error { 75 | match err { 76 | Error::Io(e) => e, 77 | _ => io::Error::new(io::ErrorKind::Other, err), 78 | } 79 | } 80 | } 81 | 82 | impl From for Error { 83 | fn from(de: metadata::MetadataDecodeError) -> Self { 84 | Error::MetadataDecode(de) 85 | } 86 | } 87 | 88 | impl From for httpbis::Error { 89 | fn from(err: Error) -> httpbis::Error { 90 | httpbis::Error::User(format!("gRPC error: {}", err)) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /grpc/src/for_test.rs: -------------------------------------------------------------------------------- 1 | //! Code useful in tests. 2 | 3 | use bytes::Bytes; 4 | 5 | use crate::error::Error; 6 | use crate::marshall::Marshaller; 7 | use crate::result::Result; 8 | 9 | pub struct MarshallerString; 10 | 11 | impl Marshaller for MarshallerString { 12 | fn write(&self, m: &String, _estimated_size: u32, out: &mut Vec) -> Result<()> { 13 | out.extend_from_slice(m.as_bytes()); 14 | Ok(()) 15 | } 16 | 17 | fn read(&self, bytes: Bytes) -> Result { 18 | String::from_utf8(bytes.as_ref().to_vec()) 19 | .map_err(|_| Error::Other("failed to parse utf-8")) 20 | } 21 | } 22 | 23 | pub struct MarshallerBytes; 24 | 25 | impl Marshaller> for MarshallerBytes { 26 | fn write(&self, m: &Vec, _estimated_size: u32, out: &mut Vec) -> Result<()> { 27 | out.extend_from_slice(&m); 28 | Ok(()) 29 | } 30 | 31 | fn read(&self, bytes: Bytes) -> Result> { 32 | Ok(bytes.as_ref().to_vec()) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /grpc/src/futures_grpc.rs: -------------------------------------------------------------------------------- 1 | use futures::Future; 2 | use futures::Stream; 3 | use std::pin::Pin; 4 | 5 | pub type GrpcFuture = Pin> + Send + 'static>>; 6 | pub type GrpcStream = Pin> + Send + 'static>>; 7 | -------------------------------------------------------------------------------- /grpc/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(broken_intra_doc_links)] 2 | 3 | #[macro_use] 4 | extern crate log; 5 | 6 | mod misc; 7 | 8 | mod client; 9 | mod client_stub; 10 | mod common; 11 | mod server; 12 | 13 | mod proto; 14 | 15 | mod assert_types; 16 | 17 | pub(crate) mod bytesx; 18 | mod chars; 19 | mod or_static; 20 | mod req; 21 | mod resp; 22 | mod result; 23 | mod stream_item; 24 | 25 | mod error; 26 | mod futures_grpc; 27 | 28 | pub mod marshall; 29 | mod method; 30 | 31 | pub mod prelude; 32 | 33 | pub mod rt; 34 | 35 | pub mod for_test; 36 | 37 | pub use error::Error; 38 | pub use error::GrpcMessageError; 39 | pub use result::Result; 40 | 41 | pub use stream_item::ItemOrMetadata; 42 | 43 | pub use client::req_sink::ClientRequestSink; 44 | pub use client::Client; 45 | pub use client::ClientBuilder; 46 | pub use client::ClientConf; 47 | 48 | pub use client_stub::ClientStub; 49 | pub use client_stub::ClientStubExt; 50 | 51 | pub use server::req_handler::ServerRequest; 52 | pub use server::req_single::ServerRequestSingle; 53 | pub use server::req_stream::ServerRequestStream; 54 | pub use server::resp_sink::ServerResponseSink; 55 | pub use server::resp_unary_sink::ServerResponseUnarySink; 56 | pub use server::Server; 57 | pub use server::ServerBuilder; 58 | pub use server::ServerConf; 59 | 60 | pub use resp::SingleResponse; 61 | pub use resp::StreamingResponse; 62 | 63 | pub use req::RequestOptions; 64 | pub use req::StreamingRequest; 65 | 66 | pub use futures_grpc::GrpcFuture; 67 | pub use futures_grpc::GrpcStream; 68 | 69 | pub use proto::grpc_status::GrpcStatus; 70 | pub use proto::metadata::Metadata; 71 | pub use proto::metadata::MetadataKey; 72 | -------------------------------------------------------------------------------- /grpc/src/marshall.rs: -------------------------------------------------------------------------------- 1 | use bytes::Bytes; 2 | 3 | /// Message (request or response) marshaller 4 | pub trait Marshaller: Send + Sync + 'static { 5 | /// Estimate message size. 6 | /// 7 | /// Can be used for more efficient capacity allocation. 8 | /// 9 | /// Default implementation returns zero. 10 | fn write_size_estimate(&self, _m: &M) -> crate::Result { 11 | Ok(0) 12 | } 13 | 14 | /// Serialize a message into a `Vec`. 15 | fn write(&self, m: &M, estimated_size: u32, out: &mut Vec) -> crate::Result<()>; 16 | 17 | /// Parse a message from given `Bytes` object. 18 | fn read(&self, bytes: Bytes) -> crate::Result; 19 | } 20 | -------------------------------------------------------------------------------- /grpc/src/method.rs: -------------------------------------------------------------------------------- 1 | use crate::marshall::*; 2 | use crate::or_static::arc::ArcOrStatic; 3 | use crate::or_static::string::StringOrStatic; 4 | 5 | pub enum GrpcStreaming { 6 | Unary, 7 | ClientStreaming, 8 | ServerStreaming, 9 | Bidi, 10 | } 11 | 12 | pub trait GrpcStreamingFlavor { 13 | type Flavor; 14 | 15 | fn streaming() -> GrpcStreaming; 16 | } 17 | 18 | pub struct GrpcStreamingUnary; 19 | pub struct GrpcStreamingClientStreaming; 20 | pub struct GrpcStreamingServerStreaming; 21 | pub struct GrpcStreamingBidi; 22 | 23 | pub struct MethodDescriptor { 24 | pub name: StringOrStatic, 25 | pub streaming: GrpcStreaming, 26 | pub req_marshaller: ArcOrStatic>, 27 | pub resp_marshaller: ArcOrStatic>, 28 | } 29 | -------------------------------------------------------------------------------- /grpc/src/misc.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | 3 | pub fn _any_to_string(any: Box) -> String { 4 | if any.is::() { 5 | *any.downcast::().unwrap() 6 | } else if any.is::<&str>() { 7 | (*any.downcast::<&str>().unwrap()).to_owned() 8 | } else { 9 | "unknown any".to_owned() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /grpc/src/or_static/arc.rs: -------------------------------------------------------------------------------- 1 | use std::ops; 2 | use std::sync::Arc; 3 | 4 | pub enum ArcOrStatic { 5 | Arc(Arc), 6 | Static(&'static A), 7 | } 8 | 9 | impl Clone for ArcOrStatic { 10 | fn clone(&self) -> Self { 11 | match self { 12 | ArcOrStatic::Arc(a) => ArcOrStatic::Arc(a.clone()), 13 | ArcOrStatic::Static(a) => ArcOrStatic::Static(a), 14 | } 15 | } 16 | } 17 | 18 | impl ops::Deref for ArcOrStatic { 19 | type Target = A; 20 | 21 | fn deref(&self) -> &A { 22 | match self { 23 | ArcOrStatic::Arc(a) => &*a, 24 | ArcOrStatic::Static(a) => a, 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /grpc/src/or_static/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod arc; 2 | pub(crate) mod string; 3 | -------------------------------------------------------------------------------- /grpc/src/or_static/string.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::ops; 3 | 4 | #[derive(Clone, Eq)] 5 | pub enum StringOrStatic { 6 | String(String), 7 | Static(&'static str), 8 | } 9 | 10 | impl From<&str> for StringOrStatic { 11 | fn from(s: &str) -> Self { 12 | StringOrStatic::String(s.to_owned()) 13 | } 14 | } 15 | 16 | impl From for StringOrStatic { 17 | fn from(s: String) -> Self { 18 | StringOrStatic::String(s) 19 | } 20 | } 21 | 22 | impl StringOrStatic { 23 | pub fn as_str(&self) -> &str { 24 | match self { 25 | StringOrStatic::String(s) => &s, 26 | StringOrStatic::Static(s) => s, 27 | } 28 | } 29 | 30 | pub fn to_string(&self) -> String { 31 | format!("{}", self) 32 | } 33 | } 34 | 35 | impl ops::Deref for StringOrStatic { 36 | type Target = str; 37 | 38 | fn deref(&self) -> &str { 39 | self.as_str() 40 | } 41 | } 42 | 43 | impl PartialEq for StringOrStatic { 44 | fn eq(&self, other: &StringOrStatic) -> bool { 45 | self.as_str() == other.as_str() 46 | } 47 | } 48 | 49 | impl PartialEq for StringOrStatic { 50 | fn eq(&self, other: &str) -> bool { 51 | self.as_str() == other 52 | } 53 | } 54 | 55 | impl PartialEq<&str> for StringOrStatic { 56 | fn eq(&self, other: &&str) -> bool { 57 | self.as_str() == *other 58 | } 59 | } 60 | 61 | impl PartialEq for str { 62 | fn eq(&self, other: &StringOrStatic) -> bool { 63 | self == other.as_str() 64 | } 65 | } 66 | 67 | impl PartialEq for &str { 68 | fn eq(&self, other: &StringOrStatic) -> bool { 69 | *self == other.as_str() 70 | } 71 | } 72 | 73 | impl fmt::Display for StringOrStatic { 74 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 75 | match self { 76 | StringOrStatic::String(s) => fmt::Display::fmt(s, f), 77 | StringOrStatic::Static(s) => fmt::Display::fmt(s, f), 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /grpc/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::client_stub::ClientStubExt; 2 | -------------------------------------------------------------------------------- /grpc/src/proto/grpc_frame.rs: -------------------------------------------------------------------------------- 1 | use bytes::Buf; 2 | use bytes::Bytes; 3 | 4 | use futures::stream::StreamExt; 5 | 6 | use crate::error::*; 7 | use crate::proto::grpc_frame_parser::GrpcFrameParser; 8 | use crate::result; 9 | use futures::task::Context; 10 | use httpbis::DataOrTrailers; 11 | use std::pin::Pin; 12 | use std::task::Poll; 13 | 14 | pub const GRPC_HEADER_LEN: usize = 5; 15 | 16 | pub fn parse_grpc_frame_header(stream: &mut B) -> result::Result> { 17 | if stream.remaining() < GRPC_HEADER_LEN { 18 | return Ok(None); 19 | } 20 | 21 | let compressed = match stream.get_u8() { 22 | 0 => false, 23 | 1 => true, 24 | _ => return Err(Error::Other("unknown compression flag")), 25 | }; 26 | if compressed { 27 | return Err(Error::Other("compression is not implemented")); 28 | } 29 | Ok(Some(stream.get_u32())) 30 | } 31 | 32 | /// Encode data into grpc frame (add frame prefix) 33 | pub fn write_grpc_frame_cb(stream: &mut Vec, estimate: u32, frame: F) -> Result<(), E> 34 | where 35 | F: FnOnce(&mut Vec) -> Result<(), E>, 36 | { 37 | stream.reserve(estimate as usize + GRPC_HEADER_LEN); 38 | 39 | stream.push(0); // compressed flag 40 | let size_pos = stream.len(); 41 | stream.extend_from_slice(&[0, 0, 0, 0]); // len 42 | let frame_start = stream.len(); 43 | frame(stream)?; 44 | let frame_size = stream.len() - frame_start; 45 | assert!(frame_size <= u32::max_value() as usize); 46 | stream[size_pos..size_pos + 4].copy_from_slice(&(frame_size as u32).to_be_bytes()); 47 | Ok(()) 48 | } 49 | 50 | trait RequestOrResponse { 51 | fn need_trailing_header() -> bool; 52 | } 53 | 54 | // pub struct GrpcFrameFromHttpFramesStreamRequest { 55 | // http_stream_stream: HttpStreamAfterHeaders, 56 | // buf: GrpcFrameParser, 57 | // parsed_frames: VecDeque, 58 | // } 59 | // 60 | // impl GrpcFrameFromHttpFramesStreamRequest { 61 | // pub fn _new(http_stream_stream: HttpStreamAfterHeaders) -> Self { 62 | // GrpcFrameFromHttpFramesStreamRequest { 63 | // http_stream_stream, 64 | // buf: GrpcFrameParser::default(), 65 | // parsed_frames: VecDeque::new(), 66 | // } 67 | // } 68 | // } 69 | // 70 | // impl Stream for GrpcFrameFromHttpFramesStreamRequest { 71 | // type Item = crate::Result; 72 | // 73 | // fn poll_next( 74 | // mut self: Pin<&mut Self>, 75 | // cx: &mut Context<'_>, 76 | // ) -> Poll>> { 77 | // loop { 78 | // let parsed_frames = self.buf.next_frames()?.0; 79 | // 80 | // self.parsed_frames.extend(parsed_frames); 81 | // 82 | // if let Some(frame) = self.parsed_frames.pop_front() { 83 | // return Poll::Ready(Some(Ok(frame))); 84 | // } 85 | // 86 | // let part_opt = match self.http_stream_stream.poll_next_unpin(cx)? { 87 | // Poll::Pending => return Poll::Pending, 88 | // Poll::Ready(part_opt) => part_opt, 89 | // }; 90 | // let part = match part_opt { 91 | // None => { 92 | // self.buf.check_empty()?; 93 | // return Poll::Ready(None); 94 | // } 95 | // Some(part) => part, 96 | // }; 97 | // 98 | // match part { 99 | // // unexpected but OK 100 | // DataOrTrailers::Trailers(..) => (), 101 | // DataOrTrailers::Data(data, ..) => { 102 | // self.buf.enqueue(data); 103 | // } 104 | // } 105 | // } 106 | // } 107 | // } 108 | 109 | #[cfg(test)] 110 | mod test { 111 | use super::*; 112 | use bytes::BytesMut; 113 | 114 | /// Return frame len 115 | fn parse_grpc_frame_0(stream: &[u8]) -> result::Result> { 116 | let mut stream_copy = stream; 117 | let len = match parse_grpc_frame_header(&mut stream_copy)? { 118 | Some(len) => len, 119 | None => return Ok(None), 120 | }; 121 | 122 | let end = len as usize + GRPC_HEADER_LEN; 123 | if end > stream.len() { 124 | return Ok(None); 125 | } 126 | 127 | Ok(Some(len as usize)) 128 | } 129 | 130 | fn parse_grpc_frame_from_bytes(stream: &mut Bytes) -> result::Result> { 131 | if let Some(len) = parse_grpc_frame_0(&stream)? { 132 | let r = stream.slice(GRPC_HEADER_LEN..len + GRPC_HEADER_LEN); 133 | stream.advance(len + GRPC_HEADER_LEN); 134 | Ok(Some(r)) 135 | } else { 136 | Ok(None) 137 | } 138 | } 139 | 140 | fn parse_grpc_frames_from_bytes(stream: &mut Bytes) -> result::Result> { 141 | let mut r = Vec::new(); 142 | loop { 143 | match parse_grpc_frame_from_bytes(stream)? { 144 | Some(bytes) => { 145 | r.push(bytes); 146 | } 147 | None => { 148 | return Ok(r); 149 | } 150 | } 151 | } 152 | } 153 | 154 | // return message and size consumed 155 | fn parse_grpc_frame(stream: &[u8]) -> result::Result> { 156 | parse_grpc_frame_0(stream).map(|o| { 157 | o.map(|len| { 158 | ( 159 | &stream[GRPC_HEADER_LEN..len + GRPC_HEADER_LEN], 160 | len + GRPC_HEADER_LEN, 161 | ) 162 | }) 163 | }) 164 | } 165 | 166 | #[test] 167 | fn test_parse_grpc_frame() { 168 | assert_eq!(None, parse_grpc_frame(b"").unwrap()); 169 | assert_eq!(None, parse_grpc_frame(b"1").unwrap()); 170 | assert_eq!(None, parse_grpc_frame(b"14sc").unwrap()); 171 | assert_eq!( 172 | None, 173 | parse_grpc_frame(b"\x00\x00\x00\x00\x07\x0a\x05wo").unwrap() 174 | ); 175 | assert_eq!( 176 | Some((&b"\x0a\x05world"[..], 12)), 177 | parse_grpc_frame(b"\x00\x00\x00\x00\x07\x0a\x05world").unwrap() 178 | ); 179 | } 180 | 181 | #[test] 182 | fn test_parse_grpc_frames_from_bytes() { 183 | fn t(r: &[&[u8]], input: &[u8], trail: &[u8]) { 184 | let mut b = BytesMut::new(); 185 | b.extend_from_slice(input); 186 | b.extend_from_slice(trail); 187 | let mut b = b.freeze(); 188 | 189 | let r: Vec = r.into_iter().map(|&s| Bytes::copy_from_slice(s)).collect(); 190 | 191 | let rr = parse_grpc_frames_from_bytes(&mut b).unwrap(); 192 | assert_eq!(r, rr); 193 | assert_eq!(trail, b.as_ref()); 194 | } 195 | 196 | t(&[], &[], &[]); 197 | t(&[], &[], &b"1"[..]); 198 | t(&[], &[], &b"14sc"[..]); 199 | t( 200 | &[&b"\x0a\x05world"[..]], 201 | &b"\x00\x00\x00\x00\x07\x0a\x05world"[..], 202 | &[], 203 | ); 204 | t( 205 | &[&b"ab"[..], &b"cde"[..]], 206 | &b"\0\x00\x00\x00\x02ab\0\x00\x00\x00\x03cde"[..], 207 | &b"\x00"[..], 208 | ); 209 | } 210 | 211 | #[test] 212 | fn test_write_grpc_frame_cb() { 213 | let mut f = Vec::new(); 214 | write_grpc_frame_cb(&mut f, 0, |f| { 215 | Ok::<_, ()>(f.extend_from_slice(&[0x17, 0x19])) 216 | }) 217 | .unwrap(); 218 | assert_eq!(&b"\x00\x00\x00\x00\x02\x17\x19"[..], f.as_slice()); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /grpc/src/proto/grpc_frame_parser.rs: -------------------------------------------------------------------------------- 1 | use crate::bytesx::bytes_deque::BytesDeque; 2 | use crate::error; 3 | use crate::proto::grpc_frame::parse_grpc_frame_header; 4 | use crate::proto::grpc_frame::GRPC_HEADER_LEN; 5 | use crate::result; 6 | use bytes::Buf; 7 | use bytes::Bytes; 8 | use httpbis::BufGetBytes; 9 | 10 | #[derive(Default, Debug)] 11 | pub(crate) struct GrpcFrameParser { 12 | /// Next frame length; stripped prefix from the queue 13 | next_frame_len: Option, 14 | /// Unparsed bytes 15 | buffer: BytesDeque, 16 | } 17 | 18 | impl GrpcFrameParser { 19 | /// Add `Bytes` object to the queue. 20 | pub fn enqueue(&mut self, bytes: Bytes) { 21 | self.buffer.extend(bytes); 22 | } 23 | 24 | /// Try populating `next_frame_len` field from the queue. 25 | fn fill_next_frame_len(&mut self) -> result::Result> { 26 | if let None = self.next_frame_len { 27 | self.next_frame_len = parse_grpc_frame_header(&mut self.buffer)?; 28 | } 29 | Ok(self.next_frame_len) 30 | } 31 | 32 | /// Parse next frame if buffer has full frame. 33 | pub fn next_frame(&mut self) -> result::Result> { 34 | if let Some(len) = self.fill_next_frame_len()? { 35 | if self.buffer.remaining() >= len as usize { 36 | self.next_frame_len = None; 37 | return Ok(Some(( 38 | BufGetBytes::get_bytes(&mut self.buffer, len as usize), 39 | len as usize + GRPC_HEADER_LEN, 40 | ))); 41 | } 42 | } 43 | Ok(None) 44 | } 45 | 46 | /// Parse all frames from buffer. 47 | pub fn next_frames(&mut self) -> result::Result<(Vec, usize)> { 48 | let mut r = Vec::new(); 49 | let mut consumed = 0; 50 | while let Some((frame, frame_consumed)) = self.next_frame()? { 51 | r.push(frame); 52 | consumed += frame_consumed; 53 | } 54 | Ok((r, consumed)) 55 | } 56 | 57 | /// Buffered data is empty. 58 | pub fn is_empty(&self) -> bool { 59 | self.next_frame_len.is_none() && !self.buffer.has_remaining() 60 | } 61 | 62 | pub fn check_empty(&self) -> result::Result<()> { 63 | if !self.is_empty() { 64 | return Err(error::Error::Other("partial frame")); 65 | } 66 | Ok(()) 67 | } 68 | } 69 | 70 | #[cfg(test)] 71 | mod test { 72 | use super::*; 73 | use bytes::Bytes; 74 | 75 | #[test] 76 | fn test() { 77 | fn parse(data: &[u8]) -> result::Result> { 78 | let mut parser = GrpcFrameParser::default(); 79 | parser.enqueue(Bytes::copy_from_slice(data)); 80 | parser.next_frame() 81 | } 82 | 83 | assert_eq!(None, parse(b"").unwrap()); 84 | assert_eq!(None, parse(b"1").unwrap()); 85 | assert_eq!(None, parse(b"14sc").unwrap()); 86 | assert_eq!(None, parse(b"\x00\x00\x00\x00\x07\x0a\x05wo").unwrap()); 87 | assert_eq!( 88 | Some((Bytes::copy_from_slice(b"\x0a\x05world"), 12)), 89 | parse(b"\x00\x00\x00\x00\x07\x0a\x05world").unwrap() 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /grpc/src/proto/grpc_status.rs: -------------------------------------------------------------------------------- 1 | // copied from https://github.com/grpc/grpc/blob/master/include/grpc/impl/codegen/status.h 2 | /// gRPC status constants. 3 | #[allow(dead_code)] 4 | #[derive(Copy, Clone, Debug)] 5 | pub enum GrpcStatus { 6 | /* Not an error; returned on success */ 7 | Ok = 0, 8 | 9 | /* The operation was cancelled (typically by the caller). */ 10 | Cancelled = 1, 11 | 12 | /* Unknown error. An example of where this error may be returned is 13 | if a Status value received from another address space belongs to 14 | an error-space that is not known in this address space. Also 15 | errors raised by APIs that do not return enough error information 16 | may be converted to this error. */ 17 | Unknown = 2, 18 | 19 | /* Client specified an invalid argument. Note that this differs 20 | from FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments 21 | that are problematic regardless of the state of the system 22 | (e.g., a malformed file name). */ 23 | Argument = 3, 24 | 25 | /* Deadline expired before operation could complete. For operations 26 | that change the state of the system, this error may be returned 27 | even if the operation has completed successfully. For example, a 28 | successful response from a server could have been delayed long 29 | enough for the deadline to expire. */ 30 | DeadlineExceeded = 4, 31 | 32 | /* Some requested entity (e.g., file or directory) was not found. */ 33 | NotFound = 5, 34 | 35 | /* Some entity that we attempted to create (e.g., file or directory) 36 | already exists. */ 37 | AlreadyExists = 6, 38 | 39 | /* The caller does not have permission to execute the specified 40 | operation. PERMISSION_DENIED must not be used for rejections 41 | caused by exhausting some resource (use RESOURCE_EXHAUSTED 42 | instead for those errors). PERMISSION_DENIED must not be 43 | used if the caller can not be identified (use UNAUTHENTICATED 44 | instead for those errors). */ 45 | PermissionDenied = 7, 46 | 47 | /* The request does not have valid authentication credentials for the 48 | operation. */ 49 | Unauthenticated = 16, 50 | 51 | /* Some resource has been exhausted, perhaps a per-user quota, or 52 | perhaps the entire file system is out of space. */ 53 | ResourceExhausted = 8, 54 | 55 | /* Operation was rejected because the system is not in a state 56 | required for the operation's execution. For example, directory 57 | to be deleted may be non-empty, an rmdir operation is applied to 58 | a non-directory, etc. 59 | A litmus test that may help a service implementor in deciding 60 | between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE: 61 | (a) Use UNAVAILABLE if the client can retry just the failing call. 62 | (b) Use ABORTED if the client should retry at a higher-level 63 | (e.g., restarting a read-modify-write sequence). 64 | (c) Use FAILED_PRECONDITION if the client should not retry until 65 | the system state has been explicitly fixed. E.g., if an "rmdir" 66 | fails because the directory is non-empty, FAILED_PRECONDITION 67 | should be returned since the client should not retry unless 68 | they have first fixed up the directory by deleting files from it. 69 | (d) Use FAILED_PRECONDITION if the client performs conditional 70 | REST Get/Update/Delete on a resource and the resource on the 71 | server does not match the condition. E.g., conflicting 72 | read-modify-write on the same resource. */ 73 | FailedPrecondition = 9, 74 | 75 | /* The operation was aborted, typically due to a concurrency issue 76 | like sequencer check failures, transaction aborts, etc. 77 | See litmus test above for deciding between FAILED_PRECONDITION, 78 | ABORTED, and UNAVAILABLE. */ 79 | Aborted = 10, 80 | 81 | /* Operation was attempted past the valid range. E.g., seeking or 82 | reading past end of file. 83 | Unlike INVALID_ARGUMENT, this error indicates a problem that may 84 | be fixed if the system state changes. For example, a 32-bit file 85 | system will generate INVALID_ARGUMENT if asked to read at an 86 | offset that is not in the range [0,2^32-1], but it will generate 87 | OUT_OF_RANGE if asked to read from an offset past the current 88 | file size. 89 | There is a fair bit of overlap between FAILED_PRECONDITION and 90 | OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific 91 | error) when it applies so that callers who are iterating through 92 | a space can easily look for an OUT_OF_RANGE error to detect when 93 | they are done. */ 94 | OutOfRange = 11, 95 | 96 | /* Operation is not implemented or not supported/enabled in this service. */ 97 | Unimplemented = 12, 98 | 99 | /* Internal errors. Means some invariants expected by underlying 100 | system has been broken. If you see one of these errors, 101 | something is very broken. */ 102 | Internal = 13, 103 | 104 | /* The service is currently unavailable. This is a most likely a 105 | transient condition and may be corrected by retrying with 106 | a backoff. 107 | See litmus test above for deciding between FAILED_PRECONDITION, 108 | ABORTED, and UNAVAILABLE. */ 109 | Unavailable = 14, 110 | 111 | /* Unrecoverable data loss or corruption. */ 112 | DataLoss = 15, 113 | } 114 | 115 | const ALL_STATUSES: &[GrpcStatus] = &[ 116 | GrpcStatus::Ok, 117 | GrpcStatus::Cancelled, 118 | GrpcStatus::Unknown, 119 | GrpcStatus::Argument, 120 | GrpcStatus::DeadlineExceeded, 121 | GrpcStatus::NotFound, 122 | GrpcStatus::AlreadyExists, 123 | GrpcStatus::PermissionDenied, 124 | GrpcStatus::Unauthenticated, 125 | GrpcStatus::ResourceExhausted, 126 | GrpcStatus::FailedPrecondition, 127 | GrpcStatus::Aborted, 128 | GrpcStatus::OutOfRange, 129 | GrpcStatus::Unimplemented, 130 | GrpcStatus::Internal, 131 | GrpcStatus::Unavailable, 132 | GrpcStatus::DataLoss, 133 | ]; 134 | 135 | impl GrpcStatus { 136 | /// GrpcStatus as protocol numeric code 137 | pub fn code(&self) -> u32 { 138 | *self as u32 139 | } 140 | 141 | /// Find GrpcStatus enum variant by code 142 | pub fn from_code(code: u32) -> Option { 143 | ALL_STATUSES.into_iter().cloned().find(|s| s.code() == code) 144 | } 145 | 146 | /// Find GrpcStatus enum variant by code or return default value 147 | pub fn from_code_or_unknown(code: u32) -> GrpcStatus { 148 | GrpcStatus::from_code(code).unwrap_or(GrpcStatus::Unknown) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /grpc/src/proto/headers.rs: -------------------------------------------------------------------------------- 1 | use crate::proto::grpc_status::GrpcStatus; 2 | use crate::Metadata; 3 | use bytes::Bytes; 4 | use httpbis::Header; 5 | use httpbis::Headers; 6 | 7 | pub(crate) static HEADER_GRPC_STATUS: &'static str = "grpc-status"; 8 | pub(crate) static HEADER_GRPC_MESSAGE: &'static str = "grpc-message"; 9 | 10 | pub(crate) fn headers_500(grpc_status: GrpcStatus, message: String) -> Headers { 11 | Headers::from_vec(vec![ 12 | Header::new(":status", "500"), 13 | Header::new("content-type", "application/grpc"), 14 | Header::new(HEADER_GRPC_STATUS, format!("{}", grpc_status as i32)), 15 | Header::new(HEADER_GRPC_MESSAGE, message), 16 | ]) 17 | } 18 | 19 | pub(crate) fn headers_200(metadata: Metadata) -> Headers { 20 | let mut headers = Headers::from_vec(vec![ 21 | // TODO: do not allocate 22 | Header::new(":status", "200"), 23 | Header::new("content-type", "application/grpc"), 24 | Header::new(HEADER_GRPC_STATUS, "0"), 25 | ]); 26 | headers.extend(metadata.into_headers()); 27 | headers 28 | } 29 | 30 | /// Create HTTP response for gRPC error 31 | pub(crate) fn grpc_error_message(message: &str) -> httpbis::SimpleHttpMessage { 32 | let headers = Headers::from_vec(vec![ 33 | Header::new(":status", "200"), 34 | // TODO: alloc 35 | Header::new( 36 | HEADER_GRPC_STATUS, 37 | format!("{}", GrpcStatus::Internal.code()), 38 | ), 39 | Header::new(HEADER_GRPC_MESSAGE, message.to_owned()), 40 | ]); 41 | httpbis::SimpleHttpMessage { 42 | headers, 43 | body: Bytes::new(), 44 | } 45 | } 46 | 47 | // Trailers -> Status [Status-Message] *Custom-Metadata 48 | pub(crate) fn trailers( 49 | grpc_status: GrpcStatus, 50 | message: Option, 51 | metadata: Metadata, 52 | ) -> Headers { 53 | let mut headers = Headers::from_vec(vec![Header::new( 54 | HEADER_GRPC_STATUS, 55 | format!("{}", grpc_status as i32), 56 | )]); 57 | if let Some(message) = message { 58 | headers.add_header(Header::new(HEADER_GRPC_MESSAGE, message)); 59 | } 60 | headers.extend(metadata.into_headers()); 61 | headers 62 | } 63 | -------------------------------------------------------------------------------- /grpc/src/proto/metadata.rs: -------------------------------------------------------------------------------- 1 | use base64; 2 | 3 | use crate::chars::Chars; 4 | use bytes::Bytes; 5 | 6 | use httpbis::Header; 7 | use httpbis::Headers; 8 | 9 | /// Metadata key (basically, a header name). 10 | #[derive(Debug, Clone)] 11 | pub struct MetadataKey { 12 | name: Chars, 13 | } 14 | 15 | impl MetadataKey { 16 | pub fn from>(s: S) -> MetadataKey { 17 | let chars = s.into(); 18 | 19 | // TODO: assert ASCII 20 | assert!(!chars.is_empty()); 21 | 22 | MetadataKey { name: chars } 23 | } 24 | 25 | pub fn is_bin(&self) -> bool { 26 | self.name.ends_with("-bin") 27 | } 28 | 29 | pub fn into_chars(self) -> Chars { 30 | self.name 31 | } 32 | 33 | pub fn as_str(&self) -> &str { 34 | &self.name 35 | } 36 | } 37 | 38 | #[derive(Debug, Clone)] 39 | pub struct MetadataEntry { 40 | pub key: MetadataKey, 41 | pub value: Bytes, 42 | } 43 | 44 | #[derive(Debug)] 45 | pub enum MetadataDecodeError { 46 | Base64(base64::DecodeError), 47 | } 48 | 49 | impl From for MetadataDecodeError { 50 | fn from(decode_error: base64::DecodeError) -> Self { 51 | MetadataDecodeError::Base64(decode_error) 52 | } 53 | } 54 | 55 | impl MetadataEntry { 56 | fn into_header(self) -> Header { 57 | let is_bin = self.key.is_bin(); 58 | 59 | let value = match is_bin { 60 | true => Bytes::from(base64::encode(&self.value)), 61 | false => self.value, 62 | }; 63 | 64 | Header::new(self.key.name.into_inner(), value) 65 | } 66 | 67 | fn from_header(header: Header) -> Result, MetadataDecodeError> { 68 | if header.name().starts_with(":") { 69 | return Ok(None); 70 | } 71 | if header.name().starts_with("grpc-") { 72 | return Ok(None); 73 | } 74 | let key = MetadataKey { 75 | // TODO: return subbytes 76 | name: Chars::copy_from_str(header.name()), 77 | }; 78 | let value = match key.is_bin() { 79 | true => Bytes::from(base64::decode(&header.value)?), 80 | false => header.value.into(), 81 | }; 82 | Ok(Some(MetadataEntry { key, value })) 83 | } 84 | } 85 | 86 | /// Request or response metadata. 87 | #[derive(Default, Debug, Clone)] 88 | pub struct Metadata { 89 | pub entries: Vec, 90 | } 91 | 92 | impl Metadata { 93 | /// Empty metadata object. 94 | pub fn new() -> Metadata { 95 | Default::default() 96 | } 97 | 98 | /// Create metadata from HTTP/2 headers. 99 | pub fn from_headers(headers: Headers) -> Result { 100 | let mut r = Metadata::new(); 101 | for h in headers.iter() { 102 | // TODO: clone 103 | if let Some(e) = MetadataEntry::from_header(h.clone())? { 104 | r.entries.push(e); 105 | } 106 | } 107 | Ok(r) 108 | } 109 | 110 | /// Convert metadata into HTTP/2 headers. 111 | pub fn into_headers(self) -> Headers { 112 | Headers::from_vec( 113 | self.entries 114 | .into_iter() 115 | .map(MetadataEntry::into_header) 116 | .collect(), 117 | ) 118 | } 119 | 120 | /// Get metadata by key 121 | pub fn get<'a>(&'a self, name: &str) -> Option<&'a [u8]> { 122 | for e in &self.entries { 123 | if e.key.as_str() == name { 124 | return Some(&e.value[..]); 125 | } 126 | } 127 | None 128 | } 129 | 130 | /// Concatenate. 131 | pub fn extend(&mut self, extend: Metadata) { 132 | self.entries.extend(extend.entries); 133 | } 134 | 135 | /// Add metadata object. 136 | pub fn add(&mut self, key: MetadataKey, value: Bytes) { 137 | self.entries.push(MetadataEntry { key, value }); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /grpc/src/proto/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod grpc_frame; 2 | pub(crate) mod grpc_frame_parser; 3 | pub(crate) mod grpc_status; 4 | pub(crate) mod headers; 5 | pub(crate) mod metadata; 6 | -------------------------------------------------------------------------------- /grpc/src/req.rs: -------------------------------------------------------------------------------- 1 | use futures::future; 2 | use futures::stream; 3 | 4 | use futures::stream::Stream; 5 | use futures::stream::StreamExt; 6 | 7 | use crate::error::Error; 8 | use futures::channel::mpsc; 9 | 10 | use crate::futures_grpc::GrpcStream; 11 | use crate::proto::metadata::Metadata; 12 | 13 | /// gRPC request options. 14 | #[derive(Debug, Default, Clone)] 15 | pub struct RequestOptions { 16 | /// Request metadata. 17 | pub metadata: Metadata, 18 | /// Request is cacheable (not implemented). 19 | pub cachable: bool, 20 | } 21 | 22 | impl RequestOptions { 23 | /// Default request options. 24 | pub fn new() -> RequestOptions { 25 | Default::default() 26 | } 27 | } 28 | 29 | /// Excluding initial metadata which is passed separately 30 | pub struct StreamingRequest(pub GrpcStream); 31 | 32 | impl StreamingRequest { 33 | // constructors 34 | 35 | pub fn new(stream: S) -> StreamingRequest 36 | where 37 | S: Stream> + Send + 'static, 38 | { 39 | StreamingRequest(Box::pin(stream)) 40 | } 41 | 42 | pub fn once(item: T) -> StreamingRequest { 43 | StreamingRequest::new(stream::once(future::ok(item))) 44 | } 45 | 46 | pub fn iter(iter: I) -> StreamingRequest 47 | where 48 | I: IntoIterator, 49 | I::IntoIter: Send + 'static, 50 | { 51 | StreamingRequest::new(stream::iter(iter.into_iter()).map(Ok)) 52 | } 53 | 54 | pub fn mpsc() -> (StreamingRequestSender, StreamingRequest) { 55 | let (tx, rx) = mpsc::channel(0); 56 | let tx = StreamingRequestSender { sender: Some(tx) }; 57 | // TODO: handle drop of sender 58 | //let rx = StreamingRequest::new(rx.map_err(|_: mpsc::TryRecvError| error::Error::Other("sender died"))); 59 | let rx = StreamingRequest::new(rx.map(Ok)); 60 | (tx, rx) 61 | } 62 | 63 | pub fn single(item: T) -> StreamingRequest { 64 | StreamingRequest::new(stream::once(future::ok(item))) 65 | } 66 | 67 | pub fn empty() -> StreamingRequest { 68 | StreamingRequest::new(stream::empty()) 69 | } 70 | 71 | pub fn err(err: Error) -> StreamingRequest { 72 | StreamingRequest::new(stream::once(future::err(err))) 73 | } 74 | } 75 | 76 | pub struct StreamingRequestSender { 77 | sender: Option>, 78 | } 79 | 80 | /* 81 | impl Sink for StreamingRequestSender { 82 | type Error = crate::Error; 83 | 84 | fn start_send(&mut self, item: T) -> StartSend { 85 | match self.sender.as_mut() { 86 | // TODO: error 87 | Some(sender) => sender 88 | .start_send(item) 89 | .map_err(|_send_error| error::Error::Other("channel closed")), 90 | None => Err(error::Error::Other("sender closed")), 91 | } 92 | } 93 | 94 | fn poll_complete(&mut self) -> Poll<(), error::Error> { 95 | match self.sender.as_mut() { 96 | Some(sender) => sender 97 | .poll_complete() 98 | .map_err(|_send_error| error::Error::Other("channel closed")), 99 | None => Err(error::Error::Other("sender closed")), 100 | } 101 | } 102 | 103 | fn close(&mut self) -> Poll<(), error::Error> { 104 | // Close of sender doesn't end receiver stream 105 | self.sender.take(); 106 | Poll::Ready(Ok(())) 107 | } 108 | } 109 | */ 110 | 111 | impl Drop for StreamingRequestSender { 112 | fn drop(&mut self) { 113 | // TODO: cancel 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /grpc/src/result.rs: -------------------------------------------------------------------------------- 1 | use std::result; 2 | 3 | use crate::error::*; 4 | 5 | pub type Result = result::Result; 6 | -------------------------------------------------------------------------------- /grpc/src/rt.rs: -------------------------------------------------------------------------------- 1 | //! Functions used by generated code, but not exposed in `grpc`. 2 | 3 | pub use crate::server::method::MethodHandler; 4 | pub use crate::server::method::MethodHandlerBidi; 5 | pub use crate::server::method::MethodHandlerClientStreaming; 6 | pub use crate::server::method::MethodHandlerServerStreaming; 7 | pub use crate::server::method::MethodHandlerUnary; 8 | pub use crate::server::method::ServerMethod; 9 | 10 | pub use crate::method::GrpcStreaming; 11 | pub use crate::method::GrpcStreamingFlavor; 12 | pub use crate::method::MethodDescriptor; 13 | 14 | pub use crate::or_static::arc::ArcOrStatic; 15 | pub use crate::or_static::string::StringOrStatic; 16 | pub use crate::server::ServerServiceDefinition; 17 | -------------------------------------------------------------------------------- /grpc/src/server/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod method; 2 | pub(crate) mod req_handler; 3 | pub(crate) mod req_handler_unary; 4 | pub(crate) mod req_single; 5 | pub(crate) mod req_stream; 6 | pub(crate) mod resp_sink; 7 | pub(crate) mod resp_sink_untyped; 8 | pub(crate) mod resp_unary_sink; 9 | pub(crate) mod types; 10 | 11 | use std::sync::Arc; 12 | 13 | use httpbis; 14 | 15 | use result::Result; 16 | 17 | use crate::common::sink::SinkCommonUntyped; 18 | use crate::proto::grpc_status::GrpcStatus; 19 | use crate::proto::headers::grpc_error_message; 20 | use crate::result; 21 | use crate::server::method::ServerMethod; 22 | use crate::server::req_handler::ServerRequestUntyped; 23 | use crate::server::resp_sink_untyped::ServerResponseUntypedSink; 24 | use crate::Metadata; 25 | use httpbis::AnySocketAddr; 26 | 27 | pub struct ServerServiceDefinition { 28 | pub prefix: String, 29 | pub methods: Vec, 30 | } 31 | 32 | impl ServerServiceDefinition { 33 | pub fn new(prefix: &str, methods: Vec) -> ServerServiceDefinition { 34 | ServerServiceDefinition { 35 | prefix: prefix.to_owned(), 36 | methods: methods, 37 | } 38 | } 39 | 40 | pub fn find_method(&self, name: &str) -> Option<&ServerMethod> { 41 | self.methods.iter().filter(|m| m.name == name).next() 42 | } 43 | 44 | pub(crate) fn handle_method( 45 | &self, 46 | name: &str, 47 | req: ServerRequestUntyped, 48 | mut resp: ServerResponseUntypedSink, 49 | ) -> result::Result<()> { 50 | match self.find_method(name) { 51 | Some(method) => method.dispatch.start_request(req, resp), 52 | None => { 53 | resp.send_grpc_error(GrpcStatus::Unimplemented, "Unimplemented method".to_owned())?; 54 | Ok(()) 55 | } 56 | } 57 | } 58 | } 59 | 60 | /// gRPC server configuration. 61 | #[derive(Default, Debug, Clone)] 62 | pub struct ServerConf {} 63 | 64 | impl ServerConf { 65 | /// Default configuration. 66 | pub fn new() -> ServerConf { 67 | Default::default() 68 | } 69 | } 70 | 71 | /// Builder for gRPC server. 72 | pub struct ServerBuilder { 73 | /// HTTP/2 server builder. 74 | pub http: httpbis::ServerBuilder, 75 | conf: ServerConf, 76 | } 77 | 78 | impl ServerBuilder { 79 | /// New builder for no-TLS HTTP/2. 80 | pub fn new_plain() -> ServerBuilder { 81 | ServerBuilder::new() 82 | } 83 | } 84 | 85 | impl ServerBuilder { 86 | /// New builder for given TLS acceptor. 87 | pub fn new() -> ServerBuilder { 88 | ServerBuilder { 89 | http: httpbis::ServerBuilder::new(), 90 | conf: ServerConf::new(), 91 | } 92 | } 93 | 94 | /// New builder for unix socket HTTP/2 server. 95 | #[cfg(unix)] 96 | pub fn new_unix() -> ServerBuilder { 97 | ServerBuilder { 98 | http: httpbis::ServerBuilder::new(), 99 | conf: ServerConf::new(), 100 | } 101 | } 102 | 103 | /// Register service for this server. 104 | /// 105 | /// Service definition is typically in generated code. 106 | pub fn add_service(&mut self, def: ServerServiceDefinition) { 107 | self.http.service.set_service( 108 | &def.prefix.clone(), 109 | Arc::new(GrpcServerHandler { 110 | service_definition: Arc::new(def), 111 | }), 112 | ); 113 | } 114 | 115 | /// Build server. 116 | pub fn build(mut self) -> Result { 117 | self.http.conf.thread_name = Some( 118 | self.http 119 | .conf 120 | .thread_name 121 | .unwrap_or_else(|| "grpc-server-loop".to_owned()), 122 | ); 123 | 124 | Ok(Server { 125 | server: self.http.build()?, 126 | }) 127 | } 128 | } 129 | 130 | /// Running server. 131 | #[derive(Debug)] 132 | pub struct Server { 133 | server: httpbis::Server, 134 | } 135 | 136 | impl Server { 137 | pub fn local_addr(&self) -> &AnySocketAddr { 138 | self.server.local_addr() 139 | } 140 | 141 | pub fn is_alive(&self) -> bool { 142 | self.server.is_alive() 143 | } 144 | } 145 | 146 | /// Implementation of gRPC over http2 HttpService 147 | struct GrpcServerHandler { 148 | service_definition: Arc, 149 | } 150 | 151 | impl httpbis::ServerHandler for GrpcServerHandler { 152 | fn start_request( 153 | &self, 154 | req: httpbis::ServerRequest, 155 | mut resp: httpbis::ServerResponse, 156 | ) -> httpbis::Result<()> { 157 | // TODO: clone 158 | let path = req.headers.path().to_owned(); 159 | 160 | // TODO: clone 161 | let metadata = match Metadata::from_headers(req.headers.clone()) { 162 | Ok(metadata) => metadata, 163 | Err(_) => { 164 | resp.send_message(grpc_error_message("decode metadata error"))?; 165 | return Ok(()); 166 | } 167 | }; 168 | 169 | let req = ServerRequestUntyped { req }; 170 | 171 | resp.set_drop_callback(move || { 172 | Ok(grpc_error_message( 173 | "grpc server handler did not close the sender", 174 | )) 175 | }); 176 | 177 | let resp = ServerResponseUntypedSink::Headers(resp); 178 | 179 | // TODO: catch unwind 180 | self.service_definition.handle_method(&path, req, resp)?; 181 | 182 | Ok(()) 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /grpc/src/server/req_handler_unary.rs: -------------------------------------------------------------------------------- 1 | use crate::error; 2 | use crate::result; 3 | use crate::server::req_handler::ServerRequestStreamHandler; 4 | use crate::server::req_handler::ServerRequestUnaryHandler; 5 | use httpbis::IncreaseInWindow; 6 | use std::marker; 7 | 8 | pub(crate) struct RequestHandlerUnaryToStream 9 | where 10 | H: ServerRequestUnaryHandler, 11 | M: Send + 'static, 12 | { 13 | pub(crate) increase_in_window: IncreaseInWindow, 14 | pub(crate) handler: H, 15 | pub(crate) message: Option, 16 | pub(crate) _marker: marker::PhantomData, 17 | } 18 | 19 | impl ServerRequestStreamHandler for RequestHandlerUnaryToStream 20 | where 21 | H: ServerRequestUnaryHandler, 22 | M: Send + 'static, 23 | { 24 | fn grpc_message(&mut self, message: M, frame_size: usize) -> result::Result<()> { 25 | self.increase_in_window.data_frame_processed(frame_size); 26 | self.increase_in_window.increase_window_auto()?; 27 | if let Some(_) = self.message { 28 | return Err(error::Error::Other("more than one message in a stream")); 29 | } 30 | self.message = Some(message); 31 | Ok(()) 32 | } 33 | 34 | fn end_stream(&mut self) -> result::Result<()> { 35 | match self.message.take() { 36 | Some(message) => self.handler.grpc_message(message), 37 | None => Err(error::Error::Other("no message, end of stream")), 38 | } 39 | } 40 | 41 | fn buffer_processed(&mut self, buffered: usize) -> result::Result<()> { 42 | // TODO: overflow check 43 | self.increase_in_window 44 | .increase_window_auto_above(buffered as u32)?; 45 | Ok(()) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /grpc/src/server/req_single.rs: -------------------------------------------------------------------------------- 1 | use crate::Metadata; 2 | use std::mem; 3 | use tokio::runtime::Handle; 4 | 5 | /// Unary request. 6 | /// 7 | /// This object is passed to unary request handlers. 8 | pub struct ServerRequestSingle<'a, Req> { 9 | pub(crate) loop_handle: &'a Handle, 10 | /// Request metadata. 11 | pub metadata: Metadata, 12 | /// The message. 13 | pub message: Req, 14 | } 15 | 16 | impl<'a, Req> ServerRequestSingle<'a, Req> { 17 | pub fn loop_handle(&self) -> Handle { 18 | self.loop_handle.clone() 19 | } 20 | 21 | // Return contained message and replace it with `Default::default` 22 | pub fn take_message(&mut self) -> Req 23 | where 24 | Req: Default, 25 | { 26 | mem::replace(&mut self.message, Default::default()) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /grpc/src/server/req_stream.rs: -------------------------------------------------------------------------------- 1 | use futures::channel::mpsc; 2 | use futures::stream::Stream; 3 | use futures::stream::StreamExt; 4 | 5 | use crate::result; 6 | 7 | use crate::server::req_handler::ServerRequestStreamHandler; 8 | use futures::task::Context; 9 | use httpbis::IncreaseInWindow; 10 | use std::convert::TryInto; 11 | use std::pin::Pin; 12 | use std::task::Poll; 13 | 14 | pub(crate) enum HandlerToStream { 15 | Message(Req, u32), 16 | EndStream, 17 | Error(crate::Error), 18 | BufferProcessed(usize), 19 | } 20 | 21 | /// Easy to use request stream object. 22 | /// 23 | /// This object implements [`Stream`]. 24 | /// 25 | /// This object increases incoming window after each message received. 26 | pub struct ServerRequestStream 27 | where 28 | Req: Send + 'static, 29 | { 30 | pub(crate) req: mpsc::UnboundedReceiver>, 31 | pub(crate) increase_in_window: IncreaseInWindow, 32 | } 33 | 34 | pub(crate) struct ServerRequestStreamSenderHandler { 35 | pub(crate) sender: mpsc::UnboundedSender>, 36 | } 37 | 38 | impl ServerRequestStreamSenderHandler { 39 | fn send(&mut self, message: HandlerToStream) -> result::Result<()> { 40 | // TODO: error 41 | Ok(self 42 | .sender 43 | .unbounded_send(message) 44 | .map_err(|_| crate::Error::Other("xxx"))?) 45 | } 46 | } 47 | 48 | impl ServerRequestStreamHandler 49 | for ServerRequestStreamSenderHandler 50 | { 51 | fn grpc_message(&mut self, message: Req, frame_size: usize) -> result::Result<()> { 52 | // TODO: unwrap 53 | self.send(HandlerToStream::Message( 54 | message, 55 | frame_size.try_into().unwrap(), 56 | )) 57 | } 58 | 59 | fn end_stream(&mut self) -> result::Result<()> { 60 | self.send(HandlerToStream::EndStream) 61 | } 62 | 63 | fn error(&mut self, error: crate::Error) -> result::Result<()> { 64 | self.send(HandlerToStream::Error(error)) 65 | } 66 | 67 | fn buffer_processed(&mut self, buffered: usize) -> result::Result<()> { 68 | self.send(HandlerToStream::BufferProcessed(buffered)) 69 | } 70 | } 71 | 72 | impl Stream for ServerRequestStream { 73 | type Item = crate::Result; 74 | 75 | fn poll_next( 76 | mut self: Pin<&mut Self>, 77 | cx: &mut Context<'_>, 78 | ) -> Poll>> { 79 | loop { 80 | // TODO: error 81 | let item = match self.req.poll_next_unpin(cx) { 82 | Poll::Ready(Some(r)) => r, 83 | Poll::Ready(None) => { 84 | return Poll::Ready(Some(Err(crate::Error::Other("unexpected EOF")))) 85 | } 86 | Poll::Pending => return Poll::Pending, 87 | }; 88 | 89 | match item { 90 | HandlerToStream::Message(req, frame_size) => { 91 | // TODO: increase on next poll 92 | self.increase_in_window 93 | .data_frame_processed(frame_size as usize); 94 | self.increase_in_window.increase_window_auto()?; 95 | return Poll::Ready(Some(Ok(req))); 96 | } 97 | HandlerToStream::Error(error) => { 98 | return Poll::Ready(Some(Err(error))); 99 | } 100 | HandlerToStream::BufferProcessed(buffered) => { 101 | // TODO: overflow 102 | self.increase_in_window 103 | .increase_window_auto_above(buffered as u32)?; 104 | continue; 105 | } 106 | HandlerToStream::EndStream => { 107 | return Poll::Ready(None); 108 | } 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /grpc/src/server/resp_sink.rs: -------------------------------------------------------------------------------- 1 | use crate::common::sink::SinkCommon; 2 | 3 | use crate::proto::grpc_status::GrpcStatus; 4 | use crate::proto::metadata::Metadata; 5 | use crate::result; 6 | use crate::server::types::ServerTypes; 7 | use httpbis; 8 | 9 | use futures::future; 10 | use futures::stream; 11 | use futures::task::Context; 12 | use std::task::Poll; 13 | use tokio::runtime::Handle; 14 | 15 | /// Sink for server gRPC response. 16 | pub struct ServerResponseSink { 17 | pub(crate) common: SinkCommon, 18 | } 19 | 20 | impl ServerResponseSink { 21 | /// This method returns [`Poll::Ready`] when the sink is available to accept data 22 | /// otherwise subscribe current task which will be notified when this 23 | /// stream is ready to accept data. 24 | /// 25 | /// Higher level wrapper (future) is [`ready`](ServerResponseSink::ready). 26 | pub fn poll(&mut self, cx: &mut Context<'_>) -> Poll> { 27 | self.common.poll(cx) 28 | } 29 | 30 | /// A future is resolved when the sink is available to receive more data. 31 | pub async fn ready(&mut self) -> Result<(), httpbis::Error> { 32 | future::poll_fn(|cx| self.poll(cx)).await 33 | } 34 | 35 | /// Send response metadata. 36 | /// 37 | /// This function returns error if metadata is already sent. 38 | pub fn send_metadata(&mut self, metadata: Metadata) -> result::Result<()> { 39 | self.common.sink.send_metadata(metadata)?; 40 | Ok(()) 41 | } 42 | 43 | /// Send response data. 44 | /// 45 | /// This function does not block, but if send window is not available, 46 | /// data is queued. 47 | /// 48 | /// [`ready`](ServerResponseSink::ready) function can be used to wait for windows availability. 49 | pub fn send_data(&mut self, message: Resp) -> result::Result<()> { 50 | self.common.send_data(message) 51 | } 52 | 53 | /// Send trailers after the response. 54 | /// 55 | /// This function must be called after data is sent, otherwise the response stream is reset. 56 | pub fn send_trailers(&mut self, metadata: Metadata) -> result::Result<()> { 57 | self.common.sink.send_trailers(metadata)?; 58 | Ok(()) 59 | } 60 | 61 | /// Send error. 62 | pub fn send_grpc_error(&mut self, status: GrpcStatus, message: String) -> result::Result<()> { 63 | self.common.sink.send_grpc_error(status, message)?; 64 | Ok(()) 65 | } 66 | 67 | pub fn pump_from(self, handle: &Handle, stream: S) 68 | where 69 | S: stream::Stream> + Unpin + Send + 'static, 70 | { 71 | todo!() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /grpc/src/server/resp_sink_untyped.rs: -------------------------------------------------------------------------------- 1 | use std::task::Poll; 2 | 3 | use crate::common::sink::MessageToBeSerialized; 4 | use crate::common::sink::SinkUntyped; 5 | 6 | use crate::proto::grpc_status::GrpcStatus; 7 | use crate::proto::headers::headers_200; 8 | use crate::proto::headers::headers_500; 9 | use crate::proto::headers::trailers; 10 | use crate::result; 11 | use crate::Metadata; 12 | use futures::task::Context; 13 | use httpbis::ServerResponse; 14 | use httpbis::SinkAfterHeaders; 15 | use httpbis::SinkAfterHeadersBox; 16 | use std::mem; 17 | 18 | pub(crate) enum ServerResponseUntypedSink { 19 | Headers(ServerResponse), 20 | AfterHeaders(SinkAfterHeadersBox), 21 | Done, 22 | } 23 | 24 | impl SinkUntyped for ServerResponseUntypedSink { 25 | fn poll(&mut self, cx: &mut Context<'_>) -> Poll> { 26 | match self { 27 | ServerResponseUntypedSink::Headers(_) => Poll::Ready(Ok(())), 28 | ServerResponseUntypedSink::AfterHeaders(x) => x.poll(cx), 29 | ServerResponseUntypedSink::Done => Poll::Ready(Ok(())), 30 | } 31 | } 32 | 33 | fn send_message(&mut self, message: &dyn MessageToBeSerialized) -> result::Result<()> { 34 | if let ServerResponseUntypedSink::Headers(..) = self { 35 | self.send_metadata(Metadata::new())?; 36 | } 37 | match self { 38 | ServerResponseUntypedSink::Headers(_) => unreachable!(), 39 | ServerResponseUntypedSink::AfterHeaders(r) => { 40 | Ok(r.send_data(message.serialize_to_grpc_frame()?)?) 41 | } 42 | ServerResponseUntypedSink::Done => Err(crate::Error::Other("done")), 43 | } 44 | } 45 | } 46 | 47 | impl ServerResponseUntypedSink { 48 | pub fn send_metadata(&mut self, metadata: Metadata) -> Result<(), crate::Error> { 49 | match mem::replace(self, ServerResponseUntypedSink::Done) { 50 | ServerResponseUntypedSink::Headers(resp) => { 51 | *self = ServerResponseUntypedSink::AfterHeaders( 52 | resp.send_headers(headers_200(metadata))?, 53 | ); 54 | Ok(()) 55 | } 56 | s @ ServerResponseUntypedSink::AfterHeaders(_) 57 | | s @ ServerResponseUntypedSink::Done => { 58 | *self = s; 59 | Err(crate::Error::Other("after headers")) 60 | } 61 | } 62 | } 63 | 64 | pub fn send_trailers(&mut self, metadata: Metadata) -> Result<(), crate::Error> { 65 | match mem::replace(self, ServerResponseUntypedSink::Done) { 66 | ServerResponseUntypedSink::Headers(_) => Err(crate::Error::Other("headers")), 67 | ServerResponseUntypedSink::AfterHeaders(mut x) => { 68 | Ok(x.send_trailers(trailers(GrpcStatus::Ok, None, metadata))?) 69 | } 70 | ServerResponseUntypedSink::Done => Err(crate::Error::Other("done")), 71 | } 72 | } 73 | 74 | pub fn send_grpc_error( 75 | &mut self, 76 | grpc_status: GrpcStatus, 77 | message: String, 78 | ) -> Result<(), crate::Error> { 79 | // TODO: if headers sent, then ... 80 | let headers = headers_500(grpc_status, message.to_owned()); 81 | 82 | match mem::replace(self, ServerResponseUntypedSink::Done) { 83 | ServerResponseUntypedSink::Headers(resp) => { 84 | resp.send_headers(headers)?; 85 | Ok(()) 86 | } 87 | ServerResponseUntypedSink::AfterHeaders(mut resp) => { 88 | resp.send_trailers(headers)?; 89 | Ok(()) 90 | } 91 | ServerResponseUntypedSink::Done => Err(crate::Error::Other("Already closed")), 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /grpc/src/server/resp_unary_sink.rs: -------------------------------------------------------------------------------- 1 | use crate::result; 2 | use crate::server::resp_sink::ServerResponseSink; 3 | use crate::GrpcStatus; 4 | use crate::Metadata; 5 | 6 | /// A sink for single message (for unary request). 7 | pub struct ServerResponseUnarySink { 8 | pub(crate) sink: ServerResponseSink, 9 | } 10 | 11 | impl ServerResponseUnarySink { 12 | /// Send response header metadata. 13 | /// 14 | /// This operation can be only called before data sent. 15 | pub fn send_metadata(&mut self, metadata: Metadata) -> result::Result<()> { 16 | self.sink.send_metadata(metadata) 17 | } 18 | 19 | /// Send the response with trailers metadata. 20 | pub fn finish_with_trailers(mut self, resp: Resp, metadata: Metadata) -> result::Result<()> { 21 | self.sink.send_data(resp)?; 22 | self.sink.send_trailers(metadata)?; 23 | Ok(()) 24 | } 25 | 26 | /// Send the response. 27 | pub fn finish(self, resp: Resp) -> result::Result<()> { 28 | self.finish_with_trailers(resp, Metadata::new()) 29 | } 30 | 31 | /// Send error. 32 | pub fn send_grpc_error(mut self, status: GrpcStatus, message: String) -> result::Result<()> { 33 | self.sink.send_grpc_error(status, message) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /grpc/src/server/types.rs: -------------------------------------------------------------------------------- 1 | use crate::common::types::Types; 2 | use crate::server::resp_sink_untyped::ServerResponseUntypedSink; 3 | 4 | pub(crate) struct ServerTypes; 5 | 6 | impl Types for ServerTypes { 7 | type SinkUntyped = ServerResponseUntypedSink; 8 | } 9 | 10 | unsafe impl Sync for ServerTypes {} 11 | -------------------------------------------------------------------------------- /grpc/src/stream_item.rs: -------------------------------------------------------------------------------- 1 | use futures::future; 2 | use futures::future::FutureExt; 3 | use futures::future::TryFutureExt; 4 | use futures::stream; 5 | use futures::stream::Stream; 6 | use futures::stream::StreamExt; 7 | use futures::stream::TryStreamExt; 8 | 9 | use crate::futures_grpc::*; 10 | 11 | use crate::error::Error; 12 | 13 | use crate::proto::metadata::Metadata; 14 | use crate::result; 15 | use std::future::Future; 16 | 17 | /// Either stream item or trailing metadata. 18 | pub enum ItemOrMetadata { 19 | Item(T), 20 | TrailingMetadata(Metadata), // must be the last item in the stream 21 | } 22 | 23 | /// Sequence of items with optional trailing metadata 24 | /// Useful as part of GrpcStreamingResult 25 | pub struct GrpcStreamWithTrailingMetadata( 26 | /// Stream of items followed by optional trailing metadata 27 | pub GrpcStream>, 28 | ); 29 | 30 | impl GrpcStreamWithTrailingMetadata { 31 | // constructors 32 | 33 | /// Box a stream 34 | pub fn new(stream: S) -> GrpcStreamWithTrailingMetadata 35 | where 36 | S: Stream>> + Send + 'static, 37 | { 38 | GrpcStreamWithTrailingMetadata(Box::pin(stream)) 39 | } 40 | 41 | /// Stream of items with no trailing metadata 42 | pub fn stream(stream: S) -> GrpcStreamWithTrailingMetadata 43 | where 44 | S: Stream> + Send + 'static, 45 | { 46 | GrpcStreamWithTrailingMetadata::new(stream.map_ok(ItemOrMetadata::Item)) 47 | } 48 | 49 | /// Stream of items with no trailing metadata 50 | pub fn stream_with_trailing_metadata( 51 | stream: S, 52 | trailing: F, 53 | ) -> GrpcStreamWithTrailingMetadata 54 | where 55 | S: Stream> + Send + 'static, 56 | F: Future> + Send + 'static, 57 | { 58 | let stream = stream.map_ok(ItemOrMetadata::Item); 59 | let trailing = trailing 60 | .map_ok(ItemOrMetadata::TrailingMetadata) 61 | .into_stream(); 62 | GrpcStreamWithTrailingMetadata::new(stream.chain(trailing)) 63 | } 64 | 65 | /// Single element stream with no trailing metadata 66 | pub fn once(item: T) -> GrpcStreamWithTrailingMetadata { 67 | GrpcStreamWithTrailingMetadata::stream(stream::once(future::ok(item))) 68 | } 69 | 70 | /// Single element stream with trailing metadata 71 | pub fn once_with_trailing_metadata( 72 | item: T, 73 | metadata: Metadata, 74 | ) -> GrpcStreamWithTrailingMetadata { 75 | GrpcStreamWithTrailingMetadata::new( 76 | stream::iter( 77 | vec![ 78 | ItemOrMetadata::Item(item), 79 | ItemOrMetadata::TrailingMetadata(metadata), 80 | ] 81 | .into_iter(), 82 | ) 83 | .map(Ok), 84 | ) 85 | } 86 | 87 | /// Create a result from iterator, no metadata 88 | pub fn iter(iter: I) -> GrpcStreamWithTrailingMetadata 89 | where 90 | I: IntoIterator, 91 | I::IntoIter: Send + 'static, 92 | { 93 | GrpcStreamWithTrailingMetadata::stream(stream::iter(iter.into_iter()).map(Ok)) 94 | } 95 | 96 | /// Create an empty stream 97 | pub fn empty() -> GrpcStreamWithTrailingMetadata { 98 | GrpcStreamWithTrailingMetadata::new(stream::empty()) 99 | } 100 | 101 | /// Create an error 102 | pub fn err(err: Error) -> GrpcStreamWithTrailingMetadata { 103 | GrpcStreamWithTrailingMetadata::new(stream::once(future::err(err))) 104 | } 105 | 106 | // getters 107 | 108 | /// Apply a function to transform item stream 109 | fn map_stream(self, f: F) -> GrpcStreamWithTrailingMetadata 110 | where 111 | U: Send + 'static, 112 | F: FnOnce(GrpcStream>) -> GrpcStream> + Send + 'static, 113 | { 114 | GrpcStreamWithTrailingMetadata::new(Box::new(f(self.0))) 115 | } 116 | 117 | /// Transform items of the stream, keep metadata 118 | pub fn map_items(self, mut f: F) -> GrpcStreamWithTrailingMetadata 119 | where 120 | U: Send + 'static, 121 | F: FnMut(T) -> U + Send + 'static, 122 | { 123 | self.map_stream(move |stream| { 124 | Box::pin(stream.map_ok(move |item| match item { 125 | ItemOrMetadata::Item(i) => ItemOrMetadata::Item(f(i)), 126 | ItemOrMetadata::TrailingMetadata(m) => ItemOrMetadata::TrailingMetadata(m), 127 | })) 128 | }) 129 | } 130 | 131 | /// Apply `and_then` operation to items, preserving trailing metadata 132 | pub fn and_then_items(self, mut f: F) -> GrpcStreamWithTrailingMetadata 133 | where 134 | U: Send + 'static, 135 | F: FnMut(T) -> result::Result + Send + 'static, 136 | { 137 | self.map_stream(move |stream| { 138 | Box::pin(stream.and_then(move |item| { 139 | future::ready(match item { 140 | ItemOrMetadata::Item(i) => f(i).map(ItemOrMetadata::Item), 141 | ItemOrMetadata::TrailingMetadata(m) => Ok(ItemOrMetadata::TrailingMetadata(m)), 142 | }) 143 | })) 144 | }) 145 | } 146 | 147 | /// Apply `then` operation to items, preserving trailing metadata 148 | pub fn then_items(self, mut f: F) -> GrpcStreamWithTrailingMetadata 149 | where 150 | T: Send + 'static, 151 | F: FnMut(Result) -> Result + Send + 'static, 152 | { 153 | GrpcStreamWithTrailingMetadata::new(self.0.then(move |result| { 154 | future::ready(match result { 155 | Ok(item) => match item { 156 | ItemOrMetadata::Item(i) => match f(Ok(i)) { 157 | Ok(returned_item) => Ok(ItemOrMetadata::Item(returned_item)), 158 | Err(e) => Err(e), 159 | }, 160 | ItemOrMetadata::TrailingMetadata(m) => Ok(ItemOrMetadata::TrailingMetadata(m)), 161 | }, 162 | Err(t) => match f(Err(t)) { 163 | Ok(returned_item) => Ok(ItemOrMetadata::Item(returned_item)), 164 | Err(e) => Err(e), 165 | }, 166 | }) 167 | })) 168 | } 169 | 170 | /// Return raw futures `Stream` without trailing metadata 171 | pub fn drop_metadata(self) -> GrpcStream { 172 | Box::pin(self.0.try_filter_map(|item| { 173 | future::ok(match item { 174 | ItemOrMetadata::Item(t) => Some(t), 175 | ItemOrMetadata::TrailingMetadata(_) => None, 176 | }) 177 | })) 178 | } 179 | 180 | /// Collect all elements to a stream 181 | pub fn collect(self) -> GrpcFuture> { 182 | Box::pin(self.drop_metadata().try_collect()) 183 | } 184 | 185 | /// Collect all elements and trailing metadata 186 | pub fn collect_with_metadata(self) -> GrpcFuture<(Vec, Metadata)> { 187 | Box::pin(self.0.try_fold( 188 | (Vec::new(), Metadata::new()), 189 | |(mut vec, mut metadata), item| { 190 | // Could also check that elements returned in proper order 191 | match item { 192 | ItemOrMetadata::Item(r) => vec.push(r), 193 | ItemOrMetadata::TrailingMetadata(next) => metadata.extend(next), 194 | } 195 | future::ok::<_, Error>((vec, metadata)) 196 | }, 197 | )) 198 | } 199 | 200 | /// Collect stream into single element future. 201 | /// It is an error if stream has no result items or more than one result item. 202 | pub fn single_with_metadata(self) -> GrpcFuture<(T, Metadata)> { 203 | Box::pin(self.collect_with_metadata().and_then(|(mut v, trailing)| { 204 | future::ready({ 205 | if v.is_empty() { 206 | Err(Error::Other("no result")) 207 | } else if v.len() > 1 { 208 | Err(Error::Other("more than one result")) 209 | } else { 210 | Ok((v.swap_remove(0), trailing)) 211 | } 212 | }) 213 | })) 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /grpc/tests/client.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | 4 | mod test_misc; 5 | 6 | use grpc::rt::GrpcStreaming; 7 | use grpc::*; 8 | 9 | use futures::executor; 10 | use test_misc::*; 11 | 12 | #[test] 13 | fn server_is_not_running() { 14 | init_logger(); 15 | 16 | let client = ClientBuilder::new(BIND_HOST, 2).build().unwrap(); 17 | 18 | // TODO: https://github.com/tokio-rs/tokio-core/issues/12 19 | if false { 20 | let result = executor::block_on( 21 | client 22 | .call_unary( 23 | RequestOptions::new(), 24 | "aa".to_owned(), 25 | string_string_method("/does/not/matter", GrpcStreaming::Unary), 26 | ) 27 | .join_metadata_result(), 28 | ); 29 | assert!(result.is_err(), "{:?}", result); 30 | } 31 | } 32 | 33 | #[cfg(unix)] 34 | #[test] 35 | fn server_is_not_running_unix() { 36 | init_logger(); 37 | let client = ClientBuilder::new_unix("/tmp/grpc_rust_test") 38 | .build() 39 | .unwrap(); 40 | 41 | // TODO: https://github.com/tokio-rs/tokio-core/issues/12 42 | if false { 43 | let result = executor::block_on( 44 | client 45 | .call_unary( 46 | RequestOptions::new(), 47 | "aa".to_owned(), 48 | string_string_method("/does/not/matter", GrpcStreaming::Unary), 49 | ) 50 | .join_metadata_result(), 51 | ); 52 | assert!(result.is_err(), "{:?}", result); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /grpc/tests/server.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | 4 | mod test_misc; 5 | 6 | use grpc::rt::*; 7 | use grpc::*; 8 | 9 | use futures::executor; 10 | use test_misc::*; 11 | 12 | fn echo_fn( 13 | req: ServerRequestSingle, 14 | resp: ServerResponseUnarySink, 15 | ) -> grpc::Result<()> { 16 | resp.finish(req.message) 17 | } 18 | 19 | fn reverse_fn( 20 | req: ServerRequestSingle, 21 | resp: ServerResponseUnarySink, 22 | ) -> grpc::Result<()> { 23 | resp.finish(req.message.chars().rev().collect()) 24 | } 25 | 26 | #[test] 27 | fn multiple_services() { 28 | init_logger(); 29 | 30 | let mut server = ServerBuilder::new_plain(); 31 | server.http.set_port(0); 32 | 33 | let echo = string_string_method("/foo/echo", GrpcStreaming::Unary); 34 | let reverse = string_string_method("/bar/reverse", GrpcStreaming::Unary); 35 | 36 | server.add_service(ServerServiceDefinition::new( 37 | "/foo", 38 | vec![ServerMethod::new( 39 | echo.clone(), 40 | MethodHandlerUnary::new(echo_fn), 41 | )], 42 | )); 43 | 44 | server.add_service(ServerServiceDefinition::new( 45 | "/bar", 46 | vec![ServerMethod::new( 47 | reverse.clone(), 48 | MethodHandlerUnary::new(reverse_fn), 49 | )], 50 | )); 51 | 52 | let server = server.build().expect("server"); 53 | 54 | let port = server.local_addr().port().expect("port"); 55 | 56 | let client = ClientBuilder::new(BIND_HOST, port).build().expect("client"); 57 | 58 | assert_eq!( 59 | "abc".to_owned(), 60 | executor::block_on( 61 | client 62 | .call_unary(RequestOptions::new(), "abc".to_owned(), echo) 63 | .drop_metadata() 64 | ) 65 | .unwrap() 66 | ); 67 | 68 | assert_eq!( 69 | "zyx".to_owned(), 70 | executor::block_on( 71 | client 72 | .call_unary(RequestOptions::new(), "xyz".to_owned(), reverse) 73 | .drop_metadata() 74 | ) 75 | .unwrap() 76 | ); 77 | } 78 | 79 | #[cfg(unix)] 80 | #[test] 81 | fn single_service_unix() { 82 | init_logger(); 83 | 84 | let test_socket_address = "/tmp/grpc_rust_single_service_unix"; 85 | let mut server = ServerBuilder::new_plain(); 86 | server 87 | .http 88 | .set_unix_addr(test_socket_address.to_owned()) 89 | .unwrap(); 90 | 91 | let echo = string_string_method("/foo/echo", GrpcStreaming::Unary); 92 | let reverse = string_string_method("/bar/reverse", GrpcStreaming::Unary); 93 | 94 | server.add_service(ServerServiceDefinition::new( 95 | "/foo", 96 | vec![ServerMethod::new( 97 | echo.clone(), 98 | MethodHandlerUnary::new(echo_fn), 99 | )], 100 | )); 101 | 102 | server.add_service(ServerServiceDefinition::new( 103 | "/bar", 104 | vec![ServerMethod::new( 105 | reverse.clone(), 106 | MethodHandlerUnary::new(reverse_fn), 107 | )], 108 | )); 109 | 110 | let _server = server.build().expect("server"); 111 | 112 | let client = ClientBuilder::new_unix(test_socket_address) 113 | .build() 114 | .expect("client"); 115 | 116 | assert_eq!( 117 | "abc".to_owned(), 118 | executor::block_on( 119 | client 120 | .call_unary(RequestOptions::new(), "abc".to_owned(), echo) 121 | .drop_metadata() 122 | ) 123 | .unwrap() 124 | ); 125 | 126 | assert_eq!( 127 | "zyx".to_owned(), 128 | executor::block_on( 129 | client 130 | .call_unary(RequestOptions::new(), "xyz".to_owned(), reverse) 131 | .drop_metadata() 132 | ) 133 | .unwrap() 134 | ); 135 | } 136 | -------------------------------------------------------------------------------- /grpc/tests/test_misc/mod.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | mod stream_thread_spawn_iter; 4 | mod test_sync; 5 | 6 | use grpc::for_test::*; 7 | use grpc::rt::*; 8 | 9 | pub use self::stream_thread_spawn_iter::stream_thread_spawn_iter; 10 | pub use self::test_sync::TestSync; 11 | 12 | use log_ndc_env_logger; 13 | use std::sync::Once; 14 | 15 | pub fn string_string_method( 16 | name: &str, 17 | streaming: GrpcStreaming, 18 | ) -> ArcOrStatic> { 19 | ArcOrStatic::Arc(Arc::new(MethodDescriptor { 20 | name: name.into(), 21 | streaming, 22 | req_marshaller: ArcOrStatic::Static(&MarshallerString), 23 | resp_marshaller: ArcOrStatic::Static(&MarshallerString), 24 | })) 25 | } 26 | 27 | // Bind on IPv4 because IPv6 is broken on travis 28 | pub const BIND_HOST: &str = "127.0.0.1"; 29 | 30 | pub fn init_logger() { 31 | static ONCE: Once = Once::new(); 32 | ONCE.call_once(|| { 33 | log_ndc_env_logger::init(); 34 | }); 35 | } 36 | -------------------------------------------------------------------------------- /grpc/tests/test_misc/stream_thread_spawn_iter.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use std::thread; 4 | 5 | use futures; 6 | use futures::stream::Stream; 7 | use futures::stream::StreamExt; 8 | use std::pin::Pin; 9 | 10 | /// Spawn a thread with a function which returns an iterator. 11 | /// Resulting iterator elements will be emitted as Stream. 12 | pub fn stream_thread_spawn_iter( 13 | f: F, 14 | ) -> Pin> + Send>> 15 | where 16 | I: Iterator, 17 | F: FnOnce() -> I, 18 | F: Send + 'static, 19 | E: Send + 'static, 20 | I::Item: Send + 'static, 21 | { 22 | let (sender, receiver) = futures::channel::mpsc::unbounded(); 23 | thread::spawn(move || { 24 | for item in f() { 25 | sender.unbounded_send(item).expect("send"); 26 | } 27 | }); 28 | 29 | let receiver = receiver.map(Ok); 30 | 31 | Box::pin(receiver) 32 | } 33 | -------------------------------------------------------------------------------- /grpc/tests/test_misc/test_sync.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use std::sync::Condvar; 4 | use std::sync::Mutex; 5 | use std::thread; 6 | 7 | /// Super duper utility to test multithreaded apps. 8 | pub struct TestSync { 9 | mutex: Mutex, 10 | condvar: Condvar, 11 | } 12 | 13 | impl TestSync { 14 | pub fn new() -> TestSync { 15 | TestSync { 16 | mutex: Mutex::new(0), 17 | condvar: Condvar::new(), 18 | } 19 | } 20 | 21 | /// Wait for requested value to appear 22 | /// and increment contained value. 23 | pub fn take(&self, wait_for: u32) { 24 | let mut guard = self.mutex.lock().expect("mutex poisoned"); 25 | loop { 26 | if *guard == wait_for { 27 | trace!( 28 | "take {} from thread {}", 29 | wait_for, 30 | thread::current().name().unwrap_or("?") 31 | ); 32 | *guard += 1; 33 | self.condvar.notify_all(); 34 | return; 35 | } 36 | 37 | // otherwise we would stuck here forever 38 | assert!( 39 | wait_for > *guard, 40 | "wait_for = {}, *guard = {}", 41 | wait_for, 42 | *guard 43 | ); 44 | 45 | guard = self.condvar.wait(guard).expect("wait"); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /interop/.gitignore: -------------------------------------------------------------------------------- 1 | go-grpc-interop-* 2 | -------------------------------------------------------------------------------- /interop/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "grpc-interop" 3 | description = "Implementation of the grpc-interop service." 4 | repository = "http://github.com/stephancheg/grpc-rust/interop" 5 | version = "0.0.0" 6 | authors = ["Steve Jenson "] 7 | publish = false 8 | edition = "2018" 9 | 10 | [dependencies.grpc] 11 | path = "../grpc" 12 | [dependencies.grpc-protobuf] 13 | path = "../grpc-protobuf" 14 | 15 | [dependencies] 16 | bytes = "1.0.1" 17 | log = "0.4.*" 18 | env_logger = "~0.9" 19 | protobuf = "2.23" 20 | futures = "0.3.*" 21 | tls-api = "0.6.0" 22 | chrono = "0.4" 23 | clap = "3.1" 24 | 25 | [lib] 26 | test = false 27 | doctest = false 28 | 29 | [[bin]] 30 | name = "grpc-rust-interop-server" 31 | path = "src/bin/interop_server.rs" 32 | test = false 33 | 34 | [[bin]] 35 | name = "grpc-rust-interop-client" 36 | path = "src/bin/interop_client.rs" 37 | test = false 38 | 39 | [build-dependencies] 40 | protoc-rust-grpc = { path = "../protoc-rust-grpc" } 41 | -------------------------------------------------------------------------------- /interop/README.md: -------------------------------------------------------------------------------- 1 | grpc-rust interop client/server 2 | =============================== 3 | 4 | In order to test how well grpc-rust interoperates with other grpc stacks, we 5 | implement the [https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md](standard interop service). 6 | 7 | ## Current status 8 | 9 | Most of the basic interop tests work, but many need improvements. 10 | 11 | 12 | ## Test implementation status 13 | 14 | Covered in issue #47 15 | 16 | ## How to test a grpc-rust server against the official grpc-go interop client. 17 | # build and run the interop server (from grpc-rust/interop). 18 | ``` 19 | $ cargo build 20 | $ ../target/debug/grp-interop 21 | ``` 22 | 23 | # build and run the grpc-go interop client 24 | ``` 25 | $ ./get-go-interop.sh 26 | $ ./go-grpc-interop-client -use_tls=false -test_case=empty_unary -server_port=60011 27 | ``` 28 | 29 | # To find all the test cases you can run, use the --help flag. 30 | `$ ./go-grpc-interop-client --help` 31 | 32 | ## build and run the grpc-java interop client. 33 | First, you will need gradle installed. (`brew install gradle` on macOS) 34 | ``` 35 | $ git clone https://github.com/grpc/grpc-java.git 36 | $ cd grpc-java 37 | $ ./gradlew installDist -PskipCodegen=true 38 | $ ./run-test-client.sh --use_tls=false --test_case=empty_unary --server_port=60011 39 | ``` 40 | -------------------------------------------------------------------------------- /interop/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | protoc_rust_grpc::Codegen::new() 3 | .out_dir("src") 4 | .include("proto") 5 | .inputs(&[ 6 | "proto/messages.proto", 7 | "proto/empty.proto", 8 | "proto/test.proto", 9 | ]) 10 | .rust_protobuf(true) 11 | .run() 12 | .expect("protoc-rust-grpc"); 13 | } 14 | -------------------------------------------------------------------------------- /interop/client-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ex 2 | 3 | cd $(dirname $0) 4 | 5 | kill_server() { 6 | killall -KILL go-grpc-interop-server || true 7 | } 8 | 9 | kill_server 10 | 11 | cargo build --bin grpc-rust-interop-client 12 | 13 | ./go-grpc-interop-server & 14 | 15 | ../target/debug/grpc-rust-interop-client --test_case empty_unary 16 | ../target/debug/grpc-rust-interop-client --test_case large_unary 17 | ../target/debug/grpc-rust-interop-client --test_case ping_pong 18 | ../target/debug/grpc-rust-interop-client --test_case empty_stream 19 | ../target/debug/grpc-rust-interop-client --test_case custom_metadata 20 | 21 | kill_server 22 | 23 | set +x 24 | 25 | echo "ALL TESTS PASSED" 26 | 27 | # vim: set ts=4 sw=4 et: 28 | -------------------------------------------------------------------------------- /interop/get-go-interop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ex 2 | 3 | #go get -u google.golang.org/grpc 4 | #go get -u cloud.google.com/go/compute/metadata 5 | #go get -u golang.org/x/oauth2 6 | go get -u github.com/grpc/grpc-go/interop 7 | go build -o go-grpc-interop-client github.com/grpc/grpc-go/interop/client 8 | go build -o go-grpc-interop-server github.com/grpc/grpc-go/interop/server 9 | 10 | # vim: set ts=4 sw=4 et: 11 | -------------------------------------------------------------------------------- /interop/proto/empty.proto: -------------------------------------------------------------------------------- 1 | 2 | // Copyright 2015 gRPC authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | syntax = "proto3"; 17 | 18 | package grpc.testing; 19 | 20 | // An empty message that you can re-use to avoid defining duplicated empty 21 | // messages in your project. A typical example is to use it as argument or the 22 | // return value of a service API. For instance: 23 | // 24 | // service Foo { 25 | // rpc Bar (grpc.testing.Empty) returns (grpc.testing.Empty) { }; 26 | // }; 27 | // 28 | message Empty {} 29 | -------------------------------------------------------------------------------- /interop/proto/messages.proto: -------------------------------------------------------------------------------- 1 | 2 | // Copyright 2015-2016 gRPC authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | // Message definitions to be used by integration test service definitions. 17 | 18 | syntax = "proto3"; 19 | 20 | package grpc.testing; 21 | 22 | // TODO(dgq): Go back to using well-known types once 23 | // https://github.com/grpc/grpc/issues/6980 has been fixed. 24 | // import "google/protobuf/wrappers.proto"; 25 | message BoolValue { 26 | // The bool value. 27 | bool value = 1; 28 | } 29 | 30 | // The type of payload that should be returned. 31 | enum PayloadType { 32 | // Compressable text format. 33 | COMPRESSABLE = 0; 34 | } 35 | 36 | // A block of data, to simply increase gRPC message size. 37 | message Payload { 38 | // The type of data in body. 39 | PayloadType type = 1; 40 | // Primary contents of payload. 41 | bytes body = 2; 42 | } 43 | 44 | // A protobuf representation for grpc status. This is used by test 45 | // clients to specify a status that the server should attempt to return. 46 | message EchoStatus { 47 | int32 code = 1; 48 | string message = 2; 49 | } 50 | 51 | // Unary request. 52 | message SimpleRequest { 53 | // Desired payload type in the response from the server. 54 | // If response_type is RANDOM, server randomly chooses one from other formats. 55 | PayloadType response_type = 1; 56 | 57 | // Desired payload size in the response from the server. 58 | int32 response_size = 2; 59 | 60 | // Optional input payload sent along with the request. 61 | Payload payload = 3; 62 | 63 | // Whether SimpleResponse should include username. 64 | bool fill_username = 4; 65 | 66 | // Whether SimpleResponse should include OAuth scope. 67 | bool fill_oauth_scope = 5; 68 | 69 | // Whether to request the server to compress the response. This field is 70 | // "nullable" in order to interoperate seamlessly with clients not able to 71 | // implement the full compression tests by introspecting the call to verify 72 | // the response's compression status. 73 | BoolValue response_compressed = 6; 74 | 75 | // Whether server should return a given status 76 | EchoStatus response_status = 7; 77 | 78 | // Whether the server should expect this request to be compressed. 79 | BoolValue expect_compressed = 8; 80 | } 81 | 82 | // Unary response, as configured by the request. 83 | message SimpleResponse { 84 | // Payload to increase message size. 85 | Payload payload = 1; 86 | // The user the request came from, for verifying authentication was 87 | // successful when the client expected it. 88 | string username = 2; 89 | // OAuth scope. 90 | string oauth_scope = 3; 91 | } 92 | 93 | // Client-streaming request. 94 | message StreamingInputCallRequest { 95 | // Optional input payload sent along with the request. 96 | Payload payload = 1; 97 | 98 | // Whether the server should expect this request to be compressed. This field 99 | // is "nullable" in order to interoperate seamlessly with servers not able to 100 | // implement the full compression tests by introspecting the call to verify 101 | // the request's compression status. 102 | BoolValue expect_compressed = 2; 103 | 104 | // Not expecting any payload from the response. 105 | } 106 | 107 | // Client-streaming response. 108 | message StreamingInputCallResponse { 109 | // Aggregated size of payloads received from the client. 110 | int32 aggregated_payload_size = 1; 111 | } 112 | 113 | // Configuration for a particular response. 114 | message ResponseParameters { 115 | // Desired payload sizes in responses from the server. 116 | int32 size = 1; 117 | 118 | // Desired interval between consecutive responses in the response stream in 119 | // microseconds. 120 | int32 interval_us = 2; 121 | 122 | // Whether to request the server to compress the response. This field is 123 | // "nullable" in order to interoperate seamlessly with clients not able to 124 | // implement the full compression tests by introspecting the call to verify 125 | // the response's compression status. 126 | BoolValue compressed = 3; 127 | } 128 | 129 | // Server-streaming request. 130 | message StreamingOutputCallRequest { 131 | // Desired payload type in the response from the server. 132 | // If response_type is RANDOM, the payload from each response in the stream 133 | // might be of different types. This is to simulate a mixed type of payload 134 | // stream. 135 | PayloadType response_type = 1; 136 | 137 | // Configuration for each expected response message. 138 | repeated ResponseParameters response_parameters = 2; 139 | 140 | // Optional input payload sent along with the request. 141 | Payload payload = 3; 142 | 143 | // Whether server should return a given status 144 | EchoStatus response_status = 7; 145 | } 146 | 147 | // Server-streaming response, as configured by the request and parameters. 148 | message StreamingOutputCallResponse { 149 | // Payload to increase response size. 150 | Payload payload = 1; 151 | } 152 | 153 | // For reconnect interop test only. 154 | // Client tells server what reconnection parameters it used. 155 | message ReconnectParams { 156 | int32 max_reconnect_backoff_ms = 1; 157 | } 158 | 159 | // For reconnect interop test only. 160 | // Server tells client whether its reconnects are following the spec and the 161 | // reconnect backoffs it saw. 162 | message ReconnectInfo { 163 | bool passed = 1; 164 | repeated int32 backoff_ms = 2; 165 | } 166 | -------------------------------------------------------------------------------- /interop/proto/test.proto: -------------------------------------------------------------------------------- 1 | 2 | // Copyright 2015-2016 gRPC authors. 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | // An integration test service that covers all the method signature permutations 17 | // of unary/streaming requests/responses. 18 | 19 | syntax = "proto3"; 20 | 21 | import "empty.proto"; 22 | import "messages.proto"; 23 | 24 | package grpc.testing; 25 | 26 | // A simple service to test the various types of RPCs and experiment with 27 | // performance with various types of payload. 28 | service TestService { 29 | // One empty request followed by one empty response. 30 | rpc EmptyCall(grpc.testing.Empty) returns (grpc.testing.Empty); 31 | 32 | // One request followed by one response. 33 | rpc UnaryCall(SimpleRequest) returns (SimpleResponse); 34 | 35 | // One request followed by one response. Response has cache control 36 | // headers set such that a caching HTTP proxy (such as GFE) can 37 | // satisfy subsequent requests. 38 | rpc CacheableUnaryCall(SimpleRequest) returns (SimpleResponse); 39 | 40 | // One request followed by a sequence of responses (streamed download). 41 | // The server returns the payload with client desired type and sizes. 42 | rpc StreamingOutputCall(StreamingOutputCallRequest) 43 | returns (stream StreamingOutputCallResponse); 44 | 45 | // A sequence of requests followed by one response (streamed upload). 46 | // The server returns the aggregated size of client payload as the result. 47 | rpc StreamingInputCall(stream StreamingInputCallRequest) 48 | returns (StreamingInputCallResponse); 49 | 50 | // A sequence of requests with each request served by the server immediately. 51 | // As one request could lead to multiple responses, this interface 52 | // demonstrates the idea of full duplexing. 53 | rpc FullDuplexCall(stream StreamingOutputCallRequest) 54 | returns (stream StreamingOutputCallResponse); 55 | 56 | // A sequence of requests followed by a sequence of responses. 57 | // The server buffers all the client requests and then serves them in order. A 58 | // stream of responses are returned to the client when the server starts with 59 | // first request. 60 | rpc HalfDuplexCall(stream StreamingOutputCallRequest) 61 | returns (stream StreamingOutputCallResponse); 62 | 63 | // The test server will not implement this method. It will be used 64 | // to test the behavior when clients call unimplemented methods. 65 | //rpc UnimplementedCall(grpc.testing.Empty) returns (grpc.testing.Empty); 66 | } 67 | 68 | // A simple service NOT implemented at servers so clients can test for 69 | // that case. 70 | service UnimplementedService { 71 | // A call that no server should implement 72 | rpc UnimplementedCall(grpc.testing.Empty) returns (grpc.testing.Empty); 73 | } 74 | 75 | // A service used to control reconnect server. 76 | service ReconnectService { 77 | rpc Start(grpc.testing.ReconnectParams) returns (grpc.testing.Empty); 78 | rpc Stop(grpc.testing.Empty) returns (grpc.testing.ReconnectInfo); 79 | } 80 | -------------------------------------------------------------------------------- /interop/server-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -ex 2 | 3 | cd $(dirname $0) 4 | 5 | kill_server() { 6 | killall -KILL grpc-rust-interop-server || true 7 | } 8 | 9 | cargo build 10 | kill_server 11 | ../target/debug/grpc-rust-interop-server & 12 | 13 | trap kill_server EXIT 14 | 15 | tests=( 16 | empty_unary 17 | large_unary 18 | ping_pong 19 | empty_stream 20 | custom_metadata 21 | status_code_and_message 22 | unimplemented_method 23 | cancel_after_first_response 24 | ) 25 | 26 | for testname in "${tests[@]}"; do 27 | ./go-grpc-interop-client -use_tls=false -test_case=$testname 28 | done 29 | 30 | kill_server 31 | 32 | set +x 33 | 34 | echo "ALL TESTS PASSED" 35 | 36 | # vim: set ts=4 sw=4 et: 37 | -------------------------------------------------------------------------------- /interop/src/.gitignore: -------------------------------------------------------------------------------- 1 | messages.rs 2 | test_grpc.rs 3 | empty.rs 4 | test.rs 5 | -------------------------------------------------------------------------------- /interop/src/bin/interop_client.rs: -------------------------------------------------------------------------------- 1 | use grpc::prelude::*; 2 | use grpc_interop::interop_client; 3 | use grpc_interop::test_grpc::TestServiceClient; 4 | 5 | use clap::{Arg, Command}; 6 | use grpc_interop::DEFAULT_PORT; 7 | 8 | // The flags we use are defined in the gRPC Interopability doc 9 | // https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md 10 | fn main() { 11 | env_logger::init(); 12 | 13 | let options = Command::new("gRPC interopability client") 14 | .version("0.1") 15 | .author("Steve Jenson ") 16 | .about("Interoperability Test Client for grpc-rust") 17 | .arg( 18 | Arg::new("server_host") 19 | .long("server_host") 20 | .help("The server host to connect to. For example, \"localhost\" or \"127.0.0.1\"") 21 | .takes_value(true), 22 | ) 23 | .arg( 24 | Arg::new("server_host_override") 25 | .long("server_host_override") 26 | .help( 27 | "The server host to claim to be connecting to, for use in TLS and HTTP/2 \ 28 | :authority header. If unspecified, the value of --server_host will be used", 29 | ) 30 | .takes_value(true), 31 | ) 32 | .arg( 33 | Arg::new("server_port") 34 | .long("server_port") 35 | .help("The server port to connect to. For example, \"8080\"") 36 | .takes_value(true), 37 | ) 38 | .arg( 39 | Arg::new("test_case") 40 | .long("test_case") 41 | .help("The name of the test case to execute. For example, \"empty_unary\"") 42 | .takes_value(true), 43 | ) 44 | .arg( 45 | Arg::new("use_tls") // boolean 46 | .long("use_tls") 47 | .help("Whether to use a plaintext or encrypted connection") 48 | .takes_value(true), 49 | ) 50 | .arg( 51 | Arg::new("use_test_ca") 52 | .long("use_test_ca") 53 | .help("Whether to replace platform root CAs with ca.pem as the CA root") 54 | .takes_value(true), 55 | ) 56 | .arg( 57 | Arg::new("default_service_account") 58 | .long("default_service_account") 59 | .help("Email of the GCE default service account.") 60 | .takes_value(true), 61 | ) 62 | .arg( 63 | Arg::new("oauth_scope") 64 | .long("oauth_scope") 65 | .help("OAuth scope. For example, \"https://www.googleapis.com/auth/xapi.zoo\"") 66 | .takes_value(true), 67 | ) 68 | .arg( 69 | Arg::new("service_account_key_file") 70 | .long("service_account_key_file") 71 | .help( 72 | "The path to the service account JSON key file generated from GCE developer \ 73 | console.", 74 | ) 75 | .takes_value(true), 76 | ) 77 | .get_matches(); 78 | 79 | let hostname = options.value_of("server_host").unwrap_or("127.0.0.1"); 80 | let serverport = options 81 | .value_of("server_port") 82 | .map(|s| s.parse().unwrap()) 83 | .unwrap_or(DEFAULT_PORT); 84 | 85 | let client = 86 | TestServiceClient::new_plain(hostname, serverport, Default::default()).expect("init"); 87 | 88 | let testcase = options.value_of("test_case").unwrap_or(""); 89 | 'b: loop { 90 | for &(t, f) in interop_client::TESTS { 91 | if t == testcase { 92 | f(client); 93 | break 'b; 94 | } 95 | } 96 | 97 | panic!("no test_case specified or unknown test case"); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /interop/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod empty; 2 | pub mod messages; 3 | pub mod test_grpc; 4 | 5 | pub use empty::*; 6 | pub use messages::*; 7 | pub use test_grpc::*; 8 | 9 | pub mod interop_client; 10 | 11 | pub const DEFAULT_PORT: u16 = 10000; 12 | -------------------------------------------------------------------------------- /interop/testdata/server1.key: -------------------------------------------------------------------------------- 1 | Bag Attributes 2 | friendlyName: foobar.com 3 | localKeyID: 59 17 2D 93 13 E8 44 59 BC FF 27 F9 67 E7 9E 6E 92 17 E5 84 4 | subject=/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=foobar.com 5 | issuer=/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd 6 | -----BEGIN CERTIFICATE----- 7 | MIIDGzCCAgMCCQCHcfe97pgvpTANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB 8 | VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 9 | cyBQdHkgTHRkMB4XDTE2MDgxNDE3MDAwM1oXDTI2MDgxMjE3MDAwM1owWjELMAkG 10 | A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0 11 | IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UEAwwKZm9vYmFyLmNvbTCCASIwDQYJKoZI 12 | hvcNAQEBBQADggEPADCCAQoCggEBAKj0JYxEsxejUIX+I5GH0Hg2G0kX/y1H0+Ub 13 | 3mw2/Ja5BD/yN96/7zMSumXF8uS3SkmpyiJkbyD01TSRTqjlP7/VCBlyUIChlpLQ 14 | mrGaijZiT/VCyPXqmcwFzXS5IOTpX1olJfW8rA41U1LCIcDUyFf6LtZ/v8rSeKr6 15 | TuE6SGV4WRaBm1SrjWBeHVV866CRrtSS1ieT2asFsAyOZqWhk2fakwwBDFWDhOGI 16 | ubfO+5aq9cBJbNRlzsgB3UZs3gC0O6GzbnZ6oT0TiJMeTsXXjABLUlaq/rrqFF4Y 17 | euZkkbHTFBMz288PUc3m3ZTcpN+E7+ZOUBRZXKD20K07NugqCzUCAwEAATANBgkq 18 | hkiG9w0BAQsFAAOCAQEASvYHuIl5C0NHBELPpVHNuLbQsDQNKVj3a54+9q1JkiMM 19 | 6taEJYfw7K1Xjm4RoiFSHpQBh+PWZS3hToToL2Zx8JfMR5MuAirdPAy1Sia/J/qE 20 | wQdJccqmvuLkLTSlsGbEJ/LUUgOAgrgHOZM5lUgIhCneA0/dWJ3PsN0zvn69/faY 21 | oo1iiolWiIHWWBUSdr3jM2AJaVAsTmLh00cKaDNk37JB940xConBGSl98JPrNrf9 22 | dUAiT0iIBngDBdHnn/yTj+InVEFyZSKrNtiDSObFHxPcxGteHNrCPJdP1e+GqkHp 23 | HJMRZVCQpSMzvHlofHSNgzWV1MX5h1CP4SGZdBDTfA== 24 | -----END CERTIFICATE----- 25 | Bag Attributes: 26 | subject=/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd 27 | issuer=/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd 28 | -----BEGIN CERTIFICATE----- 29 | MIIDXTCCAkWgAwIBAgIJAOIvDiVb18eVMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV 30 | BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX 31 | aWRnaXRzIFB0eSBMdGQwHhcNMTYwODE0MTY1NjExWhcNMjYwODEyMTY1NjExWjBF 32 | MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 33 | ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB 34 | CgKCAQEArVHWFn52Lbl1l59exduZntVSZyDYpzDND+S2LUcO6fRBWhV/1Kzox+2G 35 | ZptbuMGmfI3iAnb0CFT4uC3kBkQQlXonGATSVyaFTFR+jq/lc0SP+9Bd7SBXieIV 36 | eIXlY1TvlwIvj3Ntw9zX+scTA4SXxH6M0rKv9gTOub2vCMSHeF16X8DQr4XsZuQr 37 | 7Cp7j1I4aqOJyap5JTl5ijmG8cnu0n+8UcRlBzy99dLWJG0AfI3VRJdWpGTNVZ92 38 | aFff3RpK3F/WI2gp3qV1ynRAKuvmncGC3LDvYfcc2dgsc1N6Ffq8GIrkgRob6eBc 39 | klDHp1d023Lwre+VaVDSo1//Y72UFwIDAQABo1AwTjAdBgNVHQ4EFgQUbNOlA6sN 40 | XyzJjYqciKeId7g3/ZowHwYDVR0jBBgwFoAUbNOlA6sNXyzJjYqciKeId7g3/Zow 41 | DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAVVaR5QWLZIRR4Dw6TSBn 42 | BQiLpBSXN6oAxdDw6n4PtwW6CzydaA+creiK6LfwEsiifUfQe9f+T+TBSpdIYtMv 43 | Z2H2tjlFX8VrjUFvPrvn5c28CuLI0foBgY8XGSkR2YMYzWw2jPEq3Th/KM5Catn3 44 | AFm3bGKWMtGPR4v+90chEN0jzaAmJYRrVUh9vea27bOCn31Nse6XXQPmSI6Gyncy 45 | OAPUsvPClF3IjeL1tmBotWqSGn1cYxLo+Lwjk22A9h6vjcNQRyZF2VLVvtwYrNU3 46 | mwJ6GCLsLHpwW/yjyvn8iEltnJvByM/eeRnfXV6WDObyiZsE/n6DxIRJodQzFqy9 47 | GA== 48 | -----END CERTIFICATE----- 49 | Bag Attributes 50 | friendlyName: foobar.com 51 | localKeyID: 59 17 2D 93 13 E8 44 59 BC FF 27 F9 67 E7 9E 6E 92 17 E5 84 52 | Key Attributes: 53 | -----BEGIN RSA PRIVATE KEY----- 54 | MIIEpQIBAAKCAQEAqPQljESzF6NQhf4jkYfQeDYbSRf/LUfT5RvebDb8lrkEP/I3 55 | 3r/vMxK6ZcXy5LdKSanKImRvIPTVNJFOqOU/v9UIGXJQgKGWktCasZqKNmJP9ULI 56 | 9eqZzAXNdLkg5OlfWiUl9bysDjVTUsIhwNTIV/ou1n+/ytJ4qvpO4TpIZXhZFoGb 57 | VKuNYF4dVXzroJGu1JLWJ5PZqwWwDI5mpaGTZ9qTDAEMVYOE4Yi5t877lqr1wEls 58 | 1GXOyAHdRmzeALQ7obNudnqhPROIkx5OxdeMAEtSVqr+uuoUXhh65mSRsdMUEzPb 59 | zw9RzebdlNyk34Tv5k5QFFlcoPbQrTs26CoLNQIDAQABAoIBAQCi4+iDiQSl6JXM 60 | 6c2/FJMgIwJBgXpLXLHldO4HpSf35N89C7lj7+addhPx5VwduL1Nsf4pFG2z93jL 61 | GsEcwxGoUKpjZjxJGAGFAMv0KYvRjQ2L59y6g3nOI1YHkCkvqfR9g81KU6IKfM0d 62 | v/8s27lgYNvhf/8Ye71NEtEUAs4jzMe0UgYG4cUnwoXxCW0trA5834dI0UJqGPeY 63 | hb+6bnrdRSbc3WeH4AndUapgtpRTOR9HmbTfws9fZYnlbNG/fegxK3RAagpVWTtN 64 | HmG0aMwg2gc0VN8mwf3TKxicYZJF5sG95FjLc4h/ajmLKLLbhyBbvHihS5aCsRZ6 65 | tkeAxsThAoGBANilCGSEjnhiUgXCySQFCtoL4FzzaOBx1iMDgPcXjd2SZbJz5X31 66 | 0DZ5urdbLdVdoHzJvSNp9JvxdMtgf5/1xkmvG4IXzwPZdHNoefJZC9yUCUf6WmvM 67 | nE8/mGT685xMje0GtEayV1L2b/ZUCx3N/hR3Eh4DKg1dn9mxwBLvWBotAoGBAMel 68 | RCDzb6xxYI6P45NUU7pPptMRFkLGSwrPos0DK9k2niZ/CxvvdwLxbmJ9CdJ091rk 69 | rSz61IPIuCIUkupX9bSHeYryGT5uK2MDMm5QSV4Zx25O1KqHY38P97OldhwLvvge 70 | 4U3cla/Qxbkk/br+JvG5D1fK319YxCNqwD1Vr4IpAoGAfsDQED+eO8EKzGQS2wg4 71 | OSsJsliX2/m+l+3M3sThM+obpjU46GGR2M2P+QdX4aZN57UA/F9ZxoOXGgbzpNtf 72 | kGzrY0oazN3FzGAsOSbwUbYrV/maPcgRDCzhNPO+5IvF3hA2GcbuYJPfHfg+KMoF 73 | BmRELVscl4VXqT7eajWvDmECgYEAx0VYGSOihZas798DIdz7rW0vcGEPvRq7cFEL 74 | iGHv9GElvfr0la+RNKjSqw9vLFd/RYQWrly2nctMrwemFK4zGzxVvrAjLkM8nxlj 75 | zuPoNzq36oxYjNWSJBNGBFPU7e1zcakw7UyNQ+24TTJ0554iNQeoHtLp3ft12nwE 76 | 4bOS+PECgYEAk06X6jLfJFN7XCPbH9DCtN8u5AO+nebsNKWW1hcuyiOKp81rrQs2 77 | /hTD1lrKBbSySz2cv/7v2QlFRVWuHdGPwEjUCBElT1xSwJbZS6ODEprINbwgbG74 78 | hBGj3hI7zK4JIvd6WLXf2jkoJGAtU5hKteeLOa5qofofl0SH+wLtEi8= 79 | -----END RSA PRIVATE KEY----- 80 | -------------------------------------------------------------------------------- /interop/testdata/server1.pem: -------------------------------------------------------------------------------- 1 | Bag Attributes 2 | friendlyName: foobar.com 3 | localKeyID: 59 17 2D 93 13 E8 44 59 BC FF 27 F9 67 E7 9E 6E 92 17 E5 84 4 | subject=/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd/CN=foobar.com 5 | issuer=/C=AU/ST=Some-State/O=Internet Widgits Pty Ltd 6 | -----BEGIN CERTIFICATE----- 7 | MIIDGzCCAgMCCQCHcfe97pgvpTANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJB 8 | VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 9 | cyBQdHkgTHRkMB4XDTE2MDgxNDE3MDAwM1oXDTI2MDgxMjE3MDAwM1owWjELMAkG 10 | A1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0 11 | IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UEAwwKZm9vYmFyLmNvbTCCASIwDQYJKoZI 12 | hvcNAQEBBQADggEPADCCAQoCggEBAKj0JYxEsxejUIX+I5GH0Hg2G0kX/y1H0+Ub 13 | 3mw2/Ja5BD/yN96/7zMSumXF8uS3SkmpyiJkbyD01TSRTqjlP7/VCBlyUIChlpLQ 14 | mrGaijZiT/VCyPXqmcwFzXS5IOTpX1olJfW8rA41U1LCIcDUyFf6LtZ/v8rSeKr6 15 | TuE6SGV4WRaBm1SrjWBeHVV866CRrtSS1ieT2asFsAyOZqWhk2fakwwBDFWDhOGI 16 | ubfO+5aq9cBJbNRlzsgB3UZs3gC0O6GzbnZ6oT0TiJMeTsXXjABLUlaq/rrqFF4Y 17 | euZkkbHTFBMz288PUc3m3ZTcpN+E7+ZOUBRZXKD20K07NugqCzUCAwEAATANBgkq 18 | hkiG9w0BAQsFAAOCAQEASvYHuIl5C0NHBELPpVHNuLbQsDQNKVj3a54+9q1JkiMM 19 | 6taEJYfw7K1Xjm4RoiFSHpQBh+PWZS3hToToL2Zx8JfMR5MuAirdPAy1Sia/J/qE 20 | wQdJccqmvuLkLTSlsGbEJ/LUUgOAgrgHOZM5lUgIhCneA0/dWJ3PsN0zvn69/faY 21 | oo1iiolWiIHWWBUSdr3jM2AJaVAsTmLh00cKaDNk37JB940xConBGSl98JPrNrf9 22 | dUAiT0iIBngDBdHnn/yTj+InVEFyZSKrNtiDSObFHxPcxGteHNrCPJdP1e+GqkHp 23 | HJMRZVCQpSMzvHlofHSNgzWV1MX5h1CP4SGZdBDTfA== 24 | -----END CERTIFICATE----- 25 | Bag Attributes 26 | friendlyName: foobar.com 27 | localKeyID: 59 17 2D 93 13 E8 44 59 BC FF 27 F9 67 E7 9E 6E 92 17 E5 84 28 | Key Attributes: 29 | -----BEGIN RSA PRIVATE KEY----- 30 | MIIEpQIBAAKCAQEAqPQljESzF6NQhf4jkYfQeDYbSRf/LUfT5RvebDb8lrkEP/I3 31 | 3r/vMxK6ZcXy5LdKSanKImRvIPTVNJFOqOU/v9UIGXJQgKGWktCasZqKNmJP9ULI 32 | 9eqZzAXNdLkg5OlfWiUl9bysDjVTUsIhwNTIV/ou1n+/ytJ4qvpO4TpIZXhZFoGb 33 | VKuNYF4dVXzroJGu1JLWJ5PZqwWwDI5mpaGTZ9qTDAEMVYOE4Yi5t877lqr1wEls 34 | 1GXOyAHdRmzeALQ7obNudnqhPROIkx5OxdeMAEtSVqr+uuoUXhh65mSRsdMUEzPb 35 | zw9RzebdlNyk34Tv5k5QFFlcoPbQrTs26CoLNQIDAQABAoIBAQCi4+iDiQSl6JXM 36 | 6c2/FJMgIwJBgXpLXLHldO4HpSf35N89C7lj7+addhPx5VwduL1Nsf4pFG2z93jL 37 | GsEcwxGoUKpjZjxJGAGFAMv0KYvRjQ2L59y6g3nOI1YHkCkvqfR9g81KU6IKfM0d 38 | v/8s27lgYNvhf/8Ye71NEtEUAs4jzMe0UgYG4cUnwoXxCW0trA5834dI0UJqGPeY 39 | hb+6bnrdRSbc3WeH4AndUapgtpRTOR9HmbTfws9fZYnlbNG/fegxK3RAagpVWTtN 40 | HmG0aMwg2gc0VN8mwf3TKxicYZJF5sG95FjLc4h/ajmLKLLbhyBbvHihS5aCsRZ6 41 | tkeAxsThAoGBANilCGSEjnhiUgXCySQFCtoL4FzzaOBx1iMDgPcXjd2SZbJz5X31 42 | 0DZ5urdbLdVdoHzJvSNp9JvxdMtgf5/1xkmvG4IXzwPZdHNoefJZC9yUCUf6WmvM 43 | nE8/mGT685xMje0GtEayV1L2b/ZUCx3N/hR3Eh4DKg1dn9mxwBLvWBotAoGBAMel 44 | RCDzb6xxYI6P45NUU7pPptMRFkLGSwrPos0DK9k2niZ/CxvvdwLxbmJ9CdJ091rk 45 | rSz61IPIuCIUkupX9bSHeYryGT5uK2MDMm5QSV4Zx25O1KqHY38P97OldhwLvvge 46 | 4U3cla/Qxbkk/br+JvG5D1fK319YxCNqwD1Vr4IpAoGAfsDQED+eO8EKzGQS2wg4 47 | OSsJsliX2/m+l+3M3sThM+obpjU46GGR2M2P+QdX4aZN57UA/F9ZxoOXGgbzpNtf 48 | kGzrY0oazN3FzGAsOSbwUbYrV/maPcgRDCzhNPO+5IvF3hA2GcbuYJPfHfg+KMoF 49 | BmRELVscl4VXqT7eajWvDmECgYEAx0VYGSOihZas798DIdz7rW0vcGEPvRq7cFEL 50 | iGHv9GElvfr0la+RNKjSqw9vLFd/RYQWrly2nctMrwemFK4zGzxVvrAjLkM8nxlj 51 | zuPoNzq36oxYjNWSJBNGBFPU7e1zcakw7UyNQ+24TTJ0554iNQeoHtLp3ft12nwE 52 | 4bOS+PECgYEAk06X6jLfJFN7XCPbH9DCtN8u5AO+nebsNKWW1hcuyiOKp81rrQs2 53 | /hTD1lrKBbSySz2cv/7v2QlFRVWuHdGPwEjUCBElT1xSwJbZS6ODEprINbwgbG74 54 | hBGj3hI7zK4JIvd6WLXf2jkoJGAtU5hKteeLOa5qofofl0SH+wLtEi8= 55 | -----END RSA PRIVATE KEY----- 56 | -------------------------------------------------------------------------------- /long-tests/README.md: -------------------------------------------------------------------------------- 1 | ## Compatibility tests 2 | 3 | This directory contains tests against Go implementation of gRPC. 4 | 5 | Basically it contains the same implementation in Rust and in Go: 6 | Go or Rust client should behave identically when connected to the server 7 | implemented in Go or Rust. 8 | 9 | ## How to use it 10 | 11 | ``` 12 | # Rust client and Go server 13 | % ./run-test-helper.sh rust go 14 | running 10000 iterations of echo 15 | done 16 | # Go client and Rust server 17 | % ./run-test-helper.sh go rust 18 | 2017/01/21 23:38:49 running 10000 iterations of echo 19 | 2017/01/21 23:38:51 done 20 | ``` 21 | -------------------------------------------------------------------------------- /long-tests/long_tests_pb.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | 4 | message EchoRequest { 5 | string payload = 1; 6 | } 7 | 8 | message EchoResponse { 9 | string payload = 2; 10 | } 11 | 12 | message CharCountRequest { 13 | string part = 1; 14 | } 15 | 16 | message CharCountResponse { 17 | uint64 char_count = 1; 18 | } 19 | 20 | message RandomStringsRequest { 21 | uint64 count = 1; 22 | } 23 | 24 | message RandomStringsResponse { 25 | string s = 1; 26 | } 27 | 28 | service LongTests { 29 | // simple RPC 30 | rpc echo (EchoRequest) returns (EchoResponse); 31 | // client streaming 32 | rpc char_count (stream CharCountRequest) returns (CharCountResponse); 33 | // server streaming 34 | rpc random_strings (RandomStringsRequest) returns (stream RandomStringsResponse); 35 | } 36 | -------------------------------------------------------------------------------- /long-tests/run-test-helper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | die() { 4 | echo "$@" >&2 5 | exit 1 6 | } 7 | 8 | me=$0 9 | 10 | usage() { 11 | die "usage: $me " 12 | } 13 | 14 | cd $(dirname $0) 15 | 16 | case $1 in 17 | rust) client=../target/debug/long_tests_client ;; 18 | go) client=with-go/long_tests_client/long_tests_client ;; 19 | *) usage ;; 20 | esac 21 | 22 | case $2 in 23 | rust) server=../target/debug/long_tests_server ;; 24 | go) server=with-go/long_tests_server/long_tests_server ;; 25 | *) usage ;; 26 | esac 27 | 28 | killall -KILL long_tests_server 2> /dev/null || true 29 | killall -KILL long_tests_client 2> /dev/null || true 30 | 31 | $server & 32 | 33 | go_server_pid=$! 34 | 35 | sleep 0.1 # give server some time to start 36 | 37 | $client echo 10000 38 | 39 | kill $go_server_pid 40 | -------------------------------------------------------------------------------- /long-tests/with-go/.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = lf 3 | insert_final_newline = true 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | -------------------------------------------------------------------------------- /long-tests/with-go/gen-go.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | die() { 4 | echo "$@" >&2 5 | exit 1 6 | } 7 | 8 | set -ex 9 | 10 | cd $(dirname $0) 11 | 12 | # for protoc 13 | PATH=$HOME/devel/left/protobuf/src:$GOPATH/bin:$PATH 14 | 15 | protoc_ver=$(protoc --version) 16 | case "$protoc_ver" in 17 | "libprotoc 3"*) ;; 18 | *) die "protoc version 3 required: $protoc_ver" ;; 19 | esac 20 | 21 | protoc -I.. --go_out=plugins=grpc:long_tests_pb ../long_tests_pb.proto 22 | 23 | # vim: set ts=4 sw=4 et: 24 | -------------------------------------------------------------------------------- /long-tests/with-go/long_tests_client/long_tests_client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | "strconv" 7 | "fmt" 8 | 9 | "golang.org/x/net/context" 10 | "google.golang.org/grpc" 11 | 12 | pb "../long_tests_pb" 13 | "math/rand" 14 | "io" 15 | ) 16 | 17 | const ( 18 | address = "localhost:23432" 19 | ) 20 | 21 | func single_num_arg_or(cmd_args []string, or int) int { 22 | if len(cmd_args) > 1 { 23 | log.Fatalf("too many char_count params: %s", len(cmd_args)); 24 | return 0 25 | } else if len(cmd_args) == 1 { 26 | count_tmp, err := strconv.Atoi((cmd_args[0])); 27 | if err != nil { 28 | log.Fatalf("failed to parse int: %v", err); 29 | } 30 | return count_tmp 31 | } else { 32 | return or 33 | } 34 | } 35 | 36 | func run_echo(c pb.LongTestsClient, cmd_args []string) { 37 | count := single_num_arg_or(cmd_args, 1) 38 | 39 | log.Printf("running %d iterations of echo", count) 40 | 41 | for i := 0; i < count; i += 1 { 42 | payload := fmt.Sprintf("payload %s", i) 43 | 44 | r, err := c.Echo(context.Background(), &pb.EchoRequest{Payload: payload}) 45 | 46 | if err != nil { 47 | log.Fatalf("could not greet: %v", err) 48 | } 49 | 50 | if r.Payload != payload { 51 | log.Fatalf("wrong payload: %v", r) 52 | } 53 | } 54 | 55 | log.Printf("done") 56 | } 57 | 58 | func run_char_count(c pb.LongTestsClient, cmd_args []string) { 59 | count := single_num_arg_or(cmd_args, 10) 60 | 61 | log.Printf("sending %d messages to count", count) 62 | 63 | client, err := c.CharCount(context.Background()) 64 | if err != nil { 65 | log.Fatalf("failed to start request: %v", err) 66 | } 67 | 68 | expected := uint64(0); 69 | for i := 0; i < count; i += 1 { 70 | s := "abcdefghijklmnopqrstuvwxyz0123456789" 71 | part := s[:rand.Intn(len(s))] 72 | client.Send(&pb.CharCountRequest{Part: part}) 73 | expected += uint64(len(part)) 74 | } 75 | 76 | resp, err := client.CloseAndRecv() 77 | if err != nil { 78 | log.Fatalf("failed to get response: %v", err); 79 | } 80 | 81 | if expected != resp.CharCount { 82 | log.Fatalf("expected: %s actual: %s", expected, resp.CharCount) 83 | } 84 | 85 | log.Printf("successfully got correct answer") 86 | } 87 | 88 | func run_random_strings(c pb.LongTestsClient, cmd_args []string) { 89 | count := single_num_arg_or(cmd_args, 10) 90 | 91 | log.Printf("requesting %d string from server", count) 92 | 93 | client, err := c.RandomStrings(context.Background(), &pb.RandomStringsRequest{ Count: uint64(count) }) 94 | if err != nil { 95 | log.Fatalf("failed to execute request: %v", err) 96 | } 97 | 98 | for { 99 | _, err := client.Recv() 100 | if err == io.EOF { 101 | log.Printf("got eof") 102 | break 103 | } 104 | if err != nil { 105 | log.Fatalf("could not read response part: %v", err) 106 | } 107 | } 108 | } 109 | 110 | 111 | func main() { 112 | conn, err := grpc.Dial(address, grpc.WithInsecure()) 113 | if err != nil { 114 | log.Fatalf("did not connect: %v", err) 115 | } 116 | 117 | defer conn.Close() 118 | 119 | c := pb.NewLongTestsClient(conn) 120 | 121 | if len(os.Args) < 2 { 122 | log.Fatalf("too few args") 123 | } 124 | 125 | cmd := os.Args[1] 126 | 127 | cmd_args := os.Args[2:] 128 | 129 | switch cmd { 130 | case "echo": 131 | run_echo(c, cmd_args) 132 | return 133 | case "char_count": 134 | run_char_count(c, cmd_args) 135 | return 136 | case "random_strings": 137 | run_random_strings(c, cmd_args) 138 | return 139 | default: 140 | log.Fatalf("unknown command: %s", cmd) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /long-tests/with-go/long_tests_server/long_tests_server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "log" 6 | "google.golang.org/grpc" 7 | 8 | //"os" 9 | //"strconv" 10 | //"fmt" 11 | // 12 | "golang.org/x/net/context" 13 | //"google.golang.org/grpc" 14 | 15 | pb "../long_tests_pb" 16 | //"math/rand" 17 | //"io" 18 | "io" 19 | ) 20 | 21 | const ( 22 | port = ":23432" 23 | ) 24 | 25 | type server struct{} 26 | 27 | func (s *server) Echo(context context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) { 28 | return &pb.EchoResponse{Payload: req.Payload}, nil 29 | } 30 | 31 | func (s *server) CharCount(req pb.LongTests_CharCountServer) error { 32 | count := 0 33 | for { 34 | m, err := req.Recv() 35 | if err == io.EOF { 36 | req.SendAndClose(&pb.CharCountResponse{CharCount: uint64(count)}) 37 | return nil 38 | } 39 | if err != nil { 40 | return err 41 | } 42 | count += len(m.Part) 43 | } 44 | } 45 | 46 | func (s *server) RandomStrings(req *pb.RandomStringsRequest, resp pb.LongTests_RandomStringsServer) error { 47 | for i := 0; i < int(req.Count); i += 1 { 48 | err := resp.Send(&pb.RandomStringsResponse{S: "aabb"}) 49 | if err != nil { 50 | return err 51 | } 52 | } 53 | return nil 54 | } 55 | 56 | 57 | func main() { 58 | lis, err := net.Listen("tcp", port) 59 | if err != nil { 60 | log.Fatalf("failed to listen: %v", err) 61 | } 62 | 63 | s := grpc.NewServer() 64 | pb.RegisterLongTestsServer(s, &server{}) 65 | 66 | if err := s.Serve(lis); err != nil { 67 | log.Fatalf("failed to serve: %v", err) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /long-tests/with-rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "long_tests" 3 | version = "0.0.0" 4 | authors = ["Stepan Koltsov "] 5 | publish = false 6 | edition = "2018" 7 | 8 | [lib] 9 | test = false 10 | doctest = false 11 | 12 | [dependencies.grpc] 13 | path = "../../grpc" 14 | [dependencies.grpc-protobuf] 15 | path = "../../grpc-protobuf" 16 | 17 | [dependencies] 18 | log = "0.4.*" 19 | env_logger = "~0.9" 20 | protobuf = "2.23" 21 | tls-api = "0.6.0" 22 | futures = "0.3.*" 23 | 24 | [build-dependencies] 25 | protoc-rust-grpc = { path = "../../protoc-rust-grpc" } 26 | 27 | [[bin]] 28 | name = "long_tests_server" 29 | test = false 30 | 31 | [[bin]] 32 | name = "long_tests_client" 33 | test = false 34 | -------------------------------------------------------------------------------- /long-tests/with-rust/build-rs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | cd $(dirname $0) 4 | 5 | exec cargo build 6 | 7 | # vim: set ts=4 sw=4 et: 8 | -------------------------------------------------------------------------------- /long-tests/with-rust/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | protoc_rust_grpc::Codegen::new() 3 | .out_dir("src") 4 | .include("..") 5 | .input("../long_tests_pb.proto") 6 | .rust_protobuf(true) 7 | .run() 8 | .expect("protoc-rust-grpc"); 9 | } 10 | -------------------------------------------------------------------------------- /long-tests/with-rust/src/bin/long_tests_client.rs: -------------------------------------------------------------------------------- 1 | use grpc::ClientStubExt; 2 | use long_tests::long_tests_pb::*; 3 | use long_tests::long_tests_pb_grpc::*; 4 | 5 | use futures::executor; 6 | use std::env; 7 | 8 | fn single_num_arg_or(cmd_args: &[String], or: u64) -> u64 { 9 | if cmd_args.len() == 0 { 10 | or 11 | } else if cmd_args.len() == 1 { 12 | cmd_args[0].parse().expect("failed to parse as u64") 13 | } else { 14 | panic!("too many args"); 15 | } 16 | } 17 | 18 | fn run_echo(client: LongTestsClient, cmd_args: &[String]) { 19 | let count = single_num_arg_or(cmd_args, 1); 20 | 21 | println!("running {} iterations of echo", count); 22 | 23 | for i in 0..count { 24 | let payload = format!("payload {}", i); 25 | 26 | let mut req = EchoRequest::new(); 27 | req.set_payload(payload.clone()); 28 | 29 | let r = executor::block_on( 30 | client 31 | .echo(grpc::RequestOptions::new(), req) 32 | .drop_metadata(), 33 | ) 34 | .expect("failed to get echo response"); 35 | 36 | assert!(payload == r.get_payload()); 37 | } 38 | 39 | println!("done"); 40 | } 41 | 42 | fn main() { 43 | env_logger::init(); 44 | 45 | let args: Vec = env::args().collect(); 46 | if args.len() < 2 { 47 | panic!("too few args") 48 | } 49 | 50 | let client = LongTestsClient::new_plain("localhost", 23432, Default::default()).expect("init"); 51 | 52 | let cmd = &args[1]; 53 | let cmd_args = &args[2..]; 54 | if cmd == "echo" { 55 | run_echo(client, cmd_args); 56 | } else { 57 | panic!("unknown command: {}", cmd); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /long-tests/with-rust/src/bin/long_tests_server.rs: -------------------------------------------------------------------------------- 1 | use std::thread; 2 | 3 | use long_tests::long_tests_pb::*; 4 | use long_tests::long_tests_pb_grpc::*; 5 | 6 | use grpc::*; 7 | use std::task::Poll; 8 | 9 | struct LongTestsServerImpl {} 10 | 11 | impl LongTests for LongTestsServerImpl { 12 | fn echo( 13 | &self, 14 | req: ServerRequestSingle, 15 | resp: ServerResponseUnarySink, 16 | ) -> grpc::Result<()> { 17 | let mut m = EchoResponse::new(); 18 | m.set_payload(req.message.payload); 19 | resp.finish(m) 20 | } 21 | 22 | fn char_count( 23 | &self, 24 | req: ServerRequest, 25 | resp: ServerResponseUnarySink, 26 | ) -> grpc::Result<()> { 27 | let mut resp = Some(resp); 28 | let mut char_count = 0; 29 | req.register_stream_handler_basic(move |message| { 30 | Ok(match message { 31 | Some(m) => { 32 | char_count += m.part.len(); 33 | } 34 | None => { 35 | resp.take().unwrap().finish({ 36 | let mut r = CharCountResponse::new(); 37 | r.char_count = char_count as u64; 38 | r 39 | })?; 40 | } 41 | }) 42 | }); 43 | Ok(()) 44 | } 45 | 46 | fn random_strings( 47 | &self, 48 | req: ServerRequestSingle, 49 | mut resp: ServerResponseSink, 50 | ) -> grpc::Result<()> { 51 | let mut rem = req.message.count; 52 | req.loop_handle() 53 | .spawn(futures::future::poll_fn(move |cx| loop { 54 | if let Poll::Pending = resp.poll(cx)? { 55 | return Poll::Pending; 56 | } 57 | if rem == 0 { 58 | resp.send_trailers(Metadata::new())?; 59 | return Poll::Ready(Ok::<_, grpc::Error>(())); 60 | } 61 | let s = "aabb".to_owned(); 62 | let mut m = RandomStringsResponse::new(); 63 | m.set_s(s); 64 | resp.send_data(m)?; 65 | rem -= 1; 66 | })); 67 | Ok(()) 68 | } 69 | } 70 | 71 | fn main() { 72 | env_logger::init(); 73 | 74 | let mut server = ServerBuilder::new_plain(); 75 | server 76 | .http 77 | .set_addr(long_tests::TEST_HOST) 78 | .expect("set_addr"); 79 | server.add_service(LongTestsServer::new_service_def(LongTestsServerImpl {})); 80 | let _server = server.build().expect("server"); 81 | 82 | loop { 83 | thread::park(); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /long-tests/with-rust/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod long_tests_pb; 2 | pub mod long_tests_pb_grpc; 3 | 4 | pub const TEST_HOST: &'static str = "localhost:23432"; 5 | -------------------------------------------------------------------------------- /protoc-rust-grpc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "protoc-rust-grpc" 3 | version = "0.9.0-pre" 4 | authors = ["Stepan Koltsov "] 5 | homepage = "https://github.com/stepancheg/rust-protobuf/protoc-rust/" 6 | repository = "https://github.com/stepancheg/rust-protobuf/protoc-rust/" 7 | license = "MIT/Apache-2.0" 8 | edition = "2018" 9 | description = """ 10 | protoc --rust-grpc_out=... available as API. protoc needs to be in $PATH, protoc-gen-rust-grpc does not. 11 | """ 12 | 13 | [lib] 14 | doctest = false 15 | test = false 16 | 17 | [dependencies] 18 | protoc = "2.23" 19 | protoc-rust = "2.23" 20 | protobuf = "2.23" 21 | grpc-compiler = { path = "../grpc-compiler", version = "=0.9.0-pre" } 22 | tempdir = "0.3" 23 | -------------------------------------------------------------------------------- /protoc-rust-grpc/README.md: -------------------------------------------------------------------------------- 1 | # API to generate .rs files 2 | 3 | API to generate .rs files to be used e. g. 4 | [from `build.rs`](https://github.com/stepancheg/grpc-rust/blob/master/interop/build.rs). 5 | 6 | Example code: 7 | 8 | ```rust 9 | fn main() { 10 | protoc_rust_grpc::run(protoc_rust_grpc::Args { 11 | out_dir: "src", 12 | includes: &["proto"], 13 | input: &["proto/aaa.proto", "proto/bbb.proto"], 14 | rust_protobuf: true, // also generate protobuf messages, not just services 15 | ..Default::default() 16 | }).expect("protoc-rust-grpc"); 17 | } 18 | ``` 19 | 20 | Note this API requires protoc command present in `$PATH`. 21 | Although `protoc-gen-rust-grpc` command is not needed. 22 | -------------------------------------------------------------------------------- /protoc-rust-grpc/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! API to generate `.rs` files. 2 | 3 | #![deny(broken_intra_doc_links)] 4 | #![deny(missing_docs)] 5 | 6 | use protoc::Protoc; 7 | use protoc_rust::Customize; 8 | use std::fs; 9 | use std::io; 10 | use std::io::Read; 11 | use std::io::Write; 12 | use std::path::Path; 13 | use std::path::PathBuf; 14 | 15 | /// Error type alias. 16 | pub type Error = io::Error; 17 | /// Result type alias. 18 | pub type Result = io::Result; 19 | 20 | /// Utility to generate `.rs` files. 21 | #[derive(Debug, Default)] 22 | pub struct Codegen { 23 | protoc: Option, 24 | /// --lang_out= param 25 | out_dir: PathBuf, 26 | /// -I args 27 | includes: Vec, 28 | /// List of .proto files to compile 29 | inputs: Vec, 30 | /// Generate rust-protobuf files along with rust-gprc 31 | rust_protobuf: bool, 32 | /// Customize rust-protobuf codegen 33 | rust_protobuf_customize: protoc_rust::Customize, 34 | } 35 | 36 | impl Codegen { 37 | /// Create new codegen object. 38 | pub fn new() -> Codegen { 39 | Default::default() 40 | } 41 | 42 | /// Set `--LANG_out=...` param 43 | pub fn out_dir(&mut self, out_dir: impl AsRef) -> &mut Self { 44 | self.out_dir = out_dir.as_ref().to_owned(); 45 | self 46 | } 47 | 48 | /// Append a path to `-I` args 49 | pub fn include(&mut self, include: impl AsRef) -> &mut Self { 50 | self.includes.push(include.as_ref().to_owned()); 51 | self 52 | } 53 | 54 | /// Append multiple paths to `-I` args 55 | pub fn includes(&mut self, includes: impl IntoIterator>) -> &mut Self { 56 | for include in includes { 57 | self.include(include); 58 | } 59 | self 60 | } 61 | 62 | /// Append a `.proto` file path to compile 63 | pub fn input(&mut self, input: impl AsRef) -> &mut Self { 64 | self.inputs.push(input.as_ref().to_owned()); 65 | self 66 | } 67 | 68 | /// Append multiple `.proto` file paths to compile 69 | pub fn inputs(&mut self, inputs: impl IntoIterator>) -> &mut Self { 70 | for input in inputs { 71 | self.input(input); 72 | } 73 | self 74 | } 75 | 76 | /// Generate rust-protobuf files along with rust-gprc 77 | pub fn rust_protobuf(&mut self, rust_protobuf: bool) -> &mut Self { 78 | self.rust_protobuf = rust_protobuf; 79 | self 80 | } 81 | 82 | /// Generate rust-protobuf files along with rust-gprc 83 | pub fn rust_protobuf_customize(&mut self, rust_protobuf_customize: Customize) -> &mut Self { 84 | self.rust_protobuf_customize = rust_protobuf_customize; 85 | self 86 | } 87 | 88 | /// Run the codegen. 89 | /// 90 | /// Generate `_grpc.rs` files, and if [`rust_protobuf_customize`](Codegen::rust_protobuf_customize) 91 | /// is specified, generate rust-protobuf `.rs` files too. 92 | /// 93 | pub fn run(&self) -> Result<()> { 94 | let protoc = self 95 | .protoc 96 | .clone() 97 | .unwrap_or_else(|| protoc::Protoc::from_env_path()); 98 | let version = protoc.version().expect("protoc version"); 99 | if !version.is_3() { 100 | panic!("protobuf must have version 3"); 101 | } 102 | 103 | if self.rust_protobuf { 104 | protoc_rust::Codegen::new() 105 | .out_dir(&self.out_dir) 106 | .includes(&self.includes) 107 | .inputs(&self.inputs) 108 | .customize(self.rust_protobuf_customize.clone()) 109 | .run()?; 110 | } 111 | 112 | let temp_dir = tempdir::TempDir::new("protoc-rust")?; 113 | let temp_file = temp_dir.path().join("descriptor.pbbin"); 114 | let temp_file = temp_file.to_str().expect("utf-8 file name"); 115 | 116 | let includes: Vec<&str> = self 117 | .includes 118 | .iter() 119 | .map(|p| p.as_os_str().to_str().unwrap()) 120 | .collect(); 121 | let inputs: Vec<&str> = self 122 | .inputs 123 | .iter() 124 | .map(|p| p.as_os_str().to_str().unwrap()) 125 | .collect(); 126 | protoc.write_descriptor_set(protoc::DescriptorSetOutArgs { 127 | out: temp_file, 128 | includes: &includes, 129 | input: &inputs, 130 | include_imports: true, 131 | })?; 132 | 133 | let mut fds = Vec::new(); 134 | let mut file = fs::File::open(temp_file)?; 135 | file.read_to_end(&mut fds)?; 136 | 137 | drop(file); 138 | drop(temp_dir); 139 | 140 | let fds: protobuf::descriptor::FileDescriptorSet = protobuf::parse_from_bytes(&fds) 141 | .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; 142 | 143 | let mut includes = self.includes.clone(); 144 | if includes.is_empty() { 145 | includes = vec![PathBuf::from(".")]; 146 | } 147 | 148 | let mut files_to_generate = Vec::new(); 149 | 'outer: for file in &self.inputs { 150 | for include in &includes { 151 | if let Some(truncated) = 152 | remove_path_prefix(file.to_str().unwrap(), include.to_str().unwrap()) 153 | { 154 | files_to_generate.push(truncated.to_owned()); 155 | continue 'outer; 156 | } 157 | } 158 | 159 | return Err(Error::new( 160 | io::ErrorKind::Other, 161 | format!( 162 | "file {:?} is not found in includes {:?}", 163 | file, self.includes 164 | ), 165 | )); 166 | } 167 | 168 | let gen_result = grpc_compiler::codegen::gen(fds.get_file(), &files_to_generate); 169 | 170 | for r in gen_result { 171 | let r: protobuf::compiler_plugin::GenResult = r; 172 | let file = format!("{}/{}", self.out_dir.display(), r.name); 173 | let mut file = fs::File::create(&file)?; 174 | file.write_all(&r.content)?; 175 | file.flush()?; 176 | } 177 | 178 | Ok(()) 179 | } 180 | } 181 | 182 | fn remove_dot_slash(path: &str) -> &str { 183 | if path == "." { 184 | "" 185 | } else if path.starts_with("./") || path.starts_with(".\\") { 186 | &path[2..] 187 | } else { 188 | path 189 | } 190 | } 191 | 192 | fn remove_path_prefix<'a>(mut path: &'a str, mut prefix: &str) -> Option<&'a str> { 193 | path = remove_dot_slash(path); 194 | prefix = remove_dot_slash(prefix); 195 | 196 | if prefix == "" { 197 | return Some(path); 198 | } 199 | 200 | if prefix.ends_with("/") || prefix.ends_with("\\") { 201 | prefix = &prefix[..prefix.len() - 1]; 202 | } 203 | 204 | if !path.starts_with(prefix) { 205 | return None; 206 | } 207 | 208 | if path.len() <= prefix.len() { 209 | return None; 210 | } 211 | 212 | if path.as_bytes()[prefix.len()] == b'/' || path.as_bytes()[prefix.len()] == b'\\' { 213 | return Some(&path[prefix.len() + 1..]); 214 | } else { 215 | return None; 216 | } 217 | } 218 | 219 | #[cfg(test)] 220 | mod test { 221 | #[test] 222 | fn remove_path_prefix() { 223 | assert_eq!( 224 | Some("abc.proto"), 225 | super::remove_path_prefix("xxx/abc.proto", "xxx") 226 | ); 227 | assert_eq!( 228 | Some("abc.proto"), 229 | super::remove_path_prefix("xxx/abc.proto", "xxx/") 230 | ); 231 | assert_eq!( 232 | Some("abc.proto"), 233 | super::remove_path_prefix("../xxx/abc.proto", "../xxx/") 234 | ); 235 | assert_eq!( 236 | Some("abc.proto"), 237 | super::remove_path_prefix("abc.proto", ".") 238 | ); 239 | assert_eq!( 240 | Some("abc.proto"), 241 | super::remove_path_prefix("abc.proto", "./") 242 | ); 243 | assert_eq!(None, super::remove_path_prefix("xxx/abc.proto", "yyy")); 244 | assert_eq!(None, super::remove_path_prefix("xxx/abc.proto", "yyy/")); 245 | } 246 | } 247 | --------------------------------------------------------------------------------