golang基于context的web范式

golang基于context的web范式

适用框架:golf、echo、gin、dotweb、iris、beego。html

本文章未彻底完成,指定部分框架的主要结构mysql

golang大部分框架都是基于标准库net/http包现实,fasthttp框架就是本身解析http协议,重新实现了相似net/http包的功能。git

一般框架包含的部分有Application、Context、Request、Response、Router、Middleware、Logger、Binder、render、View、Session、Cache这些部分,通常都是有部分,不过前五个是必定存在。github

如下列出了各框架主要部分定义位置:golang

golf echo gin dotweb iris beego
Application app.go echo.go gin.go dotweb.go iris.go app.go
Context context.go context.go context.go context.go context.go context.go
Request http.Request http.Request http.Request request.go http.Request input.go
Response http.ResponseWriter response.go response_writer_1.8.go response.go response_writer.go output.go
Router router.go router.go routergroup.go router.go router.go router.go
Middleware middleware.go echo.go gin.go middleware.go handler.go app.go
Logger log.go logger.go logger.go log.go
Binder bind.go binding.go bind.go
Render render.go render.go
View view.go engine.go
Session session.go session.go session.go session.go session.go
Cache cache.go cache.go cache.go
Websocket websocket.go server.go
MVC controller.go controller.go

源码解析:golfweb

Application

application通常都是框架的主体,一般XXX框架的主体叫作XXX,固然也有叫App、Application、Server的实现,具体状况不你一致,不过通常都是叫XXX,源码就是XXX.go。redis

这部分通常都会实现两个方法Start() errorServeHTTP(ResponseWriter, *Request)sql

Start() error通常是框架的启动函数,用来启动服务,名称可能会是Run,建立一个http.Server对象,设置TLS相关等配置,而后启动服务,固然也会出现Shutdown方法。数据库

ServeHTTP(ResponseWriter, *Request)函数实现http.Handler接口,通常框架都是使用http.Server对象来启动的服务,因此须要实现此方法。后端

此本方法大概就三步,Init、Handle、Release。

第一步Init,通常就是初始化Context对象,其中包括Request和Response的初始化Reset,使用ResponseWriter*Request对象来初始化,一般会使用Sync.Pool来回收释放减小GC。

第二步Handle,一般就是框架处理请求,其中必定包含路由处理,使用路由匹配出对应的Handler来处理当前请求。

第三步释放Context等对象。

简单实现:

Router简单实现

// 定义Application
type Application struct {
	mux		*Router
	pool	sync.Pool
}

func NewApplication() *Application{
	return &Application{
		mux:	new(Router),
		pool:	sync.Pool{
			New:	func() interface{} {
				return &Context{}
			},
		}
	}
}

// 注册一个GET请求方法,其余类型
func (app *Application) Get(path string, handle HandleFunc) {
	app.RegisterFunc("GET", path, handle)
}

// 调用路由注册一个请求
func (app *Application) RegisterFunc(method string, path string, handle HandleFunc)  {
	app.router.RegisterFunc(method, path, handle)
}

// 启动Application
func (app *Application) Start(addr string) error {
	// 建立一个http.Server并启动
	return http.Server{
		Addr:		addr,
		Handler:	app,
	}.ListenAndServe()
}

// 实现http.Handler接口,并出去net/http请求。
func (app *Application) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 建立一个ctx对象
	ctx := app.pool.Get().(*Context)
	// 初始化ctx
	ctx.Reset(w, r)	
	// 路由器匹配请求并处理ctx
	app.router.Match(r.Method, r.URL.Path)(ctx)
	// 回收ctx
	app.pool.Put(ctx)
}
复制代码

Context

Context包含Request和Response两部分,Request部分是请求,Response是响应。Context的各类方法基本都是围绕Request和Response来实现来,一般就是各类请求信息的获取和写入的封装。

简单实现:

// Context简单实现使用结构体,不使用接口,若有其余须要可继续增长方法。
type Context struct {
	http.ResponseWriter
	req *http.Request
}

// 初始化ctx
func (ctx *Context) Reset(w http.ResponseWriter, r *http.Request) {
	ctx.ResponseWriter, ctx.req = w, r
}

// Context实现获取方法
func (ctx *Context) Method() string {
	return ctx.req.Method
}

复制代码

RequestReader & ResponseWriter

http协议解析文档

实现RequestReader和ResponseWriter接口。

