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。

 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 单独设置。

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 了:

 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

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 函数:

 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

 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

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

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))

参考资料