Jersey webservice 可扩展的下载文件和回复客户端的方法
Jersey webservice scalable approach to download file and reply to client
我需要用 Jersey 构建一个网络服务,从另一个服务下载一个大文件,然后 returns 到客户端。
我希望球衣将一些字节读入缓冲区并将这些字节写入客户端套接字。
I would like it to use non blocking I/O so I dont keep a thread busy. (This could not be achieved)
@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
}
});
}
到目前为止,我有这段代码,我相信 Jersey 会下载完整的文件,然后将其写入客户端,这不是我想要做的。
有什么想法吗??
客户端异步请求不会为您的用例做太多事情。对于“即发即忘”的用例,它更有意义。不过,您可以做的只是从客户端 Response
获取 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 属性 ServerProperties.OUTBOUND_CONTENT_LENGTH_BUFFER
设置为 0。原因是 Jersey 保留在大小为 8192 的内部缓冲区中,这将导致我的 512 字节数据块不刷新,直到缓冲了 8192 字节。所以我只是禁用它。
当使用AsyncResponse
时,你应该像我一样使用另一个线程。不过,您可能希望使用执行程序而不是显式创建线程。如果你不使用另一个线程,那么你仍然在从容器的线程池中占用线程。
更新
您可以使用 @ManagedAsync
注释客户端资源,而不是管理您自己的 threads/executor,并让 Jersey 管理线程
@ManagedAsync
@GET
@Path("client")
public void clientEndpoint(@Suspended final AsyncResponse asyncResponse) {
...
asyncResponse.resume(responseBuilder.build());
}
我需要用 Jersey 构建一个网络服务,从另一个服务下载一个大文件,然后 returns 到客户端。 我希望球衣将一些字节读入缓冲区并将这些字节写入客户端套接字。
I would like it to use non blocking I/O so I dont keep a thread busy. (This could not be achieved)
@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
}
});
}
到目前为止,我有这段代码,我相信 Jersey 会下载完整的文件,然后将其写入客户端,这不是我想要做的。 有什么想法吗??
客户端异步请求不会为您的用例做太多事情。对于“即发即忘”的用例,它更有意义。不过,您可以做的只是从客户端 Response
获取 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 属性
ServerProperties.OUTBOUND_CONTENT_LENGTH_BUFFER
设置为 0。原因是 Jersey 保留在大小为 8192 的内部缓冲区中,这将导致我的 512 字节数据块不刷新,直到缓冲了 8192 字节。所以我只是禁用它。当使用
AsyncResponse
时,你应该像我一样使用另一个线程。不过,您可能希望使用执行程序而不是显式创建线程。如果你不使用另一个线程,那么你仍然在从容器的线程池中占用线程。
更新
您可以使用 @ManagedAsync
注释客户端资源,而不是管理您自己的 threads/executor,并让 Jersey 管理线程
@ManagedAsync
@GET
@Path("client")
public void clientEndpoint(@Suspended final AsyncResponse asyncResponse) {
...
asyncResponse.resume(responseBuilder.build());
}