restful hello world
首次瀏覽下go-restful的工程結(jié)構(gòu)矾缓,從工程組織上面來看萝映,工程包括兩個部分:source文件及example文件记罚,其中source文件組成了工程的主體次泽,包括restful主要功能接口的實現(xiàn)及單元測試文件(以test.go結(jié)尾命名的文件)变秦,另外example目錄中主要包括了接口的使用案例成榜。
第一次閱讀go-restful源碼,以example目錄下的restful-hello-world.go作為入坑樣例,來跟蹤了解下restful如何組織封裝webservice及route的維護蹦玫。
restful-hello-world.go代碼如下:
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, "world")
}
restful初始化流程
1. ws := new(restful.WebService)
webservice定義在web_service.go中, 通過new分配了webservicef空間, 傳遞給ws指向新分配零值的指針
// 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
}
2.ws.Route(ws.GET("/hello").To(hello))
2.1 ws.GET("/hello")
第一步執(zhí)行的函數(shù),GET方法綁定在WebServices結(jié)構(gòu)體上赎婚,調(diào)用改函數(shù)參數(shù)為subPath = /hello
, 返回RouteBuilder指針
func (w *WebService) GET(subPath string) *RouteBuilder {
return new(RouteBuilder).typeNameHandler(w.typeNameHandleFunc).servicePath(w.rootPath).Method("GET").Path(subPath)
}
2.1.1 new(RouteBuilder)
在Get函數(shù)中刘绣,首先執(zhí)行new(RouteBuilder)
, 返回routebuilder
指針,在webservice的初始化后期可以發(fā)現(xiàn)挣输,RouteBuilder
的作用是通過用戶定義的參數(shù)纬凤,初始化route
結(jié)構(gòu),route結(jié)構(gòu)最終由websocket中的routes結(jié)構(gòu)進行存儲維護
// 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
typeNameHandleFunc TypeNameHandleFunction // required
// documentation
doc string
notes string
operation string
readSample, writeSample interface{}
parameters []*Parameter
errorMap map[int]ResponseError
metadata map[string]interface{}
deprecated bool
}
2.1.2 typeNameHandler(w.typeNameHandleFunc)
typeNameHandler
方法綁定在RouteBuilder
上, 該函數(shù)為賦值函數(shù)撩嚼,將webservice
中定義的typeNameHandleFunc TypeNameHandleFunction // required
傳遞給routebuilder
, 該變量定義了一類環(huán)函數(shù)的通用化模板停士,在初始化前RouteBuilder
結(jié)構(gòu)體中已經(jīng)初始化定義了typeNameHandleFunc TypeNameHandleFunction
。
// TypeNameHandleFunction declares functions that can handle translating the name of a sample object
// into the restful documentation for the service.
type TypeNameHandleFunction func(sample interface{}) string
// typeNameHandler sets the function that will convert types to strings in the parameter
// and model definitions.
func (b *RouteBuilder) typeNameHandler(handler TypeNameHandleFunction) *RouteBuilder {
b.typeNameHandleFunc = handler
return b
}
2.1.3 servicePath(w.rootPath)
w.rootPath 初始值為"",在該函數(shù)中完丽, 將webservice中定義的rootpath初始值傳遞給RouteBuilder,在RouteBuilder中默認的rootPath初始值也為""
func (b *RouteBuilder) servicePath(path string) *RouteBuilder {
b.rootPath = path
return b
}
2.1.4 Method("GET")
設(shè)置http方法恋技, 在該例子中,傳遞了GET方法
// Method specifies what HTTP method to match. Required.
func (b *RouteBuilder) Method(method string) *RouteBuilder {
b.httpMethod = method
return b
}
2.1.5 Path(subPath)
設(shè)置RouteBuilder的currentPath設(shè)置為subPath
// 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
}
2.2 To(hello)
改函數(shù)綁定用戶自定義的的處理函數(shù)handler
逻族,當用戶發(fā)起的http
訪問, 命中method=GET
path = subPath
后蜻底,執(zhí)行相關(guān)邏輯的function handler
func hello(req *restful.Request, resp *restful.Response) {
io.WriteString(resp, "world")
}
實際上在執(zhí)行webservice GET()
函數(shù),直接生成了RouteBuilder并返回對象聘鳞, 因此function的綁定朱躺,直接在RouteBuilder中進行
// RouteFunction declares the signature of a function that can be bound to a Route.
type RouteFunction func(*Request, *Response)
// 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
}
從代碼中可以看出自定義的函數(shù)類型包括兩部分, 分別是*Request, *Response
在restful中搁痛, 定義的request和response都是結(jié)構(gòu)體, 該結(jié)構(gòu)體中定義了http包數(shù)據(jù)各個模塊的存儲及獲取格式
type Request struct {
Request *http.Request
pathParameters map[string]string
attributes map[string]interface{} // for storing request-scoped values
selectedRoutePath string // root path + route path that matched the request, e.g. /meetings/{id}/attendees
}
type Response struct {
http.ResponseWriter
requestAccept string // mime-type what the Http Request says it wants to receive
routeProduces []string // mime-types what the Route says it can produce
statusCode int // HTTP status code that has been written explicitly (if zero then net/http has written 200)
contentLength int // number of bytes written for the response body
prettyPrint bool // controls the indentation feature of XML and JSON serialization. It is initialized using var PrettyPrintResponses.
err error // err property is kept when WriteError is called
hijacker http.Hijacker // if underlying ResponseWriter supports it
}
2.3 ws.Route(RouteBuilder))]
該函數(shù)主要創(chuàng)建新的路由關(guān)系,并將產(chǎn)生的routebuilder
添加到路由表中
// 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
}
2.3.1 w.routesLock.Lock()
在webservice初始化中宇弛,routesLock初始化為routesLock sync.RWMutex
,該變量主要作用是在動態(tài)路由使用后鸡典,保護路由表,防止被多線程同時讀寫
2.3.2 defer w.routesLock.Unlock()
采用defer方式golang特性枪芒, 函數(shù)執(zhí)行完成后彻况,stack執(zhí)行,釋放掉持有的鎖
2.3.3 builder.copyDefaults(w.produces, w.consumes)
將produces和consumes賦值給RouteBuilder
2.3.4 RouteBuilder.build()
// 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,
Notes: b.notes,
Operation: operationName,
ParameterDocs: b.parameters,
ResponseErrors: b.errorMap,
ReadSample: b.readSample,
WriteSample: b.writeSample,
Metadata: b.metadata,
Deprecated: b.deprecated}
route.postBuild()
return route
}
build的過程主要是初始化參數(shù)的檢查及route初始化過程, 在route的初始化中除了使用routebuiler之前初始化的部分參數(shù)舅踪,還針對path處理了pathexpr纽甘,newPathExpression(b.currentPath)
2.3.5 w.routes = append(w.routes, builder.Build())
將創(chuàng)建的route添加到routes slice中。
- 可以看出來結(jié)構(gòu)上 route是由routebuilder進行創(chuàng)建
- 實際上產(chǎn)生作用的是webservice中的route列表
3.restful.Add(ws)
// Add registers a new WebService add it to the DefaultContainer.
func Add(service *WebService) {
DefaultContainer.Add(service)
}
在ws外面在加了一層container抽碌, 改container在init中進行了初始化
func init() {
DefaultContainer = NewContainer()
DefaultContainer.ServeMux = http.DefaultServeMux
}
3.1 DefaultContainer = NewContainer()
創(chuàng)建container對象悍赢,并進行初始化, 因此可以看出ws可以有很多個货徙,統(tǒng)一由container進行維護左权,默認的route路徑/
就是在這里進行的賦值
// NewContainer creates a new Container using a new ServeMux and default router (CurlyRouter)
func NewContainer() *Container {
return &Container{
webServices: []*WebService{},
ServeMux: http.NewServeMux(),
isRegisteredOnRoot: false,
containerFilters: []FilterFunction{},
doNotRecover: true,
recoverHandleFunc: logStackOnRecover,
serviceErrorHandleFunc: writeServiceError,
router: CurlyRouter{},
contentEncodingEnabled: false}
}
3.2 DefaultContainer.Add(service)
從代碼中可以看出來webservice的區(qū)分是通過rootPath進行區(qū)分的, webservices要保證rootpath的唯一性
// Add a WebService to the Container. It will detect duplicate root paths and exit in that case.
func (c *Container) Add(service *WebService) *Container {
c.webServicesLock.Lock()
defer c.webServicesLock.Unlock()
// if rootPath was not set then lazy initialize it
if len(service.rootPath) == 0 {
service.Path("/")
}
// cannot have duplicate root paths
for _, each := range c.webServices {
if each.RootPath() == service.RootPath() {
log.Printf("WebService with duplicate root path detected:['%v']", each)
os.Exit(1)
}
}
// 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的綁定中痴颊,只將根目錄注冊到了ServeMux中, 綁定的對應(yīng)的函數(shù)為container.dispatch()
赏迟,這樣做的原因是在http請求中,通過golang中net包中的ServeMux進行路由轉(zhuǎn)發(fā)蠢棱,將所有命中根目錄的uri流量分發(fā)到ws中锌杀。
// addHandler may set a new HandleFunc for the serveMux
// this function must run inside the critical region protected by the webServicesLock.
// returns true if the function was registered on root ("/")
func (c *Container) addHandler(service *WebService, serveMux *http.ServeMux) bool {
pattern := fixedPrefixPath(service.RootPath())
// check if root path registration is needed
if "/" == pattern || "" == pattern {
serveMux.HandleFunc("/", c.dispatch)
return true
}
// detect if registration already exists
alreadyMapped := false
for _, each := range c.webServices {
if each.RootPath() == service.RootPath() {
alreadyMapped = true
break
}
}
if !alreadyMapped {
serveMux.HandleFunc(pattern, c.dispatch)
if !strings.HasSuffix(pattern, "/") {
serveMux.HandleFunc(pattern+"/", c.dispatch)
}
}
return false
}
4. log.Fatal(http.ListenAndServe(":8080", nil))
創(chuàng)建端口監(jiān)聽甩栈,將用戶的http請求route到對應(yīng)的函數(shù)中, ListenAndServe 在net package中, http routes相關(guān)由ServMux進行維護, 由于在上文hello worlde的樣子中,container將根目錄及c.dispatch初始化到ServeMux中糕再。因此量没,當發(fā)送get請求后,會跳轉(zhuǎn)到container.dispatch中進行二次http route亿鲜。
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
restful響應(yīng)流程
1. 路由
上文中通過container允蜈、webservice、route的初始化流程蒿柳,在helloworld的樣例中饶套,外部通過http get訪問時,http package中的servMux進行了第一次路由找到了對應(yīng)的container垒探,后調(diào)用container.dispatch進行二次路由查找
//閉包
func() {
c.webServicesLock.RLock()
defer c.webServicesLock.RUnlock()
webService, route, err = c.router.SelectRoute(
c.webServices,
httpRequest)
}()
2 c.router.SelectRoute
func (c CurlyRouter) SelectRoute(
webServices []*WebService,
httpRequest *http.Request) (selectedService *WebService, selected *Route, err error) {
requestTokens := tokenizePath(httpRequest.URL.Path)
detectedService := c.detectWebService(requestTokens, webServices)
if detectedService == nil {
if trace {
traceLogger.Printf("no WebService was found to match URL path:%s\n", httpRequest.URL.Path)
}
return nil, nil, NewError(http.StatusNotFound, "404: Page Not Found")
}
candidateRoutes := c.selectRoutes(detectedService, requestTokens)
if len(candidateRoutes) == 0 {
if trace {
traceLogger.Printf("no Route in WebService with path %s was found to match URL path:%s\n", detectedService.rootPath, httpRequest.URL.Path)
}
return detectedService, nil, NewError(http.StatusNotFound, "404: Page Not Found")
}
selectedRoute, err := c.detectRoute(candidateRoutes, httpRequest)
if selectedRoute == nil {
return detectedService, nil, err
}
return detectedService, selectedRoute, nil
}
在路由轉(zhuǎn)換的邏輯中妓蛮, 主要包含了3次路由轉(zhuǎn)換
2.1 路由查找webservice
//從url中split路徑為token列表
requestTokens := tokenizePath(httpRequest.URL.Path)
/*
將獲取到的tokens與webServices中的pathExpr.tokens進行計算最大分值(計算分值的方式是token比對
直到出現(xiàn)不同對于{分數(shù)自增1跳轉(zhuǎn)到下一步),并返回分值最高的匹配
*/
c.detectWebService(requestTokens, webServices)
func (c CurlyRouter) detectWebService(requestTokens []string, webServices []*WebService) *WebService {
var best *WebService
score := -1
for _, each := range webServices {
//在該例子中each.pathExpr.tokens為空圾叼,如果為空蛤克,默認范圍列表中第一個ws
matches, eachScore := c.computeWebserviceScore(requestTokens, each.pathExpr.tokens)
if matches && (eachScore > score) {
best = each
score = eachScore
}
}
return best
}
2.2 路由查找Route
func (c CurlyRouter) selectRoutes(ws *WebService, requestTokens []string) sortableCurlyRoutes {
candidates := sortableCurlyRoutes{}
for _, each := range ws.routes {
//遍歷路由查找,將routes中的tokens進行遍歷查找夷蚊, 找到能夠匹配到當前路徑的route
matches, paramCount, staticCount := c.matchesRouteByPathTokens(each.pathParts, requestTokens)
if matches {
candidates.add(curlyRoute{each, paramCount, staticCount}) // TODO make sure Routes() return pointers?
}
}
sort.Sort(sort.Reverse(candidates))
return candidates
}
func (c CurlyRouter) matchesRouteByPathTokens(routeTokens, requestTokens []string) (matches bool, paramCount int, staticCount int) {
if len(routeTokens) < len(requestTokens) {
// proceed in matching only if last routeToken is wildcard
count := len(routeTokens)
if count == 0 || !strings.HasSuffix(routeTokens[count-1], "*}") {
return false, 0, 0
}
// proceed
}
for i, routeToken := range routeTokens {
if i == len(requestTokens) {
// reached end of request path
return false, 0, 0
}
requestToken := requestTokens[i]
if strings.HasPrefix(routeToken, "{") {
paramCount++
if colon := strings.Index(routeToken, ":"); colon != -1 {
// match by regex
matchesToken, matchesRemainder := c.regularMatchesPathToken(routeToken, colon, requestToken)
if !matchesToken {
return false, 0, 0
}
if matchesRemainder {
break
}
}
} else { // no { prefix
if requestToken != routeToken {
return false, 0, 0
}
staticCount++
}
}
return true, paramCount, staticCount
}
在routes中查找到的對應(yīng)的route构挤,統(tǒng)計放在sortableCurlyRoute中進行處理,sortableCurlyRoute是一個封裝的按照一定規(guī)則進行排序的curlyRoute數(shù)組
type curlyRoute struct {
route Route
paramCount int //正則命中
staticCount int //完全匹配命中
}
2.3. route屬性相關(guān)匹配
在上面中通過path的比對拿到了一個route列表惕鼓, 列表中記錄了route的優(yōu)先級筋现,在列表中的route都保證了對path的命中, 在function的匹配過程中需要依次檢查:
- http method
// http method methodOk := []Route{} for _, each := range ifOk { if httpRequest.Method == each.Method { methodOk = append(methodOk, each) } } if len(methodOk) == 0 { if trace { traceLogger.Printf("no Route found (in %d routes) that matches HTTP method %s\n", len(routes), httpRequest.Method) } return nil, NewError(http.StatusMethodNotAllowed, "405: Method Not Allowed") } inputMediaOk := methodOk
- content-type
contentType := httpRequest.Header.Get(HEADER_ContentType) inputMediaOk = []Route{} for _, each := range methodOk { if each.matchesContentType(contentType) { inputMediaOk = append(inputMediaOk, each) } }
- accept
outputMediaOk := []Route{} accept := httpRequest.Header.Get(HEADER_Accept) if len(accept) == 0 { accept = "*/*" } for _, each := range inputMediaOk { if each.matchesAccept(accept) { outputMediaOk = append(outputMediaOk, each) } } if len(outputMediaOk) == 0 { if trace { traceLogger.Printf("no Route found (from %d) that matches HTTP Accept: %s\n", len(inputMediaOk), accept) } return nil, NewError(http.StatusNotAcceptable, "406: Not Acceptable") }
pathProcessor, routerProcessesPath := c.router.(PathProcessor) if !routerProcessesPath { pathProcessor = defaultPathProcessor{} }
在go-restful中支持fliter的定義箱歧, 在執(zhí)行對應(yīng)function之前矾飞,需要對fliter檢查是否命中
pathProcessor, routerProcessesPath := c.router.(PathProcessor)
if !routerProcessesPath {
pathProcessor = defaultPathProcessor{}
}
pathParams := pathProcessor.ExtractParameters(route, webService, httpRequest.URL.Path)
wrappedRequest, wrappedResponse := route.wrapRequestResponse(writer, httpRequest, pathParams)
// pass through filters (if any)
if len(c.containerFilters)+len(webService.filters)+len(route.Filters) > 0 {
// compose filter chain
allFilters := []FilterFunction{}
allFilters = append(allFilters, c.containerFilters...)
allFilters = append(allFilters, webService.filters...)
allFilters = append(allFilters, route.Filters...)
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 {
// no filters, handle request by route
route.Function(wrappedRequest, wrappedResponse)
}
在hello world例子中沒有fliter相關(guān)功能, 因此可以直接跳轉(zhuǎn)到route.Function(wrappedRequest, wrappedResponse)
呀邢,執(zhí)行hello world對應(yīng)的function, function中調(diào)用io.WriteString(resp, "world")
將resp寫回到net/server緩沖區(qū)中洒沦,io loop并寫回到socket fd中完成通信。
func hello(req *restful.Request, resp *restful.Response) {
fmt.Println("hello world")
io.WriteString(resp, "world")
}
層級關(guān)系
Route
路由包含兩種价淌,一種是標準JSR311接口規(guī)范的實現(xiàn)RouterJSR311,一種是快速路由CurlyRouter申眼。
CurlyRouter支持正則表達式和動態(tài)參數(shù),相比RouterJSR11更加輕量級输钩,apiserver中使用的就是這種路由豺型。
一種Route的設(shè)定包含:請求方法(http Method),請求路徑(URL Path),輸入輸出類型(JSON/YAML)以及對應(yīng)的回掉函數(shù)restful.RouteFunction买乃,響應(yīng)內(nèi)容類型(Accept)等姻氨。
WebService
webservice中維護了route的集合,功能上主要維護了一組route的rootpath、method剪验、fliter等屬性
Container
Container邏輯上是WebService的集合肴焊,功能上可以實現(xiàn)多終端的效果前联,不同的container可以綁定到不同的ip或者port上面