Open wzshiming opened 4 years ago
What is the use case for something like this? Could you just create a net.Listener implementation which returns your connection when Accept is called?
This is what I wrote now, but it does n’t feel good.
type Listener struct {
ch chan net.Conn
}
func NewListener() *Listener {
return &Listener{
ch: make(chan net.Conn),
}
}
// Send a conn to listener.
func (l *Listener) Send(conn net.Conn) error {
l.ch <- conn
return nil
}
// Accept waits for and returns the next connection to the listener.
func (l *Listener) Accept() (net.Conn, error) {
conn, ok := <-l.ch
if !ok {
return nil, io.ErrClosedPipe
}
return conn, nil
}
// Close closes the listener.
// Any blocked Accept operations will be unblocked and return errors.
func (l *Listener) Close() error {
close(l.ch)
return nil
}
// Addr returns the listener's network address.
func (l *Listener) Addr() net.Addr {
return noneAddr{}
}
type noneAddr struct {
}
func (noneAddr) Network() string {
return "none"
}
func (noneAddr) String() string {
return "none"
}
Yeah, implementing your own single connection listener is what's usually done. I see a few on GitHub:
https://github.com/search?q=singleConnListener&type=Code
And IIRC we have one or more in the standard library's tests.
================== WARNING: DATA RACE Read at 0x00c000001b60 by goroutine 75: net/http.http2ConfigureServer() /usr/local/Cellar/go/1.13.5/libexec/src/net/http/h2_bundle.go:3828 +0x307 net/http.(*Server).onceSetNextProtoDefaults() /usr/local/Cellar/go/1.13.5/libexec/src/net/http/server.go:3168 +0x145 net/http.(*Server).onceSetNextProtoDefaults-fm() /usr/local/Cellar/go/1.13.5/libexec/src/net/http/server.go:3158 +0x41 sync.(*Once).doSlow() /usr/local/Cellar/go/1.13.5/libexec/src/sync/once.go:66 +0x100 sync.(*Once).Do() /usr/local/Cellar/go/1.13.5/libexec/src/sync/once.go:57 +0x68 net/http.(*Server).setupHTTP2_ServeTLS() /usr/local/Cellar/go/1.13.5/libexec/src/net/http/server.go:3132 +0x68 net/http.(*Server).ServeTLS() /usr/local/Cellar/go/1.13.5/libexec/src/net/http/server.go:2948 +0x53 Previous write at 0x00c000001b60 by goroutine 42: net/http.http2ConfigureServer() /usr/local/Cellar/go/1.13.5/libexec/src/net/http/h2_bundle.go:3835 +0x63b net/http.(*Server).onceSetNextProtoDefaults() /usr/local/Cellar/go/1.13.5/libexec/src/net/http/server.go:3168 +0x145 net/http.(*Server).onceSetNextProtoDefaults-fm() /usr/local/Cellar/go/1.13.5/libexec/src/net/http/server.go:3158 +0x41 sync.(*Once).doSlow() /usr/local/Cellar/go/1.13.5/libexec/src/sync/once.go:66 +0x100 sync.(*Once).Do() /usr/local/Cellar/go/1.13.5/libexec/src/sync/once.go:57 +0x68 net/http.(*Server).setupHTTP2_ServeTLS() /usr/local/Cellar/go/1.13.5/libexec/src/net/http/server.go:3132 +0x68 net/http.(*Server).ServeTLS() /usr/local/Cellar/go/1.13.5/libexec/src/net/http/server.go:2948 +0x53 github.com/wzshiming/pipe/stream/http.(*server).serve()
There seems to be something wrong with TLS I can only add "h2" to tls.nextprotos in advance
s.tls.NextProtos = append(s.tls.NextProtos, "h2")
func (s *server) ServeConn(ctx context.Context, conn net.Conn) {
listener := &singleConnListener{conn}
var svc = http.Server{
Handler: s,
TLSConfig: s.tls,
BaseContext: func(net.Listener) context.Context {
return ctx
},
}
if s.tls == nil {
err := svc.Serve(listener)
if err != nil {
log.Println("[ERROR]", err)
}
} else {
err := svc.ServeTLS(listener, "", "")
if err != nil {
log.Println("[ERROR]", err)
}
}
}
================== WARNING: DATA RACE Write at 0x00c00008b738 by goroutine 75: net/http.http2ConfigureServer() /usr/local/Cellar/go/1.13.5/libexec/src/net/http/h2_bundle.go:3825 +0x2c4 net/http.(*Server).onceSetNextProtoDefaults() /usr/local/Cellar/go/1.13.5/libexec/src/net/http/server.go:3168 +0x145 net/http.(*Server).onceSetNextProtoDefaults-fm() /usr/local/Cellar/go/1.13.5/libexec/src/net/http/server.go:3158 +0x41 sync.(*Once).doSlow() /usr/local/Cellar/go/1.13.5/libexec/src/sync/once.go:66 +0x100 sync.(*Once).Do() /usr/local/Cellar/go/1.13.5/libexec/src/sync/once.go:57 +0x68 net/http.(*Server).setupHTTP2_ServeTLS() /usr/local/Cellar/go/1.13.5/libexec/src/net/http/server.go:3132 +0x68 net/http.(*Server).ServeTLS() /usr/local/Cellar/go/1.13.5/libexec/src/net/http/server.go:2948 +0x53 github.com/wzshiming/pipe/stream/http.(*server).serve() Previous write at 0x00c00008b738 by goroutine 43: net/http.http2ConfigureServer() /usr/local/Cellar/go/1.13.5/libexec/src/net/http/h2_bundle.go:3825 +0x2c4 net/http.(*Server).onceSetNextProtoDefaults() /usr/local/Cellar/go/1.13.5/libexec/src/net/http/server.go:3168 +0x145 net/http.(*Server).onceSetNextProtoDefaults-fm() /usr/local/Cellar/go/1.13.5/libexec/src/net/http/server.go:3158 +0x41 sync.(*Once).doSlow() /usr/local/Cellar/go/1.13.5/libexec/src/sync/once.go:66 +0x100 sync.(*Once).Do() /usr/local/Cellar/go/1.13.5/libexec/src/sync/once.go:57 +0x68 net/http.(*Server).setupHTTP2_ServeTLS() /usr/local/Cellar/go/1.13.5/libexec/src/net/http/server.go:3132 +0x68 net/http.(*Server).ServeTLS() /usr/local/Cellar/go/1.13.5/libexec/src/net/http/server.go:2948 +0x53
I think it is necessary to build a pool for the same tlsconfig for reuse
func (s *server) serve(ctx context.Context, listener net.Listener, handler http.Handler) { var svc = http.Server{ Handler: handler, TLSConfig: s.tls, BaseContext: func(net.Listener) context.Context { return ctx }, } if s.tls == nil { err := svc.Serve(listener) if err != nil { log.Println("[ERROR] ", err) } } else { err := svc.ServeTLS(listener, "", "") if err != nil && err != io.ErrClosedPipe { log.Println("[ERROR] ", err) } } } func (s *server) ServeConn(ctx context.Context, conn net.Conn) { wait := make(chan struct{}) s.serve(ctx, &singleConnListener{conn}, http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { s.handler.ServeHTTP(rw, r) wait <- struct{}{} })) <-wait }
The listener will fork a goroutine, need to wait for it to finish.
+1. x/net/http2 already has this - but it would be great to at the very least have something that implements this for http1.1 as well.
Even better would be if net/http had one that could do http1.1 and http2 based on the connection it was given (if c.ConnectionState().NegotiatedProtocol == "h2"
yadda yadda).
I'd need this as well, here's my use-case.
I'm writing an HTTP server behind a TLS reverse proxy. The reverse proxy terminates TLS connections, and forwards them to my HTTP server. The PROXY protocol is used to forward TLS metadata (e.g. ALPN).
I'd like to make HTTP2 work if advertised by the client by ALPN. However, net/http
won't enable HTTP2 unless the connection is a *tls.Conn
. The x/net/http2
package won't support HTTP1. So it's not easy to do the routing.
One solution would be to listen for incoming connections manually, and add them to the HTTP1 or HTTP2 servers depending on the relayed ALPN protocol string. This requires ServeConn
.
Is this use-case enough for you? I'm willing to work on a patch.
(Another solution would be to make net/http
assert on an interface rather than *tls.Conn
.)
cc @ianlancetaylor re incoming proposal
Any updates on this? I'd love to take a crack at it myself if that's acceptable.
I also need the same function.
My case: I'm writing a proxy that starts TLS termination, and if the proxy has a certificate for the requested SNI, it uses it for termination, but if there is no certificate, the proxy must send TLS Hello to the specified upstream (another server). Unfortunately, I can't implement this in the custom listener in the Accept()
method, because then the TLS termination would happen synchronously (which would slow down my program), while in http.Server
it happens in a separate goroutine.
So, for HTTP/2 requests I am using ServeConn
from x/net/http2
package and everything works well. When I got HTTP/1 request, I should handle it by myself, so I have to write my own HTTP server. I am using ReadRequest
function to parse HTTP request, but I should create my own ResponseWriter
, handle deadlines and keep-alive connections.
Has there been any further work on this? I'd also love for this to be a feature
Example real-world use of the workaround: https://github.com/pires/go-proxyproto/blob/e5b291b295b4a6e0425a134c8b3bfa6b2e692866/helper/http2/listener.go#L8
or