0%

【Awesome Go】Go RESTful

RESTful API 我介绍了 RESTful 架构的约束条件与最佳实践,但还没有实际上手构建过一个 RESTful 的架构服务。本文将基于 go-restful 这一轻量级框架,介绍在 Go 语言中如何实现 RESTful API,本文中所有代码参考自go-restful examples,可在我的 Github 中找到。

go-restful 并不是 Go 语言中唯一的 RESTful API 框架,beegogin 都属于这一范畴,go-restful 的一大优点在于其轻量性,k8s apisever 中也使用了 go-restful 框架。在深入了解 go-restful 之前,结合 RESTful API 中讨论的 REST 架构的基本原则,我们先提出一个问题,Go 语言不是已经给我们提供了原生的 net/http 包吗?我们为什么还需要一个独立的 RESTful API 框架呢, RESTful API 框架需要实现什么?

一个 RESTful API 框架应该具备以下几个元素:

  • Resources:资源的定义,即 HTTP URI 的定义,RESTful API 的设计围绕着 Resource 进行建模。
  • Handlers:资源处理器,是资源业务逻辑处理的具体实现。
  • Request Routers:资源请求路由器,完成 HTTP URIsHTTP Request MethodsHandlers 三者之间的映射与路由。
  • Request Verification SchemasHTTP Request Body 校验器,验证请求实体的合法性。
  • Response View BuilderHTTP Response Body 生成器,生成合法的响应实体。
  • Controllers:资源表现层状态转移控制器,每个 Resource 都有着各自的 Controller,将 Resource 自身及其所拥有的 HandlersRequest Verification Schemas 以及 Response View Builder 进行封装,配合 Request Routers 完成 RESTful 请求的处理即响应。

基本概念

Route

Route 表示一条请求路由记录,即:Resource 的 URL Path(URI),从编程的角度可细分为 RootPath 和 SubPath。Route 包含了 Resource 的 URL PathHTTP MethodHandler 三者之间的组合映射关系。

1
2
3
4
5
6
7
8
9
10
11
12
// Route binds a HTTP Method,Path,Consumes combination to a RouteFunction.
type Route struct {
Method string
Produces []string
Consumes []string
Path string // webservice root path + described path
Function RouteFunction
Filters []FilterFunction
If []RouteSelectionConditionFunction

//...
}

go-restful 内置的 RouteSelector 根据 Route 将客户端发出的 HTTP 请求路由到相应的 Handler 进行处理,Handler 具体也就是 Route 数据结构中的 Function,这个函数包含了用户的请求与返回给用户的响应。

1
type RouteFunction func(*Request, *Response)

WebService

一个 WebService 由若干个 Routes 组成,并且 WebService 内的 Routes 拥有同一个 RootPath、输入输出格式、基本一致的请求数据类型等等一系列的通用属性。通常的,我们会根据需要将一组相关性非常强的 API 封装成为一个 WebServiice,继而将 Web Application 所拥有的全部 APIs 划分若干个 Group。

go-restful/web_service.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// WebService holds a collection of Route values that bind a Http Method + URL Path to a function.
type WebService struct {
rootPath string
pathExpr *pathExpression // cached compilation of rootPath as RegExp
routes []Route
produces []string
consumes []string
pathParameters []*Parameter
filters []FilterFunction
documentation string
apiVersion string

typeNameHandleFunc TypeNameHandleFunction

dynamicRoutes bool

// protects 'routes' if dynamic routes are enabled
routesLock sync.RWMutex
}

Root Path

WebService 有一个 Root Path,通过 ws.Path() 方法设置,例如:/users,作为 Group 的 根。

1
2
3
4
5
ws := new(restful.WebService)
ws.
Path("/users").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well

Path 的具体实现就是设置了 rootPath 字段,而上面的 ConsumesProduces 则设置了 WebService 所能接收和返回的 MIME 类型,你也可以对每个 Route 单独设置。

go-restful/web_service.go
1
2
3
4
5
6
7
8
func (w *WebService) Path(root string) *WebService {
w.rootPath = root
if len(w.rootPath) == 0 {
w.rootPath = "/"
}
w.compilePathExpression()
return w
}

