HTTP/2 to HTTP priority translation

Motivation:

The priority information reported by the HTTP/2 to HTTP tranlsation layer is not correct in all situations.
The HTTP translation layer is not using the Http2Connection.Listener interface to track tree restructures.
This incorrect information is being sent up to clients and is misleading.

Modifications:
-Restructure InboundHttp2ToHttpAdapter to allow a default data/header mode
-Extend this interface to provide an optional priority translation layer

Result:
-Priority information being correctly reported in HTTP/2 to HTTP translation layer
-Cleaner code with seperation of concerns (optional priority conversion).
This commit is contained in:
Scott Mitchell 2014-08-25 23:57:25 -04:00 committed by nmittler
parent 6409a5a1d5
commit 63d5925d6e
18 changed files with 850 additions and 298 deletions

View File

@ -108,7 +108,7 @@ public abstract class DefaultHttpMessage extends DefaultHttpObject implements Ht
} }
void appendHeaders(StringBuilder buf, HttpHeaders headers) { void appendHeaders(StringBuilder buf, HttpHeaders headers) {
for (Map.Entry<String, String> e: headers()) { for (Map.Entry<String, String> e: headers) {
buf.append(e.getKey()); buf.append(e.getKey());
buf.append(": "); buf.append(": ");
buf.append(e.getValue()); buf.append(e.getValue());

View File

@ -894,26 +894,20 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
stream = createRemoteStream(streamId, endStream); stream = createRemoteStream(streamId, endStream);
} else { } else {
if (stream.state() == RESERVED_REMOTE) { if (stream.state() == RESERVED_REMOTE) {
// Received headers for a reserved push stream ... open it for push to the // Received headers for a reserved push stream ... open it for push to the local endpoint.
// local endpoint.
stream.verifyState(PROTOCOL_ERROR, RESERVED_REMOTE); stream.verifyState(PROTOCOL_ERROR, RESERVED_REMOTE);
stream.openForPush(); stream.openForPush();
} else { } else {
// Receiving headers on an existing stream. Make sure the stream is in an // Receiving headers on an existing stream. Make sure the stream is in an allowed state.
// allowed
// state.
stream.verifyState(PROTOCOL_ERROR, OPEN, HALF_CLOSED_LOCAL); stream.verifyState(PROTOCOL_ERROR, OPEN, HALF_CLOSED_LOCAL);
// Update the outbound priority if outbound traffic is allowed.
if (stream.state() == OPEN) {
stream.setPriority(streamDependency, weight, exclusive);
}
} }
} }
AbstractHttp2ConnectionHandler.this.onHeadersRead(ctx, streamId, headers, streamDependency, AbstractHttp2ConnectionHandler.this.onHeadersRead(ctx, streamId, headers, streamDependency,
weight, exclusive, padding, endStream); weight, exclusive, padding, endStream);
stream.setPriority(streamDependency, weight, exclusive);
// If the headers completes this stream, close it. // If the headers completes this stream, close it.
if (endStream) { if (endStream) {
closeRemoteSide(stream, ctx.newSucceededFuture()); closeRemoteSide(stream, ctx.newSucceededFuture());
@ -933,10 +927,10 @@ public abstract class AbstractHttp2ConnectionHandler extends ByteToMessageDecode
return; return;
} }
stream.setPriority(streamDependency, weight, exclusive);
AbstractHttp2ConnectionHandler.this.onPriorityRead(ctx, streamId, streamDependency, AbstractHttp2ConnectionHandler.this.onPriorityRead(ctx, streamId, streamDependency,
weight, exclusive); weight, exclusive);
stream.setPriority(streamDependency, weight, exclusive);
} }
@Override @Override

View File

@ -32,7 +32,6 @@ import static io.netty.handler.codec.http2.Http2Stream.State.RESERVED_REMOTE;
import io.netty.handler.codec.http2.Http2StreamRemovalPolicy.Action; import io.netty.handler.codec.http2.Http2StreamRemovalPolicy.Action;
import io.netty.util.collection.IntObjectHashMap; import io.netty.util.collection.IntObjectHashMap;
import io.netty.util.collection.IntObjectMap; import io.netty.util.collection.IntObjectMap;
import io.netty.util.collection.PrimitiveCollections;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -470,10 +469,6 @@ public class DefaultHttp2Connection implements Http2Connection {
} }
final IntObjectMap<DefaultStream> removeAllChildren() { final IntObjectMap<DefaultStream> removeAllChildren() {
if (children.isEmpty()) {
return PrimitiveCollections.emptyIntObjectMap();
}
totalChildWeights = 0; totalChildWeights = 0;
IntObjectMap<DefaultStream> prevChildren = children; IntObjectMap<DefaultStream> prevChildren = children;
children = newChildMap(); children = newChildMap();
@ -494,10 +489,12 @@ public class DefaultHttp2Connection implements Http2Connection {
// If it was requested that this child be the exclusive dependency of this node, // If it was requested that this child be the exclusive dependency of this node,
// move any previous children to the child node, becoming grand children // move any previous children to the child node, becoming grand children
// of this node. // of this node.
if (!children.isEmpty()) {
for (DefaultStream grandchild : removeAllChildren().values(DefaultStream.class)) { for (DefaultStream grandchild : removeAllChildren().values(DefaultStream.class)) {
child.takeChild(grandchild, false, events); child.takeChild(grandchild, false, events);
} }
} }
}
if (children.put(child.id(), child) == null) { if (children.put(child.id(), child) == null) {
totalChildWeights += child.weight(); totalChildWeights += child.weight();

View File

@ -178,6 +178,15 @@ public final class DefaultHttp2Headers extends Http2Headers {
} }
} }
/**
* Adds the given header to the collection.
*
* @throws IllegalArgumentException if the name or value of this header is invalid for any reason.
*/
public Builder add(final CharSequence name, final Object value) {
return add(name.toString(), value);
}
/** /**
* Adds the given header to the collection. * Adds the given header to the collection.
* *
@ -198,6 +207,13 @@ public final class DefaultHttp2Headers extends Http2Headers {
return this; return this;
} }
/**
* Removes the header with the given name from this collection.
*/
public Builder remove(final CharSequence name) {
return remove(name.toString());
}
/** /**
* Removes the header with the given name from this collection. * Removes the header with the given name from this collection.
*/ */
@ -217,6 +233,15 @@ public final class DefaultHttp2Headers extends Http2Headers {
return this; return this;
} }
/**
* Sets the given header in the collection, replacing any previous values.
*
* @throws IllegalArgumentException if the name or value of this header is invalid for any reason.
*/
public Builder set(final CharSequence name, final Object value) {
return set(name.toString(), value);
}
/** /**
* Sets the given header in the collection, replacing any previous values. * Sets the given header in the collection, replacing any previous values.
* *

View File

@ -68,17 +68,17 @@ public class DelegatingHttp2HttpConnectionHandler extends DelegatingHttp2Connect
} }
// Consume the Authority extension header if present // Consume the Authority extension header if present
value = httpHeaders.get(Http2ToHttpHeaders.Names.AUTHORITY); value = httpHeaders.get(HttpUtil.ExtensionHeaders.Names.AUTHORITY);
if (value != null) { if (value != null) {
http2Headers.authority(value); http2Headers.authority(value);
httpHeaders.remove(Http2ToHttpHeaders.Names.AUTHORITY); httpHeaders.remove(HttpUtil.ExtensionHeaders.Names.AUTHORITY);
} }
// Consume the Scheme extension header if present // Consume the Scheme extension header if present
value = httpHeaders.get(Http2ToHttpHeaders.Names.SCHEME); value = httpHeaders.get(HttpUtil.ExtensionHeaders.Names.SCHEME);
if (value != null) { if (value != null) {
http2Headers.scheme(value); http2Headers.scheme(value);
httpHeaders.remove(Http2ToHttpHeaders.Names.SCHEME); httpHeaders.remove(HttpUtil.ExtensionHeaders.Names.SCHEME);
} }
} }
@ -114,7 +114,7 @@ public class DelegatingHttp2HttpConnectionHandler extends DelegatingHttp2Connect
*/ */
private int getStreamId(HttpHeaders httpHeaders) throws Http2Exception { private int getStreamId(HttpHeaders httpHeaders) throws Http2Exception {
int streamId = 0; int streamId = 0;
String value = httpHeaders.get(Http2ToHttpHeaders.Names.STREAM_ID); String value = httpHeaders.get(HttpUtil.ExtensionHeaders.Names.STREAM_ID);
if (value == null) { if (value == null) {
streamId = nextStreamId(); streamId = nextStreamId();
} else { } else {
@ -124,7 +124,7 @@ public class DelegatingHttp2HttpConnectionHandler extends DelegatingHttp2Connect
throw Http2Exception.format(Http2Error.INTERNAL_ERROR, throw Http2Exception.format(Http2Error.INTERNAL_ERROR,
"Invalid user-specified stream id value '%s'", value); "Invalid user-specified stream id value '%s'", value);
} }
httpHeaders.remove(Http2ToHttpHeaders.Names.STREAM_ID); httpHeaders.remove(HttpUtil.ExtensionHeaders.Names.STREAM_ID);
} }
return streamId; return streamId;
@ -141,7 +141,6 @@ public class DelegatingHttp2HttpConnectionHandler extends DelegatingHttp2Connect
boolean hasData = httpMsg.content().isReadable(); boolean hasData = httpMsg.content().isReadable();
// Convert and write the headers. // Convert and write the headers.
String value = null;
HttpHeaders httpHeaders = httpMsg.headers(); HttpHeaders httpHeaders = httpMsg.headers();
DefaultHttp2Headers.Builder http2Headers = DefaultHttp2Headers.newBuilder(); DefaultHttp2Headers.Builder http2Headers = DefaultHttp2Headers.newBuilder();
if (msg instanceof HttpRequest) { if (msg instanceof HttpRequest) {

View File

@ -0,0 +1,120 @@
/*
* Copyright 2014 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.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
/**
* This class brings {@link Http2Connection.Listener} and {@link Http2FrameObserver} together to provide
* NOOP implementation so inheriting classes can selectively choose which methods to override.
*/
public class Http2EventAdapter implements Http2Connection.Listener, Http2FrameObserver {
@Override
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream)
throws Http2Exception {
}
@Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding,
boolean endStream) throws Http2Exception {
}
@Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency,
short weight, boolean exclusive, int padding, boolean endStream) throws Http2Exception {
}
@Override
public void onPriorityRead(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight,
boolean exclusive) throws Http2Exception {
}
@Override
public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) throws Http2Exception {
}
@Override
public void onSettingsAckRead(ChannelHandlerContext ctx) throws Http2Exception {
}
@Override
public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) throws Http2Exception {
}
@Override
public void onPingRead(ChannelHandlerContext ctx, ByteBuf data) throws Http2Exception {
}
@Override
public void onPingAckRead(ChannelHandlerContext ctx, ByteBuf data) throws Http2Exception {
}
@Override
public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
Http2Headers headers, int padding) throws Http2Exception {
}
@Override
public void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData)
throws Http2Exception {
}
@Override
public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement)
throws Http2Exception {
}
@Override
public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
ByteBuf payload) {
}
@Override
public void streamAdded(Http2Stream stream) {
}
@Override
public void streamActive(Http2Stream stream) {
}
@Override
public void streamHalfClosed(Http2Stream stream) {
}
@Override
public void streamInactive(Http2Stream stream) {
}
@Override
public void streamRemoved(Http2Stream stream) {
}
@Override
public void priorityTreeParentChanged(Http2Stream stream, Http2Stream oldParent) {
}
@Override
public void priorityTreeParentChanging(Http2Stream stream, Http2Stream newParent) {
}
@Override
public void onWeightChanged(Http2Stream stream, short oldWeight) {
}
@Override
public void goingAway() {
}
}

