go语言http库详解
调用过程
最简单的http
服务器代码实现如下
package main
import (
"net/http"
)
func main() {
http.ListenAndServe("", nil)
}
这个段代码会启动一个监听80端口的web服务器,但是无论访问任何地址都会返回404 page not found
,因为我们还没添加相应的处理程序。
其中http.ListenAndServe
的实现如下:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
它会创建一个http.Server
实例,然后调用它的ListenAndServer
接口。如果用户指定了http服务器监听的端口,则用用户指定的,否则监听”:http”也就是”:80”。
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)
}
http.Server
的Serve
会接受每一个客户端的请求,并对每个请求调用serve
。
func (srv *Server) Serve(l net.Listener) error {
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
rw, err := l.Accept()
connCtx := ctx
if cc := srv.ConnContext; cc != nil {
connCtx = cc(connCtx, rw)
}
c := srv.newConn(rw)
go c.serve(connCtx)
}
}
func (c *conn) serve(ctx context.Context) {
// ...
w, err := c.readRequest(ctx)
serverHandler{c.server}.ServeHTTP(w, w.req)
// ...
}
serve
会通过serverHandler{c.server}
的方式给http.Server
实例附加一个ServeHTTP
接口,并调用它,其实现方法如下:
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.Server.Handler
指定的处理器来接力处理。这个处理器是实现了如下Handler
接口的处理器:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
而如果传入的Handler
为nil
,即最开头程序的http.ListenAndServe("", nil)
第二个参数为nil
,则将DefaultServeMux
作为Handler
,因为DefaultServeMux
是http.ServeMux
类型,而http.ServeMux
也实现了这个接口
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)
}
在http.ServeMux
这个多路复用器实现的Handler
接口中,它会根据请求里的路径匹配对应的处理器,然后调用处理的Handler
接口。
所以DefaultServeMux
既是一个多路复用器,也是个处理器。因此最开头提到的程序与下列代码是等价的
package main
import (
"net/http"
)
func main() {
server := http.Server {
Addr: ":80",
Handler: nil,
}
server.ListenAndServe()
}
处理器
所以我们可以为这个程序添加一个HelloHandler
处理器
package main
import (
"fmt"
"net/http"
)
type AHandler struct {}
func (h AHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Handler A")
}
func main() {
ha := AHandler{ }
server := http.Server {
Addr: ":80",
Handler: ha,
}
server.ListenAndServe()
}
这样,无论访问任何页面,都会得到Handler A
的结果。其实不太实用,那如果我们想针对不同的地址调用不同的处理器呢?
多个处理器
其实可以利用多路复用器,上文提到多路复用器其实也实现了Handler
接口,在这个接口的ServeHTTP
函数中它会根据请求中的路径匹配到一个对应的处理器,并调用。因此我们只需要创建一个多路复用器,再将不同的Handler
注册到多路复用器里,就可以实现根据不同的请求地址调用不同的处理器。
package main
import (
"fmt"
"net/http"
)
type AHandler struct {}
type BHandler struct {}
func (h AHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Handler A")
}
func (h BHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Handler B")
}
func main() {
ha := AHandler{ }
hb := BHandler{ }
// 创建一个多路复用器
m := http.NewServeMux()
// 将处理器注册进复用器
m.Handle("/a", ha)
m.Handle("/b", hb)
server := http.Server {
Addr: ":80",
Handler: m, // 因为多路复用器也实现了Handler(处理器)接口
}
server.ListenAndServe()
}
这样访问/a
和访问/b
就会得到Handler A
和Handler B
两个不同的结果,访问其它,则会得到404 page not found
。这里不创建多路复用器直接注册也可以,因为http
库会默认使用DefaultServeMux
func main() {
ha := AHandler{ }
hb := BHandler{ }
// 将处理器注册进复用器DefaultServeMux里
http.Handle("/a", ha)
http.Handle("/b", hb)
server := http.Server {
Addr: ":80",
Handler: nil, // 会调用DefaultServeMux
}
server.ListenAndServe()
}
处理器函数
但是我们在实际使用中,使用先定义一个结构体,再对这个结构体实现一个Handler
接口,再注册的方式有点繁琐。经常用如下直接注册处理器函数的方式:
package main
import (
"fmt"
"net/http"
)
func AHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Handler A")
}
func BHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Handler B")
}
func main() {
m := http.NewServeMux()
m.HandleFunc("/a", AHandler)
m.HandleFunc("/b", BHandler)
server := http.Server {
Addr: ":80",
Handler: m,
}
server.ListenAndServe()
}
但很明显,AHandler
和BHandler
并没有实现Handler
接口,那么这又是如何做到的呢?
可以看到其注册函数稍稍有些不同,注册处理器的是m.Handle()
,注册处理器函数的是m.HandleFunc()
,可以接着看一下HandleFunc
的实现细节。
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
HandleFunc会将处理器函数转换成HandlerFunc
类型,HandlerFunc
和处理器函数具有同的形式,所以可以强制类型转换。同时http
库还为HandlerFunc
实现了Handler
接口。
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
也就是当调用该Handler
接口ServeHTTP
的时候就会调用到该函数自身。也就是说多路复用器的HandleFunc
函数利用Handle
函数封装了一个简易使用的版本。
实现简易的多路复用器
例如,我们想对每次访问页面的请求作日志记录,如果按之前的实现,则要在每个处理器或处理器函数中添加,这样太麻烦,我们可以利用http.ServeMux
实现一个自己多路器。
主要是实现Handler
接口和Handle
、HandleFunc
两个函数。
package main
import (
"fmt"
"log"
"net/http"
)
func AHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Handler A")
}
func BHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Handler B")
}
type CHandler struct {}
func (c CHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Handler C")
}
type Mux struct {
m *http.ServeMux
}
func (mux *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
mux.m.ServeHTTP(w, r)
}
func doMuxLog(r *http.Request) {
log.Printf("Request: %s from %s %s\n", r.URL.Path, r.RemoteAddr, r.UserAgent())
}
func (mux *Mux) Handle(pattern string, handler http.Handler) {
f := func(w http.ResponseWriter, r *http.Request) {
doMuxLog(r)
handler.ServeHTTP(w, r)
}
logf := http.HandlerFunc(f)
mux.m.Handle(pattern, logf)
}
func (mux *Mux) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) {
logf := func(w http.ResponseWriter, r *http.Request) {
doMuxLog(r)
handler(w, r)
}
mux.m.HandleFunc(pattern, logf)
}
func NewMux() *Mux {
mux := &Mux {
m: http.NewServeMux(),
}
return mux
}
func main() {
m := NewMux()
m.HandleFunc("/a", AHandler)
m.HandleFunc("/b", BHandler)
m.Handle("/c", CHandler{})
server := http.Server {
Addr: ":80",
Handler: m,
}
server.ListenAndServe()
}
其中Handle
和HandleFunc
也可以按如下方式实现
func (mux *Mux) Handle(pattern string, handler http.Handler) {
logf := func(http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
doMuxLog(r)
handler.ServeHTTP(w, r)
})
}
mux.m.Handle(pattern, logf(handler))
}
func (mux *Mux) HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)) {
logf := func(http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
doMuxLog(r)
handler(w, r)
}
}
mux.m.HandleFunc(pattern, logf(handler))
}