Saltar al contenido

Cómo descargar un archivo usando un servicio REST de Java y un flujo de datos

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í InputStreamLos 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 mismo Client a petición. Puedes extraer un WebTarget 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 el Client y WebResource (que es equivalente a WebTarget 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 el java.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

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *