跳至主要內容

處理器中繼資料(HandlerMeta) ​

core.HandlerMeta 包含執行對應請求的控制器方法所需的資訊,包括控制器型別、方法、路徑參數順序,以及路由攔截器。應用程式使用 app.Route 註冊處理器,並可在攔截器中讀取 HandlerMeta,確認即將執行的對象。

本頁依功能劃分的 src/user 範例使用專案結構中以 --layout legacy 建立的 my-app。CLI v0.4.0 預設產生依角色劃分的 layered-v1 結構。兩種布局都使用 Spine v0.6.0 與 Echo 平台 v0.1.0。

本頁以 Spine v0.6.0 的公開型別和 HTTP 執行流程為基礎。應用程式不需要自行編寫建立中繼資料的函式或路由器內部實作。

在 CLI 專案中的位置 ​

以CLI 專案結構中建立的 example.com/my-app 為例,使用者路由宣告於 src/user/user_route.go 的 Register(app)。產生的處理器為 (*UserController).Get,路徑為 GET /user/:id。internal/application/application.go 中的 BuildApplication 會檢查並回傳 user.Register(app) 的錯誤,再由產生的 main.go 進行驗證與執行。

go
// src/user/user_route.go의 Register 내부 발췌
app.Route(
    "GET",
    "/user/:id",
    (*UserController).Get,  // 메서드 표현식
)

以下的 GetUser、管理員處理器和驗證攔截器,是用來說明概念的應用程式擴充範例。它們與 CLI 產生的 Get 是不同的方法;若要直接加入,還必須實作對應的型別和方法。攔截器的完整實作與註冊方式,請參閱攔截器教學。

HandlerMeta 的欄位 ​

core.HandlerMeta 的欄位如下,透過 reflect 型別表示控制器和方法的資訊。

go
// core/handler_meta.go
type HandlerMeta struct {
    // 컨트롤러 타입 (Container Resolve 대상)
    ControllerType reflect.Type

    // 호출할 메서드
    Method reflect.Method

    // 라우트에 선언된 경로 매개변수 키의 순서
    PathKeys []string

    // 핸들러에 적용된 인터셉터
    Interceptors []Interceptor
}
欄位型別用途
ControllerTypereflect.Type要從相依性容器取得的控制器指標型別
Methodreflect.Method要呼叫的方法名稱、簽章和函式資訊
PathKeys[]string路由中宣告的路徑參數名稱順序
Interceptors[]core.Interceptor套用至該路由的攔截器清單

若 ControllerType 為 *UserController,且 Method.Name 為 GetUser,管線會先從容器取得 *UserController 執行個體,再呼叫 GetUser。

go
meta.ControllerType  // reflect.Type of *UserController
go
meta.Method.Name           // "GetUser"
meta.Method.Type           // func(*UserController, path.Int) (httpx.Response[User], error)
meta.Method.Func           // 호출 가능한 reflect.Value

PathKeys 儲存的是路徑參數名稱,並非實際請求值。例如 /users/:userId/orders/:orderId 會依序儲存 userId、orderId,再依此順序對應處理器中 path 型別的引數。每次請求可能不同的路徑參數值,則從請求上下文讀取。

Interceptors 僅包含路由攔截器。透過 app.Interceptor 註冊的全域攔截器,由管線另外管理。

go
meta.Interceptors  // []core.Interceptor (라우트 레벨)

使用方法運算式註冊處理器 ​

向路由傳入 (*UserController).GetUser 之類的方法運算式。這個運算式是未綁定至特定控制器執行個體的函式,會以控制器指標作為第一個引數。

go
// 메서드 표현식의 실제 타입
func(*UserController, path.Int) (httpx.Response[User], error)
//   ↑ receiver가 첫 번째 인자로 변환됨

Spine 會據此確認控制器型別和要呼叫的方法。控制器執行個體則依照應用程式註冊的建構函式,從相依性容器取得。

請區分以下註冊形式。

註冊形式是否使用說明
(*UserController).GetUser使用第一個引數為 *UserController 的方法運算式
UserController.GetUser不使用第一個引數為值型別,不符合指標型別的條件
ctrl.GetUser不使用這是綁定至特定執行個體的方法值,不會另外接收控制器引數
一般函式或閉包不使用不符合用來確認控制器型別和方法的註冊格式

這項條件針對的是傳入路由的運算式形式。請依照教學,以指標接收者編寫應用程式的控制器,並以 (*Controller).Method 形式註冊。

指定路由攔截器 ​

route.WithInterceptors 接收要套用至該路由的攔截器。使用時需匯入 github.com/NARUBROWN/spine/pkg/route。

go
app.Route(
    "GET",
    "/users/:id",
    (*UserController).GetUser,
    route.WithInterceptors(&AuthInterceptor{}),
)

傳入攔截器有兩種方式。

