Class AbstractFileServlet

java.lang.Object
jakarta.servlet.GenericServlet
jakarta.servlet.http.HttpServlet
org.openremote.container.web.file.AbstractFileServlet
All Implemented Interfaces:
jakarta.servlet.Servlet, jakarta.servlet.ServletConfig, Serializable
Direct Known Subclasses:
FileServlet

public abstract class AbstractFileServlet extends jakarta.servlet.http.HttpServlet

The well known "BalusC FileServlet", as an abstract template, slightly refactored, rewritten and modernized with a.o. fast NIO stuff instead of legacy RandomAccessFile. GZIP support is stripped off as that can be done application wide via GzipResponseFilter.

This servlet properly deals with ETag, If-None-Match and If-Modified-Since caching requests, hereby improving browser caching. This servlet also properly deals with Range and If-Range ranging requests (RFC7233), which is required by most media players for proper audio/video streaming, and by webbrowsers and for a proper resume of an paused download, and by download accelerators to be able to request smaller parts simultaneously. This servlet is ideal when you have large files like media files placed outside the web application and you can't use the default servlet.

Usage

Just extend this class and override the getResource(HttpServletRequest) method to return the desired file. If you want to trigger a HTTP 400 "Bad Request" error, simply throw IllegalArgumentException. If you want to trigger a HTTP 404 "Not Found" error, simply return null, or a non-existent file.

Here's a concrete example which serves it via an URL like /media/foo.ext:

 @WebServlet("/media/*")
 public class MediaFileServlet extends FileServlet {

     private File folder;

     @Override
     public void init() throws ServletException {
         folder = new File("/var/webapp/media");
     }

     @Override
     protected File getFile(HttpServletRequest request) {
         String pathInfo = request.getPathInfo();

         if (pathInfo == null || pathInfo.isEmpty() || "/".equals(pathInfo)) {
             throw new IllegalArgumentException();
         }

         return new File(folder, pathInfo);
     }

 }
 

You can embed it in e.g. HTML5 video tag as below:

 <video src="#{request.contextPath}/media/video.mp4" controls="controls" />
 

Customizing FileServlet

If more fine grained control is desired for handling "file not found" error, determining the cache expire time, the content type, whether the file should be supplied as an attachment and the attachment's file name, then the developer can opt to override one or more of the following protected methods:

See also:

