Compare commits

..

No commits in common. "master" and "1.0.1" have entirely different histories.

252 changed files with 10361 additions and 18506 deletions

View File

@ -1,14 +0,0 @@
---
# TODO(yannic): Enable buildifier and test on Windows and RBE (both unsupported by rules_closure).
platforms:
macos:
build_targets:
- //...
test_targets:
- //...
ubuntu1804:
build_targets:
- //...
test_targets:
- //...

View File

@ -1,2 +0,0 @@
# //third_party conatins git submodules.
third_party/

View File

@ -1,14 +0,0 @@
build --copt=-Wno-error=deprecated-declarations --host_copt=-Wno-error=deprecated-declarations
# Required until this is the default; expected in Bazel 7
common --enable_bzlmod
# Load any settings specific to the current user.
# .bazelrc.user should appear in .gitignore so that settings are not shared with team members
# This needs to be last statement in this
# config, as the user configuration should be able to overwrite flags from this file.
# See https://docs.bazel.build/versions/master/best-practices.html#bazelrc
# (Note that we use .bazelrc.user so the file appears next to .bazelrc in directory listing,
# rather than user.bazelrc as suggested in the Bazel docs)
try-import %workspace%/.bazelrc.user

View File

@ -1,3 +0,0 @@
**/dist
**/node_modules
packages/grpc-web/generated

View File

@ -1,42 +0,0 @@
name: Make ARM Plugins (Windows/macOS/Linux)
on:
workflow_dispatch:
inputs:
version_number:
description: 'Version number'
required: true
default: '1.x.x'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: 'recursive'
- name: Setup Zig
run: |
mkdir -p $HOME/.local/bin $HOME/.local/zig
curl 'https://ziglang.org/download/0.9.1/zig-linux-x86_64-0.9.1.tar.xz' | tar xJ --strip-components=1 --directory=$HOME/.local/zig
ln -s $HOME/.local/zig/zig $HOME/.local/bin/zig
echo "PATH=$PATH:$HOME/.local/bin" >> $GITHUB_ENV
- name: Build plugin
env:
VERSION: ${{ github.event.inputs.version_number }}
run: |
cd javascript/net/grpc/web/generator
zig build -Drelease-fast
- name: gen and verify sha256
run: |
cd javascript/net/grpc/web/generator/zig-out/bin
for exe in $(ls)
do
openssl dgst -sha256 -r -out $exe'.sha256' $exe
sha256sum -c $exe'.sha256'
done
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: plugin
path: javascript/net/grpc/web/generator/zig-out/bin/

View File

@ -1,32 +0,0 @@
name: Make Linux Plugin
on:
workflow_dispatch:
inputs:
version_number:
description: 'Version number'
required: true
default: '1.x.x'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build plugin docker image
run: docker-compose build prereqs protoc-plugin
- name: Copy binary from Docker image
run: |
docker cp $(docker create grpcweb/protoc-plugin):/github/grpc-web/javascript/net/grpc/web/generator/protoc-gen-grpc-web \
./protoc-gen-grpc-web-${{ github.event.inputs.version_number }}-linux-x86_64
- name: gen sha256
run: |
openssl dgst -sha256 -r -out protoc-gen-grpc-web-${{ github.event.inputs.version_number }}-linux-x86_64.sha256 \
protoc-gen-grpc-web-${{ github.event.inputs.version_number }}-linux-x86_64
- name: verify sha256
run: sha256sum -c protoc-gen-grpc-web-${{ github.event.inputs.version_number }}-linux-x86_64.sha256
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: plugin
path: protoc-gen-grpc-web*

View File

@ -1,48 +0,0 @@
name: Make MacOS Plugin
on:
workflow_dispatch:
inputs:
version_number:
description: 'Version number'
required: true
default: '1.x.x'
jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- name: Install build utils
run: brew install coreutils automake
- name: Checkout protobuf code
run: |
./scripts/init_submodules.sh
# Protobuf build instructions from:
# https://github.com/protocolbuffers/protobuf/blob/master/src/README.md
- name: Build protobuf (configure & make)
run: |
cd ./third_party/protobuf
./autogen.sh
./configure
make -j$(nproc)
make install
- name: Remove dynamite dependencies (similar to `-static` on linux)
run: rm /usr/local/lib/libproto*.dylib
- name: make
run: make clean && make plugin
- name: move
run: |
mv javascript/net/grpc/web/generator/protoc-gen-grpc-web \
./protoc-gen-grpc-web-${{ github.event.inputs.version_number }}-darwin-x86_64
- name: gen sha256
run: |
openssl dgst -sha256 -r -out protoc-gen-grpc-web-${{ github.event.inputs.version_number }}-darwin-x86_64.sha256 \
protoc-gen-grpc-web-${{ github.event.inputs.version_number }}-darwin-x86_64
- name: verify sha256
run: sha256sum -c protoc-gen-grpc-web-${{ github.event.inputs.version_number }}-darwin-x86_64.sha256
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: plugin
path: protoc-gen-grpc-web*

View File

@ -1,37 +0,0 @@
name: Make Windows Plugin
on:
workflow_dispatch:
inputs:
version_number:
description: 'Version number'
required: true
default: '1.x.x'
jobs:
build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- name: Print Bazel version
run: |
bazel version
- name: build
run: bazel build javascript/net/grpc/web/generator:protoc-gen-grpc-web
- name: move
run: |
mv bazel-bin/javascript/net/grpc/web/generator/protoc-gen-grpc-web.exe \
./protoc-gen-grpc-web-${{ github.event.inputs.version_number }}-windows-x86_64.exe
shell: bash
- name: gen sha256
run: |
openssl dgst -sha256 -r -out protoc-gen-grpc-web-${{ github.event.inputs.version_number }}-windows-x86_64.exe.sha256 \
protoc-gen-grpc-web-${{ github.event.inputs.version_number }}-windows-x86_64.exe
shell: bash
# TODO: Check sha256 (sha256sum not available for now. )
#- name: verify sha256
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: plugin
path: protoc-gen-grpc-web*

View File

@ -1,36 +0,0 @@
name: Publish Stable Source Archive
on:
release:
types: [published]
jobs:
# Whenever a release is published, this uploads an accompanying stable source archive.
#
# Github doesn't guarantee stability of source archives for more than 6 months[1].
# More stability is required by projects like Bazel Central Registry[2][3].
#
# [1]: https://github.blog/open-source/git/update-on-the-future-stability-of-source-code-archives-and-hashes/
# [2]: https://github.com/bazelbuild/bazel-central-registry/blob/main/docs/README.md#validations
# [3]: https://blog.bazel.build/2023/02/15/github-archive-checksum.html
bazel-release-archive:
defaults:
run:
# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
shell: /usr/bin/bash -euxo pipefail {0}
env:
# github.ref_name is defined here:
# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/accessing-contextual-information-about-workflow-runs#github-context
TAG: ${{github.ref_name}}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout
# GITHUB_REF is defined here:
# https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#default-environment-variables
- run: git archive --format zip --prefix "grpc-web-$TAG/" --output "grpc-web-source-${TAG}.zip" "$GITHUB_REF"
- run: git archive --format tar.gz --prefix "grpc-web-$TAG/" --output "grpc-web-source-${TAG}.tar.gz" "$GITHUB_REF"
- run: gh release upload "${TAG}" "grpc-web-source-${TAG}.zip" "grpc-web-source-${TAG}.tar.gz"
env:
GH_TOKEN: ${{ github.token }}

8
.gitignore vendored
View File

@ -1,4 +1,3 @@
.vscode
bazel-bin
bazel-genfiles
bazel-grpc-web
@ -6,10 +5,3 @@ bazel-out
bazel-testlogs
*.o
protoc-gen-*
.DS_Store
target
.project
.classpath
.settings
zig-out
zig-cache

15
.gitmodules vendored
View File

@ -1,3 +1,12 @@
[submodule "third_party/protobuf"]
path = third_party/protobuf
url = https://github.com/protocolbuffers/protobuf.git
[submodule "third_party/grpc"]
path = third_party/grpc
url = https://github.com/grpc/grpc.git
[submodule "third_party/openssl"]
path = third_party/openssl
url = https://github.com/openssl/openssl.git
[submodule "third_party/nginx/src"]
path = third_party/nginx/src
url = https://nginx.googlesource.com/nginx
[submodule "third_party/closure-library"]
path = third_party/closure-library
url = https://github.com/google/closure-library.git

View File

