golang实现一个简单的http server
如果搜索golang http server,会发现网上有很多不同的写法,本节将介绍多种写法,并把他们的关系捋清楚。
写法1
直接传入函数
func SayHello(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello"))
}
func main() {
http.HandleFunc("/say_hello", SayHello)
err := http.ListenAndServe(":12345", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
复制代码
写法2
定义结构体,需要实现ServerHTTP方法。
type ValueHandler struct {}
func (p *ValueHandler) ServerHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("value-pretty"))
}
func main() {
http.Handle("/get_value", &ValueHandler)
err := http.ListenAndServe(":12345", nil)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
复制代码
1,2两种写法极其相似,区别在于写法2需要一个结构体,并且必须实现ServerHTTP这个接口。1其实是对2的简化,相当于在net/http帮大家定义了一个实现了ServerHTTP的函数定义。
- http.HandleFunc 注册了路由关系
- http.ListenAndServe表示启动了一个服务。
写法3
显示使用Mux,Mux即multiplexor,保存了路由和方法的映射。可以记录多个url和handler的对应关系。
func SayHello(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello"))
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/say_hello", SayHello)
err := http.ListenAndServe(":12345", mux)
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
复制代码
这里可能会问,http.HanlderFunc没有使用mux,也可以注册多路映射。实际是底层有DefaultMux,在http Server中若没有传递,即用默认的。以下是http.HanlderFunc定义:
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
var DefaultServeMux = &defaultServeMux
var defaultServeMux ServeMux
复制代码
写法4
使用server.ListenAndServe
func SayHello(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello"))
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/say_hello", SayHello)
server := http.Server{
Addr: ":12345",
Handler: serveMux,
ReadTimeout: 5 * time.Second,
}
err := server.ListenAndServe()
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
复制代码
和写法3的区别,在于监听函数http.ListenAndServe,替换为了server.ListenAndServe 。其实Server结构体是必然存在的,http.ListenAndServe只是做了封装,实际还是生成了一个Server字段,默认填了addr, handler两个参数。以下是http.ListenAndServe定义
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
复制代码
显示指定http.Server,可以灵活选择更多配置。比如超时时间。
小结
四种写法本质上是一样的,建议使用最完整的方法4。 因为其能提供更完整,灵活的配置,同时也并不复杂。
流程分析
通过对http包的分析之后,现在让我们来梳理一下整个的代码执行过程。
HandleFunc 流程
如果是Http.HandleFunc,首先会调用调用了DefaultServeMux的HandleFunc。
进入实际流程:
- 调用了DefaultServeMux的Handle
- 往路由映射表map[string]muxEntry,添加对应的handler和路由规则
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
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
}
复制代码
ListenAndServe流程
若http.ListenAndServe(":9090", nil),首先实例化Server
server := &Server{Addr: addr, Handler: handler}
type Server struct {
Addr string // TCP address to listen on, ":http" if empty
Handler Handler // handler to invoke, http.DefaultServeMux if nil
TLSConfig *tls.Config
ReadTimeout time.Duration
ReadHeaderTimeout time.Duration
WriteTimeout time.Duration
IdleTimeout time.Duration
MaxHeaderBytes int
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
ConnState func(net.Conn, ConnState)
ErrorLog *log.Logger
disableKeepAlives int32 // accessed atomically.
inShutdown int32
nextProtoOnce sync.Once // guards setupHTTP2_* init
nextProtoErr error
mu sync.Mutex
listeners map[*net.Listener]struct{}
activeConn map[*conn]struct{}
doneChan chan struct{}
onShutdown []func()
}
复制代码
Server结构比较复杂,关注ReadTimeout,WriteTimeout,Handler几个常用字段即可。
实际流程:
- 调用Server的ListenAndServe()
- 调用net.Listen("tcp", addr)监听端口,拿到Listen的实例
- srv.Serve 传入上面的Listen示例
- Accept请求,每收到一个请求,开启一个协程处理
- 协程中会根据路由和handler的映射,选择handler来处理请求
处理函数如何拿到参数
the http.Request 包含了请求的所有信息,我们可以从中拿出请求的参数:
- 针对Get 请求: vars := r.URL.Query(); vars["username"][0]
- 针对Post 表单请求,r.ParseForm(); r.Form("username")
- 针对Post json请求: 解析r.Body
// deal get
func SayHello(w http.ResponseWriter, r *http.Request) {
vars := r.URL.Query()
username := vars["username"][0]
io.WriteString(w, "hello world " + username)
}
// 处理表单
func SayHello(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
username, err := request.Form["username"]
io.WriteString(w, "hello world " + username)
}
//deal json
type User struct {
UserName string
}
func SayHello(w http.ResponseWriter, r *http.Request) {
err := json.NewDecoder(r.Body).Decode(&u)
if err != nil {
http.Error(w, err.Error(), 400)
return
}
io.WriteString(w, "hello world " + u.Username)
}
复制代码
中间件抽象
如果需要计算接口处理时间,那么我们可以这么写
func SayHello(w http.ResponseWriter, r *http.Request) {
timeStart := time.Now()
err := json.NewDecoder(r.Body).Decode(&u)
if err != nil {
http.Error(w, err.Error(), 400)
return
}
io.WriteString(w, "hello world " + u.Username)
timeElapsed := time.Since(timeStart)
fmt.Println(timeElapsed)
}
复制代码
存在一个问题,就是如果有二十个接口,那同样的代码得写20次,当有类似需求增加,或者修改时。需要改几十处。这些同样的代码,我们要想办法抽象起来。
func timeMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(wr http.ResponseWriter, r *http.Request) {
timeStart := time.Now()
// next handler
next.ServeHTTP(wr, r)
timeElapsed := time.Since(timeStart)
fmt.Println(timeElapsed)
})
}
复制代码
如此就可以通过中间件的思想,抽象出公用代码。而且这种中间件思想,还可以嵌套使用。拥有很强的扩展性。
更优雅的写法
// 示例来自《go语言高级编程》
r = NewRouter()
r.Use(timeMiddleware)
r.Use(ratelimitMiddleware)
r.Add("/", SayHello)
复制代码
type middleware func(http.Handler) http.Handler
type Router struct {
middlewareChain [] middleware
mux map[string] http.Handler
}
func NewRouter() *Router{
return &Router{}
}
func (r *Router) Use(m middleware) {
r.middlewareChain = append(r.middlewareChain, m)
}
func (r *Router) Add(route string, h http.Handler) {
var mergedHandler = h
for i := len(r.middlewareChain) - 1; i >= 0; i-- {
mergedHandler = r.middlewareChain[i](mergedHandler)
}
r.mux[route] = mergedHandler
}
复制代码
web框架
在实际开发中,基本上都会用到一些完善的框架,比如gin, beego, echo等。 均提供了比较方便,简单的编程架子。并且拥有良好的程序结构。 后续会针对其中一些进行单独介绍。
有疑问加站长微信联系(非本文作者)