1 package services
2
3 import (
4 "context"
5 "crypto/tls"
6 "encoding/json"
7 "fmt"
8 "io/ioutil"
9 "net/http"
10 "os"
11 "strconv"
12 "strings"
13 "time"
14
15 "github.com/datawire/dlib/dgroup"
16 "github.com/datawire/dlib/dhttp"
17 "github.com/datawire/dlib/dlog"
18 )
19
20
21 type HTTP struct {
22 Port int16
23 Backend string
24 SecurePort int16
25 SecureBackend string
26 Cert string
27 Key string
28 TLSVersion string
29 }
30
31 func getTLSVersion(state *tls.ConnectionState) string {
32 switch state.Version {
33 case tls.VersionTLS10:
34 return "v1.0"
35 case tls.VersionTLS11:
36 return "v1.1"
37 case tls.VersionTLS12:
38 return "v1.2"
39
40 case 0x0304:
41 return "v1.3"
42 default:
43 return "unknown"
44 }
45 }
46
47
48 func (h *HTTP) Start(ctx context.Context) <-chan bool {
49 dlog.Printf(ctx, "HTTP: %s listening on %d/%d", h.Backend, h.Port, h.SecurePort)
50
51 mux := http.NewServeMux()
52 mux.HandleFunc("/", h.handler)
53
54 sc := &dhttp.ServerConfig{
55 Handler: mux,
56 }
57
58 g := dgroup.NewGroup(ctx, dgroup.GroupConfig{})
59 g.Go("cleartext", func(ctx context.Context) error {
60 return sc.ListenAndServe(ctx, fmt.Sprintf(":%v", h.Port))
61 })
62 g.Go("tls", func(ctx context.Context) error {
63 return sc.ListenAndServeTLS(ctx, fmt.Sprintf(":%v", h.SecurePort), h.Cert, h.Key)
64 })
65
66 exited := make(chan bool)
67 go func() {
68 if err := g.Wait(); err != nil {
69 dlog.Error(ctx, err)
70 panic(err)
71 }
72 close(exited)
73 }()
74 return exited
75 }
76
77
78 func lower(m map[string][]string) (result map[string][]string) {
79 result = make(map[string][]string)
80 for k, v := range m {
81 result[strings.ToLower(k)] = v
82 }
83 return result
84 }
85
86 func (h *HTTP) handler(w http.ResponseWriter, r *http.Request) {
87 ctx := r.Context()
88
89 backend := h.Backend
90 conntype := "CLR"
91
92 var request = make(map[string]interface{})
93 var url = make(map[string]interface{})
94 request["url"] = url
95 url["fragment"] = r.URL.Fragment
96 url["host"] = r.URL.Host
97 url["opaque"] = r.URL.Opaque
98 url["path"] = r.URL.Path
99 url["query"] = r.URL.Query()
100 url["rawQuery"] = r.URL.RawQuery
101 url["scheme"] = r.URL.Scheme
102 if r.URL.User != nil {
103 url["username"] = r.URL.User.Username()
104 pw, ok := r.URL.User.Password()
105 if ok {
106 url["password"] = pw
107 }
108 }
109 request["method"] = r.Method
110 request["headers"] = lower(r.Header)
111 request["host"] = r.Host
112
113 var tlsrequest = make(map[string]interface{})
114 request["tls"] = tlsrequest
115
116 tlsrequest["enabled"] = r.TLS != nil
117
118 if r.TLS != nil {
119
120 backend = h.SecureBackend
121 conntype = "TLS"
122
123 tlsrequest["negotiated-protocol"] = r.TLS.NegotiatedProtocol
124 tlsrequest["server-name"] = r.TLS.ServerName
125 tlsrequest["negotiated-protocol-version"] = getTLSVersion(r.TLS)
126 }
127
128
129 status := r.Header.Get("Kat-Req-Http-Requested-Status")
130 if status == "" {
131 status = "200"
132 }
133
134 statusCode, err := strconv.Atoi(status)
135 if err != nil {
136 dlog.Print(ctx, err)
137 statusCode = 500
138 }
139
140
141 headers, ok := r.Header["Kat-Req-Http-Requested-Header"]
142 if ok {
143 for _, header := range headers {
144 canonical := http.CanonicalHeaderKey(header)
145 value, ok := r.Header[canonical]
146 if ok {
147 w.Header()[canonical] = value
148 }
149 }
150 }
151
152 if b, _ := ioutil.ReadAll(r.Body); b != nil {
153 body := string(b)
154 if len(body) > 0 {
155 dlog.Printf(ctx, "received body: %s", body)
156 }
157 w.Header()[http.CanonicalHeaderKey("Kat-Resp-Http-Request-Body")] = []string{body}
158 }
159 defer r.Body.Close()
160
161 cookies, ok := r.Header["Kat-Req-Http-Requested-Cookie"]
162 if ok {
163 for _, v := range strings.Split(cookies[0], ",") {
164 val := strings.Trim(v, " ")
165 http.SetCookie(w, &http.Cookie{
166 Name: val,
167 Value: val,
168 })
169 }
170 }
171
172
173 location, ok := r.Header["Kat-Req-Http-Requested-Location"]
174
175 if ok {
176 w.Header()[http.CanonicalHeaderKey("Location")] = location
177 }
178
179 addExtauthEnv := os.Getenv("INCLUDE_EXTAUTH_HEADER")
180
181
182 addExtAuthOverride := r.URL.Query().Get("override_extauth_header")
183
184 if len(addExtauthEnv) > 0 && len(addExtAuthOverride) == 0 {
185 extauth := make(map[string]interface{})
186 extauth["request"] = request
187 extauth["resp_headers"] = lower(w.Header())
188
189 eaJSON, err := json.Marshal(extauth)
190
191 if err != nil {
192 eaJSON = []byte(fmt.Sprintf("err: %v", err))
193 }
194
195 eaArray := make([]string, 1, 1)
196 eaArray[0] = string(eaJSON)
197
198 w.Header()[http.CanonicalHeaderKey("extauth")] = eaArray
199 }
200
201
202 if h, ok := r.Header["Kat-Req-Http-Requested-Backend-Delay"]; ok {
203 if v, err := strconv.Atoi(h[0]); err == nil {
204 dlog.Printf(ctx, "Delaying response by %v ms", v)
205 time.Sleep(time.Duration(v) * time.Millisecond)
206 }
207 }
208
209
210 w.Header().Set("Date", time.Now().Format(time.RFC1123))
211
212 w.WriteHeader(statusCode)
213
214
215 var response = make(map[string]interface{})
216 response["headers"] = lower(w.Header())
217
218 var body = make(map[string]interface{})
219 body["backend"] = backend
220 body["request"] = request
221 body["response"] = response
222
223 b, err := json.MarshalIndent(body, "", " ")
224 if err != nil {
225 b = []byte(fmt.Sprintf("Error: %v", err))
226 }
227
228 dlog.Printf(ctx, "%s (%s): \"%s %s\" -> HTTP %v", r.Method, r.URL.Path, backend, conntype, statusCode)
229 _, _ = w.Write(b)
230 }
231
View as plain text