Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added HTTP GET Middleware Options #2583

Merged
merged 11 commits into from
Nov 16, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Microsoft.AspNetCore.Http;

namespace HotChocolate.AspNetCore
{
internal static class HttpContextExtensions
{
public static GraphQLServerOptions? GetGraphQLServerOptions(this HttpContext context) =>
context.GetEndpoint()?.Metadata.GetMetadata<GraphQLServerOptions>();

public static GraphQLToolOptions? GetGraphQLToolOptions(this HttpContext context) =>
GetGraphQLServerOptions(context)?.Tool;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using HotChocolate;
using HotChocolate.AspNetCore;
using Microsoft.AspNetCore.Http;
Expand Down Expand Up @@ -40,7 +41,12 @@ public static class HttpEndpointRouteBuilderExtensions
.UseMiddleware<ToolDefaultFileMiddleware>(fileProvider, path)
.UseMiddleware<ToolOptionsFileMiddleware>(schemaNameOrDefault, path)
.UseMiddleware<ToolStaticFileMiddleware>(fileProvider, path)
.UseMiddleware<HttpGetMiddleware>(schemaNameOrDefault);
.UseMiddleware<HttpGetMiddleware>(schemaNameOrDefault)
.Use(next => context =>
{
context.Response.StatusCode = 404;
return Task.CompletedTask;
});

return endpointRouteBuilder
.Map(pattern, requestPipeline.Build())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ namespace Microsoft.AspNetCore.Builder
{
public static class RoutingEndpointConventionBuilderExtensions
{
public static TBuilder WithToolOptions<TBuilder>(this TBuilder builder, ToolOptions options)
where TBuilder : IEndpointConventionBuilder
{
return builder.WithMetadata(options);
}
public static TBuilder WithOptions<TBuilder>(
this TBuilder builder,
GraphQLServerOptions serverOptions)
where TBuilder : IEndpointConventionBuilder =>
builder.WithMetadata(serverOptions);
}
}
83 changes: 83 additions & 0 deletions src/HotChocolate/AspNetCore/src/AspNetCore/GraphQLServerOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using Microsoft.AspNetCore.Http;

namespace HotChocolate.AspNetCore
{
/// <summary>
/// Represents the GraphQL server options.
/// </summary>
public class GraphQLServerOptions
{
/// <summary>
/// Gets the GraphQL tool options for Banana Cake Pop.
/// </summary>
public GraphQLToolOptions Tool { get; } = new();

/// <summary>
/// Gets or sets which GraphQL options are allowed on GET requests.
/// </summary>
public AllowedGetOperations AllowedGetOperations { get; set; } =
AllowedGetOperations.Query;

/// <summary>
/// Defines if GraphQL HTTP GET requests are allowed.
/// </summary>
/// <value></value>
public bool EnableGetRequest { get; set; } = true;

/// <summary>
/// Defines if the GraphQL schema SDL can be downloaded.
/// </summary>
/// <value></value>
public bool EnableSchemaRequest { get; set; } = true;
}

/// <summary>
/// Represents the GraphQL tool options for Banana Cake Pop.
/// </summary>
public class GraphQLToolOptions
{
/// <summary>
/// Gets or sets the default document content.
/// </summary>
public string? Document { get; set; }

/// <summary>
/// Gets or sets the default method.
/// </summary>
public DefaultCredentials? Credentials { get; set; }

/// <summary>
/// Gets or sets the default http headers for Banana Cake Pop.
/// </summary>
public IHeaderDictionary? HttpHeaders { get; set; }

/// <summary>
/// Gets or sets the default
/// </summary>
public DefaultHttpMethod? HttpMethod { get; set; }

/// <summary>
/// Defines if Banana Cake Pop is enabled.
/// </summary>
public bool Enable { get; set; } = true;
}

public enum DefaultCredentials
{
Include,
Omit,
SameOrigin,
}

public enum DefaultHttpMethod
{
Get,
Post
}

public enum AllowedGetOperations
{
Query,
QueryAndMutation
}
}
16 changes: 13 additions & 3 deletions src/HotChocolate/AspNetCore/src/AspNetCore/HttpGetMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ namespace HotChocolate.AspNetCore
{
public class HttpGetMiddleware : MiddlewareBase
{
private static readonly OperationType[] _onlyQueries = { OperationType.Query };

private readonly IHttpRequestParser _requestParser;

public HttpGetMiddleware(
Expand All @@ -28,7 +30,8 @@ public class HttpGetMiddleware : MiddlewareBase

public async Task InvokeAsync(HttpContext context)
{
if (HttpMethods.IsGet(context.Request.Method))
if (HttpMethods.IsGet(context.Request.Method) &&
(context.GetGraphQLServerOptions()?.EnableGetRequest ?? true))
{
await HandleRequestAsync(context);
}
Expand All @@ -54,8 +57,15 @@ private async Task HandleRequestAsync(HttpContext context)
{
// next we parse the GraphQL request.
GraphQLRequest request = _requestParser.ReadParamsRequest(context.Request.Query);
GraphQLServerOptions? options = context.GetGraphQLServerOptions();
result = await ExecuteSingleAsync(
context, requestExecutor, requestInterceptor, request);
context,
requestExecutor,
requestInterceptor,
request,
options is null or { AllowedGetOperations: AllowedGetOperations.Query }
? _onlyQueries
: null);
}
catch (GraphQLRequestException ex)
{
Expand All @@ -74,7 +84,7 @@ private async Task HandleRequestAsync(HttpContext context)

// in any case we will have a valid GraphQL result at this point that can be written
// to the HTTP response stream.
Debug.Assert(result is not null, "No GraphQL result was created.");
Debug.Assert(result is not null!, "No GraphQL result was created.");
await WriteResultAsync(context.Response, result, statusCode, context.RequestAborted);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ public class HttpGetSchemaMiddleware : MiddlewareBase
public async Task InvokeAsync(HttpContext context)
{
if (HttpMethods.IsGet(context.Request.Method) &&
context.Request.Query.ContainsKey("SDL"))
context.Request.Query.ContainsKey("SDL") &&
(context.GetGraphQLServerOptions()?.EnableSchemaRequest ?? true))
{
await HandleRequestAsync(context);
}
Expand Down
4 changes: 3 additions & 1 deletion src/HotChocolate/AspNetCore/src/AspNetCore/MiddlewareBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,11 @@ public class MiddlewareBase : IDisposable
HttpContext context,
IRequestExecutor requestExecutor,
IHttpRequestInterceptor requestInterceptor,
GraphQLRequest request)
GraphQLRequest request,
OperationType[]? allowedOperations = null)
{
QueryRequestBuilder requestBuilder = QueryRequestBuilder.From(request);
requestBuilder.SetAllowedOperations(allowedOperations);

await requestInterceptor.OnCreateAsync(
context, requestExecutor, requestBuilder, context.RequestAborted);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ public Task Invoke(HttpContext context)
{
if (context.Request.IsGetOrHeadMethod() &&
context.Request.AcceptHeaderContainsHtml() &&
context.Request.TryMatchPath(_matchUrl, true, out PathString subPath))
context.Request.TryMatchPath(_matchUrl, true, out PathString subPath) &&
(context.GetGraphQLToolOptions()?.Enable ?? true))
{
var dirContents = _fileProvider.GetDirectoryContents(subPath.Value);

Expand Down
28 changes: 0 additions & 28 deletions src/HotChocolate/AspNetCore/src/AspNetCore/ToolOptions.cs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Extensions.Primitives;
using HotChocolate.AspNetCore.Utilities;
using HotChocolate.Execution;
using Microsoft.AspNetCore.Http;
using HttpRequestDelegate = Microsoft.AspNetCore.Http.RequestDelegate;
using Microsoft.AspNetCore.StaticFiles;

namespace HotChocolate.AspNetCore
{
Expand All @@ -15,7 +15,6 @@ public class ToolOptionsFileMiddleware
: MiddlewareBase
{
private const string _configFile = "/bcp-config.json";
private readonly IContentTypeProvider _contentTypeProvider;
private readonly PathString _matchUrl;

public ToolOptionsFileMiddleware(
Expand All @@ -26,7 +25,6 @@ public class ToolOptionsFileMiddleware
PathString matchUrl)
: base(next, executorResolver, resultSerializer, schemaName)
{
_contentTypeProvider = new FileExtensionContentTypeProvider();
_matchUrl = matchUrl;
}

Expand All @@ -38,9 +36,10 @@ public async Task Invoke(HttpContext context)
{
if (context.Request.IsGetOrHeadMethod() &&
context.Request.TryMatchPath(_matchUrl, false, out PathString subPath) &&
subPath.Value == _configFile)
subPath.Value == _configFile &&
(context.GetGraphQLToolOptions()?.Enable ?? true))
{
ToolOptions? options = context.GetEndpoint()?.Metadata.GetMetadata<ToolOptions>();
GraphQLToolOptions? options = context.GetGraphQLToolOptions();
string endpointPath = context.Request.Path.Value!.Replace(_configFile, "");
string schemaEndpoint = CreateEndpointUri(
context.Request.Host.Value,
Expand Down Expand Up @@ -78,9 +77,9 @@ public async Task Invoke(HttpContext context)
}
}

private string? ConvertCredentialsToString(DefaultCredentials? credentials)
private static string? ConvertCredentialsToString(DefaultCredentials? credentials)
{
if (credentials is { })
if (credentials is not null)
{
switch (credentials)
{
Expand All @@ -96,13 +95,14 @@ public async Task Invoke(HttpContext context)
return null;
}

private IDictionary<string, string>? ConvertHttpHeadersToDictionary(IHeaderDictionary? httpHeaders)
private static IDictionary<string, string>? ConvertHttpHeadersToDictionary(
IHeaderDictionary? httpHeaders)
{
if (httpHeaders is { })
if (httpHeaders is not null)
{
var result = new Dictionary<string, string>();

foreach (var (key, value) in httpHeaders)
foreach ((var key, StringValues value) in httpHeaders)
{
result.Add(key, value.ToString());
}
Expand All @@ -115,7 +115,7 @@ public async Task Invoke(HttpContext context)

private string? ConvertHttpMethodToString(DefaultHttpMethod? httpMethod)
{
if (httpMethod is { })
if (httpMethod is not null)
{
switch (httpMethod)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ public Task Invoke(HttpContext context)
{
if (context.Request.IsGetOrHeadMethod() &&
context.Request.TryMatchPath(_matchUrl, false, out PathString subPath) &&
_contentTypeProvider.TryGetContentType(subPath.Value, out string contentType))
_contentTypeProvider.TryGetContentType(subPath.Value, out string contentType) &&
(context.GetGraphQLToolOptions()?.Enable ?? true))
{
return TryServeStaticFile(context, contentType, subPath);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class DefaultHttpRequestInterceptor : IHttpRequestInterceptor

if (context.IsTracingEnabled())
{
requestBuilder.TryAddProperty(ContextDataKeys.EnableTracing, true);
requestBuilder.TryAddProperty(WellKnownContextData.EnableTracing, true);
}

return default;
Expand Down