結論を急ぎたい人は↓へ
WebSocket のリポジトリ
https://bitbucket.org/freeonterminate/websocket/
Discord のボット作りたい
「Discord のボット作りたい!」その思いから僕の旅は始まった…
Discord Gateway API は WebSocket を使う
Delphi にも WebSocket ぐらい作ってる人いるっしょ!見たことあるし!楽勝!楽勝!
あったあった
DelphiWebsockets
https://github.com/andremussche/DelphiWebsockets
ん?何か書いてあるぞ…
Not active anymore
Unfortunately I don't have time to support this project anymore.
Also the websocket protocol has changed in the meantime, so it won't work with browser and other modern implementations.
もうアクティブではありません
残念ながら、私はもうこのプロジェクトをサポートする時間がありません。
また、その間にwebsocketプロトコルも変更されたため、ブラウザやその他の最近の実装では機能しません。
ほげえ!
あ、でも代替手段が書いてある!
Please take a look at the free (but closed) 3rd party component:
http://www.esegece.com/websockets/download
http://www.esegece.com/download/sgcWebSockets_free.zip
よっしゃー
アクセスや!
デスヨネー…
個人的使用であれば安いし(€199.00 ⇒ 約25000円)普通に買うんだけど…Discord Bot のソースはオープンにしたいんだよなあ。
これじゃあなあ…
WebSocket を実装する
こうなったら自分で実装するしかあるまい…
幸い RFC 6455 は日本語訳がある!
RFC 6455, The WebSocket Protocol (非公式日本語訳)
https://triple-underscore.github.io/RFC6455-ja.html
WebSocket の詳細については上記の文書に記載されているので詳しくは書きませんが、実装時についての注意点をいくつか。
1. IdTCPClient を使う
WebSocket は HTTP を拡張した物ですが、ネゴシエーションを HTTP ヘッダで行います。
そのため通常の THttpClient は使えず自分で HTTP を偽装しなくてはなりません。
そこで TCP Socket の出番ですが、Delphi RTL で用意されている TSocket は SSL 対応されていないため、使えません。
そこで、Indy の TIdTCPClient を使います。
ただ、TIdTCPClient で SSL 通信するためには以下のライブラリが必要です。
OS | ライブラリ1 | ライブラリ2 |
---|---|---|
Windows | libeay32.dll | ssleay32.dll |
macOS | libcrypto.dylib | libssl.dylib |
iOS | libcrypto.a | libssl.a |
Android | libcrypto.so | libssl.so |
Linux | libcrypto.so | libssl.so |
※iOS は静的リンクの必要あり |
これらの必要なライブラリはこのリンクから落とせます。
ただ、将来的に RTL の TSocket が SSL 対応されれば、そちらを使いたい(DLL が要らないため)ので、TIdTCPClient をラップして使っています。
2. ws, wss を http に偽装する
WebSocket のスキーム ws
, wss
は本質的に http
, https
です。
上記の TIdTCPClient で接続する時は ws://
や wss://
を http://
, https://
に置換します。
3. 終了時に Close コードを送出
WebSocket は終了時に Close コード 8
をコネクション相手に送る必要がありますが、これがまた難しい。
FormDestory ⇒ WebSocket.DisposeOf ⇒ SocketThread.Terminate ⇒ 8
の送出、と手順を踏みますが、Socket の都合上 Thread から 8
を送出するようになっています。
それによって、8
送出より先に WebSocket のインスタンスだったり Form のインスタンスが削除されてしまいエラーになります。
この問題を解決するため終了処理がえらいめんどくさい事になっています。
4. コンソールでは TThead.Queue は使えない
GUI アプリで言うところのメインスレッドが無いため TThread.Queue は使えません。
そのため、CreateAnonymousThread を使って遅延処理を実現しています。
5.手抜きポイント
複数フレーム対応は省略しています。
WebSocket は「フレーム」という単位で通信するのですが、送るデータの量によっては複数のフレームに分けて転送します。
分ける分けないは自由ですがフレームで保持できるデータ量を超えた場合は必ず分けないといけません。
…ですが、その分ける値というのは Unsigned Int64 で表せる 18446744073709551615
= 15 [EiB]
です。
これ超える事ある!?!?ということで、送信時の複数フレームに対する対応はしていません。
他にも 126 文字以上になった時の文字列長データの並び順とかいくつかはまったポイントがありました。
WebSocket の使い方
そんなこんなでできあがった WebSocket の簡単な使い方を紹介します。
// WebSocket の作成
procedure TfrmMain.FormCreate(Sender: TObject);
begin
FWebSocket := TWebSocket.Create(Self);
FWebSocket.OnError := WebSocketError; // エラーイベント
FWebSocket.OnText := WebSocketText; // 文字列受信イベント
end;
// WebSocket の破棄
procedure TfrmMain.FormDestroy(Sender: TObject);
begin
FWebSocket.DisposeOf;
end;
// 接続
procedure TfrmMain.Button1Click(Sender: TObject);
begin
FWebSocket.Connect('wss://echo.websocket.org');
end;
// 送信
procedure TfrmMain.Button2Click(Sender: TObject);
begin
FWebSocket.Send('Hello, WebSocket !');
end;
// 受信
procedure TfrmMain.WebSocketText(Sender: TObject; const iText: String);
begin
Memo1.Lines.Add(iText);
end;
// エラー処理(ログに出すだけ…)
procedure TfrmMain.WebSocketError(Sender: TObject; const iMessage: String);
begin
Memo1.Lines.Add('ERROR: ' + iMessage);
end;
まとめ
一度は自分で車輪を作ってみるのもいいよ!