31

I try to do file upload from a JavaScript client to a JAX-RS Java server.

I use the following REST upload function on my server:

@POST
@Produces('application/json')
UploadDto upload(
        @Context HttpServletRequest request,
        @QueryParam("cookie") String cookie) {

    def contentType
    byte [] fileBytes

    log.debug "upload - cookie: "+cookie

    try{
        if (request instanceof MultipartHttpServletRequest) {
            log.debug "request instanceof MultipartHttpServletRequest"

            MultipartHttpServletRequest myrequest = request
            CommonsMultipartFile file = (CommonsMultipartFile) myrequest.getFile('file')
            fileBytes = file.bytes
            contentType = file.contentType
            log.debug ">>>>> upload size of the file in byte: "+ file.size
        }
        else if (request instanceof SecurityContextHolderAwareRequestWrapper) {
            log.debug "request instanceof SecurityContextHolderAwareRequestWrapper"

            SecurityContextHolderAwareRequestWrapper myrequest = request

            //get uploaded file's inputStream
            InputStream inputStream = myrequest.inputStream

            fileBytes = IOUtils.toByteArray(inputStream);
            contentType = myrequest.getHeader("Content-Type")
            log.debug ">>>>> upload size of the file in byte: "+ fileBytes.size()
        }
        else {
            log.error "request is not a MultipartHttpServletRequest or SecurityContextHolderAwareRequestWrapper"
            println "request: "+request.class
        }
    }
    catch (IOException e) {
        log.error("upload() failed to save file error: ", e)
    }
}

On the client side I send the file as follows:

var str2ab_blobreader = function(str, callback) {
    var blob;
    BlobBuilder = window.MozBlobBuilder || window.WebKitBlobBuilder
            || window.BlobBuilder;
    if (typeof (BlobBuilder) !== 'undefined') {
        var bb = new BlobBuilder();
        bb.append(str);
        blob = bb.getBlob();
    } else {
        blob = new Blob([ str ]);
    }
    var f = new FileReader();
    f.onload = function(e) {
        callback(e.target.result)
    }
    f.readAsArrayBuffer(blob);
}

var fileName = "fileName.jpg";
var contentType = "image/jpeg";
if (file.type.toString().toLowerCase().indexOf("png") > -1) {
    fileName = "fileName.png";
    contentType = "image/png";
}

var xhrNativeObject = new XMLHttpRequest();
var urlParams = ?test=123;
xhrNativeObject.open("post", url + urlParams, true);
xhrNativeObject.setRequestHeader("Content-Type", contentType);

xhrNativeObject.onload = function(event) {

    var targetResponse = event.currentTarget;
    if ((targetResponse.readyState == 4)
            && (targetResponse.status == 200)) {
        var obj = JSON.parse(targetResponse.responseText);
        console.log(obj.uploadImageId);
    } else {
        console.log("fail");
    }
}

var buffer = str2ab_blobreader(file, function(buf) {
    xhrNativeObject.send(buf);
});

When I use the code in my Grails Controller it worked well but when I use it in a REST Resource I always get: request is not a MultipartHttpServletRequest or SecurityContextHolderAwareRequestWrapper

The log output is

request: com.sun.proxy.$Proxy58

The send a file blob from JavaScript I use XMLHttpRequest which contains the blob in the body and some query parameters.

How can I make JAX-RS file upload working? How do I receive some additional query params with my POST request?

1

6 Answers 6

54
+25

On Server Side you can use something like this

@POST
@Path("/fileupload")  //Your Path or URL to call this service
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(
        @DefaultValue("true") @FormDataParam("enabled") boolean enabled,
        @FormDataParam("file") InputStream uploadedInputStream,
        @FormDataParam("file") FormDataContentDisposition fileDetail) {
     //Your local disk path where you want to store the file
    String uploadedFileLocation = "D://uploadedFiles/" + fileDetail.getFileName();
    System.out.println(uploadedFileLocation);
    // save it
    File  objFile=new File(uploadedFileLocation);
    if(objFile.exists())
    {
        objFile.delete();

    }

    saveToFile(uploadedInputStream, uploadedFileLocation);

    String output = "File uploaded via Jersey based RESTFul Webservice to: " + uploadedFileLocation;

    return Response.status(200).entity(output).build();

}
private void saveToFile(InputStream uploadedInputStream,
        String uploadedFileLocation) {

    try {
        OutputStream out = null;
        int read = 0;
        byte[] bytes = new byte[1024];

        out = new FileOutputStream(new File(uploadedFileLocation));
        while ((read = uploadedInputStream.read(bytes)) != -1) {
            out.write(bytes, 0, read);
        }
        out.flush();
        out.close();
    } catch (IOException e) {

        e.printStackTrace();
    }

}

Again this can be checked with the client code in java with

