package http import ( "net/http" "slices" ) type Observer struct { Response func(hostClass, codeClass string) Limited func(class string) Waited func(gate string) Refused func(gate string) UnknownClient func() } var hostClassNames = [...]string{classUnknown: "unknown", classIDP: "idp", classAccount: "account", classAdmin: "admin", classApp: "app"} var rateClassNames = [...]string{rateNone: "", rateAuthorize: "authorize", rateLoginForm: "login_form", ratePassword: "password", rateInvite: "invite", rateCallback: "callback", rateSSOLogin: "sso_login", rateLoginClaim: "login_claim"} const ( gateToken = "token" gateSignin = "signin" ) var ( HostClasses = slices.Clone(hostClassNames[:]) StatusClasses = []string{"1xx", "2xx", "3xx", "4xx", "5xx"} LimitClasses = slices.Clone(rateClassNames[rateAuthorize:]) Gates = []string{gateToken, gateSignin} ) func WithObserver(o Observer) RouterOption { return func(rt *router) { rt.observe = o.orDiscard() } } func (o Observer) orDiscard() Observer { if o.Response == nil { o.Response = func(string, string) {} } if o.Limited == nil { o.Limited = func(string) {} } if o.Waited == nil { o.Waited = func(string) {} } if o.Refused == nil { o.Refused = func(string) {} } if o.UnknownClient == nil { o.UnknownClient = func() {} } return o } func (c hostClass) name() string { if c < 0 || int(c) >= len(hostClassNames) { return "" } return hostClassNames[c] } func (c rateClass) name() string { if c < 0 || int(c) >= len(rateClassNames) { return "" } return rateClassNames[c] } func statusClass(code int) string { if code < 100 || code > 599 { return "" } return string(rune('0'+code/100)) + "xx" } func (l *Limits) gateName(g *gate) string { if g == l.token { return gateToken } return gateSignin } func (l *Limits) Len() int { return l.table.Len() } func (l *Limits) Evictions() uint64 { return l.table.Evictions() } func (rt *router) ServeHTTP(w http.ResponseWriter, r *http.Request) { class := rt.hosts[CanonicalHost(r.Host)] if !ClientOf(r.Context()).Addr.IsValid() { rt.observe.UnknownClient() } sw := &statusWriter{ResponseWriter: w, status: http.StatusInternalServerError} defer func() { rt.observe.Response(class.name(), statusClass(sw.status)) }() rt.route(sw, r, class) if !sw.wrote { sw.status = http.StatusOK } } type statusWriter struct { http.ResponseWriter status int wrote bool } func (s *statusWriter) record(code int) { if !s.wrote { s.status, s.wrote = code, true } } func (s *statusWriter) WriteHeader(code int) { s.record(code) s.ResponseWriter.WriteHeader(code) } func (s *statusWriter) Write(b []byte) (int, error) { s.record(http.StatusOK) return s.ResponseWriter.Write(b) } func (s *statusWriter) Flush() { s.record(http.StatusOK) if f, ok := s.ResponseWriter.(http.Flusher); ok { f.Flush() } } func (s *statusWriter) Unwrap() http.ResponseWriter { return s.ResponseWriter }