View Javadoc

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