Skip to content

Commit 47e284e

Browse files
committed
Merge in 'release/7.0' changes
2 parents f098a09 + 764a98d commit 47e284e

File tree

6 files changed

+70
-5
lines changed

6 files changed

+70
-5
lines changed

src/OpenApi/src/OpenApiGenerator.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ private OpenApiOperation GetOperation(string httpMethod, MethodInfo methodInfo,
7878
Description = metadata.GetMetadata<IEndpointDescriptionMetadata>()?.Description,
7979
Tags = GetOperationTags(methodInfo, metadata),
8080
Parameters = GetOpenApiParameters(methodInfo, pattern, disableInferredBody),
81-
RequestBody = GetOpenApiRequestBody(methodInfo, metadata, pattern),
81+
RequestBody = GetOpenApiRequestBody(methodInfo, metadata, pattern, disableInferredBody),
8282
Responses = GetOpenApiResponses(methodInfo, metadata)
8383
};
8484

@@ -251,15 +251,15 @@ private static void GenerateDefaultResponses(Dictionary<int, (Type?, MediaTypeCo
251251
}
252252
}
253253

254-
private OpenApiRequestBody? GetOpenApiRequestBody(MethodInfo methodInfo, EndpointMetadataCollection metadata, RoutePattern pattern)
254+
private OpenApiRequestBody? GetOpenApiRequestBody(MethodInfo methodInfo, EndpointMetadataCollection metadata, RoutePattern pattern, bool disableInferredBody)
255255
{
256256
var hasFormOrBodyParameter = false;
257257
ParameterInfo? requestBodyParameter = null;
258258

259259
var parameters = PropertyAsParameterInfo.Flatten(methodInfo.GetParameters(), ParameterBindingMethodCache);
260260
foreach (var parameter in parameters)
261261
{
262-
var (bodyOrFormParameter, _, _) = GetOpenApiParameterLocation(parameter, pattern, false);
262+
var (bodyOrFormParameter, _, _) = GetOpenApiParameterLocation(parameter, pattern, disableInferredBody);
263263
hasFormOrBodyParameter |= bodyOrFormParameter;
264264
if (hasFormOrBodyParameter)
265265
{

src/OpenApi/test/OpenApiGeneratorTests.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,18 @@ static void ValidateParameter(OpenApiOperation operation, string expectedName)
928928
ValidateParameter(GetOpenApiOperation(([FromHeader(Name = "headerName")] string param) => ""), "headerName");
929929
}
930930

931+
[Fact]
932+
public void DoesNotGenerateRequestBodyWhenInferredBodyDisabled()
933+
{
934+
var operation = GetOpenApiOperation((string[] names) => { }, httpMethods: new[] { "GET" });
935+
936+
var parameter = Assert.Single(operation.Parameters);
937+
938+
Assert.Equal("names", parameter.Name);
939+
Assert.Equal(ParameterLocation.Query, parameter.In);
940+
Assert.Null(operation.RequestBody);
941+
}
942+
931943
private static OpenApiOperation GetOpenApiOperation(
932944
Delegate action,
933945
string pattern = null,

src/Servers/HttpSys/src/HttpSysOptions.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ public class HttpSysOptions
3434
/// </summary>
3535
public HttpSysOptions()
3636
{
37+
const string EnableKernelResponseBufferingSwitch = "Microsoft.AspNetCore.Server.HttpSys.EnableKernelResponseBuffering";
38+
EnableKernelResponseBuffering = AppContext.TryGetSwitch(EnableKernelResponseBufferingSwitch, out var enabled) && enabled;
3739
}
3840

3941
/// <summary>
@@ -108,6 +110,15 @@ public string? RequestQueueName
108110
/// </summary>
109111
public bool ThrowWriteExceptions { get; set; }
110112

113+
/// <summary>
114+
/// Enable buffering of response data in the Kernel.
115+
/// It should be used by an application doing synchronous I/O or by an application doing asynchronous I/O with
116+
/// no more than one outstanding write at a time, and can significantly improve throughput over high-latency connections.
117+
/// Applications that use asynchronous I/O and that may have more than one send outstanding at a time should not use this flag.
118+
/// Enabling this can results in higher CPU and memory usage by Http.Sys.
119+
/// </summary>
120+
internal bool EnableKernelResponseBuffering { get; set; } // internal via app-context in pre-8.0
121+
111122
/// <summary>
112123
/// Gets or sets the maximum number of concurrent connections to accept, -1 for infinite, or null to
113124
/// use the machine wide setting from the registry. The default value is null.

src/Servers/HttpSys/src/RequestProcessing/Response.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,8 +275,6 @@ actual HTTP header will be generated by the application and sent as entity body.
275275
// This will give us more control of the bytes that hit the wire, including encodings, HTTP 1.0, etc..
276276
// It may also be faster to do this work in managed code and then pass down only one buffer.
277277
// What would we loose by bypassing HttpSendHttpResponse?
278-
//
279-
// TODO: Consider using the HTTP_SEND_RESPONSE_FLAG_BUFFER_DATA flag for most/all responses rather than just Opaque.
280278
internal unsafe uint SendHeaders(HttpApiTypes.HTTP_DATA_CHUNK[]? dataChunks,
281279
ResponseStreamAsyncResult? asyncResult,
282280
HttpApiTypes.HTTP_FLAGS flags,

src/Servers/HttpSys/src/RequestProcessing/ResponseBody.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ internal RequestContext RequestContext
3939

4040
internal bool ThrowWriteExceptions => RequestContext.Server.Options.ThrowWriteExceptions;
4141

42+
internal bool EnableKernelResponseBuffering => RequestContext.Server.Options.EnableKernelResponseBuffering;
43+
4244
internal bool IsDisposed => _disposed;
4345

4446
public override bool CanSeek
@@ -463,6 +465,13 @@ private HttpApiTypes.HTTP_FLAGS ComputeLeftToWrite(long writeCount, bool endOfRe
463465
{
464466
flags |= HttpApiTypes.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_MORE_DATA;
465467
}
468+
if (EnableKernelResponseBuffering)
469+
{
470+
// "When this flag is set, it should also be used consistently in calls to the HttpSendResponseEntityBody function."
471+
// so: make sure we add it in *all* scenarios where it applies - our "close" could be at the end of a bunch
472+
// of buffered chunks
473+
flags |= HttpApiTypes.HTTP_FLAGS.HTTP_SEND_RESPONSE_FLAG_BUFFER_DATA;
474+
}
466475

467476
// Update _leftToWrite now so we can queue up additional async writes.
468477
if (_leftToWrite > 0)

src/Servers/HttpSys/test/FunctionalTests/ResponseBodyTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,41 @@ public async Task ResponseBody_WriteNoHeaders_SetsChunked()
133133
}
134134
}
135135

136+
[ConditionalTheory]
137+
[InlineData(true)]
138+
[InlineData(false)]
139+
public async Task ResponseBody_WriteNoHeaders_SetsChunked_LargeBody(bool enableKernelBuffering)
140+
{
141+
const int WriteSize = 1024 * 1024;
142+
const int NumWrites = 32;
143+
144+
string address;
145+
using (Utilities.CreateHttpServer(
146+
baseAddress: out address,
147+
configureOptions: options => { options.EnableKernelResponseBuffering = enableKernelBuffering; },
148+
app: async httpContext =>
149+
{
150+
httpContext.Features.Get<IHttpBodyControlFeature>().AllowSynchronousIO = true;
151+
for (int i = 0; i < NumWrites - 1; i++)
152+
{
153+
httpContext.Response.Body.Write(new byte[WriteSize], 0, WriteSize);
154+
}
155+
await httpContext.Response.Body.WriteAsync(new byte[WriteSize], 0, WriteSize);
156+
}))
157+
{
158+
var response = await SendRequestAsync(address);
159+
Assert.Equal(200, (int)response.StatusCode);
160+
Assert.Equal(new Version(1, 1), response.Version);
161+
IEnumerable<string> ignored;
162+
Assert.False(response.Content.Headers.TryGetValues("content-length", out ignored), "Content-Length");
163+
Assert.True(response.Headers.TransferEncodingChunked.HasValue, "Chunked");
164+
165+
var bytes = await response.Content.ReadAsByteArrayAsync();
166+
Assert.Equal(WriteSize * NumWrites, bytes.Length);
167+
Assert.True(bytes.All(b => b == 0));
168+
}
169+
}
170+
136171
[ConditionalFact]
137172
public async Task ResponseBody_WriteNoHeadersAndFlush_DefaultsToChunked()
138173
{

0 commit comments

Comments
 (0)