Using apr_socket_sendfile () from servlets under Tomcat

    In this topic, I’ll talk about a small but effective way to transfer files to a user from a servlet using the HTTP protocol. Used by:
    • Apache tomcat
    • Apache Portable Runtime Library
    • Apache Tomcat Native Library
    • Your servlet that needs to give files to the user

    Of course, servlet file rendering is not very good in terms of performance. First, giving static content is best without any scripts at all. But sometimes you can’t do without it. Secondly, the return of data comes down, most often, to something like this:
    1.     long writed = 0;
    2.     byte[] buffer = new byte[BUFFER_LENGTH];
    3.     int readed =, 0, BUFFER_LENGTH);
    4.     while (readed != -1) {
    5.       out.write(buffer, 0, readed);
    6.       writed += readed;
    7.       readed =, 0, BUFFER_LENGTH);
    8.     }
    * This source code was highlighted with Source Code Highlighter.

    After reading books on NIO and using a microscope, you can redo this into a slightly more effective tool:
    1.   public static long transfer(File file, OutputStream out) throws IOException {
    2.     return transfer(file, 0, file.length(), out);
    3.   }
    5.   public static long transfer(File file, long position, long count,
    6.       OutputStream out) throws IOException {
    7.     FileChannel in = new FileInputStream(file).getChannel();
    8.     try {
    9.       long writed = in.transferTo(position, count, Channels
    10.           .newChannel(out));
    11.       return writed;
    12.     } finally {
    13.       in.close();
    14.     }
    15.   }
    * This source code was highlighted with Source Code Highlighter.

    However, those who carefully studied the stack trace of their server, know that Tomcat's OutputStream does not support transmission via channels, and it all comes down to the first example, but already in the bowels of the JVM.

    The obvious disadvantages of this approach are:
    • Performance. The code written in Java in this case will obviously be slower than the native code if it could copy directly from the file to OutputStream
    • Memory usage. It’s hard for developers to hold on and not wrap each stream in a couple of other Buffered (Input | Output) Stream. It turns out that each piece of the file will in turn visit three or four places of our RAM (remember the disk cache of the operating system and, most likely, some TCP / IP cache)
    • The code actively uses processor resources to copy pieces of data back and forth

    However, Apache Tomcat allows servlets to use (via a convenient interface) the apr_socket_sendfile function from the Apache Portable Runtime library. This function accepts an input pointer to a socket, to a file, as well as start and length parameters of the transmitted data (you can transfer not only the entire file). Access to this functionality is done through the use of request attributes (HttpServletRequest). Check for this functionality:
    1.   private static final String TOMCAT_SENDFILE_SUPPORT = "";
    3.   final boolean sendFileSupport = Boolean.TRUE.equals(request
    4.         .getAttribute(TOMCAT_SENDFILE_SUPPORT));
    * This source code was highlighted with Source Code Highlighter.

    Now if:
    1. sendFileSupport == true
    2. The file will not be deleted immediately after code execution
    3. File size less than 2 GB

    Instead of transferring the file yourself, you can instruct Apache Tomcat:
    1. private static final String TOMCAT_SENDFILE_FILENAME = "org.apache.tomcat.sendfile.filename";
    2. private static final String TOMCAT_SENDFILE_START = "org.apache.tomcat.sendfile.start";
    3. private static final String TOMCAT_SENDFILE_END = "org.apache.tomcat.sendfile.end";
    5. // using Apache APR and/or NIO to transfer file
    6. response.setBufferSize(1 << 18);
    7. request.setAttribute(TOMCAT_SENDFILE_FILENAME, file.getCanonicalPath());
    8. request.setAttribute(TOMCAT_SENDFILE_START, Long.valueOf(0));
    9. request.setAttribute(TOMCAT_SENDFILE_END, Long.valueOf(fileLength));
    * This source code was highlighted with Source Code Highlighter.

    The second limitation is that the file transfer process will begin after we finish the work in the servlet. The third - with which it is not clear, but perhaps with the fact that I have a 32-bit JVM and 32-bit Gentoo on a test machine (Tomcat did not want to give the file more than 2 GB myself).

    As a result:
    • The number of working Java server threads has decreased by two to three times, since the files are now transferred in separate native threads
    • CPU usage has decreased as APR uses operating system features to optimize file transfer
    • There is less “garbage” left in the heap, which improves the performance of the Garbage Collector

    Of course, for the production system, you need to not only be able to give the whole file, but also in parts, and also take into account the possibility that the user already has the file (process NotModifiedSince).

    For further study

    Also popular now: