当前位置: 首页 > 面试题库 >

如何使用Java REST服务和数据流下载文件

乐欣可
2023-03-14
问题内容

我有3台机器:

  1. 文件所在的服务器

  2. 运行REST服务的服务器(泽西岛)

  3. 可以访问第二台服务器但不能访问第一台服务器的客户端(浏览器)

我如何直接(不将文件保存在第二台服务器上)将文件从第一台服务器下载到客户端计算机?
从第二台服务器可以获取 ByteArrayOutputStream 来从第一台服务器获取文件,我可以使用REST服务将此流进一步传递给客户端吗?

这样行吗?

因此,基本上我想实现的是允许客户端使用第二服务器上的REST服务从第一服务器下载文件(因为没有从客户端到第一服务器的直接访问),而仅使用数据流(因此没有数据接触文件)第二服务器系统)。

我现在尝试使用EasyStream库进行以下操作:

       final FTDClient client = FTDClient.getInstance();

        try {
            final InputStreamFromOutputStream<String> isOs = new InputStreamFromOutputStream<String>() {
                @Override
                public String produce(final OutputStream dataSink) throws Exception {
                    return client.downloadFile2(location, Integer.valueOf(spaceId), URLDecoder.decode(filePath, "UTF-8"), dataSink);
                }
            };
            try {
                String fileName = filePath.substring(filePath.lastIndexOf("/") + 1);

                StreamingOutput output = new StreamingOutput() {
                    @Override
                    public void write(OutputStream outputStream) throws IOException, WebApplicationException {
                        int length;
                        byte[] buffer = new byte[1024];
                        while ((length = isOs.read(buffer)) != -1){
                            outputStream.write(buffer, 0, length);
                        }
                        outputStream.flush();

                    }
                };
                return Response.ok(output, MediaType.APPLICATION_OCTET_STREAM)
                        .header("Content-Disposition", "attachment; filename=\"" + fileName + "\"" )
                        .build();

更新2

因此,现在带有自定义MessageBodyWriter的代码看起来很简单:

ByteArrayOutputStream baos = new ByteArrayOutputStream(2048) ;
client.downloadFile(location, spaceId, filePath, baos);
return Response.ok(baos).build();

但是当尝试使用大文件时,我得到了相同的堆错误。

UPDATE3 终于设法使其正常工作!StreamingOutput达到了目的。

谢谢@peeskillet!非常感谢 !


问题答案:

“如何直接(不将文件保存在第二台服务器上)将文件从第一台服务器下载到客户端计算机?”

只需使用ClientAPI并InputStream从响应中获取

Client client = ClientBuilder.newClient();
String url = "...";
final InputStream responseStream = client.target(url).request().get(InputStream.class);

有两种口味可以得到InputStream。您也可以使用

Response response = client.target(url).request().get();
InputStream is = (InputStream)response.getEntity();

哪一个效率更高?我不确定,但是返回的InputStreams是不同的类,因此如果您愿意,可以考虑一下。

从第二台服务器可以获取ByteArrayOutputStream来从第一台服务器获取文件,我可以使用REST服务将此流进一步传递给客户端吗?

因此,您在@GradyGCooper提供的链接中看到的大多数答案似乎都倾向于使用StreamingOutput。一个示例实现可能类似于

final InputStream responseStream = client.target(url).request().get(InputStream.class);
System.out.println(responseStream.getClass());
StreamingOutput output = new StreamingOutput() {
    @Override
    public void write(OutputStream out) throws IOException, WebApplicationException {  
        int length;
        byte[] buffer = new byte[1024];
        while((length = responseStream.read(buffer)) != -1) {
            out.write(buffer, 0, length);
        }
        out.flush();
        responseStream.close();
    }   
};
return Response.ok(output).header(
        "Content-Disposition", "attachment, filename=\"...\"").build();

但是,如果我们查看StreamingOutputProvider的源代码,您将在中看到writeTo,它只是将数据从一个流写入另一个流。因此,在上面的实现中,我们必须写两次。

我们怎样才能只写一个?简单返回InputStreamResponse

final InputStream responseStream = client.target(url).request().get(InputStream.class);
return Response.ok(responseStream).header(
        "Content-Disposition", "attachment, filename=\"...\"").build();

如果我们看一下InputStreamProvider的源代码,它只是委托给ReadWriter.writeTo(in, out),它只是执行了我们在StreamingOutput实现中所做的工作

 public static void writeTo(InputStream in, OutputStream out) throws IOException {
    int read;
    final byte[] data = new byte[BUFFER_SIZE];
    while ((read = in.read(data)) != -1) {
        out.write(data, 0, read);
    }
}

助手:

  • Client对象是昂贵的资源。您可能要重复使用相同Client的请求。您可以WebTarget为每个请求从客户端提取一个。
    WebTarget target = client.target(url);
    

    InputStream is = target.request().get(InputStream.class);

我认为WebTarget甚至可以共享。我在Jersey
2.x文档中
找不到任何内容(仅因为它是一个较大的文档,而且我现在懒得扫描它:-),但是在Jersey
1.x文档中却说:Client并且WebResourceWebTarget在2.x中等效)可以在线程之间共享。所以我猜Jersey
2.x会是一样的。但您可能需要自己确认。

  • 您不必使用ClientAPI。使用java.net包API 可以轻松实现下载。但是,由于您已经在使用Jersey,因此使用其API不会造成任何伤害

  • 以上是假设Jersey2.x。对于Jersey 1.x,一个简单的Google搜索应会为您提供使用API​​(或我上面链接到的文档)的大量帮助。

我真是个骗子。虽然OP和我正在考虑如何把一个ByteArrayOutputStream一个InputStream的,我错过了简单的解决方案,这是简单地写MessageBodyWriterByteArrayOutputStream

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;

@Provider
public class OutputStreamWriter implements MessageBodyWriter<ByteArrayOutputStream> {

    @Override
    public boolean isWriteable(Class<?> type, Type genericType,
            Annotation[] annotations, MediaType mediaType) {
        return ByteArrayOutputStream.class == type;
    }

    @Override
    public long getSize(ByteArrayOutputStream t, Class<?> type, Type genericType,
            Annotation[] annotations, MediaType mediaType) {
        return -1;
    }

    @Override
    public void writeTo(ByteArrayOutputStream t, Class<?> type, Type genericType,
            Annotation[] annotations, MediaType mediaType,
            MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
            throws IOException, WebApplicationException {
        t.writeTo(entityStream);
    }
}

然后我们可以简单地ByteArrayOutputStream在响应中返回

return Response.ok(baos).build();

D’OH!

更新2

这是我使用的测试(

资源类别

@Path("test")
public class TestResource {

    final String path = "some_150_mb_file";

    @GET
    @Produces(MediaType.APPLICATION_OCTET_STREAM)
    public Response doTest() throws Exception {
        InputStream is = new FileInputStream(path);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int len;
        byte[] buffer = new byte[4096];
        while ((len = is.read(buffer, 0, buffer.length)) != -1) {
            baos.write(buffer, 0, len);
        }
        System.out.println("Server size: " + baos.size());
        return Response.ok(baos).build();
    }
}

客户测试

public class Main {
    public static void main(String[] args) throws Exception {
        Client client = ClientBuilder.newClient();
        String url = "http://localhost:8080/api/test";
        Response response = client.target(url).request().get();
        String location = "some_location";
        FileOutputStream out = new FileOutputStream(location);
        InputStream is = (InputStream)response.getEntity();
        int len = 0;
        byte[] buffer = new byte[4096];
        while((len = is.read(buffer)) != -1) {
            out.write(buffer, 0, len);
        }
        out.flush();
        out.close();
        is.close();
    }
}

更新3

因此,对于这个特定的用例的最终解决方案是为OP简单地通过OutputStream从所述StreamingOutputwrite方法。似乎是第三方API,需要以aOutputStream作为参数。

StreamingOutput output = new StreamingOutput() {
    @Override
    public void write(OutputStream out) {
        thirdPartyApi.downloadFile(.., .., .., out);
    }
}
return Response.ok(output).build();

不太确定,但是似乎使用ByteArrayOutputStream`在资源方法中进行读/写操作已将某些内容存储到内存中。

downloadFile方法的接受点OutputStream是,因此它可以将结果直接写入提供的结果OutputStream。例如FileOutputStream,如果您将a写入文件中,而下载即将到来,它将直接流式传输到文件中。

这并不意味着我们要保留对的引用OutputStream,因为您正尝试使用baos,这是实现内存的地方。

因此,通过有效的方法,我们将直接写入为我们提供的响应流。该方法write实际上并没有被调用,直到writeTo方法(在MessageBodyWriter),其中OutputStream传递给它。

MessageBodyWriter我写的书,你会得到更好的印象。基本上是在writeTo方法中,替换ByteArrayOutputStream使用StreamingOutput,则内部的方法,呼叫streamingOutput.write(entityStream)。您可以在答案的前面部分中看到我提供的链接,在该链接中,我指向StreamingOutputProvider。这正是发生的情况



 类似资料:
  • 问题内容: 我需要将文件从服务器下载到桌面。(UBUNTU 10.04)我没有Web访问服务器,只是ssh。 如果有帮助,我的操作系统是Mac OS X和iTerm 2作为终端。 问题答案: 在您的终端中,键入: 相应地替换用户名,主机,远程文件名和本地目录。 如果要访问EC2(或其他需要使用私钥进行身份验证的服务),请使用以下选项:

  • 我想从网站上下载一份PDF。PDF需要在代码中生成,我认为这是freemarker和像iText这样的PDF生成框架的组合。还有更好的办法吗? 然而,主要的问题是,我不知道如何允许用户通过Spring控制器下载文件?

  • 问题内容: 我有一个后端API,它在我们调用它时基本上会下载一个模板。我在html页面上提供了href,因此,每当有人单击该href时,它就会调用后端API,并且应该下载该文件。 但是该文件未下载。 我正在使用React。如果我只是从浏览器访问后端,则文件将被下载,但是如果我从react调用,则不会。 有线索吗? 反应代码: 后端API代码: 问题答案: JS中的请求与访问浏览器中的URL不同。您

  • 问题内容: 嗨,我有一堆.mp3文件,我想与NSFileManager一起使用并存储在documents文件夹中。有没有一种方法可以在线下载.mp3文件,然后将其保存到documents文件夹?这就是我正在使用的本地文件。 问题答案: 编辑/更新: Xcode 11.5•Swift 5.2 原始答案 Xcode 8.3.2•Swift 3.1

  • 问题内容: 在我的Java应用程序中,我正在使用以下方法从服务器下载文件。 但是此下载非常慢。我该如何快速? 问题答案: 从Java 7开始,您可以下载具有以下内置功能的文件: 对于早期版本,从Java 1.4到Java 6的解决方案是 此代码将URL内容传输到没有任何第三方库的文件。如果仍然很慢,那您就知道这不是附加库的问题,很可能不是Java的问题。至少您在这里没有什么可以改善的。因此,您应该