html2pdf-api
Thread-safe headless browser pool for high-performance HTML to PDF conversion with native Rust web framework integration.
A production-ready Rust library for managing a pool of headless Chrome browsers to convert HTML to PDF. Designed for high-performance web APIs with built-in support for popular Rust web frameworks.
✨ Features
- 🔒 Thread-Safe Pool Management - Efficient browser reuse with RAII handles
- ❤️ Automatic Health Monitoring - Background health checks with automatic browser retirement
- ⏰ TTL-Based Lifecycle - Configurable browser time-to-live prevents memory leaks
- 🛡️ Production-Ready - Comprehensive error handling and graceful shutdown
- 🚀 Framework Integration - Pre-built handlers for Actix-web, Rocket, and Axum
- ⚙️ Flexible Configuration - Environment variables or direct configuration
- 📊 Pool Statistics - Real-time metrics for monitoring
- 🌍 Cross-Platform - Works on Linux, macOS, and Windows
Installation
Add to your Cargo.toml:
[]
= "0.2"
Feature Flags
| Feature | Description | Default |
|---|---|---|
env-config |
Load configuration from environment variables | Yes |
actix-integration |
Actix-web framework support with pre-built handlers | No |
rocket-integration |
Rocket framework support | No |
axum-integration |
Axum framework support | No |
test-utils |
Mock factory for testing | No |
Enable features as needed:
[]
= { = "0.2", = ["actix-integration"] }
Quick Start
Basic Usage
use *;
use Duration;
async
Environment Configuration
Enable the env-config feature for simpler initialization:
use init_browser_pool;
async
Environment Variables
| Variable | Type | Default | Description |
|---|---|---|---|
BROWSER_POOL_SIZE |
usize | 5 | Maximum browsers in pool |
BROWSER_WARMUP_COUNT |
usize | 3 | Browsers to pre-create on startup |
BROWSER_TTL_SECONDS |
u64 | 3600 | Browser lifetime before retirement |
BROWSER_WARMUP_TIMEOUT_SECONDS |
u64 | 60 | Maximum warmup duration |
BROWSER_PING_INTERVAL_SECONDS |
u64 | 15 | Health check frequency |
BROWSER_MAX_PING_FAILURES |
u32 | 3 | Failures before browser removal |
CHROME_PATH |
String | auto | Custom Chrome/Chromium binary path |
Web Framework Integration
Actix-web
Option 1: Pre-built Routes (Recommended)
Get a fully functional PDF API with just a few lines of code:
use ;
use *;
use configure_routes;
async
This gives you these endpoints automatically:
| Method | Path | Description |
|---|---|---|
| GET | /pdf?url=https://example.com |
Convert URL to PDF |
| POST | /pdf/html |
Convert HTML to PDF |
| GET | /pool/stats |
Pool statistics |
| GET | /health |
Health check |
| GET | /ready |
Readiness check |
Option 2: Custom Handler with Service Functions
For custom logic (authentication, rate limiting, etc.):
use ;
use *;
use ;
async
Option 3: Manual Browser Control
For complete control over browser operations:
use ;
use *;
async
Rocket (Manual Browser Control Only)
use ;
use *;
async
async
Axum (Manual Browser Control Only)
use ;
use *;
async
async
Pre-built API Endpoints (Actix-web)
When using configure_routes, these endpoints are available:
GET /pdf - Convert URL to PDF
Query Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
url |
string | Yes | - | URL to convert |
filename |
string | No | document.pdf |
Output filename |
waitsecs |
u64 | No | 5 | Seconds to wait for JavaScript |
landscape |
bool | No | false | Landscape orientation |
download |
bool | No | false | Force download vs inline display |
print_background |
bool | No | true | Include background graphics |
Example:
POST /pdf/html - Convert HTML to PDF
Request Body (JSON):
Example:
GET /pool/stats - Pool Statistics
Response:
GET /health - Health Check
Response (200 OK):
GET /ready - Readiness Check
Response (200 OK):
Response (503 Service Unavailable):
JavaScript Wait Behavior
The waitsecs parameter controls how long to wait for JavaScript rendering. For pages that signal completion, you can enable early exit:
// In your web page, signal when rendering is complete:
window. = true;
The service polls every 200ms for this flag. If set, PDF generation proceeds immediately without waiting the full duration.
Recommended waitsecs values:
| Page Type | Value |
|---|---|
| Static HTML | 1-2 |
| Light JavaScript | 3-5 |
| Heavy SPA (React, Vue) | 5-10 |
| Complex charts/visualizations | 10-15 |
Architecture
┌─────────────────────────────────────────────┐
│ Your Web Application │
│ (Actix-web / Rocket / Axum) │
└─────────────────┬───────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ BrowserPool │
│ ┌─────────────────────────────────────────┐ │
│ │ Available Pool (idle browsers) │ │
│ │ [Browser1] [Browser2] [Browser3] │ │
│ └─────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────┐ │
│ │ Active Tracking (in-use browsers) │ │
│ │ {id → Browser} │ │
│ └─────────────────────────────────────────┘ │
│ ┌─────────────────────────────────────────┐ │
│ │ Keep-Alive Thread │ │
│ │ (health checks + TTL enforcement) │ │
│ └─────────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────┐
│ Headless Chrome Browsers │
│ (managed by headless_chrome crate) │
└─────────────────────────────────────────────┘
Key Design Decisions
- RAII Pattern: Browsers are automatically returned to the pool when
BrowserHandleis dropped - Lock Ordering: Strict lock ordering (active → available) prevents deadlocks
- Health Checks: Lock-free health checks avoid blocking other operations
- Staggered Warmup: TTLs are offset to prevent simultaneous browser expiration
- Graceful Shutdown: Condvar signaling enables immediate shutdown response
⚙️ Configuration Guide
Recommended Production Settings
use Duration;
use BrowserPoolConfigBuilder;
let config = new
.max_pool_size // Adjust based on load
.warmup_count // Pre-warm half the pool
.browser_ttl // 1 hour lifetime
.ping_interval // Check every 15s
.max_ping_failures // Tolerate transient failures
.warmup_timeout // 2 min warmup limit
.build?;
Custom Chrome Path
use ChromeBrowserFactory;
// Linux
let factory = with_path;
// macOS
let factory = with_path;
// Windows
let factory = with_path;
Testing
Use the test-utils feature for testing without Chrome:
use MockBrowserFactory;
// Factory that always fails (for error handling tests)
let factory = always_fails;
// Factory that fails after N creations (for exhaustion tests)
let factory = fail_after_n;
let pool = builder
.factory
.enable_keep_alive // Disable for faster tests
.build?;
Monitoring
let stats = pool.stats;
println!;
println!;
println!;
// For metrics systems
gauge!;
gauge!;
❗ Error Handling
Pool Errors
use ;
match pool.get
Service Errors (Actix-web Integration)
When using the service layer, errors include HTTP status code mapping:
use ;
Error Codes:
| Error | HTTP Status | Retryable |
|---|---|---|
INVALID_URL |
400 | No |
EMPTY_HTML |
400 | No |
BROWSER_UNAVAILABLE |
503 | Yes |
NAVIGATION_FAILED |
502 | Yes |
NAVIGATION_TIMEOUT |
504 | Yes |
PDF_GENERATION_FAILED |
502 | Yes |
TIMEOUT |
504 | Yes |
POOL_SHUTTING_DOWN |
503 | No |
Requirements
- Rust: 1.85 or later
- Tokio: Runtime required for async operations
- Chrome/Chromium
No installation required! 🎉
The library automatically downloads a compatible Chromium binary if Chrome is not detected on your system. Downloaded binaries are cached for future use:
| Platform | Cache Location |
|---|---|
| Linux | ~/.local/share/headless-chrome |
| macOS | ~/Library/Application Support/headless-chrome |
| Windows | C:\Users\<User>\AppData\Roaming\headless-chrome\data |
- First run: May take a few minutes to download Chromium (~170MB)
- Subsequent runs: Uses cached version instantly
Chrome/Chromium - Manual Installation (Optional)
While not required, you can install Chrome manually if preferred:
Ubuntu/Debian:
macOS:
Windows: Download from google.com/chrome
Examples
See the examples directory for complete working examples:
actix_web_example.rs- Actix-web with pre-built routes, custom handlers, and manual controlrocket_example.rs- Rocket integrationaxum_example.rs- Axum integration
Run examples:
# Actix-web (demonstrates all integration patterns)
# Rocket
# Axum
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
License
Licensed:
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.
Acknowledgments
This crate builds upon the excellent headless_chrome crate.