一、go中HTTP服务处理流程
超文本传输协议(HTTP,Hyper Text Transfer Protocol)是互联网上应用最为广泛的一种网络传输协议,所有的WWW文件都必须遵守这个标准。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。
HTTP 协议从诞生到现在,发展从1.0,1.1到2.0也不断在进步。除去细节,理解 HTTP 构建的网络应用只要关注两个端——客户端(client)和服务端(server),两个端的交互来自 client 的 request,以及server端的response。所谓的http服务器,主要在于如何接受 client 的 request,并向client返回response。接收request的过程中,最重要的莫过于路由(router),即实现一个Multiplexer器。
Go中既可以使用内置的 multiplexer - DefaultServeMux,也可以自定义。Multiplexer路由的目的就是为了找到处理器函数(handler),后者将对request进行处理,同时构建response。
二、构建一个简单的http服务
代码示例
package main
import "net/http"
func main() {
// 1.设置路由
// 访问"/",调用indexHandleFunc函数处理
http.HandleFunc("/", indexHandleFunc)
// 访问"/home",调用homeHandleFunc函数处理
http.HandleFunc("/home", homeHandleFunc)
// 2.开启监听
http.ListenAndServe(":8080", nil)
}
func indexHandleFunc(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("index"))
}
func homeHandleFunc(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("home"))
}
运行程序
访问http://localhost:8080/
,页面上显示index
访问http://localhost:8080/home/
,页面上显示home
三、深入net/http包理解go语言http
1、http.Server
HTTP 服务器在 Go 语言中是由 http.Server 结构体对象实现的。参考 http.ListenAndServe() 的实现:
// src/net/http/server.go
// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
可见过程是先实例化 Server 对象,再完成 ListenAndServe 。其中 Serve 对象就是表示 HTTP 服务器的对象。其结构如下 :
// src/net/http/server.go
type Server struct {
Addr string // TCP 监听地址, 留空为:":http"
Handler Handler // 调用的 handler(路由处理器), 设为 nil 表示 http.DefaultServeMux
ReadTimeout time.Duration // 请求超时时长
WriteTimeout time.Duration // 响应超时时长
...
}
实例化了 Server
对象后,调用其 func (srv *Server) ListenAndServe() error
方法。该方法会监听 srv.Addr
指定的 TCP 地址,并通过 func (srv *Server) Serve(l net.Listener) error
方法接收浏览器端连接请求。Serve
方法会接收监听器 l
收到的每一个连接,并为每一个连接创建一个新的服务协程goroutine。
该 go 协程会读取请求,然后调用 srv.Handler
处理并响应。srv.Handler
通常会是 nil,这意味着需要调用 http.DefaultServeMux
来处理请求,这个 DefaultServeMux
是默认的路由,我们使用 http.HandleFunc
就是在 http.DefaultServeMux
上注册方法。
看一下详细过程(不完全摘录go源码):
step1:
初始化一个Server结构体实例后, 执行Server.ListenAndServer函数(其中主要调用net.Listen 和 Server.Serve函数)
func (srv *Server) ListenAndServe() error {
// 调用net.Listen方法启用监听
ln, err := net.Listen("tcp", addr)
// 调用Server对象的Serve方法,将监听对象作为参数传入
return srv.Serve(ln)
}
step2:
接着进入Server.Serve
部分的代码,看看详细的处理过程,可以看到主要是在主goroutine中用for循环阻塞,不断通过Accept
函数读取连接请求,然后调用Server.newConn()
函数,创建一个连接实例c
,然后每个连接实例启动一个新的goroutine去执行处理,从这里也能看出, 如果并发请求很高的时候,会创建出海量的goroutine来并发处理请求。
这一步,调用newConn
函数,会创建一个conn
结构体的连接实例,其中的server
成员变量是Server
实例,即每个connection
实例都保留了指向server
的信息。
func (srv *Server) Serve(l net.Listener) error {
for {
// 循环读取连接请求
rw, err := l.Accept()
// 创建一个新的连接实例 c := &conn{server: srv, rwc: rw}
c := srv.newConn(rw)
// 启动一个新的goroutine去执行处理
go c.serve(connCtx)
}
}
step3
接着进入每个connection
处理的goroutine,看具体做了什么工作:先调用connection
的readRequest
方法构造一个response
对象, 然后执行serverHandler{c.server}.ServeHTTP(w, w.req)
进行实际的请求处理。
func (c *conn) serve(ctx context.Context) {
for{
...
// 调用connection的readRequest函数构造一个response对象
w, err := c.readRequest(ctx)
...
req := w.req
...
// serverHandler{c.server}.ServeHTTP(w, w.req)进行实际的请求处理
serverHandler{c.server}.ServeHTTP(w, w.req)
}
}
step4
serverHandler
是一个结构体,有一个成员属性srv *Server
,结合step 3可以看出,serverHandler{c.server}.ServeHTTP(w, w.req)
就是实例化了一个serverHandler
结构体,然后执行其成员方法 ServeHTTP
,在ServeHTTP
成员方法的定义中,可以看到,主要是调用了初始化server
时的Handler
成员方法,执行handler.ServeHTTP(rw, req)
进行调用。
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)
}
2、http.Handler
// Handler 接口
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
任何结构体,只要实现了ServeHTTP方法,这个结构就可以称之为Handler对象。
代码示例:
package main
import (
"net/http"
)
type a struct{}
func (*a) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world"))
}
func main() {
// 从go的源码中可以发现函数的第二个参数是一个Handler对象,a实现了Handler的方法
// a就可以看做是一个Handler对象传入。
http.ListenAndServe(":8080", &a{})
}
运行程序
访问http://localhost:8080/
,页面上显示hello world
3、http.HandleFunc
http.HandleFunc注册路由,从源码中可以看到,http.HandleFunc的功能是向http包的DefaultServeMux加入新的处理路由。
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
4、http.DefaultServeMux
DefaultServeMux
是一个 ServeMux
结构体的实例。
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
5、http.ServeMux
ServeMux
是一个结构体,其中有个m
属性是一个map
类型,存放了不同 URL pattern
的处理Handler
。同时ServeMux
也实现了 ServeHTTP
函数,是 http.Handler
接口的实现。
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
hosts bool
}
type muxEntry struct {
h Handler
pattern string
}
// ServeMux实现了Handler接口
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)
}
DefaultServeMux
调用 ServeMux
的 HandleFunc
方法
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
接着调用 ServeMux
的 Handle
方法向 ServeMux
结构体的 m
字段写入信息 map[pattern] = muxEntry{h: handler, pattern: pattern}
。
func (mux *ServeMux) Handle(pattern string, handler Handler) {}
四、go语言http的其他实现方式
1、自定义server
创建一个Server对象,可以设置server的其他参数。
package main
import (
"net/http"
"time"
)
type a struct{}
func (*a) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world"))
}
func main() {
// 创建一个新的Server对象
s := http.Server{
// 监听端口
Addr: "localhost:8080",
// 处理路由
Handler: &a{},
// 设置超时
ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second,
}
s.ListenAndServe()
}
2、使用自定义的serveMux
package main
import (
"net/http"
)
type a struct{}
func (*a) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world"))
}
func index(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("index"))
}
func page(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("page"))
}
func main() {
mux := http.NewServeMux()
// 通过Handle将a这个实现Handler接口的结构体,注册到map表中
mux.Handle("/", &a{})
//通过HandleFunc方法去向mux中handler中注册处理函数
//其底层仍然是通过mux.Handle方法实现的
mux.HandleFunc("/index", index)
// 直接调用Handle方法去注册处理函数,
// 这里的http.HandleFunc 是一个函数类型,这里将page转换为一个Handler接口的实现
mux.Handle("/page", http.HandlerFunc(page))
mux.HandleFunc("/home", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("home"))
})
http.ListenAndServe("localhost:8080", mux)
}
五、http的执行过程
1、传入自己实现Handler接口的对象
http.ListenAndServe("localhost:8080", &a{})
第二个参数传入的是自己实现Handler接口的对象,http的server会调用这个对象的 ServeHTTP(w, r)
成员方法,实现 request
和 response
示例代码:
package main
import (
"fmt"
"net/http"
)
type a struct{}
func (*a) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.URL.Path)
switch r.URL.Path {
case "/":
w.Write([]byte("<h1>hello world</h1>"))
case "/index":
w.Write([]byte("<h1>index</h1>"))
case "/home":
w.Write([]byte(`<div style="color:red;font-size: 20px;">This is home page</div>`))
default:
w.Write([]byte(`<h1 style="color:red">404 NOT FOUND</h1>`))
}
}
func main() {
http.ListenAndServe("localhost:8080", &a{})
}
2、传入自定义的ServeMux或默认的DefaultServeMux
http.ListenAndServe("localhost:8080", mux)
第二个参数传入的是自定义的ServeMux或默认的DefaultServeMux(就是传入 nil
),http的server会调用ServeMux的 ServeHTTP(w, r)
成员方法,实现 request
和 response
,详细步骤如下(不完全摘录go源码):
step1:
调用 ServeHTTP(w, r){}
实现 request
和 response
,先调用 mux.Handler(r)
方法返回一个Handler对象,调用其 ServeHTTP(w, r)
方法
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
step2:在调用 mux.Handler(r *Request)
方法时,是接着调用
mux.handler(host, r.URL.Path)
方法
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
return mux.handler(host, r.URL.Path)
}
step3:在调用 mux.handler(host, path string)
方法,是接着调用mux.match(path)
方法
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
// Host-specific pattern takes precedence over generic ones
if mux.hosts {
h, pattern = mux.match(host + path)
}
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
h, pattern = NotFoundHandler(), ""
}
return
}
step4: 在调用 mux.match(path)
方法时,返回 mux.m[path].h
一个Handler对象,其实就是注册的路由器处理函数,并且和 r.URL.Path
相匹配,返回的 mux.m[path].pattern
就是路由地址 r.URL.Path
。
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
v, ok := mux.m[path]
if ok {
return v.h, v.pattern
}
return nil, ""
}
step5:回到step1调用 mux.m[path].h
的 ServeHTTP(w, r)
方法。
参考文章
https://my.oschina.net/u/943306/blog/151293
https://studygolang.com/articles/16179