【go语言学习】网络编程之HTTP

一、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,看具体做了什么工作:先调用connectionreadRequest方法构造一个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 调用 ServeMuxHandleFunc 方法

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    mux.Handle(pattern, HandlerFunc(handler))
}

接着调用 ServeMuxHandle 方法向 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) 成员方法,实现 requestresponse

示例代码:

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) 成员方法,实现 requestresponse ,详细步骤如下(不完全摘录go源码):

step1:
调用 ServeHTTP(w, r){} 实现 requestresponse ,先调用 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].hServeHTTP(w, r) 方法。

参考文章
https://my.oschina.net/u/943306/blog/151293
https://studygolang.com/articles/16179

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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