// ======================================================================== // $Id: HttpContext.java,v 1.136 2006/02/21 09:47:43 gregwilkins Exp $ // Copyright 2000-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.browsermob.proxy.jetty.http; import org.apache.commons.logging.Log; import org.browsermob.proxy.jetty.http.ResourceCache.ResourceMetaData; import org.browsermob.proxy.jetty.http.handler.ErrorPageHandler; import org.browsermob.proxy.jetty.log.LogFactory; import org.browsermob.proxy.jetty.util.*; import org.browsermob.proxy.jetty.util.URI; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.net.*; import java.security.Permission; import java.security.PermissionCollection; import java.security.Permissions; import java.util.*; /* ------------------------------------------------------------ */ /** 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. * <p> * A HttpContext is analagous to a ServletContext in the * Servlet API, except that it may contain other types of handler * other than servlets. * <p> * 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() * <p> * * <B>Note. that order is important when configuring a HttpContext. * For example, if resource serving is enabled before servlets, then resources * take priority.</B> * * @see HttpServer * @see HttpHandler * @see org.browsermob.proxy.jetty.jetty.servlet.ServletHttpContext * @version $Id: HttpContext.java,v 1.136 2006/02/21 09:47:43 gregwilkins Exp $ * @author Greg Wilkins (gregw) */ public class HttpContext extends Container implements LifeCycle, HttpHandler, EventProvider, Serializable { private static Log log = LogFactory.getLog(HttpContext.class); /* ------------------------------------------------------------ */ /** 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.browsermob.proxy.jetty.http.HttpContext.FileClassPathAttribute"; public final static String __ErrorHandler= "org.browsermob.proxy.jetty.http.ErrorHandler"; /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ // 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=true; private boolean _statsOn=false; private PermissionCollection _permissions; private boolean _classLoaderJava2Compliant=true; private ResourceCache _resources; private String[] _systemClasses=new String [] {"java.","javax.servlet.","javax.xml.","org.browsermob.proxy.jetty.","org.xml.","org.w3c.","org.apache.commons.logging."}; private String[] _serverClasses = new String[] {"-org.browsermob.proxy.jetty.http.PathMap","-org.browsermob.proxy.jetty.jetty.servlet.Invoker","-org.browsermob.proxy.jetty.jetty.servlet.JSR154Filter","-org.browsermob.proxy.jetty.jetty.servlet.Default","org.browsermob.proxy.jetty.jetty.Server","org.browsermob.proxy.jetty.http.","org.browsermob.proxy.jetty.start.","org.browsermob.proxy.jetty.stop."}; /* ------------------------------------------------------------ */ private String _contextName; private String _classPath; private Map _initParams = new HashMap(11); private UserRealm _userRealm; private String _realmName; private PathMap _constraintMap=new PathMap(); private Authenticator _authenticator; private RequestLog _requestLog; private String[] _welcomes= { "welcome.html", "index.html", "index.htm", "index.jsp" }; /* ------------------------------------------------------------ */ private transient boolean _gracefulStop; private transient ClassLoader _parent; private transient ClassLoader _loader; private transient HttpServer _httpServer; private transient File _tmpDir; 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() { setAttribute(__ErrorHandler, new ErrorPageHandler()); _resources=new ResourceCache(); addComponent(_resources); } /* ------------------------------------------------------------ */ /** 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]; 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 void setStopGracefully(boolean graceful) { _gracefulStop=graceful; } /* ------------------------------------------------------------ */ public boolean getStopGracefully() { return _gracefulStop; } /* ------------------------------------------------------------ */ 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); if (hosts!=null) { for (int i=0;i<hosts.length;i++) { boolean existing=old.remove(hosts[i]); if (!existing) addVirtualHost(hosts[i]); } } for (int i=0;i<old.size();i++) removeVirtualHost((String)old.get(i)); } /* ------------------------------------------------------------ */ /** Get 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. * @return Array of virtual hosts that this context responds to. A * null host name or empty array means any hostname is acceptable. * Host names may be String representation of IP addresses. */ public String[] getVirtualHosts() { if (_vhostsArray!=null) return _vhostsArray; if (_vhosts==null) _vhostsArray=new String[0]; else { _vhostsArray=new String[_vhosts.size()]; _vhostsArray=(String[])_vhosts.toArray(_vhostsArray); } return _vhostsArray; } /* ------------------------------------------------------------ */ /** Set the hosts for the context. * Set the real hosts that this context will accept requests for. * If not null or empty, then only requests from HttpListeners for hosts * in this array are accepted by this context. * Unlike virutal hosts, this value is not used by HttpServer for * matching a request to a context. */ public void setHosts(String[] hosts) throws UnknownHostException { if (hosts==null || hosts.length==0) _hosts=null; else { _hosts=new ArrayList(); for (int i=0;i<hosts.length;i++) if (hosts[i]!=null) _hosts.add(InetAddress.getByName(hosts[i])); } } /* ------------------------------------------------------------ */ /** Get the hosts for the context. */ public String[] getHosts() { if (_hosts==null || _hosts.size()==0) return null; String[] hosts=new String[_hosts.size()]; for (int i=0;i<hosts.length;i++) { InetAddress a = (InetAddress)_hosts.get(i); if (a!=null) hosts[i]=a.getHostName(); } return hosts; } /* ------------------------------------------------------------ */ /** Set system classes. * System classes cannot be overriden by context classloaders. * @param classes array of classname Strings. Names ending with '.' are treated as package names. Names starting with '-' are treated as * negative matches and must be listed before any enclosing packages. */ public void setSystemClasses(String[] classes) { _systemClasses=classes; } /* ------------------------------------------------------------ */ /** Get system classes. * System classes cannot be overriden by context classloaders. * @return array of classname Strings. Names ending with '.' are treated as package names. Names starting with '-' are treated as * negative matches and must be listed before any enclosing packages. Null if not set. */ public String[] getSystemClasses() { return _systemClasses; } /* ------------------------------------------------------------ */ /** Set system classes. * Servers classes cannot be seen by context classloaders. * @param classes array of classname Strings. Names ending with '.' are treated as package names. Names starting with '-' are treated as * negative matches and must be listed before any enclosing packages. */ public void setServerClasses(String[] classes) { _serverClasses=classes; } /* ------------------------------------------------------------ */ /** Get system classes. * System classes cannot be seen by context classloaders. * @return array of classname Strings. Names ending with '.' are treated as package names. Names starting with '-' are treated as * negative matches and must be listed before any enclosing packages. Null if not set. */ public String[] getServerClasses() { return _serverClasses; } /* ------------------------------------------------------------ */ public void setHandlers(HttpHandler[] handlers) { List old = new ArrayList(_handlers); if (handlers!=null) { for (int i=0;i<handlers.length;i++) { boolean existing=old.remove(handlers[i]); if (!existing) addHandler(handlers[i]); } } for (int i=0;i<old.size();i++) removeHandler((HttpHandler)old.get(i)); } /* ------------------------------------------------------------ */ /** Get all handlers. * @return List of all HttpHandlers */ public HttpHandler[] getHandlers() { if (_handlersArray!=null) return _handlersArray; if (_handlers==null) _handlersArray=new HttpHandler[0]; else { _handlersArray=new HttpHandler[_handlers.size()]; _handlersArray=(HttpHandler[])_handlers.toArray(_handlersArray); } return _handlersArray; } /* ------------------------------------------------------------ */ /** Add a handler. * @param i The position in the handler list * @param handler The handler. */ public synchronized void addHandler(int i,HttpHandler handler) { _handlers.add(i,handler); _handlersArray=null; HttpContext context = handler.getHttpContext(); if (context==null) handler.initialize(this); else if (context!=this) throw new IllegalArgumentException("Handler in another HttpContext"); addComponent(handler); } /* ------------------------------------------------------------ */ /** Add a HttpHandler to the context. * @param handler */ public synchronized void addHandler(HttpHandler handler) { addHandler(_handlers.size(),handler); } /* ------------------------------------------------------------ */ /** Get handler index. * @param handler instance * @return Index of handler in context or -1 if not found. */ public int getHandlerIndex(HttpHandler handler) { for (int h=0;h<_handlers.size();h++) { if ( handler == _handlers.get(h)) return h; } return -1; } /* ------------------------------------------------------------ */ /** Get a handler by class. * @param handlerClass * @return The first handler that is an instance of the handlerClass */ public synchronized HttpHandler getHandler(Class handlerClass) { for (int h=0;h<_handlers.size();h++) { HttpHandler handler = (HttpHandler)_handlers.get(h); if (handlerClass.isInstance(handler)) return handler; } return null; } /* ------------------------------------------------------------ */ /** Remove a handler. * The handler must be stopped before being removed. * @param i index of handler */ public synchronized HttpHandler removeHandler(int i) { HttpHandler handler = _handlersArray[i]; if (handler.isStarted()) try{handler.stop();} catch (InterruptedException e){log.warn(LogSupport.EXCEPTION,e);} _handlers.remove(i); _handlersArray=null; removeComponent(handler); return handler; } /* ------------------------------------------------------------ */ /** Remove a handler. * The handler must be stopped before being removed. */ public synchronized void removeHandler(HttpHandler handler) { if (handler.isStarted()) try{handler.stop();} catch (InterruptedException e){log.warn(LogSupport.EXCEPTION,e);} _handlers.remove(handler); removeComponent(handler); _handlersArray=null; } /* ------------------------------------------------------------ */ /** Set context init parameter. * Init Parameters differ from attributes as they can only * have string values, servlets cannot set them and they do * not have a package scoped name space. * @param param param name * @param value param value or null */ public void setInitParameter(String param, String value) { _initParams.put(param,value); } /* ------------------------------------------------------------ */ /** Get context init parameter. * @param param param name * @return param value or null */ public String getInitParameter(String param) { return (String)_initParams.get(param); } /* ------------------------------------------------------------ */ /** Get context init parameter. * @return Enumeration of names */ public Enumeration getInitParameterNames() { return Collections.enumeration(_initParams.keySet()); } /* ------------------------------------------------------------ */ /** Set a context attribute. * @param name attribute name * @param value attribute value */ public synchronized void setAttribute(String name, Object value) { _attributes.put(name,value); } /* ------------------------------------------------------------ */ /** * @param name attribute name * @return attribute value or null */ public Object getAttribute(String name) { return _attributes.get(name); } /* ------------------------------------------------------------ */ /** */ public Map getAttributes() { return _attributes; } /* ------------------------------------------------------------ */ /** */ public void setAttributes(Map attributes) { _attributes=attributes; } /* ------------------------------------------------------------ */ /** * @return enumaration of names. */ public Enumeration getAttributeNames() { return Collections.enumeration(_attributes.keySet()); } /* ------------------------------------------------------------ */ /** * @param name attribute name */ public synchronized void removeAttribute(String name) { _attributes.remove(name); } /* ------------------------------------------------------------ */ public void flushCache() { _resources.flushCache(); } /* ------------------------------------------------------------ */ public String[] getWelcomeFiles() { return _welcomes; } /* ------------------------------------------------------------ */ public void setWelcomeFiles(String[] welcomes) { if (welcomes==null) _welcomes=new String[0]; else _welcomes=welcomes; } /* ------------------------------------------------------------ */ public void addWelcomeFile(String welcomeFile) { if (welcomeFile.startsWith("/") || welcomeFile.startsWith(java.io.File.separator) || welcomeFile.endsWith("/") || welcomeFile.endsWith(java.io.File.separator)) log.warn("Invalid welcome file: "+welcomeFile); List list = new ArrayList(Arrays.asList(_welcomes)); list.add(welcomeFile); _welcomes=(String[])list.toArray(_welcomes); } /* ------------------------------------------------------------ */ public void removeWelcomeFile(String welcomeFile) { List list = new ArrayList(Arrays.asList(_welcomes)); list.remove(welcomeFile); _welcomes=(String[])list.toArray(_welcomes); } /* ------------------------------------------------------------ */ 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; } /* ------------------------------------------------------------ */ /** 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. * It is obtained by walking the classloader hierarchy and looking for * URLClassLoaders. The system property java.class.path is also checked for * file elements not already found in the loader hierarchy. * @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"); LinkedList paths =new LinkedList(); LinkedList loaders=new LinkedList(); // Walk the loader hierarchy while (loader !=null) { loaders.add(0,loader); loader = loader.getParent(); } // Try to handle java2compliant modes loader=getClassLoader(); if (loader instanceof ContextLoader && !((ContextLoader)loader).isJava2Compliant()) { loaders.remove(loader); loaders.add(0,loader); } for (int i=0;i<loaders.size();i++) { loader=(ClassLoader)loaders.get(i); if (log.isDebugEnabled()) log.debug("extract paths from "+loader); if (loader instanceof URLClassLoader) { URL[] urls = ((URLClassLoader)loader).getURLs(); for (int j=0;urls!=null && j<urls.length;j++) { try { Resource path = Resource.newResource(urls[j]); if (log.isTraceEnabled()) log.trace("path "+path); File file = path.getFile(); if (file!=null) paths.add(file.getAbsolutePath()); } catch(Exception e) { LogSupport.ignore(log,e); } } } } // Add the system classpath elements from property. String jcp=System.getProperty("java.class.path"); if (jcp!=null) { StringTokenizer tok=new StringTokenizer(jcp,File.pathSeparator); while (tok.hasMoreTokens()) { String path=tok.nextToken(); if (!paths.contains(path)) { if(log.isTraceEnabled())log.trace("PATH="+path); paths.add(path); } else if(log.isTraceEnabled())log.trace("done="+path); } } StringBuffer buf = new StringBuffer(); Iterator iter = paths.iterator(); while(iter.hasNext()) { if (buf.length()>0) buf.append(File.pathSeparator); buf.append(iter.next().toString()); } if (log.isDebugEnabled()) log.debug("fileClassPath="+buf); return buf.toString(); } /* ------------------------------------------------------------ */ /** 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()) log.warn("classpath set while started"); } /* ------------------------------------------------------------ */ /** Add the class path element to 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 addClassPath(String classPath) { if (_classPath==null || _classPath.length()==0) _classPath=classPath; else _classPath+=","+classPath; if (isStarted()) log.warn("classpath set while started"); } /* ------------------------------------------------------------ */ /** Add elements to 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 addClassPaths(Resource lib) { if (isStarted()) log.warn("classpaths set while started"); if (lib.exists() && lib.isDirectory()) { String[] files=lib.list(); for (int f=0;files!=null && f<files.length;f++) { try { Resource fn=lib.addPath(files[f]); String fnlc=fn.getName().toLowerCase(); if (fnlc.endsWith(".jar") || fnlc.endsWith(".zip")) { addClassPath(fn.toString()); } } catch (Exception ex) { log.warn(LogSupport.EXCEPTION,ex); } } } } /* ------------------------------------------------------------ */ /** 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){log.warn(LogSupport.EXCEPTION,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()) { if(log.isDebugEnabled())log.debug("Converted to File "+_tmpDir+" for "+this); setAttribute("javax.servlet.context.tempdir",_tmpDir); return _tmpDir; } } catch(Exception e) { log.warn(LogSupport.EXCEPTION,e); } } // No tempdir so look for a WEB-INF/work directory to use as tempDir base File work=null; try { work=new File(System.getProperty("jetty.home"),"work"); if (!work.exists() || !work.canWrite() || !work.isDirectory()) work=null; } catch(Exception e) { LogSupport.ignore(log,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('\\','_'); if (work!=null) _tmpDir=new File(work,temp); else { _tmpDir=new File(System.getProperty("java.io.tmpdir"),temp); if (_tmpDir.exists()) { if(log.isDebugEnabled())log.debug("Delete existing temp dir "+_tmpDir+" for "+this); if (!IO.delete(_tmpDir)) { if(log.isDebugEnabled())log.debug("Failed to delete temp dir "+_tmpDir); } if (_tmpDir.exists()) { String old=_tmpDir.toString(); _tmpDir=File.createTempFile(temp+"_",""); if (_tmpDir.exists()) _tmpDir.delete(); log.warn("Can't reuse "+old+", using "+_tmpDir); } } } if (!_tmpDir.exists()) _tmpDir.mkdir(); if (work==null) _tmpDir.deleteOnExit(); if(log.isDebugEnabled())log.debug("Created temp dir "+_tmpDir+" for "+this); } catch(Exception e) { _tmpDir=null; LogSupport.ignore(log,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(); if(log.isDebugEnabled())log.debug("Created temp dir "+_tmpDir+" for "+this); } catch(IOException e) { log.fatal(e); System.exit(1); } } 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 { ClassLoader parent=_parent; 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(); if(log.isDebugEnabled())log.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) { log.warn(LogSupport.EXCEPTION,e); return null; } } if (className==null) return null; if (_loader == null) return Class.forName(className); return _loader.loadClass(className); } /* ------------------------------------------------------------ */ /** 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) { Object scs = _constraintMap.get(pathSpec); scs = LazyList.add(scs,sc); _constraintMap.put(pathSpec,scs); if(log.isDebugEnabled())log.debug("added "+sc+" at "+pathSpec); } /* ------------------------------------------------------------ */ public void clearSecurityConstraints() { _constraintMap.clear(); } /* ------------------------------------------------------------ */ public boolean checkSecurityConstraints( String pathInContext, HttpRequest request, HttpResponse response) throws HttpException, IOException { UserRealm realm= getRealm(); List scss= _constraintMap.getMatches(pathInContext); String pattern=null; if (scss != null && scss.size() > 0) { Object constraints= null; // for each path match // Add only constraints that have the correct method // break if the matching pattern changes. This allows only // constraints with matching pattern and method to be combined. loop: for (int m= 0; m < scss.size(); m++) { Map.Entry entry= (Map.Entry)scss.get(m); Object scs= entry.getValue(); String p=(String)entry.getKey(); for (int c=0;c<LazyList.size(scs);c++) { SecurityConstraint sc=(SecurityConstraint)LazyList.get(scs,c); if (!sc.forMethod(request.getMethod())) continue; if (pattern!=null && !pattern.equals(p)) break loop; pattern=p; constraints= LazyList.add(constraints, sc); } } return SecurityConstraint.check( LazyList.getList(constraints), _authenticator, realm, pathInContext, request, response); } request.setUserPrincipal(HttpRequest.__NOT_CHECKED); return true; } /* ------------------------------------------------------------ */ /** Set null path redirection. * @param b if true a /context request will be redirected to * /context/ if there is not path in the context. */ public void setRedirectNullPath(boolean b) { _redirectNullPath=b; } /* ------------------------------------------------------------ */ /** * @return True if a /context request is redirected to /context/ if * there is not path in the context. */ public boolean isRedirectNullPath() { return _redirectNullPath; } /* ------------------------------------------------------------ */ /** Set the permissions to be used for this context. * The collection of permissions set here are used for all classes * loaded by this context. This is simpler that creating a * security policy file, as not all code sources may be statically * known. * @param permissions */ public void setPermissions(PermissionCollection permissions) { _permissions=permissions; } /* ------------------------------------------------------------ */ /** Get the permissions to be used for this context. */ public PermissionCollection getPermissions() { return _permissions; } /* ------------------------------------------------------------ */ /** Add a permission to this context. * The collection of permissions set here are used for all classes * loaded by this context. This is simpler that creating a * security policy file, as not all code sources may be statically * known. * @param permission */ public void addPermission(Permission permission) { if (_permissions==null) _permissions=new Permissions(); _permissions.add(permission); } /* ------------------------------------------------------------ */ /** Handler request. * Determine the path within the context and then call * handle(pathInContext,request,response). * @param request * @param response * @return True if the request has been handled. * @exception HttpException * @exception IOException */ public void handle(HttpRequest request, HttpResponse response) throws HttpException, IOException { if (!isStarted() || _gracefulStop) return; // reject requests by real host if (_hosts!=null && _hosts.size()>0) { Object o = request.getHttpConnection().getConnection(); if (o instanceof Socket) { Socket s=(Socket)o; if (!_hosts.contains(s.getLocalAddress())) { if(log.isDebugEnabled())log.debug(s.getLocalAddress()+" not in "+_hosts); return; } } } // 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.sendRedirect(buf.toString()); if (log.isDebugEnabled()) log.debug(this+" consumed all of path "+ request.getPath()+ ", redirect to "+buf.toString()); return; } 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 { handle(pathInContext,pathParams,request,response); } finally { if (_userRealm!=null && request.hasUserPrincipal()) _userRealm.disassociate(request.getUserPrincipal()); } } /* ------------------------------------------------------------ */ /** 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 void handle(String pathInContext, String pathParams, HttpRequest request, HttpResponse response) throws HttpException, IOException { Object old_scope= null; try { old_scope= enterContextScope(request,response); HttpHandler[] handlers= getHandlers(); for (int k= 0; k < handlers.length; k++) { HttpHandler handler= handlers[k]; if (handler == null) { handlers= getHandlers(); k= -1; continue; } if (!handler.isStarted()) { if (log.isDebugEnabled()) log.debug(handler + " not started in " + this); continue; } if (log.isDebugEnabled()) log.debug("Handler " + handler); handler.handle(pathInContext, pathParams, request, response); if (request.isHandled()) { if (log.isDebugEnabled()) log.debug("Handled by " + handler); return; } } return; } finally { leaveContextScope(request, response, old_scope); } } /* ------------------------------------------------------------ */ /** Enter the context scope. * This method is called (by handle or servlet dispatchers) to indicate that * request handling is entering the scope of this context. The opaque scope object * returned, should be passed to the leaveContextScope method. */ public Object enterContextScope(HttpRequest request, HttpResponse response) { // Save the thread context loader Thread thread = Thread.currentThread(); ClassLoader cl=thread.getContextClassLoader(); HttpContext c=response.getHttpContext(); Scope scope=null; if (cl!=HttpContext.class.getClassLoader() || c!=null) { scope=new Scope(); scope._classLoader=cl; scope._httpContext=c; } if (_loader!=null) thread.setContextClassLoader(_loader); response.setHttpContext(this); return scope; } /* ------------------------------------------------------------ */ /** Leave the context scope. * This method is called (by handle or servlet dispatchers) to indicate that * request handling is leaveing the scope of this context. The opaque scope object * returned by enterContextScope should be passed in. */ public void leaveContextScope(HttpRequest request, HttpResponse response,Object oldScope) { if (oldScope==null) { Thread.currentThread() .setContextClassLoader(HttpContext.class.getClassLoader()); response.setHttpContext(null); } else { Scope old = (Scope)oldScope; Thread.currentThread().setContextClassLoader(old._classLoader); response.setHttpContext(old._httpContext); } } /* ------------------------------------------------------------ */ public String getHttpContextName() { if (_contextName==null) _contextName = (_vhosts.size()>1?(_vhosts.toString()+":"):"")+_contextPath; return _contextName; } /* ------------------------------------------------------------ */ public void setHttpContextName(String s) { _contextName=s; } /* ------------------------------------------------------------ */ public String toString() { return "HttpContext["+getContextPath()+","+getHttpContextName()+"]"; } /* ------------------------------------------------------------ */ public String toString(boolean detail) { return "HttpContext["+getContextPath()+","+getHttpContextName()+"]" + (detail?("="+_handlers):""); } /* ------------------------------------------------------------ */ protected synchronized void doStart() throws Exception { if (isStarted()) return; if (_httpServer.getServerClasses()!=null) _serverClasses=_httpServer.getServerClasses(); if (_httpServer.getSystemClasses()!=null) _systemClasses=_httpServer.getSystemClasses(); _resources.start(); statsReset(); if (_httpServer==null) throw new IllegalStateException("No server for "+this); // start the context itself _resources.getMimeMap(); _resources.getEncodingMap(); // Setup realm if (_userRealm==null && _authenticator!=null) { _userRealm=_httpServer.getRealm(_realmName); if (_userRealm==null) log.warn("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); getHandlers(); } } /* ------------------------------------------------------------ */ /** 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(); } /* ------------------------------------------------------------ */ /** 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 { boolean gs=_gracefulStop; try { _gracefulStop=true; // 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){LogSupport.ignore(log,e);} stop(); } finally { _gracefulStop=gs; } } /* ------------------------------------------------------------ */ /** Stop the context. */ protected void doStop() throws Exception { 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){log.warn(LogSupport.EXCEPTION,e);} } } if (_requestLog!=null) _requestLog.stop(); } finally { thread.setContextClassLoader(lastContextLoader); } // TODO this is a poor test if (_loader instanceof ContextLoader) { ((ContextLoader)_loader).destroy(); LogFactory.release(_loader); } _loader=null; } _resources.flushCache(); _resources.stop(); } /* ------------------------------------------------------------ */ /** 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; if (_attributes!=null) _attributes.clear(); _attributes=null; if (_initParams!=null) _initParams.clear(); _initParams=null; if (_vhosts!=null) _vhosts.clear(); _vhosts=null; _hosts=null; _tmpDir=null; _permissions=null; removeComponent(_resources); if (_resources!=null) { _resources.flushCache(); if (_resources.isStarted()) try{_resources.stop();}catch(Exception e){LogSupport.ignore(log,e);} _resources.destroy(); } _resources=null; super.destroy(); } /* ------------------------------------------------------------ */ /** Set the request log. * @param log RequestLog to use. */ public void setRequestLog(RequestLog log) { _requestLog=log; } /* ------------------------------------------------------------ */ public RequestLog getRequestLog() { return _requestLog; } /* ------------------------------------------------------------ */ /** Send an error response. * This method may be specialized to provide alternative error handling for * errors generated by the container. The default implemenation calls HttpResponse.sendError * @param response the response to send * @param code The error code * @param msg The message for the error or null for the default * @throws IOException Problem sending response. */ public void sendError(HttpResponse response,int code,String msg) throws IOException { response.sendError(code,msg); } /* ------------------------------------------------------------ */ /** Send an error response. * This method obtains the responses context and call sendError for context specific * error handling. * @param response the response to send * @param code The error code * @param msg The message for the error or null for the default * @throws IOException Problem sending response. */ public static void sendContextError(HttpResponse response,int code,String msg) throws IOException { HttpContext context = response.getHttpContext(); if (context!=null) context.sendError(response,code,msg); else response.sendError(code,msg); } /* ------------------------------------------------------------ */ /** 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.info("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; _requestsActiveMax=_requestsActive; _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); } /* ------------------------------------------------------------ */ /* Class to save scope of nested context calls */ private static class Scope { ClassLoader _classLoader; HttpContext _httpContext; } /* * @see org.browsermob.proxy.jetty.http.HttpHandler#getName() */ public String getName() { return this.getContextPath(); } /* * @see org.browsermob.proxy.jetty.http.HttpHandler#getHttpContext() */ public HttpContext getHttpContext() { return this; } /* * @see org.browsermob.proxy.jetty.http.HttpHandler#initialize(org.browsermob.proxy.jetty.http.HttpContext) */ public void initialize(HttpContext context) { throw new UnsupportedOperationException(); } /** * @return */ public Resource getBaseResource() { return _resources.getBaseResource(); } /** * @param type * @return */ public String getEncodingByMimeType(String type) { return _resources.getEncodingByMimeType(type); } /** * @return */ public Map getEncodingMap() { return _resources.getEncodingMap(); } /** * @return */ public int getMaxCachedFileSize() { return _resources.getMaxCachedFileSize(); } /** * @return */ public int getMaxCacheSize() { return _resources.getMaxCacheSize(); } /** * @param filename * @return */ public String getMimeByExtension(String filename) { return _resources.getMimeByExtension(filename); } /** * @return */ public Map getMimeMap() { return _resources.getMimeMap(); } /** * @param pathInContext * @return * @throws IOException */ public Resource getResource(String pathInContext) throws IOException { return _resources.getResource(pathInContext); } /** * @return */ public String getResourceBase() { return _resources.getResourceBase(); } /** * @param resource * @return */ public ResourceMetaData getResourceMetaData(Resource resource) { return _resources.getResourceMetaData(resource); } /** * @param base */ public void setBaseResource(Resource base) { _resources.setBaseResource(base); } /** * @param encodingMap */ public void setEncodingMap(Map encodingMap) { _resources.setEncodingMap(encodingMap); } /** * @param maxCachedFileSize */ public void setMaxCachedFileSize(int maxCachedFileSize) { _resources.setMaxCachedFileSize(maxCachedFileSize); } /** * @param maxCacheSize */ public void setMaxCacheSize(int maxCacheSize) { _resources.setMaxCacheSize(maxCacheSize); } /** * @param mimeMap */ public void setMimeMap(Map mimeMap) { _resources.setMimeMap(mimeMap); } /** * @param extension * @param type */ public void setMimeMapping(String extension, String type) { _resources.setMimeMapping(extension, type); } /** * @param resourceBase */ public void setResourceBase(String resourceBase) { _resources.setResourceBase(resourceBase); } /** * @param mimeType * @param encoding */ public void setTypeEncoding(String mimeType, String encoding) { _resources.setTypeEncoding(mimeType, encoding); } }