shotgun/serve/proxy.go

52 lines
1.4 KiB
Go

package serve
import (
"context"
"net/http"
"net/netip"
"strings"
"git.sunturtle.xyz/studio/shotgun/player"
)
// WithPlayerID is a middleware that adds a player ID to the request context
// based on the X-Forwarded-For header. If there is no such header, or the
// originator addr otherwise cannot be parsed from it, the request fails with
// a 500 error.
func WithPlayerID(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ff := r.Header.Get("X-Forwarded-For")
addr, err := originator(ff)
if err != nil {
http.Error(w, "missing or invalid X-Forwarded-For header; check server configuration", http.StatusInternalServerError)
return
}
id := player.ID(addr.As16())
ctx := ctxWith(r.Context(), id)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// Player returns the player ID set by WithPlayerID in the request context.
func PlayerID(ctx context.Context) player.ID {
return ctxValue[player.ID](ctx)
}
// originator parses the IP of the client that originated a request from the
// content of its X-Forwarded-For header.
func originator(ff string) (netip.Addr, error) {
ff, _, _ = strings.Cut(ff, ",")
return netip.ParseAddr(ff)
}
type ctxKey[T any] struct{}
func ctxValue[T any](ctx context.Context) T {
r, _ := ctx.Value(ctxKey[T]{}).(T)
return r
}
func ctxWith[T any](ctx context.Context, v T) context.Context {
return context.WithValue(ctx, ctxKey[T]{}, v)
}