| /* |
| * 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.http2; |
| |
| import org.junit.Assert; |
| import org.junit.Test; |
| |
| /** |
| * Unit tests for Section 5.3 of |
| * <a href="https://tools.ietf.org/html/rfc7540">RFC 7540</a>. |
| * <br> |
| * The order of tests in this class is aligned with the order of the |
| * requirements in the RFC. |
| * |
| * Note: Unit tests for the examples described by each of the figures may be |
| * found in {@link TestAbstractStream}. |
| */ |
| public class TestHttp2Section_5_3 extends Http2TestBase { |
| |
| // Section 5.3.1 |
| |
| @Test |
| public void testStreamDependsOnSelf() throws Exception { |
| http2Connect(); |
| |
| sendPriority(3, 3, 15); |
| |
| parser.readFrame(true); |
| |
| Assert.assertEquals("3-RST-[1]\n", output.getTrace()); |
| } |
| |
| |
| // Section 5.3.2 |
| |
| @Test |
| public void testWeighting() throws Exception { |
| |
| http2Connect(); |
| |
| // Default connection window size is 64k - 1. Initial request will have |
| // used 8k (56k -1). Increase it to 57k |
| sendWindowUpdate(0, 1 + 1024); |
| |
| // Use up 56k of the connection window |
| for (int i = 3; i < 17; i += 2) { |
| sendSimpleGetRequest(i); |
| readSimpleGetResponse(); |
| } |
| |
| // Set the default window size to 1024 bytes |
| sendSettings(0, false, new SettingValue(4, 1024)); |
| // Wait for the ack |
| parser.readFrame(true); |
| // Debugging Gump failure |
| log.info(output.getTrace()); |
| output.clearTrace(); |
| |
| // At this point the connection window should be 1k and any new stream |
| // should have a window of 1k as well |
| |
| // Set up streams A=17, B=19, C=21 |
| sendPriority(17, 0, 15); |
| sendPriority(19, 17, 3); |
| sendPriority(21, 17, 11); |
| |
| // First, process a request on stream 17. This should consume both |
| // stream 17's window and the connection window. |
| sendSimpleGetRequest(17); |
| // 17-headers, 17-1k-body |
| parser.readFrame(true); |
| // Debugging Gump failure |
| log.info(output.getTrace()); |
| parser.readFrame(true); |
| // Debugging Gump failure |
| log.info(output.getTrace()); |
| output.clearTrace(); |
| |
| // Send additional requests. Connection window is empty so only headers |
| // will be returned. |
| sendSimpleGetRequest(19); |
| sendSimpleGetRequest(21); |
| |
| // Open up the flow control windows for stream 19 & 21 to more than the |
| // size of a simple request (8k) |
| sendWindowUpdate(19, 16*1024); |
| sendWindowUpdate(21, 16*1024); |
| |
| // Read some frames |
| // 19-headers, 21-headers |
| parser.readFrame(true); |
| // Debugging Gump failure |
| log.info(output.getTrace()); |
| parser.readFrame(true); |
| // Debugging Gump failure |
| log.info(output.getTrace()); |
| output.clearTrace(); |
| |
| // At this point 17 is blocked because the stream window is zero and |
| // 19 & 21 are blocked because the connection window is zero. |
| // |
| // To test allocation, the connection window size is increased by 1. |
| // This should result in an allocation of 1 byte each to streams 19 and |
| // 21 but because each stream is processed in a separate thread it is |
| // not guaranteed that both streams will be blocked when the connection |
| // window size is increased. The test therefore sends 1 byte window |
| // updates until a small body has been seen from each stream. Then the |
| // tests sends a larger (1024 byte) window update and checks that it is |
| // correctly distributed between the streams. |
| // |
| // The test includes a margin to allow for the potential differences in |
| // response caused by timing differences on the server. |
| // |
| // The loop below handles 0, 1 or 2 stream being blocked |
| // - If 0 streams are blocked the connection window will be set to one |
| // and that will be consumed by the first stream to attempt to write. |
| // That body frame will be read by the client. The stream will then be |
| // blocked and the loop will start again. |
| // - If 1 stream is blocked, the connection window will be set to one |
| // which will then be consumed by the blocked stream. After writing |
| // the single byte the stream will again be blocked and the loop will |
| // start again. |
| // - If 2 streams are blocked the connection window will be set to one |
| // but one byte will be permitted for both streams (due to rounding in |
| // the allocation). The window size should be -1 (see below). Two |
| // frames (one for each stream will be written) one of which will be |
| // consumed by the client. The loop will start again and the Window |
| // size incremented to zero. No data will be written by the streams |
| // but the second data frame written in the last iteration of the loop |
| // will be read. The loop will then exit since frames from both |
| // streams will have been observed. |
| boolean seen19 = false; |
| boolean seen21 = false; |
| while (!seen19 || !seen21) { |
| sendWindowUpdate(0, 1); |
| parser.readFrame(true); |
| // Debugging Gump failure |
| log.info(output.getTrace()); |
| int[] data = parseBodyFrame(output.getTrace()); |
| if (data[0] == 19) { |
| seen19 = true; |
| } else if (data[0] == 21) { |
| seen21 = true; |
| } else { |
| // Unexpected stream |
| Assert.fail("Unexpected stream: [" + output.getTrace() + "]"); |
| } |
| // A value of more than 1 here is unlikely but possible depending on |
| // how threads are scheduled. This has been observed as high as 12 |
| // on ci.apache.org so allow a margin and use 20. |
| if (data[1] > 20) { |
| // Larger than expected body size |
| Assert.fail("Larger than expected body: [" + output.getTrace() + "]"); |
| } |
| output.clearTrace(); |
| } |
| |
| sendWindowUpdate(0, 1024); |
| parser.readFrame(true); |
| |
| // Make sure you have read the big comment before the loop above. It is |
| // possible that the timing of the server threads is such that there are |
| // still small body frames to read. |
| int[] data = parseBodyFrame(output.getTrace()); |
| while (data[1] < 20) { |
| // Debugging Gump failure |
| log.info(output.getTrace()); |
| output.clearTrace(); |
| parser.readFrame(true); |
| data = parseBodyFrame(output.getTrace()); |
| } |
| |
| // Should now have two larger body frames. One has already been read. |
| seen19 = false; |
| seen21 = false; |
| while (!seen19 && !seen21) { |
| // Debugging Gump failure |
| log.info(output.getTrace()); |
| if (data[0] == 19) { |
| seen19 = true; |
| // If everything works instantly this should be 256 but allow a |
| // fairly large margin for timing differences |
| if (data[1] < 216 || data[1] > 296) { |
| Assert.fail("Unexpected body size: [" + output.getTrace() + "]"); |
| } |
| } else if (data[0] == 21) { |
| seen21 = true; |
| // If everything works instantly this should be 768 but allow a |
| // fairly large margin for timing differences |
| if (data[1] < 728 || data[1] > 808) { |
| Assert.fail("Unexpected body size: [" + output.getTrace() + "]"); |
| } |
| } else { |
| Assert.fail("Unexpected stream: [" + output.getTrace() + "]"); |
| } |
| output.clearTrace(); |
| parser.readFrame(true); |
| data = parseBodyFrame(output.getTrace()); |
| } |
| // Debugging Gump failure |
| log.info(output.getTrace()); |
| output.clearTrace(); |
| |
| // Release everything and read all the remaining data |
| sendWindowUpdate(0, 1024 * 1024); |
| sendWindowUpdate(17, 1024 * 1024); |
| |
| // Read remaining frames |
| // 17-7k-body, 19~8k-body, 21~8k-body |
| for (int i = 0; i < 3; i++) { |
| parser.readFrame(true); |
| // Debugging Gump failure |
| log.info(output.getTrace()); |
| } |
| } |
| |
| |
| private int[] parseBodyFrame(String output) { |
| String[] parts = output.trim().split("-"); |
| if (parts.length != 3 || !"Body".equals(parts[1])) { |
| Assert.fail("Unexpected output: [" + output + "]"); |
| } |
| |
| int[] result = new int[2]; |
| |
| result[0] = Integer.parseInt(parts[0]); |
| result[1] = Integer.parseInt(parts[2]); |
| |
| return result; |
| } |
| } |