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