HandlerMeta
Metadata about Route Handlers.
Overview
HandlerMeta is a structure that holds metadata about the Controller method to be executed. When the Router matches a request path, it returns HandlerMeta, and the Pipeline uses this information to call the actual method.
HandlerMeta Struct
// core/handler_meta.go
type HandlerMeta struct {
// Controller Type (Target for Container Resolve)
ControllerType reflect.Type
// Method to call
Method reflect.Method
}Field Description
ControllerType
Pointer type of the Controller. Used when resolving an instance from the IoC Container.
meta.ControllerType // reflect.Type of *UserControllerMethod
Reflection information of the method to call. Includes method name, signature, and function pointer.
meta.Method.Name // "GetUser"
meta.Method.Type // func(*UserController, path.Int) (User, error)
meta.Method.Func // Callable reflect.ValueCreating HandlerMeta
Method Expression
Spine uses Method Expressions to register handlers.
// cmd/demo/main.go
app.Route(
"GET",
"/users/:id",
(*UserController).GetUser, // Method Expression
)The method expression (*UserController).GetUser is treated as a regular function:
// Actual type of method expression
func(*UserController, path.Int) (User, error)
// ↑ receiver is converted to the first argumentNewHandlerMeta Function
Analyzes the method expression to generate HandlerMeta.
// internal/router/handler_meta.go
func NewHandlerMeta(handler any) (core.HandlerMeta, error) {
t := reflect.TypeOf(handler)
v := reflect.ValueOf(handler)
// 1. Verify if it is a function
if t.Kind() != reflect.Func {
return core.HandlerMeta{}, fmt.Errorf("handler must be a function")
}
// 2. Verify if it is a method expression (first arg is receiver)
if t.NumIn() < 1 {
return core.HandlerMeta{}, fmt.Errorf("handler must be a method expression")
}
// 3. Verify receiver is a pointer type
receiverType := t.In(0)
if receiverType.Kind() != reflect.Ptr {
return core.HandlerMeta{}, fmt.Errorf("handler receiver must be a pointer type")
}
// 4. Extract method name
fn := runtime.FuncForPC(v.Pointer())
if fn == nil {
return core.HandlerMeta{}, fmt.Errorf("cannot extract method info")
}
fullName := fn.Name()
// e.g.: github.com/NARUBROWN/spine-demo.(*UserController).GetUser
lastDot := strings.LastIndex(fullName, ".")
methodName := fullName[lastDot+1:]
// 5. Get method info via reflection
method, ok := receiverType.MethodByName(methodName)
if !ok {
return core.HandlerMeta{}, fmt.Errorf("method not found: %s", methodName)
}
return core.HandlerMeta{
ControllerType: receiverType,
Method: method,
}, nil
}Creation Process Details
Step 1: Verify Function
t := reflect.TypeOf((*UserController).GetUser)
t.Kind() // reflect.Func ✓Step 2: Verify Method Expression
t.NumIn() // 2 (receiver + path.Int)
t.In(0) // *UserController (receiver)
t.In(1) // path.IntStep 3: Extract Method Name
Get the full path of the function using runtime.FuncForPC, and the string after the last . is the method name.
fn.Name() // "github.com/NARUBROWN/spine-demo.(*UserController).GetUser"
// ↑ methodNameStep 4: Acquire Method
method, _ := reflect.TypeOf(&UserController{}).MethodByName("GetUser")
// method.Name: "GetUser"
// method.Type: func(*UserController, path.Int) (User, error)
// method.Func: Callable reflect.ValueUsage in Router
Route Registration
// internal/router/router.go
type Route struct {
Method string // HTTP Method
Path string // URL Pattern
Meta core.HandlerMeta // Handler Metadata
}
func (r *DefaultRouter) Register(method string, path string, meta core.HandlerMeta) {
r.routes = append(r.routes, Route{
Method: method,
Path: path,
Meta: meta,
})
}Route Matching
func (r *DefaultRouter) Route(ctx core.ExecutionContext) (core.HandlerMeta, error) {
for _, route := range r.routes {
if route.Method != ctx.Method() {
continue
}
ok, params, keys := matchPath(route.Path, ctx.Path())
if !ok {
continue
}
ctx.Set("spine.params", params)
ctx.Set("spine.pathKeys", keys)
return route.Meta, nil // Return HandlerMeta
}
return core.HandlerMeta{}, fmt.Errorf("Handler not found.")
}Usage in Pipeline
ParameterMeta Creation
Analyzes HandlerMeta.Method to generate meta-information for each parameter.
// internal/pipeline/pipeline.go
func buildParameterMeta(method reflect.Method, ctx core.ExecutionContext) []resolver.ParameterMeta {
pathKeys := ctx.PathKeys()
pathIdx := 0
var metas []resolver.ParameterMeta
// method.Type.NumIn() includes receiver
// i=0 is receiver, so start from i=1
for i := 1; i < method.Type.NumIn(); i++ {
pt := method.Type.In(i)
pm := resolver.ParameterMeta{
Index: i - 1,
Type: pt,
}
if isPathType(pt) {
if pathIdx < len(pathKeys) {
pm.PathKey = pathKeys[pathIdx]
}
pathIdx++
}
metas = append(metas, pm)
}
return metas
}Controller Invocation
// internal/invoker/invoker.go
func (i *Invoker) Invoke(controllerType reflect.Type, method reflect.Method, args []any) ([]any, error) {
// 1. Resolve Controller instance from Container
controller, err := i.container.Resolve(controllerType)
if err != nil {
return nil, err
}
// 2. Construct invocation arguments (receiver + args)
values := make([]reflect.Value, len(args)+1)
values[0] = reflect.ValueOf(controller) // receiver
for idx, arg := range args {
values[idx+1] = reflect.ValueOf(arg)
}
// 3. Call method via reflection
results := method.Func.Call(values)
// 4. Convert results
out := make([]any, len(results))
for i, result := range results {
out[i] = result.Interface()
}
return out, nil
}Passing to Interceptor
All Interceptor methods receive HandlerMeta and can access execution target information.
// cmd/demo/loggin_interceptor.go
func (i *LoggingInterceptor) PreHandle(ctx core.ExecutionContext, meta core.HandlerMeta) error {
log.Printf(
"[REQ] %s %s -> %s.%s",
ctx.Method(),
ctx.Path(),
meta.ControllerType.Name(), // "UserController"
meta.Method.Name, // "GetUser"
)
return nil
}Bootstrap Process
1. Route Declaration
// cmd/demo/main.go
app.Route("GET", "/users/:id", (*UserController).GetUser)2. RouteSpec Collection
// app.go
func (a *app) Route(method string, path string, handler any) {
a.routes = append(a.routes, router.RouteSpec{
Method: method,
Path: path,
Handler: handler, // Method Expression
})
}3. HandlerMeta Creation and Registration
// internal/bootstrap/bootstrap.go
router := spineRouter.NewRouter()
for _, route := range config.Routes {
// Method Expression → HandlerMeta Conversion
meta, err := spineRouter.NewHandlerMeta(route.Handler)
if err != nil {
return err
}
router.Register(route.Method, route.Path, meta)
}4. Controller Type Collection
Collects all Controller types registered in the Router.
// internal/router/router.go
func (r *DefaultRouter) ControllerTypes() []reflect.Type {
seen := map[reflect.Type]struct{}{}
var result []reflect.Type
for _, route := range r.routes {
t := route.Meta.ControllerType
if _, ok := seen[t]; ok {
continue
}
seen[t] = struct{}{}
result = append(result, t)
}
return result
}5. Warm-Up
Pre-instantiates all Controllers at bootstrap time.
// internal/bootstrap/bootstrap.go
if err := container.WarmUp(router.ControllerTypes()); err != nil {
panic(err)
}Internal Flow Summary
Design Principles
1. Enforcing Method Expressions
Only allows method expressions, not regular functions or closures.
// ✓ Method Expression
app.Route("GET", "/users/:id", (*UserController).GetUser)
// ❌ Regular Function (Not supported)
app.Route("GET", "/users/:id", func(id path.Int) User { ... })
// ❌ Instance Method (Not supported)
ctrl := &UserController{}
app.Route("GET", "/users/:id", ctrl.GetUser)2. Enforcing Pointer Receivers
Value receivers are not supported.
// ✓ Pointer Receiver
func (c *UserController) GetUser(id path.Int) User
// ❌ Value Receiver (Not supported)
func (c UserController) GetUser(id path.Int) User3. Bootstrap Verification
NewHandlerMeta is called at bootstrap, so invalid handler registration fails before server start.
// Bootstrap fails on invalid handler registration
meta, err := spineRouter.NewHandlerMeta(invalidHandler)
if err != nil {
return err // Errors before server start
}Summary
| Component | Role |
|---|---|
HandlerMeta | Metadata containing Controller type and Method info |
NewHandlerMeta() | Converts Method Expression → HandlerMeta |
Router | Returns HandlerMeta on request match |
Invoker | Resolves Controller instance and calls method using HandlerMeta |
Interceptor | Accesses execution target info using HandlerMeta |
Core: HandlerMeta is metadata about "what to execute". Created at bootstrap and used at runtime, it serves as the key link between execution model and business logic.
