как правильно отправить файл из веб-службы 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}), это правильный путь? Файлы, которые я отправляю, являются конкретными, поэтому клиенту не нужно просматривать файлы. Может ли кто-нибудь объяснить мне, как мне отправить файл? Должен ли я кодировать его с помощью base64, чтобы отправить его как объект JSON? или кодировка не требуется, чтобы отправить его как объект JSON? Спасибо за любую помощь, которую вы можете оказать.

Уриил
источник
Есть ли у вас фактический java.io.File(или путь к файлу) на вашем сервере или данные поступают из какого-то другого источника, такого как база данных, веб-служба, вызов метода, возвращающий InputStream?
Филипп Рейхарт,

Ответы:

138

Я не рекомендую кодировать двоичные данные в base64 и оборачивать их в JSON. Это просто без нужды увеличит размер ответа и замедлит работу.

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

@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возможность разобрать ответ.
Филипп Рейхарт
1
Это может помочь: stackoverflow.com/questions/10100936/…
Василий Дсуза
Есть ли способ добавить метаданные файла в ответ вместе с двоичными данными файла?
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();
}
Иоанн 4d5
источник
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 03
3
Нет никакой пользы от кодирования двоичных данных в base64, а затем обертывания их в JSON. Это просто без нужды увеличит размер ответа и замедлит работу.
Филипп Рейхарт,