Skip to content

Commit

Permalink
remove fasthttp2 due to instability and number of bugs. new feature o…
Browse files Browse the repository at this point in the history
…f parallel reqs with http/2 per connection as http/2 can send multiple reqs on same conn using streams
  • Loading branch information
dominicriordan committed Dec 2, 2023
1 parent 364a7d9 commit 9fdf5b1
Show file tree
Hide file tree
Showing 19 changed files with 385 additions and 149 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ gopayloader-darwin-amd64
gopayloader-linux-amd64
gopayloader-windows-amd64.exe
*.tar.gz
build
build
go.work
main2.go
go.work.sum
16 changes: 7 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/domsolutions/gopayloader)](https://goreportcard.com/report/github.com/domsolutions/gopayloader)
[![GoDoc](https://godoc.org/github.com/domsolutions/gopayloader?status.svg)](http://godoc.org/github.com/domsolutions/gopayloader)

Gopayloader is an HTTP/S benchmarking tool. Inspired by [bombardier](https://github.com/codesenberg/bombardier/) it also uses [fasthttp](https://github.com/valyala/fasthttp) which allows for fast creation and sending of requests due to low allocations and lots of other improvements. But with
added improvement of also supporting fashttp for HTTP/2.
Gopayloader is an HTTP/S benchmarking tool. Inspired by [bombardier](https://github.com/codesenberg/bombardier/) it also uses [fasthttp](https://github.com/valyala/fasthttp) which allows for fast creation and sending of requests due to low allocations and lots of other improvements.
It uses this client by default, a different client can be used with `--client` flag.

Supports all HTTP versions, using [quic-go](https://github.com/quic-go/quic-go) for HTTP/3 client with `--client nethttp-3`. For HTTP/2 can use fasthttp with `--client fasthttp-2` or standard core golang `net/http` with `--client nethttp`
Supports all HTTP versions, using [quic-go](https://github.com/quic-go/quic-go) for HTTP/3 client with `--client nethttp3`. For HTTP/2 can use with `--client nethttp2`. By default uses fasthttp HTTP/1.1 client.

Supports ability to generate custom JWTs to send in headers with payload (only limited by HDD size). This can be useful if the service being
tested is JWT authenticated. Each JWT generated will be unique as contains a unique `jti` in claims i.e.
Expand Down Expand Up @@ -68,8 +67,6 @@ achieved mean RPS of **53,098**
To list all available flags run;

```shell
./gopayloader run --help

Load test HTTP/S server - supports HTTP/1.1 HTTP/2 HTTP/3

Usage:
Expand All @@ -78,10 +75,10 @@ Usage:
Flags:
-b, --body string request body
--body-file string read request body from file
--client string fasthttp-1 for fast http/1.1 requests
fasthttp-2 for fast http/2 requests
nethttp for standard net/http requests supporting http/1.1 http/2
nethttp-3 for standard net/http requests supporting http/3 using quic-go (default "fasthttp-1")
--client string fasthttp for fast http/1.1 requests
nethttp for standard net/http requests using http/1.1
nethttp2 for standard net/http requests using http/2
nethttp3 for standard net/http requests supporting http/3 using quic-go (default "fasthttp")
-c, --connections uint Number of simultaneous connections (default 1)
-k, --disable-keep-alive Disable keep-alive connections
-H, --headers strings headers to send in request, can have multiple i.e -H 'content-type:application/json' -H' connection:close'
Expand All @@ -97,6 +94,7 @@ Flags:
-m, --method string request method (default "GET")
--mtls-cert string mTLS cert path
--mtls-key string mTLS cert private key path
--parallel Sends reqs in parallel per connection with HTTP/2
--read-timeout duration Read timeout (default 5s)
-r, --requests int Number of requests
--skip-verify Skip verify SSL cert signer
Expand Down
14 changes: 9 additions & 5 deletions cmd/payloader/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const (
argBody = "body"
argBodyFile = "body-file"
argClient = "client"
argParallel = "parallel"
)

var (
Expand Down Expand Up @@ -60,6 +61,7 @@ var (
headers *[]string
body string
bodyFile string
parallel bool
)

var runCmd = &cobra.Command{
Expand Down Expand Up @@ -98,19 +100,21 @@ var runCmd = &cobra.Command{
*headers,
body,
bodyFile,
client)
client,
parallel)
},
}

func init() {
runCmd.Flags().Int64VarP(&reqs, argRequests, "r", 0, "Number of requests")
runCmd.Flags().UintVarP(&conns, argConnections, "c", 1, "Number of simultaneous connections")
runCmd.Flags().BoolVarP(&disableKeepAlive, argKeepAlive, "k", false, "Disable keep-alive connections")
runCmd.Flags().BoolVar(&parallel, argParallel, false, "Sends reqs in parallel per connection with HTTP/2")

runCmd.Flags().BoolVar(&skipVerify, argVerifySigner, false, "Skip verify SSL cert signer")
runCmd.Flags().DurationVarP(&duration, argTime, "t", 0, "Execution time window, if used with -r will uniformly distribute reqs within time window, without -r reqs are unlimited")
runCmd.Flags().DurationVar(&readTimeout, argReadTimeout, 5*time.Second, "Read timeout")
runCmd.Flags().DurationVar(&writeTimeout, argWriteTimeout, 5*time.Second, "Write timeout")
runCmd.Flags().DurationVar(&readTimeout, argReadTimeout, 10*time.Second, "Read timeout")
runCmd.Flags().DurationVar(&writeTimeout, argWriteTimeout, 10*time.Second, "Write timeout")
runCmd.Flags().StringVarP(&method, argMethod, "m", "GET", "request method")
runCmd.Flags().StringVarP(&body, argBody, "b", "", "request body")
runCmd.Flags().StringVar(&bodyFile, argBodyFile, "", "read request body from file")
Expand All @@ -121,8 +125,8 @@ func init() {
runCmd.Flags().StringVar(&mTLSKey, argMTLSKey, "", "mTLS cert private key path")

runCmd.Flags().StringVar(&client, argClient, worker.HttpClientFastHTTP1, worker.HttpClientFastHTTP1+` for fast http/1.1 requests
`+worker.HttpClientFastHTTP2+` for fast http/2 requests
`+worker.HttpClientNetHTTP+` for standard net/http requests supporting http/1.1 http/2
`+worker.HttpClientNetHTTP+` for standard net/http requests using http/1.1
`+worker.HttpClientNetHTTP2+` for standard net/http requests using http/2
`+worker.HttpClientNetHTTP3+` for standard net/http requests supporting http/3 using quic-go`)

runCmd.Flags().StringVar(&jwtKID, argJWTKid, "", "JWT KID")
Expand Down
110 changes: 103 additions & 7 deletions cmd/payloader/test-server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ package payloader

import (
"bufio"
"context"
"crypto/tls"
"errors"
"github.com/domsolutions/http2"
"github.com/quic-go/quic-go"
httpv3server "github.com/quic-go/quic-go/http3"
"github.com/spf13/cobra"
"github.com/valyala/fasthttp"
golanghttp2 "golang.org/x/net/http2"
"log"
"net"
"net/http"
"os"
"os/signal"
Expand All @@ -23,6 +27,7 @@ var (
port int
responseSize int
fasthttp1 bool
fasthttp2 bool
nethttp2 bool
httpv3 bool
debug bool
Expand All @@ -31,6 +36,8 @@ var (
var (
serverCert string
privateKey string
crt []byte
key []byte
)

func init() {
Expand All @@ -43,12 +50,13 @@ func init() {
}

func tlsConfig() *tls.Config {
crt, err := os.ReadFile(serverCert)
var err error
crt, err = os.ReadFile(serverCert)
if err != nil {
log.Fatal(err)
}

key, err := os.ReadFile(privateKey)
key, err = os.ReadFile(privateKey)
if err != nil {
log.Fatal(err)
}
Expand Down Expand Up @@ -103,20 +111,85 @@ var runServerCmd = &cobra.Command{
select {
case <-c:
log.Println("User cancelled, shutting down")
server.Shutdown()
case err := <-errs:
log.Printf("Got error from server; %v \n", err)
}

server.Shutdown()
return nil
}

if fasthttp2 {
var err error

server := fasthttp.Server{
ErrorHandler: func(c *fasthttp.RequestCtx, err error) {
log.Println(err)
c.WriteString(err.Error())
},
Handler: func(c *fasthttp.RequestCtx) {
_, err = c.WriteString(response)
if err != nil {
log.Println(err)
}
if debug {
log.Printf("%s\n", c.Request.Header.String())
log.Printf("%s\n", c.Request.Body())
}
},
}

tlsConfig()
err = server.AppendCertEmbed(crt, key)
if err != nil {
log.Fatalln(err)
}

http2.ConfigureServer(&server, http2.ServerConfig{
Debug: debug,
})

errs := make(chan error)
go func() {
if err := server.ListenAndServeTLSEmbed(addr, crt, key); err != nil {
log.Println(err)
errs <- err
}
}()

c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)

select {
case <-c:
log.Println("User cancelled, shutting down")
case err := <-errs:
log.Printf("Got error from server; %v \n", err)
}

server.Shutdown()
return nil
}

if nethttp2 {
server := &http.Server{
Addr: addr,
ReadTimeout: 5 * time.Second,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
TLSConfig: tlsConfig(),
ConnState: func(c net.Conn, s http.ConnState) {
if !debug {
return
}
switch s {
case http.StateNew:
log.Println("NEW conn")
case http.StateClosed:
log.Println("CLOSED conn")
case http.StateHijacked:
log.Println("HIJACKED conn")
}
},
}
var err error

Expand All @@ -126,13 +199,35 @@ var runServerCmd = &cobra.Command{
log.Println(err)
}
if debug {
log.Printf("%+v\n", r.Header.Get("Some-Jwt"))
log.Printf("%+v\n", r.Header)
log.Printf("%+v\n", r.Body)
}
})

if err := server.ListenAndServeTLS("", ""); err != nil {
log.Fatal(err)
err = golanghttp2.ConfigureServer(server, &golanghttp2.Server{})
if err != nil {
return err
}

errs := make(chan error)
go func() {
if err := server.ListenAndServeTLS(serverCert, privateKey); err != nil {
errs <- err
}
}()

c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)

select {
case <-c:
log.Println("User cancelled, shutting down")
case err := <-errs:
log.Printf("Got error from server; %v \n", err)
}

server.Shutdown(context.Background())
return nil
}

if httpv3 {
Expand Down Expand Up @@ -172,6 +267,7 @@ func init() {
runServerCmd.Flags().IntVarP(&port, "port", "p", 8080, "Port")
runServerCmd.Flags().IntVarP(&responseSize, "response-size", "s", 10, "Response size")
runServerCmd.Flags().BoolVar(&fasthttp1, "fasthttp-1", false, "Fasthttp HTTP/1.1 server")
runServerCmd.Flags().BoolVar(&fasthttp2, "fasthttp-2", false, "Fasthttp HTTP/2 server")
runServerCmd.Flags().BoolVar(&nethttp2, "netHTTP-2", false, "net/http HTTP/2 server")
runServerCmd.Flags().BoolVar(&httpv3, "http-3", false, "HTTP/3 server")
runServerCmd.Flags().BoolVarP(&debug, "verbose", "v", false, "print logs")
Expand Down
11 changes: 9 additions & 2 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@ package config

import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/domsolutions/gopayloader/pkgs/payloader/worker"
"net/url"
"os"
"regexp"
"strings"
"time"
"encoding/json"
)

type Config struct {
Expand Down Expand Up @@ -40,9 +41,10 @@ type Config struct {
Body string
BodyFile string
Client string
Parallel bool
}

func NewConfig(ctx context.Context, reqURI, mTLScert, mTLSKey string, disableKeepAlive bool, reqs int64, conns uint, totalTime time.Duration, skipVerify bool, readTimeout, writeTimeout time.Duration, method string, verbose bool, ticker time.Duration, jwtKID, jwtKey, jwtSub, jwtCustomClaimsJSON, jwtIss, jwtAud, jwtHeader, jwtsFilename string, headers []string, body, bodyFile string, client string) *Config {
func NewConfig(ctx context.Context, reqURI, mTLScert, mTLSKey string, disableKeepAlive bool, reqs int64, conns uint, totalTime time.Duration, skipVerify bool, readTimeout, writeTimeout time.Duration, method string, verbose bool, ticker time.Duration, jwtKID, jwtKey, jwtSub, jwtCustomClaimsJSON, jwtIss, jwtAud, jwtHeader, jwtsFilename string, headers []string, body, bodyFile string, client string, parallel bool) *Config {
return &Config{
Ctx: ctx,
ReqURI: reqURI,
Expand Down Expand Up @@ -70,6 +72,7 @@ func NewConfig(ctx context.Context, reqURI, mTLScert, mTLSKey string, disableKee
Body: body,
BodyFile: bodyFile,
Client: client,
Parallel: parallel,
}
}

Expand Down Expand Up @@ -197,6 +200,10 @@ func (c *Config) Validate() error {
}
}

if c.Parallel && c.Client != worker.HttpClientNetHTTP2 {
return fmt.Errorf("can only run parallel with %s client", worker.HttpClientNetHTTP2)
}

if c.VerboseTicker == 0 {
return errors.New("ticker value can't be zero")
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ require (
github.com/quic-go/quic-go v0.40.0
github.com/spf13/cobra v1.8.0
github.com/valyala/fasthttp v1.51.0
golang.org/x/net v0.17.0
golang.org/x/text v0.14.0
)

Expand Down Expand Up @@ -39,7 +40,6 @@ require (
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
golang.org/x/mod v0.11.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/term v0.13.0 // indirect
golang.org/x/tools v0.10.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions pkgs/http-clients/definitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type GoPayLoaderClient interface {
NewReq(method, url string) (Request, error)
NewResponse() Response
CloseConns()
HTTP2() bool
}

type Config struct {
Expand All @@ -49,6 +50,7 @@ type Config struct {
HTTPV3 bool
ReqStats chan<- time.Duration
Client string
Parallel bool
}

func (c *Config) ReqLimitedOnly() bool {
Expand Down
Loading

0 comments on commit 9fdf5b1

Please sign in to comment.