diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3f986f07..acc4e594 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -12,6 +12,7 @@ - change: lock funplugin version when creating scaffold project - fix: call referenced api/testcase with relative path - refactor: redesign `IStep` to make step extensible to support implementing new protocols and test types +- feat: support HTTP/2.0 protocol **python version** diff --git a/go.mod b/go.mod index e794a328..16067104 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/rs/zerolog v1.26.1 github.com/spf13/cobra v1.2.1 github.com/stretchr/testify v1.7.0 + golang.org/x/net v0.0.0-20220225172249-27dd8689420f gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ) diff --git a/hrp/response.go b/hrp/response.go index 37164eb0..0019991a 100644 --- a/hrp/response.go +++ b/hrp/response.go @@ -47,6 +47,7 @@ func newResponseObject(t *testing.T, parser *Parser, resp *http.Response) (*resp } respObjMeta := respObjMeta{ + Proto: resp.Proto, StatusCode: resp.StatusCode, Headers: headers, Cookies: cookies, @@ -74,6 +75,7 @@ func newResponseObject(t *testing.T, parser *Parser, resp *http.Response) (*resp } type respObjMeta struct { + Proto string `json:"proto"` StatusCode int `json:"status_code"` Headers map[string]string `json:"headers"` Cookies map[string]string `json:"cookies"` diff --git a/hrp/runner.go b/hrp/runner.go index 6847d9fd..8e7e593f 100644 --- a/hrp/runner.go +++ b/hrp/runner.go @@ -11,6 +11,7 @@ import ( "time" "github.com/rs/zerolog/log" + "golang.org/x/net/http2" "github.com/httprunner/httprunner/hrp/internal/builtin" "github.com/httprunner/httprunner/hrp/internal/sdk" @@ -31,12 +32,18 @@ func NewRunner(t *testing.T) *HRPRunner { t: t, failfast: true, // default to failfast genHTMLReport: false, - client: &http.Client{ + httpClient: &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, }, Timeout: 30 * time.Second, }, + http2Client: &http.Client{ + Transport: &http2.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + Timeout: 30 * time.Second, + }, } } @@ -47,13 +54,18 @@ type HRPRunner struct { pluginLogOn bool saveTests bool genHTMLReport bool - client *http.Client + httpClient *http.Client + http2Client *http.Client } // SetClientTransport configures transport of http client for high concurrency load testing func (r *HRPRunner) SetClientTransport(maxConns int, disableKeepAlive bool, disableCompression bool) *HRPRunner { - log.Info().Int("maxConns", maxConns).Msg("[init] SetClientTransport") - r.client.Transport = &http.Transport{ + log.Info(). + Int("maxConns", maxConns). + Bool("disableKeepAlive", disableKeepAlive). + Bool("disableCompression", disableCompression). + Msg("[init] SetClientTransport") + r.httpClient.Transport = &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, DialContext: (&net.Dialer{}).DialContext, MaxIdleConns: 0, @@ -61,6 +73,10 @@ func (r *HRPRunner) SetClientTransport(maxConns int, disableKeepAlive bool, disa DisableKeepAlives: disableKeepAlive, DisableCompression: disableCompression, } + r.http2Client.Transport = &http2.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + DisableCompression: disableCompression, + } return r } @@ -93,7 +109,7 @@ func (r *HRPRunner) SetProxyUrl(proxyUrl string) *HRPRunner { log.Error().Err(err).Str("proxyUrl", proxyUrl).Msg("[init] invalid proxyUrl") return r } - r.client.Transport = &http.Transport{ + r.httpClient.Transport = &http.Transport{ Proxy: http.ProxyURL(p), TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, } diff --git a/hrp/step_rendezvous.go b/hrp/step_rendezvous.go index 8dd57cdd..77291a36 100644 --- a/hrp/step_rendezvous.go +++ b/hrp/step_rendezvous.go @@ -86,16 +86,6 @@ func isPreRendezvousAllReleased(rendezvous *Rendezvous, testCase *TCase) bool { return true } -// Rendezvous creates a new rendezvous -func (s *StepRequest) Rendezvous(name string) *StepRendezvous { - s.step.Rendezvous = &Rendezvous{ - Name: name, - } - return &StepRendezvous{ - step: s.step, - } -} - // WithUserNumber sets the user number needed to release the current rendezvous func (s *StepRendezvous) WithUserNumber(number int64) *StepRendezvous { s.step.Rendezvous.Number = number diff --git a/hrp/step_rendezvous_test.go b/hrp/step_rendezvous_test.go index 5f9ed709..a0a49428 100644 --- a/hrp/step_rendezvous_test.go +++ b/hrp/step_rendezvous_test.go @@ -16,26 +16,26 @@ func TestRunCaseWithRendezvous(t *testing.T) { }), TestSteps: []IStep{ NewStep("test negative number"). - Rendezvous("test negative number"). + SetRendezvous("test negative number"). WithUserNumber(-1), NewStep("test overflow number"). - Rendezvous("test overflow number"). + SetRendezvous("test overflow number"). WithUserNumber(1000000), NewStep("test negative percent"). - Rendezvous("test very low percent"). + SetRendezvous("test very low percent"). WithUserPercent(-0.5), NewStep("test very low percent"). - Rendezvous("test very low percent"). + SetRendezvous("test very low percent"). WithUserPercent(0.00001), NewStep("test overflow percent"). - Rendezvous("test overflow percent"). + SetRendezvous("test overflow percent"). WithUserPercent(1.5), NewStep("test conflict params"). - Rendezvous("test conflict params"). + SetRendezvous("test conflict params"). WithUserNumber(1). WithUserPercent(0.123), NewStep("test negative timeout"). - Rendezvous("test negative timeout"). + SetRendezvous("test negative timeout"). WithTimeout(-1000), }, } diff --git a/hrp/step_request.go b/hrp/step_request.go index 372bd50e..9715b1b6 100644 --- a/hrp/step_request.go +++ b/hrp/step_request.go @@ -39,6 +39,7 @@ const ( type Request struct { Method HTTPMethod `json:"method" yaml:"method"` // required URL string `json:"url" yaml:"url"` // required + EnableHTTP2 bool `json:"enable_HTTP2,omitempty" yaml:"enableHTTP2,omitempty"` Params map[string]interface{} `json:"params,omitempty" yaml:"params,omitempty"` Headers map[string]string `json:"headers,omitempty" yaml:"headers,omitempty"` Cookies map[string]string `json:"cookies,omitempty" yaml:"cookies,omitempty"` @@ -56,17 +57,23 @@ func newRequestBuilder(parser *Parser, config *TConfig, stepRequest *Request) *r var requestMap map[string]interface{} _ = json.Unmarshal(jsonRequest, &requestMap) + request := &http.Request{ + Header: make(http.Header), + } + if stepRequest.EnableHTTP2 { + request.ProtoMajor = 2 + request.ProtoMinor = 0 + } else { + request.ProtoMajor = 1 + request.ProtoMinor = 1 + } + return &requestBuilder{ stepRequest: stepRequest, - req: &http.Request{ - Header: make(http.Header), - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - }, - config: config, - parser: parser, - requestMap: requestMap, + req: request, + config: config, + parser: parser, + requestMap: requestMap, } } @@ -318,7 +325,13 @@ func runStepRequest(r *SessionRunner, step *TStep) (stepResult *StepResult, err // do request action start := time.Now() - resp, err := r.hrpRunner.client.Do(rb.req) + var resp *http.Response + if step.Request.EnableHTTP2 { + resp, err = r.hrpRunner.http2Client.Do(rb.req) + } else { + resp, err = r.hrpRunner.httpClient.Do(rb.req) + } + stepResult.Elapsed = time.Since(start).Milliseconds() if err != nil { return stepResult, errors.Wrap(err, "do request failed") @@ -612,6 +625,16 @@ func (s *StepRequest) SetThinkTime(time float64) *StepThinkTime { } } +// SetRendezvous creates a new rendezvous +func (s *StepRequest) SetRendezvous(name string) *StepRendezvous { + s.step.Rendezvous = &Rendezvous{ + Name: name, + } + return &StepRendezvous{ + step: s.step, + } +} + // StepRequestWithOptionalArgs implements IStep interface. type StepRequestWithOptionalArgs struct { step *TStep @@ -647,6 +670,12 @@ func (s *StepRequestWithOptionalArgs) SetAuth(auth map[string]string) *StepReque return s } +// EnableHTTP2 enables HTTP/2.0 protocol +func (s *StepRequestWithOptionalArgs) EnableHTTP2() *StepRequestWithOptionalArgs { + s.step.Request.EnableHTTP2 = true + return s +} + // WithParams sets HTTP request params for current step. func (s *StepRequestWithOptionalArgs) WithParams(params map[string]interface{}) *StepRequestWithOptionalArgs { s.step.Request.Params = params diff --git a/hrp/tests/protocol_test.go b/hrp/tests/protocol_test.go new file mode 100644 index 00000000..c202ffec --- /dev/null +++ b/hrp/tests/protocol_test.go @@ -0,0 +1,54 @@ +package tests + +import ( + "testing" + + "github.com/httprunner/httprunner/hrp" +) + +func TestProtocol(t *testing.T) { + testcase := &hrp.TestCase{ + Config: hrp.NewConfig("run request with different protocol types"). + SetBaseURL("https://postman-echo.com"), + TestSteps: []hrp.IStep{ + hrp.NewStep("HTTP/1.1 get"). + GET("/get"). + WithParams(map[string]interface{}{"foo1": "foo1", "foo2": "foo2"}). + WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus"}). + Validate(). + AssertEqual("status_code", 200, "check status code"). + AssertEqual("proto", "HTTP/1.1", "check protocol type"). + AssertLengthEqual("body.args.foo1", 4, "check param foo1"), + hrp.NewStep("HTTP/1.1 post"). + POST("/post"). + WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus"}). + WithBody(map[string]interface{}{"foo1": "foo1", "foo2": "foo2"}). + Validate(). + AssertEqual("status_code", 200, "check status code"). + AssertEqual("proto", "HTTP/1.1", "check protocol type"). + AssertLengthEqual("body.json.foo1", 4, "check body foo1"), + hrp.NewStep("HTTP2.0 get"). + GET("/get"). + EnableHTTP2(). + WithParams(map[string]interface{}{"foo1": "foo1", "foo2": "foo2"}). + WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus"}). + Validate(). + AssertEqual("status_code", 200, "check status code"). + AssertEqual("proto", "HTTP/2.0", "check protocol type"). + AssertLengthEqual("body.args.foo1", 4, "check param foo1"), + hrp.NewStep("HTTP/2.0 post"). + POST("/post"). + EnableHTTP2(). + WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus"}). + WithBody(map[string]interface{}{"foo1": "foo1", "foo2": "foo2"}). + Validate(). + AssertEqual("status_code", 200, "check status code"). + AssertEqual("proto", "HTTP/2.0", "check protocol type"). + AssertLengthEqual("body.json.foo1", 4, "check body foo1"), + }, + } + err := hrp.NewRunner(t).Run(testcase) + if err != nil { + t.Fatalf("run testcase error: %v", err) + } +} diff --git a/hrp/tests/rendezvous_test.go b/hrp/tests/rendezvous_test.go index 760e99cf..ce68c2e5 100644 --- a/hrp/tests/rendezvous_test.go +++ b/hrp/tests/rendezvous_test.go @@ -6,50 +6,47 @@ import ( "github.com/httprunner/httprunner/hrp" ) -const rendezvousTestJSONPath = "rendezvous_test.json" - -var rendezvousTestcase = &hrp.TestCase{ - Config: hrp.NewConfig("run request with functions"). - SetBaseURL("https://postman-echo.com"). - WithVariables(map[string]interface{}{ - "n": 5, - "a": 12.3, - "b": 3.45, - }), - TestSteps: []hrp.IStep{ - hrp.NewStep("waiting for all users in the beginning"). - Rendezvous("rendezvous0"), - hrp.NewStep("rendezvous before get"). - Rendezvous("rendezvous1"). - WithUserNumber(50). - WithTimeout(3000), - hrp.NewStep("get with params"). - GET("/get"). - WithParams(map[string]interface{}{"foo1": "foo1", "foo2": "foo2"}). - WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus"}). - Extract(). - WithJmesPath("body.args.foo1", "varFoo1"). - Validate(). - AssertEqual("status_code", 200, "check status code"), - hrp.NewStep("rendezvous before post"). - Rendezvous("rendezvous2"). - WithUserNumber(20). - WithTimeout(2000), - hrp.NewStep("post json data with functions"). - POST("/post"). - WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus"}). - WithBody(map[string]interface{}{"foo1": "foo1", "foo2": "foo2"}). - Validate(). - AssertEqual("status_code", 200, "check status code"). - AssertLengthEqual("body.json.foo1", 4, "check args foo1"). - AssertEqual("body.json.foo2", "foo2", "check args foo2"), - hrp.NewStep("waiting for all users in the end"). - Rendezvous("rendezvous3"), - }, -} - func TestRendezvous(t *testing.T) { - err := hrp.NewRunner(t).Run(rendezvousTestcase) + testcase := &hrp.TestCase{ + Config: hrp.NewConfig("run request with rendezvous"). + SetBaseURL("https://postman-echo.com"). + WithVariables(map[string]interface{}{ + "n": 5, + "a": 12.3, + "b": 3.45, + }), + TestSteps: []hrp.IStep{ + hrp.NewStep("waiting for all users in the beginning"). + SetRendezvous("rendezvous0"), + hrp.NewStep("rendezvous before get"). + SetRendezvous("rendezvous1"). + WithUserNumber(50). + WithTimeout(3000), + hrp.NewStep("get with params"). + GET("/get"). + WithParams(map[string]interface{}{"foo1": "foo1", "foo2": "foo2"}). + WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus"}). + Extract(). + WithJmesPath("body.args.foo1", "varFoo1"). + Validate(). + AssertEqual("status_code", 200, "check status code"), + hrp.NewStep("rendezvous before post"). + SetRendezvous("rendezvous2"). + WithUserNumber(20). + WithTimeout(2000), + hrp.NewStep("post json data with functions"). + POST("/post"). + WithHeaders(map[string]string{"User-Agent": "HttpRunnerPlus"}). + WithBody(map[string]interface{}{"foo1": "foo1", "foo2": "foo2"}). + Validate(). + AssertEqual("status_code", 200, "check status code"). + AssertLengthEqual("body.json.foo1", 4, "check args foo1"). + AssertEqual("body.json.foo2", "foo2", "check args foo2"), + hrp.NewStep("waiting for all users in the end"). + SetRendezvous("rendezvous3"), + }, + } + err := hrp.NewRunner(t).Run(testcase) if err != nil { t.Fatalf("run testcase error: %v", err) }