Handling File Upload Responses in GWT

First of all let's make one concept clear: FileUploads don't use XmlHttpRequest as most AJAX requests do. A file upload needs to be a multipart request, and you can't do that with XHR.

So GWT (just like most other AJAX toolkits out there) supports file uploads by making a regular POST of an HTML form to a hidden iframe. That's documented in the API for FileUpload and FormPanel.

This means you can't simply use a GWT RPC service to upload a file, and you can't provide an AsyncCallback for handling the response.

You need to write a custom servlet/controller/action on the server side to handle the request, and that's not a big deal. But then you also need a way of interpreting the server response on the client side, and that turns out to be a bit more challenging than one would expect.

In my case I implemented a Spring controller to handle the file upload, and chose to return a JSON response, so it could easily be read by the client using the JSON module.

The response simply consists, when no error occurs, of a fileId variable containing the ID assigned to the uploaded file, so the client application can reference that file by its ID.

All pretty straightfoward. Here's a simplified version of my first attempt at writing the controller code:

public class FileUploadController extends AbstractController {

@Autowired
private MultipartResolver multipartResolver;

@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
  MultipartHttpServletRequest multipartRequest = multipartResolver.resolveMultipart(request);
  MultipartFile file = multipartRequest.getFile("file");
  String fileId = saveFile(file.getInputStream());
  response.getWriter().printf("{ \"fileId\": \"%s\" }", fileId);
  return null;
}

private String saveFile(InputStream inputStream) {
  // ...
}

}

and the GWT client code to parse the response:

formPanel.addFormHandler(new FormHandler() {
  public void onSubmit(FormSubmitEvent event) {
    // pass
  }
  public void onSubmitComplete(FormSubmitCompleteEvent event) {
    String body = event.getResults();
    try {
      JSONObject response = (JSONObject) JSONParser.parse(body);
      String fileId = ((JSONString) response.get("fileId")).stringValue();
      // use fileId
    } catch (Exception exception) {
      Window.alert("Error! " + exception.getMessage() + "; json: " + body);
  }
}

(As for how to make the file upload request, the FormPanel JavaDoc already provides an example.)

It's such a simple case that I chose to write the JSON string manually rather than using a Java JSON library. Because of such simplicity, I was quite surprised when I got a JSONException when trying to parse the response in the client.

After some debugging, I understood what was going on. The string that the client was trying to parse was not actually the one I was returning from the server side, but

<pre>{ "fileId": "abc123" }</pre>

Where the heck did that <pre>...</pre> tag come from?

Well, remember I said that the file upload request is actually a regular POST request made with an iframe as the form target. So to read the response, GWT will actually get the HTML from the iframe, rather than reading the response directly from the server. And that HTML is actually the browser-generated HTML, i.e. probably what is returned by calling .body.innerHTML on the iframe from JavaScript.

Now guess what? Firefox thinks that the response needs to be displayed in preformatted style, so it wraps it in <pre>...</pre>.

Weird, but not a big deal, I thought, let's just modify the client code to strip that <pre> tag out of the response before parsing it as JSON.

Well, did I really think that all browsers would behave in the same way? I really should know better: of course they don't! For example, Safari (3.1.1 at least) wraps the server response not in a simple <pre> tag, but in a styled one, i.e. something like

<pre style="...">{ "fileId": "abc123" }</pre>

After some fiddling around, I found that there is in fact a way to prevent the browser from manipulating the server response, and that's simply to return the response as text/html, even if that's not true. So

response.setContentType("text/html");
response.getWriter().printf("{ \"fileId\": \"%s\" }", fileId);

seems to work with all browsers and doesn't require extra parsing code on the client side. (Tested on Firefox 2 and 3, IE 7, Safari 3, and Opera 9.)

Incidentally, I tried returning other Content-Type values with no luck. At one point I tried application/json, which is supposed to be the correct Content-Type for JSON according to RFC 4627, but all I got was Firefox displaying a Download File dialogue rather than loading the response into the iframe!

So this whole AJAX file upload stuff feels a bit brittle to me, but setting the response Content-Type to text/html seems to be a simple trick to let us read the server response without browsers interferences.