-
-
Notifications
You must be signed in to change notification settings - Fork 4k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
caddyhttp: Serve http2 when listener wrapper doesn't return *tls.Conn (…
…#4929) * Serve http2 when listener wrapper doesn't return *tls.Conn * close conn when h2server serveConn returns * merge from upstream * rebase from latest * run New and Closed ConnState hook for h2 conns * go fmt * fix lint * Add comments * reorder import
- Loading branch information
Showing
3 changed files
with
153 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package caddyhttp | ||
|
||
import ( | ||
"context" | ||
"crypto/tls" | ||
weakrand "math/rand" | ||
"net" | ||
"net/http" | ||
"sync/atomic" | ||
"time" | ||
|
||
"golang.org/x/net/http2" | ||
) | ||
|
||
// http2Listener wraps the listener to solve the following problems: | ||
// 1. server h2 natively without using h2c hack when listener handles tls connection but | ||
// don't return *tls.Conn | ||
// 2. graceful shutdown. the shutdown logic is copied from stdlib http.Server, it's an extra maintenance burden but | ||
// whatever, the shutdown logic maybe extracted to be used with h2c graceful shutdown. http2.Server supports graceful shutdown | ||
// sending GO_AWAY frame to connected clients, but doesn't track connection status. It requires explicit call of http2.ConfigureServer | ||
type http2Listener struct { | ||
cnt uint64 | ||
net.Listener | ||
server *http.Server | ||
h2server *http2.Server | ||
} | ||
|
||
type connectionStateConn interface { | ||
net.Conn | ||
ConnectionState() tls.ConnectionState | ||
} | ||
|
||
func (h *http2Listener) Accept() (net.Conn, error) { | ||
for { | ||
conn, err := h.Listener.Accept() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if csc, ok := conn.(connectionStateConn); ok { | ||
// *tls.Conn will return empty string because it's only populated after handshake is complete | ||
if csc.ConnectionState().NegotiatedProtocol == http2.NextProtoTLS { | ||
go h.serveHttp2(csc) | ||
continue | ||
} | ||
} | ||
|
||
return conn, nil | ||
} | ||
} | ||
|
||
func (h *http2Listener) serveHttp2(csc connectionStateConn) { | ||
atomic.AddUint64(&h.cnt, 1) | ||
h.runHook(csc, http.StateNew) | ||
defer func() { | ||
csc.Close() | ||
atomic.AddUint64(&h.cnt, ^uint64(0)) | ||
h.runHook(csc, http.StateClosed) | ||
}() | ||
h.h2server.ServeConn(csc, &http2.ServeConnOpts{ | ||
Context: h.server.ConnContext(context.Background(), csc), | ||
BaseConfig: h.server, | ||
Handler: h.server.Handler, | ||
}) | ||
} | ||
|
||
const shutdownPollIntervalMax = 500 * time.Millisecond | ||
|
||
func (h *http2Listener) Shutdown(ctx context.Context) error { | ||
pollIntervalBase := time.Millisecond | ||
nextPollInterval := func() time.Duration { | ||
// Add 10% jitter. | ||
//nolint:gosec | ||
interval := pollIntervalBase + time.Duration(weakrand.Intn(int(pollIntervalBase/10))) | ||
// Double and clamp for next time. | ||
pollIntervalBase *= 2 | ||
if pollIntervalBase > shutdownPollIntervalMax { | ||
pollIntervalBase = shutdownPollIntervalMax | ||
} | ||
return interval | ||
} | ||
|
||
timer := time.NewTimer(nextPollInterval()) | ||
defer timer.Stop() | ||
for { | ||
if atomic.LoadUint64(&h.cnt) == 0 { | ||
return nil | ||
} | ||
select { | ||
case <-ctx.Done(): | ||
return ctx.Err() | ||
case <-timer.C: | ||
timer.Reset(nextPollInterval()) | ||
} | ||
} | ||
} | ||
|
||
func (h *http2Listener) runHook(conn net.Conn, state http.ConnState) { | ||
if h.server.ConnState != nil { | ||
h.server.ConnState(conn, state) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters