/// <summary> /// Invokes this middleware component allowing it to perform its work against the supplied context. /// </summary> /// <param name="context">The context containing the request passed through the pipeline.</param> /// <param name="next">The delegate pointing to the next piece of middleware to be invoked.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> public Task InvokeAsync(GraphQueryExecutionContext context, GraphMiddlewareInvocationDelegate <GraphQueryExecutionContext> next, CancellationToken cancelToken) { if (context.QueryPlan == null) { context.Metrics?.StartPhase(ApolloExecutionPhase.PARSING); try { context.SyntaxTree = _parser.ParseQueryDocument(context.Request.QueryText?.AsMemory() ?? ReadOnlyMemory <char> .Empty); } catch (GraphQLSyntaxException syntaxException) { // expose syntax exception messages to the client // the parser is self contained and ensures its exception messages are // related to the text being parsed context.Messages.Critical( syntaxException.Message, Constants.ErrorCodes.SYNTAX_ERROR, syntaxException.Location.AsOrigin()); } finally { context.Metrics?.EndPhase(ApolloExecutionPhase.PARSING); } } return(next(context, cancelToken)); }
/// <summary> /// Called by the runtime to invoke the middleware logic. In most cases, the middleware component should return the task /// generated by calling the next delegate in the request chain with the provided context. /// </summary> /// <param name="context">The context governing data in this pipeline run.</param> /// <param name="next">The next delegate in the chain to be invoked.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> public async Task InvokeAsync(GraphQueryExecutionContext context, GraphMiddlewareInvocationDelegate <GraphQueryExecutionContext> next, CancellationToken cancelToken) { context?.Metrics?.Start(); await next(context, cancelToken).ConfigureAwait(false); context?.Metrics?.End(); }
/// <summary> /// Initializes a new instance of the <see cref="RequestCompletedLogEntry" /> class. /// </summary> /// <param name="context">The primary query context.</param> public RequestCompletedLogEntry(GraphQueryExecutionContext context) : base(LogEventIds.RequestCompleted) { this.OperationRequestId = context.Request.Id; this.ResultHasErrors = context.Messages.Severity.IsCritical(); this.ResultHasData = context.Result?.Data != null; }
/// <summary> /// Initializes a new instance of the <see cref="RequestReceivedLogEntry" /> class. /// </summary> /// <param name="context">The context.</param> public RequestReceivedLogEntry(GraphQueryExecutionContext context) : base(LogEventIds.RequestReceived) { this.OperationRequestId = context.Request?.Id; this.Username = context.User?.Identity?.Name; this.QueryOperationName = context.Request?.OperationName; this.QueryText = context.Request?.QueryText; }
/// <inheritdoc /> public virtual void RequestCompleted(GraphQueryExecutionContext queryContext) { if (!this.IsEnabled(LogLevel.Debug)) { return; } var entry = new RequestCompletedLogEntry(queryContext); this.LogEvent(LogLevel.Trace, entry); }
/// <summary> /// Invokes the asynchronous. /// </summary> /// <param name="context">The context.</param> /// <param name="next">The next.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> public async Task InvokeAsync(GraphQueryExecutionContext context, GraphMiddlewareInvocationDelegate <GraphQueryExecutionContext> next, CancellationToken cancelToken) { context.Metrics?.StartPhase(ApolloExecutionPhase.EXECUTION); if (context.IsValid && context.QueryOperation != null) { await this.ExecuteOperation(context).ConfigureAwait(false); } await next(context, cancelToken).ConfigureAwait(false); context.Metrics?.EndPhase(ApolloExecutionPhase.EXECUTION); }
/// <summary> /// Invokes this middleware component allowing it to perform its work against the supplied context. /// </summary> /// <param name="context">The context containing the request passed through the pipeline.</param> /// <param name="next">The delegate pointing to the next piece of middleware to be invoked.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> public Task InvokeAsync(GraphQueryExecutionContext context, GraphMiddlewareInvocationDelegate <GraphQueryExecutionContext> next, CancellationToken cancelToken) { // create and attach the result IResponseFieldSet fieldSet = null; if (context.FieldResults != null && context.FieldResults.Any()) { fieldSet = this.CreateFinalDictionary(context); } context.Result = new GraphOperationResult(context.Request, context.Messages, fieldSet, context.Metrics); context.Logger?.RequestCompleted(context); return(next(context, cancelToken)); }
/// <summary> /// Invokes this middleware component allowing it to perform its work against the supplied context. /// </summary> /// <param name="context">The context containing the request passed through the pipeline.</param> /// <param name="next">The delegate pointing to the next piece of middleware to be invoked.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> public async Task InvokeAsync(GraphQueryExecutionContext context, GraphMiddlewareInvocationDelegate <GraphQueryExecutionContext> next, CancellationToken cancelToken) { bool planFound = false; string key = null; if (_keyManager != null && _cacheProvider != null && context.Request?.QueryText != null) { key = _keyManager.CreateKey <TSchema>(context.Request.QueryText); planFound = await _cacheProvider.TryGetPlanAsync(key, out var queryPlan).ConfigureAwait(false); if (planFound) { context.QueryPlan = queryPlan; context.Logger?.QueryPlanCacheFetchHit <TSchema>(key); } else { context.Logger?.QueryPlanCacheFetchMiss <TSchema>(key); } } // invoke the rest of the pipeline await next(context, cancelToken).ConfigureAwait(false); if (!planFound && !string.IsNullOrWhiteSpace(key) && context.QueryPlan != null && context.QueryPlan.IsValid) { // calculate the plan's time to live in the cache and cache it DateTimeOffset?absoluteExpiration = null; TimeSpan? slidingExpiration = null; if (_cacheOptions.TimeToLiveInMilliseconds.HasValue) { absoluteExpiration = DateTimeOffset.UtcNow.AddMilliseconds(_cacheOptions.TimeToLiveInMilliseconds.Value); } else if (_cacheOptions.SlidingExpiration.HasValue) { slidingExpiration = _cacheOptions.SlidingExpiration.Value; } var successful = await _cacheProvider.TryCachePlanAsync( key, context.QueryPlan, absoluteExpiration, slidingExpiration).ConfigureAwait(false); if (successful) { context.Logger?.QueryPlanCached(key, context.QueryPlan); } } }
/// <summary> /// Invokes this middleware component allowing it to perform its work against the supplied context. /// </summary> /// <param name="context">The context containing the request passed through the pipeline.</param> /// <param name="next">The delegate pointing to the next piece of middleware to be invoked.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> public async Task InvokeAsync(GraphQueryExecutionContext context, GraphMiddlewareInvocationDelegate <GraphQueryExecutionContext> next, CancellationToken cancelToken) { if (context.IsValid && context.QueryOperation != null) { var anyFieldFailed = await this.AuthorizeOperation(context, cancelToken).ConfigureAwait(false); if (anyFieldFailed) { context.Cancel(); } } await next(context, cancelToken).ConfigureAwait(false); }
/// <summary> /// Invokes this middleware component allowing it to perform its work against the supplied context. /// </summary> /// <param name="context">The context containing the request passed through the pipeline.</param> /// <param name="next">The delegate pointing to the next piece of middleware to be invoked.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> public Task InvokeAsync(GraphQueryExecutionContext context, GraphMiddlewareInvocationDelegate <GraphQueryExecutionContext> next, CancellationToken cancelToken) { if (context.IsValid && context.QueryPlan != null && context.QueryPlan.IsValid) { context.QueryOperation = context.QueryPlan.RetrieveOperation(context.Request.OperationName); if (context.QueryOperation == null) { context.Messages.Critical( $"No operation found with the name '{context.Request.OperationName}'.", Constants.ErrorCodes.BAD_REQUEST); } } return(next(context, cancelToken)); }
public async Task ValidateRequestMiddleware_EmptyQueryText_YieldsCriticalMessage() { var component = new ValidateQueryRequestMiddleware(); var req = new Mock <IGraphOperationRequest>(); req.Setup(x => x.QueryText).Returns(null as string); var context = new GraphQueryExecutionContext( req.Object, new Mock <IServiceProvider>().Object); await component.InvokeAsync(context, EmptyNextDelegate, default); Assert.AreEqual(1, context.Messages.Count); Assert.AreEqual(GraphMessageSeverity.Critical, context.Messages[0].Severity); Assert.AreEqual(Constants.ErrorCodes.EXECUTION_ERROR, context.Messages[0].Code); }
/// <summary> /// Executes the query. /// </summary> /// <param name="queryText">The query text.</param> /// <param name="jsonText">The json text.</param> /// <returns>Task.</returns> private async Task ExecuteQuery(string queryText, string jsonText = "{}") { // top level services to execute the query var queryPipeline = _serviceProvider.GetService <ISchemaPipeline <GraphSchema, GraphQueryExecutionContext> >(); var writer = _serviceProvider.GetService <IGraphResponseWriter <GraphSchema> >(); // parse the json doc, simulating a request recieved to the QueryController var inputVars = InputVariableCollection.FromJsonDocument(jsonText); var query = new GraphQueryData() { Query = queryText, Variables = inputVars ?? InputVariableCollection.Empty, }; var request = new GraphOperationRequest(query); var context = new GraphQueryExecutionContext(request, _serviceProvider, null); // execute await queryPipeline.InvokeAsync(context, default); var response = context.Result; if (response.Messages.Count > 0) { throw new InvalidOperationException("Query failed: " + response.Messages[0].Message); } // simulate writing hte result to an output stream string result = null; using (var memStream = new MemoryStream()) { await writer.WriteAsync(memStream, response); memStream.Seek(0, SeekOrigin.Begin); using (var streamReader = new StreamReader(memStream)) result = streamReader.ReadToEnd(); } if (result == null) { throw new InvalidOperationException("Query failed: No Response was serialized"); } }
/// <summary> /// Invokes this middleware component allowing it to perform its work against the supplied context. /// </summary> /// <param name="context">The context containing the request passed through the pipeline.</param> /// <param name="next">The delegate pointing to the next piece of middleware to be invoked.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> public async Task InvokeAsync(GraphQueryExecutionContext context, GraphMiddlewareInvocationDelegate <GraphQueryExecutionContext> next, CancellationToken cancelToken) { if (context.IsValid && context.QueryPlan == null && context.SyntaxTree != null) { context.Metrics?.StartPhase(ApolloExecutionPhase.VALIDATION); context.QueryPlan = await _planGenerator.CreatePlan(context.SyntaxTree).ConfigureAwait(false); context.Messages.AddRange(context.QueryPlan.Messages); context.Metrics?.EndPhase(ApolloExecutionPhase.VALIDATION); if (context.QueryPlan.IsValid) { context.Logger?.QueryPlanGenerated(context.QueryPlan); } } await next(context, cancelToken).ConfigureAwait(false); }
/// <summary> /// Invokes this middleware component allowing it to perform its work against the supplied context. /// </summary> /// <param name="context">The context containing the request passed through the pipeline.</param> /// <param name="next">The delegate pointing to the next piece of middleware to be invoked.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> public Task InvokeAsync(GraphQueryExecutionContext context, GraphMiddlewareInvocationDelegate <GraphQueryExecutionContext> next, CancellationToken cancelToken) { if (context is SubcriptionExecutionContext subContext && subContext.IsSubscriptionOperation && subContext.QueryPlan != null && subContext.QueryOperation != null) { subContext.Subscription = new ClientSubscription <TSchema>( subContext.Client, subContext.Request.ToDataPackage(), subContext.QueryPlan, subContext.QueryOperation, subContext.SubscriptionId); return(Task.CompletedTask); } return(next(context, cancelToken)); }
/// <inheritdoc /> public Task <IGraphOperationResult> ExecuteRequest( IServiceProvider serviceProvider, ClaimsPrincipal user, IGraphOperationRequest request, IGraphQueryExecutionMetrics metricsPackage = null, CancellationToken cancelToken = default) { Validation.ThrowIfNull(serviceProvider, nameof(serviceProvider)); Validation.ThrowIfNull(user, nameof(user)); Validation.ThrowIfNull(request, nameof(request)); var context = new GraphQueryExecutionContext( request, serviceProvider, user, metricsPackage, _logger); return(this.ExecuteRequest(context, cancelToken)); }
/// <summary> /// Creates this query context instance that can be executed against the test server. /// </summary> /// <returns>GraphQueryContext.</returns> public virtual GraphQueryExecutionContext Build() { var metaData = new MetaDataCollection(); // unchangable items about the request var request = new Mock <IGraphOperationRequest>(); // updateable items about the request var context = new GraphQueryExecutionContext(this.OperationRequest, _serviceProvider, _user, _metrics, _eventLogger, metaData); foreach (var kvp in _sourceData) { var mockField = new Mock <IGraphField>(); mockField.Setup(x => x.FieldSource).Returns(Internal.TypeTemplates.GraphFieldTemplateSource.Action); mockField.Setup(x => x.Route).Returns(kvp.Key); context.DefaultFieldSources.AddSource(mockField.Object, kvp.Value); } return(context); }
/// <summary> /// Convert the data items that were generated pushing their results into a final top level dictionary /// to be returned as the graph projection. Takes care of an final messaging in case one of the tasks failed. /// </summary> /// <param name="context">The context.</param> /// <returns>GraphQL.AspNet.Interfaces.Response.IResponseFieldSet.</returns> private IResponseFieldSet CreateFinalDictionary(GraphQueryExecutionContext context) { var topFieldResponses = new ResponseFieldSet(); foreach (var fieldResult in context.FieldResults) { var generated = fieldResult.GenerateResult(out var result); if (generated) { topFieldResponses.Add(fieldResult.Name, result); } } if (topFieldResponses.Fields.Count == 0 || topFieldResponses.Fields.All(x => x.Value == null)) { topFieldResponses = null; } return(topFieldResponses); }
/// <summary> /// Invokes this middleware component allowing it to perform its work against the supplied context. /// </summary> /// <param name="context">The context containing the request passed through the pipeline.</param> /// <param name="next">The delegate pointing to the next piece of middleware to be invoked.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> public Task InvokeAsync( GraphQueryExecutionContext context, GraphMiddlewareInvocationDelegate <GraphQueryExecutionContext> next, CancellationToken cancelToken) { if (context?.Items != null && context.IsValid && !context.IsCancelled) { // if a context item for the subscription event key was added by one of the extension methods // inspect it to try and find the events that were registered if (context.Items.ContainsKey(SubscriptionConstants.RAISED_EVENTS_COLLECTION_KEY)) { var collection = context.Items[SubscriptionConstants.RAISED_EVENTS_COLLECTION_KEY] as IList <SubscriptionEventProxy>; if (collection == null) { throw new GraphExecutionException( $"Unable to cast the context item '{SubscriptionConstants.RAISED_EVENTS_COLLECTION_KEY}' into " + $"{typeof(IList<SubscriptionEventProxy>).FriendlyName()}. Published subscription events could not be raised.", SourceOrigin.None); } else { foreach (var proxy in collection) { var eventData = new SubscriptionEvent() { Id = Guid.NewGuid().ToString(), SchemaTypeName = SchemaExtensions.RetrieveFullyQualifiedSchemaTypeName(typeof(TSchema)), Data = proxy.DataObject, DataTypeName = SchemaExtensions.RetrieveFullyQualifiedDataObjectTypeName(proxy.DataObject?.GetType()), EventName = proxy.EventName?.Trim(), }; _eventQueue.Enqueue(eventData); } } } } return(next(context, cancelToken)); }
/// <summary> /// Invokes this middleware component allowing it to perform its work against the supplied context. /// </summary> /// <param name="context">The context containing the request passed through the pipeline.</param> /// <param name="next">The delegate pointing to the next piece of middleware to be invoked.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns>Task.</returns> public Task InvokeAsync(GraphQueryExecutionContext context, GraphMiddlewareInvocationDelegate <GraphQueryExecutionContext> next, CancellationToken cancelToken) { if (context?.ServiceProvider == null) { throw new GraphExecutionException( "No context and/or service provider was supplied on which to process the request", SourceOrigin.None, new InvalidOperationException($"The {nameof(GraphQueryExecutionContext)} governing the execution of the pipeline was provided as null. Operation failed.")); } if (context.Request == null || string.IsNullOrWhiteSpace(context.Request.QueryText)) { // capture execution exceptions, they will relate to the internal processing // of the server and should only be exposed to authorized parties (via exception details) context.Messages.Critical("No query text was provided.", Constants.ErrorCodes.EXECUTION_ERROR); } else { context.Logger?.RequestReceived(context); } return(next(context, cancelToken)); }
/// <summary> /// Iterates over every secure field in the operation on the context, attempting to authorize the /// user to each one. /// </summary> /// <param name="context">The primary query context.</param> /// <param name="cancelToken">The cancel token.</param> /// <returns><c>true</c> if authorization was successful, otherwise false.</returns> private async Task <bool> AuthorizeOperation(GraphQueryExecutionContext context, CancellationToken cancelToken) { var authTasks = new List <Task>(); bool anyFieldFailed = false; foreach (var fieldContext in context.QueryOperation.SecureFieldContexts) { var authRequest = new GraphFieldAuthorizationRequest(fieldContext); var authContext = new GraphFieldAuthorizationContext(context, authRequest); var pipelineTask = _authPipeline.InvokeAsync(authContext, cancelToken) .ContinueWith( (_) => { var authResult = authContext.Result ?? FieldAuthorizationResult.Default(); // fake the path elements from the field route. since we don't have a full resolution chain // when doing query level authorization (no indexers on potential child fields since // nothing is actually resolved yet) if (!authResult.Status.IsAuthorized()) { context.Messages.Critical( $"Access Denied to field {fieldContext.Field.Route.Path}", Constants.ErrorCodes.ACCESS_DENIED, fieldContext.Origin); anyFieldFailed = true; } }, cancelToken); authTasks.Add(pipelineTask); } await Task.WhenAll(authTasks).ConfigureAwait(false); return(anyFieldFailed); }
/// <inheritdoc /> public async Task <IGraphOperationResult> ExecuteRequest( GraphQueryExecutionContext context, CancellationToken cancelToken = default) { Validation.ThrowIfNull(context, nameof(context)); // ******************************* // Primary query execution // ******************************* await _pipeline.InvokeAsync(context, cancelToken).ConfigureAwait(false); // ******************************* // Response Generation // ******************************* var queryResponse = context.Result; if (queryResponse == null) { queryResponse = new GraphOperationResult(context.Request); queryResponse.Messages.Add(GraphMessageSeverity.Critical, ERROR_NO_RESPONSE, Constants.ErrorCodes.GENERAL_ERROR); } return(queryResponse); }
/// <summary> /// Submits the GraphQL query for processing. /// </summary> /// <param name="queryData">The query data.</param> /// <returns>Task<IActionResult>.</returns> public virtual async Task SubmitGraphQLQuery(GraphQueryData queryData) { // ensure data was received if (queryData == null || string.IsNullOrWhiteSpace(queryData.Query)) { await this.WriteStatusCodeResponse(HttpStatusCode.BadRequest, ERROR_NO_QUERY_PROVIDED).ConfigureAwait(false); return; } using (var cancelSource = new CancellationTokenSource()) { try { // ******************************* // Setup // ******************************* this.GraphQLRequest = this.CreateRequest(queryData); if (this.GraphQLRequest == null) { await this.WriteStatusCodeResponse(HttpStatusCode.InternalServerError, ERROR_NO_REQUEST_CREATED).ConfigureAwait(false); return; } // ******************************* // Primary query execution // ******************************* var metricPackage = this.EnableMetrics ? _metricsFactory.CreateMetricsPackage() : null; var context = new GraphQueryExecutionContext( this.GraphQLRequest, this.HttpContext.RequestServices, this.HttpContext.User, metricPackage, _logger); await _queryPipeline.InvokeAsync(context, cancelSource.Token).ConfigureAwait(false); // ******************************* // Response Generation // ******************************* var queryResponse = context.Result; if (queryResponse == null) { queryResponse = this.ErrorMessageAsGraphQLResponse(ERROR_NO_RESPONSE); } // if any metrics were populated in the execution, allow a child class to process them if (context.Metrics != null) { this.HandleQueryMetrics(context.Metrics); } // all done, finalize and return queryResponse = this.FinalizeResult(queryResponse); await this.WriteResponse(queryResponse).ConfigureAwait(false); } catch (Exception ex) { var exceptionResult = this.HandleQueryException(ex); if (exceptionResult == null) { // no one was able to handle hte exception. Log it if able and just fail out to the caller _logger?.UnhandledExceptionEvent(ex); await this.WriteStatusCodeResponse(HttpStatusCode.InternalServerError, ERROR_INTERNAL_SERVER_ISSUE).ConfigureAwait(false); } else { await this.WriteResponse(exceptionResult).ConfigureAwait(false); } } } }
/// <summary> /// Renders the provided operation request through the engine and generates a JSON string output. /// </summary> /// <param name="context">The query context to render out to a json string.</param> /// <returns>Task<System.String>.</returns> public async Task <string> RenderResult(GraphQueryExecutionContext context) { await this.ExecuteQuery(context).ConfigureAwait(false); return(await this.RenderResult(context.Result).ConfigureAwait(false)); }
/// <summary> /// Executes the provided query context through the engine. This method executes the parsing and execution phases only. /// </summary> /// <param name="context">The context.</param> /// <returns>Task<System.String>.</returns> public async Task ExecuteQuery(GraphQueryExecutionContext context) { var pipeline = this.ServiceProvider.GetService <ISchemaPipeline <TSchema, GraphQueryExecutionContext> >(); await pipeline.InvokeAsync(context, default).ConfigureAwait(false); }
private async Task ExecuteOperation(GraphQueryExecutionContext context) { // create a cancelation sourc irrespective of the required timeout for exeucting this operation // this allows for indication of why a task was canceled (timeout or other user driven reason) // vs just "it was canceled" which allows for tighter error messages in the response. var operation = context.QueryOperation; var fieldInvocations = new List <FieldPipelineInvocation>(); // Convert the supplied variable values to usable objects of the type expression // of the chosen operation var variableResolver = new ResolvedVariableGenerator(_schema, operation); var variableData = variableResolver.Resolve(context.Request.VariableData); var cancelSource = new CancellationTokenSource(); try { // begin a field execution pipeline for each top level field foreach (var invocationContext in operation.FieldContexts) { var path = new SourcePath(); path.AddFieldName(invocationContext.Name); object dataSourceValue; // fetch the source data value to use for the field invocation // attempt to retrieve from the master context if it was supplied by the pipeline // invoker, otherwise generate a root source if (!context.DefaultFieldSources.TryRetrieveSource(invocationContext.Field, out dataSourceValue)) { dataSourceValue = this.GenerateRootSourceData(operation.OperationType); } var topLevelDataItem = new GraphDataItem(invocationContext, dataSourceValue, path); var sourceData = new GraphFieldDataSource(dataSourceValue, path, topLevelDataItem); var fieldRequest = new GraphFieldRequest( invocationContext, sourceData, new SourceOrigin(invocationContext.Origin.Location, path), context.Items); var fieldContext = new GraphFieldExecutionContext( context, fieldRequest, variableData, context.DefaultFieldSources); var fieldTask = _fieldExecutionPipeline.InvokeAsync(fieldContext, cancelSource.Token); var pipelineInvocation = new FieldPipelineInvocation() { Task = fieldTask, DataItem = topLevelDataItem, FieldContext = fieldContext, }; fieldInvocations.Add(pipelineInvocation); // top level mutation operatons must be executed in sequential order // https://graphql.github.io/graphql-spec/June2018/#sec-Mutation if (_awaitEachTask || operation.OperationType == GraphCollection.Mutation) { await fieldTask.ConfigureAwait(false); } } // await all the outstanding tasks or a configured timeout var fieldPipelineTasksWrapper = Task.WhenAll(fieldInvocations.Select(x => x.Task)); var timeOutTask = Task.Delay(_timeoutMs, cancelSource.Token); var completedTask = await Task.WhenAny(fieldPipelineTasksWrapper, timeOutTask).ConfigureAwait(false); var isTimedOut = completedTask == timeOutTask; var cancelationWasRequested = cancelSource.IsCancellationRequested; if (!isTimedOut) { // Field resolutions completed within the timeout period. // Consider that the task may have faulted or been canceled causing them to complete incorrectly. // "re-await" so that any exceptions/cancellation are rethrown correctly. // and not aggregated under the `WhenAll/WhenAny` task from above // https://stackoverflow.com/questions/4238345/asynchronously-wait-for-taskt-to-complete-with-timeout foreach (var invocation in fieldInvocations) { await invocation.Task.ConfigureAwait(false); // load the reslts of each field (in order) to the context // for further processing context.FieldResults.Add(invocation.DataItem); context.Messages.AddRange(invocation.FieldContext.Messages); } } else { // when the timeout finishes first, process the cancel token in case any outstanding tasks are running // helps in cases where the timeout finished first but any of the field resolutions are perhaps stuck open // instruct all outstanding tasks to clean them selves up at the earlest possible point if (!cancelationWasRequested) { cancelSource.Cancel(); } } if (cancelationWasRequested) { context.Messages.Critical("The execution was canceled prior to completion of the requested query.", Constants.ErrorCodes.OPERATION_CANCELED); } else if (isTimedOut) { context.Messages.Critical($"The execution timed out prior to completion of the requested query. (Total Time: {_timeoutMs}ms)", Constants.ErrorCodes.OPERATION_CANCELED); } } finally { cancelSource.Dispose(); } }
/// <summary> /// Instructs the client to process the new event. If this is an event the client subscribes /// to it should process the data appropriately and send down any data to its underlying connection /// as necessary. /// </summary> /// <param name="field">The unique field corrisponding to the event that was raised /// by the publisher.</param> /// <param name="sourceData">The source data sent from the publisher when the event was raised.</param> /// <param name="cancelToken">A cancellation token.</param> /// <returns>Task.</returns> public async Task ReceiveEvent(GraphFieldPath field, object sourceData, CancellationToken cancelToken = default) { await Task.Yield(); if (field == null) { return; } var subscriptions = _subscriptions.RetreiveByRoute(field); _logger?.SubscriptionEventReceived(field, subscriptions); if (subscriptions.Count == 0) { return; } var runtime = this.ServiceProvider.GetRequiredService <IGraphQLRuntime <TSchema> >(); var schema = this.ServiceProvider.GetRequiredService <TSchema>(); var tasks = new List <Task>(); foreach (var subscription in subscriptions) { IGraphQueryExecutionMetrics metricsPackage = null; IGraphEventLogger logger = this.ServiceProvider.GetService <IGraphEventLogger>(); if (schema.Configuration.ExecutionOptions.EnableMetrics) { var factory = this.ServiceProvider.GetRequiredService <IGraphQueryExecutionMetricsFactory <TSchema> >(); metricsPackage = factory.CreateMetricsPackage(); } var context = new GraphQueryExecutionContext( runtime.CreateRequest(subscription.QueryData), this.ServiceProvider, this.User, metricsPackage, logger); // register the event data as a source input for the target subscription field context.DefaultFieldSources.AddSource(subscription.Field, sourceData); context.QueryPlan = subscription.QueryPlan; context.QueryOperation = subscription.QueryOperation; tasks.Add(runtime.ExecuteRequest(context, cancelToken) .ContinueWith( task => { if (task.IsFaulted) { return(task); } // send the message with the resultant data package var message = new ApolloServerDataMessage(subscription.Id, task.Result); return(this.SendMessage(message)); }, cancelToken)); } await Task.WhenAll(tasks).ConfigureAwait(false); }
public Task EmptyNextDelegate(GraphQueryExecutionContext context, CancellationToken token) { return(Task.CompletedTask); }