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

Allow non root resources #454

Merged
merged 10 commits into from
May 28, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
13 changes: 13 additions & 0 deletions go/grpcweb/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ import (
"fmt"
"net/http"
"net/url"
"regexp"
"strings"

"google.golang.org/grpc"
)

var pathMatcher = regexp.MustCompile(`/[^/]*/[^/]*$`)

// 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.
Expand All @@ -35,3 +39,12 @@ func WebsocketRequestOrigin(req *http.Request) (string, error) {
}
return parsed.Host, nil
}

func GetGRPCEndpoint(req *http.Request) string {
johanbrandhorst marked this conversation as resolved.
Show resolved Hide resolved
endpoint := pathMatcher.FindString(strings.TrimRight(req.URL.Path, "/"))
if len(endpoint) == 0 {
return req.URL.Path
}

return endpoint
}
24 changes: 24 additions & 0 deletions go/grpcweb/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package grpcweb_test

import (
"net/http/httptest"
"sort"
"testing"

Expand Down Expand Up @@ -34,3 +35,26 @@ func TestListGRPCResources(t *testing.T) {
actual,
"list grpc resources must provide an exhaustive list of all registered handlers")
}

func TestGetGRPCEndpoint(t *testing.T) {
cases := []struct {
input string
output string
}{
{input: "/", output: "/"},
{input: "/resource", output: "/resource"},
{input: "/improbable.grpcweb.test.TestService/PingEmpty", output: "/improbable.grpcweb.test.TestService/PingEmpty"},
{input: "/improbable.grpcweb.test.TestService/PingEmpty/", output: "/improbable.grpcweb.test.TestService/PingEmpty"},
{input: "/a/b/c/improbable.grpcweb.test.TestService/PingEmpty", output: "/improbable.grpcweb.test.TestService/PingEmpty"},
{input: "/a/b/c/improbable.grpcweb.test.TestService/PingEmpty/", output: "/improbable.grpcweb.test.TestService/PingEmpty"},
}

for _, c := range cases {
req := httptest.NewRequest("GET", c.input, nil)

result := grpcweb.GetGRPCEndpoint(req)

assert.Equal(t, c.output, result)
}

mangas marked this conversation as resolved.
Show resolved Hide resolved
}
8 changes: 8 additions & 0 deletions go/grpcweb/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ var (
allowedRequestHeaders: []string{"*"},
corsForRegisteredEndpointsOnly: true,
originFunc: func(origin string) bool { return false },
allowNonRootResources: false,
}
)

Expand All @@ -19,6 +20,7 @@ type options struct {
originFunc func(origin string) bool
enableWebsockets bool
websocketOriginFunc func(req *http.Request) bool
allowNonRootResources bool
}

func evaluateOptions(opts []Option) *options {
Expand Down Expand Up @@ -99,3 +101,9 @@ func WithWebsocketOriginFunc(websocketOriginFunc func(req *http.Request) bool) O
o.websocketOriginFunc = websocketOriginFunc
}
}

func WithAllowNonRootResource(allowNonRootResources bool) Option {
johanbrandhorst marked this conversation as resolved.
Show resolved Hide resolved
return func(o *options) {
o.allowNonRootResources = allowNonRootResources
}
}
12 changes: 11 additions & 1 deletion go/grpcweb/wrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type WrappedGrpcServer struct {
originFunc func(origin string) bool
enableWebsockets bool
websocketOriginFunc func(req *http.Request) bool
endpointFunc func(req *http.Request) string
}

// WrapServer takes a gRPC Server in Go and returns a WrappedGrpcServer that provides gRPC-Web Compatibility.
Expand All @@ -56,13 +57,20 @@ func WrapServer(server *grpc.Server, options ...Option) *WrappedGrpcServer {
if websocketOriginFunc == nil {
websocketOriginFunc = defaultWebsocketOriginFunc
}

endpointFunc := func(req *http.Request) string { return req.URL.Path }
mangas marked this conversation as resolved.
Show resolved Hide resolved
if opts.allowNonRootResources {
endpointFunc = GetGRPCEndpoint
}

return &WrappedGrpcServer{
server: server,
opts: opts,
corsWrapper: corsWrapper,
originFunc: opts.originFunc,
enableWebsockets: opts.enableWebsockets,
websocketOriginFunc: websocketOriginFunc,
endpointFunc: endpointFunc,
}
}

Expand Down Expand Up @@ -105,6 +113,7 @@ func (w *WrappedGrpcServer) IsGrpcWebSocketRequest(req *http.Request) bool {
func (w *WrappedGrpcServer) HandleGrpcWebRequest(resp http.ResponseWriter, req *http.Request) {
intReq, isTextFormat := hackIntoNormalGrpcRequest(req)
intResp := newGrpcWebResponse(resp, isTextFormat)
req.URL.Path = w.endpointFunc(req)
w.server.ServeHTTP(intResp, intReq)
intResp.finishRequest(req)
}
Expand Down Expand Up @@ -161,6 +170,7 @@ func (w *WrappedGrpcServer) handleWebSocket(wsConn *websocket.Conn, req *http.Re
grpclog.Errorf("web socket text format requests not yet supported")
return
}
req.URL.Path = w.endpointFunc(req)
w.server.ServeHTTP(respWriter, interceptedRequest)
}

Expand All @@ -187,7 +197,7 @@ func (w *WrappedGrpcServer) IsAcceptableGrpcCorsRequest(req *http.Request) bool

func (w *WrappedGrpcServer) isRequestForRegisteredEndpoint(req *http.Request) bool {
registeredEndpoints := ListGRPCResources(w.server)
requestedEndpoint := req.URL.Path
requestedEndpoint := w.endpointFunc(req)
for _, v := range registeredEndpoints {
if v == requestedEndpoint {
return true
Expand Down