【Go语言】解析处理结构化响应数据

前言

之前我们已经讲了如何通过各种技术来利用TCP协议的特性创建可用的客户端和服务器。下面我们来探讨一下OSI模型的几种上层协议,那就先从HTTP开始吧!

1、基础知识

HTTP

HTTP是一种无状态协议:服务器不会维护每个请求的状态,而是通过多种方式对其进行跟踪,这些方式可能包括会话标识符cookieHTTP标头等。客户端和服务器有责任正确协商和验证状态。

客户端和服务器之间的通信可以同步或异步进行,但它们要以请求/响应的方式循环进行。可以在请求中添加几个选项和标头,以影响服务器的行为并创建可用的web应用程序。最常见的是服务器托管web浏览器渲染的文件,以生成数据的图形化、组织化和时尚化的表示形式。API通常使用XMLJSONMSGRPC进行通信。某些情况下,检索到的数据可能是二进制格式,表示要下载的任意文件类型。

调用HTTP API

Gonet/http 标准包包含多个便捷函数,可便捷地发送POSTGETHEAD请求,函数使用形式如下:

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》

posted @ 2023-02-19 13:48  Seversan-Sickle  阅读(122)  评论(0)    收藏  举报  来源