Group 下属的 APIs 都是 RootRoute(RootPath)下属的 SubRoute(SubPath)。每个 Group 就是提供一项服务的 API 集合,每个 Group 会维护一个 Version。Group 的抽象是为了能够安全隔离的对各项服务进行敏捷迭代,当我们对一项服务进行升级时,只需要通过对特定版本号的更新来升级相关的 APIs,而不会影响到整个 Web Server。视实际情况而定,可能是若干个 APIs 分为一个 Group,也有可能一个 API 就是一个 Group。

RouteBuilder

Route 包含了 Resource 的 URL PathHTTP MethodHandler 三者之间的组合映射关系,为了在 WebService 能够注册到这种映射关系,用户需要调用 Route() 函数,这里的 hello 则是一个典型的 RouteFunction,其参数为 RequestResponse

1
2
3
4
5
ws.Route(ws.GET("/hello").To(hello))

func hello(req *restful.Request, resp *restful.Response) {
io.WriteString(resp, "world")
}

我们看看 Route() 函数的具体实现,其参数是 RouteBuilder 这种数据结构:

1
2
3
4
5
6
7
8
// Route creates a new Route using the RouteBuilder and add to the ordered list of 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
}

下面是 RouteBuilder 的数据结构,很像 Route 的数据结构,主要是为了便于收集 Route 需要用到的各种信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// RouteBuilder is a helper to construct Routes.
type RouteBuilder struct {
rootPath string
currentPath string
produces []string
consumes []string
httpMethod string // required
function RouteFunction // required
filters []FilterFunction
conditions []RouteSelectionConditionFunction
allowedMethodsWithoutContentType []string // see Route

typeNameHandleFunc TypeNameHandleFunction // required

// ...
}

比如首先通过 GET 注册了请求的路径,因为 RouteBuilder 的函数返回都是 RouteBuilder ,所以可以链式调用下去。在后面的 To 就指定了路由的 Handler 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// GET is a shortcut for .Method("GET").Path(subPath)
func (w *WebService) GET(subPath string) *RouteBuilder {
return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("GET").Path(subPath)
}

func (b *RouteBuilder) servicePath(path string) *RouteBuilder {
b.rootPath = path
return b
}

// Method specifies what HTTP method to match. Required.
func (b *RouteBuilder) Method(method string) *RouteBuilder {
b.httpMethod = method
return b
}

// Path specifies the relative (w.r.t WebService root path) URL path to match. Default is "/".
func (b *RouteBuilder) Path(subPath string) *RouteBuilder {
b.currentPath = subPath
return b
}

// To bind the route to a function.
// If this route is matched with the incoming Http Request then call this function with the *Request,*Response pair. Required.
func (b *RouteBuilder) To(function RouteFunction) *RouteBuilder {
b.function = function
return b
}

经过这种链式调用之后,就通过 builder.Build() 函数构建了 Route 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Build creates a new Route using the specification details collected by the RouteBuilder
func (b *RouteBuilder) Build() Route {
pathExpr, err := newPathExpression(b.currentPath)
if err != nil {
log.Printf("Invalid path:%s because:%v", b.currentPath, err)
os.Exit(1)
}
if b.function == nil {
log.Printf("No function specified for route:" + b.currentPath)
os.Exit(1)
}
operationName := b.operation
if len(operationName) == 0 && b.function != nil {
// extract from definition
operationName = nameOfFunction(b.function)
}
route := Route{
Method: b.httpMethod,
Path: concatPath(b.rootPath, b.currentPath),
Produces: b.produces,
Consumes: b.consumes,
Function: b.function,
Filters: b.filters,
If: b.conditions,
relativePath: b.currentPath,
pathExpr: pathExpr,
Doc: b.doc,
// ...
}
route.postBuild()
return route
}

Example

至此,结合上面的内容,我们就已经可以借助 go-restful 框架实现一个最简单的 Hello World 了:

hello.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"io"
"log"
"net/http"

restful "github.com/emicklei/go-restful"
)

// This example shows the minimal code needed to get a restful.WebService working.
//
// GET http://localhost:8080/hello

func main() {
ws := new(restful.WebService)
ws.Route(ws.GET("/hello").To(hello))
restful.Add(ws)
log.Fatal(http.ListenAndServe(":8080", nil))
}

func hello(req *restful.Request, resp *restful.Response) {
io.WriteString(resp, "hello world from houmin\n")
}

上面的代码比较简单,包含一个 helloHandler,通过 ws.Route(ws.GET("/hello").To(hello)) 将其注册到 WebService,然后启动了一个 WebServer,就可以了通过 GET 方法访问了,如下所示:

