java - servicios - El enfoque escalable del servicio web Jersey permite descargar archivos y responder al cliente
web service rest java eclipse (1)
Necesito crear un servicio web con Jersey que descargue un archivo grande de otro servicio y lo devuelva al cliente. Me gustaría que Jersey lea algunos bytes en un buffer y escriba esos bytes en el socket del cliente.
Me gustaría usar E / S no bloqueante, así que no mantendré un hilo ocupado. (Esto no se pudo lograr)
@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
}
});
}
Hasta ahora tengo este código y creo que Jersey descargará el archivo completo y luego lo escribirá al cliente, que no es lo que quiero hacer. ¿¿Alguna idea??
La solicitud asincrónica del lado del cliente no va a hacer mucho para su caso de uso. Es más malo para los casos de uso de "disparar y olvidar". Sin embargo, lo que puedes hacer es obtener el InputStream
de la Response
del cliente y mezclarlo con un StreamingResource
lado del servidor para transmitir los resultados. El servidor comenzará a enviar los datos cuando vengan del otro recurso remoto.
A continuación hay un ejemplo. El punto final "/file"
es el recurso remoto ficticio que sirve el archivo. El punto final "/client"
consume.
@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
para hacer la solicitud, y jetty-maven-plugin
para poder ejecutar el ejemplo desde la línea de comando. Cuando lo ejecute y realice la solicitud, debería ver el registro del servidor
---- 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 ----
...
mientras que el cliente cURL
realiza un seguimiento de los resultados
El punto que debe alejarse de esto es que el registro del "servidor remoto" está sucediendo al mismo tiempo que el recurso del cliente está iniciando sesión. Esto muestra que el cliente no espera para recibir el archivo completo. Comienza a enviar bytes tan pronto como comienza a recibirlos.
Algunas cosas para tener en cuenta sobre el ejemplo:
Usé un tamaño de búfer muy pequeño (512) porque estaba probando con un archivo pequeño (1Mb). Realmente no quería esperar un archivo grande para probar. Pero me imagino que los archivos grandes deberían funcionar igual. Por supuesto, querrás aumentar el tamaño del búfer a algo más grande.
Para usar el tamaño de búfer más pequeño, debe establecer la propiedad Jersey
ServerProperties.OUTBOUND_CONTENT_LENGTH_BUFFER
en 0. La razón es que Jersey conserva el búfer interno del tamaño 8192, lo que hará que mis fragmentos de datos de 512 bytes no se vacíen, hasta que 8192 los bytes fueron almacenados Entonces lo deshabilité.Cuando use
AsyncResponse
, debería usar otro hilo, como lo hice. Es posible que desee utilizar ejecutores en lugar de crear hilos explícitamente. Si no usa otro subproceso, aún está reteniendo el subproceso del grupo de subprocesos del contenedor.
ACTUALIZAR
En lugar de administrar sus propios hilos / ejecutor, puede anotar el recurso del cliente con @ManagedAsync
, y dejar que Jersey administre los hilos
@ManagedAsync
@GET
@Path("client")
public void clientEndpoint(@Suspended final AsyncResponse asyncResponse) {
...
asyncResponse.resume(responseBuilder.build());
}