View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.server;
20  
21  import java.io.IOException;
22  import java.lang.management.ManagementFactory;
23  import java.net.InetAddress;
24  import java.net.InetSocketAddress;
25  import java.net.URI;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.Collections;
29  import java.util.Date;
30  import java.util.Enumeration;
31  import java.util.List;
32  import java.util.concurrent.CopyOnWriteArrayList;
33  import java.util.concurrent.Future;
34  import java.util.concurrent.TimeUnit;
35  
36  import javax.servlet.ServletContext;
37  import javax.servlet.ServletException;
38  import javax.servlet.http.HttpServletRequest;
39  import javax.servlet.http.HttpServletResponse;
40  
41  import org.eclipse.jetty.http.DateGenerator;
42  import org.eclipse.jetty.http.HttpField;
43  import org.eclipse.jetty.http.HttpGenerator;
44  import org.eclipse.jetty.http.HttpHeader;
45  import org.eclipse.jetty.http.HttpMethod;
46  import org.eclipse.jetty.http.HttpStatus;
47  import org.eclipse.jetty.http.HttpURI;
48  import org.eclipse.jetty.server.handler.ContextHandler;
49  import org.eclipse.jetty.server.handler.HandlerWrapper;
50  import org.eclipse.jetty.server.handler.StatisticsHandler;
51  import org.eclipse.jetty.util.Attributes;
52  import org.eclipse.jetty.util.AttributesMap;
53  import org.eclipse.jetty.util.Jetty;
54  import org.eclipse.jetty.util.MultiException;
55  import org.eclipse.jetty.util.URIUtil;
56  import org.eclipse.jetty.util.annotation.ManagedAttribute;
57  import org.eclipse.jetty.util.annotation.ManagedObject;
58  import org.eclipse.jetty.util.annotation.Name;
59  import org.eclipse.jetty.util.component.Graceful;
60  import org.eclipse.jetty.util.component.LifeCycle;
61  import org.eclipse.jetty.util.log.Log;
62  import org.eclipse.jetty.util.log.Logger;
63  import org.eclipse.jetty.util.thread.QueuedThreadPool;
64  import org.eclipse.jetty.util.thread.ShutdownThread;
65  import org.eclipse.jetty.util.thread.ThreadPool;
66  import org.eclipse.jetty.util.thread.ThreadPool.SizedThreadPool;
67  
68  /* ------------------------------------------------------------ */
69  /** Jetty HTTP Servlet Server.
70   * This class is the main class for the Jetty HTTP Servlet server.
71   * It aggregates Connectors (HTTP request receivers) and request Handlers.
72   * The server is itself a handler and a ThreadPool.  Connectors use the ThreadPool methods
73   * to run jobs that will eventually call the handle method.
74   */
75  @ManagedObject(value="Jetty HTTP Servlet server")
76  public class Server extends HandlerWrapper implements Attributes
77  {
78      private static final Logger LOG = Log.getLogger(Server.class);
79  
80      private final AttributesMap _attributes = new AttributesMap();
81      private final ThreadPool _threadPool;
82      private final List<Connector> _connectors = new CopyOnWriteArrayList<>();
83      private SessionIdManager _sessionIdManager;
84      private boolean _stopAtShutdown;
85      private boolean _dumpAfterStart=false;
86      private boolean _dumpBeforeStop=false;
87      
88      private volatile DateField _dateField;
89      
90      /* ------------------------------------------------------------ */
91      public Server()
92      {
93          this((ThreadPool)null);
94      }
95  
96      /* ------------------------------------------------------------ */
97      /** Convenience constructor
98       * Creates server and a {@link ServerConnector} at the passed port.
99       * @param port The port of a network HTTP connector (or 0 for a randomly allocated port).
100      * @see NetworkConnector#getLocalPort()
101      */
102     public Server(@Name("port")int port)
103     {
104         this((ThreadPool)null);
105         ServerConnector connector=new ServerConnector(this);
106         connector.setPort(port);
107         setConnectors(new Connector[]{connector});
108     }
109 
110     /* ------------------------------------------------------------ */
111     /** Convenience constructor
112      * Creates server and a {@link ServerConnector} at the passed address.
113      */
114     public Server(@Name("address")InetSocketAddress addr)
115     {
116         this((ThreadPool)null);
117         ServerConnector connector=new ServerConnector(this);
118         connector.setHost(addr.getHostName());
119         connector.setPort(addr.getPort());
120         setConnectors(new Connector[]{connector});
121     }
122 
123 
124 
125     /* ------------------------------------------------------------ */
126     public Server(@Name("threadpool") ThreadPool pool)
127     {
128         _threadPool=pool!=null?pool:new QueuedThreadPool();
129         addBean(_threadPool);
130         setServer(this);
131     }
132 
133 
134     /* ------------------------------------------------------------ */
135     @ManagedAttribute("version of this server")
136     public static String getVersion()
137     {
138         return Jetty.VERSION;
139     }
140 
141     /* ------------------------------------------------------------ */
142     public boolean getStopAtShutdown()
143     {
144         return _stopAtShutdown;
145     }
146    
147     
148     /* ------------------------------------------------------------ */
149     /**
150      * Set a graceful stop time.
151      * The {@link StatisticsHandler} must be configured so that open connections can
152      * be tracked for a graceful shutdown.
153      * @see org.eclipse.jetty.util.component.ContainerLifeCycle#setStopTimeout(long)
154      */
155     @Override
156     public void setStopTimeout(long stopTimeout)
157     {
158         super.setStopTimeout(stopTimeout);
159     }
160 
161     /* ------------------------------------------------------------ */
162     /** Set stop server at shutdown behaviour.
163      * @param stop If true, this server instance will be explicitly stopped when the
164      * JVM is shutdown. Otherwise the JVM is stopped with the server running.
165      * @see Runtime#addShutdownHook(Thread)
166      * @see ShutdownThread
167      */
168     public void setStopAtShutdown(boolean stop)
169     {
170         //if we now want to stop
171         if (stop)
172         {
173             //and we weren't stopping before
174             if (!_stopAtShutdown)
175             {
176                 //only register to stop if we're already started (otherwise we'll do it in doStart())
177                 if (isStarted())
178                     ShutdownThread.register(this);
179             }
180         }
181         else
182             ShutdownThread.deregister(this);
183 
184         _stopAtShutdown=stop;
185     }
186 
187     /* ------------------------------------------------------------ */
188     /**
189      * @return Returns the connectors.
190      */
191     @ManagedAttribute(value="connectors for this server", readonly=true)
192     public Connector[] getConnectors()
193     {
194         List<Connector> connectors = new ArrayList<>(_connectors);
195         return connectors.toArray(new Connector[connectors.size()]);
196     }
197 
198     /* ------------------------------------------------------------ */
199     public void addConnector(Connector connector)
200     {
201         if (connector.getServer() != this)
202             throw new IllegalArgumentException("Connector " + connector +
203                     " cannot be shared among server " + connector.getServer() + " and server " + this);
204         if (_connectors.add(connector))
205             addBean(connector);
206     }
207 
208     /* ------------------------------------------------------------ */
209     /**
210      * Convenience method which calls {@link #getConnectors()} and {@link #setConnectors(Connector[])} to
211      * remove a connector.
212      * @param connector The connector to remove.
213      */
214     public void removeConnector(Connector connector)
215     {
216         if (_connectors.remove(connector))
217             removeBean(connector);
218     }
219 
220     /* ------------------------------------------------------------ */
221     /** Set the connectors for this server.
222      * Each connector has this server set as it's ThreadPool and its Handler.
223      * @param connectors The connectors to set.
224      */
225     public void setConnectors(Connector[] connectors)
226     {
227         if (connectors != null)
228         {
229             for (Connector connector : connectors)
230             {
231                 if (connector.getServer() != this)
232                     throw new IllegalArgumentException("Connector " + connector +
233                             " cannot be shared among server " + connector.getServer() + " and server " + this);
234             }
235         }
236 
237         Connector[] oldConnectors = getConnectors();
238         updateBeans(oldConnectors, connectors);
239         _connectors.removeAll(Arrays.asList(oldConnectors));
240         if (connectors != null)
241             _connectors.addAll(Arrays.asList(connectors));
242     }
243 
244     /* ------------------------------------------------------------ */
245     /**
246      * @return Returns the threadPool.
247      */
248     @ManagedAttribute("the server thread pool")
249     public ThreadPool getThreadPool()
250     {
251         return _threadPool;
252     }
253 
254     /**
255      * @return true if {@link #dumpStdErr()} is called after starting
256      */
257     @ManagedAttribute("dump state to stderr after start")
258     public boolean isDumpAfterStart()
259     {
260         return _dumpAfterStart;
261     }
262 
263     /**
264      * @param dumpAfterStart true if {@link #dumpStdErr()} is called after starting
265      */
266     public void setDumpAfterStart(boolean dumpAfterStart)
267     {
268         _dumpAfterStart = dumpAfterStart;
269     }
270 
271     /**
272      * @return true if {@link #dumpStdErr()} is called before stopping
273      */
274     @ManagedAttribute("dump state to stderr before stop")
275     public boolean isDumpBeforeStop()
276     {
277         return _dumpBeforeStop;
278     }
279 
280     /**
281      * @param dumpBeforeStop true if {@link #dumpStdErr()} is called before stopping
282      */
283     public void setDumpBeforeStop(boolean dumpBeforeStop)
284     {
285         _dumpBeforeStop = dumpBeforeStop;
286     }
287 
288     /* ------------------------------------------------------------ */
289     public HttpField getDateField()
290     {
291         long now=System.currentTimeMillis();
292         long seconds = now/1000;
293         DateField df = _dateField;
294         
295         if (df==null || df._seconds!=seconds)
296         {
297             synchronized (this) // Trade some contention for less garbage
298             {
299                 df = _dateField;
300                 if (df==null || df._seconds!=seconds)
301                 {
302                     HttpField field=new HttpGenerator.CachedHttpField(HttpHeader.DATE,DateGenerator.formatDate(now));
303                     _dateField=new DateField(seconds,field);
304                     return field;
305                 }
306             }
307         }
308         return df._dateField;
309     }
310 
311     /* ------------------------------------------------------------ */
312     @Override
313     protected void doStart() throws Exception
314     {
315         //If the Server should be stopped when the jvm exits, register
316         //with the shutdown handler thread.
317         if (getStopAtShutdown())
318             ShutdownThread.register(this);
319 
320         //Register the Server with the handler thread for receiving
321         //remote stop commands
322         ShutdownMonitor.register(this);
323         
324         //Start a thread waiting to receive "stop" commands.
325         ShutdownMonitor.getInstance().start(); // initialize
326 
327         LOG.info("jetty-" + getVersion());
328         HttpGenerator.setJettyVersion(HttpConfiguration.SERVER_VERSION);
329         MultiException mex=new MultiException();
330 
331         // check size of thread pool
332         SizedThreadPool pool = getBean(SizedThreadPool.class);
333         int max=pool==null?-1:pool.getMaxThreads();
334         int selectors=0;
335         int acceptors=0;
336         if (mex.size()==0)
337         {
338             for (Connector connector : _connectors)
339             {
340                 if (connector instanceof AbstractConnector)
341                     acceptors+=((AbstractConnector)connector).getAcceptors();
342                     
343                 if (connector instanceof ServerConnector)
344                     selectors+=((ServerConnector)connector).getSelectorManager().getSelectorCount();
345             }
346         }
347 
348         int needed=1+selectors+acceptors;
349         if (max>0 && needed>max)
350             throw new IllegalStateException(String.format("Insufficient threads: max=%d < needed(acceptors=%d + selectors=%d + request=1)",max,acceptors,selectors));
351         
352         try
353         {
354             super.doStart();
355         }
356         catch(Throwable e)
357         {
358             mex.add(e);
359         }
360 
361         // start connectors last
362         for (Connector connector : _connectors)
363         {
364             try
365             {   
366                 connector.start();
367             }
368             catch(Throwable e)
369             {
370                 mex.add(e);
371             }
372         }
373         
374         if (isDumpAfterStart())
375             dumpStdErr();
376 
377         mex.ifExceptionThrow();
378 
379         LOG.info(String.format("Started @%dms",ManagementFactory.getRuntimeMXBean().getUptime()));
380     }
381 
382     @Override
383     protected void start(LifeCycle l) throws Exception
384     {
385         // start connectors last
386         if (!(l instanceof Connector))
387             super.start(l);
388     }
389 
390     /* ------------------------------------------------------------ */
391     @Override
392     protected void doStop() throws Exception
393     {
394         if (isDumpBeforeStop())
395             dumpStdErr();
396 
397         MultiException mex=new MultiException();
398 
399         // list if graceful futures
400         List<Future<Void>> futures = new ArrayList<>();
401 
402         // First close the network connectors to stop accepting new connections
403         for (Connector connector : _connectors)
404             futures.add(connector.shutdown());
405 
406         // Then tell the contexts that we are shutting down
407         
408         Handler[] gracefuls = getChildHandlersByClass(Graceful.class);
409         for (Handler graceful : gracefuls)
410             futures.add(((Graceful)graceful).shutdown());
411 
412         // Shall we gracefully wait for zero connections?
413         long stopTimeout = getStopTimeout();
414         if (stopTimeout>0)
415         {
416             long stop_by=System.currentTimeMillis()+stopTimeout;
417             if (LOG.isDebugEnabled())
418                 LOG.debug("Graceful shutdown {} by ",this,new Date(stop_by));
419 
420             // Wait for shutdowns
421             for (Future<Void> future: futures)
422             {
423                 try
424                 {
425                     if (!future.isDone())
426                         future.get(Math.max(1L,stop_by-System.currentTimeMillis()),TimeUnit.MILLISECONDS);
427                 }
428                 catch (Exception e)
429                 {
430                     mex.add(e);
431                 }
432             }
433         }
434 
435         // Cancel any shutdowns not done
436         for (Future<Void> future: futures)
437             if (!future.isDone())
438                 future.cancel(true);
439 
440         // Now stop the connectors (this will close existing connections)
441         for (Connector connector : _connectors)
442         {
443             try
444             {
445                 connector.stop();
446             }
447             catch (Throwable e)
448             {
449                 mex.add(e);
450             }
451         }
452 
453         // And finally stop everything else
454         try
455         {
456             super.doStop();
457         }
458         catch (Throwable e)
459         {
460             mex.add(e);
461         }
462 
463         if (getStopAtShutdown())
464             ShutdownThread.deregister(this);
465         
466         //Unregister the Server with the handler thread for receiving
467         //remote stop commands as we are stopped already
468         ShutdownMonitor.deregister(this);
469         
470 
471         mex.ifExceptionThrow();
472 
473     }
474 
475     /* ------------------------------------------------------------ */
476     /* Handle a request from a connection.
477      * Called to handle a request on the connection when either the header has been received,
478      * or after the entire request has been received (for short requests of known length), or
479      * on the dispatch of an async request.
480      */
481     public void handle(HttpChannel<?> connection) throws IOException, ServletException
482     {
483         final String target=connection.getRequest().getPathInfo();
484         final Request request=connection.getRequest();
485         final Response response=connection.getResponse();
486 
487         if (LOG.isDebugEnabled())
488             LOG.debug(request.getDispatcherType()+" "+target+" on "+connection);
489 
490         if ("*".equals(target))
491         {
492             handleOptions(request,response);
493             if (!request.isHandled())
494                 handle(target, request, request, response);
495         }
496         else
497             handle(target, request, request, response);
498 
499         if (LOG.isDebugEnabled())
500             LOG.debug("RESPONSE "+target+"  "+connection.getResponse().getStatus()+" handled="+request.isHandled());
501     }
502 
503     /* ------------------------------------------------------------ */
504     /* Handle Options request to server
505      */
506     protected void handleOptions(Request request,Response response) throws IOException
507     {
508         if (!HttpMethod.OPTIONS.is(request.getMethod()))
509             response.sendError(HttpStatus.BAD_REQUEST_400);
510         request.setHandled(true);
511         response.setStatus(200);
512         response.setContentLength(0);
513         response.closeOutput();
514     }
515 
516     /* ------------------------------------------------------------ */
517     /* Handle a request from a connection.
518      * Called to handle a request on the connection when either the header has been received,
519      * or after the entire request has been received (for short requests of known length), or
520      * on the dispatch of an async request.
521      */
522     public void handleAsync(HttpChannel<?> connection) throws IOException, ServletException
523     {
524         final HttpChannelState state = connection.getRequest().getHttpChannelState();
525         final AsyncContextEvent event = state.getAsyncContextEvent();
526 
527         final Request baseRequest=connection.getRequest();
528         final String path=event.getPath();
529         
530         if (path!=null)
531         {
532             // this is a dispatch with a path
533             ServletContext context=event.getServletContext();
534             HttpURI uri = new HttpURI(URIUtil.addPaths(context==null?null:context.getContextPath(), path));            
535             baseRequest.setUri(uri);
536             baseRequest.setRequestURI(null);
537             baseRequest.setPathInfo(uri.getDecodedPath());
538             if (uri.getQuery()!=null)
539                 baseRequest.mergeQueryParameters(uri.getQuery(), true); //we have to assume dispatch path and query are UTF8
540         }
541 
542         final String target=baseRequest.getPathInfo();
543         final HttpServletRequest request=(HttpServletRequest)event.getSuppliedRequest();
544         final HttpServletResponse response=(HttpServletResponse)event.getSuppliedResponse();
545 
546         if (LOG.isDebugEnabled())
547         {
548             LOG.debug(request.getDispatcherType()+" "+target+" on "+connection);
549             handle(target, baseRequest, request, response);
550             LOG.debug("RESPONSE "+target+"  "+connection.getResponse().getStatus());
551         }
552         else
553             handle(target, baseRequest, request, response);
554 
555     }
556 
557     /* ------------------------------------------------------------ */
558     public void join() throws InterruptedException
559     {
560         getThreadPool().join();
561     }
562 
563     /* ------------------------------------------------------------ */
564     /**
565      * @return Returns the sessionIdManager.
566      */
567     public SessionIdManager getSessionIdManager()
568     {
569         return _sessionIdManager;
570     }
571 
572     /* ------------------------------------------------------------ */
573     /**
574      * @param sessionIdManager The sessionIdManager to set.
575      */
576     public void setSessionIdManager(SessionIdManager sessionIdManager)
577     {
578         updateBean(_sessionIdManager,sessionIdManager);
579         _sessionIdManager=sessionIdManager;
580     }
581 
582     /* ------------------------------------------------------------ */
583     /*
584      * @see org.eclipse.util.AttributesMap#clearAttributes()
585      */
586     @Override
587     public void clearAttributes()
588     {
589         Enumeration<String> names = _attributes.getAttributeNames();
590         while (names.hasMoreElements())
591             removeBean(_attributes.getAttribute(names.nextElement()));
592         _attributes.clearAttributes();
593     }
594 
595     /* ------------------------------------------------------------ */
596     /*
597      * @see org.eclipse.util.AttributesMap#getAttribute(java.lang.String)
598      */
599     @Override
600     public Object getAttribute(String name)
601     {
602         return _attributes.getAttribute(name);
603     }
604 
605     /* ------------------------------------------------------------ */
606     /*
607      * @see org.eclipse.util.AttributesMap#getAttributeNames()
608      */
609     @Override
610     public Enumeration<String> getAttributeNames()
611     {
612         return AttributesMap.getAttributeNamesCopy(_attributes);
613     }
614 
615     /* ------------------------------------------------------------ */
616     /*
617      * @see org.eclipse.util.AttributesMap#removeAttribute(java.lang.String)
618      */
619     @Override
620     public void removeAttribute(String name)
621     {
622         Object bean=_attributes.getAttribute(name);
623         if (bean!=null)
624             removeBean(bean);
625         _attributes.removeAttribute(name);
626     }
627 
628     /* ------------------------------------------------------------ */
629     /*
630      * @see org.eclipse.util.AttributesMap#setAttribute(java.lang.String, java.lang.Object)
631      */
632     @Override
633     public void setAttribute(String name, Object attribute)
634     {
635         addBean(attribute);
636         _attributes.setAttribute(name, attribute);
637     }
638 
639     /* ------------------------------------------------------------ */
640     /**
641      * @return The URI of the first {@link NetworkConnector} and first {@link ContextHandler}, or null
642      */
643     @SuppressWarnings("resource")
644     public URI getURI()
645     {
646         NetworkConnector connector=null;
647         for (Connector c: _connectors)
648         {
649             if (c instanceof NetworkConnector)
650             {
651                 connector=(NetworkConnector)c;
652                 break;
653             }
654         }
655 
656         if (connector==null)
657             return null;
658 
659         ContextHandler context = getChildHandlerByClass(ContextHandler.class);
660 
661         try
662         {
663             String scheme=connector.getDefaultConnectionFactory().getProtocol().startsWith("SSL-")?"https":"http";
664 
665             String host=connector.getHost();
666             if (context!=null && context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
667                 host=context.getVirtualHosts()[0];
668             if (host==null)
669                 host=InetAddress.getLocalHost().getHostAddress();
670 
671             String path=context==null?null:context.getContextPath();
672             if (path==null)
673                 path="/";
674             return new URI(scheme,null,host,connector.getLocalPort(),path,null,null);
675         }
676         catch(Exception e)
677         {
678             LOG.warn(e);
679             return null;
680         }
681     }
682 
683     /* ------------------------------------------------------------ */
684     @Override
685     public String toString()
686     {
687         return this.getClass().getName()+"@"+Integer.toHexString(hashCode());
688     }
689 
690     /* ------------------------------------------------------------ */
691     @Override
692     public void dump(Appendable out,String indent) throws IOException
693     {
694         dumpBeans(out,indent,Collections.singleton(new ClassLoaderDump(this.getClass().getClassLoader())));
695     }
696 
697     /* ------------------------------------------------------------ */
698     public static void main(String...args) throws Exception
699     {
700         System.err.println(getVersion());
701     }
702 
703     /* ------------------------------------------------------------ */
704     /* ------------------------------------------------------------ */
705     private static class DateField
706     {
707         final long _seconds;
708         final HttpField _dateField;
709         public DateField(long seconds, HttpField dateField)
710         {
711             super();
712             _seconds = seconds;
713             _dateField = dateField;
714         }
715         
716     }
717 }