分享两个接地气的 Golang 库

2020-03-04 00:36:17 +08:00
 winterssy

gjson —— 方便地获取 JSON 中的任意数据

package main

import (
	"fmt"
	"log"

	"github.com/winterssy/gjson"
)

const dummyData = `
{
  "code": 200,
  "data": {
    "list": [
      {
        "artist": "周杰伦",
        "album": "周杰伦的床边故事",
        "name": "告白气球"
      },
      {
        "artist": "周杰伦",
        "album": "说好不哭 (with 五月天阿信)",
        "name": "说好不哭 (with 五月天阿信)"
      }
    ]
  }
}
`

type S struct {
	Code int          `json:"code"`
	Data gjson.Object `json:"data"`
}

func main() {
	// 直接解析到 gjson.Object
	obj, err := gjson.ParseFromString(dummyData)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(obj.GetNumber("code"))
	fmt.Println(obj.GetArray("data", "list").Index(0).ToObject().GetString("name"))
	// Output:
	// 200
	// 告白气球

	var s S
	// 绑定到结构体
	err = gjson.UnmarshalFromString(dummyData, &s)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(s.Code)
	fmt.Println(s.Data.GetArray("list").Index(0).ToObject().GetString("name"))
	// Output:
	// 200
	// 告白气球
}

ghttp —— 好用的 HTTP 客户端

功能特性

// 设置查询参数
ghttp.Post("https://httpbin.org/post",
	ghttp.WithQuery(ghttp.Params{
		"k1": "v1",
		"k2": "v2",
	}),
)

// 设置请求头
ghttp.Post("https://httpbin.org/post",
	ghttp.WithHeaders(ghttp.Headers{
		"k1": "v1",
		"k2": "v2",
	}),
)

// 发送 Form 表单
ghttp.Post("https://httpbin.org/post",
	ghttp.WithForm(ghttp.Form{
		"k1": "v1",
		"k2": "v2",
	}),
)

// 上传文件
ghttp.Post("https://httpbin.org/post",
	ghttp.WithFiles(ghttp.Files{
		"file1": ghttp.MustOpen("/path/to/testfile1.txt"),
		"file2": ghttp.MustOpen("/path/to/testfile2.txt"),
		"file3": ghttp.FileFromReader(strings.NewReader("some,data,to,send\nanother,row,to,send\n")).WithFilename("report.csv"),
	}),
)
ghttp.Post("https://api.example.com/login",
	ghttp.WithBasicAuth("user", "p@ssw$"),
	ghttp.EnableRetry(),
)

// 自定义最大尝试次数
ghttp.Post("https://api.example.com/login",
	ghttp.WithBasicAuth("user", "p@ssw$"),
	ghttp.EnableRetry(ghttp.WithRetryMaxAttempts(5)),
)

// 自定义回退算法
ghttp.Post("https://api.example.com/login",
	ghttp.WithBasicAuth("user", "p@ssw$"),
	ghttp.EnableRetry(ghttp.WithRetryBackoff(ghttp.NewFibonacciBackoff(30, time.Second))),
)

// 自定义触发器
ghttp.Post("https://api.example.com/login",
	ghttp.WithBasicAuth("user", "p@ssw$"),
	ghttp.EnableRetry(ghttp.WithRetryTriggers(func(resp *ghttp.Response, err error) bool {
		return err != nil || resp.StatusCode != 200
	})),
)
// 全局请求头
ghttp.DefaultClient.RegisterBeforeRequestCallbacks(
	ghttp.WithUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"),
	ghttp.WithReferer("https://www.google.com"),
)

// 全局重试策略
ghttp.DefaultClient.RegisterBeforeRequestCallbacks(ghttp.EnableRetry())

// 反向代理
withReverseProxy := func(target string) ghttp.RequestHook {
	return func(req *ghttp.Request) error {
		u, err := url.Parse(target)
		if err != nil {
			return err
		}

		req.URL.Scheme = u.Scheme
		req.URL.Host = u.Host
		req.Host = u.Host
		req.SetOrigin(u.Host)
		return nil
	}
}
ghttp.DefaultClient.RegisterBeforeRequestCallbacks(withReverseProxy("https://httpbin.org"))
ghttp.Get("/get")
ghttp.Post("/post")
ghttp.Put("/put")
ghttp.Patch("/patch")
ghttp.Delete("/delete")
// 速率限制
ghttp.DefaultClient.EnableRateLimiting(rate.NewLimiter(1, 10))
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
	wg.Add(1)
	go func() {
		defer wg.Done()
		ghttp.Get("https://www.example.com")
	}()
}
wg.Wait()

