Nginxでecho(golang)でWebSocket 「golang.org/x/net/websocket」編
前回で単純なhtmlのHello worldは出来たので、今度はwebsocketを疎通してみます。
Golangでwebsocketを扱うにあたり、echoのサンプルでも取り上げられている二つのパッケージを試してみることにしました。
WebSocket Recipe | Echo - High performance, minimalist Go web framework
上がgo公式なのですが機能が少ない上2019年現在で3年ほど?メンテされておらず、
今は下のgolliraを使うことが推奨されているようです。
本採用はgollira予定ですが、今回はテストで上のnet/websocketを使います。
このテストはタイトル通り、
「nginx」「echo(golang)」「golang.org/x/net/websocket」でどう繋げるかということに主眼をおいています。
メインロジックには以下のサンプルチャットを使っています。
github.com
Nginx側
前回構築したgo-echoのhttpサーバのうち、
パス「/ws」をwebsocket接続するように修正します。
Unixドメインソケットはhttpと使い回しで良いようです。
参考:WebSocket proxying
vim /srv/www/nginx.conf
server { #~略~ location /ws { proxy_pass http://goecho_socket_server; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 6h; } #~略~ }
タイムアウトを設定しない場合、60秒で切断されます。
ここでは6時間に設定しています。
go-echo側
今回使わせてもらった「go-websocket-chat」のうち、main.go以外を使います。
mein.goについては自前で前回構築した物に取り入れます。
package main //~略~ // ハンドラ用のレシーバ関数 func (s *Server) handleWebScoket(c echo.Context) error { s.Handler().ServeHTTP(c.Response(), c.Request()) return nil }
go-websocket-chatの独自構造体「Server」はシングルトンであるように設計されているようです。
またイベントハンドラも専用の関数があります。
これをgo-echoのwebsocketサンプルの形式と合わせるため、Serverにレシーバを生やします。
// WebSocket ------ func main() { //~略~ s := NewServer() go s.Listen() e.GET("/ws", s.handleWebScoket) //~略~ }
あとはmain関数でServerインスタンスを作成し、
レシーバをイベントハンドラとして登録すればokです。
これでサンプルのread.meにあるhtmlを設置すれば、簡易チャットが立ち上がります。
サンプルチャットについて
本題とは逸れるのですが、このサンプルチャットはGolangの「チャンネル型」を理解する上で、個人的に非常に参考になりました。
ざっくり書くと、処理は以下のようになります。
- func main(起動時に一回のみ実行)
→ Serverインスタンスを作成
→ 「/ws」パス(サンプルのままだと「/」パス)へのリクエストをServerのハンドラに紐づけ
→ Serverゴルーチンの待ち受け処理開始(forの無限ループを行うサブプロセス) - Serverのハンドラ(server.go Handler)
→ リクエストがあったらClientインスタンス作成
→ Clientゴルーチンの待ち受け処理開始
Serverインスタンスはシングルトンなため、常に一つだけ存在します。
Clientインスタンスは「/ws」に接続があるたびに新規作成され、ゴルーチン(サブプロセス)として常駐します。
チャットですから、クライアントAさんがチャットを打ったときには、クライアントBさんにメッセージを流す必要があります。
別々のサブプロセスであるゴルーチン間でメッセージをやり取りする仕組みが、チャンネル型の変数になります。
- Aさん、Bさんが「/ws」に接続
- それぞれにClientインスタンスが作成、Clientゴルーチン開始
- Aさんが書き込み「こんにちは」
- Clientゴルーチンがwebsocketからのメッセージ(本例ではJSONデータ)を拾って解析、エラーがなければClient.readチャンネルにメッセージを送信
- ClientゴルーチンはClient.readチャンネルに来たメッセージをServerゴルーチン(別プロセス)のServer.SendAllチャンネルに送る
- ServerゴルーチンはClientからServer.SendAllチャンネルにメッセージが来たら、管理下の全Clientゴルーチンのチャンネルにメッセージを送る(Client.Write)
- ClientゴルーチンはClient.Writeメッセージを受け取ったらwebsocketクライアント(ブラウザ)にJSON形式でメッセージを送る
- Aさん、Bさんの画面に「こんにちは」と表示される
完全に自分用レベルの粗いメモになってしまいましたが…とりあえずここまで。