servicio invocar example desde consumir consume java rest download jersey bytestream

invocar - java consume rest service json



Cómo descargar un archivo utilizando un servicio REST de Java y un flujo de datos (3)

"¿Cómo puedo directamente (sin guardar el archivo en el segundo servidor) descargar el archivo del primer servidor a la máquina del cliente?"

Simplemente use la API de Client y obtenga InputStream de la respuesta

Client client = ClientBuilder.newClient(); String url = "..."; final InputStream responseStream = client.target(url).request().get(InputStream.class);

Hay dos sabores para obtener el InputStream . También puedes usar

Response response = client.target(url).request().get(); InputStream is = (InputStream)response.getEntity();

No estoy seguro de cuál es más eficiente, pero los InputStream devueltos son clases diferentes, por lo que es posible que desee examinar eso si lo desea.

Desde el segundo servidor, puedo obtener un ByteArrayOutputStream para obtener el archivo del primer servidor. ¿Puedo pasar esta secuencia más al cliente usando el servicio REST?

Así que 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 una secuencia a otra. Así que con nuestra implementación anterior, tenemos que escribir dos veces.

¿Cómo podemos conseguir solo una escritura? Simplemente devuelve el InputStream como la Response

final InputStream responseStream = client.target(url).request().get(InputStream.class); return Response.ok(responseStream).header( "Content-Disposition", "attachment, filename=/".../"").build();

Si observamos el código fuente de InputStreamProvider , simplemente lo delega a ReadWriter.writeTo(in, out) , que simplemente hace lo que hicimos anteriormente en la implementación de StreamingOutput

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 objetos del Client son recursos caros. Es posible que desee reutilizar el mismo Client para su solicitud. Puede extraer un WebTarget del cliente para cada solicitud.

    WebTarget target = client.target(url); InputStream is = target.request().get(InputStream.class);

    Creo que el WebTarget puede incluso ser compartido. No puedo encontrar nada en la documentación de Jersey 2.x (solo porque es un documento más grande, y soy demasiado perezoso para escanearlo en este momento :-), pero en la documentación de Jersey 1.x , dice que Client y WebResource (que es equivalente a WebTarget en 2.x) se pueden compartir entre subprocesos. Así que supongo que Jersey 2.x sería lo mismo. pero es posible que desee confirmar por sí mismo.

  • No tienes que hacer uso de la API del Client . Una descarga se puede lograr fácilmente con las APIs del paquete java.net . Pero como ya estás usando Jersey, no está mal usar sus API

  • Lo anterior está asumiendo Jersey 2.x. Para Jersey 1.x, una simple búsqueda en Google debería darle un montón de resultados para trabajar con la API (o la documentación a la que he vinculado anteriormente)

ACTUALIZAR

Soy un dufus Mientras el OP y yo contemplamos formas de convertir un ByteArrayOutputStream en un 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 recursos

@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

