Multipart File Upload on Google Appengine using jersey-1.7
I wrote an application on Google Appengine with Jersey to handle simple file uploading. This works fine when it was on jersey 1.2. In the later versions (current 1.7) @FormDataParam is introduced to handle multipart/form inputs. I am using jersey-multipart and the mimepull dependency. It seems that the new way of doing it is creating temporary files in appengine which we all know is illegal...
Am I missing something or doing something wrong here since Jersey is now supposedly compatible with AppEngine?
@POST
@Path("upload")
@Consumes(MediaType.MULTIPART_FORM_DATA)
public void upload(@FormDataParam("file") InputStream in) { .... }
The above will fail when called with these exceptions...
/upload
java.lang.SecurityException: Unable to create temporary file
at java.io.File.checkAndCreate(File.java:1778)
at java.io.File.createTempFile(File.java:1870)
at java.io.File.createTempFile(File.java:1907)
at org.jvnet.mimepull.MemoryData.createNext(MemoryData.java:87)
at org.jvnet.mimepull.Chunk.createNext(Chunk.java:59)
at org.jvnet.mimepull.DataHead.addBody(DataHead.java:82)
at org.jvnet.mimepull.MIMEPart.addBody(MIMEPart.java:192)
at org.jvnet.mimepull.MIMEMessage.makeProgress(MIMEMessage.java:235)
at org.jvnet.mimepull.MIMEMessage.parseAll(MIMEMessage.java:176)
at org.jvnet.mimepull.MIMEMessage.getAttachments(MIMEMessage.java:101)
at com.sun.jersey.multipart.impl.MultiPartReaderClientSide.readMultiPart(MultiPartReaderClientSide.java:177)
at com.sun.jersey.multipart.impl.MultiPartReaderServerSide.readMultiPart(MultiPartReaderServerSide.java:80)
at com.sun.jersey.multipart.impl.MultiPartReaderClientSide.readFrom(MultiPartReaderClientSide.java:139)
at com.sun.jersey.multipart.impl.MultiPartReaderClientSide.readFrom(MultiPartReaderClientSide.java:77)
at com.sun.jersey.spi.container.ContainerRequest.getEntity(ContainerRequest.java:474)
at com.sun.jersey.spi.container.ContainerRequest.getEntity(ContainerRequest.java:538)
Anyone have a clue? Is there a way to do thing while preventing mimepull from creating the temporary file?
For files beyond its default size, multipart
will create a temporary file. To avoid this — creating a file is impossible on gae — you can create a jersey-multipart-config.properties
file in the project's resources folder and add this line to it:
bufferThreshold = -1
Then, the code is the one you gave:
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response post(@FormDataParam("file") InputStream stream, @FormDataParam("file") FormDataContentDisposition disposition) throws IOException {
post(file, disposition.getFileName());
return Response.ok().build();
}
For the benefit of those struggling when using Eclipse with GPE (Google Plugin for Eclipse) I give this slightly modified solution derived from @yves' answer.
I have tested it with App Engine SDK 1.9.10
and Jersey 2.12
. It will not work with App Engine SDK 1.9.6 -> 1.9.9
amongst others due to a different issue.
Under your \war\WEB-INF\classes
folder create a new file called jersey-multipart-config.properties
. Edit the file so it contains the line jersey.config.multipart.bufferThreshold = -1
.
Note that the \classes
folder is hidden in Eclipse so look for the folder in your operating system's file explorer (e.g. Windows Explorer).
Now, both when the multipart
feature gets initialized (on Jersey servlet initialization) and when a file upload is done (on Jersey servlet post request) the temp file will not be created anymore and GAE won't complain.
It is very important to put the file jersey-multipart-config.properties
under WEB-INF/classes
inside the WAR.
Usually in a WAR file structure you put the config files (web.xml
, appengine-web.xml
) into WEB-INF/
, but here you need to put into WEB-INF/classes
.
Example Maven configuration:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.4</version>
<configuration>
<archiveClasses>true</archiveClasses>
<webResources>
<resource>
<directory>${basedir}/src/main/webapp/WEB-INF</directory>
<filtering>true</filtering>
<targetPath>WEB-INF</targetPath>
</resource>
<resource>
<directory>${basedir}/src/main/resources</directory>
<targetPath>WEB-INF/classes</targetPath>
</resource>
</webResources>
</configuration>
</plugin>
And your project structure can look like:
Content of jersey-multipart-config.properties
with Jersey 2.x:
jersey.config.multipart.bufferThreshold = -1