2011-06-10 42 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 
相關問題