大部份情况下,开发者并不想每写一个Controller,还要去改写一下路由表,因为这样的话,Controller一多,维护路由表并不是一件讨好的事件。
较好的解决方案是直接由请求的路径(或请求参数)来动态分派到相应的控制器方法,习惯地,这种方式称为动态路由或规则路由,一个明显的特征是自动进行请求路径与控制器方法的匹配。
比如:
- 请求 /User 表示调用名字为User的Controller方法
- 请求 /Task 表示调用名字为Task的Controller方法
- 如果对应的方法不存在,则响应404
(如果需要区分请求方法来作不同的处理,在Controller方法中实现)
在php的实现
常见方式是根椐请求的路径或参数,直接include相应的controller类,然后在new一下就可以:
<?php
$controller = $_GET['c];
$method = $_GET['m'];
$file = "{$controller}_control.php";
file_exists($file) && include $file;
//如果class或method不存在,输出404
$c = new $controller;
$c -> $method();
?>
在GO中实现
由于go是静态语言,不能动态载入,只能通过反射来解决:
package main
import (
"fmt"
"log"
"net/http"
"reflect"
"strings"
)
type App struct {
w http.ResponseWriter
}
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/public/") {
//匹配静态文件服务
} else {
app := &App{w}
rValue := reflect.ValueOf(app)
rType := reflect.TypeOf(app)
path := strings.Split(r.URL.Path, "/")
controlName = path[1]
method, exist := rType.MethodByName(controlName)
if exist {
args := []reflect.Value{rValue}
method.Func.Call(args)
} else {
fmt.Fprintf(w, "method %s not found", r.URL.Path)
}
}
})
log.Fatal(http.ListenAndServe("localhost:8080", nil))
}
//控制器
func (this *App) Say() {
fmt.Fprintf(this.w, "Say called")
}
实际使用时,由于应用与框架不属于同一个包,控制器的reciver肯定不能是框架内的App结构实例(go不能使用别的包的结构来作为reciver):
package controller
import "framework"
type Controller struct{
*framework.App
//controller其它属性
}
func (this *Controller) Say({
//可以直接调用framework.App提供的方法和属性
}
这个时候,http.HandleFunc内的实现方式是:
app := controller.Controller{}
rValue := reflect.ValueOf(app)
rType := reflect.TypeOf(app)
reciver := rValue.Elem().FieldByName("App")
reciver.Set(reflect.ValueOf(&App{w})) //设置controller.Controller的匿名字段App
method, exist := rType.MethodByName(controlName)
if exist {
args := []reflect.Value{rValue}
method.Func.Call(args)
}else{
//404
}
路径分配策略
上述示例代码是直接使用Path的第一段来dispatch,如有需要,可有更多的方式,比如:
- 根椐固定的请求参数 c=xxx
- 根椐某个指定Header
- 根椐Cookie
关于如何支持RESTful,将在下篇讲述。
本篇示例代码只是演示动态路由的实现,没有考虑安全和其它因素