Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JSON targets format #300

Merged
merged 11 commits into from
Jul 10, 2018
Merged

JSON targets format #300

merged 11 commits into from
Jul 10, 2018

Conversation

tsenart
Copy link
Owner

@tsenart tsenart commented Jun 29, 2018

Background

The original targets format closely resembles plain HTTP/1.x messages. It was meant to be used by humans writing those targets directly on their terminals. With its lack of support for in-line body definitions, this ill-defined legacy format made Vegeta hard to integrate with other programs generating dynamic targets on the fly with distinct request bodies.

Extending the legacy format to support arbitrary in-line request bodies would require further deviation from the HTTP/1.x message format since, to the best of my knowledge, there is no official plain text framing format that would allow the boundaries between multiple targets with arbitrary request bodies to be easily recognized (and hence, parsed).

It could be done, certainly, but for the use case of integrating Vegeta with other programs generating targets, I think a simpler alternative JSON based format could be better.

This PR proposes just such a format and is intended to gather feedback from interested parties.

It's currently a work-in-progress, but the CLI interface is already specified and documented.

Please provide feedback: @coxx, @1u0, @mbrookes1304, @cwinters, @xla.

Related Issues and PRs

Checklist

  • Git commit messages conform to community standards.
  • Each Git commit represents meaningful milestones or atomic units of work.
  • Changed or added code is covered by appropriate tests.

tsenart added 2 commits June 29, 2018 19:25
This commit introduces the following changes in preparation for
alternative target formats:

- LazyTargeter renamed to LegacyTargeter
- EagerTargeter refactored to take in any Targeter
- `-format` flag added to the attack command
- Extended documentation of the legacy target format
@tsenart tsenart added the WIP label Jun 29, 2018
attack.go Outdated
return err

switch opts.format {
case "json":

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string json has 3 occurrences, make it a constant

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, should be constants.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I'll make some constants for these values.

@matthewbrookes
Copy link

@tsenart I like the approach taken here. I think it's much cleaner creating a new format than trying to find an ugly way to fit this feature into the legacy format

Copy link
Contributor

@xla xla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This proposal is sound to the bone. The only gripe I have is with the naming of the formats. While json communicates the expected input format, legacy implies lifecycle. There are certainly perceived limitations for the current format, yet it might serve well for a good amount of use-cases. So I'd propose to give it a name which aligns well e.g.: plain, text or something similar.

README.md Outdated

##### `legacy` format

The ill-defined legacy format almost resembles the plain-text HTTP message format but
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In here ill-defined seems self-deprecating, could be left out.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, agree there's no need to deprecate it. I'll change the wording.

attack.go Outdated
@@ -25,6 +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, json]")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Defaults could be constants.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They could. I'll make this change together with the other constant related changes.

attack.go Outdated
return err

