diff --git a/Directory.Build.props b/Directory.Build.props index 1680af7..52df9d9 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,8 +1,8 @@ - 3.10.0 - 3.9.0 + 3.10.1 + 3.10.0 12.0 enable enable diff --git a/ReleaseNotes.md b/ReleaseNotes.md index ddb1c69..8a0e719 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,5 +1,9 @@ # Release Notes +## 3.10.1 + +* Disable buffering for text/event-stream. + ## 3.10.0 * Support events. diff --git a/conformance/WebApiControllerServer/WebApiControllerServerApp.cs b/conformance/WebApiControllerServer/WebApiControllerServerApp.cs index dfe0ec3..7a7690a 100644 --- a/conformance/WebApiControllerServer/WebApiControllerServerApp.cs +++ b/conformance/WebApiControllerServer/WebApiControllerServerApp.cs @@ -37,14 +37,15 @@ public void Configuration(IAppBuilder app) { var configuration = new HttpConfiguration(); configuration.MapHttpAttributeRoutes(); - configuration.Services.Replace(typeof(IHostBufferPolicySelector), new NoBufferPolicySelector()); + configuration.Services.Replace(typeof(IHostBufferPolicySelector), new SseBufferPolicySelector(configuration.Services.GetHostBufferPolicySelector())); app.UseWebApi(configuration); } - private sealed class NoBufferPolicySelector : IHostBufferPolicySelector + private sealed class SseBufferPolicySelector(IHostBufferPolicySelector? inner) : IHostBufferPolicySelector { - public bool UseBufferedInputStream(object hostContext) => false; - public bool UseBufferedOutputStream(HttpResponseMessage response) => false; //// required for event streaming + public bool UseBufferedInputStream(object hostContext) => inner?.UseBufferedInputStream(hostContext) ?? false; + public bool UseBufferedOutputStream(HttpResponseMessage response) => + response.Content.Headers.ContentType?.MediaType != "text/event-stream" && (inner?.UseBufferedOutputStream(response) ?? false); } } diff --git a/conformance/WebApiMiddlewareServer/WebApiMiddlewareServerApp.cs b/conformance/WebApiMiddlewareServer/WebApiMiddlewareServerApp.cs index f55185c..7f6fc6f 100644 --- a/conformance/WebApiMiddlewareServer/WebApiMiddlewareServerApp.cs +++ b/conformance/WebApiMiddlewareServer/WebApiMiddlewareServerApp.cs @@ -34,14 +34,15 @@ public void Configuration(IAppBuilder app) Tests = LoadTests(), JsonSerializer = JsonSerializer, }))); - configuration.Services.Replace(typeof(IHostBufferPolicySelector), new NoBufferPolicySelector()); + configuration.Services.Replace(typeof(IHostBufferPolicySelector), new SseBufferPolicySelector(configuration.Services.GetHostBufferPolicySelector())); app.UseWebApi(configuration); } - private sealed class NoBufferPolicySelector : IHostBufferPolicySelector + private sealed class SseBufferPolicySelector(IHostBufferPolicySelector? inner) : IHostBufferPolicySelector { - public bool UseBufferedInputStream(object hostContext) => false; - public bool UseBufferedOutputStream(HttpResponseMessage response) => false; //// required for event streaming + public bool UseBufferedInputStream(object hostContext) => inner?.UseBufferedInputStream(hostContext) ?? false; + public bool UseBufferedOutputStream(HttpResponseMessage response) => + response.Content.Headers.ContentType?.MediaType != "text/event-stream" && (inner?.UseBufferedOutputStream(response) ?? false); } } diff --git a/src/Facility.AspNetCore/FacilityAspNetCoreUtility.cs b/src/Facility.AspNetCore/FacilityAspNetCoreUtility.cs index c4ff4fe..c686198 100644 --- a/src/Facility.AspNetCore/FacilityAspNetCoreUtility.cs +++ b/src/Facility.AspNetCore/FacilityAspNetCoreUtility.cs @@ -3,6 +3,7 @@ using Facility.Core.Http; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Http.Features; namespace Facility.AspNetCore; @@ -62,20 +63,22 @@ public static HttpResponseMessage CreateHttpResponseMessage(ServiceErrorDto erro /// public static async Task WriteHttpResponseMessageAsync(HttpResponseMessage httpResponseMessage, HttpResponse contextResponse) { - contextResponse.StatusCode = (int) httpResponseMessage.StatusCode; + // disable buffering for text/event-stream + var contentHeaders = httpResponseMessage.Content.Headers; + if (contentHeaders.ContentType?.MediaType == "text/event-stream") + contextResponse.HttpContext.Features.Get()?.DisableBuffering(); - var responseHeaders = httpResponseMessage.Headers; + contextResponse.StatusCode = (int) httpResponseMessage.StatusCode; // Ignore the Transfer-Encoding header if it is just "chunked". // We let the host decide about whether the response should be chunked or not. + var responseHeaders = httpResponseMessage.Headers; if (responseHeaders is { TransferEncodingChunked: true, TransferEncoding.Count: 1 }) responseHeaders.TransferEncoding.Clear(); foreach (var header in responseHeaders) contextResponse.Headers.Append(header.Key, header.Value.ToArray()); - var contentHeaders = httpResponseMessage.Content.Headers; - // Copy the response content headers only after ensuring they are complete. // We ask for Content-Length first because HttpContent lazily computes this // and only afterward writes the value into the content headers.