/// <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); }
// 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> /// Create a new <see cref="IFullHttpMessage"/> based upon the current connection parameters /// </summary> /// <param name="stream">The stream to create a message for</param> /// <param name="headers">The headers associated with <paramref name="stream"/>.</param> /// <param name="validateHttpHeaders"><c>true</c> to validate HTTP headers in the http-codec /// <para><c>false</c> not to validate HTTP headers in the http-codec</para></param> /// <param name="alloc">The <see cref="IByteBufferAllocator"/> to use to generate the content of the message</param> /// <returns></returns> protected virtual IFullHttpMessage NewMessage(IHttp2Stream stream, IHttp2Headers headers, bool validateHttpHeaders, IByteBufferAllocator alloc) { if (_connection.IsServer) { return(HttpConversionUtil.ToFullHttpRequest(stream.Id, headers, alloc, validateHttpHeaders)); } return(HttpConversionUtil.ToFullHttpResponse(stream.Id, headers, alloc, validateHttpHeaders)); }
/// <summary> /// Handles conversion of <see cref="IHttpMessage"/> and <see cref="IHttpContent"/> to HTTP/2 frames. /// </summary> public override void Write(IChannelHandlerContext ctx, object msg, IPromise promise) { var httpMsg = msg as IHttpMessage; var contentMsg = msg as IHttpContent; if (httpMsg is null && contentMsg is null) { _ = ctx.WriteAsync(msg, promise); return; } var release = true; var promiseAggregator = new SimplePromiseAggregator(promise); try { var encoder = Encoder; var endStream = false; if (httpMsg is object) { // Provide the user the opportunity to specify the streamId _currentStreamId = GetStreamId(httpMsg.Headers); // Convert and write the headers. var http2Headers = HttpConversionUtil.ToHttp2Headers(httpMsg, _validateHeaders); endStream = msg is IFullHttpMessage fullHttpMsg && !fullHttpMsg.Content.IsReadable(); WriteHeaders(ctx, encoder, _currentStreamId, httpMsg.Headers, http2Headers, endStream, promiseAggregator); } if (!endStream && contentMsg is object) { var isLastContent = false; HttpHeaders trailers = EmptyHttpHeaders.Default; IHttp2Headers http2Trailers = EmptyHttp2Headers.Instance; if (msg is ILastHttpContent lastContentMsg) { isLastContent = true; // Convert any trailing headers. trailers = lastContentMsg.TrailingHeaders; http2Trailers = HttpConversionUtil.ToHttp2Headers(trailers, _validateHeaders); } // Write the data var content = contentMsg.Content; endStream = isLastContent && trailers.IsEmpty; _ = encoder.WriteDataAsync(ctx, _currentStreamId, content, 0, endStream, promiseAggregator.NewPromise()); release = false; if (!trailers.IsEmpty) { // Write trailing headers. WriteHeaders(ctx, encoder, _currentStreamId, trailers, http2Trailers, true, promiseAggregator); } } } catch (Exception t) { OnError(ctx, true, t); promiseAggregator.SetException(t); } finally { if (release) { _ = ReferenceCountUtil.Release(msg); } _ = promiseAggregator.DoneAllocatingPromises(); } }