欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-06-18 08:49:46
rest(representational state transfer,表现层状态转化)是近几年使用较广泛的分布式结点间同步通信的实现方式。rest原则描述网络中clie...

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的解析

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