Nginx反向代理NodeJS实现WSS协议

Nginx站点反向代理配置

upstream nodewebsocket {
    //node监听10000端口作为websocket端口
    //这里使用443反向代理回nodejs的端口
    server 127.0.0.1:10000;
}
server{
    //监听443端口 我们配置的是http+wss
    listen 443;
    //开启ssl
    ssl on;
    //绑定域名
    server_name wss.hamm.cn;
    //配置pem
    ssl_certificate  /asserver/https/ws.hamm.cn/pem.pem;
    //配置key
    ssl_certificate_key   /asserver/https/ws.hamm.cn/key.key;
    //配置ssl版本开始
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    //配置ssl版本结束
    //ssl超时时间
    ssl_session_timeout 5m;
    location / {
        //使用nodewebsocket反向代理
        proxy_pass http://nodewebsocket;
        //代理版本1.1 防止426错误
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
	}
}

Server端Node环境中的Js代码

debug("Ready now.\n**********************************");
var Config = {
    portSocket: 10000
};
var websocket = require("nodejs-websocket"),
crypto = require('crypto'),
http = require('http');
var webSocketServer = websocket.createServer(function(conn) {
    if (!login(conn.path)) {
        conn.close();
    }
    conn.on("close",
    function(code, reason) {
        debug("客户端连接失败(用户断开)");
    });
    conn.on("error",
    function(code, reason) {
        debug("客户端连接失败(用户断开)");
    });
    conn.on("text",
    function(msg) {
        if(msg=="heartBeat"){
            debug("heartBeat");
            return;
        }
        try {
            var msgObj = JSON.parse(msg);
            var account = msgObj.account;
            var channel = msgObj.channel;
            var ticket = msgObj.ticket;

            if (sha1("account" + account + "channel" + channel) !== ticket) {
                debug("用户身份验证失败");
            } else {
                switch (msgObj.type) {
                case 'system':
                    webSocketServer.connections.forEach(function(conn) {
                        conn.sendText(msgObj.msg);
                    });
                    break;
                case 'channel':
                    webSocketServer.connections.forEach(function(conn) {
                        var query = new QueryString(conn.path);
                        if (query.channel == msgObj.to) {
                            conn.sendText(msgObj.msg);
                        }
                    });
                    break;
                case 'chat':
                    webSocketServer.connections.forEach(function(conn) {
                        var query = new QueryString(conn.path);
                        if (query.account == msgObj.to) {
                            conn.sendText(msgObj.msg);
                        }
                    });
                    break;
                default:
                    debug("未知的消息类型" + msgObj.type);
                    return;
                }
            }
            //debug(msg);
        } catch(e) {
            debug("消息类型解析失败");
            return;
        }
    });
});
webSocketServer.listen(Config.portSocket);
debug("服务启动成功(" + Config.portSocket.toString() + ")Websocket");
checkConnection();
function checkConnection() {
    setTimeout(function() {
        debug("当前在线连接数:(" + webSocketServer.connections.length + ")");
        checkConnection();
    },
    10000);
}

function getTime() {
    var now = new Date();
    var hours = now.getHours();
    var minutes = now.getMinutes();
    var seconds = now.getSeconds();
    if (hours < 10) {
        hours = "0" + hours;
    }
    if (minutes < 10) {
        minutes = "0" + minutes;
    }
    if (seconds < 10) {
        seconds = "0" + seconds;
    }
    return hours + ":" + minutes + ":" + seconds;
}

function debug(message) {
    console.log(getTime()+" : "+message);
}

function login(url) {
    var query = new QueryString(url);
    if (sha1("account" + query.account + "channel" + query.channel) == query.ticket) {
        debug("客户端连接成功 "+query.account);
        return true;
    } else {
        debug("客户端连接失败:登录失败)");
        return false;
    }
}

function QueryString(url) {
    var name, value;
    url = url.replace("/?", "");
    var arr = url.split("&"); //各个参数放到数组里
    for (var i = 0; i < arr.length; i++) {
        num = arr[i].indexOf("=");
        if (num > 0) {
            name = arr[i].substring(0, num);
            value = arr[i].substr(num + 1);
            this[name] = value;
        }
    }
}

function sha1(str) {
    var sha1 = crypto.createHash("sha1"); //定义加密方式:md5不可逆,此处的md5可以换成任意hash加密的方法名称;
    sha1.update(str);
    var res = sha1.digest("hex"); //加密后的值d
    return res;
}

网页端HTML5代码实现Websocket连接

