mirror of https://github.com/grpc/grpc-web.git
Compare commits
No commits in common. "master" and "1.0.1" have entirely different histories.
|
@ -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:
|
|
||||||
- //...
|
|
|
@ -1,2 +0,0 @@
|
||||||
# //third_party conatins git submodules.
|
|
||||||
third_party/
|
|
14
.bazelrc
14
.bazelrc
|
@ -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
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
**/dist
|
|
||||||
**/node_modules
|
|
||||||
packages/grpc-web/generated
|
|
|
@ -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/
|
|
|
@ -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*
|
|
|
@ -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*
|
|
|
@ -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*
|
|
|
@ -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 }}
|
|
|
@ -1,4 +1,3 @@
|
||||||
.vscode
|
|
||||||
bazel-bin
|
bazel-bin
|
||||||
bazel-genfiles
|
bazel-genfiles
|
||||||
bazel-grpc-web
|
bazel-grpc-web
|
||||||
|
@ -6,10 +5,3 @@ bazel-out
|
||||||
bazel-testlogs
|
bazel-testlogs
|
||||||
*.o
|
*.o
|
||||||
protoc-gen-*
|
protoc-gen-*
|
||||||
.DS_Store
|
|
||||||
target
|
|
||||||
.project
|
|
||||||
.classpath
|
|
||||||
.settings
|
|
||||||
zig-out
|
|
||||||
zig-cache
|
|
||||||
|
|
|
@ -1,3 +1,12 @@
|
||||||
[submodule "third_party/protobuf"]
|
[submodule "third_party/grpc"]
|
||||||
path = third_party/protobuf
|
path = third_party/grpc
|
||||||
url = https://github.com/protocolbuffers/protobuf.git
|
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
|
||||||
|
|
|
@ -1,23 +1,20 @@
|
||||||
# gRPC-Web features for browser (HTML) clients
|
# 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).
|
than the [HTTP/2 based gRPC protocol](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md).
|
||||||
The difference between the gRPC-Web
|
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).
|
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.
|
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.
|
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
|
# 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-Credentials to allow Authorization headers
|
||||||
* Access-Control-Allow-Methods to allow POST and (preflight) OPTIONS only
|
* Access-Control-Allow-Methods to allow POST and (preflight) OPTIONS only
|
||||||
* Access-Control-Allow-Headers to whatever the preflight request carries
|
* 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
|
* The client library is expected to support header overwrites to avoid preflight
|
||||||
* https://github.com/whatwg/fetch/issues/210
|
* https://github.com/whatwg/fetch/issues/210
|
||||||
* CSP support to be specified
|
* 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.
|
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
|
# Security
|
||||||
|
|
||||||
* XSRF, XSS policy to be published
|
* XSRF, XSS policy to be published
|
266
CHANGELOG.md
266
CHANGELOG.md
|
@ -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)
|
|
|
@ -3,10 +3,6 @@
|
||||||
We definitely welcome patches and contribution to gRPC-Web! Here is some guideline
|
We definitely welcome patches and contribution to gRPC-Web! Here is some guideline
|
||||||
and information about how to do so.
|
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
|
## Getting started
|
||||||
|
|
||||||
### Legal requirements
|
### Legal requirements
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
This repository is governed by the gRPC organization's [governance rules](https://github.com/grpc/grpc-community/blob/master/governance.md).
|
|
|
@ -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)
|
|
24
MODULE.bazel
24
MODULE.bazel
|
@ -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",
|
|
||||||
)
|
|
8886
MODULE.bazel.lock
8886
MODULE.bazel.lock
File diff suppressed because it is too large
Load Diff
114
Makefile
114
Makefile
|
@ -1,13 +1,119 @@
|
||||||
|
OS := $(shell uname)
|
||||||
|
CC := g++
|
||||||
ROOT_DIR := $(shell pwd)
|
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:
|
plugin:
|
||||||
cd "$(ROOT_DIR)"/javascript/net/grpc/web/generator && make
|
cd "$(ROOT_DIR)"/javascript/net/grpc/web && make
|
||||||
|
|
||||||
install-plugin:
|
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:
|
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)"
|
cd "$(ROOT_DIR)"
|
||||||
|
|
270
README.md
270
README.md
|
@ -1,27 +1,26 @@
|
||||||
# gRPC Web · [](https://www.npmjs.com/package/grpc-web)
|
## Overview
|
||||||
|
|
||||||
A JavaScript implementation of [gRPC][] for browser clients. For more information,
|
gRPC-Web provides a Javascript library that lets browser clients access a gRPC
|
||||||
including a **quick start**, see the [gRPC-web documentation][grpc-web-docs].
|
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 is now Generally Available, and considered stable enough for production
|
||||||
gRPC-web uses [Envoy][].
|
use.
|
||||||
|
|
||||||
In the future, we expect gRPC-web to be supported in language-specific web
|
gRPC-Web clients connect to gRPC services via a special gateway proxy: the
|
||||||
frameworks for languages such as Python, Java, and Node. For details, see the
|
current version of the library uses [Envoy](https://www.envoyproxy.io/) by
|
||||||
[roadmap](doc/roadmap.md).
|
default, in which gRPC-Web support is built-in.
|
||||||
|
|
||||||
## Streaming Support
|
In the future, we expect gRPC-Web to be supported in language-specific Web
|
||||||
gRPC-web currently supports 2 RPC modes:
|
frameworks, such as Python, Java, and Node. See the
|
||||||
- Unary RPCs ([example](#make-a-unary-rpc-call))
|
[roadmap](https://github.com/grpc/grpc-web/blob/master/ROADMAP.md) doc.
|
||||||
- Server-side Streaming RPCs ([example](#server-side-streaming)) (NOTE: Only when [`grpcwebtext`](#wire-format-mode) mode is used.)
|
|
||||||
|
|
||||||
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
|
You can follow the [Hello World Guide][] to get started with gRPC-Web quickly.
|
||||||
learn how to do the following:
|
|
||||||
|
|
||||||
|
From the guide, you will learn how to
|
||||||
- Define your service using protocol buffers
|
- Define your service using protocol buffers
|
||||||
- Implement a simple gRPC Service using NodeJS
|
- Implement a simple gRPC Service using NodeJS
|
||||||
- Configure the Envoy proxy
|
- Configure the Envoy proxy
|
||||||
|
@ -37,84 +36,43 @@ streaming example.
|
||||||
From the repo root directory:
|
From the repo root directory:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ docker-compose pull prereqs node-server envoy commonjs-client
|
$ docker-compose pull prereqs common node-server envoy commonjs-client
|
||||||
$ docker-compose up 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`.
|
To shutdown: `docker-compose down`.
|
||||||
|
|
||||||
## Runtime Library
|
## Runtime Library
|
||||||
|
|
||||||
The gRPC-web runtime library is available at `npm`:
|
The gRPC-Web runtime library is available at `npm`:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
$ npm i grpc-web
|
$ 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)
|
You can compile the `protoc-gen-grpc-web` protoc plugin from this repo:
|
||||||
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:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
brew install protobuf
|
$ sudo make install-plugin
|
||||||
```
|
```
|
||||||
|
|
||||||
### (Prerequisite) 2. Protobuf-javascript (`protoc-gen-js`)
|
If you don't already have `protoc` installed, you may have to do this first:
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo mv protoc-gen-grpc-web-1.5.0-darwin-aarch64 \
|
$ ./scripts/init_submodules.sh
|
||||||
/usr/local/bin/protoc-gen-grpc-web
|
$ cd third_party/grpc/third_party/protobuf
|
||||||
|
$ ./autogen.sh && ./configure && make -j8 && sudo make install
|
||||||
chmod +x /usr/local/bin/protoc-gen-grpc-web
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### (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
|
## Client Configuration Options
|
||||||
|
|
||||||
|
@ -122,14 +80,15 @@ Typically, you will run the following command to generate the proto messages
|
||||||
and the service client stub from your `.proto` definitions:
|
and the service client stub from your `.proto` definitions:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
protoc -I=$DIR echo.proto \
|
$ protoc -I=$DIR echo.proto \
|
||||||
--js_out=import_style=commonjs:$OUT_DIR \
|
--js_out=import_style=commonjs:$OUT_DIR \
|
||||||
--grpc-web_out=import_style=commonjs,mode=grpcwebtext:$OUT_DIR
|
--grpc-web_out=import_style=commonjs,mode=grpcwebtext:$OUT_DIR
|
||||||
```
|
```
|
||||||
|
|
||||||
You can then use Browserify, Webpack, Closure Compiler, etc. to resolve imports
|
You can then use Browserify, Webpack, Closure Compiler, etc. to resolve imports
|
||||||
at compile time.
|
at compile time.
|
||||||
|
|
||||||
|
|
||||||
### Import Style
|
### Import Style
|
||||||
|
|
||||||
`import_style=closure`: The default generated code has
|
`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.
|
typings file will also be generated for the protobuf messages and service stub.
|
||||||
|
|
||||||
`import_style=typescript`: (Experimental) The service stub will be generated
|
`import_style=typescript`: (Experimental) The service stub will be generated
|
||||||
in TypeScript. See **TypeScript Support** below for information on how to
|
in TypeScript.
|
||||||
generate TypeScript files.
|
|
||||||
|
|
||||||
> **Note:** The `commonjs+dts` and `typescript` styles are only supported by
|
**Note: `commonjs+dts` and `typescript` only works with `--grpc-web_out=` import style.**
|
||||||
`--grpc-web_out=import_style=...`, not by `--js_out=import_style=...`.
|
|
||||||
|
|
||||||
### Wire Format Mode
|
### Wire Format Mode
|
||||||
|
|
||||||
For more information about the gRPC-web wire format, see the
|
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).
|
[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
|
`mode=grpcwebtext`: The default generated code sends the payload in the
|
||||||
`grpc-web-text` format.
|
`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`
|
- `Content-type: application/grpc-web+proto`
|
||||||
- Payload are in the binary protobuf format.
|
- Payload are in the binary protobuf format.
|
||||||
- Only unary calls are supported.
|
- Only unary calls are supported for now.
|
||||||
|
|
||||||
|
|
||||||
## How It Works
|
## 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
|
how to build, run and explore the example yourself in
|
||||||
[Build and Run the Echo Example](net/grpc/gateway/examples/echo).
|
[Build and Run the Echo Example](net/grpc/gateway/examples/echo).
|
||||||
|
|
||||||
|
|
||||||
### 1. Define your service
|
### 1. Define your service
|
||||||
|
|
||||||
The first step when creating any gRPC service is to define it. Like all gRPC
|
The first step when creating any gRPC service is to define it. Like all gRPC
|
||||||
services, gRPC-web uses
|
services, gRPC-Web uses
|
||||||
[protocol buffers](https://developers.google.com/protocol-buffers) to define
|
[protocol buffers](https://developers.google.com/protocol-buffers/) to define
|
||||||
its RPC service methods and their message request and response types.
|
its RPC service methods and their message request and response types.
|
||||||
|
|
||||||
```protobuf
|
```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.
|
builds a simple Node gRPC backend server and the Envoy proxy.
|
||||||
|
|
||||||
For the Echo service: see the
|
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
|
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
|
### 3. Write your JS client
|
||||||
|
|
||||||
Once the server and gateway are up and running, you can start making gRPC calls
|
Once the server and gateway are up and running, you can start making gRPC calls
|
||||||
from the browser!
|
from the browser!
|
||||||
|
|
||||||
Create your client:
|
Create your client
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var echoService = new proto.mypackage.EchoServiceClient(
|
var echoService = new proto.mypackage.EchoServiceClient(
|
||||||
'http://localhost:8080');
|
'http://localhost:8080');
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Make a unary RPC call:
|
Make a unary RPC call
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var request = new proto.mypackage.EchoRequest();
|
var request = new proto.mypackage.EchoRequest();
|
||||||
request.setMessage(msg);
|
request.setMessage(msg);
|
||||||
var metadata = {'custom-header-1': 'value1'};
|
var metadata = {'custom-header-1': 'value1'};
|
||||||
echoService.echo(request, metadata, function(err, response) {
|
var call = echoService.echo(request, metadata, function(err, response) {
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log(err.code);
|
console.log(err.code);
|
||||||
console.log(err.message);
|
console.log(err.message);
|
||||||
|
@ -234,9 +195,14 @@ echoService.echo(request, metadata, function(err, response) {
|
||||||
console.log(response.getMessage());
|
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
|
```js
|
||||||
var stream = echoService.serverStreamingEcho(streamRequest, metadata);
|
var stream = echoService.serverStreamingEcho(streamRequest, metadata);
|
||||||
|
@ -251,13 +217,10 @@ stream.on('status', function(status) {
|
||||||
stream.on('end', function(end) {
|
stream.on('end', function(end) {
|
||||||
// stream end signal
|
// stream end signal
|
||||||
});
|
});
|
||||||
|
|
||||||
// to close the stream
|
|
||||||
stream.cancel()
|
|
||||||
```
|
```
|
||||||
|
|
||||||
For an in-depth tutorial, see [this
|
You can find a more in-depth tutorial from
|
||||||
page](net/grpc/gateway/examples/echo/tutorial.md).
|
[this page](https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/examples/echo/tutorial.md).
|
||||||
|
|
||||||
## Setting Deadline
|
## Setting Deadline
|
||||||
|
|
||||||
|
@ -268,7 +231,7 @@ should be a Unix timestamp, in milliseconds.
|
||||||
var deadline = new Date();
|
var deadline = new Date();
|
||||||
deadline.setSeconds(deadline.getSeconds() + 1);
|
deadline.setSeconds(deadline.getSeconds() + 1);
|
||||||
|
|
||||||
client.sayHelloAfterDelay(request, {deadline: deadline.getTime().toString()},
|
client.sayHelloAfterDelay(request, {deadline: deadline.getTime()},
|
||||||
(err, response) => {
|
(err, response) => {
|
||||||
// err will be populated if the RPC exceeds the deadline
|
// 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=commonjs+dts`: existing CommonJS style stub + `.d.ts` typings
|
||||||
- `import_style=typescript`: full TypeScript output
|
- `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
|
```ts
|
||||||
import * as grpcWeb from 'grpc-web';
|
import * as grpcWeb from 'grpc-web';
|
||||||
import {EchoServiceClient} from './EchoServiceClientPb';
|
import {EchoServiceClient} from './echo_grpc_web_pb';
|
||||||
import {EchoRequest, EchoResponse} from './echo_pb';
|
import {EchoRequest, EchoResponse} from './echo_pb';
|
||||||
|
|
||||||
const echoService = new EchoServiceClient('http://localhost:8080', null, null);
|
const echoService = new EchoServiceClient('http://localhost:8080', null, null);
|
||||||
|
@ -325,7 +260,7 @@ const request = new EchoRequest();
|
||||||
request.setMessage('Hello World!');
|
request.setMessage('Hello World!');
|
||||||
|
|
||||||
const call = echoService.echo(request, {'custom-header-1': 'value1'},
|
const call = echoService.echo(request, {'custom-header-1': 'value1'},
|
||||||
(err: grpcWeb.RpcError, response: EchoResponse) => {
|
(err: grpcWeb.Error, response: EchoResponse) => {
|
||||||
console.log(response.getMessage());
|
console.log(response.getMessage());
|
||||||
});
|
});
|
||||||
call.on('status', (status: grpcWeb.Status) => {
|
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.
|
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.
|
||||||
|
|
||||||
```ts
|
```sh
|
||||||
// Create a Promise client instead
|
$ docker-compose up -d node-server envoy commonjs-client
|
||||||
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
|
An alternative is to build Nginx that comes with this repository.
|
||||||
[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
|
```sh
|
||||||
|
$ docker-compose up -d node-server nginx commonjs-client
|
||||||
|
```
|
||||||
|
|
||||||
Custom interceptors can be implemented and chained, which could be useful for features like auth, retries, etc.
|
You can also try this
|
||||||
|
[gRPC-Web Go Proxy](https://github.com/improbable-eng/grpc-web/tree/master/go/grpcwebproxy).
|
||||||
|
|
||||||
There are 2 types of interceptors ([interfaces](https://github.com/grpc/grpc-web/blob/3cd7e0d43493d4694fed78400e4ad78031d70c09/packages/grpc-web/index.d.ts#L55-L65)):
|
```sh
|
||||||
|
$ docker-compose up -d node-server grpcwebproxy binary-client
|
||||||
|
```
|
||||||
|
|
||||||
- `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.
|
## Acknowledgement
|
||||||
- `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/).
|
Big thanks to the following contributors for making significant contributions to
|
||||||
|
this project!
|
||||||
|
|
||||||
|
* [zaucy](https://github.com/zaucy): NPM package, CommonJS
|
||||||
|
* [yannic](https://github.com/yannic): Bazel
|
||||||
|
* [mitar](https://github.com/mitar): Codegen Plugin
|
||||||
|
|
||||||
|
|
||||||
## Ecosystem
|
[Hello World Guide]:https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/examples/helloworld/
|
||||||
|
|
||||||
### Proxy Interoperability
|
|
||||||
|
|
||||||
Multiple proxies support the gRPC-web protocol.
|
|
||||||
|
|
||||||
1. The current **default proxy** is [Envoy][], 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][].
|
|
||||||
|
|
||||||
```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/).
|
|
||||||
|
|
||||||
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).
|
|
||||||
|
|
||||||
### 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/)
|
|
||||||
|
|
||||||
### 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
|
|
||||||
|
|
|
@ -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.
|
|
@ -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).
|
|
|
@ -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,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"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
)
|
|
@ -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.
|
|
|
@ -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 | ✓ | ✓ |
|
|
||||||
| cacheable_unary | TBD | TBD |
|
|
||||||
| large_unary | ✓ | ✓ |
|
|
||||||
| client_compressed_unary | ✓ | ✓ |
|
|
||||||
| server_compressed_unary | ✓ | ✓ |
|
|
||||||
| client_streaming | ✗ | ✗ |
|
|
||||||
| client_compressed_streaming | ✗ | ✗ |
|
|
||||||
| server_streaming | ✓ | ✗ |
|
|
||||||
| server_compressed_streaming | ✓ | ✗ |
|
|
||||||
| ping_pong | ✗ | ✗ |
|
|
||||||
| empty_stream | ✗ | ✗ |
|
|
||||||
| 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 * | ✓ | ✓ |
|
|
||||||
| status_code_and_message * | ✓ | ✓ |
|
|
||||||
| special_status_message | ✓ | ✓ |
|
|
||||||
| unimplemented_method | ✓ | ✓ |
|
|
||||||
| unimplemented_service | ✓ | ✓ |
|
|
||||||
| cancel_after_begin | ✗ | ✗ |
|
|
||||||
| cancel_after_first_response | ✗ | ✗ |
|
|
||||||
| timeout_on_sleeping_server | ✗ | ✗ |
|
|
||||||
|
|
||||||
\* 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.
|
|
|
@ -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-Web’s 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!
|
|
|
@ -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 don’t 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 won’t 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.
|
|
|
@ -5,12 +5,19 @@ services:
|
||||||
context: ./
|
context: ./
|
||||||
dockerfile: ./net/grpc/gateway/docker/prereqs/Dockerfile
|
dockerfile: ./net/grpc/gateway/docker/prereqs/Dockerfile
|
||||||
image: grpcweb/prereqs
|
image: grpcweb/prereqs
|
||||||
|
common:
|
||||||
|
build:
|
||||||
|
context: ./
|
||||||
|
dockerfile: ./net/grpc/gateway/docker/common/Dockerfile
|
||||||
|
depends_on:
|
||||||
|
- prereqs
|
||||||
|
image: grpcweb/common
|
||||||
echo-server:
|
echo-server:
|
||||||
build:
|
build:
|
||||||
context: ./
|
context: ./
|
||||||
dockerfile: ./net/grpc/gateway/docker/echo_server/Dockerfile
|
dockerfile: ./net/grpc/gateway/docker/echo_server/Dockerfile
|
||||||
depends_on:
|
depends_on:
|
||||||
- prereqs
|
- common
|
||||||
image: grpcweb/echo-server
|
image: grpcweb/echo-server
|
||||||
ports:
|
ports:
|
||||||
- "9090:9090"
|
- "9090:9090"
|
||||||
|
@ -19,17 +26,10 @@ services:
|
||||||
context: ./
|
context: ./
|
||||||
dockerfile: ./net/grpc/gateway/docker/node_server/Dockerfile
|
dockerfile: ./net/grpc/gateway/docker/node_server/Dockerfile
|
||||||
depends_on:
|
depends_on:
|
||||||
- prereqs
|
- common
|
||||||
image: grpcweb/node-server
|
image: grpcweb/node-server
|
||||||
ports:
|
ports:
|
||||||
- "9090:9090"
|
- "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:
|
envoy:
|
||||||
build:
|
build:
|
||||||
context: ./
|
context: ./
|
||||||
|
@ -39,6 +39,17 @@ services:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
links:
|
links:
|
||||||
- node-server
|
- 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:
|
grpcwebproxy:
|
||||||
build:
|
build:
|
||||||
context: ./
|
context: ./
|
||||||
|
@ -53,7 +64,7 @@ services:
|
||||||
context: ./
|
context: ./
|
||||||
dockerfile: ./net/grpc/gateway/docker/commonjs_client/Dockerfile
|
dockerfile: ./net/grpc/gateway/docker/commonjs_client/Dockerfile
|
||||||
depends_on:
|
depends_on:
|
||||||
- prereqs
|
- common
|
||||||
image: grpcweb/commonjs-client
|
image: grpcweb/commonjs-client
|
||||||
ports:
|
ports:
|
||||||
- "8081:8081"
|
- "8081:8081"
|
||||||
|
@ -62,7 +73,7 @@ services:
|
||||||
context: ./
|
context: ./
|
||||||
dockerfile: ./net/grpc/gateway/docker/closure_client/Dockerfile
|
dockerfile: ./net/grpc/gateway/docker/closure_client/Dockerfile
|
||||||
depends_on:
|
depends_on:
|
||||||
- prereqs
|
- common
|
||||||
image: grpcweb/closure-client
|
image: grpcweb/closure-client
|
||||||
ports:
|
ports:
|
||||||
- "8081:8081"
|
- "8081:8081"
|
||||||
|
@ -71,7 +82,7 @@ services:
|
||||||
context: ./
|
context: ./
|
||||||
dockerfile: ./net/grpc/gateway/docker/ts_client/Dockerfile
|
dockerfile: ./net/grpc/gateway/docker/ts_client/Dockerfile
|
||||||
depends_on:
|
depends_on:
|
||||||
- prereqs
|
- common
|
||||||
image: grpcweb/ts-client
|
image: grpcweb/ts-client
|
||||||
ports:
|
ports:
|
||||||
- "8081:8081"
|
- "8081:8081"
|
||||||
|
@ -80,28 +91,7 @@ services:
|
||||||
context: ./
|
context: ./
|
||||||
dockerfile: ./net/grpc/gateway/docker/binary_client/Dockerfile
|
dockerfile: ./net/grpc/gateway/docker/binary_client/Dockerfile
|
||||||
depends_on:
|
depends_on:
|
||||||
- prereqs
|
- common
|
||||||
image: grpcweb/binary-client
|
image: grpcweb/binary-client
|
||||||
ports:
|
ports:
|
||||||
- "8081:8081"
|
- "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
|
|
||||||
|
|
|
@ -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",
|
||||||
|
],
|
||||||
|
)
|
|
@ -12,22 +12,10 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
CXX ?= g++
|
CXX = g++
|
||||||
CPPFLAGS += -I/usr/local/include -pthread
|
CPPFLAGS += -I/usr/local/include -pthread
|
||||||
CXXFLAGS += -std=c++11
|
CXXFLAGS += -std=c++11
|
||||||
LDFLAGS += -L/usr/local/lib -lprotoc -lprotobuf -lpthread -ldl
|
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
|
all: protoc-gen-grpc-web
|
||||||
|
|
||||||
|
@ -35,8 +23,7 @@ protoc-gen-grpc-web: grpc_generator.o
|
||||||
$(CXX) $^ $(LDFLAGS) -o $@
|
$(CXX) $^ $(LDFLAGS) -o $@
|
||||||
|
|
||||||
install: protoc-gen-grpc-web
|
install: protoc-gen-grpc-web
|
||||||
mkdir -p $(PREFIX)/bin
|
install protoc-gen-grpc-web /usr/local/bin/protoc-gen-grpc-web
|
||||||
install protoc-gen-grpc-web $(PREFIX)/bin/protoc-gen-grpc-web
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f *.o protoc-gen-grpc-web
|
rm -f *.o protoc-gen-grpc-web
|
|
@ -28,84 +28,75 @@ goog.module.declareLegacyNamespace();
|
||||||
|
|
||||||
|
|
||||||
const ClientReadableStream = goog.require('grpc.web.ClientReadableStream');
|
const ClientReadableStream = goog.require('grpc.web.ClientReadableStream');
|
||||||
const MethodDescriptor = goog.require('grpc.web.MethodDescriptor');
|
const Error = goog.require('grpc.web.Error');
|
||||||
const RpcError = goog.require('grpc.web.RpcError');
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This interface represents a grpc-web client
|
||||||
|
*
|
||||||
|
* @interface
|
||||||
|
*/
|
||||||
|
const AbstractClientBase = function() {};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @constructor
|
* @constructor
|
||||||
* @struct
|
* @struct
|
||||||
* @final
|
* @template REQUEST, RESPONSE
|
||||||
|
* @param {function(new: RESPONSE, ...)} responseType
|
||||||
|
* @param {function(REQUEST): ?} requestSerializeFn
|
||||||
|
* @param {function(?): RESPONSE} responseDeserializeFn
|
||||||
*/
|
*/
|
||||||
const PromiseCallOptions = function() {};
|
AbstractClientBase.MethodInfo = function(
|
||||||
|
responseType,
|
||||||
/**
|
requestSerializeFn,
|
||||||
* An AbortSignal to abort the call.
|
responseDeserializeFn) {
|
||||||
* @type {AbortSignal|undefined}
|
/** @const */
|
||||||
*/
|
this.responseType = responseType;
|
||||||
PromiseCallOptions.prototype.signal;
|
/** @const */
|
||||||
|
this.requestSerializeFn = requestSerializeFn;
|
||||||
|
/** @const */
|
||||||
/**
|
this.responseDeserializeFn = responseDeserializeFn;
|
||||||
* 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>}
|
|
||||||
*/
|
|
||||||
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) {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the hostname of the current request.
|
|
||||||
* @template REQUEST, RESPONSE
|
* @template REQUEST, RESPONSE
|
||||||
* @param {string} method
|
* Even with ?RESPONSE the RESPONSE will still be inferred as
|
||||||
* @param {!MethodDescriptor<REQUEST,RESPONSE>} methodDescriptor
|
* "FooResponse|Null". Use RESPONSE_LEAN to extract out the "FooResponse"
|
||||||
* @return {string}
|
* 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) {
|
AbstractClientBase.prototype.rpcCall = goog.abstractMethod;
|
||||||
// method = hostname + methodDescriptor.name(relative path of this method)
|
|
||||||
return method.substr(0, method.length - methodDescriptor.name.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -43,25 +43,10 @@ const ClientReadableStream = function() {};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a callback to handle different stream events.
|
* Register a callback to handle I/O 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.
|
|
||||||
*
|
*
|
||||||
* @param {string} eventType The event type
|
* @param {string} eventType The event type
|
||||||
* @param {function(?)} callback The callback to handle the event with
|
* @param {function(?)} callback The call back to handle the event with
|
||||||
* an optional input object
|
* an optional input object
|
||||||
* @return {!ClientReadableStream} this object
|
* @return {!ClientReadableStream} this object
|
||||||
*/
|
*/
|
||||||
|
@ -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.
|
* Close the stream.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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;
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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",
|
|
||||||
],
|
|
||||||
)
|
|
|
@ -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
|
@ -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
|
* This class is being returned after a gRPC streaming call has been
|
||||||
* implementation underneath the ClientReadableStream layer.
|
* 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)
|
* @author stanleycheung@google.com (Stanley Cheung)
|
||||||
*/
|
*/
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -28,384 +28,195 @@ goog.module('grpc.web.GrpcWebClientBase');
|
||||||
goog.module.declareLegacyNamespace();
|
goog.module.declareLegacyNamespace();
|
||||||
|
|
||||||
|
|
||||||
const ClientOptions = goog.requireType('grpc.web.ClientOptions');
|
const AbstractClientBase = goog.require('grpc.web.AbstractClientBase');
|
||||||
const ClientReadableStream = goog.require('grpc.web.ClientReadableStream');
|
|
||||||
const ClientUnaryCallImpl = goog.require('grpc.web.ClientUnaryCallImpl');
|
|
||||||
const GrpcWebClientReadableStream = goog.require('grpc.web.GrpcWebClientReadableStream');
|
const GrpcWebClientReadableStream = goog.require('grpc.web.GrpcWebClientReadableStream');
|
||||||
const HttpCors = goog.require('goog.net.rpc.HttpCors');
|
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 StatusCode = goog.require('grpc.web.StatusCode');
|
||||||
const XhrIo = goog.require('goog.net.XhrIo');
|
const XhrIo = goog.require('goog.net.XhrIo');
|
||||||
const googCrypt = goog.require('goog.crypt.base64');
|
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
|
* Base class for gRPC web client using the application/grpc-web wire format
|
||||||
|
* @param {?Object=} opt_options
|
||||||
|
* @constructor
|
||||||
* @implements {AbstractClientBase}
|
* @implements {AbstractClientBase}
|
||||||
* @unrestricted
|
|
||||||
*/
|
*/
|
||||||
class GrpcWebClientBase {
|
const GrpcWebClientBase = function(opt_options) {
|
||||||
/**
|
/**
|
||||||
* @param {!ClientOptions=} options
|
* @const
|
||||||
* @param {!XhrIo=} xhrIo
|
* @private {string}
|
||||||
*/
|
*/
|
||||||
constructor(options = {}, xhrIo = undefined) {
|
this.format_ =
|
||||||
/**
|
goog.getObjectByName('format', opt_options) || "text";
|
||||||
* @const
|
|
||||||
* @private {string}
|
|
||||||
*/
|
|
||||||
this.format_ =
|
|
||||||
options.format || goog.getObjectByName('format', options) || 'text';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @const
|
|
||||||
* @private {boolean}
|
|
||||||
*/
|
|
||||||
this.suppressCorsPreflight_ = options.suppressCorsPreflight ||
|
|
||||||
goog.getObjectByName('suppressCorsPreflight', 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
|
* @const
|
||||||
* @export
|
* @private {boolean}
|
||||||
*/
|
*/
|
||||||
rpcCall(method, requestMessage, metadata, methodDescriptor, callback) {
|
this.suppressCorsPreflight_ =
|
||||||
const hostname = getHostname(method, methodDescriptor);
|
goog.getObjectByName('suppressCorsPreflight', opt_options) || false;
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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;
|
* @override
|
||||||
let unaryStatus;
|
*/
|
||||||
let unaryMsg;
|
GrpcWebClientBase.prototype.rpcCall = function(
|
||||||
GrpcWebClientBase.setCallback_(
|
method, request, metadata, methodInfo, callback) {
|
||||||
stream,
|
var xhr = this.newXhr_();
|
||||||
(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.
|
var genericTransportInterface = {
|
||||||
if (signal) {
|
xhr: xhr,
|
||||||
signal.addEventListener('abort', () => {
|
};
|
||||||
stream.cancel();
|
var stream = new GrpcWebClientReadableStream(genericTransportInterface);
|
||||||
|
stream.setResponseDeserializeFn(methodInfo.responseDeserializeFn);
|
||||||
|
|
||||||
const error = new RpcError(StatusCode.CANCELLED, 'Aborted');
|
stream.on('data', function(response) {
|
||||||
error.cause = /** @type {!AbortSignal} */ (signal).reason;
|
callback(null, response);
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
stream.on('status', function(status) {
|
||||||
* @export
|
if (status.code != StatusCode.OK) {
|
||||||
* @param {string} method The method to invoke
|
callback({
|
||||||
* @param {REQUEST} requestMessage The request proto
|
code: status.code,
|
||||||
* @param {!Object<string, string>} metadata User defined call metadata
|
message: status.details
|
||||||
* @param {!MethodDescriptor<REQUEST, RESPONSE>} methodDescriptor Information
|
}, null);
|
||||||
* 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 = {
|
|
||||||
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();
|
stream.on('error', function(error) {
|
||||||
const serialized = requestSerializeFn(request.getRequestMessage());
|
if (error.code != StatusCode.OK) {
|
||||||
let payload = this.encodeRequest_(serialized);
|
callback({
|
||||||
if (this.format_ == 'text') {
|
code: error.code,
|
||||||
payload = googCrypt.encodeByteArray(payload);
|
message: error.message
|
||||||
} else if (this.format_ == 'binary') {
|
}, null);
|
||||||
xhr.setResponseType(XhrIo.ResponseType.ARRAY_BUFFER);
|
|
||||||
}
|
}
|
||||||
xhr.send(path, 'POST', payload);
|
});
|
||||||
return stream;
|
|
||||||
|
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);
|
||||||
* @private
|
var payload = this.encodeRequest_(serialized);
|
||||||
* @static
|
if (this.format_ == "text") {
|
||||||
* @template RESPONSE
|
payload = googCrypt.encodeByteArray(payload);
|
||||||
* @param {!ClientReadableStream<RESPONSE>} stream
|
} else if (this.format_ == "binary") {
|
||||||
* @param {function(?RpcError, ?RESPONSE, ?Status=, ?Object<string, string>=, ?boolean)|
|
xhr.setResponseType(XhrIo.ResponseType.ARRAY_BUFFER);
|
||||||
* function(?RpcError,?RESPONSE)} callback
|
}
|
||||||
* @param {boolean} useUnaryResponse Pass true to have the client make
|
xhr.send(method, 'POST', payload);
|
||||||
* multiple calls to the callback, using (error, response, status,
|
return stream;
|
||||||
* 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;
|
|
||||||
|
|
||||||
stream.on('data', function(response) {
|
|
||||||
isResponseReceived = true;
|
|
||||||
responseReceived = response;
|
|
||||||
});
|
|
||||||
|
|
||||||
stream.on('error', function(error) {
|
/**
|
||||||
if (error.code != StatusCode.OK && !errorEmitted) {
|
* @override
|
||||||
errorEmitted = true;
|
*/
|
||||||
callback(error, null);
|
GrpcWebClientBase.prototype.serverStreaming = function(
|
||||||
}
|
method, request, metadata, methodInfo) {
|
||||||
});
|
var xhr = this.newXhr_();
|
||||||
|
|
||||||
stream.on('status', function(status) {
|
var genericTransportInterface = {
|
||||||
if (status.code != StatusCode.OK && !errorEmitted) {
|
xhr: xhr,
|
||||||
errorEmitted = true;
|
};
|
||||||
callback(
|
var stream = new GrpcWebClientReadableStream(genericTransportInterface);
|
||||||
{
|
stream.setResponseDeserializeFn(methodInfo.responseDeserializeFn);
|
||||||
code: status.code,
|
|
||||||
message: status.details,
|
|
||||||
metadata: status.metadata
|
|
||||||
},
|
|
||||||
null);
|
|
||||||
} else if (useUnaryResponse) {
|
|
||||||
callback(null, null, status);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (useUnaryResponse) {
|
xhr.headers.addAll(metadata);
|
||||||
stream.on('metadata', function(metadata) {
|
this.processHeaders_(xhr);
|
||||||
callback(null, null, null, metadata);
|
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
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {!Uint8Array} serialized The serialized proto payload
|
||||||
|
* @return {!Uint8Array} The application/grpc-web padded request
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
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 {
|
||||||
|
xhr.headers.set('Content-Type', 'application/grpc-web+proto');
|
||||||
|
}
|
||||||
|
xhr.headers.set('X-User-Agent', 'grpc-web-javascript/0.1');
|
||||||
|
xhr.headers.set('X-Grpc-Web', '1');
|
||||||
|
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');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
stream.on('end', function() {
|
/**
|
||||||
if (!errorEmitted) {
|
* @private
|
||||||
if (!isResponseReceived) {
|
* @static
|
||||||
callback({
|
* @param {string} method The method to invoke
|
||||||
code: StatusCode.UNKNOWN,
|
* @param {!Object<string,string>} headerObject The xhr headers
|
||||||
message: 'Incomplete response',
|
* @return {string} The URI object or a string path with headers
|
||||||
});
|
*/
|
||||||
} else if (useUnaryResponse) {
|
GrpcWebClientBase.setCorsOverride_ = function(method, headerObject) {
|
||||||
callback(
|
return /** @type {string} */ (HttpCors.setHttpHeadersWithOverwriteParam(
|
||||||
null, responseReceived, null, null,
|
method, HttpCors.HTTP_HEADERS_PARAM_NAME, headerObject));
|
||||||
/* unaryResponseReceived= */ true);
|
};
|
||||||
} else {
|
|
||||||
callback(null, responseReceived);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (useUnaryResponse) {
|
|
||||||
callback(null, null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encode the grpc-web request
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @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--) {
|
|
||||||
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') {
|
|
||||||
xhr.headers.set('Content-Type', 'application/grpc-web-text');
|
|
||||||
xhr.headers.set('Accept', 'application/grpc-web-text');
|
|
||||||
} else {
|
|
||||||
xhr.headers.set('Content-Type', 'application/grpc-web+proto');
|
|
||||||
}
|
|
||||||
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 (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
|
|
||||||
* @static
|
|
||||||
* @param {string} method The method to invoke
|
|
||||||
* @param {!Object<string,string>} headerObject The xhr headers
|
|
||||||
* @return {string} The URI object or a string path with headers
|
|
||||||
*/
|
|
||||||
static setCorsOverride_(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;
|
exports = GrpcWebClientBase;
|
||||||
|
|
|
@ -18,425 +18,179 @@
|
||||||
goog.module('grpc.web.GrpcWebClientBaseTest');
|
goog.module('grpc.web.GrpcWebClientBaseTest');
|
||||||
goog.setTestOnly('grpc.web.GrpcWebClientBaseTest');
|
goog.setTestOnly('grpc.web.GrpcWebClientBaseTest');
|
||||||
|
|
||||||
const ClientReadableStream = goog.require('grpc.web.ClientReadableStream');
|
var GrpcWebClientBase = goog.require('grpc.web.GrpcWebClientBase');
|
||||||
const ErrorCode = goog.require('goog.net.ErrorCode');
|
var Map = goog.require('goog.structs.Map');
|
||||||
const GrpcWebClientBase = goog.require('grpc.web.GrpcWebClientBase');
|
var googCrypt = goog.require('goog.crypt.base64');
|
||||||
const MethodDescriptor = goog.require('grpc.web.MethodDescriptor');
|
var googEvents = goog.require('goog.events');
|
||||||
const ReadyState = goog.require('goog.net.XmlHttp.ReadyState');
|
var testSuite = goog.require('goog.testing.testSuite');
|
||||||
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');
|
|
||||||
goog.require('goog.testing.jsunit');
|
goog.require('goog.testing.jsunit');
|
||||||
|
|
||||||
// This parses to [ { DATA: [4, 5, 6] }, { TRAILER: "a: b" } ]
|
var REQUEST_BYTES = [1,2,3];
|
||||||
const DEFAULT_RPC_RESPONSE =
|
var FAKE_METHOD = "fake-method";
|
||||||
new Uint8Array([0, 0, 0, 0, 3, 4, 5, 6, 128, 0, 0, 0, 4, 97, 58, 32, 98]);
|
var PROTO_FIELD_VALUE = "meow";
|
||||||
const DEFAULT_RPC_RESPONSE_DATA = [4, 5, 6];
|
var EXPECTED_HEADERS;
|
||||||
const DEFAULT_UNARY_HEADERS =
|
var EXPECTED_HEADER_VALUES;
|
||||||
['Content-Type', 'Accept', 'X-User-Agent', 'X-Grpc-Web'];
|
var EXPECTED_UNARY_HEADERS = ['Content-Type', 'Accept',
|
||||||
const DEFAULT_UNARY_HEADER_VALUES = [
|
'X-User-Agent', 'X-Grpc-Web'];
|
||||||
'application/grpc-web-text',
|
var EXPECTED_UNARY_HEADER_VALUES = ['application/grpc-web-text',
|
||||||
'application/grpc-web-text',
|
'application/grpc-web-text',
|
||||||
'grpc-web-javascript/0.1',
|
'grpc-web-javascript/0.1',
|
||||||
'1',
|
'1'];
|
||||||
];
|
var dataCallback;
|
||||||
const DEFAULT_RESPONSE_HEADERS = {
|
|
||||||
'Content-Type': 'application/grpc-web-text',
|
|
||||||
};
|
|
||||||
|
|
||||||
testSuite({
|
testSuite({
|
||||||
async testRpcResponse() {
|
setUp: function() {
|
||||||
const xhr = new XhrIo();
|
googEvents.listen = function(a, b, listener, d, e) {
|
||||||
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
|
dataCallback = listener;
|
||||||
const methodDescriptor = createMethodDescriptor((bytes) => {
|
return;
|
||||||
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));
|
|
||||||
},
|
|
||||||
|
|
||||||
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));
|
|
||||||
},
|
|
||||||
|
|
||||||
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,
|
|
||||||
])),
|
|
||||||
DEFAULT_RESPONSE_HEADERS);
|
|
||||||
});
|
|
||||||
assertTrue(error instanceof RpcError);
|
|
||||||
assertEquals(3, error.code);
|
|
||||||
},
|
|
||||||
|
|
||||||
async testRpcDeserializationError() {
|
|
||||||
const xhr = new XhrIo();
|
|
||||||
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
|
|
||||||
|
|
||||||
const responseDeserializeFn = () => {
|
|
||||||
throw new Error('Decoding error :)');
|
|
||||||
};
|
};
|
||||||
const methodDescriptor = createMethodDescriptor(responseDeserializeFn);
|
|
||||||
const error = await new Promise((resolve, reject) => {
|
|
||||||
client.rpcCall(
|
|
||||||
'urlurl', new MockRequest(), /* metadata= */ {}, methodDescriptor,
|
|
||||||
(error, response) => {
|
|
||||||
assertNull(response);
|
|
||||||
resolve(error);
|
|
||||||
});
|
|
||||||
xhr.simulatePartialResponse(
|
|
||||||
googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)),
|
|
||||||
DEFAULT_RESPONSE_HEADERS);
|
|
||||||
});
|
|
||||||
assertTrue(error instanceof RpcError);
|
|
||||||
assertEquals(StatusCode.INTERNAL, error.code);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async testRpcResponseHeader() {
|
tearDown: function() {
|
||||||
const xhr = new XhrIo();
|
EXPECTED_HEADERS = null;
|
||||||
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
|
EXPECTED_HEADER_VALUES = null;
|
||||||
const methodDescriptor = createMethodDescriptor((bytes) => {
|
},
|
||||||
assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes));
|
|
||||||
return new MockReply('value');
|
|
||||||
});
|
|
||||||
|
|
||||||
const metadata = await new Promise((resolve, reject) => {
|
testRpcResponse: function() {
|
||||||
const call = client.rpcCall(
|
var client = new GrpcWebClientBase();
|
||||||
'url', new MockRequest(), /* metadata= */ {}, methodDescriptor,
|
client.newXhr_ = function() {
|
||||||
(error, response) => {
|
return new MockXhr({
|
||||||
assertNull(error);
|
// This parses to [ { DATA: [4,5,6] }, { TRAILER: "a: b" } ]
|
||||||
});
|
response: googCrypt.encodeByteArray(new Uint8Array([
|
||||||
call.on('metadata', (metadata) => {
|
0, 0, 0, 0, 3, 4, 5, 6, 128, 0, 0, 0, 4, 97, 58, 32, 98
|
||||||
resolve(metadata);
|
])),
|
||||||
});
|
});
|
||||||
xhr.simulatePartialResponse(
|
};
|
||||||
googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)), {
|
|
||||||
'Content-Type': 'application/grpc-web-text',
|
expectUnaryHeaders();
|
||||||
'initial-metadata-key': 'initial-metadata-value',
|
client.rpcCall(FAKE_METHOD, {}, {}, {
|
||||||
});
|
requestSerializeFn : function(request) {
|
||||||
xhr.simulateReadyStateChange(ReadyState.COMPLETE);
|
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);
|
||||||
});
|
});
|
||||||
assertEquals('initial-metadata-value', metadata['initial-metadata-key']);
|
dataCallback();
|
||||||
},
|
},
|
||||||
|
|
||||||
async testStreamInterceptor() {
|
testRpcError: function() {
|
||||||
const xhr = new XhrIo();
|
var client = new GrpcWebClientBase();
|
||||||
const interceptor = new StreamResponseInterceptor();
|
client.newXhr_ = function() {
|
||||||
const methodDescriptor = createMethodDescriptor((bytes) => {
|
return new MockXhr({
|
||||||
assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes));
|
// This decodes to "grpc-status: 3"
|
||||||
return new MockReply('value');
|
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 client =
|
])),
|
||||||
new GrpcWebClientBase({'streamInterceptors': [interceptor]}, xhr);
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const response = await new Promise((resolve, reject) => {
|
expectUnaryHeaders();
|
||||||
client.rpcCall(
|
client.rpcCall(FAKE_METHOD, {}, {}, {
|
||||||
'url', new MockRequest(), /* metadata= */ {}, methodDescriptor,
|
requestSerializeFn : function(request) {
|
||||||
(error, response) => {
|
return REQUEST_BYTES;
|
||||||
assertNull(error);
|
},
|
||||||
resolve(response);
|
responseDeserializeFn : function(bytes) {
|
||||||
});
|
return {};
|
||||||
xhr.simulatePartialResponse(
|
}
|
||||||
googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)),
|
}, function(error, response) {
|
||||||
DEFAULT_RESPONSE_HEADERS);
|
assertNull(response);
|
||||||
xhr.simulateReadyStateChange(ReadyState.COMPLETE);
|
assertEquals(3, error.code);
|
||||||
});
|
});
|
||||||
assertEquals('Intercepted value', response.data);
|
dataCallback();
|
||||||
},
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/** Mocks a request proto object. */
|
|
||||||
class MockRequest {
|
|
||||||
/**
|
|
||||||
* @param {string=} data
|
|
||||||
*/
|
|
||||||
constructor(data = '') {
|
|
||||||
/** @type {string} */
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Mocks a response proto object. */
|
/** Sets expected headers as the unary response headers */
|
||||||
class MockReply {
|
function expectUnaryHeaders() {
|
||||||
/**
|
EXPECTED_HEADERS = EXPECTED_UNARY_HEADERS;
|
||||||
* @param {string=} data
|
EXPECTED_HEADER_VALUES = EXPECTED_UNARY_HEADER_VALUES;
|
||||||
*/
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @implements {StreamInterceptor}
|
* @constructor
|
||||||
* @unrestricted
|
* @param {?Object} mockValues
|
||||||
|
* Mock XhrIO object to test the outgoing values
|
||||||
*/
|
*/
|
||||||
class StreamResponseInterceptor {
|
function MockXhr(mockValues) {
|
||||||
constructor() {}
|
this.mockValues = mockValues;
|
||||||
|
this.headers = new Map();
|
||||||
/**
|
|
||||||
* @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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @implements {ClientReadableStream}
|
* @param {string} url
|
||||||
* @template RESPONSE
|
* @param {string=} opt_method
|
||||||
* @final
|
* @param {string=} opt_content
|
||||||
|
* @param {string=} opt_headers
|
||||||
*/
|
*/
|
||||||
class InterceptedStream {
|
MockXhr.prototype.send = function(url, opt_method, opt_content, opt_headers) {
|
||||||
/**
|
assertEquals(FAKE_METHOD, url);
|
||||||
* @param {!ClientReadableStream<RESPONSE>} stream
|
assertEquals("POST", opt_method);
|
||||||
*/
|
assertElementsEquals(googCrypt.encodeByteArray(new Uint8Array([0, 0, 0, 0, 3, 1, 2, 3])), opt_content);
|
||||||
constructor(stream) {
|
assertElementsEquals(EXPECTED_HEADERS, this.headers.getKeys());
|
||||||
/** @const {!ClientReadableStream<RESPONSE>} */
|
assertElementsEquals(EXPECTED_HEADER_VALUES, this.headers.getValues());
|
||||||
this.stream = stream;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @param {string} eventType
|
|
||||||
* @param {function(?)} callback
|
|
||||||
* @return {!ClientReadableStream<RESPONSE>}
|
|
||||||
*/
|
|
||||||
on(eventType, callback) {
|
|
||||||
if (eventType == 'data') {
|
|
||||||
const newCallback = (response) => {
|
|
||||||
response.data = 'Intercepted ' + response.data;
|
|
||||||
callback(response);
|
|
||||||
};
|
|
||||||
this.stream.on(eventType, newCallback);
|
|
||||||
} else {
|
|
||||||
this.stream.on(eventType, callback);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* @param {boolean} withCredentials
|
||||||
* @return {!ClientReadableStream<RESPONSE>}
|
*/
|
||||||
*/
|
MockXhr.prototype.setWithCredentials = function(withCredentials) {
|
||||||
cancel() {
|
return;
|
||||||
this.stream.cancel();
|
};
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
/**
|
||||||
* @param {string} eventType
|
* @return {string} response
|
||||||
* @param {function(?)} callback
|
*/
|
||||||
* @return {!ClientReadableStream<RESPONSE>}
|
MockXhr.prototype.getResponseText = function() {
|
||||||
*/
|
return this.mockValues.response;
|
||||||
removeListener(eventType, callback) {
|
};
|
||||||
this.stream.removeListener(eventType, callback);
|
|
||||||
return this;
|
|
||||||
}
|
/**
|
||||||
}
|
* @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;
|
||||||
|
};
|
||||||
|
|
|
@ -35,9 +35,9 @@ const ClientReadableStream = goog.require('grpc.web.ClientReadableStream');
|
||||||
const ErrorCode = goog.require('goog.net.ErrorCode');
|
const ErrorCode = goog.require('goog.net.ErrorCode');
|
||||||
const EventType = goog.require('goog.net.EventType');
|
const EventType = goog.require('goog.net.EventType');
|
||||||
const GrpcWebStreamParser = goog.require('grpc.web.GrpcWebStreamParser');
|
const GrpcWebStreamParser = goog.require('grpc.web.GrpcWebStreamParser');
|
||||||
const RpcError = goog.require('grpc.web.RpcError');
|
|
||||||
const StatusCode = goog.require('grpc.web.StatusCode');
|
const StatusCode = goog.require('grpc.web.StatusCode');
|
||||||
const XhrIo = goog.require('goog.net.XhrIo');
|
const XhrIo = goog.require('goog.net.XhrIo');
|
||||||
|
const XmlHttp = goog.require('goog.net.XmlHttp');
|
||||||
const events = goog.require('goog.events');
|
const events = goog.require('goog.events');
|
||||||
const googCrypt = goog.require('goog.crypt.base64');
|
const googCrypt = goog.require('goog.crypt.base64');
|
||||||
const googString = goog.require('goog.string');
|
const googString = goog.require('goog.string');
|
||||||
|
@ -46,402 +46,222 @@ const {Status} = goog.require('grpc.web.Status');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const GRPC_STATUS = 'grpc-status';
|
const GRPC_STATUS = "grpc-status";
|
||||||
const GRPC_STATUS_MESSAGE = 'grpc-message';
|
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
|
* A stream that the client can read from. Used for calls that are streaming
|
||||||
* from the server side.
|
* from the server side.
|
||||||
|
*
|
||||||
* @template RESPONSE
|
* @template RESPONSE
|
||||||
|
* @constructor
|
||||||
* @implements {ClientReadableStream}
|
* @implements {ClientReadableStream}
|
||||||
* @final
|
* @final
|
||||||
* @unrestricted
|
* @param {!GenericTransportInterface} genericTransportInterface The
|
||||||
|
* GenericTransportInterface
|
||||||
*/
|
*/
|
||||||
class GrpcWebClientReadableStream {
|
const GrpcWebClientReadableStream = function(genericTransportInterface) {
|
||||||
/**
|
/**
|
||||||
* @param {!GenericTransportInterface} genericTransportInterface The
|
* @const
|
||||||
* GenericTransportInterface
|
* @private
|
||||||
|
* @type {?XhrIo} The XhrIo object
|
||||||
*/
|
*/
|
||||||
constructor(genericTransportInterface) {
|
this.xhr_ = /** @type {?XhrIo} */ (genericTransportInterface.xhr);
|
||||||
/**
|
|
||||||
* @const
|
|
||||||
* @private
|
|
||||||
* @type {?XhrIo} The XhrIo object
|
|
||||||
*/
|
|
||||||
this.xhr_ = /** @type {?XhrIo} */ (genericTransportInterface.xhr);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {function(?):!RESPONSE|null} The deserialize function for the proto
|
* @type {function(?):!RESPONSE|null} The deserialize function for the proto
|
||||||
*/
|
*/
|
||||||
this.responseDeserializeFn_ = null;
|
this.responseDeserializeFn_ = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @const
|
* @private
|
||||||
* @private
|
* @type {function(!RESPONSE)|null} The data callback
|
||||||
* @type {!Array<function(!RESPONSE)>} The list of data callbacks
|
*/
|
||||||
*/
|
this.onDataCallback_ = null;
|
||||||
this.onDataCallbacks_ = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @const
|
* @private
|
||||||
* @private
|
* @type {function(!Status)|null} The status callback
|
||||||
* @type {!Array<function(!Status)>} The list of status callbacks
|
*/
|
||||||
*/
|
this.onStatusCallback_ = null;
|
||||||
this.onStatusCallbacks_ = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @const
|
* @private
|
||||||
* @private
|
* @type {function(...):?|null} The error callback
|
||||||
* @type {!Array<function(!Metadata)>} The list of metadata callbacks
|
*/
|
||||||
*/
|
this.onErrorCallback_ = null;
|
||||||
this.onMetadataCallbacks_ = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @const
|
* @private
|
||||||
* @private
|
* @type {function(...):?|null} The stream end callback
|
||||||
* @type {!Array<function(!RpcError)>} The list of error callbacks
|
*/
|
||||||
*/
|
this.onEndCallback_ = null;
|
||||||
this.onErrorCallbacks_ = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @const
|
* @private
|
||||||
* @private
|
* @type {number} The stream parser position
|
||||||
* @type {!Array<function(...):?>} The list of stream end callbacks
|
*/
|
||||||
*/
|
this.pos_ = 0;
|
||||||
this.onEndCallbacks_ = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {boolean} Whether the stream has been aborted
|
* @type {!GrpcWebStreamParser} The grpc-web stream parser
|
||||||
*/
|
* @const
|
||||||
this.aborted_ = false;
|
*/
|
||||||
|
this.parser_ = new GrpcWebStreamParser();
|
||||||
|
|
||||||
/**
|
var self = this;
|
||||||
* @private
|
events.listen(this.xhr_, EventType.READY_STATE_CHANGE,
|
||||||
* @type {number} The stream parser position
|
function(e) {
|
||||||
*/
|
var contentType = self.xhr_.getStreamingResponseHeader('Content-Type');
|
||||||
this.pos_ = 0;
|
if (!contentType) return;
|
||||||
|
contentType = contentType.toLowerCase();
|
||||||
|
|
||||||
/**
|
if (googString.startsWith(contentType, 'application/grpc-web-text')) {
|
||||||
* @private
|
var responseText = self.xhr_.getResponseText();
|
||||||
* @type {!GrpcWebStreamParser} The grpc-web stream parser
|
var newPos = responseText.length - responseText.length % 4;
|
||||||
* @const
|
var newData = responseText.substr(self.pos_, newPos - self.pos_);
|
||||||
*/
|
if (newData.length == 0) return;
|
||||||
this.parser_ = new GrpcWebStreamParser();
|
self.pos_ = newPos;
|
||||||
|
var byteSource = googCrypt.decodeStringToUint8Array(newData);
|
||||||
|
} else if (googString.startsWith(contentType, 'application/grpc')) {
|
||||||
|
var byteSource = new Uint8Array(
|
||||||
|
/** @type {!ArrayBuffer} */ (self.xhr_.getResponse()));
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var messages = self.parser_.parse([].slice.call(byteSource));
|
||||||
|
if (!messages) return;
|
||||||
|
|
||||||
const self = this;
|
var FrameType = GrpcWebStreamParser.FrameType;
|
||||||
events.listen(this.xhr_, EventType.READY_STATE_CHANGE, function(e) {
|
for (var i = 0; i < messages.length; i++) {
|
||||||
let contentType = self.xhr_.getStreamingResponseHeader('Content-Type');
|
if (FrameType.DATA in messages[i]) {
|
||||||
if (!contentType) return;
|
var data = messages[i][FrameType.DATA];
|
||||||
contentType = contentType.toLowerCase();
|
if (data) {
|
||||||
|
var response = self.responseDeserializeFn_(data);
|
||||||
let byteSource;
|
if (response) {
|
||||||
if (googString.startsWith(contentType, 'application/grpc-web-text')) {
|
self.onDataCallback_(response);
|
||||||
// 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_);
|
|
||||||
if (newData.length == 0) return;
|
|
||||||
self.pos_ = newPos;
|
|
||||||
byteSource = googCrypt.decodeStringToUint8Array(newData);
|
|
||||||
} else if (googString.startsWith(contentType, 'application/grpc')) {
|
|
||||||
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++) {
|
|
||||||
if (FrameType.DATA in messages[i]) {
|
|
||||||
const 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
pos++) {
|
|
||||||
trailerString +=
|
|
||||||
String.fromCharCode(messages[i][FrameType.TRAILER][pos]);
|
|
||||||
}
|
|
||||||
const trailers = self.parseHttp1Headers_(trailerString);
|
|
||||||
let grpcStatusCode = StatusCode.OK;
|
|
||||||
let grpcStatusMessage = '';
|
|
||||||
if (GRPC_STATUS in trailers) {
|
|
||||||
grpcStatusCode =
|
|
||||||
/** @type {!StatusCode} */ (Number(trailers[GRPC_STATUS]));
|
|
||||||
delete 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 (FrameType.TRAILER in messages[i]) {
|
||||||
|
if (messages[i][FrameType.TRAILER].length > 0) {
|
||||||
events.listen(this.xhr_, EventType.COMPLETE, function(e) {
|
var trailerString = "";
|
||||||
const lastErrorCode = self.xhr_.getLastErrorCode();
|
for (var pos = 0; pos < messages[i][FrameType.TRAILER].length;
|
||||||
let grpcStatusCode = StatusCode.UNKNOWN;
|
pos++) {
|
||||||
let grpcStatusMessage = '';
|
trailerString += String.fromCharCode(
|
||||||
const initialMetadata = /** @type {!Metadata} */ ({});
|
messages[i][FrameType.TRAILER][pos]);
|
||||||
|
}
|
||||||
// Get response headers with lower case keys.
|
var trailers = self.parseHttp1Headers_(trailerString);
|
||||||
const rawResponseHeaders = self.xhr_.getResponseHeaders();
|
var grpcStatusCode = StatusCode.OK;
|
||||||
const responseHeaders = {};
|
var grpcStatusMessage = "";
|
||||||
for (const key in rawResponseHeaders) {
|
if (GRPC_STATUS in trailers) {
|
||||||
if (rawResponseHeaders.hasOwnProperty(key)) {
|
grpcStatusCode = trailers[GRPC_STATUS];
|
||||||
responseHeaders[key.toLowerCase()] = rawResponseHeaders[key];
|
}
|
||||||
|
if (GRPC_STATUS_MESSAGE in trailers) {
|
||||||
|
grpcStatusMessage = trailers[GRPC_STATUS_MESSAGE];
|
||||||
|
}
|
||||||
|
if (self.onStatusCallback_) {
|
||||||
|
self.onStatusCallback_({
|
||||||
|
code: Number(grpcStatusCode),
|
||||||
|
details: grpcStatusMessage,
|
||||||
|
metadata: trailers,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Object.keys(responseHeaders).forEach((header_) => {
|
var readyState = self.xhr_.getReadyState();
|
||||||
if (!(EXCLUDED_RESPONSE_HEADERS.includes(header_))) {
|
if (readyState == XmlHttp.ReadyState.COMPLETE) {
|
||||||
initialMetadata[header_] = responseHeaders[header_];
|
if (self.onEndCallback_) {
|
||||||
}
|
self.onEndCallback_();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
events.listen(this.xhr_, EventType.COMPLETE, function(e) {
|
||||||
|
if (!self.onErrorCallback_) return;
|
||||||
|
var lastErrorCode = self.xhr_.getLastErrorCode();
|
||||||
|
if (lastErrorCode != ErrorCode.NO_ERROR) {
|
||||||
|
self.onErrorCallback_({
|
||||||
|
code: StatusCode.UNAVAILABLE,
|
||||||
|
message: ErrorCode.getDebugMessage(lastErrorCode)
|
||||||
});
|
});
|
||||||
self.sendMetadataCallbacks_(initialMetadata);
|
return;
|
||||||
|
|
||||||
// There's an XHR level error
|
|
||||||
let xhrStatusCode = -1;
|
|
||||||
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_) {
|
|
||||||
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_();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @export
|
|
||||||
*/
|
|
||||||
on(eventType, callback) {
|
|
||||||
// TODO(stanleycheung): change eventType to @enum type
|
|
||||||
if (eventType == 'data') {
|
|
||||||
this.onDataCallbacks_.push(callback);
|
|
||||||
} else if (eventType == 'status') {
|
|
||||||
this.onStatusCallbacks_.push(callback);
|
|
||||||
} else if (eventType == 'metadata') {
|
|
||||||
this.onMetadataCallbacks_.push(callback);
|
|
||||||
} else if (eventType == 'end') {
|
|
||||||
this.onEndCallbacks_.push(callback);
|
|
||||||
} else if (eventType == 'error') {
|
|
||||||
this.onErrorCallbacks_.push(callback);
|
|
||||||
}
|
}
|
||||||
return this;
|
var responseHeaders = self.xhr_.getResponseHeaders();
|
||||||
}
|
if (GRPC_STATUS in responseHeaders &&
|
||||||
|
responseHeaders[GRPC_STATUS] != StatusCode.OK) {
|
||||||
/**
|
self.onErrorCallback_({
|
||||||
* @private
|
code: Number(responseHeaders[GRPC_STATUS]),
|
||||||
* @param {!Array<function(?)>} callbacks the internal list of callbacks
|
message: responseHeaders[GRPC_STATUS_MESSAGE]
|
||||||
* @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
|
* @override
|
||||||
*
|
*/
|
||||||
* @param {function(?):!RESPONSE} responseDeserializeFn The deserialize
|
GrpcWebClientReadableStream.prototype.on = function(
|
||||||
* function for the proto
|
eventType, callback) {
|
||||||
*/
|
// TODO(stanleycheung): change eventType to @enum type
|
||||||
setResponseDeserializeFn(responseDeserializeFn) {
|
if (eventType == 'data') {
|
||||||
this.responseDeserializeFn_ = responseDeserializeFn;
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @override
|
|
||||||
* @export
|
|
||||||
*/
|
|
||||||
cancel() {
|
|
||||||
this.aborted_ = true;
|
|
||||||
this.xhr_.abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse HTTP headers
|
* Register a callbackl to parse the response
|
||||||
*
|
*
|
||||||
* @private
|
* @param {function(?):!RESPONSE} responseDeserializeFn The deserialize
|
||||||
* @param {string} str The raw http header string
|
* function for the proto
|
||||||
* @return {!Object} The header:value pairs
|
*/
|
||||||
*/
|
GrpcWebClientReadableStream.prototype.setResponseDeserializeFn =
|
||||||
parseHttp1Headers_(str) {
|
function(responseDeserializeFn) {
|
||||||
const chunks = str.trim().split('\r\n');
|
this.responseDeserializeFn_ = responseDeserializeFn;
|
||||||
const headers = {};
|
};
|
||||||
for (let i = 0; i < chunks.length; i++) {
|
|
||||||
const 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
|
* @override
|
||||||
* @param {!RESPONSE} data The data to send back
|
*/
|
||||||
*/
|
GrpcWebClientReadableStream.prototype.cancel = function() {
|
||||||
sendDataCallbacks_(data) {
|
this.xhr_.abort();
|
||||||
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
|
* Parse HTTP headers
|
||||||
* @param {!Metadata} metadata The metadata to send back
|
*
|
||||||
*/
|
* @private
|
||||||
sendMetadataCallbacks_(metadata) {
|
* @param {string} str The raw http header string
|
||||||
for (let i = 0; i < this.onMetadataCallbacks_.length; i++) {
|
* @return {!Object} The header:value pairs
|
||||||
this.onMetadataCallbacks_[i](metadata);
|
*/
|
||||||
}
|
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;
|
||||||
/**
|
};
|
||||||
* @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]();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -49,210 +49,71 @@ const asserts = goog.require('goog.asserts');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default grpc-web stream parser.
|
* The default grpc-web stream parser.
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @struct
|
||||||
* @implements {StreamParser}
|
* @implements {StreamParser}
|
||||||
* @final
|
* @final
|
||||||
*/
|
*/
|
||||||
class GrpcWebStreamParser {
|
const GrpcWebStreamParser = function() {
|
||||||
constructor() {
|
/**
|
||||||
/**
|
* The current error message, if any.
|
||||||
* The current error message, if any.
|
* @private {?string}
|
||||||
* @private {?string}
|
*/
|
||||||
*/
|
this.errorMessage_ = null;
|
||||||
this.errorMessage_ = null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The currently buffered result (parsed messages).
|
|
||||||
* @private {!Array<!Object>}
|
|
||||||
*/
|
|
||||||
this.result_ = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current position in the streamed data.
|
|
||||||
* @private {number}
|
|
||||||
*/
|
|
||||||
this.streamPos_ = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current parser state.
|
|
||||||
* @private {number}
|
|
||||||
*/
|
|
||||||
this.state_ = Parser.State_.INIT;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The current frame byte being parsed
|
|
||||||
* @private {number}
|
|
||||||
*/
|
|
||||||
this.frame_ = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The length of the proto message being parsed.
|
|
||||||
* @private {number}
|
|
||||||
*/
|
|
||||||
this.length_ = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Count of processed length bytes.
|
|
||||||
* @private {number}
|
|
||||||
*/
|
|
||||||
this.countLengthBytes_ = 0;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Count of processed message bytes.
|
|
||||||
* @private {number}
|
|
||||||
*/
|
|
||||||
this.countMessageBytes_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* The currently buffered result (parsed messages).
|
||||||
|
* @private {!Array<!Object>}
|
||||||
*/
|
*/
|
||||||
isInputValid() {
|
this.result_ = [];
|
||||||
return this.state_ != Parser.State_.INVALID;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* The current position in the streamed data.
|
||||||
|
* @private {number}
|
||||||
*/
|
*/
|
||||||
getErrorMessage() {
|
this.streamPos_ = 0;
|
||||||
return this.errorMessage_;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @override
|
* The current parser state.
|
||||||
* @return {boolean}
|
* @private {number}
|
||||||
*/
|
*/
|
||||||
acceptsBinaryInput() {
|
this.state_ = Parser.State_.INIT;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse the new input.
|
* The current frame byte being parsed
|
||||||
*
|
* @private {number}
|
||||||
* 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) {
|
this.frame_ = 0;
|
||||||
asserts.assert(
|
|
||||||
input instanceof Array || input instanceof ArrayBuffer ||
|
|
||||||
input instanceof Uint8Array);
|
|
||||||
|
|
||||||
var parser = this;
|
/**
|
||||||
var inputBytes;
|
* The length of the proto message being parsed.
|
||||||
var pos = 0;
|
* @private {number}
|
||||||
|
*/
|
||||||
|
this.length_ = 0;
|
||||||
|
|
||||||
if (input instanceof Uint8Array || input instanceof Array) {
|
/**
|
||||||
inputBytes = input;
|
* Count of processed length bytes.
|
||||||
} else {
|
* @private {number}
|
||||||
inputBytes = new Uint8Array(input);
|
*/
|
||||||
}
|
this.countLengthBytes_ = 0;
|
||||||
|
|
||||||
while (pos < inputBytes.length) {
|
/**
|
||||||
switch (parser.state_) {
|
* Raw bytes of the current message. Uses Uint8Array by default. Falls back to
|
||||||
case Parser.State_.INVALID: {
|
* native array when Uint8Array is unsupported.
|
||||||
parser.error_(inputBytes, pos, 'stream already broken');
|
* @private {?Uint8Array|?Array<number>}
|
||||||
break;
|
*/
|
||||||
}
|
this.messageBuffer_ = null;
|
||||||
case Parser.State_.INIT: {
|
|
||||||
processFrameByte(inputBytes[pos]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Parser.State_.LENGTH: {
|
|
||||||
processLengthByte(inputBytes[pos]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case Parser.State_.MESSAGE: {
|
|
||||||
processMessageByte(inputBytes[pos]);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
throw new Error('unexpected parser state: ' + parser.state_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.streamPos_++;
|
/**
|
||||||
pos++;
|
* Count of processed message bytes.
|
||||||
}
|
* @private {number}
|
||||||
|
*/
|
||||||
var msgs = parser.result_;
|
this.countMessageBytes_ = 0;
|
||||||
parser.result_ = [];
|
};
|
||||||
return msgs.length > 0 ? msgs : null;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} b A frame byte to process
|
|
||||||
*/
|
|
||||||
function processFrameByte(b) {
|
|
||||||
if (b == FrameType.DATA) {
|
|
||||||
parser.frame_ = b;
|
|
||||||
} else if (b == FrameType.TRAILER) {
|
|
||||||
parser.frame_ = b;
|
|
||||||
} else {
|
|
||||||
parser.error_(inputBytes, pos, 'invalid frame byte');
|
|
||||||
}
|
|
||||||
|
|
||||||
parser.state_ = Parser.State_.LENGTH;
|
|
||||||
parser.length_ = 0;
|
|
||||||
parser.countLengthBytes_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} b A length byte to process
|
|
||||||
*/
|
|
||||||
function processLengthByte(b) {
|
|
||||||
parser.countLengthBytes_++;
|
|
||||||
parser.length_ = (parser.length_ << 8) + b;
|
|
||||||
|
|
||||||
if (parser.countLengthBytes_ == 4) { // no more length byte
|
|
||||||
parser.state_ = Parser.State_.MESSAGE;
|
|
||||||
parser.countMessageBytes_ = 0;
|
|
||||||
if (typeof Uint8Array !== 'undefined') {
|
|
||||||
parser.messageBuffer_ = new Uint8Array(parser.length_);
|
|
||||||
} else {
|
|
||||||
parser.messageBuffer_ = new Array(parser.length_);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parser.length_ == 0) { // empty message
|
|
||||||
finishMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} b A message byte to process
|
|
||||||
*/
|
|
||||||
function processMessageByte(b) {
|
|
||||||
parser.messageBuffer_[parser.countMessageBytes_++] = b;
|
|
||||||
if (parser.countMessageBytes_ == parser.length_) {
|
|
||||||
finishMessage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finishes up building the current message and resets parser state
|
|
||||||
*/
|
|
||||||
function finishMessage() {
|
|
||||||
var message = {};
|
|
||||||
message[parser.frame_] = parser.messageBuffer_;
|
|
||||||
parser.result_.push(message);
|
|
||||||
parser.state_ = Parser.State_.INIT;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const Parser = GrpcWebStreamParser;
|
var Parser = GrpcWebStreamParser;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -272,14 +133,29 @@ Parser.State_ = {
|
||||||
* @enum {number}
|
* @enum {number}
|
||||||
*/
|
*/
|
||||||
GrpcWebStreamParser.FrameType = {
|
GrpcWebStreamParser.FrameType = {
|
||||||
DATA: 0x00, // expecting a data frame
|
DATA: 0x00, // expecting a data frame
|
||||||
TRAILER: 0x80, // expecting a trailer frame
|
TRAILER: 0x80, // expecting a trailer frame
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
var FrameType = GrpcWebStreamParser.FrameType;
|
var FrameType = GrpcWebStreamParser.FrameType;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
GrpcWebStreamParser.prototype.isInputValid = function() {
|
||||||
|
return this.state_ != Parser.State_.INVALID;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
GrpcWebStreamParser.prototype.getErrorMessage = function() {
|
||||||
|
return this.errorMessage_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {!Uint8Array|!Array<number>} inputBytes The current input buffer
|
* @param {!Uint8Array|!Array<number>} inputBytes The current input buffer
|
||||||
|
@ -298,5 +174,106 @@ Parser.prototype.error_ = function(inputBytes, pos, errorMsg) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws {!Error} Throws an error message if the input is invalid.
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
GrpcWebStreamParser.prototype.parse = function(input) {
|
||||||
|
asserts.assert(input instanceof Array || input instanceof ArrayBuffer);
|
||||||
|
|
||||||
|
var parser = this;
|
||||||
|
var inputBytes = (input instanceof Array) ? input : new Uint8Array(input);
|
||||||
|
var pos = 0;
|
||||||
|
|
||||||
|
while (pos < inputBytes.length) {
|
||||||
|
switch (parser.state_) {
|
||||||
|
case Parser.State_.INVALID: {
|
||||||
|
parser.error_(inputBytes, pos, 'stream already broken');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Parser.State_.INIT: {
|
||||||
|
processFrameByte(inputBytes[pos]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Parser.State_.LENGTH: {
|
||||||
|
processLengthByte(inputBytes[pos]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case Parser.State_.MESSAGE: {
|
||||||
|
processMessageByte(inputBytes[pos]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: { throw new Error('unexpected parser state: ' + parser.state_); }
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.streamPos_++;
|
||||||
|
pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
var msgs = parser.result_;
|
||||||
|
parser.result_ = [];
|
||||||
|
return msgs.length > 0 ? msgs : null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} b A frame byte to process
|
||||||
|
*/
|
||||||
|
function processFrameByte(b) {
|
||||||
|
if (b == FrameType.DATA) {
|
||||||
|
parser.frame_ = b;
|
||||||
|
} else if (b == FrameType.TRAILER) {
|
||||||
|
parser.frame_ = b;
|
||||||
|
} else {
|
||||||
|
parser.error_(inputBytes, pos, 'invalid frame byte');
|
||||||
|
}
|
||||||
|
|
||||||
|
parser.state_ = Parser.State_.LENGTH;
|
||||||
|
parser.length_ = 0;
|
||||||
|
parser.countLengthBytes_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} b A length byte to process
|
||||||
|
*/
|
||||||
|
function processLengthByte(b) {
|
||||||
|
parser.countLengthBytes_++;
|
||||||
|
parser.length_ = (parser.length_ << 8) + b;
|
||||||
|
|
||||||
|
if (parser.countLengthBytes_ == 4) { // no more length byte
|
||||||
|
parser.state_ = Parser.State_.MESSAGE;
|
||||||
|
parser.countMessageBytes_ = 0;
|
||||||
|
if (typeof Uint8Array !== 'undefined') {
|
||||||
|
parser.messageBuffer_ = new Uint8Array(parser.length_);
|
||||||
|
} else {
|
||||||
|
parser.messageBuffer_ = new Array(parser.length_);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parser.length_ == 0) { // empty message
|
||||||
|
finishMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} b A message byte to process
|
||||||
|
*/
|
||||||
|
function processMessageByte(b) {
|
||||||
|
parser.messageBuffer_[parser.countMessageBytes_++] = b;
|
||||||
|
if (parser.countMessageBytes_ == parser.length_) {
|
||||||
|
finishMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finishes up building the current message and resets parser state
|
||||||
|
*/
|
||||||
|
function finishMessage() {
|
||||||
|
var message = {};
|
||||||
|
message[parser.frame_] = parser.messageBuffer_;
|
||||||
|
parser.result_.push(message);
|
||||||
|
parser.state_ = Parser.State_.INIT;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
exports = GrpcWebStreamParser;
|
exports = GrpcWebStreamParser;
|
||||||
|
|
|
@ -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
|
|
||||||
};
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -23,19 +23,16 @@
|
||||||
* @author stanleycheung@google.com (Stanley Cheung)
|
* @author stanleycheung@google.com (Stanley Cheung)
|
||||||
*/
|
*/
|
||||||
goog.module('grpc.web.Status');
|
goog.module('grpc.web.Status');
|
||||||
|
|
||||||
goog.module.declareLegacyNamespace();
|
goog.module.declareLegacyNamespace();
|
||||||
|
|
||||||
|
|
||||||
/** @record */
|
|
||||||
function Status() {}
|
|
||||||
|
|
||||||
/** @export {number} */
|
/**
|
||||||
Status.prototype.code;
|
* @typedef {{
|
||||||
|
* code: number,
|
||||||
/** @export {string} */
|
* details: string,
|
||||||
Status.prototype.details;
|
* metadata: (!Object<string, string>|undefined)
|
||||||
|
* }}
|
||||||
/** @export {(!Object<string, string>|undefined)} */
|
*/
|
||||||
Status.prototype.metadata;
|
exports.Status;
|
||||||
|
|
||||||
exports.Status = Status;
|
|
||||||
|
|
|
@ -24,61 +24,62 @@
|
||||||
*/
|
*/
|
||||||
goog.module('grpc.web.StatusCode');
|
goog.module('grpc.web.StatusCode');
|
||||||
|
|
||||||
|
goog.module.declareLegacyNamespace();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* gRPC Status Codes
|
* gRPC Status Codes
|
||||||
* See:
|
* See: https://github.com/grpc/grpc/blob/master/include/grpc%2B%2B/impl/codegen/status_code_enum.h
|
||||||
* https://github.com/grpc/grpc/blob/master/include/grpcpp/impl/codegen/status_code_enum.h
|
|
||||||
* @enum {number}
|
* @enum {number}
|
||||||
*/
|
*/
|
||||||
const StatusCode = {
|
const StatusCode = {
|
||||||
// LINT.IfChange(status_codes)
|
|
||||||
|
|
||||||
// Not an error; returned on success.
|
// Not an error; returned on success.
|
||||||
'OK': 0,
|
OK: 0,
|
||||||
|
|
||||||
// The operation was cancelled (typically by the caller).
|
// 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
|
// 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
|
// 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
|
// 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.
|
// 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
|
// Client specified an invalid argument. Note that this differs from
|
||||||
// FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments that are
|
// FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments that are
|
||||||
// problematic regardless of the state of the system (e.g., a malformed file
|
// problematic regardless of the state of the system (e.g., a malformed file
|
||||||
// name).
|
// name).
|
||||||
'INVALID_ARGUMENT': 3,
|
INVALID_ARGUMENT: 3,
|
||||||
|
|
||||||
// Deadline expired before operation could complete. For operations that
|
// Deadline expired before operation could complete. For operations that
|
||||||
// change the state of the system, this error may be returned even if the
|
// change the state of the system, this error may be returned even if the
|
||||||
// operation has completed successfully. For example, a successful response
|
// operation has completed successfully. For example, a successful response
|
||||||
// from a server could have been delayed long enough for the deadline to
|
// from a server could have been delayed long enough for the deadline to
|
||||||
// expire.
|
// expire.
|
||||||
'DEADLINE_EXCEEDED': 4,
|
DEADLINE_EXCEEDED: 4,
|
||||||
|
|
||||||
// Some requested entity (e.g., file or directory) was not found.
|
// 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
|
// Some entity that we attempted to create (e.g., file or directory) already
|
||||||
// exists.
|
// exists.
|
||||||
'ALREADY_EXISTS': 6,
|
ALREADY_EXISTS: 6,
|
||||||
|
|
||||||
// The caller does not have permission to execute the specified operation.
|
// The caller does not have permission to execute the specified operation.
|
||||||
// PERMISSION_DENIED must not be used for rejections caused by exhausting
|
// PERMISSION_DENIED must not be used for rejections caused by exhausting
|
||||||
// some resource (use RESOURCE_EXHAUSTED instead for those errors).
|
// some resource (use RESOURCE_EXHAUSTED instead for those errors).
|
||||||
// PERMISSION_DENIED must not be used if the caller can not be identified
|
// PERMISSION_DENIED must not be used if the caller can not be identified
|
||||||
// (use UNAUTHENTICATED instead for those errors).
|
// (use UNAUTHENTICATED instead for those errors).
|
||||||
'PERMISSION_DENIED': 7,
|
PERMISSION_DENIED: 7,
|
||||||
|
|
||||||
// The request does not have valid authentication credentials for the
|
// The request does not have valid authentication credentials for the
|
||||||
// operation.
|
// operation.
|
||||||
'UNAUTHENTICATED': 16,
|
UNAUTHENTICATED: 16,
|
||||||
|
|
||||||
// Some resource has been exhausted, perhaps a per-user quota, or perhaps the
|
// Some resource has been exhausted, perhaps a per-user quota, or perhaps the
|
||||||
// entire file system is out of space.
|
// 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
|
// 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
|
// 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
|
// REST Get/Update/Delete on a resource and the resource on the
|
||||||
// server does not match the condition. E.g., conflicting
|
// server does not match the condition. E.g., conflicting
|
||||||
// read-modify-write on the same resource.
|
// read-modify-write on the same resource.
|
||||||
'FAILED_PRECONDITION': 9,
|
FAILED_PRECONDITION: 9,
|
||||||
|
|
||||||
// The operation was aborted, typically due to a concurrency issue like
|
// The operation was aborted, typically due to a concurrency issue like
|
||||||
// sequencer check failures, transaction aborts, etc.
|
// sequencer check failures, transaction aborts, etc.
|
||||||
//
|
//
|
||||||
// See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
|
// See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
|
||||||
// and UNAVAILABLE.
|
// and UNAVAILABLE.
|
||||||
'ABORTED': 10,
|
ABORTED: 10,
|
||||||
|
|
||||||
// Operation was attempted past the valid range. E.g., seeking or reading
|
// Operation was attempted past the valid range. E.g., seeking or reading
|
||||||
// past end of file.
|
// past end of file.
|
||||||
|
@ -120,35 +121,34 @@ const StatusCode = {
|
||||||
// OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific error)
|
// 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
|
// 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.
|
// 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.
|
// 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
|
// Internal errors. Means some invariants expected by underlying System has
|
||||||
// been broken. If you see one of these errors, Something is very broken.
|
// 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
|
// The service is currently unavailable. This is a most likely a transient
|
||||||
// condition and may be corrected by retrying with a backoff.
|
// condition and may be corrected by retrying with a backoff.
|
||||||
//
|
//
|
||||||
// See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
|
// See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
|
||||||
// and UNAVAILABLE.
|
// and UNAVAILABLE.
|
||||||
'UNAVAILABLE': 14,
|
UNAVAILABLE: 14,
|
||||||
|
|
||||||
// Unrecoverable data loss or corruption.
|
// Unrecoverable data loss or corruption.
|
||||||
'DATA_LOSS': 15,
|
DATA_LOSS: 15,
|
||||||
|
|
||||||
// LINT.ThenChange(:status_code_name)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert HTTP Status code to gRPC Status code
|
* Convert HTTP Status code to gRPC Status code
|
||||||
* @param {number} httpStatus HTTP Status Code
|
* @param {number} http_status HTTP Status Code
|
||||||
* @return {!StatusCode} gRPC Status Code
|
* @return {number} gRPC Status Code
|
||||||
*/
|
*/
|
||||||
StatusCode.fromHttpStatus = function(httpStatus) {
|
StatusCode.fromHttpStatus = function(http_status) {
|
||||||
switch (httpStatus) {
|
switch (http_status) {
|
||||||
case 200:
|
case 200:
|
||||||
return StatusCode.OK;
|
return StatusCode.OK;
|
||||||
case 400:
|
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;
|
exports = StatusCode;
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -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;
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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;
|
|
@ -1 +0,0 @@
|
||||||
build_file: "grpc-web/scripts/run_interop_tests.sh"
|
|
|
@ -1 +0,0 @@
|
||||||
build_file: "grpc-web/scripts/kokoro.sh"
|
|
|
@ -1 +1 @@
|
||||||
build_file: "grpc-web/scripts/run_basic_tests.sh"
|
build_file: "grpc-web/scripts/kokoro.sh"
|
||||||
|
|
|
@ -15,13 +15,15 @@
|
||||||
* limitations under the License.
|
* 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();
|
namespace grpc {
|
||||||
inner1.setValue(123);
|
namespace gateway {
|
||||||
let msgOuter = new MessageOuter();
|
|
||||||
msgOuter.setSomepropList([inner1]);
|
|
||||||
|
|
||||||
export {msgOuter}
|
Backend::Backend() : frontend_(nullptr) {}
|
||||||
|
|
||||||
|
Backend::~Backend() {}
|
||||||
|
|
||||||
|
} // namespace gateway
|
||||||
|
} // namespace grpc
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -15,21 +15,29 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import * as grpcWeb from 'grpc-web';
|
|
||||||
|
|
||||||
import {EchoRequest, EchoResponse} from './generated/echo_pb';
|
#include "net/grpc/gateway/codec/b64_proto_decoder.h"
|
||||||
import {EchoServicePromiseClient} from './generated/echo_grpc_web_pb';
|
|
||||||
|
|
||||||
const echoService = new EchoServicePromiseClient(
|
namespace grpc {
|
||||||
'http://localhost:8080', null, null);
|
namespace gateway {
|
||||||
|
|
||||||
let req = new EchoRequest();
|
B64ProtoDecoder::B64ProtoDecoder() {}
|
||||||
req.setMessage('aaa');
|
|
||||||
|
|
||||||
// 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?
|
inputs()->clear();
|
||||||
let p2 : Promise<void | EchoResponse> = p1.then((response: EchoResponse) => {
|
for (Slice& s : buffer) {
|
||||||
});
|
Append(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ProtoDecoder::Decode();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace gateway
|
||||||
|
} // namespace grpc
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -15,13 +15,17 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
import * as grpcWeb from 'grpc-web';
|
|
||||||
|
|
||||||
import {Integer} from './generated/test03_pb';
|
#include "net/grpc/gateway/codec/decoder.h"
|
||||||
import {MyServiceClient} from './generated/Test02ServiceClientPb';
|
|
||||||
|
|
||||||
const service = new MyServiceClient('http://mydummy.com', null, null);
|
namespace grpc {
|
||||||
const req = new Integer();
|
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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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_
|
|
@ -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
|
|
@ -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
Loading…
Reference in New Issue