Revert "Validate pseudo and conditional HTTP/2 headers (#8619)"
This reverts commit dd5d4887ed
.
This commit is contained in:
parent
0e4c073bcf
commit
93267f9b98
@ -535,8 +535,8 @@ public abstract class AbstractHttp2ConnectionHandlerBuilder<T extends Http2Conne
|
||||
encoder = new StreamBufferingEncoder(encoder);
|
||||
}
|
||||
|
||||
Http2ConnectionDecoder decoder = new DefaultHttp2ConnectionDecoder(connection, encoder, reader,
|
||||
promisedRequestVerifier(), isAutoAckSettingsFrame(), isAutoAckPingFrame(), isValidateHeaders());
|
||||
DefaultHttp2ConnectionDecoder decoder = new DefaultHttp2ConnectionDecoder(connection, encoder, reader,
|
||||
promisedRequestVerifier(), isAutoAckSettingsFrame(), isAutoAckPingFrame());
|
||||
return buildFromCodec(decoder, encoder);
|
||||
}
|
||||
|
||||
|
@ -29,9 +29,6 @@ import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
|
||||
import static io.netty.handler.codec.http2.Http2Error.STREAM_CLOSED;
|
||||
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
|
||||
import static io.netty.handler.codec.http2.Http2Exception.streamError;
|
||||
import static io.netty.handler.codec.http2.Http2HeadersValidator.validateConnectionSpecificHeaders;
|
||||
import static io.netty.handler.codec.http2.Http2HeadersValidator.validateRequestPseudoHeaders;
|
||||
import static io.netty.handler.codec.http2.Http2HeadersValidator.validateResponsePseudoHeaders;
|
||||
import static io.netty.handler.codec.http2.Http2PromisedRequestVerifier.ALWAYS_VERIFY;
|
||||
import static io.netty.handler.codec.http2.Http2Stream.State.CLOSED;
|
||||
import static io.netty.handler.codec.http2.Http2Stream.State.HALF_CLOSED_REMOTE;
|
||||
@ -60,7 +57,6 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
|
||||
private final Http2PromisedRequestVerifier requestVerifier;
|
||||
private final Http2SettingsReceivedConsumer settingsReceivedConsumer;
|
||||
private final boolean autoAckPing;
|
||||
private final boolean validateHeaders;
|
||||
|
||||
public DefaultHttp2ConnectionDecoder(Http2Connection connection,
|
||||
Http2ConnectionEncoder encoder,
|
||||
@ -95,15 +91,6 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
|
||||
this(connection, encoder, frameReader, requestVerifier, autoAckSettings, true);
|
||||
}
|
||||
|
||||
public DefaultHttp2ConnectionDecoder(Http2Connection connection,
|
||||
Http2ConnectionEncoder encoder,
|
||||
Http2FrameReader frameReader,
|
||||
Http2PromisedRequestVerifier requestVerifier,
|
||||
boolean autoAckSettings,
|
||||
boolean autoAckPing) {
|
||||
this(connection, encoder, frameReader, requestVerifier, autoAckSettings, autoAckPing, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new instance.
|
||||
* @param connection The {@link Http2Connection} associated with this decoder.
|
||||
@ -124,8 +111,7 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
|
||||
Http2FrameReader frameReader,
|
||||
Http2PromisedRequestVerifier requestVerifier,
|
||||
boolean autoAckSettings,
|
||||
boolean autoAckPing,
|
||||
boolean validateHeaders) {
|
||||
boolean autoAckPing) {
|
||||
this.autoAckPing = autoAckPing;
|
||||
if (autoAckSettings) {
|
||||
settingsReceivedConsumer = null;
|
||||
@ -140,7 +126,6 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
|
||||
this.frameReader = requireNonNull(frameReader, "frameReader");
|
||||
this.encoder = requireNonNull(encoder, "encoder");
|
||||
this.requestVerifier = requireNonNull(requestVerifier, "requestVerifier");
|
||||
this.validateHeaders = validateHeaders;
|
||||
if (connection.local().flowController() == null) {
|
||||
connection.local().flowController(new DefaultHttp2LocalFlowController(connection));
|
||||
}
|
||||
@ -350,10 +335,6 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
|
||||
streamId, endOfStream, stream.state());
|
||||
}
|
||||
|
||||
if (validateHeaders) {
|
||||
validateHeaders(streamId, headers, stream);
|
||||
}
|
||||
|
||||
switch (stream.state()) {
|
||||
case RESERVED_REMOTE:
|
||||
stream.open(endOfStream);
|
||||
@ -646,18 +627,6 @@ public class DefaultHttp2ConnectionDecoder implements Http2ConnectionDecoder {
|
||||
throw connectionError(PROTOCOL_ERROR, "Stream %d does not exist", streamId);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateHeaders(int streamId, Http2Headers headers, Http2Stream stream) throws Http2Exception {
|
||||
if (connection.isServer()) {
|
||||
if (!stream.isHeadersReceived() || stream.state() == HALF_CLOSED_REMOTE) {
|
||||
validateRequestPseudoHeaders(headers, streamId);
|
||||
}
|
||||
} else {
|
||||
validateResponsePseudoHeaders(headers, streamId);
|
||||
}
|
||||
|
||||
validateConnectionSpecificHeaders(headers, streamId);
|
||||
}
|
||||
}
|
||||
|
||||
private final class PrefaceFrameListener implements Http2FrameListener {
|
||||
|
@ -393,8 +393,13 @@ final class HpackDecoder {
|
||||
throw streamError(streamId, PROTOCOL_ERROR, "Invalid HTTP/2 pseudo-header '%s' encountered.", name);
|
||||
}
|
||||
|
||||
return pseudoHeader.isRequestOnly() ?
|
||||
final HeaderType currentHeaderType = pseudoHeader.isRequestOnly() ?
|
||||
HeaderType.REQUEST_PSEUDO_HEADER : HeaderType.RESPONSE_PSEUDO_HEADER;
|
||||
if (previousHeaderType != null && currentHeaderType != previousHeaderType) {
|
||||
throw streamError(streamId, PROTOCOL_ERROR, "Mix of request and response pseudo-headers.");
|
||||
}
|
||||
|
||||
return currentHeaderType;
|
||||
}
|
||||
|
||||
return HeaderType.REGULAR_HEADER;
|
||||
|
@ -1,159 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 The Netty Project
|
||||
*
|
||||
* The Netty Project 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 io.netty.handler.codec.http2;
|
||||
|
||||
import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName;
|
||||
import io.netty.util.AsciiString;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
|
||||
import static io.netty.handler.codec.http.HttpHeaderNames.KEEP_ALIVE;
|
||||
import static io.netty.handler.codec.http.HttpHeaderNames.TE;
|
||||
import static io.netty.handler.codec.http.HttpHeaderNames.TRANSFER_ENCODING;
|
||||
import static io.netty.handler.codec.http.HttpHeaderNames.UPGRADE;
|
||||
import static io.netty.handler.codec.http.HttpHeaderValues.TRAILERS;
|
||||
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
|
||||
import static io.netty.handler.codec.http2.Http2Exception.streamError;
|
||||
import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.METHOD;
|
||||
import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.PATH;
|
||||
import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.SCHEME;
|
||||
import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.hasPseudoHeaderFormat;
|
||||
|
||||
final class Http2HeadersValidator {
|
||||
|
||||
private static final List<AsciiString> connectionSpecificHeaders = Collections.unmodifiableList(
|
||||
Arrays.asList(CONNECTION, TRANSFER_ENCODING, KEEP_ALIVE, UPGRADE));
|
||||
|
||||
private Http2HeadersValidator() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates connection-specific headers according to
|
||||
* <a href="https://tools.ietf.org/html/rfc7540#section-8.1.2.2">RFC7540, section-8.1.2.2</a>
|
||||
*/
|
||||
static void validateConnectionSpecificHeaders(Http2Headers headers, int streamId) throws Http2Exception {
|
||||
for (int i = 0; i < connectionSpecificHeaders.size(); i++) {
|
||||
final AsciiString header = connectionSpecificHeaders.get(i);
|
||||
if (headers.contains(header)) {
|
||||
throw streamError(streamId, PROTOCOL_ERROR,
|
||||
"Connection-specific headers like [%s] must not be used with HTTP/2.", header);
|
||||
}
|
||||
}
|
||||
|
||||
final CharSequence teHeader = headers.get(TE);
|
||||
if (teHeader != null && !AsciiString.contentEqualsIgnoreCase(teHeader, TRAILERS)) {
|
||||
throw streamError(streamId, PROTOCOL_ERROR,
|
||||
"TE header must not contain any value other than \"%s\"", TRAILERS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates response pseudo-header fields
|
||||
*/
|
||||
static void validateResponsePseudoHeaders(Http2Headers headers, int streamId) throws Http2Exception {
|
||||
for (Entry<CharSequence, CharSequence> entry : headers) {
|
||||
final CharSequence key = entry.getKey();
|
||||
if (!hasPseudoHeaderFormat(key)) {
|
||||
// We know that pseudo header appears first so we can stop
|
||||
// looking once we get to the first non pseudo headers.
|
||||
break;
|
||||
}
|
||||
|
||||
final PseudoHeaderName pseudoHeader = PseudoHeaderName.getPseudoHeader(key);
|
||||
if (pseudoHeader.isRequestOnly()) {
|
||||
throw streamError(streamId, PROTOCOL_ERROR,
|
||||
"Request pseudo-header [%s] is not allowed in a response.", key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates request pseudo-header fields according to
|
||||
* <a href="https://tools.ietf.org/html/rfc7540#section-8.1.2.3">RFC7540, section-8.1.2.3</a>
|
||||
*/
|
||||
static void validateRequestPseudoHeaders(Http2Headers headers, int streamId) throws Http2Exception {
|
||||
final CharSequence method = headers.get(METHOD.value());
|
||||
if (method == null) {
|
||||
throw streamError(streamId, PROTOCOL_ERROR,
|
||||
"Mandatory header [:method] is missing.");
|
||||
}
|
||||
|
||||
if (HttpMethod.CONNECT.asciiName().contentEqualsIgnoreCase(method)) {
|
||||
if (headers.contains(SCHEME.value())) {
|
||||
throw streamError(streamId, PROTOCOL_ERROR,
|
||||
"Header [:scheme] must be omitted when using CONNECT method.");
|
||||
}
|
||||
|
||||
if (headers.contains(PATH.value())) {
|
||||
throw streamError(streamId, PROTOCOL_ERROR,
|
||||
"Header [:path] must be omitted when using CONNECT method.");
|
||||
}
|
||||
|
||||
if (headers.getAll(METHOD.value()).size() > 1) {
|
||||
throw streamError(streamId, PROTOCOL_ERROR,
|
||||
"Header [:method] should have a unique value.");
|
||||
}
|
||||
} else {
|
||||
final CharSequence path = headers.get(PATH.value());
|
||||
if (path != null && path.length() == 0) {
|
||||
throw streamError(streamId, PROTOCOL_ERROR, "[:path] header cannot be empty.");
|
||||
}
|
||||
|
||||
int methodHeadersCount = 0;
|
||||
int pathHeadersCount = 0;
|
||||
int schemeHeadersCount = 0;
|
||||
for (Entry<CharSequence, CharSequence> entry : headers) {
|
||||
final CharSequence key = entry.getKey();
|
||||
if (!hasPseudoHeaderFormat(key)) {
|
||||
// We know that pseudo header appears first so we can stop
|
||||
// looking once we get to the first non pseudo headers.
|
||||
break;
|
||||
}
|
||||
|
||||
final PseudoHeaderName pseudoHeader = PseudoHeaderName.getPseudoHeader(key);
|
||||
if (METHOD.value().contentEquals(key)) {
|
||||
methodHeadersCount++;
|
||||
} else if (PATH.value().contentEquals(key)) {
|
||||
pathHeadersCount++;
|
||||
} else if (SCHEME.value().contentEquals(key)) {
|
||||
schemeHeadersCount++;
|
||||
} else if (!pseudoHeader.isRequestOnly()) {
|
||||
throw streamError(streamId, PROTOCOL_ERROR,
|
||||
"Response pseudo-header [%s] is not allowed in a request.", key);
|
||||
}
|
||||
}
|
||||
|
||||
validatePseudoHeaderCount(streamId, methodHeadersCount, METHOD);
|
||||
validatePseudoHeaderCount(streamId, pathHeadersCount, PATH);
|
||||
validatePseudoHeaderCount(streamId, schemeHeadersCount, SCHEME);
|
||||
}
|
||||
}
|
||||
|
||||
private static void validatePseudoHeaderCount(int streamId, int valueCount, PseudoHeaderName headerName)
|
||||
throws Http2Exception {
|
||||
if (valueCount == 0) {
|
||||
throw streamError(streamId, PROTOCOL_ERROR,
|
||||
"Mandatory header [%s] is missing.", headerName.value());
|
||||
} else if (valueCount > 1) {
|
||||
throw streamError(streamId, PROTOCOL_ERROR,
|
||||
"Header [%s] should have a unique value.", headerName.value());
|
||||
}
|
||||
}
|
||||
}
|
@ -391,14 +391,9 @@ public final class HttpConversionUtil {
|
||||
if (in instanceof HttpRequest) {
|
||||
HttpRequest request = (HttpRequest) in;
|
||||
URI requestTargetUri = URI.create(request.uri());
|
||||
out.path(toHttp2Path(requestTargetUri));
|
||||
out.method(request.method().asciiName());
|
||||
|
||||
// According to the spec https://tools.ietf.org/html/rfc7540#section-8.3 scheme and path
|
||||
// should be omitted for CONNECT method
|
||||
if (request.method() != HttpMethod.CONNECT) {
|
||||
setHttp2Scheme(inHeaders, requestTargetUri, out);
|
||||
out.path(toHttp2Path(requestTargetUri));
|
||||
}
|
||||
setHttp2Scheme(inHeaders, requestTargetUri, out);
|
||||
|
||||
if (!isOriginForm(requestTargetUri) && !isAsteriskForm(requestTargetUri)) {
|
||||
// Attempt to take from HOST header before taking from the request-line
|
||||
|
@ -68,7 +68,6 @@ public class DataCompressionHttp2Test {
|
||||
private static final AsciiString GET = new AsciiString("GET");
|
||||
private static final AsciiString POST = new AsciiString("POST");
|
||||
private static final AsciiString PATH = new AsciiString("/some/path");
|
||||
private static final AsciiString SCHEME = new AsciiString("http");
|
||||
|
||||
@Mock
|
||||
private Http2FrameListener serverListener;
|
||||
@ -137,7 +136,7 @@ public class DataCompressionHttp2Test {
|
||||
@Test
|
||||
public void justHeadersNoData() throws Exception {
|
||||
bootstrapEnv(0);
|
||||
final Http2Headers headers = new DefaultHttp2Headers().method(GET).path(PATH).scheme(SCHEME)
|
||||
final Http2Headers headers = new DefaultHttp2Headers().method(GET).path(PATH)
|
||||
.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.GZIP);
|
||||
|
||||
runInChannel(clientChannel, () -> {
|
||||
@ -155,7 +154,7 @@ public class DataCompressionHttp2Test {
|
||||
final ByteBuf data = Unpooled.copiedBuffer(text.getBytes());
|
||||
bootstrapEnv(data.readableBytes());
|
||||
try {
|
||||
final Http2Headers headers = new DefaultHttp2Headers().method(POST).path(PATH).scheme(SCHEME)
|
||||
final Http2Headers headers = new DefaultHttp2Headers().method(POST).path(PATH)
|
||||
.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.GZIP);
|
||||
|
||||
runInChannel(clientChannel, () -> {
|
||||
@ -176,7 +175,7 @@ public class DataCompressionHttp2Test {
|
||||
final ByteBuf data = Unpooled.copiedBuffer(text.getBytes());
|
||||
bootstrapEnv(data.readableBytes());
|
||||
try {
|
||||
final Http2Headers headers = new DefaultHttp2Headers().method(POST).path(PATH).scheme(SCHEME)
|
||||
final Http2Headers headers = new DefaultHttp2Headers().method(POST).path(PATH)
|
||||
.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.GZIP);
|
||||
|
||||
runInChannel(clientChannel, () -> {
|
||||
@ -199,7 +198,7 @@ public class DataCompressionHttp2Test {
|
||||
final ByteBuf data2 = Unpooled.copiedBuffer(text2.getBytes());
|
||||
bootstrapEnv(data1.readableBytes() + data2.readableBytes());
|
||||
try {
|
||||
final Http2Headers headers = new DefaultHttp2Headers().method(POST).path(PATH).scheme(SCHEME)
|
||||
final Http2Headers headers = new DefaultHttp2Headers().method(POST).path(PATH)
|
||||
.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.GZIP);
|
||||
|
||||
runInChannel(clientChannel, () -> {
|
||||
@ -224,7 +223,7 @@ public class DataCompressionHttp2Test {
|
||||
bootstrapEnv(BUFFER_SIZE);
|
||||
final ByteBuf data = Unpooled.wrappedBuffer(bytes);
|
||||
try {
|
||||
final Http2Headers headers = new DefaultHttp2Headers().method(POST).path(PATH).scheme(SCHEME)
|
||||
final Http2Headers headers = new DefaultHttp2Headers().method(POST).path(PATH)
|
||||
.set(HttpHeaderNames.CONTENT_ENCODING, HttpHeaderValues.DEFLATE);
|
||||
|
||||
runInChannel(clientChannel, () -> {
|
||||
|
@ -23,13 +23,13 @@ import io.netty.channel.ChannelPromise;
|
||||
import io.netty.channel.DefaultChannelPromise;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.util.concurrent.ImmediateEventExecutor;
|
||||
import io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName;
|
||||
import junit.framework.AssertionFailedError;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.util.Collections;
|
||||
@ -39,11 +39,9 @@ import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
|
||||
import static io.netty.buffer.Unpooled.wrappedBuffer;
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT;
|
||||
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
|
||||
import static io.netty.handler.codec.http2.Http2Stream.State.HALF_CLOSED_REMOTE;
|
||||
import static io.netty.handler.codec.http2.Http2Stream.State.IDLE;
|
||||
import static io.netty.handler.codec.http2.Http2Stream.State.OPEN;
|
||||
import static io.netty.handler.codec.http2.Http2Stream.State.RESERVED_REMOTE;
|
||||
import static io.netty.handler.codec.http2.Http2TestUtil.newHttp2HeadersWithRequestPseudoHeaders;
|
||||
import static io.netty.util.CharsetUtil.UTF_8;
|
||||
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
@ -185,8 +183,7 @@ public class DefaultHttp2ConnectionDecoderTest {
|
||||
when(ctx.newPromise()).thenReturn(promise);
|
||||
when(ctx.write(any())).thenReturn(future);
|
||||
|
||||
decoder = new DefaultHttp2ConnectionDecoder(connection, encoder, reader,
|
||||
Http2PromisedRequestVerifier.ALWAYS_VERIFY, true, true, true);
|
||||
decoder = new DefaultHttp2ConnectionDecoder(connection, encoder, reader);
|
||||
decoder.lifecycleManager(lifecycleManager);
|
||||
decoder.frameListener(listener);
|
||||
|
||||
@ -501,64 +498,6 @@ public class DefaultHttp2ConnectionDecoderTest {
|
||||
eq(DEFAULT_PRIORITY_WEIGHT), eq(false), eq(0), eq(false));
|
||||
}
|
||||
|
||||
@Test(expected = Http2Exception.class)
|
||||
public void requestPseudoHeadersInResponseThrows() throws Exception {
|
||||
when(connection.isServer()).thenReturn(false);
|
||||
when(connection.stream(STREAM_ID)).thenReturn(null);
|
||||
when(connection.streamMayHaveExisted(STREAM_ID)).thenReturn(false);
|
||||
when(remote.createStream(eq(STREAM_ID), anyBoolean())).thenReturn(stream);
|
||||
when(stream.state()).thenReturn(HALF_CLOSED_REMOTE);
|
||||
final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders();
|
||||
decode().onHeadersRead(ctx, STREAM_ID, headers, 0, false);
|
||||
}
|
||||
|
||||
@Test(expected = Http2Exception.class)
|
||||
public void missingPseudoHeadersInLeadingHeaderThrows() throws Exception {
|
||||
when(connection.isServer()).thenReturn(true);
|
||||
when(connection.stream(STREAM_ID)).thenReturn(null);
|
||||
when(connection.streamMayHaveExisted(STREAM_ID)).thenReturn(false);
|
||||
when(remote.createStream(eq(STREAM_ID), anyBoolean())).thenReturn(stream);
|
||||
when(stream.state()).thenReturn(HALF_CLOSED_REMOTE);
|
||||
final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders();
|
||||
headers.remove(PseudoHeaderName.METHOD.value());
|
||||
decode().onHeadersRead(ctx, STREAM_ID, headers, 0, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingPseudoHeadersInLeadingHeaderShouldNotThrowsIfValidationDisabled() throws Exception {
|
||||
when(connection.isServer()).thenReturn(true);
|
||||
when(connection.stream(STREAM_ID)).thenReturn(null);
|
||||
when(connection.streamMayHaveExisted(STREAM_ID)).thenReturn(false);
|
||||
when(remote.createStream(eq(STREAM_ID), anyBoolean())).thenReturn(stream);
|
||||
when(stream.state()).thenReturn(HALF_CLOSED_REMOTE);
|
||||
final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders();
|
||||
headers.remove(PseudoHeaderName.METHOD.value());
|
||||
|
||||
decoder = new DefaultHttp2ConnectionDecoder(connection, encoder, reader,
|
||||
Http2PromisedRequestVerifier.ALWAYS_VERIFY, true, true, false);
|
||||
decoder.lifecycleManager(lifecycleManager);
|
||||
decoder.frameListener(listener);
|
||||
|
||||
// Simulate receiving the initial settings from the remote endpoint.
|
||||
decode().onSettingsRead(ctx, new Http2Settings());
|
||||
// Simulate receiving the SETTINGS ACK for the initial settings.
|
||||
decode().onSettingsAckRead(ctx);
|
||||
|
||||
decode().onHeadersRead(ctx, STREAM_ID, headers, 0, false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingPseudoHeadersInTrailerHeaderDoesNotThrow() throws Exception {
|
||||
when(connection.isServer()).thenReturn(true);
|
||||
when(connection.stream(STREAM_ID)).thenReturn(stream);
|
||||
|
||||
decode().onHeadersRead(ctx, STREAM_ID, newHttp2HeadersWithRequestPseudoHeaders(), 0, false);
|
||||
|
||||
final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders();
|
||||
headers.remove(PseudoHeaderName.METHOD.value());
|
||||
decode().onHeadersRead(ctx, STREAM_ID, headers, 0, true);
|
||||
}
|
||||
|
||||
@Test(expected = Http2Exception.class)
|
||||
public void trailersDoNotEndStreamThrows() throws Exception {
|
||||
decode().onHeadersRead(ctx, STREAM_ID, EmptyHttp2Headers.INSTANCE, 0, false);
|
||||
|
@ -575,6 +575,46 @@ public class HpackDecoderTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestPseudoHeaderInResponse() throws Exception {
|
||||
ByteBuf in = Unpooled.buffer(200);
|
||||
try {
|
||||
HpackEncoder hpackEncoder = new HpackEncoder(true);
|
||||
|
||||
Http2Headers toEncode = new DefaultHttp2Headers();
|
||||
toEncode.add(":status", "200");
|
||||
toEncode.add(":method", "GET");
|
||||
hpackEncoder.encodeHeaders(1, in, toEncode, NEVER_SENSITIVE);
|
||||
|
||||
Http2Headers decoded = new DefaultHttp2Headers();
|
||||
|
||||
expectedException.expect(Http2Exception.StreamException.class);
|
||||
hpackDecoder.decode(1, in, decoded, true);
|
||||
} finally {
|
||||
in.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void responsePseudoHeaderInRequest() throws Exception {
|
||||
ByteBuf in = Unpooled.buffer(200);
|
||||
try {
|
||||
HpackEncoder hpackEncoder = new HpackEncoder(true);
|
||||
|
||||
Http2Headers toEncode = new DefaultHttp2Headers();
|
||||
toEncode.add(":method", "GET");
|
||||
toEncode.add(":status", "200");
|
||||
hpackEncoder.encodeHeaders(1, in, toEncode, NEVER_SENSITIVE);
|
||||
|
||||
Http2Headers decoded = new DefaultHttp2Headers();
|
||||
|
||||
expectedException.expect(Http2Exception.StreamException.class);
|
||||
hpackDecoder.decode(1, in, decoded, true);
|
||||
} finally {
|
||||
in.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pseudoHeaderAfterRegularHeader() throws Exception {
|
||||
ByteBuf in = Unpooled.buffer(200);
|
||||
@ -604,7 +644,7 @@ public class HpackDecoderTest {
|
||||
|
||||
Http2Headers toEncode = new DefaultHttp2Headers();
|
||||
toEncode.add(":method", "GET");
|
||||
toEncode.add(":unknownpseudoheader", "200");
|
||||
toEncode.add(":status", "200");
|
||||
toEncode.add("foo", "bar");
|
||||
hpackEncoder.encodeHeaders(1, in1, toEncode, NEVER_SENSITIVE);
|
||||
|
||||
@ -624,7 +664,7 @@ public class HpackDecoderTest {
|
||||
|
||||
assertEquals(3, decoded.size());
|
||||
assertEquals("GET", decoded.method().toString());
|
||||
assertEquals("200", decoded.get(":unknownpseudoheader").toString());
|
||||
assertEquals("200", decoded.status().toString());
|
||||
assertEquals("bar", decoded.get("foo").toString());
|
||||
} finally {
|
||||
in1.release();
|
||||
|
@ -1,174 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018 The Netty Project
|
||||
*
|
||||
* The Netty Project 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 io.netty.handler.codec.http2;
|
||||
|
||||
import io.netty.handler.codec.http2.Http2Exception.StreamException;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
|
||||
import static io.netty.handler.codec.http.HttpHeaderNames.TE;
|
||||
import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.METHOD;
|
||||
import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.PATH;
|
||||
import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.SCHEME;
|
||||
import static io.netty.handler.codec.http2.Http2Headers.PseudoHeaderName.STATUS;
|
||||
import static io.netty.handler.codec.http2.Http2HeadersValidator.validateConnectionSpecificHeaders;
|
||||
import static io.netty.handler.codec.http2.Http2HeadersValidator.validateRequestPseudoHeaders;
|
||||
import static io.netty.handler.codec.http2.Http2TestUtil.newHttp2HeadersWithRequestPseudoHeaders;
|
||||
|
||||
public class Http2HeadersValidatorTest {
|
||||
|
||||
private static final int STREAM_ID = 3;
|
||||
|
||||
@Rule
|
||||
public final ExpectedException expectedException = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void validateConnectionSpecificHeadersShouldThrowIfConnectionHeaderPresent() throws Http2Exception {
|
||||
expectedException.expect(StreamException.class);
|
||||
expectedException.expectMessage("Connection-specific headers like [connection] must not be used with HTTP");
|
||||
|
||||
final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders();
|
||||
headers.add(CONNECTION, "keep-alive");
|
||||
validateConnectionSpecificHeaders(headers, STREAM_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validateConnectionSpecificHeadersShouldThrowIfTeHeaderValueIsNotTrailers() throws Http2Exception {
|
||||
expectedException.expect(StreamException.class);
|
||||
expectedException.expectMessage("TE header must not contain any value other than \"trailers\"");
|
||||
|
||||
final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders();
|
||||
headers.add(TE, "trailers, deflate");
|
||||
validateConnectionSpecificHeaders(headers, STREAM_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validatePseudoHeadersShouldThrowWhenMethodHeaderIsMissing() throws Http2Exception {
|
||||
expectedException.expect(StreamException.class);
|
||||
expectedException.expectMessage("Mandatory header [:method] is missing.");
|
||||
|
||||
final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders();
|
||||
headers.remove(METHOD.value());
|
||||
validateRequestPseudoHeaders(headers, STREAM_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validatePseudoHeadersShouldThrowWhenPathHeaderIsMissing() throws Http2Exception {
|
||||
expectedException.expect(StreamException.class);
|
||||
expectedException.expectMessage("Mandatory header [:path] is missing.");
|
||||
|
||||
final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders();
|
||||
headers.remove(PATH.value());
|
||||
validateRequestPseudoHeaders(headers, STREAM_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validatePseudoHeadersShouldThrowWhenPathHeaderIsEmpty() throws Http2Exception {
|
||||
expectedException.expect(StreamException.class);
|
||||
expectedException.expectMessage("[:path] header cannot be empty.");
|
||||
|
||||
final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders();
|
||||
headers.set(PATH.value(), "");
|
||||
validateRequestPseudoHeaders(headers, STREAM_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validatePseudoHeadersShouldThrowWhenSchemeHeaderIsMissing() throws Http2Exception {
|
||||
expectedException.expect(StreamException.class);
|
||||
expectedException.expectMessage("Mandatory header [:scheme] is missing.");
|
||||
|
||||
final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders();
|
||||
headers.remove(SCHEME.value());
|
||||
validateRequestPseudoHeaders(headers, STREAM_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validatePseudoHeadersShouldThrowIfMethodHeaderIsNotUnique() throws Http2Exception {
|
||||
expectedException.expect(StreamException.class);
|
||||
expectedException.expectMessage("Header [:method] should have a unique value.");
|
||||
|
||||
final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders();
|
||||
headers.add(METHOD.value(), "GET");
|
||||
validateRequestPseudoHeaders(headers, STREAM_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validatePseudoHeadersShouldThrowIfPathHeaderIsNotUnique() throws Http2Exception {
|
||||
expectedException.expect(StreamException.class);
|
||||
expectedException.expectMessage("Header [:path] should have a unique value.");
|
||||
|
||||
final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders();
|
||||
headers.add(PATH.value(), "/");
|
||||
validateRequestPseudoHeaders(headers, STREAM_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validatePseudoHeadersShouldThrowIfSchemeHeaderIsNotUnique() throws Http2Exception {
|
||||
expectedException.expect(StreamException.class);
|
||||
expectedException.expectMessage("Header [:scheme] should have a unique value.");
|
||||
|
||||
final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders();
|
||||
headers.add(SCHEME.value(), "/");
|
||||
validateRequestPseudoHeaders(headers, STREAM_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validatePseudoHeadersShouldThrowIfMethodHeaderIsNotUniqueWhenMethodIsConnect() throws Http2Exception {
|
||||
expectedException.expect(StreamException.class);
|
||||
expectedException.expectMessage("Header [:method] should have a unique value.");
|
||||
|
||||
final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders();
|
||||
headers.remove(SCHEME.value());
|
||||
headers.remove(PATH.value());
|
||||
headers.set(METHOD.value(), "CONNECT");
|
||||
headers.add(METHOD.value(), "CONNECT");
|
||||
validateRequestPseudoHeaders(headers, STREAM_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validatePseudoHeadersShouldThrowIfPathHeaderIsPresentWhenMethodIsConnect() throws Http2Exception {
|
||||
expectedException.expect(StreamException.class);
|
||||
expectedException.expectMessage("Header [:path] must be omitted when using CONNECT method.");
|
||||
|
||||
final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders();
|
||||
headers.set(METHOD.value(), "CONNECT");
|
||||
headers.remove(SCHEME.value());
|
||||
validateRequestPseudoHeaders(headers, STREAM_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validatePseudoHeadersShouldThrowIfSchemeHeaderIsPresentWhenMethodIsConnect() throws Http2Exception {
|
||||
expectedException.expect(StreamException.class);
|
||||
expectedException.expectMessage("Header [:scheme] must be omitted when using CONNECT method.");
|
||||
|
||||
final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders();
|
||||
headers.set(METHOD.value(), "CONNECT");
|
||||
headers.remove(PATH.value());
|
||||
validateRequestPseudoHeaders(headers, STREAM_ID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validatePseudoHeadersShouldThrowIfResponseHeaderInRequest() throws Http2Exception {
|
||||
expectedException.expect(StreamException.class);
|
||||
expectedException.expectMessage("Response pseudo-header [:status] is not allowed in a request.");
|
||||
|
||||
final Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders();
|
||||
headers.add(STATUS.value(), "200");
|
||||
validateRequestPseudoHeaders(headers, STREAM_ID);
|
||||
}
|
||||
|
||||
}
|
@ -42,7 +42,6 @@ import org.junit.Test;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2CodecUtil.isStreamIdValid;
|
||||
import static io.netty.handler.codec.http2.Http2TestUtil.newHttp2HeadersWithRequestPseudoHeaders;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
@ -158,8 +157,8 @@ public class Http2MultiplexCodecBuilderTest {
|
||||
assertTrue(childChannel2.isActive());
|
||||
assertFalse(isStreamIdValid(childChannel2.stream().id()));
|
||||
|
||||
Http2Headers headers1 = newHttp2HeadersWithRequestPseudoHeaders();
|
||||
Http2Headers headers2 = newHttp2HeadersWithRequestPseudoHeaders();
|
||||
Http2Headers headers1 = new DefaultHttp2Headers();
|
||||
Http2Headers headers2 = new DefaultHttp2Headers();
|
||||
// Test that streams can be made active (headers sent) in different order than the corresponding channels
|
||||
// have been created.
|
||||
childChannel2.writeAndFlush(new DefaultHttp2HeadersFrame(headers2));
|
||||
@ -188,7 +187,7 @@ public class Http2MultiplexCodecBuilderTest {
|
||||
assertTrue(childChannel.isRegistered());
|
||||
assertTrue(childChannel.isActive());
|
||||
|
||||
Http2Headers headers = newHttp2HeadersWithRequestPseudoHeaders();
|
||||
Http2Headers headers = new DefaultHttp2Headers();
|
||||
childChannel.writeAndFlush(new DefaultHttp2HeadersFrame(headers));
|
||||
ByteBuf data = Unpooled.buffer(100).writeZero(100);
|
||||
childChannel.writeAndFlush(new DefaultHttp2DataFrame(data, true));
|
||||
|
@ -94,13 +94,6 @@ public final class Http2TestUtil {
|
||||
return data;
|
||||
}
|
||||
|
||||
public static Http2Headers newHttp2HeadersWithRequestPseudoHeaders() {
|
||||
return new DefaultHttp2Headers(true)
|
||||
.method("GET")
|
||||
.path("/")
|
||||
.scheme("https");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link AsciiString} that wraps a randomly-filled byte array.
|
||||
*/
|
||||
|
@ -244,8 +244,8 @@ public class HttpToHttp2ConnectionHandlerTest {
|
||||
final HttpHeaders httpHeaders = request.headers();
|
||||
httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
|
||||
final Http2Headers http2Headers =
|
||||
new DefaultHttp2Headers().method(new AsciiString("CONNECT"))
|
||||
.authority(new AsciiString("www.example.com:80"));
|
||||
new DefaultHttp2Headers().method(new AsciiString("CONNECT")).path(new AsciiString("/"))
|
||||
.scheme(new AsciiString("http")).authority(new AsciiString("www.example.com:80"));
|
||||
|
||||
ChannelPromise writePromise = newPromise();
|
||||
verifyHeadersOnly(http2Headers, writePromise, clientChannel.writeAndFlush(request, writePromise));
|
||||
|
@ -42,7 +42,6 @@ import io.netty.handler.codec.http.HttpMethod;
|
||||
import io.netty.handler.codec.http.HttpObject;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import io.netty.handler.codec.http2.HttpConversionUtil.ExtensionHeaderNames;
|
||||
import io.netty.util.AsciiString;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import io.netty.util.concurrent.Future;
|
||||
@ -256,15 +255,12 @@ public class InboundHttp2ToHttpAdapterTest {
|
||||
httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3);
|
||||
httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length());
|
||||
httpHeaders.setShort(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), (short) 16);
|
||||
httpHeaders.set(ExtensionHeaderNames.SCHEME.text(), "http");
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers()
|
||||
.method(new AsciiString("GET"))
|
||||
.scheme(new AsciiString("http"))
|
||||
.path(new AsciiString("/some/path/resource2"));
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path(
|
||||
new AsciiString("/some/path/resource2"));
|
||||
runInChannel(clientChannel, () -> {
|
||||
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient());
|
||||
clientHandler.encoder().writeData(ctxClient(), 3, content.retainedDuplicate(), 0, true,
|
||||
newPromiseClient());
|
||||
newPromiseClient());
|
||||
clientChannel.flush();
|
||||
});
|
||||
awaitRequests();
|
||||
@ -289,11 +285,8 @@ public class InboundHttp2ToHttpAdapterTest {
|
||||
httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3);
|
||||
httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length());
|
||||
httpHeaders.setShort(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), (short) 16);
|
||||
httpHeaders.set(ExtensionHeaderNames.SCHEME.text(), "http");
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers()
|
||||
.method(new AsciiString("GET"))
|
||||
.scheme(new AsciiString("http"))
|
||||
.path(new AsciiString("/some/path/resource2"));
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path(
|
||||
new AsciiString("/some/path/resource2"));
|
||||
final int midPoint = text.length() / 2;
|
||||
runInChannel(clientChannel, () -> {
|
||||
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient());
|
||||
@ -326,11 +319,8 @@ public class InboundHttp2ToHttpAdapterTest {
|
||||
httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3);
|
||||
httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length());
|
||||
httpHeaders.setShort(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), (short) 16);
|
||||
httpHeaders.set(ExtensionHeaderNames.SCHEME.text(), "http");
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers()
|
||||
.method(new AsciiString("GET"))
|
||||
.scheme("http")
|
||||
.path(new AsciiString("/some/path/resource2"));
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path(
|
||||
new AsciiString("/some/path/resource2"));
|
||||
runInChannel(clientChannel, () -> {
|
||||
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient());
|
||||
clientHandler.encoder().writeData(ctxClient(), 3, content.retain(), 0, false, newPromiseClient());
|
||||
@ -360,15 +350,12 @@ public class InboundHttp2ToHttpAdapterTest {
|
||||
httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3);
|
||||
httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length());
|
||||
httpHeaders.setShort(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), (short) 16);
|
||||
httpHeaders.set(ExtensionHeaderNames.SCHEME.text(), "http");
|
||||
HttpHeaders trailingHeaders = request.trailingHeaders();
|
||||
trailingHeaders.set(of("Foo"), of("goo"));
|
||||
trailingHeaders.set(of("fOo2"), of("goo2"));
|
||||
trailingHeaders.add(of("foO2"), of("goo3"));
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers()
|
||||
.method(new AsciiString("GET"))
|
||||
.scheme(new AsciiString("http"))
|
||||
.path(new AsciiString("/some/path/resource2"));
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("GET")).path(
|
||||
new AsciiString("/some/path/resource2"));
|
||||
final Http2Headers http2Headers2 = new DefaultHttp2Headers()
|
||||
.set(new AsciiString("foo"), new AsciiString("goo"))
|
||||
.set(new AsciiString("foo2"), new AsciiString("goo2"))
|
||||
@ -406,31 +393,24 @@ public class InboundHttp2ToHttpAdapterTest {
|
||||
httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3);
|
||||
httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, text.length());
|
||||
httpHeaders.setShort(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), (short) 16);
|
||||
httpHeaders.set(ExtensionHeaderNames.SCHEME.text(), "http");
|
||||
HttpHeaders httpHeaders2 = request2.headers();
|
||||
httpHeaders2.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 5);
|
||||
httpHeaders2.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_DEPENDENCY_ID.text(), 3);
|
||||
httpHeaders2.setShort(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), (short) 123);
|
||||
httpHeaders2.setInt(HttpHeaderNames.CONTENT_LENGTH, text2.length());
|
||||
|
||||
httpHeaders2.set(ExtensionHeaderNames.SCHEME.text(), "http");
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers()
|
||||
.method(new AsciiString("PUT"))
|
||||
.scheme(new AsciiString("http"))
|
||||
.path(new AsciiString("/some/path/resource"));
|
||||
final Http2Headers http2Headers2 = new DefaultHttp2Headers()
|
||||
.method(new AsciiString("PUT"))
|
||||
.scheme(new AsciiString("http"))
|
||||
.path(new AsciiString("/some/path/resource2"));
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("PUT")).path(
|
||||
new AsciiString("/some/path/resource"));
|
||||
final Http2Headers http2Headers2 = new DefaultHttp2Headers().method(new AsciiString("PUT")).path(
|
||||
new AsciiString("/some/path/resource2"));
|
||||
runInChannel(clientChannel, () -> {
|
||||
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient());
|
||||
clientHandler.encoder().writeHeaders(ctxClient(), 5, http2Headers2, 3, (short) 123, true, 0,
|
||||
false, newPromiseClient());
|
||||
clientChannel.flush(); // Headers are queued in the flow controller and so flush them.
|
||||
clientHandler.encoder().writeData(ctxClient(), 3, content.retainedDuplicate(), 0, true,
|
||||
newPromiseClient());
|
||||
newPromiseClient());
|
||||
clientHandler.encoder().writeData(ctxClient(), 5, content2.retainedDuplicate(), 0, true,
|
||||
newPromiseClient());
|
||||
newPromiseClient());
|
||||
clientChannel.flush();
|
||||
});
|
||||
awaitRequests();
|
||||
@ -474,10 +454,7 @@ public class InboundHttp2ToHttpAdapterTest {
|
||||
httpHeaders.setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_ID.text(), 3);
|
||||
httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0);
|
||||
httpHeaders.setShort(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), (short) 16);
|
||||
httpHeaders.set(ExtensionHeaderNames.SCHEME.text(), "http");
|
||||
final Http2Headers http2Headers3 = new DefaultHttp2Headers()
|
||||
.method(new AsciiString("GET"))
|
||||
.scheme("http")
|
||||
final Http2Headers http2Headers3 = new DefaultHttp2Headers().method(new AsciiString("GET"))
|
||||
.path(new AsciiString("/push/test"));
|
||||
runInChannel(clientChannel, () -> {
|
||||
clientHandler.encoder().writeHeaders(ctxClient(), 3, http2Headers3, 0, true, newPromiseClient());
|
||||
@ -529,11 +506,9 @@ public class InboundHttp2ToHttpAdapterTest {
|
||||
httpHeaders.set(HttpHeaderNames.EXPECT, HttpHeaderValues.CONTINUE);
|
||||
httpHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, 0);
|
||||
httpHeaders.setShort(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), (short) 16);
|
||||
httpHeaders.set(ExtensionHeaderNames.SCHEME.text(), "http");
|
||||
|
||||
final Http2Headers http2Headers = new DefaultHttp2Headers().method(new AsciiString("PUT"))
|
||||
.path(new AsciiString("/info/test"))
|
||||
.scheme(new AsciiString("http"))
|
||||
.set(new AsciiString(HttpHeaderNames.EXPECT.toString()),
|
||||
new AsciiString(HttpHeaderValues.CONTINUE.toString()));
|
||||
final FullHttpMessage response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
|
||||
|
@ -94,6 +94,15 @@
|
||||
<excludeSpec>5.1 - half closed (remote): Sends a HEADERS frame</excludeSpec>
|
||||
<excludeSpec>5.1 - closed: Sends a HEADERS frame</excludeSpec>
|
||||
<excludeSpec>5.1.1 - Sends stream identifier that is numerically smaller than previous</excludeSpec>
|
||||
<excludeSpec>8.1.2.2 - Sends a HEADERS frame that contains the connection-specific header field</excludeSpec>
|
||||
<excludeSpec>8.1.2.2 - Sends a HEADERS frame that contains the TE header field with any value other than "trailers"</excludeSpec>
|
||||
<excludeSpec>8.1.2.3 - Sends a HEADERS frame with empty ":path" pseudo-header field</excludeSpec>
|
||||
<excludeSpec>8.1.2.3 - Sends a HEADERS frame that omits ":method" pseudo-header field</excludeSpec>
|
||||
<excludeSpec>8.1.2.3 - Sends a HEADERS frame that omits ":scheme" pseudo-header field</excludeSpec>
|
||||
<excludeSpec>8.1.2.3 - Sends a HEADERS frame that omits ":path" pseudo-header field</excludeSpec>
|
||||
<excludeSpec>8.1.2.3 - Sends a HEADERS frame with duplicated ":method" pseudo-header field</excludeSpec>
|
||||
<excludeSpec>8.1.2.3 - Sends a HEADERS frame with duplicated ":method" pseudo-header field</excludeSpec>
|
||||
<excludeSpec>8.1.2.3 - Sends a HEADERS frame with duplicated ":scheme" pseudo-header field</excludeSpec>
|
||||
<excludeSpec>8.1.2.6 - Sends a HEADERS frame with the "content-length" header field which does not equal the DATA frame payload length</excludeSpec>
|
||||
<excludeSpec>8.1.2.6 - Sends a HEADERS frame with the "content-length" header field which does not equal the sum of the multiple DATA frames payload length</excludeSpec>
|
||||
</excludeSpecs>
|
||||
|
Loading…
Reference in New Issue
Block a user