REST web hizmetinden istemciye dosya göndermenin doğru yolu nedir?


103

REST hizmetlerini yeni geliştirmeye başladım, ancak zor bir durumla karşılaştım: REST hizmetimden müşterime dosya göndermek. Şimdiye kadar basit veri türlerini (dizeler, tam sayılar, vb.) Nasıl göndereceğimi anladım, ancak bir dosya göndermek farklı bir konu çünkü o kadar çok dosya biçimi var ki nereden başlamam gerektiğini bile bilmiyorum. REST servisim Java üzerinde yapılıyor ve Jersey kullanıyorum, tüm verileri JSON formatını kullanarak gönderiyorum.

Base64 kodlaması hakkında bir şeyler okudum, bazıları bunun iyi bir teknik olduğunu söylüyor, diğerleri bunun dosya boyutu sorunları yüzünden olmadığını söylüyor. Doğru yol nedir? Projemdeki basit bir kaynak sınıfı şöyle görünüyor:

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

Bir dosya göndermek için kodun aşağıdaki gibi olacağını tahmin ediyorum:

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

Ne tür ek açıklamalar kullanmalıyım? Bazı insanlar bir tavsiye gördüm @GETkullanarak @Produces({application/x-octet-stream}), doğru bir şekilde bu? Gönderdiğim dosyalar belirli dosyalar olduğundan, istemcinin dosyalara göz atması gerekmiyor. Dosyayı nasıl göndermem gerektiği konusunda bana yol gösteren var mı? JSON nesnesi olarak göndermek için base64 kullanarak kodlamalı mıyım? veya kodlama, onu JSON nesnesi olarak göndermek için gerekli değil mi? Verebileceğiniz her türlü yardım için teşekkürler.


java.io.FileSunucunuzda gerçek bir (veya dosya yolu) var mı yoksa veri veritabanı, web hizmeti, yöntem çağrısı gibi başka bir kaynaktan mı geliyor InputStream?
Philipp Reichart

Yanıtlar:


138

İkili verileri base64'te kodlamayı ve JSON ile sarmayı önermiyorum. Sadece gereksiz yere tepkinin boyutunu artıracak ve işleri yavaşlatacaktır.

Dosya verilerinizi GET application/octect-streamkullanarak ve fabrika yöntemlerinden birini kullanarak sunmanız yeterlidir javax.ws.rs.core.Response(JAX-RS API'sinin parçası, böylece Jersey'e kilitli kalmazsınız):

@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();
}

Gerçek bir Filenesneniz yoksa , ancak bir InputStream, Response.ok(entity, mediaType)bunu da halledebilmelisiniz.


teşekkürler, bu harika çalıştı, peki ya bütün bir klasör yapısını kullanmak istersem? Ben böyle bir şey düşünüyordum bu ben istemci üzerinde çeşitli dosyaları alacaksanız beri nasıl HttpResponse en varlık yanıtını davranmalı, aynı zamanda?
Uriel

4
ZipOutputStreamBir ' StreamingOutputden dönerken bir göz atın getFile(). Bu şekilde, çoğu istemcinin kolayca okuyabileceği, iyi bilinen bir çoklu dosya formatı elde edersiniz. Sıkıştırmayı yalnızca verileriniz için anlamlıysa, yani JPEG'ler gibi önceden sıkıştırılmış dosyalar için kullanmayın. İstemci tarafında, ZipInputStreamyanıtı ayrıştırmak var .
Philipp Reichart


Dosya ikili verileriyle birlikte yanıtta dosyanın meta verilerini eklemenin bir yolu var mı?
abhig

Yanıta her zaman daha fazla başlık ekleyebilirsiniz. Bu yeterli değilse, onu sekizli akışa kodlamanız gerekir, yani hem meta verileri hem de istediğiniz dosyayı içeren bir kapsayıcı biçimi sunmanız gerekir.
Philipp Reichart

6

İndirilecek bir Dosyayı döndürmek istiyorsanız, özellikle dosya yükleme / indirmenin bazı javascript kitaplıklarıyla entegre etmek istiyorsanız, aşağıdaki kod işi yapmalıdır:

@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

Makine adresini yerel ana bilgisayardan, istemcinizin aşağıda belirtilen hizmeti aramak için bağlanmasını istediğiniz IP adresine değiştirin.

İstemci, REST web hizmetini arayacak:

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

Müşteriye yanıt verme hizmeti:

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 gerekli:

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 kullandığınız için, kablo üzerinden göndermeden önce Base64 kodlayacağım.

Dosyalar büyükse, BSON'a veya ikili aktarımlarda daha iyi olan başka bir biçime bakmaya çalışın.

Ayrıca dosyaları, base64 kodlamadan önce, iyi sıkıştırılırsa sıkıştırabilirsiniz.


Tüm dosya boyutu nedeniyle göndermeden önce bunları sıkıştırmayı planlıyordum, ancak bunu base64 kodlarsam @Producesek açıklamam ne içermeli?
Uriel

JSON spesifikasyonuna göre uygulama / json, içine ne koyduğunuza bakılmaksızın. ( İetf.org/rfc/rfc4627.txt?number=4627 ) Ayı akılda base64 kodlanmış dosya hala JSON etiketlerinin içinde olmalıdır
LarsK

3
İkili verileri base64'te kodlamanın ve ardından JSON'da paketlemenin hiçbir faydası yoktur. Sadece gereksiz yere tepkinin boyutunu artıracak ve işleri yavaşlatacaktır.
Philipp Reichart
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.