Skip to content

Commit f3ef8ab

Browse files
authored
Merge pull request grpc-ecosystem#23 from Unix4ever/properly-encode-uint8array
Properly encode Uint8Array fields
2 parents 4a93113 + f6cb5af commit f3ef8ab

File tree

9 files changed

+628
-240
lines changed

9 files changed

+628
-240
lines changed

generator/template.go

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,119 @@ const fetchTmpl = `
9898
* This file is a generated Typescript file for GRPC Gateway, DO NOT MODIFY
9999
*/
100100
101+
/**
102+
* base64 encoder and decoder
103+
* Copied and adapted from https://github.com/protobufjs/protobuf.js/blob/master/lib/base64/index.js
104+
*/
105+
// Base64 encoding table
106+
const b64 = new Array(64);
107+
108+
// Base64 decoding table
109+
const s64 = new Array(123);
110+
111+
// 65..90, 97..122, 48..57, 43, 47
112+
for (let i = 0; i < 64;)
113+
s64[b64[i] = i < 26 ? i + 65 : i < 52 ? i + 71 : i < 62 ? i - 4 : i - 59 | 43] = i++;
114+
115+
export function b64Encode(buffer: Uint8Array, start: number, end: number): string {
116+
let parts: string[] = null;
117+
const chunk = [];
118+
let i = 0, // output index
119+
j = 0, // goto index
120+
t; // temporary
121+
while (start < end) {
122+
const b = buffer[start++];
123+
switch (j) {
124+
case 0:
125+
chunk[i++] = b64[b >> 2];
126+
t = (b & 3) << 4;
127+
j = 1;
128+
break;
129+
case 1:
130+
chunk[i++] = b64[t | b >> 4];
131+
t = (b & 15) << 2;
132+
j = 2;
133+
break;
134+
case 2:
135+
chunk[i++] = b64[t | b >> 6];
136+
chunk[i++] = b64[b & 63];
137+
j = 0;
138+
break;
139+
}
140+
if (i > 8191) {
141+
(parts || (parts = [])).push(String.fromCharCode.apply(String, chunk));
142+
i = 0;
143+
}
144+
}
145+
if (j) {
146+
chunk[i++] = b64[t];
147+
chunk[i++] = 61;
148+
if (j === 1)
149+
chunk[i++] = 61;
150+
}
151+
if (parts) {
152+
if (i)
153+
parts.push(String.fromCharCode.apply(String, chunk.slice(0, i)));
154+
return parts.join("");
155+
}
156+
return String.fromCharCode.apply(String, chunk.slice(0, i));
157+
}
158+
159+
const invalidEncoding = "invalid encoding";
160+
161+
export function b64Decode(s: string): Uint8Array {
162+
const buffer = [];
163+
let offset = 0;
164+
let j = 0, // goto index
165+
t; // temporary
166+
for (let i = 0; i < s.length;) {
167+
let c = s.charCodeAt(i++);
168+
if (c === 61 && j > 1)
169+
break;
170+
if ((c = s64[c]) === undefined)
171+
throw Error(invalidEncoding);
172+
switch (j) {
173+
case 0:
174+
t = c;
175+
j = 1;
176+
break;
177+
case 1:
178+
buffer[offset++] = t << 2 | (c & 48) >> 4;
179+
t = c;
180+
j = 2;
181+
break;
182+
case 2:
183+
buffer[offset++] = (t & 15) << 4 | (c & 60) >> 2;
184+
t = c;
185+
j = 3;
186+
break;
187+
case 3:
188+
buffer[offset++] = (t & 3) << 6 | c;
189+
j = 0;
190+
break;
191+
}
192+
}
193+
if (j === 1)
194+
throw Error(invalidEncoding);
195+
return new Uint8Array(buffer);
196+
}
197+
198+
function b64Test(s: string): boolean {
199+
return /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(s);
200+
}
201+
101202
export interface InitReq extends RequestInit {
102203
pathPrefix?: string
103204
}
104205
206+
export function replacer(key: any, value: any): any {
207+
if(value && value.constructor === Uint8Array) {
208+
return b64Encode(value, 0, value.length);
209+
}
210+
211+
return value;
212+
}
213+
105214
export function fetchReq<I, O>(path: string, init?: InitReq): Promise<O> {
106215
const {pathPrefix, ...req} = init || {}
107216
@@ -399,9 +508,9 @@ func buildInitReq(method data.Method) string {
399508
m := `method: "` + httpMethod + `"`
400509
fields := []string{m}
401510
if method.HTTPRequestBody == nil || *method.HTTPRequestBody == "*" {
402-
fields = append(fields, "body: JSON.stringify(req)")
511+
fields = append(fields, "body: JSON.stringify(req, fm.replacer)")
403512
} else if *method.HTTPRequestBody != "" {
404-
fields = append(fields, `body: JSON.stringify(req["`+*method.HTTPRequestBody+`"])`)
513+
fields = append(fields, `body: JSON.stringify(req["`+*method.HTTPRequestBody+`"], fm.replacer)`)
405514
}
406515

407516
return strings.Join(fields, ", ")

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ require (
1818
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 // indirect
1919
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013
2020
google.golang.org/grpc v1.33.1
21+
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 // indirect
2122
google.golang.org/protobuf v1.25.0
2223
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
114114
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
115115
google.golang.org/grpc v1.33.1 h1:DGeFlSan2f+WEtCERJ4J9GJWk15TxUi8QGagfI87Xyc=
116116
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
117+
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE=
118+
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
117119
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
118120
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
119121
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=

integration_tests/integration_test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { expect } from 'chai';
22
import camelCase from 'lodash.camelcase';
33
import { pathOr } from 'ramda';
44
import { CounterService } from "./service.pb";
5+
import { b64Decode } from './fetch.pb';
56

67
function getFieldName(name: string) {
78
const useCamelCase = pathOr(false, ['__karma__', 'config', 'useProtoNames'], window) === false
@@ -36,6 +37,17 @@ describe("test grpc-gateway-ts communication", () => {
3637
expect(response).to.deep.equal([2, 3, 4, 5, 6])
3738
})
3839

40+
it('binary echo', async () => {
41+
const message = "→ ping";
42+
43+
const resp:any = await CounterService.EchoBinary({
44+
data: new TextEncoder().encode(message),
45+
}, { pathPrefix: "http://localhost:8081" })
46+
47+
const bytes = b64Decode(resp["data"])
48+
expect(new TextDecoder().decode(bytes)).to.equal(message)
49+
})
50+
3951
it('http get check request', async () => {
4052
const result = await CounterService.HTTPGet({ [getFieldName('num_to_increase')]: 10 }, { pathPrefix: "http://localhost:8081" })
4153
expect(result.result).to.equal(11)

integration_tests/msg.pb.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

integration_tests/service.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import (
99
"google.golang.org/protobuf/types/known/emptypb"
1010
)
1111

12-
type RealCounterService struct{}
12+
type RealCounterService struct {
13+
UnimplementedCounterServiceServer
14+
}
1315

1416
func (r *RealCounterService) ExternalMessage(ctx context.Context, request *ExternalRequest) (*ExternalResponse, error) {
1517
return &ExternalResponse{
@@ -27,6 +29,12 @@ func (r *RealCounterService) FailingIncrement(c context.Context, req *UnaryReque
2729
return nil, status.Errorf(codes.Unavailable, "this increment does not work")
2830
}
2931

32+
func (r *RealCounterService) EchoBinary(c context.Context, req *BinaryRequest) (*BinaryResponse, error) {
33+
return &BinaryResponse{
34+
Data: req.Data,
35+
}, nil
36+
}
37+
3038
func (r *RealCounterService) StreamingIncrements(req *StreamingRequest, service CounterService_StreamingIncrementsServer) error {
3139
times := 5
3240
counter := req.Counter

0 commit comments

Comments
 (0)