web 框架之与http.Func兼容

 一下假设我们正在写的web框架叫nice

为了兼容使一些函数能够兼容http.handleFunc,比如


    Nice.Get("/hello",func(){
            return "hello"        
        })

在访问127.0.0.1:3000的时候就能看到hello。

实现的方法就是,我们要为每个请求维护一个上下文,然后利用对被兼容函数的反射,自适应相应的handleFunc。
然后再实现http的接口Nice.ServerHTTP(w,r)即可。

好吧,我觉得这句话也没怎么说清楚。先看代码。

首先,怎样才能自适应呢?怎样才能达到变参的效果呢?如果换作别的场景,做起来就很困难了,因为函数的参数变化多端,类型,数量,都是不确定的,虽然反射没办法获得函数的参数名,只能获得参数类型。但是web的场景却比较特殊,因为在web里面,每个handle请求所要用到的参数是有限,并且类型一般是唯一的,比如resPonseWriter和*request,这两个参数。http服务器为每个请求单独开了一个gorutine,我们可以为每个请求再创建一个上下文,用来维护可能用到的参数。
那就创建一个对象用来维护上下文

    type context struct {
        S      *server
        //这是一个接口
        RW http.ResponseWriter
        R      *http.Request
        handle Handler

    }

这个Handler是一个interface{},之所以定义成interface{}是为了能够存下各种handle函数。
s 就是我们的nice对象,存在上下文里面表示一个全局对象。这章的内容与这个对象无关。
对于上下文中的handle,在实际调用中需要用到反射。

    func (ct *context) process() {
        if ct.handle ==nil{
            panic("handle is nil")
        }

        fmt.Println(ct.R.URL.Path)

        t := reflect.TypeOf(ct.handle)

        var in = make([]reflect.Value,0,t.NumIn())

        for i := 0; i < t.NumIn(); i++ {
            argType := t.In(i)
            if val, ok := ct.get(argType); ok {
                in = append(in, val)
            }
        }
        ct.S.logger.Println(len(in))
        rets := reflect.ValueOf(ct.handle).Call(in)
        if len(rets) > 0 {
            var content []byte
            sval := rets[0]
            if sval.Kind() == reflect.String {
                content = []byte(sval.String())
            } else if sval.Kind() == reflect.Slice && sval.Type().Elem().Kind() == reflect.Uint8 {
                content = sval.Interface().([]byte)
            }
            //ct.SetHeader("Content-Length", strconv.Itoa(len(content)), true)
            ct.RW.Header().Set("Content-Length", strconv.Itoa(len(content)))

            _, err := ct.RW.Write(content)
            if err != nil {
                ct.S.logger.Println("Error during write: ", err)
            }
        }
    }

这个process的工作就是通过反射把函数的参数类型提取出来,再根据这个类型从上下文里面取需要的参数。因为上下文中的参数的类型都是不同的,所以非常好取。最后把运行结果写入rw里面就可以了。

提取参数的代码

    func (ct *context) get(t reflect.Type) (reflect.Value, bool) {
        val := reflect.ValueOf(ct)
        //这个只要是针对*http.Request的
        //如果指针类型的
        if val.Kind()==reflect.Ptr{
            //就把指向的value取到
            val = val.Elem()
        }
        for i := 0; i < val.NumField(); i++ {
            v := val.Field(i)
            if v.Type() == t {
                return v, true
            }
        }
        panic("Type doesn't exists")
    }

这就是整个自适应http.handleFunc的主要代码了。

