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