package com.elibom.jogger.middleware.statik;
import java.net.URLDecoder;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import com.elibom.jogger.Middleware;
import com.elibom.jogger.MiddlewareChain;
import com.elibom.jogger.asset.Asset;
import com.elibom.jogger.asset.AssetLoader;
import com.elibom.jogger.asset.FileAssetLoader;
import com.elibom.jogger.http.Request;
import com.elibom.jogger.http.Response;
import com.elibom.jogger.util.Preconditions;
/**
* This middleware serve static files, usually from (but not limited to) the file system. Keep in mind the following when
* using this middleware:
*
* <ol>
* <li>It will only handle requests where its path matches the prefix configured in the middleware (e.g. the request with
* path "/assets/my_asset.gif" will be handled if the path prefix of the middleware is "assets").</li>
* <li>If the path matches but the method is not GET, a <strong>404 Not Found</strong> will be returned (i.e. it won't
* recognized other HTTP method different to GET).</li>
* <li>If the asset is not found, a <strong>404 Not Found</strong> will be returned.</li>
* <li>If the asset hasn't been modified (i.e. using the If-Modified-Since header), a <strong>304 Not Modified</strong> will
* be returned.</li>
* </ol>
*
* @author German Escobar
*/
public class StaticMiddleware implements Middleware {
/**
* Used to load the assets.
*/
private AssetLoader assetLoader;
/**
* The path prefix this middleware uses to decide to match a request.
*/
private String prefix;
/**
* Constructor. Creates a new instance with the provided path and with a {@link FileAssetLoader} as the
* {@link AssetLoader} implementation.
*
* @param path the prefix used to match the request path; also the path where the static files are located in the file system.
*/
public StaticMiddleware(String path) {
Preconditions.notNull(path, "no path provided.");
this.prefix = fixPrefix(path);
this.assetLoader = new FileAssetLoader(path);
}
/**
* Helper method. Adds a leading and trailing slash if they are not present (i.e. "assets" becomes "/assets/")
*
* @param prefix the path to be fixed, can't be null.
*
* @return the prefix with leading and trailing slashes.
*/
private String fixPrefix(String prefix) {
String fixedPrefix = prefix.startsWith("/") ? prefix : "/" + prefix;
return fixedPrefix.endsWith("/") ? fixedPrefix : fixedPrefix + "/";
}
/**
* Constructor. Creates a new instance with a {@link FileAssetLoader} with the provided path, and the prefix.
*
* @param path the path where the static files are located in the file system.
* @param prefix the prefix used to match the request path
*/
public StaticMiddleware(String path, String prefix) {
Preconditions.notNull(path, "no path provided.");
Preconditions.notNull(prefix, "no prefix provided.");
this.prefix = fixPrefix(prefix);
this.assetLoader = new FileAssetLoader(path);
}
/**
* Constructor. Creates a new instance with the provided asset loader and prefix.
*
* @param assetLoader the object used to load the assets.
* @param prefix the prefix used to match the request path.
*/
public StaticMiddleware(AssetLoader assetLoader, String prefix) {
Preconditions.notNull(assetLoader, "no assetLoader provided.");
Preconditions.notNull(prefix, "no prefix provided.");
this.prefix = fixPrefix(prefix);
this.assetLoader = assetLoader;
}
@Override
public void handle(Request request, Response response, MiddlewareChain chain) throws Exception {
// check if we have to handle the request or not
String requestPath = fixRequestPath(request.getPath());
if (!requestPath.startsWith(prefix)) {
chain.next();
return;
}
// only handle GET requests
if (!request.getMethod().equalsIgnoreCase("get")) {
chain.next();
return;
}
// load the asset
requestPath = requestPath.replaceFirst(prefix, "");
Asset asset = assetLoader.load(URLDecoder.decode(requestPath, "UTF-8"));
if (asset == null) {
chain.next();
return;
}
// check if asset has been modified
if (!assetHasBeenModified(request, asset)) {
response.status(Response.NOT_MODIFIED);
return;
}
response.status(Response.OK);
response.write(asset);
}
/**
* Helper method. The request path shouldn't have a trailing slash.
*
* @param path the path to fix.
* @return the path with its trailing slash
*/
private String fixRequestPath(String path) {
return path.endsWith("/") ? path.substring(0, path.length() - 1) : path;
}
private boolean assetHasBeenModified(Request request, Asset asset) throws ParseException {
String ifModifiedSince = request.getHeader("If-Modified-Since");
if (ifModifiedSince != null) {
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
Date dt = sdf.parse(ifModifiedSince);
if (dt.getTime() == asset.getLastModified()) {
return false;
}
}
return true;
}
}