傳入值建立方式
&AuthInterceptor{}直接使用應用程式建立的執行個體
(*AuthInterceptor)(nil)在啟動初始化期間,由容器建立該指標型別的執行個體

需要注入相依性的攔截器,可以使用帶有型別的 nil 指標。此時必須註冊攔截器的建構函式及所需的相依性。若傳入不含型別資訊的 nil,路由攔截器的註冊會失敗。

go
// src/user/user_route.go의 Register 내부 발췌
app.Route("GET", "/users/:id", (*UserController).GetUser)

// 라우트 인터셉터와 함께
app.Route("GET", "/admin/users/:id", (*AdminController).GetUser,
    route.WithInterceptors((*AuthInterceptor)(nil)),  // nil 포인터 → Container에서 Resolve
)

也可以同時使用全域攔截器和路由攔截器。全域攔截器套用至所有請求,路由攔截器則只套用至對應的路由。

go
// 글로벌: 모든 요청에 적용 (라우팅 전 실행)
app.Interceptor(&CORSInterceptor{})

// 라우트: 특정 핸들러에만 적용 (라우팅 후, Controller 호출 전 실행)
app.Route("GET", "/admin/:id", (*AdminController).Get,
    route.WithInterceptors(&AuthInterceptor{}),
)

在攔截器中確認執行對象 ​

攔截器的 PreHandle、PostHandle、BeforeResponse 和 AfterCompletion 都會接收 HandlerMeta。記錄日誌或依處理器套用政策時,可以讀取這些資訊。

全域 PreHandle 在路由比對前執行,因此會接收空的 HandlerMeta。此時 ControllerType 為 nil,Method.Name 也是空值。決定路由後才會傳入實際的處理器中繼資料;但若請求在路由比對時就失敗,可能沒有執行對象。請先檢查,再存取欄位。

以下程式碼僅節錄 PreHandle。使用時需匯入 core 和 log;若要註冊該攔截器,還必須實作其餘三個方法。

go
// src/interceptor/logging_interceptor.go
func (i *LoggingInterceptor) PreHandle(ctx core.ExecutionContext, meta core.HandlerMeta) error {
    controllerName := "<unresolved>"
    if meta.ControllerType != nil {
        controllerName = meta.ControllerType.String()
    }
    log.Printf(
        "[REQ] %s %s -> %s.%s",
        ctx.Method(),
        ctx.Path(),
        controllerName,
        meta.Method.Name,            // "GetUser"
    )
    return nil
}

攔截器透過 core.ExecutionContext 存取請求資訊,如上述範例所示。HTTP 控制器不必以同一型別作為引數。請在控制器中宣告符合依型別注入引數規則的請求型別。

註冊階段與請求執行階段 ​

在啟動初始化期間,Spine 會將路由宣告準備為執行資訊。

  1. 從 app.Route 宣告的處理器確認控制器型別和方法。
  2. 準備路由攔截器,並將其連結至 HandlerMeta。
  3. 向路由器註冊完整路徑及路徑參數順序。
  4. 預先建立已註冊的控制器和相依性。

若處理器格式不正確,或無法建立控制器、攔截器的相依性,伺服器就會在啟動過程中失敗。請保留產生專案的錯誤回傳路徑,將這項失敗傳遞給呼叫端。

收到請求時,會依下列順序使用已準備的中繼資料。

  1. 執行全域攔截器的 PreHandle。
  2. 找出符合請求的路由,取得 HandlerMeta。
  3. 根據方法的參數型別和 PathKeys 準備引數。
  4. 執行路由攔截器的 PreHandle。
  5. 取得控制器執行個體,呼叫方法。
  6. 處理回傳值並完成回應前處理,再送出回應。

正常執行流程如下。若發生錯誤,或攔截器要求中止,可能會略過後續階段。

請求成功時,PostHandle 會先執行路由攔截器,再執行全域攔截器;各清單內皆依註冊的反向順序呼叫。BeforeResponse 和 AfterCompletion 的錯誤處理與執行條件,請參閱攔截器教學。

發生錯誤時應檢查的位置 ​

情況檢查內容
處理器註冊失敗確認是否為 (*Controller).Method 形式,以及是否能找到該方法
控制器建立失敗確認是否已註冊控制器建構函式及該建構函式所接收的相依性
路由攔截器建立失敗確認是否使用帶有型別的 nil 指標,以及是否已註冊所需的建構函式
請求引數準備失敗確認處理器引數型別是否受支援,以及路徑參數宣告順序是否正確
沒有符合的路由確認 HTTP 方法、路徑和全域路徑前綴。預設 HTTP 處理會回傳 404 錯誤回應

HandlerMeta 描述處理器的執行對象。請求資料的轉換由引數解析器負責,實際將回傳值轉換為 HTTP 回應的工作則由回傳值處理器負責。嘗試直接替換各項職責的實作前,請先檢查路由宣告和控制器簽章。