七天系列-gee

4 阅读3分钟

自从拿到offer之后主要都是玩和准备毕业论文的事,是时候该继续学习啦!!!

gee

简单来说,gee就是在net库的基础上实现一个更加强大的web框架。

net/http库的基本使用

package main

import (
"fmt"
"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, World!")
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

以上是使用net/http库实现一个简单的服务。如果你有底层网络库的经验的话,这里应该会很熟悉。

http.ListenAndServer主要功能如下:

  1. 监听并接受新的连接
  2. 起一个新协程去处理连接,然后循环执行,继续监听

大致过程如下:

for {
    rw, err := l.Accept()
    ...
    
    go srv.Handle(rw)
}

接收连接的过程大致如下:

  1. 使用HTTP解析器解析数据流
  2. 读取解析相应的请求参数
  3. 根据请求方法和URL,路由到相应的函数

第一天:

http.ListenAndServer的第二个参数则是用来指定路由函数的,如果我们传入nil,则启动一个默认的路由。查看源码可以发现:它的第二个参数一个接口

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

只需要实现这个接口的类型,就可以作为自定义的路由传入。

package main

import (
    "fmt"
    "net/http"
)

type Engine struct{}

func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    switch r.URL.Path {
    case "/":
       fmt.Fprintf(w, "Hello World")
    case "/favicon.ico":
       fmt.Fprintf(w, "favicon")
    default:
       fmt.Fprintf(w, "404 not found")
    }
}

func main() {
    http.ListenAndServe(":8080", &Engine{})
}

可以看到,只要实现相应的接口;我们自定义的实例就可以接受并处理所有的请求。接下来,我们仿照gin实现一个类似的功能:

package gee

import (
    "fmt"
    "net/http"
)

type HandlerFunc func(w http.ResponseWriter, r *http.Request)

type Engine struct {
    router map[string]HandlerFunc
}

func NewEngine() *Engine {
    return &Engine{router: make(map[string]HandlerFunc)}
}

func (e *Engine) AddRoute(method, pattern string, handler HandlerFunc) {
    key := method + "-" + pattern
    e.router[key] = handler
}

func (e *Engine) GET(pattern string, handler HandlerFunc) {
    e.AddRoute("GET", pattern, handler)
}

func (e *Engine) POST(pattern string, handler HandlerFunc) {
    e.AddRoute("POST", pattern, handler)
}

func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    key := r.Method + "-" + r.URL.Path
    if handler, ok := e.router[key]; ok {
       handler(w, r)
    } else {
       fmt.Fprintf(w, "404 Not Found: %s\n", r.URL)
    }
}

func (e *Engine) Run(addr string) error {
    return http.ListenAndServe(addr, e)
}

实现思路:

  • 关键是完成请求-函数的映射
  • 请求包括请求类型和URL,把两个字段组合在一起
  • 使用一个map记录映射关系

缺点:

  • map效率低
  • 只能完成最基础的路由映射功能

第二条:设计上下文,封装更多功能

为了实现类似于gin框架的使用体验,我们需要对上述实现进一步的封装。底层的网络库已经帮我们完成了监听、建立连接、HTTP协议解析。在此基础上,实现该接口:

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

就可以用自定义实例去处理所有的请求。处理请求无非就是:

  • http.request中取出数据
  • 逻辑处理
  • 将响应写回http.ResponseWriter

为了实现下列的调用:

func main() {
    r := gee.NewEngine()
    r.GET("/", func(c *gee.Context) {
       c.HTML("<h1>Hello Gee</h1>")
    })
    r.Run(":9090")
}

我们需要对第一天编写的内容进一步封装。由于context和请求具有高度相关性,它伴随一条请求的整个生命周期,可以把一些繁琐的操作封装到context中。

  • 上述例子中用户传入的不再是Handler类型接口,但由上面的分析可知:这是处理请求必须的
  • 那么很自然地就想到把该变量封装到context
// 只展示了修改部分

type HandlerFunc func(c *Context)

func (c *Context) HTML(templateStr string) {
    tmpl, _ := template.New("test").Parse(templateStr)
    tmpl.Execute(c.w, nil)
}

// 底层网络库传入的必须两个参数w,r被封装到context中
// 同时封装了一些常用方法
func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    key := r.Method + "-" + r.URL.Path
    if handler, ok := e.router[key]; ok {
       ctx := &Context{w, r}
       handler(ctx)
    } else {
       fmt.Fprintf(w, "404 Not Found: %s\n", r.URL)
    }
}