Skip to content

Commit

Permalink
feat: add gRPC-web unary support
Browse files Browse the repository at this point in the history
  • Loading branch information
notmedia committed Aug 23, 2022
1 parent 7c86e8d commit 0e03b31
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 0 deletions.
8 changes: 8 additions & 0 deletions __tests__/simple-service/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
```bash
$ npm run proto

$ npm run start

# For gRPC web running on 8080 port
$ docker-compose up
```
7 changes: 7 additions & 0 deletions __tests__/simple-service/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
services:
envoy:
image: envoyproxy/envoy:v1.22.0
ports:
- 8080:8080
volumes:
- ./envoy.yaml:/etc/envoy/envoy.yaml:ro
56 changes: 56 additions & 0 deletions __tests__/simple-service/envoy.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
static_resources:
listeners:
- name: listener_0
address:
socket_address: { address: 0.0.0.0, port_value: 8080 }
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
codec_type: auto
stat_prefix: ingress_http
route_config:
name: local_route
virtual_hosts:
- name: local_service
domains: ["*"]
routes:
- match: { prefix: "/" }
route:
cluster: simple_service
max_stream_duration:
grpc_timeout_header_max: 0s
cors:
allow_origin_string_match:
- prefix: "*"
allow_methods: GET, PUT, DELETE, POST, OPTIONS
allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout
max_age: "1728000"
expose_headers: custom-header-1,grpc-status,grpc-message
http_filters:
- name: envoy.filters.http.grpc_web
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.grpc_web.v3.GrpcWeb
- name: envoy.filters.http.cors
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.cors.v3.Cors
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
clusters:
- name: simple_service
connect_timeout: 0.25s
type: logical_dns
http2_protocol_options: {}
lb_policy: round_robin
load_assignment:
cluster_name: cluster_0
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
# address: 0.0.0.0
address: host.docker.internal
port_value: 4000
25 changes: 25 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"chroma-js": "2.4.2",
"electron-squirrel-startup": "1.0.0",
"electron-store": "8.1.0",
"grpc-web": "1.3.1",
"immer": "9.0.15",
"kbar": "0.1.0-beta.36",
"lodash": "4.17.21",
Expand All @@ -57,6 +58,7 @@
"react-select": "5.4.0",
"react-toastify": "9.0.8",
"react-use": "17.4.0",
"xhr2": "0.2.1",
"zustand": "4.1.1"
},
"devDependencies": {
Expand Down
75 changes: 75 additions & 0 deletions src/core/clients/grpc-web-client/grpc-web-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import type { MethodDefinition, PackageDefinition } from '@grpc/proto-loader';
import { GrpcWebClientBase, Metadata, MethodDescriptor, MethodType } from 'grpc-web';
import * as _ from 'lodash';

import { GrpcClientRequestOptions } from '../grpc-client/interfaces';

// https://github.com/grpc/grpc-web/issues/453#issuecomment-522022719
global.XMLHttpRequest = require('xhr2');

function instanceOfMethodDefinition(object: any): object is MethodDefinition<any, any> {
return 'requestSerialize' in object && 'responseDeserialize' in object;
}

export class GrpcWebClient {
private static loadClient() {
return new GrpcWebClientBase({ format: 'text' });
}

private static loadMethodDescriptor(
packageDefinition: PackageDefinition,
requestOptions: GrpcClientRequestOptions
) {
const service = _.get(packageDefinition, requestOptions.serviceName);

if (service) {
const method = _.get(service, requestOptions.methodName);

if (method && instanceOfMethodDefinition(method)) {
const methodDescriptor = new MethodDescriptor<
Record<string, unknown>,
Record<string, unknown>
>(
method.path,
method.requestStream ? MethodType.SERVER_STREAMING : MethodType.UNARY,
// @ts-ignore
method.requestType,
// @ts-ignore
method.responseType,
method.requestSerialize,
method.responseDeserialize
);

return methodDescriptor;
}

throw new Error('No method definition');
}

throw new Error('No service definition');
}

private static getMethodUrl(
requestOptions: GrpcClientRequestOptions,
methodDescriptor: MethodDescriptor<unknown, unknown>
) {
return `http://${requestOptions.address}${methodDescriptor.getName()}`;
}

static async invokeUnaryRequest(
packageDefinition: PackageDefinition,
requestOptions: GrpcClientRequestOptions,
payload: Record<string, unknown>,
metadata?: Metadata
): Promise<Record<string, unknown>> {
const client = this.loadClient();
const methodDescriptor = this.loadMethodDescriptor(packageDefinition, requestOptions);

return client.thenableCall<Record<string, unknown>, Record<string, unknown>>(
this.getMethodUrl(requestOptions, methodDescriptor),
payload,
metadata || {},
methodDescriptor
);
}
}
1 change: 1 addition & 0 deletions src/core/clients/grpc-web-client/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './grpc-web-client';

0 comments on commit 0e03b31

Please sign in to comment.