Go Web编程(十三——如何设计一个web框架)

项目规划

如何谁知目录结构

  • gopath以及项目设置

GOPATH可以是多个目录:在window系统设置环境变量;在Linux/MacOS系统只需要输入终端命令:

export gopath=/home/astaxie/gopath

但是必须保证gopath目录下有三个目录:pkg、bin、src。新建的源码放在src目录下。

  • 应用程序流程图

博客系统是基于模型-视图-控制器这一设计模式的。MVC是一种将应用程序的逻辑层和表现层进行分离的结构方式。

模型:代表数据结构,通常来说,包含取出、插入、更新数据库资料这些功能

视图:是展示给用户的信息的结构及样式。一个视图通常是一个网页,但是在GO中也可以是一个页面片段,如页头、页尾。GO实现的template包已经很好的实现了View层中的部分功能。

控制器是模型、视图以及其他任何处理HTTP请求所必须的资源之间的中介,并生成网页。

数据流:

Go Web编程(十三——如何设计一个web框架)

1.main.go作为应用入口,初始化一些运行博客所需要的基本资源,配置信息,监听端口。

2.路由功能检查HTTP请求,根据URL以及method来确定谁(控制层)来处理请求的转发资源。

3.如果缓存文件存在,它将绕过通常的流程执行,将直接发送给浏览器。

4.安全监测:应用程序控制器调用之前,HTTP请求和任一用户提交的数据将被过滤。

5.控制器装在模型、核心库、辅助函数、以及任何处理特定请求所需的其他资源,控制器主要负责处理业务逻辑

6.输出视图层中渲染好的即将发送到Web浏览器中的内容,如果开启缓存,视图首先被缓存,将用于以后的常规请求。

  • 目录结构:
|——main.go         入口文件
|——conf            配置文件和处理模块
|——controllers     控制器入口
|——models          数据库处理模块
|——utils           辅助函数库
|——static          静态文件目录
|——views           视图库

自定义路由器设计

  • HTTP路由

HTTP路由组件负责将HTTP请求交到对应的函数处理。

路由在框架中相当于一个事件的处理器,而这个事件包括:

1.用户请求的路径path,当然还有查询串信息

2.HTTP的请求方法(method)(GET、POST、PUT、DELETE、PATCH等)

路由器就是根据用户请求的事件信息转发到相应的处理函数(控制层)。

  • 默认的路由实现

GO默认的路由添加是通过函数Http.Handle和Http.HandleFunc等来添加,底层都是调用了

DefaultServerMux.Handle(pattern string,handler Handler),这个函数会把路由信息存储到一个map当中map[string]muxEntry

GO监听端口,然后接收到TCP连接就会扔给Handler来处理,参数为nil时,Handler就为http.DefaultServeMux通过 DefaultServeMux.ServeHTTP 函数来进行调度,遍历之前存储的 map 路由信息,和用户访问的 URL 进行匹配,以查询对应注册的处理函数

for k,v := range mux.m {
    if !pathMatch(k,path){
        contine
    }
    if h == nile || len(k) > n {
        n = len(k)
        n = v.h
    }
}
  • beego框架的路由实现

目前几乎所有的Web应用路由实现都是基于http默认的路由器,但是Go自带的路由器有几个限制:

1.不支持参数设定,例如/user/:uid这种泛类型的匹配。

2.无法很好的支持REST模式,无法限制访问的方法。

3.一般网站的路由规则太多了,编写繁琐,可以通过struct的方法进行简化。

beego设计了一种REST方式的路由实现,路由设计:存储路由和转发路由

  • 存储路由

针对前面所说的限制点,我们首先要解决参数支持就需要用到正则,第二和第三点我们通过一种变通的方法来解决,REST 的方法对应到 struct 的方法中去,然后路由到 struct 而不是函数,这样在转发路由的时候就可以根据 method 来执行不同的方法。

设计两个数据类型:controllerinfo(保存路径和对应的struct,这里是一个reflect.Type类型)和ControllerRegistor(routers 是一个slice用来保存用户添加的路由信息,以及beego框架的应用信息)

type controllerInfo struct {
    regex *regex.Regexp
    params map[int]string
    controllerType reflect.Type
}
type ControllerRegister struct {
    routers []*controllerInfo
    Application *App
}

ControllerRegister对外的接口函数有:

func (p *ControllerRegistor) Add(pattern string,c ControllerInterface)

详细的实现:

func (p *ControllerRegistor) Add(pattern string, c ControllerInterface) {
    parts := strings.Split(pattern, "/")

    j := 0
    params := make(map[int]string)
    for i, part := range parts {
        if strings.HasPrefix(part, ":") {
            expr := "([^/]+)"

            // a user may choose to override the defult expression
            // similar to expressjs: ‘/user/:id([0-9]+)’

            if index := strings.Index(part, "("); index != -1 {
                expr = part[index:]
                part = part[:index]
            }
            params[j] = part
            parts[i] = expr
            j++
        }
    }

    // recreate the url pattern, with parameters replaced
    // by regular expressions. then compile the regex

    pattern = strings.Join(parts, "/")
    regex, regexErr := regexp.Compile(pattern)
    if regexErr != nil {

        // TODO add error handling here to avoid panic
        panic(regexErr)
        return
    }

    // now create the Route
    t := reflect.Indirect(reflect.ValueOf(c)).Type()
    route := &controllerInfo{}
    route.regex = regex
    route.params = params
    route.controllerType = t

    p.routers = append(p.routers, route)

}
  • 静态路由的实现

