Was trying out the http2 package in go, and while creating an HTTP2 connection I am getting an unexpected EOF error. Not able to figure out the exact issue.
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)
Output
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
CodePudding user response:
You should use TLS encryption with Application-Layer Protocol Negotiation (ALPN).
What happens?
- Client establish network connection to server.
- Client sends a request using HTTP/2 protocol.
- Server receives the data and expects it to be part of TLS Handshake protocol.
- Server has to close the connection because the data does not satisfy TLS protocol.
- An io.ErrUnexpectedEOF error is returned because the connection was closed before the response was read.
What needs to be change
The client establishes a connection to the standard HTTPS port (port number is 443), but use net.Dial() function. HTTPS protocol use TLS to secure HTTP connection over the Internet. Therefore, the client have to use tls.Dial() function to establish a connection:
tcpConn, err := tls.Dial("tcp", "clients1.google.com:443", new(tls.Config))
However, the client cannot use an empty TLS configuration. In this particular case, the server supports both protocols: HTTP/1.1 and HTTP/2. By default, the server uses HTTP/1.1 protocol. There are two ways to request the use of HTTP/2 protocol:
- Use Application-Layer Protocol Negotiation (ALPN) extension of TLS protocol.
- Use the HTTP Upgrade mechanism.
The second method is not suitable. The first reason is that http2 package is not intended for this. The second reason is that the server ignores HTTP Upgrade mechanism for the given request.
In order to use the first methods, we need to add "h2" identifier to a list of supported application level protocols (a list of all registered identifiers can be found on IANA website).
conf := & tls.Config {
NextProtos: []string{"h2"},
}
tcpConn, err := tls.Dial("tcp", "clients1.google.com:443", conf)
Working Code
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)
}
And a slightly modified version.
cURL is a handy tool for checking.
Make an HTTP/2 request:
$ curl -i --http2 https://clients1.google.com/generate_204
-| HTTP/2 204
Disable Application-Layer Protocol Negotiation (ALPN):
$ curl -i --http2 --no-alpn https://clients1.google.com/generate_204
-| HTTP/1.1 204 No Content
Send an HTTP/2 request without Application-Layer Protocol Negotiation (ALPN):
$ curl -i --http2-prior-knowledge --no-alpn https://clients1.google.com/generate_204
-| curl: (52) Empty reply from server
Error 52: The server did not reply anything, which here is considered an error.
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. |