マジかよ!? WebSocketsいらないかも…代替技術で十分イケるらしい
引用元:https://news.ycombinator.com/item?id=43659370
記事の細かい点だけど、RequestIDをサーバーに送ってリクエスト/レスポンスのサイクルを作るのは、別に変なことじゃないし、ありだと思うよ。
真面目なアプリならsend(message).then(res => ...)
みたいなAPIはあって損はないんじゃないかな。
でも、確かにupgradeリクエストはわかりにくいし、WebSocketサーバーがHTTPサーバーの中に組み込まれてるのがスッキリしないのはわかる。
ヘッダーのheaders['authorization']
を読み取るmiddlewareを再利用せずに、connectionParams
っていう変なオブジェクトにアクセスして、それをリクエストヘッダーみたいに扱うんだもんね、へへ。
まあ、癖はそんなに気にならないけどね(慣れただけかも)。WebSocketのブラウザAPIは、例えばEventSourceよりは使いやすいし。
それってよくある手だよね。嫌いなプロセスのすべてのステップを細かく説明して、プロセスが複雑に見えるようにして、自分の代替案を提示して、それが単純に聞こえるようにするんだ。
例えば、サンドイッチを作る場合:冷蔵庫からパンを見つけた後、正確に2枚のパンを取り出す必要がある。適切なナイフを見つけた後、バターを均一に塗り、約2.1mmのコーティングを塗るようにしてください。すべての後、トースターを調整する必要があるでしょう!
その通り。この場合、WebSocketsの方がHTTP2よりも実装が簡単だよ。raw TCPに近くて、rawパケットを送受信するだけだし… 客観的に見て、よりシンプルで、より効率的で、より柔軟性があると思う。
リソース転送のために設計されたプロトコルで、厳密なステートレスのリクエスト-レスポンス形式のインタラクションで、サーバープッシュが後付けされたものが、双方向通信のためにゼロから構築されたものよりも単純だとはどうしても思えない。
WebSocketクライアントのバグをいくつか修正したんだけど、古いプロキシを騙して全部台無しにしないようにするための工夫に驚いたよ。
大きいのは、プロキシがレスポンスを効果的にキャッシュできないように、すべてのクライアントリクエストを’マスク’することだね。リクエストが常に変わるから。
RFCに書いてあるよ:
>https://datatracker.ietf.org/doc/html/rfc6455#section-5.3”
WebSocketsってJavaScriptでマルチコアとスレッドを使ったコードを書く唯一の方法なの?それともシングルスレッドの制限があるの?nodeみたいに動くのかな?
それってWeb Workersのこと?
>https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers…
一方、僕らは記事の最初の小さなサブヘッダーにこだわって、まともな記事の残りの部分に焦点を当てないっていう、もっと悪い戦術をとってるよね。
それに、彼らの代替案はただのライブラリじゃん。SaaSを売ってるわけじゃないんだから、意地悪くしちゃだめだよ。
>…意地悪くしちゃだめだよ。
ここってそういうサイトじゃないの?
URLチェック
みんな何かにつけて意地悪くなるんだから。
パンは冷蔵庫に入れるべきじゃないし、バター2.1mmは多すぎない?特にトーストする前に塗るならね。
バター多すぎ?そんなんじゃ生きてるって言えないよ!
サンドイッチのコードレビューこそがHNの存在意義だよね。
パンのサイズに合わせて最適なバターの量を返す関数が必要だと思うな。パンの種類ごとの吸収率とかも考慮して、バターの好みもパラメータに入れるべきかも。
その通り。作者は、WebSocketsを使えばフロントエンドのjsコードが不要になるっていうメリットを都合よく省いてるよね。著者が宣伝してるライブラリもそうだし。バックエンドはエラーメッセージをフロントエンドに送ってレンダリングさせるんじゃなくて、レンダリング済みのViewを送るべき。
トーストする前にバター塗るの?マジか。試してみる。
20年前、TCPで同じようなことやってたな。リクエストIDをサーバーがエコーバックして、クライアントが保留中のリクエストと照合するんだ。タイムアウトしたらエラーにするタイマーもあったし。C++で実装して、たいしたコード量じゃなかったよ。今のWeb開発者が複雑だとか難しいとか言うことが信じられない。ソフトウェアエンジニアリングの厳格さが失われてるんじゃないかな。
ブラウザにはリクエストとレスポンスを対応付けて、タイムアウトをチェックするシステムが既にあるじゃん。車輪の再発明は不要。
ソフトウェアエンジニアリングの問題は、ビジネス上の問題を解決するよりも、無意味なアーキテクチャに気を取られすぎることだよ。
ソフトウェアエンジニアリングの本当の問題は、無意味なアーキテクチャ宇宙飛行士ごっこに気を取られて、実際のビジネス問題を解決できなくなっちゃうことだよね。
ソフトウェアエンジニアリングには2つのテーマがあると思うんだ。
・問題領域を理解してない人が、システムが複雑すぎると文句を言う。
・問題領域を理解してる人が、システムをリファクタリングして、未熟でメンテナンスできないハックをなくし、エレガントに対応できていない要件をサポートする必要があると主張する。
君のコメントはステップ1だね。
まあ、ここではウェブサイトを作る話をしてるだけだし。自分でHTTPを実装するのはやりすぎだって。君はGoogleじゃないんだから。
エンドポイントのアルゴリズム的なセキュリティ(認証とか)を保証することと、開発者のコードとは関係ないセキュリティ問題を予測することの間には大きな違いがあると思うな。前者は可能だけど、後者は無理じゃん。作者がWebSocketのアップグレードプロセスを嫌がるのはわかるよ。どっかにゼロデイ脆弱性が潜んでてもおかしくないし。
最近、ますますそう思うようになってきたんだよね。基本的なミスを犯してるアプリとか、前の3つのフレームワークで既に修正されたことを修正しようとする新しいフレームワークとか、意味不明なUXデザインとか、昔は解決されてたエラーを出すとか。小さな会社(まあ、それは仕方ない)から数十億ドル規模の会社(もっとちゃんとすべき)まで、厳密さが足りない気がする。 最近、Rust-to-WASMのクライアントサイド状態管理が、JS版のコードでスタックオーバーフローを引き起こすような数万件のレコードを処理できるのがどれだけ素晴らしいかを比較してるフレームワークが投稿されてたんだけど… それって基本的にWS上のRPCだよね。 >ゲーム、リアルタイムな双方向インタラクション mdnドキュメントにも、特定のケースにおけるWebSocketの精神的な後継者として言及されてるよ。 ”アプリケーションが非標準のカスタムソリューションを必要とする場合は、WebTransport APIを使用する必要がある” >WebTransportはWebSocketsをHTTP/3で書き直したようなものじゃないかって? そのドキュメントのどこにWebTransportがHTTP/3のWebSocketsだって書いてあるの?共通点は双方向のストリームを提供することだけじゃん。WebTransportは信頼性の低いストリームとか色々サポートしてるし。ドキュメント読んでよ。RFC 9220もあるし。HTTP/3でWebSocketsをブートストラップすることもできるけど、それは文字通りHTTP/3のWebSocketsだよ。 “基本的に”って言ってるから、“大体”って解釈すべきじゃない?だとしたら、彼の主張はほぼ正しいんじゃない? >RequestIDをサーバーに送ってリクエスト/レスポンスのサイクルを得るのがおかしいとかありえないとか言うのは違うと思うな。 レスポンスがタイムアウトするまで待つライブラリが必要だよね。 WebSocketのプログラミング経験は少ないんだけど、ping pongの仕組みはプロトコルに組み込まれてると思ってた。タイムアウトはあるの?アプリケーション層で役立つの? クライアントまたはサーバー側の並行処理モデルをめちゃくちゃにして、待機中のものとは別に応答できない場合にのみ、自分で実装する必要があるよ。 Discordは独自のheartbeatメカニズムを実装してる。WebSocketネイティブのpingはなんか信頼性がないって聞いた。WebSocket接続は大丈夫でも、アプリケーション層で何か問題が発生した場合かな? これってどうなんだろうね?HTTP streamingってそういう使い方のために設計されたものじゃない気がするんだ。 >HTTP streamingってそういう使い方のために設計されたものじゃない気がするんだ WebSocketよりSSEの方がシンプルで好きだな。HTTPベースだから、HTTP関連の技術とかツールがそのまま使えるし、WebSocketみたいに特別な設定もいらない。curlとかnetcatもそのまま使えるし、特別なclientもいらない。CDNの設定も、bufferingを切るだけでOK。 「全部うまくいく」って言うけど、scripting supportが必要なserverを自分で作る必要があったじゃん。 「全部うまくいく」ってのは、こういうことだよ。 HTTPの上に薄いlayerを重ねた、lobotomized WebSocket実装もできるよ。 一方向のeventしか気にしないなら、全然良い解決策だと思うけど、そうじゃない場合は、SSEと普通のHTTP callを組み合わせると、ちょっと面倒になるかもね。長生きHTTP connectionを使ったとしても。 >If you only care about events in one direction, it’s a perfectly fine solution” 双方向通信が必要な場合は別だよ。例えば、request/responseのping-pongとか。それはWebSocketで解決できるけど、SSE+requestだと難しい。client requestが同じSSE serverに届くとは限らないしね。回避策はあるけど、複雑になるよね。 WebSocketsって、cookieとかカスタムヘッダーで認証できるんじゃないの? そりゃそうでしょ。WebSocketsでHTTPヘッダーとかcookieを使った認証が必要なら、ヘッダー全部入ったHTTPリクエストが必要になるよね。よくあるケースだし、そういう設計にするのは簡単だよ。 へー、なんか例とかある?WebSocketsをそういうコンテキストで使ったことないけど、アプリケーションサーバーにどう渡されるのかいつも気になってたんだよね。 TCPのせいで、大きいデータは必ず小さく分割されるんだよね。HTTPレベルだと見えないけど。UDPだと自分でプロトコル設計する必要がある。ソケットコーディングした経験からすると、WebSocketsはハイエンドなブラウザゲームとか、シミュレーション、トレーディングシステムに良いと思う。ブラウザはただの窓になるけどね。他の人が言ってるように、普通のWebアプリなら他の選択肢がいっぱいあるし。 ビデオゲームとかトレーディングで問題なのは、WebSocketsがデフォルトでTCPしかサポートしてないこと。WebRTCみたいな技術の方がずっと速いアップデートができるんだよね。 SSEの問題は、ブラウザ全体でドメインごとに6つしかコネクションできないこと。 それはHTTP/1.1の問題で、SSEだけの問題じゃないよ。WebSocketsも同じ制限がある。 HTTP/2を使えば解決するよ。もう解決済みの問題。 最近のAI/LLMブームでSSEがまた注目されてて、LLMチャットのフロントエンドはほとんどSSEを使ってるよね。HTTPサーバーフレームワークのSSEサポートもかなり改善されてる気がする。 GraphQLのsubscriptionも同じだね。 ブラウザでのSSEの制限は、やっぱり面倒だよね。 SSEとこの記事で提案されていること(すごく似てるけど)の問題点は、コネクションがめっちゃ長生きすること。 自分でSSEを再構築するメリットが分かんない。普通にSSE使えば良くない? eventsourceの問題は、標準の認証を使わないこと。JWTをクエリ文字列に入れるのは変だし、ログにトークンが漏れる可能性が高い気がする。 >どうしてるのか気になる? >標準の認証を使わないってどういうこと? SSEはHTTP GETで実装できるから、ヘッダーのJWTトークンの扱い方は変わらないよ。 たぶん間違ってるかもだけど、HTTP streamingってでかいblobをchunkに分けるためのものだと思ってた。 GPは中間のproxyとかCDNの話をしてるんだよ。レスポンスがちょっとずつ来るような長期間の接続を嫌がるかもって。client側で動くかどうかを疑ってるわけじゃないと思う。 もしそうじゃなかったら、ファイル全体を先に読み込まなくてもvideoをstreamで見れないじゃん。 それ違うと思うよ。 Netflixとかではstandardだと思うけど、普通のwebmとかmp4の<video>tagでもそうなの?そういうのはrequest1回でdownloadされるけど、fileの最初にplaybackを開始できるだけのmetadataがあるんだと思ってた。 そうだよ。 video tagで最初から最後までlinearにplaybackするつもりなら、browserはなんでbyte range requestを送るの?additionalのoverheadになるんじゃないの? >video tagで最初から最後までlinearにplaybackするつもりなら、browserはなんでbyte range requestを送るの? 俺のoriginal commentは、俺がreplyしたcommenterが言ってたことについてだよ。 >file全体のbyte range requestは、”singleのlong livedなHTTP connection”に入るんじゃないの? クライアントはファイル全部を読み込みたくないから、必要な部分だけをrange requestで取得してるんだね。 クライアントはファイル全体を要求して、不要になったらリクエストを中断すると思うよ。少なくともブラウザはそうしてる。 どっちもアリだよね。サーバーによっては、HTTPのpersistent connectionで動画データを少しずつ送り続けるのが嫌な場合もあるかも。OSレベルでTCPのsend bufferが常にいっぱいになっちゃうし。 >どっちもアリ >クライアントを自分でコントロールできるなら可能だけど、ブラウザはmp4ファイルをrequestごとにstreamしないよ。 >最近のブラウザはそうしてると思うけど なるほど、勘違いしてたかも。range request basedのHLS playlistとごっちゃにしてたみたい。ありがとう! 実際にはTCP bufferから直接読み込むことはないはず。appとTCPの間にはTLSがあるし、TLSがbufferingしてくれるはず。 それって結局、application layerのsmall bufferってことじゃない?TCP receive bufferにあったものが、application layerに移るだけじゃないかな。 そうだね、TLSの話をするのに慣れすぎてる。bufferingなしでsmall readをするとsyscallのcostもかかるから、ごめんxDもっとコメントを表示(1)
…JS版のスタックオーバーフローは簡単に修正できて、JS版もかなりうまく動いたんだよね。
この記事はいろんなトピックをごっちゃにしてる気がする。もしWebSocket接続がSSE+POSTリクエストで簡単に置き換えられるなら、確かにWebSocketは必要ないかもね。でも、ゲームとか、リアルタイムな双方向インタラクションが必要なものとか、非常に有効なユースケースはたくさんあるよ。
WebSocketは要らないよ。WebTransportをチェックしてみて。
https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_…
説得力のあるユースケースだね。標準のものがあるのに、なんで非標準のカスタムソリューションを使うんだ!
違うよ。
俺が引っかかるのは、“レスポンス”が来なかったらどうするかってこと。WebSocketプロトコルにはメッセージを承認する必要があるなんて書いてないし。リクエスト/レスポンスならクライアントはネイティブにそのケースを処理できる。
>WebSocketのブラウザAPIはEventSourceより使いやすい。
具体的に何が?
TCPが独自のping/pongを送ってるのに、keepaliveのためにWebSocket上でアプリ層のping/pongを実装する必要があるのと同じようにね。-_-
EventSourceについては、正確には覚えてないけど、いつも何か問題が起こるんだよね。同じことがWebSocketにも言えるけど。再接続/バックオフのロジックを実装するのも面倒だし。
記事で提案してることを試してみる時が来たかも。
参考:https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_…
たぶん間違ってるかもだけど、HTTP streamingって大きいデータをchunkに分けるためのものだと思ってるんだよね。このパターンでstreamingをpub/subみたいに使うと後悔しそう。HTTP intermediary(NGINXとかCloudFlareとか)はこういうトラフィックパターンを想定してないんじゃないかな。それに、streamが開いてる時にWiFiが切れるたびに、fetch APIがエラーを出すと思うよ。
でも、WebSocketがいろんな場面で使われすぎてるのは確かだと思う。Server-Sent Eventsの方がシンプルで良い場合も多いよね。SSEがもっと注目されても良かったのに。
>server-sent events are a simpler solution”
Server-Sent EventsはHTTP Streamingの上のprotocolだよ。
この記事でSSEに触れてないのがちょっと意外。代わりに、SSEのローレベル版みたいなのを自分で作ってるみたいに見える(素人目だけど)。chunkをpackageの区切りにするのはちょっと変な気がするな。大きいresponseは複数のchunkに分割されたりしないか心配。
WebSocketはL7 stackを全部作り直すみたいな感じで、Upgradeとかtext/data frameとか、いろいろ設定が必要になる。それに、HTTPモードから外れると、認証とかredirectとかsessionとか、基本的な機能を全部自分で実装しないといけなくなる。
だから、Tiny SSEっていう、Rustで書かれたSSE serverを作ったんだ。Luaでprogramできるよ。
俺的には「全部うまくいく」って言うなら、Apacheが最初からsupportしてて、config fileをちょっといじるだけでclient IPにmessageを送れるようになるって意味だと思う。
while true; do
curl example.com/sse | handle-messages.sh
done
ただのtext-over-httpだからね。WebSocketだと、custom clientとlayer 7 protocol stackがないと無理だよ。
そう考えると、SSEとWebSocketは全く同じなんだ。HTTP requestをずっと開いてるだけ。firewallとかnetwork機器から見ると、両方とも同じに見える。長生きしてるHTTP requestに見えるんだ。だって、そうなんだから。もっとコメントを表示(2)
>once you’re out of “HTTP mode” you now have to implement the primitive mechanics of basically everything yourself, like auth, redirects, sessions, etc.”
WebSocketはcookieとかcustom headerで認証できるんじゃないの?
clientがserverにrequestを送るのは、普通のHTTPで解決済みの問題じゃない?そこが難しいとは思えないんだけど。
実装によるけど、CDNとかAPIゲートウェイでHTTPコネクション切って、ただのTCPソケットとしてバックエンドに転送するシステムだと、HTTPの情報が残らない場合があるよ。
WebSocketの認証は、普通HTTPリクエストの認証と全く同じくらい簡単だよ。だって全く同じなんだもん。
WebSocketsにも使い道はあると思うよ。SSEがすぐ使えない場合とか、連続で短い通信を送りたい時に、ブラウザが自動でパイプライン処理してくれるかどうかわからない場合とか。
ただ、SSEで便利なことをしようとすると、仕様に準拠しないことをしないといけないのが残念だよね(例えば、POSTで最初のデータを送るとか)。
サーバーレスアーキテクチャだと、WSよりSSEの方が使いやすいのもあるかも。LambdaとかAPI Gatewayでそういうのやりたいなら、最初から考えてないと大変なことになるよ。
OpenAIはコールバックにSSEを使ってる。チャットとか中くらいの長さのやり取りならいいんだけど、ファインチューニング(すごい時間かかることある)だと、SSEがいつも壊れて、クライアント側でリトライしないとダメになるんだよね。
だから、ロングポーリング + HTTPストリーミング(SSEをちょっと変えたやつ)みたいなのを使ってみたらどうかな?
1) 標準のGETで/api/v1/eventsを呼ぶ(認証とかも普通に)
2) バッファ/キューに何かあったらすぐ返す
3) 新しいイベントを最大60秒ストリームする。各イベントにはシーケンスID(記事みたいに)をつける。メッセージがない場合は、10秒ごとにキープアライブメッセージを送る。
4) 60秒経ったらコネクションを切る。クライアント側でちゃんと終わらせる。
5) クライアントは最後に受け取ったシーケンスを使って、別のGETリクエストを送る。
これのいいところは、すごく分かりやすい(SSEみたいに)、レイテンシーが低い、普通のGETで認証も普通にできる、ロードバランサーとかの設定に関係なく動くってこと。もちろん、たまにエラーは出るだろうけど、タイムアウトとかのエラーに対処するのが普通にはならないと思う。
>SSEはいつも壊れて、クライアント側でリトライしないとダメになるって言うけど
ブラウザが自動でやってくれるじゃん。SSEはマジで簡単に始められるよ。
どうしてるのか気になる。
あと、クライアントはブラウザだけじゃないし(OpenAI/ファインチューンの例はブラウザじゃない)。
最後に、問題解決のために裏で何か動いてるせいで、しょっちゅう失敗するってのが嫌なんだよね。ログのエラーとか警告は意味を持ってほしい。
Cookieでうまくいくよ。ブラウザでは普通の認証方法だし。
>クライアントはブラウザだけじゃないし(OpenAI/ファインチューンの例はブラウザじゃない)。
それはそうかも。でも、ブラウザベースのクライアントの負担を減らして、(自分で仕様を書くのを避けて)既存の技術を使った方が簡単だと思う。それに、言ってることはSSEと矛盾しないよ。サーバーが60秒ごとにコネクションを切ればいいだけ。認証以外は全部カバーできる(ブラウザでBearerトークンを見たことがない。Cookieを使わないとダメだよね)。
CORSで許可されていれば、認証ヘッダーを送るwithCredentialsオプションをサポートしてるよ。もっとコメントを表示(3)
ChromeとFirefoxの場合は違うよ。試したけど、例えばunordered listの要素とかはすぐに表示される。Safariだと、”text/html” streamingは512バイトずつのchunkになるらしい。
[1] https://bugs.webkit.org/show_bug.cgi?id=265386
とは言え、proxy softwareとかCloudflareみたいなサービスが、”text/event-stream”を見たら自動的に”CDN mode”をやめて、もっとtransparentなものに切り替えるロジックを持ってても驚かないな。そんなに珍しいことじゃないし。
それ違うと思う。俺の知る限り、video streamはrangeでchunkをrequestしてて、clientがcontrolしてる部分が大きい。singleのlong livedなHTTP connectionじゃないよ。
うん、その発言は明らかに間違ってるね。HTTP Live StreamingとかMPEG-DASHみたいな、HTTPでchunkに分けるのがmainのfeatureになってるvideo formatはいくつかあるよ。
static web serverとやり取りするbrowserは、HTTP byte range requestを使ってvideoのchunkを取得して、file内の任意のpointにseekするのにも同じ仕組を使える。そのstreamingの方法は速くてsimple。fancyなtechnologyはいらない。MP4をそれで動かすには、fragmented MP4としてrenderする必要がある。
seekが必要だからじゃない?最初からplaybackするのは、0にseekするのと同じだし。
>additionalのoverheadになるんじゃないの?
ならないよ。最初のbyte range requestのrangeはfile全体(bytes=0-
)だから。
>俺の知る限り、video streamはrangeでchunkをrequestしてて、clientがcontrolしてる部分が大きい。singleのlong livedなHTTP connectionじゃないよ。
file全体のbyte range requestは、”singleのlong livedなHTTP connection”に入るんじゃないの?seekのためにearlyにterminateしてanother requestを送ることはできるけど、videoはencodeがcorrectならfile全体をdownloadする前にstartできるよね?
うん、そうなるね(もっとcorrectな言い方をすると、”singleのlong livedなHTTP request”かな。connectionとは関係ないから)。wewewedxfgdfもYesってreplyしてるし。
>seekのためにearlyにterminateしてanother requestを送ることはできるけど、videoはencodeがcorrectならfile全体をdownloadする前にstartできるよね?
うん。
クライアントを自分でコントロールできるなら可能だけど、ブラウザはmp4ファイルをrequestごとにstreamしないよ。
>OSレベルでTCPのsend bufferが常にいっぱい
flow controlがあるから問題ないはず。データはkernelに小さいchunkで送られるし、一度にファイル全体を送るわけじゃない。
最近のブラウザはそうしてると思うけど。
>flow controlがあるから問題ないはず。
flow controlを活用してるけど、サーバーのmemory usageとかconnection数によっては、大きめのchunkをdownloadしてHTTP connectionを閉じる方が効率的な場合もあるかも。無線protocolも、constant trickleよりburst transmissionの方が好きな場合が多いし。
違うよ。ブラウザはファイル全体に対してbyte range request (0-)を送って、downloadが進むにつれてtime rangeが広がっていくんだ。seekしたら、別のbyte range request (10000-)を送る。だからブラウザがsmall chunkでrequestを送ってる証拠はない。
>memory usage
flow controlがあるから、serverはsendできる以上のデータをfilesystemから読み込まない。
>concurrent open connections
HTTP/1ならそうかもだけど、今はHTTP/2-3だよ。
>無線protocol
browserがdownload speedをthrottleしてるわけじゃない。
>browserがdownload speedをthrottleしてるわけじゃない。
client側で大きめのbufferを用意して、media codecが許す最小単位じゃなくて、大きめのchunkで読み込むことで同じ効果が得られるかも。TCP bufferからMP3 frameを1つずつ読み込むと、Nagle’s Algorithmでdownloadがthrottleされちゃうけど、無線には非効率的だよね。