まずはリバースプロキシを使うことになった経緯から。
開発中のWebサービスにチャット機能を実装するにあたり、Socket.IOを使う必要がありました。
しかし、セキュリティが強い(制限が強い)ネットワークから「https://example.com:8080」のようにオリジン直指定でSocket.IOサーバーへ接続しようとすると接続が弾かれるため、それを回避するためにリバースプロキシを使うことにしました。
やり方をググって、nginxの設定ファイルに変更を加えたものの、
No 'Access-Control-Allow-Origin' header is present on the requested resource
というCORSエラー連発で、Access-Control-Allow-Originヘッダーを追加しているのにも関わらず中々うまくいかず苦戦していたのですが、やっと解決法が判明し、リバースプロキシもうまく機能するようになったのでポイントも交えながら手順を解説していきます。
(何気に技術系の記事書くの初めてなので温かい目で読んでくれると嬉しいです)
前提条件
- Socket.IOサーバーは8080番ポートでListenしている
- Socket.IOクライアントで、https://example.comを接続先として指定するが、既にこのドメインは自分の別のサイトで使用中である(ブラウザーからexample.comにアクセスすると自分が運用中の別のサイトに繋がる)。つまり、リバースプロキシ用のサブドメインを設定したり、わざわざSSL証明書取ってきたり設定ファイル新しく作ったりするのが面倒なので、既存サイトのドメインを使いつつ、その設定ファイルにちょっと手を加えるだけでリバースプロキシを使えるようにする、という横着なやつですね!←
nginxの設定ファイルを書き換える
自分はnginxのバーチャルホスト機能で、複数サイトを同時に運用しているので
$ sudo nano /etc/nginx/sites-available/example.com
で設定ファイルを編集していきます。設定ファイルは環境によって異なりますので、ご自身に合ったファイルを編集してください。
server {
listen 80;
listen [::]:80;
server_name example.com;
return 301 https://$host$request_uri;
}
server {
server_name example.com;
root /path/to/example.com;
index index.html index.htm index.nginx-debian.html index.php;
location / {
try_files $uri $uri/ =404;
}
#ここから!
location /socket.io/ {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_pass http://localhost:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Access-Control-Allow-Origin "*";
proxy_set_header Access-Control-Allow-Methods "POST, GET, OPTIONS";
proxy_set_header Access-Control-Allow-Headers "DNT, X-Mx-ReqToken, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type";
proxy_set_header Access-Control-Allow-Credentials true;
}
...
...
...以下略
}
関係ない部分は端折ってますが、20行目~34行目が今回追加した部分です。
serverディレクティブの中にlocationディレクティブを追加していますが、ポイント1つ目が、リクエストURIのパスに「/socket.io/」を指定することです。
というのも、Socket.IOクライアントは
https://example.com/socket.io/?EIO=4&transport=polling
というURLに接続しに行くので、「/socket.io/」というパスに対しての導線を張ってやらないといけないというわけなんですね。
普通Socket.IOサーバーのURLを指定するとき、「https://example.com:8080」のように「/socket.io/」の部分は端折るので、locationディレクティブのリクエストURIに「ws」とか自分の好きな文字列を設定しておいて、クライアントから「https://example.com/ws/」のように指定して接続するイメージだったのが違いました。この誤解が原因で結構時間を食ってしまいました。
その下のproxy_set_headerの記述は公式ドキュメントからの引用です。
proxy_passにはnginxから見た場合のSocket.IOサーバーのURLを指定しましょう。今回は、どちらも同一サーバー上で動かしているのでアドレスはグローバルIPなどではなくlocalhostになります。
更に下のproxy_set_headerはCORS関連の記述になります。開発の都合上、Access-Control-Allow-Originを*にしていますが、セキュリティのためにもオリジンは限定しておいたほうが良いでしょう。
設定ファイルの編集・保存が終了したら、
$ sudo systemctl stop nginx
で一旦nginxを止めて、
$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
設定ファイルに記述ミスなどが起きていないことを確認し、
$ sudo systemctl start nginx
再度nginxを立ち上げます。
クライアントから接続先を指定する
サーバー側の設定が完了したので、クライアントのソースコードから接続先を指定します。
import { createContext } from "react"
import { io } from "socket.io-client"
export const socket = io("https://example.com")
export const SocketContext = createContext()
socket.io-clientをContextAPIで使っているので若干関係ない記述も入っていますが、4行目でアドレスを指定していますね。ここでポイント2つ目なのですが、接続先URLには「/socket.io/」は含めません。
さっき/socket.io/に対する導線を張ったので接続先URLに入れたくなりますが、入れません。
この辺りがややこしいですね。
接続確認してみよう
サーバー、クライアントともに設定できたところで、正常に接続できるか確認してみましょう。
意外と設定だけで満足して、肝心のSocket.IOサーバーを起動し忘れてることがよくあるので忘れずに!w
こんな感じで、Socket.IOのオブジェクトを見てみるとconnectedがtrueになっていることが確認できました。
コメント