This is an automated email from the ASF dual-hosted git repository.

markt pushed a commit to branch 8.5.x
in repository https://gitbox.apache.org/repos/asf/tomcat.git


The following commit(s) were added to refs/heads/8.5.x by this push:
     new b709415  Complete fix for BZ 63362. Collect stats for h2, websocket 
and upgrade
b709415 is described below

commit b709415efb391d6f6f3aa4423494a2dd3610c59d
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
    
    https://bz.apache.org/bugzilla/show_bug.cgi?id=63362
---
 java/org/apache/catalina/connector/Request.java    |   2 +-
 java/org/apache/coyote/AbstractProtocol.java       |  26 -----
 java/org/apache/coyote/LocalStrings.properties     |   1 -
 java/org/apache/coyote/UpgradeToken.java           |   9 +-
 .../coyote/http11/AbstractHttp11Protocol.java      | 100 +++++++++++++++--
 java/org/apache/coyote/http11/Http11Processor.java |   2 +-
 .../apache/coyote/http11/LocalStrings.properties   |   2 +
 .../http11/upgrade/InternalHttpUpgradeHandler.java |   2 +
 .../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    |  46 ++++----
 .../apache/coyote/http2/Http2UpgradeHandler.java   |   8 ++
 .../apache/coyote/http2/LocalStrings.properties    |   2 +
 java/org/apache/coyote/http2/StreamProcessor.java  |   6 +-
 java/org/apache/coyote/mbeans-descriptors.xml      |  92 ++++++++++++++++
 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 +
 .../http11/upgrade/TestUpgradeInternalHandler.java |   6 +-
 webapps/docs/changelog.xml                         |   4 +-
 27 files changed, 562 insertions(+), 82 deletions(-)

diff --git a/java/org/apache/catalina/connector/Request.java 
b/java/org/apache/catalina/connector/Request.java
index 1d2c341..67be9ce 100644
--- a/java/org/apache/catalina/connector/Request.java
+++ b/java/org/apache/catalina/connector/Request.java
@@ -2046,7 +2046,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 dd1359b..247a5b0 100644
--- a/java/org/apache/coyote/AbstractProtocol.java
+++ b/java/org/apache/coyote/AbstractProtocol.java
@@ -35,7 +35,6 @@ import javax.management.ObjectName;
 import javax.servlet.http.HttpUpgradeHandler;
 import javax.servlet.http.WebConnection;
 
-import org.apache.coyote.http2.Http2Protocol;
 import org.apache.juli.logging.Log;
 import org.apache.tomcat.InstanceManager;
 import org.apache.tomcat.util.ExceptionUtils;
@@ -589,17 +588,6 @@ public abstract class AbstractProtocol<S> implements 
ProtocolHandler,
         endpoint.setDomain(domain);
 
         endpoint.init();
-
-        UpgradeProtocol[] upgradeProtocols = findUpgradeProtocols();
-        for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
-            // Need to do this as we can't add methods to UpgradeProtocol
-            // without running the risk of breaking a custom upgrade protocol
-            if (upgradeProtocol instanceof Http2Protocol) {
-                // Implementation note: Failure of one upgrade protocol fails 
the
-                // whole Connector
-                ((Http2Protocol) upgradeProtocol).init();
-            }
-        }
     }
 
 
@@ -664,20 +652,6 @@ public abstract class AbstractProtocol<S> implements 
ProtocolHandler,
             getLog().info(sm.getString("abstractProtocolHandler.destroy", 
getName()));
         }
 
