Claude
Skills
Sign in
Back

rack-middleware

Included with Lifetime
$97 forever

Rack middleware development, configuration, and integration patterns. Use when working with middleware stacks or creating custom middleware.

General

What this skill does


# Rack Middleware Skill

## Tier 1: Quick Reference - Middleware Basics

### Middleware Structure

```ruby
class MyMiddleware
  def initialize(app, options = {})
    @app = app
    @options = options
  end

  def call(env)
    # Before request
    # Modify env if needed

    # Call next middleware
    status, headers, body = @app.call(env)

    # After request
    # Modify response if needed

    [status, headers, body]
  end
end

# Usage
use MyMiddleware, option: 'value'
```

### Common Middleware

```ruby
# Session management
use Rack::Session::Cookie, secret: ENV['SESSION_SECRET']

# Security
use Rack::Protection

# Compression
use Rack::Deflater

# Logging
use Rack::CommonLogger

# Static files
use Rack::Static, urls: ['/css', '/js'], root: 'public'
```

### Middleware Ordering

```ruby
# config.ru - Correct order
use Rack::Deflater           # 1. Compression
use Rack::Static             # 2. Static files
use Rack::CommonLogger       # 3. Logging
use Rack::Session::Cookie    # 4. Sessions
use Rack::Protection          # 5. Security
use CustomAuth               # 6. Authentication
run Application              # 7. Application
```

### Request/Response Access

```ruby
class SimpleMiddleware
  def initialize(app)
    @app = app
  end

  def call(env)
    # Access request via env hash
    method = env['REQUEST_METHOD']
    path = env['PATH_INFO']
    query = env['QUERY_STRING']

    # Or use Rack::Request
    request = Rack::Request.new(env)
    params = request.params

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

    # Modify response
    headers['X-Custom-Header'] = 'value'

    [status, headers, body]
  end
end
```

---

## Tier 2: Detailed Instructions - Advanced Middleware

### Custom Middleware Development

**Request Logging Middleware:**
```ruby
require 'logger'

class RequestLogger
  def initialize(app, options = {})
    @app = app
    @logger = options[:logger] || Logger.new(STDOUT)
    @skip_paths = options[:skip_paths] || []
  end

  def call(env)
    return @app.call(env) if skip_logging?(env)

    start_time = Time.now
    request = Rack::Request.new(env)

    log_request_start(request)

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

    duration = Time.now - start_time
    log_request_end(request, status, duration)

    [status, headers, body]
  rescue StandardError => e
    log_error(request, e)
    raise
  end

  private

  def skip_logging?(env)
    path = env['PATH_INFO']
    @skip_paths.any? { |skip| path.start_with?(skip) }
  end

  def log_request_start(request)
    @logger.info({
      event: 'request.start',
      method: request.request_method,
      path: request.path,
      ip: request.ip,
      user_agent: request.user_agent
    }.to_json)
  end

  def log_request_end(request, status, duration)
    @logger.info({
      event: 'request.end',
      method: request.request_method,
      path: request.path,
      status: status,
      duration: duration.round(3)
    }.to_json)
  end

  def log_error(request, error)
    @logger.error({
      event: 'request.error',
      method: request.request_method,
      path: request.path,
      error: error.class.name,
      message: error.message,
      backtrace: error.backtrace[0..5]
    }.to_json)
  end
end

# Usage
use RequestLogger, skip_paths: ['/health', '/metrics']
```

