当前位置: 移动技术网 > IT编程>脚本编程>Go语言 > 浅谈go-restful框架的使用和实现

浅谈go-restful框架的使用和实现

2018年04月10日  | 移动技术网IT编程  | 我要评论

rest(representational state transfer,表现层状态转化)是近几年使用较广泛的分布式结点间同步通信的实现方式。rest原则描述网络中client-server的一种交互形式,即用url定位资源,用http方法描述操作的交互形式。如果cs之间交互的网络接口满足rest风格,则称为restful api。以下是 理解restful架构 总结的rest原则:

  1. 网络上的资源通过uri统一标示。
  2. 客户端和服务器之间传递,这种资源的某种表现层。表现层可以是json,文本,二进制或者图片等。
  3. 客户端通过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三个重要数据结构。

  1. route 表示一条路由,包含 url/http method/输入输出类型/回调处理函数routefunction
  2. webservice 表示一个服务,由多个route组成,他们共享同一个root path
  3. 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 中实现。

  1. selectroute根据req在注册的webservice中选择匹配的webservice和匹配的route。其中路由选择器默认是 curlyrouter 。
  2. 解析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
}

实在是比较简单,就不写了。今天好困。

遗留问题

  1. reflect在路由注册中的使用,反射与性能
  2. route select时涉及到模糊匹配 如何保证处理速度
  3. pathparams的解析

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网