2021SC@SDUSC
引言
sduoj是一个web应用,为了提高该项目的开发效率,我们可以选用一个后端框架。这里选用的是gin框架,它具有高性能、小巧、易用的优点,并深受gopher的喜爱。
接下来我们学习如何安装并使用gin。
首先创建项目目录gin-test
,并进入该目录,输入以下命令。
go mod init gin-test
go get -u github.com/gin-gonic/gin
创建main.go
,并输入以下代码。
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!"})
})
r.Run()
}
编译运行这个文件,然后它就可以对外提供服务了。
go run main.go
在运行结果中有下面一行,这说明本次启动时监听8080
端口,这是默认的端口号。
[GIN-debug] Listening and serving HTTP on :8080
然后我们可以对它进行访问,然后我们就可以获取到响应结果。
curl http://localhost:8080/
源码分析
gin.Default
在Default
方法中,debugPrintWARNINGDefault
用来检查Go版本能否达到gin的最低要求,我当前的gin版本是1.7.4
,它要求Go的版本要在1.13
以上。
Engine
的初始化交给New
函数,这个我们稍后在看。
我们将得到的Engine
实例引入Logger
和Recovery
两个中间件,前者用来输出请求日志,并标准化日志的格式,后者用来异常捕获,防止因为出现panic
导致服务崩溃,同时也将异常日志的格式标准化。
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
engine.Use
方法将全局中间件连接到路由,rebuild404Handlers
方法和rebuild405Handlers
方法分别处理找不到路由和找不到方法的情况。
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
engine.RouterGroup.Use(middleware...)
engine.rebuild404Handlers()
engine.rebuild405Handlers()
return engine
}
group.Use
方法将中间件加入到group.Handlers
中。
func (group *RouterGroup) Use(middleware ...HandlerFunc) IRoutes {
group.Handlers = append(group.Handlers, middleware...)
return group.returnObj()
}
gin.New
New
方法会对Engine
实例进行初始化并返回。
func New() *Engine {
debugPrintWARNINGNew()
engine := &Engine{
RouterGroup: RouterGroup{
Handlers: nil,
basePath: "/",
root: true,
},
FuncMap: template.FuncMap{},
RedirectTrailingSlash: true,
RedirectFixedPath: false,
HandleMethodNotAllowed: false,
ForwardedByClientIP: true,
RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
TrustedPlatform: defaultPlatform,
UseRawPath: false,
RemoveExtraSlash: false,
UnescapePathValues: true,
MaxMultipartMemory: defaultMultipartMemory,
trees: make(methodTrees, 0, 9),
delims: render.Delims{Left: "{{", Right: "}}"},
secureJSONPrefix: "while(1);",
trustedProxies: []string{"0.0.0.0/0"},
trustedCIDRs: defaultTrustedCIDRs,
}
engine.RouterGroup.engine = engine
engine.pool.New = func() interface{} {
return engine.allocateContext()
}
return engine
}
RouterGroup
:路由组,在内部用于配置路由,它与前缀和处理程序数组相关联。
RedirectTrailingSlash
:启用时,如果当前路由无法匹配,则自动重定向到带有(不带)尾随斜杠的处理程序中,即/hello
和/hello/
是相同的。
RedirectFixedPath
:启用时,如果当前请求路径没有被注册,那么路由会尝试修复该路径,比如删除多余的路径元素和不区分大小写进行路由查找。
HandleMethodNotAllowed
:启用时,如果当前请求没有被注册,路由会将其重定向到其他方法,比如在请求路径为/hello
的POST
方法时,如果此时该方法未注册,但存在路径为/hello
的GET
方法时,路由会重定向到该路径的GET
方法。
ForwardedByClientIP
:启用时,客户端 IP 将会被请求头解析出来。
UseRawPath
:启用时,url.RawPath
将会被用来获取参数。
UnescapePathValues
:启用时,路径值将会被取消转义。
MaxMultipartMemory
:用户控制最大的文件上传大小。
GET
GET
方法可以注册路由,同样的还有POST
方法、DELETE
方法等。
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodGet, relativePath, handlers)
}
relativePath
是路由的相对路径,handlers
是处理程序,在使用该路由时会被调用。
HandlerFunc
是处理程序的类型。
type HandlerFunc func(*Context)
GET
方法调用了handle
方法,并将常量值http.MethodGet
、相对路径relativePath
、处理程序handlers
作为参数传入到里面。
handle
方法能通过给定的路径和方法注册一个新的请求处理程序和中间件。
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
absolutePath := group.calculateAbsolutePath(relativePath)
handlers = group.combineHandlers(handlers)
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
calculateAbsolutePath
方法计算路由的绝对路径,即basePath
与我们定义的relativePath
进行组装之后得到的路径。
func (group *RouterGroup) calculateAbsolutePath(relativePath string) string {
return joinPaths(group.basePath, relativePath)
}
combineHandlers
合并现有的和新注册的Handle
,并返回一个函数链HandlersChain
(类型是[]HandlerFunc
)。从源码中我们可以看到,新传入的函数会附再原函数链后面。
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.Handlers) + len(handlers)
assert1(finalSize < int(abortIndex), "too many handlers")
mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, group.Handlers)
copy(mergedHandlers[len(group.Handlers):], handlers)
return mergedHandlers
}
addRoute
方法将当前注册的路由规则追加到对应的树中。
func (engine *Engine) addRoute(method, path string, handlers HandlersChain) {
assert1(path[0] == '/', "path must begin with '/'")
assert1(method != "", "HTTP method can not be empty")
assert1(len(handlers) > 0, "there must be at least one handler")
debugPrintRoute(method, path, handlers)
root := engine.trees.get(method)
if root == nil {
root = new(node)
root.fullPath = "/"
engine.trees = append(engine.trees, methodTree{method: method, root: root})
}
root.addRoute(path, handlers)
// Update maxParams
if paramsCount := countParams(path); paramsCount > engine.maxParams {
engine.maxParams = paramsCount
}
}
Run
Run
方法在解析完地址之后,调用http.ListenAndServe
方法,将engine
作为handle
注册进入,然后启动,开始对外提供 HTTP 服务。
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
}
在resolveAddress
方法中,我们看到,如果一开始没有地址参数传入,那么它会从环境变量PORT
中找端口号,找不到的话就用 8080 端口;如果传入了一个字符串,那么就将这个字符串作为端口号;如果传入字符串数量多于一个,则报参数过多错误。
func resolveAddress(addr []string) string {
switch len(addr) {
case 0:
if port := os.Getenv("PORT"); port != "" {
debugPrint("Environment variable PORT=\"%s\"", port)
return ":" + port
}
debugPrint("Environment variable PORT is undefined. Using port :8080 by default")
return ":8080"
case 1:
return addr[0]
default:
panic("too many parameters")
}
}