Skip to content

Commit

Permalink
Refactored into client-based
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcusLongmuir committed Feb 6, 2018
1 parent 2d3f53b commit fb8346b
Show file tree
Hide file tree
Showing 62 changed files with 4,933 additions and 1,689 deletions.
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
8.9.4
5 changes: 4 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@ env:
- BROWSER=firefox53_osx
- BROWSER=firefox39_osx
- BROWSER=firefox38_osx
- BROWSER=firefox21_osx
- BROWSER=chrome_57
- BROWSER=chrome_52
- BROWSER=chrome_43
- BROWSER=chrome_42
- BROWSER=chrome_41
- BROWSER=safari11
- BROWSER=safari9_1
- BROWSER=safari8
- BROWSER=safari6
cache:
directories:
- node_modules
Expand All @@ -39,7 +42,7 @@ install:
- go get github.com/golang/protobuf/protoc-gen-go
- dep ensure
- export PATH=/home/travis/gopath/src/github.com/improbable-eng/grpc-web/protobuf/bin:$PATH
- rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) && source ~/.nvm/nvm.sh && nvm install 6
- nvm install
- npm install
before_script:
- ./test/start-testserver.sh &
Expand Down
24 changes: 12 additions & 12 deletions Gopkg.lock

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

55 changes: 33 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,38 @@

[gRPC-Web](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md) is a cutting-edge spec that enables invoking gRPC services from *modern* browsers.