根据http协议请求报文和响应报文RequestReader和ResponseWriter大概定义以下:

type (
	Header map[string][]string
	RequestReader interface {
		Method() string
		RequestURI() string
		Proto() string
		Header() Header
		Read([]byte) (int, error)
	}
	ResponseWriter interface {
		WriteHeader(int)
		Header() Header
		Write([]byte) (int, error)
	}
)

复制代码

RequestReader用于读取http协议请求的请求行(Request Line)、请求头(Request Header)、body。

ResponseWriter用于返回http写法响应的状态行(Statue Line)、响应头(Response Header)、body这些数据。

在实际过程还会加入net.Conn对象的tcp链接信息。

一般net/http库下的RequestReader和ResponseWriter定义为http.Request和http.ResponseWriter,请求是一个结构体,拥有请求信息,不一样状况下可能会有不一样封装,或者直接使用net/http定义的读写对象。

Router

Router是请求匹配的路由,并不复杂,可是每一个框架都是必备的。

一般实现两个方法Match和RegisterFunc,给路由器注册新路由,匹配一个请求的路由,而后处理请求。

type (
	HandleFunc func(*Context) Router interface{
		Match(string, string) HandleFunc
		RegisterFunc(string, string, HandleFunc)
	}
)
复制代码

定义一个很是很是简单的路由器。

type Router struct {
	Routes	map[string]map[string]HandleFunc
}

// 匹配一个Context的请求
func (r *Router) Match(path ,method string) HandleFunc {
	// 查找方法定义的路由
	rs, ok := r.Routes[method]
	if !ok {
		return Handle405
	}
	// 查找路由
	h, ok := rs[path]
	if !ok {
		return Handle404
	}
	return h
}

// 注册路由处理函数
func (r *Router) RegisterFunc(method string, path string, handle HandleFunc) {
	rs, ok := r.Routes[ctx.Method()]
	if !ok {
		rs = make(map[string]HandleFunc)
		r.Routes[ctx.Method()] = rs
	}
	rs[path] = handle
}

// 处理405响应,方法不容许;Allow Header返回容许的方法。
func Handle405(ctx Context) {
	ctx.Response().WriteHeader(405)
	ctx.Response().Header().Add("Allow", "GET, POST, HEAD")
}


// 处理404响应,没有找到对应的资源。
func Handle404(ctx Context) {
	ctx.Response().WriteHeader(404)
}
复制代码

这个简单路由仅支持了rsetful风格,连通配符匹配都没有实现;可是体现了路由器的做用,输出一个参数,返回一个对应的处理者。

至于如何对应一个处理路由,就是路由器规则的设定了;例如通配符、参数、正则、数据校验等功能。

Middleware

一般是多个Handler函数组合,在handler以前以后增一些处理函数。

echo

type MiddlewareFunc func(HandlerFunc) HandlerFunc // echo.ServeHTTP h := NotFoundHandler for i := len(e.premiddleware) - 1; i >= 0; i-- {
	h = e.premiddleware[i](h)
}
if err := h(c); err != nil {
	e.HTTPErrorHandler(err, c)
}
复制代码

echo中间件使用装饰器模式。

echo中间件使用HandlerFunc进行一层层装饰,最后返回一个HandlerFunc处理Context

gin

gin在路由注册的会中间件和route合并成一个handlers对象,而后httprouter返回匹配返回handlrs,在context reset时设置ctx的handlers为路由匹配出现的,handlers是一个HanderFunc数组,Next方法执行下一个索引的HandlerFunc,若是在一个HandlerFunc中使用ctx.Next()就先将后续的HandlerFunc执行,后续执行完才会继续那个HandlerFunc,调用ctx.End() 执行索引直接修改成最大值,应该是64以上,毕竟Handlers合并时的数据长度限制是64,执行索引成最大值了,那么后面就没有HandlerFunc,就完整了一次ctx的处理。

type HandlerFunc func(*Context) // https://github.com/gin-gonic/gin/blob/master/context.go#L105 func (c *Context) Next() {
	c.index++
	for s := int8(len(c.handlers)); c.index < s; c.index++ {
		c.handlers[c.index](c)
	}
}
复制代码

echo经过路由匹配返回一个[]HandlerFunc对象,并保存到Context里面,执行时按ctx.index一个个执行。

若是HandlerFunc里面调用ctx.Next(),就会提早将后序HandlerFunc执行,返回执行ctx.Next()后的内容,能够简单的指定调用顺序,ctx.Next()以前的在Handler前执行,ctx.Next()以后的在Handler后执行。

