기본적인 운영 웹서비스 위에 node.js의 socket.io를 이용하여 실시간으로 request/response를 주고받아 화면에 표현하는 프로젝트들이 있었는데, 한번은 Nginx(Reverse Proxy Server), 한번은 Apache로 운영하는 환경에서 작업을 하게 됐습니다.
프로젝트를 위해서는 front 화면에서 socket.io 연결 시 CORS 정책도 해소 될 겸, 메인 운영 서비스 도메인의 하위 도메인에 node.js를 연결하기로 되었습니다.
막상 그냥 프록시만 연결하면 될 줄 알았는데,
WebSocket connection to 'ws://.../socket.io/?EIO=...' failed: Error during WebSocket handshake: Unexpected response code: 400.
같은 에러가 브라우저 콘솔에서 발생합니다.
프록시로 socket.io 연결 시, 서버설정 파일에 웹소켓의 프록시 연결에 관련하여 따로 해야되는 설정이 필요합니다.
기본적으로는 가상호스트로 도메인을 연결하는 환경입니다.
참고할 점
1. mod_proxy와 mod_proxy_wstunnel 모듈이 enabled 되어야 합니다.
2. example.com 는 메인 운영 웹서비스와 연결됩니다.
3. example.com/node 는 node.js 서비스와 연결됩니다.
4. node.js의 서비스는 localhost의 8080 포트로 서비스되고 있습니다.
<VirtualHost *:80>
ServerAdmin root@localhost
ServerName example.com
DocumentRoot /my/document/root
RewriteEngine On
RewriteCond %{REQUEST_URI} ^/node [NC]
RewriteCond %{QUERY_STRING} transport=websocket [NC]
RewriteRule /node/(.*) ws://localhost:8080/$1 [P,L]
ProxyPass /node http://localhost:8080
ProxyPassReverse /node http://localhost:8080
ErrorLog "logs/example_site__error_log 86400"
</VirtualHost>
<VirtualHost *:443>
ServerAdmin root@localhost
ServerName example.com
DocumentRoot /my/document/root
RewriteEngine On
RewriteCond %{REQUEST_URI} ^/node [NC]
RewriteCond %{QUERY_STRING} transport=websocket [NC]
RewriteRule /node/(.*) ws://localhost:8080/$1 [P,L]
ProxyPass /node http://localhost:8080
ProxyPassReverse /node http://localhost:8080
SSLEngine on
SSLProtocol all -SSLv2 -SSLv3
SSLCipherSuite DEFAULT:!EXP:!SSLv2:!DES:!IDEA:!SEED:+3DES
SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem
ErrorLog "logs/example_site__error_log 86400"
</VirtualHost>
RewriteEngine On
RewriteCond %{REQUEST_URI} ^/node [NC]
RewriteCond %{QUERY_STRING} transport=websocket [NC]
RewriteRule /node/(.*) ws://localhost:8080/$1 [P,L]
참고문서 👉stackoverflow - WebSockets and Apache proxy : how to configure mod_proxy_wstunnel?
upstream origin {
server origin-server;
}
upstream nodejs {
server nodejs-server;
}
server {
listen 80;
server_name example.com;
return 301 https://$server_name$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/privkey.pem;
ssl_stapling on;
ssl_stapling_verify on;
location / {
client_max_body_size 20M;
proxy_pass http://origin;
proxy_redirect off;
proxy_set_header Host $server_name;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /node {
rewrite ^/node(/.*)$ $1 break;
client_max_body_size 20M;
proxy_pass http://nodejs;
proxy_redirect off;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $server_name;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
참고문서 👉NGINX as a WebSocket Proxy
해당 문서를 보면 Reverse Proxy 환경에서 WebSocket를 연결했을 때 생기는 문제를 알려줍니다.
... One is that WebSocket is a hop‑by‑hop protocol, so when a proxy server intercepts an Upgrade request from a client it needs to send its own Upgrade request to the backend server, including the appropriate headers. Also, since WebSocket connections are long lived, as opposed to the typical short‑lived connections used by HTTP, the reverse proxy needs to allow these connections to remain open, rather than closing them because they seem to be idle.
리퀘스트 실시간 표현에는 socket.io를 자주 쓸 것 같으니 또 웹소켓 연결 안된다고 해당 설정 또 나중에 부랴부랴 찾기 전에 여기에 킵해두겠습니다.