Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added experimental Websocket transport #137

Merged
merged 12 commits into from
Apr 10, 2018

Conversation

MarcusLongmuir
Copy link
Contributor

@MarcusLongmuir MarcusLongmuir commented Feb 7, 2018

This PR adds a Websocket transport that must be explicitly used via grpc.WebsocketTransportFactory (default transport factory does not include Websockets):

transport: grpc.WebsocketTransportFactory

The server compatibility with Websockets is also added behind an option:

websocketOriginFunc := grpcweb.WithWebsocketOriginFunc(func(req *http.Request) bool {
	return true
})
wrappedServer := grpcweb.WrapServer(grpcServer, grpcweb.WithWebsockets(true), websocketOriginFunc)

This is currently experimental as it has yet to be used in any production environment.

if strings.ToLower(k) == "trailer" {
continue
}
//// Skip existing headers that were already sent.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO

// First byte of a binary WebSocket frame is used for control flow:
// 0 = Data
// 1 = End of client send
func (w *WebSocketWrappedReader) Read(p []byte) (int, error) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here's the interesting bit

"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
"net/url"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This import should be with the other standard library imports

"bufio"
"bytes"
"encoding/binary"
"github.com/gorilla/websocket"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This import should not be with the standard library imports

return
}
}
resp.WriteHeader(400)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StatusBadRequest? Doesn't StatusForbidden (403) make more sense here? 403 instead of 401 because it's Forbidden from this Origin and not a matter of authentication.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, 403 is better.


wrappedReader := NewWebsocketWrappedReader(wsConn, respWriter)

req.Body = wrappedReader
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is mind blowing. Well done!

@easyCZ easyCZ mentioned this pull request Feb 16, 2018
@johanbrandhorst
Copy link
Contributor

What's the status of this? Doesn't look too bad to me but no updates for a week and a half.

@easyCZ
Copy link
Contributor

easyCZ commented Feb 16, 2018

I'm looking to do a review and some testing with this over the weekend. No concrete ETA on this yet.

@johanbrandhorst
Copy link
Contributor

Ok cool, I'll probably start integrating it into downstream (my gopherjs library) based on the contents of this branch, it's a much cleverer implementation than my current websocket proxy. Just excited to see this in here :).

@abemedia
Copy link

Awesome work done here! Correct me if I'm wrong but for this to be useful the grpc-web typescript packages would need updating to support websockets too, right?

Copy link
Contributor

@easyCZ easyCZ left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass review looks good. Nice job @MarcusLongmuir. Gonna try to use it as a user on a sample app before approving.

@@ -41,11 +46,25 @@ func WrapServer(server *grpc.Server, options ...Option) *WrappedGrpcServer {
AllowCredentials: true, // always allow credentials, otherwise :authorization headers won't work
MaxAge: int(10 * time.Minute / time.Second), // make sure pre-flights don't happen too often (every 5s for Chromium :( )
})
websocketOriginFunc := opts.websocketOriginFunc
if websocketOriginFunc == nil {
websocketOriginFunc = func(req *http.Request) bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we pull this into a separate function? Something like defaultWebsocketOriginFunc

return
}
}
resp.WriteHeader(400)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, 403 is better.

@@ -525,6 +528,181 @@ describe(`client`, () => {
});
});
});

if (!process.env.DISABLE_WEBSOCKET_TESTS) {
describe("client-streaming (websockets)", () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to pull this into a separate file?

@johanbrandhorst
Copy link
Contributor

Tested this in my downstream projects, works well :).

@@ -59,3 +60,7 @@ function detectTransport(): TransportConstructor {

throw new Error("No suitable transport found for gRPC-Web");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any chance we could add the WebsocketTransport to auto-detected transports?

Copy link
Contributor Author

@MarcusLongmuir MarcusLongmuir Mar 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to separate the implementation of the transport (this PR) from a discussion around how this should be used.

Currently the default transport factory just chooses the transport through a fallback process, but Websockets aren't necessarily the first in that list of transports to fallback through as they have some potentially undesirable tradeoffs.

I'd be happy to look at a PR that proposes a good standard way of using this transport after this lands 👍

@johanbrandhorst
Copy link
Contributor

// WithWebsockets allows for handling grpc-web requests of websockets - enabling bidirectional requests.
//
// The default behaviour is false, i.e. to disallow websockets
func WithWebsockets(enableWebsockets bool) Option {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be keen to just turn this on, it's not a backwards compatibility breaking change, and unless my suggestion to add Websockets to DetectTransport is implemented it won't change anything.

@johanbrandhorst
Copy link
Contributor

johanbrandhorst commented Mar 11, 2018

I've discovered a bit of an inconsistency with regards to headers:

When using the MozXHR transport, the headers received look like this (both in the case of unary and server streaming calls):

rawOnHeaders {
    headersMap: {
        "content-type": Array [ "application/octet-stream" ]​​
    }​
}

When using the websocket transport, the headers received look like this:

rawOnHeaders {
​    headersMap: {
        "content-type": Array [ "application/grpc" ]​​
        trailer: Array(3) [ "Grpc-Status", "Grpc-Message", "Grpc-Status-Details-Bin" ]​​
    }
}

So the curious thing to me is that we see the trailer in rawOnHeaders. Not sure we can or should do anything about it, but would be cool to hear your thoughts on this.

This is when using a Go gRPC Server as the backend.

@johanbrandhorst
Copy link
Contributor

Disregard my previous comment, it's not particularly interesting. I've tested this pretty extensively and would be keen to see it merged sooner rather than later :).

@DieMyst
Copy link

DieMyst commented Apr 10, 2018

hello. What is about this PR? No updates for 10 days. Can I use this branch for prototyping for now?

@johanbrandhorst
Copy link
Contributor

@MarcusLongmuir has been pushing things recently, so I remain hopeful this will be merged soon :)

@MarcusLongmuir
Copy link
Contributor Author

@johanbrandhorst + @DieMyst: Apologies for delay. Testing this has presented some issues - Browserstack's testing tunnel, websockets and self-signed certs have taken some effort to root cause and overcome. Hoping to get this merged ASAP.

@MarcusLongmuir MarcusLongmuir merged commit 6fb683f into improbable-eng:master Apr 10, 2018
@MarcusLongmuir MarcusLongmuir deleted the client-websockets branch April 10, 2018 15:54
@johanbrandhorst
Copy link
Contributor

Thanks for this, super excited to expose it in my downstream package :)

@DieMyst
Copy link

DieMyst commented Apr 11, 2018

Good news! Big thanks @MarcusLongmuir

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants