Monthly Archives: October 2014

Uploading a file to Jersey REST API using JQuery and Angularjs

There’s the traditional way of uploading a file using Java servlets. In this post, I’m going to walk through how you can do this with a REST API to keep a clean separation between frontend and backend.

Client-side
This should be familiar html. We set up a form to upload a file like so

<form enctype="multipart/form-data" method="post" action="./rest/upload/">
  <div class="form-group">
    <label for="uploadfile">File</label>
    <input type="file" name="uploadfile">
  </div>
  <input type="submit">
</form>

This will make a POST request to “../rest/file” with the file.

Now let’s see how we can do the same with JQuery

$(document).ready(function() {
  $('#submitJquery').click(submitForm);
});      

function submitForm() {
  var file = $('input[name="uploadfile"').get(0).files[0];

  var formData = new FormData();
  formData.append('uploadfile', file);

  $.ajax({
      url: "../rest/file",
      type: 'POST',
      xhr: function() {  // Custom XMLHttpRequest
        var myXhr = $.ajaxSettings.xhr();
        return myXhr;
      },
      // beforeSend: beforeSendHandler,
      success: function(data) {
        alert('successfully uploaded file with '+data+' lines');
      },
      // Form data
      data: formData,
      //Options to tell jQuery not to process data or worry about content-type.
      cache: false,
      contentType: false,
      processData: false
    });
}

In the html, make sure you have a submit link to bind to

<a id="submitJquery" href="#">Submit via JQuery</a>

Take care to note the FormData object. And that’s all there is to it.
Now, why would we want to do this? Maybe you want to add additional fields to send. In which case, you can add more key/values by adding to the FormData like so

formData.append('key1','value1');
formData.append('key2','value2');
...

And here’s how to do this with angularjs

<input type="file" id="csvfile"/>
<button ng-click="uploadCSV()">Upload CSV</button>
  $scope.uploadCSV = function() {
    var formData = new FormData();
    formData.append('uploadfile', document.getElementById('csvfile').files[0]);
    $http.post('/my-webapp/rest/csv/upload',
               formData,
               {
               transformRequest: angular.identity,
               headers: {'Content-Type': undefined}
               })
  }

Server-side
Now, let’s take a look at what needs to be done on the jersey server-side.

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.sun.jersey.core.header.FormDataContentDisposition;
import com.sun.jersey.multipart.FormDataParam;

@Path ("/file")
public class FileResource {
  @POST
  @Consumes(MediaType.MULTIPART_FORM_DATA)
  @Produces(MediaType.TEXT_PLAIN)
  public Response uploadFile(@FormDataParam("uploadfile") InputStream uploadedInputStream,
                             @FormDataParam("uploadfile") FormDataContentDisposition fileDetail) {
    try (
      BufferedReader reader = new BufferedReader(new InputStreamReader(uploadedInputStream));
    ) {
      int numLines = 0;
      String line = null;
      while( (line = reader.readLine()) != null ) {
        numLines++;
        System.out.println(line);
      }
      return Response.ok(Integer.toString(numLines), "text/plain").build();
    } catch (final Exception e) {
      throw new WebApplicationException(e);
    }
  }
}

The things to note are the MediaType.MULTIPART_FORM_DATA and the @FormDataParam parameters.
If you wanted to accept the additional “key1” and “key2” values, then just add two more parameters to the method signature like so:

  public Response uploadFile(@FormDataParam("uploadfile") InputStream uploadedInputStream,
                             @FormDataParam("uploadfile") FormDataContentDisposition fileDetail,
                             @FormDataParam("key1") String value1,
                             @FormDataParam("key2") String value2) {

Thanks for reading.

Check out this post on how to send and receive arrays of values with the same key name here.

Tagged , , ,

Receiving arrays from form elements with Jersey

You may have worked with Jersey before, and for the most part, writing a REST API has been as easy as writing a java API. So to accept an array of Strings, you could do something like this.

Jersey Code:

@Path("/rest/")
public class ServiceClass {

@GET
@Path("/strings")
public Response postStringArray(@QueryParam("keywords") List<String> inputList) {
...
}

HTTP Request:

GET http://example.com/rest/strings?keywords=Hello&keywords=World&keywords=...

And that would work fine. But what if you had to send an array from a form element (because you need to upload a file for example).

Suppose you had this form:

<form enctype="multipart/form-data" method="post" action="/rest/upload">
    Keyword: <input name="keywords" />
    Keyword: <input name="keywords" />
    Keyword: <input name="keywords" />

    File:
    <input type="file" name="file">
    <input type="submit" value="upload">
</form>

Then your Jersey API signature will have to consume MediaType.MULTIPART_FORM_DATA. So you’d think something like this will work

@POST
@Path("upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFileAndKeywords(
    @FormDataParam("keywords") List<String> keywords,
    @FormDataParam("file") InputStream file_in,
    @FormDataParam("file") FormDataContentDisposition fileDetail) {
  ...
}

But you’ll run into a “wrong number of arguments” exception.

Instead, you’ll need to change the List declaration to something unintuitive like this

@POST
@Path("upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFileAndKeywords(
    @FormDataParam("keywords") List<FormDataBodyPart> keywords,
    @FormDataParam("file") InputStream file_in,
    @FormDataParam("file") FormDataContentDisposition fileDetail) {

      for(FormDataBodyPart keyword : keywords)
        System.out.println( keyword.getValueAs(String.class) );

}

I should note one last thing. If you’re posting the form attributes via jQuery, you’ll want to take advantage of the FormData object. But you should take care to post the array of keywords correctly.

For example, this is the wrong way of posting it

var formData = new FormData();
var keywords = [];
$('select[name="keywords"]').each(function() {
  if( $(this).val() )
    keywords.push($(this).val());
});
formData.append('keywords', keywords);
formData.append('file', $('input[name="file"]').get(0).files[0]);

And this would be the right way:

var formData = new FormData();
$('select[name="keywords"]').each(function() {
  if( $(this).val() )
    formData.append('keywords',$(this).val());
});
formData.append('file', $('input[name="file"]').get(0).files[0]);
Tagged ,