// ========================================================================
// Copyright (c) 2000 Mort Bay Consulting (Australia) Pty. Ltd.
// $Id: HttpContext.java,v 1.17.2.21 2003/07/11 00:55:12 jules_gosnell Exp $
// ========================================================================
package org.mortbay.http;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import org.mortbay.http.SecurityConstraint.Authenticator;
import org.mortbay.util.CachedResource;
import org.mortbay.util.Code;
import org.mortbay.util.IO;
import org.mortbay.util.LifeCycle;
import org.mortbay.util.Log;
import org.mortbay.util.MultiException;
import org.mortbay.util.Resource;
import org.mortbay.util.StringUtil;
import org.mortbay.util.URI;
/* ------------------------------------------------------------ */
/** Context for a collection of HttpHandlers.
* HTTP Context provides an ordered container for HttpHandlers
* that share the same path prefix, filebase, resourcebase and/or
* classpath.
*
* A HttpContext is analagous to a ServletContext in the
* Servlet API, except that it may contain other types of handler
* other than servlets.
*
* A ClassLoader is created for the context and it uses
* Thread.currentThread().getContextClassLoader(); as it's parent loader.
* The class loader is initialized during start(), when a derived
* context calls initClassLoader() or on the first call to loadClass()
*
*
* Note. that order is important when configuring a HttpContext.
* For example, if resource serving is enabled before servlets, then resources
* take priority.
*
* @see HttpServer
* @see HttpHandler
* @see org.mortbay.jetty.servlet.ServletHttpContext
* @version $Id: HttpContext.java,v 1.17.2.21 2003/07/11 00:55:12 jules_gosnell Exp $
* @author Greg Wilkins (gregw)
*/
public class HttpContext implements LifeCycle,
Serializable
{
/* ------------------------------------------------------------ */
/** File class path attribute.
* If this name is set as a context init parameter, then the attribute
* name given will be used to set the file classpath for the context as a
* context attribute.
*/
public final static String __fileClassPathAttr=
"org.mortbay.http.HttpContext.FileClassPathAttribute";
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private final static Map __dftMimeMap = new HashMap();
private final static Map __encodings = new HashMap();
static
{
ResourceBundle mime = ResourceBundle.getBundle("org/mortbay/http/mime");
Enumeration i = mime.getKeys();
while(i.hasMoreElements())
{
String ext = (String)i.nextElement();
__dftMimeMap.put(ext,mime.getString(ext));
}
ResourceBundle encoding = ResourceBundle.getBundle("org/mortbay/http/encoding");
i = encoding.getKeys();
while(i.hasMoreElements())
{
String type = (String)i.nextElement();
__encodings.put(type,encoding.getString(type));
}
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
// These attributes are serialized by WebApplicationContext, which needs
// to be updated if you add to these
private String _contextPath;
private List _vhosts=new ArrayList(2);
private List _hosts=new ArrayList(2);
private List _handlers=new ArrayList(3);
private Map _attributes = new HashMap(3);
private boolean _redirectNullPath=false;
private int _maxCachedFileSize =100*1024;
private int _maxCacheSize =1024*1024;
private boolean _statsOn=false;
private PermissionCollection _permissions;
private boolean _classLoaderJava2Compliant;
/* ------------------------------------------------------------ */
private String _contextName;
private String _classPath;
private Map _initParams = new HashMap(11);
private Map _errorPages;
private UserRealm _userRealm;
private String _realmName;
private PathMap _constraintMap=new PathMap();
private Authenticator _authenticator;
private RequestLog _requestLog;
private Resource _resourceBase;
private Map _mimeMap;
private Map _encodingMap;
private String[] _welcomes=
{
"welcome.html",
"index.html",
"index.htm",
"index.jsp"
};
/* ------------------------------------------------------------ */
private transient boolean _started;
private transient ClassLoader _parent;
private transient ClassLoader _loader;
private transient HttpServer _httpServer;
private transient File _tmpDir;
private transient Map _cache=new HashMap();
private transient int _cacheSize;
private transient CachedMetaData _mostRecentlyUsed;
private transient CachedMetaData _leastRecentlyUsed;
private transient HttpHandler[] _handlersArray;
private transient String[] _vhostsArray;
/* ------------------------------------------------------------ */
transient Object _statsLock=new Object[0];
transient long _statsStartedAt;
transient int _requests;
transient int _requestsActive;
transient int _requestsActiveMax;
transient int _responses1xx; // Informal
transient int _responses2xx; // Success
transient int _responses3xx; // Redirection
transient int _responses4xx; // Client Error
transient int _responses5xx; // Server Error
/* ------------------------------------------------------------ */
/** Constructor.
*/
public HttpContext()
{}
/* ------------------------------------------------------------ */
/** Constructor.
* @param httpServer
* @param contextPathSpec
*/
public HttpContext(HttpServer httpServer,String contextPathSpec)
{
this();
setHttpServer(httpServer);
setContextPath(contextPathSpec);
}
/* ------------------------------------------------------------ */
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException
{
in.defaultReadObject();
_statsLock=new Object[0];
_cache=new HashMap();
getHandlers();
for (int i=0;i<_handlersArray.length;i++)
_handlersArray[i].initialize(this);
}
/* ------------------------------------------------------------ */
/** Get the ThreadLocal HttpConnection.
* Get the HttpConnection for current thread, if any. This method is
* not static in order to control access.
* @return HttpConnection for this thread.
*/
public HttpConnection getHttpConnection()
{
return HttpConnection.getHttpConnection();
}
/* ------------------------------------------------------------ */
void setHttpServer(HttpServer httpServer)
{
_httpServer=httpServer;
_contextName=null;
}
/* ------------------------------------------------------------ */
public HttpServer getHttpServer()
{
return _httpServer;
}
/* ------------------------------------------------------------ */
public static String canonicalContextPathSpec(String contextPathSpec)
{
// check context path
if (contextPathSpec==null ||
contextPathSpec.indexOf(',')>=0 ||
contextPathSpec.startsWith("*"))
throw new IllegalArgumentException ("Illegal context spec:"+contextPathSpec);
if(!contextPathSpec.startsWith("/"))
contextPathSpec='/'+contextPathSpec;
if (contextPathSpec.length()>1)
{
if (contextPathSpec.endsWith("/"))
contextPathSpec+="*";
else if (!contextPathSpec.endsWith("/*"))
contextPathSpec+="/*";
}
return contextPathSpec;
}
/* ------------------------------------------------------------ */
public void setContextPath(String contextPathSpec)
{
if (_httpServer!=null)
_httpServer.removeMappings(this);
contextPathSpec=canonicalContextPathSpec(contextPathSpec);
if (contextPathSpec.length()>1)
_contextPath=contextPathSpec.substring(0,contextPathSpec.length()-2);
else
_contextPath="/";
_contextName=null;
if (_httpServer!=null)
_httpServer.addMappings(this);
}
/* ------------------------------------------------------------ */
/**
* @return The context prefix
*/
public String getContextPath()
{
return _contextPath;
}
/* ------------------------------------------------------------ */
/** Add a virtual host alias to this context.
* @see #setVirtualHosts
* @param hostname A hostname. A null host name means any hostname is
* acceptable. Host names may String representation of IP addresses.
*/
public void addVirtualHost(String hostname)
{
// Note that null hosts are also added.
if (!_vhosts.contains(hostname))
{
_vhosts.add(hostname);
_contextName=null;
if (_httpServer!=null)
{
if (_vhosts.size()==1)
_httpServer.removeMapping(null,this);
_httpServer.addMapping(hostname,this);
}
_vhostsArray=null;
}
}
/* ------------------------------------------------------------ */
/** remove a virtual host alias to this context.
* @see #setVirtualHosts
* @param hostname A hostname. A null host name means any hostname is
* acceptable. Host names may String representation of IP addresses.
*/
public void removeVirtualHost(String hostname)
{
// Note that null hosts are also added.
if (_vhosts.remove(hostname))
{
_contextName=null;
if (_httpServer!=null)
{
_httpServer.removeMapping(hostname,this);
if (_vhosts.size()==0)
_httpServer.addMapping(null,this);
}
_vhostsArray=null;
}
}
/* ------------------------------------------------------------ */
/** Set the virtual hosts for the context.
* Only requests that have a matching host header or fully qualified
* URL will be passed to that context with a virtual host name.
* A context with no virtual host names or a null virtual host name is
* available to all requests that are not served by a context with a
* matching virtual host name.
* @param hosts Array of virtual hosts that this context responds to. A
* null host name or null/empty array means any hostname is acceptable.
* Host names may String representation of IP addresses.
*/
public void setVirtualHosts(String[] hosts)
{
List old = new ArrayList(_vhosts);
for (int i=0;i0 && len<_maxCachedFileSize && len<_maxCacheSize)
{
int needed=_maxCacheSize-(int)len;
while(_cacheSize>needed)
_leastRecentlyUsed.invalidate();
cached=resource.cache();
if (Code.verbose()) Code.debug("CACHED: ",resource);
new CachedMetaData(cached,pathInContext);
return cached;
}
}
}
// Non cached response
new ResourceMetaData(resource);
return resource;
}
/* ------------------------------------------------------------ */
public String getWelcomeFile(Resource resource)
throws IOException
{
if (!resource.isDirectory())
return null;
for (int i=0;i<_welcomes.length;i++)
{
Resource welcome=resource.addPath(_welcomes[i]);
if (welcome.exists())
return _welcomes[i];
}
return null;
}
/* ------------------------------------------------------------ */
public synchronized Map getMimeMap()
{
return _mimeMap;
}
/* ------------------------------------------------------------ */
/**
* Also sets the org.mortbay.http.mimeMap context attribute
* @param mimeMap
*/
public void setMimeMap(Map mimeMap)
{
_mimeMap = mimeMap;
}
/* ------------------------------------------------------------ */
/** Get the MIME type by filename extension.
* @param filename A file name
* @return MIME type matching the longest dot extension of the
* file name.
*/
public String getMimeByExtension(String filename)
{
String type=null;
if (filename!=null)
{
int i=-1;
while(type==null)
{
i=filename.indexOf(".",i+1);
if (i<0 || i>=filename.length())
break;
String ext=StringUtil.asciiToLowerCase(filename.substring(i+1));
if (_mimeMap!=null)
type = (String)_mimeMap.get(ext);
if (type==null)
type=(String)__dftMimeMap.get(ext);
}
}
if (type==null)
{
if (_mimeMap!=null)
type=(String)_mimeMap.get("*");
if (type==null)
type=(String)__dftMimeMap.get("*");
}
return type;
}
/* ------------------------------------------------------------ */
/** Set a mime mapping
* @param extension
* @param type
*/
public void setMimeMapping(String extension,String type)
{
if (_mimeMap==null)
_mimeMap=new HashMap();
_mimeMap.put(extension,type);
}
/* ------------------------------------------------------------ */
/** Get the context classpath.
* This method only returns the paths that have been set for this
* context and does not include any paths from a parent or the
* system classloader.
* Note that this may not be a legal javac classpath.
* @return a comma or ';' separated list of class
* resources. These may be jar files, directories or URLs to jars
* or directories.
* @see #getFileClassPath()
*/
public String getClassPath()
{
return _classPath;
}
/* ------------------------------------------------------------ */
/** Get the file classpath of the context.
* This method makes a best effort to return a complete file
* classpath for the context. The default implementation returns
*
* ((ContextLoader)getClassLoader()).getFileClassPath()+
* System.getProperty("path.separator")+
* System.getProperty("java.class.path");
*
* The default implementation requires the classloader to be
* initialized before it is called. It will not include any
* classpaths used by a non-system parent classloader.
*
* The main user of this method is the start() method. If a JSP
* servlet is detected, the string returned from this method is
* used as the default value for the "classpath" init parameter.
*
* Derivations may replace this method with a more accurate or
* specialized version.
* @return Path of files and directories for loading classes.
* @exception IllegalStateException HttpContext.initClassLoader
* has not been called.
*/
public String getFileClassPath()
throws IllegalStateException
{
ClassLoader loader = getClassLoader();
if (loader==null)
throw new IllegalStateException("Context classloader not initialized");
String fileClassPath =
((loader instanceof ContextLoader)
? ((ContextLoader)loader).getFileClassPath()
: getClassPath())+
System.getProperty("path.separator")+
System.getProperty("java.class.path");
return fileClassPath;
}
/* ------------------------------------------------------------ */
/** Sets the class path for the context.
* A class path is only required for a context if it uses classes
* that are not in the system class path.
* @param classPath a comma or ';' separated list of class
* resources. These may be jar files, directories or URLs to jars
* or directories.
*/
public void setClassPath(String classPath)
{
_classPath=classPath;
if (isStarted())
Code.warning("classpath set while started");
}
/* ------------------------------------------------------------ */
/** Sets the class path for the context from the jar and zip files found
* in the specified resource.
* @param lib the resource that contains the jar and/or zip files.
* @param append true if the classpath entries are to be appended to any
* existing classpath, or false if they replace the existing classpath.
* @see #setClassPath(String)
*/
public void setClassPaths(Resource lib, boolean append)
{
if (isStarted())
Code.warning("classpaths set while started");
if (lib.exists() && lib.isDirectory())
{
StringBuffer classPath=new StringBuffer();
if (append && this.getClassPath()!=null)
classPath.append(_classPath);
String[] files=lib.list();
for (int f=0;files!=null && f0?",":"");
classPath.append(fn.toString());
}
}
catch (Exception ex)
{
Code.warning(ex);
}
}
if (classPath.length()>0)
_classPath=classPath.toString();
}
}
/* ------------------------------------------------------------ */
/** Sets the class path for the context from the jar and zip files found
* in the specified resource.
* @param lib the resource that contains the jar and/or zip files.
* @param append true if the classpath entries are to be appended to any
* existing classpath, or false if they are to be prepended.
* @exception IOException
*/
public void setClassPaths(String lib, boolean append) throws IOException
{
if (_loader!=null)
throw new IllegalStateException("ClassLoader already initialized");
this.setClassPaths(Resource.newResource(lib), append);
}
/* ------------------------------------------------------------ */
/** Get Java2 compliant classloading.
* @return If true, the class loader will conform to the java 2
* specification and delegate all loads to the parent classloader. If
* false, the context classloader only delegate loads for system classes
* or classes that it can't find itself.
*/
public boolean isClassLoaderJava2Compliant()
{
return _classLoaderJava2Compliant;
}
/* ------------------------------------------------------------ */
/** Set Java2 compliant classloading.
* @param compliant If true, the class loader will conform to the java 2
* specification and delegate all loads to the parent classloader. If
* false, the context classloader only delegate loads for system classes
* or classes that it can't find itself.
*/
public void setClassLoaderJava2Compliant(boolean compliant)
{
_classLoaderJava2Compliant = compliant;
if (_loader!=null && (_loader instanceof ContextLoader))
((ContextLoader)_loader).setJava2Compliant(compliant);
}
/* ------------------------------------------------------------ */
/** Set temporary directory for context.
* The javax.servlet.context.tempdir attribute is also set.
* @param dir Writable temporary directory.
*/
public void setTempDirectory(File dir)
{
if (isStarted())
throw new IllegalStateException("Started");
if (dir!=null)
{
try{dir=new File(dir.getCanonicalPath());}
catch (IOException e){Code.warning(e);}
}
if (dir!=null && !dir.exists())
{
dir.mkdir();
dir.deleteOnExit();
}
if (dir!=null && ( !dir.exists() || !dir.isDirectory() || !dir.canWrite()))
throw new IllegalArgumentException("Bad temp directory: "+dir);
_tmpDir=dir;
setAttribute("javax.servlet.context.tempdir",_tmpDir);
}
/* ------------------------------------------------------------ */
/** Get Context temporary directory.
* A tempory directory is generated if it has not been set. The
* "javax.servlet.context.tempdir" attribute is consulted and if
* not set, the host, port and context are used to generate a
* directory within the JVMs temporary directory.
* @return Temporary directory as a File.
*/
public File getTempDirectory()
{
if (_tmpDir!=null)
return _tmpDir;
// Initialize temporary directory
//
// I'm afraid that this is very much black magic.
// but if you can think of better....
Object t = getAttribute("javax.servlet.context.tempdir");
if (t!=null && (t instanceof File))
{
_tmpDir=(File)t;
if (_tmpDir.isDirectory() && _tmpDir.canWrite())
return _tmpDir;
}
if (t!=null && (t instanceof String))
{
try
{
_tmpDir=new File((String)t);
if (_tmpDir.isDirectory() && _tmpDir.canWrite())
{
Code.debug("Converted to File ",_tmpDir," for ",this);
setAttribute("javax.servlet.context.tempdir",_tmpDir);
return _tmpDir;
}
}
catch(Exception e)
{
Code.warning(e);
}
}
// No tempdir set so make one!
try
{
HttpListener httpListener=_httpServer.getListeners()[0];
String vhost = null;
for (int h=0;vhost==null && _vhosts!=null && h<_vhosts.size();h++)
vhost=(String)_vhosts.get(h);
String host=httpListener.getHost();
String temp="Jetty_"+
(host==null?"":host)+
"_"+
httpListener.getPort()+
"_"+
(vhost==null?"":vhost)+
getContextPath();
temp=temp.replace('/','_');
temp=temp.replace('.','_');
temp=temp.replace('\\','_');
_tmpDir=new File(System.getProperty("java.io.tmpdir"),temp);
if (_tmpDir.exists())
{
Code.debug("Delete existing temp dir ",_tmpDir," for ",this);
if (!IO.delete(_tmpDir))
Code.debug("Failed to delete temp dir "+_tmpDir);
if (_tmpDir.exists())
{
String old=_tmpDir.toString();
_tmpDir=File.createTempFile(temp+"_","");
if (_tmpDir.exists())
_tmpDir.delete();
Code.warning("Can't reuse "+old+", using "+_tmpDir);
}
}
_tmpDir.mkdir();
_tmpDir.deleteOnExit();
Code.debug("Created temp dir ",_tmpDir," for ",this);
}
catch(Exception e)
{
_tmpDir=null;
Code.ignore(e);
}
if (_tmpDir==null)
{
try{
// that didn't work, so try something simpler (ish)
_tmpDir=File.createTempFile("JettyContext","");
if (_tmpDir.exists())
_tmpDir.delete();
_tmpDir.mkdir();
_tmpDir.deleteOnExit();
Code.debug("Created temp dir ",_tmpDir," for ",this);
}
catch(IOException e)
{
Code.fail(e);
}
}
setAttribute("javax.servlet.context.tempdir",_tmpDir);
return _tmpDir;
}
/* ------------------------------------------------------------ */
/** Set ClassLoader.
* @param loader The loader to be used by this context.
*/
public synchronized void setClassLoader(ClassLoader loader)
{
if (isStarted())
throw new IllegalStateException("Started");
_loader=loader;
}
/* ------------------------------------------------------------ */
/** Get the classloader.
* If no classloader has been set and the context has been loaded
* normally, then null is returned.
* If no classloader has been set and the context was loaded from
* a classloader, that loader is returned.
* If a classloader has been set and no classpath has been set then
* the set classloader is returned.
* If a classloader and a classpath has been set, then a new
* URLClassloader initialized on the classpath with the set loader as a
* partent is return.
* @return Classloader or null.
*/
public synchronized ClassLoader getClassLoader()
{
return _loader;
}
/* ------------------------------------------------------------ */
/** Set Parent ClassLoader.
* By default the parent loader is the thread context classloader
* of the thread that calls initClassLoader. If setClassLoader is
* called, then the parent is ignored.
* @param loader The class loader to use for the parent loader of
* the context classloader.
*/
public synchronized void setParentClassLoader(ClassLoader loader)
{
if (isStarted())
throw new IllegalStateException("Started");
_parent=loader;
}
/* ------------------------------------------------------------ */
public ClassLoader getParentClassLoader()
{
return _parent;
}
/* ------------------------------------------------------------ */
/** Initialize the context classloader.
* Initialize the context classloader with the current parameters.
* Any attempts to change the classpath after this call will
* result in a IllegalStateException
* @param forceContextLoader If true, a ContextLoader is always if
* no loader has been set.
*/
protected void initClassLoader(boolean forceContextLoader)
throws MalformedURLException, IOException
{
if (_loader==null)
{
// If no parent, then try this threads classes loader as parent
if (_parent==null)
_parent=Thread.currentThread().getContextClassLoader();
// If no parent, then try this classes loader as parent
if (_parent==null)
_parent=this.getClass().getClassLoader();
Code.debug("Init classloader from ",_classPath,
", ",_parent," for ",this);
if (forceContextLoader || _classPath!=null || _permissions!=null)
{
ContextLoader loader=new ContextLoader(this,_classPath,_parent,_permissions);
loader.setJava2Compliant(_classLoaderJava2Compliant);
_loader=loader;
}
else
_loader=_parent;
}
}
/* ------------------------------------------------------------ */
public synchronized Class loadClass(String className)
throws ClassNotFoundException
{
if (_loader==null)
{
try{initClassLoader(false);}
catch(Exception e)
{
Code.warning(e);
return null;
}
}
if (className==null)
return null;
return _loader.loadClass(className);
}
/* ------------------------------------------------------------ */
/** set error page URI.
* @param error A string representing an error code or a
* exception classname
* @param uriInContext
*/
public void setErrorPage(String error,String uriInContext)
{
if (_errorPages==null)
_errorPages=new HashMap(5);
_errorPages.put(error,uriInContext);
}
/* ------------------------------------------------------------ */
/** get error page URI.
* @param error A string representing an error code or a
* exception classname
* @return URI within context
*/
public String getErrorPage(String error)
{
if (_errorPages==null)
return null;
return (String) _errorPages.get(error);
}
/* ------------------------------------------------------------ */
public String removeErrorPage(String error)
{
if (_errorPages==null)
return null;
return (String) _errorPages.remove(error);
}
/* ------------------------------------------------------------ */
/** Set the realm name.
* @param realmName The name to use to retrieve the actual realm
* from the HttpServer
*/
public void setRealmName(String realmName)
{
_realmName=realmName;
}
/* ------------------------------------------------------------ */
public String getRealmName()
{
return _realmName;
}
/* ------------------------------------------------------------ */
/** Set the realm.
*/
public void setRealm(UserRealm realm)
{
_userRealm=realm;
}
/* ------------------------------------------------------------ */
public UserRealm getRealm()
{
return _userRealm;
}
/* ------------------------------------------------------------ */
public Authenticator getAuthenticator()
{
return _authenticator;
}
/* ------------------------------------------------------------ */
public void setAuthenticator(Authenticator authenticator)
{
_authenticator=authenticator;
}
/* ------------------------------------------------------------ */
public void addSecurityConstraint(String pathSpec, SecurityConstraint sc)
{
List scs = (List)_constraintMap.get(pathSpec);
if (scs==null)
{
scs=new ArrayList(2);
_constraintMap.put(pathSpec,scs);
}
scs.add(sc);
Code.debug("added ",sc," at ",pathSpec);
}
/* ------------------------------------------------------------ */
public boolean isAuthConstrained()
{
Iterator i = _constraintMap.values().iterator();
while(i.hasNext())
{
Iterator j= ((ArrayList)i.next()).iterator();
while(j.hasNext())
{
SecurityConstraint sc = (SecurityConstraint)j.next();
if (sc.isAuthenticate())
{
return true;
}
}
}
return false;
}
/* ------------------------------------------------------------ */
public boolean checkSecurityConstraints(String pathInContext,
HttpRequest request,
HttpResponse response)
throws HttpException, IOException
{
UserRealm realm = getRealm();
// Get all path matches
List scss =_constraintMap.getMatches(pathInContext);
if (scss!=null)
{
Code.debug("Security Constraint on ",pathInContext," against ",scss);
// for each path match
matches:
for (int m=0;m0)
{
Object o = request.getHttpConnection().getConnection();
if (o instanceof Socket)
{
Socket s=(Socket)o;
if (!_hosts.contains(s.getLocalAddress()))
{
Code.debug(s.getLocalAddress()," not in ",_hosts);
return false;
}
}
}
// handle stats
if (_statsOn)
{
synchronized(_statsLock)
{
_requests++;
_requestsActive++;
if (_requestsActive>_requestsActiveMax)
_requestsActiveMax=_requestsActive;
}
}
String pathInContext = URI.canonicalPath(request.getPath());
if (pathInContext==null)
{
// Must be a bad request.
throw new HttpException(HttpResponse.__400_Bad_Request);
}
if (_contextPath.length()>1)
pathInContext=pathInContext.substring(_contextPath.length());
if (_redirectNullPath && (pathInContext==null ||
pathInContext.length()==0))
{
StringBuffer buf=request.getRequestURL();
buf.append("/");
String q=request.getQuery();
if (q!=null&&q.length()!=0)
buf.append("?"+q);
response.setField(HttpFields.__Location,
buf.toString());
if (Code.debug())
Code.warning(this+" consumed all of path "+
request.getPath()+
", redirect to "+buf.toString());
response.sendError(302);
return true;
}
String pathParams=null;
int semi = pathInContext.lastIndexOf(';');
if (semi>=0)
{
int pl = pathInContext.length()-semi;
String ep=request.getEncodedPath();
if(';'==ep.charAt(ep.length()-pl))
{
pathParams=pathInContext.substring(semi+1);
pathInContext=pathInContext.substring(0,semi);
}
}
try
{
return handle(pathInContext,pathParams,request,response);
}
finally
{
UserPrincipal user = request.getUserPrincipal();
if (_userRealm!=null)
_userRealm.disassociate(user);
}
}
/* ------------------------------------------------------------ */
/** Handler request.
* Call each HttpHandler until request is handled.
* @param pathInContext Path in context
* @param pathParams Path parameters such as encoded Session ID
* @param request
* @param response
* @return True if the request has been handled.
* @exception HttpException
* @exception IOException
*/
public boolean handle(String pathInContext,
String pathParams,
HttpRequest request,
HttpResponse response)
throws HttpException, IOException
{
// Save the thread context loader
Thread thread = Thread.currentThread();
ClassLoader lastContextLoader=thread.getContextClassLoader();
HttpContext lastHttpContext=response.getHttpContext();
try
{
if (_loader!=null)
thread.setContextClassLoader(_loader);
response.setHttpContext(this);
HttpHandler[] handlers=getHandlers();
for (int k=0;k1?(_vhosts.toString()+":"):"")+_contextPath;
return _contextName;
}
/* ------------------------------------------------------------ */
public String toString()
{
return "HttpContext["+getHttpContextName()+"]";
}
/* ------------------------------------------------------------ */
public String toString(boolean detail)
{
return "HttpContext["+getHttpContextName()+"]" +
(detail?("="+_handlers):"");
}
/* ------------------------------------------------------------ */
public synchronized void start()
throws Exception
{
if (isStarted())
return;
statsReset();
if (_httpServer==null)
throw new IllegalStateException("No server for "+this);
// start the context itself
getMimeMap();
getEncodingMap();
// Setup realm
if (_userRealm==null && _authenticator!=null)
{
_userRealm=_httpServer.getRealm(_realmName);
if (_userRealm==null)
Code.warning("No Realm: "+_realmName);
}
// setup the context loader
initClassLoader(false);
// Set attribute if needed
String attr = getInitParameter(__fileClassPathAttr);
if (attr!=null && attr.length()>0)
setAttribute(attr,getFileClassPath());
// Start the handlers
Thread thread = Thread.currentThread();
ClassLoader lastContextLoader=thread.getContextClassLoader();
try
{
if (_loader!=null)
thread.setContextClassLoader(_loader);
if (_requestLog!=null)
_requestLog.start();
startHandlers();
}
finally
{
thread.setContextClassLoader(lastContextLoader);
_started=true;
}
Log.event("Started "+this);
}
/* ------------------------------------------------------------ */
/** Start the handlers.
* This is called by start after the classloader has been
* initialized and set as the thread context loader.
* It may be specialized to provide custom handling
* before any handlers are started.
* @exception Exception
*/
protected void startHandlers()
throws Exception
{
// Prepare a multi exception
MultiException mx = new MultiException();
Iterator handlers = _handlers.iterator();
while(handlers.hasNext())
{
HttpHandler handler=(HttpHandler)handlers.next();
if (!handler.isStarted())
try{handler.start();}catch(Exception e){mx.add(e);}
}
mx.ifExceptionThrow();
}
/* ------------------------------------------------------------ */
public synchronized boolean isStarted()
{
return _started;
}
/* ------------------------------------------------------------ */
/** Stop the context.
* @param graceful If true and statistics are on, then this method will wait
* for requestsActive to go to zero before calling stop()
*/
public void stop(boolean graceful)
throws InterruptedException
{
_started=false;
// wait for all requests to complete.
while (graceful && _statsOn && _requestsActive>0 && _httpServer!=null)
try {Thread.sleep(100);}
catch (InterruptedException e){throw e;}
catch (Exception e){Code.ignore(e);}
stop();
}
/* ------------------------------------------------------------ */
/** Stop the context.
*/
public void stop()
throws InterruptedException
{
_started=false;
if (_httpServer==null)
throw new InterruptedException("Destroy called");
synchronized(this)
{
// Notify the container for the stop
Thread thread = Thread.currentThread();
ClassLoader lastContextLoader=thread.getContextClassLoader();
try
{
if (_loader!=null)
thread.setContextClassLoader(_loader);
Iterator handlers = _handlers.iterator();
while(handlers.hasNext())
{
HttpHandler handler=(HttpHandler)handlers.next();
if (handler.isStarted())
{
try{handler.stop();}
catch(Exception e){Code.warning(e);}
}
}
if (_requestLog!=null)
_requestLog.stop();
}
finally
{
thread.setContextClassLoader(lastContextLoader);
}
_loader=null;
}
_cache.clear();
_constraintMap.clear();
if (_attributes!=null)
_attributes.clear();
Log.event("Stopped "+this);
}
/* ------------------------------------------------------------ */
/** Destroy a context.
* Destroy a context and remove it from the HttpServer. The
* HttpContext must be stopped before it can be destroyed.
*/
public void destroy()
{
if (isStarted())
throw new IllegalStateException("Started");
if (_httpServer!=null)
_httpServer.removeContext(this);
_httpServer=null;
if (_handlers!=null)
_handlers.clear();
_handlers=null;
_parent=null;
_loader=null;
_resourceBase=null;
_attributes=null;
if (_initParams!=null)
_initParams.clear();
_initParams=null;
if (_vhosts!=null)
_vhosts.clear();
_vhosts=null;
_hosts=null;
_tmpDir=null;
setMimeMap(null);
_encodingMap=null;
if (_errorPages!=null)
_errorPages.clear();
_errorPages=null;
_permissions=null;
}
/* ------------------------------------------------------------ */
/** Set the request log.
* @param log RequestLog to use.
*/
public void setRequestLog(RequestLog log)
{
_requestLog=log;
}
/* ------------------------------------------------------------ */
public RequestLog getRequestLog()
{
return _requestLog;
}
/* ------------------------------------------------------------ */
/** True set statistics recording on for this context.
* @param on If true, statistics will be recorded for this context.
*/
public void setStatsOn(boolean on)
{
Log.event("setStatsOn "+on+" for "+this);
_statsOn=on;
statsReset();
}
/* ------------------------------------------------------------ */
public boolean getStatsOn() {return _statsOn;}
/* ------------------------------------------------------------ */
public long getStatsOnMs()
{return _statsOn?(System.currentTimeMillis()-_statsStartedAt):0;}
/* ------------------------------------------------------------ */
public void statsReset()
{
synchronized(_statsLock)
{
if (_statsOn)
_statsStartedAt=System.currentTimeMillis();
_requests=0;
_requestsActive=0;
_requestsActiveMax=0;
_responses1xx=0;
_responses2xx=0;
_responses3xx=0;
_responses4xx=0;
_responses5xx=0;
}
}
/* ------------------------------------------------------------ */
/**
* @return Get the number of requests handled by this context
* since last call of statsReset(). If setStatsOn(false) then this
* is undefined.
*/
public int getRequests() {return _requests;}
/* ------------------------------------------------------------ */
/**
* @return Number of requests currently active.
* Undefined if setStatsOn(false).
*/
public int getRequestsActive() {return _requestsActive;}
/* ------------------------------------------------------------ */
/**
* @return Maximum number of active requests
* since statsReset() called. Undefined if setStatsOn(false).
*/
public int getRequestsActiveMax() {return _requestsActiveMax;}
/* ------------------------------------------------------------ */
/**
* @return Get the number of responses with a 2xx status returned
* by this context since last call of statsReset(). Undefined if
* if setStatsOn(false).
*/
public int getResponses1xx() {return _responses1xx;}
/* ------------------------------------------------------------ */
/**
* @return Get the number of responses with a 100 status returned
* by this context since last call of statsReset(). Undefined if
* if setStatsOn(false).
*/
public int getResponses2xx() {return _responses2xx;}
/* ------------------------------------------------------------ */
/**
* @return Get the number of responses with a 3xx status returned
* by this context since last call of statsReset(). Undefined if
* if setStatsOn(false).
*/
public int getResponses3xx() {return _responses3xx;}
/* ------------------------------------------------------------ */
/**
* @return Get the number of responses with a 4xx status returned
* by this context since last call of statsReset(). Undefined if
* if setStatsOn(false).
*/
public int getResponses4xx() {return _responses4xx;}
/* ------------------------------------------------------------ */
/**
* @return Get the number of responses with a 5xx status returned
* by this context since last call of statsReset(). Undefined if
* if setStatsOn(false).
*/
public int getResponses5xx() {return _responses5xx;}
/* ------------------------------------------------------------ */
/** Log a request and response.
* Statistics are also collected by this method.
* @param request
* @param response
*/
public void log(HttpRequest request,
HttpResponse response,
int length)
{
if (_statsOn)
{
synchronized(_statsLock)
{
if (--_requestsActive<0)
_requestsActive=0;
if (response!=null)
{
switch(response.getStatus()/100)
{
case 1: _responses1xx++;break;
case 2: _responses2xx++;break;
case 3: _responses3xx++;break;
case 4: _responses4xx++;break;
case 5: _responses5xx++;break;
}
}
}
}
if (_requestLog!=null &&
request!=null &&
response!=null)
_requestLog.log(request,response,length);
else if (_httpServer!=null)
_httpServer.log(request,response,length);
}
/* ------------------------------------------------------------ */
/** Get Resource MetaData.
* This is a temp method until the resource cache is split out from the HttpContext.
* @param resource
* @return Meta data for the resource.
*/
public ResourceMetaData getResourceMetaData(Resource resource)
{
Object o=resource.getAssociate();
if (o instanceof ResourceMetaData)
return (ResourceMetaData)o;
return new ResourceMetaData(resource);
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
/** MetaData associated with a context Resource.
*/
public class ResourceMetaData
{
protected String _name;
protected Resource _resource;
ResourceMetaData(Resource resource)
{
_resource=resource;
_name=_resource.toString();
_resource.setAssociate(this);
}
public String getLength()
{
return Long.toString(_resource.length());
}
public String getLastModified()
{
return HttpFields.__dateSend.format(new Date(_resource.lastModified()));
}
public String getEncoding()
{
return getMimeByExtension(_name);
}
}
/* ------------------------------------------------------------ */
/* ------------------------------------------------------------ */
private class CachedMetaData extends ResourceMetaData
{
String _lastModified;
String _encoding;
String _length;
String _key;
CachedResource _cached;
CachedMetaData _prev;
CachedMetaData _next;
CachedMetaData(CachedResource resource, String pathInContext)
{
super(resource);
_cached=resource;
_length=super.getLength();
_lastModified=super.getLastModified();
_encoding=super.getEncoding();
_key=pathInContext;
_next=_mostRecentlyUsed;
_mostRecentlyUsed=this;
if (_next!=null)
_next._prev=this;
_prev=null;
if (_leastRecentlyUsed==null)
_leastRecentlyUsed=this;
_cache.put(_key,resource);
_cacheSize+=_cached.length();
}
public String getLength()
{
return _length;
}
public String getLastModified()
{
return _lastModified;
}
public String getEncoding()
{
return _encoding;
}
/* ------------------------------------------------------------ */
boolean isValid()
throws IOException
{
if (_cached.isUptoDate())
{
if (_mostRecentlyUsed!=this)
{
CachedMetaData tp = _prev;
CachedMetaData tn = _next;
_next=_mostRecentlyUsed;
_mostRecentlyUsed=this;
if (_next!=null)
_next._prev=this;
_prev=null;
if (tp!=null)
tp._next=tn;
if (tn!=null)
tn._prev=tp;
if (_leastRecentlyUsed==this && tp!=null)
_leastRecentlyUsed=tp;
}
return true;
}
invalidate();
return false;
}
public void invalidate()
{
// Invalidate it
_cache.remove(_key);
_cacheSize=_cacheSize-(int)_cached.length();
if (_mostRecentlyUsed==this)
_mostRecentlyUsed=_next;
else
_prev._next=_next;
if (_leastRecentlyUsed==this)
_leastRecentlyUsed=_prev;
else
_next._prev=_prev;
_prev=null;
_next=null;
}
}
}