golang实现简易http服务器以及关键函数分析

简易HTTP服务器的实现

先看一个使用net/http包实现的简单服务器程序示例。

package main

import (
    "io"
    "log"
    "net/http"
)

type myHandler struct{}

func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "I'm home page")
}

func sayHello(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "hello world!")
}

func main() {
    // 设置路由
    http.Handle("/", &myHandler{})
    http.HandleFunc("/hello", sayHello)
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Fatal(err)
    }
}

运行程序,打开浏览器,访问http://localhost:8080,可以看到浏览器上显示I'm home page,访问http://localhost:8080/hello,可以看到hello world!。就是如此简单,但是程序背后做了哪些事呢?

分析

http.Handle主要是设置路由规则,先看它的具体实现吧:

// Handle registers the handler for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func Handle(pattern string, handler Handler) { 
    DefaultServeMux.Handle(pattern, handler) 
}

可以看到两个不认识的东西,HandlerDefaultServeMux。继续查看定义:

// Handler是一个接口
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

// ServeMux是一个自定义类型
// ServeMux also takes care of sanitizing the URL request path,
// redirecting any request containing . or .. elements or repeated slashes
// to an equivalent, cleaner URL.
type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry //模式字符串与处理器的映射关系
    hosts bool // whether any patterns contain hostnames
}

// muxEntry 是一个自定义类型,包括一个模式字符串和对应的Handler接口
type muxEntry struct {
    h       Handler
    pattern string
}

var defaultServeMux ServeMux
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

DefaultServeMux*ServeMux类型,指向defaultServeMux这个内部变量。ServeMux结构体的含义是路由器,它的成员m比较重要,存储的是模式字符串与处理器的映射关系。*ServeMux实现了Handle函数,主要用途就是填充成员m的,具体代码如下:

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()
    defer mux.mu.Unlock()
    // 错误判断,模式字符串为空、处理起handler为空、对应的模式字符串的处理器已经存在
    if pattern == "" {
        panic("http: invalid pattern")
    }
    if handler == nil {
        panic("http: nil handler")
    }
    if _, exist := mux.m[pattern]; exist {
        panic("http: multiple registrations for " + pattern)
    }

    // 如果没有实例化,就make一下
    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    mux.m[pattern] = muxEntry{h: handler, pattern: pattern}

    if pattern[0] != '/' {
        mux.hosts = true
    }
}

可以看到前面有三个错误判断,这个不影响理解,重要的是mux.m[pattern] = muxEntry{h: handler, pattern: pattern},将传递进来的处理器handler和模式字符串pattern组合成muxEntry结构体,使用map结构绑定patternmuxEntry之间的关系。

综上所述,http.Handle干了什么呢?主要就是操作DefaultServeMux这个结构体变量,填充它的成员m(map[string]muxEntry类型),建立路由规则。通俗一点,就是为每个url模式串注册了一个回调函数(go语言中称之为处理器Handler)!

接下来的http.ListenAndServe函数,具体实现如下:

// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

这一段代码不追究细节还是很好理解的,第一个参数是端口号,第二个参数是具体的处理器(该示例中传入nil),这个函数就是先创建一个具体的Server对象,然后调用ListenAndServe(),不看具体实现,也能猜到,就是先listen(监听),再建立一个事件循环,监听到了连接事件或者读写事件发生,就启动一个协程进行处理。

问题是,传递进去的handlerServeHTTP方法再什么时候调用的呢?继续翻源码,server.ListenAndServe()主要分为两个步骤,1.处理新连接,即调用Accpet函数。2.处理其他事件,代码中主要体现在go c.serve(ctx)。而在c.serve函数中找到了这句话:serverHandler{c.server}.ServeHTTP(w, w.req)serverHandler类型其实就是重新包装了*Server类型,如下:

// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
type serverHandler struct {
    srv *Server
}

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    // 重点, 取出内部的Handler接口变量
    handler := sh.srv.Handler
    // 如果是空,就是用默认的Handler
    if handler == nil {
        handler = DefaultServeMux
    }
    if req.RequestURI == "*" && req.Method == "OPTIONS" {
        handler = globalOptionsHandler{}
    }
    // 调用 ServeHTTP 方法!
    handler.ServeHTTP(rw, req)
}

