FULL PRODUCT VERSION :
Java(TM) SE Runtime Environment (build 1.6.0_05-b13)
Java HotSpot(TM) Client VM (build 10.0-b19, mixed mode, sharing)
ADDITIONAL OS VERSION INFORMATION :
Windows XP
A DESCRIPTION OF THE PROBLEM :
Performance of HttpURLConnecton class for uploading chunked content is significantly slower than using socket and custom implementation of chunked stream
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Performing content upload from client machine to application server (Tomcat).
Use HttpURLConnection to get output stream set on HttpURLConnection method
"POST" and setChunkedStreamingMode(32*1024) after that upload content on the server and record the time it took.
Alternatively we just open socket without any additional configuration and using our implementation of Chunked stream stream the same content on the application server and again measure time.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Stream received from HttpURLConnection should perform not worse than our implementation
ACTUAL -
Stream received from HttpURLConnection actually performs 50-100% worse
ERROR MESSAGES/STACK TRACES THAT OCCUR :
no error messages
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
Server side simple servlet that reads from input stream
import java.io.*;
import java.util.Enumeration;
import javax.servlet.ServletException;
import javax.servlet.http.*;
public class TestPostServlet extends HttpServlet
{
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
System.out.println((new StringBuilder()).append("DoGet ===> ").append(req.getSession().getId()).toString());
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException
{
System.out.println((new StringBuilder()).append("doPost ===> ").append(req.getSession().getId()).toString());
long start = System.currentTimeMillis();
InputStream input = req.getInputStream();
int c;
do
c = input.read();
while(c >= 0);
long time = System.currentTimeMillis() - start;
System.out.println((new StringBuilder()).append("time = ").append(time).toString());
}
private Object requstId(HttpServletRequest req)
{
return req;
}
private void dumpHeader(HttpServletRequest req)
{
String name;
for(Enumeration names = req.getHeaderNames(); names.hasMoreElements(); System.out.println((new StringBuilder()).append(name).append(" --> ").append(req.getHeader(name)).toString()))
name = (String)names.nextElement();
}
static long s_date = System.currentTimeMillis();
private static final int BUFFER_SIZE = 10000;
private static final int BUFFER_COUNT = 3000;
private static final String FILENAME = "R:\\__Projects\\AppServer\\out\\exploded\\TestWeb1\\1.ppt";
}
Client side
has two files one is the test:
package com.documentum.test;
import java.io.IOException;
import java.io.OutputStream;
import java.net.*;
public class Test1
{
public static void main (String[] args)
{
try
{
new Test1(args[0]).run1();
Thread.sleep(5000);
new Test1(args[0]).run2();
Thread.sleep(5000);
new Test1(args[0]).run1();
Thread.sleep(5000);
new Test1(args[0]).run2();
}
catch (Exception e)
{
e.printStackTrace();
}
}
public Test1 (String spec) throws MalformedURLException
{
m_url = new URL(spec);
}
private void run1 () throws IOException
{
HttpURLConnection connection = (HttpURLConnection) m_url.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setChunkedStreamingMode(32 * 1024);
OutputStream out = connection.getOutputStream();
streamContent(out);
out.flush ();
System.out.println("connection.getResponseCode() = " + connection.getResponseCode());
connection.disconnect();
}
private void run2 () throws IOException
{
Socket socket = new Socket(m_url.getHost(), m_url.getPort());
OutputStream out = socket.getOutputStream();
out.write(("POST " +
m_url.getPath() +
" HTTP/1.1\r\n").getBytes());
out.write(("host: " +
m_url.getHost() +
":" +
m_url.getPort() +
"\r\n").getBytes());
out.write("User-agent: TestApp\r\n".getBytes());
out.write("Transfer-Encoding: chunked\r\n".getBytes());
out.write("\r\n".getBytes());
ChunkedOutputStream chunked = new ChunkedOutputStream(out, 16, 32 * 1024);
streamContent(chunked);
chunked.finish ();
out.close();
socket.close();
}
private static void streamContent (OutputStream out)
throws IOException
{
byte[] buffer = new byte[8192];
for (int i = 0; i < 8192; i++)
{
out.write(buffer);
}
}
static final int BUFFER_LENGTH = 32768;
private URL m_url;
}
Implementation of chunked output stream
import java.io.OutputStream;
import java.io.IOException;
public class ChunkedOutputStream extends OutputStream
{
public ChunkedOutputStream (OutputStream stream, int radix)
{
this(stream, radix, BUFFER_SIZE);
}
public ChunkedOutputStream (OutputStream stream, int radix, int bufferSize)
{
m_stream = stream;
m_radix = radix;
m_bufferSize = bufferSize;
m_standardChunkHeader = (Integer.toString (m_bufferSize, m_radix) + HEADER_END).getBytes();
m_fullBufferSize = m_bufferSize + m_standardChunkHeader.length + FOOTER_BUFFER.length;
m_buffer = new byte[m_fullBufferSize];
m_pos = 0;
initBuffer();
}
public void write (int b) throws IOException
{
if (m_pos < 0)
throw new IOException();
m_buffer[m_standardChunkHeader.length + m_pos++] = (byte)b;
if (m_pos == m_bufferSize)
internalFlush(true);
}
public void write (byte b[], int off, int len) throws IOException
{
if (m_pos < 0)
throw new IOException();
int left = m_bufferSize - m_pos;
if (left >= len)
{
System.arraycopy(b, off, m_buffer, m_pos + m_standardChunkHeader.length, len);
m_pos += len;
len = 0;
}
else
{
System.arraycopy(b, off, m_buffer, m_pos + m_standardChunkHeader.length, left);
m_pos += left;
len -= left;
}
if (m_pos == m_bufferSize)
internalFlush(false);
if (len > 0)
write(b, off + left, len);
}
public void flush () throws IOException
{
if (m_pos < 0)
throw new IOException();
internalFlush(false);
m_stream.flush ();
initBuffer();
}
public void close () throws IOException
{
if (m_pos > -1)
finish();
m_stream.close();
}
public void finish () throws IOException
{
if (m_pos > 0)
internalFlush(false);
internalFlush(true);
m_stream.flush ();
m_pos = -1;
}
public long getContentLength()
{
throw new UnsupportedOperationException(
"ChunkedOutputStream does not support getContentLength()");
}
private void internalFlush (boolean needWriteEmptyChunk) throws IOException
{
int offset = 0;
int length = m_fullBufferSize;
if (m_pos == 0 && !needWriteEmptyChunk)
return;
if (m_pos != m_bufferSize)
{
byte [] customHeader = Integer.toString (m_pos, m_radix).getBytes();
offset = m_standardChunkHeader.length - HEADER_END_BUFFER.length - customHeader.length;
System.arraycopy(customHeader, 0, m_buffer, offset, customHeader.length);
System.arraycopy(FOOTER_BUFFER, 0, m_buffer, m_standardChunkHeader.length + m_pos, FOOTER_BUFFER.length);
length = customHeader.length + FOOTER_BUFFER.length + m_pos + FOOTER_BUFFER.length;
}
m_stream.write (m_buffer, offset, length);
m_pos = 0;
}
private void initBuffer ()
{
System.arraycopy(m_standardChunkHeader, 0, m_buffer, 0, m_standardChunkHeader.length);
System.arraycopy(FOOTER_BUFFER, 0, m_buffer, m_buffer.length - FOOTER_BUFFER.length, FOOTER_BUFFER.length);
}
private OutputStream m_stream;
private byte [] m_buffer;
private int m_pos;
private final int m_radix;
private final int m_bufferSize;
private final int m_fullBufferSize;
private static final int BUFFER_SIZE = 1460 * 22;
private final byte[] m_standardChunkHeader;
private static final byte[] FOOTER_BUFFER = "\r\n".getBytes ();
private static final String HEADER_END = "\r\n";
private static final byte[] HEADER_END_BUFFER = HEADER_END.getBytes ();
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Use our own implementation instead of sun HttpURLConnection