2015-01-20 01:48:11 +01:00
|
|
|
/*
|
|
|
|
* 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;
|
2016-02-13 13:30:48 +01:00
|
|
|
import io.netty.buffer.ByteBufAllocator;
|
2016-04-14 10:31:48 +02:00
|
|
|
import io.netty.buffer.Unpooled;
|
2015-01-20 01:48:11 +01:00
|
|
|
import io.netty.channel.ChannelHandlerContext;
|
|
|
|
import io.netty.handler.codec.http.FullHttpMessage;
|
|
|
|
import io.netty.handler.codec.http.FullHttpRequest;
|
|
|
|
import io.netty.handler.codec.http.FullHttpResponse;
|
|
|
|
import io.netty.handler.codec.http.HttpHeaderNames;
|
|
|
|
import io.netty.handler.codec.http.HttpStatusClass;
|
2015-12-17 07:52:32 +01:00
|
|
|
import io.netty.handler.codec.http.HttpUtil;
|
2016-04-12 14:22:41 +02:00
|
|
|
import io.netty.util.internal.UnstableApi;
|
2015-01-20 01:48:11 +01:00
|
|
|
|
2015-12-17 07:52:32 +01:00
|
|
|
import static io.netty.handler.codec.http2.Http2Error.INTERNAL_ERROR;
|
|
|
|
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
|
|
|
|
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
|
2017-09-15 09:19:30 +02:00
|
|
|
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
|
2015-12-17 07:52:32 +01:00
|
|
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
|
|
|
|
2015-01-20 01:48:11 +01:00
|
|
|
/**
|
|
|
|
* 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-16#section-8.1.">HTTP/2 Spec Message Flow</a>.
|
|
|
|
* <p>
|
|
|
|
* See {@link HttpToHttp2ConnectionHandler} to get translation from HTTP/1.x objects to HTTP/2 frames for writes.
|
|
|
|
*/
|
2016-04-12 14:22:41 +02:00
|
|
|
@UnstableApi
|
2015-01-20 01:48:11 +01:00
|
|
|
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().codeClass() == HttpStatusClass.INFORMATIONAL;
|
|
|
|
}
|
|
|
|
if (msg instanceof FullHttpRequest) {
|
|
|
|
return msg.headers().contains(HttpHeaderNames.EXPECT);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public FullHttpMessage copyIfNeeded(FullHttpMessage msg) {
|
|
|
|
if (msg instanceof FullHttpRequest) {
|
2016-04-14 10:31:48 +02:00
|
|
|
FullHttpRequest copy = ((FullHttpRequest) msg).replace(Unpooled.buffer(0));
|
2015-01-20 01:48:11 +01:00
|
|
|
copy.headers().remove(HttpHeaderNames.EXPECT);
|
|
|
|
return copy;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
private final int maxContentLength;
|
|
|
|
private final ImmediateSendDetector sendDetector;
|
2016-01-28 22:07:56 +01:00
|
|
|
private final Http2Connection.PropertyKey messageKey;
|
2015-01-20 01:48:11 +01:00
|
|
|
private final boolean propagateSettings;
|
2016-01-28 22:07:56 +01:00
|
|
|
protected final Http2Connection connection;
|
|
|
|
protected final boolean validateHttpHeaders;
|
2015-01-20 01:48:11 +01:00
|
|
|
|
2015-12-17 07:52:32 +01:00
|
|
|
protected InboundHttp2ToHttpAdapter(Http2Connection connection, int maxContentLength,
|
|
|
|
boolean validateHttpHeaders, boolean propagateSettings) {
|
2015-01-20 01:48:11 +01:00
|
|
|
|
2015-12-17 07:52:32 +01:00
|
|
|
checkNotNull(connection, "connection");
|
|
|
|
if (maxContentLength <= 0) {
|
|
|
|
throw new IllegalArgumentException("maxContentLength: " + maxContentLength + " (expected: > 0)");
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
2015-12-17 07:52:32 +01:00
|
|
|
this.connection = connection;
|
|
|
|
this.maxContentLength = maxContentLength;
|
|
|
|
this.validateHttpHeaders = validateHttpHeaders;
|
|
|
|
this.propagateSettings = propagateSettings;
|
2015-01-20 01:48:11 +01:00
|
|
|
sendDetector = DEFAULT_SEND_DETECTOR;
|
2016-01-28 22:07:56 +01:00
|
|
|
messageKey = connection.newKey();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The stream is out of scope for the HTTP message flow and will no longer be tracked
|
|
|
|
* @param stream The stream to remove associated state with
|
|
|
|
* @param release {@code true} to call release on the value if it is present. {@code false} to not call release.
|
|
|
|
*/
|
|
|
|
protected final void removeMessage(Http2Stream stream, boolean release) {
|
|
|
|
FullHttpMessage msg = stream.removeProperty(messageKey);
|
|
|
|
if (release && msg != null) {
|
|
|
|
msg.release();
|
|
|
|
}
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2016-01-28 22:07:56 +01:00
|
|
|
* Get the {@link FullHttpMessage} associated with {@code stream}.
|
|
|
|
* @param stream The stream to get the associated state from
|
|
|
|
* @return The {@link FullHttpMessage} associated with {@code stream}.
|
2015-01-20 01:48:11 +01:00
|
|
|
*/
|
2016-01-28 22:07:56 +01:00
|
|
|
protected final FullHttpMessage getMessage(Http2Stream stream) {
|
|
|
|
return (FullHttpMessage) stream.getProperty(messageKey);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Make {@code message} be the state associated with {@code stream}.
|
|
|
|
* @param stream The stream which {@code message} is associated with.
|
|
|
|
* @param message The message which contains the HTTP semantics.
|
|
|
|
*/
|
|
|
|
protected final void putMessage(Http2Stream stream, FullHttpMessage message) {
|
|
|
|
FullHttpMessage previous = stream.setProperty(messageKey, message);
|
|
|
|
if (previous != message && previous != null) {
|
|
|
|
previous.release();
|
|
|
|
}
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2015-03-20 18:04:38 +01:00
|
|
|
public void onStreamRemoved(Http2Stream stream) {
|
2016-01-28 22:07:56 +01:00
|
|
|
removeMessage(stream, true);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set final headers and fire a channel read event
|
|
|
|
*
|
|
|
|
* @param ctx The context to fire the event on
|
|
|
|
* @param msg The message to send
|
2017-02-21 21:27:23 +01:00
|
|
|
* @param release {@code true} to call release on the value if it is present. {@code false} to not call release.
|
2016-01-28 22:07:56 +01:00
|
|
|
* @param stream the stream of the message which is being fired
|
2015-01-20 01:48:11 +01:00
|
|
|
*/
|
2016-01-28 22:07:56 +01:00
|
|
|
protected void fireChannelRead(ChannelHandlerContext ctx, FullHttpMessage msg, boolean release,
|
|
|
|
Http2Stream stream) {
|
|
|
|
removeMessage(stream, release);
|
2015-08-22 17:25:57 +02:00
|
|
|
HttpUtil.setContentLength(msg, msg.content().readableBytes());
|
2015-01-20 01:48:11 +01:00
|
|
|
ctx.fireChannelRead(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new {@link FullHttpMessage} based upon the current connection parameters
|
|
|
|
*
|
2016-01-28 22:07:56 +01:00
|
|
|
* @param stream The stream to create a message for
|
|
|
|
* @param headers The headers associated with {@code stream}
|
2015-01-20 01:48:11 +01:00
|
|
|
* @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>
|
2016-02-13 13:30:48 +01:00
|
|
|
* @param alloc The {@link ByteBufAllocator} to use to generate the content of the message
|
2015-01-20 01:48:11 +01:00
|
|
|
* @throws Http2Exception
|
|
|
|
*/
|
2016-02-13 13:30:48 +01:00
|
|
|
protected FullHttpMessage newMessage(Http2Stream stream, Http2Headers headers, boolean validateHttpHeaders,
|
|
|
|
ByteBufAllocator alloc)
|
2015-01-20 01:48:11 +01:00
|
|
|
throws Http2Exception {
|
2016-05-04 08:51:45 +02:00
|
|
|
return connection.isServer() ? HttpConversionUtil.toFullHttpRequest(stream.id(), headers, alloc,
|
2017-09-20 02:48:37 +02:00
|
|
|
validateHttpHeaders) : HttpConversionUtil.toFullHttpResponse(stream.id(), headers, alloc,
|
2016-02-13 13:30:48 +01:00
|
|
|
validateHttpHeaders);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Provides translation between HTTP/2 and HTTP header objects while ensuring the stream
|
|
|
|
* 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.
|
2016-01-28 22:07:56 +01:00
|
|
|
* @param stream The stream the {@code headers} apply to
|
2015-01-20 01:48:11 +01:00
|
|
|
* @param headers The headers to process
|
2016-01-28 22:07:56 +01:00
|
|
|
* @param endOfStream {@code true} if the {@code stream} has received the end of stream flag
|
2015-01-20 01:48:11 +01:00
|
|
|
* @param allowAppend
|
|
|
|
* <ul>
|
|
|
|
* <li>{@code true} if headers will be appended if the stream already exists.</li>
|
|
|
|
* <li>if {@code false} and the stream already exists this method returns {@code null}.</li>
|
|
|
|
* </ul>
|
|
|
|
* @param appendToTrailer
|
|
|
|
* <ul>
|
2016-01-28 22:07:56 +01:00
|
|
|
* <li>{@code true} if a message {@code stream} already exists then the headers
|
2015-01-20 01:48:11 +01:00
|
|
|
* should be added to the trailing headers.</li>
|
|
|
|
* <li>{@code false} then appends will be done to the initial headers.</li>
|
|
|
|
* </ul>
|
2016-01-28 22:07:56 +01:00
|
|
|
* @return The object used to track the stream corresponding to {@code stream}. {@code null} if
|
2015-01-20 01:48:11 +01:00
|
|
|
* {@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
|
|
|
|
*/
|
2016-01-28 22:07:56 +01:00
|
|
|
protected FullHttpMessage processHeadersBegin(ChannelHandlerContext ctx, Http2Stream stream, Http2Headers headers,
|
2015-01-20 01:48:11 +01:00
|
|
|
boolean endOfStream, boolean allowAppend, boolean appendToTrailer) throws Http2Exception {
|
2016-01-28 22:07:56 +01:00
|
|
|
FullHttpMessage msg = getMessage(stream);
|
|
|
|
boolean release = true;
|
2015-01-20 01:48:11 +01:00
|
|
|
if (msg == null) {
|
2016-02-13 13:30:48 +01:00
|
|
|
msg = newMessage(stream, headers, validateHttpHeaders, ctx.alloc());
|
2015-01-20 01:48:11 +01:00
|
|
|
} else if (allowAppend) {
|
2016-01-28 22:07:56 +01:00
|
|
|
release = false;
|
|
|
|
HttpConversionUtil.addHttp2ToHttpHeaders(stream.id(), headers, msg, appendToTrailer);
|
2015-01-20 01:48:11 +01:00
|
|
|
} else {
|
2016-01-28 22:07:56 +01:00
|
|
|
release = false;
|
2015-01-20 01:48:11 +01:00
|
|
|
msg = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2016-01-28 22:07:56 +01:00
|
|
|
fireChannelRead(ctx, msg, release, stream);
|
2015-01-20 01:48:11 +01:00
|
|
|
return copy;
|
|
|
|
}
|
|
|
|
|
|
|
|
return msg;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* After HTTP/2 headers have been processed by {@link #processHeadersBegin} this method either
|
|
|
|
* sends the result up the pipeline or retains the message for future processing.
|
|
|
|
*
|
|
|
|
* @param ctx The context for which this message has been received
|
2016-01-28 22:07:56 +01:00
|
|
|
* @param stream The stream the {@code objAccumulator} corresponds to
|
|
|
|
* @param msg The object which represents all headers/data for corresponding to {@code stream}
|
2015-01-20 01:48:11 +01:00
|
|
|
* @param endOfStream {@code true} if this is the last event for the stream
|
|
|
|
*/
|
2016-01-28 22:07:56 +01:00
|
|
|
private void processHeadersEnd(ChannelHandlerContext ctx, Http2Stream stream, FullHttpMessage msg,
|
|
|
|
boolean endOfStream) {
|
2015-01-20 01:48:11 +01:00
|
|
|
if (endOfStream) {
|
2016-01-28 22:07:56 +01:00
|
|
|
// Release if the msg from the map is different from the object being forwarded up the pipeline.
|
|
|
|
fireChannelRead(ctx, msg, getMessage(stream) != msg, stream);
|
2015-01-20 01:48:11 +01:00
|
|
|
} else {
|
2016-01-28 22:07:56 +01:00
|
|
|
putMessage(stream, msg);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream)
|
|
|
|
throws Http2Exception {
|
2016-01-28 22:07:56 +01:00
|
|
|
Http2Stream stream = connection.stream(streamId);
|
|
|
|
FullHttpMessage msg = getMessage(stream);
|
2015-01-20 01:48:11 +01:00
|
|
|
if (msg == null) {
|
2015-12-17 07:52:32 +01:00
|
|
|
throw connectionError(PROTOCOL_ERROR, "Data Frame received for unknown stream id %d", streamId);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
ByteBuf content = msg.content();
|
|
|
|
final int dataReadableBytes = data.readableBytes();
|
|
|
|
if (content.readableBytes() > maxContentLength - dataReadableBytes) {
|
|
|
|
throw connectionError(INTERNAL_ERROR,
|
|
|
|
"Content length exceeded max of %d for stream id %d", maxContentLength, streamId);
|
|
|
|
}
|
|
|
|
|
|
|
|
content.writeBytes(data, data.readerIndex(), dataReadableBytes);
|
|
|
|
|
|
|
|
if (endOfStream) {
|
2016-01-28 22:07:56 +01:00
|
|
|
fireChannelRead(ctx, msg, false, stream);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// All bytes have been processed.
|
|
|
|
return dataReadableBytes + padding;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding,
|
|
|
|
boolean endOfStream) throws Http2Exception {
|
2016-01-28 22:07:56 +01:00
|
|
|
Http2Stream stream = connection.stream(streamId);
|
|
|
|
FullHttpMessage msg = processHeadersBegin(ctx, stream, headers, endOfStream, true, true);
|
2015-01-20 01:48:11 +01:00
|
|
|
if (msg != null) {
|
2016-01-28 22:07:56 +01:00
|
|
|
processHeadersEnd(ctx, stream, msg, endOfStream);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency,
|
|
|
|
short weight, boolean exclusive, int padding, boolean endOfStream) throws Http2Exception {
|
2016-01-28 22:07:56 +01:00
|
|
|
Http2Stream stream = connection.stream(streamId);
|
|
|
|
FullHttpMessage msg = processHeadersBegin(ctx, stream, headers, endOfStream, true, true);
|
2015-01-20 01:48:11 +01:00
|
|
|
if (msg != null) {
|
2016-09-28 08:43:15 +02:00
|
|
|
// Add headers for dependency and weight.
|
|
|
|
// See https://github.com/netty/netty/issues/5866
|
|
|
|
if (streamDependency != Http2CodecUtil.CONNECTION_STREAM_ID) {
|
|
|
|
msg.headers().setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_DEPENDENCY_ID.text(),
|
|
|
|
streamDependency);
|
|
|
|
}
|
|
|
|
msg.headers().setShort(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), weight);
|
|
|
|
|
2016-01-28 22:07:56 +01:00
|
|
|
processHeadersEnd(ctx, stream, msg, endOfStream);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) throws Http2Exception {
|
2016-01-28 22:07:56 +01:00
|
|
|
Http2Stream stream = connection.stream(streamId);
|
|
|
|
FullHttpMessage msg = getMessage(stream);
|
2015-01-20 01:48:11 +01:00
|
|
|
if (msg != null) {
|
2016-01-28 22:07:56 +01:00
|
|
|
onRstStreamRead(stream, msg);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
2016-01-28 22:07:56 +01:00
|
|
|
ctx.fireExceptionCaught(Http2Exception.streamError(streamId, Http2Error.valueOf(errorCode),
|
|
|
|
"HTTP/2 to HTTP layer caught stream reset"));
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onPushPromiseRead(ChannelHandlerContext ctx, int streamId, int promisedStreamId,
|
|
|
|
Http2Headers headers, int padding) throws Http2Exception {
|
|
|
|
// A push promise should not be allowed to add headers to an existing stream
|
2016-01-28 22:07:56 +01:00
|
|
|
Http2Stream promisedStream = connection.stream(promisedStreamId);
|
2017-09-15 09:19:30 +02:00
|
|
|
if (headers.status() == null) {
|
|
|
|
// A PUSH_PROMISE frame has no Http response status.
|
|
|
|
// https://tools.ietf.org/html/rfc7540#section-8.2.1
|
|
|
|
// Server push is semantically equivalent to a server responding to a
|
|
|
|
// request; however, in this case, that request is also sent by the
|
|
|
|
// server, as a PUSH_PROMISE frame.
|
|
|
|
headers.status(OK.codeAsText());
|
|
|
|
}
|
2016-01-28 22:07:56 +01:00
|
|
|
FullHttpMessage msg = processHeadersBegin(ctx, promisedStream, headers, false, false, false);
|
2015-01-20 01:48:11 +01:00
|
|
|
if (msg == null) {
|
2015-12-17 07:52:32 +01:00
|
|
|
throw connectionError(PROTOCOL_ERROR, "Push Promise Frame received for pre-existing stream id %d",
|
2015-01-20 01:48:11 +01:00
|
|
|
promisedStreamId);
|
|
|
|
}
|
|
|
|
|
2015-08-22 17:25:57 +02:00
|
|
|
msg.headers().setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_PROMISE_ID.text(), streamId);
|
2016-10-03 18:36:18 +02:00
|
|
|
msg.headers().setShort(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(),
|
|
|
|
Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT);
|
2015-01-20 01:48:11 +01:00
|
|
|
|
2016-01-28 22:07:56 +01:00
|
|
|
processHeadersEnd(ctx, promisedStream, msg, false);
|
2015-01-20 01:48:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) throws Http2Exception {
|
|
|
|
if (propagateSettings) {
|
|
|
|
// Provide an interface for non-listeners to capture settings
|
|
|
|
ctx.fireChannelRead(settings);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-28 22:07:56 +01:00
|
|
|
/**
|
|
|
|
* Called if a {@code RST_STREAM} is received but we have some data for that stream.
|
|
|
|
*/
|
|
|
|
protected void onRstStreamRead(Http2Stream stream, FullHttpMessage msg) {
|
|
|
|
removeMessage(stream, true);
|
|
|
|
}
|
|
|
|
|
2015-01-20 01:48:11 +01:00
|
|
|
/**
|
|
|
|
* Allows messages to be sent up the pipeline before the next phase in the
|
|
|
|
* HTTP message flow is detected.
|
|
|
|
*/
|
|
|
|
private interface ImmediateSendDetector {
|
|
|
|
/**
|
|
|
|
* Determine if the response should be sent immediately, or wait for the end of the stream
|
|
|
|
*
|
|
|
|
* @param msg The response to test
|
|
|
|
* @return {@code true} if the message should be sent immediately
|
|
|
|
* {@code false) if we should wait for the end of the stream
|
|
|
|
*/
|
|
|
|
boolean mustSendImmediately(FullHttpMessage msg);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determine if a copy must be made after an immediate send happens.
|
|
|
|
* <p>
|
|
|
|
* An example of this use case is if a request is received
|
|
|
|
* with a 'Expect: 100-continue' header. The message will be sent immediately,
|
|
|
|
* and the data will be queued and sent at the end of the stream.
|
|
|
|
*
|
|
|
|
* @param msg The message which has just been sent due to {@link #mustSendImmediately(FullHttpMessage)}
|
|
|
|
* @return A modified copy of the {@code msg} or {@code null} if a copy is not needed.
|
|
|
|
*/
|
|
|
|
FullHttpMessage copyIfNeeded(FullHttpMessage msg);
|
|
|
|
}
|
|
|
|
}
|