Container

上一小节虽然 Work 了,但是还是有一个问题,我们通过 Go 自带的 net/http 包启动的 WebServer 是如何和我们定义的 WebService 联系起来的呢?在解答这个问题之前,我们首先来了解下 Container

Container 表示一个 Web Server,由多个 WebServices组成,此外还包含了若干个 Filters、一个 http.ServeMux多路复用器以及一个 dispatch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Container holds a collection of WebServices and a http.ServeMux to dispatch http requests.
// The requests are further dispatched to routes of WebServices using a RouteSelector
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 (RouterJSR311 is a slower alternative)
contentEncodingEnabled bool // default is false
}

ServeMux

我们看到 Container 有一个 ServeMux,这就是利用 net/http 的标准多路复用器,它会将不同的请求路径注册上对应的 Handler

go/net/http/server.go
1
2
3
4
5
6
7
// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}

Container 会在 addHandler函数中,将不同的路径都分发到它自己的 dispatch 函数:

go-restful/container.go
1
2
3
4
5
6
7
8
9
10
11
// addHandler may set a new HandleFunc for the serveMux
func (c *Container) addHandler(service *WebService, serveMux *http.ServeMux) bool {
// ...
if !alreadyMapped {
serveMux.HandleFunc(pattern, c.dispatch)
if !strings.HasSuffix(pattern, "/") {
serveMux.HandleFunc(pattern+"/", c.dispatch)
}
}
return false
}

那么 addHandler 是在哪里被调用的呢?这就是我们的 Add 函数

1
2
3
4
5
6
7
8
9
10
11
// Add a WebService to the Container. It will detect duplicate root paths and exit in that case.
func (c *Container) Add(service *WebService) *Container {
// ...

// If not registered on root then add specific mapping
if !c.isRegisteredOnRoot {
c.isRegisteredOnRoot = c.addHandler(service, c.ServeMux)
}
c.webServices = append(c.webServices, service)
return c
}

因此,如果我们创建了自己的 Container 的话,需要将 WebService 关联到这个 Container 才能生效:

1
2
3
4
5
6
container := restful.NewContainer()
ws := new(restful.WebService)
ws.Route(ws.GET("/hello").To(hello))
container.Add(ws)
server := &http.Server{Addr: ":8080", Handler: container}
log.Fatal(server.ListenAndServe())

因为这里的 Container 实现了 ServeHTTP 接口,所以就可以直接作为一个Handler传递给 http.Server

1
2
3
4
5
// ServeHTTP implements net/http.Handler therefore a Container can be a Handler in a http.Server
func (c *Container) ServeHTTP(httpWriter http.ResponseWriter, httpRequest *http.Request) {
// ...
c.ServeMux.ServeHTTP(writer, httpRequest)
}

DefaultContainer

还是回到 Hello World 的示例,我们并没有发现 Container 的创建啊,那是如何实现关联的呢?这就借助来 net/httpDefaultServeMuxgo-restfulDefaultContainer

go-resetful/web_service_container.go
1
2
3
4
5
6
7
8
9
10
11
12
// DefaultContainer is a restful.Container that uses http.DefaultServeMux
var DefaultContainer *Container

func init() {
DefaultContainer = NewContainer()
DefaultContainer.ServeMux = http.DefaultServeMux
}

// Add registers a new WebService add it to the DefaultContainer.
func Add(service *WebService) {
DefaultContainer.Add(service)
}

可以看到,在 go-restful包初始化的时候,默认就会创建一个 DefaultContainer,并且将它的 ServeMux 设置为了 http.DefaultServeMux。我们通过 restful.Add(ws)DefaultServeMux 注册了 WebService,也就是把 dispatch 函数注册给了 WebServer,这样就可以使用 http.DefaultServeMux 的机制调用它们了。

1
2
restful.Add(ws)
log.Fatal(http.ListenAndServe(":8080", nil))

Dispatch

