Cách chính xác để gửi một tệp từ dịch vụ web REST tới máy khách là gì?


103

Tôi vừa mới bắt đầu phát triển các dịch vụ REST, nhưng tôi đã gặp phải một tình huống khó khăn: gửi các tệp từ dịch vụ REST tới khách hàng của tôi. Cho đến nay, tôi đã hiểu về cách gửi các kiểu dữ liệu đơn giản (chuỗi, số nguyên, v.v.) nhưng gửi một tệp lại là một vấn đề khác vì có rất nhiều định dạng tệp mà tôi không biết mình nên bắt đầu từ đâu. Dịch vụ REST của tôi được tạo trên Java và tôi đang sử dụng Jersey, tôi đang gửi tất cả dữ liệu bằng định dạng JSON.

Tôi đã đọc về mã hóa base64, một số người nói rằng đó là một kỹ thuật tốt, những người khác nói rằng đó không phải là do vấn đề kích thước tệp. Cách chính xác là gì? Đây là cách một lớp tài nguyên đơn giản trong dự án của tôi đang tìm kiếm:

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

Tôi đoán mã để gửi một tệp sẽ giống như sau:

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

Tôi nên sử dụng loại chú thích nào? Tôi đã thấy một số người giới thiệu để @GETsử dụng @Produces({application/x-octet-stream}), đó có phải là cách chính xác? Các tệp tôi đang gửi là những tệp cụ thể nên khách hàng không cần phải duyệt qua các tệp. Bất cứ ai có thể hướng dẫn tôi cách tôi phải gửi tệp? Tôi có nên mã hóa nó bằng base64 để gửi nó dưới dạng một đối tượng JSON không? hoặc mã hóa không cần thiết để gửi nó như một đối tượng JSON? Cảm ơn vì bất kỳ sự giúp đỡ nào bạn có thể đưa ra.


Bạn có thực tế java.io.File(hoặc đường dẫn tệp) trên máy chủ của mình không hay dữ liệu đến từ một số nguồn khác, như cơ sở dữ liệu, dịch vụ web, lệnh gọi phương thức trả về một InputStream?
Philipp Reichart

Câu trả lời:


138

Tôi không khuyên bạn nên mã hóa dữ liệu nhị phân trong base64 và gói nó trong JSON. Nó sẽ chỉ cần tăng kích thước phản hồi và làm chậm mọi thứ một cách không cần thiết.

Chỉ cần cung cấp dữ liệu tệp của bạn bằng GET và application/octect-streamsử dụng một trong các phương pháp gốc của javax.ws.rs.core.Response(một phần của API JAX-RS, vì vậy bạn không bị khóa trong 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();
}

Nếu bạn không có một Fileđối tượng thực tế , nhưng một InputStream, Response.ok(entity, mediaType)cũng có thể xử lý điều đó.


cảm ơn, điều này hoạt động tốt, nhưng nếu tôi muốn sử dụng toàn bộ cấu trúc thư mục thì sao? Tôi đang nghĩ điều gì đó như thế này Ngoài ra vì tôi sẽ nhận được nhiều tệp khác nhau trên máy khách, tôi nên xử lý phản hồi thực thể của HttpResponse như thế nào?
Uriel

4
Hãy xem ZipOutputStreamcùng với việc trả về một StreamingOutputtừ getFile(). Bằng cách này, bạn sẽ có được một định dạng nhiều tệp nổi tiếng mà hầu hết các khách hàng đều có thể dễ dàng đọc được. Chỉ sử dụng tính năng nén nếu nó có ý nghĩa đối với dữ liệu của bạn, tức là không phù hợp với các tệp nén trước như JPEG. Về phía khách hàng, ZipInputStreamphải phân tích cú pháp phản hồi.
Philipp Reichart,

1
Điều này có thể hữu ích: stackoverflow.com/questions/10100936/…
Basil Dsouza,

Có cách nào để thêm siêu dữ liệu của tệp trong phản hồi cùng với dữ liệu nhị phân của tệp không?
abhig

Bạn luôn có thể thêm nhiều tiêu đề hơn vào phản hồi. Nếu vẫn chưa đủ, bạn sẽ phải mã hóa nó thành luồng octet, tức là phân phát định dạng vùng chứa có cả siêu dữ liệu và tệp bạn muốn.
Philipp Reichart

6

Nếu bạn muốn trả lại một Tệp sẽ được tải xuống, đặc biệt nếu bạn muốn tích hợp với một số javascript lib của tệp tải lên / tải xuống, thì đoạn mã dưới đây sẽ thực hiện công việc:

@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

Thay đổi địa chỉ máy từ localhost thành địa chỉ IP mà bạn muốn máy khách của mình kết nối để gọi dịch vụ được đề cập bên dưới.

Khách hàng để gọi dịch vụ web 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();
    }
}

Dịch vụ trả lời khách hàng:

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 cần thiết:

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

Vì bạn đang sử dụng JSON, tôi sẽ Mã hóa Base64 trước khi gửi nó qua đường dây.

Nếu tệp lớn, hãy thử xem BSON hoặc một số định dạng khác tốt hơn với chuyển nhị phân.

Bạn cũng có thể nén các tệp, nếu chúng nén tốt, trước khi mã hóa base64.


Tôi đã định nén chúng trước khi gửi vì lý do toàn bộ kích thước tệp, nhưng nếu tôi mã hóa base64, thì @Produceschú thích của tôi nên chứa những gì?
Uriel

application / json theo thông số JSON, bất kể bạn đã đưa gì vào đó. ( Ietf.org/rfc/rfc4627.txt?number=4627 ) Ghi nhớ các tập tin mã hóa base64 vẫn nên được bên trong thẻ JSON
LarsK

3
Không có lợi ích gì khi mã hóa dữ liệu nhị phân trong base64 và sau đó gói nó trong JSON. Nó sẽ chỉ cần tăng kích thước phản hồi và làm chậm mọi thứ một cách không cần thiết.
Philipp Reichart,
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.