/* * 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;
// Nothing special about this date apart from it being the date I ran the // test that demonstrated that most HTTP/2 tests were failing because the // response now included a date header protectedstaticfinal String DEFAULT_DATE = "Wed, 11 Nov 2015 19:18:42 GMT"; protectedstaticfinallong DEFAULT_TIME = FastHttpDateFormat.parseDate(DEFAULT_DATE);
/** * Standard setup. Creates HTTP/2 connection via HTTP upgrade and ensures that the first response is correctly * received.
*/ protectedvoid http2Connect() throws Exception {
http2Connect(false);
}
MimeHeaders headers = new MimeHeaders();
headers.addValue(":method").setString("POST");
headers.addValue(":scheme").setString("http");
headers.addValue(":path").setString(path);
headers.addValue(":authority").setString("localhost:" + getPort()); if (useExpectation) {
headers.addValue("expect").setString("100-continue");
} if (contentType != null) {
headers.addValue("content-type").setString(contentType);
} if (contentLength > -1) {
headers.addValue("content-length").setLong(contentLength);
}
hpackEncoder.encode(headers, headersPayload);
headersPayload.flip();
ByteUtil.setThreeBytes(headersFrameHeader, 0, headersPayload.limit());
headersFrameHeader[3] = FrameType.HEADERS.getIdByte(); // Flags. end of headers (0x04)
headersFrameHeader[4] = 0x04; // Stream id
ByteUtil.set31Bits(headersFrameHeader, 5, streamId);
// Data if (padding != null) {
dataPayload.put((byte) (padding.length & 0xFF));
dataPayload.limit(dataPayload.capacity() - padding.length);
}
while (dataPayload.hasRemaining()) {
dataPayload.put((byte) 'x');
} if (padding != null && padding.length > 0) {
dataPayload.limit(dataPayload.capacity());
dataPayload.put(padding);
}
dataPayload.flip();
// Size
ByteUtil.setThreeBytes(dataFrameHeader, 0, dataPayload.limit()); // Data is type 0 // Flags: End of stream 1, Padding 8 if (trailersPayload == null) {
dataFrameHeader[4] = 0x01;
} else {
dataFrameHeader[4] = 0x00;
} if (padding != null) {
dataFrameHeader[4] += 0x08;
}
ByteUtil.set31Bits(dataFrameHeader, 5, streamId);
// Trailers if (trailersPayload != null) {
MimeHeaders trailerHeaders = new MimeHeaders();
trailerHeaders.addValue(TRAILER_HEADER_NAME).setString(TRAILER_HEADER_VALUE);
hpackEncoder.encode(trailerHeaders, trailersPayload);
trailersPayload.flip();
ByteUtil.setThreeBytes(trailersFrameHeader, 0, trailersPayload.limit());
trailersFrameHeader[3] = FrameType.HEADERS.getIdByte(); // Flags. end of headers (0x04) and end of stream (0x01)
trailersFrameHeader[4] = 0x05; // Stream id
ByteUtil.set31Bits(trailersFrameHeader, 5, streamId);
}
}
protectedvoid readSimplePostResponse(boolean padding) throws Http2Exception, IOException { /* * If there is padding there will always be a window update for the connection and, depending on timing, there * may be an update for the stream. The Window updates for padding (if present) may appear at any time. The * comments in the code below are only indicative of what the frames are likely to contain. Actual frame order * with padding may be different.
*/
// Connection window update after reading request body
parser.readFrame(); // Stream window update after reading request body
parser.readFrame(); // Headers
parser.readFrame(); // Body (includes end of stream)
parser.readFrame();
if (padding) { // Connection window update for padding
parser.readFrame();
// If EndOfStream has not been received then the stream window // update must have been received so a further frame needs to be // read for EndOfStream. if (!output.getTrace().contains("EndOfStream")) {
parser.readFrame();
}
}
}
protectedvoid enableHttp2(long maxConcurrentStreams, boolean tls, long readTimeout, long writeTimeout, long keepAliveTimeout, long streamReadTimout, long streamWriteTimeout) {
Tomcat tomcat = getTomcatInstance();
Connector connector = tomcat.getConnector(); Assert.assertTrue(connector.setProperty("useAsyncIO", Boolean.toString(useAsyncIO)));
http2Protocol = new UpgradableHttp2Protocol(); // Short timeouts for now. May need to increase these for CI systems.
http2Protocol.setReadTimeout(readTimeout);
http2Protocol.setWriteTimeout(writeTimeout);
http2Protocol.setKeepAliveTimeout(keepAliveTimeout);
http2Protocol.setStreamReadTimeout(streamReadTimout);
http2Protocol.setStreamWriteTimeout(streamWriteTimeout);
http2Protocol.setMaxConcurrentStreams(maxConcurrentStreams);
http2Protocol.setHttp11Protocol((AbstractHttp11Protocol<?>) connector.getProtocolHandler());
connector.addUpgradeProtocol(http2Protocol); if (tls) { // Enable TLS
TesterSupport.initSsl(tomcat);
}
}
if (responseHeaders.length < 3) { returnfalse;
} if (!responseHeaders[0].startsWith("HTTP/1.1 101 ")) { returnfalse;
}
if (!validateHeader(responseHeaders, "Connection: Upgrade")) { returnfalse;
} if (!validateHeader(responseHeaders, "Upgrade: h2c")) { returnfalse;
}
returntrue;
}
privateboolean validateHeader(String[] responseHeaders, String header) { boolean found = false; for (String responseHeader : responseHeaders) { if (responseHeader.equalsIgnoreCase(header)) {
found = true; break;
}
} return found;
}
String[] readHttpResponseHeaders() throws IOException { // Only used by test code so safe to keep this just a little larger than // we are expecting.
ByteBuffer data = ByteBuffer.allocate(256); byte[] singleByte = newbyte[1]; // Looking for \r\n\r\n int seen = 0; while (seen < 4) {
input.fill(true, singleByte); switch (seen) { case 0: case 2: { if (singleByte[0] == '\r') {
seen++;
} else {
seen = 0;
} break;
} case 1: case 3: { if (singleByte[0] == '\n') {
seen++;
} else {
seen = 0;
} break;
}
}
data.put(singleByte[0]);
}
if (seen != 4) { thrownew IOException("End of headers not found");
}
String response = new String(data.array(), data.arrayOffset(), data.arrayOffset() + data.position(),
StandardCharsets.ISO_8859_1);
void sendPriorityUpdate(int streamId, int urgency, boolean incremental) throws IOException { // Need to know the payload length first
StringBuilder sb = new StringBuilder("u=");
sb.append(urgency); if (incremental) {
sb.append(", i");
} byte[] payload = sb.toString().getBytes(StandardCharsets.US_ASCII);
// Payload for (int i = 0; i < settingsCount; i++) { // Stops IDE complaining about possible NPE Assert.assertNotNull(settings);
ByteUtil.setTwoBytes(settingFrame, (i * 6) + 9, settings[i].getSetting());
ByteUtil.setFourBytes(settingFrame, (i * 6) + 11, settings[i].getValue());
}
Assert.assertTrue(output.getTrace(),
output.getTrace().startsWith("0-Goaway-[" + lastStream + "]-[" + expectedError.getCode() + "]-["));
} catch (SocketException se) { // On some platform / Connector combinations (e.g. Windows / NIO2), // the TCP connection close will be processed before the client gets // a chance to read the connection close frame.
Tomcat tomcat = getTomcatInstance();
Connector connector = tomcat.getConnector();
Assume.assumeTrue("This test is only expected to trigger an exception with NIO2",
connector.getProtocolHandlerClassName().contains("Nio2"));
Assume.assumeTrue("This test is only expected to trigger an exception on Windows", JrePlatform.IS_WINDOWS);
}
}
staticvoid setOneBytes(byte[] output, int firstByte, int value) {
output[firstByte] = (byte) (value & 0xFF);
}
@Override publicboolean fill(boolean block, byte[] data, int offset, int length) throws IOException { // Note: Block is ignored for this test class. Reads always block. int off = offset; int len = length; while (len > 0) { int read = is.read(data, off, len); if (read == -1) { thrownew IOException("End of input stream with [" + len + "] bytes left to read");
}
off += read;
len -= read;
} returntrue;
}
@Override publicint getMaxFrameSize() { // Hard-coded to use the default return ConnectionSettingsBase.DEFAULT_MAX_FRAME_SIZE;
}
}
@Override publicvoid emitHeader(String name, String value) { if ("date".equals(name)) { // Date headers will always change so use a hard-coded default
value = DEFAULT_DATE;
} elseif ("etag".equals(name) && value.startsWith("W/\"")) { // etag headers will vary depending on when the source was // checked out, unpacked, copied etc so use the same default as // for date headers int startOfTime = value.indexOf('-');
value = value.substring(0, startOfTime + 1) + DEFAULT_TIME + "\"";
} // Some header values vary so ignore them if (HEADER_IGNORED.equals(name)) {
trace.append(lastStreamId + "-Header-[" + name + "]-[...]\n");
} else {
trace.append(lastStreamId + "-Header-[" + name + "]-[" + value + "]\n");
}
}
@Override publicvoid validateHeaders() { // NO-OP: Accept anything the server sends for the unit tests
}
@Override publicvoid setHeaderException(StreamException streamException) { // NO-OP: Accept anything the server sends for the unit tests
}
@Override publicvoid onSwallowedUnknownFrame(int streamId, int frameTypeId, int flags, int size) {
trace.append(streamId);
trace.append(',');
trace.append(frameTypeId);
trace.append(',');
trace.append(flags);
trace.append(',');
trace.append(size);
trace.append("\n");
}
@Override publicvoid onSwallowedDataFramePayload(int streamId, int swallowedDataBytesCount) { // NO-OP // Many tests swallow request bodies which triggers this // notification. It is added to the trace to reduce noise.
}
@Override protectedvoid doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // Generate content with a simple known format.
resp.setContentType("application/octet-stream");
int count = 4 * 1024; // Two bytes per entry
resp.setContentLengthLong(count * 2);
OutputStream os = resp.getOutputStream(); byte[] data = newbyte[2]; for (int i = 0; i < count; i++) {
data[0] = (byte) (i & 0xFF);
data[1] = (byte) ((i >> 8) & 0xFF);
os.write(data);
}
}
@Override protectedvoid doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // Do not do this at home. The unconstrained buffer is a DoS risk.
// Have to read into a buffer because clients typically do not start // to read the response until the request is fully written.
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IOTools.flow(req.getInputStream(), baos);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
IOTools.flow(bais, resp.getOutputStream());
// Check for trailer headers
String trailerValue = req.getTrailerFields().get(TRAILER_HEADER_NAME); if (trailerValue != null) {
resp.getOutputStream().write(trailerValue.getBytes(StandardCharsets.UTF_8));
}
}
}
@Override protectedvoid doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // Generate content with a simple known format that will exceed the // default flow control window for a stream.
resp.setContentType("application/octet-stream");
int count = 128 * 1024; // Two bytes per entry
resp.setContentLengthLong(count * 2L);
OutputStream os = resp.getOutputStream(); byte[] data = newbyte[2]; for (int i = 0; i < count; i++) {
data[0] = (byte) (i & 0xFF);
data[1] = (byte) ((i >> 8) & 0xFF);
os.write(data);
}
}
}
@Override protectedvoid doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // Request bodies are unusual with GET but not illegal
doPost(req, resp);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.