全部代码如下
    package nice

    import (
        "fmt"
        "net/http"
        "os"
        "reflect"
        "strconv"
        "regexp"
        "log"
    )

    type Handler interface{}

    func doNothing() {
        fmt.Println("hello")
    }

    type context struct {
        S      *server
        //这是一个接口
        RW http.ResponseWriter
        R      *http.Request
        handle Handler

    }
    type route struct {
        method string
        regex  *regexp.Regexp
        handle Handler
    }

    type Config struct {
        StaticDir    string
        Addr         string
        Port         int
        CookieSecret string
        RecoverPanic bool
        Profiler     bool
    }



    type server struct {
        Config
        //represents global Middlewares
        mids []Handler
        //represents route dispatch handler
        routes []route
        //global logger
        logger *log.Logger
    }

    func (ct *context) get(t reflect.Type) (reflect.Value, bool) {
        val := reflect.ValueOf(ct)
        //如果指针类型的
        if val.Kind()==reflect.Ptr{
            //就把指向的value取到
            val = val.Elem()
        }
        for i := 0; i < val.NumField(); i++ {
            v := val.Field(i)
            if v.Type() == t {
                return v, true
            }
        }
        panic("Type doesn't exists")
    }
    func (ct *context) process() {
        if ct.handle ==nil{
            panic("handle is nil")
        }

        fmt.Println(ct.R.URL.Path)

        t := reflect.TypeOf(ct.handle)

        var in = make([]reflect.Value,0,t.NumIn())

        for i := 0; i < t.NumIn(); i++ {
            argType := t.In(i)
            if val, ok := ct.get(argType); ok {
                in = append(in, val)
            }
        }
        ct.S.logger.Println(len(in))
        //in的参数要是结构体里面可以到处的域,说白了就是要大写
        rets := reflect.ValueOf(ct.handle).Call(in)
        if len(rets) > 0 {
            var content []byte
            sval := rets[0]
            if sval.Kind() == reflect.String {
                content = []byte(sval.String())
            } else if sval.Kind() == reflect.Slice && sval.Type().Elem().Kind() == reflect.Uint8 {
                content = sval.Interface().([]byte)
            }
            //ct.SetHeader("Content-Length", strconv.Itoa(len(content)), true)
            ct.RW.Header().Set("Content-Length", strconv.Itoa(len(content)))

            _, err := ct.RW.Write(content)
            if err != nil {
                ct.S.logger.Println("Error during write: ", err)
            }
        }
    }

    //match url

    func (nice *server) matchRoute(req *http.Request) Handler {
        
        for _, route := range nice.routes {
            //HEAD can be used as GET
            if route.method != req.Method && !(req.Method == "HEAD" && route.method == "GET") {
                continue
            }
            if !route.regex.MatchString(req.URL.Path) {
                continue
            }
            if route.handle != nil {
                return route.handle
            }
        }
        return nil
    }

    //method to create a new context with res and req
    func (nice *server) touchContext(res http.ResponseWriter, req *http.Request) *context {
        handle := nice.matchRoute(req)
        ctx := context{nice, res, req, handle}
        return &ctx
    }
    func (nice *server) ServeHTTP(res http.ResponseWriter, req *http.Request) {
        nice.touchContext(res, req).process()
    }
    func (nice *server) addRoute(pattern string, method string, handler Handler) {
        cr, err := regexp.Compile(pattern)
        if err != nil {
            panic(err)
        }
        nice.routes = append(nice.routes, route{method, cr, handler})
    }

    func (nice *server) GET(pattern string,handler Handler) {
        nice.addRoute(pattern, "GET", handler)
    }

    func (nice *server) Run() {
        port := os.Getenv("PORT")
        if port == "" {
            port = "3000"
        }

        host := os.Getenv("HOST")

        nice.logger.Println("listening on " + host + ":" + port)
        nice.logger.Fatalln(http.ListenAndServe(host+":"+port, nice))
    }

    func New() *server {
        return &server{logger: log.New(os.Stdout, "", log.Ldate|log.Ltime)}
    }

参考了 martini 和 web 两个开源go web框架的代码。
但是martini另外实现了一个injector对象把上下文存在了map里面,这样的好处是可以动态添加上下文为实现中间件埋下伏笔。这里为了实现简单就直接存成了固定的结构体。

 

2014-4-1补充:

看了一下revel的代码,其实没看懂,是问别人的。revel的兼容方法,把参数的Name和Value存起来供函数的反射调用。问题是我一直没看出来,revel是怎么做到的。原来是通过go 下面的那些包,生成了语法树,去构建的。直接用到了编译的语法了。然后再去生成相应的代码,那段代码在app/tmp/main.go里面。真是应该好好学编译呀。


打个小广告:求一份实习

posted @ 2014-04-02 08:42  ggaaooppeenngg  阅读(329)  评论(0编辑  收藏  举报