零基础学goGO黑帽子学习笔记-3.1 GO的HTTP基础知识

HTTP基础

  1. HTTP是一种无状态协议,服务器不会维护没个请求的状态,而是通过多种方式跟踪其状态,这些方式可能包括会话标识符,cookie,HTTP标头等。客户端和服务器有责任正确协商和验证状态。
  2. 客户端和服务器之间的通信可以同步或异步进行,但它们需要以请求或相应的方式循环运行。可以在请求中添加几个标头,以影响服务器的行为并创建可用的Web应用程序。最常见的是服务器托管Web浏览器的渲染的文件,以生成数据的图形化、组织化和时尚化的表示形式。API通常使用XML、JSON或MSGRPC进行通信,某些情况下,检索到的数据可能是二进制形式,表示要下载的任意文件类型。
  3. Go中包含许多便捷函数,使你可以快速轻松的构建HTTP请求并将其发宋到服务器,然后检索和处理相应。

调用HTTP API

GO的net/http标准包包含多个便捷函数,可以便捷地发送POST、GET和HEAD请求。

GET(url string) (resp *Response, err errror)
Head(url string) (resp *Response, err error)
Post(url string, bodyType string, body io.Reader) (resp *Response, err error)

相较于GET和HEAD请求,POST请求需要两个额外的参数,一个是bodyType,用来接收请求正文中的Content-Type HTTP标头(通常为application/x-www-form-urlencoded),而io.Reader我们在之前已经说过。

package main

import (
	"net/http"
	"net/url"
	"strings"
)

func main() {
	r1, _ := http.Get("https://www.haochen1204.com")
	// 读取相应正文,未显示
	defer r1.Body.Close()
	r2, _ := http.Head("https://www.haochen1204.com")
	// 读取相应正文,未显示
	defer r2.Body.Close()
	form := url.Values{}
	form.Add("foo", "bar")
	r3, _ := http.Post(
		"https://www.haochen1204.com",
		"application/x-www-form-urlencoded",
		strings.NewReader(form.Encode()),
	)
	// 读取相应正文,未显示
	defer r3.Body.Close()
}

Go中还有一个可以快速发送POST请求的函数,PostForm()。我们如果使用该函数,那么就无法设置这些值和手动编码每个请求。

func PostForm(url string, data url.Values) (resp *Response, err error)

使用PostForm函数进行POST请求:

package main

import (
	"net/http"
	"net/url"
)

func main() {
	form := url.Values{}
	form.Add("foo", "bar")
	r3, _ := http.PostForm("https://www.haochen1204.com", form)
	defer r3.Body.Close()
}

生成一个请求

除去上面的方法,我们还可以使用NewRequest()函数来创建一个请求的包,然后将其发送给我们的服务器。

func NewRequest(method, url string, body io.Reader) (req *Request, err error)

其需要3个参数,首先是我们要请求的方法,其次是我们要请求的目标,最后通过io.Reader作为最后一个参数来提供请求正文。但是这样仅仅是创建了一个结构体,我们如何将其发送给我们的服务器呢,我们这里就需要另一个东西,http.Client,他存在一个方法Do()可以将我们刚刚生成好的HTTP请求发送给我们的服务器。

package main

import (
	"net/http"
	"net/url"
	"strings"
)

func main() {
	form := url.Values{}
	form.Add("foo", "bar")
	var client http.Client
	req, _ := http.NewRequest(
		"POST",
		"https://www.haochen1204.com",
		strings.NewReader(form.Encode()),
	)
	resp, _ := client.Do(req)
	defer resp.Body.Close()
}

使用结构化解析响应

在Go中,我们可以使用ioutil.ReadAll()函数从响应正文读取数据,进行一些错误检查,并将HTTP状态代码和响应正文打印到stdout中。

package main

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

func main() {
	resp, err := http.Get("https://www.haochen1204.com")
	if err != nil {
		log.Panicln(err)
	}
	// 打印HTTP状态码
	fmt.Println(resp.Status)
	// 读取并显示响应正文
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Panicln(err)
	}
	fmt.Println(string(body))
	resp.Body.Close()
}

如上代码,我们在该断代码中首先发送了一个get请求,然后将响应包赋值给参数resp,之后通过Status参数来获取响应码,通过ioutil.ReadAll对Body参数进行处理,进而得倒了响应包。

而我们需要额外知道的是,我们通过Status获取的并不仅仅是一个响应码的整数,而是包括了其后面的OK,我们如果想仅仅获取响应码,应该使用的是StatusCode参数。而后我们的Body参数,其实际上是一个io,.ReadCloser类型,用来充当io.,Reader以及io.Close的接口,或者是实现Close()函数以关闭reader并执行任何清理的接口。从io.ReadCloser读取数据后,需要在响应正文上调用Close()函数来进行关闭。

image-20220905214444290

而对于POST请求,我们经常需要使用其来获取一些json的响应,所以我们如何处理json的响应呢?假设我们现在json响应如下

{"Message":"All is good with the world","Status":"Success"}

首先,如图,我们定义了一个名为Status的结构体,其中包含服务器响应中json的元素。然后我们首先使用http.Post的方法来发送我们的Post请求,然后使用json.NewDecoder函数来解析我们的响应体中的json数据,然后通过我们上述定义的结构体中的Message和Status来获取返回中json的值。

package main

import (
	"encoding/json"
	"log"
	"net/http"
)

type Status struct {
	Message string
	Status  string
}

func main() {
	res, err := http.Post(
		"http://ip:port/ping",
		"application/json",
		nil,
	)
	if err != nil {
		log.Fatalln(err)
	}
	var status Status
	if err := json.NewDecoder(res.Body).Decode(&status); err != nil {
		log.Fatalln(err)
	}
	defer res.Body.Close()
	log.Panicf("%s -> %s\n", status.Status, status.Message)
}

其实总体来说,他和python中解析json的响应体步骤是基本一致的,首先通过请求来获取响应包,然后将响应包交给对应的json处理函数来进行处理并切得到结果,只不过在python中会将json数据直接转换为字典,而在go中需要以结构体的形式来获取其内容。当然,不仅仅是json,其他编码格式比如XML或二进制的表示形式也适用于这种方法。