Handler Classes

This document provides a comprehensive reference for Azu's built-in middleware handlers, including their configuration options and usage patterns.

Base Handler

Azu::Handler::Base

The base class for all middleware handlers.

abstract class Azu::Handler::Base
  # Base class for all middleware handlers
end

Methods

call(context : HTTP::Server::Context)

Process the HTTP request/response through the middleware.

def call(context : HTTP::Server::Context)
  # Process request
  call_next(context)
  # Process response
end

call_next(context : HTTP::Server::Context)

Call the next handler in the middleware stack.

def call_next(context : HTTP::Server::Context)
  # Implementation
end

Built-in Handlers

Logger Handler

Azu::Handler::Logger

Provides request/response logging with configurable formats.

class Azu::Handler::Logger < Azu::Handler::Base
  # Logs HTTP requests and responses
end

Constructor

Logger.new(
  format: LogFormat::Common,
  level: Log::Severity::Info,
  exclude_paths: [] of String
)

Parameters:

  • format - Log format (Common, Combined, Custom)

  • level - Log level (Debug, Info, Warn, Error)

  • exclude_paths - Paths to exclude from logging

Usage

# Basic usage
Azu::Handler::Logger.new

# With custom format
Azu::Handler::Logger.new(
  format: Azu::LogFormat::Combined,
  level: Log::Severity::Debug
)

# Exclude health check endpoints
Azu::Handler::Logger.new(
  exclude_paths: ["/health", "/metrics"]
)

Rescuer Handler

Azu::Handler::Rescuer

Handles exceptions and provides error responses.

class Azu::Handler::Rescuer < Azu::Handler::Base
  # Handles exceptions and provides error responses
end

Constructor

Rescuer.new(
  show_details: false,
  log_errors: true,
  custom_handlers: {} of String.class => Proc(Exception, HTTP::Server::Context, Nil)
)

Parameters:

  • show_details - Show exception details in development

  • log_errors - Log exceptions

  • custom_handlers - Custom exception handlers

Usage

# Basic usage
Azu::Handler::Rescuer.new

# Show details in development
Azu::Handler::Rescuer.new(
  show_details: EnvironmentManager.development?
)

# Custom exception handlers
custom_handlers = {
  ValidationError => ->(ex : Exception, context : HTTP::Server::Context) {
    context.response.status_code = 422
    context.response.print({error: ex.message}.to_json)
  }
}

Azu::Handler::Rescuer.new(
  custom_handlers: custom_handlers
)

CORS Handler

Azu::Handler::CORS

Handles Cross-Origin Resource Sharing (CORS) headers.

class Azu::Handler::CORS < Azu::Handler::Base
  # Handles CORS headers
end

Constructor

CORS.new(
  origins: ["*"],
  methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
  headers: ["Content-Type", "Authorization"],
  credentials: false,
  max_age: 86400
)

Parameters:

  • origins - Allowed origins (use ["*"] for all)

  • methods - Allowed HTTP methods

  • headers - Allowed headers

  • credentials - Allow credentials

  • max_age - Preflight cache duration

Usage

# Allow all origins
Azu::Handler::CORS.new

# Specific origins
Azu::Handler::CORS.new(
  origins: ["https://example.com", "https://app.example.com"],
  credentials: true
)

# Development settings
Azu::Handler::CORS.new(
  origins: ["http://localhost:3000", "http://localhost:3001"],
  methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"],
  headers: ["Content-Type", "Authorization", "X-Requested-With"]
)

Static Handler

Azu::Handler::Static

Serves static files from a directory.

class Azu::Handler::Static < Azu::Handler::Base
  # Serves static files
end

Constructor

Static.new(
  path: "public",
  index: "index.html",
  headers: {} of String => String,
  gzip: true,
  cache_control: "public, max-age=31536000"
)

Parameters:

  • path - Directory to serve files from

  • index - Default index file

  • headers - Additional headers to add

  • gzip - Enable gzip compression

  • cache_control - Cache control header

Usage

# Basic usage
Azu::Handler::Static.new

# Custom path
Azu::Handler::Static.new(path: "assets")

# With custom headers
Azu::Handler::Static.new(
  path: "public",
  headers: {
    "X-Frame-Options" => "DENY",
    "X-Content-Type-Options" => "nosniff"
  }
)

# Development settings (no caching)
Azu::Handler::Static.new(
  cache_control: "no-cache, no-store, must-revalidate"
)

Throttle Handler

Azu::Handler::Throttle

Implements rate limiting for requests.

class Azu::Handler::Throttle < Azu::Handler::Base
  # Implements rate limiting
end

Constructor

Throttle.new(
  requests_per_minute: 60,
  requests_per_hour: 1000,
  key_generator: ->(context : HTTP::Server::Context) { String },
  store: MemoryStore.new,
  headers: true
)

