diff --git a/benchmarks/Kestrel.Performance/Mocks/MockTrace.cs b/benchmarks/Kestrel.Performance/Mocks/MockTrace.cs
index 0fc02aa7d9..a98cfaf7f8 100644
--- a/benchmarks/Kestrel.Performance/Mocks/MockTrace.cs
+++ b/benchmarks/Kestrel.Performance/Mocks/MockTrace.cs
@@ -49,6 +49,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Performance
public void Http2ConnectionError(string connectionId, Http2ConnectionErrorException ex) { }
public void Http2StreamError(string connectionId, Http2StreamErrorException ex) { }
public void HPackDecodingError(string connectionId, int streamId, HPackDecodingException ex) { }
+ public void HPackEncodingError(string connectionId, int streamId, HPackEncodingException ex) { }
public void Http2StreamResetAbort(string traceIdentifier, Http2ErrorCode error, ConnectionAbortedException abortReason) { }
public void Http2ConnectionClosing(string connectionId) { }
public void Http2ConnectionClosed(string connectionId, int highestOpenedStreamId) { }
diff --git a/build/dependencies.props b/build/dependencies.props
index 09cb28d399..0abd7a09aa 100644
--- a/build/dependencies.props
+++ b/build/dependencies.props
@@ -11,13 +11,13 @@
2.2.0-preview3-35359
2.2.0-preview3-35359
2.2.0-preview3-35359
- 2.2.0-preview3-35359
- 2.2.0-preview3-35359
- 2.2.0-preview3-35359
- 2.2.0-preview3-35359
- 2.2.0-preview3-35359
+ 2.2.0-a-preview3-tratcher-trailers-17154
+ 2.2.0-a-preview3-tratcher-trailers-17154
+ 2.2.0-a-preview3-tratcher-trailers-16760
+ 2.2.0-a-preview3-tratcher-trailers-16760
+ 2.2.0-a-preview3-tratcher-trailers-16760
2.2.0-preview3-35359
- 2.2.0-preview3-35359
+ 2.2.0-a-preview3-tratcher-trailers-16760
2.2.0-preview3-35359
2.2.0-preview3-35359
2.2.0-preview3-35359
diff --git a/samples/Http2SampleApp/Startup.cs b/samples/Http2SampleApp/Startup.cs
index 904e07cbb8..4f45eb97cc 100644
--- a/samples/Http2SampleApp/Startup.cs
+++ b/samples/Http2SampleApp/Startup.cs
@@ -14,6 +14,7 @@ namespace Http2SampleApp
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
+ app.UseTimingMiddleware();
app.Run(context =>
{
return context.Response.WriteAsync("Hello World! " + context.Request.Protocol);
diff --git a/samples/Http2SampleApp/TimingMiddleware.cs b/samples/Http2SampleApp/TimingMiddleware.cs
new file mode 100644
index 0000000000..09bb1c80ff
--- /dev/null
+++ b/samples/Http2SampleApp/TimingMiddleware.cs
@@ -0,0 +1,50 @@
+using System.Diagnostics;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Http;
+
+namespace Http2SampleApp
+{
+ // You may need to install the Microsoft.AspNetCore.Http.Abstractions package into your project
+ public class TimingMiddleware
+ {
+ private readonly RequestDelegate _next;
+
+ public TimingMiddleware(RequestDelegate next)
+ {
+ _next = next;
+ }
+
+ public async Task Invoke(HttpContext httpContext)
+ {
+ if (httpContext.Response.SupportsTrailers())
+ {
+ httpContext.Response.DeclareTrailer("Server-Timing");
+
+ var stopWatch = new Stopwatch();
+ stopWatch.Start();
+
+ await _next(httpContext);
+
+ stopWatch.Stop();
+ // Not yet supported in any browser dev tools
+ httpContext.Response.AppendTrailer("Server-Timing", $"app;dur={stopWatch.ElapsedMilliseconds}.0");
+ }
+ else
+ {
+ // Works in chrome
+ // httpContext.Response.Headers.Append("Server-Timing", $"app;dur=25.0");
+ await _next(httpContext);
+ }
+ }
+ }
+
+ // Extension method used to add the middleware to the HTTP request pipeline.
+ public static class TimingMiddlewareExtensions
+ {
+ public static IApplicationBuilder UseTimingMiddleware(this IApplicationBuilder builder)
+ {
+ return builder.UseMiddleware();
+ }
+ }
+}
diff --git a/src/Kestrel.Core/Internal/Http/HttpHeaders.Generated.cs b/src/Kestrel.Core/Internal/Http/HttpHeaders.Generated.cs
index daee31b4f0..7ac04398ed 100644
--- a/src/Kestrel.Core/Internal/Http/HttpHeaders.Generated.cs
+++ b/src/Kestrel.Core/Internal/Http/HttpHeaders.Generated.cs
@@ -8998,4 +8998,235 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
}
}
}
+
+ public partial class HttpResponseTrailers
+ {
+ private static byte[] _headerBytes = new byte[]
+ {
+ 13,10,69,84,97,103,58,32,
+ };
+
+ private long _bits = 0;
+ private HeaderReferences _headers;
+
+
+
+ public StringValues HeaderETag
+ {
+ get
+ {
+ StringValues value;
+ if ((_bits & 1L) != 0)
+ {
+ value = _headers._ETag;
+ }
+ return value;
+ }
+ set
+ {
+ _bits |= 1L;
+ _headers._ETag = value;
+ }
+ }
+
+ protected override int GetCountFast()
+ {
+ return (_contentLength.HasValue ? 1 : 0 ) + BitCount(_bits) + (MaybeUnknown?.Count ?? 0);
+ }
+
+ protected override bool TryGetValueFast(string key, out StringValues value)
+ {
+ switch (key.Length)
+ {
+ case 4:
+ {
+ if ("ETag".Equals(key, StringComparison.OrdinalIgnoreCase))
+ {
+ if ((_bits & 1L) != 0)
+ {
+ value = _headers._ETag;
+ return true;
+ }
+ return false;
+ }
+ }
+ break;
+ }
+
+ return MaybeUnknown?.TryGetValue(key, out value) ?? false;
+ }
+
+ protected override void SetValueFast(string key, in StringValues value)
+ {
+ ValidateHeaderValueCharacters(value);
+ switch (key.Length)
+ {
+ case 4:
+ {
+ if ("ETag".Equals(key, StringComparison.OrdinalIgnoreCase))
+ {
+ _bits |= 1L;
+ _headers._ETag = value;
+ return;
+ }
+ }
+ break;
+ }
+
+ SetValueUnknown(key, value);
+ }
+
+ protected override bool AddValueFast(string key, in StringValues value)
+ {
+ ValidateHeaderValueCharacters(value);
+ switch (key.Length)
+ {
+ case 4:
+ {
+ if ("ETag".Equals(key, StringComparison.OrdinalIgnoreCase))
+ {
+ if ((_bits & 1L) == 0)
+ {
+ _bits |= 1L;
+ _headers._ETag = value;
+ return true;
+ }
+ return false;
+ }
+ }
+ break;
+ }
+
+ Unknown.Add(key, value);
+ // Return true, above will throw and exit for false
+ return true;
+ }
+
+ protected override bool RemoveFast(string key)
+ {
+ switch (key.Length)
+ {
+ case 4:
+ {
+ if ("ETag".Equals(key, StringComparison.OrdinalIgnoreCase))
+ {
+ if ((_bits & 1L) != 0)
+ {
+ _bits &= ~1L;
+ _headers._ETag = default(StringValues);
+ return true;
+ }
+ return false;
+ }
+ }
+ break;
+ }
+
+ return MaybeUnknown?.Remove(key) ?? false;
+ }
+
+ protected override void ClearFast()
+ {
+ MaybeUnknown?.Clear();
+ _contentLength = null;
+ var tempBits = _bits;
+ _bits = 0;
+ if(HttpHeaders.BitCount(tempBits) > 12)
+ {
+ _headers = default(HeaderReferences);
+ return;
+ }
+
+ if ((tempBits & 1L) != 0)
+ {
+ _headers._ETag = default(StringValues);
+ if((tempBits & ~1L) == 0)
+ {
+ return;
+ }
+ tempBits &= ~1L;
+ }
+
+ }
+
+ protected override bool CopyToFast(KeyValuePair[] array, int arrayIndex)
+ {
+ if (arrayIndex < 0)
+ {
+ return false;
+ }
+
+ if ((_bits & 1L) != 0)
+ {
+ if (arrayIndex == array.Length)
+ {
+ return false;
+ }
+ array[arrayIndex] = new KeyValuePair("ETag", _headers._ETag);
+ ++arrayIndex;
+ }
+ if (_contentLength.HasValue)
+ {
+ if (arrayIndex == array.Length)
+ {
+ return false;
+ }
+ array[arrayIndex] = new KeyValuePair("Content-Length", HeaderUtilities.FormatNonNegativeInt64(_contentLength.Value));
+ ++arrayIndex;
+ }
+ ((ICollection>)MaybeUnknown)?.CopyTo(array, arrayIndex);
+
+ return true;
+ }
+
+
+
+ private struct HeaderReferences
+ {
+ public StringValues _ETag;
+
+ }
+
+ public partial struct Enumerator
+ {
+ public bool MoveNext()
+ {
+ switch (_state)
+ {
+
+ case 0:
+ goto state0;
+
+ case 1:
+ goto state1;
+ default:
+ goto state_default;
+ }
+
+ state0:
+ if ((_bits & 1L) != 0)
+ {
+ _current = new KeyValuePair("ETag", _collection._headers._ETag);
+ _state = 1;
+ return true;
+ }
+
+ state1:
+ if (_collection._contentLength.HasValue)
+ {
+ _current = new KeyValuePair("Content-Length", HeaderUtilities.FormatNonNegativeInt64(_collection._contentLength.Value));
+ _state = 2;
+ return true;
+ }
+ state_default:
+ if (!_hasUnknown || !_unknownEnumerator.MoveNext())
+ {
+ _current = default(KeyValuePair);
+ return false;
+ }
+ _current = _unknownEnumerator.Current;
+ return true;
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/src/Kestrel.Core/Internal/Http/HttpHeaders.cs b/src/Kestrel.Core/Internal/Http/HttpHeaders.cs
index a0a7306d2f..6acb332a26 100644
--- a/src/Kestrel.Core/Internal/Http/HttpHeaders.cs
+++ b/src/Kestrel.Core/Internal/Http/HttpHeaders.cs
@@ -97,7 +97,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
throw new ArgumentException(CoreStrings.KeyAlreadyExists);
}
- int ICollection>.Count => GetCountFast();
+ public int Count => GetCountFast();
bool ICollection>.IsReadOnly => _isReadOnly;
diff --git a/src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs b/src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs
index 7f3b047d70..51b5daf836 100644
--- a/src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs
+++ b/src/Kestrel.Core/Internal/Http/HttpProtocol.FeatureCollection.cs
@@ -209,9 +209,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_currentIHttpUpgradeFeature = this;
}
- protected void ResetIHttp2StreamIdFeature()
+ protected void ResetHttp2Features()
{
_currentIHttp2StreamIdFeature = this;
+ _currentIHttpResponseTrailersFeature = this;
}
void IHttpResponseFeature.OnStarting(Func