All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.netty.handler.codec.http2.InboundHttp2ToHttpAdapter Maven / Gradle / Ivy

The newest version!
/*
 * 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.buffer.ByteBufAllocator;
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;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.util.internal.UnstableApi;

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;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.util.internal.ObjectUtil.checkNotNull;

/**
 * This adapter provides just header/data events from the HTTP message flow defined
 * in [RFC 7540], Section 8.1.
 * 

* See {@link HttpToHttp2ConnectionHandler} to get translation from HTTP/1.x objects to HTTP/2 frames for writes. */ @UnstableApi 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(ByteBufAllocator allocator, FullHttpMessage msg) { if (msg instanceof FullHttpRequest) { FullHttpRequest copy = ((FullHttpRequest) msg).replace(allocator.buffer(0)); copy.headers().remove(HttpHeaderNames.EXPECT); return copy; } return null; } }; private final int maxContentLength; private final ImmediateSendDetector sendDetector; private final Http2Connection.PropertyKey messageKey; private final boolean propagateSettings; protected final Http2Connection connection; protected final boolean validateHttpHeaders; protected InboundHttp2ToHttpAdapter(Http2Connection connection, int maxContentLength, boolean validateHttpHeaders, boolean propagateSettings) { if (maxContentLength <= 0) { throw new IllegalArgumentException("maxContentLength: " + maxContentLength + " (expected: > 0)"); } this.connection = checkNotNull(connection, "connection"); this.maxContentLength = maxContentLength; this.validateHttpHeaders = validateHttpHeaders; this.propagateSettings = propagateSettings; sendDetector = DEFAULT_SEND_DETECTOR; 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(); } } /** * 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}. */ 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(); } } @Override public void onStreamRemoved(Http2Stream stream) { removeMessage(stream, true); } /** * Set final headers and fire a channel read event * * @param ctx The context to fire the event on * @param msg The message to send * @param release {@code true} to call release on the value if it is present. {@code false} to not call release. * @param stream the stream of the message which is being fired */ protected void fireChannelRead(ChannelHandlerContext ctx, FullHttpMessage msg, boolean release, Http2Stream stream) { removeMessage(stream, release); HttpUtil.setContentLength(msg, msg.content().readableBytes()); ctx.fireChannelRead(msg); } /** * Create a new {@link FullHttpMessage} based upon the current connection parameters * * @param stream The stream to create a message for * @param headers The headers associated with {@code stream} * @param validateHttpHeaders *

    *
  • {@code true} to validate HTTP headers in the http-codec
  • *
  • {@code false} not to validate HTTP headers in the http-codec
  • *
* @param alloc The {@link ByteBufAllocator} to use to generate the content of the message * @throws Http2Exception */ protected FullHttpMessage newMessage(Http2Stream stream, Http2Headers headers, boolean validateHttpHeaders, ByteBufAllocator alloc) throws Http2Exception { return connection.isServer() ? HttpConversionUtil.toFullHttpRequest(stream.id(), headers, alloc, validateHttpHeaders) : HttpConversionUtil.toFullHttpResponse(stream.id(), headers, alloc, validateHttpHeaders); } /** * 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. * @param stream The stream the {@code headers} apply to * @param headers The headers to process * @param endOfStream {@code true} if the {@code stream} 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 the stream already exists this method returns {@code null}.
  • *
* @param appendToTrailer *
    *
  • {@code true} if a message {@code stream} already exists then the headers * should be added to the trailing headers.
  • *
  • {@code false} then appends will be done to the initial headers.
  • *
* @return The object used to track the stream corresponding to {@code stream}. {@code null} if * {@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 */ protected FullHttpMessage processHeadersBegin(ChannelHandlerContext ctx, Http2Stream stream, Http2Headers headers, boolean endOfStream, boolean allowAppend, boolean appendToTrailer) throws Http2Exception { FullHttpMessage msg = getMessage(stream); boolean release = true; if (msg == null) { msg = newMessage(stream, headers, validateHttpHeaders, ctx.alloc()); } else if (allowAppend) { release = false; HttpConversionUtil.addHttp2ToHttpHeaders(stream.id(), headers, msg, appendToTrailer); } else { release = false; 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(ctx.alloc(), msg); fireChannelRead(ctx, msg, release, stream); 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 * @param stream The stream the {@code objAccumulator} corresponds to * @param msg The object which represents all headers/data for corresponding to {@code stream} * @param endOfStream {@code true} if this is the last event for the stream */ private void processHeadersEnd(ChannelHandlerContext ctx, Http2Stream stream, FullHttpMessage msg, boolean endOfStream) { if (endOfStream) { // Release if the msg from the map is different from the object being forwarded up the pipeline. fireChannelRead(ctx, msg, getMessage(stream) != msg, stream); } else { putMessage(stream, msg); } } @Override public int onDataRead(ChannelHandlerContext ctx, int streamId, ByteBuf data, int padding, boolean endOfStream) throws Http2Exception { Http2Stream stream = connection.stream(streamId); FullHttpMessage msg = getMessage(stream); if (msg == null) { throw connectionError(PROTOCOL_ERROR, "Data Frame received for unknown stream id %d", streamId); } 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) { fireChannelRead(ctx, msg, false, stream); } // All bytes have been processed. return dataReadableBytes + padding; } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int padding, boolean endOfStream) throws Http2Exception { Http2Stream stream = connection.stream(streamId); FullHttpMessage msg = processHeadersBegin(ctx, stream, headers, endOfStream, true, true); if (msg != null) { processHeadersEnd(ctx, stream, msg, endOfStream); } } @Override public void onHeadersRead(ChannelHandlerContext ctx, int streamId, Http2Headers headers, int streamDependency, short weight, boolean exclusive, int padding, boolean endOfStream) throws Http2Exception { Http2Stream stream = connection.stream(streamId); FullHttpMessage msg = processHeadersBegin(ctx, stream, headers, endOfStream, true, true); if (msg != null) { // 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); processHeadersEnd(ctx, stream, msg, endOfStream); } } @Override public void onRstStreamRead(ChannelHandlerContext ctx, int streamId, long errorCode) throws Http2Exception { Http2Stream stream = connection.stream(streamId); FullHttpMessage msg = getMessage(stream); if (msg != null) { onRstStreamRead(stream, msg); } ctx.fireExceptionCaught(Http2Exception.streamError(streamId, Http2Error.valueOf(errorCode), "HTTP/2 to HTTP layer caught stream reset")); } @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 Http2Stream promisedStream = connection.stream(promisedStreamId); 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()); } FullHttpMessage msg = processHeadersBegin(ctx, promisedStream, headers, false, false, false); if (msg == null) { throw connectionError(PROTOCOL_ERROR, "Push Promise Frame received for pre-existing stream id %d", promisedStreamId); } msg.headers().setInt(HttpConversionUtil.ExtensionHeaderNames.STREAM_PROMISE_ID.text(), streamId); msg.headers().setShort(HttpConversionUtil.ExtensionHeaderNames.STREAM_WEIGHT.text(), Http2CodecUtil.DEFAULT_PRIORITY_WEIGHT); processHeadersEnd(ctx, promisedStream, msg, false); } @Override public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) throws Http2Exception { if (propagateSettings) { // Provide an interface for non-listeners to capture settings ctx.fireChannelRead(settings); } } /** * 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); } /** * 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. *

* 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 allocator The {@link ByteBufAllocator} that can be used to allocate * @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(ByteBufAllocator allocator, FullHttpMessage msg); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy