Raw
  1package http
  2
  3import (
  4	"net/http"
  5	"slices"
  6)
  7
  8type Observer struct {
  9	Response      func(hostClass, codeClass string)
 10	Limited       func(class string)
 11	Waited        func(gate string)
 12	Refused       func(gate string)
 13	UnknownClient func()
 14}
 15
 16var hostClassNames = [...]string{classUnknown: "unknown", classIDP: "idp", classAccount: "account", classAdmin: "admin", classApp: "app"}
 17
 18var rateClassNames = [...]string{rateNone: "", rateAuthorize: "authorize", rateLoginForm: "login_form", ratePassword: "password", rateInvite: "invite", rateCallback: "callback", rateSSOLogin: "sso_login", rateLoginClaim: "login_claim"}
 19
 20const (
 21	gateToken  = "token"
 22	gateSignin = "signin"
 23)
 24
 25var (
 26	HostClasses   = slices.Clone(hostClassNames[:])
 27	StatusClasses = []string{"1xx", "2xx", "3xx", "4xx", "5xx"}
 28	LimitClasses  = slices.Clone(rateClassNames[rateAuthorize:])
 29	Gates         = []string{gateToken, gateSignin}
 30)
 31
 32func WithObserver(o Observer) RouterOption {
 33	return func(rt *router) { rt.observe = o.orDiscard() }
 34}
 35
 36func (o Observer) orDiscard() Observer {
 37	if o.Response == nil {
 38		o.Response = func(string, string) {}
 39	}
 40	if o.Limited == nil {
 41		o.Limited = func(string) {}
 42	}
 43	if o.Waited == nil {
 44		o.Waited = func(string) {}
 45	}
 46	if o.Refused == nil {
 47		o.Refused = func(string) {}
 48	}
 49	if o.UnknownClient == nil {
 50		o.UnknownClient = func() {}
 51	}
 52	return o
 53}
 54
 55func (c hostClass) name() string {
 56	if c < 0 || int(c) >= len(hostClassNames) {
 57		return ""
 58	}
 59	return hostClassNames[c]
 60}
 61
 62func (c rateClass) name() string {
 63	if c < 0 || int(c) >= len(rateClassNames) {
 64		return ""
 65	}
 66	return rateClassNames[c]
 67}
 68
 69func statusClass(code int) string {
 70	if code < 100 || code > 599 {
 71		return ""
 72	}
 73	return string(rune('0'+code/100)) + "xx"
 74}
 75
 76func (l *Limits) gateName(g *gate) string {
 77	if g == l.token {
 78		return gateToken
 79	}
 80	return gateSignin
 81}
 82
 83func (l *Limits) Len() int { return l.table.Len() }
 84
 85func (l *Limits) Evictions() uint64 { return l.table.Evictions() }
 86
 87func (rt *router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 88	class := rt.hosts[CanonicalHost(r.Host)]
 89	if !ClientOf(r.Context()).Addr.IsValid() {
 90		rt.observe.UnknownClient()
 91	}
 92	sw := &statusWriter{ResponseWriter: w, status: http.StatusInternalServerError}
 93	defer func() { rt.observe.Response(class.name(), statusClass(sw.status)) }()
 94	rt.route(sw, r, class)
 95	if !sw.wrote {
 96		sw.status = http.StatusOK
 97	}
 98}
 99
100type statusWriter struct {
101	http.ResponseWriter
102	status int
103	wrote  bool
104}
105
106func (s *statusWriter) record(code int) {
107	if !s.wrote {
108		s.status, s.wrote = code, true
109	}
110}
111
112func (s *statusWriter) WriteHeader(code int) {
113	s.record(code)
114	s.ResponseWriter.WriteHeader(code)
115}
116
117func (s *statusWriter) Write(b []byte) (int, error) {
118	s.record(http.StatusOK)
119	return s.ResponseWriter.Write(b)
120}
121
122func (s *statusWriter) Flush() {
123	s.record(http.StatusOK)
124	if f, ok := s.ResponseWriter.(http.Flusher); ok {
125		f.Flush()
126	}
127}
128
129func (s *statusWriter) Unwrap() http.ResponseWriter {
130	return s.ResponseWriter
131}