在线字典实现详解 | 青训营笔记

144 阅读4分钟

“这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天

在线字典通过代码发起HTTP请求翻译网页(彩云小译 - 在线翻译 (caiyunapp.com))响应,然后我们接收并解析响应,获取我们需要的结果打印出来。

通过该项目的实现我们可以学到:如何用Go语言发送HTTP请求,解析JSON以及如何使用代码生成提高开发效率

功能说明

1675305913(1).png

1675305937(1).png

实现流程

graph LR
创建HTTP客户 --> 构造请求 --> 发送并接收响应 --> 解析响应
  1. 创建HTTP客户:

net/http 包提供了最简洁的 HTTP 客户端实现,无需借助第三方网络通信库就可以直接使用最常见的 GETPOST 方式发起 HTTP 请求。 具体来说,我们可以通过 net/http 包里面的 Client 类提供的如下方法发起 HTTP 请求: http.client 是用来创建一个客户端,模拟客户端发送请求,可以通过它来实现设置请求头部、重定向等等操作。

   // 创建HTTP client,可以指定参数如请求超时是否使用cookie等
  client := &http.Client{} 
  1. 构造请求:

使用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的获取:代码生成
  1. 发送请求并接收响应:

使用client.Do方法将上一步生成的请求对象req发送,并得到响应对象,返回的响应对象包含状态码,接收主体,头信息等

  resp, err := client.Do(req)
  if err != nil {   // 请求失败
       // 原因:由于DNS解析失败或断网各种方式连不上服务器
  	log.Fatal(err)
  }
  1. 解析响应:

需要把这个巨大的响应对象解析出来,获取到里面的几个字段。

graph LR
接收流--> 读到内存 --> JSON反序列化为结构体--> 输出结构体中需要信息
  1. 接收流:响应对于的实体部分body是流。
  2. 读到内存: ioutil.ReadAll(body流)可以给流整个读到内存转为byte数组
  3. 反序列化:
    在JS或Python这些脚本语言里面,返回的body会是一个字典或map结构,可以直接用方括号或点取值。
    对goland不是最佳实践,更常见的方式是和request得处理一样,写一个字段和返回得response是一一对应的结构体,JSON字符串反序列化到该结构体。
  4. 输出
  • 反序列化的结构体怎么构造? ——使用代码生成
    浏览器API返回得结构非常复杂, 一一定义结构体字段来对于非常繁琐且容易出错。
  • 防御式编程:
    1. defer手动关闭接收流:
      返回的响应实体body同样也是一个流,需要加上defer手动关闭这个流,defer会在函数结束之后从下往上出发,在函数结束之后调用close函数,避免资源泄露,释放网络资源,结束请求.
    2. 状态码检查
  // 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存到内存的代码。
  1. 在翻译网页右键点开检查,进入浏览器开发者工具,选择网络选项,然后ctrl+R
  2. 随机输入一个单词翻译后,会有两个dict的请求出现,选择请求方法为POST的dict,复制为cURL(bash)
  3. 将复制的cURL粘贴到网站 curlconverter.com/go/ ,选择语言为Go,
  • 生成接收响应到内存后,JSON反序列化为结构体需要的结构体
  1. 将dict请求下的预览下的值复制下来
  2. 粘贴到 oktools.net/json2go ,选择嵌套转换

参考文献:

Go 语言网络编程系列(三)—— HTTP 编程篇:客户端如何发起请求 - 阅读清单 - 腾讯云开发者社区-腾讯云 (tencent.com)