在线词典课后实践 | 青训营笔记

203 阅读4分钟

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

Created: January 16, 2023 4:59 PM Tags: golang

课程上我们已经基于 Go 编写了一个在线词典小程序,用户能够在命令行将想要查询的单词以参数的形式传递给程序,然后程序会调用第三方翻译网站的 API 获取对应的单词信息,并将翻译结果打印到终端。现在我们将在原始版本的基础上,增加对多种翻译引擎的支持,并实现并行多个翻译请求来提高响应速度。

该课后实践的主要工作在于发送 HTTP 请求和处理请求响应,不同翻译引擎的请求与响应方式不尽相同,可以分别构造多个 Request 和 Response 结构体来承载信息。并行多个请求可以简单通过 Go 协程方式实现,对于并行的技术细节以及更复杂的并行操作暂不讨论。

参考示例:

github.com/wangkechun/…

处理 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 请求,以火山翻译引擎为例:

Untitled 1.png

利用 curl 命令转换工具 可以快速生成程序代码,此时运行程序获得的结果将是 json 文本

Untitled 2.png

然后创建 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"`
}

再次运行程序,得到翻译结果

Untitled 3.png

并行执行翻译请求

这里以彩云翻译引擎和火山翻译引擎为例,每个翻译请求都封装成一个函数,待翻译单词为输入参数,并且为了衡量程序执行时间,增加了 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:])
}

直接执行的结果如下:

Untitled 4.png

下面将通过 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()
}

Untitled 5.png

完整代码如下:

github.com/wehyy/go-le…