Skip to content

Could not connect with AWS graphQL client (Appsync APIs) #243

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

Open
JackPriyan opened this issue May 29, 2020 · 14 comments
Open

Could not connect with AWS graphQL client (Appsync APIs) #243

JackPriyan opened this issue May 29, 2020 · 14 comments

Comments

@JackPriyan
Copy link

JackPriyan commented May 29, 2020

I am trying to implement the same example given below in python with .net and this grahQL-client nuget but I am always getting error that the "server closed the connection without a closing handshake".

https://aws.amazon.com/blogs/mobile/appsync-websockets-python/

The code in above sample is working good in python.

could any please help with this. I am trying to fix from last two week. Not sure its something inside the library or I am not able to use the library correctly.

My code looks like

var header = System.Convert.ToBase64String(Encoding.UTF8.GetBytes("{'host': '--HOST Name here--', 'x-api-key': '--APISYNC KEY HERE--'}"));
var gQL = new GraphQLHttpClient("--WEBSOCKET URL HERE--", new NewtonsoftJsonSerializer());
gQL.HttpClient.DefaultRequestHeaders.Add("header", header);

var request = new GraphQLHttpRequest{Query = "subscription SubscribeToEventComments{ subscribeToEventComments(eventId: 'test'){  content }}",OperationName = "SubscribeToEventComments", Variables = new{}};
IObservable <GraphQLResponse<string>> subscriptionStream = gQL.CreateSubscriptionStream<string>(request, (Exception ex)=>{
Console.WriteLine(ex.ToString());
});
var subscription = subscriptionStream.Subscribe(response =>
{
Console.WriteLine($"user '{Newtonsoft.Json.JsonConvert.SerializeObject(response)}' joined");
},
ex=>{
Console.WriteLine(ex.ToString());
 });
@dariusjs
Copy link

I managed to get it working with Appsync but only for SendQueryAsync calls. I've also not had much luck yet in getting the websocket connection for realtime subscriptions.

@symposiumjim
Copy link

Anyone figure this out? I too can query without an issue but get the following error when trying to connect with websockets.

The server returned status code '401' when status code '101' was expected.

@rose-a
Copy link
Collaborator

rose-a commented Sep 15, 2020

AppSync seems to use a different endpoint for websocket connections, see here.

You need to create a second instance of GraphQLHttpClient and point that to the correct endpoint. Please upgrade to V3.1.7, previous versions don't handle the wss:// scheme correctly.

@symposiumjim
Copy link

symposiumjim commented Sep 18, 2020

Sorry, I am trying to use the client in csharp and changing the url to wss does not work. I do this;

clientWebsockets = new GraphQLHttpClient("wss://host/graphql", new NewtonsoftJsonSerializer());

clientWebsockets.HttpClient.DefaultRequestHeaders.Add("x-api-key", clientApiKey);
clientWebsockets.HttpClient.DefaultRequestHeaders.Add("host", host);

var onUpdateNotificationReq = new GraphQLRequest
                {
                    Query = @"
                    subscription {
                        onUpdateNotification {
                            id
                            name
                            action
                        }
                    }"
                };

                IObservable<GraphQLResponse<UserJoinedSubscriptionResult>> subOnUpdateNotificationStream = clientWebsockets.CreateSubscriptionStream<NotificationOnUpdateSubscriptionResult>(onUpdateNotificationReq, (ex) =>
                {
// ERROR HERE
                    Log("EX: " + ex.Message + Environment.NewLine);
                });

                subOnUpdateNotification = subOnUpdateNotificationStream.Subscribe(response =>
                {
                    Log("NEVER SHOWS UP");
                });

end up with same error - The server returned status code '401' when status code '101' was expected.

@rose-a
Copy link
Collaborator

rose-a commented Sep 19, 2020

The header must be sent as url query parameter as described here: https://docs.aws.amazon.com/appsync/latest/devguide/real-time-websocket-client.html.

The example there is

wss://example1234567890000.appsync-realtime-api.us-east-1.amazonaws.com/graphql?header=eyJob3N0IjoiZXhhbXBsZTEyMzQ1Njc4OTAwMDAuYXBwc3luYy1hcGkudXMtZWFzdC0xLmFtYXpvbmF3cy5jb20iLCJ4LWFtei1kYXRlIjoiMjAyMDA0MDFUMDAxMDEwWiIsIngtYXBpLWtleSI6ImRhMi16NHc0NHZoczV6Z2MzZHRqNXNranJsbGxqaSJ9&payload=e30=

@symposiumjim
Copy link

symposiumjim commented Sep 20, 2020

Are you telling me that I need to create my own websocket requests and cannot use your system for subscriptions? I do not get the error anymore with the below code, but I never see any data.

var headersWs = new Dictionary<string, string>() {
                    { "host", clientHost },
                    { "x-api-key", clientApiKey }
                };

                var sHeadersWs = JsonConvert.SerializeObject(headersWs);
                var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(sHeadersWs);
                var x = System.Convert.ToBase64String(plainTextBytes);
                var uri = clientEndpointWs + "?header=" + x + "&payload=e30=";

                clientWs = new GraphQLHttpClient(uri, new NewtonsoftJsonSerializer());

                await clientWs.InitializeWebsocketConnection();

                var req = new GraphQLHttpRequest
                {
                    Query = @"
                    subscription OnUpdateNotification {
                        onUpdateNotification {
                          id
                          name
                          action
                          createdAt
                          updatedAt
                        }
                      }",
                    OperationName = "OnUpdateNotification",
                };

                subscriptionStream = clientWs.CreateSubscriptionStream<NotificationOnUpdateSubscriptionResult>(req);

                sub = subscriptionStream.Subscribe(response =>
                {
                    Log("Here");
                    Log(response.Data.onUpdateNotification.Name);
                });

