Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exp/refactoring http #183

Closed
wants to merge 11 commits into from
29 changes: 20 additions & 9 deletions components/guns/http/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import (
"net/url"

"github.com/pkg/errors"
"go.uber.org/zap"

"github.com/yandex/pandora/core"
"github.com/yandex/pandora/core/aggregator/netsample"
"go.uber.org/zap"
"github.com/yandex/pandora/core/warmup"
)

const (
Expand Down Expand Up @@ -48,17 +50,17 @@ type HTTPTraceConfig struct {

func DefaultBaseGunConfig() BaseGunConfig {
return BaseGunConfig{
AutoTagConfig{
AutoTag: AutoTagConfig{
Enabled: false,
URIElements: 2,
NoTagOnly: true,
},
AnswLogConfig{
AnswLog: AnswLogConfig{
Enabled: false,
Path: "answ.log",
Filter: "error",
},
HTTPTraceConfig{
HTTPTrace: HTTPTraceConfig{
DumpEnabled: false,
TraceEnabled: false,
},
Expand All @@ -68,17 +70,26 @@ func DefaultBaseGunConfig() BaseGunConfig {
type BaseGun struct {
DebugLog bool // Automaticaly set in Bind if Log accepts debug messages.
Config BaseGunConfig
Do func(r *http.Request) (*http.Response, error) // Required.
Connect func(ctx context.Context) error // Optional hook.
OnClose func() error // Optional. Called on Close().
Aggregator netsample.Aggregator // Lazy set via BindResultTo.
Connect func(ctx context.Context) error // Optional hook.
OnClose func() error // Optional. Called on Close().
Aggregator netsample.Aggregator // Lazy set via BindResultTo.
AnswLog *zap.Logger

scheme string
hostname string
targetResolved string
client Client

core.GunDeps
}

var _ Gun = (*BaseGun)(nil)
var _ io.Closer = (*BaseGun)(nil)

func (b *BaseGun) WarmUp(_ *warmup.Options) (any, error) {
return nil, nil
}

func (b *BaseGun) Bind(aggregator netsample.Aggregator, deps core.GunDeps) error {
log := deps.Log
if ent := log.Check(zap.DebugLevel, "Gun bind"); ent != nil {
Expand Down Expand Up @@ -157,7 +168,7 @@ func (b *BaseGun) Shoot(ammo Ammo) {
}
}
var res *http.Response
res, err = b.Do(req)
res, err = b.client.Do(req)
if b.Config.HTTPTrace.TraceEnabled && timings != nil {
sample.SetReceiveTime(timings.GetReceiveTime())
}
Expand Down
69 changes: 51 additions & 18 deletions components/guns/http/base_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,16 @@ import (
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"

ammomock "github.com/yandex/pandora/components/guns/http/mocks"
"github.com/yandex/pandora/core"
"github.com/yandex/pandora/core/aggregator/netsample"
"github.com/yandex/pandora/core/coretest"
"github.com/yandex/pandora/core/engine"
"github.com/yandex/pandora/lib/monitoring"
"github.com/yandex/pandora/lib/testutil"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)

func newLogger() *zap.Logger {
Expand Down Expand Up @@ -103,9 +104,38 @@ func (a *ammoMock) IsInvalid() bool {
return false
}

type testDecoratedClient struct {
client Client
t *testing.T
before func(req *http.Request)
after func(req *http.Request, res *http.Response, err error)
returnRes *http.Response
returnErr error
}

func (c *testDecoratedClient) Do(req *http.Request) (*http.Response, error) {
if c.before != nil {
c.before(req)
}
if c.client == nil {
return c.returnRes, c.returnErr
}
res, err := c.client.Do(req)
if c.after != nil {
c.after(req, res, err)
}
return res, err
}

func (c *testDecoratedClient) CloseIdleConnections() {
c.client.CloseIdleConnections()
}

func (s *BaseGunSuite) Test_Shoot_BeforeBindPanics() {
s.base.Do = func(*http.Request) (_ *http.Response, _ error) {
panic("should not be called")
s.base.client = &testDecoratedClient{
client: s.base.client,
before: func(req *http.Request) { panic("should not be called\"") },
after: nil,
}
am := &ammoMock{}

Expand All @@ -121,7 +151,6 @@ func (s *BaseGunSuite) Test_Shoot() {
am *ammomock.Ammo
req *http.Request
tag string
res *http.Response
sample *netsample.Sample
results *netsample.TestAggregator
shootErr error
Expand All @@ -138,11 +167,7 @@ func (s *BaseGunSuite) Test_Shoot() {
justBeforeEach := func() {
sample = netsample.Acquire(tag)
am.On("Request").Return(req, sample).Maybe()
res = &http.Response{
StatusCode: http.StatusNotFound,
Body: ioutil.NopCloser(body),
Request: req,
}

s.base.Shoot(am)
s.Require().Len(results.Samples, 1)
shootErr = results.Samples[0].Err()
Expand All @@ -152,9 +177,15 @@ func (s *BaseGunSuite) Test_Shoot() {
beforeEachDoOk := func() {
body = ioutil.NopCloser(strings.NewReader("aaaaaaa"))
s.base.AnswLog = zap.NewNop()
s.base.Do = func(doReq *http.Request) (*http.Response, error) {
s.Require().Equal(req, doReq)
return res, nil
s.base.client = &testDecoratedClient{
before: func(doReq *http.Request) {
s.Require().Equal(req, doReq)
},
returnRes: &http.Response{
StatusCode: http.StatusNotFound,
Body: ioutil.NopCloser(body),
Request: req,
},
}
}
s.Run("ammo sample sent to results", func() {
Expand All @@ -166,7 +197,7 @@ func (s *BaseGunSuite) Test_Shoot() {
s.Assert().Len(results.Samples, 1)
s.Assert().Equal(sample, results.Samples[0])
s.Assert().Equal("__EMPTY__", sample.Tags())
s.Assert().Equal(res.StatusCode, sample.ProtoCode())
s.Assert().Equal(http.StatusNotFound, sample.ProtoCode())
_ = shootErr
})

Expand Down Expand Up @@ -232,10 +263,12 @@ func (s *BaseGunSuite) Test_Shoot() {
connectCalled = true
return nil
}
oldDo := s.base.Do
s.base.Do = func(r *http.Request) (*http.Response, error) {
doCalled = true
return oldDo(r)

s.base.client = &testDecoratedClient{
client: s.base.client,
before: func(doReq *http.Request) {
doCalled = true
},
}
}
s.Run("Connect called", func() {
Expand Down
35 changes: 31 additions & 4 deletions components/guns/http/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import (
"time"

"github.com/pkg/errors"
"github.com/yandex/pandora/lib/netutil"
"go.uber.org/zap"
"golang.org/x/net/http2"

"github.com/yandex/pandora/lib/netutil"
)

//go:generate mockery --name=Client --case=underscore --inpackage --testonly
Expand All @@ -21,11 +22,14 @@ type Client interface {
}

type ClientConfig struct {
Redirect bool // When true, follow HTTP redirects.
Dialer DialerConfig `config:"dial"`
Transport TransportConfig `config:",squash"`
Redirect bool // When true, follow HTTP redirects.
Dialer DialerConfig `config:"dial"`
Transport TransportConfig `config:",squash"`
ConnectSSL bool `config:"connect-ssl"` // Defines if tunnel encrypted.
}

type clientConstructor func(clientConfig ClientConfig, target string) Client

func DefaultClientConfig() ClientConfig {
return ClientConfig{
Transport: DefaultTransportConfig(),
Expand Down Expand Up @@ -170,6 +174,29 @@ func (c *panicOnHTTP1Client) Do(req *http.Request) (*http.Response, error) {
return res, nil
}

type httpDecoratedClient struct {
client Client
scheme string
hostname string
targetResolved string
}

func (c *httpDecoratedClient) Do(req *http.Request) (*http.Response, error) {
if req.Host == "" {
req.Host = c.hostname
}

if c.targetResolved != "" {
req.URL.Host = c.targetResolved
}
req.URL.Scheme = c.scheme
return c.client.Do(req)
}

func (c *httpDecoratedClient) CloseIdleConnections() {
c.client.CloseIdleConnections()
}

func checkHTTP2(state *tls.ConnectionState) error {
if state == nil {
return errors.New("http2: non TLS connection")
Expand Down
80 changes: 31 additions & 49 deletions components/guns/http/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,70 +10,52 @@ import (
"net/url"

"github.com/pkg/errors"
"github.com/yandex/pandora/lib/netutil"
"go.uber.org/zap"
)

type ConnectGunConfig struct {
Target string `validate:"endpoint,required"`
ConnectSSL bool `config:"connect-ssl"` // Defines if tunnel encrypted.
SSL bool // As in HTTP gun, defines scheme for http requests.
Client ClientConfig `config:",squash"`
BaseGunConfig `config:",squash"`
}
"github.com/yandex/pandora/lib/netutil"
)

func NewConnectGun(conf ConnectGunConfig, answLog *zap.Logger) *ConnectGun {
func NewConnectGun(cfg HTTPGunConfig, answLog *zap.Logger) *BaseGun {
scheme := "http"
if conf.SSL {
if cfg.SSL {
scheme = "https"
}
client := newConnectClient(conf)
var g ConnectGun
g = ConnectGun{
BaseGun: BaseGun{
Config: conf.BaseGunConfig,
Do: g.Do,
OnClose: func() error {
client.CloseIdleConnections()
return nil
},
AnswLog: answLog,
client := newConnectClient(cfg.Client, cfg.Target)
wrappedClient := &httpDecoratedClient{
client: client,
scheme: scheme,
hostname: "",
targetResolved: cfg.Target,
}
return &BaseGun{
Config: cfg.Base,
OnClose: func() error {
client.CloseIdleConnections()
return nil
},
scheme: scheme,
client: client,
AnswLog: answLog,
client: wrappedClient,
}
return &g
}

type ConnectGun struct {
BaseGun
scheme string
client Client
}

var _ Gun = (*ConnectGun)(nil)

func (g *ConnectGun) Do(req *http.Request) (*http.Response, error) {
req.URL.Scheme = g.scheme
return g.client.Do(req)
}

func DefaultConnectGunConfig() ConnectGunConfig {
return ConnectGunConfig{
SSL: false,
ConnectSSL: false,
Client: DefaultClientConfig(),
func DefaultConnectGunConfig() HTTPGunConfig {
return HTTPGunConfig{
SSL: false,
Client: DefaultClientConfig(),
Base: DefaultBaseGunConfig(),
}
}

func newConnectClient(conf ConnectGunConfig) Client {
transport := NewTransport(conf.Client.Transport,
var newConnectClient clientConstructor = func(conf ClientConfig, target string) Client {
transport := NewTransport(
conf.Transport,
newConnectDialFunc(
conf.Target,
target,
conf.ConnectSSL,
NewDialer(conf.Client.Dialer),
), conf.Target)
return newClient(transport, conf.Client.Redirect)
NewDialer(conf.Dialer),
),
target)
return newClient(transport, conf.Redirect)
}

func newConnectDialFunc(target string, connectSSL bool, dialer netutil.Dialer) netutil.DialerFunc {
Expand Down
Loading
Loading