| name | rack-middleware |
| description | Rack middleware development, configuration, and integration patterns. Use when working with middleware stacks or creating custom middleware. Use when this capability is needed. |
| metadata | {"author":"geoffjay"} |
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)
status, headers, body = @app.call(env)
[status, headers, body]
end
end
use MyMiddleware, option: 'value'
Common Middleware
use Rack::Session::Cookie, secret: ENV['SESSION_SECRET']
use Rack::Protection
use Rack::Deflater
use Rack::CommonLogger
use Rack::Static, urls: ['/css', '/js'], root: 'public'
Middleware Ordering
use Rack::Deflater
use Rack::Static
use Rack::CommonLogger
use Rack::Session::Cookie
use Rack::Protection
use CustomAuth
run Application
Request/Response Access
class SimpleMiddleware
def initialize(app)
@app = app
end
def call(env)
method = env['REQUEST_METHOD']
path = env['PATH_INFO']
query = env['QUERY_STRING']
request = Rack::Request.new(env)
params = request.params
status, headers, body = @app.call(env)
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,
request.ip,
request.user_agent
}.to_json)
()
.info({
,
request.request_method,
request.path,
status,
duration.round()
}.to_json)
()
.error({
,
request.request_method,
request.path,
error..name,
error.message,
error.backtrace[..]
}.to_json)
use , [, ]
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_paths.any? { |skip| path.start_with?(skip) } ||
method == 'OPTIONS'
end
def extract_token(env)
auth_header = env[@token_header]
return auth_header
auth_header.start_with?()
auth_header.split(, ).last
auth_header
()
token
token.length >=
()
{ , }
[
,
{
=> ,
=>
},
[]
]
use ,
[, , ]
Caching Middleware:
require 'digest/md5'
class SimpleCache
def initialize(app, options = {})
@app = app
@cache = {}
@ttl = options[:ttl] || 300
@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)
.hexdigest([
env[],
env[],
env[]
].join())
()
entry = [key]
entry
.now - entry[] <=
entry[]
.delete(key)
()
[key] = {
response,
.now
}
()
= app
= .new( options[])
= options[] ||
= options[] ||
()
request = .new(env)
.call(env) request.get?
cache_key = generate_cache_key(env)
cached = .get(cache_key)
.load(cached)
status, headers, body = .call(env)
status ==
.setex(cache_key, , .dump([status, headers, body]))
[status, headers, body]
()
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
class XMLBodyParser
def initialize()
= app
()
xml_request?(env)
body = env[].read
env[].rewind
parsed = .from_xml(body)
env[] = parsed
env[] = parsed
=> e
error_response(, )
.call(env)
()
content_type = env[]
content_type && (content_type.?() ||
content_type.?())
()
[
status,
{ => },
[{ message }.to_json]
]
Middleware Ordering Patterns
Security-First Stack:
use Rack::SSL if ENV['RACK_ENV'] == 'production'
use Rack::Attack
use SecurityHeaders
use Rack::Cors do
allow do
origins '*'
resource '*', headers: :any, methods: [:get, :post, :put, :delete, :options]
end
end
use Rack::Deflater
use Rack::Static, urls: ['/public'], root: 'public'
use Rack::CommonLogger
use JSONBodyParser
use Rack:,
[],
,
,
[] ==
use
use , [, ]
use
run
API-Focused Stack:
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
use Rack::Attack
use Rack::Deflater
use RequestLogger
use JSONBodyParser
use TokenAuthentication, skip_paths: ['/auth']
use RedisCache, ttl: 300
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
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
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)
@logger.error({
error: error.class.name,
message: error.message,
path: request.path,
method: request.request_method,
backtrace: error.backtrace[0..10]
}.to_json)
if handler = @error_handlers[error.class]
return handler.call(error)
end
status = status_for_error(error)
[
status,
{ 'Content-Type' => 'application/json' },
[{ error: error.message, error..name }.to_json]
]
()
error
,
use ,
{
=> ->(e) {
[, { => },
[{ e.message, 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
@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
headers['X-Runtime'] = duration.to_s
headers['X-Memory-Delta'] = memory_delta.to_s
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
end
def log_slow_request(env, duration, memory)
@logger.warn({
event: 'slow_request',
method: env[],
env[],
duration.round(),
memory.round()
}.to_json)
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
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) { , } }
it
expect(logger).to receive().with(hash_including(
,
))
expect { request.get() }.to raise_error()
describe
let() { .new(app, logger, []) }
it
expect(logger).not_to receive()
request.get()
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
Converted and distributed by TomeVault — claim your Tome and manage your conversions.