将Socket.io与PHP web应用程序和Nginx集成


Integrating Socket.io with a PHP web app and Nginx

我目前正在开发一款包含聊天功能的移动应用程序。API后端是用Laravel构建的,但对于聊天,我们将使用Socket.io。我们使用Let's Encrypt 的SSL证书

我很难让Socket.io正常工作。它似乎确实连接了,因为在页面加载时,"新连接"消息似乎会显示在浏览器的控制台中,但我发送的任何消息似乎都没有收到。我认为Nginx的配置可能不正确。

以下是当前最小形式的聊天脚本:

var io = require('socket.io')(9000);
io.on('connection', function (socket) {
  io.emit('connected', {'msg': 'Someone has connected'});
  socket.on('messsage', function (data) {
    io.emit('relaymessage', data);
  });
  socket.on('disconnect', function () {
    io.emit('disconnected');
  });
});

以及客户端实现:

<!DOCTYPE html>
<html>
    <head>
        <title>Laravel</title>
        <link href="https://fonts.googleapis.com/css?family=Lato:100" rel="stylesheet" type="text/css">
        <style>
            html, body {
                height: 100%;
            }
            body {
                margin: 0;
                padding: 0;
                width: 100%;
                display: table;
                font-weight: 100;
                font-family: 'Lato';
            }
            .container {
                text-align: center;
                display: table-cell;
                vertical-align: middle;
            }
            .content {
                text-align: center;
                display: inline-block;
            }
            .title {
                font-size: 96px;
            }
        </style>
    </head>
    <body>
        <div class="container">
            <div class="content">
                <div class="title">Chat</div>
                <div id="output"></div>
                <form>
                    <textarea id="message"></textarea>
                    <button id="submit">Submit</button>
                </form>
            </div>
        </div>
        <script type="text/javascript" src="//code.jquery.com/jquery-2.2.3.min.js"></script>>
        <script type="text/javascript" src="/client.js"></script>>
        <script type="text/javascript">
            var socket = io(window.location.href);
            var txt = document.getElementById('message');
            var btn = document.getElementById('submit');
            socket.on('connected', function (data) {
                console.log('New connection');
            });
            socket.on('relaymessage', function (data) {
                console.log(data);
                $('div#output').append('<p>' + data.content + '</p>');
            });
            socket.on('disconnected', function () {
                console.log('Disconnected');
            });
            $('button#submit').on('click', function (event) {
                event.preventDefault();
                var content = $('textarea#message').val();
                socket.emit('message', { content: content });
                $('textarea#message').val('');
            });
        </script>>
    </body>
</html>

这是我的Nginx配置:

server {
    listen         80;
    server_name example.com;
    return 301 https://$host$request_uri;
}
server {
    listen 443 ssl;
    server_name example.com;
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com.com/privkey.pem;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
    client_max_body_size 50M;
    server_tokens off;
    add_header X-Frame-Options SAMEORIGIN;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    root /var/www/public;
    index index.php index.html index.htm;
    location / {
        try_files $uri $uri/ /index.php?$query_string;
        gzip on;
        gzip_proxied any;
        gzip_types text/plain text/css application/javascript application/x-javascript text/xml application/xml application/xml-rss text/javascript text/js application/json;
        expires 1y;
        charset utf-8;
    }
    location ~ '.php$ {
        try_files $uri /index.php =404;
        fastcgi_split_path_info ^(.+'.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
    location /socket.io/ {
        proxy_pass http://localhost:9000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
    }
    location ~ /.well-known {
        root /var/www/public;
        allow all;
    }   
}

你知道出了什么问题吗?

最终解决了它。第一个问题是QUEUE_DRIVERBROADCAST_DRIVER都设置为redis,这打破了广播系统——它使用PUSH而不是PUBLISH。因此,我删除了QUEUE_DRIVER,消息被正确接收。

使用HTTPS和加载SSL证书所需的聊天脚本:

var fs = require('fs');
var pkey = fs.readFileSync('/etc/letsencrypt/live/example.com/privkey.pem');
var pcert = fs.readFileSync('/etc/letsencrypt/live/example.com/fullchain.pem')
var options = {
  key: pkey,
  cert: pcert
};
var app = require('https').createServer(options);
var io = require('socket.io')(app);
var Redis = require('ioredis');
var redis = new Redis();
app.listen(9000, function() {
    console.log('Server is running!');
});
function handler(req, res) {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.writeHead(200);
    res.end('');
}
io.on('connection', function(socket) {
    //
});
redis.psubscribe('*', function(err, count) {
    //
});
redis.on('pmessage', function(subscribed, channel, message) {
    message = JSON.parse(message);
    console.log('Channel is ' + channel + ' and message is ' + message);
    io.emit(channel, message.data);
});

客户端实现需要使用secure参数:

var url = window.location.protocol + '//' + window.location.hostname;
var socket = io(url, {
  'secure': true,
  'reconnect': true,
  'reconnection delay': 500,
  'max reconnection attempts': 10
});
var chosenEvent = 'room_' + room.id;
socket.on(chosenEvent, function (data) {
  console.log(data);
});

一旦我做出了这些改变,效果就很好。