add realip, requestlog, recoverer middlewares
Initial implementation of lightmux-contrib, a sibling module to lightmux that hosts opinionated middlewares with one sub-package per middleware: - realip: resolves the originating client IP from CF-Connecting-IP, True-Client-IP, X-Real-IP, or X-Forwarded-For. Optional peer-CIDR allowlist via netip.Prefix. - requestlog: emits a structured http.request record (method, path, status, duration, client) per request via splinter. - recoverer: catches panics, wraps with errx under op "recoverer", logs with stack, and writes a 500 response. Each package exposes a single New(...) constructor returning func(http.Handler) http.Handler. The contrib module intentionally does not import lightmux — middlewares interoperate via the standard stdlib middleware shape. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9dc0fc5d26
commit
b26ef7439e
10 changed files with 624 additions and 0 deletions
92
requestlog/requestlog_test.go
Normal file
92
requestlog/requestlog_test.go
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
package requestlog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.juancwu.dev/juancwu/splinter"
|
||||
)
|
||||
|
||||
func captureSplinter(t *testing.T) *bytes.Buffer {
|
||||
t.Helper()
|
||||
var buf bytes.Buffer
|
||||
logger := splinter.New(splinter.WithStream(splinter.NewConsoleStream(
|
||||
splinter.ConsoleJSON,
|
||||
splinter.LevelDebug,
|
||||
splinter.ConsoleWriter(&buf),
|
||||
)))
|
||||
prev := splinter.SetDefault(logger)
|
||||
t.Cleanup(func() { splinter.SetDefault(prev) })
|
||||
return &buf
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
buf := captureSplinter(t)
|
||||
|
||||
h := New(nil)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusTeapot)
|
||||
}))
|
||||
|
||||
rr := httptest.NewRecorder()
|
||||
h.ServeHTTP(rr, httptest.NewRequest(http.MethodGet, "/foo", nil))
|
||||
|
||||
if rr.Code != http.StatusTeapot {
|
||||
t.Errorf("status code = %d, want 418", rr.Code)
|
||||
}
|
||||
out := buf.String()
|
||||
for _, want := range []string{`"method":"GET"`, `"path":"/foo"`, `"status":418`, `"client":"192.0.2.1:1234"`} {
|
||||
if !strings.Contains(out, want) {
|
||||
t.Errorf("log output missing %s\nfull output: %s", want, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewDefaultStatusOK(t *testing.T) {
|
||||
buf := captureSplinter(t)
|
||||
|
||||
h := New(nil)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("hi"))
|
||||
}))
|
||||
h.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", nil))
|
||||
|
||||
if !strings.Contains(buf.String(), `"status":200`) {
|
||||
t.Errorf("expected default status 200 in log, got %q", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewWithCustomLogger(t *testing.T) {
|
||||
defaultBuf := captureSplinter(t)
|
||||
|
||||
var customBuf bytes.Buffer
|
||||
custom := splinter.New(splinter.WithStream(splinter.NewConsoleStream(
|
||||
splinter.ConsoleJSON,
|
||||
splinter.LevelDebug,
|
||||
splinter.ConsoleWriter(&customBuf),
|
||||
)))
|
||||
|
||||
h := New(custom)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
}))
|
||||
h.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodPost, "/x", nil))
|
||||
|
||||
if !strings.Contains(customBuf.String(), `"path":"/x"`) {
|
||||
t.Errorf("custom logger did not receive record: %q", customBuf.String())
|
||||
}
|
||||
if defaultBuf.Len() != 0 {
|
||||
t.Errorf("default logger should not have been written to, got: %q", defaultBuf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewNilFallsBackToDefault(t *testing.T) {
|
||||
buf := captureSplinter(t)
|
||||
|
||||
h := New(nil)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
h.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/y", nil))
|
||||
|
||||
if !strings.Contains(buf.String(), `"path":"/y"`) {
|
||||
t.Errorf("nil logger should fall back to splinter.Default(): %q", buf.String())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue