# 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
# Request modification middleware
class JsonApiMiddleware
def initialize(app)
@app = app
end
def call(env)
request = Rack::Request.new(env)
# Auto-convert POST/PUT JSON body to params
if json_request?(request)
body = request.body.read
request.body.rewind
begin
json_params = JSON.parse(body)
env['rack.request.form_hash'] = json_params
rescue JSON::ParserError
return error_response(400, 'Invalid JSON')
end
end
status, headers, body = @app.call(env)
# Ensure JSON responses have correct content type
if json_request?(request)
headers['Content-Type'] = 'application/json'
end
[status, headers, body]
end
private
def json_request?(request)
request.content_type&.include?('application/json')
end
def error_response(status, message)
[
status,
{ 'Content-Type' => 'application/json' },
[{ error: message }.to_json]
]
end
end
# CORS middleware
class CorsMiddleware
def initialize(app, options = {})
@app = app
@allowed_origins = options[:origins] || ['*']
@allowed_methods = options[:methods] || ['GET', 'POST', 'PUT', 'DELETE']
@allowed_headers = options[:headers] || ['Content-Type', 'Authorization']
end
def call(env)
request = Rack::Request.new(env)
# Handle preflight OPTIONS request
if request.request_method == 'OPTIONS'
return preflight_response
end
status, headers, body = @app.call(env)
# Add CORS headers
headers['Access-Control-Allow-Origin'] = @allowed_origins.join(', ')
headers['Access-Control-Allow-Methods'] = @allowed_methods.join(', ')
headers['Access-Control-Allow-Headers'] = @allowed_headers.join(', ')
headers['Access-Control-Max-Age'] = '7200'
[status, headers, body]
end
private
def preflight_response
[
200,
{
'Access-Control-Allow-Origin' => @allowed_origins.join(', '),
'Access-Control-Allow-Methods' => @allowed_methods.join(', '),
'Access-Control-Allow-Headers' => @allowed_headers.join(', '),
'Access-Control-Max-Age' => '7200'
},
[]
]
end
end
# Caching middleware
class HttpCacheMiddleware
def initialize(app)
@app = app
end
def call(env)
request = Rack::Request.new(env)
# Only cache GET requests
return @app.call(env) unless request.get?
cache_key = "http_cache:#{request.path}:#{request.query_string}"
if cached = Rails.cache.read(cache_key)
return cached
end
status, headers, body = @app.call(env)
response = [status, headers, body]
# Cache successful responses for 5 minutes
if status == 200
Rails.cache.write(cache_key, response, expires_in: 5.minutes)
end
response
end
end