- Notifications
You must be signed in to change notification settings - Fork113
gout to become the Swiss Army Knife of the http client @^^@---> gout 是http client领域的瑞士军刀,小巧,强大,犀利。具体用法可看文档,如使用迷惑或者API用得不爽都可提issues
License
guonaihong/gout
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
gout 是go写的http 客户端,为提高工作效率而开发
- 支持设置 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等)
- 等等更多
- GET POST PUT DELETE PATH HEAD OPTIONS
- Query Parameters
- http header*Do not convert http headers
- http body
- proxy
- cookie
- context
- unix socket
- http2 doc
- debug mode
go get github.com/guonaihong/gout
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
Gout默认使用语言内置的encoding/json
包。但是如果你想使用其他的json包,可以通过build tag
来修改。
go build -tags=jsoniter.
go build -tags=go_json.
go build -tags="sonic avx".
package mainimport ("fmt""github.com/guonaihong/gout""time")// 用于解析 服务端 返回的http bodytypeRspBodystruct {ErrMsgstring`json:"errmsg"`ErrCodeint`json:"errcode"`Datastring`json:"data"`}// 用于解析 服务端 返回的http headertypeRspHeaderstruct {Sidstring`header:"sid"`Timeint`header:"time"`}funcmain() {rsp:=RspBody{}header:=RspHeader{}//code := 0err:=gout.// POST请求POST("127.0.0.1:8080").// 打开debug模式Debug(true).// 设置查询字符串SetQuery(gout.H{"page":10,"size":10}).// 设置http headerSetHeader(gout.H{"X-IP":"127.0.0.1","sid":fmt.Sprintf("%x",time.Now().UnixNano())}).// SetJSON设置http body为json// 同类函数有SetBody, SetYAML, SetXML, SetForm, SetWWWFormSetJSON(gout.H{"text":"gout"}).// BindJSON解析返回的body内容// 同类函数有BindBody, BindYAML, BindXMLBindJSON(&rsp).// 解析返回的http headerBindHeader(&header).// http code// Code(&code).// 结束函数Do()// 判断错误iferr!=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"}*/
package mainimport ("github.com/guonaihong/gout")funcmain() {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()// 发送OPTIONSgout.OPTIONS(url).Do()}
package mainimport ("github.com/guonaihong/gout")typetestURLTemplateCasestruct {Hoststring}funcmain() {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()// 发送OPTIONSgout.OPTIONS(url,testURLTemplateCase{Host:"www.google.com"}).Do()}
package mainimport ("fmt""github.com/guonaihong/gout""time")funcmain() {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()iferr!=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*/
package mainimport ("github.com/guonaihong/gout")funcmain() {code:=0err:=gout.//发送GET请求 :8080/testquery是127.0.0.1:8080/testquery简写GET(":8080/testquery").// 设置查询字符串SetQuery(/*看下面支持的情况*/ ).//解析http code,如不关心服务端返回状态吗,不设置该函数即可Code(&code).Do()iferr!=nil { }}/*SetQuery支持的类型有* string* map[string]interface{},可以使用gout.H别名* struct* array, slice(长度必须是偶数)*/// 1.stringSetQuery("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.structtypetestQuerystruct {CheckInstring`query:checkin`CheckOutstring`query:checkout`}SetQuery(&testQuery{CheckIn:2019-06-18,CheckOut:2019-06-18})// 4.array or slice// ?active=enable&action=dropSetQuery([]string{"active","enable","action","drop"})`
与SetHeader API唯一的区别就是不修改header名. 大部分情况用SetHeader,如果有不修改header的需求再使用SetHeaderRaw。
package mainimport ("fmt""github.com/guonaihong/gout""time")funcmain() {err:=gout.//设置GET请求和url,:8080/test.header是127.0.0.1:8080/test.header的简写GET(":8080/test.header").//设置debug模式Debug(true).//设置请求http headerSetHeaderRaw(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()iferr!=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*/
package mainimport ("fmt""github.com/guonaihong/gout""time")funcmain() {err:=gout.//设置GET请求和url,:8080/test.header是127.0.0.1:8080/test.header的简写GET(":8080/test.header").//设置debug模式Debug(true).//设置请求http headerSetHeader(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()iferr!=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*/
package mainimport ("fmt""github.com/guonaihong/gout""time")// 和解析json类似,如要解析http header需设置header tagtyperspHeaderstruct {Totalint`header:"total"`Sidstring`header:"sid"`Time time.Time`header:"time" time_format:"2006-01-02"`}funcmain() {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()iferr!=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*/
package mainimport ("fmt""github.com/guonaihong/gout")typetestHeaderstruct {CheckInstring`header:checkin`CheckOutstring`header:checkout`}funcmain() {t:=testHeader{}code:=0err:=gout.GET(":8080/testquery").Code(&code).SetHeader(/*看下面支持的类型*/ ).BindHeader(&t).Do()iferr!=nil {fmt.Printf("fail:%s\n",err) } }
- BindHeader支持的类型有结构体
// structtypetestHeaderstruct {CheckInstring`header:checkin`CheckOutstring`header:checkout`}
- SetHeader支持的类型有
/*map[string]interface{},可以使用gout.H别名structarray, slice(长度必须是偶数)*/// gout.H 或者 map[string]interface{}SetHeader(gout.H{"check_in":"2019-06-18","check_out":"2019-06-18",})// structtypetestHeaderstruct {CheckInstring`header:checkin`CheckOutstring`header:checkout`}SetHeader(&testHeader{CheckIn:2019-06-18,CheckOut:2019-06-18})// array or slice// -H active:enable -H action:dropSetHeader([]string{"active","enable","action","drop"})
数据校验使用https://github.com/go-playground/validator 完成功能, 更多语法可看该文档.
import ("fmt""github.com/guonaihong/gout")typetestValidstruct {Valstring`valid:"required"` }funcmain() {tv:=testValid{}err:=gout.// 设置POST方法和urlPOST(":8080/req/body").//打开debug模式Debug(true).//解析json, 并且当需要的字段没有值时, 返回错误BindJSON(&tv).//结束函数Do()iferr!=nil {fmt.Printf("%s\n",err)return }}
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拦截器修改示例typedemoResponseMiddlerstruct{}func (d*demoResponseMiddler)ModifyResponse(response*http.Response)error {// 修改responseBody。 因为返回值大概率会有 { code, data,msg} 等字段,希望进行统一处理//这里想验证code. 如果不对就返回error。 对的话将 data中的内容写入body,这样后面BindJson的时候就可以直接处理业务了all,err:=ioutil.ReadAll(response.Body)iferr!=nil {returnerr }obj:=make(map[string]interface{})err=json.Unmarshal(all,&obj)iferr!=nil {returnerr }code:=obj["code"]msg:=obj["msg"]data:=obj["data"]// Go中json中的数字经过反序列化会成为float64类型iffloat64(200)!=code {returnerrors.New(fmt.Sprintf("请求失败, code %d msg %s",code,msg)) }else {byt,_:=json.Marshal(&data)response.Body=ioutil.NopCloser(bytes.NewReader(byt))returnnil }}funcdemoResponse()*demoResponseMiddler {return&demoResponseMiddler{}}funcmain() {goserver()//等会起测试服务time.Sleep(time.Millisecond*500)//用时间做个等待同步responseUseExample()}funcresponseUseExample() {//成功请求successRes:=new(map[string]interface{})err:=gout.GET(":8080/success").ResponseUse(demoResponse(),// 注意这里使用了WithResponseMiddlerFuncmiddler.WithResponseMiddlerFunc(func(response*http.Response)error {// Do your magicreturnnil }), ).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)}typeResultstruct {Codeint`json:"code"`Msgstring`json:"msg"`Datainterface{}`json:"data"`}typeItemstruct {Idstring`json:"id"`Namestring`json:"name"`}funcserver() {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()}
// SetBody 设置string, []byte等类型数据到http body里面// SetBody支持的更多数据类型可看下面package mainimport ("fmt""github.com/guonaihong/gout")funcmain() {err:=gout.// 设置POST方法和urlPOST(":8080/req/body").//打开debug模式Debug(true).// 设置非结构化数据到http body里面// 设置json需使用SetJSONSetBody("send string").//结束函数Do()iferr!=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*/
package mainimport ("fmt""github.com/guonaihong/gout""net/http")funcmain() {header:=make(http.Header)err:=gout.// 设置POST方法和urlPOST(":8080/req/body").//打开debug模式Debug(true).// 获取所有的响应http headerBindHeader(&header).//结束函数Do()iferr!=nil {fmt.Printf("%s\n",err)return }}
// BindBody bind body到string, []byte等类型变量里面package mainimport ("fmt""github.com/guonaihong/gout")funcmain() {s:=""err:=gout.// 设置GET 方法及urlGET("www.baidu.com").// 绑定返回值BindBody(&s).// 结束函数Do()iferr!=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
package mainimport ("fmt""github.com/guonaihong/gout")funcmain() {err:=gout.POST(":8080/colorjson").//打开debug模式Debug(true).//设置json到请求bodySetJSON( gout.H{"str":"foo","num":100,"bool":false,"null":nil,"array": gout.A{"foo","bar","baz"},"obj": gout.H{"a":1,"b":2}, }, ).Do()iferr!=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"}*/
package mainimport ("fmt""github.com/guonaihong/gout")typerspstruct {ErrMsgstring`json:"errmsg"`ErrCodeint`json:"errcode"`}funcmain() {rsp:=rsp{}err:=gout.GET(":8080/colorjson").//打开debug模式Debug(true).//绑定响应json数据到结构体BindJSON(&rsp).//结束函数Do()iferr!=nil {fmt.Printf("err = %v\n",err) }}
- 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
- SetYAML() 设置请求http body为yaml
- BindYAML() 解析响应http body里面的yaml到结构体里面
发送yaml到服务端,然后把服务端返回的yaml结果解析到结构体里面
typedatastruct {Idint`yaml:"id"`Datastring`yaml:"data"`}vard1,d2datavarhttpCodeinterr:=gout.POST(":8080/test.yaml").SetYAML(&d1).BindYAML(&d2).Code(&httpCode).Do()iferr!=nil||httpCode!=200{fmt.Printf("send fail:%s\n",err)}
- SetXML() 设置请求http body为xml
- BindXML() 解析响应http body里面的xml到结构体里面
发送xml到服务端,然后把服务端返回的xml结果解析到结构体里面
typedatastruct {Idint`xml:"id"`Datastring`xml:"data"`}vard1,d2datavarhttpCodeinterr:=gout.POST(":8080/test.xml").SetXML(&d1).BindXML(&d2).Code(&httpCode).Do()iferr!=nil||httpCode!=200{fmt.Printf("send fail:%s\n",err)}
- 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 mainimport ("fmt""github.com/guonaihong/gout")funcmain() {code:=0err:=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()iferr!=nil {fmt.Printf("%s\n",err) }ifcode!=200 { }}/*> POST /test HTTP/1.1> Content-Type: multipart/form-data; boundary=2b0685e5b98e540f80b247d5e7c1283807aa07e62b827543859a6db765a8>--2b0685e5b98e540f80b247d5e7c1283807aa07e62b827543859a6db765a8Content-Disposition: form-data; name="mode"A--2b0685e5b98e540f80b247d5e7c1283807aa07e62b827543859a6db765a8Content-Disposition: form-data; name="text"good--2b0685e5b98e540f80b247d5e7c1283807aa07e62b827543859a6db765a8Content-Disposition: form-data; name="voice"; filename="voice"Content-Type: application/octet-streampcm pcm pcm--2b0685e5b98e540f80b247d5e7c1283807aa07e62b827543859a6db765a8Content-Disposition: form-data; name="voice2"; filename="voice2"Content-Type: application/octet-streampcm--2b0685e5b98e540f80b247d5e7c1283807aa07e62b827543859a6db765a8--< HTTP/1.1 200 OK< Server: gurl-server< Content-Length: 0*/
- 使用结构体
typetestFormstruct {Modestring`form:"mode"`Textstring`form:"text"`Voicestring`form:"voice" form-file:"true"`//从文件中读取Voice2 []byte`form:"voice2" form-file:"mem"`//从内存中构造}typerspstruct{ErrMsgstring`json:"errmsg"`ErrCodeint`json:"errcode"`}t:=testForm{}r:=rsp{}code:=0err:=gout.POST(url).SetForm(&t).ShoudBindJSON(&r).Code(&code).Do()iferr!=nil {}
- 使用SetWWWForm函数实现发送x-www-form-urlencoded类型数据
package mainimport ("fmt""github.com/guonaihong/gout")funcmain() {code:=0err:=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()iferr!=nil {fmt.Printf("%s\n",err)return }ifcode!=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*/
SetProtoBuf支持,protobuf序列化后的[]byte,或者生成的protobuf结构体指针
package mainimport ("github.com/guonaihong/gout")funcmain() {httpCode:=0err:=GET(":8080/echo").SetProtoBuf(/* protobuf 生成的结构体,必须传指针类型*/ ).Code(&httpCode).Do()}
callback主要用在,服务端会返回多种格式body的场景, 比如404返回的是html, 200返回json。这时候要用Callback挂载多种处理函数,处理不同的数据结构
funcmain() {r,str404:=Result{},""code:=0err:=gout.GET(":8080").Callback(func(c*gout.Context) (errerror) {switchc.Code {case200://http code为200时,服务端返回的是json 结构c.BindJSON(&r)case404://http code为404时,服务端返回是html 字符串c.BindBody(&str404) }code=c.Codereturnnil }).Do()iferr!=nil {fmt.Printf("err = %s\n",err)return }fmt.Printf("http code = %d, str404(%s) or json result(%v)\n",code,str404,r)}
funcmain() {resp,err:=gout.GET(":8080").SetJSON(`{"test":"value"}`).Response()ifresp!=nil {deferresp.Body.Close() }}
支持绑定多个对象, BindXXX函数可以多次调用。例子里面是BindJSON和BindBody
varresponseStructstruct {Namestring`json:"name"`Ageint`json:"age"`}funcmain() {varresponseStrstringerr:=gout.GET("url").SetQuery(gout.H{}).BindJSON(&responseStruct).BindBody(&responseStr).Do()iferr!=nil {return }log.Println(responseStr)}
响应头里面指明压缩格式,使用AutoDecodeBody接口可以自动解压。
//Content-Encoding: gzip//Content-Encoding: deflate//Content-Encoding: br//gzip由标准库原生支持,不需要使用AutoDecodeBody接口,后两种由gout支持.funcmain() {gout.GET(url).AutoDecodeBody().BindBody(&s).Do()}
setimeout是request级别的超时方案。相比http.Client级别,更灵活。
package mainimport ("fmt""github.com/guonaihong/gout""time")funcmain() {err:=gout.GET(":8080").SetTimeout(2*time.Second).Do()iferr!=nil {fmt.Printf("err = %v\n",err) }}
- SetProxy 设置代理服务地址
package mainimport ("fmt""github.com/guonaihong/gout""log")funcmain() {c:=&http.Client{}s:=""err:=gout.New(c).GET("www.qq.com").// 设置proxy服务地址SetProxy("http://127.0.0.1:7000").// 绑定返回数据到s里面BindBody(&s).Do()iferr!=nil {log.Println(err)return }fmt.Println(s)}
- SetSOCKS5 设置socks5地址
package mainimport ("fmt""github.com/guonaihong/gout""log""net/http")funcmain() {c:=&http.Client{}s:=""err:=gout.New(c).GET("www.qq.com").// 设置proxy服务地址SetSOCKS5("127.0.0.1:7000").// 绑定返回数据到s里面BindBody(&s).Do()iferr!=nil {log.Println(err)return }fmt.Println(s)}
- SetCookies设置cookie, 可以设置一个或者多个cookie
package mainimport ("fmt""github.com/guonaihong/gout""net/http")funcmain() {// === 发送多个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()iferr!=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)}
使用SetBasicAuth
接口
funcmain() {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()iferr!=nil {fmt.Printf("err = %v\n",err) }}
- WithContext设置context,可以取消http请求
package mainimport ("context""github.com/guonaihong/gout""time")funcmain() {// 声明一个contextctx,cancel:=context.WithCancel(context.Background())//调用cancel可取消http请求gofunc() {time.Sleep(time.Second)cancel() }()err:=gout.GET("127.0.0.1:8080/cancel").//设置GET请求以及需要访问的urlWithContext(ctx).//设置context, 外层调用cancel函数就可取消这个http请求Do()iferr!=nil { } }
- UnixSocket可以把http底层通信链路由tcp修改为unix domain socket
下面的例子,会通过domain socket发送http GET请求,http body的内容是hello world
package mainimport ("fmt""github.com/guonaihong/gout""net/http")funcmain() {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)}
go 使用https访问http2的服务会自动启用http2协议,这里不需要任何特殊处理
- https://http2.golang.org/ (bradfitz建的http2测试网址,里面大约有十来个测试地址,下面的例子选了一个)
package mainimport ("fmt""github.com/guonaihong/gout")funcmain() {s:=""err:=gout.GET("https://http2.golang.org/reqinfo").//设置GET请求和请求urlDebug(true).//打开debug模式,可以看到请求数据和响应数据SetBody("hello, ###########").//设置请求body的内容,如果你的请求内容是json格式,需要使用SetJSON函数BindBody(&s).//解析响应body内容Do()//结束函数iferr!=nil {fmt.Printf("send fail:%s\n",err) }_=s }
该模式主要方便调试用的,默认开启颜色高亮(如果要关闭颜色高亮,请往下看)
funcmain() {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()iferr!=nil {fmt.Printf("err = %v\n",err) }}
使用debug.NoColor()传入Debug函数关闭颜色高亮
import ("github.com/guonaihong/gout""github.com/guonaihong/gout/debug")funcmain() {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()iferr!=nil {fmt.Printf("err = %v\n",err) }}
debug 自定义模式,可传递函数。下面演示用环境变量开启debug模式(只有传递IOS_DEBUG环境变量才输出日志)
package mainimport ("fmt""github.com/guonaihong/gout""github.com/guonaihong/gout/debug""os")funcIOSDebug() debug.Apply {returngout.DebugFunc(func(o*debug.Options) {iflen(os.Getenv("IOS_DEBUG"))>0 {o.Debug=true } }) }funcmain() {s:=""err:=gout.GET("127.0.0.1:8080").// Debug可以支持自定义方法// 可以实现设置某个环境变量才输出debug信息// 或者debug信息保存到文件里面,可以看下_example/15-debug-save-file.goDebug(IOSDebug()).SetBody("test hello").BindBody(&s).Do()fmt.Printf("err = %v\n",err)}// env IOS_DEBUG=true go run customize.go
debug.Trace()可输出http各个阶段的耗时,比如dns lookup时间,tcp连接时间等等。可以很方便的做些性能调优。
package mainimport ("fmt""github.com/guonaihong/gout""github.com/guonaihong/gout/debug")funcopenDebugTrace() {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()iferr!=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): ===================
debug.ToWriter
可以传递任何io.Writer对象,比如bytes.Buffer
, 文件等。。。
package mainimport ("bytes""fmt""github.com/guonaihong/gout""github.com/guonaihong/gout/debug")funcmain() {varbuf bytes.Buffererr:=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()iferr!=nil {fmt.Printf("err = %v\n",err) }fmt.Println(buf.String())}
import ("github.com/guonaihong/gout""github.com/guonaihong/gout/debug")funcmain() {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()iferr!=nil {fmt.Printf("err = %v\n",err) }}
package mainimport ("fmt""github.com/guonaihong/gout""github.com/guonaihong/gout/debug")funcmain() {varbuf bytes.Buffererr:=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()iferr!=nil {fmt.Printf("err = %v\n",err) }fmt.Printf("%s",buf.String())}
下面的例子,起了20并发。对:8080端口的服务,发送3000次请求进行压测,内容为json结构
package mainimport ("fmt""github.com/guonaihong/gout")const (benchNumber=30000benchConcurrent=20)funcmain() {err:=gout.POST(":8080").//压测本地8080端口SetJSON(gout.H{"hello":"world"}).//设置请求body内容Filter().//打开过滤器Bench().//选择bench功能Concurrent(benchConcurrent).//并发数Number(benchNumber).//压测次数Do()iferr!=nil {fmt.Printf("%v\n",err) }}
下面的例子,起了20并发。对:8080端口的服务,压测持续时间为10s,内容为json结构
package mainimport ("fmt""github.com/guonaihong/gout""time")const (benchTime=10*time.SecondbenchConcurrent=30)funcmain() {err:=gout.POST(":8080").//压测本机8080端口SetJSON(gout.H{"hello":"world"}).//设置请求body内容Filter().//打开过滤器Bench().//选择bench功能Concurrent(benchConcurrent).//并发数Durations(benchTime).//压测时间Do()iferr!=nil {fmt.Printf("%v\n",err) }}
下面的例子,起了20并发。对:8080端口的服务,压测总次数为3000次,其中每秒发送1000次。内容为json结构
package mainimport ("fmt""github.com/guonaihong/gout")const (benchNumber=3000benchConcurrent=20)funcmain() {err:=gout.POST(":8080").//压测本机8080端口SetJSON(gout.H{"hello":"world"}).//设置请求body内容Filter().//打开过滤器Bench().//选择bench功能Rate(1000).//每秒发1000请求Concurrent(benchConcurrent).//并发数Number(benchNumber).//压测次数Do()iferr!=nil {fmt.Printf("%v\n",err) }}
自定义压测函数,构造每次不一样的http request数据
package mainimport ("fmt""github.com/google/uuid""github.com/guonaihong/gout""github.com/guonaihong/gout/filter""sync/atomic")funcmain() {i:=int32(0)err:=filter.NewBench().Concurrent(30).//开30个go程Number(30000).//压测30000次Loop(func(c*gout.Context)error {// 下面的代码,每次生成不一样的http body 用于压测uid:=uuid.New()//生成uuidid:=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)})returnnil }).Do()iferr!=nil {fmt.Printf("err = %v\n",err) }}
retry 功能使用带抖动功能和指数回退的算法实现backoff
package mainimport ("fmt""github.com/guonaihong/gout""time")funcmain() {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()iferr!=nil {fmt.Printf("err = %v\n",err) }}
指定重试条件,这里面的例子是服务端返回的状态码是209进行重试完整代码
package mainimport ("fmt""github.com/guonaihong/gout""github.com/guonaihong/gout/filter""time")funcuseRetryFuncCode() {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 {ifc.Error!=nil||c.Code==209 {returnfilter.ErrRetry }returnnil }).Do()fmt.Printf("err = %v\n",err)}
指定条件进行重试,这里的例子是默认url不能访问,使用backup url进行访问完整代码
package mainimport ("fmt""github.com/guonaihong/gout""github.com/guonaihong/gout/core""github.com/guonaihong/gout/filter""time")funcuseRetryFunc() {// 获取一个没有服务绑定的端口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 {ifc.Error!=nil {c.SetHost(":1234")//必须是存在的端口returnfilter.ErrRetry }returnnil }).Do()fmt.Printf("err = %v\n",err)}
package mainimport ("fmt""github.com/guonaihong/gout")funcmain() {s:=`POST /colorjson HTTP/1.1Host: 127.0.0.1:8080User-Agent: Go-http-client/1.1Content-Length: 97Content-Type: application/jsonAccept-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()iferr!=nil {fmt.Printf("err = %s\n",err)return }}
仅仅生成curl命令, 不会发送http请求
package mainimport ("fmt""github.com/guonaihong/gout")funcmain() {// 1.formdataerr:=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 bodyerr=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)}
生成curl命令, 同时执行http请求, 在Curl()
命令之行跟上GenAndSend()
接口
package mainimport ("fmt""github.com/guonaihong/gout")funcmain() {// 1.formdataerr:=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 bodyerr=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)}
使用New接口即可使用自己的http.Client对象
package mainimport ("fmt""net/http""github.com/guonaihong/gout")funcmain() {c:=&http.Client{}//http.Client里面有fd连接池,如果对这块优化不是太了解,只使用一个实例就行err:=gout.New(c).// New接口可传入http.Client对象GET("www.qq.com").Debug(true).Do()iferr!=nil {fmt.Printf("err = %s\n",err)return }}
使用Chunked接口, 设置为"Transfer-Encoding: chunked"的数据编码方式
package mainimport ("fmt""github.com/guonaihong/gout")funcmain() {err:=gout.POST(":8080").Chunked().SetBody("11111111111").Do()iferr!=nil {fmt.Printf("err :%v\n",err) } }// 使用nc 起一个tcp服务, 使用上面的代码发起数据观察下结果// nc -l 8080
这里记录全局配置的方法, 后面所有的全局配置都推荐使用gout.NewWithOpt
接口的实现
忽略ssl验证, 使用gout.WithInsecureSkipVerify()
接口配置该功能, 传入gout.NewWithOpt
接口即可生效.
import ("github.com/guonaihong/gout")funcmain() {// globalWithOpt里面包含连接池, 这是一个全局可复用的对象globalWithOpt:=gout.NewWithOpt(gout.WithInsecureSkipVerify())err:=globalWithOpt.GET("url").Do()iferr!=nil {fmt.Printf("err = %v\n" ,err)return }}
golang client库默认遇到301的状态码会自动跳转重新发起新请求, 你希望关闭这种默认形为, 那就使用下面的功能
import ("github.com/guonaihong/gout")funcmain() {// globalWithOpt里面包含连接池, 这是一个全局可复用的对象, 一个进程里面可能只需创建1个globalWithOpt:=gout.NewWithOpt(gout.WithClose3xxJump())err:=globalWithOpt.GET("url").Do()iferr!=nil {fmt.Printf("err = %v\n" ,err)return }}
gout.WithTimeout
为了让大家少用gout.SetTimeout
而设计
import ("github.com/guonaihong/gout")funcmain() {// globalWithOpt里面包含连接池, 这是一个全局可复用的对象, 一个进程里面可能只需创建1个globalWithOpt:=gout.NewWithOpt(gout.WithTimeout())err:=globalWithOpt.GET("url").Do()iferr!=nil {fmt.Printf("err = %v\n" ,err)return }}
gout.WithUnixSocket
为了让大家少用.UnixSocket
而设计
import ("github.com/guonaihong/gout")funcmain() {// globalWithOpt里面包含连接池, 这是一个全局可复用的对象, 一个进程里面可能只需创建1个, 如果有多个不同的unixsocket,可以创建多个globalWithOpt:=gout.NewWithOpt(gout.WithUnixSocket("/tmp/test.socket"))err:=globalWithOpt.GET("url").Do()iferr!=nil {fmt.Printf("err = %v\n" ,err)return }}
gout.WithProxy
为了让大家少用.SetProxy
而设计
import ("github.com/guonaihong/gout")funcmain() {// globalWithOpt里面包含连接池, 这是一个全局可复用的对象, 一个进程里面可能只需创建1个, 如果有多个不同的proxy,可以创建多个globalWithOpt:=gout.NewWithOpt(gout.WithProxy("http://127.0.0.1:7000"))err:=globalWithOpt.GET("url").Do()iferr!=nil {fmt.Printf("err = %v\n" ,err)return }}
gout.WithSocks5
为了让大家少用.SetSOCKS5
而设计
import ("github.com/guonaihong/gout")funcmain() {// globalWithOpt里面包含连接池, 这是一个全局可复用的对象, 一个进程里面可能只需创建1个, 如果有多个不同的socks5,可以创建多个globalWithOpt:=gout.NewWithOpt(gout.WithSocks5("127.0.0.1:7000"))err:=globalWithOpt.GET("url").Do()iferr!=nil {fmt.Printf("err = %v\n" ,err)return }}
设置全局超时时间。可以简化一些代码。在使用全局配置默认你已经了解它会带来的一些弊端.
package mainimport ("fmt""github.com/guonaihong/gout""time")funcmain() {gout.SetTimeout(time.Second*1)err:=gout.GET("www.baidu.com").Do()iferr!=nil {fmt.Printf("err is:%v\n") }}
打开全局debug开关。
package mainimport ("fmt""github.com/guonaihong/gout")funcmain() {gout.SetDebug(true)err:=gout.GET(":8080/colorjson").Do()iferr!=nil {fmt.Printf("err is:%v\n") }}
gout 设计之初就考虑到要和gin协同工作的可能性,下面展示如何方便地使用gout转发gin绑定的数据。
package mainimport ("github.com/gin-gonic/gin""github.com/guonaihong/gout")typetestQuerystruct {Sizeint`query:"size" form:"size"`// query tag是gout设置查询字符串需要的Pageint`query:"page" form:"page"`Akstring`query:"ak" form:"ak"`}//下一个服务节点funcnextSever() {r:=gin.Default()r.GET("/query",func(c*gin.Context) {q:=testQuery{}err:=c.ShouldBindQuery(&q)iferr!=nil {return }c.JSON(200,q) })r.Run(":1234")}funcmain() {gonextSever()r:=gin.Default()// 演示把gin绑定到的查询字符串转发到nextServer节点r.GET("/query",func(c*gin.Context) {q:=testQuery{}// 绑定查询字符串err:=c.ShouldBindQuery(&q)iferr!=nil {return }// 开发转发, 复用gin所用结构体变量qcode:=0// http codeerr:=gout.//发起GET请求GET("127.0.0.1:1234/query").//设置查询字符串SetQuery(q).//关心http server返回的状态码 设置该函数Code(&code).Do()iferr!=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'
下面是与apache ab的性能对比_example/16d-benchmark-vs-ab.go
About
gout to become the Swiss Army Knife of the http client @^^@---> gout 是http client领域的瑞士军刀,小巧,强大,犀利。具体用法可看文档,如使用迷惑或者API用得不爽都可提issues