GAE上的Resteasy multipart / data-form文件上传

我正在尝试使用resteasy 2.0.1.GA将带有文件的表单上传到GAE应用程序中,使用建议如何使用jax-rs上传多部分/表单文件?

的index.html

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

但是我收到以下错误消息(可能是由于RESTEasy Provider的implmenetation使用临时文件来处理输入流):

 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.(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.(Message.java:141) at org.apache.james.mime4j.message.Message.(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) ... 

是否有人遇到过GAE和RESTEasy这个问题? 有人解决了吗? 我在任何地方都找不到这个问题。 谢谢!

我刚遇到这个问题并查看了mime4j的Message构造函数的源代码。 它通过调用DefaultStorageProvider.getInstance()获取TempFileStorageProvider 。 您可以通过调用以下命令将默认值更改为不写入文件系统的默认值:

 DefaultStorageProvider.setInstance(new MemoryStorageProvider()); 

那是org.apache.james.mime4j.storage.DefaultStorageProvider

感谢使用@MultipartForm的简洁示例!

好吧,我已经找到了它的循环 – 我正在使用apache commons-upload with RESTEasy,通过将HttpServletRequest注入RESTEasy方法(并使用commons-IO将流转换为字节数组/字符串)。 所有包都支持应用引擎。

 @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"; } } 

我仍然宁愿找到一个RESTEasy解决方案,以避免围绕fileIterator编译代码。

看起来mime4j库试图写出临时文件,这在app引擎上是不允许的。 mime4j可以配置为使用内存存储提供程序,但我不知道RESTeasy使用它是否允许该配置。

要将MemoryStorageProvider与RESTEasy一起使用,您可以设置以下系统属性:

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

我用RESTEasy 2.3.1.GA和jboss-as-7.1.0.Final尝试了它。

之前的RESTEasy版本中还存在一个错误,其中临时文件未被删除( https://issues.jboss.org/browse/RESTEASY-681 )。 使用MemoryStorageProvider是一种解决方法。

我试着使用MemoryStorageProvider。 但看起来它对大多数文件不起作用。

我想出了另一种解决方案,即使用谷歌云存储扩展AbstractStorageProvider,它运行良好。

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

* Example usage: * *

 * final String bucketName = "my-bucket"; * DefaultStorageProvider.setInstance(new GcsStorageProvider(gcsService, bucketName)); * 

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

我刚刚将resteasy-multipart-provider jar从2.2.0.GA升级到3.1.4.Final。 我们必须明确地调用close方法。 它将负责删除m4jxxxx.tmp文件。

见@docs http://docs.jboss.org/resteasy/docs/3.1.4.Final/userguide/html_single/index.html

 package org.jboss.resteasy.plugins.providers.multipart; public interface MultipartInput { List getParts(); String getPreamble(); // You must call close to delete any temporary files created // Otherwise they will be deleted on garbage collection or on JVM exit void close(); }