使用jersey-1.7在Google Appengine上进行多部分文件上传

我用Jersey在Google Appengine上写了一个应用程序来处理简单的文件上传。 这在运动衫1.2上运行正常。 在更高版本(当前1.7)中,引入了@FormDataParam来处理多部分/表单输入。 我正在使用jersey-multipart和mimepull依赖。 似乎新的做法是在appengine中创建临时文件,我们都知道这是非法的……

我是否因为泽西岛现在与AppEngine兼容而错过了什么或做错了什么?

@POST @Path("upload") @Consumes(MediaType.MULTIPART_FORM_DATA) public void upload(@FormDataParam("file") InputStream in) { .... } 

使用这些例外调用时,上述操作将失败…

 /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) 

有人有线索吗? 有没有办法在阻止mimepull创建临时文件的同时做某事?

对于超出其默认大小的文件, multipart将创建一个临时文件。 要避免这种情况 – 在gae上创建文件是不可能的 – 您可以在项目的resources文件夹中创建一个jersey-multipart-config.properties文件,并将此行添加到它:

 bufferThreshold = -1 

然后,代码是你给的代码:

 @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(); } 

为了让那些在使用Eclipse和GPE(Google Plugin for Eclipse)时苦苦挣扎的人们,我给出了这个稍微修改过的解决方案,该解决方案源于@yves的回答。

我已经使用App Engine SDK 1.9.10Jersey 2.12对其进行了测试。 由于不同的问题,它不适用于App Engine SDK 1.9.6 -> 1.9.9

\war\WEB-INF\classes文件夹下,创建一个名为jersey-multipart-config.properties的新文件。 编辑文件,使其包含行jersey.config.multipart.bufferThreshold = -1

请注意\classes文件夹在Eclipse中隐藏,因此请在操作系统的文件资源管理器中查找该文件夹(例如Windows资源管理器)。

现在,当multipartfunction初始化(在Jersey servlet初始化时)和文件上传完成时(在Jersey servlet post请求上),将不再创建临时文件,GAE也不会抱怨。

将文件jersey-multipart-config.properties放在WAR内的WEB-INF/classes下非常重要。

通常在WAR文件结构中,您将配置文件( web.xmlappengine-web.xml )放入WEB-INF/ ,但是这里需要放入WEB-INF/classes

示例Maven配置:

   org.apache.maven.plugins maven-war-plugin 2.4  true   ${basedir}/src/main/webapp/WEB-INF true WEB-INF   ${basedir}/src/main/resources WEB-INF/classes     

您的项目结构可能如下所示:

项目结构

Jersey 2.x的jersey-multipart-config.properties内容:

 jersey.config.multipart.bufferThreshold = -1 

我找到了以编程方式避免使用临时文件创建的解决方案(对于GAE实现非常有用)

我的解决方案包括在我的代码下创建一个新的MultiPartReader Provider


  @Provider @Consumes("multipart/*") public class GaeMultiPartReader implements MessageBodyReader { final Log logger = org.apache.commons.logging.LogFactory.getLog(getClass()); private final Providers providers; private final CloseableService closeableService; private final MIMEConfig mimeConfig; private String getFixedHeaderValue(Header h) { String result = h.getValue(); if (h.getName().equals("Content-Disposition") && (result.indexOf("filename=") != -1)) { try { result = new String(result.getBytes(), "utf8"); } catch (UnsupportedEncodingException e) { final String msg = "Can't convert header \"Content-Disposition\" to UTF8 format."; logger.error(msg,e); throw new RuntimeException(msg); } } return result; } public GaeMultiPartReader(@Context Providers providers, @Context MultiPartConfig config, @Context CloseableService closeableService) { this.providers = providers; if (config == null) { final String msg = "The MultiPartConfig instance we expected is not present. " + "Have you registered the MultiPartConfigProvider class?"; logger.error( msg ); throw new IllegalArgumentException(msg); } this.closeableService = closeableService; mimeConfig = new MIMEConfig(); //mimeConfig.setMemoryThreshold(config.getBufferThreshold()); mimeConfig.setMemoryThreshold(-1L); // GAE FIX } @Override public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return MultiPart.class.isAssignableFrom(type); } @Override public MultiPart readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap headers, InputStream stream) throws IOException, WebApplicationException { try { MIMEMessage mm = new MIMEMessage(stream, mediaType.getParameters().get("boundary"), mimeConfig); boolean formData = false; MultiPart multiPart = null; if (MediaTypes.typeEquals(mediaType, MediaType.MULTIPART_FORM_DATA_TYPE)) { multiPart = new FormDataMultiPart(); formData = true; } else { multiPart = new MultiPart(); } multiPart.setProviders(providers); if (!formData) { multiPart.setMediaType(mediaType); } for (MIMEPart mp : mm.getAttachments()) { BodyPart bodyPart = null; if (formData) { bodyPart = new FormDataBodyPart(); } else { bodyPart = new BodyPart(); } bodyPart.setProviders(providers); for (Header h : mp.getAllHeaders()) { bodyPart.getHeaders().add(h.getName(), getFixedHeaderValue(h)); } try { String contentType = bodyPart.getHeaders().getFirst("Content-Type"); if (contentType != null) { bodyPart.setMediaType(MediaType.valueOf(contentType)); } bodyPart.getContentDisposition(); } catch (IllegalArgumentException ex) { logger.error( "readFrom error", ex ); throw new WebApplicationException(ex, 400); } bodyPart.setEntity(new BodyPartEntity(mp)); multiPart.getBodyParts().add(bodyPart); } if (closeableService != null) { closeableService.add(multiPart); } return multiPart; } catch (MIMEParsingException ex) { logger.error( "readFrom error", ex ); throw new WebApplicationException(ex, 400); } } } 

我们遇到了类似的问题,Jetty不会让我们上传超过9194字节的文件(突然间 – 一天),之后我们意识到有人从/ tmp获取了我们的用户访问权限,这对应于java.io.某些Linux版本上有tmpdir,所以Jetty无法在那里存储上传的文件,我们收到400错误。