1
2
3
4
5
6
7
8
9
10
11
12
13
14 package internal
15
16 import (
17 "net/http"
18 "net/http/httptest"
19 "net/url"
20 "strconv"
21 "testing"
22
23 "github.com/stretchr/testify/assert"
24 "github.com/stretchr/testify/require"
25
26 "go.opentelemetry.io/otel/attribute"
27 "go.opentelemetry.io/otel/codes"
28 )
29
30 var hc = &HTTPConv{
31 NetConv: nc,
32
33 EnduserIDKey: attribute.Key("enduser.id"),
34 HTTPClientIPKey: attribute.Key("http.client_ip"),
35 NetProtocolNameKey: attribute.Key("net.protocol.name"),
36 NetProtocolVersionKey: attribute.Key("net.protocol.version"),
37 HTTPMethodKey: attribute.Key("http.method"),
38 HTTPRequestContentLengthKey: attribute.Key("http.request_content_length"),
39 HTTPResponseContentLengthKey: attribute.Key("http.response_content_length"),
40 HTTPRouteKey: attribute.Key("http.route"),
41 HTTPSchemeHTTP: attribute.String("http.scheme", "http"),
42 HTTPSchemeHTTPS: attribute.String("http.scheme", "https"),
43 HTTPStatusCodeKey: attribute.Key("http.status_code"),
44 HTTPTargetKey: attribute.Key("http.target"),
45 HTTPURLKey: attribute.Key("http.url"),
46 UserAgentOriginalKey: attribute.Key("user_agent.original"),
47 }
48
49 func TestHTTPClientResponse(t *testing.T) {
50 const stat, n = 201, 397
51 resp := &http.Response{
52 StatusCode: stat,
53 ContentLength: n,
54 }
55 got := hc.ClientResponse(resp)
56 assert.Equal(t, 2, cap(got), "slice capacity")
57 assert.ElementsMatch(t, []attribute.KeyValue{
58 attribute.Key("http.status_code").Int(stat),
59 attribute.Key("http.response_content_length").Int(n),
60 }, got)
61 }
62
63 func TestHTTPSClientRequest(t *testing.T) {
64 req := &http.Request{
65 Method: http.MethodGet,
66 URL: &url.URL{
67 Scheme: "https",
68 Host: "127.0.0.1:443",
69 Path: "/resource",
70 },
71 Proto: "HTTP/1.0",
72 ProtoMajor: 1,
73 ProtoMinor: 0,
74 }
75
76 assert.Equal(
77 t,
78 []attribute.KeyValue{
79 attribute.String("http.method", "GET"),
80 attribute.String("net.protocol.version", "1.0"),
81 attribute.String("http.url", "https://127.0.0.1:443/resource"),
82 attribute.String("net.peer.name", "127.0.0.1"),
83 },
84 hc.ClientRequest(req),
85 )
86 }
87
88 func TestHTTPClientRequest(t *testing.T) {
89 const (
90 user = "alice"
91 n = 128
92 agent = "Go-http-client/1.1"
93 )
94 req := &http.Request{
95 Method: http.MethodGet,
96 URL: &url.URL{
97 Scheme: "http",
98 Host: "127.0.0.1:8080",
99 Path: "/resource",
100 },
101 Proto: "HTTP/1.0",
102 ProtoMajor: 1,
103 ProtoMinor: 0,
104 Header: http.Header{
105 "User-Agent": []string{agent},
106 },
107 ContentLength: n,
108 }
109 req.SetBasicAuth(user, "pswrd")
110
111 assert.Equal(
112 t,
113 []attribute.KeyValue{
114 attribute.String("http.method", "GET"),
115 attribute.String("net.protocol.version", "1.0"),
116 attribute.String("http.url", "http://127.0.0.1:8080/resource"),
117 attribute.String("net.peer.name", "127.0.0.1"),
118 attribute.Int("net.peer.port", 8080),
119 attribute.String("user_agent.original", agent),
120 attribute.Int("http.request_content_length", n),
121 attribute.String("enduser.id", user),
122 },
123 hc.ClientRequest(req),
124 )
125 }
126
127 func TestHTTPClientRequestRequired(t *testing.T) {
128 req := new(http.Request)
129 var got []attribute.KeyValue
130 assert.NotPanics(t, func() { got = hc.ClientRequest(req) })
131 want := []attribute.KeyValue{
132 attribute.String("http.method", "GET"),
133 attribute.String("net.protocol.name", ""),
134 attribute.String("http.url", ""),
135 attribute.String("net.peer.name", ""),
136 }
137 assert.Equal(t, want, got)
138 }
139
140 func TestHTTPServerRequest(t *testing.T) {
141 got := make(chan *http.Request, 1)
142 handler := func(w http.ResponseWriter, r *http.Request) {
143 got <- r
144 w.WriteHeader(http.StatusOK)
145 }
146
147 srv := httptest.NewServer(http.HandlerFunc(handler))
148 defer srv.Close()
149
150 srvURL, err := url.Parse(srv.URL)
151 require.NoError(t, err)
152 srvPort, err := strconv.ParseInt(srvURL.Port(), 10, 32)
153 require.NoError(t, err)
154
155 resp, err := srv.Client().Get(srv.URL)
156 require.NoError(t, err)
157 require.NoError(t, resp.Body.Close())
158
159 req := <-got
160 peer, peerPort := splitHostPort(req.RemoteAddr)
161
162 const user = "alice"
163 req.SetBasicAuth(user, "pswrd")
164
165 const clientIP = "127.0.0.5"
166 req.Header.Add("X-Forwarded-For", clientIP)
167
168 assert.ElementsMatch(t,
169 []attribute.KeyValue{
170 attribute.String("http.method", "GET"),
171 attribute.String("http.scheme", "http"),
172 attribute.String("net.protocol.version", "1.1"),
173 attribute.String("net.host.name", srvURL.Hostname()),
174 attribute.Int("net.host.port", int(srvPort)),
175 attribute.String("net.sock.peer.addr", peer),
176 attribute.Int("net.sock.peer.port", peerPort),
177 attribute.String("user_agent.original", "Go-http-client/1.1"),
178 attribute.String("enduser.id", user),
179 attribute.String("http.client_ip", clientIP),
180 },
181 hc.ServerRequest("", req))
182 }
183
184 func TestHTTPServerName(t *testing.T) {
185 req := new(http.Request)
186 var got []attribute.KeyValue
187 const (
188 host = "test.semconv.server"
189 port = 8080
190 )
191 portStr := strconv.Itoa(port)
192 server := host + ":" + portStr
193 assert.NotPanics(t, func() { got = hc.ServerRequest(server, req) })
194 assert.Contains(t, got, attribute.String("net.host.name", host))
195 assert.Contains(t, got, attribute.Int("net.host.port", port))
196
197 req = &http.Request{Host: "alt.host.name:" + portStr}
198
199
200 assert.NotPanics(t, func() { got = hc.ServerRequest(host, req) })
201 assert.Contains(t, got, attribute.String("net.host.name", host))
202 assert.Contains(t, got, attribute.Int("net.host.port", port))
203 }
204
205 func TestHTTPServerRequestFailsGracefully(t *testing.T) {
206 req := new(http.Request)
207 var got []attribute.KeyValue
208 assert.NotPanics(t, func() { got = hc.ServerRequest("", req) })
209 want := []attribute.KeyValue{
210 attribute.String("http.method", "GET"),
211 attribute.String("http.scheme", "http"),
212 attribute.String("net.protocol.name", ""),
213 attribute.String("net.host.name", ""),
214 }
215 assert.ElementsMatch(t, want, got)
216 }
217
218 func TestMethod(t *testing.T) {
219 assert.Equal(t, attribute.String("http.method", "POST"), hc.method("POST"))
220 assert.Equal(t, attribute.String("http.method", "GET"), hc.method(""))
221 assert.Equal(t, attribute.String("http.method", "garbage"), hc.method("garbage"))
222 }
223
224 func TestScheme(t *testing.T) {
225 assert.Equal(t, attribute.String("http.scheme", "http"), hc.scheme(false))
226 assert.Equal(t, attribute.String("http.scheme", "https"), hc.scheme(true))
227 }
228
229 func TestProto(t *testing.T) {
230 testCases := []struct {
231 in string
232 want attribute.KeyValue
233 }{
234 {
235 in: "HTTP/1.0",
236 want: attribute.String("net.protocol.version", "1.0"),
237 },
238 {
239 in: "HTTP/1.1",
240 want: attribute.String("net.protocol.version", "1.1"),
241 },
242 {
243 in: "HTTP/2",
244 want: attribute.String("net.protocol.version", "2.0"),
245 },
246 {
247 in: "HTTP/3",
248 want: attribute.String("net.protocol.version", "3.0"),
249 },
250 {
251 in: "SPDY",
252 want: attribute.String("net.protocol.name", "SPDY"),
253 },
254 {
255 in: "QUIC",
256 want: attribute.String("net.protocol.name", "QUIC"),
257 },
258 {
259 in: "other",
260 want: attribute.String("net.protocol.name", "other"),
261 },
262 }
263 for _, tc := range testCases {
264 t.Run(tc.in, func(t *testing.T) {
265 got := hc.proto(tc.in)
266 assert.Equal(t, tc.want, got)
267 })
268 }
269 }
270
271 func TestServerClientIP(t *testing.T) {
272 tests := []struct {
273 xForwardedFor string
274 want string
275 }{
276 {"", ""},
277 {"127.0.0.1", "127.0.0.1"},
278 {"127.0.0.1,127.0.0.5", "127.0.0.1"},
279 }
280 for _, test := range tests {
281 got := serverClientIP(test.xForwardedFor)
282 assert.Equal(t, test.want, got, test.xForwardedFor)
283 }
284 }
285
286 func TestRequiredHTTPPort(t *testing.T) {
287 tests := []struct {
288 https bool
289 port int
290 want int
291 }{
292 {true, 443, -1},
293 {true, 80, 80},
294 {true, 8081, 8081},
295 {false, 443, 443},
296 {false, 80, -1},
297 {false, 8080, 8080},
298 }
299 for _, test := range tests {
300 got := requiredHTTPPort(test.https, test.port)
301 assert.Equal(t, test.want, got, test.https, test.port)
302 }
303 }
304
305 func TestFirstHostPort(t *testing.T) {
306 host, port := "127.0.0.1", 8080
307 hostport := "127.0.0.1:8080"
308 sources := [][]string{
309 {hostport},
310 {"", hostport},
311 {"", "", hostport},
312 {"", "", hostport, ""},
313 {"", "", hostport, "127.0.0.3:80"},
314 }
315
316 for _, src := range sources {
317 h, p := firstHostPort(src...)
318 assert.Equal(t, host, h, src)
319 assert.Equal(t, port, p, src)
320 }
321 }
322
323 func TestRequestHeader(t *testing.T) {
324 ips := []string{"127.0.0.5", "127.0.0.9"}
325 user := []string{"alice"}
326 h := http.Header{"ips": ips, "user": user}
327
328 got := hc.RequestHeader(h)
329 assert.Equal(t, 2, cap(got), "slice capacity")
330 assert.ElementsMatch(t, []attribute.KeyValue{
331 attribute.StringSlice("http.request.header.ips", ips),
332 attribute.StringSlice("http.request.header.user", user),
333 }, got)
334 }
335
336 func TestReponseHeader(t *testing.T) {
337 ips := []string{"127.0.0.5", "127.0.0.9"}
338 user := []string{"alice"}
339 h := http.Header{"ips": ips, "user": user}
340
341 got := hc.ResponseHeader(h)
342 assert.Equal(t, 2, cap(got), "slice capacity")
343 assert.ElementsMatch(t, []attribute.KeyValue{
344 attribute.StringSlice("http.response.header.ips", ips),
345 attribute.StringSlice("http.response.header.user", user),
346 }, got)
347 }
348
349 func TestClientStatus(t *testing.T) {
350 tests := []struct {
351 code int
352 stat codes.Code
353 msg bool
354 }{
355 {0, codes.Error, true},
356 {http.StatusContinue, codes.Unset, false},
357 {http.StatusSwitchingProtocols, codes.Unset, false},
358 {http.StatusProcessing, codes.Unset, false},
359 {http.StatusEarlyHints, codes.Unset, false},
360 {http.StatusOK, codes.Unset, false},
361 {http.StatusCreated, codes.Unset, false},
362 {http.StatusAccepted, codes.Unset, false},
363 {http.StatusNonAuthoritativeInfo, codes.Unset, false},
364 {http.StatusNoContent, codes.Unset, false},
365 {http.StatusResetContent, codes.Unset, false},
366 {http.StatusPartialContent, codes.Unset, false},
367 {http.StatusMultiStatus, codes.Unset, false},
368 {http.StatusAlreadyReported, codes.Unset, false},
369 {http.StatusIMUsed, codes.Unset, false},
370 {http.StatusMultipleChoices, codes.Unset, false},
371 {http.StatusMovedPermanently, codes.Unset, false},
372 {http.StatusFound, codes.Unset, false},
373 {http.StatusSeeOther, codes.Unset, false},
374 {http.StatusNotModified, codes.Unset, false},
375 {http.StatusUseProxy, codes.Unset, false},
376 {306, codes.Error, true},
377 {http.StatusTemporaryRedirect, codes.Unset, false},
378 {http.StatusPermanentRedirect, codes.Unset, false},
379 {http.StatusBadRequest, codes.Error, false},
380 {http.StatusUnauthorized, codes.Error, false},
381 {http.StatusPaymentRequired, codes.Error, false},
382 {http.StatusForbidden, codes.Error, false},
383 {http.StatusNotFound, codes.Error, false},
384 {http.StatusMethodNotAllowed, codes.Error, false},
385 {http.StatusNotAcceptable, codes.Error, false},
386 {http.StatusProxyAuthRequired, codes.Error, false},
387 {http.StatusRequestTimeout, codes.Error, false},
388 {http.StatusConflict, codes.Error, false},
389 {http.StatusGone, codes.Error, false},
390 {http.StatusLengthRequired, codes.Error, false},
391 {http.StatusPreconditionFailed, codes.Error, false},
392 {http.StatusRequestEntityTooLarge, codes.Error, false},
393 {http.StatusRequestURITooLong, codes.Error, false},
394 {http.StatusUnsupportedMediaType, codes.Error, false},
395 {http.StatusRequestedRangeNotSatisfiable, codes.Error, false},
396 {http.StatusExpectationFailed, codes.Error, false},
397 {http.StatusTeapot, codes.Error, false},
398 {http.StatusMisdirectedRequest, codes.Error, false},
399 {http.StatusUnprocessableEntity, codes.Error, false},
400 {http.StatusLocked, codes.Error, false},
401 {http.StatusFailedDependency, codes.Error, false},
402 {http.StatusTooEarly, codes.Error, false},
403 {http.StatusUpgradeRequired, codes.Error, false},
404 {http.StatusPreconditionRequired, codes.Error, false},
405 {http.StatusTooManyRequests, codes.Error, false},
406 {http.StatusRequestHeaderFieldsTooLarge, codes.Error, false},
407 {http.StatusUnavailableForLegalReasons, codes.Error, false},
408 {http.StatusInternalServerError, codes.Error, false},
409 {http.StatusNotImplemented, codes.Error, false},
410 {http.StatusBadGateway, codes.Error, false},
411 {http.StatusServiceUnavailable, codes.Error, false},
412 {http.StatusGatewayTimeout, codes.Error, false},
413 {http.StatusHTTPVersionNotSupported, codes.Error, false},
414 {http.StatusVariantAlsoNegotiates, codes.Error, false},
415 {http.StatusInsufficientStorage, codes.Error, false},
416 {http.StatusLoopDetected, codes.Error, false},
417 {http.StatusNotExtended, codes.Error, false},
418 {http.StatusNetworkAuthenticationRequired, codes.Error, false},
419 {600, codes.Error, true},
420 }
421
422 for _, test := range tests {
423 c, msg := hc.ClientStatus(test.code)
424 assert.Equal(t, test.stat, c)
425 if test.msg && msg == "" {
426 t.Errorf("expected non-empty message for %d", test.code)
427 } else if !test.msg && msg != "" {
428 t.Errorf("expected empty message for %d, got: %s", test.code, msg)
429 }
430 }
431 }
432
433 func TestServerStatus(t *testing.T) {
434 tests := []struct {
435 code int
436 stat codes.Code
437 msg bool
438 }{
439 {0, codes.Error, true},
440 {http.StatusContinue, codes.Unset, false},
441 {http.StatusSwitchingProtocols, codes.Unset, false},
442 {http.StatusProcessing, codes.Unset, false},
443 {http.StatusEarlyHints, codes.Unset, false},
444 {http.StatusOK, codes.Unset, false},
445 {http.StatusCreated, codes.Unset, false},
446 {http.StatusAccepted, codes.Unset, false},
447 {http.StatusNonAuthoritativeInfo, codes.Unset, false},
448 {http.StatusNoContent, codes.Unset, false},
449 {http.StatusResetContent, codes.Unset, false},
450 {http.StatusPartialContent, codes.Unset, false},
451 {http.StatusMultiStatus, codes.Unset, false},
452 {http.StatusAlreadyReported, codes.Unset, false},
453 {http.StatusIMUsed, codes.Unset, false},
454 {http.StatusMultipleChoices, codes.Unset, false},
455 {http.StatusMovedPermanently, codes.Unset, false},
456 {http.StatusFound, codes.Unset, false},
457 {http.StatusSeeOther, codes.Unset, false},
458 {http.StatusNotModified, codes.Unset, false},
459 {http.StatusUseProxy, codes.Unset, false},
460 {306, codes.Error, true},
461 {http.StatusTemporaryRedirect, codes.Unset, false},
462 {http.StatusPermanentRedirect, codes.Unset, false},
463 {http.StatusBadRequest, codes.Unset, false},
464 {http.StatusUnauthorized, codes.Unset, false},
465 {http.StatusPaymentRequired, codes.Unset, false},
466 {http.StatusForbidden, codes.Unset, false},
467 {http.StatusNotFound, codes.Unset, false},
468 {http.StatusMethodNotAllowed, codes.Unset, false},
469 {http.StatusNotAcceptable, codes.Unset, false},
470 {http.StatusProxyAuthRequired, codes.Unset, false},
471 {http.StatusRequestTimeout, codes.Unset, false},
472 {http.StatusConflict, codes.Unset, false},
473 {http.StatusGone, codes.Unset, false},
474 {http.StatusLengthRequired, codes.Unset, false},
475 {http.StatusPreconditionFailed, codes.Unset, false},
476 {http.StatusRequestEntityTooLarge, codes.Unset, false},
477 {http.StatusRequestURITooLong, codes.Unset, false},
478 {http.StatusUnsupportedMediaType, codes.Unset, false},
479 {http.StatusRequestedRangeNotSatisfiable, codes.Unset, false},
480 {http.StatusExpectationFailed, codes.Unset, false},
481 {http.StatusTeapot, codes.Unset, false},
482 {http.StatusMisdirectedRequest, codes.Unset, false},
483 {http.StatusUnprocessableEntity, codes.Unset, false},
484 {http.StatusLocked, codes.Unset, false},
485 {http.StatusFailedDependency, codes.Unset, false},
486 {http.StatusTooEarly, codes.Unset, false},
487 {http.StatusUpgradeRequired, codes.Unset, false},
488 {http.StatusPreconditionRequired, codes.Unset, false},
489 {http.StatusTooManyRequests, codes.Unset, false},
490 {http.StatusRequestHeaderFieldsTooLarge, codes.Unset, false},
491 {http.StatusUnavailableForLegalReasons, codes.Unset, false},
492 {http.StatusInternalServerError, codes.Error, false},
493 {http.StatusNotImplemented, codes.Error, false},
494 {http.StatusBadGateway, codes.Error, false},
495 {http.StatusServiceUnavailable, codes.Error, false},
496 {http.StatusGatewayTimeout, codes.Error, false},
497 {http.StatusHTTPVersionNotSupported, codes.Error, false},
498 {http.StatusVariantAlsoNegotiates, codes.Error, false},
499 {http.StatusInsufficientStorage, codes.Error, false},
500 {http.StatusLoopDetected, codes.Error, false},
501 {http.StatusNotExtended, codes.Error, false},
502 {http.StatusNetworkAuthenticationRequired, codes.Error, false},
503 {600, codes.Error, true},
504 }
505
506 for _, test := range tests {
507 c, msg := hc.ServerStatus(test.code)
508 assert.Equal(t, test.stat, c)
509 if test.msg && msg == "" {
510 t.Errorf("expected non-empty message for %d", test.code)
511 } else if !test.msg && msg != "" {
512 t.Errorf("expected empty message for %d, got: %s", test.code, msg)
513 }
514 }
515 }
516
View as plain text