public class TryFile {
public static void main(String[] ar)
       throws HttpException, IOException, URISyntaxException {
    TryFile t = new TryFile();
    t.method();
}
public void method() throws HttpException, IOException, URISyntaxException {
    String url = "http://localhost:8080/...../fileupload";  //Your service URL
    String fileName = ""; //file name to be uploaded
    HttpClient httpclient = new DefaultHttpClient();
    HttpPost httppost = new HttpPost(url);
    FileBody fileContent = new FiSystem.out.println("hello");
    StringBody comment = new StringBody("Filename: " + fileName);
    MultipartEntity reqEntity = new MultipartEntity();
    reqEntity.addPart("file", fileContent);
    httppost.setEntity(reqEntity);

    HttpResponse response = httpclient.execute(httppost);
    HttpEntity resEntity = response.getEntity();
}
}

With HTML, you can simply check with this code

<html>
<body>
<h1>Upload File with RESTFul WebService</h1>
<form action="<Your service URL (htp://localhost:8080/.../fileupload)" method="post" enctype="multipart/form-data">
   <p>
    Choose a file : <input type="file" name="file" />
   </p>
   <input type="submit" value="Upload" />
</form>

To get QueryParam, Check @QueryParam or for header param use @HeaderParam

Example of @QueryParam

Example of @HeaderParam

Try this, hope this helps you with your problem.

Sign up to request clarification or add additional context in comments.

4 Comments

how to do this using apache-wink?
One thing worth mentioning is that such method signatures, which rely on FormDataContentDisposition and @FormDataParam, can't be used on the client side to generate a dynamic proxy (WebResourceFactory.newResource(...)). So on the client side, if you want a dynamic proxy generated on the fly from a server interface, you'd rather use @PathParam to specify the name of the file, e.g.: @POST @Path("/test/{fileName}") @Consumes(APPLICATION_OCTET_STREAM) void upload(@PathParam("fileName") String fileName, InputStream in)
This is not part of JAX-RS. It doesn't work with every server. This works with Tomcat using a Jersey implementation for instance.
Using this, pdf file is getting uploaded perfectly, when uploading jpg file, it is getting uploaded but the file is corrupted. Any suggestions?
16

There is no Jax-RS way to do this. Each server have their own extensions, all using Multi-part form submissions. For example, in CXF, the following will allow you to upload via a multipart form. (Attachment is a CXF specific extension)

