...

Source file src/go.opentelemetry.io/otel/semconv/internal/v3/http_test.go

Documentation: go.opentelemetry.io/otel/semconv/internal/v3

     1  // Copyright The OpenTelemetry Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  //     http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    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  	UserAgentOriginalKey:         attribute.Key("user_agent.original"),
    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("user_agent.original", 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("user_agent.original", "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  	// The server parameter does not include a port, ServerRequest should use
   198  	// the port in the request Host field.
   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