Coder Social home page Coder Social logo

guonaihong / gout Goto Github PK

View Code? Open in Web Editor NEW
1.3K 22.0 113.0 772 KB

gout to become the Swiss Army Knife of the http client @^^@---> gout 是http client领域的瑞士军刀,小巧,强大,犀利。具体用法可看文档,如使用迷惑或者API用得不爽都可提issues

License: Apache License 2.0

Go 99.97% Makefile 0.03%
http-client http-request go golang httpclient request http-requests

gout's Introduction

gout

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

Go codecov Go Report Card

构架

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 等类型)
  • 支持设置 protobuf 编码到请求 body里面(可传struct)
  • 支持设置 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命令
  • 传入自定义*http.Client
  • 支持请求中间件(https://github.com/antlabs/gout-middleware)
  • 支持响应中间件ResponseUse
  • 支持设置chunked数据格式发送
  • 支持body, header的数据校验
  • 支持通过build tag自由选择不同的json序列化方式(可选jsoniter,go_json,sonic等)
  • 等等更多

演示

gout-example.gif

内容

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

build tag

Gout默认使用语言内置的encoding/json包。但是如果你想使用其他的json包,可以通过build tag来修改。

jsoniter

go build -tags=jsoniter .

go-json

go build -tags=go_json .

sonic

$ go build -tags="sonic avx" .

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()
}

GET POST PUT DELETE PATH HEAD OPTIONS template

package main

import (
	"github.com/guonaihong/gout"
)

type testURLTemplateCase struct {
	Host   string
}

func main() {

	url := "https://{{.Host}}"
	// 发送GET方法
	gout.GET(url, testURLTemplateCase{Host:"www.qq.com"}).Do()

	// 发送POST方法
	gout.POST(url, testURLTemplateCase{Host:"www.github.com"}).Do()

	// 发送PUT方法
	gout.PUT(url, testURLTemplateCase{Host:"www.baidu.com"}).Do()

	// 发送DELETE方法
	gout.DELETE(url, testURLTemplateCase{Host:"www.google.com"}).Do()

	// 发送PATH方法
	gout.PATCH(url, testURLTemplateCase{Host:"www.google.com"}).Do()

	// 发送HEAD方法
	gout.HEAD(url, testURLTemplateCase{Host:"www.google.com"}).Do()

	// 发送OPTIONS
	gout.OPTIONS(url, testURLTemplateCase{Host:"www.google.com"}).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支持的更多数据类型

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"})`

http header

Do not convert http headers

与SetHeader API唯一的区别就是不修改header名. 大部分情况用SetHeader,如果有不修改header的需求再使用SetHeaderRaw。

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
        SetHeaderRaw(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
*/

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支持的更多类型

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"})

data valid

数据校验使用 https://github.com/go-playground/validator 完成功能, 更多语法可看该文档.

import (
	"fmt"
	"github.com/guonaihong/gout"
)

type testValid struct {        
        Val string `valid:"required"`   
} 

func main() {
	tv := testValid{}
	err := gout.
		// 设置POST方法和url
		POST(":8080/req/body").
		//打开debug模式
		Debug(true).
		//解析json, 并且当需要的字段没有值时, 返回错误
		BindJSON(&tv).
		//结束函数
		Do()

	if err != nil {
		fmt.Printf("%s\n", err)
		return
	}

}

responseUse

response中间件,在Bind()之前执行。可以对response进行通用逻辑处理。 如果只需要闭包逻辑,则可以使用WithResponseMiddlerFunc,而不必创建一个结构体,下面的例子中对两种方法都进行了使用。

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/guonaihong/gout"
	"github.com/guonaihong/gout/middler"
	"io/ioutil"
	"log"
	"net/http"
	"time"
)

// response拦截器修改示例
type demoResponseMiddler struct{}

func (d *demoResponseMiddler) ModifyResponse(response *http.Response) error {
	// 修改responseBody。 因为返回值大概率会有 { code, data,msg} 等字段,希望进行统一处理
	//这里想验证code. 如果不对就返回error。 对的话将 data中的内容写入body,这样后面BindJson的时候就可以直接处理业务了
	all, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return err
	}
	obj := make(map[string]interface{})
	err = json.Unmarshal(all, &obj)
	if err != nil {
		return err
	}
	code := obj["code"]
	msg := obj["msg"]
	data := obj["data"]

	// Go中json中的数字经过反序列化会成为float64类型
	if float64(200) != code {
		return errors.New(fmt.Sprintf("请求失败, code %d msg %s", code, msg))
	} else {
		byt, _ := json.Marshal(&data)
		response.Body = ioutil.NopCloser(bytes.NewReader(byt))
		return nil
	}
}
func demoResponse() *demoResponseMiddler {
	return &demoResponseMiddler{}
}

func main() {
	go server()                        //等会起测试服务
	time.Sleep(time.Millisecond * 500) //用时间做个等待同步
	responseUseExample()
}

func responseUseExample() {
	//成功请求
	successRes := new(map[string]interface{})
	err := gout.GET(":8080/success").ResponseUse(
		demoResponse(),
		// 注意这里使用了WithResponseMiddlerFunc
		middler.WithResponseMiddlerFunc(func(response *http.Response) error {
			// Do your magic
			return nil
		}),
	).BindJSON(&successRes).Do()
	log.Printf("success请求  -->   响应 %s  \n  err  %s \n ", successRes, err)

	//fail请求
	failRes := new(map[string]interface{})
	err = gout.GET(":8080/fail").ResponseUse(demoResponse()).BindJSON(&failRes).Do()
	log.Printf("fail请求  -->   响应 %s  \n  err  %s \n ", successRes, err)
}

type Result struct {
	Code int         `json:"code"`
	Msg  string      `json:"msg"`
	Data interface{} `json:"data"`
}
type Item struct {
	Id   string `json:"id"`
	Name string `json:"name"`
}

func server() {
	router := gin.New()
	router.GET("/success", func(c *gin.Context) {
		c.JSON(200, Result{200, "请求成功了", Item{"001", "张三"}})
	})
	router.GET("/fail", func(c *gin.Context) {
		c.JSON(200, Result{500, "查询数据库出错了", nil})
	})
	router.Run()
}

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

*/

get all header

package main

import (
	"fmt"
	"github.com/guonaihong/gout"
	"net/http"
)

func main() {
	header := make(http.Header)
	err := gout.
		// 设置POST方法和url
		POST(":8080/req/body").
		//打开debug模式
		Debug(true).
		// 获取所有的响应http header
		BindHeader(&header).
		//结束函数
		Do()

	if err != nil {
		fmt.Printf("%s\n", err)
		return
	}

}

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)
	}
}

do not escape html characters

  • SetJSONNotEscape 和SetJSON唯一的区别就是不转义HTML字符
err := gout.POST(ts.URL).
			Debug(true).
			SetJSONNotEscape(gout.H{"url": "http://www.com?a=b&c=d"}).
			Do()

//> POST / HTTP/1.1
//> Content-Type: application/json
//>

//{
//    "url": "http://www.com?a=b&c=d"
//}

//< HTTP/1.1 200 OK
//< Date: Sun, 18 Dec 2022 14:05:21 GMT
//< Content-Length: 0

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

*/

protobuf

SetProtoBuf支持,protobuf序列化后的[]byte,或者生成的protobuf结构体指针

package main

import (
	"github.com/guonaihong/gout"
)

func main() {
	httpCode := 0
	err := GET(":8080/echo").
		SetProtoBuf( /* protobuf 生成的结构体,必须传指针类型*/ ).
		Code(&httpCode).
		Do()
}

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)

}

get .Response

func main() {
	

	resp, err := gout.GET(":8080").SetJSON(`{"test":"value"}`).Response()

	if resp != nil {
		defer resp.Body.Close()
	}
}

multiple binding functions

支持绑定多个对象, BindXXX函数可以多次调用。例子里面是BindJSON和BindBody

var responseStruct struct {
	Name string `json:"name"`
	Age  int    `json:"age"`
}

func main() {

	var responseStr string
	err := gout.GET("url").
		SetQuery(gout.H{}).
		BindJSON(&responseStruct).
		BindBody(&responseStr).
		Do()

	if err != nil {
		return
	}

	log.Println(responseStr)
}

Auto decode body

响应头里面指明压缩格式,使用AutoDecodeBody接口可以自动解压。

//Content-Encoding: gzip
//Content-Encoding: deflate
//Content-Encoding: br
//gzip由标准库原生支持,不需要使用AutoDecodeBody接口,后两种由gout支持.
func main() {
	gout.GET(url).AutoDecodeBody().BindBody(&s).Do()
}

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)
	}
}

proxy

  • SetProxy 设置代理服务地址
package main

import (
	"fmt"
	"github.com/guonaihong/gout"
	"log"
)

func main() {
	c := &http.Client{}
	s := ""
	err := gout.
		New(c).
		GET("www.qq.com").
		// 设置proxy服务地址
		SetProxy("http://127.0.0.1:7000").
		// 绑定返回数据到s里面
		BindBody(&s).
		Do()

	if err != nil {
		log.Println(err)
		return
	}

	fmt.Println(s)
}

socks5

  • SetSOCKS5 设置socks5地址
package main

import (
    "fmt"
    "github.com/guonaihong/gout"
    "log"
    "net/http"
)

func main() {
    c := &http.Client{}
    s := ""
    err := gout.
        New(c).
        GET("www.qq.com").
        // 设置proxy服务地址
        SetSOCKS5("127.0.0.1:7000").
        // 绑定返回数据到s里面
        BindBody(&s).
        Do()

    if err != nil {
        log.Println(err)
        return
    }   

    fmt.Println(s)
}

cookie

  • SetCookies设置cookie, 可以设置一个或者多个cookie
package main

import (
	"fmt"
	"github.com/guonaihong/gout"
	"net/http"
)

func main() {

	// === 发送多个cookie ====
	err := gout.
		// :8080/cookie是http://127.0.0.1:8080/cookie的简写
		GET(":8080/cookie").
		//设置debug模式
		Debug(true).
		SetCookies(
			//设置cookie1
			&http.Cookie{
				Name:  "test1",
				Value: "test1",
			},
			//设置cookie2
			&http.Cookie{
				Name:  "test2",
				Value: "test2",
			},
		).
		Do()

	if err != nil {
		fmt.Println(err)
		return
	}

	// === 发送一个cookie ===
	err = gout.
		// :8080/cookie/one是http://127.0.0.1:8080/cookie/one的简写
		GET(":8080/cookie/one").
		//设置debug模式
		Debug(true).
		SetCookies(
			//设置cookie1
			&http.Cookie{
				Name:  "test3",
				Value: "test3",
			},
		).
		Do()
	fmt.Println(err)

}

basic auth

使用SetBasicAuth接口

func main() {
	
	err := gout.POST(":8080/colorjson").
		SetBasicAuth("userName", "password").
		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)
	}
}

context

  • WithContext设置context,可以取消http请求

Cancel a sending request

package main

import (
    "context"
    "github.com/guonaihong/gout"
    "time"
)

func main() {
    // 声明一个context
    ctx, cancel := context.WithCancel(context.Background())

    //调用cancel可取消http请求
    go func() {
        time.Sleep(time.Second)
        cancel()
    }() 

    err := gout.
        GET("127.0.0.1:8080/cancel"). //设置GET请求以及需要访问的url
        WithContext(ctx).             //设置context, 外层调用cancel函数就可取消这个http请求
        Do()

    if err != nil {
    }   
}

unix socket

  • UnixSocket可以把http底层通信链路由tcp修改为unix domain socket
    下面的例子,会通过domain socket发送http GET请求,http body的内容是hello world
package main

import (
    "fmt"
    "github.com/guonaihong/gout"
    "net/http"
)

