Monday, January 21, 2008

Serving files from your GWT app.

Also there are other ways to make this trick, I found this implementation easiest.

So. I have a GWT app nicely running but at some point I faced a new requirement - application need to be able create files at server side and serve this files through client to the user, and requirement is that user browser should open "Save as" dialog after the user clicks on the link.

Okay, what I've done first? Since everything coded in GWT, I've immediately created servlet at the server side like:


import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class FileServlet extends HttpServlet {

private static final long serialVersionUID = -4356636877078339046L;

byte[] bbuf = new byte[1024];

private static final String textFileName = "files/text.txt";

public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

String filename = textFileName;

try {
ServletOutputStream out = response.getOutputStream();
ServletContext context = getServletConfig().getServletContext();

File file = new File(context.getRealPath("") + "/" + filename);
String mimetype = context.getMimeType(filename);

response.setContentType((mimetype != null) ? mimetype : "application/octet-stream");
response.setContentLength((int) file.length());
response.setHeader("Content-Disposition", "attachement; filename=\"" + filename + "\"");

DataInputStream in = new DataInputStream(new FileInputStream(file));

int length;
while ((in != null) && ((length = in.read(bbuf)) != -1)) {
out.write(bbuf, 0, length);
}

in.close();
out.flush();
out.close();
}
catch (Exception e) {
e.printStackTrace();
}
}

}


i.e. servlet which reads the file (hardcoded name here) and streams it to the client side of webapp, and mapped it in the web.xml as independent from GWT servlet named FileServer. In my app I had a button which "generates a file" so i've attached code to the the button listener:


RequestBuilder rb = new RequestBuilder(RequestBuilder.GET, GWT.getModuleBaseURL() + "FileServer");

try {
rb.sendRequest("GetFile", new RequestCallback() {
public void onError(Request request, Throwable exception) {
Window.alert("Error getting file");
}

public void onResponseReceived(Request request, Response response) {
System.out.println("response Recieved");
}
});

} catch (RequestException e) {
e.printStackTrace();
}


while everything looks nice, nothing worked, by debugging I found that server gets the request and generates file, servlet streams the data, but no "Save As..." dialog appears... hmm... Than I wasted _a lot_ of time trying to fix this stuff until I got a point that my GET request is not the same GET request that hyperlink generates, so right solution on the client side is to create a hyperlink like follows:

new HTML("< \a href=\"/file-server-sample/FileServer\">Get a text file")

which queries same servlet and invokes proper dialog.
now it works, everybody happy :). Of course you can add parameters to you hyperlink and see those in servlet to alter file generation behavior.