/// <summary> /// Translate and add HTTP/2 headers to HTTP/1.x headers. /// </summary> /// <param name="streamId">The stream associated with <paramref name="sourceHeaders"/>.</param> /// <param name="sourceHeaders">The HTTP/2 headers to convert.</param> /// <param name="destinationMessage">The object which will contain the resulting HTTP/1.x headers.</param> /// <param name="addToTrailer"><c>true</c> to add to trailing headers. <c>false</c> to add to initial headers.</param> /// <exception cref="Http2Exception">If not all HTTP/2 headers can be translated to HTTP/1.x.</exception> public static void AddHttp2ToHttpHeaders(int streamId, IHttp2Headers sourceHeaders, IFullHttpMessage destinationMessage, bool addToTrailer) { AddHttp2ToHttpHeaders(streamId, sourceHeaders, addToTrailer ? destinationMessage.TrailingHeaders : destinationMessage.Headers, destinationMessage.ProtocolVersion, addToTrailer, destinationMessage is IHttpRequest); }
public override int OnDataRead(IChannelHandlerContext ctx, int streamId, IByteBuffer data, int padding, bool endOfStream) { IHttp2Stream stream = _connection.Stream(streamId); IFullHttpMessage msg = GetMessage(stream); if (msg is null) { ThrowHelper.ThrowConnectionError_DataFrameReceivedForUnknownStream(streamId); } var content = msg.Content; int dataReadableBytes = data.ReadableBytes; if (content.ReadableBytes > _maxContentLength - dataReadableBytes) { ThrowHelper.ThrowConnectionError_ContentLengthExceededMax(_maxContentLength, streamId); } _ = content.WriteBytes(data, data.ReaderIndex, dataReadableBytes); if (endOfStream) { FireChannelRead(ctx, msg, false, stream); } // All bytes have been processed. return(dataReadableBytes + padding); }
/// <summary> /// Provides translation between HTTP/2 and HTTP header objects while ensuring the stream /// is in a valid state for additional headers. /// </summary> /// <param name="ctx">The context for which this message has been received. /// Used to send informational header if detected.</param> /// <param name="stream">The stream the <paramref name="headers"/> apply to</param> /// <param name="headers">The headers to process</param> /// <param name="endOfStream"><c>true</c> if the <paramref name="stream"/> has received the end of stream flag</param> /// <param name="allowAppend"><c>true</c> if headers will be appended if the stream already exists. /// <para>if <c>false</c> and the stream already exists this method returns <c>null</c>.</para></param> /// <param name="appendToTrailer"><c>true</c> if a message <paramref name="stream"/> already exists then the headers /// should be added to the trailing headers. /// <para><c>false</c> then appends will be done to the initial headers.</para></param> /// <returns>The object used to track the stream corresponding to <paramref name="stream"/>. <c>null</c> if /// <paramref name="allowAppend"/> is <c>false</c> and the stream already exists.</returns> /// <exception cref="Http2Exception">If the stream id is not in the correct state to process the headers request</exception> protected virtual IFullHttpMessage ProcessHeadersBegin(IChannelHandlerContext ctx, IHttp2Stream stream, IHttp2Headers headers, bool endOfStream, bool allowAppend, bool appendToTrailer) { IFullHttpMessage msg = GetMessage(stream); var release = true; if (msg is null) { msg = NewMessage(stream, headers, _validateHttpHeaders, ctx.Allocator); } 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 IFullHttpMessage copy = endOfStream ? null : _sendDetector.CopyIfNeeded(ctx.Allocator, msg); FireChannelRead(ctx, msg, release, stream); return(copy); } return(msg); }
public override void OnPushPromiseRead(IChannelHandlerContext ctx, int streamId, int promisedStreamId, IHttp2Headers headers, int padding) { // A push promise should not be allowed to add headers to an existing stream IHttp2Stream promisedStream = _connection.Stream(promisedStreamId); if (headers.Status is 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 = HttpResponseStatus.OK.CodeAsText; } IFullHttpMessage msg = ProcessHeadersBegin(ctx, promisedStream, headers, false, false, false); if (msg is null) { ThrowHelper.ThrowConnectionError_PushPromiseFrameReceivedForPreExistingStreamId(promisedStreamId); } _ = msg.Headers.SetInt(HttpConversionUtil.ExtensionHeaderNames.StreamPromiseId, streamId); _ = msg.Headers.SetShort(HttpConversionUtil.ExtensionHeaderNames.StreamWeight, Http2CodecUtil.DefaultPriorityWeight); ProcessHeadersEnd(ctx, promisedStream, msg, false); }
// note that this may behave strangely when used for the initial upgrade // message when using h2c, since that message is ineligible for flow // control, but there is not yet an API for signaling that. internal static void Handle(IChannelHandlerContext ctx, IHttp2Connection connection, IHttp2FrameListener listener, IFullHttpMessage message) { try { int streamId = GetStreamId(connection, message.Headers); IHttp2Stream stream = connection.Stream(streamId); if (stream is null) { stream = connection.Remote.CreateStream(streamId, false); } _ = message.Headers.Set(HttpConversionUtil.ExtensionHeaderNames.Scheme, HttpScheme.Http.Name); IHttp2Headers messageHeaders = HttpConversionUtil.ToHttp2Headers(message, true); var hasContent = message.Content.IsReadable(); var hasTrailers = !message.TrailingHeaders.IsEmpty; listener.OnHeadersRead(ctx, streamId, messageHeaders, 0, !(hasContent || hasTrailers)); if (hasContent) { _ = listener.OnDataRead(ctx, streamId, message.Content, 0, !hasTrailers); } if (hasTrailers) { IHttp2Headers headers = HttpConversionUtil.ToHttp2Headers(message.TrailingHeaders, true); listener.OnHeadersRead(ctx, streamId, headers, 0, true); } _ = stream.CloseRemoteSide(); } finally { _ = message.Release(); } }
/// <summary> /// The stream is out of scope for the HTTP message flow and will no longer be tracked /// </summary> /// <param name="stream">The stream to remove associated state with</param> /// <param name="release"><c>true</c> to call release on the value if it is present. <c>false</c> to not call release.</param> protected void RemoveMessage(IHttp2Stream stream, bool release) { IFullHttpMessage msg = stream.RemoveProperty <IFullHttpMessage>(_messageKey); if (release && msg is object) { _ = msg.Release(); } }
/// <summary> /// Make <paramref name="message"/> be the state associated with <paramref name="stream"/>. /// </summary> /// <param name="stream">The stream which <paramref name="message"/> is associated with.</param> /// <param name="message">The message which contains the HTTP semantics.</param> protected void PutMessage(IHttp2Stream stream, IFullHttpMessage message) { IFullHttpMessage previous = stream.SetProperty <IFullHttpMessage>(_messageKey, message); if (previous != message && previous is object) { _ = previous.Release(); } }
public override void OnHeadersRead(IChannelHandlerContext ctx, int streamId, IHttp2Headers headers, int padding, bool endOfStream) { IHttp2Stream stream = _connection.Stream(streamId); IFullHttpMessage msg = ProcessHeadersBegin(ctx, stream, headers, endOfStream, true, true); if (msg is object) { ProcessHeadersEnd(ctx, stream, msg, endOfStream); } }
public IFullHttpMessage CopyIfNeeded(IByteBufferAllocator allocator, IFullHttpMessage msg) { if (msg is IFullHttpRequest request) { var copy = (IFullHttpRequest)request.Replace(allocator.Buffer(0)); _ = copy.Headers.Remove(HttpHeaderNames.Expect); return(copy); } return(null); }
public override void OnRstStreamRead(IChannelHandlerContext ctx, int streamId, Http2Error errorCode) { IHttp2Stream stream = _connection.Stream(streamId); IFullHttpMessage msg = GetMessage(stream); if (msg is object) { OnRstStreamRead(stream, msg); } _ = ctx.FireExceptionCaught(ThrowHelper.GetStreamError_Http2ToHttpLayerCaughtStreamReset(streamId, errorCode)); }
static void AppendFullCommon(StringBuilder buf, IFullHttpMessage msg) { _ = buf.Append(StringUtil.SimpleClassName(msg)); _ = buf.Append("(decodeResult: "); _ = buf.Append(msg.Result); _ = buf.Append(", version: "); _ = buf.Append(msg.ProtocolVersion); _ = buf.Append(", content: "); _ = buf.Append(msg.Content); _ = buf.Append(')'); _ = buf.Append(StringUtil.Newline); }
public bool MustSendImmediately(IFullHttpMessage msg) { switch (msg) { case IFullHttpResponse response: return(response.Status.CodeClass == HttpStatusClass.Informational); case IFullHttpRequest _: return(msg.Headers.Contains(HttpHeaderNames.Expect)); default: return(false); } }
public override void ChannelRead(IChannelHandlerContext context, object message) { IFullHttpMessage fm = message as IFullHttpMessage; try { Task.Run(async() => { HandleProxy.OnEnd(context, message); } ); } catch (Exception ex) { Console.WriteLine(ex.Message); } }
public override void OnHeadersRead(IChannelHandlerContext ctx, int streamId, IHttp2Headers headers, int streamDependency, short weight, bool exclusive, int padding, bool endOfStream) { IHttp2Stream stream = _connection.Stream(streamId); IFullHttpMessage msg = ProcessHeadersBegin(ctx, stream, headers, endOfStream, true, true); if (msg is object) { // Add headers for dependency and weight. // See https://github.com/netty/netty/issues/5866 if (streamDependency != Http2CodecUtil.ConnectionStreamId) { _ = msg.Headers.SetInt(HttpConversionUtil.ExtensionHeaderNames.StreamDependencyId, streamDependency); } _ = msg.Headers.SetShort(HttpConversionUtil.ExtensionHeaderNames.StreamWeight, weight); ProcessHeadersEnd(ctx, stream, msg, endOfStream); } }
public void ServerResponseHeaderInformational() { this.BootstrapEnv(1, 2, 1, 2, 1); IFullHttpMessage request = new DefaultFullHttpRequest(DotNetty.Codecs.Http.HttpVersion.Http11, HttpMethod.Put, "/info/test", true); HttpHeaders httpHeaders = request.Headers; httpHeaders.SetInt(HttpConversionUtil.ExtensionHeaderNames.StreamId, 3); httpHeaders.Set(HttpHeaderNames.Expect, HttpHeaderValues.Continue); httpHeaders.SetInt(HttpHeaderNames.ContentLength, 0); httpHeaders.SetShort(HttpConversionUtil.ExtensionHeaderNames.StreamWeight, 16); IHttp2Headers http2Headers = new DefaultHttp2Headers() { Method = new AsciiString("PUT"), Path = new AsciiString("/info/test") }; http2Headers.Set(new AsciiString(HttpHeaderNames.Expect.ToString()), new AsciiString(HttpHeaderValues.Continue.ToString())); IFullHttpMessage response = new DefaultFullHttpResponse(DotNetty.Codecs.Http.HttpVersion.Http11, HttpResponseStatus.Continue); string text = "a big payload"; IByteBuffer payload = Unpooled.CopiedBuffer(Encoding.UTF8.GetBytes(text)); IFullHttpMessage request2 = (IFullHttpMessage)request.Replace(payload); IFullHttpMessage response2 = new DefaultFullHttpResponse(DotNetty.Codecs.Http.HttpVersion.Http11, HttpResponseStatus.OK); try { Http2TestUtil.RunInChannel(this.clientChannel, () => { this.clientHandler.Encoder.WriteHeadersAsync(this.CtxClient(), 3, http2Headers, 0, false, this.NewPromiseClient()); this.clientChannel.Flush(); }); this.AwaitRequests(); httpHeaders = response.Headers; httpHeaders.SetInt(HttpConversionUtil.ExtensionHeaderNames.StreamId, 3); httpHeaders.SetInt(HttpHeaderNames.ContentLength, 0); IHttp2Headers http2HeadersResponse = new DefaultHttp2Headers() { Status = new AsciiString("100") }; Http2TestUtil.RunInChannel(this.serverConnectedChannel, () => { this.serverHandler.Encoder.WriteHeadersAsync(this.CtxServer(), 3, http2HeadersResponse, 0, false, this.NewPromiseServer()); this.serverConnectedChannel.Flush(); }); this.AwaitResponses(); httpHeaders = request2.Headers; httpHeaders.SetInt(HttpHeaderNames.ContentLength, text.Length); httpHeaders.Remove(HttpHeaderNames.Expect); Http2TestUtil.RunInChannel(this.clientChannel, () => { this.clientHandler.Encoder.WriteDataAsync(this.CtxClient(), 3, payload.RetainedDuplicate(), 0, true, this.NewPromiseClient()); this.clientChannel.Flush(); }); this.AwaitRequests2(); httpHeaders = response2.Headers; httpHeaders.SetInt(HttpConversionUtil.ExtensionHeaderNames.StreamId, 3); httpHeaders.SetInt(HttpHeaderNames.ContentLength, 0); httpHeaders.SetShort(HttpConversionUtil.ExtensionHeaderNames.StreamWeight, 16); IHttp2Headers http2HeadersResponse2 = new DefaultHttp2Headers() { Status = new AsciiString("200") }; Http2TestUtil.RunInChannel(this.serverConnectedChannel, () => { this.serverHandler.Encoder.WriteHeadersAsync(this.CtxServer(), 3, http2HeadersResponse2, 0, true, this.NewPromiseServer()); this.serverConnectedChannel.Flush(); }); this.AwaitResponses2(); var requestCaptor = new ArgumentCaptor <IFullHttpMessage>(); this.serverListener.Verify(x => x.MessageReceived(It.Is <IHttpObject>(v => requestCaptor.Capture((IFullHttpMessage)v))), Times.Exactly(2)); this.capturedRequests = requestCaptor.GetAllValues(); Assert.Equal(2, this.capturedRequests.Count); // We do not expect to have this header in the captured request so remove it now. Assert.NotNull(request.Headers.Remove((AsciiString)"x-http2-stream-weight")); Assert.Equal(request, (IFullHttpRequest)this.capturedRequests[0]); Assert.Equal(request2, this.capturedRequests[1]); var responseCaptor = new ArgumentCaptor <IFullHttpMessage>(); this.clientListener.Verify(x => x.MessageReceived(It.Is <IHttpObject>(v => responseCaptor.Capture((IFullHttpMessage)v))), Times.Exactly(2)); this.capturedResponses = responseCaptor.GetAllValues(); Assert.Equal(2, this.capturedResponses.Count); Assert.Equal(response, this.capturedResponses[0]); Assert.Equal(response2, this.capturedResponses[1]); } finally { request.Release(); request2.Release(); response.Release(); response2.Release(); } }
/// <summary> /// Set final headers and fire a channel read event /// </summary> /// <param name="ctx">The context to fire the event on</param> /// <param name="msg">The message to send</param> /// <param name="release"><c>true</c> to call release on the value if it is present. <c>false</c> to not call release.</param> /// <param name="stream">the stream of the message which is being fired</param> protected virtual void FireChannelRead(IChannelHandlerContext ctx, IFullHttpMessage msg, bool release, IHttp2Stream stream) { RemoveMessage(stream, release); HttpUtil.SetContentLength(msg, msg.Content.ReadableBytes); _ = ctx.FireChannelRead(msg); }
/// <summary> /// Called if a <c>RST_STREAM</c> is received but we have some data for that stream. /// </summary> /// <param name="stream"></param> /// <param name="msg"></param> protected virtual void OnRstStreamRead(IHttp2Stream stream, IFullHttpMessage msg) { RemoveMessage(stream, true); }
/// <summary> /// After HTTP/2 headers have been processed by <see cref="ProcessHeadersBegin"/> this method either /// sends the result up the pipeline or retains the message for future processing. /// </summary> /// <param name="ctx">The context for which this message has been received</param> /// <param name="stream">The stream the <paramref name="msg"/> corresponds to</param> /// <param name="msg">The object which represents all headers/data for corresponding to <paramref name="stream"/>.</param> /// <param name="endOfStream"><c>true</c> if this is the last event for the stream</param> private void ProcessHeadersEnd(IChannelHandlerContext ctx, IHttp2Stream stream, IFullHttpMessage msg, bool 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); } }