You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by ma...@apache.org on 2016/10/25 13:54:09 UTC

svn commit: r1766533 - in /tomcat/trunk: java/org/apache/coyote/ java/org/apache/coyote/http2/ test/org/apache/coyote/http2/ webapps/docs/ webapps/docs/config/

Author: markt
Date: Tue Oct 25 13:54:09 2016
New Revision: 1766533

URL: http://svn.apache.org/viewvc?rev=1766533&view=rev
Log:
Add support for limiting trailer header count and size

Added:
    tomcat/trunk/java/org/apache/coyote/CloseNowException.java   (with props)
Modified:
    tomcat/trunk/java/org/apache/coyote/AbstractProcessor.java
    tomcat/trunk/java/org/apache/coyote/http2/Constants.java
    tomcat/trunk/java/org/apache/coyote/http2/Http2Protocol.java
    tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java
    tomcat/trunk/java/org/apache/coyote/http2/Stream.java
    tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Limits.java
    tomcat/trunk/webapps/docs/changelog.xml
    tomcat/trunk/webapps/docs/config/http2.xml

Modified: tomcat/trunk/java/org/apache/coyote/AbstractProcessor.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/AbstractProcessor.java?rev=1766533&r1=1766532&r2=1766533&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/AbstractProcessor.java (original)
+++ tomcat/trunk/java/org/apache/coyote/AbstractProcessor.java Tue Oct 25 13:54:09 2016
@@ -269,6 +269,8 @@ public abstract class AbstractProcessor
             action(ActionCode.COMMIT, null);
             try {
                 finishResponse();
+            } catch (CloseNowException cne) {
+                setErrorState(ErrorState.CLOSE_NOW, cne);
             } catch (IOException e) {
                 setErrorState(ErrorState.CLOSE_CONNECTION_NOW, e);
             }

Added: tomcat/trunk/java/org/apache/coyote/CloseNowException.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/CloseNowException.java?rev=1766533&view=auto
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/CloseNowException.java (added)
+++ tomcat/trunk/java/org/apache/coyote/CloseNowException.java Tue Oct 25 13:54:09 2016
@@ -0,0 +1,51 @@
+/*
+ *  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;
+
+import java.io.IOException;
+
+/**
+ * This exception is thrown to signal to the Tomcat internals that an error has
+ * occurred that requires the connection to be closed. For multiplexed protocols
+ * such as HTTP/2, this means the channel must be closed but the connection can
+ * continue. For non-multiplexed protocols, the connection must be closed. It
+ * corresponds to {@link ErrorState#CLOSE_NOW}.
+ */
+public class CloseNowException extends IOException {
+
+    private static final long serialVersionUID = 1L;
+
+
+    public CloseNowException() {
+        super();
+    }
+
+
+    public CloseNowException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+
+    public CloseNowException(String message) {
+        super(message);
+    }
+
+
+    public CloseNowException(Throwable cause) {
+        super(cause);
+    }
+}

Propchange: tomcat/trunk/java/org/apache/coyote/CloseNowException.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: tomcat/trunk/java/org/apache/coyote/http2/Constants.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Constants.java?rev=1766533&r1=1766532&r2=1766533&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/Constants.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/Constants.java Tue Oct 25 13:54:09 2016
@@ -25,7 +25,9 @@ public class Constants {
     static final int DEFAULT_HEADER_READ_BUFFER_SIZE = 1024;
 
     // Limits
+    static final int DEFAULT_MAX_COOKIE_COUNT = 200;
     static final int DEFAULT_MAX_HEADER_COUNT = 100;
     static final int DEFAULT_MAX_HEADER_SIZE = 8 * 1024;
-    static final int DEFAULT_MAX_COOKIE_COUNT = 200;
+    static final int DEFAULT_MAX_TRAILER_COUNT = 100;
+    static final int DEFAULT_MAX_TRAILER_SIZE = 8 * 1024;
 }

Modified: tomcat/trunk/java/org/apache/coyote/http2/Http2Protocol.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Http2Protocol.java?rev=1766533&r1=1766532&r2=1766533&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/Http2Protocol.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/Http2Protocol.java Tue Oct 25 13:54:09 2016
@@ -67,6 +67,8 @@ public class Http2Protocol implements Up
     private int maxCookieCount = Constants.DEFAULT_MAX_COOKIE_COUNT;
     private int maxHeaderCount = Constants.DEFAULT_MAX_HEADER_COUNT;
     private int maxHeaderSize = Constants.DEFAULT_MAX_HEADER_SIZE;
+    private int maxTrailerCount = Constants.DEFAULT_MAX_TRAILER_COUNT;
+    private int maxTrailerSize = Constants.DEFAULT_MAX_TRAILER_SIZE;
 
 
     @Override
@@ -111,6 +113,8 @@ public class Http2Protocol implements Up
         result.setMaxCookieCount(getMaxCookieCount());
         result.setMaxHeaderCount(getMaxHeaderCount());
         result.setMaxHeaderSize(getMaxHeaderSize());
+        result.setMaxTrailerCount(getMaxTrailerCount());
+        result.setMaxTrailerSize(getMaxTrailerSize());
         return result;
     }
 
@@ -236,6 +240,16 @@ public class Http2Protocol implements Up
     }
 
 
+    public void setMaxCookieCount(int maxCookieCount) {
+        this.maxCookieCount = maxCookieCount;
+    }
+
+
+    public int getMaxCookieCount() {
+        return maxCookieCount;
+    }
+
+
     public void setMaxHeaderCount(int maxHeaderCount) {
         this.maxHeaderCount = maxHeaderCount;
     }
@@ -256,12 +270,22 @@ public class Http2Protocol implements Up
     }
 
 
-    public void setMaxCookieCount(int maxCookieCount) {
-        this.maxCookieCount = maxCookieCount;
+    public void setMaxTrailerCount(int maxTrailerCount) {
+        this.maxTrailerCount = maxTrailerCount;
     }
 
 
-    public int getMaxCookieCount() {
-        return maxCookieCount;
+    public int getMaxTrailerCount() {
+        return maxTrailerCount;
+    }
+
+
+    public void setMaxTrailerSize(int maxTrailerSize) {
+        this.maxTrailerSize = maxTrailerSize;
+    }
+
+
+    public int getMaxTrailerSize() {
+        return maxTrailerSize;
     }
 }

Modified: tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java?rev=1766533&r1=1766532&r2=1766533&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/Http2UpgradeHandler.java Tue Oct 25 13:54:09 2016
@@ -37,6 +37,7 @@ import java.util.concurrent.atomic.Atomi
 import javax.servlet.http.WebConnection;
 
 import org.apache.coyote.Adapter;
+import org.apache.coyote.CloseNowException;
 import org.apache.coyote.ProtocolException;
 import org.apache.coyote.Request;
 import org.apache.coyote.Response;
@@ -148,6 +149,10 @@ class Http2UpgradeHandler extends Abstra
     // Limits
     private Set<String> allowedTrailerHeaders = Collections.emptySet();
     private int maxCookieCount = Constants.DEFAULT_MAX_COOKIE_COUNT;
+    private int maxHeaderCount = Constants.DEFAULT_MAX_HEADER_COUNT;
+    private int maxHeaderSize = Constants.DEFAULT_MAX_HEADER_SIZE;
+    private int maxTrailerCount = Constants.DEFAULT_MAX_TRAILER_COUNT;
+    private int maxTrailerSize = Constants.DEFAULT_MAX_TRAILER_SIZE;
 
 
     Http2UpgradeHandler(Adapter adapter, Request coyoteRequest) {
@@ -513,6 +518,10 @@ class Http2UpgradeHandler extends Abstra
                     stream.getIdentifier()));
         }
 
+        if (!stream.canWrite()) {
+            return;
+        }
+
         prepareHeaders(coyoteResponse);
 
         byte[] header = new byte[9];
@@ -633,6 +642,8 @@ class Http2UpgradeHandler extends Abstra
             log.debug(sm.getString("upgradeHandler.writeBody", connectionId, stream.getIdentifier(),
                     Integer.toString(len)));
         }
