2011-02-02 21 views
7

Estoy intentando utilizar 2.0.1.GA resteasy para cargar un formulario con un archivo de aplicación en GAE, utilizando el método aconsejado en How do I do a multipart/form file upload with jax-rs?Resteasy archivo /-formulario de datos de varias partes de subida en GAE

Índice .html

<form action="/rest/upload" method="post" enctype="multipart/form-data"> 
    <input type="text" name="name" /> 
    <input type="file" name="file" /> 
    <input type="submit" /> 
</form> 

Rest.java

@Path("") 
public class Rest { 
    @POST 
    @Path("/rest/upload") 
    @Consumes("multipart/form-data") 
    public String postContent(@MultipartForm UploadForm form) { 
     System.out.println(form.getData().length); 
     System.out.println(form.getName()); 
     return "Done"; 
    } 
} 

UploadForm.java

public class UploadForm { 

    private String name; 
    private byte[] data; 

    @FormParam("name") 
    public void setPath(String name) { 
     this.name = name; 
    } 

    public String getName() { 
     return name; 
    } 

    @FormParam("file") 
    public void setContentData(byte[] data) { 
     this.data = data; 
    } 

    public byte[] getData() { 
     return data; 
    } 
} 

Pero yo estoy recibiendo el siguiente mensaje de error (probablemente debido a implmenetation del Proveedor RESTEasy que utiliza archivos temporales para manejar el flujo de entrada):

HTTP ERROR 500 

Problem accessing /files/service/upload. Reason: 

    java.io.FileOutputStream is a restricted class. Please see the Google App Engine developer's guide for more details. 

Caused by: 

java.lang.NoClassDefFoundError: java.io.FileOutputStream is a restricted class. Please see the Google App Engine developer's guide for more details. 
    at com.google.appengine.tools.development.agent.runtime.Runtime.reject(Runtime.java:51) 
    at org.apache.james.mime4j.storage.TempFileStorageProvider$TempFileStorageOutputStream.<init>(TempFileStorageProvider.java:117) 
    at org.apache.james.mime4j.storage.TempFileStorageProvider.createStorageOutputStream(TempFileStorageProvider.java:107) 
    at org.apache.james.mime4j.storage.ThresholdStorageProvider$ThresholdStorageOutputStream.write0(ThresholdStorageProvider.java:113) 
    at org.apache.james.mime4j.storage.StorageOutputStream.write(StorageOutputStream.java:119) 
    at org.apache.james.mime4j.codec.CodecUtil.copy(CodecUtil.java:43) 
    at org.apache.james.mime4j.storage.AbstractStorageProvider.store(AbstractStorageProvider.java:57) 
    at org.apache.james.mime4j.message.BodyFactory.textBody(BodyFactory.java:167) 
    at org.apache.james.mime4j.message.MessageBuilder.body(MessageBuilder.java:148) 
    at org.apache.james.mime4j.parser.MimeStreamParser.parse(MimeStreamParser.java:101) 
    at org.apache.james.mime4j.message.Message.<init>(Message.java:141) 
    at org.apache.james.mime4j.message.Message.<init>(Message.java:100) 
    at org.jboss.resteasy.plugins.providers.multipart.MultipartInputImpl.parse(MultipartInputImpl.java:76) 
    at org.jboss.resteasy.plugins.providers.multipart.MultipartFormAnnotationReader.readFrom(MultipartFormAnnotationReader.java:55) 
    at org.jboss.resteasy.core.interception.MessageBodyReaderContextImpl.proceed(MessageBodyReaderContextImpl.java:105) 
    at org.jboss.resteasy.plugins.interceptors.encoding.GZIPDecodingInterceptor.read(GZIPDecodingInterceptor.java:46) 
    at org.jboss.resteasy.core.interception.MessageBodyReaderContextImpl.proceed(MessageBodyReaderContextImpl.java:108) 
    at org.jboss.resteasy.core.messagebody.ReaderUtility.doRead(ReaderUtility.java:111) 
    at org.jboss.resteasy.core.messagebody.ReaderUtility.doRead(ReaderUtility.java:93) 
    at org.jboss.resteasy.core.MessageBodyParameterInjector.inject(MessageBodyParameterInjector.java:146) 
    at org.jboss.resteasy.core.MethodInjectorImpl.injectArguments(MethodInjectorImpl.java:114) 
    at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:137) 
    at org.jboss.resteasy.core.ResourceMethod.invokeOnTarget(ResourceMethod.java:252) 
    at org.jboss.resteasy.core.ResourceMethod.invoke(ResourceMethod.java:217) 
    at org.jboss.resteasy.core.ResourceMethod.invoke(ResourceMethod.java:206) 
    at org.jboss.resteasy.core.SynchronousDispatcher.getResponse(SynchronousDispatcher.java:514) 
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:491) 
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:120) 
    at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:200) 
    at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:48) 
    at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:43) 
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:717) 
    ... 

