ジン
Gin は Go 言語で書かれた Web フレームワークです
1 Web ワークフロー
- クライアントは、TCP/IP プロトコルを通じてサーバーへの TCP 接続を確立します。
- クライアントは、HTTP プロトコル要求 Request をサーバーに送信し
GET /url
、サーバー内のリソース ドキュメントを要求します。 - サーバーは HTTP プロトコル応答 Response をクライアントに送信します。要求されたリソースに動的言語コンテンツが含まれている場合、サーバーは動的言語の解釈エンジンを呼び出して「動的コンテンツ」を処理し、処理されたデータをクライアントに返します。
- クライアントがサーバーから切断されました。HTML ドキュメントはクライアントによって解釈され、グラフィック結果がクライアント画面に表示されます。
2 ジンフレーム
Go 言語で最も人気のある 2 つの軽量 Web フレームワークは、Gin と Echo です。これら 2 つのフレームワークは似ており、どちらもプラグイン軽量フレームワークであり、その背後にはさまざまな小さなプラグインを提供するオープンソース エコロジーがあります。これらのパフォーマンス2 つのフレームワークも非常に優れており、ネイキッド テストは高速に実行されます。ジンはエコーよりも早くスタートし、より高い市場シェアとより豊かなエコロジーを備えていました。
参考優れた記事:
2.1 ハローワールド
ジン実行の一般的なプロセスは次のとおりです。
- エンジン構造をインスタンス化する
- net/http ライブラリを介して Engine インスタンスを http.Server にバインドします。
- net/http リスニングサービスを開始する
- リクエストを受信した後、リクエストをエンジンの ServeHTTP インターフェースに転送し、handleHTTPRequest を呼び出してリクエストを処理します。
- handleHTTPRequest はパスを処理し、ツリーのノードをクエリしてリクエスト ハンドラーを取得します。
- ハンドラーはコンテキストに渡されて、実際のハンドラー呼び出しを行い、リクエスト結果を返します。
func main() {
// 初始化一个http服务对象
engine := gin.Default()
// 设置一个get请求的路由,url为localhost
engine.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "hello World!")
})
//监听并启动服务,默认 http://localhost:8080/
engine.Run()
}
Engine.Run()
// 将 Engine 的路由挂载到 http.Server 上,并开起监听,等待 HTTP 请求
// addr:监听地址
func (engine *Engine) Run(addr ...string) (err error) {
defer func() {
debugPrintError(err) }()
address := resolveAddress(addr)
debugPrint("Listening and serving HTTP on %s\n", address)
err = http.ListenAndServe(address, engine)
return
}
2.2ジンエンジン
Engine は、Gin フレームワークの最も重要なデータ構造であり、フレームワークのエントリ ポイントです。Engine オブジェクトを使用して、サービス ルーティング情報を定義し、プラグインを組み立て、サービスを実行します。Web サービス全体がそれによって動かされます。基盤となる HTTP サーバーは Go 言語の組み込み http サーバーを使用しますが、Engine の本質は組み込み HTTP サーバーのラッパーにすぎず、より使いやすくなっています。
gin.Default()
// 返回一个启用 Recovery 中间件 和 Logger 中间件的 Engine 实例
// Logger 用于输出请求日志,Recovery 确保单个请求发生 panic 时记录异常堆栈日志,输出统一的错误响应。
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
リクエスト応答プロセス:
- クライアントリクエストサーバー
- HTTP サービス エンジン http.Server はリクエストを受信し、最初にそれを http.ResponseWriter および *http.Request に処理し、登録された上位層のリクエスト処理ハンドラーに渡します (ServerHTTP インターフェイスを実装し、http.ListenAndServe のハンドラーに登録します)。 (つまりジンのエンジン)
- エンジンはリクエスト データをコンテキスト プールに置き、処理のためにエンジンの handleHTTPRequest に渡します。
- handleHTTPRequestはツリーから対応するノードを見つけ、登録されたリクエスト処理ハンドラをコールバックします。
2.3 ルーティングとコントローラー
ルーティングは、http リクエストを参照して、対応するプロセッサ関数 (コントローラー関数とも呼ばれます) を見つける方法のプロセスです。コントローラー関数は主に http リクエストと応答のタスクの実行を担当します。
r := gin.Default()
// 路由定义post请求, url路径为:/user/login, 绑定doLogin控制器函数
r.POST("/user/login", doLogin)
// 控制器函数
func doLogin(c *gin.Context) {
// 获取post请求参数
username := c.PostForm("username")
password := c.PostForm("password")
// 通过请求上下文对象Context, 直接往客户端返回一个字符串
c.String(200, "username=%s,password=%s", username,password)
}
2.3.1 ルーティングルール
ルーティング ルールは次の 3 つの部分で構成されます。
- httpリクエストメソッド
- 得る
- 役職
- 置く
- 消去
- URLパス
- コントローラー機能
2.3.2 URLパス
エコー フレームに URL パスを記述する方法は 3 つあります。
- 静的 URL パス
- パスパラメータを含む URL パス
- アスタリスク (*) のあいまい一致パラメータを含む URL パス
// 例子1, 静态Url路径, 即不带任何参数的url路径
/users/center
/user/111
/food/12
// 例子2,带路径参数的url路径,url路径上面带有参数,参数由冒号(:)跟着一个字符串定义。
// 路径参数值可以是数值,也可以是字符串
//定义参数:id, 可以匹配/user/1, /user/899 /user/xiaoli 这类Url路径
/user/:id
//定义参数:id, 可以匹配/food/2, /food/100 /food/apple 这类Url路径
/food/:id
//定义参数:type和:page, 可以匹配/foods/2/1, /food/100/25 /food/apple/30 这类Url路径
/foods/:type/:page
// 例子3. 带星号(*)模糊匹配参数的url路径
// 星号代表匹配任意路径的意思, 必须在*号后面指定一个参数名,后面可以通过这个参数获取*号匹配的内容。
//以/foods/ 开头的所有路径都匹配
//匹配:/foods/1, /foods/200, /foods/1/20, /foods/apple/1
/foods/*path
//可以通过path参数获取*号匹配的内容。
2.3.3 コントローラの機能
コントローラー関数の定義:
// 控制器函数接受一个上下文参数。可以通过上下文参数,获取http请求参数,响应http请求。
func HandlerFunc(c *gin.Context)
//实例化gin实例对象。
r := gin.Default()
//定义post请求, url路径为:/users, 绑定saveUser控制器函数
r.POST("/users", saveUser)
//定义get请求,url路径为:/users/:id (:id是参数,例如: /users/10, 会匹配这个url模式),绑定getUser控制器函数
r.GET("/users/:id", getUser)
//定义put请求
r.PUT("/users/:id", updateUser)
//定义delete请求
r.DELETE("/users/:id", deleteUser)
//控制器函数实现
func saveUser(c *gin.Context) {
...忽略实现...
}
func getUser(c *gin.Context) {
...忽略实现...
}
func updateUser(c *gin.Context) {
...忽略实现...
}
func deleteUser(c *gin.Context) {
...忽略实现...
}
ヒント: 実際のプロジェクト開発では、メンテナンスが不便なので、ルーティング定義とコントローラー関数の両方を 1 つの go ファイルに記述しないでください。第 1 章のプロジェクト構造を参照して、独自のビジネス モジュールを計画できます。
2.3.4 パケットルーティング
実際、ルートのグループ化は、同じタイプのルートの URL プレフィックスを設定することです。API 開発を行う際に、複数の API バージョンをサポートしたい場合は、グループ ルーティングを通じて API バージョン処理を実装できます。
func main() {
router := gin.Default()
// 创建v1组
v1 := router.Group("/v1")
{
// 在v1这个分组下,注册路由
v1.POST("/login", loginEndpoint)
v1.POST("/submit", submitEndpoint)
v1.POST("/read", readEndpoint)
}
// 创建v2组
v2 := router.Group("/v2")
{
// 在v2这个分组下,注册路由
v2.POST("/login", loginEndpoint)
v2.POST("/submit", submitEndpoint)
v2.POST("/read", readEndpoint)
}
router.Run(":8080")
}
上記の例では、次のルーティング情報が登録されます。
- /v1/ログイン
- /v1/送信
- /v1/読み取り
- /v2/ログイン
- /v2/送信
- /v2/読み取り
2.4 ルーターグループ
RouterGroup はルーティング ツリーのパッケージ化であり、すべてのルーティング ルールは最終的にそれによって管理されます。Engine の構造は RouterGroup を継承しているため、Engine は RouterGroup のすべてのルーティング管理機能を直接備えています。同時に、RouteGroup オブジェクトには Engine へのポインターも含まれます。
type Engine struct {
RouterGroup
...
}
type RouterGroup struct {
...
engine *Engine
...
}
2.4.1 エンジンとルーターグループの関係
- エンジンのツリー (ルーティング ツリー) を管理する
- ミドルウェアの管理
- ルーティンググループの管理
RouterGroup の実装コードを見ると、単独で存在するのは無意味であることがわかりますが、これは Engine 専用の抽象化レイヤーであり、主に Engine のルーティングを管理するために使用されます。
- ツリーのエンティティはエンジン内にあるため、RouterGroup はツリーを操作するためにエンジンを使用する必要があるため、RouterGroup にはエンジン インスタンスを指すポインタ変数が常に存在します。
- RouterGroup の Group インターフェイスとルート変数の設計により、ルートをグループ化する機能が与えられます。
2.4.2 RouterGroup メソッド
RouterGroup は IRouter インターフェイスを実装し、一連のルーティング メソッドを公開します。これらのメソッドは最終的に、Engine.addRoute メソッドを呼び出すことによって、リクエスト プロセッサをルーティング ツリーにフックします。
GET(string, ...HandlerFunc) IRoutes
POST(string, ...HandlerFunc) IRoutes
DELETE(string, ...HandlerFunc) IRoutes
PATCH(string, ...HandlerFunc) IRoutes
PUT(string, ...HandlerFunc) IRoutes
OPTIONS(string, ...HandlerFunc) IRoutes
HEAD(string, ...HandlerFunc) IRoutes
// 匹配所有 HTTP Method
Any(string, ...HandlerFunc) IRoutes
2.4.3 ルーティング登録処理
- エンジン インスタンス (RouterGroup が含まれることが知られているエンジン) が GET を呼び出し、リクエスト パス、応答関数メソッドを渡します。
- GET は group.handle を呼び出します。
- そして、それ自体の Engine ポインター (Engine の RouterGroup) を介して Engine の AddRouter を呼び出します。
- リクエストパスとレスポンスメソッドをツリーの対応するルートに追加します。
2.5 ジン.コンテキスト
リクエストのコンテキスト情報を保存します。これは、すべてのリクエスト ハンドラのエントリ パラメータです。
type HandlerFunc func(*Context)
type Context struct {
...
Request *http.Request // 请求对象
Writer ResponseWriter // 响应对象
Params Params // URL匹配参数
...
Keys map[string]interface{
} // 自定义上下文信息
...
}
Context オブジェクトには、現在のリクエストのコンテキスト情報を取得するための非常に豊富なメソッドが用意されています。リクエスト内の URL パラメータ、Cookie、ヘッダーを取得する必要がある場合は、Context オブジェクトを通じて取得できます。この一連のメソッドは、本質的に http.Request オブジェクトのラッパーです。
// 获取 URL 匹配参数 /book/:id
func (c *Context) Param(key string) string
// 获取 URL 查询参数 /book?id=123&page=10
func (c *Context) Query(key string) string
// 获取 POST 表单参数
func (c *Context) PostForm(key string) string
// 获取上传的文件对象
func (c *Context) FormFile(name string) (*multipart.FileHeader, error)
// 获取请求Cookie
func (c *Context) Cookie(name string) (string, error)
...
2.6 Jin フレームワークの動作モード
デバッグを容易にするために、Gin フレームワークは実行中にデフォルトでデバッグ モードになり、デフォルトで多くのデバッグ ログがコンソールに出力されます。オンラインにするときは、デバッグ モードをオフにしてリリースに変更する必要があります。モード。
Gin フレームワークの動作モードを設定します。
2.6.1 環境変数による設定
export GIN_MODE=release
GIN_MODE 環境変数。デバッグまたはリリースに設定可能
2.6.2 コードによる設定
main 関数で、gin フレームワークを初期化するときに次のコードを実行します。
// 设置 release模式
gin.SetMode(gin.ReleaseMode)
// 或者 设置debug模式
gin.SetMode(gin.DebugMode)
3 ジン処理リクエストパラメータ
3.1 Get リクエストパラメータの取得
リクエスト URL を取得する例:*/path?id=1234&name=Manu&value=*111
Get リクエストパラメータを取得するための一般的な関数:
- func (c *Context)クエリ(キー文字列) 文字列
- func (c *Context) DefaultQuery (キー、defaultValue 文字列) 文字列
- func (c *Context) GetQuery (キー文字列) (文字列、ブール値)
例:
func Handler(c *gin.Context) {
//获取name参数, 通过Query获取的参数值是String类型。
name := c.Query("name")
//获取name参数, 跟Query函数的区别是,可以通过第二个参数设置默认值。
name := c.DefaultQuery("name", "tizi365")
//获取id参数, 通过GetQuery获取的参数值也是String类型,
// 区别是GetQuery返回两个参数,第一个是参数值,第二个参数是参数是否存在的bool值,可以用来判断参数是否存在。
id, ok := c.GetQuery("id")
if !ok {
// 参数不存在
}
}
ヒント: GetQuery 関数でパラメータの存在を判断するロジックは、パラメータの値が空の場合にパラメータが存在するとみなされ、パラメータが送信されていない場合にのみパラメータが存在しません。
3.2 Post リクエストパラメータの取得
- フォーム送信はポストリクエストであり、http には次の 4 つの一般的な送信形式があります。
- アプリケーション/json
- application/x-www-form-urlencoded
- アプリケーション/xml
- マルチパート/フォームデータ
- フォームパラメータは、デフォルトで x-www-form-urlencoded または from-data 形式でパラメータを解析する PostForm() メソッドを通じて取得できます。
Post リクエストパラメータを取得するための一般的な関数:
- func (c *Context) PostForm (キー文字列) 文字列
- func (c *Context) DefaultPostForm (キー、defaultValue 文字列) 文字列
- func (c *Context) GetPostForm (キー文字列) (文字列、ブール値)
例:
func Handler(c *gin.Context) {
//获取name参数, 通过PostForm获取的参数值是String类型。
name := c.PostForm("name")
// 跟PostForm的区别是可以通过第二个参数设置参数默认值
name := c.DefaultPostForm("name", "tizi365")
//获取id参数, 通过GetPostForm获取的参数值也是String类型,
// 区别是GetPostForm返回两个参数,第一个是参数值,第二个参数是参数是否存在的bool值,可以用来判断参数是否存在。
id, ok := c.GetPostForm("id")
if !ok {
// 参数不存在
}
}
テスト投稿
1. HTMLページを編集する
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<form action="http://localhost:8080/form" method="post" action="application/x-www-form-urlencoded">
用户名:<input type="text" name="username" placeholder="请输入你的用户名"> <br>
密 码:<input type="password" name="userpassword" placeholder="请输入你的密码"> <br>
<input type="submit" value="提交">
</form>
</body>
</html>
2.投稿リクエストの編集
func main() {
engine := gin.Default()
engine.POST("/form", func(c *gin.Context) {
types := c.DefaultPostForm("type", "post")
username := c.PostForm("username")
password := c.PostForm("userpassword")
c.String(http.StatusOK, fmt.Sprintf("username:%s ,password: %s,type: %s", username, password, types))
})
engine.Run()
}
3. サービスを開始します。サービスはデフォルトで常にポート 8080 の「/form」をリッスンします。
4. HTML のローカル絶対パスをコピーし、ブラウザで開き、フォームを送信します
5.送信をクリックすると自動的に にジャンプしhttp://localhost:8080/form
、投稿で取得したフォーム項目と印刷表示が表示されます。
3.3 URL パスパラメータの取得
URL パス パラメータの取得とは、/user/: id
このタイプのルートにバインドされたパラメータを取得することを指します。この例では、パラメータ ID がバインドされています。
URL パスパラメータを取得するための一般的な関数:
- func (c *Context) Param (キー文字列) 文字列
例:
r := gin.Default()
r.GET("/user/:id", func(c *gin.Context) {
// 获取url参数id
id := c.Param("id")
})
3.4 リクエストパラメータを構造体オブジェクトにバインドする
パラメータを取得する以前の方法は、パラメータを 1 つずつ読み取ることであり、面倒でした。Gin フレームワークは、リクエスト パラメータの構造体オブジェクトへの自動バインディングをサポートしています。このメソッドは、Get/Post リクエストをサポートし、json/xml を使用した HTTP リクエストもサポートしています。本文の内容のフォーマットパラメータ。
例:
次の例は、リクエスト パラメーターを User struct オブジェクトにバインドします。
// User 结构体定义
type User struct {
Name string `json:"name" form:"name"`
Email string `json:"email" form:"email"`
}
structフィールドのラベルを定義することで、リクエストパラメータとstructフィールドの関係を定義します。
UserのNameフィールドのラベルを以下に説明します。
構造体タグの説明:
ラベル | 説明する |
---|---|
json:「名前」 | データ形式はjson形式で、jsonフィールド名はnameです。 |
形式:「名前」 | フォームパラメータの名前は name です |
xml:「名前」 |
ヒント: 独自のニーズに応じてサポートされるデータ型を選択できます。たとえば、json データ形式をサポートする必要がある場合は、次のようにフィールド ラベルを定義できます: json: "name"
コントローラーのコードを見てみましょう。
r.POST("/user/:id", func(c *gin.Context) {
// 初始化user struct
u := User{
}
// 通过ShouldBind函数,将请求参数绑定到struct对象, 处理json请求代码是一样的。
// 如果是post请求则根据Content-Type判断,接收的是json数据,还是普通的http请求参数
if c.ShouldBind(&u) == nil {
// 绑定成功, 打印请求参数
log.Println(u.Name)
log.Println(u.Email)
}
// http 请求返回一个字符串
c.String(200, "Success")
})
ヒント: http リクエスト本文を通じてリクエスト パラメータを json 形式で渡し、ポスト リクエストを通じてパラメータを送信する場合は、Content-Type を application/json に設定する必要があります。xml 形式のデータの場合は、application/xml に設定します。
3.5 Gin がクライアント IP を取得する方法
r := gin.Default()
r.GET("/ip", func(c *gin.Context) {
// 获取用户IP
ip := c.ClientIP()
})
4 Gin はコンテキスト応答結果を処理します
gin.Context コンテキスト オブジェクトは複数の返される処理結果をサポートします。次に、さまざまな応答メソッドを紹介します。
4.1 c.文字列
リクエストに文字列として応答し、String 関数を通じて文字列を返します。
関数定義:
func (c *Context) String(code int, format string, values ...interface{
})
パラメータの説明:
パラメータ | 説明する |
---|---|
コード | httpステータスコード |
フォーマット | 返された結果は、Sprintf 関数と同様の文字列形式定義をサポートします。たとえば、%d は整数の挿入を表し、%s は文字列の挿入を表します。 |
価値観 | 任意の数のフォーマット パラメータで定義された文字列フォーマット パラメータ |
例:
func Handler(c *gin.Context) {
// 例子1:
c.String(200, "欢迎访问tizi360.com!")
// 例子2: 这里定义了两个字符串参数(两个%s),后面传入的两个字符串参数将会替换对应的%s
c.String(200,"欢迎访问%s, 你是%s", "tizi360.com!","最靓的仔!")
}
ヒント: net/http パッケージは、さまざまな一般的なステータス コード定数を定義します。たとえば、http.StatusOK == 200、http.StatusMovedPermanently == 301、http.StatusNotFound == 404 などです。詳細については、net を参照してください。 /http パッケージ
4.2 c.JSON
json形式でリクエストに応答する
// User 定义
type User struct {
Name string `json:"name"` // 通过json标签定义struct字段转换成json字段的名字。
Email string `json:"email"`
}
// Handler 控制器
func(c *gin.Context) {
//初始化user对象
u := &User{
Name: "tizi365",
Email: "[email protected]",
}
//返回json数据
//返回结果:{"name":"tizi365", "email":"[email protected]"}
c.JSON(200, u)
}
4.3 c.XML
XML形式でリクエストに応答する
// User 定义, 默认struct的名字就是xml的根节点名字,这里转换成xml后根节点的名字为User.
type User struct {
Name string `xml:"name"` // 通过xml标签定义struct字段转换成xml字段的名字。
Email string `xml:"email"`
}
// Handler 控制器
func(c *gin.Context) {
//初始化user对象
u := &User{
Name: "tizi365",
Email: "[email protected]",
}
//返回xml数据
//返回结果:
// <?xml version="1.0" encoding="UTF-8"?>
// <User><name>tizi365</name><email>[email protected]</email></User>
c.XML(200, u)
}
4.4 c.ファイル
ファイル形式でリクエストに応答する
以下では、gin フレームワークがファイルのダウンロードに使用できるファイルを直接返す方法について説明します。
例子1:
func(c *gin.Context) {
//通过File函数,直接返回本地文件,参数为本地文件地址。
//函数说明:c.File("文件路径")
c.File("/var/www/1.jpg")
}
例子2:
func(c *gin.Context) {
//通过FileAttachment函数,返回本地文件,类似File函数,区别是可以指定下载的文件名。
//函数说明: c.FileAttachment("文件路径", "下载的文件名")
c.FileAttachment("/var/www/1.jpg", "1.jpg")
}
4.5 c.ヘッダー
httpレスポンスヘッダーの設定(set Header)
func(c *gin.Context) {
//设置http响应 header, key/value方式,支持设置多个header
c.Header("site","tizi365")
}
5 ジンミドルウェア
ミドルウェア(Middleware)は、Gin フレームワークにおいて、http のリクエストとレスポンスのライフ サイクルをインターセプトできる特別な機能を指します。リクエストとレスポンスのライフ サイクルには複数のミドルウェアを登録できます。各ミドルウェアは異なる機能を実行します。ミドルウェアは実行後、次のミドルウェアが実行される番です。
ミドルウェアの一般的なアプリケーション シナリオは次のとおりです。
- 速度制限をリクエストする
- APIインターフェースの署名処理
- 権限チェック
- 統合されたエラー処理
ヒント: すべてのリクエストをインターセプトして何かを実行したい場合は、それを実現するミドルウェア機能を開発できます。
Gin は、グローバル ミドルウェアの設定とルーティング グループのミドルウェアの設定をサポートしています。グローバル ミドルウェアの設定は、すべてのリクエストがインターセプトされることを意味し、グループ ルーティングのミドルウェアの設定は、このグループの下のルートでのみ機能することを意味します。
5.1 ミドルウェアの使用
func main() {
r := gin.New()
// 通过use设置全局中间件
// 设置日志中间件,主要用于打印请求日志
r.Use(gin.Logger())
// 设置Recovery中间件,主要用于拦截paic错误,不至于导致进程崩掉
r.Use(gin.Recovery())
// 忽略后面代码
}
5.2 カスタムミドルウェア
例を使用してミドルウェアをカスタマイズする方法を理解しましょう
package main
// 导入gin包
import (
"github.com/gin-gonic/gin"
"log"
"time"
)
// 自定义个日志中间件
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
t := time.Now()
// 可以通过上下文对象,设置一些依附在上下文对象里面的键/值数据
c.Set("example", "12345")
// 在这里处理请求到达控制器函数之前的逻辑
// 调用下一个中间件,或者控制器处理函数,具体得看注册了多少个中间件。
c.Next()
// 在这里可以处理请求返回给用户之前的逻辑
latency := time.Since(t)
log.Print(latency)
// 例如,查询请求状态吗
status := c.Writer.Status()
log.Println(status)
}
}
func main() {
r := gin.New()
// 注册上面自定义的日志中间件
r.Use(Logger())
r.GET("/test", func(c *gin.Context) {
// 查询我们之前在日志中间件,注入的键值数据
example := c.MustGet("example").(string)
// it would print: "12345"
log.Println(example)
})
// Listen and serve on 0.0.0.0:8080
r.Run(":8080")
}