HTTP/2 to HTTP buffer ordering

Motivation:
The HTTP tranlsation layer uses a FullHttpMessage object after it has been fired up the pipeline.
Although the content ByteBuf is not used by default it is still not ideal to use a releasable object
after it has potentially been released.

Modifications:
-InboundHttp2ToHttpAdapter ordering issues will be corrected

Result:
Safer access to releasable objects in the HTTP/2 to HTTP translation layer.
This commit is contained in:
Scott Mitchell 2014-09-22 21:59:23 -04:00 committed by Mitchell
parent 0790678c72
commit fc273ccbf0

View File

@ -30,6 +30,28 @@ import io.netty.util.collection.IntObjectMap;
* here <a href="http://tools.ietf.org/html/draft-ietf-httpbis-http2-14#section-8.1.">HTTP/2 Spec Message Flow</a> * 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 Http2EventAdapter { public class InboundHttp2ToHttpAdapter extends Http2EventAdapter {
private static final ImmediateSendDetector DEFAULT_SEND_DETECTOR = new ImmediateSendDetector() {
@Override
public boolean mustSendImmediately(FullHttpMessage msg) {
if (msg instanceof FullHttpResponse) {
return ((FullHttpResponse) msg).status().isInformational();
} else if (msg instanceof FullHttpRequest) {
return ((FullHttpRequest) msg).headers().contains(HttpHeaders.Names.EXPECT);
}
return false;
}
@Override
public FullHttpMessage copyIfNeeded(FullHttpMessage msg) {
if (msg instanceof FullHttpRequest) {
FullHttpRequest copy = ((FullHttpRequest) msg).copy(null);
copy.headers().remove(HttpHeaders.Names.EXPECT);
return copy;
}
return null;
}
};
private final int maxContentLength; private final int maxContentLength;
protected final Http2Connection connection; protected final Http2Connection connection;
protected final boolean validateHttpHeaders; protected final boolean validateHttpHeaders;
@ -111,7 +133,7 @@ public class InboundHttp2ToHttpAdapter extends Http2EventAdapter {
this.maxContentLength = maxContentLength; this.maxContentLength = maxContentLength;
this.validateHttpHeaders = validateHttpHeaders; this.validateHttpHeaders = validateHttpHeaders;
this.connection = connection; this.connection = connection;
sendDetector = DefaultImmediateSendDetector.getInstance(); sendDetector = DEFAULT_SEND_DETECTOR;
messageMap = new IntObjectHashMap<FullHttpMessage>(); messageMap = new IntObjectHashMap<FullHttpMessage>();
} }
@ -201,8 +223,11 @@ public class InboundHttp2ToHttpAdapter extends Http2EventAdapter {
} }
if (sendDetector.mustSendImmediately(msg)) { if (sendDetector.mustSendImmediately(msg)) {
// Copy the message (if necessary) before sending. The content is not expected to be copied (or used) in
// this operation but just in case it is used do the copy before sending and the resource may be released
final FullHttpMessage copy = endOfStream ? null : sendDetector.copyIfNeeded(msg);
fireChannelRead(ctx, msg, streamId); fireChannelRead(ctx, msg, streamId);
return endOfStream ? null : sendDetector.copyIfNeeded(msg); return copy;
} }
return msg; return msg;
@ -235,12 +260,13 @@ public class InboundHttp2ToHttpAdapter extends Http2EventAdapter {
} }
ByteBuf content = msg.content(); ByteBuf content = msg.content();
if (content.readableBytes() > maxContentLength - data.readableBytes()) { final int dataReadableBytes = data.readableBytes();
if (content.readableBytes() > maxContentLength - dataReadableBytes) {
throw Http2Exception.format(Http2Error.INTERNAL_ERROR, throw Http2Exception.format(Http2Error.INTERNAL_ERROR,
"Content length exceeded max of %d for stream id %d", maxContentLength, streamId); "Content length exceeded max of %d for stream id %d", maxContentLength, streamId);
} }
content.writeBytes(data, data.readerIndex(), data.readableBytes()); content.writeBytes(data, data.readerIndex(), dataReadableBytes);
if (endOfStream) { if (endOfStream) {
fireChannelRead(ctx, msg, streamId); fireChannelRead(ctx, msg, streamId);
@ -314,41 +340,4 @@ public class InboundHttp2ToHttpAdapter extends Http2EventAdapter {
*/ */
FullHttpMessage copyIfNeeded(FullHttpMessage msg); FullHttpMessage copyIfNeeded(FullHttpMessage msg);
} }
/**
* Default implementation of {@link ImmediateSendDetector}
*/
private static final class DefaultImmediateSendDetector implements ImmediateSendDetector {
private static DefaultImmediateSendDetector instance;
private DefaultImmediateSendDetector() {
}
public static DefaultImmediateSendDetector getInstance() {
if (instance == null) {
instance = new DefaultImmediateSendDetector();
}
return instance;
}
@Override
public boolean mustSendImmediately(FullHttpMessage msg) {
if (msg instanceof FullHttpResponse) {
return ((FullHttpResponse) msg).status().isInformational();
} else if (msg instanceof FullHttpRequest) {
return ((FullHttpRequest) msg).headers().contains(HttpHeaders.Names.EXPECT);
}
return false;
}
@Override
public FullHttpMessage copyIfNeeded(FullHttpMessage msg) {
if (msg instanceof FullHttpRequest) {
FullHttpRequest copy = ((FullHttpRequest) msg).copy(null);
copy.headers().remove(HttpHeaders.Names.EXPECT);
return copy;
}
return null;
}
}
} }