Write an InputStream to an HttpServletResponse
Asked Answered
S

3

27

I have an InputStream that I want written to a HttpServletResponse. There's this approach, which takes too long due to the use of byte[]

InputStream is = getInputStream();
int contentLength = getContentLength();

byte[] data = new byte[contentLength];
is.read(data);

//response here is the HttpServletResponse object
response.setContentLength(contentLength);
response.write(data);

I was wondering what could possibly be the best way to do it, in terms of speed and efficiency.

Scaly answered 13/4, 2012 at 14:9 Comment(0)
A
54

Just write in blocks instead of copying it entirely into Java's memory first. The below basic example writes it in blocks of 10KB. This way you end up with a consistent memory usage of only 10KB instead of the complete content length. Also the enduser will start getting parts of the content much sooner.

response.setContentLength(getContentLength());
byte[] buffer = new byte[10240];

try (
    InputStream input = getInputStream();
    OutputStream output = response.getOutputStream();
) {
    for (int length = 0; (length = input.read(buffer)) > 0;) {
        output.write(buffer, 0, length);
    }
}

As creme de la creme with regard to performance, you could use NIO Channels and a directly allocated ByteBuffer. Create the following utility/helper method in some custom utility class, e.g. Utils:

public static long stream(InputStream input, OutputStream output) throws IOException {
    try (
        ReadableByteChannel inputChannel = Channels.newChannel(input);
        WritableByteChannel outputChannel = Channels.newChannel(output);
    ) {
        ByteBuffer buffer = ByteBuffer.allocateDirect(10240);
        long size = 0;

        while (inputChannel.read(buffer) != -1) {
            buffer.flip();
            size += outputChannel.write(buffer);
            buffer.clear();
        }

        return size;
    }
}

Which you then use as below:

response.setContentLength(getContentLength());
Utils.stream(getInputStream(), response.getOutputStream());
Allowance answered 13/4, 2012 at 14:21 Comment(5)
Of course many utility packages have this method already defined, so once you start using Guava... docs.guava-libraries.googlecode.com/git/javadoc/com/google/…, java.io.OutputStream)Longevity
+1 @Allowance Do we need to set its ContentType? If yes, what would it be?Await
@Roylee: yes, that's recommended. Just set the content type to the type of the content :) freeformatter.com/mime-types-list.htmlAllowance
i tried this,, why is that my stream will return an error like this: org.apache.catalina.connector.ClientAbortException: java.io.IOException: An established connection was aborted by the software in your host machine at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:356) at org.apache.catalina.connector.OutputBuffer.appendByteArray(OutputBuffer.java:778) at org.apache.catalina.connector.OutputBuffer.append(OutputBuffer.java:707) at org.apache.catalina.connector.OutputBuffer.writeBytes(OutputBuffer.java:391)Dhoti
how to getContentLenght() of input Stream?Voice
C
1
BufferedInputStream in = null;
BufferedOutputStream out = null;
OutputStream os;
os = new BufferedOutputStream(response.getOutputStream());
in = new BufferedInputStream(new FileInputStream(file));
out = new BufferedOutputStream(os);
byte[] buffer = new byte[1024 * 8];
int j = -1;
while ((j = in.read(buffer)) != -1) {
    out.write(buffer, 0, j);
}
Circumstantiate answered 13/4, 2012 at 14:18 Comment(3)
I wan to avoid the usage of byte[], if possibleScaly
@MuhammadSabry there's no way to read the InputStream w/o using byte[]Ketosis
What's wrong with byte[]. You need to store the data someware :}Gemmulation
G
0

I think that is very close to the best way, but I would suggest the following change. Use a fixed size buffer(Say 20K) and then do the read/write in a loop.

For the loop do something like

byte[] buffer=new byte[20*1024];
outputStream=response.getOutputStream();
while(true) {
  int readSize=is.read(buffer);
  if(readSize==-1)
    break;
  outputStream.write(buffer,0,readSize);
}

ps: Your program will not always work as is, because read don't always fill up the entire array you give it.

Gemmulation answered 13/4, 2012 at 14:20 Comment(2)
what exactly do you mean by read doesn't fill up the entire array you give it?Scaly
Read does not always fill up the input array. So you need to check the value which read return which is the number of bytes read. (See docs.oracle.com/javase/6/docs/api/java/io/…)Gemmulation

© 2022 - 2024 — McMap. All rights reserved.