diff options
Diffstat (limited to 'jetty-server/src/main')
49 files changed, 1403 insertions, 817 deletions
diff --git a/jetty-server/src/main/config/etc/jetty-http-forwarded.xml b/jetty-server/src/main/config/etc/jetty-http-forwarded.xml new file mode 100644 index 0000000000..0aacbb2468 --- /dev/null +++ b/jetty-server/src/main/config/etc/jetty-http-forwarded.xml @@ -0,0 +1,17 @@ +<?xml version="1.0"?> +<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure_9_3.dtd"> +<Configure id="httpConfig" class="org.eclipse.jetty.server.HttpConfiguration"> + <Call name="addCustomizer"> + <Arg> + <New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"> + <Set name="forwardedHostHeader"><Property name="jetty.httpConfig.forwardedHostHeader" default="X-Forwarded-Host"/></Set> + <Set name="forwardedServerHeader"><Property name="jetty.httpConfig.forwardedServerHeader" default="X-Forwarded-Server"/></Set> + <Set name="forwardedProtoHeader"><Property name="jetty.httpConfig.forwardedProtoHeader" default="X-Forwarded-Proto"/></Set> + <Set name="forwardedForHeader"><Property name="jetty.httpConfig.forwardedForHeader" default="X-Forwarded-For"/></Set> + <Set name="forwardedSslSessionIdHeader"><Property name="jetty.httpConfig.forwardedSslSessionIdHeader" /></Set> + <Set name="forwardedCipherSuiteHeader"><Property name="jetty.httpConfig.forwardedCipherSuiteHeader" /></Set> + </New> + </Arg> + </Call> +</Configure> + diff --git a/jetty-server/src/main/config/etc/jetty.xml b/jetty-server/src/main/config/etc/jetty.xml index 5412979cac..8e6d1a4ae5 100644 --- a/jetty-server/src/main/config/etc/jetty.xml +++ b/jetty-server/src/main/config/etc/jetty.xml @@ -89,11 +89,6 @@ <Set name="delayDispatchUntilContent"><Property name="jetty.httpConfig.delayDispatchUntilContent" deprecated="jetty.delayDispatchUntilContent" default="true"/></Set> <Set name="maxErrorDispatches"><Property name="jetty.httpConfig.maxErrorDispatches" default="10"/></Set> <Set name="blockingTimeout"><Property name="jetty.httpConfig.blockingTimeout" default="-1"/></Set> - <!-- Uncomment to enable handling of X-Forwarded- style headers - <Call name="addCustomizer"> - <Arg><New class="org.eclipse.jetty.server.ForwardedRequestCustomizer"/></Arg> - </Call> - --> </New> <!-- =========================================================== --> diff --git a/jetty-server/src/main/config/modules/continuation.mod b/jetty-server/src/main/config/modules/continuation.mod index 231c09d0f3..af03ae41ce 100644 --- a/jetty-server/src/main/config/modules/continuation.mod +++ b/jetty-server/src/main/config/modules/continuation.mod @@ -1,6 +1,7 @@ -# -# Classic Jetty Continuation Support Module -# +[description] +Enables support for Continuation style asynchronous +Servlets. Now deprecated in favour of Servlet 3.1 +API [lib] lib/jetty-continuation-${jetty.version}.jar diff --git a/jetty-server/src/main/config/modules/debug.mod b/jetty-server/src/main/config/modules/debug.mod index 0141699461..7b75ecc0e7 100644 --- a/jetty-server/src/main/config/modules/debug.mod +++ b/jetty-server/src/main/config/modules/debug.mod @@ -1,6 +1,7 @@ -# -# Debug module -# +[description] +Enables the DebugListener to generate additional +logging regarding detailed request handling events. +Renames threads to include request URI. [depend] deploy diff --git a/jetty-server/src/main/config/modules/debuglog.mod b/jetty-server/src/main/config/modules/debuglog.mod index ba8b60a727..a76f728a5b 100644 --- a/jetty-server/src/main/config/modules/debuglog.mod +++ b/jetty-server/src/main/config/modules/debuglog.mod @@ -1,6 +1,6 @@ -# -# Debug module -# +[description] +Deprecated Debug Log using the DebugHandle. +Replaced with the debug module. [depend] server diff --git a/jetty-server/src/main/config/modules/ext.mod b/jetty-server/src/main/config/modules/ext.mod index 56b10f7ea4..4171f8dfc2 100644 --- a/jetty-server/src/main/config/modules/ext.mod +++ b/jetty-server/src/main/config/modules/ext.mod @@ -1,6 +1,6 @@ -# -# Module to add all lib/ext/**.jar files to classpath -# +[description] +Adds all jar files discovered in $JETTY_HOME/lib/ext +and $JETTY_BASE/lib/ext to the servers classpath. [lib] lib/ext/**.jar diff --git a/jetty-server/src/main/config/modules/gzip.mod b/jetty-server/src/main/config/modules/gzip.mod index 1efc834648..65663a1606 100644 --- a/jetty-server/src/main/config/modules/gzip.mod +++ b/jetty-server/src/main/config/modules/gzip.mod @@ -1,7 +1,6 @@ -# -# GZIP module -# Applies GzipHandler to entire server -# +[description] +Enable GzipHandler for dynamic gzip compression +for the entire server. [depend] server diff --git a/jetty-server/src/main/config/modules/home-base-warning.mod b/jetty-server/src/main/config/modules/home-base-warning.mod index 28e5757e81..3e599f0788 100644 --- a/jetty-server/src/main/config/modules/home-base-warning.mod +++ b/jetty-server/src/main/config/modules/home-base-warning.mod @@ -1,6 +1,6 @@ -# -# Home and Base Warning -# +[description] +Generates a warning that server has been run from $JETTY_HOME +rather than from a $JETTY_BASE. [xml] etc/home-base-warning.xml diff --git a/jetty-server/src/main/config/modules/http-forwarded.mod b/jetty-server/src/main/config/modules/http-forwarded.mod new file mode 100644 index 0000000000..60f10da736 --- /dev/null +++ b/jetty-server/src/main/config/modules/http-forwarded.mod @@ -0,0 +1,20 @@ +[description] +Adds a forwarded request customizer to the HTTP Connector +to process forwarded-for style headers from a proxy. + +[depend] +http + +[xml] +etc/jetty-http-forwarded.xml + +[ini-template] +### ForwardedRequestCustomizer Configuration + +# jetty.httpConfig.forwardedHostHeader=X-Forwarded-Host +# jetty.httpConfig.forwardedServerHeader=X-Forwarded-Server +# jetty.httpConfig.forwardedProtoHeader=X-Forwarded-Proto +# jetty.httpConfig.forwardedForHeader=X-Forwarded-For +# jetty.httpConfig.forwardedSslSessionIdHeader= +# jetty.httpConfig.forwardedCipherSuiteHeader= + diff --git a/jetty-server/src/main/config/modules/http.mod b/jetty-server/src/main/config/modules/http.mod index 01e986243e..c59ee4b4d9 100644 --- a/jetty-server/src/main/config/modules/http.mod +++ b/jetty-server/src/main/config/modules/http.mod @@ -1,6 +1,7 @@ -# -# Jetty HTTP Connector -# +[description] +Enables a HTTP connector on the server. +By default HTTP/1 is support, but HTTP2C can +be added to the connector with the http2c module. [depend] server diff --git a/jetty-server/src/main/config/modules/https.mod b/jetty-server/src/main/config/modules/https.mod index 092e0d70c7..6ffbd69d0c 100644 --- a/jetty-server/src/main/config/modules/https.mod +++ b/jetty-server/src/main/config/modules/https.mod @@ -1,12 +1,12 @@ -# -# Jetty HTTPS Connector -# +[description] +Adds HTTPS protocol support to the TLS(SSL) Connector [depend] ssl [optional] http2 +http-forwarded [xml] etc/jetty-https.xml diff --git a/jetty-server/src/main/config/modules/ipaccess.mod b/jetty-server/src/main/config/modules/ipaccess.mod index 956ea0f2e3..68f04dfc57 100644 --- a/jetty-server/src/main/config/modules/ipaccess.mod +++ b/jetty-server/src/main/config/modules/ipaccess.mod @@ -1,6 +1,6 @@ -# -# IPAccess module -# +[description] +Enable the ipaccess handler to apply a white/black list +control of the remote IP of requests. [depend] server diff --git a/jetty-server/src/main/config/modules/jdbc-sessions.mod b/jetty-server/src/main/config/modules/jdbc-sessions.mod index d77ff043e2..9fe2beba15 100644 --- a/jetty-server/src/main/config/modules/jdbc-sessions.mod +++ b/jetty-server/src/main/config/modules/jdbc-sessions.mod @@ -1,6 +1,5 @@ -# -# Jetty JDBC Session module -# +[description] +Enables JDBC Session management. [depend] annotations @@ -9,7 +8,6 @@ webapp [xml] etc/jetty-jdbc-sessions.xml - [ini-template] ## JDBC Session config diff --git a/jetty-server/src/main/config/modules/jvm.mod b/jetty-server/src/main/config/modules/jvm.mod index 195521c57f..296c1b6a2b 100644 --- a/jetty-server/src/main/config/modules/jvm.mod +++ b/jetty-server/src/main/config/modules/jvm.mod @@ -1,3 +1,6 @@ +[description] +A noop module that creates an ini template useful for +setting JVM arguments (eg -Xmx ) [ini-template] ## JVM Configuration ## If JVM args are include in an ini file then --exec is needed diff --git a/jetty-server/src/main/config/modules/lowresources.mod b/jetty-server/src/main/config/modules/lowresources.mod index 2f765d9af2..257829afd8 100644 --- a/jetty-server/src/main/config/modules/lowresources.mod +++ b/jetty-server/src/main/config/modules/lowresources.mod @@ -1,6 +1,7 @@ -# -# Low Resources module -# +[description] +Enables a low resource monitor on the server +that can take actions if threads and/or connections +cross configured threshholds. [depend] server diff --git a/jetty-server/src/main/config/modules/proxy-protocol-ssl.mod b/jetty-server/src/main/config/modules/proxy-protocol-ssl.mod index 764d24b847..374763d0b5 100644 --- a/jetty-server/src/main/config/modules/proxy-protocol-ssl.mod +++ b/jetty-server/src/main/config/modules/proxy-protocol-ssl.mod @@ -1,6 +1,9 @@ -# -# PROXY Protocol Module - SSL -# +[description] +Enables the Proxy Protocol on the TLS(SSL) Connector. +http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt +This allows a Proxy operating in TCP mode to transport +details of the proxied connection to the server. +Both V1 and V2 versions of the protocol are supported. [depend] ssl diff --git a/jetty-server/src/main/config/modules/proxy-protocol.mod b/jetty-server/src/main/config/modules/proxy-protocol.mod index 9df2700f4e..48820e5c14 100644 --- a/jetty-server/src/main/config/modules/proxy-protocol.mod +++ b/jetty-server/src/main/config/modules/proxy-protocol.mod @@ -1,6 +1,10 @@ -# -# PROXY Protocol Module - HTTP -# +[description] +Enables the Proxy Protocol on the HTTP Connector. +http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt +This allows a proxy operating in TCP mode to +transport details of the proxied connection to +the server. +Both V1 and V2 versions of the protocol are supported. [depend] http diff --git a/jetty-server/src/main/config/modules/requestlog.mod b/jetty-server/src/main/config/modules/requestlog.mod index e27b246ea2..c849f65f31 100644 --- a/jetty-server/src/main/config/modules/requestlog.mod +++ b/jetty-server/src/main/config/modules/requestlog.mod @@ -1,6 +1,5 @@ -# -# Request Log module -# +[description] +Enables a NCSA style request log. [depend] server diff --git a/jetty-server/src/main/config/modules/resources.mod b/jetty-server/src/main/config/modules/resources.mod index 8647d81325..5648948640 100644 --- a/jetty-server/src/main/config/modules/resources.mod +++ b/jetty-server/src/main/config/modules/resources.mod @@ -1,6 +1,7 @@ -# -# Module to add resources directory to classpath -# +[description] +Adds the $JETTY_HOME/resources and/or $JETTY_BASE/resources +directory to the server classpath. Useful for configuration +property files (eg jetty-logging.properties) [lib] resources/ diff --git a/jetty-server/src/main/config/modules/server.mod b/jetty-server/src/main/config/modules/server.mod index 14d6b58e88..19e21c56fe 100644 --- a/jetty-server/src/main/config/modules/server.mod +++ b/jetty-server/src/main/config/modules/server.mod @@ -1,6 +1,5 @@ -# -# Base Server Module -# +[description] +Enables the core Jetty server on the classpath. [optional] jvm diff --git a/jetty-server/src/main/config/modules/ssl.mod b/jetty-server/src/main/config/modules/ssl.mod index 292780a1cb..acc8d380c9 100644 --- a/jetty-server/src/main/config/modules/ssl.mod +++ b/jetty-server/src/main/config/modules/ssl.mod @@ -1,6 +1,7 @@ -# -# SSL Keystore module -# +[description] +Enables a TLS(SSL) Connector on the server. +This may be used for HTTPS and/or HTTP2 by enabling +the associated support modules. [name] ssl diff --git a/jetty-server/src/main/config/modules/stats.mod b/jetty-server/src/main/config/modules/stats.mod index 0922469cdf..838d54a904 100644 --- a/jetty-server/src/main/config/modules/stats.mod +++ b/jetty-server/src/main/config/modules/stats.mod @@ -1,6 +1,6 @@ -# -# Stats module -# +[description] +Enable detailed statistics collection for the server, +available via JMX. [depend] server diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java index bfd8206df9..c46ed08be5 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java @@ -253,9 +253,11 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co @Override protected void doStart() throws Exception { + if(_defaultProtocol==null) + throw new IllegalStateException("No default protocol for "+this); _defaultConnectionFactory = getConnectionFactory(_defaultProtocol); if(_defaultConnectionFactory==null) - throw new IllegalStateException("No protocol factory for default protocol: "+_defaultProtocol); + throw new IllegalStateException("No protocol factory for default protocol '"+_defaultProtocol+"' in "+this); super.doStart(); @@ -298,7 +300,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co // If we have a stop timeout long stopTimeout = getStopTimeout(); CountDownLatch stopping=_stopping; - if (stopTimeout > 0 && stopping!=null) + if (stopTimeout > 0 && stopping!=null && getAcceptors()>0) stopping.await(stopTimeout,TimeUnit.MILLISECONDS); _stopping=null; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java index ab46bd5217..036b5142ad 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractNCSARequestLog.java @@ -142,7 +142,7 @@ public abstract class AbstractNCSARequestLog extends AbstractLifeCycle implement buf.append("] \""); append(buf,request.getMethod()); buf.append(' '); - append(buf,request.getHttpURI().toString()); + append(buf,request.getOriginalURI()); buf.append(' '); append(buf,request.getProtocol()); buf.append("\" "); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/DebugListener.java b/jetty-server/src/main/java/org/eclipse/jetty/server/DebugListener.java index e1f6a3e36b..955a655775 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/DebugListener.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/DebugListener.java @@ -33,7 +33,6 @@ import javax.servlet.ServletRequest; import javax.servlet.ServletRequestEvent; import javax.servlet.ServletRequestListener; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandler.Context; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java index 68798b87a7..1a04bd749a 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java @@ -31,16 +31,15 @@ import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpURI; -import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.Attributes; import org.eclipse.jetty.util.MultiMap; public class Dispatcher implements RequestDispatcher { + public final static String __ERROR_DISPATCH="org.eclipse.jetty.server.Dispatcher.ERROR"; + /** Dispatch include attribute names */ public final static String __INCLUDE_PREFIX="javax.servlet.include."; @@ -76,7 +75,15 @@ public class Dispatcher implements RequestDispatcher public void error(ServletRequest request, ServletResponse response) throws ServletException, IOException { - forward(request, response, DispatcherType.ERROR); + try + { + request.setAttribute(__ERROR_DISPATCH,Boolean.TRUE); + forward(request, response, DispatcherType.ERROR); + } + finally + { + request.setAttribute(__ERROR_DISPATCH,null); + } } @Override @@ -129,7 +136,7 @@ public class Dispatcher implements RequestDispatcher protected void forward(ServletRequest request, ServletResponse response, DispatcherType dispatch) throws ServletException, IOException { - Request baseRequest=Request.getBaseRequest(request); + Request baseRequest=Request.getBaseRequest(request); Response base_response=baseRequest.getResponse(); base_response.resetForForward(); @@ -137,21 +144,18 @@ public class Dispatcher implements RequestDispatcher request = new ServletRequestHttpWrapper(request); if (!(response instanceof HttpServletResponse)) response = new ServletResponseHttpWrapper(response); - - final boolean old_handled=baseRequest.isHandled(); - + final HttpURI old_uri=baseRequest.getHttpURI(); final String old_context_path=baseRequest.getContextPath(); final String old_servlet_path=baseRequest.getServletPath(); final String old_path_info=baseRequest.getPathInfo(); - + final MultiMap<String> old_query_params=baseRequest.getQueryParameters(); final Attributes old_attr=baseRequest.getAttributes(); final DispatcherType old_type=baseRequest.getDispatcherType(); try { - baseRequest.setHandled(false); baseRequest.setDispatcherType(dispatch); if (_named!=null) @@ -182,18 +186,18 @@ public class Dispatcher implements RequestDispatcher attr._contextPath=old_context_path; attr._servletPath=old_servlet_path; } - + HttpURI uri = new HttpURI(old_uri.getScheme(),old_uri.getHost(),old_uri.getPort(), _uri.getPath(),_uri.getParam(),_uri.getQuery(),_uri.getFragment()); - + baseRequest.setHttpURI(uri); - + baseRequest.setContextPath(_contextHandler.getContextPath()); baseRequest.setServletPath(null); baseRequest.setPathInfo(_pathInContext); if (_uri.getQuery()!=null || old_uri.getQuery()!=null) baseRequest.mergeQueryParameters(old_uri.getQuery(),_uri.getQuery(), true); - + baseRequest.setAttributes(attr); _contextHandler.handle(_pathInContext, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response); @@ -204,7 +208,6 @@ public class Dispatcher implements RequestDispatcher } finally { - baseRequest.setHandled(old_handled); baseRequest.setHttpURI(old_uri); baseRequest.setContextPath(old_context_path); baseRequest.setServletPath(old_servlet_path); @@ -215,35 +218,7 @@ public class Dispatcher implements RequestDispatcher baseRequest.setDispatcherType(old_type); } } - - /** - * <p>Pushes a secondary resource identified by this dispatcher.</p> - * - * @param request the primary request - * @deprecated Use {@link Request#getPushBuilder()} instead - */ - @Deprecated - public void push(ServletRequest request) - { - Request baseRequest = Request.getBaseRequest(request); - HttpFields fields = new HttpFields(baseRequest.getHttpFields()); - - String query=baseRequest.getQueryString(); - if (_uri.hasQuery()) - { - if (query==null) - query=_uri.getQuery(); - else - query=query+"&"+_uri.getQuery(); // TODO is this correct semantic? - } - - HttpURI uri = HttpURI.createHttpURI(request.getScheme(),request.getServerName(),request.getServerPort(),_uri.getPath(),baseRequest.getHttpURI().getParam(),query,null); - - MetaData.Request push = new MetaData.Request(HttpMethod.GET.asString(),uri,baseRequest.getHttpVersion(),fields); - - baseRequest.getHttpChannel().getHttpTransport().push(push); - } - + @Override public String toString() { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java index 813ffd06d6..16aba09eec 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java @@ -215,6 +215,7 @@ public class ForwardedRequestCustomizer implements Customizer { request.setAttribute("javax.servlet.request.ssl_session_id", ssl_session_id); request.setScheme(HttpScheme.HTTPS.asString()); + request.setSecure(true); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java index 874ae5bb5a..942f12bde6 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java @@ -18,25 +18,24 @@ package org.eclipse.jetty.server; +import static javax.servlet.RequestDispatcher.ERROR_EXCEPTION; +import static javax.servlet.RequestDispatcher.ERROR_STATUS_CODE; + import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.util.List; -import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import javax.servlet.DispatcherType; -import javax.servlet.RequestDispatcher; -import javax.servlet.UnavailableException; -import javax.servlet.http.HttpServletRequest; import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.HttpFields; import org.eclipse.jetty.http.HttpGenerator; import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.http.MetaData; @@ -44,6 +43,7 @@ import org.eclipse.jetty.io.ByteBufferPool; import org.eclipse.jetty.io.ChannelEndPoint; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.EofException; +import org.eclipse.jetty.io.RuntimeIOException; import org.eclipse.jetty.server.HttpChannelState.Action; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ErrorHandler; @@ -262,6 +262,8 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor handle(); } + AtomicReference<Action> caller = new AtomicReference<>(); + /** * @return True if the channel is ready to continue handling (ie it is not suspended) */ @@ -333,68 +335,32 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor case ERROR_DISPATCH: { - Throwable ex = _state.getAsyncContextEvent().getThrowable(); - - // Check for error dispatch loops - Integer loop_detect = (Integer)_request.getAttribute("org.eclipse.jetty.server.ERROR_DISPATCH"); - if (loop_detect==null) - loop_detect=1; + if (_response.isCommitted()) + { + LOG.warn("Error Dispatch already committed"); + _transport.abort((Throwable)_request.getAttribute(ERROR_EXCEPTION)); + } else - loop_detect=loop_detect+1; - _request.setAttribute("org.eclipse.jetty.server.ERROR_DISPATCH",loop_detect); - if (loop_detect > getHttpConfiguration().getMaxErrorDispatches()) { - LOG.warn("ERROR_DISPATCH loop detected on {} {}",_request,ex); + _response.reset(); + Integer icode = (Integer)_request.getAttribute(ERROR_STATUS_CODE); + int code = icode!=null?icode.intValue():HttpStatus.INTERNAL_SERVER_ERROR_500; + _response.setStatus(code); + _request.setAttribute(ERROR_STATUS_CODE,code); + if (icode==null) + _request.setAttribute(ERROR_STATUS_CODE,code); + _request.setHandled(false); + _response.getHttpOutput().reopen(); + try { - _response.sendError(HttpStatus.INTERNAL_SERVER_ERROR_500); + _request.setDispatcherType(DispatcherType.ERROR); + getServer().handle(this); } finally { - _state.errorComplete(); + _request.setDispatcherType(null); } - break loop; - } - - _request.setHandled(false); - _response.resetBuffer(); - _response.getHttpOutput().reopen(); - - - String reason; - if (ex == null || ex instanceof TimeoutException) - { - reason = "Async Timeout"; - } - else - { - reason = HttpStatus.Code.INTERNAL_SERVER_ERROR.getMessage(); - _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, ex); - } - - _request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, 500); - _request.setAttribute(RequestDispatcher.ERROR_MESSAGE, reason); - _request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, _request.getRequestURI()); - - _response.setStatusWithReason(HttpStatus.INTERNAL_SERVER_ERROR_500, reason); - - ErrorHandler eh = ErrorHandler.getErrorHandler(getServer(), _state.getContextHandler()); - if (eh instanceof ErrorHandler.ErrorPageMapper) - { - String error_page = ((ErrorHandler.ErrorPageMapper)eh).getErrorPage((HttpServletRequest)_state.getAsyncContextEvent().getSuppliedRequest()); - if (error_page != null) - _state.getAsyncContextEvent().setDispatchPath(error_page); - } - - - try - { - _request.setDispatcherType(DispatcherType.ERROR); - getServer().handleAsync(this); - } - finally - { - _request.setDispatcherType(null); } break; } @@ -419,24 +385,14 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor break; } - case ASYNC_ERROR: - { - _state.onError(); - break; - } - case COMPLETE: { - // TODO do onComplete here for continuations to work -// _state.onComplete(); - if (!_response.isCommitted() && !_request.isHandled()) - _response.sendError(404); + _response.sendError(HttpStatus.NOT_FOUND_404); else _response.closeOutput(); _request.setHandled(true); - // TODO do onComplete here to detect errors in final flush _state.onComplete(); onCompleted(); @@ -450,26 +406,12 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor } } } - catch (EofException|QuietServletException|BadMessageException e) - { - if (LOG.isDebugEnabled()) - LOG.debug(e); - handleException(e); - } - catch (Throwable e) - { - if ("ContinuationThrowable".equals(e.getClass().getSimpleName())) - { - LOG.ignore(e); - } + catch (Throwable failure) + { + if ("org.eclipse.jetty.continuation.ContinuationThrowable".equals(failure.getClass().getName())) + LOG.ignore(failure); else - { - if (_connector.isStarted()) - LOG.warn(String.valueOf(_request.getHttpURI()), e); - else - LOG.debug(String.valueOf(_request.getHttpURI()), e); - handleException(e); - } + handleException(failure); } action = _state.unhandle(); @@ -482,6 +424,23 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor return !suspended; } + protected void sendError(int code, String reason) + { + try + { + _response.sendError(code, reason); + } + catch (Throwable x) + { + if (LOG.isDebugEnabled()) + LOG.debug("Could not send error " + code + " " + reason, x); + } + finally + { + _state.errorComplete(); + } + } + /** * <p>Sends an error 500, performing a special logic to detect whether the request is suspended, * to avoid concurrent writes from the application.</p> @@ -489,69 +448,61 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor * spawned thread writes the response content; in such case, we attempt to commit the error directly * bypassing the {@link ErrorHandler} mechanisms and the response OutputStream.</p> * - * @param x the Throwable that caused the problem + * @param failure the Throwable that caused the problem */ - protected void handleException(Throwable x) + protected void handleException(Throwable failure) { - if (_state.isAsyncStarted()) + // Unwrap wrapping Jetty exceptions. + if (failure instanceof RuntimeIOException) + failure = failure.getCause(); + + if (failure instanceof QuietServletException || !getServer().isRunning()) { - // Handle exception via AsyncListener onError - Throwable root = _state.getAsyncContextEvent().getThrowable(); - if (root==null) - { - _state.error(x); - } + if (LOG.isDebugEnabled()) + LOG.debug(_request.getRequestURI(), failure); + } + else if (failure instanceof BadMessageException) + { + if (LOG.isDebugEnabled()) + LOG.warn(_request.getRequestURI(), failure); else - { - // TODO Can this happen? Should this just be ISE??? - // We've already processed an error before! - root.addSuppressed(x); - LOG.warn("Error while handling async error: ", root); - abort(x); - _state.errorComplete(); - } + LOG.warn("{} {}",_request.getRequestURI(), failure.getMessage()); } else { + LOG.info(_request.getRequestURI(), failure); + } + + try + { try { - // Handle error normally - _request.setHandled(true); - _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, x); - _request.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE, x.getClass()); - - if (isCommitted()) + _state.onError(failure); + } + catch (Exception e) + { + LOG.warn(e); + // Error could not be handled, probably due to error thrown from error dispatch + if (_response.isCommitted()) { - abort(x); - if (LOG.isDebugEnabled()) - LOG.debug("Could not send response error 500, already committed", x); + LOG.warn("ERROR Dispatch failed: ",failure); + _transport.abort(failure); } else { - _response.setHeader(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString()); - - if (x instanceof BadMessageException) - { - BadMessageException bme = (BadMessageException)x; - _response.sendError(bme.getCode(), bme.getReason()); - } - else if (x instanceof UnavailableException) - { - if (((UnavailableException)x).isPermanent()) - _response.sendError(HttpStatus.NOT_FOUND_404); - else - _response.sendError(HttpStatus.SERVICE_UNAVAILABLE_503); - } - else - _response.sendError(HttpStatus.INTERNAL_SERVER_ERROR_500); + // Minimal response + Integer code=(Integer)_request.getAttribute(ERROR_STATUS_CODE); + _response.reset(); + _response.setStatus(code==null?500:code.intValue()); + _response.flushBuffer(); } } - catch (Throwable e) - { - abort(e); - if (LOG.isDebugEnabled()) - LOG.debug("Could not commit response error 500", e); - } + } + catch(Exception e) + { + failure.addSuppressed(e); + LOG.warn("ERROR Dispatch failed: ",failure); + _transport.abort(failure); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java index 8a2b1cb761..2c16602ec4 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java @@ -18,16 +18,23 @@ package org.eclipse.jetty.server; +import static javax.servlet.RequestDispatcher.ERROR_EXCEPTION; +import static javax.servlet.RequestDispatcher.ERROR_MESSAGE; +import static javax.servlet.RequestDispatcher.ERROR_STATUS_CODE; + import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicReference; import javax.servlet.AsyncListener; import javax.servlet.RequestDispatcher; import javax.servlet.ServletContext; import javax.servlet.ServletResponse; +import javax.servlet.UnavailableException; +import org.eclipse.jetty.http.BadMessageException; +import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandler.Context; import org.eclipse.jetty.util.log.Log; @@ -45,12 +52,13 @@ public class HttpChannelState private final static long DEFAULT_TIMEOUT=Long.getLong("org.eclipse.jetty.server.HttpChannelState.DEFAULT_TIMEOUT",30000L); /** - * The dispatched state of the HttpChannel, used to control the overall lifecycle + * The state of the HttpChannel,used to control the overall lifecycle. */ public enum State { IDLE, // Idle request DISPATCHED, // Request dispatched to filter/servlet + THROWN, // Exception thrown while DISPATCHED ASYNC_WAIT, // Suspended and waiting ASYNC_WOKEN, // Dispatch to handle from ASYNC_WAIT ASYNC_IO, // Dispatched for async IO @@ -67,7 +75,6 @@ public class HttpChannelState DISPATCH, // handle a normal request dispatch ASYNC_DISPATCH, // handle an async request dispatch ERROR_DISPATCH, // handle a normal error - ASYNC_ERROR, // handle an async error WRITE_CALLBACK, // handle an IO write callback READ_CALLBACK, // handle an IO read callback COMPLETE, // Complete the response @@ -76,14 +83,12 @@ public class HttpChannelState } /** - * The state of the servlet async API. This can lead or follow the - * channel dispatch state and also includes reasons such as expired, - * dispatched or completed. + * The state of the servlet async API. */ public enum Async { STARTED, // AsyncContext.startAsync() has been called - DISPATCH, // + DISPATCH, // AsyncContext.dispatch() has been called COMPLETE, // AsyncContext.complete() has been called EXPIRING, // AsyncContext timeout just happened EXPIRED, // AsyncContext timeout has been processed @@ -160,12 +165,18 @@ public class HttpChannelState { try(Locker.Lock lock= _locker.lock()) { - return String.format("%s@%x{s=%s a=%s i=%b r=%s w=%b}",getClass().getSimpleName(),hashCode(),_state,_async,_initial, - _asyncReadPossible?(_asyncReadUnready?"PU":"P!U"):(_asyncReadUnready?"!PU":"!P!U"), - _asyncWrite); + return toStringLocked(); } } + public String toStringLocked() + { + return String.format("%s@%x{s=%s a=%s i=%b r=%s w=%b}",getClass().getSimpleName(),hashCode(),_state,_async,_initial, + _asyncReadPossible?(_asyncReadUnready?"PU":"P!U"):(_asyncReadUnready?"!PU":"!P!U"), + _asyncWrite); + } + + private String getStatusStringLocked() { return String.format("s=%s i=%b a=%s",_state,_initial,_async); @@ -184,10 +195,11 @@ public class HttpChannelState */ protected Action handling() { - if(DEBUG) - LOG.debug("{} handling {}",this,_state); try(Locker.Lock lock= _locker.lock()) { + if(DEBUG) + LOG.debug("handling {}",toStringLocked()); + switch(_state) { case IDLE: @@ -228,17 +240,15 @@ public class HttpChannelState _state=State.DISPATCHED; _async=null; return Action.ASYNC_DISPATCH; - case EXPIRING: - break; case EXPIRED: + case ERRORED: _state=State.DISPATCHED; _async=null; return Action.ERROR_DISPATCH; case STARTED: - return Action.WAIT; + case EXPIRING: case ERRORING: - _state=State.DISPATCHED; - return Action.ASYNC_ERROR; + return Action.WAIT; default: throw new IllegalStateException(getStatusStringLocked()); @@ -264,6 +274,9 @@ public class HttpChannelState try(Locker.Lock lock= _locker.lock()) { + if(DEBUG) + LOG.debug("startAsync {}",toStringLocked()); + if (_state!=State.DISPATCHED || _async!=null) throw new IllegalStateException(this.getStatusStringLocked()); @@ -304,19 +317,10 @@ public class HttpChannelState } } - protected void error(Throwable th) - { - try(Locker.Lock lock= _locker.lock()) - { - if (_event!=null) - _event.addThrowable(th); - _async=Async.ERRORING; - } - } /** * Signal that the HttpConnection has finished handling the request. - * For blocking connectors, this call may block if the request has + * For blocking connectors,this call may block if the request has * been suspended (startAsync called). * @return next actions * be handled again (eg because of a resume that happened before unhandle was called) @@ -327,17 +331,21 @@ public class HttpChannelState AsyncContextEvent schedule_event=null; boolean read_interested=false; - if(DEBUG) - LOG.debug("{} unhandle {}",this,_state); - try(Locker.Lock lock= _locker.lock()) { + if(DEBUG) + LOG.debug("unhandle {}",toStringLocked()); + switch(_state) { case COMPLETING: case COMPLETED: return Action.TERMINATED; + case THROWN: + _state=State.DISPATCHED; + return Action.ERROR_DISPATCH; + case DISPATCHED: case ASYNC_IO: break; @@ -363,12 +371,6 @@ public class HttpChannelState action=Action.ASYNC_DISPATCH; break; - case EXPIRED: - _state=State.DISPATCHED; - _async=null; - action = Action.ERROR_DISPATCH; - break; - case STARTED: if (_asyncReadUnready && _asyncReadPossible) { @@ -392,26 +394,27 @@ public class HttpChannelState break; case EXPIRING: - schedule_event=_event; + // onTimeout callbacks still being called, so just WAIT _state=State.ASYNC_WAIT; action=Action.WAIT; break; - case ERRORING: + case EXPIRED: + // onTimeout handling is complete, but did not dispatch as + // we were handling. So do the error dispatch here _state=State.DISPATCHED; - action=Action.ASYNC_ERROR; + _async=null; + action=Action.ERROR_DISPATCH; break; - + case ERRORED: _state=State.DISPATCHED; - action=Action.ERROR_DISPATCH; _async=null; + action=Action.ERROR_DISPATCH; break; default: - _state=State.COMPLETING; - action=Action.COMPLETE; - break; + throw new IllegalStateException(this.getStatusStringLocked()); } } else @@ -431,9 +434,12 @@ public class HttpChannelState public void dispatch(ServletContext context, String path) { boolean dispatch=false; - AsyncContextEvent event=null; + AsyncContextEvent event; try(Locker.Lock lock= _locker.lock()) { + if(DEBUG) + LOG.debug("dispatch {} -> {}",toStringLocked(),path); + boolean started=false; event=_event; switch(_async) @@ -442,6 +448,7 @@ public class HttpChannelState started=true; break; case EXPIRING: + case ERRORING: case ERRORED: break; default: @@ -484,6 +491,9 @@ public class HttpChannelState AsyncContextEvent event; try(Locker.Lock lock= _locker.lock()) { + if(DEBUG) + LOG.debug("onTimeout {}",toStringLocked()); + if (_async!=Async.STARTED) return; _async=Async.EXPIRING; @@ -492,12 +502,10 @@ public class HttpChannelState } - if (LOG.isDebugEnabled()) - LOG.debug("Async timeout {}",this); - + final AtomicReference<Throwable> error=new AtomicReference<Throwable>(); if (listeners!=null) { - Runnable callback=new Runnable() + Runnable task=new Runnable() { @Override public void run() @@ -508,12 +516,13 @@ public class HttpChannelState { listener.onTimeout(event); } - catch(Exception e) + catch(Throwable x) { - LOG.debug(e); - event.addThrowable(e); - _channel.getRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable()); - break; + LOG.debug("Exception while invoking listener " + listener,x); + if (error.get()==null) + error.set(x); + else + error.get().addSuppressed(x); } } } @@ -524,30 +533,28 @@ public class HttpChannelState } }; - runInContext(event,callback); + runInContext(event,task); } + Throwable th=error.get(); boolean dispatch=false; try(Locker.Lock lock= _locker.lock()) { switch(_async) { case EXPIRING: - if (event.getThrowable()==null) - { - _async=Async.EXPIRED; - _event.addThrowable(new TimeoutException("Async API violation")); - } - else - { - _async=Async.ERRORING; - } + _async=th==null ? Async.EXPIRED : Async.ERRORING; break; - + case COMPLETE: case DISPATCH: + if (th!=null) + { + LOG.ignore(th); + th=null; + } break; - + default: throw new IllegalStateException(); } @@ -559,6 +566,13 @@ public class HttpChannelState } } + if (th!=null) + { + if (LOG.isDebugEnabled()) + LOG.debug("Error after async timeout {}",this,th); + onError(th); + } + if (dispatch) { if (LOG.isDebugEnabled()) @@ -569,11 +583,15 @@ public class HttpChannelState public void complete() { + // just like resume, except don't set _dispatched=true; boolean handle=false; - AsyncContextEvent event=null; + AsyncContextEvent event; try(Locker.Lock lock= _locker.lock()) { + if(DEBUG) + LOG.debug("complete {}",toStringLocked()); + boolean started=false; event=_event; @@ -583,8 +601,11 @@ public class HttpChannelState started=true; break; case EXPIRING: + case ERRORING: case ERRORED: break; + case COMPLETE: + return; default: throw new IllegalStateException(this.getStatusStringLocked()); } @@ -606,6 +627,9 @@ public class HttpChannelState { try(Locker.Lock lock= _locker.lock()) { + if(DEBUG) + LOG.debug("error complete {}",toStringLocked()); + _async=Async.COMPLETE; _event.setDispatchContext(null); _event.setDispatchPath(null); @@ -613,40 +637,142 @@ public class HttpChannelState cancelTimeout(); } - - protected void onError() + + protected void onError(Throwable failure) { - final List<AsyncListener> aListeners; + final List<AsyncListener> listeners; final AsyncContextEvent event; - + final Request baseRequest = _channel.getRequest(); + + int code=HttpStatus.INTERNAL_SERVER_ERROR_500; + String reason=null; + if (failure instanceof BadMessageException) + { + BadMessageException bme = (BadMessageException)failure; + code = bme.getCode(); + reason = bme.getReason(); + } + else if (failure instanceof UnavailableException) + { + if (((UnavailableException)failure).isPermanent()) + code = HttpStatus.NOT_FOUND_404; + else + code = HttpStatus.SERVICE_UNAVAILABLE_503; + } + try(Locker.Lock lock= _locker.lock()) { - if (_state!=State.DISPATCHED/* || _async!=Async.ERRORING*/) + if(DEBUG) + LOG.debug("onError {} {}",toStringLocked(),failure); + + // Set error on request. + if(_event!=null) + { + if (_event.getThrowable()!=null) + throw new IllegalStateException("Error already set",_event.getThrowable()); + _event.addThrowable(failure); + _event.getSuppliedRequest().setAttribute(ERROR_STATUS_CODE,code); + _event.getSuppliedRequest().setAttribute(ERROR_EXCEPTION,failure); + _event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,failure==null?null:failure.getClass()); + + _event.getSuppliedRequest().setAttribute(ERROR_MESSAGE,reason!=null?reason:null); + } + else + { + Throwable error = (Throwable)baseRequest.getAttribute(ERROR_EXCEPTION); + if (error!=null) + throw new IllegalStateException("Error already set",error); + baseRequest.setAttribute(ERROR_STATUS_CODE,code); + baseRequest.setAttribute(ERROR_EXCEPTION,failure); + baseRequest.setAttribute(RequestDispatcher.ERROR_EXCEPTION_TYPE,failure==null?null:failure.getClass()); + baseRequest.setAttribute(ERROR_MESSAGE,reason!=null?reason:null); + } + + // Are we blocking? + if (_async==null) + { + // Only called from within HttpChannel Handling, so much be dispatched, let's stay dispatched! + if (_state==State.DISPATCHED) + { + _state=State.THROWN; + return; + } throw new IllegalStateException(this.getStatusStringLocked()); - - aListeners=_asyncListeners; + } + + // We are Async + _async=Async.ERRORING; + listeners=_asyncListeners; event=_event; - _async=Async.ERRORED; } - if (event!=null && aListeners!=null) + if(listeners!=null) { - event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_EXCEPTION,event.getThrowable()); - event.getSuppliedRequest().setAttribute(RequestDispatcher.ERROR_MESSAGE,event.getThrowable().getMessage()); - for (AsyncListener listener : aListeners) + Runnable task=new Runnable() { - try + @Override + public void run() { - listener.onError(event); + for (AsyncListener listener : listeners) + { + try + { + listener.onError(event); + } + catch (Throwable x) + { + LOG.info("Exception while invoking listener " + listener,x); + } + } } - catch(Exception x) + + @Override + public String toString() { - LOG.info("Exception while invoking listener " + listener, x); + return "onError"; + } + }; + runInContext(event,task); + } + + boolean dispatch=false; + try(Locker.Lock lock= _locker.lock()) + { + switch(_async) + { + case ERRORING: + { + // Still in this state ? The listeners did not invoke API methods + // and the container must provide a default error dispatch. + _async=Async.ERRORED; + break; + } + case DISPATCH: + case COMPLETE: + { + // The listeners called dispatch() or complete(). + break; + } + default: + { + throw new IllegalStateException(toString()); } } + + if(_state==State.ASYNC_WAIT) + { + _state=State.ASYNC_WOKEN; + dispatch=true; + } } - } + if(dispatch) + { + if(LOG.isDebugEnabled()) + LOG.debug("Dispatch after error {}",this); + scheduleDispatch(); + } + } protected void onComplete() { @@ -655,6 +781,9 @@ public class HttpChannelState try(Locker.Lock lock= _locker.lock()) { + if(DEBUG) + LOG.debug("onComplete {}",toStringLocked()); + switch(_state) { case COMPLETING: @@ -686,7 +815,7 @@ public class HttpChannelState } catch(Exception e) { - LOG.warn(e); + LOG.warn("Exception while invoking listener " + listener,e); } } } @@ -708,6 +837,9 @@ public class HttpChannelState cancelTimeout(); try(Locker.Lock lock= _locker.lock()) { + if(DEBUG) + LOG.debug("recycle {}",toStringLocked()); + switch(_state) { case DISPATCHED: @@ -734,6 +866,9 @@ public class HttpChannelState cancelTimeout(); try(Locker.Lock lock= _locker.lock()) { + if(DEBUG) + LOG.debug("upgrade {}",toStringLocked()); + switch(_state) { case IDLE: @@ -932,6 +1067,9 @@ public class HttpChannelState boolean interested=false; try(Locker.Lock lock= _locker.lock()) { + if(DEBUG) + LOG.debug("onReadUnready {}",toStringLocked()); + // We were already unready, this is not a state change, so do nothing if (!_asyncReadUnready) { @@ -958,6 +1096,9 @@ public class HttpChannelState boolean woken=false; try(Locker.Lock lock= _locker.lock()) { + if(DEBUG) + LOG.debug("onReadPossible {}",toStringLocked()); + _asyncReadPossible=true; if (_state==State.ASYNC_WAIT && _asyncReadUnready) { @@ -980,6 +1121,9 @@ public class HttpChannelState boolean woken=false; try(Locker.Lock lock= _locker.lock()) { + if(DEBUG) + LOG.debug("onReadReady {}",toStringLocked()); + _asyncReadUnready=true; _asyncReadPossible=true; if (_state==State.ASYNC_WAIT) @@ -1005,6 +1149,9 @@ public class HttpChannelState try(Locker.Lock lock= _locker.lock()) { + if(DEBUG) + LOG.debug("onWritePossible {}",toStringLocked()); + _asyncWrite=true; if (_state==State.ASYNC_WAIT) { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java index ea69d416f3..926a7a6b83 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java @@ -82,7 +82,7 @@ public class HttpOutput extends ServletOutputStream implements Runnable setWriteListener() READY->owp ise ise ise ise ise write() OPEN ise PENDING wpe wpe eof flush() OPEN ise PENDING wpe wpe eof - close() CLOSED CLOSED CLOSED CLOSED wpe CLOSED + close() CLOSED CLOSED CLOSED CLOSED CLOSED CLOSED isReady() OPEN:true READY:true READY:true UNREADY:false UNREADY:false CLOSED:true write completed - - - ASYNC READY->owp - */ @@ -195,11 +195,17 @@ public class HttpOutput extends ServletOutputStream implements Runnable { return; } + + case ASYNC: case UNREADY: + case PENDING: { - if (_state.compareAndSet(state,OutputState.ERROR)) - _writeListener.onError(_onError==null?new EofException("Async close"):_onError); - break; + if (!_state.compareAndSet(state,OutputState.CLOSED)) + break; + IOException ex = new IOException("Closed while Pending/Unready"); + LOG.warn(ex.toString()); + LOG.debug(ex); + _channel.abort(ex); } default: { @@ -286,6 +292,20 @@ public class HttpOutput extends ServletOutputStream implements Runnable return _state.get()==OutputState.CLOSED; } + public boolean isAsync() + { + switch(_state.get()) + { + case ASYNC: + case READY: + case PENDING: + case UNREADY: + return true; + default: + return false; + } + } + @Override public void flush() throws IOException { @@ -307,6 +327,8 @@ public class HttpOutput extends ServletOutputStream implements Runnable return; case PENDING: + return; + case UNREADY: throw new WritePendingException(); @@ -1255,4 +1277,5 @@ public class HttpOutput extends ServletOutputStream implements Runnable super.onCompleteFailure(x); } } + } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java index 7865c6b7f2..b1c1618e63 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java @@ -197,27 +197,16 @@ public class LocalConnector extends AbstractConnector } @Override - public void close() - { - boolean wasOpen=isOpen(); - super.close(); - if (wasOpen) - { - getConnection().onClose(); - onClose(); - } - } - - @Override public void onClose() { + getConnection().onClose(); LocalConnector.this.onEndPointClosed(this); super.onClose(); _closed.countDown(); } @Override - public void shutdownOutput() + public void doShutdownOutput() { super.shutdownOutput(); close(); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java index cfd96c1808..e4ed0a2c59 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/NetworkTrafficServerConnector.java @@ -26,10 +26,10 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.Executor; import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.ChannelEndPoint; import org.eclipse.jetty.io.ManagedSelector; import org.eclipse.jetty.io.NetworkTrafficListener; import org.eclipse.jetty.io.NetworkTrafficSelectChannelEndPoint; -import org.eclipse.jetty.io.SelectChannelEndPoint; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.Scheduler; @@ -84,7 +84,7 @@ public class NetworkTrafficServerConnector extends ServerConnector } @Override - protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException + protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException { NetworkTrafficSelectChannelEndPoint endPoint = new NetworkTrafficSelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout(), listeners); return endPoint; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java index b72cf5718d..7a5e51cca1 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java @@ -19,17 +19,23 @@ package org.eclipse.jetty.server; import java.io.IOException; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ReadPendingException; import java.nio.channels.WritePendingException; +import java.nio.charset.StandardCharsets; import java.util.Iterator; import org.eclipse.jetty.io.AbstractConnection; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.util.AttributesMap; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -38,14 +44,17 @@ import org.eclipse.jetty.util.log.Logger; /** * ConnectionFactory for the PROXY Protocol. * <p>This factory can be placed in front of any other connection factory - * to process the proxy line before the normal protocol handling</p> + * to process the proxy v1 or v2 line before the normal protocol handling</p> * * @see <a href="http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt">http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt</a> */ public class ProxyConnectionFactory extends AbstractConnectionFactory { + public static final String TLS_VERSION = "TLS_VERSION"; + private static final Logger LOG = Log.getLogger(ProxyConnectionFactory.class); private final String _next; + private int _maxProxyHeader=1024; /* ------------------------------------------------------------ */ /** Proxy Connection Factory that uses the next ConnectionFactory @@ -63,6 +72,16 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory _next=nextProtocol; } + public int getMaxProxyHeader() + { + return _maxProxyHeader; + } + + public void setMaxProxyHeader(int maxProxyHeader) + { + _maxProxyHeader = maxProxyHeader; + } + @Override public Connection newConnection(Connector connector, EndPoint endp) { @@ -80,10 +99,79 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory } } - return new ProxyConnection(endp,connector,next); + return new ProxyProtocolV1orV2Connection(endp,connector,next); + } + + public class ProxyProtocolV1orV2Connection extends AbstractConnection + { + private final Connector _connector; + private final String _next; + private ByteBuffer _buffer = BufferUtil.allocate(16); + + protected ProxyProtocolV1orV2Connection(EndPoint endp, Connector connector, String next) + { + super(endp,connector.getExecutor()); + _connector=connector; + _next=next; + } + + @Override + public void onOpen() + { + super.onOpen(); + fillInterested(); + } + + @Override + public void onFillable() + { + try + { + while(BufferUtil.space(_buffer)>0) + { + // Read data + int fill=getEndPoint().fill(_buffer); + if (fill<0) + { + getEndPoint().shutdownOutput(); + return; + } + if (fill==0) + { + fillInterested(); + return; + } + } + + // Is it a V1? + switch(_buffer.get(0)) + { + case 'P': + { + ProxyProtocolV1Connection v1 = new ProxyProtocolV1Connection(getEndPoint(),_connector,_next,_buffer); + getEndPoint().upgrade(v1); + return; + } + case 0x0D: + { + ProxyProtocolV2Connection v2 = new ProxyProtocolV2Connection(getEndPoint(),_connector,_next,_buffer); + getEndPoint().upgrade(v2); + return; + } + default: + LOG.warn("Not PROXY protocol for {}",getEndPoint()); + close(); + } + } + catch (Throwable x) + { + LOG.warn("PROXY error for "+getEndPoint(),x); + close(); + } + } } - public static class ProxyConnection extends AbstractConnection + public static class ProxyProtocolV1Connection extends AbstractConnection { // 0 1 2 3 4 5 6 // 98765432109876543210987654321 @@ -97,11 +185,13 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory private int _fields; private int _length; - protected ProxyConnection(EndPoint endp, Connector connector, String next) + protected ProxyProtocolV1Connection(EndPoint endp, Connector connector, String next,ByteBuffer buffer) { super(endp,connector.getExecutor()); _connector=connector; _next=next; + _length=buffer.remaining(); + parse(buffer); } @Override @@ -110,16 +200,60 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory super.onOpen(); fillInterested(); } + + + private boolean parse(ByteBuffer buffer) + { + // parse fields + while (buffer.hasRemaining()) + { + byte b = buffer.get(); + if (_fields<6) + { + if (b==' ' || b=='\r' && _fields==5) + { + _field[_fields++]=_builder.toString(); + _builder.setLength(0); + } + else if (b<' ') + { + LOG.warn("Bad character {} for {}",b&0xFF,getEndPoint()); + close(); + return false; + } + else + { + _builder.append((char)b); + } + } + else + { + if (b=='\n') + { + _fields=7; + return true; + } + LOG.warn("Bad CRLF for {}",getEndPoint()); + close(); + return false; + } + } + + return true; + } + @Override public void onFillable() { try { ByteBuffer buffer=null; - loop: while(true) + while(_fields<7) { // Create a buffer that will not read too much data + // since once read it is impossible to push back for the + // real connection to read it. int size=Math.max(1,__size[_fields]-_builder.length()); if (buffer==null || buffer.capacity()!=size) buffer=BufferUtil.allocate(size); @@ -147,38 +281,8 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory return; } - // parse fields - while (buffer.hasRemaining()) - { - byte b = buffer.get(); - if (_fields<6) - { - if (b==' ' || b=='\r' && _fields==5) - { - _field[_fields++]=_builder.toString(); - _builder.setLength(0); - } - else if (b<' ') - { - LOG.warn("Bad character {} for {}",b&0xFF,getEndPoint()); - close(); - return; - } - else - { - _builder.append((char)b); - } - } - else - { - if (b=='\n') - break loop; - - LOG.warn("Bad CRLF for {}",getEndPoint()); - close(); - return; - } - } + if (!parse(buffer)) + return; } // Check proxy @@ -197,10 +301,13 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory ConnectionFactory connectionFactory = _connector.getConnectionFactory(_next); if (connectionFactory == null) { - LOG.info("Next protocol '{}' for {}",_next,getEndPoint()); + LOG.warn("No Next protocol '{}' for {}",_next,getEndPoint()); close(); return; } + + if (LOG.isDebugEnabled()) + LOG.warn("Next protocol '{}' for {} r={} l={}",_next,getEndPoint(),remote,local); EndPoint endPoint = new ProxyEndPoint(getEndPoint(),remote,local); Connection newConnection = connectionFactory.newConnection(_connector, endPoint); @@ -213,8 +320,260 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory } } } + + + enum Family { UNSPEC, INET, INET6, UNIX }; + enum Transport { UNSPEC, STREAM, DGRAM }; + private static final byte[] MAGIC = new byte[]{0x0D,0x0A,0x0D,0x0A,0x00,0x0D,0x0A,0x51,0x55,0x49,0x54,0x0A}; + + public class ProxyProtocolV2Connection extends AbstractConnection + { + private final Connector _connector; + private final String _next; + private final boolean _local; + private final Family _family; + private final Transport _transport; + private final int _length; + private final ByteBuffer _buffer; + + protected ProxyProtocolV2Connection(EndPoint endp, Connector connector, String next,ByteBuffer buffer) + throws IOException + { + super(endp,connector.getExecutor()); + _connector=connector; + _next=next; + + if (buffer.remaining()!=16) + throw new IllegalStateException(); + + if (LOG.isDebugEnabled()) + LOG.debug("PROXYv2 header {} for {}",BufferUtil.toHexSummary(buffer),this); + + // struct proxy_hdr_v2 { + // uint8_t sig[12]; /* hex 0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A */ + // uint8_t ver_cmd; /* protocol version and command */ + // uint8_t fam; /* protocol family and address */ + // uint16_t len; /* number of following bytes part of the header */ + // }; + for (int i=0;i<MAGIC.length;i++) + if (buffer.get()!=MAGIC[i]) + throw new IOException("Bad PROXY protocol v2 signature"); + + int versionAndCommand = 0xff & buffer.get(); + if ((versionAndCommand&0xf0) != 0x20) + throw new IOException("Bad PROXY protocol v2 version"); + _local=(versionAndCommand&0xf)==0x00; + + int transportAndFamily = 0xff & buffer.get(); + switch(transportAndFamily>>4) + { + case 0: _family=Family.UNSPEC; break; + case 1: _family=Family.INET; break; + case 2: _family=Family.INET6; break; + case 3: _family=Family.UNIX; break; + default: + throw new IOException("Bad PROXY protocol v2 family"); + } + + switch(0xf&transportAndFamily) + { + case 0: _transport=Transport.UNSPEC; break; + case 1: _transport=Transport.STREAM; break; + case 2: _transport=Transport.DGRAM; break; + default: + throw new IOException("Bad PROXY protocol v2 family"); + } + + _length = buffer.getChar(); + + if (!_local && (_family==Family.UNSPEC || _family==Family.UNIX || _transport!=Transport.STREAM)) + throw new IOException(String.format("Unsupported PROXY protocol v2 mode 0x%x,0x%x",versionAndCommand,transportAndFamily)); + + if (_length>_maxProxyHeader) + throw new IOException(String.format("Unsupported PROXY protocol v2 mode 0x%x,0x%x,0x%x",versionAndCommand,transportAndFamily,_length)); + + _buffer = _length>0?BufferUtil.allocate(_length):BufferUtil.EMPTY_BUFFER; + } + + @Override + public void onOpen() + { + super.onOpen(); + if (_buffer.remaining()==_length) + next(); + else + fillInterested(); + } + + @Override + public void onFillable() + { + try + { + while(_buffer.remaining()<_length) + { + // Read data + int fill=getEndPoint().fill(_buffer); + if (fill<0) + { + getEndPoint().shutdownOutput(); + return; + } + if (fill==0) + { + fillInterested(); + return; + } + } + } + catch (Throwable x) + { + LOG.warn("PROXY error for "+getEndPoint(),x); + close(); + return; + } + + next(); + } + + private void next() + { + if (LOG.isDebugEnabled()) + LOG.debug("PROXYv2 next {} from {} for {}",_next,BufferUtil.toHexSummary(_buffer),this); + + // Create the next protocol + ConnectionFactory connectionFactory = _connector.getConnectionFactory(_next); + if (connectionFactory == null) + { + LOG.info("Next protocol '{}' for {}",_next,getEndPoint()); + close(); + return; + } + + // Do we need to wrap the endpoint? + EndPoint endPoint=getEndPoint(); + if (!_local) + { + try + { + InetAddress src; + InetAddress dst; + int sp; + int dp; + + switch(_family) + { + case INET: + { + byte[] addr=new byte[4]; + _buffer.get(addr); + src = Inet4Address.getByAddress(addr); + _buffer.get(addr); + dst = Inet4Address.getByAddress(addr); + sp = _buffer.getChar(); + dp = _buffer.getChar(); + + break; + } + + case INET6: + { + byte[] addr=new byte[16]; + _buffer.get(addr); + src = Inet6Address.getByAddress(addr); + _buffer.get(addr); + dst = Inet6Address.getByAddress(addr); + sp = _buffer.getChar(); + dp = _buffer.getChar(); + break; + } + + default: + throw new IllegalStateException(); + } + + + // Extract Addresses + InetSocketAddress remote=new InetSocketAddress(src,sp); + InetSocketAddress local =new InetSocketAddress(dst,dp); + ProxyEndPoint proxyEndPoint = new ProxyEndPoint(endPoint,remote,local); + endPoint = proxyEndPoint; + + + // Any additional info? + while(_buffer.hasRemaining()) + { + int type = 0xff & _buffer.get(); + int length = _buffer.getShort(); + byte[] value = new byte[length]; + _buffer.get(value); + + if (LOG.isDebugEnabled()) + LOG.debug(String.format("T=%x L=%d V=%s for %s",type,length,TypeUtil.toHexString(value),this)); + + // TODO interpret these values + switch(type) + { + case 0x01: // PP2_TYPE_ALPN + break; + case 0x02: // PP2_TYPE_AUTHORITY + break; + case 0x20: // PP2_TYPE_SSL + { + int i=0; + int client = 0xff & value[i++]; + int verify = (0xff & value[i++])<<24 + (0xff & value[i++])<<16 + (0xff & value[i++])<<8 + (0xff&value[i++]); + while(i<value.length) + { + int ssl_type = 0xff & value[i++]; + int ssl_length = (0xff & value[i++])*0x100 + (0xff&value[i++]); + byte[] ssl_val = new byte[ssl_length]; + System.arraycopy(value,i,ssl_val,0,ssl_length); + i+=ssl_length; + + switch(ssl_type) + { + case 0x21: // PP2_TYPE_SSL_VERSION + String version=new String(ssl_val,0,ssl_length,StandardCharsets.ISO_8859_1); + if (client==1) + proxyEndPoint.setAttribute(TLS_VERSION,version); + break; + + default: + break; + } + } + break; + } + case 0x21: // PP2_TYPE_SSL_VERSION + break; + case 0x22: // PP2_TYPE_SSL_CN + break; + case 0x30: // PP2_TYPE_NETNS + break; + default: + break; + } + } + + if (LOG.isDebugEnabled()) + LOG.debug("{} {}",getEndPoint(),proxyEndPoint.toString()); + + + } + catch(Exception e) + { + LOG.warn(e); + } + } + + Connection newConnection = connectionFactory.newConnection(_connector, endPoint); + endPoint.upgrade(newConnection); + } + } + - public static class ProxyEndPoint implements EndPoint + public static class ProxyEndPoint extends AttributesMap implements EndPoint { private final EndPoint _endp; private final InetSocketAddress _remote; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilder.java b/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilder.java index 5e807b45dc..a16c9960e6 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilder.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilder.java @@ -25,63 +25,99 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; + /** Build a request to be pushed. - * <p> - * A PushBuilder is obtained by calling {@link Request#getPushBuilder()} - * which creates an initializes the builder as follows: + * + * <p>A PushBuilder is obtained by calling {@link + * Request#getPushBuilder()} (<code>Eventually HttpServletRequest.getPushBuilder()</code>). + * Each call to this method will + * return a new instance of a PushBuilder based off the current {@code + * HttpServletRequest}. Any mutations to the returned PushBuilder are + * not reflected on future returns.</p> + * + * <p>The instance is initialized as follows:</p> + * * <ul> - * <li> Each call to getPushBuilder() will return a new instance of a - * PushBuilder based off the Request. Any mutations to the - * returned PushBuilder are not reflected on future returns.</li> + * * <li>The method is initialized to "GET"</li> - * <li>The requests headers are added to the Builder, except for:<ul> + * + * <li>The existing headers of the current {@link HttpServletRequest} + * are added to the builder, except for: + * + * <ul> * <li>Conditional headers (eg. If-Modified-Since) * <li>Range headers * <li>Expect headers * <li>Authorization headers * <li>Referrer headers - * </ul></li> - * <li>If the request was Authenticated, an Authorization header will + * </ul> + * + * </li> + * + * <li>If the request was authenticated, an Authorization header will * be set with a container generated token that will result in equivalent - * Authorization for the pushed request</li> - * <li>The query string from {@link HttpServletRequest#getQueryString()} - * <li>The {@link HttpServletRequest#getRequestedSessionId()} value, unless at the time - * of the call {@link HttpServletRequest#getSession(boolean)} - * has previously been called to create a new {@link HttpSession}, in - * which case the new session ID will be used as the PushBuilders - * requested session ID. The source of the requested session id will be the - * same as for the request</li> - * <li>The Referer header will be set to {@link HttpServletRequest#getRequestURL()} - * plus any {@link HttpServletRequest#getQueryString()} </li> + * Authorization for the pushed request.</li> + * + * <li>The {@link HttpServletRequest#getRequestedSessionId()} value, + * unless at the time of the call {@link + * HttpServletRequest#getSession(boolean)} has previously been called to + * create a new {@link HttpSession}, in which case the new session ID + * will be used as the PushBuilder's requested session ID. The source of + * the requested session id will be the same as for the request</li> + * + * <li>The Referer(sic) header will be set to {@link + * HttpServletRequest#getRequestURL()} plus any {@link + * HttpServletRequest#getQueryString()} </li> + * * <li>If {@link HttpServletResponse#addCookie(Cookie)} has been called * on the associated response, then a corresponding Cookie header will be added * to the PushBuilder, unless the {@link Cookie#getMaxAge()} is <=0, in which * case the Cookie will be removed from the builder.</li> - * <li>If this request has has the conditional headers If-Modified-Since or - * If-None-Match then the {@link #isConditional()} header is set to true.</li> - * </ul> - * <p>A PushBuilder can be customized by chained calls to mutator methods before the - * {@link #push()} method is called to initiate a push request with the current state - * of the builder. After the call to {@link #push()}, the builder may be reused for - * another push, however the {@link #path(String)}, {@link #etag(String)} and - * {@link #lastModified(String)} values will have been nulled. All other - * values are retained over calls to {@link #push()}. + * + * <li>If this request has has the conditional headers If-Modified-Since + * or If-None-Match, then the {@link #isConditional()} header is set to + * true.</li> + * + * </ul> + * + * <p>The {@link #path} method must be called on the {@code PushBuilder} + * instance before the call to {@link #push}. Failure to do so must + * cause an exception to be thrown from {@link + * #push}, as specified in that method.</p> + * + * <p>A PushBuilder can be customized by chained calls to mutator + * methods before the {@link #push()} method is called to initiate an + * asynchronous push request with the current state of the builder. + * After the call to {@link #push()}, the builder may be reused for + * another push, however the implementation must make it so the {@link + * #path(String)}, {@link #etag(String)} and {@link + * #lastModified(String)} values are cleared before returning from + * {@link #push}. All other values are retained over calls to {@link + * #push()}. + * + * @since 4.0 */ public interface PushBuilder { - /** Set the method to be used for the push. - * Defaults to GET. + /** + * <p>Set the method to be used for the push.</p> + * + * <p>Any non-empty String may be used for the method.</p> + * * @param method the method to be used for the push. * @return this builder. + * @throws NullPointerException if the argument is {@code null} + * @throws IllegalArgumentException if the argument is the empty String */ public abstract PushBuilder method(String method); /** Set the query string to be used for the push. - * Defaults to the requests query string. - * Will be appended to any query String included in a call to {@link #path(String)}. This - * method should be used instead of a query in {@link #path(String)} when multiple - * {@link #push()} calls are to be made with the same query string, or to remove a - * query string obtained from the associated request. + * + * Will be appended to any query String included in a call to {@link + * #path(String)}. Any duplicate parameters must be preserved. This + * method should be used instead of a query in {@link #path(String)} + * when multiple {@link #push()} calls are to be made with the same + * query string. * @param queryString the query string to be used for the push. * @return this builder. */ @@ -108,33 +144,55 @@ public interface PushBuilder */ public abstract PushBuilder conditional(boolean conditional); - /** Set a header to be used for the push. + /** + * <p>Set a header to be used for the push. If the builder has an + * existing header with the same name, its value is overwritten.</p> + * * @param name The header name to set * @param value The header value to set * @return this builder. */ public abstract PushBuilder setHeader(String name, String value); + - /** Add a header to be used for the push. + /** + * <p>Add a header to be used for the push.</p> * @param name The header name to add * @param value The header value to add * @return this builder. */ public abstract PushBuilder addHeader(String name, String value); + + + /** + * <p>Remove the named header. If the header does not exist, take + * no action.</p> + * + * @param name The name of the header to remove + * @return this builder. + */ + public abstract PushBuilder removeHeader(String name); + - /** Set the URI path to be used for the push. - * The path may start with "/" in which case it is treated as an - * absolute path, otherwise it is relative to the context path of - * the associated request. - * There is no path default and {@link #path(String)} must be called - * before every call to {@link #push()} + + /** + * Set the URI path to be used for the push. The path may start + * with "/" in which case it is treated as an absolute path, + * otherwise it is relative to the context path of the associated + * request. There is no path default and {@link #path(String)} must + * be called before every call to {@link #push()}. If a query + * string is present in the argument {@code path}, its contents must + * be merged with the contents previously passed to {@link + * #queryString}, preserving duplicates. + * * @param path the URI path to be used for the push, which may include a * query string. * @return this builder. */ public abstract PushBuilder path(String path); - /** Set the etag to be used for conditional pushes. + /** + * Set the etag to be used for conditional pushes. * The etag will be used only if {@link #isConditional()} is true. * Defaults to no etag. The value is nulled after every call to * {@link #push()} @@ -143,33 +201,44 @@ public interface PushBuilder */ public abstract PushBuilder etag(String etag); - /** Set the last modified date to be used for conditional pushes. - * The last modified date will be used only if {@link #isConditional()} is true. - * Defaults to no date. The value is nulled after every call to - * {@link #push()} + /** + * Set the last modified date to be used for conditional pushes. + * The last modified date will be used only if {@link + * #isConditional()} is true. Defaults to no date. The value is + * nulled after every call to {@link #push()} * @param lastModified the last modified date to be used for the push. * @return this builder. - * */ + */ public abstract PushBuilder lastModified(String lastModified); - /** Push a resource. - * Push a resource based on the current state of the PushBuilder. If {@link #isConditional()} - * is true and an etag or lastModified value is provided, then an appropriate conditional header - * will be generated. If both an etag and lastModified value are provided only an If-None-Match header - * will be generated. If the builder has a session ID, then the pushed request - * will include the session ID either as a Cookie or as a URI parameter as appropriate. The builders - * query string is merged with any passed query string. - * After initiating the push, the builder has its path, etag and lastModified fields nulled. All - * other fields are left as is for possible reuse in another push. - * @throws IllegalArgumentException if the method set expects a request body (eg POST) + /** Push a resource given the current state of the builder, + * returning immediately without blocking. + * + * <p>Push a resource based on the current state of the PushBuilder. + * If {@link #isConditional()} is true and an etag or lastModified + * value is provided, then an appropriate conditional header will be + * generated. If both an etag and lastModified value are provided + * only an If-None-Match header will be generated. If the builder + * has a session ID, then the pushed request will include the + * session ID either as a Cookie or as a URI parameter as + * appropriate. The builders query string is merged with any passed + * query string.</p> + * + * <p>Before returning from this method, the builder has its path, + * etag and lastModified fields nulled. All other fields are left as + * is for possible reuse in another push.</p> + * + * @throws IllegalArgumentException if the method set expects a + * request body (eg POST) + * + * @throws IllegalStateException if there was no call to {@link + * #path} on this instance either between its instantiation or the + * last call to {@code push()} that did not throw an + * IllegalStateException. */ public abstract void push(); - - - - public abstract String getMethod(); public abstract String getQueryString(); public abstract String getSessionId(); @@ -179,7 +248,4 @@ public interface PushBuilder public abstract String getPath(); public abstract String getEtag(); public abstract String getLastModified(); - - - }
\ No newline at end of file diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilderImpl.java b/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilderImpl.java index f39cffa878..def6bed888 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilderImpl.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilderImpl.java @@ -32,14 +32,14 @@ import org.eclipse.jetty.util.log.Logger; /* ------------------------------------------------------------ */ -/** +/** */ public class PushBuilderImpl implements PushBuilder -{ +{ private static final Logger LOG = Log.getLogger(PushBuilderImpl.class); private final static HttpField JettyPush = new HttpField("x-http2-push","PushBuilder"); - + private final Request _request; private final HttpFields _fields; private String _method; @@ -49,7 +49,7 @@ public class PushBuilderImpl implements PushBuilder private String _path; private String _etag; private String _lastModified; - + public PushBuilderImpl(Request request, HttpFields fields, String method, String queryString, String sessionId, boolean conditional) { super(); @@ -65,124 +65,88 @@ public class PushBuilderImpl implements PushBuilder } /* ------------------------------------------------------------ */ - /** - * @see org.eclipse.jetty.server.PushBuilder#getMethod() - */ @Override public String getMethod() { return _method; } - + /* ------------------------------------------------------------ */ - /** - * @see org.eclipse.jetty.server.PushBuilder#method(java.lang.String) - */ @Override public PushBuilder method(String method) { _method = method; return this; } - + /* ------------------------------------------------------------ */ - /** - * @see org.eclipse.jetty.server.PushBuilder#getQueryString() - */ @Override public String getQueryString() { return _queryString; } - + /* ------------------------------------------------------------ */ - /** - * @see org.eclipse.jetty.server.PushBuilder#queryString(java.lang.String) - */ @Override public PushBuilder queryString(String queryString) { _queryString = queryString; return this; } - + /* ------------------------------------------------------------ */ - /** - * @see org.eclipse.jetty.server.PushBuilder#getSessionId() - */ @Override public String getSessionId() { return _sessionId; } - + /* ------------------------------------------------------------ */ - /** - * @see org.eclipse.jetty.server.PushBuilder#sessionId(java.lang.String) - */ @Override public PushBuilder sessionId(String sessionId) { _sessionId = sessionId; return this; } - + /* ------------------------------------------------------------ */ - /** - * @see org.eclipse.jetty.server.PushBuilder#isConditional() - */ @Override public boolean isConditional() { return _conditional; } - + /* ------------------------------------------------------------ */ - /** - * @see org.eclipse.jetty.server.PushBuilder#conditional(boolean) - */ @Override public PushBuilder conditional(boolean conditional) { _conditional = conditional; return this; } - + /* ------------------------------------------------------------ */ - /** - * @see org.eclipse.jetty.server.PushBuilder#getHeaderNames() - */ @Override public Set<String> getHeaderNames() { return _fields.getFieldNamesCollection(); } - + /* ------------------------------------------------------------ */ - /** - * @see org.eclipse.jetty.server.PushBuilder#getHeader(java.lang.String) - */ @Override public String getHeader(String name) { return _fields.get(name); } - + /* ------------------------------------------------------------ */ - /** - * @see org.eclipse.jetty.server.PushBuilder#setHeader(java.lang.String, java.lang.String) - */ @Override public PushBuilder setHeader(String name,String value) { _fields.put(name,value); return this; } - + /* ------------------------------------------------------------ */ - /** - * @see org.eclipse.jetty.server.PushBuilder#addHeader(java.lang.String, java.lang.String) - */ @Override public PushBuilder addHeader(String name,String value) { @@ -190,11 +154,15 @@ public class PushBuilderImpl implements PushBuilder return this; } - /* ------------------------------------------------------------ */ - /** - * @see org.eclipse.jetty.server.PushBuilder#getPath() - */ + @Override + public PushBuilder removeHeader(String name) + { + _fields.remove(name); + return this; + } + + /* ------------------------------------------------------------ */ @Override public String getPath() { @@ -202,9 +170,6 @@ public class PushBuilderImpl implements PushBuilder } /* ------------------------------------------------------------ */ - /** - * @see org.eclipse.jetty.server.PushBuilder#path(java.lang.String) - */ @Override public PushBuilder path(String path) { @@ -213,9 +178,6 @@ public class PushBuilderImpl implements PushBuilder } /* ------------------------------------------------------------ */ - /** - * @see org.eclipse.jetty.server.PushBuilder#getEtag() - */ @Override public String getEtag() { @@ -223,9 +185,6 @@ public class PushBuilderImpl implements PushBuilder } /* ------------------------------------------------------------ */ - /** - * @see org.eclipse.jetty.server.PushBuilder#etag(java.lang.String) - */ @Override public PushBuilder etag(String etag) { @@ -234,9 +193,6 @@ public class PushBuilderImpl implements PushBuilder } /* ------------------------------------------------------------ */ - /** - * @see org.eclipse.jetty.server.PushBuilder#getLastModified() - */ @Override public String getLastModified() { @@ -244,9 +200,6 @@ public class PushBuilderImpl implements PushBuilder } /* ------------------------------------------------------------ */ - /** - * @see org.eclipse.jetty.server.PushBuilder#lastModified(java.lang.String) - */ @Override public PushBuilder lastModified(String lastModified) { @@ -255,40 +208,36 @@ public class PushBuilderImpl implements PushBuilder } /* ------------------------------------------------------------ */ - /* ------------------------------------------------------------ */ - /** - * @see org.eclipse.jetty.server.PushBuilder#push() - */ @Override public void push() { if (HttpMethod.POST.is(_method) || HttpMethod.PUT.is(_method)) throw new IllegalStateException("Bad Method "+_method); - + if (_path==null || _path.length()==0) throw new IllegalStateException("Bad Path "+_path); - + String path=_path; String query=_queryString; int q=path.indexOf('?'); if (q>=0) { - query=(query!=null && query.length()>0)?(_path.substring(q+1)+'&'+query):_path.substring(q+1); - path=_path.substring(0,q); + query=(query!=null && query.length()>0)?(path.substring(q+1)+'&'+query):path.substring(q+1); + path=path.substring(0,q); } - + if (!path.startsWith("/")) path=URIUtil.addPaths(_request.getContextPath(),path); - + String param=null; if (_sessionId!=null) { if (_request.isRequestedSessionIdFromURL()) param="jsessionid="+_sessionId; - // TODO else + // TODO else // _fields.add("Cookie","JSESSIONID="+_sessionId); } - + if (_conditional) { if (_etag!=null) @@ -296,16 +245,17 @@ public class PushBuilderImpl implements PushBuilder else if (_lastModified!=null) _fields.add(HttpHeader.IF_MODIFIED_SINCE,_lastModified); } - - HttpURI uri = HttpURI.createHttpURI(_request.getScheme(),_request.getServerName(),_request.getServerPort(),_path,param,query,null); + + HttpURI uri = HttpURI.createHttpURI(_request.getScheme(),_request.getServerName(),_request.getServerPort(),path,param,query,null); MetaData.Request push = new MetaData.Request(_method,uri,_request.getHttpVersion(),_fields); - + if (LOG.isDebugEnabled()) LOG.debug("Push {} {} inm={} ims={}",_method,uri,_fields.get(HttpHeader.IF_NONE_MATCH),_fields.get(HttpHeader.IF_MODIFIED_SINCE)); - + _request.getHttpChannel().getHttpTransport().push(push); _path=null; _etag=null; _lastModified=null; } + } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java index fbc42e0708..e358cd64a6 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -161,6 +161,7 @@ public class Request implements HttpServletRequest private final HttpInput _input; private MetaData.Request _metadata; + private String _originalURI; private String _contextPath; private String _servletPath; @@ -937,22 +938,25 @@ public class Request implements HttpServletRequest @Override public String getLocalName() { - if (_channel==null) + if (_channel!=null) { - try - { - String name =InetAddress.getLocalHost().getHostName(); - if (StringUtil.ALL_INTERFACES.equals(name)) - return null; - return name; - } - catch (java.net.UnknownHostException e) - { - LOG.ignore(e); - } + InetSocketAddress local=_channel.getLocalAddress(); + if (local!=null) + return local.getHostString(); } - InetSocketAddress local=_channel.getLocalAddress(); - return local.getHostString(); + + try + { + String name =InetAddress.getLocalHost().getHostName(); + if (StringUtil.ALL_INTERFACES.equals(name)) + return null; + return name; + } + catch (java.net.UnknownHostException e) + { + LOG.ignore(e); + } + return null; } /* ------------------------------------------------------------ */ @@ -965,7 +969,7 @@ public class Request implements HttpServletRequest if (_channel==null) return 0; InetSocketAddress local=_channel.getLocalAddress(); - return local.getPort(); + return local==null?0:local.getPort(); } /* ------------------------------------------------------------ */ @@ -1270,6 +1274,8 @@ public class Request implements HttpServletRequest @Override public RequestDispatcher getRequestDispatcher(String path) { + path = URIUtil.compactPath(path); + if (path == null || _context == null) return null; @@ -1580,6 +1586,14 @@ public class Request implements HttpServletRequest /* ------------------------------------------------------------ */ /** + * @return Returns the original uri passed in metadata before customization/rewrite + */ + public String getOriginalURI() + { + return _originalURI; + } + /* ------------------------------------------------------------ */ + /** * @param uri the URI to set */ public void setHttpURI(HttpURI uri) @@ -1739,7 +1753,6 @@ public class Request implements HttpServletRequest return _savedNewSessions.get(key); } - /* ------------------------------------------------------------ */ /** * @param request the Request metadata @@ -1747,6 +1760,7 @@ public class Request implements HttpServletRequest public void setMetaData(org.eclipse.jetty.http.MetaData.Request request) { _metadata=request; + _originalURI=_metadata.getURIString(); setMethod(request.getMethod()); HttpURI uri = request.getURI(); @@ -1803,6 +1817,7 @@ public class Request implements HttpServletRequest protected void recycle() { _metadata=null; + _originalURI=null; if (_context != null) throw new IllegalStateException("Request in context!"); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/RequestLogCollection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/RequestLogCollection.java index 2359c32616..b6cfa1cea3 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/RequestLogCollection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/RequestLogCollection.java @@ -18,10 +18,10 @@ package org.eclipse.jetty.server; -import java.util.ArrayList; - import static java.util.Arrays.asList; +import java.util.ArrayList; + class RequestLogCollection implements RequestLog { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java index d7c2425963..9a5b170ddb 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java @@ -51,9 +51,8 @@ import org.eclipse.jetty.http.MetaData; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.http.PreEncodedHttpField; import org.eclipse.jetty.io.RuntimeIOException; +import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ErrorHandler; -import org.eclipse.jetty.util.ByteArrayISO8859Writer; -import org.eclipse.jetty.util.Jetty; import org.eclipse.jetty.util.QuotedStringTokenizer; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.URIUtil; @@ -65,12 +64,12 @@ import org.eclipse.jetty.util.log.Logger; */ public class Response implements HttpServletResponse { - private static final Logger LOG = Log.getLogger(Response.class); + private static final Logger LOG = Log.getLogger(Response.class); private static final String __COOKIE_DELIM="\",;\\ \t"; private final static String __01Jan1970_COOKIE = DateGenerator.formatCookieDate(0).trim(); private final static int __MIN_BUFFER_SIZE = 1; private final static HttpField __EXPIRES_01JAN1970 = new PreEncodedHttpField(HttpHeader.EXPIRES,DateGenerator.__01Jan1970); - + // Cookie building buffer. Reduce garbage for cookie using applications private static final ThreadLocal<StringBuilder> __cookieBuilder = new ThreadLocal<StringBuilder>() @@ -81,7 +80,7 @@ public class Response implements HttpServletResponse return new StringBuilder(128); } }; - + public enum OutputType { NONE, STREAM, WRITER @@ -114,7 +113,7 @@ public class Response implements HttpServletResponse private OutputType _outputType = OutputType.NONE; private ResponseWriter _writer; private long _contentLength = -1; - + public Response(HttpChannel channel, HttpOutput out) { @@ -141,7 +140,7 @@ public class Response implements HttpServletResponse _fields.clear(); _explicitEncoding=false; } - + public HttpOutput getHttpOutput() { return _out; @@ -178,7 +177,7 @@ public class Response implements HttpServletResponse cookie.getComment(), cookie.isSecure(), cookie.isHttpOnly(), - cookie.getVersion());; + cookie.getVersion()); } @Override @@ -241,13 +240,13 @@ public class Response implements HttpServletResponse // Format value and params StringBuilder buf = __cookieBuilder.get(); buf.setLength(0); - + // Name is checked for legality by servlet spec, but can also be passed directly so check again for quoting boolean quote_name=isQuoteNeededForCookie(name); quoteOnlyOrAppend(buf,name,quote_name); - + buf.append('='); - + // Remember name= part to look for other matching set-cookie String name_equals=buf.toString(); @@ -260,7 +259,7 @@ public class Response implements HttpServletResponse boolean quote_domain = has_domain && isQuoteNeededForCookie(domain); boolean has_path = path!=null && path.length()>0; boolean quote_path = has_path && isQuoteNeededForCookie(path); - + // Upgrade the version if we have a comment or we need to quote value/path/domain or if they were already quoted if (version==0 && ( comment!=null || quote_name || quote_value || quote_domain || quote_path || QuotedStringTokenizer.isQuoted(name) || QuotedStringTokenizer.isQuoted(value) || @@ -272,14 +271,14 @@ public class Response implements HttpServletResponse buf.append (";Version=1"); else if (version>1) buf.append (";Version=").append(version); - + // Append path if (has_path) { buf.append(";Path="); quoteOnlyOrAppend(buf,path,quote_path); } - + // Append domain if (has_domain) { @@ -297,7 +296,7 @@ public class Response implements HttpServletResponse buf.append(__01Jan1970_COOKIE); else DateGenerator.formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge); - + // for v1 cookies, also send max-age if (version>=1) { @@ -336,7 +335,7 @@ public class Response implements HttpServletResponse } } } - + // add the set cookie _fields.add(HttpHeader.SET_COOKIE.toString(), buf.toString()); @@ -355,7 +354,7 @@ public class Response implements HttpServletResponse { if (s==null || s.length()==0) return true; - + if (QuotedStringTokenizer.isQuoted(s)) return false; @@ -364,15 +363,15 @@ public class Response implements HttpServletResponse char c = s.charAt(i); if (__COOKIE_DELIM.indexOf(c)>=0) return true; - + if (c<0x20 || c>=0x7f) throw new IllegalArgumentException("Illegal character in cookie value"); } return false; } - - + + private static void quoteOnlyOrAppend(StringBuilder buf, String s, boolean quote) { if (quote) @@ -380,7 +379,7 @@ public class Response implements HttpServletResponse else buf.append(s); } - + @Override public boolean containsHeader(String name) { @@ -404,7 +403,7 @@ public class Response implements HttpServletResponse int port = uri.getPort(); if (port < 0) port = HttpScheme.HTTPS.asString().equalsIgnoreCase(uri.getScheme()) ? 443 : 80; - + // Is it the same server? if (!request.getServerName().equalsIgnoreCase(uri.getHost())) return url; @@ -422,7 +421,7 @@ public class Response implements HttpServletResponse return null; // should not encode if cookies in evidence - if ((sessionManager.isUsingCookies() && request.isRequestedSessionIdFromCookie()) || !sessionManager.isUsingURLs()) + if ((sessionManager.isUsingCookies() && request.isRequestedSessionIdFromCookie()) || !sessionManager.isUsingURLs()) { int prefix = url.indexOf(sessionURLPrefix); if (prefix != -1) @@ -524,7 +523,7 @@ public class Response implements HttpServletResponse LOG.debug("Aborting on sendError on committed response {} {}",code,message); code=-1; } - + switch(code) { case -1: @@ -534,91 +533,44 @@ public class Response implements HttpServletResponse sendProcessing(); return; default: + break; } - if (isCommitted()) - LOG.warn("Committed before "+code+" "+message); - resetBuffer(); + _mimeType=null; _characterEncoding=null; + _outputType = OutputType.NONE; setHeader(HttpHeader.EXPIRES,null); setHeader(HttpHeader.LAST_MODIFIED,null); setHeader(HttpHeader.CACHE_CONTROL,null); setHeader(HttpHeader.CONTENT_TYPE,null); - setHeader(HttpHeader.CONTENT_LENGTH,null); + setHeader(HttpHeader.CONTENT_LENGTH, null); - _outputType = OutputType.NONE; setStatus(code); - _reason=message; Request request = _channel.getRequest(); Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION); if (message==null) - message=cause==null?HttpStatus.getMessage(code):cause.toString(); + { + _reason=HttpStatus.getMessage(code); + message=cause==null?_reason:cause.toString(); + } + else + _reason=message; - // If we are allowed to have a body - if (code!=SC_NO_CONTENT && - code!=SC_NOT_MODIFIED && - code!=SC_PARTIAL_CONTENT && - code>=SC_OK) - { - ErrorHandler error_handler = ErrorHandler.getErrorHandler(_channel.getServer(),request.getContext()==null?null:request.getContext().getContextHandler()); - if (error_handler!=null) - { - request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE,new Integer(code)); - request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message); - request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI()); - request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME,request.getServletName()); - error_handler.handle(null,_channel.getRequest(),_channel.getRequest(),this ); - } - else - { - setHeader(HttpHeader.CACHE_CONTROL, "must-revalidate,no-cache,no-store"); - setContentType(MimeTypes.Type.TEXT_HTML_8859_1.toString()); - try (ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(2048);) - { - message=StringUtil.sanitizeXmlString(message); - String uri= request.getRequestURI(); - uri=StringUtil.sanitizeXmlString(uri); - - writer.write("<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html;charset=ISO-8859-1\"/>\n"); - writer.write("<title>Error "); - writer.write(Integer.toString(code)); - writer.write(' '); - if (message==null) - writer.write(message); - writer.write("</title>\n</head>\n<body>\n<h2>HTTP ERROR: "); - writer.write(Integer.toString(code)); - writer.write("</h2>\n<p>Problem accessing "); - writer.write(uri); - writer.write(". Reason:\n<pre> "); - writer.write(message); - writer.write("</pre>"); - writer.write("</p>\n<hr />"); - - getHttpChannel().getHttpConfiguration().writePoweredBy(writer,null,"<hr/>"); - writer.write("\n</body>\n</html>\n"); - - writer.flush(); - setContentLength(writer.size()); - try (ServletOutputStream outputStream = getOutputStream()) - { - writer.writeTo(outputStream); - writer.destroy(); - } - } - } - } - else if (code!=SC_PARTIAL_CONTENT) + // If we are allowed to have a body, then produce the error page. + if (code != SC_NO_CONTENT && code != SC_NOT_MODIFIED && + code != SC_PARTIAL_CONTENT && code >= SC_OK) { - // TODO work out why this is required? - _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_TYPE); - _channel.getRequest().getHttpFields().remove(HttpHeader.CONTENT_LENGTH); - _characterEncoding=null; - _mimeType=null; + ContextHandler.Context context = request.getContext(); + ContextHandler contextHandler = context == null ? _channel.getState().getContextHandler() : context.getContextHandler(); + ErrorHandler error_handler = ErrorHandler.getErrorHandler(_channel.getServer(), contextHandler); + request.setAttribute(RequestDispatcher.ERROR_STATUS_CODE, code); + request.setAttribute(RequestDispatcher.ERROR_MESSAGE, message); + request.setAttribute(RequestDispatcher.ERROR_REQUEST_URI, request.getRequestURI()); + request.setAttribute(RequestDispatcher.ERROR_SERVLET_NAME, request.getServletName()); + error_handler.handle(null, request, request, this); } - - closeOutput(); } /** @@ -637,7 +589,7 @@ public class Response implements HttpServletResponse _channel.sendResponse(HttpGenerator.PROGRESS_102_INFO, null, true); } } - + /** * Sends a response with one of the 300 series redirection codes. * @param code the redirect status code @@ -648,7 +600,7 @@ public class Response implements HttpServletResponse { if ((code < HttpServletResponse.SC_MULTIPLE_CHOICES) || (code >= HttpServletResponse.SC_BAD_REQUEST)) throw new IllegalArgumentException("Not a 3xx redirect code"); - + if (isIncluding()) return; @@ -672,11 +624,11 @@ public class Response implements HttpServletResponse if (!location.startsWith("/")) buf.append('/'); } - + if(location==null) throw new IllegalStateException("path cannot be above root"); buf.append(location); - + location=buf.toString(); } @@ -791,13 +743,13 @@ public class Response implements HttpServletResponse setContentType(value); return; } - + if (HttpHeader.CONTENT_LENGTH.is(name)) { setHeader(name,value); return; } - + _fields.add(name, value); } @@ -822,7 +774,7 @@ public class Response implements HttpServletResponse _contentLength = value; } } - + @Override public void setStatus(int sc) { @@ -841,7 +793,7 @@ public class Response implements HttpServletResponse { setStatusWithReason(sc,sm); } - + public void setStatusWithReason(int sc, String sm) { if (sc <= 0) @@ -903,9 +855,9 @@ public class Response implements HttpServletResponse setCharacterEncoding(encoding,false); } } - + Locale locale = getLocale(); - + if (_writer != null && _writer.isFor(locale,encoding)) _writer.reopen(); else @@ -917,7 +869,7 @@ public class Response implements HttpServletResponse else _writer = new ResponseWriter(new EncodingHttpWriter(_out, encoding),locale,encoding); } - + // Set the output type at the end, because setCharacterEncoding() checks for it _outputType = OutputType.WRITER; } @@ -939,7 +891,7 @@ public class Response implements HttpServletResponse long written = _out.getWritten(); if (written > len) throw new IllegalArgumentException("setContentLength(" + len + ") when already written " + written); - + _fields.putLongField(HttpHeader.CONTENT_LENGTH, len); if (isAllContentWritten(written)) { @@ -963,7 +915,7 @@ public class Response implements HttpServletResponse else _fields.remove(HttpHeader.CONTENT_LENGTH); } - + public long getContentLength() { return _contentLength; @@ -1006,7 +958,7 @@ public class Response implements HttpServletResponse _contentLength = len; _fields.putLongField(HttpHeader.CONTENT_LENGTH.toString(), len); } - + @Override public void setContentLengthLong(long length) { @@ -1018,7 +970,7 @@ public class Response implements HttpServletResponse { setCharacterEncoding(encoding,true); } - + private void setCharacterEncoding(String encoding, boolean explicit) { if (isIncluding() || isWriting()) @@ -1029,12 +981,12 @@ public class Response implements HttpServletResponse if (encoding == null) { _explicitEncoding=false; - + // Clear any encoding. if (_characterEncoding != null) { _characterEncoding = null; - + if (_mimeType!=null) { _mimeType=_mimeType.getBaseType(); @@ -1070,7 +1022,7 @@ public class Response implements HttpServletResponse } } } - + @Override public void setContentType(String contentType) { @@ -1092,7 +1044,7 @@ public class Response implements HttpServletResponse { _contentType = contentType; _mimeType = MimeTypes.CACHE.get(contentType); - + String charset; if (_mimeType!=null && _mimeType.getCharset()!=null && !_mimeType.isCharsetAssumed()) charset=_mimeType.getCharsetString(); @@ -1129,7 +1081,7 @@ public class Response implements HttpServletResponse _fields.put(_mimeType.getContentTypeField()); } } - + } @Override @@ -1165,7 +1117,7 @@ public class Response implements HttpServletResponse _fields.clear(); String connection = _channel.getRequest().getHeader(HttpHeader.CONNECTION.asString()); - + if (connection != null) { for (String value: StringUtil.csvSplit(null,connection,0,connection.length())) @@ -1195,12 +1147,12 @@ public class Response implements HttpServletResponse } public void reset(boolean preserveCookies) - { + { if (!preserveCookies) reset(); else { - ArrayList<String> cookieValues = new ArrayList<String>(5); + ArrayList<String> cookieValues = new ArrayList<>(5); Enumeration<String> vals = _fields.getValues(HttpHeader.SET_COOKIE.asString()); while (vals.hasMoreElements()) cookieValues.add(vals.nextElement()); @@ -1229,11 +1181,11 @@ public class Response implements HttpServletResponse { return new MetaData.Response(_channel.getRequest().getHttpVersion(), getStatus(), getReason(), _fields, getLongContentLength()); } - + /** Get the MetaData.Response committed for this response. - * This may differ from the meta data in this response for + * This may differ from the meta data in this response for * exceptional responses (eg 4xx and 5xx responses generated - * by the container) and the committedMetaData should be used + * by the container) and the committedMetaData should be used * for logging purposes. * @return The committed MetaData or a {@link #newResponseMetaData()} * if not yet committed. @@ -1307,7 +1259,7 @@ public class Response implements HttpServletResponse { return String.format("%s %d %s%n%s", _channel.getRequest().getHttpVersion(), _status, _reason == null ? "" : _reason, _fields); } - + public void putHeaders(HttpContent content,long contentLength, boolean etag) { @@ -1334,11 +1286,11 @@ public class Response implements HttpServletResponse _characterEncoding=content.getCharacterEncoding(); _mimeType=content.getMimeType(); } - + HttpField ce=content.getContentEncoding(); if (ce!=null) _fields.put(ce); - + if (etag) { HttpField et = content.getETag(); @@ -1346,9 +1298,9 @@ public class Response implements HttpServletResponse _fields.put(et); } } - + public static void putHeaders(HttpServletResponse response, HttpContent content, long contentLength, boolean etag) - { + { long lml=content.getResource().lastModified(); if (lml>=0) response.setDateHeader(HttpHeader.LAST_MODIFIED.asString(),lml); @@ -1370,7 +1322,7 @@ public class Response implements HttpServletResponse String ce=content.getContentEncodingValue(); if (ce!=null) response.setHeader(HttpHeader.CONTENT_ENCODING.asString(),ce); - + if (etag) { String et=content.getETagValue(); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java b/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java index 7dc74588d8..aefb547f2a 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java @@ -27,6 +27,7 @@ import javax.servlet.ServletRequest; import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.HttpScheme; +import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.ssl.SslConnection; import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint; import org.eclipse.jetty.util.TypeUtil; @@ -66,18 +67,26 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer @Override public void customize(Connector connector, HttpConfiguration channelConfig, Request request) { - if (request.getHttpChannel().getEndPoint() instanceof DecryptedEndPoint) + EndPoint endp = request.getHttpChannel().getEndPoint(); + if (endp instanceof DecryptedEndPoint) { - request.setSecure(true); - - if (request.getHttpURI().getScheme()==null) - request.setScheme(HttpScheme.HTTPS.asString()); - - SslConnection.DecryptedEndPoint ssl_endp = (DecryptedEndPoint)request.getHttpChannel().getEndPoint(); + SslConnection.DecryptedEndPoint ssl_endp = (DecryptedEndPoint)endp; SslConnection sslConnection = ssl_endp.getSslConnection(); SSLEngine sslEngine=sslConnection.getSSLEngine(); customize(sslEngine,request); + + if (request.getHttpURI().getScheme()==null) + request.setScheme(HttpScheme.HTTPS.asString()); + } + else if (endp instanceof ProxyConnectionFactory.ProxyEndPoint) + { + ProxyConnectionFactory.ProxyEndPoint proxy = (ProxyConnectionFactory.ProxyEndPoint)endp; + if (request.getHttpURI().getScheme()==null && proxy.getAttribute(ProxyConnectionFactory.TLS_VERSION)!=null) + request.setScheme(HttpScheme.HTTPS.asString()); } + + if (HttpScheme.HTTPS.is(request.getScheme())) + request.setSecure(true); } /** @@ -104,7 +113,6 @@ public class SecureRequestCustomizer implements HttpConfiguration.Customizer */ public void customize(SSLEngine sslEngine, Request request) { - request.setScheme(HttpScheme.HTTPS.asString()); SSLSession sslSession = sslEngine.getSession(); if (_sniHostCheck) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java index 1665a19b16..4cd668dd95 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java @@ -24,6 +24,7 @@ import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.nio.channels.Channel; +import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; @@ -32,6 +33,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.Future; import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.ChannelEndPoint; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.ManagedSelector; @@ -229,7 +231,6 @@ public class ServerConnector extends AbstractNetworkConnector _manager = newSelectorManager(getExecutor(), getScheduler(), selectors>0?selectors:Math.max(1,Math.min(4,Runtime.getRuntime().availableProcessors()/2))); addBean(_manager, true); - setSelectorPriorityDelta(-1); setAcceptorPriorityDelta(-2); } @@ -426,7 +427,7 @@ public class ServerConnector extends AbstractNetworkConnector return _localPort; } - protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException + protected ChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey key) throws IOException { return new SelectChannelEndPoint(channel, selectSet, key, getScheduler(), getIdleTimeout()); } @@ -493,19 +494,19 @@ public class ServerConnector extends AbstractNetworkConnector } @Override - protected void accepted(SocketChannel channel) throws IOException + protected void accepted(SelectableChannel channel) throws IOException { - ServerConnector.this.accepted(channel); + ServerConnector.this.accepted((SocketChannel)channel); } @Override - protected SelectChannelEndPoint newEndPoint(SocketChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException + protected ChannelEndPoint newEndPoint(SelectableChannel channel, ManagedSelector selectSet, SelectionKey selectionKey) throws IOException { - return ServerConnector.this.newEndPoint(channel, selectSet, selectionKey); + return ServerConnector.this.newEndPoint((SocketChannel)channel, selectSet, selectionKey); } @Override - public Connection newConnection(SocketChannel channel, EndPoint endpoint, Object attachment) throws IOException + public Connection newConnection(SelectableChannel channel, EndPoint endpoint, Object attachment) throws IOException { return getDefaultConnectionFactory().newConnection(ServerConnector.this, endpoint); } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/SocketCustomizationListener.java b/jetty-server/src/main/java/org/eclipse/jetty/server/SocketCustomizationListener.java index cdc402120c..9ac93c7a52 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/SocketCustomizationListener.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/SocketCustomizationListener.java @@ -20,12 +20,12 @@ package org.eclipse.jetty.server; import java.net.Socket; -import org.eclipse.jetty.io.ChannelEndPoint; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.io.Connection.Listener; +import org.eclipse.jetty.io.EndPoint; +import org.eclipse.jetty.io.SocketChannelEndPoint; import org.eclipse.jetty.io.ssl.SslConnection; import org.eclipse.jetty.io.ssl.SslConnection.DecryptedEndPoint; -import org.eclipse.jetty.io.EndPoint; /* ------------------------------------------------------------ */ @@ -70,9 +70,9 @@ public class SocketCustomizationListener implements Listener ssl=true; } - if (endp instanceof ChannelEndPoint) + if (endp instanceof SocketChannelEndPoint) { - Socket socket = ((ChannelEndPoint)endp).getSocket(); + Socket socket = ((SocketChannelEndPoint)endp).getSocket(); customize(socket,connection.getClass(),ssl); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java index d5fca99b09..7501d84def 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/AbstractHandler.java @@ -21,7 +21,14 @@ package org.eclipse.jetty.server.handler; import java.io.IOException; +import javax.servlet.DispatcherType; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + import org.eclipse.jetty.server.Handler; +import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.component.ContainerLifeCycle; @@ -47,6 +54,31 @@ public abstract class AbstractHandler extends ContainerLifeCycle implements Hand { } + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (baseRequest.getDispatcherType()==DispatcherType.ERROR) + doError(target,baseRequest,request,response); + else + doHandle(target,baseRequest,request,response); + } + + /* ------------------------------------------------------------ */ + protected void doHandle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + } + + /* ------------------------------------------------------------ */ + protected void doError(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + Object o = request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE); + int code = (o instanceof Integer)?((Integer)o).intValue():(o!=null?Integer.valueOf(o.toString()):500); + o = request.getAttribute(RequestDispatcher.ERROR_MESSAGE); + String reason = o!=null?o.toString():null; + + response.sendError(code,reason); + } + /* ------------------------------------------------------------ */ /* * @see org.eclipse.thread.LifeCycle#start() diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java index 94f931cb7f..668df07f96 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java @@ -1143,11 +1143,30 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu } } - if (DispatcherType.REQUEST.equals(dispatch) && isProtectedTarget(target)) + switch(dispatch) { - response.sendError(HttpServletResponse.SC_NOT_FOUND); - baseRequest.setHandled(true); - return; + case REQUEST: + if (isProtectedTarget(target)) + { + response.sendError(HttpServletResponse.SC_NOT_FOUND); + baseRequest.setHandled(true); + return; + } + break; + + case ERROR: + // If this is already a dispatch to an error page, proceed normally + if (Boolean.TRUE.equals(baseRequest.getAttribute(Dispatcher.__ERROR_DISPATCH))) + break; + + Object error = request.getAttribute(Dispatcher.ERROR_STATUS_CODE); + // We can just call sendError here. If there is no error page, then one will + // be generated. If there is an error page, then a RequestDispatcher will be + // used to route the request through appropriate filters etc. + response.sendError((error instanceof Integer)?((Integer)error).intValue():500); + return; + default: + break; } // start manual inline of nextHandle(target,baseRequest,request,response); @@ -1654,7 +1673,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu return null; if (_classLoader == null) - return Loader.loadClass(this.getClass(),className); + return Loader.loadClass(className); return _classLoader.loadClass(className); } @@ -2298,7 +2317,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu try { @SuppressWarnings({ "unchecked", "rawtypes" }) - Class<? extends EventListener> clazz = _classLoader==null?Loader.loadClass(ContextHandler.class,className):(Class)_classLoader.loadClass(className); + Class<? extends EventListener> clazz = _classLoader==null?Loader.loadClass(className):(Class)_classLoader.loadClass(className); addListener(clazz); } catch (ClassNotFoundException e) @@ -2391,7 +2410,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu //classloader, or a parent of it try { - Class<?> reflect = Loader.loadClass(getClass(), "sun.reflect.Reflection"); + Class<?> reflect = Loader.loadClass("sun.reflect.Reflection"); Method getCallerClass = reflect.getMethod("getCallerClass", Integer.TYPE); Class<?> caller = (Class<?>)getCallerClass.invoke(null, 2); @@ -2869,7 +2888,7 @@ public class ContextHandler extends ScopedHandler implements Attributes, Gracefu /** * @param context The context being entered * @param request A request that is applicable to the scope, or null - * @param reason An object that indicates the reason the scope is being entered + * @param reason An object that indicates the reason the scope is being entered. */ void enterScope(Context context, Request request, Object reason); diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java index 97a9b388ca..f8fd5a33e2 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/DebugHandler.java @@ -27,7 +27,6 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.HttpURI; import org.eclipse.jetty.io.Connection; import org.eclipse.jetty.server.AbstractConnector; import org.eclipse.jetty.server.Connector; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java index a30a1e3149..97c29dac20 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java @@ -24,6 +24,7 @@ import java.io.StringWriter; import java.io.Writer; import java.nio.ByteBuffer; +import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -33,38 +34,35 @@ import org.eclipse.jetty.http.HttpHeader; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.MimeTypes; +import org.eclipse.jetty.server.AsyncContextEvent; import org.eclipse.jetty.server.Dispatcher; +import org.eclipse.jetty.server.HttpOutput; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.ByteArrayISO8859Writer; -import org.eclipse.jetty.util.Jetty; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -/* ------------------------------------------------------------ */ -/** Handler for Error pages - * An ErrorHandler is registered with {@link ContextHandler#setErrorHandler(ErrorHandler)} or - * {@link org.eclipse.jetty.server.Server#addBean(Object)}. - * It is called by the HttpResponse.sendError method to write a error page via {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)} - * or via {@link #badMessageError(int, String, HttpFields)} for bad requests for which a dispatch cannot be done. - * +/** + * <p>Component that handles Error Pages.</p> + * <p>An ErrorHandler is registered with {@link ContextHandler#setErrorHandler(ErrorHandler)} or + * {@link org.eclipse.jetty.server.Server#addBean(Object)}.</p> + * <p>It is called by {@link HttpServletResponse#sendError(int)} to write an error page via + * {@link #handle(String, Request, HttpServletRequest, HttpServletResponse)} + * or via {@link #badMessageError(int, String, HttpFields)} for bad requests for which a + * dispatch cannot be done.</p> */ public class ErrorHandler extends AbstractHandler -{ +{ private static final Logger LOG = Log.getLogger(ErrorHandler.class); - public final static String ERROR_PAGE="org.eclipse.jetty.server.error_page"; - - boolean _showStacks=true; - boolean _showMessageInTitle=true; - String _cacheControl="must-revalidate,no-cache,no-store"; - /* ------------------------------------------------------------ */ - /* - * @see org.eclipse.jetty.server.server.Handler#handle(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int) - */ + private boolean _showStacks = true; + private boolean _showMessageInTitle = true; + private String _cacheControl = "must-revalidate,no-cache,no-store"; + @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { @@ -74,139 +72,151 @@ public class ErrorHandler extends AbstractHandler baseRequest.setHandled(true); return; } - + if (this instanceof ErrorPageMapper) { - String error_page=((ErrorPageMapper)this).getErrorPage(request); - if (error_page!=null && request.getServletContext()!=null) + String error_page = ((ErrorPageMapper)this).getErrorPage(request); + + ServletContext context = request.getServletContext(); + if (context == null) { - String old_error_page=(String)request.getAttribute(ERROR_PAGE); - if (old_error_page==null || !old_error_page.equals(error_page)) - { - request.setAttribute(ERROR_PAGE, error_page); + AsyncContextEvent event = baseRequest.getHttpChannelState().getAsyncContextEvent(); + context = event == null ? null : event.getServletContext(); + } - Dispatcher dispatcher = (Dispatcher) request.getServletContext().getRequestDispatcher(error_page); + if (error_page != null && context != null) + { + Dispatcher dispatcher = (Dispatcher)context.getRequestDispatcher(error_page); + if (dispatcher != null) + { try { - if(dispatcher!=null) - { - dispatcher.error(request, response); - return; - } - LOG.warn("No error page "+error_page); + dispatcher.error(request, response); + return; } - catch (ServletException e) + catch (ServletException x) { - LOG.warn(Log.EXCEPTION, e); - return; + throw new IOException(x); } } + else + { + LOG.warn("Could not dispatch to error page: {}", error_page); + // Fall through to provide the default error page. + } } } - + baseRequest.setHandled(true); - response.setContentType(MimeTypes.Type.TEXT_HTML_8859_1.asString()); - if (_cacheControl!=null) - response.setHeader(HttpHeader.CACHE_CONTROL.asString(), _cacheControl); - ByteArrayISO8859Writer writer= new ByteArrayISO8859Writer(4096); - String reason=(response instanceof Response)?((Response)response).getReason():null; - handleErrorPage(request, writer, response.getStatus(), reason); - writer.flush(); - response.setContentLength(writer.size()); - writer.writeTo(response.getOutputStream()); - writer.destroy(); + + HttpOutput out = baseRequest.getResponse().getHttpOutput(); + if (!out.isAsync()) + { + response.setContentType(MimeTypes.Type.TEXT_HTML_8859_1.asString()); + String cacheHeader = getCacheControl(); + if (cacheHeader != null) + response.setHeader(HttpHeader.CACHE_CONTROL.asString(), cacheHeader); + ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(4096); + String reason = (response instanceof Response) ? ((Response)response).getReason() : null; + handleErrorPage(request, writer, response.getStatus(), reason); + writer.flush(); + response.setContentLength(writer.size()); + writer.writeTo(response.getOutputStream()); + writer.destroy(); + } } /* ------------------------------------------------------------ */ protected void handleErrorPage(HttpServletRequest request, Writer writer, int code, String message) - throws IOException + throws IOException { - writeErrorPage(request, writer, code, message, _showStacks); + writeErrorPage(request, writer, code, message, isShowStacks()); } /* ------------------------------------------------------------ */ protected void writeErrorPage(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks) - throws IOException + throws IOException { if (message == null) - message=HttpStatus.getMessage(code); + message = HttpStatus.getMessage(code); writer.write("<html>\n<head>\n"); - writeErrorPageHead(request,writer,code,message); + writeErrorPageHead(request, writer, code, message); writer.write("</head>\n<body>"); - writeErrorPageBody(request,writer,code,message,showStacks); + writeErrorPageBody(request, writer, code, message, showStacks); writer.write("\n</body>\n</html>\n"); } /* ------------------------------------------------------------ */ protected void writeErrorPageHead(HttpServletRequest request, Writer writer, int code, String message) - throws IOException - { + throws IOException + { writer.write("<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\n"); writer.write("<title>Error "); writer.write(Integer.toString(code)); - if (_showMessageInTitle) + if (getShowMessageInTitle()) { writer.write(' '); - write(writer,message); + write(writer, message); } writer.write("</title>\n"); } /* ------------------------------------------------------------ */ protected void writeErrorPageBody(HttpServletRequest request, Writer writer, int code, String message, boolean showStacks) - throws IOException + throws IOException { - String uri= request.getRequestURI(); + String uri = request.getRequestURI(); - writeErrorPageMessage(request,writer,code,message,uri); + writeErrorPageMessage(request, writer, code, message, uri); if (showStacks) - writeErrorPageStacks(request,writer); + writeErrorPageStacks(request, writer); Request.getBaseRequest(request).getHttpChannel().getHttpConfiguration() - .writePoweredBy(writer,"<hr>","<hr/>\n"); + .writePoweredBy(writer, "<hr>", "<hr/>\n"); } /* ------------------------------------------------------------ */ - protected void writeErrorPageMessage(HttpServletRequest request, Writer writer, int code, String message,String uri) - throws IOException + protected void writeErrorPageMessage(HttpServletRequest request, Writer writer, int code, String message, String uri) + throws IOException { writer.write("<h2>HTTP ERROR "); writer.write(Integer.toString(code)); writer.write("</h2>\n<p>Problem accessing "); - write(writer,uri); + write(writer, uri); writer.write(". Reason:\n<pre> "); - write(writer,message); + write(writer, message); writer.write("</pre></p>"); } /* ------------------------------------------------------------ */ protected void writeErrorPageStacks(HttpServletRequest request, Writer writer) - throws IOException + throws IOException { Throwable th = (Throwable)request.getAttribute("javax.servlet.error.exception"); - while(th!=null) + while (th != null) { writer.write("<h3>Caused by:</h3><pre>"); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); th.printStackTrace(pw); pw.flush(); - write(writer,sw.getBuffer().toString()); + write(writer, sw.getBuffer().toString()); writer.write("</pre>\n"); - th =th.getCause(); + th = th.getCause(); } } /* ------------------------------------------------------------ */ - /** Bad Message Error body - * <p>Generate a error response body to be sent for a bad message. - * In this case there is something wrong with the request, so either + /** + * <p>Generate a error response body to be sent for a bad message.</p> + * <p>In this case there is something wrong with the request, so either * a request cannot be built, or it is not safe to build a request. - * This method allows for a simple error page body to be returned - * and some response headers to be set. + * This method allows for a simple error page body to be returned + * and some response headers to be set.</p> + * * @param status The error code that will be sent * @param reason The reason for the error code (may be null) * @param fields The header fields that will be sent with the response. @@ -214,14 +224,14 @@ public class ErrorHandler extends AbstractHandler */ public ByteBuffer badMessageError(int status, String reason, HttpFields fields) { - if (reason==null) - reason=HttpStatus.getMessage(status); - fields.put(HttpHeader.CONTENT_TYPE,MimeTypes.Type.TEXT_HTML_8859_1.asString()); + if (reason == null) + reason = HttpStatus.getMessage(status); + fields.put(HttpHeader.CONTENT_TYPE, MimeTypes.Type.TEXT_HTML_8859_1.asString()); return BufferUtil.toBuffer("<h1>Bad Message " + status + "</h1><pre>reason: " + reason + "</pre>"); - } - + } + /* ------------------------------------------------------------ */ - /** Get the cacheControl. + /** * @return the cacheControl header to set on error responses. */ public String getCacheControl() @@ -230,7 +240,7 @@ public class ErrorHandler extends AbstractHandler } /* ------------------------------------------------------------ */ - /** Set the cacheControl. + /** * @param cacheControl the cacheControl header to set on error responses. */ public void setCacheControl(String cacheControl) @@ -240,7 +250,7 @@ public class ErrorHandler extends AbstractHandler /* ------------------------------------------------------------ */ /** - * @return True if stack traces are shown in the error pages + * @return whether stack traces are shown in the error pages */ public boolean isShowStacks() { @@ -249,7 +259,7 @@ public class ErrorHandler extends AbstractHandler /* ------------------------------------------------------------ */ /** - * @param showStacks True if stack traces are shown in the error pages + * @param showStacks whether stack traces are shown in the error pages */ public void setShowStacks(boolean showStacks) { @@ -258,25 +268,27 @@ public class ErrorHandler extends AbstractHandler /* ------------------------------------------------------------ */ /** - * @param showMessageInTitle if true, the error message appears in page title + * @return whether the error message appears in page title */ - public void setShowMessageInTitle(boolean showMessageInTitle) + public boolean getShowMessageInTitle() { - _showMessageInTitle = showMessageInTitle; + return _showMessageInTitle; } - /* ------------------------------------------------------------ */ - public boolean getShowMessageInTitle() + /** + * @param showMessageInTitle whether the error message appears in page title + */ + public void setShowMessageInTitle(boolean showMessageInTitle) { - return _showMessageInTitle; + _showMessageInTitle = showMessageInTitle; } /* ------------------------------------------------------------ */ - protected void write(Writer writer,String string) - throws IOException + protected void write(Writer writer, String string) + throws IOException { - if (string==null) + if (string == null) return; writer.write(StringUtil.sanitizeXmlString(string)); @@ -291,11 +303,22 @@ public class ErrorHandler extends AbstractHandler /* ------------------------------------------------------------ */ public static ErrorHandler getErrorHandler(Server server, ContextHandler context) { - ErrorHandler error_handler=null; - if (context!=null) - error_handler=context.getErrorHandler(); - if (error_handler==null && server!=null) - error_handler = server.getBean(ErrorHandler.class); + ErrorHandler error_handler = null; + if (context != null) + error_handler = context.getErrorHandler(); + if (error_handler == null) + { + synchronized (ErrorHandler.class) + { + error_handler = server.getBean(ErrorHandler.class); + if (error_handler == null) + { + error_handler = new ErrorHandler(); + error_handler.setServer(server); + server.addManaged(error_handler); + } + } + } return error_handler; } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java index f799b3a4f8..2a1b771d6c 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java @@ -28,7 +28,6 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.ArrayUtil; import org.eclipse.jetty.util.MultiException; import org.eclipse.jetty.util.annotation.ManagedAttribute; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java index c8ef17b61c..bc60dc7c05 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerWrapper.java @@ -25,10 +25,8 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Server; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.component.LifeCycle; diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java index 535020f77b..9ebec26543 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java @@ -144,9 +144,27 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory /* ------------------------------------------------------------ */ /** + * Add path to excluded paths list. + * <p> + * There are 2 syntaxes supported, Servlet <code>url-pattern</code> based, and + * Regex based. This means that the initial characters on the path spec + * line are very strict, and determine the behavior of the path matching. + * <ul> + * <li>If the spec starts with <code>'^'</code> the spec is assumed to be + * a regex based path spec and will match with normal Java regex rules.</li> + * <li>If the spec starts with <code>'/'</code> then spec is assumed to be + * a Servlet url-pattern rules path spec for either an exact match + * or prefix based match.</li> + * <li>If the spec starts with <code>'*.'</code> then spec is assumed to be + * a Servlet url-pattern rules path spec for a suffix based match.</li> + * <li>All other syntaxes are unsupported</li> + * </ul> + * <p> + * Note: inclusion takes precedence over exclude. + * * @param pathspecs Path specs (as per servlet spec) to exclude. If a * ServletContext is available, the paths are relative to the context path, - * otherwise they are absolute. + * otherwise they are absolute.<br> * For backward compatibility the pathspecs may be comma separated strings, but this * will not be supported in future versions. */ @@ -191,12 +209,27 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory /* ------------------------------------------------------------ */ /** - * Add path specs to include. Inclusion takes precedence over exclusion. + * Add path specs to include. + * <p> + * There are 2 syntaxes supported, Servlet <code>url-pattern</code> based, and + * Regex based. This means that the initial characters on the path spec + * line are very strict, and determine the behavior of the path matching. + * <ul> + * <li>If the spec starts with <code>'^'</code> the spec is assumed to be + * a regex based path spec and will match with normal Java regex rules.</li> + * <li>If the spec starts with <code>'/'</code> then spec is assumed to be + * a Servlet url-pattern rules path spec for either an exact match + * or prefix based match.</li> + * <li>If the spec starts with <code>'*.'</code> then spec is assumed to be + * a Servlet url-pattern rules path spec for a suffix based match.</li> + * <li>All other syntaxes are unsupported</li> + * </ul> + * <p> + * Note: inclusion takes precedence over exclude. + * * @param pathspecs Path specs (as per servlet spec) to include. If a * ServletContext is available, the paths are relative to the context path, * otherwise they are absolute - * For backward compatibility the pathspecs may be comma separated strings, but this - * will not be supported in future versions. */ public void addIncludedPaths(String... pathspecs) { @@ -334,9 +367,9 @@ public class GzipHandler extends HandlerWrapper implements GzipFactory /* ------------------------------------------------------------ */ /** - * Get the minimum reponse size. + * Get the minimum response size. * - * @return minimum reponse size + * @return minimum response size */ public int getMinGzipSize() { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java index 7b86fbb95d..6962694507 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionManager.java @@ -535,6 +535,7 @@ public class JDBCSessionManager extends AbstractSessionManager session.setLastNode(getSessionIdManager().getWorkerName()); _sessions.put(idInCluster, session); + _sessionsStats.increment(); //update in db try @@ -843,6 +844,7 @@ public class JDBCSessionManager extends AbstractSessionManager //loaded an expired session last managed on this node for this context, add it to the list so we can //treat it like a normal expired session _sessions.put(session.getClusterId(), session); + _sessionsStats.increment(); } else { |