Skip to content

Commit 157e17e

Browse files
authored
Introduce optimized persisted query pipeline. (#6951)
1 parent 854d763 commit 157e17e

25 files changed

+1300
-289
lines changed

src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/EndpointRouteBuilderExtensions.cs

Lines changed: 105 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
using System.Diagnostics.CodeAnalysis;
12
using Microsoft.AspNetCore.Http;
23
using Microsoft.AspNetCore.Routing;
34
using Microsoft.AspNetCore.Routing.Patterns;
45
using HotChocolate.AspNetCore;
56
using HotChocolate.AspNetCore.Extensions;
67
using BananaCakePop.Middleware;
8+
using HotChocolate.AspNetCore.Instrumentation;
9+
using HotChocolate.AspNetCore.Serialization;
710
using static HotChocolate.AspNetCore.MiddlewareRoutingType;
811
using static Microsoft.AspNetCore.Routing.Patterns.RoutePatternFactory;
912

@@ -19,6 +22,7 @@ public static class EndpointRouteBuilderExtensions
1922
private const string _graphQLWebSocketPath = "/graphql/ws";
2023
private const string _graphQLSchemaPath = "/graphql/sdl";
2124
private const string _graphQLToolPath = "/graphql/ui";
25+
private const string _graphQLPersistedOperationPath = "/graphql/q";
2226
private const string _graphQLToolRelativeRequestPath = "..";
2327

2428
/// <summary>
@@ -73,11 +77,9 @@ public static GraphQLEndpointConventionBuilder MapGraphQL(
7377
}
7478

7579
path = path.ToString().TrimEnd('/');
76-
80+
var schemaNameOrDefault = schemaName ?? Schema.DefaultName;
7781
var pattern = Parse(path + "/{**slug}");
7882
var requestPipeline = endpointRouteBuilder.CreateApplicationBuilder();
79-
var schemaNameOrDefault = schemaName ?? Schema.DefaultName;
80-
8183
requestPipeline.MapGraphQL(path, schemaNameOrDefault);
8284

8385
return new GraphQLEndpointConventionBuilder(
@@ -125,11 +127,12 @@ public static IApplicationBuilder MapGraphQL(
125127
.UseMiddleware<HttpGetMiddleware>(schemaName)
126128
.UseMiddleware<HttpGetSchemaMiddleware>(schemaName, Integrated)
127129
.UseBananaCakePop(path)
128-
.Use(_ => context =>
129-
{
130-
context.Response.StatusCode = 404;
131-
return Task.CompletedTask;
132-
});
130+
.Use(
131+
_ => context =>
132+
{
133+
context.Response.StatusCode = 404;
134+
return Task.CompletedTask;
135+
});
133136

134137
return applicationBuilder;
135138
}
@@ -201,11 +204,12 @@ public static GraphQLHttpEndpointConventionBuilder MapGraphQLHttp(
201204
.UseMiddleware<HttpPostMiddleware>(schemaNameOrDefault)
202205
.UseMiddleware<HttpMultipartMiddleware>(schemaNameOrDefault)
203206
.UseMiddleware<HttpGetMiddleware>(schemaNameOrDefault)
204-
.Use(_ => context =>
205-
{
206-
context.Response.StatusCode = 404;
207-
return Task.CompletedTask;
208-
});
207+
.Use(
208+
_ => context =>
209+
{
210+
context.Response.StatusCode = 404;
211+
return Task.CompletedTask;
212+
});
209213

210214
return new GraphQLHttpEndpointConventionBuilder(
211215
endpointRouteBuilder
@@ -278,11 +282,12 @@ public static WebSocketEndpointConventionBuilder MapGraphQLWebSocket(
278282
requestPipeline
279283
.UseCancellation()
280284
.UseMiddleware<WebSocketSubscriptionMiddleware>(schemaNameOrDefault)
281-
.Use(_ => context =>
282-
{
283-
context.Response.StatusCode = 404;
284-
return Task.CompletedTask;
285-
});
285+
.Use(
286+
_ => context =>
287+
{
288+
context.Response.StatusCode = 404;
289+
return Task.CompletedTask;
290+
});
286291

287292
var builder = new GraphQLEndpointConventionBuilder(
288293
endpointRouteBuilder
@@ -357,11 +362,12 @@ public static IEndpointConventionBuilder MapGraphQLSchema(
357362
requestPipeline
358363
.UseCancellation()
359364
.UseMiddleware<HttpGetSchemaMiddleware>(schemaNameOrDefault, Explicit)
360-
.Use(_ => context =>
361-
{
362-
context.Response.StatusCode = 404;
363-
return Task.CompletedTask;
364-
});
365+
.Use(
366+
_ => context =>
367+
{
368+
context.Response.StatusCode = 404;
369+
return Task.CompletedTask;
370+
});
365371

366372
return new GraphQLEndpointConventionBuilder(
367373
endpointRouteBuilder
@@ -425,11 +431,12 @@ public static BananaCakePopEndpointConventionBuilder MapBananaCakePop(
425431

426432
requestPipeline
427433
.UseBananaCakePop(toolPath)
428-
.Use(_ => context =>
429-
{
430-
context.Response.StatusCode = 404;
431-
return Task.CompletedTask;
432-
});
434+
.Use(
435+
_ => context =>
436+
{
437+
context.Response.StatusCode = 404;
438+
return Task.CompletedTask;
439+
});
433440

434441
var builder = endpointRouteBuilder
435442
.Map(pattern, requestPipeline.Build())
@@ -439,6 +446,56 @@ public static BananaCakePopEndpointConventionBuilder MapBananaCakePop(
439446
return new BananaCakePopEndpointConventionBuilder(builder);
440447
}
441448

449+
#if NET8_0_OR_GREATER
450+
/// <summary>
451+
/// Adds a persisted query endpoint to the endpoint configurations.
452+
/// </summary>
453+
/// <param name="endpointRouteBuilder">
454+
/// The <see cref="IEndpointRouteBuilder"/>.
455+
/// </param>
456+
/// <param name="path">
457+
/// The path to which the persisted query endpoint shall be mapped.
458+
/// </param>
459+
/// <param name="schemaName">
460+
/// The name of the schema that shall be used by this endpoint.
461+
/// </param>
462+
/// <returns>
463+
/// Returns the <see cref="IEndpointConventionBuilder"/> so that
464+
/// </returns>
465+
public static IEndpointConventionBuilder MapGraphQLPersistedOperations(
466+
this IEndpointRouteBuilder endpointRouteBuilder,
467+
[StringSyntax("Route")] string path = _graphQLPersistedOperationPath,
468+
string? schemaName = default)
469+
=> MapGraphQLPersistedOperations(endpointRouteBuilder, Parse(path), schemaName);
470+
471+
/// <summary>
472+
/// Adds a persisted query endpoint to the endpoint configurations.
473+
/// </summary>
474+
/// <param name="endpointRouteBuilder">
475+
/// The <see cref="IEndpointRouteBuilder"/>.
476+
/// </param>
477+
/// <param name="path">
478+
/// The path to which the persisted query endpoint shall be mapped.
479+
/// </param>
480+
/// <param name="schemaName">
481+
/// The name of the schema that shall be used by this endpoint.
482+
/// </param>
483+
/// <returns>
484+
/// Returns the <see cref="IEndpointConventionBuilder"/> so that
485+
/// </returns>
486+
public static IEndpointConventionBuilder MapGraphQLPersistedOperations(
487+
this IEndpointRouteBuilder endpointRouteBuilder,
488+
RoutePattern path,
489+
string? schemaName = default)
490+
{
491+
var schemaNameOrDefault = schemaName ?? Schema.DefaultName;
492+
493+
var group = endpointRouteBuilder.MapGroup(path);
494+
group.MapPersistedQueryMiddleware(schemaNameOrDefault);
495+
return group;
496+
}
497+
#endif
498+
442499
/// <summary>
443500
/// Specifies the GraphQL server options.
444501
/// </summary>
@@ -454,7 +511,7 @@ public static BananaCakePopEndpointConventionBuilder MapBananaCakePop(
454511
/// </returns>
455512
public static GraphQLEndpointConventionBuilder WithOptions(
456513
this GraphQLEndpointConventionBuilder builder,
457-
GraphQLServerOptions serverOptions)
514+
GraphQLServerOptions serverOptions)
458515
=> builder
459516
.WithMetadata(serverOptions)
460517
.WithMetadata(serverOptions.Tool.ToBcpOptions());
@@ -475,12 +532,13 @@ public static GraphQLEndpointConventionBuilder WithOptions(
475532
public static GraphQLHttpEndpointConventionBuilder WithOptions(
476533
this GraphQLHttpEndpointConventionBuilder builder,
477534
GraphQLHttpOptions httpOptions) =>
478-
builder.WithMetadata(new GraphQLServerOptions
479-
{
480-
AllowedGetOperations = httpOptions.AllowedGetOperations,
481-
EnableGetRequests = httpOptions.EnableGetRequests,
482-
EnableMultipartRequests = httpOptions.EnableMultipartRequests,
483-
});
535+
builder.WithMetadata(
536+
new GraphQLServerOptions
537+
{
538+
AllowedGetOperations = httpOptions.AllowedGetOperations,
539+
EnableGetRequests = httpOptions.EnableGetRequests,
540+
EnableMultipartRequests = httpOptions.EnableMultipartRequests,
541+
});
484542

485543
/// <summary>
486544
/// Specifies the Banana Cake Pop tooling options.
@@ -522,17 +580,18 @@ public static WebSocketEndpointConventionBuilder WithOptions(
522580
builder.WithMetadata(new GraphQLServerOptions { Sockets = socketOptions, });
523581

524582
private static IApplicationBuilder UseCancellation(this IApplicationBuilder builder)
525-
=> builder.Use(next => async context =>
526-
{
527-
try
528-
{
529-
await next(context);
530-
}
531-
catch (OperationCanceledException)
583+
=> builder.Use(
584+
next => async context =>
532585
{
533-
// we just catch cancellations here and do nothing.
534-
}
535-
});
586+
try
587+
{
588+
await next(context);
589+
}
590+
catch (OperationCanceledException)
591+
{
592+
// we just catch cancellations here and do nothing.
593+
}
594+
});
536595

537596
internal static BananaCakePopOptions ToBcpOptions(this GraphQLToolOptions options)
538597
=> new()
@@ -549,4 +608,4 @@ internal static BananaCakePopOptions ToBcpOptions(this GraphQLToolOptions option
549608
GaTrackingId = options.GaTrackingId,
550609
DisableTelemetry = options.DisableTelemetry,
551610
};
552-
}
611+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using HotChocolate.Execution.Configuration;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using static HotChocolate.AspNetCore.ServerDefaults;
4+
5+
#if NET8_0_OR_GREATER
6+
namespace Microsoft.Extensions.Hosting;
7+
8+
public static class HotChocolateAspNetCoreHostingBuilderExtensions
9+
{
10+
public static IRequestExecutorBuilder AddGraphQL(
11+
IHostApplicationBuilder builder,
12+
string? schemaName = default,
13+
int maxAllowedRequestSize = MaxAllowedRequestSize)
14+
=> builder.Services.AddGraphQLServer(schemaName, maxAllowedRequestSize);
15+
}
16+
#endif

src/HotChocolate/AspNetCore/src/AspNetCore/Extensions/HotChocolateAspNetCoreServiceCollectionExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,4 @@ public static IRequestExecutorBuilder AddUploadType(
145145
builder.AddType<UploadType>();
146146
return builder;
147147
}
148-
}
148+
}

0 commit comments

Comments
 (0)