-
Notifications
You must be signed in to change notification settings - Fork 435
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
Added experimental Websocket transport #137
Conversation
go/grpcweb/websocket_wrapper.go
Outdated
if strings.ToLower(k) == "trailer" { | ||
continue | ||
} | ||
//// Skip existing headers that were already sent. |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
go/grpcweb/wrapper.go
Outdated
"google.golang.org/grpc" | ||
"google.golang.org/grpc/grpclog" | ||
"net/url" |
There was a problem hiding this comment.
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
go/grpcweb/websocket_wrapper.go
Outdated
"bufio" | ||
"bytes" | ||
"encoding/binary" | ||
"github.com/gorilla/websocket" |
There was a problem hiding this comment.
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
go/grpcweb/wrapper.go
Outdated
return | ||
} | ||
} | ||
resp.WriteHeader(400) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, 403 is better.
go/grpcweb/wrapper.go
Outdated
|
||
wrappedReader := NewWebsocketWrappedReader(wsConn, respWriter) | ||
|
||
req.Body = wrappedReader |
There was a problem hiding this comment.
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!
What's the status of this? Doesn't look too bad to me but no updates for a week and a half. |
I'm looking to do a review and some testing with this over the weekend. No concrete ETA on this yet. |
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 :). |
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? |
There was a problem hiding this 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 { |
There was a problem hiding this comment.
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
go/grpcweb/wrapper.go
Outdated
return | ||
} | ||
} | ||
resp.WriteHeader(400) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, 403 is better.
test/ts/src/client.spec.ts
Outdated
@@ -525,6 +528,181 @@ describe(`client`, () => { | |||
}); | |||
}); | |||
}); | |||
|
|||
if (!process.env.DISABLE_WEBSOCKET_TESTS) { | |||
describe("client-streaming (websockets)", () => { |
There was a problem hiding this comment.
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?
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"); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 👍
// 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 { |
There was a problem hiding this comment.
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.
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):
When using the websocket transport, the headers received look like this:
So the curious thing to me is that we see the This is when using a Go gRPC Server as the backend. |
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 :). |
8495203
to
4ff05c1
Compare
4ff05c1
to
fce5c0f
Compare
56ad3d0
to
ee7ec9b
Compare
hello. What is about this PR? No updates for 10 days. Can I use this branch for prototyping for now? |
@MarcusLongmuir has been pushing things recently, so I remain hopeful this will be merged soon :) |
@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. |
… to avoid failOnEmptyTestSuite usage
Thanks for this, super excited to expose it in my downstream package :) |
Good news! Big thanks @MarcusLongmuir |
This PR adds a Websocket transport that must be explicitly used via
grpc.WebsocketTransportFactory
(default transport factory does not include Websockets):The server compatibility with Websockets is also added behind an option:
This is currently experimental as it has yet to be used in any production environment.