このプロジェクトはGO 言語で認証機能つきのチャットアプリを作ってみた。を参考にしている。最初はこのサイトの写経からはじめ、のちに不具合修正、機能追加と行った形で発展させていく。
$ go run *.go
ユーザが入室した時、ブラウザからの ws 通信は chatroom ハンドラで対処され、受信したユーザは join チャネルに入れる。これを go routine で走っている chatroom.run において対処する。
- Enter でメッセージを送れるようにする
- 認証未完了の状態でも"/chat"ページへアクセスできてしまい、それがユーザとしてカウントされ、またメッセージを送れてしまう
- チャットルーム内の各ユーザ一覧(と合計ユーザ数)を表示したい
- ルームへのメンバーの入退室をリアルタイムでクライアントのユーザ一覧を更新する
- サーバにデプロイする
- ある一つメッセージを送ったらそのユーザはルームを自動的に退出することになる(リロードで再入室する)
- データの永続化
- chatroom を複数用意する
(200205)ある一つメッセージを送ったらそのユーザはルームを自動的に退出することになる(リロードで再入室する)
今回のチャットルームへのアクセスは chatroom 構造体の ServeHTTP メソッドによってハンドリングされる。この中で以下の三点で websocket の通信が切断されていたので、これらを取り除いた。
chatroom.ServeHTTP()におけるdefer func() {c.leave <- user}()
user.read()の最後のc.socket.Close()
user.write()の最後のc.socket.Close()
[のちに追記]以上は全くもって不必要でトンチンカンな処理であった。メッセージ送信時に自動退出してしまうことに関しては他のどこかが問題になっていたようだ。上記3つはのちに復活させた。
(200205)チャットルーム内の各ユーザのプロフィール(と合計ユーザ数)を表示したい
ユーザは入室時に現在入室中のユーザ情報が(サーバーサイドで既に)レンダリングされた html を取得する(ユーザが chatroom に入室するのは chat.html 取得時ではなく、html をブラウザが受信して WS 通信をはじめた時。したがってユーザの描写はこの WS 通信開設後しか不可能)- ユーザは WS 通信を開始時に WS 通信でユーザ一覧情報を受信し、DOM 操作でページを書き換える
- ユーザが入室 or 退出した際には
その旨を_毎回 avator 一覧を_ WS でクライアントサイドに送信し、DOM 操作でページを書き換える
これを直す過程でまず以下を行った。
- avatar の URL は message ではなく、user が持つべき
- message 構造はどの user によるものなのかを持つべき
- message 送信の際には user 情報は json に含めない。user 情報は WS 接続確率時にクッキーからサーバーサイドで取り出す。接続後のメッセージはサーバーサイドに置いて user 情報と紐づけた message 構造体を作る。
終了後、以下を実装した。
- ユーザは WS 通信を開始時に WS 通信でユーザ一覧情報を受信し、DOM 操作でページを書き換える
ただし、これを実装したところ、以下の問題が生じた
- WS 通信で受信するデータが現状で「入室時のルーム内のユーザ一覧」と「新規メッセージ」の二種類になる
これに対処するために、websocket で送信する json を以下のフォーマットに統一する
- chatroom のメンバー一覧は
{'member_avatars': ['url1', 'url2'] }
- 新規メッセージは
{'new_message': '新規メッセージ'}
なお、chatroom members に関しては、自分自身の avator は表示されない(される)。これは、user が chatroom.serveHTTP()における処理の順番が以下だからである。
- cookie からユーザ自身に相当するクライアント構造体の初期化
user.send_members()で websocket 通信でメンバー一覧をブラウザに送信(取り除く)- クライアント構造体を join チャネルに追加
- join チャネルに追加されたクライアント構造体を chatroom.users に追加(ここで user.send_members していることにのちに気づく)
(200206)ユーザがチャットルームに入室した時にはチャットルーム内の他のユーザに通知したい。具体的には Chatroom members の avator 一覧を更新したい
chatroom.run()内の無限ループにおいて、join チャネルと leave チャネルとのそれぞれで新しい user 要素が追加された時に、その user と同じ chatroom に所属する全ての user に対して user.send_members を実行することで実行可能である。
(200206)ユーザ認証機能をまともにする。
現状 現時点での google,github との連携では、これらのリソースサーバからユーザ名、アバター URL を取得し、これらをブラウザのクッキーに埋め込んでいるだけであり、実質サーバーサイド側にユーザ情報は全く保持していない。この結果、ユーザがログアウトを押して再び同じアカウントでログインしたとしても、これは以前のログイン時のユーザとは紐づけられない。これは後のデータの永続化の際のネックになりうる。
アプリケーション側でusers テーブルと*sessions データベース(KV)*を作る
外部認証で得られる user_id と、このアプリケーションが保持する user_id を紐づけるためのテーブル
column | Type | Description |
---|---|---|
id | int | ユーザ ID(PK) |
name | string | NOT NULL |
avatar_url | string | NOT NULL |
provider_name | string | NOT NULL |
provided_id | string | NOT NULL |
-
ユーザが外部サービスを使い認証した際にはまずこのテーブルを参照する。(外部サービスのプロバイダ名, リソースサーバから取得した user_id)の組み合わせがこのテーブルに既存だった場合には、このテーブルに変更はなされない。もし存在しなかった場合には、その user がこのテーブルに insert される。 -
ユーザのプロフィール情報は認証のたびにリソースサーバに取りに行き、更新する。(これが一般的なやり方なのかはわからないが) -
同一セッションであってもリクエストのたびにこのテーブルは参照される。セッション情報は user_id と結びつけのみに使用され、user のプロフィールはセッション情報には保存しない。
以下に関しては今後の課題になる。
同一 user が複数のサービスを使って認証した場合、それらは別々の user として見なされる、これに対処するにはリソースサーバにアクセスした時に email を取得し、上記 users テーブルに email カラムを追加して保持することである。これによって外部サービスでの認証時にすでにその user が別のサービスで認証していて同じ email を使っていた場合には検知ができ、認証を許さないようにできる。
key が session_id, value が user_id。
- name, avatar_url は永続化せず、セッション情報として保持する。
- セッションの開始時には毎回リソースサーバにアクセスすることになる。この際に毎回 name, avatar_url を取ってくる。これをセッションに保存する。
- users テーブルは以下のようにする。
column | Type | Description |
---|---|---|
id | int | ユーザ ID(PK) |
provider_name | string | NOT NULL |
provided_id | string | NOT NULL |
- sessions 情報は gorilla/sessions を使って、(ブラウザではなく)サーバサイドの FileSystemStore に保存する。