Go1.22 新循环语义

前言 前段时间看到了一个提案,是关于 go for 循环的一个提案,根据提案看到了去年 rsc 在社区发出的讨论,讨论的内容主要是为了解决 for 循环变量的问题,是什么样的问题呢,常见的例子如下: var all []*Item for _, item := range items { all = append(all, &item) } 这段代码有一个问题,循环结束后,all 的内容是包含了 len(all) 个相同的指针,指针指向迭代的最后一个 item。为什么会发生这种情况呢,因为 item 变量是每个循环的而不是每次迭代的,&item 每次迭代都是相同的,并且每次迭代都会被覆盖。 怎么解决呢,最简单的方法就是添加 item := item 这段代码: var all []*Item for _, item := range items { item := item all = append(all, &item) } 我在使用 Goroutine 协程时也经常遇到这种问题。这种错误已导致许多公司出现生产问题,包括 Lets Encrypt 公开记录的问题。 go 社区为了解决这个问题,打算将循环变量改为每次迭代,即隐式的添加上面的代码。由于其它一些原因,直到今年的 6 月才正式决定在 go 1.21 中添加 GOEXPERIMENT=loopvar 进行相应的尝试,并且将在 go1.22 版本中正式推出。 为了确保与现有代码的向后兼容性,新语义将仅适用于声明 go 1.22 或稍后在其 go.mod 文件中声明的模块中包含的包。 特性测试 我们通过不同版本运行的结果来对比 package main import "fmt" func main() { var prints []func() for _, v := range []int{1, 2, 3} { prints = append(prints, func() { fmt.Println(v) }) } for _, print := range prints { print() } } 没使用旧版本运行的结果是: ...

十一月 4, 2023 · overstarry

apisix 代理 gRPC 服务

最近需要使用 apisix 来代理 gRPC 服务,本文记录一下 apisix 代理 gRPC 服务以及实践过程中遇到的一些问题。 准备 在接下来的步骤前,我们需要准备一个 gRPC 服务,我们使用 kratos 简单启动一个 gRPC 服务: $ kratos new hellowrold $ cd helloworld $ kratos run 一个简单的 gRPC 服务就启动了,我们先直接请求 gRPC 服务看看,通过 postman 请求接口后,接口顺利返回相应的值。 接下来我们开始本篇的主要内容:apisix 代理服务。 apisix 代理 gRPC 服务 我们使用 apisix admin 接口创建 Route: upstream 的 scheme 指定为 grpc 或 grpcs,nodes 指定需要代理的服务地址。 curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "methods": ["POST", "GET"], "uri": "/helloworld.v1.Greeter/SayHello", "upstream": { "scheme": "grpc", "type": "roundrobin", "nodes": { "127.0.0.1:9001": 1 } } }' ...

十月 20, 2023 · overstarry

Go 漏洞管理工具 govulncheck

