@@ -42,12 +42,26 @@ const (
4242 HTTPMethodKey = "HttpMethod"
4343 // HTTPHostKey is used to get or set http Method in QUIC ALPN if the underlying proxy connection type is HTTP.
4444 HTTPHostKey = "HttpHost"
45+ // HTTPRequestBodyHintKey is used in ConnectRequest metadata to indicate if the request has body
46+ HTTPRequestBodyHintKey = "HttpReqBodyHint"
4547
4648 QUICMetadataFlowID = "FlowID"
4749 // emperically this capacity has been working well
4850 demuxChanCapacity = 16
4951)
5052
53+ type RequestBodyHint uint64
54+
55+ const (
56+ RequestBodyHintMissing RequestBodyHint = iota
57+ RequestBodyHintEmpty
58+ RequestBodyHintHasData
59+ )
60+
61+ func (rbh RequestBodyHint ) String () string {
62+ return [... ]string {"missing" , "empty" , "data" }[rbh ]
63+ }
64+
5165var (
5266 portForConnIndex = make (map [uint8 ]int , 0 )
5367 portMapMutex sync.Mutex
@@ -486,7 +500,6 @@ func buildHTTPRequest(
486500 dest := connectRequest .Dest
487501 method := metadata [HTTPMethodKey ]
488502 host := metadata [HTTPHostKey ]
489- isWebsocket := connectRequest .Type == pogs .ConnectionTypeWebsocket
490503
491504 req , err := http .NewRequestWithContext (ctx , method , dest , body )
492505 if err != nil {
@@ -511,13 +524,8 @@ func buildHTTPRequest(
511524 return nil , fmt .Errorf ("Error setting content-length: %w" , err )
512525 }
513526
514- // Go's client defaults to chunked encoding after a 200ms delay if the following cases are true:
515- // * the request body blocks
516- // * the content length is not set (or set to -1)
517- // * the method doesn't usually have a body (GET, HEAD, DELETE, ...)
518- // * there is no transfer-encoding=chunked already set.
519- // So, if transfer cannot be chunked and content length is 0, we dont set a request body.
520- if ! isWebsocket && ! isTransferEncodingChunked (req ) && req .ContentLength == 0 {
527+ if shouldSetRequestBodyToEmpty (connectRequest , metadata , req ) {
528+ log .Debug ().Str ("host" , req .Host ).Str ("method" , req .Method ).Msg ("Set request to have no body" )
521529 req .Body = http .NoBody
522530 }
523531 stripWebsocketUpgradeHeader (req )
@@ -542,6 +550,35 @@ func isTransferEncodingChunked(req *http.Request) bool {
542550 return strings .Contains (strings .ToLower (transferEncodingVal ), "chunked" )
543551}
544552
553+ // Borrowed from https://github.com/golang/go/blob/go1.22.6/src/net/http/request.go#L1541
554+ func requestMethodUsuallyLacksBody (req * http.Request ) bool {
555+ switch strings .ToUpper (req .Method ) {
556+ case "GET" , "HEAD" , "DELETE" , "OPTIONS" , "PROPFIND" , "SEARCH" :
557+ return true
558+ }
559+ return false
560+ }
561+
562+ func shouldSetRequestBodyToEmpty (connectRequest * pogs.ConnectRequest , metadata map [string ]string , req * http.Request ) bool {
563+ switch metadata [HTTPRequestBodyHintKey ] {
564+ case RequestBodyHintEmpty .String ():
565+ return true
566+ case RequestBodyHintHasData .String ():
567+ return false
568+ default :
569+ }
570+
571+ isWebsocket := connectRequest .Type == pogs .ConnectionTypeWebsocket
572+ // Go's client defaults to chunked encoding after a 200ms delay if the following cases are true:
573+ // * the request body blocks
574+ // * the content length is not set (or set to -1)
575+ // * the method doesn't usually have a body (GET, HEAD, DELETE, ...)
576+ // * there is no transfer-encoding=chunked already set.
577+ // So, if transfer cannot be chunked and content length is 0, we dont set a request body.
578+ // Reference: https://github.com/golang/go/blob/go1.22.2/src/net/http/transfer.go#L192-L206
579+ return ! isWebsocket && requestMethodUsuallyLacksBody (req ) && ! isTransferEncodingChunked (req ) && req .ContentLength == 0
580+ }
581+
545582// A helper struct that guarantees a call to close only affects read side, but not write side.
546583type nopCloserReadWriter struct {
547584 io.ReadWriteCloser
0 commit comments