cloudflare/cloudflared

Public

mirrored from https://github.com/cloudflare/cloudflaredAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
119d96cdc1b4b96d57442d089198b1b9bcd4e748

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

connection/http2_test.go

304lines · modecode

1package connection
2
3import (
4 "context"
5 "fmt"
6 "io"
7 "io/ioutil"
8 "net"
9 "net/http"
10 "net/http/httptest"
11 "sync"
12 "testing"
13 "time"
14
15 "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
16 tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
17
18 "github.com/gobwas/ws/wsutil"
19 "github.com/rs/zerolog"
20 "github.com/stretchr/testify/require"
21 "golang.org/x/net/http2"
22)
23
24var (
25 testTransport = http2.Transport{}
26)
27
28func newTestHTTP2Connection() (*http2Connection, net.Conn) {
29 edgeConn, originConn := net.Pipe()
30 var connIndex = uint8(0)
31 return NewHTTP2Connection(
32 originConn,
33 testConfig,
34 &NamedTunnelConfig{},
35 &pogs.ConnectionOptions{},
36 testObserver,
37 connIndex,
38 mockConnectedFuse{},
39 ), edgeConn
40}
41
42func TestServeHTTP(t *testing.T) {
43 tests := []testRequest{
44 {
45 name: "ok",
46 endpoint: "ok",
47 expectedStatus: http.StatusOK,
48 expectedBody: []byte(http.StatusText(http.StatusOK)),
49 },
50 {
51 name: "large_file",
52 endpoint: "large_file",
53 expectedStatus: http.StatusOK,
54 expectedBody: testLargeResp,
55 },
56 {
57 name: "Bad request",
58 endpoint: "400",
59 expectedStatus: http.StatusBadRequest,
60 expectedBody: []byte(http.StatusText(http.StatusBadRequest)),
61 },
62 {
63 name: "Internal server error",
64 endpoint: "500",
65 expectedStatus: http.StatusInternalServerError,
66 expectedBody: []byte(http.StatusText(http.StatusInternalServerError)),
67 },
68 {
69 name: "Proxy error",
70 endpoint: "error",
71 expectedStatus: http.StatusBadGateway,
72 expectedBody: nil,
73 isProxyError: true,
74 },
75 }
76
77 http2Conn, edgeConn := newTestHTTP2Connection()
78
79 ctx, cancel := context.WithCancel(context.Background())
80 var wg sync.WaitGroup
81 wg.Add(1)
82 go func() {
83 defer wg.Done()
84 http2Conn.Serve(ctx)
85 }()
86
87 edgeHTTP2Conn, err := testTransport.NewClientConn(edgeConn)
88 require.NoError(t, err)
89
90 for _, test := range tests {
91 endpoint := fmt.Sprintf("http://localhost:8080/%s", test.endpoint)
92 req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
93 require.NoError(t, err)
94
95 resp, err := edgeHTTP2Conn.RoundTrip(req)
96 require.NoError(t, err)
97 require.Equal(t, test.expectedStatus, resp.StatusCode)
98 if test.expectedBody != nil {
99 respBody, err := ioutil.ReadAll(resp.Body)
100 require.NoError(t, err)
101 require.Equal(t, test.expectedBody, respBody)
102 }
103 if test.isProxyError {
104 require.Equal(t, responseMetaHeaderCfd, resp.Header.Get(ResponseMetaHeaderField))
105 } else {
106 require.Equal(t, responseMetaHeaderOrigin, resp.Header.Get(ResponseMetaHeaderField))
107 }
108 }
109 cancel()
110 wg.Wait()
111}
112
113type mockNamedTunnelRPCClient struct {
114 registered chan struct{}
115 unregistered chan struct{}
116}
117
118func (mc mockNamedTunnelRPCClient) RegisterConnection(
119 c context.Context,
120 config *NamedTunnelConfig,
121 options *tunnelpogs.ConnectionOptions,
122 connIndex uint8,
123 observer *Observer,
124) error {
125 close(mc.registered)
126 return nil
127}
128
129func (mc mockNamedTunnelRPCClient) GracefulShutdown(ctx context.Context, gracePeriod time.Duration) {
130 close(mc.unregistered)
131}
132
133func (mockNamedTunnelRPCClient) Close() {}
134
135type mockRPCClientFactory struct {
136 registered chan struct{}
137 unregistered chan struct{}
138}
139
140func (mf *mockRPCClientFactory) newMockRPCClient(context.Context, io.ReadWriteCloser, *zerolog.Logger) NamedTunnelRPCClient {
141 return mockNamedTunnelRPCClient{
142 registered: mf.registered,
143 unregistered: mf.unregistered,
144 }
145}
146
147type wsRespWriter struct {
148 *httptest.ResponseRecorder
149 readPipe *io.PipeReader
150 writePipe *io.PipeWriter
151}
152
153func newWSRespWriter() *wsRespWriter {
154 readPipe, writePipe := io.Pipe()
155 return &wsRespWriter{
156 httptest.NewRecorder(),
157 readPipe,
158 writePipe,
159 }
160}
161
162func (w *wsRespWriter) RespBody() io.ReadWriter {
163 return nowriter{w.readPipe}
164}
165
166func (w *wsRespWriter) Write(data []byte) (n int, err error) {
167 return w.writePipe.Write(data)
168}
169
170func TestServeWS(t *testing.T) {
171 http2Conn, _ := newTestHTTP2Connection()
172
173 ctx, cancel := context.WithCancel(context.Background())
174 var wg sync.WaitGroup
175 wg.Add(1)
176 go func() {
177 defer wg.Done()
178 http2Conn.Serve(ctx)
179 }()
180
181 respWriter := newWSRespWriter()
182 readPipe, writePipe := io.Pipe()
183
184 req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://localhost:8080/ws", readPipe)
185 require.NoError(t, err)
186 req.Header.Set(internalUpgradeHeader, websocketUpgrade)
187
188 wg.Add(1)
189 go func() {
190 defer wg.Done()
191 http2Conn.ServeHTTP(respWriter, req)
192 }()
193
194 data := []byte("test websocket")
195 err = wsutil.WriteClientText(writePipe, data)
196 require.NoError(t, err)
197
198 respBody, err := wsutil.ReadServerText(respWriter.RespBody())
199 require.NoError(t, err)
200 require.Equal(t, data, respBody, fmt.Sprintf("Expect %s, got %s", string(data), string(respBody)))
201
202 cancel()
203 resp := respWriter.Result()
204 // http2RespWriter should rewrite status 101 to 200
205 require.Equal(t, http.StatusOK, resp.StatusCode)
206 require.Equal(t, responseMetaHeaderOrigin, resp.Header.Get(ResponseMetaHeaderField))
207
208 wg.Wait()
209}
210
211func TestServeControlStream(t *testing.T) {
212 http2Conn, edgeConn := newTestHTTP2Connection()
213
214 rpcClientFactory := mockRPCClientFactory{
215 registered: make(chan struct{}),
216 unregistered: make(chan struct{}),
217 }
218 http2Conn.newRPCClientFunc = rpcClientFactory.newMockRPCClient
219
220 ctx, cancel := context.WithCancel(context.Background())
221 var wg sync.WaitGroup
222 wg.Add(1)
223 go func() {
224 defer wg.Done()
225 http2Conn.Serve(ctx)
226 }()
227
228 req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://localhost:8080/", nil)
229 require.NoError(t, err)
230 req.Header.Set(internalUpgradeHeader, controlStreamUpgrade)
231
232 edgeHTTP2Conn, err := testTransport.NewClientConn(edgeConn)
233 require.NoError(t, err)
234
235 wg.Add(1)
236 go func() {
237 defer wg.Done()
238 edgeHTTP2Conn.RoundTrip(req)
239 }()
240
241 <-rpcClientFactory.registered
242 cancel()
243 <-rpcClientFactory.unregistered
244
245 wg.Wait()
246}
247
248func benchmarkServeHTTP(b *testing.B, test testRequest) {
249 http2Conn, edgeConn := newTestHTTP2Connection()
250
251 ctx, cancel := context.WithCancel(context.Background())
252 var wg sync.WaitGroup
253 wg.Add(1)
254 go func() {
255 defer wg.Done()
256 http2Conn.Serve(ctx)
257 }()
258
259 endpoint := fmt.Sprintf("http://localhost:8080/%s", test.endpoint)
260 req, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
261 require.NoError(b, err)
262
263 edgeHTTP2Conn, err := testTransport.NewClientConn(edgeConn)
264 require.NoError(b, err)
265
266 b.ResetTimer()
267 for i := 0; i < b.N; i++ {
268 b.StartTimer()
269 resp, err := edgeHTTP2Conn.RoundTrip(req)
270 b.StopTimer()
271 require.NoError(b, err)
272 require.Equal(b, test.expectedStatus, resp.StatusCode)
273 if test.expectedBody != nil {
274 respBody, err := ioutil.ReadAll(resp.Body)
275 require.NoError(b, err)
276 require.Equal(b, test.expectedBody, respBody)
277 }
278 resp.Body.Close()
279 }
280
281 cancel()
282 wg.Wait()
283}
284func BenchmarkServeHTTPSimple(b *testing.B) {
285 test := testRequest{
286 name: "ok",
287 endpoint: "ok",
288 expectedStatus: http.StatusOK,
289 expectedBody: []byte(http.StatusText(http.StatusOK)),
290 }
291
292 benchmarkServeHTTP(b, test)
293}
294
295func BenchmarkServeHTTPLargeFile(b *testing.B) {
296 test := testRequest{
297 name: "large_file",
298 endpoint: "large_file",
299 expectedStatus: http.StatusOK,
300 expectedBody: testLargeResp,
301 }
302
303 benchmarkServeHTTP(b, test)
304}
305