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
LiftResponse
):
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.
Lift Cookbook
suggests to throw ResponseShortcutException
with wrapped response in order to transfer it to the dispatcher from any
place in the code:
1
|
|
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 try
block,
it is necessary to provide an additional case
in the catch
block to re-throw ResponseShortcutException
.
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 |
|
The 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 makeDownloadCall
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
to the Boot.boot
method:
1
|
|
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:
1
|
|
Hope you find this post helpful. Please find the full source code on GitHub.
Thanks,
This post first appeared on Programming | SysGears - Custom Software, please read the originial post: here