1

I have to rectifiy an application that basically serves content by using two REST endpoints. Both endpoints transfer files to the browser, one endpoint does so by providing the file in binary format, the other sends the file base64 encoded. The application had a file handle leak in the endpoint that serves binary data. The reason for this was that a InputStream was used that could never be closed because I couldn't do so before handing it over Spring and Spring didn't close it.

The code for function 1 now looks like this and works fine in the sense of it doesn't create a leak and it streams content of even huge files without consuming much memory:

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);
}

The problematic function I want to improve looks like this:

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));
}

It has the big disadvantage that it copies the complete file to memory before converting it to base64. I would prefer an approach where the content gets streamed and base64 encoded during streaming.

3
+50

The good news is that for just about any problem like this, there is most likely a library that handles it. Based on the answer to a similar question you might want to look at the Base64InputStream from the Apache Commons Codec project.

That combined with using the InputStreamResource will make your code that returns the base 64 response look very similar to the binary response, making it really easy to follow.

The new code would look something like this:

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);
}

This is going to be the most straightforward way of doing this.

In Java 8, the Base64 has Base64.wrap() to handle conversions to and from a stream, but the direction of conversion is opposite of the streams you need to use. In other words, Base64.wrap(outputStream) converts to base 64 and Base64.wrap(inputStream) converts from base 64. That means the Apache Commons Codec project is going to be your best bet.

0

If you are working with data that is larger than your JVM heap size then you have to divide it to smaller chunks before you can do anything with it.

It's not very clear from your question what is your "content" object type but I assume it's some kind of InputStream because you are calling close() on this object.

If you are working with InputStream then you can encode your data using following:

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));
        }
    }
}

I used code from: https://stackoverflow.com/questions/39082816/java-buffered-base64-encoder-for-streams

  • 2
    This site is about conceptual questions and answers are expected to explain things. Throwing code dumps instead of explanation is like copying code from IDE to whiteboard: it may look familiar and even sometimes be understandable, but it feels weird... just weird. Whiteboard doesn't have compiler – gnat Sep 28 '18 at 21:25

protected by gnat Sep 28 '18 at 21:24

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.