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