“这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天
在线字典通过代码发起HTTP请求翻译网页(彩云小译 - 在线翻译 (caiyunapp.com))响应,然后我们接收并解析响应,获取我们需要的结果打印出来。
通过该项目的实现我们可以学到:如何用Go语言发送HTTP请求,解析JSON以及如何使用代码生成提高开发效率
功能说明
实现流程
graph LR
创建HTTP客户 --> 构造请求 --> 发送并接收响应 --> 解析响应
- 创建HTTP客户:
net/http 包提供了最简洁的 HTTP 客户端实现,无需借助第三方网络通信库就可以直接使用最常见的
GET和POST方式发起 HTTP 请求。 具体来说,我们可以通过net/http包里面的Client类提供的如下方法发起 HTTP 请求: http.client 是用来创建一个客户端,模拟客户端发送请求,可以通过它来实现设置请求头部、重定向等等操作。// 创建HTTP client,可以指定参数如请求超时是否使用cookie等 client := &http.Client{}
- 构造请求:
使用http.NewRequest方法初始化一个请求对象,并为该对象添加头信息即可, 该方法需要传入三个参数:
- 请求方法:POST
- 目标URL
- 请求实体body
- 内部包含:
1:待翻译单词Source
2:以及一个trans_type(若省略会导致响应的状态码为501,unsupported trans_type)- 实现流程:
结构体对象-->JSON序列化成byte数组-->只读流- 实现细节:
body为流而不是字符串:因为可能本地读取了一个很大文件,如果全部放在内存里面可能会导致发送请求需要发送消耗很大内存,流只需要很小的内存type DictRequest struct { TransType string `json:"trans_type"` Source string `json:"source" } // 构造body结构体对象 request := DictRequest{TransType: "en2zh", Source: word} // JSON序列化成byte数组 buf, err := json.Marshal(request) if err != nil { // 转化失败-->报错并退出 log.Fatal(err) } // 将byte数组转换为只读流 var data = bytes.NewReader(buf) // 传入参数初始化请求对象 req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data) if err != nil { // 初始化请求失败-->报错退出 fmt.Println("生成请求失败") log.Fatal(err) } // 对已经初始化好的请求对象设置头信息 ......
- 头信息和trans_type的获取:代码生成
- 发送请求并接收响应:
使用client.Do方法将上一步生成的请求对象req发送,并得到响应对象,返回的响应对象包含状态码,接收主体,头信息等
resp, err := client.Do(req) if err != nil { // 请求失败 // 原因:由于DNS解析失败或断网各种方式连不上服务器 log.Fatal(err) }
- 解析响应:
需要把这个巨大的响应对象解析出来,获取到里面的几个字段。
graph LR 接收流--> 读到内存 --> JSON反序列化为结构体--> 输出结构体中需要信息
- 接收流:响应对于的实体部分body是流。
- 读到内存: ioutil.ReadAll(body流)可以给流整个读到内存转为byte数组
- 反序列化:
在JS或Python这些脚本语言里面,返回的body会是一个字典或map结构,可以直接用方括号或点取值。
对goland不是最佳实践,更常见的方式是和request得处理一样,写一个字段和返回得response是一一对应的结构体,JSON字符串反序列化到该结构体。- 输出
- 反序列化的结构体怎么构造? ——使用代码生成
浏览器API返回得结构非常复杂, 一一定义结构体字段来对于非常繁琐且容易出错。- 防御式编程:
- defer手动关闭接收流:
返回的响应实体body同样也是一个流,需要加上defer手动关闭这个流,defer会在函数结束之后从下往上出发,在函数结束之后调用close函数,避免资源泄露,释放网络资源,结束请求.- 状态码检查
// defer手动关闭resp.Body流 defer resp.Body.Close() // 将接收流整个读到内存里变成一个byte数组 bodyText, err := ioutil.ReadAll(resp.Body) if err != nil { // 读取失败 log.Fatal(err) } if resp.StatusCode != 200 { // 检查状态码, // 若状态码非200可能会使后续结构体为空 log.Fatal("bad StatusCode:", resp.StatusCode, "body", string(bodyText)) } // 写一个结构体,将JSON反序列化到结构体里面 */ var dictResponse DictResponse err = json.Unmarshal(bodyText, &dictResponse) if err != nil { log.Fatal(err) } // 打印响应结构体里面的特定字段 fmt.Println(word, "UK:", dictResponse.Dictionary.Prons.En, "US:", dictResponse.Dictionary.Prons.EnUs) for _, item := range dictResponse.Dictionary.Explanations >{ fmt.Println(item) } }
代码生成
- 生成在固定单词翻译下,从创建客户到将响应实体body存到内存的代码。
- 在翻译网页右键点开检查,进入浏览器开发者工具,选择网络选项,然后ctrl+R
- 随机输入一个单词翻译后,会有两个dict的请求出现,选择请求方法为POST的dict,复制为cURL(bash)
- 将复制的cURL粘贴到网站 curlconverter.com/go/ ,选择语言为Go,
- 生成接收响应到内存后,JSON反序列化为结构体需要的结构体
- 将dict请求下的预览下的值复制下来
- 粘贴到 oktools.net/json2go ,选择嵌套转换
参考文献:
Go 语言网络编程系列(三)—— HTTP 编程篇:客户端如何发起请求 - 阅读清单 - 腾讯云开发者社区-腾讯云 (tencent.com)