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