BrowserSync / browser-sync

Keep multiple browsers & devices in sync when building websites. https://browsersync.io
https://discord.gg/2d2xUThp
Apache License 2.0
12.17k stars 755 forks source link

websocket problems 502 #780

Open jmls opened 9 years ago

jmls commented 9 years ago

I've logged an issue here (https://github.com/chimurai/http-proxy-middleware/issues/15) where I am having a problem with browser-sync, http-proxy-middleware and websockets

In a nutshell, I have this code

var server = {
    baseDir: baseDir,
    routes: routes
  };

  var proxies = [];

  proxies.push(proxyMiddleware('/socket.io', { 
        target: 'http://localhost:5000/' , ws: true
  }));

  server.middleware = proxies;

  browserSync.instance = browserSync.init({
    startPath: '/',
    server: server,
    browser: browser
  });

but get these errors :

woot! connection
[HPM] Upgrading to WebSocket
[HPM] Client disconnected

and the client console has

WebSocket connection to 'ws://mysite.io/socket.io/?EIO=3&transport=websocket&sid=5_BeDfck0LFYcYxPAAAA' failed: Error during WebSocket handshake: Unexpected response code: 502

and obviously, I can't get my websocket connected.

Am I missing something or is it a bug ?

thanks

xak2000 commented 9 years ago

Same problem with similar configuration but using sock.js. I also tried to use raw websockets. Also I tried using different proxy middlewares: proxy-middleware, http-proxy-middleware. I even tried using express + http-proxy-middleware as middleware of browser-sync but with no luck. All http requests proxying fine with all this configurations. But not Websocket request.

This is example of my try using express + http-proxy-middleware:

var express = require('express')
  , proxy = require('http-proxy-middleware');

    $.browserSync({
      host: config.host,
      open: 'external',
      port: config.port,
      ws: true,
      server: {
        baseDir: config.buildDir,
        middleware: [
          (function () {
            var app = express()
              , context = '/api'
              , options = {
              target: 'http://localhost:8080',
              changeOrigin: true,
              ws: true, // proxy websockets
              pathRewrite: {
                '^/api': ''
              }
            };
            app.use(proxy(context, options));
            return app;
          })()
        ],
        ws: true
      }
    });

The error is

WebSocket connection to 'ws://192.168.250.3:3000/api/ws/023/xddwmfnu/websocket' failed: Connection closed before receiving a handshake response

chimurai commented 9 years ago

