You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2021/09/24 17:30:36 UTC

[tomcat] branch main updated: Implement the new connection ID and request ID API for Servlet 6.0

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-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: [tomcat] branch main updated: Implement the new connection ID and request ID API for Servlet 6.0

Posted by Mark Thomas <ma...@apache.org>.
On 24/09/2021 19:03, Christopher Schultz wrote:
> Mark,
> 
> I haven't looked at the patch yet but I was thinking about this since 
> you mentioned it at ApacheCon.
> 
> Many load-balancers and other similar network systems are capable of 
> generating their own request-identifiers and sending them as request 
> headers to origin servers. I think it would make sense to allow the user 
> to either use a container-generated request identifier (as you have 
> implemented it) or to allow a (trusted) upstream component to provide 
> one to the container.
> 
> WDYT?

I don't think that covers all use cases as it requires the successful 
parsing of the incoming request.

Users are free to log multiple IDs from different sources if that makes 
sense for their environment. These IDs are primarily to fill the gap 
that there wasn't IDs from the container perspective.

Mark

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: [tomcat] branch main updated: Implement the new connection ID and request ID API for Servlet 6.0

Posted by Christopher Schultz <ch...@christopherschultz.net>.
Mark,

I haven't looked at the patch yet but I was thinking about this since 
you mentioned it at ApacheCon.

Many load-balancers and other similar network systems are capable of 
generating their own request-identifiers and sending them as request 
headers to origin servers. I think it would make sense to allow the user 
to either use a container-generated request identifier (as you have 
implemented it) or to allow a (trusted) upstream component to provide 
one to the container.

WDYT?

-chris

On 9/24/21 13:30, markt@apache.org wrote:
> 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-unsubscribe@tomcat.apache.org
> For additional commands, e-mail: dev-help@tomcat.apache.org
> 


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: [tomcat] branch main updated: Implement the new connection ID and request ID API for Servlet 6.0

Posted by Mark Thomas <ma...@apache.org>.
On 24/09/2021 18:30, markt@apache.org wrote:
> 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

Doing some simple testing locally highlighted something interesting.

The Coyote Request gets recycled twice for most requests. Once when the 
HTTP request is complete and once when the Processor is recycled just 
before the socket is added to the Poller.

I took a look to see if I could convince myself that one of these could 
be removed. The first is definitely required for pipelined requests. The 
second looks to be required for some error conditions. If anyone fancies 
a brain teaser then finding a way to have just one call to 
Request.recycle() might keep you entertained for a while ;)

Mark

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: [tomcat] branch main updated: Implement the new connection ID and request ID API for Servlet 6.0

Posted by Mark Thomas <ma...@apache.org>.
On 24/09/2021 20:27, Christopher Schultz wrote:
> Mark,
> 
> On 9/24/21 14:39, Mark Thomas wrote:
>> On 24/09/2021 19:21, Christopher Schultz wrote:
>>> Mark,
>>>
>>> Sorry for the top-post but this is quite a long patch and it will be 
>>> easier to find my comments up here.
>>
>> No problem. Makes sense.
>>
>>> When generating the String value for the request identifier, you 
>>> could use:
>>>
>>>    private String requestId = 
>>> Long.toHexString(requestIdGenerator.getAndIncrement());
>>>
>>> This would produce smaller strings, use slightly less CPU, and then 
>>> nobody cares whether the value is - or +.
>>
>> Excellent. I like it. I'll make the change. We might need those extra 
>> 3,000,000 years before failover ;)
>>
>>> Should the new request-id be generated during recycle() or during 
>>> another time? Is there a method which is always called to 
>>> re-commission a Request object? If so, I think it would make more 
>>> sense to leave the requestId alone in recycle() and allocate the new 
>>> request-id in that other method.
>>
>> Not sure. I'll take a look.
>>
>>> In cases where there are use-after-recycle errors in an application, 
>>> having the old request-id might be helpful in debugging.
>>
>> I think that could go either way. The ID changing as soon as recycle 
>> is called might make use after recycle easier to spot.
>>
>> The more I think about this, the more I think changing ID on recycle 
>> is the way to go but I am prepared to be convinced otherwise.
> 
> How about requestId = -1 on recycle (and create) but set it to a 
> definite value when it's "in service"?

Whilst I like the idea of being able to tell from the ID that the 
request is recycled, at the back of my mind are some historical bugs 
where we have two Processors (and hence 2 Requests) assigned to the same 
connection at the same time. I think always having a unique ID for the 
request is important for debugging that use case.

Your suggestion got me thinking though. It might be possible to ensure 
that once the request has been recycled (and the new ID issued) that 
subsequent calls to recycle before the start of the next request don't 
trigger an increment in the ID.

Mark

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: [tomcat] branch main updated: Implement the new connection ID and request ID API for Servlet 6.0

Posted by Christopher Schultz <ch...@christopherschultz.net>.
Mark,

On 9/24/21 14:39, Mark Thomas wrote:
> On 24/09/2021 19:21, Christopher Schultz wrote:
>> Mark,
>>
>> Sorry for the top-post but this is quite a long patch and it will be 
>> easier to find my comments up here.
> 
> No problem. Makes sense.
> 
>> When generating the String value for the request identifier, you could 
>> use:
>>
>>    private String requestId = 
>> Long.toHexString(requestIdGenerator.getAndIncrement());
>>
>> This would produce smaller strings, use slightly less CPU, and then 
>> nobody cares whether the value is - or +.
> 
> Excellent. I like it. I'll make the change. We might need those extra 
> 3,000,000 years before failover ;)
> 
>> Should the new request-id be generated during recycle() or during 
>> another time? Is there a method which is always called to 
>> re-commission a Request object? If so, I think it would make more 
>> sense to leave the requestId alone in recycle() and allocate the new 
>> request-id in that other method.
> 
> Not sure. I'll take a look.
> 
>> In cases where there are use-after-recycle errors in an application, 
>> having the old request-id might be helpful in debugging.
> 
> I think that could go either way. The ID changing as soon as recycle is 
> called might make use after recycle easier to spot.
> 
> The more I think about this, the more I think changing ID on recycle is 
> the way to go but I am prepared to be convinced otherwise.

How about requestId = -1 on recycle (and create) but set it to a 
definite value when it's "in service"?

-chris

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: [tomcat] branch main updated: Implement the new connection ID and request ID API for Servlet 6.0

Posted by Mark Thomas <ma...@apache.org>.
On 24/09/2021 19:21, Christopher Schultz wrote:
> Mark,
> 
> Sorry for the top-post but this is quite a long patch and it will be 
> easier to find my comments up here.

No problem. Makes sense.

> When generating the String value for the request identifier, you could use:
> 
>    private String requestId = 
> Long.toHexString(requestIdGenerator.getAndIncrement());
> 
> This would produce smaller strings, use slightly less CPU, and then 
> nobody cares whether the value is - or +.

Excellent. I like it. I'll make the change. We might need those extra 
3,000,000 years before failover ;)

> Should the new request-id be generated during recycle() or during 
> another time? Is there a method which is always called to re-commission 
> a Request object? If so, I think it would make more sense to leave the 
> requestId alone in recycle() and allocate the new request-id in that 
> other method.

Not sure. I'll take a look.

> In cases where there are use-after-recycle errors in an application, 
> having the old request-id might be helpful in debugging.

I think that could go either way. The ID changing as soon as recycle is 
called might make use after recycle easier to spot.

The more I think about this, the more I think changing ID on recycle is 
the way to go but I am prepared to be convinced otherwise.

Mark

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org


Re: [tomcat] branch main updated: Implement the new connection ID and request ID API for Servlet 6.0

Posted by Christopher Schultz <ch...@christopherschultz.net>.
Mark,

Sorry for the top-post but this is quite a long patch and it will be 
easier to find my comments up here.

When generating the String value for the request identifier, you could use:

   private String requestId = 
Long.toHexString(requestIdGenerator.getAndIncrement());

This would produce smaller strings, use slightly less CPU, and then 
nobody cares whether the value is - or +.

This field cannot be FINAL because request objects can be re-used. Okay.

Should the new request-id be generated during recycle() or during 
another time? Is there a method which is always called to re-commission 
a Request object? If so, I think it would make more sense to leave the 
requestId alone in recycle() and allocate the new request-id in that 
other method.

In cases where there are use-after-recycle errors in an application, 
having the old request-id might be helpful in debugging.

-chris

On 9/24/21 13:30, markt@apache.org wrote:
> 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-unsubscribe@tomcat.apache.org
> For additional commands, e-mail: dev-help@tomcat.apache.org
> 


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@tomcat.apache.org
For additional commands, e-mail: dev-help@tomcat.apache.org