func main() {
    c := http.Client{}

    g := gout.
        New(&c).
        UnixSocket("/tmp/test.socket") //设置unixsocket文件位置

    err := g.
        GET("http://a/test").   //设置GET请求
        SetBody("hello world"). //设置body内容
        Do()
    fmt.Println(err)
}

http2 doc

go 使用https访问http2的服务会自动启用http2协议,这里不需要任何特殊处理

  • https://http2.golang.org/ (bradfitz建的http2测试网址,里面大约有十来个测试地址,下面的例子选了一个)
package main

import (
    "fmt"
    "github.com/guonaihong/gout"
)

func main() {
    s := ""
    err := gout.
        GET("https://http2.golang.org/reqinfo"). //设置GET请求和请求url
        Debug(true).                             //打开debug模式,可以看到请求数据和响应数据
        SetBody("hello, ###########").           //设置请求body的内容,如果你的请求内容是json格式,需要使用SetJSON函数
        BindBody(&s).                            //解析响应body内容
        Do()                                     //结束函数

    if err != nil {
        fmt.Printf("send fail:%s\n", err)
    }   
    _ = s 
}

debug mode

Turn on debug mode

该模式主要方便调试用的,默认开启颜色高亮(如果要关闭颜色高亮,请往下看)

func main() {
	
	err := gout.POST(":8080/colorjson").
		Debug(true). //打开debug模式
		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)
	}
}

Turn off color highlighting in debug mode

使用debug.NoColor()传入Debug函数关闭颜色高亮

import (
	"github.com/guonaihong/gout"
	"github.com/guonaihong/gout/debug"
)
func main() {
	
	err := gout.POST(":8080/colorjson").
		Debug(debug.NoColor()).
		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)
	}
}

Custom debug mode

debug 自定义模式,可传递函数。下面演示用环境变量开启debug模式(只有传递IOS_DEBUG环境变量才输出日志)

package main

import (
    "fmt"
    "github.com/guonaihong/gout"
	"github.com/guonaihong/gout/debug"
    "os"
)

func IOSDebug() debug.Apply {
    return gout.DebugFunc(func(o *debug.Options) {
        if len(os.Getenv("IOS_DEBUG")) > 0 { 
            o.Debug = true
        }
    })  
}

func main() {

    s := ""
    err := gout.
        GET("127.0.0.1:8080").
        // Debug可以支持自定义方法
        // 可以实现设置某个环境变量才输出debug信息
        // 或者debug信息保存到文件里面,可以看下_example/15-debug-save-file.go
        Debug(IOSDebug()).
        SetBody("test hello").
        BindBody(&s).
        Do()

    fmt.Printf("err = %v\n", err)
}

// env IOS_DEBUG=true go run customize.go

trace info

debug.Trace()可输出http各个阶段的耗时,比如dns lookup时间,tcp连接时间等等。可以很方便的做些性能调优。

package main

import (
    "fmt"
    "github.com/guonaihong/gout"
	"github.com/guonaihong/gout/debug"
)

func openDebugTrace() {
    err := gout.POST(":8080/colorjson").
        Debug(debug.Trace()).
        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)
    }
}
  • output
=================== Trace Info(S): ===================
     DnsDuration           : 0s
     ConnDuration          : 868.623µs
     TLSDuration           : 0s
     RequestDuration       : 376.712µs
     WaitResponeDuration   : 717.008µs
     ResponseDuration      : 76.158µs
     TotalDuration         : 2.13921ms
=================== Trace Info(E): ===================

save to writer

debug.ToWriter可以传递任何io.Writer对象,比如bytes.Buffer, 文件等。。。

package main

import (
	"bytes"
	"fmt"

	"github.com/guonaihong/gout"
	"github.com/guonaihong/gout/debug"
)

func main() {
	var buf bytes.Buffer
	err := gout.POST(":8080/colorjson").
		Debug(debug.ToWriter(&buf, false)).
		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)
	}
	fmt.Println(buf.String())
}

save to file

import (
	"github.com/guonaihong/gout"
	"github.com/guonaihong/gout/debug"
)
func main() {
	
	err := gout.POST(":8080/colorjson").
		Debug(debug.ToFile("./req.txt", false)).
		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)
	}
}

extracting trace information

package main

import (
    "fmt"
    "github.com/guonaihong/gout"
	"github.com/guonaihong/gout/debug"
)

func main() {
    var buf bytes.Buffer
    err := gout.POST(":8080/colorjson").
        Debug(debug.TraceJSONToWriter(&buf)).
        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)
    }
    fmt.Printf("%s", buf.String())
}

benchmark

benchmarking a certain number of times

下面的例子,起了20并发。对:8080端口的服务,发送3000次请求进行压测,内容为json结构

package main

import (
	"fmt"
	"github.com/guonaihong/gout"
)

const (
	benchNumber     = 30000
	benchConcurrent = 20
)

func main() {
	err := gout.
		POST(":8080").                     //压测本地8080端口
		SetJSON(gout.H{"hello": "world"}). //设置请求body内容
		Filter().                          //打开过滤器
		Bench().                           //选择bench功能
		Concurrent(benchConcurrent).       //并发数
		Number(benchNumber).               //压测次数
		Do()

	if err != nil {
		fmt.Printf("%v\n", err)
	}
}

benchmark-duration

下面的例子,起了20并发。对:8080端口的服务,压测持续时间为10s,内容为json结构

package main

import (
	"fmt"
	"github.com/guonaihong/gout"
	"time"
)

const (
	benchTime       = 10 * time.Second
	benchConcurrent = 30
)

func main() {
	err := gout.
		POST(":8080").                     //压测本机8080端口
		SetJSON(gout.H{"hello": "world"}). //设置请求body内容
		Filter().                          //打开过滤器
		Bench().                           //选择bench功能
		Concurrent(benchConcurrent).       //并发数
		Durations(benchTime).              //压测时间
		Do()

	if err != nil {
		fmt.Printf("%v\n", err)
	}
}

benchmark-rate

下面的例子,起了20并发。对:8080端口的服务,压测总次数为3000次,其中每秒发送1000次。内容为json结构

