2023-1-24
Posted by
ginとは
ginはgoで書かれた高速なバックエンドフレームワークです
活発なコミュニティがありフレームワーク自体も更新され続けています、そのためドキュメントも数多くあり言語の特性や機能の充実度などもあいまって比較的学習しやすいフレームワークワークです
ドキュメントが充実してるとか言いながら何で書いてるのかは訊くな
プロジェクトを作成する
$ mkdir gin_test
$ cd gin_test
$ go mod init gin_test
go: creating new go.mod: module test
go: to add module requirements and sums:
go mod tidy
$ go get -u github.com/gin-gonic/gin
go: downloading github.com/gin-gonic/gin v1.8.2
go: downloading golang.org/x/net v0.4.0
~~~
go: added google.golang.org/protobuf v1.28.1
go: added gopkg.in/yaml.v2 v2.4.0
これで作成できました
ファイル構成は以下のようになっているはずです
gin_test
go.mod
go.sum
Hello, world!を実行する
今はまだフォルダーが何もありませんがHello, world!を実行するために下のようにファイルを作成しましょう
gin_test
go.mod
go.sum
main.go
そしてHello, world!を追加します
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "Hello, world!",
})
})
// listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
r.Run()
}
実行したらこのようになります
info
コメントにあるようにosによってアクセス方法が変わります
これはgoのデフォルトのライブラリnet/httpにあるサーバーと同じです
因みにwindowsで127.0.0.1:8080でアクセスできませんでしたが、その後時間を空けて実行してみたら127.0.0.1:8080でアクセスできました
謎です
$ go run main.go
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET / --> main.main.func1 (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080
因みに、本当に最小のアプリは下のようになります
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.Run()
}
これに機能を足していく感じです
因みにgin.Default
は既にロガーなどのミドルウェアが登録された状態です、そのため真っ新な状態が良いならgin.New
を使用できます
デフォルトのミドルウェアの二つにgin.Logger
とgin.Recovery
があります
gin.Logger
は名前から分かるとおり、ロガーです、gin.Recovery
はサバーでエラーが起きた場合に500 Internal Server Error
を返すミドルウェアです
gin.Default
の場合はわざわざ追加する必要はありませんが追加した場合はr.Use(gin.Recovery)
で追加できます
ルーティング
先ほどのHello, world!にも出てきましたが
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "Hello, world!",
})
})
.GET
などのリクエストメソッドのような名前のついた関数を使います
この関数の引数はこれ->GET(relativePath string, handlers ...HandlerFunc)
なのですが、これを見てわかるとおりhandler
をたくさんしようできます
r.GET("/", func(c *gin.Context) {
c.JSON(400, gin.H{
"message": "Hello, world!",
})
}, func(c *gin.Context) {
c.JSON(200, gin.H{
"mes": "Hello",
})
})
response
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Date: ___, __ Jan 2023 __:__:__ GMT
Content-Length: 42
{"message":"Hello, world!"}{"mes":"Hello"}
ステータスコードは一番最初のコード(例では400
)が使用されるようです
またハンドラーは関数を直接記述しなくても関数を渡すこともできます
func handleA(c *gin.Context) {
c.JSON(400, gin.H{
"message": "Hello, world!",
})
}
r.GET("/", handleA)
メソッド
メソッドは全部で9個あります
下のは説明不要でしょう
r.GET("/", handleA)
r.POST("/", handleA)
r.DELETE("/", handleA)
r.PATCH("/", handleA)
r.PUT("/", handleA)
r.OPTION("/", handleA)
r.HEAD("/", handleA)
そして少し特殊なのが二つあります
r.Any("/", handleA)
これは全てのメソッドを受けいれます
正確にはGET
, POST
, PUT
, PATCH
, HEAD
, OPTIONS
, DELETE
, CONNECT
, TRACE
です
r.Handle("GET", "/", handleA)
これはメソッドを文字列で指定できます
そのため推奨はしませんがオリジナルのメソッドを使用できます
info
POSTやGET、DELETEなどの目的に応じて既にあるメソッドを使用する事を推奨します
r.Handle("ORIGINAL", "/", handleA)
r.Handle("MYMETHOD", "/", handleA)
スタティックファイル
info
例に出てくるプログラムは以下のようなファイル構成を想定しています
使う際は適度pathを変えてください
gin_test
static
img.png
img2.png
go.mod
go.sum
main.go
まず一番簡単な方法は.Static
を使う事です
r.Static("/static", "./static")
これだと/static
にアクセスした時"無"が返されますが
(実際には404
で<pre></pre>
が帰ってきているがブラウザで見ると無)
.StaticFS
を使う事でファイルの一覧を表示する事が出来ます
r.StaticFS("/static", http.Dir("static"))
response ( path: /static
<pre>
<a href="img.png">img.png</a>
<a href="img2.png">img2.png</a>
</pre>
また.StaticFile
を使えば一つのファイルのみ返す事も出来ます
r.StaticFile("/img", "./static/img.png")
スコープ
スケープを使う事でミドルウェアなどを一部に適応する事が出来ます
articles := r.Group("/article")
{
articles.Use(
func(c *gin.Context) {
println("Hello, world!")
},
)
articles.GET("/", func(c *gin.Context) {
c.String(200, "/article/index")
})
article := articles.Group("/:id")
{
article.GET("/", func(c *gin.Context) {
c.String(200, "/article/%s", c.Param("id"))
})
article.GET("/comment", func(c *gin.Context) {
c.String(200, "/article/%s/comment", c.Param("id"))
})
}
}
routes
-
/article/
-
/article/:id/
-
/article/:id/comment
階層が深くなると便利な機能です
また{}
を使いわざわざスケープを分けたりインデントする必要は無く
下のように書けますが可読性が低いため公式ドキュメントなどではインデントしているようです
articles := r.Group("/article")
articles.Use(
func(c *gin.Context) {
println("Hello, world!")
},
)
articles.GET("/", func(c *gin.Context) {
c.String(200, "/article/index")
})
article := articles.Group("/:id")
article.GET("/", func(c *gin.Context) {
c.String(200, "/article/%s", c.Param("id"))
})
article.GET("/comment", func(c *gin.Context) {
c.String(200, "/article/%s/comment", c.Param("id"))
})
Cors
今回はgin-contrib/cors
を使います
まずインストールし
$ go get github.com/gin-contrib/cors
go: downloading github.com/gin-contrib/cors v1.4.0
go: added github.com/gin-contrib/cors v1.4.0
インポートします
import "github.com/gin-contrib/cors"
そしてデフォルトのミドルウェアを追加すれば使えます
r.Use(cors.Default())
余談ですが上のプログラムは以下のプログラムと等価です
r.Use(cors.New(cors.Config{
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Length", "Content-Type"},
AllowCredentials: false,
AllowAllOrigins: true,
MaxAge: 12 * time.Hour,
}))
今回はCorsメインではないので詳しくは書きませんが詳しく知りたい場合は本家のgithubを参照してください
ミドルウェア
ミドルウェアは関数が実行される前に実行されます
上のgin-contrib/cors
もミドルウェアの一種ですし認証などにも使えます
func middlewareA() gin.HandlerFunc {
return func(c *gin.Context) {
println("middlewareA")
}
}
使用するには.User
を使います
r.Use(middlewareA())
先ほど出てきた.Group
と組み合わせれば一部のみに適応する事が可能です
user_route := r.Group("/user")
user_route.Use(middlewareA())
user_route.GET("/post", test)
また個別に適応することもできます
user_route.GET("/post", middlewareA(), test)
ミドルウェアの機能
勿論これだけではありません
ミドルウェアで処理を打ち切る機能があります
1
2
3
4
5
6
+
7
8
func middlewareA() gin.HandlerFunc {
return func(c *gin.Context) {
c.JSON(200, gin.H{
"mes": "Hello",
})
c.Abort()
}
}
また関数が実行される前後で処理をする事も出来ます
1
2
3
4
+
5
6
7
func middlewareA() gin.HandlerFunc {
return func(c *gin.Context) {
println("start")
c.Next()
println("end")
}
}
リクエスト
パスの値を取得する
パスの値はParam
関数を使えばstring
型で取得できます
r.GET("/user/:id/post/:title", func(c *gin.Context) {
id := c.Param("id")
title := c.Param("title")
c.String(200, "user id: %s, title: %s", id, title)
})
request / response
http://127.0.0.1:8080/user/1/post/Is%20Gin%20the%20best%20choice%20as%20a%20BACKEND%20FRAMEWARK%3F
user_id: "1", title: "Is Gin the best choice as a BACKEND FRAMEWARK?"
ですがこれではファイルのパスなどを送る事が出来ません
任意の個数にしたい場合は:
ではなく*
を使用できます
r.GET("/image/:user_id/*path", func(c *gin.Context) {
id := c.Param("user_id")
path := c.Param("path")
c.String(200, "user id: %s, file path: %s", id, path)
})
request / response
http://127.0.0.1:8080/image/1/public/img/apple.png
user_id: "1", file_path: "/public/img/apple.png"
因みにc.Param
は便利ですが使わなくても一応生で取得できます
r.GET("/any/*path", func(c *gin.Context) {
path := c.Request.URL.Path
c.String(200, "path: %s", path)
})
クエリ文字列を取得する
クエリ文字列は/user?id=1
などの事ですがこれは.Query
で取得できます
r.GET("/color", func(c *gin.Context) {
primary := c.Query("primary")
c.String(200, "primary color: %s", primary)
})
またこちらも例に漏れず色々な方法があります
まずc.Query( key string )
のほかにc.Request.URL.Query().Get( key string )
があります、これはc.Query ( key string)
と等価ですですが後者の方は意味は伝わりやすいですが長いのでショートカットとして前者のc.Query( key string )
があります
1
2
3
-
4
+
5
+
6
r.GET("/color", func(c *gin.Context) {
primary := c.Query("primary")
c.String(200, "primary color: %s", primary)
secondary := c.Request.URL.Query().Get("secondary")
c.String(200, "primary color: %s, secondary color: %s", primary, secondary)
})
またデフォルトを設定したい場合はc.DefaultQuery
があります
1
2
3
4
-
5
+
6
+
7
r.GET("/color", func(c *gin.Context) {
primary := c.Query("primary")
secondary := c.Request.URL.Query().Get("secondary")
c.String(200, "primary color: %s, secondary color: %s", primary, secondary)
theme := c.DefaultQuery("theme", "light")
c.String(200, "theme: %s, primary color: %s, secondary color: %s", theme, primary, secondary)
})
また生で取得したい場合はc.Request.URL.RawQuery
があります
formの値を取得する
post
などのform
の値はc.PostForm
で取得できます、またこちらもc.DefaultPostForm
を使えばデフォルトの値を設定できます
r.POST("/user/create", func(c *gin.Context) {
username := c.DefaultPostForm("username", "apple")
password := c.PostForm("password")
c.String(201, "created!, username: %s, password: %s", username, password)
})
ヘッダーの値を取得する
ヘッダーの値はc.GetHeader
を使用して取得します、またc.GetHeader
とc.Request.Header.Get
は内部的にも機能的にも同じです
r.GET("/whoami", func(c *gin.Context) {
ua := c.GetHeader("User-Agent")
ct := c.Request.Header.Get("Content-Type")
ip := c.ClientIP()
c.String(200, "Content Type: %s, User Agent: %s, ip: %s", ct, ua, ip)
})
ファイルの取得と保存
ファイルはc.FormFile
で取得できます
また保存もc.SaveUploadedFile
を使えば保存できるためgin
の過保護さが窺えます
r.POST("/file/upload", func(c *gin.Context) {
file, _ := c.FormFile("file")
c.SaveUploadedFile(file, file.Filename)
c.String(201, "Uploaded file!")
})
レスポンス
文字列を返す
文字列はc.String
で返します
r.GET("/response/string", func(c *gin.Context) {
c.String(200, "OK!")
})
また今回は使って無いですがフォーマットを使用できます例えばc.String(200, "hi! %s", "apple")
とするとhi! apple
となります
Jsonを返す
jsonはc.Json
で返します
r.GET("/response/json", func(c *gin.Context) {
c.JSON(200, gin.H{
"msg": "Hello, world!",
})
})
二番目の引数にgin.H
を使ってますがこれはmap[string]interface{}
の短縮形ですその為map[string]interface{}{"msg": "Hello, world!",}
でも何ら問題は有りません
ファイルや画像を返す
画像などを返すにはc.File
が便利です
r.GET("/response/image", func(c *gin.Context) {
c.File("./static/img.png")
})
リダイレクト
リダイレクトです
r.GET("/response/redirect", func(c *gin.Context) {
c.Redirect(301, "https://github.com/gin-gonic/gin/blob/v1.8.2/context.go#L995")
})
その他
余談ですがc.Redirect
たc.String
などはc.Render
です
c.Render(-1, render.Redirect{
Code: 301,
Location: "https://github.com/gin-gonic/gin/blob/v1.8.2/context.go#L995",
Request: c.Request,
})
c.Render(200, render.String{Format: "OK!", Data: nil})
終わり
これでひと通りできた気がする
気のせいだったら言ってほしいが実装してないんだよな-
emoji四つくらい下に並べたいんだけどね
(追記: 実装したまし、emoji並べました)
リンク
ginのgithub
ginのgo.dev
このドキュメントどう?