All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.lightbody.bmp.proxy.jetty.http.HttpContext Maven / Gradle / Ivy

// ========================================================================
// $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 net.lightbody.bmp.proxy.jetty.http;

import net.lightbody.bmp.proxy.jetty.http.ResourceCache.ResourceMetaData;
import net.lightbody.bmp.proxy.jetty.http.handler.ErrorPageHandler;
import net.lightbody.bmp.proxy.jetty.util.Container;
import net.lightbody.bmp.proxy.jetty.util.IO;
import net.lightbody.bmp.proxy.jetty.util.LazyList;
import net.lightbody.bmp.proxy.jetty.util.LifeCycle;
import net.lightbody.bmp.proxy.jetty.util.LogSupport;
import net.lightbody.bmp.proxy.jetty.util.MultiException;
import net.lightbody.bmp.proxy.jetty.util.Resource;
import net.lightbody.bmp.proxy.jetty.util.URI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URL;
import java.net.URLClassLoader;
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.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

/**
 * 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. * * @author Greg Wilkins (gregw) * @version $Id: HttpContext.java,v 1.136 2006/02/21 09:47:43 gregwilkins Exp $ * @see HttpServer * @see HttpHandler * @see net.lightbody.bmp.proxy.jetty.jetty.servlet.ServletHttpContext */ public class HttpContext extends Container implements LifeCycle, HttpHandler, 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 = "net.lightbody.bmp.proxy.jetty.http.HttpContext.FileClassPathAttribute"; public final static String __ErrorHandler = "net.lightbody.bmp.proxy.jetty.http.ErrorHandler"; private final Logger log = LoggerFactory.getLogger(HttpContext.class); private final PathMap _constraintMap = new PathMap(); /* ------------------------------------------------------------ */ 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 /* ------------------------------------------------------------ */ // 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.", "net.lightbody.bmp.proxy.jetty.", "org.xml.", "org.w3c.", "org.apache.commons.logging."}; private String[] _serverClasses = new String[]{"-net.lightbody.bmp.proxy.jetty.http.PathMap", "-net.lightbody.bmp.proxy.jetty.jetty.servlet.Invoker", "-net.lightbody.bmp.proxy.jetty.jetty.servlet.JSR154Filter", "-net.lightbody.bmp.proxy.jetty.jetty.servlet.Default", "net.lightbody.bmp.proxy.jetty.jetty.Server", "net.lightbody.bmp.proxy.jetty.http.", "net.lightbody.bmp.proxy.jetty.start.", "net.lightbody.bmp.proxy.jetty.stop."}; /* ------------------------------------------------------------ */ private String _contextName; private String _classPath; private Map _initParams = new HashMap(11); private UserRealm _userRealm; private String _realmName; 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; /** * 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); } 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; } /** * 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); } } private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); _statsLock = new Object[0]; getHandlers(); for (HttpHandler httpHandler : _handlersArray) { httpHandler.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(); } public HttpServer getHttpServer() { return _httpServer; } void setHttpServer(HttpServer httpServer) { _httpServer = httpServer; _contextName = null; } public boolean getStopGracefully() { return _gracefulStop; } public void setStopGracefully(boolean graceful) { _gracefulStop = graceful; } /** * @return The context prefix */ public String getContextPath() { return _contextPath; } 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); } } /** * Add a virtual host alias to this context. * * @param hostname A hostname. A null host name means any hostname is acceptable. * Host names may String representation of IP addresses. * @see #setVirtualHosts */ 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. * * @param hostname A hostname. A null host name means any hostname is acceptable. * Host names may String representation of IP addresses. * @see #setVirtualHosts */ 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; } } /** * 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 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 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 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 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. * 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 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; } /** * 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 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; } public void setHandlers(HttpHandler[] handlers) { List old = new ArrayList(_handlers); if (handlers != null) { for (HttpHandler handler : handlers) { boolean existing = old.remove(handler); if (!existing) { addHandler(handler); } } } for (Object o : old) { removeHandler((HttpHandler) o); } } /** * 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 (Object o : _handlers) { HttpHandler handler = (HttpHandler) o; 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; } /** * 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"); } /** * 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. * @throws 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); 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]); log.trace("path {}", path); File file = path.getFile(); if (file != null) { paths.add(file.getAbsolutePath()); } } catch (Exception 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)) { log.trace("PATH={}", path); paths.add(path); } else { 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()); } log.debug("fileClassPath={}", buf); return buf.toString(); } /** * 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. * @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); } } /** * 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 instanceof File) { _tmpDir = (File) t; if (_tmpDir.isDirectory() && _tmpDir.canWrite()) { return _tmpDir; } } if (t instanceof String) { try { _tmpDir = new File((String) t); if (_tmpDir.isDirectory() && _tmpDir.canWrite()) { log.debug("Converted to File {} for {}", _tmpDir, 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) { // } // 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()) { log.debug("Delete existing temp dir {} for {}", _tmpDir, this); if (!IO.delete(_tmpDir)) { 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 {}, using {}", old, _tmpDir); } } } if (!_tmpDir.exists()) { _tmpDir.mkdir(); } if (work == null) { _tmpDir.deleteOnExit(); } log.debug("Created temp dir {} for {}", _tmpDir, this); } catch (Exception e) { _tmpDir = null; } 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(); log.debug("Created temp dir {} for {}", _tmpDir, this); } catch (IOException e) { log.error(e.getMessage(), e); System.exit(1); } } setAttribute("javax.servlet.context.tempdir", _tmpDir); return _tmpDir; } /** * 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 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 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; } public ClassLoader getParentClassLoader() { return _parent; } /** * 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; } /** * 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 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(); } log.debug("Init classloader from {}, {} for {}", _classPath, parent, 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); } public String getRealmName() { return _realmName; } /** * 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 UserRealm getRealm() { return _userRealm; } /** * Set the realm. */ public void setRealm(UserRealm realm) { _userRealm = realm; } 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); log.debug("added {} at {}", sc, pathSpec); } public void clearSecurityConstraints() { _constraintMap.clear(); } public boolean checkSecurityConstraints(String pathInContext, HttpRequest request, HttpResponse response) throws 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 (Object o : scss) { Map.Entry entry = (Map.Entry) o; 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; } /** * @return True if a /context request is redirected to /context/ if there is not path in the context. */ public boolean isRedirectNullPath() { return _redirectNullPath; } /** * 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; } /** * Get the permissions to be used for this context. */ public PermissionCollection getPermissions() { return _permissions; } /** * 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; } /** * 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 * @throws HttpException * @throws 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())) { log.debug("{} not in {}", s.getLocalAddress(), _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()); log.debug("{} consumed all of path {}, redirect to {}", this, request.getPath(), 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 * @throws HttpException * @throws 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()) { log.debug("{} not started in {}", handler, this); continue; } log.debug("Handler {}", handler); handler.handle(pathInContext, pathParams, request, response); if (request.isHandled()) { log.debug("Handled by {}", handler); 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. * * @throws 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) { // } } 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(); } _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) { // } } _resources.destroy(); } _resources = null; super.destroy(); } public RequestLog getRequestLog() { return _requestLog; } /** * Set the request log. * * @param log RequestLog to use. */ public void setRequestLog(RequestLog log) { _requestLog = log; } /** * 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); } public boolean getStatsOn() { return _statsOn; } /** * 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 {} for {}", on, this); _statsOn = on; statsReset(); } 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 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 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 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 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); } } } /** * @see net.lightbody.bmp.proxy.jetty.http.HttpHandler#getName() . */ public String getName() { return this.getContextPath(); } /** * @see net.lightbody.bmp.proxy.jetty.http.HttpHandler#getHttpContext() . */ public HttpContext getHttpContext() { return this; } /** * @see net.lightbody.bmp.proxy.jetty.http.HttpHandler#initialize(net.lightbody.bmp.proxy.jetty.http.HttpContext) . */ public void initialize(HttpContext context) { throw new UnsupportedOperationException(); } /** * @return */ public Resource getBaseResource() { return _resources.getBaseResource(); } /** * @param base */ public void setBaseResource(Resource base) { _resources.setBaseResource(base); } /** * @param type * @return */ public String getEncodingByMimeType(String type) { return _resources.getEncodingByMimeType(type); } /** * @return */ public Map getEncodingMap() { return _resources.getEncodingMap(); } /** * @param encodingMap */ public void setEncodingMap(Map encodingMap) { _resources.setEncodingMap(encodingMap); } /** * @return */ public int getMaxCachedFileSize() { return _resources.getMaxCachedFileSize(); } /** * @param maxCachedFileSize */ public void setMaxCachedFileSize(int maxCachedFileSize) { _resources.setMaxCachedFileSize(maxCachedFileSize); } /** * @return */ public int getMaxCacheSize() { return _resources.getMaxCacheSize(); } /** * @param maxCacheSize */ public void setMaxCacheSize(int maxCacheSize) { _resources.setMaxCacheSize(maxCacheSize); } /** * @param filename * @return */ public String getMimeByExtension(String filename) { return _resources.getMimeByExtension(filename); } /** * @return */ public Map getMimeMap() { return _resources.getMimeMap(); } /** * @param mimeMap */ public void setMimeMap(Map mimeMap) { _resources.setMimeMap(mimeMap); } /** * @param pathInContext * @return * @throws IOException */ public Resource getResource(String pathInContext) throws IOException { return _resources.getResource(pathInContext); } /** * @return */ public String getResourceBase() { return _resources.getResourceBase(); } /** * @param resourceBase */ public void setResourceBase(String resourceBase) { _resources.setResourceBase(resourceBase); } /** * @param resource * @return */ public ResourceMetaData getResourceMetaData(Resource resource) { return _resources.getResourceMetaData(resource); } /** * @param extension * @param type */ public void setMimeMapping(String extension, String type) { _resources.setMimeMapping(extension, type); } /** * @param mimeType * @param encoding */ public void setTypeEncoding(String mimeType, String encoding) { _resources.setTypeEncoding(mimeType, encoding); } /** * Class to save scope of nested context calls. */ private static class Scope { ClassLoader _classLoader; HttpContext _httpContext; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy