Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/mortbay/http/HttpServer.java


1   // ========================================================================
2   // Copyright (c) 1999 Mort Bay Consulting (Australia) Pty. Ltd.
3   // $Id: HttpServer.java,v 1.54 2003/11/22 16:06:02 gregwilkins Exp $
4   // ========================================================================
5   
6   package org.mortbay.http;
7   
8   import java.io.IOException;
9   import java.io.ObjectInputStream;
10  import java.io.ObjectOutputStream;
11  import java.io.Serializable;
12  import java.net.MalformedURLException;
13  import java.util.ArrayList;
14  import java.util.Arrays;
15  import java.util.Collection;
16  import java.util.Collections;
17  import java.util.EventListener;
18  import java.util.EventObject;
19  import java.util.HashMap;
20  import java.util.Iterator;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.WeakHashMap;
24  
25  import org.apache.commons.logging.Log;
26  import org.apache.commons.logging.LogFactory;
27  import org.mortbay.http.handler.DumpHandler;
28  import org.mortbay.http.handler.NotFoundHandler;
29  import org.mortbay.http.handler.ResourceHandler;
30  import org.mortbay.util.InetAddrPort;
31  import org.mortbay.util.LifeCycle;
32  import org.mortbay.util.LogSupport;
33  import org.mortbay.util.MultiException;
34  import org.mortbay.util.Resource;
35  import org.mortbay.util.StringMap;
36  import org.mortbay.util.ThreadPool;
37  import org.mortbay.util.URI;
38  
39  
40  
41  /* ------------------------------------------------------------ */
42  /** HTTP Server.
43   * Services HTTP requests by maintaining a mapping between
44   * a collection of HttpListeners which generate requests and
45   * HttpContexts which contain collections of HttpHandlers.
46   *
47   * This class is configured by API calls.  The
48   * org.mortbay.jetty.Server class uses XML configuration files to
49   * configure instances of this class.
50   *
51   * The HttpServer implements the BeanContext API so that membership
52   * events may be generated for HttpListeners, HttpContexts and WebApplications.
53   *
54   * @see HttpContext
55   * @see HttpHandler
56   * @see HttpConnection
57   * @see HttpListener
58   * @see org.mortbay.jetty.Server
59   * @version $Id: HttpServer.java,v 1.54 2003/11/22 16:06:02 gregwilkins Exp $
60   * @author Greg Wilkins (gregw)
61   */
62  public class HttpServer implements LifeCycle,
63                                     Serializable
64  {
65      private static Log log = LogFactory.getLog(HttpServer.class);
66      private static LogSupport logx = new LogSupport();
67      
68      /* ------------------------------------------------------------ */
69      private static WeakHashMap __servers = new WeakHashMap();
70      private static Collection __roServers =
71          Collections.unmodifiableCollection(__servers.keySet());
72      private static String[] __noVirtualHost=new String[1];
73      
74      /* ------------------------------------------------------------ */
75      /** Get HttpServer Collection.
76       * Get a collection of all known HttpServers.  Servers can be
77       * removed from this list with the setAnonymous call.
78       * @return  Collection of all servers.
79       */
80      public static Collection getHttpServers()
81      {
82          return __roServers;
83      }
84  
85      /* ------------------------------------------------------------ */
86      /** 
87       * @deprecated User getHttpServers()
88       */
89      public static List getHttpServerList()
90      {
91          return new ArrayList(__roServers);
92      }
93      
94      /* ------------------------------------------------------------ */
95      private List _listeners = new ArrayList(3);
96      private HashMap _realmMap = new HashMap(3);    
97      private StringMap _virtualHostMap = new StringMap();
98      private boolean _trace=false;
99      private RequestLog _requestLog;
100     private int _requestsPerGC ;
101     private boolean _resolveRemoteHost =false;
102     
103     private transient int _gcRequests;
104     private transient HttpContext _notFoundContext=null;
105     private transient List _eventListeners;
106     private transient List _components;
107     
108     /* ------------------------------------------------------------ */
109     private boolean _statsOn=false;
110     private transient Object _statsLock=new Object[0];
111     private transient long _statsStartedAt=0;
112     private transient int _connections;
113     private transient int _connectionsOpen;
114     private transient int _connectionsOpenMax;
115     private transient long _connectionsDurationAve;
116     private transient long _connectionsDurationMax;
117     private transient int _connectionsRequestsAve;
118     private transient int _connectionsRequestsMax;
119 
120     private transient int _errors;
121     private transient int _requests;
122     private transient int _requestsActive;
123     private transient int _requestsActiveMax;
124     private transient long _requestsDurationAve;
125     private transient long _requestsDurationMax;
126 
127     
128     /* ------------------------------------------------------------ */
129     /** Constructor. 
130      */
131     public HttpServer()
132     {
133         this(false);
134     }
135     
136     /* ------------------------------------------------------------ */
137     /** Constructor. 
138      * @param anonymous If true, the server is not included in the
139      * static server lists and stopAll methods.
140      */
141     public HttpServer(boolean anonymous)
142     {
143         setAnonymous(anonymous);
144         _virtualHostMap.setIgnoreCase(true);
145     }
146     
147     /* ------------------------------------------------------------ */
148     private void readObject(java.io.ObjectInputStream in)
149         throws IOException, ClassNotFoundException
150     {
151         in.defaultReadObject();
152         HttpListener[] listeners=getListeners();
153         HttpContext[] contexts=getContexts();
154         _listeners.clear();
155         _virtualHostMap.clear();
156         setContexts(contexts);
157         setListeners(listeners);
158         _statsLock=new Object[0];
159     }
160  
161     
162     /* ------------------------------------------------------------ */
163     /** 
164      * @param anonymous If true, the server is not included in the
165      * static server lists and stopAll methods.
166      */
167     public void setAnonymous(boolean anonymous)
168     {
169         if (anonymous)
170             __servers.remove(this);
171         else
172             __servers.put(this,__servers);
173     }
174 
175 
176 
177     /* ------------------------------------------------------------ */
178     /** 
179      * @param listeners Array of HttpListeners.
180      */
181     public void setListeners(HttpListener[] listeners)
182     {
183         List old = new ArrayList(_listeners);
184         
185         for (int i=0;i<listeners.length;i++)
186         {
187             boolean existing=old.remove(listeners[i]);
188             if (!existing)
189                 addListener(listeners[i]);
190         }
191 
192         for (int i=0;i<old.size();i++)
193         {
194             HttpListener listener=(HttpListener)old.get(i);
195             removeListener(listener);
196         }
197     }
198     
199     /* ------------------------------------------------------------ */
200     /** 
201      * @return Array of HttpListeners.
202      */
203     public HttpListener[] getListeners()
204     {
205         if (_listeners==null)
206             return new HttpListener[0];
207         HttpListener[] listeners=new HttpListener[_listeners.size()];
208         return (HttpListener[])_listeners.toArray(listeners);
209     }
210     
211     
212     /* ------------------------------------------------------------ */
213     /** Create and add a SocketListener.
214      * Conveniance method.
215      * @param address
216      * @return the HttpListener.
217      * @exception IOException 
218      */
219     public HttpListener addListener(String address)
220         throws IOException
221     {
222         return addListener(new InetAddrPort(address));
223     }
224     
225     /* ------------------------------------------------------------ */
226     /** Create and add a SocketListener.
227      * Conveniance method.
228      * @param address
229      * @return the HttpListener.
230      * @exception IOException 
231      */
232     public HttpListener addListener(InetAddrPort address)
233         throws IOException
234     {
235         HttpListener listener = new SocketListener(address);
236         listener.setHttpServer(this);
237         _listeners.add(listener);
238         addComponent(listener);
239         return listener;
240     }
241     
242     /* ------------------------------------------------------------ */
243     /** Add a HTTP Listener to the server.
244      * @param listener The Listener.
245      * @exception IllegalArgumentException If the listener is not for this
246      * server.
247      */
248     public HttpListener addListener(HttpListener listener)
249         throws IllegalArgumentException
250     {
251         listener.setHttpServer(this);        
252         _listeners.add(listener);
253         addComponent(listener);
254         return listener;
255     }
256     
257     /* ------------------------------------------------------------ */
258     /** Remove a HTTP Listener.
259      * @param listener 
260      */
261     public void removeListener(HttpListener listener)
262     {
263         if (listener==null)
264             return;
265         
266         for (int l=0;l<_listeners.size();l++)
267         {
268             if (listener.equals(_listeners.get(l)))
269             {
270                 _listeners.remove(l);
271                 removeComponent(listener);
272                 if (listener.isStarted())
273                     try{listener.stop();}catch(InterruptedException e){log.warn(LogSupport.EXCEPTION,e);}
274                 listener.setHttpServer(null);
275             }
276         }
277     }
278 
279     
280     /* ------------------------------------------------------------ */
281     public synchronized void setContexts(HttpContext[] contexts)
282     {
283         List old = Arrays.asList(getContexts());
284         
285         for (int i=0;i<contexts.length;i++)
286         {
287             boolean existing=old.remove(contexts[i]);
288             if (!existing)
289                 addContext(contexts[i]);
290         }
291 
292         for (int i=0;i<old.size();i++)
293             removeContext((HttpContext)old.get(i));
294     }
295 
296     
297     /* ------------------------------------------------------------ */
298     public synchronized HttpContext[] getContexts()
299     {
300         if (_virtualHostMap==null)
301             return new HttpContext[0];
302         
303         ArrayList contexts = new ArrayList(33);
304         Iterator maps=_virtualHostMap.values().iterator();
305         while (maps.hasNext())
306         {
307             PathMap pm=(PathMap)maps.next();
308             Iterator lists=pm.values().iterator();
309             while(lists.hasNext())
310             {
311                 List list=(List)lists.next();
312                 for (int i=0;i<list.size();i++)
313                 {
314                     HttpContext context=(HttpContext)list.get(i);
315                     if (!contexts.contains(context))
316                         contexts.add(context);
317                 }
318             }
319         }
320         return (HttpContext[])contexts.toArray(new HttpContext[contexts.size()]);
321     }
322 
323 
324 
325     /* ------------------------------------------------------------ */
326     /** Add a context.
327      * @param context 
328      */
329     public HttpContext addContext(HttpContext context)
330     {
331         if (context.getContextPath()==null ||
332             context.getContextPath().length()==0)
333             throw new IllegalArgumentException("No Context Path Set");
334         boolean existing=removeMappings(context);
335         if (!existing)
336         {
337             context.setHttpServer(this);
338             addComponent(context);
339         }
340         addMappings(context);
341         return context;
342     }
343 
344 
345     /* ------------------------------------------------------------ */
346     /** Remove a context or Web application.
347      * @exception IllegalStateException if context not stopped
348      */
349     public boolean removeContext(HttpContext context)
350         throws IllegalStateException
351     {
352         if (removeMappings(context))
353         {
354             removeComponent(context);
355             if (context.isStarted())
356                 try{context.stop();} catch (InterruptedException e){log.warn(LogSupport.EXCEPTION,e);}
357             context.setHttpServer(null);
358             return true;
359         }
360         return false;
361     }
362     
363 
364     /* ------------------------------------------------------------ */
365     /** Add a context.
366      * As contexts cannot be publicly created, this may be used to
367      * alias an existing context.
368      * @param virtualHost The virtual host or null for all hosts.
369      * @param context 
370      */
371     public HttpContext addContext(String virtualHost,
372                                   HttpContext context)
373     {
374         if (virtualHost!=null)
375             context.addVirtualHost(virtualHost);
376         addContext(context);
377         return context;
378     }
379 
380 
381     /* ------------------------------------------------------------ */
382     /** Create and add a new context.
383      * Note that multiple contexts can be created for the same
384      * virtualHost and contextPath. Requests are offered to multiple
385      * contexts in the order they where added to the HttpServer.
386      * @param contextPath
387      * @return A HttpContext instance created by a call to newHttpContext.
388      */
389     public HttpContext addContext(String contextPath)
390     {
391         HttpContext hc = newHttpContext();
392         hc.setContextPath(contextPath);
393         addContext(hc);
394         return hc;
395     }
396     
397     /* ------------------------------------------------------------ */
398     /** Create and add a new context.
399      * Note that multiple contexts can be created for the same
400      * virtualHost and contextPath. Requests are offered to multiple
401      * contexts in the order they where added to the HttpServer.
402      * @param virtualHost Virtual hostname or null for all hosts.
403      * @param contextPathSpec Path specification relative to the context path.
404      * @return A HttpContext instance created by a call to newHttpContext.
405      */
406     public HttpContext addContext(String virtualHost, String contextPathSpec)
407     {
408         if (virtualHost!=null && virtualHost.length()==0)
409             virtualHost=null;
410         HttpContext hc = newHttpContext();
411         hc.setContextPath(contextPathSpec);
412         if (virtualHost!=null)
413             hc.addVirtualHost(virtualHost);
414         addContext(hc);
415         return hc;
416     }
417     
418     
419     /* ------------------------------------------------------------ */
420     /** Get specific context. 
421      * @param virtualHost The virtual host or null for all hosts.
422      * @param contextPathSpec Path specification relative to the context path.
423      * @param i Index among contexts of same virtualHost and pathSpec.
424      * @return The HttpContext or null.
425      */
426     public HttpContext getContext(String virtualHost, String contextPathSpec, int i)
427     {
428         HttpContext hc=null;
429         contextPathSpec=HttpContext.canonicalContextPathSpec(contextPathSpec);
430 
431         PathMap contextMap=(PathMap)_virtualHostMap.get(virtualHost);
432         if (contextMap!=null)
433         {
434             List contextList = (List)contextMap.get(contextPathSpec);
435             if (contextList!=null)
436             {
437                 if (i>=contextList.size())
438                     return null;
439                 hc=(HttpContext)contextList.get(i);
440             }
441         }
442 
443         return hc;
444     }
445 
446     
447     /* ------------------------------------------------------------ */
448     /** Get or create context. 
449      * @param virtualHost The virtual host or null for all hosts.
450      * @param contextPathSpec
451      * @return HttpContext. If multiple contexts exist for the same
452      * virtualHost and pathSpec, the most recently added context is returned.
453      * If no context exists, a new context is created by a call to newHttpContext.
454      */
455     public HttpContext getContext(String virtualHost, String contextPathSpec)
456     { 
457         HttpContext hc=null;
458         contextPathSpec=HttpContext.canonicalContextPathSpec(contextPathSpec);
459 
460         PathMap contextMap=(PathMap)_virtualHostMap.get(virtualHost);
461         if (contextMap!=null)
462         {
463             List contextList = (List)contextMap.get(contextPathSpec);
464             if (contextList!=null && contextList.size()>0)
465                 hc=(HttpContext)contextList.get(contextList.size()-1);
466             
467         }
468         if (hc==null)
469             hc=addContext(virtualHost,contextPathSpec);
470 
471         return hc;
472     }
473     
474     /* ------------------------------------------------------------ */
475     /** Get or create context. 
476      * @param contextPathSpec Path specification relative to the context path.
477      * @return The HttpContext  If multiple contexts exist for the same
478      * pathSpec, the most recently added context is returned.
479      * If no context exists, a new context is created by a call to newHttpContext.
480      */
481     public HttpContext getContext(String contextPathSpec)
482     {
483   return getContext(null,contextPathSpec);
484     }    
485  
486     /* ------------------------------------------------------------ */
487     /** Create a new HttpContext.
488      * Specialized HttpServer classes may override this method to
489      * return subclasses of HttpContext.
490      * @return A new instance of HttpContext or a subclass of HttpContext
491      */
492     protected HttpContext newHttpContext()
493     {
494         return  new HttpContext();
495     }
496 
497     /* ------------------------------------------------------------ */    
498     synchronized void addMapping(String virtualHost, HttpContext context)
499     {
500         // Get the map of contexts
501         PathMap contextMap=(PathMap)_virtualHostMap.get(virtualHost);
502         if (contextMap==null)
503         {
504             contextMap=new PathMap(7);
505             _virtualHostMap.put(virtualHost,contextMap);
506         }
507         
508         // Generalize contextPath
509         String contextPathSpec=
510             HttpContext.canonicalContextPathSpec(context.getContextPath());
511         
512         // Get the list of contexts at this path
513         List contextList = (List)contextMap.get(contextPathSpec);
514         if (contextList==null)
515         {
516             contextList=new ArrayList(1);
517             contextMap.put(contextPathSpec,contextList);
518         }
519         
520         // Add the context to the list
521         contextList.add(context);
522             
523         if(log.isDebugEnabled())log.debug("Added "+context+" for host "+(virtualHost==null?"*":virtualHost));
524     }
525     
526 
527     /* ------------------------------------------------------------ */
528     synchronized void addMappings(HttpContext context)
529     {
530         if (context==_notFoundContext)
531             return;
532         
533         String[] hosts=context.getVirtualHosts();
534         if (hosts==null || hosts.length==0)
535             hosts = __noVirtualHost;
536 
537         // For each host name
538         for (int h=0;h<hosts.length;h++)
539         {
540             String virtualHost=hosts[h];
541             addMapping(virtualHost,context);
542         }
543     }
544 
545 
546     /* ------------------------------------------------------------ */
547     synchronized boolean removeMapping(String virtualHost, HttpContext context)
548     {
549         boolean existing=false;
550         if (_virtualHostMap!=null)
551         {
552             PathMap contextMap=(PathMap)_virtualHostMap.get(virtualHost);
553             
554             Iterator i2=contextMap.values().iterator();
555             while(i2.hasNext())
556             {
557                 List contextList = (List)i2.next();
558                 if (contextList.remove(context))
559                     existing=true;                
560                 if (contextList.size()==0)
561                     i2.remove();
562             }
563         }
564         return existing;
565     }
566     
567     /* ------------------------------------------------------------ */
568     synchronized boolean removeMappings(HttpContext context)
569     {
570         boolean existing=false;
571         
572         if (_virtualHostMap!=null)
573         {
574             Iterator i1 = _virtualHostMap.keySet().iterator();
575             while(i1.hasNext())
576             {
577                 String virtualHost=(String)i1.next();
578                 if (removeMapping(virtualHost,context))
579                     existing=true;
580             }
581         }
582         return existing;
583     }
584     
585     
586     /* ------------------------------------------------------------ */
587     /** 
588      * @return True if the TRACE method is fully implemented.
589      */
590     public boolean getTrace()
591     {
592         return _trace;
593     }
594     
595     /* ------------------------------------------------------------ */
596     /** 
597      * @param trace True if the TRACE method is fully implemented.
598      */
599     public void setTrace(boolean trace)
600     {
601         _trace = trace;
602     }
603     
604     /* ------------------------------------------------------------ */
605     /** Get the requests per GC.
606      * If this is set greater than zero, then the System garbage collector
607      * will be invoked after approximately this number of requests.  For
608      * predictable response, it is often best to have frequent small runs of
609      * the GC rather than infrequent large runs.  The request count is only
610      * approximate as it is not synchronized and multi CPU machines may miss
611      * counting some requests.
612      * @return Approx requests per garbage collection.
613      */
614     public int getRequestsPerGC()
615     {
616         return _requestsPerGC;
617     }
618 
619     /* ------------------------------------------------------------ */
620     /** Set the requests per GC.
621      * If this is set greater than zero, then the System garbage collector
622      * will be invoked after approximately this number of requests.  For
623      * predictable response, it is often best to have frequent small runs of
624      * the GC rather than infrequent large runs.  The request count is only
625      * approximate as it is not synchronized and multi CPU machines may miss
626      * counting some requests.
627      * @param requestsPerGC Approx requests per garbage collection.
628      */
629     public void setRequestsPerGC(int requestsPerGC)
630     {
631         _requestsPerGC = requestsPerGC;
632     }
633     
634     /* ------------------------------------------------------------ */
635     /** Start all handlers then listeners.
636      * If a subcomponent fails to start, it's exception is added to a
637      * org.mortbay.util.MultiException and the start method continues.
638      * @exception MultiException A collection of exceptions thrown by
639      * start() method of subcomponents of the HttpServer. 
640      */
641     public synchronized void start()
642         throws MultiException
643     {
644         log.info("Starting "+Version.__VersionImpl);
645         MultiException mex = new MultiException();
646 
647         statsReset();
648         
649         if (log.isDebugEnabled())
650         {
651             log.debug("LISTENERS: "+_listeners);
652             log.debug("HANDLER: "+_virtualHostMap);
653         }   
654 
655         if (_requestLog!=null && !_requestLog.isStarted())
656         {
657             try{
658                 _requestLog.start();
659                 log.info("Started "+_requestLog);
660             }
661             catch(Exception e){mex.add(e);}
662         }
663         
664         HttpContext[] contexts = getContexts();
665         for (int i=0;i<contexts.length;i++)
666         {
667             HttpContext context=contexts[i];
668             try{context.start();}catch(Exception e){mex.add(e);}
669         }
670         
671         for (int l=0;l<_listeners.size();l++)
672         {
673             HttpListener listener =(HttpListener)_listeners.get(l);
674             listener.setHttpServer(this);
675             if (!listener.isStarted())
676                 try{listener.start();}catch(Exception e){mex.add(e);}
677         }
678 
679         mex.ifExceptionThrowMulti();
680         log.info("Started "+this);
681     }
682     
683     /* ------------------------------------------------------------ */
684     public synchronized boolean isStarted()
685     {
686         for (int l=0;l<_listeners.size();l++)
687         {
688             HttpListener listener =(HttpListener)_listeners.get(l);
689             if (listener.isStarted())
690                 return true;
691         }
692         
693         return false;
694     }
695     
696     /* ------------------------------------------------------------ */
697     /** Stop all listeners then all contexts.
698      * Equivalent to stop(false);
699      * @exception InterruptedException If interrupted, stop may not have
700      * been called on everything.
701      */
702     public synchronized void stop()
703         throws InterruptedException
704     {
705         stop(false);
706     }
707     
708     /* ------------------------------------------------------------ */
709     /** Stop all listeners then all contexts.
710      * @param graceful If true and statistics are on for a context,
711      * then this method will wait for requestsActive to go to zero
712      * before stopping that context.
713      */
714     public synchronized void stop(boolean graceful)
715         throws InterruptedException
716     {
717         for (int l=0;l<_listeners.size();l++)
718         {
719             HttpListener listener =(HttpListener)_listeners.get(l); 
720             if (listener.isStarted())
721             {
722                 try{listener.stop();}
723                 catch(Exception e)
724                 {
725                     if (log.isDebugEnabled())
726                         log.warn(LogSupport.EXCEPTION,e);
727                     else
728                         log.warn(e.toString());
729                 }
730             }
731         }
732         
733         HttpContext[] contexts = getContexts();
734         for (int i=0;i<contexts.length;i++)
735         {
736             HttpContext context=contexts[i];
737             context.stop(graceful);
738         }
739 
740         if (_notFoundContext!=null)
741         {
742             _notFoundContext.stop();
743             removeComponent(_notFoundContext);
744         }
745         _notFoundContext=null;
746         
747         if (_requestLog!=null && _requestLog.isStarted())
748         {
749             _requestLog.stop();
750             log.info("Stopped "+_requestLog);
751         }
752         
753         log.info("Stopped "+this);
754     }
755     
756     /* ------------------------------------------------------------ */
757     /** Join the listeners.
758      * Join all listeners that are instances of ThreadPool.
759      * @exception InterruptedException 
760      */
761     public void join()
762         throws InterruptedException
763     { 
764         for (int l=0;l<_listeners.size();l++)
765         {
766             HttpListener listener =(HttpListener)_listeners.get(l); 
767             if (listener.isStarted() && listener instanceof ThreadPool)
768             {
769                 ((ThreadPool)listener).join();
770             }
771         }
772     }
773     
774     /* ------------------------------------------------------------ */
775     /** Define a virtual host alias.
776      * All requests to the alias are handled the same as request for
777      * the virtualHost.
778      * @deprecated Use HttpContext.addVirtualHost
779      * @param virtualHost Host name or IP
780      * @param alias Alias hostname or IP
781      */
782     public void addHostAlias(String virtualHost, String alias)
783     {
784         log.warn("addHostAlias is deprecated. Use HttpContext.addVirtualHost");
785         Object contextMap=_virtualHostMap.get(virtualHost);
786         if (contextMap==null)
787             throw new IllegalArgumentException("No Such Host: "+virtualHost);
788         _virtualHostMap.put(alias,contextMap);
789     }
790 
791     /* ------------------------------------------------------------ */
792     /** Set the request log.
793      * @param log RequestLog to use.
794      */
795     public synchronized void setRequestLog(RequestLog log)
796     {
797         if (_requestLog!=null)
798             removeComponent(_requestLog);
799         _requestLog=log;
800         if (_requestLog!=null)
801             addComponent(_requestLog);
802     }
803 
804     
805     /* ------------------------------------------------------------ */
806     public RequestLog getRequestLog()
807     {
808         return _requestLog;
809     }
810     
811 
812     /* ------------------------------------------------------------ */
813     /** Log a request to the request log
814      * @param request The request.
815      * @param response The response generated.
816      * @param length The length of the body.
817      */
818     void log(HttpRequest request,
819              HttpResponse response,
820              int length)
821     {
822         if (_requestLog!=null &&
823             request!=null &&
824             response!=null)
825             _requestLog.log(request,response,length);
826     }
827     
828     /* ------------------------------------------------------------ */
829     /** Service a request.
830      * Handle the request by passing it to the HttpHandler contained in
831      * the mapped HttpContexts.
832      * The requests host and path are used to select a list of
833      * HttpContexts. Each HttpHandler in these context is offered
834      * the request in turn, until the request is handled.
835      *
836      * If no handler handles the request, 404 Not Found is returned.
837      *
838      * @param request 
839      * @param response
840      * @return The HttpContext that completed handling of the request or null.
841      * @exception IOException 
842      * @exception HttpException 
843      */
844     public HttpContext service(HttpRequest request,HttpResponse response)
845         throws IOException, HttpException
846     {
847         String host=request.getHost();
848 
849         if (_requestsPerGC>0 && _gcRequests++>_requestsPerGC)
850         {
851             _gcRequests=0;
852             System.gc();
853         }
854         
855         while (true)
856         {
857             PathMap contextMap=(PathMap)_virtualHostMap.get(host);
858             if (contextMap!=null)
859             {
860                 List contextLists =contextMap.getMatches(request.getPath());
861                 if(contextLists!=null)
862                 {
863                     if(LogSupport.isTraceEnabled(log))log.trace("Contexts at "+request.getPath()+": "+contextLists);
864                     
865                     for (int i=0;i<contextLists.size();i++)
866                     {
867                         Map.Entry entry=
868                             (Map.Entry)
869                             contextLists.get(i);
870                         List contextList = (List)entry.getValue();
871                 
872                         for (int j=0;j<contextList.size();j++)
873                         {
874                             HttpContext context=
875                                 (HttpContext)contextList.get(j);
876                             
877                             if(log.isDebugEnabled())log.debug("Try "+context+","+j);
878 
879                             context.handle(request,response);
880                             if (request.isHandled())
881                                 return context;
882                         }
883                     }   
884                 }
885             }
886             
887             // try no host
888             if (host==null)
889                 break;
890             host=null;
891         }  
892 
893         synchronized(this)
894         {
895             if (_notFoundContext==null)
896             {
897                 _notFoundContext=new HttpContext();
898                 _notFoundContext.setContextPath("/");
899                 _notFoundContext.setHttpServer(this);
900 
901                 try
902                 {
903                     _notFoundContext
904                         .addHandler((NotFoundHandler)Class.forName
905                                     ("org.mortbay.http.handler.RootNotFoundHandler").newInstance());
906                 }
907                 catch (Exception e)
908                 {
909                     _notFoundContext.addHandler(new NotFoundHandler());
910                 }
911                 
912                 addComponent(_notFoundContext);
913                 try{_notFoundContext.start();}catch(Exception e){log.warn(LogSupport.EXCEPTION,e);}
914             }
915             
916             _notFoundContext.handle(request,response);
917             if (!request.isHandled())
918                 response.sendError(HttpResponse.__404_Not_Found);
919             return _notFoundContext;
920         }
921     }
922     
923     /* ------------------------------------------------------------ */
924     /** Find handler.
925      * Find a handler for a URI.  This method is provided for
926      * the servlet context getContext method to search for another
927      * context by URI.  A list of hosts may be passed to qualify the
928      * search.
929      * @param uri URI that must be satisfied by the servlet handler 
930      * @param vhosts null or a list of virtual hosts names to search
931      * @return HttpHandler
932      */
933     public HttpHandler findHandler(Class handlerClass,
934                                    String uri,
935                                    String[] vhosts)
936     {
937         uri = URI.stripPath(uri);
938 
939         if (vhosts==null || vhosts.length==0)
940             vhosts=__noVirtualHost;
941         
942         for (int h=0; h<vhosts.length ; h++)
943         {
944             String host = vhosts[h];
945             
946             PathMap contextMap=(PathMap)_virtualHostMap.get(host);
947             if (contextMap!=null)
948             {
949                 List contextLists =contextMap.getMatches(uri);
950                 if(contextLists!=null)
951                 {
952                     
953                     for (int i=0;i<contextLists.size();i++)
954                     {
955                         Map.Entry entry=
956                             (Map.Entry)
957                             contextLists.get(i);
958                         
959                         List contextList = (List)entry.getValue();
960                 
961                         for (int j=0;j<contextList.size();j++)
962                         {
963                             HttpContext context=
964                                 (HttpContext)contextList.get(j);
965 
966                             HttpHandler handler = context.getHandler(handlerClass);
967 
968                             if (handler!=null)
969                                 return handler;
970                         }
971                     }   
972                 }
973             }
974         }  
975         return null;
976     }
977     
978     /* ------------------------------------------------------------ */
979     public UserRealm addRealm(UserRealm realm)
980     {
981         return (UserRealm)_realmMap.put(realm.getName(),realm);
982     }
983     
984     /* ------------------------------------------------------------ */
985     /** Get a named UserRealm.
986      * @param realmName The name of the realm or null.
987      * @return The named realm. If the name is null and only a single realm
988      * is known, that is returned.
989      */
990     public UserRealm getRealm(String realmName)
991     {
992         if (realmName==null)
993         {
994             if (_realmMap.size()==1)
995                 return (UserRealm)_realmMap.values().iterator().next();
996             log.warn("Null realmName with multiple known realms");
997         }
998         return (UserRealm)_realmMap.get(realmName);
999     }
1000    
1001    /* ------------------------------------------------------------ */
1002    public UserRealm removeRealm(String realmName)
1003    {
1004        return (UserRealm)_realmMap.remove(realmName);
1005    }    
1006    
1007
1008    /* ------------------------------------------------------------ */
1009    public Map getHostMap()
1010    {
1011        return _virtualHostMap;
1012    }
1013
1014    /* ------------------------------------------------------------ */
1015    /** 
1016     * @return True if the remote host name of connections is resolved.
1017     */
1018    public boolean getResolveRemoteHost()
1019    {
1020        return _resolveRemoteHost;
1021    }
1022    
1023    /* ------------------------------------------------------------ */
1024    /** 
1025     * @param resolveRemoteHost True if the remote host name of connections is resolved.
1026     */
1027    public void setResolveRemoteHost(boolean resolveRemoteHost)
1028    {
1029        _resolveRemoteHost = resolveRemoteHost;
1030    }
1031
1032    /* ------------------------------------------------------------ */
1033    /** Reset statistics.
1034     */
1035    public void statsReset()
1036    {
1037        _statsStartedAt=System.currentTimeMillis();
1038        
1039        _connections=0;
1040        _connectionsOpen=0;
1041        _connectionsOpenMax=0;
1042        _connectionsDurationAve=0;
1043        _connectionsDurationMax=0;
1044        _connectionsRequestsAve=0;
1045        _connectionsRequestsMax=0;
1046        
1047        _errors=0;
1048        _requests=0;
1049        _requestsActive=0;
1050        _requestsActiveMax=0;
1051        _requestsDurationAve=0;
1052        _requestsDurationMax=0;
1053    }
1054    
1055    /* ------------------------------------------------------------ */
1056    public void setStatsOn(boolean on)
1057    {
1058        log.info("Statistics on = "+on+" for "+this);
1059        _statsOn=on;
1060    }
1061    
1062    /* ------------------------------------------------------------ */
1063    /** 
1064     * @return True if statistics collection is turned on.
1065     */
1066    public boolean getStatsOn()
1067    {
1068        return _statsOn;
1069    }
1070    
1071    /* ------------------------------------------------------------ */
1072    /** 
1073     * @return Timestamp stats were started at.
1074     */
1075    public long getStatsOnMs()
1076    {
1077        return _statsOn?(System.currentTimeMillis()-_statsStartedAt):0;
1078    }
1079
1080    /* ------------------------------------------------------------ */
1081    /** 
1082     * @return Number of connections accepted by the server since
1083     * statsReset() called. Undefined if setStatsOn(false).
1084     */
1085    public int getConnections() {return _connections;}
1086
1087    /* ------------------------------------------------------------ */
1088    /** 
1089     * @return Number of connections currently open that were opened
1090     * since statsReset() called. Undefined if setStatsOn(false).
1091     */
1092    public int getConnectionsOpen() {return _connectionsOpen;}
1093
1094    /* ------------------------------------------------------------ */
1095    /** 
1096     * @return Maximum number of connections opened simultaneously
1097     * since statsReset() called. Undefined if setStatsOn(false).
1098     */
1099    public int getConnectionsOpenMax() {return _connectionsOpenMax;}
1100
1101    /* ------------------------------------------------------------ */
1102    /** 
1103     * @return Sliding average duration in milliseconds of open connections
1104     * since statsReset() called. Undefined if setStatsOn(false).
1105     */
1106    public long getConnectionsDurationAve() {return _connectionsDurationAve/128;}
1107
1108    /* ------------------------------------------------------------ */
1109    /** 
1110     * @return Maximum duration in milliseconds of an open connection
1111     * since statsReset() called. Undefined if setStatsOn(false).
1112     */
1113    public long getConnectionsDurationMax() {return _connectionsDurationMax;}
1114
1115    /* ------------------------------------------------------------ */
1116    /** 
1117     * @return Sliding average number of requests per connection
1118     * since statsReset() called. Undefined if setStatsOn(false).
1119     */
1120    public int getConnectionsRequestsAve() {return _connectionsRequestsAve/16;}
1121
1122    /* ------------------------------------------------------------ */
1123    /** 
1124     * @return Maximum number of requests per connection
1125     * since statsReset() called. Undefined if setStatsOn(false).
1126     */
1127    public int getConnectionsRequestsMax() {return _connectionsRequestsMax;}
1128
1129
1130    /* ------------------------------------------------------------ */
1131    /** 
1132     * @return Number of errors generated while handling requests.
1133     * since statsReset() called. Undefined if setStatsOn(false).
1134     */
1135    public int getErrors() {return _errors;}
1136
1137    /* ------------------------------------------------------------ */
1138    /** 
1139     * @return Number of requests
1140     * since statsReset() called. Undefined if setStatsOn(false).
1141     */
1142    public int getRequests() {return _requests;}
1143
1144    /* ------------------------------------------------------------ */
1145    /** 
1146     * @return Number of requests currently active.
1147     * Undefined if setStatsOn(false).
1148     */
1149    public int getRequestsActive() {return _requestsActive;}
1150
1151    /* ------------------------------------------------------------ */
1152    /** 
1153     * @return Maximum number of active requests
1154     * since statsReset() called. Undefined if setStatsOn(false).
1155     */
1156    public int getRequestsActiveMax() {return _requestsActiveMax;}
1157
1158    /* ------------------------------------------------------------ */
1159    /** 
1160     * @return Average duration of request handling in milliseconds 
1161     * since statsReset() called. Undefined if setStatsOn(false).
1162     */
1163    public long getRequestsDurationAve() {return _requestsDurationAve/128;}
1164
1165    /* ------------------------------------------------------------ */
1166    /** 
1167     * @return Get maximum duration in milliseconds of request handling
1168     * since statsReset() called. Undefined if setStatsOn(false).
1169     */
1170    public long getRequestsDurationMax() {return _requestsDurationMax;}
1171    
1172    /* ------------------------------------------------------------ */
1173    void statsOpenConnection()
1174    {
1175        synchronized(_statsLock)
1176        {
1177            if (++_connectionsOpen > _connectionsOpenMax)
1178                _connectionsOpenMax=_connectionsOpen;
1179        }
1180    }
1181    
1182    /* ------------------------------------------------------------ */
1183    void statsGotRequest()
1184    {
1185        synchronized(_statsLock)
1186        {
1187            if (++_requestsActive > _requestsActiveMax)
1188                _requestsActiveMax=_requestsActive;
1189        }
1190    }
1191    
1192    
1193    /* ------------------------------------------------------------ */
1194    void statsEndRequest(long duration,boolean ok)
1195    {
1196        synchronized(_statsLock)
1197        {
1198            _requests++;
1199            if (--_requestsActive<0)
1200                _requestsActive=0;
1201            if (!ok)
1202                _errors++;
1203            else
1204            {
1205                if (duration>_requestsDurationMax)
1206                    _requestsDurationMax=duration;
1207                if (_requestsDurationAve==0)
1208                    _requestsDurationAve=duration*128;
1209                _requestsDurationAve=_requestsDurationAve-_requestsDurationAve/128+duration;
1210            }
1211        }
1212    }
1213    
1214    
1215    /* ------------------------------------------------------------ */
1216    void statsCloseConnection(long duration,int requests)
1217    {
1218        synchronized(_statsLock)
1219        {
1220            _connections++;
1221            _connectionsOpen--;
1222            if (_connectionsOpen<0)
1223                _connectionsOpen=0;
1224            if (duration>_connectionsDurationMax)
1225                _connectionsDurationMax=duration;
1226            if (_connectionsDurationAve==0)
1227                _connectionsDurationAve=128*duration;
1228            _connectionsDurationAve=_connectionsDurationAve-_connectionsDurationAve/128+duration;
1229            if (requests>_connectionsRequestsMax)
1230                _connectionsRequestsMax=requests;
1231            if (_connectionsRequestsAve==0)
1232                _connectionsRequestsAve=16;
1233            _connectionsRequestsAve=_connectionsRequestsAve-_connectionsRequestsAve/16+requests;
1234        }
1235    }
1236    
1237    
1238    /* ------------------------------------------------------------ */
1239    private void addComponent(Object o)
1240    {
1241        if(log.isDebugEnabled())log.debug("add component: "+o);
1242        if (_components==null)
1243            _components=new ArrayList();
1244        _components.add(o);
1245
1246        if (_eventListeners!=null)
1247        {
1248            ComponentEvent event = new ComponentEvent(o);
1249            for(int i=0;i<_eventListeners.size();i++)
1250            {
1251                EventListener listener =
1252                    (EventListener)_eventListeners.get(i);
1253                if (listener instanceof ComponentEventListener)
1254                    ((ComponentEventListener)listener).addComponent(event);
1255            }
1256        }
1257    }
1258    
1259    /* ------------------------------------------------------------ */
1260    private void removeComponent(Object o)
1261    {
1262        if(log.isDebugEnabled())log.debug("remove component: "+o);
1263        if (_components.remove(o) && _eventListeners!=null)
1264        {
1265            ComponentEvent event = new ComponentEvent(o);
1266            for(int i=0;i<_eventListeners.size();i++)
1267            {
1268                EventListener listener =
1269                    (EventListener)_eventListeners.get(i);
1270                if (listener instanceof ComponentEventListener)
1271                    ((ComponentEventListener)listener).removeComponent(event);
1272            }
1273        }
1274    }
1275
1276    /* ------------------------------------------------------------ */
1277    /** Add a server event listener.
1278     * Listeners are sent HttpServer.ComponentEvent instances when components
1279     * such as listeners and contexts are added to the HttpServer.
1280     * @param listener HttpServer.ComponentEventListener
1281     */
1282    public void addEventListener(EventListener listener)
1283    {
1284        if(log.isDebugEnabled())log.debug("addEventListener: "+listener);
1285        if (_eventListeners==null)
1286            _eventListeners=new ArrayList();
1287        if (listener instanceof ComponentEventListener)
1288            _eventListeners.add(listener);
1289        else
1290            log.warn("Not a ComponentEventListener: "+listener);
1291    }
1292    
1293    /* ------------------------------------------------------------ */
1294    public void removeEventListener(EventListener listener)
1295    {
1296        if(log.isDebugEnabled())log.debug("removeEventListener: "+listener);
1297        _eventListeners.remove(listener);
1298    }
1299
1300    /* ------------------------------------------------------------ */
1301    /** Save the HttpServer
1302     * The server is saved by serialization to the given filename or URL.
1303     *
1304     * @param saveat A file or URL to save the configuration at. 
1305     * @exception MalformedURLException 
1306     * @exception IOException 
1307     */
1308    public void save(String saveat)
1309        throws MalformedURLException,
1310               IOException
1311    {
1312        Resource resource = Resource.newResource(saveat);
1313        ObjectOutputStream out = new ObjectOutputStream(resource.getOutputStream());
1314        out.writeObject(this);
1315        out.flush();
1316        out.close();
1317        log.info("Saved "+this+" to "+resource);
1318    }
1319    
1320    /* ------------------------------------------------------------ */
1321    /** Destroy a stopped server.
1322     * Remove all components and send notifications to all event
1323     * listeners. The HttpServer must be stopped before it can be destroyed.
1324     */
1325    public void destroy()
1326    {
1327        __servers.remove(this);
1328        if (isStarted())
1329            throw new IllegalStateException("Started");
1330        if (_listeners!=null)
1331            _listeners.clear();
1332        _listeners=null;
1333        if (_virtualHostMap!=null)
1334            _virtualHostMap.clear();
1335        _virtualHostMap=null;
1336        _notFoundContext=null;
1337
1338        if (_components!=null && _eventListeners!=null)
1339        {
1340            for (int c=0;c<_components.size();c++)
1341            {
1342                Object o=_components.get(c);
1343                if (o instanceof HttpContext )
1344                    ((HttpContext)o).destroy();
1345                
1346                if (_eventListeners!=null)
1347                {
1348                    ComponentEvent event = new ComponentEvent(o);
1349                    for(int i=0;i<_eventListeners.size();i++)
1350                    {
1351                        EventListener listener =
1352                            (EventListener)_eventListeners.get(i);
1353                        if (listener instanceof ComponentEventListener)
1354                            ((ComponentEventListener)listener).removeComponent(event);
1355                    }
1356                }
1357            }
1358        }
1359        if (_components!=null)
1360            _components.clear();
1361        _components=null;
1362        
1363        if (_eventListeners!=null)
1364        {
1365            ComponentEvent event = new ComponentEvent(this);
1366            for(int i=0;i<_eventListeners.size();i++)
1367            {
1368                EventListener listener =
1369                    (EventListener)_eventListeners.get(i);
1370                if (listener instanceof ComponentEventListener)
1371                    ((ComponentEventListener)listener).removeComponent(event);
1372            }
1373        }
1374        if (_eventListeners!=null)
1375            _eventListeners.clear();
1376        _eventListeners=null;
1377    }
1378    
1379    /* ------------------------------------------------------------ */
1380    /* ------------------------------------------------------------ */
1381    /** Construct server from command line arguments.
1382     * @param args 
1383     */
1384    public static void main(String[] args)
1385    {
1386        if (args.length==0 || args.length>2)
1387        {
1388            System.err.println
1389                ("\nUsage - java org.mortbay.http.HttpServer [<addr>:]<port>");
1390            System.err.println
1391                ("\nUsage - java org.mortbay.http.HttpServer -r [savefile]");
1392            System.err.println
1393                ("  Serves files from '.' directory");
1394            System.err.println
1395                ("  Dump handler for not found requests");
1396            System.err.println
1397                ("  Default port is 8080");
1398            System.exit(1);
1399        }
1400        
1401        try{
1402            
1403            if (args.length==1)
1404            {
1405                // Create the server
1406                HttpServer server = new HttpServer();
1407                
1408                // Default is no virtual host
1409                String host=null;
1410                HttpContext context = server.getContext(host,"/");
1411                context.setResourceBase(".");
1412                context.addHandler(new ResourceHandler());
1413                context.addHandler(new DumpHandler());
1414                context.addHandler(new NotFoundHandler());
1415
1416                InetAddrPort address = new InetAddrPort(args[0]);
1417                server.addListener(address);
1418
1419                server.start();
1420            }
1421            else
1422            {
1423                Resource resource = Resource.newResource(args[1]);
1424                ObjectInputStream in = new ObjectInputStream(resource.getInputStream());
1425                HttpServer server = (HttpServer)in.readObject();
1426                in.close();
1427                server.start();
1428            }
1429            
1430        }
1431        catch (Exception e)
1432        {
1433            log.warn(LogSupport.EXCEPTION,e);
1434        }
1435    }
1436
1437
1438    /* ------------------------------------------------------------ */
1439    /* ------------------------------------------------------------ */
1440    public class ComponentEvent extends EventObject
1441    {
1442        private Object component;
1443        ComponentEvent(Object component)
1444        {
1445            super(HttpServer.this);
1446            this.component=component;
1447        }
1448        public Object getComponent()
1449        {
1450            return component;
1451        }
1452    }
1453    
1454    /* ------------------------------------------------------------ */
1455    /* ------------------------------------------------------------ */
1456    public interface ComponentEventListener extends EventListener
1457    {
1458        public void addComponent(ComponentEvent event);
1459        public void removeComponent(ComponentEvent event);
1460    }
1461}