Wie kann eine Datei vom REST-Webdienst korrekt an den Client gesendet werden?

102

Ich habe gerade mit der Entwicklung von REST-Diensten begonnen, bin jedoch auf eine schwierige Situation gestoßen: Senden von Dateien von meinem REST-Dienst an meinen Client. Bisher habe ich verstanden, wie man einfache Datentypen (Zeichenfolgen, Ganzzahlen usw.) sendet, aber das Senden einer Datei ist eine andere Sache, da es so viele Dateiformate gibt, dass ich nicht weiß, wo ich überhaupt anfangen soll. Mein REST-Service wird auf Java erstellt und ich verwende Jersey. Ich sende alle Daten im JSON-Format.

Ich habe über Base64-Codierung gelesen. Einige Leute sagen, dass dies eine gute Technik ist, andere sagen, dass dies nicht auf Probleme mit der Dateigröße zurückzuführen ist. Was ist der richtige Weg? So sieht eine einfache Ressourcenklasse in meinem Projekt aus:

import java.sql.SQLException;
import java.util.List;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.UriInfo;

import com.mx.ipn.escom.testerRest.dao.TemaDao;
import com.mx.ipn.escom.testerRest.modelo.Tema;

@Path("/temas")
public class TemaResource {

    @GET
    @Produces({MediaType.APPLICATION_JSON})
    public List<Tema> getTemas() throws SQLException{

        TemaDao temaDao = new TemaDao();        
        List<Tema> temas=temaDao.getTemas();
        temaDao.terminarSesion();

        return temas;
    }
}

Ich vermute, der Code zum Senden einer Datei wäre ungefähr so:

import java.sql.SQLException;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

@Path("/resourceFiles")
public class FileResource {

    @GET
    @Produces({application/x-octet-stream})
    public File getFiles() throws SQLException{ //I'm not really sure what kind of data type I should return

        // Code for encoding the file or just send it in a data stream, I really don't know what should be done here

        return file;
    }
}

Welche Art von Anmerkungen sollte ich verwenden? Ich habe einige Leute gesehen, die eine @GETVerwendung empfohlen haben. Ist @Produces({application/x-octet-stream})das der richtige Weg? Die Dateien, die ich sende, sind spezifisch, sodass der Client die Dateien nicht durchsuchen muss. Kann mich jemand anleiten, wie ich die Datei senden soll? Sollte ich es mit base64 codieren, um es als JSON-Objekt zu senden? oder ist die Codierung nicht erforderlich, um es als JSON-Objekt zu senden? Vielen Dank für jede Hilfe, die Sie geben können.

Uriel
quelle
Haben Sie einen tatsächlichen java.io.File(oder Dateipfad) auf Ihrem Server oder stammen die Daten aus einer anderen Quelle, z. B. einer Datenbank, einem Webdienst oder einem Methodenaufruf, der einen zurückgibt InputStream?
Philipp Reichart

Antworten:

137

Ich empfehle nicht, Binärdaten in base64 zu codieren und in JSON zu verpacken. Es wird nur unnötig die Größe der Antwort erhöhen und die Dinge verlangsamen.

Stellen Sie Ihre Dateidaten einfach mit GET und application/octect-streameiner der werkseitigen Methoden von javax.ws.rs.core.Response(Teil der JAX-RS-API, damit Sie nicht an Jersey gebunden sind) bereit:

@GET
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public Response getFile() {
  File file = ... // Initialize this to the File path you want to serve.
  return Response.ok(file, MediaType.APPLICATION_OCTET_STREAM)
      .header("Content-Disposition", "attachment; filename=\"" + file.getName() + "\"" ) //optional
      .build();
}

Wenn Sie kein tatsächliches FileObjekt haben, aber ein InputStream, Response.ok(entity, mediaType)sollte dies ebenfalls möglich sein.

