Browse Source

Fix connection reuse in splithttp HTTP/1.1 (#3485)

mmmray 1 year ago
parent
commit
c6a57b2cc1
1 changed files with 23 additions and 10 deletions
  1. 23 10
      transport/internet/splithttp/dialer.go

+ 23 - 10
transport/internet/splithttp/dialer.go

@@ -1,6 +1,7 @@
 package splithttp
 
 import (
+	"bytes"
 	"context"
 	gotls "crypto/tls"
 	"io"
@@ -263,6 +264,7 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
 					return
 				}
 
+				req.ContentLength = int64(chunk.Len())
 				req.Header = transportConfiguration.GetRequestHeader()
 
 				if httpClient.isH2 {
@@ -280,11 +282,19 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
 						return
 					}
 				} else {
-					var err error
 					var uploadConn any
-					for i := 0; i < 5; i++ {
+
+					// stringify the entire HTTP/1.1 request so it can be
+					// safely retried. if instead req.Write is called multiple
+					// times, the body is already drained after the first
+					// request
+					requestBytes := new(bytes.Buffer)
+					common.Must(req.Write(requestBytes))
+
+					for {
 						uploadConn = httpClient.uploadRawPool.Get()
-						if uploadConn == nil {
+						newConnection := uploadConn == nil
+						if newConnection {
 							uploadConn, err = httpClient.dialUploadConn(context.WithoutCancel(ctx))
 							if err != nil {
 								errors.LogInfoInner(ctx, err, "failed to connect upload")
@@ -293,18 +303,21 @@ func Dial(ctx context.Context, dest net.Destination, streamSettings *internet.Me
 							}
 						}
 
-						err = req.Write(uploadConn.(net.Conn))
+						_, err = uploadConn.(net.Conn).Write(requestBytes.Bytes())
+
+						// if the write failed, we try another connection from
+						// the pool, until the write on a new connection fails.
+						// failed writes to a pooled connection are normal when
+						// the connection has been closed in the meantime.
 						if err == nil {
 							break
+						} else if newConnection {
+							errors.LogInfoInner(ctx, err, "failed to send upload")
+							uploadPipeReader.Interrupt()
+							return
 						}
 					}
 
-					if err != nil {
-						errors.LogInfoInner(ctx, err, "failed to send upload")
-						uploadPipeReader.Interrupt()
-						return
-					}
-
 					httpClient.uploadRawPool.Put(uploadConn)
 				}
 			}()