This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/tomcat.git
The following commit(s) were added to refs/heads/main by this push: new b7c05a8 Implement the new connection ID and request ID API for Servlet 6.0 b7c05a8 is described below commit b7c05a8f60003c42e6f367bf307188ce391dbad2 Author: Mark Thomas <ma...@apache.org> AuthorDate: Fri Sep 24 18:30:24 2021 +0100 Implement the new connection ID and request ID API for Servlet 6.0 --- java/jakarta/el/ImportHandler.java | 1 + java/jakarta/servlet/ServletConnection.java | 95 +++++++++++++++ java/jakarta/servlet/ServletRequest.java | 48 ++++++++ java/jakarta/servlet/ServletRequestWrapper.java | 36 ++++++ java/org/apache/catalina/Globals.java | 16 --- java/org/apache/catalina/connector/Request.java | 48 ++++---- .../apache/catalina/connector/RequestFacade.java | 19 +++ java/org/apache/coyote/AbstractProcessor.java | 37 +++--- java/org/apache/coyote/ActionCode.java | 13 ++- java/org/apache/coyote/Request.java | 39 ++++++- java/org/apache/coyote/ajp/AjpProcessor.java | 7 ++ java/org/apache/coyote/http11/Http11Processor.java | 7 ++ .../coyote/http2/Http2AsyncUpgradeHandler.java | 4 +- java/org/apache/coyote/http2/Http2Protocol.java | 4 +- .../apache/coyote/http2/Http2UpgradeHandler.java | 20 +++- java/org/apache/coyote/http2/StreamProcessor.java | 18 +-- .../tomcat/util/net/ServletConnectionImpl.java | 55 +++++++++ .../apache/tomcat/util/net/SocketWrapperBase.java | 30 +++++ .../catalina/filters/TesterHttpServletRequest.java | 16 +++ .../apache/coyote/http2/TestAbstractStream.java | 130 +++++++++++++++++++-- webapps/docs/changelog.xml | 4 + 21 files changed, 553 insertions(+), 94 deletions(-) diff --git a/java/jakarta/el/ImportHandler.java b/java/jakarta/el/ImportHandler.java index 138a6da..b824d5d 100644 --- a/java/jakarta/el/ImportHandler.java +++ b/java/jakarta/el/ImportHandler.java @@ -54,6 +54,7 @@ public class ImportHandler { servletClassNames.add("RequestDispatcher"); servletClassNames.add("Servlet"); servletClassNames.add("ServletConfig"); + servletClassNames.add("ServletConnection"); servletClassNames.add("ServletContainerInitializer"); servletClassNames.add("ServletContext"); servletClassNames.add("ServletContextAttributeListener"); diff --git a/java/jakarta/servlet/ServletConnection.java b/java/jakarta/servlet/ServletConnection.java new file mode 100644 index 0000000..97ded16 --- /dev/null +++ b/java/jakarta/servlet/ServletConnection.java @@ -0,0 +1,95 @@ +/* +* Licensed to the Apache Software Foundation (ASF) under one or more +* contributor license agreements. See the NOTICE file distributed with +* this work for additional information regarding copyright ownership. +* The ASF licenses this file to You under the Apache License, Version 2.0 +* (the "License"); you may not use this file except in compliance with +* the License. You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +package jakarta.servlet; + +/** + * Provides information about the connection made to the Servlet container. This + * interface is intended primarily for debugging purposes and as such provides + * the raw information as seen by the container. Unless explicitly stated + * otherwise in the Javadoc for a method, no adjustment is made for the presence + * of reverse proxies or similar configurations. + * + * @since Servlet 6.0 + */ +public interface ServletConnection { + + /** + * Obtain a unique (within the lifetime of the JVM) identifier string for + * the network connection to the JVM that is being used for the + * {@code ServletRequest} from which this {@code ServletConnection} was + * obtained. + * <p> + * There is no defined format for this string. The format is implementation + * dependent. + * + * @return A unique identifier for the network connection + */ + String getConnectionId(); + + /** + * Obtain the name of the protocol as presented to the server after the + * removal, if present, of any TLS or similar encryption. This may not be + * the same as the protocol seen by the application. For example, a reverse + * proxy may present AJP whereas the application will see HTTP 1.1. + * <p> + * If the protocol has an entry in the <a href= + * "https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml#alpn-protocol-ids">IANA + * registry for ALPN names then the identification sequence, in string form, + * must be returned. Registered identification sequences MUST only be used + * for the associated protocol. Return values for other protocols are + * implementation dependent. Unknown protocols should return the string + * "unknown". + * + * @return The name of the protocol presented to the server after decryption + * of TLS, or similar encryption, if any. + */ + String getProtocol(); + + /** + * Obtain the connection identifier for the network connection to the server + * that is being used for the {@code ServletRequest} from which this + * {@code ServletConnection} was obtained as defined by the protocol in use. + * Note that some protocols do not define such an identifier. + * <p> + * Examples of protocol provided connection identifiers include: + * <dl> + * <dt>HTTP 1.x</dt> + * <dd>None, so the empty string should be returned</dd> + * <dt>HTTP 2</dt> + * <dd>None, so the empty string should be returned</dd> + * <dt>HTTP 3</dt> + * <dd>The QUIC connection ID</dd> + * <dt>AJP</dt> + * <dd>None, so the empty string should be returned</dd> + * </dl> + * + * @return The connection identifier if one is defined, otherwise an empty + * string + */ + String getProtocolConnectionId(); + + /** + * Determine whether or not the incoming network connection to the server + * used encryption or not. Note that where a reverse proxy is used, the + * application may have a different view as to whether encryption is being + * used due to the use of headers like {@code X-Forwarded-Proto}. + * + * @return {@code true} if the incoming network connection used encryption, + * otherwise {@code false} + */ + boolean isSecure(); +} \ No newline at end of file diff --git a/java/jakarta/servlet/ServletRequest.java b/java/jakarta/servlet/ServletRequest.java index 4bb3206..ba57afe 100644 --- a/java/jakarta/servlet/ServletRequest.java +++ b/java/jakarta/servlet/ServletRequest.java @@ -495,4 +495,52 @@ public interface ServletRequest { * @since Servlet 3.0 TODO SERVLET3 - Add comments */ public DispatcherType getDispatcherType(); + + /** + * Obtain a unique (within the lifetime of the Servlet container) identifier + * string for this request. + * <p> + * There is no defined format for this string. The format is implementation + * dependent. + * + * @return A unique identifier for the request + * + * @since Servlet 6.0 + */ + String getRequestId(); + + /** + * Obtain the request identifier for this request as defined by the protocol + * in use. Note that some protocols do not define such an identifier. + * <p> + * Examples of protocol provided request identifiers include: + * <dl> + * <dt>HTTP 1.x</dt> + * <dd>None, so the empty string should be returned</dd> + * <dt>HTTP 2</dt> + * <dd>The stream identifier</dd> + * <dt>HTTP 3</dt> + * <dd>The stream identifier</dd> + * <dt>AJP</dt> + * <dd>None, so the empty string should be returned</dd> + * + * @return The request identifier if one is defined, otherwise an empty + * string + * + * @since Servlet 6.0 + */ + String getProtocolRequestId(); + + /** + * Obtain details of the network connection to the Servlet container that is + * being used by this request. The information presented may differ from + * information presented elsewhere in the Servlet API as raw information is + * presented without adjustments for, example, use of reverse proxies that + * may be applied elsewhere in the Servlet API. + * + * @return The network connection details. + * + * @since Servlet 6.0 + */ + ServletConnection getServletConnection(); } diff --git a/java/jakarta/servlet/ServletRequestWrapper.java b/java/jakarta/servlet/ServletRequestWrapper.java index fb5bcf7..c3c076d 100644 --- a/java/jakarta/servlet/ServletRequestWrapper.java +++ b/java/jakarta/servlet/ServletRequestWrapper.java @@ -470,4 +470,40 @@ public class ServletRequestWrapper implements ServletRequest { public DispatcherType getDispatcherType() { return this.request.getDispatcherType(); } + + /** + * Gets the request ID for the wrapped request. + * + * @return the request ID for the wrapped request + * + * @since Servlet 6.0 + */ + @Override + public String getRequestId() { + return request.getRequestId(); + } + + /** + * Gets the protocol defined request ID, if any, for the wrapped request. + * + * @return the protocol defined request ID, if any, for the wrapped request + * + * @since Servlet 6.0 + */ + @Override + public String getProtocolRequestId() { + return request.getProtocolRequestId(); + } + + /** + * Gets the connection information for the wrapped request. + * + * @return the connection information for the wrapped request + * + * @since Servlet 6.0 + */ + @Override + public ServletConnection getServletConnection() { + return request.getServletConnection(); + } } diff --git a/java/org/apache/catalina/Globals.java b/java/org/apache/catalina/Globals.java index 916dd38..f04839f 100644 --- a/java/org/apache/catalina/Globals.java +++ b/java/org/apache/catalina/Globals.java @@ -51,22 +51,6 @@ public final class Globals { /** - * The request attribute used to expose the current connection ID associated - * with the request, if any. Used with multiplexing protocols such as - * HTTTP/2. - */ - public static final String CONNECTION_ID = "org.apache.coyote.connectionID"; - - - /** - * The request attribute used to expose the current stream ID associated - * with the request, if any. Used with multiplexing protocols such as - * HTTTP/2. - */ - public static final String STREAM_ID = "org.apache.coyote.streamID"; - - - /** * The request attribute that is set to {@code Boolean.TRUE} if some request * parameters have been ignored during request parameters parsing. It can * happen, for example, if there is a limit on the total count of parseable diff --git a/java/org/apache/catalina/connector/Request.java b/java/org/apache/catalina/connector/Request.java index de46807..bc02c77 100644 --- a/java/org/apache/catalina/connector/Request.java +++ b/java/org/apache/catalina/connector/Request.java @@ -38,7 +38,6 @@ import java.util.Set; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; import javax.naming.NamingException; import javax.security.auth.Subject; @@ -48,6 +47,7 @@ import jakarta.servlet.DispatcherType; import jakarta.servlet.FilterChain; import jakarta.servlet.MultipartConfigElement; import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.ServletConnection; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; import jakarta.servlet.ServletInputStream; @@ -1765,9 +1765,27 @@ public class Request implements HttpServletRequest { return this.internalDispatcherType; } - // ---------------------------------------------------- HttpRequest Methods + + @Override + public String getRequestId() { + return coyoteRequest.getRequestId(); + } + + + @Override + public String getProtocolRequestId() { + return coyoteRequest.getProtocolRequestId(); + } + @Override + public ServletConnection getServletConnection() { + return coyoteRequest.getServletConnection(); + } + + + // ---------------------------------------------------- HttpRequest Methods + /** * Add a Cookie to the set of Cookies associated with this Request. * @@ -3502,31 +3520,5 @@ public class Request implements HttpServletRequest { // NO-OP } }); - specialAttributes.put(Globals.CONNECTION_ID, - new SpecialAttributeAdapter() { - @Override - public Object get(Request request, String name) { - AtomicReference<Object> result = new AtomicReference<>(); - request.getCoyoteRequest().action(ActionCode.CONNECTION_ID, result); - return result.get(); - } - @Override - public void set(Request request, String name, Object value) { - // NO-OP - } - }); - specialAttributes.put(Globals.STREAM_ID, - new SpecialAttributeAdapter() { - @Override - public Object get(Request request, String name) { - AtomicReference<Object> result = new AtomicReference<>(); - request.getCoyoteRequest().action(ActionCode.STREAM_ID, result); - return result.get(); - } - @Override - public void set(Request request, String name, Object value) { - // NO-OP - } - }); } } diff --git a/java/org/apache/catalina/connector/RequestFacade.java b/java/org/apache/catalina/connector/RequestFacade.java index 69cad36..5696183 100644 --- a/java/org/apache/catalina/connector/RequestFacade.java +++ b/java/org/apache/catalina/connector/RequestFacade.java @@ -28,6 +28,7 @@ import java.util.Map; import jakarta.servlet.AsyncContext; import jakarta.servlet.DispatcherType; import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.ServletConnection; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; import jakarta.servlet.ServletInputStream; @@ -1120,4 +1121,22 @@ public class RequestFacade implements HttpServletRequest { public Map<String, String> getTrailerFields() { return request.getTrailerFields(); } + + + @Override + public String getRequestId() { + return request.getRequestId(); + } + + + @Override + public String getProtocolRequestId() { + return request.getProtocolRequestId(); + } + + + @Override + public ServletConnection getServletConnection() { + return request.getServletConnection(); + } } diff --git a/java/org/apache/coyote/AbstractProcessor.java b/java/org/apache/coyote/AbstractProcessor.java index 0884442..699a935 100644 --- a/java/org/apache/coyote/AbstractProcessor.java +++ b/java/org/apache/coyote/AbstractProcessor.java @@ -25,6 +25,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.ServletConnection; import org.apache.tomcat.util.ExceptionUtils; import org.apache.tomcat.util.buf.ByteChunk; @@ -630,17 +631,17 @@ public abstract class AbstractProcessor extends AbstractProcessorLight implement break; } - // Identifiers associated with multiplexing protocols like HTTP/2 - case CONNECTION_ID: { + // Identifiers + case PROTOCOL_REQUEST_ID: { @SuppressWarnings("unchecked") AtomicReference<Object> result = (AtomicReference<Object>) param; - result.set(getConnectionID()); + result.set(getProtocolRequestId()); break; } - case STREAM_ID: { + case SERVLET_CONNECTION: { @SuppressWarnings("unchecked") AtomicReference<Object> result = (AtomicReference<Object>) param; - result.set(getStreamID()); + result.set(getServletConnection()); break; } } @@ -987,27 +988,25 @@ public abstract class AbstractProcessor extends AbstractProcessorLight implement /** - * Protocols that support multiplexing (e.g. HTTP/2) should override this - * method and return the appropriate ID. + * Protocols that provide per HTTP request IDs (e.g. Stream ID for HTTP/2) + * should override this method and return the appropriate ID. * - * @return The stream ID associated with this request or {@code null} if a - * multiplexing protocol is not being used - */ - protected Object getConnectionID() { + * @return The ID associated with this request or the empty string if no + * such ID is defined + */ + protected Object getProtocolRequestId() { return null; } /** - * Protocols that support multiplexing (e.g. HTTP/2) should override this - * method and return the appropriate ID. + * Protocols must override this method and return an appropriate + * ServletConnection instance * - * @return The stream ID associated with this request or {@code null} if a - * multiplexing protocol is not being used - */ - protected Object getStreamID() { - return null; - } + * @return the ServletConnection instance associated with the current + * request. + */ + protected abstract ServletConnection getServletConnection(); /** diff --git a/java/org/apache/coyote/ActionCode.java b/java/org/apache/coyote/ActionCode.java index 69d5ad5..ff3b713 100644 --- a/java/org/apache/coyote/ActionCode.java +++ b/java/org/apache/coyote/ActionCode.java @@ -272,14 +272,15 @@ public enum ActionCode { IS_TRAILER_FIELDS_SUPPORTED, /** - * Obtain the connection identifier for the request. Used with multiplexing - * protocols such as HTTP/2. + * Obtain the request identifier for this request as defined by the protocol + * in use. Note that some protocols do not define such an identifier. E.g. + * this will be Stream ID for HTTP/2. */ - CONNECTION_ID, + PROTOCOL_REQUEST_ID, /** - * Obtain the stream identifier for the request. Used with multiplexing - * protocols such as HTTP/2. + * Obtain the servlet connection instance for the network connection + * supporting the current request. */ - STREAM_ID + SERVLET_CONNECTION } diff --git a/java/org/apache/coyote/Request.java b/java/org/apache/coyote/Request.java index e4726e5..2bbabaf 100644 --- a/java/org/apache/coyote/Request.java +++ b/java/org/apache/coyote/Request.java @@ -23,8 +23,11 @@ import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; import jakarta.servlet.ReadListener; +import jakarta.servlet.ServletConnection; import org.apache.tomcat.util.buf.B2CConverter; import org.apache.tomcat.util.buf.MessageBytes; @@ -69,6 +72,18 @@ public final class Request { // Expected maximum typical number of cookies per request. private static final int INITIAL_COOKIE_SIZE = 4; + /* + * At 100,000 requests a second there are enough IDs here for ~3,000,000 + * years before it overflows (and then we have another 3,000,000 years + * before it gets back to zero). + * + * Local testing shows that 5, 10, 50, 500 or 1000 threads can obtain + * 60,000,000+ IDs a second from a single AtomicLong. That is about about + * 17ns per request. It does not appear that the introduction of this + * counter will cause a bottleneck for request processing. + */ + private static final AtomicLong requestIdGenerator = new AtomicLong(0); + // ----------------------------------------------------------- Constructors public Request() { @@ -93,6 +108,8 @@ public final class Request { private final MessageBytes queryMB = MessageBytes.newInstance(); private final MessageBytes protoMB = MessageBytes.newInstance(); + private String requestId = Long.toString(requestIdGenerator.getAndIncrement()); + // remote address/host private final MessageBytes remoteAddrMB = MessageBytes.newInstance(); private final MessageBytes peerAddrMB = MessageBytes.newInstance(); @@ -103,7 +120,6 @@ public final class Request { private final MimeHeaders headers = new MimeHeaders(); private final Map<String,String> trailerFields = new HashMap<>(); - /** * Path parameters */ @@ -676,6 +692,25 @@ public final class Request { // -------------------- debug -------------------- + public String getRequestId() { + return requestId; + } + + + public String getProtocolRequestId() { + AtomicReference<String> ref = new AtomicReference<>(); + hook.action(ActionCode.PROTOCOL_REQUEST_ID, ref); + return ref.get(); + } + + + public ServletConnection getServletConnection() { + AtomicReference<ServletConnection> ref = new AtomicReference<>(); + hook.action(ActionCode.SERVLET_CONNECTION, ref); + return ref.get(); + } + + @Override public String toString() { return "R( " + requestURI().toString() + ")"; @@ -758,6 +793,8 @@ public final class Request { available = 0; sendfile = true; + requestId = Long.toString(requestIdGenerator.getAndIncrement()); + serverCookies.recycle(); parameters.recycle(); pathParameters.clear(); diff --git a/java/org/apache/coyote/ajp/AjpProcessor.java b/java/org/apache/coyote/ajp/AjpProcessor.java index b98f78c..9736723 100644 --- a/java/org/apache/coyote/ajp/AjpProcessor.java +++ b/java/org/apache/coyote/ajp/AjpProcessor.java @@ -33,6 +33,7 @@ import java.util.Map; import java.util.Set; import java.util.regex.Pattern; +import jakarta.servlet.ServletConnection; import jakarta.servlet.http.HttpServletResponse; import org.apache.coyote.AbstractProcessor; @@ -1289,6 +1290,12 @@ public class AjpProcessor extends AbstractProcessor { } + @Override + protected ServletConnection getServletConnection() { + return socketWrapper.getServletConnection("ajp", ""); + } + + // ------------------------------------- InputStreamInputBuffer Inner Class /** diff --git a/java/org/apache/coyote/http11/Http11Processor.java b/java/org/apache/coyote/http11/Http11Processor.java index 1886f22..4f27473 100644 --- a/java/org/apache/coyote/http11/Http11Processor.java +++ b/java/org/apache/coyote/http11/Http11Processor.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Set; import java.util.regex.Pattern; +import jakarta.servlet.ServletConnection; import jakarta.servlet.http.HttpServletResponse; import org.apache.coyote.AbstractProcessor; @@ -1118,6 +1119,12 @@ public class Http11Processor extends AbstractProcessor { } + @Override + protected ServletConnection getServletConnection() { + return socketWrapper.getServletConnection("http/1.1", ""); + } + + /* * No more input will be passed to the application. Remaining input will be * swallowed or the connection dropped depending on the error and diff --git a/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java b/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java index 19c88a1..94114a2 100644 --- a/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java +++ b/java/org/apache/coyote/http2/Http2AsyncUpgradeHandler.java @@ -49,8 +49,8 @@ public class Http2AsyncUpgradeHandler extends Http2UpgradeHandler { private final AtomicReference<IOException> applicationIOE = new AtomicReference<>(); public Http2AsyncUpgradeHandler(Http2Protocol protocol, Adapter adapter, - Request coyoteRequest) { - super(protocol, adapter, coyoteRequest); + Request coyoteRequest, SocketWrapperBase<?> socketWrapper) { + super(protocol, adapter, coyoteRequest, socketWrapper); } private final CompletionHandler<Long, Void> errorCompletion = new CompletionHandler<>() { diff --git a/java/org/apache/coyote/http2/Http2Protocol.java b/java/org/apache/coyote/http2/Http2Protocol.java index e96a945..8b7718d 100644 --- a/java/org/apache/coyote/http2/Http2Protocol.java +++ b/java/org/apache/coyote/http2/Http2Protocol.java @@ -130,8 +130,8 @@ public class Http2Protocol implements UpgradeProtocol { public InternalHttpUpgradeHandler getInternalUpgradeHandler(SocketWrapperBase<?> socketWrapper, Adapter adapter, Request coyoteRequest) { return socketWrapper.hasAsyncIO() - ? new Http2AsyncUpgradeHandler(this, adapter, coyoteRequest) - : new Http2UpgradeHandler(this, adapter, coyoteRequest); + ? new Http2AsyncUpgradeHandler(this, adapter, coyoteRequest, socketWrapper) + : new Http2UpgradeHandler(this, adapter, coyoteRequest, socketWrapper); } diff --git a/java/org/apache/coyote/http2/Http2UpgradeHandler.java b/java/org/apache/coyote/http2/Http2UpgradeHandler.java index 529b4f7..f61f921 100644 --- a/java/org/apache/coyote/http2/Http2UpgradeHandler.java +++ b/java/org/apache/coyote/http2/Http2UpgradeHandler.java @@ -34,6 +34,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import jakarta.servlet.ServletConnection; import jakarta.servlet.http.WebConnection; import org.apache.coyote.Adapter; @@ -77,7 +78,6 @@ class Http2UpgradeHandler extends AbstractStream implements InternalHttpUpgradeH protected static final Log log = LogFactory.getLog(Http2UpgradeHandler.class); protected static final StringManager sm = StringManager.getManager(Http2UpgradeHandler.class); - private static final AtomicInteger connectionIdGenerator = new AtomicInteger(0); private static final Integer STREAM_ID_ZERO = Integer.valueOf(0); protected static final int FLAG_END_OF_STREAM = 1; @@ -100,7 +100,7 @@ class Http2UpgradeHandler extends AbstractStream implements InternalHttpUpgradeH protected final Http2Protocol protocol; private final Adapter adapter; - protected volatile SocketWrapperBase<?> socketWrapper; + protected final SocketWrapperBase<?> socketWrapper; private volatile SSLSupport sslSupport; private volatile Http2Parser parser; @@ -147,11 +147,11 @@ class Http2UpgradeHandler extends AbstractStream implements InternalHttpUpgradeH private volatile int lastWindowUpdate; - Http2UpgradeHandler(Http2Protocol protocol, Adapter adapter, Request coyoteRequest) { + Http2UpgradeHandler(Http2Protocol protocol, Adapter adapter, Request coyoteRequest, SocketWrapperBase<?> socketWrapper) { super (STREAM_ID_ZERO); this.protocol = protocol; this.adapter = adapter; - this.connectionId = Integer.toString(connectionIdGenerator.getAndIncrement()); + this.socketWrapper = socketWrapper; // Defaults to -10 * the count factor. // i.e. when the connection opens, 10 'overhead' frames in a row will @@ -164,6 +164,8 @@ class Http2UpgradeHandler extends AbstractStream implements InternalHttpUpgradeH lastNonFinalDataPayload = protocol.getOverheadDataThreshold() * 2; lastWindowUpdate = protocol.getOverheadWindowUpdateThreshold() * 2; + connectionId = getServletConnection().getConnectionId(); + remoteSettings = new ConnectionSettingsRemote(connectionId); localSettings = new ConnectionSettingsLocal(connectionId); @@ -302,7 +304,7 @@ class Http2UpgradeHandler extends AbstractStream implements InternalHttpUpgradeH @Override public void setSocketWrapper(SocketWrapperBase<?> wrapper) { - this.socketWrapper = wrapper; + // NO-OP. It is passed via the constructor } @@ -1858,6 +1860,14 @@ class Http2UpgradeHandler extends AbstractStream implements InternalHttpUpgradeH } + public ServletConnection getServletConnection() { + if (socketWrapper.getSslSupport() == null) { + return socketWrapper.getServletConnection("h2c", ""); + } else { + return socketWrapper.getServletConnection("h2", ""); + } + } + protected class PingManager { protected boolean initiateDisabled = false; diff --git a/java/org/apache/coyote/http2/StreamProcessor.java b/java/org/apache/coyote/http2/StreamProcessor.java index d1ae2f9..bbaf902 100644 --- a/java/org/apache/coyote/http2/StreamProcessor.java +++ b/java/org/apache/coyote/http2/StreamProcessor.java @@ -20,6 +20,8 @@ import java.io.File; import java.io.IOException; import java.util.Iterator; +import jakarta.servlet.ServletConnection; + import org.apache.coyote.AbstractProcessor; import org.apache.coyote.ActionCode; import org.apache.coyote.Adapter; @@ -366,14 +368,8 @@ class StreamProcessor extends AbstractProcessor { @Override - protected Object getConnectionID() { - return stream.getConnectionId(); - } - - - @Override - protected Object getStreamID() { - return stream.getIdAsString().toString(); + protected String getProtocolRequestId() { + return stream.getIdAsString(); } @@ -402,6 +398,12 @@ class StreamProcessor extends AbstractProcessor { @Override + protected ServletConnection getServletConnection() { + return handler.getServletConnection(); + } + + + @Override public final void pause() { // NO-OP. Handled by the Http2UpgradeHandler } diff --git a/java/org/apache/tomcat/util/net/ServletConnectionImpl.java b/java/org/apache/tomcat/util/net/ServletConnectionImpl.java new file mode 100644 index 0000000..9b32dc7 --- /dev/null +++ b/java/org/apache/tomcat/util/net/ServletConnectionImpl.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.tomcat.util.net; + +import jakarta.servlet.ServletConnection; + + +public class ServletConnectionImpl implements ServletConnection { + + private final String connectionId; + private final String protocol; + private final String protocolConnectionId; + private final boolean secure; + + public ServletConnectionImpl(String connectionId, String protocol, String protocolConnectionId, boolean secure) { + this.connectionId = connectionId; + this.protocol = protocol; + this.protocolConnectionId = protocolConnectionId; + this.secure = secure; + } + + @Override + public String getConnectionId() { + return connectionId; + } + + @Override + public String getProtocol() { + return protocol; + } + + @Override + public String getProtocolConnectionId() { + return protocolConnectionId; + } + + @Override + public boolean isSecure() { + return secure; + } +} diff --git a/java/org/apache/tomcat/util/net/SocketWrapperBase.java b/java/org/apache/tomcat/util/net/SocketWrapperBase.java index f96ddc2..4b26219 100644 --- a/java/org/apache/tomcat/util/net/SocketWrapperBase.java +++ b/java/org/apache/tomcat/util/net/SocketWrapperBase.java @@ -29,6 +29,9 @@ import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; + +import jakarta.servlet.ServletConnection; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; @@ -41,6 +44,18 @@ public abstract class SocketWrapperBase<E> { protected static final StringManager sm = StringManager.getManager(SocketWrapperBase.class); + /* + * At 100,000 connections a second there are enough IDs here for ~3,000,000 + * years before it overflows (and then we have another 3,000,000 years + * before it gets back to zero). + * + * Local testing shows that 5 threads can obtain 60,000,000+ IDs a second + * from a single AtomicLong. That is about about 17ns per request. It does + * not appear that the introduction of this counter will cause a bottleneck + * for connection processing. + */ + private static final AtomicLong connectionIdGenerator = new AtomicLong(0); + private E socket; private final AbstractEndpoint<E,?> endpoint; @@ -56,6 +71,8 @@ public abstract class SocketWrapperBase<E> { private volatile int keepAliveLeft = 100; private String negotiatedProtocol = null; + private final String connectionId; + /* * Following cached for speed / reduced GC */ @@ -65,6 +82,7 @@ public abstract class SocketWrapperBase<E> { protected String remoteAddr = null; protected String remoteHost = null; protected int remotePort = -1; + protected volatile ServletConnection servletConnection = null; /** * Used to record the first IOException that occurs during non-blocking @@ -119,6 +137,7 @@ public abstract class SocketWrapperBase<E> { readPending = null; writePending = null; } + connectionId = Long.toString(connectionIdGenerator.getAndIncrement()); } public E getSocket() { @@ -1472,4 +1491,15 @@ public abstract class SocketWrapperBase<E> { } return false; } + + + // -------------------------------------------------------------- ID methods + + public ServletConnection getServletConnection(String protocol, String protocolConnectionId) { + if (servletConnection == null) { + servletConnection = new ServletConnectionImpl( + connectionId, protocol, protocolConnectionId, endpoint.isSSLEnabled()); + } + return servletConnection; + } } diff --git a/test/org/apache/catalina/filters/TesterHttpServletRequest.java b/test/org/apache/catalina/filters/TesterHttpServletRequest.java index e66d2e9..1e733c8 100644 --- a/test/org/apache/catalina/filters/TesterHttpServletRequest.java +++ b/test/org/apache/catalina/filters/TesterHttpServletRequest.java @@ -32,6 +32,7 @@ import java.util.Map; import jakarta.servlet.AsyncContext; import jakarta.servlet.DispatcherType; import jakarta.servlet.RequestDispatcher; +import jakarta.servlet.ServletConnection; import jakarta.servlet.ServletContext; import jakarta.servlet.ServletException; import jakarta.servlet.ServletInputStream; @@ -446,4 +447,19 @@ public class TesterHttpServletRequest implements HttpServletRequest { public Map<String, String> getTrailerFields() { throw new RuntimeException("Not implemented"); } + + @Override + public String getRequestId() { + throw new RuntimeException("Not implemented"); + } + + @Override + public String getProtocolRequestId() { + throw new RuntimeException("Not implemented"); + } + + @Override + public ServletConnection getServletConnection() { + throw new RuntimeException("Not implemented"); + } } diff --git a/test/org/apache/coyote/http2/TestAbstractStream.java b/test/org/apache/coyote/http2/TestAbstractStream.java index 9a44868..3ed0151 100644 --- a/test/org/apache/coyote/http2/TestAbstractStream.java +++ b/test/org/apache/coyote/http2/TestAbstractStream.java @@ -16,9 +16,23 @@ */ package org.apache.coyote.http2; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.CompletionHandler; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + import org.junit.Assert; import org.junit.Test; +import org.apache.tomcat.util.net.ApplicationBufferHandler; +import org.apache.tomcat.util.net.NioChannel; +import org.apache.tomcat.util.net.NioEndpoint; +import org.apache.tomcat.util.net.SSLSupport; +import org.apache.tomcat.util.net.SendfileDataBase; +import org.apache.tomcat.util.net.SendfileState; +import org.apache.tomcat.util.net.SocketWrapperBase; + /* * This tests use A=1, B=2, etc to map stream IDs to the names used in the * figures. @@ -28,7 +42,8 @@ public class TestAbstractStream { @Test public void testDependenciesFig3() { // Setup - Http2UpgradeHandler handler = new Http2UpgradeHandler(new Http2Protocol(), null, null); + Http2UpgradeHandler handler = + new Http2UpgradeHandler(new Http2Protocol(), null, null, new TesterSocketWrapper()); Stream a = new Stream(Integer.valueOf(1), handler); Stream b = new Stream(Integer.valueOf(2), handler); Stream c = new Stream(Integer.valueOf(3), handler); @@ -59,7 +74,8 @@ public class TestAbstractStream { @Test public void testDependenciesFig4() { // Setup - Http2UpgradeHandler handler = new Http2UpgradeHandler(new Http2Protocol(), null, null); + Http2UpgradeHandler handler = + new Http2UpgradeHandler(new Http2Protocol(), null, null, new TesterSocketWrapper()); Stream a = new Stream(Integer.valueOf(1), handler); Stream b = new Stream(Integer.valueOf(2), handler); Stream c = new Stream(Integer.valueOf(3), handler); @@ -90,7 +106,8 @@ public class TestAbstractStream { @Test public void testDependenciesFig5NonExclusive() { // Setup - Http2UpgradeHandler handler = new Http2UpgradeHandler(new Http2Protocol(), null, null); + Http2UpgradeHandler handler = + new Http2UpgradeHandler(new Http2Protocol(), null, null, new TesterSocketWrapper()); Stream a = new Stream(Integer.valueOf(1), handler); Stream b = new Stream(Integer.valueOf(2), handler); Stream c = new Stream(Integer.valueOf(3), handler); @@ -132,7 +149,8 @@ public class TestAbstractStream { @Test public void testDependenciesFig5Exclusive() { // Setup - Http2UpgradeHandler handler = new Http2UpgradeHandler(new Http2Protocol(), null, null); + Http2UpgradeHandler handler = + new Http2UpgradeHandler(new Http2Protocol(), null, null, new TesterSocketWrapper()); Stream a = new Stream(Integer.valueOf(1), handler); Stream b = new Stream(Integer.valueOf(2), handler); Stream c = new Stream(Integer.valueOf(3), handler); @@ -174,7 +192,8 @@ public class TestAbstractStream { @Test public void testCircular01() { // Setup - Http2UpgradeHandler handler = new Http2UpgradeHandler(new Http2Protocol(), null, null); + Http2UpgradeHandler handler = + new Http2UpgradeHandler(new Http2Protocol(), null, null, new TesterSocketWrapper()); Stream a = new Stream(Integer.valueOf(1), handler); Stream b = new Stream(Integer.valueOf(2), handler); Stream c = new Stream(Integer.valueOf(3), handler); @@ -204,7 +223,8 @@ public class TestAbstractStream { @Test public void testCircular02() { // Setup - Http2UpgradeHandler handler = new Http2UpgradeHandler(new Http2Protocol(), null, null); + Http2UpgradeHandler handler = + new Http2UpgradeHandler(new Http2Protocol(), null, null, new TesterSocketWrapper()); Stream a = new Stream(Integer.valueOf(1), handler); Stream b = new Stream(Integer.valueOf(2), handler); Stream c = new Stream(Integer.valueOf(3), handler); @@ -250,7 +270,8 @@ public class TestAbstractStream { @Test public void testCircular03() { // Setup - Http2UpgradeHandler handler = new Http2UpgradeHandler(new Http2Protocol(), null, null); + Http2UpgradeHandler handler = + new Http2UpgradeHandler(new Http2Protocol(), null, null, new TesterSocketWrapper()); Stream a = new Stream(Integer.valueOf(1), handler); Stream b = new Stream(Integer.valueOf(3), handler); Stream c = new Stream(Integer.valueOf(5), handler); @@ -283,4 +304,99 @@ public class TestAbstractStream { Assert.assertTrue(b.getChildStreams().contains(d)); Assert.assertEquals(0, d.getChildStreams().size()); } + + + private static class TesterSocketWrapper extends SocketWrapperBase<NioChannel> { + + public TesterSocketWrapper() { + super(null, new NioEndpoint()); + } + + @Override + protected void populateRemoteHost() { + } + + @Override + protected void populateRemoteAddr() { + } + + @Override + protected void populateRemotePort() { + } + + @Override + protected void populateLocalName() { + } + + @Override + protected void populateLocalAddr() { + } + + @Override + protected void populateLocalPort() { + } + + @Override + public int read(boolean block, byte[] b, int off, int len) throws IOException { + return 0; + } + + @Override + public int read(boolean block, ByteBuffer to) throws IOException { + return 0; + } + + @Override + public boolean isReadyForRead() throws IOException { + return false; + } + + @Override + public void setAppReadBufHandler(ApplicationBufferHandler handler) { + } + + @Override + protected void doClose() { + } + + @Override + protected void doWrite(boolean block, ByteBuffer from) throws IOException { + } + + @Override + public void registerReadInterest() { + } + + @Override + public void registerWriteInterest() { + } + + @Override + public SendfileDataBase createSendfileData(String filename, long pos, long length) { + return null; + } + + @Override + public SendfileState processSendfile(SendfileDataBase sendfileData) { + return null; + } + + @Override + public void doClientAuth(SSLSupport sslSupport) throws IOException { + } + + @Override + public SSLSupport getSslSupport() { + return null; + } + + @Override + protected <A> SocketWrapperBase<NioChannel>.OperationState<A> newOperationState( + boolean read, ByteBuffer[] buffers, int offset, int length, BlockingMode block, + long timeout, TimeUnit unit, A attachment, CompletionCheck check, + CompletionHandler<Long, ? super A> handler, Semaphore semaphore, + SocketWrapperBase<NioChannel>.VectoredIOCompletionHandler<A> completion) { + return null; + } + } } diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml index efe4093..6d7afcb 100644 --- a/webapps/docs/changelog.xml +++ b/webapps/docs/changelog.xml @@ -123,6 +123,10 @@ Add the current available Jakarta EE 10 schemas from the Jakarta EE schema project. (markt) </add> + <add> + Implement the new connection ID and request ID API for Servlet 6.0. + (markt) + </add> </changelog> </subsection> <subsection name="Coyote"> --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org