@ -1,23 +1,20 @@
# gRPC-Web features for browser (HTML) clients
Due to browser limitation, gRPC-Web uses a different transport
Due to browser limitation, gRPC-Web supports a different transport
than the [HTTP/2 based gRPC protocol](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md).
The difference between the gRPC-Web
protocol and the HTTP/2 based gRPC protocol is specified in the core gRPC repo as [PROTOCOL-WEB](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md).
In addition to the wire-transport spec, gRPC-Web also supports features that are unique to browser (HTML) clients.
This document serves as the official spec for those features. As the Web platform evolves,
This document is the official spec for those features. As the Web platform evolves,
we expect some of those features will evolve too or become deprecated.
On the server-side, [Envoy](https://www.envoyproxy.io/) is the official proxy with built-in gRPC-Web support. New features will be implemented in Envoy first. For [in-process gRPC-Web support](https://github.com/grpc/grpc-web/blob/master/doc/in-process-proxy.md), we recommend that the gRPC-Web module implement only a minimum set of features, e.g. to enable local development. Those features are identified as mandatory features in this doc.
# CORS support
* Should follow the [CORS spec](https://developer.mozilla.org/en-US/docs/Web/HTTP/Server-Side_Access_Control) (Mandatory)
* Should follow the [CORS spec](https://developer.mozilla.org/en-US/docs/Web/HTTP/Server-Side_Access_Control)
* Access-Control-Allow-Credentials to allow Authorization headers
* Access-Control-Allow-Methods to allow POST and (preflight) OPTIONS only
* Access-Control-Allow-Headers to whatever the preflight request carries
* Access-Control-Expose-Headers to allow Javascript access to `grpc-status,grpc-message` headers.
* The client library is expected to support header overwrites to avoid preflight
* https://github.com/whatwg/fetch/issues/210
* CSP support to be specified
@ -26,12 +23,6 @@ On the server-side, [Envoy](https://www.envoyproxy.io/) is the official proxy wi
A grpc-web gateway is recommended to overwrite the default 200 status code and map any gateway-generated or server-generated error status to standard HTTP status codes (such as 503) when it is possible. This will help with debugging and may also improve security protection for web apps.
# URL query params
To enable query-param based routing rules in reverse proxies and to avoid CORS preflight, a grpc-web client may "advertise" certain request data or metadata as query params. The grpc-web proxy module should remove the query params before the request is sent to the downstream gRPC server.
The actual data in query params is not interpreted by grpc-web libraries. Standard URL encoding rules shoud be followed to encode those query params.
# Security
* XSRF, XSS policy to be published

View File

@ -1,266 +0,0 @@
[//]: # (GENERATED FILE -- DO NOT EDIT!)
[//]: # (See scripts/release_notes.py for more details.)
## 1.5.0
- [#1369](https://github.com/grpc/grpc-web/pull/1369) (Typescript) Mark some `metadata` parameters as optional @andrewmbenton
- [#1335](https://github.com/grpc/grpc-web/pull/1335) Update Debian (and other deps) and remove Java In-process Proxy
- [#1334](https://github.com/grpc/grpc-web/pull/1334) Allow mixed-case headers
- [#1330](https://github.com/grpc/grpc-web/pull/1330) Update ES6 .d.ts imports with comment about corresponding proto import... @gonzojive
- [#1313](https://github.com/grpc/grpc-web/pull/1313) Update ES6 imports with comment about corresponding proto import path. @reddaly
## 1.4.2
- [#1289](https://github.com/grpc/grpc-web/pull/1289) Expose getName() in MethodDescriptor and fix TS definitions.
- [#1230](https://github.com/grpc/grpc-web/pull/1230) GrpcWebClientReadableStream: keep falsy data @pro-wh
## 1.4.1
- [#1286](https://github.com/grpc/grpc-web/pull/1286) Fix duplicate dot in enum name (when "package" is specified)
## 1.4.0
### Major Features
- [#1249](https://github.com/grpc/grpc-web/pull/1249) Use Zig to build aarch64 binaries @hronro
- [#1203](https://github.com/grpc/grpc-web/pull/1203) Github Actions (workflows) for building `protoc-gen-grpc-web` plugins
### Other Changes
- [#1279](https://github.com/grpc/grpc-web/pull/1279) Fixes the status codes ordering in typescript definitions @chandraaditya
- [#1278](https://github.com/grpc/grpc-web/pull/1278) Fix Enum with module in generated TS interface.
- [#1254](https://github.com/grpc/grpc-web/pull/1254) Remove Trailing Slashes from Hostname @jkjk822
- [#1252](https://github.com/grpc/grpc-web/pull/1252) Fix Zig setup step in CI @hronro
- [#1231](https://github.com/grpc/grpc-web/pull/1231) Add version flag and version info in generated code @meling
- [#1225](https://github.com/grpc/grpc-web/pull/1225) Improve error message & Internal code sync
- [#1222](https://github.com/grpc/grpc-web/pull/1222) Update envoy version to 1.22 (with config updates) @tomk9
- [#1211](https://github.com/grpc/grpc-web/pull/1211) Upgrade protobuf and grpc deps @aapeliv
- [#1199](https://github.com/grpc/grpc-web/pull/1199) Revert "Expose MethodDescriptor's public methods"
## 1.3.1
- [#1184](https://github.com/grpc/grpc-web/pull/1184) Correctly support proto3 optional fields in commonjs+dts .d.ts output @mattnathan
- [#1173](https://github.com/grpc/grpc-web/pull/1173) Update envoy version to 1.20
- [#1172](https://github.com/grpc/grpc-web/pull/1172) Fix issue where **no RPC is issued when `deadline` is specified.**
- [#1167](https://github.com/grpc/grpc-web/pull/1167) Fix missing TypeScript return type for `serverStreaming` calls. @lukasmoellerch
- [#1166](https://github.com/grpc/grpc-web/pull/1166) Add missing exports from `RpcError` and add test.
- [#1164](https://github.com/grpc/grpc-web/pull/1164) Add missing class exports @tinrab
- [#1160](https://github.com/grpc/grpc-web/pull/1160) Expose MethodDescriptor's public methods @tomferreira
## 1.3.0
### Major Features
- [#1139](https://github.com/grpc/grpc-web/pull/1139) Improve error type with `RpcError` & internal code sync (contributor: @TomiBelan)
+ (experimental) Typescript users need to update type references from `Error` -> `RpcError`
### Other Changes
- [#1140](https://github.com/grpc/grpc-web/pull/1140) Improve `RpcError.code` typing & internal code sync (contributor: @richieforeman)
- [#1138](https://github.com/grpc/grpc-web/pull/1138) Remove Bazel in Javascript toolchain
- [#1137](https://github.com/grpc/grpc-web/pull/1137) Revamp Closure JsUnit tests runtime and optimize test/build flows.
- [#1115](https://github.com/grpc/grpc-web/pull/1115) Bump Bazel version -> 4.1.0 and Protobuf version -> 3.17.3
- [#1107](https://github.com/grpc/grpc-web/pull/1107) Allow for custom install prefix @06kellyjac
- [#1063](https://github.com/grpc/grpc-web/pull/1063) Also set timeout on HTTP request if deadline for grpc call is set @Yannic
- [#1004](https://github.com/grpc/grpc-web/pull/1004) Bump closure library version to v20201102
- [#1002](https://github.com/grpc/grpc-web/pull/1002) Bump Envoy version to 1.16.1
- [#998](https://github.com/grpc/grpc-web/pull/998) Fix GrpcWebClientBaseOptions types in index.d.ts @acalvo
- [#971](https://github.com/grpc/grpc-web/pull/971) Add grpc.web.ClientOptions to better document options and add type res... @jennnnny
- [#969](https://github.com/grpc/grpc-web/pull/969) Fix non-determinism in code generator
- [#941](https://github.com/grpc/grpc-web/pull/941) Fix Protobuf .d.ts typings for .proto files without package @Yannic
## 1.2.1
- [#910](https://github.com/grpc/grpc-web/pull/910) Add test to show how to access metadata in interceptor
- [#903](https://github.com/grpc/grpc-web/pull/903) Add error handling to a few error conditions
- [#886](https://github.com/grpc/grpc-web/pull/886) Add missing types definitions
- [#885](https://github.com/grpc/grpc-web/pull/885) Bump Envoy to 1.15.0
- [#884](https://github.com/grpc/grpc-web/pull/884) Update protoc plugin to support Proto3 optional
- [#882](https://github.com/grpc/grpc-web/pull/882) Add @interface MethodDescroptorInterface [@Jennnnny](https://github.com/Jennnnny)
- [#880](https://github.com/grpc/grpc-web/pull/880) Update Bazel to 3.3.1 [@Yannic](https://github.com/Yannic)
- [#874](https://github.com/grpc/grpc-web/pull/874) Add removeListener and missing metadata event types [@danielthank](https://github.com/danielthank)
- [#872](https://github.com/grpc/grpc-web/pull/872) [bazel] Introduce grpc_web_toolchain [@Yannic](https://github.com/Yannic)
- [#871](https://github.com/grpc/grpc-web/pull/871) [generator] Refactor dependency management [@Yannic](https://github.com/Yannic)
- [#869](https://github.com/grpc/grpc-web/pull/869) Add scripts to run interop-tests on grpc-web Java connector
## 1.2.0
### Major Features
- [#847](https://github.com/grpc/grpc-web/pull/847) Allow multiple .on() callbacks and fix issue with non-OK status
### Other Changes
- [#859](https://github.com/grpc/grpc-web/pull/859) Fix envoy.yaml deprecated fields [@dmaixner](https://github.com/dmaixner)
- [#858](https://github.com/grpc/grpc-web/pull/858) Refactor error handling in grpcwebclientbase
- [#857](https://github.com/grpc/grpc-web/pull/857) Migrate to ES6 classes
- [#852](https://github.com/grpc/grpc-web/pull/852) Update to use @grpc/grpc-js node package, and update helloworld exampl...
- [#851](https://github.com/grpc/grpc-web/pull/851) Add a ThenableCall base class for the promise-based unaryCall function [@Jennnnny](https://github.com/Jennnnny)
- [#844](https://github.com/grpc/grpc-web/pull/844) Fix code generator bug and add tests
- [#833](https://github.com/grpc/grpc-web/pull/833) Add proper author attribution to release notes / changelog
- [#827](https://github.com/grpc/grpc-web/pull/827) Splitting callback based client and Promise based client into multiple... [@Jennnnny](https://github.com/Jennnnny)
- [#822](https://github.com/grpc/grpc-web/pull/822) use explicit envoy release tag [@xsbchen](https://github.com/xsbchen)
- [#821](https://github.com/grpc/grpc-web/pull/821) Experimental Feature: Add ES6 import style [@Yannic](https://github.com/Yannic)
- [#738](https://github.com/grpc/grpc-web/pull/738) Avoid double slash in url when client hostname has tailing slash [@hanabi1224](https://github.com/hanabi1224)
## 1.1.0
### Major Features
- [#785](https://github.com/grpc/grpc-web/pull/785) grpc-web interceptors implementation [@Jennnnny](https://github.com/Jennnnny)
- [#772](https://github.com/grpc/grpc-web/pull/772) Add interop test spec and interop tests
### Other Changes
- [#818](https://github.com/grpc/grpc-web/pull/818) All java connector interop tests are passing now
- [#804](https://github.com/grpc/grpc-web/pull/804) Fix a bug in test: callback not properly intercepted
- [#801](https://github.com/grpc/grpc-web/pull/801) Trying to speed up tests
- [#797](https://github.com/grpc/grpc-web/pull/797) Split basic tests with interop tests
- [#780](https://github.com/grpc/grpc-web/pull/780) Add missing separator to imports from external files [@tomiaijo](https://github.com/tomiaijo)
- [#777](https://github.com/grpc/grpc-web/pull/777) Add .on(metadata,...) callback to distinguish initial metadata
- [#764](https://github.com/grpc/grpc-web/pull/764) [generator] Move options parsing into dedicated class [@Yannic](https://github.com/Yannic)
- [#761](https://github.com/grpc/grpc-web/pull/761) Update generic client [@Jennnnny](https://github.com/Jennnnny)
- [#756](https://github.com/grpc/grpc-web/pull/756) Add eval test for TypeScript generated code
- [#752](https://github.com/grpc/grpc-web/pull/752) Disable static checkers on generated js files [@IagoLast](https://github.com/IagoLast)
- [#747](https://github.com/grpc/grpc-web/pull/747) Enable builder pattern in Typescript protobuf messages. [@Orphis](https://github.com/Orphis)
- [#746](https://github.com/grpc/grpc-web/pull/746) Generate Promise based overloads for unary calls in Typescript [@Orphis](https://github.com/Orphis)
- [#745](https://github.com/grpc/grpc-web/pull/745) [bazel] Update rules_closure + fix linter warnings [@Yannic](https://github.com/Yannic)
- [#734](https://github.com/grpc/grpc-web/pull/734) Allow GrpcWebStreamParser to accept Uint8Array [@travikk](https://github.com/travikk)
- [#723](https://github.com/grpc/grpc-web/pull/723) Update bazel version
- [#720](https://github.com/grpc/grpc-web/pull/720) Fix grpcwebproxy interop
- [#716](https://github.com/grpc/grpc-web/pull/716) allow_origin is deprecated in latest envoy server [@noconnor](https://github.com/noconnor)
- [#695](https://github.com/grpc/grpc-web/pull/695) Fix issue 632 (double execution of callback) [@hfinger](https://github.com/hfinger)
- [#692](https://github.com/grpc/grpc-web/pull/692) Do not hardcode CXX to g++
## 1.0.7
- [#671](https://github.com/grpc/grpc-web/pull/671) Add metadata to error callback
- [#668](https://github.com/grpc/grpc-web/pull/668) Remove stream_body.proto
- [#665](https://github.com/grpc/grpc-web/pull/665) Add config for Bazel CI [@Yannic](https://github.com/Yannic)
- [#663](https://github.com/grpc/grpc-web/pull/663) nginx example Expose-Headers add Grpc-Message,Grpc-Status [@zsluedem](https://github.com/zsluedem)
- [#657](https://github.com/grpc/grpc-web/pull/657) Ensure that the end callback is called [@vbfox](https://github.com/vbfox)
- [#655](https://github.com/grpc/grpc-web/pull/655) Use closure compiler from npm in build.js [@vbfox](https://github.com/vbfox)
- [#654](https://github.com/grpc/grpc-web/pull/654) Ignore MacOS .DS_Store files [@vbfox](https://github.com/vbfox)
- [#652](https://github.com/grpc/grpc-web/pull/652) Fix error callback
- [#644](https://github.com/grpc/grpc-web/pull/644) Add CallOptions class [@Jennnnny](https://github.com/Jennnnny)
- [#641](https://github.com/grpc/grpc-web/pull/641) Add/update GOVERNANCE.md and CONTRIBUTING.md
- [#635](https://github.com/grpc/grpc-web/pull/635) Fix generated code return type, and remove unused var
- [#628](https://github.com/grpc/grpc-web/pull/628) Added API for simple unary call [@Jennnnny](https://github.com/Jennnnny)
- [#621](https://github.com/grpc/grpc-web/pull/621) Fix output directory name when using import_style=typescript [@asv](https://github.com/asv)
- [#619](https://github.com/grpc/grpc-web/pull/619) Return specific grpc status code on http error [@harmangakhal](https://github.com/harmangakhal)
- [#618](https://github.com/grpc/grpc-web/pull/618) Generate method descriptors into multiple files [@Jennnnny](https://github.com/Jennnnny)
- [#617](https://github.com/grpc/grpc-web/pull/617) Remove `enabled` deprecated field [@gsalisi](https://github.com/gsalisi)
- [#615](https://github.com/grpc/grpc-web/pull/615) Add support in code generator for printing only method descriptors [@Jennnnny](https://github.com/Jennnnny)
- [#608](https://github.com/grpc/grpc-web/pull/608) Fix status and error callbacks
## 1.0.6
- [#604](https://github.com/grpc/grpc-web/pull/604) Add option to set withCredentials to true
- [#603](https://github.com/grpc/grpc-web/pull/603) Adding some groundwork for generic client [@Jennnnny](https://github.com/Jennnnny)
- [#600](https://github.com/grpc/grpc-web/pull/600) Add generated code eval test
- [#599](https://github.com/grpc/grpc-web/pull/599) fix wrong package name of input type [@lqs](https://github.com/lqs)
- [#593](https://github.com/grpc/grpc-web/pull/593) Fix: Helloworld Example - Enabled Deprecation [@gary-lo](https://github.com/gary-lo)
## 1.0.5
- [#582](https://github.com/grpc/grpc-web/pull/582) Ensure credentials are not undefined in typescript [@Globegitter](https://github.com/Globegitter)
- [#579](https://github.com/grpc/grpc-web/pull/579) Uppercase enum keys in TypeScript definitions [@benfoxbotica](https://github.com/benfoxbotica)
- [#578](https://github.com/grpc/grpc-web/pull/578) Fix depset issues with/upgrade to Bazel 0.27.1 [@factuno-db](https://github.com/factuno-db)
- [#567](https://github.com/grpc/grpc-web/pull/567) Introducing MethodDescriptor [@Jennnnny](https://github.com/Jennnnny)
- [#559](https://github.com/grpc/grpc-web/pull/559) Adding new fields to MethodInfo [@Jennnnny](https://github.com/Jennnnny)
- [#556](https://github.com/grpc/grpc-web/pull/556) Add fix for deadline of strings, NaN, Infinity and -Infinity [@CatEars](https://github.com/CatEars)
- [#546](https://github.com/grpc/grpc-web/pull/546) Changes to deserializeBinary API
- [#540](https://github.com/grpc/grpc-web/pull/540) Method Derserializer should take Uint8Array [@pnegahdar](https://github.com/pnegahdar)
- [#519](https://github.com/grpc/grpc-web/pull/519) remove duplicated has$field$ method for oneof [@yangjian](https://github.com/yangjian)
- [#512](https://github.com/grpc/grpc-web/pull/512) Make client args `credentials` and `options` optional [@jonahbron](https://github.com/jonahbron)
## 1.0.4
- [#502](https://github.com/grpc/grpc-web/pull/502) Attempt to fix flakiness of 'bazel test' [@Yannic](https://github.com/Yannic)
- [#497](https://github.com/grpc/grpc-web/pull/497) Remove a return that skip emission of end callback [@tinou98](https://github.com/tinou98)
- [#494](https://github.com/grpc/grpc-web/pull/494) [bazel] Migrate protobuf info provider to new-style one [@Yannic](https://github.com/Yannic)
- [#482](https://github.com/grpc/grpc-web/pull/482) feature: Typings codegen for bytes field type [@shaxbee](https://github.com/shaxbee)
- [#481](https://github.com/grpc/grpc-web/pull/481) Add module alias to enums for Typescript [@rogchap](https://github.com/rogchap)
- [#460](https://github.com/grpc/grpc-web/pull/460) add typescript definition for Oneof fields [@yangjian](https://github.com/yangjian)
- [#452](https://github.com/grpc/grpc-web/pull/452) fix: exclude map entry message from typings, fix optional values [@shaxbee](https://github.com/shaxbee)
- [#448](https://github.com/grpc/grpc-web/pull/448) Export Map types correctly, optional getter/setters for message types [@shaxbee](https://github.com/shaxbee)
- [#444](https://github.com/grpc/grpc-web/pull/444) feature: Messages in typings extending jspb.Message [@shaxbee](https://github.com/shaxbee)
- [#433](https://github.com/grpc/grpc-web/pull/433) Match name nesting and imports in .d.ts with .js files [@shaxbee](https://github.com/shaxbee)
- [#430](https://github.com/grpc/grpc-web/pull/430) Use camelCase in AsObject definition [@johanbrandhorst](https://github.com/johanbrandhorst)
- [#429](https://github.com/grpc/grpc-web/pull/429) Fix type error in serverStreaming method [@johanbrandhorst](https://github.com/johanbrandhorst)
- [#427](https://github.com/grpc/grpc-web/pull/427) Promise function should use ES5 functions rather than fat arrows (IE b... [@rogchap](https://github.com/rogchap)
- [#422](https://github.com/grpc/grpc-web/pull/422) Enable ADVANCED_OPTIMIZATIONS in Closure Compiler [@jjbubudi](https://github.com/jjbubudi)
- [#421](https://github.com/grpc/grpc-web/pull/421) [bazel] Upgrade to 0.22.0 [@Yannic](https://github.com/Yannic)
- [#413](https://github.com/grpc/grpc-web/pull/413) Emit status event on empty stream response [@shaxbee](https://github.com/shaxbee)
- [#409](https://github.com/grpc/grpc-web/pull/409) Fix metadata typings for TS client [@bpicolo](https://github.com/bpicolo)
- [#404](https://github.com/grpc/grpc-web/pull/404) Generate Typescript definition for top level Enums [@rogchap](https://github.com/rogchap)
## 1.0.3
- [#391](https://github.com/grpc/grpc-web/pull/391) A script to compile protoc plugin
- [#385](https://github.com/grpc/grpc-web/pull/385) Codegen: Support nested types and enums [@shaxbee](https://github.com/shaxbee)
- [#368](https://github.com/grpc/grpc-web/pull/368) Make the bazel rules work with current rules_closure. [@factuno-db](https://github.com/factuno-db)
- [#367](https://github.com/grpc/grpc-web/pull/367) update examples to use addService [@mitchdraft](https://github.com/mitchdraft)
- [#365](https://github.com/grpc/grpc-web/pull/365) Fix response header value with colon
- [#362](https://github.com/grpc/grpc-web/pull/362) Fix the method name clashes for generated commonjs files [@weilip1803](https://github.com/weilip1803)
- [#360](https://github.com/grpc/grpc-web/pull/360) Fix the import path for generated typescript files [@at-ishikawa](https://github.com/at-ishikawa)
## 1.0.2
## 1.0.1
- [#354](https://github.com/grpc/grpc-web/pull/354) [dts] Generate PromiseClient type definitions in d.ts file [@at-ishikawa](https://github.com/at-ishikawa)
- [#352](https://github.com/grpc/grpc-web/pull/352) Add a max grpc timeout to the echo example. [@mjduijn](https://github.com/mjduijn)
- [#348](https://github.com/grpc/grpc-web/pull/348) Fix output dts about 'repeated' for --grpc-web_out=import_style=common... [@rybbchao](https://github.com/rybbchao)
- [#345](https://github.com/grpc/grpc-web/pull/345) update typescript generation to work in strict mode [@henriiik](https://github.com/henriiik)
- [#330](https://github.com/grpc/grpc-web/pull/330) Use official rules_closure repository [@Yannic](https://github.com/Yannic)
## 1.0.0
- [#314](https://github.com/grpc/grpc-web/pull/314) Add a unit test for proto with no package
- [#313](https://github.com/grpc/grpc-web/pull/313) Show how deadline can be set
- [#311](https://github.com/grpc/grpc-web/pull/311) Document how to prevent Envoy to timeout streaming [@mitar](https://github.com/mitar)
- [#310](https://github.com/grpc/grpc-web/pull/310) Correctly generate code if package name is empty [@mitar](https://github.com/mitar)
- [#304](https://github.com/grpc/grpc-web/pull/304) Add a simple Hello World Guide
- [#303](https://github.com/grpc/grpc-web/pull/303) Error code should be number
- [#276](https://github.com/grpc/grpc-web/pull/276) Fix plugin compile error
- [#272](https://github.com/grpc/grpc-web/pull/272) Fix cpp warnings
## 0.4.0
- [#263](https://github.com/grpc/grpc-web/pull/263) Make "Quick" start quicker
- [#258](https://github.com/grpc/grpc-web/pull/258) Experimental Typescript support
- [#257](https://github.com/grpc/grpc-web/pull/257) Fix bug with button in example
## 0.3.0
- [#249](https://github.com/grpc/grpc-web/pull/249) Various fixes to codegen plugin
- [#247](https://github.com/grpc/grpc-web/pull/247) Add generated code unit test
- [#240](https://github.com/grpc/grpc-web/pull/240) webpack demo
- [#239](https://github.com/grpc/grpc-web/pull/239) Expose response metadata for unary calls
- [#219](https://github.com/grpc/grpc-web/pull/219) Add bazel rule closure_grpc_web_library [@Yannic](https://github.com/Yannic)
- [#217](https://github.com/grpc/grpc-web/pull/217) Added multiple proxies interoperability
## 0.2.0
- [#212](https://github.com/grpc/grpc-web/pull/212) Added commonjs-example Dockerfile
- [#211](https://github.com/grpc/grpc-web/pull/211) commonjs support with import_style option [@zaucy](https://github.com/zaucy)
- [#210](https://github.com/grpc/grpc-web/pull/210) grpcweb npm runtime module [@zaucy](https://github.com/zaucy)
- [#209](https://github.com/grpc/grpc-web/pull/209) Add bazel integration and tests
- [#206](https://github.com/grpc/grpc-web/pull/206) Surface underlying XHR errors better
- [#185](https://github.com/grpc/grpc-web/pull/185) Support for proto files without packages [@zaucy](https://github.com/zaucy)

View File

@ -3,10 +3,6 @@
We definitely welcome patches and contribution to gRPC-Web! Here is some guideline
and information about how to do so.
Please read the gRPC
organization's [governance rules](https://github.com/grpc/grpc-community/blob/master/governance.md)
and [contribution guidelines](https://github.com/grpc/grpc-community/blob/master/CONTRIBUTING.md) before proceeding.
## Getting started
### Legal requirements

View File

@ -1 +0,0 @@
This repository is governed by the gRPC organization's [governance rules](https://github.com/grpc/grpc-community/blob/master/governance.md).

View File

@ -1,16 +0,0 @@
This page lists all active maintainers of this repository. If you were a
maintainer and would like to add your name to the Emeritus list, please send us a
PR.
See [GOVERNANCE.md](https://github.com/grpc/grpc-community/blob/master/governance.md)
for governance guidelines and how to become a maintainer.
See [CONTRIBUTING.md](https://github.com/grpc/grpc-community/blob/master/CONTRIBUTING.md)
for general contribution guidelines.
## Maintainers (in alphabetical order)
- [sampajano](https://github.com/sampajano), Google Inc.
- [wenbozhu](https://github.com/wenbozhu), Google Inc.
## Emeritus Maintainers (in alphabetical order)
- [fengli79](https://github.com/fengli79)
- [stanley-cheung](https://github.com/stanley-cheung)

View File

@ -1,24 +0,0 @@
"""
A bazel module for the grpc-web project.
Visit https://grpc.io/ and https://github.com/grpc/grpc-web for
more information about the project.
"""
module(
name = "grpc-web",
version = "1.6.0",
compatibility_level = 1,
repo_name = "com_github_grpc_grpc_web",
)
bazel_dep(name = "protobuf", version = "27.1", repo_name = "com_google_protobuf")
bazel_dep(name = "grpc", version = "1.65.0", repo_name = "com_github_grpc_grpc")
bazel_dep(name = "rules_cc", version = "0.0.2")
bazel_dep(name = "rules_proto", version = "6.0.2")
# Needed to resolve https://github.com/bazelbuild/bazel-central-registry/issues/2538.
single_version_override(
module_name = "grpc-java",
version = "1.64.0",
)

File diff suppressed because it is too large Load Diff

114
Makefile
View File

@ -1,13 +1,119 @@
OS := $(shell uname)
CC := g++
ROOT_DIR := $(shell pwd)
GRPC_GATEWAY_PROTOS := $(ROOT_DIR)/net/grpc/gateway/protos
PROTO_INC := $(ROOT_DIR)/third_party/grpc/third_party/protobuf/include
PROTO_SRC := $(ROOT_DIR)/third_party/grpc/third_party/protobuf/src
PROTO_LIB := $(PROTO_SRC)/.libs
PROTOC := $(PROTO_SRC)/protoc
GRPC_INC := $(ROOT_DIR)/third_party/grpc/include
GRPC_SRC := $(ROOT_DIR)/third_party/grpc
GRPC_LIB := $(ROOT_DIR)/third_party/grpc/libs/opt
all: clean
all: clean package_static
protos:
cd "$(ROOT_DIR)" && LD_LIBRARY_PATH="$(PROTO_LIB):$(GRPC_LIB)" "$(PROTOC)" \
--proto_path="$(GRPC_GATEWAY_PROTOS)" \
--proto_path="$(PROTO_SRC)" "$(GRPC_GATEWAY_PROTOS)/pair.proto" \
--cpp_out="$(GRPC_GATEWAY_PROTOS)"
cd "$(ROOT_DIR)" && LD_LIBRARY_PATH="$(PROTO_LIB):$(GRPC_LIB)" "$(PROTOC)" \
--proto_path="$(GRPC_GATEWAY_PROTOS)" \
--proto_path="$(PROTO_SRC)" "$(GRPC_GATEWAY_PROTOS)/stream_body.proto" \
--cpp_out="$(GRPC_GATEWAY_PROTOS)"
NGINX_DIR := third_party/nginx
NGINX_LD_OPT := -L"$(PROTO_LIB)" -L"$(GRPC_LIB)" -lgrpc++ \
-lgrpc -lprotobuf -lpthread -ldl -lrt -lstdc++ -lm
ifeq ($(OS), Darwin)
NGINX_LD_OPT := -L"$(PROTO_LIB)" -L"$(GRPC_LIB)" -lgrpc++ \
-lgrpc -lprotobuf -lpthread -lstdc++ -lm
endif
NGINX_STATIC_LD_OPT := -L"$(PROTO_LIB)" -L"$(GRPC_LIB)" \
-l:libgrpc++.a -l:libgrpc.a -l:libprotobuf.a -lpthread -ldl \
-lrt -lstdc++ -lm
ifeq ($(OS), Darwin)
NGINX_STATIC_LD_OPT := $(NGINX_LD_OPT)
endif
nginx_config:
cd "$(NGINX_DIR)/src" && LD_LIBRARY_PATH="$(PROTO_LIB):$(GRPC_LIB)" \
auto/configure \
--with-http_ssl_module \
--with-http_v2_module \
--with-cc-opt="-I /usr/local/include -I $(ROOT_DIR) -I $(PROTO_INC) -I $(PROTO_SRC) \
-I $(GRPC_INC) -I $(GRPC_SRC)" \
--with-ld-opt="$(NGINX_LD_OPT)" \
--with-openssl="$(ROOT_DIR)/third_party/openssl" \
--add-module="$(ROOT_DIR)/net/grpc/gateway/nginx"
nginx_config_static:
cd "$(NGINX_DIR)/src" && LD_LIBRARY_PATH="$(PROTO_LIB):$(GRPC_LIB)" \
auto/configure \
--with-http_ssl_module \
--with-http_v2_module \
--with-cc-opt="-I /usr/local/include -I $(ROOT_DIR) -I $(PROTO_INC) -I $(PROTO_SRC) \
-I $(GRPC_INC) -I $(GRPC_SRC)" \
--with-ld-opt="$(NGINX_STATIC_LD_OPT)" \
--add-module="$(ROOT_DIR)/net/grpc/gateway/nginx"
nginx: protos nginx_config
cd "$(NGINX_DIR)/src" && make
nginx_static: protos nginx_config_static
cd "$(NGINX_DIR)/src" && make
package: nginx
mkdir -p "$(ROOT_DIR)"/gConnector/conf
cp "$(ROOT_DIR)"/third_party/nginx/src/conf/* "$(ROOT_DIR)"/gConnector/conf
cp "$(ROOT_DIR)"/net/grpc/gateway/nginx/package/nginx.conf \
"$(ROOT_DIR)"/gConnector/conf
cp "$(ROOT_DIR)"/net/grpc/gateway/nginx/package/nginx.sh \
"$(ROOT_DIR)"/gConnector
cp "$(ROOT_DIR)"/third_party/nginx/src/objs/nginx \
"$(ROOT_DIR)"/gConnector
cd "$(ROOT_DIR)" && zip -r gConnector.zip gConnector/*
package_static: nginx_static
mkdir -p "$(ROOT_DIR)"/gConnector_static/conf
cp "$(ROOT_DIR)"/third_party/nginx/src/conf/* \
"$(ROOT_DIR)"/gConnector_static/conf
cp "$(ROOT_DIR)"/net/grpc/gateway/nginx/package/nginx.conf \
"$(ROOT_DIR)"/gConnector_static/conf
cp "$(ROOT_DIR)"/net/grpc/gateway/nginx/package/nginx.sh \
"$(ROOT_DIR)"/gConnector_static
cp "$(ROOT_DIR)"/third_party/nginx/src/objs/nginx \
"$(ROOT_DIR)"/gConnector_static
cd "$(ROOT_DIR)" && zip -r gConnector_static.zip gConnector_static/*
plugin:
cd "$(ROOT_DIR)"/javascript/net/grpc/web/generator && make
cd "$(ROOT_DIR)"/javascript/net/grpc/web && make
install-plugin:
cd "$(ROOT_DIR)"/javascript/net/grpc/web/generator && make install
cd "$(ROOT_DIR)"/javascript/net/grpc/web && make install
example: plugin
cd "$(ROOT_DIR)"/net/grpc/gateway/examples/echo && make
standalone-proxy: package_static
cd "$(ROOT_DIR)"/net/grpc/gateway/examples/echo && make standalone-proxy
echo_server:
cd "$(ROOT_DIR)"/net/grpc/gateway/examples/echo && make echo_server
client: plugin
cd "$(ROOT_DIR)"/net/grpc/gateway/examples/echo && make client
install-example:
cd "$(ROOT_DIR)"/net/grpc/gateway/examples/echo && make install
clean:
cd "$(ROOT_DIR)"/javascript/net/grpc/web/generator && make clean
cd "$(ROOT_DIR)" && rm -rf objs gConnector gConnector_static \
third_party/nginx/src/objs third_party/openssl/.openssl
cd "$(ROOT_DIR)" && rm -f gConnector.zip gConnector_static.zip \
"$(GRPC_GATEWAY_PROTOS)"/*.pb.cc "$(GRPC_GATEWAY_PROTOS)"/*.pb.h \
third_party/nginx/src/Makefile
cd "$(ROOT_DIR)"/net/grpc/gateway/examples/echo && make clean
cd "$(ROOT_DIR)"/javascript/net/grpc/web && make clean
cd "$(ROOT_DIR)"

258
README.md
View File

@ -1,27 +1,26 @@
# gRPC Web · [![npm version](https://img.shields.io/npm/v/grpc-web.svg?style=flat)](https://www.npmjs.com/package/grpc-web)
## Overview
A JavaScript implementation of [gRPC][] for browser clients. For more information,
including a **quick start**, see the [gRPC-web documentation][grpc-web-docs].
gRPC-Web provides a Javascript library that lets browser clients access a gRPC
service. You can find out much more about gRPC in its own
[website](https://grpc.io).
gRPC-web clients connect to gRPC services via a special proxy; by default,
gRPC-web uses [Envoy][].
gRPC-Web is now Generally Available, and considered stable enough for production
use.
In the future, we expect gRPC-web to be supported in language-specific web
frameworks for languages such as Python, Java, and Node. For details, see the
[roadmap](doc/roadmap.md).
gRPC-Web clients connect to gRPC services via a special gateway proxy: the
current version of the library uses [Envoy](https://www.envoyproxy.io/) by
default, in which gRPC-Web support is built-in.
## Streaming Support
gRPC-web currently supports 2 RPC modes:
- Unary RPCs ([example](#make-a-unary-rpc-call))
- Server-side Streaming RPCs ([example](#server-side-streaming)) (NOTE: Only when [`grpcwebtext`](#wire-format-mode) mode is used.)
In the future, we expect gRPC-Web to be supported in language-specific Web
frameworks, such as Python, Java, and Node. See the
[roadmap](https://github.com/grpc/grpc-web/blob/master/ROADMAP.md) doc.
Client-side and Bi-directional streaming is not currently supported (see [streaming roadmap](doc/streaming-roadmap.md)).
## Quick Start
## Quick Start Guide: Hello World
Eager to get started? Try the [Hello World example][]. From this example, you'll
learn how to do the following:
You can follow the [Hello World Guide][] to get started with gRPC-Web quickly.
From the guide, you will learn how to
- Define your service using protocol buffers
- Implement a simple gRPC Service using NodeJS
- Configure the Envoy proxy
@ -37,84 +36,43 @@ streaming example.
From the repo root directory:
```sh
$ docker-compose pull prereqs node-server envoy commonjs-client
$ docker-compose up node-server envoy commonjs-client
$ docker-compose pull prereqs common node-server envoy commonjs-client
$ docker-compose up -d node-server envoy commonjs-client
```
Open a browser tab, and visit http://localhost:8081/echotest.html.
Open a browser tab, and go to:
```
http://localhost:8081/echotest.html
```
To shutdown: `docker-compose down`.
## Runtime Library
The gRPC-web runtime library is available at `npm`:
The gRPC-Web runtime library is available at `npm`:
```sh
$ npm i grpc-web
```
## Code Generator Plugins
### (Prerequisite) 1. Protobuf (`protoc`)
## Code Generator Plugin
If you don't already have [`protoc`](https://github.com/protocolbuffers/protobuf)
installed, download it first from [here](https://github.com/protocolbuffers/protobuf/releases) and install it on your PATH.
If you use Homebrew (on macOS), you could run:
You can compile the `protoc-gen-grpc-web` protoc plugin from this repo:
```sh
brew install protobuf
$ sudo make install-plugin
```
### (Prerequisite) 2. Protobuf-javascript (`protoc-gen-js`)
If you don't have [`protoc-gen-js`](https://github.com/protocolbuffers/protobuf-javascript) installed, download it from [protocolbuffers/protobuf-javascript](https://github.com/protocolbuffers/protobuf-javascript/releases) and install it on your PATH.
Or, use the [third-party](https://www.npmjs.com/package/protoc-gen-js) NPM installer:
```
npm install -g protoc-gen-js
```
### 3. Install gRPC-Web Code Generator
You can download the `protoc-gen-grpc-web` protoc plugin from our
[release](https://github.com/grpc/grpc-web/releases) page:
Make sure all executables are discoverable from your PATH.
For example, on MacOS, you can do:
If you don't already have `protoc` installed, you may have to do this first:
```sh
sudo mv protoc-gen-grpc-web-1.5.0-darwin-aarch64 \
/usr/local/bin/protoc-gen-grpc-web
chmod +x /usr/local/bin/protoc-gen-grpc-web
$ ./scripts/init_submodules.sh
$ cd third_party/grpc/third_party/protobuf
$ ./autogen.sh && ./configure && make -j8 && sudo make install
```
### (Optional) 4. Verify Installations
You can optionally verify the plugins works follwoing our [Hello world example](https://github.com/grpc/grpc-web/tree/master/net/grpc/gateway/examples/helloworld#generating-stubs):
```sh
cd net/grpc/gateway/examples/helloworld
protoc -I=. helloworld.proto \
--js_out=import_style=commonjs:. \
--grpc-web_out=import_style=commonjs,mode=grpcwebtext:.
```
After the command runs successfully, you should now see two new files generated
in the current directory. By running:
```
ls -1 *_pb.js
```
Installation is successful if you see the following 2 files:
- `helloworld_pb.js` # Generated by `protoc-gen-js` plugin
- `helloworld_grpc_web_pb.js` - Generated by gRPC-Web plugin
## Client Configuration Options
@ -122,7 +80,7 @@ Typically, you will run the following command to generate the proto messages
and the service client stub from your `.proto` definitions:
```sh
protoc -I=$DIR echo.proto \
$ protoc -I=$DIR echo.proto \
--js_out=import_style=commonjs:$OUT_DIR \
--grpc-web_out=import_style=commonjs,mode=grpcwebtext:$OUT_DIR
```
@ -130,6 +88,7 @@ protoc -I=$DIR echo.proto \
You can then use Browserify, Webpack, Closure Compiler, etc. to resolve imports
at compile time.
### Import Style
`import_style=closure`: The default generated code has
@ -144,16 +103,15 @@ also supported.
typings file will also be generated for the protobuf messages and service stub.
`import_style=typescript`: (Experimental) The service stub will be generated
in TypeScript. See **TypeScript Support** below for information on how to
generate TypeScript files.
in TypeScript.
> **Note:** The `commonjs+dts` and `typescript` styles are only supported by
`--grpc-web_out=import_style=...`, not by `--js_out=import_style=...`.
**Note: `commonjs+dts` and `typescript` only works with `--grpc-web_out=` import style.**
### Wire Format Mode
For more information about the gRPC-web wire format, see the
[specification](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md#protocol-differences-vs-grpc-over-http2).
For more information about the gRPC-Web wire format, please see the
[specification](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md#protocol-differences-vs-grpc-over-http2)
here.
`mode=grpcwebtext`: The default generated code sends the payload in the
`grpc-web-text` format.
@ -166,19 +124,21 @@ For more information about the gRPC-web wire format, see the
- `Content-type: application/grpc-web+proto`
- Payload are in the binary protobuf format.
- Only unary calls are supported.
- Only unary calls are supported for now.
## How It Works
Let's take a look at how gRPC-web works with a simple example. You can find out
Let's take a look at how gRPC-Web works with a simple example. You can find out
how to build, run and explore the example yourself in
[Build and Run the Echo Example](net/grpc/gateway/examples/echo).
### 1. Define your service
The first step when creating any gRPC service is to define it. Like all gRPC
services, gRPC-web uses
[protocol buffers](https://developers.google.com/protocol-buffers) to define
services, gRPC-Web uses
[protocol buffers](https://developers.google.com/protocol-buffers/) to define
its RPC service methods and their message request and response types.
```protobuf
@ -203,30 +163,31 @@ gateway proxy that allows the client to connect to the server. Our example
builds a simple Node gRPC backend server and the Envoy proxy.
For the Echo service: see the
[service implementations](net/grpc/gateway/examples/echo/node-server/server.js).
[service implementations](https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/examples/echo/node-server/server.js).
For the Envoy proxy: see the
[config yaml file](net/grpc/gateway/examples/echo/envoy.yaml).
[config yaml file](https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/examples/echo/envoy.yaml).
### 3. Write your JS client
Once the server and gateway are up and running, you can start making gRPC calls
from the browser!
Create your client:
Create your client
```js
var echoService = new proto.mypackage.EchoServiceClient(
'http://localhost:8080');
```
#### Make a unary RPC call:
Make a unary RPC call
```js
var request = new proto.mypackage.EchoRequest();
request.setMessage(msg);
var metadata = {'custom-header-1': 'value1'};
echoService.echo(request, metadata, function(err, response) {
var call = echoService.echo(request, metadata, function(err, response) {
if (err) {
console.log(err.code);
console.log(err.message);
@ -234,9 +195,14 @@ echoService.echo(request, metadata, function(err, response) {
console.log(response.getMessage());
}
});
call.on('status', function(status) {
console.log(status.code);
console.log(status.details);
console.log(status.metadata);
});
```
#### Server-side streaming:
Server-side streaming is supported!
```js
var stream = echoService.serverStreamingEcho(streamRequest, metadata);
@ -251,13 +217,10 @@ stream.on('status', function(status) {
stream.on('end', function(end) {
// stream end signal
});
// to close the stream
stream.cancel()
```
For an in-depth tutorial, see [this
page](net/grpc/gateway/examples/echo/tutorial.md).
You can find a more in-depth tutorial from
[this page](https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/examples/echo/tutorial.md).
## Setting Deadline
@ -268,7 +231,7 @@ should be a Unix timestamp, in milliseconds.
var deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + 1);
client.sayHelloAfterDelay(request, {deadline: deadline.getTime().toString()},
client.sayHelloAfterDelay(request, {deadline: deadline.getTime()},
(err, response) => {
// err will be populated if the RPC exceeds the deadline
...
@ -286,37 +249,9 @@ either:
- `import_style=commonjs+dts`: existing CommonJS style stub + `.d.ts` typings
- `import_style=typescript`: full TypeScript output
Do *not* use `import_style=typescript` for `--js_out`, it will silently be
ignored. Instead you should use `--js_out=import_style=commonjs`, or
`--js_out=import_style=commonjs,binary` if you are using `mode=grpcweb`. The
`--js_out` plugin will generate JavaScript code (`echo_pb.js`), and the
`-grpc-web_out` plugin will generate a TypeScript definition file for it
(`echo_pb.d.ts`). This is a temporary hack until the `--js_out` supports
TypeScript itself.
For example, this is the command you should use to generate TypeScript code
using the binary wire format
```sh
protoc -I=$DIR echo.proto \
--js_out=import_style=commonjs,binary:$OUT_DIR \
--grpc-web_out=import_style=typescript,mode=grpcweb:$OUT_DIR
```
It will generate the following files:
* `EchoServiceClientPb.ts` - Generated by `--grpc-web_out`, contains the
TypeScript gRPC-web code.
* `echo_pb.js` - Generated by `--js_out`, contains the JavaScript Protobuf
code.
* `echo_pb.d.ts` - Generated by `--grpc-web_out`, contains TypeScript
definitions for `echo_pb.js`.
### Using Callbacks
```ts
import * as grpcWeb from 'grpc-web';
import {EchoServiceClient} from './EchoServiceClientPb';
import {EchoServiceClient} from './echo_grpc_web_pb';
import {EchoRequest, EchoResponse} from './echo_pb';
const echoService = new EchoServiceClient('http://localhost:8080', null, null);
@ -325,7 +260,7 @@ const request = new EchoRequest();
request.setMessage('Hello World!');
const call = echoService.echo(request, {'custom-header-1': 'value1'},
(err: grpcWeb.RpcError, response: EchoResponse) => {
(err: grpcWeb.Error, response: EchoResponse) => {
console.log(response.getMessage());
});
call.on('status', (status: grpcWeb.Status) => {
@ -333,72 +268,39 @@ call.on('status', (status: grpcWeb.Status) => {
});
```
(See [here](https://github.com/grpc/grpc-web/blob/4d7dc44c2df522376394d3e3315b7ab0e010b0c5/packages/grpc-web/index.d.ts#L29-L39) full list of possible `.on(...)` callbacks)
See a full TypeScript example
[here](https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/examples/echo/ts-example/client.ts).
### (Option) Using Promises (Limited features)
## Proxy Interoperability
> **NOTE:** It is not possible to access the `.on(...)` callbacks (e.g. for `metadata` and `status`) when Promise is used.
```ts
// Create a Promise client instead
const echoService = new EchoServicePromiseClient('http://localhost:8080', null, null);
... (same as above)
this.echoService.echo(request, {'custom-header-1': 'value1'})
.then((response: EchoResponse) => {
console.log(`Received response: ${response.getMessage()}`);
}).catch((err: grpcWeb.RpcError) => {
console.log(`Received error: ${err.code}, ${err.message}`);
});
```
For the full TypeScript example, see
[ts-example/client.ts](net/grpc/gateway/examples/echo/ts-example/client.ts) with the [instructions](net/grpc/gateway/examples/echo/ts-example) to run.
## Custom Interceptors
Custom interceptors can be implemented and chained, which could be useful for features like auth, retries, etc.
There are 2 types of interceptors ([interfaces](https://github.com/grpc/grpc-web/blob/3cd7e0d43493d4694fed78400e4ad78031d70c09/packages/grpc-web/index.d.ts#L55-L65)):
- `UnaryInterceptor` ([doc](https://grpc.io/blog/grpc-web-interceptor/#stream-interceptor-example), [example](https://github.com/grpc/grpc-web/blob/master/packages/grpc-web/test/tsc-tests/client04.ts)) - Intercept Unary RPCs; can only be used with Promise clients.
- `StreamInterceptor` ([doc](https://grpc.io/blog/grpc-web-interceptor/#stream-interceptor-example), [example](https://github.com/grpc/grpc-web/blob/master/packages/grpc-web/test/tsc-tests/client03.ts)) - More versatile; can be used with regular clients.
For more details, see [this blog post](https://grpc.io/blog/grpc-web-interceptor/).
## Ecosystem
### Proxy Interoperability
Multiple proxies support the gRPC-web protocol.
1. The current **default proxy** is [Envoy][], which supports gRPC-web out of the box.
Multiple proxies supports the gRPC-Web protocol. Currently, the default proxy
is [Envoy](https://www.envoyproxy.io), which supports gRPC-Web out of the box.
```sh
$ docker-compose up -d node-server envoy commonjs-client
```
2. You can also try the [gRPC-web Go proxy][].
An alternative is to build Nginx that comes with this repository.
```sh
$ docker-compose up -d node-server nginx commonjs-client
```
You can also try this
[gRPC-Web Go Proxy](https://github.com/improbable-eng/grpc-web/tree/master/go/grpcwebproxy).
```sh
$ docker-compose up -d node-server grpcwebproxy binary-client
```
3. Apache [APISIX](https://apisix.apache.org/) has also added grpc-web support, and more details can be found [here](https://apisix.apache.org/blog/2022/01/25/apisix-grpc-web-integration/).
## Acknowledgement
4. [Nginx](https://www.nginx.com/) has a grpc-web module ([doc](https://nginx.org/en/docs/http/ngx_http_grpc_module.html), [announcement](https://www.nginx.com/blog/nginx-1-13-10-grpc/))), and seems to work with simple configs, according to user [feedback](https://github.com/grpc/grpc-web/discussions/1322).
Big thanks to the following contributors for making significant contributions to
this project!
### Server Frameworks with gRPC-Web support
- [Armeria (JVM)](https://armeria.dev/docs/server-grpc/#grpc-web)
- [Tonic (Rust)](https://docs.rs/tonic-web/latest/tonic_web/)
* [zaucy](https://github.com/zaucy): NPM package, CommonJS
* [yannic](https://github.com/yannic): Bazel
* [mitar](https://github.com/mitar): Codegen Plugin
### Web Frameworks Compatibility
- **Vite** - See this [demo app](https://github.com/a2not/vite-grpc-web), as well as this [comment](https://github.com/grpc/grpc-web/issues/1242#issuecomment-1816249928).
[Envoy]: https://www.envoyproxy.io
[gRPC]: https://grpc.io
[grpc-web-docs]: https://grpc.io/docs/languages/web
[gRPC-web Go Proxy]: https://github.com/improbable-eng/grpc-web/tree/master/go/grpcwebproxy
[Hello World example]: net/grpc/gateway/examples/helloworld
[Hello World Guide]:https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/examples/helloworld/

113
ROADMAP.md Normal file
View File

@ -0,0 +1,113 @@
# Overview
The purpose of this document is to list all the features that we believe are
useful for gRPC users.
We would like your feedback! Please tell us which features you would most want
to see, so that we can prioritize the work to either publish Google's existing
solutions or develop some of the features directly in the open-source repo. For
the latter case, please mention if you are interested in contributing to any of
the road-map features :)
[Survey link](https://docs.google.com/forms/d/1NjWpyRviohn5jaPntosBHXRXZYkh_Ffi4GxJZFibylM/edit)
# Background
gRPC-Web has been developed internally at Google as part of the future front-end
stacks for Google's Web applications and cloud services. Over time we plan to
open-source and publish most of the features and make them available to gRPC
users.
Like everywhere, Web platforms and technologies are constantly evolving, often
with many inter-dependent ecosystems. As much as we like to open-source
everything, we also need keep the balance between creating a reusable and stable
open-source solution and meeting those requirements unique to Google's Web
ecosystems or their applications (such as search).
# Roadmap Features (in no particular order)
## Non-Binary Message Encoding
The binary protobuf encoding format is not most CPU efficient for browser
clients. Furthermore, the generated code size increases as the total protobuf
definition increases.
For Google's Web applications (e.g. gmail), we use a JSON like format which is
comparable to JSON in efficiency but also very compact in both the message size
and code size.
## Streaming-Friendly Transport Implementation
Currently the gRPC-Web client library uses XHR to ensure cross-browser support
and to support platforms such as React-Native.
We do plan to add fetch/streams support at some point, which is more efficient
for binary streams and incurs less memory overhead on the client-side.
However, fetch still has certain gaps compared to XHR, most notably the lack of
cancellation support. Progressing events, I/O event throttling are other
concerns.
## Bidi Support
As WebSocket over HTTP/2 becomes more available, we may add bidi support over
WebSockets.
At the same time, please tell us your exact use case, and maybe explain why
server-streaming is insufficient.
## Security
We plan to publish a comprehensive guideline doc on how to create secure Web
applications.
Native support such as XSRF, XSS prevention may also be added to the gRPC-Web
protocol.
## Compression
Do you need request compression? Brotli?
## CORS
We plan to support CORS preflight as specified in
[PROTOCOL-WEB.md](https://github.com/grpc/grpc-web/blob/master/PROTOCOL-WEB.md).
## Local Proxies
In-process proxies will eliminate the need to deploy an extra proxy such as
Nginx.
We have plans to add proxy support in Python, Java, Node, C++ etc. Let us know
if you are interested in implementing any language-specific in-process
gRPC-Web proxy.
To minimize maintenance overhead, we don't have any plan to add gRPC-Web support
to any new HTTP reverse proxies other than Nginx and Envoy.
## Web Framework Integration
This is to provide first-class support for gRPC API and gRPC-Web in popular Web
frameworks such as Angular.
Note Dart gRPC will be using gRPC-Web as the underlying implementation on the
Dart Web platform.
## TypeScript Support
We now have experimental TypeScript Support! See the main README for more
information.
## Non-Closure compiler support
With the addition of CommonJS style imports, gRPC-Web client stubs can now be
compiled with various tools such as Browserify, Webpack, etc. Let us know
what else we should try!
## Web UI Support
This allows the user to construct and submit a gRPC request directly using the
browser.
We need define a standard look & feel for creating and rendering nested protobuf
messages.

View File

@ -1,3 +0,0 @@
# Security Policy
For information on gRPC Security Policy and reporting potentional security issues, please see [gRPC CVE Process](https://github.com/grpc/proposal/blob/master/P4-grpc-cve-process.md).

14
WORKSPACE Normal file
View File

@ -0,0 +1,14 @@
workspace(name = "com_github_grpc_grpc_web")
http_archive(
name = "io_bazel_rules_closure",
sha256 = "4463509e8f86c9b7726b6b7c751132f0ca14f907cba00759b21f8577c2dcf710",
strip_prefix = "rules_closure-acad96981d76b60844bf815d03043619714839ad",
urls = [
"https://github.com/bazelbuild/rules_closure/archive/acad96981d76b60844bf815d03043619714839ad.zip",
],
)
load("@io_bazel_rules_closure//closure:defs.bzl", "closure_repositories")
closure_repositories()

0
bazel/BUILD.bazel Normal file
View File

View File

@ -0,0 +1,197 @@
# This rule was inspired by rules_closure`s implementation of
# |closure_proto_library|, licensed under Apache 2.
# https://github.com/bazelbuild/rules_closure/blob/3555e5ba61fdcc17157dd833eaf7d19b313b1bca/closure/protobuf/closure_proto_library.bzl
load(
"@io_bazel_rules_closure//closure/compiler:closure_js_library.bzl",
"closure_js_library_impl",
)
load(
"@io_bazel_rules_closure//closure/private:defs.bzl",
"CLOSURE_WORKER_ATTR",
"CLOSURE_LIBRARY_BASE_ATTR",
"unfurl",
)
load(
"@io_bazel_rules_closure//closure/protobuf:closure_proto_library.bzl",
"closure_proto_aspect",
)
# This was borrowed from Rules Go, licensed under Apache 2.
# https://github.com/bazelbuild/rules_go/blob/67f44035d84a352cffb9465159e199066ecb814c/proto/compiler.bzl#L72
def _proto_path(proto):
path = proto.path
root = proto.root.path
ws = proto.owner.workspace_root
if path.startswith(root):
path = path[len(root):]
if path.startswith("/"):
path = path[1:]
if path.startswith(ws):
path = path[len(ws):]
if path.startswith("/"):
path = path[1:]
return path
def _proto_include_path(proto):
path = proto.path[:-len(_proto_path(proto))]
if not path:
return "."
if path.endswith("/"):
path = path[:-1]
return path
def _proto_include_paths(protos):
return depset([_proto_include_path(proto) for proto in protos])
def _generate_closure_grpc_web_src_progress_message(name):
# TODO(yannic): Add a better message?
return "Generating GRPC Web %s" % name
def _generate_closure_grpc_web_srcs(
actions, protoc, protoc_gen_grpc_web, import_style, mode,
sources, transitive_sources):
all_sources = [src for src in sources] + [src for src in transitive_sources]
proto_include_paths = [
"-I%s" % p for p in _proto_include_paths(
[f for f in all_sources])
]
grpc_web_out_common_options = ",".join([
"import_style={}".format(import_style),
"mode={}".format(mode),
])
files = []
for src in sources:
name = "{}.grpc.js".format(
".".join(src.path.split("/")[-1].split(".")[:-1]))
js = actions.declare_file(name)
files.append(js)
args = proto_include_paths + [
"--plugin=protoc-gen-grpc-web={}".format(protoc_gen_grpc_web.path),
"--grpc-web_out={options},out={out_file}:{path}".format(
options = grpc_web_out_common_options,
out_file = name,
path = js.path[:js.path.rfind("/")],
),
src.path
]
actions.run(
inputs = [protoc_gen_grpc_web] + all_sources,
outputs = [js],
executable = protoc,
arguments = args,
progress_message =
_generate_closure_grpc_web_src_progress_message(name),
)
return files
_error_multiple_deps = "".join([
"'deps' attribute must contain exactly one label ",
"(we didn't name it 'dep' for consistency). ",
"We may revisit this restriction later.",
])
def _closure_grpc_web_library_impl(ctx):
if len(ctx.attr.deps) > 1:
# TODO(yannic): Revisit this restriction.
fail(_error_multiple_deps, "deps");
dep = ctx.attr.deps[0]
srcs = _generate_closure_grpc_web_srcs(
actions = ctx.actions,
protoc = ctx.executable._protoc,
protoc_gen_grpc_web = ctx.executable._protoc_gen_grpc_web,
import_style = ctx.attr.import_style,
mode = ctx.attr.mode,
sources = dep.proto.direct_sources,
transitive_sources = dep.proto.transitive_imports,
)
deps = unfurl(ctx.attr.deps, provider = "closure_js_library")
deps += [
ctx.attr._grpc_web_abstractclientbase,
ctx.attr._grpc_web_clientreadablestream,
ctx.attr._grpc_web_error,
ctx.attr._grpc_web_grpcwebclientbase
]
suppress = [
"misplacedTypeAnnotation",
"unusedPrivateMembers",
"strictDependencies",
]
library = closure_js_library_impl(
actions = ctx.actions,
label = ctx.label,
workspace_name = ctx.workspace_name,
srcs = srcs,
deps = deps,
testonly = ctx.attr.testonly,
suppress = suppress,
lenient = False,
closure_library_base = ctx.files._closure_library_base,
_ClosureWorker = ctx.executable._ClosureWorker,
)
return struct(
exports = library.exports,
closure_js_library = library.closure_js_library,
# The usual suspects are exported as runfiles, in addition to raw source.
runfiles = ctx.runfiles(files = srcs),
)
closure_grpc_web_library = rule(
implementation = _closure_grpc_web_library_impl,
attrs = {
"deps": attr.label_list(
mandatory = True,
providers = ["proto", "closure_js_library"],
# The files generated by this aspect are required dependencies.
aspects = [closure_proto_aspect],
),
"import_style": attr.string(
default = "closure",
values = ["closure"],
),
"mode": attr.string(
default = "grpcwebtext",
values = ["grpcwebtext", "grpcweb"],
),
# Required for closure_js_library_impl
"_ClosureWorker": CLOSURE_WORKER_ATTR,
"_closure_library_base": CLOSURE_LIBRARY_BASE_ATTR,
# internal only
"_protoc": attr.label(
default = Label("@com_google_protobuf//:protoc"),
executable = True,
cfg = "host",
),
"_protoc_gen_grpc_web": attr.label(
default = Label("//javascript/net/grpc/web:protoc-gen-grpc-web"),
executable = True,
cfg = "host",
),
"_grpc_web_abstractclientbase": attr.label(
default = Label("//javascript/net/grpc/web:abstractclientbase"),
),
"_grpc_web_clientreadablestream": attr.label(
default = Label("//javascript/net/grpc/web:clientreadablestream"),
),
"_grpc_web_error": attr.label(
default = Label("//javascript/net/grpc/web:error"),
),
"_grpc_web_grpcwebclientbase": attr.label(
default = Label("//javascript/net/grpc/web:grpcwebclientbase"),
),
},
)

View File

@ -1,30 +0,0 @@
# Overview
In-process proxies allow a browser client to talk to a gRPC server directly without relying on any intermediary process
such as an Envoy proxy. This document provides a high-level design guidelines on how we expect such a "proxy" to work.
# The choice of HTTP stack
We strongly recommend that the gRPC-Web module use the default HTTP stack provided by the language platform, or in the case of Java,
the standard Java Servlet framework. This is to ensure maximum portability and to ease integration between gRPC-Web and existing Web
frameworks.
The actual HTTP version that the HTTP stack supports may include both HTTP/1.1 and HTTP/2. In the runtime, it's up to the user-agent and
intermediaries to negotiate the HTTP version, which is transparent to the gRPC-Web module.
# Request translation
For most languages, the gRPC-Web module will handle the gRPC-Web request, perform the translation, and then proxy the request using a gRPC client
to the gRPC server via a local socket. The gRPC-Web support is fully transparent to the gRPC server.
For some languages, such as Swift, .NET, if the gRPC server implementation uses the same HTTP stack that the gRPC-Web module uses, then gRPC-Web may be supported
directly as part of the gRPC server implementation. The added complexity to the gRPC implementation itself is still a concern.
# HTTP port
We expect that gRPC-Web requests are handled on a separate port. If the HTTP stack supports both HTTP/2 and HTTP/1.1, port sharing could be supported.
However, since CORS is a mandatory feature for gRPC-Web proxies, port sharing should be optional for in-process proxies.
# Core features
The gRPC-Web module should implement only the [core gRPC-Web features](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md) and leave to the HTTP/Web stack provided by the language platform to handle [Web-framework-level features](https://github.com/grpc/grpc-web/blob/master/doc/browser-features.md) such as XSRF, CORS policies. Some of those features may be incompatible with what Envoy supports for gRPC-Web.

View File

@ -1,83 +0,0 @@
gRPC-Web Interop Tests
======================
This document describes the set of tests any gRPC-Web clients or proxies need
to implement. The proto definition for the messages and RPCs we are using for
the tests can be found
[here](https://github.com/grpc/grpc/blob/master/src/proto/grpc/testing/test.proto).
The canonical set of interop tests was defined in the main
[grpc/grpc repo](https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md).
Here in gRPC-Web, we will only implement a subset of tests that's relevant to
gRPC-Web. For example, we will not be implementing any tests involving
client-streaming or bidi-streaming for now. On the other hand, there are
gRPC-Web specific tests that we will add here.
```
gRPC-Web Client <--> Proxy <--> gRPC Service
```
The idea is that we should be able to swap out any of the 3 components above
and all the interop tests should still pass.
This repository will provide a canonical implementation of the interop test
suite using the Javascript client, Envoy and a gRPC service implemented in
Node.
For any new gRPC-Web client implementation, you need to swap out the JS
client and make sure all tests still pass.
For any in-process proxies implementation, you need to swap out the proxy
and the service as a unit and make sure the standard JS client will still
pass the tests.
List of Tests
-------------
| Test Name | grpc-web-text Mode | grpc-web Binary mode |
| --------- |:------------------:|:--------------------:|
| empty_unary | &#10003; | &#10003; |
| cacheable_unary | TBD | TBD |
| large_unary | &#10003; | &#10003; |
| client_compressed_unary | &#10003; | &#10003; |
| server_compressed_unary | &#10003; | &#10003; |
| client_streaming | &#10007; | &#10007; |
| client_compressed_streaming | &#10007; | &#10007; |
| server_streaming | &#10003; | &#10007; |
| server_compressed_streaming | &#10003; | &#10007; |
| ping_pong | &#10007; | &#10007; |
| empty_stream | &#10007; | &#10007; |
| compute_engine_creds | TBD | TBD |
| jwt_token_creds | TBD | TBD |
| oauth2_auth_token | TBD | TBD |
| per_rpc_creds | TBD | TBD |
| google_default_credentials | TBD | TBD |
| compute_engine_channel_credentials | TBD | TBD |
| custom_metadata * | &#10003; | &#10003; |
| status_code_and_message * | &#10003; | &#10003; |
| special_status_message | &#10003; | &#10003; |
| unimplemented_method | &#10003; | &#10003; |
| unimplemented_service | &#10003; | &#10003; |
| cancel_after_begin | &#10007; | &#10007; |
| cancel_after_first_response | &#10007; | &#10007; |
| timeout_on_sleeping_server | &#10007; | &#10007; |
\* only need to implement the UnaryCall RPC
gRPC-Web specific considerations
--------------------------------
### Text vs Binary mode
As mentioned in the table above, client needs to be tested in both the text
format `application/grpc-web-text` and the binary mode
`application/grpc-web+proto`. The latter we don't need to test any streaming
methods.
### CORS and other web specific scenarios
We may add specific tests to account for web-related scenarios like CORS
handling, etc. Mostly these are to test the connection between the browser
client and the proxy.

View File

@ -1,72 +0,0 @@
# gRPC-Web Roadmap
The purpose of this document is to collect all the features that we believe are
useful for gRPC users.
## Background
gRPC-Web has been developed internally at Google as part of the front-end
stacks for Google's Web applications and cloud services. Over time we plan to
open-source and publish most of the features and make them available to open-source
users.
Like everywhere, Web platforms and technologies are constantly evolving, often
with many inter-dependent ecosystems. As much as we like to open-source
everything, we also need keep the balance between creating a reusable and stable
open-source solution and meeting those requirements unique to Google's Web applications
(such as search).
## Roadmap Features
> NOTE: Due to the status of two of gRPC-Webs core dependencies — [Google
Closure](https://github.com/google/closure-library/issues/1214), which has been
archived, and [Protobuf
JavaScript](https://github.com/protocolbuffers/protobuf-javascript?tab=readme-ov-file#project-status),
which is receiving only minimal updates — the gRPC-Web project is no longer able
to deliver new, modern solutions for the open source community. As a result, we
do not plan to be adding new features going forward.
>
> We recommend you to use [gRPC-Gateway](https://github.com/grpc-ecosystem/grpc-gateway) as an alternative.
### TypeScript Codebase
Migrate the codebase to TypeScript and update the related toolchains (incl. remove
dependency on `closure-compiler`). Enhance overall TypeScript support.
### Streaming Support
Enhance Fetch/streams support (e.g. cancellation support) and improve runtime
support, including service workers.
See streaming roadmap [here](streaming-roadmap.md).
### Non-Binary Message Encoding
The binary protobuf encoding format is not most CPU efficient for browser
clients. Furthermore, the generated code size increases as the total protobuf
definition increases.
For Google's Web applications (e.g. gmail), we use a JSON like format which is
comparable to JSON in efficiency but also very compact in both the message size
and code size.
### Security
We plan to publish a comprehensive guideline doc on how to create secure Web
applications.
Native support such as XSRF, XSS prevention may also be added to the gRPC-Web
protocol.
### Web Framework Integration
This is to provide first-class support for gRPC API and gRPC-Web in popular Web
frameworks such as Angular.
Note: Dart gRPC will use gRPC-Web as the underlying implementation on the
Dart Web platform.
### Non-Closure compiler support
With the addition of CommonJS style imports, gRPC-Web client stubs can now be
compiled with various tools such as Browserify, Webpack, etc. Let us know
what else we should try!

View File

@ -1,38 +0,0 @@
# Streaming Roadmap
This document describes the road-map for gRPC-Web to support different streaming features.
* Server-streaming
* Client-streaming and half-duplex streaming
## Server-streaming
We will keep improving server-streaming in the following areas:
* Fetch cancellation support - 2024
* Performance improvements and whatwg Fetch/streams support, including service workers - 2024
* Finalizing keep-alive support (via Envoy) - 2024+
* Addressing runtime behavior gaps between Fetch and XHR - 2024+
## Client-streaming and half-duplex streaming
We dont plan to support client-streaming via Fetch/upload-streams (See [Appendix](#chrome-origin-trial-on-upload-streaming) on backgrounds on the Chrome Origin Trial). As a result, half-duplex bidi streaming wont be supported via Fetch/streams either.
Client-streaming and half-duplex bidi streaming will be addressed when Full-duplex streaming is supported via WebTransport (see below).
## Full-duplex streaming
Not planned.
## Issues with WebSockets
We have no plan to support full-duplex streaming over WebSockets (over TCP or HTTP/2). We will not publish any experimental spec for gRPC over WebSockets either.
The main issue with WebSockets is its incompatibility with HTTP, i.e. the ubiquitous Web infrastructure. This means HTTP fallback is always needed. Recent IETF proposal to tunnel WebSockets over HTTP/2 is not widely implemented either.
## Appendix
### Chrome Origin Trial on `upload-streaming`
We worked on a Chrome [Origin Trial](https://developers.chrome.com/origintrials/#/view_trial/3524066708417413121)
to finalize the fetch/upload stream API spec (whatwg). One of the pending issues that blocks the final spec is to decide whether it is safe to enable
upload-streaming over HTTP/1.1. We believe that upload-streaming should be enabled for both HTTP/2 and HTTP/1.1. Specifically for gRPC-Web, the server can't control the client deployment. As a result, if upload-streaming is only enabled over HTTP/2, a gRPC service will have to implement a non-streaming method
as a fallback for each client-streaming method.

View File

@ -5,12 +5,19 @@ services:
context: ./
dockerfile: ./net/grpc/gateway/docker/prereqs/Dockerfile
image: grpcweb/prereqs
common:
build:
context: ./
dockerfile: ./net/grpc/gateway/docker/common/Dockerfile
depends_on:
- prereqs
image: grpcweb/common
echo-server:
build:
context: ./
dockerfile: ./net/grpc/gateway/docker/echo_server/Dockerfile
depends_on:
- prereqs
- common
image: grpcweb/echo-server
ports:
- "9090:9090"
@ -19,17 +26,10 @@ services:
context: ./
dockerfile: ./net/grpc/gateway/docker/node_server/Dockerfile
depends_on:
- prereqs
- common
image: grpcweb/node-server
ports:
- "9090:9090"
node-interop-server:
build:
context: ./
dockerfile: ./net/grpc/gateway/docker/node_interop_server/Dockerfile
image: grpcweb/node-interop-server
ports:
- "7074:7074"
envoy:
build:
context: ./
@ -39,6 +39,17 @@ services:
- "8080:8080"
links:
- node-server
nginx:
build:
context: ./
dockerfile: ./net/grpc/gateway/docker/nginx/Dockerfile
depends_on:
- common
image: grpcweb/nginx
ports:
- "8080:8080"
links:
- node-server
grpcwebproxy:
build:
context: ./
@ -53,7 +64,7 @@ services:
context: ./
dockerfile: ./net/grpc/gateway/docker/commonjs_client/Dockerfile
depends_on:
- prereqs
- common
image: grpcweb/commonjs-client
ports:
- "8081:8081"
@ -62,7 +73,7 @@ services:
context: ./
dockerfile: ./net/grpc/gateway/docker/closure_client/Dockerfile
depends_on:
- prereqs
- common
image: grpcweb/closure-client
ports:
- "8081:8081"
@ -71,7 +82,7 @@ services:
context: ./
dockerfile: ./net/grpc/gateway/docker/ts_client/Dockerfile
depends_on:
- prereqs
- common
image: grpcweb/ts-client
ports:
- "8081:8081"
@ -80,28 +91,7 @@ services:
context: ./
dockerfile: ./net/grpc/gateway/docker/binary_client/Dockerfile
depends_on:
- prereqs
- common
image: grpcweb/binary-client
ports:
- "8081:8081"
interop-client:
build:
context: ./
dockerfile: ./net/grpc/gateway/docker/interop_client/Dockerfile
depends_on:
- prereqs
image: grpcweb/interop-client
ports:
- "8081:8081"
protoc-plugin:
build:
context: ./
dockerfile: ./net/grpc/gateway/docker/protoc_plugin/Dockerfile
depends_on:
- prereqs
image: grpcweb/protoc-plugin
jsunit-test:
build:
context: ./
dockerfile: ./packages/grpc-web/docker/jsunit-test/Dockerfile
image: grpcweb/jsunit-test

View File

@ -0,0 +1,163 @@
package(default_visibility = ["//visibility:public"])
load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_binary")
load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_library")
load("@io_bazel_rules_closure//closure:defs.bzl", "closure_js_test")
cc_binary(
name = "protoc-gen-grpc-web",
srcs = [
"grpc_generator.cc",
],
deps = [
"@com_google_protobuf//:protoc_lib"
],
)
closure_js_library(
name = "abstractclientbase",
srcs = [
"abstractclientbase.js",
],
deps = [
":clientreadablestream",
":error",
],
)
closure_js_library(
name = "clientreadablestream",
srcs = [
"clientreadablestream.js",
],
)
closure_js_library(
name = "error",
srcs = [
"error.js",
],
)
closure_js_library(
name = "generictransportinterface",
srcs = [
"generictransportinterface.js",
],
deps = [
"@io_bazel_rules_closure//closure/library/net/streams:nodereadablestream",
"@io_bazel_rules_closure//closure/library/net:xhrio",
],
)
closure_js_library(
name = "grpcwebclientbase",
srcs = [
"grpcwebclientbase.js",
],
suppress = [
"checkTypes",
"reportUnknownTypes",
],
deps = [
":abstractclientbase",
":grpcwebclientreadablestream",
":statuscode",
"@io_bazel_rules_closure//closure/library/crypt:base64",
"@io_bazel_rules_closure//closure/library/net:xhrio",
"@io_bazel_rules_closure//closure/library/net/rpc:httpcors",
],
)
closure_js_library(
name = "grpcwebclientreadablestream",
srcs = [
"grpcwebclientreadablestream.js",
],
deps = [
":clientreadablestream",
":generictransportinterface",
":grpcwebstreamparser",
":status",
":statuscode",
"@io_bazel_rules_closure//closure/library/crypt:base64",
"@io_bazel_rules_closure//closure/library/events:events",
"@io_bazel_rules_closure//closure/library/net:errorcode",
"@io_bazel_rules_closure//closure/library/net:eventtype",
"@io_bazel_rules_closure//closure/library/net:xhrio",
"@io_bazel_rules_closure//closure/library/net:xmlhttp",
"@io_bazel_rules_closure//closure/library/string",
],
suppress = [
"reportUnknownTypes",
],
)
closure_js_library(
name = "grpcwebstreamparser",
srcs = [
"grpcwebstreamparser.js",
],
suppress = [
"reportUnknownTypes",
],
deps = [
"@io_bazel_rules_closure//closure/library/asserts",
"@io_bazel_rules_closure//closure/library/net/streams:streamparser",
],
)
closure_js_library(
name = "status",
srcs = [
"status.js",
],
)
closure_js_library(
name = "statuscode",
srcs = [
"statuscode.js",
],
)
closure_js_test(
name = "grpcwebclientbase_test",
srcs = [
"grpcwebclientbase_test.js",
],
entry_points = [
"goog:grpc.web.GrpcWebClientBaseTest",
],
suppress = [
"visibility",
"checkTypes",
"deprecated",
"reportUnknownTypes",
"strictCheckTypes",
],
deps = [
"@io_bazel_rules_closure//closure/library:testing",
"@io_bazel_rules_closure//closure/library/crypt:base64",
"@io_bazel_rules_closure//closure/library/events:events",
"@io_bazel_rules_closure//closure/library/structs:map",
":grpcwebclientbase",
],
)
closure_js_test(
name = "grpcwebstreamparser_test",
srcs = [
"grpcwebstreamparser_test.js",
],
entry_points = [
"goog:grpc.web.GrpcWebStreamParserTest",
],
suppress = [
"reportUnknownTypes",
],
deps = [
":grpcwebstreamparser",
"@io_bazel_rules_closure//closure/library:testing",
],
)

View File

@ -12,22 +12,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
CXX ?= g++
CXX = g++
CPPFLAGS += -I/usr/local/include -pthread
CXXFLAGS += -std=c++11
LDFLAGS += -L/usr/local/lib -lprotoc -lprotobuf -lpthread -ldl
PREFIX ?= /usr/local
MIN_MACOS_VERSION := 10.7 # Supports OS X Lion
STATIC ?= yes
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
CXXFLAGS += -stdlib=libc++ -mmacosx-version-min=$(MIN_MACOS_VERSION)
else ifeq ($(UNAME_S),Linux)
ifeq ($(STATIC),yes)
LDFLAGS += -static
endif
endif
all: protoc-gen-grpc-web
@ -35,8 +23,7 @@ protoc-gen-grpc-web: grpc_generator.o
$(CXX) $^ $(LDFLAGS) -o $@
install: protoc-gen-grpc-web
mkdir -p $(PREFIX)/bin
install protoc-gen-grpc-web $(PREFIX)/bin/protoc-gen-grpc-web
install protoc-gen-grpc-web /usr/local/bin/protoc-gen-grpc-web
clean:
rm -f *.o protoc-gen-grpc-web

View File

@ -28,84 +28,75 @@ goog.module.declareLegacyNamespace();
const ClientReadableStream = goog.require('grpc.web.ClientReadableStream');
const MethodDescriptor = goog.require('grpc.web.MethodDescriptor');
const RpcError = goog.require('grpc.web.RpcError');
const Error = goog.require('grpc.web.Error');
/**
* This interface represents a grpc-web client
*
* @interface
*/
const AbstractClientBase = function() {};
/**
* @constructor
* @struct
* @final
*/
const PromiseCallOptions = function() {};
/**
* An AbortSignal to abort the call.
* @type {AbortSignal|undefined}
*/
PromiseCallOptions.prototype.signal;
/**
* This interface represents a grpc-web client
* @interface
*/
const AbstractClientBase = class {
constructor() {}
/**
* @abstract
* @template REQUEST, RESPONSE
* @param {string} method The method to invoke
* @param {REQUEST} requestMessage The request proto
* @param {!Object<string, string>} metadata User defined call metadata
* @param {!MethodDescriptor<REQUEST, RESPONSE>}
* methodDescriptor Information of this RPC method
* @param {function(?RpcError, ?)}
* callback A callback function which takes (error, RESPONSE or null)
* @return {!ClientReadableStream<RESPONSE>}
* @param {function(new: RESPONSE, ...)} responseType
* @param {function(REQUEST): ?} requestSerializeFn
* @param {function(?): RESPONSE} responseDeserializeFn
*/
rpcCall(method, requestMessage, metadata, methodDescriptor, callback) {}
/**
* @abstract
* @protected
* @template REQUEST, RESPONSE
* @param {string} method The method to invoke
* @param {REQUEST} requestMessage The request proto
* @param {!Object<string, string>} metadata User defined call metadata
* @param {!MethodDescriptor<REQUEST, RESPONSE>}
* methodDescriptor Information of this RPC method
* @param options Options for the call
* @return {!IThenable<RESPONSE>}
* A promise that resolves to the response message
*/
thenableCall(method, requestMessage, metadata, methodDescriptor, options) {}
/**
* @abstract
* @template REQUEST, RESPONSE
* @param {string} method The method to invoke
* @param {REQUEST} requestMessage The request proto
* @param {!Object<string, string>} metadata User defined call metadata
* @param {!MethodDescriptor<REQUEST, RESPONSE>}
* methodDescriptor Information of this RPC method
* @return {!ClientReadableStream<RESPONSE>} The Client Readable Stream
*/
serverStreaming(method, requestMessage, metadata, methodDescriptor) {}
AbstractClientBase.MethodInfo = function(
responseType,
requestSerializeFn,
responseDeserializeFn) {
/** @const */
this.responseType = responseType;
/** @const */
this.requestSerializeFn = requestSerializeFn;
/** @const */
this.responseDeserializeFn = responseDeserializeFn;
};
/**
* Get the hostname of the current request.
* @template REQUEST, RESPONSE
* @param {string} method
* @param {!MethodDescriptor<REQUEST,RESPONSE>} methodDescriptor
* @return {string}
* Even with ?RESPONSE the RESPONSE will still be inferred as
* "FooResponse|Null". Use RESPONSE_LEAN to extract out the "FooResponse"
* part. See go/closure-ttl.
* @template RESPONSE_LEAN :=
* cond(isUnknown(RESPONSE), unknown(),
* mapunion(RESPONSE, (X) =>
* cond(eq(X, 'undefined'), none(),
* cond(eq(X, 'null'), none(),
* X))))
* =:
* @param {string} method The method to invoke
* @param {REQUEST} request The request proto
* @param {!Object<string, string>} metadata User defined call metadata
* @param {!AbstractClientBase.MethodInfo<REQUEST, RESPONSE_LEAN>}
* methodInfo Information of this RPC method
* @param {function(?Error, ?RESPONSE)}
* callback A callback function which takes (error, response)
* @return {!ClientReadableStream<RESPONSE_LEAN>|undefined}
* The Client Readable Stream
*/
function getHostname(method, methodDescriptor) {
// method = hostname + methodDescriptor.name(relative path of this method)
return method.substr(0, method.length - methodDescriptor.name.length);
}
AbstractClientBase.prototype.rpcCall = goog.abstractMethod;
exports = {AbstractClientBase, PromiseCallOptions, getHostname};
/**
* @template REQUEST, RESPONSE
* @param {string} method The method to invoke
* @param {REQUEST} request The request proto
* @param {!Object<string, string>} metadata User defined call metadata
* @param {!AbstractClientBase.MethodInfo<REQUEST, RESPONSE>}
* methodInfo Information of this RPC method
* @return {!ClientReadableStream<RESPONSE>} The Client Readable Stream
*/
AbstractClientBase.prototype.serverStreaming = goog.abstractMethod;
exports = AbstractClientBase;

View File

@ -1,66 +0,0 @@
/**
* @fileoverview grpc.web.CallOptions
*/
goog.module('grpc.web.CallOptions');
goog.module.declareLegacyNamespace();
/**
* The collection of runtime options for a new RPC call.
* @unrestricted
*/
class CallOptions {
/**
* @param {!Object<string, !Object>=} options
*/
constructor(options) {
/**
* @const {!Object<string, !Object>}
* @private
*/
this.properties_ = options || {};
}
/**
* Add a new CallOption or override an existing one.
*
* @param {string} name name of the CallOption that should be
* added/overridden.
* @param {VALUE} value value of the CallOption
* @template VALUE
*/
setOption(name, value) {
this.properties_[name] = value;
}
/**
* Get the value of one CallOption.
*
* @param {string} name name of the CallOption.
* @return {!Object} value of the CallOption. If name doesn't exist, will
* return 'undefined'.
*/
get(name) {
return this.properties_[name];
}
/**
* Remove a CallOption.
*
* @param {string} name name of the CallOption that shoud be removed.
*/
removeOption(name) {
delete this.properties_[name];
}
/**
* @return {!Array<string>}
*/
getKeys() {
return Object.keys(this.properties_);
}
}
exports = CallOptions;

View File

@ -1,66 +0,0 @@
goog.module('grpc.web.ClientOptions');
goog.module.declareLegacyNamespace();
const {StreamInterceptor, UnaryInterceptor} = goog.require('grpc.web.Interceptor');
/**
* Options that are available during the client construction.
* @record
*/
class ClientOptions {
constructor() {
/**
* Whether to use the HttpCors library to pack http headers into a special
* url query param $httpHeaders= so that browsers can bypass CORS OPTIONS
* requests.
* @type {boolean|undefined}
*/
this.suppressCorsPreflight;
/**
* Whether to turn on XMLHttpRequest's withCredentials flag.
* @type {boolean|undefined}
*/
this.withCredentials;
/**
* Unary interceptors. Note that they are only available in grpcweb and
* grpcwebtext mode
* @type {!Array<!UnaryInterceptor>|undefined}
*/
this.unaryInterceptors;
/**
* Stream interceptors. Note that they are only available in grpcweb and
* grpcwebtext mode
* @type {!Array<!StreamInterceptor>|undefined}
*/
this.streamInterceptors;
/**
* Protocol buffer format for open source gRPC-Web. This attribute should be
* specified by the gRPC-Web build rule by default.
* @type {string|undefined}
*/
this.format;
/**
* The Worker global scope. Once this option is specified, gRPC-Web will
* also use 'fetch' API as the underlying transport instead of native
* XmlHttpRequest.
* @type {!WorkerGlobalScope|undefined}
*/
this.workerScope;
/**
* This is an experimental feature to reduce memory consumption
* during high throughput server-streaming calls by using
* 'streamBinaryChunks' mode FetchXmlHttpFactory.
* @type {boolean|undefined}
*/
this.useFetchDownloadStreams;
}
}
exports = ClientOptions;

View File

@ -43,22 +43,7 @@ const ClientReadableStream = function() {};
/**
* Register a callback to handle different stream events.
*
* Available event types for gRPC-Web:
* 'data': The 'data' event is emitted when a new response message chunk is
* received and successfully handled by gRPC-Web client.
* 'status': the google RPC status of the response stream.
* 'end': The 'end' event is emitted when all the data have been successfully
* consumed from the stream.
* 'error': typically, this may occur when an underlying internal failure
* happens, or a stream implementation attempts to push an invalid chunk of
* data.
* 'metadata': the response metadata. Response headers should be read via
* 'metadata' callbacks.
*
* For server-streaming calls. the 'data' and 'status' callbacks (if exist)
* will always precede 'metadata', 'error', or 'end' callbacks.
* Register a callback to handle I/O events.
*
* @param {string} eventType The event type
* @param {function(?)} callback The call back to handle the event with
@ -69,17 +54,6 @@ ClientReadableStream.prototype.on = goog.abstractMethod;
/**
* Remove a particular callback.
*
* @param {string} eventType The event type
* @param {function(?)} callback The callback to remove
* @return {!ClientReadableStream} this object
*/
ClientReadableStream.prototype.removeListener = goog.abstractMethod;
/**
* Close the stream.
*/

View File

@ -1,51 +0,0 @@
/**
* @fileoverview This class handles ClientReadableStream returned by unary
* calls.
*/
goog.module('grpc.web.ClientUnaryCallImpl');
goog.module.declareLegacyNamespace();
const ClientReadableStream = goog.require('grpc.web.ClientReadableStream');
/**
* @implements {ClientReadableStream<RESPONSE>}
* @template RESPONSE
*/
class ClientUnaryCallImpl {
/**
* @param {!ClientReadableStream<RESPONSE>} stream
*/
constructor(stream) {
this.stream = stream;
}
/**
* @override
*/
on(eventType, callback) {
if (eventType == 'data' || eventType == 'error') {
// unary call responses and errors should be handled by the main
// (err, resp) => ... callback
return this;
}
return this.stream.on(eventType, callback);
}
/**
* @override
*/
removeListener(eventType, callback) {
return this.stream.removeListener(eventType, callback);
}
/**
* @override
*/
cancel() {
this.stream.cancel();
}
}
exports = ClientUnaryCallImpl;

View File

@ -0,0 +1,39 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/**
* @fileoverview gRPC-Web Error objects
*
* gRPC-Web Error objects
*
* @author stanleycheung@google.com (Stanley Cheung)
*/
goog.module('grpc.web.Error');
goog.module.declareLegacyNamespace();
/**
* @typedef {{
* code: (number|undefined),
* message: (string|undefined),
* }}
*/
let Error;
exports = Error;

View File

@ -0,0 +1,188 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/**
* @fileoverview gRPC browser client library.
*
* Base class for gRPC Web JS clients to be used with the gRPC Gateway
*
* @author stanleycheung@google.com (Stanley Cheung)
*/
goog.module('grpc.web.GatewayClientBase');
goog.module.declareLegacyNamespace();
const AbstractClientBase = goog.require('grpc.web.AbstractClientBase');
const ClientReadableStream = goog.require('grpc.web.ClientReadableStream');
const GoogleRpcStatus = goog.require('proto.google.rpc.Status');
const NodeReadableStream = goog.require('goog.net.streams.NodeReadableStream');
const Pair = goog.require('proto.grpc.gateway.Pair');
const StatusCode = goog.require('grpc.web.StatusCode');
const StreamBodyClientReadableStream = goog.require('grpc.web.StreamBodyClientReadableStream');
const XhrIo = goog.require('goog.net.XhrIo');
const createXhrNodeReadableStream = goog.require('goog.net.streams.createXhrNodeReadableStream');
const googCrypt = goog.require('goog.crypt');
const {Status} = goog.require('grpc.web.Status');
/**
* Base class for gRPC web client (gRPC Gateway)
* @param {?Object=} opt_options
* @constructor
* @implements {AbstractClientBase}
*/
const GatewayClientBase = function(opt_options) {
};
/**
* @override
*/
GatewayClientBase.prototype.rpcCall = function(
method, request, metadata, methodInfo, callback) {
var xhr = this.newXhr_();
var serialized = methodInfo.requestSerializeFn(request);
xhr.headers.addAll(metadata);
var stream = this.createClientReadableStream_(
xhr,
methodInfo.responseDeserializeFn);
stream.on('data', function(response) {
callback(null, response);
});
stream.on('status', function(status) {
if (status.code != StatusCode.OK) {
callback({
'code': status.code,
'message': status.details
}, null);
}
});
xhr.headers.set('Content-Type', 'application/x-protobuf');
xhr.headers.set('X-User-Agent', 'grpc-web-javascript/0.1');
xhr.headers.set('X-Accept-Content-Transfer-Encoding', 'base64');
xhr.headers.set('X-Accept-Response-Streaming', 'true');
xhr.send(method, 'POST', serialized);
return stream;
};
/**
* @override
*/
GatewayClientBase.prototype.serverStreaming = function(
method, request, metadata, methodInfo) {
var xhr = this.newXhr_();
var serialized = methodInfo.requestSerializeFn(request);
xhr.headers.addAll(metadata);
var stream = this.createClientReadableStream_(
xhr,
methodInfo.responseDeserializeFn);
xhr.headers.set('Content-Type', 'application/x-protobuf');
xhr.headers.set('X-User-Agent', 'grpc-web-javascript/0.1');
xhr.headers.set('X-Accept-Content-Transfer-Encoding', 'base64');
xhr.headers.set('X-Accept-Response-Streaming', 'true');
xhr.send(method, 'POST', serialized);
return stream;
};
/**
* Create a new XhrIo object
*
* @private
* @return {!XhrIo} The created XhrIo object
*/
GatewayClientBase.prototype.newXhr_ = function() {
return new XhrIo();
};
/**
* Create a new XhrNodeReadableStream object
*
* @private
* @param {!XhrIo} xhr The XhrIo object
* @return {?NodeReadableStream} The XHR NodeReadableStream object
*/
GatewayClientBase.prototype.newXhrNodeReadableStream_ = function(xhr) {
return createXhrNodeReadableStream(xhr);
};
/**
* @template RESPONSE
* @private
* @param {!XhrIo} xhr The XhrIo object
* @param {function(?):!RESPONSE} responseDeserializeFn
* The deserialize function for the proto
* @return {!ClientReadableStream<RESPONSE>} The Client Readable Stream
*/
GatewayClientBase.prototype.createClientReadableStream_ = function(
xhr, responseDeserializeFn) {
var xhrNodeReadableStream = this.newXhrNodeReadableStream_(xhr);
var genericTransportInterface = {
xhr: xhr,
nodeReadableStream: xhrNodeReadableStream,
};
var stream = new StreamBodyClientReadableStream(genericTransportInterface);
stream.setResponseDeserializeFn(responseDeserializeFn);
stream.setRpcStatusParseFn(GatewayClientBase.parseRpcStatus_);
return stream;
};
/**
* @private
* @static
* @param {!Uint8Array} data Data returned from underlying stream
* @return {!Status} status The Rpc Status details
*/
GatewayClientBase.parseRpcStatus_ = function(data) {
var rpcStatus = GoogleRpcStatus.deserializeBinary(data);
var metadata = {};
var details = rpcStatus.getDetailsList();
for (var i = 0; i < details.length; i++) {
var pair = Pair.deserializeBinary(
details[i].getValue());
var first = googCrypt.utf8ByteArrayToString(
pair.getFirst_asU8());
var second = googCrypt.utf8ByteArrayToString(
pair.getSecond_asU8());
metadata[first] = second;
}
var status = {
code: rpcStatus.getCode(),
details: rpcStatus.getMessage(),
metadata: metadata
};
return status;
};
exports = GatewayClientBase;

View File

@ -1,12 +0,0 @@
load("@rules_cc//cc:defs.bzl", "cc_binary")
cc_binary(
name = "protoc-gen-grpc-web",
srcs = [
"grpc_generator.cc",
],
visibility = ["//visibility:public"],
deps = [
"@com_google_protobuf//:protoc_lib",
],
)

View File

@ -1,215 +0,0 @@
const std = @import("std");
const CrossTarget = std.zig.CrossTarget;
fn format(comptime fmt: []const u8, args: anytype) []const u8 {
return std.fmt.allocPrint(std.testing.allocator, fmt, args) catch unreachable;
}
const BinaryTarget = struct {
name: []const u8,
arch: []const u8,
};
pub fn build(b: *std.build.Builder) void {
// Standard release options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
const mode = b.standardReleaseOptions();
var version = if (std.os.getenv("VERSION")) |v| v else "unknown";
var targets = [_]BinaryTarget{
// for now, let's only build aarch64 binaries
// .{ .name = format("protoc-gen-grpc-web-{s}-linux-x86_64", .{version}), .arch = "x86_64-linux" },
// .{ .name = format("protoc-gen-grpc-web-{s}-darwin-x86_64", .{version}), .arch = "x86_64-macos" },
// .{ .name = format("protoc-gen-grpc-web-{s}-windows-x86_64", .{version}), .arch = "x86_64-windows" },
.{ .name = format("protoc-gen-grpc-web-{s}-linux-aarch64", .{version}), .arch = "aarch64-linux" },
.{ .name = format("protoc-gen-grpc-web-{s}-darwin-aarch64", .{version}), .arch = "aarch64-macos" },
.{ .name = format("protoc-gen-grpc-web-{s}-windows-aarch64", .{version}), .arch = "aarch64-windows" },
};
for (targets) |target| {
const exe = b.addExecutable(target.name, "grpc_generator.cc");
exe.linkLibCpp();
exe.linkSystemLibrary("pthread");
exe.linkSystemLibrary("dl");
exe.addIncludeDir("../../../../../third_party/protobuf/src");
exe.defineCMacro("HAVE_PTHREAD", "1");
exe.addCSourceFiles(&[_][]const u8{
// libprotobuf_lite source files (copied from third_party/protobuf/cmake/libprotobuf-lite.cmake)
"../../../../../third_party/protobuf/src/google/protobuf/any_lite.cc",
"../../../../../third_party/protobuf/src/google/protobuf/arena.cc",
"../../../../../third_party/protobuf/src/google/protobuf/arenastring.cc",
"../../../../../third_party/protobuf/src/google/protobuf/extension_set.cc",
"../../../../../third_party/protobuf/src/google/protobuf/generated_enum_util.cc",
"../../../../../third_party/protobuf/src/google/protobuf/generated_message_table_driven_lite.cc",
"../../../../../third_party/protobuf/src/google/protobuf/generated_message_util.cc",
"../../../../../third_party/protobuf/src/google/protobuf/implicit_weak_message.cc",
"../../../../../third_party/protobuf/src/google/protobuf/io/coded_stream.cc",
"../../../../../third_party/protobuf/src/google/protobuf/io/io_win32.cc",
"../../../../../third_party/protobuf/src/google/protobuf/io/strtod.cc",
"../../../../../third_party/protobuf/src/google/protobuf/io/zero_copy_stream.cc",
"../../../../../third_party/protobuf/src/google/protobuf/io/zero_copy_stream_impl.cc",
"../../../../../third_party/protobuf/src/google/protobuf/io/zero_copy_stream_impl_lite.cc",
"../../../../../third_party/protobuf/src/google/protobuf/map.cc",
"../../../../../third_party/protobuf/src/google/protobuf/message_lite.cc",
"../../../../../third_party/protobuf/src/google/protobuf/parse_context.cc",
"../../../../../third_party/protobuf/src/google/protobuf/repeated_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/stubs/bytestream.cc",
"../../../../../third_party/protobuf/src/google/protobuf/stubs/common.cc",
"../../../../../third_party/protobuf/src/google/protobuf/stubs/int128.cc",
"../../../../../third_party/protobuf/src/google/protobuf/stubs/status.cc",
"../../../../../third_party/protobuf/src/google/protobuf/stubs/statusor.cc",
"../../../../../third_party/protobuf/src/google/protobuf/stubs/stringpiece.cc",
"../../../../../third_party/protobuf/src/google/protobuf/stubs/stringprintf.cc",
"../../../../../third_party/protobuf/src/google/protobuf/stubs/structurally_valid.cc",
"../../../../../third_party/protobuf/src/google/protobuf/stubs/strutil.cc",
"../../../../../third_party/protobuf/src/google/protobuf/stubs/time.cc",
"../../../../../third_party/protobuf/src/google/protobuf/wire_format_lite.cc",
// libprotobuf ssource files (copied from third_party/protobuf/cmake/libprotobuf.cmake)
"../../../../../third_party/protobuf/src/google/protobuf/any.cc",
"../../../../../third_party/protobuf/src/google/protobuf/any.pb.cc",
"../../../../../third_party/protobuf/src/google/protobuf/api.pb.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/importer.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/parser.cc",
"../../../../../third_party/protobuf/src/google/protobuf/descriptor.cc",
"../../../../../third_party/protobuf/src/google/protobuf/descriptor.pb.cc",
"../../../../../third_party/protobuf/src/google/protobuf/descriptor_database.cc",
"../../../../../third_party/protobuf/src/google/protobuf/duration.pb.cc",
"../../../../../third_party/protobuf/src/google/protobuf/dynamic_message.cc",
"../../../../../third_party/protobuf/src/google/protobuf/empty.pb.cc",
"../../../../../third_party/protobuf/src/google/protobuf/extension_set_heavy.cc",
"../../../../../third_party/protobuf/src/google/protobuf/field_mask.pb.cc",
"../../../../../third_party/protobuf/src/google/protobuf/generated_message_reflection.cc",
"../../../../../third_party/protobuf/src/google/protobuf/generated_message_table_driven.cc",
"../../../../../third_party/protobuf/src/google/protobuf/io/gzip_stream.cc",
"../../../../../third_party/protobuf/src/google/protobuf/io/printer.cc",
"../../../../../third_party/protobuf/src/google/protobuf/io/tokenizer.cc",
"../../../../../third_party/protobuf/src/google/protobuf/map_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/message.cc",
"../../../../../third_party/protobuf/src/google/protobuf/reflection_ops.cc",
"../../../../../third_party/protobuf/src/google/protobuf/service.cc",
"../../../../../third_party/protobuf/src/google/protobuf/source_context.pb.cc",
"../../../../../third_party/protobuf/src/google/protobuf/struct.pb.cc",
"../../../../../third_party/protobuf/src/google/protobuf/stubs/substitute.cc",
"../../../../../third_party/protobuf/src/google/protobuf/text_format.cc",
"../../../../../third_party/protobuf/src/google/protobuf/timestamp.pb.cc",
"../../../../../third_party/protobuf/src/google/protobuf/type.pb.cc",
"../../../../../third_party/protobuf/src/google/protobuf/unknown_field_set.cc",
"../../../../../third_party/protobuf/src/google/protobuf/util/delimited_message_util.cc",
"../../../../../third_party/protobuf/src/google/protobuf/util/field_comparator.cc",
"../../../../../third_party/protobuf/src/google/protobuf/util/field_mask_util.cc",
"../../../../../third_party/protobuf/src/google/protobuf/util/internal/datapiece.cc",
"../../../../../third_party/protobuf/src/google/protobuf/util/internal/default_value_objectwriter.cc",
"../../../../../third_party/protobuf/src/google/protobuf/util/internal/error_listener.cc",
"../../../../../third_party/protobuf/src/google/protobuf/util/internal/field_mask_utility.cc",
"../../../../../third_party/protobuf/src/google/protobuf/util/internal/json_escaping.cc",
"../../../../../third_party/protobuf/src/google/protobuf/util/internal/json_objectwriter.cc",
"../../../../../third_party/protobuf/src/google/protobuf/util/internal/json_stream_parser.cc",
"../../../../../third_party/protobuf/src/google/protobuf/util/internal/object_writer.cc",
"../../../../../third_party/protobuf/src/google/protobuf/util/internal/proto_writer.cc",
"../../../../../third_party/protobuf/src/google/protobuf/util/internal/protostream_objectsource.cc",
"../../../../../third_party/protobuf/src/google/protobuf/util/internal/protostream_objectwriter.cc",
"../../../../../third_party/protobuf/src/google/protobuf/util/internal/type_info.cc",
"../../../../../third_party/protobuf/src/google/protobuf/util/internal/type_info_test_helper.cc",
"../../../../../third_party/protobuf/src/google/protobuf/util/internal/utility.cc",
"../../../../../third_party/protobuf/src/google/protobuf/util/json_util.cc",
"../../../../../third_party/protobuf/src/google/protobuf/util/message_differencer.cc",
"../../../../../third_party/protobuf/src/google/protobuf/util/time_util.cc",
"../../../../../third_party/protobuf/src/google/protobuf/util/type_resolver_util.cc",
"../../../../../third_party/protobuf/src/google/protobuf/wire_format.cc",
"../../../../../third_party/protobuf/src/google/protobuf/wrappers.pb.cc",
// libprotoc source files (copied from third_party/protobuf/cmake/libprotoc.cmake)
"../../../../../third_party/protobuf/src/google/protobuf/compiler/code_generator.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/command_line_interface.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_enum.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_enum_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_extension.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_file.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_generator.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_helpers.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_map_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_message.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_message_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_padding_optimizer.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_primitive_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_service.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/cpp/cpp_string_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_doc_comment.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_enum.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_enum_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_field_base.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_generator.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_helpers.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_map_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_message.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_message_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_primitive_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_reflection_class.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_repeated_enum_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_repeated_message_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_repeated_primitive_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_source_generator_base.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/csharp/csharp_wrapper_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_context.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_doc_comment.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_enum.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_enum_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_enum_field_lite.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_enum_lite.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_extension.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_extension_lite.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_file.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_generator.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_generator_factory.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_helpers.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_map_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_map_field_lite.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_message.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_message_builder.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_message_builder_lite.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_message_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_message_field_lite.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_message_lite.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_name_resolver.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_primitive_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_primitive_field_lite.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_service.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_shared_code_generator.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_string_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/java/java_string_field_lite.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/js/js_generator.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/js/well_known_types_embed.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_enum.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_enum_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_extension.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_file.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_generator.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_helpers.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_map_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_message.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_message_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_oneof.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/objectivec/objectivec_primitive_field.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/php/php_generator.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/plugin.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/plugin.pb.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/python/python_generator.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/ruby/ruby_generator.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/subprocess.cc",
"../../../../../third_party/protobuf/src/google/protobuf/compiler/zip_writer.cc",
}, &[_][]const u8{
"-pthread",
});
exe.setTarget(CrossTarget.parse(.{ .arch_os_abi = target.arch }) catch unreachable);
exe.setBuildMode(mode);
exe.strip = true;
exe.install();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -16,10 +16,13 @@
*
*/
/**
* @fileoverview gRPC-Web generic transport interface
* @fileoverview gRPC web client Readable Stream
*
* This class provides an abstraction for the underlying transport
* implementation underneath the ClientReadableStream layer.
* This class is being returned after a gRPC streaming call has been
* started. This class provides functionality for user to operates on
* the stream, e.g. set onData callback, etc.
*
* This wraps the underlying goog.net.streams.NodeReadableStream
*
* @author stanleycheung@google.com (Stanley Cheung)
*/

File diff suppressed because it is too large Load Diff

View File

@ -28,299 +28,137 @@ goog.module('grpc.web.GrpcWebClientBase');
goog.module.declareLegacyNamespace();
const ClientOptions = goog.requireType('grpc.web.ClientOptions');
const ClientReadableStream = goog.require('grpc.web.ClientReadableStream');
const ClientUnaryCallImpl = goog.require('grpc.web.ClientUnaryCallImpl');
const AbstractClientBase = goog.require('grpc.web.AbstractClientBase');
const GrpcWebClientReadableStream = goog.require('grpc.web.GrpcWebClientReadableStream');
const HttpCors = goog.require('goog.net.rpc.HttpCors');
const MethodDescriptor = goog.requireType('grpc.web.MethodDescriptor');
const Request = goog.require('grpc.web.Request');
const RpcError = goog.require('grpc.web.RpcError');
const StatusCode = goog.require('grpc.web.StatusCode');
const XhrIo = goog.require('goog.net.XhrIo');
const googCrypt = goog.require('goog.crypt.base64');
const {AbstractClientBase, PromiseCallOptions, getHostname} = goog.require('grpc.web.AbstractClientBase');
const {Status} = goog.require('grpc.web.Status');
const {StreamInterceptor, UnaryInterceptor} = goog.require('grpc.web.Interceptor');
const {toObject} = goog.require('goog.collections.maps');
/**
* Base class for gRPC web client using the application/grpc-web wire format
* @param {?Object=} opt_options
* @constructor
* @implements {AbstractClientBase}
* @unrestricted
*/
class GrpcWebClientBase {
/**
* @param {!ClientOptions=} options
* @param {!XhrIo=} xhrIo
*/
constructor(options = {}, xhrIo = undefined) {
const GrpcWebClientBase = function(opt_options) {
/**
* @const
* @private {string}
*/
this.format_ =
options.format || goog.getObjectByName('format', options) || 'text';
goog.getObjectByName('format', opt_options) || "text";
/**
* @const
* @private {boolean}
*/
this.suppressCorsPreflight_ = options.suppressCorsPreflight ||
goog.getObjectByName('suppressCorsPreflight', options) || false;
this.suppressCorsPreflight_ =
goog.getObjectByName('suppressCorsPreflight', opt_options) || false;
};
/**
* @const
* @private {boolean}
*/
this.withCredentials_ = options.withCredentials ||
goog.getObjectByName('withCredentials', options) || false;
/**
* @const {!Array<!StreamInterceptor>}
* @private
*/
this.streamInterceptors_ = options.streamInterceptors ||
goog.getObjectByName('streamInterceptors', options) || [];
/**
* @const {!Array<!UnaryInterceptor>}
* @private
*/
this.unaryInterceptors_ = options.unaryInterceptors ||
goog.getObjectByName('unaryInterceptors', options) || [];
/** @const @private {?XhrIo} */
this.xhrIo_ = xhrIo || null;
}
/**
* @override
* @export
*/
rpcCall(method, requestMessage, metadata, methodDescriptor, callback) {
const hostname = getHostname(method, methodDescriptor);
const invoker = GrpcWebClientBase.runInterceptors_(
(request) => this.startStream_(request, hostname),
this.streamInterceptors_);
const stream = /** @type {!ClientReadableStream<?>} */ (invoker.call(
this, methodDescriptor.createRequest(requestMessage, metadata)));
GrpcWebClientBase.setCallback_(stream, callback, false);
return new ClientUnaryCallImpl(stream);
}
GrpcWebClientBase.prototype.rpcCall = function(
method, request, metadata, methodInfo, callback) {
var xhr = this.newXhr_();
/**
* @param {string} method The method to invoke
* @param {REQUEST} requestMessage The request proto
* @param {!Object<string, string>} metadata User defined call metadata
* @param {!MethodDescriptor<REQUEST, RESPONSE>} methodDescriptor
* @param {?PromiseCallOptions=} options Options for the call
* @return {!Promise<RESPONSE>}
* @template REQUEST, RESPONSE
*/
thenableCall(
method, requestMessage, metadata, methodDescriptor, options = {}) {
const hostname = getHostname(method, methodDescriptor);
const signal = options && options.signal;
const initialInvoker = (request) => new Promise((resolve, reject) => {
// If the signal is already aborted, immediately reject the promise
// and don't issue the call.
if (signal && signal.aborted) {
const error = new RpcError(StatusCode.CANCELLED, 'Aborted');
error.cause = signal.reason;
reject(error);
return;
}
const stream = this.startStream_(request, hostname);
let unaryMetadata;
let unaryStatus;
let unaryMsg;
GrpcWebClientBase.setCallback_(
stream,
(error, response, status, metadata, unaryResponseReceived) => {
if (error) {
reject(error);
} else if (unaryResponseReceived) {
unaryMsg = response;
} else if (status) {
unaryStatus = status;
} else if (metadata) {
unaryMetadata = metadata;
} else {
resolve(request.getMethodDescriptor().createUnaryResponse(
unaryMsg, unaryMetadata, unaryStatus));
}
},
true);
// Wire up cancellation from the abort signal, if any.
if (signal) {
signal.addEventListener('abort', () => {
stream.cancel();
const error = new RpcError(StatusCode.CANCELLED, 'Aborted');
error.cause = /** @type {!AbortSignal} */ (signal).reason;
reject(error);
});
}
});
const invoker = GrpcWebClientBase.runInterceptors_(
initialInvoker, this.unaryInterceptors_);
const unaryResponse = /** @type {!Promise<?>} */ (invoker.call(
this, methodDescriptor.createRequest(requestMessage, metadata)));
return unaryResponse.then((response) => response.getResponseMessage());
}
/**
* @export
* @param {string} method The method to invoke
* @param {REQUEST} requestMessage The request proto
* @param {!Object<string, string>} metadata User defined call metadata
* @param {!MethodDescriptor<REQUEST, RESPONSE>} methodDescriptor Information
* of this RPC method
* @param {?PromiseCallOptions=} options Options for the call
* @return {!Promise<RESPONSE>}
* @template REQUEST, RESPONSE
*/
unaryCall(method, requestMessage, metadata, methodDescriptor, options = {}) {
return /** @type {!Promise<RESPONSE>}*/ (this.thenableCall(
method, requestMessage, metadata, methodDescriptor, options));
}
/**
* @override
* @export
*/
serverStreaming(method, requestMessage, metadata, methodDescriptor) {
const hostname = getHostname(method, methodDescriptor);
const invoker = GrpcWebClientBase.runInterceptors_(
(request) => this.startStream_(request, hostname),
this.streamInterceptors_);
return /** @type {!ClientReadableStream<?>} */ (invoker.call(
this, methodDescriptor.createRequest(requestMessage, metadata)));
}
/**
* @private
* @template REQUEST, RESPONSE
* @param {!Request<REQUEST, RESPONSE>} request
* @param {string} hostname
* @return {!ClientReadableStream<RESPONSE>}
*/
startStream_(request, hostname) {
const methodDescriptor = request.getMethodDescriptor();
let path = hostname + methodDescriptor.getName();
const xhr = this.xhrIo_ ? this.xhrIo_ : new XhrIo();
xhr.setWithCredentials(this.withCredentials_);
const genericTransportInterface = {
var genericTransportInterface = {
xhr: xhr,
};
const stream = new GrpcWebClientReadableStream(genericTransportInterface);
stream.setResponseDeserializeFn(
methodDescriptor.getResponseDeserializeFn());
const metadata = request.getMetadata();
for(const key in metadata) {
xhr.headers.set(key, metadata[key]);
}
this.processHeaders_(xhr);
if (this.suppressCorsPreflight_) {
const headerObject = toObject(xhr.headers);
xhr.headers.clear();
path = GrpcWebClientBase.setCorsOverride_(path, headerObject);
}
const requestSerializeFn = methodDescriptor.getRequestSerializeFn();
const serialized = requestSerializeFn(request.getRequestMessage());
let payload = this.encodeRequest_(serialized);
if (this.format_ == 'text') {
payload = googCrypt.encodeByteArray(payload);
} else if (this.format_ == 'binary') {
xhr.setResponseType(XhrIo.ResponseType.ARRAY_BUFFER);
}
xhr.send(path, 'POST', payload);
return stream;
}
/**
* @private
* @static
* @template RESPONSE
* @param {!ClientReadableStream<RESPONSE>} stream
* @param {function(?RpcError, ?RESPONSE, ?Status=, ?Object<string, string>=, ?boolean)|
* function(?RpcError,?RESPONSE)} callback
* @param {boolean} useUnaryResponse Pass true to have the client make
* multiple calls to the callback, using (error, response, status,
* metadata, unaryResponseReceived) arguments. One of error, status,
* metadata, or unaryResponseReceived will be truthy to indicate which piece
* of information the client is providing in that call. After the stream
* ends, it will call the callback an additional time with all falsy
* arguments. Pass false to have the client make one call to the callback
* using (error, response) arguments.
*/
static setCallback_(stream, callback, useUnaryResponse) {
let isResponseReceived = false;
let responseReceived = null;
let errorEmitted = false;
var stream = new GrpcWebClientReadableStream(genericTransportInterface);
stream.setResponseDeserializeFn(methodInfo.responseDeserializeFn);
stream.on('data', function(response) {
isResponseReceived = true;
responseReceived = response;
});
stream.on('error', function(error) {
if (error.code != StatusCode.OK && !errorEmitted) {
errorEmitted = true;
callback(error, null);
}
callback(null, response);
});
stream.on('status', function(status) {
if (status.code != StatusCode.OK && !errorEmitted) {
errorEmitted = true;
callback(
{
code: status.code,
message: status.details,
metadata: status.metadata
},
null);
} else if (useUnaryResponse) {
callback(null, null, status);
}
});
if (useUnaryResponse) {
stream.on('metadata', function(metadata) {
callback(null, null, null, metadata);
});
}
stream.on('end', function() {
if (!errorEmitted) {
if (!isResponseReceived) {
if (status.code != StatusCode.OK) {
callback({
code: StatusCode.UNKNOWN,
message: 'Incomplete response',
});
} else if (useUnaryResponse) {
callback(
null, responseReceived, null, null,
/* unaryResponseReceived= */ true);
} else {
callback(null, responseReceived);
}
}
if (useUnaryResponse) {
callback(null, null);
code: status.code,
message: status.details
}, null);
}
});
stream.on('error', function(error) {
if (error.code != StatusCode.OK) {
callback({
code: error.code,
message: error.message
}, null);
}
});
xhr.headers.addAll(metadata);
this.processHeaders_(xhr);
if (this.suppressCorsPreflight_) {
var headerObject = xhr.headers.toObject();
xhr.headers.clear();
method = GrpcWebClientBase.setCorsOverride_(method, headerObject);
}
var serialized = methodInfo.requestSerializeFn(request);
var payload = this.encodeRequest_(serialized);
if (this.format_ == "text") {
payload = googCrypt.encodeByteArray(payload);
} else if (this.format_ == "binary") {
xhr.setResponseType(XhrIo.ResponseType.ARRAY_BUFFER);
}
xhr.send(method, 'POST', payload);
return stream;
};
/**
* @override
*/
GrpcWebClientBase.prototype.serverStreaming = function(
method, request, metadata, methodInfo) {
var xhr = this.newXhr_();
var genericTransportInterface = {
xhr: xhr,
};
var stream = new GrpcWebClientReadableStream(genericTransportInterface);
stream.setResponseDeserializeFn(methodInfo.responseDeserializeFn);
xhr.headers.addAll(metadata);
this.processHeaders_(xhr);
if (this.suppressCorsPreflight_) {
var headerObject = xhr.headers.toObject();
xhr.headers.clear();
method = GrpcWebClientBase.setCorsOverride_(method, headerObject);
}
var serialized = methodInfo.requestSerializeFn(request);
var payload = this.encodeRequest_(serialized);
if (this.format_ == "text") {
payload = googCrypt.encodeByteArray(payload);
} else if (this.format_ == "binary") {
xhr.setResponseType(XhrIo.ResponseType.ARRAY_BUFFER);
}
xhr.send(method, 'POST', payload);
return stream;
};
/**
* Create a new XhrIo object
*
* @private
* @return {!XhrIo} The created XhrIo object
*/
GrpcWebClientBase.prototype.newXhr_ = function() {
return new XhrIo();
};
/**
* Encode the grpc-web request
@ -329,25 +167,27 @@ class GrpcWebClientBase {
* @param {!Uint8Array} serialized The serialized proto payload
* @return {!Uint8Array} The application/grpc-web padded request
*/
encodeRequest_(serialized) {
let len = serialized.length;
const bytesArray = [0, 0, 0, 0];
const payload = new Uint8Array(5 + len);
for (let i = 3; i >= 0; i--) {
GrpcWebClientBase.prototype.encodeRequest_ = function(serialized) {
var len = serialized.length;
var bytesArray = [0, 0, 0, 0];
var payload = new Uint8Array(5 + len);
for (var i = 3; i >= 0; i--) {
bytesArray[i] = (len % 256);
len = len >>> 8;
}
payload.set(new Uint8Array(bytesArray), 1);
payload.set(serialized, 5);
return payload;
}
};
/**
* @private
* @param {!XhrIo} xhr The xhr object
*/
processHeaders_(xhr) {
if (this.format_ == 'text') {
GrpcWebClientBase.prototype.processHeaders_ = function(xhr) {
if (this.format_ == "text") {
xhr.headers.set('Content-Type', 'application/grpc-web-text');
xhr.headers.set('Accept', 'application/grpc-web-text');
} else {
@ -355,26 +195,16 @@ class GrpcWebClientBase {
}
xhr.headers.set('X-User-Agent', 'grpc-web-javascript/0.1');
xhr.headers.set('X-Grpc-Web', '1');
if (xhr.headers.has('deadline')) {
const deadline = Number(xhr.headers.get('deadline')); // in ms
const currentTime = (new Date()).getTime();
let timeout = Math.ceil(deadline - currentTime);
xhr.headers.delete('deadline');
if (timeout === Infinity) {
// grpc-timeout header defaults to infinity if not set.
timeout = 0;
}
if (xhr.headers.containsKey('deadline')) {
var deadline = xhr.headers.get('deadline'); // in ms
var currentTime = (new Date()).getTime();
var timeout = Math.round(deadline - currentTime);
xhr.headers.remove('deadline');
if (timeout > 0) {
xhr.headers.set('grpc-timeout', timeout + 'm');
// Also set timeout on the xhr request to terminate the HTTP request
// if the server doesn't respond within the deadline. We use 110% of
// grpc-timeout for this to allow the server to terminate the connection
// with DEADLINE_EXCEEDED rather than terminating it in the Browser, but
// at least 1 second in case the user is on a high-latency network.
xhr.setTimeoutInterval(Math.max(1000, Math.ceil(timeout * 1.1)));
}
}
}
};
/**
* @private
@ -383,29 +213,10 @@ class GrpcWebClientBase {
* @param {!Object<string,string>} headerObject The xhr headers
* @return {string} The URI object or a string path with headers
*/
static setCorsOverride_(method, headerObject) {
GrpcWebClientBase.setCorsOverride_ = function(method, headerObject) {
return /** @type {string} */ (HttpCors.setHttpHeadersWithOverwriteParam(
method, HttpCors.HTTP_HEADERS_PARAM_NAME, headerObject));
}
/**
* @private
* @static
* @template REQUEST, RESPONSE
* @param {function(!Request<REQUEST,RESPONSE>):
* (!Promise<RESPONSE>|!ClientReadableStream<RESPONSE>)} invoker
* @param {!Array<!UnaryInterceptor|!StreamInterceptor>}
* interceptors
* @return {function(!Request<REQUEST,RESPONSE>):
* (!Promise<RESPONSE>|!ClientReadableStream<RESPONSE>)}
*/
static runInterceptors_(invoker, interceptors) {
return interceptors.reduce((accumulatedInvoker, interceptor) => {
return (request) => interceptor.intercept(request, accumulatedInvoker);
}, invoker);
}
}
};
exports = GrpcWebClientBase;

View File

@ -18,425 +18,179 @@
goog.module('grpc.web.GrpcWebClientBaseTest');
goog.setTestOnly('grpc.web.GrpcWebClientBaseTest');
const ClientReadableStream = goog.require('grpc.web.ClientReadableStream');
const ErrorCode = goog.require('goog.net.ErrorCode');
const GrpcWebClientBase = goog.require('grpc.web.GrpcWebClientBase');
const MethodDescriptor = goog.require('grpc.web.MethodDescriptor');
const ReadyState = goog.require('goog.net.XmlHttp.ReadyState');
const Request = goog.requireType('grpc.web.Request');
const RpcError = goog.require('grpc.web.RpcError');
const StatusCode = goog.require('grpc.web.StatusCode');
const XhrIo = goog.require('goog.testing.net.XhrIo');
const googCrypt = goog.require('goog.crypt.base64');
const testSuite = goog.require('goog.testing.testSuite');
const {StreamInterceptor} = goog.require('grpc.web.Interceptor');
var GrpcWebClientBase = goog.require('grpc.web.GrpcWebClientBase');
var Map = goog.require('goog.structs.Map');
var googCrypt = goog.require('goog.crypt.base64');
var googEvents = goog.require('goog.events');
var testSuite = goog.require('goog.testing.testSuite');
goog.require('goog.testing.jsunit');
// This parses to [ { DATA: [4, 5, 6] }, { TRAILER: "a: b" } ]
const DEFAULT_RPC_RESPONSE =
new Uint8Array([0, 0, 0, 0, 3, 4, 5, 6, 128, 0, 0, 0, 4, 97, 58, 32, 98]);
const DEFAULT_RPC_RESPONSE_DATA = [4, 5, 6];
const DEFAULT_UNARY_HEADERS =
['Content-Type', 'Accept', 'X-User-Agent', 'X-Grpc-Web'];
const DEFAULT_UNARY_HEADER_VALUES = [
'application/grpc-web-text',
var REQUEST_BYTES = [1,2,3];
var FAKE_METHOD = "fake-method";
var PROTO_FIELD_VALUE = "meow";
var EXPECTED_HEADERS;
var EXPECTED_HEADER_VALUES;
var EXPECTED_UNARY_HEADERS = ['Content-Type', 'Accept',
'X-User-Agent', 'X-Grpc-Web'];
var EXPECTED_UNARY_HEADER_VALUES = ['application/grpc-web-text',
'application/grpc-web-text',
'grpc-web-javascript/0.1',
'1',
];
const DEFAULT_RESPONSE_HEADERS = {
'Content-Type': 'application/grpc-web-text',
};
'1'];
var dataCallback;
testSuite({
async testRpcResponse() {
const xhr = new XhrIo();
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
const methodDescriptor = createMethodDescriptor((bytes) => {
assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes));
return new MockReply('value');
});
const response = await new Promise((resolve, reject) => {
client.rpcCall(
'url', new MockRequest(), /* metadata= */ {}, methodDescriptor,
(error, response) => {
assertNull(error);
resolve(response);
});
xhr.simulatePartialResponse(
googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)),
DEFAULT_RESPONSE_HEADERS);
xhr.simulateReadyStateChange(ReadyState.COMPLETE);
});
assertEquals('value', response.data);
const headers = /** @type {!Object} */ (xhr.getLastRequestHeaders());
assertElementsEquals(DEFAULT_UNARY_HEADERS, Object.keys(headers));
assertElementsEquals(DEFAULT_UNARY_HEADER_VALUES, Object.values(headers));
setUp: function() {
googEvents.listen = function(a, b, listener, d, e) {
dataCallback = listener;
return;
};
},
async testRpcFalsyResponse_ForNonProtobufDescriptor() {
const xhr = new XhrIo();
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
const methodDescriptor = createMethodDescriptor((bytes) => {
assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes));
return 0;
});
const response = await new Promise((resolve, reject) => {
client.rpcCall(
'url', new MockRequest(), /* metadata= */ {}, methodDescriptor,
(error, response) => {
assertNull(error);
resolve(response);
});
xhr.simulatePartialResponse(
googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)),
DEFAULT_RESPONSE_HEADERS);
xhr.simulateReadyStateChange(ReadyState.COMPLETE);
});
assertEquals(0, response);
const headers = /** @type {!Object} */ (xhr.getLastRequestHeaders());
assertElementsEquals(DEFAULT_UNARY_HEADERS, Object.keys(headers));
assertElementsEquals(DEFAULT_UNARY_HEADER_VALUES, Object.values(headers));
tearDown: function() {
EXPECTED_HEADERS = null;
EXPECTED_HEADER_VALUES = null;
},
async testRpcResponseThenableCall() {
const xhr = new XhrIo();
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
const methodDescriptor = createMethodDescriptor((bytes) => {
assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes));
return new MockReply('value');
});
const responsePromise = client.thenableCall(
'url', new MockRequest(), /* metadata= */ {}, methodDescriptor);
xhr.simulatePartialResponse(
googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)),
DEFAULT_RESPONSE_HEADERS);
xhr.simulateReadyStateChange(ReadyState.COMPLETE);
const response = await responsePromise;
assertEquals('value', /** @type {!MockReply} */ (response).data);
const headers = /** @type {!Object} */ (xhr.getLastRequestHeaders());
assertElementsEquals(DEFAULT_UNARY_HEADERS, Object.keys(headers));
assertElementsEquals(DEFAULT_UNARY_HEADER_VALUES, Object.values(headers));
},
async testRpcFalsyResponseThenableCall_ForNonProtobufDescriptor() {
const xhr = new XhrIo();
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
const methodDescriptor = createMethodDescriptor((bytes) => {
assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes));
return 0;
});
const responsePromise = client.thenableCall(
'url', new MockRequest(), /* metadata= */ {}, methodDescriptor);
xhr.simulatePartialResponse(
googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)),
DEFAULT_RESPONSE_HEADERS);
xhr.simulateReadyStateChange(ReadyState.COMPLETE);
const response = await responsePromise;
assertEquals(0, response);
const headers = /** @type {!Object} */ (xhr.getLastRequestHeaders());
assertElementsEquals(DEFAULT_UNARY_HEADERS, Object.keys(headers));
assertElementsEquals(DEFAULT_UNARY_HEADER_VALUES, Object.values(headers));
},
async testCancelledThenableCall() {
const xhr = new XhrIo();
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
const methodDescriptor = createMethodDescriptor((bytes) => {
assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes));
return 0;
});
const abortController = new AbortController();
const signal = abortController.signal;
const responsePromise = client.thenableCall(
'url', new MockRequest(), /* metadata= */ {}, methodDescriptor,
{signal});
abortController.abort();
const error = await assertRejects(responsePromise);
assertTrue(error instanceof RpcError);
assertEquals(StatusCode.CANCELLED, /** @type {!RpcError} */ (error).code);
assertEquals('Aborted', /** @type {!RpcError} */ (error).message);
// Default abort reason if none provided.
const cause = /** @type {!RpcError} */ (error).cause;
assertTrue(cause instanceof Error);
assertEquals('AbortError', /** @type {!Error} */ (cause).name);
assertEquals(ErrorCode.ABORT, xhr.getLastErrorCode());
},
async testCancelledThenableCallWithReason() {
const xhr = new XhrIo();
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
const methodDescriptor = createMethodDescriptor((bytes) => {
assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes));
return 0;
});
const abortController = new AbortController();
const signal = abortController.signal;
const responsePromise = client.thenableCall(
'url', new MockRequest(), /* metadata= */ {}, methodDescriptor,
{signal});
abortController.abort('cancelling');
const error = await assertRejects(responsePromise);
assertTrue(error instanceof RpcError);
assertEquals(StatusCode.CANCELLED, /** @type {!RpcError} */ (error).code);
assertEquals('Aborted', /** @type {!RpcError} */ (error).message);
// Abort reason forwarded as cause.
const cause = /** @type {!RpcError} */ (error).cause;
assertEquals('cancelling', cause);
assertEquals(ErrorCode.ABORT, xhr.getLastErrorCode());
},
async testDeadline() {
const xhr = new XhrIo();
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
const methodDescriptor = createMethodDescriptor((bytes) => new MockReply());
const deadline = new Date();
deadline.setSeconds(deadline.getSeconds() + 1);
await new Promise((resolve, reject) => {
client.rpcCall(
'url', new MockRequest(), {'deadline': deadline.getTime().toString()},
methodDescriptor, (error, response) => {
assertNull(error);
resolve();
});
xhr.simulatePartialResponse(
googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)),
DEFAULT_RESPONSE_HEADERS);
xhr.simulateReadyStateChange(ReadyState.COMPLETE);
});
const headers = /** @type {!Object} */ (xhr.getLastRequestHeaders());
const headersWithDeadline = [...DEFAULT_UNARY_HEADERS, 'grpc-timeout'];
assertElementsEquals(headersWithDeadline, Object.keys(headers));
},
async testRpcError() {
const xhr = new XhrIo();
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
const methodDescriptor = createMethodDescriptor((bytes) => new MockReply());
const error = await new Promise((resolve, reject) => {
client.rpcCall(
'urlurl', new MockRequest(), /* metadata= */ {}, methodDescriptor,
(error, response) => {
assertNull(response);
resolve(error);
});
// This decodes to "grpc-status: 3"
xhr.simulatePartialResponse(
googCrypt.encodeByteArray(new Uint8Array([
128, 0, 0, 0, 14, 103, 114, 112, 99, 45,
115, 116, 97, 116, 117, 115, 58, 32, 51,
testRpcResponse: function() {
var client = new GrpcWebClientBase();
client.newXhr_ = function() {
return new MockXhr({
// This parses to [ { DATA: [4,5,6] }, { TRAILER: "a: b" } ]
response: googCrypt.encodeByteArray(new Uint8Array([
0, 0, 0, 0, 3, 4, 5, 6, 128, 0, 0, 0, 4, 97, 58, 32, 98
])),
DEFAULT_RESPONSE_HEADERS);
});
assertTrue(error instanceof RpcError);
assertEquals(3, error.code);
};
expectUnaryHeaders();
client.rpcCall(FAKE_METHOD, {}, {}, {
requestSerializeFn : function(request) {
return REQUEST_BYTES;
},
responseDeserializeFn : function(bytes) {
assertElementsEquals([4,5,6], [].slice.call(bytes));
return {"field1": PROTO_FIELD_VALUE};
}
}, function(error, response) {
assertNull(error);
assertEquals(PROTO_FIELD_VALUE, response.field1);
});
dataCallback();
},
async testRpcDeserializationError() {
const xhr = new XhrIo();
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
const responseDeserializeFn = () => {
throw new Error('Decoding error :)');
testRpcError: function() {
var client = new GrpcWebClientBase();
client.newXhr_ = function() {
return new MockXhr({
// This decodes to "grpc-status: 3"
response: googCrypt.encodeByteArray(new Uint8Array([
128, 0, 0, 0, 14, 103, 114, 112, 99, 45, 115, 116, 97, 116, 117, 115, 58, 32, 51
])),
});
};
const methodDescriptor = createMethodDescriptor(responseDeserializeFn);
const error = await new Promise((resolve, reject) => {
client.rpcCall(
'urlurl', new MockRequest(), /* metadata= */ {}, methodDescriptor,
(error, response) => {
expectUnaryHeaders();
client.rpcCall(FAKE_METHOD, {}, {}, {
requestSerializeFn : function(request) {
return REQUEST_BYTES;
},
responseDeserializeFn : function(bytes) {
return {};
}
}, function(error, response) {
assertNull(response);
resolve(error);
assertEquals(3, error.code);
});
xhr.simulatePartialResponse(
googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)),
DEFAULT_RESPONSE_HEADERS);
});
assertTrue(error instanceof RpcError);
assertEquals(StatusCode.INTERNAL, error.code);
},
async testRpcResponseHeader() {
const xhr = new XhrIo();
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
const methodDescriptor = createMethodDescriptor((bytes) => {
assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes));
return new MockReply('value');
});
const metadata = await new Promise((resolve, reject) => {
const call = client.rpcCall(
'url', new MockRequest(), /* metadata= */ {}, methodDescriptor,
(error, response) => {
assertNull(error);
});
call.on('metadata', (metadata) => {
resolve(metadata);
});
xhr.simulatePartialResponse(
googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)), {
'Content-Type': 'application/grpc-web-text',
'initial-metadata-key': 'initial-metadata-value',
});
xhr.simulateReadyStateChange(ReadyState.COMPLETE);
});
assertEquals('initial-metadata-value', metadata['initial-metadata-key']);
},
async testStreamInterceptor() {
const xhr = new XhrIo();
const interceptor = new StreamResponseInterceptor();
const methodDescriptor = createMethodDescriptor((bytes) => {
assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes));
return new MockReply('value');
});
const client =
new GrpcWebClientBase({'streamInterceptors': [interceptor]}, xhr);
const response = await new Promise((resolve, reject) => {
client.rpcCall(
'url', new MockRequest(), /* metadata= */ {}, methodDescriptor,
(error, response) => {
assertNull(error);
resolve(response);
});
xhr.simulatePartialResponse(
googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)),
DEFAULT_RESPONSE_HEADERS);
xhr.simulateReadyStateChange(ReadyState.COMPLETE);
});
assertEquals('Intercepted value', response.data);
},
});
/** Mocks a request proto object. */
class MockRequest {
/**
* @param {string=} data
*/
constructor(data = '') {
/** @type {string} */
this.data = data;
}
dataCallback();
}
});
/** Mocks a response proto object. */
class MockReply {
/**
* @param {string=} data
*/
constructor(data = '') {
/** @type {string} */
this.data = data;
}
}
/**
* Typedef for allowed response types.
*
* Number is allowed specifically for supporting falsy responses `0`, see:
* https://github.com/grpc/grpc-web/pull/1025
*
* @typedef {!MockReply|number}
*/
let AllowedResponseType;
/**
* @param {function(string): !AllowedResponseType} responseDeSerializeFn
* @return {!MethodDescriptor<!MockRequest, !AllowedResponseType>}
*/
function createMethodDescriptor(responseDeSerializeFn) {
return new MethodDescriptor(
/* name= */ '', /* methodType= */ null, MockRequest, MockReply,
(request) => [1, 2, 3], responseDeSerializeFn);
/** Sets expected headers as the unary response headers */
function expectUnaryHeaders() {
EXPECTED_HEADERS = EXPECTED_UNARY_HEADERS;
EXPECTED_HEADER_VALUES = EXPECTED_UNARY_HEADER_VALUES;
}
/**
* @implements {StreamInterceptor}
* @unrestricted
* @constructor
* @param {?Object} mockValues
* Mock XhrIO object to test the outgoing values
*/
class StreamResponseInterceptor {
constructor() {}
/**
* @override
* @template REQUEST, RESPONSE
* @param {!Request<REQUEST, RESPONSE>} request
* @param {function(!Request<REQUEST,RESPONSE>):
* !ClientReadableStream<RESPONSE>} invoker
* @return {!ClientReadableStream<RESPONSE>}
*/
intercept(request, invoker) {
return new InterceptedStream(invoker(request));
}
function MockXhr(mockValues) {
this.mockValues = mockValues;
this.headers = new Map();
}
/**
* @implements {ClientReadableStream}
* @template RESPONSE
* @final
*/
class InterceptedStream {
/**
* @param {!ClientReadableStream<RESPONSE>} stream
*/
constructor(stream) {
/** @const {!ClientReadableStream<RESPONSE>} */
this.stream = stream;
}
/**
* @override
* @param {string} eventType
* @param {function(?)} callback
* @return {!ClientReadableStream<RESPONSE>}
* @param {string} url
* @param {string=} opt_method
* @param {string=} opt_content
* @param {string=} opt_headers
*/
on(eventType, callback) {
if (eventType == 'data') {
const newCallback = (response) => {
response.data = 'Intercepted ' + response.data;
callback(response);
MockXhr.prototype.send = function(url, opt_method, opt_content, opt_headers) {
assertEquals(FAKE_METHOD, url);
assertEquals("POST", opt_method);
assertElementsEquals(googCrypt.encodeByteArray(new Uint8Array([0, 0, 0, 0, 3, 1, 2, 3])), opt_content);
assertElementsEquals(EXPECTED_HEADERS, this.headers.getKeys());
assertElementsEquals(EXPECTED_HEADER_VALUES, this.headers.getValues());
};
this.stream.on(eventType, newCallback);
} else {
this.stream.on(eventType, callback);
}
return this;
}
/**
* @override
* @return {!ClientReadableStream<RESPONSE>}
* @param {boolean} withCredentials
*/
cancel() {
this.stream.cancel();
return this;
}
MockXhr.prototype.setWithCredentials = function(withCredentials) {
return;
};
/**
* @override
* @param {string} eventType
* @param {function(?)} callback
* @return {!ClientReadableStream<RESPONSE>}
* @return {string} response
*/
removeListener(eventType, callback) {
this.stream.removeListener(eventType, callback);
return this;
}
}
MockXhr.prototype.getResponseText = function() {
return this.mockValues.response;
};
/**
* @return {string} response
*/
MockXhr.prototype.getResponseHeaders = function() {
return {};
};
/**
* @return {number} xhr state
*/
MockXhr.prototype.getReadyState = function() {
return 0;
};
/**
* @return {number} lastErrorCode
*/
MockXhr.prototype.getLastErrorCode = function() {
return 0;
};
/**
* @return {string} lastError
*/
MockXhr.prototype.getLastError = function() {
return 'server not responding';
};
/**
* @param {string} responseType
*/
MockXhr.prototype.setResponseType = function(responseType) {
return;
};

View File

@ -35,9 +35,9 @@ const ClientReadableStream = goog.require('grpc.web.ClientReadableStream');
const ErrorCode = goog.require('goog.net.ErrorCode');
const EventType = goog.require('goog.net.EventType');
const GrpcWebStreamParser = goog.require('grpc.web.GrpcWebStreamParser');
const RpcError = goog.require('grpc.web.RpcError');
const StatusCode = goog.require('grpc.web.StatusCode');
const XhrIo = goog.require('goog.net.XhrIo');
const XmlHttp = goog.require('goog.net.XmlHttp');
const events = goog.require('goog.events');
const googCrypt = goog.require('goog.crypt.base64');
const googString = goog.require('goog.string');
@ -46,27 +46,22 @@ const {Status} = goog.require('grpc.web.Status');
const GRPC_STATUS = 'grpc-status';
const GRPC_STATUS_MESSAGE = 'grpc-message';
const GRPC_STATUS = "grpc-status";
const GRPC_STATUS_MESSAGE = "grpc-message";
/** @type {!Array<string>} */
const EXCLUDED_RESPONSE_HEADERS =
['content-type', GRPC_STATUS, GRPC_STATUS_MESSAGE];
/**
* A stream that the client can read from. Used for calls that are streaming
* from the server side.
*
* @template RESPONSE
* @constructor
* @implements {ClientReadableStream}
* @final
* @unrestricted
*/
class GrpcWebClientReadableStream {
/**
* @param {!GenericTransportInterface} genericTransportInterface The
* GenericTransportInterface
*/
constructor(genericTransportInterface) {
const GrpcWebClientReadableStream = function(genericTransportInterface) {
/**
* @const
* @private
@ -81,45 +76,28 @@ class GrpcWebClientReadableStream {
this.responseDeserializeFn_ = null;
/**
* @const
* @private
* @type {!Array<function(!RESPONSE)>} The list of data callbacks
* @type {function(!RESPONSE)|null} The data callback
*/
this.onDataCallbacks_ = [];
/**
* @const
* @private
* @type {!Array<function(!Status)>} The list of status callbacks
*/
this.onStatusCallbacks_ = [];
/**
* @const
* @private
* @type {!Array<function(!Metadata)>} The list of metadata callbacks
*/
this.onMetadataCallbacks_ = [];
/**
* @const
* @private
* @type {!Array<function(!RpcError)>} The list of error callbacks
*/
this.onErrorCallbacks_ = [];
/**
* @const
* @private
* @type {!Array<function(...):?>} The list of stream end callbacks
*/
this.onEndCallbacks_ = [];
this.onDataCallback_ = null;
/**
* @private
* @type {boolean} Whether the stream has been aborted
* @type {function(!Status)|null} The status callback
*/
this.aborted_ = false;
this.onStatusCallback_ = null;
/**
* @private
* @type {function(...):?|null} The error callback
*/
this.onErrorCallback_ = null;
/**
* @private
* @type {function(...):?|null} The stream end callback
*/
this.onEndCallback_ = null;
/**
* @private
@ -134,209 +112,117 @@ class GrpcWebClientReadableStream {
*/
this.parser_ = new GrpcWebStreamParser();
const self = this;
events.listen(this.xhr_, EventType.READY_STATE_CHANGE, function(e) {
let contentType = self.xhr_.getStreamingResponseHeader('Content-Type');
var self = this;
events.listen(this.xhr_, EventType.READY_STATE_CHANGE,
function(e) {
var contentType = self.xhr_.getStreamingResponseHeader('Content-Type');
if (!contentType) return;
contentType = contentType.toLowerCase();
let byteSource;
if (googString.startsWith(contentType, 'application/grpc-web-text')) {
// Ensure responseText is not null
const responseText = self.xhr_.getResponseText() || '';
const newPos = responseText.length - responseText.length % 4;
const newData = responseText.substr(self.pos_, newPos - self.pos_);
var responseText = self.xhr_.getResponseText();
var newPos = responseText.length - responseText.length % 4;
var newData = responseText.substr(self.pos_, newPos - self.pos_);
if (newData.length == 0) return;
self.pos_ = newPos;
byteSource = googCrypt.decodeStringToUint8Array(newData);
var byteSource = googCrypt.decodeStringToUint8Array(newData);
} else if (googString.startsWith(contentType, 'application/grpc')) {
byteSource = new Uint8Array(
var byteSource = new Uint8Array(
/** @type {!ArrayBuffer} */ (self.xhr_.getResponse()));
} else {
self.handleError_(
new RpcError(StatusCode.UNKNOWN, 'Unknown Content-type received.'));
return;
}
let messages = null;
try {
messages = self.parser_.parse(byteSource);
} catch (err) {
self.handleError_(
new RpcError(StatusCode.UNKNOWN, 'Error in parsing response body'));
}
if (messages) {
const FrameType = GrpcWebStreamParser.FrameType;
for (let i = 0; i < messages.length; i++) {
var messages = self.parser_.parse([].slice.call(byteSource));
if (!messages) return;
var FrameType = GrpcWebStreamParser.FrameType;
for (var i = 0; i < messages.length; i++) {
if (FrameType.DATA in messages[i]) {
const data = messages[i][FrameType.DATA];
var data = messages[i][FrameType.DATA];
if (data) {
let isResponseDeserialized = false;
let response;
try {
response = self.responseDeserializeFn_(data);
isResponseDeserialized = true;
} catch (err) {
self.handleError_(new RpcError(
StatusCode.INTERNAL,
`Error when deserializing response data; error: ${err}` +
`, response: ${response}`));
}
if (isResponseDeserialized) {
self.sendDataCallbacks_(response);
var response = self.responseDeserializeFn_(data);
if (response) {
self.onDataCallback_(response);
}
}
}
if (FrameType.TRAILER in messages[i]) {
if (messages[i][FrameType.TRAILER].length > 0) {
let trailerString = '';
for (let pos = 0; pos < messages[i][FrameType.TRAILER].length;
var trailerString = "";
for (var pos = 0; pos < messages[i][FrameType.TRAILER].length;
pos++) {
trailerString +=
String.fromCharCode(messages[i][FrameType.TRAILER][pos]);
trailerString += String.fromCharCode(
messages[i][FrameType.TRAILER][pos]);
}
const trailers = self.parseHttp1Headers_(trailerString);
let grpcStatusCode = StatusCode.OK;
let grpcStatusMessage = '';
var trailers = self.parseHttp1Headers_(trailerString);
var grpcStatusCode = StatusCode.OK;
var grpcStatusMessage = "";
if (GRPC_STATUS in trailers) {
grpcStatusCode =
/** @type {!StatusCode} */ (Number(trailers[GRPC_STATUS]));
delete trailers[GRPC_STATUS];
grpcStatusCode = trailers[GRPC_STATUS];
}
if (GRPC_STATUS_MESSAGE in trailers) {
grpcStatusMessage = trailers[GRPC_STATUS_MESSAGE];
delete trailers[GRPC_STATUS_MESSAGE];
}
self.handleError_(
new RpcError(grpcStatusCode, grpcStatusMessage, trailers));
if (self.onStatusCallback_) {
self.onStatusCallback_({
code: Number(grpcStatusCode),
details: grpcStatusMessage,
metadata: trailers,
});
}
}
}
}
var readyState = self.xhr_.getReadyState();
if (readyState == XmlHttp.ReadyState.COMPLETE) {
if (self.onEndCallback_) {
self.onEndCallback_();
}
return;
}
});
events.listen(this.xhr_, EventType.COMPLETE, function(e) {
const lastErrorCode = self.xhr_.getLastErrorCode();
let grpcStatusCode = StatusCode.UNKNOWN;
let grpcStatusMessage = '';
const initialMetadata = /** @type {!Metadata} */ ({});
// Get response headers with lower case keys.
const rawResponseHeaders = self.xhr_.getResponseHeaders();
const responseHeaders = {};
for (const key in rawResponseHeaders) {
if (rawResponseHeaders.hasOwnProperty(key)) {
responseHeaders[key.toLowerCase()] = rawResponseHeaders[key];
}
}
Object.keys(responseHeaders).forEach((header_) => {
if (!(EXCLUDED_RESPONSE_HEADERS.includes(header_))) {
initialMetadata[header_] = responseHeaders[header_];
}
});
self.sendMetadataCallbacks_(initialMetadata);
// There's an XHR level error
let xhrStatusCode = -1;
if (!self.onErrorCallback_) return;
var lastErrorCode = self.xhr_.getLastErrorCode();
if (lastErrorCode != ErrorCode.NO_ERROR) {
switch (lastErrorCode) {
case ErrorCode.ABORT:
grpcStatusCode = StatusCode.ABORTED;
break;
case ErrorCode.TIMEOUT:
grpcStatusCode = StatusCode.DEADLINE_EXCEEDED;
break;
case ErrorCode.HTTP_ERROR:
xhrStatusCode = self.xhr_.getStatus();
grpcStatusCode = StatusCode.fromHttpStatus(xhrStatusCode);
break;
default:
grpcStatusCode = StatusCode.UNAVAILABLE;
}
if (grpcStatusCode == StatusCode.ABORTED && self.aborted_) {
self.onErrorCallback_({
code: StatusCode.UNAVAILABLE,
message: ErrorCode.getDebugMessage(lastErrorCode)
});
return;
}
let errorMessage = ErrorCode.getDebugMessage(lastErrorCode);
if (xhrStatusCode != -1) {
errorMessage += ', http status code: ' + xhrStatusCode;
}
self.handleError_(new RpcError(grpcStatusCode, errorMessage));
return;
}
let errorEmitted = false;
// Check whethere there are grpc specific response headers
if (GRPC_STATUS in responseHeaders) {
grpcStatusCode = /** @type {!StatusCode} */ (
Number(responseHeaders[GRPC_STATUS]));
if (GRPC_STATUS_MESSAGE in responseHeaders) {
grpcStatusMessage = responseHeaders[GRPC_STATUS_MESSAGE];
}
if (grpcStatusCode != StatusCode.OK) {
self.handleError_(new RpcError(
grpcStatusCode, grpcStatusMessage || '', responseHeaders));
errorEmitted = true;
}
}
if (!errorEmitted) {
self.sendEndCallbacks_();
}
var responseHeaders = self.xhr_.getResponseHeaders();
if (GRPC_STATUS in responseHeaders &&
responseHeaders[GRPC_STATUS] != StatusCode.OK) {
self.onErrorCallback_({
code: Number(responseHeaders[GRPC_STATUS]),
message: responseHeaders[GRPC_STATUS_MESSAGE]
});
}
});
};
/**
* @override
* @export
*/
on(eventType, callback) {
GrpcWebClientReadableStream.prototype.on = function(
eventType, callback) {
// TODO(stanleycheung): change eventType to @enum type
if (eventType == 'data') {
this.onDataCallbacks_.push(callback);
this.onDataCallback_ = callback;
} else if (eventType == 'status') {
this.onStatusCallbacks_.push(callback);
} else if (eventType == 'metadata') {
this.onMetadataCallbacks_.push(callback);
this.onStatusCallback_ = callback;
} else if (eventType == 'end') {
this.onEndCallbacks_.push(callback);
this.onEndCallback_ = callback;
} else if (eventType == 'error') {
this.onErrorCallbacks_.push(callback);
this.onErrorCallback_ = callback;
}
return this;
}
};
/**
* @private
* @param {!Array<function(?)>} callbacks the internal list of callbacks
* @param {function(?)} callback the callback to remove
*/
removeListenerFromCallbacks_(callbacks, callback) {
const index = callbacks.indexOf(callback);
if (index > -1) {
callbacks.splice(index, 1);
}
}
/**
* @export
* @override
*/
removeListener(eventType, callback) {
if (eventType == 'data') {
this.removeListenerFromCallbacks_(this.onDataCallbacks_, callback);
} else if (eventType == 'status') {
this.removeListenerFromCallbacks_(this.onStatusCallbacks_, callback);
} else if (eventType == 'metadata') {
this.removeListenerFromCallbacks_(this.onMetadataCallbacks_, callback);
} else if (eventType == 'end') {
this.removeListenerFromCallbacks_(this.onEndCallbacks_, callback);
} else if (eventType == 'error') {
this.removeListenerFromCallbacks_(this.onErrorCallbacks_, callback);
}
return this;
}
/**
* Register a callbackl to parse the response
@ -344,18 +230,19 @@ class GrpcWebClientReadableStream {
* @param {function(?):!RESPONSE} responseDeserializeFn The deserialize
* function for the proto
*/
setResponseDeserializeFn(responseDeserializeFn) {
GrpcWebClientReadableStream.prototype.setResponseDeserializeFn =
function(responseDeserializeFn) {
this.responseDeserializeFn_ = responseDeserializeFn;
}
};
/**
* @override
* @export
*/
cancel() {
this.aborted_ = true;
GrpcWebClientReadableStream.prototype.cancel = function() {
this.xhr_.abort();
}
};
/**
* Parse HTTP headers
@ -364,84 +251,17 @@ class GrpcWebClientReadableStream {
* @param {string} str The raw http header string
* @return {!Object} The header:value pairs
*/
parseHttp1Headers_(str) {
const chunks = str.trim().split('\r\n');
const headers = {};
for (let i = 0; i < chunks.length; i++) {
const pos = chunks[i].indexOf(':');
GrpcWebClientReadableStream.prototype.parseHttp1Headers_ =
function(str) {
var chunks = str.trim().split("\r\n");
var headers = {};
for (var i = 0; i < chunks.length; i++) {
var pos = chunks[i].indexOf(":");
headers[chunks[i].substring(0, pos).trim()] =
chunks[i].substring(pos+1).trim();
}
return headers;
}
/**
* A central place to handle errors
*
* @private
* @param {!RpcError} error The error object
*/
handleError_(error) {
if (error.code != StatusCode.OK) {
this.sendErrorCallbacks_(new RpcError(
error.code, decodeURIComponent(error.message || ''), error.metadata));
}
this.sendStatusCallbacks_(/** @type {!Status} */ ({
code: error.code,
details: decodeURIComponent(error.message || ''),
metadata: error.metadata
}));
}
/**
* @private
* @param {!RESPONSE} data The data to send back
*/
sendDataCallbacks_(data) {
for (let i = 0; i < this.onDataCallbacks_.length; i++) {
this.onDataCallbacks_[i](data);
}
}
/**
* @private
* @param {!Status} status The status to send back
*/
sendStatusCallbacks_(status) {
for (let i = 0; i < this.onStatusCallbacks_.length; i++) {
this.onStatusCallbacks_[i](status);
}
}
/**
* @private
* @param {!Metadata} metadata The metadata to send back
*/
sendMetadataCallbacks_(metadata) {
for (let i = 0; i < this.onMetadataCallbacks_.length; i++) {
this.onMetadataCallbacks_[i](metadata);
}
}
/**
* @private
* @param {!RpcError} error The error to send back
*/
sendErrorCallbacks_(error) {
for (let i = 0; i < this.onErrorCallbacks_.length; i++) {
this.onErrorCallbacks_[i](error);
}
}
/**
* @private
*/
sendEndCallbacks_() {
for (let i = 0; i < this.onEndCallbacks_.length; i++) {
this.onEndCallbacks_[i]();
}
}
}
};

View File

@ -49,11 +49,13 @@ const asserts = goog.require('goog.asserts');
/**
* The default grpc-web stream parser.
*
* @constructor
* @struct
* @implements {StreamParser}
* @final
*/
class GrpcWebStreamParser {
constructor() {
const GrpcWebStreamParser = function() {
/**
* The current error message, if any.
* @private {?string}
@ -97,8 +99,8 @@ class GrpcWebStreamParser {
this.countLengthBytes_ = 0;
/**
* Raw bytes of the current message. Uses Uint8Array by default. Falls back
* to native array when Uint8Array is unsupported.
* Raw bytes of the current message. Uses Uint8Array by default. Falls back to
* native array when Uint8Array is unsupported.
* @private {?Uint8Array|?Array<number>}
*/
this.messageBuffer_ = null;
@ -108,57 +110,81 @@ class GrpcWebStreamParser {
* @private {number}
*/
this.countMessageBytes_ = 0;
}
};
var Parser = GrpcWebStreamParser;
/**
* The parser state.
* @private @enum {number}
*/
Parser.State_ = {
INIT: 0, // expecting the next frame byte
LENGTH: 1, // expecting 4 bytes of length
MESSAGE: 2, // expecting more message bytes
INVALID: 3
};
/**
* Possible frame byte
* @enum {number}
*/
GrpcWebStreamParser.FrameType = {
DATA: 0x00, // expecting a data frame
TRAILER: 0x80, // expecting a trailer frame
};
var FrameType = GrpcWebStreamParser.FrameType;
/**
* @override
*/
isInputValid() {
GrpcWebStreamParser.prototype.isInputValid = function() {
return this.state_ != Parser.State_.INVALID;
}
};
/**
* @override
*/
getErrorMessage() {
GrpcWebStreamParser.prototype.getErrorMessage = function() {
return this.errorMessage_;
}
};
/**
* @override
* @return {boolean}
* @param {!Uint8Array|!Array<number>} inputBytes The current input buffer
* @param {number} pos The position in the current input that triggers the error
* @param {string} errorMsg Additional error message
* @throws {!Error} Throws an error indicating where the stream is broken
* @private
*/
acceptsBinaryInput() {
return true;
}
Parser.prototype.error_ = function(inputBytes, pos, errorMsg) {
this.state_ = Parser.State_.INVALID;
this.errorMessage_ = 'The stream is broken @' + this.streamPos_ + '/' + pos +
'. ' +
'Error: ' + errorMsg + '. ' +
'With input:\n' + inputBytes;
throw new Error(this.errorMessage_);
};
/**
* Parse the new input.
*
* Note that there is no Parser state to indicate the end of a stream.
*
* @param {string|!ArrayBuffer|!Uint8Array|!Array<number>} input The input
* data
* @throws {!Error} Throws an error message if the input is invalid.
* @return {?Array<string|!Object>} any parsed objects (atomic messages)
* in an array, or null if more data needs be read to parse any new object.
* @override
*/
parse(input) {
asserts.assert(
input instanceof Array || input instanceof ArrayBuffer ||
input instanceof Uint8Array);
GrpcWebStreamParser.prototype.parse = function(input) {
asserts.assert(input instanceof Array || input instanceof ArrayBuffer);
var parser = this;
var inputBytes;
var inputBytes = (input instanceof Array) ? input : new Uint8Array(input);
var pos = 0;
if (input instanceof Uint8Array || input instanceof Array) {
inputBytes = input;
} else {
inputBytes = new Uint8Array(input);
}
while (pos < inputBytes.length) {
switch (parser.state_) {
case Parser.State_.INVALID: {
@ -177,9 +203,7 @@ class GrpcWebStreamParser {
processMessageByte(inputBytes[pos]);
break;
}
default: {
throw new Error('unexpected parser state: ' + parser.state_);
}
default: { throw new Error('unexpected parser state: ' + parser.state_); }
}
parser.streamPos_++;
@ -248,53 +272,6 @@ class GrpcWebStreamParser {
parser.result_.push(message);
parser.state_ = Parser.State_.INIT;
}
}
}
const Parser = GrpcWebStreamParser;
/**
* The parser state.
* @private @enum {number}
*/
Parser.State_ = {
INIT: 0, // expecting the next frame byte
LENGTH: 1, // expecting 4 bytes of length
MESSAGE: 2, // expecting more message bytes
INVALID: 3
};
/**
* Possible frame byte
* @enum {number}
*/
GrpcWebStreamParser.FrameType = {
DATA: 0x00, // expecting a data frame
TRAILER: 0x80, // expecting a trailer frame
};
var FrameType = GrpcWebStreamParser.FrameType;
/**
* @param {!Uint8Array|!Array<number>} inputBytes The current input buffer
* @param {number} pos The position in the current input that triggers the error
* @param {string} errorMsg Additional error message
* @throws {!Error} Throws an error indicating where the stream is broken
* @private
*/
Parser.prototype.error_ = function(inputBytes, pos, errorMsg) {
this.state_ = Parser.State_.INVALID;
this.errorMessage_ = 'The stream is broken @' + this.streamPos_ + '/' + pos +
'. ' +
'Error: ' + errorMsg + '. ' +
'With input:\n' + inputBytes;
throw new Error(this.errorMessage_);
};

View File

@ -1,78 +0,0 @@
/**
* @fileoverview grpc-web client interceptors.
*
* The type of interceptors is determined by the response type of the RPC call.
* gRPC-Web has two generated clients for one service:
* FooServiceClient and FooServicePromiseClient. The response type of
* FooServiceClient is ClientReadableStream for BOTH unary calls and server
* streaming calls, so StreamInterceptor is expected to be used for intercepting
* FooServiceClient calls. The response type of PromiseClient is Promise, so use
* UnaryInterceptor for PromiseClients.
*/
goog.module('grpc.web.Interceptor');
goog.module.declareLegacyNamespace();
const ClientReadableStream = goog.require('grpc.web.ClientReadableStream');
const Request = goog.require('grpc.web.Request');
const UnaryResponse = goog.require('grpc.web.UnaryResponse');
/**
* Interceptor for RPC calls with response type `UnaryResponse`.
* An example implementation of UnaryInterceptor
* <pre>
* TestUnaryInterceptor.prototype.intercept = function(request, invoker) {
* const newRequest = ...
* return invoker(newRequest).then((response) => {
* // Do something with response.getMetadata
// Do something with response.getResponseMessage
* return response;
* });
* };
* </pre>
* @interface
*/
const UnaryInterceptor = function() {};
/**
* @export
* @abstract
* @template REQUEST, RESPONSE
* @param {!Request<REQUEST, RESPONSE>} request
* @param {function(!Request<REQUEST,RESPONSE>):!Promise<!UnaryResponse<RESPONSE>>}
* invoker
* @return {!Promise<!UnaryResponse<RESPONSE>>}
*/
UnaryInterceptor.prototype.intercept = function(request, invoker) {};
/**
* Interceptor for RPC calls with response type `ClientReadableStream`.
*
* Two steps to create a stream interceptor:
* <1>Create a new subclass of ClientReadableStream that wraps around the
* original stream and overrides its methods. <2>Create a new subclass of
* StreamInterceptor. While implementing the
* StreamInterceptor.prototype.intercept method, return the wrapped
* ClientReadableStream.
* @interface
*/
const StreamInterceptor = function() {};
/**
* @export
* @abstract
* @template REQUEST, RESPONSE
* @param {!Request<REQUEST, RESPONSE>} request
* @param {function(!Request<REQUEST,RESPONSE>):!ClientReadableStream<RESPONSE>}
* invoker
* @return {!ClientReadableStream<RESPONSE>}
*/
StreamInterceptor.prototype.intercept = function(request, invoker) {};
exports = {
UnaryInterceptor,
StreamInterceptor
};

View File

@ -1,15 +0,0 @@
/**
* @fileoverview grpc-web request/response metadata.
*
* Request and response headers will be included in the Metadata.
*/
goog.module('grpc.web.Metadata');
goog.module.declareLegacyNamespace();
/**
* @typedef {!Object<string,string>}
*/
let Metadata;
exports = Metadata;

View File

@ -1,119 +0,0 @@
/**
* @fileoverview Description of this file.
*
* A templated class that is used to address gRPC Web requests.
*/
goog.module('grpc.web.MethodDescriptor');
goog.module.declareLegacyNamespace();
const CallOptions = goog.require('grpc.web.CallOptions');
const Metadata = goog.requireType('grpc.web.Metadata');
const MethodDescriptorInterface = goog.requireType('grpc.web.MethodDescriptorInterface');
const MethodType = goog.requireType('grpc.web.MethodType');
const Request = goog.requireType('grpc.web.Request');
const RequestInternal = goog.require('grpc.web.RequestInternal');
const UnaryResponse = goog.requireType('grpc.web.UnaryResponse');
const UnaryResponseInternal = goog.require('grpc.web.UnaryResponseInternal');
const {Status} = goog.requireType('grpc.web.Status');
/**
* @final
* @implements {MethodDescriptorInterface<REQUEST, RESPONSE>}
* @template REQUEST, RESPONSE
* @unrestricted
*/
const MethodDescriptor = class {
/**
* @param {string} name
* @param {?MethodType} methodType
* @param {function(new: REQUEST, ...)} requestType
* @param {function(new: RESPONSE, ...)} responseType
* @param {function(REQUEST): ?} requestSerializeFn
* @param {function(?): RESPONSE} responseDeserializeFn
*/
constructor(
name, methodType, requestType, responseType, requestSerializeFn,
responseDeserializeFn) {
/** @const */
this.name = name;
/** @const */
this.methodType = methodType;
/** @const */
this.requestType = requestType;
/** @const */
this.responseType = responseType;
/** @const */
this.requestSerializeFn = requestSerializeFn;
/** @const */
this.responseDeserializeFn = responseDeserializeFn;
}
/**
* @override
* @param {REQUEST} requestMessage
* @param {!Metadata=} metadata
* @param {!CallOptions=} callOptions
* @return {!Request<REQUEST, RESPONSE>}
*/
createRequest(
requestMessage, metadata = {}, callOptions = new CallOptions()) {
return new RequestInternal(requestMessage, this, metadata, callOptions);
}
/**
* @override
* @param {RESPONSE} responseMessage
* @param {!Metadata=} metadata
* @param {?Status=} status
* @return {!UnaryResponse<REQUEST, RESPONSE>}
*/
createUnaryResponse(responseMessage, metadata = {}, status = null) {
return new UnaryResponseInternal(responseMessage, this, metadata, status);
}
/**
* @override
* @export
*/
getName() {
return this.name;
}
/**
* @override
*/
getMethodType() {
return this.methodType;
}
/**
* @override
* @return {function(new: RESPONSE, ...)}
*/
getResponseMessageCtor() {
return this.responseType;
}
/**
* @override
* @return {function(new: REQUEST, ...)}
*/
getRequestMessageCtor() {
return this.requestType;
}
/** @override */
getResponseDeserializeFn() {
return this.responseDeserializeFn;
}
/** @override */
getRequestSerializeFn() {
return this.requestSerializeFn;
}
};
exports = MethodDescriptor;

View File

@ -1,60 +0,0 @@
/**
* @fileoverview Description of this file.
*
* A templated class that is used to address gRPC Web requests.
*/
goog.module('grpc.web.MethodDescriptorInterface');
goog.module.declareLegacyNamespace();
const CallOptions = goog.requireType('grpc.web.CallOptions');
const Metadata = goog.requireType('grpc.web.Metadata');
const MethodType = goog.requireType('grpc.web.MethodType');
const Request = goog.requireType('grpc.web.Request');
const UnaryResponse = goog.requireType('grpc.web.UnaryResponse');
const {Status} = goog.requireType('grpc.web.Status');
/**
* @interface
* @template REQUEST, RESPONSE
*/
const MethodDescriptorInterface = function() {};
/**
* @param {REQUEST} requestMessage
* @param {!Metadata=} metadata
* @param {!CallOptions=} callOptions
* @return {!Request<REQUEST, RESPONSE>}
*/
MethodDescriptorInterface.prototype.createRequest = function(
requestMessage, metadata, callOptions) {};
/**
* @param {RESPONSE} responseMessage
* @param {!Metadata=} metadata
* @param {?Status=} status
* @return {!UnaryResponse<REQUEST, RESPONSE>}
*/
MethodDescriptorInterface.prototype.createUnaryResponse = function(
responseMessage, metadata, status) {};
/** @return {string} */
MethodDescriptorInterface.prototype.getName = function() {};
/** @return {?MethodType} */
MethodDescriptorInterface.prototype.getMethodType = function() {};
/** @return {function(new: RESPONSE, ?Array=)} */
MethodDescriptorInterface.prototype.getResponseMessageCtor = function() {};
/** @return {function(new: REQUEST, ?Array=)} */
MethodDescriptorInterface.prototype.getRequestMessageCtor = function() {};
/** @return {function(?): RESPONSE} */
MethodDescriptorInterface.prototype.getResponseDeserializeFn = function() {};
/** @return {function(REQUEST): ?} */
MethodDescriptorInterface.prototype.getRequestSerializeFn = function() {};
exports = MethodDescriptorInterface;

View File

@ -1,24 +0,0 @@
/**
* @fileoverview gRPC-Web method types.
*/
goog.module('grpc.web.MethodType');
goog.module.declareLegacyNamespace();
/**
* Available method types:
* MethodType.UNARY: unary request and unary response.
* MethodType.SERVER_STREAMING: unary request and streaming responses.
* MethodType.BIDI_STREAMING: streaming requests and streaming responses.
*
* @enum {string}
*/
const MethodType = {
'UNARY': 'unary',
'SERVER_STREAMING': 'server_streaming',
// Bidi streaming is experimental. Do not use.
'BIDI_STREAMING': 'bidi_streaming',
};
exports = MethodType;

View File

@ -1,60 +0,0 @@
/**
* @fileoverview A templated class that is used to address an individual
* gRPC-Web request instance.
*/
goog.module('grpc.web.Request');
goog.module.declareLegacyNamespace();
const CallOptions = goog.require('grpc.web.CallOptions');
const Metadata = goog.require('grpc.web.Metadata');
const MethodDescriptorInterface = goog.requireType('grpc.web.MethodDescriptorInterface');
/**
* @interface
* @template REQUEST, RESPONSE
*/
class Request {
/**
* @export
* @return {REQUEST}
*/
getRequestMessage() {}
/**
* @export
* @return {!MethodDescriptorInterface<REQUEST, RESPONSE>}
*/
getMethodDescriptor() {}
/**
* @export
* @return {!Metadata}
*/
getMetadata() {}
/**
* Client CallOptions. Note that CallOptions has not been implemented in
* grpc.web.AbstractClientbase yet, but will be used in
* grpc.web.GenericClient.
* @export
* @return {!CallOptions|undefined}
*/
getCallOptions() {}
/**
* @param {string} key
* @param {string} value
* @return {!Request<REQUEST, RESPONSE>}
*/
withMetadata(key, value) {}
/**
* @param {string} name
* @param {VALUE} value
* @template VALUE
* @return {!Request<REQUEST, RESPONSE>}
*/
withGrpcCallOption(name, value) {}
}
exports = Request;

View File

@ -1,94 +0,0 @@
/**
* @fileoverview Internal implementation of grpc.web.Request.
*/
goog.module('grpc.web.RequestInternal');
goog.module.declareLegacyNamespace();
const CallOptions = goog.require('grpc.web.CallOptions');
const Metadata = goog.require('grpc.web.Metadata');
const MethodDescriptor = goog.requireType('grpc.web.MethodDescriptor');
const Request = goog.require('grpc.web.Request');
/**
* @template REQUEST, RESPONSE
* @implements {Request<REQUEST, RESPONSE>}
* @final
* @package
*/
class RequestInternal {
/**
* @param {REQUEST} requestMessage
* @param {!MethodDescriptor<REQUEST, RESPONSE>} methodDescriptor
* @param {!Metadata} metadata
* @param {!CallOptions} callOptions
*/
constructor(requestMessage, methodDescriptor, metadata, callOptions) {
/**
* @const {REQUEST}
* @private
*/
this.requestMessage_ = requestMessage;
/**
* @const {!MethodDescriptor<REQUEST, RESPONSE>}
* @private
*/
this.methodDescriptor_ = methodDescriptor;
/** @const @private */
this.metadata_ = metadata;
/** @const @private */
this.callOptions_ = callOptions;
}
/**
* @override
* @return {REQUEST}
*/
getRequestMessage() {
return this.requestMessage_;
}
/**
* @override
* @return {!MethodDescriptor<REQUEST, RESPONSE>}
*/
getMethodDescriptor() {
return this.methodDescriptor_;
}
/**
* @override
* @return {!Metadata}
*/
getMetadata() {
return this.metadata_;
}
/**
* @override
* @return {!CallOptions|undefined}
*/
getCallOptions() {
return this.callOptions_;
}
/**
* @override
*/
withMetadata(key, value) {
this.metadata_[key] = value;
return this;
}
/**
* @override
*/
withGrpcCallOption(name, value) {
this.callOptions_.setOption(name, value);
return this;
}
}
exports = RequestInternal;

View File

@ -1,63 +0,0 @@
/**
*
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/**
* @fileoverview gRPC-Web Error objects
*
* gRPC-Web Error objects
*
* @suppress {lintChecks} gRPC-Web is still using default goog.module exports
* right now, and the output of grpc_generator.cc uses goog.provide.
*/
goog.module('grpc.web.RpcError');
const Metadata = goog.require('grpc.web.Metadata');
const StatusCode = goog.require('grpc.web.StatusCode');
/**
* gRPC-Web Error object, contains the {@link StatusCode}, a string message
* and {@link Metadata} contained in the error response.
*/
class RpcError extends Error {
/**
* @param {!StatusCode} code
* @param {string} message
* @param {!Metadata=} metadata
*/
constructor(code, message, metadata = {}) {
super(message);
/** @type {!StatusCode} */
this.code = code;
/** @type {!Metadata} */
this.metadata = metadata;
}
/** @override */
toString() {
const status = StatusCode.statusCodeName(this.code) || String(this.code);
let out = `RpcError(${status})`;
if (this.message) {
out += ': ' + this.message;
}
return out;
}
}
/** @override */
RpcError.prototype.name = 'RpcError';
exports = RpcError;

View File

@ -23,19 +23,16 @@
* @author stanleycheung@google.com (Stanley Cheung)
*/
goog.module('grpc.web.Status');
goog.module.declareLegacyNamespace();
/** @record */
function Status() {}
/** @export {number} */
Status.prototype.code;
/** @export {string} */
Status.prototype.details;
/** @export {(!Object<string, string>|undefined)} */
Status.prototype.metadata;
exports.Status = Status;
/**
* @typedef {{
* code: number,
* details: string,
* metadata: (!Object<string, string>|undefined)
* }}
*/
exports.Status;

View File

@ -24,61 +24,62 @@
*/
goog.module('grpc.web.StatusCode');
goog.module.declareLegacyNamespace();
/**
* gRPC Status Codes
* See:
* https://github.com/grpc/grpc/blob/master/include/grpcpp/impl/codegen/status_code_enum.h
* See: https://github.com/grpc/grpc/blob/master/include/grpc%2B%2B/impl/codegen/status_code_enum.h
* @enum {number}
*/
const StatusCode = {
// LINT.IfChange(status_codes)
// Not an error; returned on success.
'OK': 0,
OK: 0,
// The operation was cancelled (typically by the caller).
'CANCELLED': 1,
CANCELLED: 1,
// Unknown error. An example of where this error may be returned is if a
// Status value received from another address space belongs to an error-space
// that is not known in this address space. Also errors raised by APIs that
// do not return enough error information may be converted to this error.
'UNKNOWN': 2,
UNKNOWN: 2,
// Client specified an invalid argument. Note that this differs from
// FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments that are
// problematic regardless of the state of the system (e.g., a malformed file
// name).
'INVALID_ARGUMENT': 3,
INVALID_ARGUMENT: 3,
// Deadline expired before operation could complete. For operations that
// change the state of the system, this error may be returned even if the
// operation has completed successfully. For example, a successful response
// from a server could have been delayed long enough for the deadline to
// expire.
'DEADLINE_EXCEEDED': 4,
DEADLINE_EXCEEDED: 4,
// Some requested entity (e.g., file or directory) was not found.
'NOT_FOUND': 5,
NOT_FOUND: 5,
// Some entity that we attempted to create (e.g., file or directory) already
// exists.
'ALREADY_EXISTS': 6,
ALREADY_EXISTS: 6,
// The caller does not have permission to execute the specified operation.
// PERMISSION_DENIED must not be used for rejections caused by exhausting
// some resource (use RESOURCE_EXHAUSTED instead for those errors).
// PERMISSION_DENIED must not be used if the caller can not be identified
// (use UNAUTHENTICATED instead for those errors).
'PERMISSION_DENIED': 7,
PERMISSION_DENIED: 7,
// The request does not have valid authentication credentials for the
// operation.
'UNAUTHENTICATED': 16,
UNAUTHENTICATED: 16,
// Some resource has been exhausted, perhaps a per-user quota, or perhaps the
// entire file system is out of space.
'RESOURCE_EXHAUSTED': 8,
RESOURCE_EXHAUSTED: 8,
// Operation was rejected because the system is not in a state required for
// the operation's execution. For example, directory to be deleted may be
@ -98,14 +99,14 @@ const StatusCode = {
// REST Get/Update/Delete on a resource and the resource on the
// server does not match the condition. E.g., conflicting
// read-modify-write on the same resource.
'FAILED_PRECONDITION': 9,
FAILED_PRECONDITION: 9,
// The operation was aborted, typically due to a concurrency issue like
// sequencer check failures, transaction aborts, etc.
//
// See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
// and UNAVAILABLE.
'ABORTED': 10,
ABORTED: 10,
// Operation was attempted past the valid range. E.g., seeking or reading
// past end of file.
@ -120,35 +121,34 @@ const StatusCode = {
// OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific error)
// when it applies so that callers who are iterating through a space can
// easily look for an OUT_OF_RANGE error to detect when they are done.
'OUT_OF_RANGE': 11,
OUT_OF_RANGE: 11,
// Operation is not implemented or not supported/enabled in this service.
'UNIMPLEMENTED': 12,
UNIMPLEMENTED: 12,
// Internal errors. Means some invariants expected by underlying System has
// been broken. If you see one of these errors, Something is very broken.
'INTERNAL': 13,
INTERNAL: 13,
// The service is currently unavailable. This is a most likely a transient
// condition and may be corrected by retrying with a backoff.
//
// See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
// and UNAVAILABLE.
'UNAVAILABLE': 14,
UNAVAILABLE: 14,
// Unrecoverable data loss or corruption.
'DATA_LOSS': 15,
// LINT.ThenChange(:status_code_name)
DATA_LOSS: 15,
};
/**
* Convert HTTP Status code to gRPC Status code
* @param {number} httpStatus HTTP Status Code
* @return {!StatusCode} gRPC Status Code
* @param {number} http_status HTTP Status Code
* @return {number} gRPC Status Code
*/
StatusCode.fromHttpStatus = function(httpStatus) {
switch (httpStatus) {
StatusCode.fromHttpStatus = function(http_status) {
switch (http_status) {
case 200:
return StatusCode.OK;
case 400:
@ -182,91 +182,4 @@ StatusCode.fromHttpStatus = function(httpStatus) {
};
/**
* Convert a {@link StatusCode} to an HTTP Status code
* @param {!StatusCode} statusCode GRPC Status Code
* @return {number} HTTP Status code
*/
StatusCode.getHttpStatus = function(statusCode) {
switch (statusCode) {
case StatusCode.OK:
return 200;
case StatusCode.INVALID_ARGUMENT:
return 400;
case StatusCode.UNAUTHENTICATED:
return 401;
case StatusCode.PERMISSION_DENIED:
return 403;
case StatusCode.NOT_FOUND:
return 404;
case StatusCode.ABORTED:
return 409;
case StatusCode.FAILED_PRECONDITION:
return 412;
case StatusCode.RESOURCE_EXHAUSTED:
return 429;
case StatusCode.CANCELLED:
return 499;
case StatusCode.UNKNOWN:
return 500;
case StatusCode.UNIMPLEMENTED:
return 501;
case StatusCode.UNAVAILABLE:
return 503;
case StatusCode.DEADLINE_EXCEEDED:
return 504;
/* everything else is unknown */
default:
return 0;
}
};
/**
* Returns the human readable name for a {@link StatusCode}. Useful for logging.
* @param {!StatusCode} statusCode GRPC Status Code
* @return {string} the human readable name for the status code
*/
StatusCode.statusCodeName = function(statusCode) {
switch (statusCode) {
// LINT.IfChange(status_code_name)
case StatusCode.OK:
return 'OK';
case StatusCode.CANCELLED:
return 'CANCELLED';
case StatusCode.UNKNOWN:
return 'UNKNOWN';
case StatusCode.INVALID_ARGUMENT:
return 'INVALID_ARGUMENT';
case StatusCode.DEADLINE_EXCEEDED:
return 'DEADLINE_EXCEEDED';
case StatusCode.NOT_FOUND:
return 'NOT_FOUND';
case StatusCode.ALREADY_EXISTS:
return 'ALREADY_EXISTS';
case StatusCode.PERMISSION_DENIED:
return 'PERMISSION_DENIED';
case StatusCode.UNAUTHENTICATED:
return 'UNAUTHENTICATED';
case StatusCode.RESOURCE_EXHAUSTED:
return 'RESOURCE_EXHAUSTED';
case StatusCode.FAILED_PRECONDITION:
return 'FAILED_PRECONDITION';
case StatusCode.ABORTED:
return 'ABORTED';
case StatusCode.OUT_OF_RANGE:
return 'OUT_OF_RANGE';
case StatusCode.UNIMPLEMENTED:
return 'UNIMPLEMENTED';
case StatusCode.INTERNAL:
return 'INTERNAL';
case StatusCode.UNAVAILABLE:
return 'UNAVAILABLE';
case StatusCode.DATA_LOSS:
return 'DATA_LOSS';
default:
return '';
// LINT.ThenChange(:status_codes)
}
};
exports = StatusCode;

View File

@ -1,41 +0,0 @@
goog.module('grpc.web.StatusCodeTest');
goog.setTestOnly('grpc.web.StatusCodeTest');
const StatusCode = goog.require('grpc.web.StatusCode');
const testSuite = goog.require('goog.testing.testSuite');
/** @type {!Map<number, !StatusCode>} */
const statusMap = new Map([
[200, StatusCode.OK],
[400, StatusCode.INVALID_ARGUMENT],
[401, StatusCode.UNAUTHENTICATED],
[403, StatusCode.PERMISSION_DENIED],
[404, StatusCode.NOT_FOUND],
[409, StatusCode.ABORTED],
[412, StatusCode.FAILED_PRECONDITION],
[429, StatusCode.RESOURCE_EXHAUSTED],
[500, StatusCode.UNKNOWN],
[501, StatusCode.UNIMPLEMENTED],
[503, StatusCode.UNAVAILABLE],
[504, StatusCode.DEADLINE_EXCEEDED],
]);
testSuite({
testFromHttpStatus() {
statusMap.forEach((statusCode, httpStatus) => {
assertEquals(StatusCode.fromHttpStatus(httpStatus), statusCode);
});
},
testGetHttpStatus() {
statusMap.forEach((statusCode, httpStatus) => {
assertEquals(StatusCode.getHttpStatus(statusCode), httpStatus);
});
},
testUnknown() {
assertEquals(StatusCode.getHttpStatus(StatusCode.UNKNOWN), 500);
assertEquals(StatusCode.fromHttpStatus(511), StatusCode.UNKNOWN);
}
});

View File

@ -0,0 +1,192 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/**
* @fileoverview gRPC web client Readable Stream
*
* This class is being returned after a gRPC streaming call has been
* started. This class provides functionality for user to operates on
* the stream, e.g. set onData callback, etc.
*
* This wraps the underlying goog.net.streams.NodeReadableStream
*
* @author stanleycheung@google.com (Stanley Cheung)
*/
goog.module('grpc.web.StreamBodyClientReadableStream');
goog.module.declareLegacyNamespace();
const ClientReadableStream = goog.require('grpc.web.ClientReadableStream');
const ErrorCode = goog.require('goog.net.ErrorCode');
const NodeReadableStream = goog.require('goog.net.streams.NodeReadableStream');
const StatusCode = goog.require('grpc.web.StatusCode');
const XhrIo = goog.require('goog.net.XhrIo');
const {GenericTransportInterface} = goog.require('grpc.web.GenericTransportInterface');
const {Status} = goog.require('grpc.web.Status');
/**
* A stream that the client can read from. Used for calls that are streaming
* from the server side.
*
* @template RESPONSE
* @constructor
* @implements {ClientReadableStream}
* @final
* @param {!GenericTransportInterface} genericTransportInterface The
* GenericTransportInterface
*/
const StreamBodyClientReadableStream = function(genericTransportInterface) {
/**
* @const
* @private
* @type {?NodeReadableStream|undefined} The XHR Node Readable Stream
*/
this.xhrNodeReadableStream_ = genericTransportInterface.nodeReadableStream;
/**
* @private
* @type {function(?): RESPONSE|null} The deserialize function for the proto
*/
this.responseDeserializeFn_ = null;
/**
* @const
* @private
* @type {?XhrIo|undefined} The XhrIo object
*/
this.xhr_ = genericTransportInterface.xhr;
/**
* @private
* @type {function(RESPONSE)|null} The data callback
*/
this.onDataCallback_ = null;
/**
* @private
* @type {function(!Status)|null}
* The status callback
*/
this.onStatusCallback_ = null;
/**
* @private
* @type {function(...):?|null}
* The stream end callback
*/
this.onEndCallback_ = null;
/**
* @private
* @type {function(...):?|null}
* The stream error callback
*/
this.onErrorCallback_ = null;
/**
* @private
* @type {function(?):!Status|null}
* A function to parse the Rpc Status response
*/
this.rpcStatusParseFn_ = null;
// Add the callback to the underlying stream
var self = this;
this.xhrNodeReadableStream_.on('data', function(data) {
if ('1' in data && self.onDataCallback_) {
var response = self.responseDeserializeFn_(data['1']);
self.onDataCallback_(response);
}
if ('2' in data && self.onStatusCallback_) {
var status = self.rpcStatusParseFn_(data['2']);
self.onStatusCallback_(status);
}
});
this.xhrNodeReadableStream_.on('end', function() {
if (self.onEndCallback_) {
self.onEndCallback_();
}
});
this.xhrNodeReadableStream_.on('error', function() {
if (!self.onErrorCallback_) return;
var lastErrorCode = self.xhr_.getLastErrorCode();
if (lastErrorCode == ErrorCode.NO_ERROR) return;
self.onErrorCallback_({
code: StatusCode.UNAVAILABLE,
message: ErrorCode.getDebugMessage(lastErrorCode)
});
});
};
/**
* @override
*/
StreamBodyClientReadableStream.prototype.on = function(
eventType, callback) {
// TODO(stanleycheung): change eventType to @enum type
if (eventType == 'data') {
this.onDataCallback_ = callback;
} else if (eventType == 'status') {
this.onStatusCallback_ = callback;
} else if (eventType == 'end') {
this.onEndCallback_ = callback;
} else if (eventType == 'error') {
this.onErrorCallback_ = callback;
}
return this;
};
/**
* Register a callbackl to parse the response
*
* @param {function(?): RESPONSE} responseDeserializeFn The deserialize
* function for the proto
*/
StreamBodyClientReadableStream.prototype.setResponseDeserializeFn =
function(responseDeserializeFn) {
this.responseDeserializeFn_ = responseDeserializeFn;
};
/**
* Register a function to parse RPC status response
*
* @param {function(?):!Status} rpcStatusParseFn A function to parse
* the RPC status response
*/
StreamBodyClientReadableStream.prototype.setRpcStatusParseFn = function(rpcStatusParseFn) {
this.rpcStatusParseFn_ = rpcStatusParseFn;
};
/**
* @override
*/
StreamBodyClientReadableStream.prototype.cancel = function() {
this.xhr_.abort();
};
exports = StreamBodyClientReadableStream;

View File

@ -1,44 +0,0 @@
/**
* @fileoverview gRPC web client UnaryResponse returned by grpc unary calls.
*/
goog.module('grpc.web.UnaryResponse');
goog.module.declareLegacyNamespace();
const Metadata = goog.requireType('grpc.web.Metadata');
const MethodDescriptorInterface = goog.requireType('grpc.web.MethodDescriptorInterface');
const {Status} = goog.requireType('grpc.web.Status');
/**
* @interface
* @template REQUEST, RESPONSE
*/
class UnaryResponse {
/**
* @export
* @return {RESPONSE}
*/
getResponseMessage() {}
/**
* @export
* @return {!Metadata}
*/
getMetadata() {}
/**
* @export
* @return {!MethodDescriptorInterface<REQUEST, RESPONSE>}
*/
getMethodDescriptor() {}
/**
* gRPC status. Trailer metadata returned from a gRPC server is in
* status.metadata.
* @export
* @return {?Status}
*/
getStatus() {}
}
exports = UnaryResponse;

View File

@ -1,73 +0,0 @@
/**
* @fileoverview gRPC-Web UnaryResponse internal implementation.
*/
goog.module('grpc.web.UnaryResponseInternal');
goog.module.declareLegacyNamespace();
const Metadata = goog.requireType('grpc.web.Metadata');
const MethodDescriptor = goog.requireType('grpc.web.MethodDescriptor');
const UnaryResponse = goog.requireType('grpc.web.UnaryResponse');
const {Status} = goog.requireType('grpc.web.Status');
/**
* @template REQUEST, RESPONSE
* @implements {UnaryResponse<REQUEST, RESPONSE>}
* @final
* @package
*/
class UnaryResponseInternal {
/**
* @param {RESPONSE} responseMessage
* @param {!MethodDescriptor<REQUEST, RESPONSE>} methodDescriptor
* @param {!Metadata=} metadata
* @param {?Status=} status
*/
constructor(responseMessage, methodDescriptor, metadata = {}, status = null) {
/**
* @const {RESPONSE}
* @private
*/
this.responseMessage_ = responseMessage;
/**
* @const {!Metadata}
* @private
*/
this.metadata_ = metadata;
/**
* @const {!MethodDescriptor<REQUEST, RESPONSE>}
* @private
*/
this.methodDescriptor_ = methodDescriptor;
/**
* @const {?Status}
* @private
*/
this.status_ = status;
}
/** @override */
getResponseMessage() {
return this.responseMessage_;
}
/** @override */
getMetadata() {
return this.metadata_;
}
/** @override */
getMethodDescriptor() {
return this.methodDescriptor_;
}
/** @override */
getStatus() {
return this.status_;
}
}
exports = UnaryResponseInternal;

View File

@ -0,0 +1,105 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/**
* @fileoverview A generic gRPC-Web client customized for Protobuf.js.
*
* (This API is experimental and subject to change.)
*
* @author updogliu@google.com (Zihan Liu)
*/
// TODO(updogliu): add support of server streaming request.
goog.module('grpc.web.util.GenericPbjsClient');
var AbstractClientBase = goog.require('grpc.web.AbstractClientBase');
var Error = goog.require('grpc.web.Error');
var GatewayClientBase = goog.require('grpc.web.GatewayClientBase');
/**
* A generic gRPC-Web client customized for Protobuf.js
*
* @param {string} hostname The hostname of the server
* @constructor
* @struct
* @final
*/
var GenericPbjsClient = function(hostname) {
/**
* The underlying client base
* @private @const {!GatewayClientBase}
*/
this.clientBase_ = new GatewayClientBase();
/**
* The hostname of the server
* @private {string}
*/
this.hostname_ = hostname;
};
/**
* Get the full name (without the leading dot) of the service of the method.
*
* @param {!Object} method The method (a Protobuf.js Method object)
* @return {string} The full name of the service containing the method
*/
function getServiceName(method) {
var fullName = method.parent.fullName;
if (fullName.startsWith('.')) {
fullName = fullName.substring(1);
}
return fullName;
}
/**
* @param {!Object} method
* The method to invoke (an instance of Protobuf.js Method)
* @param {!Object} request
* The request (an instance of Protobuf.js Message or a payload object)
* @param {!Object<string, string>} metadata User defined call metadata
* @param {function(?Error, ?Object)} callback A callback function
* which takes (error, response)
*/
GenericPbjsClient.prototype.rpcCall = function(
method, request, metadata, callback) {
method.resolve();
var requestType = method.resolvedRequestType;
var responseType = method.resolvedResponseType;
var methodInfo = /** @type {!AbstractClientBase.MethodInfo<?, ?>} */ ({
requestSerializeFn: function(request) {
return requestType.encode(request).finish();
},
responseDeserializeFn: function(payload) {
return responseType.decode(payload);
}
});
// Make a gRPC-Web call.
var url = this.hostname_ + '/' + getServiceName(method) + '/' + method.name;
this.clientBase_.rpcCall(url, request, metadata, methodInfo, callback);
};
exports = GenericPbjsClient;

View File

@ -1 +0,0 @@
build_file: "grpc-web/scripts/run_interop_tests.sh"

View File

@ -1 +0,0 @@
build_file: "grpc-web/scripts/kokoro.sh"

View File

@ -1 +1 @@
build_file: "grpc-web/scripts/run_basic_tests.sh"
build_file: "grpc-web/scripts/kokoro.sh"

View File

@ -15,13 +15,15 @@
* limitations under the License.
*
*/
import * as grpcWeb from 'grpc-web';
import {MessageOuter} from './generated/test01_pb';
#include "net/grpc/gateway/backend/backend.h"
let inner1 = new MessageOuter.MessageInner();
inner1.setValue(123);
let msgOuter = new MessageOuter();
msgOuter.setSomepropList([inner1]);
namespace grpc {
namespace gateway {
export {msgOuter}
Backend::Backend() : frontend_(nullptr) {}
Backend::~Backend() {}
} // namespace gateway
} // namespace grpc

View File

@ -0,0 +1,62 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef NET_GRPC_GATEWAY_BACKEND_BACKEND_H_
#define NET_GRPC_GATEWAY_BACKEND_BACKEND_H_
#include <memory>
#include "net/grpc/gateway/runtime/request.h"
#include "net/grpc/gateway/runtime/tag.h"
#include "third_party/grpc/include/grpcpp/support/status.h"
namespace grpc {
namespace gateway {
class Frontend;
class Backend {
public:
Backend();
virtual ~Backend();
Backend(const Backend&) = delete;
Backend& operator=(const Backend&) = delete;
// Start the backend proxy progress.
virtual void Start() = 0;
// Send request to backend.
virtual void Send(std::unique_ptr<Request> request, Tag* on_done) = 0;
// Cancel the request to backend.
virtual void Cancel(const Status& reason) = 0;
protected:
Frontend* frontend() { return frontend_; }
private:
friend class Frontend;
void set_frontend(Frontend* frontend) { frontend_ = frontend; }
Frontend* frontend_;
};
} // namespace gateway
} // namespace grpc
#endif // NET_GRPC_GATEWAY_BACKEND_BACKEND_H_

View File

@ -0,0 +1,346 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "net/grpc/gateway/backend/grpc_backend.h"
#include <algorithm>
#include <cctype>
#include <iterator>
#include <utility>
#include <vector>
#include "net/grpc/gateway/frontend/frontend.h"
#include "net/grpc/gateway/log.h"
#include "net/grpc/gateway/runtime/runtime.h"
#include "net/grpc/gateway/runtime/types.h"
#include "third_party/grpc/include/grpc/byte_buffer.h"
#include "third_party/grpc/include/grpc/byte_buffer_reader.h"
#include "third_party/grpc/include/grpc/grpc.h"
#include "third_party/grpc/include/grpc/slice.h"
#include "third_party/grpc/include/grpc/support/alloc.h"
#include "third_party/grpc/include/grpc/support/time.h"
#define BACKEND_PREFIX "[addr: %s, host: %s, method: %s] "
#define BACKEND_INFO(f, ...) \
INFO(BACKEND_PREFIX f, address_.c_str(), host_.c_str(), method_.c_str(), \
##__VA_ARGS__)
#define BACKEND_DEBUG(f, ...) \
DEBUG(BACKEND_PREFIX f, address_.c_str(), host_.c_str(), method_.c_str(), \
##__VA_ARGS__)
#define BACKEND_ERROR(f, ...) \
ERROR(BACKEND_PREFIX f, address_.c_str(), host_.c_str(), method_.c_str(), \
##__VA_ARGS__)
namespace grpc {
namespace gateway {
GrpcBackend::GrpcBackend()
: use_shared_channel_pool_(false),
channel_(nullptr),
call_(nullptr),
request_buffer_(nullptr),
response_buffer_(nullptr),
status_code_(grpc_status_code::GRPC_STATUS_OK),
status_details_(grpc_empty_slice()),
is_cancelled_(false) {
BACKEND_DEBUG("Creating GRPC backend proxy.");
grpc_metadata_array_init(&response_initial_metadata_);
grpc_metadata_array_init(&response_trailing_metadata_);
}
GrpcBackend::~GrpcBackend() {
BACKEND_DEBUG("Deleting GRPC backend proxy.");
for (auto& m : request_initial_metadata_) {
grpc_slice_unref(m.key);
grpc_slice_unref(m.value);
}
grpc_metadata_array_destroy(&response_initial_metadata_);
grpc_metadata_array_destroy(&response_trailing_metadata_);
if (request_buffer_ != nullptr) {
grpc_byte_buffer_destroy(request_buffer_);
}
if (response_buffer_ != nullptr) {
grpc_byte_buffer_destroy(response_buffer_);
}
grpc_slice_unref(status_details_);
if (call_ != nullptr) {
BACKEND_DEBUG("Destroying GRPC call.");
grpc_call_unref(call_);
}
if (!use_shared_channel_pool_ && channel_ != nullptr) {
BACKEND_DEBUG("Destroying GRPC channel.");
grpc_channel_destroy(channel_);
}
}
grpc_channel* GrpcBackend::CreateChannel() {
return Runtime::Get().GetBackendChannel(
address_, use_shared_channel_pool_, ssl_, ssl_target_override_,
ssl_pem_root_certs_, ssl_pem_private_key_, ssl_pem_cert_chain_);
}
grpc_call* GrpcBackend::CreateCall() {
BACKEND_DEBUG("Creating GRPC call.");
grpc_slice method_slice = grpc_slice_from_copied_string(method_.c_str());
grpc_slice host_slice = grpc_slice_from_static_string(host_.c_str());
grpc_call* call = grpc_channel_create_call(
channel_, nullptr, 0, Runtime::Get().grpc_event_queue(), method_slice,
host_.empty() ? nullptr : &host_slice, gpr_inf_future(GPR_CLOCK_REALTIME),
nullptr);
grpc_slice_unref(method_slice);
return call;
}
void GrpcBackend::Start() {
channel_ = CreateChannel();
call_ = CreateCall();
// Receives GRPC response initial metadata.
grpc_op ops[1];
ops[0].op = GRPC_OP_RECV_INITIAL_METADATA;
ops[0].data.recv_initial_metadata.recv_initial_metadata =
&response_initial_metadata_;
ops[0].flags = 0;
ops[0].reserved = nullptr;
grpc_call_error error = grpc_call_start_batch(
call_, ops, 1,
BindTo(frontend(), this, &GrpcBackend::OnResponseInitialMetadata),
nullptr);
if (error != GRPC_CALL_OK) {
BACKEND_DEBUG("GRPC batch failed: %s", grpc_call_error_to_string(error));
}
}
void GrpcBackend::OnResponseInitialMetadata(bool result) {
if (!result) {
FinishWhenTagFail(
"Receiving initial metadata for GRPC response from backend failed.");
return;
}
std::unique_ptr<Response> response(new Response());
std::unique_ptr<Headers> response_headers(new Headers());
for (size_t i = 0; i < response_initial_metadata_.count; i++) {
grpc_metadata* metadata = response_initial_metadata_.metadata + i;
response_headers->push_back(Header(
std::string(
reinterpret_cast<char*>(GRPC_SLICE_START_PTR(metadata->key)),
GRPC_SLICE_LENGTH(metadata->key)),
string_ref(
reinterpret_cast<char*>(GRPC_SLICE_START_PTR(metadata->value)),
GRPC_SLICE_LENGTH(metadata->value))));
}
response->set_headers(std::move(response_headers));
frontend()->Send(std::move(response));
// Receives next GRPC response message.
grpc_op ops[1];
ops[0].op = GRPC_OP_RECV_MESSAGE;
ops[0].data.recv_message.recv_message = &response_buffer_;
ops[0].flags = 0;
ops[0].reserved = nullptr;
grpc_call_error error = grpc_call_start_batch(
call_, ops, 1, BindTo(frontend(), this, &GrpcBackend::OnResponseMessage),
nullptr);
if (error != GRPC_CALL_OK) {
BACKEND_DEBUG("GRPC batch failed: %s", grpc_call_error_to_string(error));
}
}
void GrpcBackend::OnResponseMessage(bool result) {
if (!result) {
FinishWhenTagFail("Receiving GRPC response message from backend failed.");
return;
}
if (response_buffer_ == nullptr) {
// Receives the GRPC response status.
grpc_op ops[1];
memset(ops, 0, sizeof(ops));
ops[0].op = GRPC_OP_RECV_STATUS_ON_CLIENT;
ops[0].data.recv_status_on_client.status = &status_code_;
ops[0].data.recv_status_on_client.status_details = &status_details_;
ops[0].data.recv_status_on_client.trailing_metadata =
&response_trailing_metadata_;
ops[0].flags = 0;
ops[0].reserved = nullptr;
grpc_call_error error = grpc_call_start_batch(
call_, ops, 1, BindTo(frontend(), this, &GrpcBackend::OnResponseStatus),
nullptr);
if (error != GRPC_CALL_OK) {
BACKEND_DEBUG("GRPC batch failed: %s", grpc_call_error_to_string(error));
}
return;
}
std::unique_ptr<Response> response(new Response());
std::unique_ptr<Message> message(new Message());
grpc_byte_buffer_reader reader;
grpc_byte_buffer_reader_init(&reader, response_buffer_);
grpc_slice slice;
while (grpc_byte_buffer_reader_next(&reader, &slice)) {
message->push_back(Slice(slice, Slice::STEAL_REF));
}
grpc_byte_buffer_reader_destroy(&reader);
grpc_byte_buffer_destroy(response_buffer_);
response->set_message(std::move(message));
frontend()->Send(std::move(response));
// Receives next GRPC response message.
grpc_op ops[1];
ops[0].op = GRPC_OP_RECV_MESSAGE;
ops[0].data.recv_message.recv_message = &response_buffer_;
ops[0].flags = 0;
ops[0].reserved = nullptr;
grpc_call_error error = grpc_call_start_batch(
call_, ops, 1, BindTo(frontend(), this, &GrpcBackend::OnResponseMessage),
nullptr);
if (error != GRPC_CALL_OK) {
BACKEND_DEBUG("GRPC batch failed: %s", grpc_call_error_to_string(error));
}
}
void GrpcBackend::OnResponseStatus(bool result) {
if (!result) {
FinishWhenTagFail("Receiving GRPC response's status from backend failed.");
return;
}
std::unique_ptr<Response> response(new Response());
grpc::string status_details;
if (!GRPC_SLICE_IS_EMPTY(status_details_)) {
status_details = grpc::string(
reinterpret_cast<char*>(GRPC_SLICE_START_PTR(status_details_)),
GRPC_SLICE_LENGTH(status_details_));
}
response->set_status(std::unique_ptr<grpc::Status>(new grpc::Status(
static_cast<grpc::StatusCode>(status_code_), status_details)));
std::unique_ptr<Trailers> response_trailers(new Trailers());
for (size_t i = 0; i < response_trailing_metadata_.count; i++) {
grpc_metadata* metadata = response_trailing_metadata_.metadata + i;
response_trailers->push_back(Trailer(
std::string(
reinterpret_cast<char*>(GRPC_SLICE_START_PTR(metadata->key)),
GRPC_SLICE_LENGTH(metadata->key)),
string_ref(
reinterpret_cast<char*>(GRPC_SLICE_START_PTR(metadata->value)),
GRPC_SLICE_LENGTH(metadata->value))));
}
response->set_trailers(std::move(response_trailers));
frontend()->Send(std::move(response));
}
void GrpcBackend::Send(std::unique_ptr<Request> request, Tag* on_done) {
grpc_op ops[3] = {};
grpc_op* op = ops;
if (request->headers() != nullptr) {
for (Header& header : *request->headers()) {
std::transform(header.first.begin(), header.first.end(),
header.first.begin(), ::tolower);
if (header.first == kGrpcAcceptEncoding) {
continue;
}
grpc_metadata initial_metadata;
initial_metadata.key =
grpc_slice_from_copied_string(header.first.c_str());
initial_metadata.value = grpc_slice_from_copied_buffer(
header.second.data(), header.second.size());
initial_metadata.flags = 0;
request_initial_metadata_.push_back(initial_metadata);
}
op->op = GRPC_OP_SEND_INITIAL_METADATA;
op->data.send_initial_metadata.metadata = request_initial_metadata_.data();
op->data.send_initial_metadata.count = request_initial_metadata_.size();
op->flags = 0;
op->reserved = nullptr;
op++;
}
if (request->message() != nullptr) {
op->op = GRPC_OP_SEND_MESSAGE;
std::vector<grpc_slice> slices;
for (auto& piece : *request->message()) {
// TODO(fengli): Once I get an API to access the grpc_slice in a Slice,
// the copy can be eliminated.
slices.push_back(grpc_slice_from_copied_buffer(
reinterpret_cast<const char*>(piece.begin()), piece.size()));
}
if (request_buffer_ != nullptr) {
grpc_byte_buffer_destroy(request_buffer_);
}
request_buffer_ = grpc_raw_byte_buffer_create(slices.data(), slices.size());
for (auto& slice : slices) {
grpc_slice_unref(slice);
}
op->data.send_message.send_message = request_buffer_;
op->flags = 0;
op->reserved = nullptr;
op++;
}
if (request->final()) {
op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT;
op->flags = 0;
op->reserved = nullptr;
op++;
}
GPR_ASSERT(op != ops);
if (op != ops) {
grpc_call_error error =
grpc_call_start_batch(call_, ops, op - ops, on_done, nullptr);
BACKEND_DEBUG("grpc_call_start_batch: %s",
grpc_call_error_to_string(error));
}
}
void GrpcBackend::Cancel(const Status& reason) {
if (is_cancelled_) {
BACKEND_DEBUG("GRPC has been cancelled, skip redundant cancellation: %s",
reason.error_message().c_str());
return;
}
is_cancelled_ = true;
BACKEND_DEBUG("Canceling GRPC: %s", reason.error_message().c_str());
cancel_reason_ = reason;
grpc_call_error error = grpc_call_cancel_with_status(
call_, static_cast<grpc_status_code>(cancel_reason_.error_code()),
cancel_reason_.error_message().c_str(), nullptr);
if (error != GRPC_CALL_OK) {
BACKEND_DEBUG("GRPC cancel failed: %s", grpc_call_error_to_string(error));
}
}
void GrpcBackend::FinishWhenTagFail(const char* error) {
BACKEND_DEBUG("%s", error);
std::unique_ptr<Response> response(new Response());
if (is_cancelled_) {
response->set_status(std::unique_ptr<Status>(new Status(cancel_reason_)));
} else {
response->set_status(
std::unique_ptr<Status>(new Status(StatusCode::INTERNAL, error)));
}
frontend()->Send(std::move(response));
}
} // namespace gateway
} // namespace grpc

View File

@ -0,0 +1,122 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef NET_GRPC_GATEWAY_BACKEND_GRPC_BACKEND_H_
#define NET_GRPC_GATEWAY_BACKEND_GRPC_BACKEND_H_
#include <memory>
#include <string>
#include "net/grpc/gateway/backend/backend.h"
#include "net/grpc/gateway/runtime/request.h"
#include "net/grpc/gateway/runtime/response.h"
#include "net/grpc/gateway/runtime/tag.h"
#include "third_party/grpc/include/grpcpp/support/config.h"
#include "third_party/grpc/include/grpc/grpc.h"
namespace grpc {
namespace gateway {
class GrpcBackend : public Backend {
public:
GrpcBackend();
~GrpcBackend() override;
GrpcBackend(const GrpcBackend&) = delete;
GrpcBackend& operator=(const GrpcBackend&) = delete;
void Start() override;
void Send(std::unique_ptr<Request> request, Tag* on_done) override;
void Cancel(const Status& reason) override;
void set_address(const string& address) { address_ = address; }
void set_host(const string& host) { host_ = host; }
void set_method(const string& method) { method_ = method; }
void set_use_shared_channel_pool(bool use_shared_channel_pool) {
use_shared_channel_pool_ = use_shared_channel_pool;
}
void set_ssl(bool ssl) { ssl_ = ssl; }
void set_ssl_target_override(const string& ssl_target_override) {
ssl_target_override_ = ssl_target_override;
}
void set_ssl_pem_root_certs(const string& ssl_pem_root_certs) {
ssl_pem_root_certs_ = ssl_pem_root_certs;
}
void set_ssl_pem_private_key(const string& ssl_pem_private_key) {
ssl_pem_private_key_ = ssl_pem_private_key;
}
void set_ssl_pem_cert_chain(const string& ssl_pem_cert_chain) {
ssl_pem_cert_chain_ = ssl_pem_cert_chain;
}
private:
// Create a GRPC channel.
grpc_channel* CreateChannel();
// Create a GRPC call.
grpc_call* CreateCall();
void OnResponseInitialMetadata(bool result);
void OnResponseMessage(bool result);
void OnResponseStatus(bool result);
void FinishWhenTagFail(const char* error);
// The backend address we connect to.
string address_;
// The HTTP host header of the request.
string host_;
// The HTTP method of the request.
string method_;
// True if the shared channel pool should be used.
bool use_shared_channel_pool_;
// True if ssl should be used.
bool ssl_;
// The GRPC SSL target override.
string ssl_target_override_;
// The file location which contains the root certs in pem format.
string ssl_pem_root_certs_;
// The file location which contains the client private key in pem format.
string ssl_pem_private_key_;
// The file location which contains the client cert chain in pem format.
string ssl_pem_cert_chain_;
// The GRPC channel.
grpc_channel* channel_;
// The GRPC call.
grpc_call* call_;
// The GRPC request buffer.
Request request_;
// The GRPC request initial metadata.
std::vector<grpc_metadata> request_initial_metadata_;
// The GRPC response initial metadata.
grpc_metadata_array response_initial_metadata_;
// The GRPC request buffer.
grpc_byte_buffer* request_buffer_;
// The GRPC response buffer.
grpc_byte_buffer* response_buffer_;
grpc_status_code status_code_;
grpc_slice status_details_;
grpc_metadata_array response_trailing_metadata_;
// True if the GRPC call has been cancelled by client.
bool is_cancelled_;
// The status which represents why the GRPC call is cancelled.
Status cancel_reason_;
};
} // namespace gateway
} // namespace grpc
#endif // NET_GRPC_GATEWAY_BACKEND_GRPC_BACKEND_H_

View File

@ -15,21 +15,29 @@
* limitations under the License.
*
*/
import * as grpcWeb from 'grpc-web';
import {EchoRequest, EchoResponse} from './generated/echo_pb';
import {EchoServicePromiseClient} from './generated/echo_grpc_web_pb';
#include "net/grpc/gateway/codec/b64_proto_decoder.h"
const echoService = new EchoServicePromiseClient(
'http://localhost:8080', null, null);
namespace grpc {
namespace gateway {
let req = new EchoRequest();
req.setMessage('aaa');
B64ProtoDecoder::B64ProtoDecoder() {}
// this test tries to make sure that these types are as accurate as possible
B64ProtoDecoder::~B64ProtoDecoder() {}
let p1 : Promise<EchoResponse> = echoService.echo(req, {});
Status B64ProtoDecoder::Decode() {
std::vector<Slice> buffer;
if (!base64_.Decode(*inputs(), &buffer)) {
return Status(StatusCode::INVALID_ARGUMENT, "Invalid base64 inputs.");
}
// why does the .then() add this extra 'void' type to the returned Promise?
let p2 : Promise<void | EchoResponse> = p1.then((response: EchoResponse) => {
});
inputs()->clear();
for (Slice& s : buffer) {
Append(s);
}
return ProtoDecoder::Decode();
}
} // namespace gateway
} // namespace grpc

View File

@ -0,0 +1,44 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef NET_GRPC_GATEWAY_CODEC_B64_PROTO_DECODER_H_
#define NET_GRPC_GATEWAY_CODEC_B64_PROTO_DECODER_H_
#include "net/grpc/gateway/codec/base64.h"
#include "net/grpc/gateway/codec/proto_decoder.h"
#include "third_party/grpc/include/grpcpp/support/status.h"
namespace grpc {
namespace gateway {
class B64ProtoDecoder : public ProtoDecoder {
public:
B64ProtoDecoder();
~B64ProtoDecoder() override;
B64ProtoDecoder(const B64ProtoDecoder&) = delete;
B64ProtoDecoder& operator=(const B64ProtoDecoder&) = delete;
Status Decode() override;
private:
Base64 base64_;
};
} // namespace gateway
} // namespace grpc
#endif // NET_GRPC_GATEWAY_CODEC_B64_PROTO_DECODER_H_

View File

@ -0,0 +1,44 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "net/grpc/gateway/codec/b64_proto_encoder.h"
namespace grpc {
namespace gateway {
B64ProtoEncoder::B64ProtoEncoder() {}
B64ProtoEncoder::~B64ProtoEncoder() {}
void B64ProtoEncoder::Encode(grpc::ByteBuffer* input,
std::vector<Slice>* result) {
std::vector<Slice> buffer;
ProtoEncoder::Encode(input, &buffer);
base64_.Encode(buffer, result);
}
void B64ProtoEncoder::EncodeStatus(const grpc::Status& status,
const Trailers* trailers,
std::vector<Slice>* result) {
std::vector<Slice> buffer;
ProtoEncoder::EncodeStatus(status, trailers, &buffer);
base64_.Encode(buffer, result);
}
} // namespace gateway
} // namespace grpc

View File

@ -0,0 +1,47 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef NET_GRPC_GATEWAY_CODEC_B64_PROTO_ENCODER_H_
#define NET_GRPC_GATEWAY_CODEC_B64_PROTO_ENCODER_H_
#include "net/grpc/gateway/codec/base64.h"
#include "net/grpc/gateway/codec/proto_encoder.h"
namespace grpc {
namespace gateway {
class B64ProtoEncoder : public ProtoEncoder {
public:
B64ProtoEncoder();
~B64ProtoEncoder() override;
// B64ProtoEncoder is neither copyable nor movable.
B64ProtoEncoder(const B64ProtoEncoder&) = delete;
B64ProtoEncoder& operator=(const B64ProtoEncoder&) = delete;
void Encode(grpc::ByteBuffer* input, std::vector<Slice>* result) override;
void EncodeStatus(const grpc::Status& status, const Trailers* trailers,
std::vector<Slice>* result) override;
private:
Base64 base64_;
};
} // namespace gateway
} // namespace grpc
#endif // NET_GRPC_GATEWAY_CODEC_B64_PROTO_ENCODER_H_

View File

@ -0,0 +1,49 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "net/grpc/gateway/codec/b64_stream_body_decoder.h"
#include <vector>
#include "net/grpc/gateway/codec/decoder.h"
#include "third_party/grpc/include/grpcpp/support/slice.h"
#include "third_party/grpc/include/grpcpp/support/status.h"
namespace grpc {
namespace gateway {
B64StreamBodyDecoder::B64StreamBodyDecoder() {}
B64StreamBodyDecoder::~B64StreamBodyDecoder() {}
Status B64StreamBodyDecoder::Decode() {
std::vector<Slice> buffer;
if (!base64_.Decode(*inputs(), &buffer)) {
return Status(StatusCode::INVALID_ARGUMENT, "Invalid base64 inputs.");
}
inputs()->clear();
for (Slice& s : buffer) {
Append(s);
}
return StreamBodyDecoder::Decode();
}
} // namespace gateway
} // namespace grpc

View File

@ -0,0 +1,44 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef NET_GRPC_GATEWAY_CODEC_B64_STREAM_BODY_DECODER_H_
#define NET_GRPC_GATEWAY_CODEC_B64_STREAM_BODY_DECODER_H_
#include "net/grpc/gateway/codec/base64.h"
#include "net/grpc/gateway/codec/stream_body_decoder.h"
#include "third_party/grpc/include/grpcpp/support/status.h"
namespace grpc {
namespace gateway {
class B64StreamBodyDecoder : public StreamBodyDecoder {
public:
B64StreamBodyDecoder();
~B64StreamBodyDecoder() override;
B64StreamBodyDecoder(const B64StreamBodyDecoder&) = delete;
B64StreamBodyDecoder& operator=(const B64StreamBodyDecoder&) = delete;
Status Decode() override;
private:
Base64 base64_;
};
} // namespace gateway
} // namespace grpc
#endif // NET_GRPC_GATEWAY_CODEC_B64_STREAM_BODY_DECODER_H_

View File

@ -0,0 +1,44 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "net/grpc/gateway/codec/b64_stream_body_encoder.h"
namespace grpc {
namespace gateway {
B64StreamBodyEncoder::B64StreamBodyEncoder() {}
B64StreamBodyEncoder::~B64StreamBodyEncoder() {}
void B64StreamBodyEncoder::Encode(grpc::ByteBuffer* input,
std::vector<Slice>* result) {
std::vector<Slice> buffer;
StreamBodyEncoder::Encode(input, &buffer, true);
base64_.Encode(buffer, result);
}
void B64StreamBodyEncoder::EncodeStatus(const grpc::Status& status,
const Trailers* trailers,
std::vector<Slice>* result) {
std::vector<Slice> buffer;
StreamBodyEncoder::EncodeStatus(status, trailers, &buffer, true);
base64_.Encode(buffer, result);
}
} // namespace gateway
} // namespace grpc

View File

@ -0,0 +1,51 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef NET_GRPC_GATEWAY_CODEC_B64_STREAM_BODY_ENCODER_H_
#define NET_GRPC_GATEWAY_CODEC_B64_STREAM_BODY_ENCODER_H_
#include <vector>
#include "net/grpc/gateway/codec/base64.h"
#include "net/grpc/gateway/codec/stream_body_encoder.h"
#include "net/grpc/gateway/runtime/types.h"
#include "third_party/grpc/include/grpcpp/support/byte_buffer.h"
#include "third_party/grpc/include/grpcpp/support/slice.h"
#include "third_party/grpc/include/grpcpp/support/status.h"
namespace grpc {
namespace gateway {
class B64StreamBodyEncoder : public StreamBodyEncoder {
public:
B64StreamBodyEncoder();
~B64StreamBodyEncoder() override;
B64StreamBodyEncoder(const B64StreamBodyEncoder&) = delete;
B64StreamBodyEncoder& operator=(const B64StreamBodyEncoder&) = delete;
void Encode(grpc::ByteBuffer* input, std::vector<Slice>* result) override;
void EncodeStatus(const grpc::Status& status, const Trailers* trailers,
std::vector<Slice>* result) override;
private:
Base64 base64_;
};
} // namespace gateway
} // namespace grpc
#endif // NET_GRPC_GATEWAY_CODEC_B64_STREAM_BODY_ENCODER_H_

View File

@ -0,0 +1,274 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "net/grpc/gateway/codec/base64.h"
#include <stdint.h>
#include <cstring>
namespace grpc {
namespace gateway {
namespace {
const char kPad = '=';
// Map from base64 encoded char to raw byte.
const int32_t b64_bytes[] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0 - 9
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 10 - 19
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 20 - 29
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 30 - 39
-1, -1, -1, 0x3E, -1, -1, -1, 0x3F, 0x34, 0x35, // 40 - 49
0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, -1, -1, // 50 - 59
-1, 0x7F, -1, -1, -1, 0x00, 0x01, 0x02, 0x03, 0x04, // 60 - 69
0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, // 70 - 79
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, // 80 - 89
0x19, -1, -1, -1, -1, -1, -1, 0x1A, 0x1B, 0x1C, // 90 - 99
0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, // 100 - 109
0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, // 110 - 119
0x31, 0x32, 0x33, -1, -1, -1, -1, -1, -1, -1, // 120 - 129
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 130 - 139
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 140 - 149
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 150 - 159
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 160 - 169
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 170 - 179
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 180 - 189
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 190 - 199
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 200 - 209
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 210 - 219
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 220 - 229
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 230 - 239
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 240 - 249
-1, -1, -1, -1, -1, -1, // 250 - 255
};
const char b64_chars[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
} // namespace
bool Base64::IsBase64Char(uint8_t c) { return b64_bytes[c] != -1; }
Base64::Base64() : decode_buffer_{0}, decode_buffer_length_(0) {}
Base64::~Base64() {}
std::unique_ptr<Slice> Base64::Encode(const Slice& input_slice, uint8_t* buffer,
size_t* buffer_length, bool is_last) {
size_t data_size = *buffer_length + input_slice.size();
size_t encoded_size = data_size / 3 * 4;
size_t tail_size = data_size % 3;
if (is_last && tail_size > 0) {
encoded_size += 4;
}
if (encoded_size == 0) {
if (input_slice.size() > 0) {
memcpy(buffer + *buffer_length, input_slice.begin(), input_slice.size());
*buffer_length += input_slice.size();
}
return nullptr;
}
grpc_slice output_slice = grpc_slice_malloc(encoded_size);
uint8_t* output = GRPC_SLICE_START_PTR(output_slice);
const uint8_t* input = input_slice.begin();
// trailers only.
if (data_size == 1) {
if (*buffer_length == 0) {
Encode1CharGroup(*input, output);
} else {
Encode1CharGroup(buffer[0], output);
}
*buffer_length = 0;
} else if (data_size == 2) {
if (*buffer_length == 0) {
Encode2CharGroup(*input, *(input + 1), output);
} else if (*buffer_length == 1) {
Encode2CharGroup(buffer[0], *input, output);
} else if (*buffer_length == 2) {
Encode2CharGroup(buffer[0], buffer[1], output);
}
*buffer_length = 0;
} else if (data_size > 2) {
// Encodes the first group, together with the buffer.
if (*buffer_length == 1) {
Encode3CharGroup(buffer[0], *input, *(input + 1), output);
output += 4;
input += 2;
} else if (*buffer_length == 2) {
Encode3CharGroup(buffer[0], buffer[1], *input, output);
output += 4;
input += 1;
}
// Encodes the other groups, besides the tail.
while (input < input_slice.end() - tail_size) {
Encode3CharGroup(*input, *(input + 1), *(input + 2), output);
output += 4;
input += 3;
}
// Encodes the tail group if current slice is the last one.
if (tail_size > 0) {
if (is_last) {
if (tail_size == 2) {
Encode2CharGroup(*input, *(input + 1), output);
} else if (tail_size == 1) {
Encode1CharGroup(*input, output);
}
*buffer_length = 0;
} else {
memcpy(buffer, input, tail_size);
*buffer_length = tail_size;
}
} else {
*buffer_length = 0;
}
}
return std::unique_ptr<Slice>(new Slice(output_slice, Slice::STEAL_REF));
}
void Base64::Encode1CharGroup(uint8_t input_0, uint8_t* output) {
*output++ = b64_chars[(input_0 >> 2) & 0x3F];
*output++ = b64_chars[(input_0 & 0x03) << 4];
*output++ = kPad;
*output++ = kPad;
}
void Base64::Encode2CharGroup(uint8_t input_0, uint8_t input_1,
uint8_t* output) {
*output++ = b64_chars[(input_0 >> 2) & 0x3F];
*output++ = b64_chars[((input_0 & 0x03) << 4) | ((input_1 >> 4) & 0x0F)];
*output++ = b64_chars[(input_1 & 0x0F) << 2];
*output++ = kPad;
}
void Base64::Encode3CharGroup(uint8_t input_0, uint8_t input_1, uint8_t input_2,
uint8_t* output) {
*output++ = b64_chars[(input_0 >> 2) & 0x3F];
*output++ = b64_chars[((input_0 & 0x03) << 4) | ((input_1 >> 4) & 0x0F)];
*output++ = b64_chars[((input_1 & 0x0F) << 2) | ((input_2 >> 6) & 0x03)];
*output++ = b64_chars[input_2 & 0x3F];
}
bool Base64::Encode(const std::vector<Slice>& input,
std::vector<Slice>* output) {
uint8_t buffer[2] = {0};
size_t buffer_length = 0;
for (size_t i = 0; i < input.size(); i++) {
std::unique_ptr<Slice> encoded_slice =
Encode(input[i], buffer, &buffer_length, (i == input.size() - 1));
if (encoded_slice) {
output->push_back(*encoded_slice);
}
}
return true;
}
bool Base64::Decode(const std::vector<Slice>& input,
std::vector<Slice>* output) {
for (const Slice& slice_in : input) {
size_t base64_length = slice_in.size() + decode_buffer_length_;
size_t binary_length = base64_length / 4 * 3;
size_t base64_leftover_length = base64_length % 4;
if (base64_length < 4) {
// No enough data to form a group for decoding, copy everything to decode
// buffer.
if (slice_in.size() > 0) {
memcpy(decode_buffer_ + decode_buffer_length_, slice_in.begin(),
slice_in.size());
decode_buffer_length_ += slice_in.size();
}
continue;
}
grpc_slice slice_out = grpc_slice_malloc(binary_length);
uint8_t* result_offset = GRPC_SLICE_START_PTR(slice_out);
if (decode_buffer_length_ > 0) {
// Decode the leftover.
memcpy(decode_buffer_ + decode_buffer_length_, slice_in.begin(),
4 - decode_buffer_length_);
int size = DecodeGroup(decode_buffer_, result_offset);
if (size == -1) {
return false;
}
result_offset += size;
if (size != 3) {
binary_length -= (3 - size);
}
}
for (size_t i = (4 - decode_buffer_length_) % 4;
i < slice_in.size() - base64_leftover_length; i = i + 4) {
int size = DecodeGroup(slice_in.begin() + i, result_offset);
if (size == -1) {
return false;
}
result_offset += size;
if (size != 3) {
binary_length -= (3 - size);
}
}
GRPC_SLICE_SET_LENGTH(slice_out, binary_length);
output->push_back(Slice(slice_out, Slice::STEAL_REF));
if (base64_leftover_length > 0) {
memcpy(decode_buffer_, slice_in.end() - base64_leftover_length,
base64_leftover_length);
}
decode_buffer_length_ = base64_leftover_length;
}
if (output->empty()) {
output->push_back(Slice(grpc_empty_slice(), Slice::STEAL_REF));
}
return true;
}
int Base64::DecodeGroup(const uint8_t* input, uint8_t* output) {
int size = 3;
uint8_t byte0 = *input;
uint8_t byte1 = *(input + 1);
uint8_t byte2 = *(input + 2);
uint8_t byte3 = *(input + 3);
int32_t packed0 = b64_bytes[byte0];
int32_t packed1 = b64_bytes[byte1];
int32_t packed2 = b64_bytes[byte2];
int32_t packed3 = b64_bytes[byte3];
if (packed0 == -1 || packed1 == -1 || packed2 == -1 || packed3 == -1) {
return -1;
}
uint32_t packed = packed0 << 18 | packed1 << 12 | packed2 << 6 | packed3;
if ((packed & 0xFF000000) != 0 || byte0 == kPad || byte1 == kPad ||
(byte2 == kPad && byte3 != kPad)) {
return -1;
}
if (byte2 == kPad) {
size--;
}
if (byte3 == kPad) {
size--;
}
*output++ = static_cast<uint8_t>(packed >> 16);
*output++ = static_cast<uint8_t>(packed >> 8);
*output++ = static_cast<uint8_t>(packed);
return size;
}
} // namespace gateway
} // namespace grpc

View File

@ -0,0 +1,73 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef NET_GRPC_GATEWAY_CODEC_BASE64_H_
#define NET_GRPC_GATEWAY_CODEC_BASE64_H_
#include <stddef.h>
#include <cstdint>
#include <memory>
#include <vector>
#include "third_party/grpc/include/grpcpp/support/slice.h"
namespace grpc {
namespace gateway {
class Base64 {
public:
// Returns true if the given character is a valid base64 character, includes
// padding.
static bool IsBase64Char(uint8_t c);
Base64();
virtual ~Base64();
Base64(const Base64&) = delete;
Base64& operator=(const Base64&) = delete;
// Encodes the input to base64 encoding, returns true if success.
bool Encode(const std::vector<Slice>& input, std::vector<Slice>* output);
// Decodes the base64 encoded input, returns true if decode success.
bool Decode(const std::vector<Slice>& input, std::vector<Slice>* output);
private:
// Encodes once single slice together with the data remain in last slice to
// base64. Remained data which cannot be encoded will be put back to the
// buffer. Padding applied when the input slice is the last one.
std::unique_ptr<Slice> Encode(const Slice& input, uint8_t* buffer,
size_t* buffer_length, bool is_last);
void Encode1CharGroup(uint8_t input_0, uint8_t* output);
void Encode2CharGroup(uint8_t input_0, uint8_t input_1, uint8_t* output);
void Encode3CharGroup(uint8_t input_0, uint8_t input_1, uint8_t input_2,
uint8_t* output);
// Decodes a base64 group. The input must be a pointer to uint8_t array with
// at least 4 elements. The output must be a pointer to uint8_t array with
// at least 3 elements. Returns the decoded data size if decode success, else
// returns -1.
int DecodeGroup(const uint8_t* input, uint8_t* output);
uint8_t decode_buffer_[4];
size_t decode_buffer_length_;
};
} // namespace gateway
} // namespace grpc
#endif // NET_GRPC_GATEWAY_CODEC_BASE64_H_

View File

@ -15,13 +15,17 @@
* limitations under the License.
*
*/
import * as grpcWeb from 'grpc-web';
import {Integer} from './generated/test03_pb';
import {MyServiceClient} from './generated/Test02ServiceClientPb';
#include "net/grpc/gateway/codec/decoder.h"
const service = new MyServiceClient('http://mydummy.com', null, null);
const req = new Integer();
namespace grpc {
namespace gateway {
service.addOne(req, {}, (err: grpcWeb.RpcError, resp: Integer) => {
});
Decoder::Decoder() {}
Decoder::~Decoder() {}
void Decoder::Append(Slice input) { inputs_.push_back(input); }
} // namespace gateway
} // namespace grpc

View File

@ -0,0 +1,66 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef NET_GRPC_GATEWAY_CODEC_DECODER_H_
#define NET_GRPC_GATEWAY_CODEC_DECODER_H_
#include <deque>
#include <memory>
#include <vector>
#include "third_party/grpc/include/grpcpp/support/byte_buffer.h"
#include "third_party/grpc/include/grpcpp/support/slice.h"
namespace grpc {
namespace gateway {
// Interface for GRPC-Gateway decoders. A decoder instance records the internal
// states during the processing of a request (stream). Decoder decodes different
// front end protocols to GRPC backend.
class Decoder {
public:
Decoder();
virtual ~Decoder();
Decoder(const Decoder&) = delete;
Decoder& operator=(const Decoder&) = delete;
// Appends a piece of data to decode.
virtual void Append(Slice input);
// Decodes the inputs passed to `Append()` since the last call to `Decode()`
// and appends the decoded results to those available from `results()`.
// This method may be invoked multiple times when processing a streamed
// request.
virtual Status Decode() = 0;
// Returns the decoded messages.
std::deque<std::unique_ptr<ByteBuffer>>* results() { return &results_; }
protected:
// Returns the buffered inputs. When the inputs are not enough to be decoded
// into a new message they will be buffered in this field.
std::vector<Slice>* inputs() { return &inputs_; }
private:
std::vector<Slice> inputs_;
std::deque<std::unique_ptr<ByteBuffer>> results_;
};
} // namespace gateway
} // namespace grpc
#endif // NET_GRPC_GATEWAY_CODEC_DECODER_H_

View File

@ -0,0 +1,34 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "net/grpc/gateway/codec/encoder.h"
namespace grpc {
namespace gateway {
Encoder::Encoder() {}
Encoder::~Encoder() {}
void Encoder::EncodeStatus(const grpc::Status& status,
std::vector<Slice>* result) {
EncodeStatus(status, nullptr, result);
}
} // namespace gateway
} // namespace grpc

View File

@ -0,0 +1,53 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef NET_GRPC_GATEWAY_CODEC_ENCODER_H_
#define NET_GRPC_GATEWAY_CODEC_ENCODER_H_
#include "net/grpc/gateway/runtime/types.h"
#include "third_party/grpc/include/grpcpp/support/byte_buffer.h"
namespace grpc {
namespace gateway {
// Interface for GRPC-Gateway encoders. A encoder instance records the internal
// states during the processing of a response (stream). Encoder encodes the GRPC
// response to different front end protocols.
class Encoder {
public:
Encoder();
virtual ~Encoder();
Encoder(const Encoder&) = delete;
Encoder& operator=(const Encoder&) = delete;
// Encodes a GRPC response message to the front end protocol.
virtual void Encode(grpc::ByteBuffer* input, std::vector<Slice>* result) = 0;
// Encodes a GRPC response status to the front end protocol.
virtual void EncodeStatus(const grpc::Status& status,
std::vector<Slice>* result);
// Encodes a GRPC response status and trailers to the frontend protocol.
virtual void EncodeStatus(const grpc::Status& status,
const Trailers* trailers,
std::vector<Slice>* result) = 0;
};
} // namespace gateway
} // namespace grpc
#endif // NET_GRPC_GATEWAY_CODEC_ENCODER_H_

View File

@ -0,0 +1,140 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "net/grpc/gateway/codec/grpc_decoder.h"
#include "net/grpc/gateway/log.h"
#include "net/grpc/gateway/utils.h"
#include "third_party/grpc/src/core/lib/compression/message_compress.h"
#include "third_party/grpc/src/core/lib/iomgr/exec_ctx.h"
namespace grpc {
namespace gateway {
GrpcDecoder::GrpcDecoder()
: state_(kExpectingCompressedFlag),
compression_algorithm_(kIdentity),
compressed_flag_(0),
message_length_(0) {}
GrpcDecoder::~GrpcDecoder() {}
Status GrpcDecoder::Decode() {
grpc_core::ExecCtx exec_ctx;
for (const Slice& slice : *inputs()) {
if (slice.size() == 0) {
continue;
}
for (size_t i = 0; i < slice.size(); i++) {
uint8_t c = *(slice.begin() + i);
switch (state_) {
case kExpectingCompressedFlag: {
if (c != CompressedFlag::kUncompressed &&
c != CompressedFlag::kCompressed) {
// TODO(fengli): The following code is repeated 12 times. Extract it
// into a function or a macro.
Status status(StatusCode::INVALID_ARGUMENT,
Format("Receives invalid compressed flag: %c.", c));
DEBUG("%s", status.error_message().c_str());
return status;
}
compressed_flag_ = c;
state_ = kExpectingMessageLengthByte0;
continue;
}
case kExpectingMessageLengthByte0: {
message_length_ = c << 24;
state_ = kExpectingMessageLengthByte1;
continue;
}
case kExpectingMessageLengthByte1: {
message_length_ += c << 16;
state_ = kExpectingMessageLengthByte2;
continue;
}
case kExpectingMessageLengthByte2: {
message_length_ += c << 8;
state_ = kExpectingMessageLengthByte3;
continue;
}
case kExpectingMessageLengthByte3: {
message_length_ += c;
if (message_length_ == 0) {
buffer_.reset(new Slice(grpc_empty_slice(), Slice::STEAL_REF));
results()->push_back(
std::unique_ptr<ByteBuffer>(new ByteBuffer(buffer_.get(), 1)));
state_ = kExpectingCompressedFlag;
} else {
buffer_.reset(new Slice(grpc_slice_malloc(message_length_),
Slice::STEAL_REF));
state_ = kExpectingMessageData;
}
continue;
}
case kExpectingMessageData: {
uint8_t* end = const_cast<uint8_t*>(buffer_->end());
*(end - message_length_) = c;
message_length_--;
if (message_length_ == 0) {
if (compressed_flag_ == CompressedFlag::kCompressed &&
compression_algorithm() == kGzip) {
grpc_slice_buffer input;
grpc_slice_buffer_init(&input);
// TODO(fengli): Remove the additional copy.
grpc_slice slice_input = grpc_slice_from_copied_buffer(
reinterpret_cast<const char*>(buffer_->begin()),
buffer_->size());
grpc_slice_buffer_add(&input, slice_input);
grpc_slice_buffer output;
grpc_slice_buffer_init(&output);
if (grpc_msg_decompress(grpc_message_compression_algorithm::
GRPC_MESSAGE_COMPRESS_GZIP,
&input, &output) != 1) {
grpc_slice_buffer_destroy(&input);
grpc_slice_buffer_destroy(&output);
return Status(StatusCode::INTERNAL,
"Failed to uncompress the GRPC data frame.");
}
std::vector<Slice> s;
while (output.count > 0) {
s.push_back(Slice(grpc_slice_buffer_take_first(&output),
Slice::STEAL_REF));
}
results()->push_back(std::unique_ptr<ByteBuffer>(
new ByteBuffer(s.data(), s.size())));
grpc_slice_buffer_destroy(&input);
grpc_slice_buffer_destroy(&output);
} else {
results()->push_back(std::unique_ptr<ByteBuffer>(
new ByteBuffer(buffer_.get(), 1)));
}
buffer_.reset();
state_ = kExpectingCompressedFlag;
}
continue;
}
}
}
}
inputs()->clear();
return Status::OK;
}
} // namespace gateway
} // namespace grpc

View File

@ -0,0 +1,87 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef NET_GRPC_GATEWAY_CODEC_GRPC_DECODER_H_
#define NET_GRPC_GATEWAY_CODEC_GRPC_DECODER_H_
#include <cstdint>
#include <memory>
#include "net/grpc/gateway/codec/decoder.h"
#include "third_party/grpc/include/grpcpp/support/slice.h"
namespace grpc {
namespace gateway {
// Decodes the GRPC requests from raw GRPC frames over HTTP2 to a series of
// protobuf messages.
class GrpcDecoder : public Decoder {
public:
enum State {
// The initial decode state, expecting the compression flag (1 byte).
kExpectingCompressedFlag,
// Expecting the 1st byte of message length (4 bytes in total).
kExpectingMessageLengthByte0,
// Expecting the 2nd byte of message length (4 bytes in total).
kExpectingMessageLengthByte1,
// Expecting the 3rd byte of message length (4 bytes in total).
kExpectingMessageLengthByte2,
// Expecting the 4th byte of message length (4 bytes in total).
kExpectingMessageLengthByte3,
// Expecting the message data.
kExpectingMessageData
};
enum CompressionAlgorithm { kIdentity = 0, kGzip = 1, kSnappy = 2 };
enum CompressedFlag { kUncompressed = 0, kCompressed = 1 };
GrpcDecoder();
~GrpcDecoder() override;
GrpcDecoder(const GrpcDecoder&) = delete;
GrpcDecoder& operator=(const GrpcDecoder&) = delete;
Status Decode() override;
// Sets the GRPC compression algorithm to be used when receiving a compressed
// data frame.
void set_compression_algorithm(CompressionAlgorithm compression_algorithm) {
compression_algorithm_ = compression_algorithm;
}
// Returns the GRPC compression algorithm to be used when receiving a
// compressed data frame.
CompressionAlgorithm compression_algorithm() {
return compression_algorithm_;
}
private:
State state_;
// The compression algorithm used to decode the GRPC frame.
CompressionAlgorithm compression_algorithm_;
// The compressed of the current decoding GRPC frame.
uint8_t compressed_flag_;
// The message length of the current decoding GRPC frame.
uint32_t message_length_;
// The data buffered for the current decoding GRPC frame.
std::unique_ptr<Slice> buffer_;
};
} // namespace gateway
} // namespace grpc
#endif // NET_GRPC_GATEWAY_CODEC_GRPC_DECODER_H_

View File

@ -0,0 +1,50 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "net/grpc/gateway/codec/grpc_encoder.h"
#include "third_party/grpc/include/grpcpp/support/slice.h"
namespace grpc {
namespace gateway {
GrpcEncoder::GrpcEncoder() {}
GrpcEncoder::~GrpcEncoder() {}
void GrpcEncoder::Encode(grpc::ByteBuffer* input, std::vector<Slice>* result) {
std::vector<Slice> input_slices;
input->Dump(&input_slices);
uint32_t message_length = input->Length();
grpc_slice message_slice = grpc_slice_malloc(message_length + 5);
uint8_t* p = GRPC_SLICE_START_PTR(message_slice);
*p++ = 0; // no compression.
*p++ = (message_length & 0xFF000000) >> 24;
*p++ = (message_length & 0x00FF0000) >> 16;
*p++ = (message_length & 0x0000FF00) >> 8;
*p++ = message_length & 0x000000FF;
for (const Slice& input_slice : input_slices) {
memcpy(p, input_slice.begin(), input_slice.size());
p += input_slice.size();
}
result->push_back(Slice(message_slice, Slice::STEAL_REF));
}
void GrpcEncoder::EncodeStatus(const grpc::Status& status,
const Trailers* trailers,
std::vector<Slice>* result) {}
} // namespace gateway
} // namespace grpc

View File

@ -0,0 +1,47 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef NET_GRPC_GATEWAY_CODEC_GRPC_ENCODER_H_
#define NET_GRPC_GATEWAY_CODEC_GRPC_ENCODER_H_
#include "net/grpc/gateway/codec/encoder.h"
namespace grpc {
namespace gateway {
// Encoder for GRPC to GRPC traffic. Encodes the response headers, response
// messages, response status and response trailers to the raw GRPC wire format
// base on HTTP2.
class GrpcEncoder : public Encoder {
public:
GrpcEncoder();
~GrpcEncoder() override;
GrpcEncoder(const GrpcEncoder&) = delete;
GrpcEncoder& operator=(const GrpcEncoder&) = delete;
// Encodes a GRPC response message to raw GRPC wire format base on HTTP2.
void Encode(grpc::ByteBuffer* input, std::vector<Slice>* result) override;
// Encodes the GRPC response status and trailers to raw GRPC wire format base
// on HTTP2.
void EncodeStatus(const grpc::Status& status, const Trailers* trailers,
std::vector<Slice>* result) override;
};
} // namespace gateway
} // namespace grpc
#endif // NET_GRPC_GATEWAY_CODEC_GRPC_ENCODER_H_

View File

@ -0,0 +1,103 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "net/grpc/gateway/codec/grpc_web_decoder.h"
#include "net/grpc/gateway/log.h"
#include "net/grpc/gateway/utils.h"
namespace grpc {
namespace gateway {
const uint8_t GrpcWebDecoder::kGrpcWebMessage = 0;
GrpcWebDecoder::GrpcWebDecoder()
: state_(kExpectingFlags), message_length_(0) {}
GrpcWebDecoder::~GrpcWebDecoder() {}
Status GrpcWebDecoder::Decode() {
for (const Slice& slice : *inputs()) {
if (slice.size() == 0) {
continue;
}
for (size_t i = 0; i < slice.size(); i++) {
uint8_t c = *(slice.begin() + i);
switch (state_) {
case kExpectingFlags: {
if (c != kGrpcWebMessage) {
// TODO(fengli): The following code is repeated 12 times. Extract it
// into a function or a macro.
Status status(StatusCode::INVALID_ARGUMENT,
Format("Receives invalid compressed flag: %X.", c));
DEBUG("%s", status.error_message().c_str());
return status;
}
state_ = kExpectingMessageLengthByte0;
continue;
}
case kExpectingMessageLengthByte0: {
message_length_ = c << 24;
state_ = kExpectingMessageLengthByte1;
continue;
}
case kExpectingMessageLengthByte1: {
message_length_ += c << 16;
state_ = kExpectingMessageLengthByte2;
continue;
}
case kExpectingMessageLengthByte2: {
message_length_ += c << 8;
state_ = kExpectingMessageLengthByte3;
continue;
}
case kExpectingMessageLengthByte3: {
message_length_ += c;
if (message_length_ == 0) {
buffer_.reset(new Slice(grpc_empty_slice(), Slice::STEAL_REF));
results()->push_back(
std::unique_ptr<ByteBuffer>(new ByteBuffer(buffer_.get(), 1)));
state_ = kExpectingFlags;
} else {
buffer_.reset(new Slice(grpc_slice_malloc(message_length_),
Slice::STEAL_REF));
state_ = kExpectingMessageData;
}
continue;
}
case kExpectingMessageData: {
uint8_t* end = const_cast<uint8_t*>(buffer_->end());
*(end - message_length_) = c;
message_length_--;
if (message_length_ == 0) {
results()->push_back(
std::unique_ptr<ByteBuffer>(new ByteBuffer(buffer_.get(), 1)));
buffer_.reset();
state_ = kExpectingFlags;
}
continue;
}
}
}
}
inputs()->clear();
return Status::OK;
}
} // namespace gateway
} // namespace grpc

View File

@ -0,0 +1,69 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef NET_GRPC_GATEWAY_CODEC_GRPC_WEB_DECODER_H_
#define NET_GRPC_GATEWAY_CODEC_GRPC_WEB_DECODER_H_
#include <cstdint>
#include <memory>
#include "net/grpc/gateway/codec/decoder.h"
#include "third_party/grpc/include/grpcpp/support/slice.h"
namespace grpc {
namespace gateway {
class GrpcWebDecoder : public Decoder {
public:
static const uint8_t kGrpcWebMessage;
enum State : uint8_t {
// The initial decode state, expecting the flags (1 byte).
kExpectingFlags,
// Expecting the 1st byte of message length (4 bytes in total).
kExpectingMessageLengthByte0,
// Expecting the 2nd byte of message length (4 bytes in total).
kExpectingMessageLengthByte1,
// Expecting the 3rd byte of message length (4 bytes in total).
kExpectingMessageLengthByte2,
// Expecting the 4th byte of message length (4 bytes in total).
kExpectingMessageLengthByte3,
// Expecting the message data.
kExpectingMessageData
};
GrpcWebDecoder();
virtual ~GrpcWebDecoder();
// GrpcWebDecoder is neither copyable nor movable.
GrpcWebDecoder(const GrpcWebDecoder&) = delete;
GrpcWebDecoder& operator=(const GrpcWebDecoder&) = delete;
Status Decode() override;
private:
State state_;
// The message length of the current decoding GRPC-Web frame.
uint32_t message_length_;
// The data buffered for the current decoding GRPC-Web frame.
std::unique_ptr<Slice> buffer_;
};
} // namespace gateway
} // namespace grpc
#endif // NET_GRPC_GATEWAY_CODEC_GRPC_WEB_DECODER_H_

View File

@ -0,0 +1,130 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "net/grpc/gateway/codec/grpc_web_encoder.h"
#include <cstdint>
#include <cstring>
#include <vector>
#include "net/grpc/gateway/runtime/types.h"
#include "third_party/grpc/include/grpcpp/support/byte_buffer.h"
#include "third_party/grpc/include/grpcpp/support/slice.h"
namespace grpc {
namespace gateway {
namespace {
const char kGrpcStatus[] = "grpc-status: %i\r\n";
const char kGrpcMessage[] = "grpc-message: %s\r\n";
// GRPC Web message frame.
const uint8_t GRPC_WEB_FH_DATA = 0b0u;
// GRPC Web trailer frame.
const uint8_t GRPC_WEB_FH_TRAILER = 0b10000000u;
// Creates a new GRPC data frame with the given flags and length.
// @param flags supplies the GRPC data frame flags.
// @param length supplies the GRPC data frame length.
// @param output the buffer to store the encoded data, it's size must be 5.
void NewFrame(uint8_t flags, uint64_t length, uint8_t* output) {
output[0] = flags;
output[1] = static_cast<uint8_t>(length >> 24);
output[2] = static_cast<uint8_t>(length >> 16);
output[3] = static_cast<uint8_t>(length >> 8);
output[4] = static_cast<uint8_t>(length);
}
} // namespace
GrpcWebEncoder::GrpcWebEncoder() {}
GrpcWebEncoder::~GrpcWebEncoder() {}
void GrpcWebEncoder::Encode(grpc::ByteBuffer* input,
std::vector<Slice>* result) {
uint8_t header[5];
NewFrame(GRPC_WEB_FH_DATA, input->Length(), header);
result->push_back(
Slice(gpr_slice_from_copied_buffer(reinterpret_cast<char*>(header), 5),
Slice::STEAL_REF));
std::vector<Slice> buffer;
// TODO(fengli): Optimize if needed. Today we cannot dump data to the result
// directly since it will clear the target.
input->Dump(&buffer);
for (Slice& s : buffer) {
result->push_back(s);
}
}
void GrpcWebEncoder::EncodeStatus(const grpc::Status& status,
const Trailers* trailers,
std::vector<Slice>* result) {
std::vector<Slice> buffer;
uint64_t length = 0;
// Encodes GRPC status.
size_t grpc_status_size =
snprintf(nullptr, 0, kGrpcStatus, status.error_code());
grpc_slice grpc_status = grpc_slice_malloc(grpc_status_size + 1);
snprintf(reinterpret_cast<char*>(GPR_SLICE_START_PTR(grpc_status)),
grpc_status_size + 1, kGrpcStatus, status.error_code());
GPR_SLICE_SET_LENGTH(grpc_status, grpc_status_size);
buffer.push_back(Slice(grpc_status, Slice::STEAL_REF));
length += grpc_status_size;
// Encodes GRPC message.
if (!status.error_message().empty()) {
size_t grpc_message_size =
snprintf(nullptr, 0, kGrpcMessage, status.error_message().c_str());
grpc_slice grpc_message = grpc_slice_malloc(grpc_message_size + 1);
snprintf(reinterpret_cast<char*>(GPR_SLICE_START_PTR(grpc_message)),
grpc_message_size + 1, kGrpcMessage,
status.error_message().c_str());
GPR_SLICE_SET_LENGTH(grpc_message, grpc_message_size);
buffer.push_back(Slice(grpc_message, Slice::STEAL_REF));
length += grpc_message_size;
}
// Encodes GRPC trailers.
if (trailers != nullptr) {
for (auto& trailer : *trailers) {
size_t grpc_trailer_size =
trailer.first.size() + trailer.second.size() + 4;
grpc_slice grpc_trailer = grpc_slice_malloc(grpc_trailer_size);
uint8_t* p = GPR_SLICE_START_PTR(grpc_trailer);
memcpy(p, trailer.first.c_str(), trailer.first.size());
p += trailer.first.size();
memcpy(p, ": ", 2);
p += 2;
memcpy(p, trailer.second.data(), trailer.second.size());
p += trailer.second.size();
memcpy(p, "\r\n", 2);
buffer.push_back(Slice(grpc_trailer, Slice::STEAL_REF));
length += grpc_trailer_size;
}
}
// Encodes GRPC trailer frame.
grpc_slice header = grpc_slice_malloc(5);
NewFrame(GRPC_WEB_FH_TRAILER, length, GPR_SLICE_START_PTR(header));
result->push_back(Slice(header, Slice::STEAL_REF));
result->insert(result->end(), buffer.begin(), buffer.end());
}
} // namespace gateway
} // namespace grpc

View File

@ -0,0 +1,44 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef NET_GRPC_GATEWAY_CODEC_GRPC_WEB_ENCODER_H_
#define NET_GRPC_GATEWAY_CODEC_GRPC_WEB_ENCODER_H_
#include "net/grpc/gateway/codec/encoder.h"
namespace grpc {
namespace gateway {
class GrpcWebEncoder : public Encoder {
public:
GrpcWebEncoder();
virtual ~GrpcWebEncoder();
// GrpcWebEncoder is neither copyable nor movable.
GrpcWebEncoder(const GrpcWebEncoder&) = delete;
GrpcWebEncoder& operator=(const GrpcWebEncoder&) = delete;
void Encode(grpc::ByteBuffer* input, std::vector<Slice>* result) override;
void EncodeStatus(const grpc::Status& status, const Trailers* trailers,
std::vector<Slice>* result) override;
};
} // namespace gateway
} // namespace grpc
#endif // NET_GRPC_GATEWAY_CODEC_GRPC_WEB_ENCODER_H_

View File

@ -0,0 +1,43 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "net/grpc/gateway/codec/grpc_web_text_decoder.h"
namespace grpc {
namespace gateway {
GrpcWebTextDecoder::GrpcWebTextDecoder() {}
GrpcWebTextDecoder::~GrpcWebTextDecoder() {}
Status GrpcWebTextDecoder::Decode() {
std::vector<Slice> buffer;
if (!base64_.Decode(*inputs(), &buffer)) {
return Status(StatusCode::INVALID_ARGUMENT, "Invalid base64 inputs.");
}
inputs()->clear();
for (Slice& s : buffer) {
Append(s);
}
return GrpcWebDecoder::Decode();
}
} // namespace gateway
} // namespace grpc

View File

@ -0,0 +1,44 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef NET_GRPC_GATEWAY_CODEC_GRPC_WEB_TEXT_DECODER_H_
#define NET_GRPC_GATEWAY_CODEC_GRPC_WEB_TEXT_DECODER_H_
#include "net/grpc/gateway/codec/base64.h"
#include "net/grpc/gateway/codec/grpc_web_decoder.h"
namespace grpc {
namespace gateway {
class GrpcWebTextDecoder : public GrpcWebDecoder {
public:
GrpcWebTextDecoder();
~GrpcWebTextDecoder() override;
GrpcWebTextDecoder(const GrpcWebTextDecoder&) = delete;
GrpcWebTextDecoder& operator=(const GrpcWebTextDecoder&) = delete;
Status Decode() override;
private:
Base64 base64_;
};
} // namespace gateway
} // namespace grpc
#endif // NET_GRPC_GATEWAY_CODEC_GRPC_WEB_TEXT_DECODER_H_

View File

@ -0,0 +1,44 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "net/grpc/gateway/codec/grpc_web_text_encoder.h"
namespace grpc {
namespace gateway {
GrpcWebTextEncoder::GrpcWebTextEncoder() {}
GrpcWebTextEncoder::~GrpcWebTextEncoder() {}
void GrpcWebTextEncoder::Encode(grpc::ByteBuffer* input,
std::vector<Slice>* result) {
std::vector<Slice> buffer;
GrpcWebEncoder::Encode(input, &buffer);
base64_.Encode(buffer, result);
}
void GrpcWebTextEncoder::EncodeStatus(const grpc::Status& status,
const Trailers* trailers,
std::vector<Slice>* result) {
std::vector<Slice> buffer;
GrpcWebEncoder::EncodeStatus(status, trailers, &buffer);
base64_.Encode(buffer, result);
}
} // namespace gateway
} // namespace grpc

View File

@ -0,0 +1,46 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef NET_GRPC_GATEWAY_CODEC_GRPC_WEB_TEXT_ENCODER_H_
#define NET_GRPC_GATEWAY_CODEC_GRPC_WEB_TEXT_ENCODER_H_
#include "net/grpc/gateway/codec/base64.h"
#include "net/grpc/gateway/codec/grpc_web_encoder.h"
namespace grpc {
namespace gateway {
class GrpcWebTextEncoder : public GrpcWebEncoder {
public:
GrpcWebTextEncoder();
~GrpcWebTextEncoder() override;
GrpcWebTextEncoder(const GrpcWebTextEncoder&) = delete;
GrpcWebTextEncoder& operator=(const GrpcWebTextEncoder&) = delete;
void Encode(grpc::ByteBuffer* input, std::vector<Slice>* result) override;
void EncodeStatus(const grpc::Status& status, const Trailers* trailers,
std::vector<Slice>* result) override;
private:
Base64 base64_;
};
} // namespace gateway
} // namespace grpc
#endif // NET_GRPC_GATEWAY_CODEC_GRPC_WEB_TEXT_ENCODER_H_

View File

@ -0,0 +1,258 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "net/grpc/gateway/codec/json_decoder.h"
#include <cctype>
#include <cstdint>
#include "net/grpc/gateway/codec/base64.h"
#include "net/grpc/gateway/log.h"
#include "net/grpc/gateway/utils.h"
namespace grpc {
namespace gateway {
const char JSON_ARRAY_LEFT_BRACKET = '[';
const char JSON_ARRAY_RIGHT_BRACKET = ']';
const char JSON_OBJECT_LEFT_BRACKET = '{';
const char JSON_OBJECT_RIGHT_BRACKET = '}';
const char JSON_OBJECT_DELIMITER = ',';
const char JSON_DOUBLE_QUOTE = '"';
const char JSON_MESSAGE_TAG = '1';
const char JSON_TAG_VALUE_DELIMITER = ':';
JsonDecoder::JsonDecoder() : state_(EXPECTING_JSON_ARRAY_LEFT_BRACKET) {}
JsonDecoder::~JsonDecoder() {}
Status JsonDecoder::Decode() {
for (Slice& slice : *inputs()) {
int start = -1;
for (size_t i = 0; i < slice.size(); i++) {
char c = *(slice.begin() + i);
if (isblank(c)) {
// Ignore blank characters.
continue;
}
switch (state_) {
case EXPECTING_JSON_ARRAY_LEFT_BRACKET: {
if (c != JSON_ARRAY_LEFT_BRACKET) {
// TODO(fengli): The following code is repeated 12 times. Extract it
// into a function or a macro.
Status status(
StatusCode::INVALID_ARGUMENT,
Format("Receives invalid character: %c when expecting "
"left bracket of the JSON array.",
c));
DEBUG("%s", status.error_message().c_str());
return status;
}
state_ = EXPECTING_JSON_OBJECT_LEFT_BRACKET;
continue;
}
case EXPECTING_JSON_OBJECT_LEFT_BRACKET: {
if (c != JSON_OBJECT_LEFT_BRACKET) {
// TODO(fengli): The following code is repeated 12 times. Extract it
// into a function or a macro.
Status status(
StatusCode::INVALID_ARGUMENT,
Format("Receives invalid character: %c when expecting "
"left bracket of the JSON object.",
c));
DEBUG("%s", status.error_message().c_str());
return status;
}
state_ = EXPECTING_JSON_MESSAGE_TAG_LEFT_QUOTE;
continue;
}
case EXPECTING_JSON_MESSAGE_TAG_LEFT_QUOTE: {
if (c != JSON_DOUBLE_QUOTE) {
// TODO(fengli): The following code is repeated 12 times. Extract it
// into a function or a macro.
Status status(
StatusCode::INVALID_ARGUMENT,
Format("Receives invalid character: %c when expecting "
"left double quote of the JSON message tag.",
c));
DEBUG("%s", status.error_message().c_str());
return status;
}
state_ = EXPECTING_JSON_MESSAGE_TAG;
continue;
}
case EXPECTING_JSON_MESSAGE_TAG: {
if (c != JSON_MESSAGE_TAG) {
// TODO(fengli): The following code is repeated 12 times. Extract it
// into a function or a macro.
Status status(StatusCode::INVALID_ARGUMENT,
Format("Receives invalid character: %c when "
"expecting the message tag.",
c));
DEBUG("%s", status.error_message().c_str());
return status;
}
state_ = EXPECTING_JSON_MESSAGE_TAG_RIGHT_QUOTE;
continue;
}
case EXPECTING_JSON_MESSAGE_TAG_RIGHT_QUOTE: {
if (c != JSON_DOUBLE_QUOTE) {
// TODO(fengli): The following code is repeated 12 times. Extract it
// into a function or a macro.
Status status(
StatusCode::INVALID_ARGUMENT,
Format("Receives invalid character: %c when expecting "
"right double quote of the JSON message tag.",
c));
DEBUG("%s", status.error_message().c_str());
return status;
}
state_ = EXPECTING_JSON_MESSAGE_TAG_VALUE_DELIMITER;
continue;
}
case EXPECTING_JSON_MESSAGE_TAG_VALUE_DELIMITER: {
if (c != JSON_TAG_VALUE_DELIMITER) {
// TODO(fengli): The following code is repeated 12 times. Extract it
// into a function or a macro.
Status status(
StatusCode::INVALID_ARGUMENT,
Format("Receives invalid character: %c when expecting "
"colon after the JSON message tag.",
c));
DEBUG("%s", status.error_message().c_str());
return status;
}
state_ = EXPECTING_JSON_MESSAGE_VALUE_LEFT_QUOTE;
continue;
}
case EXPECTING_JSON_MESSAGE_VALUE_LEFT_QUOTE: {
if (c != JSON_DOUBLE_QUOTE) {
// TODO(fengli): The following code is repeated 12 times. Extract it
// into a function or a macro.
Status status(
StatusCode::INVALID_ARGUMENT,
Format("Receives invalid character: %c when expecting "
"double quote of the JSON message value.",
c));
DEBUG("%s", status.error_message().c_str());
return status;
}
start = i + 1;
state_ = EXPECTING_JSON_MESSAGE_VALUE_OR_RIGHT_QUOTE;
continue;
}
case EXPECTING_JSON_MESSAGE_VALUE_OR_RIGHT_QUOTE: {
if (c == JSON_DOUBLE_QUOTE) {
if (start == -1) {
start = 0;
}
if (static_cast<size_t>(start) == i) {
base64_buffer_.push_back(
Slice(grpc_empty_slice(), Slice::STEAL_REF));
} else {
base64_buffer_.push_back(Slice(
grpc_slice_from_copied_buffer(
reinterpret_cast<const char*>(slice.begin() + start),
i - start),
Slice::STEAL_REF));
}
std::vector<Slice> decoded;
base64_.Decode(base64_buffer_, &decoded);
results()->push_back(std::unique_ptr<ByteBuffer>(
new ByteBuffer(&decoded[0], decoded.size())));
base64_buffer_.clear();
start = -1;
state_ = EXPECTING_JSON_OBJECT_RIGHT_BRACKET;
continue;
}
if (!Base64::IsBase64Char(c)) {
// TODO(fengli): The following code is repeated 12 times. Extract it
// into a function or a macro.
Status status(
StatusCode::INVALID_ARGUMENT,
Format("Receives invalid character: %c when expecting "
"base64 characters for the JSON message value.",
c));
DEBUG("%s", status.error_message().c_str());
return status;
}
if (start == -1) {
start = i;
}
continue;
}
case EXPECTING_JSON_OBJECT_RIGHT_BRACKET: {
if (c != JSON_OBJECT_RIGHT_BRACKET) {
// TODO(fengli): The following code is repeated 12 times. Extract it
// into a function or a macro.
Status status(
StatusCode::INVALID_ARGUMENT,
Format("Receives invalid character: %c when expecting "
"right bracket of the JSON object.",
c));
DEBUG("%s", status.error_message().c_str());
return status;
}
state_ = EXPECTING_JSON_OBJECT_DELIMITER_OR_JSON_ARRAY_RIGHT_BRACKET;
continue;
}
case EXPECTING_JSON_OBJECT_DELIMITER_OR_JSON_ARRAY_RIGHT_BRACKET: {
if (c == JSON_OBJECT_DELIMITER) {
state_ = EXPECTING_JSON_OBJECT_LEFT_BRACKET;
continue;
}
if (c == JSON_ARRAY_RIGHT_BRACKET) {
state_ = FINISH;
continue;
}
// TODO(fengli): The following code is repeated 12 times. Extract it
// into a function or a macro.
Status status(StatusCode::INVALID_ARGUMENT,
Format("Receives invalid character: %c when expecting "
"right bracket of the JSON array.",
c));
DEBUG("%s", status.error_message().c_str());
return status;
}
case FINISH: {
// TODO(fengli): The following code is repeated 12 times. Extract it
// into a function or a macro.
Status status(StatusCode::INVALID_ARGUMENT,
Format("Receives invalid character: %c when "
"the JSON array already completed.",
c));
DEBUG("%s", status.error_message().c_str());
return status;
}
}
}
if (start >= 0) {
base64_buffer_.push_back(
Slice(grpc_slice_from_copied_buffer(
reinterpret_cast<const char*>(slice.begin() + start),
slice.size() - start),
Slice::STEAL_REF));
}
}
inputs()->clear();
return Status::OK;
}
} // namespace gateway
} // namespace grpc

View File

@ -0,0 +1,64 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef NET_GRPC_GATEWAY_CODEC_JSON_DECODER_H_
#define NET_GRPC_GATEWAY_CODEC_JSON_DECODER_H_
#include <memory>
#include <vector>
#include "net/grpc/gateway/codec/base64.h"
#include "net/grpc/gateway/codec/decoder.h"
#include "third_party/grpc/include/grpcpp/support/byte_buffer.h"
#include "third_party/grpc/include/grpcpp/support/slice.h"
namespace grpc {
namespace gateway {
class JsonDecoder : public Decoder {
public:
enum State {
EXPECTING_JSON_ARRAY_LEFT_BRACKET,
EXPECTING_JSON_OBJECT_LEFT_BRACKET,
EXPECTING_JSON_OBJECT_RIGHT_BRACKET,
EXPECTING_JSON_OBJECT_DELIMITER_OR_JSON_ARRAY_RIGHT_BRACKET,
EXPECTING_JSON_MESSAGE_TAG_LEFT_QUOTE,
EXPECTING_JSON_MESSAGE_TAG,
EXPECTING_JSON_MESSAGE_TAG_RIGHT_QUOTE,
EXPECTING_JSON_MESSAGE_TAG_VALUE_DELIMITER,
EXPECTING_JSON_MESSAGE_VALUE_LEFT_QUOTE,
EXPECTING_JSON_MESSAGE_VALUE_OR_RIGHT_QUOTE,
FINISH
};
JsonDecoder();
~JsonDecoder() override;
JsonDecoder(const JsonDecoder&) = delete;
JsonDecoder& operator=(const JsonDecoder&) = delete;
Status Decode() override;
private:
State state_;
std::vector<Slice> base64_buffer_;
Base64 base64_;
};
} // namespace gateway
} // namespace grpc
#endif // NET_GRPC_GATEWAY_CODEC_JSON_DECODER_H_

View File

@ -0,0 +1,98 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#include "net/grpc/gateway/codec/json_encoder.h"
#include <string>
#include "google/protobuf/any.pb.h"
#include "net/grpc/gateway/protos/pair.pb.h"
#include "net/grpc/gateway/protos/stream_body.pb.h"
#include "net/grpc/gateway/runtime/constants.h"
#include "third_party/grpc/include/grpcpp/support/config.h"
#include "third_party/grpc/include/grpc/slice.h"
namespace grpc {
namespace gateway {
const char kJsonArrayFirstMessagePrefix[] = "[{\"1\":\"";
const char kJsonArrayMessagePrefix[] = ",{\"1\":\"";
const char kDoubleQuote[] = "\"}";
const char kJsonArrayStatusPrefix[] = ",{\"2\":\"";
const char kJsonArrayStatusOnlyPrefix[] = "[{\"2\":\"";
const char kJsonArrayStatusSurfix[] = "\"}]";
void do_nothing() {}
JsonEncoder::JsonEncoder() : is_first_message_(true) {}
JsonEncoder::~JsonEncoder() {}
void JsonEncoder::Encode(grpc::ByteBuffer* input, std::vector<Slice>* result) {
std::vector<Slice> input_slices;
input->Dump(&input_slices);
if (is_first_message_) {
is_first_message_ = false;
grpc_slice s = grpc_slice_from_static_string(kJsonArrayFirstMessagePrefix);
result->push_back(Slice(s, Slice::STEAL_REF));
} else {
grpc_slice s = grpc_slice_from_static_string(kJsonArrayMessagePrefix);
result->push_back(Slice(s, Slice::STEAL_REF));
}
base64_.Encode(input_slices, result);
grpc_slice s = grpc_slice_from_static_string(kDoubleQuote);
result->push_back(Slice(s, Slice::STEAL_REF));
}
void JsonEncoder::EncodeStatus(const grpc::Status& status,
const Trailers* trailers,
std::vector<Slice>* result) {
if (is_first_message_) {
grpc_slice prefix =
grpc_slice_from_static_string(kJsonArrayStatusOnlyPrefix);
result->push_back(Slice(prefix, Slice::STEAL_REF));
} else {
grpc_slice prefix = grpc_slice_from_static_string(kJsonArrayStatusPrefix);
result->push_back(Slice(prefix, Slice::STEAL_REF));
}
std::vector<Slice> input_slices;
google::rpc::Status status_proto;
status_proto.set_code(status.error_code());
status_proto.set_message(status.error_message());
if (trailers != nullptr) {
for (auto& trailer : *trailers) {
::google::protobuf::Any* any = status_proto.add_details();
any->set_type_url(kTypeUrlPair);
Pair pair;
pair.set_first(trailer.first);
pair.set_second(trailer.second.data(), trailer.second.length());
// TODO(fengli): Change to open source protobuf.
pair.SerializeToString(any->mutable_value());
}
}
std::string serialized_status_proto;
status_proto.SerializeToString(&serialized_status_proto);
grpc_slice status_slice =
grpc_slice_from_copied_string(serialized_status_proto.c_str());
input_slices.push_back(Slice(status_slice, Slice::STEAL_REF));
base64_.Encode(input_slices, result);
grpc_slice surfix = grpc_slice_from_static_string(kJsonArrayStatusSurfix);
result->push_back(Slice(surfix, Slice::STEAL_REF));
}
} // namespace gateway
} // namespace grpc

View File

@ -0,0 +1,50 @@
/**
*
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
#ifndef NET_GRPC_GATEWAY_CODEC_JSON_ENCODER_H_
#define NET_GRPC_GATEWAY_CODEC_JSON_ENCODER_H_
#include <vector>
#include "net/grpc/gateway/codec/base64.h"
#include "net/grpc/gateway/codec/encoder.h"
#include "third_party/grpc/include/grpcpp/support/byte_buffer.h"
#include "third_party/grpc/include/grpcpp/support/slice.h"
namespace grpc {
namespace gateway {
class JsonEncoder : public Encoder {
public:
JsonEncoder();
~JsonEncoder() override;
JsonEncoder(const JsonEncoder&) = delete;
JsonEncoder& operator=(const JsonEncoder&) = delete;
void Encode(grpc::ByteBuffer* input, std::vector<Slice>* result) override;
void EncodeStatus(const grpc::Status& status, const Trailers* trailers,
std::vector<Slice>* result) override;
private:
bool is_first_message_;
Base64 base64_;
};
} // namespace gateway
} // namespace grpc
#endif // NET_GRPC_GATEWAY_CODEC_JSON_ENCODER_H_

Some files were not shown because too many files have changed in this diff Show More