Skip to main content

Module axum

Module axum 

Source
Expand description

Axum framework integration.

This module provides helpers and pre-built handlers for using BrowserPool with Axum. You can choose between using the pre-built handlers for quick setup, or writing custom handlers for full control.

§Quick Start

§Option 1: Pre-built Routes (Fastest Setup)

Use configure_routes to add all PDF endpoints with a single line:

use axum::Router;
use html2pdf_api::prelude::*;

#[tokio::main]
async fn main() {
    let pool = init_browser_pool().await
        .expect("Failed to initialize browser pool");

    let app = Router::new()
        .merge(html2pdf_api::integrations::axum::configure_routes())
        .with_state(pool);

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

This gives you the following endpoints:

MethodPathDescription
GET/pdf?url=...Convert URL to PDF
POST/pdf/htmlConvert HTML to PDF
GET/pool/statsPool statistics
GET/healthHealth check
GET/readyReadiness check

§Option 2: Mix Pre-built and Custom Handlers

Use individual pre-built handlers alongside your own:

use axum::{Router, routing::get};
use html2pdf_api::prelude::*;
use html2pdf_api::integrations::axum::{pdf_from_url, health_check};

async fn my_custom_handler() -> &'static str {
    "Custom response"
}

#[tokio::main]
async fn main() {
    let pool = init_browser_pool().await.unwrap();

    let app = Router::new()
        .route("/pdf", get(pdf_from_url))
        .route("/health", get(health_check))
        .route("/custom", get(my_custom_handler))
        .with_state(pool);

    // ... serve app
}

§Option 3: Custom Handlers with Service Functions

For full control, use the service functions directly:

use axum::{
    extract::{Query, State},
    http::StatusCode,
    response::IntoResponse,
};
use html2pdf_api::prelude::*;
use html2pdf_api::service::{generate_pdf_from_url, PdfFromUrlRequest};

async fn my_pdf_handler(
    State(pool): State<SharedBrowserPool>,
    Query(request): Query<PdfFromUrlRequest>,
) -> impl IntoResponse {
    // Call service in blocking context
    let result = tokio::task::spawn_blocking(move || {
        generate_pdf_from_url(&pool, &request)
    }).await;

    match result {
        Ok(Ok(pdf)) => {
            // Custom post-processing
            (
                [(axum::http::header::CONTENT_TYPE, "application/pdf")],
                pdf.data,
            ).into_response()
        }
        Ok(Err(_)) => StatusCode::BAD_REQUEST.into_response(),
        Err(_) => StatusCode::INTERNAL_SERVER_ERROR.into_response(),
    }
}

§Option 4: Full Manual Control (Original Approach)

For complete control over browser operations:

use axum::{extract::State, http::StatusCode, response::IntoResponse};
use html2pdf_api::prelude::*;

async fn manual_pdf_handler(
    State(pool): State<SharedBrowserPool>,
) -> Result<impl IntoResponse, StatusCode> {
    let pool_guard = pool.lock()
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    let browser = pool_guard.get()
        .map_err(|_| StatusCode::SERVICE_UNAVAILABLE)?;

    let tab = browser.new_tab()
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
    tab.navigate_to("https://example.com")
        .map_err(|_| StatusCode::BAD_GATEWAY)?;
    tab.wait_until_navigated()
        .map_err(|_| StatusCode::BAD_GATEWAY)?;

    let pdf_data = tab.print_to_pdf(None)
        .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;

    Ok((
        [(axum::http::header::CONTENT_TYPE, "application/pdf")],
        pdf_data,
    ))
}

§Setup

Add to your Cargo.toml:

[dependencies]
html2pdf-api = { version = "0.3", features = ["axum-integration"] }
axum = "0.8"

§Graceful Shutdown

For proper cleanup with graceful shutdown:

use axum::Router;
use html2pdf_api::prelude::*;
use std::sync::Arc;
use tokio::signal;

#[tokio::main]
async fn main() {
    let pool = init_browser_pool().await.unwrap();
    let shutdown_pool = Arc::clone(&pool);

    let app = Router::new()
        .merge(html2pdf_api::integrations::axum::configure_routes())
        .with_state(pool);

    let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();
     
    axum::serve(listener, app)
        .with_graceful_shutdown(shutdown_signal(shutdown_pool))
        .await
        .unwrap();
}

async fn shutdown_signal(pool: SharedBrowserPool) {
    let ctrl_c = async {
        signal::ctrl_c().await.expect("Failed to listen for ctrl+c");
    };

    #[cfg(unix)]
    let terminate = async {
        signal::unix::signal(signal::unix::SignalKind::terminate())
            .expect("Failed to install signal handler")
            .recv()
            .await;
    };

    #[cfg(not(unix))]
    let terminate = std::future::pending::<()>();

    tokio::select! {
        _ = ctrl_c => {},
        _ = terminate => {},
    }

    println!("Shutting down...");
    if let Ok(mut pool) = pool.lock() {
        pool.shutdown();
    }
}

Traits§

BrowserPoolAxumExt
Extension trait for BrowserPool with Axum helpers.

Functions§

configure_routes
Returns a router configured with all PDF routes.
create_extension
Create an Axum Extension from an existing shared pool.
health_check
Health check endpoint.
pdf_from_html
Generate PDF from HTML content.
pdf_from_url
Generate PDF from a URL.
pool_stats
Get browser pool statistics.
readiness_check
Readiness check endpoint.

Type Aliases§

BrowserPoolState
Type alias for Axum State extractor with the shared pool.
SharedPool
Type alias for shared browser pool.