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