浅谈go-restful框架的使用和实现
rest(representational state transfer,表现层状态转化)是近几年使用较广泛的分布式结点间同步通信的实现方式。rest原则描述网络中client-server的一种交互形式,即用url定位资源,用http方法描述操作的交互形式。如果cs之间交互的网络接口满足rest风格,则称为restful api。以下是 理解restful架构 总结的rest原则:
- 网络上的资源通过uri统一标示。
- 客户端和服务器之间传递,这种资源的某种表现层。表现层可以是json,文本,二进制或者图片等。
- 客户端通过http的四个动词,对服务端资源进行操作,实现表现层状态转化。
为什么要设计restful的api,个人理解原因在于:用http的操作统一数据操作接口,限制url为资源,即每次请求对应某种资源的某种操作,这种 无状态的设计可以实现client-server的解耦分离,保证系统两端都有横向扩展能力。
go-restful
go-restful is a package for building rest-style web services using google go。go-restful定义了container webservice和route三个重要数据结构。
- route 表示一条路由,包含 url/http method/输入输出类型/回调处理函数routefunction
- webservice 表示一个服务,由多个route组成,他们共享同一个root path
- container 表示一个服务器,由多个webservice和一个 http.servermux 组成,使用routeselector进行分发
最简单的使用实例,向webservice注册路由,将webservice添加到container中,由container负责分发。
func main() { ws := new(restful.webservice) ws.path("/users") ws.route(ws.get("/").to(u.findallusers). doc("get all users"). metadata(restfulspec.keyopenapitags, tags). writes([]user{}). returns(200, "ok", []user{})) container := restful.newcontainer().add(ws) http.listenandserve(":8080", container) }
container
container是根据标准库http的路由器servemux写的,并且它通过servemux的路由表实现了handler接口,可参考以前的这篇 http协议与go的实现 。
type container struct { webserviceslock sync.rwmutex webservices []*webservice servemux *http.servemux isregisteredonroot bool containerfilters []filterfunction donotrecover bool // default is true recoverhandlefunc recoverhandlefunction serviceerrorhandlefunc serviceerrorhandlefunction router routeselector // default is a curlyrouter contentencodingenabled bool // default is false }
func (c *container)servehttp(httpwriter http.responsewriter, httprequest *http.request) { c.servemux.servehttp(httpwriter, httprequest) }
往container内添加webservice,内部维护的webservices不能有重复的rootpath,
func (c *container)add(service *webservice)*container { c.webserviceslock.lock() defer c.webserviceslock.unlock() if !c.isregisteredonroot { c.isregisteredonroot = c.addhandler(service, c.servemux) } c.webservices = append(c.webservices, service) return c }
添加到container并注册到mux的是dispatch这个函数,它负责根据不同webservice的rootpath进行分发。
func (c *container)addhandler(service *webservice, servemux *http.servemux)bool { pattern := fixedprefixpath(service.rootpath()) servemux.handlefunc(pattern, c.dispatch) }
webservice
每组webservice表示一个共享rootpath的服务,其中rootpath通过 ws.path() 设置。
type webservice struct { rootpath string pathexpr *pathexpression routes []route produces []string consumes []string pathparameters []*parameter filters []filterfunction documentation string apiversion string typenamehandlefunc typenamehandlefunction dynamicroutes bool routeslock sync.rwmutex }
通过route注册的路由最终构成route结构体,添加到webservice的routes中。
func (w *webservice)route(builder *routebuilder)*webservice { w.routeslock.lock() defer w.routeslock.unlock() builder.copydefaults(w.produces, w.consumes) w.routes = append(w.routes, builder.build()) return w }
route
通过routebuilder构造route信息,path结合了rootpath和subpath。function是路由handler,即处理函数,它通过 ws.get(subpath).to(function) 的方式加入。filters实现了个类似grpc拦截器的东西,也类似go-chassis的chain。
type route struct { method string produces []string consumes []string path string // webservice root path + described path function routefunction filters []filterfunction if []routeselectionconditionfunction // cached values for dispatching relativepath string pathparts []string pathexpr *pathexpression // documentation doc string notes string operation string parameterdocs []*parameter responseerrors map[int]responseerror readsample, writesample interface{} metadata map[string]interface{} deprecated bool }
dispatch
server侧的主要功能就是路由选择和分发。http包实现了一个 servemux ,go-restful在这个基础上封装了多个服务,如何在从container开始将路由分发给webservice,再由webservice分发给具体处理函数。这些都在 dispatch 中实现。
- selectroute根据req在注册的webservice中选择匹配的webservice和匹配的route。其中路由选择器默认是 curlyrouter 。
- 解析pathparams,将wrap的请求和相应交给路由的处理函数处理。如果有filters定义,则链式处理。
func (c *container)dispatch(httpwriter http.responsewriter, httprequest *http.request) { func() { c.webserviceslock.rlock() defer c.webserviceslock.runlock() webservice, route, err = c.router.selectroute( c.webservices, httprequest) }() pathprocessor, routerprocessespath := c.router.(pathprocessor) pathparams := pathprocessor.extractparameters(route, webservice, httprequest.url.path) wrappedrequest, wrappedresponse := route.wraprequestresponse(writer, httprequest, pathparams) if len(c.containerfilters)+len(webservice.filters)+len(route.filters) > 0 { chain := filterchain{filters: allfilters, target: func(req *request, resp *response) { // handle request by route after passing all filters route.function(wrappedrequest, wrappedresponse) }} chain.processfilter(wrappedrequest, wrappedresponse) } else { route.function(wrappedrequest, wrappedresponse) } }
go-chassis
go-chassis实现的rest-server是在go-restful上的一层封装。register时只要将注册的schema解析成routes,并注册到webservice中,start启动server时 container.add(r.ws) ,同时将container作为handler交给 http.server , 最后开始listenandserve即可。
type restfulserver struct { microservicename string container *restful.container ws *restful.webservice opts server.options mux sync.rwmutex exit chan chan error server *http.server }
根据method不同,向webservice注册不同方法的handle,从schema读取的routes信息包含method,func以及pathpattern。
func (r *restfulserver)register(schemainterface{}, options ...server.registeroption)(string, error) { schematype := reflect.typeof(schema) schemavalue := reflect.valueof(schema) var schemaname string tokens := strings.split(schematype.string(), ".") if len(tokens) >= 1 { schemaname = tokens[len(tokens)-1] } routes, err := getroutes(schema) for _, route := range routes { lager.logger.infof("add route path: [%s] method: [%s] func: [%s]. ", route.path, route.method, route.resourcefuncname) method, exist := schematype.methodbyname(route.resourcefuncname) ... handle := func(req *restful.request, rep *restful.response) { c, err := handler.getchain(common.provider, r.opts.chainname) inv := invocation.invocation{ microservicename: config.selfservicename, sourcemicroservice: req.headerparameter(common.headersourcename), args: req, protocol: common.protocolrest, schemaid: schemaname, operationid: method.name, } bs := newbaseserver(context.todo()) bs.req = req bs.resp = rep c.next(&inv, func(ir *invocation.invocationresponse)error { if ir.err != nil { return ir.err } method.func.call([]reflect.value{schemavalue, reflect.valueof(bs)}) if bs.resp.statuscode() >= http.statusbadrequest { return ... } return nil }) } switch route.method { case http.methodget: r.ws.route(r.ws.get(route.path).to(handle). doc(route.resourcefuncname). operation(route.resourcefuncname)) ... } } return reflect.typeof(schema).string(), nil }
实在是比较简单,就不写了。今天好困。
遗留问题
- reflect在路由注册中的使用,反射与性能
- route select时涉及到模糊匹配 如何保证处理速度
- pathparams的解析
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
下一篇: python 以16进制打印输出的方法
推荐阅读
-
Laravel框架使用monolog_mysql实现将系统日志信息保存到mysql数据库的方法
-
使用jQuery实现input数值增量和减量的方法教程
-
vue中使用cookies和crypto-js实现记住密码和加密的方法
-
ThinkPHP框架使用redirect实现页面重定向的方法实例分析
-
浅谈sql语句中GROUP BY 和 HAVING的使用方法
-
浅谈JavaScript中的apply/call/bind和this的使用
-
JavaScript使用原型和原型链实现对象继承的方法详解
-
thinkphp5.1框架中容器(Container)和门面(Facade)的实现方法分析
-
使用Python的Twisted框架实现一个简单的服务器
-
在iOS开发的Quartz2D使用中实现图片剪切和截屏功能