Since:
2.2
See Also:
  • Nested Class Summary

    Nested Classes
    Modifier and Type
    Class
    Description
    static class 
    Convenience class for a byte range.
    protected class 
     
  • Field Summary

    Fields inherited from class jakarta.servlet.http.HttpServlet

    LEGACY_DO_HEAD
  • Constructor Summary

    Constructors
    Constructor
    Description
     
  • Method Summary

    Modifier and Type
    Method
    Description
    static <T> T
    coalesce(T... objects)
    Returns the first non-null object of the argument list, or null if there is no such element.
    protected void
    doGet(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response)
     
    protected void
    doHead(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response)
     
    protected String
    getContentType(jakarta.servlet.http.HttpServletRequest request, String fileName)
    Returns the content type associated with the given HTTP servlet request and file.
    protected long
    getExpireTime(jakarta.servlet.http.HttpServletRequest request, String fileName)
    Returns how long the resource may be cached by the client before it expires, in seconds.
    protected abstract Resource
    getResource(jakarta.servlet.http.HttpServletRequest request)
    Returns the file associated with the given HTTP servlet request.
    protected void
    handleFileNotFound(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response)
    Handles the case when the file is not found.
    static void
    setCacheHeaders(jakarta.servlet.http.HttpServletResponse response, long expires)
    Set the cache headers.
    protected String
    setContentHeaders(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response, Resource resource, List<AbstractFileServlet.Range> ranges)
    Set content headers.
    static void
    setNoCacheHeaders(jakarta.servlet.http.HttpServletResponse response)
    Set the no-cache headers.
    static long
    Stream the given input to the given output via NIO Channels and a directly allocated NIO ByteBuffer.
    static long
    stream(Resource resource, OutputStream output, long start, long length)
    Stream a specified range of the given file to the given output via NIO Channels and a directly allocated NIO ByteBuffer.

    Methods inherited from class jakarta.servlet.http.HttpServlet

    doDelete, doOptions, doPost, doPut, doTrace, getLastModified, init, service, service

    Methods inherited from class jakarta.servlet.GenericServlet

    destroy, getInitParameter, getInitParameterNames, getServletConfig, getServletContext, getServletInfo, getServletName, init, log, log

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Constructor Details

    • AbstractFileServlet

      public AbstractFileServlet()
  • Method Details

    • doHead

      protected void doHead(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response) throws jakarta.servlet.ServletException, IOException
      Overrides:
      doHead in class jakarta.servlet.http.HttpServlet
      Throws:
      jakarta.servlet.ServletException
      IOException
    • doGet

      protected void doGet(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response) throws jakarta.servlet.ServletException, IOException
      Overrides:
      doGet in class jakarta.servlet.http.HttpServlet
      Throws:
      jakarta.servlet.ServletException
      IOException
    • getResource

      protected abstract Resource getResource(jakarta.servlet.http.HttpServletRequest request) throws IllegalArgumentException, AbstractFileServlet.RedirectException
      Returns the file associated with the given HTTP servlet request. If this method throws IllegalArgumentException, then the servlet will return a HTTP 400 error. If this method returns null, or if File.isFile() returns false, then the servlet will invoke handleFileNotFound(HttpServletRequest, HttpServletResponse).
      Parameters:
      request - The involved HTTP servlet request.
      Returns:
      The Resource associated with the given HTTP servlet request.
      Throws:
      IllegalArgumentException - When the request is mangled in such way that it's not recognizable as a valid file request. The servlet will then return a HTTP 400 error.
      AbstractFileServlet.RedirectException
    • handleFileNotFound

      protected void handleFileNotFound(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response) throws IOException
      Handles the case when the file is not found.

      The default implementation sends a HTTP 404 error.

      Parameters:
      request - The involved HTTP servlet request.
      response - The involved HTTP servlet response.
      Throws:
      IOException - When something fails at I/O level.
      Since:
      2.3
    • getExpireTime

      protected long getExpireTime(jakarta.servlet.http.HttpServletRequest request, String fileName)
      Returns how long the resource may be cached by the client before it expires, in seconds.

      The default implementation returns 30 days in seconds.

      Parameters:
      request - The involved HTTP servlet request.
      fileName - The involved file name.
      Returns:
      The client cache expire time in seconds (not milliseconds!).
    • getContentType

      protected String getContentType(jakarta.servlet.http.HttpServletRequest request, String fileName)
      Returns the content type associated with the given HTTP servlet request and file.

      The default implementation delegates File.getName() to ServletContext.getMimeType(String) with a fallback default value of application/octet-stream.

      Parameters:
      request - The involved HTTP servlet request.
      fileName - The involved file name.
      Returns:
      The content type associated with the given HTTP servlet request and file.
    • setContentHeaders

      protected String setContentHeaders(jakarta.servlet.http.HttpServletRequest request, jakarta.servlet.http.HttpServletResponse response, Resource resource, List<AbstractFileServlet.Range> ranges)
      Set content headers.
    • coalesce

      @SafeVarargs public static <T> T coalesce(T... objects)
      Returns the first non-null object of the argument list, or null if there is no such element.
      Type Parameters:
      T - The generic object type.
      Parameters:
      objects - The argument list of objects to be tested for non-null.
      Returns:
      The first non-null object of the argument list, or null if there is no such element.
    • stream

      public static long stream(InputStream input, OutputStream output) throws IOException
      Stream the given input to the given output via NIO Channels and a directly allocated NIO ByteBuffer. Both the input and output streams will implicitly be closed after streaming, regardless of whether an exception is been thrown or not.
      Parameters:
      input - The input stream.
      output - The output stream.
      Returns:
      The length of the written bytes.
      Throws:
      IOException - When an I/O error occurs.
    • stream

      public static long stream(Resource resource, OutputStream output, long start, long length) throws IOException
      Stream a specified range of the given file to the given output via NIO Channels and a directly allocated NIO ByteBuffer. The output stream will only implicitly be closed after streaming when the specified range represents the whole file, regardless of whether an exception is been thrown or not.
      Parameters:
      resource - The resource.
      output - The output stream.
      start - The start position (offset).
      length - The (intented) length of written bytes.
      Returns:
      The (actual) length of the written bytes. This may be smaller when the given length is too large.
      Throws:
      IOException - When an I/O error occurs.
      Since:
      2.2
    • setCacheHeaders

      public static void setCacheHeaders(jakarta.servlet.http.HttpServletResponse response, long expires)

      Set the cache headers. If the expires argument is larger than 0 seconds, then the following headers will be set:

      • Cache-Control: public,max-age=[expiration time in seconds],must-revalidate
      • Expires: [expiration date of now plus expiration time in seconds]

      Else the method will delegate to setNoCacheHeaders(HttpServletResponse).

      Parameters:
      response - The HTTP servlet response to set the headers on.
      expires - The expire time in seconds (not milliseconds!).
      Since:
      2.2
    • setNoCacheHeaders

      public static void setNoCacheHeaders(jakarta.servlet.http.HttpServletResponse response)

      Set the no-cache headers. The following headers will be set:

      • Cache-Control: no-cache,no-store,must-revalidate
      • Expires: [expiration date of 0]
      • Pragma: no-cache
      Set the no-cache headers.
      Parameters:
      response - The HTTP servlet response to set the headers on.
      Since:
      2.2