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 += "<"; break;
case ">": s += ">"; break;
case "&": s += "&"; break;
case " ":
if(str.substr(i + 1, 1) == " "){
s += " ";
i++;
} else{
s += " ";
}
break;
case "\"": s += """; break;
case "\n": s += "<br>"; break;
default: s += str.substr(i,1); break;
}
}
return s;
}
</script>
</body>
</html>