前言 在 7 月份 go 团队正式推出了官方的漏洞检测工具 - govulncheck, govulncheck 默认向 https://vuln.go.dev/ 漏洞数据库请求,对数据库的请求不包含代码只有使用的包,你也可以使用 -db 来指导所使用的漏洞数据库,所使用的数据库必须实现相应的规范。 govulncheck 具有以下限制: Govulncheck 在分析函数指针和接口调用时采用保守的方法,这可能导致一些情况下的误报或不准确的调用堆栈。 使用 reflect 进行的函数调用对于静态分析是不可见的。仅通过这些调用才能访问的易受攻击的代码将不会被报告。使用 unsafe 包可能导致漏报。 由于 Go 二进制文件不包含详细的调用信息,govulncheck 无法显示检测到的漏洞的调用图。对于二进制文件中但无法访问的代码,它可能还会报告误报。 目前还不支持 silencing 漏洞发现。请参阅 https://go.dev/issue/61211。 Govulncheck 只能读取使用 Go 1.18 及更高版本编译的二进制文件。 对于无法提取符号信息的二进制文件,govulncheck 会报告二进制文件所依赖的所有模块的漏洞。 govulncheck 架构图 govulncheck 还提供了一个可用的 API govulncheck,使开发者能够方便的将 govulncheck 集成到各种工具之中。 安装 通过以下命令安装 govulncheck: go install golang.org/x/vuln/cmd/govulncheck@latest 使用 安装完 govulncheck 后,我们就可以使用了,进入项目路径中,执行 govulncheck ./... 。 以下是我的一个项目的输出: Scanning your code and 508 packages across 71 dependent modules for known vulnerabilities... Vulnerability #1: GO-2023-2102 HTTP/2 rapid reset can cause excessive work in net/http More info: https://pkg.go.dev/vuln/GO-2023-2102 Standard library Found in: net/[email protected] Fixed in: net/[email protected] Example traces found: #1: cmd\xxx\main.go:94:19: xxx.main calls kratos.App.Run, which eventually calls http.Server.Serve #2: cmd\xxx\main.go:94:19: xxx.main calls kratos.App.Run, which eventually calls http.Server.ServeTLS Vulnerability #2: GO-2023-2043 Improper handling of special tags within script contexts in html/template More info: https://pkg.go.dev/vuln/GO-2023-2043 Standard library Found in: html/[email protected] Fixed in: html/[email protected] Example traces found: #1: cmd\xxx\main.go:94:19: xxx.main calls kratos.App.Run, which eventually calls template.Template.Execute #2: cmd\xxx\main.go:94:19: xxx.main calls kratos.App.Run, which eventually calls template.Template.ExecuteTemplate Vulnerability #3: GO-2023-2041 Improper handling of HTML-like comments in script contexts in html/template More info: https://pkg.go.dev/vuln/GO-2023-2041 Standard library Found in: html/[email protected] Fixed in: html/[email protected] Example traces found: #1: cmd\xxx\main.go:94:19: xxx.main calls kratos.App.Run, which eventually calls template.Template.Execute #2: cmd\xxx\main.go:94:19: xxx.main calls kratos.App.Run, which eventually calls template.Template.ExecuteTemplate === Informational === Found 3 vulnerabilities in packages that you import, but there are no call stacks leading to the use of these vulnerabilities. You may not need to take any action. See https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck for details. Vulnerability #1: GO-2023-2045 Memory exhaustion in QUIC connection handling in crypto/tls More info: https://pkg.go.dev/vuln/GO-2023-2045 Standard library Found in: crypto/[email protected] Fixed in: crypto/[email protected] Vulnerability #2: GO-2023-2044 Panic when processing post-handshake message on QUIC connections in crypto/tls More info: https://pkg.go.dev/vuln/GO-2023-2044 Standard library Found in: crypto/[email protected] Fixed in: crypto/[email protected] Vulnerability #3: GO-2023-1988 Improper rendering of text nodes in golang.org/x/net/html More info: https://pkg.go.dev/vuln/GO-2023-1988 Module: golang.org/x/net Found in: golang.org/x/[email protected] Fixed in: golang.org/x/[email protected] Your code is affected by 3 vulnerabilities from the Go standard library. Share feedback at https://go.dev/s/govulncheck-feedback. 输出内容分为 2 块,第一块是 Vulnerability 信息,是项目中存在的漏洞,可以看到发现了 3 个漏洞及具体的原因、触发函数、发现的版本和修复的版本。第二块是 Informational 是一些没有直接引用的包中存在的安全漏洞。 ...

十月 14, 2023 · overstarry

Go WSDL

最近在实现一个功能时需要使用第三方的接口,由于第三方只提供了一个 WSDL 文件的链接,于是研究了 golang 如何解析 WSDL 并调用相应接口,本文就是介绍 WSDL 和 go 如何解析并调用。 WSDL 介绍 WSDL 是 Web Services Description Language(Web 服务描述语言)的缩写。它是一种用于描述基于 Web 服务的通信协议和消息格式的 XML 格式语言。 WSDL 被广泛用于描述 Web 服务的接口和操作。它定义了 Web 服务所提供的功能、方法、参数、数据类型以及与服务进行交互的方式。通过 WSDL 文件,客户端应用程序可以了解如何与特定的 Web 服务进行通信。 WSDL 文件通常包含以下几个主要部分: 服务定义:描述了 Web 服务的名称、命名空间和位置。 类型定义:定义了 Web 服务中使用的数据类型,例如字符串、整数等。 消息定义:定义了 Web 服务中使用的消息格式,包括输入和输出消息。 操作定义:定义了 Web 服务的操作或方法,包括输入和输出消息以及相关的参数。 绑定定义:定义了 Web 服务使用的通信协议和消息格式,例如 SOAP(Simple Object Access Protocol)和 HTTP(Hypertext Transfer Protocol)。 服务定义:将服务、绑定和端口等部分组合在一起,定义了 Web 服务的完整描述。 使用 WSDL,开发人员可以生成客户端代码,使其能够与 Web 服务进行交互。客户端可以根据 WSDL 文件了解 Web 服务的结构和可用方法,以便正确地构造请求和解析响应。 总之,WSDL 是一种用于描述 Web 服务接口和操作的语言,它提供了一种标准化的方式来描述和访问 Web 服务。 ...

