gf框架之服務註冊 - 框架核心組件,比你覺得的更強大

文章來源:http://gf.johng.cn/494368git

當用戶訪問某個URI時,Web Server可以精確的調用特定的服務接口提供服務,這些都是經過「服務註冊」來實現的。Web Server提供服務須要回調函數/方法/對象/控制器的支持,ghttp包支持多種服務註冊模式,爲開發者提供很是強大和靈活的接口功能。服務註冊是整個Web Server最核心的部分,也是gf框架中最精心設計的一個模塊。本章節將會進行詳細介紹。github

服務註冊管理由ghttp包提供,API文檔地址:godoc.org/github.com/johng-cn/gf設計模式

服務註冊介紹

本章開始以前,咱們再來看一下本手冊開頭的Hello World程序:安全

package main

import "gitee.com/johng/gf/g/net/ghttp"

func main() {
    ghttp.GetServer().BindHandler("/", func(r *ghttp.Request) {
        r.Response.Write("哈嘍世界!")
    })
}

其中,使用BindHandler方法進行服務註冊的方式叫作「回調函數註冊」,是最簡單的一種服務註冊方式。經過給指定的Web Server上對應的URI註冊一個可執行的方法,當客戶端訪問該URI時,Web Server便自動調用對應註冊的回調函數來執行處理。在回調函數註冊中,每一個註冊函數都會有一個ghttp.Request對象參數指針,表示每一個請求特定的獨立的請求處理對象,回調函數能夠經過該對象獲取提交請求參數,也能夠返回處理結果數據。併發

服務註冊方式比較

在詳細講解每一種註冊方式以前,先看看每種註冊方式各自的優缺點,以便在不一樣的業務場景中選擇更適合的註冊方式。若是暫時不理解這個表格沒有關係,能夠在瞭解完每一種註冊方式以後再回過頭來看,也許會更清晰。mvc

註冊方式 使用難度 安全係數 執行性能 內存消耗
控制器註冊
執行對象註冊
回調函數註冊

比較指標說明:app

  1. 使用難度:主要指對於執行流程以及數據管理維護的複雜度;
  2. 安全係數:主要指在異步多協程下的數據安全管理維護;
  3. 執行性能:執行性能,相對比較的結果;
  4. 內存消耗:內存消耗,相對比較的結果;

服務註冊方法列表

func (s *Server) BindController(pattern string, c Controller) error
func (s *Server) BindControllerMethod(pattern string, c Controller, methods string) error
func (s *Server) BindControllerRest(pattern string, c Controller) error

func (s *Server) BindObject(pattern string, obj interface{}) error
func (s *Server) BindObjectMethod(pattern string, obj interface{}, methods string) error
func (s *Server) BindObjectRest(pattern string, obj interface{}) error

func (s *Server) BindHandler(pattern string, handler HandlerFunc) error

其中BindController*方法用於控制器相關注冊,BindObject*方法用於對象相關注冊,BindHandler方法用於特定的回調函數註冊。框架

服務註冊使用的pattern參數格式以下:異步

[HttpMethod:]路由規則[@域名]

其中HttpMethod(支持的Method:GET,PUT,POST,DELETE,PATCH,HEAD,CONNECT,OPTIONS,TRACE)和域名爲非必需參數,通常來講直接給定路由規則(路由規則分爲靜態路由動態路由,爲便於演示本章節全部服務註冊均採用靜態路由規則,路由規則的詳細介紹請查看【路由控制】章節)參數便可。由於須要使用HttpMethod註冊的狀況大多數爲RESTful控制器,直接使用RESTful相關方法註冊便可,域名支持也可使用Domain方法來進行綁定。函數

此外BindController*系列方法第二個參數爲控制器接口,給定的參數必須實現ghttp.Controller接口。簡便的作法是用戶自定義的控制器直接繼承gmvc.Controller基類便可,gmvc.Controller已經實現了對應的接口方法。

域名服務註冊方法

服務註冊支持綁定域名,如下是對應的方法列表:

func (d *Domain) BindController(pattern string, c Controller) error
func (d *Domain) BindControllerMethod(pattern string, c Controller, methods string) error
func (d *Domain) BindControllerRest(pattern string, c Controller) error

func (d *Domain) BindObject(pattern string, obj interface{}) error
func (d *Domain) BindObjectMethod(pattern string, obj interface{}, methods string) error
func (d *Domain) BindObjectRest(pattern string, obj interface{}) error

