який правильний спосіб надіслати файл клієнту з веб-сервісу REST?


103

Я щойно почав розробляти REST-сервіси, але зіткнувся зі складною ситуацією: надсилаючи файли з мого сервісу REST моєму клієнту. До цих пір я зрозумів, як надсилати прості типи даних (рядки, цілі числа тощо), але надсилання файлу - це інша справа, оскільки існує стільки форматів файлів, що я не знаю, з чого я повинен починати. Моя послуга REST створена на Java, і я використовую Джерсі, я надсилаю всі дані у форматі JSON.

Я читав про кодування base64, деякі кажуть, що це хороша техніка, інші кажуть, що це не через проблеми з розміром файлу. Який правильний спосіб? Ось як виглядає простий клас ресурсів у моєму проекті:

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

Я здогадуюсь, код для надсилання файлу був би таким:

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

Які анотації я повинен використовувати? Я бачив, як деякі люди рекомендують @GETвикористовувати @Produces({application/x-octet-stream}), чи це правильний спосіб? Файли, які я надсилаю, є конкретними, тому клієнту не потрібно переглядати файли. Хтось може мене вказувати, як я повинен надіслати файл? Чи повинен я кодувати його за допомогою64, щоб надіслати його як об'єкт JSON? чи кодування не потрібно, щоб надсилати його як об'єкт JSON? Дякуємо за будь-яку допомогу, яку ви можете надати.


Чи є у вас фактичний java.io.File(або шлях до файлу) на вашому сервері чи дані, які надходять з якогось іншого джерела, наприклад, бази даних, веб-служби, методу повернення дзвінка InputStream?
Philipp Reichart

Відповіді:


138

Я не рекомендую кодувати двійкові дані в base64 та загортати їх у JSON. Це просто зайве збільшить розмір відповідей і сповільнить справи.

Просто подайте дані своїх файлів за допомогою GET та application/octect-streamза допомогою одного із заводських методів javax.ws.rs.core.Response(частина API JAX-RS, щоб ви не були заблоковані у Джерсі):

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

Якщо у вас немає фактичного Fileоб'єкта, але антитер InputStream, він Response.ok(entity, mediaType)повинен мати можливість і з цим.


спасибі, це спрацювало чудово, але що робити, якщо я хочу споживати цілу структуру папок? Я думав , що - щось на зразок цього Крім того, так як я буду отримувати різні файли на клієнті, як я повинен ставитися до HttpResponse відповідному осіб?
Уріель

4
Погляньте ZipOutputStreamразом із поверненням StreamingOutputіз getFile(). Таким чином ви отримуєте відомий багатофайловий формат, який більшість клієнтів мають легко читати. Використовуйте стиснення, лише якщо це має сенс для ваших даних, тобто не для попередньо стиснених файлів, таких як JPEG. На стороні клієнта є ZipInputStreamрозбір відповіді.
Філіп Рейхарт


Чи є спосіб додати метадані файлу у відповідь разом із бінарними даними файлу?
abhig

Ви завжди можете додати більше заголовків у відповідь. Якщо цього недостатньо, вам доведеться закодувати його в потік октету, тобто подати формат контейнера, який містить і метадані, і потрібний файл.
Philipp Reichart

6

Якщо ви хочете повернути файл для завантаження, особливо якщо ви хочете інтегруватися з деякими javascript-файлами для завантаження / завантаження файлів, тоді наведений нижче код повинен виконати цю роботу:

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

3

Змініть адресу машини з localhost на IP-адресу, з якою потрібно підключитися до вашого клієнта, щоб зателефонувати нижче згаданої послуги.

Клієнт зателефонував до веб-сервісу REST:

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

Клієнт служби відповіді:

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 потрібно:

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>

-2

Оскільки ви користуєтесь JSON, я б Base64 кодував його, перш ніж надсилати його через провід.

Якщо файли великі, спробуйте переглянути BSON або інший формат, який краще підходить у бінарних передачах.

Ви також можете копіювати файли, якщо вони добре стискаються, перед кодуванням base64.


Я планував зіштовхувати їх, перш ніж надсилати їх через всю причину розміру файлу, але якщо я base64 кодувати його, що повинна @Producesмістити моя анотація?
Уріель

application / json відповідно до специфікації JSON, незалежно від того, що ви вклали в нього. ( ietf.org/rfc/rfc4627.txt?number=4627 ) Майте на увазі, що закодований файл base64 все ще повинен міститись у тегах JSON
LarsK

3
Немає користі в кодуванні двійкових даних у base64 та згортанні їх у JSON. Це просто зайве збільшить розмір відповідей і сповільнить справи.
Philipp Reichart
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.