package org.smartly.packages.http.impl.handlers;
import org.eclipse.jetty.http.*;
import org.eclipse.jetty.io.WriterOutputStream;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Response;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.server.handler.ResourceHandler;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.resource.Resource;
import org.smartly.commons.logging.Logger;
import org.smartly.commons.logging.util.LoggingUtils;
import org.smartly.commons.util.DateUtils;
import org.smartly.commons.util.PathUtils;
import org.smartly.packages.http.SmartlyHttp;
import org.smartly.packages.http.impl.WebServer;
import org.smartly.packages.http.impl.util.ServletUtils;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
/**
* User: angelo.geminiani
*/
public class SmartlyResourceHandler extends HandlerWrapper {
private WebServer _server;
private ContextHandler _context;
private Resource _baseResource;
private Resource _defaultStylesheet;
private Resource _stylesheet;
private String[] _welcomeFiles = {"index.html"};
private MimeTypes _mimeTypes = new MimeTypes();
private boolean _directory;
private boolean _etags;
private String _cacheControl;
public SmartlyResourceHandler() {
_mimeTypes.addMimeMapping("vcf", "text/vcard");
}
public void setServer(final WebServer server) {
_server = server;
}
// --------------------------------------------------------------------
// p u b l i c
// --------------------------------------------------------------------
/* ------------------------------------------------------------ */
public MimeTypes getMimeTypes() {
return _mimeTypes;
}
/* ------------------------------------------------------------ */
public void setMimeTypes(MimeTypes mimeTypes) {
_mimeTypes = mimeTypes;
}
/* ------------------------------------------------------------ */
/**
* Get the directory option.
*
* @return true if directories are listed.
*/
public boolean isDirectoriesListed() {
return _directory;
}
/* ------------------------------------------------------------ */
/**
* Set the directory.
*
* @param directory true if directories are listed.
*/
public void setDirectoriesListed(boolean directory) {
_directory = directory;
}
/* ------------------------------------------------------------ */
/**
* @return True if ETag processing is done
*/
public boolean isEtags() {
return _etags;
}
/* ------------------------------------------------------------ */
/**
* @param etags True if ETag processing is done
*/
public void setEtags(boolean etags) {
_etags = etags;
}
/* ------------------------------------------------------------ */
@Override
public void doStart()
throws Exception {
ContextHandler.Context scontext = ContextHandler.getCurrentContext();
_context = (scontext == null ? null : scontext.getContextHandler());
super.doStart();
}
/* ------------------------------------------------------------ */
/**
* @return Returns the resourceBase.
*/
public Resource getBaseResource() {
if (_baseResource == null)
return null;
return _baseResource;
}
/* ------------------------------------------------------------ */
/**
* @return Returns the base resource as a string.
*/
public String getResourceBase() {
if (_baseResource == null)
return null;
return _baseResource.toString();
}
/* ------------------------------------------------------------ */
/**
* @param base The resourceBase to set.
*/
public void setBaseResource(Resource base) {
_baseResource = base;
}
/* ------------------------------------------------------------ */
/**
* @param resourceBase The base resource as a string.
*/
public void setResourceBase(String resourceBase) {
try {
setBaseResource(Resource.newResource(resourceBase));
} catch (Exception e) {
this.getLogger().warning(e.toString());
throw new IllegalArgumentException(resourceBase);
}
}
/* ------------------------------------------------------------ */
/**
* @return Returns the stylesheet as a Resource.
*/
public Resource getStylesheet() {
if (_stylesheet != null) {
return _stylesheet;
} else {
if (_defaultStylesheet == null) {
try {
_defaultStylesheet = Resource.newResource(this.getClass().getResource("/jetty-dir.css"));
} catch (IOException e) {
this.getLogger().warning(e.toString());
}
}
return _defaultStylesheet;
}
}
/* ------------------------------------------------------------ */
/**
* @param stylesheet The location of the stylesheet to be used as a String.
*/
public void setStylesheet(String stylesheet) {
try {
_stylesheet = Resource.newResource(stylesheet);
if (!_stylesheet.exists()) {
this.getLogger().warning("unable to find custom stylesheet: " + stylesheet);
_stylesheet = null;
}
} catch (Exception e) {
this.getLogger().warning(e.toString());
throw new IllegalArgumentException(stylesheet.toString());
}
}
/* ------------------------------------------------------------ */
/**
* @return the cacheControl header to set on all static content.
*/
public String getCacheControl() {
return _cacheControl.toString();
}
/* ------------------------------------------------------------ */
/**
* @param cacheControl the cacheControl header to set on all static content.
*/
public void setCacheControl(String cacheControl) {
_cacheControl = cacheControl;
}
/* ------------------------------------------------------------ */
/*
*/
public Resource getResource(String path) throws MalformedURLException {
if (path == null || !path.startsWith("/"))
throw new MalformedURLException(path);
Resource base = _baseResource;
if (base == null) {
if (_context == null)
return null;
base = _context.getBaseResource();
if (base == null)
return null;
}
try {
path = URIUtil.canonicalPath(path);
return base.addPath(path);
} catch (Exception ignored) {
}
return null;
}
/* ------------------------------------------------------------ */
protected Resource getResource(HttpServletRequest request) throws MalformedURLException {
String servletPath;
String pathInfo;
Boolean included = request.getAttribute(RequestDispatcher.INCLUDE_REQUEST_URI) != null;
if (included != null && included.booleanValue()) {
servletPath = (String) request.getAttribute(RequestDispatcher.INCLUDE_SERVLET_PATH);
pathInfo = (String) request.getAttribute(RequestDispatcher.INCLUDE_PATH_INFO);
if (servletPath == null && pathInfo == null) {
servletPath = request.getServletPath();
pathInfo = request.getPathInfo();
}
} else {
servletPath = request.getServletPath();
pathInfo = request.getPathInfo();
}
String pathInContext = URIUtil.addPaths(servletPath, pathInfo);
return getResource(pathInContext);
}
/* ------------------------------------------------------------ */
public String[] getWelcomeFiles() {
return _welcomeFiles;
}
/* ------------------------------------------------------------ */
public void setWelcomeFiles(String[] welcomeFiles) {
_welcomeFiles = welcomeFiles;
}
/* ------------------------------------------------------------ */
protected Resource getWelcome(Resource directory) throws MalformedURLException, IOException {
for (int i = 0; i < _welcomeFiles.length; i++) {
Resource welcome = directory.addPath(_welcomeFiles[i]);
if (welcome.exists() && !welcome.isDirectory())
return welcome;
}
return null;
}
/* ------------------------------------------------------------ */
/*
* @see org.eclipse.jetty.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
*/
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
if (baseRequest.isHandled())
return;
boolean skipContentBody = false;
if (!HttpMethod.GET.is(request.getMethod())) {
if (!HttpMethod.HEAD.is(request.getMethod())) {
//try another handler
super.handle(target, baseRequest, request, response);
return;
}
skipContentBody = true;
}
final String resourcePath = this.getResourcePath(request);
Resource resource = getResource(request);
if (resource == null || !resource.exists()) {
if (this.isCMSPath(request.getPathInfo())) {
baseRequest.setHandled(false);
return;
}
if (target.endsWith("/jetty-dir.css")) {
resource = getStylesheet();
if (resource == null)
return;
response.setContentType("text/css");
} else {
//no resource - try other handlers
super.handle(target, baseRequest, request, response);
// ServletUtils.notFound404(response);
return;
}
}
if (resource.isDirectory()) {
if (this.isCMSPath(resourcePath)) {
baseRequest.setHandled(false);
return;
}
if (!request.getPathInfo().endsWith(URIUtil.SLASH)) {
if (!this.isServletPath(resourcePath.concat(URIUtil.SLASH))) {
response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(request.getRequestURI(), URIUtil.SLASH)));
baseRequest.setHandled(true);
} else {
// is a servlet
baseRequest.setHandled(false);
}
return;
}
final Resource welcome = getWelcome(resource);
if (welcome != null && welcome.exists()) {
//resource = welcome;
// does not serve "/", but send redirect
response.sendRedirect(response.encodeRedirectURL(URIUtil.addPaths(resourcePath, welcome.getFile().getName())));
baseRequest.setHandled(true);
return;
} else {
if (!this.isServletPath(resourcePath)) {
this.doDirectory(request, response, resource);
baseRequest.setHandled(true);
} else {
// is a servlet
baseRequest.setHandled(false);
}
return;
}
}
final boolean is_servlet_file = this.isServletExtension(resourcePath);
if (is_servlet_file) {
baseRequest.setHandled(false);
return;
}
// We are going to serve something
baseRequest.setHandled(true);
// set some headers
String etag = null;
if (_etags) {
// simple handling of only a single etag
final String ifnm = request.getHeader(HttpHeader.IF_NONE_MATCH.asString());
etag = resource.getWeakETag();
if (ifnm != null && resource != null && ifnm.equals(etag)) {
response.setStatus(HttpStatus.NOT_MODIFIED_304);
baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG, etag);
return;
}
}
final long last_modified = is_servlet_file ? DateUtils.now().getTime() : resource.lastModified();
if (!is_servlet_file && last_modified > 0) {
long if_modified = request.getDateHeader(HttpHeader.IF_MODIFIED_SINCE.asString());
if (if_modified > 0 && last_modified / 1000 <= if_modified / 1000) {
response.setStatus(HttpStatus.NOT_MODIFIED_304);
return;
}
}
final String mimeType = this.getMimeType(resource, request);
// set the headers
this.doResponseHeaders(response, resource, mimeType != null ? mimeType : null);
response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(), last_modified);
if (_etags)
baseRequest.getResponse().getHttpFields().put(HttpHeader.ETAG, etag);
if (skipContentBody)
return;
// Send the content
OutputStream out = null;
try {
out = response.getOutputStream();
} catch (IllegalStateException e) {
out = new WriterOutputStream(response.getWriter());
}
// Write content normally
resource.writeTo(out, 0, resource.length());
}
/* ------------------------------------------------------------ */
protected void doDirectory(HttpServletRequest request, HttpServletResponse response, Resource resource)
throws IOException {
if (_directory) {
String listing = resource.getListHTML(request.getRequestURI(), request.getPathInfo().lastIndexOf("/") > 0);
response.setContentType("text/html; charset=UTF-8");
response.getWriter().println(listing);
} else
response.sendError(HttpStatus.FORBIDDEN_403);
}
/* ------------------------------------------------------------ */
/**
* Set the response headers.
* This method is called to set the response headers such as content type and content length.
* May be extended to add additional headers.
*
* @param response
* @param resource
* @param mimeType
*/
protected void doResponseHeaders(HttpServletResponse response, Resource resource, String mimeType) {
if (mimeType != null)
response.setContentType(mimeType);
long length = resource.length();
if (response instanceof Response) {
HttpFields fields = ((Response) response).getHttpFields();
if (length > 0)
fields.putLongField(HttpHeader.CONTENT_LENGTH, length);
if (_cacheControl != null)
fields.put(HttpHeader.CACHE_CONTROL, _cacheControl);
} else {
if (length > 0)
response.setHeader(HttpHeader.CONTENT_LENGTH.asString(), Long.toString(length));
if (_cacheControl != null)
response.setHeader(HttpHeader.CACHE_CONTROL.asString(), _cacheControl.toString());
}
}
// ------------------------------------------------------------------------
// p r i v a t e
// ------------------------------------------------------------------------
protected String getResourcePath(final HttpServletRequest request) throws MalformedURLException {
return ServletUtils.getResourcePath(request);
}
private Logger getLogger() {
return LoggingUtils.getLogger(this);
}
private String getMimeType(final Resource resource, final HttpServletRequest request) {
String mime = _mimeTypes.getMimeByExtension(resource.toString());
if (mime == null)
mime = _mimeTypes.getMimeByExtension(request.getPathInfo());
return mime;
}
private boolean isServletExtension(final String target) {
if (null != _server) {
final String ext = PathUtils.getFilenameExtension(target, true);
return _server.getServletExtensions().contains(ext);
}
return false;
}
private boolean isServletPath(final String target) {
if (null != _server) {
final String path = this.stripPath(target);
return _server.getServletPaths().contains(path);
}
return false;
}
private boolean isCMSPath(final String target) {
return SmartlyHttp.getCMSPaths().contains(target);
}
private static String stripPath(String path) {
if (path == null)
return null;
int semi = path.indexOf(';');
if (semi < 0)
return path;
return path.substring(0, semi);
}
}