【问题标题】:what's the correct way to send a file from REST web service to client?将文件从 REST Web 服务发送到客户端的正确方法是什么?
【发布时间】:2012-08-27 17:15:35
【问题描述】:

我刚刚开始开发 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 的方法调用?

标签: java json web-services rest jersey


【解决方案1】:

由于您使用的是 JSON,我会在通过网络发送之前对其进行 Base64 编码。

如果文件很大,请尝试查看 BSON,或其他更适合二进制传输的格式。

如果文件压缩良好,您也可以在 base64 编码之前压缩文件。

【讨论】:

  • 出于整个文件大小的原因,我打算在发送它们之前对其进行压缩,但如果我对其进行 base64 编码,我的 @Produces 注释应该包含什么?
  • application/json 根据 JSON 规范,无论您放入什么。 (ietf.org/rfc/rfc4627.txt?number=4627) 请记住,base64 编码文件仍应位于 JSON 标记内
  • 在 base64 中编码二进制数据然后将其包装在 JSON 中没有任何好处。它只会不必要地增加响应的大小并减慢速度。
【解决方案2】:

如果你想返回一个要下载的文件,特别是如果你想与一些文件上传/下载的 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】:

    我不建议使用 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 对象,但有一个 InputStreamResponse.ok(entity, mediaType) 也应该能够处理它。

    【讨论】:

    • 谢谢,这很好用,但是如果我想使用整个文件夹结构怎么办?我在想this 之类的东西,因为我将在客户端接收各种文件,我应该如何处理 HttpResponse 的实体响应?
    • 查看ZipOutputStream 并从getFile() 返回StreamingOutput。通过这种方式,您可以获得大多数客户应该能够轻松读取的众所周知的多文件格式。仅在对您的数据有意义时才使用压缩,即不适用于 JPEG 等预压缩文件。在客户端,有ZipInputStream 来解析响应。
    • 这可能会有所帮助:stackoverflow.com/questions/10100936/…
    • 有没有办法在响应中添加文件的元数据以及文件二进制数据?
    • 您可以随时在响应中添加更多标头。如果这还不够,您必须将其编码到八位字节流中,即提供包含元数据和所需文件的容器格式。
    【解决方案4】:

    将机器地址从 localhost 更改为您希望客户端连接的 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>
    

    【讨论】:

      猜你喜欢
      • 2020-03-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-11-23
      • 1970-01-01
      • 2011-05-30
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多