官方样例
// 创建一个Foo路由和处理函数
http.Handle("/foo", fooHandler)
// 创建一个bar路由和处理函数
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})
// 监听8080端口
log.Fatal(http.ListenAndServe(":8080", nil))
阅读代码库流程
阅读顺序,库函数 > 结构定义 > 结构函数,首先搞清楚库要提供什么功能(对外函数), 为了提供这些功能要把库分为几个核心模块(结构), 最后每个模块该提供什么能力(结构函数)。
库函数
go doc net/http | grep “^func” 查看对外函数:
func CanonicalHeaderKey(s string) string
func DetectContentType(data []byte) string
func Error(w ResponseWriter, error string, code int)
func Get(url string) (resp *Response, err error)
func Handle(pattern string, handler Handler)
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
func Head(url string) (resp *Response, err error)
func ListenAndServe(addr string, handler Handler) error
func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error
func MaxBytesReader(w ResponseWriter, r io.ReadCloser, n int64) io.ReadCloser
func NewRequest(method, url string, body io.Reader) (*Request, error)
func NewRequestWithContext(ctx context.Context, method, url string, body io.Reader) (*Request, error)
func NotFound(w ResponseWriter, r *Request)
func ParseHTTPVersion(vers string) (major, minor int, ok bool)
func ParseTime(text string) (t time.Time, err error)
func Post(url, contentType string, body io.Reader) (resp *Response, err error)
func PostForm(url string, data url.Values) (resp *Response, err error)
func ProxyFromEnvironment(req *Request) (*url.URL, error)
func ProxyURL(fixedURL *url.URL) func(*Request) (*url.URL, error)
func ReadRequest(b *bufio.Reader) (*Request, error)
func ReadResponse(r *bufio.Reader, req *Request) (*Response, error)
func Redirect(w ResponseWriter, r *Request, url string, code int)
func Serve(l net.Listener, handler Handler) error
func ServeContent(w ResponseWriter, req *Request, name string, modtime time.Time, ...)
func ServeFile(w ResponseWriter, r *Request, name string)
func ServeTLS(l net.Listener, handler Handler, certFile, keyFile string) error
func SetCookie(w ResponseWriter, cookie *Cookie)
func StatusText(code int) string
大致分为三类:
- 为服务端提供创建HTTP服务,名字中包含Serve,如Server、ServeFile、ListenAndServe
- 为客户端提供调用HTTP服务,以http的method同名,如Get、Post、Head
- 提供中转代理,如ProxyURL、ProxyFromEnvironment
结构定义
go doc net/http | grep “^type” | grep struct
type Client struct{ ... }
type Cookie struct{ ... }
type ProtocolError struct{ ... }
type PushOptions struct{ ... }
type Request struct{ ... }
type Response struct{ ... }
type ServeMux struct{ ... }
type Server struct{ ... }
type Transport struct{ ... }
- Client 负责构建 HTTP 客户端
- Server 负责构建 HTTP 服务端
- ServerMux 负责 HTTP 服务端路由
- Transport、Request、Response、Cookie 负责客户端和服务端传输对应的不同模块
构建的 HTTP 服务端除了提供真实服务之外,也能提供代理中转服务,它们分别由 Client 和 Server 两个数据结构负责。除了这两个最重要的数据结构之外,HTTP 协议的每个部分,比如请求、返回、传输设置等都有具体的数据结构负责。
结构函数
跟着 http.ListenAndServe 这个函数来理一下 net/http 创建服务的主流程逻辑
第一层,http.ListenAndServe 本质是通过创建一个 Server 数据结构,调用 server.ListenAndServe 对外提供服务。这一层完全是比较简单的封装,目的将内部结构体的方法对外暴露成库函数,增加易用性。
第二层, 此ListenAndServe函数先定义了监听信息net.Listen, 然后调用Serve函数(很合乎ListenAndServe这个函数名,内部就是listen+serve)。
第三层, Serve 函数中,用了一个 for 循环,通过 l.Accept不断接收从客户端传进来的请求连接。当接收到了一个新的请求连接的时候,通过 srv.NewConn创建了一个连接结构(http.conn)作为c,并创建一个 Goroutine 为这个请求连接对应服务(c.serve)。
从第四层开始,后面就是单个连接的服务逻辑了。
在第四层,c.serve函数先判断本次 HTTP 请求是否需要升级为 HTTPs,接着创建读文本的 reader 和写文本的 buffer,再进一步读取本次请求数据,然后第五层调用最关键的方法 serverHandler{c.server}.ServeHTTP(w, w.req) ,来处理这次请求。
这个关键方法是为了实现自定义的路由和业务逻辑,调用写法是比较有意思的:
serverHandler{c.server}.ServeHTTP(w, w.req)
serverHandler 结构体,是标准库封装的,代表“请求对应的处理逻辑”,它只包含了一个指向总入口服务 server 的指针。
这个结构将总入口的服务结构 Server 和每个连接的处理逻辑巧妙联系在一起了,你可以看接着的第六层逻辑(意思就是每个连接c内部有个字段server,指向了总的入口结构体):
// 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 结构已经设置了 Handler,就调用这个 Handler 来处理此次请求,反之则使用库自带的 DefaultServerMux。
那么 DefaultServeMux 是怎么寻找 Handler 的呢,这就是思维导图的最后一部分第七层。
DefaultServeMux的ServeHTTP(rw, req)方法,就是根据req的url中的path,找到注册时候的路由,而注册时候的http.Handle(“foo”, fooHandler)和http.HandleFunc(“bar”, func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, “Hello, %q”, html.EscapeString(r.URL.Path)) }),作用就是写入DefaultServeMux中的map结构,用于后续的寻找。
各层的关键结论:
- 第一层,标准库创建 HTTP 服务是通过创建一个 Server 数据结构完成的;
- 第二层,Server 数据结构在 for 循环中不断监听每一个连接;
- 第三层,每个连接默认开启一个 Goroutine 为其服务;
- 第四、五层,serverHandler 结构代表请求对应的处理逻辑,并且通过这个结构进行具体业务逻辑处理;
- 第六层,Server 数据结构如果没有设置处理函数 Handler,默认使用 DefaultServerMux 处理请求;
- 第七层,DefaultServerMux 是使用 map 结构来存储和查找路由规则。
创建框架的server结构
通过 go doc net/http.Server 我们可以看到 Server 的结构:
type Server struct {
// 请求监听地址
Addr string
// 请求核心处理函数
Handler Handler
...
}
其中最核心的是 Handler 这个字段,从主流程中我们知道(第六层关键结论),当 Handler 这个字段设置为空的时候,它会默认使用 DefaultServerMux 这个路由器来填充这个值,但是我们一般都会使用自己定义的路由来替换这个默认路由。
所以在框架代码中,我们要创建一个自己的核心路由结构,实现 Handler。
创建framework目录,以下内容写入core.go:
package framework
import "net/http"
type Core struct {
}
func NewCore() *Core {
return &Core{}
}
func (c *Core) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
在framework目录之外,创建main.go:
package main
import (
"net/http"
"webframework/framework"
)
func main() {
server := http.Server{
Handler: framework.NewCore(),
Addr: ":8080",
}
server.ListenAndServe()
}
我们通过自己创建Server数据结构,并且在数据结构中创建了自定义的 Handler(Core 数据结构)和监听地址,实现了一个 HTTP 服务。这个服务的具体业务逻辑都集中在我们自定义的 Core 结构中,后续我们要做的事情就是不断丰富这个 Core 数据结构的功能逻辑。
补充
net/http中Request结构体
Request结构体可以表示server端收到的请求,或者客户端主动发出的请求。
- 写服务端代码时,ServeHTTP方法时接收的request参数
- 写客户端代码时,发送方法的request参数
type Request struct {
Method string
// 定义了被请求的URI(对于服务端请求)或者要访问的URL(对于客户端请求)
// URI不包含域名,URL包含域名
//
// 对于服务端请求,此URL字段是http请求行Request-Line中的URI解析而来,
// Request-Line存储在RequestURI字段中。
// 对于大多数请求,除了Path和RawQuery字段外,其他字段都为空
// 对于客户端请求,此URL的Host字段表明要连接的服务器地址,而此结构体的
// Host字段可选择性地定义了要发送地http header值
URL *url.URL
// 从PATCH, POST 或者PUT请求的body中解析的参数
PostForm url.Values
...
}
type URL struct {
Scheme string
Opaque string // encoded opaque data
User *Userinfo // username and password information
Host string // host or host:port
Path string // path (relative paths may omit leading slash)
RawPath string // encoded path hint (see EscapedPath method)
ForceQuery bool // append a query ('?') even if RawQuery is empty
RawQuery string // encoded query values, without '?'
Fragment string // fragment for references, without '#'
RawFragment string // encoded fragment hint (see EscapedFragment method)
}
type Values map[string][]string
// 解析RUL中的参数,返回{key: value_list}这样的map
func (u *URL) Query() Values {
v, _ := ParseQuery(u.RawQuery)
return v
}
可以看到,对于服务端而言,Request.URL代表请求行中的URI录像,用Query方法返回URI的键值对(URI中key可以写多次,每个key最终对应的value是一个列表形式, 不过一般URL中不会传重复的key)。
比如 http://192.168.1.241:8080/login?user=111&passwd=abc&user=222
因此框架中解析命令行参数如下:
// 获取所有参数
func (ctx *Context) QueryAll() map[string][]string {
if ctx.request != nil {
return map[string][]string(ctx.request.URL.Query())
}
return map[string][]string{}
}
// 获取string类型的参数,默认值为“”
func (ctx *Context) QueryString(key string, def string) string {
params := ctx.QueryAll()
if vals, ok := params[key]; ok {
len := len(vals)
if len > 0 {
return vals[len-1] // 取列表中最后一项
}
}
return def
}
// 获取int类型的key,默认值为0
func (ctx *Context) QueryInt(key string, def int) int {
params := ctx.QueryAll()
if vals, ok := params[key]; ok {
len := len(vals)
if len > 0 {
intval, err := strconv.Atoi(vals[len-1])
if err != nil {
return def
}
return intval
}
}
return def
}
测试样例:
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
fmt.Println(request.URL)
fmt.Println("url params: ")
url_params := request.URL.Query()
for key, list := range url_params {
fmt.Printf("%v: %v\n", key, list)
}
fmt.Println("body params: ")
request.ParseForm()
body_params := request.PostForm
for key, list := range body_params {
fmt.Printf("%v: %v\n", key, list)
}
fmt.Println()
})
http.ListenAndServe(":8080", nil)
}
【小结】:
- 每个http请求会开一个goroutine进行处理,调用注册时server中handler路由的ServerHTTP方法
- 由于多线程并发调用ServerHTTP方法,因此如果是自己写逻辑要注意并发问题。官方样例中只是从map中找处理函数,map并发读不会出现问题。