mirror of
https://github.com/soheilhy/cmux.git
synced 2024-11-14 11:31:28 +08:00
feat: add h2 package for cmux support http2
This commit is contained in:
parent
177106da34
commit
0ef50993ff
291
h2/h2_server.go
Normal file
291
h2/h2_server.go
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
package h2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// validNextProto reports whether the proto is a valid ALPN protocol name.
|
||||||
|
// Everything is valid except the empty string and built-in protocol types,
|
||||||
|
// so that those can't be overridden with alternate implementations.
|
||||||
|
//
|
||||||
|
// grabbed from net/http.
|
||||||
|
func validNextProto(proto string) bool {
|
||||||
|
switch proto {
|
||||||
|
case "", "http/1.1", "http/1.0":
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// tlsRecordHeaderLooksLikeHTTP reports whether a TLS record header
|
||||||
|
// looks like it might've been a misdirected plaintext HTTP request.
|
||||||
|
//
|
||||||
|
// grabbed from net/http.
|
||||||
|
func tlsRecordHeaderLooksLikeHTTP(hdr [5]byte) bool {
|
||||||
|
switch string(hdr[:]) {
|
||||||
|
case "GET /", "HEAD ", "POST ", "PUT /", "OPTIO":
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
hs *http.Server
|
||||||
|
srv *http2.Server
|
||||||
|
doneChan chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type tlsConn interface {
|
||||||
|
net.Conn
|
||||||
|
ConnectionState() tls.ConnectionState
|
||||||
|
HandshakeContext(ctx context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// globalOptionsHandler responds to "OPTIONS *" requests.
|
||||||
|
//
|
||||||
|
// grabbed from net/http.
|
||||||
|
type globalOptionsHandler struct{}
|
||||||
|
|
||||||
|
func (globalOptionsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Length", "0")
|
||||||
|
if r.ContentLength != 0 {
|
||||||
|
// Read up to 4KB of OPTIONS body (as mentioned in the
|
||||||
|
// spec as being reserved for future use), but anything
|
||||||
|
// over that is considered a waste of server resources
|
||||||
|
// (or an attack) and we abort and close the connection,
|
||||||
|
// courtesy of MaxBytesReader's EOF behavior.
|
||||||
|
mb := http.MaxBytesReader(w, r.Body, 4<<10)
|
||||||
|
io.Copy(io.Discard, mb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initALPNRequest is an HTTP handler that initializes certain
|
||||||
|
// uninitialized fields in its *Request. Such partially-initialized
|
||||||
|
// Requests come from ALPN protocol handlers.
|
||||||
|
//
|
||||||
|
// grabbed from net/http.
|
||||||
|
type initALPNRequest struct {
|
||||||
|
ctx context.Context
|
||||||
|
c tlsConn
|
||||||
|
h serverHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// BaseContext is an exported but unadvertised http.Handler method
|
||||||
|
// recognized by x/net/http2 to pass down a context; the TLSNextProto
|
||||||
|
// API predates context support so we shoehorn through the only
|
||||||
|
// interface we have available.
|
||||||
|
//
|
||||||
|
// grabbed from net/http.
|
||||||
|
func (h initALPNRequest) BaseContext() context.Context { return h.ctx }
|
||||||
|
|
||||||
|
func (h initALPNRequest) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
if req.TLS == nil {
|
||||||
|
req.TLS = &tls.ConnectionState{}
|
||||||
|
*req.TLS = h.c.ConnectionState()
|
||||||
|
}
|
||||||
|
if req.Body == nil {
|
||||||
|
req.Body = http.NoBody
|
||||||
|
}
|
||||||
|
if req.RemoteAddr == "" {
|
||||||
|
req.RemoteAddr = h.c.RemoteAddr().String()
|
||||||
|
}
|
||||||
|
h.h.ServeHTTP(rw, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// serverHandler delegates to either the server's Handler or
|
||||||
|
// DefaultServeMux and also handles "OPTIONS *" requests.
|
||||||
|
//
|
||||||
|
// grabbed from net/http.
|
||||||
|
type serverHandler struct {
|
||||||
|
srv *http.Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sh serverHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
handler := sh.srv.Handler
|
||||||
|
if handler == nil {
|
||||||
|
handler = http.DefaultServeMux
|
||||||
|
}
|
||||||
|
if req.RequestURI == "*" && req.Method == "OPTIONS" {
|
||||||
|
handler = globalOptionsHandler{}
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.ServeHTTP(rw, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
type conn struct {
|
||||||
|
rwc net.Conn
|
||||||
|
Server
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c conn) tlsHandshakeTimeout() time.Duration {
|
||||||
|
srv := c.hs
|
||||||
|
var ret time.Duration
|
||||||
|
for _, v := range [...]time.Duration{
|
||||||
|
srv.ReadHeaderTimeout,
|
||||||
|
srv.ReadTimeout,
|
||||||
|
srv.WriteTimeout,
|
||||||
|
} {
|
||||||
|
if v <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ret == 0 || v < ret {
|
||||||
|
ret = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c conn) serve(ctx context.Context) {
|
||||||
|
remoteAddr := c.rwc.RemoteAddr().String()
|
||||||
|
ctx = context.WithValue(ctx, http.LocalAddrContextKey, c.rwc.LocalAddr())
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil && err != http.ErrAbortHandler {
|
||||||
|
const size = 64 << 10
|
||||||
|
buf := make([]byte, size)
|
||||||
|
buf = buf[:runtime.Stack(buf, false)]
|
||||||
|
c.logf("http: panic serving %v: %v\n%s", remoteAddr, err, buf)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if tlsConn, ok := c.rwc.(tlsConn); ok {
|
||||||
|
tlsTO := c.tlsHandshakeTimeout()
|
||||||
|
if tlsTO > 0 {
|
||||||
|
dl := time.Now().Add(tlsTO)
|
||||||
|
c.rwc.SetReadDeadline(dl)
|
||||||
|
c.rwc.SetWriteDeadline(dl)
|
||||||
|
}
|
||||||
|
if err := tlsConn.HandshakeContext(ctx); err != nil {
|
||||||
|
// If the handshake failed due to the client not speaking
|
||||||
|
// TLS, assume they're speaking plaintext HTTP and write a
|
||||||
|
// 400 response on the TLS conn's underlying net.Conn.
|
||||||
|
if re, ok := err.(tls.RecordHeaderError); ok && re.Conn != nil && tlsRecordHeaderLooksLikeHTTP(re.RecordHeader) {
|
||||||
|
io.WriteString(re.Conn, "HTTP/1.0 400 Bad Request\r\n\r\nClient sent an HTTP request to an HTTPS server.\n")
|
||||||
|
re.Conn.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Restore Conn-level deadlines.
|
||||||
|
if tlsTO > 0 {
|
||||||
|
c.rwc.SetReadDeadline(time.Time{})
|
||||||
|
c.rwc.SetWriteDeadline(time.Time{})
|
||||||
|
}
|
||||||
|
tlsState := tlsConn.ConnectionState()
|
||||||
|
if proto := tlsState.NegotiatedProtocol; validNextProto(proto) {
|
||||||
|
h := (http.Handler)(initALPNRequest{ctx, tlsConn, serverHandler{c.hs}})
|
||||||
|
|
||||||
|
type baseContexter interface {
|
||||||
|
BaseContext() context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
if bc, ok := h.(baseContexter); ok {
|
||||||
|
ctx = bc.BaseContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
c.srv.ServeConn(c.rwc, &http2.ServeConnOpts{
|
||||||
|
Context: ctx,
|
||||||
|
BaseConfig: c.hs,
|
||||||
|
Handler: h,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.rwc.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h2 Server) logf(format string, args ...interface{}) {
|
||||||
|
if h2.hs.ErrorLog != nil {
|
||||||
|
h2.hs.ErrorLog.Printf(format, args...)
|
||||||
|
} else {
|
||||||
|
log.Printf(format, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h2 Server) newConn(rwc net.Conn) conn {
|
||||||
|
return conn{
|
||||||
|
rwc: rwc,
|
||||||
|
Server: h2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h2 Server) Serve(l net.Listener) (err error) {
|
||||||
|
srv := h2.hs
|
||||||
|
|
||||||
|
baseCtx := context.Background()
|
||||||
|
if srv.BaseContext != nil {
|
||||||
|
baseCtx = srv.BaseContext(l)
|
||||||
|
if baseCtx == nil {
|
||||||
|
panic("BaseContext returned a nil context")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tempDelay time.Duration // how long to sleep on accept failure
|
||||||
|
|
||||||
|
ctx := context.WithValue(baseCtx, http.ServerContextKey, srv)
|
||||||
|
|
||||||
|
for {
|
||||||
|
rw, err := l.Accept()
|
||||||
|
if err != nil {
|
||||||
|
select {
|
||||||
|
case <-h2.doneChan:
|
||||||
|
return http.ErrServerClosed
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
if ne, ok := err.(net.Error); ok && ne.Temporary() {
|
||||||
|
if tempDelay == 0 {
|
||||||
|
tempDelay = 5 * time.Millisecond
|
||||||
|
} else {
|
||||||
|
tempDelay *= 2
|
||||||
|
}
|
||||||
|
if max := 1 * time.Second; tempDelay > max {
|
||||||
|
tempDelay = max
|
||||||
|
}
|
||||||
|
h2.logf("http: Accept error: %v; retrying in %v", err, tempDelay)
|
||||||
|
time.Sleep(tempDelay)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
connCtx := ctx
|
||||||
|
if cc := srv.ConnContext; cc != nil {
|
||||||
|
connCtx = cc(connCtx, rw)
|
||||||
|
if connCtx == nil {
|
||||||
|
panic("ConnContext returned nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tempDelay = 0
|
||||||
|
conn := h2.newConn(rw)
|
||||||
|
go conn.serve(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewServer(srv *http2.Server) *Server {
|
||||||
|
return &Server{
|
||||||
|
srv: srv,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h2 *Server) ConfigureServer(hs *http.Server) {
|
||||||
|
if h2.doneChan == nil {
|
||||||
|
h2.doneChan = make(chan struct{})
|
||||||
|
}
|
||||||
|
if h2.srv == nil {
|
||||||
|
h2.srv = &http2.Server{}
|
||||||
|
}
|
||||||
|
h2.hs = hs
|
||||||
|
http2.ConfigureServer(hs, h2.srv)
|
||||||
|
hs.RegisterOnShutdown(func() { close(h2.doneChan) })
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user