文章目录
- 快速使用
- 返回响应
- 路由匹配
- path
- query
- Multipart/Urlencoded Form
- 解析请求
- MultipartFrom
- MiddleWare
github.com/gin-gonic/gin 是 golang 的 web 框架,其用字典树做路由匹配、支持中间件,本文介绍其源码实现。
快速使用
package mainimport ("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 examples repository 可以找到官方使用示例。
返回响应
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()})
}
路由匹配
path
package mainimport ("github.com/gin-gonic/gin""net/http"
)func main() {router := gin.Default()// 可以用:匹配路径 This handler will match /user/john or /user/ but will not match /userrouter.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 " + actionc.String(http.StatusOK, message)}) 可以用完整路径匹配 For each matched request Context will hold the route definitionrouter.POST("/user/:name/*action", func(c *gin.Context) {b := c.FullPath() == "/user/:name/*action" // truec.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/... routesrouter.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 ,怎么能让参数name匹配成 b/c),示例如下:
query
package mainimport ("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=Doerouter.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")
}
Multipart/Urlencoded Form
package mainimport ("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")
}
解析请求
MultipartFrom
// 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 errorif 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 = freturn 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][]stringFile map[string][]*FileHeader
}// A FileHeader describes a file part of a multipart request.
type FileHeader struct {Filename stringHeader textproto.MIMEHeaderSize int64content []bytetmpfile string
}
MiddleWare
gin 的 middleware 和 处理函数,都是 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 有各种中间件,是通过数组实现的。