-
Notifications
You must be signed in to change notification settings - Fork 135
GraphQL.Client: add Websocket support for Blazor WebAssembly host #262
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
Comments
I'll update the issue description based on feedback in this discussion. |
Hi @bjorg, thanks for your initiative! I've never worked with Blazor WebAssembly, so I'd be very happy if someone familiar with this environment contributes to this. As of now I'd favor Approach 1, since the whole subscription thing is built around The reactive approach isn't implemented perfectly yet, as you can see in #260 and #161. Perhaps GraphQLHttpWebSocket can be refactored to work properly without explicitly specifying an |
I have never worked with |
@bjorg @rose-a I cannot get the example provided here https://github.com/graphql-dotnet/graphql-client/tree/master/examples/GraphQL.Client.Example to work in a Blazor WebAssembly. The call is made without an error, but the response contains no data. Is my problem related to this thread? I would love to use GraphQL in a Blazor WebAssembly. Thank you, Karl |
@kdawg1406 This issue pertains to using WebSocket with GraphQL, which is required for subscriptions or when enabling If the response is empty it could be a CORS issue, which blocks the browser from reading the response body. Check the returned HTTP headers. |
@bjorg In the latest release all the "EventLoopSchedulers" have been eliminated... Could you please test if there are still threading issues using this lib with WebAssembly? |
@rose-a Oh nice! I will spend some time on it today an report back. |
@kdawg1406 The original issue has been resolved. However, I had to also add the following code to ConnectAsync() because Blazor WASM doesn't support the try
{
_clientWebSocket.Options.ClientCertificates = ((HttpClientHandler)Options.HttpMessageHandler).ClientCertificates;
_clientWebSocket.Options.UseDefaultCredentials = ((HttpClientHandler)Options.HttpMessageHandler).UseDefaultCredentials;
}
catch(PlatformNotSupportedException)
{
// ignore error as Blazor WebAssembly does not support these properties
} Even with these fixes, I can't get it to run, because of an issue in _client.Options.ConfigureWebsocketOptions = options => {
System.Diagnostics.Debug.WriteLine($"setting X-Api-Key header");
options.SetRequestHeader("X-Api-Key", "--my-very-secret-api-key--");
}; |
@bjorg thank you for trying. I'm going to try and use the IHTTPClientFactory in .NET Core and make the calls using this. I'll need to write the code to make the PostAsyc and unpack the results; either data or errors returned. |
Filed an issues about |
Reading on AppSync more, I found out that I misunderstood what they mean by authentication header. Browser websockets don't allow headers to be set on initial connect (see dotnet/runtime#41941 (comment)), instead AppSync expects a |
hm... yet another solution on how to implement authorization on websockets 🙄 I guess this affords beeing able to set the websocket endpoint separately from the HTTP endpoint.... |
One more complication is that the The new implementation for private Uri GetWebSocketUri()
{
string webSocketSchema = Options.EndPoint.Scheme == "https"
? "wss"
: Options.EndPoint.Scheme == "http"
? "ws"
: Options.EndPoint.Scheme;
return new Uri($"{webSocketSchema}://{Options.EndPoint.Host}:{Options.EndPoint.Port}{Options.EndPoint.AbsolutePath}{Options.EndPoint.Query}");
} This allows the initialization of the client to look something like this: var header = new AppSyncHeader {
Host = "example1234567890000.appsync-api.us-west-2.amazonaws.com",
ApiKey = "da2-12345678901234567890123456"
};
var uri = $"wss://example1234567890000.appsync-realtime-api.us-west-2.amazonaws.com/graphql"
+ $"?header={Convert.ToBase64String(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(header)))}"
+ "&payload=e30=";
_graphQlClient = new GraphQLHttpClient(uri, new SystemTextJsonSerializer()); |
Sounds reasonable... |
One convenience change in the Something like this: if ((Options.EndPoint?.Scheme == "wss") || (Options.EndPoint?.Scheme == "ws"))
{
Options.UseWebSocketForQueriesAndMutations = true;
} Thoughts? |
Not sure if that is convenience or a must, because what's the point of specifying a websocket endpoint ( |
Would appreciate some feedback. This is what AppSync expects for subscriptions: https://docs.aws.amazon.com/appsync/latest/devguide/real-time-websocket-client.html#subscription-registration-message I was able to bend it to my will by doing this: _graphQlClient.CreateSubscriptionStream<GetMyModelTypeResponse>(new GraphQLRequest {
["data"] = JsonSerializer.Serialize(new GraphQLRequest {
Query = @"
subscription {
onCreateMyModelType {
id
title
}
}
"
}),
["extensions"] = new {
authorization = _header
}
}).Subscribe(create => _result += "Notification: " + JsonSerializer.Serialize(create) + "\n"); In the above code, {
"host":"example1234567890000.appsync-api.us-east-1.amazonaws.com",
"x-api-key":"da2-12345678901234567890123456"
} Notice how there is no The whole contraption seems to finally work. Except, when the websocket gets a notification with the following payload, my listening code never gets triggered. I'm assuming it's not in the right shape, am I right? If so, what shape does the client expect? |
@bjorg @rose-a I blogged about using GraphQL with Blazor WASM. Seems that Blazor WASM uses its own HttpClient. I wrote a super simple service that makes the GraphQL calls using the native Blazor HttpClient and it worked perfectly. Not sure if this will help or not. Best regards, Karl https://oceanware.wordpress.com/2020/09/08/blazor-wasm-graphql-client/ |
@kdawg1406 Great, I'll check it out! My quest has been to get subscriptions to work over WebSocket with AppSync. It's been a journey of learning! On the positive side, I figured out my mistake from the previous comment. I passed in the wrong type on the subscription! Instead of |
I'd say for |
If I got this spontaneously right I think you need to add a The |
@Rose-e Thanks for the assist! Alas, my mistake was even more basic. Wrong type and forgot to trigger a UI update. Bad combination of mistakes. :) |
This issue is fixed by #274 There are some unrelated issues having to do with using AWS AppSync. I will open a separate ticket for those. |
I would like to help address this issue as I'm a big fan of Blazor WebAssembly and a strong believer in GraphQL as the best API tech. So, supporting
GraphQL.Client
is of paramount interest. If there is already an effort underway to achieve this, please close this issue as a duplicate and point me in the right direction.Problem
I've started looking at the code to better understand where the problem is originating from. At the core of the problem are the limited capabilities of the Blazor WASM runtime. For instance, it's not possible to create threads or even block on a thread with
Task.Wait()
orTask.Result
. All operations must be asynchronous as there is only one main thread in the browser execution environment.The implementation of the
GraphQL.Client.Http.Websocket.GraphQLHttpWebSocket
class uses System.Reactive.Concurency.EventLoopScheduler, which creates a thread internally. This causes theSystem.NotSupportedException: Cannot start threads on this runtime.
error when the websocket connection is opened.Solution
There are 2 approaches to potentially address this problem:
Approach 1 - Blazor WASM-friendly
System.Reactive.Concurrency.IScheduler
implementationImplement a Blazor WASM-friendly version of
System.Reactive.Concurrency.IScheduler
that does not require background threads. It would only work reliably when scheduled operations are asynchronous. However, it would be the least invasive change.Benefits
GraphQLHttpWebSocket
that could be controlled via aGraphQLHttpClientOptions
property to allow current and new implementations to exist side-by-sideDrawback
Approach 2 - Replace
System.Reactive
in favor of a pure asynchronous implementationBenefits
System.Reactive
(less is better)async/await
code pattern enforcement.System.Reactive
works by deferring continuations toTask
-related methodsDrawbacks
GraphQL.Client
exposesIObservable
Additional
As follow up to this issue, there should also be a sample Blazor WASM application to showcase and test the proper behavior.
Related
The following issues are related:
The text was updated successfully, but these errors were encountered: