wasmws: Web-Assembly Websocket (for Go)
What is wasmws? Why would I want to use this?
wasmws was written primarily to allow Go applications targeting WASM to communicate with a gRPC server. This is normally challenging for two reasons:
- In general, many internet ingress paths are not HTTP/2 end to end (gRPC uses HTTP/2). In particular, most CDN vendors do not support HTTP/2 back-haul to origin (ex. Cloudflare).
- Browser WASM applications cannot use grpc-go due to the low level networking that go-grpc requires for native operation not being available. (ex.
dial tcp: Protocol not available
fun...)
This library allows you to use a websocket as net.Conn for arbitrary traffic, this includes running protocols like HTTP, gRPC or any other TCP protocol over it). This is most useful for protocols that are not normally exposed to client side web applications. In our examples we will focus on gRPC since that was my use-case.
Aproach taken
Client-side (Go WASM application running in browser)
wasmws provides Go WASM specific net.Conn implementation that is backed by a browser native websocket:
myConn, err := wasmws.Dial("websocket", "ws://demos.kaazing.com/echo")
It is fairly straight forward to use this package to set up a gRPC connection:
conn, err := grpc.DialContext(dialCtx, "passthrough:///"+websocketURL, grpc.WithContextDialer(wasmws.GRPCDialer), grpc.WithTransportCredentials(creds))
See the demo client for an extended example.
Server-side
wasmws includes websocket net.Listener that provides a HTTP handler method to accept HTTP websocket connections...
wsl := wasmws.NewWebSocketListener(appCtx)
router.HandleFunc("/grpc-proxy", wsl.HTTPAccept)
And a network listener to provide net.Conns to network oriented servers:
err := grpcServer.Serve(wsl)
See the demo server for an extended example. If you need more server-side helpers checkout nhooyr.io/websocket which these helpers use themselves.
Security
If you use a secure websocket and gRPC or HTTPS this means you get double TLS (once using the browser's TLS stack and once again using Go's). Unless the extra defense in depth is desirable, you may want to consider using an unsecured websocket.
Performance
Due to challenges around having the sever running as a native application and the client running in a browser coordinate, I have not yet added unit benches... :( yet. However, running the demo which performs 8192 gRPC hello world calls provides an idea of the library's performance:
Median of 6 runs:
- FireFox 71.0 (64-bit) on Linux:
SUCCESS running 8192 transactions! (average 452.88µs per operation)
- Chrome Version 79.0.3945.88 (Official Build) (64-bit) on Linux:
SUCCESS running 8192 transactions! (average 475.485µs per operation)
This implementation tries to be intelligent about managing buffers (via pooling) and switches on the fly between JavaScript ArrayBuffer and streaming Blob based websocket read interfaces based the size of the chunks/messages being received. Web browsers which do not support Blob stream and Blob arrayBuffer methods, such as Microsoft Edge, always use ArrayBuffer-based message consumption.
The test results above are from tests run on a local development workstation:
- CPU: AMD Ryzen 9 3900X 12-Core Processor
- OS: Ubuntu 18.04 LTS (Linux 4.15.X)
Running the demo
If you do not have Go installed, you will of course need to install it.
- Checkout repo:
git clone https://github.com/tarndt/wasmws.git
- Change to the demo directory:
cd ./wasmws/demo/server
- Build the client, server and run the server (which serves the client):
./build.bash && ./server
- Open http://localhost:8080/ in your web browser
- Open the web console (Ctrl+Shift+K in Firefox, Ctrl+Shift+I in Chrome) and observe the output!
Alternatives
- Use gRPC-Web as a HTTP to gRPC gateway/proxy. (If you don't mind a TCP connection per request, running extra middleware which are also extra points of failure...)
- Use "nhooyr.io/websocket"'s implementation which unlike "wasmws" does not use the browser provided websocket functionality. Test and bench your own use-case!
Future
wasmws is actively being maintained, but that does not mean there are not things to do:
Contributing
Issues, and especially issues with pull requests are welcome!
This code is licensed under MPL 2.0.