九月 23, 2023 · overstarry

Nginx Ingress http 请求 413 状态码问题及解决方法

问题 最近在调用一个上传文件的接口时,发现接口调用响应状态码为 413,并且控制台显示跨域错误信息。查找了相关信息,得知 413 状态码表示请求的包体过大导致的。 出现这种情况,我想到了 2 种解决方案:1) 调整上传文件的方式 2) 调整网关的参数。 综合目前的现况,采取了第二种方式调整网关客户端请求体最大值的参数。 解决 通过查阅 nginx ingress 的文档,得知可以添加 nginx.ingress.kubernetes.io/proxy-body-size 注解来设置请求体的最大值,设置 nginx.ingress.kubernetes.io/proxy-body-size 值为合适的值后,再请求接口发现接口顺利响应。 小结 本文介绍了客户端请求接口时,由于 nginx 默认 proxy-body-size 参数太小,导致请求 413 的问题及相应的解决方案。 参考 https://opendocs.alipay.com/support/01rb44 https://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size https://github.com/kubernetes/ingress-nginx/blob/main/docs/user-guide/nginx-configuration/annotations.md#custom-max-body-size

九月 16, 2023 · overstarry

playwright-go 浏览器截图

以前的文章介绍了 chromedp 进行浏览器网页截图,这次介绍一种新的网页截图的方法——即使用 playwright 进行浏览器网页截图。 playwright 介绍 Playwright 是一个用于自动化浏览器操作的开源工具集。它由微软开发并于 2020 年发布,旨在提供一种跨浏览器、跨平台的解决方案,可用于测试 Web 应用程序、编写爬虫、执行自动化任务等。 Playwright 支持多种主流浏览器,包括 Chrome、Firefox、Safari 和 Edge,它提供了一组简单易用的 API,可以模拟用户与 Web 页面的交互行为,例如点击、填写表单、导航等。与其他类似工具相比,Playwright 的一个重要特点是它的跨浏览器支持,这意味着你可以使用相同的代码在不同浏览器上运行你的自动化任务,而不需要为每个浏览器单独编写代码。 Playwright 还提供了强大的调试功能,可以帮助开发人员在自动化过程中定位和解决问题。它支持截图和录制操作,使得调试变得更加直观和高效。 另外,Playwright 还具有一些高级功能,例如可以模拟不同的设备、网络环境和地理位置,以及支持并发执行多个浏览器实例等,这些功能使得它在编写复杂的自动化任务时非常有用。 由于我日常主要使用 go 语言进行开发,所以本文的内容主要以 playwright 的 go 模块 playwright-go 为主要介绍。 安装 go get -u github.com/playwright-community/playwright-go 安装相关浏览器和操作系统依赖项: go run github.com/playwright-community/playwright-go/cmd/playwright@latest install --with-deps # Or go install github.com/playwright-community/playwright-go/cmd/playwright@latest playwright install --with-deps 也可以在代码中使用以下代码安装:err := playwright.Install() 通过安装截图可以看出安装了 3 大主流浏览器和 ffmpeg。 例子 接下来我们看一个官方的例子,从 Hacker News 中抓取当前投票最高的项目。 package main import ( "fmt" "log" "github.com/playwright-community/playwright-go" ) func main() { pw, err := playwright.Run() if err != nil { log.Fatalf("could not start playwright: %v", err) } browser, err := pw.Chromium.Launch() if err != nil { log.Fatalf("could not launch browser: %v", err) } page, err := browser.NewPage() if err != nil { log.Fatalf("could not create page: %v", err) } if _, err = page.Goto("https://news.ycombinator.com"); err != nil { log.Fatalf("could not goto: %v", err) } entries, err := page.Locator(".athing").All() if err != nil { log.Fatalf("could not get entries: %v", err) } for i, entry := range entries { title, err := entry.Locator("td.title > span > a").TextContent() if err != nil { log.Fatalf("could not get text content: %v", err) } fmt.Printf("%d: %s\n", i+1, title) } if err = browser.Close(); err != nil { log.Fatalf("could not close browser: %v", err) } if err = pw.Stop(); err != nil { log.Fatalf("could not stop Playwright: %v", err) } } ...

