mirror of
https://github.com/aykhans/sarin.git
synced 2026-10-13 21:28:52 +00:00
680 lines
27 KiB
Markdown
680 lines
27 KiB
Markdown
# Configuration
|
|
|
|
Sarin supports environment variables, CLI flags, and YAML files. However, they are not exactly equivalent: YAML files have the most configuration options, followed by CLI flags, and then environment variables.
|
|
|
|
When the same option is specified in multiple sources, the following priority order applies:
|
|
|
|
```
|
|
CLI Flags (Highest) > YAML > Environment Variables (Lowest)
|
|
```
|
|
|
|
Use `-s` or `--show-config` to see the final merged configuration before sending requests.
|
|
|
|
## Properties
|
|
|
|
> **Note:** For CLI flags with `string / []string` type, the flag can be used once with a single value or multiple times to provide multiple values.
|
|
|
|
| Name | YAML | CLI | ENV | Default | Description |
|
|
| --------------------------------------- | ----------------------------------- | -------------------------------------------- | ----------------------------------- | ------- | ----------------------------- |
|
|
| [Help](#help) | - | `-help` / `-h` | - | - | Show help message |
|
|
| [Version](#version) | - | `-version` / `-v` | - | - | Show version and build info |
|
|
| [Show Config](#show-config) | `showConfig`<br>(boolean) | `-show-config` / `-s`<br>(boolean) | `SARIN_SHOW_CONFIG`<br>(boolean) | `false` | Show merged configuration |
|
|
| [Config File](#config-file) | `configFile`<br>(string / []string) | `-config-file` / `-f`<br>(string / []string) | `SARIN_CONFIG_FILE`<br>(string) | - | Path to config file(s) |
|
|
| [URL](#url) | `url`<br>(string) | `-url` / `-U`<br>(string) | `SARIN_URL`<br>(string) | - | Target URL (HTTP/HTTPS) |
|
|
| [Method](#method) | `method`<br>(string / []string) | `-method` / `-M`<br>(string / []string) | `SARIN_METHOD`<br>(string) | `GET` | HTTP method(s) |
|
|
| [Timeout](#timeout) | `timeout`<br>(duration) | `-timeout` / `-T`<br>(duration) | `SARIN_TIMEOUT`<br>(duration) | `10s` | Request timeout |
|
|
| [Max Response Body](#max-response-body) | `maxResponseBody`<br>(size) | `-max-response-body` / `-S`<br>(size) | `SARIN_MAX_RESPONSE_BODY`<br>(size) | `10MiB` | Largest response body to read |
|
|
| [Concurrency](#concurrency) | `concurrency`<br>(number) | `-concurrency` / `-c`<br>(number) | `SARIN_CONCURRENCY`<br>(number) | `1` | Number of concurrent workers |
|
|
| [Requests](#requests) | `requests`<br>(number) | `-requests` / `-r`<br>(number) | `SARIN_REQUESTS`<br>(number) | - | Total requests to send |
|
|
| [Duration](#duration) | `duration`<br>(duration) | `-duration` / `-d`<br>(duration) | `SARIN_DURATION`<br>(duration) | - | Test duration |
|
|
| [Log Level](#log-level) | `logLevel`<br>(string) | `-log-level` / `-l`<br>(string) | `SARIN_LOG_LEVEL`<br>(string) | `error` | Runtime log levels to emit |
|
|
| [Log File](#log-file) | `logFile`<br>(string) | `-log-file` / `-w`<br>(string) | `SARIN_LOG_FILE`<br>(string) | - | Write runtime logs to a file |
|
|
| [Progress](#progress) | `progress`<br>(string) | `-progress` / `-p`<br>(string) | `SARIN_PROGRESS`<br>(string) | `bar` | Progress display (bar/none) |
|
|
| [Output](#output) | `output`<br>(string) | `-output` / `-o`<br>(string) | `SARIN_OUTPUT`<br>(string) | `table` | Output format for stats |
|
|
| [Dry Run](#dry-run) | `dryRun`<br>(boolean) | `-dry-run` / `-z`<br>(boolean) | `SARIN_DRY_RUN`<br>(boolean) | `false` | Generate without sending |
|
|
| [Insecure](#insecure) | `insecure`<br>(boolean) | `-insecure` / `-I`<br>(boolean) | `SARIN_INSECURE`<br>(boolean) | `false` | Skip TLS verification |
|
|
| [Body](#body) | `body`<br>(string / []string) | `-body` / `-B`<br>(string / []string) | `SARIN_BODY`<br>(string) | - | Request body |
|
|
| [Params](#params) | `params`<br>(object) | `-param` / `-P`<br>(string / []string) | `SARIN_PARAM`<br>(string) | - | URL query parameters |
|
|
| [Headers](#headers) | `headers`<br>(object) | `-header` / `-H`<br>(string / []string) | `SARIN_HEADER`<br>(string) | - | HTTP headers |
|
|
| [Cookies](#cookies) | `cookies`<br>(object) | `-cookie` / `-C`<br>(string / []string) | `SARIN_COOKIE`<br>(string) | - | HTTP cookies |
|
|
| [Proxy](#proxy) | `proxy`<br>(string / []string) | `-proxy` / `-X`<br>(string / []string) | `SARIN_PROXY`<br>(string) | - | Proxy URL(s) |
|
|
| [Values](#values) | `values`<br>(string / []string) | `-values` / `-V`<br>(string / []string) | `SARIN_VALUES`<br>(string) | - | Template values (key=value) |
|
|
| [Lua](#lua) | `lua`<br>(string / []string) | `-lua`<br>(string / []string) | `SARIN_LUA`<br>(string) | - | Lua script(s) |
|
|
| [Js](#js) | `js`<br>(string / []string) | `-js`<br>(string / []string) | `SARIN_JS`<br>(string) | - | JavaScript script(s) |
|
|
|
|
---
|
|
|
|
## Help
|
|
|
|
Show help message.
|
|
|
|
```sh
|
|
sarin -help
|
|
```
|
|
|
|
## Version
|
|
|
|
Show version and build information.
|
|
|
|
```sh
|
|
sarin -version
|
|
```
|
|
|
|
## Show Config
|
|
|
|
Show the final merged configuration before sending requests.
|
|
|
|
```sh
|
|
sarin -show-config
|
|
```
|
|
|
|
## Config File
|
|
|
|
Path to configuration file(s). Supports local paths and remote URLs.
|
|
|
|
**Priority Rules:**
|
|
|
|
1. **CLI flags** (`-f`) have highest priority, processed left to right (rightmost wins)
|
|
2. **Included files** (via `configFile` property) are processed with lower priority than their parent
|
|
3. **Environment variable** (`SARIN_CONFIG_FILE`) has lowest priority
|
|
|
|
**Example:**
|
|
|
|
```yaml
|
|
# config2.yaml
|
|
configFile: /config4.yaml
|
|
url: http://from-config2.com
|
|
```
|
|
|
|
```sh
|
|
SARIN_CONFIG_FILE=/config1.yaml sarin -f /config2.yaml -f https://example.com/config3.yaml
|
|
```
|
|
|
|
**Resolution order (lowest to highest priority):**
|
|
|
|
| Source | File | Priority |
|
|
| ------------------------ | ------------ | -------- |
|
|
| ENV (SARIN_CONFIG_FILE) | config1.yaml | Lowest |
|
|
| Included by config2.yaml | config4.yaml | ↑ |
|
|
| CLI -f (first) | config2.yaml | ↑ |
|
|
| CLI -f (second) | config3.yaml | Highest |
|
|
|
|
**Why this order?**
|
|
|
|
- `config1.yaml` comes from ENV → lowest priority
|
|
- `config2.yaml` comes from CLI → higher than ENV
|
|
- `config4.yaml` is included BY `config2.yaml` → inherits position below its parent
|
|
- `config3.yaml` comes from CLI after `config2.yaml` → highest priority
|
|
|
|
If all four files define `url`, the value from `config3.yaml` wins.
|
|
|
|
**Merge behavior by field:**
|
|
|
|
- **Scalar fields** (`url`, `requests`, `duration`, `timeout`, `concurrency`, etc.): higher priority overrides lower priority
|
|
- **Method and Body**: higher priority overrides lower priority (no merging)
|
|
- **Headers, Params, Cookies, Proxies, Values, Lua, and Js**: accumulated across all config files
|
|
|
|
## URL
|
|
|
|
Target URL. Must be HTTP or HTTPS. The URL path supports [templating](templating.md), allowing dynamic path generation per request.
|
|
|
|
> **Note:** Templating is only supported in the URL path. Host and scheme must be static.
|
|
|
|
**Example with dynamic path:**
|
|
|
|
```yaml
|
|
url: http://example.com/users/{{ fakeit_UUID }}/profile
|
|
```
|
|
|
|
**CLI example with dynamic path:**
|
|
|
|
```sh
|
|
sarin -U "http://example.com/users/{{ fakeit_UUID }}" -r 1000 -c 10
|
|
```
|
|
|
|
## Method
|
|
|
|
HTTP method(s). Defaults to `GET`. If multiple values are provided, Sarin starts at a random index and cycles through them in order. Once the cycle completes, it picks a new random starting point. Supports [templating](templating.md).
|
|
|
|
**YAML example:**
|
|
|
|
```yaml
|
|
method: GET
|
|
|
|
# OR
|
|
|
|
method:
|
|
- GET
|
|
- POST
|
|
- PUT
|
|
```
|
|
|
|
**CLI example:**
|
|
|
|
```sh
|
|
-method GET -method POST -method PUT
|
|
```
|
|
|
|
**ENV example:**
|
|
|
|
```sh
|
|
SARIN_METHOD=GET
|
|
```
|
|
|
|
## Timeout
|
|
|
|
Request timeout. Must be greater than 0. Defaults to `10s`.
|
|
|
|
Valid time units: `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`
|
|
|
|
**Examples:** `5s`, `300ms`, `1m20s`
|
|
|
|
## Max Response Body
|
|
|
|
Largest response body sarin reads. Defaults to `10MiB`, and `0` removes the limit.
|
|
|
|
The value is a whole number of bytes, with or without a unit: `B`, `KB`, `MB`, `GB`, `TB` are powers of 1000, `KiB`, `MiB`, `GiB`, `TiB` are powers of 1024. Case is ignored and a space before the unit is allowed. The largest accepted value is `1GiB`, because one response can take a few times the limit in memory.
|
|
|
|
**Examples:** `1048576`, `10MiB`, `2MB`, `512KiB`
|
|
|
|
A server can declare a `Content-Length` far larger than it sends, and the body is allocated from that declared length, so without a limit a single response can exhaust memory. A response above the limit fails with `response body exceeds the body limit` and is counted like any other error.
|
|
|
|
The limit applies per response in flight, and a declared Content-Length is allocated up to it, so the worst case is a few times the limit times the concurrency. It covers the requests scripts make as well.
|
|
|
|
A compressed response in a script is decoded first, so the limit covers the decoded size as well as the bytes that arrived. Decoding holds both at once, so a brotli or zstd body peaks at over ten times the limit. Ratios are unbounded, and 106 bytes of brotli can hold 64MiB, so with `0` a tiny download can still exhaust memory.
|
|
|
|
## Concurrency
|
|
|
|
Number of concurrent workers. Must be between 1 and 100,000,000. Defaults to `1`.
|
|
|
|
## Requests
|
|
|
|
Total number of requests to send. At least one of `requests` or `duration` must be specified. If both are provided, the test stops when either limit is reached first.
|
|
|
|
## Duration
|
|
|
|
Test duration. At least one of `requests` or `duration` must be specified. If both are provided, the test stops when either limit is reached first.
|
|
|
|
Valid time units: `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`
|
|
|
|
**Examples:** `1m30s`, `25s`, `1h`
|
|
|
|
## Log Level
|
|
|
|
Runtime log levels to emit, comma-separated. Valid levels: `info`, `error`. Defaults to `error`.
|
|
|
|
- `error`: errors that occur while generating or sending a request
|
|
- `info`: every completed response
|
|
|
|
Leave empty to disable logging entirely.
|
|
|
|
**Examples:** `error` (only errors), `info` (only responses), `info,error` (both)
|
|
|
|
## Log File
|
|
|
|
Write runtime logs to this file instead of the terminal or stderr. The parent directory must exist.
|
|
|
|
```sh
|
|
sarin -U http://example.com -r 1000 --log-file ./run.log
|
|
```
|
|
|
|
## Progress
|
|
|
|
Progress display. Valid values: `bar` (default), `none`. Use `none` to hide the progress bar.
|
|
|
|
## Output
|
|
|
|
Output format for response statistics.
|
|
|
|
Valid formats: `table` (default), `json`, `yaml`, `none`
|
|
|
|
Using `none` disables output and reduces memory usage since response statistics are not stored.
|
|
|
|
## Dry Run
|
|
|
|
Generate requests without sending them. Useful for testing templates.
|
|
|
|
A script's own `http` calls are still sent, since a script needs them to build the request you are inspecting. Only the requests sarin generates are held back.
|
|
|
|
## Insecure
|
|
|
|
Skip TLS certificate verification.
|
|
|
|
## Body
|
|
|
|
Request body. If multiple values are provided, Sarin starts at a random index and cycles through them in order. Once the cycle completes, it picks a new random starting point. Supports [templating](templating.md).
|
|
|
|
**YAML example:**
|
|
|
|
```yaml
|
|
body: '{"product": "car"}'
|
|
|
|
# OR
|
|
|
|
body:
|
|
- '{"product": "car"}'
|
|
- '{"product": "phone"}'
|
|
- '{"product": "watch"}'
|
|
```
|
|
|
|
**CLI example:**
|
|
|
|
```sh
|
|
-body '{"product": "car"}' -body '{"product": "phone"}' -body '{"product": "watch"}'
|
|
```
|
|
|
|
**ENV example:**
|
|
|
|
```sh
|
|
SARIN_BODY='{"product": "car"}'
|
|
```
|
|
|
|
## Params
|
|
|
|
URL query parameters. Supports [templating](templating.md).
|
|
|
|
When the same key appears as **separate entries** (in CLI or config file), all values are sent in every request. When multiple values are specified as an **array on a single key** (config file only), Sarin cycles through them.
|
|
|
|
**YAML example:**
|
|
|
|
```yaml
|
|
params:
|
|
key1: value1
|
|
key2: [value2, value3] # cycles between value2 and value3
|
|
|
|
# OR
|
|
|
|
params:
|
|
- key1: value1
|
|
- key2: [value2, value3] # cycles between value2 and value3
|
|
|
|
# To send both values in every request, use separate entries:
|
|
params:
|
|
- key2: value2
|
|
- key2: value3 # both sent in every request
|
|
```
|
|
|
|
**CLI example:**
|
|
|
|
```sh
|
|
-param "key1=value1" -param "key2=value2" -param "key2=value3" # sends both value2 and value3
|
|
```
|
|
|
|
**ENV example:**
|
|
|
|
```sh
|
|
SARIN_PARAM="key1=value1"
|
|
```
|
|
|
|
## Headers
|
|
|
|
HTTP headers. Supports [templating](templating.md).
|
|
|
|
When the same key appears as **separate entries** (in CLI or config file), all values are sent in every request. When multiple values are specified as an **array on a single key** (config file only), Sarin cycles through them.
|
|
|
|
**YAML example:**
|
|
|
|
```yaml
|
|
headers:
|
|
key1: value1
|
|
key2: [value2, value3] # cycles between value2 and value3
|
|
|
|
# OR
|
|
|
|
headers:
|
|
- key1: value1
|
|
- key2: [value2, value3] # cycles between value2 and value3
|
|
|
|
# To send both values in every request, use separate entries:
|
|
headers:
|
|
- key2: value2
|
|
- key2: value3 # both sent in every request
|
|
```
|
|
|
|
**CLI example:**
|
|
|
|
```sh
|
|
-header "key1: value1" -header "key2: value2" -header "key2: value3" # sends both value2 and value3
|
|
```
|
|
|
|
**ENV example:**
|
|
|
|
```sh
|
|
SARIN_HEADER="key1: value1"
|
|
```
|
|
|
|
## Cookies
|
|
|
|
HTTP cookies. Supports [templating](templating.md).
|
|
|
|
When the same key appears as **separate entries** (in CLI or config file), all values are sent in every request. When multiple values are specified as an **array on a single key** (config file only), Sarin cycles through them.
|
|
|
|
**YAML example:**
|
|
|
|
```yaml
|
|
cookies:
|
|
key1: value1
|
|
key2: [value2, value3] # cycles between value2 and value3
|
|
|
|
# OR
|
|
|
|
cookies:
|
|
- key1: value1
|
|
- key2: [value2, value3] # cycles between value2 and value3
|
|
|
|
# To send both values in every request, use separate entries:
|
|
cookies:
|
|
- key2: value2
|
|
- key2: value3 # both sent in every request
|
|
```
|
|
|
|
**CLI example:**
|
|
|
|
```sh
|
|
-cookie "key1=value1" -cookie "key2=value2" -cookie "key2=value3" # sends both value2 and value3
|
|
```
|
|
|
|
**ENV example:**
|
|
|
|
```sh
|
|
SARIN_COOKIE="key1=value1"
|
|
```
|
|
|
|
## Proxy
|
|
|
|
Proxy URL(s). If multiple values are provided, Sarin starts at a random index and cycles through them in order. Once the cycle completes, it picks a new random starting point.
|
|
|
|
Supported protocols: `http`, `https`, `socks5`, `socks5h`
|
|
|
|
**YAML example:**
|
|
|
|
```yaml
|
|
proxy: http://proxy1.com
|
|
|
|
# OR
|
|
|
|
proxy:
|
|
- http://proxy1.com
|
|
- socks5://proxy2.com
|
|
- socks5h://proxy3.com
|
|
```
|
|
|
|
**CLI example:**
|
|
|
|
```sh
|
|
-proxy http://proxy1.com -proxy socks5://proxy2.com -proxy socks5h://proxy3.com
|
|
```
|
|
|
|
**ENV example:**
|
|
|
|
```sh
|
|
SARIN_PROXY="http://proxy1.com"
|
|
```
|
|
|
|
## Values
|
|
|
|
Template values in key=value format. Supports [templating](templating.md). Multiple values can be specified and all are rendered for each request.
|
|
|
|
See the [Templating Guide](templating.md) for more details on using values and available template functions.
|
|
|
|
**YAML example:**
|
|
|
|
```yaml
|
|
values: "key=value"
|
|
|
|
# OR
|
|
|
|
values: |
|
|
key1=value1
|
|
key2=value2
|
|
key3=value3
|
|
```
|
|
|
|
**CLI example:**
|
|
|
|
```sh
|
|
-values "key1=value1" -values "key2=value2" -values "key3=value3"
|
|
```
|
|
|
|
**ENV example:**
|
|
|
|
```sh
|
|
SARIN_VALUES="key1=value1"
|
|
```
|
|
|
|
## Lua
|
|
|
|
Lua script(s) for request transformation. Each script must define a global `transform` function that receives a request object and returns the modified request object. Scripts run after template rendering, before the request is sent.
|
|
|
|
If multiple Lua scripts are provided, they are chained in order-the output of one becomes the input to the next. When both Lua and JavaScript scripts are specified, all Lua scripts run first, then all JavaScript scripts.
|
|
|
|
**Script sources:**
|
|
|
|
Scripts can be provided as:
|
|
|
|
- **Inline script:** Direct script code
|
|
- **File reference:** `@/path/to/script.lua` or `@./relative/path.lua`
|
|
- **URL reference:** `@http://...` or `@https://...`
|
|
- **Escaped `@`:** `@@...` for inline scripts that start with a literal `@`
|
|
|
|
**The `transform` function:**
|
|
|
|
```lua
|
|
function transform(req)
|
|
-- req.method (string) - HTTP method (e.g. "GET", "POST")
|
|
-- req.path (string) - URL path (e.g. "/api/users")
|
|
-- req.body (string) - Request body
|
|
-- req.headers (table of string/arrays) - HTTP headers (e.g. {["X-Key"] = "value"})
|
|
-- req.params (table of string/arrays) - Query parameters (e.g. {["id"] = "123"})
|
|
-- req.cookies (table of string/arrays) - Cookies (e.g. {["session"] = "abc"})
|
|
|
|
req.headers["X-Custom"] = "my-value"
|
|
return req
|
|
end
|
|
```
|
|
|
|
> **Note:** Header, parameter, and cookie values can be a single string or a table (array) for multiple values per key (e.g. `{"val1", "val2"}`). Numbers and booleans are converted to text, other values are skipped.
|
|
|
|
> **Note:** Lua scripts can send their own HTTP requests with `http` and work with JSON through the global `json` table. See [HTTP Requests in Scripts](#http-requests-in-scripts).
|
|
|
|
**YAML example:**
|
|
|
|
```yaml
|
|
lua: |
|
|
function transform(req)
|
|
req.headers["X-Custom"] = "my-value"
|
|
return req
|
|
end
|
|
|
|
# OR
|
|
|
|
lua:
|
|
- "@/path/to/script1.lua"
|
|
- "@/path/to/script2.lua"
|
|
```
|
|
|
|
**CLI example:**
|
|
|
|
```sh
|
|
-lua 'function transform(req) req.headers["X-Custom"] = "my-value" return req end'
|
|
|
|
# OR
|
|
|
|
-lua @/path/to/script1.lua -lua @/path/to/script2.lua
|
|
```
|
|
|
|
**ENV example:**
|
|
|
|
```sh
|
|
SARIN_LUA='function transform(req) req.headers["X-Custom"] = "my-value" return req end'
|
|
```
|
|
|
|
## Js
|
|
|
|
JavaScript script(s) for request transformation. Each script must define a global `transform` function that receives a request object and returns the modified request object. Scripts run after template rendering, before the request is sent.
|
|
|
|
If multiple JavaScript scripts are provided, they are chained in order-the output of one becomes the input to the next. When both Lua and JavaScript scripts are specified, all Lua scripts run first, then all JavaScript scripts.
|
|
|
|
**Script sources:**
|
|
|
|
Scripts can be provided as:
|
|
|
|
- **Inline script:** Direct script code
|
|
- **File reference:** `@/path/to/script.js` or `@./relative/path.js`
|
|
- **URL reference:** `@http://...` or `@https://...`
|
|
- **Escaped `@`:** `@@...` for inline scripts that start with a literal `@`
|
|
|
|
**The `transform` function:**
|
|
|
|
```javascript
|
|
function transform(req) {
|
|
// req.method (string) - HTTP method (e.g. "GET", "POST")
|
|
// req.path (string) - URL path (e.g. "/api/users")
|
|
// req.body (string) - Request body
|
|
// req.headers (object of string/arrays) - HTTP headers (e.g. {"X-Key": "value"})
|
|
// req.params (object of string/arrays) - Query parameters (e.g. {"id": "123"})
|
|
// req.cookies (object of string/arrays) - Cookies (e.g. {"session": "abc"})
|
|
|
|
req.headers["X-Custom"] = "my-value";
|
|
return req;
|
|
}
|
|
```
|
|
|
|
> **Note:** Header, parameter, and cookie values can be a single string or an array for multiple values per key (e.g. `["val1", "val2"]`). Numbers and booleans are converted to text, other values are skipped.
|
|
|
|
> **Note:** JavaScript scripts can send their own HTTP requests with `http`. See [HTTP Requests in Scripts](#http-requests-in-scripts).
|
|
|
|
**YAML example:**
|
|
|
|
```yaml
|
|
js: |
|
|
function transform(req) {
|
|
req.headers["X-Custom"] = "my-value";
|
|
return req;
|
|
}
|
|
|
|
# OR
|
|
|
|
js:
|
|
- "@/path/to/script1.js"
|
|
- "@/path/to/script2.js"
|
|
```
|
|
|
|
**CLI example:**
|
|
|
|
```sh
|
|
-js 'function transform(req) { req.headers["X-Custom"] = "my-value"; return req; }'
|
|
|
|
# OR
|
|
|
|
-js @/path/to/script1.js -js @/path/to/script2.js
|
|
```
|
|
|
|
**ENV example:**
|
|
|
|
```sh
|
|
SARIN_JS='function transform(req) { req.headers["X-Custom"] = "my-value"; return req; }'
|
|
```
|
|
|
|
## HTTP Requests in Scripts
|
|
|
|
Lua and JavaScript scripts can send their own HTTP requests and use the response in the main request, for example to fetch a fresh token or CSRF value from another endpoint.
|
|
|
|
**Function:**
|
|
|
|
```
|
|
http(url, opts)
|
|
```
|
|
|
|
`url` must be an absolute `http` or `https` URL. `opts` is optional.
|
|
|
|
**Options:**
|
|
|
|
| Option | Type | Default | Description |
|
|
| -------------- | --------------------------- | ------- | ------------------------------------------------------------------------------------------------------ |
|
|
| `method` | string | `GET` | HTTP method, sent as given |
|
|
| `headers` | table/object | - | Request headers, same shape as `req.headers` |
|
|
| `params` | table/object | - | Query parameters, added to any already in the URL |
|
|
| `cookies` | table/object | - | Cookies, sent as a single `Cookie` header |
|
|
| `body` | string | `""` | Request body |
|
|
| `timeout` | duration string (`"500ms"`) | `30s` | Request timeout (the `timeout` config does not apply), applied to each hop when redirects are followed |
|
|
| `insecure` | boolean | `false` | Skip TLS verification (the `insecure` config does not apply) |
|
|
| `maxRedirects` | number | `0` | How many redirects to follow, `0` follows none, maximum 100 |
|
|
|
|
Unknown options and values of the wrong type raise an error.
|
|
|
|
**Response:**
|
|
|
|
| Field / Method | Description |
|
|
| -------------- | ------------------------------------------------------------------ |
|
|
| `status` | Status code (number) |
|
|
| `headers` | Response headers, each key holding an array of values |
|
|
| `body` | Response body (string) |
|
|
| `header(name)` | First value of a header, case-insensitive; `nil`/`null` if missing |
|
|
| `cookie(name)` | Value of a cookie from `Set-Cookie`; `nil`/`null` if missing |
|
|
| `json()` | JavaScript only: parses the body as JSON |
|
|
| `bytes()` | JavaScript only: the body as a `Uint8Array` |
|
|
|
|
In Lua, call the helpers as `res:header("name")` or `res.header("name")`.
|
|
|
|
> **Note:** JavaScript strings hold UTF-16, so `body` replaces bytes that are not valid UTF-8 and `body.length` counts code units rather than bytes. Use `bytes()` for binary responses and for the real byte count. Lua strings keep the bytes as they arrived.
|
|
|
|
**Behavior:**
|
|
|
|
- **Runs on every request:** the requests are sent each time `transform` runs, so every main request triggers them. Script globals persist across requests within a worker, so a script can cache a value itself (e.g. `if not token then token = login() end`).
|
|
- **Same proxy:** when proxies are configured, a script's requests go through the same proxy as the main request they belong to.
|
|
- **Status codes:** non-2xx responses are returned normally; check `status` in the script.
|
|
- **Errors:** network failures and timeouts raise a script error. The main request is not sent and the error is counted in the results. Scripts can catch it with `pcall` (Lua) or `try`/`catch` (JavaScript).
|
|
- **Response bodies:** the [Max Response Body](#max-response-body) config bounds these requests too, and a body above it raises an error the script can catch.
|
|
- **Compressed responses:** sarin asks for no encoding, so this only applies when a script sends `Accept-Encoding` itself or the server compresses unasked. A `gzip`, `deflate`, `br` or `zstd` body is then decoded before the script sees it, and `Content-Encoding` and `Content-Length` are both dropped from `headers` because neither describes what the script receives. Anything else is left raw with its header intact: an empty body, `identity`, a `Content-Encoding` sarin does not recognise, and two or more codings at once. A body that fails to decode raises an error the script can catch, though `br` carries no checksum, so damage to a brotli body can go unnoticed.
|
|
- **Only inside `transform`:** `http` works anywhere while `transform` runs, including in helper functions it calls. Calling it at the top level of a script raises an error, because that code also runs during config validation and when each worker starts.
|
|
- **Dry run:** script requests are still sent in dry-run mode.
|
|
- **Not measured:** script requests are not included in the results.
|
|
|
|
**Lua JSON:**
|
|
|
|
Lua has no built-in JSON support, so Lua scripts get a global `json` table:
|
|
|
|
- `json.decode(string)`: arrays become 1-indexed tables and `null` becomes `nil` (so `null` array items leave holes).
|
|
- `json.encode(value)`: a table with keys exactly `1..n` becomes an array, any other table becomes an object, and an empty table becomes `{}`.
|
|
|
|
JavaScript scripts use the built-in `JSON` object.
|
|
|
|
**Lua example:**
|
|
|
|
```lua
|
|
function transform(req)
|
|
local res = http("https://api.example.com/login", {
|
|
method = "POST",
|
|
headers = { ["Content-Type"] = "application/json" },
|
|
body = json.encode({ user = "test", password = "secret" }),
|
|
timeout = "2s",
|
|
})
|
|
if res.status ~= 200 then
|
|
error("login failed: " .. res.status)
|
|
end
|
|
|
|
req.headers["Authorization"] = "Bearer " .. json.decode(res.body).token
|
|
return req
|
|
end
|
|
```
|
|
|
|
**JavaScript example:**
|
|
|
|
```javascript
|
|
function transform(req) {
|
|
const res = http("https://api.example.com/login", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ user: "test", password: "secret" }),
|
|
timeout: "2s",
|
|
});
|
|
if (res.status !== 200) {
|
|
throw new Error("login failed: " + res.status);
|
|
}
|
|
|
|
req.headers["Authorization"] = "Bearer " + res.json().token;
|
|
return req;
|
|
}
|
|
```
|