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             ShutdownThread.register(this);
285             ShutdownMonitor.getInstance().start(); // initialize
286         }
287 
288         LOG.info("jetty-"+getVersion());
289         HttpGenerator.setServerVersion(getVersion());
290         MultiException mex=new MultiException();
291 
292         try
293         {
294             super.doStart();
295         }
296         catch(Throwable e)
297         {
298             mex.add(e);
299         }
300 
301         if (mex.size()==0)
302         {
303             for (Connector _connector : _connectors)
304             {
305                 try
306                 {
307                     _connector.start();
308                 }
309                 catch (Throwable e)
310                 {
311                     mex.add(e);
312                 }
313             }
314         }
315 
316         if (isDumpAfterStart())
317             dumpStdErr();
318 
319         
320         // use DateCache timer for Date field reformat
321         final HttpFields.DateGenerator date = new HttpFields.DateGenerator();
322         long now=System.currentTimeMillis();
323         long tick=1000*((now/1000)+1)-now;
324         _dateField=new HttpField.CachedHttpField(HttpHeader.DATE,date.formatDate(now));
325         DateCache.getTimer().scheduleAtFixedRate(new TimerTask()
326         {
327             @Override
328             public void run()
329             {
330                 long now=System.currentTimeMillis();
331                 _dateField=new HttpField.CachedHttpField(HttpHeader.DATE,date.formatDate(now));
332                 if (!isRunning())
333                     this.cancel();
334             }
335         },tick,1000);
336         
337         
338         mex.ifExceptionThrow();
339     }
340 
341     @Override
342     protected void start(LifeCycle l) throws Exception
343     {
344         // start connectors last
345         if (!(l instanceof Connector))
346             super.start(l);
347     }
348 
349     /* ------------------------------------------------------------ */
350     @Override
351     protected void doStop() throws Exception
352     {
353         if (isDumpBeforeStop())
354             dumpStdErr();
355 
356         MultiException mex=new MultiException();
357 
358         // list if graceful futures
359         List<Future<Void>> futures = new ArrayList<>();
360 
361         // First close the network connectors to stop accepting new connections
362         for (Connector connector : _connectors)
363             futures.add(connector.shutdown());
364 
365         // Then tell the contexts that we are shutting down
366         Handler[] contexts = getChildHandlersByClass(Graceful.class);
367         for (Handler context : contexts)
368             futures.add(((Graceful)context).shutdown());
369 
370         // Shall we gracefully wait for zero connections?
371         long stopTimeout = getStopTimeout();
372         if (stopTimeout>0)
373         {
374             long stop_by=System.currentTimeMillis()+stopTimeout;
375             LOG.info("Graceful shutdown {} by ",this,new Date(stop_by));
376 
377             // Wait for shutdowns
378             for (Future<Void> future: futures)
379             {
380                 try
381                 {
382                     if (!future.isDone())
383                         future.get(Math.max(1L,stop_by-System.currentTimeMillis()),TimeUnit.MILLISECONDS);
384                 }
385                 catch (Exception e)
386                 {
387                     mex.add(e.getCause());
388                 }
389             }
390         }
391 
392         // Cancel any shutdowns not done
393         for (Future<Void> future: futures)
394             if (!future.isDone())
395                 future.cancel(true);
396 
397         // Now stop the connectors (this will close existing connections)
398         for (Connector connector : _connectors)
399         {
400             try
401             {
402                 connector.stop();
403             }
404             catch (Throwable e)
405             {
406                 mex.add(e);
407             }
408         }
409 
410         // And finally stop everything else
411         try
412         {
413             super.doStop();
414         }
415         catch (Throwable e)
416         {
417             mex.add(e);
418         }
419 
420         if (getStopAtShutdown())
421             ShutdownThread.deregister(this);
422 
423         mex.ifExceptionThrow();
424 
425     }
426 
427     /* ------------------------------------------------------------ */
428     /* Handle a request from a connection.
429      * Called to handle a request on the connection when either the header has been received,
430      * or after the entire request has been received (for short requests of known length), or
431      * on the dispatch of an async request.
432      */
433     public void handle(HttpChannel<?> connection) throws IOException, ServletException
434     {
435         final String target=connection.getRequest().getPathInfo();
436         final Request request=connection.getRequest();
437         final Response response=connection.getResponse();
438 
439         if (LOG.isDebugEnabled())
440             LOG.debug("REQUEST "+target+" on "+connection);
441 
442         if ("*".equals(target))
443         {
444             handleOptions(request,response);
445             if (!request.isHandled())
446                 handle(target, request, request, response);
447         }
448         else
449             handle(target, request, request, response);
450 
451         if (LOG.isDebugEnabled())
452             LOG.debug("RESPONSE "+target+"  "+connection.getResponse().getStatus()+" handled="+request.isHandled());
453     }
454 
455     /* ------------------------------------------------------------ */
456     /* Handle Options request to server
457      */
458     protected void handleOptions(Request request,Response response) throws IOException
459     {
460         if (!HttpMethod.OPTIONS.is(request.getMethod()))
461             response.sendError(HttpStatus.BAD_REQUEST_400);
462         request.setHandled(true);
463         response.setStatus(200);
464         response.getHttpFields().put(HttpHeader.ALLOW,"GET,POST,HEAD,OPTIONS");
465         response.setContentLength(0);
466         response.complete();
467     }
468 
469     /* ------------------------------------------------------------ */
470     /* Handle a request from a connection.
471      * Called to handle a request on the connection when either the header has been received,
472      * or after the entire request has been received (for short requests of known length), or
473      * on the dispatch of an async request.
474      */
475     public void handleAsync(HttpChannel<?> connection) throws IOException, ServletException
476     {
477         final HttpChannelState async = connection.getRequest().getHttpChannelState();
478         final HttpChannelState.AsyncEventState state = async.getAsyncEventState();
479 
480         final Request baseRequest=connection.getRequest();
481         final String path=state.getPath();
482 
483         if (path!=null)
484         {
485             // this is a dispatch with a path
486             ServletContext context=state.getServletContext();
487             HttpURI uri = new HttpURI(context==null?path:URIUtil.addPaths(context.getContextPath(),path));
488             baseRequest.setUri(uri);
489             baseRequest.setRequestURI(null);
490             baseRequest.setPathInfo(baseRequest.getRequestURI());
491             if (uri.getQuery()!=null)
492                 baseRequest.mergeQueryString(uri.getQuery()); //we have to assume dispatch path and query are UTF8
493         }
494 
495         final String target=baseRequest.getPathInfo();
496         final HttpServletRequest request=(HttpServletRequest)async.getRequest();
497         final HttpServletResponse response=(HttpServletResponse)async.getResponse();
498 
499         if (LOG.isDebugEnabled())
500         {
501             LOG.debug("REQUEST "+target+" on "+connection);
502             handle(target, baseRequest, request, response);
503             LOG.debug("RESPONSE "+target+"  "+connection.getResponse().getStatus());
504         }
505         else
506             handle(target, baseRequest, request, response);
507 
508     }
509 
510 
511     /* ------------------------------------------------------------ */
512     public void join() throws InterruptedException
513     {
514         getThreadPool().join();
515     }
516 
517     /* ------------------------------------------------------------ */
518     /**
519      * @return Returns the sessionIdManager.
520      */
521     public SessionIdManager getSessionIdManager()
522     {
523         return _sessionIdManager;
524     }
525 
526     /* ------------------------------------------------------------ */
527     /**
528      * @param sessionIdManager The sessionIdManager to set.
529      */
530     public void setSessionIdManager(SessionIdManager sessionIdManager)
531     {
532         updateBean(_sessionIdManager,sessionIdManager);
533         _sessionIdManager=sessionIdManager;
534     }
535 
536     /* ------------------------------------------------------------ */
537     /*
538      * @see org.eclipse.util.AttributesMap#clearAttributes()
539      */
540     @Override
541     public void clearAttributes()
542     {
543         Enumeration names = _attributes.getAttributeNames();
544         while (names.hasMoreElements())
545             removeBean(_attributes.getAttribute((String)names.nextElement()));
546         _attributes.clearAttributes();
547     }
548 
549     /* ------------------------------------------------------------ */
550     /*
551      * @see org.eclipse.util.AttributesMap#getAttribute(java.lang.String)
552      */
553     @Override
554     public Object getAttribute(String name)
555     {
556         return _attributes.getAttribute(name);
557     }
558 
559     /* ------------------------------------------------------------ */
560     /*
561      * @see org.eclipse.util.AttributesMap#getAttributeNames()
562      */
563     @Override
564     public Enumeration<String> getAttributeNames()
565     {
566         return AttributesMap.getAttributeNamesCopy(_attributes);
567     }
568 
569     /* ------------------------------------------------------------ */
570     /*
571      * @see org.eclipse.util.AttributesMap#removeAttribute(java.lang.String)
572      */
573     @Override
574     public void removeAttribute(String name)
575     {
576         Object bean=_attributes.getAttribute(name);
577         if (bean!=null)
578             removeBean(bean);
579         _attributes.removeAttribute(name);
580     }
581 
582     /* ------------------------------------------------------------ */
583     /*
584      * @see org.eclipse.util.AttributesMap#setAttribute(java.lang.String, java.lang.Object)
585      */
586     @Override
587     public void setAttribute(String name, Object attribute)
588     {
589         addBean(attribute);
590         _attributes.setAttribute(name, attribute);
591     }
592 
593     /* ------------------------------------------------------------ */
594     /**
595      * @return The URI of the first {@link NetworkConnector} and first {@link ContextHandler}, or null
596      */
597     public URI getURI()
598     {
599         NetworkConnector connector=null;
600         for (Connector c: _connectors)
601         {
602             if (c instanceof NetworkConnector)
603             {
604                 connector=(NetworkConnector)c;
605                 break;
606             }
607         }
608             
609         if (connector==null)
610             return null;
611         
612         ContextHandler context = getChildHandlerByClass(ContextHandler.class);
613 
614         try
615         {
616             String scheme=connector.getDefaultConnectionFactory().getProtocol().startsWith("SSL-")?"https":"http";
617 
618             String host=connector.getHost();
619             if (context!=null && context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
620                 host=context.getVirtualHosts()[0];
621             if (host==null)
622                 host=InetAddress.getLocalHost().getHostAddress();
623             
624             String path=context==null?null:context.getContextPath();
625             if (path==null)
626                 path="/";
627             return new URI(scheme,null,host,connector.getLocalPort(),path,null,null);
628         }
629         catch(Exception e)
630         {
631             LOG.warn(e);
632             return null;
633         }
634     }
635     
636     /* ------------------------------------------------------------ */
637     @Override
638     public String toString()
639     {
640         return this.getClass().getName()+"@"+Integer.toHexString(hashCode());
641     }
642 
643     @Override
644     public void dump(Appendable out,String indent) throws IOException
645     {
646         dumpBeans(out,indent,Collections.singleton(new ClassLoaderDump(this.getClass().getClassLoader())));
647     }
648     
649     /* ------------------------------------------------------------ */
650     public static void main(String...args) throws Exception
651     {
652         System.err.println(getVersion());
653     }
654 }