/// <summary> /// Attempts to find and remove a subscription with the given client id on the message for the target subscription. /// </summary> /// <param name="message">The message containing the subscription id to stop.</param> /// <returns>Task.</returns> private async Task ExecuteStopRequest(ApolloClientStopMessage message) { var totalRemaining = _subscriptions.Remove(message.Id, out var subFound); if (subFound != null) { _reservedMessageIds.ReleaseMessageId(subFound.Id); if (totalRemaining == 0) { this.SubscriptionRouteRemoved?.Invoke(this, new ApolloSubscriptionFieldEventArgs(subFound.Field)); } _logger?.SubscriptionStopped(subFound); await this .SendMessage(new ApolloServerCompleteMessage(subFound.Id)) .ConfigureAwait(false); } else { var errorMessage = new ApolloServerErrorMessage( $"No active subscription exists with id '{message.Id}'", Constants.ErrorCodes.BAD_REQUEST, lastMessage: message, clientProvidedId: message.Id); await this .SendMessage(errorMessage) .ConfigureAwait(false); } }
public void ErrorMessage_WithId_SerializesWithIdParameter() { var dt = DateTime.UtcNow; var server = new TestServerBuilder() .AddGraphController <ApolloDataMessageController>() .AddSubscriptionServer() .AddGraphQL(options => { options.ResponseOptions.TimeStampLocalizer = (time) => dt; }) .Build(); var converter = new ApolloServerErrorMessageConverter(server.Schema); var message = new ApolloServerErrorMessage( "an error occured", Constants.ErrorCodes.BAD_REQUEST, GraphMessageSeverity.Warning, "prev123", lastMessageType: ApolloMessageType.START, clientProvidedId: "abc123"); var options = new JsonSerializerOptions(); options.Converters.Add(converter); var response = JsonSerializer.Serialize(message, message.GetType(), options); // error message should render a single IGraphMessage // that is normally part of the errors collection on a standard response var expected = @" { ""id"": ""abc123"", ""type"":""error"", ""payload"":{ ""message"":""an error occured"", ""extensions"":{ ""code"":""BAD_REQUEST"", ""timestamp"":""{dateString}"", ""severity"":""WARNING"", ""metaData"":{ ""lastMessage_id"":""prev123"", ""lastMessage_type"":""start"" } } } }"; expected = expected.Replace("{dateString}", dt.ToRfc3339String()); CommonAssertions.AreEqualJsonStrings(expected, response); }
/// <summary> /// Returns a generic error to the client indicating that the last message recieved was unknown or invalid. /// </summary> /// <param name="lastMessage">The last message that was received that was unprocessable.</param> /// <returns>Task.</returns> private Task UnknownMessageRecieved(ApolloMessage lastMessage) { var apolloError = new ApolloServerErrorMessage( "The last message recieved was unknown or could not be processed " + "by this server. This graph ql is configured to use Apollo's GraphQL over websockets " + "message schema.", Constants.ErrorCodes.BAD_REQUEST, lastMessage: lastMessage, clientProvidedId: lastMessage.Id); apolloError.Payload.MetaData.Add( Constants.Messaging.REFERENCE_RULE_NUMBER, "Unknown Message Type"); apolloError.Payload.MetaData.Add( Constants.Messaging.REFERENCE_RULE_URL, "https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md"); return(this.SendMessage(apolloError)); }
/// <summary> /// Parses the message contents to generate a valid client subscription and adds it to the watched /// set for this instance. /// </summary> /// <param name="message">The message with the subscription details.</param> private async Task ExecuteStartRequest(ApolloClientStartMessage message) { // ensure the id isnt already in use if (!_reservedMessageIds.ReserveMessageId(message.Id)) { await this .SendMessage(new ApolloServerErrorMessage( $"The message id {message.Id} is already reserved for an outstanding request and cannot " + "be processed against. Allow the in-progress request to complete or stop the associated subscription.", SubscriptionConstants.ErrorCodes.DUPLICATE_MESSAGE_ID, lastMessage : message, clientProvidedId : message.Id)) .ConfigureAwait(false); return; } var retainMessageId = false; var runtime = this.ServiceProvider.GetRequiredService(typeof(IGraphQLRuntime <TSchema>)) as IGraphQLRuntime <TSchema>; var request = runtime.CreateRequest(message.Payload); var metricsPackage = _enableMetrics ? runtime.CreateMetricsPackage() : null; var context = new SubcriptionExecutionContext( this, request, message.Id, metricsPackage); var result = await runtime .ExecuteRequest(context) .ConfigureAwait(false); if (context.IsSubscriptionOperation) { retainMessageId = await this .RegisterSubscriptionOrRespond(context.Subscription as ISubscription <TSchema>) .ConfigureAwait(false); } else { // not a subscription, just send back the generated response and close out the id ApolloMessage responseMessage; // report syntax errors as error messages // allow others to bubble into a fully reslt (per apollo spec) if (result.Messages.Count == 1 && result.Messages[0].Code == Constants.ErrorCodes.SYNTAX_ERROR) { responseMessage = new ApolloServerErrorMessage( result.Messages[0], message, message.Id); } else { responseMessage = new ApolloServerDataMessage(message.Id, result); } await this .SendMessage(responseMessage) .ConfigureAwait(false); await this .SendMessage(new ApolloServerCompleteMessage(message.Id)) .ConfigureAwait(false); } if (!retainMessageId) { _reservedMessageIds.ReleaseMessageId(message.Id); } }