そこで、サーバーに関する 1 つの問題について話しました。それは、ルーティング ロジックがプログラムの複数の場所に分散していることです。
これは、依存関係を使用せずに HTTP サーバーを作成するすべての人が直面する問題です。ルートのシステムを考慮に入れたサーバーが極端に最小限の設計でない限り (たとえば、これらは 1 つまたは 2 つのルートしかない特殊なサーバーです)、組織のサイズと複雑さが明らかになります。ルーター コードは、経験豊富なプログラマーがすぐに注意を払うものです。
改善されたルーティング システム
サーバーを改善することを決めた人が最初に考えたのは、おそらく一連の関数またはメソッドを備えたデータ型を使用して、ルーティング システムを抽象化するという考えかもしれません。この問題を解決するには、それぞれの特定の状況に適用できる興味深いアプローチがたくさんあります。 Go エコシステムには、ルーター機能を実装するためにさまざまなプロジェクトで正常に使用されている多くの強力なサードパーティ ライブラリがあります。この資料を参照することを強くお勧めします。 この資料では、単純な一連のルートを処理するためのいくつかのアプローチを比較しています。
実用的な例に移る前に、サーバーの API がどのように機能するかを思い出しましょう。
POST /task/ : ID GET /task/<taskid> : ID GET /task/ : DELETE /task/<taskid> : ID GET /tag/<tagname> : GET /due/<yy>/<mm>/<dd> : ,
ルーティング システムをより便利にするために、これを行うことができます。
- 同じルートの異なるメソッドに対して個別のハンドラーを定義できるメカニズムを作成できます。たとえば、リクエスト
POST /task/
は 1 つのハンドラーによって処理され、リクエストGET /task/
は別のハンドラーによって処理される必要があります。 - リクエストの分析を今よりも深めた上でルートハンドラーが選択されるようにすることができます。つまり、たとえば、このアプローチでは、1 つのハンドラーが へのリクエストを処理し
/task/
、別のハンドラー/task/<taskid>
が数値の 1 でリクエストを処理することを示すことができるはずID
です。 - この場合、ルート処理システムは単純に数値
ID
を抽出し、/task/<taskid>
便利な方法でハンドラーに渡す必要があります。
Go で独自のルーターを作成するのは非常に簡単です。これは、レイアウトを使用して HTTP ハンドラーでの作業を整理できるためです。しかし、ここで私はすべてを自分で書きたいという欲求にふけるつもりはありません。代わりに、gorilla / muxと呼ばれる最も一般的なルーターの 1 つを使用してルーティング システムを構成する方法について説明することを提案します 。
集合<0x96><0x8D> gorilla / muxを利用したタスク管理アプリケーションサーバー
gorilla / mux パッケージは、Go 用の最も古く、最も一般的な HTTP ルーターの 1 つです。パッケージのドキュメントによると、「mux」という言葉 は「HTTP リクエスト マルチプレクサ」を表します (「mux」は標準ライブラリでも同じ意味です)。
専門性の高い単一の課題を解決することを目的としたパッケージなので、非常に使いやすいです。ルーティングに gorilla/mux を使用する私たちのサーバーの変種は、ここで見つけることができ ます。ルートを定義するためのコードは次のとおりです。
router := mux.NewRouter()
router.StrictSlash(true)
server := NewTaskServer()
router.HandleFunc("/task/", server.createTaskHandler).Methods("POST")
router.HandleFunc("/task/", server.getAllTasksHandler).Methods("GET")
router.HandleFunc("/task/", server.deleteAllTasksHandler).Methods("DELETE")
router.HandleFunc("/task/{id:[0-9]+}/", server.getTaskHandler).Methods("GET")
router.HandleFunc("/task/{id:[0-9]+}/", server.deleteTaskHandler).Methods("DELETE")
router.HandleFunc("/tag/{tag}/", server.tagHandler).Methods("GET")
router.HandleFunc("/due/{year:[0-9]+}/{month:[0-9]+}/{day:[0-9]+}/", server.dueHandler).Methods("GET")
これらの定義だけで、ルートの操作の利便性を向上させるために解決する必要がある上記のタスク リストの最初の 2 つの項目がすぐに終了することに注意してください。コールはルートの説明で使用される
Methods
ため、1 つのルート内の異なるハンドラーに異なるメソッドを簡単に割り当てることができます。テンプレートを (正規表現を使用して) 方法で一致させる
/task/
と
/task/<taskid>
、ルートの説明を最上位で簡単に区別できます 。
リストの 3 番目の段落にあるタスクに対処するために、 use を見てみましょう
getTaskHandler
。
func (ts *taskServer) getTaskHandler(w http.ResponseWriter, req *http.Request) {
log.Printf("handling get task at %s\n", req.URL.Path)
// Atoi,
// , [0-9]+.
id, _ := strconv.Atoi(mux.Vars(req)["id"])
ts.Lock()
task, err := ts.store.GetTask(id)
ts.Unlock()
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
renderJSON(w, task)
}
ルート定義では、ルート
/task/{id:[0-9]+}/
はパスの解析に使用される正規表現を記述し、「変数」に識別子を割り当てます
id
。この「変数」には、関数
mux.Vars
を呼び出して渡す ことでアクセスできます
req
(gorilla / mux はこの変数を各リクエストのコンテキストに保存
mux.Vars
し、それを操作するための便利なヘルパー関数です)。
ルーティングを構成するさまざまなアプローチの比較
これは、ルートがどのように処理されるかを理解しようとしている人のために、元のサーバー バージョンでのコード読み取りシーケンスの様子です
GET /task/<taskid>
。
gorilla / mux を使用するコードを理解したい場合は、以下をお読みください。
gorilla / mux を使用する場合、プログラム テキストを「ジャンプ」する必要がなくなるだけではありません。さらに、ここでは、はるかに少ないコードを読む必要があります。謙虚な意見ですが、これはコードの可読性を向上させるという点で非常に優れています。 gorilla / mux を使用する場合のパスの記述は簡単な作業であり、解決するために必要なコードは少量です。そして、このコードを読んだ人は誰でも、このコードがどのように機能するかをすぐに理解します。このアプローチのもう 1 つの利点は、コードを 1 か所で見ることで、すべてのルートを文字通り確認できることです。実際、ルーティング設定コードは、REST API の自由形式の説明と非常によく似ています。
私は gorilla / mux のようなパッケージを使用するのが好きです。それらは非常に特殊なツールだからです。彼らは 1 つの問題を解決し、うまくやっています。それらはプロジェクトのプログラム コードの隅々まで「クロール」するわけではありません。つまり、必要に応じて、簡単に削除したり、別のものに置き換えたりできます。完全なコードを見ると この記事で説明しているサーバー バリアントの場合、ゴリラ/マルチプレクサー メカニズムの範囲が数行のコードに制限されていることがわかります。プロジェクトの開発中に、このプロジェクトの仕様と互換性のない何らかの制限が gorilla / mux パッケージに見つかった場合は、gorilla / mux を別のサードパーティのルーター (または独自のルーター) に置き換えるタスクを解決する必要があります。すばやく簡単に。
Go で REST サーバーを開発するとき、どのルーターを使用しますか?