@rose-a
Copy link
Collaborator

rose-a commented Sep 21, 2020

Nope, I didn't tell you anything like that.

Please post your NotificationOnUpdateSubscriptionResult class and the JSON payload you're expecting from your endpoint.

@symposiumjim
Copy link

symposiumjim commented Sep 21, 2020

public class NotificationType
        {
            public string Id { get; set; }
            public string Name { get; set; }
            public string Action { get; set; }
        }

public class NotificationOnUpdateSubscriptionResult
        {
            public NotificationType onUpdateNotification { get; set; }
        }

sub = subscriptionStream.Subscribe(response =>
                {
                    Log("Here");
                    Log(response.Data.onUpdateNotification.Name);
                }, error =>
                {
                    Log("ERROR");
                    Log(error.Message);
                });

I never get a response, not even an error. Note that when I used an incorrect URI I would get the 401 error so I think the URI is not connecting, but not sure as there are no messages.

Thanks for all your help so far!

@rose-a
Copy link
Collaborator

rose-a commented Sep 22, 2020

Looks ok to me. Did you try to create a subscription using a different tool (like Altair)?

Does this work? Please post the subscription query string and response from there...

@symposiumjim
Copy link

I tested with Altair and do not receive any error message nor any data. Thank you for your help but it seems connecting to AppSync is not something via C# is not going to work so I am moving to another code base

@rose-a
Copy link
Collaborator

rose-a commented Sep 22, 2020

Altair is a JavaScript app... I'm sorry, but it seems something else is wrong there. But go ahead.

@bjorg
Copy link
Contributor

bjorg commented Sep 29, 2020

There are a couple of showstopper issues. I have fixes for them locally but have not yet polished them for a pull-request.

Issue 1 - Set explicit endpoint for WebSocket

The GraphQLHttpClientOptions class needs a WebSocketEndPoint property to explicitly set the WebSocket endpoint for AppSync as it's different from the REST endpoint. There is some additional scaffolding required, but it doesn't belong in GraphQL.Client. Specifically, the WebSocket URI needs to be passed in with query parameters as follows:

var header = new AppSyncHeader {
    Host = "abcedef.appsync-api.us-west-2.amazonaws.com",
    ApiKey = "abcdef"
};

_graphQlClient.Options.WebSocketEndPoint = new Uri($"wss://abcedef.appsync-realtime-api.us-west-2.amazonaws.com/graphql"
    + $"?header={Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(header)))}"
    + "&payload=e30="
);

With AppSyncHeader declared as follows:

class AppSyncHeader {

    //--- Properties ---

    [JsonPropertyName("host")]
    public string Host { get; set; }

    [JsonPropertyName("x-api-key")]
    public string ApiKey { get; set; }
}

Issue 2 - NullReferenceException in GraphQLRequest.GetHashCode()

The GraphQLRequest class implementation expects to always have the Query property set, but the AppSync subscription request looks like this:

{
  "data": {
    "query": "subscription { ... }"
  },
  "extensions": {
    "authoriziation": {
      "Host": "...",
      "ApiKey": "..."
    }
  }
}

A simple fix is to allow Query to be null. However, I think the hash code should be computed over the entire dictionary instead.

public override int GetHashCode()
{
    unchecked
    {
        var hashCode = Query?.GetHashCode() ?? 0;
        hashCode = (hashCode * 397) ^ OperationName?.GetHashCode() ?? 0;
        hashCode = (hashCode * 397) ^ Variables?.GetHashCode() ?? 0;
        return hashCode;
    }
}

Issue 3 - Helper class for authorized AppSync requests

This is not a blocking issue, just a complication. The request needs to be pre-processed to add the correct authorization header.

_graphQlClient.Options.PreprocessRequest = (request, client) =>
    Task.FromResult((GraphQLHttpRequest)new AuthorizedAppSyncHttpRequest(request, _header.ApiKey));

The AuthorizedAppSyncHttpRequest is declared as follows:

class AuthorizedAppSyncHttpRequest : GraphQLHttpRequest {

    //--- Fields ---
    private readonly string _authorization;

    //--- Constructors ---
    public AuthorizedAppSyncHttpRequest(GraphQLRequest request, string authorization) : base(request)
        => _authorization = authorization;

    //--- Methods ---
    public override HttpRequestMessage ToHttpRequestMessage(GraphQLHttpClientOptions options, Client.Abstractions.IGraphQLJsonSerializer serializer) {
        var result = base.ToHttpRequestMessage(options, serializer);
        result.Headers.Add("X-Api-Key", _authorization);
        return result;
    }
}

Disclaimer

For now, I have only used the API key for authentication. However, API keys are really just for development as they expire after a week. I have not yet checked what is needed to support a Cognito authentication flow.

Question

@rose-a Do you have a recommendation for how to add the declarations for AppSyncHeader and AuthorizedAppSyncHttpRequest? Maybe as a sibling assembly, such as GraphQL.Client.AppSync?

@bjorg
Copy link
Contributor

bjorg commented Sep 29, 2020

I spoke a bit too soon. Some more changes were needed. I opened a draft pull-request: #287

I also created a sample Blazor WebAssembly app to show how it works: https://github.com/bjorg/GraphQlAppSyncTest/blob/main/MyApp/Pages/Index.razor

@estebandlp
Copy link

estebandlp commented Aug 1, 2024

I tested with Altair and do not receive any error message nor any data. Thank you for your help but it seems connecting to AppSync is not something via C# is not going to work so I am moving to another code base

Hello @symposiumjim !
I'm suffering the same problem. How did you solve it? It would be super helpful for me if you could share the solution.

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants