endpoints section

endpoints:
  - [declare: declare_subsection]
    [headers: headers]
    [body: body]
    [load_pattern: load_pattern_subsection]
    [method: method]
    [peak_load: peak_load]
    [tags: tags]
    url: template
    [provides: provides_subsection]
    [on_demand: boolean]
    [logs: logs_subsection]
    [max_parallel_requests: unsigned integer]
    [no_auto_returns: boolean]
    [request_timeout: duration]

The endpoints section declares what HTTP endpoints will be called during a test.

  • declare Optional - See the declare subsection

  • headers Optional - See headers

  • body Optional - See the body subsection

  • load_pattern Optional - See the load_pattern section

  • method Optional - A string representation for a valid HTTP method verb. Defaults to GET

  • peak_load Optional* - A template representing what the "peak load" for this endpoint should be. The term "peak load" represents how much traffic is generated for this endpoint when the load_pattern reaches 100%. A load_pattern can go higher than 100%, so a load_pattern of 200%, for example, would mean it would go double the defined peak_load. Only variables defined in the vars section can be interpolated.

    * While peak_load is marked as optional that is only true if the current endpoint has a provides_subsection, and in that case this endpoint is called only as frequently as needed to keep the buffers of the providers it feeds full.

    A valid load_pattern is a number--integer or decimal--followed by an optional space and the string "hpm" (meaning "hits per minute") or "hps" (meaning "hits per second").

    Examples:

    50hpm - 50 hits per minute

    300 hps - 300 hits per second

  • tags Optional - Key/value string/template pairs.

    Tags are a series of key/value pairs used to distinguish each endpoint. Tags can be used to include certain endpoints in a try run, and also make it possible for a single endpoint to have its results statistics aggregated in multiple groups. Because tag values are templates only tags which can be resolved statically at the beginning of a test can be used with the include flag of a try run. A reference to a provider can cause a single endpoint to have multiple groups of tags. Each one of these groups will have its own statistics in the results. For example if an endpoint had the following tags:

      tags:
        name: Subscribe
        status: ${response.status}
    

    A new group of aggregated stats will be created for every status code returned by the endpoint.

    All endpoints have the following implicitly defined tags:

    NameDescription
    methodThe HTTP method for the endpoint.
    urlThe endpoint's url with any dynamic pieces being replaced with an asterisk.
    _idThe index of this endpoint in the list of endpoints, starting with 0.

    Of the implicitly defined tags only url can be overwritten which is helpful in cases such as when an entire url is dynamically generated and it would otherwise show up as *.

  • url - A template specifying the fully qualified url to the endpoint which will be requested.

  • provides Optional - See the provides subsection

  • on_demand Optional - A boolean which indicates that this endpoint should only be called when another endpoint first needs data that this endpoint provides. If the endpoint has no provides it has no affect.

  • logs Optional - See the logs subsection

  • max_parallel_requests Optional - Limits how many requests can be "open" at any point for the endpoint. WARNING: this can cause coordinated omission, invalidating the test statistics.

  • no_auto_returns Optional - A boolean which indicates that any auto_return providers referenced within this endpoint will have auto_return disabled--meaning values pulled from those providers will not be automatically pushed back to the provider after a response is received. Defaults to false.

  • request_timeout Optional - A duration signifying how long a request will wait for a response before it times out. When not specified, the value from the client config will be used.

Using providers to build a request

Providers can be referenced anywhere templates can be used and also in the declare subsection.

body subsection

body: template
body:
  file: template
body:
  multipart: 
    field_name:
      [headers: headers]
      body: template
    field_name:
      [headers: headers]
      body:
        file: template

A request body can be in one of three formats: a template to send a string as the body, a file which will send the contents of a file as the body, or a multipart body.

To send the contents of a file the body parameter should be an object with a single key of file and the value being a template. Relative paths resolve relative to the config file used to execute pewpew.

To send a multipart body, the body parameter should be an object with a single key of multipart and the value being an object of key/value pairs, where each key/value pair represents a piece of the multipart body. The keys represent the field_names used in an HTML form and the values are objects with the following properties:

  • headers Optional - Headers that will be included with this piece of the multipart body. For example, it is not uncommon to include a content-type header with a piece of a multipart body which includes a file.
  • body - Either a template which will send a string value or an object with a single key of file and the value being a template--which will send the contents of a file.

When a multipart body is used for an endpoint each request will have the content-type header added with the value multipart/form-data and the necessary boundary. If there is already a content-type header set for the request it will be overwritten unless it is starts with multipart/--then the necessary boundary will be appended. If a multipart/... content-type is manually set with the request, make sure to not include a boundary parameter.

