Обзор
go-restfulЭто веб-фреймворк, разработанный на языке go для быстрого создания спокойного стиля. Основной компонент k8s, kube-apiserver, использует этот фреймворк.Код этого фреймворка относительно прост.Вот простое введение в функцию, а затем проанализируйте соответствующий исходный код.
go-restful основан на официальной реализации golang net/http.Прежде чем углубляться в исследование, рекомендуется взглянуть на информацию, которую я собрал ранее.Официальный анализ исходного кода httpстатья
go-restful определяет три важные структуры данных:
- Маршрутизатор: представляет маршрут, включая URL-адрес и обработчик обратного вызова.
- Веб-сервис: представляет сервис
- Контейнер: представляет сервер
Отношения между этими тремя таковы:
- go-restful поддерживает несколько контейнеров, один контейнер эквивалентен одному http-серверу, разные контейнеры отслеживают разные адреса и порты.
- Каждый контейнер может содержать несколько веб-сервисов, что эквивалентно классификации набора различных сервисов.
- Каждая веб-служба содержит несколько маршрутизаторов (маршрутов), и маршрутизаторы направляются к соответствующей функции обработки (Handler Func) в соответствии с URL-адресом http-запроса.
Начать быстро
Пакет импорта:
go get github.com/emicklei/go-restful/v3
Пример кода hello-world выглядит следующим образом, доступ после запускаloclahost:8080/hello
Вы можете видеть, что страница отвечает на мир
package main
import (
"github.com/emicklei/go-restful/v3"
"io"
"log"
"net/http"
)
func main() {
// 创建WebService
ws := new(restful.WebService)
// 为WebService设置路由和回调函数
ws.Route(ws.GET("/hello").To(hello))
// 将WebService添加到默认生成的Container中
// 默认生成的container的代码在web_service_container.go的init方法中
restful.Add(ws)
// 启动服务
log.Fatal(http.ListenAndServe(":8080", nil))
}
// 路由对应的回调函数
func hello(req *restful.Request, resp *restful.Response) {
io.WriteString(resp, "world")
}
Анализ исходного кода
Увидев код быстрого старта выше, у вас есть сомнения: после создания WebServie он добавляется в Контейнер по умолчанию, и контейнер никому не передается, но контейнер может быть автоматически распознан, просто запустив сервисный монитор ?
Чтобы найти ответ, давайте вместе проанализируем исходный код. Перед этим рекомендуется взглянуть на то, что я собрал ранее оОфициальный анализ исходного кода http, потому что go-restful будет реализовывать функции на основе официального http-пакета.
На следующем рисунке показана основная логическая схема отсортированного исходного кода.
основная структура данных
Route
Как упоминалось ранее, Route — это одна из трех концепций go-restful.Внутренняя структура данных — это Route.Давайте сначала посмотрим на исходный код.
Расположение исходного кода: github.com/emicklei/go-restful/router.go
type Route struct {
Method string
Produces []string
Consumes []string
// 请求的路径:root path + described path
Path string
// handler处理函数
Function RouteFunction
// 拦截器
Filters []FilterFunction
If []RouteSelectionConditionFunction
// cached values for dispatching
relativePath string
pathParts []string
pathExpr *pathExpression // cached compilation of relativePath as RegExp
// documentation
Doc string
Notes string
Operation string
ParameterDocs []*Parameter
ResponseErrors map[int]ResponseError
DefaultResponse *ResponseError
ReadSample, WriteSample interface{} // structs that model an example request or response payload
// Extra information used to store custom information about the route.
Metadata map[string]interface{}
// marks a route as deprecated
Deprecated bool
//Overrides the container.contentEncodingEnabled
contentEncodingEnabled *bool
}
RouteBuilder
RouteBuilder используется для создания информации о маршруте, а режим построения используется в соответствии с именем
Расположение исходного кода: github.com/emicklei/go-restful/router.go
// 大部分属性和Route一样
type RouteBuilder struct {
rootPath string
currentPath string
produces []string
consumes []string
httpMethod string // required
function RouteFunction // required
filters []FilterFunction
conditions []RouteSelectionConditionFunction
typeNameHandleFunc TypeNameHandleFunction // required
...
}
Webservice
WebService имеет набор маршрутов, которые имеют общий rootPath и логически объединяют запросы маршрутизации с одинаковым префиксом.
Расположение исходного кода: github.com/emicklei/go-restful/web_service.go
type WebService struct {
// Webservice中的Route共享一个rootPath
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
// 保护路由,防止多线程写操作出现并发问题
routesLock sync.RWMutex
}
Container
Контейнер содержит несколько Сервисов, разные Контейнеры прослушивают разные IP-адреса или порты, а предоставляемые ими сервисы независимы.
Расположение исходного кода: github.com/emicklei/go-restful/container.go
type Container struct {
webServicesLock sync.RWMutex
// Container内部有多个webservice
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
}
Сортировка основного кода
Начните с кода из предыдущей демонстрации и проанализируйте весь процесс вызова.
Общий процесс включает в себя:
- Создайте объект веб-службы
- Добавьте адрес маршрутизации и функцию обработчика в объект WebService.
- Добавить WebService в контейнер (Containerr здесь не объявлен, используется контейнер по умолчанию)
- Запустите порт прослушивания службы и дождитесь запросов на обслуживание.
func main() {
ws := new(restful.WebService)
ws.Route(ws.GET("/hello").To(hello))
restful.Add(ws)
log.Fatal(http.ListenAndServe(":8080", nil))
}
WebService добавить маршрут
основной анализws.Route(ws.GET("/hello").To(hello))
Создание объекта RouteBuilder
// Get方法内部new了一个RouteBuilder,用于构造Route对象
func (w *WebService) GET(subPath string) *RouteBuilder {
// 典型的建造者模式用法
return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("GET").Path(subPath)
}
// 建造者模式:给属性赋值
// 其他的方法类似,就不再展开
func (b *RouteBuilder) typeNameHandler(handler TypeNameHandleFunction) *RouteBuilder {
b.typeNameHandleFunc = handler
return b
}
// Get方法后,属性并没有完全构造完,handler处理函数是用单独的To方法赋值的
func (b *RouteBuilder) To(function RouteFunction) *RouteBuilder {
b.function = function
return b
}
Генерация объекта Route на основе RouteBuilder
func (w *WebService) Route(builder *RouteBuilder) *WebService {
w.routesLock.Lock()
defer w.routesLock.Unlock()
// 填充默认值
builder.copyDefaults(w.produces, w.consumes)
// 调用RouteBuilder的Build方法,构造Route
// 并将Route添加到routes列表中
w.routes = append(w.routes, builder.Build())
return w
}
// Build方法返回Route对象
func (b *RouteBuilder) Build() Route {
...
route := Route{
...
}
route.postBuild()
return route
}
Добавить веб-сервис в контейнер
основной анализrestful.Add(ws)
. Особое внимание здесь:
- Передайте DefaultServeMux из http в ServeMux из DefaultServeMux
- Вызовите функцию ServeMux.HandleFunc в официальном http-пакете Golang для обработки запроса.
- Функция обработки унифицирована как c.dispatch, после чего диспетчеризация распределяется внутри компании.
Расположение исходного кода: github.com/emicklei/go-restful/web_service_container.go
// 定义全局变量,作为默认的Container
var DefaultContainer *Container
// init函数在别的包import时,自动触发。也就是只要引用了go-restful框架,就会默认有一个Container
func init() {
DefaultContainer = NewContainer()
// 这里将Golang中标准http包下的默认路由对象DefaultServeMux赋值给Container的ServeMux
// 这里要特别注意,正是因为这个地方的逻辑,就能回答前面我们提出的问题。go-restful和http库,通过这个赋值建立了关联关系。
DefaultContainer.ServeMux = http.DefaultServeMux
}
// 生成默认的container
func NewContainer() *Container {
return &Container{
webServices: []*WebService{},
ServeMux: http.NewServeMux(),
isRegisteredOnRoot: false,
containerFilters: []FilterFunction{},
doNotRecover: true,
recoverHandleFunc: logStackOnRecover,
serviceErrorHandleFunc: writeServiceError,
// 默认的路由选择器用的是CurlyRouter
router: CurlyRouter{},
contentEncodingEnabled: false}
}
// 将WebService添加到默认Container中
func Add(service *WebService) {
DefaultContainer.Add(service)
}
// Add
func (c *Container) Add(service *WebService) *Container {
...
// if rootPath was not set then lazy initialize it
if len(service.rootPath) == 0 {
service.Path("/")
}
// 判断有没有重复的RootPath,不同的WebService,rootPath不能重复
for _, each := range c.webServices {
if each.RootPath() == service.RootPath() {
log.Printf("WebService with duplicate root path detected:['%v']", each)
os.Exit(1)
}
}
if !c.isRegisteredOnRoot {
// 核心逻辑:为servcie添加handler处理函数
// 这里将c.ServeMux作为参数传入,这个值是前面提到的http.DefaultServeMux
c.isRegisteredOnRoot = c.addHandler(service, c.ServeMux)
}
// 将webServices添加到container的webservice列表中
c.webServices = append(c.webServices, service)
return c
}
// addHandler
func (c *Container) addHandler(service *WebService, serveMux *http.ServeMux) bool {
pattern := fixedPrefixPath(service.RootPath())
...
// 这里的关键函数:serveMux.HandleFunc,是Golang标准包中实现路由的函数
// go-restful中将路由处理函数统一交给c.dispatch函数,可以看出整个go-restful框架中,最核心的就是这个函数了
if !alreadyMapped {
serveMux.HandleFunc(pattern, c.dispatch)
if !strings.HasSuffix(pattern, "/") {
serveMux.HandleFunc(pattern+"/", c.dispatch)
}
}
return false
}
Диспетчеризация функции распределения маршрутов
Как реализовать иерархическое распределение по контейнеру -> веб-сервис -> обработчик?
go-restful framework пройденserveMux.HandleFunc(pattern, c.dispatch)
Функция, с одной стороны, подключает официальный механизм http-расширения, предоставляемый Golang, а с другой стороны реализует раздачу маршрутов через диспетчер, так что нет необходимости писать много обработчиков отдельно.
Ядром этой функции являетсяc.router.SelectRoute
, найдите подходящий веб-сервис и маршрут в соответствии с запросом
Расположение исходного кода: github.com/emicklei/go-restful/container.go
func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) {
...
// 根据请求,找到最合适的webService和route
// 这个方法后面单独介绍
func() {
...
webService, route, err = c.router.SelectRoute(
c.webServices,
httpRequest)
}()
...
if err != nil {
// 构造过滤器
chain := FilterChain{Filters: c.containerFilters, Target: func(req *Request, resp *Response) {
switch err.(type) {
case ServiceError:
ser := err.(ServiceError)
c.serviceErrorHandleFunc(ser, req, resp)
}
// TODO
}}
// 运行Container的过滤器
chain.ProcessFilter(NewRequest(httpRequest), NewResponse(writer))
return
}
// 尝试将router对象转为PathProcessor对象
// 我们使用的是默认的Container,前面介绍过router默认用的CurlyRouter,
// SelectRoute的其中一个实现类RouterJSR311,也实现了PathProcessor。所以如果用了RouterJSR311,这里接口转换才能获取到值
// 而默认的CurlyRouter并没有实现PathProcessor接口,因此这里转换后是空值,会走到下一个if语句
pathProcessor, routerProcessesPath := c.router.(PathProcessor)
if !routerProcessesPath {
// 使用默认的路处理器
pathProcessor = defaultPathProcessor{}
}
// 从request的url请求中抽取参数
pathParams := pathProcessor.ExtractParameters(route, webService, httpRequest.URL.Path)
wrappedRequest, wrappedResponse := route.wrapRequestResponse(writer, httpRequest, pathParams)
// 如果有filter的话,处理将所有的filter添加到filter链中
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
// 没有filter,通过route直接处理请求
route.Function(wrappedRequest, wrappedResponse)
}
}
маршрутизация
упоминалось в предыдущей рассылкеc.router.SelectRoute
Роль заключается в выборе соответствующего веб-сервиса и маршрута, который специально представлен здесь.
Свойство маршрутизатора в контейнере представляет собойRouteSelector
интерфейс
type RouteSelector interface {
// SelectRoute根据输入的http请求和webservice列表,找到一个路由并返回
SelectRoute(
webServices []*WebService,
httpRequest *http.Request) (selectedService *WebService, selected *Route, err error)
}
В структуре go-restful есть два класса реализации:
- CurlyRouter
- RouterJSR311
Предыдущий код анализа знает, что CurlyRouter является реализацией по умолчанию, поэтому здесь мы в основном анализируем функцию SelectRoute CurlyRouter.
// 选择路由功能
func (c CurlyRouter) SelectRoute(
webServices []*WebService,
httpRequest *http.Request) (selectedService *WebService, selected *Route, err error) {
// 解析url,根据'/'拆分为token列表
requestTokens := tokenizePath(httpRequest.URL.Path)
// 根据tokens列表和webservice的路由表做匹配,返回一个最合适的webservice
detectedService := c.detectWebService(requestTokens, webServices)
...
// 返回webservice中匹配的routes集合
candidateRoutes := c.selectRoutes(detectedService, requestTokens)
...
// 从前面的list中找到最合适的route
selectedRoute, err := c.detectRoute(candidateRoutes, httpRequest)
if selectedRoute == nil {
return detectedService, nil, err
}
return detectedService, selectedRoute, nil
}
// 选择webservice
func (c CurlyRouter) detectWebService(requestTokens []string, webServices []*WebService) *WebService {
var best *WebService
score := -1
for _, each := range webServices {
// 计算webservice的得分
matches, eachScore := c.computeWebserviceScore(requestTokens, each.pathExpr.tokens)
// 返回得分最高的webservice
if matches && (eachScore > score) {
best = each
score = eachScore
}
}
// 将得分最高的webservice返回
return best
}
// 计算webservice得分
func (c CurlyRouter) computeWebserviceScore(requestTokens []string, tokens []string) (bool, int) {
if len(tokens) > len(requestTokens) {
return false, 0
}
score := 0
for i := 0; i < len(tokens); i++ {
each := requestTokens[i]
other := tokens[i]
if len(each) == 0 && len(other) == 0 {
score++
continue
}
if len(other) > 0 && strings.HasPrefix(other, "{") {
// no empty match
if len(each) == 0 {
return false, score
}
score += 1
} else {
// not a parameter
if each != other {
return false, score
}
score += (len(tokens) - i) * 10 //fuzzy
}
}
return true, score
}
// 初选:匹配path,返回一批Route作为备选
func (c CurlyRouter) selectRoutes(ws *WebService, requestTokens []string) sortableCurlyRoutes {
// 选中的Route存放到sortableCurlyRoutes中
candidates := make(sortableCurlyRoutes, 0, 8)
// 遍历webservice下所有的route
for _, each := range ws.routes {
// paramCount:正则命中
// staticCount:完全匹配命中
matches, paramCount, staticCount := c.matchesRouteByPathTokens(each.pathParts, requestTokens, each.hasCustomVerb)
// 如果匹配,加入到备选列表中
if matches {
candidates.add(curlyRoute{each, paramCount, staticCount}) // TODO make sure Routes() return pointers?
}
}
// 排序备选的route
sort.Sort(candidates)
return candidates
}
// 二次筛选:匹配属性等信息。返回最合适的Route
func (c CurlyRouter) detectRoute(candidateRoutes sortableCurlyRoutes, httpRequest *http.Request) (*Route, error) {
// tracing is done inside detectRoute
return jsr311Router.detectRoute(candidateRoutes.routes(), httpRequest)
}
// 匹配多个属性是否匹配:method、content-type、accept
func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*Route, error) {
candidates := make([]*Route, 0, 8)
for i, each := range routes {
ok := true
for _, fn := range each.If {
if !fn(httpRequest) {
ok = false
break
}
}
if ok {
candidates = append(candidates, &routes[i])
}
}
if len(candidates) == 0 {
if trace {
traceLogger.Printf("no Route found (from %d) that passes conditional checks", len(routes))
}
return nil, NewError(http.StatusNotFound, "404: Not Found")
}
// 判断 http method 是否匹配
previous := candidates
candidates = candidates[:0]
for _, each := range previous {
if httpRequest.Method == each.Method {
candidates = append(candidates, each)
}
}
if len(candidates) == 0 {
if trace {
traceLogger.Printf("no Route found (in %d routes) that matches HTTP method %s\n", len(previous), httpRequest.Method)
}
allowed := []string{}
allowedLoop:
for _, candidate := range previous {
for _, method := range allowed {
if method == candidate.Method {
continue allowedLoop
}
}
allowed = append(allowed, candidate.Method)
}
header := http.Header{"Allow": []string{strings.Join(allowed, ", ")}}
return nil, NewErrorWithHeader(http.StatusMethodNotAllowed, "405: Method Not Allowed", header)
}
// 判断 Content-Type 是否匹配
contentType := httpRequest.Header.Get(HEADER_ContentType)
previous = candidates
candidates = candidates[:0]
for _, each := range previous {
if each.matchesContentType(contentType) {
candidates = append(candidates, each)
}
}
if len(candidates) == 0 {
if trace {
traceLogger.Printf("no Route found (from %d) that matches HTTP Content-Type: %s\n", len(previous), contentType)
}
if httpRequest.ContentLength > 0 {
return nil, NewError(http.StatusUnsupportedMediaType, "415: Unsupported Media Type")
}
}
// 判断 accept 是否匹配
previous = candidates
candidates = candidates[:0]
accept := httpRequest.Header.Get(HEADER_Accept)
if len(accept) == 0 {
accept = "*/*"
}
for _, each := range previous {
if each.matchesAccept(accept) {
candidates = append(candidates, each)
}
}
if len(candidates) == 0 {
if trace {
traceLogger.Printf("no Route found (from %d) that matches HTTP Accept: %s\n", len(previous), accept)
}
available := []string{}
for _, candidate := range previous {
available = append(available, candidate.Produces...)
}
return nil, NewError(
http.StatusNotAcceptable,
fmt.Sprintf("406: Not Acceptable\n\nAvailable representations: %s", strings.Join(available, ", ")),
)
}
// 如果有多个匹配,返回第一个
return candidates[0], nil
}
запустить службу
Как упоминалось ранее, go-restful напрямую управляет объектом HTTP-маршрутизации стандартной библиотеки golang http.DefaultServeMux, поэтому на этапе запуска службы нужно только вызвать запуск стандартной службы http, и никакой дополнительной обработки не требуется. которыйhttp.ListenAndServe(":8080", nil)
Суммировать
go-restful — не очень популярный веб-фреймворк golang, но он используется в k8s.В этой статье проводится простой анализ внутренней реализации go-restful с помощью анализа исходного кода. С точки зрения процесса анализа это действительно компактная и компактная структура. Мы не продолжили изучение более глубоких внутренних функций, пока мы можем понять исходный код компонента k8s kube-apiserver.
Реализация внутреннего ядра длится до тех пор, пока:
- Добавьте диспетчеризацию функции обработчика через объект маршрутизации по умолчанию DefaultServeMux пакета http.
- Все функции распределения маршрутов передаются диспетчеру
- диспетчеризация внутренне вызывает метод SelectRoute класса реализации по умолчанию CurlyRouter класса RouteSelector для выбора соответствующего маршрута.
- Вызовите метод обработчика, зарегистрированный в Route, для обработки запроса.