Description
Proposal Details
In HTTP, a server can send multiple responses for one requests: zero or more informational responses (1xx) and one final response (2xx, 3xx, 4xx, 5xx). Go's HTTP client is capable of receiving those informational responses and exposes them to users via net/http/httptrace.ClientTrace.Got1xxResponse
. However, the client has a default limit of reading only up to 5 responses. Any additional 1xx response will trigger a net/http: too many 1xx informational responses
error.
Lines 2328 to 2329 in 8db1310
Lines 2354 to 2357 in 8db1310
The code comments and the original commit (d88b137) mention that the limit of 5 responses is arbitrary. If the limit is reached, the entire request is stopped and the client cannot receive the final response (2xx etc) anymore. This is problematic for applications, where the server repeatedly sends informational responses. 5 is a sensible default value for nearly all applications, but it would be helpful if this limit could be customized to allow more or even an unlimited amount of responses.
One option for implementing this, would be to add another field to the net/http.Client
struct. Setting it to a zero value keeps the current limit of 5 responses, while any other non-zero value sets the limit accordingly.
Background
In the HTTP working group of the IETF we are discussing a draft on resumable uploads. We are considering including a feature where the server can repeatedly send 1xx responses to inform the client about the upload progress. In these scenarios, the client sends data in the request body and repeatedly receives progress information in the 1xx responses. This progress information can be used to release data that is buffered on the client-side.
Example
Below you can find a brief program reproducing this behavior. The client sends a request to a server which responds with 10 1xx responses:
package main
import (
"context"
"fmt"
"log"
"net/http"
"net/http/httptest"
"net/http/httptrace"
"net/textproto"
"time"
)
func main() {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
for i := 0; i < 10; i++ {
w.Header().Set("X-Progress", fmt.Sprintf("%d%%", i*10))
w.WriteHeader(150)
<-time.After(100 * time.Millisecond)
}
w.WriteHeader(200)
}))
defer ts.Close()
ctx := httptrace.WithClientTrace(context.Background(), &httptrace.ClientTrace{
Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
fmt.Println("Progress:", header.Get("X-Progress"))
return nil
},
})
req, err := http.NewRequestWithContext(ctx, "GET", ts.URL, nil)
if err != nil {
log.Fatal(err)
}
client := ts.Client()
res, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
fmt.Println(res.Status)
}
The client receives the first 5 1xx responses, but then errors out. The final response is not received by the client.
$ go run clients/go/test/example.go
Progress: 0%
Progress: 10%
Progress: 20%
Progress: 30%
Progress: 40%
2024/01/09 10:05:56 Get "http://127.0.0.1:52073": net/http: HTTP/1.x transport connection broken: net/http: too many 1xx informational responses
exit status 1
If the limit could be raised, the client could receive all informational and the final response without an error.
Metadata
Metadata
Assignees
Type
Projects
Status