net.lightbody.bmp.proxy.jetty.http.HttpServer.java Source code

Java tutorial

Introduction

Here is the source code for net.lightbody.bmp.proxy.jetty.http.HttpServer.java

Source

// ========================================================================
// $Id: HttpServer.java,v 1.70 2005/12/04 11:43:21 gregwilkins Exp $
// Copyright 1999-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.handler.DumpHandler;
import net.lightbody.bmp.proxy.jetty.http.handler.NotFoundHandler;
import net.lightbody.bmp.proxy.jetty.http.handler.ResourceHandler;
import net.lightbody.bmp.proxy.jetty.log.LogFactory;
import net.lightbody.bmp.proxy.jetty.util.*;
import org.apache.commons.logging.Log;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.util.*;

/* ------------------------------------------------------------ */
/** HTTP Server.
 * Services HTTP requests by maintaining a mapping between
 * a collection of HttpListeners which generate requests and
 * HttpContexts which contain collections of HttpHandlers.
 *
 * This class is configured by API calls.  The
 * org.mortbay.jetty.Server class uses XML configuration files to
 * configure instances of this class.
 *
 * The HttpServer implements the BeanContext API so that membership
 * events may be generated for HttpListeners, HttpContexts and WebApplications.
 *
 * @see HttpContext
 * @see HttpHandler
 * @see HttpConnection
 * @see HttpListener
 * @see net.lightbody.bmp.proxy.jetty.jetty.Server
 * @version $Id: HttpServer.java,v 1.70 2005/12/04 11:43:21 gregwilkins Exp $
 * @author Greg Wilkins (gregw)
 */
public class HttpServer extends Container implements LifeCycle, EventProvider, Serializable {
    private static Log log = LogFactory.getLog(HttpServer.class);

    /* ------------------------------------------------------------ */
    private static WeakHashMap __servers = new WeakHashMap();
    private static Collection __roServers = Collections.unmodifiableCollection(__servers.keySet());
    private static String[] __noVirtualHost = new String[1];

    /* ------------------------------------------------------------ */
    /** Get HttpServer Collection.
     * Get a collection of all known HttpServers.  Servers can be
     * removed from this list with the setAnonymous call.
     * @return  Collection of all servers.
     */
    public static Collection getHttpServers() {
        return __roServers;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @deprecated User getHttpServers()
     */
    public static List getHttpServerList() {
        return new ArrayList(__roServers);
    }

    /* ------------------------------------------------------------ */
    private List _listeners = new ArrayList(3);
    private HashMap _realmMap = new HashMap(3);
    private StringMap _virtualHostMap = new StringMap();
    private boolean _trace = false;
    private RequestLog _requestLog;
    private int _requestsPerGC;
    private boolean _resolveRemoteHost = false;

    private String[] _serverClasses;
    private String[] _systemClasses;

    private transient int _gcRequests;
    private transient HttpContext _notFoundContext = null;
    private transient boolean _gracefulStop;

    /* ------------------------------------------------------------ */
    /** Constructor. 
     */
    public HttpServer() {
        this(false);
    }

    /* ------------------------------------------------------------ */
    /** Constructor. 
     * @param anonymous If true, the server is not included in the
     * static server lists and stopAll methods.
     */
    public HttpServer(boolean anonymous) {
        setAnonymous(anonymous);
        _virtualHostMap.setIgnoreCase(true);
    }

    /* ------------------------------------------------------------ */
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        HttpListener[] listeners = getListeners();
        HttpContext[] contexts = getContexts();
        _listeners.clear();
        _virtualHostMap.clear();
        setContexts(contexts);
        setListeners(listeners);
        _statsLock = new Object[0];
    }

    /* ------------------------------------------------------------ */
    /** 
     * @param anonymous If true, the server is not included in the
     * static server lists and stopAll methods.
     */
    public void setAnonymous(boolean anonymous) {
        if (anonymous)
            __servers.remove(this);
        else
            __servers.put(this, __servers);
    }

    /* ------------------------------------------------------------ */
    public void setStopGracefully(boolean graceful) {
        _gracefulStop = graceful;
    }

