Next js v13の内部通信で起きたSocket Errorの解決法(暫定)
IT技術
TL;DR
- Next jsの内部通信で
UND_ERR_SOCKET
エラーが発生。 - エラーが出た箇所で使用しているundici-fetchがバグを含んでいる可能性があり、エラーが出ているよう。
- Next js v13.4.12までバージョンを落とすとundici-fetchの代わりに、http.requestを使用するのでエラーが起きなくなる。
発生したエラー
1TypeError: fetch failed at Object.fetch (node:internal/deps/undici/undici:11576:11) at async invokeRequest (/node_modules/next/dist/server/lib/server-ipc/invoke-request.js:17:12) at async invokeRender (/node_modules/next/dist/server/lib/router-server.js:254:29) at async handleRequest (/node_modules/next/dist/server/lib/router-server.js:447:24) at async requestHandler (/node_modules/next/dist/server/lib/router-server.js:464:13) at async Server.<anonymous> (/node_modules/next/dist/server/lib/start-server.js:117:13) {
2 cause: SocketError: other side closed
3 Socket.onSocketEnd (/node_modules/next/dist/compiled/undici/index.js:1:63301)
4 Socket.emit (node:events:526:35)
5 endReadableNT (node:internal/streams/readable:1359:12)
6 process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
7 code: 'UND_ERR_SOCKET',
8 socket: {
9 localAddress: '127.0.0.1',
10 localPort: 59251,
11 remoteAddress: undefined,
12 remotePort: undefined,
13 remoteFamily: undefined,
14 timeout: undefined,
15 bytesWritten: 3641,
16 bytesRead: 297
17 }
18 }
19}
エラー発生原因
私の所属しているプロジェクトでは、先日フロントエンド側の環境をNode js v18.17.1 / Next js v13.4.19にアップグレードしたのですが、それ以後にごくたまにNext js サーバーで上記エラーが起きるようになりました。
エラーログの内容を確認するとNext jsの内部間通信時にundiciというライブラリでエラーが起こっているようでした。
最初はSocketError: other side closed
というメッセージがあるため、Keep AliveによるTCP Socketのタイムアウトタイミングのずれによるエラー(参考)なのかと思ったのですが、undiciのGithubリポジトリのイシューを確認しているとどうやらそうでもなさそうでした。
原因はどうやらundici側にありそうで、イシューなどで議論が行われていました。
fetchリクエスト時にヘッダーのTransfer-Encoding
に正しくない値がセットされるようでUND_ERR_SOCKET
エラーが起こってしまうようです。
ただ同様のイシュー上で、fetch関数のレスポンスbodyを毎リクエストごとにbody mixinのコールを行い削除する必要があると触れられており、そうすることでエラーが発生しないようなのですが依然ドキュメントにはそのことが書かれておらずこのエラーへの対処方法が謎です。
このエラーの発生原因がバグなのかどうかの判断がつきませんが、少なくとも2023/6/6時点までは同じようにbody mixinを使用しないとエラーが発生するようでした。(参考)
上記のため、バグ(?)を抱えていそうなundiciを使用しない方法はないかという方針で一旦調査してみることにしました。
ところで、ここまで何度もundici、undiciといってきましたがそもそもundiciとはなんでしょうか?
undiciとは何者か
undiciとはNode js用に開発されたHTTP/1.1クライアントです。
Node js v18から試験的に導入されたfetch関数で使用されているようです。
💡 ちなみにundiciとはイタリア語で11を意味する言葉で、HTTP/1.1 → 11 → undiciという理由で命名されたようです。 |
パフォーマンスは既存のhttpモジュールを使用しての通信より上がっているようです。(https://undici.nodejs.org/ より抜粋)
ただまだ”試験的”であるように、テストカバレッジが十分でなかったり色々とバグなどの問題を抱えているようで、
ドキュメントには-no-experimental-fetch
フラグをNode js実行時に与えてあげればundiciを使用したfetch関数を無効化できると書かれていました。
とりあえず脳死で試してみましたが、変わらずエラーがおきました。
-no-experimental-fetch
を使用してnext serverを立ち上げた時、Next js内部はどのように処理されているのでしょうか?
Next js内部でFetch関数をpolyfillしている
Nextjs 13(2023年10月21日時点最新)でサポートしているNodejsの最小バージョンは16.14.0です。
ですので、-no-experimental-fetch
フラグを使用せずともfetch関数がNodejsで用意されていない可能性があります。
そのためNext jsは内部でfetch関数をpolyfillしているようでした。(実装箇所)
Nextjsは上記ファイルでfetch関数がグローバルに用意されていなかった場合、Node jsのfetchとして使用されているundiciを呼び出し、グローバルにセットを行うことでNode jsのバージョンの違いを意識せずfetch関数をグローバルに使用可能にしているようです。
💡 polyfillとは? ポリフィルとは、最近の機能をサポートしていない古いブラウザーで、その機能を使えるようにするためのコードです。 https://developer.mozilla.org/ja/docs/Glossary/Polyfill |
さて、残念ながら-no-experimental-fetch
フラグを使用して不安定なfetch関数を使用しないようにしてもNextjs内でpolyfillされているため意味がないことがわかりました。
そこで重たい腰を上げてNextjs内部のコードを確認して解決方法を探してみることにしました。
なぜNextjs内で内部通信を行っている?
とりあえずNext内部で起きたエラーログを見てみるとinvokeRequest
ファンクションでエラーが起きているようです。
対象のコードを確認してみるとやはりエラー箇所でfetch関数を使用しておりエラーが出ているようです。
ところで余談ですが、ここの処理は何をやっているところなのでしょうか?
fetch関数でどこかにリクエストを飛ばしているようですが、どこに飛ばしているのでしょうか?
このコードがどういう流れで呼ばれるのか確認してみると下記のようになっていました。(Next js 13.4.19時点)
どうやらapp routerもしくはpage routerのレンダリング処理をするワーカー当てにリクエストを飛ばしているようでした。
この内部通信処理が追加された時のPRを確認してみると、追加した経緯が書かれていました。
This updates to have a separate routing process and separate rendering processes for
pages
andapp
so that we can properly isolate the two since they rely on different react versions. Besides allowing the above mentioned isolation this also helps us control recovering from process crashes easier as pieces are more isolated from one another e.g. an infinite loop during rendering will no longer block the compiler and can be stopped/restarted as needed.訳:
これは、pagesとappで別々のルーティング・プロセスと別々のレンダリング・プロセスを持つためのアップデートで、異なるReactのバージョンに依存しているため、この2つを適切に分離することができる。
上記のような分離が可能になるほか、レンダリング中の無限ループがコンパイラをブロックしなくなり、必要に応じて停止/再起動できるようになるなど、各プロセスがより分離されるため、プロセスのクラッシュからの復旧をより簡単に制御できるようになります。
Next js 13から新たにapp routerが加わったことや、処理の疎結合化による各処理ごとの制御のしやすさなどの向上のために、各処理をワーカーに分割(新たにサーバーを作成)してそこにリクエスト送る処理になっているようでした。
解決方法(暫定)
さて上記の処理が行われている理由はわかりましたが、fetch関数を使用している以上undici側の改善やNext jsでの何かしらの対応が図られるまでエラーが出てしまう可能性があります。
イシューなどを漁っているとNext js v13.4.12では内部通信の処理にfetch関数ではなく、Node jsのhttp.request関数を使用して通信を行っているようでした。
そこでNext jsのバージョンを13.4.19から13.4.12に下げてみるとついにエラーが出現しなくなりました。
私たちのプロジェクトではひとまずエラーが発生しないバージョンまで落とすことで今回のエラー対応を一旦終わらせ、Nextのアップデートの動向を見ながら良きタイミングでバージョンアップを図る方針で落ち着きました。
💡 現在(2023/10/21日時点)のイシューなどを確認したところ、まだ完全には解決されていないように思えました。 |
最後に
さて、ここまで色々と寄り道をしながらエラーが出た原因や対応策を確認してきました。
今回初めてフレームワークの中のコードまで追って処理の流れなど確認してみたのですが、リクエストを送ってからレスポンスが返るまでのデータの流れなどが以前より鮮明にイメージできるようになり、とても学びになったと感じました。(まだまだコードの一部しか読めてはいませんが😅)
またコードを読む中で「どうしてこんな書き方しているのだろう」と今の自分では理解できないところなども色々と出てきましたので、ソースコードを読むことで新たな知識や技術も身につけることができそうでした。(読み進めてまたブログのネタにでもしようかなと思います。)
ここまで読んでいただきありがとうございました。
この記事が同じようなエラーに出会った人のお役立てれば幸いです。
ライトコードでは、エンジニアを積極採用中!
ライトコードでは、エンジニアを積極採用しています!社長と一杯しながらお話しする機会もご用意しております。そのほかカジュアル面談等もございますので、くわしくは採用情報をご確認ください。
採用情報へ
Udemy信奉者系フロントエンジニア(バックエンドもちょっと)。 現在はNextjsを用いた不動産情報サイトのフロントエンド開発担当中。 映画好きで基本毎日Netflixしてます。
おすすめ記事
immichを知ってほしい
2024.10.31