【Golang】自前でマルチプレクサを設定している場合のWebSocketの実装
前提
- 標準ライブラリのみでの実装です。
- WebSocketのパッケージはgorilla/websocketではなく、準標準ライブラリのgolang.org/x/net/WebSocketを使用しています。
一般的なWebSocketの実装方法
Goの準標準ライブラリでWebSocketを実装したサーバーを立てる際、http.Handle(/hoge, Handle)
を用いて、
// https://pkg.go.dev/golang.org/x/net/websocketでの実装例 func EchoServer(ws *websocket.Conn) { io.Copy(ws, ws) } func main() { http.Handle("/echo", websocket.Handler(EchoServer)) err := http.ListenAndServe(":12345", nil) if err != nil { panic("ListenAndServe: " + err.Error()) } }
とDefaultServeMux
にWebSocket用のハンドラを登録するのが一般的かと思われます。
ここで問題…
しかし、私はいくつもhttp.Handle
やらhttp.HandleFunc
を書くのがなんとなく好きでなかったため、ListenAndServe
で第二引数に'nil'を渡さず、下記のような自家製マルチプレクサを渡していました。
この場合、上記のhttp.Handle("/echo", websocket.Handler(EchoServer))
の実装では、websocket.Handler
が自前のマルチプレクサに登録されずDefaultServeMux
に登録されてしまうため、/echo
のルーティング時にWebSocketが機能しません。
// WebSocketが機能しない例(http.Handleで登録) type Mux struct{} func (mux Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/hoge": case "/hage": } http.NotFound(w, r) } func EchoServer(ws *websocket.Conn) { io.Copy(ws, ws) } func main() { http.Handle("/echo", websocket.Handler(EchoServer)) //DefaultServeMuxに登録される err := http.ListenAndServe(":12345", mux) if err != nil { panic("ListenAndServe: " + err.Error()) } }
また、下記のようにマルチプレクサ内に処理を記述しても動きませんでした。
// WebSocketが機能しない例(マルチプレクサ内にハンドル記述) type Mux struct{} func (mux Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/hoge": case "/echo": //error: websocket.Handler(EchoServer) evaluated but not used websocket.Handler(EchoServer) //ただハンドラをコピペするだけではダメ } http.NotFound(w, r) } func EchoServer(ws *websocket.Conn) { io.Copy(ws, ws) } func main() { err := http.ListenAndServe(":12345", mux) if err != nil { panic("ListenAndServe: " + err.Error()) } }
解決策
Type Handler
に実装されているServeHTTP
メソッドを付け加えると、ハンドリングがうまくいきWebSocketが機能します。イマイチはっきりした理屈はわからないのですが、http.Handle
ではハンドラのServeHTTP
メソッドを勝手に呼び出してくれるところ、今回のように関数内で使用する場合はしっかりServeHTTP(w, r)
を明示しないといけないようです。
// Websocketが起動する例 type Mux struct{} func (mux Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/hoge": case "/echo": websocket.Handler(EchoServer)).ServeHTTP(w, r) } http.NotFound(w, r) } func EchoServer(ws *websocket.Conn) { io.Copy(ws, ws) } func main() { err := http.ListenAndServe(":12345", mux) if err != nil { panic("ListenAndServe: " + err.Error()) } }