+        // Need to check this now since sending end of stream will change this.
+        boolean writeable = stream.canWrite();
         byte[] header = new byte[9];
         ByteUtil.setThreeBytes(header, 0, len);
         header[3] = FrameType.DATA.getIdByte();
@@ -643,17 +654,19 @@ class Http2UpgradeHandler extends Abstra
                 activeRemoteStreamCount.decrementAndGet();
             }
         }
-        ByteUtil.set31Bits(header, 5, stream.getIdentifier().intValue());
-        synchronized (socketWrapper) {
-            try {
-                socketWrapper.write(true, header, 0, header.length);
-                int orgLimit = data.limit();
-                data.limit(data.position() + len);
-                socketWrapper.write(true, data);
-                data.limit(orgLimit);
-                socketWrapper.flush(true);
-            } catch (IOException ioe) {
-                handleAppInitiatedIOException(ioe);
+        if (writeable) {
+            ByteUtil.set31Bits(header, 5, stream.getIdentifier().intValue());
+            synchronized (socketWrapper) {
+                try {
+                    socketWrapper.write(true, header, 0, header.length);
+                    int orgLimit = data.limit();
+                    data.limit(data.position() + len);
+                    socketWrapper.write(true, data);
+                    data.limit(orgLimit);
+                    socketWrapper.flush(true);
+                } catch (IOException ioe) {
+                    handleAppInitiatedIOException(ioe);
+                }
             }
         }
     }
@@ -681,6 +694,9 @@ class Http2UpgradeHandler extends Abstra
      */
     void writeWindowUpdate(Stream stream, int increment, boolean applicationInitiated)
             throws IOException {
+        if (!stream.canWrite()) {
+            return;
+        }
         synchronized (socketWrapper) {
             // Build window update frame for stream 0
             byte[] frame = new byte[13];
@@ -722,8 +738,9 @@ class Http2UpgradeHandler extends Abstra
             do {
                 synchronized (this) {
                     if (!stream.canWrite()) {
-                        throw new IOException(sm.getString("upgradeHandler.stream.notWritable",
-                                stream.getConnectionId(), stream.getIdentifier()));
+                        throw new CloseNowException(
+                                sm.getString("upgradeHandler.stream.notWritable",
+                                        stream.getConnectionId(), stream.getIdentifier()));
                     }
                     long windowSize = getWindowSize();
                     if (windowSize < 1 || backLogSize > 0) {
@@ -1114,23 +1131,53 @@ class Http2UpgradeHandler extends Abstra
     }
 
 
+    public void setMaxCookieCount(int maxCookieCount) {
+        this.maxCookieCount = maxCookieCount;
+    }
+
+
+    public int getMaxCookieCount() {
+        return maxCookieCount;
+    }
+
+
     public void setMaxHeaderCount(int maxHeaderCount) {
-        getHpackDecoder().setMaxHeaderCount(maxHeaderCount);
+        this.maxHeaderCount = maxHeaderCount;
+    }
+
+
+    public int getMaxHeaderCount() {
+        return maxHeaderCount;
     }
 
 
     public void setMaxHeaderSize(int maxHeaderSize) {
-        getHpackDecoder().setMaxHeaderSize(maxHeaderSize);
+        this.maxHeaderSize = maxHeaderSize;
     }
 
 
-    public void setMaxCookieCount(int maxCookieCount) {
-        this.maxCookieCount = maxCookieCount;
+    public int getMaxHeaderSize() {
+        return maxHeaderSize;
     }
 
 
-    public int getMaxCookieCount() {
-        return maxCookieCount;
+    public void setMaxTrailerCount(int maxTrailerCount) {
+        this.maxTrailerCount = maxTrailerCount;
+    }
+
+
+    public int getMaxTrailerCount() {
+        return maxTrailerCount;
+    }
+
+
+    public void setMaxTrailerSize(int maxTrailerSize) {
+        this.maxTrailerSize = maxTrailerSize;
+    }
+
+
+    public int getMaxTrailerSize() {
+        return maxTrailerSize;
     }
 
 

Modified: tomcat/trunk/java/org/apache/coyote/http2/Stream.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/java/org/apache/coyote/http2/Stream.java?rev=1766533&r1=1766532&r2=1766533&view=diff
==============================================================================
--- tomcat/trunk/java/org/apache/coyote/http2/Stream.java (original)
+++ tomcat/trunk/java/org/apache/coyote/http2/Stream.java Tue Oct 25 13:54:09 2016
@@ -24,6 +24,7 @@ import java.security.PrivilegedException
 import java.util.Iterator;
 
 import org.apache.coyote.ActionCode;
+import org.apache.coyote.CloseNowException;
 import org.apache.coyote.InputBuffer;
 import org.apache.coyote.OutputBuffer;
 import org.apache.coyote.Request;
@@ -171,8 +172,8 @@ class Stream extends AbstractStream impl
         long windowSize = getWindowSize();
         while (windowSize < 1) {
             if (!canWrite()) {
-                throw new IOException(sm.getString("stream.notWritable", getConnectionId(),
-                        getIdentifier()));
+                throw new CloseNowException(sm.getString("stream.notWritable",
+                        getConnectionId(), getIdentifier()));
             }
             try {
                 if (block) {
@@ -358,8 +359,12 @@ class Stream extends AbstractStream impl
     final void receivedStartOfHeaders() {
         if (headerState == HEADER_STATE_START) {
             headerState = HEADER_STATE_PSEUDO;
+            handler.getHpackDecoder().setMaxHeaderCount(handler.getMaxHeaderCount());
+            handler.getHpackDecoder().setMaxHeaderSize(handler.getMaxHeaderSize());
         } else if (headerState == HEADER_STATE_PSEUDO || headerState == HEADER_STATE_REGULAR) {
             headerState = HEADER_STATE_TRAILER;
+            handler.getHpackDecoder().setMaxHeaderCount(handler.getMaxTrailerCount());
+            handler.getHpackDecoder().setMaxHeaderSize(handler.getMaxTrailerSize());
         }
         // Parser will catch attempt to send a headers frame after the stream
         // has closed.

Modified: tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Limits.java
URL: http://svn.apache.org/viewvc/tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Limits.java?rev=1766533&r1=1766532&r2=1766533&view=diff
==============================================================================
--- tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Limits.java (original)
+++ tomcat/trunk/test/org/apache/coyote/http2/TestHttp2Limits.java Tue Oct 25 13:54:09 2016
@@ -394,4 +394,81 @@ public class TestHttp2Limits extends Htt
         }
         }
     }
+
+
+    @Test
+    public void doTestPostWithTrailerHeadersDefaultLimit() throws Exception{
+        doTestPostWithTrailerHeaders(Constants.DEFAULT_MAX_TRAILER_COUNT,
+                Constants.DEFAULT_MAX_TRAILER_SIZE, true);
+    }
+
+
+    @Test
+    public void doTestPostWithTrailerHeadersCount0() throws Exception{
+        doTestPostWithTrailerHeaders(0, Constants.DEFAULT_MAX_TRAILER_SIZE, false);
+    }
+
+
+    @Test
+    public void doTestPostWithTrailerHeadersSize0() throws Exception{
+        doTestPostWithTrailerHeaders(Constants.DEFAULT_MAX_TRAILER_COUNT, 0, false);
+    }
+
+
+    private void doTestPostWithTrailerHeaders(int maxTrailerCount, int maxTrailerSize, boolean ok)
+            throws Exception {
+        enableHttp2();
+
+        Http2Protocol http2Protocol =
+                    (Http2Protocol) getTomcatInstance().getConnector().findUpgradeProtocols()[0];
+        http2Protocol.setAllowedTrailerHeaders(TRAILER_HEADER_NAME);
+        http2Protocol.setMaxTrailerCount(maxTrailerCount);
+        http2Protocol.setMaxTrailerSize(maxTrailerSize);
+
+        configureAndStartWebApplication();
+        openClientConnection();
+        doHttpUpgrade();
+        sendClientPreface();
+        validateHttp2InitialResponse();
+
+        byte[] headersFrameHeader = new byte[9];
+        ByteBuffer headersPayload = ByteBuffer.allocate(128);
+        byte[] dataFrameHeader = new byte[9];
+        ByteBuffer dataPayload = ByteBuffer.allocate(256);
+        byte[] trailerFrameHeader = new byte[9];
+        ByteBuffer trailerPayload = ByteBuffer.allocate(256);
+
+        buildPostRequest(headersFrameHeader, headersPayload, false, dataFrameHeader, dataPayload,
+                null, trailerFrameHeader, trailerPayload, 3);
+
+        // Write the headers
+        writeFrame(headersFrameHeader, headersPayload);
+        // Body
+        writeFrame(dataFrameHeader, dataPayload);
+        // Trailers
+        writeFrame(trailerFrameHeader, trailerPayload);
+
+        parser.readFrame(true);
+        if (ok) {
+            parser.readFrame(true);
+            parser.readFrame(true);
+            parser.readFrame(true);
+
+            String len = Integer.toString(256 + TRAILER_HEADER_VALUE.length());
+
+            Assert.assertEquals("0-WindowSize-[256]\n" +
+                    "3-WindowSize-[256]\n" +
+                    "3-HeadersStart\n" +
+                    "3-Header-[:status]-[200]\n" +
+                    "3-Header-[date]-["+ DEFAULT_DATE + "]\n" +
+                    "3-HeadersEnd\n" +
+                    "3-Body-" +
+                    len +
+                    "\n" +
+                    "3-EndOfStream\n",
+                    output.getTrace());
+        } else {
+            Assert.assertEquals("3-RST-[11]\n", output.getTrace());
+        }
+    }
 }

Modified: tomcat/trunk/webapps/docs/changelog.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/changelog.xml?rev=1766533&r1=1766532&r2=1766533&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/changelog.xml (original)
+++ tomcat/trunk/webapps/docs/changelog.xml Tue Oct 25 13:54:09 2016
@@ -127,8 +127,10 @@
       </fix>
       <add>
         Add configuration options to the HTTP/2 implementation to control the
-        maximum number of headers allowed, the maximum size of headers allowed
-        and the maximum number of cookies allowed. (markt)
+        maximum number of headers allowed, the maximum size of headers allowed,
+        the maximum number of trailer headers allowed, the maximum size of
+        trailer headers allowed and the maximum number of cookies allowed.
+        (markt)
       </add>
     </changelog>
   </subsection>

Modified: tomcat/trunk/webapps/docs/config/http2.xml
URL: http://svn.apache.org/viewvc/tomcat/trunk/webapps/docs/config/http2.xml?rev=1766533&r1=1766532&r2=1766533&view=diff
==============================================================================
--- tomcat/trunk/webapps/docs/config/http2.xml (original)
+++ tomcat/trunk/webapps/docs/config/http2.xml Tue Oct 25 13:54:09 2016
@@ -128,6 +128,23 @@
       a default of 8192 is used.</p>
     </attribute>
 
+    <attribute name="maxTrailerCount" required="false">
+      <p>The maximum number of trailer headers in a request that is allowed by
+      the container. A request that contains more trailer headers than the
+      specified limit will be rejected. A value of less than 0 means no limit.
+      If not specified, a default of 100 is used.</p>
+    </attribute>
+
+    <attribute name="maxTrailerSize" required="false">
+      <p>The maximum total size for all trailer headers in a request that is
+      allowed by the container. Total size for a header is calculated as the
+      uncompressed size of the header name in bytes, plus the uncompressed size
+      of the header value in bytes plus an HTTP/2 overhead of 3 bytes per
+      header. A request that contains a set of trailer headers that requires
+      more than the specified limit will be rejected. A value of less than 0
+      means no limit. If not specified, a default of 8192 is used.</p>
+    </attribute>
+
     <attribute name="readTimeout" required="false">
       <p>The time, in milliseconds, that Tomcat will wait for additional data
       when a partial HTTP/2 frame has been received. Negative values will be



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