ScalatraのAtmosphereはWebSocketで文字列しか送信できない
ScalatraでWebSocketをやるためのサポートとしてはAtmosphereが組み込まれていますが、Scalatra側の問題で文字列しか送信できないようになってしまっているという話。
trait ScalatraBroadcaster extends Broadcaster { private[this] val logger: Logger = Logger[ScalatraBroadcaster] protected var _resources: ConcurrentLinkedQueue[AtmosphereResource] protected var _wireFormat: WireFormat protected implicit var _actorSystem: ActorSystem def broadcast[T <: OutboundMessage](msg: T, clientFilter: ClientFilter)(implicit executionContext: ExecutionContext): Future[T] = { val selectedResources = _resources.asScala filter clientFilter logger.trace("Sending %s to %s".format(msg, selectedResources.map(_.uuid))) broadcast(_wireFormat.render(msg), selectedResources.toSet.asJava).map(_ => msg) } }
trait WireFormat { def name: String def supportsAck: Boolean def parseInMessage(message: String): InboundMessage def parseOutMessage(message: String): OutboundMessage def render(message: OutboundMessage): String }
これがScalatra+Atmosphereでメッセージを送信する場合に最終的に呼ばれるメソッドbroadcastの定義なんですが、継承しているインタフェースのBroadcaster.broadcast
の第一引数はObject(Any)なのに対し、WireFormat.render
の型はStringに固定されてしまっています。
しかも、OutboundMessageにBinaryMessageって型があるのに、デフォルト実装では対応なし(空文字の送信になる)。そこはあきらめて、自作WireFormatをAtmosphereSupportを使用しているクラスでimplicit defしてあげましょう。
class BinaryWireFormat extends JacksonSimpleWireformat { override def render(message: OutboundMessage) = message match { case TextMessage(text) => text case JsonMessage(json) => renderJson(json) case BinaryMessage(binary) => Base64.getEncoder.encodeToString(binary) case _ => "" } } class MyScalatraServlet extends AtmosphereSupport { protected override implicit def wireFormat: WireFormat = new BinaryWireFormat atmosphere("/hoge") { new AtmosphereClient { def receive = { case Connected => println("connect") case Disconnected(disconnector, Some(error)) => println("disconnect") case Error(Some(error)) => println(error) case TextMessage(text) => send(new BinaryMessage(/* some Array[Byte] */)) case JsonMessage(json) => send("not supported") } } } }
atmosphereのソースも読んでみたが、renderの戻り値型をAnyに変更してArray[Byte]とかを突っ込んだ場合にどんな動きをするかはわからなかった。そこが分かれば、pull requestを出してもいいかもしれない。