package org.eclipse.jetty.server;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.jetty.http.BadMessageException;
import org.eclipse.jetty.http.DateGenerator;
import org.eclipse.jetty.http.HttpField;
import org.eclipse.jetty.http.HttpGenerator;
import org.eclipse.jetty.http.HttpHeader;
import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.http.HttpStatus;
import org.eclipse.jetty.http.HttpURI;
import org.eclipse.jetty.http.PreEncodedHttpField;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.server.handler.HandlerWrapper;
import org.eclipse.jetty.util.Attributes;
import org.eclipse.jetty.util.Jetty;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.MultiMap;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.URIUtil;
import org.eclipse.jetty.util.Uptime;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.Name;
import org.eclipse.jetty.util.component.AttributeContainerMap;
import org.eclipse.jetty.util.component.Graceful;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ShutdownThread;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ManagedObject(value = "Jetty HTTP Servlet server")
public class Server extends HandlerWrapper implements Attributes
{
private static final Logger LOG = LoggerFactory.getLogger(Server.class);
private final AttributeContainerMap _attributes = new AttributeContainerMap();
private final ThreadPool _threadPool;
private final List<Connector> _connectors = new CopyOnWriteArrayList<>();
private SessionIdManager _sessionIdManager;
private boolean _stopAtShutdown;
private boolean _dumpAfterStart;
private boolean _dumpBeforeStop;
private ErrorHandler _errorHandler;
private RequestLog _requestLog;
private boolean _dryRun;
private final AutoLock _dateLock = new AutoLock();
private volatile DateField _dateField;
private long _stopTimeout;
public Server()
{
this((ThreadPool)null);
}
public Server(@Name("port") int port)
{
this((ThreadPool)null);
ServerConnector connector = new ServerConnector(this);
connector.setPort(port);
setConnectors(new Connector[]{connector});
addBean(_attributes);
}
public Server(@Name("address") InetSocketAddress addr)
{
this((ThreadPool)null);
ServerConnector connector = new ServerConnector(this);
connector.setHost(addr.getHostName());
connector.setPort(addr.getPort());
setConnectors(new Connector[]{connector});
}
public Server(@Name("threadpool") ThreadPool pool)
{
_threadPool = pool != null ? pool : new QueuedThreadPool();
addBean(_threadPool);
setServer(this);
}
public boolean isDryRun()
{
return _dryRun;
}
public void setDryRun(boolean dryRun)
{
_dryRun = dryRun;
}
public RequestLog getRequestLog()
{
return _requestLog;
}
public ErrorHandler getErrorHandler()
{
return _errorHandler;
}
public void setRequestLog(RequestLog requestLog)
{
updateBean(_requestLog, requestLog);
_requestLog = requestLog;
}
public void setErrorHandler(ErrorHandler errorHandler)
{
if (errorHandler instanceof ErrorHandler.ErrorPageMapper)
throw new IllegalArgumentException("ErrorPageMapper is applicable only to ContextHandler");
updateBean(_errorHandler, errorHandler);
_errorHandler = errorHandler;
if (errorHandler != null)
errorHandler.setServer(this);
}
@ManagedAttribute("version of this server")
public static String getVersion()
{
return Jetty.VERSION;
}
public void setStopTimeout(long stopTimeout)
{
_stopTimeout = stopTimeout;
}
public long getStopTimeout()
{
return _stopTimeout;
}
public boolean getStopAtShutdown()
{
return _stopAtShutdown;
}
public void setStopAtShutdown(boolean stop)
{
if (stop)
{
if (!_stopAtShutdown)
{
if (isStarted())
ShutdownThread.register(this);
}
}
else
ShutdownThread.deregister(this);
_stopAtShutdown = stop;
}
@ManagedAttribute(value = "connectors for this server", readonly = true)
public Connector[] getConnectors()
{
List<Connector> connectors = new ArrayList<>(_connectors);
return connectors.toArray(new Connector[connectors.size()]);
}
public void addConnector(Connector connector)
{
if (connector.getServer() != this)
throw new IllegalArgumentException("Connector " + connector +
" cannot be shared among server " + connector.getServer() + " and server " + this);
_connectors.add(connector);
addBean(connector);
}
public void removeConnector(Connector connector)
{
if (_connectors.remove(connector))
removeBean(connector);
}
public void setConnectors(Connector[] connectors)
{
if (connectors != null)
{
for (Connector connector : connectors)
{
if (connector.getServer() != this)
throw new IllegalArgumentException("Connector " + connector +
" cannot be shared among server " + connector.getServer() + " and server " + this);
}
}
Connector[] oldConnectors = getConnectors();
updateBeans(oldConnectors, connectors);
_connectors.removeAll(Arrays.asList(oldConnectors));
if (connectors != null)
_connectors.addAll(Arrays.asList(connectors));
}
public void addBeanToAllConnectors(Object bean)
{
for (Connector connector : getConnectors())
{
connector.addBean(bean);
}
}
@ManagedAttribute("the server thread pool")
public ThreadPool getThreadPool()
{
return _threadPool;
}
@ManagedAttribute("dump state to stderr after start")
public boolean isDumpAfterStart()
{
return _dumpAfterStart;
}
public void setDumpAfterStart(boolean dumpAfterStart)
{
_dumpAfterStart = dumpAfterStart;
}
@ManagedAttribute("dump state to stderr before stop")
public boolean isDumpBeforeStop()
{
return _dumpBeforeStop;
}
public void setDumpBeforeStop(boolean dumpBeforeStop)
{
_dumpBeforeStop = dumpBeforeStop;
}
public HttpField getDateField()
{
long now = System.currentTimeMillis();
long seconds = now / 1000;
DateField df = _dateField;
if (df == null || df._seconds != seconds)
{
try (AutoLock lock = _dateLock.lock())
{
df = _dateField;
if (df == null || df._seconds != seconds)
{
HttpField field = new PreEncodedHttpField(HttpHeader.DATE, DateGenerator.formatDate(now));
_dateField = new DateField(seconds, field);
return field;
}
}
}
return df._dateField;
}
@Override
protected void doStart() throws Exception
{
try
{
if (_errorHandler == null)
_errorHandler = getBean(ErrorHandler.class);
if (_errorHandler == null)
setErrorHandler(new ErrorHandler());
if (_errorHandler instanceof ErrorHandler.ErrorPageMapper)
LOG.warn("ErrorPageMapper not supported for Server level Error Handling");
_errorHandler.setServer(this);
if (getStopAtShutdown())
ShutdownThread.register(this);
ShutdownMonitor.register(this);
ShutdownMonitor.getInstance().start();
String gitHash = Jetty.GIT_HASH;
String timestamp = Jetty.BUILD_TIMESTAMP;
LOG.info("jetty-{}; built: {}; git: {}; jvm {}", getVersion(), timestamp, gitHash, System.getProperty("java.runtime.version", System.getProperty("java.version")));
if (!Jetty.STABLE)
{
LOG.warn("THIS IS NOT A STABLE RELEASE! DO NOT USE IN PRODUCTION!");
LOG.warn("Download a stable release from https://download.eclipse.org/jetty/");
}
HttpGenerator.setJettyVersion(HttpConfiguration.SERVER_VERSION);
MultiException mex = new MultiException();
if (!_dryRun)
{
_connectors.stream().filter(NetworkConnector.class::isInstance).map(NetworkConnector.class::cast).forEach(connector ->
{
try
{
connector.open();
}
catch (Throwable th)
{
mex.add(th);
}
});
mex.ifExceptionThrow();
}
super.doStart();
if (_dryRun)
{
LOG.info(String.format("Started(dry run) %s @%dms", this, Uptime.getUptime()));
throw new StopException();
}
for (Connector connector : _connectors)
{
try
{
connector.start();
}
catch (Throwable e)
{
mex.add(e);
_connectors.stream().filter(LifeCycle::isRunning).map(Object.class::cast).forEach(LifeCycle::stop);
}
}
mex.ifExceptionThrow();
LOG.info(String.format("Started %s @%dms", this, Uptime.getUptime()));
}
catch (Throwable th)
{
_connectors.stream().filter(NetworkConnector.class::isInstance).map(NetworkConnector.class::cast).forEach(nc ->
{
try
{
nc.close();
}
catch (Throwable th2)
{
if (th != th2)
th.addSuppressed(th2);
}
});
throw th;
}
finally
{
if (isDumpAfterStart() && !(_dryRun && isDumpBeforeStop()))
dumpStdErr();
}
}
@Override
protected void start(LifeCycle l) throws Exception
{
if (!(l instanceof Connector))
super.start(l);
}
@Override
protected void doStop() throws Exception
{
if (isDumpBeforeStop())
dumpStdErr();
LOG.info(String.format("Stopped %s", this));
if (LOG.isDebugEnabled())
LOG.debug("doStop {}", this);
MultiException mex = new MultiException();
if (getStopTimeout() > 0)
{
long end = System.nanoTime() + TimeUnit.MILLISECONDS.toNanos(getStopTimeout());
try
{
Graceful.shutdown(this).get(getStopTimeout(), TimeUnit.MILLISECONDS);
}
catch (Throwable e)
{
mex.add(e);
}
QueuedThreadPool qtp = getBean(QueuedThreadPool.class);
if (qtp != null)
qtp.setStopTimeout(Math.max(1000L, TimeUnit.NANOSECONDS.toMillis(end - System.nanoTime())));
}
for (Connector connector : _connectors)
{
try
{
connector.stop();
}
catch (Throwable e)
{
mex.add(e);
}
}
try
{
super.doStop();
}
catch (Throwable e)
{
mex.add(e);
}
if (getStopAtShutdown())
ShutdownThread.deregister(this);
ShutdownMonitor.deregister(this);
mex.ifExceptionThrow();
}
public void handle(HttpChannel channel) throws IOException, ServletException
{
final String target = channel.getRequest().getPathInfo();
final Request request = channel.getRequest();
final Response response = channel.getResponse();
if (LOG.isDebugEnabled())
LOG.debug("{} {} {} ?{} on {}", request.getDispatcherType(), request.getMethod(), target, request.getQueryString(), channel);
if (HttpMethod.OPTIONS.is(request.getMethod()) || "*".equals(target))
{
if (!HttpMethod.OPTIONS.is(request.getMethod()))
{
request.setHandled(true);
response.sendError(HttpStatus.BAD_REQUEST_400);
}
else
{
handleOptions(request, response);
if (!request.isHandled())
handle(target, request, request, response);
}
}
else
handle(target, request, request, response);
if (LOG.isDebugEnabled())
LOG.debug("handled={} async={} committed={} on {}", request.isHandled(), request.isAsyncStarted(), response.isCommitted(), channel);
}
protected void handleOptions(Request request, Response response) throws IOException
{
}
public void handleAsync(HttpChannel channel) throws IOException, ServletException
{
final HttpChannelState state = channel.getRequest().getHttpChannelState();
final AsyncContextEvent event = state.getAsyncContextEvent();
final Request baseRequest = channel.getRequest();
HttpURI baseUri = event.getBaseURI();
String encodedPathQuery = event.getDispatchPath();
if (encodedPathQuery == null && baseUri == null)
{
handleAsync(channel, event, baseRequest);
return;
}
final HttpURI oldUri = baseRequest.getHttpURI();
final MultiMap<String> oldQueryParams = baseRequest.getQueryParameters();
try
{
if (encodedPathQuery == null)
{
baseRequest.setHttpURI(baseUri);
}
else
{
ServletContext servletContext = event.getServletContext();
if (servletContext != null)
{
String encodedContextPath = servletContext instanceof ContextHandler.Context
? ((ContextHandler.Context)servletContext).getContextHandler().getContextPathEncoded()
: URIUtil.encodePath(servletContext.getContextPath());
if (!StringUtil.isEmpty(encodedContextPath))
{
encodedPathQuery = URIUtil.canonicalPath(URIUtil.addEncodedPaths(encodedContextPath, encodedPathQuery));
if (encodedPathQuery == null)
throw new BadMessageException(500,"Bad dispatch path");
}
}
if (baseUri == null)
baseUri = oldUri;
HttpURI.Mutable builder = HttpURI.build(baseUri, encodedPathQuery);
if (StringUtil.isEmpty(builder.getParam()))
builder.param(baseUri.getParam());
if (StringUtil.isEmpty(builder.getQuery()))
builder.query(baseUri.getQuery());
baseRequest.setHttpURI(builder);
if (baseUri.getQuery() != null && baseRequest.getQueryString() != null)
baseRequest.mergeQueryParameters(oldUri.getQuery(), baseRequest.getQueryString());
}
baseRequest.setContext(null, baseRequest.getHttpURI().getDecodedPath());
handleAsync(channel, event, baseRequest);
}
finally
{
baseRequest.setHttpURI(oldUri);
baseRequest.setQueryParameters(oldQueryParams);
baseRequest.resetParameters();
}
}
private void handleAsync(HttpChannel channel, AsyncContextEvent event, Request baseRequest) throws IOException, ServletException
{
final String target = baseRequest.getPathInfo();
final HttpServletRequest request = Request.unwrap(event.getSuppliedRequest());
final HttpServletResponse response = Response.unwrap(event.getSuppliedResponse());
if (LOG.isDebugEnabled())
LOG.debug("{} {} {} on {}", request.getDispatcherType(), request.getMethod(), target, channel);
handle(target, baseRequest, request, response);
if (LOG.isDebugEnabled())
LOG.debug("handledAsync={} async={} committed={} on {}", channel.getRequest().isHandled(), request.isAsyncStarted(), response.isCommitted(), channel);
}
public void join() throws InterruptedException
{
getThreadPool().join();
}
public SessionIdManager getSessionIdManager()
{
return _sessionIdManager;
}
public void setSessionIdManager(SessionIdManager sessionIdManager)
{
updateBean(_sessionIdManager, sessionIdManager);
_sessionIdManager = sessionIdManager;
}
@Override
public void clearAttributes()
{
_attributes.clearAttributes();
}
@Override
public Object getAttribute(String name)
{
return _attributes.getAttribute(name);
}
@Override
public Enumeration<String> getAttributeNames()
{
return _attributes.getAttributeNames();
}
@Override
public Set<String> getAttributeNameSet()
{
return _attributes.getAttributeNameSet();
}
@Override
public void removeAttribute(String name)
{
_attributes.removeAttribute(name);
}
@Override
public void setAttribute(String name, Object attribute)
{
_attributes.setAttribute(name, attribute);
}
public URI getURI()
{
NetworkConnector connector = null;
for (Connector c : _connectors)
{
if (c instanceof NetworkConnector)
{
connector = (NetworkConnector)c;
break;
}
}
if (connector == null)
return null;
ContextHandler context = getChildHandlerByClass(ContextHandler.class);
try
{
String protocol = connector.getDefaultConnectionFactory().getProtocol();
String scheme = "http";
if (protocol.startsWith("SSL-") || protocol.equals("SSL"))
scheme = "https";
String host = connector.getHost();
if (context != null && context.getVirtualHosts() != null && context.getVirtualHosts().length > 0)
host = context.getVirtualHosts()[0];
if (host == null)
host = InetAddress.getLocalHost().getHostAddress();
String path = context == null ? null : context.getContextPath();
if (path == null)
path = "/";
return new URI(scheme, null, host, connector.getLocalPort(), path, null, null);
}
catch (Exception e)
{
LOG.warn("Unable to build server URI", e);
return null;
}
}
@Override
public String toString()
{
return String.format("%s[%s,sto=%d]", super.toString(), getVersion(), getStopTimeout());
}
@Override
public void dump(Appendable out, String indent) throws IOException
{
dumpObjects(out, indent, new ClassLoaderDump(this.getClass().getClassLoader()));
}
public static void main(String... args) throws Exception
{
System.err.println(getVersion());
}
private static class DateField
{
final long _seconds;
final HttpField _dateField;
public DateField(long seconds, HttpField dateField)
{
super();
_seconds = seconds;
_dateField = dateField;
}
}
}