文章目录
源代码阅读
阅读源代码是学习Go语言的必备技能。我们知道,用Go语言,只需要几行代码就可以实现一个功能强大的http服务器。背后依赖的是强大的net/http库,下面我们来了解以下net/http库源码的实现原理吧。
net/http库源码分析
HTTP
我们知道,HTTP网络中有客户端(clinet)和服务端(server),分别用于发送请求(request)和作出回应(response)。在这一过程中,路由器(router)发挥重要的作用,路由器中由Multiplexer器实现路由选择,它的目标是找到合适的处理器(handler),并由处理器对消息进行处理、构建response。
- 客户端发送一条请求的流程如下:
Clinet -> Requests -> [Multiplexer(router) -> handler -> Response -> Clinet
可见,理解http服务的关键在于理解Multiplexer和handler。
Go语言中的Multiplexer基于serverMux结构,同时实现了Handler接口。
Handler
Golang中定义如下的接口去声明具有签名函数的Handler处理器:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
实现了ServerHTTP方法的结构称之为handler对象
ServeMux
ServeMux的源码如下:
type ServeMux struct {
mu sync.RWMutex //并发机制的锁
m map[string]muxEntry
hosts bool //是否带有host信息
}
type muxEntry struct {
explicit bool //表示是否精确匹配
h Handler //该路由表达式对应的handler
pattern string //匹配字符串
}
m是一个map,其中key是url模式,value是muxEntry结构(存储了具体的url模式和handler)
Server
Server的结构如下:
type Server struct {
Addr string
Handler Handler
ReadTimeout time.Duration
WriteTimeout time.Duration
TLSConfig *tls.Config
MaxHeaderBytes int
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
ConnState func(net.Conn, ConnState)
ErrorLog *log.Logger
disableKeepAlives int32 nextProtoOnce sync.Once
nextProtoErr error
}
Server结构存储了服务器处理请求常见的字段,其中Handler字段保留了Handler接口。
创建HTTP服务
创建一个http服务,大致需要两个步骤:一是注册路由(即提供url模式和handler函数的映射);二是实例化一个server对象,并开启对客户端的监听。
注册路由
gohttp的注册路由代码:
http.HandleFunc("/", indexHandler)
http.HandleFunc选取DefautServeMux作为multiplexer:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
也可以通过NewServeMux方法创建一个ServeMux实例:
// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
HandlerFunc是一个函数类型,同时实现了Handler接口的ServeHTTP方法。使用HandlerFunc类型包装一下路由定义的indexHandler函数,其目的就是为了让这个函数也实现ServeHTTP方法:
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
开启监听
注册好路由后,启动web服务还需要开启服务器监听:
http.ListenAndServe("127.0.0.1:8000", nil)
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler} //创建Server对象
return server.ListenAndServe() //调用Server对象的ListenAndServe方法
}
func (srv Server) ListenAndServe() error {
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(tcpKeepAliveListener{ln.(net.TCPListener)})
}
处理请求
监听开启后,go程序就可以处理客户端的请求。主要的处理逻辑在ServeHTTP{c.server}.ServeHTTP(w, w.req)中:
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)
}
路由ServeMux的ServeHTTP方法会根据当前请求的信息来查找最匹配的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) //规范化请求的路径格式,查找最匹配的Handler
h.ServeHTTP(w, r)
}
开发web服务程序
框架选择
本次选择的web开发框架是Martini,使用Go的net/http接口开发
代码
- main.go 基本沿用潘老师博客中的代码,依次完成绑定端口、解析端口、启动server
package main
import (
"os"
"cloudgo/service"
flag "github.com/spf13/pflag"
)
const (
//使用端口9000
PORT string = "9000"
)
func main() {
port := os.Getenv("PORT")
if len(port) == 0 {
port = PORT
}
//端口解析
pPort := flag.StringP("port", "p", PORT, "PORT for httpd listening")
flag.Parse()
if len(*pPort) != 0 {
port = *pPort
}
//启动server
service.NewServer(port)
}
- server.go使用martini框架中的格式,具体定义了启动server后的操作:
package service
import (
"github.com/go-martini/martini"
)
func NewServer(port string) {
m := martini.Classic()
m.Get("/", func(params martini.Params) string {
return "hello world"
})
m.RunOnAddr(":"+port)
}
服务器测试
- go run 运行main.go
- 在浏览器中访问 localhost:9000
curl测试
curl -v http://localhost: 9000
测试:
ab抗压测试
./ab -n 1000 -c 100 http://localhost: 9000/
测试:
程序源代码
参考博客:
Golang构建HTTP服务(一)— net/http库源码笔记
【服务计算】开发web服务程序