likes
comments
collection
share

golang http包http服务实现详解

作者站长头像
站长
· 阅读数 10

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的中间件和路由相关函数。