github.com/gin-gonic/gin は、ルーティングマッチングに辞書ツリーを使用し、ミドルウェアをサポートする golang の Web フレームワークであり、この記事ではそのソースコードの実装を紹介します。
すぐに使える
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
r.Run()
}
// curl localhost:8000/ping 则返回 PONG
encoding/json
ライブラリが使用されるgo build -tags=jsoniter .
場合デフォルトではgithub.com/json-iterator/go
ライブラリを使用します。
公式の使用例は、 Gin サンプル リポジトリにあります。
応答を返す
func Error500(ctx *gin.Context, err error) {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"msg": err.Error()})
}
func Error400(ctx *gin.Context, err error) {
ctx.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
"msg": err.Error()})
}
ルートマッチング
道
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
router := gin.Default()
// 可以用:匹配路径
This handler will match /user/john or /user/ but will not match /user
router.GET("/user/:name", func(c *gin.Context) {
name := c.Param("name")
c.String(http.StatusOK, "Hello %s", name)
})
// 可以用:匹配路径,可以用*做可选路径匹配(匹配的结果带/)
However, this one will match /user/john/ and also /user/john/send
If no other routers match /user/john, it will redirect to /user/john/
router.GET("/user/:name/*action", func(c *gin.Context) {
name := c.Param("name")
action := c.Param("action")
message := name + " is " + action
c.String(http.StatusOK, message)
})
可以用完整路径匹配
For each matched request Context will hold the route definition
router.POST("/user/:name/*action", func(c *gin.Context) {
b := c.FullPath() == "/user/:name/*action" // true
c.String(http.StatusOK, "%t, %v", b, c.FullPath())
})
// 如果没有:或* 则表示路由组,可保证路由树解析优先级高于:路径匹配
// This handler will add a new router for /user/groups.
// Exact routes are resolved before param routes, regardless of the order they were defined.
// Routes starting with /user/groups are never interpreted as /user/:name/... routes
router.GET("/user/groups", func(c *gin.Context) {
c.String(http.StatusOK, "The available groups are [...]")
})
router.Run(":8080")
}
これは、可変長の URL を照合する*
ために使用でき。たとえば、要件は (ルート /a/:name/d が定義されており、実際に要求された URL は /a/b/c/d です。パラメーター名はどのようにして b/c と一致しますか) の場合、例は次のとおりです。
クエリ
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
router := gin.Default()
// Query string parameters are parsed using the existing underlying request object.
// The request responds to an url matching: /welcome?firstname=Jane&lastname=Doe
router.GET("/welcome", func(c *gin.Context) {
firstname := c.DefaultQuery("firstname", "Guest")
lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")
c.String(http.StatusOK, "Hello %s %s", firstname, lastname)
})
router.Run(":8080")
}
マルチパート/Urlencoded フォーム
package main
import (
"github.com/gin-gonic/gin"
"net/http"
)
func main() {
router := gin.Default()
router.POST("/form_post", func(c *gin.Context) {
message := c.PostForm("message")
nick := c.DefaultPostForm("nick", "anonymous")
c.JSON(http.StatusOK, gin.H{
"status": "posted",
"message": message,
"nick": nick,
})
})
router.Run(":8080")
}
リクエストを解析する
マルチパートから
// ParseMultipartForm 解析 multipart/form-data 类型的 Content-Type,其将收到的文件先存储在内存中,若超限则存储在磁盘中
// ParseMultipartForm parses a request body as multipart/form-data.
// The whole request body is parsed and up to a total of maxMemory bytes of
// its file parts are stored in memory, with the remainder stored on
// disk in temporary files.
// ParseMultipartForm calls ParseForm if necessary.
// If ParseForm returns an error, ParseMultipartForm returns it but also
// continues parsing the request body.
// After one call to ParseMultipartForm, subsequent calls have no effect.
func (r *Request) ParseMultipartForm(maxMemory int64) error {
if r.MultipartForm == multipartByReader {
return errors.New("http: multipart handled by MultipartReader")
}
var parseFormErr error
if r.Form == nil {
// Let errors in ParseForm fall through, and just
// return it at the end.
parseFormErr = r.ParseForm()
}
if r.MultipartForm != nil {
return nil
}
mr, err := r.multipartReader(false)
if err != nil {
return err
}
f, err := mr.ReadForm(maxMemory)
if err != nil {
return err
}
if r.PostForm == nil {
r.PostForm = make(url.Values)
}
for k, v := range f.Value {
r.Form[k] = append(r.Form[k], v...)
// r.PostForm should also be populated. See Issue 9305.
r.PostForm[k] = append(r.PostForm[k], v...)
}
r.MultipartForm = f
return parseFormErr
}
postmanで渡す方法は次のとおりです。
gin がパラメータを解析するとき、それはmultipartForm.Value["k"]
受信方法で受け取ることができます[]string
。
解析されたパラメータは次のように定義されます。
// Form is a parsed multipart form.
// Its File parts are stored either in memory or on disk,
// and are accessible via the *FileHeader's Open method.
// Its Value parts are stored as strings.
// Both are keyed by field name.
type Form struct {
Value map[string][]string
File map[string][]*FileHeader
}
// A FileHeader describes a file part of a multipart request.
type FileHeader struct {
Filename string
Header textproto.MIMEHeader
Size int64
content []byte
tmpfile string
}
ミドルウェア
gin のミドルウェアと処理関数は両方とも HandlerFunc 型です。
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)
// HandlersChain defines a HandlerFunc slice.
type HandlersChain []HandlerFunc
Gin にはさまざまなミドルウェアがあり、それらは配列を通じて実装されます。