golang http包http服务实现详解
golang可以快速构建一个性能还不错的服务器,主要是通过http包提供的功能实现完成,但是看不同人写这个服务器都有不同的写法,怎么感觉很乱哦,虽然写法不一样,但是基础原理都是一样的。先对http包进行梳理,再看几个实现就会恍然大悟了。
理论梳理
1. handle
处理,其实就是路由(pattern)+处理器(handler)。
// package http line 2483
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
注意 DefaultServeMux,这是一个处理器后边会讲。
2. handler
处理器,请求和响应进行处理的地方,真正写服务逻辑的地方。
// package http line 86
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
这是一个接口,为了方便使用,http包中实现了一个函数处理器
// package http line 2042
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
注意HandlerFun这个词,这是一个处理器函数。
这个处理器只有两个参数,一个response一个request,一个处理器获取到请求和响应对象,就可以通过这两对象上的方法进行各种操作,比如读取request数据,然后对response进行响应编写。
3. ServeMux
多路复用,可以理解成路由处理器,对请求流进行多路复用,把进来的流打到到对应路由上的处理器里。但是这个对象仅仅负责路由和对应的处理器,至于http服务监听,超时控制等等不归他管理。
// package http line 2415
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
从这里也可以看出 ServeMux也是实现了handler接口的,也是一个处理器,只是这个处理器负责根据路由对请求流进行分发,相当于一个调度中心,管理不同的路由和处理器。具体实现逻辑可以在mux.Handler里查看,mux.Handler又返回一个处理器,直接执行。
4. Server
这也是一个处理器,只是它负责监听端口,控制超时,控制连接的对象,http相关参数设置等,在http包 2527行,有兴趣的可以看看。我们把他叫基础处理器。
//package http line 2857
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
...
handler.ServeHTTP(rw, req)
}
这个处理器最终也是调用其他处理来进行处理的,默认调用一个DefaultServeMux处理器。
//line 2918
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(ln)
}
// line 2971
func (srv *Server) Serve(l net.Listener) error {...}
// line 3050
func (srv *Server) ServeTLS(l net.Listener, certFile, keyFile string) error {..}
从这两行能看出来,server处理器是对tcp进行处理的一个处理器。
当然为了更友好的启动http的服务器,http里特意有两个快捷函数。
//line 3182
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
//line 3192
func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServeTLS(certFile, keyFile)
}
总结
通过以上概念可以知道以下流程
tcp--> Server --> ServerMux --(pattern)--> handler
tcp流进来后通过Server处理器进行http相关方面处理,处理完了交给ServerMux处理器处理,ServerMux通过路由进行调度,找到最终的用户编写的处理进行处理。
实战
写法1 Server处理器 ServerMux处理器 handler处理器全流程写法
//普通处理器 MyHandler
type MyHandler struct{}
func (this MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("my handler \n"))
}
//普通处理器 ZSHandler
type ZSHandler struct{}
func (this ZSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("zhang san handler\n"))
}
func main() {
//路由处理器
route := http.NewServeMux()
//路由处理器与普通处理器进行绑定
route.Handle("/api/myhandler", MyHandler{})
route.Handle("/api/zshandler", ZSHandler{})
//基础处理
ser := &http.Server{}
ser.Handler = route
ser.Addr = ":9100"
// 基础处理进行启动
ser.ListenAndServe()
}
虽然写的代码多点,但是个人比较喜欢这种写法,条理清晰,正是因为大家想着简便写法,才造成了各种各样的写法。
写法2 去掉Server处理器 只有ServerMux处理器 handler处理器全流程写法
//普通处理器 MyHandler
type MyHandler struct{}
func (this MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("my handler \n"))
}
//普通处理器 ZSHandler
type ZSHandler struct{}
func (this ZSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("zhang san handler\n"))
}
func main() {
//路由处理器
route := http.NewServeMux()
//路由处理器与普通处理器进行绑定
route.Handle("/api/myhandler", MyHandler{})
route.Handle("/api/zshandler", ZSHandler{})
// 基础处理进行启动
http.ListenAndServe(":9100",route)
}
是省了几行代码,同时也不能对http进行控制了。
写法3 干掉Server, ServerMux处理器,只要普通处理器
//普通处理器 MyHandler
type MyHandler struct{}
func (this MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("my handler \n"))
}
//普通处理器 ZSHandler
type ZSHandler struct{}
func (this ZSHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("zhang san handler\n"))
}
http.Handle("/api/myhandler", MyHandler{})
http.Handle("/api/zshandler", ZSHandler{})
http.ListenAndServe(":9100", nil)
虽然写法上没有Server和ServerMux处理器但是看过http.Handle就会明白http里有个默认处理。
写法4 干掉Server,ServerMux,和普通处理器
http.HandleFunc("/api/my", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("my handler \n"))
})
http.HandleFunc("/api/zhangsan", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("zhang san handler \n"))
})
http.ListenAndServe(":9100", nil)
各种处理器都隐藏,还用了一个函数处理器,但是本质上所有处理器都没有消失,只是被隐藏了,使用的默认处理器。
测试
http server原理弄清楚了,现在看一下http如何进行测试。
http测试主要是测试你编写的处理函数是否正确,路由是否正确,理论上是不需要真正的请求http,只需要执行一下ServeHTTP,看一下结果是否正确即可。只需要模拟出来ServeHTTP的request和response即可。
//定义ServeHTTP
type MyHandler struct{}
func (this MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("my handler \n"))
}
//对MyHandler进行测试
func TestServeHTTP(t *testing.T){
req, err := http.NewRequest("GET", "/my", nil)
rr := httptest.NewRecorder()
MyHandler{}.ServeHTTP(rr, req)
if status := rr.Code; status != http.StatusOK {
t.Errorf("返回状态:%v", status)
}
fmt.Println(rr.Body.String())
}
response用httptest.NewRecorder()来生成,测试完后,通过它来查看测试结果。
下面再来看一下gin框架如何测试。
func SetupRouter() *gin.Engine {
router := gin.Default()
router.Use(func(context *gin.Context) {
fmt.Println("this is middler")
})
router.GET("/a", func(context *gin.Context) {
context.JSONP(200, gin.H{"data": "this is a"})
})
router.GET("/b", func(context *gin.Context) {
context.JSONP(200, gin.H{"data": "this is b"})
})
return router
}
该代码定义了一个中间件,两个路由处理器,同时把*gin.Engine当作参数进行返回,后边的测试需要通过该对象来进行。 下面先来看看正常运行代码。
router := SetupRouter()
router.Run(":8000")
这段代码就会开启一个http服务器,监听8000端口。
下面看看测试时候如何做
func TestSetupRouter(t *testing.T) {
router := SetupRouter()
rr := httptest.NewRecorder()
ra := httptest.NewRequest("GET", "/a", nil)
rb := httptest.NewRequest("GET", "/b", nil)
router.ServeHTTP(rr, ra)
fmt.Printf("a:%v,%v\n", rr.Code, rr.Body.String())
router.ServeHTTP(rr, rb)
fmt.Printf("a:%v,%v\n", rr.Code, rr.Body.String())
}
这段代码可以看出,定义好response和request后直接调用*gin.Engine的ServeHTTP的实现函数既可以,该函数包括了gin的中间件和路由相关函数。
转载自:https://juejin.cn/post/7087253356581748749