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.
9 // The Eclipse Public License is available at
10 // http://www.eclipse.org/legal/epl-v10.html
12 // The Apache License v2.0 is available at
13 // http://www.opensource.org/licenses/apache2.0.php
15 // You may elect to redistribute this code under either of these licenses.
16 // ========================================================================
19 package org.eclipse.jetty.server;
21 import java.io.IOException;
22 import java.net.InetAddress;
23 import java.net.InetSocketAddress;
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;
35 import javax.servlet.ServletContext;
36 import javax.servlet.ServletException;
37 import javax.servlet.http.HttpServletRequest;
38 import javax.servlet.http.HttpServletResponse;
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.server.handler.ContextHandler;
48 import org.eclipse.jetty.server.handler.HandlerWrapper;
49 import org.eclipse.jetty.server.handler.StatisticsHandler;
50 import org.eclipse.jetty.util.Attributes;
51 import org.eclipse.jetty.util.AttributesMap;
52 import org.eclipse.jetty.util.Jetty;
53 import org.eclipse.jetty.util.MultiException;
54 import org.eclipse.jetty.util.URIUtil;
55 import org.eclipse.jetty.util.Uptime;
56 import org.eclipse.jetty.util.annotation.ManagedAttribute;
57 import org.eclipse.jetty.util.annotation.ManagedObject;
58 import org.eclipse.jetty.util.annotation.Name;
59 import org.eclipse.jetty.util.component.Graceful;
60 import org.eclipse.jetty.util.component.LifeCycle;
61 import org.eclipse.jetty.util.log.Log;
62 import org.eclipse.jetty.util.log.Logger;
63 import org.eclipse.jetty.util.thread.QueuedThreadPool;
64 import org.eclipse.jetty.util.thread.ShutdownThread;
65 import org.eclipse.jetty.util.thread.ThreadPool;
66 import org.eclipse.jetty.util.thread.ThreadPool.SizedThreadPool;
68 /* ------------------------------------------------------------ */
69 /** Jetty HTTP Servlet Server.
70 * This class is the main class for the Jetty HTTP Servlet server.
71 * It aggregates Connectors (HTTP request receivers) and request Handlers.
72 * The server is itself a handler and a ThreadPool. Connectors use the ThreadPool methods
73 * to run jobs that will eventually call the handle method.
75 @ManagedObject(value="Jetty HTTP Servlet server")
76 public class Server extends HandlerWrapper implements Attributes
78 private static final Logger LOG = Log.getLogger(Server.class);
80 private final AttributesMap _attributes = new AttributesMap();
81 private final ThreadPool _threadPool;
82 private final List<Connector> _connectors = new CopyOnWriteArrayList<>();
83 private SessionIdManager _sessionIdManager;
84 private boolean _stopAtShutdown;
85 private boolean _dumpAfterStart=false;
86 private boolean _dumpBeforeStop=false;
88 private volatile DateField _dateField;
90 /* ------------------------------------------------------------ */
93 this((ThreadPool)null);
96 /* ------------------------------------------------------------ */
97 /** Convenience constructor
98 * Creates server and a {@link ServerConnector} at the passed port.
99 * @param port The port of a network HTTP connector (or 0 for a randomly allocated port).
100 * @see NetworkConnector#getLocalPort()
102 public Server(@Name("port")int port)
104 this((ThreadPool)null);
105 ServerConnector connector=new ServerConnector(this);
106 connector.setPort(port);
107 setConnectors(new Connector[]{connector});
110 /* ------------------------------------------------------------ */
111 /** Convenience constructor
112 * Creates server and a {@link ServerConnector} at the passed address.
114 public Server(@Name("address")InetSocketAddress addr)
116 this((ThreadPool)null);
117 ServerConnector connector=new ServerConnector(this);
118 connector.setHost(addr.getHostName());
119 connector.setPort(addr.getPort());
120 setConnectors(new Connector[]{connector});
125 /* ------------------------------------------------------------ */
126 public Server(@Name("threadpool") ThreadPool pool)
128 _threadPool=pool!=null?pool:new QueuedThreadPool();
129 addBean(_threadPool);
134 /* ------------------------------------------------------------ */
135 @ManagedAttribute("version of this server")
136 public static String getVersion()
138 return Jetty.VERSION;
141 /* ------------------------------------------------------------ */
142 public boolean getStopAtShutdown()
144 return _stopAtShutdown;
148 /* ------------------------------------------------------------ */
150 * Set a graceful stop time.
151 * The {@link StatisticsHandler} must be configured so that open connections can
152 * be tracked for a graceful shutdown.
153 * @see org.eclipse.jetty.util.component.ContainerLifeCycle#setStopTimeout(long)
156 public void setStopTimeout(long stopTimeout)
158 super.setStopTimeout(stopTimeout);
161 /* ------------------------------------------------------------ */
162 /** Set stop server at shutdown behaviour.
163 * @param stop If true, this server instance will be explicitly stopped when the
164 * JVM is shutdown. Otherwise the JVM is stopped with the server running.
165 * @see Runtime#addShutdownHook(Thread)
166 * @see ShutdownThread
168 public void setStopAtShutdown(boolean stop)
170 //if we now want to stop
173 //and we weren't stopping before
174 if (!_stopAtShutdown)
176 //only register to stop if we're already started (otherwise we'll do it in doStart())
178 ShutdownThread.register(this);
182 ShutdownThread.deregister(this);
184 _stopAtShutdown=stop;
187 /* ------------------------------------------------------------ */
189 * @return Returns the connectors.
191 @ManagedAttribute(value="connectors for this server", readonly=true)
192 public Connector[] getConnectors()
194 List<Connector> connectors = new ArrayList<>(_connectors);
195 return connectors.toArray(new Connector[connectors.size()]);
198 /* ------------------------------------------------------------ */
199 public void addConnector(Connector connector)
201 if (connector.getServer() != this)
202 throw new IllegalArgumentException("Connector " + connector +
203 " cannot be shared among server " + connector.getServer() + " and server " + this);
204 if (_connectors.add(connector))
208 /* ------------------------------------------------------------ */
210 * Convenience method which calls {@link #getConnectors()} and {@link #setConnectors(Connector[])} to
211 * remove a connector.
212 * @param connector The connector to remove.
214 public void removeConnector(Connector connector)
216 if (_connectors.remove(connector))
217 removeBean(connector);
220 /* ------------------------------------------------------------ */
221 /** Set the connectors for this server.
222 * Each connector has this server set as it's ThreadPool and its Handler.
223 * @param connectors The connectors to set.
225 public void setConnectors(Connector[] connectors)
227 if (connectors != null)
229 for (Connector connector : connectors)
231 if (connector.getServer() != this)
232 throw new IllegalArgumentException("Connector " + connector +
233 " cannot be shared among server " + connector.getServer() + " and server " + this);
237 Connector[] oldConnectors = getConnectors();
238 updateBeans(oldConnectors, connectors);
239 _connectors.removeAll(Arrays.asList(oldConnectors));
240 if (connectors != null)
241 _connectors.addAll(Arrays.asList(connectors));
244 /* ------------------------------------------------------------ */
246 * @return Returns the threadPool.
248 @ManagedAttribute("the server thread pool")
249 public ThreadPool getThreadPool()
255 * @return true if {@link #dumpStdErr()} is called after starting
257 @ManagedAttribute("dump state to stderr after start")
258 public boolean isDumpAfterStart()
260 return _dumpAfterStart;
264 * @param dumpAfterStart true if {@link #dumpStdErr()} is called after starting
266 public void setDumpAfterStart(boolean dumpAfterStart)
268 _dumpAfterStart = dumpAfterStart;
272 * @return true if {@link #dumpStdErr()} is called before stopping
274 @ManagedAttribute("dump state to stderr before stop")
275 public boolean isDumpBeforeStop()
277 return _dumpBeforeStop;
281 * @param dumpBeforeStop true if {@link #dumpStdErr()} is called before stopping
283 public void setDumpBeforeStop(boolean dumpBeforeStop)
285 _dumpBeforeStop = dumpBeforeStop;
288 /* ------------------------------------------------------------ */
289 public HttpField getDateField()
291 long now=System.currentTimeMillis();
292 long seconds = now/1000;
293 DateField df = _dateField;
295 if (df==null || df._seconds!=seconds)
297 synchronized (this) // Trade some contention for less garbage
300 if (df==null || df._seconds!=seconds)
302 HttpField field=new HttpGenerator.CachedHttpField(HttpHeader.DATE,DateGenerator.formatDate(now));
303 _dateField=new DateField(seconds,field);
308 return df._dateField;
311 /* ------------------------------------------------------------ */
313 protected void doStart() throws Exception
315 //If the Server should be stopped when the jvm exits, register
316 //with the shutdown handler thread.
317 if (getStopAtShutdown())
318 ShutdownThread.register(this);
320 //Register the Server with the handler thread for receiving
321 //remote stop commands
322 ShutdownMonitor.register(this);
324 //Start a thread waiting to receive "stop" commands.
325 ShutdownMonitor.getInstance().start(); // initialize
327 LOG.info("jetty-" + getVersion());
328 HttpGenerator.setJettyVersion(HttpConfiguration.SERVER_VERSION);
329 MultiException mex=new MultiException();
331 // check size of thread pool
332 SizedThreadPool pool = getBean(SizedThreadPool.class);
333 int max=pool==null?-1:pool.getMaxThreads();
338 for (Connector connector : _connectors)
340 if (connector instanceof AbstractConnector)
341 acceptors+=((AbstractConnector)connector).getAcceptors();
343 if (connector instanceof ServerConnector)
344 selectors+=((ServerConnector)connector).getSelectorManager().getSelectorCount();
348 int needed=1+selectors+acceptors;
349 if (max>0 && needed>max)
350 throw new IllegalStateException(String.format("Insufficient threads: max=%d < needed(acceptors=%d + selectors=%d + request=1)",max,acceptors,selectors));
361 // start connectors last
362 for (Connector connector : _connectors)
374 if (isDumpAfterStart())
377 mex.ifExceptionThrow();
379 LOG.info(String.format("Started @%dms",Uptime.getUptime()));
383 protected void start(LifeCycle l) throws Exception
385 // start connectors last
386 if (!(l instanceof Connector))
390 /* ------------------------------------------------------------ */
392 protected void doStop() throws Exception
394 if (isDumpBeforeStop())
397 MultiException mex=new MultiException();
399 // list if graceful futures
400 List<Future<Void>> futures = new ArrayList<>();
402 // First close the network connectors to stop accepting new connections
403 for (Connector connector : _connectors)
404 futures.add(connector.shutdown());
406 // Then tell the contexts that we are shutting down
408 Handler[] gracefuls = getChildHandlersByClass(Graceful.class);
409 for (Handler graceful : gracefuls)
410 futures.add(((Graceful)graceful).shutdown());
412 // Shall we gracefully wait for zero connections?
413 long stopTimeout = getStopTimeout();
416 long stop_by=System.currentTimeMillis()+stopTimeout;
417 if (LOG.isDebugEnabled())
418 LOG.debug("Graceful shutdown {} by ",this,new Date(stop_by));
420 // Wait for shutdowns
421 for (Future<Void> future: futures)
425 if (!future.isDone())
426 future.get(Math.max(1L,stop_by-System.currentTimeMillis()),TimeUnit.MILLISECONDS);
435 // Cancel any shutdowns not done
436 for (Future<Void> future: futures)
437 if (!future.isDone())
440 // Now stop the connectors (this will close existing connections)
441 for (Connector connector : _connectors)
453 // And finally stop everything else
463 if (getStopAtShutdown())
464 ShutdownThread.deregister(this);
466 //Unregister the Server with the handler thread for receiving
467 //remote stop commands as we are stopped already
468 ShutdownMonitor.deregister(this);
471 mex.ifExceptionThrow();
475 /* ------------------------------------------------------------ */
476 /* Handle a request from a connection.
477 * Called to handle a request on the connection when either the header has been received,
478 * or after the entire request has been received (for short requests of known length), or
479 * on the dispatch of an async request.
481 public void handle(HttpChannel<?> connection) throws IOException, ServletException
483 final String target=connection.getRequest().getPathInfo();
484 final Request request=connection.getRequest();
485 final Response response=connection.getResponse();
487 if (LOG.isDebugEnabled())
488 LOG.debug(request.getDispatcherType()+" "+request.getMethod()+" "+target+" on "+connection);
490 if (HttpMethod.OPTIONS.is(request.getMethod()) || "*".equals(target))
492 if (!HttpMethod.OPTIONS.is(request.getMethod()))
493 response.sendError(HttpStatus.BAD_REQUEST_400);
494 handleOptions(request,response);
495 if (!request.isHandled())
496 handle(target, request, request, response);
499 handle(target, request, request, response);
501 if (LOG.isDebugEnabled())
502 LOG.debug("RESPONSE "+target+" "+connection.getResponse().getStatus()+" handled="+request.isHandled());
505 /* ------------------------------------------------------------ */
506 /* Handle Options request to server
508 protected void handleOptions(Request request,Response response) throws IOException
512 /* ------------------------------------------------------------ */
513 /* Handle a request from a connection.
514 * Called to handle a request on the connection when either the header has been received,
515 * or after the entire request has been received (for short requests of known length), or
516 * on the dispatch of an async request.
518 public void handleAsync(HttpChannel<?> connection) throws IOException, ServletException
520 final HttpChannelState state = connection.getRequest().getHttpChannelState();
521 final AsyncContextEvent event = state.getAsyncContextEvent();
523 final Request baseRequest=connection.getRequest();
524 final String path=event.getPath();
528 // this is a dispatch with a path
529 ServletContext context=event.getServletContext();
530 HttpURI uri = new HttpURI(URIUtil.addPaths(context==null?null:context.getContextPath(), path));
531 baseRequest.setUri(uri);
532 baseRequest.setRequestURI(null);
533 baseRequest.setPathInfo(uri.getDecodedPath());
534 if (uri.getQuery()!=null)
535 baseRequest.mergeQueryParameters(uri.getQuery(), true); //we have to assume dispatch path and query are UTF8
538 final String target=baseRequest.getPathInfo();
539 final HttpServletRequest request=(HttpServletRequest)event.getSuppliedRequest();
540 final HttpServletResponse response=(HttpServletResponse)event.getSuppliedResponse();
542 if (LOG.isDebugEnabled())
544 LOG.debug(request.getDispatcherType()+" "+request.getMethod()+" "+target+" on "+connection);
545 handle(target, baseRequest, request, response);
546 LOG.debug("RESPONSE "+target+" "+connection.getResponse().getStatus());
549 handle(target, baseRequest, request, response);
553 /* ------------------------------------------------------------ */
554 public void join() throws InterruptedException
556 getThreadPool().join();
559 /* ------------------------------------------------------------ */
561 * @return Returns the sessionIdManager.
563 public SessionIdManager getSessionIdManager()
565 return _sessionIdManager;
568 /* ------------------------------------------------------------ */
570 * @param sessionIdManager The sessionIdManager to set.
572 public void setSessionIdManager(SessionIdManager sessionIdManager)
574 updateBean(_sessionIdManager,sessionIdManager);
575 _sessionIdManager=sessionIdManager;
578 /* ------------------------------------------------------------ */
580 * @see org.eclipse.util.AttributesMap#clearAttributes()
583 public void clearAttributes()
585 Enumeration<String> names = _attributes.getAttributeNames();
586 while (names.hasMoreElements())
587 removeBean(_attributes.getAttribute(names.nextElement()));
588 _attributes.clearAttributes();
591 /* ------------------------------------------------------------ */
593 * @see org.eclipse.util.AttributesMap#getAttribute(java.lang.String)
596 public Object getAttribute(String name)
598 return _attributes.getAttribute(name);
601 /* ------------------------------------------------------------ */
603 * @see org.eclipse.util.AttributesMap#getAttributeNames()
606 public Enumeration<String> getAttributeNames()
608 return AttributesMap.getAttributeNamesCopy(_attributes);
611 /* ------------------------------------------------------------ */
613 * @see org.eclipse.util.AttributesMap#removeAttribute(java.lang.String)
616 public void removeAttribute(String name)
618 Object bean=_attributes.getAttribute(name);
621 _attributes.removeAttribute(name);
624 /* ------------------------------------------------------------ */
626 * @see org.eclipse.util.AttributesMap#setAttribute(java.lang.String, java.lang.Object)
629 public void setAttribute(String name, Object attribute)
632 _attributes.setAttribute(name, attribute);
635 /* ------------------------------------------------------------ */
637 * @return The URI of the first {@link NetworkConnector} and first {@link ContextHandler}, or null
639 @SuppressWarnings("resource")
642 NetworkConnector connector=null;
643 for (Connector c: _connectors)
645 if (c instanceof NetworkConnector)
647 connector=(NetworkConnector)c;
655 ContextHandler context = getChildHandlerByClass(ContextHandler.class);
659 String scheme=connector.getDefaultConnectionFactory().getProtocol().startsWith("SSL-")?"https":"http";
661 String host=connector.getHost();
662 if (context!=null && context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
663 host=context.getVirtualHosts()[0];
665 host=InetAddress.getLocalHost().getHostAddress();
667 String path=context==null?null:context.getContextPath();
670 return new URI(scheme,null,host,connector.getLocalPort(),path,null,null);
679 /* ------------------------------------------------------------ */
681 public String toString()
683 return this.getClass().getName()+"@"+Integer.toHexString(hashCode());
686 /* ------------------------------------------------------------ */
688 public void dump(Appendable out,String indent) throws IOException
690 dumpBeans(out,indent,Collections.singleton(new ClassLoaderDump(this.getClass().getClassLoader())));
693 /* ------------------------------------------------------------ */
694 public static void main(String...args) throws Exception
696 System.err.println(getVersion());
699 /* ------------------------------------------------------------ */
700 /* ------------------------------------------------------------ */
701 private static class DateField
704 final HttpField _dateField;
705 public DateField(long seconds, HttpField dateField)
709 _dateField = dateField;