package main

import (
	"fmt"
	"github.com/guonaihong/gout"
)

const (
	benchNumber     = 3000
	benchConcurrent = 20
)

func main() {
	err := gout.
		POST(":8080").                     //压测本机8080端口
		SetJSON(gout.H{"hello": "world"}). //设置请求body内容
		Filter().                          //打开过滤器
		Bench().                           //选择bench功能
		Rate(1000).                        //每秒发1000请求
		Concurrent(benchConcurrent).       //并发数
		Number(benchNumber).               //压测次数
		Do()

	if err != nil {
		fmt.Printf("%v\n", err)
	}
}

Custom benchmark functions

自定义压测函数,构造每次不一样的http request数据

package main

import (
    "fmt"
    "github.com/google/uuid"
    "github.com/guonaihong/gout"
    "github.com/guonaihong/gout/filter"
    "sync/atomic"
)

func main() {
    i := int32(0)

    err := filter.NewBench().
        Concurrent(30). //开30个go程
        Number(30000).  //压测30000次
        Loop(func(c *gout.Context) error {

			// 下面的代码,每次生成不一样的http body 用于压测
            uid := uuid.New()  //生成uuid
            id := atomic.AddInt32(&i, 1) //生成id, 可以理解为++i,线程安全版本

            c.POST(":1234").SetJSON(gout.H{"sid": uid.String(),
                "appkey": fmt.Sprintf("ak:%d", id),
                "text":   fmt.Sprintf("test text :%d", id)})
            return nil

        }).Do()

    if err != nil {
        fmt.Printf("err = %v\n", err)
    }
}

retry-backoff

retry 功能使用带抖动功能和指数回退的算法实现backoff

package main

import (
	"fmt"
	"github.com/guonaihong/gout"
	"time"
)

func main() {
	err := gout.HEAD("127.0.0.1:8080").
		Debug(true).                      //打开debug模式
		Filter().                         //打开过滤器
		Retry().                          //打开重试模式
		Attempt(5).                       //最多重试5次
		WaitTime(500 * time.Millisecond). //基本等待时间
		MaxWaitTime(3 * time.Second).     //最长等待时间
		Do()

	if err != nil {
		fmt.Printf("err = %v\n", err)
	}
}

retry conditions httpcode

指定重试条件,这里面的例子是服务端返回的状态码是209进行重试 完整代码

package main

import (
	"fmt"
	"github.com/guonaihong/gout"
	"github.com/guonaihong/gout/filter"
	"time"
)

func useRetryFuncCode() {
	s := ""
	err := gout.GET(":8080/code").Debug(true).BindBody(&s).F().
		Retry().Attempt(3).WaitTime(time.Millisecond * 10).MaxWaitTime(time.Millisecond * 50).
		Func(func(c *gout.Context) error {
			if c.Error != nil || c.Code == 209 {
				return filter.ErrRetry
			}

			return nil

		}).Do()

	fmt.Printf("err = %v\n", err)
}

retry conditions backupurl

指定条件进行重试,这里的例子是默认url不能访问,使用backup url进行访问 完整代码

package main

import (
	"fmt"
	"github.com/guonaihong/gout"
	"github.com/guonaihong/gout/core"
	"github.com/guonaihong/gout/filter"
	"time"
)
func useRetryFunc() {
	// 获取一个没有服务绑定的端口
	port := core.GetNoPortExists()
	s := ""

	err := gout.GET(":" + port).Debug(true).BindBody(&s).F().
		Retry().Attempt(3).WaitTime(time.Millisecond * 10).MaxWaitTime(time.Millisecond * 50).
		Func(func(c *gout.Context) error {
			if c.Error != nil {
				c.SetHost(":1234") //必须是存在的端口
				return filter.ErrRetry
			}
			return nil

		}).Do()
	fmt.Printf("err = %v\n", err)
}

import

send raw http request

package main

import (
    "fmt"
    "github.com/guonaihong/gout"
)

func main() {
	s := `POST /colorjson HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Go-http-client/1.1
Content-Length: 97
Content-Type: application/json
Accept-Encoding: gzip

{"array":["foo","bar","baz"],"bool":false,"null":null,"num":100,"obj":{"a":1,"b":2},"str":"foo"}
    `
	err := gout.NewImport().RawText(s).Debug(true).SetHost(":1234").Do()
	if err != nil {
		fmt.Printf("err = %s\n", err)
		return
	}
}

export

generate curl command

仅仅生成curl命令, 不会发送http请求

package main

import (
    "fmt"
    "github.com/guonaihong/gout"
)

func main() {
    // 1.formdata
    err := gout.GET(":1234").
        SetForm(gout.A{"text", "good", "mode", "A", "voice", gout.FormFile("./t8.go")}).
        Export().Curl().Do()
    // output:
    // curl -X GET -F "text=good" -F "mode=A" -F "voice=@./voice" "http://127.0.0.1:1234"

    // 2.json body
    err = gout.GET(":1234").
        SetJSON(gout.H{"key1": "val1", "key2": "val2"}).
        Export().Curl().Do()
    // output:
    // curl -X GET -H "Content-Type:application/json" -d "{\"key1\":\"val1\",\"key2\":\"val2\"}" "http://127.0.0.1:1234"

    fmt.Printf("%v\n", err)
}

generate curl command and send HTTP request

生成curl命令, 同时执行http请求, 在Curl()命令之行跟上GenAndSend()接口

package main

import (
    "fmt"
    "github.com/guonaihong/gout"
)

func main() {
    // 1.formdata
    err := gout.GET(":1234").
        SetForm(gout.A{"text", "good", "mode", "A", "voice", gout.FormFile("./t8.go")}).
        Export().Curl().GenAndSend().Do()
    // output:
    // curl -X GET -F "text=good" -F "mode=A" -F "voice=@./voice" "http://127.0.0.1:1234"

    // 2.json body
    err = gout.GET(":1234").
        SetJSON(gout.H{"key1": "val1", "key2": "val2"}).
        Export().Curl().GenAndSend().Do()
    // output:
    // curl -X GET -H "Content-Type:application/json" -d "{\"key1\":\"val1\",\"key2\":\"val2\"}" "http://127.0.0.1:1234"

    fmt.Printf("%v\n", err)
}

