public void CanSendGoAwayUsingVoidPromise() { _handler = NewHandler(); IByteBuffer data = DummyData(); var errorCode = Http2Error.InternalError; _handler = NewHandler(); var cause = new Http2RuntimeException("fake exception"); _frameWriter .Setup(x => x.WriteGoAwayAsync( It.IsAny <IChannelHandlerContext>(), It.IsAny <int>(), It.IsAny <Http2Error>(), It.IsAny <IByteBuffer>(), It.IsAny <IPromise>() )) .Returns <IChannelHandlerContext, int, Http2Error, IByteBuffer, IPromise>((c, id, err, buf, p) => { Assert.False(p.IsVoid); // This is what DefaultHttp2FrameWriter does... I hate mocking :-(. var aggregatedPromise = new SimplePromiseAggregator(p); aggregatedPromise.NewPromise(); aggregatedPromise.DoneAllocatingPromises(); aggregatedPromise.SetException(cause); return(aggregatedPromise.Task); }); _handler.GoAwayAsync(_ctx.Object, STREAM_ID, errorCode, data, Http2TestUtil.NewVoidPromise(_channel.Object)); _pipeline.Verify(x => x.FireExceptionCaught(It.Is <Exception>(v => ReferenceEquals(v, cause)))); }
private static void WriteHeaders(IChannelHandlerContext ctx, IHttp2ConnectionEncoder encoder, int streamId, HttpHeaders headers, IHttp2Headers http2Headers, bool endStream, SimplePromiseAggregator promiseAggregator) { int dependencyId = headers.GetInt( HttpConversionUtil.ExtensionHeaderNames.StreamDependencyId, 0); short weight = headers.GetShort( HttpConversionUtil.ExtensionHeaderNames.StreamWeight, Http2CodecUtil.DefaultPriorityWeight); _ = encoder.WriteHeadersAsync(ctx, streamId, http2Headers, dependencyId, weight, false, 0, endStream, promiseAggregator.NewPromise()); }
public virtual Task WriteGoAwayAsync(IChannelHandlerContext ctx, int lastStreamId, Http2Error errorCode, IByteBuffer debugData, IPromise promise) { SimplePromiseAggregator promiseAggregator = new SimplePromiseAggregator(promise); try { if (lastStreamId < 0) { ThrowHelper.ThrowArgumentException_PositiveOrZero(ExceptionArgument.LastStreamId); } VerifyErrorCode((long)errorCode); int payloadLength = 8 + debugData.ReadableBytes; IByteBuffer buf = ctx.Allocator.Buffer(Http2CodecUtil.GoAwayFrameHeaderLength); // Assume nothing below will throw until buf is written. That way we don't have to take care of ownership // in the catch block. Http2CodecUtil.WriteFrameHeaderInternal(buf, payloadLength, Http2FrameTypes.GoAway, new Http2Flags(), 0); _ = buf.WriteInt(lastStreamId); _ = buf.WriteInt((int)errorCode); _ = ctx.WriteAsync(buf, promiseAggregator.NewPromise()); } catch (Exception t) { try { _ = debugData.Release(); } finally { promiseAggregator.SetException(t); _ = promiseAggregator.DoneAllocatingPromises(); } return(promiseAggregator.Task); } try { _ = ctx.WriteAsync(debugData, promiseAggregator.NewPromise()); } catch (Exception t) { promiseAggregator.SetException(t); } _ = promiseAggregator.DoneAllocatingPromises(); return(promiseAggregator.Task); }
public virtual Task WriteFrameAsync(IChannelHandlerContext ctx, Http2FrameTypes frameType, int streamId, Http2Flags flags, IByteBuffer payload, IPromise promise) { SimplePromiseAggregator promiseAggregator = new SimplePromiseAggregator(promise); try { if (streamId < 0) { ThrowHelper.ThrowArgumentException_PositiveOrZero(ExceptionArgument.StreamID); } IByteBuffer buf = ctx.Allocator.Buffer(Http2CodecUtil.FrameHeaderLength); // Assume nothing below will throw until buf is written. That way we don't have to take care of ownership // in the catch block. Http2CodecUtil.WriteFrameHeaderInternal(buf, payload.ReadableBytes, frameType, flags, streamId); _ = ctx.WriteAsync(buf, promiseAggregator.NewPromise()); } catch (Exception t) { try { _ = payload.Release(); } finally { promiseAggregator.SetException(t); _ = promiseAggregator.DoneAllocatingPromises(); } return(promiseAggregator.Task); } try { _ = ctx.WriteAsync(payload, promiseAggregator.NewPromise()); } catch (Exception t) { promiseAggregator.SetException(t); } _ = promiseAggregator.DoneAllocatingPromises(); return(promiseAggregator.Task); }
/// <summary> /// Writes as many continuation frames as needed until <paramref name="headerBlock"/> are consumed. /// </summary> /// <param name="ctx"></param> /// <param name="streamId"></param> /// <param name="headerBlock"></param> /// <param name="promiseAggregator"></param> /// <returns></returns> Task WriteContinuationFramesAsync(IChannelHandlerContext ctx, int streamId, IByteBuffer headerBlock, SimplePromiseAggregator promiseAggregator) { Http2Flags flags = new Http2Flags(); if (headerBlock.IsReadable()) { // The frame header (and padding) only changes on the last frame, so allocate it once and re-use int fragmentReadableBytes = Math.Min(headerBlock.ReadableBytes, _maxFrameSize); IByteBuffer buf = ctx.Allocator.Buffer(Http2CodecUtil.ContinuationFrameHeaderLength); Http2CodecUtil.WriteFrameHeaderInternal(buf, fragmentReadableBytes, Http2FrameTypes.Continuation, flags, streamId); do { fragmentReadableBytes = Math.Min(headerBlock.ReadableBytes, _maxFrameSize); IByteBuffer fragment = headerBlock.ReadRetainedSlice(fragmentReadableBytes); if (headerBlock.IsReadable()) { _ = ctx.WriteAsync(buf.Retain(), promiseAggregator.NewPromise()); } else { // The frame header is different for the last frame, so re-allocate and release the old buffer flags = flags.EndOfHeaders(true); _ = buf.Release(); buf = ctx.Allocator.Buffer(Http2CodecUtil.ContinuationFrameHeaderLength); Http2CodecUtil.WriteFrameHeaderInternal(buf, fragmentReadableBytes, Http2FrameTypes.Continuation, flags, streamId); _ = ctx.WriteAsync(buf, promiseAggregator.NewPromise()); } _ = ctx.WriteAsync(fragment, promiseAggregator.NewPromise()); }while (headerBlock.IsReadable()); } return(promiseAggregator.Task); }
public virtual Task WriteSettingsAckAsync(IChannelHandlerContext ctx, IPromise promise) { if (_outstandingRemoteSettingsQueue is null) { return(_frameWriter.WriteSettingsAckAsync(ctx, promise)); } Http2Settings settings = _outstandingRemoteSettingsQueue.RemoveFirst(); if (settings is null) { _ = promise.TrySetException(ThrowHelper.GetConnectionError_attempted_to_write_a_SETTINGS_ACK_with_no_pending_SETTINGS()); return(promise.Task); } SimplePromiseAggregator aggregator = new SimplePromiseAggregator(promise); // , ctx.channel(), ctx.executor() // Acknowledge receipt of the settings. We should do this before we process the settings to ensure our // remote peer applies these settings before any subsequent frames that we may send which depend upon // these new settings. See https://github.com/netty/netty/issues/6520. _ = _frameWriter.WriteSettingsAckAsync(ctx, aggregator.NewPromise()); // We create a "new promise" to make sure that status from both the write and the application are taken into // account independently. var applySettingsPromise = aggregator.NewPromise(); try { RemoteSettings(settings); applySettingsPromise.Complete(); } catch (Exception e) { applySettingsPromise.SetException(e); _lifecycleManager.OnError(ctx, true, e); } return(aggregator.DoneAllocatingPromises().Task); }
/// <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(); } }
public virtual Task WriteDataAsync(IChannelHandlerContext ctx, int streamId, IByteBuffer data, int padding, bool endOfStream, IPromise promise) { SimplePromiseAggregator promiseAggregator = new SimplePromiseAggregator(promise); IByteBuffer frameHeader = null; try { if ((uint)(streamId - 1) > SharedConstants.TooBigOrNegative) { ThrowHelper.ThrowArgumentException_Positive(ExceptionArgument.StreamID); } Http2CodecUtil.VerifyPadding(padding); int remainingData = data.ReadableBytes; Http2Flags flags = new Http2Flags(); _ = flags.EndOfStream(false); _ = flags.PaddingPresent(false); // Fast path to write frames of payload size maxFrameSize first. if (remainingData > _maxFrameSize) { frameHeader = ctx.Allocator.Buffer(Http2CodecUtil.FrameHeaderLength); Http2CodecUtil.WriteFrameHeaderInternal(frameHeader, _maxFrameSize, Http2FrameTypes.Data, flags, streamId); do { // Write the header. _ = ctx.WriteAsync(frameHeader.RetainedSlice(), promiseAggregator.NewPromise()); // Write the payload. _ = ctx.WriteAsync(data.ReadRetainedSlice(_maxFrameSize), promiseAggregator.NewPromise()); remainingData -= _maxFrameSize; // Stop iterating if remainingData == _maxFrameSize so we can take care of reference counts below. }while (remainingData > _maxFrameSize); } if (0u >= (uint)padding) { // Write the header. if (frameHeader is object) { _ = frameHeader.Release(); frameHeader = null; } IByteBuffer frameHeader2 = ctx.Allocator.Buffer(Http2CodecUtil.FrameHeaderLength); _ = flags.EndOfStream(endOfStream); Http2CodecUtil.WriteFrameHeaderInternal(frameHeader2, remainingData, Http2FrameTypes.Data, flags, streamId); _ = ctx.WriteAsync(frameHeader2, promiseAggregator.NewPromise()); // Write the payload. IByteBuffer lastFrame = data.ReadSlice(remainingData); data = null; _ = ctx.WriteAsync(lastFrame, promiseAggregator.NewPromise()); } else { if (remainingData != _maxFrameSize) { if (frameHeader is object) { _ = frameHeader.Release(); frameHeader = null; } } else { remainingData -= _maxFrameSize; // Write the header. IByteBuffer lastFrame; if (frameHeader is null) { lastFrame = ctx.Allocator.Buffer(Http2CodecUtil.FrameHeaderLength); Http2CodecUtil.WriteFrameHeaderInternal(lastFrame, _maxFrameSize, Http2FrameTypes.Data, flags, streamId); } else { lastFrame = frameHeader.Slice(); frameHeader = null; } _ = ctx.WriteAsync(lastFrame, promiseAggregator.NewPromise()); // Write the payload. lastFrame = data.ReadableBytes != _maxFrameSize?data.ReadSlice(_maxFrameSize) : data; data = null; _ = ctx.WriteAsync(lastFrame, promiseAggregator.NewPromise()); } do { int frameDataBytes = Math.Min(remainingData, _maxFrameSize); int framePaddingBytes = Math.Min(padding, Math.Max(0, (_maxFrameSize - 1) - frameDataBytes)); // Decrement the remaining counters. padding -= framePaddingBytes; remainingData -= frameDataBytes; // Write the header. IByteBuffer frameHeader2 = ctx.Allocator.Buffer(Http2CodecUtil.DataFrameHeaderLength); _ = flags.EndOfStream(endOfStream && 0u >= (uint)remainingData && 0u >= (uint)padding); _ = flags.PaddingPresent(framePaddingBytes > 0); Http2CodecUtil.WriteFrameHeaderInternal(frameHeader2, framePaddingBytes + frameDataBytes, Http2FrameTypes.Data, flags, streamId); WritePaddingLength(frameHeader2, framePaddingBytes); _ = ctx.WriteAsync(frameHeader2, promiseAggregator.NewPromise()); // Write the payload. if (frameDataBytes != 0) { if (0u >= (uint)remainingData) { IByteBuffer lastFrame = data.ReadSlice(frameDataBytes); data = null; _ = ctx.WriteAsync(lastFrame, promiseAggregator.NewPromise()); } else { _ = ctx.WriteAsync(data.ReadRetainedSlice(frameDataBytes), promiseAggregator.NewPromise()); } } // Write the frame padding. if (PaddingBytes(framePaddingBytes) > 0) { _ = ctx.WriteAsync( ZeroBuffer.Slice(0, PaddingBytes(framePaddingBytes)), promiseAggregator.NewPromise()); } }while (remainingData != 0 || padding != 0); } } catch (Exception cause) { if (frameHeader is object) { _ = frameHeader.Release(); } // Use a try/finally here in case the data has been released before calling this method. This is not // necessary above because we internally allocate frameHeader. try { if (data is object) { _ = data.Release(); } } finally { promiseAggregator.SetException(cause); _ = promiseAggregator.DoneAllocatingPromises(); } return(promiseAggregator.Task); } _ = promiseAggregator.DoneAllocatingPromises(); return(promiseAggregator.Task); }
Task WriteHeadersInternal(IChannelHandlerContext ctx, int streamId, IHttp2Headers headers, int padding, bool endOfStream, bool hasPriority, int streamDependency, short weight, bool exclusive, IPromise promise) { IByteBuffer headerBlock = null; SimplePromiseAggregator promiseAggregator = new SimplePromiseAggregator(promise); try { if ((uint)(streamId - 1) > SharedConstants.TooBigOrNegative) { ThrowHelper.ThrowArgumentException_Positive(ExceptionArgument.StreamID); } if (hasPriority) { if (streamDependency < 0) { ThrowHelper.ThrowArgumentException_PositiveOrZero(ExceptionArgument.StreamDependency); } Http2CodecUtil.VerifyPadding(padding); VerifyWeight(weight); } // Encode the entire header block. headerBlock = ctx.Allocator.Buffer(); _headersEncoder.EncodeHeaders(streamId, headers, headerBlock); Http2Flags flags = new Http2Flags().EndOfStream(endOfStream).PriorityPresent(hasPriority).PaddingPresent(padding > 0); // Read the first fragment (possibly everything). int nonFragmentBytes = padding + flags.GetNumPriorityBytes(); int maxFragmentLength = _maxFrameSize - nonFragmentBytes; IByteBuffer fragment = headerBlock.ReadRetainedSlice(Math.Min(headerBlock.ReadableBytes, maxFragmentLength)); // Set the end of headers flag for the first frame. _ = flags.EndOfHeaders(!headerBlock.IsReadable()); int payloadLength = fragment.ReadableBytes + nonFragmentBytes; IByteBuffer buf = ctx.Allocator.Buffer(Http2CodecUtil.HeadersFrameHeaderLength); Http2CodecUtil.WriteFrameHeaderInternal(buf, payloadLength, Http2FrameTypes.Headers, flags, streamId); WritePaddingLength(buf, padding); if (hasPriority) { _ = buf.WriteInt(exclusive ? (int)(uint)(0x80000000L | streamDependency) : streamDependency); // Adjust the weight so that it fits into a single byte on the wire. _ = buf.WriteByte(weight - 1); } _ = ctx.WriteAsync(buf, promiseAggregator.NewPromise()); // Write the first fragment. _ = ctx.WriteAsync(fragment, promiseAggregator.NewPromise()); // Write out the padding, if any. if (PaddingBytes(padding) > 0) { _ = ctx.WriteAsync(ZeroBuffer.Slice(0, PaddingBytes(padding)), promiseAggregator.NewPromise()); } if (!flags.EndOfHeaders()) { _ = WriteContinuationFramesAsync(ctx, streamId, headerBlock, promiseAggregator); } } catch (Http2Exception e) { promiseAggregator.SetException(e); } catch (Exception t) { promiseAggregator.SetException(t); _ = promiseAggregator.DoneAllocatingPromises(); throw; } finally { _ = (headerBlock?.Release()); } _ = promiseAggregator.DoneAllocatingPromises(); return(promiseAggregator.Task); }
public virtual Task WritePushPromiseAsync(IChannelHandlerContext ctx, int streamId, int promisedStreamId, IHttp2Headers headers, int padding, IPromise promise) { IByteBuffer headerBlock = null; SimplePromiseAggregator promiseAggregator = new SimplePromiseAggregator(promise); try { if ((uint)(streamId - 1) > SharedConstants.TooBigOrNegative) { ThrowHelper.ThrowArgumentException_Positive(ExceptionArgument.StreamID); } if (promisedStreamId <= 0) { ThrowHelper.ThrowArgumentException_Positive(ExceptionArgument.PromisedStreamId); } Http2CodecUtil.VerifyPadding(padding); // Encode the entire header block into an intermediate buffer. headerBlock = ctx.Allocator.Buffer(); _headersEncoder.EncodeHeaders(streamId, headers, headerBlock); // Read the first fragment (possibly everything). Http2Flags flags = new Http2Flags().PaddingPresent(padding > 0); // IntFieldLength is for the length of the promisedStreamId int nonFragmentLength = Http2CodecUtil.IntFieldLength + padding; int maxFragmentLength = _maxFrameSize - nonFragmentLength; IByteBuffer fragment = headerBlock.ReadRetainedSlice(Math.Min(headerBlock.ReadableBytes, maxFragmentLength)); _ = flags.EndOfHeaders(!headerBlock.IsReadable()); int payloadLength = fragment.ReadableBytes + nonFragmentLength; IByteBuffer buf = ctx.Allocator.Buffer(Http2CodecUtil.PushPromiseFrameHeaderLength); Http2CodecUtil.WriteFrameHeaderInternal(buf, payloadLength, Http2FrameTypes.PushPromise, flags, streamId); WritePaddingLength(buf, padding); // Write out the promised stream ID. _ = buf.WriteInt(promisedStreamId); _ = ctx.WriteAsync(buf, promiseAggregator.NewPromise()); // Write the first fragment. _ = ctx.WriteAsync(fragment, promiseAggregator.NewPromise()); // Write out the padding, if any. if (PaddingBytes(padding) > 0) { _ = ctx.WriteAsync(ZeroBuffer.Slice(0, PaddingBytes(padding)), promiseAggregator.NewPromise()); } if (!flags.EndOfHeaders()) { _ = WriteContinuationFramesAsync(ctx, streamId, headerBlock, promiseAggregator); } } catch (Http2Exception e) { promiseAggregator.SetException(e); } catch (Exception t) { promiseAggregator.SetException(t); _ = promiseAggregator.DoneAllocatingPromises(); throw; } finally { if (headerBlock is object) { _ = headerBlock.Release(); } } _ = promiseAggregator.DoneAllocatingPromises(); return(promiseAggregator.Task); }