mirror of
https://github.com/taigrr/wasm-experiments
synced 2025-01-18 04:03:21 -08:00
Add experimental gRPC-Web folder
This commit is contained in:
174
vendor/github.com/improbable-eng/grpc-web/LICENSE.txt
generated
vendored
Normal file
174
vendor/github.com/improbable-eng/grpc-web/LICENSE.txt
generated
vendored
Normal file
@@ -0,0 +1,174 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
211
vendor/github.com/improbable-eng/grpc-web/go/grpcweb/DOC.md
generated
vendored
Normal file
211
vendor/github.com/improbable-eng/grpc-web/go/grpcweb/DOC.md
generated
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
# grpcweb
|
||||
--
|
||||
import "github.com/improbable-eng/grpc-web/go/grpcweb"
|
||||
|
||||
`grpcweb` implements the gRPC-Web spec as a wrapper around a gRPC-Go Server.
|
||||
|
||||
It allows web clients (see companion JS library) to talk to gRPC-Go servers over
|
||||
the gRPC-Web spec. It supports HTTP/1.1 and HTTP2 encoding of a gRPC stream and
|
||||
supports unary and server-side streaming RPCs. Bi-di and client streams are
|
||||
unsupported due to limitations in browser protocol support.
|
||||
|
||||
See https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md for the
|
||||
protocol specification.
|
||||
|
||||
Here's an example of how to use it inside an existing gRPC Go server on a
|
||||
separate http.Server that serves over TLS:
|
||||
|
||||
grpcServer := grpc.Server()
|
||||
wrappedGrpc := grpcweb.WrapServer(grpcServer)
|
||||
tlsHttpServer.Handler = http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
if wrappedGrpc.IsGrpcWebRequest(req) {
|
||||
wrappedGrpc.ServeHTTP(resp, req)
|
||||
}
|
||||
// Fall back to other servers.
|
||||
http.DefaultServeMux.ServeHTTP(resp, req)
|
||||
})
|
||||
|
||||
If you'd like to have a standalone binary, please take a look at `grpcwebproxy`.
|
||||
|
||||
## Usage
|
||||
|
||||
#### func ListGRPCResources
|
||||
|
||||
```go
|
||||
func ListGRPCResources(server *grpc.Server) []string
|
||||
```
|
||||
ListGRPCResources is a helper function that lists all URLs that are registered
|
||||
on gRPC server.
|
||||
|
||||
This makes it easy to register all the relevant routes in your HTTP router of
|
||||
choice.
|
||||
|
||||
#### type Option
|
||||
|
||||
```go
|
||||
type Option func(*options)
|
||||
```
|
||||
|
||||
|
||||
#### func WithAllowedRequestHeaders
|
||||
|
||||
```go
|
||||
func WithAllowedRequestHeaders(headers []string) Option
|
||||
```
|
||||
WithAllowedRequestHeaders allows for customizing what gRPC request headers a
|
||||
browser can add.
|
||||
|
||||
This is controlling the CORS pre-flight `Access-Control-Allow-Headers` method
|
||||
and applies to *all* gRPC handlers. However, a special `*` value can be passed
|
||||
in that allows the browser client to provide *any* header, by explicitly
|
||||
whitelisting all `Access-Control-Request-Headers` of the pre-flight request.
|
||||
|
||||
The default behaviour is `[]string{'*'}`, allowing all browser client headers.
|
||||
This option overrides that default, while maintaining a whitelist for
|
||||
gRPC-internal headers.
|
||||
|
||||
Unfortunately, since the CORS pre-flight happens independently from gRPC handler
|
||||
execution, it is impossible to automatically discover it from the gRPC handler
|
||||
itself.
|
||||
|
||||
The relevant CORS pre-flight docs:
|
||||
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
|
||||
|
||||
#### func WithCorsForRegisteredEndpointsOnly
|
||||
|
||||
```go
|
||||
func WithCorsForRegisteredEndpointsOnly(onlyRegistered bool) Option
|
||||
```
|
||||
WithCorsForRegisteredEndpointsOnly allows for customizing whether OPTIONS
|
||||
requests with the `X-GRPC-WEB` header will only be accepted if they match a
|
||||
registered gRPC endpoint.
|
||||
|
||||
This should be set to false to allow handling gRPC requests for unknown
|
||||
endpoints (e.g. for proxying).
|
||||
|
||||
The default behaviour is `true`, i.e. only allows CORS requests for registered
|
||||
endpoints.
|
||||
|
||||
#### func WithOriginFunc
|
||||
|
||||
```go
|
||||
func WithOriginFunc(originFunc func(origin string) bool) Option
|
||||
```
|
||||
WithOriginFunc allows for customizing what CORS Origin requests are allowed.
|
||||
|
||||
This is controlling the CORS pre-flight `Access-Control-Allow-Origin`. This
|
||||
mechanism allows you to limit the availability of the APIs based on the domain
|
||||
name of the calling website (Origin). You can provide a function that filters
|
||||
the allowed Origin values.
|
||||
|
||||
The default behaviour is `*`, i.e. to allow all calling websites.
|
||||
|
||||
The relevant CORS pre-flight docs:
|
||||
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
|
||||
|
||||
#### func WithWebsocketOriginFunc
|
||||
|
||||
```go
|
||||
func WithWebsocketOriginFunc(websocketOriginFunc func(req *http.Request) bool) Option
|
||||
```
|
||||
WithWebsocketOriginFunc allows for customizing the acceptance of Websocket
|
||||
requests - usually to check that the origin is valid.
|
||||
|
||||
The default behaviour is to check that the origin of the request matches the
|
||||
host of the request.
|
||||
|
||||
#### func WithWebsockets
|
||||
|
||||
```go
|
||||
func WithWebsockets(enableWebsockets bool) Option
|
||||
```
|
||||
WithWebsockets allows for handling grpc-web requests of websockets - enabling
|
||||
bidirectional requests.
|
||||
|
||||
The default behaviour is false, i.e. to disallow websockets
|
||||
|
||||
#### type WrappedGrpcServer
|
||||
|
||||
```go
|
||||
type WrappedGrpcServer struct {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
#### func WrapServer
|
||||
|
||||
```go
|
||||
func WrapServer(server *grpc.Server, options ...Option) *WrappedGrpcServer
|
||||
```
|
||||
WrapServer takes a gRPC Server in Go and returns a WrappedGrpcServer that
|
||||
provides gRPC-Web Compatibility.
|
||||
|
||||
The internal implementation fakes out a http.Request that carries standard gRPC,
|
||||
and performs the remapping inside http.ResponseWriter, i.e. mostly the
|
||||
re-encoding of Trailers (that carry gRPC status).
|
||||
|
||||
You can control the behaviour of the wrapper (e.g. modifying CORS behaviour)
|
||||
using `With*` options.
|
||||
|
||||
#### func (*WrappedGrpcServer) HandleGrpcWebRequest
|
||||
|
||||
```go
|
||||
func (w *WrappedGrpcServer) HandleGrpcWebRequest(resp http.ResponseWriter, req *http.Request)
|
||||
```
|
||||
HandleGrpcWebRequest takes a HTTP request that is assumed to be a gRPC-Web
|
||||
request and wraps it with a compatibility layer to transform it to a standard
|
||||
gRPC request for the wrapped gRPC server and transforms the response to comply
|
||||
with the gRPC-Web protocol.
|
||||
|
||||
#### func (*WrappedGrpcServer) HandleGrpcWebsocketRequest
|
||||
|
||||
```go
|
||||
func (w *WrappedGrpcServer) HandleGrpcWebsocketRequest(resp http.ResponseWriter, req *http.Request)
|
||||
```
|
||||
HandleGrpcWebsocketRequest takes a HTTP request that is assumed to be a
|
||||
gRPC-Websocket request and wraps it with a compatibility layer to transform it
|
||||
to a standard gRPC request for the wrapped gRPC server and transforms the
|
||||
response to comply with the gRPC-Web protocol.
|
||||
|
||||
#### func (*WrappedGrpcServer) IsAcceptableGrpcCorsRequest
|
||||
|
||||
```go
|
||||
func (w *WrappedGrpcServer) IsAcceptableGrpcCorsRequest(req *http.Request) bool
|
||||
```
|
||||
IsAcceptableGrpcCorsRequest determines if a request is a CORS pre-flight request
|
||||
for a gRPC-Web request and that this request is acceptable for CORS.
|
||||
|
||||
You can control the CORS behaviour using `With*` options in the WrapServer
|
||||
function.
|
||||
|
||||
#### func (*WrappedGrpcServer) IsGrpcWebRequest
|
||||
|
||||
```go
|
||||
func (w *WrappedGrpcServer) IsGrpcWebRequest(req *http.Request) bool
|
||||
```
|
||||
IsGrpcWebRequest determines if a request is a gRPC-Web request by checking that
|
||||
the "content-type" is "application/grpc-web" and that the method is POST.
|
||||
|
||||
#### func (*WrappedGrpcServer) IsGrpcWebSocketRequest
|
||||
|
||||
```go
|
||||
func (w *WrappedGrpcServer) IsGrpcWebSocketRequest(req *http.Request) bool
|
||||
```
|
||||
IsGrpcWebSocketRequest determines if a request is a gRPC-Web request by checking
|
||||
that the "Sec-Websocket-Protocol" header value is "grpc-websockets"
|
||||
|
||||
#### func (*WrappedGrpcServer) ServeHTTP
|
||||
|
||||
```go
|
||||
func (w *WrappedGrpcServer) ServeHTTP(resp http.ResponseWriter, req *http.Request)
|
||||
```
|
||||
ServeHTTP takes a HTTP request and if it is a gRPC-Web request wraps it with a
|
||||
compatibility layer to transform it to a standard gRPC request for the wrapped
|
||||
gRPC server and transforms the response to comply with the gRPC-Web protocol.
|
||||
|
||||
The gRPC-Web compatibility is only invoked if the request is a gRPC-Web request
|
||||
as determined by IsGrpcWebRequest or the request is a pre-flight (CORS) request
|
||||
as determined by IsAcceptableGrpcCorsRequest.
|
||||
|
||||
You can control the CORS behaviour using `With*` options in the WrapServer
|
||||
function.
|
||||
1
vendor/github.com/improbable-eng/grpc-web/go/grpcweb/README.md
generated
vendored
Symbolic link
1
vendor/github.com/improbable-eng/grpc-web/go/grpcweb/README.md
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
DOC.md
|
||||
28
vendor/github.com/improbable-eng/grpc-web/go/grpcweb/doc.go
generated
vendored
Normal file
28
vendor/github.com/improbable-eng/grpc-web/go/grpcweb/doc.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2017 Improbable. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
/*
|
||||
`grpcweb` implements the gRPC-Web spec as a wrapper around a gRPC-Go Server.
|
||||
|
||||
It allows web clients (see companion JS library) to talk to gRPC-Go servers over the gRPC-Web spec. It supports
|
||||
HTTP/1.1 and HTTP2 encoding of a gRPC stream and supports unary and server-side streaming RPCs. Bi-di and client
|
||||
streams are unsupported due to limitations in browser protocol support.
|
||||
|
||||
See https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md for the protocol specification.
|
||||
|
||||
Here's an example of how to use it inside an existing gRPC Go server on a separate http.Server that serves over TLS:
|
||||
|
||||
grpcServer := grpc.Server()
|
||||
wrappedGrpc := grpcweb.WrapServer(grpcServer)
|
||||
tlsHttpServer.Handler = http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
|
||||
if wrappedGrpc.IsGrpcWebRequest(req) {
|
||||
wrappedGrpc.ServeHTTP(resp, req)
|
||||
}
|
||||
// Fall back to other servers.
|
||||
http.DefaultServeMux.ServeHTTP(resp, req)
|
||||
})
|
||||
|
||||
If you'd like to have a standalone binary, please take a look at `grpcwebproxy`.
|
||||
|
||||
*/
|
||||
package grpcweb
|
||||
138
vendor/github.com/improbable-eng/grpc-web/go/grpcweb/grpc_web_response.go
generated
vendored
Normal file
138
vendor/github.com/improbable-eng/grpc-web/go/grpcweb/grpc_web_response.go
generated
vendored
Normal file
@@ -0,0 +1,138 @@
|
||||
//Copyright 2017 Improbable. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
package grpcweb
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
// grpcWebResponse implements http.ResponseWriter.
|
||||
type grpcWebResponse struct {
|
||||
wroteHeaders bool
|
||||
wroteBody bool
|
||||
headers http.Header
|
||||
wrapped http.ResponseWriter
|
||||
}
|
||||
|
||||
func newGrpcWebResponse(resp http.ResponseWriter) *grpcWebResponse {
|
||||
return &grpcWebResponse{headers: make(http.Header), wrapped: resp}
|
||||
}
|
||||
|
||||
func (w *grpcWebResponse) Header() http.Header {
|
||||
return w.headers
|
||||
}
|
||||
|
||||
func (w *grpcWebResponse) Write(b []byte) (int, error) {
|
||||
w.wroteBody = true
|
||||
return w.wrapped.Write(b)
|
||||
}
|
||||
|
||||
func (w *grpcWebResponse) WriteHeader(code int) {
|
||||
w.copyJustHeadersToWrapped()
|
||||
w.writeCorsExposedHeaders()
|
||||
w.wrapped.WriteHeader(code)
|
||||
w.wroteHeaders = true
|
||||
}
|
||||
|
||||
func (w *grpcWebResponse) Flush() {
|
||||
if w.wroteHeaders || w.wroteBody {
|
||||
// Work around the fact that WriteHeader and a call to Flush would have caused a 200 response.
|
||||
// This is the case when there is no payload.
|
||||
w.wrapped.(http.Flusher).Flush()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *grpcWebResponse) CloseNotify() <-chan bool {
|
||||
return w.wrapped.(http.CloseNotifier).CloseNotify()
|
||||
}
|
||||
|
||||
func (w *grpcWebResponse) copyJustHeadersToWrapped() {
|
||||
wrappedHeader := w.wrapped.Header()
|
||||
for k, vv := range w.headers {
|
||||
// Skip the pre-annoucement of Trailer headers. Don't add them to the response headers.
|
||||
if strings.ToLower(k) == "trailer" {
|
||||
continue
|
||||
}
|
||||
for _, v := range vv {
|
||||
wrappedHeader.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *grpcWebResponse) finishRequest(req *http.Request) {
|
||||
if w.wroteHeaders || w.wroteBody {
|
||||
w.copyTrailersToPayload()
|
||||
} else {
|
||||
w.copyTrailersAndHeadersToWrapped()
|
||||
}
|
||||
}
|
||||
|
||||
func (w *grpcWebResponse) copyTrailersAndHeadersToWrapped() {
|
||||
w.wroteHeaders = true
|
||||
wrappedHeader := w.wrapped.Header()
|
||||
for k, vv := range w.headers {
|
||||
// Skip the pre-annoucement of Trailer headers. Don't add them to the response headers.
|
||||
if strings.ToLower(k) == "trailer" {
|
||||
continue
|
||||
}
|
||||
// Skip the Trailer prefix
|
||||
if strings.HasPrefix(k, http2.TrailerPrefix) {
|
||||
k = k[len(http2.TrailerPrefix):]
|
||||
}
|
||||
for _, v := range vv {
|
||||
wrappedHeader.Add(k, v)
|
||||
}
|
||||
}
|
||||
w.writeCorsExposedHeaders()
|
||||
w.wrapped.WriteHeader(http.StatusOK)
|
||||
w.wrapped.(http.Flusher).Flush()
|
||||
}
|
||||
|
||||
func (w *grpcWebResponse) writeCorsExposedHeaders() {
|
||||
// These cors handlers are added to the *response*, not a preflight.
|
||||
knownHeaders := []string{}
|
||||
for h := range w.wrapped.Header() {
|
||||
knownHeaders = append(knownHeaders, http.CanonicalHeaderKey(h))
|
||||
}
|
||||
w.wrapped.Header().Set("Access-Control-Expose-Headers", strings.Join(knownHeaders, ", "))
|
||||
}
|
||||
|
||||
func (w *grpcWebResponse) copyTrailersToPayload() {
|
||||
trailers := w.extractTrailerHeaders()
|
||||
trailerBuffer := new(bytes.Buffer)
|
||||
trailers.Write(trailerBuffer)
|
||||
trailerGrpcDataHeader := []byte{1 << 7, 0, 0, 0, 0} // MSB=1 indicates this is a trailer data frame.
|
||||
binary.BigEndian.PutUint32(trailerGrpcDataHeader[1:5], uint32(trailerBuffer.Len()))
|
||||
w.wrapped.Write(trailerGrpcDataHeader)
|
||||
w.wrapped.Write(trailerBuffer.Bytes())
|
||||
w.wrapped.(http.Flusher).Flush()
|
||||
}
|
||||
|
||||
func (w *grpcWebResponse) extractTrailerHeaders() http.Header {
|
||||
flushedHeaders := w.wrapped.Header()
|
||||
trailerHeaders := make(http.Header)
|
||||
for k, vv := range w.headers {
|
||||
// Skip the pre-annoucement of Trailer headers. Don't add them to the response headers.
|
||||
if strings.ToLower(k) == "trailer" {
|
||||
continue
|
||||
}
|
||||
// Skip existing headers that were already sent.
|
||||
if _, exists := flushedHeaders[k]; exists {
|
||||
continue
|
||||
}
|
||||
// Skip the Trailer prefix
|
||||
if strings.HasPrefix(k, http2.TrailerPrefix) {
|
||||
k = k[len(http2.TrailerPrefix):]
|
||||
}
|
||||
for _, v := range vv {
|
||||
trailerHeaders.Add(k, v)
|
||||
}
|
||||
}
|
||||
return trailerHeaders
|
||||
}
|
||||
24
vendor/github.com/improbable-eng/grpc-web/go/grpcweb/helpers.go
generated
vendored
Normal file
24
vendor/github.com/improbable-eng/grpc-web/go/grpcweb/helpers.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
//Copyright 2017 Improbable. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
package grpcweb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// ListGRPCResources is a helper function that lists all URLs that are registered on gRPC server.
|
||||
//
|
||||
// This makes it easy to register all the relevant routes in your HTTP router of choice.
|
||||
func ListGRPCResources(server *grpc.Server) []string {
|
||||
ret := []string{}
|
||||
for serviceName, serviceInfo := range server.GetServiceInfo() {
|
||||
for _, methodInfo := range serviceInfo.Methods {
|
||||
fullResource := fmt.Sprintf("/%s/%s", serviceName, methodInfo.Name)
|
||||
ret = append(ret, fullResource)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
101
vendor/github.com/improbable-eng/grpc-web/go/grpcweb/options.go
generated
vendored
Normal file
101
vendor/github.com/improbable-eng/grpc-web/go/grpcweb/options.go
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
//Copyright 2017 Improbable. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
package grpcweb
|
||||
|
||||
import "net/http"
|
||||
|
||||
var (
|
||||
defaultOptions = &options{
|
||||
allowedRequestHeaders: []string{"*"},
|
||||
corsForRegisteredEndpointsOnly: true,
|
||||
originFunc: func(origin string) bool { return true },
|
||||
}
|
||||
)
|
||||
|
||||
type options struct {
|
||||
allowedRequestHeaders []string
|
||||
corsForRegisteredEndpointsOnly bool
|
||||
originFunc func(origin string) bool
|
||||
enableWebsockets bool
|
||||
websocketOriginFunc func(req *http.Request) bool
|
||||
}
|
||||
|
||||
func evaluateOptions(opts []Option) *options {
|
||||
optCopy := &options{}
|
||||
*optCopy = *defaultOptions
|
||||
for _, o := range opts {
|
||||
o(optCopy)
|
||||
}
|
||||
return optCopy
|
||||
}
|
||||
|
||||
type Option func(*options)
|
||||
|
||||
// WithOriginFunc allows for customizing what CORS Origin requests are allowed.
|
||||
//
|
||||
// This is controlling the CORS pre-flight `Access-Control-Allow-Origin`. This mechanism allows you to limit the
|
||||
// availability of the APIs based on the domain name of the calling website (Origin). You can provide a function that
|
||||
// filters the allowed Origin values.
|
||||
//
|
||||
// The default behaviour is `*`, i.e. to allow all calling websites.
|
||||
//
|
||||
// The relevant CORS pre-flight docs:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
|
||||
func WithOriginFunc(originFunc func(origin string) bool) Option {
|
||||
return func(o *options) {
|
||||
o.originFunc = originFunc
|
||||
}
|
||||
}
|
||||
|
||||
// WithCorsForRegisteredEndpointsOnly allows for customizing whether OPTIONS requests with the `X-GRPC-WEB` header will
|
||||
// only be accepted if they match a registered gRPC endpoint.
|
||||
//
|
||||
// This should be set to false to allow handling gRPC requests for unknown endpoints (e.g. for proxying).
|
||||
//
|
||||
// The default behaviour is `true`, i.e. only allows CORS requests for registered endpoints.
|
||||
func WithCorsForRegisteredEndpointsOnly(onlyRegistered bool) Option {
|
||||
return func(o *options) {
|
||||
o.corsForRegisteredEndpointsOnly = onlyRegistered
|
||||
}
|
||||
}
|
||||
|
||||
// WithAllowedRequestHeaders allows for customizing what gRPC request headers a browser can add.
|
||||
//
|
||||
// This is controlling the CORS pre-flight `Access-Control-Allow-Headers` method and applies to *all* gRPC handlers.
|
||||
// However, a special `*` value can be passed in that allows
|
||||
// the browser client to provide *any* header, by explicitly whitelisting all `Access-Control-Request-Headers` of the
|
||||
// pre-flight request.
|
||||
//
|
||||
// The default behaviour is `[]string{'*'}`, allowing all browser client headers. This option overrides that default,
|
||||
// while maintaining a whitelist for gRPC-internal headers.
|
||||
//
|
||||
// Unfortunately, since the CORS pre-flight happens independently from gRPC handler execution, it is impossible to
|
||||
// automatically discover it from the gRPC handler itself.
|
||||
//
|
||||
// The relevant CORS pre-flight docs:
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
|
||||
func WithAllowedRequestHeaders(headers []string) Option {
|
||||
return func(o *options) {
|
||||
o.allowedRequestHeaders = headers
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return func(o *options) {
|
||||
o.enableWebsockets = enableWebsockets
|
||||
}
|
||||
}
|
||||
|
||||
// WithWebsocketOriginFunc allows for customizing the acceptance of Websocket requests - usually to check that the origin
|
||||
// is valid.
|
||||
//
|
||||
// The default behaviour is to check that the origin of the request matches the host of the request.
|
||||
func WithWebsocketOriginFunc(websocketOriginFunc func(req *http.Request) bool) Option {
|
||||
return func(o *options) {
|
||||
o.websocketOriginFunc = websocketOriginFunc
|
||||
}
|
||||
}
|
||||
216
vendor/github.com/improbable-eng/grpc-web/go/grpcweb/websocket_wrapper.go
generated
vendored
Normal file
216
vendor/github.com/improbable-eng/grpc-web/go/grpcweb/websocket_wrapper.go
generated
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
package grpcweb
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
type webSocketResponseWriter struct {
|
||||
writtenHeaders bool
|
||||
wsConn *websocket.Conn
|
||||
headers http.Header
|
||||
flushedHeaders http.Header
|
||||
closeNotifyChan chan bool
|
||||
}
|
||||
|
||||
func newWebSocketResponseWriter(wsConn *websocket.Conn) *webSocketResponseWriter {
|
||||
return &webSocketResponseWriter{
|
||||
writtenHeaders: false,
|
||||
headers: make(http.Header),
|
||||
flushedHeaders: make(http.Header),
|
||||
wsConn: wsConn,
|
||||
closeNotifyChan: make(chan bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (w *webSocketResponseWriter) Header() http.Header {
|
||||
return w.headers
|
||||
}
|
||||
|
||||
func (w *webSocketResponseWriter) Write(b []byte) (int, error) {
|
||||
if !w.writtenHeaders {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
return len(b), w.wsConn.WriteMessage(websocket.BinaryMessage, b)
|
||||
}
|
||||
|
||||
func (w *webSocketResponseWriter) writeHeaderFrame(headers http.Header) {
|
||||
headerBuffer := new(bytes.Buffer)
|
||||
headers.Write(headerBuffer)
|
||||
headerGrpcDataHeader := []byte{1 << 7, 0, 0, 0, 0} // MSB=1 indicates this is a header data frame.
|
||||
binary.BigEndian.PutUint32(headerGrpcDataHeader[1:5], uint32(headerBuffer.Len()))
|
||||
w.wsConn.WriteMessage(websocket.BinaryMessage, headerGrpcDataHeader)
|
||||
w.wsConn.WriteMessage(websocket.BinaryMessage, headerBuffer.Bytes())
|
||||
}
|
||||
|
||||
func (w *webSocketResponseWriter) copyFlushedHeaders() {
|
||||
for k, vv := range w.headers {
|
||||
// Skip the pre-annoucement of Trailer headers. Don't add them to the response headers.
|
||||
if strings.ToLower(k) == "trailer" {
|
||||
continue
|
||||
}
|
||||
for _, v := range vv {
|
||||
w.flushedHeaders.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *webSocketResponseWriter) WriteHeader(code int) {
|
||||
w.copyFlushedHeaders()
|
||||
w.writtenHeaders = true
|
||||
w.writeHeaderFrame(w.headers)
|
||||
return
|
||||
}
|
||||
|
||||
func (w *webSocketResponseWriter) extractTrailerHeaders() http.Header {
|
||||
trailerHeaders := make(http.Header)
|
||||
for k, vv := range w.headers {
|
||||
// Skip the pre-annoucement of Trailer headers. Don't add them to the response headers.
|
||||
if strings.ToLower(k) == "trailer" {
|
||||
continue
|
||||
}
|
||||
// Skip existing headers that were already sent.
|
||||
if _, exists := w.flushedHeaders[k]; exists {
|
||||
continue
|
||||
}
|
||||
// Skip the Trailer prefix
|
||||
if strings.HasPrefix(k, http2.TrailerPrefix) {
|
||||
k = k[len(http2.TrailerPrefix):]
|
||||
}
|
||||
for _, v := range vv {
|
||||
trailerHeaders.Add(k, v)
|
||||
}
|
||||
}
|
||||
return trailerHeaders
|
||||
}
|
||||
|
||||
func (w *webSocketResponseWriter) FlushTrailers() {
|
||||
w.writeHeaderFrame(w.extractTrailerHeaders())
|
||||
}
|
||||
|
||||
func (w *webSocketResponseWriter) Flush() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
func (w *webSocketResponseWriter) CloseNotify() <-chan bool {
|
||||
return w.closeNotifyChan
|
||||
}
|
||||
|
||||
type webSocketWrappedReader struct {
|
||||
wsConn *websocket.Conn
|
||||
respWriter *webSocketResponseWriter
|
||||
remainingBuffer []byte
|
||||
remainingError error
|
||||
}
|
||||
|
||||
func (w *webSocketWrappedReader) Close() error {
|
||||
w.respWriter.FlushTrailers()
|
||||
return w.wsConn.Close()
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// If a buffer remains from a previous WebSocket frame read then continue reading it
|
||||
if w.remainingBuffer != nil {
|
||||
|
||||
// If the remaining buffer fits completely inside the argument slice then read all of it and return any error
|
||||
// that was retained from the original call
|
||||
if len(w.remainingBuffer) <= len(p) {
|
||||
copy(p, w.remainingBuffer)
|
||||
|
||||
remainingLength := len(w.remainingBuffer)
|
||||
err := w.remainingError
|
||||
|
||||
// Clear the remaining buffer and error so that the next read will be a read from the websocket frame,
|
||||
// unless the error terminates the stream
|
||||
w.remainingBuffer = nil
|
||||
w.remainingError = nil
|
||||
return remainingLength, err
|
||||
}
|
||||
|
||||
// The remaining buffer doesn't fit inside the argument slice, so copy the bytes that will fit and retain the
|
||||
// bytes that don't fit - don't return the remainingError as there are still bytes to be read from the frame
|
||||
copy(p, w.remainingBuffer[:len(p)])
|
||||
w.remainingBuffer = w.remainingBuffer[len(p):]
|
||||
|
||||
// Return the length of the argument slice as that was the length of the written bytes
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// Read a whole frame from the WebSocket connection
|
||||
messageType, framePayload, err := w.wsConn.ReadMessage()
|
||||
if err == io.EOF || messageType == -1 {
|
||||
// The client has closed the connection. Indicate to the response writer that it should close
|
||||
w.respWriter.closeNotifyChan <- true
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
// Only Binary frames are valid
|
||||
if messageType != websocket.BinaryMessage {
|
||||
return 0, errors.New("websocket frame was not a binary frame")
|
||||
}
|
||||
|
||||
// If the frame consists of only a single byte of value 1 then this indicates the client has finished sending
|
||||
if len(framePayload) == 1 && framePayload[0] == 1 {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
// If the frame is somehow empty then just return the error
|
||||
if len(framePayload) == 0 {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// The first byte is used for control flow, so the data starts from the second byte
|
||||
dataPayload := framePayload[1:]
|
||||
|
||||
// If the remaining buffer fits completely inside the argument slice then read all of it and return the error
|
||||
if len(dataPayload) <= len(p) {
|
||||
copy(p, dataPayload)
|
||||
return len(dataPayload), err
|
||||
}
|
||||
|
||||
// The data read from the frame doesn't fit inside the argument slice, so copy the bytes that fit into the argument
|
||||
// slice
|
||||
copy(p, dataPayload[:len(p)])
|
||||
|
||||
// Retain the bytes that do not fit in the argument slice
|
||||
w.remainingBuffer = dataPayload[len(p):]
|
||||
// Retain the error instead of returning it so that the retained bytes will be read
|
||||
w.remainingError = err
|
||||
|
||||
// Return the length of the argument slice as that is the length of the written bytes
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func newWebsocketWrappedReader(wsConn *websocket.Conn, respWriter *webSocketResponseWriter) *webSocketWrappedReader {
|
||||
return &webSocketWrappedReader{
|
||||
wsConn: wsConn,
|
||||
respWriter: respWriter,
|
||||
remainingBuffer: nil,
|
||||
remainingError: nil,
|
||||
}
|
||||
}
|
||||
|
||||
func parseHeaders(headerString string) (http.Header, error) {
|
||||
reader := bufio.NewReader(strings.NewReader(headerString + "\r\n"))
|
||||
tp := textproto.NewReader(reader)
|
||||
|
||||
mimeHeader, err := tp.ReadMIMEHeader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// http.Header and textproto.MIMEHeader are both just a map[string][]string
|
||||
return http.Header(mimeHeader), nil
|
||||
}
|
||||
204
vendor/github.com/improbable-eng/grpc-web/go/grpcweb/wrapper.go
generated
vendored
Normal file
204
vendor/github.com/improbable-eng/grpc-web/go/grpcweb/wrapper.go
generated
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
//Copyright 2017 Improbable. All Rights Reserved.
|
||||
// See LICENSE for licensing terms.
|
||||
|
||||
package grpcweb
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/rs/cors"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
var (
|
||||
internalRequestHeadersWhitelist = []string{
|
||||
"U-A", // for gRPC-Web User Agent indicator.
|
||||
}
|
||||
)
|
||||
|
||||
type WrappedGrpcServer struct {
|
||||
server *grpc.Server
|
||||
opts *options
|
||||
corsWrapper *cors.Cors
|
||||
originFunc func(origin string) bool
|
||||
enableWebsockets bool
|
||||
websocketOriginFunc func(req *http.Request) bool
|
||||
}
|
||||
|
||||
// WrapServer takes a gRPC Server in Go and returns a WrappedGrpcServer that provides gRPC-Web Compatibility.
|
||||
//
|
||||
// The internal implementation fakes out a http.Request that carries standard gRPC, and performs the remapping inside
|
||||
// http.ResponseWriter, i.e. mostly the re-encoding of Trailers (that carry gRPC status).
|
||||
//
|
||||
// You can control the behaviour of the wrapper (e.g. modifying CORS behaviour) using `With*` options.
|
||||
func WrapServer(server *grpc.Server, options ...Option) *WrappedGrpcServer {
|
||||
opts := evaluateOptions(options)
|
||||
corsWrapper := cors.New(cors.Options{
|
||||
AllowOriginFunc: opts.originFunc,
|
||||
AllowedHeaders: append(opts.allowedRequestHeaders, internalRequestHeadersWhitelist...),
|
||||
ExposedHeaders: nil, // make sure that this is *nil*, otherwise the WebResponse overwrite will not work.
|
||||
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 {
|
||||
origin := req.Header.Get("Origin")
|
||||
parsedUrl, err := url.ParseRequestURI(origin)
|
||||
if err != nil {
|
||||
grpclog.Warningf("Unable to parse url for grpc-websocket origin check: %s. error: %v", origin, err)
|
||||
return false
|
||||
}
|
||||
return parsedUrl.Host == req.Host
|
||||
}
|
||||
}
|
||||
return &WrappedGrpcServer{
|
||||
server: server,
|
||||
opts: opts,
|
||||
corsWrapper: corsWrapper,
|
||||
originFunc: opts.originFunc,
|
||||
enableWebsockets: opts.enableWebsockets,
|
||||
websocketOriginFunc: websocketOriginFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP takes a HTTP request and if it is a gRPC-Web request wraps it with a compatibility layer to transform it to
|
||||
// a standard gRPC request for the wrapped gRPC server and transforms the response to comply with the gRPC-Web protocol.
|
||||
//
|
||||
// The gRPC-Web compatibility is only invoked if the request is a gRPC-Web request as determined by IsGrpcWebRequest or
|
||||
// the request is a pre-flight (CORS) request as determined by IsAcceptableGrpcCorsRequest.
|
||||
//
|
||||
// You can control the CORS behaviour using `With*` options in the WrapServer function.
|
||||
func (w *WrappedGrpcServer) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
|
||||
if w.enableWebsockets && w.IsGrpcWebSocketRequest(req) {
|
||||
if w.websocketOriginFunc(req) {
|
||||
if !w.opts.corsForRegisteredEndpointsOnly || w.isRequestForRegisteredEndpoint(req) {
|
||||
w.HandleGrpcWebsocketRequest(resp, req)
|
||||
return
|
||||
}
|
||||
}
|
||||
resp.WriteHeader(403)
|
||||
resp.Write(make([]byte, 0))
|
||||
return
|
||||
}
|
||||
|
||||
if w.IsAcceptableGrpcCorsRequest(req) || w.IsGrpcWebRequest(req) {
|
||||
w.corsWrapper.Handler(http.HandlerFunc(w.HandleGrpcWebRequest)).ServeHTTP(resp, req)
|
||||
return
|
||||
}
|
||||
w.server.ServeHTTP(resp, req)
|
||||
}
|
||||
|
||||
// IsGrpcWebSocketRequest determines if a request is a gRPC-Web request by checking that the "Sec-Websocket-Protocol"
|
||||
// header value is "grpc-websockets"
|
||||
func (w *WrappedGrpcServer) IsGrpcWebSocketRequest(req *http.Request) bool {
|
||||
return req.Header.Get("Upgrade") == "websocket" && req.Header.Get("Sec-Websocket-Protocol") == "grpc-websockets"
|
||||
}
|
||||
|
||||
// HandleGrpcWebRequest takes a HTTP request that is assumed to be a gRPC-Web request and wraps it with a compatibility
|
||||
// layer to transform it to a standard gRPC request for the wrapped gRPC server and transforms the response to comply
|
||||
// with the gRPC-Web protocol.
|
||||
func (w *WrappedGrpcServer) HandleGrpcWebRequest(resp http.ResponseWriter, req *http.Request) {
|
||||
intReq := hackIntoNormalGrpcRequest(req)
|
||||
intResp := newGrpcWebResponse(resp)
|
||||
w.server.ServeHTTP(intResp, intReq)
|
||||
intResp.finishRequest(req)
|
||||
}
|
||||
|
||||
var websocketUpgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool { return true },
|
||||
Subprotocols: []string{"grpc-websockets"},
|
||||
}
|
||||
|
||||
// HandleGrpcWebsocketRequest takes a HTTP request that is assumed to be a gRPC-Websocket request and wraps it with a
|
||||
// compatibility layer to transform it to a standard gRPC request for the wrapped gRPC server and transforms the
|
||||
// response to comply with the gRPC-Web protocol.
|
||||
func (w *WrappedGrpcServer) HandleGrpcWebsocketRequest(resp http.ResponseWriter, req *http.Request) {
|
||||
conn, err := websocketUpgrader.Upgrade(resp, req, nil)
|
||||
if err != nil {
|
||||
grpclog.Errorf("Unable to upgrade websocket request: %v", err)
|
||||
return
|
||||
}
|
||||
w.handleWebSocket(conn, req)
|
||||
}
|
||||
|
||||
func (w *WrappedGrpcServer) handleWebSocket(wsConn *websocket.Conn, req *http.Request) {
|
||||
messageType, readBytes, err := wsConn.ReadMessage()
|
||||
if err != nil {
|
||||
grpclog.Errorf("Unable to read first websocket message: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if messageType != websocket.BinaryMessage {
|
||||
grpclog.Errorf("First websocket message is non-binary")
|
||||
return
|
||||
}
|
||||
|
||||
headers, err := parseHeaders(string(readBytes))
|
||||
if err != nil {
|
||||
grpclog.Errorf("Unable to parse websocket headers: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
respWriter := newWebSocketResponseWriter(wsConn)
|
||||
wrappedReader := newWebsocketWrappedReader(wsConn, respWriter)
|
||||
|
||||
req.Body = wrappedReader
|
||||
req.Method = http.MethodPost
|
||||
req.Header = headers
|
||||
req.ProtoMajor = 2
|
||||
req.ProtoMinor = 0
|
||||
contentType := req.Header.Get("content-type")
|
||||
req.Header.Set("content-type", strings.Replace(contentType, "application/grpc-web", "application/grpc", 1))
|
||||
|
||||
w.server.ServeHTTP(respWriter, req)
|
||||
}
|
||||
|
||||
// IsGrpcWebRequest determines if a request is a gRPC-Web request by checking that the "content-type" is
|
||||
// "application/grpc-web" and that the method is POST.
|
||||
func (w *WrappedGrpcServer) IsGrpcWebRequest(req *http.Request) bool {
|
||||
return req.Method == http.MethodPost && strings.HasPrefix(req.Header.Get("content-type"), "application/grpc-web")
|
||||
}
|
||||
|
||||
// IsAcceptableGrpcCorsRequest determines if a request is a CORS pre-flight request for a gRPC-Web request and that this
|
||||
// request is acceptable for CORS.
|
||||
//
|
||||
// You can control the CORS behaviour using `With*` options in the WrapServer function.
|
||||
func (w *WrappedGrpcServer) IsAcceptableGrpcCorsRequest(req *http.Request) bool {
|
||||
accessControlHeaders := strings.ToLower(req.Header.Get("Access-Control-Request-Headers"))
|
||||
if req.Method == http.MethodOptions && strings.Contains(accessControlHeaders, "x-grpc-web") {
|
||||
if w.opts.corsForRegisteredEndpointsOnly {
|
||||
return w.isRequestForRegisteredEndpoint(req)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (w *WrappedGrpcServer) isRequestForRegisteredEndpoint(req *http.Request) bool {
|
||||
registeredEndpoints := ListGRPCResources(w.server)
|
||||
requestedEndpoint := req.URL.Path
|
||||
for _, v := range registeredEndpoints {
|
||||
if v == requestedEndpoint {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hackIntoNormalGrpcRequest(req *http.Request) *http.Request {
|
||||
// Hack, this should be a shallow copy, but let's see if this works
|
||||
req.ProtoMajor = 2
|
||||
req.ProtoMinor = 0
|
||||
contentType := req.Header.Get("content-type")
|
||||
req.Header.Set("content-type", strings.Replace(contentType, "application/grpc-web", "application/grpc", 1))
|
||||
return req
|
||||
}
|
||||
Reference in New Issue
Block a user