serverHandlerServeHTTP方法中调用了它的成员Handler(最开始由你传递进来的)的ServeHTTP方法(这就是你自定义的处理器实现的ServeHTTP的作用所在了)。本示例中的传入的Handlernil,所以最后使用的是DefaultServeMuxServeHTTP方法,而DefaultServeMux*ServeMux类型,它的ServeHTTP方法实现如下:

// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
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)
}

// handler is the main implementation of Handler.
// The path is known to be in canonical form, except for CONNECT methods.
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
}

mux.Handler函数中调用了mux.match函数,mux.match函数主要是在mux.m这个之前注册好的map(字典)中查找url的对应处理器。根据之前定义的路由规则找到了对应的处理器之后,就调用处理器的ServeHTTP方法。

所以,在访问http://localhost:8080时,执行的是myHandler自定义类型的ServeHTTP方法,返回浏览器I'm home page字符串,并在浏览器上显示出来。

http.HandleFunc的用法和http.Handle一样,但是第二个参数是func(ResponseWriter, *Request)这个函数类型而不是Handler接口,由上面的分析可知,ServeMux的成员m存储要求是个Handler接口,那么传入个函数怎么处理呢?见源码:

// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    mux.Handle(pattern, HandlerFunc(handler))
}

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

DefaultServeMux使用了HandleFunc方法,该方法将函数类型的handler强行转换成了自定义类型HandlerFunc类型,该类型实现了Handler接口,也就使得传入的函数也实现了Handler接口,在ServeHTTP方法中,直接调用自身代表的函数。很是巧妙!

这些就是net/http包背后默默帮你做的事情。

自定义路由器

下一个问题,如果不使用DefaultServeMux呢,能否自己定义一个ServeMux(路由器)?答案是可以的,将简单的HTTP服务器修改一下:

package main

import (
    "io"
    "log"
    "net/http"
)

type myHandler struct{}

func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "I'm home page")
}

func sayHello(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "hello world")
}

func main() {
    mux := http.NewServeMux() // new出一个新的ServeMux对象

    mux.Handle("/", &myHandler{})
    mux.HandleFunc("/hello", sayHello)
    err := http.ListenAndServe(":8080", mux) //传入自定义的路由器(mux)
    if err != nil {
        log.Fatal(err)
    }
}

与文章开头不同的地方在于,使用http.NewServeMux()创建了一个ServeMux对象,然后调用mux.Handle注册路由,最后在http.ListenAndServe中传入自定义的路由器。

更进一步,能否不使用ServeMux结构体呢?我们可以直接使用http.ListenAndServe中使用的Server结构,并且自己处理路由分发(自己定义字典存储路由规则)。

package main

import (
    "io"
    "log"
    "net/http"
    "time"
)

// 用字典实现路由器
var mux map[string]func(http.ResponseWriter, *http.Request)

// 自己实现的Handler
type myHandler struct{}

func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // 根据url选择对应的处理函数
    if h, ok := mux[r.URL.String()]; ok {
        h(w, r)
        return
    }
    io.WriteString(w, "sorry, error happened!")
}

func sayHello(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "hello world")
}

func sayHome(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "I'm home page")
}

func main() {
    server := http.Server{
        Addr:        ":8080",
        Handler:     &myHandler{},
        ReadTimeout: 5 * time.Second, //设置超时时间
    }

    mux = make(map[string]func(http.ResponseWriter, *http.Request))
    mux["/"] = sayHome
    mux["/hello"] = sayHello
    err := server.ListenAndServe()
    if err != nil {
        log.Fatal(err)
    }
}

参考

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • 【回顾盘面】: 10月24日(周二)两市小幅低开,之后沪指在持续横盘整理至中午收盘,而创业板则一路单边下行;午后沪...
    齐利阅读 294评论 0 21
  • 默默中 我回味着那动人心眩的眼神 匆匆里 我追忆着那梦幻的眼神 执着而坚定的眼神 给人一种顽强的信念 又似激情飞越...
    婉_霞阅读 358评论 3 11
  • 【学习内容】 1、阅读《25岁,规划你的人生》50min (完) 2、参加我们产业园主办的互联网+时代人才战略论坛...
    暖暖的魔羯麻麻阅读 165评论 0 0