使用net/http
包编写一个最简单的Web服务器:
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", index)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
func index(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, world!")
}
Web服务器一次请求的流程如下:
客户端→request→多路器(multiplexer)/路由器(router)→handler→response→客户端
这一过程的核心是路由,即使用多路器找到URL模式对应的处理函数,因此net/http
包中最重要的概念就是多路器和handler,分别对应ServeMux
结构体和Handler
接口
main()
函数的第1行调用http.HandleFunc()
函数进行路由注册,第2行调用http.ListenAndServe()
函数监听端口启动服务
http.HandleFunc()
函数调用默认多路器的同名方法:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
http.ListenAndServe()
函数创建了一个Server
实例,之后调用该实例的同名方法:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
type Server struct {
Addr string
Handler Handler
// ...
}
http.ListenAndServe()
的第二个参数和Server
的第二个字段都是Handler
类型
Handler
是一个接口,只有一个ServeHTTP()
方法:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
Handler
接口描述的是“给定请求能够返回响应的对象”,即执行实际的业务逻辑,handler函数就是这样的对象
示例代码中的index()
就是一个handler函数,该函数的参数类型与ServeHTTP()
方法相同,但函数名不同
为了能够将handler函数包装为Handler
接口值,net/http
包定义了一个HandlerFunc
类型:
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
HandlerFunc
是一个函数类型,且实现了Handler
接口,其ServeHTTP()
方法就是调用函数本身,因此HandlerFunc(index)
是一个Handler
类型的值,其ServeHTTP()
方法就是调用index()
函数
http.HandleFunc()
函数就利用了这种转换,http.HandleFunc("/", index)
等价于http.Handle("/", http.HandlerFunc(index))
ServeMux
结构体定义如下:
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry
hosts bool
}
type muxEntry struct {
h Handler
pattern string
}
可以看到ServeMux
本质上就是URL模式到handler的映射,存储在其m
字段中
可以使用http.NewServeMux()
函数创建一个新的ServeMux
,也可以直接使用默认的DefaultServeMux
,http.HandleFunc()
就使用了DefaultServeMux
func NewServeMux() *ServeMux { return new(ServeMux) }
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
示例代码中执行完main()
函数第1行后http.DefaultServeMux
的值如下:
ServeMux
的两大功能是路由注册和路由查找
路由注册由Handle()
和HandleFunc()
方法实现:
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
// ...
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
// ...
}
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
// ...
mux.Handle(pattern, HandlerFunc(handler))
}
可以看到Handle()
方法就是将指定的URL模式和handler写入映射,而HandleFunc()
方法只是一个快捷操作
由于http.Handle()
和http.HandleFunc()
分别调用了DefaultServeMux.Handle()
和DefaultServeMux.HandleFunc()
,因此
http.HandleFunc("/", index)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
等价于
mux := http.NewServeMux()
mux.HandleFunc("/", index)
log.Fatal(http.ListenAndServe("localhost:8000", mux))
http.ListenAndServe()
的第二个参数是用于处理所有请求的handler,如果是nil
则使用DefaultServeMux
上面最后一行代码将mux
作为http.ListenAndServe()
的第二个参数,因为ServeMux
也实现了Handler
接口
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
// ...
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
其中mux.Handler(r)
可以理解为mux.m[r.URL.Path]
,即在映射中查找request的URL对应的handler
上面提到,Handler
接口描述的是“给定请求能够返回响应的对象”,ServeMux
实现这一功能的方式就是根据请求的URL在映射中找到handler并调用该handler,这也是ServeMux
的路由查找功能
注册好路由后还需要启动服务器监听端口
http.ListenAndServe()
创建了一个Server
实例并调用其同名方法
(*Server).ListenAndServe()
调用net.Listen()
监听端口,并使用其返回的listener调用自己的Serve()
方法
func (srv *Server) ListenAndServe() error {
// ...
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(ln)
}
(*Server).Serve()
遵循套接字编程标准:主体是一个无限循环,每次循环调用Listener.Accept()
接受客户端请求,返回一个连接对象,最后启动一个goroutine调用连接对象的serve()
方法来处理客户端请求
func (srv *Server) Serve(l net.Listener) error {
// ...
for {
rw, err := l.Accept()
// ...
c := srv.newConn(rw)
// ...
go c.serve(connCtx)
}
}
(*conn).serve()
方法的代码如下:
func (c *conn) serve(ctx context.Context) {
// ...
for {
w, err := c.readRequest(ctx)
// ...
if err != nil {
// return
}
// ...
serverHandler{c.server}.ServeHTTP(w, w.req)
// ...
}
}
该方法的代码很长,但核心逻辑很简单,主体也是一个无限循环(每个连接能处理多次请求),每次循环获取一次客户端请求,并调用对应的handler处理请求
readRequest()
的第一个返回值是response
指针,该结构体实现了ResponseWriter
接口,并且有一个*Request
成员req
,因此w
和w.req
可以充当handler的两个参数
如果readRequest()
返回了一个错误(例如客户端断开连接)则serve()
方法返回,本次连接结束
调用handler时创建了一个serverHandler
实例,该类型定义如下:
type serverHandler struct {
srv *Server
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
// ...
handler.ServeHTTP(rw, req)
}
该结构体只有一个Server
指针字段srv
,并且也实现了Handler
接口,其ServeHTTP()
方法就是调用srv.Handler.ServeHTTP()
,其中srv.Handler
就是一开始调用http.ListenAndServe()
时的第二个参数,是一个多路器,如果是nil
则使用DefaultServeMux
(不是很懂为什么要定义这个类型)