// 限制最大并发数
ghttp.DefaultClient.SetMaxConcurrency(32)
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
	wg.Add(1)
	go func() {
		defer wg.Done()
		ghttp.Get("https://www.example.com")
	}()
}
wg.Wait()
ghttp.DefaultClient.EnableDebugging(os.Stdout, true)
ghttp.Post("https://httpbin.org/post",
	ghttp.WithForm(ghttp.Form{
		"k1": "v1",
		"k2": "v2",
	}),
)
// Output:
// > POST /post HTTP/1.1
// > Host: httpbin.org
// > Content-Type: application/x-www-form-urlencoded
// >
// k1=v1&k2=v2
// < HTTP/2.0 200 OK
// < Access-Control-Allow-Origin: *
// < Access-Control-Allow-Credentials: true
// < Date: Tue, 03 Mar 2020 14:17:30 GMT
// < Content-Type: application/json
// < Content-Length: 422
// < Server: gunicorn/19.9.0
// <
// {
//   "args": {},
//   "data": "",
//   "files": {},
//   "form": {
//     "k1": "v1",
//     "k2": "v2"
//   }, 
//   "headers": {
//     "Accept-Encoding": "gzip",
//     "Content-Length": "0",
//     "Content-Type": "application/x-www-form-urlencoded",
//     "Host": "httpbin.org",
//     "User-Agent": "Go-http-client/2.0",
//     "X-Amzn-Trace-Id": "Root=1-5e5e66fa-46092511ae526478bf2e637f"
//   },
//   "json": null,
//   "origin": "8.8.8.8",
//   "url": "https://httpbin.org/post"
// }

从标准库迁移

ghttp 保留了 net/http 的使用习惯,你可以快速地从标准库迁移。

client := ghttp.New()
// Now you can manipulate client like net/http
client.CheckRedirect = ghttp.NoRedirect
client.Timeout = 300 * time.Second
req, err := ghttp.NewRequest("GET", "https://httpbin.org/get")
if err != nil {
    log.Fatal(err)
}
// Now you can manipulate req like net/http
req.Close = true
resp, err := ghttp.Get("https://www.google.com")
if err != nil {
    log.Fatal(err)
}
// Now you can access resp like net/http
fmt.Println(resp.StatusCode)

API 文档

ghttp 没有晦涩难懂的代码,同时具备完整的 API 文档和非常高的测试覆盖率,如果你对某 API 的用法感到困惑,可参考文档中的例程 /测试用例。

4177 次点击
所在节点    分享创造
13 条回复
huson
2020-03-04 01:09:00 +08:00
看来不错 收藏了
lxml
2020-03-04 01:35:08 +08:00
ghttp 很适合我的需求
blless
2020-03-04 01:46:50 +08:00
gjson 应该跟某个大牛的库重名了,那个库我觉得更强大一点,而且性能贼强
github.com/tidwall/gjson
winterssy
2020-03-04 08:45:29 +08:00
@blless #3 二者是不一样的,这里的 gjson 本质上是反序列化到 map[string]interface{} 并提供了友好的 API 获取 map 中的字段而已,本质上还是调用了标准库,而 tidwall/gjson 是一个 json-parser。
lance6716
2020-03-04 09:41:43 +08:00
官方库是有多难用…整天看到轮子
winterssy
2020-03-04 09:58:43 +08:00
@lance6716 通篇也没说官方库难用吧,再说这本来就基于标准库。我写给自己用的,分享给需要的人,不喜你当路过就行了
blless
2020-03-04 13:23:35 +08:00
@winterssy 这么一说我倒是想起来 go-jsoniter 有个 Any 类型 API,跟你这个功能也类似,但是官方自己实现性能不是很好,我用 todwall 的 gjson 封装实现了一个 Any 接口 感觉挺好用的
pkwenda
2020-03-04 14:20:39 +08:00
自己造的轮子,有空学习下
winterssy
2020-03-04 17:56:27 +08:00
@blless #7 捣鼓这玩意只是为了省事而已(有的时候不希望反序列化到结构体)。gjson 兼容 encoding/json 和 jsoniter,追求性能的话可切换 jsoniter 进行构建的( go build -tags=jsoniter .)
a308057848
2020-03-05 22:03:40 +08:00
ghttp 感觉 gout 好点
wsseo
2020-03-06 14:22:58 +08:00
有机会用一下
linvaux
2020-03-08 18:36:48 +08:00
go 的这个包管理我是到现在都接受不了
Aether
2020-03-08 18:58:44 +08:00
虽然如此,但正文里还是预先提一下这是自己的作品不是更好吗?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/649616

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX