一、Go原生代码库实现一个简单的web程序
首先来浏览一下以下使用Go内置的net/http包实现的一个简单的web实例:
示例代码:
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
//r.URL.Path是请求URL的路径部分
//其尾随的[1:]意思是“创建Path中第一个字符到结尾的子片段”(这将从路径名中删除前导“ /”)。
//将format字符串写入w
fmt.Fprintf(w, "Hello world, I love %s!", r.URL.Path[1:])
}
func main() {
//设置路由
http.HandleFunc("/", handler)
//设置监听的端口
http.ListenAndServe(":8080", nil)
}
运行上面程序,打开http://localhost:8080/coding可以见到如下效果:
先来大概解释一下这个web程序是如何运行的:
- 首先我们启动程序,main函数以调用http.HandleFunc开始,它告诉http包使用handler处理所有对web根目录("/")的请求。
- 然后调用http.ListenAndServe,指定监听8080端口。(暂时不必担心它的第二个参数nil。)此函数将阻塞,直到程序终止。
- 所以当在浏览器输入http://localhost:8080/coding时,程序收到此请求并将其路由给给handler函数处理。
- 通过变量r获得请求信息里的请求路径,获取到路径名后将去掉第一个字符的字符串写入w输出到客户端
handler函数:
- handler的函数签名是
func(ResponseWriter, *Request)
,它以http.ResponseWriter和http.Request作为参数。 - 一个http.ResponseWriter的值w,组装了HTTP服务器的响应;通过写入它,我们将数据发送到HTTP客户端。
- http.Request是一个表示客户端的HTTP请求的数据结构
二、细说Go如何使Web工作
其实Go使得Web工作主要就是两步:
- 先通过HandleFunc函数在DefaultServeMux中为给定的模式(pattern)注册处理函数。
- 然后通过ListenAndServe监听TCP端口8080,然后调用Serve和handler来处理传入连接的请求。handler通常为nil,在这种情况下使用DefaultServeMux中匹配的handler。
下面通过剖析源码来了解Go是如何实现上面的两步
1、HandleFunc函数为DefaultServeMux注册handler
DefaultServeMux&ServeMux
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
...
type muxEntry struct {
h Handler
pattern string
}
...
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
由net/http.server.go里的源码可知,DefaultServeMux
是一个指向ServeMux
的指针变量。
而ServeMux
是一个HTTP请求多路复用器。它将每个传入请求的URL与一组已注册的模式(ServerMux中的m)进行匹配,并调用与URL最匹配的模式的处理程序。
HandleFunc函数:
HandleFunc在DefaultServeMux中为给定的模式(pattern)注册处理函数(handler)。
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
//为给定的模式(pattern)注册处理函数(handler)。
DefaultServeMux.HandleFunc(pattern, handler)
}
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
...
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
...
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
//Handle方法为pattern注册Handler
mux.Handle(pattern, HandlerFunc(handler))
}
- 由net/http.server.go里的源码可知HandlerFunc是一个实现了Handler接口的类型
- 而ServeMux的Handle方法是为pattern注册Handler。
ServeMux的Handler方法
func (mux *ServeMux) Handle(pattern string, handler Handler) {
...
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
//为pattern注册Handler
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
...
}
2、ListenAndServe监听端口、处理接收的请求
ListenAndServe监听TCP的地址addr,然后调用Serve和handler来处理传入连接的请求。处理程序通常为nil,在这种情况下使用DefaultServeMux。
http.ListenAndServe
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
- 利用Server的ListenAndServe监听端口和处理请求
Server的ListenAndServe方法
func (srv *Server) ListenAndServe() error {
...
addr := srv.Addr
...
ln, err := net.Listen("tcp", addr)
...
return srv.Serve(ln)
}
- 通过net.Listen监听tcp端口,返回侦听器ln,然后调用Server的Serve方法接收侦听器接收的请求并处理。
Server的Serve方法
在侦听器l上接收传入的连接,为每个连接创建一个新的goroutine。这些goroutines读取请求,处理请求。
func (srv *Server) Serve(l net.Listener) error {
...
baseCtx := context.Background()
...
//ServerContextKey是一个全局变量,它等于&contextKey{"http-server"}
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
rw, err := l.Accept()
...
connCtx := ctx
...
c := srv.newConn(rw)
...
go c.serve(connCtx)
}
}
conn的serve方法
func (c *conn) serve(ctx context.Context) {
...
for {
w, err := c.readRequest(ctx)
...
req := w.req
...
//重点方法
serverHandler{c.server}.ServeHTTP(w, w.req)
...
}
}
serverHandler结构体
type serverHandler struct {
srv *Server
}
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux //handler为nil则使用默认的DefaultServeMux
}
...
handler.ServeHTTP(rw, req)
}
然后调用ServeMux的ServeHTTP方法,方法内部会根据其匹配模式返回符合请求URL的handler,然后调用handler的实现的ServeHTTP接口的方法处理请求
本文参考了
go官方文档给出的Writing Web Applications