rack-middleware

Rack Middleware Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "rack-middleware" with this command: npx skills add geoffjay/claude-plugins/geoffjay-claude-plugins-rack-middleware

Rack Middleware Skill

Tier 1: Quick Reference - Middleware Basics

Middleware Structure

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

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

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

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:

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:

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: 'user@example.com' } 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:

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:

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 XMLBodyParser def initialize(app) @app = app end

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

  begin
    parsed = Hash.from_xml(body)
    env['rack.request.form_hash'] = parsed
    env['parsed_xml'] = parsed
  rescue StandardError => e
    return error_response('Invalid XML', 400)
  end
end

@app.call(env)

end

private

def xml_request?(env) content_type = env['CONTENT_TYPE'] content_type && (content_type.include?('application/xml') || content_type.include?('text/xml')) end

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

Middleware Ordering Patterns

Security-First Stack:

config.ru

1. SSL redirect (production only)

use Rack::SSL if ENV['RACK_ENV'] == 'production'

2. Rate limiting (before everything else)

use Rack::Attack

3. Security headers

use SecurityHeaders

4. CORS (for API applications)

use Rack::Cors do allow do origins '' resource '', headers: :any, methods: [:get, :post, :put, :delete, :options] end end

5. Compression

use Rack::Deflater

6. Static files

use Rack::Static, urls: ['/public'], root: 'public'

7. Logging

use Rack::CommonLogger

8. Request parsing

use JSONBodyParser

9. Sessions

use Rack::Session::Cookie, secret: ENV['SESSION_SECRET'], same_site: :strict, httponly: true, secure: ENV['RACK_ENV'] == 'production'

10. Protection (CSRF, etc.)

use Rack::Protection

11. Authentication

use TokenAuthentication, skip_paths: ['/login', '/public']

12. Performance monitoring

use PerformanceMonitor

13. Application

run Application

API-Focused Stack:

config.ru for API

1. CORS first for preflight

use Rack::Cors do allow do origins ENV.fetch('ALLOWED_ORIGINS', '').split(',') resource '', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options], credentials: true, max_age: 86400 end end

2. Rate limiting

use Rack::Attack

3. Compression

use Rack::Deflater

4. Logging (structured JSON logs)

use RequestLogger

5. Request parsing

use JSONBodyParser

6. Authentication

use TokenAuthentication, skip_paths: ['/auth']

7. Caching

use RedisCache, ttl: 300

8. Application

run API

Conditional Middleware

Environment-Based:

class ConditionalMiddleware def initialize(app, condition, middleware, *args) @app = if condition.call middleware.new(app, *args) else app end end

def call(env) @app.call(env) end end

Usage

use ConditionalMiddleware, -> { ENV['RACK_ENV'] == 'development' }, Rack::ShowExceptions

use ConditionalMiddleware, -> { ENV['ENABLE_PROFILING'] == 'true' }, RackMiniProfiler

Path-Based:

class PathBasedMiddleware def initialize(app, pattern, middleware, *args) @app = app @pattern = pattern @middleware = middleware.new(app, *args) end

def call(env) if env['PATH_INFO'].match?(@pattern) @middleware.call(env) else @app.call(env) end end end

Usage

use PathBasedMiddleware, %r{^/api}, CacheMiddleware, ttl: 300 use PathBasedMiddleware, %r{^/admin}, AdminAuth

Error Handling Middleware

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

def call(env) @app.call(env) rescue StandardError => e handle_error(env, e) end

private

def handle_error(env, error) request = Rack::Request.new(env)

# Log error
@logger.error({
  error: error.class.name,
  message: error.message,
  path: request.path,
  method: request.request_method,
  backtrace: error.backtrace[0..10]
}.to_json)

# Custom handler for specific error types
if handler = @error_handlers[error.class]
  return handler.call(error)
end

# Default error response
status = status_for_error(error)
[
  status,
  { 'Content-Type' => 'application/json' },
  [{ error: error.message, type: error.class.name }.to_json]
]

end

def status_for_error(error) case error when ArgumentError, ValidationError 400 when NotFoundError 404 when AuthorizationError 403 when AuthenticationError 401 else 500 end end end

Usage

use ErrorHandler, handlers: { ValidationError => ->(e) { [422, { 'Content-Type' => 'application/json' }, [{ error: e.message, details: e.details }.to_json]] } }

Tier 3: Resources & Examples

Complete Middleware Examples

Performance Monitoring:

class PerformanceMonitor def initialize(app, options = {}) @app = app @threshold = options[:threshold] || 1.0 # 1 second @logger = options[:logger] || Logger.new(STDOUT) end

def call(env) start_time = Time.now memory_before = memory_usage

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

duration = Time.now - start_time
memory_after = memory_usage
memory_delta = memory_after - memory_before

# Add performance headers
headers['X-Runtime'] = duration.to_s
headers['X-Memory-Delta'] = memory_delta.to_s

# Log slow requests
if duration > @threshold
  log_slow_request(env, duration, memory_delta)
end

[status, headers, body]

end

private

def memory_usage ps -o rss= -p #{Process.pid}.to_i / 1024.0 # MB end

def log_slow_request(env, duration, memory) @logger.warn({ event: 'slow_request', method: env['REQUEST_METHOD'], path: env['PATH_INFO'], duration: duration.round(3), memory_delta: memory.round(2) }.to_json) end end

Request ID Tracking:

class RequestID def initialize(app, options = {}) @app = app @header = options[:header] || 'X-Request-ID' end

def call(env) request_id = env["HTTP_#{@header.upcase.tr('-', '_')}"] || generate_id env['request.id'] = request_id

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

headers[@header] = request_id

[status, headers, body]

end

private

def generate_id SecureRandom.uuid end end

Response Modification:

class ResponseTransformer def initialize(app, &block) @app = app @transformer = block end

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

if should_transform?(headers)
  body = transform_body(body)
end

[status, headers, body]

end

private

def should_transform?(headers) headers['Content-Type']&.include?('application/json') end

def transform_body(body) content = body.is_a?(Array) ? body.join : body.read transformed = @transformer.call(content) [transformed] end end

Usage

use ResponseTransformer do |body| data = JSON.parse(body) data['timestamp'] = Time.now.to_i data.to_json end

Testing Middleware

RSpec.describe RequestLogger do let(:app) { ->(env) { [200, {}, ['OK']] } } let(:logger) { double('Logger', info: nil, error: nil) } let(:middleware) { RequestLogger.new(app, logger: logger) } let(:request) { Rack::MockRequest.new(middleware) }

describe 'request logging' do it 'logs request start' do expect(logger).to receive(:info).with(hash_including(event: 'request.start')) request.get('/') end

it 'logs request end with duration' do
  expect(logger).to receive(:info).with(hash_including(
    event: 'request.end',
    duration: kind_of(Numeric)
  ))
  request.get('/')
end

it 'includes request details' do
  expect(logger).to receive(:info).with(hash_including(
    method: 'GET',
    path: '/test'
  ))
  request.get('/test')
end

end

describe 'error logging' do let(:app) { ->(env) { raise StandardError, 'Test error' } }

it 'logs errors' do
  expect(logger).to receive(:error).with(hash_including(
    event: 'request.error',
    error: 'StandardError'
  ))

  expect { request.get('/') }.to raise_error(StandardError)
end

end

describe 'skip paths' do let(:middleware) { RequestLogger.new(app, logger: logger, skip_paths: ['/health']) }

it 'skips logging for configured paths' do
  expect(logger).not_to receive(:info)
  request.get('/health')
end

end end

Additional Resources

  • Middleware Template: assets/middleware-template.rb

  • Boilerplate for new middleware

  • Middleware Examples: assets/middleware-examples/

  • Collection of useful middleware

  • Configuration Guide: assets/configuration-guide.md

  • Best practices for middleware configuration

  • Performance Guide: references/performance-optimization.md

  • Optimizing middleware performance

  • Testing Guide: references/middleware-testing.md

  • Comprehensive testing strategies

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

documentation-update

No summary provided by upstream source.

Repository SourceNeeds Review
General

git-troubleshooting

No summary provided by upstream source.

Repository SourceNeeds Review
General

git-advanced

No summary provided by upstream source.

Repository SourceNeeds Review