View File

@ -1,87 +0,0 @@
/*
* Copyright 2014 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.AsciiString;
import io.netty.handler.codec.http.HttpResponseStatus;
/**
* Provides the constants for the header names used by
* {@link InboundHttp2ToHttpAdapter} and {@link DelegatingHttp2HttpConnectionHandler}
*/
public final class Http2ToHttpHeaders {
private Http2ToHttpHeaders() { }
public static final class Names {
/**
* {@code "X-HTTP2-Stream-ID"}
*/
public static final AsciiString STREAM_ID = new AsciiString("X-HTTP2-Stream-ID");
/**
* {@code "X-HTTP2-Authority"}
*/
public static final AsciiString AUTHORITY = new AsciiString("X-HTTP2-Authority");
/**
* {@code "X-HTTP2-Scheme"}
*/
public static final AsciiString SCHEME = new AsciiString("X-HTTP2-Scheme");
/**
* {@code "X-HTTP2-Path"}
*/
public static final AsciiString PATH = new AsciiString("X-HTTP2-Path");
/**
* {@code "X-HTTP2-Stream-Promise-ID"}
*/
public static final AsciiString STREAM_PROMISE_ID = new AsciiString("X-HTTP2-Stream-Promise-ID");
/**
* {@code "X-HTTP2-Stream-Dependency-ID"}
*/
public static final AsciiString STREAM_DEPENDENCY_ID = new AsciiString("X-HTTP2-Stream-Dependency-ID");
/**
* {@code "X-HTTP2-Stream-Weight"}
*/
public static final AsciiString STREAM_WEIGHT = new AsciiString("X-HTTP2-Stream-Weight");
/**
* {@code "X-HTTP2-Stream-Exclusive"}
*/
public static final AsciiString STREAM_EXCLUSIVE = new AsciiString("X-HTTP2-Stream-Exclusive");
private Names() {
}
}
/**
* Apply HTTP/2 rules while translating status code to {@link HttpResponseStatus}
*
* @param status The status from an HTTP/2 frame
* @return The HTTP/1.x status
* @throws Http2Exception If there is a problem translating from HTTP/2 to HTTP/1.x
*/
public static HttpResponseStatus parseStatus(String status) throws Http2Exception {
HttpResponseStatus result = null;
try {
result = HttpResponseStatus.parseLine(status);
if (result == HttpResponseStatus.SWITCHING_PROTOCOLS) {
throw Http2Exception.protocolError("Invalid HTTP/2 status code '%d'", result.code());
}
} catch (Http2Exception e) {
throw e;
} catch (Exception e) {
throw Http2Exception.protocolError(
"Unrecognized HTTP status code '%s' encountered in translation to HTTP/1.x", status);
}
return result;
}
}

View File

@ -0,0 +1,130 @@
/*
* Copyright 2014 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.AsciiString;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus;
/**
* Provides utility methods and constants for the HTTP/2 to HTTP conversion
*/
public final class HttpUtil {
/**
* This will be the method used for {@link HttpRequest} objects generated
* out of the HTTP message flow defined in
* <a href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.">HTTP/2 Spec Message Flow</a>
*/
public static final HttpMethod OUT_OF_MESSAGE_SEQUENCE_METHOD = HttpMethod.OPTIONS;
/**
* This will be the path used for {@link HttpRequest} objects generated
* out of the HTTP message flow defined in
* <a href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.">HTTP/2 Spec Message Flow</a>
*/
public static final String OUT_OF_MESSAGE_SEQUENCE_PATH = "";
/**
* This will be the status code used for {@link HttpResponse} objects generated
* out of the HTTP message flow defined in
* <a href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.">HTTP/2 Spec Message Flow</a>
*/
public static final HttpResponseStatus OUT_OF_MESSAGE_SEQUENCE_RETURN_CODE = HttpResponseStatus.OK;
private HttpUtil() { }
/**
* Provides the HTTP header extensions used to carry HTTP/2 information in HTTP objects
*/
public static final class ExtensionHeaders {
public static final class Names {
private Names() { }
/**
* HTTP extension header which will identify the stream id
* from the HTTP/2 event(s) responsible for generating a {@code HttpObject}
* <p>
* {@code "X-HTTP2-Stream-ID"}
*/
public static final AsciiString STREAM_ID = new AsciiString("X-HTTP2-Stream-ID");
/**
* HTTP extension header which will identify the authority pseudo header
* from the HTTP/2 event(s) responsible for generating a {@code HttpObject}
* <p>
* {@code "X-HTTP2-Authority"}
*/
public static final AsciiString AUTHORITY = new AsciiString("X-HTTP2-Authority");
/**
* HTTP extension header which will identify the scheme pseudo header
* from the HTTP/2 event(s) responsible for generating a {@code HttpObject}
* <p>
* {@code "X-HTTP2-Scheme"}
*/
public static final AsciiString SCHEME = new AsciiString("X-HTTP2-Scheme");
/**
* HTTP extension header which will identify the path pseudo header
* from the HTTP/2 event(s) responsible for generating a {@code HttpObject}
* <p>
* {@code "X-HTTP2-Path"}
*/
public static final AsciiString PATH = new AsciiString("X-HTTP2-Path");
/**
* HTTP extension header which will identify the stream id used to create this stream
* in a HTTP/2 push promise frame
* <p>
* {@code "X-HTTP2-Stream-Promise-ID"}
*/
public static final AsciiString STREAM_PROMISE_ID = new AsciiString("X-HTTP2-Stream-Promise-ID");
/**
* HTTP extension header which will identify the stream id which this stream is dependent on.
* This stream will be a child node of the stream id associated with this header value.
* <p>
* {@code "X-HTTP2-Stream-Dependency-ID"}
*/
public static final AsciiString STREAM_DEPENDENCY_ID = new AsciiString("X-HTTP2-Stream-Dependency-ID");
/**
* HTTP extension header which will identify the weight
* (if non-default and the priority is not on the default stream) of the associated HTTP/2 stream
* responsible responsible for generating a {@code HttpObject}
* <p>
* {@code "X-HTTP2-Stream-Weight"}
*/
public static final AsciiString STREAM_WEIGHT = new AsciiString("X-HTTP2-Stream-Weight");
}
}
/**
* Apply HTTP/2 rules while translating status code to {@link HttpResponseStatus}
*
* @param status The status from an HTTP/2 frame
* @return The HTTP/1.x status
* @throws Http2Exception If there is a problem translating from HTTP/2 to HTTP/1.x
*/
public static HttpResponseStatus parseStatus(String status) throws Http2Exception {
HttpResponseStatus result = null;
try {
result = HttpResponseStatus.parseLine(status);
if (result == HttpResponseStatus.SWITCHING_PROTOCOLS) {
throw Http2Exception.protocolError("Invalid HTTP/2 status code '%d'", result.code());
}
} catch (Http2Exception e) {
throw e;
} catch (Exception e) {
throw Http2Exception.protocolError(
"Unrecognized HTTP status code '%s' encountered in translation to HTTP/1.x", status);
}
return result;
}
}

View File

@ -22,10 +22,8 @@ import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpMessage; import io.netty.handler.codec.http.FullHttpMessage;
import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaderUtil; import io.netty.handler.codec.http.HttpHeaderUtil;
import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.HttpVersion;
@ -40,15 +38,15 @@ import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
/** /**
* Decodes HTTP/2 data and associated streams into {@link HttpMessage}s and {@link HttpContent}s. * This adapter provides just header/data events from the HTTP message flow defined
* here <a href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.">HTTP/2 Spec Message Flow</a>
*/ */
public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements Http2FrameObserver { public class InboundHttp2ToHttpAdapter extends Http2EventAdapter {
private final int maxContentLength; private final int maxContentLength;
private final boolean validateHttpHeaders; protected final Http2Connection connection;
private final Http2Connection connection; protected final boolean validateHttpHeaders;
private final ImmediateSendDetector sendDetector; private final ImmediateSendDetector sendDetector;
private final IntObjectMap<FullHttpMessage> messageMap; protected final IntObjectMap<FullHttpMessage> messageMap;
private static final Set<String> HEADERS_TO_EXCLUDE; private static final Set<String> HEADERS_TO_EXCLUDE;
private static final Map<String, String> HEADER_NAME_TRANSLATIONS_REQUEST; private static final Map<String, String> HEADER_NAME_TRANSLATIONS_REQUEST;
@ -63,12 +61,12 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements
} }
HEADER_NAME_TRANSLATIONS_RESPONSE.put(Http2Headers.PseudoHeaderName.AUTHORITY.value(), HEADER_NAME_TRANSLATIONS_RESPONSE.put(Http2Headers.PseudoHeaderName.AUTHORITY.value(),
Http2ToHttpHeaders.Names.AUTHORITY.toString()); HttpUtil.ExtensionHeaders.Names.AUTHORITY.toString());
HEADER_NAME_TRANSLATIONS_RESPONSE.put(Http2Headers.PseudoHeaderName.SCHEME.value(), HEADER_NAME_TRANSLATIONS_RESPONSE.put(Http2Headers.PseudoHeaderName.SCHEME.value(),
Http2ToHttpHeaders.Names.SCHEME.toString()); HttpUtil.ExtensionHeaders.Names.SCHEME.toString());
HEADER_NAME_TRANSLATIONS_REQUEST.putAll(HEADER_NAME_TRANSLATIONS_RESPONSE); HEADER_NAME_TRANSLATIONS_REQUEST.putAll(HEADER_NAME_TRANSLATIONS_RESPONSE);
HEADER_NAME_TRANSLATIONS_RESPONSE.put(Http2Headers.PseudoHeaderName.PATH.value(), HEADER_NAME_TRANSLATIONS_RESPONSE.put(Http2Headers.PseudoHeaderName.PATH.value(),
Http2ToHttpHeaders.Names.PATH.toString()); HttpUtil.ExtensionHeaders.Names.PATH.toString());
} }
/** /**
@ -80,12 +78,10 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements
* @throws NullPointerException If {@code connection} is null * @throws NullPointerException If {@code connection} is null
* @throws IllegalArgumentException If {@code maxContentLength} is less than or equal to {@code 0} * @throws IllegalArgumentException If {@code maxContentLength} is less than or equal to {@code 0}
*/ */
public static InboundHttp2ToHttpAdapter newInstance(Http2Connection connection, int maxContentLength) public static InboundHttp2ToHttpAdapter newInstance(Http2Connection connection, int maxContentLength) {
throws NullPointerException, IllegalArgumentException { InboundHttp2ToHttpAdapter instance = new InboundHttp2ToHttpAdapter(connection, maxContentLength);
InboundHttp2ToHttpAdapter adapter = new InboundHttp2ToHttpAdapter(connection, maxContentLength, connection.addListener(instance);
DefaultImmediateSendDetector.getInstance()); return instance;
connection.addListener(adapter);
return adapter;
} }
/** /**
@ -94,16 +90,20 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements
* @param connection The object which will provide connection notification events for the current connection * @param connection The object which will provide connection notification events for the current connection
* @param maxContentLength the maximum length of the message content. If the length of the message content exceeds * @param maxContentLength the maximum length of the message content. If the length of the message content exceeds
* this value, a {@link TooLongFrameException} will be raised. * this value, a {@link TooLongFrameException} will be raised.
* @param validateHeaders {@code true} if http headers should be validated * @param validateHttpHeaders
* <ul>
* <li>{@code true} to validate HTTP headers in the http-codec</li>
* <li>{@code false} not to validate HTTP headers in the http-codec</li>
* </ul>
* @throws NullPointerException If {@code connection} is null * @throws NullPointerException If {@code connection} is null
* @throws IllegalArgumentException If {@code maxContentLength} is less than or equal to {@code 0} * @throws IllegalArgumentException If {@code maxContentLength} is less than or equal to {@code 0}
*/ */
public static InboundHttp2ToHttpAdapter newInstance(Http2Connection connection, int maxContentLength, public static InboundHttp2ToHttpAdapter newInstance(Http2Connection connection, int maxContentLength,
boolean validateHttpHeaders) throws NullPointerException, IllegalArgumentException { boolean validateHttpHeaders) {
InboundHttp2ToHttpAdapter adapter = new InboundHttp2ToHttpAdapter(connection, maxContentLength, InboundHttp2ToHttpAdapter instance = new InboundHttp2ToHttpAdapter(connection,
DefaultImmediateSendDetector.getInstance(), validateHttpHeaders); maxContentLength, validateHttpHeaders);
connection.addListener(adapter); connection.addListener(instance);
return adapter; return instance;
} }
/** /**
@ -112,14 +112,11 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements
* @param connection The object which will provide connection notification events for the current connection * @param connection The object which will provide connection notification events for the current connection
* @param maxContentLength the maximum length of the message content. If the length of the message content exceeds * @param maxContentLength the maximum length of the message content. If the length of the message content exceeds
* this value, a {@link TooLongFrameException} will be raised. * this value, a {@link TooLongFrameException} will be raised.
* @param sendDetector Decides when HTTP messages must be sent before the typically HTTP message events are detected
* @throws NullPointerException If {@code connection} is null * @throws NullPointerException If {@code connection} is null
* @throws IllegalArgumentException If {@code maxContentLength} is less than or equal to {@code 0} * @throws IllegalArgumentException If {@code maxContentLength} is less than or equal to {@code 0}
*/ */
protected InboundHttp2ToHttpAdapter(Http2Connection connection, int maxContentLength, protected InboundHttp2ToHttpAdapter(Http2Connection connection, int maxContentLength) {
ImmediateSendDetector sendDetector) throws NullPointerException, this(connection, maxContentLength, true);
IllegalArgumentException {
this(connection, maxContentLength, sendDetector, true);
} }
/** /**
@ -128,33 +125,40 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements
* @param connection The object which will provide connection notification events for the current connection * @param connection The object which will provide connection notification events for the current connection
* @param maxContentLength the maximum length of the message content. If the length of the message content exceeds * @param maxContentLength the maximum length of the message content. If the length of the message content exceeds
* this value, a {@link TooLongFrameException} will be raised. * this value, a {@link TooLongFrameException} will be raised.
* @param sendDetector Decides when HTTP messages must be sent before the typically HTTP message events are detected * @param validateHttpHeaders
* @param validateHeaders {@code true} if http headers should be validated * <ul>
* <li>{@code true} to validate HTTP headers in the http-codec</li>
* <li>{@code false} not to validate HTTP headers in the http-codec</li>
* </ul>
* @throws NullPointerException If {@code connection} is null * @throws NullPointerException If {@code connection} is null
* @throws IllegalArgumentException If {@code maxContentLength} is less than or equal to {@code 0} * @throws IllegalArgumentException If {@code maxContentLength} is less than or equal to {@code 0}
*/ */
protected InboundHttp2ToHttpAdapter(Http2Connection connection, int maxContentLength, protected InboundHttp2ToHttpAdapter(Http2Connection connection, int maxContentLength,
ImmediateSendDetector sendDetector, boolean validateHttpHeaders) boolean validateHttpHeaders) {
throws NullPointerException, IllegalArgumentException {
if (connection == null) { if (connection == null) {
throw new NullPointerException("connection"); throw new NullPointerException("connection");
} }
if (maxContentLength <= 0) { if (maxContentLength <= 0) {
throw new IllegalArgumentException("maxContentLength must be a positive integer: " + maxContentLength); throw new IllegalArgumentException("maxContentLength must be a positive integer: " + maxContentLength);
} }
if (sendDetector == null) {
throw new NullPointerException("sendDetector");
}
this.maxContentLength = maxContentLength; this.maxContentLength = maxContentLength;
this.validateHttpHeaders = validateHttpHeaders; this.validateHttpHeaders = validateHttpHeaders;
this.connection = connection; this.connection = connection;
this.sendDetector = sendDetector; sendDetector = DefaultImmediateSendDetector.getInstance();
this.messageMap = new IntObjectHashMap<FullHttpMessage>(); messageMap = new IntObjectHashMap<FullHttpMessage>();
}
/**
* The streamId is out of scope for the HTTP message flow and will no longer be tracked
* @param streamId The stream id to remove associated state with
*/
protected void removeMessage(int streamId) {
messageMap.remove(streamId);
} }
@Override @Override
public void streamRemoved(Http2Stream stream) { public void streamRemoved(Http2Stream stream) {
messageMap.remove(stream.id()); removeMessage(stream.id());
} }
/** /**
@ -164,40 +168,65 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements
* @param msg The message to send * @param msg The message to send
* @param streamId the streamId of the message which is being fired * @param streamId the streamId of the message which is being fired
*/ */
private void fireChannelRead(ChannelHandlerContext ctx, FullHttpMessage msg, int streamId) { protected void fireChannelRead(ChannelHandlerContext ctx, FullHttpMessage msg, int streamId) {
messageMap.remove(streamId); removeMessage(streamId);
HttpHeaderUtil.setContentLength(msg, msg.content().readableBytes()); HttpHeaderUtil.setContentLength(msg, msg.content().readableBytes());
ctx.fireChannelRead(msg); ctx.fireChannelRead(msg);
} }
/**
* Create a new {@link FullHttpMessage} based upon the current connection parameters
*
* @param streamId The stream id to create a message for
* @param headers The headers associated with {@code streamId}
* @param validateHttpHeaders
* <ul>
* <li>{@code true} to validate HTTP headers in the http-codec</li>
* <li>{@code false} not to validate HTTP headers in the http-codec</li>
* </ul>
* @return
* @throws Http2Exception
*/
protected FullHttpMessage newMessage(int streamId, Http2Headers headers, boolean validateHttpHeaders)
throws Http2Exception {
return connection.isServer() ? newHttpRequest(streamId, headers, validateHttpHeaders) :
newHttpResponse(streamId, headers, validateHttpHeaders);
}
/** /**
* Provides translation between HTTP/2 and HTTP header objects while ensuring the stream * Provides translation between HTTP/2 and HTTP header objects while ensuring the stream
* is in a valid state for additional headers. * is in a valid state for additional headers.
* *
* @param ctx The context for which this message has been received. Used to send informational header if detected. * @param ctx The context for which this message has been received.
* Used to send informational header if detected.
* @param streamId The stream id the {@code headers} apply to * @param streamId The stream id the {@code headers} apply to
* @param headers The headers to process * @param headers The headers to process
* @param endOfStream {@code true} if the {@code streamId} has received the end of stream flag * @param endOfStream {@code true} if the {@code streamId} has received the end of stream flag
* @param allowAppend {@code true} if headers will be appended if the stream already exists. if {@code false} and * @param allowAppend
* the stream already exists this method returns {@code null}. * <ul>
* @param appendToTrailer {@code true} if a message {@code streamId} already exists then the headers * <li>{@code true} if headers will be appended if the stream already exists.</li>
* should be added to the trailing headers. * <li>if {@code false} and the stream already exists this method returns {@code null}.</li>
* {@code false} then appends will be done to the initial headers. * </ul>
* @param appendToTrailer
* <ul>
* <li>{@code true} if a message {@code streamId} already exists then the headers
* should be added to the trailing headers.</li>
* <li>{@code false} then appends will be done to the initial headers.</li>
* </ul>
* @return The object used to track the stream corresponding to {@code streamId}. {@code null} if * @return The object used to track the stream corresponding to {@code streamId}. {@code null} if
* {@code allowAppend} is {@code false} and the stream already exists. * {@code allowAppend} is {@code false} and the stream already exists.
* @throws Http2Exception If the stream id is not in the correct state to process the headers request * @throws Http2Exception If the stream id is not in the correct state to process the headers request
*/ */
private FullHttpMessage processHeadersBegin(ChannelHandlerContext ctx, int streamId, Http2Headers headers, protected FullHttpMessage processHeadersBegin(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
boolean endOfStream, boolean allowAppend, boolean appendToTrailer) throws Http2Exception { boolean endOfStream, boolean allowAppend, boolean appendToTrailer) throws Http2Exception {
FullHttpMessage msg = messageMap.get(streamId); FullHttpMessage msg = messageMap.get(streamId);
if (msg == null) { if (msg == null) {
msg = connection.isServer() ? newHttpRequest(streamId, headers, validateHttpHeaders) : msg = newMessage(streamId, headers, validateHttpHeaders);
newHttpResponse(streamId, headers, validateHttpHeaders);
} else if (allowAppend) { } else if (allowAppend) {
try { try {
addHttp2ToHttpHeaders(streamId, headers, msg, appendToTrailer); addHttp2ToHttpHeaders(streamId, headers, msg, appendToTrailer);
} catch (Http2Exception e) { } catch (Http2Exception e) {
messageMap.remove(streamId); removeMessage(streamId);
throw e; throw e;
} }
} else { } else {
@ -221,7 +250,8 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements
* @param msg The object which represents all headers/data for corresponding to {@code streamId} * @param msg The object which represents all headers/data for corresponding to {@code streamId}
* @param endOfStream {@code true} if this is the last event for the stream * @param endOfStream {@code true} if this is the last event for the stream
*/ */
private void processHeadersEnd(ChannelHandlerContext ctx, int streamId, FullHttpMessage msg, boolean endOfStream) { private void processHeadersEnd(ChannelHandlerContext ctx, int streamId,
FullHttpMessage msg, boolean endOfStream) {
if (endOfStream) { if (endOfStream) {
fireChannelRead(ctx, msg, streamId); fireChannelRead(ctx, msg, streamId);
} else { } else {
@ -267,24 +297,10 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements
short weight, boolean exclusive, int padding, boolean endOfStream) throws Http2Exception { short weight, boolean exclusive, int padding, boolean endOfStream) throws Http2Exception {
FullHttpMessage msg = processHeadersBegin(ctx, streamId, headers, endOfStream, true, true); FullHttpMessage msg = processHeadersBegin(ctx, streamId, headers, endOfStream, true, true);
if (msg != null) { if (msg != null) {
// TODO: fix me...consume priority tree listener interface
setDependencyHeaders(msg.headers(), streamDependency, weight, exclusive);
processHeadersEnd(ctx, streamId, msg, endOfStream); processHeadersEnd(ctx, streamId, msg, endOfStream);
} }
} }
@Override
public void onPriorityRead(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight,
boolean exclusive) throws Http2Exception {
FullHttpMessage msg = messageMap.get(streamId);
if (msg == null) {
throw Http2Exception.protocolError("Priority Frame recieved for unknown stream id %d", streamId);
}
// TODO: fix me...consume priority tree listener interface
setDependencyHeaders(msg.headers(), streamDependency, weight, exclusive);
}
@Override @Override
public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) throws Http2Exception { public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) throws Http2Exception {
FullHttpMessage msg = messageMap.get(streamId); FullHttpMessage msg = messageMap.get(streamId);
@ -294,8 +310,8 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements
} }
@Override @Override
public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId, Http2Headers headers, public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
int padding) throws Http2Exception { Http2Headers headers, int padding) throws Http2Exception {
// A push promise should not be allowed to add headers to an existing stream // A push promise should not be allowed to add headers to an existing stream
FullHttpMessage msg = processHeadersBegin(ctx, promisedStreamId, headers, false, false, false); FullHttpMessage msg = processHeadersBegin(ctx, promisedStreamId, headers, false, false, false);
if (msg == null) { if (msg == null) {
@ -303,49 +319,11 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements
promisedStreamId); promisedStreamId);
} }
msg.headers().set(Http2ToHttpHeaders.Names.STREAM_PROMISE_ID, streamId); msg.headers().set(HttpUtil.ExtensionHeaders.Names.STREAM_PROMISE_ID, streamId);
processHeadersEnd(ctx, promisedStreamId, msg, false); processHeadersEnd(ctx, promisedStreamId, msg, false);
} }
@Override
public void onSettingsAckRead(ChannelHandlerContext ctx) throws Http2Exception {
// NOOP
}
@Override
public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) throws Http2Exception {
// NOOP
}
@Override
public void onPingRead(ChannelHandlerContext ctx, ByteBuf data) throws Http2Exception {
// NOOP
}
@Override
public void onPingAckRead(ChannelHandlerContext ctx, ByteBuf data) throws Http2Exception {
// NOOP
}
@Override
public void onGoAwayRead(ChannelHandlerContext ctx, int lastStreamId, long errorCode, ByteBuf debugData)
throws Http2Exception {
// NOOP
}
@Override
public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement)
throws Http2Exception {
// NOOP
}
@Override
public void onUnknownFrame(ChannelHandlerContext ctx, byte frameType, int streamId, Http2Flags flags,
ByteBuf payload) {
// NOOP
}
/** /**
* Allows messages to be sent up the pipeline before the next phase in the * Allows messages to be sent up the pipeline before the next phase in the
* HTTP message flow is detected. * HTTP message flow is detected.
@ -410,39 +388,22 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements
} }
} }
/**
* Set/clear all HTTP/1.x headers related to stream dependencies
*
* @param headers The headers to set
* @param streamDependency The stream id for which the {@code objAccumulator} is dependent on
* @param weight The dependency weight
* @param exclusive The exclusive HTTP/2 flag
*/
private static void setDependencyHeaders(HttpHeaders headers, int streamDependency,
short weight, boolean exclusive) {
if (streamDependency != 0) {
headers.set(Http2ToHttpHeaders.Names.STREAM_DEPENDENCY_ID, streamDependency);
headers.set(Http2ToHttpHeaders.Names.STREAM_EXCLUSIVE, exclusive);
headers.set(Http2ToHttpHeaders.Names.STREAM_WEIGHT, weight);
} else {
headers.set(Http2ToHttpHeaders.Names.STREAM_DEPENDENCY_ID);
headers.set(Http2ToHttpHeaders.Names.STREAM_EXCLUSIVE);
headers.set(Http2ToHttpHeaders.Names.STREAM_WEIGHT);
}
}
/** /**
* Create a new object to contain the response data * Create a new object to contain the response data
* *
* @param streamId The stream associated with the response * @param streamId The stream associated with the response
* @param http2Headers The initial set of HTTP/2 headers to create the response with * @param http2Headers The initial set of HTTP/2 headers to create the response with
* @param validateHttpHeaders Used by http-codec to validate headers * @param validateHttpHeaders
* <ul>
* <li>{@code true} to validate HTTP headers in the http-codec</li>
* <li>{@code false} not to validate HTTP headers in the http-codec</li>
* </ul>
* @return A new response object which represents headers/data * @return A new response object which represents headers/data
* @throws Http2Exception see {@link #addHttp2ToHttpHeaders(int, Http2Headers, FullHttpMessage, Map)} * @throws Http2Exception see {@link #addHttp2ToHttpHeaders(int, Http2Headers, FullHttpMessage, Map)}
*/ */
private static FullHttpMessage newHttpResponse(int streamId, Http2Headers http2Headers, boolean validateHttpHeaders) private static FullHttpMessage newHttpResponse(int streamId, Http2Headers http2Headers, boolean validateHttpHeaders)
throws Http2Exception { throws Http2Exception {
HttpResponseStatus status = Http2ToHttpHeaders.parseStatus(http2Headers.status()); HttpResponseStatus status = HttpUtil.parseStatus(http2Headers.status());
// HTTP/2 does not define a way to carry the version or reason phrase that is included in an HTTP/1.1 // HTTP/2 does not define a way to carry the version or reason phrase that is included in an HTTP/1.1
// status line. // status line.
FullHttpMessage msg = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, validateHttpHeaders); FullHttpMessage msg = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, validateHttpHeaders);
@ -455,7 +416,11 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements
* *
* @param streamId The stream associated with the request * @param streamId The stream associated with the request
* @param http2Headers The initial set of HTTP/2 headers to create the request with * @param http2Headers The initial set of HTTP/2 headers to create the request with
* @param validateHttpHeaders Used by http-codec to validate headers * @param validateHttpHeaders
* <ul>
* <li>{@code true} to validate HTTP headers in the http-codec</li>
* <li>{@code false} not to validate HTTP headers in the http-codec</li>
* </ul>
* @return A new request object which represents headers/data * @return A new request object which represents headers/data
* @throws Http2Exception see {@link #addHttp2ToHttpHeaders(int, Http2Headers, FullHttpMessage, Map)} * @throws Http2Exception see {@link #addHttp2ToHttpHeaders(int, Http2Headers, FullHttpMessage, Map)}
*/ */
@ -499,7 +464,7 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements
FullHttpMessage destinationMessage, boolean addToTrailer, Map<String, String> translations) FullHttpMessage destinationMessage, boolean addToTrailer, Map<String, String> translations)
throws Http2Exception { throws Http2Exception {
HttpHeaders headers = addToTrailer ? destinationMessage.trailingHeaders() : destinationMessage.headers(); HttpHeaders headers = addToTrailer ? destinationMessage.trailingHeaders() : destinationMessage.headers();
HttpForEachVisitor visitor = new HttpForEachVisitor(headers, translations); HttpAdapterVisitor visitor = new HttpAdapterVisitor(headers, translations);
sourceHeaders.forEach(visitor); sourceHeaders.forEach(visitor);
if (visitor.exception() != null) { if (visitor.exception() != null) {
throw visitor.exception(); throw visitor.exception();
@ -508,7 +473,7 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements
headers.remove(HttpHeaders.Names.TRANSFER_ENCODING); headers.remove(HttpHeaders.Names.TRANSFER_ENCODING);
headers.remove(HttpHeaders.Names.TRAILER); headers.remove(HttpHeaders.Names.TRAILER);
if (!addToTrailer) { if (!addToTrailer) {
headers.set(Http2ToHttpHeaders.Names.STREAM_ID, streamId); headers.set(HttpUtil.ExtensionHeaders.Names.STREAM_ID, streamId);
HttpHeaderUtil.setKeepAlive(destinationMessage, true); HttpHeaderUtil.setKeepAlive(destinationMessage, true);
} }
} }
@ -516,7 +481,7 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements
/** /**
* A visitor which translates HTTP/2 headers to HTTP/1 headers * A visitor which translates HTTP/2 headers to HTTP/1 headers
*/ */
private static final class HttpForEachVisitor implements HeaderVisitor { private static final class HttpAdapterVisitor implements HeaderVisitor {
private Map<String, String> translations; private Map<String, String> translations;
private HttpHeaders headers; private HttpHeaders headers;
private Http2Exception e; private Http2Exception e;
@ -527,7 +492,7 @@ public class InboundHttp2ToHttpAdapter extends Http2ConnectionAdapter implements
* @param headers The HTTP/1.x headers object to store the results of the translation * @param headers The HTTP/1.x headers object to store the results of the translation
* @param translations A map used to help translate HTTP/2 headers to HTTP/1.x headers * @param translations A map used to help translate HTTP/2 headers to HTTP/1.x headers
*/ */
public HttpForEachVisitor(HttpHeaders headers, Map<String, String> translations) { public HttpAdapterVisitor(HttpHeaders headers, Map<String, String> translations) {
this.translations = translations; this.translations = translations;
this.headers = headers; this.headers = headers;
this.e = null; this.e = null;

View File

@ -0,0 +1,259 @@
/*
* Copyright 2014 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.channel.ChannelHandlerContext;
import io.netty.handler.codec.TextHeaderProcessor;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.FullHttpMessage;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.util.collection.IntObjectHashMap;
import io.netty.util.collection.IntObjectMap;
/**
* Translate header/data/priority HTTP/2 frame events into HTTP events. Just as {@link InboundHttp2ToHttpAdapter}
* may generate multiple {@link FullHttpMessage} objects per stream, this class is more likely to
* generate multiple messages per stream because the chances of an HTTP/2 event happening outside
* the header/data message flow is more likely.
*/
public final class InboundHttp2ToHttpPriorityAdapter extends InboundHttp2ToHttpAdapter {
private final IntObjectMap<HttpHeaders> outOfMessageFlowHeaders;
/**
* Creates a new instance
*
* @param connection The object which will provide connection notification events for the current connection
* @param maxContentLength the maximum length of the message content. If the length of the message content exceeds
* this value, a {@link TooLongFrameException} will be raised.
* @throws NullPointerException If {@code connection} is null
* @throws IllegalArgumentException If {@code maxContentLength} is less than or equal to {@code 0}
*/
public static InboundHttp2ToHttpPriorityAdapter newInstance(Http2Connection connection, int maxContentLength) {
InboundHttp2ToHttpPriorityAdapter instance = new InboundHttp2ToHttpPriorityAdapter(connection,
maxContentLength);
connection.addListener(instance);
return instance;
}
/**
* Creates a new instance
*
* @param connection The object which will provide connection notification events for the current connection
* @param maxContentLength the maximum length of the message content. If the length of the message content exceeds
* this value, a {@link TooLongFrameException} will be raised.
* @param validateHttpHeaders
* <ul>
* <li>{@code true} to validate HTTP headers in the http-codec</li>
* <li>{@code false} not to validate HTTP headers in the http-codec</li>
* </ul>
* @throws NullPointerException If {@code connection} is null
* @throws IllegalArgumentException If {@code maxContentLength} is less than or equal to {@code 0}
*/
public static InboundHttp2ToHttpPriorityAdapter newInstance(Http2Connection connection, int maxContentLength,
boolean validateHttpHeaders) {
InboundHttp2ToHttpPriorityAdapter instance = new InboundHttp2ToHttpPriorityAdapter(connection,
maxContentLength, validateHttpHeaders);
connection.addListener(instance);
return instance;
}
/**
* Creates a new instance
*
* @param connection The object which will provide connection notification events for the current connection
* @param maxContentLength the maximum length of the message content. If the length of the message content exceeds
* this value, a {@link TooLongFrameException} will be raised.
* @throws NullPointerException If {@code connection} is null
* @throws IllegalArgumentException If {@code maxContentLength} is less than or equal to {@code 0}
*/
private InboundHttp2ToHttpPriorityAdapter(Http2Connection connection, int maxContentLength) {
super(connection, maxContentLength);
outOfMessageFlowHeaders = new IntObjectHashMap<HttpHeaders>();
}
/**
* Creates a new instance
*
* @param connection The object which will provide connection notification events for the current connection
* @param maxContentLength the maximum length of the message content. If the length of the message content exceeds
* this value, a {@link TooLongFrameException} will be raised.
* @param validateHttpHeaders
* <ul>
* <li>{@code true} to validate HTTP headers in the http-codec</li>
* <li>{@code false} not to validate HTTP headers in the http-codec</li>
* </ul>
* @throws NullPointerException If {@code connection} is null
* @throws IllegalArgumentException If {@code maxContentLength} is less than or equal to {@code 0}
*/
private InboundHttp2ToHttpPriorityAdapter(Http2Connection connection, int maxContentLength,
boolean validateHttpHeaders) {
super(connection, maxContentLength, validateHttpHeaders);
outOfMessageFlowHeaders = new IntObjectHashMap<HttpHeaders>();
}
@Override
protected void removeMessage(int streamId) {
super.removeMessage(streamId);
outOfMessageFlowHeaders.remove(streamId);
}
/**
* Get either the header or the trailing headers depending on which is valid to add to
* @param msg The message containing the headers and trailing headers
* @return The headers object which can be appended to or modified
*/
private HttpHeaders getActiveHeaders(FullHttpMessage msg) {
return msg.content().isReadable() ? msg.trailingHeaders() : msg.headers();
}
/**
* This method will add the {@code headers} to the out of order headers map
* @param streamId The stream id associated with {@code headers}
* @param headers Newly encountered out of order headers which must be stored for future use
*/
private void importOutOfMessageFlowHeaders(int streamId, HttpHeaders headers) {
final HttpHeaders outOfMessageFlowHeader = outOfMessageFlowHeaders.get(streamId);
if (outOfMessageFlowHeader == null) {
outOfMessageFlowHeaders.put(streamId, headers);
} else {
outOfMessageFlowHeader.setAll(headers);
}
}
/**
* Take any saved out of order headers and export them to {@code headers}
* @param streamId The stream id to search for out of order headers for
* @param headers If any out of order headers exist for {@code streamId} they will be added to this object
*/
private void exportOutOfMessageFlowHeaders(int streamId, final HttpHeaders headers) {
final HttpHeaders outOfMessageFlowHeader = outOfMessageFlowHeaders.get(streamId);
if (outOfMessageFlowHeader != null) {
headers.setAll(outOfMessageFlowHeader);
}
}
/**
* This will remove all headers which are related to priority tree events
* @param headers The headers to remove the priority tree elements from
*/
private void removePriorityRelatedHeaders(HttpHeaders headers) {
headers.remove(HttpUtil.ExtensionHeaders.Names.STREAM_DEPENDENCY_ID);
headers.remove(HttpUtil.ExtensionHeaders.Names.STREAM_WEIGHT);
}
/**
* Initializes the pseudo header fields for out of message flow HTTP/2 headers
* @param builder The builder to set the pseudo header values
*/
private void initializePseudoHeaders(DefaultHttp2Headers.Builder builder) {
if (connection.isServer()) {
builder.method(HttpUtil.OUT_OF_MESSAGE_SEQUENCE_METHOD.toString())
.path(HttpUtil.OUT_OF_MESSAGE_SEQUENCE_PATH);
} else {
builder.status(HttpUtil.OUT_OF_MESSAGE_SEQUENCE_RETURN_CODE.toString());
}
}
/**
* Add all the HTTP headers into the HTTP/2 headers {@code builder} object
* @param headers The HTTP headers to translate to HTTP/2
* @param builder The container for the HTTP/2 headers
*/
private void addHttpHeadersToHttp2Headers(HttpHeaders headers, final DefaultHttp2Headers.Builder builder) {
headers.forEachEntry(new TextHeaderProcessor() {
@Override
public boolean process(CharSequence name, CharSequence value) throws Exception {
builder.add(name, value);
return true;
}
});
}
@Override
protected void fireChannelRead(ChannelHandlerContext ctx, FullHttpMessage msg, int streamId) {
exportOutOfMessageFlowHeaders(streamId, getActiveHeaders(msg));
super.fireChannelRead(ctx, msg, streamId);
}
@Override
protected FullHttpMessage processHeadersBegin(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
boolean endOfStream, boolean allowAppend, boolean appendToTrailer) throws Http2Exception {
FullHttpMessage msg = super.processHeadersBegin(ctx, streamId, headers,
endOfStream, allowAppend, appendToTrailer);
if (msg != null) {
exportOutOfMessageFlowHeaders(streamId, getActiveHeaders(msg));
}
return msg;
}
@Override
public void priorityTreeParentChanged(Http2Stream stream, Http2Stream oldParent) {
Http2Stream parent = stream.parent();
FullHttpMessage msg = messageMap.get(stream.id());
if (msg == null) {
// msg may be null if a HTTP/2 frame event in received outside the HTTP message flow
// For example a PRIORITY frame can be received in any state besides IDLE
// and the HTTP message flow exists in OPEN.
if (parent != null && !parent.equals(connection.connectionStream())) {
HttpHeaders headers = new DefaultHttpHeaders();
headers.set(HttpUtil.ExtensionHeaders.Names.STREAM_DEPENDENCY_ID, parent.id());
importOutOfMessageFlowHeaders(stream.id(), headers);
}
} else {
if (parent == null) {
removePriorityRelatedHeaders(msg.headers());
removePriorityRelatedHeaders(msg.trailingHeaders());
} else if (!parent.equals(connection.connectionStream())) {
HttpHeaders headers = getActiveHeaders(msg);
headers.set(HttpUtil.ExtensionHeaders.Names.STREAM_DEPENDENCY_ID, parent.id());
}
}
}
@Override
public void onWeightChanged(Http2Stream stream, short oldWeight) {
FullHttpMessage msg = messageMap.get(stream.id());
HttpHeaders headers = null;
if (msg == null) {
// msg may be null if a HTTP/2 frame event in received outside the HTTP message flow
// For example a PRIORITY frame can be received in any state besides IDLE
// and the HTTP message flow exists in OPEN.
headers = new DefaultHttpHeaders();
importOutOfMessageFlowHeaders(stream.id(), headers);
} else {
headers = getActiveHeaders(msg);
}
headers.set(HttpUtil.ExtensionHeaders.Names.STREAM_WEIGHT, stream.weight());
}
@Override
public void onPriorityRead(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight,
boolean exclusive) throws Http2Exception {
FullHttpMessage msg = messageMap.get(streamId);
if (msg == null) {
HttpHeaders headers = outOfMessageFlowHeaders.remove(streamId);
if (headers == null) {
throw Http2Exception.protocolError("Priority Frame recieved for unknown stream id %d", streamId);
}
DefaultHttp2Headers.Builder builder = DefaultHttp2Headers.newBuilder();
initializePseudoHeaders(builder);
addHttpHeadersToHttp2Headers(headers, builder);
msg = newMessage(streamId, builder.build(), validateHttpHeaders);
fireChannelRead(ctx, msg, streamId);
}
}
}

View File

@ -124,10 +124,10 @@ public class DelegatingHttp2HttpConnectionHandlerTest {
public void testJustHeadersRequest() throws Exception { public void testJustHeadersRequest() throws Exception {
final HttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, "/example"); final HttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, "/example");
final HttpHeaders httpHeaders = request.headers(); final HttpHeaders httpHeaders = request.headers();
httpHeaders.set(Http2ToHttpHeaders.Names.STREAM_ID, 5); httpHeaders.set(HttpUtil.ExtensionHeaders.Names.STREAM_ID, 5);
httpHeaders.set(HttpHeaders.Names.HOST, "http://my-user_name@www.example.org:5555/example"); httpHeaders.set(HttpHeaders.Names.HOST, "http://my-user_name@www.example.org:5555/example");
httpHeaders.set(Http2ToHttpHeaders.Names.AUTHORITY, "www.example.org:5555"); httpHeaders.set(HttpUtil.ExtensionHeaders.Names.AUTHORITY, "www.example.org:5555");
httpHeaders.set(Http2ToHttpHeaders.Names.SCHEME, "http"); httpHeaders.set(HttpUtil.ExtensionHeaders.Names.SCHEME, "http");
httpHeaders.add("foo", "goo"); httpHeaders.add("foo", "goo");
httpHeaders.add("foo", "goo2"); httpHeaders.add("foo", "goo2");
httpHeaders.add("foo2", "goo2"); httpHeaders.add("foo2", "goo2");

View File

@ -63,7 +63,8 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
/** /**
* Testing the {@link InboundHttp2ToHttpAdapter} for HTTP/2 frames into {@link HttpObject}s * Testing the {@link InboundHttp2ToHttpPriorityAdapter} and base class {@link InboundHttp2ToHttpAdapter}
* for HTTP/2 frames into {@link HttpObject}s
*/ */
public class InboundHttp2ToHttpAdapterTest { public class InboundHttp2ToHttpAdapterTest {
@ -107,8 +108,8 @@ public class InboundHttp2ToHttpAdapterTest {
protected void initChannel(Channel ch) throws Exception { protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline(); ChannelPipeline p = ch.pipeline();
Http2Connection connection = new DefaultHttp2Connection(true); Http2Connection connection = new DefaultHttp2Connection(true);
p.addLast("reader", p.addLast("reader", new FrameAdapter(connection,
new FrameAdapter(InboundHttp2ToHttpAdapter.newInstance(connection, maxContentLength), InboundHttp2ToHttpPriorityAdapter.newInstance(connection, maxContentLength),
new CountDownLatch(10))); new CountDownLatch(10)));
serverDelegator = new HttpResponseDelegator(serverObserver, serverLatch); serverDelegator = new HttpResponseDelegator(serverObserver, serverLatch);
p.addLast(serverDelegator); p.addLast(serverDelegator);
@ -123,8 +124,8 @@ public class InboundHttp2ToHttpAdapterTest {
protected void initChannel(Channel ch) throws Exception { protected void initChannel(Channel ch) throws Exception {
ChannelPipeline p = ch.pipeline(); ChannelPipeline p = ch.pipeline();
Http2Connection connection = new DefaultHttp2Connection(false); Http2Connection connection = new DefaultHttp2Connection(false);
p.addLast("reader", p.addLast("reader", new FrameAdapter(connection,
new FrameAdapter(InboundHttp2ToHttpAdapter.newInstance(connection, maxContentLength), InboundHttp2ToHttpPriorityAdapter.newInstance(connection, maxContentLength),
new CountDownLatch(10))); new CountDownLatch(10)));
clientDelegator = new HttpResponseDelegator(clientObserver, clientLatch); clientDelegator = new HttpResponseDelegator(clientObserver, clientLatch);
p.addLast(clientDelegator); p.addLast(clientDelegator);
@ -156,9 +157,9 @@ public class InboundHttp2ToHttpAdapterTest {
final HttpMessage request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, final HttpMessage request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
"/some/path/resource2", true); "/some/path/resource2", true);
HttpHeaders httpHeaders = request.headers(); HttpHeaders httpHeaders = request.headers();
httpHeaders.set(Http2ToHttpHeaders.Names.SCHEME, "https"); httpHeaders.set(HttpUtil.ExtensionHeaders.Names.SCHEME, "https");
httpHeaders.set(Http2ToHttpHeaders.Names.AUTHORITY, "example.org"); httpHeaders.set(HttpUtil.ExtensionHeaders.Names.AUTHORITY, "example.org");
httpHeaders.set(Http2ToHttpHeaders.Names.STREAM_ID, 3); httpHeaders.set(HttpUtil.ExtensionHeaders.Names.STREAM_ID, 3);
httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, 0); httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, 0);
final Http2Headers http2Headers = new DefaultHttp2Headers.Builder().method("GET").scheme("https") final Http2Headers http2Headers = new DefaultHttp2Headers.Builder().method("GET").scheme("https")
.authority("example.org").path("/some/path/resource2").build(); .authority("example.org").path("/some/path/resource2").build();
@ -180,7 +181,7 @@ public class InboundHttp2ToHttpAdapterTest {
final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
"/some/path/resource2", content, true); "/some/path/resource2", content, true);
HttpHeaders httpHeaders = request.headers(); HttpHeaders httpHeaders = request.headers();
httpHeaders.set(Http2ToHttpHeaders.Names.STREAM_ID, 3); httpHeaders.set(HttpUtil.ExtensionHeaders.Names.STREAM_ID, 3);
httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, text.length()); httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, text.length());
final Http2Headers http2Headers = new DefaultHttp2Headers.Builder().method("GET").path("/some/path/resource2") final Http2Headers http2Headers = new DefaultHttp2Headers.Builder().method("GET").path("/some/path/resource2")
.build(); .build();
@ -205,7 +206,7 @@ public class InboundHttp2ToHttpAdapterTest {
final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
"/some/path/resource2", content, true); "/some/path/resource2", content, true);
HttpHeaders httpHeaders = request.headers(); HttpHeaders httpHeaders = request.headers();
httpHeaders.set(Http2ToHttpHeaders.Names.STREAM_ID, 3); httpHeaders.set(HttpUtil.ExtensionHeaders.Names.STREAM_ID, 3);
httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, text.length()); httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, text.length());
final Http2Headers http2Headers = new DefaultHttp2Headers.Builder().method("GET").path("/some/path/resource2") final Http2Headers http2Headers = new DefaultHttp2Headers.Builder().method("GET").path("/some/path/resource2")
.build(); .build();
@ -233,7 +234,7 @@ public class InboundHttp2ToHttpAdapterTest {
final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
"/some/path/resource2", content, true); "/some/path/resource2", content, true);
HttpHeaders httpHeaders = request.headers(); HttpHeaders httpHeaders = request.headers();
httpHeaders.set(Http2ToHttpHeaders.Names.STREAM_ID, 3); httpHeaders.set(HttpUtil.ExtensionHeaders.Names.STREAM_ID, 3);
httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, text.length()); httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, text.length());
final Http2Headers http2Headers = new DefaultHttp2Headers.Builder().method("GET").path("/some/path/resource2") final Http2Headers http2Headers = new DefaultHttp2Headers.Builder().method("GET").path("/some/path/resource2")
.build(); .build();
@ -261,7 +262,7 @@ public class InboundHttp2ToHttpAdapterTest {
final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
"/some/path/resource2", content, true); "/some/path/resource2", content, true);
HttpHeaders httpHeaders = request.headers(); HttpHeaders httpHeaders = request.headers();
httpHeaders.set(Http2ToHttpHeaders.Names.STREAM_ID, 3); httpHeaders.set(HttpUtil.ExtensionHeaders.Names.STREAM_ID, 3);
httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, text.length()); httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, text.length());
HttpHeaders trailingHeaders = request.trailingHeaders(); HttpHeaders trailingHeaders = request.trailingHeaders();
trailingHeaders.set("FoO", "goo"); trailingHeaders.set("FoO", "goo");
@ -293,7 +294,7 @@ public class InboundHttp2ToHttpAdapterTest {
final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET,
"/some/path/resource2", content, true); "/some/path/resource2", content, true);
HttpHeaders httpHeaders = request.headers(); HttpHeaders httpHeaders = request.headers();
httpHeaders.set(Http2ToHttpHeaders.Names.STREAM_ID, 3); httpHeaders.set(HttpUtil.ExtensionHeaders.Names.STREAM_ID, 3);
httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, text.length()); httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, text.length());
HttpHeaders trailingHeaders = request.trailingHeaders(); HttpHeaders trailingHeaders = request.trailingHeaders();
trailingHeaders.set("Foo", "goo"); trailingHeaders.set("Foo", "goo");
@ -318,7 +319,7 @@ public class InboundHttp2ToHttpAdapterTest {
} }
@Test @Test
public void clientRequestStreamDependency() throws Exception { public void clientRequestStreamDependencyInHttpMessageFlow() throws Exception {
setServerLatch(2); setServerLatch(2);
final String text = "hello world big time data!"; final String text = "hello world big time data!";
final ByteBuf content = Unpooled.copiedBuffer(text.getBytes()); final ByteBuf content = Unpooled.copiedBuffer(text.getBytes());
@ -328,15 +329,14 @@ public class InboundHttp2ToHttpAdapterTest {
"/some/path/resource", content, true); "/some/path/resource", content, true);
HttpHeaders httpHeaders = request.headers(); HttpHeaders httpHeaders = request.headers();
httpHeaders.set(Http2ToHttpHeaders.Names.STREAM_ID, 3); httpHeaders.set(HttpUtil.ExtensionHeaders.Names.STREAM_ID, 3);
httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, text.length()); httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, text.length());
final FullHttpMessage request2 = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, final FullHttpMessage request2 = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT,
"/some/path/resource2", content2, true); "/some/path/resource2", content2, true);
HttpHeaders httpHeaders2 = request2.headers(); HttpHeaders httpHeaders2 = request2.headers();
httpHeaders2.set(Http2ToHttpHeaders.Names.STREAM_ID, 5); httpHeaders2.set(HttpUtil.ExtensionHeaders.Names.STREAM_ID, 5);
httpHeaders2.set(Http2ToHttpHeaders.Names.STREAM_DEPENDENCY_ID, 3); httpHeaders2.set(HttpUtil.ExtensionHeaders.Names.STREAM_DEPENDENCY_ID, 3);
httpHeaders2.set(Http2ToHttpHeaders.Names.STREAM_EXCLUSIVE, true); httpHeaders2.set(HttpUtil.ExtensionHeaders.Names.STREAM_WEIGHT, 123);
httpHeaders2.set(Http2ToHttpHeaders.Names.STREAM_WEIGHT, 256);
httpHeaders2.set(HttpHeaders.Names.CONTENT_LENGTH, text2.length()); httpHeaders2.set(HttpHeaders.Names.CONTENT_LENGTH, text2.length());
final Http2Headers http2Headers = new DefaultHttp2Headers.Builder().method("PUT").path("/some/path/resource") final Http2Headers http2Headers = new DefaultHttp2Headers.Builder().method("PUT").path("/some/path/resource")
.build(); .build();
@ -347,7 +347,7 @@ public class InboundHttp2ToHttpAdapterTest {
public void run() { public void run() {
frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient()); frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient());
frameWriter.writeHeaders(ctxClient(), 5, http2Headers2, 0, false, newPromiseClient()); frameWriter.writeHeaders(ctxClient(), 5, http2Headers2, 0, false, newPromiseClient());
frameWriter.writePriority(ctxClient(), 5, 3, (short) 256, true, newPromiseClient()); frameWriter.writePriority(ctxClient(), 5, 3, (short) 123, true, newPromiseClient());
frameWriter.writeData(ctxClient(), 3, content.retain(), 0, true, newPromiseClient()); frameWriter.writeData(ctxClient(), 3, content.retain(), 0, true, newPromiseClient());
frameWriter.writeData(ctxClient(), 5, content2.retain(), 0, true, newPromiseClient()); frameWriter.writeData(ctxClient(), 5, content2.retain(), 0, true, newPromiseClient());
ctxClient().flush(); ctxClient().flush();
@ -363,6 +363,59 @@ public class InboundHttp2ToHttpAdapterTest {
request2.release(); request2.release();
} }
@Test
public void clientRequestStreamDependencyOutsideHttpMessageFlow() throws Exception {
setServerLatch(3);
final String text = "hello world big time data!";
final ByteBuf content = Unpooled.copiedBuffer(text.getBytes());
final String text2 = "hello world big time data...number 2!!";
final ByteBuf content2 = Unpooled.copiedBuffer(text2.getBytes());
final FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT,
"/some/path/resource", content, true);
HttpHeaders httpHeaders = request.headers();
httpHeaders.set(HttpUtil.ExtensionHeaders.Names.STREAM_ID, 3);
httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, text.length());
final FullHttpMessage request2 = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT,
"/some/path/resource2", content2, true);
HttpHeaders httpHeaders2 = request2.headers();
httpHeaders2.set(HttpUtil.ExtensionHeaders.Names.STREAM_ID, 5);
httpHeaders2.set(HttpHeaders.Names.CONTENT_LENGTH, text2.length());
final Http2Headers http2Headers = new DefaultHttp2Headers.Builder().method("PUT").path("/some/path/resource")
.build();
final Http2Headers http2Headers2 = new DefaultHttp2Headers.Builder().method("PUT").path("/some/path/resource2")
.build();
final FullHttpMessage request3 = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1,
HttpUtil.OUT_OF_MESSAGE_SEQUENCE_METHOD,
HttpUtil.OUT_OF_MESSAGE_SEQUENCE_PATH, true);
HttpHeaders httpHeaders3 = request3.headers();
httpHeaders3.set(HttpUtil.ExtensionHeaders.Names.STREAM_ID, 5);
httpHeaders3.set(HttpUtil.ExtensionHeaders.Names.STREAM_DEPENDENCY_ID, 3);
httpHeaders3.set(HttpUtil.ExtensionHeaders.Names.STREAM_WEIGHT, 222);
httpHeaders3.set(HttpHeaders.Names.CONTENT_LENGTH, 0);
runInChannel(clientChannel, new Http2Runnable() {
@Override
public void run() {
frameWriter.writeHeaders(ctxClient(), 3, http2Headers, 0, false, newPromiseClient());
frameWriter.writeHeaders(ctxClient(), 5, http2Headers2, 0, false, newPromiseClient());
frameWriter.writeData(ctxClient(), 3, content.retain(), 0, true, newPromiseClient());
frameWriter.writeData(ctxClient(), 5, content2.retain(), 0, true, newPromiseClient());
frameWriter.writePriority(ctxClient(), 5, 3, (short) 222, false, newPromiseClient());
ctxClient().flush();
}
});
awaitRequests();
ArgumentCaptor<HttpObject> httpObjectCaptor = ArgumentCaptor.forClass(HttpObject.class);
verify(serverObserver, times(3)).messageReceived(httpObjectCaptor.capture());
List<HttpObject> capturedHttpObjects = httpObjectCaptor.getAllValues();
assertEquals(request, capturedHttpObjects.get(0));
assertEquals(request2, capturedHttpObjects.get(1));
assertEquals(request3, capturedHttpObjects.get(2));
request.release();
request2.release();
request3.release();
}
@Test @Test
public void serverRequestPushPromise() throws Exception { public void serverRequestPushPromise() throws Exception {
setClientLatch(2); setClientLatch(2);
@ -373,20 +426,20 @@ public class InboundHttp2ToHttpAdapterTest {
final FullHttpMessage response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, final FullHttpMessage response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
content, true); content, true);
HttpHeaders httpHeaders = response.headers(); HttpHeaders httpHeaders = response.headers();
httpHeaders.set(Http2ToHttpHeaders.Names.STREAM_ID, 3); httpHeaders.set(HttpUtil.ExtensionHeaders.Names.STREAM_ID, 3);
httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, text.length()); httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, text.length());
final FullHttpMessage response2 = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CREATED, final FullHttpMessage response2 = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CREATED,
content2, true); content2, true);
HttpHeaders httpHeaders2 = response2.headers(); HttpHeaders httpHeaders2 = response2.headers();
httpHeaders2.set(Http2ToHttpHeaders.Names.SCHEME, "https"); httpHeaders2.set(HttpUtil.ExtensionHeaders.Names.SCHEME, "https");
httpHeaders2.set(Http2ToHttpHeaders.Names.AUTHORITY, "example.org"); httpHeaders2.set(HttpUtil.ExtensionHeaders.Names.AUTHORITY, "example.org");
httpHeaders2.set(Http2ToHttpHeaders.Names.STREAM_ID, 5); httpHeaders2.set(HttpUtil.ExtensionHeaders.Names.STREAM_ID, 5);
httpHeaders2.set(Http2ToHttpHeaders.Names.STREAM_PROMISE_ID, 3); httpHeaders2.set(HttpUtil.ExtensionHeaders.Names.STREAM_PROMISE_ID, 3);
httpHeaders2.set(HttpHeaders.Names.CONTENT_LENGTH, text2.length()); httpHeaders2.set(HttpHeaders.Names.CONTENT_LENGTH, text2.length());
final HttpMessage request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/push/test", true); final HttpMessage request = new DefaultHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/push/test", true);
httpHeaders = request.headers(); httpHeaders = request.headers();
httpHeaders.set(Http2ToHttpHeaders.Names.STREAM_ID, 3); httpHeaders.set(HttpUtil.ExtensionHeaders.Names.STREAM_ID, 3);
httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, 0); httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, 0);
final Http2Headers http2Headers3 = new DefaultHttp2Headers.Builder().method("GET").path("/push/test").build(); final Http2Headers http2Headers3 = new DefaultHttp2Headers.Builder().method("GET").path("/push/test").build();
runInChannel(clientChannel, new Http2Runnable() { runInChannel(clientChannel, new Http2Runnable() {
@ -427,7 +480,7 @@ public class InboundHttp2ToHttpAdapterTest {
final FullHttpMessage request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "/info/test", final FullHttpMessage request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.PUT, "/info/test",
true); true);
HttpHeaders httpHeaders = request.headers(); HttpHeaders httpHeaders = request.headers();
httpHeaders.set(Http2ToHttpHeaders.Names.STREAM_ID, 3); httpHeaders.set(HttpUtil.ExtensionHeaders.Names.STREAM_ID, 3);
httpHeaders.set(HttpHeaders.Names.EXPECT, HttpHeaders.Values.CONTINUE); httpHeaders.set(HttpHeaders.Names.EXPECT, HttpHeaders.Values.CONTINUE);
httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, 0); httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, 0);
final Http2Headers http2Headers = new DefaultHttp2Headers.Builder().method("PUT").path("/info/test") final Http2Headers http2Headers = new DefaultHttp2Headers.Builder().method("PUT").path("/info/test")
@ -445,7 +498,7 @@ public class InboundHttp2ToHttpAdapterTest {
final FullHttpMessage response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE); final FullHttpMessage response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
httpHeaders = response.headers(); httpHeaders = response.headers();
httpHeaders.set(Http2ToHttpHeaders.Names.STREAM_ID, 3); httpHeaders.set(HttpUtil.ExtensionHeaders.Names.STREAM_ID, 3);
httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, 0); httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, 0);
final Http2Headers http2HeadersResponse = new DefaultHttp2Headers.Builder().status("100").build(); final Http2Headers http2HeadersResponse = new DefaultHttp2Headers.Builder().status("100").build();
runInChannel(serverConnectedChannel, new Http2Runnable() { runInChannel(serverConnectedChannel, new Http2Runnable() {
@ -481,7 +534,7 @@ public class InboundHttp2ToHttpAdapterTest {
setClientLatch(1); setClientLatch(1);
final FullHttpMessage response2 = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); final FullHttpMessage response2 = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
httpHeaders = response2.headers(); httpHeaders = response2.headers();
httpHeaders.set(Http2ToHttpHeaders.Names.STREAM_ID, 3); httpHeaders.set(HttpUtil.ExtensionHeaders.Names.STREAM_ID, 3);
httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, 0); httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, 0);
final Http2Headers http2HeadersResponse2 = new DefaultHttp2Headers.Builder().status("200").build(); final Http2Headers http2HeadersResponse2 = new DefaultHttp2Headers.Builder().status("200").build();
runInChannel(serverConnectedChannel, new Http2Runnable() { runInChannel(serverConnectedChannel, new Http2Runnable() {
@ -510,11 +563,11 @@ public class InboundHttp2ToHttpAdapterTest {
} }
private void awaitRequests() throws Exception { private void awaitRequests() throws Exception {
serverLatch.await(200, SECONDS); serverLatch.await(2, SECONDS);
} }
private void awaitResponses() throws Exception { private void awaitResponses() throws Exception {
clientLatch.await(200, SECONDS); clientLatch.await(2, SECONDS);
} }
private ChannelHandlerContext ctxClient() { private ChannelHandlerContext ctxClient() {
@ -559,11 +612,13 @@ public class InboundHttp2ToHttpAdapterTest {
} }
private final class FrameAdapter extends ByteToMessageDecoder { private final class FrameAdapter extends ByteToMessageDecoder {
private final Http2Connection connection;
private final Http2FrameObserver observer; private final Http2FrameObserver observer;
private final DefaultHttp2FrameReader reader; private final DefaultHttp2FrameReader reader;
private final CountDownLatch latch; private final CountDownLatch latch;
FrameAdapter(Http2FrameObserver observer, CountDownLatch latch) { FrameAdapter(Http2Connection connection, Http2FrameObserver observer, CountDownLatch latch) {
this.connection = connection;
this.observer = observer; this.observer = observer;
reader = new DefaultHttp2FrameReader(); reader = new DefaultHttp2FrameReader();
this.latch = latch; this.latch = latch;
@ -572,18 +627,41 @@ public class InboundHttp2ToHttpAdapterTest {
@Override @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
reader.readFrame(ctx, in, new Http2FrameObserver() { reader.readFrame(ctx, in, new Http2FrameObserver() {
public Http2Stream getOrCreateStream(int streamId, boolean halfClosed) throws Http2Exception {
Http2Stream stream = connection.stream(streamId);
if (stream == null) {
if ((connection.isServer() && streamId % 2 == 0) ||
(!connection.isServer() && streamId % 2 != 0)) {
stream = connection.local().createStream(streamId, halfClosed);
} else {
stream = connection.remote().createStream(streamId, halfClosed);
}
}
return stream;
}
public void closeStream(Http2Stream stream) {
if (stream != null) {
stream.close();
}
}
@Override @Override
public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, public void onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding,
boolean endOfStream) throws Http2Exception { boolean endOfStream) throws Http2Exception {
observer.onDataRead(ctx, streamId, copy(data), padding, endOfStream); observer.onDataRead(ctx, streamId, copy(data), padding, endOfStream);
// NOTE: Do not close the stream to allow the out of order messages to be processed
latch.countDown(); latch.countDown();
} }
@Override @Override
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding, public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding,
boolean endStream) throws Http2Exception { boolean endStream) throws Http2Exception {
Http2Stream stream = getOrCreateStream(streamId, endStream);
observer.onHeadersRead(ctx, streamId, headers, padding, endStream); observer.onHeadersRead(ctx, streamId, headers, padding, endStream);
if (endStream) {
closeStream(stream);
}
latch.countDown(); latch.countDown();
} }
@ -591,14 +669,21 @@ public class InboundHttp2ToHttpAdapterTest {
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers,
int streamDependency, short weight, boolean exclusive, int padding, boolean endStream) int streamDependency, short weight, boolean exclusive, int padding, boolean endStream)
throws Http2Exception { throws Http2Exception {
Http2Stream stream = getOrCreateStream(streamId, endStream);
stream.setPriority(streamDependency, weight, exclusive);
observer.onHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive, padding, observer.onHeadersRead(ctx, streamId, headers, streamDependency, weight, exclusive, padding,
endStream); endStream);
if (endStream) {
closeStream(stream);
}
latch.countDown(); latch.countDown();
} }
@Override @Override
public void onPriorityRead(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight, public void onPriorityRead(ChannelHandlerContext ctx, int streamId, int streamDependency, short weight,
boolean exclusive) throws Http2Exception { boolean exclusive) throws Http2Exception {
Http2Stream stream = getOrCreateStream(streamId, false);
stream.setPriority(streamDependency, weight, exclusive);
observer.onPriorityRead(ctx, streamId, streamDependency, weight, exclusive); observer.onPriorityRead(ctx, streamId, streamDependency, weight, exclusive);
latch.countDown(); latch.countDown();
} }
@ -606,7 +691,9 @@ public class InboundHttp2ToHttpAdapterTest {
@Override @Override
public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode)
throws Http2Exception { throws Http2Exception {
Http2Stream stream = getOrCreateStream(streamId, false);
observer.onRstStreamRead(ctx, streamId, errorCode); observer.onRstStreamRead(ctx, streamId, errorCode);
closeStream(stream);
latch.countDown(); latch.countDown();
} }
@ -637,6 +724,7 @@ public class InboundHttp2ToHttpAdapterTest {
@Override @Override
public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId, public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
Http2Headers headers, int padding) throws Http2Exception { Http2Headers headers, int padding) throws Http2Exception {
getOrCreateStream(promisedStreamId, false);
observer.onPushPromiseRead(ctx, streamId, promisedStreamId, headers, padding); observer.onPushPromiseRead(ctx, streamId, promisedStreamId, headers, padding);
latch.countDown(); latch.countDown();
} }
@ -651,6 +739,7 @@ public class InboundHttp2ToHttpAdapterTest {
@Override @Override
public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement) public void onWindowUpdateRead(ChannelHandlerContext ctx, int streamId, int windowSizeIncrement)
throws Http2Exception { throws Http2Exception {
getOrCreateStream(streamId, false);
observer.onWindowUpdateRead(ctx, streamId, windowSizeIncrement); observer.onWindowUpdateRead(ctx, streamId, windowSizeIncrement);
latch.countDown(); latch.countDown();
} }

View File

@ -305,6 +305,30 @@ public class DefaultTextHeaders implements TextHeaders {
return this; return this;
} }
@Override
public TextHeaders setAll(TextHeaders headers) {
if (headers == null) {
throw new NullPointerException("headers");
}
if (headers instanceof DefaultTextHeaders) {
DefaultTextHeaders m = (DefaultTextHeaders) headers;
HeaderEntry e = m.head.after;
while (e != m.head) {
CharSequence name = e.name;
name = convertName(name);
set(name, convertValue(e.value));
e = e.after;
}
} else {
for (Entry<CharSequence, CharSequence> e: headers.unconvertedEntries()) {
set(e.getKey(), e.getValue());
}
}
return this;
}
@Override @Override
public TextHeaders clear() { public TextHeaders clear() {
Arrays.fill(entries, null); Arrays.fill(entries, null);

View File

@ -211,6 +211,11 @@ public class EmptyTextHeaders implements TextHeaders {
throw new UnsupportedOperationException("read only"); throw new UnsupportedOperationException("read only");
} }
@Override
public TextHeaders setAll(TextHeaders rhs) {
throw new UnsupportedOperationException("read only");
}
@Override @Override
public boolean remove(CharSequence name) { public boolean remove(CharSequence name) {
return false; return false;

View File

@ -417,6 +417,13 @@ public interface TextHeaders extends Iterable<Map.Entry<String, String>> {
*/ */
TextHeaders set(TextHeaders headers); TextHeaders set(TextHeaders headers);
/**
* Retains all current headers but calls {@link #set(CharSequence, Object)} for each entry in {@code headers}
* @param headers The headers used to {@link #set(CharSequence, Object)} values in this instance
* @return {@code this}
*/
TextHeaders setAll(TextHeaders headers);
/** /**
* Removes the header with the specified name. * Removes the header with the specified name.
* *

View File

@ -15,6 +15,7 @@
*/ */
package io.netty.handler.codec; package io.netty.handler.codec;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -76,4 +77,31 @@ public class DefaultTextHeadersTest {
assertTrue(h2.equals(h2)); assertTrue(h2.equals(h2));
assertTrue(h1.equals(h1)); assertTrue(h1.equals(h1));
} }
@Test
public void testSetAll() {
DefaultTextHeaders h1 = new DefaultTextHeaders();
h1.set("FOO", "goo");
h1.set("foo2", "goo2");
h1.add("foo2", "goo3");
h1.add("foo", "goo4");
DefaultTextHeaders h2 = new DefaultTextHeaders();
h2.set("foo", "goo");
h2.set("foo2", "goo2");
h2.add("foo", "goo4");
DefaultTextHeaders expected = new DefaultTextHeaders();
expected.set("FOO", "goo");
expected.set("foo2", "goo2");
expected.add("foo2", "goo3");
expected.add("foo", "goo4");
expected.set("foo", "goo");
expected.set("foo2", "goo2");
expected.set("foo", "goo4");
h1.setAll(h2);
assertEquals(expected, h1);
}
} }

View File

@ -14,11 +14,17 @@
*/ */
package io.netty.example.http2.client; package io.netty.example.http2.client;
import static io.netty.util.internal.logging.InternalLogLevel.INFO;
import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpClientUpgradeHandler;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http2.DefaultHttp2Connection; import io.netty.handler.codec.http2.DefaultHttp2Connection;
import io.netty.handler.codec.http2.DefaultHttp2FrameReader; import io.netty.handler.codec.http2.DefaultHttp2FrameReader;
import io.netty.handler.codec.http2.DefaultHttp2FrameWriter; import io.netty.handler.codec.http2.DefaultHttp2FrameWriter;
@ -26,26 +32,17 @@ import io.netty.handler.codec.http2.DefaultHttp2InboundFlowController;
import io.netty.handler.codec.http2.DefaultHttp2OutboundFlowController; import io.netty.handler.codec.http2.DefaultHttp2OutboundFlowController;
import io.netty.handler.codec.http2.DelegatingHttp2ConnectionHandler; import io.netty.handler.codec.http2.DelegatingHttp2ConnectionHandler;
import io.netty.handler.codec.http2.DelegatingHttp2HttpConnectionHandler; import io.netty.handler.codec.http2.DelegatingHttp2HttpConnectionHandler;
import io.netty.handler.codec.http2.Http2ClientUpgradeCodec;
import io.netty.handler.codec.http2.Http2Connection; import io.netty.handler.codec.http2.Http2Connection;
import io.netty.handler.codec.http2.Http2FrameLogger;
import io.netty.handler.codec.http2.Http2FrameReader; import io.netty.handler.codec.http2.Http2FrameReader;
import io.netty.handler.codec.http2.Http2FrameWriter; import io.netty.handler.codec.http2.Http2FrameWriter;
import io.netty.handler.codec.http2.Http2InboundFrameLogger; import io.netty.handler.codec.http2.Http2InboundFrameLogger;
import io.netty.handler.codec.http2.Http2OutboundFrameLogger; import io.netty.handler.codec.http2.Http2OutboundFrameLogger;
import io.netty.handler.codec.http.HttpContentDecompressor;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpClientUpgradeHandler;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http2.Http2FrameLogger;
import io.netty.handler.codec.http2.InboundHttp2ToHttpAdapter; import io.netty.handler.codec.http2.InboundHttp2ToHttpAdapter;
import io.netty.handler.codec.http2.Http2ClientUpgradeCodec;
import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContext;
import io.netty.util.internal.logging.InternalLoggerFactory; import io.netty.util.internal.logging.InternalLoggerFactory;
import static io.netty.util.internal.logging.InternalLogLevel.*;
/** /**
* Configures the client pipeline to support HTTP/2 frames. * Configures the client pipeline to support HTTP/2 frames.
*/ */

View File

@ -19,7 +19,7 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise; import io.netty.channel.ChannelPromise;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http2.Http2ToHttpHeaders; import io.netty.handler.codec.http2.HttpUtil;
import io.netty.util.CharsetUtil; import io.netty.util.CharsetUtil;
import java.util.Iterator; import java.util.Iterator;
@ -76,7 +76,7 @@ public class HttpResponseHandler extends SimpleChannelInboundHandler<FullHttpRes
@Override @Override
protected void messageReceived(ChannelHandlerContext ctx, FullHttpResponse msg) throws Exception { protected void messageReceived(ChannelHandlerContext ctx, FullHttpResponse msg) throws Exception {
int streamId = Integer.parseInt(msg.headers().get(Http2ToHttpHeaders.Names.STREAM_ID)); int streamId = Integer.parseInt(msg.headers().get(HttpUtil.ExtensionHeaders.Names.STREAM_ID));
ChannelPromise promise = streamidPromiseMap.get(streamId); ChannelPromise promise = streamidPromiseMap.get(streamId);
if (promise == null) { if (promise == null) {
System.err.println("Message received for unknown stream id " + streamId); System.err.println("Message received for unknown stream id " + streamId);