HTTPower ⚡

HTTPower is a production-ready HTTP client library for Elixir that provides bulletproof HTTP behavior with advanced features like test mode blocking, smart retries, and comprehensive error handling.

Hex.pmDocumentation

Features

🛡️ Production-Ready Reliability

🔧 Developer-Friendly

🚀 Coming Soon (Phase 1)

Adapter Support

HTTPower works with multiple HTTP clients through its adapter system, allowing you to choose the right foundation for your needs:

Req Adapter (Default - Batteries Included)

Perfect for new projects and simple use cases. Req provides automatic JSON handling, compression, and a friendly API.

# Works out of the box - no configuration needed
HTTPower.get("https://api.example.com/users")

Tesla Adapter (Bring Your Own Configuration)

Ideal for existing applications or when you need specific HTTP client features. Use your existing Tesla setup and add HTTPower's production features on top.

# Use your existing Tesla client
tesla_client = MyApp.ApiClient.client()
client = HTTPower.new(
adapter: {HTTPower.Adapter.Tesla, tesla_client}
)
HTTPower.get(client, "/users")

Why adapters? HTTPower's production features (retry logic, circuit breakers, rate limiting, PCI logging) work consistently across all adapters. Choose the HTTP client that fits your architecture, get the reliability patterns you need.

Quick Start

Installation

Add httpower and at least one HTTP client adapter to your dependencies in mix.exs:

def deps do
[
{:httpower, "~> 0.3.0"},
# Choose at least one adapter:
{:req, "~> 0.4.0"}, # Recommended for new projects
# OR
{:tesla, "~> 1.11"} # If you already use Tesla
]
end

Note: HTTPower requires either Req or Tesla. If both are present, Req is used by default (can be overridden with the adapter option).

Basic Usage

# Simple GET request
{:ok, response} = HTTPower.get("https://api.example.com/users")
IO.inspect(response.status) # 200
IO.inspect(response.body) # %{"users" => [...]}
# POST with data
{:ok, response} = HTTPower.post("https://api.example.com/users",
body: "name=John&email=john@example.com",
headers: %{"Content-Type" => "application/x-www-form-urlencoded"}
)
# With configuration options
{:ok, response} = HTTPower.get("https://api.example.com/slow-endpoint",
timeout: 30, # 30 second timeout
max_retries: 5, # Retry up to 5 times
retry_safe: true # Retry connection resets
)
# Error handling (never raises!)
case HTTPower.get("https://unreachable-api.com") do
{:ok, response} ->
IO.puts("Success: #{response.status}")
{:error, error} ->
IO.puts("Failed: #{error.message}") # "Connection refused"
end

Test Mode Integration

HTTPower can completely block real HTTP requests during testing while allowing mocked requests:

# In test_helper.exs or test configuration
Application.put_env(:httpower, :test_mode, true)
# In your tests
defmodule MyAppTest do
use ExUnit.Case
test "API integration with mocking" do
# This will work - uses Req.Test
Req.Test.stub(HTTPower, fn conn ->
Req.Test.json(conn, %{status: "success"})
end)
{:ok, response} = HTTPower.get("https://api.example.com/test",
plug: {Req.Test, HTTPower}
)
assert response.body == %{"status" => "success"}
end
test "real requests are blocked" do
# This will be blocked in test mode
{:error, error} = HTTPower.get("https://real-api.com")
assert error.reason == :network_blocked
end
end

Configuration Options

HTTPower supports extensive configuration for production use:

HTTPower.get("https://api.example.com/endpoint",
# Request configuration
timeout: 60, # Request timeout in seconds (default: 60)
max_retries: 3, # Maximum retry attempts (default: 3)
retry_safe: false, # Retry connection resets (default: false)
# Headers and body
headers: %{
"Authorization" => "Bearer token",
"User-Agent" => "MyApp/1.0"
},
body: "request data",
# SSL and proxy
ssl_verify: true, # Enable SSL verification (default: true)
proxy: :system, # Use system proxy settings
# proxy: [host: "proxy.com", port: 8080], # Custom proxy
# Additional Req options are passed through
connect_timeout: 15_000
)

Error Handling

HTTPower provides comprehensive error handling with clean result tuples:

case HTTPower.get("https://api.example.com") do
{:ok, %HTTPower.Response{status: 200, body: body}} ->
# Success case
process_data(body)
{:ok, %HTTPower.Response{status: 404}} ->
# Handle 404 - still a successful HTTP response
handle_not_found()
{:error, %HTTPower.Error{reason: :timeout}} ->
# Network timeout
handle_timeout()
{:error, %HTTPower.Error{reason: :econnrefused}} ->
# Connection refused
handle_connection_error()
{:error, %HTTPower.Error{reason: :network_blocked}} ->
# Blocked in test mode
handle_test_mode()
end

PCI-Compliant Logging

HTTPower automatically logs all HTTP requests and responses with PCI-compliant data sanitization. This helps with debugging and observability while maintaining security and compliance.

Automatic Sanitization

Sensitive data is automatically redacted from logs:

