본문 바로가기
개발/Programming

Jersey File Upload Example

by 카루딘 2017. 6. 2.
반응형

In this example we are going to see how you can upload a File to a server using a JAX-RS REST Service using Jersey. Uploading a File using Jersey is fairly easy, as it uses all the HTTP infrastructure for file upload operations.

In this example we are going to use an HTML Form that has one input field of type file. When the HTTP POST request is constructed, it will contain a media type of multipart/form-data. The media-type multipart/form-datafollows the rules of all multipart MIME data streams. multipart/form-datacontains a number of parts, corresponding to the input parameters of the form. Each part contains a content-disposition header where the disposition type is form-data. The disposition, also contains a “name” parameter, the value of which is the input field name in the HTML form and can be used to obtain this header in our service. Other headers like content-type are usually included as well. For example, a part might contain a header like this:

1Content-Disposition: form-data; name="file"; filename="AVD1.png"
2Content-Type: image/png

In our case, parsing this header we will enable us to obtain the original name of the file the user selected to upload (the filename parameter of the above header). Luckily, Jersey provides all the necessary infrastructure to do that. Following the headers is the the actual value of the part, as expected.

In this example we are not going to focus on how to create JAX-RS application from top to bottom. So make sure you read carefully Jersey Hello World Example  and pay attention to the sections concerning the creation of the project with Eclipse IDE as well as the deployment of the project in Tomcat.

You can create your own project following the instructions on Jersey Hello World Example. But you can also download the Eclipse project of this tutorial here : JAXRS-HelloWorld.zip, and build your code on top of that.

1. Project structure

For this example, I’ve created a new Project called “JAXRS-FileUpload“. You can see the final structure of the project in the image below:

project-structure

The code presented in this new tutorial will only concern JerseyFileUpload.java file.

At this point you can also take a look at the web.xml file to see how the project is configured:

web.xml:

01<?xml version="1.0" encoding="UTF-8"?>
03  <display-name>Restful Web Application</display-name>
04 
05    <servlet>
06        <servlet-name>jersey-helloworld-serlvet</servlet-name>
07        <servlet-class>
08                     com.sun.jersey.spi.container.servlet.ServletContainer
09                </servlet-class>
10        <init-param>
11             <param-name>com.sun.jersey.config.property.packages</param-name>
12             <param-value>com.javacodegeeks.enterprise.rest.jersey</param-value>
13        </init-param>
14        <load-on-startup>1</load-on-startup>
15    </servlet>
16 
17    <servlet-mapping>
18        <servlet-name>jersey-helloworld-serlvet</servlet-name>
19        <url-pattern>/rest/*</url-pattern>
20    </servlet-mapping>
21</web-app>

As you can see our servlet is mapped to /rest/ URI pattern. So the basic structure of the URIs to reach the REST Services used in this example will have the form :

2. Jersey Multipart dependencies

In order to use all the classes that Jersey offers for multipart media manipulation you have to include jersey-multipart.jarto your project dependencies. To resolve this, open pom.xml and paste the following code:

pom.xml:

02  <modelVersion>4.0.0</modelVersion>
03  <groupId>com.javacodegeeks.enterprise.rest.jersey</groupId>
04  <artifactId>JAXRS-HelloWorld</artifactId>
05  <version>0.0.1-SNAPSHOT</version>
06 
07  <repositories>
08        <repository>
09            <id>maven2-repository.java.net</id>
10            <name>Java.net Repository for Maven</name>
11            <url>http://download.java.net/maven/2/</url>
12            <layout>default</layout>
13        </repository>
14    </repositories>
15 
16    <dependencies>
17 
18        <dependency>
19            <groupId>com.sun.jersey</groupId>
20            <artifactId>jersey-server</artifactId>
21            <version>1.9</version>
22        </dependency>
23 
24        <dependency>
25            <groupId>com.sun.jersey.contribs</groupId>
26            <artifactId>jersey-multipart</artifactId>
27            <version>1.9</version>
28        </dependency>
29 
30    </dependencies>
31 
32</project>

3. HTML Upload Form

This is of course to host a simple HTML form to demonstrate the use of file uploading. Go to the Package Explorer, Right Click on the project -> New -> HTML File. The new file will be created in the WebContent folder.

form.html:

01<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Strict//EN" "http://www.w3.org/TR/html4/strict.dtd">
02<html>
03<head>
04<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
05<title>Form Page</title>
06</head>
07<body>
08<h1>Upload a File</h1>
09 
10    <form action="rest/files/upload" method="post" enctype="multipart/form-data">
11 
12       <p>
13        Select a file : <input type="file" name="file" size="50" />
14       </p>
15 
16       <input type="submit" value="Upload It" />
17    </form>
18 
19</body>
20</html>

4. Upload REST Service

Let’s see the code of the JAX-RS REST Service and then discuss the important points.

JerseyFileUpload.java:

01package com.javacodegeeks.enterprise.rest.jersey;
02 
03import java.io.File;
04import java.io.FileOutputStream;
05import java.io.IOException;
06import java.io.InputStream;
07import java.io.OutputStream;
08import javax.ws.rs.Consumes;
09import javax.ws.rs.POST;
10import javax.ws.rs.Path;
11import javax.ws.rs.core.MediaType;
12import javax.ws.rs.core.Response;
13import com.sun.jersey.core.header.FormDataContentDisposition;
14import com.sun.jersey.multipart.FormDataParam;
15 
16@Path("/files")
17public class JerseyFileUpload {
18 
19    private static final String SERVER_UPLOAD_LOCATION_FOLDER = "C://Users/nikos/Desktop/Upload_Files/";
20 
21    /**
22     * Upload a File
23     */
24 
25    @POST
26    @Path("/upload")
27    @Consumes(MediaType.MULTIPART_FORM_DATA)
28    public Response uploadFile(
29            @FormDataParam("file") InputStream fileInputStream,
30            @FormDataParam("file") FormDataContentDisposition contentDispositionHeader) {
31 
32        String filePath = SERVER_UPLOAD_LOCATION_FOLDER + contentDispositionHeader.getFileName();
33 
34        // save the file to the server
35        saveFile(fileInputStream, filePath);
36 
37        String output = "File saved to server location : " + filePath;
38 
39        return Response.status(200).entity(output).build();
40 
41    }
42 
43    // save uploaded file to a defined location on the server
44    private void saveFile(InputStream uploadedInputStream,
45            String serverLocation) {
46 
47        try {
48            OutputStream outpuStream = new FileOutputStream(new File(serverLocation));
49            int read = 0;
50            byte[] bytes = new byte[1024];
51 
52            outpuStream = new FileOutputStream(new File(serverLocation));
53            while ((read = uploadedInputStream.read(bytes)) != -1) {
54                outpuStream.write(bytes, 0, read);
55            }
56            outpuStream.flush();
57            outpuStream.close();
58        catch (IOException e) {
59 
60            e.printStackTrace();
61        }
62 
63    }
64 
65}

Let’s discuss the above code in detail:

  • The @Consumes annotation is used to specify which MIME media types a service can consume from the client. In our case it’s MediaType.MULTIPART_FORM_DATA.
  • @FormDataParam binds the named body part of a multipart/form-data request entity to a method parameter. The type of the annotated parameter can be a class that is able to read that particular media type. In this example, the server consumes a multipart/form-data request entity body that contains one body part, named file, which is of course the uploaded file. The value of the part file will be handled by an InputStream.
  • Additional information about the file from the “Content-Disposition” header are injected to contentDispositionHeaderparameter of type FormDataContentDisposition, which is simply a representation of the Content-Disposition Header. In this case we can obtain the original name of the uploaded file. To give you an example of how content-dispositionworks in a multipart form, here is a POST request when uploading and image:

    POST Request:

    POST /JAXRS-FileUpload/rest/files/upload HTTP/1.1
    Host: localhost:8080
    Connection: keep-alive
    Content-Length: 25606
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    Origin: http://localhost:9090
    User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36
    Content-Type: multipart/form-data; boundary=----WebKitFormBoundary4eqSAKp0q8C1bMNj
    DNT: 1
    Referer: http://localhost:9090/JAXRS-FileUpload/form.html
    Accept-Encoding: gzip,deflate,sdch
    Accept-Language: el,en;q=0.8,en-GB;q=0.6
    
    ------WebKitFormBoundary4eqSAKp0q8C1bMNj
    Content-Disposition: form-data; name="file"; filename="AVD1.png"
    Content-Type: image/png
    
    ‰PNG
    
    

5. Run the code

After deploying your service, open a browser and go to the form URL.

form_URI:

Here it is on the browser:

upload-form

If you press “Choose File” a file selection dialogue will pop up. I’ve randomly selected an image from my Desktop.

Click “Open” and you are ready to submit the form. You can see the original name of the file:

upload-form-ready

When you click submit, as a result you will see the path of the uploaded file on the server:

