View Javadoc

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