apollographql / apollo-server

🌍  Spec-compliant and production ready JavaScript GraphQL server that lets you develop in a schema-first way. Built for Express, Connect, Hapi, Koa, and more.
https://www.apollographql.com/docs/apollo-server/
MIT License
13.8k stars 2.03k forks source link

not finding websocket with subscriptions #1844

Closed tensor-programming closed 6 years ago

tensor-programming commented 6 years ago

Writing a simple chat application using apollo and express. Prior to this I was using subscriptions-transport-ws and graphql-server-express but there was an issue with graphiql not being able to call subscriptions due to a client error with graphiql. Because of this issue, I swapped over to apollo.

Currently, when I try to access a subscription through graphiql the client comes back and says: "Could not connect to websocket endpoint ws://localhost:6001/graphql. Please check if the endpoint url is correct." If I try to use a simple websocket client to find the websocket endpoint, it doesn't seem to exist.

I stripped out most of the stuff from my server/index.js file to see if I could solve this problem, heres what it looks like:

import { createServer } from 'http'
import express from 'express'

import { typeDefs } from './types'
import resolvers from './resolvers'
import { ApolloServer } from 'apollo-server-express'

const PORT = process.env.PORT || 6001

const app = express()

const apollo = new ApolloServer({
  typeDefs,
  resolvers
})

apollo.applyMiddleware({ app })

const httpServer = createServer(app)

apollo.installSubscriptionHandlers(httpServer)

app.listen({ port: PORT }, () => {
  console.log(`server ready at http://localhost:${PORT}${apollo.graphqlPath}`)
  console.log(`Subscriptions ready at ws://localhost:${PORT}${apollo.subscriptionsPath}`)
})

My imports in package.json look like this:

"dependencies": {
    "apollo-server": "^2.1.0",
    "apollo-server-express": "^2.1.0",
    "bluebird": "^3.5.2",
    "body-parser": "^1.18.3",
    "compression": "^1.7.3",
    "cors": "^2.8.4",
    "express": "^4.16.4",
    "graphql": "^14.0.2",
    "graphql-playground-middleware-express": "^1.7.6",
    "graphql-subscriptions": "^1.0.0",
    "graphql-tools": "^4.0.2",
    "mongoose": "^5.3.4"
  },

Not really sure why the subscription endpoint isn't properly being deployed through the apollo.installSubscriptionHandlers(httpServer) command.

tensor-programming commented 6 years ago

Never mind, figured it out right after I posted this.

gforge commented 6 years ago

Sorry - could you enlighten me with your fix? I'm debugging a piece of code that worked a few days ago :sob:

gforge commented 6 years ago

Ok, for anyone who has this issue it was in my case a problem that RabbitMQ was connected on 'amqp://localhost', after changing to 'amqp://localhost:5762' everything seems to work as expected

clouds8 commented 5 years ago

@tensor-programming I came out with the same problem like you. How did you fix it ?

gforge commented 5 years ago

@clouds8 - in my case it seemed to be a Firefox issue. It went away with a Firefox-update or when I used Chrome.

clouds8 commented 5 years ago

@gforge thx for reply. I find the point why I got stuck.

It seems that apollo-server do not support more than one subscription. I mean use installSubscriptionHandlers twice or more in one express service, because I generate two instances of ApolloServer and both of them use installSubscriptionHandlers method. It causes the error "Could not connect to websocket endpoint ws://127.0.0.1:4000/xxx. Please check if the endpoint url is correct.".

gforge commented 5 years ago

@clouds8 - two instances of ApolloServer sounds like a bad idea... Without knowing your motivation for this, I would consider looking into using Nginx to split the path into separate services.

jakec-dev commented 5 years ago

I'm having the same issue. Changing browsers doesn't fix it, and it's only the one subscription. Any other tips?

nattyrice commented 5 years ago

Trying changing app.listen to httpServer.listen

like so:

httpServer.listen({ port: PORT }, () => {
  console.log(`server ready at http://localhost:${PORT}${apollo.graphqlPath}`)
  console.log(`Subscriptions ready at ws://localhost:${PORT}${apollo.subscriptionsPath}`)
})
ivansard commented 5 years ago

@tensor-programming could you please give a clarification on how you solved this issue?

