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

fre*_*crs 10 java web-services scalability jersey nonblocking

我需要使用Jersey构建一个web服务,从另一个服务下载一个大文件并返回给客户端.我希望jersey将一些字节读入缓冲区并将这些字节写入客户端套接字.

我希望它使用非阻塞I/O,所以我不要让线程忙.(这无法实现)

    @GET
    @Path("mypath")
    public void getFile(final @Suspended AsyncResponse res) {
        Client client = ClientBuilder.newClient();
        WebTarget t = client.target("http://webserviceURL");
        t.request()
            .header("some header", "value for header")
                .async().get(new InvocationCallback<byte[]>(){

            public void completed(byte[] response) {
                res.resume(response);
            }

            public void failed(Throwable throwable) {
                res.resume(throwable.getMessage());
                throwable.printStackTrace();
                //reply with error
            }

        });
    }
Run Code Online (Sandbox Code Playgroud)

到目前为止,我有这个代码,我相信泽西岛将下载完整的文件,然后将其写入客户端,这不是我想要做的.有什么想法吗??

Pau*_*tha 5

客户端异步请求不会为您的用例做太多事情。对于“即发即弃”用例来说,这更有意义。您可以做的只是InputStream从客户端获取Response并与服务器端混合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();
    }
}
Run Code Online (Sandbox Code Playgroud)

我曾经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 ----
...
Run Code Online (Sandbox Code Playgroud)

cURL客户跟踪结果时

在此处输入图片说明

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

关于示例的一些注意事项:

  • 我使用了一个非常小的缓冲区大小 (512),因为我正在使用一个小 (1Mb) 的文件进行测试。我真的不想等待一个大文件进行测试。但我想大文件应该一样工作。当然,您会希望将缓冲区大小增加到更大的大小。

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

  • 使用时AsyncResponse,您应该像我一样使用另一个线程。不过,您可能希望使用执行程序而不是显式创建线程。如果您不使用另一个线程,那么您仍然会从容器的线程池中占用线程。


更新

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

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