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

时间:2011-02-02 14:45:28

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

我正在尝试使用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的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.<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这个问题?有人解决了吗?在任何地方我找不到任何关于这个问题的提及。 谢谢!

6 个答案:

答案 0 :(得分:8)

我刚遇到这个问题并查看了mime4j的Message构造函数的源代码。它通过调用TempFileStorageProvider获得DefaultStorageProvider.getInstance()。您可以通过调用:

将默认值更改为不写入文件系统的默认值
DefaultStorageProvider.setInstance(new MemoryStorageProvider());

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

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

答案 1 :(得分:7)

好吧,我找到了它的随处 - 我正在使用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编译代码。

答案 2 :(得分:1)

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

答案 3 :(得分:1)

要在RESTEasy中使用MemoryStorageProvider,您可以设置以下系统属性:

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

答案 4 :(得分:0)

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

答案 5 :(得分:0)

我刚刚将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

<?php
function cmp($a, $b)
{
    if ($a['custom_price'] == $b['custom_price']) {
        return 0;
    }
    return ($a['custom_price'] < $b['custom_price']) ? -1 : 1;
}