2

I have a web service capable of returning PDF files in two ways:

RAW: The file is simply included in the response body. For example:

HTTP/1.1 200 OK
Content-Type: application/pdf

<file_contents>

JSON: The file is encoded (Base 64) and served as a JSON with the following structure:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "base64": <file_contents_base64>
}

I want to be able to consume both services on Android / Java by using the following architecture:

// Get response body input stream (OUT OF THE SCOPE OF THIS QUESTION)
InputStream bodyStream = getResponseBodyInputStream();

// Get PDF file contents input stream from body stream
InputStream fileStream = getPDFFileContentsInputStream(bodyStream);

// Write stream to a local file (OUT OF THE SCOPE OF THIS QUESTION)
saveToFile(fileStream);

For the first case (RAW response), the response body will the file itself. This means that the getPDFFileContentsInputStream(InputStream) method implementation is trivial:

@NonNull InputStream getPDFFileContentsInputStream(@NonNull InputStream bodyStream) {
    // Return the input
    return bodyStream;
}

The question is: how to implement the getPDFFileContentsInputStream(InputStream) method for the second case (JSON response)?

Gabriel Huff
  • 743
  • 1
  • 6
  • 18

1 Answers1

1

You can use any json parser (like Jackson or Gson), and then use Base64InputStream from apache-commons codec.

EDIT: You can obtain an input stream from string using ByteArrayInputStream, i.e.

InputStream stream = new ByteArrayInputStream(exampleString.getBytes(StandardCharsets.UTF_8));

as stated here.

EDIT 2: This will cause 2 pass over the data, and if the file is big, you might have memory problems. To solve it, you can use Jackson and parse the content yourself like this example instead of obtaining the whole object through reflection. you can wrap original input stream in another one, say ExtractingInputStream, and this will skip the data in the underlying input stream until the encoded part. Then you can wrap this ExtractingInputStream instance in a Base64InputStream. A simple algorithm to skip unnecessary parts would be like this: In the constructor of ExtractingInputStream, skip until you have read three quotation marks. In read method, return what underlying stream returns except return -1 if the underlying stream returns quotation mark, which corresponds to the end of base 64 encoded data.

Community
  • 1
  • 1
ram
  • 1,099
  • 1
  • 7
  • 22
  • Cool! But how can I get an `InputStream` that emits the value of the "base64" key from the JSON so I can wrap it in a `Base64InputStream`? – Gabriel Huff Feb 20 '17 at 19:45
  • Thanks for editing the answer. The thing is: I don't wan't to parse the JSON stream by getting the *base64* value as an object (String). The PDF files can be big (+10MB without base64 encoding), which can potentially consume more memory than the devices can offer. From what I know, neither `JsonReader` (Gson) or `JsonParser` (Jackson) have a method to get a value as an `InputStream` instead of a `String` – Gabriel Huff Feb 20 '17 at 21:14
  • Did you check the example in edit 2? `JsonParser` class of `Jackson` has a method called `getInputSource`, which is a "Method that can be used to get access to object that is used to access input being parsed; this is usually either InputStream or Reader, depending on what parser was constructed with", according to their [doc](http://fasterxml.github.io/jackson-core/javadoc/2.1.0/com/fasterxml/jackson/core/JsonParser.html). If you still have further help, I may need to write the method completely. Try yourself, if you cannot, I will help. – ram Feb 20 '17 at 21:22
  • I would be glad if you could write the method implementation so I can accept your answer – Gabriel Huff Feb 20 '17 at 21:40
  • @GabrielHuff is your structure static? – ram Feb 20 '17 at 22:56
  • It can be. I guess this is not relevant in this case. – Gabriel Huff Feb 20 '17 at 23:04
  • @GabrielHuff You can wrap original input stream in another one, say `ExtractingInputStream`, and this will skip the data in underlying input stream until the encoded part. Then you can wrap this `ExtractingInputStream` instance in a `Base64InputStream`. – ram Feb 20 '17 at 23:07
  • Yeah, I got the part about the `Base64InputStream`, it should definitely work. The challenge here is how to get the `ExtractingInputStream`, i.e. how to 'isolate' the file base 64 string from the JSON. Feel free to post a code snippet if you have any idea. – Gabriel Huff Feb 21 '17 at 00:14
  • Writing from mobile, sorry... In the constructor of extracting input stream, skip until you have read three quotation marks. In read method, return what underlying stream returns except return -1 if the underlying stream returns quotation mark. That's all. – ram Feb 21 '17 at 00:19
  • Alright, this should work. I'll accept your answer. I would appreciate if you update your answer whenever you have time so that people can quickly see your solution. Cheers! – Gabriel Huff Feb 21 '17 at 01:28