dispatch 是整个框架最关键的函数了,它作为 Container 这个 WebServer 的入口,通过 SelectRouter 将路由分发给各个 WebService,再由 WebService 分发给具体的 Handler 函数。找到对应的 WebServiceRoute 之后,就可以运行 Filters 和把 Route 的 Function 作为 Handler 了。关于 SelectRouter 的实现,将会在后面的 路由分发 介绍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Dispatch the incoming Http Request to a matching WebService.
func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) {
// so we can assign a compressing one later
writer := httpWriter

// ...

// Find best match Route ; err is non nil if no match was found
var webService *WebService
var route *Route
var err error
func() {
c.webServicesLock.RLock()
defer c.webServicesLock.RUnlock()
webService, route, err = c.router.SelectRoute(
c.webServices,
httpRequest)
}()

// ...

// pass through filters (if any)
if size := len(c.containerFilters) + len(webService.filters) + len(route.Filters); size > 0 {
// compose filter chain
allFilters := make([]FilterFunction, 0, size)
allFilters = append(allFilters, c.containerFilters...)
allFilters = append(allFilters, webService.filters...)
allFilters = append(allFilters, route.Filters...)
chain := FilterChain{Filters: allFilters, Target: route.Function}
chain.ProcessFilter(wrappedRequest, wrappedResponse)
}
// ...
}

Example

经过上面的讲解,我们现在可以实现一个稍微复杂一点的 RESTful API 了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
package main

import (
"log"
"net/http"

restful "github.com/emicklei/go-restful"
)

// This example has the same service definition as restful-user-resource
// but uses a different router (CurlyRouter) that does not use regular expressions
//
// POST http://localhost:8080/users
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
//
// GET http://localhost:8080/users/1
//
// PUT http://localhost:8080/users/1
// <User><Id>1</Id><Name>Melissa</Name></User>
//
// DELETE http://localhost:8080/users/1
//

type User struct {
Id, Name string
}

type UserResource struct {
// normally one would use DAO (data access object)
users map[string]User
}

func (u UserResource) Register(container *restful.Container) {
ws := new(restful.WebService)
ws.
Path("/users").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well

ws.Route(ws.GET("/{user-id}").To(u.findUser))
ws.Route(ws.POST("").To(u.updateUser))
ws.Route(ws.PUT("/{user-id}").To(u.createUser))
ws.Route(ws.DELETE("/{user-id}").To(u.removeUser))

container.Add(ws)
}

// GET http://localhost:8080/users/1
//
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
id := request.PathParameter("user-id")
usr, ok := u.users[id]
if !ok {
response.AddHeader("Content-Type", "text/plain")
response.WriteErrorString(http.StatusNotFound, "User could not be found.")
} else {
response.WriteEntity(usr)
}
}

// POST http://localhost:8080/users
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
//
func (u *UserResource) updateUser(request *restful.Request, response *restful.Response) {
usr := new(User)
err := request.ReadEntity(&usr)
if err == nil {
u.users[usr.Id] = *usr
response.WriteEntity(usr)
} else {
response.AddHeader("Content-Type", "text/plain")
response.WriteErrorString(http.StatusInternalServerError, err.Error())
}
}

// PUT http://localhost:8080/users/1
// <User><Id>1</Id><Name>Melissa</Name></User>
//
func (u *UserResource) createUser(request *restful.Request, response *restful.Response) {
usr := User{Id: request.PathParameter("user-id")}
err := request.ReadEntity(&usr)
if err == nil {
u.users[usr.Id] = usr
response.WriteHeaderAndEntity(http.StatusCreated, usr)
} else {
response.AddHeader("Content-Type", "text/plain")
response.WriteErrorString(http.StatusInternalServerError, err.Error())
}
}

// DELETE http://localhost:8080/users/1
//
func (u *UserResource) removeUser(request *restful.Request, response *restful.Response) {
id := request.PathParameter("user-id")
delete(u.users, id)
}

func main() {
wsContainer := restful.NewContainer()
wsContainer.Router(restful.CurlyRouter{})
u := UserResource{map[string]User{}}
u.Register(wsContainer)

log.Printf("start listening on localhost:8080")
server := &http.Server{Addr: ":8080", Handler: wsContainer}
log.Fatal(server.ListenAndServe())
}

编译运行上面这个程序,发出请求如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
 # Window 1
$ go run user.go
2020/12/05 18:36:27 start listening on localhost:8080

