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}), 이것이 올바른 방법입니까? 내가 보내는 파일은 특정 파일이므로 클라이언트가 파일을 찾아 볼 필요가 없습니다. 누구든지 파일을 어떻게 보내야하는지 안내해 줄 수 있습니까? JSON 객체로 보내려면 base64를 사용하여 인코딩해야합니까? 또는 JSON 객체로 보내기 위해 인코딩이 필요하지 않습니까? 도움을 주셔서 감사합니다.


java.io.File서버에 실제 (또는 파일 경로)가 있거나 데이터가 데이터베이스, 웹 서비스, 메서드 호출과 같은 다른 소스에서 오는 데이터 InputStream입니까?
Philipp Reichart 2015 년

답변:


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

다운로드 할 파일을 반환하려는 경우, 특히 파일 업로드 / 다운로드의 일부 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 주소로 머신 주소를 localhost에서 변경합니다.

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주석에 무엇이 포함 되어야 합니까?
Uriel

무엇을 넣었는지에 관계없이 JSON 사양에 따라 application / json. ( ietf.org/rfc/rfc4627.txt?number=4627 ) base64로 인코딩 된 파일은 여전히 ​​JSON 태그 안에 있어야합니다
LarsK

3
이진 데이터를 base64로 인코딩 한 다음이를 JSON으로 래핑해도 이점이 없습니다. 불필요하게 응답의 크기를 늘리고 속도를 늦출 것입니다.
Philipp Reichart 2015 년
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.