From f0606a0f82b6b2ec8c7ace292a5417b51a6dac09 Mon Sep 17 00:00:00 2001 From: Aykhan Shahsuvarov Date: Sat, 14 Feb 2026 03:21:52 +0400 Subject: [PATCH] Add Lua and JavaScript scripting documentation --- README.md | 15 ++--- docs/configuration.md | 132 +++++++++++++++++++++++++++++++++++++++++ docs/examples.md | 122 +++++++++++++++++++++++++++++++++++++ internal/config/cli.go | 4 +- 4 files changed, 264 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3c756d6..fd85ba1 100644 --- a/README.md +++ b/README.md @@ -22,13 +22,14 @@ 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 | -| ---------------------------------------------------------- | --------------------------------- | -| High-performance with low memory footprint | Detailed response body analysis | -| Long-running duration/count based tests | Extensive response statistics | -| Dynamic requests via 320+ template functions | Web UI or complex TUI | -| Multiple proxy protocols
(HTTP, HTTPS, SOCKS5, SOCKS5H) | Scripting or multi-step scenarios | -| Flexible config (CLI, ENV, YAML) | HTTP/2, HTTP/3, WebSocket, gRPC | +| ✅ Supported | ❌ Not Supported | +| ---------------------------------------------------------- | ------------------------------- | +| High-performance with low memory footprint | Detailed response body analysis | +| Long-running duration/count based tests | Extensive response statistics | +| Dynamic requests via 320+ template functions | Web UI or complex TUI | +| Request scripting with Lua and JavaScript | Distributed load testing | +| Multiple proxy protocols
(HTTP, HTTPS, SOCKS5, SOCKS5H) | HTTP/2, HTTP/3, WebSocket, gRPC | +| Flexible config (CLI, ENV, YAML) | Plugins / extensions ecosystem | ## Installation diff --git a/docs/configuration.md b/docs/configuration.md index 51d555d..489998b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -36,6 +36,8 @@ Use `-s` or `--show-config` to see the final merged configuration before sending | [Cookies](#cookies) | `cookies`
(object) | `-cookie` / `-C`
(string / []string) | `SARIN_COOKIE`
(string) | - | HTTP cookies | | [Proxy](#proxy) | `proxy`
(string / []string) | `-proxy` / `-X`
(string / []string) | `SARIN_PROXY`
(string) | - | Proxy URL(s) | | [Values](#values) | `values`
(string / []string) | `-values` / `-V`
(string / []string) | `SARIN_VALUES`
(string) | - | Template values (key=value) | +| [Lua](#lua) | `lua`
(string / []string) | `-lua`
(string / []string) | `SARIN_LUA`
(string) | - | Lua script(s) | +| [Js](#js) | `js`
(string / []string) | `-js`
(string / []string) | `SARIN_JS`
(string) | - | JavaScript script(s) | --- @@ -374,3 +376,133 @@ values: | ```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"}`). + +**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"]`). + +**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; }' +``` diff --git a/docs/examples.md b/docs/examples.md index 060adf5..eeb75b2 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -15,6 +15,7 @@ This guide provides practical examples for common Sarin use cases. - [Docker Usage](#docker-usage) - [Dry Run Mode](#dry-run-mode) - [Show Configuration](#show-configuration) +- [Scripting](#scripting) --- @@ -894,3 +895,124 @@ headers: ``` + +## Scripting + +Transform requests using Lua or JavaScript scripts. Scripts run after template rendering, before the request is sent. + +**Add a custom header with Lua:** + +```sh +sarin -U http://example.com/api -r 1000 -c 10 \ + -lua 'function transform(req) req.headers["X-Custom"] = "my-value" return req end' +``` + +
+YAML equivalent + +```yaml +url: http://example.com/api +requests: 1000 +concurrency: 10 +lua: | + function transform(req) + req.headers["X-Custom"] = "my-value" + return req + end +``` + +
+ +**Modify request body with JavaScript:** + +```sh +sarin -U http://example.com/api/data -r 1000 -c 10 \ + -M POST \ + -H "Content-Type: application/json" \ + -B '{"name": "test"}' \ + -js 'function transform(req) { var body = JSON.parse(req.body); body.timestamp = Date.now(); req.body = JSON.stringify(body); return req; }' +``` + +
+YAML equivalent + +```yaml +url: http://example.com/api/data +requests: 1000 +concurrency: 10 +method: POST +headers: + Content-Type: application/json +body: '{"name": "test"}' +js: | + function transform(req) { + var body = JSON.parse(req.body); + body.timestamp = Date.now(); + req.body = JSON.stringify(body); + return req; + } +``` + +
+ +**Load script from a file:** + +```sh +sarin -U http://example.com/api -r 1000 -c 10 \ + -lua @./scripts/transform.lua +``` + +
+YAML equivalent + +```yaml +url: http://example.com/api +requests: 1000 +concurrency: 10 +lua: "@./scripts/transform.lua" +``` + +
+ +**Load script from a URL:** + +```sh +sarin -U http://example.com/api -r 1000 -c 10 \ + -js @https://example.com/scripts/transform.js +``` + +
+YAML equivalent + +```yaml +url: http://example.com/api +requests: 1000 +concurrency: 10 +js: "@https://example.com/scripts/transform.js" +``` + +
+ +**Chain multiple scripts (Lua runs first, then JavaScript):** + +```sh +sarin -U http://example.com/api -r 1000 -c 10 \ + -lua @./scripts/auth.lua \ + -lua @./scripts/headers.lua \ + -js @./scripts/body.js +``` + +
+YAML equivalent + +```yaml +url: http://example.com/api +requests: 1000 +concurrency: 10 +lua: + - "@./scripts/auth.lua" + - "@./scripts/headers.lua" +js: "@./scripts/body.js" +``` + +
diff --git a/internal/config/cli.go b/internal/config/cli.go index a79829e..47aa8e6 100644 --- a/internal/config/cli.go +++ b/internal/config/cli.go @@ -42,8 +42,8 @@ Flags: -V, -values []string List of values for templating (e.g. "key1=value1") -T, -timeout time Timeout for the request (e.g. 400ms, 3s, 1m10s) (default %v) -I, -insecure bool Skip SSL/TLS certificate verification (default %v) - -lua []string Lua script for request transformation (inline or @file/@url) - -js []string JavaScript script for request transformation (inline or @file/@url)` + -lua []string Lua script for request transformation (inline or @file/@url) + -js []string JavaScript script for request transformation (inline or @file/@url)` var _ IParser = ConfigCLIParser{}