HTTPower.Middleware.Dedup (HTTPower v0.16.0)
View SourceIn-flight request deduplication to prevent duplicate operations.
This module prevents duplicate requests from causing duplicate side effects (e.g., double charges, duplicate orders) by tracking in-flight requests and sharing responses with identical concurrent requests.
How It Works
- Request Fingerprinting - Each request gets a hash based on method + URL + body
- In-Flight Tracking - First request executes normally, subsequent identical requests wait
- Response Sharing - When the first request completes, all waiting requests receive the same response
- Automatic Cleanup - Tracking data is automatically removed after configurable TTL
Use Cases
- Prevent double charges from double-clicks on payment buttons
- Prevent duplicate orders from retry storms or race conditions
- Ensure idempotency for critical mutations (POST/PUT/DELETE)
Configuration
# Global configuration
config :httpower, :deduplicate,
enabled: true,
ttl: 5_000 # 5 seconds - how long to track in-flight requests
# Per-request configuration
HTTPower.post(url,
body: payment_data,
deduplicate: true
)
# Or with options
HTTPower.post(url,
body: payment_data,
deduplicate: [
enabled: true,
ttl: 10_000,
key: "custom-dedup-key" # Optional: override hash generation
]
)States
:in_flight- Request currently executing, other identical requests will wait:completed- Brief period after completion to catch race conditions (100-500ms)
Thread Safety
Uses ETS for thread-safe storage and GenServer for coordination.
Summary
Functions
Cancels an in-flight request (called on error/timeout).
Returns a specification to start this module under a supervisor.
Completes a request, storing the response and notifying waiters.
Attempts to deduplicate a request.
Feature callback for the HTTPower pipeline.
Generates a deduplication hash from request parameters.
Starts the request deduplicator GenServer.
Functions
@spec cancel(String.t()) :: :ok
Cancels an in-flight request (called on error/timeout).
Examples
HTTPower.RequestDeduplicator.cancel(request_hash)
Returns a specification to start this module under a supervisor.
See Supervisor.
Completes a request, storing the response and notifying waiters.
Examples
HTTPower.RequestDeduplicator.complete(request_hash, response, config)
@spec deduplicate( String.t(), keyword() ) :: {:ok, :execute} | {:ok, :wait, reference()} | {:ok, any()} | {:error, atom()}
Attempts to deduplicate a request.
Returns:
{:ok, :execute}- First occurrence, proceed with execution{:ok, :wait, ref}- Duplicate request, wait for in-flight to complete{:ok, response}- Request just completed, return cached response{:error, reason}- Deduplication disabled or error occurred
Examples
case HTTPower.RequestDeduplicator.deduplicate(request_hash, config) do
{:ok, :execute} ->
# Execute the request
execute_request()
{:ok, :wait, ref} ->
# Wait for in-flight request to complete
await_response(ref)
{:ok, response} ->
# Use cached response from just-completed request
{:ok, response}
end
Feature callback for the HTTPower pipeline.
Checks for duplicate requests and either executes, waits, or returns cached response.
Returns:
{:ok, request}with dedup info stored in private (first occurrence){:halt, response}if cached response available (short-circuit)- Waits and returns
{:halt, response}for duplicate in-flight requests
Examples
iex> request = %HTTPower.Request{method: :post, url: "https://api.example.com/charge", body: "..."}
iex> HTTPower.Dedup.handle_request(request, [enabled: true])
{:ok, modified_request}
Generates a deduplication hash from request parameters.
Examples
hash = HTTPower.RequestDeduplicator.hash(:post, "https://api.com/charge", ~s({"amount": 100}))
Starts the request deduplicator GenServer.