วิธีที่ถูกต้องในการส่งไฟล์จากบริการเว็บ REST ไปยังไคลเอนต์คืออะไร?


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(หรือเส้นทางไฟล์) จริงบนเซิร์ฟเวอร์ของคุณหรือข้อมูลที่มาจากแหล่งอื่น ๆ เช่นฐานข้อมูลบริการเว็บวิธีการเรียกคืนInputStream?
Philipp Reichart

คำตอบ:


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

4
ลองดูZipOutputStreamพร้อมกับการส่งคืนStreamingOutputจากgetFile(). ด้วยวิธีนี้คุณจะได้รูปแบบไฟล์หลายไฟล์ที่รู้จักกันดีซึ่งไคลเอนต์ส่วนใหญ่ควรอ่านได้ง่าย ใช้การบีบอัดเฉพาะในกรณีที่เหมาะสมกับข้อมูลของคุณเท่านั้นไม่ใช่สำหรับไฟล์ที่บีบอัดล่วงหน้าเช่น JPEG ในฝั่งไคลเอ็นต์มีZipInputStreamการแยกวิเคราะห์การตอบสนอง
Philipp Reichart

1
สิ่งนี้อาจช่วยได้: stackoverflow.com/questions/10100936/…
Basil Dsouza

มีวิธีเพิ่มข้อมูลเมตาของไฟล์ในการตอบกลับพร้อมกับข้อมูลไบนารีของไฟล์หรือไม่
abhig

คุณสามารถเพิ่มส่วนหัวเพิ่มเติมในการตอบกลับได้ตลอดเวลา หากยังไม่เพียงพอคุณจะต้องเข้ารหัสลงในออคเต็ตสตรีมเช่นให้ใช้รูปแบบคอนเทนเนอร์ที่มีทั้งข้อมูลเมตาและไฟล์ที่คุณต้องการ
Philipp Reichart

6

หากคุณต้องการส่งคืนไฟล์ที่จะดาวน์โหลดโดยเฉพาะอย่างยิ่งหากคุณต้องการผสานรวมกับ libs จาวาสคริปต์ของการอัปโหลด / ดาวน์โหลดไฟล์จากนั้นผู้ร้องรหัสควรทำงาน:

@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 webservice:

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 หรือรูปแบบอื่นที่ดีกว่าสำหรับการโอนไบนารี

คุณยังสามารถ zip ไฟล์ได้หากบีบอัดได้ดีก่อนที่ base64 จะเข้ารหัสไฟล์เหล่านั้น


ฉันวางแผนที่จะ zip ก่อนที่จะส่งด้วยเหตุผลด้านขนาดไฟล์ทั้งหมด แต่ถ้าฉันเข้ารหัส base64 @Producesคำอธิบายประกอบของฉันควรมีอะไรบ้าง?
Uriel

application / json ตามข้อกำหนดของ JSON ไม่ว่าคุณจะใส่อะไรลงไปก็ตาม ( ietf.org/rfc/rfc4627.txt?number=4627 ) โปรดทราบว่าไฟล์ที่เข้ารหัส base64 ควรอยู่ในแท็ก JSON
LarsK

3
ไม่มีประโยชน์ในการเข้ารหัสข้อมูลไบนารีใน base64 แล้วห่อเป็น JSON มันจะเพิ่มขนาดของการตอบสนองโดยไม่จำเป็นและทำให้สิ่งต่างๆช้าลง
Philipp Reichart
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.