package au.com.vaadinutils.servlet;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import javax.servlet.ServletConfig;
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;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class StaticContentServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
Logger logger = LogManager.getLogger();
private ServletContext sc;
@Override
public void init(ServletConfig config) throws ServletException
{
sc = config.getServletContext();
}
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
ServletContext servletContext = request.getServletContext();
String requestURI = java.net.URLDecoder.decode(request.getRequestURI(), "UTF-8");
// strip the context as otherwise it gets duplicated in the next step
String relativePath = requestURI.replace(servletContext.getContextPath(), "");
relativePath = requestURI.replace(servletContext.getContextPath(), "");
String file = servletContext.getRealPath(relativePath);
File resource = new File(file);
if (!resource.exists())
{
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
send(request, response, resource);
}
public void send(HttpServletRequest request, HttpServletResponse response, File resource)
{
// Find the modification timestamp
long lastModifiedTime = resource.lastModified();
// Remove milliseconds to avoid comparison problems (milliseconds
// are not returned by the browser in the "If-Modified-Since"
// header).
lastModifiedTime = lastModifiedTime - lastModifiedTime % 1000;
if (browserHasNewestVersion(request, lastModifiedTime))
{
response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
return;
}
// Set type mime type if we can determine it based on the filename
final String mimetype = sc.getMimeType(resource.getName());
if (mimetype != null)
{
response.setContentType(mimetype);
}
// Provide modification timestamp to the browser if it is known.
if (lastModifiedTime > 0)
{
response.setDateHeader("Last-Modified", lastModifiedTime);
String cacheControl = "public, max-age=0, must-revalidate";
int resourceCacheTime = getCacheTime(resource.getName());
if (resourceCacheTime > 0)
{
cacheControl = "max-age=" + String.valueOf(resourceCacheTime);
}
response.setHeader("Cache-Control", cacheControl);
}
writeStaticResourceResponse(request, response, resource);
}
/**
* Calculates the cache lifetime for the given filename in seconds. By
* default filenames containing ".nocache." return 0, filenames containing
* ".cache." return one year, all other return the value defined in the
* web.xml using resourceCacheTime (defaults to 1 hour).
*
* @param filename
* @return cache lifetime for the given filename in seconds
*/
protected int getCacheTime(String filename)
{
/*
* GWT conventions: - files containing .nocache. will not be cached. -
* files containing .cache. will be cached for one year.
* https://developers.google.com/web-toolkit/doc/latest/
* DevGuideCompilingAndDebugging#perfect_caching
*/
if (filename.contains(".nocache."))
{
return 0;
}
if (filename.contains(".cache."))
{
return 60 * 60 * 24 * 365;
}
/*
* For all other files, the browser is allowed to cache for 1 hour
* without checking if the file has changed. This forces browsers to
* fetch a new version when the Vaadin version is updated. This will
* cause more requests to the servlet than without this but for high
* volume sites the static files should never be served through the
* servlet.
*/
return 60 * 60;
}
private void writeStaticResourceResponse(HttpServletRequest request, HttpServletResponse response, File file)
{
// this always will point to the root of the web site
try (FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
ServletOutputStream outStream = response.getOutputStream();)
{
byte[] buf = new byte[4 * 1024]; // 4K buffer
int bytesRead;
while ((bytesRead = bis.read(buf)) != -1)
{
outStream.write(buf, 0, bytesRead);
}
}
catch (Exception e)
{
logger.warn("EWengine: file not found or unable to read: '" + file + "'");
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
return;
}
}
/**
* Checks if the browser has an up to date cached version of requested
* resource. Currently the check is performed using the "If-Modified-Since"
* header. Could be expanded if needed.
*
* @param request The HttpServletRequest from the browser.
* @param resourceLastModifiedTimestamp The timestamp when the resource was
* last modified. 0 if the last modification time is unknown.
* @return true if the If-Modified-Since header tells the cached version in
* the browser is up to date, false otherwise
*/
private boolean browserHasNewestVersion(HttpServletRequest request, long resourceLastModifiedTimestamp)
{
if (resourceLastModifiedTimestamp < 1)
{
// We do not know when it was modified so the browser cannot have an
// up-to-date version
return false;
}
/*
* The browser can request the resource conditionally using an
* If-Modified-Since header. Check this against the last modification
* time.
*/
try
{
// If-Modified-Since represents the timestamp of the version cached
// in the browser
long headerIfModifiedSince = request.getDateHeader("If-Modified-Since");
if (headerIfModifiedSince >= resourceLastModifiedTimestamp)
{
// Browser has this an up-to-date version of the resource
return true;
}
}
catch (Exception e)
{
// Failed to parse header. Fail silently - the browser does not have
// an up-to-date version in its cache.
}
return false;
}
/**
* used by StaticFilter to initialize the servletContext
*
* @param servletContext
*/
public void setContext(ServletContext servletContext)
{
sc = servletContext;
}
}