Incoming custom *http.Client

使用New接口即可使用自己的http.Client对象

package main

import (
	"fmt"
	"net/http"

	"github.com/guonaihong/gout"
)

func main() {

	c := &http.Client{} //http.Client里面有fd连接池,如果对这块优化不是太了解,只使用一个实例就行
	err := gout.New(c). // New接口可传入http.Client对象
				GET("www.qq.com").
				Debug(true).
				Do()

	if err != nil {
		fmt.Printf("err = %s\n", err)
		return
	}
}

Using chunked data format

使用Chunked接口, 设置为"Transfer-Encoding: chunked"的数据编码方式

package main

import (
        "fmt"

        "github.com/guonaihong/gout"
)

func main() {
        err := gout.POST(":8080").
                Chunked().
                SetBody("11111111111").
                Do()
        if err != nil {
                fmt.Printf("err :%v\n", err)
        }   
}
// 使用nc 起一个tcp服务, 使用上面的代码发起数据观察下结果
// nc -l 8080

NewWithOpt

这里记录全局配置的方法, 后面所有的全局配置都推荐使用gout.NewWithOpt接口的实现

insecure skip verify

忽略ssl验证, 使用gout.WithInsecureSkipVerify()接口配置该功能, 传入gout.NewWithOpt接口即可生效.

import (
	"github.com/guonaihong/gout"
)

func main() {
	// globalWithOpt里面包含连接池, 这是一个全局可复用的对象
	globalWithOpt := gout.NewWithOpt(gout.WithInsecureSkipVerify())
	err := globalWithOpt.GET("url").Do()
	if err != nil {
		fmt.Printf("err = %v\n" ,err)
		return
	}
}

turn off 3xx status code automatic jump

golang client库默认遇到301的状态码会自动跳转重新发起新请求, 你希望关闭这种默认形为, 那就使用下面的功能

import (
	"github.com/guonaihong/gout"
)

func main() {
	// globalWithOpt里面包含连接池, 这是一个全局可复用的对象, 一个进程里面可能只需创建1个
	globalWithOpt := gout.NewWithOpt(gout.WithClose3xxJump())
	err := globalWithOpt.GET("url").Do()
	if err != nil {
		fmt.Printf("err = %v\n" ,err)
		return
	}
}

new with opt set timeout

gout.WithTimeout 为了让大家少用gout.SetTimeout而设计

import (
	"github.com/guonaihong/gout"
)

func main() {
	// globalWithOpt里面包含连接池, 这是一个全局可复用的对象, 一个进程里面可能只需创建1个
	globalWithOpt := gout.NewWithOpt(gout.WithTimeout())
	err := globalWithOpt.GET("url").Do()
	if err != nil {
		fmt.Printf("err = %v\n" ,err)
		return
	}
}

new with opt unix socket

gout.WithUnixSocket 为了让大家少用.UnixSocket 而设计

import (
	"github.com/guonaihong/gout"
)

func main() {
	// globalWithOpt里面包含连接池, 这是一个全局可复用的对象, 一个进程里面可能只需创建1个, 如果有多个不同的unixsocket,可以创建多个
	globalWithOpt := gout.NewWithOpt(gout.WithUnixSocket("/tmp/test.socket"))
	err := globalWithOpt.GET("url").Do()
	if err != nil {
		fmt.Printf("err = %v\n" ,err)
		return
	}
}

new with opt proxy

gout.WithProxy 为了让大家少用.SetProxy 而设计

import (
	"github.com/guonaihong/gout"
)

func main() {
	// globalWithOpt里面包含连接池, 这是一个全局可复用的对象, 一个进程里面可能只需创建1个, 如果有多个不同的proxy,可以创建多个
	globalWithOpt := gout.NewWithOpt(gout.WithProxy("http://127.0.0.1:7000"))
	err := globalWithOpt.GET("url").Do()
	if err != nil {
		fmt.Printf("err = %v\n" ,err)
		return
	}
}

new with opt socks5

gout.WithSocks5 为了让大家少用.SetSOCKS5而设计

import (
	"github.com/guonaihong/gout"
)

func main() {
	// globalWithOpt里面包含连接池, 这是一个全局可复用的对象, 一个进程里面可能只需创建1个, 如果有多个不同的socks5,可以创建多个
	globalWithOpt := gout.NewWithOpt(gout.WithSocks5("127.0.0.1:7000"))
	err := globalWithOpt.GET("url").Do()
	if err != nil {
		fmt.Printf("err = %v\n" ,err)
		return
	}
}

Global configuration

set timeout

设置全局超时时间。可以简化一些代码。在使用全局配置默认你已经了解它会带来的一些弊端.

package main

import (
	"fmt"
	"github.com/guonaihong/gout"
	"time"
)

func main() {
	gout.SetTimeout(time.Second * 1)
	err := gout.GET("www.baidu.com").Do()
	if err != nil {
		fmt.Printf("err is:%v\n")
	}
}

set debug

打开全局debug开关。

package main

import (
	"fmt"

	"github.com/guonaihong/gout"
)

func main() {
	gout.SetDebug(true)
	err := gout.GET(":8080/colorjson").Do()
	if err != nil {
		fmt.Printf("err is:%v\n")
	}
}

Unique features

forward gin data

gout 设计之初就考虑到要和gin协同工作的可能性,下面展示如何方便地使用gout转发gin绑定的数据。

package main

import (
	"github.com/gin-gonic/gin"
	"github.com/guonaihong/gout"
)

type testQuery struct {
	Size int    `query:"size" form:"size"` // query tag是gout设置查询字符串需要的
	Page int    `query:"page" form:"page"`
	Ak   string `query:"ak" form:"ak"`
}

