7 Stimmen

Einfaches Hochladen von Dateien im Multipart/Data-Formular auf GAE

Ich versuche, mit resteasy 2.0.1.GA ein Formular mit einer Datei in die GAE-Anwendung hochzuladen, und zwar mit der Methode, die unter Wie führe ich einen Multipart/Form-Dateiupload mit jax-rs durch?

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

Ich erhalte jedoch die folgende Fehlermeldung (wahrscheinlich aufgrund der Implenetation des RESTEasy-Providers, der temporäre Dateien zur Verarbeitung des Eingabestroms verwendet):

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

Ist dieses Problem bei GAE und RESTEasy schon einmal aufgetreten? Hat es jemand gelöst? Ich konnte nirgendwo einen Hinweis auf dieses Problem finden. Vielen Dank!

8voto

stickfigure Punkte 12925

Ich bin gerade auf dieses Problem gestoßen und habe mir den Quellcode für den Message-Konstruktor von mime4j angesehen. Er erhält die TempFileStorageProvider durch den Aufruf DefaultStorageProvider.getInstance() . Sie können die Standardeinstellung so ändern, dass nicht in das Dateisystem geschrieben wird, indem Sie die Funktion aufrufen:

DefaultStorageProvider.setInstance(new MemoryStorageProvider());

Das ist org.apache.james.mime4j.storage.DefaultStorageProvider .

Vielen Dank für das prägnante Beispiel zur Verwendung von @MultipartForm!

7voto

Yonatan Punkte 2473

Nun, ich habe eine Umgehung dafür gefunden - ich verwende Apache Commons-Upload mit RESTEasy, indem ich den HttpServletRequest in eine RESTEasy-Methode einfüge (und die Streams mit Commons-IO in Byte-Arrays/Strings umwandle). Alle Pakete werden von der App-Engine unterstützt.

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

Ich würde trotzdem lieber eine RESTEasy-Lösung finden, um den aufwändigen Code rund um den fileIterator zu vermeiden.

2voto

Daniel Engmann Punkte 2830

Zur Verwendung des MemoryStorageProvider mit RESTEasy können Sie die folgende Systemeigenschaft einstellen:

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

Ich habe es mit RESTEasy 2.3.1.GA und jboss-as-7.1.0.Final versucht.

Außerdem gab es in früheren RESTEasy-Versionen einen Fehler, bei dem temporäre Dateien nicht gelöscht wurden ( https://issues.jboss.org/browse/RESTEASY-681 ). Mit dem MemoryStorageProvider ist eine Abhilfe für dieses Problem.

1voto

MattMcKnight Punkte 8064

Es sieht so aus, als ob die mime4j-Bibliothek versucht, temporäre Dateien zu schreiben, was in der App-Engine nicht erlaubt ist. mime4j kann so konfiguriert werden, dass es einen Speicherplatzanbieter verwendet, aber ich weiß nicht, ob die Verwendung von RESTeasy diese Konfiguration erlaubt.

0voto

Ich habe versucht, MemoryStorageProvider zu verwenden. Aber es sieht so aus, als ob es für die meisten Dateien nicht funktioniert, außer für kleinere Dateien.

Ich kam mit einer anderen Lösung, die AbstractStorageProvider mit Google Cloud Storage erweitert und es funktioniert gut.

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X