以下文章来源于进击的代码 ,作者阿然
本文将分享6个 Golang 实用小技巧:
01
Go 循环跳转语句使用标签的作用之一
Go 中的 for 循环可以使用 continue, break 进行控制,同时也可以标签
下面看一下使用标签的一个好处
示例:Go 求 N 以内的素数
package main
import (
"fmt"
)
func main() {
printPrime(10)
}
func printPrime(maxNum int) {
next:
for outer := 2; outer < maxNum; outer++ {
for inner := 2; inner < outer; inner++ {
if outer%inner == 0 {
continue next
}
}
fmt.Printf("%d\n", outer)
}
fmt.Println("Completed")
}
再看一下用C语言实现的(C 的 continue, break 不支持标签)
#include <stdio.h>
void printPrime(int maxNum)
{
int outer, inner;
int flag;
for (outer = 2; outer < maxNum; outer++)
{
flag = 1;
for (inner = 2; inner < outer; inner++)
{
if (outer % inner == 0)
{
flag = 0;
break;
}
}
if (flag == 1)
{
printf("%d\n", outer);
}
}
printf("Completed\n");
}
int main()
{
printPrime(10);
return 0;
}
可以看出,对于计算素数这个程序,使用循环使用跳转标签,可以省去如C语言 flag 这样的判断
02
Golang IP 地址字符串整数/string int 相互转换
Go 中没有 C 语言 inet_aton
和 inet_ntoa
类似的转换 IP 函数,
所以需要手动封装两个函数
(转换的方法有多种,我选择了简洁的)
package main
import (
"fmt"
"math/big"
"net"
)
func InetNtoA(ip int64) string {
return fmt.Sprintf("%d.%d.%d.%d",
byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip))
}
func InetAtoN(ip string) int64 {
ret := big.NewInt(0)
ret.SetBytes(net.ParseIP(ip).To4())
return ret.Int64()
}
func main() {
ip := "192.168.78.123"
ipInt := InetAtoN(ip)
fmt.Printf("convert string ip [%s] to int: %d\n", ip, ipInt)
fmt.Printf("convert int ip [%d] to string: %s\n", ipInt, InetNtoA(ipInt))
}
注:InetAtoN
最好加安全验证,检查 IP 字符串的有效性, 可以判断 net.ParseIP(ip).To4()
是否为 nil
可以使用 ping 命令简单验证一下转换的结果
03
Golang 八进制 utf-8 编码转中文
当调试程序,打印出变量的值时,有可能输出的是八进制 utf-8 编码(尤其是 protobuf 变量)
例如:\346\200\241\346\200\241\346\200
这样的字符串如果作为字面量,go 可以自动转化,
但如果是从文件或 string 中读取,就不行了。
可以使用正则表达式简单处理下
package main
import (
"fmt"
"regexp"
"strconv"
)
// 转换8进制utf-8字符串到中文
// eg: `\346\200\241` -> 怡
func convertOctonaryUtf8(in string) string {
s := []byte(in)
reg := regexp.MustCompile(`\\[0-7]{3}`)
out := reg.ReplaceAllFunc(s,
func(b []byte) []byte {
i, _ := strconv.ParseInt(string(b[1:]), 8, 0)
return []byte{byte(i)}
})
return string(out)
}
func main() {
s1 := "\346\200\241" // 字面量
s2 := `\346\200\241` // 原始字符串
fmt.Println("s1 =", s1)
fmt.Println("s2 =", s2)
// 转化 s2
s3 := convertOctonaryUtf8(s2)
fmt.Println("s3 =", s3)
}
04
Golang httputil 库,一行代码搞定 http 请求
在 golang 程序中进行 http 请求时,一般的步骤是:
如果后端程序每个 http 请求都如上面这样处理,将产生很多相似的代码,
并且在复制代码的时候,容易出错(需要修改的地方忘记修改),浪费 debug 的时间。
于是,我封装了一个简便的 httputil 库,用于 http 请求。
详见 https://github.com/chinaran/httputil
get, post, put, patch, delete
方法string, []byte, map, struct
作为 request 和 response 数据application/json
package main
import (
"context"
"log"
hu "github.com/chinaran/httputil"
)
func main() {
// get
urlGet := "https://httpbin.org/get?hello=world"
respGetM := map[string]interface{}{}
if err := hu.Get(context.TODO(), urlGet, &respGetM, hu.WithLogTimeCost()); err != nil {
log.Printf("Get %s err: %s", urlGet, err)
return
}
log.Printf("Get %s map response: %+v", urlGet, respGetM)
respGetStr := ""
if err := hu.Get(context.TODO(), urlGet, &respGetStr, hu.WithLogTimeCost()); err != nil {
log.Printf("Get %s err: %s", urlGet, err)
return
}
log.Printf("Get %s string response: %+v", urlGet, respGetStr)
// post
urlPost := "https://httpbin.org/post"
req := map[string]string{"hello": "world"}
respPost := struct {
Data string `json:"data"`
}{}
if err := hu.Post(context.TODO(), urlPost, &req, &respPost, hu.WithLogTimeCost()); err != nil {
log.Printf("Post %s err: %s", urlPost, err)
return
}
log.Printf("Post %s struct response: %+v", urlPost, respPost)
}
05
Golang debuglog 库,调试程序时快捷查看变量值
在调试 golang 程序时,加断点查看变量值固然是一种方法,但更多的时候只是简单的加个 log 看一下。
可以 fmt.Printf("%+v", xxx)
, 如果想查看 json 还要转换,很麻烦。
于是,我封装了一个简便的 debuglog 库,用于 debug 变量。
详见 https://github.com/chinaran/debuglog
debuglog.Val()
: 打印变量debuglog.SpewVal()
: 使用 spew
库打印变量(可以详细看到结构体每个字段的定义和值)debuglog.ToJson()
: 转成 json 字符串打印debuglog.ToJsonPretty()
: 有缩进和换行的 json 字符串调试好程序解决 bug 后,删掉所有的 debuglog
即可。
package main
import "github.com/chinaran/debuglog"
type TestJson struct {
Id int64
Name string
}
func main() {
intVal := 123
debuglog.Val(intVal)
debuglog.Val(intVal, "prefix1")
debuglog.Val(intVal, "prefix1", "prefix2")
testJson := TestJson{Id: 987, Name: "alan"}
debuglog.SpewVal(testJson, "testJson Spew")
// debuglog.OctUtf8Val(testJson, "testJson")
debuglog.ToJson(testJson, "testJson")
debuglog.ToJsonPretty(testJson, "testJson Pretty")
}
06
go-httpbin, http 请求测试工具推荐
A simple HTTP Request & Response Service (written in Python + Flask).
一个简单的 HTTP 请求和回复测试服务。
详见: https://httpbin.org/
http 开发调试
httpbin 提供了 GET/PUT/POST/PATCH/DELETET
常见方法,可以把请求头、参数等返回,类似 Echo 服务。方便调试 http 请求。
http 库测试
使用开源或者自己项目封装的 http 请求库,可以把 httpbin 作为服务端,测试各种场景。
除了在线的 https://httpbin.org/,也可以本地部署。
云原生示例服务
可作为演示服务,测试平台功能。例如,isito 就有 httpbin sample 服务,用来实验 istio 各种特性
详见: https://github.com/istio/istio/tree/master/samples/httpbin
/get 接口
/put, /post, /patch
还会打印出请求体数据
curl 'https://httpbin.org/get?paramA=aa'
{
"args": {
"paramA": "aa"
},
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"User-Agent": "curl/7.64.1",
"X-Amzn-Trace-Id": "Root=1-6107dcac-1d6d3fb32f67d8184c184992"
},
"origin": "111.207.117.50",
"url": "https://httpbin.org/get?paramA=aa"
}
/status/{codes} 接口
用来返回特定的状态码
curl 'https://httpbin.org/status/418'
-=[ teapot ]=-
_...._
.' _ _ `.
| ."` ^ `". _,
\_;`"---"`|//
| ;/
\_ _/
`"""`
/ip 接口
返回原始 ip
curl 'https://httpbin.org/ip'
{
"origin": "xxx.xxx.xxx.xxx"
}
/delay/{delay} 接口
延时一定时间返回
还有很多测试接口,可在官网查看
httpbin 的 github https://github.com/postmanlabs/httpbin 在 2018.11 后就没有更新了,有一些需求和 bug 都没有解决。
无意将发现了 go-httpbin, 是一个只使用 golang 标准库实现的 httpbin。
所以我 fork 了 mccutchen/go-httpbin 项目,并添加了一些特性。
详见: https://github.com/chinaran/go-httpbin
可以直接使用 docker 尝试运行
docker run -p 8080:80 ghcr.io/chinaran/go-httpbin:1.2-alpine3.13
示例:(请求注入了 sidecar 的 httpbin 服务)
kubectl -n ran-test exec -it client-54d574b9b-6w9xc -- curl http://go-httpbin/get
{
"envs": {
"HOSTNAME": "go-httpbin-6f55bbcfcd-skrxl"
},
"args": {},
"headers": {
"Accept": "*/*",
"Content-Length": "0",
"Host": "go-httpbin",
"User-Agent": "curl/7.76.1",
"X-B3-Parentspanid": "d4ae8aad7301da2d",
"X-B3-Sampled": "1",
"X-B3-Spanid": "68b5055ffd105f5b",
"X-B3-Traceid": "26e3ee5a3196b1bcd4ae8aad7301da2d",
"X-Client-Name": "client",
"X-Client-Namespace": "ran-test",
"X-Envoy-Attempt-Count": "1",
"X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/ran-test/sa/default;Hash=0cb9c538a64d9f747c86731252bd28d6e9121c7cf0be731f2484fd3b4a1af57f;Subject=\"\";URI=spiffe://cluster.local/ns/ran-test/sa/default",
"X-Forwarded-Proto": "http",
"X-Request-Id": "1f2a3700-1bdf-9a0a-9091-2c6aba324606"
},
"origin": "127.0.0.1:46756",
"url": "http://go-httpbin/get"
}