rack-middleware
Rack middleware development, configuration, and integration patterns. Use when working with middleware stacks or creating custom middleware.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Rack middleware development, configuration, and integration patterns. Use when working with middleware stacks or creating custom middleware.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Common Clap patterns and idioms for argument parsing, validation, and CLI design. Use when implementing CLI arguments with Clap v4+.
Configuration management patterns including file formats, precedence, environment variables, and XDG directories. Use when implementing configuration systems for CLI applications.
Distribution and packaging patterns including shell completions, man pages, cross-compilation, and release automation. Use when preparing CLI tools for distribution.
CLI user experience best practices for error messages, colors, progress indicators, and output formatting. Use when improving CLI usability and user experience.
Advanced concurrency patterns for Tokio including fan-out/fan-in, pipeline processing, rate limiting, and coordinated shutdown. Use when building high-concurrency async systems.
Network programming patterns with Hyper, Tonic, and Tower. Use when building HTTP services, gRPC applications, implementing middleware, connection pooling, or health checks.
| name | rack-middleware |
| description | Rack middleware development, configuration, and integration patterns. Use when working with middleware stacks or creating custom middleware. |
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'
# 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'
# 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
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
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
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
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
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]]
}
}
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
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
assets/middleware-template.rb - Boilerplate for new middlewareassets/middleware-examples/ - Collection of useful middlewareassets/configuration-guide.md - Best practices for middleware configurationreferences/performance-optimization.md - Optimizing middleware performancereferences/middleware-testing.md - Comprehensive testing strategies