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