背景
Railsアプリケーションにおいて、Rack::Attackを使用して特定のリクエストに制限を設けた際に、以下のエラーメッセージが発生しました。
NoMethodError at /reservations/confirm
undefined method `env' for {:request=>#<Rack::Attack::Request:0x00007fd2b4b8c3b8 @params=nil, @env={...}}>
このエラーは、Rack::Attack
内のイベント購読の部分で発生しており、ペイロードから直接 env
を呼び出そうとしたために発生しました。
問題点の特定
問題のコードは以下の部分です。
config/initializers/rack_attack.rb
ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, req|
Rails.logger.info "Rack::Attack: #{req.env['rack.attack.match_type']} from #{req.ip} blocked."
end
または
config/initializers/rack_attack.rb
ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, payload|
Rails.logger.info "Rack::Attack: #{payload[:request].env['rack.attack.match_type']} from #{payload[:request].ip} blocked."
end
などがエラーの原因になります。
解決策
payload[:request]
が実際には Rack::Attack::Request
オブジェクトであるため、.env
を直接呼び出す必要があります。修正後のコードは以下のようになります。
ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, request_id, payload|
Rails.logger.info "Rack::Attack: #{payload[:request].env['rack.attack.match_type']} from #{payload[:request].env['REMOTE_ADDR']} blocked."
end
おまけ: ユーザーフレンドリーな429エラーページの追加
リクエスト制限を超えた際に、ユーザーに対してより親切なフィードバックを提供するため、次のようなカスタム429エラーページを作成しました。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>リクエストが多すぎます</title>
<style>
body { font-family: "Helvetica Neue", Arial, sans-serif; line-height: 1.6; padding: 20px; }
h1 { color: #ff6347; }
p { max-width: 550px; }
</style>
</head>
<body>
<h1>お待ちください!</h1>
<p>ただいまアクセスが集中しております。サーバーへの負担軽減のため、少し時間を置いてから再度お試しください。</p>
<p>何度もこのメッセージが表示される場合は、サポートへお問い合わせください。</p>
</body>
</html>
このページは public/429.html
として保存し、Rack::Attackの設定で指定することで、サーバーがリクエスト制限を行った際にこのページを表示するように設定しました。