mirror of
https://github.com/aykhans/sarin.git
synced 2026-02-28 14:59:14 +00:00
Compare commits
7 Commits
v1.0.0
...
a9738c0a11
| Author | SHA1 | Date | |
|---|---|---|---|
| a9738c0a11 | |||
| 76225884e6 | |||
| a512f3605d | |||
|
|
635c33008b | ||
| 3f2147ec6c | |||
| 92d0c5e003 | |||
| 27bc8f2e96 |
28
README.md
28
README.md
@@ -19,11 +19,11 @@
|
|||||||
Sarin is designed for efficient HTTP load testing with minimal resource consumption. It prioritizes simplicity—features like templating add zero overhead when unused.
|
Sarin is designed for efficient HTTP load testing with minimal resource consumption. It prioritizes simplicity—features like templating add zero overhead when unused.
|
||||||
|
|
||||||
| ✅ Supported | ❌ Not Supported |
|
| ✅ Supported | ❌ Not Supported |
|
||||||
| ---------------------------------------------------- | --------------------------------- |
|
| ---------------------------------------------------------- | --------------------------------- |
|
||||||
| High-performance with low memory footprint | Detailed response body analysis |
|
| High-performance with low memory footprint | Detailed response body analysis |
|
||||||
| Long-running duration/count based tests | Extensive response statistics |
|
| Long-running duration/count based tests | Extensive response statistics |
|
||||||
| Dynamic requests via 320+ template functions | Web UI or complex TUI |
|
| Dynamic requests via 320+ template functions | Web UI or complex TUI |
|
||||||
| Multiple proxy protocols (HTTP/HTTPS/SOCKS5/SOCKS5H) | Scripting or multi-step scenarios |
|
| Multiple proxy protocols<br>(HTTP, HTTPS, SOCKS5, SOCKS5H) | Scripting or multi-step scenarios |
|
||||||
| Flexible config (CLI, ENV, YAML) | HTTP/2, HTTP/3, WebSocket, gRPC |
|
| Flexible config (CLI, ENV, YAML) | HTTP/2, HTTP/3, WebSocket, gRPC |
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
@@ -74,18 +74,6 @@ Send 10,000 GET requests with 50 concurrent connections and a random User-Agent
|
|||||||
sarin -U http://example.com -r 10_000 -c 50 -H "User-Agent: {{ fakeit_UserAgent }}"
|
sarin -U http://example.com -r 10_000 -c 50 -H "User-Agent: {{ fakeit_UserAgent }}"
|
||||||
```
|
```
|
||||||
|
|
||||||
Example output:
|
|
||||||
|
|
||||||
```
|
|
||||||
┌──────────┬───────┬──────────┬───────────┬─────────┬───────────┬──────────┬───────────┐
|
|
||||||
│ Response │ Count │ Min │ Max │ Average │ P90 │ P95 │ P99 │
|
|
||||||
├──────────┼───────┼──────────┼───────────┼─────────┼───────────┼──────────┼───────────┤
|
|
||||||
│ 200 │ 10000 │ 78.038ms │ 288.153ms │ 94.71ms │ 103.078ms │ 131.08ms │ 269.218ms │
|
|
||||||
├──────────┼───────┼──────────┼───────────┼─────────┼───────────┼──────────┼───────────┤
|
|
||||||
│ Total │ 10000 │ 78.038ms │ 288.153ms │ 94.71ms │ 103.078ms │ 131.08ms │ 269.218ms │
|
|
||||||
└──────────┴───────┴──────────┴───────────┴─────────┴───────────┴──────────┴───────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
Run a 5-minute duration-based test:
|
Run a 5-minute duration-based test:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@@ -105,22 +93,22 @@ For more usage examples, see the **[Examples Guide](docs/examples.md)**.
|
|||||||
Sarin supports environment variables, CLI flags, and YAML files. When the same option is specified in multiple sources, the following priority order applies:
|
Sarin supports environment variables, CLI flags, and YAML files. When the same option is specified in multiple sources, the following priority order applies:
|
||||||
|
|
||||||
```
|
```
|
||||||
YAML (Highest) > CLI Flags > Environment Variables (Lowest)
|
CLI Flags (Highest) > YAML > Environment Variables (Lowest)
|
||||||
```
|
```
|
||||||
|
|
||||||
For detailed documentation on all configuration options (URL, method, timeout, concurrency, headers, cookies, proxy, etc.), see the **[Configuration Guide](docs/configuration.md)**.
|
For detailed documentation on all configuration options (URL, method, timeout, concurrency, headers, cookies, proxy, etc.), see the **[Configuration Guide](docs/configuration.md)**.
|
||||||
|
|
||||||
## Templating
|
## Templating
|
||||||
|
|
||||||
Sarin supports Go templates in methods, bodies, headers, params, cookies, and values. Use the 320+ built-in functions to generate dynamic data for each request.
|
Sarin supports Go templates in URL paths, methods, bodies, headers, params, cookies, and values. Use the 320+ built-in functions to generate dynamic data for each request.
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sarin -U http://example.com/users \
|
sarin -U "http://example.com/users/{{ fakeit_UUID }}" -r 1000 -c 10 \
|
||||||
-V "ID={{ fakeit_UUID }}" \
|
-V "REQUEST_ID={{ fakeit_UUID }}" \
|
||||||
-H "X-Request-ID: {{ .Values.ID }}" \
|
-H "X-Request-ID: {{ .Values.REQUEST_ID }}" \
|
||||||
-B '{"id": "{{ .Values.ID }}"}'
|
-B '{"request_id": "{{ .Values.REQUEST_ID }}"}'
|
||||||
```
|
```
|
||||||
|
|
||||||
For the complete templating guide and functions reference, see the **[Templating Guide](docs/templating.md)**.
|
For the complete templating guide and functions reference, see the **[Templating Guide](docs/templating.md)**.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ Sarin supports environment variables, CLI flags, and YAML files. However, they a
|
|||||||
When the same option is specified in multiple sources, the following priority order applies:
|
When the same option is specified in multiple sources, the following priority order applies:
|
||||||
|
|
||||||
```
|
```
|
||||||
YAML (Highest) > CLI Flags > Environment Variables (Lowest)
|
CLI Flags (Highest) > YAML > Environment Variables (Lowest)
|
||||||
```
|
```
|
||||||
|
|
||||||
Use `-s` or `--show-config` to see the final merged configuration before sending requests.
|
Use `-s` or `--show-config` to see the final merged configuration before sending requests.
|
||||||
@@ -15,7 +15,7 @@ Use `-s` or `--show-config` to see the final merged configuration before sending
|
|||||||
> **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.
|
> **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 |
|
| Name | YAML | CLI | ENV | Default | Description |
|
||||||
| --------------------------- | ----------------------------------- | --------------------------------------------- | -------------------------------- | ------- | ---------------------------- |
|
| --------------------------- | ----------------------------------- | -------------------------------------------- | -------------------------------- | ------- | ---------------------------- |
|
||||||
| [Help](#help) | - | `-help` / `-h` | - | - | Show help message |
|
| [Help](#help) | - | `-help` / `-h` | - | - | Show help message |
|
||||||
| [Version](#version) | - | `-version` / `-v` | - | - | Show version and build info |
|
| [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 |
|
| [Show Config](#show-config) | `showConfig`<br>(boolean) | `-show-config` / `-s`<br>(boolean) | `SARIN_SHOW_CONFIG`<br>(boolean) | `false` | Show merged configuration |
|
||||||
@@ -43,44 +43,87 @@ Use `-s` or `--show-config` to see the final merged configuration before sending
|
|||||||
|
|
||||||
Show help message.
|
Show help message.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sarin -help
|
||||||
|
```
|
||||||
|
|
||||||
## Version
|
## Version
|
||||||
|
|
||||||
Show version and build information.
|
Show version and build information.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sarin -version
|
||||||
|
```
|
||||||
|
|
||||||
## Show Config
|
## Show Config
|
||||||
|
|
||||||
Show the final merged configuration before sending requests.
|
Show the final merged configuration before sending requests.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sarin -show-config
|
||||||
|
```
|
||||||
|
|
||||||
## Config File
|
## Config File
|
||||||
|
|
||||||
Path to configuration file(s). Supports local paths and remote URLs.
|
Path to configuration file(s). Supports local paths and remote URLs.
|
||||||
|
|
||||||
If multiple config files are specified, they are merged in order. Later files override earlier ones.
|
**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:**
|
**Example:**
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# config2.yaml
|
# config2.yaml
|
||||||
configFile: /config4.yaml
|
configFile: /config4.yaml
|
||||||
|
url: http://from-config2.com
|
||||||
```
|
```
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
SARIN_CONFIG_FILE=/config1.yaml sarin -f /config2.yaml -f https://example.com/config3.yaml
|
SARIN_CONFIG_FILE=/config1.yaml sarin -f /config2.yaml -f https://example.com/config3.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example, all 4 config files are read and merged with the following priority:
|
**Resolution order (lowest to highest priority):**
|
||||||
|
|
||||||
```
|
| Source | File | Priority |
|
||||||
config3.yaml > config2.yaml > config4.yaml > config1.yaml
|
| ------------------------ | ------------ | -------- |
|
||||||
```
|
| 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.
|
||||||
|
|
||||||
## URL
|
## URL
|
||||||
|
|
||||||
Target URL. Must be HTTP or HTTPS.
|
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
|
## Method
|
||||||
|
|
||||||
HTTP method(s). If multiple values are provided, Sarin cycles through them randomly for each request. Supports [templating](templating.md).
|
HTTP method(s). If multiple values are provided, Sarin cycles through them in order, starting from a random index for each request. Supports [templating](templating.md).
|
||||||
|
|
||||||
**YAML example:**
|
**YAML example:**
|
||||||
|
|
||||||
@@ -153,7 +196,7 @@ Skip TLS certificate verification.
|
|||||||
|
|
||||||
## Body
|
## Body
|
||||||
|
|
||||||
Request body. If multiple values are provided, Sarin cycles through them randomly for each request. Supports [templating](templating.md).
|
Request body. If multiple values are provided, Sarin cycles through them in order, starting from a random index for each request. Supports [templating](templating.md).
|
||||||
|
|
||||||
**YAML example:**
|
**YAML example:**
|
||||||
|
|
||||||
@@ -182,7 +225,7 @@ SARIN_BODY='{"product": "car"}'
|
|||||||
|
|
||||||
## Params
|
## Params
|
||||||
|
|
||||||
URL query parameters. If multiple values are provided for a key, Sarin cycles through them randomly for each request. Supports [templating](templating.md).
|
URL query parameters. If multiple values are provided for a key, Sarin cycles through them in order, starting from a random index for each request. Supports [templating](templating.md).
|
||||||
|
|
||||||
**YAML example:**
|
**YAML example:**
|
||||||
|
|
||||||
@@ -212,7 +255,7 @@ SARIN_PARAM="key1=value1"
|
|||||||
|
|
||||||
## Headers
|
## Headers
|
||||||
|
|
||||||
HTTP headers. If multiple values are provided for a key, Sarin cycles through them randomly for each request. Supports [templating](templating.md).
|
HTTP headers. If multiple values are provided for a key, Sarin cycles through them in order, starting from a random index for each request. Supports [templating](templating.md).
|
||||||
|
|
||||||
**YAML example:**
|
**YAML example:**
|
||||||
|
|
||||||
@@ -242,7 +285,7 @@ SARIN_HEADER="key1: value1"
|
|||||||
|
|
||||||
## Cookies
|
## Cookies
|
||||||
|
|
||||||
HTTP cookies. If multiple values are provided for a key, Sarin cycles through them randomly for each request. Supports [templating](templating.md).
|
HTTP cookies. If multiple values are provided for a key, Sarin cycles through them in order, starting from a random index for each request. Supports [templating](templating.md).
|
||||||
|
|
||||||
**YAML example:**
|
**YAML example:**
|
||||||
|
|
||||||
@@ -272,7 +315,7 @@ SARIN_COOKIE="key1=value1"
|
|||||||
|
|
||||||
## Proxy
|
## Proxy
|
||||||
|
|
||||||
Proxy URL(s). If multiple values are provided, Sarin cycles through them randomly for each request.
|
Proxy URL(s). If multiple values are provided, Sarin cycles through them in order, starting from a random index for each request.
|
||||||
|
|
||||||
Supported protocols: `http`, `https`, `socks5`, `socks5h`
|
Supported protocols: `http`, `https`, `socks5`, `socks5h`
|
||||||
|
|
||||||
|
|||||||
274
docs/examples.md
274
docs/examples.md
@@ -6,8 +6,8 @@ This guide provides practical examples for common Sarin use cases.
|
|||||||
|
|
||||||
- [Basic Usage](#basic-usage)
|
- [Basic Usage](#basic-usage)
|
||||||
- [Request-Based vs Duration-Based Tests](#request-based-vs-duration-based-tests)
|
- [Request-Based vs Duration-Based Tests](#request-based-vs-duration-based-tests)
|
||||||
- [Dynamic Requests with Templating](#dynamic-requests-with-templating)
|
|
||||||
- [Headers, Cookies, and Parameters](#headers-cookies-and-parameters)
|
- [Headers, Cookies, and Parameters](#headers-cookies-and-parameters)
|
||||||
|
- [Dynamic Requests with Templating](#dynamic-requests-with-templating)
|
||||||
- [Request Bodies](#request-bodies)
|
- [Request Bodies](#request-bodies)
|
||||||
- [Using Proxies](#using-proxies)
|
- [Using Proxies](#using-proxies)
|
||||||
- [Output Formats](#output-formats)
|
- [Output Formats](#output-formats)
|
||||||
@@ -108,9 +108,162 @@ concurrency: 100
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
## Headers, Cookies, and Parameters
|
||||||
|
|
||||||
|
**Custom headers:**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sarin -U http://example.com -r 1000 -c 10 \
|
||||||
|
-H "Authorization: Bearer token123" \
|
||||||
|
-H "X-Custom-Header: value"
|
||||||
|
```
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>YAML equivalent</summary>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
url: http://example.com
|
||||||
|
requests: 1000
|
||||||
|
concurrency: 10
|
||||||
|
headers:
|
||||||
|
Authorization: Bearer token123
|
||||||
|
X-Custom-Header: value
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
**Random headers from multiple values:**
|
||||||
|
|
||||||
|
> **Note:** When multiple values are provided for the same header, Sarin starts at a random index and cycles through all values in order. Once the cycle completes, it picks a new random starting point. This ensures all values are used while maintaining some randomness.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sarin -U http://example.com -r 1000 -c 10 \
|
||||||
|
-H "X-Region: us-east" \
|
||||||
|
-H "X-Region: us-west" \
|
||||||
|
-H "X-Region: eu-central"
|
||||||
|
```
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>YAML equivalent</summary>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
url: http://example.com
|
||||||
|
requests: 1000
|
||||||
|
concurrency: 10
|
||||||
|
headers:
|
||||||
|
X-Region:
|
||||||
|
- us-east
|
||||||
|
- us-west
|
||||||
|
- eu-central
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
**Query parameters:**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sarin -U http://example.com/search -r 1000 -c 10 \
|
||||||
|
-P "query=test" \
|
||||||
|
-P "limit=10"
|
||||||
|
```
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>YAML equivalent</summary>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
url: http://example.com/search
|
||||||
|
requests: 1000
|
||||||
|
concurrency: 10
|
||||||
|
params:
|
||||||
|
query: "test"
|
||||||
|
limit: 10
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
**Dynamic query parameters:**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sarin -U http://example.com/users -r 1000 -c 10 \
|
||||||
|
-P "id={{ fakeit_IntRange 1 1000 }}" \
|
||||||
|
-P "fields=name,email"
|
||||||
|
```
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>YAML equivalent</summary>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
url: http://example.com/users
|
||||||
|
requests: 1000
|
||||||
|
concurrency: 10
|
||||||
|
params:
|
||||||
|
id: "{{ fakeit_IntRange 1 1000 }}"
|
||||||
|
fields: "name,email"
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
**Cookies:**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sarin -U http://example.com -r 1000 -c 10 \
|
||||||
|
-C "session_id=abc123" \
|
||||||
|
-C "user_id={{ fakeit_UUID }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>YAML equivalent</summary>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
url: http://example.com
|
||||||
|
requests: 1000
|
||||||
|
concurrency: 10
|
||||||
|
cookies:
|
||||||
|
session_id: abc123
|
||||||
|
user_id: "{{ fakeit_UUID }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## Dynamic Requests with Templating
|
## Dynamic Requests with Templating
|
||||||
|
|
||||||
Generate a random User-Agent for each request:
|
**Dynamic URL paths:**
|
||||||
|
|
||||||
|
Test different resource endpoints with random IDs:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sarin -U "http://example.com/users/{{ fakeit_UUID }}/profile" -r 1000 -c 10
|
||||||
|
```
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>YAML equivalent</summary>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
url: http://example.com/users/{{ fakeit_UUID }}/profile
|
||||||
|
requests: 1000
|
||||||
|
concurrency: 10
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
Test with random numeric IDs:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sarin -U "http://example.com/products/{{ fakeit_Number 1 10000 }}" -r 1000 -c 10
|
||||||
|
```
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>YAML equivalent</summary>
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
url: http://example.com/products/{{ fakeit_Number 1 10000 }}
|
||||||
|
requests: 1000
|
||||||
|
concurrency: 10
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
**Generate a random User-Agent for each request:**
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sarin -U http://example.com -r 1000 -c 10 \
|
sarin -U http://example.com -r 1000 -c 10 \
|
||||||
@@ -206,123 +359,6 @@ body: '{"ip": "{{ fakeit_IPv4Address }}", "timestamp": "{{ fakeit_Date }}", "act
|
|||||||
|
|
||||||
> For the complete list of 320+ template functions, see the **[Templating Guide](templating.md)**.
|
> For the complete list of 320+ template functions, see the **[Templating Guide](templating.md)**.
|
||||||
|
|
||||||
## Headers, Cookies, and Parameters
|
|
||||||
|
|
||||||
**Custom headers:**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sarin -U http://example.com -r 1000 -c 10 \
|
|
||||||
-H "Authorization: Bearer token123" \
|
|
||||||
-H "X-Custom-Header: value"
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>YAML equivalent</summary>
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
url: http://example.com
|
|
||||||
requests: 1000
|
|
||||||
concurrency: 10
|
|
||||||
headers:
|
|
||||||
Authorization: Bearer token123
|
|
||||||
X-Custom-Header: value
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
**Random headers from multiple values:**
|
|
||||||
|
|
||||||
> **Note:** When multiple values are provided for the same header, Sarin starts at a random index and cycles through all values in order. Once the cycle completes, it picks a new random starting point. This ensures all values are used while maintaining some randomness.
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sarin -U http://example.com -r 1000 -c 10 \
|
|
||||||
-H "X-Region: us-east" \
|
|
||||||
-H "X-Region: us-west" \
|
|
||||||
-H "X-Region: eu-central"
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>YAML equivalent</summary>
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
url: http://example.com
|
|
||||||
requests: 1000
|
|
||||||
concurrency: 10
|
|
||||||
headers:
|
|
||||||
X-Region:
|
|
||||||
- us-east
|
|
||||||
- us-west
|
|
||||||
- eu-central
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
**Query parameters:**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sarin -U http://example.com/search -r 1000 -c 10 \
|
|
||||||
-P "query=test" \
|
|
||||||
-P "limit=10"
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>YAML equivalent</summary>
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
url: http://example.com/search
|
|
||||||
requests: 1000
|
|
||||||
concurrency: 10
|
|
||||||
params:
|
|
||||||
query: test
|
|
||||||
limit: "10"
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
**Dynamic query parameters:**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sarin -U http://example.com/users -r 1000 -c 10 \
|
|
||||||
-P "id={{ fakeit_IntRange 1 1000 }}" \
|
|
||||||
-P "fields=name,email"
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>YAML equivalent</summary>
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
url: http://example.com/users
|
|
||||||
requests: 1000
|
|
||||||
concurrency: 10
|
|
||||||
params:
|
|
||||||
id: "{{ fakeit_IntRange 1 1000 }}"
|
|
||||||
fields: name,email
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
**Cookies:**
|
|
||||||
|
|
||||||
```sh
|
|
||||||
sarin -U http://example.com -r 1000 -c 10 \
|
|
||||||
-C "session_id=abc123" \
|
|
||||||
-C "user_id={{ fakeit_UUID }}"
|
|
||||||
```
|
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>YAML equivalent</summary>
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
url: http://example.com
|
|
||||||
requests: 1000
|
|
||||||
concurrency: 10
|
|
||||||
cookies:
|
|
||||||
session_id: abc123
|
|
||||||
user_id: "{{ fakeit_UUID }}"
|
|
||||||
```
|
|
||||||
|
|
||||||
</details>
|
|
||||||
|
|
||||||
## Request Bodies
|
## Request Bodies
|
||||||
|
|
||||||
**Simple JSON body:**
|
**Simple JSON body:**
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
# Templating
|
# Templating
|
||||||
|
|
||||||
Sarin supports Go templates in methods, bodies, headers, params, cookies, and values.
|
Sarin supports Go templates in URL paths, methods, bodies, headers, params, cookies, and values.
|
||||||
|
|
||||||
|
> **Note:** Templating in URL host and scheme is not supported. Only the path portion of the URL can contain templates.
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
@@ -530,7 +532,7 @@ These functions are powered by [gofakeit](https://github.com/brianvoe/gofakeit)
|
|||||||
| Function | Description | Example |
|
| Function | Description | Example |
|
||||||
| ------------------------------------- | ------------------------------- | --------------------------------------------------------------- |
|
| ------------------------------------- | ------------------------------- | --------------------------------------------------------------- |
|
||||||
| `fakeit_Digit` | Single random digit | `"0"` |
|
| `fakeit_Digit` | Single random digit | `"0"` |
|
||||||
| `fakeit_DigitN(n uint)` | Generate `n` random digits | `{{ fakeit_DigitN 5 }}` → `"0136459948"` |
|
| `fakeit_DigitN(n uint)` | Generate `n` random digits | `{{ fakeit_DigitN 5 }}` → `"71364"` |
|
||||||
| `fakeit_Letter` | Single random letter | `"g"` |
|
| `fakeit_Letter` | Single random letter | `"g"` |
|
||||||
| `fakeit_LetterN(n uint)` | Generate `n` random letters | `{{ fakeit_LetterN 10 }}` → `"gbRMaRxHki"` |
|
| `fakeit_LetterN(n uint)` | Generate `n` random letters | `{{ fakeit_LetterN 10 }}` → `"gbRMaRxHki"` |
|
||||||
| `fakeit_Lexify(pattern string)` | Replace `?` with random letters | `{{ fakeit_Lexify "?????@??????.com" }}` → `"billy@mister.com"` |
|
| `fakeit_Lexify(pattern string)` | Replace `?` with random letters | `{{ fakeit_Lexify "?????@??????.com" }}` → `"billy@mister.com"` |
|
||||||
|
|||||||
6
go.mod
6
go.mod
@@ -13,7 +13,7 @@ require (
|
|||||||
github.com/valyala/fasthttp v1.69.0
|
github.com/valyala/fasthttp v1.69.0
|
||||||
go.aykhans.me/utils v1.0.7
|
go.aykhans.me/utils v1.0.7
|
||||||
go.yaml.in/yaml/v4 v4.0.0-rc.3
|
go.yaml.in/yaml/v4 v4.0.0-rc.3
|
||||||
golang.org/x/net v0.48.0
|
golang.org/x/net v0.49.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -50,6 +50,6 @@ require (
|
|||||||
github.com/yuin/goldmark v1.7.16 // indirect
|
github.com/yuin/goldmark v1.7.16 // indirect
|
||||||
github.com/yuin/goldmark-emoji v1.0.6 // indirect
|
github.com/yuin/goldmark-emoji v1.0.6 // indirect
|
||||||
golang.org/x/sys v0.40.0 // indirect
|
golang.org/x/sys v0.40.0 // indirect
|
||||||
golang.org/x/term v0.38.0 // indirect
|
golang.org/x/term v0.39.0 // indirect
|
||||||
golang.org/x/text v0.32.0 // indirect
|
golang.org/x/text v0.33.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
12
go.sum
12
go.sum
@@ -101,15 +101,15 @@ go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
|
|||||||
go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
@@ -180,6 +180,15 @@ func validateTemplateValues(values []string, funcMap template.FuncMap) []types.F
|
|||||||
return validationErrors
|
return validationErrors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func validateTemplateURLPath(urlPath string, funcMap template.FuncMap) []types.FieldValidationError {
|
||||||
|
if err := validateTemplateString(urlPath, funcMap); err != nil {
|
||||||
|
return []types.FieldValidationError{
|
||||||
|
types.NewFieldValidationError("URL.Path", urlPath, err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func ValidateTemplates(config *Config) []types.FieldValidationError {
|
func ValidateTemplates(config *Config) []types.FieldValidationError {
|
||||||
// Create template function map using the same functions as sarin package
|
// Create template function map using the same functions as sarin package
|
||||||
randSource := sarin.NewDefaultRandSource()
|
randSource := sarin.NewDefaultRandSource()
|
||||||
@@ -190,6 +199,11 @@ func ValidateTemplates(config *Config) []types.FieldValidationError {
|
|||||||
|
|
||||||
var allErrors []types.FieldValidationError
|
var allErrors []types.FieldValidationError
|
||||||
|
|
||||||
|
// Validate URL path
|
||||||
|
if config.URL != nil {
|
||||||
|
allErrors = append(allErrors, validateTemplateURLPath(config.URL.Path, funcMap)...)
|
||||||
|
}
|
||||||
|
|
||||||
// Validate methods
|
// Validate methods
|
||||||
allErrors = append(allErrors, validateTemplateMethods(config.Methods, funcMap)...)
|
allErrors = append(allErrors, validateTemplateMethods(config.Methods, funcMap)...)
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ func NewRequestGenerator(
|
|||||||
localRand := rand.New(randSource)
|
localRand := rand.New(randSource)
|
||||||
templateFuncMap := NewDefaultTemplateFuncMap(randSource)
|
templateFuncMap := NewDefaultTemplateFuncMap(randSource)
|
||||||
|
|
||||||
|
pathGenerator, isPathGeneratorDynamic := createTemplateFunc(requestURL.Path, templateFuncMap)
|
||||||
methodGenerator, isMethodGeneratorDynamic := NewMethodGeneratorFunc(localRand, methods, templateFuncMap)
|
methodGenerator, isMethodGeneratorDynamic := NewMethodGeneratorFunc(localRand, methods, templateFuncMap)
|
||||||
paramsGenerator, isParamsGeneratorDynamic := NewParamsGeneratorFunc(localRand, params, templateFuncMap)
|
paramsGenerator, isParamsGeneratorDynamic := NewParamsGeneratorFunc(localRand, params, templateFuncMap)
|
||||||
headersGenerator, isHeadersGeneratorDynamic := NewHeadersGeneratorFunc(localRand, headers, templateFuncMap)
|
headersGenerator, isHeadersGeneratorDynamic := NewHeadersGeneratorFunc(localRand, headers, templateFuncMap)
|
||||||
@@ -51,35 +52,45 @@ func NewRequestGenerator(
|
|||||||
|
|
||||||
valuesGenerator := NewValuesGeneratorFunc(values, templateFuncMap)
|
valuesGenerator := NewValuesGeneratorFunc(values, templateFuncMap)
|
||||||
|
|
||||||
|
var (
|
||||||
|
data valuesData
|
||||||
|
path string
|
||||||
|
err error
|
||||||
|
)
|
||||||
return func(req *fasthttp.Request) error {
|
return func(req *fasthttp.Request) error {
|
||||||
req.SetRequestURI(requestURL.Path)
|
|
||||||
req.Header.SetHost(requestURL.Host)
|
req.Header.SetHost(requestURL.Host)
|
||||||
|
|
||||||
data, err := valuesGenerator()
|
data, err = valuesGenerator()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := methodGenerator(req, data); err != nil {
|
path, err = pathGenerator(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.SetRequestURI(path)
|
||||||
|
|
||||||
|
if err = methodGenerator(req, data); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
bodyTemplateFuncMapData.ClearFormDataContenType()
|
bodyTemplateFuncMapData.ClearFormDataContenType()
|
||||||
if err := bodyGenerator(req, data); err != nil {
|
if err = bodyGenerator(req, data); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := headersGenerator(req, data); err != nil {
|
if err = headersGenerator(req, data); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if bodyTemplateFuncMapData.GetFormDataContenType() != "" {
|
if bodyTemplateFuncMapData.GetFormDataContenType() != "" {
|
||||||
req.Header.Add("Content-Type", bodyTemplateFuncMapData.GetFormDataContenType())
|
req.Header.Add("Content-Type", bodyTemplateFuncMapData.GetFormDataContenType())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := paramsGenerator(req, data); err != nil {
|
if err = paramsGenerator(req, data); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := cookiesGenerator(req, data); err != nil {
|
if err = cookiesGenerator(req, data); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +98,8 @@ func NewRequestGenerator(
|
|||||||
req.URI().SetScheme("https")
|
req.URI().SetScheme("https")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}, isMethodGeneratorDynamic ||
|
}, isPathGeneratorDynamic ||
|
||||||
|
isMethodGeneratorDynamic ||
|
||||||
isParamsGeneratorDynamic ||
|
isParamsGeneratorDynamic ||
|
||||||
isHeadersGeneratorDynamic ||
|
isHeadersGeneratorDynamic ||
|
||||||
isCookiesGeneratorDynamic ||
|
isCookiesGeneratorDynamic ||
|
||||||
|
|||||||
Reference in New Issue
Block a user