Streaming del contenuto dei file in modo efficiente senza rischiare perdite di risorse

0

Devo rettificare un'applicazione che fondamentalmente serve contenuti utilizzando due endpoint REST. Entrambi gli endpoint trasferiscono i file al browser, un endpoint lo fa fornendo il file in formato binario, l'altro invia il file base64 codificato. L'applicazione aveva una perdita di handle di file nell'endpoint che serve dati binari. Il motivo è che è stato utilizzato un InputStream che non potrebbe mai essere chiuso perché non potevo farlo prima di consegnarlo a Spring e Spring non l'ha chiuso.

Il codice per la funzione 1 ora assomiglia a questo e funziona bene nel senso che non crea una perdita e trasmette il contenuto di file anche enormi senza consumare molta memoria:

public ResponseEntity<?> getContentAsBinary(@RequestParam(value = "id", defaultValue = "") id ) {
  /// stuff not relevant here
  FileSystemResource fsr = new FileSystemResource(fileName);
  return ResponseEntity.status(HttpServletResponse.SC_OK).body(fsr);
}

La funzione problematica voglio migliorare aspetto come questo:

public ResponseEntity<byte[]> getContentAsBase64(@RequestParam(value = "id", defaultValue = "") id ) {
  /// stuff not relevant here
  byte[] bytes = IOUtils.toByteArray(content);

  try {
     content.close();
  } catch (Exception e) {
    log.warn("Error closing inputstream of id {}", id);
  }

  return ResponseEntity.status(HttpServletResponse.SC_OK).body(Base64.encodeBase64(bytes));
}

Ha il grande svantaggio di copiare il file completo in memoria prima di convertirlo in base64. Preferirei un approccio in cui il contenuto viene trasmesso in streaming e codificato Base64 durante lo streaming.

    
posta Marged 22.09.2018 - 08:46
fonte

2 risposte

3

La buona notizia è che per quasi tutti i problemi come questo, molto probabilmente c'è una libreria che la gestisce. In base alla risposta a una domanda simile potresti voler esaminare Base64InputStream dal progetto Apache Commons Codec .

Questo, combinato con l'utilizzo di InputStreamResource , renderà il tuo codice che restituisce la risposta di base 64 molto simile alla risposta binaria, rendendolo davvero facile da seguire.

Il nuovo codice sarà simile a questo:

public ResponseEntity<?> getContentAsBase64(
                              @RequestParam(value = "id", defaultValue = "") id ) {
    /// stuff not relevant here
    InputStreamResponse isr = new InputStreamResponse(new Base64InputStream(content));
    return ResponseEntity.status(HttpServletResponse.SC_OK).body(isr);
}

Questo sarà il modo più semplice per farlo.

In Java 8, Base64 ha Base64.wrap() per gestire le conversioni da e verso uno stream, ma la direzione della conversione è opposta agli stream che è necessario utilizzare. In altre parole, Base64.wrap(outputStream) converte in base 64 e Base64.wrap(inputStream) converte da base 64. Ciò significa che il progetto Apache Commons Codec sarà la soluzione migliore.

    
risposta data 29.09.2018 - 16:11
fonte
0

Se lavori con dati di dimensioni maggiori della dimensione dell'heap JVM, devi dividerlo in blocchi più piccoli prima di poterne fare qualcosa.

Non è molto chiaro dalla tua domanda qual è il tuo tipo di oggetto "contenuto", ma presumo che sia una specie di InputStream perché stai chiamando close () su questo oggetto.

Se stai lavorando con InputStream, puoi codificare i tuoi dati usando:

private static final int BUFFER_SIZE = 3 * 1024;

@RequestMapping("/stream")
public void stream(HttpServletResponse response) throws IOException {
    java.util.Base64.Encoder encoder = java.util.Base64.getEncoder();

    try (InputStream in = new FileInputStream(new File(pathToBigFile))) {
        byte[] bytes = new byte[BUFFER_SIZE];

        int len = 0;

        while ((len = in.read(bytes)) == BUFFER_SIZE) {
            response.getWriter().write(encoder.encodeToString(bytes));
        }

        // do not run into error when we have 0 bytes
        if (len > 0) {
            bytes = Arrays.copyOf(bytes, len);
            response.getWriter().write(encoder.encodeToString(bytes));
        }
    }
}

Ho usato il codice da: link

    
risposta data 28.09.2018 - 18:31
fonte

Leggi altre domande sui tag