2023-1-24

go

gin

Posted by

applemango

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.Loggergin.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.GetHeaderc.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.Redirectc.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

このドキュメントどう?

emoji
emoji
emoji
emoji