为什么要写这个项目

​ 使用 Go 语言实现一个简单的 Web 框架,通过这个项目可以让我更好的去理解Go语言是如何实现一个简单的Web框架的。

​ 当我们离开框架,使用基础库时,需要频繁手工处理的地方,就是框架的价值所在。但并不是每一个频繁处理的地方都适合在框架中完成。此时理解一个框架的特性是非常重要的,这也是我想要通过Go去实现一个框架的真正原因。

项目地址

HTTP基础

一个简单的Web服务

Go语言内置了 net/http库,封装了HTTP网络编程的基础的接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
"net/http"
)

func index(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello World")
}

func main() {
http.HandleFunc("/", index)
http.ListenAndServe(":9090", nil)
}

http.HandleFunc

http.HandleFunc("/", index)注册路径处理函数,这里将路径/的处理函数设置为index

处理函数的类型:

1
func (http.ResponseWriter, *http.Request)

*http.Request表示 HTTP 请求对象,该对象包含请求的所有信息,如 URL、首部、表单内容、请求的其他内容等。

http.ResponseWriter是一个接口类型:(去看一看源码)

1
2
3
4
5
6
// net/http/server.go
type ResponseWriter interface {
Header() Header
Write([]byte) (int, error)
WriteHeader(statusCode int)
}

用于向客户端发送响应,实现了ResponseWriter接口的类型显然也实现了io.Writer接口。所以在处理函数index中,可以调用fmt.Fprintln()ResponseWriter写入响应信息。

http.ListenAndServe

http.ListenAndServe 用以监听本地 9090 端口以提供服务。标准的 HTTP 端口是 80 端口,如 baidu.com:80,另一个 Web 常用是 HTTPS 的 443 端口,如 baidu.com:443

1
2
3
4
5
6
7
8
9
10
11
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}


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

查看源码得知,http.ListenAndServe第二个参数是Handler类型,Handler需要实现ServeHTTP方法。所以我们需要传入一个ServerHTTP 接口的实例,就可以将HTTP请求传给这个实例处理。

实现一个ServeHTTP

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

import (
"fmt"
"log"
"net/http"
)

// Engine is the uni handler for all requests
type Engine struct{}

func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
switch req.URL.Path {
case "/":
fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path)
case "/hello":
for k, v := range req.Header {
fmt.Fprintf(w, "Header[%q] = %q\n", k, v)
}
default:
fmt.Fprintf(w, "404 NOT FOUND: %s\n", req.URL)
}
}

func main() {
engine := new(Engine)
log.Fatal(http.ListenAndServe(":9090", engine))
}

ServeHTTP方法的两个参数

  • Request包含了该HTTP请求的所有信息(请求地址、Header、Body等)
  • ResponseWriter可以构造针对该请求的响应

LisrenAndServe方法的第二个参数传入了engine实例。engine这个实例拦截了所有的HTTP请求,有了统一的控制入口。我们就可以自定义路由映射规则,也可以统一添加一些处理逻辑,如日志、异常处理等。

Gee框架雏形

目录结构

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

go.mod

go.mod 中使用 replace 将 gee 指向 ./gee

1
2
3
4
5
6
7
module demo01

go 1.18

require gee v0.0.0

replace gee => ./gee

main.go

main.go的内容和当初使用gin框架的时候感觉是一摸一样,因为gee设计参考了gin。

  1. 使用New()创建gee实例(创建gin实例)
  2. 使用GET()方法添加路由。
  3. 使用Run()启动Web服务。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"fmt"
"net/http"

"gee"
)

func main() {
r := gee.New()
r.GET("/", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path)
})

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

r.Run(":9090")
}

gee.go

  • gee.go通过Engine实现了ServeHTTP方法,解析请求路径,查找路由映射表
    • 若查找到就执行注册的处理方法
    • 若没查找到返回404
  • 定义自定义类型HandlerFunc
  • router map[string]HandlerFunc作为路由映射表
  • key是请求方法如:GET-/GET-/helloPOST-/hello,value是映射处理的方法。
  • (*Engine).GET()方法时,会将路由和处理方法注册到映射表 router 中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package gee

import (
"fmt"
"net/http"
)

// HandlerFunc defines the request handler used by gee
type HandlerFunc func(http.ResponseWriter, *http.Request)

// Engine implement the interface of ServeHTTP
type Engine struct {
router map[string]HandlerFunc
}

// New is the constructor of gee.Engine
func New() *Engine {
return &Engine{router: make(map[string]HandlerFunc)}
}

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

// GET defines the method to add GET request
func (engine *Engine) GET(pattern string, handler HandlerFunc) {
engine.addRoute("GET", pattern, handler)
}

// POST defines the method to add POST request
func (engine *Engine) POST(pattern string, handler HandlerFunc) {
engine.addRoute("POST", pattern, handler)
}

// Run defines the method to start a http server
func (engine *Engine) Run(addr string) (err error) {
return http.ListenAndServe(addr, engine)
}

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

这就是Gee框架的原型。实现了路由映射表,提供了用户注册静态路由的方法,包装了服务启动函数。