阅读 93

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

文章分类
后端
文章标签