// ======================================================================== // Copyright (c) 1999 Mort Bay Consulting (Australia) Pty. Ltd. // $Id: HttpServer.java,v 1.17.2.13 2003/07/11 00:55:12 jules_gosnell Exp $ // ======================================================================== package org.mortbay.http; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EventListener; import java.util.EventObject; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.WeakHashMap; import org.mortbay.http.handler.DumpHandler; import org.mortbay.http.handler.NotFoundHandler; import org.mortbay.http.handler.ResourceHandler; import org.mortbay.util.Code; import org.mortbay.util.InetAddrPort; import org.mortbay.util.LifeCycle; import org.mortbay.util.Log; import org.mortbay.util.MultiException; import org.mortbay.util.Resource; import org.mortbay.util.StringMap; import org.mortbay.util.ThreadPool; import org.mortbay.util.URI; /* ------------------------------------------------------------ */ /** 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 org.mortbay.jetty.Server * @version $Id: HttpServer.java,v 1.17.2.13 2003/07/11 00:55:12 jules_gosnell Exp $ * @author Greg Wilkins (gregw) */ public class HttpServer implements LifeCycle, Serializable { /* ------------------------------------------------------------ */ 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 transient int _gcRequests; private transient HttpContext _notFoundContext=null; private transient List _eventListeners; private transient List _components; /* ------------------------------------------------------------ */ private boolean _statsOn=false; private transient Object _statsLock=new Object[0]; private transient long _statsStartedAt=0; private transient int _connections; private transient int _connectionsOpen; private transient int _connectionsOpenMax; private transient long _connectionsDurationAve; private transient long _connectionsDurationMax; private transient int _connectionsRequestsAve; private transient int _connectionsRequestsMax; private transient int _errors; private transient int _requests; private transient int _requestsActive; private transient int _requestsActiveMax; private transient long _requestsDurationAve; private transient long _requestsDurationMax; /* ------------------------------------------------------------ */ /** 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); } /* ------------------------------------------------------------ */ /** * @param listeners Array of HttpListeners. */ public void setListeners(HttpListener[] listeners) { List old = new ArrayList(_listeners); for (int i=0;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); Code.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;h0 && _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 (Code.verbose(99)) Code.debug("Contexts at ",request.getPath(), ": ",contextLists); for (int i=0;i _connectionsOpenMax) _connectionsOpenMax=_connectionsOpen; } } /* ------------------------------------------------------------ */ void statsGotRequest() { synchronized(_statsLock) { if (++_requestsActive > _requestsActiveMax) _requestsActiveMax=_requestsActive; } } /* ------------------------------------------------------------ */ void statsEndRequest(long duration,boolean ok) { synchronized(_statsLock) { _requests++; if (!ok) _errors++; if (--_requestsActive<0) _requestsActive=0; if (duration>_requestsDurationMax) _requestsDurationMax=duration; if (_requestsDurationAve==0) _requestsDurationAve=duration*128; _requestsDurationAve=_requestsDurationAve-_requestsDurationAve/128+duration; } } /* ------------------------------------------------------------ */ void statsCloseConnection(long duration,int requests) { synchronized(_statsLock) { _connections++; _connectionsOpen--; if (_connectionsOpen<0) _connectionsOpen=0; if (duration>_connectionsDurationMax) _connectionsDurationMax=duration; if (_connectionsDurationAve==0) _connectionsDurationAve=128*duration; _connectionsDurationAve=_connectionsDurationAve-_connectionsDurationAve/128+duration; if (requests>_connectionsRequestsMax) _connectionsRequestsMax=requests; if (_connectionsRequestsAve==0) _connectionsRequestsAve=16; _connectionsRequestsAve=_connectionsRequestsAve-_connectionsRequestsAve/16+requests; } } /* ------------------------------------------------------------ */ private void addComponent(Object o) { Code.debug("add component: ",o); if (_components==null) _components=new ArrayList(); _components.add(o); if (_eventListeners!=null) { ComponentEvent event = new ComponentEvent(o); for(int i=0;i<_eventListeners.size();i++) { EventListener listener = (EventListener)_eventListeners.get(i); if (listener instanceof ComponentEventListener) ((ComponentEventListener)listener).addComponent(event); } } } /* ------------------------------------------------------------ */ private void removeComponent(Object o) { Code.debug("remove component: ",o); if (_components.remove(o) && _eventListeners!=null) { ComponentEvent event = new ComponentEvent(o); for(int i=0;i<_eventListeners.size();i++) { EventListener listener = (EventListener)_eventListeners.get(i); if (listener instanceof ComponentEventListener) ((ComponentEventListener)listener).removeComponent(event); } } } /* ------------------------------------------------------------ */ /** Add a server event listener. * Listeners are sent HttpServer.ComponentEvent instances when components * such as listeners and contexts are added to the HttpServer. * @param listener HttpServer.ComponentEventListener */ public void addEventListener(EventListener listener) { Code.debug("addEventListener: ",listener); if (_eventListeners==null) _eventListeners=new ArrayList(); if (listener instanceof ComponentEventListener) _eventListeners.add(listener); else Code.warning("Not a ComponentEventListener: "+listener); } /* ------------------------------------------------------------ */ public void removeEventListener(EventListener listener) { Code.debug("removeEventListener: ",listener); _eventListeners.remove(listener); } /* ------------------------------------------------------------ */ /** 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.event("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; if (_components!=null && _eventListeners!=null) { for (int c=0;c<_components.size();c++) { Object o=_components.get(c); if (o instanceof HttpContext ) ((HttpContext)o).destroy(); if (_eventListeners!=null) { ComponentEvent event = new ComponentEvent(o); for(int i=0;i<_eventListeners.size();i++) { EventListener listener = (EventListener)_eventListeners.get(i); if (listener instanceof ComponentEventListener) ((ComponentEventListener)listener).removeComponent(event); } } } } if (_components!=null) _components.clear(); _components=null; if (_eventListeners!=null) { ComponentEvent event = new ComponentEvent(this); for(int i=0;i<_eventListeners.size();i++) { EventListener listener = (EventListener)_eventListeners.get(i); if (listener instanceof ComponentEventListener) ((ComponentEventListener)listener).removeComponent(event); } } if (_eventListeners!=null) _eventListeners.clear(); _eventListeners=null; } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ /** 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 org.mortbay.http.HttpServer [:]"); System.err.println ("\nUsage - java org.mortbay.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) { Code.warning(e); } } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ public class ComponentEvent extends EventObject { private Object component; private ComponentEvent(Object component) { super(HttpServer.this); this.component=component; } public Object getComponent() { return component; } } /* ------------------------------------------------------------ */ /* ------------------------------------------------------------ */ public interface ComponentEventListener extends EventListener { public void addComponent(ComponentEvent event); public void removeComponent(ComponentEvent event); } }