...

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

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

     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  	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  	// The server parameter does not include a port, ServerRequest should use
   199  	// the port in the request Host field.
   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