はじめに
Next.js と Go を利用して WebSocket 通信を行なってみます。
環境構築は前回記事を参照してください。
今回は実際に WebSocket 接続を行い、ローカル環境で双方向通信を実現させます。
このように form にデータを入力し、サーバーサイドでデータを加工して受け取ることをゴールとします。
また、今回実装したコードは上記リポジトリに公開しています。
対象読者
- WebSocket 通信を用いて双方向通信を行いたい人
WebSocket とは?
ウェブ上で双方向通信を行うためのプロトコルの一種です。
ウェブ上では通常 HTTP プロトコルを用いますが、それですと一回のやり取りで一つのレスポンスしか返せない、サーバーサイド自らが通信できない等制約があります。
そうした制約を回避するために WebSocket 通信が生まれました。
WebSocket を用いることでリアルタイムで双方向通信を手軽に実現することができます。
WebSocket を用いて接続させる
それでは早速実装を行なっていきます。
以下の様な手順で WebSocket 通信を実現させます。
- クライアンドとサーバーを接続
- クライアントサイド実装
- サーバーサイド実装
- 疎通確認
- データを送信して双方向通信を行う
- form 作成
- view に表示させる
クライアンドとサーバーを接続
上記記事で環境構築を行うと localhost:3000
でクライアンドサイドのサーバーが。localhost:8080
でサーバーサイドが立ち上がる状態になっているはずです。ただ、それだけだとお互い独立してサーバーが立ち上がっているだけですので、localhpst:3000
と localhost:8080
を明示的に接続する必要があります。
ですのでまずこの両者の接続を行います。
クライアンドサイド実装
-
まずは next.js デフォルトの画面を消して、
index.tsx
をすっきりさせます。index.tsximport type { NextPage } from 'next' const Home: NextPage = () => { return ( <h1>WebSocket</h1> ) } export default Home
-
JS 標準の APIである WebSocket を使ってサーバーサイドと接続します。
-
socket.io ライブラリも当初使ってみましたが、Go server 側とうまく接続できなかったので標準を使用します。
new WebSocket('<接続する相手側のpath>')
-
このようにインスタンス化させると指定した path と WebSocket 通信を試みます。
-
今回はサーバー側のポート番号を8080にして
/socket
で接続できる様にします。index.tsximport type { NextPage } from 'next' const Home: NextPage = () => { const socket = new WebSocket('ws://localhost:8080/socket') return ( <h1>WebSocket</h1> ) } export default Home
-
-
useRef
を用いて値を保持できる様にします。-
ただ上記のように単純に定数に入れてもうまくいきません。なぜなら
new WebSocket
した瞬間に WebSocket 通信が始まるため、この様に定義するとレンダリングされるたびに WebSocket 通信が初期化されてしまいます。 -
なので
useRef
を使います。 useRef を使用すればコンポーネントが再レンダリングされても値を保持してくれます。index.tsxconst Home: NextPage = () => { + const socketRef = useRef<WebSocket>() + const [isConnected, setIsConnected] = useState(false) + useEffect(() => { + socketRef.current = new WebSocket('ws://localhost:8080/socket') + console.log(socketRef) + socketRef.current.onopen = function () { + setIsConnected(true) + console.log('Connected') + } + + socketRef.current.onclose = function () { + console.log('closed') + setIsConnected(false) + } + }, []) return ( <h1>WebSocket is connected : {`${isConnected}`}</h1> ) }
-
ついでに useState で接続できているかどうかの値を入れて確認できる様にしておきましょう。
-
当然現状では server 側では何もしていないので接続もできません。
-
-
useEffect
の中に unmount 時の処理も追加します。- socketRef.current は undefined の可能性もあるので
?
も入れておきます。index.tsxreturn () => { socketRef.current?.close() }
- socketRef.current は undefined の可能性もあるので
最終的なコードは以下のようになります。
import type { NextPage } from 'next'
import { useEffect, useRef, useState } from 'react'
const Home: NextPage = () => {
const socketRef = useRef<WebSocket>()
const [isConnected, setIsConnected] = useState(false)
useEffect(() => {
socketRef.current = new WebSocket('ws://localhost:8080/socket')
socketRef.current.onopen = function () {
setIsConnected(true)
console.log('Connected')
}
socketRef.current.onclose = function () {
console.log('closed')
setIsConnected(false)
}
return () => {
if (socketRef.current == null) {
return
}
socketRef.current.close()
}
}, [])
return (
<>
<h1>WebSocket is connected : {`${isConnected}`}</h1>
</>
)
}
export default Home
これでクライアントサイドの実装は終わりです。これからサーバーサイドを実装し isConnected
に true
が入る様にします。
サーバーサイド実装
それでは go server 側の処理を書いていきます。
-
path の追加を行います。
serve.gofunc main() { ・・・省略・・・ e.GET("/socket", handleWebSocket) }
- この様に追加することで
/socket
に接続された時にhandleWebSocket
メソッドが発火する様にします。
- この様に追加することで
-
handleWebSocket
メソッドの中身を記述します。serve.goimport ( "fmt" "log" "net/http" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "golang.org/x/net/websocket" ) func handleWebSocket(c echo.Context) error { log.Println("Serving at localhost:8000...") websocket.Handler(func(ws *websocket.Conn) { defer ws.Close() // 初回のメッセージを送信 err := websocket.Message.Send(ws, "Server: Hello, Next.js!") if err != nil { c.Logger().Error(err) } for { // Client からのメッセージを読み込む msg := "" err := websocket.Message.Receive(ws, &msg) if err != nil { log.Println(fmt.Errorf("read %s", err)) c.Logger().Error(err) } // Client からのメッセージを元に返すメッセージを作成し送信する err = websocket.Message.Send(ws, fmt.Sprintf("Server: \"%s\" received!", msg)) if err != nil { c.Logger().Error(err) } } }).ServeHTTP(c.Response(), c.Request()) return nil }
-
handleWebSocket
メソッドの中身はこの様に記述します。受け取った msg をもとにメッセージを送信するようにします。
-
-
EOF error が返ってきた時にループ処理を抜ける様にします。
2022/07/23 11:46:41 2022/07/23 11:46:41 read EOF {"time":"2022-07- 23T11:46:41.0074853Z","level":"ERROR","prefix":"echo","file":"serve.go","line":"31","message":"EOF"}
上記の実装だとこのようなエラーが出るので EOF が返ってくるときはループ文を抜ける様にします。
if err != nil {
+ if err.Error() == "EOF" {
+ log.Println(fmt.Errorf("read %s", err))
+ break
+ }
log.Println(fmt.Errorf("read %s", err))
c.Logger().Error(err)
}
これでサーバーサイドの実装が完了しました。続けて実際に接続できるか確認してみましょう。
疎通確認
-
サーバーを起動して接続確認をします。
$ cd backend $ go run serve.go
-
別ターミナルを開き、クライアントサイドのサーバーも起動します。
$ cd front $ yarn dev
-
http://localhost:3000 にアクセスします。
この様な画面が表示されていれば server 側と WebSocket 通信ができています。
データを送信して双方向通信を行う
接続ができたので実際にデータを送って双方向通信ができているか試してみましょう。
form の作成
socketRef.current?.send('送りたいデータ')
このように send
メソッドを用いると server 側にデータを送ることができます。
-
form を作って入力した値をボタンを押すことで送信できるようにしましょう。
<form onSubmit={sendData}> <input type="text" name="socketData" /> <button type="submit">Server に送信</button> </form>
-
submit 時に
sendData
メソッドを発火させます。const sendData = (event: any) => { event.preventDefault() socketRef.current?.send(event.target[0].value) }
-
分かりやすいように state に値を保存して view に表示させる様にします。
const [formMessage, setFormMessage] = useState('')
const [sentMessage, setSentMessage] = useState('')
最終的なコードは以下になります。
import type { NextPage } from 'next'
import { useEffect, useRef, useState } from 'react'
const Home: NextPage = () => {
const socketRef = useRef<WebSocket>()
const [isConnected, setIsConnected] = useState(false)
const [formMessage, setFormMessage] = useState('')
const [sentMessage, setSentMessage] = useState('')
const sendData = (event: any) => {
event.preventDefault()
setFormMessage(event.target[0].value)
socketRef.current?.send(event.target[0].value)
}
useEffect(() => {
socketRef.current = new WebSocket('ws://localhost:8080/socket')
socketRef.current.onopen = function () {
setIsConnected(true)
console.log('Connected')
}
socketRef.current.onclose = function () {
console.log('closed')
setIsConnected(false)
}
// server 側から送られてきたデータを受け取る
socketRef.current.onmessage = function (event) {
setSentMessage(event.data)
}
return () => {
if (socketRef.current == null) {
return
}
socketRef.current.close()
}
}, [])
return (
<>
<h1>WebSocket is connected : {`${isConnected}`}</h1>
<form onSubmit={sendData}>
<input type="text" name="socketData" />
<button type="submit">Server に送信</button>
</form>
<h3>form message: {formMessage}</h3>
<h3>sent message: {sentMessage}</h3>
</>
)
}
export default Home
view に表示
再びサーバーを立ち上げて通信ができるか確認してみます。
form の input に適当な値を入力して Server に送信してみましょう。
socket data
というデータを送ると、サーバー側でデータを加工して返ってきます。
sent message 欄に返ってきたデータが表示されるはずです。
このような画面が表示されていれば WebSocket 通信を利用して双方向にデータをやり取りすることができる様になっています。
まとめ
Next.js と Go で WebSocket 通信ができる様になりました。この技術を使えばチャットアプリのような双方向通信を利用するサービスを生み出すことができます。
今回は localhost 同士の通信を行いましたが、 path を変更すればデプロイされたサーバー同士で通信もできる様になるはずです。
この記事を読んで少しでも WebSocket 通信について参考になれば幸いです。
参考文献