首页 > 解决方案 > 如何让自定义节点服务器上的 socket.io 接受 CORS 请求?

问题描述

socket.io我在服务器上有一个 CORS 错误:

Access to XMLHttpRequest at 'http://dev.learnintouch.com:9001/socket.io/?EIO=3&transport=polling&t=NbAVesU' from origin 'http://dev.learnintouch.com:83' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

我确实使用了以下cors属性:

module.exports.io = socketio(httpsServer, {
  cors: {
    origin: 'http://dev.learnintouch.com:83'
  }
});

我还尝试了该*属性:

module.exports.io = socketio(httpsServer, {
  cors: {
    origin: '*',
    methods: [
      'GET',
      'POST'
    ],
    allowedHeaders: [],
    credentials: true
  }
});

但错误是完全相同的。

这是日志必须说的:

The NodeJS HTTP server [port: 9001] is listening...
{
  redis: { hostname: 'redis', port: 6379 },
  socketio: { port: 9001, sslport: 9002 },
  ssl: {
    path: '/usr/local/learnintouch/letsencrypt/',
    key: 'current-privkey.pem',
    certificate: 'current-cert.pem',
    chain: 'current-fullchain.pem'
  }
}
The virtual host DOESN'T have an SSL private key
Configuring the server for HTTP
The HTTP server is used by the healthcheck even if the socket is served on the HTTPS server
Server {
  _events: [Object: null prototype] {},
  _eventsCount: 0,
  _maxListeners: undefined,
  _nsps: Map {
    '/' => Namespace {
      _events: [Object: null prototype] {},
      _eventsCount: 0,
      _maxListeners: undefined,
      sockets: Map {},
      _fns: [],
      _ids: 0,
      server: [Circular],
      name: '/',
      adapter: [Adapter],
      [Symbol(kCapture)]: false
    }
  },
  parentNsps: Map {},
  _path: '/socket.io',
  clientPathRegex: /^\/socket\.io\/socket\.io(\.min|\.msgpack\.min)?\.js(\.map)?$/,
  _connectTimeout: 45000,
  _serveClient: true,
  _parser: {
    protocol: 5,
    PacketType: {
      '0': 'CONNECT',
      '1': 'DISCONNECT',
      '2': 'EVENT',
      '3': 'ACK',
      '4': 'CONNECT_ERROR',
      '5': 'BINARY_EVENT',
      '6': 'BINARY_ACK',
      CONNECT: 0,
      DISCONNECT: 1,
      EVENT: 2,
      ACK: 3,
      CONNECT_ERROR: 4,
      BINARY_EVENT: 5,
      BINARY_ACK: 6
    },
    Encoder: [Function: Encoder],
    Decoder: [Function: Decoder]
  },
  encoder: Encoder {},
  _adapter: [Function: Adapter],
  sockets: Namespace {
    _events: [Object: null prototype] {},
    _eventsCount: 0,
    _maxListeners: undefined,
    sockets: Map {},
    _fns: [],
    _ids: 0,
    server: [Circular],
    name: '/',
    adapter: Adapter {
      _events: [Object: null prototype] {},
      _eventsCount: 0,
      _maxListeners: undefined,
      nsp: [Circular],
      rooms: Map {},
      sids: Map {},
      encoder: Encoder {},
      [Symbol(kCapture)]: false
    },
    [Symbol(kCapture)]: false
  },
  opts: { cors: { origin: 'http://dev.learnintouch.com:83' } },
  [Symbol(kCapture)]: false
}

我的socket.io.min.js版本是:

/*!
 * Socket.IO v4.0.1
 * (c) 2014-2021 Guillermo Rauch
 * Released under the MIT License.
 */

npm安装cors并添加了一个require,但我不确定是否需要。

有关其他信息,服务器实现:

var http = require('http');
var https = require('https');
var cors = require('cors');
var connect = require('connect');
var cookie = require('cookie');
var path = require('path');
var fs = require('fs');
var redis = require('redis');
var ioredis = require('socket.io-redis');
var socketio = require('socket.io');

var utils = require('./utils.js');
var config = require('./config');

var sslKey = '';
var sslCertificate = '';
var sslChain = '';
if (fs.existsSync(config.ssl.path + config.ssl.key)) {
  sslKey = fs.readFileSync(path.resolve(config.ssl.path + config.ssl.key));
  sslCertificate = fs.readFileSync(path.resolve(config.ssl.path + config.ssl.certificate));
  sslChain = fs.readFileSync(path.resolve(config.ssl.path + config.ssl.chain));
  console.log("The virtual host HAS an SSL private key");
} else {
  console.log("The virtual host DOESN'T have an SSL private key");
}

console.log("Configuring the server for HTTP");
console.log("The HTTP server is used by the healthcheck even if the socket is served on the HTTPS server");
var httpServer = http.createServer(utils.httpHandler);
httpServer.listen(config.socketio.port, function() {
  console.log('The NodeJS HTTP server [port: ' + config.socketio.port + '] is listening...');
});

