Skip to content

Commit 7059ef8

Browse files
TUN-5195: Do not set empty body if not applicable
Go's client defaults to chunked encoding after a 200ms delay if the following cases are true: * the request body blocks * the content length is not set (or set to -1) * the method doesn't usually have a body (GET, HEAD, DELETE, ...) * there is no transfer-encoding=chunked already set. So for non websocket requests, if transfer-encoding isn't chunked and content length is 0, we dont set a request body.
1 parent cbdf88e commit 7059ef8

File tree

2 files changed

+151
-2
lines changed

2 files changed

+151
-2
lines changed

connection/quic.go

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,12 @@ func (hrw httpResponseAdapter) WriteErrorResponse(err error) {
158158
quicpogs.WriteConnectResponseData(hrw, err, quicpogs.Metadata{Key: "HttpStatus", Val: strconv.Itoa(http.StatusBadGateway)})
159159
}
160160

161-
func buildHTTPRequest(connectRequest *quicpogs.ConnectRequest, body io.Reader) (*http.Request, error) {
161+
func buildHTTPRequest(connectRequest *quicpogs.ConnectRequest, body io.ReadCloser) (*http.Request, error) {
162162
metadata := connectRequest.MetadataMap()
163163
dest := connectRequest.Dest
164164
method := metadata[HTTPMethodKey]
165165
host := metadata[HTTPHostKey]
166+
isWebsocket := connectRequest.Type == quicpogs.ConnectionTypeWebsocket
166167

167168
req, err := http.NewRequest(method, dest, body)
168169
if err != nil {
@@ -186,6 +187,16 @@ func buildHTTPRequest(connectRequest *quicpogs.ConnectRequest, body io.Reader) (
186187
if err := setContentLength(req); err != nil {
187188
return nil, fmt.Errorf("Error setting content-length: %w", err)
188189
}
190+
191+
// Go's client defaults to chunked encoding after a 200ms delay if the following cases are true:
192+
// * the request body blocks
193+
// * the content length is not set (or set to -1)
194+
// * the method doesn't usually have a body (GET, HEAD, DELETE, ...)
195+
// * there is no transfer-encoding=chunked already set.
196+
// So, if transfer cannot be chunked and content length is 0, we dont set a request body.
197+
if !isWebsocket && !isTransferEncodingChunked(req) && req.ContentLength == 0 {
198+
req.Body = nil
199+
}
189200
stripWebsocketUpgradeHeader(req)
190201
return req, err
191202
}
@@ -197,3 +208,10 @@ func setContentLength(req *http.Request) error {
197208
}
198209
return err
199210
}
211+
212+
func isTransferEncodingChunked(req *http.Request) bool {
213+
transferEncodingVal := req.Header.Get("Transfer-Encoding")
214+
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Transfer-Encoding suggests that this can be a comma
215+
// separated value as well.
216+
return strings.Contains(strings.ToLower(transferEncodingVal), "chunked")
217+
}

connection/quic_test.go

Lines changed: 132 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ func TestQUICServer(t *testing.T) {
108108
Key: "HttpMethod",
109109
Val: "POST",
110110
},
111+
quicpogs.Metadata{
112+
Key: "HttpHeader:Content-Length",
113+
Val: "24",
114+
},
111115
},
112116
message: []byte("This is the message body"),
113117
expectedResponse: []byte("This is the message body"),
@@ -297,6 +301,7 @@ func TestBuildHTTPRequest(t *testing.T) {
297301
var tests = []struct {
298302
name string
299303
connectRequest *quicpogs.ConnectRequest
304+
body io.ReadCloser
300305
req *http.Request
301306
}{
302307
{
@@ -341,7 +346,9 @@ func TestBuildHTTPRequest(t *testing.T) {
341346
},
342347
ContentLength: 514,
343348
Host: "cf.host",
349+
Body: io.NopCloser(&bytes.Buffer{}),
344350
},
351+
body: io.NopCloser(&bytes.Buffer{}),
345352
},
346353
{
347354
name: "if content length isn't part of request headers, then it's not set",
@@ -380,13 +387,137 @@ func TestBuildHTTPRequest(t *testing.T) {
380387
},
381388
ContentLength: 0,
382389
Host: "cf.host",
390+
Body: nil,
391+
},
392+
body: io.NopCloser(&bytes.Buffer{}),
393+
},
394+
{
395+
name: "if content length is 0, but transfer-encoding is chunked, body is not nil",
396+
connectRequest: &quicpogs.ConnectRequest{
397+
Dest: "http://test.com",
398+
Metadata: []quicpogs.Metadata{
399+
quicpogs.Metadata{
400+
Key: "HttpHeader:Another-Header",
401+
Val: "Misc",
402+
},
403+
quicpogs.Metadata{
404+
Key: "HttpHeader:Transfer-Encoding",
405+
Val: "chunked",
406+
},
407+
quicpogs.Metadata{
408+
Key: "HttpHost",
409+
Val: "cf.host",
410+
},
411+
quicpogs.Metadata{
412+
Key: "HttpMethod",
413+
Val: "get",
414+
},
415+
},
416+
},
417+
req: &http.Request{
418+
Method: "get",
419+
URL: &url.URL{
420+
Scheme: "http",
421+
Host: "test.com",
422+
},
423+
Proto: "HTTP/1.1",
424+
ProtoMajor: 1,
425+
ProtoMinor: 1,
426+
Header: http.Header{
427+
"Another-Header": []string{"Misc"},
428+
"Transfer-Encoding": []string{"chunked"},
429+
},
430+
ContentLength: 0,
431+
Host: "cf.host",
432+
Body: io.NopCloser(&bytes.Buffer{}),
433+
},
434+
body: io.NopCloser(&bytes.Buffer{}),
435+
},
436+
{
437+
name: "if content length is 0, but transfer-encoding is gzip,chunked, body is not nil",
438+
connectRequest: &quicpogs.ConnectRequest{
439+
Dest: "http://test.com",
440+
Metadata: []quicpogs.Metadata{
441+
quicpogs.Metadata{
442+
Key: "HttpHeader:Another-Header",
443+
Val: "Misc",
444+
},
445+
quicpogs.Metadata{
446+
Key: "HttpHeader:Transfer-Encoding",
447+
Val: "gzip,chunked",
448+
},
449+
quicpogs.Metadata{
450+
Key: "HttpHost",
451+
Val: "cf.host",
452+
},
453+
quicpogs.Metadata{
454+
Key: "HttpMethod",
455+
Val: "get",
456+
},
457+
},
458+
},
459+
req: &http.Request{
460+
Method: "get",
461+
URL: &url.URL{
462+
Scheme: "http",
463+
Host: "test.com",
464+
},
465+
Proto: "HTTP/1.1",
466+
ProtoMajor: 1,
467+
ProtoMinor: 1,
468+
Header: http.Header{
469+
"Another-Header": []string{"Misc"},
470+
"Transfer-Encoding": []string{"gzip,chunked"},
471+
},
472+
ContentLength: 0,
473+
Host: "cf.host",
474+
Body: io.NopCloser(&bytes.Buffer{}),
475+
},
476+
body: io.NopCloser(&bytes.Buffer{}),
477+
},
478+
{
479+
name: "if content length is 0, and connect request is a websocket, body is not nil",
480+
connectRequest: &quicpogs.ConnectRequest{
481+
Type: quicpogs.ConnectionTypeWebsocket,
482+
Dest: "http://test.com",
483+
Metadata: []quicpogs.Metadata{
484+
quicpogs.Metadata{
485+
Key: "HttpHeader:Another-Header",
486+
Val: "Misc",
487+
},
488+
quicpogs.Metadata{
489+
Key: "HttpHost",
490+
Val: "cf.host",
491+
},
492+
quicpogs.Metadata{
493+
Key: "HttpMethod",
494+
Val: "get",
495+
},
496+
},
497+
},
498+
req: &http.Request{
499+
Method: "get",
500+
URL: &url.URL{
501+
Scheme: "http",
502+
Host: "test.com",
503+
},
504+
Proto: "HTTP/1.1",
505+
ProtoMajor: 1,
506+
ProtoMinor: 1,
507+
Header: http.Header{
508+
"Another-Header": []string{"Misc"},
509+
},
510+
ContentLength: 0,
511+
Host: "cf.host",
512+
Body: io.NopCloser(&bytes.Buffer{}),
383513
},
514+
body: io.NopCloser(&bytes.Buffer{}),
384515
},
385516
}
386517

387518
for _, test := range tests {
388519
t.Run(test.name, func(t *testing.T) {
389-
req, err := buildHTTPRequest(test.connectRequest, nil)
520+
req, err := buildHTTPRequest(test.connectRequest, test.body)
390521
assert.NoError(t, err)
391522
test.req = test.req.WithContext(req.Context())
392523
assert.Equal(t, test.req, req)

0 commit comments

Comments
 (0)