【Go语言】解析处理结构化响应数据
前言
之前我们已经讲了如何通过各种技术来利用TCP协议的特性创建可用的客户端和服务器。下面我们来探讨一下OSI模型的几种上层协议,那就先从HTTP开始吧!
1、基础知识
HTTP
HTTP是一种无状态协议:服务器不会维护每个请求的状态,而是通过多种方式对其进行跟踪,这些方式可能包括会话标识符、cookie、HTTP标头等。客户端和服务器有责任正确协商和验证状态。
客户端和服务器之间的通信可以同步或异步进行,但它们要以请求/响应的方式循环进行。可以在请求中添加几个选项和标头,以影响服务器的行为并创建可用的web应用程序。最常见的是服务器托管web浏览器渲染的文件,以生成数据的图形化、组织化和时尚化的表示形式。API通常使用XML、JSON或MSGRPC进行通信。某些情况下,检索到的数据可能是二进制格式,表示要下载的任意文件类型。
调用HTTP API
Go的 net/http 标准包包含多个便捷函数,可便捷地发送POST、GET和HEAD请求,函数使用形式如下:
Get (url string) (resp *Response, err error)
Head (url string) (resp *Response, err error)
Post (url string, bodyType string, body io.Reader) (resp *Response, err error)
函数Post() 具有两个附加参数 (bodyType 和 io.Reader) ,其中bodyType用于接收正文的Content-Type HTTP标头 (通常为application/x-www-form-urlencoded)
生成一个请求
我们可以使用函数NewRequest()创建结构体Request, 然后使用函数Client 的方法 Do 发送该结构体。http.NewRequest() 的函数原型如下
func NewRequest (method, url string, body io.Reader) (req *Request, err error)
需要将HTTP动词和目标URL提供给函数NewRequest() 作为其前两个参数。可以选择通过传入io.Reader作为第三个参数来提供请求正文。下面进行演示
//没有HTTP正文的调用,发送一个DELETE请求
reg, err := http.NewRequest("DELETE", "https://www.baidu.com/robots.txt", nil)
var client http.Client
resp, err := client.Do(reg)
// 读取响应正文并关闭
//带有io.Reader正文的PUT请求(类似于PATCH请求)
form := url.Values{}
form.Add("foo", "bar")
var client http.Cilent
req, err := http.NewRequest(
"PUT"
"https://www.goole.com/robots.txt"
strings.NewReader(form.Encode)
)
resp, err := client.Do(reg)
使用结构化响应解析
我们知道,只要是执行与HTTP相关的任务,就必须检查HTTP响应的各个组成部分。包括读取响应正文、访问cookie和标头或仅检查HTTP的状态代码。下面我们就使用 ioutil.ReadAll() 函数从响应正文读取数据,进行一些错误检查,并将HTTP状态代码和响应正文打印到stdout。
//处理HTTP响应正文
resp, err := http.Get("https://www.baidu.com/robots.txt")
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()
2、处理HTTP响应正文
将上述步骤结合起来就可以得到我们的主程序
package main
import (
"fmt"
"io/ioutil"
"log"
"net/http"
"net/url"
"strings"
)
func main() {
resp, err := http.Get("https://www.baidu.com/robots.txt")
if err != nil {
log.Panicln(err)
}
//print http status
fmt.Println(resp.Status)
//read and display response body
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Panicln(err)
}
fmt.Println(string(body))
resp.Body.Close()
resp, err = http.Head("https://www.baidu.com/robots.txt")
if err != nil {
log.Panicln(err)
}
resp.Body.Close()
fmt.Println(resp.Status)
form := url.Values{}
form.Add("foo", "bar")
resp, err = http.Post(
"https://www.baidu.com?robots.txt",
"application/x-www-form-urlencoded",
strings.NewReader(form.Encode()),
)
//POST函数调用对表单数据进行URL编码时,将Content-Type设置为 application/x-www-form-urlencoded
if err != nil {
log.Panicln(err)
}
resp.Body.Close()
fmt.Println(resp.Status)
req, err := http.NewRequest("DELETE", "https://www.baidu.com/robots.txt", nil)
if err != nil {
log.Panicln(err)
}
var client http.Client
resp, err = client.Do(req)
if err != nil {
log.Panicln(err)
}
resp.Body.Close()
fmt.Println(resp.Status)
req, err = http.NewRequest("PUT", "https://www.baidu.com/robots.txt", strings.NewReader(form.Encode()))
if err != nil {
log.Panicln(err)
}
resp, err = client.Do(req)
if err != nil {
log.Panicln(err)
}
resp.Body.Close()
fmt.Println(resp.Status)
}
程序运行后,结果如下:
200 OK
User-agent: Baiduspider
Disallow: /baidu
Disallow: /s?
Disallow: /ulink?
Disallow: /link?
Disallow: /home/news/data/
Disallow: /bh
User-agent: Googlebot
Disallow: /baidu
Disallow: /s?
Disallow: /shifen/
Disallow: /homepage/
Disallow: /cpro
Disallow: /ulink?
Disallow: /link?
Disallow: /home/news/data/
Disallow: /bh
......//部分展示
需要注意的是:
我们在发送请求建立连接时,除了主goroutine外,还会新增两个goroutine, readLoop 和 writeLoop,而我们在Close之后会进行回收,如果不加Close的话,可能会造成goroutine泄露。
有兴趣的话可以看看这篇文章,讲的很清楚https://juejin.cn/post/6987372070120194055
3、解码一个JSON响应正文
当我们在与使用JSON进行通信的API交互,一个名为 /ping 的端点返回以下服务器状态的响应:
{"Message": "All is good with the world", "Status": "Success"}
下面构造程序与此端点进行交互并解码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.Printf("%s -> %s\n", status.Status, status.Message)
}
总结
现在我们了解了如何构建自定义HTTP请求以及接收其响应,并且知道了如何解析结构化的数据,以便客户端可以查询信息以确定可执行的或相关联的数据。今天的内容就到这里了,如果有什么疑问欢迎在评论区一起讨论,go!go!go!
参考 : 《Black Hat Go:Go Programming for Hackers and Pentesters》

浙公网安备 33010602011771号