if (sslKey) {
  console.log("Configuring the server for HTTPS");
  var options = {
    key: sslKey,
    cert: sslCertificate,
    ca: sslChain,
    requestCert: false,
    rejectUnauthorized: false
  };
  var httpsServer = https.createServer(options, utils.httpHandler);
  httpsServer.listen(config.socketio.sslport, function() {
    console.log('The NodeJS HTTPS server [port: ' + config.socketio.sslport + '] is listening...');
  });
}

module.exports.io = socketio(httpsServer, {
  cors: {
    origin: '*',
    methods: [
      'GET',
      'POST'
    ],
    allowedHeaders: [],
    credentials: true
  }
});
console.log(module.exports.io);

module.exports.io.adapter(ioredis({ host: config.redis.hostname, port: config.redis.port }));
var redisClient = redis.createClient(config.redis.port, config.redis.hostname);

module.exports.io.use(function (socket, handler) {
  if (socket.request.headers.cookie) {
    socket.request.cookies = cookie.parse(decodeURIComponent(socket.request.headers.cookie));
    socket.request.sessionID = socket.request.cookies['PHPSESSID'];
    socket.request.socketSessionId = socket.request.cookies['socketSessionId'];
    console.log("Authorization attempt with sessionID: " + socket.request.sessionID + " and socketSessionId: " + socket.request.socketSessionId);
    redisClient.get("PHPREDIS_SESSION:" + socket.request.sessionID, function (error, reply) {
      if (error) {
        console.log("The redis client had an error: " + error);
        return handler(new Error('The connection was refused because the redis client had an error.'));
      } else if (!reply) {
        console.log('The connection was refused because the redis client did not find the sessionID.');
        return handler(new Error('The connection was refused because the redis client did not find the sessionID.'));
      } else {
        var redisSocketSessionId = utils.getRedisValue(reply, "socketSessionId");
        if ('undefined' == typeof socket.request.socketSessionId || redisSocketSessionId != socket.request.socketSessionId) {
          console.log('The connection was refused because the socketSessionId was invalid.');
          return handler(new Error('The connection was refused because the socketSessionId was invalid.'));
        } else {
          console.log('The connection was granted.');
          handler();
        }
      }
    });
  } else {
    console.log('The connection was refused because no cookie was transmitted.');
    return handler(new Error('The connection was refused because no cookie was transmitted.'));
  }
});

更新:utils.js文件:

var formidable = require('formidable');

Array.prototype.contains = function(k, callback) {
  var self = this;
  return (function check(i) {
    if (i >= self.length) {
      return callback(false);
    }
    if (self[i] === k) {
      return callback(true);
    }
    return process.nextTick(check.bind(null, i+1));
  }(0));
};

module.exports.isEmpty = function(obj) {
  for(var prop in obj) {
    if(obj.hasOwnProperty(prop))
      return false;
  }
  return true;
}

module.exports.getRedisValue = function(data, name) {
  var redisBits = data.split(";");
  for (var i in redisBits) {
    if (redisBits.hasOwnProperty(i)) {
      if (redisBits[i].substring(0, name.length) == name) {
        var value = redisBits[i].split("|")[1].split(":")[2].replace("\"", "").replace("\"", "");
        return(value);
      }
    }
  }
};

// Handle http requests sent to the Node.js server
module.exports.httpHandler = function(req, res) {
  switch(req.url) {
    case '/ping':
      if (req.method == 'GET') {
//        console.log("Received a [200] " + req.method + " to " + req.url);
        res.writeHead(200, {'Content-Type': 'text/plain'});
        res.end('');
      }
      break;
    case '/push':
      if (req.method == 'POST') {
//        console.log("Received a [200] " + req.method + " to " + req.url);
        form = new formidable.IncomingForm();
        form.parse(req, function(e, fields, files) {
          res.writeHead(200, {'Content-Type': 'text/plain'});
          res.end('');
          httpHandleServerPostRequest(fields);
        });
      }
      break;
    default:
      send404(res);
  };
};

send404 = function(res) {
  res.writeHead(404);
  res.write('404');
  res.end();
};

标签: socket.iocors

解决方案


我在我的 HTTP 服务器处理程序中添加了标头,它解决了这个问题:

// Handle http requests sent to the Node.js server
module.exports.httpHandler = function(req, res, next) {
  // Allow CORS
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader("Access-Control-Allow-Headers", "X-Requested-With");

  switch(req.url) {
    case '/ping':
      if (req.method == 'GET') {
        res.writeHead(200, {'Content-Type': 'text/plain'});
        res.end('');
      }
      break;
    case '/push':
      if (req.method == 'POST') {
        form = new formidable.IncomingForm();
        form.parse(req, function(e, fields, files) {
          res.writeHead(200, {'Content-Type': 'text/plain'});
          res.end('');
          httpHandleServerPostRequest(fields);
        });
      }
      break;
    default:
  };
};

推荐阅读