golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
120.99k stars 17.36k forks source link

net/http: h2 does not support other net.Conn than *tls.Conn #55076

Open lukeo3o1 opened 1 year ago

lukeo3o1 commented 1 year ago

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

$ go version
go1.19.1 windows/amd64

Does this issue reproduce with the latest release?

Yes

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

go env Output
$ go env
set GO111MODULE=
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\Luke\AppData\Local\go-build
set GOENV=C:\Users\Luke\AppData\Roaming\go\env
set GOEXE=.exe
set GOEXPERIMENT=
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GOINSECURE=
set GOMODCACHE=D:\lukeo3o1\go\pkg\mod
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=D:\lukeo3o1\go
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=C:\Program Files\Go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLDIR=C:\Program Files\Go\pkg\tool\windows_amd64
set GOVCS=
set GOVERSION=go1.19.1
set GCCGO=gccgo
set GOAMD64=v1
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=D:\lukeo3o1\go\src\test\go.mod
set GOWORK=
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -Wl,--no-gc-sections -fmessage-length=0 -fdebug-prefix-map=C:\Users\Luke\AppData\Local\Temp\go-build3309280822=/tmp/go-build -gno-record-gcc-switches

What did you do?

package main

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

    "github.com/soheilhy/cmux"
)

func main() {
    cer, err := tls.LoadX509KeyPair("server.crt", "server.key")
    if err != nil {
        panic(err)
    }

    l, err := tls.Listen("tcp", ":443", &tls.Config{
        Certificates: []tls.Certificate{cer},
        MinVersion:   tls.VersionTLS13,
        NextProtos:   []string{"h2", "http/1.1"},
    })
    if err != nil {
        panic(err)
    }
    defer l.Close()

    m := cmux.New(l)
    http2l := m.Match(cmux.HTTP2())

    server := http.Server{
        Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            fmt.Fprintln(w, "Hello, World!")
        }),
    }

    go m.Serve()

    if err := server.Serve(http2l); err != nil {
        log.Println(err)
    }
}

What did you expect to see?

2022/09/15 01:24:30 http2: Transport creating client conn 0xc00013c480 to [::1]:443
2022/09/15 01:24:30 http2: Framer 0xc0001540e0: wrote SETTINGS len=18, settings: ENABLE_PUSH=0, INITIAL_WINDOW_SIZE=4194304, MAX_HEADER_LIST_SIZE=10485760
2022/09/15 01:24:30 http2: Framer 0xc0001540e0: wrote WINDOW_UPDATE len=4 (conn) incr=1073741824
2022/09/15 01:24:30 http2: Transport encoding header ":authority" = "localhost"
2022/09/15 01:24:30 http2: Transport encoding header ":method" = "GET"
2022/09/15 01:24:30 http2: Transport encoding header ":path" = "/"
2022/09/15 01:24:30 http2: Transport encoding header ":scheme" = "https"
2022/09/15 01:24:30 http2: Transport encoding header "accept-encoding" = "gzip"
2022/09/15 01:24:30 http2: Transport encoding header "user-agent" = "Go-http-client/2.0"
2022/09/15 01:24:30 http2: Framer 0xc0001540e0: read SETTINGS len=24, settings: MAX_FRAME_SIZE=1048576, MAX_CONCURRENT_STREAMS=250, MAX_HEADER_LIST_SIZE=1048896, INITIAL_WINDOW_SIZE=1048576
2022/09/15 01:24:30 http2: Framer 0xc0001540e0: wrote HEADERS flags=END_STREAM|END_HEADERS stream=1 len=31
2022/09/15 01:24:30 http2: Transport received SETTINGS len=24, settings: MAX_FRAME_SIZE=1048576, MAX_CONCURRENT_STREAMS=250, MAX_HEADER_LIST_SIZE=1048896, INITIAL_WINDOW_SIZE=1048576
2022/09/15 01:24:30 http2: Framer 0xc0001540e0: wrote SETTINGS flags=ACK len=0
2022/09/15 01:24:30 http2: Framer 0xc0001540e0: read SETTINGS flags=ACK len=0
2022/09/15 01:24:30 http2: Transport received SETTINGS flags=ACK len=0
2022/09/15 01:24:30 http2: Framer 0xc0001540e0: read WINDOW_UPDATE len=4 (conn) incr=983041
2022/09/15 01:24:30 http2: Transport received WINDOW_UPDATE len=4 (conn) incr=983041
2022/09/15 01:24:30 http2: Framer 0xc0001540e0: read HEADERS flags=END_HEADERS stream=1 len=49
2022/09/15 01:24:30 http2: decoded hpack field header field ":status" = "200"
2022/09/15 01:24:30 http2: decoded hpack field header field "content-type" = "text/plain; charset=utf-8"
2022/09/15 01:24:30 http2: decoded hpack field header field "content-length" = "14"
2022/09/15 01:24:30 http2: decoded hpack field header field "date" = "Wed, 14 Sep 2022 17:24:30 GMT"
2022/09/15 01:24:30 http2: Transport received HEADERS flags=END_HEADERS stream=1 len=49
2022/09/15 01:24:30 http2: Framer 0xc0001540e0: read DATA flags=END_STREAM stream=1 len=14 data="Hello, World!\n"
2022/09/15 01:24:30 http2: Transport received DATA flags=END_STREAM stream=1 len=14 data="Hello, World!\n"