¿Alguien encontró este problema con GAE y RESTEasy? ¿Alguien lo ha solucionado? No pude encontrar ninguna mención para este problema en ninguna parte. Gracias!

Respuesta

7

Bueno, he encontrado un truco para ello - estoy usando apache commons -carbo con RESTEasy, inyectando HttpServletRequest en un método RESTEasy (y transformando las secuencias en byte array/string usando commons-IO) Todos los paquetes son compatibles con el motor de la aplicación.

@Path("") 
public class Rest { 
    @POST 
    @Path("/rest/upload") 
    public String postContent(@Context HttpServletRequest request) { 
     ServletFileUpload upload = new ServletFileUpload(); 
     FileItemIterator fileIterator = upload.getItemIterator(request); 
     while (fileIterator.hasNext()) { 
      FileItemStream item = fileIterator.next(); 
      if ("file".equals(item.getFieldName())){ 
       byte[] content = IOUtils.toByteArray(item.openStream()) 
       // Save content into datastore 
       // ... 
      } else if ("name".equals(item.getFieldName())){ 
       String name=IOUtils.toString(item.openStream()); 
       // Do something with the name string 
       // ... 
      } 
     } 
     return "Done"; 
    } 
} 

aun así, en lugar de encontrar una solución RESTEasy, para evitar el código ase-en torno a la fileIterator.

1

Parece que la biblioteca mime4j está tratando de escribir archivos temporales, lo que no está permitido en el motor de la aplicación. mime4j se puede configurar para usar un proveedor de almacenamiento de memoria, pero no sé si el uso de RESteasy permite esa configuración.

8

Acabo de toparme con este problema y busqué en el código fuente el constructor de mensajes de mime4j. Obtiene el TempFileStorageProvider llamando al DefaultStorageProvider.getInstance(). Puede cambiar el valor predeterminado a uno que no escriba en el sistema de archivos, llamando al:

DefaultStorageProvider.setInstance(new MemoryStorageProvider()); 

Eso es org.apache.james.mime4j.storage.DefaultStorageProvider.

Gracias por el ejemplo conciso de usar @MultipartForm!

1

Para utilizar el MemoryStorageProvider con RESTEasy se puede establecer la siguiente propiedad del sistema:

-Dorg.apache.james.mime4j.defaultStorageProvider=org.apache.james.mime4j.storage.MemoryStorageProvider 

he probado con RESTEasy 2.3.1.GA y jboss-as-7.1.0.Final.

