0
votes

I'm new to HTTP/2.0, and I'm trying to set up a TCP server, written in Golang, which receives and writes HTTP/2.0 frames. I'm having trouble writing any data back to the client.

The following code snippet shows how the request is handled.

conn, err := l.Accept()
if err != nil {
    log.Fatal("could not accept connection:", err)
}
defer conn.Close()

// Every connection starts with a connection preface send first, which has to be read prior
// to reading any frames (RFC 7540, section 3.5)
const preface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
b := make([]byte, len(preface))
if _, err := io.ReadFull(conn, b); err != nil {
    log.Fatal("could not read from connection:", err)
}
if string(b) != preface {
    log.Fatal("invalid preface")
}

framer := http2.NewFramer(conn, conn)

// Read client request (SETTINGS and HEADERS)
readFrames(framer)

// Send empty SETTINGS frame to the client
framer.WriteRawFrame(http2.FrameSettings, 0, 0, []byte{})

// Read clients response (contains empty SETTINGS with END_STREAM flag)
readFrames(framer)

// Prepare HEADERS
hbuf := bytes.NewBuffer([]byte{})
encoder := hpack.NewEncoder(hbuf)
encoder.WriteField(hpack.HeaderField{Name: ":status:", Value: "200"})
encoder.WriteField(hpack.HeaderField{Name: "date", Value: time.Now().UTC().Format(http.TimeFormat)})
encoder.WriteField(hpack.HeaderField{Name: "content-length", Value: strconv.Itoa(len("ok"))})
encoder.WriteField(hpack.HeaderField{Name: "content-type", Value: "text/html"})

// Write HEADERS frame 
err = framer.WriteHeaders(http2.HeadersFrameParam{StreamID: 2, BlockFragment: hbuf.Bytes(), EndHeaders: true})
if err != nil {
    log.Fatal("could not write headers: ", err)
}

// Clients response contains GOAWAY
readFrames(framer)

framer.WriteData(2, true, []byte("ok"))
conn.Close()

The full server can be found here: https://play.golang.org/p/ONA_OoyAMg-

It is called by doing a curl:

curl -kv https://127.0.0.1:8080 --http2

As far as I know, after the SETTINGS frames, the connection should be ready for traffic. A stream should be opened by sending a HEADERS frame, after which a DATA frame can be send on the open stream. However, after sending the HEADERS frame I get the following error message:

curl: (16) Error in the HTTP2 framing layer

The clients responds with a GOAWAY.

1
Why don't you just use the standard library's http package, which already does this for you? - Flimzy
@Flimzy I need a solution which takes the raw headers and body of the request and returns it as plain text in the body. - plambein
So you're building some sort of echo server? Why does the standard library not work for this? - Flimzy
Indeed. The idea is to inspect the raw package. Once the data has been parsed into an http.Request and dumped out again, you lose information like header order for example. - plambein
I am scratching my head why on earth the order of headers should make any difference. Iirc, any application MUST NOT (in RFCnese) rely on the order of headers. If the order of key-value pairs matters, they MUST be sent in one header. See Accept-* for example - Markus W Mahlberg

1 Answers

1
votes

I found the issue myself, if fact there were two. I found both issues in this article: http://undertow.io/blog/2015/04/27/An-in-depth-overview-of-HTTP2.html

First of all the required response header containing the status code should be :status instead of :status:.

And finally I didn't respond on the same stream ID from which the request was made and instead tried opening a new stream.

So I should loop over the request frames, look for the stream ID, and use it for writing out the response headers.

// Read client request (SETTINGS and HEADERS)
readFrames(framer)
var streamID uint32
for _, frame := range frames {
    if headersframe, ok := frame.(*http2.HeadersFrame); ok {
        streamID = headersframe.StreamID
    }
}

//... Prepare headers

// Write HEADERS frame 
err = framer.WriteHeaders(http2.HeadersFrameParam{StreamID: streamID, BlockFragment: hbuf.Bytes(), EndHeaders: true})
if err != nil {
    log.Fatal("could not write headers: ", err)
}

framer.WriteData(streamID, true, []byte("ok"))
conn.Close()