lumetas / ht
HTTP tool for executing requests from .ht files
Installs: 7
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/lumetas/ht
Requires
- php: >=8.0
- symfony/yaml: ^6.0 || ^7.0 || ^8.0
This package is not auto-updated.
Last update: 2026-03-01 01:41:11 UTC
README
A command-line HTTP client for testing APIs, written in PHP. Inspired by ht.nvim (HTTP Toolkit for Neovim).
Features
- Define multiple HTTP requests in a single
.htfile - Support for global and local variables
- Variable substitution with
{{variable}}syntax - Environment variables support (
$VAR) - Command execution in variables (
>command,>>commandfor cached) - Pre and Post scripts (full PHP)
- Request chaining with
$api->send() - Output control with
$output->write()and$output->append() - JSON body support
- Headers and query parameters
- Configurable timeout, insecure SSL, proxy
Installation
chmod +x ht ./ht --help
Usage
ht <file.ht> [requestName] # Run specific request (body only) ht -a <file.ht> [requestName] # Run with all info (headers, status, etc.) ht --all <file.ht> [requestName] # Same as -a
If requestName is omitted, defaults to main.
Output Modes
Default (body only):
$ ht file.ht myRequest
{"success":true,"data":{"id":1}}
With -a flag (all information):
$ ht -a file.ht myRequest
[INFO] Executing: GET https://api.example.com/data
=== Request ===
GET https://api.example.com/data
Headers:
Content-Type: application/json
Body:
{"key":"value"}
================
=== Response ===
Status: 200
Headers:
HTTP/1.1 200 OK
Content-Type: application/json
Body:
{"success":true,"data":{"id":1}}
================
File Format
Basic Structure
### #requestName METHOD https://api.example.com/endpoint Header-Name: header-value { "body": "json" }
Comments
Lines starting with # are comments:
# This is a comment ### #myRequest GET https://api.example.com # Another comment
Variables
Global variables (available to all requests):
@baseUrl = https://api.example.com @apiKey = secret-key ### #main GET {{baseUrl}}/users Authorization: Bearer {{apiKey}}
Local variables (available only in current request):
### #getUser @userId = 123 GET https://api.example.com/users/{{userId}}
Variable types:
| Syntax | Description |
|---|---|
{{variable}} |
Regular variable |
{{$ENV_VAR}} |
Environment variable |
{{>command}} |
Execute command and use output |
{{>>command}} |
Execute command, cache result |
Configuration
Variables starting with @cfg. configure the request:
@cfg.timeout = 5000 # Timeout in milliseconds @cfg.insecure = true # Skip SSL verification @cfg.dry_run = true # Don't send request @cfg.proxy = http://proxy:8080
Headers
### #apiRequest GET https://api.example.com/data Content-Type: application/json Accept: application/json X-Custom-Header: value
Query Parameters
### #search GET https://api.example.com/search name=John age=30
Request Body
### #create POST https://api.example.com/users Content-Type: application/json { "name": "John", "email": "john@example.com" }
Scripts
Scripts are written in PHP and executed before or after the request.
Post-Script (after request)
### #login POST https://api.example.com/login Content-Type: application/json { "username": "admin", "password": "secret" } <?php // Access response $api->set('token', $response->json_body()['token']); $output->write('Login successful!'); // Available functions: var_dump($var); print_r($var); json_decode($json); json_encode($data); ?>
Pre-Script (before request)
### #modifyRequest GET https://api.example.com/data <?php #pre // Modify request before sending $request->method = 'POST'; $request->url = 'https://api.example.com/create'; $request->headers['X-Custom'] = 'value'; $request->body = '{"modified": true}'; $api->set('some_var', 'value'); #post // This runs after response $output->append('Request completed'); ?>
Script API
$response (post-script only):
| Property/Method | Description |
|---|---|
$response->body |
Response body as string |
$response->status_code |
HTTP status code |
$response->headers |
Headers as associative array |
$response->json_body() |
Parse JSON body (cached) |
$api:
| Method | Description |
|---|---|
$api->set(key, value) |
Set global variable |
$api->get(key) |
Get global variable |
$api->send(name) |
Execute another request |
$output:
| Method | Description |
|---|---|
$output->write(text) |
Replace response body with custom text |
$output->append(text) |
Add text after response body |
Output behavior:
| Mode | write() |
append() |
No output call |
|---|---|---|---|
Without -a |
Custom text only | Body + custom | Body only |
With -a |
Full info + custom | Full info + custom | Full info |
Global functions:
var_dump($var)- Dump variableprint_r($var)- Print readablejson_decode($json, true)- Parse JSONjson_encode($data)- Encode to JSON
Request Chaining
### #first GET https://api.example.com/step1 <?php $api->set('id', $response->json_body()['id']); $api->send('second'); ?> ### #second GET https://api.example.com/step2/{{id}}
Examples
Simple GET
@host = jsonplaceholder.org ### #main GET https://{{host}}/users/1
POST with JSON
### #create POST https://api.example.com/users Content-Type: application/json { "name": "John Doe", "email": "john@example.com" }
Authentication Flow
@host = api.example.com ### #login POST https://{{host}}/auth/login Content-Type: application/json { "username": "admin", "password": "secret" } <?php $api->set('token', $response->json_body()['token']); ?> ### #getData GET https://{{host}}/data Authorization: Bearer {{token}}
Custom Output
### #custom GET https://api.example.com/data <?php // Replace response with custom output $output->write('Request completed successfully!'); ?> ### #append GET https://api.example.com/data <?php // Add info after response $output->append('Cached: yes'); ?>
Testing
Start the mock server:
php -S 127.0.0.1:8888 api.php
Run tests:
./run-tests.sh
Requirements
- PHP 8.1+