Gin は、Go (Golang) で書かれた HTTP Web フレームワークです。 Martini に似た API を備えていますが、パフォーマンスは Martini よりも最大 40 倍高速です。圧倒的なパフォーマンスが必要な場合は、ジンを買ってください。
Gin の公式 Web サイトでは、「高いパフォーマンス」と「優れた生産性」を備えた Web フレームワークと紹介されています。他の 2 つのライブラリについても触れています。 1 つ目は Martini です。これも Web フレームワークであり、酒の名前が付いています。 Gin は自社の API を使用しているが、その速度は 40 倍であると述べています。 httprouter を使用することが、Martini よりも 40 倍高速になる重要な理由です。
公式ウェブサイトの「機能」の中には 8 つの主要な機能がリストされており、これらの機能は今後徐々に実装される予定です。
公式ドキュメントに記載されている最小の例を見てみましょう。
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
この例を実行し、ブラウザを使用して http://localhost:8080/ping にアクセスすると、「ポン」が返されます。
この例は非常に単純です。たった 3 つのステップに分割できます:
上記の小さな例の GET メソッドから、Gin では、HTTP メソッドの処理メソッドは、同じ名前の対応する関数を使用して登録する必要があることがわかります。
HTTP メソッドは 9 つあり、最もよく使用される 4 つは GET、POST、PUT、および DELETE で、それぞれクエリ、挿入、更新、削除の 4 つの機能に対応します。 Gin は、すべての HTTP メソッド処理メソッドを 1 つのアドレスに直接バインドできる Any インターフェイスも提供していることに注意してください。
返される結果には通常、2 つまたは 3 つの部分が含まれます。コードとメッセージは常に存在し、データは通常、追加データを表すために使用されます。返す追加データがない場合は省略できます。この例では、200 がコード フィールドの値、「pong」がメッセージ フィールドの値です。
上記の例では、エンジンの作成に gin.Default() が使用されています。ただし、この関数は New のラッパーです。実際、エンジンは New インターフェイスを通じて作成されます。
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
ここでは作成プロセスを簡単に見てみましょう。エンジン構造内のさまざまなメンバー変数の意味には焦点を当てないでください。 New は、Engine 型のエンジン変数の作成と初期化に加えて、engine.allocateContext() を呼び出す匿名関数に Engine.pool.New を設定することもわかります。この関数の機能については後述します。
エンジンには埋め込み構造体 RouterGroup があります。エンジンの HTTP メソッドに関連するインターフェイスはすべて RouterGroup から継承されます。公式サイトに記載されている特徴点の「ルートのグループ化」は RouterGroup 構造体によって実現されています。
func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ //... Initialize the fields of RouterGroup }, //... Initialize the remaining fields } engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup engine.pool.New = func() any { return engine.allocateContext() } return engine }
各 RouterGroup はベース パスbasePath に関連付けられます。エンジンに埋め込まれた RouterGroup の BasePath は「/」です。
一連の処理関数ハンドラーもあります。このグループに関連付けられたパスにあるすべてのリクエストは、主にミドルウェア呼び出しに使用されるこのグループの処理機能をさらに実行します。エンジンの作成時にはハンドラーは nil であり、一連の関数は Use メソッドを通じてインポートできます。この使用法については後で説明します。
type RouterGroup struct { Handlers HandlersChain // Processing functions of the group itself basePath string // Associated base path engine *Engine // Save the associated engine object root bool // root flag, only the one created by default in Engine is true }
RouterGroup のハンドル メソッドは、すべての HTTP メソッド コールバック関数を登録するための最後のエントリです。最初の例で呼び出された GET メソッドと HTTP メソッドに関連するその他のメソッドは、handle メソッドの単なるラッパーです。
handle メソッドは、RouterGroup のbasePath と相対パス パラメーターに従って絶対パスを計算し、同時に、combineHandlers メソッドを呼び出して最終的なハンドラー配列を取得します。これらの結果は、処理関数を登録するために、エンジンの addRoute メソッドにパラメータとして渡されます。
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { absolutePath := group.calculateAbsolutePath(relativePath) handlers = group.combineHandlers(handlers) group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() }
combineHandlers メソッドが行うことは、スライスmergedHandlersを作成し、次にRouterGroup自体のハンドラをそこにコピーし、次にパラメータのハンドラをそこにコピーし、最後にmergedHandlersを返すことです。つまり、ハンドルを使用してメソッドを登録すると、実際の結果には RouterGroup 自体のハンドラーが含まれます。
公式 Web サイトに記載されている「高速」の機能点では、ネットワーク リクエストのルーティングが基数ツリー (Radix Tree) に基づいて実装されていると記載されています。この部分はGinではなく、冒頭のGinの紹介で述べたhttprouterによって実装されています。 Gin は、この部分の機能を実現するために httprouter を使用します。基数ツリーの実装については、当面はここでは触れません。ここでは、その使用方法のみに焦点を当てます。基数ツリーの実装については、後で別の記事を書くかもしれません。
エンジンには、methodTree 構造のスライスであるtrees変数があります。すべての基数ツリーへの参照を保持するのはこの変数です。
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
エンジンは、HTTP メソッドごとに基数ツリーを維持します。このツリーのルート ノードとメソッドの名前は、methodTree 変数に一緒に保存され、すべての methodTree 変数はツリー内にあります。
func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ //... Initialize the fields of RouterGroup }, //... Initialize the remaining fields } engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup engine.pool.New = func() any { return engine.allocateContext() } return engine }
エンジンの addRoute メソッドでは、まずツリーの get メソッドを使用して、そのメソッドに対応する基数ツリーのルート ノードを取得することがわかります。基数ツリーのルート ノードが取得できない場合は、このメソッドに対して以前にメソッドが登録されていないことを意味し、ツリー ノードがツリーのルート ノードとして作成され、ツリーに追加されます。
ルート ノードを取得した後、ルート ノードの addRoute メソッドを使用して、パス path の処理関数ハンドラーのセットを登録します。このステップでは、パスとハンドラーのノードを作成し、それを基数ツリーに保存します。すでに登録されているアドレスを登録しようとすると、addRoute は直接パニック エラーをスローします。
HTTP リクエストを処理する場合、パスを通じて対応するノードの値を見つける必要があります。ルート ノードには、クエリ操作の処理を担当する getValue メソッドがあります。これについては、Gin による HTTP リクエストの処理について説明するときに説明します。
RouterGroup の Use メソッドは、一連のミドルウェア処理関数をインポートできます。公式ウェブサイトに記載されている機能ポイントの「ミドルウェアのサポート」は、Use メソッドによって実現されます。
最初の例では、Engine 構造体変数を作成するときに、New は使用されず、Default が使用されました。デフォルトで何が追加されるのかを見てみましょう。
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
非常に単純な関数であることがわかります。 New を呼び出して Engine オブジェクトを作成することに加えて、Use を呼び出して 2 つのミドルウェア関数 (Logger と Recovery) の戻り値をインポートするだけです。 Logger の戻り値はログを記録するための関数、Recovery の戻り値はパニックに対処するための関数です。ここではこれをスキップし、これら 2 つの関数については後ほど説明します。
エンジンには RouterGroup が埋め込まれており、Use メソッドも実装されていますが、これは RouterGroup の Use メソッドといくつかの補助操作の呼び出しにすぎません。
func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ //... Initialize the fields of RouterGroup }, //... Initialize the remaining fields } engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup engine.pool.New = func() any { return engine.allocateContext() } return engine }
RouterGroup の Use メソッドも非常にシンプルであることがわかります。パラメーターのミドルウェア処理関数を、append を通じて独自のハンドラーに追加するだけです。
この小さな例では、最後のステップはパラメーターなしでエンジンの Run メソッドを呼び出すことです。呼び出し後、フレームワーク全体が実行を開始し、ブラウザーで登録されたアドレスにアクセスすると、コールバックを正しくトリガーできます。
type RouterGroup struct { Handlers HandlersChain // Processing functions of the group itself basePath string // Associated base path engine *Engine // Save the associated engine object root bool // root flag, only the one created by default in Engine is true }
Run メソッドは、アドレスの解析とサービスの開始という 2 つのことだけを行います。ここで、アドレスは実際には文字列を渡すだけですが、渡すか渡さないかの効果を実現するために、可変個引数パラメータが使用されます。 solveAddress メソッドは、addr.
のさまざまな状況の結果を処理します。
サービスの開始には、標準ライブラリの net/http パッケージの ListenAndServe メソッドを使用します。このメソッドは、リスニング アドレスとハンドラー インターフェイスの変数を受け入れます。 Handler インターフェースの定義は非常に単純で、ServeHTTP メソッドが 1 つだけあります。
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { absolutePath := group.calculateAbsolutePath(relativePath) handlers = group.combineHandlers(handlers) group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() }
エンジンは ServeHTTP を実装しているため、ここではエンジン自体が ListenAndServe メソッドに渡されます。監視対象ポートへの新しい接続がある場合、ListenAndServe は接続の受け入れと確立を担当し、接続上にデータがある場合は、処理のためにハンドラーの ServeHTTP メソッドを呼び出します。
エンジンの ServeHTTP は、メッセージを処理するためのコールバック関数です。その内容を見てみましょう。
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain { finalSize := len(group.Handlers) + len(handlers) assert1(finalSize < int(abortIndex), "too many handlers") mergedHandlers := make(HandlersChain, finalSize) copy(mergedHandlers, group.Handlers) copy(mergedHandlers[len(group.Handlers):], handlers) return mergedHandlers }
コールバック関数には 2 つのパラメータがあります。 1 つ目は w で、リクエストの応答を受信するために使用されます。 wに返信データを書き込みます。もう 1 つは、このリクエストのデータを保持する req です。後続の処理に必要なすべてのデータは req.
から読み取ることができます。
ServeHTTP メソッドは 4 つのことを行います。まず、プール pool からコンテキストを取得し、次にそのコンテキストをコールバック関数のパラメータにバインドし、次にコンテキストをパラメータとして使用して handleHTTPRequest メソッドを呼び出してこのネットワーク リクエストを処理し、最後にコンテキストをプールに戻します。
まず、handleHTTPRequest メソッドのコア部分だけを見てみましょう。
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
handleHTTPRequest メソッドは主に 2 つのことを行います。まず、リクエストのアドレスに従って基数ツリーから以前に登録されたメソッドを取得します。ここでは、この処理のためにハンドラーが Context に割り当てられ、Context の Next 関数を呼び出してハンドラー内のメソッドが実行されます。最後に、このリクエストの戻りデータをContextのresponseWriter型オブジェクトに書き込みます。
HTTP リクエストを処理するとき、すべてのコンテキスト関連データは Context 変数内にあります。著者は Context 構造体のコメントに「ジンの最も重要な部分は Context である」と書いており、その重要性がわかります。
上記のエンジンの ServeHTTP メソッドについて説明すると、コンテキストは直接作成されず、エンジンのプール変数の Get メソッドを通じて取得されることがわかります。取り出した状態は使用前にリセットされ、使用後はプールに戻されます。
エンジンのプール変数のタイプは sync.Pool です。現時点では、同時使用をサポートする Go 公式が提供するオブジェクト プールであることだけ知っておいてください。 Get メソッドを使用してプールからオブジェクトを取得できます。また、Put メソッドを使用してオブジェクトをプールに入れることもできます。プールが空で Get メソッドが使用されると、独自の New メソッドを通じてオブジェクトが作成され、それが返されます。
この New メソッドは、エンジンの New メソッドで定義されます。エンジンの新しいメソッドをもう一度見てみましょう。
func New() *Engine { debugPrintWARNINGNew() engine := &Engine{ RouterGroup: RouterGroup{ //... Initialize the fields of RouterGroup }, //... Initialize the remaining fields } engine.RouterGroup.engine = engine // Save the pointer of the engine in RouterGroup engine.pool.New = func() any { return engine.allocateContext() } return engine }
コードから、Context の作成メソッドは Engine の assignContext メソッドであることがわかります。 assignContext メソッドには謎はありません。スライス長の 2 段階の事前割り当てを実行し、オブジェクトを作成して返します。
type RouterGroup struct { Handlers HandlersChain // Processing functions of the group itself basePath string // Associated base path engine *Engine // Save the associated engine object root bool // root flag, only the one created by default in Engine is true }
上記の Context の Next メソッドは、ハンドラー内のすべてのメソッドを実行します。その実装を見てみましょう。
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes { absolutePath := group.calculateAbsolutePath(relativePath) handlers = group.combineHandlers(handlers) group.engine.addRoute(httpMethod, absolutePath, handlers) return group.returnObj() }
ハンドラーはスライスですが、Next メソッドは単にハンドラーの走査として実装されるのではなく、処理進行状況レコードのインデックスを導入します。このインデックスは 0 に初期化され、メソッドの開始時に増分され、メソッドの終了後に再び増分されます。実行が完了しました。
Next の設計は、主にいくつかのミドルウェア機能と連携するために、その使用法と大きな関係があります。たとえば、特定のハンドラーの実行中にパニックがトリガーされた場合、ミドルウェアの回復を使用してエラーを捕捉でき、問題によってハンドラー配列全体に影響を与えることなく、Next を再度呼び出して後続のハンドラーの実行を続行できます。 1 つのハンドラーの。
Gin では、特定のリクエストの処理関数がパニックを引き起こしても、フレームワーク全体が直接クラッシュすることはありません。代わりに、エラー メッセージがスローされ、サービスは引き続き提供されます。これは、Lua フレームワークが通常 xpcall を使用してメッセージ処理関数を実行する方法に似ています。この操作は、公式ドキュメントで言及されている「クラッシュフリー」機能ポイントです。
前述したように、gin.Default を使用してエンジンを作成すると、エンジンの Use メソッドが実行されて 2 つの関数がインポートされます。そのうちの 1 つは、他の関数のラッパーである Recovery 関数の戻り値です。最後に呼び出される関数は CustomRecoveryWithWriter です。この関数の実装を見てみましょう。
package main import "github.com/gin-gonic/gin" func main() { r := gin.Default() r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{ "message": "pong", }) }) r.Run() // listen and serve on 0.0.0.0:8080 }
ここではエラー処理の詳細には焦点を当てず、エラー処理が何を行うかだけを見ていきます。この関数は匿名関数を返します。この無名関数の中に、deferを使って別の無名関数を登録します。この内部匿名関数では、recover を使用してパニックを捕捉し、その後エラー処理を実行します。処理が完了すると、Context の Next メソッドが呼び出され、元々順番に実行されていた Context のハンドラが引き続き実行されます。
最後に、Gin サービスを展開するための最適なプラットフォームである Leapcell を紹介します。
ドキュメントでさらに詳しく見てみましょう!
Leapcell Twitter: https://x.com/LeapcellHQ
以上がジンの詳細: Golang の主要なフレームワークの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。