<?php
$ticket="";
$account="";
$channel=""; 
if(!isset($_GET['ticket']) || !isset($_GET['account']) || !isset($_GET['channel'])){
    if(!isset($_COOKIE['ticket']) || !isset($_COOKIE['account']) || !isset($_COOKIE['channel'])){
        $account= "Guest-".rand(1000,9999);
        $channel = 0;
        $ticket=sha1( "account".$account. "channel".$channel);
    }else{
        $ticket=$_COOKIE['ticket'];
        $account=$_COOKIE['account'];
        $channel=$_COOKIE['channel']; 
    }
}else{
    $ticket=$_GET['ticket'];
    $account=$_GET['account'];
    $channel=$_GET['channel']; 
    if($ticket!=sha1("account".$account. "channel".$channel)){
        setcookie("account","");
        setcookie("ticket","");
        setcookie("channel","");
        echo "签名错误";die;
    }else{
        
    }
}
setcookie("account",$account);
setcookie( "ticket",$ticket);
setcookie( "channel",$channel);
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    
    <head>
        <title>Hello World!</title>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <meta name="format-detection" content="telephone=no" />
        <meta name="apple-mobile-web-app-capable" content="yes" />
        <meta name="apple-mobile-web-app-status-bar-style" content="black">
        <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
        <link rel="stylesheet" href="/static/weui/0.4.3/style/weui.min.css">
        <link rel="stylesheet" href="/static/jquery-weui/0.8.2/css/jquery-weui.min.css">
        <style>
        body, html {
            overflow: hidden;
            font-size: 14px;
            width: 100%;
            height: 100%;
            margin: 0;
        }
        * {
            -webkit-touch-callout:none;
            -moz-touch-callout:none;
            -ms-touch-callout:none;
            touch-callout:none;
        }
        .radius-3 {
            -webkit-border-radius: 3px;
            -moz-border-radius: 3px;
            border-radius: 3px;
        }
        .radius-5 {
            -webkit-border-radius: 5px;
            -moz-border-radius: 5px;
            border-radius: 5px;
        }
        .weui_media_desc {
            margin: 0;
            padding: 0;
        }
        .weui_media_info {
            padding: 0;
            margin: 0;
        }
        .weui_tabbar_label {
            padding: 0;
            margin: 0;
        }
        .weui_tab {
            position: fixed;
            left: 0;
            right: 0;
            bottom: 0;
            height: 60px;
        }
        .space-60 {
            height: 60px;
        }
        .weui_cell_bd>p {
            margin: 0;
            padding: 0;
        }
        h4 {
            margin:0;
        }
        .space {
            height: 60px;
        }
        .clear {
            clear: both;
        }
        ul {
            margin:0;
            padding:0;
        }
        .weui_dialog {
            z-index: 1001;
        }
        .relative {
            position: relative;
        }
        .screen {
            height: 100%;
            margin:10px;
            color: lightgreen;
        }
        .nick {
            color: orangered;
        }
        .input>input {
            color: lightgreen;
            background-color: transparent;
            border: none;
            outline: none;
            font-weight: bold;
        }
        .error {
            color: orangered;
        }
        .message {
            color: yellow;
        }
        </style>
    </head>
    
    <body onclick="Game.checkFocus();">
        <div style="background:transparent;background-attachment:fixed;z-index:-1;position:fixed;left:0;right:0;top:0;bottom:0;"></div>
        <div class="screen" id="screen">
        </div>
        <script src="/static/js/fastclick.js"></script>
        <script src="//libs.baidu.com/jquery/1.10.1/jquery.min.js"></script>
        <script src="/static/jquery-weui/0.8.2/js/jquery-weui.min.js"></script>
        <script>
        if(top==self){
            $("body").css('background-color','#111');
        }
        var Game = {
            name:"Guest",
            now: "ready",
            ready: false,
            socket: null,
            channel: 0,
            dokeydown: function (e) {
                var keynum;
                var keychar;
                var numcheck;

                if (window.event) // IE
                {
                    keynum = e.keyCode
                } else if (e.which) // Netscape/Firefox/Opera
                {
                    keynum = e.which
                }
                if (keynum == 13) {
                    Game.doControl($(".inputbox").val());
                }
            },
            doControl: function (msg) {
                if(!Game.ready){
                    Game.socketError();
                }else{
                    if(msg.length==""){
                        return;
                    }
                    switch(msg){
                        case 'clear':
                            msg="我清理了我的屏幕。"
                            break;
                        case 'whoami':
                            msg="我是一个连自己是谁都不知道的呆逼。"
                            break;
                        case 'ls':
                            msg="我想查看一下目录结构,但是没有权限。"
                            break;
                        case 'mkdir':
                            msg="我想创建文件夹,但是没有权限。"
                            break;
                        case 'reboot':
                            msg="天啦,我竟然天真的想重启这台服务器,但是没有权限。"
                            break;
                        default:
                    }
                    var msgData = new Object();
                    msgData.name = '<?php echo $account;?>';
                    msgData.msg = msg;
                    Game.socket.send(JSON.stringify({
                        account: '<?php echo $account;?>',
                        channel: '<?php echo $channel;?>',
                        ticket: '<?php echo $ticket;?>',
                        type: 'channel',
                        to: '0',
                        msg: encodeURIComponent(JSON.stringify(msgData))
                    }));
                    Game.input();
                }
            },
            input: function (status) {
                $(".input").remove();
                $(".screen").append(
                    '<div class="input"><input class="inputbox" autocomplete="off" onkeydown="Game.dokeydown(event)" type="text"/></div>');
                Game.status = status;
            },
            showmsg:function(name,msg){
                $(".input").remove();
                $(".screen").append(
                    '<div class="item"><span class="nick">'+removeXss(name)+'</span> says : <font color=white>'+removeXss(msg)+'</font></div>');
                Game.input();
                Game.checkFocus();
            },
            message: function (msg) {
                $(".input").remove();
                $(".screen").append('<div class="warning">' + msg + '</div>');
            },
            error: function (msg) {
                $(".input").remove();
                $(".screen").append('<div class="error">' + msg + '</div>');
            },
            checkFocus: function () {
                $(".inputbox").focus();
                return;
				document.getElementById("screen").scrollTop = document.getElementById("screen").scrollHeight;
                setTimeout(function () {
                    Game.checkFocus();
                }, 1000);
            },
            init: function () {
                Game.now = "Connecting";
                try{
                    Game.socket = new WebSocket("wss://wss.hamm.cn/?account=<?php echo $account;?>&channel=<?php echo $channel;?>&ticket=<?php echo $ticket;?>");
                    Game.heartBeat();
                    Game.socket.onopen = function (evt) {
                        $.hideLoading();
                        $.toast("Connected");
                        Game.now = "Connected";
                        Game.message("Success : Chat server connected success!");
                        Game.ready = true;
                        var msgData = new Object();
                        msgData.name = '<?php echo $account;?>';
                        msgData.msg = "Hello ? I'm <?php echo $account;?>";
                        Game.socket.send(JSON.stringify({
                            account: '<?php echo $account;?>',
                            channel: '<?php echo $channel;?>',
                            ticket: '<?php echo $ticket;?>',
                            type: 'channel',
                            to: '0',
                            msg: encodeURIComponent(JSON.stringify(msgData))
                        }));
                        Game.input();
                        
                        // 监听消息
                        Game.socket.onmessage = function (event) {
                            //console.log(event);
                            try{
                                //Game.showmsg('who',event.data);return;
                                var obj=JSON.parse(decodeURIComponent(event.data));
                                Game.showmsg(obj.name,obj.msg);
                            }catch(e){
                                console.log("error");
                            }
                        };
    
                        // 监听Socket的关闭
                        Game.socket.onclose = function (event) {
                            Game.socketError();
                        };
                        // 监听Socket的关闭
                        Game.socket.onerror  = function (event) {
                            Game.socketError();
                        };
    
                    };
                }catch(e){
                    console.log(e)
                }
            },
            socketError:function(){
                Game.ready = false;
                clearTimeout(Game.loginTimer);
                Game.error("Error : Connection closed and it will reconnect in 5s");
                Game.checkFocus();
                setTimeout(function(){
                    Game.reconnect();
                },5000);
            },
            heartBeat: function () {
                clearTimeout(Game.loginTimer);
                Game.loginTimer = setTimeout(function () {
                    $.hideLoading();
                    if(Game.ready){
                        Game.socket.send("heartBeat");
                        Game.heartBeat();
                    }else{
                        Game.error("Error : Connection closed and it will reconnect in 10s");
                        Game.checkFocus();
                        Game.reconnect();
                    }
                }, 10000);
            },
            reconnect: function () {
                Game.init();
            },
        };
        window.onload = function () {
            Game.message("Connecting...");
            Game.init();
            Game.checkFocus();
        };
        function removeXss(str) {        
            var s = "";  
            if (str.length == 0) return "";  
            for   (var i=0; i<str.length; i++){
                switch (str.substr(i,1)){  
                    case "<": s += "&lt;"; break;  
                    case ">": s += "&gt;"; break;  
                    case "&": s += "&amp;"; break;  
                    case " ":  
                        if(str.substr(i + 1, 1) == " "){  
                            s += " &nbsp;";  
                            i++;  
                        } else{
                            s += " "; 
                        }  
                    break;  
                    case "\"": s += "&quot;"; break;  
                    case "\n": s += "<br>"; break;  
                    default: s += str.substr(i,1); break;  
                }  
            }
            return s;  
          }
        </script>
    </body>

</html>