就决定是你啦,gin!
前言
我有一个朋友,他叫李晓得,入行多年php,业务代码得心应手。左手laravel,右手swoole,一杯茶,一根烟,一行代码敲一天。终于有一天他做了一个决定:“我要转做golang!”隔壁B哥见状面露鄙夷之色:“go只是个玩具,php才是世界上最好的语言。”李晓得当即表示:“我不管,我就要折腾...”随后收拾起包袱,踏上了golang的转型之路...
开局一个web,经验全靠敲
在经过李晓得的充分准备之后,他开始了第一个单体web应用选择,他参考了github的:
框架名称 | github-star | 诞生日期 | 备注 |
---|---|---|---|
gin | 68k+ | 2014年 | 轻量级,高性能,流行度高 |
beego | 29k+ | 2012年 | 国人开发的web框架,老牌,集成度和成熟度高 |
fiber | 25k+ | 2020年 | 基于fasthttp实现(没错,就是那个可以比net/http快10倍的http实现),后起之秀 |
echo | 25k+ | 2015年 | 高性能、极简框架 |
iris | 23k+ | 2016年 | 提供完整的MVC |
web框架的使用都是大同小异,没有过多的犹豫,李晓得选择了gin,因为gin够轻量,是个不错的学习框架。
不一会,按照gin官网照葫芦画瓢第一个"hello world"就出来了,easy!
package main
import (
"context"
"github.com/gin-gonic/gin"
"log"
"net/http"
"os"
"os/signal"
"time"
)
func main() {
//设置路由
router := gin.Default()
router.Use(gin.Recovery())
router.Use(corsMiddleware())
router.GET("/", func(c *gin.Context) {
c.String(http.StatusOK, "Welcome Gin Server")
})
//启动服务
srv := &http.Server{
Addr: ":8080",
Handler: router,
}
go func() {
// 服务连接
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
// 等待中断信号以优雅地关闭服务器(设置 5 秒的超时时间)
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt)
<-quit
log.Println("Shutdown Server ...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}
log.Println("Server exiting")
}
// corsMiddleware cors中间件
func corsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma")
c.Header("Access-Control-Allow-Methods", "POST,GET,OPTIONS,PUT,DELETE")
c.Header("Access-Control-Allow-Credentials", "true")
method := c.Request.Method
if method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
}
c.Next()
}
}
运行访问一下,很顺利,看下响应时间:"0s",很合理吧。
[GIN-debug] GET / --> main.main.func1 (5 handlers)
[GIN] xxxx/xx/xx - xx:xx:xx | 200 | 0s | 127.0.0.1 | GET "/"
这个请求都经历了些啥?
首先,accept客户端的发起的连接,新建c.serve协程处理该请求。
for {
rw, err := l.Accept()
if err != nil {
//错误逻辑处理
return err
}
connCtx := ctx
//...
c := srv.newConn(rw)
c.setState(c.rwc, StateNew, runHooks) // before Serve can return
go c.serve(connCtx)
}
进入serve中的ServeHTTP方法处理
// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
//...
serverHandler{c.server}.ServeHTTP(w, w.req)
}
由于go语言的接口都是隐式继承的,所以调用的gin.Engine下的ServeHTTP方法。gin使用的是sync.Pool管理对象的思想对协程使用非常友好,究其原因是对象池拥有重用性,重用对象避免请求过多时内存的频繁申请,同时大大降低了垃圾回收所带来的消耗。有效避免其多个协程后,每个请求协程所带来的gc开销过大导致协程优势丧失。
// ServeHTTP conforms to the http.Handler interface.
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
c := engine.pool.Get().(*Context)
c.writermem.reset(w)
c.Request = req
c.reset()
engine.handleHTTPRequest(c)
engine.pool.Put(c)
}
让我们看看真正开始处理请求handleHTTPRequest()都干了些啥。
func (engine *Engine) handleHTTPRequest(c *Context) {
// 略
// 这里gin用的是Radix Tree(基数树)的方式保存路由信息,这里不展开讲,有兴趣可以自行了解
t := engine.trees
for i, tl := 0, len(t); i < tl; i++ {
if t[i].method != httpMethod {
continue
}
root := t[i].root
// 在基数树查找到路由结果
value := root.getValue(rPath, c.params, c.skippedNodes, unescape)
if value.params != nil {
c.Params = *value.params
}
if value.handlers != nil {
c.handlers = value.handlers
c.fullPath = value.fullPath
c.Next() // 处理函数链的调用 包括middleware和需要处理业务逻辑的函数
c.writermem.WriteHeaderNow()
return
}
if httpMethod != http.MethodConnect && rPath != "/" {
if value.tsr && engine.RedirectTrailingSlash {
redirectTrailingSlash(c)
return
}
if engine.RedirectFixedPath && redirectFixedPath(c, root, engine.RedirectFixedPath) {
return
}
}
break
}
// 略
}
不是吧,这年头没点算法功底都没法看代码啊,为啥要用基数树啊。那是因为基数树拥有公共前缀字符串的特点,查询高效,空间利用率高,拓展性强!不懂,很好,那就把想懂的想法吞到肚子里。
至此,整个请求的处理链路已经很清晰了。
好的,简简单单拿下今日首胜~李晓得感觉自己行了!
本故事纯属虚构,怎么可能是真事,不会是真事吧...
转载自:https://juejin.cn/post/7225454068406353979