经常看到很多同学在打算使用go做开发的时候会问用什么http框架比较好。其实go的 http package 非常强大,对于一般的 http rest api 开发,完全可以不用框架就可以实现想要的功能。
我们开始尝试用不到100行代码定制出基本的功能框架。
首先思考下基本功能需求:
- 输出访问日子,需要知道:
- Method
- status code
- url
- 响应消耗时间
- response content-length
- 错误捕获,当http请求出现异常时捕获错误,返回异常信息
以上是几个基本需求,未来可能还会有很多,所以应该尝试设计成中间件的形式来应对未来需求的变化,让功能根据需求增减。
我们可以把 http 框架中间件的设计比做洋葱,一层一层的,到最中间就进入业务层,再一层一层的出来。
把流程画出来是这个样子的:
Http:
| LogRequst
| ErrCatch
| Cookie
| Handler
| cookie
| ErrCatch
V LogRequst
调用过程类似于每个中间件逐层包裹,这样的过程很符合函数栈层层调用的过程。
注意:因为这个小框架最终是要被 http.Serve 或 http.ListenAndServe 调用的,所以需要实现 http.Handler 接口,接收到的参数为 http.ResponseWriter 和 *http.Request
好啦!目标确定了下面我们开始想办法实现它
首先需要定义一个 struct 结构,其中需要保存中间件,和最终要执行的 http.Handler
// MiddlewareFunc filter type
type MiddlewareFunc func(ResponseWriteReader, *http.Request, func())
// MiddlewareServe server struct
type MiddlewareServe struct {
middlewares []MiddlewareFunc
Handler http.Handler
}
这里有个问题,因为默认接收到的参数 http.ResponseWriter 接口是一个只能写入不能读取的接口,但我们又需要能读取 status code 和 content-length 。这个时候接口设计的神奇之处就体现出来啦,重新定义一个接口且包涵 http.ResponseWriter ,加入读取 status code 和 content-length 的功能
// ResponseWriteReader for middleware
type ResponseWriteReader interface {
StatusCode() int
ContentLength() int
http.ResponseWriter
}
定义一个 warp struct 实现 ResponseWriteReader 接口
// WrapResponseWriter implement ResponseWriteReader interface
type WrapResponseWriter struct {
status int
length int
http.ResponseWriter
}
// NewWrapResponseWriter create wrapResponseWriter
func NewWrapResponseWriter(w http.ResponseWriter) *WrapResponseWriter {
wr := new(WrapResponseWriter)
wr.ResponseWriter = w
wr.status = 200
return wr
}
// WriteHeader write status code
func (p *WrapResponseWriter) WriteHeader(status int) {
p.status = status
p.ResponseWriter.WriteHeader(status)
}
func (p *WrapResponseWriter) Write(b []byte) (int, error) {
n, err := p.ResponseWriter.Write(b)
p.length += n
return n, err
}
// StatusCode return status code
func (p *WrapResponseWriter) StatusCode() int {
return p.status
}
// ContentLength return content length
func (p *WrapResponseWriter) ContentLength() int {
return p.length
}
接下来,MiddlewareServe 本身需要符合 http.Handler, 所以我们需要定义 ServeHTTP。
// ServeHTTP for http.Handler interface
func (p *MiddlewareServe) ServeHTTP(w http.ResponseWriter, r *http.Request) {
i := 0
// warp http.ResponseWriter 可以让中间件读取到 status code
wr := NewWrapResponseWriter(w)
var next func() // next 函数指针
next = func() {
if i < len(p.middlewares) {
i++
p.middlewares[i-1](wr, r, next)
} else if p.Handler != nil {
p.Handler.ServeHTTP(wr, r)
}
}
next()
}
再加入一个插入中间件的方法
// Use push MiddlewareFunc
func (p *MiddlewareServe) Use(funcs ...MiddlewareFunc) { // 可以一次插入一个或多个
for _, f := range funcs {
p.middlewares = append(p.middlewares, f)
}
}
到这里,一个支持中间件的小框架就定义好了,加上注释一共也不到80行代码
下面开始实现几个中间件测试一下。
// LogRequest print a request status
func LogRequest(w ResponseWriteReader, r *http.Request, next func()) {
t := time.Now()
next()
log.Printf("%v %v %v use time %v content-length %v",
r.Method,
w.StatusCode(),
r.URL.String(),
time.Now().Sub(t).String(),
w.ContentLength())
}
这个函数会打印出 http request Method, status code, url, 处理请求消耗时间, response content-length
测试一下
package main
import (
"fmt"
"net/http"
)
func helloHandle(w http.ResponseWrite