Search this blog ...

Tuesday, July 17, 2012

File Download Java Servlet example - 2GB overflow workaround

I discovered a few days back an issue with our product on HTTP downloads > 2GB.  It appears to be a simple overflow on the HttpServletResponse.setContentLength method. You can probably excuse the API designers circa 1997 assuming a 32-bit signed Integer with max value 231-1 (2147483647 bytes) would be sufficient.  The Gigabit ethernet standard did not come for another year (1998)!

Here is the exception seen when you provide a long value greater than 2147483647 bytes to the setContentLength(int) method:

java.net.ProtocolException: Exceeded stated content-length of: '-XXXX' bytes
        at weblogic.servlet.internal.ServletOutputStreamImpl.checkCL(ServletOutputStreamImpl.java:200)

Below is a sample download servlet with workaround for the 2gb limitation.  It has been tested on Firefox 3.6 against WebLogic Server 10.3.6 with a 2.2GB download and worked perfectly.


import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


public class DownloadServlet
  extends HttpServlet
{
  @SuppressWarnings("compatibility:1533750721037291976")
  private static final long serialVersionUID = 1L;

  protected void doGet(HttpServletRequest request,
    HttpServletResponse response)
    throws ServletException, IOException
  {
    doPost(request, response);
  }

  protected void doPost(HttpServletRequest request,
    HttpServletResponse response)
    throws ServletException, IOException
  {
    // if no file parameter specified, download hosts file
    String file = request.getParameter("file");
    file = (file == null || file.length() == 0) ? "/etc/hosts" : file;

    File fileObj = new File(file);
    if ((!fileObj.exists()) || (!fileObj.isFile()) || (!fileObj.canRead()))
    {
      throw new IOException("'file' '" + file + "' cannot be read.");
    }

    ServletContext context = getServletConfig().getServletContext();

    String mimetype = context.getMimeType(file);
    response.setContentType(mimetype == null ? "application/octet-stream" :
        mimetype);

    long length = fileObj.length();
    if (length <= Integer.MAX_VALUE)
    {
      response.setContentLength((int)length);
    }
    else
    {
      response.addHeader("Content-Length", Long.toString(length));
    }

    response.setHeader("Content-Disposition",
        "attachment; filename=\"" + fileObj.getName() + "\"");

    ServletOutputStream out = response.getOutputStream();
    InputStream in = null;
    byte[] buffer = new byte[32768];
    try
    {
      in = new FileInputStream(fileObj);

      int bytesRead;
      while ((bytesRead = in.read(buffer)) >= 0)
      {
        out.write(buffer, 0, bytesRead);
      }
    }
    finally
    {
      if (in != null)
      {
        in.close();
      }
    }
  }
}