这是我参与「第五届青训营 」伴学笔记创作活动的第1天
go语言基础
因为之前有过一点基础, 所以看起来还算轻松, 需要注意的点是json那里, 没有讲到的是channel和go routine, 应该会在后续设计
json
序列化
从网页端返回的数据一般多是json类型, 而go存储这些数据一般是创建对应的结构体
序列化结构体为json, 主要有两点需要注意的, 一是对应的数据名称, 必须大写(public), 而是序列化时不能出现循环调用
type Message struct {
Name string `name`
Body string `body`
Time int64 `time`
}
另外, 结构体对应名称后方, 使用反单引号包围的string值, 与json中的key一一对应.
最终, 生成的数据, 通过json.Marshal(x)序列化为json数据
eg:
m := Message{"Alice", "Hello", 1294706395881547000}
b, err := json.Marshal(m)
fmt.Println(b) //{"Name":"Alice","Body":"Hello","Time":1294706395881547000}
反序列化
反序列化是输入json数据, 将其转化为对应的struct, 以便代码中进行调用, 因此, 需要使用第三方网站json转go结构体帮助生成对应结构体, 接着将json数据反序列化为结构体
var m Message
err := json.UnMarshal(jsonText,&m)
其中, jsonText为[]byte([]uint8), 是抓取到的json数据
request请求与response响应
request请求头
与python写爬虫类似, 进行http请求时, 需要先设置请求头
而设置请求头有一个简便的方法, 便是浏览器抓取对应的请求头, 复制为curl(bash), 在第三方网站curlConverter转化为go代码,
eg:
func setHeader(req *http.Request) {
req.Header.Set("Accept", "application/json, text/plain, */*")
req.Header.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
req.Header.Set("Connection", "keep-alive")
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
req.Header.Set("Origin", "https://fanyi.caiyunapp.com")
req.Header.Set("Referer", "https://fanyi.caiyunapp.com/")
req.Header.Set("Sec-Fetch-Dest", "empty")
req.Header.Set("Sec-Fetch-Mode", "cors")
req.Header.Set("Sec-Fetch-Site", "same-site")
req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36 Edg/109.0.1518.49")
req.Header.Set("X-Authorization", "token:qgemv4jr1y38jyq6vhvi")
req.Header.Set("app-name", "xy")
req.Header.Set("device-id", "")
req.Header.Set("os-type", "web")
req.Header.Set("os-version", "")
req.Header.Set("sec-ch-ua", `"Not_A Brand";v="99", "Microsoft Edge";v="109", "Chromium";v="109"`)
req.Header.Set("sec-ch-ua-mobile", "?0")
req.Header.Set("sec-ch-ua-platform", `"Windows"`)
}
接着流程便与python类似, 值得注意的是, 需要先将请求头附带的信息, 通过相应的数据结构包装起来, 序列化为json数据, 再将json数据请求一起通过http.Clinet()发送出去
type dictRequest struct {
//只有public的变量可以被序列化
TransType string `json:"trans_type"`
Source string `json:"source"`
UserID string `json:"user_id"`
}
//请求体携带的数据
requestContext := dictRequest{TransType:"en2zh",Source:"test"}
//将其序列化为json
buf,_ := json,Marshal(requeset)
//设置请求链接, 并将携带的数据转化为Reader流(存储数据, 当前读到的下标, 以及prevRune的下标)
request,_ := http.NewRequest(method:"POST","https://www.example.com",bytes.NewReader(buf))
setHeader(request)
response,err := (&http.Client{}).Do(request)
defer response.Close()
body,_ := ioutil.ReadAll(response.Body)
ioutil.Reader(r io.Reader)与io.Copy(dst Writer, src Reader) (written int64, err error)
从io.Reader中读取所有数据, 但是应当避免使用ReadAll, 主要因为性能问题, 再面对大文件时, 存在性能差, 可能oom, 因为底层存储是使用buf([]byte)存储, 因此面临扩容问题, 在面对大文件时, 扩容次数频繁, 造成性能下降.
扩容是指切片的扩容, 当切片长度小于1024时, 每次是翻倍扩容, 大于1024时, 每次时1/4扩容
面对大文件时, 推荐使用io.Copy()代替
与ioutil.ReadAll()相比, io.Copy()实现了对数据的完整处理, 即从src中读出数据, 再将其写入到dst中, 而ReadAll仅仅时获取数据缓冲区的数据, 并且只能先将数据全部读出来, 才能使用数据
对于copy()而言, 若是底层实现了WriteTo或是ReaderFrom, 那么便省区了中间buf缓存的步骤, 数据直接从src写到了dst
并且copy()使用了固定的buffer作为临时缓冲区, 不会像ReadAll一样频繁扩容.
另外, 如果是读取出来的数据, 需要使用json解码, 可以省去io.Copy()这一步
type Result struct {
Msg string `json:"msg"`
Rescode string `json:"rescode"`
}
func parseBody(body io.Reader) {
var v Result
err := json.NewDecoder(body).Decode(&v)
if err != nil {
return nil, fmt.Errorf("DecodeJsonFailed:%s", err.Error())
}
}
直接使用json.NewDecoder(body).Decode(&v), 更加提升性能
今天课程中的代码, 这一部分便可以使用json.NewDecoder(body).Decode(&v)优化
服务器代理
首先创建服务器对特定ip端口进行监听, 并在接收到请求时启动go routine处理请求
在go routine中, 对请求链接进行验证(身份验证), 即事先约定好的认证方式, 进行验证, 验证无误后, 对其进行连接
其中需要注意context.WithCancel(context.Background)
withCancel接受一个context并返回其子context和取消函数
新创建协程中传入子Context做参数, 且需要监控子Context的Done通道, 若收到消息, 则退出
当想要新协程结束时, 在外部调用cancel(), 即会向子context的Done通道发送消息
父协程的Context的Done()关闭时, 子ctx的Done()也会关闭
这里还需要学习, 目前了解的还不是很清楚, 因为接触的太浅, 对整个流程掌控很弱