九月 2, 2023 · overstarry

使用 Google Api Go Client 调用 Google Adsense 报告接口失败的问题及解决方案

问题 最近需要使用 Google Api Go Client 获取 Google Adsense 的数据,由于常规 generate 接口返回的数据是有 10w 行的限制,不满足目前的使用需要,于是使用了 csv 的生成方法 generateCsv,和 generate 相比 generateCsv 能返回更多的数据并且不需要改动太多参数。 在调用 generateCsv 接口时,发现接口不能返回正确数据,提示错误,错误信息如下: panic: invalid character 'D' looking for beginning of value 根据错误信息可以得知错误应该是解析错误,通过方法接口和源代码看出,接口返回的是 csv 类型的数据,但 api 只针对 json 进行解析,没有判断数据类型,导致解析失败。 解决 找到问题后,我立即就想到一个方法,就是在 decode 代码里添加 csv 格式的判断,尝试了一下,由于对 csv 解析方法不熟悉,并且修改 Client 代码会导致模块更新不方便 (客户端代码都是生成的,于是决定另寻它法。 通过寻找各种资料 issue 后,找到了一个可能可行的方法,就是创建手动创建 http 连接发送请求。 根据相应的文档,很快就写好了相应的代码: package main import ( "context" "fmt" "golang.org/x/oauth2" "golang.org/x/oauth2/google" "google.golang.org/api/adsense/v2" "google.golang.org/api/option" "google.golang.org/api/transport/http" "io" "net/url" ) func main() { ctx := context.Background() var config = &oauth2.Config{ ClientID: "xxx.apps.googleusercontent.com", // from https://console.developers.google.com/project/<your-project-id>/apiui/credential ClientSecret: "GOCSPX-xxxxxx", // from https://console.developers.google.com/project/<your-project-id>/apiui/credential Endpoint: google.Endpoint, Scopes: []string{adsense.AdsenseReadonlyScope}, RedirectURL: "https://developers.google.com", } //token := newOAuthClient(ctx, config) client, _, err := http.NewClient(ctx, option.WithTokenSource(config.TokenSource(ctx, &oauth2.Token{RefreshToken: "code"}))) if err != nil { return } URL, err := url.Parse(fmt.Sprintf("https://adsense.googleapis.com/v2/accounts/pub-123123/reports:generateCsv")) if err != nil { return } query := URL.Query() query.Set("dateRange", "YESTERDAY") query.Set("dimensions", "URL_CHANNEL_NAME") query.Set("metrics", "PAGE_VIEWS") URL.RawQuery = query.Encode() resp, err := client.Get(URL.String()) if err != nil { return } defer resp.Body.Close() all, err := io.ReadAll(resp.Body) if err != nil { return } fmt.Println(string(all)) } 主要逻辑就是创建到 google cloud service 的连接,调用 generateCsv 接口即可。 ...

八月 26, 2023 · overstarry

gonew 介绍

前几周。在关注的 go 提案每周会议中,发现了一条比较感兴趣的内容:gonew: templates for new modules ,通过标题可以猜到 gonew 应该是一个通过配置项目模板生成新项目的模块。 通过对 discussions 中 rsc 所描述的内容进行分析,可以得知为什么要启动这么一个新项目: go 团队经常收到用户的请求,想要通过模板启动一个新项目,即以某种基本的项目模板来创建一个新 Go module。Russ 私下编写了一个实现这个功能的小工具:rsc.io/tmp/gonew. Russ 在 google 内部宣传该工具后,Google 内部的一些团队便定制了一些模板 (template) ,尤其是 ServiceWeaver 团队的响应尤为积极。这一切最终让 Russ 决定引入 golang.org/x/tools/cmd/gonew。 gonew 工具的引入大幅简化了 Go 项目的创建,同时由于对自定义项目模板的支持,也可以提高 Go 项目的标准化水平。目前 gonew 工具是实验性的,后续可能会增加新的特性,但目前的核心功能是会保留的。 通过对 discussions 中社区用户开发着的回应可以看出,大家纷纷讲述了没有 gonew 前所使用的工具,并对 gonew 建言献策,可以看出大部分的开发者都十分欢迎这个新功能的。 接下来就由我来介绍 gonew。 安装 通过以下命令安装 gonew: go install golang.org/x/tools/cmd/gonew@latest $ go install golang.org/x/tools/cmd/gonew@latest go: downloading golang.org/x/tools v0.12.0 go: downloading golang.org/x/mod v0.12.0 执行 gonew: ...

八月 19, 2023 · overstarry

ent 相同列名排序问题解决

前言 最近在开发的时候,需要进行数据库计算,主要是根据表中的某些字段进行汇总计算,但由于数据库表中已有同名字段名,ent 不会使用计算后的指标,默认使用 schema 中定义的字段,导致无法返回正确的结果。 针对这种情况,我能想到的方法有 2 种:1) 不使用同名的字段名 2) 查找 ent 是否有相关的解决方案。 这里我采用了第二种方法,查找相关的 issues, 通过查找相关 issue,找到了相关的解决方案:ent 的 sql/modifier 特性。 场景重现 定义一个新的数据库表结构,结构如下: func (Ad) Fields() []ent.Field { return []ent.Field{ field.Float("estimated_earnings"), field.Int("page_views"), field.Time("date"), field.Float("page_views_rpm").Optional(), } } page_views_rpm 字段是由 estimated_earnings 和 page_views 计算而来。 编写相应的查询代码: package main import ( "context" "entgo.io/ent/dialect/sql" "fmt" _ "github.com/lib/pq" "log" "modifier-demo/ent" "modifier-demo/ent/ad" "time" ) type Ads struct { EstimatedEarnings float64 `json:"estimated_earnings"` PageViews int64 `json:"page_views"` Date time.Time `json:"date"` PageViewsRpm float64 `json:"page_views_rpm"` } func main() { client, err := ent.Open("postgres", "host=127.0.0.1 port=5432 sslmode=disable user=postgres dbname=data_test password=mysecretpassword") if err != nil { log.Fatalf("failed opening connection to postgres: %v", err) } defer client.Close() // Run the auto migration tool. if err := client.Schema.Create(context.Background()); err != nil { log.Fatalf("failed creating schema resources: %v", err) } var a []Ads err = client.Debug().Ad.Query().Order(ent.Desc(ad.FieldPageViewsRpm)).GroupBy(ad.FieldDate).Aggregate(func(selector *sql.Selector) string { return sql.As(" CAST(COALESCE(SUM(estimated_earnings) / NULLIF(SUM(page_views)*1.0, 0.0)*1000, 0)AS numeric(10,2))", "page_views_rpm") }).Scan(context.TODO(), &a) if err != nil { return } fmt.Println(a) } 主要是根据 date 来汇总并重新计算 page_views_rpm 字段,运行代码后发现没有成功输出,打印后发现 ent 使用了旧的 PageViewsRpm 字段进行排序,导致 sql 无法顺利运行。 ...

八月 12, 2023 · overstarry

Golang_embed 简单介绍

最近需要使用 golang1.16 中的功能 embed ,本文简单记录下 embed 的使用。 embed 介绍 Go 1.16 引入了 embed 包,允许我们在编译时将静态文件(例如 .go、.html、.css、.js 等)嵌入到 Go 源文件中。这在构建静态网站、单页应用程序(SPA)和其他项目时非常有用。 主要有几个优点: 方便部署:不需要再部署静态资源文件,所有的资源都直接嵌入到可执行文件中。 安全:用户无法直接访问或修改嵌入的文件。 版本管理:和 Go 代码一起版本控制。 使用 嵌入为字符串 可以将文件内容保存到字符串变量中。 package main import ( _ "embed" "fmt" ) //go:embed hello.txt var s string func main() { fmt.Println(s) } 文件路径下有个 hello.txt,内容如下:hello, overstarry,代码运行输出:hello, overstarry 保存为 []bytes 还可以将文件内容保存为 []bytes 变量 package main import ( _ "embed" "fmt" ) //go:embed hello.txt var s []byte func main() { fmt.Println(string(s)) } 运行代码输出内容与上面一致。 ...

八月 5, 2023 · overstarry