2011-06-10 41 views
11

我在Jersey上编写了一个应用程序,用于处理简单文件上传。这在球衣1.2上效果很好。在后来的版本(当前为1.7)中引入@FormDataParam来处理多部分/表单输入。我使用jersey-multipart和mimepull依赖。看来,这样做的新方法是在appengine中创建临时文件,我们都知道这是非法的...使用jersey-1.7在Google 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创建临时文件?

回答

17

对于超出其默认大小的文件,multipart将创建一个临时文件。为了避免这种情况 - 创建一个文件是不可能在GAE上 - 你可以创建一个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(); 
} 
+0

任何想法如何以编程方式做到这一点? – gk5885 2012-09-28 00:16:44

+0

@ gk5885通过“编程”你是指没有Jersey/JAX-RS? – 2012-09-28 13:43:03

+0

我的意思是如何在代码中设置bufferThrehold而不是通过属性文件。 – gk5885 2012-10-06 17:01:25

1

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

我的解决方案包括创建一个新的MultiPartReader提供商......我下面的代码


@Provider 
    @Consumes("multipart/*") 
    public class GaeMultiPartReader implements MessageBodyReader<MultiPart> { 

    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<MultiPart> type, Type genericType, Annotation[] annotations, MediaType mediaType, 
     MultivaluedMap<String, String> 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); 
     } 
    } 

} 
0

我们经历了类似的问题,码头不让我们上传文件超过9194个字节,(忽然 - 一天),我们后来意识到有人拿走我们的用户访问权限的/ tmp,它对应于某些linux版本的java.io.tmpdir,因此Jetty无法将上传的文件存储在那里,并且出现了400错误。

11

为了使用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资源管理器)中查找文件夹。

现在,无论是multipart功能初始化(在Jersey servlet初始化时)还是文件上传完成(在Jersey servlet发布请求上),都不会再创建临时文件,GAE也不会抱怨。

+1

谢谢你!在'bufferThreshold'修复之前''jersey.config.multipart.'! ;) – DominikAngerer 2015-03-23 12:35:54

+1

对于任何想在Eclipse中执行此操作的人:是的,'Package'文件夹不会显示在Package Explorer视图中,但仍然可以通过使用_Navigator_视图而不是_Package Explorer_来访问它。 (从那里,如果你想探索底层文件系统,你甚至可以右键单击并执行“在远程系统视图中显示”。) – 2015-12-04 01:26:55

+0

你有泽西岛2与App Engine合作?我认为这取决于servlet-api:1.3.x。我知道这是超旧的,但我有兴趣看到你的依赖树... – ndtreviv 2016-10-03 11:28:28

3

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

通常在一个WAR文件结构你把配置文件(web.xmlappengine-web.xml)为WEB-INF/,但在这里,你需要投入WEB-INF/classes

例Maven配置:

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

和你的项目结构是这样的:的jersey-multipart-config.properties与新泽西2.X

Project Structure

内容:

jersey.config.multipart.bufferThreshold = -1 
相关问题