Java Jersey webservice下载文件并回复客户端的可扩展方法

Java Jersey webservice下载文件并回复客户端的可扩展方法,java,web-services,jersey,scalability,nonblocking,Java,Web Services,Jersey,Scalability,Nonblocking,我需要用Jersey构建一个Web服务,从另一个服务下载一个大文件并返回到客户端。 我想将一些字节读入缓冲区,然后将这些字节写入客户端套接字 我希望它使用非阻塞I/O,这样我就不会让线程忙了。(这是无法实现的) @GET @路径(“我的路径”) public void getFile(final@Suspended async response){ Client Client=ClientBuilder.newClient(); WebTarget t=client.target(“http:/

我需要用Jersey构建一个Web服务,从另一个服务下载一个大文件并返回到客户端。 我想将一些字节读入缓冲区,然后将这些字节写入客户端套接字

我希望它使用非阻塞I/O,这样我就不会让线程忙了。(这是无法实现的)

@GET
@路径(“我的路径”)
public void getFile(final@Suspended async response){
Client Client=ClientBuilder.newClient();
WebTarget t=client.target(“http://webserviceURL");
t、 请求()
.header(“某些头”,“头的值”)
.async().get(新调用回调()){
公共无效已完成(字节[]响应){
回复;
}
公共作废失败(可丢弃可丢弃){
res.resume(throwable.getMessage());
printStackTrace();
//答错
}
});
}
到目前为止,我有这段代码,我相信Jersey会下载完整的文件,然后将其写入客户端,这不是我想要做的。
有什么想法吗?

客户端异步请求对您的用例没有多大帮助。它对于“fire-and-forget”用例更为重要。不过,您可以做的只是从客户端
响应
获取
InputStream
,并与服务器端
StreamingResource
混合以流式传输结果。当数据从其他远程资源传入时,服务器将开始发送数据

下面是一个例子。
“/file”
端点是提供文件的虚拟远程资源。
“/client”
端点使用它

@Path("stream")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
public class ClientStreamingResource {

    private static final String INFILE = "Some File";

    @GET
    @Path("file")
    public Response fileEndpoint() {
        final File file = new File(INFILE);
        final StreamingOutput output = new StreamingOutput() {
            @Override
            public void write(OutputStream out) {

                try (FileInputStream in = new FileInputStream(file)) {
                    byte[] buf = new byte[512];
                    int len;
                    while ((len = in.read(buf)) != -1) {
                        out.write(buf, 0, len);
                        out.flush();
                        System.out.println("---- wrote 512 bytes file ----");
                    }
                } catch (IOException ex) {
                    throw new InternalServerErrorException(ex);
                }
            }
        };
        return Response.ok(output)
                .header(HttpHeaders.CONTENT_LENGTH, file.length())
                .build();
    }
    
    @GET
    @Path("client")
    public void clientEndpoint(@Suspended final AsyncResponse asyncResponse) {
        final Client client = ClientBuilder.newClient();
        final WebTarget target = client.target("http://localhost:8080/stream/file");
        final Response clientResponse = target.request().get();

        final StreamingOutput output = new StreamingOutput() {
            @Override
            public void write(OutputStream out) {
                try (final InputStream entityStream = clientResponse.readEntity(InputStream.class)) {
                    byte[] buf = new byte[512];
                    int len;
                    while ((len = entityStream.read(buf)) != -1) {
                        out.write(buf, 0, len);
                        out.flush();
                        System.out.println("---- wrote 512 bytes client ----");
                    }
                } catch (IOException ex) {
                    throw new InternalServerErrorException(ex);
                }
            }
        };
        ResponseBuilder responseBuilder = Response.ok(output);
        if (clientResponse.getHeaderString("Content-Length") != null) {
            responseBuilder.header("Content-Length", clientResponse.getHeaderString("Content-Length"));
        }
        new Thread(() -> {
            asyncResponse.resume(responseBuilder.build());
        }).start();
    }
}
我使用
cURL
发出请求,并使用
jetty-maven-plugin
从命令行运行示例。当您运行它并发出请求时,您应该看到服务器日志

---- wrote 512 bytes file ----
---- wrote 512 bytes file ----
---- wrote 512 bytes client ----
---- wrote 512 bytes file ----
---- wrote 512 bytes client ----
---- wrote 512 bytes file ----
---- wrote 512 bytes client ----
---- wrote 512 bytes file ----
---- wrote 512 bytes client ----
...
cURL
客户端正在跟踪结果

需要注意的是,“远程服务器”日志记录与客户端资源日志记录同时发生。这表明客户端并不等待接收整个文件。一旦开始接收字节,它就开始发送字节

关于该示例,需要注意以下几点:
  • 我使用了非常小的缓冲区大小(512),因为我是用一个小(1Mb)文件进行测试的。我真的不想等待一个大文件进行测试。但我想大文件也应该是一样的。当然,您需要将缓冲区大小增加到更大的值

  • 为了使用较小的缓冲区大小,需要将Jersey属性设置为0。原因是Jersey保留在大小为8192的内部缓冲区中,这将导致我的512字节数据块不会刷新,直到8192字节被缓冲。所以我把它禁用了

  • 当使用
    AsyncResponse
    时,您应该像我一样使用另一个线程。您可能希望使用执行器,而不是显式地创建线程。如果不使用另一个线程,则仍然保留容器线程池中的线程


更新 您可以使用
@ManagedAsync
注释客户机资源,让Jersey管理线程,而不是管理自己的线程/执行器

@ManagedAsync
@GET
@Path("client")
public void clientEndpoint(@Suspended final AsyncResponse asyncResponse) {
    ...
    asyncResponse.resume(responseBuilder.build());
}
    

我不明白你的回答很好,但不能解决问题。如果我有500个请求,它将使用500个线程,那么您仍然在使用阻塞IO。不是吗?下载文件确实有效,但当你要发送文件时,它会阻止。因此,当您上载文件时,一个速度较慢的客户端会长时间(out.write(buf,0,len))保留线程。Jersey的异步API的要点不是传统意义上的非阻塞IO。它的目的不是阻止主请求线程,而是允许您在单独的线程中处理请求。即使您尝试使用客户端异步,这也不是非阻塞IO。您似乎建议使用Java的NIO。这是我知道如何实现真正的非阻塞IO的唯一方法。我不是NIO专家,所以我甚至不想尝试实现它。我甚至不知道与使用线程相比,您会从中获得多少性能提升。我将继续寻找是否有办法使用非阻塞IO。不过还是谢谢你的回答是helpful@fredcrs你可能想退房。我正在浏览文档,如果你看一下,它会告诉你
StreamingOutput
ChunkedOutput
之间的区别。我想你可能会得到你想要的非阻塞行为。虽然我还没有用这个做过任何测试
@ManagedAsync
@GET
@Path("client")
public void clientEndpoint(@Suspended final AsyncResponse asyncResponse) {
    ...
    asyncResponse.resume(responseBuilder.build());
}