Skip to content

Commit

Permalink
Merge pull request #300 from tsenart/json-targets
Browse files Browse the repository at this point in the history
JSON targets format
  • Loading branch information
tsenart committed Jul 10, 2018
2 parents fc5ca53 + 6de2156 commit 966dd85
Show file tree
Hide file tree
Showing 8 changed files with 447 additions and 145 deletions.
8 changes: 7 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion Makefile
Expand Up @@ -2,13 +2,17 @@ COMMIT=$(shell git rev-parse HEAD)
VERSION=$(shell git describe --tags --exact-match --always)
DATE=$(shell date +'%FT%TZ%z')

vegeta: vendor
vegeta: vendor generate
CGO_ENABLED=0 go build -v -a -tags=netgo \
-ldflags '-s -w -extldflags "-static" -X main.Version=$(VERSION) -X main.Commit=$(COMMIT) -X main.Date=$(DATE)'

clean-vegeta:
rm vegeta

generate: vendor
go install ./internal/cmd/...
go generate ./...

vendor:
dep ensure -v

Expand Down
166 changes: 70 additions & 96 deletions README.md
Expand Up @@ -47,6 +47,10 @@ attack command:
Max open idle connections per target host (default 10000)
-duration duration
Duration of the test [0 = forever]
-format string
Targets format [http, json] (default "http")
-h2c
Send HTTP/2 requests without TLS encryption
-header value
Request header
-http2
Expand Down Expand Up @@ -113,47 +117,7 @@ Specifies which profiler to enable during execution. Both *cpu* and
#### `-version`
Prints the version and exits.

### `attack`
```console
$ vegeta attack -h
Usage of vegeta attack:
-body string
Requests body file
-cert string
TLS client PEM encoded certificate file
-connections int
Max open idle connections per target host (default 10000)
-duration duration
Duration of the test [0 = forever]
-header value
Request header
-http2
Send HTTP/2 requests when supported by the server (default true)
-insecure
Ignore invalid server TLS certificates
-keepalive
Use persistent connections (default true)
-key string
TLS client PEM encoded private key file
-laddr value
Local IP address (default 0.0.0.0)
-lazy
Read targets lazily
-output string
Output file (default "stdout")
-rate uint
Requests per second (default 50)
-redirects int
Number of redirects to follow. -1 will not follow but marks as success (default 10)
-root-certs value
TLS root certificate files (comma separated list)
-targets string
Targets file (default "stdin")
-timeout duration
Requests timeout (default 30s)
-workers uint
Initial number of workers (default 10)
```
### `attack` command

#### `-body`
Specifies the file whose content will be set as the body of every
Expand All @@ -172,6 +136,69 @@ The internal concurrency structure's setup has this value as a variable.
The actual run time of the test can be longer than specified due to the
responses delay. Use 0 for an infinite attack.

#### `-format`
Specifies the targets format to decode.

##### `json` format

The JSON format makes integration with programs that produce targets dynamically easier.
Each target is one JSON object in its own line. The method and url fields are required.
If present, the body field must be base64 encoded. The generated [JSON Schema](lib/target.schema.json)
defines the format in detail.

```bash
jq -ncM '{method: "GET", url: "http://goku", body: "Punch!" | @base64, header: {"Content-Type": ["text/plain"]}}' |
vegeta attack -format=json -rate=100 | vegeta dump
```

##### `http` format

The http format almost resembles the plain-text HTTP message format defined in
[RFC 2616](https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html) but it
doesn't support in-line HTTP bodies, only references to files that are loaded and used
as request bodies (as exemplified below).

Although targets in this format can be produced by other programs, it was originally
meant to be used by people writing targets by hand for simple use cases.

Here are a few examples of valid targets files in the http format:

###### Simple targets
```
GET http://goku:9090/path/to/dragon?item=ball
GET http://user:password@goku:9090/path/to
HEAD http://goku:9090/path/to/success
```

###### Targets with custom headers
```
GET http://user:password@goku:9090/path/to
X-Account-ID: 8675309
DELETE http://goku:9090/path/to/remove
Confirmation-Token: 90215
Authorization: Token DEADBEEF
```

###### Targets with custom bodies
```
POST http://goku:9090/things
@/path/to/newthing.json
PATCH http://goku:9090/thing/71988591
@/path/to/thing-71988591.json
```

###### Targets with custom bodies and headers
```
POST http://goku:9090/things
X-Account-ID: 99
@/path/to/newthing.json
```

#### `-h2c`
Specifies that HTTP2 requests are to be sent over TCP without TLS encryption.

#### `-header`
Specifies a request header to be used in all targets defined, see `-targets`.
You can specify as many as needed by repeating the flag.
Expand Down Expand Up @@ -220,39 +247,6 @@ list. If unspecified, the default system CAs certificates will be used.
Specifies the attack targets in a line separated file, defaulting to stdin.
The format should be as follows, combining any or all of the following:

Simple targets
```
GET http://goku:9090/path/to/dragon?item=balls
GET http://user:password@goku:9090/path/to
HEAD http://goku:9090/path/to/success
```

