Skip to content

Commit 2011735

Browse files
Ensure HTTP headers are propagated through the rpcchainvm (#3917)
Signed-off-by: Joshua Kim <20001595+joshua-kim@users.noreply.github.com> Co-authored-by: Stephen Buttolph <stephen@avalabs.org>
1 parent 70c2df2 commit 2011735

File tree

7 files changed

+309
-175
lines changed

7 files changed

+309
-175
lines changed

proto/http/http.proto

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@ syntax = "proto3";
22

33
package http;
44

5-
import "google/protobuf/empty.proto";
6-
75
option go_package = "github.com/ava-labs/avalanchego/proto/pb/http";
86

97
service HTTP {
108
// Handle wraps http1 over http2 and provides support for websockets by implementing
119
// net conn and responsewriter in http2.
12-
rpc Handle(HTTPRequest) returns (google.protobuf.Empty);
10+
rpc Handle(HTTPRequest) returns (HTTPResponse);
1311
// HandleSimple wraps http1 requests over http2 similar to Handle but only passes headers
1412
// and body bytes. Because the request and response are single protos with no inline
1513
// gRPC servers the CPU cost as well as file descriptor overhead is less
@@ -147,6 +145,11 @@ message HTTPRequest {
147145
Request request = 2;
148146
}
149147

148+
message HTTPResponse {
149+
// header is the http headers for the response
150+
repeated Element header = 1;
151+
}
152+
150153
message HandleSimpleHTTPRequest {
151154
// method specifies the HTTP method (GET, POST, PUT, etc.)
152155
string method = 1;

proto/pb/http/http.pb.go

Lines changed: 202 additions & 155 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

proto/pb/http/http_grpc.pb.go

Lines changed: 5 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vms/rpcchainvm/ghttp/http_client.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,13 @@ func (c *Client) ServeHTTP(w http.ResponseWriter, r *http.Request) {
156156
}
157157
}
158158

159-
_, err = c.client.Handle(r.Context(), req)
159+
reply, err := c.client.Handle(r.Context(), req)
160160
if err != nil {
161161
http.Error(w, err.Error(), http.StatusInternalServerError)
162+
return
162163
}
164+
165+
grpcutils.SetHeaders(w.Header(), reply.Header)
163166
}
164167

165168
// serveHTTPSimple converts an http request to a gRPC HTTPRequest and returns the
@@ -205,7 +208,7 @@ func getHTTPSimpleRequest(r *http.Request) (*httppb.HandleSimpleHTTPRequest, err
205208

206209
// convertWriteResponse converts a gRPC HandleSimpleHTTPResponse to an HTTP response.
207210
func convertWriteResponse(w http.ResponseWriter, resp *httppb.HandleSimpleHTTPResponse) error {
208-
grpcutils.MergeHTTPHeader(resp.Headers, w.Header())
211+
grpcutils.SetHeaders(w.Header(), resp.Headers)
209212
w.WriteHeader(grpcutils.EnsureValidResponseCode(int(resp.Code)))
210213
_, err := w.Write(resp.Body)
211214
return err

vms/rpcchainvm/ghttp/http_server.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@ import (
88
"context"
99
"crypto/tls"
1010
"crypto/x509"
11+
"fmt"
1112
"net/http"
1213
"net/url"
1314

14-
"google.golang.org/protobuf/types/known/emptypb"
15-
1615
"github.com/ava-labs/avalanchego/proto/pb/io/reader"
1716
"github.com/ava-labs/avalanchego/vms/rpcchainvm/ghttp/greader"
1817
"github.com/ava-labs/avalanchego/vms/rpcchainvm/ghttp/gresponsewriter"
@@ -40,7 +39,7 @@ func NewServer(handler http.Handler) *Server {
4039
}
4140
}
4241

43-
func (s *Server) Handle(ctx context.Context, req *httppb.HTTPRequest) (*emptypb.Empty, error) {
42+
func (s *Server) Handle(ctx context.Context, req *httppb.HTTPRequest) (*httppb.HTTPResponse, error) {
4443
clientConn, err := grpcutils.Dial(req.ResponseWriter.ServerAddr)
4544
if err != nil {
4645
return nil, err
@@ -142,7 +141,13 @@ func (s *Server) Handle(ctx context.Context, req *httppb.HTTPRequest) (*emptypb.
142141

143142
s.handler.ServeHTTP(writer, request)
144143

145-
return &emptypb.Empty{}, clientConn.Close()
144+
if err := clientConn.Close(); err != nil {
145+
return nil, fmt.Errorf("failed to close client conn: %w", err)
146+
}
147+
148+
return &httppb.HTTPResponse{
149+
Header: grpcutils.GetHTTPHeader(writerHeaders),
150+
}, nil
146151
}
147152

148153
// HandleSimple handles http requests over http2 using a simple request response model.
@@ -153,7 +158,7 @@ func (s *Server) HandleSimple(ctx context.Context, r *httppb.HandleSimpleHTTPReq
153158
return nil, err
154159
}
155160

156-
grpcutils.MergeHTTPHeader(r.Headers, req.Header)
161+
grpcutils.SetHeaders(req.Header, r.Headers)
157162

158163
req = req.WithContext(ctx)
159164
req.RequestURI = r.Url

vms/rpcchainvm/ghttp/http_test.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,20 @@
44
package ghttp
55

66
import (
7+
"bytes"
78
"io"
89
"net/http"
910
"net/http/httptest"
11+
"strings"
1012
"testing"
1113

1214
"github.com/stretchr/testify/require"
1315
"google.golang.org/grpc"
1416
"google.golang.org/grpc/credentials/insecure"
1517
"google.golang.org/grpc/test/bufconn"
1618

19+
"github.com/ava-labs/avalanchego/vms/rpcchainvm/grpcutils"
20+
1721
httppb "github.com/ava-labs/avalanchego/proto/pb/http"
1822
)
1923

@@ -84,6 +88,77 @@ func TestRequestClientArbitrarilyLongBody(t *testing.T) {
8488
client.ServeHTTP(w, r) // Shouldn't block forever reading the body
8589
}
8690

91+
// Tests that writes to the http response in the server are propagated to the
92+
// client
93+
func TestHttpResponse(t *testing.T) {
94+
tests := []struct {
95+
name string
96+
header http.Header
97+
}{
98+
{
99+
// Requests with an upgrade header do not use the "Simple*" http response
100+
// apis and must be separately tested
101+
name: "upgrade header specified",
102+
header: http.Header{
103+
"Upgrade": {"upgrade"},
104+
"foo": {"foo"},
105+
},
106+
},
107+
{
108+
name: "arbitrary headers",
109+
header: http.Header{
110+
"foo": {"foo"},
111+
},
112+
},
113+
}
114+
115+
for _, tt := range tests {
116+
t.Run(tt.name, func(t *testing.T) {
117+
require := require.New(t)
118+
119+
handler := http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
120+
w.Header()["Bar"] = []string{"bar"}
121+
_, err := w.Write([]byte("baz"))
122+
require.NoError(err)
123+
})
124+
125+
listener, err := grpcutils.NewListener()
126+
require.NoError(err)
127+
server := grpc.NewServer()
128+
httppb.RegisterHTTPServer(server, NewServer(handler))
129+
130+
go func() {
131+
require.NoError(server.Serve(listener))
132+
}()
133+
134+
conn, err := grpc.NewClient(
135+
listener.Addr().String(),
136+
grpc.WithTransportCredentials(insecure.NewCredentials()),
137+
)
138+
require.NoError(err)
139+
140+
recorder := &httptest.ResponseRecorder{
141+
Body: bytes.NewBuffer(nil),
142+
}
143+
144+
client := NewClient(httppb.NewHTTPClient(conn))
145+
client.ServeHTTP(recorder, &http.Request{
146+
Body: io.NopCloser(strings.NewReader("foo")),
147+
Header: tt.header,
148+
})
149+
150+
require.Equal(http.StatusOK, recorder.Code)
151+
require.Equal(
152+
http.Header{
153+
"Bar": []string{"bar"},
154+
},
155+
recorder.Header(),
156+
)
157+
require.Equal("baz", recorder.Body.String())
158+
})
159+
}
160+
}
161+
87162
type infiniteStream struct{}
88163

89164
func (infiniteStream) Read(p []byte) (n int, err error) {

vms/rpcchainvm/grpcutils/util.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,12 @@ func GetHTTPHeader(hs http.Header) []*httppb.Element {
6464
return result
6565
}
6666

67-
// MergeHTTPHeader takes a slice of Header and merges with http.Header map.
68-
func MergeHTTPHeader(hs []*httppb.Element, header http.Header) {
69-
for _, h := range hs {
70-
header[h.Key] = h.Values
67+
// SetHeaders sets headers to next
68+
func SetHeaders(headers http.Header, next []*httppb.Element) {
69+
clear(headers)
70+
71+
for _, h := range next {
72+
headers[h.Key] = h.Values
7173
}
7274
}
7375

0 commit comments

Comments
 (0)