Websockets are fixed in proxy mode BrowserSync v2.8.1. (https://github.com/browsersync/browser-sync/commit/40017b4)

Wondering if it is the same issue in server mode.

Background info on websocket fix in proxy mode: https://github.com/BrowserSync/browser-sync/issues/625#issuecomment-125044205

shakyShane commented 9 years ago

@jmls or @xak2000 Can someone provide a small sample app showing this problem, this will enable me to debug it much faster

xak2000 commented 9 years ago

I cloned http-proxy-middleware repository and add example with browser-sync + websocket together.

To run it:

$ git clone https://github.com/xak2000/http-proxy-middleware.git
$ cd http-proxy-middleware
$ npm install

Then run browser-sync-ws example:

$ node examples/browser-sync-ws

You can open browser console and see the error:

WebSocket connection to 'ws://localhost:3000/ws' failed: Connection closed before receiving a handshake response

This example proxies all requests to ws://localhost:3000/ws to echo.websocket.org.

micheledisalvatore commented 9 years ago

I've the same problem, any news about a fix? Here my code, that works except for the error in object

var proxyMiddleware = require('http-proxy-middleware');

[...]
    browserSync: {
      server: {
        bsFiles: {
          src: ['./dist/{**/*,*}.js', './dist/{**/*,*}.html', './dist/{**/*,*}.css']
        },
        options: {
          watchTask: true,
          logLevel: "info",
          logConnections: true,
          open: true,
          notify: false,
          port: 9000,
          logFileChanges: false,
          server: {
            baseDir: './dist',
            middleware: [
              proxyMiddleware('/appserve/api', {
                target: 'http://127.0.0.1:8080',
                changeOrigin: false
              }),
              proxyMiddleware('/ws', {
                target: 'http://127.0.0.1:3000',
                changeOrigin: false,
                ws: true
              })
            ]
          }
        }
      }

    }
[...]

WebSocket connection to 'ws://localhost:9000/ws/?EIO=3&transport=websocket&sid=H75IeRaFmZqKFgqwAA' failed: Connection closed before receiving a handshake response

micheledisalvatore commented 9 years ago

@xak2000 @jmls have you found a solution?

xak2000 commented 9 years ago

No. But I am using SockJS and when it cannot connet through websocket it emulates it through backup transports (xhr_streaming in my case). Not too convinient in dev mode as I don't see data frames in Chrome's Network tab. But until this bug will be fixed I have no alternatives.

@shakyShane, any progress of this issue? Do my example of this bug suit your needs?

micheledisalvatore commented 9 years ago

I've seen that using the soket.io it works, also with the error above. So for the development environment is not a problem. In production, I use nginx.

xak2000 commented 9 years ago

socket.io also uses backup transports so if it works for you, it uses them instead of native websocket. But native websockets still doesn't work.

micheledisalvatore commented 9 years ago

yes, I know...

redi-madhava commented 8 years ago

This issue has been thorny for me too: https://github.com/chimurai/http-proxy-middleware/issues/37 As you see @chimurai already linked the issue.

Due to this issue, I'm now thinking of using something else other than brower-sync.

aaronchar commented 8 years ago

I had to move on from browsersync at work because of this issue. Would be nice to see a resolution.

redi-madhava commented 8 years ago

I tried starting up two browersync instances: one for WS only and the other for file serving, reloading etc, but that ended up in port conflicts. Next option would be to start a node server only to forward WS traffic, but I'm researching on how to do this. Websockets are mainstream, and a simpler & default support for WS forwarding/proxying is very desired. No idea why browersync folks are not prioritizing this.

shakyShane commented 8 years ago

@redi-madhava the problem is that Browsersync is intercepting the upgrade server event.

so this works

var WebSocketServer = require('ws').Server;
var wss = new WebSocketServer({ port: 8000 });

wss.on('connection', function connection(ws) {
    ws.on('message', function incoming(message) {
        console.log('received: %s', message);
    });
    ws.send('something');
});

/**
 * Setup proxy
 * @type {*|exports|module.exports}
 */
var httpProxy = require('http-proxy');
var http = require('http');
var proxy = new httpProxy.createProxyServer({
    target: {
        host: 'localhost',
        port: 8000
    }
});

/**
 * Setup static file server + proxy websockets
 */
var connect = require('connect')();
connect.use(require('serve-static')('app'));
var http = require('http');
var server = http.createServer(connect);
server.on('upgrade', function (req, socket, head) {
    proxy.ws(req, socket, head);
});
server.listen(3000);

But swap the last chunk for

var browserSync = require('browser-sync');
browserSync({
    server: "app",
    files: ["app/*.html", "app/css/*.css"]
}, function (err, bs) {
    bs.server.on('upgrade', function (req, socket, head) {
        proxy.ws(req, socket, head);
    });
});

and it all just breaks!

shakyShane commented 8 years ago

using the example above, 1 way would be to simply call the correct websocket url and not expect Browsersync to proxy it

// browser
var websocket = new WebSocket("ws://0.0.0.0:8000"); // don't use the same port as Browsersync

var int = setInterval(x => {
    if (websocket.readyState === 1) {
        clearInterval(int);
        wsReady();
    }
}, 1000);

function wsReady () {
    websocket.send(JSON.stringify({
        id: "client1"
    }));
}
redi-madhava commented 8 years ago

@shakyShane Thanks! I removed browser-sync and used above code to make it work!

jmls commented 8 years ago

@redi-madhava @shakyShane : sorry, I must be dumb - what do I need to change where to make this work ? thanks!

redi-madhava commented 8 years ago

We use gulp, so my gulp task file for serving up html5 app looks like below based on the tips from @shakyShane above.

When "gulp serve" is run, it starts a node server listening on port 3000 and forwards websocket traffic to a websocket server running on localhost at 9090. It grabs 'upgrade' event in websocket handshake to use proxy created for localhost:9090.

conf below just contains some constants, for eg conf.paths.tmp=/.tmp, conf.paths.src=src and bower_components folder is at the same level as src folder.

Hope that helps!

'use strict';

var gulp = require('gulp'),
  conf = require('./conf'),
  gutil = require('gulp-util'),
  http = require('http'),
  httpProxy = require('http-proxy');

gulp.task('serve', ['watch'], function () {
  httpProxyInit(conf.paths.tmp + '/serve');
});

var proxy = httpProxy.createProxyServer({
  target: {
    host: 'localhost',
    port: 9090
  }
});

proxy.on('error', function(e) {
  gutil.log('error on proxying websocket');
  gutil.log(e);
})

var httpProxyInit = function (baseDir) {

  var connect = require('connect')();
  connect.use(require('serve-static')(baseDir));
  connect.use(require('serve-static')(conf.paths.src));
  connect.use('/bower_components', require('serve-static')(conf.paths.src + '/../bower_components'));
  var server = http.createServer(connect);

  server.on('upgrade', function (req, socket, head) {
    proxy.ws(req, socket, head);
  });

  server.listen(3000);
};
xak2000 commented 8 years ago

I doesn't understand where is browser-sync in this solution? The main problem is that browser-sync doesn't work with websocket proxy. Browser-sync is needed to auto-refresh page in browser after js-files was changed. Without browser-sync we could just use connect + http-proxy-middleware and all working fine except auto-refresh of browser.

To make it cleaner: In my application I have: 1) a client - bunch of static html files + js-files. 2) a server - java application that running on some port (just one port for both http and websocket requests). So the server url is http://localhost:SERVER_PORT

