page contents

浅谈Go restful框架的使用和实现

REST(Representational State Transfer,表现层状态转化)是近几年使用较广泛的分布式结点间同步通信的实现方式。REST原则描述网络中client-server的一种交互形式,即用URL定位资源,用HTTP...

attachments-2021-07-ug03mFIi60de7c4cd067c.png

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

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

更多相关技术内容咨询欢迎前往并持续关注六星社区了解详情。

程序员编程交流QQ群:805358732

如果你想用Python开辟副业赚钱,但不熟悉爬虫与反爬虫技术,没有接单途径,也缺乏兼职经验
关注下方微信公众号:Python编程学习圈,获取价值999元全套Python入门到进阶的学习资料以及教程,还有Python技术交流群一起交流学习哦。

attachments-2022-06-KsFWfcgc62ad2edf6a491.jpeg

  • 发表于 2021-07-02 10:41
  • 阅读 ( 449 )
  • 分类:Golang

0 条评论

请先 登录 后评论
轩辕小不懂
轩辕小不懂

2403 篇文章

作家榜 »

  1. 轩辕小不懂 2403 文章
  2. 小柒 1478 文章
  3. Pack 1135 文章
  4. Nen 576 文章
  5. 王昭君 209 文章
  6. 文双 71 文章
  7. 小威 64 文章
  8. Cara 36 文章