    /* ------------------------------------------------------------ */
    public boolean getStopGracefully() {
        return _gracefulStop;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @param listeners Array of HttpListeners.
     */
    public void setListeners(HttpListener[] listeners) {
        List old = new ArrayList(_listeners);

        for (int i = 0; i < listeners.length; i++) {
            boolean existing = old.remove(listeners[i]);
            if (!existing)
                addListener(listeners[i]);
        }

        for (int i = 0; i < old.size(); i++) {
            HttpListener listener = (HttpListener) old.get(i);
            removeListener(listener);
        }
    }

    /* ------------------------------------------------------------ */
    /** 
     * @return Array of HttpListeners.
     */
    public HttpListener[] getListeners() {
        if (_listeners == null)
            return new HttpListener[0];
        HttpListener[] listeners = new HttpListener[_listeners.size()];
        return (HttpListener[]) _listeners.toArray(listeners);
    }

    /* ------------------------------------------------------------ */
    /** Create and add a SocketListener.
     * Conveniance method.
     * @param address
     * @return the HttpListener.
     * @exception IOException 
     */
    public HttpListener addListener(String address) throws IOException {
        return addListener(new InetAddrPort(address));
    }

    /* ------------------------------------------------------------ */
    /** Create and add a SocketListener.
     * Conveniance method.
     * @param address
     * @return the HttpListener.
     * @exception IOException 
     */
    public HttpListener addListener(InetAddrPort address) throws IOException {
        HttpListener listener = new SocketListener(address);
        listener.setHttpServer(this);
        _listeners.add(listener);
        addComponent(listener);
        return listener;
    }

    /* ------------------------------------------------------------ */
    /** Add a HTTP Listener to the server.
     * @param listener The Listener.
     * @exception IllegalArgumentException If the listener is not for this
     * server.
     */
    public HttpListener addListener(HttpListener listener) throws IllegalArgumentException {
        listener.setHttpServer(this);
        _listeners.add(listener);
        addComponent(listener);
        return listener;
    }

    /* ------------------------------------------------------------ */
    /** Remove a HTTP Listener.
     * @param listener 
     */
    public void removeListener(HttpListener listener) {
        if (listener == null)
            return;

        for (int l = 0; l < _listeners.size(); l++) {
            if (listener.equals(_listeners.get(l))) {
                _listeners.remove(l);
                removeComponent(listener);
                if (listener.isStarted())
                    try {
                        listener.stop();
                    } catch (InterruptedException e) {
                        log.warn(LogSupport.EXCEPTION, e);
                    }
                listener.setHttpServer(null);
            }
        }
    }

    /* ------------------------------------------------------------ */
    public synchronized void setContexts(HttpContext[] contexts) {
        List old = Arrays.asList(getContexts());

        for (int i = 0; i < contexts.length; i++) {
            boolean existing = old.remove(contexts[i]);
            if (!existing)
                addContext(contexts[i]);
        }

        for (int i = 0; i < old.size(); i++)
            removeContext((HttpContext) old.get(i));
    }

    /* ------------------------------------------------------------ */
    public synchronized HttpContext[] getContexts() {
        if (_virtualHostMap == null)
            return new HttpContext[0];

        ArrayList contexts = new ArrayList(33);
        Iterator maps = _virtualHostMap.values().iterator();
        while (maps.hasNext()) {
            PathMap pm = (PathMap) maps.next();
            Iterator lists = pm.values().iterator();
            while (lists.hasNext()) {
                List list = (List) lists.next();
                for (int i = 0; i < list.size(); i++) {
                    HttpContext context = (HttpContext) list.get(i);
                    if (!contexts.contains(context))
                        contexts.add(context);
                }
            }
        }
        return (HttpContext[]) contexts.toArray(new HttpContext[contexts.size()]);
    }

    /* ------------------------------------------------------------ */
    /** Add a context.
     * @param context 
     */
    public HttpContext addContext(HttpContext context) {
        if (context.getContextPath() == null || context.getContextPath().length() == 0)
            throw new IllegalArgumentException("No Context Path Set");
        boolean existing = removeMappings(context);
        if (!existing) {
            context.setHttpServer(this);
            addComponent(context);
        }
        addMappings(context);
        return context;
    }

    /* ------------------------------------------------------------ */
    /** Remove a context or Web application.
     * @exception IllegalStateException if context not stopped
     */
    public boolean removeContext(HttpContext context) throws IllegalStateException {
        if (removeMappings(context)) {
            removeComponent(context);
            if (context.isStarted())
                try {
                    context.stop();
                } catch (InterruptedException e) {
                    log.warn(LogSupport.EXCEPTION, e);
                }
            context.setHttpServer(null);
            return true;
        }
        return false;
    }

    /* ------------------------------------------------------------ */
    /** Add a context.
     * As contexts cannot be publicly created, this may be used to
     * alias an existing context.
     * @param virtualHost The virtual host or null for all hosts.
     * @param context 
     */
    public HttpContext addContext(String virtualHost, HttpContext context) {
        if (virtualHost != null)
            context.addVirtualHost(virtualHost);
        addContext(context);
        return context;
    }

    /* ------------------------------------------------------------ */
    /** Create and add a new context.
     * Note that multiple contexts can be created for the same
     * virtualHost and contextPath. Requests are offered to multiple
     * contexts in the order they where added to the HttpServer.
     * @param contextPath
     * @return A HttpContext instance created by a call to newHttpContext.
     */
    public HttpContext addContext(String contextPath) {
        HttpContext hc = newHttpContext();
        hc.setContextPath(contextPath);
        addContext(hc);
        return hc;
    }

    /* ------------------------------------------------------------ */
    /** Create and add a new context.
     * Note that multiple contexts can be created for the same
     * virtualHost and contextPath. Requests are offered to multiple
     * contexts in the order they where added to the HttpServer.
     * @param virtualHost Virtual hostname or null for all hosts.
     * @param contextPathSpec Path specification relative to the context path.
     * @return A HttpContext instance created by a call to newHttpContext.
     */
    public HttpContext addContext(String virtualHost, String contextPathSpec) {
        if (virtualHost != null && virtualHost.length() == 0)
            virtualHost = null;
        HttpContext hc = newHttpContext();
        hc.setContextPath(contextPathSpec);
        if (virtualHost != null)
            hc.addVirtualHost(virtualHost);
        addContext(hc);
        return hc;
    }

    /* ------------------------------------------------------------ */
    /** Get specific context. 
     * @param virtualHost The virtual host or null for all hosts.
     * @param contextPathSpec Path specification relative to the context path.
     * @param i Index among contexts of same virtualHost and pathSpec.
     * @return The HttpContext or null.
     */
    public HttpContext getContext(String virtualHost, String contextPathSpec, int i) {
        HttpContext hc = null;
        contextPathSpec = HttpContext.canonicalContextPathSpec(contextPathSpec);

        PathMap contextMap = (PathMap) _virtualHostMap.get(virtualHost);
        if (contextMap != null) {
            List contextList = (List) contextMap.get(contextPathSpec);
            if (contextList != null) {
                if (i >= contextList.size())
                    return null;
                hc = (HttpContext) contextList.get(i);
            }
        }

        return hc;
    }

    /* ------------------------------------------------------------ */
    /** Get or create context. 
     * @param virtualHost The virtual host or null for all hosts.
     * @param contextPathSpec
     * @return HttpContext. If multiple contexts exist for the same
     * virtualHost and pathSpec, the most recently added context is returned.
     * If no context exists, a new context is created by a call to newHttpContext.
     */
    public HttpContext getContext(String virtualHost, String contextPathSpec) {
        HttpContext hc = null;
        contextPathSpec = HttpContext.canonicalContextPathSpec(contextPathSpec);
        PathMap contextMap = (PathMap) _virtualHostMap.get(virtualHost);

        if (contextMap != null) {
            List contextList = (List) contextMap.get(contextPathSpec);
            if (contextList != null && contextList.size() > 0)
                hc = (HttpContext) contextList.get(contextList.size() - 1);
        }
        if (hc == null)
            hc = addContext(virtualHost, contextPathSpec);

        return hc;
    }

    /* ------------------------------------------------------------ */
    /** Get or create context. 
     * @param contextPathSpec Path specification relative to the context path.
     * @return The HttpContext  If multiple contexts exist for the same
     * pathSpec, the most recently added context is returned.
     * If no context exists, a new context is created by a call to newHttpContext.
     */
    public HttpContext getContext(String contextPathSpec) {
        return getContext(null, contextPathSpec);
    }

    /* ------------------------------------------------------------ */
    /** Create a new HttpContext.
     * Specialized HttpServer classes may override this method to
     * return subclasses of HttpContext.
     * @return A new instance of HttpContext or a subclass of HttpContext
     */
    protected HttpContext newHttpContext() {
        return new HttpContext();
    }

    /* ------------------------------------------------------------ */
    synchronized void addMapping(String virtualHost, HttpContext context) {
        // Get the map of contexts
        PathMap contextMap = (PathMap) _virtualHostMap.get(virtualHost);
        if (contextMap == null) {
            contextMap = new PathMap(7);
            _virtualHostMap.put(virtualHost, contextMap);
        }

        // Generalize contextPath
        String contextPathSpec = HttpContext.canonicalContextPathSpec(context.getContextPath());

        // Get the list of contexts at this path
        List contextList = (List) contextMap.get(contextPathSpec);
        if (contextList == null) {
            contextList = new ArrayList(1);
            contextMap.put(contextPathSpec, contextList);
        }

        // Add the context to the list
        contextList.add(context);

        if (log.isDebugEnabled())
            log.debug("Added " + context + " for host " + (virtualHost == null ? "*" : virtualHost));
    }

    /* ------------------------------------------------------------ */
    synchronized void addMappings(HttpContext context) {
        if (context == _notFoundContext)
            return;

        String[] hosts = context.getVirtualHosts();
        if (hosts == null || hosts.length == 0)
            hosts = __noVirtualHost;

        // For each host name
        for (int h = 0; h < hosts.length; h++) {
            String virtualHost = hosts[h];
            addMapping(virtualHost, context);
        }
    }

    /* ------------------------------------------------------------ */
    synchronized boolean removeMapping(String virtualHost, HttpContext context) {
        boolean existing = false;
        if (_virtualHostMap != null) {
            PathMap contextMap = (PathMap) _virtualHostMap.get(virtualHost);

            Iterator i2 = contextMap.values().iterator();
            while (i2.hasNext()) {
                List contextList = (List) i2.next();
                if (contextList.remove(context))
                    existing = true;
                if (contextList.size() == 0)
                    i2.remove();
            }
        }
        return existing;
    }

    /* ------------------------------------------------------------ */
    synchronized boolean removeMappings(HttpContext context) {
        boolean existing = false;

        if (_virtualHostMap != null) {
            Iterator i1 = _virtualHostMap.keySet().iterator();
            while (i1.hasNext()) {
                String virtualHost = (String) i1.next();
                if (removeMapping(virtualHost, context))
                    existing = true;
            }
        }
        return existing;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @return True if the TRACE method is fully implemented.
     */
    public boolean getTrace() {
        return _trace;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @param trace True if the TRACE method is fully implemented.
     */
    public void setTrace(boolean trace) {
        _trace = trace;
    }

    /* ------------------------------------------------------------ */
    /** Get the requests per GC.
     * If this is set greater than zero, then the System garbage collector
     * will be invoked after approximately this number of requests.  For
     * predictable response, it is often best to have frequent small runs of
     * the GC rather than infrequent large runs.  The request count is only
     * approximate as it is not synchronized and multi CPU machines may miss
     * counting some requests.
     * @return Approx requests per garbage collection.
     */
    public int getRequestsPerGC() {
        return _requestsPerGC;
    }

    /* ------------------------------------------------------------ */
    /** Set the requests per GC.
     * If this is set greater than zero, then the System garbage collector
     * will be invoked after approximately this number of requests.  For
     * predictable response, it is often best to have frequent small runs of
     * the GC rather than infrequent large runs.  The request count is only
     * approximate as it is not synchronized and multi CPU machines may miss
     * counting some requests.
     * @param requestsPerGC Approx requests per garbage collection.
     */
    public void setRequestsPerGC(int requestsPerGC) {
        _requestsPerGC = requestsPerGC;
    }

    /* ------------------------------------------------------------ */
    /** Set system classes.
     * @deprecated. Use HttpContext
     */
    public void setSystemClasses(String[] classes) {
        _systemClasses = classes;
    }

    /* ------------------------------------------------------------ */
    /** Get system classes.
     * @deprecated. Use HttpContext
     */
    public String[] getSystemClasses() {
        return _systemClasses;
    }

    /* ------------------------------------------------------------ */
    /** Set system classes.
     * @deprecated. Use HttpContext
     */
    public void setServerClasses(String[] classes) {
        _serverClasses = classes;
    }

    /* ------------------------------------------------------------ */
    /** Get system classes.
     * @deprecated. Use HttpContext
     */
    public String[] getServerClasses() {
        return _serverClasses;
    }

    /* ------------------------------------------------------------ */
    /** Start all handlers then listeners.
     * If a subcomponent fails to start, it's exception is added to a
     * org.mortbay.util.MultiException and the start method continues.
     * @exception MultiException A collection of exceptions thrown by
     * start() method of subcomponents of the HttpServer. 
     */
    protected synchronized void doStart() throws Exception {
        log.info("Version " + Version.getImplVersion());

        MultiException mex = new MultiException();

        statsReset();

        if (log.isDebugEnabled()) {
            log.debug("LISTENERS: " + _listeners);
            log.debug("HANDLER: " + _virtualHostMap);
        }

        if (_requestLog != null && !_requestLog.isStarted()) {
            try {
                _requestLog.start();
            } catch (Exception e) {
                mex.add(e);
            }
        }

        HttpContext[] contexts = getContexts();
        for (int i = 0; i < contexts.length; i++) {
            HttpContext context = contexts[i];
            try {
                context.start();
            } catch (Exception e) {
                mex.add(e);
            }
        }

        for (int l = 0; l < _listeners.size(); l++) {
            HttpListener listener = (HttpListener) _listeners.get(l);
            listener.setHttpServer(this);
            if (!listener.isStarted())
                try {
                    listener.start();
                } catch (Exception e) {
                    mex.add(e);
                }
        }

        mex.ifExceptionThrowMulti();
    }

    /* ------------------------------------------------------------ */
    /** Stop all listeners then all contexts.
     * Equivalent to stop(false);
     * @exception InterruptedException If interrupted, stop may not have
     * been called on everything.
     */
    protected synchronized void doStop() throws InterruptedException {
        for (int l = 0; l < _listeners.size(); l++) {
            HttpListener listener = (HttpListener) _listeners.get(l);
            if (listener.isStarted()) {
                try {
                    listener.stop();
                } catch (Exception e) {
                    if (log.isDebugEnabled())
                        log.warn(LogSupport.EXCEPTION, e);
                    else
                        log.warn(e.toString());
                }
            }
        }

        HttpContext[] contexts = getContexts();
        for (int i = 0; i < contexts.length; i++) {
            HttpContext context = contexts[i];
            context.stop(_gracefulStop);
        }

        if (_notFoundContext != null) {
            _notFoundContext.stop();
            removeComponent(_notFoundContext);
        }
        _notFoundContext = null;

        if (_requestLog != null && _requestLog.isStarted())
            _requestLog.stop();
    }

    /* ------------------------------------------------------------ */
    /** Stop all listeners then all contexts.
     * @param graceful If true and statistics are on for a context,
     * then this method will wait for requestsActive to go to zero
     * before stopping that context.
     */
    public synchronized void stop(boolean graceful) throws InterruptedException {
        boolean ov = _gracefulStop;
        try {
            _gracefulStop = graceful;
            stop();
        } finally {
            _gracefulStop = ov;
        }
    }

    /* ------------------------------------------------------------ */
    /** Join the listeners.
     * Join all listeners that are instances of ThreadPool.
     * @exception InterruptedException 
     */
    public void join() throws InterruptedException {
        for (int l = 0; l < _listeners.size(); l++) {
            HttpListener listener = (HttpListener) _listeners.get(l);
            if (listener.isStarted() && listener instanceof ThreadPool) {
                ((ThreadPool) listener).join();
            }
        }
    }

    /* ------------------------------------------------------------ */
    /** Define a virtual host alias.
     * All requests to the alias are handled the same as request for
     * the virtualHost.
     * @deprecated Use HttpContext.addVirtualHost
     * @param virtualHost Host name or IP
     * @param alias Alias hostname or IP
     */
    public void addHostAlias(String virtualHost, String alias) {
        log.warn("addHostAlias is deprecated. Use HttpContext.addVirtualHost");
        Object contextMap = _virtualHostMap.get(virtualHost);
        if (contextMap == null)
            throw new IllegalArgumentException("No Such Host: " + virtualHost);
        _virtualHostMap.put(alias, contextMap);
    }

    /* ------------------------------------------------------------ */
    /** Set the request log.
     * @param log RequestLog to use.
     */
    public synchronized void setRequestLog(RequestLog log) {
        if (_requestLog != null)
            removeComponent(_requestLog);
        _requestLog = log;
        if (_requestLog != null)
            addComponent(_requestLog);
    }

    /* ------------------------------------------------------------ */
    public RequestLog getRequestLog() {
        return _requestLog;
    }

    /* ------------------------------------------------------------ */
    /** Log a request to the request log
     * @param request The request.
     * @param response The response generated.
     * @param length The length of the body.
     */
    void log(HttpRequest request, HttpResponse response, int length) {
        if (_requestLog != null && request != null && response != null)
            _requestLog.log(request, response, length);
    }

    /* ------------------------------------------------------------ */
    /** Service a request.
     * Handle the request by passing it to the HttpHandler contained in
     * the mapped HttpContexts.
     * The requests host and path are used to select a list of
     * HttpContexts. Each HttpHandler in these context is offered
     * the request in turn, until the request is handled.
     *
     * If no handler handles the request, 404 Not Found is returned.
     *
     * @param request 
     * @param response
     * @return The HttpContext that completed handling of the request or null.
     * @exception IOException 
     * @exception HttpException 
     */
    public HttpContext service(HttpRequest request, HttpResponse response) throws IOException, HttpException {
        String host = request.getHost();

        if (_requestsPerGC > 0 && _gcRequests++ > _requestsPerGC) {
            _gcRequests = 0;
            System.gc();
        }

        while (true) {
            PathMap contextMap = (PathMap) _virtualHostMap.get(host);
            if (contextMap != null) {
                List contextLists = contextMap.getMatches(request.getPath());
                if (contextLists != null) {
                    if (log.isTraceEnabled())
                        log.trace("Contexts at " + request.getPath() + ": " + contextLists);

                    for (int i = 0; i < contextLists.size(); i++) {
                        Map.Entry entry = (Map.Entry) contextLists.get(i);
                        List contextList = (List) entry.getValue();

                        for (int j = 0; j < contextList.size(); j++) {
                            HttpContext context = (HttpContext) contextList.get(j);

                            if (log.isDebugEnabled())
                                log.debug("Try " + context + "," + j);

                            context.handle(request, response);
                            if (request.isHandled())
                                return context;
                        }
                    }
                }
            }

            // try no host
            if (host == null)
                break;
            host = null;
        }

        synchronized (this) {
            if (_notFoundContext == null) {
                _notFoundContext = new HttpContext();
                _notFoundContext.setContextPath("/");
                _notFoundContext.setHttpServer(this);

                try {
                    _notFoundContext.addHandler((NotFoundHandler) Class
                            .forName("net.lightbody.bmp.proxy.jetty.http.handler.RootNotFoundHandler")
                            .newInstance());
                } catch (Exception e) {
                    _notFoundContext.addHandler(new NotFoundHandler());
                }

                addComponent(_notFoundContext);
                try {
                    _notFoundContext.start();
                } catch (Exception e) {
                    log.warn(LogSupport.EXCEPTION, e);
                }
            }

            _notFoundContext.handle(request, response);
            if (!request.isHandled())
                response.sendError(HttpResponse.__404_Not_Found);
            return _notFoundContext;
        }
    }

    /* ------------------------------------------------------------ */
    /** Find handler.
     * Find a handler for a URI.  This method is provided for
     * the servlet context getContext method to search for another
     * context by URI.  A list of hosts may be passed to qualify the
     * search.
     * @param uri URI that must be satisfied by the servlet handler 
     * @param vhosts null or a list of virtual hosts names to search
     * @return HttpHandler
     */
    public HttpHandler findHandler(Class handlerClass, String uri, String[] vhosts) {
        uri = URI.stripPath(uri);

        if (vhosts == null || vhosts.length == 0)
            vhosts = __noVirtualHost;

        for (int h = 0; h < vhosts.length; h++) {
            String host = vhosts[h];

            PathMap contextMap = (PathMap) _virtualHostMap.get(host);
            if (contextMap != null) {
                List contextLists = contextMap.getMatches(uri);
                if (contextLists != null) {

                    for (int i = 0; i < contextLists.size(); i++) {
                        Map.Entry entry = (Map.Entry) contextLists.get(i);

                        List contextList = (List) entry.getValue();

                        for (int j = 0; j < contextList.size(); j++) {
                            HttpContext context = (HttpContext) contextList.get(j);

                            HttpHandler handler = context.getHandler(handlerClass);

                            if (handler != null)
                                return handler;
                        }
                    }
                }
            }
        }
        return null;
    }

    /* ------------------------------------------------------------ */
    public UserRealm addRealm(UserRealm realm) {
        return (UserRealm) _realmMap.put(realm.getName(), realm);
    }

    /* ------------------------------------------------------------ */
    /** Get a named UserRealm.
     * @param realmName The name of the realm or null.
     * @return The named realm. If the name is null and only a single realm
     * is known, that is returned.
     */
    public UserRealm getRealm(String realmName) {
        if (realmName == null) {
            if (_realmMap.size() == 1)
                return (UserRealm) _realmMap.values().iterator().next();
            log.warn("Null realmName with multiple known realms");
        }
        return (UserRealm) _realmMap.get(realmName);
    }

    /* ------------------------------------------------------------ */
    public UserRealm removeRealm(String realmName) {
        return (UserRealm) _realmMap.remove(realmName);
    }

    /* ------------------------------------------------------------ */
    public Map getHostMap() {
        return _virtualHostMap;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @return True if the remote host name of connections is resolved.
     */
    public boolean getResolveRemoteHost() {
        return _resolveRemoteHost;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @param resolveRemoteHost True if the remote host name of connections is resolved.
     */
    public void setResolveRemoteHost(boolean resolveRemoteHost) {
        _resolveRemoteHost = resolveRemoteHost;
    }

    /* ------------------------------------------------------------ */
    private boolean _statsOn = false;
    private transient Object _statsLock = new Object[0];

    private transient long _statsStartedAt = 0;

    private transient int _connections; // total number of connections made to server

    private transient int _connectionsOpen; // number of connections currently open
    private transient int _connectionsOpenMin; // min number of connections open simultaneously
    private transient int _connectionsOpenMax; // max number of connections open simultaneously

    private transient long _connectionsDurationMin; // min duration of a connection
    private transient long _connectionsDurationMax; // max duration of a connection
    private transient long _connectionsDurationTotal; // total duration of all coneection

    private transient int _errors; // total bad requests to the server
    private transient int _requests; // total requests made to the server

    private transient int _requestsActive; // number of requests currently being handled
    private transient int _requestsActiveMin; // min number of connections handled simultaneously
    private transient int _requestsActiveMax; // max number of connections handled simultaneously

    private transient int _connectionsRequestsMin; // min requests per connection
    private transient int _connectionsRequestsMax; // max requests per connection

    private transient long _requestsDurationMin; // min request duration
    private transient long _requestsDurationMax; // max request duration
    private transient long _requestsDurationTotal; // total request duration

    /* ------------------------------------------------------------ */
    /** Reset statistics.
     */
    public void statsReset() {
        _statsStartedAt = System.currentTimeMillis();

        _connections = 0;

        _connectionsOpenMin = _connectionsOpen;
        _connectionsOpenMax = _connectionsOpen;
        _connectionsOpen = 0;

        _connectionsDurationMin = 0;
        _connectionsDurationMax = 0;
        _connectionsDurationTotal = 0;

        _errors = 0;
        _requests = 0;

        _requestsActiveMin = _requestsActive;
        _requestsActiveMax = _requestsActive;
        _requestsActive = 0;

        _connectionsRequestsMin = 0;
        _connectionsRequestsMax = 0;

        _requestsDurationMin = 0;
        _requestsDurationMax = 0;
        _requestsDurationTotal = 0;
    }

    /* ------------------------------------------------------------ */
    public void setStatsOn(boolean on) {
        log.info("Statistics on = " + on + " for " + this);
        _statsOn = on;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @return True if statistics collection is turned on.
     */
    public boolean getStatsOn() {
        return _statsOn;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @return Timestamp stats were started at.
     */
    public long getStatsOnMs() {
        return _statsOn ? (System.currentTimeMillis() - _statsStartedAt) : 0;
    }

    /* ------------------------------------------------------------ */
    /**
     * @return Returns the connectionsDurationMin.
     */
    public long getConnectionsDurationMin() {
        return _connectionsDurationMin;
    }

    /* ------------------------------------------------------------ */
    /**
     * @return Returns the connectionsDurationTotal.
     */
    public long getConnectionsDurationTotal() {
        return _connectionsDurationTotal;
    }

    /* ------------------------------------------------------------ */
    /**
     * @return Returns the connectionsOpenMin.
     */
    public int getConnectionsOpenMin() {
        return _connectionsOpenMin;
    }

    /* ------------------------------------------------------------ */
    /**
     * @return Returns the connectionsRequestsMin.
     */
    public int getConnectionsRequestsMin() {
        return _connectionsRequestsMin;
    }

    /* ------------------------------------------------------------ */
    /**
     * @return Returns the requestsActiveMin.
     */
    public int getRequestsActiveMin() {
        return _requestsActiveMin;
    }

    /* ------------------------------------------------------------ */
    /**
     * @return Returns the requestsDurationMin.
     */
    public long getRequestsDurationMin() {
        return _requestsDurationMin;
    }

    /* ------------------------------------------------------------ */
    /**
     * @return Returns the requestsDurationTotal.
     */
    public long getRequestsDurationTotal() {
        return _requestsDurationTotal;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @return Number of connections accepted by the server since
     * statsReset() called. Undefined if setStatsOn(false).
     */
    public int getConnections() {
        return _connections;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @return Number of connections currently open that were opened
     * since statsReset() called. Undefined if setStatsOn(false).
     */
    public int getConnectionsOpen() {
        return _connectionsOpen;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @return Maximum number of connections opened simultaneously
     * since statsReset() called. Undefined if setStatsOn(false).
     */
    public int getConnectionsOpenMax() {
        return _connectionsOpenMax;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @return Average duration in milliseconds of open connections
     * since statsReset() called. Undefined if setStatsOn(false).
     */
    public long getConnectionsDurationAve() {
        return _connections == 0 ? 0 : (_connectionsDurationTotal / _connections);
    }

    /* ------------------------------------------------------------ */
    /** 
     * @return Maximum duration in milliseconds of an open connection
     * since statsReset() called. Undefined if setStatsOn(false).
     */
    public long getConnectionsDurationMax() {
        return _connectionsDurationMax;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @return Average number of requests per connection
     * since statsReset() called. Undefined if setStatsOn(false).
     */
    public int getConnectionsRequestsAve() {
        return _connections == 0 ? 0 : (_requests / _connections);
    }

    /* ------------------------------------------------------------ */
    /** 
     * @return Maximum number of requests per connection
     * since statsReset() called. Undefined if setStatsOn(false).
     */
    public int getConnectionsRequestsMax() {
        return _connectionsRequestsMax;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @return Number of errors generated while handling requests.
     * since statsReset() called. Undefined if setStatsOn(false).
     */
    public int getErrors() {
        return _errors;
    }

    /* ------------------------------------------------------------ */
    /** 
     * @return Number of requests
     * since statsReset() called. Undefined if setStatsOn(false).
     */
    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 Average duration of request handling in milliseconds 
     * since statsReset() called. Undefined if setStatsOn(false).
     */
    public long getRequestsDurationAve() {
        return _requests == 0 ? 0 : (_requestsDurationTotal / _requests);
    }

    /* ------------------------------------------------------------ */
    /** 
     * @return Get maximum duration in milliseconds of request handling
     * since statsReset() called. Undefined if setStatsOn(false).
     */
    public long getRequestsDurationMax() {
        return _requestsDurationMax;
    }

    /* ------------------------------------------------------------ */
    void statsOpenConnection() {
        synchronized (_statsLock) {
            _connectionsOpen++;
            if (_connectionsOpen > _connectionsOpenMax)
                _connectionsOpenMax = _connectionsOpen;
        }
    }

    /* ------------------------------------------------------------ */
    void statsGotRequest() {
        synchronized (_statsLock) {
            _requestsActive++;
            if (_requestsActive > _requestsActiveMax)
                _requestsActiveMax = _requestsActive;
        }
    }

    /* ------------------------------------------------------------ */
    void statsEndRequest(long duration, boolean ok) {
        synchronized (_statsLock) {
            _requests++;
            _requestsActive--;
            if (_requestsActive < 0)
                _requestsActive = 0;
            if (_requestsActive < _requestsActiveMin)
                _requestsActiveMin = _requestsActive;

            if (ok) {
                _requestsDurationTotal += duration;
                if (_requestsDurationMin == 0 || duration < _requestsDurationMin)
                    _requestsDurationMin = duration;
                if (duration > _requestsDurationMax)
                    _requestsDurationMax = duration;
            } else
                _errors++;
        }
    }

    /* ------------------------------------------------------------ */
    void statsCloseConnection(long duration, int requests) {
        synchronized (_statsLock) {
            _connections++;
            _connectionsOpen--;
            _connectionsDurationTotal += duration;
            if (_connectionsOpen < 0)
                _connectionsOpen = 0;
            if (_connectionsOpen < _connectionsOpenMin)
                _connectionsOpenMin = _connectionsOpen;
            if (_connectionsDurationMin == 0 || duration < _connectionsDurationMin)
                _connectionsDurationMin = duration;
            if (duration > _connectionsDurationMax)
                _connectionsDurationMax = duration;
            if (_connectionsRequestsMin == 0 || requests < _connectionsRequestsMin)
                _connectionsRequestsMin = requests;
            if (requests > _connectionsRequestsMax)
                _connectionsRequestsMax = requests;
        }
    }

    /* ------------------------------------------------------------ */
    /** Save the HttpServer
     * The server is saved by serialization to the given filename or URL.
     *
     * @param saveat A file or URL to save the configuration at. 
     * @exception MalformedURLException 
     * @exception IOException 
     */
    public void save(String saveat) throws MalformedURLException, IOException {
        Resource resource = Resource.newResource(saveat);
        ObjectOutputStream out = new ObjectOutputStream(resource.getOutputStream());
        out.writeObject(this);
        out.flush();
        out.close();
        log.info("Saved " + this + " to " + resource);
    }

    /* ------------------------------------------------------------ */
    /** Destroy a stopped server.
     * Remove all components and send notifications to all event
     * listeners. The HttpServer must be stopped before it can be destroyed.
     */
    public void destroy() {
        __servers.remove(this);
        if (isStarted())
            throw new IllegalStateException("Started");
        if (_listeners != null)
            _listeners.clear();
        _listeners = null;
        if (_virtualHostMap != null)
            _virtualHostMap.clear();
        _virtualHostMap = null;
        _notFoundContext = null;

        super.destroy();
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /** Construct server from command line arguments.
     * @param args 
     */
    public static void main(String[] args) {
        if (args.length == 0 || args.length > 2) {
            System.err.println("\nUsage - java net.lightbody.bmp.proxy.jetty.http.HttpServer [<addr>:]<port>");
            System.err.println("\nUsage - java net.lightbody.bmp.proxy.jetty.http.HttpServer -r [savefile]");
            System.err.println("  Serves files from '.' directory");
            System.err.println("  Dump handler for not found requests");
            System.err.println("  Default port is 8080");
            System.exit(1);
        }

        try {

            if (args.length == 1) {
                // Create the server
                HttpServer server = new HttpServer();

                // Default is no virtual host
                String host = null;
                HttpContext context = server.getContext(host, "/");
                context.setResourceBase(".");
                context.addHandler(new ResourceHandler());
                context.addHandler(new DumpHandler());
                context.addHandler(new NotFoundHandler());

                InetAddrPort address = new InetAddrPort(args[0]);
                server.addListener(address);

                server.start();
            } else {
                Resource resource = Resource.newResource(args[1]);
                ObjectInputStream in = new ObjectInputStream(resource.getInputStream());
                HttpServer server = (HttpServer) in.readObject();
                in.close();
                server.start();
            }

        } catch (Exception e) {
            log.warn(LogSupport.EXCEPTION, e);
        }
    }
}