2011-06-10 3 views
11

Я написал приложение на Google Appengine с Джерси для обработки простой загрузки файлов. Это прекрасно работает, когда он был на майке 1.2. В более поздних версиях (текущий 1.7) вводится @FormDataParam для обработки входов multipart/form. Я использую jersey-multipart и зависимость mimepull. Кажется, что новый способ сделать это создание временных файлов в appengine, которые все мы знаем, является незаконным ...Загрузка многостраничного файла на Google Appengine с использованием jersey-1.7

Я что-то пропустил или что-то не так сделал, поскольку Джерси теперь предположительно совместим с 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

+0

@ gk5885 Под «программным» вы подразумеваете без Джерси/JAX-RS? –

+0

Я имею в виду, как установить bufferThrehold в коде, а не через файл свойств. – gk5885

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

Мы столкнулись с подобной проблемой, Jetty не позволит нам загружать файлы более чем 9194 байт, (все вдруг - один день), мы поняли потом, что кто-то взял наш пользовательский доступ из/tmp, что соответствует java.io.tmpdir в некоторых версиях Linux, поэтому Jetty не смог сохранить загруженный файл там, и мы получили ошибку 400.

11

В интересах тех, кто борется при использовании Eclipse с GPE (плагин Google для Eclipse) Я даю это слегка измененное решение, полученное из ответа @yves.

Я проверил его с App Engine SDK 1.9.10 и Jersey 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 инициализируется (при инициализации сервлета Джерси), и когда загрузка файла завершена (по запросу на сообщение сержанта Сервера), временный файл больше не будет создан, и GAE не будет жаловаться.

+1

Спасибо человеку! 'jersey.config.multipart.' до того, как' bufferThreshold' зафиксировал это! ;) – DominikAngerer

+1

Для тех, кто хочет сделать это в eclipse: Да, папка 'classes' не отображается в представлении Package Explorer, но вы все равно можете получить к ней доступ, используя представление _Navigator_ вместо _Package Explorer_. (Оттуда вы можете щелкнуть правой кнопкой мыши и сделать «Показать в окне« Удаленные системы », если хотите изучить основную файловую систему.) –

+0

У вас есть Джерси 2, работающий с App Engine? Я думал, что это зависит от servlet-api: 1.3.x. Я знаю, что это очень старый, но мне было бы интересно увидеть ваше дерево зависимостей ... – ndtreviv

3

Очень важно поставить файл jersey-multipart-config.properties под WEB-INF/classes внутри WAR.

Обычно в WAR файловой структуры вы кладете файлы конфигурации (web.xml, appengine-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> 

И ваша структура проекта может выглядеть следующим образом:

Project Structure

Содержание jersey-multipart-config.properties с Джерси 2.x:

jersey.config.multipart.bufferThreshold = -1 
Смежные вопросы