mirror of
https://github.com/caddyserver/caddy.git
synced 2026-06-28 04:41:41 +00:00
caddyhttp: add terminating WebTransport handler
Introduces http.handlers.webtransport, an EXPERIMENTAL handler that
terminates an incoming WebTransport session on top of Caddy's HTTP/3
server and echoes bytes on each bidirectional stream. Primary use case
is as a test upstream for the forthcoming WebTransport reverse-proxy
transport; it also serves as the minimal proof that the server-side
WebTransport wiring works end-to-end.
Plumbing changes:
* caddyhttp.Server gains a *webtransport.Server field alongside
h3server. It's built in buildWebTransportServer(), wrapping the
existing http3.Server. Exposed via WebTransportServer() any on the
Server, so the caddyhttp public API does not name the upstream
webtransport-go type (per AGENTS.md).
* serveHTTP3 now runs a custom accept loop (serveH3AcceptLoop) that
dispatches each accepted QUIC connection to
webtransport.Server.ServeQUICConn instead of
http3.Server.ServeListener. The WebTransport server transparently
forwards non-WT streams to the underlying http3 request handler
(cost: one varint peek per stream), so behavior for non-WT clients
is unchanged.
* ListenQUIC enables EnableDatagrams and
EnableStreamResetPartialDelivery on the QUIC listener config.
These are capability bits negotiated during the QUIC handshake and
are prerequisites for any WebTransport session; they do not force
usage so non-WT H3 traffic is unaffected.
* Stop path closes wtServer after h3server.Shutdown to clean up any
remaining WebTransport session state.
The handler uses caddyhttp.UnwrapResponseWriterAs to reach the naked
http3.Settingser/HTTPStreamer writer through Caddy's wrapping chain
before calling webtransport.Server.Upgrade.
Includes unit tests for request-shape detection plus an integration
test (caddytest/integration/webtransport_test.go) that spins up a
Caddy HTTP/3 server with the handler, dials it with a real
webtransport.Dialer, and asserts end-to-end bidirectional-stream
echo.
This commit is contained in:
parent
faaaa6f453
commit
dad86ff10b
8 changed files with 446 additions and 2 deletions
150
caddytest/integration/webtransport_test.go
Normal file
150
caddytest/integration/webtransport_test.go
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/quic-go/quic-go"
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
"github.com/quic-go/webtransport-go"
|
||||
|
||||
"github.com/caddyserver/caddy/v2/caddytest"
|
||||
)
|
||||
|
||||
// TestWebTransport_EchoHandlerBidi spins up Caddy with an HTTP/3 listener
|
||||
// that terminates a WebTransport session via the http.handlers.webtransport
|
||||
// echo handler, then dials it with a real webtransport.Dialer and asserts
|
||||
// an end-to-end bidirectional-stream round-trip. This exercises the
|
||||
// serveH3AcceptLoop path (webtransport.Server.ServeQUICConn instead of
|
||||
// http3.Server.ServeListener) and the UnwrapResponseWriterAs helper.
|
||||
func TestWebTransport_EchoHandlerBidi(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip()
|
||||
}
|
||||
tester := caddytest.NewTester(t)
|
||||
tester.InitServer(`{
|
||||
"admin": {
|
||||
"listen": "localhost:2999"
|
||||
},
|
||||
"apps": {
|
||||
"http": {
|
||||
"http_port": 9080,
|
||||
"https_port": 9443,
|
||||
"grace_period": 1,
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [":9443"],
|
||||
"protocols": ["h3"],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [{"handler": "webtransport"}]
|
||||
}
|
||||
],
|
||||
"tls_connection_policies": [
|
||||
{
|
||||
"certificate_selection": {"any_tag": ["cert0"]},
|
||||
"default_sni": "a.caddy.localhost"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"certificates": {
|
||||
"load_files": [
|
||||
{
|
||||
"certificate": "/a.caddy.localhost.crt",
|
||||
"key": "/a.caddy.localhost.key",
|
||||
"tags": ["cert0"]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"pki": {
|
||||
"certificate_authorities": {
|
||||
"local": {"install_trust": false}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`, "json")
|
||||
|
||||
dialer := &webtransport.Dialer{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true, //nolint:gosec // test uses a local CA
|
||||
ServerName: "a.caddy.localhost",
|
||||
NextProtos: []string{http3.NextProtoH3},
|
||||
},
|
||||
QUICConfig: &quic.Config{
|
||||
EnableDatagrams: true,
|
||||
EnableStreamResetPartialDelivery: true,
|
||||
},
|
||||
}
|
||||
|
||||
// Connect. Give the freshly-reconfigured server a brief window to be
|
||||
// ready on the UDP port; retry a handful of times instead of racing.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var (
|
||||
rsp *http.Response
|
||||
sess *webtransport.Session
|
||||
err error
|
||||
)
|
||||
deadline := time.Now().Add(3 * time.Second)
|
||||
for {
|
||||
rsp, sess, err = dialer.Dial(ctx, "https://127.0.0.1:9443/", nil)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatalf("webtransport dial failed after retries: %v", err)
|
||||
}
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
defer sess.CloseWithError(0, "")
|
||||
|
||||
if rsp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("unexpected status: %d", rsp.StatusCode)
|
||||
}
|
||||
|
||||
// Open a bidirectional stream and send payload; expect it echoed back.
|
||||
str, err := sess.OpenStreamSync(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("open stream: %v", err)
|
||||
}
|
||||
|
||||
const payload = "hello webtransport"
|
||||
if _, err := io.WriteString(str, payload); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
if err := str.Close(); err != nil {
|
||||
t.Fatalf("close send: %v", err)
|
||||
}
|
||||
|
||||
got, err := io.ReadAll(str)
|
||||
if err != nil {
|
||||
t.Fatalf("read: %v", err)
|
||||
}
|
||||
if string(got) != payload {
|
||||
t.Fatalf("echo mismatch:\n got: %q\n want: %q", strings.TrimSpace(string(got)), payload)
|
||||
}
|
||||
}
|
||||
|
|
@ -482,6 +482,11 @@ func (na NetworkAddress) ListenQUIC(ctx context.Context, portOffset uint, config
|
|||
&quic.Config{
|
||||
Allow0RTT: allow0rtt,
|
||||
Tracer: h3qlog.DefaultConnectionTracer,
|
||||
// Required by WebTransport. Enabling unconditionally: these
|
||||
// are capability bits negotiated during the QUIC handshake
|
||||
// and do not force usage, so non-WT H3 traffic is unaffected.
|
||||
EnableDatagrams: true,
|
||||
EnableStreamResetPartialDelivery: true,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -768,6 +768,17 @@ func (app *App) Stop() error {
|
|||
zap.Strings("addresses", server.Listen))
|
||||
}
|
||||
|
||||
// WebTransport session state is managed separately from the
|
||||
// HTTP/3 server; Close after Shutdown to drop any remaining
|
||||
// sessions and terminate the per-connection accept goroutines.
|
||||
if server.wtServer != nil {
|
||||
if err := server.wtServer.Close(); err != nil {
|
||||
app.logger.Error("WebTransport server close",
|
||||
zap.Error(err),
|
||||
zap.Strings("addresses", server.Listen))
|
||||
}
|
||||
}
|
||||
|
||||
// close the underlying net.PacketConns now
|
||||
// see the comment for ListenQUIC
|
||||
for _, h3ln := range server.quicListeners {
|
||||
|
|
|
|||
|
|
@ -304,6 +304,7 @@ type Server struct {
|
|||
|
||||
server *http.Server
|
||||
h3server *http3.Server
|
||||
wtServer *webtransport.Server
|
||||
addresses []caddy.NetworkAddress
|
||||
|
||||
trustedProxies IPRangeSource
|
||||
|
|
@ -822,16 +823,36 @@ func (s *Server) serveHTTP3(addr caddy.NetworkAddress, tlsCfg *tls.Config) error
|
|||
// create HTTP/3 server if not done already
|
||||
if s.h3server == nil {
|
||||
s.h3server = s.buildHTTP3Server(tlsCfg)
|
||||
s.wtServer = s.buildWebTransportServer()
|
||||
}
|
||||
|
||||
s.quicListeners = append(s.quicListeners, h3ln)
|
||||
|
||||
//nolint:errcheck
|
||||
go s.h3server.ServeListener(h3ln)
|
||||
go s.serveH3AcceptLoop(h3ln)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// serveH3AcceptLoop accepts incoming QUIC connections from the HTTP/3
|
||||
// listener and dispatches each to the WebTransport-aware serve loop.
|
||||
// webtransport.Server.ServeQUICConn wraps http3.Server: non-WebTransport
|
||||
// streams are transparently forwarded to the normal HTTP/3 request path
|
||||
// (at the cost of one varint peek per stream), so behavior for non-WT
|
||||
// clients is unchanged. This replaces http3.Server.ServeListener's
|
||||
// accept loop so webtransport.Server.Upgrade has the per-connection
|
||||
// session manager state it requires.
|
||||
func (s *Server) serveH3AcceptLoop(h3ln http3.QUICListener) {
|
||||
for {
|
||||
conn, err := h3ln.Accept(s.ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
_ = s.wtServer.ServeQUICConn(conn)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// buildHTTP3Server constructs the http3.Server used by this server for HTTP/3.
|
||||
// WebTransport support is advertised in SETTINGS and the underlying *quic.Conn
|
||||
// is stashed in each request's context, which is a prerequisite for any
|
||||
|
|
@ -854,6 +875,14 @@ func (s *Server) buildHTTP3Server(tlsCfg *tls.Config) *http3.Server {
|
|||
return h3
|
||||
}
|
||||
|
||||
// buildWebTransportServer constructs the webtransport.Server that wraps
|
||||
// the http3.Server. It owns the per-connection session state needed by
|
||||
// webtransport.Server.Upgrade and demultiplexes WebTransport streams
|
||||
// from normal HTTP/3 streams on each accepted QUIC connection.
|
||||
func (s *Server) buildWebTransportServer() *webtransport.Server {
|
||||
return &webtransport.Server{H3: s.h3server}
|
||||
}
|
||||
|
||||
// configureServer applies/binds the registered callback functions to the server.
|
||||
func (s *Server) configureServer(server *http.Server) {
|
||||
for _, f := range s.connStateFuncs {
|
||||
|
|
@ -1130,6 +1159,22 @@ func (s *Server) Listeners() []net.Listener { return s.listeners }
|
|||
// Name returns the server's name.
|
||||
func (s *Server) Name() string { return s.name }
|
||||
|
||||
// WebTransportServer returns the server's underlying WebTransport
|
||||
// serving state as an opaque value. Modules that import
|
||||
// github.com/quic-go/webtransport-go may type-assert it to
|
||||
// *webtransport.Server. Returns nil if HTTP/3 is not in use.
|
||||
//
|
||||
// This is exposed as any so caddyhttp's public API does not leak the
|
||||
// upstream webtransport-go type to packages that don't use it.
|
||||
//
|
||||
// EXPERIMENTAL: Subject to change or removal.
|
||||
func (s *Server) WebTransportServer() any {
|
||||
if s.wtServer == nil {
|
||||
return nil
|
||||
}
|
||||
return s.wtServer
|
||||
}
|
||||
|
||||
// PrepareRequest fills the request r for use in a Caddy HTTP handler chain. w and s can
|
||||
// be nil, but the handlers will lose response placeholders and access to the server.
|
||||
func PrepareRequest(r *http.Request, repl *caddy.Replacer, w http.ResponseWriter, s *Server) *http.Request {
|
||||
|
|
|
|||
|
|
@ -1034,3 +1034,21 @@ func TestServer_BuildHTTP3ServerAppliesHandlerAndTLS(t *testing.T) {
|
|||
assert.Same(t, tlsCfg, h3.TLSConfig, "http3.Server.TLSConfig should be the config passed in")
|
||||
assert.Equal(t, 4096, h3.MaxHeaderBytes)
|
||||
}
|
||||
|
||||
// TestServer_BuildWebTransportServerWrapsHTTP3Server asserts that the
|
||||
// webtransport.Server wraps the correct http3.Server.
|
||||
func TestServer_BuildWebTransportServerWrapsHTTP3Server(t *testing.T) {
|
||||
s := &Server{}
|
||||
s.h3server = s.buildHTTP3Server(&tls.Config{})
|
||||
wt := s.buildWebTransportServer()
|
||||
|
||||
assert.NotNil(t, wt, "expected non-nil webtransport.Server")
|
||||
assert.Same(t, s.h3server, wt.H3, "webtransport.Server should wrap this server's http3.Server")
|
||||
}
|
||||
|
||||
// TestServer_WebTransportServerNilUntilH3 asserts the accessor returns nil
|
||||
// when HTTP/3 has not been configured.
|
||||
func TestServer_WebTransportServerNilUntilH3(t *testing.T) {
|
||||
s := &Server{}
|
||||
assert.Nil(t, s.WebTransportServer(), "expected nil before HTTP/3 setup")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,4 +22,5 @@ import (
|
|||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/rewrite"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/templates"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/tracing"
|
||||
_ "github.com/caddyserver/caddy/v2/modules/caddyhttp/webtransport"
|
||||
)
|
||||
|
|
|
|||
145
modules/caddyhttp/webtransport/handler.go
Normal file
145
modules/caddyhttp/webtransport/handler.go
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package webtransport is an EXPERIMENTAL HTTP handler that terminates a
|
||||
// WebTransport session (draft-ietf-webtrans-http3) on top of Caddy's HTTP/3
|
||||
// server and echoes bytes on each bidirectional stream. It exists mainly as
|
||||
// a test upstream for the WebTransport reverse-proxy transport. Behavior
|
||||
// and configuration are subject to change without notice.
|
||||
package webtransport
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/quic-go/quic-go/http3"
|
||||
"github.com/quic-go/webtransport-go"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
)
|
||||
|
||||
func init() {
|
||||
caddy.RegisterModule(Handler{})
|
||||
}
|
||||
|
||||
// Protocol is the :protocol pseudo-header value sent by a client that wants
|
||||
// to establish a WebTransport session over HTTP/3 Extended CONNECT.
|
||||
const Protocol = "webtransport"
|
||||
|
||||
// Writer is the interface satisfied by the naked HTTP/3 response writer.
|
||||
// webtransport.Server.Upgrade performs these assertions itself; callers
|
||||
// can use caddyhttp.UnwrapResponseWriterAs[Writer] to reach it past
|
||||
// Caddy's ResponseWriter wrapping chain before calling Upgrade.
|
||||
type Writer interface {
|
||||
http.ResponseWriter
|
||||
http3.Settingser
|
||||
http3.HTTPStreamer
|
||||
}
|
||||
|
||||
// Handler terminates an incoming WebTransport session and echoes bytes on
|
||||
// each bidirectional stream. EXPERIMENTAL: intended primarily as a test
|
||||
// upstream for the WebTransport reverse-proxy transport.
|
||||
type Handler struct {
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
func (Handler) CaddyModule() caddy.ModuleInfo {
|
||||
return caddy.ModuleInfo{
|
||||
ID: "http.handlers.webtransport",
|
||||
New: func() caddy.Module { return new(Handler) },
|
||||
}
|
||||
}
|
||||
|
||||
// Provision sets up the handler.
|
||||
func (h *Handler) Provision(ctx caddy.Context) error {
|
||||
h.logger = ctx.Logger()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServeHTTP upgrades the request to a WebTransport session and echoes bytes
|
||||
// on each accepted bidirectional stream. Non-WebTransport requests are
|
||||
// passed through to the next handler.
|
||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
||||
if !isWebTransportUpgrade(r) {
|
||||
return next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
srv, ok := r.Context().Value(caddyhttp.ServerCtxKey).(*caddyhttp.Server)
|
||||
if !ok || srv == nil {
|
||||
return caddyhttp.Error(http.StatusInternalServerError,
|
||||
errors.New("webtransport: caddyhttp.Server not in request context"))
|
||||
}
|
||||
wtServer, ok := srv.WebTransportServer().(*webtransport.Server)
|
||||
if !ok || wtServer == nil {
|
||||
return caddyhttp.Error(http.StatusInternalServerError,
|
||||
errors.New("webtransport: HTTP/3 is not enabled on this server"))
|
||||
}
|
||||
|
||||
naked, ok := caddyhttp.UnwrapResponseWriterAs[Writer](w)
|
||||
if !ok {
|
||||
return caddyhttp.Error(http.StatusInternalServerError,
|
||||
errors.New("webtransport: underlying writer does not support WebTransport upgrade"))
|
||||
}
|
||||
|
||||
session, err := wtServer.Upgrade(naked, r)
|
||||
if err != nil {
|
||||
h.logger.Debug("webtransport upgrade failed", zap.Error(err))
|
||||
return caddyhttp.Error(http.StatusBadRequest,
|
||||
fmt.Errorf("webtransport upgrade: %w", err))
|
||||
}
|
||||
|
||||
h.echoStreams(session)
|
||||
return nil
|
||||
}
|
||||
|
||||
// echoStreams accepts bidirectional streams on session until the session
|
||||
// ends, and echoes bytes on each one.
|
||||
func (h *Handler) echoStreams(session *webtransport.Session) {
|
||||
ctx := session.Context()
|
||||
for {
|
||||
str, err := session.AcceptStream(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
go func(s *webtransport.Stream) {
|
||||
// io.Copy from the stream back to itself echoes everything
|
||||
// received on this bidirectional stream. When the peer closes
|
||||
// its send side we observe EOF and close our send side too.
|
||||
if _, err := io.Copy(s, s); err != nil && h.logger != nil {
|
||||
h.logger.Debug("webtransport echo stream error", zap.Error(err))
|
||||
}
|
||||
_ = s.Close()
|
||||
}(str)
|
||||
}
|
||||
}
|
||||
|
||||
// isWebTransportUpgrade reports whether r is an HTTP/3 Extended CONNECT that
|
||||
// requests a WebTransport session. The quic-go/http3 server places the
|
||||
// :protocol pseudo-header value in r.Proto for CONNECT requests.
|
||||
func isWebTransportUpgrade(r *http.Request) bool {
|
||||
return r.ProtoMajor == 3 &&
|
||||
r.Method == http.MethodConnect &&
|
||||
r.Proto == Protocol
|
||||
}
|
||||
|
||||
// Interface guards.
|
||||
var (
|
||||
_ caddy.Provisioner = (*Handler)(nil)
|
||||
_ caddyhttp.MiddlewareHandler = (*Handler)(nil)
|
||||
)
|
||||
69
modules/caddyhttp/webtransport/handler_test.go
Normal file
69
modules/caddyhttp/webtransport/handler_test.go
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package webtransport
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIsWebTransportUpgrade(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
proto string
|
||||
major int
|
||||
meth string
|
||||
want bool
|
||||
}{
|
||||
{"h3 connect webtransport", "webtransport", 3, http.MethodConnect, true},
|
||||
{"h3 connect websocket", "websocket", 3, http.MethodConnect, false},
|
||||
{"h2 connect webtransport", "webtransport", 2, http.MethodConnect, false},
|
||||
{"h3 GET", "HTTP/3.0", 3, http.MethodGet, false},
|
||||
{"h3 connect missing :protocol", "HTTP/3.0", 3, http.MethodConnect, false},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r := httptest.NewRequest(tc.meth, "/", nil)
|
||||
r.ProtoMajor = tc.major
|
||||
r.Proto = tc.proto
|
||||
if got := isWebTransportUpgrade(r); got != tc.want {
|
||||
t.Errorf("isWebTransportUpgrade = %v, want %v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// nextNoop is a stand-in for the next handler. It records whether it was
|
||||
// invoked, used to assert that non-WebTransport requests pass through.
|
||||
type nextNoop struct{ called bool }
|
||||
|
||||
func (n *nextNoop) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
|
||||
n.called = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestHandler_PassesThroughNonWebTransportRequests(t *testing.T) {
|
||||
h := &Handler{}
|
||||
r := httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
nx := &nextNoop{}
|
||||
if err := h.ServeHTTP(w, r, nx); err != nil {
|
||||
t.Fatalf("ServeHTTP returned error: %v", err)
|
||||
}
|
||||
if !nx.called {
|
||||
t.Error("expected next handler to be invoked for non-WebTransport request")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue