mirror of https://github.com/grpc/grpc-web.git
Compare commits
493 Commits
Author | SHA1 | Date |
---|---|---|
|
7ef7bd7efe | |
|
dd96066a52 | |
|
70f2b595ce | |
|
7ea072a94d | |
|
b5ff5d303d | |
|
ecb890429f | |
|
7bd92c665a | |
|
9856bfea5d | |
|
8ba8fbeb5f | |
|
20790fc259 | |
|
132ed2cd94 | |
|
e6909cd284 | |
|
0137f1fc61 | |
|
31e9b61ed8 | |
|
2c39859be8 | |
|
8ab32b945c | |
|
76ef002ca6 | |
|
3da394753a | |
|
ce8aa02c7a | |
|
e91e540fc2 | |
|
cfcc5e39d9 | |
|
a639b4cf26 | |
|
0350052fab | |
|
55b9218d2b | |
|
4aab99f73e | |
|
ea2ba42337 | |
|
83eec72cc3 | |
|
9d2e24462a | |
|
de3557acc3 | |
|
db386b99e6 | |
|
1ab0bdc25b | |
|
49d3b70868 | |
|
3cd7e0d434 | |
|
9cb8524caa | |
|
4d7dc44c2d | |
|
7c52878457 | |
|
9c48d290b2 | |
|
3bfa7641a8 | |
|
d28d3f6814 | |
|
c4f0905288 | |
|
f8fbbe32f9 | |
|
c5c1e29756 | |
|
063bb42d85 | |
|
e4084fd259 | |
|
7b6e8ed491 | |
|
b1297e616a | |
|
2d42fd608d | |
|
60caece154 | |
|
e49389873d | |
|
9cacb7606a | |
|
6d1ee0d3df | |
|
0ec55aaad7 | |
|
6577c66b53 | |
|
e11903b337 | |
|
b0ea9c7c45 | |
|
4974a7b5a9 | |
|
ac17547d78 | |
|
8a123090f8 | |
|
d1d99eb80b | |
|
17e6a81709 | |
|
1e8e8c926b | |
|
903601a426 | |
|
b3d7dbdd5e | |
|
94a98d7ced | |
|
a5bd765d1d | |
|
ccef7bd239 | |
|
39200f6cb0 | |
|
056a1c652f | |
|
e1660d71b4 | |
|
7fe18d1e08 | |
|
e5ebedd3b4 | |
|
3ca2e70edf | |
|
012b226922 | |
|
ef287fa478 | |
|
1779661dde | |
|
35284bfe15 | |
|
a1b706ade0 | |
|
176e831b07 | |
|
c6ef0a55b6 | |
|
58e8c1574f | |
|
8c55021864 | |
|
78313a239a | |
|
0fe8d4d586 | |
|
3102383234 | |
|
ce7d734e8a | |
|
173cb546b7 | |
|
81ce4c52ff | |
|
454fe5f458 | |
|
11370185b6 | |
|
4dbd519bc8 | |
|
eb313c1f3d | |
|
3fcc2a2a8a | |
|
6b1d1e97a9 | |
|
97baed4dbe | |
|
cc1a135855 | |
|
b849db4dfa | |
|
f1d863f2dc | |
|
9f76a56ac9 | |
|
1efe741695 | |
|
53964f3668 | |
|
5831a96b56 | |
|
0368e31aaa | |
|
4835cb6df8 | |
|
4676d14333 | |
|
7f70efec6e | |
|
541e3aed20 | |
|
41f7cc71cf | |
|
3956560ad0 | |
|
a992d943f8 | |
|
f1fe57473f | |
|
627e33718d | |
|
78ddf996d8 | |
|
32fe12459b | |
|
d9a6c7a738 | |
|
a489de6f9e | |
|
a56e7212a3 | |
|
da552fc7ee | |
|
fb3f864a0d | |
|
ef0f44e1ce | |
|
3c74b0da5c | |
|
b24f5ca6ec | |
|
511cc32525 | |
|
78d5ded10f | |
|
6c1a2ceda1 | |
|
9c0169748a | |
|
35c16a9e4e | |
|
b63099d977 | |
|
c5bd9f7061 | |
|
7b944d9dd4 | |
|
5a2ab7a75f | |
|
7afe497d8f | |
|
7984205077 | |
|
247e211031 | |
|
3d7789ab28 | |
|
ecf7512b88 | |
|
b9d865ef4c | |
|
6b046c2fab | |
|
de43d4bbf9 | |
|
201c689222 | |
|
f960b8f89b | |
|
1580f3a1cd | |
|
042ccd160e | |
|
3fc20fbaf5 | |
|
e88fdc809d | |
|
302ef34811 | |
|
45ad6bec6d | |
|
ab4d32e4cf | |
|
e2bae61baf | |
|
367f25a25a | |
|
85a76adcf5 | |
|
5c815503f1 | |
|
2e3e8d2c50 | |
|
eecc629b00 | |
|
1d37ed57bf | |
|
35aaf77cba | |
|
8569c5b554 | |
|
9f20f76411 | |
|
6b6b7ac2ea | |
|
2637f14d91 | |
|
5783b0f657 | |
|
969cc8fce4 | |
|
5de092aa6b | |
|
e444626fd2 | |
|
2b9850e9e0 | |
|
cce2f19faa | |
|
83cb0406c1 | |
|
0c62fe8a3a | |
|
4a4923f928 | |
|
30747b3796 | |
|
f71bbb8c41 | |
|
fecfe1cef4 | |
|
69a16b6033 | |
|
6d786ac023 | |
|
0c56ed1de3 | |
|
3d921fffb7 | |
|
0238da3b6e | |
|
6f5ecb0ad1 | |
|
91702e7461 | |
|
68c1810a37 | |
|
0599c38922 | |
|
0a4b48e2d0 | |
|
71be2dc948 | |
|
9ecc15db8b | |
|
9c1a174e19 | |
|
90e83cd618 | |
|
7ea35cfacc | |
|
222cc8d484 | |
|
d8e039f255 | |
|
fdcff71b47 | |
|
bcdf3520b0 | |
|
85f7751e77 | |
|
1e450e683c | |
|
630ab16f4a | |
|
6ad62b1670 | |
|
675deb6e34 | |
|
5276b1e36d | |
|
e994bb108b | |
|
9d90faacca | |
|
0aac5494fe | |
|
1ac8aa5a63 | |
|
80e410bbc4 | |
|
fb5641e629 | |
|
714501a544 | |
|
e9092acf0f | |
|
916d6cbef4 | |
|
03691eeae4 | |
|
023cad0644 | |
|
9b73daa521 | |
|
25a888c974 | |
|
a913c18158 | |
|
50e456a7cc | |
|
dfa6e478f2 | |
|
f1e3cbff5f | |
|
664fd9c3e9 | |
|
d5550cd52b | |
|
e25afb40de | |
|
6c7cde153d | |
|
e27be18859 | |
|
76c04d6fe9 | |
|
cd059a28b2 | |
|
3cf33f96ff | |
|
4b3959852f | |
|
e0835e2d1c | |
|
95db26456b | |
|
a816d9c56e | |
|
74ea9b3b2a | |
|
550c8d2375 | |
|
62a03ec722 | |
|
01c3421c74 | |
|
88d70ecbdb | |
|
0a283f6c6e | |
|
4f5a803f34 | |
|
4e470884bd | |
|
0d747ec02c | |
|
65b43d9971 | |
|
c470eaa0f5 | |
|
a8416b5e7a | |
|
daf7cc8f17 | |
|
fdbfdb1aeb | |
|
c9b2e34e57 | |
|
670326124a | |
|
8c96b8cee3 | |
|
7a93e7f93e | |
|
d32a30a5a6 | |
|
6e632b4183 | |
|
b89d8f122c | |
|
b80a65f6e6 | |
|
6900db20c0 | |
|
aadbdb7e3b | |
|
6b99a37519 | |
|
05029f3d00 | |
|
a055960f80 | |
|
f516ccbf3c | |
|
721afdf958 | |
|
de0f650a72 | |
|
825dd2a9ee | |
|
9d96b7a4b8 | |
|
504c43ecb0 | |
|
7f16bffd79 | |
|
8ff7b57ac7 | |
|
c963bcfec5 | |
|
2391e1cc6b | |
|
9ad8ba64c7 | |
|
1fda10af5f | |
|
569d92f528 | |
|
c73f5f40b9 | |
|
c7dedab92c | |
|
7293d9d421 | |
|
39753a38ee | |
|
5c1b6c7060 | |
|
5864e5dd80 | |
|
3487a96c9c | |
|
d6f959b17b | |
|
fffbf2df1d | |
|
36c3064c68 | |
|
781f53da94 | |
|
0b214401e7 | |
|
df81680d5e | |
|
fa21417814 | |
|
cc6cdfb428 | |
|
b62d3629c2 | |
|
319c2c6836 | |
|
1dffd21b6a | |
|
499f644a8b | |
|
621dd26180 | |
|
17fbe35265 | |
|
ea88b10600 | |
|
0bf05fd26a | |
|
acdd180c7a | |
|
e908cd0f60 | |
|
3eccf72fbc | |
|
289ec8020f | |
|
39f7736410 | |
|
b0ea6a1e55 | |
|
6e2a1877da | |
|
d2858c7440 | |
|
aca18bc2bb | |
|
eeb65e66f9 | |
|
1bcdae540a | |
|
2badafa034 | |
|
fe3617c511 | |
|
d2f9e0a1eb | |
|
cc5cbf6e40 | |
|
508f50a32e | |
|
40aeadef9a | |
|
87209cb94b | |
|
e100cfa712 | |
|
b7fb330e3e | |
|
40cbb49772 | |
|
16a54fa05a | |
|
7f9f7e8e5a | |
|
410ca10711 | |
|
ebb0ec6549 | |
|
4206f4a546 | |
|
63d78243e9 | |
|
dc25fa759a | |
|
d8b362128c | |
|
9ce4ea51b4 | |
|
edaccf6a9b | |
|
086e1cf706 | |
|
26e6df7005 | |
|
cdc8ef19ac | |
|
61cb3e4218 | |
|
48f547f4bb | |
|
7b3f343ff0 | |
|
9a672c93f3 | |
|
aff8c3d6de | |
|
69f23cb5cf | |
|
c90ae6b329 | |
|
764d5bcd18 | |
|
4a5b5b3da6 | |
|
b92360af38 | |
|
d03435fc6e | |
|
55ebde4719 | |
|
5c65dc4a2c | |
|
03b73fbe45 | |
|
3fccf33401 | |
|
4bedc90a84 | |
|
0e45b53c82 | |
|
80358bbeaa | |
|
6272637894 | |
|
3ff3d6f153 | |
|
779167024d | |
|
37636129a0 | |
|
a96b958120 | |
|
0f79fb4366 | |
|
fe2ea6b351 | |
|
ef8adb3df4 | |
|
b0437e7014 | |
|
6bd0db08e4 | |
|
7d9492b5ac | |
|
1c2a0b75f1 | |
|
c22f57497b | |
|
401e54063c | |
|
e38d7bb6c0 | |
|
bf29f0272b | |
|
bc3394170d | |
|
ab6ff71786 | |
|
c284b1533a | |
|
f0e6074dae | |
|
06bda9256e | |
|
5def18f94c | |
|
73b388c0c5 | |
|
7f0bcc916b | |
|
c6af7c6130 | |
|
2fa4270334 | |
|
192fef8909 | |
|
f16acc0a0d | |
|
8113edeeff | |
|
5ee2126d85 | |
|
ea4f1aa637 | |
|
1711674d66 | |
|
9365fd62fc | |
|
fd8ee6b8bf | |
|
5df306a8d6 | |
|
ab906036ea | |
|
6a3b282fb0 | |
|
e2f999a0df | |
|
d69f188c9e | |
|
36598a2ab1 | |
|
3b76ca5810 | |
|
513a578f7f | |
|
8b501a96f4 | |
|
365783307e | |
|
2ae8118d78 | |
|
86d67cf8e1 | |
|
8a01f3022a | |
|
a651f78e67 | |
|
8a13b6f7db | |
|
ab242d2bb9 | |
|
66cab4cd35 | |
|
952d0f5869 | |
|
3568cde5e9 | |
|
dad250d37e | |
|
e24206f1fb | |
|
6c6f7748a1 | |
|
a3174cb1d0 | |
|
885dfbc546 | |
|
04f78beb36 | |
|
d74c3f0abf | |
|
ed2c747a34 | |
|
3e92de6ef0 | |
|
db6cd68df9 | |
|
10828d7ccc | |
|
0c24e95a38 | |
|
d16364954a | |
|
cee964f746 | |
|
a8e54b3885 | |
|
b8bbb459d7 | |
|
968b3d2bb2 | |
|
9e666831d1 | |
|
81426015b3 | |
|
d7fac321b6 | |
|
671255e706 | |
|
8d90ec5dcb | |
|
c227d3d8d8 | |
|
7a5672712b | |
|
a4d898dcfe | |
|
15351a0401 | |
|
ffe8e9c903 | |
|
aadfb92bdb | |
|
91177052a5 | |
|
591d16c47f | |
|
9593703052 | |
|
3f1de33059 | |
|
556c9b30c6 | |
|
97851b40f8 | |
|
0de9635db4 | |
|
ee21d8871e | |
|
8d538a1ac4 | |
|
d8ee2d9d24 | |
|
ef94d2e7e0 | |
|
f9bfe720b5 | |
|
cb3499c967 | |
|
5852e276e0 | |
|
626ce9702b | |
|
ab82d7d133 | |
|
459487e050 | |
|
aecdc2c96c | |
|
c0d3a1d0e5 | |
|
cfe789f4f0 | |
|
84e0ea356e | |
|
95aba7c4d2 | |
|
3c869187d0 | |
|
514edc7e27 | |
|
194c97e650 | |
|
5a296841ce | |
|
cc7dca23a2 | |
|
d139a6ad99 | |
|
b75825f660 | |
|
73358722d8 | |
|
30f8b0e183 | |
|
edb21bf544 | |
|
347cea046c | |
|
ec4d1480fa | |
|
35f2abed45 | |
|
4e23d78d4b | |
|
4aaa044916 | |
|
aa1bfccf4b | |
|
1173a85e7f | |
|
9fd4a8d9b9 | |
|
81350d639c | |
|
01ed7060f6 | |
|
4feb57a913 | |
|
8d7e89d34e | |
|
66139ecf13 | |
|
bb3624ea8d | |
|
c2536577a6 | |
|
68eeabaf89 | |
|
bca630832e | |
|
f181d1efae | |
|
3d280c34be | |
|
7852f2a2df | |
|
42e654c912 | |
|
09604c4837 | |
|
1bbfb214c5 | |
|
a24bcf46da | |
|
6fe20c7092 | |
|
d8a828a92a | |
|
30ed257c49 | |
|
fab336348d | |
|
b2d8b34c5e | |
|
7bf901ae7a | |
|
8bcf950813 | |
|
3ecf2939e9 | |
|
3401083c03 | |
|
e03f5ddb63 | |
|
7f64402965 | |
|
63dac016bd | |
|
def314aec4 | |
|
f28d6cb9ed | |
|
5d104af9a8 | |
|
81c07aad88 |
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
# 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:
|
||||
- //...
|
|
@ -0,0 +1,2 @@
|
|||
# //third_party conatins git submodules.
|
||||
third_party/
|
|
@ -0,0 +1,14 @@
|
|||
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
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
**/dist
|
||||
**/node_modules
|
||||
packages/grpc-web/generated
|
|
@ -0,0 +1,42 @@
|
|||
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/
|
|
@ -0,0 +1,32 @@
|
|||
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*
|
|
@ -0,0 +1,48 @@
|
|||
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*
|
|
@ -0,0 +1,37 @@
|
|||
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*
|
|
@ -0,0 +1,36 @@
|
|||
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,3 +1,4 @@
|
|||
.vscode
|
||||
bazel-bin
|
||||
bazel-genfiles
|
||||
bazel-grpc-web
|
||||
|
@ -5,3 +6,10 @@ bazel-out
|
|||
bazel-testlogs
|
||||
*.o
|
||||
protoc-gen-*
|
||||
.DS_Store
|
||||
target
|
||||
.project
|
||||
.classpath
|
||||
.settings
|
||||
zig-out
|
||||
zig-cache
|
||||
|
|
|
@ -1,12 +1,3 @@
|
|||
[submodule "third_party/grpc"]
|
||||
path = third_party/grpc
|
||||
url = https://github.com/grpc/grpc.git
|
||||
[submodule "third_party/openssl"]
|
||||
path = third_party/openssl
|
||||
url = https://github.com/openssl/openssl.git
|
||||
[submodule "third_party/nginx/src"]
|
||||
path = third_party/nginx/src
|
||||
url = https://nginx.googlesource.com/nginx
|
||||
[submodule "third_party/closure-library"]
|
||||
path = third_party/closure-library
|
||||
url = https://github.com/google/closure-library.git
|
||||
[submodule "third_party/protobuf"]
|
||||
path = third_party/protobuf
|
||||
url = https://github.com/protocolbuffers/protobuf.git
|
||||
|
|
|
@ -0,0 +1,266 @@
|
|||
[//]: # (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,6 +3,10 @@
|
|||
We definitely welcome patches and contribution to gRPC-Web! Here is some guideline
|
||||
and information about how to do so.
|
||||
|
||||
Please read the gRPC
|
||||
organization's [governance rules](https://github.com/grpc/grpc-community/blob/master/governance.md)
|
||||
and [contribution guidelines](https://github.com/grpc/grpc-community/blob/master/CONTRIBUTING.md) before proceeding.
|
||||
|
||||
## Getting started
|
||||
|
||||
### Legal requirements
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
This repository is governed by the gRPC organization's [governance rules](https://github.com/grpc/grpc-community/blob/master/governance.md).
|
|
@ -0,0 +1,16 @@
|
|||
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)
|
|
@ -0,0 +1,24 @@
|
|||
"""
|
||||
A bazel module for the grpc-web project.
|
||||
|
||||
Visit https://grpc.io/ and https://github.com/grpc/grpc-web for
|
||||
more information about the project.
|
||||
"""
|
||||
|
||||
module(
|
||||
name = "grpc-web",
|
||||
version = "1.6.0",
|
||||
compatibility_level = 1,
|
||||
repo_name = "com_github_grpc_grpc_web",
|
||||
)
|
||||
|
||||
bazel_dep(name = "protobuf", version = "27.1", repo_name = "com_google_protobuf")
|
||||
bazel_dep(name = "grpc", version = "1.65.0", repo_name = "com_github_grpc_grpc")
|
||||
bazel_dep(name = "rules_cc", version = "0.0.2")
|
||||
bazel_dep(name = "rules_proto", version = "6.0.2")
|
||||
|
||||
# Needed to resolve https://github.com/bazelbuild/bazel-central-registry/issues/2538.
|
||||
single_version_override(
|
||||
module_name = "grpc-java",
|
||||
version = "1.64.0",
|
||||
)
|
File diff suppressed because it is too large
Load Diff
114
Makefile
114
Makefile
|
@ -1,119 +1,13 @@
|
|||
OS := $(shell uname)
|
||||
CC := g++
|
||||
ROOT_DIR := $(shell pwd)
|
||||
GRPC_GATEWAY_PROTOS := $(ROOT_DIR)/net/grpc/gateway/protos
|
||||
PROTO_INC := $(ROOT_DIR)/third_party/grpc/third_party/protobuf/include
|
||||
PROTO_SRC := $(ROOT_DIR)/third_party/grpc/third_party/protobuf/src
|
||||
PROTO_LIB := $(PROTO_SRC)/.libs
|
||||
PROTOC := $(PROTO_SRC)/protoc
|
||||
GRPC_INC := $(ROOT_DIR)/third_party/grpc/include
|
||||
GRPC_SRC := $(ROOT_DIR)/third_party/grpc
|
||||
GRPC_LIB := $(ROOT_DIR)/third_party/grpc/libs/opt
|
||||
|
||||
all: clean 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/*
|
||||
all: clean
|
||||
|
||||
plugin:
|
||||
cd "$(ROOT_DIR)"/javascript/net/grpc/web && make
|
||||
cd "$(ROOT_DIR)"/javascript/net/grpc/web/generator && make
|
||||
|
||||
install-plugin:
|
||||
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
|
||||
cd "$(ROOT_DIR)"/javascript/net/grpc/web/generator && make install
|
||||
|
||||
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)"/javascript/net/grpc/web/generator && make clean
|
||||
cd "$(ROOT_DIR)"
|
||||
|
|
270
README.md
270
README.md
|
@ -1,26 +1,27 @@
|
|||
## Overview
|
||||
# gRPC Web · [](https://www.npmjs.com/package/grpc-web)
|
||||
|
||||
gRPC-Web provides a Javascript library that lets browser clients access a gRPC
|
||||
service. You can find out much more about gRPC in its own
|
||||
[website](https://grpc.io).
|
||||
A JavaScript implementation of [gRPC][] for browser clients. For more information,
|
||||
including a **quick start**, see the [gRPC-web documentation][grpc-web-docs].
|
||||
|
||||
gRPC-Web is now Generally Available, and considered stable enough for production
|
||||
use.
|
||||
gRPC-web clients connect to gRPC services via a special proxy; by default,
|
||||
gRPC-web uses [Envoy][].
|
||||
|
||||
gRPC-Web clients connect to gRPC services via a special gateway proxy: the
|
||||
current version of the library uses [Envoy](https://www.envoyproxy.io/) by
|
||||
default, in which gRPC-Web support is built-in.
|
||||
In the future, we expect gRPC-web to be supported in language-specific web
|
||||
frameworks for languages such as Python, Java, and Node. For details, see the
|
||||
[roadmap](doc/roadmap.md).
|
||||
|
||||
In the future, we expect gRPC-Web to be supported in language-specific Web
|
||||
frameworks, such as Python, Java, and Node. See the
|
||||
[roadmap](https://github.com/grpc/grpc-web/blob/master/ROADMAP.md) doc.
|
||||
## Streaming Support
|
||||
gRPC-web currently supports 2 RPC modes:
|
||||
- Unary RPCs ([example](#make-a-unary-rpc-call))
|
||||
- Server-side Streaming RPCs ([example](#server-side-streaming)) (NOTE: Only when [`grpcwebtext`](#wire-format-mode) mode is used.)
|
||||
|
||||
Client-side and Bi-directional streaming is not currently supported (see [streaming roadmap](doc/streaming-roadmap.md)).
|
||||
|
||||
## Quick Start Guide: Hello World
|
||||
## Quick Start
|
||||
|
||||
You can follow the [Hello World Guide][] to get started with gRPC-Web quickly.
|
||||
Eager to get started? Try the [Hello World example][]. From this example, you'll
|
||||
learn how to do the following:
|
||||
|
||||
From the guide, you will learn how to
|
||||
- Define your service using protocol buffers
|
||||
- Implement a simple gRPC Service using NodeJS
|
||||
- Configure the Envoy proxy
|
||||
|
@ -36,43 +37,84 @@ streaming example.
|
|||
From the repo root directory:
|
||||
|
||||
```sh
|
||||
$ docker-compose pull prereqs common node-server envoy commonjs-client
|
||||
$ docker-compose up -d node-server envoy commonjs-client
|
||||
$ docker-compose pull prereqs node-server envoy commonjs-client
|
||||
$ docker-compose up node-server envoy commonjs-client
|
||||
```
|
||||
|
||||
Open a browser tab, and go to:
|
||||
|
||||
```
|
||||
http://localhost:8081/echotest.html
|
||||
```
|
||||
Open a browser tab, and visit http://localhost:8081/echotest.html.
|
||||
|
||||
To shutdown: `docker-compose down`.
|
||||
|
||||
## Runtime Library
|
||||
|
||||
The gRPC-Web runtime library is available at `npm`:
|
||||
The gRPC-web runtime library is available at `npm`:
|
||||
|
||||
```sh
|
||||
$ npm i grpc-web
|
||||
```
|
||||
|
||||
## Code Generator Plugins
|
||||
|
||||
## Code Generator Plugin
|
||||
### (Prerequisite) 1. Protobuf (`protoc`)
|
||||
|
||||
You can compile the `protoc-gen-grpc-web` protoc plugin from this repo:
|
||||
If you don't already have [`protoc`](https://github.com/protocolbuffers/protobuf)
|
||||
installed, download it first from [here](https://github.com/protocolbuffers/protobuf/releases) and install it on your PATH.
|
||||
|
||||
If you use Homebrew (on macOS), you could run:
|
||||
|
||||
```sh
|
||||
$ sudo make install-plugin
|
||||
brew install protobuf
|
||||
```
|
||||
|
||||
If you don't already have `protoc` installed, you may have to do this first:
|
||||
### (Prerequisite) 2. Protobuf-javascript (`protoc-gen-js`)
|
||||
|
||||
If you don't have [`protoc-gen-js`](https://github.com/protocolbuffers/protobuf-javascript) installed, download it from [protocolbuffers/protobuf-javascript](https://github.com/protocolbuffers/protobuf-javascript/releases) and install it on your PATH.
|
||||
|
||||
Or, use the [third-party](https://www.npmjs.com/package/protoc-gen-js) NPM installer:
|
||||
|
||||
```
|
||||
npm install -g protoc-gen-js
|
||||
```
|
||||
|
||||
### 3. Install gRPC-Web Code Generator
|
||||
|
||||
You can download the `protoc-gen-grpc-web` protoc plugin from our
|
||||
[release](https://github.com/grpc/grpc-web/releases) page:
|
||||
|
||||
Make sure all executables are discoverable from your PATH.
|
||||
|
||||
For example, on MacOS, you can do:
|
||||
|
||||
```sh
|
||||
$ ./scripts/init_submodules.sh
|
||||
$ cd third_party/grpc/third_party/protobuf
|
||||
$ ./autogen.sh && ./configure && make -j8 && sudo make install
|
||||
sudo mv protoc-gen-grpc-web-1.5.0-darwin-aarch64 \
|
||||
/usr/local/bin/protoc-gen-grpc-web
|
||||
|
||||
chmod +x /usr/local/bin/protoc-gen-grpc-web
|
||||
```
|
||||
|
||||
### (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
|
||||
|
||||
|
@ -80,15 +122,14 @@ Typically, you will run the following command to generate the proto messages
|
|||
and the service client stub from your `.proto` definitions:
|
||||
|
||||
```sh
|
||||
$ protoc -I=$DIR echo.proto \
|
||||
--js_out=import_style=commonjs:$OUT_DIR \
|
||||
--grpc-web_out=import_style=commonjs,mode=grpcwebtext:$OUT_DIR
|
||||
protoc -I=$DIR echo.proto \
|
||||
--js_out=import_style=commonjs:$OUT_DIR \
|
||||
--grpc-web_out=import_style=commonjs,mode=grpcwebtext:$OUT_DIR
|
||||
```
|
||||
|
||||
You can then use Browserify, Webpack, Closure Compiler, etc. to resolve imports
|
||||
at compile time.
|
||||
|
||||
|
||||
### Import Style
|
||||
|
||||
`import_style=closure`: The default generated code has
|
||||
|
@ -103,15 +144,16 @@ also supported.
|
|||
typings file will also be generated for the protobuf messages and service stub.
|
||||
|
||||
`import_style=typescript`: (Experimental) The service stub will be generated
|
||||
in TypeScript.
|
||||
in TypeScript. See **TypeScript Support** below for information on how to
|
||||
generate TypeScript files.
|
||||
|
||||
**Note: `commonjs+dts` and `typescript` only works with `--grpc-web_out=` import style.**
|
||||
> **Note:** The `commonjs+dts` and `typescript` styles are only supported by
|
||||
`--grpc-web_out=import_style=...`, not by `--js_out=import_style=...`.
|
||||
|
||||
### Wire Format Mode
|
||||
|
||||
For more information about the gRPC-Web wire format, please see the
|
||||
[specification](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md#protocol-differences-vs-grpc-over-http2)
|
||||
here.
|
||||
For more information about the gRPC-web wire format, see the
|
||||
[specification](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md#protocol-differences-vs-grpc-over-http2).
|
||||
|
||||
`mode=grpcwebtext`: The default generated code sends the payload in the
|
||||
`grpc-web-text` format.
|
||||
|
@ -124,21 +166,19 @@ here.
|
|||
|
||||
- `Content-type: application/grpc-web+proto`
|
||||
- Payload are in the binary protobuf format.
|
||||
- Only unary calls are supported for now.
|
||||
|
||||
- Only unary calls are supported.
|
||||
|
||||
## How It Works
|
||||
|
||||
Let's take a look at how gRPC-Web works with a simple example. You can find out
|
||||
Let's take a look at how gRPC-web works with a simple example. You can find out
|
||||
how to build, run and explore the example yourself in
|
||||
[Build and Run the Echo Example](net/grpc/gateway/examples/echo).
|
||||
|
||||
|
||||
### 1. Define your service
|
||||
|
||||
The first step when creating any gRPC service is to define it. Like all gRPC
|
||||
services, gRPC-Web uses
|
||||
[protocol buffers](https://developers.google.com/protocol-buffers/) to define
|
||||
services, gRPC-web uses
|
||||
[protocol buffers](https://developers.google.com/protocol-buffers) to define
|
||||
its RPC service methods and their message request and response types.
|
||||
|
||||
```protobuf
|
||||
|
@ -163,31 +203,30 @@ gateway proxy that allows the client to connect to the server. Our example
|
|||
builds a simple Node gRPC backend server and the Envoy proxy.
|
||||
|
||||
For the Echo service: see the
|
||||
[service implementations](https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/examples/echo/node-server/server.js).
|
||||
[service implementations](net/grpc/gateway/examples/echo/node-server/server.js).
|
||||
|
||||
For the Envoy proxy: see the
|
||||
[config yaml file](https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/examples/echo/envoy.yaml).
|
||||
|
||||
[config yaml file](net/grpc/gateway/examples/echo/envoy.yaml).
|
||||
|
||||
### 3. Write your JS client
|
||||
|
||||
Once the server and gateway are up and running, you can start making gRPC calls
|
||||
from the browser!
|
||||
|
||||
Create your client
|
||||
Create your client:
|
||||
|
||||
```js
|
||||
var echoService = new proto.mypackage.EchoServiceClient(
|
||||
'http://localhost:8080');
|
||||
```
|
||||
|
||||
Make a unary RPC call
|
||||
#### Make a unary RPC call:
|
||||
|
||||
```js
|
||||
var request = new proto.mypackage.EchoRequest();
|
||||
request.setMessage(msg);
|
||||
var metadata = {'custom-header-1': 'value1'};
|
||||
var call = echoService.echo(request, metadata, function(err, response) {
|
||||
echoService.echo(request, metadata, function(err, response) {
|
||||
if (err) {
|
||||
console.log(err.code);
|
||||
console.log(err.message);
|
||||
|
@ -195,14 +234,9 @@ var call = echoService.echo(request, metadata, function(err, response) {
|
|||
console.log(response.getMessage());
|
||||
}
|
||||
});
|
||||
call.on('status', function(status) {
|
||||
console.log(status.code);
|
||||
console.log(status.details);
|
||||
console.log(status.metadata);
|
||||
});
|
||||
```
|
||||
|
||||
Server-side streaming is supported!
|
||||
#### Server-side streaming:
|
||||
|
||||
```js
|
||||
var stream = echoService.serverStreamingEcho(streamRequest, metadata);
|
||||
|
@ -217,10 +251,13 @@ stream.on('status', function(status) {
|
|||
stream.on('end', function(end) {
|
||||
// stream end signal
|
||||
});
|
||||
|
||||
// to close the stream
|
||||
stream.cancel()
|
||||
```
|
||||
|
||||
You can find a more in-depth tutorial from
|
||||
[this page](https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/examples/echo/tutorial.md).
|
||||
For an in-depth tutorial, see [this
|
||||
page](net/grpc/gateway/examples/echo/tutorial.md).
|
||||
|
||||
## Setting Deadline
|
||||
|
||||
|
@ -231,7 +268,7 @@ should be a Unix timestamp, in milliseconds.
|
|||
var deadline = new Date();
|
||||
deadline.setSeconds(deadline.getSeconds() + 1);
|
||||
|
||||
client.sayHelloAfterDelay(request, {deadline: deadline.getTime()},
|
||||
client.sayHelloAfterDelay(request, {deadline: deadline.getTime().toString()},
|
||||
(err, response) => {
|
||||
// err will be populated if the RPC exceeds the deadline
|
||||
...
|
||||
|
@ -249,9 +286,37 @@ either:
|
|||
- `import_style=commonjs+dts`: existing CommonJS style stub + `.d.ts` typings
|
||||
- `import_style=typescript`: full TypeScript output
|
||||
|
||||
Do *not* use `import_style=typescript` for `--js_out`, it will silently be
|
||||
ignored. Instead you should use `--js_out=import_style=commonjs`, or
|
||||
`--js_out=import_style=commonjs,binary` if you are using `mode=grpcweb`. The
|
||||
`--js_out` plugin will generate JavaScript code (`echo_pb.js`), and the
|
||||
`-grpc-web_out` plugin will generate a TypeScript definition file for it
|
||||
(`echo_pb.d.ts`). This is a temporary hack until the `--js_out` supports
|
||||
TypeScript itself.
|
||||
|
||||
For example, this is the command you should use to generate TypeScript code
|
||||
using the binary wire format
|
||||
|
||||
```sh
|
||||
protoc -I=$DIR echo.proto \
|
||||
--js_out=import_style=commonjs,binary:$OUT_DIR \
|
||||
--grpc-web_out=import_style=typescript,mode=grpcweb:$OUT_DIR
|
||||
```
|
||||
|
||||
It will generate the following files:
|
||||
|
||||
* `EchoServiceClientPb.ts` - Generated by `--grpc-web_out`, contains the
|
||||
TypeScript gRPC-web code.
|
||||
* `echo_pb.js` - Generated by `--js_out`, contains the JavaScript Protobuf
|
||||
code.
|
||||
* `echo_pb.d.ts` - Generated by `--grpc-web_out`, contains TypeScript
|
||||
definitions for `echo_pb.js`.
|
||||
|
||||
### Using Callbacks
|
||||
|
||||
```ts
|
||||
import * as grpcWeb from 'grpc-web';
|
||||
import {EchoServiceClient} from './echo_grpc_web_pb';
|
||||
import {EchoServiceClient} from './EchoServiceClientPb';
|
||||
import {EchoRequest, EchoResponse} from './echo_pb';
|
||||
|
||||
const echoService = new EchoServiceClient('http://localhost:8080', null, null);
|
||||
|
@ -260,7 +325,7 @@ const request = new EchoRequest();
|
|||
request.setMessage('Hello World!');
|
||||
|
||||
const call = echoService.echo(request, {'custom-header-1': 'value1'},
|
||||
(err: grpcWeb.Error, response: EchoResponse) => {
|
||||
(err: grpcWeb.RpcError, response: EchoResponse) => {
|
||||
console.log(response.getMessage());
|
||||
});
|
||||
call.on('status', (status: grpcWeb.Status) => {
|
||||
|
@ -268,39 +333,72 @@ call.on('status', (status: grpcWeb.Status) => {
|
|||
});
|
||||
```
|
||||
|
||||
See a full TypeScript example
|
||||
[here](https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/examples/echo/ts-example/client.ts).
|
||||
(See [here](https://github.com/grpc/grpc-web/blob/4d7dc44c2df522376394d3e3315b7ab0e010b0c5/packages/grpc-web/index.d.ts#L29-L39) full list of possible `.on(...)` callbacks)
|
||||
|
||||
## Proxy Interoperability
|
||||
### (Option) Using Promises (Limited features)
|
||||
|
||||
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.
|
||||
> **NOTE:** It is not possible to access the `.on(...)` callbacks (e.g. for `metadata` and `status`) when Promise is used.
|
||||
|
||||
```sh
|
||||
$ docker-compose up -d node-server envoy commonjs-client
|
||||
```ts
|
||||
// Create a Promise client instead
|
||||
const echoService = new EchoServicePromiseClient('http://localhost:8080', null, null);
|
||||
|
||||
... (same as above)
|
||||
|
||||
this.echoService.echo(request, {'custom-header-1': 'value1'})
|
||||
.then((response: EchoResponse) => {
|
||||
console.log(`Received response: ${response.getMessage()}`);
|
||||
}).catch((err: grpcWeb.RpcError) => {
|
||||
console.log(`Received error: ${err.code}, ${err.message}`);
|
||||
});
|
||||
```
|
||||
|
||||
An alternative is to build Nginx that comes with this repository.
|
||||
For the full TypeScript example, see
|
||||
[ts-example/client.ts](net/grpc/gateway/examples/echo/ts-example/client.ts) with the [instructions](net/grpc/gateway/examples/echo/ts-example) to run.
|
||||
|
||||
```sh
|
||||
$ docker-compose up -d node-server nginx commonjs-client
|
||||
```
|
||||
## Custom Interceptors
|
||||
|
||||
You can also try this
|
||||
[gRPC-Web Go Proxy](https://github.com/improbable-eng/grpc-web/tree/master/go/grpcwebproxy).
|
||||
Custom interceptors can be implemented and chained, which could be useful for features like auth, retries, etc.
|
||||
|
||||
```sh
|
||||
$ docker-compose up -d node-server grpcwebproxy binary-client
|
||||
```
|
||||
There are 2 types of interceptors ([interfaces](https://github.com/grpc/grpc-web/blob/3cd7e0d43493d4694fed78400e4ad78031d70c09/packages/grpc-web/index.d.ts#L55-L65)):
|
||||
|
||||
## Acknowledgement
|
||||
- `UnaryInterceptor` ([doc](https://grpc.io/blog/grpc-web-interceptor/#stream-interceptor-example), [example](https://github.com/grpc/grpc-web/blob/master/packages/grpc-web/test/tsc-tests/client04.ts)) - Intercept Unary RPCs; can only be used with Promise clients.
|
||||
- `StreamInterceptor` ([doc](https://grpc.io/blog/grpc-web-interceptor/#stream-interceptor-example), [example](https://github.com/grpc/grpc-web/blob/master/packages/grpc-web/test/tsc-tests/client03.ts)) - More versatile; can be used with regular clients.
|
||||
|
||||
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
|
||||
For more details, see [this blog post](https://grpc.io/blog/grpc-web-interceptor/).
|
||||
|
||||
|
||||
[Hello World Guide]:https://github.com/grpc/grpc-web/blob/master/net/grpc/gateway/examples/helloworld/
|
||||
## Ecosystem
|
||||
|
||||
### Proxy Interoperability
|
||||
|
||||
Multiple proxies support the gRPC-web protocol.
|
||||
|
||||
1. The current **default proxy** is [Envoy][], which supports gRPC-web out of the box.
|
||||
|
||||
```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
|
||||
|
|
113
ROADMAP.md
113
ROADMAP.md
|
@ -1,113 +0,0 @@
|
|||
# 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.
|
|
@ -0,0 +1,3 @@
|
|||
# Security Policy
|
||||
|
||||
For information on gRPC Security Policy and reporting potentional security issues, please see [gRPC CVE Process](https://github.com/grpc/proposal/blob/master/P4-grpc-cve-process.md).
|
14
WORKSPACE
14
WORKSPACE
|
@ -1,14 +0,0 @@
|
|||
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()
|
|
@ -1,197 +0,0 @@
|
|||
# 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,20 +1,23 @@
|
|||
# gRPC-Web features for browser (HTML) clients
|
||||
|
||||
Due to browser limitation, gRPC-Web supports a different transport
|
||||
Due to browser limitation, gRPC-Web uses a different transport
|
||||
than the [HTTP/2 based gRPC protocol](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md).
|
||||
The difference between the gRPC-Web
|
||||
protocol and the HTTP/2 based gRPC protocol is specified in the core gRPC repo as [PROTOCOL-WEB](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md).
|
||||
|
||||
In addition to the wire-transport spec, gRPC-Web also supports features that are unique to browser (HTML) clients.
|
||||
This document is the official spec for those features. As the Web platform evolves,
|
||||
This document serves as the official spec for those features. As the Web platform evolves,
|
||||
we expect some of those features will evolve too or become deprecated.
|
||||
|
||||
On the server-side, [Envoy](https://www.envoyproxy.io/) is the official proxy with built-in gRPC-Web support. New features will be implemented in Envoy first. For [in-process gRPC-Web support](https://github.com/grpc/grpc-web/blob/master/doc/in-process-proxy.md), we recommend that the gRPC-Web module implement only a minimum set of features, e.g. to enable local development. Those features are identified as mandatory features in this doc.
|
||||
|
||||
# CORS support
|
||||
|
||||
* Should follow the [CORS spec](https://developer.mozilla.org/en-US/docs/Web/HTTP/Server-Side_Access_Control)
|
||||
* Should follow the [CORS spec](https://developer.mozilla.org/en-US/docs/Web/HTTP/Server-Side_Access_Control) (Mandatory)
|
||||
* Access-Control-Allow-Credentials to allow Authorization headers
|
||||
* Access-Control-Allow-Methods to allow POST and (preflight) OPTIONS only
|
||||
* Access-Control-Allow-Headers to whatever the preflight request carries
|
||||
* Access-Control-Expose-Headers to allow Javascript access to `grpc-status,grpc-message` headers.
|
||||
* The client library is expected to support header overwrites to avoid preflight
|
||||
* https://github.com/whatwg/fetch/issues/210
|
||||
* CSP support to be specified
|
||||
|
@ -23,6 +26,12 @@ we expect some of those features will evolve too or become deprecated.
|
|||
|
||||
A grpc-web gateway is recommended to overwrite the default 200 status code and map any gateway-generated or server-generated error status to standard HTTP status codes (such as 503) when it is possible. This will help with debugging and may also improve security protection for web apps.
|
||||
|
||||
# URL query params
|
||||
|
||||
To enable query-param based routing rules in reverse proxies and to avoid CORS preflight, a grpc-web client may "advertise" certain request data or metadata as query params. The grpc-web proxy module should remove the query params before the request is sent to the downstream gRPC server.
|
||||
|
||||
The actual data in query params is not interpreted by grpc-web libraries. Standard URL encoding rules shoud be followed to encode those query params.
|
||||
|
||||
# Security
|
||||
|
||||
* XSRF, XSS policy to be published
|
|
@ -0,0 +1,30 @@
|
|||
# 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.
|
|
@ -0,0 +1,83 @@
|
|||
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.
|
|
@ -0,0 +1,72 @@
|
|||
# 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!
|
|
@ -0,0 +1,38 @@
|
|||
# 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,19 +5,12 @@ services:
|
|||
context: ./
|
||||
dockerfile: ./net/grpc/gateway/docker/prereqs/Dockerfile
|
||||
image: grpcweb/prereqs
|
||||
common:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: ./net/grpc/gateway/docker/common/Dockerfile
|
||||
depends_on:
|
||||
- prereqs
|
||||
image: grpcweb/common
|
||||
echo-server:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: ./net/grpc/gateway/docker/echo_server/Dockerfile
|
||||
depends_on:
|
||||
- common
|
||||
- prereqs
|
||||
image: grpcweb/echo-server
|
||||
ports:
|
||||
- "9090:9090"
|
||||
|
@ -26,10 +19,17 @@ services:
|
|||
context: ./
|
||||
dockerfile: ./net/grpc/gateway/docker/node_server/Dockerfile
|
||||
depends_on:
|
||||
- common
|
||||
- prereqs
|
||||
image: grpcweb/node-server
|
||||
ports:
|
||||
- "9090:9090"
|
||||
node-interop-server:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: ./net/grpc/gateway/docker/node_interop_server/Dockerfile
|
||||
image: grpcweb/node-interop-server
|
||||
ports:
|
||||
- "7074:7074"
|
||||
envoy:
|
||||
build:
|
||||
context: ./
|
||||
|
@ -39,17 +39,6 @@ services:
|
|||
- "8080:8080"
|
||||
links:
|
||||
- node-server
|
||||
nginx:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: ./net/grpc/gateway/docker/nginx/Dockerfile
|
||||
depends_on:
|
||||
- common
|
||||
image: grpcweb/nginx
|
||||
ports:
|
||||
- "8080:8080"
|
||||
links:
|
||||
- node-server
|
||||
grpcwebproxy:
|
||||
build:
|
||||
context: ./
|
||||
|
@ -64,7 +53,7 @@ services:
|
|||
context: ./
|
||||
dockerfile: ./net/grpc/gateway/docker/commonjs_client/Dockerfile
|
||||
depends_on:
|
||||
- common
|
||||
- prereqs
|
||||
image: grpcweb/commonjs-client
|
||||
ports:
|
||||
- "8081:8081"
|
||||
|
@ -73,7 +62,7 @@ services:
|
|||
context: ./
|
||||
dockerfile: ./net/grpc/gateway/docker/closure_client/Dockerfile
|
||||
depends_on:
|
||||
- common
|
||||
- prereqs
|
||||
image: grpcweb/closure-client
|
||||
ports:
|
||||
- "8081:8081"
|
||||
|
@ -82,7 +71,7 @@ services:
|
|||
context: ./
|
||||
dockerfile: ./net/grpc/gateway/docker/ts_client/Dockerfile
|
||||
depends_on:
|
||||
- common
|
||||
- prereqs
|
||||
image: grpcweb/ts-client
|
||||
ports:
|
||||
- "8081:8081"
|
||||
|
@ -91,7 +80,28 @@ services:
|
|||
context: ./
|
||||
dockerfile: ./net/grpc/gateway/docker/binary_client/Dockerfile
|
||||
depends_on:
|
||||
- common
|
||||
- prereqs
|
||||
image: grpcweb/binary-client
|
||||
ports:
|
||||
- "8081:8081"
|
||||
interop-client:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: ./net/grpc/gateway/docker/interop_client/Dockerfile
|
||||
depends_on:
|
||||
- prereqs
|
||||
image: grpcweb/interop-client
|
||||
ports:
|
||||
- "8081:8081"
|
||||
protoc-plugin:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: ./net/grpc/gateway/docker/protoc_plugin/Dockerfile
|
||||
depends_on:
|
||||
- prereqs
|
||||
image: grpcweb/protoc-plugin
|
||||
jsunit-test:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: ./packages/grpc-web/docker/jsunit-test/Dockerfile
|
||||
image: grpcweb/jsunit-test
|
||||
|
|
|
@ -1,163 +0,0 @@
|
|||
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",
|
||||
],
|
||||
)
|
|
@ -28,75 +28,84 @@ goog.module.declareLegacyNamespace();
|
|||
|
||||
|
||||
const ClientReadableStream = goog.require('grpc.web.ClientReadableStream');
|
||||
const Error = goog.require('grpc.web.Error');
|
||||
|
||||
|
||||
/**
|
||||
* This interface represents a grpc-web client
|
||||
*
|
||||
* @interface
|
||||
*/
|
||||
const AbstractClientBase = function() {};
|
||||
const MethodDescriptor = goog.require('grpc.web.MethodDescriptor');
|
||||
const RpcError = goog.require('grpc.web.RpcError');
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @struct
|
||||
* @template REQUEST, RESPONSE
|
||||
* @param {function(new: RESPONSE, ...)} responseType
|
||||
* @param {function(REQUEST): ?} requestSerializeFn
|
||||
* @param {function(?): RESPONSE} responseDeserializeFn
|
||||
* @final
|
||||
*/
|
||||
AbstractClientBase.MethodInfo = function(
|
||||
responseType,
|
||||
requestSerializeFn,
|
||||
responseDeserializeFn) {
|
||||
/** @const */
|
||||
this.responseType = responseType;
|
||||
/** @const */
|
||||
this.requestSerializeFn = requestSerializeFn;
|
||||
/** @const */
|
||||
this.responseDeserializeFn = responseDeserializeFn;
|
||||
const PromiseCallOptions = function() {};
|
||||
|
||||
/**
|
||||
* An AbortSignal to abort the call.
|
||||
* @type {AbortSignal|undefined}
|
||||
*/
|
||||
PromiseCallOptions.prototype.signal;
|
||||
|
||||
|
||||
/**
|
||||
* This interface represents a grpc-web client
|
||||
* @interface
|
||||
*/
|
||||
const AbstractClientBase = class {
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* @abstract
|
||||
* @template REQUEST, RESPONSE
|
||||
* @param {string} method The method to invoke
|
||||
* @param {REQUEST} requestMessage The request proto
|
||||
* @param {!Object<string, string>} metadata User defined call metadata
|
||||
* @param {!MethodDescriptor<REQUEST, RESPONSE>}
|
||||
* methodDescriptor Information of this RPC method
|
||||
* @param {function(?RpcError, ?)}
|
||||
* callback A callback function which takes (error, RESPONSE or null)
|
||||
* @return {!ClientReadableStream<RESPONSE>}
|
||||
*/
|
||||
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
|
||||
* Even with ?RESPONSE the RESPONSE will still be inferred as
|
||||
* "FooResponse|Null". Use RESPONSE_LEAN to extract out the "FooResponse"
|
||||
* part. See go/closure-ttl.
|
||||
* @template RESPONSE_LEAN :=
|
||||
* cond(isUnknown(RESPONSE), unknown(),
|
||||
* mapunion(RESPONSE, (X) =>
|
||||
* cond(eq(X, 'undefined'), none(),
|
||||
* cond(eq(X, 'null'), none(),
|
||||
* X))))
|
||||
* =:
|
||||
* @param {string} method The method to invoke
|
||||
* @param {REQUEST} request The request proto
|
||||
* @param {!Object<string, string>} metadata User defined call metadata
|
||||
* @param {!AbstractClientBase.MethodInfo<REQUEST, RESPONSE_LEAN>}
|
||||
* methodInfo Information of this RPC method
|
||||
* @param {function(?Error, ?RESPONSE)}
|
||||
* callback A callback function which takes (error, response)
|
||||
* @return {!ClientReadableStream<RESPONSE_LEAN>|undefined}
|
||||
* The Client Readable Stream
|
||||
* @param {string} method
|
||||
* @param {!MethodDescriptor<REQUEST,RESPONSE>} methodDescriptor
|
||||
* @return {string}
|
||||
*/
|
||||
AbstractClientBase.prototype.rpcCall = goog.abstractMethod;
|
||||
function getHostname(method, methodDescriptor) {
|
||||
// method = hostname + methodDescriptor.name(relative path of this method)
|
||||
return method.substr(0, method.length - methodDescriptor.name.length);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
exports = {AbstractClientBase, PromiseCallOptions, getHostname};
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* @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;
|
|
@ -0,0 +1,66 @@
|
|||
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,10 +43,25 @@ const ClientReadableStream = function() {};
|
|||
|
||||
|
||||
/**
|
||||
* Register a callback to handle I/O events.
|
||||
* Register a callback to handle different stream events.
|
||||
*
|
||||
* Available event types for gRPC-Web:
|
||||
* 'data': The 'data' event is emitted when a new response message chunk is
|
||||
* received and successfully handled by gRPC-Web client.
|
||||
* 'status': the google RPC status of the response stream.
|
||||
* 'end': The 'end' event is emitted when all the data have been successfully
|
||||
* consumed from the stream.
|
||||
* 'error': typically, this may occur when an underlying internal failure
|
||||
* happens, or a stream implementation attempts to push an invalid chunk of
|
||||
* data.
|
||||
* 'metadata': the response metadata. Response headers should be read via
|
||||
* 'metadata' callbacks.
|
||||
*
|
||||
* For server-streaming calls. the 'data' and 'status' callbacks (if exist)
|
||||
* will always precede 'metadata', 'error', or 'end' callbacks.
|
||||
*
|
||||
* @param {string} eventType The event type
|
||||
* @param {function(?)} callback The call back to handle the event with
|
||||
* @param {function(?)} callback The callback to handle the event with
|
||||
* an optional input object
|
||||
* @return {!ClientReadableStream} this object
|
||||
*/
|
||||
|
@ -54,6 +69,17 @@ 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.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* @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;
|
|
@ -1,39 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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;
|
|
@ -1,188 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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;
|
|
@ -0,0 +1,12 @@
|
|||
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",
|
||||
],
|
||||
)
|
|
@ -12,10 +12,22 @@
|
|||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
CXX = g++
|
||||
CXX ?= g++
|
||||
CPPFLAGS += -I/usr/local/include -pthread
|
||||
CXXFLAGS += -std=c++11
|
||||
LDFLAGS += -L/usr/local/lib -lprotoc -lprotobuf -lpthread -ldl
|
||||
PREFIX ?= /usr/local
|
||||
MIN_MACOS_VERSION := 10.7 # Supports OS X Lion
|
||||
STATIC ?= yes
|
||||
|
||||
UNAME_S := $(shell uname -s)
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
CXXFLAGS += -stdlib=libc++ -mmacosx-version-min=$(MIN_MACOS_VERSION)
|
||||
else ifeq ($(UNAME_S),Linux)
|
||||
ifeq ($(STATIC),yes)
|
||||
LDFLAGS += -static
|
||||
endif
|
||||
endif
|
||||
|
||||
all: protoc-gen-grpc-web
|
||||
|
||||
|
@ -23,7 +35,8 @@ protoc-gen-grpc-web: grpc_generator.o
|
|||
$(CXX) $^ $(LDFLAGS) -o $@
|
||||
|
||||
install: protoc-gen-grpc-web
|
||||
install protoc-gen-grpc-web /usr/local/bin/protoc-gen-grpc-web
|
||||
mkdir -p $(PREFIX)/bin
|
||||
install protoc-gen-grpc-web $(PREFIX)/bin/protoc-gen-grpc-web
|
||||
|
||||
clean:
|
||||
rm -f *.o protoc-gen-grpc-web
|
|
@ -0,0 +1,215 @@
|
|||
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,13 +16,10 @@
|
|||
*
|
||||
*/
|
||||
/**
|
||||
* @fileoverview gRPC web client Readable Stream
|
||||
* @fileoverview gRPC-Web generic transport interface
|
||||
*
|
||||
* 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
|
||||
* This class provides an abstraction for the underlying transport
|
||||
* implementation underneath the ClientReadableStream layer.
|
||||
*
|
||||
* @author stanleycheung@google.com (Stanley Cheung)
|
||||
*/
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -28,195 +28,384 @@ goog.module('grpc.web.GrpcWebClientBase');
|
|||
goog.module.declareLegacyNamespace();
|
||||
|
||||
|
||||
const AbstractClientBase = goog.require('grpc.web.AbstractClientBase');
|
||||
const ClientOptions = goog.requireType('grpc.web.ClientOptions');
|
||||
const ClientReadableStream = goog.require('grpc.web.ClientReadableStream');
|
||||
const ClientUnaryCallImpl = goog.require('grpc.web.ClientUnaryCallImpl');
|
||||
const GrpcWebClientReadableStream = goog.require('grpc.web.GrpcWebClientReadableStream');
|
||||
const HttpCors = goog.require('goog.net.rpc.HttpCors');
|
||||
const MethodDescriptor = goog.requireType('grpc.web.MethodDescriptor');
|
||||
const Request = goog.require('grpc.web.Request');
|
||||
const RpcError = goog.require('grpc.web.RpcError');
|
||||
const StatusCode = goog.require('grpc.web.StatusCode');
|
||||
const XhrIo = goog.require('goog.net.XhrIo');
|
||||
const googCrypt = goog.require('goog.crypt.base64');
|
||||
const {AbstractClientBase, PromiseCallOptions, getHostname} = goog.require('grpc.web.AbstractClientBase');
|
||||
const {Status} = goog.require('grpc.web.Status');
|
||||
const {StreamInterceptor, UnaryInterceptor} = goog.require('grpc.web.Interceptor');
|
||||
const {toObject} = goog.require('goog.collections.maps');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Base class for gRPC web client using the application/grpc-web wire format
|
||||
* @param {?Object=} opt_options
|
||||
* @constructor
|
||||
* @implements {AbstractClientBase}
|
||||
* @unrestricted
|
||||
*/
|
||||
const GrpcWebClientBase = function(opt_options) {
|
||||
class GrpcWebClientBase {
|
||||
/**
|
||||
* @const
|
||||
* @private {string}
|
||||
* @param {!ClientOptions=} options
|
||||
* @param {!XhrIo=} xhrIo
|
||||
*/
|
||||
this.format_ =
|
||||
goog.getObjectByName('format', opt_options) || "text";
|
||||
constructor(options = {}, xhrIo = undefined) {
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @const
|
||||
* @private {boolean}
|
||||
* @override
|
||||
* @export
|
||||
*/
|
||||
this.suppressCorsPreflight_ =
|
||||
goog.getObjectByName('suppressCorsPreflight', opt_options) || false;
|
||||
};
|
||||
rpcCall(method, requestMessage, metadata, methodDescriptor, callback) {
|
||||
const hostname = getHostname(method, methodDescriptor);
|
||||
const invoker = GrpcWebClientBase.runInterceptors_(
|
||||
(request) => this.startStream_(request, hostname),
|
||||
this.streamInterceptors_);
|
||||
const stream = /** @type {!ClientReadableStream<?>} */ (invoker.call(
|
||||
this, methodDescriptor.createRequest(requestMessage, metadata)));
|
||||
GrpcWebClientBase.setCallback_(stream, callback, false);
|
||||
return new ClientUnaryCallImpl(stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
GrpcWebClientBase.prototype.rpcCall = function(
|
||||
method, request, metadata, methodInfo, callback) {
|
||||
var xhr = this.newXhr_();
|
||||
const stream = this.startStream_(request, hostname);
|
||||
let unaryMetadata;
|
||||
let unaryStatus;
|
||||
let unaryMsg;
|
||||
GrpcWebClientBase.setCallback_(
|
||||
stream,
|
||||
(error, response, status, metadata, unaryResponseReceived) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else if (unaryResponseReceived) {
|
||||
unaryMsg = response;
|
||||
} else if (status) {
|
||||
unaryStatus = status;
|
||||
} else if (metadata) {
|
||||
unaryMetadata = metadata;
|
||||
} else {
|
||||
resolve(request.getMethodDescriptor().createUnaryResponse(
|
||||
unaryMsg, unaryMetadata, unaryStatus));
|
||||
}
|
||||
},
|
||||
true);
|
||||
|
||||
var genericTransportInterface = {
|
||||
xhr: xhr,
|
||||
};
|
||||
var stream = new GrpcWebClientReadableStream(genericTransportInterface);
|
||||
stream.setResponseDeserializeFn(methodInfo.responseDeserializeFn);
|
||||
// Wire up cancellation from the abort signal, if any.
|
||||
if (signal) {
|
||||
signal.addEventListener('abort', () => {
|
||||
stream.cancel();
|
||||
|
||||
stream.on('data', function(response) {
|
||||
callback(null, response);
|
||||
});
|
||||
const error = new RpcError(StatusCode.CANCELLED, 'Aborted');
|
||||
error.cause = /** @type {!AbortSignal} */ (signal).reason;
|
||||
reject(error);
|
||||
});
|
||||
}
|
||||
});
|
||||
const invoker = GrpcWebClientBase.runInterceptors_(
|
||||
initialInvoker, this.unaryInterceptors_);
|
||||
const unaryResponse = /** @type {!Promise<?>} */ (invoker.call(
|
||||
this, methodDescriptor.createRequest(requestMessage, metadata)));
|
||||
return unaryResponse.then((response) => response.getResponseMessage());
|
||||
}
|
||||
|
||||
stream.on('status', function(status) {
|
||||
if (status.code != StatusCode.OK) {
|
||||
callback({
|
||||
code: status.code,
|
||||
message: status.details
|
||||
}, null);
|
||||
/**
|
||||
* @export
|
||||
* @param {string} method The method to invoke
|
||||
* @param {REQUEST} requestMessage The request proto
|
||||
* @param {!Object<string, string>} metadata User defined call metadata
|
||||
* @param {!MethodDescriptor<REQUEST, RESPONSE>} methodDescriptor Information
|
||||
* of this RPC method
|
||||
* @param {?PromiseCallOptions=} options Options for the call
|
||||
* @return {!Promise<RESPONSE>}
|
||||
* @template REQUEST, RESPONSE
|
||||
*/
|
||||
unaryCall(method, requestMessage, metadata, methodDescriptor, options = {}) {
|
||||
return /** @type {!Promise<RESPONSE>}*/ (this.thenableCall(
|
||||
method, requestMessage, metadata, methodDescriptor, options));
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @export
|
||||
*/
|
||||
serverStreaming(method, requestMessage, metadata, methodDescriptor) {
|
||||
const hostname = getHostname(method, methodDescriptor);
|
||||
const invoker = GrpcWebClientBase.runInterceptors_(
|
||||
(request) => this.startStream_(request, hostname),
|
||||
this.streamInterceptors_);
|
||||
return /** @type {!ClientReadableStream<?>} */ (invoker.call(
|
||||
this, methodDescriptor.createRequest(requestMessage, metadata)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @template REQUEST, RESPONSE
|
||||
* @param {!Request<REQUEST, RESPONSE>} request
|
||||
* @param {string} hostname
|
||||
* @return {!ClientReadableStream<RESPONSE>}
|
||||
*/
|
||||
startStream_(request, hostname) {
|
||||
const methodDescriptor = request.getMethodDescriptor();
|
||||
let path = hostname + methodDescriptor.getName();
|
||||
|
||||
const xhr = this.xhrIo_ ? this.xhrIo_ : new XhrIo();
|
||||
xhr.setWithCredentials(this.withCredentials_);
|
||||
|
||||
const genericTransportInterface = {
|
||||
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]);
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('error', function(error) {
|
||||
if (error.code != StatusCode.OK) {
|
||||
callback({
|
||||
code: error.code,
|
||||
message: error.message
|
||||
}, null);
|
||||
this.processHeaders_(xhr);
|
||||
if (this.suppressCorsPreflight_) {
|
||||
const headerObject = toObject(xhr.headers);
|
||||
xhr.headers.clear();
|
||||
path = GrpcWebClientBase.setCorsOverride_(path, headerObject);
|
||||
}
|
||||
});
|
||||
|
||||
xhr.headers.addAll(metadata);
|
||||
this.processHeaders_(xhr);
|
||||
if (this.suppressCorsPreflight_) {
|
||||
var headerObject = xhr.headers.toObject();
|
||||
xhr.headers.clear();
|
||||
method = GrpcWebClientBase.setCorsOverride_(method, headerObject);
|
||||
}
|
||||
|
||||
var serialized = methodInfo.requestSerializeFn(request);
|
||||
var payload = this.encodeRequest_(serialized);
|
||||
if (this.format_ == "text") {
|
||||
payload = googCrypt.encodeByteArray(payload);
|
||||
} else if (this.format_ == "binary") {
|
||||
xhr.setResponseType(XhrIo.ResponseType.ARRAY_BUFFER);
|
||||
}
|
||||
xhr.send(method, 'POST', payload);
|
||||
return stream;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
GrpcWebClientBase.prototype.serverStreaming = function(
|
||||
method, request, metadata, methodInfo) {
|
||||
var xhr = this.newXhr_();
|
||||
|
||||
var genericTransportInterface = {
|
||||
xhr: xhr,
|
||||
};
|
||||
var stream = new GrpcWebClientReadableStream(genericTransportInterface);
|
||||
stream.setResponseDeserializeFn(methodInfo.responseDeserializeFn);
|
||||
|
||||
xhr.headers.addAll(metadata);
|
||||
this.processHeaders_(xhr);
|
||||
if (this.suppressCorsPreflight_) {
|
||||
var headerObject = xhr.headers.toObject();
|
||||
xhr.headers.clear();
|
||||
method = GrpcWebClientBase.setCorsOverride_(method, headerObject);
|
||||
}
|
||||
|
||||
var serialized = methodInfo.requestSerializeFn(request);
|
||||
var payload = this.encodeRequest_(serialized);
|
||||
if (this.format_ == "text") {
|
||||
payload = googCrypt.encodeByteArray(payload);
|
||||
} else if (this.format_ == "binary") {
|
||||
xhr.setResponseType(XhrIo.ResponseType.ARRAY_BUFFER);
|
||||
}
|
||||
xhr.send(method, 'POST', payload);
|
||||
|
||||
return stream;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create a new XhrIo object
|
||||
*
|
||||
* @private
|
||||
* @return {!XhrIo} The created XhrIo object
|
||||
*/
|
||||
GrpcWebClientBase.prototype.newXhr_ = function() {
|
||||
return new XhrIo();
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Encode the grpc-web request
|
||||
*
|
||||
* @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');
|
||||
const requestSerializeFn = methodDescriptor.getRequestSerializeFn();
|
||||
const serialized = requestSerializeFn(request.getRequestMessage());
|
||||
let payload = this.encodeRequest_(serialized);
|
||||
if (this.format_ == 'text') {
|
||||
payload = googCrypt.encodeByteArray(payload);
|
||||
} else if (this.format_ == 'binary') {
|
||||
xhr.setResponseType(XhrIo.ResponseType.ARRAY_BUFFER);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send(path, 'POST', payload);
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @static
|
||||
* @template RESPONSE
|
||||
* @param {!ClientReadableStream<RESPONSE>} stream
|
||||
* @param {function(?RpcError, ?RESPONSE, ?Status=, ?Object<string, string>=, ?boolean)|
|
||||
* function(?RpcError,?RESPONSE)} callback
|
||||
* @param {boolean} useUnaryResponse Pass true to have the client make
|
||||
* multiple calls to the callback, using (error, response, status,
|
||||
* metadata, unaryResponseReceived) arguments. One of error, status,
|
||||
* metadata, or unaryResponseReceived will be truthy to indicate which piece
|
||||
* of information the client is providing in that call. After the stream
|
||||
* ends, it will call the callback an additional time with all falsy
|
||||
* arguments. Pass false to have the client make one call to the callback
|
||||
* using (error, response) arguments.
|
||||
*/
|
||||
static setCallback_(stream, callback, useUnaryResponse) {
|
||||
let isResponseReceived = false;
|
||||
let responseReceived = null;
|
||||
let errorEmitted = false;
|
||||
|
||||
stream.on('data', function(response) {
|
||||
isResponseReceived = true;
|
||||
responseReceived = response;
|
||||
});
|
||||
|
||||
stream.on('error', function(error) {
|
||||
if (error.code != StatusCode.OK && !errorEmitted) {
|
||||
errorEmitted = true;
|
||||
callback(error, null);
|
||||
}
|
||||
});
|
||||
|
||||
stream.on('status', function(status) {
|
||||
if (status.code != StatusCode.OK && !errorEmitted) {
|
||||
errorEmitted = true;
|
||||
callback(
|
||||
{
|
||||
code: status.code,
|
||||
message: status.details,
|
||||
metadata: status.metadata
|
||||
},
|
||||
null);
|
||||
} else if (useUnaryResponse) {
|
||||
callback(null, null, status);
|
||||
}
|
||||
});
|
||||
|
||||
if (useUnaryResponse) {
|
||||
stream.on('metadata', function(metadata) {
|
||||
callback(null, null, null, metadata);
|
||||
});
|
||||
}
|
||||
|
||||
stream.on('end', function() {
|
||||
if (!errorEmitted) {
|
||||
if (!isResponseReceived) {
|
||||
callback({
|
||||
code: StatusCode.UNKNOWN,
|
||||
message: 'Incomplete response',
|
||||
});
|
||||
} else if (useUnaryResponse) {
|
||||
callback(
|
||||
null, responseReceived, null, null,
|
||||
/* unaryResponseReceived= */ true);
|
||||
} else {
|
||||
callback(null, responseReceived);
|
||||
}
|
||||
}
|
||||
if (useUnaryResponse) {
|
||||
callback(null, null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
GrpcWebClientBase.setCorsOverride_ = function(method, headerObject) {
|
||||
return /** @type {string} */ (HttpCors.setHttpHeadersWithOverwriteParam(
|
||||
method, HttpCors.HTTP_HEADERS_PARAM_NAME, headerObject));
|
||||
};
|
||||
|
||||
|
||||
exports = GrpcWebClientBase;
|
||||
|
|
|
@ -18,179 +18,425 @@
|
|||
goog.module('grpc.web.GrpcWebClientBaseTest');
|
||||
goog.setTestOnly('grpc.web.GrpcWebClientBaseTest');
|
||||
|
||||
var GrpcWebClientBase = goog.require('grpc.web.GrpcWebClientBase');
|
||||
var Map = goog.require('goog.structs.Map');
|
||||
var googCrypt = goog.require('goog.crypt.base64');
|
||||
var googEvents = goog.require('goog.events');
|
||||
var testSuite = goog.require('goog.testing.testSuite');
|
||||
const ClientReadableStream = goog.require('grpc.web.ClientReadableStream');
|
||||
const ErrorCode = goog.require('goog.net.ErrorCode');
|
||||
const GrpcWebClientBase = goog.require('grpc.web.GrpcWebClientBase');
|
||||
const MethodDescriptor = goog.require('grpc.web.MethodDescriptor');
|
||||
const ReadyState = goog.require('goog.net.XmlHttp.ReadyState');
|
||||
const Request = goog.requireType('grpc.web.Request');
|
||||
const RpcError = goog.require('grpc.web.RpcError');
|
||||
const StatusCode = goog.require('grpc.web.StatusCode');
|
||||
const XhrIo = goog.require('goog.testing.net.XhrIo');
|
||||
const googCrypt = goog.require('goog.crypt.base64');
|
||||
const testSuite = goog.require('goog.testing.testSuite');
|
||||
const {StreamInterceptor} = goog.require('grpc.web.Interceptor');
|
||||
goog.require('goog.testing.jsunit');
|
||||
|
||||
var REQUEST_BYTES = [1,2,3];
|
||||
var FAKE_METHOD = "fake-method";
|
||||
var PROTO_FIELD_VALUE = "meow";
|
||||
var EXPECTED_HEADERS;
|
||||
var EXPECTED_HEADER_VALUES;
|
||||
var EXPECTED_UNARY_HEADERS = ['Content-Type', 'Accept',
|
||||
'X-User-Agent', 'X-Grpc-Web'];
|
||||
var EXPECTED_UNARY_HEADER_VALUES = ['application/grpc-web-text',
|
||||
'application/grpc-web-text',
|
||||
'grpc-web-javascript/0.1',
|
||||
'1'];
|
||||
var dataCallback;
|
||||
|
||||
// This parses to [ { DATA: [4, 5, 6] }, { TRAILER: "a: b" } ]
|
||||
const DEFAULT_RPC_RESPONSE =
|
||||
new Uint8Array([0, 0, 0, 0, 3, 4, 5, 6, 128, 0, 0, 0, 4, 97, 58, 32, 98]);
|
||||
const DEFAULT_RPC_RESPONSE_DATA = [4, 5, 6];
|
||||
const DEFAULT_UNARY_HEADERS =
|
||||
['Content-Type', 'Accept', 'X-User-Agent', 'X-Grpc-Web'];
|
||||
const DEFAULT_UNARY_HEADER_VALUES = [
|
||||
'application/grpc-web-text',
|
||||
'application/grpc-web-text',
|
||||
'grpc-web-javascript/0.1',
|
||||
'1',
|
||||
];
|
||||
const DEFAULT_RESPONSE_HEADERS = {
|
||||
'Content-Type': 'application/grpc-web-text',
|
||||
};
|
||||
|
||||
testSuite({
|
||||
setUp: function() {
|
||||
googEvents.listen = function(a, b, listener, d, e) {
|
||||
dataCallback = listener;
|
||||
return;
|
||||
};
|
||||
},
|
||||
|
||||
tearDown: function() {
|
||||
EXPECTED_HEADERS = null;
|
||||
EXPECTED_HEADER_VALUES = null;
|
||||
},
|
||||
|
||||
testRpcResponse: function() {
|
||||
var client = new GrpcWebClientBase();
|
||||
client.newXhr_ = function() {
|
||||
return new MockXhr({
|
||||
// This parses to [ { DATA: [4,5,6] }, { TRAILER: "a: b" } ]
|
||||
response: googCrypt.encodeByteArray(new Uint8Array([
|
||||
0, 0, 0, 0, 3, 4, 5, 6, 128, 0, 0, 0, 4, 97, 58, 32, 98
|
||||
])),
|
||||
});
|
||||
};
|
||||
|
||||
expectUnaryHeaders();
|
||||
client.rpcCall(FAKE_METHOD, {}, {}, {
|
||||
requestSerializeFn : function(request) {
|
||||
return REQUEST_BYTES;
|
||||
},
|
||||
responseDeserializeFn : function(bytes) {
|
||||
assertElementsEquals([4,5,6], [].slice.call(bytes));
|
||||
return {"field1": PROTO_FIELD_VALUE};
|
||||
}
|
||||
}, function(error, response) {
|
||||
assertNull(error);
|
||||
assertEquals(PROTO_FIELD_VALUE, response.field1);
|
||||
async testRpcResponse() {
|
||||
const xhr = new XhrIo();
|
||||
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
|
||||
const methodDescriptor = createMethodDescriptor((bytes) => {
|
||||
assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes));
|
||||
return new MockReply('value');
|
||||
});
|
||||
dataCallback();
|
||||
|
||||
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));
|
||||
},
|
||||
|
||||
testRpcError: function() {
|
||||
var client = new GrpcWebClientBase();
|
||||
client.newXhr_ = function() {
|
||||
return new MockXhr({
|
||||
// This decodes to "grpc-status: 3"
|
||||
response: googCrypt.encodeByteArray(new Uint8Array([
|
||||
128, 0, 0, 0, 14, 103, 114, 112, 99, 45, 115, 116, 97, 116, 117, 115, 58, 32, 51
|
||||
])),
|
||||
});
|
||||
};
|
||||
|
||||
expectUnaryHeaders();
|
||||
client.rpcCall(FAKE_METHOD, {}, {}, {
|
||||
requestSerializeFn : function(request) {
|
||||
return REQUEST_BYTES;
|
||||
},
|
||||
responseDeserializeFn : function(bytes) {
|
||||
return {};
|
||||
}
|
||||
}, function(error, response) {
|
||||
assertNull(response);
|
||||
assertEquals(3, error.code);
|
||||
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;
|
||||
});
|
||||
dataCallback();
|
||||
}
|
||||
|
||||
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() {
|
||||
const xhr = new XhrIo();
|
||||
const client = new GrpcWebClientBase(/* options= */ {}, xhr);
|
||||
const methodDescriptor = createMethodDescriptor((bytes) => {
|
||||
assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes));
|
||||
return new MockReply('value');
|
||||
});
|
||||
|
||||
const metadata = await new Promise((resolve, reject) => {
|
||||
const call = client.rpcCall(
|
||||
'url', new MockRequest(), /* metadata= */ {}, methodDescriptor,
|
||||
(error, response) => {
|
||||
assertNull(error);
|
||||
});
|
||||
call.on('metadata', (metadata) => {
|
||||
resolve(metadata);
|
||||
});
|
||||
xhr.simulatePartialResponse(
|
||||
googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)), {
|
||||
'Content-Type': 'application/grpc-web-text',
|
||||
'initial-metadata-key': 'initial-metadata-value',
|
||||
});
|
||||
xhr.simulateReadyStateChange(ReadyState.COMPLETE);
|
||||
});
|
||||
assertEquals('initial-metadata-value', metadata['initial-metadata-key']);
|
||||
},
|
||||
|
||||
async testStreamInterceptor() {
|
||||
const xhr = new XhrIo();
|
||||
const interceptor = new StreamResponseInterceptor();
|
||||
const methodDescriptor = createMethodDescriptor((bytes) => {
|
||||
assertElementsEquals(DEFAULT_RPC_RESPONSE_DATA, [].slice.call(bytes));
|
||||
return new MockReply('value');
|
||||
});
|
||||
const client =
|
||||
new GrpcWebClientBase({'streamInterceptors': [interceptor]}, xhr);
|
||||
|
||||
const response = await new Promise((resolve, reject) => {
|
||||
client.rpcCall(
|
||||
'url', new MockRequest(), /* metadata= */ {}, methodDescriptor,
|
||||
(error, response) => {
|
||||
assertNull(error);
|
||||
resolve(response);
|
||||
});
|
||||
xhr.simulatePartialResponse(
|
||||
googCrypt.encodeByteArray(new Uint8Array(DEFAULT_RPC_RESPONSE)),
|
||||
DEFAULT_RESPONSE_HEADERS);
|
||||
xhr.simulateReadyStateChange(ReadyState.COMPLETE);
|
||||
});
|
||||
assertEquals('Intercepted value', response.data);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
/** Mocks a request proto object. */
|
||||
class MockRequest {
|
||||
/**
|
||||
* @param {string=} data
|
||||
*/
|
||||
constructor(data = '') {
|
||||
/** @type {string} */
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets expected headers as the unary response headers */
|
||||
function expectUnaryHeaders() {
|
||||
EXPECTED_HEADERS = EXPECTED_UNARY_HEADERS;
|
||||
EXPECTED_HEADER_VALUES = EXPECTED_UNARY_HEADER_VALUES;
|
||||
/** Mocks a response proto object. */
|
||||
class MockReply {
|
||||
/**
|
||||
* @param {string=} data
|
||||
*/
|
||||
constructor(data = '') {
|
||||
/** @type {string} */
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Typedef for allowed response types.
|
||||
*
|
||||
* Number is allowed specifically for supporting falsy responses `0`, see:
|
||||
* https://github.com/grpc/grpc-web/pull/1025
|
||||
*
|
||||
* @typedef {!MockReply|number}
|
||||
*/
|
||||
let AllowedResponseType;
|
||||
|
||||
/**
|
||||
* @param {function(string): !AllowedResponseType} responseDeSerializeFn
|
||||
* @return {!MethodDescriptor<!MockRequest, !AllowedResponseType>}
|
||||
*/
|
||||
function createMethodDescriptor(responseDeSerializeFn) {
|
||||
return new MethodDescriptor(
|
||||
/* name= */ '', /* methodType= */ null, MockRequest, MockReply,
|
||||
(request) => [1, 2, 3], responseDeSerializeFn);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {?Object} mockValues
|
||||
* Mock XhrIO object to test the outgoing values
|
||||
* @implements {StreamInterceptor}
|
||||
* @unrestricted
|
||||
*/
|
||||
function MockXhr(mockValues) {
|
||||
this.mockValues = mockValues;
|
||||
this.headers = new Map();
|
||||
class StreamResponseInterceptor {
|
||||
constructor() {}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @template REQUEST, RESPONSE
|
||||
* @param {!Request<REQUEST, RESPONSE>} request
|
||||
* @param {function(!Request<REQUEST,RESPONSE>):
|
||||
* !ClientReadableStream<RESPONSE>} invoker
|
||||
* @return {!ClientReadableStream<RESPONSE>}
|
||||
*/
|
||||
intercept(request, invoker) {
|
||||
return new InterceptedStream(invoker(request));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} url
|
||||
* @param {string=} opt_method
|
||||
* @param {string=} opt_content
|
||||
* @param {string=} opt_headers
|
||||
* @implements {ClientReadableStream}
|
||||
* @template RESPONSE
|
||||
* @final
|
||||
*/
|
||||
MockXhr.prototype.send = function(url, opt_method, opt_content, opt_headers) {
|
||||
assertEquals(FAKE_METHOD, url);
|
||||
assertEquals("POST", opt_method);
|
||||
assertElementsEquals(googCrypt.encodeByteArray(new Uint8Array([0, 0, 0, 0, 3, 1, 2, 3])), opt_content);
|
||||
assertElementsEquals(EXPECTED_HEADERS, this.headers.getKeys());
|
||||
assertElementsEquals(EXPECTED_HEADER_VALUES, this.headers.getValues());
|
||||
};
|
||||
class InterceptedStream {
|
||||
/**
|
||||
* @param {!ClientReadableStream<RESPONSE>} stream
|
||||
*/
|
||||
constructor(stream) {
|
||||
/** @const {!ClientReadableStream<RESPONSE>} */
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} withCredentials
|
||||
*/
|
||||
MockXhr.prototype.setWithCredentials = function(withCredentials) {
|
||||
return;
|
||||
};
|
||||
/**
|
||||
* @override
|
||||
* @return {!ClientReadableStream<RESPONSE>}
|
||||
*/
|
||||
cancel() {
|
||||
this.stream.cancel();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} response
|
||||
*/
|
||||
MockXhr.prototype.getResponseText = function() {
|
||||
return this.mockValues.response;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} response
|
||||
*/
|
||||
MockXhr.prototype.getResponseHeaders = function() {
|
||||
return {};
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} xhr state
|
||||
*/
|
||||
MockXhr.prototype.getReadyState = function() {
|
||||
return 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} lastErrorCode
|
||||
*/
|
||||
MockXhr.prototype.getLastErrorCode = function() {
|
||||
return 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {string} lastError
|
||||
*/
|
||||
MockXhr.prototype.getLastError = function() {
|
||||
return 'server not responding';
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} responseType
|
||||
*/
|
||||
MockXhr.prototype.setResponseType = function(responseType) {
|
||||
return;
|
||||
};
|
||||
/**
|
||||
* @override
|
||||
* @param {string} eventType
|
||||
* @param {function(?)} callback
|
||||
* @return {!ClientReadableStream<RESPONSE>}
|
||||
*/
|
||||
removeListener(eventType, callback) {
|
||||
this.stream.removeListener(eventType, callback);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,9 +35,9 @@ const ClientReadableStream = goog.require('grpc.web.ClientReadableStream');
|
|||
const ErrorCode = goog.require('goog.net.ErrorCode');
|
||||
const EventType = goog.require('goog.net.EventType');
|
||||
const GrpcWebStreamParser = goog.require('grpc.web.GrpcWebStreamParser');
|
||||
const RpcError = goog.require('grpc.web.RpcError');
|
||||
const StatusCode = goog.require('grpc.web.StatusCode');
|
||||
const XhrIo = goog.require('goog.net.XhrIo');
|
||||
const XmlHttp = goog.require('goog.net.XmlHttp');
|
||||
const events = goog.require('goog.events');
|
||||
const googCrypt = goog.require('goog.crypt.base64');
|
||||
const googString = goog.require('goog.string');
|
||||
|
@ -46,222 +46,402 @@ const {Status} = goog.require('grpc.web.Status');
|
|||
|
||||
|
||||
|
||||
const GRPC_STATUS = "grpc-status";
|
||||
const GRPC_STATUS_MESSAGE = "grpc-message";
|
||||
const GRPC_STATUS = 'grpc-status';
|
||||
const GRPC_STATUS_MESSAGE = 'grpc-message';
|
||||
|
||||
/** @type {!Array<string>} */
|
||||
const EXCLUDED_RESPONSE_HEADERS =
|
||||
['content-type', GRPC_STATUS, GRPC_STATUS_MESSAGE];
|
||||
|
||||
/**
|
||||
* A stream that the client can read from. Used for calls that are streaming
|
||||
* from the server side.
|
||||
*
|
||||
* @template RESPONSE
|
||||
* @constructor
|
||||
* @implements {ClientReadableStream}
|
||||
* @final
|
||||
* @param {!GenericTransportInterface} genericTransportInterface The
|
||||
* GenericTransportInterface
|
||||
* @unrestricted
|
||||
*/
|
||||
const GrpcWebClientReadableStream = function(genericTransportInterface) {
|
||||
class GrpcWebClientReadableStream {
|
||||
/**
|
||||
* @const
|
||||
* @private
|
||||
* @type {?XhrIo} The XhrIo object
|
||||
* @param {!GenericTransportInterface} genericTransportInterface The
|
||||
* GenericTransportInterface
|
||||
*/
|
||||
this.xhr_ = /** @type {?XhrIo} */ (genericTransportInterface.xhr);
|
||||
constructor(genericTransportInterface) {
|
||||
/**
|
||||
* @const
|
||||
* @private
|
||||
* @type {?XhrIo} The XhrIo object
|
||||
*/
|
||||
this.xhr_ = /** @type {?XhrIo} */ (genericTransportInterface.xhr);
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {function(?):!RESPONSE|null} The deserialize function for the proto
|
||||
*/
|
||||
this.responseDeserializeFn_ = null;
|
||||
/**
|
||||
* @private
|
||||
* @type {function(?):!RESPONSE|null} The deserialize function for the proto
|
||||
*/
|
||||
this.responseDeserializeFn_ = null;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {function(!RESPONSE)|null} The data callback
|
||||
*/
|
||||
this.onDataCallback_ = null;
|
||||
/**
|
||||
* @const
|
||||
* @private
|
||||
* @type {!Array<function(!RESPONSE)>} The list of data callbacks
|
||||
*/
|
||||
this.onDataCallbacks_ = [];
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {function(!Status)|null} The status callback
|
||||
*/
|
||||
this.onStatusCallback_ = null;
|
||||
/**
|
||||
* @const
|
||||
* @private
|
||||
* @type {!Array<function(!Status)>} The list of status callbacks
|
||||
*/
|
||||
this.onStatusCallbacks_ = [];
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {function(...):?|null} The error callback
|
||||
*/
|
||||
this.onErrorCallback_ = null;
|
||||
/**
|
||||
* @const
|
||||
* @private
|
||||
* @type {!Array<function(!Metadata)>} The list of metadata callbacks
|
||||
*/
|
||||
this.onMetadataCallbacks_ = [];
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {function(...):?|null} The stream end callback
|
||||
*/
|
||||
this.onEndCallback_ = null;
|
||||
/**
|
||||
* @const
|
||||
* @private
|
||||
* @type {!Array<function(!RpcError)>} The list of error callbacks
|
||||
*/
|
||||
this.onErrorCallbacks_ = [];
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {number} The stream parser position
|
||||
*/
|
||||
this.pos_ = 0;
|
||||
/**
|
||||
* @const
|
||||
* @private
|
||||
* @type {!Array<function(...):?>} The list of stream end callbacks
|
||||
*/
|
||||
this.onEndCallbacks_ = [];
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {!GrpcWebStreamParser} The grpc-web stream parser
|
||||
* @const
|
||||
*/
|
||||
this.parser_ = new GrpcWebStreamParser();
|
||||
/**
|
||||
* @private
|
||||
* @type {boolean} Whether the stream has been aborted
|
||||
*/
|
||||
this.aborted_ = false;
|
||||
|
||||
var self = this;
|
||||
events.listen(this.xhr_, EventType.READY_STATE_CHANGE,
|
||||
function(e) {
|
||||
var contentType = self.xhr_.getStreamingResponseHeader('Content-Type');
|
||||
if (!contentType) return;
|
||||
contentType = contentType.toLowerCase();
|
||||
/**
|
||||
* @private
|
||||
* @type {number} The stream parser position
|
||||
*/
|
||||
this.pos_ = 0;
|
||||
|
||||
if (googString.startsWith(contentType, 'application/grpc-web-text')) {
|
||||
var responseText = self.xhr_.getResponseText();
|
||||
var newPos = responseText.length - responseText.length % 4;
|
||||
var newData = responseText.substr(self.pos_, newPos - self.pos_);
|
||||
if (newData.length == 0) return;
|
||||
self.pos_ = newPos;
|
||||
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;
|
||||
/**
|
||||
* @private
|
||||
* @type {!GrpcWebStreamParser} The grpc-web stream parser
|
||||
* @const
|
||||
*/
|
||||
this.parser_ = new GrpcWebStreamParser();
|
||||
|
||||
var FrameType = GrpcWebStreamParser.FrameType;
|
||||
for (var i = 0; i < messages.length; i++) {
|
||||
if (FrameType.DATA in messages[i]) {
|
||||
var data = messages[i][FrameType.DATA];
|
||||
if (data) {
|
||||
var response = self.responseDeserializeFn_(data);
|
||||
if (response) {
|
||||
self.onDataCallback_(response);
|
||||
const self = this;
|
||||
events.listen(this.xhr_, EventType.READY_STATE_CHANGE, function(e) {
|
||||
let contentType = self.xhr_.getStreamingResponseHeader('Content-Type');
|
||||
if (!contentType) return;
|
||||
contentType = contentType.toLowerCase();
|
||||
|
||||
let byteSource;
|
||||
if (googString.startsWith(contentType, 'application/grpc-web-text')) {
|
||||
// Ensure responseText is not null
|
||||
const responseText = self.xhr_.getResponseText() || '';
|
||||
const newPos = responseText.length - responseText.length % 4;
|
||||
const newData = responseText.substr(self.pos_, newPos - self.pos_);
|
||||
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) {
|
||||
var trailerString = "";
|
||||
for (var pos = 0; pos < messages[i][FrameType.TRAILER].length;
|
||||
pos++) {
|
||||
trailerString += String.fromCharCode(
|
||||
messages[i][FrameType.TRAILER][pos]);
|
||||
}
|
||||
var trailers = self.parseHttp1Headers_(trailerString);
|
||||
var grpcStatusCode = StatusCode.OK;
|
||||
var grpcStatusMessage = "";
|
||||
if (GRPC_STATUS in trailers) {
|
||||
grpcStatusCode = trailers[GRPC_STATUS];
|
||||
}
|
||||
if (GRPC_STATUS_MESSAGE in trailers) {
|
||||
grpcStatusMessage = trailers[GRPC_STATUS_MESSAGE];
|
||||
}
|
||||
if (self.onStatusCallback_) {
|
||||
self.onStatusCallback_({
|
||||
code: Number(grpcStatusCode),
|
||||
details: grpcStatusMessage,
|
||||
metadata: trailers,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
events.listen(this.xhr_, EventType.COMPLETE, function(e) {
|
||||
const lastErrorCode = self.xhr_.getLastErrorCode();
|
||||
let grpcStatusCode = StatusCode.UNKNOWN;
|
||||
let grpcStatusMessage = '';
|
||||
const initialMetadata = /** @type {!Metadata} */ ({});
|
||||
|
||||
// Get response headers with lower case keys.
|
||||
const rawResponseHeaders = self.xhr_.getResponseHeaders();
|
||||
const responseHeaders = {};
|
||||
for (const key in rawResponseHeaders) {
|
||||
if (rawResponseHeaders.hasOwnProperty(key)) {
|
||||
responseHeaders[key.toLowerCase()] = rawResponseHeaders[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var readyState = self.xhr_.getReadyState();
|
||||
if (readyState == XmlHttp.ReadyState.COMPLETE) {
|
||||
if (self.onEndCallback_) {
|
||||
self.onEndCallback_();
|
||||
Object.keys(responseHeaders).forEach((header_) => {
|
||||
if (!(EXCLUDED_RESPONSE_HEADERS.includes(header_))) {
|
||||
initialMetadata[header_] = responseHeaders[header_];
|
||||
}
|
||||
});
|
||||
self.sendMetadataCallbacks_(initialMetadata);
|
||||
|
||||
// There's an XHR level error
|
||||
let xhrStatusCode = -1;
|
||||
if (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;
|
||||
}
|
||||
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)
|
||||
});
|
||||
return;
|
||||
}
|
||||
var responseHeaders = self.xhr_.getResponseHeaders();
|
||||
if (GRPC_STATUS in responseHeaders &&
|
||||
responseHeaders[GRPC_STATUS] != StatusCode.OK) {
|
||||
self.onErrorCallback_({
|
||||
code: Number(responseHeaders[GRPC_STATUS]),
|
||||
message: responseHeaders[GRPC_STATUS_MESSAGE]
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
GrpcWebClientReadableStream.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;
|
||||
if (!errorEmitted) {
|
||||
self.sendEndCallbacks_();
|
||||
}
|
||||
});
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Register a callbackl to parse the response
|
||||
*
|
||||
* @param {function(?):!RESPONSE} responseDeserializeFn The deserialize
|
||||
* function for the proto
|
||||
*/
|
||||
GrpcWebClientReadableStream.prototype.setResponseDeserializeFn =
|
||||
function(responseDeserializeFn) {
|
||||
this.responseDeserializeFn_ = responseDeserializeFn;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
GrpcWebClientReadableStream.prototype.cancel = function() {
|
||||
this.xhr_.abort();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Parse HTTP headers
|
||||
*
|
||||
* @private
|
||||
* @param {string} str The raw http header string
|
||||
* @return {!Object} The header:value pairs
|
||||
*/
|
||||
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();
|
||||
/**
|
||||
* @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;
|
||||
}
|
||||
return headers;
|
||||
};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {!Array<function(?)>} callbacks the internal list of callbacks
|
||||
* @param {function(?)} callback the callback to remove
|
||||
*/
|
||||
removeListenerFromCallbacks_(callbacks, callback) {
|
||||
const index = callbacks.indexOf(callback);
|
||||
if (index > -1) {
|
||||
callbacks.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @export
|
||||
* @override
|
||||
*/
|
||||
removeListener(eventType, callback) {
|
||||
if (eventType == 'data') {
|
||||
this.removeListenerFromCallbacks_(this.onDataCallbacks_, callback);
|
||||
} else if (eventType == 'status') {
|
||||
this.removeListenerFromCallbacks_(this.onStatusCallbacks_, callback);
|
||||
} else if (eventType == 'metadata') {
|
||||
this.removeListenerFromCallbacks_(this.onMetadataCallbacks_, callback);
|
||||
} else if (eventType == 'end') {
|
||||
this.removeListenerFromCallbacks_(this.onEndCallbacks_, callback);
|
||||
} else if (eventType == 'error') {
|
||||
this.removeListenerFromCallbacks_(this.onErrorCallbacks_, callback);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callbackl to parse the response
|
||||
*
|
||||
* @param {function(?):!RESPONSE} responseDeserializeFn The deserialize
|
||||
* function for the proto
|
||||
*/
|
||||
setResponseDeserializeFn(responseDeserializeFn) {
|
||||
this.responseDeserializeFn_ = responseDeserializeFn;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @export
|
||||
*/
|
||||
cancel() {
|
||||
this.aborted_ = true;
|
||||
this.xhr_.abort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse HTTP headers
|
||||
*
|
||||
* @private
|
||||
* @param {string} str The raw http header string
|
||||
* @return {!Object} The header:value pairs
|
||||
*/
|
||||
parseHttp1Headers_(str) {
|
||||
const chunks = str.trim().split('\r\n');
|
||||
const headers = {};
|
||||
for (let i = 0; i < chunks.length; i++) {
|
||||
const pos = chunks[i].indexOf(':');
|
||||
headers[chunks[i].substring(0, pos).trim()] =
|
||||
chunks[i].substring(pos + 1).trim();
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* A central place to handle errors
|
||||
*
|
||||
* @private
|
||||
* @param {!RpcError} error The error object
|
||||
*/
|
||||
handleError_(error) {
|
||||
if (error.code != StatusCode.OK) {
|
||||
this.sendErrorCallbacks_(new RpcError(
|
||||
error.code, decodeURIComponent(error.message || ''), error.metadata));
|
||||
}
|
||||
this.sendStatusCallbacks_(/** @type {!Status} */ ({
|
||||
code: error.code,
|
||||
details: decodeURIComponent(error.message || ''),
|
||||
metadata: error.metadata
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {!RESPONSE} data The data to send back
|
||||
*/
|
||||
sendDataCallbacks_(data) {
|
||||
for (let i = 0; i < this.onDataCallbacks_.length; i++) {
|
||||
this.onDataCallbacks_[i](data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {!Status} status The status to send back
|
||||
*/
|
||||
sendStatusCallbacks_(status) {
|
||||
for (let i = 0; i < this.onStatusCallbacks_.length; i++) {
|
||||
this.onStatusCallbacks_[i](status);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {!Metadata} metadata The metadata to send back
|
||||
*/
|
||||
sendMetadataCallbacks_(metadata) {
|
||||
for (let i = 0; i < this.onMetadataCallbacks_.length; i++) {
|
||||
this.onMetadataCallbacks_[i](metadata);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {!RpcError} error The error to send back
|
||||
*/
|
||||
sendErrorCallbacks_(error) {
|
||||
for (let i = 0; i < this.onErrorCallbacks_.length; i++) {
|
||||
this.onErrorCallbacks_[i](error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
sendEndCallbacks_() {
|
||||
for (let i = 0; i < this.onEndCallbacks_.length; i++) {
|
||||
this.onEndCallbacks_[i]();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -49,71 +49,210 @@ const asserts = goog.require('goog.asserts');
|
|||
|
||||
/**
|
||||
* The default grpc-web stream parser.
|
||||
*
|
||||
* @constructor
|
||||
* @struct
|
||||
* @implements {StreamParser}
|
||||
* @final
|
||||
*/
|
||||
const GrpcWebStreamParser = function() {
|
||||
/**
|
||||
* The current error message, if any.
|
||||
* @private {?string}
|
||||
*/
|
||||
this.errorMessage_ = null;
|
||||
class GrpcWebStreamParser {
|
||||
constructor() {
|
||||
/**
|
||||
* The current error message, if any.
|
||||
* @private {?string}
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* The currently buffered result (parsed messages).
|
||||
* @private {!Array<!Object>}
|
||||
* @override
|
||||
*/
|
||||
this.result_ = [];
|
||||
isInputValid() {
|
||||
return this.state_ != Parser.State_.INVALID;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current position in the streamed data.
|
||||
* @private {number}
|
||||
* @override
|
||||
*/
|
||||
this.streamPos_ = 0;
|
||||
getErrorMessage() {
|
||||
return this.errorMessage_;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current parser state.
|
||||
* @private {number}
|
||||
* @override
|
||||
* @return {boolean}
|
||||
*/
|
||||
this.state_ = Parser.State_.INIT;
|
||||
acceptsBinaryInput() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current frame byte being parsed
|
||||
* @private {number}
|
||||
* Parse the new input.
|
||||
*
|
||||
* Note that there is no Parser state to indicate the end of a stream.
|
||||
*
|
||||
* @param {string|!ArrayBuffer|!Uint8Array|!Array<number>} input The input
|
||||
* data
|
||||
* @throws {!Error} Throws an error message if the input is invalid.
|
||||
* @return {?Array<string|!Object>} any parsed objects (atomic messages)
|
||||
* in an array, or null if more data needs be read to parse any new object.
|
||||
* @override
|
||||
*/
|
||||
this.frame_ = 0;
|
||||
parse(input) {
|
||||
asserts.assert(
|
||||
input instanceof Array || input instanceof ArrayBuffer ||
|
||||
input instanceof Uint8Array);
|
||||
|
||||
/**
|
||||
* The length of the proto message being parsed.
|
||||
* @private {number}
|
||||
*/
|
||||
this.length_ = 0;
|
||||
var parser = this;
|
||||
var inputBytes;
|
||||
var pos = 0;
|
||||
|
||||
/**
|
||||
* Count of processed length bytes.
|
||||
* @private {number}
|
||||
*/
|
||||
this.countLengthBytes_ = 0;
|
||||
if (input instanceof Uint8Array || input instanceof Array) {
|
||||
inputBytes = input;
|
||||
} else {
|
||||
inputBytes = new Uint8Array(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
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_);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Count of processed message bytes.
|
||||
* @private {number}
|
||||
*/
|
||||
this.countMessageBytes_ = 0;
|
||||
};
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var Parser = GrpcWebStreamParser;
|
||||
const Parser = GrpcWebStreamParser;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -133,29 +272,14 @@ Parser.State_ = {
|
|||
* @enum {number}
|
||||
*/
|
||||
GrpcWebStreamParser.FrameType = {
|
||||
DATA: 0x00, // expecting a data frame
|
||||
TRAILER: 0x80, // expecting a trailer frame
|
||||
DATA: 0x00, // expecting a data frame
|
||||
TRAILER: 0x80, // expecting a trailer frame
|
||||
};
|
||||
|
||||
|
||||
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
|
||||
|
@ -174,106 +298,5 @@ 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;
|
||||
|
|
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* @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
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* @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;
|
|
@ -0,0 +1,119 @@
|
|||
/**
|
||||
* @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;
|
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* @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;
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* @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;
|
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* @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;
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* @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;
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
*
|
||||
* 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,16 +23,19 @@
|
|||
* @author stanleycheung@google.com (Stanley Cheung)
|
||||
*/
|
||||
goog.module('grpc.web.Status');
|
||||
|
||||
goog.module.declareLegacyNamespace();
|
||||
|
||||
|
||||
/** @record */
|
||||
function Status() {}
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* code: number,
|
||||
* details: string,
|
||||
* metadata: (!Object<string, string>|undefined)
|
||||
* }}
|
||||
*/
|
||||
exports.Status;
|
||||
/** @export {number} */
|
||||
Status.prototype.code;
|
||||
|
||||
/** @export {string} */
|
||||
Status.prototype.details;
|
||||
|
||||
/** @export {(!Object<string, string>|undefined)} */
|
||||
Status.prototype.metadata;
|
||||
|
||||
exports.Status = Status;
|
||||
|
|
|
@ -24,62 +24,61 @@
|
|||
*/
|
||||
goog.module('grpc.web.StatusCode');
|
||||
|
||||
goog.module.declareLegacyNamespace();
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* gRPC Status Codes
|
||||
* See: https://github.com/grpc/grpc/blob/master/include/grpc%2B%2B/impl/codegen/status_code_enum.h
|
||||
* See:
|
||||
* https://github.com/grpc/grpc/blob/master/include/grpcpp/impl/codegen/status_code_enum.h
|
||||
* @enum {number}
|
||||
*/
|
||||
const StatusCode = {
|
||||
// LINT.IfChange(status_codes)
|
||||
|
||||
// Not an error; returned on success.
|
||||
OK: 0,
|
||||
'OK': 0,
|
||||
|
||||
// The operation was cancelled (typically by the caller).
|
||||
CANCELLED: 1,
|
||||
'CANCELLED': 1,
|
||||
|
||||
// Unknown error. An example of where this error may be returned is if a
|
||||
// Status value received from another address space belongs to an error-space
|
||||
// that is not known in this address space. Also errors raised by APIs that
|
||||
// do not return enough error information may be converted to this error.
|
||||
UNKNOWN: 2,
|
||||
'UNKNOWN': 2,
|
||||
|
||||
// Client specified an invalid argument. Note that this differs from
|
||||
// FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments that are
|
||||
// problematic regardless of the state of the system (e.g., a malformed file
|
||||
// name).
|
||||
INVALID_ARGUMENT: 3,
|
||||
'INVALID_ARGUMENT': 3,
|
||||
|
||||
// Deadline expired before operation could complete. For operations that
|
||||
// change the state of the system, this error may be returned even if the
|
||||
// operation has completed successfully. For example, a successful response
|
||||
// from a server could have been delayed long enough for the deadline to
|
||||
// expire.
|
||||
DEADLINE_EXCEEDED: 4,
|
||||
'DEADLINE_EXCEEDED': 4,
|
||||
|
||||
// Some requested entity (e.g., file or directory) was not found.
|
||||
NOT_FOUND: 5,
|
||||
'NOT_FOUND': 5,
|
||||
|
||||
// Some entity that we attempted to create (e.g., file or directory) already
|
||||
// exists.
|
||||
ALREADY_EXISTS: 6,
|
||||
'ALREADY_EXISTS': 6,
|
||||
|
||||
// The caller does not have permission to execute the specified operation.
|
||||
// PERMISSION_DENIED must not be used for rejections caused by exhausting
|
||||
// some resource (use RESOURCE_EXHAUSTED instead for those errors).
|
||||
// PERMISSION_DENIED must not be used if the caller can not be identified
|
||||
// (use UNAUTHENTICATED instead for those errors).
|
||||
PERMISSION_DENIED: 7,
|
||||
'PERMISSION_DENIED': 7,
|
||||
|
||||
// The request does not have valid authentication credentials for the
|
||||
// operation.
|
||||
UNAUTHENTICATED: 16,
|
||||
'UNAUTHENTICATED': 16,
|
||||
|
||||
// Some resource has been exhausted, perhaps a per-user quota, or perhaps the
|
||||
// entire file system is out of space.
|
||||
RESOURCE_EXHAUSTED: 8,
|
||||
'RESOURCE_EXHAUSTED': 8,
|
||||
|
||||
// Operation was rejected because the system is not in a state required for
|
||||
// the operation's execution. For example, directory to be deleted may be
|
||||
|
@ -99,14 +98,14 @@ const StatusCode = {
|
|||
// REST Get/Update/Delete on a resource and the resource on the
|
||||
// server does not match the condition. E.g., conflicting
|
||||
// read-modify-write on the same resource.
|
||||
FAILED_PRECONDITION: 9,
|
||||
'FAILED_PRECONDITION': 9,
|
||||
|
||||
// The operation was aborted, typically due to a concurrency issue like
|
||||
// sequencer check failures, transaction aborts, etc.
|
||||
//
|
||||
// See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
|
||||
// and UNAVAILABLE.
|
||||
ABORTED: 10,
|
||||
'ABORTED': 10,
|
||||
|
||||
// Operation was attempted past the valid range. E.g., seeking or reading
|
||||
// past end of file.
|
||||
|
@ -121,34 +120,35 @@ const StatusCode = {
|
|||
// OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific error)
|
||||
// when it applies so that callers who are iterating through a space can
|
||||
// easily look for an OUT_OF_RANGE error to detect when they are done.
|
||||
OUT_OF_RANGE: 11,
|
||||
'OUT_OF_RANGE': 11,
|
||||
|
||||
// Operation is not implemented or not supported/enabled in this service.
|
||||
UNIMPLEMENTED: 12,
|
||||
'UNIMPLEMENTED': 12,
|
||||
|
||||
// Internal errors. Means some invariants expected by underlying System has
|
||||
// been broken. If you see one of these errors, Something is very broken.
|
||||
INTERNAL: 13,
|
||||
'INTERNAL': 13,
|
||||
|
||||
// The service is currently unavailable. This is a most likely a transient
|
||||
// condition and may be corrected by retrying with a backoff.
|
||||
//
|
||||
// See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
|
||||
// and UNAVAILABLE.
|
||||
UNAVAILABLE: 14,
|
||||
'UNAVAILABLE': 14,
|
||||
|
||||
// Unrecoverable data loss or corruption.
|
||||
DATA_LOSS: 15,
|
||||
};
|
||||
'DATA_LOSS': 15,
|
||||
|
||||
// LINT.ThenChange(:status_code_name)
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert HTTP Status code to gRPC Status code
|
||||
* @param {number} http_status HTTP Status Code
|
||||
* @return {number} gRPC Status Code
|
||||
* @param {number} httpStatus HTTP Status Code
|
||||
* @return {!StatusCode} gRPC Status Code
|
||||
*/
|
||||
StatusCode.fromHttpStatus = function(http_status) {
|
||||
switch (http_status) {
|
||||
StatusCode.fromHttpStatus = function(httpStatus) {
|
||||
switch (httpStatus) {
|
||||
case 200:
|
||||
return StatusCode.OK;
|
||||
case 400:
|
||||
|
@ -182,4 +182,91 @@ StatusCode.fromHttpStatus = function(http_status) {
|
|||
};
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
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);
|
||||
}
|
||||
});
|
|
@ -1,192 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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;
|
|
@ -0,0 +1,44 @@
|
|||
/**
|
||||
* @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;
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* @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;
|
|
@ -1,105 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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;
|
|
@ -0,0 +1 @@
|
|||
build_file: "grpc-web/scripts/run_interop_tests.sh"
|
|
@ -0,0 +1 @@
|
|||
build_file: "grpc-web/scripts/kokoro.sh"
|
|
@ -1 +1 @@
|
|||
build_file: "grpc-web/scripts/kokoro.sh"
|
||||
build_file: "grpc-web/scripts/run_basic_tests.sh"
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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/backend.h"
|
||||
|
||||
namespace grpc {
|
||||
namespace gateway {
|
||||
|
||||
Backend::Backend() : frontend_(nullptr) {}
|
||||
|
||||
Backend::~Backend() {}
|
||||
|
||||
} // namespace gateway
|
||||
} // namespace grpc
|
|
@ -1,62 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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_
|
|
@ -1,346 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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
|
|
@ -1,122 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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_
|
|
@ -1,44 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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_
|
|
@ -1,44 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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
|
|
@ -1,47 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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_
|
|
@ -1,49 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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
|
|
@ -1,44 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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_
|
|
@ -1,44 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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
|
|
@ -1,51 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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_
|
|
@ -1,274 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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
|
|
@ -1,73 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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_
|
|
@ -1,66 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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_
|
|
@ -1,34 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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
|
|
@ -1,53 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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_
|
|
@ -1,140 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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
|
|
@ -1,87 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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_
|
|
@ -1,50 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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
|
|
@ -1,47 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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_
|
|
@ -1,103 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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
|
|
@ -1,69 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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_
|
|
@ -1,130 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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
|
|
@ -1,44 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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_
|
|
@ -1,43 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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
|
|
@ -1,44 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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_
|
|
@ -1,44 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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
|
|
@ -1,46 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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_
|
|
@ -1,258 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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
|
|
@ -1,64 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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_
|
|
@ -1,98 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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
|
|
@ -1,50 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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_
|
|
@ -1,45 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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/proto_decoder.h"
|
||||
|
||||
#include "third_party/grpc/include/grpcpp/support/byte_buffer.h"
|
||||
#include "third_party/grpc/include/grpcpp/support/status.h"
|
||||
|
||||
namespace grpc {
|
||||
namespace gateway {
|
||||
|
||||
ProtoDecoder::ProtoDecoder() {}
|
||||
|
||||
ProtoDecoder::~ProtoDecoder() {}
|
||||
|
||||
Status ProtoDecoder::Decode() {
|
||||
if (inputs()->empty()) {
|
||||
Slice slice(grpc_empty_slice(), Slice::STEAL_REF);
|
||||
ByteBuffer* buffer = new ByteBuffer(&slice, 1);
|
||||
results()->push_back(std::unique_ptr<ByteBuffer>(buffer));
|
||||
} else {
|
||||
ByteBuffer* buffer = new ByteBuffer(inputs()->data(), inputs()->size());
|
||||
results()->push_back(std::unique_ptr<ByteBuffer>(buffer));
|
||||
}
|
||||
inputs()->clear();
|
||||
return Status::OK;
|
||||
}
|
||||
|
||||
} // namespace gateway
|
||||
} // namespace grpc
|
|
@ -1,39 +0,0 @@
|
|||
/**
|
||||
*
|
||||
* 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_PROTO_DECODER_H_
|
||||
#define NET_GRPC_GATEWAY_CODEC_PROTO_DECODER_H_
|
||||
|
||||
#include "net/grpc/gateway/codec/decoder.h"
|
||||
|
||||
namespace grpc {
|
||||
namespace gateway {
|
||||
|
||||
class ProtoDecoder : public Decoder {
|
||||
public:
|
||||
ProtoDecoder();
|
||||
~ProtoDecoder() override;
|
||||
ProtoDecoder(const ProtoDecoder&) = delete;
|
||||
ProtoDecoder& operator=(const ProtoDecoder&) = delete;
|
||||
|
||||
Status Decode() override;
|
||||
};
|
||||
|
||||
} // namespace gateway
|
||||
} // namespace grpc
|
||||
#endif // NET_GRPC_GATEWAY_CODEC_PROTO_DECODER_H_
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue