feat: game lobby service
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
// Package httpcommon hosts cross-router HTTP middleware shared by the
|
||||
// Game Lobby Service public and internal listeners.
|
||||
package httpcommon
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base32"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"galaxy/lobby/internal/logging"
|
||||
)
|
||||
|
||||
// RequestIDHeader is the canonical HTTP header used to carry a
|
||||
// caller-supplied request id across service hops.
|
||||
const RequestIDHeader = "X-Request-Id"
|
||||
|
||||
// requestIDTokenBytes controls the entropy of generated request ids. Eight
|
||||
// bytes produce a 13-character base32 token, well above what is needed to
|
||||
// keep collisions vanishingly rare within any single service's logs.
|
||||
const requestIDTokenBytes = 8
|
||||
|
||||
// requestIDMaxLength caps the length of caller-supplied request ids so a
|
||||
// hostile or buggy upstream cannot blow up logs and trace attributes.
|
||||
const requestIDMaxLength = 128
|
||||
|
||||
// base32NoPadding mirrors the encoding used elsewhere in the lobby module
|
||||
// (see `internal/adapters/idgen`) so generated ids stay visually similar.
|
||||
var base32NoPadding = base32.StdEncoding.WithPadding(base32.NoPadding)
|
||||
|
||||
// RequestID is the HTTP middleware that materialises the per-request
|
||||
// `request_id` for downstream loggers. It reads the X-Request-Id header
|
||||
// (case-insensitively); when the header is absent, malformed, or longer
|
||||
// than requestIDMaxLength it generates a fresh token from crypto/rand.
|
||||
// The id is stored on the request context via logging.WithRequestID and
|
||||
// echoed back on the response header.
|
||||
func RequestID(next http.Handler) http.Handler {
|
||||
if next == nil {
|
||||
panic("httpcommon: nil next handler")
|
||||
}
|
||||
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
requestID := normalizeRequestID(request.Header.Get(RequestIDHeader))
|
||||
if requestID == "" {
|
||||
requestID = generateRequestID()
|
||||
}
|
||||
|
||||
writer.Header().Set(RequestIDHeader, requestID)
|
||||
|
||||
ctx := logging.WithRequestID(request.Context(), requestID)
|
||||
next.ServeHTTP(writer, request.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
// normalizeRequestID returns a trimmed copy of value when it satisfies the
|
||||
// per-request constraints, otherwise the empty string. The empty return
|
||||
// signals that the middleware must generate a fresh id.
|
||||
func normalizeRequestID(value string) string {
|
||||
trimmed := strings.TrimSpace(value)
|
||||
if trimmed == "" {
|
||||
return ""
|
||||
}
|
||||
if len(trimmed) > requestIDMaxLength {
|
||||
return ""
|
||||
}
|
||||
for _, r := range trimmed {
|
||||
if r < 0x20 || r == 0x7f {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return trimmed
|
||||
}
|
||||
|
||||
// generateRequestID returns a fresh opaque id derived from crypto/rand.
|
||||
// Errors from the random source are vanishingly unlikely; the helper
|
||||
// returns the literal "fallback" on the impossible path so the middleware
|
||||
// remains panic-free.
|
||||
func generateRequestID() string {
|
||||
buf := make([]byte, requestIDTokenBytes)
|
||||
if _, err := rand.Read(buf); err != nil {
|
||||
return "rid-fallback"
|
||||
}
|
||||
return "rid-" + strings.ToLower(base32NoPadding.EncodeToString(buf))
|
||||
}
|
||||
Reference in New Issue
Block a user