这是我参与「第三届青训营 -后端场」笔记创作活动的的第5篇笔记
Go Web开发入门学习
原生web框架
package main
import (
"fmt"
"log"
"net/http"
"weblearning/originweb"
)
func main() {
fmt.Println("Starting the server")
hellohandler := originweb.NewHelloHandler()
//设置路由 传入必须实现Handler接口
http.Handle("/Hello", hellohandler)
//创建服务器 ListenAndServer设置监听端口 若服务器宕机,则返回异常
log.Fatal(http.ListenAndServe("localhost:8080", nil))
}
package originweb
import (
"bytes"
"fmt"
"net/http"
)
type HelloHandler struct {
m map[string]string
}
func NewHelloHandler() *HelloHandler {
return &HelloHandler{m: make(map[string]string)}
}
//实现Handler接口
func (h HelloHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
//接收request中body
b := request.Body
defer b.Close()
buf := bytes.Buffer{}
buf.ReadFrom(b)
//转换为字符串
s := buf.String()
fmt.Printf("get request :\nMethod:%s\n%s\n%s\n", request.Method, request.RequestURI, s)
//返回body
writer.Write(buf.Bytes())
}
返回示例
存在问题:
- 请求方法必须要先由路由拦截
- 需要手动处理IO,读取后body需要映射到实例
- 参数需要用正则取得
- 路由管理分组混乱
- 返回值需要我们手动处理为[]byte类型
路由改进
使用httprouter框架httprouter
Gin等框架也采用了httprouter框架
go get github.com/julienschmidt/httprouter
实现
http.ListenAndServe("localhost:8080", handler)
当ListenAndServer方法传入handler时,Go将忽略http.Handle中注册的路由,使用该位置Handler解析所有路由 httprouter构建了此位置Handler,请求发送至服务,拦截后先经由默认Handler进行处理,然后进入到httprouter框架,分发给你在框架中注册的路由
package main
import (
"fmt"
"github.com/julienschmidt/httprouter"
"log"
"net/http"
)
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprintf(w, "hello\n")
}
func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
fmt.Fprintf(w, "hello,%s\n", ps.ByName("name"))
}
func main() {
//创建路由
router := httprouter.New()
router.GET("/", Index)
//:name进行参数绑定 通过ByName()方法获取
router.GET("/hello/:name", Hello)
log.Fatal(http.ListenAndServe("localhost:8080", router))
}
httprouter提供的接口
传入handle为func变量,相较于原生更为灵活
httprouter支持配置CORS,同时我们可以利用Go函数闭包(匿名函数),将原有逻辑封装一层,达到预处理效果
httprouter只是路由框架,本质上还是使用的原生http服务器
- 设置跨域示例
router.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Access-Control-Request-Method") != "" {
// Set CORS headers
header := w.Header()
header.Set("Access-Control-Allow-Methods", header.Get("Allow"))
header.Set("Access-Control-Allow-Origin", "*")
}
// Adjust status code to 204
w.WriteHeader(http.StatusNoContent)
})
- 设置预处理示例
- Basic Authentication
package main
import (
"fmt"
"log"
"net/http"
"github.com/julienschmidt/httprouter"
)
//将原有逻辑再次封装,加入验证环境
func BasicAuth(h httprouter.Handle, requiredUser, requiredPassword string) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
// Get the Basic Authentication credentials
user, password, hasAuth := r.BasicAuth()
if hasAuth && user == requiredUser && password == requiredPassword {
// Delegate request to the given handle
h(w, r, ps)
} else {
// Request Basic Authentication otherwise
w.Header().Set("WWW-Authenticate", "Basic realm=Restricted")
http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
}
}
}
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "Not protected!\n")
}
func Protected(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "Protected!\n")
}
func main() {
user := "gordon"
pass := "secret!"
router := httprouter.New()
router.GET("/", Index)
router.GET("/protected/", BasicAuth(Protected, user, pass))
log.Fatal(http.ListenAndServe(":8080", router))
}
Gin框架
go get github.com/gin-gonic/gin
创建路由引擎
r := gin.New()
r := gin.Default()
Default()创建的路由会默认使用Logger()和Recovery()中间件
// Default returns an Engine instance with the Logger and Recovery middleware already attached.
func Default() *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine
}
中间件
Use()接口会将Logger()和Recover()处理函数追加到UserHandler()前,形成handers链
中间件部署是使用了gin.Use()
// HandlerFunc defines the handler used by gin middleware as return value.
type HandlerFunc func(*Context)
// HandlersChain defines a HandlerFunc array.
type HandlersChain []HandlerFunc
func (engine *Engine) Use(middleware ...HandlerFunc) IRoutes {
engine.RouterGroup.Use(middleware...)
engine.rebuild404Handlers()
engine.rebuild405Handlers()
return engine
}
gin的中间件就是一个HandlerFunc数组 HandlerFunc是一个返回类型为*Context的函数
默认中间件Logger与Recovery
可以参考Go Web开发入门指南<前半> By 可乐可乐可 --Gin默认的两个middleware
Gin Router配置
Gin框架提供了分组路由
func main() {
router := gin.Default()
// Simple group: v1
v1 := router.Group("/v1")
{
v1.POST("/login", loginEndpoint)
v1.POST("/submit", submitEndpoint)
v1.POST("/read", readEndpoint)
}
// Simple group: v2
v2 := router.Group("/v2")
{
v2.POST("/login", loginEndpoint)
v2.POST("/submit", submitEndpoint)
v2.POST("/read", readEndpoint)
}
router.Run(":8080")
}
Group()函数
// Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix.
// For example, all the routes that use a common middleware for authorization could be grouped.
//传入url HandlerFunc数组
func (group *RouterGroup) Group(relativePath string, handlers ...HandlerFunc) *RouterGroup {
//继续返回一个RouterGroup
return &RouterGroup{
Handlers: group.combineHandlers(handlers),
basePath: group.calculateAbsolutePath(relativePath),
engine: group.engine,
}
}
combineHandlers()函数
func (group *RouterGroup) combineHandlers(handlers HandlersChain) HandlersChain {
finalSize := len(group.Handlers) + len(handlers)
if finalSize >= int(abortIndex) {
panic("too many handlers")
}
//复制传入的路由添加至原数组 手动合并后返回
mergedHandlers := make(HandlersChain, finalSize)
copy(mergedHandlers, group.Handlers)
copy(mergedHandlers[len(group.Handlers):], handlers)
return mergedHandlers
}
总结
- roup与父Group公用engine
- roup会获取父Group当前的Handler拼装成自己的Group,形成一个Handler调用链,我们可以在父Group插入Handler链作为中间件
- 子Goup通过计算得到自己的绝对路径
若在子Group创建后再在父Group添加中间件,子Group无法同步handler链 所有的router都处在了gin.Engine下,RouterGroup.Get/POst/..方法才可以注册路由 GET方法中调用了RouterGoup.handle()
// GET is a shortcut for router.Handle("GET", path, handle).
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle("GET", relativePath, handlers)
}
handle通过engine.addRouter注册路由
func (group *RouterGroup) handle(httpMethod, relativePath string, handlers HandlersChain) IRoutes {
//路径拼接
absolutePath := group.calculateAbsolutePath(relativePath)
//生成handlers链
handlers = group.combineHandlers(handlers)
group.engine.addRoute(httpMethod, absolutePath, handlers)
return group.returnObj()
}
所以RouterGroup内engine
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)
engine.trees = append(engine.trees, methodTree{method: method, root: root})
}
root.addRoute(path, handlers)
}
所以创建RouterGoup时添加的Handler不会被注册在路由,而是在使用GET,POST等方法时,返回一条handlers链,注册路由到路由表。
Gin路由记录方法
Gin的路由由一颗methodTrees成
在小量数据下,使用map是不划算的,map的构建与维护,包括hash操作,在小量数据下,是一种入不敷出的选择,所以这里,遍历比map效率更高
type methodTree struct {
method string
root *node
}
//返回根节点
func (trees methodTrees) get(method string) *node {
for _, tree := range trees {
if tree.method == method {
return tree.root
}
}
return nil
}
type node struct {
path string
indices string
children []*node
handlers HandlersChain
priority uint32
nType nodeType
maxParams uint8
wildChild bool
}
Gin在注册路由时,遍历树,找到自己的位置,将绝对路径与调用链一起记录
root := engine.trees.get(method)
if root == nil {
root = new(node)
engine.trees = append(engine.trees, methodTree{method: method, root: root})
}
root.addRoute(path, handlers)
Gin启动
创建engine进行添加路由等工作
最后
engine.Run(":8080")
参数为空则默认为8080端口,可以传入多个地址进行监听
参数解析与绑定
type HandlerFunc func(*Context) 传入了context实例
// Context is the most important part of gin. It allows us to pass variables between middleware,
// manage the flow, validate the JSON of a request and render a JSON response for example.
type Context struct {
writermem responseWriter
Request *http.Request
Writer ResponseWriter
Params Params
handlers HandlersChain
index int8
fullPath string
engine *Engine
params *Params
skippedNodes *[]skippedNode
// This mutex protect Keys map
mu sync.RWMutex
// Keys is a key/value pair exclusively for the context of each request.
Keys map[string]interface{}
// Errors is a list of errors attached to all the handlers/middlewares who used this context.
Errors errorMsgs
// Accepted defines a list of manually accepted formats for content negotiation.
Accepted []string
// queryCache use url.ParseQuery cached the param query result from c.Request.URL.Query()
queryCache url.Values
// formCache use url.ParseQuery cached PostForm contains the parsed form data from POST, PATCH,
// or PUT body parameters.
formCache url.Values
// SameSite allows a server to define a cookie attribute making it impossible for
// the browser to send this cookie along with cross-site requests.
sameSite http.SameSite
}
主要包括了以下方法
- handler信息
- 调用链控制
- 错误管理
- 上下文资源(类似Java Tread Local)控制
- 获取参数
- 写响应
请求信息
调用链控制
上下文资源方法
参数获取方法
总结
很多方法最近也用不到,我就不写了,主要是还是学习Go Web开发入门指南<前半> By 可乐可乐可大佬文章的笔记是重复23333,参数解析绑定这里他写的十分详细,我就不copy过来了哈哈哈哈
第五步,Gin 参数解析,绑定
参考来源
Go Web开发入门指南<前半> By 可乐可乐可
github.com/gin-gonic/g…
gin框架源码学习笔记(二)中间件