func (d *Domain) BindHandler(pattern string, handler HandlerFunc) error

各項參數說明和Server的對應方法一致,只不過在Domain對象的底層會自動將方法綁定到Domain指定的域名列表中,只有對應的域名才能提供訪問。

咱們來看一個簡單的例子,咱們將前面的Hello World程序改爲以下形式:

package main

import "gitee.com/johng/gf/g/net/ghttp"

func init() {
    ghttp.GetServer().Domain("localhost").BindHandler("/", func(r *ghttp.Request) {
        r.Response.Write("Hello World!")
    })
}

咱們再次使用 http://127.0.0.1/ 進行訪問,發現Web Server返回404,爲何呢?由於該程序中的回調函數只註冊到了localhost域名中,其餘域名天然沒法訪問。固然,前面也提到Domain方法的域名參數支持多個,自定義域名的服務註冊至關方便。

服務註冊初始化

全部的服務註冊統一在包的init初始化方法中完成(init是Go語言內置的包初始化方法,而且一個包中支持多個init方法),一個包能夠包含多個文件,每一個文件均可以有一個init初始化方法,能夠分開註冊,在使用的時候會經過同一個包引入進程序,自動調用初始化方法完成註冊。能夠參考示例文件。

來看一個例子:

gitee.com/johng/gf/blob/master/geg/frame/mvc/main.go

package main

import (
    "gitee.com/johng/gf/g/net/ghttp"
    _ "gitee.com/johng/gf/geg/frame/mvc/controller/demo"
)

func main() {
    ghttp.GetServer().SetPort(8199)
    ghttp.GetServer().Run()
}

其中經過:

import _ "gitee.com/johng/gf/geg/frame/mvc/controller/demo"

這樣一條相似於all in one的語句便完成了對包中的全部控制器的引入和註冊(固然,包中的init應當實現註冊方法調用),在demo包中包含了多個控制器、執行對象、回調函數的註冊,demo包具體的控制器註冊以及相關邏輯咱們將在後續章節繼續介紹。

控制器註冊

控制器註冊

這種方式將每個請求都當作一個控制器對象來處理,比較相似且媲美於PHP的請求執行模式,當一個請求進來以後,當即初始化一個新的控制器對象進行處理,處理完成以後釋放控制器資源。這種服務註冊方式的優勢是簡單、安全、OOP設計,每一個請求的控制器嚴格數據隔離,成員變量沒法相互共享。

咱們能夠經過ghttp.BindController方法完成控制器的註冊。

gitee.com/johng/gf/blob/master/geg/frame/mvc/controller/demo/user.go

package demo

import (
    "gitee.com/johng/gf/g/net/ghttp"
    "gitee.com/johng/gf/g/frame/gmvc"
)

// 定義業務相關的控制器對象,
// 建議命名規範中控制器統一使用Controller前綴,後期代碼維護時便於區分
type ControllerUser struct {
    gmvc.Controller
}

// 初始化控制器對象,並綁定操做到Web Server
func init() {
    // 綁定控制器到指定URI,全部控制器的公開方法將會映射到指定URI末尾
    // 例如該方法執行後,查看效果可訪問:
    // http://127.0.0.1:8199/user/name
    // http://127.0.0.1:8199/user/age
    ghttp.GetServer().BindController("/user", &ControllerUser{})
}

// 定義操做邏輯 - 展現姓名
func (c *ControllerUser) Name() {
    c.Response.Write("John")
}

// 定義操做邏輯 - 展現年齡
func (c *ControllerUser) Age() {
    c.Response.Write("18")
}

服務註冊必須提供註冊的URI,註冊時ghttp會將全部控制器的公開方法將會映射到指定URI末尾,具體參見示例代碼說明。註冊的控制器參數是一個ghttp.Controller接口,參數直接傳遞自定義的控制器對象指針便可(&ControllerUser{},實際上只要繼承了gmvc.Controller基類,控制器的指針對象便已經自動實現了ghttpController接口)。ghttp經過解析該對象指針獲取對應的控制器方法,生成反射類型,處理請求時再根據該反射類型自動生成對應的控制器對象,處理客戶端請求,處理完後自動銷燬該控制器對象。

控制器方法註冊

假如控制器中有若干公開方法,可是我只想註冊其中幾個,其他的方法我不想對外公開,怎麼辦?

實際開發中不免會遇到這種場景,固然ghttp也是支持這種需求。咱們能夠經過ghttp.BindControllerMethod方法完成對控制器指定方法的註冊。相對於ghttp.BindController註冊方法,ghttp.BindControllerMetho僅僅多了一個方法名稱參數methods,參數支持傳入多個方法名稱,多個名稱以英文「,」號分隔(方法參數區分大小寫)。

看下面這個例子,執行後ControllerMethod的Name和Age方法將被註冊到Web Server提供服務,而Info方法卻不會對外公開。

gitee.com/johng/gf/blob/master/geg/frame/mvc/controller/demo/method.go

package demo

import (
    "gitee.com/johng/gf/g/net/ghttp"
    "gitee.com/johng/gf/g/frame/gmvc"
)

type ControllerMethod struct {
    gmvc.Controller
}

func init() {
    ghttp.GetServer().BindControllerMethod("/method", &ControllerMethod{}, "Name, Age")
}

func (c *ControllerMethod) Name() {
    c.Response.Write("John")
}

func (c *ControllerMethod) Age() {
    c.Response.Write("18")
}

func (c *ControllerMethod) Info() {
    c.Response.Write("Info")
}

啓動外層的main.go,咱們嘗試着訪問http://127.0.0.1:8199/method/info,由於沒有對該方法執行註冊,所以會發現返回404;而http://127.0.0.1:8199/method/namehttp://127.0.0.1:8199/method/age卻可以正常訪問。

RESTful控制器註冊

RESTful設計方式的控制器,一般用於API服務。在這種模式下,HTTP的Method將會映射到控制器對應的方法名稱,例如:POST方式將會映射到控制器的Post方法中,DELETE方式將會映射到控制器的Delete方法中。其餘非HTTP Method命名的方法,即便是定義的包公開方法,將沒法完成自動註冊,對於應用端不可見。固然,若是控制器並未定義對應HTTP Method的方法,該Method請求下將會返回 HTTP Status 404。此外,控制器方法名稱須要保證是與HTTP Method相同的公開方法且方法名首字母大寫。

這種方式註冊的控制器,運行模式和「控制器註冊」模式相同。咱們能夠經過ghttp.BindControllerRest方法完成RESTful控制器的註冊。

如下是一個示例:

gitee.com/johng/gf/blob/master/geg/frame/mvc/controller/demo/rest.go

package demo

import (
    "gitee.com/johng/gf/g/net/ghttp"
    "gitee.com/johng/gf/g/frame/gmvc"
)

// 測試控制器
type ControllerRest struct {
    gmvc.Controller
}

// 初始化控制器對象,並綁定操做到Web Server
func init() {
    // 控制器公開方法中與HTTP Method方法同名的方法將會自動綁定映射
    ghttp.GetServer().BindControllerRest("/user", &ControllerRest{})
}

// RESTFul - GET
func (c *ControllerRest) Get() {
    c.Response.Write("RESTFul HTTP Method GET")
}

// RESTFul - POST
func (c *ControllerRest) Post() {
    c.Response.Write("RESTFul HTTP Method POST")
}

// RESTFul - DELETE
func (c *ControllerRest) Delete() {
    c.Response.Write("RESTFul HTTP Method DELETE")
}

// 該方法沒法映射,將會沒法訪問到
func (c *ControllerRest) Hello() {
    c.Response.Write("Hello")
}

Init與Shut回調方法

ghttp.Controller接口中的InitShut是兩個在HTTP請求流程中被Web Server自動調用的特殊方法(相似構造函數和析構函數的做用)。gmvc.Controller基類中已經實現了這兩個方法,用戶自定義的控制器類直接繼承gmvc.Controller便可。若是須要自定義請求初始化以及請求結束時的一些業務邏輯操做,能夠在自定義控制器中重載這兩個方法

  1. ghttp.Controller接口

    type Controller interface {
        Init(*Request)
        Shut(*Request)
    }
  2. gmvc.Controller基類

    type Controller struct {
        Request  *ghttp.Request  // 請求數據對象
        Response *ghttp.Response // 返回數據對象(r.Response)
        Server   *ghttp.Server   // Web Server對象(r.Server)
        Cookie   *ghttp.Cookie   // COOKIE操做對象
        Session  *ghttp.Session  // SESSION操做對象
        View     *View           // 視圖對象
    }
  3. Init回調方法
    控制器初始化方法,參數是當前請求的對象。gmvc.Controller基類中的Init方法是對自身成員對象的初始化。
  4. Shut回調方法
    當請求結束時被Web Server自動調用,能夠用於控制器一些收尾處理的操做。默認不執行任何操做。

