Skip to content

Commit

Permalink
attack: Implement JSON target format
Browse files Browse the repository at this point in the history
  • Loading branch information
tsenart committed Jun 29, 2018
1 parent ed8efd7 commit 50ef463
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 6 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ attack command:
-duration duration
Duration of the test [0 = forever]
-format string
Targets format [legacy] (default "legacy")
Targets format [legacy, json] (default "legacy")
-h2c
Send HTTP/2 requests without TLS encryption
-header value
Expand Down Expand Up @@ -139,6 +139,16 @@ 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. If present, the body field must be base64 encoded.

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

##### `legacy` format

The ill-defined legacy format almost resembles the plain-text HTTP message format but
Expand Down
6 changes: 5 additions & 1 deletion attack.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func attackCmd() command {

fs.StringVar(&opts.name, "name", "", "Attack name")
fs.StringVar(&opts.targetsf, "targets", "stdin", "Targets file")
fs.StringVar(&opts.format, "format", "legacy", "Targets format [legacy]")
fs.StringVar(&opts.format, "format", "legacy", "Targets format [legacy, json]")
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 @@ -115,7 +115,11 @@ func attack(opts *attackOpts) (err error) {
)

switch opts.format {
case "json":
tr = vegeta.NewJSONTargeter(src, body, hdr)
case "legacy":
fallthrough
default:
tr = vegeta.NewLegacyTargeter(src, body, hdr)
}

Expand Down
47 changes: 43 additions & 4 deletions lib/targets.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package vegeta
import (
"bufio"
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
Expand All @@ -17,10 +18,10 @@ import (

// Target is an HTTP request blueprint.
type Target struct {
Method string
URL string
Body []byte
Header http.Header
Method string `json:"method"`
URL string `json:"url"`
Body []byte `json:"body"`
Header http.Header `json:"header"`
}

// Request creates an *http.Request out of Target and returns it along with an
Expand Down Expand Up @@ -51,6 +52,44 @@ var (
// Implementations must be safe for concurrent use.
type Targeter func(*Target) error

// NewJSONTargeter returns a new targeter that decodes one Target from the
// given io.Reader on every invocation. Each target is one JSON object in its own line.
// The body field of each target must be base64 encoded.
//
// {"method":"POST", "url":"https://goku/1", "header":{"Content-Type":["text/plain"], "body": "Rk9P"}
// {"method":"GET", "url":"https://goku/2"}
//
// body will be set as the Target's body if no body is provided in each target definiton.
// hdr will be merged with the each Target's headers.
//
func NewJSONTargeter(src io.Reader, body []byte, header http.Header) Targeter {
type decoder struct {
*json.Decoder
sync.Mutex
}
dec := decoder{Decoder: json.NewDecoder(src)}

return func(tgt *Target) (err error) {
if tgt == nil {
return ErrNilTarget
}

dec.Lock()
defer dec.Unlock()

if err = dec.Decode(tgt); err == nil {
return nil
}

switch err {
case io.EOF:
return ErrNoTargets
default:
return err
}
}
}

// NewStaticTargeter returns a Targeter which round-robins over the passed
// Targets.
func NewStaticTargeter(tgts ...Target) Targeter {
Expand Down

0 comments on commit 50ef463

Please sign in to comment.