github.com/yandex/pandora@v0.5.32/components/guns/http/http_test.go (about)

     1  package phttp
     2  
     3  import (
     4  	"crypto/tls"
     5  	"net/http"
     6  	"net/http/httptest"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/require"
    11  	ammomock "github.com/yandex/pandora/components/guns/http/mocks"
    12  	"github.com/yandex/pandora/core/aggregator/netsample"
    13  	"github.com/yandex/pandora/core/config"
    14  	"go.uber.org/atomic"
    15  	"go.uber.org/zap"
    16  	"golang.org/x/net/http2"
    17  )
    18  
    19  func TestBaseGun_GunClientConfig_decode(t *testing.T) {
    20  	conf := DefaultHTTPGunConfig()
    21  	data := map[interface{}]interface{}{
    22  		"target": "test-trbo01e.haze.yandex.net:3000",
    23  	}
    24  	err := config.DecodeAndValidate(data, &conf)
    25  	require.NoError(t, err)
    26  }
    27  
    28  func TestBaseGun_integration(t *testing.T) {
    29  	const host = "example.com"
    30  	const path = "/smth"
    31  	expectedReq, err := http.NewRequest("GET", "http://"+host+path, nil)
    32  	expectedReq.Host = "" // Important. Ammo may have empty host.
    33  	require.NoError(t, err)
    34  	var actualReq *http.Request
    35  	server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
    36  		rw.WriteHeader(http.StatusOK)
    37  		actualReq = req
    38  	}))
    39  	defer server.Close()
    40  	log := zap.NewNop()
    41  	conf := DefaultHTTPGunConfig()
    42  	conf.Target = host + ":80"
    43  	targetResolved := strings.TrimPrefix(server.URL, "http://")
    44  	results := &netsample.TestAggregator{}
    45  	conf.TargetResolved = targetResolved
    46  	httpGun := NewHTTP1Gun(conf, log)
    47  	_ = httpGun.Bind(results, testDeps())
    48  
    49  	am := newAmmoReq(t, expectedReq)
    50  	httpGun.Shoot(am)
    51  	require.NoError(t, results.Samples[0].Err())
    52  	require.NotNil(t, actualReq)
    53  
    54  	require.Equal(t, actualReq.Method, "GET")
    55  	require.Equal(t, actualReq.Proto, "HTTP/1.1")
    56  	require.Equal(t, actualReq.Host, host)
    57  	require.NotNil(t, actualReq.URL)
    58  	require.Empty(t, actualReq.URL.Host)
    59  	require.Equal(t, actualReq.URL.Path, path)
    60  }
    61  
    62  func TestHTTP(t *testing.T) {
    63  	tests := []struct {
    64  		name  string
    65  		https bool
    66  	}{
    67  		{
    68  			name:  "http ok",
    69  			https: false,
    70  		},
    71  		{
    72  			name:  "https ok",
    73  			https: true,
    74  		},
    75  	}
    76  	for _, tt := range tests {
    77  		t.Run(tt.name, func(t *testing.T) {
    78  			var isServed atomic.Bool
    79  			server := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
    80  				require.Empty(t, req.Header.Get("Accept-Encoding"))
    81  				rw.WriteHeader(http.StatusOK)
    82  				isServed.Store(true)
    83  			}))
    84  			if tt.https {
    85  				server.StartTLS()
    86  			} else {
    87  				server.Start()
    88  			}
    89  			defer server.Close()
    90  			log := zap.NewNop()
    91  			conf := DefaultHTTPGunConfig()
    92  			conf.Target = server.Listener.Addr().String()
    93  			conf.SSL = tt.https
    94  			conf.TargetResolved = conf.Target
    95  			gun := NewHTTP1Gun(conf, log)
    96  			var aggr netsample.TestAggregator
    97  			_ = gun.Bind(&aggr, testDeps())
    98  			gun.Shoot(newAmmoURL(t, "/"))
    99  
   100  			require.Equal(t, len(aggr.Samples), 1)
   101  			require.Equal(t, aggr.Samples[0].ProtoCode(), http.StatusOK)
   102  			require.True(t, isServed.Load())
   103  		})
   104  	}
   105  }
   106  
   107  func TestHTTP_Redirect(t *testing.T) {
   108  	tests := []struct {
   109  		name     string
   110  		redirect bool
   111  	}{
   112  		{
   113  			name:     "not follow redirects by default",
   114  			redirect: false,
   115  		},
   116  		{
   117  			name:     "follow redirects if option set",
   118  			redirect: true,
   119  		},
   120  	}
   121  	for _, tt := range tests {
   122  		t.Run(tt.name, func(t *testing.T) {
   123  			server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   124  				if req.URL.Path == "/redirect" {
   125  					rw.Header().Add("Location", "/")
   126  					rw.WriteHeader(http.StatusMovedPermanently)
   127  				} else {
   128  					rw.WriteHeader(http.StatusOK)
   129  				}
   130  			}))
   131  			defer server.Close()
   132  			log := zap.NewNop()
   133  			conf := DefaultHTTPGunConfig()
   134  			conf.Target = server.Listener.Addr().String()
   135  			conf.Client.Redirect = tt.redirect
   136  			conf.TargetResolved = conf.Target
   137  			gun := NewHTTP1Gun(conf, log)
   138  			var aggr netsample.TestAggregator
   139  			_ = gun.Bind(&aggr, testDeps())
   140  			gun.Shoot(newAmmoURL(t, "/redirect"))
   141  
   142  			require.Equal(t, len(aggr.Samples), 1)
   143  			expectedCode := http.StatusMovedPermanently
   144  			if tt.redirect {
   145  				expectedCode = http.StatusOK
   146  			}
   147  			require.Equal(t, aggr.Samples[0].ProtoCode(), expectedCode)
   148  		})
   149  	}
   150  }
   151  
   152  func TestHTTP_notSupportHTTP2(t *testing.T) {
   153  	server := newHTTP2TestServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   154  		if isHTTP2Request(req) {
   155  			rw.WriteHeader(http.StatusForbidden)
   156  		} else {
   157  			rw.WriteHeader(http.StatusOK)
   158  		}
   159  	}))
   160  	defer server.Close()
   161  
   162  	// Test, that configured server serves HTTP2 well.
   163  	http2OnlyClient := http.Client{
   164  		Transport: &http2.Transport{
   165  			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
   166  		}}
   167  	res, err := http2OnlyClient.Get(server.URL)
   168  	require.NoError(t, err)
   169  	require.Equal(t, res.StatusCode, http.StatusForbidden)
   170  
   171  	log := zap.NewNop()
   172  	conf := DefaultHTTPGunConfig()
   173  	conf.Target = server.Listener.Addr().String()
   174  	conf.SSL = true
   175  	conf.TargetResolved = conf.Target
   176  	gun := NewHTTP1Gun(conf, log)
   177  	var results netsample.TestAggregator
   178  	_ = gun.Bind(&results, testDeps())
   179  	gun.Shoot(newAmmoURL(t, "/"))
   180  
   181  	require.Equal(t, len(results.Samples), 1)
   182  	require.Equal(t, results.Samples[0].ProtoCode(), http.StatusOK)
   183  }
   184  
   185  func TestHTTP2(t *testing.T) {
   186  	t.Run("HTTP/2 ok", func(t *testing.T) {
   187  		server := newHTTP2TestServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   188  			if isHTTP2Request(req) {
   189  				rw.WriteHeader(http.StatusOK)
   190  			} else {
   191  				rw.WriteHeader(http.StatusForbidden)
   192  			}
   193  		}))
   194  		defer server.Close()
   195  		log := zap.NewNop()
   196  		conf := DefaultHTTP2GunConfig()
   197  		conf.Target = server.Listener.Addr().String()
   198  		conf.TargetResolved = conf.Target
   199  		gun, _ := NewHTTP2Gun(conf, log)
   200  		var results netsample.TestAggregator
   201  		_ = gun.Bind(&results, testDeps())
   202  		gun.Shoot(newAmmoURL(t, "/"))
   203  		require.Equal(t, results.Samples[0].ProtoCode(), http.StatusOK)
   204  	})
   205  
   206  	t.Run("HTTP/1.1 panic", func(t *testing.T) {
   207  		server := httptest.NewTLSServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   208  			zap.S().Info("Served")
   209  		}))
   210  		defer server.Close()
   211  		log := zap.NewNop()
   212  		conf := DefaultHTTP2GunConfig()
   213  		conf.Target = server.Listener.Addr().String()
   214  		conf.TargetResolved = conf.Target
   215  		gun, _ := NewHTTP2Gun(conf, log)
   216  		var results netsample.TestAggregator
   217  		_ = gun.Bind(&results, testDeps())
   218  		var r interface{}
   219  		func() {
   220  			defer func() {
   221  				r = recover()
   222  			}()
   223  			gun.Shoot(newAmmoURL(t, "/"))
   224  		}()
   225  		require.NotNil(t, r)
   226  		require.Contains(t, r, notHTTP2PanicMsg)
   227  	})
   228  
   229  	t.Run("no SSL construction fails", func(t *testing.T) {
   230  		server := httptest.NewTLSServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   231  			zap.S().Info("Served")
   232  		}))
   233  		defer server.Close()
   234  		log := zap.NewNop()
   235  		conf := DefaultHTTP2GunConfig()
   236  		conf.Target = server.Listener.Addr().String()
   237  		conf.SSL = false
   238  		conf.TargetResolved = conf.Target
   239  		_, err := NewHTTP2Gun(conf, log)
   240  		require.Error(t, err)
   241  	})
   242  }
   243  
   244  func newAmmoURL(t *testing.T, url string) Ammo {
   245  	req, err := http.NewRequest("GET", url, nil)
   246  	require.NoError(t, err)
   247  	return newAmmoReq(t, req)
   248  }
   249  
   250  func newAmmoReq(t *testing.T, req *http.Request) Ammo {
   251  	ammo := ammomock.NewAmmo(t)
   252  	ammo.On("IsInvalid").Return(false).Once()
   253  	ammo.On("Request").Return(req, netsample.Acquire("REQUEST")).Once()
   254  	return ammo
   255  }
   256  
   257  func isHTTP2Request(req *http.Request) bool {
   258  	return checkHTTP2(req.TLS) == nil
   259  }
   260  
   261  func newHTTP2TestServer(handler http.Handler) *httptest.Server {
   262  	server := httptest.NewUnstartedServer(handler)
   263  	_ = http2.ConfigureServer(server.Config, nil)
   264  	server.TLS = server.Config.TLSConfig // StartTLS takes TLS configuration from that field.
   265  	server.StartTLS()
   266  	return server
   267  }