|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Buffers;
using System.Globalization;
using System.Runtime.ExceptionServices;
using System.Text;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.InternalTesting;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.Extensions.Logging;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests;
public class Http2StreamTests : Http2TestBase
{
[Theory]
[InlineData("\r")]
[InlineData("\n")]
[InlineData("\r\n")]
public async Task HEADERS_Received_NewLineCharactersInValue_ConnectionError(string headerValue)
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
new KeyValuePair<string, string>("TestHeader", headerValue),
};
await InitializeConnectionAsync(_noopApplication);
await StartStreamAsync(1, headers, endStream: true);
await WaitForConnectionErrorAsync<Exception>(ignoreNonGoAwayFrames: false, 1, Http2ErrorCode.PROTOCOL_ERROR, "Malformed request: invalid headers.");
AssertConnectionEndReason(ConnectionEndReason.InvalidRequestHeaders);
}
[Fact]
public async Task HEADERS_Received_EmptyMethod_Reset()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, ""),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
};
await InitializeConnectionAsync(_noopApplication);
await StartStreamAsync(1, headers, endStream: true);
await WaitForStreamErrorAsync(expectedStreamId: 1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.FormatHttp2ErrorMethodInvalid(""));
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task HEADERS_Received_InvalidCustomMethod_Reset()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "Hello,World"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
};
await InitializeConnectionAsync(_noopApplication);
await StartStreamAsync(1, headers, endStream: true);
await WaitForStreamErrorAsync(expectedStreamId: 1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.FormatHttp2ErrorMethodInvalid("Hello,World"));
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Theory]
[InlineData("GET")]
[InlineData("POST")]
[InlineData("PUT")]
[InlineData("PATCH")]
[InlineData("DELETE")]
[InlineData("CUSTOM")]
public async Task HEADERS_Received_KnownOrCustomMethods_Accepted(string method)
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, method),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
};
await InitializeConnectionAsync(_echoMethodNoBody);
// First request
await StartStreamAsync(1, headers, endStream: true);
var headersFrame1 = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 45 + method.Length,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
_hpackDecoder.Decode(headersFrame1.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(4, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
Assert.Equal(method, _decodedHeaders["Method"]);
_decodedHeaders.Clear();
// Second request (will use dynamic table indexes)
await StartStreamAsync(3, headers, endStream: true);
var headersFrame2 = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 7,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 3);
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame2.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(4, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
Assert.Equal(method, _decodedHeaders["Method"]);
_decodedHeaders.Clear();
}
[Fact]
public async Task HEADERS_Received_HEADMethod_Accepted()
{
await InitializeConnectionAsync(_echoMethodNoBody);
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "HEAD"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
};
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 45,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("HEAD", _decodedHeaders["Method"]);
}
[Theory]
[InlineData("GET")]
[InlineData("POST")]
[InlineData("PUT")]
[InlineData("PATCH")]
[InlineData("DELETE")]
[InlineData("CUSTOM")]
public async Task HEADERS_Received_MethodsWithContentLength_Accepted(string method)
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, method),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
new KeyValuePair<string, string>(HeaderNames.ContentLength, "11"),
};
await InitializeConnectionAsync(context =>
{
Assert.True(HttpMethods.Equals(method, context.Request.Method));
Assert.True(context.Request.CanHaveBody());
Assert.Equal(11, context.Request.ContentLength);
Assert.False(context.Request.Headers.ContainsKey(HeaderNames.TransferEncoding));
return context.Request.BodyReader.CopyToAsync(context.Response.BodyWriter);
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, Encoding.UTF8.GetBytes("Hello World"), endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)(Http2HeadersFrameFlags.NONE),
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)(Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("Hello World", Encoding.UTF8.GetString(dataFrame.Payload.Span));
}
[Theory]
[InlineData("GET")]
[InlineData("POST")]
[InlineData("PUT")]
[InlineData("PATCH")]
[InlineData("DELETE")]
[InlineData("CUSTOM")]
public async Task HEADERS_Received_MethodsWithoutContentLength_Accepted(string method)
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, method),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
};
await InitializeConnectionAsync(context =>
{
Assert.True(HttpMethods.Equals(method, context.Request.Method));
Assert.True(context.Request.CanHaveBody());
Assert.Null(context.Request.ContentLength);
Assert.False(context.Request.Headers.ContainsKey(HeaderNames.TransferEncoding));
return context.Request.BodyReader.CopyToAsync(context.Response.BodyWriter);
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, Encoding.UTF8.GetBytes("Hello World"), endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)(Http2HeadersFrameFlags.NONE),
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)(Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("Hello World", Encoding.UTF8.GetString(dataFrame.Payload.Span));
}
[Fact]
public async Task HEADERS_Received_CONNECTMethod_Accepted()
{
await InitializeConnectionAsync(_echoMethodNoBody);
// :path and :scheme are not allowed, :authority is optional
var headers = new[] { new KeyValuePair<string, string>(InternalHeaderNames.Method, "CONNECT") };
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 48,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("CONNECT", _decodedHeaders["Method"]);
}
[Fact]
public async Task HEADERS_Received_OPTIONSStar_LeftOutOfPath()
{
await InitializeConnectionAsync(_echoPath);
// :path and :scheme are not allowed, :authority is optional
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "OPTIONS"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "*")
};
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 56,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(5, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("", _decodedHeaders["path"]);
Assert.Equal("*", _decodedHeaders["rawtarget"]);
Assert.Equal("0", _decodedHeaders["content-length"]);
}
[Fact]
public async Task HEADERS_Received_OPTIONSSlash_Accepted()
{
await InitializeConnectionAsync(_echoPath);
// :path and :scheme are not allowed, :authority is optional
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "OPTIONS"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/")
};
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 57,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(5, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("/", _decodedHeaders["path"]);
Assert.Equal("/", _decodedHeaders["rawtarget"]);
Assert.Equal("0", _decodedHeaders["content-length"]);
}
[Fact]
public async Task HEADERS_Received_PathAndQuery_Separated()
{
await InitializeConnectionAsync(context =>
{
context.Response.Headers["path"] = context.Request.Path.Value;
context.Response.Headers["query"] = context.Request.QueryString.Value;
context.Response.Headers["rawtarget"] = context.Features.Get<IHttpRequestFeature>().RawTarget;
return Task.CompletedTask;
});
// :path and :scheme are not allowed, :authority is optional
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/a/path?a&que%35ry")
};
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 99,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(6, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("/a/path", _decodedHeaders["path"]);
Assert.Equal("?a&que%35ry", _decodedHeaders["query"]);
Assert.Equal("/a/path?a&que%35ry", _decodedHeaders["rawtarget"]);
Assert.Equal("0", _decodedHeaders["content-length"]);
}
[Theory]
[InlineData("/", "/")]
[InlineData("/a%5E", "/a^")]
[InlineData("/a%E2%82%AC", "/a€")]
[InlineData("/a%2Fb", "/a%2Fb")] // Forward slash, not decoded
[InlineData("/a%b", "/a%b")] // Incomplete encoding, not decoded
[InlineData("/a/b/c/../d", "/a/b/d")] // Navigation processed
[InlineData("/a/b/c/../../../../d", "/d")] // Navigation escape prevented
[InlineData("/a/b/c/.%2E/d", "/a/b/d")] // Decode before navigation processing
public async Task HEADERS_Received_Path_DecodedAndNormalized(string input, string expected)
{
await InitializeConnectionAsync(context =>
{
Assert.Equal(expected, context.Request.Path.Value);
Assert.Equal(input, context.Features.Get<IHttpRequestFeature>().RawTarget);
return Task.CompletedTask;
});
// :path and :scheme are not allowed, :authority is optional
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, input)
};
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders["content-length"]);
}
[Theory]
[InlineData(":path", "/")]
[InlineData(":scheme", "http")]
public async Task HEADERS_Received_CONNECTMethod_WithSchemeOrPath_Reset(string headerName, string value)
{
await InitializeConnectionAsync(_noopApplication);
// :path and :scheme are not allowed, :authority is optional
var headers = new[] { new KeyValuePair<string, string>(InternalHeaderNames.Method, "CONNECT"),
new KeyValuePair<string, string>(headerName, value) };
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
await WaitForStreamErrorAsync(expectedStreamId: 1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2ErrorConnectMustNotSendSchemeOrPath);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Theory]
[InlineData("https")]
[InlineData("ftp")]
public async Task HEADERS_Received_SchemeMismatch_Reset(string scheme)
{
await InitializeConnectionAsync(_noopApplication);
var headers = new[] { new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, scheme) }; // Not the expected "http"
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
await WaitForStreamErrorAsync(expectedStreamId: 1, Http2ErrorCode.PROTOCOL_ERROR,
CoreStrings.FormatHttp2StreamErrorSchemeMismatch(scheme, "http"));
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Theory]
[InlineData("https")]
[InlineData("ftp")]
public async Task HEADERS_Received_SchemeMismatchAllowed_Processed(string scheme)
{
_serviceContext.ServerOptions.AllowAlternateSchemes = true;
await InitializeConnectionAsync(context =>
{
Assert.Equal(scheme, context.Request.Scheme);
Assert.False(context.Request.Headers.ContainsKey(InternalHeaderNames.Scheme));
return Task.CompletedTask;
});
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, scheme)
}; // Not the expected "http"
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
}
[Theory]
[InlineData("https,http")]
[InlineData("http://fakehost/")]
public async Task HEADERS_Received_SchemeMismatchAllowed_InvalidScheme_Reset(string scheme)
{
_serviceContext.ServerOptions.AllowAlternateSchemes = true;
await InitializeConnectionAsync(_noopApplication);
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, scheme)
}; // Not the expected "http"
await SendHeadersAsync(1, Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM, headers);
await WaitForStreamErrorAsync(expectedStreamId: 1, Http2ErrorCode.PROTOCOL_ERROR,
CoreStrings.FormatHttp2StreamErrorSchemeMismatch(scheme, "http"));
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task HEADERS_Received_MissingAuthority_200Status()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(_noopApplication);
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders["content-length"]);
}
[Fact]
public async Task HEADERS_Received_EmptyAuthority_200Status()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(InternalHeaderNames.Authority, ""),
};
await InitializeConnectionAsync(_noopApplication);
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders["content-length"]);
}
[Fact]
public async Task HEADERS_Received_MissingAuthorityFallsBackToHost_200Status()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>("Host", "abc"),
};
await InitializeConnectionAsync(_echoHost);
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 46,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(4, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
Assert.Equal("abc", _decodedHeaders[HeaderNames.Host]);
}
[Fact]
public async Task HEADERS_Received_EmptyAuthorityIgnoredOverHost_200Status()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(InternalHeaderNames.Authority, ""),
new KeyValuePair<string, string>("Host", "abc"),
};
await InitializeConnectionAsync(_echoHost);
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 46,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(4, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
Assert.Equal("abc", _decodedHeaders[HeaderNames.Host]);
}
[Fact]
public async Task HEADERS_Received_AuthorityOverridesHost_200Status()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(InternalHeaderNames.Authority, "def"),
new KeyValuePair<string, string>("Host", "abc"),
};
await InitializeConnectionAsync(_echoHost);
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 46,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(4, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
Assert.Equal("def", _decodedHeaders[HeaderNames.Host]);
}
[Fact]
public async Task HEADERS_Received_AuthorityOverridesInvalidHost_200Status()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(InternalHeaderNames.Authority, "def"),
new KeyValuePair<string, string>("Host", "a=bc"),
};
await InitializeConnectionAsync(_echoHost);
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 46,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(4, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
Assert.Equal("def", _decodedHeaders[HeaderNames.Host]);
}
[Fact]
public async Task HEADERS_Received_InvalidAuthority_Reset()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(InternalHeaderNames.Authority, "local=host:80"),
};
await InitializeConnectionAsync(_noopApplication);
await StartStreamAsync(1, headers, endStream: true);
await WaitForStreamErrorAsync(expectedStreamId: 1, Http2ErrorCode.PROTOCOL_ERROR,
CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("local=host:80"));
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task HEADERS_Received_InvalidAuthorityWithValidHost_Reset()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(InternalHeaderNames.Authority, "d=ef"),
new KeyValuePair<string, string>("Host", "abc"),
};
await InitializeConnectionAsync(_echoHost);
await StartStreamAsync(1, headers, endStream: true);
await WaitForStreamErrorAsync(expectedStreamId: 1, Http2ErrorCode.PROTOCOL_ERROR,
CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("d=ef"));
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task HEADERS_Received_TwoHosts_StreamReset()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>("Host", "host1"),
new KeyValuePair<string, string>("Host", "host2"),
};
await InitializeConnectionAsync(_noopApplication);
await StartStreamAsync(1, headers, endStream: true);
await WaitForStreamErrorAsync(expectedStreamId: 1, Http2ErrorCode.PROTOCOL_ERROR,
CoreStrings.FormatBadRequest_InvalidHostHeader_Detail("host1,host2"));
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task HEADERS_Received_MaxRequestLineSize_Reset()
{
// Default 8kb limit
// This test has to work around the HPack parser limit for incoming field sizes over 4kb. That's going to be a problem for people with long urls.
// https://github.com/aspnet/KestrelHttpServer/issues/2872
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET" + new string('a', 1024 * 3)),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/Hello/How/Are/You/" + new string('a', 1024 * 3)),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost" + new string('a', 1024 * 3) + ":80"),
};
await InitializeConnectionAsync(_noopApplication);
await StartStreamAsync(1, headers, endStream: true);
await WaitForStreamErrorAsync(expectedStreamId: 1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.BadRequest_RequestLineTooLong);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task HEADERS_Received_MaxRequestHeadersTotalSize_431()
{
// > 32kb
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>("a", _4kHeaderValue),
new KeyValuePair<string, string>("b", _4kHeaderValue),
new KeyValuePair<string, string>("c", _4kHeaderValue),
new KeyValuePair<string, string>("d", _4kHeaderValue),
new KeyValuePair<string, string>("e", _4kHeaderValue),
new KeyValuePair<string, string>("f", _4kHeaderValue),
new KeyValuePair<string, string>("g", _4kHeaderValue),
new KeyValuePair<string, string>("h", _4kHeaderValue),
};
await InitializeConnectionAsync(_notImplementedApp);
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 40,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("431", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task HEADERS_Received_MaxRequestHeaderCount_431()
{
// > 100 headers
var headers = new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
for (var i = 0; i < 101; i++)
{
var text = i.ToString(CultureInfo.InvariantCulture);
headers.Add(new KeyValuePair<string, string>(text, text));
}
await InitializeConnectionAsync(_notImplementedApp);
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 40,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("431", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task ContentLength_Received_SingleDataFrame_Verified()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
};
await InitializeConnectionAsync(async context =>
{
var buffer = new byte[100];
var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
Assert.Equal(12, read);
read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
Assert.Equal(0, read);
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[12], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task ContentLength_ReceivedInContinuation_SingleDataFrame_Verified()
{
await InitializeConnectionAsync(async context =>
{
var buffer = new byte[100];
var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
Assert.Equal(12, read);
read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
Assert.Equal(0, read);
});
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>("a", _4kHeaderValue),
new KeyValuePair<string, string>("b", _4kHeaderValue),
new KeyValuePair<string, string>("c", _4kHeaderValue),
new KeyValuePair<string, string>("d", _4kHeaderValue),
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
};
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[12], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task ContentLength_Received_MultipleDataFrame_Verified()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
};
await InitializeConnectionAsync(async context =>
{
var buffer = new byte[100];
var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
var total = read;
while (read > 0)
{
read = await context.Request.Body.ReadAsync(buffer, total, buffer.Length - total);
total += read;
}
Assert.Equal(12, total);
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[1], endStream: false);
await SendDataAsync(1, new byte[3], endStream: false);
await SendDataAsync(1, new byte[8], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task ContentLength_Received_MultipleDataFrame_ReadViaPipe_Verified()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
};
await InitializeConnectionAsync(async context =>
{
var readResult = await context.Request.BodyReader.ReadAsync();
while (!readResult.IsCompleted)
{
context.Request.BodyReader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
readResult = await context.Request.BodyReader.ReadAsync();
}
Assert.Equal(12, readResult.Buffer.Length);
context.Request.BodyReader.AdvanceTo(readResult.Buffer.End);
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[1], endStream: false);
await SendDataAsync(1, new byte[3], endStream: false);
await SendDataAsync(1, new byte[8], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task ContentLength_Received_MultipleDataFrame_ReadViaPipeAndStream_Verified()
{
var tcs = new TaskCompletionSource();
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
};
await InitializeConnectionAsync(async context =>
{
var readResult = await context.Request.BodyReader.ReadAsync();
Assert.Equal(1, readResult.Buffer.Length);
context.Request.BodyReader.AdvanceTo(readResult.Buffer.End);
tcs.SetResult();
var buffer = new byte[100];
var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
var total = read;
while (read > 0)
{
read = await context.Request.Body.ReadAsync(buffer, total, buffer.Length - total);
total += read;
}
Assert.Equal(11, total);
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[1], endStream: false);
await tcs.Task;
await SendDataAsync(1, new byte[3], endStream: false);
await SendDataAsync(1, new byte[8], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task ContentLength_Received_NoDataFrames_Reset()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
};
var requestDelegateCalled = false;
await InitializeConnectionAsync(c =>
{
// Bad content-length + end stream means the request delegate
// is never called by the server.
requestDelegateCalled = true;
return Task.CompletedTask;
});
await StartStreamAsync(1, headers, endStream: true);
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorLessDataThanLength);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
Assert.False(requestDelegateCalled);
}
[Fact]
public async Task ContentLength_ReceivedInContinuation_NoDataFrames_Reset()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>("a", _4kHeaderValue),
new KeyValuePair<string, string>("b", _4kHeaderValue),
new KeyValuePair<string, string>("c", _4kHeaderValue),
new KeyValuePair<string, string>("d", _4kHeaderValue),
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
};
await InitializeConnectionAsync(_noopApplication);
await StartStreamAsync(1, headers, endStream: true);
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorLessDataThanLength);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task ContentLength_Received_SingleDataFrameOverSize_Reset()
{
IOException thrownEx = null;
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
};
await InitializeConnectionAsync(async context =>
{
thrownEx = await Assert.ThrowsAsync<IOException>(async () =>
{
var buffer = new byte[100];
while (await context.Request.Body.ReadAsync(buffer, 0, buffer.Length) > 0) { }
});
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[13], endStream: true);
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorMoreDataThanLength);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
var expectedError = new Http2StreamErrorException(1, CoreStrings.Http2StreamErrorMoreDataThanLength, Http2ErrorCode.PROTOCOL_ERROR);
Assert.NotNull(thrownEx);
Assert.Equal(expectedError.Message, thrownEx.Message);
Assert.IsType<Http2StreamErrorException>(thrownEx.InnerException);
}
[Fact]
public async Task ContentLength_Received_SingleDataFrameUnderSize_Reset()
{
IOException thrownEx = null;
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
};
await InitializeConnectionAsync(async context =>
{
thrownEx = await Assert.ThrowsAsync<IOException>(async () =>
{
var buffer = new byte[100];
while (await context.Request.Body.ReadAsync(buffer, 0, buffer.Length) > 0) { }
});
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[11], endStream: true);
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorLessDataThanLength);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
var expectedError = new Http2StreamErrorException(1, CoreStrings.Http2StreamErrorLessDataThanLength, Http2ErrorCode.PROTOCOL_ERROR);
Assert.NotNull(thrownEx);
Assert.Equal(expectedError.Message, thrownEx.Message);
Assert.IsType<Http2StreamErrorException>(thrownEx.InnerException);
}
[Fact]
public async Task ContentLength_Received_MultipleDataFramesOverSize_Reset()
{
IOException thrownEx = null;
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
};
await InitializeConnectionAsync(async context =>
{
thrownEx = await Assert.ThrowsAsync<IOException>(async () =>
{
var buffer = new byte[100];
while (await context.Request.Body.ReadAsync(buffer, 0, buffer.Length) > 0) { }
});
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[1], endStream: false);
await SendDataAsync(1, new byte[2], endStream: false);
await SendDataAsync(1, new byte[10], endStream: false);
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorMoreDataThanLength);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
var expectedError = new Http2StreamErrorException(1, CoreStrings.Http2StreamErrorMoreDataThanLength, Http2ErrorCode.PROTOCOL_ERROR);
Assert.NotNull(thrownEx);
Assert.Equal(expectedError.Message, thrownEx.Message);
Assert.IsType<Http2StreamErrorException>(thrownEx.InnerException);
}
[Fact]
public async Task ContentLength_Received_MultipleDataFramesUnderSize_Reset()
{
IOException thrownEx = null;
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
};
await InitializeConnectionAsync(async context =>
{
thrownEx = await Assert.ThrowsAsync<IOException>(async () =>
{
var buffer = new byte[100];
while (await context.Request.Body.ReadAsync(buffer, 0, buffer.Length) > 0) { }
});
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[1], endStream: false);
await SendDataAsync(1, new byte[2], endStream: true);
await WaitForStreamErrorAsync(1, Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.Http2StreamErrorLessDataThanLength);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
var expectedError = new Http2StreamErrorException(1, CoreStrings.Http2StreamErrorLessDataThanLength, Http2ErrorCode.PROTOCOL_ERROR);
Assert.NotNull(thrownEx);
Assert.Equal(expectedError.Message, thrownEx.Message);
Assert.IsType<Http2StreamErrorException>(thrownEx.InnerException);
}
[Fact]
public async Task ContentLength_Received_ReadViaPipes()
{
await InitializeConnectionAsync(async context =>
{
var readResult = await context.Request.BodyReader.ReadAsync();
while (!readResult.IsCompleted)
{
context.Request.BodyReader.AdvanceTo(readResult.Buffer.Start, readResult.Buffer.End);
readResult = await context.Request.BodyReader.ReadAsync();
}
Assert.Equal(12, readResult.Buffer.Length);
context.Request.BodyReader.AdvanceTo(readResult.Buffer.End);
readResult = await context.Request.BodyReader.ReadAsync();
Assert.True(readResult.IsCompleted);
});
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>("a", _4kHeaderValue),
new KeyValuePair<string, string>("b", _4kHeaderValue),
new KeyValuePair<string, string>("c", _4kHeaderValue),
new KeyValuePair<string, string>("d", _4kHeaderValue),
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
};
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[12], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact] // TODO https://github.com/dotnet/aspnetcore/issues/7034
public async Task ContentLength_Response_FirstWriteMoreBytesWritten_Throws_Sends500()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
context.Response.ContentLength = 11;
await context.Response.WriteAsync("hello, world"); // 12
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 37,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.RST_STREAM,
withLength: 4,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
Assert.Contains(LogMessages, m => m.Exception?.Message.Contains("Response Content-Length mismatch: too many bytes written (12 of 11).") ?? false);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("11", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task ContentLength_Response_MoreBytesWritten_ThrowsAndResetsStream()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
context.Response.ContentLength = 11;
await context.Response.WriteAsync("hello,");
await context.Response.WriteAsync(" world");
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 37,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 6,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await WaitForStreamErrorAsync(1, Http2ErrorCode.INTERNAL_ERROR, "Response Content-Length mismatch: too many bytes written (12 of 11).");
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("11", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task ContentLength_Response_NoBytesWritten_Sends500()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(context =>
{
context.Response.ContentLength = 11;
return Task.CompletedTask;
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
Assert.Contains(LogMessages, m => m.Exception?.Message.Contains(CoreStrings.FormatTooFewBytesWritten(0, 11)) ?? false);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("500", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task StartAsync_Response_NoBytesWritten_Sends200()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
await context.Response.StartAsync();
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
}
[Fact]
public async Task StartAsync_ContentLength_Response_NoBytesWritten_Sends200()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
context.Response.ContentLength = 0;
await context.Response.StartAsync();
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task StartAsync_OnStartingThrowsAfterStartAsyncIsCalled()
{
InvalidOperationException ex = null;
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
await context.Response.StartAsync();
ex = Assert.Throws<InvalidOperationException>(() => context.Response.OnStarting(_ => Task.CompletedTask, null));
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
Assert.NotNull(ex);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
}
[Fact]
public async Task StartAsync_StartsResponse()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
await context.Response.StartAsync();
Assert.True(context.Response.HasStarted);
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
}
[Fact]
public async Task StartAsync_WithoutFinalFlushDoesNotFlushUntilResponseEnd()
{
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
await context.Response.StartAsync();
// Verify that the response isn't flushed by verifying the TCS isn't set
var res = await Task.WhenAny(tcs.Task, Task.Delay(1000)) == tcs.Task;
await context.Response.WriteAsync("hello, world");
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
withLength: 12,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
tcs.SetResult();
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray()));
}
[Fact]
public async Task StartAsync_FlushStillFlushesBody()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
await context.Response.StartAsync();
// Verify that the response isn't flushed by verifying the TCS isn't set
await context.Response.Body.FlushAsync();
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
}
[Fact]
public async Task StartAsync_WithContentLengthAndEmptyWriteCallsFinalFlush()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
context.Response.ContentLength = 0;
await context.Response.StartAsync();
await context.Response.WriteAsync("");
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task StartAsync_SingleWriteCallsFinalFlush()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
await context.Response.StartAsync();
await context.Response.WriteAsync("hello, world");
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
withLength: 12,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray()));
}
[Fact]
public async Task StartAsync_ContentLength_ThrowsException_DataIsFlushed_ConnectionReset()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
context.Response.ContentLength = 11;
await context.Response.StartAsync();
throw new Exception();
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 37,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await WaitForStreamErrorAsync(1, Http2ErrorCode.INTERNAL_ERROR, "");
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("11", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task StartAsync_ThrowsException_DataIsFlushed()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
await context.Response.StartAsync();
throw new Exception();
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await WaitForStreamErrorAsync(1, Http2ErrorCode.INTERNAL_ERROR, "");
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
}
[Fact]
public async Task ContentLength_Response_TooFewBytesWritten_Resets()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(context =>
{
context.Response.ContentLength = 11;
return context.Response.WriteAsync("hello,");
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 37,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 6,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await WaitForStreamErrorAsync(1, Http2ErrorCode.INTERNAL_ERROR, CoreStrings.FormatTooFewBytesWritten(6, 11));
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("11", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task MaxRequestBodySize_ContentLengthUnder_200()
{
_serviceContext.ServerOptions.Limits.MaxRequestBodySize = 15;
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
};
await InitializeConnectionAsync(async context =>
{
var buffer = new byte[100];
var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
Assert.Equal(12, read);
read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
Assert.Equal(0, read);
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[12], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task MaxRequestBodySize_ContentLengthOver_413()
{
#pragma warning disable CS0618 // Type or member is obsolete
BadHttpRequestException exception = null;
#pragma warning restore CS0618 // Type or member is obsolete
_serviceContext.ServerOptions.Limits.MaxRequestBodySize = 10;
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
};
await InitializeConnectionAsync(async context =>
{
#pragma warning disable CS0618 // Type or member is obsolete
exception = await Assert.ThrowsAsync<BadHttpRequestException>(async () =>
#pragma warning restore CS0618 // Type or member is obsolete
{
var buffer = new byte[100];
while (await context.Request.Body.ReadAsync(buffer, 0, buffer.Length) > 0) { }
});
ExceptionDispatchInfo.Capture(exception).Throw();
});
await StartStreamAsync(1, headers, endStream: false);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 40,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await WaitForStreamErrorAsync(expectedStreamId: 1, Http2ErrorCode.NO_ERROR, null);
// Logged without an exception.
Assert.Contains(LogMessages, m => m.Message.Contains("the application completed without reading the entire request body."));
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("413", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
Assert.NotNull(exception);
}
[Fact]
public async Task MaxRequestBodySize_NoContentLength_Under_200()
{
_serviceContext.ServerOptions.Limits.MaxRequestBodySize = 15;
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
var buffer = new byte[100];
var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
Assert.Equal(12, read);
read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
Assert.Equal(0, read);
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[12], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task MaxRequestBodySize_NoContentLength_Over_413()
{
#pragma warning disable CS0618 // Type or member is obsolete
BadHttpRequestException exception = null;
#pragma warning restore CS0618 // Type or member is obsolete
_serviceContext.ServerOptions.Limits.MaxRequestBodySize = 10;
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
#pragma warning disable CS0618 // Type or member is obsolete
exception = await Assert.ThrowsAsync<BadHttpRequestException>(async () =>
#pragma warning restore CS0618 // Type or member is obsolete
{
var buffer = new byte[100];
while (await context.Request.Body.ReadAsync(buffer, 0, buffer.Length) > 0) { }
});
ExceptionDispatchInfo.Capture(exception).Throw();
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[6], endStream: false);
await SendDataAsync(1, new byte[6], endStream: false);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 40,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await WaitForStreamErrorAsync(1, Http2ErrorCode.NO_ERROR, null);
// Logged without an exception.
Assert.Contains(LogMessages, m => m.Message.Contains("the application completed without reading the entire request body."));
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("413", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
Assert.NotNull(exception);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task MaxRequestBodySize_AppCanLowerLimit(bool includeContentLength)
{
#pragma warning disable CS0618 // Type or member is obsolete
BadHttpRequestException exception = null;
#pragma warning restore CS0618 // Type or member is obsolete
_serviceContext.ServerOptions.Limits.MaxRequestBodySize = 20;
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
if (includeContentLength)
{
headers.Concat(new[]
{
new KeyValuePair<string, string>(HeaderNames.ContentLength, "18"),
});
}
await InitializeConnectionAsync(async context =>
{
Assert.False(context.Features.Get<IHttpMaxRequestBodySizeFeature>().IsReadOnly);
context.Features.Get<IHttpMaxRequestBodySizeFeature>().MaxRequestBodySize = 17;
#pragma warning disable CS0618 // Type or member is obsolete
exception = await Assert.ThrowsAsync<BadHttpRequestException>(async () =>
#pragma warning restore CS0618 // Type or member is obsolete
{
var buffer = new byte[100];
while (await context.Request.Body.ReadAsync(buffer, 0, buffer.Length) > 0) { }
});
Assert.True(context.Features.Get<IHttpMaxRequestBodySizeFeature>().IsReadOnly);
ExceptionDispatchInfo.Capture(exception).Throw();
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[6], endStream: false);
await SendDataAsync(1, new byte[6], endStream: false);
await SendDataAsync(1, new byte[6], endStream: false);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 40,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await WaitForStreamErrorAsync(1, Http2ErrorCode.NO_ERROR, null);
// Logged without an exception.
Assert.Contains(LogMessages, m => m.Message.Contains("the application completed without reading the entire request body."));
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("413", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
Assert.NotNull(exception);
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task MaxRequestBodySize_AppCanRaiseLimit(bool includeContentLength)
{
_serviceContext.ServerOptions.Limits.MaxRequestBodySize = 10;
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
if (includeContentLength)
{
headers.Concat(new[]
{
new KeyValuePair<string, string>(HeaderNames.ContentLength, "12"),
});
}
await InitializeConnectionAsync(async context =>
{
Assert.False(context.Features.Get<IHttpMaxRequestBodySizeFeature>().IsReadOnly);
context.Features.Get<IHttpMaxRequestBodySizeFeature>().MaxRequestBodySize = 12;
var buffer = new byte[100];
var read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
Assert.Equal(12, read);
Assert.True(context.Features.Get<IHttpMaxRequestBodySizeFeature>().IsReadOnly);
read = await context.Request.Body.ReadAsync(buffer, 0, buffer.Length);
Assert.Equal(0, read);
});
await StartStreamAsync(1, headers, endStream: false);
await SendDataAsync(1, new byte[12], endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task ResponseHeaders_WithNonAscii_Throws()
{
await InitializeConnectionAsync(async context =>
{
Assert.Throws<InvalidOperationException>(() => context.Response.Headers.Append("Custom你好Name", "Custom Value"));
Assert.Throws<InvalidOperationException>(() => context.Response.ContentType = "Custom 你好 Type");
Assert.Throws<InvalidOperationException>(() => context.Response.Headers.Append("CustomName", "Custom 你好 Value"));
Assert.Throws<InvalidOperationException>(() => context.Response.Headers.Append("CustomName", "Custom \r Value"));
await context.Response.WriteAsync("Hello World");
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
}
[Fact]
public async Task ResponseHeaders_WithNonAsciiAndCustomEncoder_Works()
{
_serviceContext.ServerOptions.ResponseHeaderEncodingSelector = _ => Encoding.UTF8;
_serviceContext.ServerOptions.RequestHeaderEncodingSelector = _ => Encoding.UTF8; // Used for decoding response.
await InitializeConnectionAsync(async context =>
{
Assert.Throws<InvalidOperationException>(() => context.Response.Headers.Append("Custom你好Name", "Custom Value"));
Assert.Throws<InvalidOperationException>(() => context.Response.Headers.Append("CustomName", "Custom \r Value"));
context.Response.ContentType = "Custom 你好 Type";
context.Response.Headers.Append("CustomName", "Custom 你好 Value");
await context.Response.WriteAsync("Hello World");
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 84,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Equal(4, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("Custom 你好 Type", _decodedHeaders[HeaderNames.ContentType]);
Assert.Equal("Custom 你好 Value", _decodedHeaders["CustomName"]);
}
[Fact]
public async Task ResponseHeaders_WithInvalidValuesAndCustomEncoder_AbortsConnection()
{
var encoding = Encoding.GetEncoding(Encoding.Latin1.CodePage, EncoderFallback.ExceptionFallback,
DecoderFallback.ExceptionFallback);
_serviceContext.ServerOptions.ResponseHeaderEncodingSelector = _ => encoding;
await InitializeConnectionAsync(async context =>
{
context.Response.Headers.Append("CustomName", "Custom 你好 Value");
await context.Response.WriteAsync("Hello World");
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await WaitForConnectionErrorAsync<Exception>(ignoreNonGoAwayFrames: false, int.MaxValue, Http2ErrorCode.INTERNAL_ERROR);
AssertConnectionEndReason(ConnectionEndReason.ErrorWritingHeaders);
}
[Fact]
public async Task ResponseTrailers_WithoutData_Sent()
{
await InitializeConnectionAsync(context =>
{
context.Response.AppendTrailer("CustomName", "Custom Value");
return Task.CompletedTask;
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var trailersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 25,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
_decodedHeaders.Clear();
_hpackDecoder.Decode(trailersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Single(_decodedHeaders);
Assert.Equal("Custom Value", _decodedHeaders["CustomName"]);
}
[Fact]
public async Task ResponseTrailers_WithExeption500_Cleared()
{
await InitializeConnectionAsync(context =>
{
context.Response.AppendTrailer("CustomName", "Custom Value");
throw new NotImplementedException("Test Exception");
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_STREAM | Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("500", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task ResponseTrailers_WorksAcrossMultipleStreams_Cleared()
{
await InitializeConnectionAsync(context =>
{
Assert.True(context.Response.SupportsTrailers(), "SupportsTrailers");
var trailers = context.Features.Get<IHttpResponseTrailersFeature>().Trailers;
Assert.False(trailers.IsReadOnly);
context.Response.AppendTrailer("CustomName", "Custom Value");
return Task.CompletedTask;
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var headersFrame1 = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var trailersFrame1 = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 25,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
var headersFrame2 = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 6,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 3);
var trailersFrame2 = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 1,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 3);
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(trailersFrame1.PayloadSequence, endHeaders: true, handler: this);
Assert.Single(_decodedHeaders);
Assert.Equal("Custom Value", _decodedHeaders["CustomName"]);
_decodedHeaders.Clear();
_hpackDecoder.Decode(trailersFrame2.PayloadSequence, endHeaders: true, handler: this);
Assert.Single(_decodedHeaders);
Assert.Equal("Custom Value", _decodedHeaders["CustomName"]);
}
[Fact]
public async Task ResponseTrailers_WithData_Sent()
{
await InitializeConnectionAsync(async context =>
{
await context.Response.WriteAsync("Hello World");
context.Response.AppendTrailer("CustomName", "Custom Value");
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
var trailersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 25,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
_decodedHeaders.Clear();
_hpackDecoder.Decode(trailersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Single(_decodedHeaders);
Assert.Equal("Custom Value", _decodedHeaders["CustomName"]);
}
[Fact]
public async Task ResponseTrailers_WithContinuation_Sent()
{
var largeHeader = new string('a', 1024 * 3);
await InitializeConnectionAsync(async context =>
{
await context.Response.WriteAsync("Hello World");
// The first five fill the first frame
context.Response.AppendTrailer("CustomName0", largeHeader);
context.Response.AppendTrailer("CustomName1", largeHeader);
context.Response.AppendTrailer("CustomName2", largeHeader);
context.Response.AppendTrailer("CustomName3", largeHeader);
context.Response.AppendTrailer("CustomName4", largeHeader);
// This one spills over to the next frame
context.Response.AppendTrailer("CustomName5", largeHeader);
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
var trailersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 15440,
withFlags: (byte)Http2HeadersFrameFlags.END_STREAM,
withStreamId: 1);
var trailersContinuationFrame = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 3088,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
_decodedHeaders.Clear();
_hpackDecoder.Decode(trailersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(5, _decodedHeaders.Count);
Assert.Equal(largeHeader, _decodedHeaders["CustomName0"]);
Assert.Equal(largeHeader, _decodedHeaders["CustomName1"]);
Assert.Equal(largeHeader, _decodedHeaders["CustomName2"]);
Assert.Equal(largeHeader, _decodedHeaders["CustomName3"]);
Assert.Equal(largeHeader, _decodedHeaders["CustomName4"]);
_decodedHeaders.Clear();
_hpackDecoder.Decode(trailersContinuationFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Single(_decodedHeaders);
Assert.Equal(largeHeader, _decodedHeaders["CustomName5"]);
}
[Fact]
public async Task ResponseTrailers_WithNonAscii_Throws()
{
await InitializeConnectionAsync(async context =>
{
await context.Response.WriteAsync("Hello World");
Assert.Throws<InvalidOperationException>(() => context.Response.AppendTrailer("Custom你好Name", "Custom Value"));
Assert.Throws<InvalidOperationException>(() => context.Response.AppendTrailer("CustomName", "Custom 你好 Value"));
Assert.Throws<InvalidOperationException>(() => context.Response.AppendTrailer("CustomName", "Custom \r Value"));
// ETag is one of the few special cased trailers. Accept is not.
Assert.Throws<InvalidOperationException>(() => context.Features.Get<IHttpResponseTrailersFeature>().Trailers.ETag = "Custom 你好 Tag");
Assert.Throws<InvalidOperationException>(() => context.Features.Get<IHttpResponseTrailersFeature>().Trailers.Accept = "Custom 你好 Tag");
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
}
[Fact]
public async Task ResponseTrailers_WithNonAsciiAndCustomEncoder_Works()
{
_serviceContext.ServerOptions.ResponseHeaderEncodingSelector = _ => Encoding.UTF8;
_serviceContext.ServerOptions.RequestHeaderEncodingSelector = _ => Encoding.UTF8; // Used for decoding response.
await InitializeConnectionAsync(async context =>
{
await context.Response.WriteAsync("Hello World");
Assert.Throws<InvalidOperationException>(() => context.Response.AppendTrailer("Custom你好Name", "Custom Value"));
Assert.Throws<InvalidOperationException>(() => context.Response.AppendTrailer("CustomName", "Custom \r Value"));
context.Response.AppendTrailer("CustomName", "Custom 你好 Value");
// ETag is one of the few special cased trailers. Accept is not.
context.Features.Get<IHttpResponseTrailersFeature>().Trailers.ETag = "Custom 你好 Tag";
context.Features.Get<IHttpResponseTrailersFeature>().Trailers.Accept = "Custom 你好 Accept";
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
var trailersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 80,
withFlags: (byte)(Http2HeadersFrameFlags.END_STREAM | Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
_decodedHeaders.Clear();
_hpackDecoder.Decode(trailersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Equal("Custom 你好 Value", _decodedHeaders["CustomName"]);
Assert.Equal("Custom 你好 Tag", _decodedHeaders[HeaderNames.ETag]);
Assert.Equal("Custom 你好 Accept", _decodedHeaders[HeaderNames.Accept]);
}
[Fact]
public async Task ResponseTrailers_WithInvalidValuesAndCustomEncoder_AbortsConnection()
{
var encoding = Encoding.GetEncoding(Encoding.Latin1.CodePage, EncoderFallback.ExceptionFallback,
DecoderFallback.ExceptionFallback);
_serviceContext.ServerOptions.ResponseHeaderEncodingSelector = _ => encoding;
await InitializeConnectionAsync(async context =>
{
await context.Response.WriteAsync("Hello World");
context.Response.AppendTrailer("CustomName", "Custom 你好 Value");
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
await WaitForConnectionErrorAsync<Exception>(ignoreNonGoAwayFrames: false, int.MaxValue, Http2ErrorCode.INTERNAL_ERROR);
AssertConnectionEndReason(ConnectionEndReason.ErrorWritingHeaders);
}
[Fact]
public async Task ResponseTrailers_SingleLong_SplitsTrailersToContinuationFrames()
{
var trailerValue = new string('a', (int)Http2PeerSettings.DefaultMaxFrameSize);
await InitializeConnectionAsync(async context =>
{
await context.Response.WriteAsync("Hello World");
context.Response.AppendTrailer("too_long", trailerValue);
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
var trailerFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 16384,
withFlags: (byte)Http2HeadersFrameFlags.END_STREAM,
withStreamId: 1);
var trailierContinuation = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 13,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false).DefaultTimeout();
var buffer = new byte[trailerFrame.PayloadLength + trailierContinuation.PayloadLength];
trailerFrame.PayloadSequence.CopyTo(buffer);
trailierContinuation.PayloadSequence.CopyTo(buffer.AsSpan(trailerFrame.PayloadLength));
_hpackDecoder.Decode(buffer, endHeaders: true, handler: this);
Assert.Single(_decodedHeaders);
Assert.Equal(trailerValue, _decodedHeaders["too_long"]);
}
[Fact]
public async Task ResponseTrailers_ShortHeadersBeforeSingleLong_MultipleRequests_ShortHeadersInDynamicTable()
{
var trailerValue = new string('a', (int)Http2PeerSettings.DefaultMaxFrameSize);
await InitializeConnectionAsync(async context =>
{
await context.Response.WriteAsync("Hello World");
context.Response.AppendTrailer("a-key", "a-value");
context.Response.AppendTrailer("b-key", "b-value");
context.Response.AppendTrailer("too_long", trailerValue);
});
// Request 1
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
var request1TrailerFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 30,
withFlags: (byte)Http2HeadersFrameFlags.END_STREAM,
withStreamId: 1);
var request1TrailierContinuation1 = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 16384,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
var request1TrailierContinuation2 = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 13,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
_hpackDecoder.Decode(request1TrailerFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal("a-value", _decodedHeaders["a-key"]);
Assert.Equal("b-value", _decodedHeaders["b-key"]);
_decodedHeaders.Clear();
_hpackDecoder.Decode(request1TrailierContinuation1.PayloadSequence, endHeaders: false, handler: this);
Assert.Empty(_decodedHeaders);
_hpackDecoder.Decode(request1TrailierContinuation2.PayloadSequence, endHeaders: true, handler: this);
Assert.Equal(trailerValue, _decodedHeaders["too_long"]);
// Request 2
await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
withLength: 2,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 3);
await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 3);
var request2TrailerFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 2,
withFlags: (byte)Http2HeadersFrameFlags.END_STREAM,
withStreamId: 3);
var request2TrailierContinuation1 = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 16384,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 3);
var request2TrailierContinuation2 = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 13,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 3);
_hpackDecoder.Decode(request2TrailerFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal("a-value", _decodedHeaders["a-key"]);
Assert.Equal("b-value", _decodedHeaders["b-key"]);
_decodedHeaders.Clear();
_hpackDecoder.Decode(request2TrailierContinuation1.PayloadSequence, endHeaders: false, handler: this);
Assert.Empty(_decodedHeaders);
_hpackDecoder.Decode(request2TrailierContinuation2.PayloadSequence, endHeaders: true, handler: this);
Assert.Equal(trailerValue, _decodedHeaders["too_long"]);
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false).DefaultTimeout();
}
[Fact]
public async Task ResponseTrailers_DoubleLong_SplitsTrailersToContinuationFrames()
{
var trailerValue = new string('a', (int)Http2PeerSettings.DefaultMaxFrameSize);
await InitializeConnectionAsync(async context =>
{
await context.Response.WriteAsync("Hello World");
context.Response.AppendTrailer("too_long", trailerValue);
context.Response.AppendTrailer("too_long2", trailerValue);
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
var frame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 16384,
withFlags: (byte)Http2HeadersFrameFlags.END_STREAM,
withStreamId: 1);
_hpackDecoder.Decode(frame.PayloadSequence, endHeaders: false, handler: this);
Assert.Empty(_decodedHeaders);
frame = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 13,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
_hpackDecoder.Decode(frame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(trailerValue, _decodedHeaders["too_long"]);
_decodedHeaders.Clear();
frame = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 16384,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
_hpackDecoder.Decode(frame.PayloadSequence, endHeaders: false, handler: this);
Assert.Empty(_decodedHeaders);
frame = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 14,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
_hpackDecoder.Decode(frame.PayloadSequence, endHeaders: true, handler: this);
Assert.Equal(trailerValue, _decodedHeaders["too_long2"]);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false).DefaultTimeout();
}
[Fact]
public async Task ResponseTrailers_ShortThenLongThenShort_SplitsTrailers()
{
var trailerValue = new string('a', (int)Http2PeerSettings.DefaultMaxFrameSize);
string shortValue = "testValue";
await InitializeConnectionAsync(async context =>
{
await context.Response.WriteAsync("Hello World");
context.Response.AppendTrailer("short", shortValue);
context.Response.AppendTrailer("long", trailerValue);
context.Response.AppendTrailer("short2", shortValue);
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
var trailerFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 17,
withFlags: (byte)Http2HeadersFrameFlags.END_STREAM,
withStreamId: 1);
_hpackDecoder.Decode(trailerFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Single(_decodedHeaders);
Assert.Equal(shortValue, _decodedHeaders["short"]);
_decodedHeaders.Clear();
var trailierContinuation1 = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 16384,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
_hpackDecoder.Decode(trailierContinuation1.PayloadSequence, endHeaders: false, handler: this);
Assert.Empty(_decodedHeaders);
var trailierContinuation2 = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 27,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
_hpackDecoder.Decode(trailierContinuation2.PayloadSequence, endHeaders: true, handler: this);
Assert.Equal(trailerValue, _decodedHeaders["long"]);
Assert.Equal(shortValue, _decodedHeaders["short2"]);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false).DefaultTimeout();
}
[Fact]
public async Task LongResponseHeader_FollowedBy_LongResponseTrailer_SplitsTrailersToContinuationFrames()
{
var value = new string('a', (int)Http2PeerSettings.DefaultMaxFrameSize);
await InitializeConnectionAsync(async context =>
{
context.Response.Headers["too_long_header"] = value;
await context.Response.WriteAsync("Hello World");
context.Response.AppendTrailer("too_long_trailer", value);
});
// Stream 1
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
// Response headers
var frame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
_hpackDecoder.Decode(frame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Equal("200", _decodedHeaders[":status"]);
Assert.Equal("Sat, 01 Jan 2000 00:00:00 GMT", _decodedHeaders["date"]);
_decodedHeaders.Clear();
frame = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 16384,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
_hpackDecoder.Decode(frame.PayloadSequence, endHeaders: false, handler: this);
Assert.Empty(_decodedHeaders);
frame = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 20,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
_hpackDecoder.Decode(frame.PayloadSequence, endHeaders: true, handler: this);
Assert.Single(_decodedHeaders);
Assert.Equal(value, _decodedHeaders["too_long_header"]);
_decodedHeaders.Clear();
// Data
await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
// Trailers
frame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 16384,
withFlags: (byte)Http2HeadersFrameFlags.END_STREAM,
withStreamId: 1);
_hpackDecoder.Decode(frame.PayloadSequence, endHeaders: false, handler: this);
Assert.Empty(_decodedHeaders);
frame = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 21,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
_hpackDecoder.Decode(frame.PayloadSequence, endHeaders: true, handler: this);
Assert.Single(_decodedHeaders);
Assert.Equal(value, _decodedHeaders["too_long_trailer"]);
_decodedHeaders.Clear();
// Stream 3
await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
// Response headers
frame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 2,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 3);
_hpackDecoder.Decode(frame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Equal("200", _decodedHeaders[":status"]);
Assert.Equal("Sat, 01 Jan 2000 00:00:00 GMT", _decodedHeaders["date"]);
_decodedHeaders.Clear();
frame = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 16384,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 3);
_hpackDecoder.Decode(frame.PayloadSequence, endHeaders: false, handler: this);
Assert.Empty(_decodedHeaders);
frame = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 20,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 3);
_hpackDecoder.Decode(frame.PayloadSequence, endHeaders: true, handler: this);
Assert.Single(_decodedHeaders);
Assert.Equal(value, _decodedHeaders["too_long_header"]);
_decodedHeaders.Clear();
// Data
await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 3);
// Trailers
frame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 16384,
withFlags: (byte)Http2HeadersFrameFlags.END_STREAM,
withStreamId: 3);
_hpackDecoder.Decode(frame.PayloadSequence, endHeaders: false, handler: this);
Assert.Empty(_decodedHeaders);
frame = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 21,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 3);
_hpackDecoder.Decode(frame.PayloadSequence, endHeaders: true, handler: this);
Assert.Single(_decodedHeaders);
Assert.Equal(value, _decodedHeaders["too_long_trailer"]);
_decodedHeaders.Clear();
await StopConnectionAsync(expectedLastStreamId: 3, ignoreNonGoAwayFrames: false).DefaultTimeout();
}
[Fact]
public async Task ResponseTrailers_WithLargeUnflushedData_DataExceedsFlowControlAvailableAndNotSentWithTrailers()
{
const int windowSize = (int)Http2PeerSettings.DefaultMaxFrameSize;
_clientSettings.InitialWindowSize = windowSize;
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
await context.Response.StartAsync();
// Body exceeds flow control available and requires the client to allow more
// data via updating the window
context.Response.BodyWriter.GetMemory(windowSize + 1);
context.Response.BodyWriter.Advance(windowSize + 1);
context.Response.AppendTrailer("CustomName", "Custom Value");
}).DefaultTimeout();
await StartStreamAsync(1, headers, endStream: true).DefaultTimeout();
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1).DefaultTimeout();
await ExpectAsync(Http2FrameType.DATA,
withLength: 16384,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1).DefaultTimeout();
var dataTask = ExpectAsync(Http2FrameType.DATA,
withLength: 1,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1).DefaultTimeout();
// Reading final frame of data requires window update
// Verify this data task is waiting on window update
Assert.False(dataTask.IsCompletedSuccessfully);
await SendWindowUpdateAsync(1, 1);
await dataTask;
var trailersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 25,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1).DefaultTimeout();
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false).DefaultTimeout();
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
_decodedHeaders.Clear();
_hpackDecoder.Decode(trailersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Single(_decodedHeaders);
Assert.Equal("Custom Value", _decodedHeaders["CustomName"]);
}
[Fact]
public async Task ResponseTrailers_WithUnflushedData_DataSentWithTrailers()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
await context.Response.StartAsync();
var s = context.Response.BodyWriter.GetMemory(1);
s.Span[0] = byte.MaxValue;
context.Response.BodyWriter.Advance(1);
context.Response.AppendTrailer("CustomName", "Custom Value");
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 1,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
var trailersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 25,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
_decodedHeaders.Clear();
_hpackDecoder.Decode(trailersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Single(_decodedHeaders);
Assert.Equal("Custom Value", _decodedHeaders["CustomName"]);
}
[Fact]
public async Task ApplicationException_BeforeFirstWrite_Sends500()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(context =>
{
throw new Exception("App Faulted");
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
Assert.Contains(LogMessages, m => (m.Exception?.Message.Contains("App Faulted") ?? false) && m.LogLevel == LogLevel.Error);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("500", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task ApplicationException_AfterFirstWrite_Resets()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
await context.Response.WriteAsync("hello,");
throw new Exception("App Faulted");
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 6,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await WaitForStreamErrorAsync(1, Http2ErrorCode.INTERNAL_ERROR, "App Faulted");
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
}
[Fact]
public async Task RST_STREAM_Received_AbortsStream()
{
await InitializeConnectionAsync(_waitForAbortApplication);
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await SendRstStreamAsync(1);
await WaitForAllStreamsAsync();
Assert.Contains(1, _abortedStreamIds);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task RST_STREAM_Received_AbortsStream_StreamFlushedDataNotSent()
{
await InitializeConnectionAsync(async context =>
{
var streamIdFeature = context.Features.Get<IHttp2StreamIdFeature>();
var sem = new SemaphoreSlim(0);
context.RequestAborted.Register(() =>
{
lock (_abortedStreamIdsLock)
{
_abortedStreamIds.Add(streamIdFeature.StreamId);
}
sem.Release();
});
await sem.WaitAsync().DefaultTimeout();
await context.Response.Body.WriteAsync(new byte[10], 0, 10);
_runningStreams[streamIdFeature.StreamId].TrySetResult();
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await SendRstStreamAsync(1);
await WaitForAllStreamsAsync();
Assert.Contains(1, _abortedStreamIds);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task RST_STREAM_Received_AbortsStream_PipeWriterFlushedDataNotSent()
{
await InitializeConnectionAsync(async context =>
{
var streamIdFeature = context.Features.Get<IHttp2StreamIdFeature>();
var sem = new SemaphoreSlim(0);
context.RequestAborted.Register(() =>
{
lock (_abortedStreamIdsLock)
{
_abortedStreamIds.Add(streamIdFeature.StreamId);
}
sem.Release();
});
await sem.WaitAsync().DefaultTimeout();
context.Response.BodyWriter.GetMemory();
context.Response.BodyWriter.Advance(10);
await context.Response.BodyWriter.FlushAsync();
_runningStreams[streamIdFeature.StreamId].TrySetResult();
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await SendRstStreamAsync(1);
await WaitForAllStreamsAsync();
Assert.Contains(1, _abortedStreamIds);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task RST_STREAM_WaitingForRequestBody_RequestBodyThrows()
{
var sem = new SemaphoreSlim(0);
await InitializeConnectionAsync(async context =>
{
var streamIdFeature = context.Features.Get<IHttp2StreamIdFeature>();
try
{
var readTask = context.Request.Body.ReadAsync(new byte[100], 0, 100).DefaultTimeout();
sem.Release();
await readTask;
_runningStreams[streamIdFeature.StreamId].TrySetException(new Exception("ReadAsync was expected to throw."));
}
catch (IOException) // Expected failure
{
await context.Response.Body.WriteAsync(new byte[10], 0, 10);
lock (_abortedStreamIdsLock)
{
_abortedStreamIds.Add(streamIdFeature.StreamId);
}
_runningStreams[streamIdFeature.StreamId].TrySetResult();
}
catch (Exception ex)
{
_runningStreams[streamIdFeature.StreamId].TrySetException(ex);
}
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
await sem.WaitAsync().DefaultTimeout();
await SendRstStreamAsync(1);
await WaitForAllStreamsAsync();
Assert.Contains(1, _abortedStreamIds);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task RST_STREAM_IncompleteRequest_RequestBodyThrows()
{
var sem = new SemaphoreSlim(0);
await InitializeConnectionAsync(async context =>
{
var streamIdFeature = context.Features.Get<IHttp2StreamIdFeature>();
try
{
var read = await context.Request.Body.ReadAsync(new byte[100], 0, 100).DefaultTimeout();
var readTask = context.Request.Body.ReadAsync(new byte[100], 0, 100).DefaultTimeout();
sem.Release();
await readTask;
_runningStreams[streamIdFeature.StreamId].TrySetException(new Exception("ReadAsync was expected to throw."));
}
catch (IOException) // Expected failure
{
await context.Response.Body.WriteAsync(new byte[10], 0, 10);
lock (_abortedStreamIdsLock)
{
_abortedStreamIds.Add(streamIdFeature.StreamId);
}
_runningStreams[streamIdFeature.StreamId].TrySetResult();
}
catch (Exception ex)
{
_runningStreams[streamIdFeature.StreamId].TrySetException(ex);
}
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
await SendDataAsync(1, new byte[10], endStream: false);
await sem.WaitAsync().DefaultTimeout();
await SendRstStreamAsync(1);
await WaitForAllStreamsAsync();
Assert.Contains(1, _abortedStreamIds);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task RequestAbort_SendsRstStream()
{
await InitializeConnectionAsync(async context =>
{
var streamIdFeature = context.Features.Get<IHttp2StreamIdFeature>();
try
{
context.RequestAborted.Register(() =>
{
lock (_abortedStreamIdsLock)
{
_abortedStreamIds.Add(streamIdFeature.StreamId);
}
_runningStreams[streamIdFeature.StreamId].TrySetResult();
});
context.Abort();
// Not sent
await context.Response.Body.WriteAsync(new byte[10], 0, 10);
await _runningStreams[streamIdFeature.StreamId].Task;
}
catch (Exception ex)
{
_runningStreams[streamIdFeature.StreamId].TrySetException(ex);
}
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await WaitForStreamErrorAsync(expectedStreamId: 1, Http2ErrorCode.INTERNAL_ERROR, CoreStrings.ConnectionAbortedByApplication);
await WaitForAllStreamsAsync();
Assert.Contains(1, _abortedStreamIds);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task RequestAbort_AfterDataSent_SendsRstStream()
{
await InitializeConnectionAsync(async context =>
{
var streamIdFeature = context.Features.Get<IHttp2StreamIdFeature>();
try
{
context.RequestAborted.Register(() =>
{
lock (_abortedStreamIdsLock)
{
_abortedStreamIds.Add(streamIdFeature.StreamId);
}
_runningStreams[streamIdFeature.StreamId].TrySetResult();
});
await context.Response.Body.WriteAsync(new byte[10], 0, 10);
context.Abort();
// Not sent
await context.Response.Body.WriteAsync(new byte[11], 0, 11);
await _runningStreams[streamIdFeature.StreamId].Task;
}
catch (Exception ex)
{
_runningStreams[streamIdFeature.StreamId].TrySetException(ex);
}
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 10,
withFlags: 0,
withStreamId: 1);
await WaitForStreamErrorAsync(expectedStreamId: 1, Http2ErrorCode.INTERNAL_ERROR, CoreStrings.ConnectionAbortedByApplication);
await WaitForAllStreamsAsync();
Assert.Contains(1, _abortedStreamIds);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task RequestAbort_ThrowsOperationCanceledExceptionFromSubsequentRequestBodyStreamRead()
{
OperationCanceledException thrownEx = null;
await InitializeConnectionAsync(async context =>
{
context.Abort();
var buffer = new byte[100];
var thrownExTask = Assert.ThrowsAnyAsync<OperationCanceledException>(() => context.Request.Body.ReadAsync(buffer, 0, buffer.Length));
Assert.True(thrownExTask.IsCompleted);
thrownEx = await thrownExTask;
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
await WaitForStreamErrorAsync(expectedStreamId: 1, Http2ErrorCode.INTERNAL_ERROR, CoreStrings.ConnectionAbortedByApplication);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
Assert.NotNull(thrownEx);
Assert.IsType<ConnectionAbortedException>(thrownEx);
Assert.Equal(CoreStrings.ConnectionAbortedByApplication, thrownEx.Message);
}
[Fact]
public async Task RequestAbort_ThrowsOperationCanceledExceptionFromOngoingRequestBodyStreamRead()
{
OperationCanceledException thrownEx = null;
await InitializeConnectionAsync(async context =>
{
var buffer = new byte[100];
var thrownExTask = Assert.ThrowsAnyAsync<OperationCanceledException>(() => context.Request.Body.ReadAsync(buffer, 0, buffer.Length));
Assert.False(thrownExTask.IsCompleted);
context.Abort();
thrownEx = await thrownExTask.DefaultTimeout();
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
await WaitForStreamErrorAsync(expectedStreamId: 1, Http2ErrorCode.INTERNAL_ERROR, CoreStrings.ConnectionAbortedByApplication);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
Assert.NotNull(thrownEx);
Assert.IsType<TaskCanceledException>(thrownEx);
Assert.Equal("The request was aborted", thrownEx.Message);
Assert.IsType<ConnectionAbortedException>(thrownEx.InnerException);
Assert.Equal(CoreStrings.ConnectionAbortedByApplication, thrownEx.InnerException.Message);
}
// Sync writes after async writes could block the write loop if the callback is not dispatched.
// https://github.com/aspnet/KestrelHttpServer/issues/2878
[Fact]
public async Task Write_DoesNotBlockWriteLoop()
{
const int windowSize = (int)Http2PeerSettings.DefaultMaxFrameSize;
_clientSettings.InitialWindowSize = windowSize;
await InitializeConnectionAsync(async context =>
{
var bodyControlFeature = context.Features.Get<IHttpBodyControlFeature>();
bodyControlFeature.AllowSynchronousIO = true;
// Fill the flow control window to create async back pressure.
await context.Response.Body.WriteAsync(new byte[windowSize + 1], 0, windowSize + 1);
context.Response.Body.Write(new byte[1], 0, 1);
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: windowSize,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await SendWindowUpdateAsync(1, 2);
await SendWindowUpdateAsync(0, 2);
// Remaining 1 byte from the first write and then the second write
await ExpectAsync(Http2FrameType.DATA,
withLength: 1,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 1,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task ResponseWithHeaderValueTooLarge_SplitsHeaderToContinuationFrames()
{
await InitializeConnectionAsync(async context =>
{
context.Response.Headers.ETag = new string('a', (int)Http2PeerSettings.DefaultMaxFrameSize);
await context.Response.WriteAsync("Hello World");
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
// Just the StatusCode gets written before aborting in the continuation frame
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
var headersFrame2 = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 16384,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
var headersFrame3 = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 5,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: true);
var temp = new byte[headersFrame.PayloadSequence.Length + headersFrame2.PayloadSequence.Length + headersFrame3.PayloadSequence.Length];
headersFrame.PayloadSequence.CopyTo(temp.AsSpan());
headersFrame2.PayloadSequence.CopyTo(temp.AsSpan((int)headersFrame.PayloadSequence.Length));
headersFrame3.PayloadSequence.CopyTo(temp.AsSpan((int)headersFrame.PayloadSequence.Length + (int)headersFrame2.PayloadSequence.Length));
_hpackDecoder.Decode(temp, endHeaders: true, handler: this);
Assert.Equal((int)Http2PeerSettings.DefaultMaxFrameSize, _decodedHeaders[HeaderNames.ETag].Length);
}
[Fact]
public async Task TooLargeHeaderFollowedByContinuationHeaders_Split()
{
await InitializeConnectionAsync(async context =>
{
context.Response.Headers.ETag = new string('a', (int)Http2PeerSettings.DefaultMaxFrameSize);
context.Response.Headers.TE = new string('a', 30);
await context.Response.WriteAsync("Hello World");
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
// Just the StatusCode gets written before aborting in the continuation frame
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
var headersFrame2 = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 16384,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
var headersFrame3 = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 40,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: true);
var temp = new byte[headersFrame.PayloadSequence.Length + headersFrame2.PayloadSequence.Length + headersFrame3.PayloadSequence.Length];
headersFrame.PayloadSequence.CopyTo(temp.AsSpan());
headersFrame2.PayloadSequence.CopyTo(temp.AsSpan((int)headersFrame.PayloadSequence.Length));
headersFrame3.PayloadSequence.CopyTo(temp.AsSpan((int)headersFrame.PayloadSequence.Length + (int)headersFrame2.PayloadSequence.Length));
_hpackDecoder.Decode(temp, endHeaders: true, handler: this);
Assert.Equal((int)Http2PeerSettings.DefaultMaxFrameSize, _decodedHeaders[HeaderNames.ETag].Length);
Assert.Equal(30, _decodedHeaders[HeaderNames.TE].Length);
}
[Fact]
public async Task TwoTooLargeHeaderFollowedByContinuationHeaders_Split()
{
await InitializeConnectionAsync(async context =>
{
context.Response.Headers.ETag = new string('a', (int)Http2PeerSettings.DefaultMaxFrameSize);
context.Response.Headers.TE = new string('b', (int)Http2PeerSettings.DefaultMaxFrameSize);
await context.Response.WriteAsync("Hello World");
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var frames = new Http2FrameWithPayload[5];
frames[0] = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
frames[1] = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 16384,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
frames[2] = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 5,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
frames[3] = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 16384,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
frames[4] = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 7,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: true);
var totalSize = frames.Sum(x => x.PayloadSequence.Length);
var temp = new byte[totalSize];
var destinationIndex = 0;
for (var i = 0; i < frames.Length; i++)
{
frames[i].PayloadSequence.CopyTo(temp.AsSpan(destinationIndex));
destinationIndex += (int)frames[i].PayloadSequence.Length;
}
_hpackDecoder.Decode(temp, endHeaders: true, handler: this);
Assert.Equal((int)Http2PeerSettings.DefaultMaxFrameSize, _decodedHeaders[HeaderNames.ETag].Length);
Assert.Equal((int)Http2PeerSettings.DefaultMaxFrameSize, _decodedHeaders[HeaderNames.TE].Length);
}
[Fact]
public async Task ClientRequestedLargerFrame_HeadersSplitByRequestedSize()
{
_clientSettings.MaxFrameSize = 17000;
_serviceContext.ServerOptions.Limits.Http2.MaxFrameSize = 17001;
await InitializeConnectionAsync(async context =>
{
context.Response.Headers.ETag = new string('a', 17002);
await context.Response.WriteAsync("Hello World");
}, expectedSettingsCount: 5);
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
// Just the StatusCode gets written before aborting in the continuation frame
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
var headersFrame1 = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 17000,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
var headersFrame2 = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 8,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: true);
}
[Fact]
public async Task ResponseWithMultipleHeaderValueTooLargeForFrame_SplitsHeaderToContinuationFrames()
{
await InitializeConnectionAsync(async context =>
{
// This size makes it fit to a single header, but not next to the response status etc.
context.Response.Headers.ETag = new string('a', (int)Http2PeerSettings.DefaultMaxFrameSize - 20);
await context.Response.WriteAsync("Hello World");
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
// Just the StatusCode gets written before aborting in the continuation frame
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
var headersFrame2 = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 16369,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: true);
var temp = new byte[headersFrame.PayloadSequence.Length + headersFrame2.PayloadSequence.Length];
headersFrame.PayloadSequence.CopyTo(temp.AsSpan());
headersFrame2.PayloadSequence.CopyTo(temp.AsSpan((int)headersFrame.PayloadSequence.Length));
_hpackDecoder.Decode(temp, endHeaders: true, handler: this);
Assert.Equal((int)Http2PeerSettings.DefaultMaxFrameSize - 20, _decodedHeaders[HeaderNames.ETag].Length);
}
[Fact]
public async Task ResponseWithHeaderNameTooLarge_SplitsHeaderToContinuationFrames()
{
var longHeaderName = new string('a', (int)Http2PeerSettings.DefaultMaxFrameSize);
var headerValue = "some value";
await InitializeConnectionAsync(async context =>
{
context.Response.Headers[longHeaderName] = headerValue;
await context.Response.WriteAsync("Hello World");
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
// Just the StatusCode gets written before aborting in the continuation frame
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
var headersFrame2 = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 16384,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
var headersFrame3 = await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 15,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: true);
var temp = new byte[headersFrame.PayloadSequence.Length + headersFrame2.PayloadSequence.Length + headersFrame3.PayloadSequence.Length];
headersFrame.PayloadSequence.CopyTo(temp.AsSpan());
headersFrame2.PayloadSequence.CopyTo(temp.AsSpan((int)headersFrame.PayloadSequence.Length));
headersFrame3.PayloadSequence.CopyTo(temp.AsSpan((int)headersFrame.PayloadSequence.Length + (int)headersFrame2.PayloadSequence.Length));
_hpackDecoder.Decode(temp, endHeaders: true, handler: this);
Assert.Equal(headerValue, _decodedHeaders[longHeaderName]);
}
[Fact]
public async Task ResponseHeader_OneMegaByte_SplitsHeaderToContinuationFrames()
{
int frameSize = (int)Http2PeerSettings.DefaultMaxFrameSize;
int count = 64;
var headerValue = new string('a', frameSize * count); // 1 MB value
await InitializeConnectionAsync(async context =>
{
context.Response.Headers["my"] = headerValue;
await context.Response.WriteAsync("Hello World");
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
// Just the StatusCode gets written before aborting in the continuation frame
await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
for (int i = 0; i < count; i++)
{
await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 16384,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
}
// One more frame because of the header name + size of header value + size header name + 2 * H encoding
await ExpectAsync(Http2FrameType.CONTINUATION,
withLength: 8,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: true);
Assert.False(ConnectionTags.ContainsKey(KestrelMetrics.ErrorTypeAttributeName), "Non-error reason shouldn't be added to error.type");
}
[Fact]
public async Task WriteAsync_PreCancelledCancellationToken_DoesNotAbort()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
// The cancellation is checked at the start of WriteAsync and no application state is changed.
await Assert.ThrowsAsync<OperationCanceledException>(() => context.Response.WriteAsync("hello,", new CancellationToken(true)));
Assert.False(context.Response.HasStarted);
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task WriteAsync_CancellationTokenTriggeredDueToFlowControl_SendRST()
{
var cts = new CancellationTokenSource();
var writeStarted = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
await context.Response.Body.FlushAsync(); // https://github.com/aspnet/KestrelHttpServer/issues/3031
var writeTask = context.Response.WriteAsync("hello,", cts.Token);
writeStarted.SetResult();
await Assert.ThrowsAsync<OperationCanceledException>(() => writeTask);
});
_clientSettings.InitialWindowSize = 0;
await SendSettingsAsync();
await ExpectAsync(Http2FrameType.SETTINGS,
withLength: 0,
withFlags: (byte)Http2SettingsFrameFlags.ACK,
withStreamId: 0);
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await writeStarted.Task;
cts.Cancel();
await WaitForStreamErrorAsync(1, Http2ErrorCode.INTERNAL_ERROR, null);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
}
[Fact]
public async Task GetMemoryAdvance_Works()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(httpContext =>
{
var response = httpContext.Response;
var memory = response.BodyWriter.GetMemory();
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("hello,");
fisrtPartOfResponse.CopyTo(memory);
response.BodyWriter.Advance(6);
memory = response.BodyWriter.GetMemory();
var secondPartOfResponse = Encoding.ASCII.GetBytes(" world");
secondPartOfResponse.CopyTo(memory);
response.BodyWriter.Advance(6);
return Task.CompletedTask;
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
withLength: 12,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray()));
}
[Fact]
public async Task GetMemoryAdvance_WithStartAsync_Works()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async httpContext =>
{
var response = httpContext.Response;
await response.StartAsync();
var memory = response.BodyWriter.GetMemory();
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("hello,");
fisrtPartOfResponse.CopyTo(memory);
response.BodyWriter.Advance(6);
memory = response.BodyWriter.GetMemory();
var secondPartOfResponse = Encoding.ASCII.GetBytes(" world");
secondPartOfResponse.CopyTo(memory);
response.BodyWriter.Advance(6);
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
withLength: 12,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray()));
}
[Fact]
public async Task WriteAsync_GetMemoryLargeWriteBeforeFirstFlush()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async httpContext =>
{
var response = httpContext.Response;
await response.StartAsync();
var memory = response.BodyWriter.GetMemory();
Assert.Equal(4096, memory.Length);
var fisrtPartOfResponse = Encoding.ASCII.GetBytes(new string('a', memory.Length));
fisrtPartOfResponse.CopyTo(memory);
response.BodyWriter.Advance(memory.Length);
memory = response.BodyWriter.GetMemory();
var secondPartOfResponse = Encoding.ASCII.GetBytes("aaaaaa");
secondPartOfResponse.CopyTo(memory);
response.BodyWriter.Advance(6);
await response.BodyWriter.FlushAsync();
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
withLength: 4102,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal(Encoding.ASCII.GetBytes(new string('a', 4102)), dataFrame.PayloadSequence.ToArray());
}
[Fact]
public async Task WriteAsync_WithGetMemoryWithInitialFlushWorks()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async httpContext =>
{
var response = httpContext.Response;
await response.BodyWriter.FlushAsync();
var memory = response.BodyWriter.GetMemory();
var fisrtPartOfResponse = Encoding.ASCII.GetBytes(new string('a', memory.Length));
fisrtPartOfResponse.CopyTo(memory);
response.BodyWriter.Advance(memory.Length);
memory = response.BodyWriter.GetMemory();
var secondPartOfResponse = Encoding.ASCII.GetBytes("aaaaaa");
secondPartOfResponse.CopyTo(memory);
response.BodyWriter.Advance(6);
await response.BodyWriter.FlushAsync();
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
withLength: 4102,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal(Encoding.ASCII.GetBytes(new string('a', 4102)), dataFrame.PayloadSequence.ToArray());
}
[Fact]
public async Task WriteAsync_GetMemoryMultipleAdvance()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async httpContext =>
{
var response = httpContext.Response;
await response.BodyWriter.FlushAsync();
var memory = response.BodyWriter.GetMemory(4096);
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("hello,");
fisrtPartOfResponse.CopyTo(memory);
response.BodyWriter.Advance(6);
var secondPartOfResponse = Encoding.ASCII.GetBytes(" world");
secondPartOfResponse.CopyTo(memory.Slice(6));
response.BodyWriter.Advance(6);
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
withLength: 12,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray()));
}
[Fact]
public async Task WriteAsync_GetSpanMultipleAdvance()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async httpContext =>
{
var response = httpContext.Response;
await response.BodyWriter.FlushAsync();
void NonAsyncMethod()
{
var span = response.BodyWriter.GetSpan();
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("hello,");
fisrtPartOfResponse.CopyTo(span);
response.BodyWriter.Advance(6);
var secondPartOfResponse = Encoding.ASCII.GetBytes(" world");
secondPartOfResponse.CopyTo(span.Slice(6));
response.BodyWriter.Advance(6);
}
NonAsyncMethod();
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
withLength: 12,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray()));
}
[Fact]
public async Task WriteAsync_GetMemoryAndWrite()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async httpContext =>
{
var response = httpContext.Response;
var memory = response.BodyWriter.GetMemory(4096);
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("hello,");
fisrtPartOfResponse.CopyTo(memory);
response.BodyWriter.Advance(6);
await response.WriteAsync(" world");
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
withLength: 12,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame.PayloadSequence.ToArray()));
}
[Fact]
public async Task WriteAsync_GetMemoryWithSizeHintAlwaysReturnsSameSize()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async httpContext =>
{
var response = httpContext.Response;
await response.StartAsync();
var memory = response.BodyWriter.GetMemory(0);
Assert.Equal(4096, memory.Length);
memory = response.BodyWriter.GetMemory(4096);
Assert.Equal(4096, memory.Length);
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var dataFrame = await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
}
[Fact]
public async Task WriteAsync_GetMemoryWithSizeHintAlwaysReturnsSameSizeStartAsync()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async httpContext =>
{
var response = httpContext.Response;
var memory = response.BodyWriter.GetMemory(0);
Assert.Equal(4096, memory.Length);
memory = response.BodyWriter.GetMemory(4096);
Assert.Equal(4096, memory.Length);
await Task.CompletedTask;
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task BodyWriterWriteAsync_OnAbortedRequest_ReturnsResultWithIsCompletedTrue()
{
var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async httpContext =>
{
try
{
httpContext.Abort();
var payload = Encoding.ASCII.GetBytes("hello,");
var result = await httpContext.Response.BodyWriter.WriteAsync(payload);
Assert.True(result.IsCompleted);
appTcs.SetResult();
}
catch (Exception e)
{
appTcs.SetException(e);
}
});
await StartStreamAsync(1, headers, endStream: true);
await ExpectAsync(Http2FrameType.RST_STREAM,
withLength: 4,
withFlags: (byte)Http2HeadersFrameFlags.NONE,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: true);
await appTcs.Task;
}
[Fact]
public async Task BodyWriterWriteAsync_OnCanceledPendingFlush_ReturnsResultWithIsCanceled()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async httpContext =>
{
httpContext.Response.BodyWriter.CancelPendingFlush();
var payload = Encoding.ASCII.GetBytes("hello,");
var cancelledResult = await httpContext.Response.BodyWriter.WriteAsync(payload);
Assert.True(cancelledResult.IsCanceled);
var secondPayload = Encoding.ASCII.GetBytes(" world");
var goodResult = await httpContext.Response.BodyWriter.WriteAsync(secondPayload);
Assert.False(goodResult.IsCanceled);
});
await StartStreamAsync(1, headers, endStream: true);
await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 6,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 6,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: true);
}
[Fact]
public async Task WriteAsync_BothPipeAndStreamWorks()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async httpContext =>
{
var response = httpContext.Response;
await response.StartAsync();
var memory = response.BodyWriter.GetMemory(4096);
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("hello,");
fisrtPartOfResponse.CopyTo(memory);
response.BodyWriter.Advance(6);
var secondPartOfResponse = Encoding.ASCII.GetBytes(" world");
secondPartOfResponse.CopyTo(memory.Slice(6));
response.BodyWriter.Advance(6);
await response.BodyWriter.FlushAsync();
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("hello, world"));
await response.BodyWriter.WriteAsync(Encoding.ASCII.GetBytes("hello, world"));
await response.WriteAsync("hello, world");
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 12,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 12,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 12,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 12,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
}
[Fact]
public async Task ContentLengthWithGetSpanWorks()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async httpContext =>
{
var response = httpContext.Response;
response.ContentLength = 12;
await Task.CompletedTask;
void NonAsyncMethod()
{
var span = response.BodyWriter.GetSpan(4096);
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("hello,");
fisrtPartOfResponse.CopyTo(span);
response.BodyWriter.Advance(6);
var secondPartOfResponse = Encoding.ASCII.GetBytes(" world");
secondPartOfResponse.CopyTo(span.Slice(6));
response.BodyWriter.Advance(6);
}
NonAsyncMethod();
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 37,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 12,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("12", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task ContentLengthWithGetMemoryWorks()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(httpContext =>
{
var response = httpContext.Response;
response.ContentLength = 12;
var memory = response.BodyWriter.GetMemory(4096);
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("Hello ");
fisrtPartOfResponse.CopyTo(memory);
response.BodyWriter.Advance(6);
var secondPartOfResponse = Encoding.ASCII.GetBytes("World!");
secondPartOfResponse.CopyTo(memory.Slice(6));
response.BodyWriter.Advance(6);
return Task.CompletedTask;
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 37,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 12,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("12", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task ResponseBodyCanWrite()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async httpContext =>
{
httpContext.Response.ContentLength = 12;
await httpContext.Response.Body.WriteAsync(Encoding.ASCII.GetBytes("hello, world"));
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 37,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 12,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("12", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task ResponseBodyAndResponsePipeWorks()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async httpContext =>
{
var response = httpContext.Response;
response.ContentLength = 54;
var memory = response.BodyWriter.GetMemory(4096);
var fisrtPartOfResponse = Encoding.ASCII.GetBytes("hello,");
fisrtPartOfResponse.CopyTo(memory);
response.BodyWriter.Advance(6);
var secondPartOfResponse = Encoding.ASCII.GetBytes(" world\r\n");
secondPartOfResponse.CopyTo(memory.Slice(6));
response.BodyWriter.Advance(8);
await response.BodyWriter.FlushAsync();
await response.Body.WriteAsync(Encoding.ASCII.GetBytes("hello, world\r\n"));
await response.BodyWriter.WriteAsync(Encoding.ASCII.GetBytes("hello, world\r\n"));
await response.WriteAsync("hello, world");
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 37,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 14,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 14,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 14,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 12,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("54", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task ResponseBodyPipeCompleteWithoutExceptionDoesNotThrow()
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
context.Response.BodyWriter.Complete();
await Task.CompletedTask;
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
}
[Fact]
public async Task ResponseBodyPipeCompleteWithoutExceptionWritesDoesThrow()
{
InvalidOperationException writeEx = null;
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
context.Response.BodyWriter.Complete();
writeEx = await Assert.ThrowsAsync<InvalidOperationException>(() => context.Response.WriteAsync(""));
});
await StartStreamAsync(1, headers, endStream: true);
// Don't receive content length because we called WriteAsync which caused an invalid response
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS | (byte)Http2HeadersFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.NotNull(writeEx);
}
[Fact]
public async Task ResponseBodyPipeCompleteWithExceptionThrows()
{
var expectedException = new Exception();
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
context.Response.BodyWriter.Complete(expectedException);
await Task.CompletedTask;
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("500", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Contains(TestSink.Writes, w => w.EventId.Id == 13 && w.LogLevel == LogLevel.Error
&& w.Exception is ConnectionAbortedException && w.Exception.InnerException == expectedException);
}
[Fact]
public async Task CompleteAsync_BeforeBodyStarted_SendsHeadersWithEndStream()
{
var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
try
{
context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
await context.Response.CompleteAsync().DefaultTimeout();
Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
Assert.True(context.Response.Headers.IsReadOnly);
Assert.True(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
// Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
await clientTcs.Task.DefaultTimeout();
appTcs.SetResult();
}
catch (Exception ex)
{
appTcs.SetException(ex);
}
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
clientTcs.SetResult();
await appTcs.Task;
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders["content-length"]);
}
[Fact]
public async Task CompleteAsync_BeforeBodyStarted_WithTrailers_SendsHeadersAndTrailersWithEndStream()
{
var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
try
{
context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
context.Response.AppendTrailer("CustomName", "Custom Value");
await context.Response.CompleteAsync().DefaultTimeout();
await context.Response.CompleteAsync().DefaultTimeout(); // Can be called twice, no-ops
Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
Assert.True(context.Response.Headers.IsReadOnly);
Assert.True(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
// Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
await clientTcs.Task.DefaultTimeout();
appTcs.SetResult();
}
catch (Exception ex)
{
appTcs.SetException(ex);
}
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var trailersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 25,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
clientTcs.SetResult();
await appTcs.Task;
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders["content-length"]);
_decodedHeaders.Clear();
_hpackDecoder.Decode(trailersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Single(_decodedHeaders);
Assert.Equal("Custom Value", _decodedHeaders["CustomName"]);
}
[Fact]
public async Task CompleteAsync_BeforeBodyStarted_WithTrailers_TruncatedContentLength_ThrowsAnd500()
{
var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
try
{
context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
context.Response.ContentLength = 25;
context.Response.AppendTrailer("CustomName", "Custom Value");
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.Response.CompleteAsync().DefaultTimeout());
Assert.Equal(CoreStrings.FormatTooFewBytesWritten(0, 25), ex.Message);
Assert.True(startingTcs.Task.IsCompletedSuccessfully);
Assert.False(context.Response.Headers.IsReadOnly);
Assert.False(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
appTcs.SetResult();
}
catch (Exception ex)
{
appTcs.SetException(ex);
}
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await appTcs.Task;
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("500", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task CompleteAsync_AfterBodyStarted_SendsBodyWithEndStream()
{
var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
try
{
context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
await context.Response.WriteAsync("Hello World");
Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
Assert.True(context.Response.Headers.IsReadOnly);
await context.Response.CompleteAsync().DefaultTimeout();
await context.Response.CompleteAsync().DefaultTimeout(); // Can be called twice, no-ops
Assert.True(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
// Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
await clientTcs.Task.DefaultTimeout();
appTcs.SetResult();
}
catch (Exception ex)
{
appTcs.SetException(ex);
}
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var bodyFrame = await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)(Http2HeadersFrameFlags.NONE),
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)(Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
clientTcs.SetResult();
await appTcs.Task;
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("Hello World", Encoding.UTF8.GetString(bodyFrame.Payload.Span));
}
[Fact]
public async Task CompleteAsync_WriteAfterComplete_Throws()
{
var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
try
{
context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
await context.Response.CompleteAsync().DefaultTimeout();
Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
Assert.True(context.Response.Headers.IsReadOnly);
Assert.True(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.Response.WriteAsync("2 Hello World").DefaultTimeout());
Assert.Equal("Writing is not allowed after writer was completed.", ex.Message);
// Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
await clientTcs.Task.DefaultTimeout();
appTcs.SetResult();
}
catch (Exception ex)
{
appTcs.SetException(ex);
}
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
clientTcs.SetResult();
await appTcs.Task;
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
}
[Fact]
public async Task CompleteAsync_WriteAgainAfterComplete_Throws()
{
var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
try
{
context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
await context.Response.WriteAsync("Hello World").DefaultTimeout();
Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
Assert.True(context.Response.Headers.IsReadOnly);
await context.Response.CompleteAsync().DefaultTimeout();
Assert.True(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.Response.WriteAsync("2 Hello World").DefaultTimeout());
Assert.Equal("Writing is not allowed after writer was completed.", ex.Message);
// Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
await clientTcs.Task.DefaultTimeout();
appTcs.SetResult();
}
catch (Exception ex)
{
appTcs.SetException(ex);
}
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var bodyFrame = await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)(Http2HeadersFrameFlags.NONE),
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)(Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
clientTcs.SetResult();
await appTcs.Task;
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("Hello World", Encoding.UTF8.GetString(bodyFrame.Payload.Span));
}
[Fact]
public async Task CompleteAsync_AdvanceAfterComplete_AdvanceThrows()
{
var tcs = new TaskCompletionSource();
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
var memory = context.Response.BodyWriter.GetMemory(12);
await context.Response.CompleteAsync();
try
{
context.Response.BodyWriter.Advance(memory.Length);
}
catch (InvalidOperationException)
{
tcs.SetResult();
return;
}
Assert.True(false);
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders[HeaderNames.ContentLength]);
await tcs.Task.DefaultTimeout();
}
[Fact]
public async Task CompleteAsync_AfterPipeWrite_WithTrailers_SendsBodyAndTrailersWithEndStream()
{
var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
try
{
context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
var buffer = context.Response.BodyWriter.GetMemory();
var length = Encoding.UTF8.GetBytes("Hello World", buffer.Span);
context.Response.BodyWriter.Advance(length);
Assert.False(startingTcs.Task.IsCompletedSuccessfully); // OnStarting did not get called.
Assert.False(context.Response.Headers.IsReadOnly);
context.Response.AppendTrailer("CustomName", "Custom Value");
await context.Response.CompleteAsync().DefaultTimeout();
Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
Assert.True(context.Response.Headers.IsReadOnly);
Assert.True(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
// Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
await clientTcs.Task.DefaultTimeout();
appTcs.SetResult();
}
catch (Exception ex)
{
appTcs.SetException(ex);
}
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var bodyFrame = await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)(Http2HeadersFrameFlags.NONE),
withStreamId: 1);
var trailersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 25,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
clientTcs.SetResult();
await appTcs.Task;
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("Hello World", Encoding.UTF8.GetString(bodyFrame.Payload.Span));
_decodedHeaders.Clear();
_hpackDecoder.Decode(trailersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Single(_decodedHeaders);
Assert.Equal("Custom Value", _decodedHeaders["CustomName"]);
}
[Fact]
public async Task CompleteAsync_AfterBodyStarted_WithTrailers_SendsBodyAndTrailersWithEndStream()
{
var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
try
{
context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
await context.Response.WriteAsync("Hello World");
Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
Assert.True(context.Response.Headers.IsReadOnly);
context.Response.AppendTrailer("CustomName", "Custom Value");
await context.Response.CompleteAsync().DefaultTimeout();
Assert.True(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
// Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
await clientTcs.Task.DefaultTimeout();
appTcs.SetResult();
}
catch (Exception ex)
{
appTcs.SetException(ex);
}
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var bodyFrame = await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)(Http2HeadersFrameFlags.NONE),
withStreamId: 1);
var trailersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 25,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
clientTcs.SetResult();
await appTcs.Task;
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("Hello World", Encoding.UTF8.GetString(bodyFrame.Payload.Span));
_decodedHeaders.Clear();
_hpackDecoder.Decode(trailersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Single(_decodedHeaders);
Assert.Equal("Custom Value", _decodedHeaders["CustomName"]);
}
[Fact]
public async Task CompleteAsync_AfterBodyStarted_WithTrailers_TruncatedContentLength_ThrowsAndReset()
{
var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
try
{
context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
context.Response.ContentLength = 25;
await context.Response.WriteAsync("Hello World");
Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
Assert.True(context.Response.Headers.IsReadOnly);
context.Response.AppendTrailer("CustomName", "Custom Value");
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => context.Response.CompleteAsync().DefaultTimeout());
Assert.Equal(CoreStrings.FormatTooFewBytesWritten(11, 25), ex.Message);
Assert.False(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
// Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
await clientTcs.Task.DefaultTimeout();
appTcs.SetResult();
}
catch (Exception ex)
{
appTcs.SetException(ex);
}
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 37,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var bodyFrame = await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)(Http2HeadersFrameFlags.NONE),
withStreamId: 1);
clientTcs.SetResult();
await WaitForStreamErrorAsync(1, Http2ErrorCode.INTERNAL_ERROR,
expectedErrorMessage: CoreStrings.FormatTooFewBytesWritten(11, 25));
await appTcs.Task;
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("25", _decodedHeaders[HeaderNames.ContentLength]);
Assert.Equal("Hello World", Encoding.UTF8.GetString(bodyFrame.Payload.Span));
}
[Fact]
public async Task PipeWriterComplete_AfterBodyStarted_WithTrailers_TruncatedContentLength_ThrowsAndReset()
{
var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
try
{
context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
context.Response.ContentLength = 25;
await context.Response.WriteAsync("Hello World");
Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
Assert.True(context.Response.Headers.IsReadOnly);
context.Response.AppendTrailer("CustomName", "Custom Value");
var ex = Assert.Throws<InvalidOperationException>(() => context.Response.BodyWriter.Complete());
Assert.Equal(CoreStrings.FormatTooFewBytesWritten(11, 25), ex.Message);
Assert.False(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
// Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
await clientTcs.Task.DefaultTimeout();
appTcs.SetResult();
}
catch (Exception ex)
{
appTcs.SetException(ex);
}
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 37,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var bodyFrame = await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)(Http2HeadersFrameFlags.NONE),
withStreamId: 1);
clientTcs.SetResult();
await WaitForStreamErrorAsync(1, Http2ErrorCode.INTERNAL_ERROR,
expectedErrorMessage: CoreStrings.FormatTooFewBytesWritten(11, 25));
await appTcs.Task;
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("25", _decodedHeaders[HeaderNames.ContentLength]);
Assert.Equal("Hello World", Encoding.UTF8.GetString(bodyFrame.Payload.Span));
}
[Fact]
public async Task AbortAfterCompleteAsync_GETWithResponseBodyAndTrailers_ResetsAfterResponse()
{
var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
try
{
context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
await context.Response.WriteAsync("Hello World");
Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
Assert.True(context.Response.Headers.IsReadOnly);
context.Response.AppendTrailer("CustomName", "Custom Value");
await context.Response.CompleteAsync().DefaultTimeout();
Assert.True(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
// RequestAborted will no longer fire after CompleteAsync.
Assert.False(context.RequestAborted.CanBeCanceled);
context.Abort();
// Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
await clientTcs.Task.DefaultTimeout();
appTcs.SetResult();
}
catch (Exception ex)
{
appTcs.SetException(ex);
}
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var bodyFrame = await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)(Http2HeadersFrameFlags.NONE),
withStreamId: 1);
var trailersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 25,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
// Stream should return an INTERNAL_ERROR. If there is an unexpected exception from app TCS instead, then throw it here to avoid timeout waiting for the stream error.
await Task.WhenAny(WaitForStreamErrorAsync(1, Http2ErrorCode.INTERNAL_ERROR, expectedErrorMessage: null), appTcs.Task).Unwrap();
clientTcs.SetResult();
await appTcs.Task;
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("Hello World", Encoding.UTF8.GetString(bodyFrame.Payload.Span));
_decodedHeaders.Clear();
_hpackDecoder.Decode(trailersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Single(_decodedHeaders);
Assert.Equal("Custom Value", _decodedHeaders["CustomName"]);
}
[Fact]
public async Task AbortAfterCompleteAsync_POSTWithResponseBodyAndTrailers_RequestBodyThrows()
{
var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
try
{
var requestBodyTask = context.Request.BodyReader.ReadAsync();
context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
await context.Response.WriteAsync("Hello World");
Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
Assert.True(context.Response.Headers.IsReadOnly);
context.Response.AppendTrailer("CustomName", "Custom Value");
await context.Response.CompleteAsync().DefaultTimeout();
Assert.True(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
// RequestAborted will no longer fire after CompleteAsync.
Assert.False(context.RequestAborted.CanBeCanceled);
context.Abort();
await Assert.ThrowsAsync<TaskCanceledException>(async () => await requestBodyTask);
await Assert.ThrowsAsync<ConnectionAbortedException>(async () => await context.Request.BodyReader.ReadAsync());
// Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
await clientTcs.Task.DefaultTimeout();
appTcs.SetResult();
}
catch (Exception ex)
{
appTcs.SetException(ex);
}
});
await StartStreamAsync(1, headers, endStream: false);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var bodyFrame = await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)(Http2HeadersFrameFlags.NONE),
withStreamId: 1);
var trailersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 25,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await WaitForStreamErrorAsync(1, Http2ErrorCode.INTERNAL_ERROR, expectedErrorMessage: null);
clientTcs.SetResult();
await appTcs.Task;
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("Hello World", Encoding.UTF8.GetString(bodyFrame.Payload.Span));
_decodedHeaders.Clear();
_hpackDecoder.Decode(trailersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Single(_decodedHeaders);
Assert.Equal("Custom Value", _decodedHeaders["CustomName"]);
}
[Fact]
public async Task ResetAfterCompleteAsync_GETWithResponseBodyAndTrailers_ResetsAfterResponse()
{
var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
try
{
context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
await context.Response.WriteAsync("Hello World");
Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
Assert.True(context.Response.Headers.IsReadOnly);
context.Response.AppendTrailer("CustomName", "Custom Value");
await context.Response.CompleteAsync().DefaultTimeout();
Assert.True(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
// RequestAborted will no longer fire after CompleteAsync.
Assert.False(context.RequestAborted.CanBeCanceled);
var resetFeature = context.Features.Get<IHttpResetFeature>();
Assert.NotNull(resetFeature);
resetFeature.Reset((int)Http2ErrorCode.NO_ERROR);
// Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
await clientTcs.Task.DefaultTimeout();
appTcs.SetResult();
}
catch (Exception ex)
{
appTcs.SetException(ex);
}
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var bodyFrame = await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)(Http2HeadersFrameFlags.NONE),
withStreamId: 1);
var trailersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 25,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await WaitForStreamErrorAsync(1, Http2ErrorCode.NO_ERROR, expectedErrorMessage:
"The HTTP/2 stream was reset by the application with error code NO_ERROR.");
clientTcs.SetResult();
await appTcs.Task;
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("Hello World", Encoding.UTF8.GetString(bodyFrame.Payload.Span));
_decodedHeaders.Clear();
_hpackDecoder.Decode(trailersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Single(_decodedHeaders);
Assert.Equal("Custom Value", _decodedHeaders["CustomName"]);
}
[Fact]
public async Task ResetAfterCompleteAsync_POSTWithResponseBodyAndTrailers_RequestBodyThrows()
{
var startingTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var appTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var clientTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "POST"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async context =>
{
try
{
var requestBodyTask = context.Request.BodyReader.ReadAsync();
context.Response.OnStarting(() => { startingTcs.SetResult(); return Task.CompletedTask; });
await context.Response.WriteAsync("Hello World");
Assert.True(startingTcs.Task.IsCompletedSuccessfully); // OnStarting got called.
Assert.True(context.Response.Headers.IsReadOnly);
context.Response.AppendTrailer("CustomName", "Custom Value");
await context.Response.CompleteAsync().DefaultTimeout();
Assert.True(context.Features.Get<IHttpResponseTrailersFeature>().Trailers.IsReadOnly);
// RequestAborted will no longer fire after CompleteAsync.
Assert.False(context.RequestAborted.CanBeCanceled);
var resetFeature = context.Features.Get<IHttpResetFeature>();
Assert.NotNull(resetFeature);
resetFeature.Reset((int)Http2ErrorCode.NO_ERROR);
await Assert.ThrowsAsync<TaskCanceledException>(async () => await requestBodyTask);
await Assert.ThrowsAsync<ConnectionAbortedException>(async () => await context.Request.BodyReader.ReadAsync());
// Make sure the client gets our results from CompleteAsync instead of from the request delegate exiting.
await clientTcs.Task.DefaultTimeout();
appTcs.SetResult();
}
catch (Exception ex)
{
appTcs.SetException(ex);
}
});
await StartStreamAsync(1, headers, endStream: false);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS),
withStreamId: 1);
var bodyFrame = await ExpectAsync(Http2FrameType.DATA,
withLength: 11,
withFlags: (byte)(Http2HeadersFrameFlags.NONE),
withStreamId: 1);
var trailersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 25,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await WaitForStreamErrorAsync(1, Http2ErrorCode.NO_ERROR, expectedErrorMessage:
"The HTTP/2 stream was reset by the application with error code NO_ERROR.");
clientTcs.SetResult();
await appTcs.Task;
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("Hello World", Encoding.UTF8.GetString(bodyFrame.Payload.Span));
_decodedHeaders.Clear();
_hpackDecoder.Decode(trailersFrame.PayloadSequence, endHeaders: true, handler: this);
Assert.Single(_decodedHeaders);
Assert.Equal("Custom Value", _decodedHeaders["CustomName"]);
}
// :method = GET
// :path = /
// :scheme = http
// X-Test = £
private static readonly byte[] LatinHeaderData = new byte[]
{
0, 7, 58, 109, 101, 116, 104, 111, 100, 3, 71, 69, 84, 0, 5, 58, 112, 97, 116,
104, 1, 47, 0, 7, 58, 115, 99, 104, 101, 109, 101, 4, 104, 116, 116, 112, 0,
6, 120, 45, 116, 101, 115, 116, 1, 163
};
[Fact]
public async Task HEADERS_Received_Latin1_AcceptedWhenLatin1OptionIsConfigured()
{
_serviceContext.ServerOptions.RequestHeaderEncodingSelector = _ => Encoding.Latin1;
await InitializeConnectionAsync(context =>
{
Assert.Equal("£", context.Request.Headers["X-Test"]);
return Task.CompletedTask;
});
await StartStreamAsync(1, LatinHeaderData, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 36,
withFlags: (byte)(Http2HeadersFrameFlags.END_HEADERS | Http2HeadersFrameFlags.END_STREAM),
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(3, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.Equal("0", _decodedHeaders["content-length"]);
}
[Fact]
public async Task HEADERS_Received_Latin1_RejectedWhenLatin1OptionIsNotConfigured()
{
await InitializeConnectionAsync(_noopApplication);
await StartStreamAsync(1, LatinHeaderData, endStream: true);
await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(
ignoreNonGoAwayFrames: true,
expectedLastStreamId: 1,
expectedErrorCode: Http2ErrorCode.PROTOCOL_ERROR,
expectedErrorMessage: CoreStrings.BadRequest_MalformedRequestInvalidHeaders);
AssertConnectionEndReason(ConnectionEndReason.InvalidRequestHeaders);
}
[Fact]
public async Task HEADERS_Received_CustomEncoding_InvalidCharacters_AbortsConnection()
{
var encoding = Encoding.GetEncoding(Encoding.ASCII.CodePage, EncoderFallback.ExceptionFallback,
DecoderFallback.ExceptionFallback);
_serviceContext.ServerOptions.RequestHeaderEncodingSelector = _ => encoding;
await InitializeConnectionAsync(context =>
{
Assert.Equal("£", context.Request.Headers["X-Test"]);
return Task.CompletedTask;
});
await StartStreamAsync(1, LatinHeaderData, endStream: true);
await WaitForConnectionErrorAsync<Http2ConnectionErrorException>(ignoreNonGoAwayFrames: false, expectedLastStreamId: 1,
Http2ErrorCode.PROTOCOL_ERROR, CoreStrings.BadRequest_MalformedRequestInvalidHeaders);
AssertConnectionEndReason(ConnectionEndReason.InvalidRequestHeaders);
}
[Fact]
public async Task RemoveConnectionSpecificHeaders()
{
await InitializeConnectionAsync(async context =>
{
var response = context.Response;
response.Headers.Add(HeaderNames.TransferEncoding, "chunked");
response.Headers.Add(HeaderNames.Upgrade, "websocket");
response.Headers.Add(HeaderNames.Connection, "Keep-Alive");
response.Headers.Add(HeaderNames.KeepAlive, "timeout=5, max=1000");
response.Headers.Add(HeaderNames.ProxyConnection, "keep-alive");
await response.WriteAsync("hello, world");
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
var dataFrame1 = await ExpectAsync(Http2FrameType.DATA,
withLength: 12,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await ExpectAsync(Http2FrameType.DATA,
withLength: 0,
withFlags: (byte)Http2DataFrameFlags.END_STREAM,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
Assert.True(_helloWorldBytes.AsSpan().SequenceEqual(dataFrame1.PayloadSequence.ToArray()));
Assert.Contains(LogMessages, m => m.Message.Equals("One or more of the following response headers have been removed because they are invalid for HTTP/2 and HTTP/3 responses: 'Connection', 'Transfer-Encoding', 'Keep-Alive', 'Upgrade' and 'Proxy-Connection'."));
}
[Theory]
[InlineData(1000)]
[InlineData(4096)]
[InlineData(8000)] // Greater than the default max pool size (4096)
public async Task GetMemory_AfterAbort_GetsFakeMemory(int sizeHint)
{
var headers = new[]
{
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
};
await InitializeConnectionAsync(async httpContext =>
{
var response = httpContext.Response;
await response.BodyWriter.FlushAsync();
httpContext.Abort();
var memory = response.BodyWriter.GetMemory(sizeHint);
Assert.True(memory.Length >= sizeHint);
var fisrtPartOfResponse = Encoding.ASCII.GetBytes(new String('a', sizeHint));
fisrtPartOfResponse.CopyTo(memory);
response.BodyWriter.Advance(sizeHint);
});
await StartStreamAsync(1, headers, endStream: true);
var headersFrame = await ExpectAsync(Http2FrameType.HEADERS,
withLength: 32,
withFlags: (byte)Http2HeadersFrameFlags.END_HEADERS,
withStreamId: 1);
await ExpectAsync(Http2FrameType.RST_STREAM,
withLength: 4,
withFlags: (byte)Http2DataFrameFlags.NONE,
withStreamId: 1);
await StopConnectionAsync(expectedLastStreamId: 1, ignoreNonGoAwayFrames: false);
_hpackDecoder.Decode(headersFrame.PayloadSequence, endHeaders: false, handler: this);
Assert.Equal(2, _decodedHeaders.Count);
Assert.Contains("date", _decodedHeaders.Keys, StringComparer.OrdinalIgnoreCase);
Assert.Equal("200", _decodedHeaders[InternalHeaderNames.Status]);
}
}
|