//下一个服务节点
func nextSever() {
	r := gin.Default()

	r.GET("/query", func(c *gin.Context) {
		q := testQuery{}
		err := c.ShouldBindQuery(&q)
		if err != nil {
			return
		}
		c.JSON(200, q)
	})
	r.Run(":1234")
}

func main() {
	go nextSever()
	r := gin.Default()

	// 演示把gin绑定到的查询字符串转发到nextServer节点
	r.GET("/query", func(c *gin.Context) {
		q := testQuery{}
		// 绑定查询字符串
		err := c.ShouldBindQuery(&q)
		if err != nil {
			return
		}

		// 开发转发, 复用gin所用结构体变量q
		code := 0 // http code
		err := gout.
			//发起GET请求
			GET("127.0.0.1:1234/query").
			//设置查询字符串
			SetQuery(q).
			//关心http server返回的状态码 设置该函数
			Code(&code).
			Do()
		if err != nil || code != 200 { /* todo Need to handle errors here */
		}
		c.JSON(200, q)
	})

	r.Run()
}

// http client
// curl '127.0.0.1:8080/query?size=10&page=20&ak=test'

FAQ

gout benchmark性能如何

下面是与apache ab的性能对比 _example/16d-benchmark-vs-ab.go

gout-vs-ab.png

gout's People

Contributors

adiecho avatar asjdf avatar dependabot[bot] avatar garudaen avatar guonaihong avatar h1z3y3 avatar listening3 avatar orionbetasp avatar panxl6 avatar simpleapples avatar sora233 avatar tmaize avatar unbyte avatar wangxin008 avatar yanghx-git avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gout's Issues

support http header(decode)

out := gout.New(nil)
code := 0

if err := out.GET(":8080/testquery").Code(&code).ShoudBindHeader().Do(); err != nil {
}

ShouldBindHeader支持的类型有

  • 结构体
type testHeader struct {
    CheckIn string `header:checkin`
    CheckOut string `header:checkout`
}
t := testheader{}
ShoudBindHeader(&t)

新增文档以及代码示例

每个用法都需要示例文件

  • http methods(todo)
  • query(encode)
  • header(encode, decode)
  • json(encode, decode)
  • xml(encode, decode)
  • yaml(encode, decode)
  • form(encode)
  • group

benchmark模式

设计思路:在原有的gout链式调用后面接Filter().Bench函数。可由普通的api接口转变为压测接口。
Filter().Bench是gout引入的第一个过滤器函数。
伪代码如下

控制benchmark时间

gout.
    GET(url).
    Filter().
    Bench(). // 打开bench过滤器
    Concurrent(20). // 并发数
    Durations(time.Second).
    Do()

控制benchmark次数

gout.
    GET(url).
    Filter().
    Bench(). // 打开bench过滤器
    Concurrent(20).// 并发数,业务很多返回一般设置为物理核心数 2倍的关系
    Number(100).  //压测次数
    Do()

控制发送速率

gout.GET(url).
      SetHeader(gout.H{"header1":"header-value"}).
      SetBody("hell world"). // http body 发送内容
      Filter().
      Bench().                       //打开bench过滤器
      Concurrent(20).           // 并发数,业务很多返回一般设置为物理核心数 2倍的关系
      Rate(1000).                  // 控制每秒压测频率
      Number(100000).        //压测次数
      Do()

support http header(encode)

伪代码

out := gout.New(nil)
code := 0

if err := out.GET(":8080/testquery").ToHeader().Code(&code).Do(); err != nil {
}

ToHeader支持的类型有

  • map[string]interface{},可以使用gout.H别名
  • struct
  • array, slice(长度必须是偶数)
// gout.H 或者 map[string]interface{}
ToHeader(gout.H{
    "check_in":"2019-06-18",
    "check_out":"2019-06-18",
})

// struct
type testHeader struct {
    CheckIn string `header:checkin`
    CheckOut string `header:checkout`
}

ToHeader(&testHeader{CheckIn:2019-06-18, CheckOut:2019-06-18})

// array or slice
// -H active:enable -H action:drop
ToHeader([]string{"active", "enable", "action", "drop"})

支持x-www-form-urlencoded

SetWWWForm函数

func main() {
    err := gout.POST(":8080/post").
		Debug(true).
		SetWWWForm(gout.H{
			"int":     3,
			"float64": 3.14,
			"string":  "test-www-Form",
		}).
		Do()
	if err != nil {
		fmt.Printf("%s\n", err)
		return
	}

}
/*
output:
> POST /post HTTP/1.1
> Content-Type: application/x-www-form-urlencoded
>

float64=3.14&int=3&string=test-www-Form

*/

优化文档

目标

  • 提升清晰度
  • 突出亮点(人无我有的)

Grouping routes

伪代码如下

out := gout.New(nil)

// http://127.0.0.1:80/v1
v1 := out.Group("/v1")
v1.POST("/login").Next().
   POST("/submit").Next().
   POST("/read").Do()

// http://127.0.0.1:80/v2
v2 := out.Group("/v2")
v2.POST("/login").Next().
   POST("/submit").Next().
   POST("/read").Do()
func main() {
	router := gin.Default()

	// Simple group: v1
	v1 := router.Group("/v1")
	{
		v1.POST("/login", loginEndpoint)
		v1.POST("/submit", submitEndpoint)
		v1.POST("/read", readEndpoint)
	}

	// Simple group: v2
	v2 := router.Group("/v2")
	{
		v2.POST("/login", loginEndpoint)
		v2.POST("/submit", submitEndpoint)
		v2.POST("/read", readEndpoint)
	}

	router.Run(":8080")
}

support form

新增ToForm 函数

type testForm struct {
    Mode string `form:"mode"`
    Text string `form:"text"`
    Voice string `form:"voice" form-file:"true"` //从文件中读取 
    Voice2 []byte `form:"voice2" form-mem:"true"`  //从内存中构造
}

t := testForm{}

type rsp struct{
    ErrMsg string `json:"errmsg"`
    ErrCode int `json:"errcode"`
}

