Exemple #1
0
        /// <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));
        }
Exemple #2
0
        /// <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();
        }
Exemple #3
0
 /// <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;
 }
Exemple #4
0
 /// <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;
 }
Exemple #5
0
        /// <inheritdoc />
        public virtual void RequestCompleted(GraphQueryExecutionContext queryContext)
        {
            if (!this.IsEnabled(LogLevel.Debug))
            {
                return;
            }

            var entry = new RequestCompletedLogEntry(queryContext);

            this.LogEvent(LogLevel.Trace, entry);
        }
Exemple #6
0
        /// <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);
        }
Exemple #7
0
        /// <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);
                }
            }
        }
Exemple #9
0
        /// <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);
        }
Exemple #10
0
        /// <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");
            }
        }
Exemple #13
0
        /// <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);
        }
Exemple #14
0
        /// <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));
        }
Exemple #16
0
        /// <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);
        }
Exemple #17
0
        /// <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));
        }
Exemple #20
0
        /// <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);
        }
Exemple #22
0
        /// <summary>
        /// Submits the GraphQL query for processing.
        /// </summary>
        /// <param name="queryData">The query data.</param>
        /// <returns>Task&lt;IActionResult&gt;.</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);
                    }
                }
            }
        }
Exemple #23
0
        /// <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&lt;System.String&gt;.</returns>
        public async Task <string> RenderResult(GraphQueryExecutionContext context)
        {
            await this.ExecuteQuery(context).ConfigureAwait(false);

            return(await this.RenderResult(context.Result).ConfigureAwait(false));
        }
Exemple #24
0
        /// <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&lt;System.String&gt;.</returns>
        public async Task ExecuteQuery(GraphQueryExecutionContext context)
        {
            var pipeline = this.ServiceProvider.GetService <ISchemaPipeline <TSchema, GraphQueryExecutionContext> >();

            await pipeline.InvokeAsync(context, default).ConfigureAwait(false);
        }
Exemple #25
0
        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();
            }
        }
Exemple #26
0
        /// <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);
 }