mirror of
https://github.com/dalbodeule/hop-gate.git
synced 2026-09-21 08:11:06 +09:00
- document HTTP/1.1, HTTP/2, and HTTP/3 ingress - describe SSE streaming and timeout behavior - document HTTP/2 and HTTP/3 Extended CONNECT - update yamux stream and deployment architecture - document Go 1.27 and UDP HTTP/3 requirements
131 lines
4.0 KiB
Go
131 lines
4.0 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/dalbodeule/hop-gate/internal/logging"
|
|
)
|
|
|
|
type noopLogger struct{}
|
|
|
|
func (noopLogger) Debug(string, logging.Fields) {}
|
|
func (noopLogger) Info(string, logging.Fields) {}
|
|
func (noopLogger) Warn(string, logging.Fields) {}
|
|
func (noopLogger) Error(string, logging.Fields) {}
|
|
func (l noopLogger) With(logging.Fields) logging.Logger { return l }
|
|
|
|
type streamingTestTunnel struct {
|
|
forwardHTTPCalled bool
|
|
extendedConnectCalled bool
|
|
deadlineSeen bool
|
|
}
|
|
|
|
func (t *streamingTestTunnel) ForwardHTTP(ctx context.Context, _ logging.Logger, _ *http.Request, _ string, w http.ResponseWriter) error {
|
|
t.forwardHTTPCalled = true
|
|
_, t.deadlineSeen = ctx.Deadline()
|
|
w.Header().Set("Content-Type", "text/event-stream")
|
|
w.WriteHeader(http.StatusOK)
|
|
_, err := w.Write([]byte("data: ready\n\n"))
|
|
return err
|
|
}
|
|
|
|
func (t *streamingTestTunnel) ForwardExtendedConnect(_ context.Context, _ logging.Logger, _ *http.Request, _ string, w http.ResponseWriter) error {
|
|
t.extendedConnectCalled = true
|
|
w.WriteHeader(http.StatusOK)
|
|
return nil
|
|
}
|
|
|
|
func TestSSERequestStreamsWithoutProxyTimeout(t *testing.T) {
|
|
tunnel := &streamingTestTunnel{}
|
|
logger := noopLogger{}
|
|
domain := "sse-test.example"
|
|
registerTunnelForDomain(domain, tunnel, logger)
|
|
defer unregisterTunnelForDomain(domain, tunnel, logger)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "http://"+domain+"/events", nil)
|
|
req.Host = domain
|
|
req.Header.Set("Accept", "text/event-stream")
|
|
recorder := httptest.NewRecorder()
|
|
|
|
newHTTPHandler(logger, time.Nanosecond).ServeHTTP(recorder, req)
|
|
|
|
if !tunnel.forwardHTTPCalled {
|
|
t.Fatal("expected SSE request to use HTTP forwarder")
|
|
}
|
|
if tunnel.deadlineSeen {
|
|
t.Fatal("expected SSE request to avoid the normal proxy timeout")
|
|
}
|
|
if got := recorder.Header().Get("Content-Type"); got != "text/event-stream" {
|
|
t.Fatalf("Content-Type = %q, want text/event-stream", got)
|
|
}
|
|
if got := recorder.Body.String(); got != "data: ready\n\n" {
|
|
t.Fatalf("body = %q, want SSE event", got)
|
|
}
|
|
}
|
|
|
|
func TestHTTP2ExtendedConnectUsesDedicatedForwarder(t *testing.T) {
|
|
tunnel := &streamingTestTunnel{}
|
|
logger := noopLogger{}
|
|
domain := "h2-connect-test.example"
|
|
registerTunnelForDomain(domain, tunnel, logger)
|
|
defer unregisterTunnelForDomain(domain, tunnel, logger)
|
|
|
|
req := httptest.NewRequest(http.MethodConnect, "https://"+domain+"/socket", nil)
|
|
req.Host = domain
|
|
req.ProtoMajor = 2
|
|
req.ProtoMinor = 0
|
|
req.Proto = "websocket"
|
|
recorder := httptest.NewRecorder()
|
|
|
|
newHTTPHandler(logger, time.Second).ServeHTTP(recorder, req)
|
|
|
|
if !tunnel.extendedConnectCalled {
|
|
t.Fatal("expected HTTP/2 Extended CONNECT forwarder to be called")
|
|
}
|
|
if tunnel.forwardHTTPCalled {
|
|
t.Fatal("did not expect regular HTTP forwarder for Extended CONNECT")
|
|
}
|
|
if got := recorder.Code; got != http.StatusOK {
|
|
t.Fatalf("status = %d, want %d", got, http.StatusOK)
|
|
}
|
|
}
|
|
|
|
func TestHTTP3ExtendedConnectDetection(t *testing.T) {
|
|
req := httptest.NewRequest(http.MethodConnect, "https://h3.example/socket", nil)
|
|
req.ProtoMajor = 3
|
|
req.ProtoMinor = 0
|
|
req.Proto = "websocket"
|
|
|
|
if !isExtendedConnectWebSocketRequest(req) {
|
|
t.Fatal("expected HTTP/3 Extended CONNECT WebSocket request to be detected")
|
|
}
|
|
}
|
|
|
|
func TestHTTP3SSERequestUsesStreamingPolicy(t *testing.T) {
|
|
tunnel := &streamingTestTunnel{}
|
|
logger := noopLogger{}
|
|
domain := "h3-sse-test.example"
|
|
registerTunnelForDomain(domain, tunnel, logger)
|
|
defer unregisterTunnelForDomain(domain, tunnel, logger)
|
|
|
|
req := httptest.NewRequest(http.MethodGet, "https://"+domain+"/events", nil)
|
|
req.Host = domain
|
|
req.ProtoMajor = 3
|
|
req.ProtoMinor = 0
|
|
req.Header.Set("Accept", "text/event-stream")
|
|
recorder := httptest.NewRecorder()
|
|
|
|
newHTTPHandler(logger, time.Nanosecond).ServeHTTP(recorder, req)
|
|
|
|
if tunnel.deadlineSeen {
|
|
t.Fatal("expected HTTP/3 SSE request to avoid the normal proxy timeout")
|
|
}
|
|
if got := recorder.Body.String(); got != "data: ready\n\n" {
|
|
t.Fatalf("body = %q, want SSE event", got)
|
|
}
|
|
}
|