Philipp Reichart
quelle
danke, das hat super funktioniert, aber was ist, wenn ich eine ganze Ordnerstruktur verbrauchen möchte? Ich war so etwas wie das Denken dies auch , da ich auf dem Client verschiedene Dateien empfangen würde, wie soll ich die Entität Antwort des Httpresponse behandeln?
Uriel
4
Werfen Sie einen Blick ZipOutputStreamzusammen mit der Rückgabe eines StreamingOutputvon getFile(). Auf diese Weise erhalten Sie ein bekanntes Multi-File-Format, das die meisten Clients leicht lesen können sollten. Verwenden Sie die Komprimierung nur, wenn dies für Ihre Daten sinnvoll ist, dh nicht für vorkomprimierte Dateien wie JPEGs. Auf der Client-Seite muss ZipInputStreamdie Antwort analysiert werden.
Philipp Reichart
1
Dies könnte helfen: stackoverflow.com/questions/10100936/…
Basil Dsouza
Gibt es eine Möglichkeit, Metadaten der Datei in der Antwort zusammen mit Datei-Binärdaten hinzuzufügen?
Abhig
Sie können der Antwort jederzeit weitere Header hinzufügen. Wenn dies nicht ausreicht, müssen Sie es in den Oktett-Stream codieren, dh ein Container-Format bereitstellen, das sowohl Metadaten als auch die gewünschte Datei enthält.
Philipp Reichart
6

Wenn Sie eine herunterzuladende Datei zurückgeben möchten, insbesondere wenn Sie einige Javascript-Bibliotheken zum Hochladen / Herunterladen von Dateien integrieren möchten, sollte der folgende Code die folgende Aufgabe ausführen:

@GET
@Path("/{key}")
public Response download(@PathParam("key") String key,
                         @Context HttpServletResponse response) throws IOException {
    try {
        //Get your File or Object from wherever you want...
            //you can use the key parameter to indentify your file
            //otherwise it can be removed
        //let's say your file is called "object"
        response.setContentLength((int) object.getContentLength());
        response.setHeader("Content-Disposition", "attachment; filename="
                + object.getName());
        ServletOutputStream outStream = response.getOutputStream();
        byte[] bbuf = new byte[(int) object.getContentLength() + 1024];
        DataInputStream in = new DataInputStream(
                object.getDataInputStream());
        int length = 0;
        while ((in != null) && ((length = in.read(bbuf)) != -1)) {
            outStream.write(bbuf, 0, length);
        }
        in.close();
        outStream.flush();
    } catch (S3ServiceException e) {
        e.printStackTrace();
    } catch (ServiceException e) {
        e.printStackTrace();
    }
    return Response.ok().build();
}
John 4d5
quelle
3

Ändern Sie die Computeradresse von localhost in IP-Adresse, mit der Ihr Client eine Verbindung herstellen soll, um den unten genannten Dienst aufzurufen.

Client zum Aufrufen des REST-Webservices:

package in.india.client.downloadfiledemo;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response.Status;

import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.UniformInterfaceException;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.multipart.BodyPart;
import com.sun.jersey.multipart.MultiPart;

public class DownloadFileClient {

    private static final String BASE_URI = "http://localhost:8080/DownloadFileDemo/services/downloadfile";

