httper
June 12, 2026 · View on GitHub
A CLI runner for .http files (JetBrains HTTP Client format).
Usage
httper [flags] <file.http> [more.http ...]
Multiple files (or shell/quoted globs like 'requests/*.http') run in one
invocation with an aggregated report and exit code. Files are isolated —
fresh cookie jar and client.global state per file — so results never depend
on argument order.
| Flag | Description |
|---|---|
-env-file <path> | JetBrains-style http-client.env.json |
-env <name> | Environment to use from the env file |
-name <a,b> | Run only the named requests (comma-separated) |
-save | Save responses to .idea/httpRequests/ |
-strict | Treat non-2xx responses as failures |
-insecure | Skip TLS certificate verification (self-signed certs) |
-timeout <seconds> | Request timeout, default 30 (# @timeout wins per request) |
-var key=value | Set a variable (repeatable; overrides @vars and env file) |
-report-junit <path> | Write a JUnit XML report (CI test integration) |
-report-json <path> | Write a JSON report |
-vegeta | Run # @vegeta-marked requests as load tests |
-v | Verbose output (response headers, PASS lines, debug logs) |
-version | Print version and exit |
Exit codes
0— all requests sent, all tests passed1— usage, parse, or I/O error2— failingclient.testassertions or request send errors (with-strict, also any non-2xx response)
File format
Requests are separated by ### lines; text after the hashes becomes the
request title. Comments use # or //.
### Login
# @name login
POST https://example.com/api/login HTTP/2
Content-Type: application/json
{"user": "admin", "password": "{{password}}"}
> {%
client.test("logged in", function () {
client.assert(response.status === 200, "expected 200");
});
client.global.set("token", response.body.token);
%}
### Profile
GET https://example.com/api/profile
?fields=name,email
Authorization: Bearer {{token}}
- The request line is
[METHOD] URL [PROTO]; method defaults toGET, protocol may beHTTP/1.1,HTTP/2, orHTTP/2 (Prior Knowledge). - Lines indented with four spaces continue the previous line (multi-line URLs/query strings and header values).
- Headers run until the first blank line; everything after is the body.
Supported features
Requests
- All standard HTTP methods, headers, and bodies: JSON,
multipart/form-data(with< filenamefile includes),application/x-www-form-urlencoded(pairs may span multiple lines), and any other content type sent verbatim (text/plain, XML, …) - Whole-body file include: a body of just
< ./payload.jsonsends that file's contents (any content type; path sandboxed to the.httpfile's directory, placeholders in the path resolve first) - HTTP/2 and HTTP/2 (Prior Knowledge)
- Bearer auth (header passthrough) and Basic auth
(
Authorization: Basic user passwordis base64-encoded automatically) - Request naming via
# @nameor### title(unnamed requests get#1,#2, …), selection via-name - Default request timeout of 30s, overridable per request with
# @timeout
Per-request directives
| Directive | Effect |
|---|---|
# @name <name> | Name the request |
# @no-redirect | Don't follow redirects |
# @timeout <seconds> | Override the 30s default timeout |
# @no-cookie-jar | Opt this request out of the shared cookie jar |
# @no-log | Print only the status line for this response |
# @vegeta [params] | Declare a load profile for this request (see Load testing) |
Variables
{{placeholders}} are resolved per request just before sending. Precedence
(highest first):
- Request-local variables set in a pre-request script (
request.variables.set) client.globalvalues set by handler scripts-var key=valueCLI flags (above file-declared values, below runtime ones so chaining keeps working)- In-file
@name = valuedefinitions - Env file values (
-env-file+-env); a private sibling file (http-client.private.env.jsonnext tohttp-client.env.json) overlays the public one key-wise — keep secrets there, out of version control
Unknown placeholders stay verbatim. Dynamic variables are computed fresh on each use:
| Variable | Value |
|---|---|
{{$uuid}} / {{$random.uuid}} | Random UUID v4 |
{{$timestamp}} | Unix timestamp (seconds) |
{{$isoTimestamp}} | ISO-8601 / RFC 3339 UTC timestamp |
{{$randomInt}} | Random integer 0–1000 |
{{$random.integer(from, to)}} | Random integer, from inclusive to to exclusive |
{{$random.alphabetic(length)}} | Random letters |
{{$random.email}} | Random email address |
{{$env.NAME}} | OS environment variable (empty + warning when unset) |
Scripts
Pre-request scripts (< {% ... %}) and response handlers (> {% ... %}
inline, or > path/to/file.js) run on an embedded JavaScript engine
(goja). Each script gets a fresh, sandboxed
runtime — no filesystem, network, or process access — with a 5s wall-clock
timeout.
Available API:
| Object | Available in | Members |
|---|---|---|
client | both | test(name, fn) (response handlers only), assert(cond, message), log(...args), global.set(name, value), global.get(name) |
crypto | both | sha256(s), sha1(s), md5(s), hmac.sha256(key, data) / .sha1 / .md5 — hex strings |
request | pre-request | variables.set/get, method(), url() / body() (each with getRaw() and tryGetSubstituted()), headers.all() / headers.findByName(name) (header objects with name(), getRawValue(), tryGetSubstituted()), environment.get(name) (env-file values) |
response | response handlers | status, body (JSON bodies parsed into objects), headers.valueOf(name), headers.valuesOf(name), contentType.mimeType, contentType.charset |
Pre-request scripts see the request as written (placeholders intact) via
getRaw(); tryGetSubstituted() resolves with the variables known at call
time.
client.test results feed the run report and exit code; client.global.set
in one request resolves {{placeholders}} in later ones (request chaining).
gRPC
GRPC request lines execute real gRPC calls (unary and server-streaming):
### say-hello
GRPC grpc://localhost:8081/helloworld.Greeter/SayHello
X-Token: secret
{"name": "world"}
> {%
client.test("greets", function() {
client.assert(response.status === 0, "expected OK");
});
%}
- Target syntax:
[grpc://|grpcs://]host[:port]/package.Service/Method.grpc://forces plaintext,grpcs://TLS; a bare host defaults to TLS except loopback hosts (localhost,127.0.0.1,::1). A missing port defaults to 443 (TLS) / 80 (plaintext).-insecureskips TLS verification. - The message schema is resolved via server
reflection — the server must have
it enabled; no
.protofiles are read. - The JSON body becomes the request message; headers become metadata verbatim
(no Basic-auth encoding; HTTP-only names like
Content-Typeand reservedgrpc-*names are dropped). response.statusis the gRPC status code (0= OK),response.bodythe response message as JSON (an array of messages for server streams), andresponse.headersthe merged header + trailer metadata.-stricttreats any non-OK status as a failure.# @timeoutcaps the whole call including reflection;# @no-logand-savework as for HTTP.- Not supported: client/bidirectional streaming and
< filebody includes.
Load testing (vegeta)
With the -vegeta flag, requests marked # @vegeta are attacked via the
vegeta library instead of being sent
once; without the flag they run as normal single requests, so a file can be
exercised in CI and load-tested with the same content.
### login runs once; its token feeds the attack
# @name login
POST https://example.com/api/login
Content-Type: application/json
{"user": "admin", "password": "{{password}}"}
> {% client.global.set("token", response.body.token); %}
### attacked at 100 req/s for 30s
# @vegeta rate=100/s duration=30s
GET https://example.com/api/profile
Authorization: Bearer {{token}}
- Params (all optional):
rate=N/s|N/m(default50/s),duration(Go duration, default10s),workers,max-workers,connections,max-body(response bytes read per shot). Invalid values are warned about and fall back to the defaults. - The pre-request script runs once and placeholders resolve once; the frozen
request is attacked. Response handler scripts and
client.testare skipped (there is no single response). - Output is vegeta's text report (latencies p50/p95/p99, success ratio,
status code histogram);
# @no-logprints a one-line summary instead. - Any failing shot (non-2xx or transport error) marks the request as errored
→ exit code 2.
-strictadds nothing for attacked requests. # @timeoutand# @no-redirectapply to every shot;-insecureworks as for HTTP. Requests run through the shared HTTP client's cookie jar only for normal sends — the attacker has no jar.GRPCrequests cannot be attacked.
Runs
- Cookie jar shared across all requests in one run (login → authenticated
follow-up works out of the box); opt out per request with
# @no-cookie-jar - Test report summary with CI-friendly exit codes;
-report-junit/-report-jsonwrite machine-readable reports (one testcase perclient.test, send errors as<error>entries) -savewrites each response body to.idea/httpRequests/<timestamp>.<status>.<ext>under the working directory, with the extension sniffed from the body content- Sandboxed file access: body file includes and
> file.jshandler scripts are restricted to the.httpfile's directory; saved responses to the working directory (no..traversal)
Not supported
- WebSocket and GraphQL execution (request lines parse, but nothing is sent)
- gRPC client/bidirectional streaming
>> fileresponse redirects (ignored with a warning)
Installation
Prebuilt binaries for linux/macos/windows (amd64, arm64) are on the releases page.
Or install with Go:
go install github.com/gustofarbi/httper@latest
Or build from source:
go build
./httper -version