nhost / cli-go

MIT License
2 stars 0 forks source link

websocket / subscriptions not working with proxy #11

Closed elitan closed 3 years ago

elitan commented 3 years ago

Http requests are proxied correctly via http://localhost:1337/v1/graphql but WebSockets are not (ws://localhost:1337/v1/graphql).

mrinalwahal commented 3 years ago

Golang doesn't have a very good native support for web-sockets in it's inbuilt HTTP proxies. It's weird.

I'm afraid I may need to write a separate wrapper for this problem.

Or use github.com/gorilla/websocket.

It's not proper on our part to launch a separate web-socket reverse proxy on a different port. We need it to be at default HTTP proxy port 1337.

Darn!

I'll find a solution. Give me some time for this!

Update: websocket requests are not just not passing through the reverse proxy, the functions can't even receive them on a native HTTP server, without a local reverse proxy running. To reproduce this error, run nhost functions. And use that endpoint for a WS request.

Weirdly, functions are able to receive the WS requests. And even build & execute the functions absolutely fine on those requests. But postman tells me, they aren't returning a response correctly after running those functions.

Update 2: Same behaviour on my end, even while running functions behind our local reverse proxy. They are building and executing just fine (check debug logs), but not returning the response. At least in postman, they aren't!

Investigation underway at 3 AM at night! 🌙

Update 3: Oh my goodness, this is worse than I thought! Golang's internal HTTP server panics on a WS request.

I'm afraid we have 2 immediate options:

  1. Either we launch a separate WS reverse proxy on a different port.
  2. Or, I will have to write a custom request handler library/wrapper to handle HTTP, TCP and WS requests based on their schemes. This means -> almost the same amount of effort as I'd to put in building that tunnels thingy.

Update 4: Even express needs to launch a separate WS server. But this isn't something big. I can route the requests to different ports based on their scheme.

Update 5: Here's a potential solution, and the problem with it,too:

Quietly run a separate WS reverse proxy on a different port. And if any request on our HTTP proxy on port 1337 has the scheme ws://, then transfer it to this separate WS server. Do the same inside NodeJS with by launching a separate WS server with express. And transfer requests based on their schemes on different ports, from inside the CLI.

const express = require('express');
const ws = require('ws');

const app = express();

// Set up a headless websocket server that prints any
// events that come in.
const wsServer = new ws.Server({ noServer: true });
wsServer.on('connection', socket => {
  socket.on('message', message => console.log(message));
});

// `server` is a vanilla Node.js HTTP server, so use
// the same ws upgrade process described here:
// https://www.npmjs.com/package/ws#multiple-servers-sharing-a-single-https-server
const server = app.listen(3000);
server.on('upgrade', (request, socket, head) => {
  wsServer.handleUpgrade(request, socket, head, socket => {
    wsServer.emit('connection', socket, request);
  });
});

Here's the problem: this solution treats WS like complete HTTP. When, in fact, they are much more. Hasura subscriptions use web-sockets, connections with which they have to keep open, to send out real-time subscription updates.

I don't think this work-around of transferring requests to different ports, based on their schemes, can actually make it behave like a web-socket or not.

This is also a very unprofessional way on our part of handling things.

The only two long term credible solutions are the ones, I mentioned above. Either a custom library, or a different port for WS connections.

elitan commented 3 years ago

Thanks for the walk-through.

Just to be clear. We should only support WebSockets for Hasura. Not for our serverless functions.

But I guess it's the same problem since all HTTP requests goes through our reverse proxy.

elitan commented 3 years ago

I just read the description of this package: https://github.com/koding/websocketproxy

WebsocketProxy is an http.Handler interface build on top of gorilla/websocket that you can plug into your existing Go webserver to provide WebSocket reverse proxy.

Sounds exactly like what we need!

mrinalwahal commented 3 years ago

Yes, the custom request handler I'm talking about developing would be inspired from the same repo you shared. It's the same repo I also got the idea from.

But, as I mentioned above, it needs us to launch the WS reverse proxy server on a different port.

What we ideally want is to handle all requests through the same server based on their schemes. So I'll have to modify the code inside the konding/websocketproxy repo you've linked.

mrinalwahal commented 3 years ago

Web-sockets now work fine. I'm closing this issue.