init
This commit is contained in:
1353
test/org/apache/coyote/http2/Http2TestBase.java
Normal file
1353
test/org/apache/coyote/http2/Http2TestBase.java
Normal file
File diff suppressed because it is too large
Load Diff
129
test/org/apache/coyote/http2/TestAbortedUpload.java
Normal file
129
test/org/apache/coyote/http2/TestAbortedUpload.java
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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 java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.LifecycleException;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
|
||||
public class TestAbortedUpload extends Http2TestBase {
|
||||
|
||||
@Test
|
||||
public void testAbortedRequest() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
http2Protocol.setAllowedTrailerHeaders(TRAILER_HEADER_NAME);
|
||||
|
||||
int bodySize = 8192;
|
||||
int bodyCount = 20;
|
||||
|
||||
byte[] headersFrameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
byte[] dataFrameHeader = new byte[9];
|
||||
ByteBuffer dataPayload = ByteBuffer.allocate(bodySize);
|
||||
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
|
||||
for (int i = 0; i < bodyCount; i++) {
|
||||
writeFrame(dataFrameHeader, dataPayload);
|
||||
}
|
||||
|
||||
// Trailers
|
||||
writeFrame(trailerFrameHeader, trailerPayload);
|
||||
|
||||
// The actual response depends on timing issues. Particularly how much
|
||||
// data is transferred in StreamInputBuffer inBuffer to outBuffer on the
|
||||
// first read.
|
||||
while (output.getTrace().length() == 0) {
|
||||
try {
|
||||
parser.readFrame(true);
|
||||
if ("3-RST-[3]\n".equals(output.getTrace())) {
|
||||
output.clearTrace();
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
// Might not be any further frames after the reset
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (output.getTrace().startsWith("0-WindowSize-[")) {
|
||||
String trace = output.getTrace();
|
||||
int size = Integer.parseInt(trace.substring(14, trace.length() - 2));
|
||||
output.clearTrace();
|
||||
// Window updates always come in pairs
|
||||
parser.readFrame(true);
|
||||
Assert.assertEquals("3-WindowSize-[" + size + "]\n", output.getTrace());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void configureAndStartWebApplication() throws LifecycleException {
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// Retain '/simple' url-pattern since it enables code re-use
|
||||
Context ctxt = tomcat.addContext("", null);
|
||||
Tomcat.addServlet(ctxt, "abort", new AbortServlet());
|
||||
ctxt.addServletMappingDecoded("/simple", "abort");
|
||||
|
||||
tomcat.start();
|
||||
}
|
||||
|
||||
|
||||
private static class AbortServlet extends SimpleServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
|
||||
// Read upto 128 bytes and then return a 403 response
|
||||
|
||||
InputStream is = req.getInputStream();
|
||||
byte[] buf = new byte[128];
|
||||
int toRead = 128;
|
||||
|
||||
int read = is.read(buf);
|
||||
while (read != -1 && toRead > 0) {
|
||||
toRead -= read;
|
||||
read = is.read(buf);
|
||||
}
|
||||
|
||||
if (toRead == 0) {
|
||||
resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||
} else {
|
||||
resp.setStatus(HttpServletResponse.SC_OK);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
286
test/org/apache/coyote/http2/TestAbstractStream.java
Normal file
286
test/org/apache/coyote/http2/TestAbstractStream.java
Normal file
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/*
|
||||
* This tests use A=1, B=2, etc to map stream IDs to the names used in the
|
||||
* figures.
|
||||
*/
|
||||
public class TestAbstractStream {
|
||||
|
||||
@Test
|
||||
public void testDependenciesFig3() {
|
||||
// Setup
|
||||
Http2UpgradeHandler handler = new Http2UpgradeHandler(new Http2Protocol(), null, null);
|
||||
Stream a = new Stream(Integer.valueOf(1), handler);
|
||||
Stream b = new Stream(Integer.valueOf(2), handler);
|
||||
Stream c = new Stream(Integer.valueOf(3), handler);
|
||||
Stream d = new Stream(Integer.valueOf(4), handler);
|
||||
b.rePrioritise(a, false, 16);
|
||||
c.rePrioritise(a, false, 16);
|
||||
|
||||
// Action
|
||||
d.rePrioritise(a, false, 16);
|
||||
|
||||
// Check parents
|
||||
Assert.assertEquals(handler, a.getParentStream());
|
||||
Assert.assertEquals(a, b.getParentStream());
|
||||
Assert.assertEquals(a, c.getParentStream());
|
||||
Assert.assertEquals(a, d.getParentStream());
|
||||
|
||||
// Check children
|
||||
Assert.assertEquals(3, a.getChildStreams().size());
|
||||
Assert.assertTrue(a.getChildStreams().contains(b));
|
||||
Assert.assertTrue(a.getChildStreams().contains(c));
|
||||
Assert.assertTrue(a.getChildStreams().contains(d));
|
||||
Assert.assertEquals(0, b.getChildStreams().size());
|
||||
Assert.assertEquals(0, c.getChildStreams().size());
|
||||
Assert.assertEquals(0, d.getChildStreams().size());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDependenciesFig4() {
|
||||
// Setup
|
||||
Http2UpgradeHandler handler = new Http2UpgradeHandler(new Http2Protocol(), null, null);
|
||||
Stream a = new Stream(Integer.valueOf(1), handler);
|
||||
Stream b = new Stream(Integer.valueOf(2), handler);
|
||||
Stream c = new Stream(Integer.valueOf(3), handler);
|
||||
Stream d = new Stream(Integer.valueOf(4), handler);
|
||||
b.rePrioritise(a, false, 16);
|
||||
c.rePrioritise(a, false, 16);
|
||||
|
||||
// Action
|
||||
d.rePrioritise(a, true, 16);
|
||||
|
||||
// Check parents
|
||||
Assert.assertEquals(handler, a.getParentStream());
|
||||
Assert.assertEquals(d, b.getParentStream());
|
||||
Assert.assertEquals(d, c.getParentStream());
|
||||
Assert.assertEquals(a, d.getParentStream());
|
||||
|
||||
// Check children
|
||||
Assert.assertEquals(1, a.getChildStreams().size());
|
||||
Assert.assertTrue(a.getChildStreams().contains(d));
|
||||
Assert.assertEquals(2, d.getChildStreams().size());
|
||||
Assert.assertTrue(d.getChildStreams().contains(b));
|
||||
Assert.assertTrue(d.getChildStreams().contains(c));
|
||||
Assert.assertEquals(0, b.getChildStreams().size());
|
||||
Assert.assertEquals(0, c.getChildStreams().size());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDependenciesFig5NonExclusive() {
|
||||
// Setup
|
||||
Http2UpgradeHandler handler = new Http2UpgradeHandler(new Http2Protocol(), null, null);
|
||||
Stream a = new Stream(Integer.valueOf(1), handler);
|
||||
Stream b = new Stream(Integer.valueOf(2), handler);
|
||||
Stream c = new Stream(Integer.valueOf(3), handler);
|
||||
Stream d = new Stream(Integer.valueOf(4), handler);
|
||||
Stream e = new Stream(Integer.valueOf(5), handler);
|
||||
Stream f = new Stream(Integer.valueOf(6), handler);
|
||||
b.rePrioritise(a, false, 16);
|
||||
c.rePrioritise(a, false, 16);
|
||||
d.rePrioritise(c, false, 16);
|
||||
e.rePrioritise(c, false, 16);
|
||||
f.rePrioritise(d, false, 16);
|
||||
|
||||
// Action
|
||||
a.rePrioritise(d, false, 16);
|
||||
|
||||
// Check parents
|
||||
Assert.assertEquals(handler, d.getParentStream());
|
||||
Assert.assertEquals(d, f.getParentStream());
|
||||
Assert.assertEquals(d, a.getParentStream());
|
||||
Assert.assertEquals(a, b.getParentStream());
|
||||
Assert.assertEquals(a, c.getParentStream());
|
||||
Assert.assertEquals(c, e.getParentStream());
|
||||
|
||||
// Check children
|
||||
Assert.assertEquals(2, d.getChildStreams().size());
|
||||
Assert.assertTrue(d.getChildStreams().contains(a));
|
||||
Assert.assertTrue(d.getChildStreams().contains(f));
|
||||
Assert.assertEquals(0, f.getChildStreams().size());
|
||||
Assert.assertEquals(2, a.getChildStreams().size());
|
||||
Assert.assertTrue(a.getChildStreams().contains(b));
|
||||
Assert.assertTrue(a.getChildStreams().contains(c));
|
||||
Assert.assertEquals(0, b.getChildStreams().size());
|
||||
Assert.assertEquals(1, c.getChildStreams().size());
|
||||
Assert.assertTrue(c.getChildStreams().contains(e));
|
||||
Assert.assertEquals(0, e.getChildStreams().size());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDependenciesFig5Exclusive() {
|
||||
// Setup
|
||||
Http2UpgradeHandler handler = new Http2UpgradeHandler(new Http2Protocol(), null, null);
|
||||
Stream a = new Stream(Integer.valueOf(1), handler);
|
||||
Stream b = new Stream(Integer.valueOf(2), handler);
|
||||
Stream c = new Stream(Integer.valueOf(3), handler);
|
||||
Stream d = new Stream(Integer.valueOf(4), handler);
|
||||
Stream e = new Stream(Integer.valueOf(5), handler);
|
||||
Stream f = new Stream(Integer.valueOf(6), handler);
|
||||
b.rePrioritise(a, false, 16);
|
||||
c.rePrioritise(a, false, 16);
|
||||
d.rePrioritise(c, false, 16);
|
||||
e.rePrioritise(c, false, 16);
|
||||
f.rePrioritise(d, false, 16);
|
||||
|
||||
// Action
|
||||
a.rePrioritise(d, true, 16);
|
||||
|
||||
// Check parents
|
||||
Assert.assertEquals(handler, d.getParentStream());
|
||||
Assert.assertEquals(d, a.getParentStream());
|
||||
Assert.assertEquals(a, b.getParentStream());
|
||||
Assert.assertEquals(a, c.getParentStream());
|
||||
Assert.assertEquals(a, f.getParentStream());
|
||||
Assert.assertEquals(c, e.getParentStream());
|
||||
|
||||
// Check children
|
||||
Assert.assertEquals(1, d.getChildStreams().size());
|
||||
Assert.assertTrue(d.getChildStreams().contains(a));
|
||||
Assert.assertEquals(3, a.getChildStreams().size());
|
||||
Assert.assertTrue(a.getChildStreams().contains(b));
|
||||
Assert.assertTrue(a.getChildStreams().contains(c));
|
||||
Assert.assertTrue(a.getChildStreams().contains(f));
|
||||
Assert.assertEquals(0, b.getChildStreams().size());
|
||||
Assert.assertEquals(0, f.getChildStreams().size());
|
||||
Assert.assertEquals(1, c.getChildStreams().size());
|
||||
Assert.assertTrue(c.getChildStreams().contains(e));
|
||||
Assert.assertEquals(0, e.getChildStreams().size());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCircular01() {
|
||||
// Setup
|
||||
Http2UpgradeHandler handler = new Http2UpgradeHandler(new Http2Protocol(), null, null);
|
||||
Stream a = new Stream(Integer.valueOf(1), handler);
|
||||
Stream b = new Stream(Integer.valueOf(2), handler);
|
||||
Stream c = new Stream(Integer.valueOf(3), handler);
|
||||
|
||||
b.rePrioritise(a, false, 16);
|
||||
c.rePrioritise(b, false, 16);
|
||||
|
||||
// Action
|
||||
a.rePrioritise(c, false, 16);
|
||||
|
||||
// Check parents
|
||||
Assert.assertEquals(c, a.getParentStream());
|
||||
Assert.assertEquals(a, b.getParentStream());
|
||||
Assert.assertEquals(handler, c.getParentStream());
|
||||
|
||||
// Check children
|
||||
Assert.assertEquals(1, handler.getChildStreams().size());
|
||||
Assert.assertTrue(handler.getChildStreams().contains(c));
|
||||
Assert.assertEquals(1, a.getChildStreams().size());
|
||||
Assert.assertTrue(a.getChildStreams().contains(b));
|
||||
Assert.assertEquals(0, b.getChildStreams().size());
|
||||
Assert.assertEquals(1, c.getChildStreams().size());
|
||||
Assert.assertTrue(c.getChildStreams().contains(a));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCircular02() {
|
||||
// Setup
|
||||
Http2UpgradeHandler handler = new Http2UpgradeHandler(new Http2Protocol(), null, null);
|
||||
Stream a = new Stream(Integer.valueOf(1), handler);
|
||||
Stream b = new Stream(Integer.valueOf(2), handler);
|
||||
Stream c = new Stream(Integer.valueOf(3), handler);
|
||||
Stream d = new Stream(Integer.valueOf(4), handler);
|
||||
Stream e = new Stream(Integer.valueOf(5), handler);
|
||||
Stream f = new Stream(Integer.valueOf(6), handler);
|
||||
|
||||
b.rePrioritise(a, false, 16);
|
||||
c.rePrioritise(b, false, 16);
|
||||
e.rePrioritise(d, false, 16);
|
||||
f.rePrioritise(e, false, 16);
|
||||
|
||||
// Action
|
||||
a.rePrioritise(f, false, 16);
|
||||
d.rePrioritise(c, false, 16);
|
||||
|
||||
// Check parents
|
||||
Assert.assertEquals(f, a.getParentStream());
|
||||
Assert.assertEquals(a, b.getParentStream());
|
||||
Assert.assertEquals(handler, c.getParentStream());
|
||||
Assert.assertEquals(c, d.getParentStream());
|
||||
Assert.assertEquals(d, e.getParentStream());
|
||||
Assert.assertEquals(e, f.getParentStream());
|
||||
|
||||
// Check children
|
||||
Assert.assertEquals(1, handler.getChildStreams().size());
|
||||
Assert.assertTrue(handler.getChildStreams().contains(c));
|
||||
Assert.assertEquals(1, a.getChildStreams().size());
|
||||
Assert.assertTrue(a.getChildStreams().contains(b));
|
||||
Assert.assertEquals(0, b.getChildStreams().size());
|
||||
Assert.assertEquals(1, c.getChildStreams().size());
|
||||
Assert.assertTrue(c.getChildStreams().contains(d));
|
||||
Assert.assertEquals(1, d.getChildStreams().size());
|
||||
Assert.assertTrue(d.getChildStreams().contains(e));
|
||||
Assert.assertEquals(1, e.getChildStreams().size());
|
||||
Assert.assertTrue(e.getChildStreams().contains(f));
|
||||
Assert.assertEquals(1, f.getChildStreams().size());
|
||||
Assert.assertTrue(f.getChildStreams().contains(a));
|
||||
}
|
||||
|
||||
|
||||
// https://bz.apache.org/bugzilla/show_bug.cgi?id=61682
|
||||
@Test
|
||||
public void testCircular03() {
|
||||
// Setup
|
||||
Http2UpgradeHandler handler = new Http2UpgradeHandler(new Http2Protocol(), null, null);
|
||||
Stream a = new Stream(Integer.valueOf(1), handler);
|
||||
Stream b = new Stream(Integer.valueOf(3), handler);
|
||||
Stream c = new Stream(Integer.valueOf(5), handler);
|
||||
Stream d = new Stream(Integer.valueOf(7), handler);
|
||||
|
||||
// Action
|
||||
b.rePrioritise(a, false, 16);
|
||||
c.rePrioritise(a, false, 16);
|
||||
d.rePrioritise(b, false, 16);
|
||||
c.rePrioritise(handler, false, 16);
|
||||
a.rePrioritise(c, false, 16);
|
||||
|
||||
// Check parents
|
||||
Assert.assertEquals(c, a.getParentStream());
|
||||
Assert.assertEquals(a, b.getParentStream());
|
||||
Assert.assertEquals(handler, c.getParentStream());
|
||||
Assert.assertEquals(b, d.getParentStream());
|
||||
|
||||
// This triggers the StackOverflowError
|
||||
Assert.assertTrue(c.isDescendant(d));
|
||||
|
||||
// Check children
|
||||
Assert.assertEquals(1, handler.getChildStreams().size());
|
||||
Assert.assertTrue(handler.getChildStreams().contains(c));
|
||||
Assert.assertEquals(1, c.getChildStreams().size());
|
||||
Assert.assertTrue(c.getChildStreams().contains(a));
|
||||
Assert.assertEquals(1, a.getChildStreams().size());
|
||||
Assert.assertTrue(a.getChildStreams().contains(b));
|
||||
Assert.assertEquals(1, b.getChildStreams().size());
|
||||
Assert.assertTrue(b.getChildStreams().contains(d));
|
||||
Assert.assertEquals(0, d.getChildStreams().size());
|
||||
}
|
||||
}
|
||||
277
test/org/apache/coyote/http2/TestAsync.java
Normal file
277
test/org/apache/coyote/http2/TestAsync.java
Normal file
@@ -0,0 +1,277 @@
|
||||
/*
|
||||
* 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 java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.WriteListener;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.Wrapper;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
|
||||
/*
|
||||
* Based on
|
||||
* https://bz.apache.org/bugzilla/show_bug.cgi?id=62614
|
||||
* https://bz.apache.org/bugzilla/show_bug.cgi?id=62620
|
||||
* https://bz.apache.org/bugzilla/show_bug.cgi?id=62628
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class TestAsync extends Http2TestBase {
|
||||
|
||||
private static final int BLOCK_SIZE = 0x8000;
|
||||
|
||||
@Parameterized.Parameters(name = "{index}: expandConnectionFirst[{0}], " +
|
||||
"connectionUnlimited[{1}], streamUnlimited[{2}], useNonContainerThreadForWrite[{3}]," +
|
||||
"largeInitialWindow[{4}]")
|
||||
public static Collection<Object[]> parameters() {
|
||||
List<Object[]> parameterSets = new ArrayList<>();
|
||||
|
||||
for (Boolean expandConnectionFirst : booleans) {
|
||||
for (Boolean connectionUnlimited : booleans) {
|
||||
for (Boolean streamUnlimited : booleans) {
|
||||
for (Boolean useNonContainerThreadForWrite : booleans) {
|
||||
for (Boolean largeInitialWindow : booleans) {
|
||||
parameterSets.add(new Object[] {
|
||||
expandConnectionFirst, connectionUnlimited, streamUnlimited,
|
||||
useNonContainerThreadForWrite, largeInitialWindow
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return parameterSets;
|
||||
}
|
||||
|
||||
|
||||
private final boolean expandConnectionFirst;
|
||||
private final boolean connectionUnlimited;
|
||||
private final boolean streamUnlimited;
|
||||
private final boolean useNonContainerThreadForWrite;
|
||||
private final boolean largeInitialWindow;
|
||||
|
||||
|
||||
public TestAsync(boolean expandConnectionFirst, boolean connectionUnlimited,
|
||||
boolean streamUnlimited, boolean useNonContainerThreadForWrite,
|
||||
boolean largeInitialWindow) {
|
||||
this.expandConnectionFirst = expandConnectionFirst;
|
||||
this.connectionUnlimited = connectionUnlimited;
|
||||
this.streamUnlimited = streamUnlimited;
|
||||
this.useNonContainerThreadForWrite = useNonContainerThreadForWrite;
|
||||
this.largeInitialWindow = largeInitialWindow;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEmptyWindow() throws Exception {
|
||||
int blockCount = 8;
|
||||
|
||||
enableHttp2();
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context ctxt = tomcat.addContext("", null);
|
||||
Tomcat.addServlet(ctxt, "simple", new SimpleServlet());
|
||||
ctxt.addServletMappingDecoded("/simple", "simple");
|
||||
Wrapper w = Tomcat.addServlet(ctxt, "async",
|
||||
new AsyncServlet(blockCount, useNonContainerThreadForWrite));
|
||||
w.setAsyncSupported(true);
|
||||
ctxt.addServletMappingDecoded("/async", "async");
|
||||
tomcat.start();
|
||||
|
||||
int startingWindowSize;
|
||||
|
||||
openClientConnection();
|
||||
doHttpUpgrade();
|
||||
sendClientPreface();
|
||||
validateHttp2InitialResponse();
|
||||
|
||||
// Reset connection window size after initial response
|
||||
sendWindowUpdate(0, SimpleServlet.CONTENT_LENGTH);
|
||||
|
||||
if (largeInitialWindow) {
|
||||
startingWindowSize = ((1 << 17) - 1);
|
||||
SettingValue sv =
|
||||
new SettingValue(Setting.INITIAL_WINDOW_SIZE.getId(), startingWindowSize);
|
||||
sendSettings(0, false, sv);
|
||||
// Test code assumes connection window and stream window size are the same at the start
|
||||
sendWindowUpdate(0, startingWindowSize - ConnectionSettingsBase.DEFAULT_INITIAL_WINDOW_SIZE);
|
||||
} else {
|
||||
startingWindowSize = ConnectionSettingsBase.DEFAULT_INITIAL_WINDOW_SIZE;
|
||||
}
|
||||
|
||||
byte[] frameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
buildGetRequest(frameHeader, headersPayload, null, 3, "/async");
|
||||
writeFrame(frameHeader, headersPayload);
|
||||
|
||||
if (connectionUnlimited) {
|
||||
// Effectively unlimited for this test
|
||||
sendWindowUpdate(0, blockCount * BLOCK_SIZE * 2);
|
||||
}
|
||||
if (streamUnlimited) {
|
||||
// Effectively unlimited for this test
|
||||
sendWindowUpdate(3, blockCount * BLOCK_SIZE * 2);
|
||||
}
|
||||
|
||||
// Headers
|
||||
parser.readFrame(true);
|
||||
// Body
|
||||
|
||||
if (!connectionUnlimited || !streamUnlimited) {
|
||||
while (output.getBytesRead() < startingWindowSize) {
|
||||
parser.readFrame(true);
|
||||
}
|
||||
|
||||
// Check that the right number of bytes were received
|
||||
Assert.assertEquals(startingWindowSize, output.getBytesRead());
|
||||
|
||||
// Increase the Window size (50% of total body)
|
||||
int windowSizeIncrease = blockCount * BLOCK_SIZE / 2;
|
||||
if (expandConnectionFirst) {
|
||||
sendWindowUpdate(0, windowSizeIncrease);
|
||||
sendWindowUpdate(3, windowSizeIncrease);
|
||||
} else {
|
||||
sendWindowUpdate(3, windowSizeIncrease);
|
||||
sendWindowUpdate(0, windowSizeIncrease);
|
||||
}
|
||||
|
||||
while (output.getBytesRead() < startingWindowSize + windowSizeIncrease) {
|
||||
parser.readFrame(true);
|
||||
}
|
||||
|
||||
// Check that the right number of bytes were received
|
||||
Assert.assertEquals(startingWindowSize + windowSizeIncrease, output.getBytesRead());
|
||||
|
||||
// Increase the Window size
|
||||
if (expandConnectionFirst) {
|
||||
sendWindowUpdate(0, windowSizeIncrease);
|
||||
sendWindowUpdate(3, windowSizeIncrease);
|
||||
} else {
|
||||
sendWindowUpdate(3, windowSizeIncrease);
|
||||
sendWindowUpdate(0, windowSizeIncrease);
|
||||
}
|
||||
}
|
||||
|
||||
while (!output.getTrace().endsWith("3-EndOfStream\n")) {
|
||||
parser.readFrame(true);
|
||||
}
|
||||
|
||||
// Check that the right number of bytes were received
|
||||
Assert.assertEquals((long) blockCount * BLOCK_SIZE, output.getBytesRead());
|
||||
}
|
||||
|
||||
|
||||
public static class AsyncServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final int blockLimit;
|
||||
private final boolean useNonContainerThreadForWrite;
|
||||
private final transient ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
private transient volatile Future<?> future;
|
||||
|
||||
public AsyncServlet(int blockLimit, boolean useNonContainerThreadForWrite) {
|
||||
this.blockLimit = blockLimit;
|
||||
this.useNonContainerThreadForWrite = useNonContainerThreadForWrite;
|
||||
}
|
||||
|
||||
/*
|
||||
* Not thread-safe. OK for this test. NOt OK for use in the real world.
|
||||
*/
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws IOException {
|
||||
|
||||
final AsyncContext asyncContext = request.startAsync();
|
||||
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
response.setContentType("application/binary");
|
||||
|
||||
final ServletOutputStream output = response.getOutputStream();
|
||||
output.setWriteListener(new WriteListener() {
|
||||
|
||||
// Intermittent CI errors were observed where the response body
|
||||
// was exactly one block too small. Use an AtomicInteger to be
|
||||
// sure blockCount is thread-safe.
|
||||
final AtomicInteger blockCount = new AtomicInteger(0);
|
||||
byte[] bytes = new byte[BLOCK_SIZE];
|
||||
|
||||
|
||||
@Override
|
||||
public void onWritePossible() throws IOException {
|
||||
if (useNonContainerThreadForWrite) {
|
||||
future = scheduler.schedule(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
write();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}, 200, TimeUnit.MILLISECONDS);
|
||||
} else {
|
||||
write();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void write() throws IOException {
|
||||
while (output.isReady()) {
|
||||
blockCount.incrementAndGet();
|
||||
output.write(bytes);
|
||||
if (blockCount.get() == blockLimit) {
|
||||
asyncContext.complete();
|
||||
scheduler.shutdown();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable t) {
|
||||
if (future != null) {
|
||||
future.cancel(false);
|
||||
}
|
||||
t.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
154
test/org/apache/coyote/http2/TestAsyncFlush.java
Normal file
154
test/org/apache/coyote/http2/TestAsyncFlush.java
Normal file
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* 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 java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.WriteListener;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.Wrapper;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
|
||||
/*
|
||||
* Based on
|
||||
* https://bz.apache.org/bugzilla/show_bug.cgi?id=62635
|
||||
*
|
||||
* Note: Calling blocking I/O methods (such as flushBuffer()) during
|
||||
* non-blocking I/O is explicitly called out as illegal in the Servlet
|
||||
* specification but also goes on to say the behaviour if such a call is
|
||||
* made is undefined. Which means it is OK if the call works as expected
|
||||
* (a non-blocking flush is triggered) :).
|
||||
* If any of these tests fail, that should not block a release since -
|
||||
* while the specification allows this to work - it doesn't require that
|
||||
* it does work.
|
||||
*/
|
||||
public class TestAsyncFlush extends Http2TestBase {
|
||||
|
||||
private static final int BLOCK_SIZE = 1024;
|
||||
|
||||
@Test
|
||||
public void testFlush() throws Exception {
|
||||
int blockCount = 2048;
|
||||
|
||||
int targetSize = BLOCK_SIZE * blockCount;
|
||||
|
||||
int totalWindow = ConnectionSettingsBase.DEFAULT_INITIAL_WINDOW_SIZE;
|
||||
|
||||
enableHttp2();
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context ctxt = tomcat.addContext("", null);
|
||||
Tomcat.addServlet(ctxt, "simple", new SimpleServlet());
|
||||
ctxt.addServletMappingDecoded("/simple", "simple");
|
||||
Wrapper w = Tomcat.addServlet(ctxt, "async", new AsyncFlushServlet(blockCount));
|
||||
w.setAsyncSupported(true);
|
||||
ctxt.addServletMappingDecoded("/async", "async");
|
||||
tomcat.start();
|
||||
|
||||
openClientConnection();
|
||||
doHttpUpgrade();
|
||||
sendClientPreface();
|
||||
validateHttp2InitialResponse();
|
||||
|
||||
// Reset connection window size after intial response
|
||||
sendWindowUpdate(0, SimpleServlet.CONTENT_LENGTH);
|
||||
|
||||
// Send request
|
||||
byte[] frameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
buildGetRequest(frameHeader, headersPayload, null, 3, "/async");
|
||||
writeFrame(frameHeader, headersPayload);
|
||||
|
||||
// Headers
|
||||
parser.readFrame(true);
|
||||
// Body
|
||||
|
||||
while (output.getBytesRead() < targetSize ) {
|
||||
if (output.getBytesRead() == totalWindow) {
|
||||
sendWindowUpdate(3, ConnectionSettingsBase.DEFAULT_INITIAL_WINDOW_SIZE);
|
||||
sendWindowUpdate(0, ConnectionSettingsBase.DEFAULT_INITIAL_WINDOW_SIZE);
|
||||
totalWindow += ConnectionSettingsBase.DEFAULT_INITIAL_WINDOW_SIZE;
|
||||
}
|
||||
parser.readFrame(true);
|
||||
}
|
||||
|
||||
// Check that the right number of bytes were received
|
||||
Assert.assertEquals(targetSize, output.getBytesRead());
|
||||
}
|
||||
|
||||
|
||||
public static class AsyncFlushServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final int blockLimit;
|
||||
|
||||
public AsyncFlushServlet(int blockLimit) {
|
||||
this.blockLimit = blockLimit;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(final HttpServletRequest request, final HttpServletResponse response)
|
||||
throws IOException {
|
||||
|
||||
final AsyncContext asyncContext = request.startAsync();
|
||||
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
response.setContentType("application/binary");
|
||||
|
||||
final ServletOutputStream output = response.getOutputStream();
|
||||
output.setWriteListener(new WriteListener() {
|
||||
|
||||
int blockCount;
|
||||
byte[] bytes = new byte[BLOCK_SIZE];
|
||||
|
||||
|
||||
@Override
|
||||
public void onWritePossible() throws IOException {
|
||||
while (output.isReady()) {
|
||||
blockCount++;
|
||||
output.write(bytes);
|
||||
if (blockCount % 5 == 0) {
|
||||
response.flushBuffer();
|
||||
}
|
||||
if (blockCount == blockLimit) {
|
||||
asyncContext.complete();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onError(Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
226
test/org/apache/coyote/http2/TestAsyncTimeout.java
Normal file
226
test/org/apache/coyote/http2/TestAsyncTimeout.java
Normal file
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* 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 java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.AsyncEvent;
|
||||
import javax.servlet.AsyncListener;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.Wrapper;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
|
||||
public class TestAsyncTimeout extends Http2TestBase {
|
||||
|
||||
@Test
|
||||
public void testTimeout() throws Exception {
|
||||
enableHttp2();
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context ctxt = tomcat.addContext("", null);
|
||||
// This is the target of the HTTP/2 upgrade request
|
||||
Tomcat.addServlet(ctxt, "simple", new SimpleServlet());
|
||||
ctxt.addServletMappingDecoded("/simple", "simple");
|
||||
|
||||
// This is the servlet that does that actual test
|
||||
// This latch is used to signal that that async thread used by the test
|
||||
// has ended. It isn;t essential to the test but it allows the test to
|
||||
// complete without Tmcat logging an error about a still running thread.
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Wrapper w = Tomcat.addServlet(ctxt, "async", new AsyncTimeoutServlet(latch));
|
||||
w.setAsyncSupported(true);
|
||||
ctxt.addServletMappingDecoded("/async", "async");
|
||||
tomcat.start();
|
||||
|
||||
openClientConnection();
|
||||
doHttpUpgrade();
|
||||
sendClientPreface();
|
||||
validateHttp2InitialResponse();
|
||||
|
||||
// Reset connection window size after initial response
|
||||
sendWindowUpdate(0, SimpleServlet.CONTENT_LENGTH);
|
||||
|
||||
// Include the response body in the trace so we can check for the PASS /
|
||||
// FAIL text.
|
||||
output.setTraceBody(true);
|
||||
|
||||
// Send request
|
||||
byte[] frameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
buildGetRequest(frameHeader, headersPayload, null, 3, "/async");
|
||||
writeFrame(frameHeader, headersPayload);
|
||||
|
||||
// Headers
|
||||
parser.readFrame(true);
|
||||
// Body
|
||||
parser.readFrame(true);
|
||||
|
||||
// Check that the expected text was received
|
||||
String trace = output.getTrace();
|
||||
Assert.assertTrue(trace, trace.contains("PASS"));
|
||||
|
||||
latch.await(10, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
|
||||
public static class AsyncTimeoutServlet extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final CountDownLatch latch;
|
||||
|
||||
public AsyncTimeoutServlet(CountDownLatch latch) {
|
||||
this.latch = latch;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws IOException {
|
||||
|
||||
// The idea of this test is that the timeout kicks in after 2
|
||||
// seconds and stops the async thread early rather than letting it
|
||||
// complete the full 5 seconds of processing.
|
||||
final AsyncContext asyncContext = request.startAsync();
|
||||
|
||||
response.setStatus(HttpServletResponse.SC_OK);
|
||||
response.setContentType("text/plain");
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
|
||||
// Only want to call complete() once (else we get stack traces in
|
||||
// the logs so use this to track when complete() is called).
|
||||
AtomicBoolean completeCalled = new AtomicBoolean(false);
|
||||
Ticker ticker = new Ticker(asyncContext, completeCalled);
|
||||
TimeoutListener listener = new TimeoutListener(latch, ticker, completeCalled);
|
||||
asyncContext.addListener(listener);
|
||||
asyncContext.setTimeout(2000);
|
||||
ticker.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class Ticker extends Thread {
|
||||
|
||||
private final AsyncContext asyncContext;
|
||||
private final AtomicBoolean completeCalled;
|
||||
private volatile boolean running = true;
|
||||
|
||||
public Ticker(AsyncContext asyncContext, AtomicBoolean completeCalled) {
|
||||
this.asyncContext = asyncContext;
|
||||
this.completeCalled = completeCalled;
|
||||
}
|
||||
|
||||
public void end() {
|
||||
running = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
PrintWriter pw = asyncContext.getResponse().getWriter();
|
||||
int counter = 0;
|
||||
|
||||
// If the test works running will be set too false before
|
||||
// counter reaches 50.
|
||||
while (running && counter < 50) {
|
||||
Thread.sleep(100);
|
||||
counter++;
|
||||
pw.print("Tick " + counter);
|
||||
}
|
||||
// Need to call complete() here if the test fails but complete()
|
||||
// should have been called by the listener. Use the flag to make
|
||||
// sure we only call complete once.
|
||||
if (completeCalled.compareAndSet(false, true)) {
|
||||
asyncContext.complete();
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class TimeoutListener implements AsyncListener {
|
||||
|
||||
private final AtomicBoolean ended = new AtomicBoolean(false);
|
||||
private final CountDownLatch latch;
|
||||
private final Ticker ticker;
|
||||
private final AtomicBoolean completeCalled;
|
||||
|
||||
public TimeoutListener(CountDownLatch latch, Ticker ticker, AtomicBoolean completeCalled) {
|
||||
this.latch = latch;
|
||||
this.ticker = ticker;
|
||||
this.completeCalled = completeCalled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTimeout(AsyncEvent event) throws IOException {
|
||||
ticker.end();
|
||||
if (ended.compareAndSet(false, true)) {
|
||||
PrintWriter pw = event.getAsyncContext().getResponse().getWriter();
|
||||
pw.write("PASS");
|
||||
pw.flush();
|
||||
// If the timeout fires we should always need to call complete()
|
||||
// here but use the flag to be safe.
|
||||
if (completeCalled.compareAndSet(false, true)) {
|
||||
event.getAsyncContext().complete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStartAsync(AsyncEvent event) throws IOException {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(AsyncEvent event) throws IOException {
|
||||
// NO-OP
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComplete(AsyncEvent event) throws IOException {
|
||||
if (ended.compareAndSet(false, true)) {
|
||||
PrintWriter pw = event.getAsyncContext().getResponse().getWriter();
|
||||
pw.write("FAIL");
|
||||
pw.flush();
|
||||
}
|
||||
try {
|
||||
// Wait for the async thread to end before we signal that the
|
||||
// test is complete. This avoids logging an exception about a
|
||||
// still running thread when the unit test shuts down.
|
||||
ticker.join();
|
||||
latch.countDown();
|
||||
} catch (InterruptedException e) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
39
test/org/apache/coyote/http2/TestByteUtil.java
Normal file
39
test/org/apache/coyote/http2/TestByteUtil.java
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public class TestByteUtil {
|
||||
|
||||
@Test
|
||||
public void testGet31Bits() {
|
||||
byte[] input = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
|
||||
int result = ByteUtil.get31Bits(input, 0);
|
||||
Assert.assertEquals(0x7fffffff, result);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testGetFourBytes() {
|
||||
byte[] input = new byte[] { (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
|
||||
long result = ByteUtil.getFourBytes(input, 0);
|
||||
Assert.assertEquals(0xffffffffL, result);
|
||||
}
|
||||
|
||||
}
|
||||
146
test/org/apache/coyote/http2/TestHpack.java
Normal file
146
test/org/apache/coyote/http2/TestHpack.java
Normal file
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* 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 java.nio.ByteBuffer;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.tomcat.util.http.MimeHeaders;
|
||||
|
||||
public class TestHpack {
|
||||
|
||||
@Test
|
||||
public void testEncode() throws Exception {
|
||||
MimeHeaders headers = new MimeHeaders();
|
||||
headers.setValue("header1").setString("value1");
|
||||
headers.setValue(":status").setString("200");
|
||||
headers.setValue("header2").setString("value2");
|
||||
ByteBuffer output = ByteBuffer.allocate(512);
|
||||
HpackEncoder encoder = new HpackEncoder();
|
||||
encoder.encode(headers, output);
|
||||
output.flip();
|
||||
// Size is supposed to be 33 without huffman, or 27 with it
|
||||
// TODO: use the HpackHeaderFunction to enable huffman predictably
|
||||
Assert.assertEquals(27, output.remaining());
|
||||
output.clear();
|
||||
encoder.encode(headers, output);
|
||||
output.flip();
|
||||
// Size is now 3 after using the table
|
||||
Assert.assertEquals(3, output.remaining());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDecode() throws Exception {
|
||||
MimeHeaders headers = new MimeHeaders();
|
||||
headers.setValue("header1").setString("value1");
|
||||
headers.setValue(":status").setString("200");
|
||||
headers.setValue("header2").setString("value2");
|
||||
ByteBuffer output = ByteBuffer.allocate(512);
|
||||
HpackEncoder encoder = new HpackEncoder();
|
||||
encoder.encode(headers, output);
|
||||
output.flip();
|
||||
MimeHeaders headers2 = new MimeHeaders();
|
||||
HpackDecoder decoder = new HpackDecoder();
|
||||
decoder.setHeaderEmitter(new HeadersListener(headers2));
|
||||
decoder.decode(output);
|
||||
// Redo (table is supposed to be updated)
|
||||
output.clear();
|
||||
encoder.encode(headers, output);
|
||||
output.flip();
|
||||
headers2.recycle();
|
||||
Assert.assertEquals(3, output.remaining());
|
||||
// Check that the decoder is using the table right
|
||||
decoder.decode(output);
|
||||
Assert.assertEquals("value2", headers2.getHeader("header2"));
|
||||
}
|
||||
|
||||
private static class HeadersListener implements HpackDecoder.HeaderEmitter {
|
||||
private final MimeHeaders headers;
|
||||
public HeadersListener(MimeHeaders headers) {
|
||||
this.headers = headers;
|
||||
}
|
||||
@Override
|
||||
public void emitHeader(String name, String value) {
|
||||
headers.setValue(name).setString(value);
|
||||
}
|
||||
@Override
|
||||
public void setHeaderException(StreamException streamException) {
|
||||
// NO-OP
|
||||
}
|
||||
@Override
|
||||
public void validateHeaders() throws StreamException {
|
||||
// NO-OP
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeaderValueBug60451() throws HpackException {
|
||||
doTestHeaderValueBug60451("fooébar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeaderValueFullRange() {
|
||||
for (int i = 0; i < 256; i++) {
|
||||
// Skip the control characters except VTAB
|
||||
if (i == 9 || i > 31 && i < 127 || i > 127) {
|
||||
try {
|
||||
doTestHeaderValueBug60451("foo" + Character.toString((char) i) + "bar");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Assert.fail(e.getMessage() + "[" + i + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(expected=HpackException.class)
|
||||
public void testExcessiveStringLiteralPadding() throws Exception {
|
||||
MimeHeaders headers = new MimeHeaders();
|
||||
headers.setValue("X-test").setString("foobar");
|
||||
ByteBuffer output = ByteBuffer.allocate(512);
|
||||
HpackEncoder encoder = new HpackEncoder();
|
||||
encoder.encode(headers, output);
|
||||
// Hack the output buffer to extend the EOS marker for the header value
|
||||
// by another byte
|
||||
output.array()[7] = (byte) -122;
|
||||
output.put((byte) -1);
|
||||
output.flip();
|
||||
MimeHeaders headers2 = new MimeHeaders();
|
||||
HpackDecoder decoder = new HpackDecoder();
|
||||
decoder.setHeaderEmitter(new HeadersListener(headers2));
|
||||
decoder.decode(output);
|
||||
}
|
||||
|
||||
|
||||
private void doTestHeaderValueBug60451(String filename) throws HpackException {
|
||||
String headerName = "Content-Disposition";
|
||||
String headerValue = "attachment;filename=\"" + filename + "\"";
|
||||
MimeHeaders headers = new MimeHeaders();
|
||||
headers.setValue(headerName).setString(headerValue);
|
||||
ByteBuffer output = ByteBuffer.allocate(512);
|
||||
HpackEncoder encoder = new HpackEncoder();
|
||||
encoder.encode(headers, output);
|
||||
output.flip();
|
||||
MimeHeaders headers2 = new MimeHeaders();
|
||||
HpackDecoder decoder = new HpackDecoder();
|
||||
decoder.setHeaderEmitter(new HeadersListener(headers2));
|
||||
decoder.decode(output);
|
||||
Assert.assertEquals(headerValue, headers2.getHeader(headerName));
|
||||
}
|
||||
}
|
||||
174
test/org/apache/coyote/http2/TestHttp2InitialConnection.java
Normal file
174
test/org/apache/coyote/http2/TestHttp2InitialConnection.java
Normal file
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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 java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.util.ServerInfo;
|
||||
import org.apache.catalina.valves.ErrorReportValve;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
public class TestHttp2InitialConnection extends Http2TestBase {
|
||||
|
||||
private TestData testData;
|
||||
|
||||
|
||||
@Test
|
||||
public void testValidHostHeader() throws Exception {
|
||||
List<String> hostHeaders = new ArrayList<>(1);
|
||||
hostHeaders.add("localhost:8080");
|
||||
|
||||
testData = new TestData(hostHeaders, 200);
|
||||
|
||||
http2Connect();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMultipleHostHeaders() throws Exception {
|
||||
List<String> hostHeaders = new ArrayList<>(1);
|
||||
hostHeaders.add("localhost:8080");
|
||||
hostHeaders.add("localhost:8081");
|
||||
|
||||
testData = new TestData(hostHeaders, 400);
|
||||
|
||||
http2Connect();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testNoHostHeader() throws Exception {
|
||||
List<String> hostHeaders = new ArrayList<>(1);
|
||||
|
||||
testData = new TestData(hostHeaders, 400);
|
||||
http2Connect();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void doHttpUpgrade(String connection, String upgrade, String settings,
|
||||
boolean validate) throws IOException {
|
||||
StringBuilder request = new StringBuilder();
|
||||
request.append("GET /simple HTTP/1.1\r\n");
|
||||
for (String hostHeader : testData.getHostHeaders()) {
|
||||
request.append("Host: ");
|
||||
request.append(hostHeader);
|
||||
request.append("\r\n");
|
||||
}
|
||||
// Connection
|
||||
request.append("Connection: ");
|
||||
request.append(connection);
|
||||
request.append("\r\n");
|
||||
// Upgrade
|
||||
request.append("Upgrade: ");
|
||||
request.append(upgrade);
|
||||
request.append("\r\n");
|
||||
// Settings
|
||||
request.append(settings);
|
||||
// Locale - Force the en Locale else the i18n on the error page changes
|
||||
// the size of the response body and that triggers a failure as the test
|
||||
// checks the exact response length
|
||||
request.append("Accept-Language: en\r\n");
|
||||
// Request terminator
|
||||
request.append("\r\n");
|
||||
|
||||
byte[] upgradeRequest = request.toString().getBytes(StandardCharsets.ISO_8859_1);
|
||||
os.write(upgradeRequest);
|
||||
os.flush();
|
||||
|
||||
if (validate) {
|
||||
Assert.assertTrue("Failed to read HTTP Upgrade response",
|
||||
readHttpUpgradeResponse());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected String getResponseBodyFrameTrace(int streamId, String body) {
|
||||
if (testData.getExpectedStatus() == 200) {
|
||||
return super.getResponseBodyFrameTrace(streamId, body);
|
||||
} else if (testData.getExpectedStatus() == 400) {
|
||||
/*
|
||||
* Need to be careful here. The test wants the exact content length
|
||||
* in bytes.
|
||||
* This will vary depending on where the test is run due to:
|
||||
* - The length of the version string that appears once in the error
|
||||
* page
|
||||
* - The status header uses a UTF-8 EN dash. When running in an IDE
|
||||
* the UTF-8 properties files will be used directly rather than
|
||||
* after native2ascii conversion.
|
||||
*
|
||||
* Note: The status header appears twice in the error page.
|
||||
*/
|
||||
int serverInfoLength = ServerInfo.getServerInfo().getBytes().length;
|
||||
StringManager sm = StringManager.getManager(
|
||||
ErrorReportValve.class.getPackage().getName(), Locale.ENGLISH);
|
||||
String reason = sm.getString("http." + testData.getExpectedStatus() + ".reason");
|
||||
int descriptionLength = sm.getString("http." + testData.getExpectedStatus() + ".desc")
|
||||
.getBytes(StandardCharsets.UTF_8).length;
|
||||
int statusHeaderLength = sm
|
||||
.getString("errorReportValve.statusHeader",
|
||||
String.valueOf(testData.getExpectedStatus()), reason)
|
||||
.getBytes(StandardCharsets.UTF_8).length;
|
||||
int typeLabelLength = sm.getString("errorReportValve.type")
|
||||
.getBytes(StandardCharsets.UTF_8).length;
|
||||
int statusReportLabelLength = sm.getString("errorReportValve.statusReport")
|
||||
.getBytes(StandardCharsets.UTF_8).length;
|
||||
int descriptionLabelLength = sm.getString("errorReportValve.description")
|
||||
.getBytes(StandardCharsets.UTF_8).length;
|
||||
// 196 bytes is the static length of the pure HTML code from the ErrorReportValve
|
||||
int len = 196 + org.apache.catalina.util.TomcatCSS.TOMCAT_CSS
|
||||
.getBytes(StandardCharsets.UTF_8).length +
|
||||
typeLabelLength + statusReportLabelLength + descriptionLabelLength +
|
||||
descriptionLength + serverInfoLength + statusHeaderLength * 2;
|
||||
String contentLength = String.valueOf(len);
|
||||
return getResponseBodyFrameTrace(streamId,
|
||||
testData.getExpectedStatus(), "text/html;charset=utf-8",
|
||||
"en", contentLength, contentLength);
|
||||
} else {
|
||||
Assert.fail();
|
||||
// To keep the IDE happy
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static class TestData {
|
||||
private final List<String> hostHeaders;
|
||||
private final int expectedStatus;
|
||||
|
||||
public TestData(List<String> hostHeaders, int expectedStatus) {
|
||||
this.hostHeaders = hostHeaders;
|
||||
this.expectedStatus = expectedStatus;
|
||||
}
|
||||
|
||||
public List<String> getHostHeaders() {
|
||||
return hostHeaders;
|
||||
}
|
||||
|
||||
public int getExpectedStatus() {
|
||||
return expectedStatus;
|
||||
}
|
||||
}
|
||||
}
|
||||
550
test/org/apache/coyote/http2/TestHttp2Limits.java
Normal file
550
test/org/apache/coyote/http2/TestHttp2Limits.java
Normal file
@@ -0,0 +1,550 @@
|
||||
/*
|
||||
* 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 java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import org.hamcrest.Description;
|
||||
import org.hamcrest.TypeSafeMatcher;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.connector.Connector;
|
||||
import org.apache.coyote.http2.HpackEncoder.State;
|
||||
import org.apache.tomcat.util.http.MimeHeaders;
|
||||
import org.apache.tomcat.util.res.StringManager;
|
||||
|
||||
public class TestHttp2Limits extends Http2TestBase {
|
||||
|
||||
private static final StringManager sm = StringManager.getManager(TestHttp2Limits.class);
|
||||
|
||||
@Test
|
||||
public void testHeaderLimits1x128() throws Exception {
|
||||
// Well within limits
|
||||
doTestHeaderLimits(1, 128, FailureMode.NONE);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHeaderLimits100x32() throws Exception {
|
||||
// Just within default maxHeaderCount
|
||||
// Note request has 4 standard headers
|
||||
doTestHeaderLimits(96, 32, FailureMode.NONE);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHeaderLimits101x32() throws Exception {
|
||||
// Just above default maxHeaderCount
|
||||
doTestHeaderLimits(97, 32, FailureMode.STREAM_RESET);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHeaderLimits20x32WithLimit10() throws Exception {
|
||||
// Check lower count limit is enforced
|
||||
doTestHeaderLimits(20, 32, -1, 10, Constants.DEFAULT_MAX_HEADER_SIZE, 0,
|
||||
FailureMode.STREAM_RESET);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHeaderLimits8x1144() throws Exception {
|
||||
// Just within default maxHttpHeaderSize
|
||||
// per header overhead plus standard 3 headers
|
||||
doTestHeaderLimits(7, 1144, FailureMode.NONE);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHeaderLimits8x1145() throws Exception {
|
||||
// Just above default maxHttpHeaderSize
|
||||
doTestHeaderLimits(7, 1145, FailureMode.STREAM_RESET);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHeaderLimits3x1024WithLimit2048() throws Exception {
|
||||
// Check lower size limit is enforced
|
||||
doTestHeaderLimits(3, 1024, -1, Constants.DEFAULT_MAX_HEADER_COUNT, 2 * 1024, 0,
|
||||
FailureMode.STREAM_RESET);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHeaderLimits1x12k() throws Exception {
|
||||
// Bug 60232
|
||||
doTestHeaderLimits(1, 12*1024, FailureMode.STREAM_RESET);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHeaderLimits1x12kin1kChunks() throws Exception {
|
||||
// Bug 60232
|
||||
doTestHeaderLimits(1, 12*1024, 1024, FailureMode.STREAM_RESET);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHeaderLimits1x12kin1kChunksThenNewRequest() throws Exception {
|
||||
// Bug 60232
|
||||
doTestHeaderLimits(1, 12*1024, 1024, FailureMode.STREAM_RESET);
|
||||
|
||||
|
||||
output.clearTrace();
|
||||
sendSimpleGetRequest(5);
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
Assert.assertEquals(getSimpleResponseTrace(5), output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHeaderLimits1x32k() throws Exception {
|
||||
// Bug 60232
|
||||
doTestHeaderLimits(1, 32*1024, FailureMode.CONNECTION_RESET);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHeaderLimits1x32kin1kChunks() throws Exception {
|
||||
// Bug 60232
|
||||
// 500ms per frame write delay to give server a chance to process the
|
||||
// stream reset and the connection reset before the request is fully
|
||||
// sent.
|
||||
doTestHeaderLimits(1, 32*1024, 1024, 500, FailureMode.CONNECTION_RESET);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHeaderLimits1x128k() throws Exception {
|
||||
// Bug 60232
|
||||
doTestHeaderLimits(1, 128*1024, FailureMode.CONNECTION_RESET);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHeaderLimits1x512k() throws Exception {
|
||||
// Bug 60232
|
||||
doTestHeaderLimits(1, 512*1024, FailureMode.CONNECTION_RESET);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHeaderLimits10x512k() throws Exception {
|
||||
// Bug 60232
|
||||
doTestHeaderLimits(10, 512*1024, FailureMode.CONNECTION_RESET);
|
||||
}
|
||||
|
||||
|
||||
private void doTestHeaderLimits(int headerCount, int headerSize, FailureMode failMode)
|
||||
throws Exception {
|
||||
doTestHeaderLimits(headerCount, headerSize, -1, failMode);
|
||||
}
|
||||
|
||||
|
||||
private void doTestHeaderLimits(int headerCount, int headerSize, int maxHeaderPayloadSize,
|
||||
FailureMode failMode) throws Exception {
|
||||
doTestHeaderLimits(headerCount, headerSize, maxHeaderPayloadSize, 0, failMode);
|
||||
}
|
||||
|
||||
|
||||
private void doTestHeaderLimits(int headerCount, int headerSize, int maxHeaderPayloadSize,
|
||||
int delayms, FailureMode failMode) throws Exception {
|
||||
doTestHeaderLimits(headerCount, headerSize, maxHeaderPayloadSize,
|
||||
Constants.DEFAULT_MAX_HEADER_COUNT, Constants.DEFAULT_MAX_HEADER_SIZE, delayms,
|
||||
failMode);
|
||||
}
|
||||
|
||||
|
||||
private void doTestHeaderLimits(int headerCount, int headerSize, int maxHeaderPayloadSize,
|
||||
int maxHeaderCount, int maxHeaderSize, int delayms, FailureMode failMode)
|
||||
throws Exception {
|
||||
|
||||
// Build the custom headers
|
||||
List<String[]> customHeaders = new ArrayList<>();
|
||||
StringBuilder headerValue = new StringBuilder(headerSize);
|
||||
// Does not need to be secure
|
||||
Random r = new Random();
|
||||
for (int i = 0; i < headerSize; i++) {
|
||||
// Random lower case characters
|
||||
headerValue.append((char) ('a' + r.nextInt(26)));
|
||||
}
|
||||
String v = headerValue.toString();
|
||||
for (int i = 0; i < headerCount; i++) {
|
||||
customHeaders.add(new String[] {"X-TomcatTest" + i, v});
|
||||
}
|
||||
|
||||
enableHttp2();
|
||||
|
||||
http2Protocol.setMaxHeaderCount(maxHeaderCount);
|
||||
http2Protocol.setMaxHeaderSize(maxHeaderSize);
|
||||
|
||||
configureAndStartWebApplication();
|
||||
openClientConnection();
|
||||
doHttpUpgrade();
|
||||
sendClientPreface();
|
||||
validateHttp2InitialResponse();
|
||||
|
||||
if (maxHeaderPayloadSize == -1) {
|
||||
maxHeaderPayloadSize = output.getMaxFrameSize();
|
||||
}
|
||||
|
||||
// Build the simple request
|
||||
byte[] frameHeader = new byte[9];
|
||||
// Assumes at least one custom header and that all headers are the same
|
||||
// length. These assumptions are valid for these tests.
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(200 + (int) (customHeaders.size() *
|
||||
customHeaders.iterator().next()[1].length() * 1.2));
|
||||
|
||||
populateHeadersPayload(headersPayload, customHeaders, "/simple");
|
||||
|
||||
Exception e = null;
|
||||
try {
|
||||
int written = 0;
|
||||
int left = headersPayload.limit() - written;
|
||||
while (left > 0) {
|
||||
int thisTime = Math.min(left, maxHeaderPayloadSize);
|
||||
populateFrameHeader(frameHeader, written, left, thisTime, 3);
|
||||
writeFrame(frameHeader, headersPayload, headersPayload.limit() - left,
|
||||
thisTime, delayms);
|
||||
left -= thisTime;
|
||||
written += thisTime;
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
e = ioe;
|
||||
}
|
||||
|
||||
switch (failMode) {
|
||||
case NONE: {
|
||||
// Expect a normal response
|
||||
readSimpleGetResponse();
|
||||
Assert.assertEquals(getSimpleResponseTrace(3), output.getTrace());
|
||||
Assert.assertNull(e);
|
||||
break;
|
||||
}
|
||||
case STREAM_RESET: {
|
||||
// Expect a stream reset
|
||||
parser.readFrame(true);
|
||||
Assert.assertEquals("3-RST-[11]\n", output.getTrace());
|
||||
Assert.assertNull(e);
|
||||
break;
|
||||
}
|
||||
case CONNECTION_RESET: {
|
||||
// This message uses i18n and needs to be used in a regular
|
||||
// expression (since we don't know the connection ID). Generate the
|
||||
// string as a regular expression and then replace '[' and ']' with
|
||||
// the escaped values.
|
||||
String limitMessage = sm.getString("http2Parser.headerLimitSize", "\\d++", "3");
|
||||
limitMessage = limitMessage.replace("[", "\\[").replace("]", "\\]");
|
||||
// Connection reset. Connection ID will vary so use a pattern
|
||||
// On some platform / Connector combinations (e.g. Windows / APR),
|
||||
// the TCP connection close will be processed before the client gets
|
||||
// a chance to read the connection close frame which will trigger an
|
||||
// IOException when we try to read the frame.
|
||||
// Note: Some platforms will allow the read if if the write fails
|
||||
// above.
|
||||
try {
|
||||
parser.readFrame(true);
|
||||
Assert.assertThat(output.getTrace(), RegexMatcher.matchesRegex(
|
||||
"0-Goaway-\\[1\\]-\\[11\\]-\\[" + limitMessage + "\\]"));
|
||||
} catch (IOException se) {
|
||||
// Expected on some platforms
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void populateHeadersPayload(ByteBuffer headersPayload, List<String[]> customHeaders,
|
||||
String path) throws Exception {
|
||||
MimeHeaders headers = new MimeHeaders();
|
||||
headers.addValue(":method").setString("GET");
|
||||
headers.addValue(":scheme").setString("http");
|
||||
headers.addValue(":path").setString(path);
|
||||
headers.addValue(":authority").setString("localhost:" + getPort());
|
||||
for (String[] customHeader : customHeaders) {
|
||||
headers.addValue(customHeader[0]).setString(customHeader[1]);
|
||||
}
|
||||
State state = hpackEncoder.encode(headers, headersPayload);
|
||||
if (state != State.COMPLETE) {
|
||||
throw new Exception("Unable to build headers");
|
||||
}
|
||||
headersPayload.flip();
|
||||
|
||||
log.debug("Headers payload generated of size [" + headersPayload.limit() + "]");
|
||||
}
|
||||
|
||||
|
||||
private void populateFrameHeader(byte[] frameHeader, int written, int left, int thisTime,
|
||||
int streamId) throws Exception {
|
||||
ByteUtil.setThreeBytes(frameHeader, 0, thisTime);
|
||||
if (written == 0) {
|
||||
frameHeader[3] = FrameType.HEADERS.getIdByte();
|
||||
// Flags. End of stream
|
||||
frameHeader[4] = 0x01;
|
||||
} else {
|
||||
frameHeader[3] = FrameType.CONTINUATION.getIdByte();
|
||||
}
|
||||
if (left == thisTime) {
|
||||
// Flags. End of headers
|
||||
frameHeader[4] = (byte) (frameHeader[4] + 0x04);
|
||||
}
|
||||
|
||||
// Stream id
|
||||
ByteUtil.set31Bits(frameHeader, 5, streamId);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCookieLimit1() throws Exception {
|
||||
doTestCookieLimit(1, 0);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCookieLimit2() throws Exception {
|
||||
doTestCookieLimit(2, 0);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCookieLimit100() throws Exception {
|
||||
doTestCookieLimit(100, 0);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCookieLimit100WithLimit50() throws Exception {
|
||||
doTestCookieLimit(100, 50, 1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCookieLimit200() throws Exception {
|
||||
doTestCookieLimit(200, 0);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testCookieLimit201() throws Exception {
|
||||
doTestCookieLimit(201, 1);
|
||||
}
|
||||
|
||||
|
||||
private void doTestCookieLimit(int cookieCount, int failMode) throws Exception {
|
||||
doTestCookieLimit(cookieCount, Constants.DEFAULT_MAX_COOKIE_COUNT, failMode);
|
||||
}
|
||||
|
||||
|
||||
private void doTestCookieLimit(int cookieCount, int maxCookieCount, int failMode)
|
||||
throws Exception {
|
||||
|
||||
enableHttp2();
|
||||
|
||||
Connector connector = getTomcatInstance().getConnector();
|
||||
connector.setMaxCookieCount(maxCookieCount);
|
||||
|
||||
configureAndStartWebApplication();
|
||||
openClientConnection();
|
||||
doHttpUpgrade();
|
||||
sendClientPreface();
|
||||
validateHttp2InitialResponse();
|
||||
|
||||
output.setTraceBody(true);
|
||||
|
||||
byte[] frameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(8192);
|
||||
|
||||
List<String[]> customHeaders = new ArrayList<>();
|
||||
for (int i = 0; i < cookieCount; i++) {
|
||||
customHeaders.add(new String[] {"Cookie", "a" + cookieCount + "=b" + cookieCount});
|
||||
}
|
||||
|
||||
populateHeadersPayload(headersPayload, customHeaders, "/cookie");
|
||||
populateFrameHeader(frameHeader, 0, headersPayload.limit(), headersPayload.limit(), 3);
|
||||
|
||||
writeFrame(frameHeader, headersPayload);
|
||||
|
||||
switch (failMode) {
|
||||
case 0: {
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
System.out.println(output.getTrace());
|
||||
Assert.assertEquals(getCookieResponseTrace(3, cookieCount), output.getTrace());
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
// Check status is 400
|
||||
parser.readFrame(true);
|
||||
Assert.assertTrue(output.getTrace(), output.getTrace().startsWith(
|
||||
"3-HeadersStart\n3-Header-[:status]-[400]"));
|
||||
output.clearTrace();
|
||||
// Check EOS followed by error page body
|
||||
parser.readFrame(true);
|
||||
Assert.assertTrue(output.getTrace(), output.getTrace().startsWith("3-EndOfStream\n3-Body-<!doctype"));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
Assert.fail("Unknown failure mode specified");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void doTestPostWithTrailerHeadersDefaultLimit() throws Exception{
|
||||
doTestPostWithTrailerHeaders(Constants.DEFAULT_MAX_TRAILER_COUNT,
|
||||
Constants.DEFAULT_MAX_TRAILER_SIZE, FailureMode.NONE);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void doTestPostWithTrailerHeadersCount0() throws Exception{
|
||||
doTestPostWithTrailerHeaders(0, Constants.DEFAULT_MAX_TRAILER_SIZE,
|
||||
FailureMode.STREAM_RESET);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void doTestPostWithTrailerHeadersSize0() throws Exception{
|
||||
doTestPostWithTrailerHeaders(Constants.DEFAULT_MAX_TRAILER_COUNT, 0,
|
||||
FailureMode.CONNECTION_RESET);
|
||||
}
|
||||
|
||||
|
||||
private void doTestPostWithTrailerHeaders(int maxTrailerCount, int maxTrailerSize,
|
||||
FailureMode failMode) throws Exception {
|
||||
enableHttp2();
|
||||
|
||||
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);
|
||||
|
||||
switch (failMode) {
|
||||
case NONE: {
|
||||
parser.readFrame(true);
|
||||
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-[content-length]-[" + len + "]\n" +
|
||||
"3-Header-[date]-["+ DEFAULT_DATE + "]\n" +
|
||||
"3-HeadersEnd\n" +
|
||||
"3-Body-" +
|
||||
len +
|
||||
"\n" +
|
||||
"3-EndOfStream\n",
|
||||
output.getTrace());
|
||||
break;
|
||||
}
|
||||
case STREAM_RESET: {
|
||||
// NIO2 can sometimes send window updates depending timing
|
||||
skipWindowSizeFrames();
|
||||
|
||||
Assert.assertEquals("3-RST-[11]\n", output.getTrace());
|
||||
break;
|
||||
}
|
||||
case CONNECTION_RESET: {
|
||||
// NIO2 can sometimes send window updates depending timing
|
||||
skipWindowSizeFrames();
|
||||
|
||||
// This message uses i18n and needs to be used in a regular
|
||||
// expression (since we don't know the connection ID). Generate the
|
||||
// string as a regular expression and then replace '[' and ']' with
|
||||
// the escaped values.
|
||||
String limitMessage = sm.getString("http2Parser.headerLimitSize", "\\d++", "3");
|
||||
limitMessage = limitMessage.replace("[", "\\[").replace("]", "\\]");
|
||||
Assert.assertThat(output.getTrace(), RegexMatcher.matchesRegex(
|
||||
"0-Goaway-\\[3\\]-\\[11\\]-\\[" + limitMessage + "\\]"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private enum FailureMode {
|
||||
NONE,
|
||||
STREAM_RESET,
|
||||
CONNECTION_RESET,
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static class RegexMatcher extends TypeSafeMatcher<String> {
|
||||
|
||||
private final String pattern;
|
||||
|
||||
|
||||
public RegexMatcher(String pattern) {
|
||||
this.pattern = pattern;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void describeTo(Description description) {
|
||||
description.appendText("match to regular expression pattern [" + pattern + "]");
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matchesSafely(String item) {
|
||||
return item.matches(pattern);
|
||||
}
|
||||
|
||||
|
||||
public static RegexMatcher matchesRegex(String pattern) {
|
||||
return new RegexMatcher(pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
180
test/org/apache/coyote/http2/TestHttp2Section_3_2.java
Normal file
180
test/org/apache/coyote/http2/TestHttp2Section_3_2.java
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* 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 java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for Section 3.2 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.
|
||||
*/
|
||||
public class TestHttp2Section_3_2 extends Http2TestBase {
|
||||
|
||||
// Note: Tests for zero/multiple HTTP2-Settings fields can be found below
|
||||
// in the tests for section 3.2.1
|
||||
|
||||
// TODO: Test initial requests with bodies of various sizes
|
||||
|
||||
@Test
|
||||
public void testConnectionNoHttp2Support() throws Exception {
|
||||
configureAndStartWebApplication();
|
||||
openClientConnection();
|
||||
doHttpUpgrade(DEFAULT_CONNECTION_HEADER_VALUE, "h2c", EMPTY_HTTP2_SETTINGS_HEADER, false);
|
||||
parseHttp11Response();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testConnectionUpgradeWrongProtocol() throws Exception {
|
||||
enableHttp2();
|
||||
configureAndStartWebApplication();
|
||||
openClientConnection();
|
||||
doHttpUpgrade(DEFAULT_CONNECTION_HEADER_VALUE, "h2", EMPTY_HTTP2_SETTINGS_HEADER, false);
|
||||
parseHttp11Response();
|
||||
}
|
||||
|
||||
|
||||
@Test(timeout=10000)
|
||||
public void testConnectionNoPreface() throws Exception {
|
||||
setupAsFarAsUpgrade();
|
||||
|
||||
// If we don't send the preface the server should kill the connection.
|
||||
try {
|
||||
// Make the parser read something.
|
||||
parser.readFrame(true);
|
||||
} catch (IOException ioe) {
|
||||
// Expected because the server is going to drop the connection.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test(timeout=10000)
|
||||
public void testConnectionIncompletePrefaceStart() throws Exception {
|
||||
setupAsFarAsUpgrade();
|
||||
|
||||
// If we send an incomplete preface the server should kill the
|
||||
// connection.
|
||||
os.write("PRI * HTTP/2.0\r\n\r\n".getBytes(StandardCharsets.ISO_8859_1));
|
||||
os.flush();
|
||||
try {
|
||||
// Make the parser read something.
|
||||
parser.readFrame(true);
|
||||
} catch (IOException ioe) {
|
||||
// Expected because the server is going to drop the connection.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test(timeout=10000)
|
||||
public void testConnectionInvalidPrefaceStart() throws Exception {
|
||||
setupAsFarAsUpgrade();
|
||||
|
||||
// If we send an incomplete preface the server should kill the
|
||||
// connection.
|
||||
os.write("xxxxxxxxx-xxxxxxxxx-xxxxxxxxx-xxxxxxxxxx".getBytes(
|
||||
StandardCharsets.ISO_8859_1));
|
||||
os.flush();
|
||||
try {
|
||||
// Make the parser read something.
|
||||
parser.readFrame(true);
|
||||
} catch (IOException ioe) {
|
||||
// Expected because the server is going to drop the connection.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testConnectionUpgradeFirstResponse() throws Exception{
|
||||
super.http2Connect();
|
||||
}
|
||||
|
||||
|
||||
private void setupAsFarAsUpgrade() throws Exception {
|
||||
enableHttp2();
|
||||
configureAndStartWebApplication();
|
||||
openClientConnection();
|
||||
doHttpUpgrade();
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------ Section 3.2.1
|
||||
|
||||
@Test
|
||||
public void testZeroHttp2Settings() throws Exception {
|
||||
enableHttp2();
|
||||
configureAndStartWebApplication();
|
||||
openClientConnection();
|
||||
doHttpUpgrade(Http2TestBase.DEFAULT_CONNECTION_HEADER_VALUE, "h2c", "", false);
|
||||
parseHttp11Response();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMultipleHttp2Settings() throws Exception {
|
||||
enableHttp2();
|
||||
configureAndStartWebApplication();
|
||||
openClientConnection();
|
||||
doHttpUpgrade(Http2TestBase.DEFAULT_CONNECTION_HEADER_VALUE, "h2c",
|
||||
Http2TestBase.EMPTY_HTTP2_SETTINGS_HEADER +
|
||||
Http2TestBase.EMPTY_HTTP2_SETTINGS_HEADER, false);
|
||||
parseHttp11Response();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testMissingConnectionValue() throws Exception {
|
||||
enableHttp2();
|
||||
configureAndStartWebApplication();
|
||||
openClientConnection();
|
||||
doHttpUpgrade("Upgrade", "h2c", Http2TestBase.EMPTY_HTTP2_SETTINGS_HEADER, false);
|
||||
parseHttp11Response();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSplitConnectionValue01() throws Exception {
|
||||
enableHttp2();
|
||||
configureAndStartWebApplication();
|
||||
openClientConnection();
|
||||
doHttpUpgrade("Upgrade\r\nConnection: HTTP2-Settings", "h2c",
|
||||
Http2TestBase.EMPTY_HTTP2_SETTINGS_HEADER, true);
|
||||
sendClientPreface();
|
||||
validateHttp2InitialResponse();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSplitConnectionValue02() throws Exception {
|
||||
enableHttp2();
|
||||
configureAndStartWebApplication();
|
||||
openClientConnection();
|
||||
doHttpUpgrade("HTTP2-Settings\r\nConnection: Upgrade", "h2c",
|
||||
Http2TestBase.EMPTY_HTTP2_SETTINGS_HEADER, true);
|
||||
sendClientPreface();
|
||||
validateHttp2InitialResponse();
|
||||
}
|
||||
|
||||
// No need to test how trailing '=' are handled here. HTTP2Settings payloads
|
||||
// are always a multiple of 6 long which means valid payloads never end in
|
||||
// '='. Invalid payloads will be rejected anyway.
|
||||
}
|
||||
39
test/org/apache/coyote/http2/TestHttp2Section_3_5.java
Normal file
39
test/org/apache/coyote/http2/TestHttp2Section_3_5.java
Normal file
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 java.io.IOException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestHttp2Section_3_5 extends Http2TestBase {
|
||||
|
||||
@Test(expected=IOException.class)
|
||||
public void testNoConnectionPreface() throws Exception {
|
||||
enableHttp2();
|
||||
configureAndStartWebApplication();
|
||||
openClientConnection();
|
||||
doHttpUpgrade();
|
||||
// Should send client preface here
|
||||
sendPing();
|
||||
// Send several pings else server will block waiting for the client
|
||||
// preface which is longer than a single ping.
|
||||
sendPing();
|
||||
sendPing();
|
||||
validateHttp2InitialResponse();
|
||||
}
|
||||
}
|
||||
72
test/org/apache/coyote/http2/TestHttp2Section_4_1.java
Normal file
72
test/org/apache/coyote/http2/TestHttp2Section_4_1.java
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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 java.nio.ByteBuffer;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for Section 4.1 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.
|
||||
*/
|
||||
public class TestHttp2Section_4_1 extends Http2TestBase {
|
||||
|
||||
private static final byte[] UNKNOWN_FRAME = new byte[] {
|
||||
0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00 };
|
||||
|
||||
// TODO: Tests for over-sized frames. Better located in tests for section 6?
|
||||
|
||||
|
||||
@Test
|
||||
public void testUnknownFrameType() throws Exception {
|
||||
http2Connect();
|
||||
os.write(UNKNOWN_FRAME);
|
||||
os.flush();
|
||||
sendSimpleGetRequest(3);
|
||||
readSimpleGetResponse();
|
||||
Assert.assertEquals(getSimpleResponseTrace(3), output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
// TODO: Tests for unexpected flags. Better located in tests for section 6?
|
||||
|
||||
|
||||
@Test
|
||||
public void testReservedBitIgnored() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
// Build the simple request
|
||||
byte[] frameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
buildSimpleGetRequest(frameHeader, headersPayload, null, 3);
|
||||
|
||||
// Tweak the header to set the reserved bit
|
||||
frameHeader[5] = (byte) (frameHeader[5] | 0x80);
|
||||
|
||||
// Process the request
|
||||
writeFrame(frameHeader, headersPayload);
|
||||
|
||||
readSimpleGetResponse();
|
||||
Assert.assertEquals(getSimpleResponseTrace(3), output.getTrace());
|
||||
}
|
||||
}
|
||||
131
test/org/apache/coyote/http2/TestHttp2Section_4_2.java
Normal file
131
test/org/apache/coyote/http2/TestHttp2Section_4_2.java
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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 4.2 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.
|
||||
*/
|
||||
public class TestHttp2Section_4_2 extends Http2TestBase {
|
||||
|
||||
@Test
|
||||
public void testFrameSizeLimitsTooBig() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
// Overly large settings
|
||||
// Settings have to be a multiple of six
|
||||
int settingsCount = (ConnectionSettingsBase.DEFAULT_MAX_FRAME_SIZE / 6) + 1;
|
||||
int size = settingsCount * 6;
|
||||
byte[] settings = new byte[size + 9];
|
||||
// Header
|
||||
// Length
|
||||
ByteUtil.setThreeBytes(settings, 0, size);
|
||||
// Type
|
||||
settings[3] = FrameType.SETTINGS.getIdByte();
|
||||
// No flags
|
||||
// Stream 0
|
||||
|
||||
// payload
|
||||
for (int i = 0; i < settingsCount; i++) {
|
||||
// Enable server push over and over again
|
||||
ByteUtil.setTwoBytes(settings, (i * 6) + 9, 2);
|
||||
ByteUtil.setFourBytes(settings, (i * 6) + 9 + 2, 1);
|
||||
}
|
||||
|
||||
os.write(settings);
|
||||
|
||||
handleGoAwayResponse(1, Http2Error.FRAME_SIZE_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFrameTypeLimitsTooBig() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
// Overly large ping
|
||||
byte[] ping = new byte[109];
|
||||
|
||||
// Header
|
||||
// Length
|
||||
ByteUtil.setThreeBytes(ping, 0, 100);
|
||||
// Type
|
||||
ping[3] = FrameType.PING.getIdByte();
|
||||
// No flags
|
||||
// Stream 0
|
||||
// Empty payload
|
||||
|
||||
os.write(ping);
|
||||
|
||||
handleGoAwayResponse(1, Http2Error.FRAME_SIZE_ERROR);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFrameTypeLimitsTooSmall() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
// Too small ping
|
||||
byte[] ping = new byte[9];
|
||||
|
||||
// Header
|
||||
// Length 0
|
||||
// Type
|
||||
ping[3] = FrameType.PING.getIdByte();
|
||||
// No flags
|
||||
// Stream 0
|
||||
// Empty payload
|
||||
|
||||
os.write(ping);
|
||||
|
||||
handleGoAwayResponse(1, Http2Error.FRAME_SIZE_ERROR);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFrameTypeLimitsStream() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
// Invalid priority
|
||||
byte[] priority = new byte[9];
|
||||
|
||||
// Header
|
||||
// Length 0
|
||||
// Type
|
||||
priority[3] = FrameType.PRIORITY.getIdByte();
|
||||
// No flags
|
||||
// Stream 3
|
||||
ByteUtil.set31Bits(priority, 5, 3);
|
||||
// Empty payload
|
||||
|
||||
os.write(priority);
|
||||
|
||||
// Read Stream reset frame
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertTrue(output.getTrace(),
|
||||
output.getTrace().startsWith("3-RST-[6]"));
|
||||
}
|
||||
}
|
||||
92
test/org/apache/coyote/http2/TestHttp2Section_4_3.java
Normal file
92
test/org/apache/coyote/http2/TestHttp2Section_4_3.java
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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 java.nio.ByteBuffer;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for Section 4.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.
|
||||
*/
|
||||
public class TestHttp2Section_4_3 extends Http2TestBase {
|
||||
|
||||
@Test
|
||||
public void testHeaderDecodingError() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
// Build the simple request
|
||||
byte[] frameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
buildSimpleGetRequest(frameHeader, headersPayload, null, 3);
|
||||
|
||||
// Try and corrupt the headerPayload
|
||||
headersPayload.put(0, (byte) (headersPayload.get(0) + 128));
|
||||
|
||||
// Process the request
|
||||
writeFrame(frameHeader, headersPayload);
|
||||
|
||||
handleGoAwayResponse(1, Http2Error.COMPRESSION_ERROR);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHeaderContinuationContiguous() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
// Part 1
|
||||
byte[] frameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
buildSimpleGetRequestPart1(frameHeader, headersPayload, 3);
|
||||
writeFrame(frameHeader, headersPayload);
|
||||
|
||||
// Part 2
|
||||
headersPayload.clear();
|
||||
buildSimpleGetRequestPart2(frameHeader, headersPayload, 3);
|
||||
writeFrame(frameHeader, headersPayload);
|
||||
|
||||
// headers, body
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertEquals(getSimpleResponseTrace(3), output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHeaderContinuationNonContiguous() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
// Part 1
|
||||
byte[] frameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
buildSimpleGetRequestPart1(frameHeader, headersPayload, 3);
|
||||
writeFrame(frameHeader, headersPayload);
|
||||
|
||||
sendPing();
|
||||
|
||||
handleGoAwayResponse(1, Http2Error.COMPRESSION_ERROR);
|
||||
}
|
||||
}
|
||||
288
test/org/apache/coyote/http2/TestHttp2Section_5_1.java
Normal file
288
test/org/apache/coyote/http2/TestHttp2Section_5_1.java
Normal file
@@ -0,0 +1,288 @@
|
||||
/*
|
||||
* 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 java.nio.ByteBuffer;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for Section 5.§ 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.
|
||||
*/
|
||||
public class TestHttp2Section_5_1 extends Http2TestBase {
|
||||
|
||||
@Test
|
||||
public void testIdleStateInvalidFrame01() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
sendWindowUpdate(3, 200);
|
||||
|
||||
handleGoAwayResponse(1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testIdleStateInvalidFrame02() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
sendData(3, new byte[] {});
|
||||
|
||||
handleGoAwayResponse(1);
|
||||
}
|
||||
|
||||
|
||||
// TODO: reserved local
|
||||
// TODO: reserved remote
|
||||
|
||||
|
||||
@Test
|
||||
public void halfClosedRemoteInvalidFrame() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
// This half-closes the stream since it includes the end of stream flag
|
||||
sendSimpleGetRequest(3);
|
||||
readSimpleGetResponse();
|
||||
Assert.assertEquals(getSimpleResponseTrace(3), output.getTrace());
|
||||
output.clearTrace();
|
||||
|
||||
// This should trigger a connection error
|
||||
sendData(3, new byte[] {});
|
||||
|
||||
handleGoAwayResponse(3, Http2Error.STREAM_CLOSED);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testClosedInvalidFrame01() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
// Build the simple request
|
||||
byte[] frameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
buildSimpleGetRequest(frameHeader, headersPayload, null, 3);
|
||||
|
||||
// Remove the end of stream and end of headers flags
|
||||
frameHeader[4] = 0;
|
||||
|
||||
// Process the request
|
||||
writeFrame(frameHeader, headersPayload);
|
||||
|
||||
// Send a rst
|
||||
sendRst(3, Http2Error.INTERNAL_ERROR.getCode());
|
||||
|
||||
// Then try sending some data (which should fail)
|
||||
sendData(3, new byte[] {});
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertTrue(output.getTrace(),
|
||||
output.getTrace().startsWith("3-RST-[" + Http2Error.STREAM_CLOSED.getCode() + "]"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testClosedInvalidFrame02() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
// Stream 1 is closed. This should trigger a connection error
|
||||
sendData(1, new byte[] {});
|
||||
|
||||
handleGoAwayResponse(1, Http2Error.STREAM_CLOSED);
|
||||
}
|
||||
|
||||
|
||||
// TODO: Invalid frames for each of the remaining states
|
||||
|
||||
// Section 5.1.1
|
||||
|
||||
@Test
|
||||
public void testClientSendEvenStream() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
// Part 1
|
||||
byte[] frameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
buildSimpleGetRequestPart1(frameHeader, headersPayload, 4);
|
||||
writeFrame(frameHeader, headersPayload);
|
||||
|
||||
handleGoAwayResponse(1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testClientSendOldStream() throws Exception {
|
||||
http2Connect();
|
||||
sendSimpleGetRequest(5);
|
||||
readSimpleGetResponse();
|
||||
Assert.assertEquals(getSimpleResponseTrace(5), output.getTrace());
|
||||
output.clearTrace();
|
||||
|
||||
|
||||
// Build the simple request on an old stream
|
||||
byte[] frameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
buildSimpleGetRequest(frameHeader, headersPayload, null, 3);
|
||||
|
||||
os.write(frameHeader);
|
||||
os.flush();
|
||||
|
||||
handleGoAwayResponse(5);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testImplicitClose() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
sendPriority(3, 0, 16);
|
||||
sendPriority(5, 0, 16);
|
||||
|
||||
sendSimpleGetRequest(5);
|
||||
readSimpleGetResponse();
|
||||
Assert.assertEquals(getSimpleResponseTrace(5), output.getTrace());
|
||||
output.clearTrace();
|
||||
|
||||
// Should trigger an error since stream 3 should have been implicitly
|
||||
// closed.
|
||||
sendSimpleGetRequest(3);
|
||||
|
||||
handleGoAwayResponse(5);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testExceedMaxActiveStreams() throws Exception {
|
||||
// http2Connect() - modified
|
||||
enableHttp2(1);
|
||||
configureAndStartWebApplication();
|
||||
openClientConnection();
|
||||
doHttpUpgrade();
|
||||
sendClientPreface();
|
||||
|
||||
// validateHttp2InitialResponse() - modified
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertEquals("0-Settings-[3]-[1]\n" +
|
||||
"0-Settings-End\n" +
|
||||
"0-Settings-Ack\n" +
|
||||
"0-Ping-[0,0,0,0,0,0,0,1]\n" +
|
||||
getSimpleResponseTrace(1)
|
||||
, output.getTrace());
|
||||
output.clearTrace();
|
||||
|
||||
sendLargeGetRequest(3);
|
||||
|
||||
sendSimpleGetRequest(5);
|
||||
|
||||
// Default connection window size is 64k-1.
|
||||
// Initial request will have used 8k leaving 56k-1.
|
||||
// Stream window will be 64k-1.
|
||||
// Expecting
|
||||
// 1 * headers
|
||||
// 56k-1 of body (7 * ~8k)
|
||||
// 1 * error (could be in any order)
|
||||
for (int i = 0; i < 8; i++) {
|
||||
parser.readFrame(true);
|
||||
}
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertTrue(output.getTrace(),
|
||||
output.getTrace().contains("5-RST-[" +
|
||||
Http2Error.REFUSED_STREAM.getCode() + "]"));
|
||||
output.clearTrace();
|
||||
|
||||
// Connection window is zero.
|
||||
// Stream window is 8k
|
||||
|
||||
// Release the remaining body
|
||||
sendWindowUpdate(0, (1 << 31) - 2);
|
||||
// Allow for the 8k still in the stream window
|
||||
sendWindowUpdate(3, (1 << 31) - 8193);
|
||||
|
||||
// 192k of body (24 * 8k)
|
||||
// 1 * error (could be in any order)
|
||||
for (int i = 0; i < 24; i++) {
|
||||
parser.readFrame(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testErrorOnWaitingStream() throws Exception {
|
||||
// http2Connect() - modified
|
||||
enableHttp2(1);
|
||||
configureAndStartWebApplication();
|
||||
openClientConnection();
|
||||
doHttpUpgrade();
|
||||
sendClientPreface();
|
||||
|
||||
// validateHttp2InitialResponse() - modified
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertEquals("0-Settings-[3]-[1]\n" +
|
||||
"0-Settings-End\n" +
|
||||
"0-Settings-Ack\n" +
|
||||
"0-Ping-[0,0,0,0,0,0,0,1]\n" +
|
||||
getSimpleResponseTrace(1)
|
||||
, output.getTrace());
|
||||
output.clearTrace();
|
||||
|
||||
sendLargeGetRequest(3);
|
||||
|
||||
sendSimpleGetRequest(5);
|
||||
|
||||
// Default connection window size is 64k-1.
|
||||
// Initial request will have used 8k leaving 56k-1.
|
||||
// Stream window will be 64k-1.
|
||||
// Expecting
|
||||
// 1 * headers
|
||||
// 56k-1 of body (7 * ~8k)
|
||||
// 1 * error (could be in any order)
|
||||
for (int i = 0; i < 8; i++) {
|
||||
parser.readFrame(true);
|
||||
}
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertTrue(output.getTrace(),
|
||||
output.getTrace().contains("5-RST-[" +
|
||||
Http2Error.REFUSED_STREAM.getCode() + "]"));
|
||||
output.clearTrace();
|
||||
|
||||
// Connection window is zero.
|
||||
// Stream window is 8k
|
||||
|
||||
// Expand the stream window too much to trigger an error
|
||||
// Allow for the 8k still in the stream window
|
||||
sendWindowUpdate(3, (1 << 31) - 1);
|
||||
|
||||
parser.readFrame(true);
|
||||
}
|
||||
}
|
||||
120
test/org/apache/coyote/http2/TestHttp2Section_5_2.java
Normal file
120
test/org/apache/coyote/http2/TestHttp2Section_5_2.java
Normal file
@@ -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.http2;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for Section 5.2 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.
|
||||
*/
|
||||
public class TestHttp2Section_5_2 extends Http2TestBase {
|
||||
|
||||
/*
|
||||
* Get the connection to a point where 1k of 8k response body has been
|
||||
* read and the flow control for the stream has no capacity left.
|
||||
*/
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
http2Connect();
|
||||
|
||||
// This test uses small window updates that will trigger the excessive
|
||||
// overhead protection so disable it.
|
||||
http2Protocol.setOverheadWindowUpdateThreshold(0);
|
||||
|
||||
// Set the default window size to 1024 bytes
|
||||
sendSettings(0, false, new SettingValue(4, 1024));
|
||||
// Wait for the ack
|
||||
parser.readFrame(true);
|
||||
output.clearTrace();
|
||||
|
||||
// Headers + 8k response
|
||||
sendSimpleGetRequest(3);
|
||||
|
||||
// Headers
|
||||
parser.readFrame(true);
|
||||
// First 1k of body
|
||||
parser.readFrame(true);
|
||||
output.clearTrace();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFlowControlLimits01() throws Exception {
|
||||
readBytes(20);
|
||||
clearRemainder();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFlowControlLimits02() throws Exception {
|
||||
readBytes(1);
|
||||
readBytes(1);
|
||||
readBytes(1024);
|
||||
readBytes(1);
|
||||
clearRemainder();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFlowControlLimits03() throws Exception {
|
||||
readBytes(8192,7168);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFlowControlLimits04() throws Exception {
|
||||
readBytes(7168, 7168, true);
|
||||
}
|
||||
|
||||
|
||||
private void readBytes(int len) throws Exception {
|
||||
readBytes(len, len);
|
||||
}
|
||||
|
||||
|
||||
private void readBytes(int len, int expected) throws Exception {
|
||||
readBytes(len, expected, len > expected);
|
||||
}
|
||||
|
||||
|
||||
private void readBytes(int len, int expected, boolean eos) throws Exception {
|
||||
sendWindowUpdate(3, len);
|
||||
parser.readFrame(true);
|
||||
String expectedTrace = "3-Body-" + expected + "\n";
|
||||
if (eos) {
|
||||
expectedTrace += "3-EndOfStream\n";
|
||||
}
|
||||
Assert.assertEquals(expectedTrace, output.getTrace());
|
||||
output.clearTrace();
|
||||
}
|
||||
|
||||
|
||||
private void clearRemainder() throws Exception {
|
||||
// Remainder
|
||||
sendWindowUpdate(3, 8192);
|
||||
parser.readFrame(true);
|
||||
}
|
||||
}
|
||||
249
test/org/apache/coyote/http2/TestHttp2Section_5_3.java
Normal file
249
test/org/apache/coyote/http2/TestHttp2Section_5_3.java
Normal file
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* 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();
|
||||
|
||||
// This test uses small window updates that will trigger the excessive
|
||||
// overhead protection so disable it.
|
||||
http2Protocol.setOverheadWindowUpdateThreshold(0);
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
96
test/org/apache/coyote/http2/TestHttp2Section_5_5.java
Normal file
96
test/org/apache/coyote/http2/TestHttp2Section_5_5.java
Normal file
@@ -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.http2;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for Section 5.5 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.
|
||||
*/
|
||||
public class TestHttp2Section_5_5 extends Http2TestBase {
|
||||
|
||||
private static final byte[] UNKNOWN_FRAME;
|
||||
|
||||
static {
|
||||
// Unknown frame type
|
||||
UNKNOWN_FRAME = new byte[29];
|
||||
// Frame header
|
||||
ByteUtil.setThreeBytes(UNKNOWN_FRAME, 0, 20);
|
||||
// Type
|
||||
UNKNOWN_FRAME[3] = (byte) 0x80;
|
||||
// No flags
|
||||
// Stream
|
||||
ByteUtil.set31Bits(UNKNOWN_FRAME, 5, 5);
|
||||
// zero payload
|
||||
}
|
||||
|
||||
|
||||
// Section 5.5
|
||||
|
||||
@Test
|
||||
public void testUnknownSetting() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
// Unknown setting (should be ack'd)
|
||||
sendSettings(0, false, new SettingValue(1 << 15, 0));
|
||||
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertEquals("0-Settings-Ack\n", output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUnknownFrame() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
os.write(UNKNOWN_FRAME);
|
||||
os.flush();
|
||||
|
||||
// Ping
|
||||
sendPing();
|
||||
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertEquals("0-Ping-Ack-[0,0,0,0,0,0,0,0]\n", output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testNonContiguousHeaderWithUnknownFrame() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
// Part 1
|
||||
byte[] frameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
buildSimpleGetRequestPart1(frameHeader, headersPayload, 3);
|
||||
writeFrame(frameHeader, headersPayload);
|
||||
|
||||
os.write(UNKNOWN_FRAME);
|
||||
os.flush();
|
||||
|
||||
handleGoAwayResponse(1, Http2Error.COMPRESSION_ERROR);
|
||||
}
|
||||
}
|
||||
162
test/org/apache/coyote/http2/TestHttp2Section_6_1.java
Normal file
162
test/org/apache/coyote/http2/TestHttp2Section_6_1.java
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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 6.1 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.
|
||||
*/
|
||||
public class TestHttp2Section_6_1 extends Http2TestBase {
|
||||
|
||||
@Test
|
||||
public void testDataFrame() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
sendSimplePostRequest(3, null);
|
||||
readSimplePostResponse(false);
|
||||
|
||||
Assert.assertEquals("0-WindowSize-[128]\n" +
|
||||
"3-WindowSize-[128]\n" +
|
||||
"3-HeadersStart\n" +
|
||||
"3-Header-[:status]-[200]\n" +
|
||||
"3-Header-[content-length]-[128]\n" +
|
||||
"3-Header-[date]-[Wed, 11 Nov 2015 19:18:42 GMT]\n" +
|
||||
"3-HeadersEnd\n" +
|
||||
"3-Body-128\n" +
|
||||
"3-EndOfStream\n", output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDataFrameWithPadding() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
byte[] padding = new byte[8];
|
||||
|
||||
sendSimplePostRequest(3, padding);
|
||||
readSimplePostResponse(true);
|
||||
|
||||
|
||||
// The window update for the padding could occur anywhere since it
|
||||
// happens on a different thead to the response.
|
||||
String trace = output.getTrace();
|
||||
String paddingWindowUpdate = "0-WindowSize-[9]\n3-WindowSize-[9]\n";
|
||||
|
||||
Assert.assertTrue(trace, trace.contains(paddingWindowUpdate));
|
||||
trace = trace.replace(paddingWindowUpdate, "");
|
||||
|
||||
Assert.assertEquals("0-WindowSize-[119]\n" +
|
||||
"3-WindowSize-[119]\n" +
|
||||
"3-HeadersStart\n" +
|
||||
"3-Header-[:status]-[200]\n" +
|
||||
"3-Header-[content-length]-[119]\n" +
|
||||
"3-Header-[date]-[Wed, 11 Nov 2015 19:18:42 GMT]\n" +
|
||||
"3-HeadersEnd\n" +
|
||||
"3-Body-119\n" +
|
||||
"3-EndOfStream\n", trace);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDataFrameWithNonZeroPadding() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
byte[] padding = new byte[8];
|
||||
padding[4] = 0x01;
|
||||
|
||||
sendSimplePostRequest(3, padding);
|
||||
|
||||
// May see Window updates depending on timing
|
||||
skipWindowSizeFrames();
|
||||
|
||||
String trace = output.getTrace();
|
||||
Assert.assertTrue(trace, trace.startsWith("0-Goaway-[3]-[1]-["));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDataFrameOnStreamZero() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
byte[] dataFrame = new byte[10];
|
||||
|
||||
// Header
|
||||
// length
|
||||
ByteUtil.setThreeBytes(dataFrame, 0, 1);
|
||||
// type (0 for data)
|
||||
// flags (0)
|
||||
// stream (0)
|
||||
// payload (0)
|
||||
|
||||
os.write(dataFrame);
|
||||
os.flush();
|
||||
|
||||
handleGoAwayResponse(1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDataFrameTooMuchPadding() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
byte[] dataFrame = new byte[10];
|
||||
|
||||
// Header
|
||||
// length
|
||||
ByteUtil.setThreeBytes(dataFrame, 0, 1);
|
||||
// type 0 (data)
|
||||
// flags 8 (padded)
|
||||
dataFrame[4] = 0x08;
|
||||
// stream 3
|
||||
ByteUtil.set31Bits(dataFrame, 5, 3);
|
||||
// payload (pad length of 1)
|
||||
dataFrame[9] = 1;
|
||||
|
||||
os.write(dataFrame);
|
||||
os.flush();
|
||||
|
||||
handleGoAwayResponse(1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDataFrameWithZeroLengthPadding() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
byte[] padding = new byte[0];
|
||||
|
||||
sendSimplePostRequest(3, padding);
|
||||
// Since padding is zero length, response looks like there is none.
|
||||
readSimplePostResponse(false);
|
||||
|
||||
Assert.assertEquals("0-WindowSize-[127]\n" +
|
||||
"3-WindowSize-[127]\n" +
|
||||
"3-HeadersStart\n" +
|
||||
"3-Header-[:status]-[200]\n" +
|
||||
"3-Header-[content-length]-[127]\n" +
|
||||
"3-Header-[date]-[Wed, 11 Nov 2015 19:18:42 GMT]\n" +
|
||||
"3-HeadersEnd\n" +
|
||||
"3-Body-127\n" +
|
||||
"3-EndOfStream\n", output.getTrace());
|
||||
}
|
||||
}
|
||||
107
test/org/apache/coyote/http2/TestHttp2Section_6_2.java
Normal file
107
test/org/apache/coyote/http2/TestHttp2Section_6_2.java
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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 java.nio.ByteBuffer;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for Section 6.2 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.
|
||||
*/
|
||||
public class TestHttp2Section_6_2 extends Http2TestBase {
|
||||
|
||||
@Test
|
||||
public void testHeaderFrameOnStreamZero() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
// Part 1
|
||||
byte[] frameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
buildSimpleGetRequestPart1(frameHeader, headersPayload, 0);
|
||||
writeFrame(frameHeader, headersPayload);
|
||||
|
||||
handleGoAwayResponse(1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHeaderFrameWithPadding() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
byte[] padding= new byte[8];
|
||||
|
||||
sendSimpleGetRequest(3, padding);
|
||||
readSimpleGetResponse();
|
||||
Assert.assertEquals(getSimpleResponseTrace(3), output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHeaderFrameWithNonZeroPadding() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
byte[] padding= new byte[8];
|
||||
padding[4] = 1;
|
||||
|
||||
sendSimpleGetRequest(3, padding);
|
||||
|
||||
handleGoAwayResponse(1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHeaderFrameTooMuchPadding() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
byte[] headerFrame = new byte[10];
|
||||
|
||||
// Header
|
||||
// length
|
||||
ByteUtil.setThreeBytes(headerFrame, 0, 1);
|
||||
headerFrame[3] = FrameType.HEADERS.getIdByte();
|
||||
// flags 8 (padded)
|
||||
headerFrame[4] = 0x08;
|
||||
// stream 3
|
||||
ByteUtil.set31Bits(headerFrame, 5, 3);
|
||||
// payload (pad length of 1)
|
||||
headerFrame[9] = 1;
|
||||
|
||||
os.write(headerFrame);
|
||||
os.flush();
|
||||
|
||||
handleGoAwayResponse(1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHeaderFrameWithZeroLengthPadding() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
byte[] padding= new byte[0];
|
||||
|
||||
sendSimpleGetRequest(3, padding);
|
||||
readSimpleGetResponse();
|
||||
Assert.assertEquals(getSimpleResponseTrace(3), output.getTrace());
|
||||
}
|
||||
}
|
||||
86
test/org/apache/coyote/http2/TestHttp2Section_6_3.java
Normal file
86
test/org/apache/coyote/http2/TestHttp2Section_6_3.java
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* 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 java.nio.ByteBuffer;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for Section 6.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.
|
||||
*/
|
||||
public class TestHttp2Section_6_3 extends Http2TestBase {
|
||||
|
||||
@Test
|
||||
public void testPriorityFrameOnStreamZero() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
sendPriority(0, 1, 15);
|
||||
|
||||
handleGoAwayResponse(1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPriorityFrameBetweenHeaderFrames() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
// Part 1
|
||||
byte[] frameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
buildSimpleGetRequestPart1(frameHeader, headersPayload, 3);
|
||||
writeFrame(frameHeader, headersPayload);
|
||||
|
||||
sendPriority(5, 3, 15);
|
||||
|
||||
handleGoAwayResponse(1, Http2Error.COMPRESSION_ERROR);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPriorityFrameWrongLength() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
byte[] priorityFrame = new byte[10];
|
||||
// length
|
||||
ByteUtil.setThreeBytes(priorityFrame, 0, 1);
|
||||
// type
|
||||
priorityFrame[3] = FrameType.PRIORITY.getIdByte();
|
||||
// No flags
|
||||
// Stream ID
|
||||
ByteUtil.set31Bits(priorityFrame, 5, 3);
|
||||
|
||||
// Payload - left as zero
|
||||
|
||||
os.write(priorityFrame);
|
||||
os.flush();
|
||||
|
||||
// Read reset frame
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertEquals("3-RST-[" + Http2Error.FRAME_SIZE_ERROR.getCode() + "]\n",
|
||||
output.getTrace());
|
||||
}
|
||||
}
|
||||
79
test/org/apache/coyote/http2/TestHttp2Section_6_4.java
Normal file
79
test/org/apache/coyote/http2/TestHttp2Section_6_4.java
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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 6.4 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.
|
||||
*/
|
||||
public class TestHttp2Section_6_4 extends Http2TestBase {
|
||||
|
||||
@Test
|
||||
public void testResetFrameOnStreamZero() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
sendRst(0, Http2Error.NO_ERROR.getCode());
|
||||
|
||||
handleGoAwayResponse(1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testResetFrameOnIdleStream() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
sendPriority(3, 0, 15);
|
||||
sendRst(3, Http2Error.NO_ERROR.getCode());
|
||||
|
||||
handleGoAwayResponse(1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testResetFrameWrongLength() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
byte[] resetFrame = new byte[10];
|
||||
// length
|
||||
ByteUtil.setThreeBytes(resetFrame, 0, 1);
|
||||
// type
|
||||
resetFrame[3] = FrameType.RST.getIdByte();
|
||||
// No flags
|
||||
// Stream ID
|
||||
ByteUtil.set31Bits(resetFrame, 5, 3);
|
||||
|
||||
// Payload - left as zero
|
||||
|
||||
os.write(resetFrame);
|
||||
os.flush();
|
||||
|
||||
// Read reset frame
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertEquals("3-RST-[" + Http2Error.FRAME_SIZE_ERROR.getCode() + "]\n",
|
||||
output.getTrace());
|
||||
}
|
||||
}
|
||||
127
test/org/apache/coyote/http2/TestHttp2Section_6_5.java
Normal file
127
test/org/apache/coyote/http2/TestHttp2Section_6_5.java
Normal file
@@ -0,0 +1,127 @@
|
||||
/*
|
||||
* 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 6.5 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.
|
||||
*/
|
||||
public class TestHttp2Section_6_5 extends Http2TestBase {
|
||||
|
||||
|
||||
@Test
|
||||
public void testSettingsFrameNonEmptAck() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
sendSettings(0, true, new SettingValue(1,1));
|
||||
|
||||
handleGoAwayResponse(1, Http2Error.FRAME_SIZE_ERROR);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSettingsFrameNonZeroStream() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
sendPriority(3, 0, 15);
|
||||
sendSettings(3, true, new SettingValue(1,1));
|
||||
|
||||
handleGoAwayResponse(1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSettingsFrameWrongLength() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
byte[] resetFrame = new byte[10];
|
||||
// length
|
||||
ByteUtil.setThreeBytes(resetFrame, 0, 1);
|
||||
// type
|
||||
resetFrame[3] = FrameType.SETTINGS.getIdByte();
|
||||
// No flags
|
||||
// Stream ID 0
|
||||
|
||||
// Payload - left as zero
|
||||
|
||||
os.write(resetFrame);
|
||||
os.flush();
|
||||
|
||||
handleGoAwayResponse(1, Http2Error.FRAME_SIZE_ERROR);
|
||||
}
|
||||
|
||||
|
||||
// Need to test sending push promise when push promise support is disabled
|
||||
|
||||
@Test
|
||||
public void testSettingsFrameInvalidPushSetting() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
sendSettings(0, false, new SettingValue(0x2,0x2));
|
||||
|
||||
handleGoAwayResponse(1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSettingsFrameInvalidWindowSizeSetting() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
sendSettings(0, false, new SettingValue(0x4,1 << 31));
|
||||
|
||||
handleGoAwayResponse(1, Http2Error.FLOW_CONTROL_ERROR);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSettingsFrameInvalidMaxFrameSizeSetting() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
sendSettings(0, false, new SettingValue(0x5,1 << 31));
|
||||
|
||||
handleGoAwayResponse(1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSettingsUnknownSetting() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
sendSettings(0, false, new SettingValue(0xFF,0xFF));
|
||||
|
||||
// Ack
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertTrue(output.getTrace(), output.getTrace().startsWith(
|
||||
"0-Settings-Ack"));
|
||||
}
|
||||
|
||||
// delayed ACKs. Requires an API (TBD) for applications to send settings.
|
||||
}
|
||||
83
test/org/apache/coyote/http2/TestHttp2Section_6_7.java
Normal file
83
test/org/apache/coyote/http2/TestHttp2Section_6_7.java
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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 java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for Section 6.7 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.
|
||||
*/
|
||||
public class TestHttp2Section_6_7 extends Http2TestBase {
|
||||
|
||||
|
||||
@Test
|
||||
public void testPingFrame() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
sendPing(0, false, "01234567".getBytes(StandardCharsets.ISO_8859_1));
|
||||
|
||||
// Ping ack
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertEquals("0-Ping-Ack-[48,49,50,51,52,53,54,55]\n", output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPingFrameUnexpectedAck() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
sendPing(0, true, "01234567".getBytes(StandardCharsets.ISO_8859_1));
|
||||
sendPing(0, false, "76543210".getBytes(StandardCharsets.ISO_8859_1));
|
||||
|
||||
// Ping ack (only for second ping)
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertEquals("0-Ping-Ack-[55,54,53,52,51,50,49,48]\n", output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPingFrameNonZeroStream() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
sendPing(1, false, "76543210".getBytes(StandardCharsets.ISO_8859_1));
|
||||
|
||||
handleGoAwayResponse(1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPingFrameWrongPayloadSize() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
sendPing(0, false, "6543210".getBytes(StandardCharsets.ISO_8859_1));
|
||||
|
||||
handleGoAwayResponse(1, Http2Error.FRAME_SIZE_ERROR);
|
||||
}
|
||||
}
|
||||
87
test/org/apache/coyote/http2/TestHttp2Section_6_8.java
Normal file
87
test/org/apache/coyote/http2/TestHttp2Section_6_8.java
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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 6.8 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.
|
||||
*/
|
||||
public class TestHttp2Section_6_8 extends Http2TestBase {
|
||||
|
||||
private static final boolean RELAX_TIMING = Boolean.getBoolean("tomcat.test.relaxTiming");
|
||||
|
||||
private static final long PING_ACK_DELAY_MS = 2000;
|
||||
// On slow systems (Gump) may need to be higher
|
||||
private static final long TIMING_MARGIN_MS = RELAX_TIMING ? 1000 : 200;
|
||||
|
||||
@Test
|
||||
public void testGoawayIgnoreNewStreams() throws Exception {
|
||||
setPingAckDelayMillis(PING_ACK_DELAY_MS);
|
||||
|
||||
http2Connect();
|
||||
|
||||
http2Protocol.setMaxConcurrentStreams(200);
|
||||
|
||||
Thread.sleep(PING_ACK_DELAY_MS + TIMING_MARGIN_MS);
|
||||
|
||||
getTomcatInstance().getConnector().pause();
|
||||
|
||||
// Go away
|
||||
parser.readFrame(true);
|
||||
Assert.assertEquals("0-Goaway-[2147483647]-[0]-[null]", output.getTrace());
|
||||
output.clearTrace();
|
||||
|
||||
// Should be processed
|
||||
sendSimpleGetRequest(3);
|
||||
|
||||
Thread.sleep(PING_ACK_DELAY_MS + TIMING_MARGIN_MS);
|
||||
|
||||
// Should be ignored
|
||||
sendSimpleGetRequest(5);
|
||||
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertEquals(getSimpleResponseTrace(3), output.getTrace());
|
||||
output.clearTrace();
|
||||
|
||||
// Finally the go away frame
|
||||
parser.readFrame(true);
|
||||
Assert.assertEquals("0-Goaway-[3]-[0]-[null]", output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testGoawayFrameNonZeroStream() throws Exception {
|
||||
// HTTP2 upgrade
|
||||
http2Connect();
|
||||
|
||||
sendGoaway(1, 1, Http2Error.NO_ERROR.getCode(), null);
|
||||
|
||||
handleGoAwayResponse(1);
|
||||
}
|
||||
|
||||
|
||||
// TODO Test header processing and window size processing for ignored
|
||||
// streams
|
||||
}
|
||||
282
test/org/apache/coyote/http2/TestHttp2Section_6_9.java
Normal file
282
test/org/apache/coyote/http2/TestHttp2Section_6_9.java
Normal file
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* 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 java.nio.ByteBuffer;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for Section 6.9 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.
|
||||
*/
|
||||
public class TestHttp2Section_6_9 extends Http2TestBase {
|
||||
|
||||
@Test
|
||||
public void testZeroWindowUpdateConnection() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
sendWindowUpdate(0, 0);
|
||||
|
||||
handleGoAwayResponse(1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testZeroWindowUpdateStream() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
sendSimplePostRequest(3, null, false);
|
||||
sendWindowUpdate(3, 0);
|
||||
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertEquals("3-RST-[" + Http2Error.PROTOCOL_ERROR.getCode() + "]\n",
|
||||
output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testWindowUpdateOnClosedStream() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
// Should not be an error so should be nothing to read
|
||||
sendWindowUpdate(1, 200);
|
||||
|
||||
// So the next request should process normally
|
||||
sendSimpleGetRequest(3);
|
||||
readSimpleGetResponse();
|
||||
Assert.assertEquals(getSimpleResponseTrace(3), output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
// TODO: Test always accounting for changes in flow control windows even if
|
||||
// the frame is in error.
|
||||
|
||||
|
||||
@Test
|
||||
public void testWindowUpdateWrongLength() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
byte[] zeroLengthWindowFrame = new byte[9];
|
||||
// Length zero
|
||||
ByteUtil.setOneBytes(zeroLengthWindowFrame, 3, FrameType.WINDOW_UPDATE.getIdByte());
|
||||
// No flags
|
||||
// Stream 1
|
||||
ByteUtil.set31Bits(zeroLengthWindowFrame, 5, 1);
|
||||
|
||||
os.write(zeroLengthWindowFrame);
|
||||
os.flush();
|
||||
|
||||
handleGoAwayResponse(1, Http2Error.FRAME_SIZE_ERROR);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEmptyDataFrameWithNoAvailableFlowControl() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
// Default connection window size is 64k - 1. Initial request will have
|
||||
// used 8k (56k -1).
|
||||
|
||||
// Use up the remaining connection window. These requests require 56k
|
||||
// but there is only 56k - 1 available.
|
||||
for (int i = 3; i < 17; i += 2) {
|
||||
sendSimpleGetRequest(i);
|
||||
readSimpleGetResponse();
|
||||
}
|
||||
output.clearTrace();
|
||||
|
||||
// It should be possible to send a request that generates an empty
|
||||
// response at this point
|
||||
sendEmptyGetRequest(17);
|
||||
// Headers
|
||||
parser.readFrame(true);
|
||||
// Body
|
||||
parser.readFrame(true);
|
||||
|
||||
// Release Stream 15 which is waiting for a single byte.
|
||||
sendWindowUpdate(0, 1024);
|
||||
|
||||
Assert.assertEquals(getEmptyResponseTrace(17), output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testWindowSizeTooLargeStream() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
// Set up stream 3
|
||||
sendSimplePostRequest(3, null, false);
|
||||
|
||||
// Super size the flow control window.
|
||||
sendWindowUpdate(3, (1 << 31) - 1);
|
||||
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertEquals("3-RST-[" + Http2Error.FLOW_CONTROL_ERROR.getCode() + "]\n",
|
||||
output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testWindowSizeTooLargeConnection() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
// Super size the flow control window.
|
||||
sendWindowUpdate(0, (1 << 31) - 1);
|
||||
|
||||
handleGoAwayResponse(1, Http2Error.FLOW_CONTROL_ERROR);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testWindowSizeAndSettingsFrame() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
// Set up a POST request that echoes the body back
|
||||
byte[] headersFrameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
byte[] dataFrameHeader = new byte[9];
|
||||
ByteBuffer dataPayload = ByteBuffer.allocate(8 * 1024);
|
||||
|
||||
buildPostRequest(headersFrameHeader, headersPayload, false,
|
||||
dataFrameHeader, dataPayload, null, 3);
|
||||
|
||||
// Write the headers
|
||||
writeFrame(headersFrameHeader, headersPayload);
|
||||
|
||||
// Now use a settings frame to reduce the size of the flow control
|
||||
// window.
|
||||
sendSettings(0, false, new SettingValue(4, 4 * 1024));
|
||||
// Ack
|
||||
parser.readFrame(true);
|
||||
Assert.assertEquals("0-Settings-Ack\n", output.getTrace());
|
||||
output.clearTrace();
|
||||
|
||||
// Write the body
|
||||
writeFrame(dataFrameHeader, dataPayload);
|
||||
|
||||
// Window size updates after reading POST body
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
Assert.assertEquals(
|
||||
"0-WindowSize-[8192]\n" +
|
||||
"3-WindowSize-[8192]\n",
|
||||
output.getTrace());
|
||||
output.clearTrace();
|
||||
|
||||
// Read stream 3 headers and first part of body
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
Assert.assertEquals(
|
||||
"3-HeadersStart\n" +
|
||||
"3-Header-[:status]-[200]\n" +
|
||||
"3-Header-[date]-["+ DEFAULT_DATE + "]\n" +
|
||||
"3-HeadersEnd\n" +
|
||||
"3-Body-4096\n", output.getTrace());
|
||||
output.clearTrace();
|
||||
|
||||
// Now use a settings frame to further reduce the size of the flow
|
||||
// control window. This should make the stream 3 window negative
|
||||
sendSettings(0, false, new SettingValue(4, 2 * 1024));
|
||||
// Ack
|
||||
parser.readFrame(true);
|
||||
Assert.assertEquals("0-Settings-Ack\n", output.getTrace());
|
||||
output.clearTrace();
|
||||
|
||||
// Now use a settings frame to increase the size of the flow control
|
||||
// window. The stream 3 window should still be negative
|
||||
sendSettings(0, false, new SettingValue(4, 3 * 1024));
|
||||
// Ack
|
||||
parser.readFrame(true);
|
||||
Assert.assertEquals("0-Settings-Ack\n", output.getTrace());
|
||||
output.clearTrace();
|
||||
|
||||
// Do a POST that won't be affected by the above limit
|
||||
sendSimplePostRequest(5, null);
|
||||
// Window size updates after reading POST body
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
Assert.assertEquals(
|
||||
"0-WindowSize-[128]\n" +
|
||||
"5-WindowSize-[128]\n",
|
||||
output.getTrace());
|
||||
output.clearTrace();
|
||||
// Headers + body
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
Assert.assertEquals(
|
||||
"5-HeadersStart\n" +
|
||||
"5-Header-[:status]-[200]\n" +
|
||||
"5-Header-[content-length]-[128]\n" +
|
||||
"5-Header-[date]-[Wed, 11 Nov 2015 19:18:42 GMT]\n" +
|
||||
"5-HeadersEnd\n" +
|
||||
"5-Body-128\n" +
|
||||
"5-EndOfStream\n", output.getTrace());
|
||||
output.clearTrace();
|
||||
|
||||
// Now use a settings frame to restore the size of the flow control
|
||||
// window.
|
||||
sendSettings(0, false, new SettingValue(4, 64 * 1024 - 1));
|
||||
|
||||
// Settings ack and stream 3 body are written from different threads.
|
||||
// Order depends on server side timing. Handle both possibilities.
|
||||
parser.readFrame(true);
|
||||
String trace = output.getTrace();
|
||||
String settingsAck = "0-Settings-Ack\n";
|
||||
String endOfStreamThree = "3-Body-4096\n3-EndOfStream\n";
|
||||
|
||||
if (settingsAck.equals(trace)) {
|
||||
// Ack the end of stream 3
|
||||
output.clearTrace();
|
||||
parser.readFrame(true);
|
||||
Assert.assertEquals(endOfStreamThree, output.getTrace());
|
||||
} else {
|
||||
// End of stream 3 thenack
|
||||
Assert.assertEquals(endOfStreamThree, output.getTrace());
|
||||
output.clearTrace();
|
||||
parser.readFrame(true);
|
||||
Assert.assertEquals(settingsAck, output.getTrace());
|
||||
}
|
||||
output.clearTrace();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testWindowSizeTooLargeViaSettings() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
// Set up stream 3
|
||||
sendSimplePostRequest(3, null, false);
|
||||
|
||||
// Increase the flow control window but keep it under the limit
|
||||
sendWindowUpdate(3, 1 << 30);
|
||||
|
||||
// Now increase beyond the limit via a settings frame
|
||||
sendSettings(0, false, new SettingValue(4, 1 << 30));
|
||||
// Ack
|
||||
parser.readFrame(true);
|
||||
Assert.assertEquals("3-RST-[" + Http2Error.FLOW_CONTROL_ERROR.getCode() + "]\n",
|
||||
output.getTrace());
|
||||
|
||||
}
|
||||
}
|
||||
217
test/org/apache/coyote/http2/TestHttp2Section_8_1.java
Normal file
217
test/org/apache/coyote/http2/TestHttp2Section_8_1.java
Normal file
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* 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 java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Unit tests for Section 8.1 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
|
||||
* examples in the RFC.
|
||||
*/
|
||||
public class TestHttp2Section_8_1 extends Http2TestBase {
|
||||
|
||||
@Test
|
||||
public void testPostWithTrailerHeaders() throws Exception {
|
||||
doTestPostWithTrailerHeaders(true);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPostWithTrailerHeadersBlocked() throws Exception {
|
||||
doTestPostWithTrailerHeaders(false);
|
||||
}
|
||||
|
||||
|
||||
private void doTestPostWithTrailerHeaders(boolean allowTrailerHeader) throws Exception{
|
||||
http2Connect();
|
||||
if (allowTrailerHeader) {
|
||||
http2Protocol.setAllowedTrailerHeaders(TRAILER_HEADER_NAME);
|
||||
}
|
||||
|
||||
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);
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
|
||||
String len;
|
||||
if (allowTrailerHeader) {
|
||||
len = Integer.toString(256 + TRAILER_HEADER_VALUE.length());
|
||||
} else {
|
||||
len = "256";
|
||||
}
|
||||
|
||||
Assert.assertEquals("0-WindowSize-[256]\n" +
|
||||
"3-WindowSize-[256]\n" +
|
||||
"3-HeadersStart\n" +
|
||||
"3-Header-[:status]-[200]\n" +
|
||||
"3-Header-[content-length]-[" + len + "]\n" +
|
||||
"3-Header-[date]-["+ DEFAULT_DATE + "]\n" +
|
||||
"3-HeadersEnd\n" +
|
||||
"3-Body-" +
|
||||
len +
|
||||
"\n" +
|
||||
"3-EndOfStream\n",
|
||||
output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSendAck() throws Exception {
|
||||
http2Connect();
|
||||
|
||||
byte[] headersFrameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
byte[] dataFrameHeader = new byte[9];
|
||||
ByteBuffer dataPayload = ByteBuffer.allocate(256);
|
||||
|
||||
buildPostRequest(headersFrameHeader, headersPayload, true,
|
||||
dataFrameHeader, dataPayload, null, 3);
|
||||
|
||||
// Write the headers
|
||||
writeFrame(headersFrameHeader, headersPayload);
|
||||
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertEquals("3-HeadersStart\n" +
|
||||
"3-Header-[:status]-[100]\n" +
|
||||
"3-HeadersEnd\n",
|
||||
output.getTrace());
|
||||
output.clearTrace();
|
||||
|
||||
// Write the body
|
||||
writeFrame(dataFrameHeader, dataPayload);
|
||||
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertEquals("0-WindowSize-[256]\n" +
|
||||
"3-WindowSize-[256]\n" +
|
||||
"3-HeadersStart\n" +
|
||||
"3-Header-[:status]-[200]\n" +
|
||||
"3-Header-[content-length]-[256]\n" +
|
||||
"3-Header-[date]-["+ DEFAULT_DATE + "]\n" +
|
||||
"3-HeadersEnd\n" +
|
||||
"3-Body-256\n" +
|
||||
"3-EndOfStream\n",
|
||||
output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUndefinedPseudoHeader() throws Exception {
|
||||
List<Header> headers = new ArrayList<>(5);
|
||||
headers.add(new Header(":method", "GET"));
|
||||
headers.add(new Header(":scheme", "http"));
|
||||
headers.add(new Header(":path", "/simple"));
|
||||
headers.add(new Header(":authority", "localhost:" + getPort()));
|
||||
headers.add(new Header(":foo", "bar"));
|
||||
|
||||
doInvalidPseudoHeaderTest(headers);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testInvalidPseudoHeader() throws Exception {
|
||||
List<Header> headers = new ArrayList<>(5);
|
||||
headers.add(new Header(":method", "GET"));
|
||||
headers.add(new Header(":scheme", "http"));
|
||||
headers.add(new Header(":path", "/simple"));
|
||||
headers.add(new Header(":authority", "localhost:" + getPort()));
|
||||
headers.add(new Header(":status", "200"));
|
||||
|
||||
doInvalidPseudoHeaderTest(headers);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPseudoHeaderOrder() throws Exception {
|
||||
// Need to do this in two frames because HPACK encoder automatically
|
||||
// re-orders fields
|
||||
|
||||
http2Connect();
|
||||
|
||||
List<Header> headers = new ArrayList<>(4);
|
||||
headers.add(new Header(":method", "GET"));
|
||||
headers.add(new Header(":scheme", "http"));
|
||||
headers.add(new Header(":path", "/simple"));
|
||||
headers.add(new Header("x-test", "test"));
|
||||
|
||||
byte[] headersFrameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
|
||||
buildSimpleGetRequestPart1(headersFrameHeader, headersPayload, headers , 3);
|
||||
|
||||
writeFrame(headersFrameHeader, headersPayload);
|
||||
|
||||
headers.clear();
|
||||
headers.add(new Header(":authority", "localhost:" + getPort()));
|
||||
headersPayload.clear();
|
||||
|
||||
buildSimpleGetRequestPart2(headersFrameHeader, headersPayload, headers , 3);
|
||||
|
||||
writeFrame(headersFrameHeader, headersPayload);
|
||||
|
||||
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertEquals("3-RST-[1]\n", output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
private void doInvalidPseudoHeaderTest(List<Header> headers) throws Exception {
|
||||
http2Connect();
|
||||
|
||||
byte[] headersFrameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
|
||||
buildGetRequest(headersFrameHeader, headersPayload, null, headers , 3);
|
||||
|
||||
// Write the headers
|
||||
writeFrame(headersFrameHeader, headersPayload);
|
||||
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertEquals("3-RST-[1]\n", output.getTrace());
|
||||
}
|
||||
}
|
||||
107
test/org/apache/coyote/http2/TestHttp2Timeouts.java
Normal file
107
test/org/apache/coyote/http2/TestHttp2Timeouts.java
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* 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.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestHttp2Timeouts extends Http2TestBase {
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void http2Connect() throws Exception {
|
||||
super.http2Connect();
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Simple request won't fill buffer so timeout will occur in Tomcat internal
|
||||
* code during response completion.
|
||||
*/
|
||||
@Test
|
||||
public void testClientWithEmptyWindow() throws Exception {
|
||||
sendSettings(0, false, new SettingValue(Setting.INITIAL_WINDOW_SIZE.getId(), 0));
|
||||
sendSimpleGetRequest(3);
|
||||
|
||||
// Settings
|
||||
parser.readFrame(false);
|
||||
// Headers
|
||||
parser.readFrame(false);
|
||||
|
||||
output.clearTrace();
|
||||
|
||||
parser.readFrame(false);
|
||||
Assert.assertEquals("3-RST-[11]\n", output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Large request will fill buffer so timeout will occur in application code
|
||||
* during response write (when Tomcat commits the response and flushes the
|
||||
* buffer as a result of the buffer filling).
|
||||
*/
|
||||
@Test
|
||||
public void testClientWithEmptyWindowLargeResponse() throws Exception {
|
||||
sendSettings(0, false, new SettingValue(Setting.INITIAL_WINDOW_SIZE.getId(), 0));
|
||||
sendLargeGetRequest(3);
|
||||
|
||||
// Settings
|
||||
parser.readFrame(false);
|
||||
// Headers
|
||||
parser.readFrame(false);
|
||||
|
||||
output.clearTrace();
|
||||
|
||||
parser.readFrame(false);
|
||||
Assert.assertEquals("3-RST-[11]\n", output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Timeout with app reading request body directly.
|
||||
*/
|
||||
@Test
|
||||
public void testClientPostsNoBody() throws Exception {
|
||||
sendSimplePostRequest(3, null, false);
|
||||
|
||||
// Headers
|
||||
parser.readFrame(false);
|
||||
output.clearTrace();
|
||||
|
||||
parser.readFrame(false);
|
||||
|
||||
Assert.assertEquals("3-RST-[11]\n", output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Timeout with app processing parameters.
|
||||
*/
|
||||
@Test
|
||||
public void testClientPostsNoParameters() throws Exception {
|
||||
sendParameterPostRequest(3, null, null, 10, false);
|
||||
|
||||
// Headers
|
||||
parser.readFrame(false);
|
||||
output.clearTrace();
|
||||
|
||||
parser.readFrame(false);
|
||||
|
||||
Assert.assertEquals("3-RST-[11]\n", output.getTrace());
|
||||
}
|
||||
}
|
||||
72
test/org/apache/coyote/http2/TestHttp2UpgradeHandler.java
Normal file
72
test/org/apache/coyote/http2/TestHttp2UpgradeHandler.java
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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 java.nio.ByteBuffer;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
|
||||
public class TestHttp2UpgradeHandler extends Http2TestBase {
|
||||
|
||||
// https://bz.apache.org/bugzilla/show_bug.cgi?id=60970
|
||||
@Test
|
||||
public void testLargeHeader() throws Exception {
|
||||
enableHttp2();
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context ctxt = tomcat.addContext("", null);
|
||||
Tomcat.addServlet(ctxt, "simple", new SimpleServlet());
|
||||
ctxt.addServletMappingDecoded("/simple", "simple");
|
||||
Tomcat.addServlet(ctxt, "large", new LargeHeaderServlet());
|
||||
ctxt.addServletMappingDecoded("/large", "large");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
openClientConnection();
|
||||
doHttpUpgrade();
|
||||
sendClientPreface();
|
||||
validateHttp2InitialResponse();
|
||||
|
||||
byte[] frameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
buildGetRequest(frameHeader, headersPayload, null, 3, "/large");
|
||||
writeFrame(frameHeader, headersPayload);
|
||||
|
||||
// Headers
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
// Body
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertEquals(
|
||||
"3-HeadersStart\n" +
|
||||
"3-Header-[:status]-[200]\n" +
|
||||
"3-Header-[x-ignore]-[...]\n" +
|
||||
"3-Header-[content-type]-[text/plain;charset=UTF-8]\n" +
|
||||
"3-Header-[content-length]-[2]\n" +
|
||||
"3-Header-[date]-[Wed, 11 Nov 2015 19:18:42 GMT]\n" +
|
||||
"3-HeadersEnd\n" +
|
||||
"3-Body-2\n" +
|
||||
"3-EndOfStream\n", output.getTrace());
|
||||
}
|
||||
|
||||
}
|
||||
98
test/org/apache/coyote/http2/TestStream.java
Normal file
98
test/org/apache/coyote/http2/TestStream.java
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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 java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
|
||||
public class TestStream extends Http2TestBase {
|
||||
|
||||
/*
|
||||
* https://bz.apache.org/bugzilla/show_bug.cgi?id=61120
|
||||
*/
|
||||
@Test
|
||||
public void testPathParam() throws Exception {
|
||||
|
||||
enableHttp2();
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context ctxt = tomcat.addContext("", null);
|
||||
Tomcat.addServlet(ctxt, "simple", new SimpleServlet());
|
||||
ctxt.addServletMappingDecoded("/simple", "simple");
|
||||
Tomcat.addServlet(ctxt, "pathparam", new PathParam());
|
||||
ctxt.addServletMappingDecoded("/pathparam", "pathparam");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
openClientConnection();
|
||||
doHttpUpgrade();
|
||||
sendClientPreface();
|
||||
validateHttp2InitialResponse();
|
||||
|
||||
byte[] frameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
buildGetRequest(frameHeader, headersPayload, null, 3,
|
||||
"/pathparam;jsessionid=" + PathParam.EXPECTED_SESSION_ID);
|
||||
writeFrame(frameHeader, headersPayload);
|
||||
|
||||
readSimpleGetResponse();
|
||||
|
||||
Assert.assertEquals(
|
||||
"3-HeadersStart\n" +
|
||||
"3-Header-[:status]-[200]\n" +
|
||||
"3-Header-[content-type]-[text/plain;charset=UTF-8]\n" +
|
||||
"3-Header-[content-length]-[2]\n" +
|
||||
"3-Header-[date]-[Wed, 11 Nov 2015 19:18:42 GMT]\n" +
|
||||
"3-HeadersEnd\n" +
|
||||
"3-Body-2\n" +
|
||||
"3-EndOfStream\n", output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
private static final class PathParam extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static final String EXPECTED_SESSION_ID = "0123456789ABCDEF";
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
|
||||
response.setContentType("text/plain");
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
|
||||
if (EXPECTED_SESSION_ID.equals(request.getRequestedSessionId())) {
|
||||
response.getWriter().write("OK");
|
||||
} else {
|
||||
response.getWriter().write("FAIL");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
226
test/org/apache/coyote/http2/TestStreamProcessor.java
Normal file
226
test/org/apache/coyote/http2/TestStreamProcessor.java
Normal file
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
* 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 java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.AsyncContext;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.Wrapper;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.tomcat.util.compat.JrePlatform;
|
||||
import org.apache.tomcat.util.http.FastHttpDateFormat;
|
||||
|
||||
public class TestStreamProcessor extends Http2TestBase {
|
||||
|
||||
@Test
|
||||
public void testAsyncComplete() throws Exception {
|
||||
enableHttp2();
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// Map the async servlet to /simple so we can re-use the HTTP/2 handling
|
||||
// logic from the super class.
|
||||
Context ctxt = tomcat.addContext("", null);
|
||||
Tomcat.addServlet(ctxt, "simple", new SimpleServlet());
|
||||
ctxt.addServletMappingDecoded("/simple", "simple");
|
||||
Wrapper w = Tomcat.addServlet(ctxt, "async", new AsyncComplete());
|
||||
w.setAsyncSupported(true);
|
||||
ctxt.addServletMappingDecoded("/async", "async");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
openClientConnection();
|
||||
doHttpUpgrade();
|
||||
sendClientPreface();
|
||||
validateHttp2InitialResponse();
|
||||
|
||||
byte[] frameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
buildGetRequest(frameHeader, headersPayload, null, 3, "/async");
|
||||
writeFrame(frameHeader, headersPayload);
|
||||
|
||||
readSimpleGetResponse();
|
||||
// Flush before startAsync means body is written in two packets so an
|
||||
// additional frame needs to be read
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertEquals(
|
||||
"3-HeadersStart\n" +
|
||||
"3-Header-[:status]-[200]\n" +
|
||||
"3-Header-[content-type]-[text/plain;charset=UTF-8]\n" +
|
||||
"3-Header-[date]-[Wed, 11 Nov 2015 19:18:42 GMT]\n" +
|
||||
"3-HeadersEnd\n" +
|
||||
"3-Body-17\n" +
|
||||
"3-Body-8\n" +
|
||||
"3-EndOfStream\n", output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testAsyncDispatch() throws Exception {
|
||||
enableHttp2();
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
// Map the async servlet to /simple so we can re-use the HTTP/2 handling
|
||||
// logic from the super class.
|
||||
Context ctxt = tomcat.addContext("", null);
|
||||
Tomcat.addServlet(ctxt, "simple", new SimpleServlet());
|
||||
ctxt.addServletMappingDecoded("/simple", "simple");
|
||||
Wrapper w = Tomcat.addServlet(ctxt, "async", new AsyncDispatch());
|
||||
w.setAsyncSupported(true);
|
||||
ctxt.addServletMappingDecoded("/async", "async");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
openClientConnection();
|
||||
doHttpUpgrade();
|
||||
sendClientPreface();
|
||||
validateHttp2InitialResponse();
|
||||
|
||||
byte[] frameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
buildGetRequest(frameHeader, headersPayload, null, 3, "/async");
|
||||
writeFrame(frameHeader, headersPayload);
|
||||
|
||||
readSimpleGetResponse();
|
||||
Assert.assertEquals(getSimpleResponseTrace(3), output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPrepareHeaders() throws Exception {
|
||||
enableHttp2();
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
File appDir = new File("test/webapp");
|
||||
Context ctxt = tomcat.addWebapp(null, "", appDir.getAbsolutePath());
|
||||
|
||||
Tomcat.addServlet(ctxt, "simple", new SimpleServlet());
|
||||
ctxt.addServletMappingDecoded("/simple", "simple");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
openClientConnection();
|
||||
doHttpUpgrade();
|
||||
sendClientPreface();
|
||||
validateHttp2InitialResponse();
|
||||
|
||||
byte[] frameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
|
||||
List<Header> headers = new ArrayList<>(3);
|
||||
headers.add(new Header(":method", "GET"));
|
||||
headers.add(new Header(":scheme", "http"));
|
||||
headers.add(new Header(":path", "/index.html"));
|
||||
headers.add(new Header(":authority", "localhost:" + getPort()));
|
||||
headers.add(new Header("if-modified-since", FastHttpDateFormat.getCurrentDate()));
|
||||
|
||||
buildGetRequest(frameHeader, headersPayload, null, headers, 3);
|
||||
|
||||
writeFrame(frameHeader, headersPayload);
|
||||
|
||||
parser.readFrame(true);
|
||||
|
||||
StringBuilder expected = new StringBuilder();
|
||||
expected.append("3-HeadersStart\n");
|
||||
expected.append("3-Header-[:status]-[304]\n");
|
||||
// Different line-endings -> different files size -> different weak eTag
|
||||
if (JrePlatform.IS_WINDOWS) {
|
||||
expected.append("3-Header-[etag]-[W/\"957-1447269522000\"]\n");
|
||||
} else {
|
||||
expected.append("3-Header-[etag]-[W/\"934-1447269522000\"]\n");
|
||||
}
|
||||
expected.append("3-Header-[date]-[Wed, 11 Nov 2015 19:18:42 GMT]\n");
|
||||
expected.append("3-HeadersEnd\n");
|
||||
|
||||
Assert.assertEquals(expected.toString(), output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
private static final class AsyncComplete extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
|
||||
response.setContentType("text/plain");
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
|
||||
PrintWriter pw = response.getWriter();
|
||||
pw.print("Enter-");
|
||||
|
||||
final AsyncContext asyncContext = request.startAsync(request, response);
|
||||
pw.print("StartAsync-");
|
||||
pw.flush();
|
||||
|
||||
asyncContext.start(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
asyncContext.getResponse().getWriter().print("Complete");
|
||||
asyncContext.complete();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static final class AsyncDispatch extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
|
||||
final AsyncContext asyncContext = request.startAsync(request, response);
|
||||
asyncContext.start(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
asyncContext.dispatch("/simple");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
187
test/org/apache/coyote/http2/TestStreamQueryString.java
Normal file
187
test/org/apache/coyote/http2/TestStreamQueryString.java
Normal file
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
* 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 java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
import org.apache.catalina.Context;
|
||||
import org.apache.catalina.startup.Tomcat;
|
||||
import org.apache.tomcat.util.buf.HexUtils;
|
||||
|
||||
/*
|
||||
* See https://bz.apache.org/bugzilla/show_bug.cgi?id=60482
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
public class TestStreamQueryString extends Http2TestBase {
|
||||
|
||||
@Parameters
|
||||
public static Collection<Object[]> inputs() {
|
||||
List<Object[]> result = new ArrayList<>();
|
||||
// Test ASCII characters from 32 to 126 inclusive
|
||||
for (int i = 32; i < 128; i++) {
|
||||
result.add(new String[] { "%" + HexUtils.toHexString(new byte[] { (byte) i})});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private final String queryValueToTest;
|
||||
|
||||
|
||||
public TestStreamQueryString(String queryValueToTest) {
|
||||
this.queryValueToTest = queryValueToTest;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testQueryString() throws Exception {
|
||||
String queryValue = "xxx" + queryValueToTest + "xxx";
|
||||
|
||||
enableHttp2();
|
||||
|
||||
Tomcat tomcat = getTomcatInstance();
|
||||
|
||||
Context ctxt = tomcat.addContext("", null);
|
||||
Tomcat.addServlet(ctxt, "query", new Query(queryValue));
|
||||
ctxt.addServletMappingDecoded("/query", "query");
|
||||
|
||||
tomcat.start();
|
||||
|
||||
openClientConnection();
|
||||
doHttpUpgrade(queryValue);
|
||||
sendClientPreface();
|
||||
validateHttp2InitialResponse();
|
||||
|
||||
byte[] frameHeader = new byte[9];
|
||||
ByteBuffer headersPayload = ByteBuffer.allocate(128);
|
||||
buildGetRequest(frameHeader, headersPayload, null, 3,
|
||||
"/query?" + Query.PARAM_NAME + "=" + queryValue);
|
||||
writeFrame(frameHeader, headersPayload);
|
||||
|
||||
readSimpleGetResponse();
|
||||
|
||||
Assert.assertEquals(queryValue,
|
||||
"3-HeadersStart\n" +
|
||||
"3-Header-[:status]-[200]\n" +
|
||||
"3-Header-[content-type]-[text/plain;charset=UTF-8]\n" +
|
||||
"3-Header-[content-length]-[2]\n" +
|
||||
"3-Header-[date]-[Wed, 11 Nov 2015 19:18:42 GMT]\n" +
|
||||
"3-HeadersEnd\n" +
|
||||
"3-Body-2\n" +
|
||||
"3-EndOfStream\n", output.getTrace());
|
||||
}
|
||||
|
||||
|
||||
protected void doHttpUpgrade(String queryValue) throws IOException {
|
||||
byte[] upgradeRequest = ("GET /query?" + Query.PARAM_NAME + "=" + queryValue + " HTTP/1.1\r\n" +
|
||||
"Host: localhost:" + getPort() + "\r\n" +
|
||||
"Connection: "+ DEFAULT_CONNECTION_HEADER_VALUE + "\r\n" +
|
||||
"Upgrade: h2c\r\n" +
|
||||
EMPTY_HTTP2_SETTINGS_HEADER +
|
||||
"\r\n").getBytes(StandardCharsets.ISO_8859_1);
|
||||
os.write(upgradeRequest);
|
||||
os.flush();
|
||||
|
||||
Assert.assertTrue("Failed to read HTTP Upgrade response",
|
||||
readHttpUpgradeResponse());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void validateHttp2InitialResponse() throws Exception {
|
||||
// - 101 response acts as acknowledgement of the HTTP2-Settings header
|
||||
// Need to read 5 frames
|
||||
// - settings (server settings - must be first)
|
||||
// - settings ack (for the settings frame in the client preface)
|
||||
// - ping
|
||||
// - headers (for response)
|
||||
// - data (for response body)
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
parser.readFrame(true);
|
||||
|
||||
Assert.assertEquals("0-Settings-[3]-[200]\n" +
|
||||
"0-Settings-End\n" +
|
||||
"0-Settings-Ack\n" +
|
||||
"0-Ping-[0,0,0,0,0,0,0,1]\n" +
|
||||
"1-HeadersStart\n" +
|
||||
"1-Header-[:status]-[200]\n" +
|
||||
"1-Header-[content-type]-[text/plain;charset=UTF-8]\n" +
|
||||
"1-Header-[content-length]-[2]\n" +
|
||||
"1-Header-[date]-[Wed, 11 Nov 2015 19:18:42 GMT]\n" +
|
||||
"1-HeadersEnd\n" +
|
||||
"1-Body-2\n" +
|
||||
"1-EndOfStream\n", output.getTrace());
|
||||
|
||||
output.clearTrace();
|
||||
}
|
||||
|
||||
|
||||
private static final class Query extends HttpServlet {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final String PARAM_NAME = "param";
|
||||
|
||||
private final String expectedValue;
|
||||
|
||||
public Query(String expectedValue) {
|
||||
String decoded;
|
||||
try {
|
||||
decoded = URLDecoder.decode(expectedValue, "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// Can't happen with UTF-8
|
||||
decoded = null;
|
||||
}
|
||||
this.expectedValue = decoded;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest request, HttpServletResponse response)
|
||||
throws ServletException, IOException {
|
||||
|
||||
response.setContentType("text/plain");
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
|
||||
if (expectedValue.equals(request.getParameter(PARAM_NAME))) {
|
||||
response.getWriter().write("OK");
|
||||
} else {
|
||||
response.getWriter().write("FAIL");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user