Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -170,23 +170,38 @@ private static async Task<JsonDocument> BuildVariablesAsync(
do
{
result = await body.ReadAsync(cancellationToken);
body.AdvanceTo(result.Buffer.Start, result.Buffer.End);
} while (result is { IsCompleted: false, IsCanceled: false });

if (result.IsCanceled)
if (result.IsCanceled)
{
throw new OperationCanceledException();
}

// Only advance while more data is pending; the final (completed)
// read is advanced in the finally block below.
if (!result.IsCompleted)
{
body.AdvanceTo(result.Buffer.Start, result.Buffer.End);
Comment thread
hemed marked this conversation as resolved.
}
} while (!result.IsCompleted);

try
{
throw new OperationCanceledException();
}
if (result.Buffer.Length == 0)
{
throw new BadRequestException("Expected to have a body");
}

if (result.Buffer.Length == 0)
var jsonValueParser = new JsonValueParser(buffer: variableBuffer);
var bodyValue = jsonValueParser.Parse(result.Buffer);
variables[bodyVariable] = bodyValue;
}
finally
{
throw new BadRequestException("Expected to have a body");
// Advance the final (completed) read exactly once, even if parsing
// or the empty-body guard threw, so Kestrel can drain the request
// body without a PipeReader contract violation.
body.AdvanceTo(result.Buffer.End);
}

var jsonValueParser = new JsonValueParser(buffer: variableBuffer);
var bodyValue = jsonValueParser.Parse(result.Buffer);
variables[bodyVariable] = bodyValue;
body.AdvanceTo(result.Buffer.End);
}

InsertParametersIntoVariables(variables, endpointDescriptor, httpContext);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System.Net;
using System.Text;
using System.Text.Json;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.Extensions.DependencyInjection;

namespace HotChocolate.Adapters.OpenApi;

// Regression tests for @body endpoints. They must run on a real Kestrel host:
// TestServer's in-memory body PipeReader tolerates the double AdvanceTo bug,
// only Kestrel enforces the contract and returns 500.
public sealed class KestrelHttpEndpointIntegrationTests : OpenApiTestBase
{
protected override void ConfigureStorage(
IServiceCollection services,
IOpenApiDefinitionStorage storage,
OpenApiDiagnosticEventListener? eventListener)
{
services.AddGraphQLServer()
.AddOpenApi()
.AddOpenApiDefinitionStorage(storage)
.AddBasicServer();
}

[Fact]
public async Task Http_Post_Body_Succeeds_On_Kestrel()
{
var cancellationToken = TestContext.Current.CancellationToken;
var storage = CreateBasicTestDefinitionStorage();
var host = await StartKestrelHostAsync(storage, cancellationToken);

try
{
using var client = new HttpClient { BaseAddress = GetBaseAddress(host) };
var content = new StringContent(
"""{ "id": "6", "name": "Test", "email": "test@example.com" }""",
Encoding.UTF8,
"application/json");

var response = await client.PostAsync("/users", content, cancellationToken);

Assert.Equal(HttpStatusCode.OK, response.StatusCode);

var body = await response.Content.ReadAsStringAsync(cancellationToken);
using var json = JsonDocument.Parse(body);
Assert.Equal(JsonValueKind.Object, json.RootElement.ValueKind);
Assert.True(json.RootElement.TryGetProperty("name", out _));
}
finally
{
await host.StopAsync(cancellationToken);
host.Dispose();
}
}

[Fact]
public async Task Http_Put_Body_Succeeds_On_Kestrel()
{
var cancellationToken = TestContext.Current.CancellationToken;
var storage = CreateBasicTestDefinitionStorage();
var host = await StartKestrelHostAsync(storage, cancellationToken);

try
{
using var client = new HttpClient { BaseAddress = GetBaseAddress(host) };
// The PUT route is "/users/{userId:$user.id}", so "id" is supplied by the
// route segment and must not also appear in the body.
var content = new StringContent(
"""{ "name": "Test", "email": "test@example.com" }""",
Encoding.UTF8,
"application/json");

var response = await client.PutAsync("/users/6", content, cancellationToken);

Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
finally
{
await host.StopAsync(cancellationToken);
host.Dispose();
}
}

private async Task<IWebHost> StartKestrelHostAsync(

Check warning on line 86 in src/HotChocolate/Adapters/test/Adapters.OpenApi.Tests/Endpoints/KestrelHttpEndpointIntegrationTests.cs

View workflow job for this annotation

GitHub Actions / Run HotChocolate.Adapters.OpenApi.Tests

'IWebHost' is obsolete: 'IWebHost is obsolete. Use IHost instead. For more information, visit https://aka.ms/aspnet/deprecate/008.' (https://aka.ms/aspnet/deprecate/008)
IOpenApiDefinitionStorage storage,
CancellationToken cancellationToken)
{
var host = new WebHostBuilder()

Check warning on line 90 in src/HotChocolate/Adapters/test/Adapters.OpenApi.Tests/Endpoints/KestrelHttpEndpointIntegrationTests.cs

View workflow job for this annotation

GitHub Actions / Run HotChocolate.Adapters.OpenApi.Tests

'IWebHostBuilder.Build()' is obsolete: 'IWebHost is obsolete. Use IHost instead. For more information, visit https://aka.ms/aspnet/deprecate/008.' (https://aka.ms/aspnet/deprecate/008)

Check warning on line 90 in src/HotChocolate/Adapters/test/Adapters.OpenApi.Tests/Endpoints/KestrelHttpEndpointIntegrationTests.cs

View workflow job for this annotation

GitHub Actions / Run HotChocolate.Adapters.OpenApi.Tests

'WebHostBuilder' is obsolete: 'WebHostBuilder is deprecated in favor of HostBuilder and WebApplicationBuilder. For more information, visit https://aka.ms/aspnet/deprecate/004.' (https://aka.ms/aspnet/deprecate/004)
.UseKestrel()
.UseUrls("http://127.0.0.1:0")
.ConfigureServices(services =>
{
services.AddRouting();

services.AddGraphQLServer()
.AddOpenApi()
.AddOpenApiDefinitionStorage(storage)
.AddBasicServer();
})
.Configure(app =>
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapOpenApiEndpoints();
endpoints.MapGraphQL();
});
})
.Build();

await host.StartAsync(cancellationToken);
return host;
}

private static Uri GetBaseAddress(IWebHost host)

Check warning on line 117 in src/HotChocolate/Adapters/test/Adapters.OpenApi.Tests/Endpoints/KestrelHttpEndpointIntegrationTests.cs

View workflow job for this annotation

GitHub Actions / Run HotChocolate.Adapters.OpenApi.Tests

'IWebHost' is obsolete: 'IWebHost is obsolete. Use IHost instead. For more information, visit https://aka.ms/aspnet/deprecate/008.' (https://aka.ms/aspnet/deprecate/008)
{
var addresses = host.ServerFeatures.Get<IServerAddressesFeature>()?.Addresses
?? throw new InvalidOperationException("Kestrel did not expose any server addresses.");

var address = addresses.FirstOrDefault()
?? throw new InvalidOperationException("Kestrel did not bind to any address.");

return new Uri(address);
}
}
Loading