# Window 2
$ curl -X POST -v -i http://127.0.0.1:8080/users \
-H 'Content-type: application/json' \
-H 'Accept: application/xml' \
-d '{"Id": "1", "Name": "Houmin"}'

Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> POST /users HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.64.1
> Content-type: application/json
> Accept: application/xml
> Content-Length: 29
>
* upload completely sent off: 29 out of 29 bytes
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Content-Type: application/xml
Content-Type: application/xml
< Date: Sat, 05 Dec 2020 10:36:32 GMT
Date: Sat, 05 Dec 2020 10:36:32 GMT
< Content-Length: 90
Content-Length: 90

<
<?xml version="1.0" encoding="UTF-8"?>
<User>
<Id>1</Id>
<Name>Houmin</Name>
* Connection #0 to host 127.0.0.1 left intact
</User>* Closing connection 0

$ curl -X GET -v -i http://127.0.0.1:8080/users/1
Note: Unnecessary use of -X or --request, GET is already inferred.
* Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to 127.0.0.1 (127.0.0.1) port 8080 (#0)
> GET /users/1 HTTP/1.1
> Host: 127.0.0.1:8080
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Content-Type: application/json
Content-Type: application/json
< Date: Sat, 05 Dec 2020 10:39:53 GMT
Date: Sat, 05 Dec 2020 10:39:53 GMT
< Content-Length: 33
Content-Length: 33

<
{
"Id": "1",
"Name": "Houmin"
* Connection #0 to host 127.0.0.1 left intact
}* Closing connection 0

过滤器

过滤器可以动态拦截请求和响应,以及转换或使用请求和响应中包含的信息。用户可以使用过滤器来执行常规的日志记录、测量、验证、重定向、设置响应头部Header等。restful包中有三个针对请求、响应流的钩子,还可以添加过滤器。每个过滤器必须定义一个FilterFunction:go-restful 支持服务级、路由级的请求或响应过滤。开发者可以使用 Filter 来执行常规的日志记录、计量、验证、重定向、设置响应头部等工作。go-restful 提供了 3 个针对请求、响应的钩子(Hooks),此外,还可以实现自定义的 Filter。

1
2
3
4
5
6
7
type FilterFunction func(*Request, *Response, *FilterChain)

type FilterChain struct {
Filters []FilterFunction // ordered list of FilterFunction
Index int // index into filters that is currently in progress
Target RouteFunction // function to call after passing all filters
}

使用如下语句传递请求/响应对到下一个过滤器或RouteFunction:

1
chain.ProcessFilter(req, resp)
1
2
3
4
5
6
7
8
func (f *FilterChain) ProcessFilter(request *Request, response *Response) {
if f.Index < len(f.Filters) {
f.Index++
f.Filters[f.Index-1](request, response, f)
} else {
f.Target(request, response)
}
}

Container Filter

在注册 WebService 之前处理

1
2
// 安装一个全局的 Filter 到 Default Container
restful.Filter(globalLogging)

Container 的数据结构中,我们看到了有 containerFilters 这样一个 FilterFunction 切片,上面调用的 Filter 函数是实际上就是将对应的 FulterFunction 加入到这个切片中:

1
2
3
4
5
// Filter appends a container FilterFunction. These are called before dispatching
// a http.Request to a WebService from the container
func (c *Container) Filter(filter FilterFunction) {
c.containerFilters = append(c.containerFilters, filter)
}

Container 调用 Filter 函数就是通过 FilterChainProcessFilter 来实现的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) {
// ...

// pass through filters (if any)
if size := len(c.containerFilters) + len(webService.filters) + len(route.Filters); size > 0 {
// compose filter chain
allFilters := make([]FilterFunction, 0, size)
allFilters = append(allFilters, c.containerFilters...)
allFilters = append(allFilters, webService.filters...)
allFilters = append(allFilters, route.Filters...)
chain := FilterChain{Filters: allFilters, Target: route.Function}
chain.ProcessFilter(wrappedRequest, wrappedResponse)
} else {
// no filters, handle request by route
route.Function(wrappedRequest, wrappedResponse)
}
}

所有的 Filter 执行完,就去执行 Target RouteFunction,也就是用户注册的 handler。

WebService Filter

路由 WebService 之前处理

1
2
// 安装一个 WebService Filter
ws.Filter(webserviceLogging).Filter(measureTime)

WebService 的数据结构中,我们看到了有 filters 这个 FilterFunction 切片,上面调用的 Filter 函数是实际上就是将对应的 FulterFunction 加入到这个切片中:

1
2
3
4
5
// Filter adds a filter function to the chain of filters applicable to all its Routes
func (w *WebService) Filter(filter FilterFunction) *WebService {
w.filters = append(w.filters, filter)
return w
}

而对于 WebService 的 Filter 函数调用,就是在 Containerdispatch 中实现的,见上面的代码。

Route Filter

在调用 Router 相关的函数之前处理。

1
2
// 安装 2 个链式的 Route Filter
ws.Route(ws.GET("/{user-id}").Filter(routeLogging).Filter(NewCountFilter().routeCounter))

RouteFilter 安装是通过 RouterBuilder 来实现的,之后会在 Build() 函数中将 filters 传递给 Route

1
2
3
4
5
// Filter appends a FilterFunction to the end of filters for this Route to build.
func (b *RouteBuilder) Filter(filter FilterFunction) *RouteBuilder {
b.filters = append(b.filters, filter)
return b
}

对于 Route 的 Filter 函数调用,也是在 Containerdispatch 中实现的,见 Container 的代码。

路由分发

go-restful 支持两种路由分发器:快速路由 CurlyRouterRouterJSR311。实际上,CurlyRoute 也是基于 RouterJSR311 的,相比 RouterJSR11,还支持了正则表达式和动态参数,也更加轻量级,Kubernetes ApiServer 中使用的就是这种路由。

CurlyRouter 的元素包括:请求路径(URL Path),请求参数(Parameter),输入、输出类型(Writes/Reads Model),处理函数(Handler),响应内容类型(Accept)等。

Response Encoding

如果 HTTP Request 包含了 Accept-Encoding Header,那么 HTTP Response 就必须使用指定的编码格式进行压缩。go-restful 目前支持 gzipdeflate 这两种响应编码格式。

如果要为所有的响应启用它们:

1
restful.DefaultContainer.EnableContentEncoding(true)

同时,也可以通过创建一个 Filter 来实现自定义的响应编码过滤器,并将其安装到每一个 WebService 和 Route 上。

OPTIONS支持

通过安装预定义的容器过滤器,你的 WebService 可以响应 HTTP OPTIONS 请求。

1
Filter(OPTIONSFilter())

CORS

通过安装 CrossOriginResourceSharing 过滤器,使 WebService 可以响应 CORS 请求。

1
2
3
4
5
6
7
cors := CrossOriginResourceSharing{
ExposeHeaders: []string{"X-My-Header"},
CookiesAllowed: false,
Container: DefaultContainer
}

Filter(cors.Filter)

异常处理

意想不到的事情发生。如果因为故障而不能处理请求,服务端需要通过响应告诉客户端发生了什么和为什么。因此使用HTTP状态码,更重要的是要正确的使用状态码。

  • 400: Bad Request

如果路径或查询参数无效(内容或类型),那么使用http.StatusBadRequest。

  • 404: Not Found

尽管URI有效,但请求的资源可能不可用。

  • 500: Internal Server Error

如果应用程序逻辑无法处理请求(或编写响应),则使用http.StatusInternalServerError。

  • 405: Method Not Allowed

请求的URL是有效的,但请求使用的HTTP方法(GET,PUT,POST,…)是不允许的。

  • 406: Not Acceptable

请求的头部没有或设置了未知Accept Header。

  • 415: Unsupported Media Type

请求的头部没有或设置了未知的Content-Type报头。

ServiceError

除了设置HTTP状态码,还应该为响应选择写适当的ServiceError消息。

Performance Options

这个包有几个选项,它们可能会影响服务的性能。重要的是要理解这些选项,正确地设置它们。

1
restful.DefaultContainer.DoNotRecover(false)

DoNotRecover控制是否因返回HTTP 500状态码而(恐慌)停止服务。如果设置为false,那么容器Container会恢复服务。默认值为true。

1
restful.SetCompressorProvider(NewBoundedCachedCompressors(20, 20))

如果启用了内容编码,那么获得新gzip/zlib输出器(writer)和读入器(reader)的默认策略是使用sync.Pool。由于输出器writer是昂贵的结构,当使用预加载缓存时性能提高非常明显。你也可以注入自己的实现。

Trouble shooting

这个包可以对完整的Http请求的匹配过程和过滤器调用产生详细的日志记录。启用此功能需要你设置 restful.StdLogger的实现,例如 log.Logger

1
restful.TraceLogger(log.New(os.Stdout, "[restful] ", log.LstdFlags|logs.Lshortfile))

参考资料