grpc and http runs on the same port
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
a369f65cba
commit
949d97b49c
2
go.mod
2
go.mod
@ -20,6 +20,8 @@ require (
|
|||||||
github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef
|
github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef
|
||||||
go.xsfx.dev/logginghandler v0.0.4
|
go.xsfx.dev/logginghandler v0.0.4
|
||||||
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
|
||||||
|
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4
|
||||||
|
google.golang.org/genproto v0.0.0-20210426193834-eac7f76ac494
|
||||||
google.golang.org/grpc v1.37.0
|
google.golang.org/grpc v1.37.0
|
||||||
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0
|
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0
|
||||||
google.golang.org/protobuf v1.26.0
|
google.golang.org/protobuf v1.26.0
|
||||||
|
@ -19,7 +19,6 @@ type Config struct {
|
|||||||
Box struct {
|
Box struct {
|
||||||
Hostname string `mapstructure:"Hostname"`
|
Hostname string `mapstructure:"Hostname"`
|
||||||
Port int `mapstructure:"Port"`
|
Port int `mapstructure:"Port"`
|
||||||
Grpc int `mapstructure:"Grpc"`
|
|
||||||
} `mapstructure:"Box"`
|
} `mapstructure:"Box"`
|
||||||
|
|
||||||
// MPD contains the connection details for the Music Player Daemon.
|
// MPD contains the connection details for the Music Player Daemon.
|
||||||
|
@ -4,8 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
@ -16,6 +16,8 @@ import (
|
|||||||
"go.xsfx.dev/schnutibox/internal/config"
|
"go.xsfx.dev/schnutibox/internal/config"
|
||||||
api "go.xsfx.dev/schnutibox/pkg/api/v1"
|
api "go.xsfx.dev/schnutibox/pkg/api/v1"
|
||||||
"go.xsfx.dev/schnutibox/pkg/sselog"
|
"go.xsfx.dev/schnutibox/pkg/sselog"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
"golang.org/x/net/http2/h2c"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/reflection"
|
"google.golang.org/grpc/reflection"
|
||||||
)
|
)
|
||||||
@ -39,6 +41,18 @@ func root(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// grpcHandlerFunc reads header and returns a grpc handler or a http one.
|
||||||
|
// nolint:interfacer
|
||||||
|
func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler {
|
||||||
|
return h2c.NewHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.ProtoMajor == 2 && strings.Contains(r.Header.Get("Content-Type"), "application/grpc") {
|
||||||
|
grpcServer.ServeHTTP(w, r)
|
||||||
|
} else {
|
||||||
|
otherHandler.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
}), &http2.Server{})
|
||||||
|
}
|
||||||
|
|
||||||
type server struct{}
|
type server struct{}
|
||||||
|
|
||||||
// Identify searches in tracks config for entries and returns them.
|
// Identify searches in tracks config for entries and returns them.
|
||||||
@ -61,25 +75,13 @@ func (s server) Identify(ctx context.Context, in *api.IdentifyRequest) (*api.Ide
|
|||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func gw(conn string) *runtime.ServeMux {
|
func gw(s *grpc.Server, conn string) *runtime.ServeMux {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
gopts := []grpc.DialOption{grpc.WithInsecure()}
|
gopts := []grpc.DialOption{grpc.WithInsecure()}
|
||||||
s := grpc.NewServer()
|
|
||||||
|
|
||||||
api.RegisterIdentifierServiceServer(s, server{})
|
api.RegisterIdentifierServiceServer(s, server{})
|
||||||
reflection.Register(s)
|
reflection.Register(s)
|
||||||
|
|
||||||
lis, err := net.Listen("tcp", conn)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal().Err(err).Msg("could not listen")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info().Msgf("serving GRPC on %s...", conn)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
log.Fatal().Err(s.Serve(lis))
|
|
||||||
}()
|
|
||||||
|
|
||||||
gwmux := runtime.NewServeMux()
|
gwmux := runtime.NewServeMux()
|
||||||
if err := api.RegisterIdentifierServiceHandlerFromEndpoint(ctx, gwmux, conn, gopts); err != nil {
|
if err := api.RegisterIdentifierServiceHandlerFromEndpoint(ctx, gwmux, conn, gopts); err != nil {
|
||||||
log.Fatal().Err(err).Msg("could not register grpc endpoint")
|
log.Fatal().Err(err).Msg("could not register grpc endpoint")
|
||||||
@ -91,7 +93,9 @@ func gw(conn string) *runtime.ServeMux {
|
|||||||
func Run(command *cobra.Command, args []string) {
|
func Run(command *cobra.Command, args []string) {
|
||||||
// Create host string for serving web.
|
// Create host string for serving web.
|
||||||
lh := fmt.Sprintf("%s:%d", config.Cfg.Box.Hostname, config.Cfg.Box.Port)
|
lh := fmt.Sprintf("%s:%d", config.Cfg.Box.Hostname, config.Cfg.Box.Port)
|
||||||
lg := fmt.Sprintf("%s:%d", config.Cfg.Box.Hostname, config.Cfg.Box.Grpc)
|
|
||||||
|
// Create grpc server.
|
||||||
|
grpcServer := grpc.NewServer()
|
||||||
|
|
||||||
// Define http handlers.
|
// Define http handlers.
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
@ -104,11 +108,18 @@ func Run(command *cobra.Command, args []string) {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
mux.Handle("/metrics", promhttp.Handler())
|
mux.Handle("/metrics", promhttp.Handler())
|
||||||
|
mux.Handle("/api/", http.StripPrefix("/api", gw(grpcServer, lh)))
|
||||||
mux.Handle("/api/", http.StripPrefix("/api", gw(lg)))
|
|
||||||
|
|
||||||
// Serving http.
|
// Serving http.
|
||||||
log.Info().Msgf("serving HTTP on %s...", lh)
|
log.Info().Msgf("serving HTTP on %s...", lh)
|
||||||
|
|
||||||
log.Fatal().Err(http.ListenAndServe(lh, logginghandler.Handler(mux))).Msg("goodbye")
|
log.Fatal().Err(
|
||||||
|
http.ListenAndServe(
|
||||||
|
lh,
|
||||||
|
grpcHandlerFunc(
|
||||||
|
grpcServer,
|
||||||
|
logginghandler.Handler(mux),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).Msg("goodbye")
|
||||||
}
|
}
|
||||||
|
501
vendor/golang.org/x/net/http2/h2c/h2c.go
generated
vendored
Normal file
501
vendor/golang.org/x/net/http2/h2c/h2c.go
generated
vendored
Normal file
@ -0,0 +1,501 @@
|
|||||||
|
// 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"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/textproto"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/net/http/httpguts"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
|
"golang.org/x/net/http2/hpack"
|
||||||
|
)
|
||||||
|
|
||||||
|
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.)
|
||||||
|
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,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Handle Upgrade to h2c (RFC 7540 Section 3.2)
|
||||||
|
if conn, err := h2cUpgrade(w, r); err == nil {
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
s.s.ServeConn(conn, &http2.ServeConnOpts{
|
||||||
|
Context: r.Context(),
|
||||||
|
Handler: s.Handler,
|
||||||
|
})
|
||||||
|
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 {
|
||||||
|
panic("Hijack not supported.")
|
||||||
|
}
|
||||||
|
conn, rw, err := hijacker.Hijack()
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("Hijack failed: %v", 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("could not read from the buffer: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if string(buf[:n]) == expectedBody {
|
||||||
|
c := &rwConn{
|
||||||
|
Conn: conn,
|
||||||
|
Reader: io.MultiReader(strings.NewReader(http2.ClientPreface), rw),
|
||||||
|
BufWriter: rw.Writer,
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
conn.Close()
|
||||||
|
if http2VerboseLogs {
|
||||||
|
log.Printf(
|
||||||
|
"h2c: missing the request body portion of the client preface. Wanted: %v Got: %v",
|
||||||
|
[]byte(expectedBody),
|
||||||
|
buf[0:n],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return nil, errors.New("invalid client preface")
|
||||||
|
}
|
||||||
|
|
||||||
|
// drainClientPreface reads a single instance of the HTTP/2 client preface from
|
||||||
|
// the supplied reader.
|
||||||
|
func drainClientPreface(r io.Reader) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
prefaceLen := int64(len(http2.ClientPreface))
|
||||||
|
n, err := io.CopyN(&buf, r, prefaceLen)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n != prefaceLen || buf.String() != http2.ClientPreface {
|
||||||
|
return fmt.Errorf("Client never sent: %s", http2.ClientPreface)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// h2cUpgrade establishes a h2c connection using the HTTP/1 upgrade (Section 3.2).
|
||||||
|
func h2cUpgrade(w http.ResponseWriter, r *http.Request) (net.Conn, error) {
|
||||||
|
if !isH2CUpgrade(r.Header) {
|
||||||
|
return nil, errors.New("non-conforming h2c headers")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial bytes we put into conn to fool http2 server
|
||||||
|
initBytes, _, err := convertH1ReqToH2(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hijacker, ok := w.(http.Hijacker)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("hijack not supported.")
|
||||||
|
}
|
||||||
|
conn, rw, err := hijacker.Hijack()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("hijack failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
rw.Write([]byte("HTTP/1.1 101 Switching Protocols\r\n" +
|
||||||
|
"Connection: Upgrade\r\n" +
|
||||||
|
"Upgrade: h2c\r\n\r\n"))
|
||||||
|
rw.Flush()
|
||||||
|
|
||||||
|
// A conforming client will now send an H2 client preface which need to drain
|
||||||
|
// since we already sent this.
|
||||||
|
if err := drainClientPreface(rw); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c := &rwConn{
|
||||||
|
Conn: conn,
|
||||||
|
Reader: io.MultiReader(initBytes, rw),
|
||||||
|
BufWriter: newSettingsAckSwallowWriter(rw.Writer),
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert the data contained in the HTTP/1 upgrade request into the HTTP/2
|
||||||
|
// version in byte form.
|
||||||
|
func convertH1ReqToH2(r *http.Request) (*bytes.Buffer, []http2.Setting, error) {
|
||||||
|
h2Bytes := bytes.NewBuffer([]byte((http2.ClientPreface)))
|
||||||
|
framer := http2.NewFramer(h2Bytes, nil)
|
||||||
|
settings, err := getH2Settings(r.Header)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := framer.WriteSettings(settings...); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
headerBytes, err := getH2HeaderBytes(r, getMaxHeaderTableSize(settings))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
maxFrameSize := int(getMaxFrameSize(settings))
|
||||||
|
needOneHeader := len(headerBytes) < maxFrameSize
|
||||||
|
err = framer.WriteHeaders(http2.HeadersFrameParam{
|
||||||
|
StreamID: 1,
|
||||||
|
BlockFragment: headerBytes,
|
||||||
|
EndHeaders: needOneHeader,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := maxFrameSize; i < len(headerBytes); i += maxFrameSize {
|
||||||
|
if len(headerBytes)-i > maxFrameSize {
|
||||||
|
if err := framer.WriteContinuation(1,
|
||||||
|
false, // endHeaders
|
||||||
|
headerBytes[i:maxFrameSize]); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := framer.WriteContinuation(1,
|
||||||
|
true, // endHeaders
|
||||||
|
headerBytes[i:]); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return h2Bytes, settings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMaxFrameSize returns the SETTINGS_MAX_FRAME_SIZE. If not present default
|
||||||
|
// value is 16384 as specified by RFC 7540 Section 6.5.2.
|
||||||
|
func getMaxFrameSize(settings []http2.Setting) uint32 {
|
||||||
|
for _, setting := range settings {
|
||||||
|
if setting.ID == http2.SettingMaxFrameSize {
|
||||||
|
return setting.Val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 16384
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMaxHeaderTableSize returns the SETTINGS_HEADER_TABLE_SIZE. If not present
|
||||||
|
// default value is 4096 as specified by RFC 7540 Section 6.5.2.
|
||||||
|
func getMaxHeaderTableSize(settings []http2.Setting) uint32 {
|
||||||
|
for _, setting := range settings {
|
||||||
|
if setting.ID == http2.SettingHeaderTableSize {
|
||||||
|
return setting.Val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 4096
|
||||||
|
}
|
||||||
|
|
||||||
|
// bufWriter is a Writer interface that also has a Flush method.
|
||||||
|
type bufWriter interface {
|
||||||
|
io.Writer
|
||||||
|
Flush() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// rwConn implements net.Conn but overrides Read and Write so that reads and
|
||||||
|
// writes are forwarded to the provided io.Reader and bufWriter.
|
||||||
|
type rwConn struct {
|
||||||
|
net.Conn
|
||||||
|
io.Reader
|
||||||
|
BufWriter bufWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read forwards reads to the underlying Reader.
|
||||||
|
func (c *rwConn) Read(p []byte) (int, error) {
|
||||||
|
return c.Reader.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write forwards writes to the underlying bufWriter and immediately flushes.
|
||||||
|
func (c *rwConn) Write(p []byte) (int, error) {
|
||||||
|
n, err := c.BufWriter.Write(p)
|
||||||
|
if err := c.BufWriter.Flush(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// settingsAckSwallowWriter is a writer that normally forwards bytes to its
|
||||||
|
// underlying Writer, but swallows the first SettingsAck frame that it sees.
|
||||||
|
type settingsAckSwallowWriter struct {
|
||||||
|
Writer *bufio.Writer
|
||||||
|
buf []byte
|
||||||
|
didSwallow bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// newSettingsAckSwallowWriter returns a new settingsAckSwallowWriter.
|
||||||
|
func newSettingsAckSwallowWriter(w *bufio.Writer) *settingsAckSwallowWriter {
|
||||||
|
return &settingsAckSwallowWriter{
|
||||||
|
Writer: w,
|
||||||
|
buf: make([]byte, 0),
|
||||||
|
didSwallow: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write implements io.Writer interface. Normally forwards bytes to w.Writer,
|
||||||
|
// except for the first Settings ACK frame that it sees.
|
||||||
|
func (w *settingsAckSwallowWriter) Write(p []byte) (int, error) {
|
||||||
|
if !w.didSwallow {
|
||||||
|
w.buf = append(w.buf, p...)
|
||||||
|
// Process all the frames we have collected into w.buf
|
||||||
|
for {
|
||||||
|
// Append until we get full frame header which is 9 bytes
|
||||||
|
if len(w.buf) < 9 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Check if we have collected a whole frame.
|
||||||
|
fh, err := http2.ReadFrameHeader(bytes.NewBuffer(w.buf))
|
||||||
|
if err != nil {
|
||||||
|
// Corrupted frame, fail current Write
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
fSize := fh.Length + 9
|
||||||
|
if uint32(len(w.buf)) < fSize {
|
||||||
|
// Have not collected whole frame. Stop processing buf, and withold on
|
||||||
|
// forward bytes to w.Writer until we get the full frame.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have now collected a whole frame.
|
||||||
|
if fh.Type == http2.FrameSettings && fh.Flags.Has(http2.FlagSettingsAck) {
|
||||||
|
// If Settings ACK frame, do not forward to underlying writer, remove
|
||||||
|
// bytes from w.buf, and record that we have swallowed Settings Ack
|
||||||
|
// frame.
|
||||||
|
w.didSwallow = true
|
||||||
|
w.buf = w.buf[fSize:]
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not settings ack frame. Forward bytes to w.Writer.
|
||||||
|
if _, err := w.Writer.Write(w.buf[:fSize]); err != nil {
|
||||||
|
// Couldn't forward bytes. Fail current Write.
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
w.buf = w.buf[fSize:]
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
return w.Writer.Write(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush calls w.Writer.Flush.
|
||||||
|
func (w *settingsAckSwallowWriter) Flush() error {
|
||||||
|
return w.Writer.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 []http2.Setting that are encoded in the
|
||||||
|
// HTTP2-Settings header.
|
||||||
|
func getH2Settings(h http.Header) ([]http2.Setting, 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 := decodeSettings(vals[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Invalid HTTP2-Settings: %q", vals[0])
|
||||||
|
}
|
||||||
|
return settings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeSettings decodes the base64url header value of the HTTP2-Settings
|
||||||
|
// header. RFC 7540 Section 3.2.1.
|
||||||
|
func decodeSettings(headerVal string) ([]http2.Setting, error) {
|
||||||
|
b, err := base64.RawURLEncoding.DecodeString(headerVal)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(b)%6 != 0 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
settings := make([]http2.Setting, 0)
|
||||||
|
for i := 0; i < len(b)/6; i++ {
|
||||||
|
settings = append(settings, http2.Setting{
|
||||||
|
ID: http2.SettingID(binary.BigEndian.Uint16(b[i*6 : i*6+2])),
|
||||||
|
Val: binary.BigEndian.Uint32(b[i*6+2 : i*6+6]),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return settings, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getH2HeaderBytes return the headers in r a []bytes encoded by HPACK.
|
||||||
|
func getH2HeaderBytes(r *http.Request, maxHeaderTableSize uint32) ([]byte, error) {
|
||||||
|
headerBytes := bytes.NewBuffer(nil)
|
||||||
|
hpackEnc := hpack.NewEncoder(headerBytes)
|
||||||
|
hpackEnc.SetMaxDynamicTableSize(maxHeaderTableSize)
|
||||||
|
|
||||||
|
// Section 8.1.2.3
|
||||||
|
err := hpackEnc.WriteField(hpack.HeaderField{
|
||||||
|
Name: ":method",
|
||||||
|
Value: r.Method,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = hpackEnc.WriteField(hpack.HeaderField{
|
||||||
|
Name: ":scheme",
|
||||||
|
Value: "http",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = hpackEnc.WriteField(hpack.HeaderField{
|
||||||
|
Name: ":authority",
|
||||||
|
Value: r.Host,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
path := r.URL.Path
|
||||||
|
if r.URL.RawQuery != "" {
|
||||||
|
path = strings.Join([]string{path, r.URL.RawQuery}, "?")
|
||||||
|
}
|
||||||
|
err = hpackEnc.WriteField(hpack.HeaderField{
|
||||||
|
Name: ":path",
|
||||||
|
Value: path,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Implement Section 8.3
|
||||||
|
|
||||||
|
for header, values := range r.Header {
|
||||||
|
// Skip non h2 headers
|
||||||
|
if isNonH2Header(header) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, v := range values {
|
||||||
|
err := hpackEnc.WriteField(hpack.HeaderField{
|
||||||
|
Name: strings.ToLower(header),
|
||||||
|
Value: v,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return headerBytes.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connection specific headers listed in RFC 7540 Section 8.1.2.2 that are not
|
||||||
|
// suppose to be transferred to HTTP/2. The Http2-Settings header is skipped
|
||||||
|
// since already use to create the HTTP/2 SETTINGS frame.
|
||||||
|
var nonH2Headers = []string{
|
||||||
|
"Connection",
|
||||||
|
"Keep-Alive",
|
||||||
|
"Proxy-Connection",
|
||||||
|
"Transfer-Encoding",
|
||||||
|
"Upgrade",
|
||||||
|
"Http2-Settings",
|
||||||
|
}
|
||||||
|
|
||||||
|
// isNonH2Header returns true if header should not be transferred to HTTP/2.
|
||||||
|
func isNonH2Header(header string) bool {
|
||||||
|
for _, nonH2h := range nonH2Headers {
|
||||||
|
if header == nonH2h {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
@ -363,9 +363,11 @@ golang.org/x/crypto/poly1305
|
|||||||
golang.org/x/crypto/ssh
|
golang.org/x/crypto/ssh
|
||||||
golang.org/x/crypto/ssh/internal/bcrypt_pbkdf
|
golang.org/x/crypto/ssh/internal/bcrypt_pbkdf
|
||||||
# golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4
|
# golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4
|
||||||
|
## explicit
|
||||||
golang.org/x/net/context
|
golang.org/x/net/context
|
||||||
golang.org/x/net/http/httpguts
|
golang.org/x/net/http/httpguts
|
||||||
golang.org/x/net/http2
|
golang.org/x/net/http2
|
||||||
|
golang.org/x/net/http2/h2c
|
||||||
golang.org/x/net/http2/hpack
|
golang.org/x/net/http2/hpack
|
||||||
golang.org/x/net/idna
|
golang.org/x/net/idna
|
||||||
golang.org/x/net/internal/timeseries
|
golang.org/x/net/internal/timeseries
|
||||||
@ -381,6 +383,7 @@ golang.org/x/text/transform
|
|||||||
golang.org/x/text/unicode/bidi
|
golang.org/x/text/unicode/bidi
|
||||||
golang.org/x/text/unicode/norm
|
golang.org/x/text/unicode/norm
|
||||||
# google.golang.org/genproto v0.0.0-20210426193834-eac7f76ac494
|
# google.golang.org/genproto v0.0.0-20210426193834-eac7f76ac494
|
||||||
|
## explicit
|
||||||
google.golang.org/genproto/googleapis/api/annotations
|
google.golang.org/genproto/googleapis/api/annotations
|
||||||
google.golang.org/genproto/googleapis/api/httpbody
|
google.golang.org/genproto/googleapis/api/httpbody
|
||||||
google.golang.org/genproto/googleapis/rpc/status
|
google.golang.org/genproto/googleapis/rpc/status
|
||||||
|
Loading…
Reference in New Issue
Block a user