V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
guonaihong
V2EX  ›  程序员

http 请求库 gout v0.0.10 版本,增加新特性

  •  
  •   guonaihong ·
    guonaihong · 2020-03-31 09:28:48 +08:00 · 542 次点击
    这是一个创建于 1690 天前的主题,其中的信息可能已经有所发展或是发生改变。

    gout

    gout 是 go 写的 http 客户端,为提高工作效率而开发

    Build Status Go codecov Go Report Card

    地址

    https://github.com/guonaihong/gout

    构架

    gout-ad.png

    feature

    • 支持设置 GET/PUT/DELETE/PATH/HEAD/OPTIONS
    • 支持设置请求 http header(可传 struct,map,array,slice 等类型)
    • 支持设置 URL query(可传 struct,map,array,slice,string 等类型)
    • 支持设置 json 编码到请求 body 里面(可传 struct, map, string, []byte 等类型)
    • 支持设置 xml 编码到请求 body 里面(可传 struct, string, []byte 等类型)
    • 支持设置 yaml 编码到请求 body 里面(可传 struct, map, string, []byte 等类型)
    • 支持设置 form-data(可传 struct, map, array, slice 等类型)
    • 支持设置 x-www-form-urlencoded(可传 struct,map,array,slice 等类型)
    • 支持设置 io.Reader,uint/uint8/uint16...int/int8...string...[]byte...float32,float64 至请求 body 里面
    • 支持解析响应 body 里面的 json,xml,yaml 至结构体里(BindJSON/BindXML/BindYAML)
    • 支持解析响应 body 的内容至 io.Writer, uint/uint8...int/int8...string...[]byte...float32,float64
    • 支持解析响应 header 至结构体里
    • 支持接口性能 benchmark,可控制压测一定次数还是时间,可控制压测频率
    • 支持 retry-backoff,可以指定重试条件
    • 支持发送裸 http 数据包
    • 支持导出 curl 命令
    • 等等更多

    演示

    <details>

    gout-example.gif

    </details>

    内容

    Installation

    go get github.com/guonaihong/gout
    

    example

    examples 目录下面的例子,都是可以直接跑的。如果觉得运行例子还是不明白用法,可以把你迷惑的地方写出来,然后提issue

    运行命令如下

    cd _example
    # GOPROXY 是打开 go module 代理,可以更快下载模块
    # 第一次运行需要加 GOPROXY 下载模块,模块已的直接 go run 01-color-json.go 即可
    env GOPROXY=https://goproxy.cn go run 01-color-json.go
    

    quick start

    package main
    
    import (
       "fmt"
       "github.com/guonaihong/gout"
       "time"
    )
    
    // 用于解析 服务端 返回的 http body
    type RspBody struct {
       ErrMsg  string `json:"errmsg"`
       ErrCode int    `json:"errcode"`
       Data    string `json:"data"`
    }
    
    // 用于解析 服务端 返回的 http header
    type RspHeader struct {
       Sid  string `header:"sid"`
       Time int    `header:"time"`
    }
    
    func main() {
       rsp := RspBody{}
       header := RspHeader{}
    
       //code := 0
       err := gout.
    
       	// POST 请求
       	POST("127.0.0.1:8080").
    
       	// 打开 debug 模式
       	Debug(true).
    
       	// 设置查询字符串
       	SetQuery(gout.H{"page": 10, "size": 10}).
    
       	// 设置 http header
       	SetHeader(gout.H{"X-IP": "127.0.0.1", "sid": fmt.Sprintf("%x", time.Now().UnixNano())}).
    
       	// SetJSON 设置 http body 为 json
       	// 同类函数有 SetBody, SetYAML, SetXML, SetForm, SetWWWForm
       	SetJSON(gout.H{"text": "gout"}).
    
       	// BindJSON 解析返回的 body 内容
       	// 同类函数有 BindBody, BindYAML, BindXML
       	BindJSON(&rsp).
    
       	// 解析返回的 http header
       	BindHeader(&header).
       	// http code
       	// Code(&code).
    
       	// 结束函数
       	Do()
    
       	// 判度错误
       if err != nil {
       	fmt.Printf("send fail:%s\n", err)
       }
    }
    
    /*
    > POST /?page=10&size=10 HTTP/1.1
    > Sid: 15d9b742ef32c130
    > X-Ip: 127.0.0.1
    > Content-Type: application/json
    >
    
    {
       "text": "gout"
    }
    
    
    */
    

    API examples

    GET POST PUT DELETE PATH HEAD OPTIONS

    package main
    
    import (
    	"github.com/guonaihong/gout"
    )
    
    func main() {
    	url := "https://github.com"
    	// 发送 GET 方法
    	gout.GET(url).Do()
    
    	// 发送 POST 方法
    	gout.POST(url).Do()
    
    	// 发送 PUT 方法
    	gout.PUT(url).Do()
    
    	// 发送 DELETE 方法
    	gout.DELETE(url).Do()
    
    	// 发送 PATH 方法
    	gout.PATCH(url).Do()
    
    	// 发送 HEAD 方法
    	gout.HEAD(url).Do()
    
    	// 发送 OPTIONS
    	gout.OPTIONS(url).Do()
    }
    
    

    Query Parameters

    SetQuery

    package main
    
    import (
        "fmt"
        "github.com/guonaihong/gout"
        "time"
    )
    
    func main() {
        err := gout.
            //设置 GET 请求和 url,:8080/test.query 是 127.0.0.1:8080/test.query 的简写
            GET(":8080/test.query").
            //打开 debug 模式
            Debug(true).
            //设置查询字符串
            SetQuery(gout.H{
                "q1": "v1",
                "q2": 2,
                "q3": float32(3.14),
                "q4": 4.56,
                "q5": time.Now().Unix(),
                "q6": time.Now().UnixNano(),
                "q7": time.Now().Format("2006-01-02")}).
            //结束函数
            Do()
        if err != nil {
            fmt.Printf("%s\n", err)
            return
        }
    
    }
    
    /*
    > GET /test.query?q1=v1&q2=2&q3=3.14&q4=4.56&q5=1574081600&q6=1574081600258009213&q7=2019-11-18 HTTP/1.1
    >
    
    < HTTP/1.1 200 OK
    < Content-Length: 0
    */
    
    
    

    SetQuery 支持的更多数据类型

    <details>
    package main
    
    import (
    	"github.com/guonaihong/gout"
    )
    
    func main() {
    
    	code := 0
    
    	err := gout.
    
    		//发送 GET 请求 :8080/testquery 是 127.0.0.1:8080/testquery 简写
    		GET(":8080/testquery").
    
    		// 设置查询字符串
    		SetQuery( /*看下面支持的情况*/ ).
    
    		//解析 http code,如不关心服务端返回状态吗,不设置该函数即可
    		Code(&code).
    		Do()
    	if err != nil {
    
    	}
    }
    
    
    
    /*
    SetQuery 支持的类型有
    * string
    * map[string]interface{},可以使用 gout.H 别名
    * struct
    * array, slice(长度必须是偶数)
    */
    
    // 1.string
    SetQuery("check_in=2019-06-18&check_out=2018-06-18")
    
    // 2.gout.H 或者 map[string]interface{}
    SetQuery(gout.H{
        "check_in":"2019-06-18",
        "check_out":"2019-06-18",
    })
    
    // 3.struct
    type testQuery struct {
        CheckIn string `query:checkin`
        CheckOut string `query:checkout`
    }
    
    SetQuery(&testQuery{CheckIn:2019-06-18, CheckOut:2019-06-18})
    
    // 4.array or slice
    // ?active=enable&action=drop
    SetQuery([]string{"active", "enable", "action", "drop"})`
    
    </details>

    http header

    Set request header

    package main
    
    import (
        "fmt"
        "github.com/guonaihong/gout"
        "time"
    )
    
    func main() {
        err := gout.
            //设置 GET 请求和 url,:8080/test.header 是 127.0.0.1:8080/test.header 的简写
            GET(":8080/test.header").
            //设置 debug 模式
            Debug(true).
            //设置请求 http header
            SetHeader(gout.H{
                "h1": "v1",
                "h2": 2,
                "h3": float32(3.14),
                "h4": 4.56,
                "h5": time.Now().Unix(),
                "h6": time.Now().UnixNano(),
                "h7": time.Now().Format("2006-01-02")}).
            Do()
        if err != nil {
            fmt.Printf("%s\n", err)
            return
        }
    
    }
    
    /*
    > GET /test.header HTTP/1.1
    > H2: 2
    > H3: 3.14
    > H4: 4.56
    > H5: 1574081686
    > H6: 1574081686471347098
    > H7: 2019-11-18
    > H1: v1
    >
    
    
    < HTTP/1.1 200 OK
    < Content-Length: 0
    */
    

    Parsing the response header

    package main
    
    import (
        "fmt"
        "github.com/guonaihong/gout"
        "time"
    )
    
    // 和解析 json 类似,如要解析 http header 需设置 header tag
    type rspHeader struct {
        Total int       `header:"total"`
        Sid   string    `header:"sid"`
        Time  time.Time `header:"time" time_format:"2006-01-02"`
    }
    
    func main() {
    
        rsp := rspHeader{}
        err := gout.
            // :8080/test.header 是 http://127.0.0.1:8080/test.header 的简写
            GET(":8080/test.header").
            //打开 debug 模式
            Debug(true).
            //解析请求 header 至结构体中
            BindHeader(&rsp). 
            //结束函数
            Do()
        if err != nil {
            fmt.Printf("%s\n", err)
            return
        }
    
        fmt.Printf("rsp header:\n%#v \nTime:%s\n", rsp, rsp.Time)
    }
    
    /*
    > GET /test.header HTTP/1.1
    >
    
    
    
    < HTTP/1.1 200 OK
    < Content-Length: 0
    < Sid: 1234
    < Time: 2019-11-18
    < Total: 2048
    */
    
    

    SetHeader 和 BindHeader 支持的更多类型

    <details>
    package main
    
    import (
        "fmt"
        "github.com/guonaihong/gout"
    )
    
    type testHeader struct {
        CheckIn  string `header:checkin`
        CheckOut string `header:checkout`
    }
    
    func main() {
    
        t := testHeader{}
    
        code := 0
    
        err := gout.
            GET(":8080/testquery").
            Code(&code).
            SetHeader( /*看下面支持的类型*/ ).
            BindHeader(&t).
            Do()
        if err != nil {
            fmt.Printf("fail:%s\n", err)
        }   
    }
    
    
    • BindHeader 支持的类型有 结构体
    // struct
    type testHeader struct {
        CheckIn string `header:checkin`
        CheckOut string `header:checkout`
    }
    
    • SetHeader 支持的类型有
    /*
    map[string]interface{},可以使用 gout.H 别名
    struct
    array, slice(长度必须是偶数)
    */
    
    // gout.H 或者 map[string]interface{}
    SetHeader(gout.H{
        "check_in":"2019-06-18",
        "check_out":"2019-06-18",
    })
    
    // struct
    type testHeader struct {
        CheckIn string `header:checkin`
        CheckOut string `header:checkout`
    }
    
    SetHeader(&testHeader{CheckIn:2019-06-18, CheckOut:2019-06-18})
    
    // array or slice
    // -H active:enable -H action:drop
    SetHeader([]string{"active", "enable", "action", "drop"})
    
    </details>

    http body

    body

    Set the data to the http request body

    // SetBody 设置 string, []byte 等类型数据到 http body 里面
    // SetBody 支持的更多数据类型可看下面
    package main
    
    import (
    	"fmt"
    	"github.com/guonaihong/gout"
    )
    
    func main() {
    	err := gout.
    		// 设置 POST 方法和 url
    		POST(":8080/req/body").
    		//打开 debug 模式
    		Debug(true).
    		// 设置非结构化数据到 http body 里面
    		// 设置 json 需使用 SetJSON
    		SetBody("send string").
    		//结束函数
    		Do()
    
    	if err != nil {
    		fmt.Printf("%s\n", err)
    		return
    	}
    
    }
    
    /*
    > POST /req/body HTTP/1.1
    >
    
    send string
    
    < HTTP/1.1 200 OK
    < Content-Type: text/plain; charset=utf-8
    < Content-Length: 2
    
    */
    
    

    Parse the response body into a variable

    // BindBody bind body 到 string, []byte 等类型变量里面
    package main
    
    import (
    	"fmt"
    	"github.com/guonaihong/gout"
    )
    
    func main() {
    	s := ""
    	err := gout.
    		// 设置 GET 方法及 url
    		GET("www.baidu.com").
    		// 绑定返回值
    		BindBody(&s).
    		// 结束函数
    		Do()
    
    	if err != nil {
    		fmt.Printf("%s\n", err)
    		return
    	}
    
    	fmt.Printf("html size = %d\n", len(s))
    }
    
    

    支持的类型有

    • io.Reader(SetBody 支持)
    • io.Writer(BindBody 支持)
    • int, int8, int16, int32, int64
    • uint, uint8, uint16, uint32, uint64
    • string
    • []byte
    • float32, float64

    明确不支持的类型有

    • struct
    • array, slice

    json

    Serialize json to request body

    更多支持数据类型及用法

    package main
    
    import (
    	"fmt"
    	"github.com/guonaihong/gout"
    )
    
    func main() {
    	err := gout.POST(":8080/colorjson").
    		//打开 debug 模式
    		Debug(true).
    		//设置 json 到请求 body
    		SetJSON(
    			gout.H{
    				"str":   "foo",
    				"num":   100,
    				"bool":  false,
    				"null":  nil,
    				"array": gout.A{"foo", "bar", "baz"},
    				"obj":   gout.H{"a": 1, "b": 2},
    			},
    		).
    		Do()
    
    	if err != nil {
    		fmt.Printf("err = %v\n", err)
    	}
    }
    
    /*
    > POST /colorjson HTTP/1.1
    > Content-Type: application/json
    >
    
    {
        "array": [
            "foo",
            "bar",
            "baz"
        ],
        "bool": false,
        "null": null,
        "num": 100,
        "obj": {
            "a": 1,
            "b": 2
        },
        "str": "foo"
    }
    */
    
    

    Parsed http response body in json format

    package main
    
    import (
    	"fmt"
    	"github.com/guonaihong/gout"
    )
    
    type rsp struct {
    	ErrMsg  string `json:"errmsg"`
    	ErrCode int    `json:"errcode"`
    }
    
    func main() {
    	rsp := rsp{}
    	err := gout.
    		GET(":8080/colorjson").
    		//打开 debug 模式
    		Debug(true).
    		//绑定响应 json 数据到结构体
    		BindJSON(&rsp).
    		//结束函数
    		Do()
    
    	if err != nil {
    		fmt.Printf("err = %v\n", err)
    	}
    }
    
    

    yaml

    • SetYAML() 设置请求 http body 为 yaml
    • BindYAML() 解析响应 http body 里面的 yaml 到结构体里面

    发送 yaml 到服务端,然后把服务端返回的 yaml 结果解析到结构体里面

    type data struct {
        Id int `yaml:"id"`
        Data string `yaml:"data"`
    }
    
    
    var d1, d2 data
    var httpCode int 
    
    
    err := gout.POST(":8080/test.yaml").SetYAML(&d1).BindYAML(&d2).Code(&httpCode).Do()
    if err != nil || httpCode != 200{
        fmt.Printf("send fail:%s\n", err)
    }
    
    

    xml

    • SetXML() 设置请求 http body 为 xml
    • BindXML() 解析响应 http body 里面的 xml 到结构体里面

    发送 xml 到服务端,然后把服务端返回的 xml 结果解析到结构体里面

    type data struct {
        Id int `xml:"id"`
        Data string `xml:"data"`
    }
    
    
    var d1, d2 data
    var httpCode int 
    
    
    err := gout.POST(":8080/test.xml").SetXML(&d1).BindXML(&d2).Code(&httpCode).Do()
    if err != nil || httpCode != 200{
        fmt.Printf("send fail:%s\n", err)
    }
    
    

    form-data

    • SetForm() 设置 http body 为 multipart/form-data 格式数据

    客户端发送 multipart/form-data 到服务端,curl 用法等同 go 代码

    curl -F mode=A -F text="good" -F voice=@./test.pcm -f voice2=@./test2.pcm url
    
    • 使用 gout.H
    package main
    
    import (
    	"fmt"
    	"github.com/guonaihong/gout"
    )
    
    func main() {
    
    	code := 0
    	err := gout.
    		POST(":8080/test").
    		// 打开 debug 模式
    		Debug(true).
    		SetForm(
    			gout.H{
    				"mode": "A",
    				"text": "good",
    				// 从文件里面打开
    				"voice":  gout.FormFile("test.pcm"),
    				"voice2": gout.FormMem("pcm"),
    			},
    		).
    		//解析 http code,如不关心可以不设置
    		Code(&code).
    		Do()
    
    	if err != nil {
    		fmt.Printf("%s\n", err)
    	}
    
    	if code != 200 {
    	}
    }
    
    /*
    > POST /test HTTP/1.1
    > Content-Type: multipart/form-data; boundary=2b0685e5b98e540f80b247d5e7c1283807aa07e62b827543859a6db765a8
    >
    
    --2b0685e5b98e540f80b247d5e7c1283807aa07e62b827543859a6db765a8
    Content-Disposition: form-data; name="mode"
    
    A
    --2b0685e5b98e540f80b247d5e7c1283807aa07e62b827543859a6db765a8
    Content-Disposition: form-data; name="text"
    
    good
    --2b0685e5b98e540f80b247d5e7c1283807aa07e62b827543859a6db765a8
    Content-Disposition: form-data; name="voice"; filename="voice"
    Content-Type: application/octet-stream
    
    pcm pcm pcm
    
    --2b0685e5b98e540f80b247d5e7c1283807aa07e62b827543859a6db765a8
    Content-Disposition: form-data; name="voice2"; filename="voice2"
    Content-Type: application/octet-stream
    
    pcm
    --2b0685e5b98e540f80b247d5e7c1283807aa07e62b827543859a6db765a8--
    
    
    < HTTP/1.1 200 OK
    < Server: gurl-server
    < Content-Length: 0
    */
     
    
    • 使用结构体
    type testForm struct {
        Mode string `form:"mode"`
        Text string `form:"text"`
        Voice string `form:"voice" form-file:"true"` //从文件中读取 
        Voice2 []byte `form:"voice2" form-file:"mem"`  //从内存中构造
    }
    
    type rsp struct{
        ErrMsg string `json:"errmsg"`
        ErrCode int `json:"errcode"`
    }
    
    t := testForm{}
    r := rsp{}
    code := 0
    
    err := gout.POST(url).SetForm(&t).ShoudBindJSON(&r).Code(&code).Do()
    if err != nil {
    
    }
    

    x-www-form-urlencoded

    • 使用 SetWWWForm 函数实现发送 x-www-form-urlencoded 类型数据
    package main
    
    import (
    	"fmt"
    	"github.com/guonaihong/gout"
    )
    
    func main() {
    
    	code := 0
    	err := gout.
    		POST(":8080/post").
    		// 打开 debug 模式
    		Debug(true).
    		// 设置 x-www-form-urlencoded 数据
    		SetWWWForm(
    			gout.H{
    				"int":     3,
    				"float64": 3.14,
    				"string":  "test-www-Form",
    			},
    		).
    		// 关心 http code 返回值设置
    		Code(&code).
    		Do()
    	if err != nil {
    		fmt.Printf("%s\n", err)
    		return
    	}
    
    	if code != 200 {
    	}
    }
    
    /*
    > POST /post HTTP/1.1
    > Content-Type: application/x-www-form-urlencoded
    >
    
    float64=3.14&int=3&string=test-www-Form
    
    < HTTP/1.1 200 OK
    < Content-Length: 0
    < Server: gurl-server
    
    */
    
    

    callback

    callback 主要用在,服务端会返回多种格式 body 的场景, 比如 404 返回的是 html, 200 返回 json 。 这时候要用 Callback 挂载多种处理函数,处理不同的数据结构

    
    func main() {
    	
    	r, str404 := Result{}, ""
    	code := 0
    
    	err := gout.GET(":8080").Callback(func(c *gout.Context) (err error) {
    
    		switch c.Code {
    		case 200: //http code 为 200 时,服务端返回的是 json 结构
    			c.BindJSON(&r)
    		case 404: //http code 为 404 时,服务端返回是 html 字符串
    			c.BindBody(&str404)
    		}
    		code = c.Code
    		return nil
    
    	}).Do()
    
    	if err != nil {
    		fmt.Printf("err = %s\n", err)
    		return
    	}
    
    	fmt.Printf("http code = %d, str404(%s) or json result(%v)\n", code, str404, r)
    
    }
    
    

    Set request timeout

    setimeout 是 request 级别的超时方案。相比 http.Client 级别,更灵活。

    package main
    
    import (
    	"fmt"
    	"github.com/guonaihong/gout"
    	"time"
    )
    
    func main() {
    	err := gout.GET(":8080").
    		SetTimeout(2 * time.Second).
    		Do()
    
    	if err != nil {
    		fmt.Printf("err = %v\n", err)
    	}
    }
    
    
    2 条回复    2020-03-31 12:59:22 +08:00
    wsseo
        1
    wsseo  
       2020-03-31 11:23:29 +08:00
    点个赞👍
    guonaihong
        2
    guonaihong  
    OP
       2020-03-31 12:59:22 +08:00
    @wsseo 感谢支持。。。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3011 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 36ms · UTC 10:54 · PVG 18:54 · LAX 02:54 · JFK 05:54
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.