-        UpgradeProtocol[] upgradeProtocols = findUpgradeProtocols();
-        for (UpgradeProtocol upgradeProtocol : upgradeProtocols) {
-            try {
-                // Need to do this as we can't add methods to UpgradeProtocol
-                // without running the risk of breaking a custom upgrade 
protocol
-                if (upgradeProtocol instanceof Http2Protocol) {
-                    ((Http2Protocol) 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 77911cc..1aeb314 100644
--- a/java/org/apache/coyote/LocalStrings.properties
+++ b/java/org/apache/coyote/LocalStrings.properties
@@ -35,7 +35,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/UpgradeToken.java 
b/java/org/apache/coyote/UpgradeToken.java
index cf297fb..7d9d680 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 d28e4fc..30f5957 100644
--- a/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
+++ b/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
@@ -26,6 +26,8 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
 import javax.servlet.http.HttpUpgradeHandler;
 
 import org.apache.coyote.AbstractProtocol;
@@ -37,10 +39,13 @@ 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.coyote.http2.Http2Protocol;
 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 +78,33 @@ 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) {
+            if (upgradeProtocol instanceof Http2Protocol) {
+                ((Http2Protocol) 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();
     }
 
 
@@ -535,10 +567,6 @@ public abstract class AbstractHttp11Protocol<S> extends 
AbstractProtocol<S> {
                 }
             }
         }
-
-        if (upgradeProtocol instanceof Http2Protocol) {
-            ((Http2Protocol) upgradeProtocol).setHttp11Protocol(this);
-        }
     }
     @Override
     public UpgradeProtocol getNegotiatedProtocol(String negotiatedName) {
@@ -550,6 +578,66 @@ public abstract class AbstractHttp11Protocol<S> extends 
AbstractProtocol<S> {
     }
 
 
+    /**
+     * Map of upgrade protocol name to {@link UpgradeGroupInfo} instance.
+     * <p>
+     * HTTP upgrades via
+     * {@link javax.servlet.http.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
 
@@ -1014,9 +1102,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 5d27a8d..68beb91 100644
--- a/java/org/apache/coyote/http11/Http11Processor.java
+++ b/java/org/apache/coyote/http11/Http11Processor.java
@@ -579,7 +579,7 @@ public class Http11Processor extends AbstractProcessor {
                         InternalHttpUpgradeHandler upgradeHandler =
                                 upgradeProtocol.getInternalUpgradeHandler(
                                         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 d547b1d..f545ddf 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 426b1bd..608a91c 100644
--- a/java/org/apache/coyote/http11/upgrade/InternalHttpUpgradeHandler.java
+++ b/java/org/apache/coyote/http11/upgrade/InternalHttpUpgradeHandler.java
@@ -39,4 +39,6 @@ public interface InternalHttpUpgradeHandler extends 
HttpUpgradeHandler {
     void setSslSupport(SSLSupport sslSupport);
 
     void pause();
+
+    UpgradeInfo getUpgradeInfo();
 }
\ 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 b73f4e7..e642721 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 f0f5460..ac39094 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);
+        }
     }
 
 
@@ -83,6 +87,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 1c1ddb6..387581a 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 3de8096..84d8fa5 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 79f9314..b8d3b0a 100644
--- a/java/org/apache/coyote/http2/Http2Protocol.java
+++ b/java/org/apache/coyote/http2/Http2Protocol.java
@@ -41,12 +41,18 @@ 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.buf.StringUtils;
 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;
@@ -100,7 +106,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) {
@@ -123,8 +128,9 @@ public class Http2Protocol implements UpgradeProtocol {
 
     @Override
     public Processor getProcessor(SocketWrapperBase<?> socketWrapper, Adapter 
adapter) {
+        String upgradeProtocol = getUpgradeProtocolName();
         UpgradeProcessorInternal processor = new 
UpgradeProcessorInternal(socketWrapper,
-                new UpgradeToken(getInternalUpgradeHandler(adapter, null), 
null, null));
+                new UpgradeToken(getInternalUpgradeHandler(adapter, null), 
null, null, upgradeProtocol), null);
         return processor;
     }
 
@@ -434,36 +440,26 @@ public class Http2Protocol implements UpgradeProtocol {
 
     public void setHttp11Protocol(AbstractHttp11Protocol<?> http11Protocol) {
         this.http11Protocol = 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);
+        }
     }
 
 
-    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;
         }
     }
 
 
-    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/Http2UpgradeHandler.java 
b/java/org/apache/coyote/http2/Http2UpgradeHandler.java
index 458b112..d80e26a 100644
--- a/java/org/apache/coyote/http2/Http2UpgradeHandler.java
+++ b/java/org/apache/coyote/http2/Http2UpgradeHandler.java
@@ -42,6 +42,7 @@ import org.apache.coyote.Adapter;
 import org.apache.coyote.ProtocolException;
 import org.apache.coyote.Request;
 import org.apache.coyote.http11.upgrade.InternalHttpUpgradeHandler;
+import org.apache.coyote.http11.upgrade.UpgradeInfo;
 import org.apache.coyote.http2.HpackDecoder.HeaderEmitter;
 import org.apache.coyote.http2.HpackEncoder.State;
 import org.apache.coyote.http2.Http2Parser.Input;
@@ -484,6 +485,13 @@ public class Http2UpgradeHandler extends AbstractStream 
implements InternalHttpU
     }
 
 
+    @Override
+    public UpgradeInfo getUpgradeInfo() {
+        // Uses RequestInfo rather than UpgradeInfo
+        return null;
+    }
+
+
     private void checkPauseState() throws IOException {
         if (connectionState.get() == ConnectionState.PAUSING) {
             if (pausedNanoTime + pingManager.getRoundTripTimeNano() < 
System.nanoTime()) {
diff --git a/java/org/apache/coyote/http2/LocalStrings.properties 
b/java/org/apache/coyote/http2/LocalStrings.properties
index 2c17966..c1e8c0a 100644
--- a/java/org/apache/coyote/http2/LocalStrings.properties
+++ b/java/org/apache/coyote/http2/LocalStrings.properties
@@ -71,6 +71,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 119be9e..c8fc304 100644
--- a/java/org/apache/coyote/http2/StreamProcessor.java
+++ b/java/org/apache/coyote/http2/StreamProcessor.java
@@ -26,6 +26,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;
@@ -337,7 +338,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
new file mode 100644
index 0000000..e23b15b
--- /dev/null
+++ b/java/org/apache/coyote/mbeans-descriptors.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<!DOCTYPE mbeans-descriptors PUBLIC
+   "-//Apache Software Foundation//DTD Model MBeans Configuration File"
+   "http://jakarta.apache.org/commons/dtds/mbeans-descriptors.dtd";>
+<mbeans-descriptors>
+
+    <mbean name="RequestGroupInfo"
+           description="Runtime information of a group of requests"
+           domain="Catalina"
+           group="Connector"
+           type="org.apache.coyote.RequestGroupInfo">
+
+        <attribute name="maxTime"
+                   description="Maximum time to process a request"
+                   type="long"
+                   writeable="false"/>
+
+        <attribute name="processingTime"
+                   description="Total time to process the requests"
+                   type="long"
+                   writeable="false"/>
+
+        <attribute name="requestCount"
+                   description="Number of requests processed"
+                   type="int"
+                   writeable="false"/>
+
+        <attribute name="errorCount"
+                   description="Number of errors"
+                   type="int"
+                   writeable="false"/>
+
+        <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"/>
+
+        <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 3993c6a..cea5bb3 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 103f80d..75f90e6 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 84b70c6..414f9bc 100644
--- a/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java
+++ b/java/org/apache/tomcat/websocket/server/WsHttpUpgradeHandler.java
@@ -30,6 +30,7 @@ import javax.websocket.Extension;
 import javax.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 b370123..360c415 100644
--- a/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java
+++ b/java/org/apache/tomcat/websocket/server/WsRemoteEndpointImplServer.java
@@ -25,6 +25,7 @@ import java.util.concurrent.Executor;
 import javax.websocket.SendHandler;
 import javax.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.AbstractEndpoint;
@@ -44,15 +45,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();
     }
 
@@ -102,6 +105,13 @@ public class WsRemoteEndpointImplServer extends 
WsRemoteEndpointImplBase {
     }
 
 
+    @Override
+    protected void updateStats(long payloadLength) {
+        upgradeInfo.addMsgsSent(1);
+        upgradeInfo.addBytesSent(payloadLength);
+    }
+
+
     public void onWritePossible(boolean useDispatch) {
         ByteBuffer[] buffers = this.buffers;
         if (buffers == null) {
diff --git a/test/org/apache/coyote/http11/upgrade/TestUpgrade.java 
b/test/org/apache/coyote/http11/upgrade/TestUpgrade.java
index c5180d6..2d99506 100644
--- a/test/org/apache/coyote/http11/upgrade/TestUpgrade.java
+++ b/test/org/apache/coyote/http11/upgrade/TestUpgrade.java
@@ -177,6 +177,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();
 
@@ -209,6 +210,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/test/org/apache/coyote/http11/upgrade/TestUpgradeInternalHandler.java 
b/test/org/apache/coyote/http11/upgrade/TestUpgradeInternalHandler.java
index 27bce41..71b6d27 100644
--- a/test/org/apache/coyote/http11/upgrade/TestUpgradeInternalHandler.java
+++ b/test/org/apache/coyote/http11/upgrade/TestUpgradeInternalHandler.java
@@ -282,6 +282,10 @@ public class TestUpgradeInternalHandler extends 
TomcatBaseTest {
         public void setSslSupport(SSLSupport sslSupport) {
             // NO-OP
         }
-    }
 
+        @Override
+        public UpgradeInfo getUpgradeInfo() {
+            return null;
+        }
+    }
 }
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index 7fabb3b..b02f143 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -82,8 +82,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>
   </subseciton>


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

Reply via email to