前言
之前写了一个基于websocket的你画我猜小游戏,优势在于无广告、不限制参与人数,并在gitee上开源了:https://gitee.com/bychang/draw-and-guess。当时租了一年的云服务器,现在快到期了,所以打算迁移到自己的服务器上。
不过由于自己的服务器用了https、网站服务是部署在docker里的、并且端口不能随便开放,折腾了好半天,终于算是能用了(尽管可能不那么优雅hh)。因此写一篇博客记录一下踩坑历史。
背景
- 服务器只有两个对外开放的端口,都在2000以上。防火墙会将外部访问服务器7777端口的请求转发到服务器的2345端口,访问6666端口的请求转发到服务器的1999端口。
- 网站服务用的github上的docker-compose-lamp项目,集成好了apache、php、mysql等环境。
- 在apache(版本2.4.59)的config那里配置了四级域名转发,主机1999端口映射到容器443端口,conf文件类似如下:
conf
ServerName 172.18.0.5
SSLEngine on
SSLCertificateFile /etc/apache2/ssl/xxx_yyy_com.pem
SSLCertificateKeyFile /etc/apache2/ssl/xxx_yyy_com.key
<VirtualHost *:443>
DocumentRoot ${APACHE_DOCUMENT_ROOT}/default
ServerName draw-and-guess.xxx.yyy.com
<Directory ${APACHE_DOCUMENT_ROOT}/default>
AllowOverride all
</Directory>
</VirtualHost>
- 用vue写了个前端,用js写了个后端,前后端websocket逻辑分别为:
js
//前端
...
import { io } from 'socket.io-client';
...
const SERVER_ADDR = 'https://draw-and-guess.xxx.yyy.com:7777/';
const socket = ref(io(SERVER_ADDR, { transports: ['websocket'] }));
...
//后端
var app = require('http').createServer();
var io = require('socket.io')(app);
app.listen(2345,'127.0.0.1')
...
预期效果与现有效果
预期效果:玩家访问https://draw-and-guess.xxx.yyy.com:6666时,会自动和https://draw-and-guess.xxx.yyy.com:7777建立websocket连接,从而可以正常开始游戏。
现有效果:网站可以正常打开,但是建立连接时浏览器控制台报错 WebSocket connection to 'wss://draw-and-guess.xxx.yyy.com:7777/socket.io/?EIO=4&transport=websocket' failed
,没有进一步的报错信息。服务器后端js控制台也没有显示报错信息。
尝试1
先确保服务器可以收到消息:nc -lk 2345
然后打开网站,发现确实有显示。然后看了一下socket.io官方的排错教https://socket.io/docs/v4/troubleshooting-connection-issues/,说是用curl可以检测一下。测试curl "https://draw-and-guessd.xxx.yyy.com:7777/socket.io/?EIO=4&transport=polling"
发现回显是curl: (35) error:0A00010B:SSL routines::wrong version number
。应该是服务器端2345没有tls,所以无法连接。
尝试2
那么能不能用http呢?在curl测试curl "http://draw-and-guessd.xxx.yyy.com:7777/socket.io/?EIO=4&transport=polling"
是可以连接的,但是浏览器会报错Mixed Content: The page at 'https://draw-and-guess.xxx.yyy.com:6666/' was loaded over HTTPS, but attempted to connect to the insecure WebSocket endpoint 'ws://draw-and-guess.xxx.yyy.com:7777/socket.io/?EIO=4&transport=websocket'. This request has been blocked; this endpoint must be available over WSS.
。所以是https场景下不能使用http做websocket了。之前用华为云服务器的时候因为就没弄https,所以http是可以的。
尝试3
那么既然1999端口能提供tls,是否可以也把websocket放在这个端口呢?显然不行,因为docker在做1999->443端口映射的时候是跑了一个docker-proxy程序的,这个端口就被占用了,没法再跑websocket。
尝试4
能否在2345端口再开一个tls服务,然后处理websocket?查了下发现是可以的,只需要启用proxy模块,然后添加转发规则就行了。首先先进入apache的docker,启用proxy和wstunnel模块:
shell
cd /etc/apache2/mods-enabled
ln -s ../mods-available/proxy.load proxy.load
ln -s ../mods-available/proxy.conf proxy.conf
ln -s ../mods-available/proxy_wstunnel.load proxy_wstunnel.load
然后修改config
conf
<VirtualHost *:443>
ProxyPass /socket.io ws://127.0.0.1:2345/socket.io
ProxyPassReverse /socket.io ws://127.0.0.1:2345/socket.io
DocumentRoot ${APACHE_DOCUMENT_ROOT}/draw-and-guess/dist
ServerName draw-and-guess.xxx.yyy.com
<Directory ${APACHE_DOCUMENT_ROOT}/draw-and-guess/dist>
AllowOverride all
</Directory>
</VirtualHost>
其中的ProxyPass 和ProxyPassReverse用来转发。
结果发现apache错误日志文件报错:
log
[proxy:error] [pid 298] (111)Connection refused: AH00957: WS: attempt to connect to 127.0.0.1:2345 (127.0.0.1:2345) failed
[proxy_wstunnel:error] [pid 298] [client 192.3.118.133:42940] AH02452: failed to make connection to backend: 127.0.0.1
原来是因为apache跑在docker里,而2345端口是在宿主机监听的,docker里2345没有运行服务。
也没法把宿主机2345和容器2345绑定,这样又回到了docker-proxy的端口占用问题。
尝试5
本来顺理成章地可以在apache的docker里启动node后端,但当时想的是一种更为"优雅"的方法,就是再启动一个nodejs的docker和apache联动。试了下似乎不管怎么写command,启动以后都自己退了。折腾半天docker compose,略过不表。
尝试6
最后祭出大杀器,直接在compose配置文件把宿主机的nodejs映射到apache容器里,然后在容器内部运行js后端。为了保证控制台退出可用,研究了下screen里启动docker后怎么在不退出docker的情况下退出screen,无果。也研究了下docker exec -d xxxx xxx.sh
来试图运行一个后台执行后端程序的docker,也失败了。
最后直接在宿主机开一个screen,里面进入apache docker,手动启动后端,再直接鼠标关了终端才圆满解决(至少服务可以用了)hhh。