On 15/10/2020 09:46, ma...@apache.org wrote:
> This is an automated email from the ASF dual-hosted git repository.
> 
> markt pushed a commit to branch master
> in repository https://gitbox.apache.org/repos/asf/tomcat.git
> 
> commit c5e4066fc25c2b8611e476199d3361341c473257
> Author: Mark Thomas <ma...@apache.org>
> AuthorDate: Thu Oct 15 09:45:47 2020 +0100
> 
>     Complete fix for BZ 63362. Collect stats for h2, websocket and upgrade
This has more moving parts than I would like because of all the
different variations that need to be covered. There may be some
simplifications that I missed.

Mark

>     
>     https://bz.apache.org/bugzilla/show_bug.cgi?id=63362
> ---
>  java/org/apache/catalina/connector/Request.java    |   2 +-
>  java/org/apache/coyote/AbstractProtocol.java       |  17 ---
>  java/org/apache/coyote/LocalStrings.properties     |   1 -
>  java/org/apache/coyote/UpgradeProtocol.java        |  22 ----
>  java/org/apache/coyote/UpgradeToken.java           |   9 +-
>  .../coyote/http11/AbstractHttp11Protocol.java      |  97 ++++++++++++++++-
>  java/org/apache/coyote/http11/Http11Processor.java |   2 +-
>  .../apache/coyote/http11/LocalStrings.properties   |   2 +
>  .../http11/upgrade/InternalHttpUpgradeHandler.java |   4 +
>  .../coyote/http11/upgrade/UpgradeGroupInfo.java    | 120 
> +++++++++++++++++++++
>  .../apache/coyote/http11/upgrade/UpgradeInfo.java  |  96 +++++++++++++++++
>  .../http11/upgrade/UpgradeProcessorExternal.java   |  14 ++-
>  .../http11/upgrade/UpgradeProcessorInternal.java   |  12 ++-
>  .../http11/upgrade/UpgradeServletInputStream.java  |  17 ++-
>  .../http11/upgrade/UpgradeServletOutputStream.java |   7 +-
>  java/org/apache/coyote/http2/Http2Protocol.java    |  49 ++++-----
>  .../apache/coyote/http2/LocalStrings.properties    |   2 +
>  java/org/apache/coyote/http2/StreamProcessor.java  |   6 +-
>  java/org/apache/coyote/mbeans-descriptors.xml      |  30 ++++++
>  java/org/apache/tomcat/websocket/WsFrameBase.java  |  13 +++
>  .../tomcat/websocket/WsRemoteEndpointImplBase.java |  15 +++
>  .../tomcat/websocket/server/WsFrameServer.java     |  14 ++-
>  .../websocket/server/WsHttpUpgradeHandler.java     |  12 ++-
>  .../server/WsRemoteEndpointImplServer.java         |  12 ++-
>  .../apache/coyote/http11/upgrade/TestUpgrade.java  |   4 +
>  webapps/docs/changelog.xml                         |   4 +-
>  26 files changed, 489 insertions(+), 94 deletions(-)
> 
> diff --git a/java/org/apache/catalina/connector/Request.java 
> b/java/org/apache/catalina/connector/Request.java
> index 48e0167..2177a92 100644
> --- a/java/org/apache/catalina/connector/Request.java
> +++ b/java/org/apache/catalina/connector/Request.java
> @@ -2017,7 +2017,7 @@ public class Request implements HttpServletRequest {
>              throw new ServletException(e);
>          }
>          UpgradeToken upgradeToken = new UpgradeToken(handler,
> -                getContext(), instanceManager);
> +                getContext(), instanceManager, 
> response.getHeader("upgrade"));
>  
>          coyoteRequest.action(ActionCode.UPGRADE, upgradeToken);
>  
> diff --git a/java/org/apache/coyote/AbstractProtocol.java 
> b/java/org/apache/coyote/AbstractProtocol.java
> index 159531e..e3ce9d7 100644
> --- a/java/org/apache/coyote/AbstractProtocol.java
> +++ b/java/org/apache/coyote/AbstractProtocol.java
> @@ -559,13 +559,6 @@ public abstract class AbstractProtocol<S> implements 
> ProtocolHandler,
>          endpoint.setDomain(domain);
>  
>          endpoint.init();
> -
> -        UpgradeProtocol[] upgradeProtocols = findUpgradeProtocols();
> -        for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
> -            // Implementation note: Failure of one upgrade protocol fails the
> -            // whole Connector
> -            upgradeProtocol.init();
> -        }
>      }
>  
>  
> @@ -679,16 +672,6 @@ public abstract class AbstractProtocol<S> implements 
> ProtocolHandler,
>              logPortOffset();
>          }
>  
> -        UpgradeProtocol[] upgradeProtocols = findUpgradeProtocols();
> -        for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
> -            try {
> -                upgradeProtocol.destroy();
> -            } catch (Throwable t) {
> -                ExceptionUtils.handleThrowable(t);
> -                
> getLog().error(sm.getString("abstractProtocol.upgradeProtocolDestroyError"), 
> t);
> -            }
> -        }
> -
>          try {
>              endpoint.destroy();
>          } finally {
> diff --git a/java/org/apache/coyote/LocalStrings.properties 
> b/java/org/apache/coyote/LocalStrings.properties
> index 43c8d64..83960cb 100644
> --- a/java/org/apache/coyote/LocalStrings.properties
> +++ b/java/org/apache/coyote/LocalStrings.properties
> @@ -36,7 +36,6 @@ abstractProcessor.socket.ssl=Exception getting SSL 
> attributes
>  abstractProtocol.mbeanDeregistrationFailed=Failed to deregister MBean named 
> [{0}] from MBean server [{1}]
>  abstractProtocol.processorRegisterError=Error registering request processor
>  abstractProtocol.processorUnregisterError=Error unregistering request 
> processor
> -abstractProtocol.upgradeProtocolDestroyError=Error destroying upgrade 
> protocol
>  abstractProtocol.waitingProcessor.add=Added processor [{0}] to waiting 
> processors
>  abstractProtocol.waitingProcessor.remove=Removed processor [{0}] from 
> waiting processors
>  
> diff --git a/java/org/apache/coyote/UpgradeProtocol.java 
> b/java/org/apache/coyote/UpgradeProtocol.java
> index cd2767b..dc840df 100644
> --- a/java/org/apache/coyote/UpgradeProtocol.java
> +++ b/java/org/apache/coyote/UpgradeProtocol.java
> @@ -107,26 +107,4 @@ public interface UpgradeProtocol {
>      public default void setHttp11Protocol(AbstractProtocol<?> protocol) {
>          // NO-OP
>      }
> -
> -
> -    /**
> -     * Initialise the upgrade protocol. Called once the parent HTTP/1.1 
> protocol
> -     * has initialised.
> -     *
> -     * @throws Exception If initialisation fails
> -     */
> -    public default void init() throws Exception {
> -        // NO-OP
> -    }
> -
> -
> -    /**
> -     * Destroy the upgrade protocol. Called before the parent HTTP/1.1 
> protocol
> -     * is destroyed.
> -     *
> -     * @throws Exception If the upgrade protocol is not destroyed cleanly
> -     */
> -    public default void destroy() throws Exception {
> -        // NO-OP
> -    }
>  }
> diff --git a/java/org/apache/coyote/UpgradeToken.java 
> b/java/org/apache/coyote/UpgradeToken.java
> index 729f48a..0506f78 100644
> --- a/java/org/apache/coyote/UpgradeToken.java
> +++ b/java/org/apache/coyote/UpgradeToken.java
> @@ -30,12 +30,14 @@ public final class UpgradeToken {
>      private final ContextBind contextBind;
>      private final HttpUpgradeHandler httpUpgradeHandler;
>      private final InstanceManager instanceManager;
> +    private final String protocol;
>  
> -    public UpgradeToken(HttpUpgradeHandler httpUpgradeHandler,
> -            ContextBind contextBind, InstanceManager instanceManager) {
> +    public UpgradeToken(HttpUpgradeHandler httpUpgradeHandler, ContextBind 
> contextBind, InstanceManager instanceManager,
> +            String protocol) {
>          this.contextBind = contextBind;
>          this.httpUpgradeHandler = httpUpgradeHandler;
>          this.instanceManager = instanceManager;
> +        this.protocol = protocol;
>      }
>  
>      public final ContextBind getContextBind() {
> @@ -50,4 +52,7 @@ public final class UpgradeToken {
>          return instanceManager;
>      }
>  
> +    public final String getProtocol() {
> +        return protocol;
> +    }
>  }
> diff --git a/java/org/apache/coyote/http11/AbstractHttp11Protocol.java 
> b/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
> index 561ae8e..95b6028 100644
> --- a/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
> +++ b/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
> @@ -27,6 +27,10 @@ import java.util.Set;
>  import java.util.concurrent.ConcurrentHashMap;
>  import java.util.regex.Pattern;
>  
> +import javax.management.ObjectInstance;
> +import javax.management.ObjectName;
> +
> +import jakarta.servlet.http.HttpServletRequest;
>  import jakarta.servlet.http.HttpUpgradeHandler;
>  
>  import org.apache.coyote.AbstractProtocol;
> @@ -38,9 +42,12 @@ import org.apache.coyote.Response;
>  import org.apache.coyote.UpgradeProtocol;
>  import org.apache.coyote.UpgradeToken;
>  import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler;
> +import org.apache.coyote.http11.upgrade.UpgradeGroupInfo;
>  import org.apache.coyote.http11.upgrade.UpgradeProcessorExternal;
>  import org.apache.coyote.http11.upgrade.UpgradeProcessorInternal;
>  import org.apache.tomcat.util.buf.StringUtils;
> +import org.apache.tomcat.util.modeler.Registry;
> +import org.apache.tomcat.util.modeler.Util;
>  import org.apache.tomcat.util.net.AbstractEndpoint;
>  import org.apache.tomcat.util.net.SSLHostConfig;
>  import org.apache.tomcat.util.net.SocketWrapperBase;
> @@ -73,6 +80,31 @@ public abstract class AbstractHttp11Protocol<S> extends 
> AbstractProtocol<S> {
>          }
>  
>          super.init();
> +
> +        // Set the Http11Protocol (i.e. this) for any upgrade protocols once
> +        // this has completed initialisation as the upgrade protocols may 
> expect this
> +        // to be initialised when the call is made
> +        for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
> +            upgradeProtocol.setHttp11Protocol(this);
> +        }
> +    }
> +
> +
> +    @Override
> +    public void destroy() throws Exception {
> +        // There may be upgrade protocols with their own MBeans. These need 
> to
> +        // be de-registered.
> +        ObjectName rgOname = getGlobalRequestProcessorMBeanName();
> +        if (rgOname != null) {
> +            Registry registry = Registry.getRegistry(null, null);
> +            ObjectName query = new ObjectName(rgOname.getCanonicalName() + 
> ",Upgrade=*");
> +            Set<ObjectInstance> upgrades = 
> registry.getMBeanServer().queryMBeans(query, null);
> +            for (ObjectInstance upgrade : upgrades) {
> +                registry.unregisterComponent(upgrade.getObjectName());
> +            }
> +        }
> +
> +        super.destroy();
>      }
>  
>  
> @@ -502,8 +534,6 @@ public abstract class AbstractHttp11Protocol<S> extends 
> AbstractProtocol<S> {
>                  }
>              }
>          }
> -
> -        upgradeProtocol.setHttp11Protocol(this);
>      }
>      @Override
>      public UpgradeProtocol getNegotiatedProtocol(String negotiatedName) {
> @@ -515,6 +545,65 @@ public abstract class AbstractHttp11Protocol<S> extends 
> AbstractProtocol<S> {
>      }
>  
>  
> +    /**
> +     * Map of upgrade protocol name to {@link UpgradeGroupInfo} instance.
> +     * <p>
> +     * HTTP upgrades via {@link HttpServletRequest#upgrade(Class)} do not 
> have
> +     * to depend on an {@code UpgradeProtocol}. To enable basic statistics 
> to be
> +     * made available for these protocols, a map of protocol name to
> +     * {@link UpgradeGroupInfo} instances is maintained here.
> +     */
> +    private final Map<String,UpgradeGroupInfo> upgradeProtocolGroupInfos = 
> new ConcurrentHashMap<>();
> +    public UpgradeGroupInfo getUpgradeGroupInfo(String upgradeProtocol) {
> +        if (upgradeProtocol == null) {
> +            return null;
> +        }
> +        UpgradeGroupInfo result = 
> upgradeProtocolGroupInfos.get(upgradeProtocol);
> +        if (result == null) {
> +            // Protecting against multiple JMX registration, not modification
> +            // of the Map.
> +            synchronized (upgradeProtocolGroupInfos) {
> +                result = upgradeProtocolGroupInfos.get(upgradeProtocol);
> +                if (result == null) {
> +                    result = new UpgradeGroupInfo();
> +                    upgradeProtocolGroupInfos.put(upgradeProtocol, result);
> +                    ObjectName oname = getONameForUpgrade(upgradeProtocol);
> +                    if (oname != null) {
> +                        try {
> +                            Registry.getRegistry(null, 
> null).registerComponent(result, oname, null);
> +                        } catch (Exception e) {
> +                            
> getLog().warn(sm.getString("abstractHttp11Protocol.upgradeJmxRegistrationFail"),
>  e);
> +                            result = null;
> +                        }
> +                    }
> +                }
> +            }
> +        }
> +        return result;
> +    }
> +
> +
> +    public ObjectName getONameForUpgrade(String upgradeProtocol) {
> +        ObjectName oname = null;
> +        ObjectName parentRgOname = getGlobalRequestProcessorMBeanName();
> +        if (parentRgOname != null) {
> +            StringBuilder name = new 
> StringBuilder(parentRgOname.getCanonicalName());
> +            name.append(",Upgrade=");
> +            if (Util.objectNameValueNeedsQuote(upgradeProtocol)) {
> +                name.append(ObjectName.quote(upgradeProtocol));
> +            } else {
> +                name.append(upgradeProtocol);
> +            }
> +            try {
> +                oname = new ObjectName(name.toString());
> +            } catch (Exception e) {
> +                
> getLog().warn(sm.getString("abstractHttp11Protocol.upgradeJmxNameFail"), e);
> +            }
> +        }
> +        return oname;
> +    }
> +
> +
>      // ------------------------------------------------ HTTP specific 
> properties
>      // ------------------------------------------ passed through to the 
> EndPoint
>  
> @@ -596,9 +685,9 @@ public abstract class AbstractHttp11Protocol<S> extends 
> AbstractProtocol<S> {
>              UpgradeToken upgradeToken) {
>          HttpUpgradeHandler httpUpgradeHandler = 
> upgradeToken.getHttpUpgradeHandler();
>          if (httpUpgradeHandler instanceof InternalHttpUpgradeHandler) {
> -            return new UpgradeProcessorInternal(socket, upgradeToken);
> +            return new UpgradeProcessorInternal(socket, upgradeToken, 
> getUpgradeGroupInfo(upgradeToken.getProtocol()));
>          } else {
> -            return new UpgradeProcessorExternal(socket, upgradeToken);
> +            return new UpgradeProcessorExternal(socket, upgradeToken, 
> getUpgradeGroupInfo(upgradeToken.getProtocol()));
>          }
>      }
>  }
> diff --git a/java/org/apache/coyote/http11/Http11Processor.java 
> b/java/org/apache/coyote/http11/Http11Processor.java
> index 22f8787..57fd00a 100644
> --- a/java/org/apache/coyote/http11/Http11Processor.java
> +++ b/java/org/apache/coyote/http11/Http11Processor.java
> @@ -336,7 +336,7 @@ public class Http11Processor extends AbstractProcessor {
>                          InternalHttpUpgradeHandler upgradeHandler =
>                                  upgradeProtocol.getInternalUpgradeHandler(
>                                          socketWrapper, getAdapter(), 
> cloneRequest(request));
> -                        UpgradeToken upgradeToken = new 
> UpgradeToken(upgradeHandler, null, null);
> +                        UpgradeToken upgradeToken = new 
> UpgradeToken(upgradeHandler, null, null, requestedProtocol);
>                          action(ActionCode.UPGRADE, upgradeToken);
>                          return SocketState.UPGRADING;
>                      }
> diff --git a/java/org/apache/coyote/http11/LocalStrings.properties 
> b/java/org/apache/coyote/http11/LocalStrings.properties
> index 724a99b..d880c71 100644
> --- a/java/org/apache/coyote/http11/LocalStrings.properties
> +++ b/java/org/apache/coyote/http11/LocalStrings.properties
> @@ -16,6 +16,8 @@
>  abstractHttp11Protocol.alpnConfigured=The [{0}] connector has been 
> configured to support negotiation to [{1}] via ALPN
>  abstractHttp11Protocol.alpnWithNoAlpn=The upgrade handler [{0}] for [{1}] 
> only supports upgrade via ALPN but has been configured for the [{2}] 
> connector that does not support ALPN.
>  abstractHttp11Protocol.httpUpgradeConfigured=The [{0}] connector has been 
> configured to support HTTP upgrade to [{1}]
> +abstractHttp11Protocol.upgradeJmxNameFail=Failed to create ObjectName with 
> which to register upgrade protocol in JMX
> +abstractHttp11Protocol.upgradeJmxRegistrationFail=Failed to register upgrade 
> protocol in JMX
>  
>  http11processor.fallToDebug=\n\
>  \ Note: further occurrences of HTTP request parsing errors will be logged at 
> DEBUG level.
> diff --git 
> a/java/org/apache/coyote/http11/upgrade/InternalHttpUpgradeHandler.java 
> b/java/org/apache/coyote/http11/upgrade/InternalHttpUpgradeHandler.java
> index c852e5b..3151d95 100644
> --- a/java/org/apache/coyote/http11/upgrade/InternalHttpUpgradeHandler.java
> +++ b/java/org/apache/coyote/http11/upgrade/InternalHttpUpgradeHandler.java
> @@ -43,4 +43,8 @@ public interface InternalHttpUpgradeHandler extends 
> HttpUpgradeHandler {
>      default boolean hasAsyncIO() {
>          return false;
>      }
> +
> +    default UpgradeInfo getUpgradeInfo() {
> +        return null;
> +    }
>  }
> \ No newline at end of file
> diff --git a/java/org/apache/coyote/http11/upgrade/UpgradeGroupInfo.java 
> b/java/org/apache/coyote/http11/upgrade/UpgradeGroupInfo.java
> new file mode 100644
> index 0000000..7d72976
> --- /dev/null
> +++ b/java/org/apache/coyote/http11/upgrade/UpgradeGroupInfo.java
> @@ -0,0 +1,120 @@
> +/*
> + *  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.coyote.http11.upgrade;
> +
> +import java.util.ArrayList;
> +import java.util.List;
> +
> +import org.apache.tomcat.util.modeler.BaseModelMBean;
> +
> +/**
> + *  This aggregates the data collected from each UpgradeInfo instance.
> + */
> +public class UpgradeGroupInfo extends BaseModelMBean {
> +
> +    private final List<UpgradeInfo> upgradeInfos = new ArrayList<>();
> +
> +    private long deadBytesReceived = 0;
> +    private long deadBytesSent = 0;
> +    private long deadMsgsReceived = 0;
> +    private long deadMsgsSent = 0;
> +
> +
> +    public synchronized void addUpgradeInfo(UpgradeInfo ui) {
> +        upgradeInfos.add(ui);
> +    }
> +
> +
> +    public synchronized void removeUpgradeInfo(UpgradeInfo ui) {
> +        if (ui != null) {
> +            deadBytesReceived += ui.getBytesReceived();
> +            deadBytesSent += ui.getBytesSent();
> +            deadMsgsReceived += ui.getMsgsReceived();
> +            deadMsgsSent += ui.getMsgsSent();
> +
> +            upgradeInfos.remove(ui);
> +        }
> +    }
> +
> +
> +    public synchronized long getBytesReceived() {
> +        long bytes = deadBytesReceived;
> +        for (UpgradeInfo ui : upgradeInfos) {
> +            bytes += ui.getBytesReceived();
> +        }
> +        return bytes;
> +    }
> +    public synchronized void setBytesReceived(long bytesReceived) {
> +        deadBytesReceived = bytesReceived;
> +        for (UpgradeInfo ui : upgradeInfos) {
> +            ui.setBytesReceived(bytesReceived);
> +        }
> +    }
> +
> +
> +    public synchronized long getBytesSent() {
> +        long bytes = deadBytesSent;
> +        for (UpgradeInfo ui : upgradeInfos) {
> +            bytes += ui.getBytesSent();
> +        }
> +        return bytes;
> +    }
> +    public synchronized void setBytesSent(long bytesSent) {
> +        deadBytesSent = bytesSent;
> +        for (UpgradeInfo ui : upgradeInfos) {
> +            ui.setBytesSent(bytesSent);
> +        }
> +    }
> +
> +
> +    public synchronized long getMsgsReceived() {
> +        long msgs = deadMsgsReceived;
> +        for (UpgradeInfo ui : upgradeInfos) {
> +            msgs += ui.getMsgsReceived();
> +        }
> +        return msgs;
> +    }
> +    public synchronized void setMsgsReceived(long msgsReceived) {
> +        deadMsgsReceived = msgsReceived;
> +        for (UpgradeInfo ui : upgradeInfos) {
> +            ui.setMsgsReceived(msgsReceived);
> +        }
> +    }
> +
> +
> +    public synchronized long getMsgsSent() {
> +        long msgs = deadMsgsSent;
> +        for (UpgradeInfo ui : upgradeInfos) {
> +            msgs += ui.getMsgsSent();
> +        }
> +        return msgs;
> +    }
> +    public synchronized void setMsgsSent(long msgsSent) {
> +        deadMsgsSent = msgsSent;
> +        for (UpgradeInfo ui : upgradeInfos) {
> +            ui.setMsgsSent(msgsSent);
> +        }
> +    }
> +
> +
> +    public void resetCounters() {
> +        setBytesReceived(0);
> +        setBytesSent(0);
> +        setMsgsReceived(0);
> +        setMsgsSent(0);
> +    }
> +}
> diff --git a/java/org/apache/coyote/http11/upgrade/UpgradeInfo.java 
> b/java/org/apache/coyote/http11/upgrade/UpgradeInfo.java
> new file mode 100644
> index 0000000..eb3313c
> --- /dev/null
> +++ b/java/org/apache/coyote/http11/upgrade/UpgradeInfo.java
> @@ -0,0 +1,96 @@
> +/*
> + *  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.coyote.http11.upgrade;
> +
> +/**
> + * Structure to hold statistical information about connections that have been
> + * established using the HTTP/1.1 upgrade mechanism. Bytes sent/received will
> + * always be populated. Messages sent/received will be populated if that 
> makes
> + * sense for the protocol and the information is exposed by the protocol
> + * implementation.
> + */
> +public class UpgradeInfo  {
> +
> +    private UpgradeGroupInfo groupInfo = null;
> +    private volatile long bytesSent = 0;
> +    private volatile long bytesReceived = 0;
> +    private volatile long msgsSent = 0;
> +    private volatile long msgsReceived = 0;
> +
> +
> +
> +    public UpgradeGroupInfo getGlobalProcessor() {
> +        return groupInfo;
> +    }
> +
> +
> +    public void setGroupInfo(UpgradeGroupInfo groupInfo) {
> +        if (groupInfo == null) {
> +            if (this.groupInfo != null) {
> +                this.groupInfo.removeUpgradeInfo(this);
> +                this.groupInfo = null;
> +            }
> +        } else {
> +            this.groupInfo = groupInfo;
> +            groupInfo.addUpgradeInfo(this);
> +        }
> +    }
> +
> +
> +    public long getBytesSent() {
> +        return bytesSent;
> +    }
> +    public void setBytesSent(long bytesSent) {
> +        this.bytesSent = bytesSent;
> +    }
> +    public void addBytesSent(long bytesSent) {
> +        this.bytesSent += bytesSent;
> +    }
> +
> +
> +    public long getBytesReceived() {
> +        return bytesReceived;
> +    }
> +    public void setBytesReceived(long bytesReceived) {
> +        this.bytesReceived = bytesReceived;
> +    }
> +    public void addBytesReceived(long bytesReceived) {
> +        this.bytesReceived += bytesReceived;
> +    }
> +
> +
> +    public long getMsgsSent() {
> +        return msgsSent;
> +    }
> +    public void setMsgsSent(long msgsSent) {
> +        this.msgsSent = msgsSent;
> +    }
> +    public void addMsgsSent(long msgsSent) {
> +        this.msgsSent += msgsSent;
> +    }
> +
> +
> +    public long getMsgsReceived() {
> +        return msgsReceived;
> +    }
> +    public void setMsgsReceived(long msgsReceived) {
> +        this.msgsReceived = msgsReceived;
> +    }
> +    public void addMsgsReceived(long msgsReceived) {
> +        this.msgsReceived += msgsReceived;
> +    }
> +}
> diff --git 
> a/java/org/apache/coyote/http11/upgrade/UpgradeProcessorExternal.java 
> b/java/org/apache/coyote/http11/upgrade/UpgradeProcessorExternal.java
> index 11eb465..dbf744e 100644
> --- a/java/org/apache/coyote/http11/upgrade/UpgradeProcessorExternal.java
> +++ b/java/org/apache/coyote/http11/upgrade/UpgradeProcessorExternal.java
> @@ -37,13 +37,15 @@ public class UpgradeProcessorExternal extends 
> UpgradeProcessorBase {
>  
>      private final UpgradeServletInputStream upgradeServletInputStream;
>      private final UpgradeServletOutputStream upgradeServletOutputStream;
> +    private final UpgradeInfo upgradeInfo;
>  
> -
> -    public UpgradeProcessorExternal(SocketWrapperBase<?> wrapper,
> -            UpgradeToken upgradeToken) {
> +    public UpgradeProcessorExternal(SocketWrapperBase<?> wrapper, 
> UpgradeToken upgradeToken,
> +            UpgradeGroupInfo upgradeGroupInfo) {
>          super(upgradeToken);
> -        this.upgradeServletInputStream = new UpgradeServletInputStream(this, 
> wrapper);
> -        this.upgradeServletOutputStream = new 
> UpgradeServletOutputStream(this, wrapper);
> +        this.upgradeInfo = new UpgradeInfo();
> +        upgradeGroupInfo.addUpgradeInfo(upgradeInfo);
> +        this.upgradeServletInputStream = new UpgradeServletInputStream(this, 
> wrapper, upgradeInfo);
> +        this.upgradeServletOutputStream = new 
> UpgradeServletOutputStream(this, wrapper, upgradeInfo);
>  
>          /*
>           * Leave timeouts in the hands of the upgraded protocol.
> @@ -65,6 +67,8 @@ public class UpgradeProcessorExternal extends 
> UpgradeProcessorBase {
>      public void close() throws Exception {
>          upgradeServletInputStream.close();
>          upgradeServletOutputStream.close();
> +        // Triggers update of stats from UpgradeInfo to UpgradeGroupInfo
> +        upgradeInfo.setGroupInfo(null);
>      }
>  
>  
> diff --git 
> a/java/org/apache/coyote/http11/upgrade/UpgradeProcessorInternal.java 
> b/java/org/apache/coyote/http11/upgrade/UpgradeProcessorInternal.java
> index 99fbdb7..ddb5759 100644
> --- a/java/org/apache/coyote/http11/upgrade/UpgradeProcessorInternal.java
> +++ b/java/org/apache/coyote/http11/upgrade/UpgradeProcessorInternal.java
> @@ -35,8 +35,8 @@ public class UpgradeProcessorInternal extends 
> UpgradeProcessorBase {
>  
>      private final InternalHttpUpgradeHandler internalHttpUpgradeHandler;
>  
> -    public UpgradeProcessorInternal(SocketWrapperBase<?> wrapper,
> -            UpgradeToken upgradeToken) {
> +    public UpgradeProcessorInternal(SocketWrapperBase<?> wrapper, 
> UpgradeToken upgradeToken,
> +            UpgradeGroupInfo upgradeGroupInfo) {
>          super(upgradeToken);
>          this.internalHttpUpgradeHandler = (InternalHttpUpgradeHandler) 
> upgradeToken.getHttpUpgradeHandler();
>          /*
> @@ -46,6 +46,10 @@ public class UpgradeProcessorInternal extends 
> UpgradeProcessorBase {
>          wrapper.setWriteTimeout(INFINITE_TIMEOUT);
>  
>          internalHttpUpgradeHandler.setSocketWrapper(wrapper);
> +        UpgradeInfo upgradeInfo = 
> internalHttpUpgradeHandler.getUpgradeInfo();
> +        if (upgradeInfo != null && upgradeGroupInfo != null) {
> +            upgradeInfo.setGroupInfo(upgradeGroupInfo);
> +        }
>      }
>  
>  
> @@ -88,6 +92,10 @@ public class UpgradeProcessorInternal extends 
> UpgradeProcessorBase {
>  
>      @Override
>      public void close() throws Exception {
> +        UpgradeInfo upgradeInfo = 
> internalHttpUpgradeHandler.getUpgradeInfo();
> +        if (upgradeInfo != null) {
> +            upgradeInfo.setGroupInfo(null);
> +        }
>          internalHttpUpgradeHandler.destroy();
>      }
>  
> diff --git 
> a/java/org/apache/coyote/http11/upgrade/UpgradeServletInputStream.java 
> b/java/org/apache/coyote/http11/upgrade/UpgradeServletInputStream.java
> index 10b5527..b3b7fb5 100644
> --- a/java/org/apache/coyote/http11/upgrade/UpgradeServletInputStream.java
> +++ b/java/org/apache/coyote/http11/upgrade/UpgradeServletInputStream.java
> @@ -37,6 +37,7 @@ public class UpgradeServletInputStream extends 
> ServletInputStream {
>  
>      private final UpgradeProcessorBase processor;
>      private final SocketWrapperBase<?> socketWrapper;
> +    private final UpgradeInfo upgradeInfo;
>  
>      private volatile boolean closed = false;
>      private volatile boolean eof = false;
> @@ -45,10 +46,11 @@ public class UpgradeServletInputStream extends 
> ServletInputStream {
>      private volatile ReadListener listener = null;
>  
>  
> -    public UpgradeServletInputStream(UpgradeProcessorBase processor,
> -            SocketWrapperBase<?> socketWrapper) {
> +    public UpgradeServletInputStream(UpgradeProcessorBase processor, 
> SocketWrapperBase<?> socketWrapper,
> +            UpgradeInfo upgradeInfo) {
>          this.processor = processor;
>          this.socketWrapper = socketWrapper;
> +        this.upgradeInfo = upgradeInfo;
>      }
>  
>  
> @@ -139,7 +141,13 @@ public class UpgradeServletInputStream extends 
> ServletInputStream {
>                  break;
>              }
>          }
> -        return count > 0 ? count : -1;
> +
> +        if (count > 0) {
> +            upgradeInfo.addBytesReceived(count);
> +            return count;
> +        } else {
> +            return -1;
> +        }
>      }
>  
>  
> @@ -151,6 +159,8 @@ public class UpgradeServletInputStream extends 
> ServletInputStream {
>              int result = socketWrapper.read(listener == null, b, off, len);
>              if (result == -1) {
>                  eof = true;
> +            } else {
> +                upgradeInfo.addBytesReceived(result);
>              }
>              return result;
>          } catch (IOException ioe) {
> @@ -197,6 +207,7 @@ public class UpgradeServletInputStream extends 
> ServletInputStream {
>              eof = true;
>              return -1;
>          } else {
> +            upgradeInfo.addBytesReceived(1);
>              return b[0] & 0xFF;
>          }
>      }
> diff --git 
> a/java/org/apache/coyote/http11/upgrade/UpgradeServletOutputStream.java 
> b/java/org/apache/coyote/http11/upgrade/UpgradeServletOutputStream.java
> index c178e7e..c9dff36 100644
> --- a/java/org/apache/coyote/http11/upgrade/UpgradeServletOutputStream.java
> +++ b/java/org/apache/coyote/http11/upgrade/UpgradeServletOutputStream.java
> @@ -37,6 +37,7 @@ public class UpgradeServletOutputStream extends 
> ServletOutputStream {
>  
>      private final UpgradeProcessorBase processor;
>      private final SocketWrapperBase<?> socketWrapper;
> +    private final UpgradeInfo upgradeInfo;
>  
>      // Used to ensure that isReady() and onWritePossible() have a consistent
>      // view of buffer and registered.
> @@ -61,10 +62,11 @@ public class UpgradeServletOutputStream extends 
> ServletOutputStream {
>  
>  
>  
> -    public UpgradeServletOutputStream(UpgradeProcessorBase processor,
> -            SocketWrapperBase<?> socketWrapper) {
> +    public UpgradeServletOutputStream(UpgradeProcessorBase processor, 
> SocketWrapperBase<?> socketWrapper,
> +            UpgradeInfo upgradeInfo) {
>          this.processor = processor;
>          this.socketWrapper = socketWrapper;
> +        this.upgradeInfo = upgradeInfo;
>      }
>  
>  
> @@ -210,6 +212,7 @@ public class UpgradeServletOutputStream extends 
> ServletOutputStream {
>          } else {
>              socketWrapper.write(false, b, off, len);
>          }
> +        upgradeInfo.addBytesSent(len);
>      }
>  
>  
> diff --git a/java/org/apache/coyote/http2/Http2Protocol.java 
> b/java/org/apache/coyote/http2/Http2Protocol.java
> index 1be95d9..f2a97b5 100644
> --- a/java/org/apache/coyote/http2/Http2Protocol.java
> +++ b/java/org/apache/coyote/http2/Http2Protocol.java
> @@ -33,11 +33,17 @@ import org.apache.coyote.UpgradeToken;
>  import org.apache.coyote.http11.AbstractHttp11Protocol;
>  import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler;
>  import org.apache.coyote.http11.upgrade.UpgradeProcessorInternal;
> +import org.apache.juli.logging.Log;
> +import org.apache.juli.logging.LogFactory;
>  import org.apache.tomcat.util.modeler.Registry;
>  import org.apache.tomcat.util.net.SocketWrapperBase;
> +import org.apache.tomcat.util.res.StringManager;
>  
>  public class Http2Protocol implements UpgradeProtocol {
>  
> +    private static final Log log = LogFactory.getLog(Http2Protocol.class);
> +    private static final StringManager sm = 
> StringManager.getManager(Http2Protocol.class);
> +
>      static final long DEFAULT_READ_TIMEOUT = 5000;
>      static final long DEFAULT_WRITE_TIMEOUT = 5000;
>      static final long DEFAULT_KEEP_ALIVE_TIMEOUT = 20000;
> @@ -86,7 +92,6 @@ public class Http2Protocol implements UpgradeProtocol {
>      private AbstractHttp11Protocol<?> http11Protocol = null;
>  
>      private RequestGroupInfo global = new RequestGroupInfo();
> -    private ObjectName rgOname = null;
>  
>      @Override
>      public String getHttpUpgradeName(boolean isSSLEnabled) {
> @@ -109,8 +114,10 @@ public class Http2Protocol implements UpgradeProtocol {
>  
>      @Override
>      public Processor getProcessor(SocketWrapperBase<?> socketWrapper, 
> Adapter adapter) {
> +        String upgradeProtocol = getUpgradeProtocolName();
>          UpgradeProcessorInternal processor = new 
> UpgradeProcessorInternal(socketWrapper,
> -                new UpgradeToken(getInternalUpgradeHandler(socketWrapper, 
> adapter, null), null, null));
> +                new UpgradeToken(getInternalUpgradeHandler(socketWrapper, 
> adapter, null), null, null, upgradeProtocol),
> +                http11Protocol.getUpgradeGroupInfo(upgradeProtocol));
>          return processor;
>      }
>  
> @@ -339,38 +346,26 @@ public class Http2Protocol implements UpgradeProtocol {
>      @Override
>      public void setHttp11Protocol(AbstractProtocol<?> http11Protocol) {
>          this.http11Protocol = (AbstractHttp11Protocol<?>) http11Protocol;
> -    }
>  
> -
> -    public RequestGroupInfo getGlobal() {
> -        return global;
> +        try {
> +            ObjectName oname = 
> this.http11Protocol.getONameForUpgrade(getUpgradeProtocolName());
> +            Registry.getRegistry(null, null).registerComponent(global, 
> oname, null);
> +        } catch (Exception e) {
> +            log.warn(sm.getString("http2Protocol.jmxRegistration.fail"), e);
> +        }
>      }
>  
>  
> -    @Override
> -    public void init() throws Exception {
> -        ObjectName parentRgOname = 
> http11Protocol.getGlobalRequestProcessorMBeanName();
> -        if (parentRgOname != null) {
> -            StringBuilder name = new 
> StringBuilder(parentRgOname.getCanonicalName());
> -            name.append(",Upgrade=");
> -            // Neither of these names need quoting
> -            if (http11Protocol.isSSLEnabled()) {
> -                name.append(ALPN_NAME);
> -            } else {
> -                name.append(HTTP_UPGRADE_NAME);
> -            }
> -            ObjectName rgOname = new ObjectName(name.toString());
> -            this.rgOname = rgOname;
> -            Registry.getRegistry(null, null).registerComponent(global, 
> rgOname, null);
> +    public String getUpgradeProtocolName() {
> +        if (http11Protocol.isSSLEnabled()) {
> +            return ALPN_NAME;
> +        } else {
> +            return HTTP_UPGRADE_NAME;
>          }
>      }
>  
>  
> -    @Override
> -    public void destroy() throws Exception {
> -        ObjectName rgOname = this.rgOname;
> -        if (rgOname != null) {
> -            Registry.getRegistry(null, null).unregisterComponent(rgOname);
> -        }
> +    public RequestGroupInfo getGlobal() {
> +        return global;
>      }
>  }
> diff --git a/java/org/apache/coyote/http2/LocalStrings.properties 
> b/java/org/apache/coyote/http2/LocalStrings.properties
> index ca6e5af..a1c4075 100644
> --- a/java/org/apache/coyote/http2/LocalStrings.properties
> +++ b/java/org/apache/coyote/http2/LocalStrings.properties
> @@ -72,6 +72,8 @@ http2Parser.processFrameWindowUpdate.debug=Connection 
> [{0}], Stream [{1}], Windo
>  http2Parser.processFrameWindowUpdate.invalidIncrement=Window update frame 
> received with an invalid increment size of [{0}]
>  http2Parser.swallow.debug=Connection [{0}], Stream [{1}], Swallowed [{2}] 
> bytes
>  
> +http2Protocol.jmxRegistration.fail=JMX registration for the HTTP/2 protocol 
> failed
> +
>  pingManager.roundTripTime=Connection [{0}] Round trip time measured as 
> [{1}]ns
>  
>  stream.clientCancel=Client reset the stream before the response was complete
> diff --git a/java/org/apache/coyote/http2/StreamProcessor.java 
> b/java/org/apache/coyote/http2/StreamProcessor.java
> index 98c86cb..862ea35 100644
> --- a/java/org/apache/coyote/http2/StreamProcessor.java
> +++ b/java/org/apache/coyote/http2/StreamProcessor.java
> @@ -27,6 +27,7 @@ import org.apache.coyote.ContainerThreadMarker;
>  import org.apache.coyote.ContinueResponseTiming;
>  import org.apache.coyote.ErrorState;
>  import org.apache.coyote.Request;
> +import org.apache.coyote.RequestGroupInfo;
>  import org.apache.coyote.Response;
>  import org.apache.coyote.http11.filters.GzipOutputFilter;
>  import org.apache.juli.logging.Log;
> @@ -375,7 +376,10 @@ class StreamProcessor extends AbstractProcessor {
>          // Calling removeRequestProcessor even though the RequestProcesser 
> was
>          // never added will add the values from the RequestProcessor to the
>          // running total for the GlobalRequestProcessor
> -        
> handler.getProtocol().getGlobal().removeRequestProcessor(request.getRequestProcessor());
> +        RequestGroupInfo global = handler.getProtocol().getGlobal();
> +        if (global != null) {
> +            global.removeRequestProcessor(request.getRequestProcessor());
> +        }
>  
>          // Clear fields that can be cleared to aid GC and trigger NPEs if 
> this
>          // is reused
> diff --git a/java/org/apache/coyote/mbeans-descriptors.xml 
> b/java/org/apache/coyote/mbeans-descriptors.xml
> index 2c1713c..e23b15b 100644
> --- a/java/org/apache/coyote/mbeans-descriptors.xml
> +++ b/java/org/apache/coyote/mbeans-descriptors.xml
> @@ -59,4 +59,34 @@
>          <operation name="resetCounters" description="Reset counters" 
> impact="ACTION" returnType="void"/>
>  
>      </mbean>
> +
> +    <mbean name="UpgradeGroupInfo"
> +           description="Runtime information of a group of connections 
> upgraded via the HTTP upgrade process"
> +           domain="Catalina"
> +           group="Connector"
> +           type="org.apache.coyote.http11.upgrade.UpgradeGroupInfo">
> +
> +        <attribute name="bytesReceived"
> +                   description="Amount of data received, in bytes"
> +                   type="long"
> +                   writeable="false"/>
> +
> +        <attribute name="bytesSent"
> +                   description="Amount of data sent, in bytes"
> +                   type="long"
> +                   writeable="false"/>
> +
> +        <attribute name="msgsReceived"
> +                   description="Number of messages received where applicable 
> for the given protocol"
> +                   type="long"
> +                   writeable="false"/>
> +
> +        <attribute name="msgsSent"
> +                   description="Number of messages sent where applicable for 
> the given protocol"
> +                   type="long"
> +                   writeable="false"/>
> +
> +        <operation name="resetCounters" description="Reset counters" 
> impact="ACTION" returnType="void"/>
> +
> +    </mbean>
>  </mbeans-descriptors>
> \ No newline at end of file
> diff --git a/java/org/apache/tomcat/websocket/WsFrameBase.java 
> b/java/org/apache/tomcat/websocket/WsFrameBase.java
> index 3bb9bb1..792dc8f 100644
> --- a/java/org/apache/tomcat/websocket/WsFrameBase.java
> +++ b/java/org/apache/tomcat/websocket/WsFrameBase.java
> @@ -307,11 +307,24 @@ public abstract class WsFrameBase {
>                  result = processDataBinary();
>              }
>          }
> +        if (result) {
> +            updateStats(payloadLength);
> +        }
>          checkRoomPayload();
>          return result;
>      }
>  
>  
> +    /**
> +     * Hook for updating server side statistics. Called on every frame 
> received.
> +     *
> +     * @param payloadLength Size of message payload
> +     */
> +    protected void updateStats(long payloadLength) {
> +        // NO-OP by default
> +    }
> +
> +
>      private boolean processDataControl() throws IOException {
>          TransformationResult tr = transformation.getMoreData(opCode, fin, 
> rsv, controlBufferBinary);
>          if (TransformationResult.UNDERFLOW.equals(tr)) {
> diff --git a/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java 
> b/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
> index 0324e50..94b5ccb 100644
> --- a/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
> +++ b/java/org/apache/tomcat/websocket/WsRemoteEndpointImplBase.java
> @@ -491,6 +491,7 @@ public abstract class WsRemoteEndpointImplBase implements 
> RemoteEndpoint {
>              mask = null;
>          }
>  
> +        int payloadSize = mp.getPayload().remaining();
>          headerBuffer.clear();
>          writeHeader(headerBuffer, mp.isFin(), mp.getRsv(), mp.getOpCode(),
>                  isMasked(), mp.getPayload(), mask, first);
> @@ -508,6 +509,20 @@ public abstract class WsRemoteEndpointImplBase 
> implements RemoteEndpoint {
>              doWrite(mp.getEndHandler(), mp.getBlockingWriteTimeoutExpiry(),
>                      headerBuffer, mp.getPayload());
>          }
> +
> +        updateStats(payloadSize);
> +    }
> +
> +
> +    /**
> +     * Hook for updating server side statistics. Called on every frame 
> written
> +     * (including when batching is enabled and the frames are buffered 
> locally
> +     * until the buffer is full or is flushed).
> +     *
> +     * @param payloadLength Size of message payload
> +     */
> +    protected void updateStats(long payloadLength) {
> +        // NO-OP by default
>      }
>  
>  
> diff --git a/java/org/apache/tomcat/websocket/server/WsFrameServer.java 
> b/java/org/apache/tomcat/websocket/server/WsFrameServer.java
> index c1e369a..d29ea04 100644
> --- a/java/org/apache/tomcat/websocket/server/WsFrameServer.java
> +++ b/java/org/apache/tomcat/websocket/server/WsFrameServer.java
> @@ -20,6 +20,7 @@ import java.io.EOFException;
>  import java.io.IOException;
>  import java.nio.ByteBuffer;
>  
> +import org.apache.coyote.http11.upgrade.UpgradeInfo;
>  import org.apache.juli.logging.Log;
>  import org.apache.juli.logging.LogFactory;
>  import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
> @@ -37,13 +38,15 @@ public class WsFrameServer extends WsFrameBase {
>      private static final StringManager sm = 
> StringManager.getManager(WsFrameServer.class);
>  
>      private final SocketWrapperBase<?> socketWrapper;
> +    private final UpgradeInfo upgradeInfo;
>      private final ClassLoader applicationClassLoader;
>  
>  
> -    public WsFrameServer(SocketWrapperBase<?> socketWrapper, WsSession 
> wsSession,
> +    public WsFrameServer(SocketWrapperBase<?> socketWrapper, UpgradeInfo 
> upgradeInfo, WsSession wsSession,
>              Transformation transformation, ClassLoader 
> applicationClassLoader) {
>          super(wsSession, transformation);
>          this.socketWrapper = socketWrapper;
> +        this.upgradeInfo = upgradeInfo;
>          this.applicationClassLoader = applicationClassLoader;
>      }
>  
> @@ -85,6 +88,13 @@ public class WsFrameServer extends WsFrameBase {
>  
>  
>      @Override
> +    protected void updateStats(long payloadLength) {
> +        upgradeInfo.addMsgsReceived(1);
> +        upgradeInfo.addBytesReceived(payloadLength);
> +    }
> +
> +
> +    @Override
>      protected boolean isMasked() {
>          // Data is from the client so it should be masked
>          return true;
> @@ -140,6 +150,7 @@ public class WsFrameServer extends WsFrameBase {
>          socketWrapper.processSocket(SocketEvent.OPEN_READ, true);
>      }
>  
> +
>      SocketState notifyDataAvailable() throws IOException {
>          while (isOpen()) {
>              switch (getReadState()) {
> @@ -167,6 +178,7 @@ public class WsFrameServer extends WsFrameBase {
>          return SocketState.CLOSED;
>      }
>  
> +
>      private SocketState doOnDataAvailable() throws IOException {
>          onDataAvailable();
>          while (isOpen()) {
> diff --git 
> a/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java 
> b/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java
> index c449108..4f28a0e 100644
> --- a/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java
> +++ b/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java
> @@ -30,6 +30,7 @@ import jakarta.websocket.Extension;
>  import jakarta.websocket.server.ServerEndpointConfig;
>  
>  import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler;
> +import org.apache.coyote.http11.upgrade.UpgradeInfo;
>  import org.apache.juli.logging.Log;
>  import org.apache.juli.logging.LogFactory;
>  import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
> @@ -52,6 +53,7 @@ public class WsHttpUpgradeHandler implements 
> InternalHttpUpgradeHandler {
>      private final ClassLoader applicationClassLoader;
>  
>      private SocketWrapperBase<?> socketWrapper;
> +    private UpgradeInfo upgradeInfo = new UpgradeInfo();
>  
>      private Endpoint ep;
>      private ServerEndpointConfig serverEndpointConfig;
> @@ -117,7 +119,7 @@ public class WsHttpUpgradeHandler implements 
> InternalHttpUpgradeHandler {
>          ClassLoader cl = t.getContextClassLoader();
>          t.setContextClassLoader(applicationClassLoader);
>          try {
> -            wsRemoteEndpointServer = new 
> WsRemoteEndpointImplServer(socketWrapper, webSocketContainer);
> +            wsRemoteEndpointServer = new 
> WsRemoteEndpointImplServer(socketWrapper, upgradeInfo, webSocketContainer);
>              wsSession = new WsSession(ep, wsRemoteEndpointServer,
>                      webSocketContainer, handshakeRequest.getRequestURI(),
>                      handshakeRequest.getParameterMap(),
> @@ -125,7 +127,7 @@ public class WsHttpUpgradeHandler implements 
> InternalHttpUpgradeHandler {
>                      handshakeRequest.getUserPrincipal(), httpSessionId,
>                      negotiatedExtensions, subProtocol, pathParameters, 
> secure,
>                      serverEndpointConfig);
> -            wsFrame = new WsFrameServer(socketWrapper, wsSession, 
> transformation,
> +            wsFrame = new WsFrameServer(socketWrapper, upgradeInfo, 
> wsSession, transformation,
>                      applicationClassLoader);
>              // WsFrame adds the necessary final transformations. Copy the
>              // completed transformation chain to the remote end point.
> @@ -141,6 +143,12 @@ public class WsHttpUpgradeHandler implements 
> InternalHttpUpgradeHandler {
>  
>  
>      @Override
> +    public UpgradeInfo getUpgradeInfo() {
> +        return upgradeInfo;
> +    }
> +
> +
> +    @Override
>      public SocketState upgradeDispatch(SocketEvent status) {
>          switch (status) {
>              case OPEN_READ:
> diff --git 
> a/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java 
> b/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java
> index d56ba3d..fd8e6f4 100644
> --- a/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java
> +++ b/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java
> @@ -27,6 +27,7 @@ import java.util.concurrent.TimeUnit;
>  import jakarta.websocket.SendHandler;
>  import jakarta.websocket.SendResult;
>  
> +import org.apache.coyote.http11.upgrade.UpgradeInfo;
>  import org.apache.juli.logging.Log;
>  import org.apache.juli.logging.LogFactory;
>  import org.apache.tomcat.util.net.SocketWrapperBase;
> @@ -46,15 +47,17 @@ public class WsRemoteEndpointImplServer extends 
> WsRemoteEndpointImplBase {
>      private final Log log = 
> LogFactory.getLog(WsRemoteEndpointImplServer.class); // must not be static
>  
>      private final SocketWrapperBase<?> socketWrapper;
> +    private final UpgradeInfo upgradeInfo;
>      private final WsWriteTimeout wsWriteTimeout;
>      private volatile SendHandler handler = null;
>      private volatile ByteBuffer[] buffers = null;
>  
>      private volatile long timeoutExpiry = -1;
>  
> -    public WsRemoteEndpointImplServer(SocketWrapperBase<?> socketWrapper,
> +    public WsRemoteEndpointImplServer(SocketWrapperBase<?> socketWrapper, 
> UpgradeInfo upgradeInfo,
>              WsServerContainer serverContainer) {
>          this.socketWrapper = socketWrapper;
> +        this.upgradeInfo = upgradeInfo;
>          this.wsWriteTimeout = serverContainer.getTimeout();
>      }
>  
> @@ -154,6 +157,13 @@ public class WsRemoteEndpointImplServer extends 
> WsRemoteEndpointImplBase {
>      }
>  
>  
> +    @Override
> +    protected void updateStats(long payloadLength) {
> +        upgradeInfo.addMsgsSent(1);
> +        upgradeInfo.addBytesSent(payloadLength);
> +    }
> +
> +
>      public void onWritePossible(boolean useDispatch) {
>          // Note: Unused for async IO
>          ByteBuffer[] buffers = this.buffers;
> diff --git a/test/org/apache/coyote/http11/upgrade/TestUpgrade.java 
> b/test/org/apache/coyote/http11/upgrade/TestUpgrade.java
> index c6b1b8a..13eb551 100644
> --- a/test/org/apache/coyote/http11/upgrade/TestUpgrade.java
> +++ b/test/org/apache/coyote/http11/upgrade/TestUpgrade.java
> @@ -178,6 +178,7 @@ public class TestUpgrade extends TomcatBaseTest {
>  
>          uc.getWriter().write("GET / HTTP/1.1" + CRLF);
>          uc.getWriter().write("Host: whatever" + CRLF);
> +        uc.getWriter().write("Upgrade: test" + CRLF);
>          uc.getWriter().write(CRLF);
>          uc.getWriter().flush();
>  
> @@ -210,6 +211,9 @@ public class TestUpgrade extends TomcatBaseTest {
>          protected void doGet(HttpServletRequest req, HttpServletResponse 
> resp)
>                  throws ServletException, IOException {
>  
> +            // In these tests only a single protocol is requested so it is 
> safe
> +            // to echo it to the response.
> +            resp.setHeader("upgrade", req.getHeader("upgrade"));
>              req.upgrade(upgradeHandlerClass);
>          }
>      }
> diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
> index ed751ed..fbf0879 100644
> --- a/webapps/docs/changelog.xml
> +++ b/webapps/docs/changelog.xml
> @@ -83,8 +83,8 @@
>          Based on a pull request by willmeck. (markt)
>        </fix>
>        <fix>
> -        Implement a partial fix for <bug>63362</bug> that adds collection of
> -        request statistics for HTTP/2 requests. (markt)
> +        <bug>63362</bug>: Add collection of statistics for HTTP/2, WebSocket 
> and
> +        connections upgraded via the HTTP upgrade mechanism. (markt)
>        </fix>
>      </changelog>
>    </subsection>
> 
> 
> ---------------------------------------------------------------------
> 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