【问题标题】:Resteasy multipart/data-form file upload on GAE在 GAE 上上传 Resteasy 多部分/数据格式文件
【发布时间】:2011-02-02 14:45:27
【问题描述】:

我正在尝试使用 resteasy 2.0.1.GA 将包含文件的表单上传到 GAE 应用程序,使用 How do I do a multipart/form file upload with jax-rs? 建议的方法

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

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

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

有人在使用 GAE 和 RESTEasy 时遇到过这个问题吗?有人解决了吗?我在任何地方都找不到关于这个问题的任何提及。 谢谢!

【问题讨论】:

    标签: java google-app-engine file-upload resteasy


    【解决方案1】:

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

    DefaultStorageProvider.setInstance(new MemoryStorageProvider());
    

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

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

    【讨论】:

      【解决方案2】:

      好吧,我已经找到了一个解决方法 - 我正在使用带有 RESTEasy 的 apache commons-upload,通过将 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 周围出现杂乱无章的代码。

      【讨论】:

        【解决方案3】:

        要将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 是一种解决方法。

        【讨论】:

        • 我用 RESTEasy 3.15.1 和 Tomcat 9.0.40 进行了测试 - 它工作正常。非常感谢!
        【解决方案4】:

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

        【讨论】:

          【解决方案5】:

          我尝试使用 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}
           * <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();
                  }
              }
          }
          

          【讨论】:

          • 请包含解决此问题的页面部分,而不仅仅是 URL。
          • 我的意思是我在 gae 上遇到了文件上传问题并尝试了这里建议的解决方案:stackoverflow.com/a/10374097/1250435。但这不适用于更大的文件。所以我想出了另一个解决方案,并发布了我的解决方案的要点链接。我认为要点是公开的。
          【解决方案6】:

          我刚刚将 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<InputPart> 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();
          }
          

          【讨论】:

            猜你喜欢
            • 2019-09-02
            • 1970-01-01
            • 1970-01-01
            • 2011-05-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-08-01
            • 1970-01-01
            相关资源
            最近更新 更多