Rack middleware for request/response processing

Sarah Mitchell Feb 2026
2 tabs
# Basic middleware structure
class RequestTimerMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    start_time = Time.current

    status, headers, body = @app.call(env)

    duration = Time.current - start_time
    Rails.logger.info "Request completed in #{duration.round(3)}s"

    # Add custom header
    headers['X-Request-Duration'] = duration.to_s

    [status, headers, body]
  end
end

# API authentication middleware
class ApiAuthenticationMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    request = Rack::Request.new(env)

    # Only process API requests
    return @app.call(env) unless request.path.start_with?('/api/')

    token = request.env['HTTP_AUTHORIZATION']&.sub(/^Bearer /, '')

    unless valid_token?(token)
      return [
        401,
        { 'Content-Type' => 'application/json' },
        [{ error: 'Unauthorized' }.to_json]
      ]
    end

    # Add user to env for downstream use
    user = User.find_by(api_token: token)
    env['api_user'] = user

    @app.call(env)
  end

  private

  def valid_token?(token)
    token.present? && User.exists?(api_token: token)
  end
end

# Rate limiting middleware
class RateLimitMiddleware
  LIMIT = 100
  WINDOW = 3600  # 1 hour

  def initialize(app)
    @app = app
    @redis = Redis.new
  end

  def call(env)
    request = Rack::Request.new(env)
    client_ip = request.ip

    key = "rate_limit:#{client_ip}"
    count = @redis.get(key).to_i

    if count >= LIMIT
      return [
        429,
        {
          'Content-Type' => 'application/json',
          'X-RateLimit-Limit' => LIMIT.to_s,
          'X-RateLimit-Remaining' => '0',
          'Retry-After' => WINDOW.to_s
        },
        [{ error: 'Rate limit exceeded' }.to_json]
      ]
    end

    # Increment counter
    @redis.multi do |r|
      r.incr(key)
      r.expire(key, WINDOW)
    end

    status, headers, body = @app.call(env)

    # Add rate limit headers
    headers['X-RateLimit-Limit'] = LIMIT.to_s
    headers['X-RateLimit-Remaining'] = (LIMIT - count - 1).to_s

    [status, headers, body]
  end
end

# Registering middleware in config/application.rb
module MyApp
  class Application < Rails::Application
    # Insert at specific position
    config.middleware.use RequestTimerMiddleware

    # Insert before another middleware
    config.middleware.insert_before ActionDispatch::Session::CookieStore,
      ApiAuthenticationMiddleware

    # Insert after another middleware
    config.middleware.insert_after Rails::Rack::Logger,
      RateLimitMiddleware

    # Delete middleware
    config.middleware.delete Rack::Runtime
  end
end

# View middleware stack
# rails middleware
2 files · ruby Explain with highlit

Rack middleware processes HTTP requests/responses in Rails' stack. Middleware sits between web server and application, modifying requests before they reach controllers. I build custom middleware for logging, authentication, rate limiting, request modification. Middleware follows simple interface—call(env) returns [status, headers, body]. Each middleware can pass requests down the stack with @app.call(env). Rails includes middleware for cookies, sessions, logging, static files. Middleware order matters—authentication must run before authorization. Inserting custom middleware at the right position is crucial. Middleware enables cross-cutting concerns without polluting controllers. Understanding Rack unlocks building custom HTTP processing layers and debugging Rails internals.