Added websocket hub for handling websocket clients, with a wsserve command
which runs a plain TCP server for receiving websocket hub clients, with server config, CSRF and rate limit middleware. - internal/websocket: Hub pushing messages to WebSocket clients (ServeHTTP, example.com/ws upgrade with optional CSRF check) and plain TCP clients (ServeTCP, newline-delimited JSON envelopes); ping/pong keepalive, per-client message rate limit, slow consumer handling, graceful Shutdown, debug logging of messages sent and received - internal/websocket/tcpserver: standalone TCP server for a hub, with accept back-off, a per-IP connection limit (server.websocket.connection_limit) and graceful shutdown - internal/cli/wsserve: `wsserve` command running the TCP server, with --listen/--host/--port overrides - internal/middleware: keyed token bucket rate limiter, real client IP from trusted proxies - internal/httpx: RFC 9457 problem responses, request IDs in context - internal/config: server section (host, port, shutdown timeout, rate limiter idle TTL, websocket); util.ByteSize settings (e.g. "32KiB"); lists of sections as single keys; empty lists stored as nil; isExplicitlySet for printLoadedConfig; JobSearchConfigSource renamed to JSCSourcesSource; TestSections checks every section's methods - internal/util: ByteSize, parsed and formatted with go-humanize
This commit is contained in:
1 parent
803a8929e6
commit
c9f42b62ad
42 files changed
+3619
-57
No files matched your search
@@ -0,0 +1,56 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/netip"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// serveRealIP runs a request from peer with the given headers through RealIP
|
||||
// and returns the ClientInfo the next handler sees.
|
||||
func serveRealIP(t *testing.T, opts RealIPOptions, peer string, h map[string]string) ClientInfo {
|
||||
t.Helper()
|
||||
var got ClientInfo
|
||||
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { got = Client(r) })
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
req.RemoteAddr = peer
|
||||
for k, v := range h {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
RealIP(opts)(next).ServeHTTP(httptest.NewRecorder(), req)
|
||||
return got
|
||||
}
|
||||
|
||||
var defaultHeaders = []string{"X-Forwarded-For", "Forwarded", "X-Real-IP"}
|
||||
|
||||
func TestRealIP(t *testing.T) {
|
||||
opts := RealIPOptions{
|
||||
Trusted: []netip.Prefix{netip.MustParsePrefix("127.0.0.0/8")},
|
||||
Headers: defaultHeaders,
|
||||
}
|
||||
// Spoofed left-most entry is ignored; right-most untrusted wins.
|
||||
c := serveRealIP(t, opts, "127.0.0.1:1234", map[string]string{
|
||||
"X-Forwarded-For": "6.6.6.6, 203.0.113.9, 127.0.0.1", "X-Forwarded-Proto": "https",
|
||||
})
|
||||
if c.IP.String() != "203.0.113.9" || c.Scheme != "https" || !c.ViaProxy {
|
||||
t.Fatalf("xff: %+v", c)
|
||||
}
|
||||
c = serveRealIP(t, opts, "127.0.0.1:1234", map[string]string{
|
||||
"Forwarded": `for="[2001:db8::7]:4711";proto=https`,
|
||||
})
|
||||
if c.IP.String() != "2001:db8::7" {
|
||||
t.Fatalf("forwarded: %+v", c)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUntrustedPeerHeadersIgnored(t *testing.T) {
|
||||
opts := RealIPOptions{
|
||||
Trusted: []netip.Prefix{netip.MustParsePrefix("10.0.0.0/8")},
|
||||
Headers: defaultHeaders,
|
||||
}
|
||||
c := serveRealIP(t, opts, "127.0.0.1:1234", map[string]string{"X-Forwarded-For": "203.0.113.9"})
|
||||
if c.IP.String() != "127.0.0.1" || c.ViaProxy {
|
||||
t.Fatalf("untrusted XFF honoured: %+v", c)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user