Targets with custom headers
```
GET http://user:password@goku:9090/path/to
X-Account-ID: 8675309
DELETE http://goku:9090/path/to/remove
Confirmation-Token: 90215
Authorization: Token DEADBEEF
```

Targets with custom bodies
```
POST http://goku:9090/things
@/path/to/newthing.json
PATCH http://goku:9090/thing/71988591
@/path/to/thing-71988591.json
```

Targets with custom bodies and headers
```
POST http://goku:9090/things
X-Account-ID: 99
@/path/to/newthing.json
```

#### `-timeout`
Specifies the timeout for each request. The default is 0 which disables
timeouts.
Expand All @@ -262,17 +256,7 @@ Specifies the initial number of workers used in the attack. The actual
number of workers will increase if necessary in order to sustain the
requested rate.

### report
```console
$ vegeta report -h
Usage of vegeta report:
-inputs string
Input files (comma separated) (default "stdin")
-output string
Output file (default "stdout")
-reporter string
Reporter [text, json, plot, hist[buckets]] (default "text")
```
### report command

#### `-inputs`
Specifies the input files to generate the report of, defaulting to stdin.
Expand Down Expand Up @@ -363,17 +347,7 @@ Bucket # % Histogram
[6ms, +Inf] 4771 25.93% ###################
```

### `dump`
```console
$ vegeta dump -h
Usage of vegeta dump:
-dumper string
Dumper [json, csv] (default "json")
-inputs string
Input files (comma separated) (default "stdin")
-output string
Output file (default "stdout")
```
### `dump` command

#### `-inputs`
Specifies the input files containing attack results to be dumped. You can specify more than one (comma separated).
Expand Down
25 changes: 21 additions & 4 deletions attack.go
Expand Up @@ -11,6 +11,7 @@ import (
"net/http"
"os"
"os/signal"
"strings"
"time"

vegeta "github.com/tsenart/vegeta/lib"
Expand All @@ -25,6 +26,8 @@ func attackCmd() command {

fs.StringVar(&opts.name, "name", "", "Attack name")
fs.StringVar(&opts.targetsf, "targets", "stdin", "Targets file")
fs.StringVar(&opts.format, "format", vegeta.HTTPTargetFormat,
fmt.Sprintf("Targets format [%s]", strings.Join(vegeta.TargetFormats, ", ")))
fs.StringVar(&opts.outputf, "output", "stdout", "Output file")
fs.StringVar(&opts.bodyf, "body", "", "Requests body file")
fs.StringVar(&opts.certf, "cert", "", "TLS client PEM encoded certificate file")
Expand Down Expand Up @@ -59,6 +62,7 @@ var (
type attackOpts struct {
name string
targetsf string
format string
outputf string
bodyf string
certf string
Expand Down Expand Up @@ -111,10 +115,23 @@ func attack(opts *attackOpts) (err error) {
src = files[opts.targetsf]
hdr = opts.headers.Header
)
if opts.lazy {
tr = vegeta.NewLazyTargeter(src, body, hdr)
} else if tr, err = vegeta.NewEagerTargeter(src, body, hdr); err != nil {
return err

switch opts.format {
case vegeta.JSONTargetFormat:
tr = vegeta.NewJSONTargeter(src, body, hdr)
case vegeta.HTTPTargetFormat:
tr = vegeta.NewHTTPTargeter(src, body, hdr)
default:
return fmt.Errorf("format %q isn't one of [%s]",
opts.format, strings.Join(vegeta.TargetFormats, ", "))
}

if !opts.lazy {
targets, err := vegeta.ReadAllTargets(tr)
if err != nil {
return err
}
tr = vegeta.NewStaticTargeter(targets...)
}

out, err := file(opts.outputf, true)
Expand Down
63 changes: 63 additions & 0 deletions internal/cmd/jsonschema/main.go
@@ -0,0 +1,63 @@
package main

import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"os"
"strings"

"github.com/alecthomas/jsonschema"

vegeta "github.com/tsenart/vegeta/lib"
)

func main() {
types := map[string]interface{}{
"Target": &vegeta.Target{},
}

valid := strings.Join(keys(types), ", ")

fs := flag.NewFlagSet("jsonschema", flag.ContinueOnError)
typ := fs.String("type", "", fmt.Sprintf("Vegeta type to generate a JSON schema for [%s]", valid))
out := fs.String("output", "stdout", "Output file")

if err := fs.Parse(os.Args[1:]); err != nil {
die("%s", err)
}

t, ok := types[*typ]
if !ok {
die("invalid type %q not in [%s]", *typ, valid)
}

schema, err := json.MarshalIndent(jsonschema.Reflect(t), "", " ")
if err != nil {
die("%s", err)
}

switch *out {
case "stdout":
_, err = os.Stdout.Write(schema)
default:
err = ioutil.WriteFile(*out, schema, 0644)
}

if err != nil {
die("%s", err)
}
}

func die(s string, args ...interface{}) {
fmt.Fprintf(os.Stderr, s, args...)
os.Exit(1)
}

func keys(types map[string]interface{}) (ks []string) {
for k := range types {
ks = append(ks, k)
}
return ks
}

0 comments on commit 966dd85

Please sign in to comment.