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
8 changes: 8 additions & 0 deletions .nuke/build.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,14 @@
"VSCode"
]
},
"MyGetApiKey": {
"type": "string",
"description": "MyGet Api Key"
},
"MyGetSource": {
"type": "string",
"description": "NuGet Source for Packages"
},
"NoLogo": {
"type": "boolean",
"description": "Disables displaying the NUKE logo"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using HotChocolate.AspNetCore.Subscriptions;
using HotChocolate.AspNetCore.Subscriptions.Messages;
using HotChocolate.AspNetCore.Subscriptions.Protocols;

namespace HotChocolate.AspNetCore;

public class DefaultSocketSessionInterceptor : ISocketSessionInterceptor
{
public virtual ValueTask<ConnectionStatus> OnConnectAsync(
ISocketConnection connection,
InitializeConnectionMessage message,
CancellationToken cancellationToken) =>
new ValueTask<ConnectionStatus>(ConnectionStatus.Accept());
ISocketSession session,
IOperationMessagePayload connectionInitMessage,
CancellationToken cancellationToken = default)
=> new(ConnectionStatus.Accept());

public virtual ValueTask OnRequestAsync(
ISocketConnection connection,
ISocketSession session,
string operationSessionId,
IQueryRequestBuilder requestBuilder,
CancellationToken cancellationToken)
CancellationToken cancellationToken = default)
{
HttpContext context = connection.HttpContext;
requestBuilder.TrySetServices(connection.RequestServices);
requestBuilder.TryAddProperty(nameof(CancellationToken), connection.RequestAborted);
HttpContext context = session.Connection.HttpContext;
requestBuilder.TrySetServices(session.Connection.RequestServices);
requestBuilder.TryAddProperty(nameof(CancellationToken), session.Connection.RequestAborted);
requestBuilder.TryAddProperty(nameof(HttpContext), context);
requestBuilder.TryAddProperty(nameof(ClaimsPrincipal), context.User);
requestBuilder.TryAddProperty(nameof(ISocketSession), session);

if (context.IsTracingEnabled())
{
Expand All @@ -37,8 +39,34 @@ public virtual ValueTask OnRequestAsync(
return default;
}

public virtual ValueTask<IQueryResult> OnResultAsync(
ISocketSession session,
string operationSessionId,
IQueryResult result,
CancellationToken cancellationToken = default)
=> new(result);

public virtual ValueTask OnCompleteAsync(
ISocketSession session,
string operationSessionId,
CancellationToken cancellationToken = default)
=> default;

public virtual ValueTask<IReadOnlyDictionary<string, object?>?> OnPingAsync(
ISocketSession session,
IOperationMessagePayload pingMessage,
CancellationToken cancellationToken = default)
=> new(default(IReadOnlyDictionary<string, object?>?));


public virtual ValueTask OnPongAsync(
ISocketSession session,
IOperationMessagePayload pongMessage,
CancellationToken cancellationToken = default)
=> default;

public virtual ValueTask OnCloseAsync(
ISocketConnection connection,
CancellationToken cancellationToken) =>
default;
ISocketSession session,
CancellationToken cancellationToken = default)
=> default;
}
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,24 @@ public static BananaCakePopEndpointConventionBuilder WithOptions(
GraphQLToolOptions toolOptions) =>
builder.WithMetadata(new GraphQLServerOptions { Tool = toolOptions });

/// <summary>
/// Specifies the GraphQL over Websocket options.
/// </summary>
/// <param name="builder">
/// The <see cref="WebSocketEndpointConventionBuilder"/>.
/// </param>
/// <param name="socketOptions">
/// The GraphQL socket options.
/// </param>
/// <returns>
/// Returns the <see cref="WebSocketEndpointConventionBuilder"/> so that
/// configuration can be chained.
/// </returns>
public static WebSocketEndpointConventionBuilder WithOptions(
this WebSocketEndpointConventionBuilder builder,
GraphQLSocketOptions socketOptions) =>
builder.WithMetadata(new GraphQLServerOptions { Sockets = socketOptions });

