mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-25 17:44:02 +08:00
feat: support HTTP/2.0
This commit is contained in:
@@ -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**
|
||||
|
||||
|
||||
1
go.mod
1
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
|
||||
)
|
||||
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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},
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
54
hrp/tests/protocol_test.go
Normal file
54
hrp/tests/protocol_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user