Support --key, --cert, --insecure for TLS, and read flags from ENV
This commit is contained in:
parent
1f3de2afcf
commit
137c59bb64
|
@ -200,11 +200,13 @@ func (c *Charts) Handler(ctx *fasthttp.RequestCtx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Charts) Serve() {
|
func (c *Charts) Serve(open bool) {
|
||||||
server := fasthttp.Server{
|
server := fasthttp.Server{
|
||||||
Handler: cors.DefaultHandler().CorsMiddleware(c.Handler),
|
Handler: cors.DefaultHandler().CorsMiddleware(c.Handler),
|
||||||
}
|
}
|
||||||
go openBrowser("http://" + c.ln.Addr().String())
|
if open {
|
||||||
|
go openBrowser("http://" + c.ln.Addr().String())
|
||||||
|
}
|
||||||
_ = server.Serve(c.ln)
|
_ = server.Serve(c.ln)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
106
main.go
106
main.go
|
@ -23,6 +23,9 @@ var (
|
||||||
headers = kingpin.Flag("header", "Custom HTTP headers").Short('H').PlaceHolder("K:V").Strings()
|
headers = kingpin.Flag("header", "Custom HTTP headers").Short('H').PlaceHolder("K:V").Strings()
|
||||||
host = kingpin.Flag("host", "Host header").String()
|
host = kingpin.Flag("host", "Host header").String()
|
||||||
contentType = kingpin.Flag("content", "Content-Type header").Short('T').String()
|
contentType = kingpin.Flag("content", "Content-Type header").Short('T').String()
|
||||||
|
cert = kingpin.Flag("cert", "Path to the client's TLS Certificate").ExistingFile()
|
||||||
|
key = kingpin.Flag("key", "Path to the client's TLS Certificate Private Key").ExistingFile()
|
||||||
|
insecure = kingpin.Flag("insecure", "Controls whether a client verifies the server's certificate chain and host name").Short('k').Bool()
|
||||||
|
|
||||||
chartsListenAddr = kingpin.Flag("listen", "Listen addr to serve Web UI").Default(":18888").String()
|
chartsListenAddr = kingpin.Flag("listen", "Listen addr to serve Web UI").Default(":18888").String()
|
||||||
timeout = kingpin.Flag("timeout", "Timeout for each http request").PlaceHolder("DURATION").Duration()
|
timeout = kingpin.Flag("timeout", "Timeout for each http request").PlaceHolder("DURATION").Duration()
|
||||||
|
@ -31,27 +34,86 @@ var (
|
||||||
respReadTimeout = kingpin.Flag("resp-timeout", "Timeout for full response reading").PlaceHolder("DURATION").Duration()
|
respReadTimeout = kingpin.Flag("resp-timeout", "Timeout for full response reading").PlaceHolder("DURATION").Duration()
|
||||||
socks5 = kingpin.Flag("socks5", "Socks5 proxy").PlaceHolder("ip:port").String()
|
socks5 = kingpin.Flag("socks5", "Socks5 proxy").PlaceHolder("ip:port").String()
|
||||||
|
|
||||||
|
autoOpenBrowser = kingpin.Flag("auto-open-browser", "Specify whether auto open browser to show Web charts").Bool()
|
||||||
|
|
||||||
url = kingpin.Arg("url", "request url").Required().String()
|
url = kingpin.Arg("url", "request url").Required().String()
|
||||||
)
|
)
|
||||||
|
|
||||||
func errAndExit(msg string) {
|
func errAndExit(msg string) {
|
||||||
fmt.Fprintln(os.Stderr, msg)
|
fmt.Fprintln(os.Stderr, "plow: "+msg)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
var CompactUsageTemplate = `{{define "FormatCommand" -}}
|
||||||
kingpin.UsageTemplate(kingpin.CompactUsageTemplate).Version("1.0.0").Author("six-ddc@github")
|
{{if .FlagSummary}} {{.FlagSummary}}{{end -}}
|
||||||
kingpin.CommandLine.Help = `A high-performance HTTP benchmarking tool with real-time web UI and terminal displaying
|
{{range .Args}} {{if not .Required}}[{{end}}<{{.Name}}>{{if .Value|IsCumulative}} ...{{end}}{{if not .Required}}]{{end}}{{end -}}
|
||||||
|
{{end -}}
|
||||||
|
|
||||||
|
{{define "FormatCommandList" -}}
|
||||||
|
{{range . -}}
|
||||||
|
{{if not .Hidden -}}
|
||||||
|
{{.Depth|Indent}}{{.Name}}{{if .Default}}*{{end}}{{template "FormatCommand" .}}
|
||||||
|
{{end -}}
|
||||||
|
{{template "FormatCommandList" .Commands -}}
|
||||||
|
{{end -}}
|
||||||
|
{{end -}}
|
||||||
|
|
||||||
|
{{define "FormatUsage" -}}
|
||||||
|
{{template "FormatCommand" .}}{{if .Commands}} <command> [<args> ...]{{end}}
|
||||||
|
{{if .Help}}
|
||||||
|
{{.Help|Wrap 0 -}}
|
||||||
|
{{end -}}
|
||||||
|
|
||||||
|
{{end -}}
|
||||||
|
|
||||||
|
{{if .Context.SelectedCommand -}}
|
||||||
|
{{T "usage:"}} {{.App.Name}} {{template "FormatUsage" .Context.SelectedCommand}}
|
||||||
|
{{else -}}
|
||||||
|
{{T "usage:"}} {{.App.Name}}{{template "FormatUsage" .App}}
|
||||||
|
{{end -}}
|
||||||
|
Examples:
|
||||||
|
|
||||||
Example:
|
|
||||||
plow http://127.0.0.1:8080/ -c 20 -n 100000
|
plow http://127.0.0.1:8080/ -c 20 -n 100000
|
||||||
plow https://httpbin.org/post -c 20 -d 5m --body @file.json -T 'application/json' -m POST
|
plow https://httpbin.org/post -c 20 -d 5m --body @file.json -T 'application/json' -m POST
|
||||||
|
|
||||||
|
{{if .Context.Flags -}}
|
||||||
|
{{T "Flags:"}}
|
||||||
|
{{.Context.Flags|FlagsToTwoColumns|FormatTwoColumns}}
|
||||||
|
Flags default values also read from env PLOW_SOME_FLAG, such as PLOW_TIMEOUT=5s equals to --timeout=5s
|
||||||
|
|
||||||
|
{{end -}}
|
||||||
|
{{if .Context.Args -}}
|
||||||
|
{{T "Args:"}}
|
||||||
|
{{.Context.Args|ArgsToTwoColumns|FormatTwoColumns}}
|
||||||
|
{{end -}}
|
||||||
|
{{if .Context.SelectedCommand -}}
|
||||||
|
{{if .Context.SelectedCommand.Commands -}}
|
||||||
|
{{T "Commands:"}}
|
||||||
|
{{.Context.SelectedCommand}}
|
||||||
|
{{.Context.SelectedCommand.Commands|CommandsToTwoColumns|FormatTwoColumns}}
|
||||||
|
{{end -}}
|
||||||
|
{{else if .App.Commands -}}
|
||||||
|
{{T "Commands:"}}
|
||||||
|
{{.App.Commands|CommandsToTwoColumns|FormatTwoColumns}}
|
||||||
|
{{end -}}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
kingpin.UsageTemplate(CompactUsageTemplate).
|
||||||
|
Version("1.0.0").
|
||||||
|
Author("six-ddc@github").
|
||||||
|
Resolver(kingpin.PrefixedEnvarResolver("PLOW_", ";")).
|
||||||
|
Help = `A high-performance HTTP benchmarking tool with real-time web UI and terminal displaying`
|
||||||
kingpin.Parse()
|
kingpin.Parse()
|
||||||
|
|
||||||
if *requests >= 0 && *requests < int64(*concurrency) {
|
if *requests >= 0 && *requests < int64(*concurrency) {
|
||||||
errAndExit("requests must greater than or equal concurrency")
|
errAndExit("requests must greater than or equal concurrency")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (*cert != "" && *key == "") || (*cert == "" && *key != "") {
|
||||||
|
errAndExit("must specify cert and key at the same time")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
var bodyBytes []byte
|
var bodyBytes []byte
|
||||||
|
@ -76,11 +138,16 @@ Example:
|
||||||
}
|
}
|
||||||
|
|
||||||
clientOpt := ClientOpt{
|
clientOpt := ClientOpt{
|
||||||
url: *url,
|
url: *url,
|
||||||
method: *method,
|
method: *method,
|
||||||
headers: *headers,
|
headers: *headers,
|
||||||
bodyBytes: bodyBytes,
|
bodyBytes: bodyBytes,
|
||||||
bodyFile: bodyFile,
|
bodyFile: bodyFile,
|
||||||
|
|
||||||
|
certPath: *cert,
|
||||||
|
keyPath: *key,
|
||||||
|
insecure: *insecure,
|
||||||
|
|
||||||
maxConns: *concurrency,
|
maxConns: *concurrency,
|
||||||
doTimeout: *timeout,
|
doTimeout: *timeout,
|
||||||
readTimeout: *respReadTimeout,
|
readTimeout: *respReadTimeout,
|
||||||
|
@ -92,6 +159,13 @@ Example:
|
||||||
host: *host,
|
host: *host,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requester, err := NewRequester(*concurrency, *requests, *duration, &clientOpt)
|
||||||
|
if err != nil {
|
||||||
|
errAndExit(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// description
|
||||||
var desc string
|
var desc string
|
||||||
desc = fmt.Sprintf("Benchmarking %s", *url)
|
desc = fmt.Sprintf("Benchmarking %s", *url)
|
||||||
if *requests > 0 {
|
if *requests > 0 {
|
||||||
|
@ -103,6 +177,7 @@ Example:
|
||||||
desc += fmt.Sprintf(" using %d connection(s).", *concurrency)
|
desc += fmt.Sprintf(" using %d connection(s).", *concurrency)
|
||||||
fmt.Println(desc)
|
fmt.Println(desc)
|
||||||
|
|
||||||
|
// charts listener
|
||||||
var ln net.Listener
|
var ln net.Listener
|
||||||
if *chartsListenAddr != "" {
|
if *chartsListenAddr != "" {
|
||||||
ln, err = net.Listen("tcp", *chartsListenAddr)
|
ln, err = net.Listen("tcp", *chartsListenAddr)
|
||||||
|
@ -114,25 +189,24 @@ Example:
|
||||||
}
|
}
|
||||||
fmt.Printf("\n")
|
fmt.Printf("\n")
|
||||||
|
|
||||||
requester, err := NewRequester(*concurrency, *requests, *duration, &clientOpt)
|
// do request
|
||||||
if err != nil {
|
|
||||||
errAndExit(err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
go requester.Run()
|
go requester.Run()
|
||||||
|
|
||||||
|
// metrics collection
|
||||||
report := NewStreamReport()
|
report := NewStreamReport()
|
||||||
go report.Collect(requester.RecordChan())
|
go report.Collect(requester.RecordChan())
|
||||||
|
|
||||||
if ln != nil {
|
if ln != nil {
|
||||||
|
// serve charts data
|
||||||
charts, err := NewCharts(ln, report.Charts, desc)
|
charts, err := NewCharts(ln, report.Charts, desc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errAndExit(err.Error())
|
errAndExit(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go charts.Serve()
|
go charts.Serve(*autoOpenBrowser)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// terminal printer
|
||||||
printer := NewPrinter(*requests, *duration)
|
printer := NewPrinter(*requests, *duration)
|
||||||
printer.PrintLoop(report.Snapshot, *interval, *seconds, report.Done())
|
printer.PrintLoop(report.Snapshot, *interval, *seconds, report.Done())
|
||||||
}
|
}
|
||||||
|
|
26
requester.go
26
requester.go
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/valyala/fasthttp"
|
"github.com/valyala/fasthttp"
|
||||||
"github.com/valyala/fasthttp/fasthttpproxy"
|
"github.com/valyala/fasthttp/fasthttpproxy"
|
||||||
|
@ -112,6 +113,10 @@ type ClientOpt struct {
|
||||||
bodyBytes []byte
|
bodyBytes []byte
|
||||||
bodyFile string
|
bodyFile string
|
||||||
|
|
||||||
|
certPath string
|
||||||
|
keyPath string
|
||||||
|
insecure bool
|
||||||
|
|
||||||
maxConns int
|
maxConns int
|
||||||
doTimeout time.Duration
|
doTimeout time.Duration
|
||||||
readTimeout time.Duration
|
readTimeout time.Duration
|
||||||
|
@ -156,6 +161,21 @@ func addMissingPort(addr string, isTLS bool) string {
|
||||||
return net.JoinHostPort(addr, strconv.Itoa(port))
|
return net.JoinHostPort(addr, strconv.Itoa(port))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildTLSConfig(opt *ClientOpt) (*tls.Config, error) {
|
||||||
|
var certs []tls.Certificate
|
||||||
|
if opt.certPath != "" && opt.keyPath != "" {
|
||||||
|
c, err := tls.LoadX509KeyPair(opt.certPath, opt.keyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
certs = append(certs, c)
|
||||||
|
}
|
||||||
|
return &tls.Config{
|
||||||
|
InsecureSkipVerify: opt.insecure,
|
||||||
|
Certificates: certs,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func buildRequestClient(opt *ClientOpt, r *int64, w *int64) (*fasthttp.HostClient, *fasthttp.RequestHeader, error) {
|
func buildRequestClient(opt *ClientOpt, r *int64, w *int64) (*fasthttp.HostClient, *fasthttp.RequestHeader, error) {
|
||||||
u, err := url2.Parse(opt.url)
|
u, err := url2.Parse(opt.url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -180,6 +200,12 @@ func buildRequestClient(opt *ClientOpt, r *int64, w *int64) (*fasthttp.HostClien
|
||||||
}
|
}
|
||||||
httpClient.Dial = ThroughputInterceptorDial(httpClient.Dial, r, w)
|
httpClient.Dial = ThroughputInterceptorDial(httpClient.Dial, r, w)
|
||||||
|
|
||||||
|
tlsConfig, err := buildTLSConfig(opt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
httpClient.TLSConfig = tlsConfig
|
||||||
|
|
||||||
var requestHeader fasthttp.RequestHeader
|
var requestHeader fasthttp.RequestHeader
|
||||||
if opt.contentType != "" {
|
if opt.contentType != "" {
|
||||||
requestHeader.SetContentType(opt.contentType)
|
requestHeader.SetContentType(opt.contentType)
|
||||||
|
|
Loading…
Reference in New Issue