今天,我们来学习如何建立一个简单的WebServer。
一、分析WebServer
既然要进行实现,那必须参考其他Web框架是如何搭建Server的,这里我们用Gin来举例
打开源码,gin的Server命名为Engine,忽略其他字段,会发现最重要的是一个叫做RouterGroup的结构体,这个结构体的作用是:构建路由树。
参考整个实现,会发现除了Router之外,还有Context上下文,用于封装对于http的发送和请求的一些操作,但Context并不在Engine里面,所以这里暂时不讨论
那么,Engine有什么作用呢,参考下面示例代码:
func TestEngine(t *testing.T){
r := gin.Default()
r.Group("/", func(c *gin.Context) {})
r.Run(":8080")
}
由上可知,Engine的主要作用就是两个:
- 构造对应的路由树,并存放相应的业务逻辑
- 启动并监听对应的端口
因此,有了上面的这些内容,我们就可以自己来实现一个简单的WebServer
二、实现WebServer
话不多说,直接上代码
首先,Context是用来串联我们整个http链路的关键。例如在gin中Context可以用来进行字段的绑定等等操作,它是请求来的信息和我们要进行操作的业务逻辑中间的桥梁。
1. 简单的Context
这个Context,我们还没想好它要做什么,参考Gin的Context,所以暂时只用来对请求和响应进行一些封装
type Context struct {
Req *http.Request
Resp http.ResponseWriter
}
2. 简单的路由树结构体
围绕着这个路由树我们该怎么进行补充和完善的内容,篇幅过长,我们分几期来讲
type route struct{}
3. 组合并实现
因为Server的操作都是大同小异,所以,我们选择定义一个接口,让用户只专注于Server方法的调用,并且可以保证,如果我们需要其他不同操作的Server,可以实现这个接口,保证代码的扩展性
package serv
import (
"net/http"
)
// 保证Serv这个结构体一定实现了Server接口
var _ Server = &Serv{}
// HandlerFunc 就是我们的业务逻辑
type HandleFunc func(ctx Context)
// Server Server的api应该设计成什么样子
type Server interface {
//
ServeHTTP(writer http.ResponseWriter, request *http.Request)
// 启动并监听
Run(addr string) error
// 这是第一版,暂时在Server上来添加,实际我们需要在route中定义方法进行添加
AddRoute(method string, path string, handler HandleFunc)
}
type Serv struct {
*router
}
func NewServ() Server {
// 这里暂时还没写好Router,所以我先不初始化
return &Serv{}
}
// ServeHTTP
// 这里这么实现,是因为打开http的源码,发现http里的handler接口就是这么定义的
func (s *Serv) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
ctx := Context{
Req: request,
Resp: writer,
}
//查找路由树,并执行路由代码
s.Serve(ctx)
}
func (s *Serv) Serve(ctx Context) {}
func (s *Serv) AddRoute(method string, path string, handler HandleFunc) {
//panic("implement me")
}
// Get 我们的核心始终是AddRoute,所以接口里不实现Get方法,而是交给衍生的结构体去实现
func (s *Serv) Get(path string, handler HandleFunc) {
s.AddRoute(http.MethodGet, path, handler)
}
func (s *Serv) Run(addr string) error {
err := http.ListenAndServe(addr, s)
return err
}
4. 注意事项
- 对于 Serv 的 Get 方法,其实是参考了 Gin的 Handle , 其实所有 method 的本质就是调用了在图中,一个叫做 group.handler 的方法,而我们的Server,则是叫做AddRoute
- Run方法中,如果我们需要加入一个监听机制,可以不直接使用 http.ListenAndServe 方法,而是使用 net.Listen ,然后插入你的监听机制(例如 prometheus 等等),最后再使用 http.Serve 方法
-
func Run(){ listen, err := net.Listen("tcp", addr) if err != nil { // 打印日志 或者 panic } // 插入你的监听逻辑 http.Serve(listen, nil) }