【sduoj】后端框架的引入

本文介绍了Gin框架的安装、基本使用和核心功能,包括创建Web应用、运行服务、处理HTTP请求以及Gin框架的内部机制如中间件、路由注册等。通过示例展示了如何创建一个简单的GET请求并返回响应。此外,还详细分析了Gin的Default方法、New方法、RouterGroup、Run方法等关键函数的实现。
摘要由CSDN通过智能技术生成

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实例引入LoggerRecovery两个中间件,前者用来输出请求日志,并标准化日志的格式,后者用来异常捕获,防止因为出现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:启用时,如果当前请求没有被注册,路由会将其重定向到其他方法,比如在请求路径为/helloPOST方法时,如果此时该方法未注册,但存在路径为/helloGET方法时,路由会重定向到该路径的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")
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值