Merge pull request #1223 from bbx-winner/master

feat: support HTTP/2 protocol
This commit is contained in:
debugtalk
2022-04-08 09:46:21 +08:00
committed by GitHub
9 changed files with 223 additions and 96 deletions

View File

@@ -10,6 +10,7 @@
- feat: add `--profile` flag for har2case to support overwrite headers/cookies with specified yaml/json profile file
- feat: support run testcases in specified folder path, including testcases in sub folders
- feat: support HTTP/2 protocol
- change: integrate [sentry sdk][sentry sdk] for panic reporting and analysis
- change: lock funplugin version when creating scaffold project
- fix: call referenced api/testcase with relative path

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
HTTP2 bool `json:"http2,omitempty" yaml:"http2,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.HTTP2 {
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,
}
}
@@ -306,7 +313,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.HTTP2 {
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")
@@ -465,11 +478,24 @@ func (s *StepRequest) SetupHook(hook string) *StepRequest {
return s
}
// HTTP2 enables HTTP/2 protocol
func (s *StepRequest) HTTP2() *StepRequest {
s.step.Request = &Request{
HTTP2: true,
}
return s
}
// GET makes a HTTP GET request.
func (s *StepRequest) GET(url string) *StepRequestWithOptionalArgs {
s.step.Request = &Request{
Method: httpGET,
URL: url,
if s.step.Request != nil {
s.step.Request.Method = httpGET
s.step.Request.URL = url
} else {
s.step.Request = &Request{
Method: httpGET,
URL: url,
}
}
return &StepRequestWithOptionalArgs{
step: s.step,
@@ -478,9 +504,14 @@ func (s *StepRequest) GET(url string) *StepRequestWithOptionalArgs {
// HEAD makes a HTTP HEAD request.
func (s *StepRequest) HEAD(url string) *StepRequestWithOptionalArgs {
s.step.Request = &Request{
Method: httpHEAD,
URL: url,
if s.step.Request != nil {
s.step.Request.Method = httpHEAD
s.step.Request.URL = url
} else {
s.step.Request = &Request{
Method: httpHEAD,
URL: url,
}
}
return &StepRequestWithOptionalArgs{
step: s.step,
@@ -489,9 +520,14 @@ func (s *StepRequest) HEAD(url string) *StepRequestWithOptionalArgs {
// POST makes a HTTP POST request.
func (s *StepRequest) POST(url string) *StepRequestWithOptionalArgs {
s.step.Request = &Request{
Method: httpPOST,
URL: url,
if s.step.Request != nil {
s.step.Request.Method = httpPOST
s.step.Request.URL = url
} else {
s.step.Request = &Request{
Method: httpPOST,
URL: url,
}
}
return &StepRequestWithOptionalArgs{
step: s.step,
@@ -500,9 +536,14 @@ func (s *StepRequest) POST(url string) *StepRequestWithOptionalArgs {
// PUT makes a HTTP PUT request.
func (s *StepRequest) PUT(url string) *StepRequestWithOptionalArgs {
s.step.Request = &Request{
Method: httpPUT,
URL: url,
if s.step.Request != nil {
s.step.Request.Method = httpPUT
s.step.Request.URL = url
} else {
s.step.Request = &Request{
Method: httpPUT,
URL: url,
}
}
return &StepRequestWithOptionalArgs{
step: s.step,
@@ -511,9 +552,14 @@ func (s *StepRequest) PUT(url string) *StepRequestWithOptionalArgs {
// DELETE makes a HTTP DELETE request.
func (s *StepRequest) DELETE(url string) *StepRequestWithOptionalArgs {
s.step.Request = &Request{
Method: httpDELETE,
URL: url,
if s.step.Request != nil {
s.step.Request.Method = httpDELETE
s.step.Request.URL = url
} else {
s.step.Request = &Request{
Method: httpDELETE,
URL: url,
}
}
return &StepRequestWithOptionalArgs{
step: s.step,
@@ -522,9 +568,14 @@ func (s *StepRequest) DELETE(url string) *StepRequestWithOptionalArgs {
// OPTIONS makes a HTTP OPTIONS request.
func (s *StepRequest) OPTIONS(url string) *StepRequestWithOptionalArgs {
s.step.Request = &Request{
Method: httpOPTIONS,
URL: url,
if s.step.Request != nil {
s.step.Request.Method = httpOPTIONS
s.step.Request.URL = url
} else {
s.step.Request = &Request{
Method: httpOPTIONS,
URL: url,
}
}
return &StepRequestWithOptionalArgs{
step: s.step,
@@ -533,9 +584,14 @@ func (s *StepRequest) OPTIONS(url string) *StepRequestWithOptionalArgs {
// PATCH makes a HTTP PATCH request.
func (s *StepRequest) PATCH(url string) *StepRequestWithOptionalArgs {
s.step.Request = &Request{
Method: httpPATCH,
URL: url,
if s.step.Request != nil {
s.step.Request.Method = httpPATCH
s.step.Request.URL = url
} else {
s.step.Request = &Request{
Method: httpPATCH,
URL: url,
}
}
return &StepRequestWithOptionalArgs{
step: s.step,
@@ -600,6 +656,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

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("HTTP/2 get").
HTTP2().
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/2.0", "check protocol type").
AssertLengthEqual("body.args.foo1", 4, "check param foo1"),
hrp.NewStep("HTTP/2 post").
HTTP2().
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/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)
}