diff --git a/README.md b/README.md index 9214ba2..027f2de 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,8 @@ Features - **2 Load Testing modes**: one standard and one spread mode where URL Paths can be specified from a file (ideal if you want to hit several underlying microservices) - **CI Friendly**: Well-suited to be part of a CI pipeline step - **Flexible metrics**: Prometheus metrics (pushing metrics to Prometheus PushGateway), JSON file -- **Configurable**: Able to pass in arbitrary HTTP headers +- **Configurable**: Able to pass in arbitrary HTTP headers, able to configure the HTTP client +- **Supports GET, POST & PUT** - POST and PUT data can be defined in a file - **Cross Platform**: One single pre-built binary for Linux, Mac OSX and Windows - **Importable** - Besides the CLI tool cassowary can be imported as a module in your Go app @@ -146,6 +147,17 @@ Starting Load Test with 100000 requests using 125 concurrent users ``` +Example hitting a POST endpoint where POST json data is defined in a file: + +```bash +$ ./cassowary run -u http://localhost:8000/add-user -c 10 -n 1000 --post-file user.json + +Starting Load Test with 1000 requests using 10 concurrent users + +[ omitted for brevity ] + +``` + Example adding an HTTP header when running **cassowary** ```bash @@ -213,6 +225,10 @@ func main() { } ``` +Versioning +-------- + +Cassowary follows semantic versioning. The public library (pkg/client) may break backwards compability until it hits a stable v1.0.0 release. Contributing -------- diff --git a/cmd/cassowary/cli.go b/cmd/cassowary/cli.go index 6678440..f674997 100644 --- a/cmd/cassowary/cli.go +++ b/cmd/cassowary/cli.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "io/ioutil" "os" "strconv" @@ -66,10 +67,20 @@ func runLoadTest(c *client.Cassowary) error { return nil } +func readFile(file string) ([]byte, error) { + fileContent, err := ioutil.ReadFile(file) + if err != nil { + return []byte{}, err + } + return fileContent, nil +} + func validateCLI(c *cli.Context) error { prometheusEnabled := false var header []string + var httpMethod string + var data []byte if c.Int("concurrency") == 0 { return errConcurrencyLevel @@ -95,6 +106,24 @@ func validateCLI(c *cli.Context) error { } } + if c.String("postfile") != "" { + httpMethod = "POST" + fileData, err := readFile(c.String("postfile")) + if err != nil { + return err + } + data = fileData + } else if c.String("putfile") != "" { + httpMethod = "PUT" + fileData, err := readFile(c.String("putfile")) + if err != nil { + return err + } + data = fileData + } else { + httpMethod = "GET" + } + cass := &client.Cassowary{ FileMode: false, BaseURL: c.String("url"), @@ -107,6 +136,8 @@ func validateCLI(c *cli.Context) error { ExportMetricsFile: c.String("json-metrics-file"), DisableKeepAlive: c.Bool("disable-keep-alive"), Timeout: c.Int("timeout"), + HTTPMethod: httpMethod, + Data: data, } return runLoadTest(cass) @@ -149,6 +180,7 @@ func validateCLIFile(c *cli.Context) error { DisableKeepAlive: c.Bool("diable-keep-alive"), Timeout: c.Int("timeout"), Requests: c.Int("requests"), + HTTPMethod: "GET", } return runLoadTest(cass) @@ -197,7 +229,7 @@ func runCLI(args []string) { }, cli.StringFlag{ Name: "H, header", - Usage: "add Arbitrary header line, eg. 'Host: www.example.com'", + Usage: "add arbitrary header, eg. 'Host: www.example.com'", }, cli.BoolFlag{ Name: "F, json-metrics", @@ -209,7 +241,7 @@ func runCLI(args []string) { }, cli.BoolFlag{ Name: "disable-keep-alive", - Usage: "Use this flag to not use http keep-alive", + Usage: "use this flag to disable http keep-alive", }, }, Action: validateCLIFile, @@ -244,19 +276,27 @@ func runCLI(args []string) { }, cli.StringFlag{ Name: "H, header", - Usage: "add Arbitrary header line, eg. 'Host: www.example.com'", + Usage: "add arbitrary header, eg. 'Host: www.example.com'", }, cli.BoolFlag{ Name: "F, json-metrics", Usage: "outputs metrics to a json file by setting flag to true", }, + cli.StringFlag{ + Name: "postfile", + Usage: "file containing data to POST (content type will default to application/json)", + }, + cli.StringFlag{ + Name: "putfile", + Usage: "file containig data to PUT (content type will default to application/json)", + }, cli.StringFlag{ Name: "json-metrics-file", Usage: "outputs metrics to a custom json filepath, if json-metrics is set to true", }, cli.BoolFlag{ Name: "disable-keep-alive", - Usage: "Use this flag to not use http keep-alive", + Usage: "use this flag to disable http keep-alive", }, }, Action: validateCLI, diff --git a/pkg/client/load.go b/pkg/client/load.go index 36b5300..56a6d2d 100644 --- a/pkg/client/load.go +++ b/pkg/client/load.go @@ -1,6 +1,7 @@ package client import ( + "bytes" "context" "crypto/tls" "fmt" @@ -37,9 +38,24 @@ func (c *Cassowary) runLoadTest(outPutChan chan<- durationMetrics, workerChan ch panic(err) } } else { - request, err = http.NewRequest("GET", c.BaseURL, nil) - if err != nil { - panic(err) + switch c.HTTPMethod { + case "POST": + request, err = http.NewRequest("POST", c.BaseURL, bytes.NewBuffer(c.Data)) + request.Header.Set("Content-Type", "application/json") + if err != nil { + panic(err) + } + case "PUT": + request, err = http.NewRequest("PUT", c.BaseURL, bytes.NewBuffer(c.Data)) + request.Header.Set("Content-Type", "application/json") + if err != nil { + panic(err) + } + default: + request, err = http.NewRequest("GET", c.BaseURL, nil) + if err != nil { + panic(err) + } } } @@ -134,11 +150,11 @@ func (c *Cassowary) Coordinate() (ResultMetrics, error) { c.Client = &http.Client{ Timeout: time.Second * time.Duration(c.Timeout), Transport: &http.Transport{ - MaxIdleConns: 300, - MaxIdleConnsPerHost: 300, - MaxConnsPerHost: 300, - DisableCompression: false, - DisableKeepAlives: c.DisableKeepAlive, + //MaxIdleConns: 300, + MaxIdleConnsPerHost: 10000, + //MaxConnsPerHost: 300, + DisableCompression: false, + DisableKeepAlives: c.DisableKeepAlive, }, } diff --git a/pkg/client/types.go b/pkg/client/types.go index b141ca8..1438c3f 100644 --- a/pkg/client/types.go +++ b/pkg/client/types.go @@ -24,6 +24,8 @@ type Cassowary struct { Client *http.Client Bar *progressbar.ProgressBar Timeout int + HTTPMethod string + Data []byte } // ResultMetrics are the aggregated metrics after the load test