Getting Started
This guide explains how to get started with async-http-capture
, a Ruby gem for recording and replaying HTTP requests using Protocol::HTTP.
Installation
Add the gem to your project:
$ bundle add async-http-capture
Core Concepts
async-http-capture
has several core concepts:
- A
class Async::HTTP::Capture::Middleware
which captures HTTP requests and responses as they pass through your application. - An
class Async::HTTP::Capture::Interaction
which represents a single HTTP request/response pair with lazy Protocol::HTTP object construction. - A
class Async::HTTP::Capture::Cassette
which is a collection of interactions that can be loaded from and saved to JSON files. - A
class Async::HTTP::Capture::CassetteStore
which provides timestamped storage, saving each interaction to a separate file named by timestamp. - A
class Async::HTTP::Capture::ConsoleStore
which logs interactions to the console for debugging purposes.
Usage
The basic workflow involves:
- Recording: Capture HTTP interactions using middleware
- Storage: Save interactions using pluggable store backends
- Replay: Load and replay recorded interactions
Basic Recording
Here's how to record HTTP interactions to files:
require "async/http/capture"
# Create a store that saves to timestamped files:
store = Async::HTTP::Capture::CassetteStore.new("interactions")
# Create your application
app = ->(request) {Protocol::HTTP::Response[200, {}, ["OK"]]}
# Wrap it with recording middleware:
middleware = Async::HTTP::Capture::Middleware.new(app, store: store)
# Make requests - they will be automatically recorded:
request = Protocol::HTTP::Request["GET", "/users"]
response = middleware.call(request)
# This creates a file like recordings/20250821-105406-271633-12345-67890.json
Recording with Console Output
For debugging, you can log interactions to the console:
# Create a console store for debugging:
console_store = Async::HTTP::Capture::ConsoleStore.new
middleware = Async::HTTP::Capture::Middleware.new(app, store: console_store)
# This will log interactions to console:
middleware.call(request)
# Output: "Recorded: GET /users"
Loading and Replaying Interactions
# Load recorded interactions:
cassette = Async::HTTP::Capture::Cassette.load("interactions")
# Option 1: Use the built-in replay method for application warmup
cassette.replay(app)
# Option 2: Manual iteration for custom processing
cassette.each do |interaction|
request = interaction.request # Lazy Protocol::HTTP::Request construction
response = app.call(request) # Send to your app
puts "#{request.method} #{request.path} -> #{response.status}"
end
Recording HTTP Requests and Responses
The middleware automatically records both requests and responses:
middleware = Async::HTTP::Capture::Middleware.new(
app,
store: store
)
response = middleware.call(request)
# Both request and response are recorded.
Timestamped Storage
Each interaction is saved to a file named with timestamp, process ID, and object ID, providing several benefits:
recordings/
├── 20250821-105406-271633-12345-67890.json # GET /users
├── 20250821-105006-257022-12346-67891.json # POST /orders
└── 20250820-101234-567890-12347-67892.json # GET /health
Benefits:
- Chronological ordering: Files sorted by timestamp
- Parallel-safe: Multiple processes can write without conflicts
- Human-readable: Timestamps are easy to understand
Application Warmup
A common use case is warming up your application with recorded traffic:
require "async/http/capture"
# Step 1: Record requests during development/testing
endpoint = Async::HTTP::Endpoint.parse("https://api.example.com")
store = Async::HTTP::Capture::CassetteStore.new("warmup_interactions")
recording_middleware = Async::HTTP::Capture::Middleware.new(
nil,
store: store
)
client = Async::HTTP::Client.new(endpoint, middleware: [recording_middleware])
# Make the requests you want to record
Async do
client.get("/health")
client.get("/api/popular-items")
client.post("/api/user-sessions", {user_id: 123})
end
# Step 2: Use recorded interactions to warm up your application
cassette = Async::HTTP::Capture::Cassette.load("warmup_interactions")
app = MyApplication.new
puts "Warming up with #{cassette.interactions.size} recorded interactions..."
cassette.each do |interaction|
request = interaction.request
begin
app_response = app.call(request)
puts "Warmed up #{request.method} #{request.path} -> #{app_response.status}"
rescue => error
puts "Warning: #{request.method} #{request.path} -> #{error.message}"
end
end
puts "Warmup complete!"
Custom Storage Backends
You can create custom storage backends by implementing the Async::HTTP::Capture::Store
interface:
class MyCustomStore
def call(interaction)
# Handle the interaction as needed
# e.g., send to a database, external service, etc.
puts "Custom handling: #{interaction.request.method} #{interaction.request.path}"
end
end
# Use your custom store
custom_store = MyCustomStore.new
middleware = Async::HTTP::Capture::Middleware.new(app, store: custom_store)