36.5 HTTP Server 服务端

HTTP Server服务端用来接收并响应HTTP Client端发出的HTTP Request请求,是net/http包中非常重要和关键的一个功能。我们在Go语言中简单就能搭建HTTP服务器,就是因为它的存在。

在server.go文件中还定义了一个非常重要的接口:Handler,另外还有一个结构体response,这和http.Response结构体只有首字母大小写不一致,不过这个response 也是响应,只不过是专门用在服务端,和http.Response结构体是完全两回事。

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}
type Server struct
// 监听TCP网络地址srv.Addr然后调用Serve来处理接下来连接的请求。
// 如果srv.Addr是空的话,则使用“:http”。
func (srv *Server) ListenAndServe() error 
// ListenAndServeTLS监听srv.Addr确定的TCP地址,并且会调用Serve
// 方法处理接收到的连接。必须提供证书文件和对应的私钥文 件。如果证书是由
// 权威机构签发的,certFile参数必须是顺序串联的服务端证书和CA证书。
// 如果srv.Addr为空字符串,会使 用”:https”。
func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error 
// 接受Listener l的连接,创建一个新的服务协程。该服务协程读取请求然后调用
// srv.Handler来应答。实际上就是实现了对某个端口进行监听,然后创建相应的连接。 
func (srv *Server) Serve(l net.Listener) error
// 该函数控制是否http的keep-alives能够使用,默认情况下,keep-alives总是可用的。
// 只有资源非常紧张的环境或者服务端在关闭进程中时,才应该关闭该功能。 
func (s *Server) SetKeepAlivesEnabled(v bool)
// 是一个http请求多路复用器,它将每一个请求的URL和
// 一个注册模式的列表进行匹配,然后调用和URL最匹配的模式的处理器进行后续操作。
type ServeMux
// 初始化一个新的ServeMux 
func NewServeMux() *ServeMux
// 将handler注册为指定的模式,如果该模式已经有了handler,则会出错panic。
func (mux *ServeMux) Handle(pattern string, handler Handler) 
// 将handler注册为指定的模式 
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request))
// 根据指定的r.Method, r.Host以及r.RUL.Path返回一个用来处理给定请求的handler。
// 该函数总是返回一个非nil的 handler,如果path不是一个规范格式,则handler会
// 重定向到其规范path。Handler总是返回匹配该请求的的已注册模式;在内建重定向
// 处理器的情况下,pattern会在重定向后进行匹配。如果没有已注册模式可以应用于该请求,
// 本方法将返回一个内建的”404 page not found”处理器和一个空字符串模式。
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) 
// 该函数用于将最接近请求url模式的handler分配给指定的请求。 
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request)

Handler接口应该算是server.go中最关键的接口了,如果我们仔细看这个文件的源代码,将会发现很多结构体实现了这个接口的ServeHTTP方法。

注意这个接口的注释:Handler响应HTTP请求。没错,最终我们的HTTP服务是通过实现ServeHTTP(ResponseWriter, *Request)来达到服务端接收客户端请求并响应。

理解 HTTP 构建的网络应用只要关注两个端---客户端(Clinet)和服务端(Server),两个端的交互来自 Clinet 的 Request,以及Server端的Response。HTTP服务器,主要在于如何接受 Clinet端的 Request,Server端向Client端返回Response。

那这个过程是什么样的呢?要讲清楚这个过程,还需要回到开始的HTTP服务器程序。这里以前面我们了解到的HTTP Request、HTTP Response、HTTP Client作为基础,并重点分析server.go源代码才能讲清楚:

func main() {
	http.HandleFunc("/", myfunc)
	http.ListenAndServe(":8080", nil)
}

以上两行代码,就成功启动了一个http服务器。我们通过net/http 包源代码分析发现,调用Http.HandleFunc,按顺序做了几件事:

1.Http.HandleFunc调用了DefaultServeMux的HandleFunc

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

2.DefaultServeMux.HandleFunc调用了DefaultServeMux的Handle,DefaultServeMux是一个ServeMux 指针变量。而ServeMux 是Go语言中的Multiplexer(多路复用器),通过Handle匹配pattern 和我们定义的handler(其实就是http.HandlerFunc函数类型变量)。

var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
	mux.Handle(pattern, HandlerFunc(handler))
}

注意: 上面的方法命名Handle,HandleFunc和HandlerFunc,Handler(接口),他们很相似,容易混淆。记住Handle和HandleFunc和pattern 匹配有关,也即往DefaultServeMux的map[string]muxEntry中增加对应的handler和路由规则。

接着我们看看myfunc的声明和定义:

func myfunc(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "hi")
}

而type HandlerFunc func(ResponseWriter, *Request) 是一个函数类型,而我们定义的myfunc的函数签名刚好符合这个函数类型。