**Authentication Middleware:**
```ruby
class TokenAuthentication
  def initialize(app, options = {})
    @app = app
    @token_header = options[:header] || 'HTTP_AUTHORIZATION'
    @skip_paths = options[:skip_paths] || []
    @realm = options[:realm] || 'Application'
  end

  def call(env)
    return @app.call(env) if skip_authentication?(env)

    token = extract_token(env)

    if valid_token?(token)
      user = find_user_by_token(token)
      env['current_user'] = user
      @app.call(env)
    else
      unauthorized_response
    end
  end

  private

  def skip_authentication?(env)
    path = env['PATH_INFO']
    method = env['REQUEST_METHOD']

    # Skip for public paths
    @skip_paths.any? { |skip| path.start_with?(skip) } ||
      # Skip for OPTIONS (CORS preflight)
      method == 'OPTIONS'
  end

  def extract_token(env)
    auth_header = env[@token_header]
    return nil unless auth_header

    # Support "Bearer TOKEN" format
    if auth_header.start_with?('Bearer ')
      auth_header.split(' ', 2).last
    else
      auth_header
    end
  end

  def valid_token?(token)
    return false unless token

    # Implement your token validation logic
    # This is a placeholder
    token.length >= 32
  end

  def find_user_by_token(token)
    # Implement your user lookup logic
    # This is a placeholder
    { id: 1, email: '[email protected]' }
  end

  def unauthorized_response
    [
      401,
      {
        'Content-Type' => 'application/json',
        'WWW-Authenticate' => "Bearer realm=\"#{@realm}\""
      },
      ['{"error": "Unauthorized"}']
    ]
  end
end

# Usage
use TokenAuthentication,
  skip_paths: ['/login', '/register', '/public']
```

**Caching Middleware:**
```ruby
require 'digest/md5'

class SimpleCache
  def initialize(app, options = {})
    @app = app
    @cache = {}
    @ttl = options[:ttl] || 300  # 5 minutes
    @cache_methods = options[:methods] || ['GET']
  end

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

    return @app.call(env) unless cacheable?(request)

    cache_key = generate_cache_key(env)

    if cached_response = get_from_cache(cache_key)
      return cached_response
    end

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

    if cacheable_response?(status)
      cache_response(cache_key, [status, headers, body])
    end

    [status, headers, body]
  end

  private

  def cacheable?(request)
    @cache_methods.include?(request.request_method)
  end

  def cacheable_response?(status)
    status == 200
  end

  def generate_cache_key(env)
    # Include method, path, and query string
    Digest::MD5.hexdigest([
      env['REQUEST_METHOD'],
      env['PATH_INFO'],
      env['QUERY_STRING']
    ].join('|'))
  end

  def get_from_cache(key)
    entry = @cache[key]
    return nil unless entry

    # Check if cache entry is still valid
    if Time.now - entry[:cached_at] <= @ttl
      entry[:response]
    else
      @cache.delete(key)
      nil
    end
  end

  def cache_response(key, response)
    @cache[key] = {
      response: response,
      cached_at: Time.now
    }
  end
end

# Usage with Redis for distributed caching
class RedisCache
  def initialize(app, options = {})
    @app = app
    @redis = Redis.new(url: options[:redis_url])
    @ttl = options[:ttl] || 300
    @namespace = options[:namespace] || 'cache'
  end

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

    return @app.call(env) unless request.get?

    cache_key = generate_cache_key(env)

    if cached = @redis.get(cache_key)
      return Marshal.load(cached)
    end

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

    if status == 200
      @redis.setex(cache_key, @ttl, Marshal.dump([status, headers, body]))
    end

    [status, headers, body]
  end

  private

  def generate_cache_key(env)
    "#{@namespace}:#{Digest::MD5.hexdigest(env['PATH_INFO'] + env['QUERY_STRING'])}"
  end
end
```

**Request Transformation Middleware:**
```ruby
class JSONBodyParser
  def initialize(app)
    @app = app
  end

  def call(env)
    if json_request?(env)
      body = env['rack.input'].read
      env['rack.input'].rewind

      begin
        parsed = JSON.parse(body)
        env['rack.request.form_hash'] = parsed
        env['parsed_json'] = parsed
      rescue JSON::ParserError => e
        return error_response('Invalid JSON', 400)
      end
    end

    @app.call(env)
  end

  private

  def json_request?(env)
    content_type = env['CONTENT_TYPE']
    content_type && content_type.include?('application/json')
  end

  def error_response(message, status)
    [
      status,
      { 'Content-Type' => 'application/json' },
      [{ error: message }.to_json]
    ]
  end
end

# XML Parser
class XMLB

Related in General