使用 HTTP2 传输获得意外的 EOF

Getting unexpected EOF with HTTP2 Transport

正在尝试使用 go 中的 http2 包,但在创建 HTTP2 连接时出现意外的 EOF 错误。无法找出确切的问题。

    tcpConn, err := net.Dial("tcp", "clients1.google.com:443")
    if err != nil {
        panic(err)
    }
    defer tcpConn.Close()

    t := http2.Transport{}
    http2ClientConn, err := t.NewClientConn(tcpConn)
    if err != nil {
        panic(err)
    }

    req, err := http.NewRequest("GET", "https://clients1.google.com/generate_204", nil)
    if err != nil {
        panic(err)
    }

    resp, err := http2ClientConn.RoundTrip(req)
    if err != nil {
        panic(err) // getting unexpected EOF
    }

    defer resp.Body.Close()
    fmt.Println(resp.StatusCode)

输出

panic: unexpected EOF

goroutine 1 [running]:
main.main()
        /Users/rishabharya/Desktop/Projects/src/github.com/rishabh-arya95/raw_http/main.go:31 +0x217
exit status 2

您应该使用 TLS encryption with Application-Layer Protocol Negotiation (ALPN)。

会发生什么?

  1. 客户端 establish 到服务器的网络连接。
  2. 客户sends a request using HTTP/2 protocol.
  3. 服务器接收数据并期望它是 TLS Handshake 协议的一部分。
  4. 服务器不得不关闭连接,因为数据不满足TLS协议。
  5. 返回 io.ErrUnexpectedEOF 错误,因为在读取响应之前连接已关闭。

需要改变什么

客户端建立与标准HTTPS端口的连接(端口号为443),但使用net.Dial() 函数。 HTTPS 协议使用 TLS 来保护 Internet 上的 HTTP 连接。因此,客户端必须使用tls.Dial()函数来建立连接:

tcpConn, err := tls.Dial("tcp", "clients1.google.com:443", new(tls.Config))

但是,客户端不能使用空TLS configuration. In this particular case, the server supports both protocols: HTTP/1.1 and HTTP/2。默认情况下,服务器使用 HTTP/1.1 协议。请求使用HTTP/2协议有两种方式:

第二种方法不适用。第一个原因是 http2 包不是为此而设计的。第二个原因是服务器忽略了给定请求的 HTTP 升级机制。

为了使用第一种方法,我们需要添加“h2”identifier to a list of supported application level protocols (a list of all registered identifiers can be found on IANA 网站)。

conf := & tls.Config {
    NextProtos: []string{"h2"},
}

tcpConn, err := tls.Dial("tcp", "clients1.google.com:443", conf)

工作代码

package main

import (
    "crypto/tls"
    "fmt"
    "net/http"

    "golang.org/x/net/http2"
)

func main() {
    conf := & tls.Config {
        NextProtos: []string{"h2"},
    }
    tcpConn, err := tls.Dial("tcp", "clients1.google.com:443", conf)
    if err != nil {
        panic(err)
    }
    defer tcpConn.Close()

    t := http2.Transport{}
    http2ClientConn, err := t.NewClientConn(tcpConn)
    if err != nil {
        panic(err)
    }

    req, err := http.NewRequest("GET", "https://clients1.google.com/generate_204", nil)
    if err != nil {
        panic(err)
    }

    resp, err := http2ClientConn.RoundTrip(req)
    if err != nil {
        panic(err)
    }

    defer resp.Body.Close()
    fmt.Println(resp.StatusCode)

}

还有一点modified version.


cURL 是一个方便的检查工具。

发出 HTTP/2 请求:

 $ curl -i --http2 https://clients1.google.com/generate_204
-| HTTP/2 204

禁用Application-Layer协议协商(ALPN):

 $ curl -i --http2 --no-alpn https://clients1.google.com/generate_204
-| HTTP/1.1 204 No Content

不使用 Application-Layer 协议协商 (ALPN) 发送 HTTP/2 请求:

 $ curl -i --http2-prior-knowledge --no-alpn https://clients1.google.com/generate_204
-| curl: (52) Empty reply from server

错误52:服务器未回复任何内容,此处视为错误。

Parameter Description
-i Include the HTTP response headers in the output.
-v Makes curl verbose during the operation.
--no-alpn Disable the ALPN TLS extension.
--http2 Tells curl to use HTTP version 2.
--http2-prior-knowledge Tells curl to use HTTP version 2.