你好,我是孔令飞。
Kubernetes 的整个 REST 路由都是基于 go-restful 包来构建的,这节课我们就来详细介绍 go-restful Web 框架的核心概念及使用方法。
go-restful 包
github.com/emicklei/go-restful 是一个流行的 Go Web 框架,通过链式调用来构建标准的 RESTful API 接口。go-restful 包提供了众多方法对 RESTful API 接口进行需要的设置,包括但不限于:请求路径设置、请求和返回的 MIME 类型设置、中间件支持、自动生成 API 接口文档等。
RESTful API 框架核心要素
从应用程序开发的角度来看,RESTful API 的本质是一个 Web Application,而 RESTful API 框架就是实现这个 Web Application 所封装的一系列工具库,使开发者可以忽略底层实现的复杂度,专注于自身 Application 的逻辑设计。
一个 RESTful API 框架应该具备以下几个元素:
-
Resources:资源的定义,即 HTTP URI(或称之为 HTTP URL Path)的定义。RESTful API 的设计围绕着 Resource 建模。
-
Handlers:资源处理器,是资源业务逻辑处理的具体实现。
-
Request Routers:资源请求路由器,完成 HTTP URIs、HTTP Request Methods 和 Handlers 三者之间的映射与路由。
-
Request Verification Schemas:HTTP Request Body 校验器,验证请求实体的合法性。
-
Response View Builder:HTTP Response Body 生成器,生成合法的响应实体。
-
Controllers:资源表现层状态转移控制器,每个 Resource 都有着各自的 Controller,将 Resource 自身及其所拥有的 Handlers、Request Verification Schemas 以及 Response View Builder 进行封装,配合 Request Routers 完成 RESTful 请求的处理及响应。
go-restful 具有以下特性:
-
支持可配置的请求路由,默认使用 CurlyRouter 快速路由算法,也支持 RouterJSR311。
-
支持在 URL path 上定义正则表达式,如
/static/{subpath:*}。 -
提供 Request API 用于从 JSON、XML 读取路径参数、查询参数、头部参数,并转换为 Struct。
-
提供 Response API 用于将 Struct 写入到 JSON、XML 以及 Header。
-
支持在服务级或路由级对请求、响应流进行过滤和拦截。
-
支持使用过滤器自动响应 OPTIONS 请求和 CORS(跨域)请求。
-
支持使用 RecoverHandler 自定义处理 HTTP 500 错误。
-
支持使用 ServiceErrorHandler 自定义处理路由错误(如 HTTP 404/405/406/415 等错误)。
-
支持对请求、响应的有效负载进行编码(如 gzip、deflate)。
-
支持使用 CompressorProvider 注册自定义的 gzip、deflate 的读入器和输出器。
-
支持使用 EntityReaderWriter 注册自定义的编码实现。
-
支持 Swagger UI 编写的 API 文档。
-
支持可配置的日志跟踪。
go-restful 包中的核心概念

上图包含了go-restful包中几个核心概念,接下来我们一一介绍。
Route
Route 表示一条请求路由记录,即 Resource 的 URL Path(URI),从编程的角度可细分为 RootPath 和 SubPath。Route 包含了 Resource 的 URL Path、HTTP Method、Handler 三者之间的组合映射关系。go-restful 内置的 RouteSelector(请求路由分发器)根据 Route 将客户端发出的 HTTP 请求路由到相应的 Handler 进行处理。
go-restful 支持两种路由分发器:快速路由 CurlyRouter 和 RouterJSR311。实际上,CurlyRoute 也是基于 RouterJSR311 的,相比 RouterJSR11,CurlyRoute 还支持了正则表达式和动态参数,同时也更加轻量级。kubernetes-apiServer 中使用的就是这种路由。
CurlyRouter 的元素包括:请求路径(URL Path)、请求参数(Parameter)、输入、输出类型(Writes、Reads Model)、处理函数(Handler)、响应内容类型(Accept)等。
WebService(路由分组)
一个 WebService 由若干个 Routes 组成,并且 WebService 内的 Routes 拥有同一个 RootPath、输入输出格式、基本一致的请求数据类型等等一系列的通用属性。通常,我们会根据需要将一组相关性非常强的 API 封装成一个 WebServiice,继而将 Web Application 所拥有的全部 APIs 划分若干个 Group。
所以 WebService 至少会有一个 Root Path 通过 ws.Path() 方法设置,作为 Group 的 “根”(如:/user_group)。Group 下属的 APIs 都是 RootRoute(RootPath)下属的 SubRoute(SubPath)。
每个 Group 就是提供一项服务的 API 集合,每个 Group 会维护一个 Version。Group 的抽象是为了能够安全隔离地对各项服务进行敏捷迭代,当我们对一项服务进行升级时,只需要通过对特定版本号的更新来升级相关的 APIs,而不会影响到整个 Web Server。根据实际情况,可能若干个 APIs 分为一个 Group,也有可能一个 API 就是一个 Group。
Container
Container 表示一个 Web Server(服务器),由多个 WebServices 组成,此外还包含了若干个 Filters(过滤器)、一个 http.ServeMux 多路复用器,以及一个 dispatch。go-restful 从 Container 开始将路由分发给各个 WebService,再由 WebService 分发给具体的 Handler 函数,这些过程都在 dispatch 中实现。
开发者根据需要创建 Container 实例之后,将 Container 加载到一个 http.Server 上运行,具体示例如下:
// 构建一个 WebService 实例。
ws := new(restful.WebService)
// 定义 Root Path。
ws.Path("/users")
// 定义一个 WebService 下属的 Route。
ws.Route(ws.GET("/users").To(u.findAllUsers).
Doc("get all users").
Metadata(restfulspec.KeyOpenAPITags, tags).
Writes([]User{}).
Returns(200, "OK", []User{}))
// 构建一个 Container 实例。
container := restful.NewContainer()
// 将 Container 加载到 http.Server 运行。
server := &http.Server{Addr: ":8081", Handler: container}
过滤器(Filter)
go-restful 支持服务级、路由级的请求或响应过滤。开发者可以使用 Filter 来执行常规的日志记录、计量、验证、重定向、设置响应头部等工作。go-restful 除了提供了 3 个针对请求、响应的钩子(Hooks),还可以实现自定义的 Filter。
每个 Filter 必须实现一个 FilterFunction:
func(req *restful.Request, resp *restful.Response, chain *restful.FilterChain)
并且,每个 Filter 需要使用如下语句传递请求、响应对到下一个 Filter 或 RouteFunction:
chain.ProcessFilter(req, resp)
Container Filter:在注册 WebService 之前执行处理,示例代码如下:
// 安装一个全局的 Filter 到 Default Container
restful.Filter(globalLogging)
WebService Filter:在请求路由到 WebService 之前执行处理,示例代码如下:
// 安装一个 WebService Filter
ws.Filter(webserviceLogging).Filter(measureTime)
Route Filter:在调用 Router 相关的函数之前执行处理,示例代码如下:
// 安装 2 个链式的 Route Filter
ws.Route(ws.GET("/{user-id}").Filter(routeLogging).Filter(NewCountFilter().routeCounter))
OPTIONS Filter:使 WebService 可以响应 HTTP OPTIONS 请求,示例代码如下:
Filter(OPTIONSFilter())
CORS Filter:使 WebService 可以响应 CORS 请求,示例代码如下:
cors := CrossOriginResourceSharing{
ExposeHeaders: []string{"X-My-Header"},
CookiesAllowed: false,
Container: DefaultContainer
}
Filter(cors.Filter)
响应编码(Response Encoding)
如果 HTTP Request 包含了 Accept-Encoding Header,HTTP Response 就必须使用指定的编码格式进行压缩。go-restful 目前支持 zip、deflate 这两种响应编码格式。
如果要为所有的响应启用它们,可以使用如下代码:
restful.DefaultContainer.EnableContentEncoding(true)
同时,也可以通过创建一个 Filter 来实现自定义的响应编码过滤器,并将其安装到每一个 WebService 和 Route 上。
代码示例
理解了 go-restful 包中的核心概念,我们再来看看如何使用go-restful开发一个功能相对全面的 Web 服务器。
下述示例实现了对 users 资源的 CURD API :
-
定义 User resource。
-
定义 User 的 Handlers。
-
定义一个 User resource Register(资源注册器)。
-
在 User resource Register 内构造了 WebService 实例、定义了 User 的 URL RootPath /users 以及多个 SubPath 的 Routes,并且在 Routes 内建立了 HTTP Method、User Path(RootPath + SubPath)、Handlers 之间的映射关系。最后将 Routes 关联到 WebService、将 WebServices 关联到 Container。
package main
import (
"fmt"
"log"
"net/http"
"time"
restfulspec "github.com/emicklei/go-restful-openapi/v2"
restful "github.com/emicklei/go-restful/v3"
"github.com/go-openapi/spec"
)
// UserResource is the REST layer to the User domain
type UserResource struct {
// normally one would use DAO (data access object)
users map[string]User
}
// WebService creates a new service that can handle REST requests for User resources.
func (u UserResource) WebService() *restful.WebService {
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
tags := []string{"users"}
ws.Route(ws.GET("/").To(u.findAllUsers).
// docs
Doc("get all users").
Metadata(restfulspec.KeyOpenAPITags, tags).
Writes([]User{}).
Returns(200, "OK", []User{}))
ws.Route(ws.GET("/{user-id}").To(u.findUser).
// docs
Doc("get a user").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("integer").DefaultValue("1")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Writes(User{}). // on the response
Returns(200, "OK", User{}).
Returns(404, "Not Found", nil))
ws.Route(ws.PUT("/{user-id}").To(u.upsertUser).
// docs
Doc("update a user").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(User{})) // from the request
ws.Route(ws.POST("").To(u.createUser).
// docs
Doc("create a user").
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(User{})) // from the request
ws.Route(ws.DELETE("/{user-id}").To(u.removeUser).
// docs
Doc("delete a user").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")))
return ws
}
// GET http://localhost:8080/users
func (u UserResource) findAllUsers(request *restful.Request, response *restful.Response) {
log.Println("findAllUsers")
list := []User{}
for _, each := range u.users {
list = append(list, each)
}
response.WriteEntity(list)
}
// GET http://localhost:8080/users/1
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
log.Println("findUser")
id := request.PathParameter("user-id")
usr := u.users[id]
if len(usr.ID) == 0 {
response.WriteErrorString(http.StatusNotFound, "User could not be found.")
} else {
response.WriteEntity(usr)
}
}
// PUT http://localhost:8080/users/1
// <User><Id>1</Id><Name>Melissa Raspberry</Name></User>
func (u *UserResource) upsertUser(request *restful.Request, response *restful.Response) {
log.Println("upsertUser")
usr := User{ID: request.PathParameter("user-id")}
err := request.ReadEntity(&usr)
if err == nil {
u.users[usr.ID] = usr
response.WriteEntity(usr)
} else {
response.WriteError(http.StatusInternalServerError, err)
}
}
// POST http://localhost:8080/users
// <User><Id>1</Id><Name>Melissa</Name></User>
func (u *UserResource) createUser(request *restful.Request, response *restful.Response) {
log.Println("createUser")
usr := User{ID: fmt.Sprintf("%d", time.Now().Unix())}
err := request.ReadEntity(&usr)
if err == nil {
u.users[usr.ID] = usr
response.WriteHeaderAndEntity(http.StatusCreated, usr)
} else {
response.WriteError(http.StatusInternalServerError, err)
}
}
// DELETE http://localhost:8080/users/1
func (u *UserResource) removeUser(request *restful.Request, response *restful.Response) {
log.Println("removeUser")
id := request.PathParameter("user-id")
delete(u.users, id)
}
func main() {
u := UserResource{map[string]User{}}
restful.DefaultContainer.Add(u.WebService())
config := restfulspec.Config{
WebServices: restful.RegisteredWebServices(), // you control what services are visible
APIPath: "/apidocs.json",
PostBuildSwaggerObjectHandler: enrichSwaggerObject}
restful.DefaultContainer.Add(restfulspec.NewOpenAPIService(config))
// Optionally, you can install the Swagger Service which provides a nice Web UI on your REST API
// You need to download the Swagger HTML5 assets and change the FilePath location in the config below.
// Open http://localhost:8080/apidocs/?url=http://localhost:8080/apidocs.json
http.Handle("/apidocs/", http.StripPrefix("/apidocs/", http.FileServer(http.Dir("/Users/emicklei/Projects/swagger-ui/dist"))))
log.Printf("start listening on localhost:8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func enrichSwaggerObject(swo *spec.Swagger) {
swo.Info = &spec.Info{
InfoProps: spec.InfoProps{
Title: "UserService",
Description: "Resource for managing Users",
Contact: &spec.ContactInfo{
ContactInfoProps: spec.ContactInfoProps{
Name: "john",
Email: "john@doe.rp",
URL: "http://johndoe.org",
},
},
License: &spec.License{
LicenseProps: spec.LicenseProps{
Name: "MIT",
URL: "http://mit.org",
},
},
Version: "1.0.0",
},
}
swo.Tags = []spec.Tag{spec.Tag{TagProps: spec.TagProps{
Name: "users",
Description: "Managing users"}}}
}
// User is just a sample type
type User struct {
ID string `xml:"id" json:"id" description:"identifier of the user"`
Name string `xml:"name" json:"name" description:"name of the user" default:"john"`
Age int `xml:"age" json:"age" description:"age of the user" default:"21"`
}
假设,我们将上述代码保存在 user-webserver.go 文件中,执行以下命令可以启动 REST 服务器:
$ $ go run user-webserver.go
2024/08/19 08:45:42 start listening on localhost:8080
客户端调用命令如下:
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": "fanguiju"}'
curl -X GET -v -i http://127.0.0.1:8080/users/1
除此之外,go-restful 支持自动生成API文档,示例如下:
package main
// Note: this file is copied from https://github.com/emicklei/go-restful-openapi/blob/master/examples/user-resource.go
import (
"log"
"net/http"
restfulspec "github.com/emicklei/go-restful-openapi/v2"
restful "github.com/emicklei/go-restful/v3"
"github.com/go-openapi/spec"
)
type UserResource struct {
// normally one would use DAO (data access object)
users map[string]User
}
func (u UserResource) WebService() *restful.WebService {
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
tags := []string{"users"}
ws.Route(ws.GET("/").To(u.findAllUsers).
// docs
Doc("get all users").
Metadata(restfulspec.KeyOpenAPITags, tags).
Writes([]User{}).
Returns(200, "OK", []User{}))
ws.Route(ws.GET("/{user-id}").To(u.findUser).
// docs
Doc("get a user").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("integer").DefaultValue("1")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Writes(User{}). // on the response
Returns(200, "OK", User{}).
Returns(404, "Not Found", nil))
ws.Route(ws.PUT("/{user-id}").To(u.updateUser).
// docs
Doc("update a user").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(User{})) // from the request
ws.Route(ws.PUT("").To(u.createUser).
// docs
Doc("create a user").
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(User{})) // from the request
ws.Route(ws.DELETE("/{user-id}").To(u.removeUser).
// docs
Doc("delete a user").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")))
return ws
}
// GET http://localhost:8080/users
func (u UserResource) findAllUsers(request *restful.Request, response *restful.Response) {
list := []User{}
for _, each := range u.users {
list = append(list, each)
}
response.WriteEntity(list)
}
// GET http://localhost:8080/users/1
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
id := request.PathParameter("user-id")
usr := u.users[id]
if len(usr.ID) == 0 {
response.WriteErrorString(http.StatusNotFound, "User could not be found.")
} else {
response.WriteEntity(usr)
}
}
// PUT http://localhost:8080/users/1
// <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.WriteError(http.StatusInternalServerError, err)
}
}
// 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.WriteError(http.StatusInternalServerError, err)
}
}
// 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() {
u := UserResource{map[string]User{}}
restful.DefaultContainer.Add(u.WebService())
config := restfulspec.Config{
WebServices: restful.RegisteredWebServices(), // you control what services are visible
APIPath: "/apidocs.json",
PostBuildSwaggerObjectHandler: enrichSwaggerObject}
restful.DefaultContainer.Add(restfulspec.NewOpenAPIService(config))
// Optionally, you can install the Swagger Service which provides a nice Web UI on your REST API
// You need to download the Swagger HTML5 assets and change the FilePath location in the config below.
// Open http://localhost:8080/apidocs/?url=http://localhost:8080/apidocs.json
http.Handle("/apidocs/", http.StripPrefix("/apidocs/", http.FileServer(http.Dir("/Users/emicklei/Projects/swagger-ui/dist"))))
// Optionally, you may need to enable CORS for the UI to work.
cors := restful.CrossOriginResourceSharing{
AllowedHeaders: []string{"Content-Type", "Accept"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
CookiesAllowed: false,
Container: restful.DefaultContainer}
restful.DefaultContainer.Filter(cors.Filter)
log.Printf("Get the API using http://localhost:8080/apidocs.json")
log.Printf("Open Swagger UI using http://localhost:8080/apidocs/?url=http://localhost:8080/apidocs.json")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func enrichSwaggerObject(swo *spec.Swagger) {
swo.Info = &spec.Info{
InfoProps: spec.InfoProps{
Title: "UserService",
Description: "Resource for managing Users",
Contact: &spec.ContactInfo{
ContactInfoProps: spec.ContactInfoProps{
Name: "john",
Email: "john@doe.rp",
URL: "http://johndoe.org",
},
},
License: &spec.License{
LicenseProps: spec.LicenseProps{
Name: "MIT",
URL: "http://mit.org",
},
},
Version: "1.0.0",
},
}
swo.Tags = []spec.Tag{spec.Tag{TagProps: spec.TagProps{
Name: "users",
Description: "Managing users"}}}
}
// User is just a sample type
type User struct {
ID string `xml:"id" json:"id" description:"identifier of the user"`
Name string `xml:"name" json:"name" description:"name of the user" default:"john"`
Age int `xml:"age" json:"age" description:"age of the user" default:"21"`
}
go-restful 官方代码仓库提供了大量的使用示例,具体你可以参考:examples。
课程总结
这节课,我们围绕 kubernetes-apiserver 所依赖的 Go Web 框架 go-restful 展开讲解,先解释了为什么需要一个 RESTful 框架,然后系统梳理了 go-restful 的设计理念、核心概念与常用特性。
RESTful API 是一种 Web 服务接口规范,实现这类 API 的框架需要解决资源建模、路由匹配、请求校验、响应构造、过滤器、版本隔离等通用问题。go-restful 通过「Route → WebService → Container」三级抽象构建路由树:
-
Route 表示 “URL + HTTP Method + Handler” 的组合,Kubernetes 选用 CurlyRouter 以正则+动态参数的形式做高速匹配。
-
WebService 是一组拥有共同 RootPath、输入/输出格式与版本号的 Routes,相当于逻辑上的 API Group。
-
Container 则是一个真正的 HTTP 服务器,持有多个 WebService 及全局/局部 Filter,并将请求分发到对应 Handler。
go-restful 框架内置链式 Filter,为日志、认证、限流、CORS、异常恢复等横切逻辑提供挂载点,还支持 gzip/deflate 内容编码、Swagger/OpenAPI 文档自动生成、OPTIONS 处理等能力。
课后练习
-
请根据我们今天讲解的内容绘制一张图,标注出 go-restful 中 Container、WebService、Route、Filter 的调用顺序,并说明它们与标准库 net/http 的关系。
-
kubernetes-apiserver为什么不用 gin/echo 等更流行的框架?请你结合 CurlyRouter 特性与 kube-apiserver 的性能、兼容性诉求给出解释。
欢迎你在留言区与我交流讨论,如果今天的内容让你有所收获,也欢迎转发给有需要的朋友,我们下节课再见!
精选留言
2025-07-20 17:23:54