    public DownloadFileClient() {

        try {
            Client client = Client.create();
            WebResource objWebResource = client.resource(BASE_URI);
            ClientResponse response = objWebResource.path("/")
                    .type(MediaType.TEXT_HTML).get(ClientResponse.class);

            System.out.println("response : " + response);
            if (response.getStatus() == Status.OK.getStatusCode()
                    && response.hasEntity()) {
                MultiPart objMultiPart = response.getEntity(MultiPart.class);
                java.util.List<BodyPart> listBodyPart = objMultiPart
                        .getBodyParts();
                BodyPart filenameBodyPart = listBodyPart.get(0);
                BodyPart fileLengthBodyPart = listBodyPart.get(1);
                BodyPart fileBodyPart = listBodyPart.get(2);

                String filename = filenameBodyPart.getEntityAs(String.class);
                String fileLength = fileLengthBodyPart
                        .getEntityAs(String.class);
                File streamedFile = fileBodyPart.getEntityAs(File.class);

                BufferedInputStream objBufferedInputStream = new BufferedInputStream(
                        new FileInputStream(streamedFile));

                byte[] bytes = new byte[objBufferedInputStream.available()];

                objBufferedInputStream.read(bytes);

                String outFileName = "D:/"
                        + filename;
                System.out.println("File name is : " + filename
                        + " and length is : " + fileLength);
                FileOutputStream objFileOutputStream = new FileOutputStream(
                        outFileName);
                objFileOutputStream.write(bytes);
                objFileOutputStream.close();
                objBufferedInputStream.close();
                File receivedFile = new File(outFileName);
                System.out.print("Is the file size is same? :\t");
                System.out.println(Long.parseLong(fileLength) == receivedFile
                        .length());
            }
        } catch (UniformInterfaceException e) {
            e.printStackTrace();
        } catch (ClientHandlerException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static void main(String... args) {
        new DownloadFileClient();
    }
}

Service für Antwortclient:

package in.india.service.downloadfiledemo;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.sun.jersey.multipart.MultiPart;

@Path("downloadfile")
@Produces("multipart/mixed")
public class DownloadFileResource {

    @GET
    public Response getFile() {

        java.io.File objFile = new java.io.File(
                "D:/DanGilbert_2004-480p-en.mp4");
        MultiPart objMultiPart = new MultiPart();
        objMultiPart.type(new MediaType("multipart", "mixed"));
        objMultiPart
                .bodyPart(objFile.getName(), new MediaType("text", "plain"));
        objMultiPart.bodyPart("" + objFile.length(), new MediaType("text",
                "plain"));
        objMultiPart.bodyPart(objFile, new MediaType("multipart", "mixed"));

        return Response.ok(objMultiPart).build();

    }
}

JAR benötigt:

jersey-bundle-1.14.jar
jersey-multipart-1.14.jar
mimepull.jar

WEB.XML:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    id="WebApp_ID" version="2.5">
    <display-name>DownloadFileDemo</display-name>
    <servlet>
        <display-name>JAX-RS REST Servlet</display-name>
        <servlet-name>JAX-RS REST Servlet</servlet-name>
        <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
        <init-param>
             <param-name>com.sun.jersey.config.property.packages</param-name> 
             <param-value>in.india.service.downloadfiledemo</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>JAX-RS REST Servlet</servlet-name>
        <url-pattern>/services/*</url-pattern>
    </servlet-mapping>
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>
</web-app>
Shankar Saran Singh
quelle
-2

Da Sie JSON verwenden, würde ich Base64 Encode verwenden, bevor Sie es über das Kabel senden.

Wenn die Dateien groß sind, versuchen Sie, BSON oder ein anderes Format zu betrachten, das bei binären Übertragungen besser ist.

Sie können die Dateien auch komprimieren, wenn sie gut komprimiert sind, bevor Sie sie von base64 codieren.

LarsK
quelle
Ich hatte vor, sie vor dem Senden aus Gründen der gesamten Dateigröße zu komprimieren. Wenn ich sie jedoch mit base64 codiere, was sollte meine @ProducesAnmerkung enthalten?
Uriel
application / json gemäß JSON-Spezifikation, unabhängig davon, was Sie eingeben. ( ietf.org/rfc/rfc4627.txt?number=4627 ) Beachten Sie , dass sich die Base64-codierte Datei immer noch in JSON-Tags befinden sollte
LarsK
3
Es hat keinen Vorteil, Binärdaten in base64 zu codieren und dann in JSON zu verpacken. Es wird nur unnötig die Größe der Antwort erhöhen und die Dinge verlangsamen.
Philipp Reichart