result

6. Using FormDataMultiPart

You can also use the FormDataMultiPart class, that simply represents the HTML form and its parts. As you will see it is very convenient when used in a form with a big number of multipart fields. Packing them all in one Object means that you don’t have to define a lot of arguments in your method, plus you will be able to handle fields with arbitrary names etc. Let’s see how you can use it :

JerseyFileUpload.java:

01package com.javacodegeeks.enterprise.rest.jersey;
02 
03import java.io.File;
04import java.io.FileOutputStream;
05import java.io.IOException;
06import java.io.InputStream;
07import java.io.OutputStream;
08 
09import javax.ws.rs.Consumes;
10import javax.ws.rs.POST;
11import javax.ws.rs.Path;
12import javax.ws.rs.core.MediaType;
13import javax.ws.rs.core.Response;
14 
15import com.sun.jersey.core.header.ContentDisposition;
16import com.sun.jersey.multipart.FormDataBodyPart;
17import com.sun.jersey.multipart.FormDataMultiPart;
18 
19@Path("/files")
20public class JerseyFileUpload {
21 
22    private static final String SERVER_UPLOAD_LOCATION_FOLDER = "C://Users/nikos/Desktop/Upload_Files/";
23 
24    /**
25     * Upload a File
26     */
27 
28    @POST
29    @Path("/upload")
30    @Consumes(MediaType.MULTIPART_FORM_DATA)
31    public Response uploadFile(FormDataMultiPart form) {
32 
33         FormDataBodyPart filePart = form.getField("file");
34 
35         ContentDisposition headerOfFilePart =  filePart.getContentDisposition();
36 
37         InputStream fileInputStream = filePart.getValueAs(InputStream.class);
38 
39         String filePath = SERVER_UPLOAD_LOCATION_FOLDER + headerOfFilePart.getFileName();
40 
41        // save the file to the server
42        saveFile(fileInputStream, filePath);
43 
44        String output = "File saved to server location using FormDataMultiPart : " + filePath;
45 
46        return Response.status(200).entity(output).build();
47 
48    }
49 
50    // save uploaded file to a defined location on the server
51    private void saveFile(InputStream uploadedInputStream, String serverLocation) {
52 
53        try {
54            OutputStream outpuStream = new FileOutputStream(new File(
55                    serverLocation));
56            int read = 0;
57            byte[] bytes = new byte[1024];
58 
59            outpuStream = new FileOutputStream(new File(serverLocation));
60            while ((read = uploadedInputStream.read(bytes)) != -1) {
61                outpuStream.write(bytes, 0, read);
62            }
63 
64            outpuStream.flush();
65            outpuStream.close();
66 
67            uploadedInputStream.close();
68        catch (IOException e) {
69 
70            e.printStackTrace();
71        }
72 
73    }
74 
75}

As you can see we define a FormDataMultiPart argument for uploadFile method. Then, we use getField API method of FormDataMultiPart class to obtain a FormDataBodyPart instance that simply represents a body part of the form. In our case we choose the part named file. You can then call getContentDisposition API method of FormDataBodyPart class to get a ContentDisposition instance (that obviously represents a content-disposition header). Next you can call getValueAs API method of FormDataBodyPart class to read the value of that particular form field. You can also choose a suitable reader for that type of media. We choose InputStream.

Let’s run it. Here is the upload form:

second-form

And when you hit “Upload It”:

uploaded-file

Notes

It is important to note that you should be careful when using Content-Disposition headers as they suffer from several security pitfalls, many of which can be found on it’s original  documentation. Furthermore some browsers do not implement correctly the demonstrated functionality, because they pass the full path of the uploaded file as the fileName. This is the case with Internet Explorer. Instead of AVD1.png, you could get the full file path of the image : C:\Users\nikos\Desktop\AVD1.png. But, because ‘\’ should be escaped in HTTP/1.1 requests, the file name you’ll get in your service will be C:UsersnikosDesktopAVD1.png, which is a total mess. That is not an easy problem to overcome. One solution you can use is read the file path using Javascript and then either parse the file path to obtain the name, or send the name with the slashes as a hidden parameter. Having said that, it might not be very important for your application to store the file to the server using the original file name, not to mention that sometimes this is absolutely wrong and dangerous.

Donwload The Eclipse Project

This was an example on how to upload files to a server using JAX-RS with Jersey. Download the Eclipse project of this example : JAXRS-FileUpload.zipJAXRS-UsingFormData.zip

반응형