View Javadoc

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