# Authorization headers are sanitized
HTTPower.get("https://api.example.com/users",
headers: %{"Authorization" => "Bearer secret-token"}
)
# Logs: headers=%{"authorization" => "[REDACTED]"}
# Credit card numbers are sanitized
HTTPower.post("https://payment-api.com/charge",
body: ~s({"card": "4111111111111111", "amount": 100})
)
# Logs: body={"card": "[REDACTED]", "amount": 100}

What Gets Sanitized

Headers:

Body Fields:

Patterns:

Correlation IDs

Every request gets a unique correlation ID for tracing:

# Example log output:
[HTTPower] [req_a1b2c3d4e5f6g7h8] → GET https://api.example.com/users
[HTTPower] [req_a1b2c3d4e5f6g7h8]200 (245ms) body=%{"users" => [...]}

Use these IDs to correlate requests with responses in your logs.

Configuration

Control logging behavior in your config:

# config/config.exs
config :httpower, :logging,
enabled: true, # Enable/disable logging (default: true)
level: :info, # Log level (default: :info)
sanitize_headers: ["X-Custom"], # Additional headers to sanitize
sanitize_body_fields: ["secret"] # Additional body fields to sanitize

Disabling Logging

For performance-critical code or when you don't want logging:

# Disable globally in config
config :httpower, :logging, enabled: false
# Or use Logger configuration to filter HTTPower logs
config :logger, :console,
format: "$time $metadata[$level] $message\n",
metadata: [:request_id]

Rate Limiting

HTTPower includes built-in rate limiting using a token bucket algorithm to prevent overwhelming APIs and respect rate limits.

Token Bucket Algorithm

The token bucket algorithm works by:

  1. Each API endpoint has a bucket with a maximum capacity of tokens
  2. Tokens are refilled at a fixed rate (e.g., 100 tokens per minute)
  3. Each request consumes one token
  4. If no tokens are available, the request either waits or returns an error

Basic Usage

# Global rate limiting configuration
config :httpower, :rate_limit,
enabled: true,
requests: 100, # Max 100 requests
per: :minute, # Per minute
strategy: :wait # Wait for tokens (or :error to fail immediately)
# All requests automatically respect rate limits
HTTPower.get("https://api.example.com/users")

Per-Client Rate Limiting

# Configure rate limits per client
github_client = HTTPower.new(
base_url: "https://api.github.com",
rate_limit: [requests: 60, per: :minute]
)
# This client respects GitHub's 60 req/min limit
HTTPower.get(github_client, "/users")

Per-Request Configuration

# Override rate limit for specific requests
HTTPower.get("https://api.example.com/search",
rate_limit: [
requests: 10,
per: :minute,
strategy: :error # Return error instead of waiting
]
)

Custom Bucket Keys

# Use custom keys to group requests
HTTPower.get("https://api.example.com/endpoint1",
rate_limit_key: "example_api",
rate_limit: [requests: 100, per: :minute]
)
HTTPower.get("https://api.example.com/endpoint2",
rate_limit_key: "example_api", # Shares same rate limit
rate_limit: [requests: 100, per: :minute]
)

Strategies

:wait Strategy (default)

config :httpower, :rate_limit,
strategy: :wait,
max_wait_time: 5000 # Wait up to 5 seconds

:error Strategy

case HTTPower.get(url, rate_limit: [strategy: :error]) do
{:ok, response} -> handle_success(response)
{:error, %{reason: :rate_limit_exceeded}} -> handle_rate_limit()
{:error, error} -> handle_error(error)
end

Configuration Options

config :httpower, :rate_limit,
enabled: true, # Enable/disable (default: false)
requests: 100, # Max requests per time window
per: :second, # Time window: :second, :minute, :hour
strategy: :wait, # Strategy: :wait or :error
max_wait_time: 5000 # Max wait time in ms (default: 5000)

Real-World Examples

# GitHub API: 60 requests per minute
github = HTTPower.new(
base_url: "https://api.github.com",
rate_limit: [requests: 60, per: :minute]
)
# Stripe API: 100 requests per second
stripe = HTTPower.new(
base_url: "https://api.stripe.com",
rate_limit: [requests: 100, per: :second, strategy: :error]
)
# Search endpoints: Lower limits
HTTPower.get("https://api.example.com/search",
rate_limit: [requests: 10, per: :minute]
)

Production Considerations

HTTPower is designed for production use with:

Reliability

Testing

Observability

Why HTTPower?

HTTPower adds production reliability patterns on top of your HTTP client choice:

vs Building It Yourself

Get circuit breakers, rate limiting, PCI-compliant logging, and telemetry integration without building and maintaining them.

vs Using Raw HTTP Clients

Adapter Flexibility

Perfect for:

Development

# Install dependencies
mix deps.get
# Run tests
mix test
# Generate docs
mix docs
# Check coverage
mix test --cover

Roadmap

See ROADMAP.md for planned features including:

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for your changes
  4. Ensure all tests pass with mix test
  5. Submit a pull request

License

MIT License - see LICENSE for details.


HTTPower: Because your HTTP requests deserve to be as powerful as they are reliable.