go net包分析二(net/http)

net/http包也是net包中的,是在net tcp协议基础上实现的http协议。底层调用的方法也是基于net dial里面的那几个方法,当然不止于这些,下面大致从http服务器创建开始说起。

一、创建http服务
先简单看下代码:

func main()  {
    http.HandleFunc("/", func(w http.ResponseWriter, request *http.Request) {
        fmt.Println("Hello World.")
        fmt.Fprintf(w, "Hello World.\n")
    })
    err := http.ListenAndServe("0.0.0.0:8899", nil)
    if err != nil {
        fmt.Println("http listen failed.")
    }
}

简单几行代码就搭建了一个http服务,在浏览器通过http://127.0.0.1:8899 访问一下就可以看到结果。是不是很简单,只需要两个方法,一个ListenAndServe 方法,它的作用就是启动服务端监听端口,监听8899端口过来的请求,请求过来以后怎么处理,就要看HandleFunc方法,这个是对请求过来后访问的路径绑定处理函数。为了简单就只有一个路径,如果想添加多个访问uri,就在多加几个HandleFunc即可。

HandleFunc 里面传递了两个参数w http.ResponseWriter, request *http.Request
这两个是什么,我们看下它们的介绍

// A ResponseWriter interface is used by an HTTP handler to
// construct an HTTP response.
type ResponseWriter interface {
    Header() Header
    Write([]byte) (int, error)
    WriteHeader(statusCode int)
}

type Request struct {
    Method string
    URL *url.URL
    Proto      string // "HTTP/1.0"
    ProtoMajor int    // 1
    ProtoMinor int    // 0
    Header Header
    Body io.ReadCloser
    GetBody func() (io.ReadCloser, error)
    ContentLength int64
    TransferEncoding []string
    Close bool
    Host string
    Form url.Values
    PostForm url.Values
    MultipartForm *multipart.Form
    Trailer Header
    RemoteAddr string
    RequestURI string
    TLS *tls.ConnectionState
    Cancel <-chan struct{}
    Response *Response
    ctx context.Context
}

做了下简化处理,毕竟原文注释太多太长了,从官方给出的说明可以看到,一个是进行请求数据处理的,一个是相应数据处理的。ResponseWriter是个接口,只要实现了这个接口就可以,net/http/server.go 文件里面有对应的实现方法,可以自己看下,而request是个结构体变量。我们都知道变量需要初始化赋值才可用,但从示例里面似乎没看到在哪赋值,直接HandleFunc 拿来就可以用了,那么赋值这一步操作在哪呢,只有两个方法,一个是调用,那么初始化这一步只可能在ListenAndServe方法里了,我们进入ListenAndServe方法里面看看。

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

这里面没有,只是初始化了服务器变量,我们继续进入 server.ListenAndServe()

return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})

里面也没有,为了简化只贴出有用的代码,我们继续往下看。

for {
        rw, e := l.Accept()
        if e != nil {
            select {
            case <-srv.getDoneChan():
                return ErrServerClosed
            default:
            }
            if ne, ok := e.(net.Error); ok && ne.Temporary() {
                if tempDelay == 0 {
                    tempDelay = 5 * time.Millisecond
                } else {
                    tempDelay *= 2
                }
                if max := 1 * time.Second; tempDelay > max {
                    tempDelay = max
                }
                srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
                time.Sleep(tempDelay)
                continue
            }
            return e
        }
        tempDelay = 0
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx)

这里面代码就比较长了,我们只看有用的几个
1、rw, e := l.Accept() //获取一个连接请求,
2、c := srv.newConn(rw) //连接初始化一下
3、 go c.serve(ctx) //启动goroutine 来处理请求
从上一篇net dial文件分析,我们知道获取一个连接以后,就可以通过连接来读取数据,又因为这是http服务,为了处理多个连接,所以go采用的是IO多路复用模式 ,用goroutine来处理请求,我们继续往下看。

for {
        w, err := c.readRequest(ctx)
                ...
        ...
        serverHandler{c.server}.ServeHTTP(w, w.req)
                ...  
        w.cancelCtx()
        w.finishRequest()
        ...
    }

原方法太长做了简化处理,具体代码可自行查看,我们只看几个主要的地方
1、 w, err := c.readRequest(ctx)
2、 serverHandler{c.server}.ServeHTTP(w, w.req)
第一个方法就是初始化request变量的方法,在这个方法里初始化了request变量和response变量

func (c *conn) readRequest(ctx context.Context) (w *response, err error) 
req, err := readRequest(c.bufr, keepHostHeader)
func readRequest(b *bufio.Reader, deleteHostHeader bool) (req *Request, err error)

这就是整个初始化request过程,通过这个方法,最终得到了Request变量,里面包含http请求的参数,如head头,url,cookie等信息。传递到HandleFunc里面,就可以直接拿来使用了。
说到这里,还要再说个地方,我们知道调用HandleFunc 方法就可以把url请求绑定到处理方法上,那么绑定以后为什么访问url的时候自动就会调用绑定方法,就在于上面说的2:serverHandler{c.server}.ServeHTTP(w, w.req) 这里,

type HandlerFunc func(ResponseWriter, *Request)

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

可以看到只要找到了url对应的绑定方法,就会调用,从而就实现了整个http服务器流程。type Handler interface {
ServeHTTP(ResponseWriter, Request)
}
在handler接口只要实现了ServeHTTP就可以自定义属于自己的http服务,我们熟知的Gin框架就是通过重写ServeHTTP 实现自己封装的
gin.Context结构来传递信息。

总结:go net/http包内部还实现了很多处理,包括锁,goroutine数量限制很多逻辑,代码结构也封装的很好,可以多看看学习学习。

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