-
-
Notifications
You must be signed in to change notification settings - Fork 159
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
graphql-ws/Client : Infinite loop when missing credentials #244
Comments
What loops infinitely? Client keeps trying to reconnect? For the client to fail right away, set the The default for the client is to silently try reconnecting 5 times with randomized exponential back-off and then fail. Please check out the |
Hello, thanks for the answer. It loops indefinitely and tries to connect, really fast, and show nothings by default. It only stop because I have a timeout handler in my code that throw and break the execution. When I add all events handlers with console logs (npm/debug statement in fact) : it show this (at a really fast rate, my terminal is instantly filled) :
and it continues until my timeout breaks the execution. So it tries much more than 5 times. And it seems that the exponential backoff does not work (there is no delay at all between retries). |
Ok, can you elaborate a bit more? Whats the close reason? Can you create a small repro? Can you share your client/server implementation? |
Here is my client implementation : import { createClient, ExecutionResult, SubscribePayload, Client } from 'graphql-ws';
import ws from 'ws';
import { v4 } from 'uuid';
import dbg from 'debug';
export { SubscribePayload, ExecutionResult };
const debug = dbg('hpk:graphql-ws');
export function createGQLClient(url: string) {
debug('createGQLClient(%s)', url);
const client = createClient({
url,
webSocketImpl: ws,
generateID: () => v4(),
connectionParams: {
headers: {
// this should be removed to have the bug
'x-hasura-admin-secret': process.env.HASURA_GRAPHQL_ADMIN_SECRET as string,
},
},
on: {
connected: () => {
debug('connected');
},
connecting: () => {
debug('connecting');
},
opened: () => {
debug('opened');
},
closed: () => {
debug('closed');
},
message: (msg) => {
debug('message', msg);
},
ping: (msg) => {
debug('ping', msg);
},
pong: (msg) => {
debug('pong', msg);
},
error: (err) => {
debug('error');
console.error(err);
},
},
onNonLazyError: (errorOrCloseEvent) => {
debug('NonLazyError');
console.error(errorOrCloseEvent);
},
});
client.on('error', (err) => {
debug('client error');
console.error(err);
});
return client;
} For the server, as told before : I'm using hasura v2.0.9. (so no implementation to provide) When I provide credentials in connectionParams, there is no bug. |
Please provide me with the close reason. import { createClient, ExecutionResult, SubscribePayload, Client } from 'graphql-ws';
import ws from 'ws';
import { v4 } from 'uuid';
import dbg from 'debug';
export { SubscribePayload, ExecutionResult };
const debug = dbg('hpk:graphql-ws');
export function createGQLClient(url: string) {
debug('createGQLClient(%s)', url);
const client = createClient({
url,
webSocketImpl: ws,
generateID: () => v4(),
connectionParams: {
headers: {
// this should be removed to have the bug
'x-hasura-admin-secret': process.env.HASURA_GRAPHQL_ADMIN_SECRET as string,
},
},
on: {
connected: () => {
debug('connected');
},
connecting: () => {
debug('connecting');
},
opened: () => {
debug('opened');
},
- closed: () => {
- debug('closed');
- },
+ closed: (err) => {
+ debug('closed', err.code, err.reason); // 👈
+ },
message: (msg) => {
debug('message', msg);
},
ping: (msg) => {
debug('ping', msg);
},
pong: (msg) => {
debug('pong', msg);
},
error: (err) => {
debug('error');
console.error(err);
},
},
onNonLazyError: (errorOrCloseEvent) => {
debug('NonLazyError');
console.error(errorOrCloseEvent);
},
});
client.on('error', (err) => {
debug('client error');
console.error(err);
});
return client;
}
|
here is the close event trace (after printing it in the closed event handler) :
So we see a message as a buffer (_closeMessage), and a reason as 'Invalid message' (Symbol(kReason)) |
Ok : after adding proper console log : it shows this while looping :
It's better. (by the way, could you add somewhere in documentation how to handle properly the error events, as this, it's really not clear how to do it). But, we still have a infinite loop, and no exponential back-off. |
Can you first confirm that the error gets reported when setting import { createClient, ExecutionResult, SubscribePayload, Client } from 'graphql-ws';
import ws from 'ws';
import { v4 } from 'uuid';
import dbg from 'debug';
export { SubscribePayload, ExecutionResult };
const debug = dbg('hpk:graphql-ws');
export function createGQLClient(url: string) {
debug('createGQLClient(%s)', url);
const client = createClient({
url,
webSocketImpl: ws,
+ retryAttempts: 0,
generateID: () => v4(),
connectionParams: {
headers: {
// this should be removed to have the bug
'x-hasura-admin-secret': process.env.HASURA_GRAPHQL_ADMIN_SECRET as string,
},
},
on: {
connected: () => {
debug('connected');
},
connecting: () => {
debug('connecting');
},
opened: () => {
debug('opened');
},
closed: (err) => {
debug('closed', err.code, err.reason); // 👈
},
message: (msg) => {
debug('message', msg);
},
ping: (msg) => {
debug('ping', msg);
},
pong: (msg) => {
debug('pong', msg);
},
error: (err) => {
debug('error');
console.error(err);
},
},
onNonLazyError: (errorOrCloseEvent) => {
debug('NonLazyError');
console.error(errorOrCloseEvent);
},
});
client.on('error', (err) => {
debug('client error');
console.error(err);
});
return client;
} |
Same thing with It loops indefinitely... and no error handlers are triggered. None of them. |
I see here that the server is closing with a code 1000. Code 1000 is reserved for Normal Closure indicating a graceful close which is why the client retries immediately after receiving the 1000. Guess you're getting stuck here: Lines 767 to 770 in 21e44db
The actual problem here is the server... It should NOT be using close code 1000 for erroneous closes. FYI Hasura does not use this library as their server, they instead have their own implementation of GraphQL over WebSocket. |
Yes, hasura have its own implementation, but, since hasura v2.0.8, it uses your transport layer when connecting with WS. As you already know because you are the author of the related thread : hasura/graphql-engine#6264 ;) |
Yes, they use my GraphQL over WebSocket protocol. However, this does not mean they should use code 1000 for non-erroneous closes. This is something that the protocol does not need to specify since it is a part of the WebSocket standard. See close codes: https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent/code. |
When I check the error returned by hasura : it's a 4400. Not a 1000.
|
It is a 1000. The 4400 is a part of the error text message. |
Ok so hasura send two codes ? What should I do to have this fixed ? |
There is just one WebSocket close code. The 2nd one is a part of the error text message, it does not do anything, is just a message. Am working on an issue for Hasura, please bear with me. |
I'll do that ;) |
Haha, beat you to it: hasura/graphql-engine#7696. 😄 |
I am closing this issue in favor of hasura/graphql-engine#7696. As soon as it gets resolved there, it will work as expected here. |
Hello All,
Client graphql-ws v5.5.0
on nodejs v14.17.0
Server : Hasura v2.0.9
Expected Behaviour
I expected graphql-ws/client to show an error when server does not accept connexion because missing credentials.
Actual Behaviour
Instead it loops infinitely (without any error). So if you do not have acces to server's logs, you simply don't understand why such behaviour happen.
To reproduce : simply setup a server (by example hasura >= v2.0.8) that needs credentials, try to connect with graphql-ws/client without providing credentials. Boom : it loops.
By the way, there is nowhere in the documentation a clear explanation about how to setup credentials in the client.
For those who are looking for it (example with hasura) :
The text was updated successfully, but these errors were encountered: