Skip to content

Instantly share code, notes, and snippets.

@bittrance
Last active January 15, 2020 21:21
Show Gist options
  • Save bittrance/6aef4d469e9e2e2854762a26cacf99c5 to your computer and use it in GitHub Desktop.
Save bittrance/6aef4d469e9e2e2854762a26cacf99c5 to your computer and use it in GitHub Desktop.
README draft for phat - a JSON-centric HTTP streaming tool

phat - a JSON-centric HTTP request stream tool

Think curl, parallel an jq rolled into one tool.

Getting started

phat is an HTTP client:

$ phat https://ze.api/users
[{"user": "bittrance"}, {"user": "harold"}]
$ echo '{"user": "rookie"}' | phat --method=POST https://ze.api/users
{"result": "success"}

phat is a load testing tool

$ phat --interval=10ms --jitter=5ms --report-interval=1m https://ze.api/users
{
  "timestamp": "2020-01-10 17:02:00+01:00",
  "total_calls": 59998,
  "results": [
    {
      "status": 200,
      "calls": 59758,
      "ratio": 0.996,
      "median_latency": 17.2,
      "p95_latency": 40.9,
      "p99_latency_ms": 80.1,
      max_latency_ms=250
    },
    {
      "status": 501,
      "calls": 240,
      "ration": 0.004,
      "median_latency": 17.2
      "p95_latency": 40.9,
      "p99_latency_ms": 80.1,
      "max_latency_ms": 250
    }
  ]
}

phat is a REST API verification tool.

$ phat --verify-body='.user == "bittrance"' https://ze.api/users/harold
body '{"user": "harold"}' does not match '.user == "bittrance"'

phat can transform HTTP results and create streams from arrays:

$ phat --transform-body '.[]' https://ze.api/users
{"user": "bittrance"}
{"user": "harold"}

phat is an HTTP streaming tool

$ phat --transform-body '.[]' https://ze.api/users | \
  phat --smaple '0.01' --body '{"username": ${body.user}, "role": "admin"}' https://other.api/users/new

Command-line invocation

phat can be invoked as a cli tool to construct simple pipelines where each operator occur at most once, in a fixed order.

  1. sequence | file
  2. shaping
  3. request
  4. filter
  5. verify
  6. transform

Operators are described below. See above for example invocations. By default, phat will exit non-zero if any response event with status >= 400 (or marked as invalid by the verify operator) reaches the end of the pipeline.

Script-based invocation

It is also possible to invoke phat as a script engine with a YAML-based scripting language which adheres closely to the cli options. The CLI-based invocation works well for ad-hoc use cases, but when used in a formal role, e.g. to automate testing, it is desirable to have an invocation format more amenable to version control.

This format is also able create pipelines from arbitrary sequences of operators, even sequences that are not possible with cli options. This is particularly pertinent when chaining requests (e.g. reading entities from one call and using the resulting events to make further calls) since the shell pipe is likely to be a bottleneck.

Translating the streaming example from above into YAML will yield significant performance improvement for a large dataset:

pipeline:
  - operator: request
    url: https://ze.api/users
  - operator: shaping
    sample: 0.01
  - operator: transform
    body: '.[]'
  - operator: request
    method: post
    url: https://other.api/users/new
    body:
      username: ${body.user}
      role: admin

The operators

At heart, phat is a collection of operators which can be strung together to form a pipeline. Operators have an input event type and an output event type. The main ones are Request which is a set of HTTP headers and a request body, and Response which is an HTTP answer, with a status code, a set of HTTP headers, metrics and a response body. A Response event will be automatically converted into a Request event as necessary by discarding its status and headers.

The sequence operator [nil -> Request]

The sequence operator generates a stream of input events with JSON {"seq": <int>} with a sequence number starting with zero. The sequence operator is a source and will conflict with other source operators.

--sequence

The file operator [bytes -> Request]

The file operator reads a byte stream from file or stdin and emits a stream of "input" events. The file operator is a source and will conflict with other source operators.

--file=<ndjson file>

The shaping operator [Request -> Request]

The shaping operator provides various ways to sample the stream of input requests. Unlike other operators, the shaping operator has an internal queue and may not immediately propagate back pressure.

--sample=0.00-1
--count=<integer>
--interval=<float>
--jitter=<float>

The request operator [Request -> Response]

This operator performs one HTTP request for each input entry, up to a configurable maximum number of concurrent requests.

--max-concurrent=<integer>

<url>
--body=<template>
--header=<template>
--method={GET|POST|PUT|HEAD|DELETE|PATCH}

The request operator also provides a way to perform dry-runs. If any of the mock options are included, the request operator will not perform an actual HTTP request, but will instead generate an response event from the data provided by those mock options.

--mock-status=<HTTP status code>
--mock-header=<template>
--mock-body=<template>

The filter operator [Response -> Response]

The filter operator can be used to discard response events if they match various criteria. Non-matching events will be let through.

--filter-status=<HTTP status code>
--filter-header=
--filter-body=<JMESPath>

The verify operator [Response -> Response]

The verify operator will mark an event as invalid if any of its options does not match that event.

--verify-status=<HTTP status code>
--verify-header=
--verify-body=<JMESPath>

The annotate operator [Response -> Response]

The annotate operator creates a new response event which inherits status, headers and metrics from the source event, and whose body is effectively a serialized version of the source response event. This is useful to produce raw data for further statistical analysis.

The report operator [Response -> Response]

The report operator collects response events and periodically emits a new event whose body produces a statistical summary of the collected responses. It can also be instructed to send the summaries somewhere else (e.g. stderr) in which case it will propagate the original response events untouched.

--report-interval=<float>
--report-output=<file>

The transform operator [Response -> Response]

The transform operator can be used to transform the JSON body of a response event using a JMES expression. Note that the request operator can use templating to extract parts of a JSON body from a previous step in the pipeline, so the transform operator is primarily useful for transforming the body at the end of the pipeline.

--transform-body=<JMESPath>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment