gweb總結之router

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

代碼由此去

代碼結構

.- router包├── middleware│   ├── param.go     // 參數解析支援│   ├── readme.md    // 文檔│   ├── reqlog.go    // 記錄請求日誌│   ├── response.go  // 響應的相關函數│   └── safe.go      // safe recover功能└── router.go        // 入口和request處理邏輯
整個router與gweb其他模組並不耦合,只會依賴於logger。其中 router.go是整個路由的入口的,而middleware提供一些工具函數和簡單的封裝。

router處理邏輯

router.go 主要做了以下工作:

  • 定義路由,及Controller註冊
  • 自訂http.Handler, 也就是ApiHandler,實現ServeHTTP方法。

自訂路由Route

type Route struct {    Path    string         // req URI    Method  string         // GET,POST...    Fn      interface{}    // URI_METHOD hanlde Func    ReqPool *sync.Pool     // req form pool    ResPool *sync.Pool     // response pool}

在使用的時候使用一個map[string][]*Route結構來儲存URI和Method對應的路由處理函數。腦補一下,實際的儲存是這樣的:

{    "/hello": [        &Route{            Path: "/hello",            Method: "GET",            Fn: someGetFunc,            ReqPool: someGetReqPool,            ResPool: someGetRespPool        },        &Route{            Path: "/hello",            Method: "POST",            Fn: somePostFunc,            ReqPool: somePostReqPool,            ResPool: somePostRespPool        },        // ... more    ],    // ... more}
用這樣的結構主要是為了支援Restful API,其他的暫時沒有考慮

ApiHanlder

router.go 定義了一個ApiHandler如下:

type ApiHandler struct {    NotFound         http.Handler    MethodNotAllowed http.Handler}

只是簡單的包含了兩個hander,用於支援404405請求。

!!!! 重點來了,我們為什麼要定一個那樣的路由?又怎麼具體的解析參數,響應,處理請求呢?Talk is Cheap, show me the Code

func (a *ApiHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {    defer middleware.SafeHandler(w, req)    path := req.URL.Path    route, ok := foundRoute(path, req.Method)    //// handle 404    if !ok {        if a.NotFound != nil {            a.NotFound.ServeHTTP(w, req)        } else {            http.NotFound(w, req)        }        return    }    // not nil and to, ref to foundRoute    if route != nil {        goto Found    }    //// handle 405    if !allowed(path, req.Method) {        if a.MethodNotAllowed != nil {            a.MethodNotAllowed.ServeHTTP(w, req)        } else {            http.Error(w,                http.StatusText(http.StatusMethodNotAllowed),                http.StatusMethodNotAllowed,            )        }        return    }Found:    //// normal handle    reqRes := route.ReqPool.Get()    defer route.ReqPool.Put(reqRes)    // parse params    if errs := middleware.ParseParams(w, req, reqRes); len(errs) != 0 {        je := new(middleware.JsonErr)        Response(je, NewCodeInfo(CodeParamInvalid, ""))        je.Errs = errs        middleware.ResponseErrorJson(w, je)        return    }    in := make([]reflect.Value, 1)    in[0] = reflect.ValueOf(reqRes)    Fn := reflect.ValueOf(route.Fn)    //// Call web server handle function    out := Fn.Call(in)    //// response to client    resp := out[0].Interface()    defer route.ResPool.Put(resp)    middleware.ResponseJson(w, resp)    return}

流程正如你所想的那樣。處理405,405等,然後使用路由Route,進行參數解析,校正,調用,返迴響應等操作。設計參照了httprouter。關於參數解析和響應,馬上就到。

參數解析和校正(param.go)

參數的解析,一開始考慮的只有GET,POST,PUT,DELETE 沒有考慮JSON和檔案的解析。因為一開始忙於搭架構是一方面,其次因為我用的schema不支援(我也沒仔細看,自己實現起來也很簡單)。

這裡就推薦兩個我常用的golang第三方庫,這也是我用於參數解析和校正的工具:

  1. schema, converts structs to and from form values.
  2. beego/validation,valid the struct
// ParseParams, parse params into reqRes from req.Form, and support// form-data, json-body// TODO: support parse filefunc ParseParams(w http.ResponseWriter, req *http.Request, reqRes interface{}) (errs ParamErrors) {    switch req.Method {    case http.MethodGet:        req.ParseForm()    case http.MethodPost, http.MethodPut:        req.ParseMultipartForm(20 << 32)    default:        req.ParseForm()    }    // log request    logReq(req)    // if should parse Json body    // parse json into reqRes    if shouldParseJson(reqRes) {        data, err := getJsonData(req)        if err != nil {            errs = append(errs, NewParamError("parse.json", err.Error(), ""))            return        }        if err = json.Unmarshal(data, reqRes); err != nil {            errs = append(errs, NewParamError("json.unmarshal", err.Error(), ""))            return        }        bs, _ := json.Marshal(reqRes)        ReqL.Info("pasing json body: " + string(bs))        goto Valid    }    // if has FILES field,    // so parese req to get attachment files    if shouldParseFile(reqRes) {        AppL.Info("should parse files")        if req.MultipartForm == nil || req.MultipartForm.File == nil {            errs = append(errs, NewParamError("FILES", "empty file param", ""))            return        }        rv := reflect.ValueOf(reqRes).Elem().FieldByName("FILES")        // typ := reflect.ValueOf(reqRes).Elem().FieldByName("FILES").Type()        filesMap := reflect.MakeMap(rv.Type())        // parse file loop        for key, _ := range req.MultipartForm.File {            file, file_header, err := req.FormFile(key)            if err != nil {                errs = append(errs, NewParamError(Fstring("parse request.FormFile: %s", key),                    err.Error(), ""))            }            defer file.Close()            filesMap.SetMapIndex(                reflect.ValueOf(key),                reflect.ValueOf(ParamFile{                    File:       file,                    FileHeader: *file_header,                }),            )        } // loop end        // set value to reqRes.Field `FILES`        rv.Set(filesMap)        if len(errs) != 0 {            return        }    }    // decode    if err := decoder.Decode(reqRes, req.Form); err != nil {        errs = append(errs, NewParamError("decoder", err.Error(), ""))        return    }Valid:    // valid    v := poolValid.Get().(*valid.Validation)    if ok, err := v.Valid(reqRes); err != nil {        errs = append(errs, NewParamError("validation", err.Error(), ""))    } else if !ok {        for _, err := range v.Errors {            errs = append(errs, NewParamErrorFromValidError(err))        }    }    return}

或許有人會關心shouldParseJson是怎麼弄的?如下:

// shouldParseJson check `i` has field `JSON`func shouldParseJson(i interface{}) bool {    v := reflect.ValueOf(i).Elem()    if _, ok := v.Type().FieldByName("JSON"); !ok {        return false    }    return true}

這裡強制設定了reqRes必須含有JSON欄位,才會解析jsonbody;必須含有FILES才會解析請求中的檔案。因此在寫商務邏輯的時候,要寫成這個樣子了,這些樣本都在demo:

/* * JSON-Body Demo */type HelloJsonBodyForm struct {    JSON bool   `schema:"-" json:"-"` // 注意schema標籤要設定“-”    Name string `schema:"name" valid:"Required" json:"name"`    Age  int    `schema:"age" valid:"Required;Min(0)" json:"age"`}var PoolHelloJsonBodyForm = &sync.Pool{New: func() interface{} { return &HelloJsonBodyForm{} }}type HelloJsonBodyResp struct {    CodeInfo    Tip string `json:"tip"`}var PoolHelloJsonBodyResp = &sync.Pool{New: func() interface{} { return &HelloJsonBodyResp{} }}func HelloJsonBody(req *HelloJsonBodyForm) *HelloJsonBodyResp {    resp := PoolHelloJsonBodyResp.Get().(*HelloJsonBodyResp)    defer PoolHelloJsonBodyResp.Put(resp)    resp.Tip = fmt.Sprintf("JSON-Body Hello, %s! your age[%d] is valid to access", req.Name, req.Age)    Response(resp, NewCodeInfo(CodeOk, ""))    return resp}/* * File Hanlder demo */type HelloFileForm struct {    FILES map[string]mw.ParamFile `schema:"-" json:"-"` // 注意schema標籤設定“-”和FILES的type保持一直    Name  string                  `schema:"name" valid:"Required"`    Age   int                     `schema:"age" valid:"Required"`}var PoolHelloFileForm = &sync.Pool{New: func() interface{} { return &HelloFileForm{} }}type HelloFileResp struct {    CodeInfo    Data struct {        Tip  string `json:"tip"`        Name string `json:"name"`        Age  int    `json:"age"`    } `json:"data"`}var PoolHelloFileResp = &sync.Pool{New: func() interface{} { return &HelloFileResp{} }}func HelloFile(req *HelloFileForm) *HelloFileResp {    resp := PoolHelloFileResp.Get().(*HelloFileResp)    defer PoolHelloFileResp.Put(resp)    resp.Data.Tip = "foo"    for key, paramFile := range req.FILES {        AppL.Infof("%s:%s\n", key, paramFile.FileHeader.Filename)        s, _ := bufio.NewReader(paramFile.File).ReadString(0)        resp.Data.Tip += s    }    resp.Data.Name = req.Name    resp.Data.Age = req.Age    Response(resp, NewCodeInfo(CodeOk, ""))    return resp}

響應(response.go)

gweb目的在於總結一個使用Json資料格式來進行互動的web服務結構。響應體設計如下:

{    "code": 0,     // 錯誤碼,或許應該使用“error_code”, 不過不影響    "message": ""  // 錯誤訊息    "user": {        "name": "yep",        // ... other    }}

結合上面的Demo,大概看出來了,響應並沒什麼花裡胡哨的功能。只是需要將*resp使用json.Marshal轉為字串,並發送給用戶端就了事。

    // ...    //// Call web server handle function    out := Fn.Call(in)    //// response to client    resp := out[0].Interface()    defer route.ResPool.Put(resp)    middleware.ResponseJson(w, resp)

路由到這裡也就結束了,雖然最重要,但依然比較簡單。

最後可能需要一個圖來說明?

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.