For any request which has a content-type of multipart/form-data, a Content-Disposition header will be added to each piece in the multipart body with a value of form-data; name="field_name" (where field_name is substituted with the piece's field_name). If a Content-Disposition header is explicitly specified for a piece it will not be overwritten.

File example:

body:
  file: a_file.txt

Multipart example:

body:
  multipart:
    foo:
      headers:
        Content-Type: image/jpeg
      body:
        file: foo.jpg
    bar:
      body: some text

declare subsection

declare:
  name: expression

A declare_subsection provides the ability to select multiple values from a single provider. Without using a declare_subsection, multiple references to a provider will only select a single value. For example, in:

endpoints:
  - method: PUT
    url: https://localhost/ship/${shipId}/speed
    body: '{"shipId":"${shipId}","kesselRunTime":75}'

both references to the provider shipId will resolve to the same value, which in many cases is desired.

The declare_subsection is in the format of key/value pairs where the value is an expression. Every key can function as a provider and can be interpolated just as a provider would be.

Example 1

endpoints:
  - declare:
      shipIds: collect(shipId, 3, 5)
    method: DELETE
    url: https://localhost/ships
    body: '{"shipIds":${shipIds}}'

Calls the endpoint DELETE /ships where the body is interpolated with an array of ship ids. shipIds will have a length between three and five.

Example 2

endpoints:
  - declare:
      destroyedShipId: shipId
    method: PUT
    url: https://localhost/ship/${shipId}/destroys/${destroyedShipId}

Calls PUT on an endpoint where shipId and destroyedShipId are interpolated to different values.

provides subsection

provides:
  provider_name:
    select: select
    [for_each: for_each]
    [where: expression]
    [send: block | force | if_not_full]

The provides_subsection is how data can be sent to a provider from an HTTP response. provider_name is a reference to a provider which must be declared in the root providers section. For every HTTP response that is received, zero or more values can be sent to the provider based upon the conditions specified.

Sending data to a provider is done with a SQL-like syntax. The select, for_each and where sections use expressions to reference providers in addition to the special variables "request", "response" and "stats". "request" provides a means of accessing data that was sent with the request, "response" provides a means of accessing data returned with the response and "stats" give access to measurements about the request (currently only rtt meaning round-trip time).

The request object has the properties start-line, method, url, headers, headers_all and body which provide access to the respective sections in the HTTP request. Similarly, the response object has the properties start-line, headers, headers_all and body in addition to status which indicates the HTTP response status code. See this MDN article on HTTP messages for more details on the structure of HTTP requests and responses.

start-line is a string and headers is represented as a JSON object with key/value string pairs. In the event where a request or response has multiple headers with the same name, the headers_all property can be used which is a JSON object where the header name is the key and the value an array of header values. Currently, body in the request is always a string and body in the response is parsed as a JSON value, when possible, otherwise it is a string. status is a number. method is a string and url is an object with the same properties as the web URL object (see this MDN article).

  • select - Determines the shape of the data sent to the provider. select is interpreted as a JSON object where any string value is evaluated as an expression.

  • for_each Optional - Evaluates select for each element in an array or arrays. This is specified as an array of expressions. Expressions can evaluate to any JSON data type, but those which evaluate to an array will have each of their elements iterated over and select is evaluated for each. When multiple expressions evaluate to an array then the cartesian product of the arrays is produced.

    The select and where parameters can access the elements provided by for_each through the value for_each just like accessing a value from a provider. Because a for_each can iterate over multiple arrays, each element can be accessed by indexing into the array. For example for_each[1] would access the element from the second array (indexes are referenced with zero based counting so 0 represents the element in the first array).

  • where Optional - Allows conditionally sending data to a provider based on a predicate. This is an expression which evaluates to a boolean value, indicating whether select should be evaluated for the current data set.

  • send Optional - Specify the behavior that should be used when sending data to a provider. Valid options for this parameter are block, force, and if_not_full. Defaults to if_not_full if the endpoint has a peak_load otherwise block.

    block indicates that if the provider's buffer is full, further endpoint calls will be blocked until there's room in the provider's buffer for the value. If an endpoint has multiple provides which are block, then the blocking will only wait for at least one of the providers' buffers to have room.

    force indicates that the value will be sent to the provider regardless of whether its buffer is "full". This can make a provider's buffer exceed its soft limit.

    if_not_full indicates that the value will be sent to the provider only if the provider is not full.

Example 1

With an HTTP response with the following body

{ "session": "abc123" }

and a provides section defined as:

provides:
  session:
    select: response.body.session
    where: response.status < 400

the session provider would be given the value "abc123" if the status code was less than 400 otherwise nothing would be sent to the session provider.

Example 2

With an HTTP response with the following body:

{
  "characters": [
    {
      "type": "Human",
      "id": "1000",
      "name": "Luke Skywalker",
      "friends": ["1002", "1003", "2000", "2001"],
      "appearsIn": [4, 5, 6],
      "homePlanet": "Tatooine",
    },
    {
      "type": "Human",
      "id": "1001",
      "name": "Darth Vader",
      "friends": ["1004"],
      "appearsIn": [4, 5, 6],
      "homePlanet": "Tatooine",
    },
    {
      "type": "Droid",
      "id": "2001",
      "name": "R2-D2",
      "friends": ["1000", "1002", "1003"],
      "appearsIn": [4, 5, 6],
      "primaryFunction": "Astromech",
    }
  ]
}

and our provides section is defined as:

provides:
  names:
    select:
      name: for_each[0].name
    for_each:
      - response.body.characters

The names provider would be sent the following values: { "name": "Luke Skywalker" }, { "name": "Darth Vader" }, { "name": "R2-D2" }.

Example 3

It is also possible to access the length of an array by accessing the length property.

Using the same response data from example 2, with a provides section defined as:

provides:
  friendsCount:
    select:
      id: for_each[0].id
      count: for_each[0].friends.length
    for_each:
      - response.body.characters

The friendsCount provider would be sent the following values: { "id": 1000, "count": 4 }, { "id": 1001, "count": 1 }, { "id": 2001, "count": 3 }.

logs subsection

logs:
  logger_name:
    select: select
    [for_each: for_each]
    [where: expression]

The logs_subsection provides a means of sending data to a logger based on the result of an HTTP response. logger_name is a reference to a logger which must be declared in the root loggers section. It is structured in the same way as the provides_subsection except there is no explicit send parameter. When data is sent to a logger it has the same behavior as send: block, which means logging data can potentially block further requests from happening if a logger were to get "backed up". This is unlikely to be a problem unless a large amount of data was consistently logged. It is also possible to log to the same logger multiple times in a single endpoint by repeating the logger_name with a new select.

  • select - Determines the shape of the data sent into the logger.
  • for_each Optional - Evaluates select for each element in an array or arrays.
  • where Optional - Allows conditionally sending data into a logger based on a predicate.