-
Notifications
You must be signed in to change notification settings - Fork 2k
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
feat(subscriptions): enable passing WebSocket.Server
instead of HttpServer
.
#2314
Conversation
cd9b0c5
to
fb56543
Compare
…stead of HttpServer For people who need to have multiple WebSocket servers sharing a single HTTP(S) server, the `ws` docs recommend [creating multiple instances of `new WebSocket.Server({ noServer: true })`](https://github.com/websockets/ws#multiple-servers-sharing-a-single-https-server). This PR makes `installSubscriptionHandlers` accept a `WebSocket.Server` instance (in addition to accepting an http server) to support the multiple WebSocket servers use case. Example: ```js const graphQLWebSocket = new WebSocket.Server({ noServer: true, }) apolloServer.installSubscriptionHandlers(graphQLWebSocket) const symmetryWebSocket = new WebSocket.Server({ noServer: true, }) symmetryWebSocket.on('connection', socket => new SymmetryServer({socket})) /** * This is a workaround. When WebSocket.Server handles an upgrade request, * it will abort the handshake if the request path doesn't match -- meaning it * will break Meteor's DDP web socket. So we only want the WebSocket.Server * for GraphQL to receive the upgrade event if it's not for Meteor. */ httpServer.on('upgrade', (request: IncomingMessage, socket: net$Socket, head: any) => { if (request.url.startsWith(SYMMETRY_BROWSER_PATH)) { symmetryWebSocket.handleUpgrade(request, socket, head, (ws: WebSocket) => { symmetryWebSocket.emit('connection', ws) }) } else if (request.url.startsWith(GRAPHQL_PATH)) { graphQLWebSocket.handleUpgrade(request, socket, head, (ws: WebSocket) => { graphQLWebSocket.emit('connection', ws) }) } else { socket.destroy() } }) ```
fb56543
to
11ec6f0
Compare
@abernix can we get this merged? |
I fixed the conflict in the changelog, but I hope you can merge this soon so I don't have to keep merging conflicts over and over again |
@abernix this would be great to have |
@abernix please include this in v3 |
1 similar comment
@abernix please include this in v3 |
I strongly support this pull request as I find very difficult to patch it on a fork. Sadly we use apollo-server-express, which depends on apollo-server-core. So I would have to fork multiple projects in order to use this patch :( @martijnwalraven @trevor-scheer When do you plan the next release of apollo-server-core? |
Thank you so much @jedwards1211 !!! I did not exactly use your solution as my server is instantiated by a sub-library. But it gave me the idea of monkey-patching the already instantiated Object!
And it works perfectly! That will do it until they release the patch :) |
You're welcome! Yeah what I did with |
Please merge this, +1 needed here. |
@jedwards1211 how is the work around meant to be used? Currently have something like this, cut down for example sake.import stoppable from 'stoppable';
import express from 'express';
import http from 'http';
import { ApolloServer } from 'apollo-server-express';
import { graphql } from './graphql';
const app = express();
const graphApp = new ApolloServer(graphql);
graphApp.applyMiddleware({app});
const httpServer = http.createServer(app);
const stoppableServer = stoppable(httpServer);
graphApp.installSubscriptionHandlers(stoppableServer); |
@OmgImAlexis basically just monkeypatch the It's not clear from your example that you need this -- this workaround/PR is intended for cases where you need multiple separate websocket handlers. Do you need multiple websocket handlers? |
@martijnwalraven @abernix will this PR ever get merged? Will we ever get to stop monkeypatching? |
@jedwards1211 sorry to be a bother but how exactly do I do that? |
import express from 'express'
import http from 'http'
import WebSocket from 'ws'
import { ApolloServer } from 'apollo-server-express'
const app = express()
const httpServer = http.createServer(app)
const graphQLWebSocket = new WebSocket.Server({ noServer: true })
const otherWebSocket = new WebSocket.Server({ noServer: true })
const GRAPHQL_PATH = '/graphql'
const OTHER_WEB_SOCKET_PATH = '/other'
httpServer.on('upgrade', (request, socket, head) => {
const { url } = request
if (url.startsWith(GRAPHQL_PATH)) {
graphQLWebSocket.handleUpgrade(request, socket, head,ws => {
graphQLWebSocket.emit('connection', ws)
})
} else if (url.startsWith(OTHER_WEB_SOCKET_PATH)) {
otherWebsocket.handleUpgrade(request, socket, head, ws => {
otherWebsocket.emit('connection', ws)
})
} else {
socket.destroy()
}
})
const apolloServer = new ApolloServer({
path: GRAPHQL_PATH,
// you'll need to provide/flesh out the following
schema,
context: ({req, connection}) => ...,
subscriptions: {
path: GRAPHQL_PATH,
onConnect: (connectionParams, websocket) => ...,
},
})
apolloServer.installSubscriptionHandlers = <the big function from my gist>
apolloServer.applyMiddleware({ app, path: GRAPHQL_PATH })
apolloServer.installSubscriptionHandlers(graphQLWebSocket)
otherWebSocket.on('connection', ...)
httpServer.listen(4000) |
This PR is opened for a while and the solution seems to be elegant to have own websocket configuration. @abernix @martijnwalraven can this PR be reviewed quickly and merge it? |
I use multiple graphql schemes on a single http server. I would like to be able to run several web socket servers. I would like this PR to be merged. |
This comment has been minimized.
This comment has been minimized.
WebSocket.Server
instead of HttpServer
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks non-controversial enough to bring in without being a breaking change. Sorry this has sat for so long! We're trying to make our way through the backlog of things to be addressed, but subscriptions has often taken a backseat. We're working to consider it more carefully going forward, but do bear with us while we work toward eventually making it a more first-class citizen (i.e. actually benefit from the request pipeline and what it brings in terms of plugin support, caching, observability, etc.)
@abernix thanks a bunch! Even https://graphql.org/learn/ still doesn't even mention subscriptions AFAIK, so subs are kind of the second-class citizen of the whole ecosystem. Food for thought: in one of my projects I've started a pattern of using matching query and subscription fields implement an "observable" queries. For it to work I have to do a |
I've run into the same problem - needing to run two WS on the same http server, but for some reason I can't get the subscriptions to work. Work is based off of the examples for
The timesync feature works great, it enters the With a single websocket the subscriptions work great:
but once we add another websocket it doesn't. Does anyone have any idea what might be going on or how to debug this? |
@okeren-cx you might be having issue #4198, check if there are multiple versions of |
@jedwards1211 thanks! it was indeed the problem. I ended up monkey patching in the same way you showed here and it worked. |
@jedwards1211 and others I had this issue and the issue in #4198. TL;DR until some changes are merged or transport is changed the monkey patch is the way to make it work, this merge request doesn't solve #4198. The version by @jedwards1211 is in typescript, here is a working javascript equivalent. Thanks to all people that did a lot of the digging before 👍 https://gist.github.com/timkock/b38ae86cf5634c63dc482c7fc1c66be1 |
For people who need to have multiple WebSocket servers sharing a single HTTP(S) server, the
ws
docs recommend creating multiple instances ofnew WebSocket.Server({ noServer: true })
. This PR makesinstallSubscriptionHandlers
accept aWebSocket.Server
instance (in addition to accepting an http server) to support the multiple WebSocket servers use case.Real-life example from my code:
TODO:
installSubscriptionHandlers
is not currently tested anywhere)