執行對象註冊

執行對象註冊

控制器註冊的設計模式相對來講比較簡單,也易於管理,可是因爲每一次請求都是新建一個控制器對象來處理,而且使用了反射機制,會有必定的性能損耗。

執行對象註冊相對來講是一種比較高效的執行方式,可是運行機制、設計模式與控制器註冊徹底不一樣。與字面意思相同,執行對象註冊是在註冊時便給定一個實例化的對象,之後每個請求都交給該對象(同一對象)處理,該對象常駐內存不釋放。因爲相比較控制器註冊來講,執行對象註冊方式在處理請求的時候不須要不停地建立/銷燬控制器對象,所以請求處理效率會高不少。

這種註冊方式的缺點也很明顯,服務端進程在啓動時便須要初始化這些執行對象,而且這些對象須要自行負責對自身數據的併發安全維護。執行對象的定義沒有嚴格要求,也沒有強行要求繼承gmvc.Controller控制器基類,由於在請求進入時沒有自動初始化流程,內部的成員變量須要自行維護(包括變量初始化,變量銷燬等)。

咱們能夠經過ghttp.BindObject方法完成執行對象的註冊。

gitee.com/johng/gf/blob/master/geg/frame/mvc/controller/demo/object.go

package demo

import "gitee.com/johng/gf/g/net/ghttp"

type Object struct {}

func init() {
    ghttp.GetServer().BindObject("/object", &Object{})
}

func (o *Object) Show(r *ghttp.Request) {
    r.Response.Write("It's show time bibi!")
}

能夠看到,執行對象在進行服務註冊時便生成了一個對象(執行對象在Web Server啓動時便生成),此後無論多少請求進入,Web Server都是將請求轉交給該對象對應的方法進行處理。須要注意的是,公開方法的定義與控制器註冊不一樣,必須爲如下形式:

func(r *ghttp.Request)

不然沒法完成註冊,調用註冊方法時會有錯誤提示,形如:

panic: interface conversion: interface {} is xxx, not func(*ghttp.Request)

該示例執行後能夠經過,經過http://127.0.0.1:8199/object/show查看效果。

對象方法註冊

對象方法註冊原理相似於控制器方法註冊,只公開執行對象中的特定方法。

來看一個例子:

package demo

import (
    "gitee.com/johng/gf/g/net/ghttp"
)

type ObjectMethod struct {}

func init() {
    obj := &ObjectMethod{}
    ghttp.GetServer().BindObjectMethod("/object-method", obj, "Show1, Show2, Show3")
    ghttp.GetServer().Domain("localhost").BindObjectMethod("/object-method", obj, "Show4")
}

func (o *ObjectMethod) Show1(r *ghttp.Request) {
    r.Response.Write("show 1")
}

func (o *ObjectMethod) Show2(r *ghttp.Request) {
    r.Response.Write("show 2")
}

func (o *ObjectMethod) Show3(r *ghttp.Request) {
    r.Response.Write("show 3")
}

func (o *ObjectMethod) Show4(r *ghttp.Request) {
    r.Response.Write("show 4")
}

這個例子比較簡單,而且也演示了域名綁定執行對象方法的操做。ObjectMethod對象的
http://127.0.0.1:8199/object-method/show1
http://127.0.0.1:8199/object-method/show2
http://127.0.0.1:8199/object-method/show3
這3個接口只能經過127.0.0.1的IP訪問,而Show4這個方法只能經過
http://localhost:8199/object-method/show4
訪問。

RESTful對象註冊

和REST控制器註冊相似,只不過註冊的是一個實例化的對象。咱們能夠經過ghttp.BindObjectRest方法完成REST對象的註冊。

gitee.com/johng/gf/blob/master/geg/frame/mvc/controller/demo/object_rest.go

package demo

import "gitee.com/johng/gf/g/net/ghttp"

// 測試綁定對象
type ObjectRest struct {}

func init() {
    ghttp.GetServer().BindObjectRest("/object-rest", &ObjectRest{})
}

// RESTFul - GET
func (o *ObjectRest) Get(r *ghttp.Request) {
    r.Response.Write("RESTFul HTTP Method GET")
}

// RESTFul - POST
func (c *ObjectRest) Post(r *ghttp.Request) {
    r.Response.Write("RESTFul HTTP Method POST")
}

// RESTFul - DELETE
func (c *ObjectRest) Delete(r *ghttp.Request) {
    r.Response.Write("RESTFul HTTP Method DELETE")
}

// 該方法沒法映射,將會沒法訪問到
func (c *ObjectRest) Hello(r *ghttp.Request) {
    r.Response.Write("Hello")
}

回調函數註冊

這種方式的運行機制相似於執行對象註冊,不過註冊的是一個函數/方法。相比較於執行對象註冊,註冊時不會有額外的對象實例化開銷,註冊時只是保存了一個函數/方法的指針地址。這種方式的服務註冊比較靈活,註冊的服務能夠是一個實例化對象的方法地址,也能夠是一個包方法地址。服務須要的數據能夠經過包內部變量形式或者對象內部變量形式進行管理,開發者可根據實際狀況進行靈活控制。

咱們能夠經過ghttp.BindHandler方法完成回調函數的註冊。

gitee.com/johng/gf/blob/master/geg/frame/mvc/controller/demo/apple_pen.go

package demo

import "gitee.com/johng/gf/g/net/ghttp"

func init() {
    ghttp.GetServer().BindHandler("/apple",     Apple)
    ghttp.GetServer().BindHandler("/pen",       Pen)
    ghttp.GetServer().BindHandler("/apple-pen", ApplePen)
}

func Apple(r *ghttp.Request) {
    r.Response.Write("Apple")
}

func Pen(r *ghttp.Request) {
    r.Response.Write("Pen")
}

func ApplePen(r *ghttp.Request) {
    r.Response.Write("Apple-Pen")
}

事件回調註冊

圖片描述

ghttp.Server提供了事件回調註冊功能,支持用戶對於某一事件進行自定義監聽處理,按照URI pattern方式進行綁定註冊。支持多個方法對同一事件進行監聽,ghttp.Server將會按照註冊順序進行回調方法調用。
相關方法以下:

func (s *Server) BindHookHandler(pattern string, hook string, handler HandlerFunc) error
func (s *Server) BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) error

固然域名對象也支持事件回調註冊:

func (d *Domain) BindHookHandler(pattern string, hook string, handler HandlerFunc) error
func (d *Domain) BindHookHandlerByMap(pattern string, hookmap map[string]HandlerFunc) error

支持的事件列表:

  1. BeforeServe
    在進入/初始化服務對象以前。
  2. AfterServe
    在完成服務執行流程以後。
  3. BeforePatch
    在Router對返回內容打包以前。
  4. AfterrPatch
    在Router對返回內容打包以後。
  5. BeforeOutput
    向客戶端輸出返回內容以前。
  6. AfterOutput
    向客戶端輸出返回內容以後。
  7. BeforeClose
    在http請求關閉以前(注意請求關閉是異步處理操做,沒有在http執行流程中處理)。
  8. AfterClose
    在http請求關閉以後(注意請求關閉是異步處理操做,沒有在http執行流程中處理)。

使用示例:

package main

import (
    "fmt"
    "gitee.com/johng/gf/g/net/ghttp"
)

func main() {
    p := "/"
    s := ghttp.GetServer()
    s.BindHookHandlerByMap(p, map[string]ghttp.HandlerFunc{
        "BeforeServe"  : func(r *ghttp.Request){ fmt.Println("BeforeServe") },
        "AfterServe"   : func(r *ghttp.Request){ fmt.Println("AfterServe") },
        "BeforePatch"  : func(r *ghttp.Request){ fmt.Println("BeforePatch") },
        "AfterPatch"   : func(r *ghttp.Request){ fmt.Println("AfterPatch") },
        "BeforeOutput" : func(r *ghttp.Request){ fmt.Println("BeforeOutput") },
        "AfterOutput"  : func(r *ghttp.Request){ fmt.Println("AfterOutput") },
        "BeforeClose"  : func(r *ghttp.Request){ fmt.Println("BeforeClose") },
        "AfterClose"   : func(r *ghttp.Request){ fmt.Println("AfterClose") },
    })
    s.BindHandler(p, func(r *ghttp.Request) {
        r.Response.Write("哈嘍世界!")
    })
    s.SetPort(8199)
    s.Run()
}

當訪問http://127.0.0.1:8199/時,運行Web Server進程的終端將會按照事件的執行流程打印出對應的事件名稱。

相關文章
相關標籤/搜索