También hubo un error en versiones anteriores de RESTEasy donde los archivos temporales no se eliminaron (https://issues.jboss.org/browse/RESTEASY-681). Usar el MemoryStorageProvider es una solución para eso.

0

Intenté utilizar MemoryStorageProvider. Pero parece que no funciona para la mayoría de los archivos que no sean más pequeños.

Se me ocurrió otra solución que extiende AbstractStorageProvider utilizando Google Cloud Storage y funciona muy bien.

https://gist.github.com/azimbabu/0aef75192c385c6d4461118583b6d22f

import com.google.appengine.tools.cloudstorage.GcsFileOptions; 
import com.google.appengine.tools.cloudstorage.GcsFilename; 
import com.google.appengine.tools.cloudstorage.GcsInputChannel; 
import com.google.appengine.tools.cloudstorage.GcsOutputChannel; 
import com.google.appengine.tools.cloudstorage.GcsService; 
import lombok.extern.slf4j.Slf4j; 
import org.apache.james.mime4j.storage.AbstractStorageProvider; 
import org.apache.james.mime4j.storage.Storage; 
import org.apache.james.mime4j.storage.StorageOutputStream; 

import java.io.IOException; 
import java.io.InputStream; 
import java.io.OutputStream; 
import java.nio.channels.Channels; 
import java.util.HashSet; 
import java.util.Iterator; 
import java.util.Set; 
import java.util.UUID; 

/** 
* A {@link org.apache.james.mime4j.storage.StorageProvider} that stores the data in google cloud storage files. The files 
* are stored in a user specified bucket. User of this class needs to supply the google cloud storage service and bucket name. 
* 
* This implementation is based on {@link org.apache.james.mime4j.storage.TempFileStorageProvider} 
* <p> 
* Example usage: 
* 
* <pre> 
* final String bucketName = "my-bucket"; 
* DefaultStorageProvider.setInstance(new GcsStorageProvider(gcsService, bucketName)); 
* </pre> 
*/ 
@Slf4j 
public class GcsStorageProvider extends AbstractStorageProvider { 

    private static final int FETCH_SIZE_MB = 4 * 1024 * 1024; 

    private static final String PUBLIC_READ = "public-read"; 

    private static final GcsFileOptions gcsFileOpts = new GcsFileOptions.Builder().acl(PUBLIC_READ).mimeType("text/csv").build(); 

    private final GcsService gcsService; 

    private final String bucketName; 

    /** 
    * Creates a new <code>GcsStorageProvider</code> using the given 
    * values. 
    * 
    * @param gcsService an implementation of {@link GcsService} 
    * @param bucketName google cloud storage bucket name to use. 
    */ 
    public GcsStorageProvider(final GcsService gcsService, final String bucketName) { 
     this.gcsService = gcsService; 
     this.bucketName = bucketName; 
    } 

    @Override 
    public StorageOutputStream createStorageOutputStream() throws IOException { 
     return new GcsStorageProvider.GcsStorageOutputStream(gcsService, bucketName); 
    } 

    private static final class GcsStorage implements Storage { 

     private final GcsService gcsService; 

     private GcsFilename gcsFilename; 

     private static final Set<GcsFilename> filesToDelete = new HashSet(); 

     public GcsStorage(final GcsService gcsService, final GcsFilename gcsFilename) { 
      this.gcsService = gcsService; 
      this.gcsFilename = gcsFilename; 
     } 

     @Override 
     public InputStream getInputStream() throws IOException { 
      if (this.gcsFilename == null) { 
       throw new IllegalStateException("storage has been deleted"); 
      } else { 
       final GcsInputChannel readChannel = gcsService.openPrefetchingReadChannel(gcsFilename, 0, FETCH_SIZE_MB); 
       return Channels.newInputStream(readChannel); 
      } 
     } 

     @Override 
     public void delete() { 
      synchronized(filesToDelete) { 
       if (this.gcsFilename != null) { 
        filesToDelete.add(this.gcsFilename); 
        this.gcsFilename = null; 
       } 

       final Iterator iterator = filesToDelete.iterator(); 

       while(iterator.hasNext()) { 
        GcsFilename filename = (GcsFilename)iterator.next(); 
        try { 
         if (gcsService.delete(filename)) { 
          iterator.remove(); 
         } 
        } catch (final IOException ex) { 
         log.error(ex.getMessage(), ex); 
        } 
       } 

      } 
     } 
    } 

    private static final class GcsStorageOutputStream extends StorageOutputStream { 

     private final GcsService gcsService; 

     private GcsFilename gcsFilename; 

     private final OutputStream outputStream; 

     public GcsStorageOutputStream(final GcsService gcsService, final String bucketName) throws IOException { 
      this.gcsService = gcsService; 

      final String fileName = UUID.randomUUID().toString(); 
      this.gcsFilename = new GcsFilename(bucketName, fileName); 

      GcsOutputChannel gcsOutputChannel = gcsService.createOrReplace(gcsFilename, gcsFileOpts); 
      this.outputStream = Channels.newOutputStream(gcsOutputChannel); 
     } 

     @Override 
     protected void write0(byte[] buffer, int offset, int length) throws IOException { 
      this.outputStream.write(buffer, offset, length); 
     } 

     @Override 
     protected Storage toStorage0() throws IOException { 
      return new GcsStorage(gcsService, gcsFilename); 
     } 

     @Override 
     public void close() throws IOException { 
      super.close(); 
      this.outputStream.close(); 
     } 
    } 
} 
+0

Por favor incluya la parte de la página que se ocupa de esta cuestión, y no sólo la URL. – Antimony

+0

Quise decir que encontré el problema de carga de archivos en gae y probé la solución sugerida aquí: https://stackoverflow.com/a/10374097/1250435. Pero eso no funciona para archivos más grandes. Así que se me ocurrió otra solución y se publicó el enlace esencial con mi solución. Creo que la esencia es pública. –

+0

Puede agregar el código a su respuesta directamente. – Antimony

Cuestiones relacionadas