*__Please note - Whilst this package supports Node.js, there is an [official Node.js gRPC library](https://www.npmjs.com/package/grpc) that does not require the server to support gRPC-Web__*
*__If you are looking for gRPC support for Node.js there is an [official Node.js gRPC library](https://www.npmjs.com/package/grpc). This package supports Node.js, but requires that the server has the gRPC-Web compatibility layer (read on to understand more).__*

Components of the stack are based on Golang and TypeScript:
* [`grpcweb`](go/grpcweb) - a Go package that wraps an existing `grpc.Server` as a gRPC-Web `http.Handler` for both HTTP2 and HTTP/1.1
* [`grpcwebproxy`](go/grpcwebproxy) - a Go-based stand-alone reverse proxy for classic gRPC servers (e.g. in Java or C++) that exposes their services over gRPC-Web to modern browsers
* [`ts-protoc-gen`](https://github.com/improbable-eng/ts-protoc-gen) - a TypeScript plugin for the protocol buffers compiler that provides strongly typed message classes and method definitions
* [`grpc-web-client`](ts) - a TypeScript gRPC-Web client library for browsers ([and Node.js](#nodejs-support)).

* [`grpcweb`](go/grpcweb) - a Go package that wraps an existing `grpc.Server` as a gRPC-Web `http.Handler` for both HTTP2 and HTTP/1.1.
* [`grpcwebproxy`](go/grpcwebproxy) - a Go-based stand-alone reverse proxy for classic gRPC servers (e.g. in Java or C++) that exposes their services over gRPC-Web to modern browsers.
* [`ts-protoc-gen`](https://github.com/improbable-eng/ts-protoc-gen) - a TypeScript plugin for the protocol buffers compiler that provides strongly typed message classes and method definitions.
* [`grpc-web-client`](ts) - a TypeScript gRPC-Web client library for browsers ([and Node.js](#nodejs-support)).

## Why?

With gRPC-Web, it is extremely easy to build well-defined, easy to reason about APIs between browser frontend code and microservices. Frontend development changes significantly:
* no more hunting down API documentation - `.proto` is the canonical format for API contracts
* no more hand-crafted JSON call objects - all requests and responses are strongly typed and code-generated, with hints available in the IDE
* no more dealing with methods, headers, body and low level networking - everything is handled by `grpc.invoke`
* no more second-guessing the meaning of error codes - [gRPC status codes](https://godoc.org/google.golang.org/grpc/codes) are a canonical way of representing issues in APIs
* no more one-off server-side request handlers to avoid concurrent connections - gRPC-Web is based on HTTP2, with multiplexes multiple streams over the [same connection](https://hpbn.co/http2/#streams-messages-and-frames)
* no more problems streaming data from a server - gRPC-Web supports both *1:1* RPCs and *1:many* streaming requests
* no more data parse errors when rolling out new binaries - backwards and forwards-[compatibility](https://developers.google.com/protocol-buffers/docs/gotutorial#extending-a-protocol-buffer) of requests and responses

In short, gRPC-Web moves the interaction between frontend code and microservices from the sphere of hand-crafted HTTP requests to well-defined user-logic methods.
* no more hunting down API documentation - `.proto` is the canonical format for API contracts.
* no more hand-crafted JSON call objects - all requests and responses are strongly typed and code-generated, with hints available in the IDE.
* no more dealing with methods, headers, body and low level networking - everything is handled by `grpc.invoke`.
* no more second-guessing the meaning of error codes - [gRPC status codes](https://godoc.org/google.golang.org/grpc/codes) are a canonical way of representing issues in APIs.
* no more one-off server-side request handlers to avoid concurrent connections - gRPC-Web is based on HTTP2, with multiplexes multiple streams over the [same connection](https://hpbn.co/http2/#streams-messages-and-frames).
* no more problems streaming data from a server - gRPC-Web supports both *1:1* RPCs and *1:many* streaming requests.
* no more data parse errors when rolling out new binaries - [backwards and forwards-compatibility](https://developers.google.com/protocol-buffers/docs/gotutorial#extending-a-protocol-buffer) of requests and responses.

In short, gRPC-Web moves the interaction between frontend code and microservices from the sphere of hand-crafted HTTP requests to well-defined user-logic methods.

## Client-side (grpc-web-client) Docs

**Note: You'll need to add gRPC-Web compatibility to your server through either [`grpcweb`](go/grpcweb) or [`grpcwebproxy`](go/grpcwebproxy).**

[API Docs for `grpc-web-client` can be found here](ts)

## Example

For a self-contained demo of a Golang gRPC service called from a TypeScript projects, see [example](example). It contains most of the initialization code that performs the magic. Here's the application code extracted from the example:
For a self-contained demo of a Golang gRPC service called from a TypeScript project, see [example](example). It contains most of the initialization code that performs the magic. Here's the application code extracted from the example:

You use `.proto` files to define your service. In this example, one normal RPC (`GetBook`) and one server-streaming RPC (`QueryBooks`):

Expand Down Expand Up @@ -93,8 +100,8 @@ func (s *bookService) QueryBooks(bookQuery *pb_library.QueryBooksRequest, stream

You will be able to access it in a browser using TypeScript (and equally JavaScript after transpiling):

```ts
import {grpc, BrowserHeaders, Code} from "grpc-web-client";
```typescript
import {grpc} from "grpc-web-client";

// Import code-generated data structures.
import {BookService} from "../_proto/examplecom/library/book_service_pb_service";
Expand All @@ -108,8 +115,8 @@ grpc.invoke(BookService.QueryBooks, {
onMessage: (message: Book) => {
console.log("got book: ", message.toObject());
},
onEnd: (code: Code, msg: string | undefined, trailers: BrowserHeaders) => {
if (code == Code.OK) {
onEnd: (code: grpc.Code, msg: string | undefined, trailers: grpc.Metadata) => {
if (code == grpc.Code.OK) {
console.log("all ok")
} else {
console.log("hit an error", code, msg, trailers);
Expand All @@ -126,14 +133,14 @@ The gRPC semantics encourage you to make multiple requests at once. With most mo

This library is tested against:
* Chrome >= 41
* Firefox >= 38
* Firefox >= 21
* Edge >= 13
* IE >= 11
* Safari >= 8
* Safari >= 6

## Node.js Support

`grpc-web-client` also supports Node.js through a transport that uses the `http` and `https` packages. Usage does not vary from browser usage as transport is determined at runtime.
`grpc-web-client` also [supports Node.js through a transport](ts/docs/transport#Node-HTTP) that uses the `http` and `https` packages. Usage does not vary from browser usage as transport is determined at runtime.

*__Please note - There is an [official Node.js gRPC library](https://www.npmjs.com/package/grpc) that does not require the server to support gRPC-Web__*

Expand All @@ -153,7 +160,11 @@ The code here is `alpha` quality. It is being used for a subset of Improbable's

### Server-side streaming with XHR

Browsers that don't support [Fetch with `body.getReader`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) (Currently only Edge 14+ and Chrome 43+ - full ReadableStream was added in Chrome 52, but only `body.getReader()` is used) or `XMLHttpRequest.responseType = moz-chunked-arraybuffer` (Firefox 38+) use [XmlHttpRequest (XHR)](https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest). XHR keeps the entire server response in memory. This means that a long-lived or otherwise large streaming response will consume a large amount of memory in the browser and may cause instability. Fetch does not suffer from this issue. It is therefore advised that you don't use open-ended or large payload server streaming if you intend to support browsers that do not support Fetch.
Browsers that don't support [Fetch with `body.getReader`](https://developer.mozilla.org/en-US/docs/Web/API/ReadableStream) (Currently only supported by Edge 14+, Chrome 43+ - full ReadableStream was added in Chrome 52, but only `body.getReader()` is used) or `XMLHttpRequest.responseType = moz-chunked-arraybuffer` (Firefox 38+) use [XmlHttpRequest (XHR)](https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest).

XHR keeps the entire server response in memory. This means that a long-lived or otherwise large streaming response will consume a large amount of memory in the browser and may cause instability. Fetch does not suffer from this issue. It is therefore advised that you don't use open-ended or large payload server streaming if you intend to support browsers that do not support Fetch.

You can read more about how grpc-web-client determines and uses transports [here](ts/docs/transport).

### Running the tests

Expand Down
2 changes: 1 addition & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"dependencies": {
"@types/google-protobuf": "^3.2.5",
"google-protobuf": "^3.2.0",
"grpc-web-client": "0.3.0"
"grpc-web-client": "0.4.0"
},
"devDependencies": {
"concurrently": "^3.4.0",
Expand Down
8 changes: 8 additions & 0 deletions example/ts/_proto/examplecom/library/book_service_pb.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/**
* @fileoverview
* @enhanceable
* @suppress {messageConventions} JS Compiler reports an error if a variable or
* field starts with 'MSG_' and isn't a translatable message.
* @public
*/
// GENERATED CODE -- DO NOT EDIT!
Expand Down Expand Up @@ -55,6 +57,7 @@ proto.examplecom.library.Book.prototype.toObject = function(opt_includeInstance)
* http://goto/soy-param-migration
* @param {!proto.examplecom.library.Book} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.examplecom.library.Book.toObject = function(includeInstance, msg) {
var f, obj = {
Expand Down Expand Up @@ -134,6 +137,7 @@ proto.examplecom.library.Book.prototype.serializeBinary = function() {
* format), writing to the given BinaryWriter.
* @param {!proto.examplecom.library.Book} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.examplecom.library.Book.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
Expand Down Expand Up @@ -249,6 +253,7 @@ proto.examplecom.library.GetBookRequest.prototype.toObject = function(opt_includ
* http://goto/soy-param-migration
* @param {!proto.examplecom.library.GetBookRequest} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.examplecom.library.GetBookRequest.toObject = function(includeInstance, msg) {
var f, obj = {
Expand Down Expand Up @@ -318,6 +323,7 @@ proto.examplecom.library.GetBookRequest.prototype.serializeBinary = function() {
* format), writing to the given BinaryWriter.
* @param {!proto.examplecom.library.GetBookRequest} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.examplecom.library.GetBookRequest.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
Expand Down Expand Up @@ -389,6 +395,7 @@ proto.examplecom.library.QueryBooksRequest.prototype.toObject = function(opt_inc
* http://goto/soy-param-migration
* @param {!proto.examplecom.library.QueryBooksRequest} msg The msg instance to transform.
* @return {!Object}
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.examplecom.library.QueryBooksRequest.toObject = function(includeInstance, msg) {
var f, obj = {
Expand Down Expand Up @@ -458,6 +465,7 @@ proto.examplecom.library.QueryBooksRequest.prototype.serializeBinary = function(
* format), writing to the given BinaryWriter.
* @param {!proto.examplecom.library.QueryBooksRequest} message
* @param {!jspb.BinaryWriter} writer
* @suppress {unusedLocalVariables} f is only used for nested messages
*/
proto.examplecom.library.QueryBooksRequest.serializeBinaryToWriter = function(message, writer) {
var f = undefined;
Expand Down
27 changes: 14 additions & 13 deletions example/ts/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {grpc, Code, Metadata} from "grpc-web-client";
import {grpc} from "grpc-web-client";
import {BookService} from "../_proto/examplecom/library/book_service_pb_service";
import {QueryBooksRequest, Book, GetBookRequest} from "../_proto/examplecom/library/book_service_pb";

Expand All @@ -15,7 +15,7 @@ function getBook() {
const { status, statusMessage, headers, message, trailers } = res;
console.log("getBook.onEnd.status", status, statusMessage);
console.log("getBook.onEnd.headers", headers);
if (status === Code.OK && message) {
if (status === grpc.Code.OK && message) {
console.log("getBook.onEnd.message", message.toObject());
}
console.log("getBook.onEnd.trailers", trailers);
Expand All @@ -29,17 +29,18 @@ getBook();
function queryBooks() {
const queryBooksRequest = new QueryBooksRequest();
queryBooksRequest.setAuthorPrefix("Geor");
grpc.invoke(BookService.QueryBooks, {
request: queryBooksRequest,
const client = grpc.client(BookService.QueryBooks, {
host: host,
onHeaders: (headers: Metadata) => {
console.log("queryBooks.onHeaders", headers);
},
onMessage: (message: Book) => {
console.log("queryBooks.onMessage", message.toObject());
},
onEnd: (code: Code, msg: string, trailers: Metadata) => {
console.log("queryBooks.onEnd", code, msg, trailers);
}
});
client.onHeaders((headers: grpc.Metadata) => {
console.log("queryBooks.onHeaders", headers);
});
client.onMessage((message: Book) => {
console.log("queryBooks.onMessage", message.toObject());
});
client.onEnd((code: grpc.Code, msg: string, trailers: grpc.Metadata) => {
console.log("queryBooks.onEnd", code, msg, trailers);
});
client.start();
client.send(queryBooksRequest);
}
3 changes: 3 additions & 0 deletions go/grpcweb/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ func TestListGRPCResources(t *testing.T) {
"/improbable.grpcweb.test.TestService/Ping",
"/improbable.grpcweb.test.TestService/PingError",
"/improbable.grpcweb.test.TestService/PingList",
"/improbable.grpcweb.test.TestService/Echo",
"/improbable.grpcweb.test.TestService/PingPongBidi",
"/improbable.grpcweb.test.TestService/PingStream",
}
actual := grpcweb.ListGRPCResources(server)
sort.Strings(expected)
Expand Down
14 changes: 8 additions & 6 deletions go/grpcweb/wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,10 @@ var (
)

type WrappedGrpcServer struct {
server *grpc.Server
opts *options
corsWrapper *cors.Cors
server *grpc.Server
opts *options
corsWrapper *cors.Cors
originFunc func(origin string) bool
}

// WrapServer takes a gRPC Server in Go and returns a WrappedGrpcServer that provides gRPC-Web Compatibility.
Expand All @@ -41,9 +42,10 @@ func WrapServer(server *grpc.Server, options ...Option) *WrappedGrpcServer {
MaxAge: int(10 * time.Minute / time.Second), // make sure pre-flights don't happen too often (every 5s for Chromium :( )
})
return &WrappedGrpcServer{
server: server,
opts: opts,
corsWrapper: corsWrapper,
server: server,
opts: opts,
corsWrapper: corsWrapper,
originFunc: opts.originFunc,
}
}

Expand Down
Loading

0 comments on commit fb8346b

Please sign in to comment.