Solución:
“¿Cómo puedo descargar directamente (sin guardar el archivo en el segundo servidor) el archivo desde el primer servidor a la máquina del cliente?”
Solo usa el Client
API y obtenga la InputStream
de la respuesta
Client client = ClientBuilder.newClient();
String url = "...";
final InputStream responseStream = client.target(url).request().get(InputStream.class);
Hay dos sabores para conseguir el InputStream
. También puedes usar
Response response = client.target(url).request().get();
InputStream is = (InputStream)response.getEntity();
¿Cuál es el más eficiente? No estoy seguro, pero volví InputStream
Los s son clases diferentes, por lo que es posible que desee investigarlo si lo desea.
Desde el segundo servidor puedo obtener un ByteArrayOutputStream para obtener el archivo del primer servidor, ¿puedo pasar este flujo más al cliente usando el servicio REST?
Entonces, la mayoría de las respuestas que verá en el enlace proporcionado por @GradyGCooper parecen favorecer el uso de StreamingOutput
. Una implementación de ejemplo podría ser algo como
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();
Pero si miramos el código fuente de StreamingOutputProvider, verá en el writeTo
, que simplemente escribe los datos de un flujo a otro. Entonces, con nuestra implementación anterior, tenemos que escribir dos veces.
¿Cómo podemos obtener solo una escritura? Simplemente devuelva el InputStream
como el Response
final InputStream responseStream = client.target(url).request().get(InputStream.class);
return Response.ok(responseStream).header(
"Content-Disposition", "attachment, filename="..."").build();
Si miramos el código fuente de InputStreamProvider, simplemente delega a ReadWriter.writeTo(in, out)
, que simplemente hace lo que hicimos anteriormente en el StreamingOutput
implementación
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);
}
}
Aparte:
-
Client
los objetos son recursos costosos. Es posible que desee reutilizar el mismoClient
a petición. Puedes extraer unWebTarget
del cliente para cada solicitud.WebTarget target = client.target(url); InputStream is = target.request().get(InputStream.class);
creo que el
WebTarget
incluso se puede compartir. No puedo encontrar nada en la documentación de Jersey 2.x (solo porque es un documento más grande, y soy demasiado vago para escanearlo ahora :-), pero en la documentación de Jersey 1.x, dice elClient
yWebResource
(que es equivalente aWebTarget
en 2.x) se puede compartir entre subprocesos. Así que supongo que Jersey 2.x sería lo mismo. pero es posible que desee confirmarlo usted mismo. -
No tienes que hacer uso del
Client
API. Se puede realizar una descarga fácilmente con eljava.net
API de paquetes. Pero como ya está usando Jersey, no está de más usar sus API -
Lo anterior asume Jersey 2.x. Para Jersey 1.x, una simple búsqueda en Google debería proporcionarle un montón de resultados para trabajar con la API (o la documentación a la que he vinculado anteriormente)
ACTUALIZAR
Soy un tonto. Mientras el OP y yo estamos contemplando formas de convertir un ByteArrayOutputStream
a una InputStream
, Me perdí la solución más simple, que es simplemente escribir un MessageBodyWriter
Para el ByteArrayOutputStream
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);
}
}
Entonces simplemente podemos devolver el ByteArrayOutputStream
en la respuesta
return Response.ok(baos).build();
D’OH!
ACTUALIZACIÓN 2
Aquí están las pruebas que utilicé (
Clase de recurso
@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();
}
}
Prueba de cliente
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();
}
}
ACTUALIZACIÓN 3
Entonces, la solución final para este caso de uso particular fue que el OP simplemente pasara el OutputStream
desde el StreamingOutput
‘s write
método. Parece que la API de terceros requiere un OutputStream
como argumento.
StreamingOutput output = new StreamingOutput() {
@Override
public void write(OutputStream out) {
thirdPartyApi.downloadFile(.., .., .., out);
}
}
return Response.ok(output).build();
No estoy seguro, pero parece que la lectura / escritura dentro del método de recursos, usando ByteArrayOutputStream`, se dio cuenta de algo en la memoria.
El punto de la downloadFile
método aceptando un OutputStream
es para que pueda escribir el resultado directamente en el OutputStream
previsto. Por ejemplo, un FileOutputStream
, si lo escribió en un archivo, mientras se realiza la descarga, se transmitirá directamente al archivo.
No está destinado a que mantengamos una referencia a la OutputStream
, como estabas intentando hacer con el baos
, que es donde entra la realización de la memoria.
Entonces, con la forma en que funciona, estamos escribiendo directamente en el flujo de respuesta que se nos proporciona. El método write
en realidad no se llama hasta el writeTo
método (en el MessageBodyWriter
), donde el OutputStream
se le pasa.
Puede obtener una mejor imagen mirando el MessageBodyWriter
Escribí. Básicamente en el writeTo
método, reemplace el ByteArrayOutputStream
con StreamingOutput
, luego dentro del método, llame streamingOutput.write(entityStream)
. Puede ver el enlace que proporcioné en la parte anterior de la respuesta, donde me enlace al StreamingOutputProvider
. Esto es exactamente lo que sucede