r := rsp{}
code := 0
err := gout.New(nil).ToForm(&t).ShoudBindJSON(&r).Code(&code).Do()
if err != nil {

}

新增SetBody函数

伪代码如下

err := gout.New(nil).SetBody(/*支持的类型如下*/).Do()

支持的类型有

  • int, int8, int16, int32, int64
  • uint, uint8, uint16, uint32, uint64
  • string
  • []byte
  • float32, float64

明确不支持的类型有

  • struct
  • array, slice

v0.0.4版本计划

未完成

已完成

  • #95 优化文档
  • #97 修改New接口
  • #98 优化文档2
  • #103 quick start增加综合示例
  • #107 BindHeader函数tag设置没生效(fix)
  • #109 修改debug模式默认配色方案
  • #92 benckmark模式
  • #105 优化超时API
  • #102 提升测试覆盖度

时间

  • 11.18-12.08

新增BindBody函数

伪代码如下

g := gout.New(nil)
g.GET(url).BindBody(/*看下面*/)

支持的类型有

  • int, int8, int16, int32, int64
  • uint, uint8, uint16, unt32, uint64
  • float32, float64
  • string
  • []byte

明确不支持的类型有

  • struct
  • slice, array

修改API名字,目的更清晰,和更短

  • 设置请求函数
ToJSON-->SetJSON
ToXML-->SetXML
ToYAML-->SetYAML
ToQuery-->SetQuery
ToHeader-->SetHeader
ToForm-->SetForm
  • 解析类函数
ShouldBindJSON ---> BindJSON
ShouldBindXML-->BindXML
ShouldBindYAML-->BindYAML
ShouldBindHeader-->BindHeader

debug模式支持

出发点

debug模式主要方便调试,该模式下会把req和rsp都打印出来。虽然和wireshark有重合,但是可以更快地debug,毕竟加一个函数比打开wireshark快多了。

新增函数Debug(true)

伪代码

gout.GET("网址").Debug(true).SetJSON(gout.H{"testkey":"testval"}).Do()
/*
此处是输出
*/

目标

高亮json, xml, yaml的请求,响应。
高亮http header
高亮放到下个版本,考虑到没有xml, yaml高亮的库,要自己造轮子,要花费很多时间。

unix管道支持

设计备忘。
下面是标准库的用法,凡是修改到Trapsport都相当与修改全局变量,所以后面的例子都声明一个新的http.Client自个玩耍。

func fakeDial(proto, addr string) (conn net.Conn, err error) {
    return net.Dial("unix", "/tmp/test.sock")// 第二个参数是socket的位置
}

tr := &http.Transport{
    Dial: fakeDial,
}
client := &http.Client{Transport: tr}
resp, err := client.Get("http://d/test")

API原型

package main

import (
        "fmt"
        "github.com/guonaihong/gout"
        "net/http"
)

func main() {
        c := http.Client{}
        g := gout.New(&c).UnixSocket("/tmp/test.socket")
        err := g.GET("http://a/test").SetBody("hello world").Do()
        fmt.Println(err)
}

encode-decode-xml

  • ToXML 序列化数据到body里面
  • ShouldBindXML 反序列化数据到结构体里面

新增ToJSON和ShouldBindJSON和Code这3个函数

客户端代码如下

type data struct {
    Id int `json:"id"`
    Data string `json:"data"`
}

g := gout.New(nil)

var d1, d2 data
var httpCode int

err := g.POST(":8080/test.json").ToJSON(&d1).ShouldBindJSON(&d2).Code(&httpCode).Do()
if err != nil {
    fmt.Printf("send fail:%s\n", err)
}

// 如果d1内容不等于d2将要报错
// 测试的时候加上这段代码

服务端代码

func main() {
	router := gin.Default()

	
	router.POST("/test.json", func(c *gin.Context) {
                var d data
		c.ShouldBindJSON(&d)
                c.JSON(200, d)
	})

	

	// Listen and serve on 0.0.0.0:8080
	router.Run(":8080")
}

支持Cookie

伪代码如下

gout.POST(url).SetCookie(http.Cookie{}).Do()

gout.POST(url).SetCookie(http.Cookie{}, http.Cookie{}).Do()

v0.0.3版本规划

开发周期

2019-11-1-2019-11-17

正在开发功能列表

已完成

  • #78 debug模式下-支持json语法高亮
  • #84 支持x-www-form-urlencoded
  • #82 新增example目录,存放使用示例
  • #83 SetForm支持更多数据类型
  • #81 代码测试覆盖度到底90%

设计GET,POST,PUT,DELETE,PATCH,HEAD,OPTIONS函数

客户段伪代码如下

// New的入参是http.Client对象,可以为空
// 方法后面跟的是url,如果不写全路径默认补全
// 空等于 http://127.0.0.1
// :port 等于 http://127.0.0.1:port
// /path等于 http://127.0.0.1:80/path
out := New(nil)
        err := out.GET(":8080/someGet").Next().
                POST(":8080/somePost").Next().
                PUT(":8080/somePut").Next().
                DELETE(":8080/someDelete").Next().
                PATCH(":8080/somePatch").Next().
                HEAD(":8080/someHead").Next().
                OPTIONS(":8080/someOptions").Next().Do()

        if err != nil {
                t.Errorf("http client fail:%v\n", err)
        }

服务端测试代码

func main() {
	// Creates a gin router with default middleware:
	// logger and recovery (crash-free) middleware
	router := gin.Default()

	router.GET("/someGet", getting)
	router.POST("/somePost", posting)
	router.PUT("/somePut", putting)
	router.DELETE("/someDelete", deleting)
	router.PATCH("/somePatch", patching)
	router.HEAD("/someHead", head)
	router.OPTIONS("/someOptions", options)

	// By default it serves on :8080 unless a
	// PORT environment variable was defined.
	router.Run()
	// router.Run(":3000") for a hard coded port
}

新增Def函数

目的

可以少打几个字

用法如下

