Go http 包详解

Go 语言中的 http 包提供了创建 http 服务或者访问 http 服务所需要的能力,不需要额外的依赖。在这篇文章中,我们会介绍这些功能的使用,以及看一下 http 包的设计思路。

1. http 的客户端

1.1 发送普通请求

在 Go 语言中发送请求很简单,如果不需要额外的配置,可以直接使用 http 包封装的 http Client 发送请求,比如发送 GET 请求:

resp, _ := http.Get("https://golang.org")
defer resp.Body.Close()

发送 POST ,并携带 JSON 数据的请求:

data := make(map[string]string)
dataJson, _ := json.Marshal(data)
reader := bytes.NewBuffer(dataJson)

resp, _ := http.Post("https://golang.org", "application/json;charset=utf-8", reader)
defer resp.Body.Close()

发送 POST 表单请求:

resp, _ := http.PostForm("https://golang.org", url.Values{"username":{"rayjun"}, "password":{"password"}})
defer resp.Body.Close()

在每个请求发完之后,需要手动关闭响应。

1.2 客户端配置

在实际使用的过程中,我们通常不会直接上面的方法,而是会自己做一些 Client 的配置,比如调整超时时间:

client := &http.Client{
    Timeout: 5 * time.Second,
}
resp, _ := client.Get("https://golang.org")
defer resp.Body.Close()

另外在很多时候,我们需要使用 GET 和 POST 之外的 http 方法,那就需要下面这样的配置:

client := &http.Client{
    Timeout: 5 * time.Second,
}

req, _ := http.NewRequest("PUT", "https://golang.org", nil)
resp, _ := client.Do(req)
defer resp.Body.Close()

比如还需要在请求的 Header 中增加一些字段:

client := &http.Client{
    Timeout: 5 * time.Second,
}
req, _ := http.NewRequest("GET", "https://golang.org", nil)
req.Header.Add("User-Id", "userid123456")
resp, _ := client.Do(req)
defer resp.Body.Close()

或者更进一步,我们需要自定义传输层的一些配置:

tr := &http.Transport{
    MaxIdleConns:       10,
    IdleConnTimeout:    30 * time.Second,
    DisableCompression: true,
}
client := &http.Client{Transport: tr}
resp, _ := client.Get("https://golang.org")
defer resp.Body.Close()

http 包中发送请求,提供了不同层次的配置,满足不同场景的使用。

2. http 的服务端

除了客户端,使用 http 包来创建 http 服务也很方便。

2.1 一行代码创建 http 服务

创建一个 http 服务,在 Go 代码中,只需要一行代码:

func main() {
    http.ListenAndServe(":8080", nil)
}

在 main 方法中,写下上面那行代码,然后运行 main 方法,端口号为 8080 的 http 服务就运行起来了, 但目前还处理不了任何请求。

2.2 添加请求路径

在上面代码的基础上,需要添加一个路径,这样服务才可以开始处理请求:

func main() {
  http.Handle("/index", &CustomerHandler{})
    http.ListenAndServe(":8080", nil)
}

type CustomerHandler struct {

}

func (c *CustomerHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
    fmt.Println("implement http server by self")
    writer.Write([]byte("server echo"))
}

添加了 /index 路径,在这种方式下,需要为每一个请求都定义一个 Handler,然后 Handler 需要实现 ServeHttp 方法。

Handler 是一个请求处理器,我们如果使用这种方式,就需要为每一个请求的 url 实现一个 Handler,这样实现很繁琐。

但我们还有另一个选择,就是使用 HandlerFunc,添加另外一个路径:

func main() {
    http.HandleFunc("/index", func(writer http.ResponseWriter, request *http.Request) {
        writer.Write([]byte("HandleFunc implement"))
    })
    http.ListenAndServe(":8080", nil)
}

使用这种方式很简洁,值需要实现 HandlerFunc 类型的一个匿名方法就可以了,HandlerFunc 是一个适配器,可以让我们把一个与 ServeHTTP 签名相同的函数作为一个处理器。

Handler 和 HandlerFunc 都是通过 DefaultServeMux 来实现的。 DefaultServeMux 才是上面服务的核心。

在上面的代码,http.ListenAndServe 的第二个参数传入的是 nil,通常情况下,这个参数都是 nil,跟进代码,发现这个参数为 nil 的时候,就是使用 DefaultServeMux 来作为服务端的实现:

// server.go
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)
}

DefaultServeMux 的类型是 ServeMux,这是 Go 语言原生包中 http 服务端的默认实现。ServeMux 同样实现了ServeHttp 这个方法。

ServeHttp 方法才是整个 http 服务的核心,只要需要处理请求,就必须实现这个方法。Handler 和 HandlerFunc 只是 Go 语言提供的两种实现。

3. http 的反向代理

反向代理在开发 Web 应用,特别是开发网关类应用的时候会经常用到, Go 也提供了实现,基本上开箱即用。

func main() {
    http.HandleFunc("/formawd", func(writer http.ResponseWriter, request *http.Request) {
        director := func(req *http.Request) {
            req.URL.Scheme = "https"
            req.URL.Host = "golang.org"
            req.URL.Path = "upload"
        }

        proxy := &httputil.ReverseProxy{Director: director}
        proxy.ServeHTTP(writer, request)
    })
    
    http.ListenAndServe(":8080", nil)
}

上面的代码会把所有的请求都转发到一个地方,当然也可以通过配置,将请求转发到不同的地方。

4. 小结

Go 语言原生的包就自带了 http 包,这个包提供 http 编程所需要的基础能力,开箱即用,不需要额外的依赖。在实际项目中使用,做个简单的封装即可。而且还自带反向代理的能力,可以很方便的写出一个 API 网关。

文 / Rayjun

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

推荐阅读更多精彩内容

  • 前面文章我们已经实现了一个简单的Web服务。现在我们详细解剖http包,分析内部实现的细节。 1.1 http包中...
    77fbb8bc241c阅读 5,699评论 0 4
  • Go 语言net/http 包使用模式 这篇文章的内容非常基础,也非常容易理解。感觉是最能清晰的讲述了net/ht...
    我是GGY阅读 436评论 0 1
  • 在Go里你只需以下几行代码就能实现一个http服务是的没错,不需要容器,不需要服务器软件。 可以看到,是http....
    GGBond_8488阅读 825评论 0 2
  • 概述 一个典型的 Go Web 程序结构如下,摘自《Go Web 编程》: 客户端发送请求; 服务器中的多路复用器...
    darjun阅读 259评论 0 0
  • 在Go中使用及其简单的代码即可开启一个web服务。如下: 在使用ListenAndServe这个方法时,系统就会给...
    fou7阅读 2,381评论 0 9