This post describes two approaches to implementing file download in Lift framework. Firstly, we will have a look at the implementation that uses ResponseShortcutException described in the Lift Cookbook. Then, I'll show how to solve the same task with the help of REST service in the way that follows a common Lift approach. Each of the methods has its own pros and cons, so it's up to you to decide which one works better for your task.
Note: All the scenarios described in the post are implemented for Lift version 2.6.2 and Scala version 2.11.4.
As you know, a specific response with appropriate headers and requested data as a body should be sent to a
client in order to trigger file download from the server. In Lift, this response can be initialized by using the
InMemoryResponse class (or any other subclass of
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Then, this newly created response should be returned to the client. The interesting part is that this task is not trivial and can be done in several ways.
suggests to throw
ResponseShortcutException with wrapped response in order to transfer it to the dispatcher from any
place in the code:
It is a special class of exceptions, which will be caught at a higher level where Lift can extract wrapped response and pass it to the client in order to start downloading.
This implementation looks really simple. But often it can be inconvenient, especially, for a complex code that contains
a lot of exception handlers. We always have to keep in mind that, if the code above is placed inside of the
it is necessary to provide an additional
case in the
catch block to re-throw
1 2 3 4 5 6 7 8
Also, we can't use this approach inside a database transaction, because, for example in Squeryl, any exception called inside the transaction block inevitably leads to a rollback.
Fortunately, there is another solution which was mentioned at the beginning. We can create the REST endpoint which returns needed responses to the client. It can be implemented similarly to the Lift implementations of Ajax and Comet (which ensures that we still follow the nice Lift way). We will utilize a special ability of Lift commonly known as “function mapping”, which allows to save functions in the current session as a map with unique names generated by Lift as keys. This ability is usually used for associating functions with some elements on a page. Below is shown a trait which allows to associate a file download call with a page element.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
downloadInvoke method works the same way as the well known
SHtml.ajaxInvoke method. It saves a passed response
into the functions map and associate the
makeDownloadCall method with some element on the page. The
method, in turn, with the help of
JsCmds.RedirectTo, makes the
GET request to an endpoint. The parameter of the
request is a name of our response in the function map. The implementation of the endpoint is shown below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
Now, the only thing that's left is to hook our new service into
LiftRules by adding the code shown below
Requests will be handled by the created endpoint where the required response will be found by a name, converted and passed to the client. After that, the usual save dialog will be triggered on the client side.
This way we can add file download functionality (for example, using the
data-name="download" selector) by simply
adding the following line to a snippet:
Hope you find this post helpful. Please find the full source code on GitHub.