日本語 | English
An MITM HTTP/HTTPS proxy for recording and replaying web traffic with precise timing control. Designed for PageSpeed optimization, performance testing, and automated web performance analysis.
- Recording Mode: Capture HTTP/HTTPS traffic as MITM proxy with timing metadata
- Playback Mode: Replay recorded traffic with accurate TTFB and transfer duration simulation
- Content Processing: Automatic beautification of minified HTML/CSS/JS for editability
- HTTPS Support: Transparent HTTPS proxy using self-signed certificates
- Timing Accuracy: ±10% timing precision for TTFB and transfer duration
- Multi-Platform: Supports macOS (ARM64/x86_64), Linux (x86_64/ARM64), Windows (x86_64)
- Language Wrappers: Go and TypeScript/Node.js bindings for easy integration
Download from GitHub Releases:
# macOS ARM64
curl -L https://github.com/pagespeed-quest/http-playback-proxy/releases/latest/download/http-playback-proxy-darwin-arm64.tar.gz | tar xz
# Linux x86_64
curl -L https://github.com/pagespeed-quest/http-playback-proxy/releases/latest/download/http-playback-proxy-linux-amd64.tar.gz | tar xz
# Windows x86_64
# Download http-playback-proxy-windows-amd64.zip and extractBasic recording (auto-searches port from 18080):
./http-playback-proxy recording https://example.comFull options:
./http-playback-proxy recording https://example.com \
--port 18080 \ # Proxy port (default: 18080, auto-search if occupied)
--device mobile \ # Device type: mobile or desktop (default: mobile)
--inventory ./my-session # Output directory (default: ./inventory)Recording workflow:
- Start proxy:
./http-playback-proxy recording https://example.com - Configure browser proxy to
127.0.0.1:18080(or displayed port) - Visit website in browser
- Press
Ctrl+C(or send SIGTERM/SIGINT) to stop and save recording - Check
./inventory/index.jsonand./inventory/contents/
Manual browsing (no entry URL):
# Start proxy and browse manually
./http-playback-proxy recording --port 18080Basic playback:
./http-playback-proxy playback --inventory ./my-sessionFull options:
./http-playback-proxy playback \
--port 18080 \ # Proxy port (default: 18080, auto-search if occupied)
--inventory ./my-session # Recorded data directory (default: ./inventory)Playback workflow:
- Start proxy:
./http-playback-proxy playback --inventory ./my-session - Configure browser proxy to
127.0.0.1:18080(or displayed port) - Visit same website - responses match recorded timing (±10%)
- Press
Ctrl+C(or send SIGTERM/SIGINT) to stop
Chrome/Chromium:
# macOS/Linux
google-chrome --proxy-server="127.0.0.1:8080"
# Windows
chrome.exe --proxy-server="127.0.0.1:8080"Firefox: Settings → Network Settings → Manual proxy configuration:
- HTTP Proxy:
127.0.0.1, Port:8080 - Check "Also use this proxy for HTTPS"
System-wide (macOS):
# Set proxy
networksetup -setwebproxy Wi-Fi 127.0.0.1 8080
networksetup -setsecurewebproxy Wi-Fi 127.0.0.1 8080
# Unset proxy
networksetup -setwebproxystate Wi-Fi off
networksetup -setsecurewebproxystate Wi-Fi offgit clone https://github.com/pagespeed-quest/http-playback-proxy.git
cd http-playback-proxy
cargo build --releaseBinary location: target/release/http-playback-proxy
go get github.com/pagespeed-quest/http-playback-proxy/golangRecording example:
package main
import (
"fmt"
"time"
proxy "github.com/pagespeed-quest/http-playback-proxy/golang"
)
func main() {
// Start recording proxy
p, err := proxy.StartRecording(proxy.RecordingOptions{
EntryURL: "https://example.com",
Port: 8080,
DeviceType: proxy.DeviceTypeMobile,
InventoryDir: "./inventory",
})
if err != nil {
panic(err)
}
fmt.Printf("Recording proxy started on port %d\n", p.Port)
// Wait or do HTTP requests through the proxy
time.Sleep(30 * time.Second)
// Stop and save recording
if err := p.Stop(); err != nil {
panic(err)
}
// Load and analyze inventory
inventory, err := p.GetInventory()
if err != nil {
panic(err)
}
fmt.Printf("Recorded %d resources\n", len(inventory.Resources))
}Playback example:
// Start playback proxy
p, err := proxy.StartPlayback(proxy.PlaybackOptions{
Port: 8080,
InventoryDir: "./inventory",
})
if err != nil {
panic(err)
}
// Wait for requests
time.Sleep(30 * time.Second)
// Stop playback
p.Stop()Working with inventory:
// Load inventory from index.json
inventory, err := proxy.LoadInventory("./inventory/index.json")
if err != nil {
panic(err)
}
// Iterate resources
for i, resource := range inventory.Resources {
fmt.Printf("%d: %s %s (TTFB: %dms)\n",
i, resource.Method, resource.URL, resource.TtfbMs)
// Get content file path
if resource.ContentFilePath != nil {
contentPath := proxy.GetResourceContentPath("./inventory", &resource)
// Read content file...
}
}See golang/README.md for full API documentation.
npm install http-playback-proxyRecording example:
import { startRecording } from 'http-playback-proxy';
async function record() {
// Start recording proxy
const proxy = await startRecording({
entryUrl: 'https://example.com',
port: 8080,
deviceType: 'mobile',
inventoryDir: './inventory',
});
console.log(`Recording proxy started on port ${proxy.port}`);
// Wait or do HTTP requests through the proxy
await new Promise(resolve => setTimeout(resolve, 30000));
// Stop and save recording
await proxy.stop();
// Load and analyze inventory
const inventory = await proxy.getInventory();
console.log(`Recorded ${inventory.resources.length} resources`);
}
record().catch(console.error);Playback example:
import { startPlayback } from 'http-playback-proxy';
async function playback() {
// Start playback proxy
const proxy = await startPlayback({
port: 8080,
inventoryDir: './inventory',
});
console.log(`Playback proxy started on port ${proxy.port}`);
// Wait for requests
await new Promise(resolve => setTimeout(resolve, 30000));
// Stop playback
await proxy.stop();
}
playback().catch(console.error);Working with inventory:
import { loadInventory, getResourceContentPath } from 'http-playback-proxy';
// Load inventory
const inventory = await loadInventory('./inventory/index.json');
// Iterate resources
for (const [i, resource] of inventory.resources.entries()) {
console.log(`${i}: ${resource.method} ${resource.url} (TTFB: ${resource.ttfbMs}ms)`);
// Get content file path
if (resource.contentFilePath) {
const contentPath = getResourceContentPath('./inventory', resource);
// Read content file...
}
}See typescript/README.md for full API documentation.
- Runtime: Tokio async runtime for concurrent request handling
- HTTP Stack: Hyper 1.0, Hyper-util, Tower/Tower-http
- MITM Proxy: Hudsucker 0.24 with rcgen-ca for self-signed certificates
- Content Processing: Automatic beautification (prettyish-html, prettify-js)
- Compression: gzip, deflate, brotli support (flate2, brotli crates)
- Encoding: Charset detection and UTF-8 conversion (encoding_rs)
Recordings are stored as:
index.json: Metadata for all resources (URLs, timing, headers)contents/: Content files organized by method/protocol/path
Inventory Structure:
{
"entryUrl": "https://example.com",
"deviceType": "mobile",
"resources": [
{
"method": "GET",
"url": "https://example.com/style.css",
"ttfbMs": 150,
"mbps": 2.5,
"statusCode": 200,
"rawHeaders": {
"content-type": "text/css; charset=utf-8"
},
"contentEncoding": "gzip",
"contentFilePath": "get/https/example.com/style.css",
"minify": true
}
]
}Go: Process manager wrapper with Inventory helpers
- Manages binary lifecycle (start/stop)
- Type-safe Inventory reading/writing
- Goroutine-based request handling
TypeScript/Node.js: Similar wrapper for Node.js ecosystem
- Promise-based API
- npm package distribution
- Full TypeScript type definitions
cargo test # All unit tests
cargo test recording # Recording module tests
cargo test playback # Playback module tests
cargo test -- --nocapture # With detailed outputLocated in tests/integration_test.rs. Full end-to-end Rust test:
cargo test --test integration_test --release -- --nocaptureTests: Recording → Inventory saving → Playback → Content verification
Located in e2e/. Tests core binary functionality:
cd e2e
make test-all # All E2E tests
make test-performance # Performance/stress test
make test-minimum # Timing accuracy test (6 scenarios)
make test-content # Content beautification testMinimum Timing Test (e2e/minimum/):
- Tests 6 scenarios with different file sizes and latencies
- Verifies ±10% timing accuracy for TTFB and transfer duration
- Runtime: ~60-90 seconds
Content Beautification Test (e2e/content/):
- Verifies minified HTML/CSS/JS are beautified during recording
- Checks
minify: trueflag in inventory - Ensures content is editable for PageSpeed optimization
- Runtime: ~10 seconds
See e2e/README.md for details.
Located in acceptance/. Tests Go and TypeScript wrappers:
cd acceptance
make test-all # Test both Go and TypeScript wrappers
make test-golang # Go wrapper only
make test-typescript # TypeScript wrapper onlyGo Acceptance Test (acceptance/golang/):
- Verifies Go API (StartRecording, StartPlayback, Stop)
- Tests binary distribution in Go module
- Validates Inventory reading/writing
TypeScript Acceptance Test (acceptance/typescript/):
- Verifies TypeScript API
- Tests binary distribution in npm package
- Validates Promise-based workflow
See acceptance/README.md for details.
Pre-commit checks (run locally):
./check-ci.sh # Runs exact CI checks locallyThis script runs:
cargo fmt --all -- --check- Formatting verificationcargo clippy --all-targets --all-features -- -D warnings- Strict lintingcargo test- All tests (unit + integration)
Multi-platform automated release:
1. Create tag: git tag v0.0.0 && git push origin v0.0.0
2. GitHub Actions: Build binaries for 5 platforms (release.yml)
3. Create Release: Publish to GitHub Releases
4. Auto-trigger: update-binaries.yml workflow
5. Create PR: Binaries → golang/bin/ and typescript/bin/
6. Run Acceptance: Test all platforms (acceptance-test.yml)
7. Merge PR: After tests pass
8. Tag Go module: git tag golang/v0.0.0 && git push
9. Publish npm: cd typescript && npm publish
Supported platforms:
- darwin-arm64 (macOS Apple Silicon)
- darwin-amd64 (macOS Intel)
- linux-amd64 (Linux x86_64)
- linux-arm64 (Linux ARM64)
- windows-amd64 (Windows x86_64)
.
├── src/ # Rust core implementation
│ ├── recording/ # Recording mode (MITM proxy, response processing)
│ ├── playback/ # Playback mode (timing control, transaction matching)
│ └── ...
├── tests/ # Rust integration tests
├── e2e/ # Core E2E tests (performance, timing, content)
├── acceptance/ # Language wrapper acceptance tests
├── golang/ # Go language wrapper + tests
├── typescript/ # TypeScript/Node.js wrapper + tests
└── .github/workflows/ # CI/CD workflows
Pre-commit checks (recommended):
./check-ci.sh # Runs exact CI checks locallyThis script runs:
cargo fmt --all -- --check- Formatting verificationcargo clippy --all-targets --all-features -- -D warnings- Strict lintingcargo test- All tests (unit + integration)
Individual commands:
cargo fmt # Auto-format code
cargo clippy # Lint check
cargo test # Run all tests
cargo build --release # Release buildRecording:
- MITM proxy using Hudsucker with self-signed certificates
- Per-connection FIFO queues for request/response correlation
- Automatic content beautification (minified HTML/CSS/JS)
- Multi-value header support (e.g., Set-Cookie)
- UTF-8 conversion and charset detection
Playback:
- Precise timing control (±10% accuracy for TTFB and transfer duration)
- Chunk-based response streaming with target times
- Transaction matching by method + host + path + query
- Automatic re-minification and re-encoding
Testability:
- Trait-based dependency injection (FileSystem, TimeProvider)
- Mock implementations for unit testing
- Comprehensive test coverage (unit, integration, E2E, acceptance)
Proxy Connection Issues:
- Verify port availability:
lsof -i :8080 - Check firewall settings
- Verify browser proxy configuration
HTTPS Certificate Errors:
- Browser: Click "Advanced" → "Proceed" (certificate is self-signed)
- System trust: Add certificate to system trust store if needed
Binary Not Found (Tests):
cargo build --release
ls -la target/release/http-playback-proxyPort Conflicts:
- Tests use auto-assigned ports (port 0)
- Kill stuck processes:
lsof -i :8080 && kill -9 <PID>
Timing Inaccuracy:
- Check system load (high CPU/disk usage affects timing)
- Verify network stability
- See minimum timing test for expected tolerances
Contributions are welcome! Please:
- Run
./check-ci.shbefore committing - Add tests for new features
- Update documentation
- Follow existing code style
[Add license information]
- CLAUDE.md - Development guidance for AI assistants
- E2E Tests README - Core E2E testing
- Acceptance Tests README - Language wrapper testing
- Go Wrapper README - Go API documentation
- TypeScript Wrapper README - TypeScript API documentation