这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
Created: January 16, 2023 4:59 PM Tags: golang
课程上我们已经基于 Go 编写了一个在线词典小程序,用户能够在命令行将想要查询的单词以参数的形式传递给程序,然后程序会调用第三方翻译网站的 API 获取对应的单词信息,并将翻译结果打印到终端。现在我们将在原始版本的基础上,增加对多种翻译引擎的支持,并实现并行多个翻译请求来提高响应速度。
该课后实践的主要工作在于发送 HTTP 请求和处理请求响应,不同翻译引擎的请求与响应方式不尽相同,可以分别构造多个 Request 和 Response 结构体来承载信息。并行多个请求可以简单通过 Go 协程方式实现,对于并行的技术细节以及更复杂的并行操作暂不讨论。
参考示例:
处理 HTTP 请求
基于 HTTP 构建的服务标准模型包括两个端,客户端(Client)和服务端(Server)。HTTP 请求从客户端发出,服务端接受到请求后进行处理然后将响应返回给客户端。所以 http 服务器的工作就在于如何接受来自客户端的请求,并向客户端返回响应。在 Go 语言中,我们可以利用标准库 “net/http” 来构建多种请求方法,例如 Get,Head 和 Post 等:
resp, err := http.Get("http://example.com/")
...
resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf)
...
resp, err := http.PostForm("http://example.com/form",
url.Values{"key": {"Value"}, "id": {"123"}})
每次响应结束客户端都应该关闭响应主体:
resp, err := http.Get("http://example.com/")
if err != nil {
// handle error
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
// ...
该项目中我们构建 POST 请求来获取单词的翻译结果,借助 chrome 的开发者模式我们可以十分方便地构建不同翻译引擎的 POST 请求,以火山翻译引擎为例:
利用 curl 命令转换工具 可以快速生成程序代码,此时运行程序获得的结果将是 json 文本
然后创建 request 结构体用于 HTTP 请求的 json 序列化,创建 response 结构体用于响应的 json 反序列化,需要注意的问题是响应的 json 文件有可能存在多层嵌套,可能需要多次反序列化。
type VolcRequest struct {
Source string `json:"source"`
Words []string `json:"words"`
SourceLanguage string `json:"source_language"`
TargetLanguage string `json:"target_language"`
}
type VolcResponse struct {
Details []struct {
Detail string `json:"detail"`
Extra string `json:"extra"`
} `json:"details"`
BaseResp struct {
StatusCode int `json:"status_code"`
StatusMessage string `json:"status_message"`
} `json:"base_resp"`
}
type VolcResponseDetail struct {
ErrorCode string `json:"errorCode"`
RequestID string `json:"requestId"`
Msg string `json:"msg"`
Result []struct {
Ec struct {
ReturnPhrase []string `json:"returnPhrase"`
Synonyms []struct {
Pos string `json:"pos"`
Words []string `json:"words"`
Trans string `json:"trans"`
} `json:"synonyms"`
Etymology struct {
ZhCHS []struct {
Description string `json:"description"`
Detail string `json:"detail"`
Source string `json:"source"`
} `json:"zh-CHS"`
} `json:"etymology"`
SentenceSample []struct {
Sentence string `json:"sentence"`
SentenceBold string `json:"sentenceBold"`
Translation string `json:"translation"`
Source string `json:"source"`
} `json:"sentenceSample"`
WebDict string `json:"webDict"`
Web []struct {
Phrase string `json:"phrase"`
Meanings []string `json:"meanings"`
} `json:"web"`
MTerminalDict string `json:"mTerminalDict"`
RelWord struct {
Word string `json:"word"`
Stem string `json:"stem"`
Rels []struct {
Rel struct {
Pos string `json:"pos"`
Words []struct {
Word string `json:"word"`
Tran string `json:"tran"`
} `json:"words"`
} `json:"rel"`
} `json:"rels"`
} `json:"relWord"`
Dict string `json:"dict"`
Basic struct {
UsPhonetic string `json:"usPhonetic"`
UsSpeech string `json:"usSpeech"`
Phonetic string `json:"phonetic"`
UkSpeech string `json:"ukSpeech"`
ExamType []string `json:"examType"`
Explains []struct {
Pos string `json:"pos"`
Trans string `json:"trans"`
} `json:"explains"`
UkPhonetic string `json:"ukPhonetic"`
WordFormats []struct {
Name string `json:"name"`
Value string `json:"value"`
} `json:"wordFormats"`
} `json:"basic"`
Phrases []struct {
Phrase string `json:"phrase"`
Meanings []string `json:"meanings"`
} `json:"phrases"`
Lang string `json:"lang"`
IsWord bool `json:"isWord"`
} `json:"ec"`
} `json:"result"`
}
再次运行程序,得到翻译结果
并行执行翻译请求
这里以彩云翻译引擎和火山翻译引擎为例,每个翻译请求都封装成一个函数,待翻译单词为输入参数,并且为了衡量程序执行时间,增加了 func timeMeasurement 函数
func timeMeasurement(start time.Time, progname string) {
elapsed := time.Since(start)
fmt.Printf(progname, "Execution time: %s\n", elapsed)
}
func main() {
if len(os.Args) != 2 {
fmt.Fprint(os.Stderr, `usage: simpleDict WORD example: simpleDict hello`, os.Args[0])
os.Exit(1)
}
defer timeMeasurement(time.Now(), "main")
caiyunfanyi(os.Args[1])
huoshanfanyi(os.Args[1:])
}
直接执行的结果如下:
下面将通过 go 协程并行执行将能够提高翻译引擎的访问速度,使用到 sync.WaitGroup 标准库包来同步程序中的主协程与子协程,可以看到程序响应速度明显加快
var wg sync.WaitGroup
func caiyunfanyi(word string) {
defer timeMeasurement(time.Now(), "caiyunfanyi")
...
}
func huoshanfanyi(word []string) {
defer timeMeasurement(time.Now(), "huoshanfanyi")
...
}
func main() {
if len(os.Args) != 2 {
fmt.Fprint(os.Stderr, `usage: simpleDict WORD example: simpleDict hello`, os.Args[0])
os.Exit(1)
}
wg.Add(2)
defer timeMeasurement(time.Now(), "main")
go caiyunfanyi(os.Args[1])
go huoshanfanyi(os.Args[1:])
wg.Wait()
}
完整代码如下: