
org.mortbay.jetty.servlet.ServletHandler Maven / Gradle / Ivy
// ========================================================================
// Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================
package org.mortbay.jetty.servlet;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.UnavailableException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.mortbay.jetty.Dispatcher;
import org.mortbay.jetty.EofException;
import org.mortbay.jetty.HttpConnection;
import org.mortbay.jetty.HttpException;
import org.mortbay.jetty.Request;
import org.mortbay.jetty.RetryRequest;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.handler.AbstractHandler;
import org.mortbay.jetty.handler.ContextHandler;
import org.mortbay.log.Log;
import org.mortbay.util.LazyList;
import org.mortbay.util.MultiException;
import org.mortbay.util.MultiMap;
import org.mortbay.util.URIUtil;
/* --------------------------------------------------------------------- */
/** Servlet HttpHandler.
* This handler maps requests to servlets that implement the
* javax.servlet.http.HttpServlet API.
*
* This handler does not implement the full J2EE features and is intended to
* be used when a full web application is not required. Specifically filters
* and request wrapping are not supported.
*
* Unless run as part of a {@link Context} or derivative, the {@link #initialize()}
* method must be called manually after start().
*
* @see org.mortbay.jetty.webapp.WebAppContext
* @author Greg Wilkins
*/
public class ServletHandler extends AbstractHandler
{
/* ------------------------------------------------------------ */
public static final String __DEFAULT_SERVLET="default";
public static final String __J_S_CONTEXT_TEMPDIR="javax.servlet.context.tempdir";
public static final String __J_S_ERROR_EXCEPTION="javax.servlet.error.exception";
public static final String __J_S_ERROR_EXCEPTION_TYPE="javax.servlet.error.exception_type";
public static final String __J_S_ERROR_MESSAGE="javax.servlet.error.message";
public static final String __J_S_ERROR_REQUEST_URI="javax.servlet.error.request_uri";
public static final String __J_S_ERROR_SERVLET_NAME="javax.servlet.error.servlet_name";
public static final String __J_S_ERROR_STATUS_CODE="javax.servlet.error.status_code";
/* ------------------------------------------------------------ */
private ContextHandler _contextHandler;
private ContextHandler.SContext _servletContext;
private FilterHolder[] _filters;
private FilterMapping[] _filterMappings;
private boolean _filterChainsCached=true;
private int _maxFilterChainsCacheSize=1000;
private boolean _startWithUnavailable=true;
private ServletHolder[] _servlets;
private ServletMapping[] _servletMappings;
private transient Map _filterNameMap= new HashMap();
private transient List _filterPathMappings;
private transient MultiMap _filterNameMappings;
private transient Map _servletNameMap=new HashMap();
private transient PathMap _servletPathMap;
protected transient ConcurrentHashMap _chainCache[];
/* ------------------------------------------------------------ */
/** Constructor.
*/
public ServletHandler()
{
}
/* ------------------------------------------------------------ */
/*
* @see org.mortbay.jetty.handler.AbstractHandler#setServer(org.mortbay.jetty.Server)
*/
public void setServer(Server server)
{
if (getServer()!=null && getServer()!=server)
{
getServer().getContainer().update(this, _filters, null, "filter",true);
getServer().getContainer().update(this, _filterMappings, null, "filterMapping",true);
getServer().getContainer().update(this, _servlets, null, "servlet",true);
getServer().getContainer().update(this, _servletMappings, null, "servletMapping",true);
}
if (server!=null && getServer()!=server)
{
server.getContainer().update(this, null, _filters, "filter",true);
server.getContainer().update(this, null, _filterMappings, "filterMapping",true);
server.getContainer().update(this, null, _servlets, "servlet",true);
server.getContainer().update(this, null, _servletMappings, "servletMapping",true);
}
super.setServer(server);
}
/* ----------------------------------------------------------------- */
protected synchronized void doStart()
throws Exception
{
_servletContext=ContextHandler.getCurrentContext();
_contextHandler=_servletContext==null?null:_servletContext.getContextHandler();
updateNameMappings();
updateMappings();
if(_filterChainsCached)
_chainCache= new ConcurrentHashMap[]{null,new ConcurrentHashMap(),new ConcurrentHashMap(),null,new ConcurrentHashMap(),null,null,null,new ConcurrentHashMap()};
super.doStart();
if (_contextHandler==null || !(_contextHandler instanceof Context))
initialize();
}
/* ----------------------------------------------------------------- */
protected synchronized void doStop()
throws Exception
{
super.doStop();
// Stop filters
if (_filters!=null)
{
for (int i=_filters.length; i-->0;)
{
try { _filters[i].stop(); }catch(Exception e){Log.warn(Log.EXCEPTION,e);}
}
}
// Stop servlets
if (_servlets!=null)
{
for (int i=_servlets.length; i-->0;)
{
try { _servlets[i].stop(); }catch(Exception e){Log.warn(Log.EXCEPTION,e);}
}
}
_filterPathMappings=null;
_filterNameMappings=null;
_servletPathMap=null;
_chainCache=null;
}
/* ------------------------------------------------------------ */
/**
* @return Returns the contextLog.
*/
public Object getContextLog()
{
return null;
}
/* ------------------------------------------------------------ */
/**
* @return Returns the filterMappings.
*/
public FilterMapping[] getFilterMappings()
{
return _filterMappings;
}
/* ------------------------------------------------------------ */
/** Get Filters.
* @return Array of defined servlets
*/
public FilterHolder[] getFilters()
{
return _filters;
}
/* ------------------------------------------------------------ */
/** ServletHolder matching path.
* @param pathInContext Path within _context.
* @return PathMap Entries pathspec to ServletHolder
*/
public PathMap.Entry getHolderEntry(String pathInContext)
{
if (_servletPathMap==null)
return null;
return _servletPathMap.getMatch(pathInContext);
}
/* ------------------------------------------------------------ */
/**
* @return A {@link RequestDispatcher dispatcher} wrapping the resource at uriInContext
,
* or null
if the specified uri cannot be dispatched to.
*/
public RequestDispatcher getRequestDispatcher(String uriInContext)
{
if (uriInContext == null)
return null;
if (!uriInContext.startsWith("/"))
return null;
try
{
String query=null;
int q=0;
if ((q=uriInContext.indexOf('?'))>0)
{
query=uriInContext.substring(q+1);
uriInContext=uriInContext.substring(0,q);
}
if ((q=uriInContext.indexOf(';'))>0)
uriInContext=uriInContext.substring(0,q);
String pathInContext=URIUtil.canonicalPath(URIUtil.decodePath(uriInContext));
String uri=URIUtil.addPaths(_contextHandler.getContextPath(), uriInContext);
return new Dispatcher(_contextHandler, uri, pathInContext, query);
}
catch(Exception e)
{
Log.ignore(e);
}
return null;
}
/* ------------------------------------------------------------ */
public ServletContext getServletContext()
{
return _servletContext;
}
/* ------------------------------------------------------------ */
/**
* @return Returns the servletMappings.
*/
public ServletMapping[] getServletMappings()
{
return _servletMappings;
}
/* ------------------------------------------------------------ */
/** Get Servlets.
* @return Array of defined servlets
*/
public ServletHolder[] getServlets()
{
return _servlets;
}
/* ------------------------------------------------------------ */
public ServletHolder getServlet(String name)
{
return (ServletHolder)_servletNameMap.get(name);
}
/* ------------------------------------------------------------ */
/*
* @see org.mortbay.jetty.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
*/
public void handle(String target, HttpServletRequest request,HttpServletResponse response, int type)
throws IOException, ServletException
{
if (!isStarted())
return;
// Get the base requests
final Request base_request=(request instanceof Request)?((Request)request):HttpConnection.getCurrentConnection().getRequest();
final String old_servlet_name=base_request.getServletName();
final String old_servlet_path=base_request.getServletPath();
final String old_path_info=base_request.getPathInfo();
final Map old_role_map=base_request.getRoleMap();
try
{
ServletHolder servlet_holder=null;
FilterChain chain=null;
// find the servlet
if (target.startsWith("/"))
{
// Look for the servlet by path
PathMap.Entry entry=getHolderEntry(target);
if (entry!=null)
{
servlet_holder=(ServletHolder)entry.getValue();
base_request.setServletName(servlet_holder.getName());
base_request.setRoleMap(servlet_holder.getRoleMap());
if(Log.isDebugEnabled())Log.debug("servlet="+servlet_holder);
String servlet_path_spec=(String)entry.getKey();
String servlet_path=entry.getMapped()!=null?entry.getMapped():PathMap.pathMatch(servlet_path_spec,target);
String path_info=PathMap.pathInfo(servlet_path_spec,target);
if (type==INCLUDE)
{
base_request.setAttribute(Dispatcher.__INCLUDE_SERVLET_PATH,servlet_path);
base_request.setAttribute(Dispatcher.__INCLUDE_PATH_INFO, path_info);
}
else
{
base_request.setServletPath(servlet_path);
base_request.setPathInfo(path_info);
}
if (servlet_holder!=null && _filterMappings!=null && _filterMappings.length>0)
chain=getFilterChain(type, target, servlet_holder,request.isInitial());
}
}
else
{
// look for a servlet by name!
servlet_holder=(ServletHolder)_servletNameMap.get(target);
if (servlet_holder!=null && _filterMappings!=null && _filterMappings.length>0)
{
base_request.setServletName(servlet_holder.getName());
chain=getFilterChain(type, null,servlet_holder,request.isInitial());
}
}
if (Log.isDebugEnabled())
{
Log.debug("chain="+chain);
Log.debug("servlet holder="+servlet_holder);
}
// Do the filter/handling thang
if (servlet_holder!=null)
{
base_request.setHandled(true);
if (chain!=null)
chain.doFilter(request, response);
else
servlet_holder.handle(request,response);
}
else
notFound(request, response);
}
catch(RetryRequest e)
{
throw e;
}
catch(EofException e)
{
throw e;
}
catch(Exception e)
{
if (type!=REQUEST)
{
if (e instanceof IOException)
throw (IOException)e;
if (e instanceof RuntimeException)
throw (RuntimeException)e;
if (e instanceof ServletException)
throw (ServletException)e;
}
// unwrap cause
Throwable th=e;
if (th instanceof UnavailableException)
{
Log.debug(th);
}
else if (th instanceof ServletException)
{
Log.debug(th);
Throwable cause=((ServletException)th).getRootCause();
if (cause!=th && cause!=null)
th=cause;
}
// hnndle or log exception
if (th instanceof RetryRequest)
{
base_request.setHandled(false);
throw (RetryRequest)th;
}
else if (th instanceof HttpException)
throw (HttpException)th;
else if (Log.isDebugEnabled())
{
Log.warn(request.getRequestURI(), th);
Log.debug(request.toString());
}
else if (th instanceof IOException || th instanceof UnavailableException)
{
Log.warn(request.getRequestURI()+": "+th);
}
else
{
Log.warn(request.getRequestURI(),th);
}
// TODO httpResponse.getHttpConnection().forceClose();
if (!response.isCommitted())
{
request.setAttribute(ServletHandler.__J_S_ERROR_EXCEPTION_TYPE,th.getClass());
request.setAttribute(ServletHandler.__J_S_ERROR_EXCEPTION,th);
if (th instanceof UnavailableException)
{
UnavailableException ue = (UnavailableException)th;
if (ue.isPermanent())
response.sendError(HttpServletResponse.SC_NOT_FOUND,th.getMessage());
else
response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE,th.getMessage());
}
else
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,th.getMessage());
}
else
if(Log.isDebugEnabled())Log.debug("Response already committed for handling "+th);
}
catch(Error e)
{
if (type!=REQUEST)
throw e;
Log.warn("Error for "+request.getRequestURI(),e);
if(Log.isDebugEnabled())Log.debug(request.toString());
// TODO httpResponse.getHttpConnection().forceClose();
if (!response.isCommitted())
{
request.setAttribute(ServletHandler.__J_S_ERROR_EXCEPTION_TYPE,e.getClass());
request.setAttribute(ServletHandler.__J_S_ERROR_EXCEPTION,e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,e.getMessage());
}
else
if(Log.isDebugEnabled())Log.debug("Response already committed for handling ",e);
}
finally
{
base_request.setServletName(old_servlet_name);
base_request.setRoleMap(old_role_map);
if (type!=INCLUDE)
{
base_request.setServletPath(old_servlet_path);
base_request.setPathInfo(old_path_info);
}
}
return;
}
/* ------------------------------------------------------------ */
private FilterChain getFilterChain(int requestType, String pathInContext, ServletHolder servletHolder, boolean initial)
{
String key=pathInContext==null?servletHolder.getName():pathInContext;
if (!initial)
key="!"+key;
if (_filterChainsCached && _chainCache!=null)
{
FilterChain chain = (FilterChain)_chainCache[requestType].get(key);
if (chain!=null)
return chain;
}
// Build list of filters
Object filters= null;
// Path filters
if (pathInContext!=null && _filterPathMappings!=null)
{
for (int i= 0; i < _filterPathMappings.size(); i++)
{
FilterMapping mapping = (FilterMapping)_filterPathMappings.get(i);
if (mapping.appliesTo(pathInContext, requestType,initial))
filters= LazyList.add(filters, mapping.getFilterHolder());
}
}
// Servlet name filters
if (servletHolder != null && _filterNameMappings!=null && _filterNameMappings.size() > 0)
{
// Servlet name filters
if (_filterNameMappings.size() > 0)
{
Object o= _filterNameMappings.get(servletHolder.getName());
for (int i=0; i 0)
chain= new CachedChain(filters, servletHolder);
if (_maxFilterChainsCacheSize>0 && _chainCache[requestType].size()>_maxFilterChainsCacheSize)
_chainCache[requestType].clear();
_chainCache[requestType].put(key,chain);
}
else if (LazyList.size(filters) > 0)
chain = new Chain(filters, servletHolder);
return chain;
}
/* ------------------------------------------------------------ */
/**
* @return Returns the initializeAtStart.
* @deprecated
*/
public boolean isInitializeAtStart()
{
return false;
}
/* ------------------------------------------------------------ */
/**
* @param initializeAtStart The initializeAtStart to set.
* @deprecated
*/
public void setInitializeAtStart(boolean initializeAtStart)
{
}
/* ------------------------------------------------------------ */
/**
* @return true if the handler is started and there are no unavailable servlets
*/
public boolean isAvailable()
{
if (!isStarted())
return false;
ServletHolder[] holders = getServlets();
for (int i=0;i0)
{
_filterHolder=(FilterHolder)LazyList.get(filters, 0);
filters=LazyList.remove(filters,0);
_next=new CachedChain(filters,servletHolder);
}
else
_servletHolder=servletHolder;
}
/* ------------------------------------------------------------ */
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException
{
// pass to next filter
if (_filterHolder!=null)
{
if (Log.isDebugEnabled())
Log.debug("call filter " + _filterHolder);
Filter filter= _filterHolder.getFilter();
filter.doFilter(request, response, _next);
return;
}
// Call servlet
if (_servletHolder != null)
{
if (Log.isDebugEnabled())
Log.debug("call servlet " + _servletHolder);
_servletHolder.handle(request, response);
}
else // Not found
notFound((HttpServletRequest)request, (HttpServletResponse)response);
}
public String toString()
{
if (_filterHolder!=null)
return _filterHolder+"->"+_next.toString();
if (_servletHolder!=null)
return _servletHolder.toString();
return "null";
}
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private class Chain implements FilterChain
{
int _filter= 0;
Object _chain;
ServletHolder _servletHolder;
/* ------------------------------------------------------------ */
Chain(Object filters, ServletHolder servletHolder)
{
_chain= filters;
_servletHolder= servletHolder;
}
/* ------------------------------------------------------------ */
public void doFilter(ServletRequest request, ServletResponse response)
throws IOException, ServletException
{
if (Log.isDebugEnabled()) Log.debug("doFilter " + _filter);
// pass to next filter
if (_filter < LazyList.size(_chain))
{
FilterHolder holder= (FilterHolder)LazyList.get(_chain, _filter++);
if (Log.isDebugEnabled()) Log.debug("call filter " + holder);
Filter filter= holder.getFilter();
filter.doFilter(request, response, this);
return;
}
// Call servlet
if (_servletHolder != null)
{
if (Log.isDebugEnabled()) Log.debug("call servlet " + _servletHolder);
_servletHolder.handle(request, response);
}
else // Not found
notFound((HttpServletRequest)request, (HttpServletResponse)response);
}
/* ------------------------------------------------------------ */
public String toString()
{
StringBuilder b = new StringBuilder();
for (int i=0; i");
}
b.append(_servletHolder);
return b.toString();
}
}
/* ------------------------------------------------------------ */
/**
* @return The maximum entries in a filter chain cache.
*/
public int getMaxFilterChainsCacheSize()
{
return _maxFilterChainsCacheSize;
}
/* ------------------------------------------------------------ */
/** Set the maximum filter chain cache size.
* Filter chains are cached if {@link #isFilterChainsCached()} is true. If the max cache size
* is greater than zero, then the cache is flushed whenever it grows to be this size.
*
* @param maxFilterChainsCacheSize the maximum number of entries in a filter chain cache.
*/
public void setMaxFilterChainsCacheSize(int maxFilterChainsCacheSize)
{
_maxFilterChainsCacheSize = maxFilterChainsCacheSize;
}
/**
* Customize a servlet.
*
* Called before the servlet goes into service.
* Subclasses of ServletHandler should override
* this method.
*
* @param servlet
* @return
* @throws Exception
*/
public Servlet customizeServlet (Servlet servlet)
throws Exception
{
return servlet;
}
public Servlet customizeServletDestroy (Servlet servlet)
throws Exception
{
return servlet;
}
/**
* Customize a Filter.
*
* Called before the Filter goes into service.
* Subclasses of ServletHandler should override
* this method.
*
* @param filter
* @return
* @throws Exception
*/
public Filter customizeFilter (Filter filter)
throws Exception
{
return filter;
}
public Filter customizeFilterDestroy (Filter filter)
throws Exception
{
return filter;
}
}