天天看點

go-zero源碼閱讀-代碼結構#第一期

go-zero 作為一個微服務架構,不僅給我們提供了很好的參考,而且核心代碼量不多,我們可以在閑暇時間讀讀他的核心代碼,來多多學習充電。

rest 部分

代碼結構

rest
├── handler // 自帶中間件
│   ├── authhandler.go // 權限
│   ├── breakerhandler.go // 斷路器
│   ├── contentsecurityhandler.go // 安全驗證
│   ├── cryptionhandler.go // 加密解密
│   ├── gunziphandler.go // zip 壓縮
│   ├── loghandler.go // 日志
│   ├── maxbyteshandler.go // 最大請求資料限制
│   ├── maxconnshandler.go // 最大請求連接配接數限制
│   ├── metrichandler.go // 請求名額統計
│   ├── prometheushandler.go // prometheus 上報
│   ├── recoverhandler.go // 錯誤捕獲
│   ├── sheddinghandler.go // 過載保護
│   ├── timeouthandler.go // 逾時控制
│   └── tracinghandler.go // 鍊路追蹤
├── httpx
│   ├── requests.go
│   ├── responses.go
│   ├── router.go
│   ├── util.go
│   └── vars.go
├── internal
│   ├── cors // 跨域處理
│   │   └── handlers.go
│   ├── response
│   │   ├── headeronceresponsewriter.go
│   │   └── withcoderesponsewriter.go
│   ├── security // 加密處理
│   │   └── contentsecurity.go
│   ├── log.go
│   └── starter.go
├── pathvar // path 參數解析
│   └── params.go
├── router
│   └── patrouter.go
├── token
│   └── tokenparser.go
├── config.go // 配置
├── engine.go // 引擎
├── server.go
└── types.go
           

服務啟動流程

我們以 go-zero-example 項目 http/demo/main.go 代碼來分析

go-zero源碼閱讀-代碼結構#第一期

go-zero 給我們提供了如下元件與服務,我們來逐一閱讀分析

  • http架構正常元件(路由、排程器、中間件、跨域)
  • 權限控制
  • 斷路器
  • 限流器
  • 過載保護
  • prometheus
  • trace
  • cache

http架構正常元件

路由

路由使用的是二叉查找樹,高效的路由都會使用樹形結構來建構

二叉查找樹可參見源碼

https://github.com/zeromicro/go-zero/tree/master/core/search

go-zero 路由實作了 http\server.go Handler interface 來攔截每個請求

入口源碼位址: github.com/zeromicro/go-zero/rest/router/patrouter.go

func (pr *patRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	reqPath := path.Clean(r.URL.Path) // 傳回相當于path的最短路徑名稱
	if tree, ok := pr.trees[r.Method]; ok { // 查找對應 http method
		if result, ok := tree.Search(reqPath); ok { // 查找路由 path 
			if len(result.Params) > 0 {
				r = pathvar.WithVars(r, result.Params) // 擷取路由參數并且添加到 *http.Request 中
			}
			result.Item.(http.Handler).ServeHTTP(w, r) // 排程方法
			return
		}
	}

	allows, ok := pr.methodsAllowed(r.Method, reqPath)
	if !ok {
		pr.handleNotFound(w, r)
		return
	}

	if pr.notAllowed != nil {
		pr.notAllowed.ServeHTTP(w, r)
	} else {
		w.Header().Set(allowHeader, allows)
		w.WriteHeader(http.StatusMethodNotAllowed)
	}
}
           
排程器

go-zero 沒有排程器,在上文 ServeHTTP 中已經使用了排程器,這歸結于 golang 已經給我們實作了一個很好的 http 子產品,如果是其他語言,我們在設計架構的時候往往要自己實作排程器。

中間件

我們可以在 *.api 中添加如下代碼來使用

@server(
	middleware: Example // 路由中間件聲明
)
service User {
	@handler UserInfo
	post /api/user/userinfo returns (UserInfoResponse)
}
           

通過生成代碼指令,生成的代碼如下

package middleware

import (
	"log"
	"net/http"
)

type ExampleMiddleware struct{}

func NewExampleMiddleware() *ExampleMiddleware {
	return &ExampleMiddleware{}
}

func (m *ExampleMiddleware) Handle(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		// TODO generate middleware implement function, delete after code implementation
		next(w, r)
	}
}
           

go-zero 給我們提供了一些常用的中間件,友善我們在開發時候使用

  • rest.WithCors() 跨域設定
// example
server := rest.MustNewServer(c.RestConf, rest.WithCors("localhost:8080"))

// 源碼
func WithCors(origin ...string) RunOption {
	return func(server *Server) {
		server.router.SetNotAllowedHandler(cors.NotAllowedHandler(nil, origin...))
		server.Use(cors.Middleware(nil, origin...))
	}
}
           
跨域
  • resrt.WithCustomCors() 自定義跨域方法
// example
var origins = []string{
	"localhost:8080",
}
server := rest.MustNewServer(c.RestConf,
	rest.WithCustomCors(
        // 設定 http header
		func(header http.Header) {
			header.Set("Access-Control-Allow-Origin", "Access-Control-Allow-Origin")
		},
        // 不允許位址傳回指定資料
		func(writer http.ResponseWriter) {
			writer.Write([]byte("not allow"))
		},
        // 允許跨域位址
		origins...,
	),
)

// 源碼
func WithCustomCors(middlewareFn func(header http.Header), notAllowedFn func(http.ResponseWriter),
	origin ...string) RunOption {
	return func(server *Server) {
		server.router.SetNotAllowedHandler(cors.NotAllowedHandler(notAllowedFn, origin...))
		server.Use(cors.Middleware(middlewareFn, origin...))
	}
}
           
  • rest.WithJwt() jwt
// example
rest.WithJwt("uOvKLmVfztaXGpNYd4Z0I1SiT7MweJhl")

// 源碼
func WithJwt(secret string) RouteOption {
	return func(r *featuredRoutes) {
		validateSecret(secret)
		r.jwt.enabled = true
		r.jwt.secret = secret
	}
}
           
  • rest.WithJwtTransition() jwt token 轉換,新老 token 可以同時使用
// example
rest.WithJwtTransition("uOvKLmVfztaXGpNYd4Z0I1SiT7MweJhl", "uOvKLmVfztaXGpNYd4Z0I1SiT7MweJh2")

// 源碼
func WithJwtTransition(secret, prevSecret string) RouteOption {
	return func(r *featuredRoutes) {
		// why not validate prevSecret, because prevSecret is an already used one,
		// even it not meet our requirement, we still need to allow the transition.
		validateSecret(secret)
		r.jwt.enabled = true
		r.jwt.secret = secret
		r.jwt.prevSecret = prevSecret
	}
}
           

權限控制

入口源碼位址:github.com/zeromicro/go-zero/rest/handler/authhandler.go

權限控制核心檔案帶注釋代碼如下,大家可以參閱

  • https://github.com/TTSimple/go-zero-source/tree/master/code/rest/rest/handler/authhandler.go
  • https://github.com/TTSimple/go-zero-source/tree/master/code/rest/rest/token/tokenparser.go

go-zero 提供 jwt 權限控制,jwt 隻做登入與未登入驗證,細粒度的權限驗證我們可以使用其他成熟方案

jwt 原理不複雜,有興趣的可以翻閱源碼學習

繼續閱讀