🚨 エラー内容
Render and/or redirect were called multiple times in this action.
Please note that you may only call render OR redirect, and at most once per action.
Also note that neither redirect nor render terminate execution of the action,
so if you want to exit an action after redirecting,
you need to do something like "redirect_to(...); return".
(AbstractController::DoubleRenderError)
🔍 概要
以下のようなコードを書いたところ、AbstractController::DoubleRenderError が発生しました。
class ApiController < ApplicationController
before_action :verify_hmac
def update
# 実際の処理
end
private
def verify_hmac
check_hmac_timestamp
# HMAC 検証処理…
return if signature_not_match
render json: { error: 'Invalid HMAC signature' }, status: :unauthorized
end
def check_hmac_timestamp
timestamp = request.headers['X-HMAC-Timestamp']
begin
Time.iso8601(timestamp)
rescue ArgumentError
render json: { error: 'Invalid timestamp format' }, status: :bad_request
return
end
end
end
⚠️ 問題の原因
上記のように verify_hmac を before_action に指定していますが、その中で呼び出している check_hmac_timestamp の中でも render を実行しています。
Rails では before_action で render または redirect_to を呼び出すと、それ以降のアクション(updateなど)はスキップされますが、メソッドチェーンの途中で render を呼んでも処理は中断されないため、そのまま verify_hmac に戻り、さらに render が呼ばれてしまいます。
つまり、同じリクエスト内で複数回 render を実行してしまうことにより DoubleRenderError が発生しているというわけです。
✅ 対策
✔ 対策1:両メソッドを before_action に分けて定義する
class ApiController < ApplicationController
before_action :check_hmac_timestamp
before_action :verify_hmac
def update
# アクション処理
end
private
def verify_hmac
return if signature_valid?
render json: { error: 'Invalid HMAC signature' }, status: :unauthorized
end
def check_hmac_timestamp
timestamp = request.headers['X-HMAC-Timestamp']
begin
Time.iso8601(timestamp)
rescue ArgumentError
render json: { error: 'Invalid timestamp format' }, status: :bad_request
end
end
end
補足:before_action は定義された順に実行されるため、順番にも注意してください。
✔ 対策2:check_hmac_timestamp では render せず、呼び出し元で処理する
def verify_hmac
unless valid_timestamp?
render json: { error: 'Invalid timestamp format' }, status: :bad_request
return
end
unless signature_valid?
render json: { error: 'Invalid HMAC signature' }, status: :unauthorized
return
end
end
def valid_timestamp?
timestamp = request.headers['X-HMAC-Timestamp']
Time.iso8601(timestamp)
true
rescue ArgumentError
false
end