In production client files just hosted by apache http server. Also in apache config there are proxy_pass from /api/** url to http://localhost:SERVER_PORT/** url. This proxy_pass working fine for http requests and for ws requests. So js-client can do http and ws requests to port 80 and transparently proxy_passed to java server.

On my development machine I made the same configuration but with browser-sync instead of apache to auto refresh browser page in case of some html/js files changed. Instead of apache's proxy_pass I used http-proxy-middleware and all working fine except proxying of websocket.

Toub commented 8 years ago

As BrowserSync does not support WebSockets, a simple approach (apart of removing BS) is to serve WS server directly in dev mode.

e.g. with socket.io:

var ioSocket = io('http://localhost:9000', {
    path: '/socket.io'
});

I hope this to be fixed soon.

bryantp commented 7 years ago

Hi,

I am also having an issue with websockets in server mode. Here is my gulp config:

var proxyMiddleware = require('http-proxy-middleware');

module.exports = function () {
  return {
    injectChanges: true,
    port: 3002,
    server: {
      baseDir: [
        '.tmp',
        'src
      ],
      routes: {
        '/bower_components': 'bower_components'
      },
      middleware: [
        proxyMiddleware('/api/**', {target: 'http://localhost:8081', changeOrigin: true}),
        proxyMiddleware('/ws/**', {target: 'http://localhost:8081', changeOrigin: false, ws: true, logLevel: 'debug'}),
      ]
    },
    open: false
  };
};

It usually terminates with this error:

Connection closed before receiving a handshake response

apinder commented 7 years ago

It's a shame to leave browser-sync behind but this one's a blocker for me, the solution to provide websockets on a separate port causes issues for me as it breaks the same origin policy on cookies and prevents authentication over ws.

Boscop commented 6 years ago

Any update on this?

ibc commented 6 years ago

Use http-proxy-middleware as WebSocket proxy. It works with browser-sync.

Boscop commented 6 years ago

But this issue suggests it doesn't work with websockets? https://github.com/chimurai/http-proxy-middleware/issues/15 How did you make it work?

ibc commented 6 years ago
const browserSync = require('browser-sync');
const proxy = require('http-proxy-middleware');

browserSync(
  {
    open      : 'external',
    host      : MY_DOMAIN,
    server    :
    {
      baseDir : MY_OUTPUT_DIR,
      middleware :
      [
        // Proxy WebSocket requests (identified by their /websocket pathname)
        proxy(
          '/websocket',
          {
            target : MY_WEBSOCKET_SERVER_URL,
            ws     : true
          }
        )
      ]
    }
  });
Boscop commented 6 years ago

Thanks a lot!

Can browser sync also work if the json api and websockets are on the same port of my server, that I want to proxy to?

How would it look like then? :)

I have '/api' for api requests and '/ws' for websockets.

daniel-shuy commented 5 years ago

@ibc your example does not work! Notice that your example is effectively the same as the OP's.

daniel-shuy commented 5 years ago

Still does not work with browser-sync 2.26.x

ibc commented 5 years ago

@ibc your example does not work! Notice that your example is effectively the same as the OP's.

Yes, it does work.