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}