背景
一般来说,对于入门某一门语言的web开发,都会从框架入手,但是,当你的使用场景越来越复杂的时候,框架本身会制约你的实现,这个时候,你需要自己实现一个框架才能满足你的需要。框架并不神秘,跟着笔者三步实现一个最基本的golang框架,然后就可以case-by-case完善专属于你的框架。所有代码都在github 3StepWebFramerork.
首先,这里说的web framework是指一个非常精简的框架,主要包括:
url路由以及url传参
路由middeleware中间件支持,一般web业务都需要中间件的支持,以便集中做auth/log/recovery(recovery是go一大特色)
其它非核心组件可以自行添加:
-
更丰富的上下文
-
序列化组件(json/yaml/pb)
-
ORM
针对以上要求,各个组件的解决方法如下
router: httprouter,比go原生的路由性能要高一些,详见对应benckmark。ps:刚开始用gin的时候gin的star才几kstar,现在已经二十几k了,时间过得好快。gin号称最快的框架之一,就是因为用了这个路由。
middleware: 自定义
序列化支持:以json为例,使用官方json包实现
下面跟着笔者step by step
Step1: 熟悉router
路由有两个基本的功能
-
根据url,把请求传到相应的处理函数
-
把url的某一段,作为一个参数获取,如下面demo的name参数
package main
import (
"fmt"
"github.com/julienschmidt/httprouter"
"log"
"net/http"
)
func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "Welcome!\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)
router.GET("/hello/:name", Hello)
log.Fatal(http.ListenAndServe(":8082", router))
}
用curl测试一下输出,output:
> curl localhost:8082/
Welcome!
> curl localhost:8082/hello/half
hello, half!
Step2:middleware part1
第二步,要实现某一些业务(log/recovery)的统一处理,这个时候就要插入一些函数
-
before业务处理
-
实际业务处理
-
after业务处理
下面代码实现一个基本功能:
在所有的api实际处理函数之前, 打印一下日志,往header写一个Before:hello
在所有的api实际处理函数之前, 打印一下日志,往header写一个After:world(实际上这样是做是不对的)
先贴代码:
package main
import (
"fmt"
"github.com/julienschmidt/httprouter"
"io"
"log"
"net/http"
)
func Index(w http.ResponseWriter, r *http.Request) {
// get url param by GetParam
work := GetParam(r, "work")
fmt.Println("work:", work)
fmt.Fprint(w, "Welcome!\n")
}
type MiddlewareFunc func(w http.ResponseWriter, req *http.Request)
type simpleRouter struct {
BeforeMiddleware MiddlewareFunc
AfterMiddleware MiddlewareFunc
r *httprouter.Router
}
func NewSimpleRouter() (s *simpleRouter) {
s = &simpleRouter{
r: httprouter.New(),
}
return s
}
func (s *simpleRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if s.BeforeMiddleware != nil {
s.BeforeMiddleware(w, req)
}
s.r.ServeHTTP(w, req)
if s.AfterMiddleware != nil {
s.AfterMiddleware(w, req)
}
}
// wrapper for httprouter GET
func (s *simpleRouter) GET(path string, handle http.HandlerFunc) {
s.r.GET(path, wrap(handle))
}
func wrap(handler http.HandlerFunc) httprouter.Handle {
return func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
SetParam(r, params)
handler(w, r)
}
}
type contextReadCloser struct {
io.ReadCloser
params httprouter.Params
}
func SetParam(req *http.Request, params httprouter.Params) {
crc := getContextReadCloser(req)
crc.params = params
}
func GetParam(req *http.Request, key string) string {
crc := getContextReadCloser(req)
for _, v := range crc.params {
if v.Key == key {
return v.Value
}
}
return ""
}
// add a simple context
func getContextReadCloser(req *http.Request) *contextReadCloser {
crc, ok := req.Body.(*contextReadCloser)
if !ok {
crc = &contextReadCloser{
ReadCloser: req.Body,
}
req.Body = crc
}
return crc
}
func main() {
router := NewSimpleRouter()
router.BeforeMiddleware = func(w http.ResponseWriter, req *http.Request) {
fmt.Println("handle before")
w.Header().Set("Before", "hello")
}
router.AfterMiddleware = func(w http.ResponseWriter, req *http.Request) {
fmt.Println("handle after")
w.Header().Set("After", "world") // 注意,因为实际处理函数在处理的时候一般会先写resp code, 这里是不会生效的
}
router.GET("/step2/:work", Index)
log.Fatal(http.ListenAndServe(":8082", router))
}
运行代码
打开一个新的terminal:
curl -i localhost:8082/step2/test
HTTP/1.1 200 OK
Before: hello
Date: Sun, 14 Apr 2019 01:57:39 GMT
Content-Length: 9
Content-Type: text/plain; charset=utf-8
Welcome!
在运行代码的terminal输出:
handle before
work: test
handle after
已经可以实现before/after的逻辑
这里有一个结构体要说明一下,
由于httprouter的api接口形式是:func(w http.ResponseWriter, r *http.Request, params httprouter.Params)
跟golanghttp包的多了一个httprouter.Params 字段,那么为了把这个信息写共享,就封装了一个简单的上下文
Request是一个io.ReadCloser借口,所以要封装一个简单的上下文,
type contextReadCloser struct {
io.ReadCloser
params httprouter.Params
}
Step3
由于代码较多,就不贴了,
说一下几个比较重要的结构:
对于路由:
type MiddlewareFunc func(http.Handler) http.Handler
这里定义了一个中间件的处理函数,输出与输出的数据类型都是http.Handler,为链式调用服务
MiddleWare的实际处理逻辑在这里,最先注册进来的middleware[0]会在最后被调用,当你要做recoveryMiddleware的时候一定要注意先后顺序
func (r *QRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) {
handler := http.Handler(r.router)
for i := len(r.middleware) - 1; i >= 0; i-- {
handler = r.middleware[i](handler)
}
handler.ServeHTTP(w, req)
}
测试curl:
`
curl -i localhost/test/cat/1
curl -i localhost/
`
对于log_middleware
日志中间件的demo,为了实现获取Request body和Response body, 定义了一个数据结构
type bufferedWriter struct {
http.ResponseWriter
out *bufio.Writer
RespBuffer bytes.Buffer
}
但是对于实际业务处理,很多请求是不需要复制body的,非常损耗性能,这里只是实现了最简单的demo,可以按照实际情况修改。
回顾
这里只是实现了基本的路由个MiddleaWare,只包装了一个GET方法,其他POST/PUT/DELETE可以自行实现,这是一个最基本的框架,大家可以根据自己的实际需要改写
如果觉得有用,请给个star