上篇提到 DefaultServerMux 作為默認(rèn)的 HTTP Server 框架太過(guò)簡(jiǎn)單峻堰,缺少很多功能咽袜。這篇我們利用官方庫(kù)和一些三方庫(kù)來(lái)定制一個(gè)簡(jiǎn)易合用的 HTTP Server 框架丸卷。完整代碼見(jiàn)這里
Router
首先要有 router 模塊,這里我使用第三方 gorilla 框架的最小化路由模塊 mux询刹,它的作用和 DefaultServerMux 差不多谜嫉,只不過(guò)支持了 RESTful API萎坷。
在添加路由和對(duì)應(yīng) handler 時(shí),很可能我們寫(xiě)的處理函數(shù)有 bug沐兰,導(dǎo)致沒(méi)有往 response 里寫(xiě)入內(nèi)容就返回哆档,這會(huì)造成客戶(hù)端阻塞等待,所以當(dāng)出現(xiàn)錯(cuò)誤提前返回時(shí)僧鲁,需要一個(gè)默認(rèn)的錯(cuò)誤處理函數(shù)虐呻,給客戶(hù)端返回默認(rèn)錯(cuò)誤信息。
import (
"net/http"
"github.com/gorilla/mux"
)
type Router struct {
router *mux.Router
ctxPool sync.Pool
errHandler func(w http.responseWriter, r *http.request)
}
很多時(shí)候寞秃,執(zhí)行路由對(duì)應(yīng) handler 時(shí)我們并不想直接操作 http.responseWriter 和 *http.request斟叼,并且希望有一些簡(jiǎn)單的封裝,提供更多的功能春寿。再者朗涩,這兩個(gè)對(duì)象并不能很好的攜帶中間件處理過(guò)程中產(chǎn)生的一些參數(shù)。所以我們會(huì)定義一個(gè) Context (下一節(jié))來(lái)封裝它們绑改。每一個(gè)請(qǐng)求都應(yīng)該有一個(gè) Context谢床,為了方便的管理,使用 sync.Pool 做一個(gè) context 池厘线。
創(chuàng)建新的 Router:
// NewRouter returns a router.
func NewRouter() *Router {
r := &Router{
router: mux.NewRouter(),
errHandler: func(_ *Context) {},
}
r.ctxPool.New = func() interface{} {
return NewContext(nil, nil)
}
r.router.NotFoundHandler = http.NotFoundHandler()
r.router.MethodNotAllowedHandler = MethodNotAllowedHandler()
return r
}
router 注冊(cè)路由识腿,由于使用 gorilla.mux,調(diào)用其 HandleFunc 造壮,返回 router 本身渡讼,在調(diào)用 Method 即可指定請(qǐng)求方法。不過(guò)我們還可以在自己的 handler 執(zhí)行之前耳璧,提供一些鉤子成箫,這里我們可以添加一些 filter 函數(shù),以便功能擴(kuò)展旨枯。
type FilterFunc func(*Context) bool
func (rt *Router) Get(pattern string, handler HandlerFunc, filters ...FilterFunc) {
rt.router.HandleFunc(pattern, rt.wrapHandlerFunc(handler, filters...)).Methods("GET")
}
// Post adds a route path access via POST method.
func (rt *Router) Post(pattern string, handler HandlerFunc, filters ...FilterFunc) {
rt.router.HandleFunc(pattern, rt.wrapHandlerFunc(handler, filters...)).Methods("POST")
}
// Wraps a HandlerFunc to a http.HandlerFunc.
func (rt *Router) wrapHandlerFunc(f HandlerFunc, filters ...FilterFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
c := rt.ctxPool.Get().(*Context)
defer rt.ctxPool.Put(c)
c.Reset(w, r)
if len(filters) > 0 {
for _, filter := range filters {
if passed := filter(c); !passed {
c.LastError = errFilterNotPassed
return
}
}
}
if err := f(c); err != nil {
c.LastError = err
rt.errHandler(c)
}
}
}
Context
前面提到可以用一個(gè) Context 包裝 http.responseWriter 和 *http.request蹬昌,并且提供一些額外的功能。額外的功能如 validator攀隔,用來(lái)對(duì)請(qǐng)求做參數(shù)驗(yàn)證皂贩。這個(gè) validator 我們可以直接用一個(gè)第三方庫(kù),也可以做成 Interface 以便升級(jí)昆汹。
另外我們可能需要 Context 能夠攜帶額外的信息先紫,所以可以加一個(gè) map 用來(lái)存儲(chǔ)。
type Context struct {
responseWriter http.ResponseWriter
request *http.Request
Validator *validator.Validate
store map[string]interface{}
}
不要忘了在 Router 里面我們是用一個(gè)線程安全的池來(lái)管理 context 筹煮,也就是每次用完 context 需要還回去來(lái)避免臨時(shí)分配帶來(lái)的開(kāi)銷(xiāo)。所以別忘了還回去之前需要把 context 重置成原來(lái)的樣子居夹。
func (c *Context) Reset(w http.ResponseWriter, r *http.Request) {
c.responseWriter = w
c.request = r
c.store = make(map[string]interface{})
}
Server
有了 router 和 context败潦,我們還需要封裝一個(gè) server本冲。首先定義一個(gè) EntryPoiont 結(jié)構(gòu)體,當(dāng)然名字隨意劫扒。非常確認(rèn)的是我們需要用到 http 包的 Server檬洞,還可以加上可能用到的 net.Listener。另外沟饥,我們需要方便的添加一些即插即用的工具添怔,所以需要中間件,這里我使用第三方庫(kù) negroni 贤旷。然后我們可能需要一個(gè)通知關(guān)閉所有連接的機(jī)制广料,用一個(gè) channel 可以做到。所以 EntryPoint 大致如下:
type Entrypoint struct {
server *http.Server
listener net.Listener
middlewares []negroni.Handler
}
negroni
其實(shí) negroni 的核心代碼也很簡(jiǎn)單幼驶,就只是把多個(gè) middleware 串起來(lái)使其能夠串行調(diào)用艾杏。
type Negroni struct {
middleware middleware
handlers []Handler
}
type middleware struct {
handler Handler
next *middleware
}
type Handler interface {
ServeHTTP(rw http.ResponseWriter, r *http.Request, next http.HandlerFunc)
}
關(guān)鍵就是 Handler 接口,所有第三方實(shí)現(xiàn)的中間件要和 negroni 一起用的話盅藻,都要實(shí)現(xiàn)它购桑,并且每個(gè)中間件執(zhí)行完自己的功能后,要去調(diào)用 next 觸發(fā)下一個(gè)中間件的執(zhí)行氏淑。
添加中間件:
func (n *Negroni) Use(handler Handler) {
if handler == nil {
panic("handler cannot be nil")
}
n.handlers = append(n.handlers, handler)
n.middleware = build(n.handlers)
}
func build(handlers []Handler) middleware {
var next middleware
if len(handlers) == 0 {
return voidMiddleware()
} else if len(handlers) > 1 {
next = build(handlers[1:])
} else {
next = voidMiddleware()
}
return middleware{handlers[0], &next}
}
添加中間件的時(shí)候勃蜘,遞歸地調(diào)用 build ,把所有 middlewares 串起來(lái)假残。必然的缭贡,negroni 實(shí)現(xiàn)了 http.Handler 接口,這使得 Negroni 可以當(dāng)做 http.Handler 傳給 Server.Serve()
func (n *Negroni) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
n.middleware.ServeHTTP(NewResponseWriter(rw), r)
}
func (m middleware) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
m.handler.ServeHTTP(rw, r, m.next.ServeHTTP)
}
整合 router
當(dāng)所有中間件執(zhí)行完了以后守问,應(yīng)該把 context 傳給 router 去執(zhí)行對(duì)應(yīng)的路由匀归,所以把 router 作為最后一個(gè)中間件傳到 negroni 。
func (ep *Entrypoint) buildRouter(router http.Handler) http.Handler {
n := negroni.New()
for _, mw := range ep.middlewares {
n.Use(mw)
}
n.Use(negroni.Wrap(http.HandlerFunc(router.ServeHTTP)))
return n
}
當(dāng)然在啟動(dòng) Server.Serve() 之前耗帕,還要把 ep.buildRouter 返回的對(duì)象賦給 ep.Server.Handler穆端,使這個(gè)對(duì)象代替 DefaultServerMux。
func (ep *Entrypoint) prepare(router http.Handler) error {
var (
err error
listener net.Listener
)
listener, err = net.Listen("tcp", ep.configuration.Address)
if err != nil {
return err
}
ep.listener = listener
ep.server = &http.Server{
Addr: ep.configuration.Address,
Handler: ep.buildRouter(router),
}
return nil
}
接下來(lái)就可以調(diào)用 start 跑起服務(wù):
func (ep *Entrypoint) Start(router http.Handler) error {
if router == nil {
return errNoRouter
}
if err := ep.prepare(router); err != nil {
return err
}
go ep.startServer()
fmt.Println("Serving on:", ep.configuration.Address)
return nil
}
中間件封裝
有的時(shí)候有一些現(xiàn)成的中間件仿便,但是不能直接放到 negroni 里面用体啰,就需要我們給它加一層封裝。
例如嗽仪,我們要做 jwt 驗(yàn)證荒勇,使用第三方的 *jwtmiddleware.JWTMiddleware,但是有的路徑我們不需要 token闻坚,需要跳過(guò) jwt 中間件沽翔。不方便改別人的代碼,可以這樣封裝來(lái)代替原來(lái)的 *jwtmiddleware.JWTMiddleware:
type Skipper func(path string) bool
// JWTMiddleware is a wrapper of go-jwt-middleware, but added a skipper func on it.
type JWTMiddleware struct {
*jwtmiddleware.JWTMiddleware
skipper Skipper
}
使用 *jwtmiddleware.JWTMiddleware 作為一個(gè)匿名變量,這樣可以在自定義的 JWTMiddleware 上直接調(diào)用 *jwtmiddleware.JWTMiddleware 的函數(shù)仅偎。然后用 handler 函數(shù)覆蓋原有的 HandlerWithNext 函數(shù)跨蟹,這樣就能通過(guò)調(diào)用時(shí)傳入的 skipper 函數(shù)判斷是否需要跳過(guò) jwt:
func (jm *JWTMiddleware) handler(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
path := r.URL.Path
if skip := jm.skipper(path); skip {
next(w, r)
return
}
jm.HandlerWithNext(w, r, next)
}
最后用 negroni 包裝一下,使它能夠直接被 negroni 使用:
func NegroniJwtHandler(key string, skipper Skipper, signMethod *jwt.SigningMethodHMAC, errHandler func(w http.ResponseWriter, r *http.Request, err string)) negroni.Handler {
if signMethod == nil {
signMethod = jwt.SigningMethodHS256
}
jm := jwtmiddleware.New(jwtmiddleware.Options{
ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
return []byte(key), nil
},
SigningMethod: signMethod,
ErrorHandler: errHandler,
})
if skipper == nil {
skipper = defaulSkiper
}
JM := JWTMiddleware{
jm,
skipper,
}
return negroni.HandlerFunc(JM.handler)
}
總結(jié)
目前為止我們實(shí)現(xiàn)了一個(gè)簡(jiǎn)易通用的 HTTP server 框架橘沥,雖然功能還不是很完善窗轩,不過(guò)好在可擴(kuò)展性比較高,我們可以在此基礎(chǔ)上任意擴(kuò)展座咆,可以添加上緩存痢艺、數(shù)據(jù)庫(kù)、監(jiān)控等等模塊介陶。
如果有興趣的話堤舒,可以去看看 echo 的實(shí)現(xiàn),其實(shí)也是大同小異斤蔓。
最后植酥,再放一遍項(xiàng)目地址,還有一些別的庫(kù)弦牡,歡迎 star 和 pr 啦友驮!