从REST Web服务向客户端发送文件的正确方法是什么?


103

我刚刚开始开发REST服务,但是遇到了一个困难的情况:将文件从REST服务发送到客户端。到目前为止,我已经掌握了如何发送简单数据类型(字符串,整数等)的窍门,但是发送文件却是另一回事,因为存在太多的文件格式,我什至不知道从哪里开始。我的REST服务是在Java上完成的,并且我正在使用Jersey,我正在使用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您的服务器上是否有实际的(或文件路径),或者数据是否来自其他来源,例如数据库,Web服务,返回的方法调用InputStream
菲利普·里查特

Answers:


138

我不建议在base64中编码二进制数据并将其包装在JSON中。它只会不必要地增加响应的大小并使速度变慢。

只需使用GET并application/octect-stream使用的工厂方法之一即可提供文件数据javax.ws.rs.core.Response(JAX-RS API的一部分,因此您不会被锁定到Jersey):

@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对象的实体回应?
Uriel 2012年

4
看一看,ZipOutputStreamStreamingOutput从中返回a getFile()。这样,您将获得众所周知的多文件格式,大多数客户端应该可以轻松读取该文件。仅在对您的数据有意义的情况下才使用压缩,即对于像JPEG这样的预压缩文件则不适用。在客户端,有ZipInputStream解析响应。
菲利普·里查特


有没有一种方法可以在响应中添加文件的元数据以及文件二进制数据?
Abhig 2014年

您可以随时向响应添加更多标题。如果这还不够,则必须将其编码到八位字节流中,即提供一种既包含元数据又包含所需文件的容器格式。
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

将计算机地址从本地主机更改为您的客户端希望与之连接的IP地址,以调用以下提到的服务。

客户端调用REST Web服务:

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注释应包含哪些内容?
Uriel 2012年

符合JSON规范的application / json,无论您放入什么。(ietf.org/rfc/rfc4627.txt?number=4627)请记住,base64编码的文件仍应位于JSON标记内
LarsK 2012年

3
在base64中对二进制数据进行编码,然后将其包装在JSON中没有任何好处。它只会不必要地增加响应的大小并使速度变慢。
菲利普·里查特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.