V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
winterssy
V2EX  ›  分享创造

分享两个接地气的 Golang 库

  •  
  •   winterssy · 2020-03-04 00:36:17 +08:00 · 3687 次点击
    这是一个创建于 808 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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 客户端

    功能特性

    • 简单易用的 API
    // 设置查询参数
    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"),
    	}),
    )
    
    • 基于 backoff 机制的自动重试策略
    ghttp.Post("https://api.example.com/login",
    	ghttp.WithBasicAuth("user", "[email protected]$"),
    	ghttp.EnableRetry(),
    )
    
    // 自定义最大尝试次数
    ghttp.Post("https://api.example.com/login",
    	ghttp.WithBasicAuth("user", "[email protected]$"),
    	ghttp.EnableRetry(ghttp.WithRetryMaxAttempts(5)),
    )
    
    // 自定义回退算法
    ghttp.Post("https://api.example.com/login",
    	ghttp.WithBasicAuth("user", "[email protected]$"),
    	ghttp.EnableRetry(ghttp.WithRetryBackoff(ghttp.NewFibonacciBackoff(30, time.Second))),
    )
    
    // 自定义触发器
    ghttp.Post("https://api.example.com/login",
    	ghttp.WithBasicAuth("user", "[email protected]$"),
    	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 的使用习惯,你可以快速地从标准库迁移。

    • ghttp.Client
    client := ghttp.New()
    // Now you can manipulate client like net/http
    client.CheckRedirect = ghttp.NoRedirect
    client.Timeout = 300 * time.Second
    
    • ghttp.Request
    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
    
    • ghttp.Response
    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 的用法感到困惑,可参考文档中的例程 /测试用例。

    13 条回复    2020-03-08 18:58:44 +08:00
    huson
        1
    huson  
       2020-03-04 01:09:00 +08:00
    看来不错 收藏了
    lxml
        2
    lxml  
       2020-03-04 01:35:08 +08:00 via Android
    ghttp 很适合我的需求
    blless
        3
    blless  
       2020-03-04 01:46:50 +08:00 via Android
    gjson 应该跟某个大牛的库重名了,那个库我觉得更强大一点,而且性能贼强
    github.com/tidwall/gjson
    winterssy
        4
    winterssy  
    OP
       2020-03-04 08:45:29 +08:00
    @blless #3 二者是不一样的,这里的 gjson 本质上是反序列化到 map[string]interface{} 并提供了友好的 API 获取 map 中的字段而已,本质上还是调用了标准库,而 tidwall/gjson 是一个 json-parser。
    lance6716
        5
    lance6716  
       2020-03-04 09:41:43 +08:00 via Android
    官方库是有多难用…整天看到轮子
    winterssy
        6
    winterssy  
    OP
       2020-03-04 09:58:43 +08:00 via Android
    @lance6716 通篇也没说官方库难用吧,再说这本来就基于标准库。我写给自己用的,分享给需要的人,不喜你当路过就行了
    blless
        7
    blless  
       2020-03-04 13:23:35 +08:00 via Android
    @winterssy 这么一说我倒是想起来 go-jsoniter 有个 Any 类型 API,跟你这个功能也类似,但是官方自己实现性能不是很好,我用 todwall 的 gjson 封装实现了一个 Any 接口 感觉挺好用的
    pkwenda
        8
    pkwenda  
       2020-03-04 14:20:39 +08:00
    自己造的轮子,有空学习下
    winterssy
        9
    winterssy  
    OP
       2020-03-04 17:56:27 +08:00
    @blless #7 捣鼓这玩意只是为了省事而已(有的时候不希望反序列化到结构体)。gjson 兼容 encoding/json 和 jsoniter,追求性能的话可切换 jsoniter 进行构建的( go build -tags=jsoniter .)
    wsseo
        11
    wsseo  
       2020-03-06 14:22:58 +08:00
    有机会用一下
    linvaux
        12
    linvaux  
       2020-03-08 18:36:48 +08:00
    go 的这个包管理我是到现在都接受不了
    Aether
        13
    Aether  
       2020-03-08 18:58:44 +08:00
    虽然如此,但正文里还是预先提一下这是自己的作品不是更好吗?
    关于   ·   帮助文档   ·   API   ·   FAQ   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1102 人在线   最高记录 5497   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 22:56 · PVG 06:56 · LAX 15:56 · JFK 18:56
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.