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}