基于net/http框架设计思路

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目录。

先随便大概定义一下主体:

// App 框架主体
type App struct {
    context.Context
    context.CancelFunc
    Config
    Logger
    Router
    Server
    Middlewares []MiddlewareFunc
}

// HandlerFunc 请求处理函数
type HandlerFunc func(*Context)

// MiddlewareFunc 中间件函数
type MiddlewareFunc func(HandlerFunc) HandlerFunc

// Config 保存App配置信息
type Config interface {
    Get(string) interface{}
    Set(string, interface{})
}

// Logger 日志输出接口
type Logger interface {
    Print(...interface{})
    Printf(string, ...interface{})
}

// Router 路由器接口
type Router interface {
    Match(string, string) HandlerFunc
    HandleFunc(string, string, HandlerFunc)
}

// Server 服务启动接口
type Server interface {
    SetHandler(http.Handler)
    ListenAndServe(string) error
}

App

App是框架程序的主体对象,保存程序各个对象。

需要实现一下http.Handler接口,以及封装一个Run启动App的方法。

NewApp方法是App的创建函数,Run就简单的启动一个地址的监听,ServeHTTP方法实现http.Handler接口处理请求,闭包处理一下中间件函数。

// NewApp 函数创建一个app。
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
}

// Run 方法启动App。
func (app *App) Run(addr string) error {
    defer app.CancelFunc()
    app.Printf("start server: %s", addr)
    return app.Server.ListenAndServe(addr)
}

// ServeHTTP 方式实现http.Hander接口,处理Http请求。
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保存数据。

// MyConfig 使用map保存配置
type MyConfig struct {
    Data map[string]interface{}
}

// Get 方法获得一项配置
func (c *MyConfig) Get(key string) interface{} {
    return c.Data[key]
}

// Set 方法设置一项配置
func (c *MyConfig) Set(key string, val interface{}) {
    c.Data[key] = val
}

Logger

Logger部分执行日志输出,最简单的接口定义和最简单的实现仅实现Print方法即可,使用fmt输出日志。

// MyLogger 输出到标准输出的日志接口实现
type MyLogger struct {
    out io.Writer
}

// Print 方法日志输出,实现Logger接口。
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...)
}

// Printf 方法日志输出,实现Logger接口。
func (l *MyLogger) Printf(format string, args ...interface{}) {
    l.Print(fmt.Sprintf(format, args...))
}

Router

Router部分根据http请求匹配对应的处理函数,然后处理请求。

MyRouter仅实现常量和通配符两种匹配功能,将method和path合并成一个字符串,如果结尾是'*'就是通配符,使用数组保存,匹配时遍历数组报错的前缀即可;如果是常量使用map保存路由路径和处理对象即可。

Router强化实现源码版本v3。

// MyRouter 基于map和遍历实现的简化路由器
type MyRouter struct {
    RoutesConst map[string]HandlerFunc
    RoutesPath  []string
    RoutesFunc  []HandlerFunc
}

// Match 方法匹配一个Context的请求,实现Router接口。
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
}

// RegisterFunc 方法注册路由处理函数,实现Router接口。
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端口监听。

// MyServer 封装http.Server
type MyServer struct {
    *http.Server
}

// SetHandler 方法设置请求处理对象。
func (srv *MyServer) SetHandler(h http.Handler) {
    srv.Handler = h
}

// ListenAndServe 方法使Server启动一个地址的监听。
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仅实现了几个方法。

// Context 请求上下文,封装请求操作,未详细实现。
type Context struct {
    *http.Request
    http.ResponseWriter
    Logger
}

// Method 方法获取请求方法。
func (ctx *Context) Method() string {
    return ctx.Request.Method
}

// Path 方法获取请求路径。
func (ctx *Context) Path() string {
    return ctx.Request.URL.Path
}

// RemoteAddr 方法获取客户端真实地址。
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]
}

results matching ""

    No results matching ""