LoginSignup
5
8

More than 1 year has passed since last update.

Next.js と Go で WebSocket 通信を行おう【接続編】

Last updated at Posted at 2022-07-24

はじめに

  1. Next.js と Go で WebSocket 通信を行おう【環境構築編】
  2. Next.js と Go で WebSocket 通信を行おう【接続編】 <- 今回の記事

Next.js と Go を利用して WebSocket 通信を行なってみます。
環境構築は前回記事を参照してください。
今回は実際に WebSocket 接続を行い、ローカル環境で双方向通信を実現させます。

image.png

このように form にデータを入力し、サーバーサイドでデータを加工して受け取ることをゴールとします。

また、今回実装したコードは上記リポジトリに公開しています。

対象読者

  • WebSocket 通信を用いて双方向通信を行いたい人

WebSocket とは?

ウェブ上で双方向通信を行うためのプロトコルの一種です。
ウェブ上では通常 HTTP プロトコルを用いますが、それですと一回のやり取りで一つのレスポンスしか返せない、サーバーサイド自らが通信できない等制約があります。

そうした制約を回避するために WebSocket 通信が生まれました。
WebSocket を用いることでリアルタイムで双方向通信を手軽に実現することができます。

WebSocket を用いて接続させる

それでは早速実装を行なっていきます。
以下の様な手順で WebSocket 通信を実現させます。

  1. クライアンドとサーバーを接続
    1. クライアントサイド実装
    2. サーバーサイド実装
    3. 疎通確認
  2. データを送信して双方向通信を行う
    1. form 作成
    2. view に表示させる

クライアンドとサーバーを接続

上記記事で環境構築を行うと localhost:3000 でクライアンドサイドのサーバーが。localhost:8080 でサーバーサイドが立ち上がる状態になっているはずです。ただ、それだけだとお互い独立してサーバーが立ち上がっているだけですので、localhpst:3000localhost:8080 を明示的に接続する必要があります。

ですのでまずこの両者の接続を行います。

クライアンドサイド実装

  1. まずは next.js デフォルトの画面を消して、 index.tsx をすっきりさせます。

    index.tsx
    import type { NextPage } from 'next'
    
    const Home: NextPage = () => {
      return (
        <h1>WebSocket</h1>
      )
    }
    
    export default Home
    
  2. JS 標準の APIである WebSocket を使ってサーバーサイドと接続します。

    • socket.io ライブラリも当初使ってみましたが、Go server 側とうまく接続できなかったので標準を使用します。

      new WebSocket('<接続する相手側のpath>')
      
    • このようにインスタンス化させると指定した path と WebSocket 通信を試みます。

    • 今回はサーバー側のポート番号を8080にして /socket で接続できる様にします。

      index.tsx
      import type { NextPage } from 'next'
      
      const Home: NextPage = () => {
        const socket = new WebSocket('ws://localhost:8080/socket')
        return (
          <h1>WebSocket</h1>
        )
      }
      
      export default Home
      
  3. useRef を用いて値を保持できる様にします。

    • ただ上記のように単純に定数に入れてもうまくいきません。なぜなら new WebSocket した瞬間に WebSocket 通信が始まるため、この様に定義するとレンダリングされるたびに WebSocket 通信が初期化されてしまいます。

    • なので useRef を使います。 useRef を使用すればコンポーネントが再レンダリングされても値を保持してくれます。

      index.tsx
      const 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 側では何もしていないので接続もできません。

  4. useEffect の中に unmount 時の処理も追加します。

    • socketRef.current は undefined の可能性もあるので?も入れておきます。
      index.tsx
      return () => {
        socketRef.current?.close()
      }
      

最終的なコードは以下のようになります。

index.tsx
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

これでクライアントサイドの実装は終わりです。これからサーバーサイドを実装し isConnectedtrue が入る様にします。

サーバーサイド実装

それでは go server 側の処理を書いていきます。

  1. path の追加を行います。

    serve.go
    func main() {
    ・・・省略・・・
    
      e.GET("/socket", handleWebSocket)
    }
    
    • この様に追加することで /socket に接続された時に handleWebSocket メソッドが発火する様にします。
  2. handleWebSocket メソッドの中身を記述します。

    serve.go
    import (
        "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 をもとにメッセージを送信するようにします。
  3. 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)
}

これでサーバーサイドの実装が完了しました。続けて実際に接続できるか確認してみましょう。

疎通確認

  1. サーバーを起動して接続確認をします。

    $ cd backend
    $ go run serve.go
    
  2. 別ターミナルを開き、クライアントサイドのサーバーも起動します。

    $ cd front
    $ yarn dev 
    
  3. http://localhost:3000 にアクセスします。

スクリーンショット 2022-07-24 8.53.49.png

この様な画面が表示されていれば server 側と WebSocket 通信ができています。

データを送信して双方向通信を行う

接続ができたので実際にデータを送って双方向通信ができているか試してみましょう。

form の作成

socketRef.current?.send('送りたいデータ')

このように send メソッドを用いると server 側にデータを送ることができます。

  1. form を作って入力した値をボタンを押すことで送信できるようにしましょう。

    <form onSubmit={sendData}>
      <input type="text" name="socketData" />
      <button type="submit">Server に送信</button>
    </form>
    
  2. submit 時に sendData メソッドを発火させます。

    const sendData = (event: any) => {
      event.preventDefault()
      socketRef.current?.send(event.target[0].value)
    } 
    
  3. 分かりやすいように state に値を保存して view に表示させる様にします。

const [formMessage, setFormMessage] = useState('')
const [sentMessage, setSentMessage] = useState('')

最終的なコードは以下になります。

index.tsx
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 に送信してみましょう。

image.png

socket data というデータを送ると、サーバー側でデータを加工して返ってきます。
sent message 欄に返ってきたデータが表示されるはずです。

image.png

このような画面が表示されていれば WebSocket 通信を利用して双方向にデータをやり取りすることができる様になっています。

まとめ

Next.js と Go で WebSocket 通信ができる様になりました。この技術を使えばチャットアプリのような双方向通信を利用するサービスを生み出すことができます。
今回は localhost 同士の通信を行いましたが、 path を変更すればデプロイされたサーバー同士で通信もできる様になるはずです。

この記事を読んで少しでも WebSocket 通信について参考になれば幸いです。

参考文献

5
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
8