简体   繁体   English

带有Express / Apache2 / SSL的Socket.IO

[英]Socket.IO with Express/Apache2/SSL

Alright, I've searched nearly every single question/answer I can find about this on SO, and other places online, to no avail. 好吧,我几乎搜索了我在SO和其他地方找到的关于这个的每一个问题/答案都无济于事。 So, I thought I'd post this to see if anyone has a direction to point me in. 所以,我想我会发布这个,看看是否有人有指示我的方向。

The Setup 安装程序

I've got a React app and Node/Express API, on separate subdomains. 我在单独的子域上有一个React应用程序和Node / Express API。 The React app is on www.domain.com, and the API is on api.domain.com. React应用程序位于www.domain.com,API位于api.domain.com。 The React app consumes endpoints exposed by the Express app (api.domain.com/endpoint, etc). React应用程序使用Express应用程序公开的端点(api.domain.com/endpoint等)。

Additionally, the Express API uses Socket.io for some realtime communication, and is exposed on the same port as the API itself. 此外,Express API使用Socket.io进行实时通信,并在与API本身相同的端口上公开。

All of this is served up by Apache2. 所有这些都由Apache2提供。

Relevant Code Samples 相关代码样本

My root Express file (index.js): 我的根Express文件(index.js):

const app = require('express', '4.16.4')();
const bodyParser = require('body-parser');
const cors = require('cors');
const http = require('http').createServer(app);
const io = require('socket.io')(http);
require('./sockets/chat')(io);

const db = require('./services/db.service');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cors({
  credentials: true,
}));

const projects = require('./routes/projects');
const assets = require('./routes/assets');

app.use('/projects', projects);
app.use('/assets', assets);

// Test API endpoint
app.get('/test', (req, res) => {
  res.send('You got me');
});

db.connect();

http.listen(4002, () => {
  console.log('API is ready on 4002');
});

/sockets/chat , which is imported into the above file: /sockets/chat ,导入上面的文件:

const Message = require('../models/message.model');

module.exports = (io) => {
  io.on('connection', (socket) => {
    socket.on('message', async ({ asset, from, body }) => {
      const msg = new Message({
        asset,
        from,
        body,
      });
      const message = await msg.save();
      io.emit('message', message);
    });
  });
};

The React Chat component which is handling the interface with Socket.io: 使用Socket.io处理接口的React Chat组件:

import React, { Component } from 'react';
import ChatMessage from '../ChatMessage';
import { Auth0Lock } from '../Auth';
import socketIOClient from 'socket.io-client';
import MessageService from '../../services/message.service';
import { apiUrl, loginCallback } from '../../config';

require('./styles.scss');

class Chat extends Component {
  constructor(props) {
    super(props);
    this.state = {
      messages: [],
    }
  }

  componentDidMount() {
    MessageService.getAssetMessages(this.props.assetId)
      .then((messages) => {
        this.setState({ messages })
      })
      .catch((e) => console.log(e));

    this.socket = socketIOClient(apiUrl, {transports: ['websocket']}); 
    this.socket.on('message', (message) => {
      this.setState({
        messages: [
          ...this.state.messages,
          message,
        ],
      });
    });
    this.socket.on('connect_error', (err) => {
      console.log('SocketIO connection error:', err);
    });
  }

And the Apache2 config that sits in front of the API (on the subdomain): 位于API前面的Apache2配置(在子域上):

# Added to mitigate CVE-2017-8295 vulnerability
UseCanonicalName On
<VirtualHost *:80>
        ServerName api.reviewcycle.io

        RewriteCond %{SERVER_NAME} =api.reviewcycle.io
        RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=301]

        ErrorLog ${APACHE_LOG_DIR}/error-reviewcycle-api.log
</VirtualHost>

<VirtualHost *:443>
        ServerName api.reviewcycle.io

        <Proxy *>
                Order allow,deny
                Allow from all
        </Proxy>

        RewriteEngine On
        RewriteCond %{REQUEST_URI}      ^/socket.io/            [NC]
        RewriteCond %{QUERY_STRING}     transport=websocket     [NC]
        RewriteRule /(.*)               wss://localhost:4002/$1 [P,L]

        SSLEngine On
        SSLCertificateFile /etc/cloudflare/reviewcycle.io.pem
        SSLCertificateKeyFile /etc/cloudflare/reviewcycle.io.key

        ProxyPass / http://localhost:4002/
        ProxyPassReverse / http://localhost:4002/
</VirtualHost>

The Problem 问题

When the app loads, and the module attempts to make the connection to Socket.io, it's failing like so: 当应用程序加载,并且模块尝试连接到Socket.io时,它会失败,如下所示:

在此输入图像描述

As for the detail on the network request: 至于网络请求的详细信息:

在此输入图像描述

I've tried nearly every possible thing I can think of, including a bunch of changes to the Apache config. 我几乎尝试了所有可能的事情,包括对Apache配置的一系列更改。 I feel like somewhere along the way, Apache isn't proxying the request over to Express correctly, but I'm unsure. 我觉得在某个地方,Apache没有正确地将请求代理到Express,但我不确定。

If anyone sees anything here that looks amiss, I'd be super grateful for the help! 如果有人看到任何看起来不对劲的东西,我会非常感谢你的帮助! Also happy to provide whatever other information might be necessary to track this one down! 也很乐意提供跟踪这一个可能需要的任何其他信息!

TIA! TIA!

Turns out, the answer to this was pretty straightforward. 事实证明,答案非常简单。

This line in the Apache config: RewriteRule /(.*) wss://localhost:4002/$1 [P,L] , needed to have a different protocol. Apache配置中的这一行: RewriteRule /(.*) wss://localhost:4002/$1 [P,L] ,需要具有不同的协议。 Because the internal connection to the API isn't via SSL, changing that to ws:// ended up fixing things up. 由于与API的内部连接不是通过SSL,因此将其更改为ws://最终会解决问题。

If anyone else stumbles across this, hope it helps! 如果有其他人偶然发现这一点,希望它有所帮助!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM