go: net/http: unexpected 1XX status codes are not handled well

Please answer these questions before submitting your issue. Thanks!

What version of Go are you using (go version)?

go version go1.7.3 darwin/amd64

What operating system and processor architecture are you using (go env)?

GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOPATH="/Users/cory/Documents/Go"
GORACE=""
GOROOT="/usr/local/Cellar/go/1.7.3/libexec"
GOTOOLDIR="/usr/local/Cellar/go/1.7.3/libexec/pkg/tool/darwin_amd64"
CC="clang"
GOGCCFLAGS="-fPIC -m64 -pthread -fno-caret-diagnostics -Qunused-arguments -fmessage-length=0 -fdebug-prefix-map=/var/folders/nc/4r8rg3dx3h9b4t4b0bzcv8x40000gn/T/go-build564596711=/tmp/go-build -gno-record-gcc-switches -fno-common"
CXX="clang++"
CGO_ENABLED="1"

What did you do?

Running the following Python test server:

import socket
import time

document = b'''<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>title</title>
    <link rel="stylesheet" href="/other/styles.css">
    <script src="/other/action.js"></script>
  </head>
  <body>
    <h1>Hello, world!</h1>
  </body>
</html>
'''

s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('localhost', 8080))
s.listen(5)

while True:
    new_socket, _ = s.accept()
    data = b''

    while not data.endswith(b'\r\n\r\n'):
        data += new_socket.recv(8192)

    new_socket.sendall(
        b'HTTP/1.1 103 Early Hints\r\n'
        b'Content-Length: 0\r\n'
        b'Server: socketserver/1.0.0\r\n'
        b'Link: </other/styles.css>; rel=preload; as=style\r\n'
        b'Link: </other/action.js>; rel=preload; as=script\r\n'
        b'\r\n'
    )
    time.sleep(1)
    new_socket.sendall(
        b'HTTP/1.1 200 OK\r\n'
        b'Server: socketserver/1.0.0\r\n'
        b'Content-Type: text/html\r\n'
        b'Content-Length: %s\r\n'
        b'Link: </other/styles.css>; rel=preload; as=style\r\n'
        b'Link: </other/action.js>; rel=preload; as=script\r\n'
        b'Connection: close\r\n'
        b'\r\n' % len(document)
    )
    new_socket.sendall(document)
    new_socket.close()

I ran the following Go test client against it:

package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
    "log"
)

func request(client *http.Client) {
    resp, err := client.Get("http://localhost:8080/")

    if err != nil {
        log.Fatal(err);
    }
    data, err := ioutil.ReadAll(resp.Body)
    resp.Body.Close()
    if err != nil {
        log.Fatal(err)
    }

    fmt.Printf("Status: %s", resp.Status)
    fmt.Printf("Body: %s", data)
}

func main() {
    transport := &http.Transport{}
    client := &http.Client{Transport: transport}
    request(client)
    request(client)
}

What did you expect to see?

I expected to see a 200 status code and the HTML body from the 200 response.

What did you see instead?

Go reported the 103 status code as final with no body, and did not provide access to the 200 response.

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 20 (12 by maintainers)

Commits related to this issue

Most upvoted comments

I think this issue conflates a bigger, easier problem with a smaller, harder problem.

The bigger, easier problem is that the Go HTTP client is completely confused by 1xx responses other than a single 100 (Continue), in clear violation of the protocol, hampering interoperability with RFC 8297 among other things. It can be trivially fixed by replacing if resp.StatusCode == 100 with for resp.StatusCode >= 100 && resp.StatusCode <= 199 in src/net/http/transport.go.

The smaller, harder problem is that the Go HTTP client doesn’t provide an API for 1xx responses, which precludes building a correct HTTP proxy on top of it. But I don’t think this should prevent or delay fixing the bigger, easier problem. As far as I’m aware, the Go distribution doesn’t even include an HTTP proxy (httputil.ReverseProxy is not a proxy in the language of RFC 723x — it’s a gateway).

Would you like me to submit the above fix to transport.go?