Context.handlers存储本次请求全部HandlerFunc,而后使用c.index标记固然处理中HandlerFunc,

dotweb

dotweb Middleware接口中Exclude()、HasExclude()、ExistsExcludeRouter()用来排除路由级调用。

BaseMiddlware通常是Middleware的基础类,一般只重写Handle()方法,同时能够调用Next()方法执行写一个Middleware。

在Next()方法中,若是next为空就调用Context的RouterNode()方法,得到RouterNode对象,而后使用AppMiddlewares()方法读取app级别[]Middleware,对其中第一个Middleware调用Handle()就会开始执行app级别Middleware。

同级多个Middleware是单链的形式存储,那么只有最后一个Middleware的next为空,若是执行到这个Middleware,那么表示这一级Middleware所有处理完毕,就须要Context的middleware级别降级,而后开始执行下一级。

ServerHttp()时,调用

//middleware执行优先级:
//优先级1:app级别middleware
//优先级2:group级别middleware
//优先级3:router级别middleware

// Middleware middleware interface
type Middleware interface {
	Handle(ctx Context) error
	SetNext(m Middleware)
	Next(ctx Context) error
	Exclude(routers ...string)
	HasExclude() bool
	ExistsExcludeRouter(router string) bool
}


type RouterNode interface {
    Use(m ...Middleware) *Node
    AppMiddlewares() []Middleware
    GroupMiddlewares() []Middleware
    Middlewares() []Middleware
    Node() *Node
}

// Use registers a middleware
func (app *DotWeb) Use(m ...Middleware) {
	step := len(app.Middlewares) - 1
	for i := range m {
		if m[i] != nil {
			if step >= 0 {
				app.Middlewares[step].SetNext(m[i])
			}
			app.Middlewares = append(app.Middlewares, m[i])
			step++
		}
	}
}


func (bm *BaseMiddlware) Next(ctx Context) error {
	httpCtx := ctx.(*HttpContext)
	if httpCtx.middlewareStep == "" {
		httpCtx.middlewareStep = middleware_App
	}
	if bm.next == nil {
		if httpCtx.middlewareStep == middleware_App {
			httpCtx.middlewareStep = middleware_Group
			if len(httpCtx.RouterNode().GroupMiddlewares()) > 0 {
				return httpCtx.RouterNode().GroupMiddlewares()[0].Handle(ctx)
			}
		}
		if httpCtx.middlewareStep == middleware_Group {
			httpCtx.middlewareStep = middleware_Router
			if len(httpCtx.RouterNode().Middlewares()) > 0 {
				return httpCtx.RouterNode().Middlewares()[0].Handle(ctx)
			}
		}

		if httpCtx.middlewareStep == middleware_Router {
			return httpCtx.Handler()(ctx)
		}
	} else {
		//check exclude config
		if ctx.RouterNode().Node().hasExcludeMiddleware && bm.next.HasExclude() {
			if bm.next.ExistsExcludeRouter(ctx.RouterNode().Node().fullPath) {
				return bm.next.Next(ctx)
			}
		}
		return bm.next.Handle(ctx)
	}
	return nil
}

func (x *xMiddleware) Handle(ctx Context) error {
	httpCtx := ctx.(*HttpContext)
	if httpCtx.middlewareStep == "" {
		httpCtx.middlewareStep = middleware_App
	}
	if x.IsEnd {
		return httpCtx.Handler()(ctx)
	}
	return x.Next(ctx)
}
复制代码

iris

// If Handler panics, the server (the caller of Handler) assumes that the effect of the panic was isolated to the active request.
// It recovers the panic, logs a stack trace to the server error log, and hangs up the connection.
type Handler func(Context) // Handlers is just a type of slice of []Handler. // // See `Handler` for more. type Handlers []Handler 复制代码

Logger

框架基础日志接口

type Logger interface {
	Debug(...interface{})
	Info(...interface{})
	Warning(...interface{})
	Error(...interface{})
	Fatal(...interface{})
	WithField(key string, value interface{}) Logger
	WithFields(fields Fields) Logger
}
复制代码

Binder & Render & View

Binder的做用以各类格式方法解析Request请求数据,而后赋值给一个interface{}。

Render的做用和Binder相反,会把数据安装选择的格式序列化,而后写回给Response部分。

View和Render的区别是Render输入的数据,View是使用对应的模板渲染引擎渲染html页面。