Also, I think it's operating system specific. I'm using the same stack (apollo-server-express, etc..), and when I run the code on Ubuntu everything is working seamlessly, but when I run on Windows (I'm using Windows 10), I get the already mentioned error:

"Could not connect to websocket endpoint ws://localhost:6001/graphql. Please check if the endpoint url is correct."

Any help would be greatly appreciated

LiveDuo commented 5 years ago

The following answer helped me solved it https://stackoverflow.com/questions/53415286/subscription-not-connecting-using-apolloserver

ardmont commented 5 years ago

@tensor-programming could you please give a clarification on how you solved this issue?

Also, I think it's operating system specific. I'm using the same stack (apollo-server-express, etc..), and when I run the code on Ubuntu everything is working seamlessly, but when I run on Windows (I'm using Windows 10), I get the already mentioned error:

"Could not connect to websocket endpoint ws://localhost:6001/graphql. Please check if the endpoint url is correct."

Any help would be greatly appreciated

I'm having the same problem. I tested it on multiples machines within a LAN. The subscription is working fine in any Ubuntu browsers, but it is giving the error on Windows Browsers. At this moment, I only tested it using the playground tool, but I think the problem is somehow related to it. I think the playground tool have some bug on web socket connection from Windows browsers.

ivansard commented 5 years ago

@LiveDuo @ardmont thanks for your replies!

I think it's definitely not a browser-specific issue in itself, but rather OS-specific as @ardmont also mentions. I haven't run subscriptions in production yet, so I don't know if it's playground specific, but anyhow this issue shouldn't be happening, and a clarification would be nice. Dockerizing the GraphQL server on windows (Using Linux containers) also doesn't make a difference. But when running it on a DigitalOcean Droplet (Ubuntu) it works.

Hardronox commented 5 years ago

Had the same issue, console was showing 400 error. Adding highlighted line from this article solved problem for me. https://www.apollographql.com/docs/apollo-server/data/subscriptions/#subscriptions-with-additional-middleware

Dizzienoo commented 5 years ago

@Hardronox your fix worked for me, thank you!

leonetosoft commented 4 years ago

Tentando mudar app.listenparahttpServer.listen

igual a:

httpServer . listen ( {  port : PORT  } ,  ( )  =>  { 
  console . log ( `servidor pronto em http: // localhost: $ { PORT } $ { apollo . graphqlPath } ` ) 
  console . log ( `assinaturas prontas em ws: / / localhost: $ { PORT } $ { apollo . subscriptionsPath } ` ) 
} )

exatamente isso que estava faltando pro meu código funcionar ... kkk obrigado

idkjs commented 4 years ago

Had the same issue, console was showing 400 error. Adding highlighted line from this article solved problem for me. https://www.apollographql.com/docs/apollo-server/data/subscriptions/#subscriptions-with-additional-middleware

@Hardronox how did you the link to the highlighted line? Also, thanks of the working solution.

jeff-ofobrukweta commented 4 years ago

I used this and it worked for me https://www.apollographql.com/docs/apollo-server/data/subscriptions/#subscriptions-with-additional-middleware

try replacing app.listen with httpServer.listen() and that worked for me

jeff-ofobrukweta commented 4 years ago

Please can you give a clear description of this problem so ill know exactly how the expected behavior is meant to be?

jeff-ofobrukweta commented 4 years ago

`import "reflect-metadata"; import { ApolloServer } from "apollo-server-express"; import http from 'http'; import Express from "express"; import session from "express-session"; import { createConnection } from "typeorm"; import { redis } from "./redis"; import connectRedis from "connect-redis"; import cors from "cors"; import { createShema } from './utils/createShema.utils'; import { environment } from './config/constant.config/enviroment.config'; import routes from "./route/index.route"; import { logger } from "./config/constant.config/logging.config.log"; import { indexing } from "./utils/elk/api/api.elastic.index"; import { AcessControlHeader, ElasticOrigin } from './config/constant.config/response.config'; import { requestPlugin } from "./utils/plugin/logger.plugin"; import { fieldExtensionsEstimator, getComplexity, simpleEstimator } from "graphql-query-complexity";

async function bootstrap() { const schema: any = await createShema() const app = Express() const httpServer = http.createServer(app) const RedisStore = connectRedis(session)

const apolloServer = new ApolloServer({ schema, context: ({ req, res }) => ({ req, res }), formatError: err => { if (err.extensions) logger.debug("rupix-api-backend", err.extensions) // put winston here return err }, engine: { / Other, existing engine configuration should remain the same. /

  generateClientInfo: ({ request }) => {
    const headers = request.http?.headers && request.http.headers;
    if (headers) {
      return {
        clientName: headers['apollographql-client-nuggets'],
        clientVersion: headers['apollographql-client-version-0.0.1'],
      };
    } else {
      return {
        clientName: "Unknown Client",
        clientVersion: "Unversioned",
      };
    }
  },

},
plugins: [
  requestPlugin,
  {
    requestDidStart: () => ({
        didResolveOperation({ request, document }) {
            /**
             * This provides GraphQL query analysis to be able to react on complex queries to your GraphQL server.
             * This can be used to protect your GraphQL servers against resource exhaustion and DoS attacks.
             * More documentation can be found at https://github.com/ivome/graphql-query-complexity.
             */
            const complexity = getComplexity({
                // Our built schema
                schema,
                // To calculate query complexity properly,
                // we have to check only the requested operation
                // not the whole document that may contains multiple operations
                operationName: request.operationName,
                // The GraphQL query document
                query: document,
                // The variables for our GraphQL query
                variables: request.variables,
                // Add any number of estimators. The estimators are invoked in order, the first
                // numeric value that is being returned by an estimator is used as the field complexity.
                // If no estimator returns a value, an exception is raised.
                estimators: [
                    // Using fieldExtensionsEstimator is mandatory to make it work with type-graphql.
                    fieldExtensionsEstimator(),
                    // Add more estimators here...
                    // This will assign each field a complexity of 1
                    // if no other estimator returned a value.
                    simpleEstimator({ defaultComplexity: 1 }),
                ],
            });
            // Here we can react to the calculated complexity,
            // like compare it with max and throw error when the threshold is reached.
            if (complexity > 20) {
              logger.error(`Sorry, too complicated query! ${complexity} is over 20 that is the max allowed complexity.`)
                throw new Error(
                    `Sorry, too complicated query! ${complexity} is over 20 that is the max allowed complexity.`,
                );
            }
            // And here we can e.g. subtract the complexity point from hourly API calls limit.

        },
    }),
}
],
playground: true || Boolean(environment.apollo.playground),
tracing: environment.apollo.tracing

})

apolloServer.installSubscriptionHandlers(httpServer)

app.use("/", routes)

app.use((req, res, done) => { res.header( AcessControlHeader, ElasticOrigin ) // this section is to implement the logger part done(); })

// this config for cors is just the host i expect the frontend to be at it depends app.use( cors({ credentials: true, origin: process.env.CLIENT_URL || "*" //check the .env file for this i.e http://localhost:3000 }) );

app.use( session({ store: new RedisStore({ client: redis as any, host: environment.redis.host, //the host to the redis instance where session is stored port: environment.redis.port, ttl: environment.redis.ttl }), name: environment.session.name, secret: environment.session.secret, resave: environment.session.resave, saveUninitialized: environment.session.saveUninitialized, cookie: { httpOnly: environment.session.cookie.httpOnly, secure: environment.session.cookie.secure, maxAge: environment.session.cookie.maxAge // 24 hours } }) );

apolloServer.applyMiddleware({ app, bodyParserConfig: true, cors: true, // path: environment.apollo.endpoint })

createConnection().then(() => { httpServer.listen(environment.port, async () => { logger.info(Server started, 🚀 listening on port ${environment.port} for incoming requests.Subscriptions ready at ws://localhost:${environment.port}${apolloServer.subscriptionsPath}); }) }).catch((err) => { logger.info("Couldn't connect to the database.", err); throw new Error(Couldn't connect to the database. ${err}); });

}

bootstrap(); `

jeff-ofobrukweta commented 4 years ago

`import "reflect-metadata"; import { ApolloServer } from "apollo-server-express"; import http from 'http'; import Express from "express"; import session from "express-session"; import { createConnection } from "typeorm"; import { redis } from "./redis"; import connectRedis from "connect-redis"; import cors from "cors"; import { createShema } from './utils/createShema.utils'; import { environment } from './config/constant.config/enviroment.config'; import routes from "./route/index.route"; import { logger } from "./config/constant.config/logging.config.log"; import { indexing } from "./utils/elk/api/api.elastic.index"; import { AcessControlHeader, ElasticOrigin } from './config/constant.config/response.config'; import { requestPlugin } from "./utils/plugin/logger.plugin"; import { fieldExtensionsEstimator, getComplexity, simpleEstimator } from "graphql-query-complexity";

async function bootstrap() { const schema: any = await createShema() const app = Express() const httpServer = http.createServer(app) const RedisStore = connectRedis(session)

const apolloServer = new ApolloServer({ schema, context: ({ req, res }) => ({ req, res }), formatError: err => { if (err.extensions) logger.debug("rupix-api-backend", err.extensions) // put winston here return err }, engine: { / Other, existing engine configuration should remain the same. /

  generateClientInfo: ({ request }) => {
    const headers = request.http?.headers && request.http.headers;
    if (headers) {
      return {
        clientName: headers['apollographql-client-nuggets'],
        clientVersion: headers['apollographql-client-version-0.0.1'],
      };
    } else {
      return {
        clientName: "Unknown Client",
        clientVersion: "Unversioned",
      };
    }
  },

},
plugins: [
  requestPlugin,
  {
    requestDidStart: () => ({
        didResolveOperation({ request, document }) {
            /**
             * This provides GraphQL query analysis to be able to react on complex queries to your GraphQL server.
             * This can be used to protect your GraphQL servers against resource exhaustion and DoS attacks.
             * More documentation can be found at https://github.com/ivome/graphql-query-complexity.
             */
            const complexity = getComplexity({
                // Our built schema
                schema,
                // To calculate query complexity properly,
                // we have to check only the requested operation
                // not the whole document that may contains multiple operations
                operationName: request.operationName,
                // The GraphQL query document
                query: document,
                // The variables for our GraphQL query
                variables: request.variables,
                // Add any number of estimators. The estimators are invoked in order, the first
                // numeric value that is being returned by an estimator is used as the field complexity.
                // If no estimator returns a value, an exception is raised.
                estimators: [
                    // Using fieldExtensionsEstimator is mandatory to make it work with type-graphql.
                    fieldExtensionsEstimator(),
                    // Add more estimators here...
                    // This will assign each field a complexity of 1
                    // if no other estimator returned a value.
                    simpleEstimator({ defaultComplexity: 1 }),
                ],
            });
            // Here we can react to the calculated complexity,
            // like compare it with max and throw error when the threshold is reached.
            if (complexity > 20) {
              logger.error(`Sorry, too complicated query! ${complexity} is over 20 that is the max allowed complexity.`)
                throw new Error(
                    `Sorry, too complicated query! ${complexity} is over 20 that is the max allowed complexity.`,
                );
            }
            // And here we can e.g. subtract the complexity point from hourly API calls limit.

        },
    }),
}
],
playground: true || Boolean(environment.apollo.playground),
tracing: environment.apollo.tracing

})

apolloServer.installSubscriptionHandlers(httpServer)

app.use("/", routes)

app.use((req, res, done) => { res.header( AcessControlHeader, ElasticOrigin ) // this section is to implement the logger part done(); })

// this config for cors is just the host i expect the frontend to be at it depends app.use( cors({ credentials: true, origin: process.env.CLIENT_URL || "*" //check the .env file for this i.e http://localhost:3000 }) );

app.use( session({ store: new RedisStore({ client: redis as any, host: environment.redis.host, //the host to the redis instance where session is stored port: environment.redis.port, ttl: environment.redis.ttl }), name: environment.session.name, secret: environment.session.secret, resave: environment.session.resave, saveUninitialized: environment.session.saveUninitialized, cookie: { httpOnly: environment.session.cookie.httpOnly, secure: environment.session.cookie.secure, maxAge: environment.session.cookie.maxAge // 24 hours } }) );

apolloServer.applyMiddleware({ app, bodyParserConfig: true, cors: true, // path: environment.apollo.endpoint })

createConnection().then(() => { httpServer.listen(environment.port, async () => { logger.info(Server started, 🚀 listening on port ${environment.port} for incoming requests.Subscriptions ready at ws://localhost:${environment.port}${apolloServer.subscriptionsPath}); }) }).catch((err) => { logger.info("Couldn't connect to the database.", err); throw new Error(Couldn't connect to the database. ${err}); });

}

bootstrap(); `

check the highlighted bold code snippet to see how i used the http module and passed it inside my apply middleware .Bear in mind here that am using typegraphql but this should not be too far from what you have

laudebugs commented 3 years ago

@jeff-ofobrukweta Your fix works! Thanks

mironovpib101 commented 3 years ago

yes. add apolloServer.installSubscriptionHandlers(httpServer) to your code