Parameters:

  • requests_per_minute - Requests allowed per minute

  • requests_per_hour - Requests allowed per hour

  • key_generator - Function to generate rate limit keys

  • store - Storage backend for rate limit data

  • headers - Include rate limit headers in response

Usage

# Basic usage (60 requests per minute)
Azu::Handler::Throttle.new

# Stricter limits
Azu::Handler::Throttle.new(
  requests_per_minute: 30,
  requests_per_hour: 500
)

# Custom key generator (by IP)
Azu::Handler::Throttle.new(
  key_generator: ->(context : HTTP::Server::Context) {
    context.request.remote_address.try(&.address) || "unknown"
  }
)

# Redis storage
redis_store = RedisStore.new(Redis::Client.new)
Azu::Handler::Throttle.new(
  store: redis_store,
  requests_per_minute: 100
)

Request ID Handler

Azu::Handler::RequestId

Adds unique request IDs to track requests across the system.

class Azu::Handler::RequestId < Azu::Handler::Base
  # Adds unique request IDs
end

Constructor

RequestId.new(
  header_name: "X-Request-ID",
  generator: ->{ String },
  include_in_response: true
)

Parameters:

  • header_name - Name of the request ID header

  • generator - Function to generate request IDs

  • include_in_response - Include request ID in response headers

Usage

# Basic usage
Azu::Handler::RequestId.new

# Custom header name
Azu::Handler::RequestId.new(
  header_name: "X-Correlation-ID"
)

# Custom ID generator
Azu::Handler::RequestId.new(
  generator: ->{ "#{Time.utc.to_unix}-#{Random::Secure.hex(8)}" }
)

CSRF Handler

Azu::Handler::CSRF

Protects against Cross-Site Request Forgery attacks.

class Azu::Handler::CSRF < Azu::Handler::Base
  # CSRF protection
end

Constructor

CSRF.new(
  secret: String,
  token_length: 32,
  header_name: "X-CSRF-Token",
  param_name: "_csrf_token",
  exclude_methods: ["GET", "HEAD", "OPTIONS"],
  exclude_paths: [] of String
)

Parameters:

  • secret - Secret key for token generation

  • token_length - Length of CSRF tokens

  • header_name - Header name for CSRF token

  • param_name - Parameter name for CSRF token

  • exclude_methods - HTTP methods to exclude

  • exclude_paths - Paths to exclude from CSRF protection

Usage

# Basic usage
Azu::Handler::CSRF.new(secret: ENV["CSRF_SECRET"])

# Custom configuration
Azu::Handler::CSRF.new(
  secret: ENV["CSRF_SECRET"],
  token_length: 64,
  header_name: "X-XSRF-Token",
  exclude_paths: ["/api/webhooks"]
)

# Development (disabled)
Azu::Handler::CSRF.new(
  secret: "dev-secret",
  exclude_methods: ["GET", "HEAD", "OPTIONS", "POST"]  # Disable for development
)

IP Spoofing Handler

Azu::Handler::IpSpoofing

Prevents IP address spoofing by validating forwarded headers.

class Azu::Handler::IpSpoofing < Azu::Handler::Base
  # Prevents IP address spoofing
end

Constructor

IpSpoofing.new(
  trusted_proxies: [] of String,
  forwarded_for_header: "X-Forwarded-For",
  real_ip_header: "X-Real-IP"
)

Parameters:

  • trusted_proxies - List of trusted proxy IPs

  • forwarded_for_header - Header name for forwarded IPs

  • real_ip_header - Header name for real IP

Usage

# Basic usage
Azu::Handler::IpSpoofing.new

# With trusted proxies
Azu::Handler::IpSpoofing.new(
  trusted_proxies: ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
)

# Custom headers
Azu::Handler::IpSpoofing.new(
  forwarded_for_header: "X-Forwarded-For",
  real_ip_header: "X-Real-IP"
)

Simple Logger Handler

Azu::Handler::SimpleLogger

A simplified logging handler for basic request logging.

class Azu::Handler::SimpleLogger < Azu::Handler::Base
  # Simple request logging
end

Constructor

SimpleLogger.new(
  io: IO::STDOUT,
  format: SimpleLogFormat::Common
)

Parameters:

  • io - Output stream for logs

  • format - Log format (Common, Combined)

Usage

# Basic usage
Azu::Handler::SimpleLogger.new

# Custom output
Azu::Handler::SimpleLogger.new(
  io: File.new("logs/access.log", "a"),
  format: Azu::SimpleLogFormat::Combined
)

Custom Handlers

Creating Custom Handlers

You can create custom handlers by inheriting from Azu::Handler::Base.

class CustomAuthHandler < Azu::Handler::Base
  def initialize(@api_key : String)
  end

  def call(context : HTTP::Server::Context)
    api_key = context.request.headers["Authorization"]?

    if api_key != @api_key
      context.response.status_code = 401
      context.response.print({error: "Unauthorized"}.to_json)
      return
    end

    call_next(context)
  end
end

Handler Composition

Handlers can be composed to create complex middleware stacks.

class CompositeHandler < Azu::Handler::Base
  def initialize(@handlers : Array(Azu::Handler::Base))
  end

  def call(context : HTTP::Server::Context)
    call_handlers(context, 0)
  end

  private def call_handlers(context : HTTP::Server::Context, index : Int32)
    if index >= @handlers.size
      # End of middleware stack
      return
    end

    @handlers[index].call(context)
  end
end

Handler Configuration

Environment-Specific Configuration

class HandlerConfig
  def self.create_stack : Array(Azu::Handler::Base)
    stack = [] of Azu::Handler::Base

    # Always include these handlers
    stack << Azu::Handler::Rescuer.new(
      show_details: EnvironmentManager.development?
    )
    stack << Azu::Handler::Logger.new(
      level: EnvironmentManager.development? ? Log::Severity::Debug : Log::Severity::Info
    )

    # Environment-specific handlers
    case EnvironmentManager.current
    when EnvironmentManager::DEVELOPMENT
      stack << Azu::Handler::CORS.new(
        origins: ["http://localhost:3000", "http://localhost:3001"],
        credentials: true
      )
      stack << Azu::Handler::Static.new(
        path: "public",
        cache_control: "no-cache"
      )

    when EnvironmentManager::STAGING
      stack << Azu::Handler::CORS.new(
        origins: ["https://staging.example.com"],
        credentials: true
      )
      stack << Azu::Handler::Throttle.new(
        requests_per_minute: 100
      )
      stack << Azu::Handler::RequestId.new

    when EnvironmentManager::PRODUCTION
      stack << Azu::Handler::CORS.new(
        origins: ["https://example.com"],
        credentials: true
      )
      stack << Azu::Handler::Throttle.new(
        requests_per_minute: 60,
        requests_per_hour: 1000
      )
      stack << Azu::Handler::RequestId.new
      stack << Azu::Handler::CSRF.new(
        secret: ENV["CSRF_SECRET"]
      )
      stack << Azu::Handler::IpSpoofing.new(
        trusted_proxies: ["10.0.0.0/8"]
      )
    end

    stack
  end
end

Handler Ordering

The order of handlers in the middleware stack is important:

  1. Rescuer - Should be first to catch all exceptions

  2. Logger - Early logging of requests

  3. CORS - Handle preflight requests

  4. Authentication - Verify user identity

  5. Authorization - Check permissions

  6. Rate Limiting - Prevent abuse

  7. Request Processing - Your application logic

  8. Static Files - Serve static content last

# Correct order
stack = [
  Azu::Handler::Rescuer.new,
  Azu::Handler::Logger.new,
  Azu::Handler::CORS.new,
  Azu::Handler::Throttle.new,
  Azu::Handler::RequestId.new,
  # Your application handlers
  Azu::Handler::Static.new
]

Handler Testing

Testing Custom Handlers

describe "CustomAuthHandler" do
  it "allows requests with valid API key" do
    handler = CustomAuthHandler.new("valid-key")
    context = create_context(headers: {"Authorization" => "valid-key"})

    handler.call(context)

    assert context.response.status_code == 200
  end

  it "rejects requests with invalid API key" do
    handler = CustomAuthHandler.new("valid-key")
    context = create_context(headers: {"Authorization" => "invalid-key"})

    handler.call(context)

    assert context.response.status_code == 401
  end

  it "rejects requests without API key" do
    handler = CustomAuthHandler.new("valid-key")
    context = create_context

    handler.call(context)

    assert context.response.status_code == 401
  end
end

private def create_context(headers : Hash(String, String) = {} of String => String) : HTTP::Server::Context
  request = HTTP::Request.new("GET", "/")
  headers.each { |key, value| request.headers[key] = value }

  response = HTTP::Server::Response.new(IO::Memory.new)
  HTTP::Server::Context.new(request, response)
end

Performance Considerations

Handler Performance

  • Logger: Minimal overhead, but can be expensive with high request volumes

  • CORS: Very fast, minimal overhead

  • Throttle: Can be expensive with high concurrency (use Redis for distributed systems)

  • CSRF: Moderate overhead due to token generation and validation

  • Static: Very fast for file serving

Optimization Tips

# Use conditional logging
Azu::Handler::Logger.new(
  exclude_paths: ["/health", "/metrics", "/favicon.ico"]
)

# Use efficient rate limiting storage
redis_store = RedisStore.new(redis_client)
Azu::Handler::Throttle.new(
  store: redis_store,
  requests_per_minute: 100
)

# Disable CSRF for API endpoints
Azu::Handler::CSRF.new(
  exclude_paths: ["/api/*"]
)

Next Steps

Last updated

Was this helpful?