feat: support HTTP/2.0

This commit is contained in:
buyuxiang
2022-04-07 21:40:37 +08:00
parent 8aabf5a422
commit e6ca2ac5cd
9 changed files with 165 additions and 75 deletions

View File

@@ -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
View File

@@ -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
)

View File

@@ -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"`

View File

@@ -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},
}

View File

@@ -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

View File

@@ -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),
},
}

View File

@@ -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

View 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)
}
}

View File

@@ -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)
}