public virtual void GlobalSetup() { _memoryPool = PinnedBlockMemoryPoolFactory.Create(); _httpFrame = new Http2Frame(); var options = new PipeOptions(_memoryPool, readerScheduler: PipeScheduler.Inline, writerScheduler: PipeScheduler.Inline, useSynchronizationContext: false); _connectionPair = DuplexPipe.CreateConnectionPair(options, options); _httpRequestHeaders = new HttpRequestHeaders(); _httpRequestHeaders[HeaderNames.Method] = new StringValues("GET"); _httpRequestHeaders[HeaderNames.Path] = new StringValues("/"); _httpRequestHeaders[HeaderNames.Scheme] = new StringValues("http"); _httpRequestHeaders[HeaderNames.Authority] = new StringValues("localhost:80"); _headersBuffer = new byte[1024 * 16]; _hpackEncoder = new DynamicHPackEncoder(); var serviceContext = TestContextFactory.CreateServiceContext( serverOptions: new KestrelServerOptions(), dateHeaderValueManager: new DateHeaderValueManager(), systemClock: new MockSystemClock(), log: new MockTrace()); serviceContext.DateHeaderValueManager.OnHeartbeat(default);
public Http2FrameWriter( PipeWriter outputPipeWriter, BaseConnectionContext connectionContext, Http2Connection http2Connection, OutputFlowControl connectionOutputFlowControl, ITimeoutControl timeoutControl, MinDataRate?minResponseDataRate, string connectionId, MemoryPool <byte> memoryPool, ServiceContext serviceContext) { // Allow appending more data to the PipeWriter when a flush is pending. _outputWriter = new ConcurrentPipeWriter(outputPipeWriter, memoryPool, _writeLock); _connectionContext = connectionContext; _http2Connection = http2Connection; _connectionOutputFlowControl = connectionOutputFlowControl; _connectionId = connectionId; _log = serviceContext.Log; _timeoutControl = timeoutControl; _minResponseDataRate = minResponseDataRate; _flusher = new TimingPipeFlusher(timeoutControl, serviceContext.Log); _flusher.Initialize(_outputWriter); _outgoingFrame = new Http2Frame(); _headerEncodingBuffer = new byte[_maxFrameSize]; _scheduleInline = serviceContext.Scheduler == PipeScheduler.Inline; _hpackEncoder = new DynamicHPackEncoder(serviceContext.ServerOptions.AllowResponseHeaderCompression); }
public void GlobalSetup() { _http2HeadersEnumerator = new Http2HeadersEnumerator(); _hpackEncoder = new DynamicHPackEncoder(); _buffer = new byte[1024 * 1024]; _knownResponseHeaders = new HttpResponseHeaders { HeaderServer = "Kestrel", HeaderContentType = "application/json", HeaderDate = "Date!", HeaderContentLength = "0", HeaderAcceptRanges = "Ranges!", HeaderTransferEncoding = "Encoding!", HeaderVia = "Via!", HeaderVary = "Vary!", HeaderWWWAuthenticate = "Authenticate!", HeaderLastModified = "Modified!", HeaderExpires = "Expires!", HeaderAge = "Age!" }; _unknownResponseHeaders = new HttpResponseHeaders(); for (var i = 0; i < 10; i++) { _unknownResponseHeaders.Append("Unknown" + i, "Value" + i); } }
public void GlobalSetup() { _http2HeadersEnumerator = new Http2HeadersEnumerator(); _hpackEncoder = new DynamicHPackEncoder(); _buffer = new byte[1024 * 1024]; _knownResponseHeaders = new HttpResponseHeaders(); var knownHeaders = (IHeaderDictionary)_knownResponseHeaders; knownHeaders.Server = "Kestrel"; knownHeaders.ContentType = "application/json"; knownHeaders.Date = "Date!"; knownHeaders.ContentLength = 0; knownHeaders.AcceptRanges = "Ranges!"; knownHeaders.TransferEncoding = "Encoding!"; knownHeaders.Via = "Via!"; knownHeaders.Vary = "Vary!"; knownHeaders.WWWAuthenticate = "Authenticate!"; knownHeaders.LastModified = "Modified!"; knownHeaders.Expires = "Expires!"; knownHeaders.Age = "Age!"; _unknownResponseHeaders = new HttpResponseHeaders(); for (var i = 0; i < 10; i++) { _unknownResponseHeaders.Append("Unknown" + i, "Value" + i); } }
public void BeginEncodeHeaders_MaxHeaderTableSizeUpdated_SizeUpdateInHeaders() { Span <byte> buffer = new byte[1024 * 16]; var hpackEncoder = new DynamicHPackEncoder(); hpackEncoder.UpdateMaxHeaderTableSize(100); var enumerator = new Http2HeadersEnumerator(); // First request enumerator.Initialize(new Dictionary <string, StringValues>()); Assert.True(HPackHeaderWriter.BeginEncodeHeaders(hpackEncoder, enumerator, buffer, out var length)); Assert.Equal(2, length); const byte DynamicTableSizeUpdateMask = 0xe0; var integerDecoder = new IntegerDecoder(); Assert.False(integerDecoder.BeginTryDecode((byte)(buffer[0] & ~DynamicTableSizeUpdateMask), prefixLength: 5, out _)); Assert.True(integerDecoder.TryDecode(buffer[1], out var result)); Assert.Equal(100, result); // Second request enumerator.Initialize(new Dictionary <string, StringValues>()); Assert.True(HPackHeaderWriter.BeginEncodeHeaders(hpackEncoder, enumerator, buffer, out length)); Assert.Equal(0, length); }
public void BeginEncodeHeaders_CacheControlPrivate_NewIndexValue() { Span <byte> buffer = new byte[1024 * 16]; var headers = (IHeaderDictionary) new HttpResponseHeaders(); headers.CacheControl = "private"; var enumerator = new Http2HeadersEnumerator(); enumerator.Initialize(headers); var hpackEncoder = new DynamicHPackEncoder(); Assert.True(HPackHeaderWriter.BeginEncodeHeaders(302, hpackEncoder, enumerator, buffer, out var length)); var result = buffer.Slice(5, length - 5).ToArray(); var hex = BitConverter.ToString(result); Assert.Equal("58-07-70-72-69-76-61-74-65", hex); var statusHeader = GetHeaderEntry(hpackEncoder, 0); Assert.Equal("Cache-Control", statusHeader.Name); Assert.Equal("private", statusHeader.Value); }
public void BeginEncodeHeaders_ExcludedHeaders_NotAddedToTable(string headerName, bool neverIndex) { Span <byte> buffer = new byte[1024 * 16]; var headers = new HttpResponseHeaders(); headers.Append(headerName, "1"); var enumerator = new Http2HeadersEnumerator(); enumerator.Initialize(headers); var hpackEncoder = new DynamicHPackEncoder(maxHeaderTableSize: Http2PeerSettings.DefaultHeaderTableSize); Assert.True(HPackHeaderWriter.BeginEncodeHeaders(hpackEncoder, enumerator, buffer, out _)); if (neverIndex) { Assert.Equal(0x10, buffer[0] & 0x10); } else { Assert.Equal(0, buffer[0] & 0x40); } Assert.Empty(GetHeaderEntries(hpackEncoder)); }
/// <summary> /// Begin encoding headers in the first HEADERS frame. /// </summary> public static bool BeginEncodeHeaders(int statusCode, DynamicHPackEncoder hpackEncoder, Http2HeadersEnumerator headersEnumerator, Span <byte> buffer, out int length) { length = 0; if (!hpackEncoder.EnsureDynamicTableSizeUpdate(buffer, out var sizeUpdateLength)) { throw new HPackEncodingException(SR.net_http_hpack_encode_failure); } length += sizeUpdateLength; if (!EncodeStatusHeader(statusCode, hpackEncoder, buffer.Slice(length), out var statusCodeLength)) { throw new HPackEncodingException(SR.net_http_hpack_encode_failure); } length += statusCodeLength; if (!headersEnumerator.MoveNext()) { return(true); } // We're ok with not throwing if no headers were encoded because we've already encoded the status. // There is a small chance that the header will encode if there is no other content in the next HEADERS frame. var done = EncodeHeadersCore(hpackEncoder, headersEnumerator, buffer.Slice(length), throwIfNoneEncoded: false, out var headersLength); length += headersLength; return(done); }
private EncoderHeaderEntry GetHeaderEntry(DynamicHPackEncoder encoder, int index) { var entry = encoder.Head; while (index-- >= 0) { entry = entry.Before; } return(entry); }
private static bool EncodeStatusHeader(int statusCode, DynamicHPackEncoder hpackEncoder, Span <byte> buffer, out int length) { if (H2StaticTable.TryGetStatusIndex(statusCode, out var index)) { // Status codes which exist in the HTTP/2 StaticTable. return(HPackEncoder.EncodeIndexedHeaderField(index, buffer, out length)); } else { const string name = ":status"; var value = StatusCodes.ToStatusString(statusCode); return(hpackEncoder.EncodeHeader(buffer, H2StaticTable.Status200, HeaderEncodingHint.Index, name, value, valueEncoding: null, out length)); } }
private List <EncoderHeaderEntry> GetHeaderEntries(DynamicHPackEncoder encoder) { var headers = new List <EncoderHeaderEntry>(); var entry = encoder.Head; while (entry.Before != encoder.Head) { entry = entry.Before; headers.Add(entry); } ; return(headers); }
public void BeginEncodeHeaders_HeaderExceedHeaderTableSize_NoIndexAndNoHeaderEntry() { Span <byte> buffer = new byte[1024 * 16]; var headers = new HttpResponseHeaders(); headers.Append("x-Custom", new string('!', (int)Http2PeerSettings.DefaultHeaderTableSize)); var enumerator = new Http2HeadersEnumerator(); enumerator.Initialize(headers); var hpackEncoder = new DynamicHPackEncoder(); Assert.True(HPackHeaderWriter.BeginEncodeHeaders(200, hpackEncoder, enumerator, buffer, out var length)); Assert.Empty(GetHeaderEntries(hpackEncoder)); }
/// <summary> /// Begin encoding headers in the first HEADERS frame. /// </summary> public static bool BeginEncodeHeaders(DynamicHPackEncoder hpackEncoder, Http2HeadersEnumerator headersEnumerator, Span <byte> buffer, out int length) { length = 0; if (!hpackEncoder.EnsureDynamicTableSizeUpdate(buffer, out var sizeUpdateLength)) { throw new HPackEncodingException(SR.net_http_hpack_encode_failure); } length += sizeUpdateLength; if (!headersEnumerator.MoveNext()) { return(true); } var done = EncodeHeadersCore(hpackEncoder, headersEnumerator, buffer.Slice(length), throwIfNoneEncoded: true, out var headersLength); length += headersLength; return(done); }
private static bool EncodeStatusHeader(int statusCode, DynamicHPackEncoder hpackEncoder, Span <byte> buffer, out int length) { switch (statusCode) { case 200: case 204: case 206: case 304: case 400: case 404: case 500: // Status codes which exist in the HTTP/2 StaticTable. return(HPackEncoder.EncodeIndexedHeaderField(H2StaticTable.GetStatusIndex(statusCode), buffer, out length)); default: const string name = ":status"; var value = StatusCodes.ToStatusString(statusCode); return(hpackEncoder.EncodeHeader(buffer, H2StaticTable.Status200, HeaderEncodingHint.Index, name, value, out length)); } }
private static bool EncodeHeadersCore(DynamicHPackEncoder hpackEncoder, Http2HeadersEnumerator headersEnumerator, Span <byte> buffer, bool throwIfNoneEncoded, out int length) { var currentLength = 0; do { var staticTableId = headersEnumerator.HPackStaticTableId; var name = headersEnumerator.Current.Key; var value = headersEnumerator.Current.Value; var valueEncoding = ReferenceEquals(headersEnumerator.EncodingSelector, KestrelServerOptions.DefaultHeaderEncodingSelector) ? null : headersEnumerator.EncodingSelector(name); var hint = ResolveHeaderEncodingHint(staticTableId, name); if (!hpackEncoder.EncodeHeader( buffer.Slice(currentLength), staticTableId, hint, name, value, valueEncoding, out var headerLength)) { // If the header wasn't written, and no headers have been written, then the header is too large. // Throw an error to avoid an infinite loop of attempting to write large header. if (currentLength == 0 && throwIfNoneEncoded) { throw new HPackEncodingException(SR.net_http_hpack_encode_failure); } length = currentLength; return(false); } currentLength += headerLength; }while (headersEnumerator.MoveNext()); length = currentLength; return(true); }
public static void WriteStartStream(this PipeWriter writer, int streamId, DynamicHPackEncoder hpackEncoder, Http2HeadersEnumerator headers, byte[] headerEncodingBuffer, bool endStream, Http2Frame frame = null) { frame ??= new Http2Frame(); frame.PrepareHeaders(Http2HeadersFrameFlags.NONE, streamId); var buffer = headerEncodingBuffer.AsSpan(); var done = HPackHeaderWriter.BeginEncodeHeaders(hpackEncoder, headers, buffer, out var length); frame.PayloadLength = length; if (done) { frame.HeadersFlags = Http2HeadersFrameFlags.END_HEADERS; } if (endStream) { frame.HeadersFlags |= Http2HeadersFrameFlags.END_STREAM; } Http2FrameWriter.WriteHeader(frame, writer); writer.Write(buffer.Slice(0, length)); while (!done) { frame.PrepareContinuation(Http2ContinuationFrameFlags.NONE, streamId); done = HPackHeaderWriter.ContinueEncodeHeaders(hpackEncoder, headers, buffer, out length); frame.PayloadLength = length; if (done) { frame.ContinuationFlags = Http2ContinuationFrameFlags.END_HEADERS; } Http2FrameWriter.WriteHeader(frame, writer); writer.Write(buffer.Slice(0, length)); } }
public void BeginEncodeHeaders_Status302_NewIndexValue() { Span <byte> buffer = new byte[1024 * 16]; var headers = new HttpResponseHeaders(); var enumerator = new Http2HeadersEnumerator(); enumerator.Initialize(headers); var hpackEncoder = new DynamicHPackEncoder(); Assert.True(HPackHeaderWriter.BeginEncodeHeaders(302, hpackEncoder, enumerator, buffer, out var length)); var result = buffer.Slice(0, length).ToArray(); var hex = BitConverter.ToString(result); Assert.Equal("48-03-33-30-32", hex); var statusHeader = GetHeaderEntry(hpackEncoder, 0); Assert.Equal(":status", statusHeader.Name); Assert.Equal("302", statusHeader.Value); }
public void EncodesHeadersInSinglePayloadWhenSpaceAvailable(KeyValuePair <string, string>[] headers, byte[] expectedPayload, int?statusCode) { var hpackEncoder = new DynamicHPackEncoder(); var payload = new byte[1024]; var length = 0; if (statusCode.HasValue) { Assert.True(HPackHeaderWriter.BeginEncodeHeaders(statusCode.Value, hpackEncoder, GetHeadersEnumerator(headers), payload, out length)); } else { Assert.True(HPackHeaderWriter.BeginEncodeHeaders(hpackEncoder, GetHeadersEnumerator(headers), payload, out length)); } Assert.Equal(expectedPayload.Length, length); for (var i = 0; i < length; i++) { Assert.True(expectedPayload[i] == payload[i], $"{expectedPayload[i]} != {payload[i]} at {i} (len {length})"); } Assert.Equal(expectedPayload, new ArraySegment <byte>(payload, 0, length)); }
public void EncodesHeadersInMultiplePayloadsWhenSpaceNotAvailable(bool exactSize) { var statusCode = 200; var headers = new[] { new KeyValuePair <string, string>("date", "Mon, 24 Jul 2017 19:22:30 GMT"), new KeyValuePair <string, string>("content-type", "text/html; charset=utf-8"), new KeyValuePair <string, string>("server", "Kestrel") }; var expectedStatusCodePayload = new byte[] { 0x88 }; var expectedDateHeaderPayload = new byte[] { 0x40, 0x04, 0x64, 0x61, 0x74, 0x65, 0x1d, 0x4d, 0x6f, 0x6e, 0x2c, 0x20, 0x32, 0x34, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x32, 0x30, 0x31, 0x37, 0x20, 0x31, 0x39, 0x3a, 0x32, 0x32, 0x3a, 0x33, 0x30, 0x20, 0x47, 0x4d, 0x54 }; var expectedContentTypeHeaderPayload = new byte[] { 0x40, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x18, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3b, 0x20, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38 }; var expectedServerHeaderPayload = new byte[] { 0x40, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x07, 0x4b, 0x65, 0x73, 0x74, 0x72, 0x65, 0x6c }; var hpackEncoder = new DynamicHPackEncoder(); Span <byte> payload = new byte[1024]; var offset = 0; var headerEnumerator = GetHeadersEnumerator(headers); // When !exactSize, slices are one byte short of fitting the next header var sliceLength = expectedStatusCodePayload.Length + (exactSize ? 0 : expectedDateHeaderPayload.Length - 1); Assert.False(HPackHeaderWriter.BeginEncodeHeaders(statusCode, hpackEncoder, headerEnumerator, payload.Slice(offset, sliceLength), out var length)); Assert.Equal(expectedStatusCodePayload.Length, length); Assert.Equal(expectedStatusCodePayload, payload.Slice(0, length).ToArray()); offset += length; sliceLength = expectedDateHeaderPayload.Length + (exactSize ? 0 : expectedContentTypeHeaderPayload.Length - 1); Assert.False(HPackHeaderWriter.ContinueEncodeHeaders(hpackEncoder, headerEnumerator, payload.Slice(offset, sliceLength), out length)); Assert.Equal(expectedDateHeaderPayload.Length, length); Assert.Equal(expectedDateHeaderPayload, payload.Slice(offset, length).ToArray()); offset += length; sliceLength = expectedContentTypeHeaderPayload.Length + (exactSize ? 0 : expectedServerHeaderPayload.Length - 1); Assert.False(HPackHeaderWriter.ContinueEncodeHeaders(hpackEncoder, headerEnumerator, payload.Slice(offset, sliceLength), out length)); Assert.Equal(expectedContentTypeHeaderPayload.Length, length); Assert.Equal(expectedContentTypeHeaderPayload, payload.Slice(offset, length).ToArray()); offset += length; sliceLength = expectedServerHeaderPayload.Length; Assert.True(HPackHeaderWriter.ContinueEncodeHeaders(hpackEncoder, headerEnumerator, payload.Slice(offset, sliceLength), out length)); Assert.Equal(expectedServerHeaderPayload.Length, length); Assert.Equal(expectedServerHeaderPayload, payload.Slice(offset, length).ToArray()); }
/// <summary> /// Continue encoding headers in the next HEADERS frame. The enumerator should already have a current value. /// </summary> public static bool ContinueEncodeHeaders(DynamicHPackEncoder hpackEncoder, Http2HeadersEnumerator headersEnumerator, Span <byte> buffer, out int length) { return(EncodeHeadersCore(hpackEncoder, headersEnumerator, buffer, throwIfNoneEncoded: true, out length)); }
public void BeginEncodeHeaders_MaxHeaderTableSizeExceeded_EvictionsToFit() { // Test follows example https://tools.ietf.org/html/rfc7541#appendix-C.5 Span <byte> buffer = new byte[1024 * 16]; var headers = (IHeaderDictionary) new HttpResponseHeaders(); headers.CacheControl = "private"; headers.Date = "Mon, 21 Oct 2013 20:13:21 GMT"; headers.Location = "https://www.example.com"; var enumerator = new Http2HeadersEnumerator(); var hpackEncoder = new DynamicHPackEncoder(maxHeaderTableSize: 256); // First response enumerator.Initialize(headers); Assert.True(HPackHeaderWriter.BeginEncodeHeaders(302, hpackEncoder, enumerator, buffer, out var length)); var result = buffer.Slice(0, length).ToArray(); var hex = BitConverter.ToString(result); Assert.Equal( "48-03-33-30-32-61-1D-4D-6F-6E-2C-20-32-31-20-4F-" + "63-74-20-32-30-31-33-20-32-30-3A-31-33-3A-32-31-" + "20-47-4D-54-58-07-70-72-69-76-61-74-65-6E-17-68-" + "74-74-70-73-3A-2F-2F-77-77-77-2E-65-78-61-6D-70-" + "6C-65-2E-63-6F-6D", hex); var entries = GetHeaderEntries(hpackEncoder); Assert.Collection(entries, e => { Assert.Equal("Location", e.Name); Assert.Equal("https://www.example.com", e.Value); Assert.Equal(63u, e.Size); }, e => { Assert.Equal("Cache-Control", e.Name); Assert.Equal("private", e.Value); Assert.Equal(52u, e.Size); }, e => { Assert.Equal("Date", e.Name); Assert.Equal("Mon, 21 Oct 2013 20:13:21 GMT", e.Value); Assert.Equal(65u, e.Size); }, e => { Assert.Equal(":status", e.Name); Assert.Equal("302", e.Value); Assert.Equal(42u, e.Size); }); Assert.Equal(222u, hpackEncoder.TableSize); // Second response enumerator.Initialize(headers); Assert.True(HPackHeaderWriter.BeginEncodeHeaders(307, hpackEncoder, enumerator, buffer, out length)); result = buffer.Slice(0, length).ToArray(); hex = BitConverter.ToString(result); Assert.Equal("48-03-33-30-37-C1-C0-BF", hex); entries = GetHeaderEntries(hpackEncoder); Assert.Collection(entries, e => { Assert.Equal(":status", e.Name); Assert.Equal("307", e.Value); Assert.Equal(42u, e.Size); }, e => { Assert.Equal("Location", e.Name); Assert.Equal("https://www.example.com", e.Value); Assert.Equal(63u, e.Size); }, e => { Assert.Equal("Cache-Control", e.Name); Assert.Equal("private", e.Value); Assert.Equal(52u, e.Size); }, e => { Assert.Equal("Date", e.Name); Assert.Equal("Mon, 21 Oct 2013 20:13:21 GMT", e.Value); Assert.Equal(65u, e.Size); }); Assert.Equal(222u, hpackEncoder.TableSize); // Third response headers.Date = "Mon, 21 Oct 2013 20:13:22 GMT"; headers.ContentEncoding = "gzip"; headers.SetCookie = "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"; enumerator.Initialize(headers); Assert.True(HPackHeaderWriter.BeginEncodeHeaders(200, hpackEncoder, enumerator, buffer, out length)); result = buffer.Slice(0, length).ToArray(); hex = BitConverter.ToString(result); Assert.Equal( "88-61-1D-4D-6F-6E-2C-20-32-31-20-4F-63-74-20-32-" + "30-31-33-20-32-30-3A-31-33-3A-32-32-20-47-4D-54-" + "C1-5A-04-67-7A-69-70-C1-1F-28-38-66-6F-6F-3D-41-" + "53-44-4A-4B-48-51-4B-42-5A-58-4F-51-57-45-4F-50-" + "49-55-41-58-51-57-45-4F-49-55-3B-20-6D-61-78-2D-" + "61-67-65-3D-33-36-30-30-3B-20-76-65-72-73-69-6F-" + "6E-3D-31", hex); entries = GetHeaderEntries(hpackEncoder); Assert.Collection(entries, e => { Assert.Equal("Content-Encoding", e.Name); Assert.Equal("gzip", e.Value); Assert.Equal(52u, e.Size); }, e => { Assert.Equal("Date", e.Name); Assert.Equal("Mon, 21 Oct 2013 20:13:22 GMT", e.Value); Assert.Equal(65u, e.Size); }, e => { Assert.Equal(":status", e.Name); Assert.Equal("307", e.Value); Assert.Equal(42u, e.Size); }, e => { Assert.Equal("Location", e.Name); Assert.Equal("https://www.example.com", e.Value); Assert.Equal(63u, e.Size); }); Assert.Equal(222u, hpackEncoder.TableSize); }