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