背景
REST API, WebsocketのAPIを提供するとあるサービスのMockを素早く作るため、Railsを選定したが、メッセージの形式がRailsと合わなかった。
メッセージ送信では、 command
, data
, identifier
を送る必要があるが、
{
"command": "message",
"data": "{\"message\":\"test message\",\"action\":\"speak\"}",
"identifier": "{\"channel\":\"RoomChannel\"}"
}
例えば、 command
をやめて cmd
だけで任意のアクションを指定できるようにしたい。
注意
今回はローカル起動用のMockの作成を目的としていたためoverrideを試しました。
副作用の検証まではしていませんし、通常はRailに乗る方が良いので使い所は注意しましょう。
サーバーが受け取るメッセージを任意の形式に変更する
ActionCable::Conection::Subscriptions#execute_command で受け取る data
を元に、 command
を決める。
例えば以下のような感じにOverrideする。
module ActionCable
module Connection
class Subscriptions
def execute_command(data)
if %w[create_message delete_message].include?(data['cmd'])
perform_action({
'command' => 'message',
'identifier' => identifier(data),
'data' => data.merge({ 'action' => data['cmd'] }).to_json
})
else
logger.error "Received unrecognized message_type in #{data.inspect}"
end
rescue Exception => e
@connection.rescue_with_handler(e)
logger.error "Could not execute command from (#{data.inspect}) [#{e.class} - #{e.message}]: #{e.backtrace.first(5).join(' | ')}"
end
private
def identifier(data)
# ...
end
end
end
end
クライアントに送信するメッセージを任意の形式に変更する
例えばチャンネルのアクションで broadcast
する場合。
こちらは ActionCable::Channel::Base#transmit なので app/channels/application_cable/channel.rb
等でoverrideできる。
例えば、以下の例ではチャンネル側で送信したメッセージをそのままの形式で送信する。
module ApplicationCable
class Channel < ActionCable::Channel::Base
private
def transmit(data, via: nil)
status = "[Extended] #{self.class.name} transmitting #{data.inspect.truncate(300)}"
status += " (via #{via})" if via
logger.debug(status)
payload = { channel_class: self.class.name, data:, via: }
ActiveSupport::Notifications.instrument('transmit.action_cable', payload) do
connection.transmit data
end
end
end
end
他にも以下は app/channels/application_cable/
下の ApplicationCable::Xxx
でoverrideできる。
- 購読時のメッセージ : ActionCable::Channel::Base#transmit_subscription_confirmation
- PINGのメッセージ : ActionCable::Connection::Base#beat