WebSocket服务器搭建时的一些问题
在搭建自己的一个内网网站时,将这个网站通过Sakura Frp 穿了出去,因为Sakura_Frp开启隧道时打开了自动https(必须开不然访问不了网站),所以直接访问网站是https的,只不过会显示不安全(不受信任),网页还是可以正常访问的。
那么问题出在哪呢,昨天尝试在内网使用php写了一个websocket的聊天室demo,那么浏览器访问这个聊天室的时候先用https发送一个upgrade的请求,然后尝试websocket,但是通过frp的这个公网发送的请求,到了websocket会变成wss而不是ws,网页会返回状态码426,websocket尝试连接wss又不能强制转换成ws,使用frp访问网站也不能使用http访问,必须要https,所以应该需要搞一个CA证书。无法连接的报错如下:
Mixed Content: The page at ‘https://frp-xxx.xxx:port/demoPage/ ‘ was loaded over HTTPS, but attempted to connect to the insecure WebSocket endpoint ‘ws://xxx.xxx.xxx.xxx:port/chat’. This request has been blocked; this endpoint must be available over WSS. Uncaught DOMException: Failed to construct ‘WebSocket’: An insecure WebSocket connection may not be initiated from a page loaded over HTTPS.
已有配置: 使用php和Apache搭建的网站(80端口),本地的80端口通过frp-xxx.xxx:port暴露在公网上,本地开启了一个简单的websocket服务器,ws服务器监听本地8081端口。
第一次的思路 想使用Apache反向代理,因为网站是在frp-xxx.xxx:port上的,所以使用Apache代理一个frp-xxx.xxx:port/chat/,将访问这个网址时的流量转发到本地的127.0.0.1:8081。
所以第一次的代码是这样写的:
index.html: (这里没啥特殊的,放在这里可以抄一抄)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <!DOCTYPE html > <html lang ="zh" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > WebSocket Chat Room</title > <style > body { font-family : Arial, sans-serif; margin : 20px ; } #messages { width : 400px ; height : 300px ; border : 1px solid #ccc ; overflow-y : scroll; padding : 10px ; } #messageInput { width : 300px ; padding : 10px ; margin-right : 10px ; } #sendButton { padding : 10px 20px ; } </style > </head > <body > <h1 > WebSocket Chat Room</h1 > <div id ="messages" > </div > <input type ="text" id ="messageInput" placeholder ="Input Messages..." > <button id ="sendButton" > Send</button > <script src ="chat.js" > </script > </body > </html >
websocket-server.php: (放在这里也可以抄一下)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 <?php require dirname (__DIR__ ) . '/composerApp/vendor/autoload.php' ;use Ratchet \Server \IoServer ;use Ratchet \Http \HttpServer ;use Ratchet \WebSocket \WsServer ;use Ratchet \ConnectionInterface ;use Ratchet \MessageComponentInterface ;class Chat implements MessageComponentInterface { protected $clients ; public function __construct ( ) { $this ->clients = new \SplObjectStorage ; } public function onOpen (ConnectionInterface $conn ) { $this ->clients->attach ($conn ); echo "新连接! ({$conn->resourceId} )\n" ; } public function onMessage (ConnectionInterface $from , $msg ) { foreach ($this ->clients as $client ) { if ($from !== $client ) { $client ->send ($msg ); } } } public function onClose (ConnectionInterface $conn ) { $this ->clients->detach ($conn ); echo "连接 {$conn->resourceId} 已断开\n" ; } public function onError (ConnectionInterface $conn , \Exception $e ) { echo "发生错误: {$e->getMessage()} \n" ; $conn ->close (); } } $server = IoServer ::factory ( new HttpServer ( new WsServer ( new Chat ) ), 8081 ); $server ->run ();
PS:如何建立上面这样一个服务器?
首先安装Composer ,看完官方文档还是不知道咋安装就问问AI
安装完后,composer -v 有输出就说明安好了
新建一个项目文件夹,然后在文件夹里 composer init
,init的时候可以直接无脑回车,因为这里的conf配置也没啥用, 然后安装 Ratchet
1 composer require cboden/ratchet
安装好就行了,注意websocket-server.php的第一行,如果报错说什么文件找不到,就是第一行的文件夹路径错了
chat.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
/etc/apache2/sites-available/000-default.conf: Apache配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 <VirtualHost *:80> ServerAdmin webmaster@localhost DocumentRoot /xxx/html # 根据自己的设定 ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined <Location /chat> ProxyPass ws://127.0.0.1:8081/ ProxyPassReverse ws://127.0.0.1:8081/ </Location> </VirtualHost>
以上全都配置好之后,能够访问成功这个index.html页面,但是连接ws:127.0.0.1:8081是不成功的。
WHY?
使用的内网穿透工具是Sakura Frp,用frpc穿透之后,访问网站是必须要使用https的,那么,使用https连接到网站之后,Websocket是通过HTTPS运行的,意味着WebSocket连接是使用 SSL/TLS加密的,所以,对于wss的访问需要有认证的证书,但是我妹有证书啊QAQ,但是我这里也有一个问题,既然是用https访问的网站,那我用这个https网站在apache配置反向代理为啥不行呢
又看了一下为什么连接不上呢,因为这个js是相当于被浏览器加载到了本地,所以,我使用我的Windows电脑访问127.0.0.1:8081相当于访问的是我自己Windows电脑上的8081端口,那这当然是访问不到的,所以这个wsUri应该填的是公网上的一个ws服务器,所以又想了个办法,用frpc再建一条隧道,把ws服务器再穿出去。
这个方法后来试了试,当然是行得通了的。
于是代码变成了这样:
但这也有问题,安全性问题,ws服务器的地址很容易被获取到,被其他人利用,所以后期会做一些js隐藏、混淆之类的
附一个群组聊天的websocket.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 <?php require __DIR__ . '/vendor/autoload.php' ;use Ratchet \MessageComponentInterface ;use Ratchet \ConnectionInterface ;use Ratchet \Http \HttpServer ;use Ratchet \WebSocket \WsServer ;use Ratchet \Server \IoServer ;class Chat implements MessageComponentInterface { protected $clients ; protected $groups ; public function __construct ( ) { $this ->clients = new \SplObjectStorage ; $this ->groups = []; } public function onOpen (ConnectionInterface $conn ) { $this ->clients->attach ($conn ); echo "New connection! ({$conn->resourceId} )\n" ; } public function onMessage (ConnectionInterface $from , $msg ) { $parts = explode (':' , $msg , 2 ); if (count ($parts ) == 2 ) { list ($key , $message ) = $parts ; if (!isset ($this ->groups[$key ])) { $this ->groups[$key ] = new \SplObjectStorage (); } if (!$this ->groups[$key ]->contains ($from )) { $this ->groups[$key ]->attach ($from ); } foreach ($this ->groups[$key ] as $client ) { if ($from !== $client ) { $client ->send ($message ); } } } } public function onClose (ConnectionInterface $conn ) { $this ->clients->detach ($conn ); foreach ($this ->groups as $key => $group ) { if ($group ->contains ($conn )) { $group ->detach ($conn ); } } echo "Connection {$conn->resourceId} has disconnected\n" ; } public function onError (ConnectionInterface $conn , \Exception $e ) { echo "An error has occurred: {$e->getMessage()} \n" ; $conn ->close (); } } $server = IoServer ::factory ( new HttpServer ( new WsServer ( new Chat () ) ), 8080 ); $server ->run ();
对应的js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 var key = 'aaa群组' ; var value = '张三' ; var conn = new WebSocket ('ws://localhost:8080' );conn.onopen = function (e ) { console .log ("Connection established!" ); sendMessage ('Hello, this is a message to specific group!' ); }; conn.onmessage = function (e ) { console .log (e.data ); }; conn.onerror = function (e ) { console .log ('Error: ' , e); }; conn.onclose = function (e ) { console .log ('Connection closed' ); }; function sendMessage (message ) { conn.send (key + ':' + value + ':' + message); }