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