summaryrefslogtreecommitdiff
path: root/vendor/golang.org/x/net/http2/h2c
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/golang.org/x/net/http2/h2c')
-rw-r--r--vendor/golang.org/x/net/http2/h2c/h2c.go225
1 files changed, 225 insertions, 0 deletions
diff --git a/vendor/golang.org/x/net/http2/h2c/h2c.go b/vendor/golang.org/x/net/http2/h2c/h2c.go
new file mode 100644
index 000000000..c3df711d9
--- /dev/null
+++ b/vendor/golang.org/x/net/http2/h2c/h2c.go
@@ -0,0 +1,225 @@
+// Copyright 2018 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package h2c implements the unencrypted "h2c" form of HTTP/2.
+//
+// The h2c protocol is the non-TLS version of HTTP/2 which is not available from
+// net/http or golang.org/x/net/http2.
+package h2c
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/base64"
+ "errors"
+ "fmt"
+ "io"
+ "log"
+ "net"
+ "net/http"
+ "net/textproto"
+ "os"
+ "strings"
+
+ "golang.org/x/net/http/httpguts"
+ "golang.org/x/net/http2"
+)
+
+var (
+ http2VerboseLogs bool
+)
+
+func init() {
+ e := os.Getenv("GODEBUG")
+ if strings.Contains(e, "http2debug=1") || strings.Contains(e, "http2debug=2") {
+ http2VerboseLogs = true
+ }
+}
+
+// h2cHandler is a Handler which implements h2c by hijacking the HTTP/1 traffic
+// that should be h2c traffic. There are two ways to begin a h2c connection
+// (RFC 7540 Section 3.2 and 3.4): (1) Starting with Prior Knowledge - this
+// works by starting an h2c connection with a string of bytes that is valid
+// HTTP/1, but unlikely to occur in practice and (2) Upgrading from HTTP/1 to
+// h2c - this works by using the HTTP/1 Upgrade header to request an upgrade to
+// h2c. When either of those situations occur we hijack the HTTP/1 connection,
+// convert it to a HTTP/2 connection and pass the net.Conn to http2.ServeConn.
+type h2cHandler struct {
+ Handler http.Handler
+ s *http2.Server
+}
+
+// NewHandler returns an http.Handler that wraps h, intercepting any h2c
+// traffic. If a request is an h2c connection, it's hijacked and redirected to
+// s.ServeConn. Otherwise the returned Handler just forwards requests to h. This
+// works because h2c is designed to be parseable as valid HTTP/1, but ignored by
+// any HTTP server that does not handle h2c. Therefore we leverage the HTTP/1
+// compatible parts of the Go http library to parse and recognize h2c requests.
+// Once a request is recognized as h2c, we hijack the connection and convert it
+// to an HTTP/2 connection which is understandable to s.ServeConn. (s.ServeConn
+// understands HTTP/2 except for the h2c part of it.)
+//
+// The first request on an h2c connection is read entirely into memory before
+// the Handler is called. To limit the memory consumed by this request, wrap
+// the result of NewHandler in an http.MaxBytesHandler.
+func NewHandler(h http.Handler, s *http2.Server) http.Handler {
+ return &h2cHandler{
+ Handler: h,
+ s: s,
+ }
+}
+
+// ServeHTTP implement the h2c support that is enabled by h2c.GetH2CHandler.
+func (s h2cHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ // Handle h2c with prior knowledge (RFC 7540 Section 3.4)
+ if r.Method == "PRI" && len(r.Header) == 0 && r.URL.Path == "*" && r.Proto == "HTTP/2.0" {
+ if http2VerboseLogs {
+ log.Print("h2c: attempting h2c with prior knowledge.")
+ }
+ conn, err := initH2CWithPriorKnowledge(w)
+ if err != nil {
+ if http2VerboseLogs {
+ log.Printf("h2c: error h2c with prior knowledge: %v", err)
+ }
+ return
+ }
+ defer conn.Close()
+ s.s.ServeConn(conn, &http2.ServeConnOpts{
+ Context: r.Context(),
+ Handler: s.Handler,
+ SawClientPreface: true,
+ })
+ return
+ }
+ // Handle Upgrade to h2c (RFC 7540 Section 3.2)
+ if isH2CUpgrade(r.Header) {
+ conn, settings, err := h2cUpgrade(w, r)
+ if err != nil {
+ if http2VerboseLogs {
+ log.Printf("h2c: error h2c upgrade: %v", err)
+ }
+ return
+ }
+ defer conn.Close()
+ s.s.ServeConn(conn, &http2.ServeConnOpts{
+ Context: r.Context(),
+ Handler: s.Handler,
+ UpgradeRequest: r,
+ Settings: settings,
+ })
+ return
+ }
+ s.Handler.ServeHTTP(w, r)
+ return
+}
+
+// initH2CWithPriorKnowledge implements creating a h2c connection with prior
+// knowledge (Section 3.4) and creates a net.Conn suitable for http2.ServeConn.
+// All we have to do is look for the client preface that is suppose to be part
+// of the body, and reforward the client preface on the net.Conn this function
+// creates.
+func initH2CWithPriorKnowledge(w http.ResponseWriter) (net.Conn, error) {
+ hijacker, ok := w.(http.Hijacker)
+ if !ok {
+ return nil, errors.New("h2c: connection does not support Hijack")
+ }
+ conn, rw, err := hijacker.Hijack()
+ if err != nil {
+ return nil, err
+ }
+
+ const expectedBody = "SM\r\n\r\n"
+
+ buf := make([]byte, len(expectedBody))
+ n, err := io.ReadFull(rw, buf)
+ if err != nil {
+ return nil, fmt.Errorf("h2c: error reading client preface: %s", err)
+ }
+
+ if string(buf[:n]) == expectedBody {
+ return newBufConn(conn, rw), nil
+ }
+
+ conn.Close()
+ return nil, errors.New("h2c: invalid client preface")
+}
+
+// h2cUpgrade establishes a h2c connection using the HTTP/1 upgrade (Section 3.2).
+func h2cUpgrade(w http.ResponseWriter, r *http.Request) (_ net.Conn, settings []byte, err error) {
+ settings, err = getH2Settings(r.Header)
+ if err != nil {
+ return nil, nil, err
+ }
+ hijacker, ok := w.(http.Hijacker)
+ if !ok {
+ return nil, nil, errors.New("h2c: connection does not support Hijack")
+ }
+
+ body, _ := io.ReadAll(r.Body)
+ r.Body = io.NopCloser(bytes.NewBuffer(body))
+
+ conn, rw, err := hijacker.Hijack()
+ if err != nil {
+ return nil, nil, err
+ }
+
+ rw.Write([]byte("HTTP/1.1 101 Switching Protocols\r\n" +
+ "Connection: Upgrade\r\n" +
+ "Upgrade: h2c\r\n\r\n"))
+ return newBufConn(conn, rw), settings, nil
+}
+
+// isH2CUpgrade returns true if the header properly request an upgrade to h2c
+// as specified by Section 3.2.
+func isH2CUpgrade(h http.Header) bool {
+ return httpguts.HeaderValuesContainsToken(h[textproto.CanonicalMIMEHeaderKey("Upgrade")], "h2c") &&
+ httpguts.HeaderValuesContainsToken(h[textproto.CanonicalMIMEHeaderKey("Connection")], "HTTP2-Settings")
+}
+
+// getH2Settings returns the settings in the HTTP2-Settings header.
+func getH2Settings(h http.Header) ([]byte, error) {
+ vals, ok := h[textproto.CanonicalMIMEHeaderKey("HTTP2-Settings")]
+ if !ok {
+ return nil, errors.New("missing HTTP2-Settings header")
+ }
+ if len(vals) != 1 {
+ return nil, fmt.Errorf("expected 1 HTTP2-Settings. Got: %v", vals)
+ }
+ settings, err := base64.RawURLEncoding.DecodeString(vals[0])
+ if err != nil {
+ return nil, err
+ }
+ return settings, nil
+}
+
+func newBufConn(conn net.Conn, rw *bufio.ReadWriter) net.Conn {
+ rw.Flush()
+ if rw.Reader.Buffered() == 0 {
+ // If there's no buffered data to be read,
+ // we can just discard the bufio.ReadWriter.
+ return conn
+ }
+ return &bufConn{conn, rw.Reader}
+}
+
+// bufConn wraps a net.Conn, but reads drain the bufio.Reader first.
+type bufConn struct {
+ net.Conn
+ *bufio.Reader
+}
+
+func (c *bufConn) Read(p []byte) (int, error) {
+ if c.Reader == nil {
+ return c.Conn.Read(p)
+ }
+ n := c.Reader.Buffered()
+ if n == 0 {
+ c.Reader = nil
+ return c.Conn.Read(p)
+ }
+ if n < len(p) {
+ p = p[:n]
+ }
+ return c.Reader.Read(p)
+}