Async response ============== List of normal responding methods: * ``respondView``: responds view template file, with or without layout * ``respondInlineView``: responds embedded template (not separate template file), with or without layout * ``respondText("hello")``: responds a string without layout * ``respondHtml("...")``: same as above, with content type set to "text/html" * ``respondJson(List(1, 2, 3))``: converts Scala object to JSON object then responds * ``respondJs("myFunction([1, 2, 3])")`` * ``respondJsonP(List(1, 2, 3), "myFunction")``: combination of the above two * ``respondJsonText("[1, 2, 3]")`` * ``respondJsonPText("[1, 2, 3]", "myFunction")`` * ``respondBinary``: responds an array of bytes * ``respondFile``: sends a file directly from disk, very fast because `zero-copy `_ (aka send-file) is used * ``respondEventSource("data", "event")`` Xitrum does not automatically send any default response. You must explicitly call ``respondXXX`` methods above to send response. If you don't call ``respondXXX``, Xitrum will keep the HTTP connection for you, and you can call ``respondXXX`` later. To check if the connection is still open, call ``channel.isOpen``. You can also use ``addConnectionClosedListener``: :: addConnectionClosedListener { // The connection has been closed // Unsubscribe from events, release resources etc. } Because of the async nature, the response is not sent right away. ``respondXXX`` returns `ChannelFuture `_. You can use it to perform actions when the response has actually been sent. For example, if you want to close the connection after the response has been sent: :: import io.netty.channel.{ChannelFuture, ChannelFutureListener} val future = respondText("Hello") future.addListener(new ChannelFutureListener { def operationComplete(future: ChannelFuture) { future.getChannel.close() } }) Or shorter: :: respondText("Hello").addListener(ChannelFutureListener.CLOSE) WebSocket --------- :: import scala.runtime.ScalaRunTime import xitrum.annotation.WEBSOCKET import xitrum.{WebSocketAction, WebSocketBinary, WebSocketText, WebSocketPing, WebSocketPong} @WEBSOCKET("echo") class EchoWebSocketActor extends WebSocketAction { def execute() { // Here you can extract session data, request headers etc. // but do not use respondText, respondView etc. // To respond, use respondWebSocketXXX like below. log.debug("onOpen") context.become { case WebSocketText(text) => log.info("onTextMessage: " + text) respondWebSocketText(text.toUpperCase) case WebSocketBinary(bytes) => log.info("onBinaryMessage: " + ScalaRunTime.stringOf(bytes)) respondWebSocketBinary(bytes) case WebSocketPing => log.debug("onPing") case WebSocketPong => log.debug("onPong") } } override def postStop() { log.debug("onClose") super.postStop() } } An actor will be created when there's request. It will be stopped when: * The connection is closed * WebSocket close frame is received or sent Use these to send WebSocket frames: * ``respondWebSocketText`` * ``respondWebSocketBinary`` * ``respondWebSocketPing`` * ``respondWebSocketClose`` There's no respondWebSocketPong, because Xitrum will automatically send pong frame for you when it receives ping frame. To get URL to the above WebSocket action: :: // Probably you want to use this in Scalate view etc. val url = absWebSocketUrl[EchoWebSocketActor] SockJS ------ `SockJS `_ is a browser JavaScript library that provides a WebSocket-like object, for browsers that don't support WebSocket. SockJS tries to use WebSocket first. If that fails it can use a variety of ways but still presents them through the WebSocket-like object. If you want to work with WebSocket API on all kind of browsers, you should use SockJS and avoid using WebSocket directly. :: Xitrum includes the JavaScript file of SockJS. In your view template, just write like this: :: ... html head != jsDefaults ... SockJS does require a `server counterpart `_. Xitrum automatically does it for you. :: import xitrum.{Action, SockJsAction, SockJsText} import xitrum.annotation.SOCKJS @SOCKJS("echo") class EchoSockJsActor extends SockJsAction { def execute() { // To respond, use respondSockJsXXX like below log.info("onOpen") context.become { case SockJsText(text) => log.info("onMessage: " + text) respondSockJsText(text) } } override def postStop() { log.info("onClose") super.postStop() } } An actor will be created when there's a new SockJS session. It will be stopped when the SockJS session is closed. Use these to send SockJS frames: * ``respondSockJsText`` * ``respondSockJsClose`` See `Various issues and design considerations `_: :: Basically cookies are not suited for SockJS model. If you want to authorize a session, provide a unique token on a page, send it as a first thing over SockJS connection and validate it on the server side. In essence, this is how cookies work. To config SockJS clustering, see :doc:`Clustering with Akka `. Chunked response ---------------- To send `chunked response `_: 1. Call ``setChunked`` 2. Call ``respondXXX`` as many times as you want 3. Lastly, call ``respondLastChunk`` Chunked response has many use cases. For example, when you need to generate a very large CSV file that does may not fit memory, you can generate chunk by chunk and send them while you generate: :: // "Cache-Control" header will be automatically set to: // "no-store, no-cache, must-revalidate, max-age=0" // // Note that "Pragma: no-cache" is linked to requests, not responses: // http://palizine.plynt.com/issues/2008Jul/cache-control-attributes/ setChunked() val generator = new MyCsvGenerator generator.onFirstLine { line => val future = respondText(header, "text/csv") future.addListener(new ChannelFutureListener { def operationComplete(future: ChannelFuture) { if (future.isSuccess) generator.next() } } } generator.onNextLine { line => val future = respondText(line) future.addListener(new ChannelFutureListener { def operationComplete(future: ChannelFuture) { if (future.isSuccess) generator.next() } }) } generator.onLastLine { line => val future = respondText(line) future.addListener(new ChannelFutureListener { def operationComplete(future: ChannelFuture) { if (future.isSuccess) respondLastChunk() } }) } generator.generate() Notes: * Headers are sent on the first ``respondXXX`` call. * You can send optional trailing headers at ``respondLastChunk`` * :doc:`Page and action cache ` cannot be used with chunked response. Using chunked response together with ``ActorAction``, you can easily implement `Facebook BigPipe `_. Forever iframe ~~~~~~~~~~~~~~ Chunked response `can be used `_ for `Comet `_. The page that embeds the iframe: :: ... ... ... The action that responds ``\n") Later, whenever you want to pass data to the browser, just send a snippet: :: if (channel.isOpen) respondText("\n") else // The connection has been closed, unsubscribe from events etc. // You can also use ``addConnectionClosedListener``. Event Source ~~~~~~~~~~~~ See http://dev.w3.org/html5/eventsource/ Event Source response is a special kind of chunked response. Data must be UTF-8. To respond event source, call ``respondEventSource`` as many time as you want. :: respondEventSource("data1", "event1") // Event name is "event1" respondEventSource("data2") // Event name is set to "message" by default