gout.Def().POST(url).Do()
// 等于
gout.New(nil).POST(url).Do()

v0.0.2版本计划

deadline

2019年10月31日

开发计划

10月前15天开发新功能,后15天修复可能出现的bug

changelog

已完成

#52 新增WithContext,可以取消请求
#54 unix管道支持
#56 代码测试覆盖度达到60%
#71 提升测试覆盖度到90%
#62 增强SetBody函数,支持io.Reader接口
#63 增强BindBody函数,支持io.Writer接口
#53 新增http2文档
#55 debug模式
#58 新增和resty的对比文档
#57 带namespace的debug模式
#72 提升文档质量,突出特色功能

未完成

函数稳定性声明

打完版本标签的函数原则上不会修改

新增Callback函数

伪代码如下

g := gout.New(nil)

r , errCode := Result{}, 0

g.GET(url).Callback(func(c *gout.Context) error{

    switch c.Code {
        case 200:
            c.BindJSON(&Result)
        case 500:
            c.BindBody(&errCode)
    }
})

增强ToForm函数

目标:支持gout.H写法
why:这样就不需要定义结构体,可以提升开发效率

伪代码如下

package main

import (
    "fmt"
    "github.com/guonaihong/gout"
)

func main() {

    code := 0
    err := gout.New(nil).
        POST(":8080/test").
        ToForm(gout.H{"mode": "A",
            "text":   "good",
            "voice":  gout.FileFile("test.pcm"),
            "voice2": gout.FileMem("pcm")}).Code(&code).Do()

    if err != nil {
        fmt.Printf("%s\n", err)
    }   

    if code != 200 {
    }   
}

v0.0.1版本-版本计划

deadline

2019年09月30日

changelog

未完成

已完成

  • #27 新增coverprofile
  • #49 支持cookie
  • #44 新增beego/httplib对标文档,方便迁移过来
  • #47 顶层method函数
  • #43 设置代理服务
  • #42 Callback接口,支持服务端接口返回多种数据结构
  • #40 Def函数,New(nil)的别名接口
  • #38 BindBody,Bind服务端数据到string或者到[]byte里面
  • #36 SetBody,设置string或者[]byte到请求body里面
  • #29 #26 ,ToForm设置multipart/form-data到body里面
  • #17 SetYAML, BindYAML
  • #16 SetXML, BindXML
  • #13 SetQuery
  • #12 BindHeader
  • #11 SetHeader
  • #7 SetJSON, BindJSON
  • #5 Group
  • #3 method 函数

设计思路总纲

目的

  • 打造一个用着爽到极致的http client
  • 尽量一个API做一类事情
  • 性能必须最吊
  • API设计必须好看
  • 测试必须覆盖到位
  • 如有可能追求零拷贝

support http query string(encode)

伪代码

out := gout.New(nil)
code := 0

if err := out.GET(":8080/testquery").ToQuery().Code(&code).Do(); err != nil {
}

ToQuery支持的类型有
* string
* map[string]interface{},可以使用gout.H别名
* struct
* array, slice(长度必须是偶数)

具体示例

// string
ToQuery("check_in=2019-06-18&check_out=2018-06-18")

// gout.H 或者 map[string]interface{}
ToQuery(gout.H{
    "check_in":"2019-06-18",
    "check_out":"2019-06-18",
})

// struct
type testQuery struct {
    CheckIn string `query:checkin`
    CheckOut string `query:checkout`
}
ToQuery(&testQuery{CheckIn:2019-06-18, CheckOut:2019-06-18})

// array or slice
// ?active=enable&action=drop
ToQuery([]string{"active", "enable", "action", "drop"})

todo gin绑定query里面有map是怎么回事?

修改callback demo代码

说明

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

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/guonaihong/gout"
	"math/rand"
	"time"
)

type Result struct {
	Errmsg  string `json:"errmsg"`
	ErrCode int    `json:"errcode"`
}

// 模拟nginx
func server() {
	router := gin.Default()

	router.GET("/", func(c *gin.Context) {

		rand.Seed(time.Now().UnixNano())
		x := rand.Intn(2) //生成0-1随机整数
		switch x {
		case 0: // 模拟404找不到资源
			c.String(404, "<html> not found </html>")
		case 1:
			c.JSON(200, Result{Errmsg: "ok"})
		}
	})

	router.Run()
}

func main() {
	go server()
	time.Sleep(time.Millisecond * 500)

	r, str404 := Result{}, ""
	code := 0

	err := gout.GET(":8080").Code(&code).Callback(func(c *gout.Context) (err error) {

		switch c.Code {
		case 200:
			err = c.BindJSON(&r)
		case 404:
			err = c.BindBody(&str404)
		}
		return

	}).Do()

	if err != nil {
		fmt.Printf("err = %s\n", err)
		return
	}

	fmt.Printf("http code = %d, str404(%s), result(%v)\n", code, str404, r)
}

新增http2文档

http2测试网址

https://http2.golang.org/

go 使用https访问http2的服务会自动使用http2协议

package main

import (
	"fmt"
	"github.com/guonaihong/gout"
)

func main() {
	s := ""
	err := gout.GET("https://http2.golang.org/reqinfo").SetBody("hello, ###########").BindBody(&s).Do()
	fmt.Printf("err = %s\n", err)

	fmt.Printf("body length:%d\n", len(s))
	fmt.Printf("%s\n", s)
}

debug模式下,支持json语法高亮

Debug函数传入gout.DebugColor(),就可以打开颜色高亮功能。

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/guonaihong/gout"
	"time"
)

func server() {
	router := gin.Default()
	router.POST("/colorjson", func(c *gin.Context) {
		c.JSON(200, gin.H{"str2": "str2 val", "int2": 2})
	})

	router.Run()
}
func main() {
	go server()

	time.Sleep(time.Millisecond * 500)

	err := gout.POST(":8080/colorjson").
		Debug(true). //可以使用gout.NoColor() 输出没有颜色高亮的debug信息
		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)
	}
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.