View Javadoc

1   // ========================================================================
2   // Copyright (c) 2004-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.server;
15  
16  import java.io.IOException;
17  import java.lang.reflect.Method;
18  import java.util.ArrayList;
19  import java.util.Collection;
20  import java.util.Enumeration;
21  import java.util.Iterator;
22  import java.util.List;
23  import java.util.ListIterator;
24  
25  import javax.servlet.ServletException;
26  import javax.servlet.http.HttpServletRequest;
27  import javax.servlet.http.HttpServletResponse;
28  
29  import org.eclipse.jetty.http.HttpGenerator;
30  import org.eclipse.jetty.http.HttpURI;
31  import org.eclipse.jetty.server.bio.SocketConnector;
32  import org.eclipse.jetty.server.handler.HandlerCollection;
33  import org.eclipse.jetty.server.handler.HandlerWrapper;
34  import org.eclipse.jetty.server.nio.SelectChannelConnector;
35  import org.eclipse.jetty.util.Attributes;
36  import org.eclipse.jetty.util.AttributesMap;
37  import org.eclipse.jetty.util.LazyList;
38  import org.eclipse.jetty.util.MultiException;
39  import org.eclipse.jetty.util.URIUtil;
40  import org.eclipse.jetty.util.component.Container;
41  import org.eclipse.jetty.util.component.LifeCycle;
42  import org.eclipse.jetty.util.log.Log;
43  import org.eclipse.jetty.util.thread.QueuedThreadPool;
44  import org.eclipse.jetty.util.thread.ThreadPool;
45  
46  /* ------------------------------------------------------------ */
47  /** Jetty HTTP Servlet Server.
48   * This class is the main class for the Jetty HTTP Servlet server.
49   * It aggregates Connectors (HTTP request receivers) and request Handlers.
50   * The server is itself a handler and a ThreadPool.  Connectors use the ThreadPool methods
51   * to run jobs that will eventually call the handle method.
52   *
53   *  @org.apache.xbean.XBean  description="Creates an embedded Jetty web server"
54   */
55  public class Server extends HandlerWrapper implements Attributes
56  {
57      private static ShutdownHookThread hookThread = new ShutdownHookThread();
58      private static String _version = (Server.class.getPackage()!=null && Server.class.getPackage().getImplementationVersion()!=null)
59          ?Server.class.getPackage().getImplementationVersion()
60          :"7.0.y.z-SNAPSHOT";
61  
62      private ThreadPool _threadPool;
63      private Connector[] _connectors;
64      private Container _container=new Container();
65      private SessionIdManager _sessionIdManager;
66      private boolean _sendServerVersion = true; //send Server: header
67      private boolean _sendDateHeader = false; //send Date: header 
68      private AttributesMap _attributes = new AttributesMap();
69      private List<Object> _dependentBeans=new ArrayList<Object>();
70      private int _graceful=0;
71      
72      /* ------------------------------------------------------------ */
73      public Server()
74      {
75          setServer(this); 
76      }
77      
78      /* ------------------------------------------------------------ */
79      /** Convenience constructor
80       * Creates server and a {@link SocketConnector} at the passed port.
81       */
82      public Server(int port)
83      {
84          setServer(this);
85  
86          Connector connector=new SelectChannelConnector();
87          connector.setPort(port);
88          setConnectors(new Connector[]{connector});
89      }
90  
91  
92      /* ------------------------------------------------------------ */
93      public static String getVersion()
94      {
95          return _version;
96      }
97      
98      /* ------------------------------------------------------------ */
99      /**
100      * @return Returns the container.
101      */
102     public Container getContainer()
103     {
104         return _container;
105     }
106 
107     /* ------------------------------------------------------------ */
108     public boolean getStopAtShutdown()
109     {
110         return hookThread.contains(this);
111     }
112     
113     /* ------------------------------------------------------------ */
114     public void setStopAtShutdown(boolean stop)
115     {
116         if (stop)
117             hookThread.add(this);
118         else
119             hookThread.remove(this);
120     }
121     
122     /* ------------------------------------------------------------ */
123     /**
124      * @return Returns the connectors.
125      */
126     public Connector[] getConnectors()
127     {
128         return _connectors;
129     }
130     
131 
132     /* ------------------------------------------------------------ */
133     public void addConnector(Connector connector)
134     {
135         setConnectors((Connector[])LazyList.addToArray(getConnectors(), connector, Connector.class));
136     }
137 
138     /* ------------------------------------------------------------ */
139     /**
140      * Conveniance method which calls {@link #getConnectors()} and {@link #setConnectors(Connector[])} to 
141      * remove a connector.
142      * @param connector The connector to remove.
143      */
144     public void removeConnector(Connector connector) {
145         setConnectors((Connector[])LazyList.removeFromArray (getConnectors(), connector));
146     }
147 
148     /* ------------------------------------------------------------ */
149     /** Set the connectors for this server.
150      * Each connector has this server set as it's ThreadPool and its Handler.
151      * @param connectors The connectors to set.
152      */
153     public void setConnectors(Connector[] connectors)
154     {
155         if (connectors!=null)
156         {
157             for (int i=0;i<connectors.length;i++)
158                 connectors[i].setServer(this);
159         }
160         
161         _container.update(this, _connectors, connectors, "connector");
162         _connectors = connectors;
163     }
164 
165     /* ------------------------------------------------------------ */
166     /**
167      * @return Returns the threadPool.
168      */
169     public ThreadPool getThreadPool()
170     {
171         return _threadPool;
172     }
173     
174     /* ------------------------------------------------------------ */
175     /**
176      * @param threadPool The threadPool to set.
177      */
178     public void setThreadPool(ThreadPool threadPool)
179     {
180         _container.update(this,_threadPool,threadPool, "threadpool",true);
181         _threadPool = threadPool;
182     }
183 
184     /* ------------------------------------------------------------ */
185     protected void doStart() throws Exception
186     {
187         Log.info("jetty-"+_version);
188         HttpGenerator.setServerVersion(_version);
189         MultiException mex=new MultiException();
190 
191         Iterator itor = _dependentBeans.iterator();
192         while (itor.hasNext())
193         {   
194             try
195             {
196                 Object o=itor.next();
197                 if (o instanceof LifeCycle)
198                     ((LifeCycle)o).start(); 
199             }
200             catch (Throwable e) {mex.add(e);}
201         }
202         
203         if (_threadPool==null)
204         {
205             QueuedThreadPool tp=new QueuedThreadPool();
206             setThreadPool(tp);
207         }
208         
209         if (_sessionIdManager!=null)
210             _sessionIdManager.start();
211         
212         try
213         {
214             if (_threadPool instanceof LifeCycle)
215                 ((LifeCycle)_threadPool).start();
216         } 
217         catch(Throwable e) { mex.add(e);}
218         
219         try 
220         { 
221             super.doStart(); 
222         } 
223         catch(Throwable e) 
224         { 
225             Log.warn("Error starting handlers",e);
226         }
227         
228         if (_connectors!=null)
229         {
230             for (int i=0;i<_connectors.length;i++)
231             {
232                 try{_connectors[i].start();}
233                 catch(Throwable e)
234                 {
235                     mex.add(e);
236                 }
237             }
238         }
239         if (Log.isDebugEnabled())
240             Log.debug(dump());
241         mex.ifExceptionThrow();
242     }
243 
244     /* ------------------------------------------------------------ */
245     protected void doStop() throws Exception
246     {
247         MultiException mex=new MultiException();
248         
249         if (_graceful>0)
250         {
251             if (_connectors!=null)
252             {
253                 for (int i=_connectors.length;i-->0;)
254                 {
255                     Log.info("Graceful shutdown {}",_connectors[i]);
256                     try{_connectors[i].close();}catch(Throwable e){mex.add(e);}
257                 }
258             }
259             
260             Handler[] contexts = getChildHandlersByClass(Graceful.class);
261             for (int c=0;c<contexts.length;c++)
262             {
263                 Graceful context=(Graceful)contexts[c];
264                 Log.info("Graceful shutdown {}",context);
265                 context.setShutdown(true);
266             }
267             Thread.sleep(_graceful);
268         }
269         
270         if (_connectors!=null)
271         {
272             for (int i=_connectors.length;i-->0;)
273                 try{_connectors[i].stop();}catch(Throwable e){mex.add(e);}
274         }
275 
276         try {super.doStop(); } catch(Throwable e) { mex.add(e);}
277         
278         if (_sessionIdManager!=null)
279             _sessionIdManager.stop();
280         
281         try
282         {
283             if (_threadPool instanceof LifeCycle)
284                 ((LifeCycle)_threadPool).stop();
285         }
286         catch(Throwable e){mex.add(e);}
287         
288         if (!_dependentBeans.isEmpty())
289         {
290             ListIterator itor = _dependentBeans.listIterator(_dependentBeans.size());
291             while (itor.hasPrevious())
292             {
293                 try
294                 {
295                     Object o =itor.previous();
296                     if (o instanceof LifeCycle)
297                         ((LifeCycle)o).stop(); 
298                 }
299                 catch (Throwable e) {mex.add(e);}
300             }
301         }
302        
303         mex.ifExceptionThrow();
304     }
305 
306     /* ------------------------------------------------------------ */
307     /* Handle a request from a connection.
308      * Called to handle a request on the connection when either the header has been received,
309      * or after the entire request has been received (for short requests of known length), or
310      * on the dispatch of an async request.
311      */
312     public void handle(HttpConnection connection) throws IOException, ServletException
313     {
314         final String target=connection.getRequest().getPathInfo();
315         final Request request=connection.getRequest();
316         final Response response=connection.getResponse();
317         
318         if (Log.isDebugEnabled())
319         {
320             Log.debug("REQUEST "+target+" on "+connection);
321             handle(target, request, request, response);
322             Log.debug("RESPONSE "+target+"  "+connection.getResponse().getStatus());
323         }
324         else
325             handle(target, request, request, response);
326     }
327     
328     /* ------------------------------------------------------------ */
329     /* Handle a request from a connection.
330      * Called to handle a request on the connection when either the header has been received,
331      * or after the entire request has been received (for short requests of known length), or
332      * on the dispatch of an async request.
333      */
334     public void handleAsync(HttpConnection connection) throws IOException, ServletException
335     {
336         final AsyncRequest async = connection.getRequest().getAsyncRequest();
337         final AsyncRequest.AsyncEventState state = async.getAsyncEventState();
338 
339         final Request baseRequest=connection.getRequest();
340         final String path=state.getPath();
341         if (path!=null)
342         {
343             // this is a dispatch with a path
344             baseRequest.setAttribute(AsyncContext.ASYNC_REQUEST_URI,baseRequest.getRequestURI());
345             baseRequest.setAttribute(AsyncContext.ASYNC_QUERY_STRING,baseRequest.getQueryString());
346             
347             baseRequest.setAttribute(AsyncContext.ASYNC_CONTEXT_PATH,state.getSuspendedContext().getContextPath());
348 
349             final String contextPath=state.getServletContext().getContextPath();
350             HttpURI uri = new HttpURI(URIUtil.addPaths(contextPath,path));
351             baseRequest.setUri(uri);
352             baseRequest.setRequestURI(null);
353             baseRequest.setPathInfo(baseRequest.getRequestURI());
354             baseRequest.setQueryString(uri.getQuery());            
355         }
356         
357         final String target=baseRequest.getPathInfo();
358         final HttpServletRequest request=(HttpServletRequest)async.getRequest();
359         final HttpServletResponse response=(HttpServletResponse)async.getResponse();
360 
361         if (Log.isDebugEnabled())
362         {
363             Log.debug("REQUEST "+target+" on "+connection);
364             handle(target, baseRequest, request, response);
365             Log.debug("RESPONSE "+target+"  "+connection.getResponse().getStatus());
366         }
367         else
368             handle(target, baseRequest, request, response);
369     }
370     
371     
372 
373     /* ------------------------------------------------------------ */
374     public void join() throws InterruptedException 
375     {
376         getThreadPool().join();
377     }
378 
379     /* ------------------------------------------------------------ */
380     /* ------------------------------------------------------------ */
381     /**
382      * @return Returns the sessionIdManager.
383      */
384     public SessionIdManager getSessionIdManager()
385     {
386         return _sessionIdManager;
387     }
388 
389     /* ------------------------------------------------------------ */
390     /* ------------------------------------------------------------ */
391     /**
392      * @param sessionIdManager The sessionIdManager to set.
393      */
394     public void setSessionIdManager(SessionIdManager sessionIdManager)
395     {
396         _container.update(this,_sessionIdManager,sessionIdManager, "sessionIdManager",true);
397         _sessionIdManager = sessionIdManager;
398     }
399 
400     /* ------------------------------------------------------------ */
401     public void setSendServerVersion (boolean sendServerVersion)
402     {
403         _sendServerVersion = sendServerVersion;
404     }
405 
406     /* ------------------------------------------------------------ */
407     public boolean getSendServerVersion()
408     {
409         return _sendServerVersion;
410     }
411 
412     /* ------------------------------------------------------------ */
413     /**
414      * @param sendDateHeader
415      */
416     public void setSendDateHeader(boolean sendDateHeader)
417     {
418         _sendDateHeader = sendDateHeader;
419     }
420 
421     /* ------------------------------------------------------------ */
422     public boolean getSendDateHeader()
423     {
424         return _sendDateHeader;
425     }
426     
427 
428     /* ------------------------------------------------------------ */
429     /**
430      * Add a LifeCycle object to be started/stopped
431      * along with the Server.
432      * @deprecated Use {@link #addBean(LifeCycle)}
433      * @param c
434      */
435     public void addLifeCycle (LifeCycle c)
436     {
437         addBean(c);
438     }
439 
440     /* ------------------------------------------------------------ */
441     /**
442      * Add an associated bean.
443      * The bean will be added to the servers {@link Container}
444      * and if it is a {@link LifeCycle} instance, it will be 
445      * started/stopped along with the Server.
446      * @param c
447      */
448     public void addBean(Object o)
449     {
450         if (o == null)
451             return;
452         
453         if (!_dependentBeans.contains(o)) 
454         {
455             _dependentBeans.add(o);
456             _container.addBean(o);
457         }
458         
459         try
460         {
461             if (isStarted() && o instanceof LifeCycle)
462                 ((LifeCycle)o).start();
463         }
464         catch (Exception e)
465         {
466             throw new RuntimeException (e);
467         }
468     }
469 
470     /* ------------------------------------------------------------ */
471     /** Get dependent beans of a specific class
472      * @see #addBean(Object)
473      * @param clazz
474      * @return List of beans.
475      */
476     public <T> List<T> getBeans(Class<T> clazz)
477     {
478         ArrayList<T> beans = new ArrayList<T>();
479         Iterator<?> iter = _dependentBeans.iterator();
480         while (iter.hasNext())
481         {
482             Object o = iter.next();
483             if (clazz.isInstance(o))
484                 beans.add((T)o);
485         }
486         return beans;
487     }
488     
489     /**
490      * Remove a LifeCycle object to be started/stopped 
491      * along with the Server
492      * @deprecated Use {@link #removeBean(Object)}
493      */
494     public void removeLifeCycle (LifeCycle c)
495     {
496         removeBean(c);
497     }
498 
499     /**
500      * Remove an associated bean.
501      */
502     public void removeBean (Object o)
503     {
504         if (o == null)
505             return;
506         _dependentBeans.remove(o);
507         _container.removeBean(o);
508     }
509     
510  
511     
512     /* ------------------------------------------------------------ */
513     /* ------------------------------------------------------------ */
514     /* ------------------------------------------------------------ */
515     /**
516      * ShutdownHook thread for stopping all servers.
517      * 
518      * Thread is hooked first time list of servers is changed.
519      */
520     private static class ShutdownHookThread extends Thread
521     {
522         private boolean hooked = false;
523         private ArrayList servers = new ArrayList();
524 
525         /**
526          * Hooks this thread for shutdown.
527          * 
528          * @see java.lang.Runtime#addShutdownHook(java.lang.Thread)
529          */
530         private void createShutdownHook()
531         {
532             if (!Boolean.getBoolean("JETTY_NO_SHUTDOWN_HOOK") && !hooked)
533             {
534                 try
535                 {
536                     Method shutdownHook = java.lang.Runtime.class.getMethod("addShutdownHook", new Class[]
537                     { java.lang.Thread.class});
538                     shutdownHook.invoke(Runtime.getRuntime(), new Object[]
539                     { this});
540                     this.hooked = true;
541                 }
542                 catch (Exception e)
543                 {
544                     if (Log.isDebugEnabled())
545                         Log.debug("No shutdown hook in JVM ", e);
546                 }
547             }
548         }
549 
550         /**
551          * Add Server to servers list.
552          */
553         public boolean add(Server server)
554         {
555             createShutdownHook();
556             return this.servers.add(server);
557         }
558 
559         /**
560          * Contains Server in servers list?
561          */
562         public boolean contains(Server server)
563         {
564             return this.servers.contains(server);
565         }
566 
567         /**
568          * Append all Servers from Collection
569          */
570         public boolean addAll(Collection c)
571         {
572             createShutdownHook();
573             return this.servers.addAll(c);
574         }
575 
576         /**
577          * Clear list of Servers.
578          */
579         public void clear()
580         {
581             createShutdownHook();
582             this.servers.clear();
583         }
584 
585         /**
586          * Remove Server from list.
587          */
588         public boolean remove(Server server)
589         {
590             createShutdownHook();
591             return this.servers.remove(server);
592         }
593 
594         /**
595          * Remove all Servers in Collection from list.
596          */
597         public boolean removeAll(Collection c)
598         {
599             createShutdownHook();
600             return this.servers.removeAll(c);
601         }
602 
603         /**
604          * Stop all Servers in list.
605          */
606         public void run()
607         {
608             setName("Shutdown");
609             Log.info("Shutdown hook executing");
610             Iterator it = servers.iterator();
611             while (it.hasNext())
612             {
613                 Server svr = (Server) it.next();
614                 if (svr == null)
615                     continue;
616                 try
617                 {
618                     svr.stop();
619                 }
620                 catch (Exception e)
621                 {
622                     Log.warn(e);
623                 }
624                 Log.info("Shutdown hook complete");
625 
626                 // Try to avoid JVM crash
627                 try
628                 {
629                     Thread.sleep(1000);
630                 }
631                 catch (Exception e)
632                 {
633                     Log.warn(e);
634                 }
635             }
636         }
637     }
638 
639     /* ------------------------------------------------------------ */
640     /* 
641      * @see org.eclipse.util.AttributesMap#clearAttributes()
642      */
643     public void clearAttributes()
644     {
645         _attributes.clearAttributes();
646     }
647 
648     /* ------------------------------------------------------------ */
649     /* 
650      * @see org.eclipse.util.AttributesMap#getAttribute(java.lang.String)
651      */
652     public Object getAttribute(String name)
653     {
654         return _attributes.getAttribute(name);
655     }
656 
657     /* ------------------------------------------------------------ */
658     /* 
659      * @see org.eclipse.util.AttributesMap#getAttributeNames()
660      */
661     public Enumeration getAttributeNames()
662     {
663         return AttributesMap.getAttributeNamesCopy(_attributes);
664     }
665 
666     /* ------------------------------------------------------------ */
667     /* 
668      * @see org.eclipse.util.AttributesMap#removeAttribute(java.lang.String)
669      */
670     public void removeAttribute(String name)
671     {
672         _attributes.removeAttribute(name);
673     }
674 
675     /* ------------------------------------------------------------ */
676     /* 
677      * @see org.eclipse.util.AttributesMap#setAttribute(java.lang.String, java.lang.Object)
678      */
679     public void setAttribute(String name, Object attribute)
680     {
681         _attributes.setAttribute(name, attribute);
682     }
683 
684     /* ------------------------------------------------------------ */
685     /**
686      * @return the graceful
687      */
688     public int getGracefulShutdown()
689     {
690         return _graceful;
691     }
692 
693     /* ------------------------------------------------------------ */
694     /**
695      * Set graceful shutdown timeout.  If set, the {@link #doStop()} method will not immediately stop the 
696      * server. Instead, all {@link Connector}s will be closed so that new connections will not be accepted
697      * and all handlers that implement {@link Graceful} will be put into the shutdown mode so that no new requests
698      * will be accepted, but existing requests can complete.  The server will then wait the configured timeout 
699      * before stopping.
700      * @param timeoutMS the milliseconds to wait for existing request to complete before stopping the server.
701      * 
702      */
703     public void setGracefulShutdown(int timeoutMS)
704     {
705         _graceful=timeoutMS;
706     }
707     
708     public String toString()
709     {
710         return this.getClass().getName()+"@"+Integer.toHexString(hashCode());
711     }
712 
713 
714     /* ------------------------------------------------------------ */
715     /* A handler that can be gracefully shutdown.
716      * Called by doStop if a {@link #setGracefulShutdown} period is set.
717      * TODO move this somewhere better
718      */
719     public interface Graceful extends Handler
720     {
721         public void setShutdown(boolean shutdown);
722     }
723 }