5 min read

HTTP Requests, Mutations & Responses

Guide to configuring HTTP methods, mutation pipelines, response assertions, and payload handling within Budment's two-phase execution architecture.

Budment's HTTP client is tightly integrated into its native Go execution engine, managing persistent connection pools, high-throughput I/O, and automated metric extraction without burdening the JavaScript runtime.

1. HTTP Methods & Target URLs

Initialize request builders using the http factory. These builders must be exported within a scenario pipeline to be executed by the engine.

typescript
import { http } from "@budment/sdk";

export default [
  http.get("https://api.example.com/v1/users"),
  http.post("https://api.example.com/v1/users"),
  http.put("https://api.example.com/v1/users/42"),
  http.delete("https://api.example.com/v1/users/42"),
];

Target URLs support dynamic expressions using SDK template strings, which are resolved natively by the Go engine during execution:

typescript
import { http, env, get } from "@budment/sdk";

export default [
  http.get(
    `https://${env("API_HOST", "api.example.com")}/users/${get("user_id")}`,
  ),
];

2. Pre-Request Pipeline

The .before() method is not just a single configuration object; it is a sequential execution pipeline. It accepts variadic arguments (...args), allowing you to chain declarative objects, dynamic JavaScript hooks, and operational nodes (like logs or barriers) in a strict, predictable order.

typescript
import { http, log, barrier } from "@budment/sdk";

http.post("https://api.example.com/orders").before(
  // 1. JS Hook executes first
  (req) => {
    req.set({ ts: Date.now() });
  },

  // 2. Logs output to the console
  log("Order request initialized"),

  // 3. Workers wait here until the quorum is met before firing the HTTP request
  barrier("sync_orders", { quorum: 50 }),
);

Declarative Mutations (Static)

For static configurations, pass a declarative object containing headers and body. The SDK automatically serializes plain JavaScript objects into JSON during the compilation phase.

typescript
import { http, env, get, random } from "@budment/sdk";

http.post("https://api.example.com/orders").before({
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${env("AUTH_TOKEN")}`,
  },
  body: {
    order_id: random.uuid(),
    quantity: 2,
    customer_ref: get("customer_id"),
  },
});

Dynamic URLs & Scope Injection

Budment treats target URLs as reactive templates. To mutate paths or query parameters dynamically during execution, embed SDK variables directly into your URL string. The engine instantly evaluates the latest Virtual User scope on demand before dispatching the request.

The HttpRequest Interface

3. Response Pipeline

Similar to .before(), the .after() pipeline sequentially processes response codes, asserts integrity, and extracts state.

Declarative Assertions

Extract JSON fields natively without waking up a JavaScript VM, storing them directly into the Virtual User's memory scope:

typescript
http.post("https://api.example.com/auth/login").after({
  expect: {
    status: 200,
    bodyContains: "access_token",
  },
  extract: {
    // Extracts data.token and stores it as 'jwt_token' in VU context
    "data.token": "jwt_token",
  },
});

Dynamic Response Hooks

Use a JavaScript callback when validations require complex branching. Calling res.json() without arguments parses the entire payload into a JavaScript object:

typescript
import { http, set, abort } from "@budment/sdk";

http.get("https://api.example.com/account/profile").after((res, req) => {
  // 1. Query specific field using GJSON syntax
  const balance = res.json<number>("account.current_balance");

  // 2. Or parse the entire response body
  const fullProfile = res.json();

  if (balance === undefined || balance < 0) {
    abort("Invalid account balance detected. Halting iteration.");
  }

  set("user_profile", fullProfile);
});

The HttpResponse Interface

typescript
export interface HttpResponse {
    readonly status: number; // HTTP status code (0 for socket/dial errors)
    readonly headers: Record<string, string>;
    readonly error?: string; // Network timeout or socket error description

    // Extracts via GJSON, or parses full body if no selector is provided
    json<T = any>(selector?: string): T | undefined;
}

4. File

Uploading binary assets (images, PDFs, archives) requires preserving raw byte streams. Use open(path, 'b') to read the file into an ArrayBuffer, then attach it using req.file():

When req.set() receives fields containing req.file() descriptors, the Go engine automatically formats the payload as multipart/form-data and injects the corresponding Content-Type boundary header.

5. Error & Failure Classifications

Budment strictly distinguishes between business assertion failures, standard HTTP errors, and physical network errors. These define whether a request is flagged as successful (IsSuccess) in the engine's SLA metrics:

Classification Trigger Condition Engine Behavior & Metric Impact
HTTP Status Error Server returns Code < 200 or Code >= 400. Automatically marks IsSuccess = false. Reflected in standard http_req_failed rates.
Assertion Failure Fails a declarative expect condition. Automatically marks IsSuccess = false.
Network/I/O Error Socket timeouts, DNS failures, connection resets. Returns Status: 0, sets ErrorMessage. Marks IsSuccess = false.
Logic Failure Manual call to fail(reason) in JS hook. Logs a FAIL event and continues executing the pipeline.
Iteration Abort Manual call to abort(reason) in JS hook. Immediately terminates the JS hook, skips remaining pipeline steps, and restarts the VU iteration.