/src/HotChocolate/AspNetCore/src/AspNetCore/HttpPostMiddleware.cs

https://github.com/ChilliCream/hotchocolate · C# · 183 lines · 139 code · 16 blank · 28 comment · 12 complexity · 771a808f1f4335821c3627ecb7d66f30 MD5 · raw file

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Diagnostics.CodeAnalysis;
  5. using System.Net;
  6. using System.Text;
  7. using System.Threading.Tasks;
  8. using Microsoft.AspNetCore.Http;
  9. using HotChocolate.AspNetCore.Utilities;
  10. using HotChocolate.Execution;
  11. using HotChocolate.Language;
  12. using HttpRequestDelegate = Microsoft.AspNetCore.Http.RequestDelegate;
  13. namespace HotChocolate.AspNetCore
  14. {
  15. public class HttpPostMiddleware : MiddlewareBase
  16. {
  17. private const string _batchOperations = "batchOperations";
  18. private readonly IHttpRequestParser _requestParser;
  19. public HttpPostMiddleware(
  20. HttpRequestDelegate next,
  21. IRequestExecutorResolver executorResolver,
  22. IHttpResultSerializer resultSerializer,
  23. IHttpRequestParser requestParser,
  24. NameString schemaName)
  25. : base(next, executorResolver, resultSerializer, schemaName)
  26. {
  27. _requestParser = requestParser ??
  28. throw new ArgumentNullException(nameof(requestParser));
  29. }
  30. public async Task InvokeAsync(HttpContext context)
  31. {
  32. if (!HttpMethods.IsPost(context.Request.Method))
  33. {
  34. // if the request is not a post request we will just invoke the next
  35. // middleware and do nothing:
  36. await NextAsync(context);
  37. }
  38. else
  39. {
  40. AllowedContentType contentType = ParseContentType(context.Request.ContentType);
  41. if (contentType == AllowedContentType.None)
  42. {
  43. // the content type is unknown so we will invoke the next middleware.
  44. await NextAsync(context);
  45. }
  46. else
  47. {
  48. await HandleRequestAsync(context, contentType);
  49. }
  50. }
  51. }
  52. private async Task HandleRequestAsync(
  53. HttpContext context,
  54. AllowedContentType contentType)
  55. {
  56. // first we need to get the request executor to be able to execute requests.
  57. IRequestExecutor requestExecutor = await GetExecutorAsync(context.RequestAborted);
  58. IHttpRequestInterceptor requestInterceptor = requestExecutor.GetRequestInterceptor();
  59. IErrorHandler errorHandler = requestExecutor.GetErrorHandler();
  60. HttpStatusCode? statusCode = null;
  61. IExecutionResult? result;
  62. try
  63. {
  64. // next we parse the GraphQL request.
  65. IReadOnlyList<GraphQLRequest> requests =
  66. await _requestParser.ReadJsonRequestAsync(
  67. context.Request.Body, context.RequestAborted);
  68. switch (requests.Count)
  69. {
  70. // if the HTTP request body contains no GraphQL request structure the
  71. // whole request is invalid and we will create a GraphQL error response.
  72. case 0:
  73. {
  74. statusCode = HttpStatusCode.BadRequest;
  75. IError error = errorHandler.Handle(ErrorHelper.RequestHasNoElements());
  76. result = QueryResultBuilder.CreateError(error);
  77. break;
  78. }
  79. // if the HTTP request body contains a single GraphQL request and we do have
  80. // the batch operations query parameter specified we need to execute an
  81. // operation batch.
  82. //
  83. // An operation batch consists of a single GraphQL request document that
  84. // contains multiple operations. The batch operation query parameter
  85. // defines the order in which the operations shall be executed.
  86. case 1 when context.Request.Query.ContainsKey(_batchOperations):
  87. {
  88. string operationNames = context.Request.Query[_batchOperations];
  89. if (TryParseOperations(operationNames, out IReadOnlyList<string>? ops))
  90. {
  91. result = await ExecuteOperationBatchAsync(
  92. context, requestExecutor, requestInterceptor, requests[0], ops);
  93. }
  94. else
  95. {
  96. IError error = errorHandler.Handle(ErrorHelper.InvalidRequest());
  97. statusCode = HttpStatusCode.BadRequest;
  98. result = QueryResultBuilder.CreateError(error);
  99. }
  100. break;
  101. }
  102. // if the HTTP request body contains a single GraphQL request and
  103. // no batch query parameter is specified we need to execute a single
  104. // GraphQL request.
  105. //
  106. // Most GraphQL requests will be of this type where we want to execute
  107. // a single GraphQL query or mutation.
  108. case 1:
  109. {
  110. result = await ExecuteSingleAsync(
  111. context, requestExecutor, requestInterceptor, requests[0]);
  112. break;
  113. }
  114. // if the HTTP request body contains more than one GraphQL request than
  115. // we need to execute a request batch where we need to execute multiple
  116. // fully specified GraphQL requests at once.
  117. default:
  118. result = await ExecuteBatchAsync(
  119. context, requestExecutor, requestInterceptor, requests);
  120. break;
  121. }
  122. }
  123. catch (GraphQLRequestException ex)
  124. {
  125. // A GraphQL request exception is thrown if the HTTP request body couldn't be
  126. // parsed. In this case we will return HTTP status code 400 and return a
  127. // GraphQL error result.
  128. statusCode = HttpStatusCode.BadRequest;
  129. result = QueryResultBuilder.CreateError(errorHandler.Handle(ex.Errors));
  130. }
  131. catch (Exception ex)
  132. {
  133. statusCode = HttpStatusCode.InternalServerError;
  134. IError error = errorHandler.CreateUnexpectedError(ex).Build();
  135. result = QueryResultBuilder.CreateError(error);
  136. }
  137. // in any case we will have a valid GraphQL result at this point that can be written
  138. // to the HTTP response stream.
  139. Debug.Assert(result is not null, "No GraphQL result was created.");
  140. await WriteResultAsync(context.Response, result, statusCode, context.RequestAborted);
  141. }
  142. private static bool TryParseOperations(
  143. string operationNameString,
  144. [NotNullWhen(true)] out IReadOnlyList<string>? operationNames)
  145. {
  146. var reader = new Utf8GraphQLReader(Encoding.UTF8.GetBytes(operationNameString));
  147. reader.Read();
  148. if (reader.Kind != TokenKind.LeftBracket)
  149. {
  150. operationNames = null;
  151. return false;
  152. }
  153. var names = new List<string>();
  154. while (reader.Read() && reader.Kind == TokenKind.Name)
  155. {
  156. names.Add(reader.GetName());
  157. }
  158. if (reader.Kind != TokenKind.RightBracket)
  159. {
  160. operationNames = null;
  161. return false;
  162. }
  163. operationNames = names;
  164. return true;
  165. }
  166. }
  167. }