beego的静态文件夹保存在全局变量StaticDir中。staticDir是一个map类型。

func (app *App) SetStaticPath(url string,path string)
* App {
    staticDir[url] = path
    return app 
}

实现:

beego.SetStaticPath("/img","/static/img")
  • 使用入门

使用注册路由:

beego.BeeApp.RegisterController("/",&controllers.MainController{})

参数注册:

beego.BeeApp.RegisterController("/:param",&contollers.UserController{})

正则匹配:

beego.BeeApp.RegisterController("/users/:uid([0-9]+)"),&controllers.UserController{})

Controller设计

  • beego的REST设计
type Controller struct {
    Ct        *Context
    Tpl       *template.Template
    Data      map[interface{}]interface{}
    ChildName string
    TplNames  string
    Layout    []string
    TplExt    string
}

type ControllerInterface interface {
    Init(ct *Context, cn string)    // 初始化上下文和子类名称
    Prepare()                       // 开始执行之前的一些处理
    Get()                           // method=GET 的处理
    Post()                          // method=POST 的处理
    Delete()                        // method=DELETE 的处理
    Put()                           // method=PUT 的处理
    Head()                          // method=HEAD 的处理
    Patch()                         // method=PATCH 的处理
    Options()                       // method=OPTIONS 的处理
    Finish()                        // 执行完成之后的处理        
    Render() error                  // 执行完 method 对应的方法之后渲染页面
}
Init()      初始化
Prepare()   执行之前的初始化,每个继承的子类可以来实现该函数
method()    根据不同的 method 执行不同的函数:GET、POST、PUT、HEAD等,子类来实现这些函数,如果没实现,那么默认都是403
Render()    可选,根据全局变量 AutoRender 来判断是否执行
Finish()    执行完之后执行的操作,每个继承的子类可以来实现该函数
  • 应用指南

设计方法:

package controllers

import (
    "github.com/astaxie/beego"
)

type MainController struct {
    beego.Controller
}

func (this *MainController) Get() {
    this.Data["Username"] = "astaxie"
    this.Data["Email"] = "astaxie@gmail.com"
    this.TplNames = "index.tpl"
}

上面的方式我们实现了子类 MainController,实现了 Get 方法,那么如果用户通过其他的方式 (POST/HEAD 等) 来访问该资源都将返回 405,而如果是 Get 来访问,因为我们设置了 AutoRender=true,那么在执行完 Get 方法之后会自动执行 Render 函数。

Index.tpl:

<!DOCTYPE html>
<html>
  <head>
    <title>beego welcome template</title>
  </head>
  <body>
    <h1>Hello, world!{{.Username}},{{.Email}}</h1>
  </body>
</html>

日志和配置设计

  • beego的日志设计

beego 的日志设计部署思路来自于 seelog,根据不同的 level 来记录日志,但是 beego 设计的日志系统比较轻量级,采用了系统的 log.Logger 接口,默认输出到 os.Stdout, 用户可以实现这个接口然后通过 beego.SetLogger 设置自定义的输出,详细的实现如下所示

// Log levels to control the logging output.
const (
    LevelTrace = iota
    LevelDebug
    LevelInfo
    LevelWarning
    LevelError
    LevelCritical
)

// logLevel controls the global log level used by the logger.
var level = LevelTrace

// LogLevel returns the global log level and can be used in
// own implementations of the logger interface.
func Level() int {
    return level
}

// SetLogLevel sets the global log level used by the simple
// logger.
func SetLevel(l int) {
    level = l
}

默认的级别是 Trace,用户通过 SetLevel 可以设置不同的分级。

  • beego的配置设计

beego实现了有个key=value的配置文件读取。

var (
    bComment = []byte{‘#‘}
    bEmpty   = []byte{}
    bEqual   = []byte{‘=‘}
    bDQuote  = []byte{‘"‘}
)

定义了配置文件的格式:

// A Config represents the configuration.
type Config struct {
    filename string
    comment  map[int][]string  // id: []{comment, key...}; id 1 is for main comment.
    data     map[string]string // key: value
    offset   map[string]int64  // key: offset; for editing.
    sync.RWMutex
}
  • 应用指南
func GetJson() {
    resp, err := http.Get(beego.AppConfig.String("url"))
    if err != nil {
        beego.Critical("http get info error")
        return
    }
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    err = json.Unmarshal(body, &AllInfo)
    if err != nil {
        beego.Critical("error:", err)
    }
}

函数种调用beego.Critical函数用来报错,beego.AppConfig.String("url")用来获取配置文件的信息,配置文件的信息如下:

appname = hs
url = "http://www.api.com/api.html"

Go Web编程(十三——如何设计一个web框架)

上一篇:Ajax请求重复发送问题


下一篇:jquery的下载