Session & Cache

seesion是一种服务端数据存储方案

持久化

Cache持久化就须要把数据存到其余地方,避免程序关闭时缓存丢失。

存储介质通常各类DB、file等均可以,实现通常都是下面几步操做。

一、getid:从请求读取sessionid。

二、initSession:用sessionid再存储中取得对应的数据,通常底层就是[]byte

三、newSession:反序列化成一个map[string]interface{}这样相似结构Session对象。

四、Set and Get:用户对Session对象各类读写操做。

五、Release:将Session对象序列化成[]byte,而后写会到存储。

存储介质

  • 内存:使用简单,供测试使用,没法持久化。

  • 文件:存储简单。

  • sqlite:和文件差很少,使用sql方式操做。

  • mysql等:数据共享、数据库持久化。

  • redis:数据共享、协议对缓存支持好。

  • memcache:协议简单、方法少、效率好。

  • etcd:少许数据缓存,可用性高。

golang session

一组核心的session接口定义。

type (
	Session interface {
		ID() string							// back current sessionID
		Set(key, value interface{}) error	// set session value
		Get(key interface{}) interface{}	// get session value
		Del(key interface{}) error			// delete session value
		Release(w http.ResponseWriter)		// release value, save seesion to store
	}
	type Provider interface {
		SessionRead(sid string) (Session, error)
	}
)
复制代码

Provider.SessionRead(sid string) (Session, error)用sid来从Provider读取一个Session返回,sid就是sessionid通常存储与cookie中,也可使用url参数值,Session对象会更具sid从对应存储中读取数据,而后将数据反序列化来初始化Session对象。

Session.Release(w http.ResponseWriter)从名称上是释放这个Seesion,可是通常实际做用是将对应Session对象序列化,而后存储到对应的存储实现中,若是只是读取Session能够不Release。

简单的Seession实现可使用一个map,那么你的操做就是操做这个map。

在初始化Session对象的时候,使用sessionId去存储里面取数据,数据不在内存中,大家一般不是map,比较经常使用的是[]byte,例如memcache就是[]byte,[]byte能够map之间就须要序列化和反序列化了。

在初始化时,从存储读取[]map,反序列化成一个map,而后返回给用户操做;最后释放Session对象时,就要将map序列化成[]byte,而后再回写到存储之中,保存新修改的数据。

Beego.Seesion

源码github

使用例子:

func login(w http.ResponseWriter, r *http.Request) {
    sess, _ := globalSessions.SessionStart(w, r)
    defer sess.SessionRelease(w)
    username := sess.Get("username")
    if r.Method == "GET" {
        t, _ := template.ParseFiles("login.gtpl")
        t.Execute(w, nil)
    } else {
        sess.Set("username", r.Form["username"])
    }
}
复制代码

在beego.session中Store就是一个Session。

一、SessionStart定义在session.go#L193,用户获取Store对象,在194line获取sessionid,在200line用sid从存储读取sessio.Store对象。

二、memcache存储在[124line][4]定义SessionRead函数来初始化对象。

三、其中在[sess_memcache.go#L139][5]使用memcache获取的数据,使用gob编码反序列化生成了map[interface{}]interface{}类型的值kv,而后144line把kv赋值给了store。

四、在5七、65 [set&get][6]操做的rs.values,就是第三部序列化生成的对象。

五、最后释放store对象,定义在[94line][7],先把rs.values使用gob编码序列化成[]byte,而后使用memcache的set方法存储到memcache里面了,完成了持久化存储。

源码分析总结:

  • session在多线程读写是不安全的,数据可能冲突,init和release中间不要有耗时操做,参考其余思路同样,暂无解决方案,实现读写对存储压力大。

  • 对session只读就不用释放session,只读释放是无效操做,由于set值和原值同样。

  • beego.session能够适配一个beego.cache的后端,实现模块复用,不过也多封装了一层。

golang cache

实现Get and Set接口的一种实现。

简单实现

type Cache interface {
	Delete(key interface{})
	Load(key interface{}) (value interface{}, ok bool)
	Store(key, value interface{})
}
复制代码

这组接口[sync.Map][]简化出来的,这种简单的实现了get&set操做,数据存储于内存中,map类型也能够直接实现存储。

复杂实现

封装各类DB存储实现接口便可,这样就能够实现共享缓存和持久化存储。

Websocket

协议见文档

相关文章
相关标签/搜索