switch opts.format {
case "json":
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, should be constants.

attack.go Outdated
tr = vegeta.NewJSONTargeter(src, body, hdr)
case "legacy":
fallthrough
default:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user should get feedback for wrong inputs, would move this up to the case where it belongs and error for everything else.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, this implementation was rather incomplete in view of getting earlier feedback. I'll add proper validation as well as tests.

README.md Outdated

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section should be expanded with the fileds of the json objects, which are mandatory and which are optional.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1. What do you think of checking in a JSON schema file for this? Is it over-kill?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a great idea, especially with such small surface,maintaining such a file will be great for documentation and potential integrations.

lib/targets.go Outdated
Method string `json:"method"`
URL string `json:"url"`
Body []byte `json:"body"`
Header http.Header `json:"header"`
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always uncertain which one is canonical Header or Headers.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An HTTP message has a Header and a Body. A Header containers many "Headers". Yes, it's confusing. What naming do you think we should stick to?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually I consult the rfc and it's consistently referring to that part of request/response as header, so all good here. :>

@tsenart tsenart mentioned this pull request Jul 5, 2018
@tsenart
Copy link
Owner Author

tsenart commented Jul 5, 2018

@xla: So, what do you prefer? text? I feel like it is too general... The format is "almost" like HTTP. Could we find something along those lines? Would calling it http be too much of a lie? What about httpish? 😺

-reporter string
Reporter [text, json, plot, hist[buckets]] (default "text")
```
### report command

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason why you have removed the explanation of report and dump command?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it was redundant. It's already included in the output of vegeta -h.

@1u0
Copy link

1u0 commented Jul 5, 2018

Imo, the drawback of proposed api is that body payloads are not in plain text (base64 encoded).
If you remove base64, but still keep json format as "envelope", then for json body payloads one needs to escape them.
Both things reduce human readability of targets, text searching.

As an alternative, maybe yaml format can be used as an "envelope", this way allowing to put unescaped body payloads.

Or, you may re-consider the idea with HTTP/1.x message format. For example, the approach used in REST client tools (with inline body payloads and ### as requests separator):

This would also make vegeta interop with those tools as a bonus.

@tsenart
Copy link
Owner Author

tsenart commented Jul 6, 2018

@1u0 Thanks for bringing that seemingly popular format to our attention! I wasn't aware of it personally. Do you know if it's more formally specified somewhere? What are its origins?

The only issue I can see is that framing scheme doesn't allow for request bodies to contain that same separator ### in them. That's one issue the Base64 encoding in the JSON format solves. How problematic do you think it could be in practice? I can imagine people writing programs to generate Vegeta targets dynamically with request body contents they don't know and hitting this potentially surprising issue.

Regarding the JSON format, it isn't clear from what you wrote why you find the need to Base64 encode the request bodies a drawback. Could you show us how it hinders you so that we understand your context better?

@1u0
Copy link

1u0 commented Jul 6, 2018

Regarding the JSON format, it isn't clear from what you wrote why you find the need to Base64 encode the request bodies a drawback.

A potential use case can be that payload was generated (persisted into a file) and passed to vegeta. But later on, the payload needs to be manually analysed/checked. With plain text, one may just use common tools (like grep, less) to read and search the payload file.
With your proposal it should be also possible to achieve this, for example by adding jq to "unwrap" the payload. It's just a small inconvenience compared with "plain text" approach.

About the .http file format:

Do you know if it's more formally specified somewhere? What are its origins?

I'm not aware about any formal specification of it.
My guess that JetBrains came up with this format in their (proprietary) plugin, also inspired by HTTP message format (they introduced it in their blog posts). The plugin for VS Code, looks like, tries to follow the semantics as close as possible to one in IntelliJ IDEA.

The only issue I can see is that framing scheme doesn't allow for request bodies to contain that same separator ### in them.

True, but this is limited only at the start of new lines. There is also a potentially conflicting "< -rule" to include request body from external file. Both patterns can conflict with real payload.
Imo, it's a design tradeoff.

@tsenart
Copy link
Owner Author

tsenart commented Jul 6, 2018

A potential use case can be that payload was generated (persisted into a file) and passed to vegeta. But later on, the payload needs to be manually analysed/checked. With plain text, one may just use common tools (like grep, less) to read and search the payload file.
With your proposal it should be also possible to achieve this, for example by adding jq to "unwrap" the payload. It's just a small inconvenience compared with "plain text" approach.

You don't need jq to unwrap the payload. Most UNIX environments have a dedicated tool for that. In macOS, that's called base64. I understand your nuisance, but I think it's a minor one, and these tools are easy to use and compose in the terminal. Additionally, if you have a program generating targets, any programming language has a base64 module that's easy to use.

True, but this is limited only at the start of new lines. There is also a potentially conflicting "<-rule" to include request body from external file. Both patterns can conflict with real payload.
Imo, it's a design tradeoff.

Yeah, indeed a tradeoff. I suppose if it's well documented, then it shouldn't be a problem for people. So I'd be on-board with also adding this functionality to the HTTP-like format, but I'm wondering if breaking back-wards compatibility in this particular case is worth it. I'm not very sure. What's your take @xla?

@xla
Copy link
Contributor

xla commented Jul 6, 2018

@xla: So, what do you prefer? text? I feel like it is too general... The format is "almost" like HTTP. Could we find something along those lines? Would calling it http be too much of a lie? What about httpish?

Ha! httpy maybe. 👹 I think http is fine as long as the caveats are well-documented.

@xla
Copy link
Contributor

xla commented Jul 6, 2018

Yeah, indeed a tradeoff. I suppose if it's well documented, then it shouldn't be a problem for people. So I'd be on-board with also adding this functionality to the HTTP-like format, but I'm wondering if breaking back-wards compatibility in this particular case is worth it. I'm not very sure. What's your take @xla?

I'd focus on the proposed change for the json format at first and keep the additonal changes to the existing format as potential improvement for the backlog and evaluate in depth if the change is indeed breaking.

The disucssion around encoding of payload bodies seems moot as any sensible human interaction with formats like json needs specific tooling, as much as it is necessary to interact with large amounts of newline delimited plain text. It should not so much inform the design of the implementation as it seems reasonable to assume that the majoirty of interactions with the json definitions come from machine owned pipelines, basically supporting your claim @tsenart that json targets are generated for the most part.

@tsenart
Copy link
Owner Author

tsenart commented Jul 6, 2018

I'd focus on the proposed change for the json format at first and keep the additonal changes to the existing format as potential improvement for the backlog and evaluate in depth if the change is indeed breaking.

+1

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

fs.Parse(os.Args[1:])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error return value of fs.Parse is not checked

@tsenart tsenart assigned tsenart and xla and unassigned tsenart Jul 7, 2018
@tsenart
Copy link
Owner Author

tsenart commented Jul 7, 2018

@xla: Assigned to you. PTAL again.

@xla xla removed the WIP label Jul 10, 2018
Copy link
Contributor

@xla xla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 :octocat: :shipit: 🍡

Minor style nits. :>

@@ -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, ", ")))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this breaking odd, it doesn't give consistent guidance how to break long lines and makes it hard to edit. Might be preferrable to use the following rule:

func myFunc(
	param1,
	param2,
	param3,
)

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's discuss this today in our pairing session.

case vegeta.HTTPTargetFormat:
tr = vegeta.NewHTTPTargeter(src, body, hdr)
default:
return fmt.Errorf("format %q isn't one of [%s]",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

case t == nil || other == nil:
return false
default:
equal := t.Method == other.Method &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guess this is for readability, would argue just as hard to read in the if statement itself.

@xla xla assigned tsenart and unassigned xla Jul 10, 2018
@tsenart tsenart merged commit 966dd85 into master Jul 10, 2018
@tsenart tsenart deleted the json-targets branch July 10, 2018 08:29
@tsenart
Copy link
Owner Author

tsenart commented Jul 10, 2018

This feature has been looooooong in the making. Since #148, namely. Feels good to make progress again :-)

@tsenart
Copy link
Owner Author

tsenart commented Jul 10, 2018

By feature, I mean in-line response bodies which JSON targets make easy peasy.

@omjego
Copy link

omjego commented Apr 23, 2020

Hey @tsenart , what's the status of this feature? Is it merged already?

@tsenart
Copy link
Owner Author

tsenart commented Apr 23, 2020 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants