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



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

Reply via email to