@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadFile(@Multipart(value = "vendor") String vendor,
        @Multipart(value = "uploadedFile") Attachment attr) {

whereas the following is the same for Jersey (FormDataParam is a Jersey extension):

 @Consumes(MediaType.MULTIPART_FORM_DATA_TYPE)
 public String postForm(
         @DefaultValue("true") @FormDataParam("enabled") boolean enabled,
         @FormDataParam("data") FileData bean,
         @FormDataParam("file") InputStream file,
         @FormDataParam("file") FormDataContentDisposition fileDisposition) {

(I've ignored the @Path, @POST and @Produces, and other non-relevant annotations.)

7 Comments

I did not get it. Could you please post a full example of how I get the file bytes?
In Jersey, you get an Inputstream. going from that to the byte[] should be relatively straightforward.
Still, I do not know the answer. How do I get the byte[]?
stackoverflow.com/questions/1264709/… has a bunch of answers for this. Apache commons-io, Google guava, or straight Java.
on cxf specific, this could be the answer for @confile question DataHandler handler= attr.getDataHandler(); InputStream instream = handler.getInputStream();
|
10

Add enctype="multipart/form-data" in your form submitter code and @Consumes(MediaType.MULTIPART_FORM_DATA_TYPE) on your @POST method so that we know we are submitting a multipart file and the rest api can consume it. Your rest api method could look like

@POST
@Path("/uploadfile")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response upload(
    @FormDataParam("file") InputStream fileInputStream, 
    @FormDataParam("file") FormDataContentDisposition disposition) {
        //...
}

or

 @POST
 @Path("/uploadfile")
 public void post(File file) {
    Reader reader = new Reader(new FileInputStream(file));
    // ... 
 }

This will create a temporary file on server. It reads from the network and saves into the temporary file.

To program defensively, I would check the content type metadata of the file being uploaded.

Comments

5

Here is what we did to upload file (images in our case) :
Server side

@POST
@RolesAllowed("USER")
@Path("/upload")
@Consumes("multipart/form-data")
public Response uploadFile(MultipartFormDataInput input) throws IOException
{
    File local;
    final String UPLOADED_FILE_PATH = filesRoot; // Check applicationContext-Server.properties file

    //Get API input data
    Map<String, List<InputPart>> uploadForm = input.getFormDataMap();

    //The file name
    String fileName;
    String pathFileName = "";


    //Get file data to save
    List<InputPart> inputParts = uploadForm.get("attachment");

    try
    {
        for (InputPart inputPart : inputParts)
        {
            //Use this header for extra processing if required
            MultivaluedMap<String, String> header = inputPart.getHeaders();
            fileName = getFileName(header);
            String tmp = new SimpleDateFormat("yyyyMMddhhmmss").format(new Date());
            pathFileName = "images/upload/" + tmp + '_' + fileName + ".png";
            fileName = UPLOADED_FILE_PATH + pathFileName;

            // convert the uploaded file to input stream
            InputStream inputStream = inputPart.getBody(InputStream.class, null);

            byte[] bytes = IOUtils.toByteArray(inputStream);
            // constructs upload file path

            writeFile(bytes, fileName);
            // NOTE : The Target picture boundary is 800x600. Should be specified somewhere else ?
            BufferedImage scaledP = getScaledPicture(fileName, 800, 600, RenderingHints.VALUE_INTERPOLATION_BILINEAR, false);
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            ImageIO.write(scaledP, "png", os);
            local = new File(fileName);
            ImageIO.write(scaledP, "png", local);
        }
    }
    catch (Exception e)
    {
        e.printStackTrace();
        return Response.serverError().build();
    }
    return Response.status(201).entity(pathFileName).build();

}

For the client side, we use AngularJS which is coded by another team. I won't be able to explain about it, but here is the code :

    $scope.setPicture = function (element)
{
  var t = new Date();
  console.log(t + ' - ' + t.getMilliseconds());

  // Only process image files.
  if (!element[0].type.match('image.*'))
  {
    console.log('File is not an image');
    Error.current.element = $document[0].getElementById('comet-project-upload');
    Error.current.message = 'Please select a picture.';
    $scope.$apply();
  }
  else if (element[0].size > 10 * 1024 * 1024)
  {
    console.log('File is too big');
    Error.current.element = $document[0].getElementById('comet-project-upload');
    Error.current.message = 'File is too big. Please select another file.';
    $scope.$apply();
  }
  else
  {
    self.animSpinner = true;

    var fd = new FormData();
    //Take the first file
    fd.append('attachment', element[0]);
    //Note : attachment is the compulsory name ?

    Project.uploadImage(fd).then(
      function (data)
      {
        self.animSpinner = false;

        // self.$apply not needed because $digest already in progress
        self.projectPicture = data;
      },
      function ()
      {
        self.animSpinner = false;
        Error.current.element = $document[0].getElementById('comet-project-upload');
        Error.current.message = 'Error with the server when uploading the image';

        console.error('Picture Upload failed! ' + status + ' ' + headers + ' ' + config);
      }
    );
  }
};

And the uploadImage function :

    this.uploadImage = function (imageData)
{
  var deferred = $q.defer();

  $http.post('/comet/api/image/upload', imageData,
    {
      headers: { 'Content-Type': undefined, Authorization: User.hash },
      //This method will allow us to change how the data is sent up to the server
      // for which we'll need to encapsulate the model data in 'FormData'
      transformRequest: angular.identity
      //The cool part is the undefined content-type and the transformRequest: angular.identity
      // that give at the $http the ability to choose the right "content-type" and manage
      // the boundary needed when handling multipart data.
    })
    .success(function (data/*, status, headers, config*/)
    {
      deferred.resolve(data);
    })
    .error(function (data, status, headers, config)
    {
      console.error('Picture Upload failed! ' + status + ' ' + headers + ' ' + config);
      deferred.reject();
    });

  return deferred.promise;
};

Hope it will help you ...

9 Comments

What is MultipartFormDataInput. Which import is it?
It is provided by library org.jboss.resteasy:resteasy-multipart-provider:3.0.8.Final.
But this is not Jersey?
No. Maybe i missed the 'Jersey' tag when i wrote the answer :)
Do you have an answer for Jersey?
|
5

With pure JAX-RS, assuming you don't need a filename, upload method looks like:

    @POST
    @Consumes(MediaType.MULTIPART_FORM_DATA)
    public void upload(InputStream file, @QueryParam("foo") String foo) {
        // Read file contents from the InputStream and do whatever you need
    }

Comments

0

This works for files only.

  @POST
  @Consumes({MediaType.MULTIPART_FORM_DATA})
  public Response upload(Map<String, InputStream> files) {
      return Response.ok().build();
  }

But I am still looking for adding json to the request too.

Maybe, 4.2.1 chapter from JAX-RS Spec is way of achieving the purest approach in practice. That would be implementing a Provider: a MessageBodyReader specialization.

1 Comment

I guess I'm giving up. The entityStream is too hard to deal. I have little time to deal with it. One of the two options need to be chosen: third party libs for dealing with multipart/form-data payloads or implementing Resource classes based on the container implementation of JAX-RS. In the second option, is good to leave it separated and very distinct from the rest of the Resources implementations so It will be easier to change any container-based implementations.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.