Dawn's Blogs

分享技术 记录成长

0%

从零实现一个Web框架 (1) 原型框架

本系列参考于 极客兔兔-7天用Go从零实现Web框架Gee教程,将从零开始实现一个简易的仿 GIN 的框架,称为 Dawn’s Gin 简称 dain

在本节,最终的代码目录结构如下:

1
2
3
4
5
dain/
|--dain.go
|--go.mod
main.go
go.mod

实现目标

我们最终的实现效果如下,可以看到自实现的框架与 GIN 十分相似:

  • 通过 e.GETe.POST 注册路由
  • 最后调用 e.RUN 运行 Web 服务器。

main.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import (
"DawnGin/dain"
"fmt"
"net/http"
)

func main() {
e := dain.New()

e.Get("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello World, URL path = %v", r.URL.Path)
})

e.Post("/", func(w http.ResponseWriter, r *http.Request) {
for k, v := range r.Header {
fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
}
})

e.Run(":9000")
}

引擎

dain/dain.go

在 GIN 中,添加路由等的操作都是通过引擎 Engine 完成的,所以自定义 Engine。

其中 Engine.router 是一个路由器,类型为 map。key 记录路由注册时的 pattern,value 则为相应的处理逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// HandlerFunc handler 函数类型
type HandlerFunc func(w http.ResponseWriter, r *http.Request)

type Engine struct {
// 路由器
router map[string]HandlerFunc
}

// New 返回一个 Engine 指针
func New() *Engine {
return &Engine{
router: make(map[string]HandlerFunc),
}
}

静态路由注册

我们需要实现通过 POST 和 GET 注册静态路由,首先编写同一的路由注册入口,将路由注册到 Engine.router 中:

1
2
3
4
5
// AddRouter 实现路由注册功能
func (e *Engine) AddRouter(method, pattern string, handler HandlerFunc) {
key := method + "-" + pattern
e.router[key] = handler
}

添加对外暴露的与 HTTP Method 相关的路由注册方法,共实现了 GET 和 POST 方法:

1
2
3
4
5
6
7
8
9
// Get 路由注册 GET 请求方式
func (e *Engine) Get(pattern string, handler HandlerFunc) {
e.AddRouter("GET", pattern, handler)
}

// Post 路由注册 POST 请求方式
func (e *Engine) Post(pattern string, handler HandlerFunc) {
e.AddRouter("POST", pattern, handler)
}

实现 http.Handler 接口

需要使得自定义的路由器工作,首先需要实现 http.Handler 接口,接口定义如下:

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

所以只需要实现 ServeHTTP 方法即可:

1
2
3
4
5
6
7
8
9
// 实现 http.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 FOR PATH: %v", r.URL.Path)
}
}

最后,包装一层执行函数即可:

1
2
3
4
// Run 运行服务器
func (e *Engine) Run(addr string) error {
return http.ListenAndServe(addr, e)
}