所以http.HandleFunc("/", myfunc),实际上是mux.Handle("/", HandlerFunc(myfunc))。

HandlerFunc(myfunc) 让myfunc成为了HandlerFunc类型,我们称myfunc为handler。而HandlerFunc类型是具有ServeHTTP方法的,而有了ServeHTTP方法也就是实现了Handler接口。

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r) // 这相当于自身的调用
}

现在ServeMux和Handler都和我们的myfunc联系上了,myfunc是一个Handler接口变量也是HandlerFunc类型变量,接下来和结构体server有关了。

http.ListenAndServe的源码可以看出,它创建了一个server对象,并调用server对象的ListenAndServe方法:

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

而我们HTTP服务器中第二行代码:

http.ListenAndServe(":8080", nil)

创建了一个server对象,并调用server对象的ListenAndServe方法,这里没有直接传递Handler,而是默认使用DefautServeMux作为multiplexer,myfunc是存在于handler和路由规则中的。

Server的ListenAndServe方法中,会初始化监听地址Addr,同时调用Listen方法设置监听。

for {
    rw, e := l.Accept()
    ...
    c := srv.newConn(rw)
c.setState(c.rwc, StateNew) 
go c.serve(ctx)
}

监听开启之后,一旦客户端请求过来,Go就开启一个协程go c.serve(ctx)处理请求,主要逻辑都在serve方法之中。

func (c *conn) serve(ctx context.Context),这个方法很长,里面主要的一句:serverHandler{c.server}.ServeHTTP(w, w.req)。其中w由w, err := c.readRequest(ctx) 得到,因为有传递context。

还是来看源代码:

type serverHandler struct {
srv *Server
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if req.RequestURI == "" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}

http.ListenAndServe(":8080", nil)开始,handler是nil,所以最后实际ServeHTTP方法是DefaultServeMux.ServeHTTP(rw, req)

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)
}

通过func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string),我们得到Handler h,然后执行h.ServeHTTP(w, r)方法,也就是执行我们的myfunc函数(别忘了myfunc是HandlerFunc类型,而他的ServeHTTP(w, r)方法这里其实就是自己调用自己),把response写到http.ResponseWriter对象返回给客户端,fmt.Fprintf(w, "hi"),我们在客户端会接收到hi 。至此整个HTTP服务执行完成。

总结下,HTTP服务整个过程大概是这样:

Request -> ServeMux(Multiplexer) -> handler-> Response

我们再看下面代码:

http.ListenAndServe(":8080", nil)
func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

上面代码实际上就是server.ListenAndServe()执行的实际效果,只不过简单声明了一个结构体Server{Addr: addr, Handler: handler}实例。如果我们声明一个Server实例,完全可以达到深度自定义 http.Server的目的:

package main
import (
	"fmt"
	"net/http"
)
func myfunc(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "hi")
}
func main() {
	// 更多http.Server的字段可以根据情况初始化
	server := http.Server{
		Addr:         ":8080",
		ReadTimeout:  0,
		WriteTimeout: 0,
	}
	http.HandleFunc("/", myfunc)
	server.ListenAndServe()
}
这样服务也能跑起来,而且我们完全可以根据情况来自定义我们的Server!
还可以指定Servemux的用法:
GOPATH\src\go42\chapter-15\15.3\7\main.go
package main
import (
	"fmt"
	"net/http"
)
func myfunc(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "hi")
}
func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", myfunc)
	http.ListenAndServe(":8080", mux)
}

如果既指定Servemux又自定义 http.Server,因为Server中有字段Handler,所以我们可以直接把Servemux变量作为Server.Handler:

package main
import (
	"fmt"
	"net/http"
)
func myfunc(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "hi")
}
func main() {
	server := http.Server{
		Addr:         ":8080",
		ReadTimeout:  0,
		WriteTimeout: 0,
	}
	mux := http.NewServeMux()
	server.Handler = mux
	mux.HandleFunc("/", myfunc)
	server.ListenAndServe()
}

在前面pprof 包的内容中我们也用了本章开头这段代码,当我们访问http://localhost:8080/debug/pprof/ 时可以看到对应的性能分析报告。 因为我们这样导入 _"net/http/pprof" 包时,在文件 pprof.go 文件中init 函数已经定义好了handler:

func init() {
	http.HandleFunc("/debug/pprof/", Index)
	http.HandleFunc("/debug/pprof/cmdline", Cmdline)
	http.HandleFunc("/debug/pprof/profile", Profile)
	http.HandleFunc("/debug/pprof/symbol", Symbol)
	http.HandleFunc("/debug/pprof/trace", Trace)
}

所以,我们就可以通过浏览器访问上面地址来看到报告。现在再来看这些代码,我们就明白怎么回事了!

下一节:标准库http提供了Handler接口,用于开发者实现自己的handler。只要实现接口的ServeHTTP方法即可。