last time: 2020年5月12日
本文基于eudore框架六主要部分App、Config、Logger、Server、Router、Context实现简化组合而来,阅读需要net/http库基础。
net/http.Server是go web主要使用的http Sever。
可以将框架分成6个主要部分App、Config、Logger、Server、Router、Context,分别对应程序主体、配置、日志、服务、路由、请求上下文六块功能。
通常App、Server、Router、Context四部分是各框架都有,例如echo、gin、beego等框架均是。
而App和Context分别代码应用程序和一次请求的主体,内置Config再使用App更加方便,而内置Logger可以避免没有iferr忽略输出error。
获取本文源码git clone https://github.com/eudore/eudore.wiki.git
,切换到eudore.wiki/frame/v1
目录。
先随便大概定义一下主体:
type App struct {
context.Context
context.CancelFunc
Config
Logger
Router
Server
Middlewares []MiddlewareFunc
}
type HandlerFunc func(*Context)
type MiddlewareFunc func(HandlerFunc) HandlerFunc
type Config interface {
Get(string) interface{}
Set(string, interface{})
}
type Logger interface {
Print(...interface{})
Printf(string, ...interface{})
}
type Router interface {
Match(string, string) HandlerFunc
HandleFunc(string, string, HandlerFunc)
}
type Server interface {
SetHandler(http.Handler)
ListenAndServe(string) error
}
App
App是框架程序的主体对象,保存程序各个对象。
需要实现一下http.Handler
接口,以及封装一个Run启动App的方法。
NewApp方法是App的创建函数,Run就简单的启动一个地址的监听,ServeHTTP方法实现http.Handler接口处理请求,闭包处理一下中间件函数。
func NewApp() *App {
app := &App{
Config: &MyConfig{make(map[string]interface{})},
Logger: &MyLogger{},
Router: &MyRouter{},
Server: &MyServer{&http.Server{}},
}
app.Context, app.CancelFunc = context.WithCancel(context.Background())
app.SetHandler(app)
return app
}
func (app *App) Run(addr string) error {
defer app.CancelFunc()
app.Printf("start server: %s", addr)
return app.Server.ListenAndServe(addr)
}
func (app *App) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
ctx := &Context{
Request: req,
ResponseWriter: resp,
Logger: app,
}
h := app.Router.Match(ctx.Method(), ctx.Path())
for _, i := range app.Middlewares {
h = i(h)
}
h(ctx)
}
context.Context
context.Context是App整体的生命周期,在net/http.Server中,使用context.Background()为默认生命周期,在go1.13中加入了BaseContext配置可以给Serve方法设置context.Context。
在App Stop时,可以通过context.Context cannel时,关闭net/http.Server,也可以关闭基于context.Context的goroutine。
Config
Config部分保存各项配置,实现Get/Set数据即可,最简单的使用一个map保存数据。
type MyConfig struct {
Data map[string]interface{}
}
func (c *MyConfig) Get(key string) interface{} {
return c.Data[key]
}
func (c *MyConfig) Set(key string, val interface{}) {
c.Data[key] = val
}
Logger
Logger部分执行日志输出,最简单的接口定义和最简单的实现仅实现Print方法即可,使用fmt输出日志。
type MyLogger struct {
out io.Writer
}
func (l *MyLogger) Print(args ...interface{}) {
if l.out == nil {
l.out = os.Stdout
}
fmt.Print(time.Now().Format("2006-01-02 15:04:05 - "))
fmt.Fprintln(l.out, args...)
}
func (l *MyLogger) Printf(format string, args ...interface{}) {
l.Print(fmt.Sprintf(format, args...))
}
Router
Router部分根据http请求匹配对应的处理函数,然后处理请求。
MyRouter仅实现常量和通配符两种匹配功能,将method和path合并成一个字符串,如果结尾是'*'就是通配符,使用数组保存,匹配时遍历数组报错的前缀即可;如果是常量使用map保存路由路径和处理对象即可。
Router强化实现源码版本v3。
type MyRouter struct {
RoutesConst map[string]HandlerFunc
RoutesPath []string
RoutesFunc []HandlerFunc
}
func (r *MyRouter) Match(method, path string) HandlerFunc {
path = method + path
h, ok := r.RoutesConst[path]
if ok {
return h
}
for i, p := range r.RoutesPath {
if strings.HasPrefix(path, p) {
return r.RoutesFunc[i]
}
}
return Handle404
}
func (r *MyRouter) HandleFunc(method string, path string, handle HandlerFunc) {
if r.RoutesConst == nil {
r.RoutesConst = make(map[string]HandlerFunc)
}
path = method + path
if path[len(path)-1] == '*' {
r.RoutesPath = append(r.RoutesPath, path[:len(path)-1])
r.RoutesFunc = append(r.RoutesFunc, handle)
} else {
r.RoutesConst[path] = handle
}
}
Server
Server部分启动http Server处理http请求。
MyServer直接简单封装net/http.Server对象,允许设置Handler和启动Server端口监听。
type MyServer struct {
*http.Server
}
func (srv *MyServer) SetHandler(h http.Handler) {
srv.Handler = h
}
func (srv *MyServer) ListenAndServe(addr string) error {
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Server.Serve(ln)
}
也可以使用简化实现的http.Servergithub.com/eudore/eudore/component/server/eudore。源码版本v2。
Context
Context是请求上下文是一次请求的主体,保存此次请求的全部信息,通常包含一次请求的读写对象,然后保存本次请求数据,提供请求的读写操作方法。
在Context实现中包含rwl(read、write、log)对象,在次演示中Context仅实现了几个方法。
type Context struct {
*http.Request
http.ResponseWriter
Logger
}
func (ctx *Context) Method() string {
return ctx.Request.Method
}
func (ctx *Context) Path() string {
return ctx.Request.URL.Path
}
func (ctx *Context) RemoteAddr() string {
xforward := ctx.Request.Header.Get("X-Forwarded-For")
if "" == xforward {
return strings.SplitN(ctx.Request.RemoteAddr, ":", 2)[0]
}
return strings.SplitN(string(xforward), ",", 2)[0]
}