Así que la solución final para este caso de uso en particular fue que el OP simplemente pasara el OutputStream del método de write StreamingOutput . 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 es seguro, pero parece que la lectura / escritura dentro del método de recursos, utilizando ByteArrayOutputStream`, se dio cuenta de algo en la memoria.

El punto del método downloadFile que acepta un OutputStream es para que pueda escribir el resultado directamente en el OutputStream proporcionado. Por ejemplo, un FileOutputStream , si lo escribiste en un archivo, mientras se realiza la descarga, se enviará directamente al archivo.

No estamos destinados a que mantengamos una referencia a OutputStream , como intentabas hacer con los baos, que intentabas hacer, que es donde entra en juego la realización de la memoria.

Así que con la forma en que funciona, estamos escribiendo directamente en el flujo de respuesta que se nos proporciona. El método de write no se llama realmente hasta el método writeTo (en el MessageBodyWriter ), donde se le pasa el OutputStream .

Puedes obtener una imagen mejor viendo el MessageBodyWriter que escribí. Básicamente, en el método writeTo , reemplace ByteArrayOutputStream con StreamingOutput , luego, dentro del método, llame a streamingOutput.write(entityStream) . Puede ver el enlace que proporcioné en la parte anterior de la respuesta, donde me vinculo al StreamingOutputProvider . Esto es exactamente lo que sucede

Tengo 3 máquinas:

  1. servidor donde se encuentra el archivo
  2. servidor donde se ejecuta el servicio REST (Jersey)
  3. cliente (navegador) con acceso al segundo servidor pero sin acceso al primer servidor

¿Cómo puedo descargar directamente (sin guardar el archivo en el segundo servidor) el archivo del primer servidor a la máquina del cliente?
Desde el segundo servidor, puedo obtener un ByteArrayOutputStream para obtener el archivo del primer servidor. ¿Puedo pasar esta secuencia más al cliente usando el servicio REST?

¿Funcionará de esta manera?

Básicamente, lo que quiero lograr es permitir que el cliente descargue un archivo del primer servidor utilizando el servicio REST en el segundo servidor (ya que no hay acceso directo desde el cliente al primer servidor) utilizando solo flujos de datos (por lo tanto, no hay datos que toquen el archivo). sistema del 2do servidor).

Lo que intento ahora con la biblioteca EasyStream:

final FTDClient client = FTDClient.getInstance(); try { final InputStreamFromOutputStream<String> isOs = new InputStreamFromOutputStream<String>() { @Override public String produce(final OutputStream dataSink) throws Exception { return client.downloadFile2(location, Integer.valueOf(spaceId), URLDecoder.decode(filePath, "UTF-8"), dataSink); } }; try { String fileName = filePath.substring(filePath.lastIndexOf("/") + 1); StreamingOutput output = new StreamingOutput() { @Override public void write(OutputStream outputStream) throws IOException, WebApplicationException { int length; byte[] buffer = new byte[1024]; while ((length = isOs.read(buffer)) != -1){ outputStream.write(buffer, 0, length); } outputStream.flush(); } }; return Response.ok(output, MediaType.APPLICATION_OCTET_STREAM) .header("Content-Disposition", "attachment; filename=/"" + fileName + "/"" ) .build();

Actualización2

Así que mi código ahora con el MessageBodyWriter personalizado parece simple:

ByteArrayOutputStream baos = new ByteArrayOutputStream (2048); client.downloadFile (ubicación, spaceId, filePath, baos); devuelve Response.ok (baos) .build ();

Pero me sale el mismo error de pila cuando intento con archivos grandes.

ACTUALIZACIÓN3 ¡ Finalmente logró hacerlo funcionar! StreamingOutput hizo el truco.

Gracias @peeskillet! Muchas gracias !


Consulte esto:

@RequestMapping(value="download", method=RequestMethod.GET) public void getDownload(HttpServletResponse response) { // Get your file stream from wherever. InputStream myStream = someClass.returnFile(); // Set the content type and attachment header. response.addHeader("Content-disposition", "attachment;filename=myfilename.txt"); response.setContentType("txt/plain"); // Copy the stream to the response''s output stream. IOUtils.copy(myStream, response.getOutputStream()); response.flushBuffer(); }

Detalles en: https://twilblog.github.io/java/spring/rest/file/stream/2015/08/14/return-a-file-stream-from-spring-rest.html


Vea el ejemplo aquí: ¿ Entradas binarias de entrada y salida usando JERSEY?

El pseudo código sería algo como esto (hay algunas otras opciones similares en la publicación mencionada anteriormente):

@Path("file/") @GET @Produces({"application/pdf"}) public StreamingOutput getFileContent() throws Exception { public void write(OutputStream output) throws IOException, WebApplicationException { try { // // 1. Get Stream to file from first server // while(<read stream from first server>) { output.write(<bytes read from first server>) } } catch (Exception e) { throw new WebApplicationException(e); } finally { // close input stream } } }