Skip to content

Commit d46be73

Browse files
authored
Move HTTP2 routing information into headers (#4001)
Signed-off-by: Joshua Kim <[email protected]>
1 parent 3ee09b9 commit d46be73

File tree

3 files changed

+35
-42
lines changed

3 files changed

+35
-42
lines changed

api/grpcclient/client.go

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,17 @@ import (
88
"fmt"
99

1010
"google.golang.org/grpc"
11+
"google.golang.org/grpc/metadata"
1112

1213
"github.com/ava-labs/avalanchego/ids"
1314
)
1415

15-
// NewChainClient returns grpc.ClientConn that prefixes method calls with the
16-
// provided chainID prefix
16+
// NewChainClient returns a grpc.ClientConn that sets the chain-id header for
17+
// all requests
1718
func NewChainClient(uri string, chainID ids.ID, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
1819
dialOpts := []grpc.DialOption{
19-
grpc.WithUnaryInterceptor(PrefixChainIDUnaryClientInterceptor(chainID)),
20-
grpc.WithStreamInterceptor(PrefixChainIDStreamClientInterceptor(chainID)),
20+
grpc.WithUnaryInterceptor(SetChainIDHeaderUnaryClientInterceptor(chainID)),
21+
grpc.WithStreamInterceptor(SetChainIDHeaderStreamClientInterceptor(chainID)),
2122
}
2223

2324
dialOpts = append(dialOpts, opts...)
@@ -30,9 +31,9 @@ func NewChainClient(uri string, chainID ids.ID, opts ...grpc.DialOption) (*grpc.
3031
return conn, nil
3132
}
3233

33-
// PrefixChainIDUnaryClientInterceptor prefixes unary grpc calls with the
34-
// provided chainID prefix
35-
func PrefixChainIDUnaryClientInterceptor(chainID ids.ID) grpc.UnaryClientInterceptor {
34+
// SetChainIDHeaderUnaryClientInterceptor sets the chain-id header for unary
35+
// requests
36+
func SetChainIDHeaderUnaryClientInterceptor(chainID ids.ID) grpc.UnaryClientInterceptor {
3637
return func(
3738
ctx context.Context,
3839
method string,
@@ -42,13 +43,14 @@ func PrefixChainIDUnaryClientInterceptor(chainID ids.ID) grpc.UnaryClientInterce
4243
invoker grpc.UnaryInvoker,
4344
opts ...grpc.CallOption,
4445
) error {
45-
return invoker(ctx, prefix(chainID, method), req, reply, cc, opts...)
46+
ctx = newContextWithChainIDHeader(ctx, chainID)
47+
return invoker(ctx, method, req, reply, cc, opts...)
4648
}
4749
}
4850

49-
// PrefixChainIDStreamClientInterceptor prefixes streaming grpc calls with the
50-
// provided chainID prefix
51-
func PrefixChainIDStreamClientInterceptor(chainID ids.ID) grpc.StreamClientInterceptor {
51+
// SetChainIDHeaderStreamClientInterceptor sets the chain-id header for
52+
// streaming requests
53+
func SetChainIDHeaderStreamClientInterceptor(chainID ids.ID) grpc.StreamClientInterceptor {
5254
return func(
5355
ctx context.Context,
5456
desc *grpc.StreamDesc,
@@ -57,11 +59,13 @@ func PrefixChainIDStreamClientInterceptor(chainID ids.ID) grpc.StreamClientInter
5759
streamer grpc.Streamer,
5860
opts ...grpc.CallOption,
5961
) (grpc.ClientStream, error) {
60-
return streamer(ctx, desc, cc, prefix(chainID, method), opts...)
62+
ctx = newContextWithChainIDHeader(ctx, chainID)
63+
return streamer(ctx, desc, cc, method, opts...)
6164
}
6265
}
6366

64-
// http/2 :path takes the form of /ChainID/Service/Method
65-
func prefix(chainID ids.ID, method string) string {
66-
return "/" + chainID.String() + method
67+
// newContextWithChainHeader sets the chain-id header which the server uses
68+
// to route the client grpc request
69+
func newContextWithChainIDHeader(ctx context.Context, chainID ids.ID) context.Context {
70+
return metadata.AppendToOutgoingContext(ctx, "chain-id", chainID.String())
6771
}

api/server/http2_router.go

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package server
55

66
import (
77
"net/http"
8-
"strings"
98
"sync"
109

1110
"github.com/ava-labs/avalanchego/ids"
@@ -25,15 +24,14 @@ func newHTTP2Router() *http2Router {
2524
}
2625

2726
func (h *http2Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
28-
// The :path pseudo-header takes the form of /Prefix/Path
29-
parsed := strings.Split(r.URL.Path, "/")
30-
if len(parsed) < 2 {
27+
// the chain-id header must be set to route the request to the correct chain
28+
// http2 handler
29+
chainID := r.Header.Get("chain-id")
30+
if len(chainID) == 0 {
3131
w.WriteHeader(http.StatusBadRequest)
3232
return
3333
}
3434

35-
chainID := parsed[1]
36-
3735
h.lock.RLock()
3836
handler, ok := h.handlers[chainID]
3937
h.lock.RUnlock()
@@ -42,15 +40,7 @@ func (h *http2Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
4240
return
4341
}
4442

45-
// Deep copy the request to avoid weird behavior from modifying r
46-
requestDeepCopy := r.Clone(r.Context())
47-
// Route this request to the http2 handler using the chain prefix
48-
requestDeepCopy.URL.Path = strings.TrimPrefix(
49-
requestDeepCopy.URL.Path,
50-
"/"+chainID,
51-
)
52-
53-
handler.ServeHTTP(w, requestDeepCopy)
43+
handler.ServeHTTP(w, r)
5444
}
5545

5646
func (h *http2Router) Add(chainID ids.ID, handler http.Handler) bool {

api/server/http2_router_test.go

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@
44
package server
55

66
import (
7-
"fmt"
87
"net/http"
98
"net/http/httptest"
10-
"net/url"
119
"testing"
1210

1311
"github.com/stretchr/testify/require"
@@ -28,23 +26,26 @@ func TestHTTP2RouterServeHTTP(t *testing.T) {
2826
tests := []struct {
2927
name string
3028
chainIDs []ids.ID
31-
path string
29+
header http.Header
3230
wantCode int
3331
}{
3432
{
35-
name: "invalid request",
36-
path: "foo",
33+
name: "missing chain-id header",
3734
wantCode: http.StatusBadRequest,
3835
},
3936
{
40-
name: "invalid handler",
41-
path: "/foo/bar/method",
37+
name: "unknown referenced chain-id",
38+
header: http.Header{
39+
http.CanonicalHeaderKey("chain-id"): []string{ids.Empty.String()},
40+
},
4241
wantCode: http.StatusNotFound,
4342
},
4443
{
4544
name: "valid handler",
46-
chainIDs: []ids.ID{{'f', 'o', 'o'}},
47-
path: fmt.Sprintf("/%s/bar/method", ids.ID{'f', 'o', 'o'}.String()),
45+
chainIDs: []ids.ID{ids.Empty},
46+
header: http.Header{
47+
http.CanonicalHeaderKey("chain-id"): []string{ids.Empty.String()},
48+
},
4849
wantCode: http.StatusOK,
4950
},
5051
}
@@ -57,9 +58,7 @@ func TestHTTP2RouterServeHTTP(t *testing.T) {
5758
handler := http.HandlerFunc(func(http.ResponseWriter, *http.Request) {})
5859
writer := httptest.NewRecorder()
5960
request := httptest.NewRequest(http.MethodPost, "/", nil)
60-
request.URL = &url.URL{
61-
Path: tt.path,
62-
}
61+
request.Header = tt.header
6362

6463
for _, chainID := range tt.chainIDs {
6564
require.True(h.Add(chainID, handler))

0 commit comments

Comments
 (0)