本文将通过一个简单的在线词典案例体会如何利用 Go 语言来发送 http 请求并解析响应
1 在线翻译 API 获取
打开彩云翻译网站 https://fanyi.caiyunapp.com/
打开浏览器控制台,输入要翻译的单词,点击翻译,可以看到浏览器发送的请求
鼠标右键相应的请求,选择复制 cURL
将粘贴的命令复制到命令行中执行,会返回一大串 json 字符串,接下来我们利用 Golang 来发送请求
2 利用 Golang 发送 http 请求
这里我们利用网站来生成 Golang 发送 http 请求的代码 https://curlconverter.com/go/
打开网站,将浏览器中复制的 CURL 命令粘贴进去,得到代码
将代码复制到 IDE 中查看,运行代码,会输出一串 json 字符串
我们来看一下生成的代码:
- 首先创建了一个HTTP client,创建的时候可以指定很多参数,包括请求的超时时间、是否使用cookie等。
- 接下来是构造一个HTTP请求,这是一 post 请求,会用到 HTTP.NewRequest。第一个参数是 http 方法,第二个参数是URL,最后一个参数是body,body可能很大,为了支持流式发送,是一个只读流。用 strings.NewReader 来把字符串转换成一个流,这样我们就成功构造了一个HTTP request
- 接下来需要对这个 HTTP request 设置 header ,调用 client.do 就能得到 response 如果请求失败的话,error 会返回非 nil,会打印错误并且退出进程。
- response中包括 HTTP状态码,response header 和 body.。body同样是一个流,在 golang 里面,为了避免资源泄露,需要加一个 defer 来手动关闭这个流,这个defer会在这个函数运行结束之后去执行。
- 接下来用 io.ReadAll 来读取这个流,就能得到整个 body,再用 print 打印出来。
package main
import (
"fmt"
"io"
"log"
"net/http"
"strings"
)
func main() {
// 创建一个 http client
client := &http.Client{}
// 发送 post 请求,携带data参数
var data = strings.NewReader(`{"trans_type":"en2zh","source":"content"}`)
// 创建一个 post 请求
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
if err != nil {
log.Fatal(err)
}
// 给请求带上请求头参数
req.Header.Set("authority", "api.interpreter.caiyunai.com")
req.Header.Set("accept", "application/json, text/plain, */*")
req.Header.Set("accept-language", "zh-CN,zh;q=0.9")
req.Header.Set("app-name", "xy")
req.Header.Set("content-type", "application/json;charset=UTF-8")
req.Header.Set("device-id", "975c9b3a88bdb5d862f1a195b157e53e")
req.Header.Set("origin", "https://fanyi.caiyunapp.com")
req.Header.Set("os-type", "web")
req.Header.Set("os-version", "")
req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
req.Header.Set("sec-ch-ua", `"Not/A)Brand";v="99", "Google Chrome";v="115", "Chromium";v="115"`)
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("sec-ch-ua-platform", `"Windows"`)
req.Header.Set("sec-fetch-dest", "empty")
req.Header.Set("sec-fetch-mode", "cors")
req.Header.Set("sec-fetch-site", "cross-site")
req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36")
req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
// 发送请求,得到响应
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
// 读取响应体并打印到控制台
bodyText, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s\n", bodyText)
}
3 利用变量输入请求参数
现在我们已经可以发送请求并且打印 json,但是请求参数是固定的,我们希望可以通过变量来输入,此时就需要创建一个结构体并序列化成为 json 字符串
请求参数结构如下:
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
UserID string `json:"user_id"`
}
修改开头的代码:
client := &http.Client{}
//var data = strings.NewReader(`{"trans_type":"en2zh","source":"content"}`)
request := DictRequest{TransType: "en2zh", Source: word}
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
if err != nil {
log.Fatal(err)
}
var data = bytes.NewReader(buf)
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
运行代码,如果正确输出 json 字符串则说明程序正确,接下来我们用同样的方法处理响应
4 处理响应
- 代码写到这里,我们得到的响应还只是一段 json 字符串,不方便阅读,需要通过反序列化的手段将字符串保存到结构体中
- 在 python 和 JavaScript 等弱类型语言中,json 字符串直接可以转化成字典或者对象操作,但是 Golang 是强类型语言,必须先创建一个结构体来接收结果,然后再对该结构体进行操作
- 由于响应的 json 字符串十分复杂,我们可以通过在线工具生成其对应的结构体,在线转换网址 https://oktools.net/json2go
然后我们将生成的结构体复制到代码中,并在末尾加入处理响应的逻辑
type DictResponse struct {
Rc int `json:"rc"`
Wiki struct {
} `json:"wiki"`
Dictionary struct {
Prons struct {
EnUs string `json:"en-us"`
En string `json:"en"`
} `json:"prons"`
Explanations []string `json:"explanations"`
Synonym []interface{} `json:"synonym"`
Antonym []string `json:"antonym"`
WqxExample [][]string `json:"wqx_example"`
Entry string `json:"entry"`
Type string `json:"type"`
Related []interface{} `json:"related"`
Source string `json:"source"`
} `json:"dictionary"`
}
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
bodyText, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
//fmt.Printf("%s\n", bodyText)
// 将得到的 json 字符串解析到结构体
var dicResponse DictResponse
err = json.Unmarshal(bodyText, &dicResponse)
if err != nil {
log.Fatal(err)
}
最后,我们只需要结构体中的部分字段,包括音标和单词翻译,因此我们选择性输入
fmt.Println(word, "UK:", dicResponse.Dictionary.Prons.En, "US:", dicResponse.Dictionary.Prons.EnUs)
for _, item := range dicResponse.Dictionary.Explanations {
fmt.Println(item)
}
5 优化
代码写道这里,核心功能都已经完成了,接下来将代码做一些优化
- 将我们的业务代码封装成函数
func query(word string) {
client := &http.Client{}
//var data = strings.NewReader(`{"trans_type":"en2zh","source":"content"}`)
request := DictRequest{TransType: "en2zh", Source: word}
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
if err != nil {
log.Fatal(err)
}
var data = bytes.NewReader(buf)
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
req.Header.Set("authority", "api.interpreter.caiyunai.com")
req.Header.Set("accept", "application/json, text/plain, */*")
req.Header.Set("accept-language", "zh-CN,zh;q=0.9")
req.Header.Set("app-name", "xy")
req.Header.Set("content-type", "application/json;charset=UTF-8")
req.Header.Set("device-id", "975c9b3a88bdb5d862f1a195b157e53e")
req.Header.Set("origin", "https://fanyi.caiyunapp.com")
req.Header.Set("os-type", "web")
req.Header.Set("os-version", "")
req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
req.Header.Set("sec-ch-ua", `"Not/A)Brand";v="99", "Google Chrome";v="115", "Chromium";v="115"`)
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("sec-ch-ua-platform", `"Windows"`)
req.Header.Set("sec-fetch-dest", "empty")
req.Header.Set("sec-fetch-mode", "cors")
req.Header.Set("sec-fetch-site", "cross-site")
req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36")
req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
bodyText, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
//fmt.Printf("%s\n", bodyText)
var dicResponse DictResponse
err = json.Unmarshal(bodyText, &dicResponse)
if err != nil {
log.Fatal(err)
}
//fmt.Printf("%#v\n", dicResponse)
fmt.Println(word, "UK:", dicResponse.Dictionary.Prons.En, "US:", dicResponse.Dictionary.Prons.EnUs)
for _, item := range dicResponse.Dictionary.Explanations {
fmt.Println(item)
}
}
- 编写 main 函数
func main() {
var word string
fmt.Println("请输入要查询的单词:")
fmt.Scanln(&word)
query(word)
}
- 运行结果示例
6 完整代码
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
)
type DictRequest struct {
TransType string `json:"trans_type"`
Source string `json:"source"`
UserID string `json:"user_id"`
}
type DictResponse struct {
Rc int `json:"rc"`
Wiki struct {
} `json:"wiki"`
Dictionary struct {
Prons struct {
EnUs string `json:"en-us"`
En string `json:"en"`
} `json:"prons"`
Explanations []string `json:"explanations"`
Synonym []interface{} `json:"synonym"`
Antonym []string `json:"antonym"`
WqxExample [][]string `json:"wqx_example"`
Entry string `json:"entry"`
Type string `json:"type"`
Related []interface{} `json:"related"`
Source string `json:"source"`
} `json:"dictionary"`
}
func query(word string) {
client := &http.Client{}
//var data = strings.NewReader(`{"trans_type":"en2zh","source":"content"}`)
request := DictRequest{TransType: "en2zh", Source: word}
buf, err := json.Marshal(request)
if err != nil {
log.Fatal(err)
}
if err != nil {
log.Fatal(err)
}
var data = bytes.NewReader(buf)
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
req.Header.Set("authority", "api.interpreter.caiyunai.com")
req.Header.Set("accept", "application/json, text/plain, */*")
req.Header.Set("accept-language", "zh-CN,zh;q=0.9")
req.Header.Set("app-name", "xy")
req.Header.Set("content-type", "application/json;charset=UTF-8")
req.Header.Set("device-id", "975c9b3a88bdb5d862f1a195b157e53e")
req.Header.Set("origin", "https://fanyi.caiyunapp.com")
req.Header.Set("os-type", "web")
req.Header.Set("os-version", "")
req.Header.Set("referer", "https://fanyi.caiyunapp.com/")
req.Header.Set("sec-ch-ua", `"Not/A)Brand";v="99", "Google Chrome";v="115", "Chromium";v="115"`)
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("sec-ch-ua-platform", `"Windows"`)
req.Header.Set("sec-fetch-dest", "empty")
req.Header.Set("sec-fetch-mode", "cors")
req.Header.Set("sec-fetch-site", "cross-site")
req.Header.Set("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36")
req.Header.Set("x-authorization", "token:qgemv4jr1y38jyq6vhvi")
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
bodyText, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
//fmt.Printf("%s\n", bodyText)
var dicResponse DictResponse
err = json.Unmarshal(bodyText, &dicResponse)
if err != nil {
log.Fatal(err)
}
//fmt.Printf("%#v\n", dicResponse)
fmt.Println(word, "UK:", dicResponse.Dictionary.Prons.En, "US:", dicResponse.Dictionary.Prons.EnUs)
for _, item := range dicResponse.Dictionary.Explanations {
fmt.Println(item)
}
}
func main() {
var word string
fmt.Println("请输入要查询的单词:")
fmt.Scanln(&word)
query(word)
}