private static IFileProvider CreateFileProvider()
{
Type? type = typeof(EndpointRouteBuilderExtensions);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
using Microsoft.Extensions.DependencyInjection.Extensions;
using HotChocolate.AspNetCore;
using HotChocolate.AspNetCore.Subscriptions;
using HotChocolate.AspNetCore.Subscriptions.Messages;
using HotChocolate.AspNetCore.Subscriptions.Protocols;
using HotChocolate.AspNetCore.Subscriptions.Protocols.Apollo;
using HotChocolate.AspNetCore.Subscriptions.Protocols.GraphQLOverWebSocket;
using HotChocolate.Execution.Configuration;
using HotChocolate.Utilities;

Expand Down Expand Up @@ -50,21 +51,27 @@ public static IRequestExecutorBuilder AddSocketSessionInterceptor<T>(
where T : class, ISocketSessionInterceptor =>
builder.ConfigureSchemaServices(s => s
.RemoveAll<ISocketSessionInterceptor>()
.AddSingleton<ISocketSessionInterceptor, T>(
sp => factory(sp.GetCombinedServices())));
.AddSingleton<ISocketSessionInterceptor, T>(sp => factory(sp.GetCombinedServices())));

private static IRequestExecutorBuilder AddSubscriptionServices(
this IRequestExecutorBuilder builder)
{
return builder.ConfigureSchemaServices(s =>
{
s.TryAddSingleton<IMessagePipeline, DefaultMessagePipeline>();
s.TryAddSingleton<ISocketSessionInterceptor, DefaultSocketSessionInterceptor>();
=> builder
.ConfigureSchemaServices(s => s
.TryAddSingleton<ISocketSessionInterceptor, DefaultSocketSessionInterceptor>())
.AddApolloProtocol()
.AddGraphQLOverWebSocketProtocol();

private static IRequestExecutorBuilder AddApolloProtocol(
this IRequestExecutorBuilder builder)
=> builder.ConfigureSchemaServices(
s => s.AddSingleton<IProtocolHandler>(
sp => new ApolloSubscriptionProtocolHandler(
sp.GetRequiredService<ISocketSessionInterceptor>())));

s.AddSingleton<IMessageHandler, DataStartMessageHandler>();
s.AddSingleton<IMessageHandler, DataStopMessageHandler>();
s.AddSingleton<IMessageHandler, InitializeConnectionMessageHandler>();
s.AddSingleton<IMessageHandler, TerminateConnectionMessageHandler>();
});
}
private static IRequestExecutorBuilder AddGraphQLOverWebSocketProtocol(
this IRequestExecutorBuilder builder)
=> builder.ConfigureSchemaServices(
s => s.AddSingleton<IProtocolHandler>(
sp => new GraphQLOverWebSocketProtocolHandler(
sp.GetRequiredService<ISocketSessionInterceptor>())));
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using HotChocolate.AspNetCore.Serialization;
using HotChocolate.Execution.Configuration;
using HotChocolate.Language;
using static HotChocolate.AspNetCore.ServerDefaults;

namespace Microsoft.Extensions.DependencyInjection;

Expand All @@ -28,7 +29,7 @@ public static partial class HotChocolateAspNetCoreServiceCollectionExtensions
/// </exception>
public static IServiceCollection AddGraphQLServerCore(
this IServiceCollection services,
int maxAllowedRequestSize = 20 * 1000 * 1000)
int maxAllowedRequestSize = MaxAllowedRequestSize)
{
if (services is null)
{
Expand Down Expand Up @@ -76,7 +77,7 @@ public static IServiceCollection AddGraphQLServerCore(
public static IRequestExecutorBuilder AddGraphQLServer(
this IServiceCollection services,
NameString schemaName = default,
int maxAllowedRequestSize = 20 * 1000 * 1000) =>
int maxAllowedRequestSize = MaxAllowedRequestSize) =>
services
.AddGraphQLServerCore(maxAllowedRequestSize)
.AddGraphQL(schemaName)
Expand Down Expand Up @@ -106,7 +107,7 @@ public static IRequestExecutorBuilder AddGraphQLServer(
public static IServiceCollection AddGraphQL(
this IServiceCollection services,
ISchema schema,
int maxAllowedRequestSize = 20 * 1000 * 1000) =>
int maxAllowedRequestSize = MaxAllowedRequestSize) =>
RequestExecutorBuilderLegacyHelper.SetSchema(
services
.AddGraphQLServerCore(maxAllowedRequestSize)
Expand All @@ -122,7 +123,7 @@ public static IServiceCollection AddGraphQL(
public static IServiceCollection AddGraphQL(
this IServiceCollection services,
Func<IServiceProvider, ISchema> schemaFactory,
int maxAllowedRequestSize = 20 * 1000 * 1000) =>
int maxAllowedRequestSize = MaxAllowedRequestSize) =>
RequestExecutorBuilderLegacyHelper.SetSchema(
services
.AddGraphQLServerCore(maxAllowedRequestSize)
Expand All @@ -138,7 +139,7 @@ public static IServiceCollection AddGraphQL(
public static IServiceCollection AddGraphQL(
this IServiceCollection services,
ISchemaBuilder schemaBuilder,
int maxAllowedRequestSize = 20 * 1000 * 1000) =>
int maxAllowedRequestSize = MaxAllowedRequestSize) =>
RequestExecutorBuilderLegacyHelper.SetSchemaBuilder(
services
.AddGraphQLServerCore(maxAllowedRequestSize)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ o is GraphQLServerOptions options
public static GraphQLToolOptions? GetGraphQLToolOptions(this HttpContext context)
=> GetGraphQLServerOptions(context)?.Tool;

public static GraphQLSocketOptions? GetGraphQLSocketOptions(this HttpContext context)
=> GetGraphQLServerOptions(context)?.Sockets;

public static GraphQLEndpointOptions? GetGraphQLEndpointOptions(this HttpContext context)
=> context.GetEndpoint()?.Metadata.GetMetadata<GraphQLEndpointOptions>() ??
(context.Items.TryGetValue(nameof(GraphQLEndpointOptions), out var o) &&
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Microsoft.AspNetCore.Builder;

namespace HotChocolate.AspNetCore.Extensions;

/// <summary>
/// Represents the endpoint convention builder for GraphQL over WebSockets.
/// </summary>
public sealed class WebSocketEndpointConventionBuilder : IEndpointConventionBuilder
{
private readonly IEndpointConventionBuilder _builder;

internal WebSocketEndpointConventionBuilder(IEndpointConventionBuilder builder)
{
_builder = builder;
}

/// <inheritdoc />
public void Add(Action<EndpointBuilder> convention) =>
_builder.Add(convention);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ namespace HotChocolate.AspNetCore;
/// <summary>
/// Represents the GraphQL HTTP options.
/// </summary>
public class GraphQLHttpOptions
public sealed class GraphQLHttpOptions
{
/// <summary>
/// Gets or sets which GraphQL options are allowed on GET requests.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ namespace HotChocolate.AspNetCore;
/// <summary>
/// Represents the GraphQL server options.
/// </summary>
public class GraphQLServerOptions
public sealed class GraphQLServerOptions
{
/// <summary>
/// Gets the GraphQL tool options for Banana Cake Pop.
/// </summary>
public GraphQLToolOptions Tool { get; internal set; } = new();

/// <summary>
/// Gets the GraphQL socket options.
/// </summary>
public GraphQLSocketOptions Sockets { get; internal set; } = new();

/// <summary>
/// Gets or sets which GraphQL options are allowed on GET requests.
/// </summary>
Expand Down
27 changes: 27 additions & 0 deletions src/HotChocolate/AspNetCore/src/AspNetCore/GraphQLSocketOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
namespace HotChocolate.AspNetCore;

/// <summary>
/// Options relevant to GraphQL over Websocket.
/// </summary>
public sealed class GraphQLSocketOptions
{
/// <summary>
/// Defines the time in which the client must send a connection initialization
/// message before the server closes the connection.
///
/// Default: <c>TimeSpan.FromSeconds(10)</c>
/// </summary>
public TimeSpan ConnectionInitializationTimeout { get; set; } =
TimeSpan.FromSeconds(10);

/// <summary>
/// Defines an interval in which the server will send keep alive messages to the client
/// in order to keep the connection open.
///
/// If the interval is set to null the server will send no keep alive messages.
///
/// Default: <c>TimeSpan.FromSeconds(30)</c>
/// </summary>
public TimeSpan? KeepAliveInterval { get; set; } =
TimeSpan.FromSeconds(30);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace HotChocolate.AspNetCore;
/// <summary>
/// Represents the GraphQL tool options for Banana Cake Pop.
/// </summary>
public class GraphQLToolOptions
public sealed class GraphQLToolOptions
{
/// <summary>
/// Gets or sets the website title.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>AspNetCoreResources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Remove="Subscriptions\Protocols\Apollo\**" />
<EmbeddedResource Remove="Subscriptions\Protocols\Apollo\**" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,43 @@
using HotChocolate.AspNetCore.Subscriptions;
using HotChocolate.AspNetCore.Subscriptions.Messages;
using HotChocolate.AspNetCore.Subscriptions.Protocols;

namespace HotChocolate.AspNetCore;

public interface ISocketSessionInterceptor
{
ValueTask<ConnectionStatus> OnConnectAsync(
ISocketConnection connection,
InitializeConnectionMessage message,
CancellationToken cancellationToken);
ISocketSession session,
IOperationMessagePayload connectionInitMessage,
CancellationToken cancellationToken = default);

ValueTask OnRequestAsync(
ISocketConnection connection,
ISocketSession session,
string operationSessionId,
IQueryRequestBuilder requestBuilder,
CancellationToken cancellationToken);
CancellationToken cancellationToken = default);

ValueTask<IQueryResult> OnResultAsync(
ISocketSession session,
string operationSessionId,
IQueryResult result,
CancellationToken cancellationToken = default);

ValueTask OnCompleteAsync(
ISocketSession session,
string operationSessionId,
CancellationToken cancellationToken = default);

ValueTask<IReadOnlyDictionary<string, object?>?> OnPingAsync(
ISocketSession session,
IOperationMessagePayload pingMessage,
CancellationToken cancellationToken = default);

ValueTask OnPongAsync(
ISocketSession session,
IOperationMessagePayload pongMessage,
CancellationToken cancellationToken = default);

ValueTask OnCloseAsync(
ISocketConnection connection,
CancellationToken cancellationToken);
ISocketSession session,
CancellationToken cancellationToken = default);
}
Loading