1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package internal
16
17 import (
18 "fmt"
19 "net/http"
20 "strings"
21
22 "go.opentelemetry.io/otel/attribute"
23 "go.opentelemetry.io/otel/codes"
24 )
25
26
27
28 type HTTPConv struct {
29 NetConv *NetConv
30
31 EnduserIDKey attribute.Key
32 HTTPClientIPKey attribute.Key
33 HTTPFlavorKey attribute.Key
34 HTTPMethodKey attribute.Key
35 HTTPRequestContentLengthKey attribute.Key
36 HTTPResponseContentLengthKey attribute.Key
37 HTTPRouteKey attribute.Key
38 HTTPSchemeHTTP attribute.KeyValue
39 HTTPSchemeHTTPS attribute.KeyValue
40 HTTPStatusCodeKey attribute.Key
41 HTTPTargetKey attribute.Key
42 HTTPURLKey attribute.Key
43 UserAgentOriginalKey attribute.Key
44 }
45
46
47
48
49
50
51
52
53
54
55
56 func (c *HTTPConv) ClientResponse(resp *http.Response) []attribute.KeyValue {
57 var n int
58 if resp.StatusCode > 0 {
59 n++
60 }
61 if resp.ContentLength > 0 {
62 n++
63 }
64
65 attrs := make([]attribute.KeyValue, 0, n)
66 if resp.StatusCode > 0 {
67 attrs = append(attrs, c.HTTPStatusCodeKey.Int(resp.StatusCode))
68 }
69 if resp.ContentLength > 0 {
70 attrs = append(attrs, c.HTTPResponseContentLengthKey.Int(int(resp.ContentLength)))
71 }
72 return attrs
73 }
74
75
76
77
78
79
80 func (c *HTTPConv) ClientRequest(req *http.Request) []attribute.KeyValue {
81 n := 3
82 var h string
83 if req.URL != nil {
84 h = req.URL.Host
85 }
86 peer, p := firstHostPort(h, req.Header.Get("Host"))
87 port := requiredHTTPPort(req.URL != nil && req.URL.Scheme == "https", p)
88 if port > 0 {
89 n++
90 }
91 useragent := req.UserAgent()
92 if useragent != "" {
93 n++
94 }
95 if req.ContentLength > 0 {
96 n++
97 }
98 userID, _, hasUserID := req.BasicAuth()
99 if hasUserID {
100 n++
101 }
102 attrs := make([]attribute.KeyValue, 0, n)
103
104 attrs = append(attrs, c.method(req.Method))
105 attrs = append(attrs, c.proto(req.Proto))
106
107 var u string
108 if req.URL != nil {
109
110 userinfo := req.URL.User
111 req.URL.User = nil
112 u = req.URL.String()
113
114 req.URL.User = userinfo
115 }
116 attrs = append(attrs, c.HTTPURLKey.String(u))
117
118 attrs = append(attrs, c.NetConv.PeerName(peer))
119 if port > 0 {
120 attrs = append(attrs, c.NetConv.PeerPort(port))
121 }
122
123 if useragent != "" {
124 attrs = append(attrs, c.UserAgentOriginalKey.String(useragent))
125 }
126
127 if l := req.ContentLength; l > 0 {
128 attrs = append(attrs, c.HTTPRequestContentLengthKey.Int64(l))
129 }
130
131 if hasUserID {
132 attrs = append(attrs, c.EnduserIDKey.String(userID))
133 }
134
135 return attrs
136 }
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159 func (c *HTTPConv) ServerRequest(server string, req *http.Request) []attribute.KeyValue {
160
161
162
163
164
165
166
167 n := 4
168 var host string
169 var p int
170 if server == "" {
171 host, p = splitHostPort(req.Host)
172 } else {
173
174 host, p = splitHostPort(server)
175 if p < 0 {
176 _, p = splitHostPort(req.Host)
177 }
178 }
179 hostPort := requiredHTTPPort(req.TLS != nil, p)
180 if hostPort > 0 {
181 n++
182 }
183 peer, peerPort := splitHostPort(req.RemoteAddr)
184 if peer != "" {
185 n++
186 if peerPort > 0 {
187 n++
188 }
189 }
190 useragent := req.UserAgent()
191 if useragent != "" {
192 n++
193 }
194 userID, _, hasUserID := req.BasicAuth()
195 if hasUserID {
196 n++
197 }
198 clientIP := serverClientIP(req.Header.Get("X-Forwarded-For"))
199 if clientIP != "" {
200 n++
201 }
202 attrs := make([]attribute.KeyValue, 0, n)
203
204 attrs = append(attrs, c.method(req.Method))
205 attrs = append(attrs, c.scheme(req.TLS != nil))
206 attrs = append(attrs, c.proto(req.Proto))
207 attrs = append(attrs, c.NetConv.HostName(host))
208
209 if hostPort > 0 {
210 attrs = append(attrs, c.NetConv.HostPort(hostPort))
211 }
212
213 if peer != "" {
214
215
216 attrs = append(attrs, c.NetConv.SockPeerAddr(peer))
217 if peerPort > 0 {
218 attrs = append(attrs, c.NetConv.SockPeerPort(peerPort))
219 }
220 }
221
222 if useragent != "" {
223 attrs = append(attrs, c.UserAgentOriginalKey.String(useragent))
224 }
225
226 if hasUserID {
227 attrs = append(attrs, c.EnduserIDKey.String(userID))
228 }
229
230 if clientIP != "" {
231 attrs = append(attrs, c.HTTPClientIPKey.String(clientIP))
232 }
233
234 return attrs
235 }
236
237 func (c *HTTPConv) method(method string) attribute.KeyValue {
238 if method == "" {
239 return c.HTTPMethodKey.String(http.MethodGet)
240 }
241 return c.HTTPMethodKey.String(method)
242 }
243
244 func (c *HTTPConv) scheme(https bool) attribute.KeyValue {
245 if https {
246 return c.HTTPSchemeHTTPS
247 }
248 return c.HTTPSchemeHTTP
249 }
250
251 func (c *HTTPConv) proto(proto string) attribute.KeyValue {
252 switch proto {
253 case "HTTP/1.0":
254 return c.HTTPFlavorKey.String("1.0")
255 case "HTTP/1.1":
256 return c.HTTPFlavorKey.String("1.1")
257 case "HTTP/2":
258 return c.HTTPFlavorKey.String("2.0")
259 case "HTTP/3":
260 return c.HTTPFlavorKey.String("3.0")
261 default:
262 return c.HTTPFlavorKey.String(proto)
263 }
264 }
265
266 func serverClientIP(xForwardedFor string) string {
267 if idx := strings.Index(xForwardedFor, ","); idx >= 0 {
268 xForwardedFor = xForwardedFor[:idx]
269 }
270 return xForwardedFor
271 }
272
273 func requiredHTTPPort(https bool, port int) int {
274 if https {
275 if port > 0 && port != 443 {
276 return port
277 }
278 } else {
279 if port > 0 && port != 80 {
280 return port
281 }
282 }
283 return -1
284 }
285
286
287 func firstHostPort(source ...string) (host string, port int) {
288 for _, hostport := range source {
289 host, port = splitHostPort(hostport)
290 if host != "" || port > 0 {
291 break
292 }
293 }
294 return
295 }
296
297
298 func (c *HTTPConv) RequestHeader(h http.Header) []attribute.KeyValue {
299 return c.header("http.request.header", h)
300 }
301
302
303 func (c *HTTPConv) ResponseHeader(h http.Header) []attribute.KeyValue {
304 return c.header("http.response.header", h)
305 }
306
307 func (c *HTTPConv) header(prefix string, h http.Header) []attribute.KeyValue {
308 key := func(k string) attribute.Key {
309 k = strings.ToLower(k)
310 k = strings.ReplaceAll(k, "-", "_")
311 k = fmt.Sprintf("%s.%s", prefix, k)
312 return attribute.Key(k)
313 }
314
315 attrs := make([]attribute.KeyValue, 0, len(h))
316 for k, v := range h {
317 attrs = append(attrs, key(k).StringSlice(v))
318 }
319 return attrs
320 }
321
322
323
324 func (c *HTTPConv) ClientStatus(code int) (codes.Code, string) {
325 stat, valid := validateHTTPStatusCode(code)
326 if !valid {
327 return stat, fmt.Sprintf("Invalid HTTP status code %d", code)
328 }
329 return stat, ""
330 }
331
332
333
334
335 func (c *HTTPConv) ServerStatus(code int) (codes.Code, string) {
336 stat, valid := validateHTTPStatusCode(code)
337 if !valid {
338 return stat, fmt.Sprintf("Invalid HTTP status code %d", code)
339 }
340
341 if code/100 == 4 {
342 return codes.Unset, ""
343 }
344 return stat, ""
345 }
346
347 type codeRange struct {
348 fromInclusive int
349 toInclusive int
350 }
351
352 func (r codeRange) contains(code int) bool {
353 return r.fromInclusive <= code && code <= r.toInclusive
354 }
355
356 var validRangesPerCategory = map[int][]codeRange{
357 1: {
358 {http.StatusContinue, http.StatusEarlyHints},
359 },
360 2: {
361 {http.StatusOK, http.StatusAlreadyReported},
362 {http.StatusIMUsed, http.StatusIMUsed},
363 },
364 3: {
365 {http.StatusMultipleChoices, http.StatusUseProxy},
366 {http.StatusTemporaryRedirect, http.StatusPermanentRedirect},
367 },
368 4: {
369 {http.StatusBadRequest, http.StatusTeapot},
370 {http.StatusMisdirectedRequest, http.StatusUpgradeRequired},
371 {http.StatusPreconditionRequired, http.StatusTooManyRequests},
372 {http.StatusRequestHeaderFieldsTooLarge, http.StatusRequestHeaderFieldsTooLarge},
373 {http.StatusUnavailableForLegalReasons, http.StatusUnavailableForLegalReasons},
374 },
375 5: {
376 {http.StatusInternalServerError, http.StatusLoopDetected},
377 {http.StatusNotExtended, http.StatusNetworkAuthenticationRequired},
378 },
379 }
380
381
382
383
384 func validateHTTPStatusCode(code int) (codes.Code, bool) {
385 category := code / 100
386 ranges, ok := validRangesPerCategory[category]
387 if !ok {
388 return codes.Error, false
389 }
390 ok = false
391 for _, crange := range ranges {
392 ok = crange.contains(code)
393 if ok {
394 break
395 }
396 }
397 if !ok {
398 return codes.Error, false
399 }
400 if category > 0 && category < 4 {
401 return codes.Unset, true
402 }
403 return codes.Error, true
404 }
405
View as plain text