What did you see instead?

2022/09/15 01:26:25 http2: Transport creating client conn 0xc00013c480 to [::1]:443
2022/09/15 01:26:25 http2: Framer 0xc0001540e0: wrote SETTINGS len=18, settings: ENABLE_PUSH=0, INITIAL_WINDOW_SIZE=4194304, MAX_HEADER_LIST_SIZE=10485760
2022/09/15 01:26:25 http2: Framer 0xc0001540e0: wrote WINDOW_UPDATE len=4 (conn) incr=1073741824
2022/09/15 01:26:25 http2: Transport encoding header ":authority" = "localhost"
2022/09/15 01:26:25 http2: Transport encoding header ":method" = "GET"
2022/09/15 01:26:25 http2: Transport encoding header ":path" = "/"
2022/09/15 01:26:25 http2: Transport encoding header ":scheme" = "https"
2022/09/15 01:26:25 http2: Transport encoding header "accept-encoding" = "gzip"
2022/09/15 01:26:25 http2: Transport encoding header "user-agent" = "Go-http-client/2.0"
2022/09/15 01:26:25 http2: Framer 0xc0001540e0: wrote HEADERS flags=END_STREAM|END_HEADERS stream=1 len=31
2022/09/15 01:26:25 http2: Transport readFrame error on conn 0xc00013c480: (*net.OpError) read tcp [::1]:56749->[::1]:443: wsarecv: An established connection was aborted by the software in your host machine.
2022/09/15 01:26:25 RoundTrip failure: read tcp [::1]:56749->[::1]:443: wsarecv: An established connection was aborted by the software in your host machine.
neild commented 1 year ago

Server.Serve is documented as only supporting HTTP/2 on a *tls.Conn:

HTTP/2 support is only enabled if the Listener returns *tls.Conn connections and they were configured with "h2" in the TLS Config.NextProtos.

I'm not certain we want to weaken that requirement; the server is already fairly tightly coupled to *tls.Conn since it needs to access the ALPN negotiated protocol, and it seems plausible that we'd want to access other details of the TLS connection in the future. What's the use case for this?

lukeo3o1 commented 1 year ago

@neild thanks for your reply and notice

In my use case I want to use cmux to listen to http2 and grpc services on the same port.

But this *tls.Conn type requirement will make custom net.Conn not work with http2 even though the underlying operation is *tls.Conn

seankhliao commented 1 year ago

The usual way is via grpc's ServeHTTP and treat it like any other handler

gopherbot commented 1 year ago

Change https://go.dev/cl/431155 mentions this issue: net/http: http2 conn serve's net.Conn type assertion supports non *tls.Conn types

neild commented 1 year ago

Another way to do this today is to use http2.Server.ServeConn directly, rather than relying on net/http routing the connection to the HTTP/2 implementation.

lukeo3o1 commented 1 year ago

Another way to do this today is to use http2.Server.ServeConn directly, rather than relying on net/http routing the connection to the HTTP/2 implementation.

Oh, thx! This is a great idea to me

lukeo3o1 commented 1 year ago

If anyone has the same issue and usecase as me

Maybe you can try to use lukeo3o1/cmux@h2-support