Goのhttp.ServeMuxがあれば十分
Wenhao Wang
Dev Intern · Leapcell

Go 1.22標準ライブラリにおけるhttp.ServeMuxの最適化とアプリケーション分析
Goのウェブ開発の分野では、より効率的で柔軟なルーティング機能を実現するために、多くの開発者がhttprouterやgorilla/muxなどのサードパーティライブラリを導入することを選択しています。しかし、Go 1.22バージョンでは、公式が標準ライブラリのhttp.ServeMuxを大幅に最適化しました。この動きにより、開発者のサードパーティルーティングライブラリへの依存度が下がることが期待されます。
I. Go 1.22のハイライト:拡張されたパターンマッチング能力
Go 1.22バージョンでは、標準ライブラリのnet/httpパッケージのデフォルトHTTPサービスマルチプレクサのパターンマッチング能力を強化するという、待望の提案が実装されました。既存のマルチプレクサ(http.ServeMux)は、基本的なパスマッチング機能しか提供できず、比較的制限されています。これにより、開発者のより強力なルーティング機能のニーズを満たすために、多数のサードパーティライブラリが登場しました。Go 1.22の新しいマルチプレクサは、高度なマッチング機能を導入することで、サードパーティライブラリとの機能的なギャップを大幅に縮めます。この記事では、新しいマルチプレクサ(mux)を簡単に紹介し、RESTサーバーの例を提供し、新しい標準ライブラリmuxとgorilla/muxの性能を比較します。
II. 新しいmuxの使い方
サードパーティのmux/router(gorilla/muxなど)の使用経験があるGoの開発者にとって、新しい標準muxの使用は簡単で馴染みのある作業になるでしょう。開発者はまず、その公式ドキュメントを読むことをお勧めします。これは簡潔で明確です。
(I)基本的な使用例
次のコードは、muxのいくつかの新しいパターンマッチング機能を示しています。
package main import ( "fmt" "net/http" ) func main() { mux := http.NewServeMux() mux.HandleFunc("GET /path/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprint(w, "got path\n") }) mux.HandleFunc("/task/{id}/", func(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") fmt.Fprintf(w, "handling task with id=%v\n", id) }) http.ListenAndServe("localhost:8090", mux) }
経験豊富なGoプログラマは、すぐに2つの新機能に気づくでしょう。
- 最初のハンドラでは、HTTPメソッド(この例ではGET)がパターンの一部として明示的に使用されています。これは、このハンドラが/path/で始まるパスに対するGETリクエストにのみ応答し、他のHTTPメソッドのリクエストは処理しないことを意味します。
- 2番目のハンドラでは、2番目のパスコンポーネント- {id}にワイルドカードが含まれています。これは以前のバージョンではサポートされていませんでした。このワイルドカードは単一のパスコンポーネントに一致させることができ、ハンドラはリクエストのPathValueメソッドを通じて一致した値を取得できます。
以下は、curlコマンドを使用してこのサーバーをテストする例です。
$ gotip run sample.go # 別のターミナルでテスト $ curl localhost:8090/what/ 404 page not found $ curl localhost:8090/path/ got path $ curl -X POST localhost:8090/path/ Method Not Allowed $ curl localhost:8090/task/leapcell/ handling task with id=leapcell
テスト結果から、サーバーが/path/へのPOSTリクエストを拒否し、GETリクエストのみを許可することがわかります(curlはデフォルトでGETリクエストを使用します)。同時に、リクエストが一致すると、idワイルドカードに対応する値が割り当てられます。開発者は、より多くの機能を理解するために、新しいServeMuxのドキュメントを詳細に参照することをお勧めします。たとえば、末尾のパスのルール、{id}を使用したワイルドカードマッチング、{$}で終わるパスの厳密なマッチングなどです。
(II)パターン競合の処理
この提案では、異なるパターン間の潜在的な競合問題に特に注意が払われています。以下は例です。
mux := http.NewServeMux() mux.HandleFunc("/task/{id}/status/", func(w http.ResponseWriter, r *http.Request) { id := r.PathValue("id") fmt.Fprintf(w, "handling task status with id=%v\n", id) }) mux.HandleFunc("/task/0/{action}/", func(w http.ResponseWriter, r *http.Request) { action := r.PathValue("action") fmt.Fprintf(w, "handling task 0 with action=%v\n", action) })
サーバーが/task/0/status/のリクエストを受信すると、両方のハンドラがこのリクエストに一致する可能性があります。新しいServeMuxドキュメントは、パターンの優先順位規則と、潜在的な競合を処理する方法について詳しく説明しています。競合が発生した場合、登録プロセスはパニックをトリガーします。上記の例では、次のエラーメッセージが表示されます。
panic: pattern "/task/0/{action}/" (registered at sample - conflict.go:14) conflicts with pattern "/task/{id}/status/" (registered at sample - conflict.go:10):
/task/0/{action}/ and /task/{id}/status/ both match some paths, like "/task/0/status/".
But neither is more specific than the other.
/task/0/{action}/ matches "/task/0/action/", but /task/{id}/status/ doesn't.
/task/{id}/status/ matches "/task/id/status/", but /task/0/{action}/ doesn't.
このエラーメッセージは詳細で実用的です。複雑な登録シナリオ(特にパターンがソースコードの複数の場所に登録されている場合)では、これらの詳細は、開発者が競合問題を迅速に特定して解決するのに役立ちます。
III. 新しいmuxでサーバーを実装する
GoのRESTサーバーシリーズでは、複数の方法を使用して、Goのタスク/ To - Doリストアプリケーション用の簡単なサーバーを実装しました。最初の部分は標準ライブラリに基づいて実装され、2番目の部分はgorilla/muxルーターを使用して同じサーバーを再実装しました。ここで、Go 1.22の強化されたmuxを使用してこのサーバーを再度実装することは非常に重要であり、gorilla/muxを使用したソリューションと比較することも興味深いです。
(I)パターン登録の例
以下は、代表的なパターン登録コードです。
mux := http.NewServeMux() server := NewTaskServer() mux.HandleFunc("POST /task/", server.createTaskHandler) mux.HandleFunc("GET /task/", server.getAllTasksHandler) mux.HandleFunc("DELETE /task/", server.deleteAllTasksHandler) mux.HandleFunc("GET /task/{id}/", server.getTaskHandler) mux.HandleFunc("DELETE /task/{id}/", server.deleteTaskHandler) mux.HandleFunc("GET /tag/{tag}/", server.tagHandler) mux.HandleFunc("GET /due/{year}/{month}/{day}/", server.dueHandler)
gorilla/muxの例と同様に、ここでは同じパスを持つリクエストが、特定のHTTPメソッドを使用して異なるハンドラにルーティングされます。古いhttp.ServeMuxを使用すると、このようなマッチャーはリクエストを同じハンドラに転送し、次にハンドラはリクエストメソッドに基づいて後続の操作を決定します。
(II)ハンドラの例
以下は、ハンドラのコード例です。
func (ts *taskServer) getTaskHandler(w http.ResponseWriter, req *http.Request) { log.Printf("handling get task at %s\n", req.URL.Path) id, err := strconv.Atoi(req.PathValue("id")) if err!= nil { http.Error(w, "invalid id", http.StatusBadRequest) return } task, err := ts.store.GetTask(id) if err!= nil { http.Error(w, err.Error(), http.StatusNotFound) return } renderJSON(w, task) }
このハンドラは、Gorillaメソッドと同様に、req.PathValue( "id")からID値を抽出します。ただし、正規表現を使用して{id}が整数のみに一致するように指定されていないため、strconv.Atoiによって返されるエラーに注意する必要があります。
全体として、最終的な結果はgorilla/muxを使用したソリューションと非常によく似ています。従来の標準ライブラリメソッドと比較して、新しいmuxはより複雑なルーティング操作を実行でき、ルーティングの決定をハンドラ自体に任せる必要性を減らし、開発効率とコードの保守性を向上させます。
IV. 結論
「どのルーターライブラリを選択すべきか?」は、常にGo初心者が直面する一般的な質問でした。 Go 1.22のリリース後、この質問への答えが変わる可能性があります。多くの開発者は、新しい標準ライブラリmuxが自分のニーズを満たすのに十分であり、サードパーティパッケージに依存する必要がなくなることに気付くでしょう。
もちろん、一部の開発者は、使い慣れたサードパーティライブラリを選択し続けるでしょう。これも合理的です。 gorilla/muxのようなルーターは、標準ライブラリよりも多くの機能を備えています。さらに、多くのGoプログラマーは、ルーターだけでなく、ウェブバックエンドを構築するために必要な追加ツールも提供するため、Ginのような軽量フレームワークを選択します。
結論として、Go 1.22における標準ライブラリhttp.ServeMuxの最適化は、間違いなく前向きな変化です。開発者がサードパーティパッケージを使用するか、標準ライブラリを使用するかにかかわらず、標準ライブラリの機能を強化することは、Go開発コミュニティ全体にとって有益です。
【Leapcell:Goアプリのホスティング、非同期タスク、Redisに最適なサーバーレスプラットフォーム】(https://leapcell.io/)
最後に、Goサービスのデプロイに最適なプラットフォームをお勧めします:Leapcell
1. マルチ言語サポート
- JavaScript、Python、Go、またはRustで開発します。
2. 無制限のプロジェクトを無料でデプロイ
- 使用量に対してのみ支払い - リクエストなし、料金なし。
3. 比類なき費用対効果
- アイドル料金なしの従量課金制。
- 例:25ドルで、平均応答時間60msで694万リクエストをサポートします。
4. 合理化された開発者エクスペリエンス
- 簡単なセットアップのための直感的なUI。
- 完全に自動化されたCI/CDパイプラインとGitOps統合。
- 実用的な洞察のためのリアルタイムのメトリクスとロギング。
5. 容易なスケーラビリティと高パフォーマンス
- 高い同時実行性を容易に処理するための自動スケーリング。
- 運用オーバーヘッドゼロ - 構築に集中するだけです。
Leapcell Twitter: https://x.com/LeapcellHQ