├── .github
├── ISSUE_TEMPLATE
│ ├── bug-report.md
│ ├── feature-request.md
│ └── general-question.md
└── workflows
│ └── go.yml
├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── _example
├── 01-color-json.go
├── 02-query.go
├── 03a-set-header.go
├── 03b-bind-header.go
├── 04a-set-body.go
├── 04b-bind-body.go
├── 05a-timeout.go
├── 05b-cancel.go
├── 06-x-www-form-urlencoded.go
├── 07-callback.go
├── 08-cookie.go
├── 09-unix.go
├── 10a-debug.go
├── 10b-debug-custom.go
├── 10c-debug-trace.go
├── 11-xml.go
├── 12-yaml.go
├── 13-form-data.go
├── 14-upload-file.go
├── 15-debug-save-file.go
├── 16a-benchmark-number.go
├── 16b-benchmark-duration.go
├── 16c-benchmark-rate.go
├── 16d-benchmark-vs-ab.go
├── 16e-customize-bench.go
├── 17a-import-rawhttp.go
├── 18a-gen-curl.go
├── 19a-retry.go
├── 19b-retry-customize-backup.go
├── 19c-retry-httpcode.go
├── 20-socks5.go
├── README.md
├── go.mod
└── go.sum
├── bench
├── report.go
├── report_test.go
├── task.go
├── task_test.go
├── tmpl.go
└── tmpl_test.go
├── color
├── color.go
├── color_core.go
├── color_core_test.go
└── color_test.go
├── core
├── core.go
├── core_test.go
├── port.go
├── port_test.go
├── test.go
├── utils.go
└── utils_test.go
├── dataflow
├── cleanpath.go
├── cleanpath_test.go
├── context.go
├── context_test.go
├── dataflow.go
├── dataflow_auto_decode_body_test.go
├── dataflow_body_test.go
├── dataflow_form_test.go
├── dataflow_header_test.go
├── dataflow_json_test.go
├── dataflow_middleware_test.go
├── dataflow_query_test.go
├── dataflow_test.go
├── dataflow_wwwform_test.go
├── debug_test.go
├── debug_trace_test.go
├── export.go
├── export_test.go
├── filter.go
├── filter_interface.go
├── filter_test.go
├── global_config.go
├── gout.go
├── gout_test.go
├── register.go
├── req.go
├── req_basic_auth_test.go
├── req_bench_test.go
├── req_test.go
├── req_url_template_test.go
├── response_test.go
└── validator.go
├── debug
├── api.go
├── debug.go
├── debug_test.go
└── debug_trace.go
├── decode
├── body.go
├── body_test.go
├── decode.go
├── decode_core.go
├── decode_core_test.go
├── decoder.go
├── header.go
├── header_test.go
├── json.go
├── json_test.go
├── xml.go
├── xml_test.go
├── yaml.go
└── yaml_test.go
├── encode
├── body.go
├── body_test.go
├── encode_core.go
├── encode_core_test.go
├── form.go
├── form_test.go
├── header.go
├── header_test.go
├── protobuf.go
├── protobuf_test.go
├── query.go
├── query_test.go
├── www_form.go
├── www_form_test.go
├── xml.go
├── xml_test.go
├── yaml.go
└── yaml_test.go
├── encoder
└── encoder.go
├── enjson
├── json.go
└── json_test.go
├── export
├── curl.go
├── curl_core.go
├── curl_core_test.go
├── curl_test.go
├── curl_tmpl.go
└── init.go
├── filter
├── bench.go
├── bench_test.go
├── init.go
├── retry.go
└── retry_test.go
├── go.mod
├── go.sum
├── gout.go
├── gout_chunked_test.go
├── gout_global_setdebug_test.go
├── gout_issue_389_test.go
├── gout_newopt.go
├── gout_newopt_method_test.go
├── gout_newopt_timeout_and_global_test.go
├── gout_newopt_with_3xx_test.go
├── gout_newopt_with_skip_verify_test.go
├── gout_options.go
├── gout_test.go
├── gout_valid_test.go
├── hcutil
├── README.md
├── hcutil.go
└── hcutil_test.go
├── import.go
├── import_rawtext.go
├── import_rawtext_test.go
├── interface
├── README.md
├── do.go
├── request_use_interface.go
└── response_use_interface.go
├── json
├── go_json.go
├── json.go
├── jsoniter.go
└── sonic.go
├── middler
├── do.go
├── request_use_interface.go
└── response_use_interface.go
├── middleware
└── rsp
│ └── autodecodebody
│ ├── autodecodebody.go
│ └── autodecodebody_test.go
├── query_test.go
├── req_url_template_test.go
├── setprotobuf_test.go
├── setting
└── setting.go
├── testdata
├── color.data
├── raw-http-post-formdata.txt
├── raw-http-post-json.txt
├── test.pb.go
├── test.pcm
├── test.proto
└── voice.pcm
├── trace_test.go
├── utils.go
├── version.go
└── version_test.go
/.github/ISSUE_TEMPLATE/bug-report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F91D Bug Report"
3 | about: As a User, I want to report a Bug.
4 | labels: type/bug
5 | ---
6 |
7 | ## Bug Report
8 |
9 | Please answer these questions before submitting your issue. Thanks!
10 |
11 | ### 1. Minimal reproduce step (Required)
12 |
13 |
14 |
15 | ### 2. What did you expect to see? (Required)
16 |
17 | ### 3. What did you see instead (Required)
18 |
19 | ### 4. What is your gout version? (Required)
20 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F44F Feature Request"
3 | about: As a user, I want to request a New Feature on the product.
4 | labels: type/feature-request
5 | ---
6 |
7 | ## Feature Request
8 |
9 | **Is your feature request related to a problem? Please describe:**
10 |
11 |
12 | **Describe the feature you'd like:**
13 |
14 |
15 | **Describe alternatives you've considered:**
16 |
17 |
18 | **Teachability, Documentation, Adoption, Migration Strategy:**
19 |
20 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/general-question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: "\U0001F600 Ask a Question"
3 | about: I want to ask a question.
4 | labels: type/question
5 | ---
6 |
7 | ## General Question
8 |
9 |
--------------------------------------------------------------------------------
/.github/workflows/go.yml:
--------------------------------------------------------------------------------
1 | name: Go
2 |
3 | on:
4 | push:
5 | pull_request:
6 |
7 | jobs:
8 |
9 | build:
10 | runs-on: ubuntu-latest
11 | strategy:
12 | matrix:
13 | go: ['1.18', '1.19', '1.20', '1.21', '1.22', '1.23']
14 | name: Go ${{ matrix.go }} sample
15 |
16 | steps:
17 |
18 | - name: Set up Go 1.13
19 | uses: actions/setup-go@v1
20 | with:
21 | go-version: ${{ matrix.go }}
22 | id: go
23 |
24 | - name: Check out code into the Go module directory
25 | uses: actions/checkout@v1
26 |
27 | - name: Get dependencies
28 | run: |
29 | go get -v -t -d ./...
30 | if [ -f Gopkg.toml ]; then
31 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh
32 | dep ensure
33 | fi
34 | - name: Build
35 | run: go build -v .
36 |
37 | - name: Test-386
38 | run: env GOARCH=386 go test -test.run=Test_Retry_sleep -v
39 | #run: env GOARCH=386 go test -v -coverprofile='coverage.out' -covermode=count ./...
40 |
41 | - name: Test-amd64
42 | run: env GOARCH=amd64 go test -v -coverprofile='coverage.out' -covermode=count ./...
43 |
44 |
45 | - name: Upload Coverage report
46 | uses: codecov/codecov-action@v1
47 | with:
48 | token: ${{secrets.CODECOV_TOKEN}}
49 | file: ./coverage.out
50 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *~
3 | *swp
4 | *.exe
5 | *.exe~
6 | *.dll
7 | *.so
8 | *.dylib
9 | /cover.cov
10 | .DS_Store
11 | .idea
12 |
13 |
14 | # Test binary, build with `go test -c`
15 | *.test
16 |
17 | # Output of the go coverage tool, specifically when used with LiteIDE
18 | *.out
19 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | protobuf:
2 | protoc --go_out=. --go_opt=paths=source_relative testdata/test.proto
3 | test:
4 | go test ./...
5 |
--------------------------------------------------------------------------------
/_example/01-color-json.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout"
7 | "time"
8 | )
9 |
10 | // ================SetJSON=接口用法===============
11 | // gout使用SetJSON函数发送json请求至服务端
12 | // 亮点有SetJSON函数支持多种数据类型,map/struct/array/string/bytes
13 | // 下面的xxxExample对应该类型的用法
14 |
15 | func mapExample() {
16 | fmt.Printf("\n\n1.=============color json======map example=====\n\n")
17 | err := gout.POST(":8080/colorjson").
18 | Debug(true).
19 | SetJSON(gout.H{"str": "foo",
20 | "num": 100,
21 | "bool": false,
22 | "null": nil,
23 | "array": gout.A{"foo", "bar", "baz"},
24 | "obj": gout.H{"a": 1, "b": 2},
25 | }).Do()
26 |
27 | if err != nil {
28 | fmt.Printf("err = %v\n", err)
29 | }
30 | }
31 |
32 | func structExample() {
33 |
34 | type req struct {
35 | Str string `json:"str"`
36 | Num int `json:"num"`
37 | Bool bool `json:"bool"`
38 | Null *int `json:"null"`
39 | }
40 |
41 | fmt.Printf("\n\n2.=============color json======struct example=====\n\n")
42 | err := gout.POST(":8080/colorjson").
43 | Debug(true).
44 | SetJSON(req{Str: "foo",
45 | Num: 100,
46 | Bool: false,
47 | Null: nil,
48 | }).Do()
49 |
50 | if err != nil {
51 | fmt.Printf("err = %v\n", err)
52 | }
53 | }
54 |
55 | var query = `
56 | {
57 | "query": {
58 | "bool": {
59 | "must": [
60 | {
61 | "exists": {
62 | "field": "voice"
63 | }
64 | },
65 | {
66 | "match": {
67 | "errcode": 3
68 | }
69 | },
70 | {
71 | "range": {
72 | "time": {
73 | "lt": "2020-01-13T23:16:04+08:00",
74 | "gt": "2020-01-13T00:00:00+08:00"
75 | }
76 | }
77 | }
78 | ]
79 | }
80 | }
81 | }
82 | `
83 |
84 | func stringExample() {
85 | fmt.Printf("\n\n3.=============color json======string example=====\n\n")
86 | err := gout.POST(":8080/colorjson").
87 | Debug(true).
88 | SetJSON(query).Do()
89 |
90 | if err != nil {
91 | fmt.Printf("err = %v\n", err)
92 | }
93 | }
94 |
95 | func bytesExample() {
96 | fmt.Printf("\n\n4.=============color json======bytes example=====\n\n")
97 | err := gout.POST(":8080/colorjson").
98 | Debug(true).
99 | SetJSON(query).Do()
100 |
101 | if err != nil {
102 | fmt.Printf("err = %v\n", err)
103 | }
104 | }
105 |
106 | func main() {
107 | go server()
108 |
109 | time.Sleep(time.Millisecond * 200)
110 |
111 | mapExample()
112 | structExample()
113 | stringExample()
114 | bytesExample()
115 | }
116 |
117 | func server() {
118 | router := gin.New()
119 | router.POST("/colorjson", func(c *gin.Context) {
120 | c.JSON(200, gin.H{"str2": "str2 val", "int2": 2})
121 | })
122 |
123 | router.Run()
124 | }
125 |
--------------------------------------------------------------------------------
/_example/02-query.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout"
7 | "time"
8 | )
9 |
10 | // =====================设置查询字符串example==================
11 | // gout使用SetQuery来设置查询字符串
12 | // 其中SetQuery支持多种数据类型 map/struct/string/array
13 |
14 | type testQuery struct {
15 | // struct里面的form tag是gin用来绑定数据用的,query才是gout需要的tag
16 | Q1 string `query:"q1" form:"q1"`
17 | Q2 int `query:"q2" form:"q2"`
18 | Q3 float32 `query:"q3" form:"q3"`
19 | Q4 float64 `query:"q4" form:"q4"`
20 | Q5 time.Time `query:"q5" form:"q5" time_format:"unix" time_location:"Asia/Shanghai"`
21 | Q6 time.Time `query:"q6" form:"q6" time_format:"unixNano" time_location:"Asia/Shanghai"`
22 | Q7 time.Time `query:"q7" form:"q7" time_format:"2006-01-02" time_location:"Asia/Shanghai"`
23 | }
24 |
25 | func mapExample() {
26 | // 1.使用gout.H
27 | fmt.Printf("======1. SetQuery======use gout.H=====\n")
28 | err := gout.GET(":8080/test.query").
29 | Debug(true).
30 | SetQuery(gout.H{"q1": "v1",
31 | "q2": 2,
32 | "q3": float32(3.14),
33 | "q4": 4.56,
34 | "q5": time.Now().Unix(),
35 | "q6": time.Now().UnixNano(),
36 | "q7": time.Now().Format("2006-01-02")}).
37 | Do()
38 | if err != nil {
39 | fmt.Printf("%s\n", err)
40 | return
41 | }
42 | }
43 |
44 | func arrayExample() {
45 | // 2.使用数组变量
46 | fmt.Printf("======2. SetQuery======use array=====\n")
47 | err := gout.GET(":8080/test.query").
48 | Debug(true).
49 | SetQuery(gout.A{"q1", "v1",
50 | "q2", 2,
51 | "q3", float32(3.14),
52 | "q4", 4.56,
53 | "q5", time.Now().Unix(),
54 | "q6", time.Now().UnixNano(),
55 | "q7", time.Now().Format("2006-01-02")}).
56 | Do()
57 | if err != nil {
58 | fmt.Printf("%s\n", err)
59 | return
60 | }
61 | }
62 |
63 | func structExample() {
64 | // 3.使用结构体
65 | // 使用结构体需要设置query tag
66 | fmt.Printf("======3. SetQuery======use struct=====\n")
67 | err := gout.GET(":8080/test.query").
68 | Debug(true).
69 | SetQuery(testQuery{Q1: "v1",
70 | Q2: 2,
71 | Q3: float32(3.14),
72 | Q4: 4.56,
73 | Q5: time.Now(),
74 | Q6: time.Now(),
75 | Q7: time.Now()}).
76 | Do()
77 | if err != nil {
78 | fmt.Printf("%s\n", err)
79 | return
80 | }
81 | }
82 |
83 | func stringExample() {
84 | // 4.使用string
85 | fmt.Printf("======4. SetQuery======use string=====\n")
86 | err := gout.GET(":8080/test.query").
87 | Debug(true).
88 | SetQuery("q1=v1&q2=2&q3=3.14&q4=3.1415&q5=1564295760&q6=1564295760000001000&q7=2019-07-28").
89 | Do()
90 | if err != nil {
91 | fmt.Printf("%s\n", err)
92 | return
93 | }
94 | }
95 |
96 | func bytesExample() {
97 | // 4.使用string
98 | fmt.Printf("======4. SetQuery======use bytes=====\n")
99 | err := gout.GET(":8080/test.query").
100 | Debug(true).
101 | SetQuery([]byte("q1=v1&q2=2&q3=3.14&q4=3.1415&q5=1564295760&q6=1564295760000001000&q7=2019-07-28")).
102 | Do()
103 | if err != nil {
104 | fmt.Printf("%s\n", err)
105 | return
106 | }
107 | }
108 | func main() {
109 | go server()
110 |
111 | time.Sleep(time.Millisecond)
112 | mapExample()
113 | structExample()
114 | arrayExample()
115 | stringExample()
116 | bytesExample()
117 | }
118 |
119 | func server() {
120 | router := gin.New()
121 | router.GET("/test.query", func(c *gin.Context) {
122 | q2 := testQuery{}
123 | err := c.ShouldBindQuery(&q2)
124 | if err != nil {
125 | c.String(500, "fail")
126 | return
127 | }
128 |
129 | })
130 |
131 | router.Run()
132 | }
133 |
--------------------------------------------------------------------------------
/_example/03a-set-header.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout"
7 | "time"
8 | )
9 |
10 | // ============== gout 设置http header example============
11 | // 使用SetHeader接口 设置http header
12 | // SetHeader支持的数据类型有map/array/struct
13 | type testHeader struct {
14 | H1 string `header:"h1"`
15 | H2 int `header:"h2"`
16 | H3 float32 `header:"h3"`
17 | H4 float64 `header:"h4"`
18 | H5 time.Time `header:"h5" time_format:"unix"`
19 | H6 time.Time `header:"h6" time_format:"unixNano"`
20 | H7 time.Time `header:"h7" time_format:"2006-01-02"`
21 | }
22 |
23 | func mapExample() {
24 | // 1.使用gout.H
25 | fmt.Printf("======1. SetHeader======use gout.H=====\n")
26 | err := gout.GET(":8080/test.header").
27 | Debug(true).
28 | SetHeader(gout.H{"h1": "v1",
29 | "h2": 2,
30 | "h3": float32(3.14),
31 | "h4": 4.56,
32 | "h5": time.Now().Unix(),
33 | "h6": time.Now().UnixNano(),
34 | "h7": time.Now().Format("2006-01-02")}).
35 | Do()
36 | if err != nil {
37 | fmt.Printf("%s\n", err)
38 | return
39 | }
40 | }
41 |
42 | func arrayExample() {
43 | // 2.使用数组变量
44 | fmt.Printf("======2. SetHeader======use array=====\n")
45 | err := gout.GET(":8080/test.header").
46 | Debug(true).
47 | SetHeader(gout.A{"h1", "v1",
48 | "h2", 2,
49 | "h3", float32(3.14),
50 | "h4", 4.56,
51 | "h5", time.Now().Unix(),
52 | "h6", time.Now().UnixNano(),
53 | "h7", time.Now().Format("2006-01-02")}).
54 | Do()
55 | if err != nil {
56 | fmt.Printf("%s\n", err)
57 | return
58 | }
59 | }
60 |
61 | func structExample() {
62 | // 3.使用结构体
63 | // 使用结构体需要设置"header" tag
64 | fmt.Printf("======3. SetHeader======use struct=====\n")
65 | err := gout.GET(":8080/test.header").
66 | Debug(true).
67 | SetHeader(testHeader{H1: "v1",
68 | H2: 2,
69 | H3: float32(3.14),
70 | H4: 4.56,
71 | H5: time.Now(),
72 | H6: time.Now(),
73 | H7: time.Now()}).
74 | Do()
75 | if err != nil {
76 | fmt.Printf("%s\n", err)
77 | return
78 | }
79 | }
80 |
81 | func main() {
82 | go server()
83 |
84 | time.Sleep(time.Millisecond)
85 | mapExample()
86 | arrayExample()
87 | structExample()
88 | }
89 |
90 | func server() {
91 | router := gin.New()
92 | router.GET("/test.header", func(c *gin.Context) {
93 | h2 := testHeader{}
94 | err := c.BindHeader(&h2)
95 | if err != nil {
96 | c.String(500, "fail")
97 | return
98 | }
99 | })
100 |
101 | router.Run()
102 | }
103 |
--------------------------------------------------------------------------------
/_example/03b-bind-header.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout"
7 | "time"
8 | )
9 |
10 | // ============== 解析http header
11 | // 使用BindHeader接口解析http header, 基本数据类型可自动绑定
12 | type rspHeader struct {
13 | Total int `header:"total"`
14 | Sid string `header:"sid"`
15 | Time time.Time `header:"time" time_format:"2006-01-02"`
16 | }
17 |
18 | func bindHeader() {
19 | rsp := rspHeader{}
20 | err := gout.GET(":8080/test.header").
21 | Debug(true).
22 | BindHeader(&rsp). //解析请求header
23 | Do()
24 | if err != nil {
25 | fmt.Printf("%s\n", err)
26 | return
27 | }
28 |
29 | fmt.Printf("rsp header:\n%#v \nTime:%s\n", rsp, rsp.Time)
30 | }
31 |
32 | func main() {
33 | go server()
34 |
35 | time.Sleep(time.Millisecond)
36 | bindHeader()
37 | }
38 |
39 | func server() {
40 | router := gin.New()
41 | router.GET("/test.header", func(c *gin.Context) {
42 | c.Writer.Header().Add("sid", "1234")
43 | c.Writer.Header().Add("total", "2048")
44 | c.Writer.Header().Add("time", time.Now().Format("2006-01-02"))
45 | })
46 |
47 | router.Run()
48 | }
49 |
--------------------------------------------------------------------------------
/_example/04a-set-body.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout"
7 | "strings"
8 | "time"
9 | )
10 |
11 | // 写入非结构化数据至http.body里,使用SetBody接口
12 | // 当然结构化数据主要有json/xml/yaml
13 | func stringExample() {
14 | // 1.发送string
15 | fmt.Printf("\n\n=====1.=====send string========\n")
16 | err := gout.POST(":8080/req/body").
17 | Debug(true).
18 | SetBody("send string"). // string
19 | Do()
20 |
21 | if err != nil {
22 | fmt.Printf("%s\n", err)
23 | return
24 | }
25 | }
26 |
27 | func bytesExample() {
28 | // 2.发送[]byte
29 | fmt.Printf("=====2.=====send string========\n")
30 | err := gout.POST(":8080/req/body").
31 | Debug(true).
32 | SetBody([]byte("send bytes")). // []byte
33 | Do()
34 |
35 | if err != nil {
36 | fmt.Printf("%s\n", err)
37 | return
38 | }
39 | }
40 |
41 | func ReaderExample() {
42 | // 3.发送实现io.Reader接口的变量
43 | fmt.Printf("=====3.=====io.Reader========\n")
44 | err := gout.POST(":8080/req/body").
45 | Debug(true).
46 | SetBody(strings.NewReader("io.Reader")). // io.Reader
47 | Do()
48 |
49 | if err != nil {
50 | fmt.Printf("%s\n", err)
51 | return
52 | }
53 | }
54 |
55 | func baseTypeExample() {
56 | // 4.发送基础类型的变量
57 | fmt.Printf("=====4.=====base type========\n")
58 | err := gout.POST(":8080/req/body").
59 | Debug(true).
60 | SetBody(3.14). //float64
61 | Do()
62 |
63 | if err != nil {
64 | fmt.Printf("%s\n", err)
65 | return
66 | }
67 |
68 | //SetBody支持的更多基础类型有int, int8, int16, int32, int64
69 | //uint, uint8, uint16, uint32, uint64
70 | //float32, float64
71 | }
72 |
73 | func main() {
74 | go server()
75 |
76 | time.Sleep(time.Millisecond)
77 |
78 | stringExample()
79 | bytesExample()
80 | ReaderExample()
81 | baseTypeExample()
82 | }
83 |
84 | func server() {
85 | router := gin.New()
86 | router.POST("/req/body", func(c *gin.Context) {
87 | c.String(200, "ok")
88 | })
89 |
90 | router.Run()
91 | }
92 |
--------------------------------------------------------------------------------
/_example/04b-bind-body.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout"
7 | "time"
8 | )
9 |
10 | // 可以使用BindBody解析返回的非结构化http body(结构化指json/xml/yaml等)
11 | // 可以做到基础类型自动绑定, 下面是string/[]byte/int的example
12 | func bindString() {
13 | // 1.解析string
14 | fmt.Printf("\n\n=========1. bind string=====\n")
15 | s := ""
16 | err := gout.GET(":8080/rsp/body/string").
17 | Debug(true).
18 | BindBody(&s).
19 | Do()
20 |
21 | if err != nil {
22 | fmt.Printf("%s\n", err)
23 | return
24 | }
25 | fmt.Printf("need(string) got(%s)\n", s)
26 | }
27 |
28 | func bindBytes() {
29 | // 2.解析[]byte
30 | fmt.Printf("\n\n=========2. bind []byte=====\n")
31 | var b []byte
32 | err := gout.GET(":8080/rsp/body/bytes").
33 | Debug(true).
34 | BindBody(&b).
35 | Do()
36 |
37 | if err != nil {
38 | fmt.Printf("%s\n", err)
39 | return
40 | }
41 | fmt.Printf("need(bytes) got(%s)\n", b)
42 | }
43 |
44 | func bindInt() {
45 | // 3.解析int
46 | fmt.Printf("\n\n=========3. bind int=====\n")
47 | i := 0
48 | err := gout.GET(":8080/rsp/body/int").
49 | Debug(true).
50 | BindBody(&i).
51 | Do()
52 |
53 | if err != nil {
54 | fmt.Printf("%s\n", err)
55 | return
56 | }
57 | fmt.Printf("need(65535) got(%d)\n", i)
58 | //BindBody支持的更多基础类型有int, int8, int16, int32, int64
59 | //uint, uint8, uint16, uint32, uint64
60 | //float32, float64
61 | }
62 |
63 | func main() {
64 | go server()
65 |
66 | time.Sleep(time.Millisecond)
67 |
68 | bindString()
69 | bindBytes()
70 | bindInt()
71 | }
72 |
73 | func server() {
74 | router := gin.New()
75 | router.GET("/rsp/body/bytes", func(c *gin.Context) {
76 | c.String(200, "bytes")
77 | })
78 |
79 | router.GET("/rsp/body/string", func(c *gin.Context) {
80 | c.String(200, "string")
81 | })
82 |
83 | router.GET("/rsp/body/int", func(c *gin.Context) {
84 | c.String(200, "65535")
85 | })
86 |
87 | router.Run()
88 | }
89 |
--------------------------------------------------------------------------------
/_example/05a-timeout.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout"
7 | "time"
8 | )
9 |
10 | func setTimeoutExample() {
11 | // 给http请求 设置超时
12 |
13 | err := gout.GET(":8080/timeout").
14 | SetTimeout(2 * time.Second).
15 | Do()
16 |
17 | fmt.Printf("err = %s\n", err)
18 | }
19 |
20 | func main() {
21 | go server()
22 | time.Sleep(time.Millisecond)
23 | setTimeoutExample()
24 | }
25 |
26 | func server() {
27 | router := gin.New()
28 | router.GET("/timeout", func(c *gin.Context) {
29 | ctx := c.Request.Context()
30 | select {
31 | case <-ctx.Done():
32 | fmt.Printf("timeout done\n")
33 | }
34 | })
35 |
36 | router.Run()
37 | }
38 |
--------------------------------------------------------------------------------
/_example/05b-cancel.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/gin-gonic/gin"
7 | "github.com/guonaihong/gout"
8 | "time"
9 | )
10 |
11 | func cancelExample() {
12 | // 给http请求 设置超时
13 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*1)
14 |
15 | cancel() //取消
16 |
17 | err := gout.GET(":8080/cancel").
18 | WithContext(ctx).
19 | Do()
20 |
21 | fmt.Printf("err = %s\n", err)
22 | }
23 |
24 | func main() {
25 | go server()
26 | time.Sleep(time.Millisecond)
27 | cancelExample()
28 | }
29 |
30 | func server() {
31 | router := gin.New()
32 | router.GET("/cancel", func(c *gin.Context) {
33 | ctx := c.Request.Context()
34 | select {
35 | case <-ctx.Done():
36 | fmt.Printf("cancel done\n")
37 | }
38 | })
39 |
40 | router.Run()
41 | }
42 |
--------------------------------------------------------------------------------
/_example/06-x-www-form-urlencoded.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout"
7 | "time"
8 | )
9 |
10 | // 使用SetWWWForm 接口设置x-www-form-urlencoded格式数据
11 | // 下面的代码有 map/array/struct 使用example
12 | type testWWWForm struct {
13 | Int int `form:"int" www-form:"int"`
14 | Float64 float64 `form:"float64" www-form:"float64"`
15 | String string `form:"string" www-form:"string"`
16 | }
17 |
18 | func mapExample() {
19 | fmt.Printf("====1.===============www-form=====use gout.H==\n\n")
20 | // 1.第一种方式,使用gout.H
21 | err := gout.POST(":8080/post").
22 | Debug(true).
23 | SetWWWForm(gout.H{
24 | "int": 3,
25 | "float64": 3.14,
26 | "string": "test-www-Form",
27 | }).
28 | Do()
29 | if err != nil {
30 | fmt.Printf("%s\n", err)
31 | return
32 | }
33 | }
34 |
35 | func arrayExample() {
36 | fmt.Printf("====2.===============www-form=====use gout.A==\n\n")
37 | // 2.第一种方式,使用gout.A
38 | err := gout.POST(":8080/post").
39 | Debug(true).
40 | SetWWWForm(gout.A{
41 | "int", 3,
42 | "float64", 3.14,
43 | "string", "test-www-Form",
44 | }).
45 | Do()
46 | if err != nil {
47 | fmt.Printf("%s\n", err)
48 | return
49 | }
50 | }
51 |
52 | func structExample() {
53 | fmt.Printf("====3.=================www-form=====use struct==\n\n")
54 | // 3.第一种方式,使用结构体
55 | need := testWWWForm{
56 | Int: 3,
57 | Float64: 3.14,
58 | String: "test-www-Form",
59 | }
60 |
61 | err := gout.POST(":8080/post").
62 | Debug(true).
63 | SetWWWForm(need).Do()
64 |
65 | if err != nil {
66 | fmt.Printf("%s\n", err)
67 | return
68 | }
69 |
70 | }
71 |
72 | func main() {
73 |
74 | go server()
75 |
76 | time.Sleep(time.Millisecond * 500)
77 |
78 | mapExample()
79 | arrayExample()
80 | structExample()
81 | }
82 |
83 | func server() {
84 | router := gin.New()
85 |
86 | router.POST("/post", func(c *gin.Context) {
87 |
88 | t := testWWWForm{}
89 | err := c.ShouldBind(&t)
90 | if err != nil {
91 | c.String(200, "demo fail")
92 | return
93 | }
94 |
95 | fmt.Printf("\n\nread client data#------->%#v\n\n", t)
96 | c.String(200, "I am the server response: www-form demo ok")
97 | })
98 | router.Run(":8080")
99 | }
100 |
--------------------------------------------------------------------------------
/_example/07-callback.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout"
7 | "math/rand"
8 | "time"
9 | )
10 |
11 | type Result struct {
12 | Errmsg string `json:"errmsg"`
13 | ErrCode int `json:"errcode"`
14 | }
15 |
16 | // Callback接口用于处理服务段会返回多种数据结构,比如404返回出错html, 200返回json
17 | // 客户端example
18 | func callbackExample() {
19 | r, str404 := Result{}, ""
20 | code := 0
21 |
22 | err := gout.GET(":8080").Callback(func(c *gout.Context) (err error) {
23 |
24 | switch c.Code {
25 | case 200: //http code为200时,服务端返回的是json 结构
26 | c.BindJSON(&r)
27 | case 404: //http code为404时,服务端返回是html 字符串
28 | c.BindBody(&str404)
29 | }
30 | code = c.Code
31 | return nil
32 |
33 | }).Do()
34 |
35 | if err != nil {
36 | fmt.Printf("err = %s\n", err)
37 | return
38 | }
39 |
40 | fmt.Printf("http code = %d, str404(%s) or json result(%v)\n", code, str404, r)
41 | }
42 |
43 | func main() {
44 | go server() //等会起测试服务
45 | time.Sleep(time.Millisecond * 500) //用时间做个等待同步
46 |
47 | callbackExample()
48 | }
49 |
50 | // 模拟 API网关
51 | func server() {
52 | router := gin.New()
53 |
54 | router.GET("/", func(c *gin.Context) {
55 |
56 | rand.Seed(time.Now().UnixNano())
57 | x := rand.Intn(2) //使用随机函数模拟某个服务有一定概率出现404
58 | switch x {
59 | case 0: // 模拟 404 找不到资源
60 | c.String(404, " not found ")
61 | case 1:
62 | // 正确业务返回结果
63 | c.JSON(200, Result{Errmsg: "ok"})
64 | }
65 | })
66 |
67 | router.Run()
68 | }
69 |
--------------------------------------------------------------------------------
/_example/08-cookie.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout"
7 | "net/http"
8 | "time"
9 | )
10 |
11 | // 使用SetCookies接口设置两个cookie
12 | func twoCookieExample() {
13 | // 发送两个cookie
14 | fmt.Printf("\n\n===1. Send two cookies=========\n")
15 | err := gout.GET(":8080/cookie").
16 | Debug(true).
17 | SetCookies(
18 | &http.Cookie{
19 | Name: "test1",
20 | Value: "test1"},
21 | &http.Cookie{
22 | Name: "test2",
23 | Value: "test2"}).
24 | Do()
25 | if err != nil {
26 | fmt.Println(err)
27 | return
28 | }
29 | }
30 |
31 | // 使用SetCookies接口设置一个cookie
32 | func oneCookieExample() {
33 |
34 | // 发送一个cookie
35 | fmt.Printf("\n\n===1. Send a cookies=========\n")
36 | err := gout.GET(":8080/cookie/one").
37 | Debug(true).
38 | SetCookies(&http.Cookie{Name: "test3", Value: "test3"}).
39 | Do()
40 | fmt.Println(err)
41 | }
42 |
43 | func main() {
44 | go server() // 起测试服务
45 | time.Sleep(time.Millisecond * 500) //sleep下等服务端真正起好
46 | twoCookieExample()
47 | oneCookieExample()
48 | }
49 |
50 | func server() {
51 | router := gin.Default()
52 |
53 | router.GET("/cookie", func(c *gin.Context) {
54 |
55 | cookie1, err := c.Request.Cookie("test1")
56 | if err != nil {
57 | fmt.Printf("%s\n", err)
58 | return
59 | }
60 |
61 | cookie2, err := c.Request.Cookie("test2")
62 | if err != nil {
63 | fmt.Printf("%s\n", err)
64 | return
65 | }
66 |
67 | fmt.Printf("cookie1 = %v, cookie2 = %v\n", cookie1, cookie2)
68 | })
69 |
70 | router.GET("/cookie/one", func(c *gin.Context) {
71 |
72 | cookie3, err := c.Request.Cookie("test3")
73 | if err != nil {
74 | fmt.Printf("%s\n", err)
75 | return
76 | }
77 |
78 | fmt.Printf("cookie3 = %v\n", cookie3)
79 | })
80 |
81 | router.Run()
82 | }
83 |
--------------------------------------------------------------------------------
/_example/09-unix.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "github.com/gin-gonic/gin"
7 | "github.com/guonaihong/gout"
8 | "net"
9 | "net/http"
10 | "os"
11 | )
12 |
13 | func main() {
14 | path := "./unix.sock"
15 | defer os.Remove(path)
16 |
17 | ctx, cancel := context.WithCancel(context.Background())
18 | srv := server(path)
19 | defer func() {
20 | srv.Shutdown(ctx)
21 | cancel()
22 | }()
23 |
24 | c := http.Client{}
25 | s := ""
26 | err := gout.New(&c).
27 | Debug(true).
28 | UnixSocket(path).
29 | POST("http://xxx/test/unix/").
30 | SetHeader(gout.H{"h1": "v1", "h2": "v2"}).
31 | BindBody(&s).Do()
32 |
33 | if err != nil {
34 | fmt.Printf("%s\n", err)
35 | return
36 | }
37 | fmt.Printf("result = %s\n", s)
38 | }
39 |
40 | func server(path string) *http.Server {
41 | router := gin.Default()
42 | type testHeader struct {
43 | H1 string `header:"h1"`
44 | H2 string `header:"h2"`
45 | }
46 |
47 | router.POST("/test/unix", func(c *gin.Context) {
48 |
49 | tHeader := testHeader{}
50 | err := c.ShouldBindHeader(&tHeader)
51 | if err != nil {
52 | c.String(200, "fail")
53 | return
54 | }
55 |
56 | c.String(200, "ok")
57 | })
58 |
59 | listener, err := net.Listen("unix", path)
60 | if err != nil {
61 | return nil
62 | }
63 |
64 | srv := http.Server{Handler: router}
65 | go func() {
66 | srv.Serve(listener)
67 | }()
68 |
69 | return &srv
70 | }
71 |
--------------------------------------------------------------------------------
/_example/10a-debug.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout"
7 | "io/ioutil"
8 | "time"
9 | )
10 |
11 | func debugExample() {
12 | err := gout.POST(":8080/").
13 | Debug(true).
14 | SetJSON(gout.H{"str": "foo",
15 | "num": 100,
16 | "bool": false,
17 | "null": nil,
18 | "array": gout.A{"foo", "bar", "baz"},
19 | "obj": gout.H{"a": 1, "b": 2},
20 | }).Do()
21 |
22 | if err != nil {
23 | fmt.Printf("err = %v\n", err)
24 | }
25 |
26 | }
27 |
28 | func noColorExample() {
29 |
30 | err := gout.POST(":8080/").
31 | Debug(gout.NoColor()).
32 | SetJSON(gout.H{"str": "foo",
33 | "num": 100,
34 | "bool": false,
35 | "null": nil,
36 | "array": gout.A{"foo", "bar", "baz"},
37 | "obj": gout.H{"a": 1, "b": 2},
38 | }).Do()
39 |
40 | if err != nil {
41 | fmt.Printf("err = %v\n", err)
42 | }
43 | }
44 |
45 | func main() {
46 | go server() // 起测试服务
47 | time.Sleep(time.Millisecond * 500) //sleep下等服务端真正起好
48 |
49 | debugExample()
50 | noColorExample()
51 | }
52 |
53 | func server() {
54 | r := gin.New()
55 |
56 | r.POST("/", func(c *gin.Context) {
57 | all, err := ioutil.ReadAll(c.Request.Body)
58 | if err != nil {
59 | c.String(200, "fail")
60 | return
61 | }
62 |
63 | c.String(200, string(all))
64 | })
65 |
66 | r.Run()
67 | }
68 |
--------------------------------------------------------------------------------
/_example/10b-debug-custom.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout"
7 | "io/ioutil"
8 | "os"
9 | "time"
10 | )
11 |
12 | // 自定义debug example,下面使用环境变量输出日志输出
13 | // 日志输出功能,使用环境变量打开
14 | func IOSDebug() gout.DebugOpt {
15 | return gout.DebugFunc(func(o *gout.DebugOption) {
16 | if len(os.Getenv("IOS_DEBUG")) > 0 {
17 | o.Debug = true
18 | o.Color = true //打开颜色高亮
19 | }
20 | })
21 | }
22 |
23 | func customExample() {
24 | err := gout.POST(":8080/").
25 | Debug(IOSDebug()).
26 | SetJSON(gout.H{"str": "foo",
27 | "num": 100,
28 | "bool": false,
29 | "null": nil,
30 | "array": gout.A{"foo", "bar", "baz"},
31 | "obj": gout.H{"a": 1, "b": 2},
32 | }).Do()
33 |
34 | if err != nil {
35 | fmt.Printf("err = %v\n", err)
36 | }
37 | }
38 |
39 | // 运行 example(其中env IOS_DEBUG=on 用于设置环境变量)
40 | // env IOS_DEBUG=on go run 10b-debug-custom.go
41 | func main() {
42 | go server() // 起测试服务
43 | time.Sleep(time.Millisecond * 500) //sleep下等服务端真正起好
44 |
45 | customExample()
46 | }
47 |
48 | func server() {
49 | r := gin.New()
50 |
51 | r.POST("/", func(c *gin.Context) {
52 | all, err := ioutil.ReadAll(c.Request.Body)
53 | if err != nil {
54 | c.String(200, "fail")
55 | return
56 | }
57 |
58 | c.Writer.Header().Set("Content-Type", "application/json")
59 | c.String(200, string(all))
60 | })
61 |
62 | r.Run()
63 | }
64 |
--------------------------------------------------------------------------------
/_example/10c-debug-trace.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout"
7 | "time"
8 | )
9 |
10 | func openDebugTrace() {
11 | err := gout.POST(":8080/colorjson").
12 | Debug(gout.Trace()).
13 | SetJSON(gout.H{"str": "foo",
14 | "num": 100,
15 | "bool": false,
16 | "null": nil,
17 | "array": gout.A{"foo", "bar", "baz"},
18 | "obj": gout.H{"a": 1, "b": 2},
19 | }).Do()
20 |
21 | if err != nil {
22 | fmt.Printf("err = %v\n", err)
23 | }
24 | }
25 |
26 | func main() {
27 | go server()
28 |
29 | time.Sleep(time.Millisecond * 200)
30 |
31 | openDebugTrace()
32 | }
33 |
34 | func server() {
35 | router := gin.New()
36 | router.POST("/colorjson", func(c *gin.Context) {
37 | c.JSON(200, gin.H{"str2": "str2 val", "int2": 2})
38 | })
39 |
40 | router.Run()
41 | }
42 |
--------------------------------------------------------------------------------
/_example/11-xml.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout"
7 | "time"
8 | )
9 |
10 | type data struct {
11 | Id int `json:"id" xml:"id"`
12 | Data string `json:"data xml:"data""`
13 | }
14 |
15 | func useStruct() {
16 | var rsp data
17 | code := 200
18 |
19 | err := gout.POST(":8080/test.xml").
20 | Debug(true).
21 | SetXML(data{Id: 3, Data: "test data"}).
22 | BindXML(&rsp).
23 | Code(&code).
24 | Do()
25 |
26 | if err != nil || code != 200 {
27 | fmt.Printf("%v:%d\n", err, code)
28 | }
29 | }
30 |
31 | func useString() {
32 | var rsp data
33 | code := 200
34 |
35 | err := gout.POST(":8080/test.xml").
36 | Debug(true).
37 | SetXML(`3test data`).
38 | BindXML(&rsp).
39 | Code(&code).
40 | Do()
41 |
42 | if err != nil || code != 200 {
43 | fmt.Printf("%v:%d\n", err, code)
44 | }
45 | }
46 |
47 | func useBytes() {
48 | var rsp data
49 | code := 200
50 |
51 | err := gout.POST(":8080/test.xml").
52 | Debug(true).
53 | SetXML([]byte(`3test data`)).
54 | BindXML(&rsp).
55 | Code(&code).
56 | Do()
57 |
58 | if err != nil || code != 200 {
59 | fmt.Printf("%v:%d\n", err, code)
60 | }
61 | }
62 |
63 | func main() {
64 | go server()
65 | time.Sleep(time.Millisecond * 500) //sleep下等服务端真正起好
66 |
67 | useStruct()
68 | useString()
69 | useBytes()
70 | }
71 |
72 | func server() {
73 | router := gin.Default()
74 |
75 | router.POST("/test.xml", func(c *gin.Context) {
76 | var d3 data
77 | err := c.BindXML(&d3)
78 | if err != nil {
79 | fmt.Printf("%s\n", err)
80 | return
81 | }
82 | c.XML(200, d3)
83 | })
84 |
85 | router.Run()
86 | }
87 |
--------------------------------------------------------------------------------
/_example/12-yaml.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout"
7 | "time"
8 | )
9 |
10 | type data struct {
11 | Id int `json:"id" xml:"id" yaml:"id"`
12 | Data string `json:"data xml:"data" yaml:"data"`
13 | }
14 |
15 | func useStruct() {
16 | var rsp data
17 | code := 200
18 |
19 | err := gout.POST(":8080/test.yaml").
20 | Debug(true).
21 | SetYAML(data{Id: 3, Data: "test data"}).
22 | BindYAML(&rsp).
23 | Code(&code).
24 | Do()
25 |
26 | if err != nil || code != 200 {
27 | fmt.Printf("%v:%d\n", err, code)
28 | }
29 | }
30 |
31 | func useMap() {
32 | var rsp data
33 | code := 200
34 |
35 | err := gout.POST(":8080/test.yaml").
36 | Debug(true).
37 | SetYAML(gout.H{"id": 3, "data": "test data"}).
38 | BindYAML(&rsp).
39 | Code(&code).
40 | Do()
41 |
42 | if err != nil || code != 200 {
43 | fmt.Printf("%v:%d\n", err, code)
44 | }
45 | }
46 |
47 | func useString() {
48 | var rsp data
49 | code := 200
50 |
51 | err := gout.POST(":8080/test.yaml").
52 | Debug(true).
53 | SetYAML(`
54 | id: 3
55 | data: test data
56 | `).
57 | BindYAML(&rsp).
58 | Code(&code).
59 | Do()
60 |
61 | if err != nil || code != 200 {
62 | fmt.Printf("%v:%d\n", err, code)
63 | }
64 | }
65 |
66 | func useBytes() {
67 | var rsp data
68 | code := 200
69 |
70 | err := gout.POST(":8080/test.yaml").
71 | Debug(true).
72 | SetYAML([]byte(`
73 | id: 3
74 | data: test data
75 | `)).
76 | BindYAML(&rsp).
77 | Code(&code).
78 | Do()
79 |
80 | if err != nil || code != 200 {
81 | fmt.Printf("%v:%d\n", err, code)
82 | }
83 | }
84 |
85 | func main() {
86 | go server()
87 | time.Sleep(time.Millisecond * 500) //sleep下等服务端真正起好
88 |
89 | useStruct()
90 | useMap()
91 | useString()
92 | useBytes()
93 | }
94 |
95 | func server() {
96 | router := gin.Default()
97 |
98 | router.POST("/test.yaml", func(c *gin.Context) {
99 | var d3 data
100 | err := c.BindYAML(&d3)
101 | if err != nil {
102 | fmt.Printf("%s\n", err)
103 | return
104 | }
105 | c.YAML(200, d3)
106 | })
107 |
108 | router.Run()
109 | }
110 |
--------------------------------------------------------------------------------
/_example/13-form-data.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "time"
6 |
7 | "github.com/gin-gonic/gin"
8 | "github.com/guonaihong/gout"
9 | )
10 |
11 | type testForm struct {
12 | Mode string `form:"mode"`
13 | Text string `form:"text"`
14 | //Voice []byte `form:"voice" form-mem:"true"` //todo open
15 | }
16 |
17 | type testForm2 struct {
18 | Mode string `form:"mode"`
19 | Text string `form:"text"`
20 | Voice string `form:"voice" form-file:"file"` //从文件中读取
21 | Voice2 []byte `form:"voice2" form-file:"mem"` //从内存中构造
22 | }
23 |
24 | // 使用map装载数据
25 | func mapExample() {
26 |
27 | // 1.使用gout.H
28 | fmt.Printf("\n\n====1. use gout.H==============\n\n")
29 | code := 0
30 | err := gout.
31 | POST(":8080/test.form").
32 | Debug(true).
33 | SetForm(gout.H{"mode": "A",
34 | "text": "good",
35 | "voice": gout.FormFile("../testdata/voice.pcm"),
36 | "voice2": gout.FormMem("pcm")}).
37 | Code(&code).
38 | Do()
39 |
40 | if err != nil || code != 200 {
41 | fmt.Printf("%s:code = %d\n", err, code)
42 | return
43 | }
44 | }
45 |
46 | // 使用结构体装载数据
47 | func structExample() {
48 | code := 0
49 | // 2.使用结构体里面的数据
50 | fmt.Printf("\n\n====2. use struct==============\n\n")
51 | err := gout.
52 | POST(":8080/test.form").
53 | Debug(true).
54 | SetForm(testForm2{
55 | Mode: "A",
56 | Text: "good",
57 | Voice: "../testdata/voice.pcm",
58 | Voice2: []byte("pcm")}).
59 | Code(&code).Do()
60 | if err != nil || code != 200 {
61 |
62 | }
63 | }
64 |
65 | // 自定义filename
66 | func mapExample2() {
67 | code := 0
68 | // 2.使用结构体里面的数据
69 | fmt.Printf("\n\n====3. use struct==============\n\n")
70 | err := gout.
71 | POST(":8080/test.form").
72 | Debug(true).
73 | SetForm(gout.H{
74 | "Mode": "A",
75 | "Text": "good",
76 | "Voice": gout.FormType{FileName: "test-file-name", File: gout.FormFile("../testdata/voice.pcm")},
77 | }).
78 | Code(&code).Do()
79 | if err != nil || code != 200 {
80 |
81 | }
82 | }
83 |
84 | func main() {
85 | go server()
86 | time.Sleep(time.Millisecond * 500) //sleep下等服务端真正起好
87 |
88 | mapExample()
89 | structExample()
90 | mapExample2()
91 | }
92 |
93 | func server() {
94 | router := gin.New()
95 | router.POST("/test.form", func(c *gin.Context) {
96 |
97 | t2 := testForm{}
98 | err := c.Bind(&t2)
99 | if err != nil {
100 | fmt.Printf("err = %s\n", err)
101 | return
102 | }
103 | })
104 |
105 | router.Run()
106 | }
107 |
--------------------------------------------------------------------------------
/_example/14-upload-file.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/gin-gonic/gin"
7 | "github.com/guonaihong/gout"
8 | "io"
9 | "os"
10 | "time"
11 | )
12 |
13 | func uploadExample() {
14 | fmt.Printf("=====3.=====upload file========\n")
15 | fd, err := os.Open("./14-upload-file.go")
16 | if err != nil {
17 | fmt.Printf("%s\n", err)
18 | return
19 | }
20 | defer fd.Close()
21 |
22 | err = gout.POST(":8080/upload/file").
23 | SetBody(fd). // upload file
24 | Do()
25 |
26 | if err != nil {
27 | fmt.Printf("%s\n", err)
28 | return
29 | }
30 |
31 | fi, err := fd.Stat()
32 | if err != nil {
33 | return
34 | }
35 |
36 | fmt.Printf("client:file size:%d\n", fi.Size())
37 | }
38 |
39 | func main() {
40 | go server()
41 | time.Sleep(time.Millisecond * 500) //sleep下等服务端真正起好
42 |
43 | uploadExample()
44 | }
45 |
46 | func server() {
47 | router := gin.New()
48 | router.POST("/upload/file", func(c *gin.Context) {
49 | var file bytes.Buffer
50 | io.Copy(&file, c.Request.Body)
51 | fmt.Printf("server:file size = %d\n", file.Len())
52 | })
53 |
54 | router.Run()
55 | }
56 |
--------------------------------------------------------------------------------
/_example/15-debug-save-file.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/guonaihong/gout"
6 | "io"
7 | "os"
8 | )
9 |
10 | func SaveFile(w io.Writer) gout.DebugOpt {
11 | return gout.DebugFunc(func(o *gout.DebugOption) {
12 | o.Debug = true
13 | o.Write = w
14 | })
15 | }
16 |
17 | func main() {
18 |
19 | fd, err := os.Create("debug.log")
20 | if err != nil {
21 | return
22 | }
23 |
24 | defer fd.Close()
25 |
26 | s := ""
27 | err = gout.
28 | // 发起GET请求
29 | GET("www.baidu.com").
30 | //打开debug模式
31 | Debug(SaveFile(fd)).
32 | //解析响应body至s变量中
33 | BindBody(&s).
34 | //结束函数
35 | Do()
36 | if err != nil {
37 | fmt.Printf("%s\n", err)
38 | return
39 | }
40 |
41 | fmt.Printf("html size:%d", len(s))
42 | }
43 |
--------------------------------------------------------------------------------
/_example/16a-benchmark-number.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout"
7 | "time"
8 | )
9 |
10 | const (
11 | benchNumber = 30000
12 | benchConcurrent = 20
13 | )
14 |
15 | func server() {
16 | router := gin.New()
17 | router.POST("/", func(c *gin.Context) {
18 | c.String(200, "12345")
19 | })
20 |
21 | router.Run()
22 | }
23 |
24 | func main() {
25 | go server()
26 | time.Sleep(time.Millisecond)
27 |
28 | err := gout.
29 | POST(":8080").
30 | SetJSON(gout.H{"hello": "world"}). //设置请求body内容
31 | Filter(). //打开过滤器
32 | Bench(). //选择bench功能
33 | Concurrent(benchConcurrent). //并发数
34 | Number(benchNumber). //压测次数
35 | Do()
36 |
37 | if err != nil {
38 | fmt.Printf("%v\n", err)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/_example/16b-benchmark-duration.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout"
7 | "time"
8 | )
9 |
10 | const (
11 | benchTime = 4 * time.Second
12 | benchConcurrent = 30
13 | )
14 |
15 | func server() {
16 | router := gin.New()
17 | router.POST("/", func(c *gin.Context) {
18 | c.String(200, "hello world:gout")
19 | })
20 |
21 | router.Run()
22 | }
23 |
24 | func main() {
25 | go server()
26 | time.Sleep(300 * time.Millisecond)
27 |
28 | err := gout.
29 | POST(":8080").
30 | SetJSON(gout.H{"hello": "world"}). //设置请求body内容
31 | Filter(). //打开过滤器
32 | Bench(). //选择bench功能
33 | Concurrent(benchConcurrent). //并发数
34 | Durations(benchTime). //压测时间
35 | Do()
36 |
37 | if err != nil {
38 | fmt.Printf("%v\n", err)
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/_example/16c-benchmark-rate.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout"
7 | "time"
8 | )
9 |
10 | const (
11 | benchNumber = 3000
12 | benchConcurrent = 20
13 | )
14 |
15 | func server() {
16 | router := gin.New()
17 | router.POST("/", func(c *gin.Context) {
18 | c.String(200, "12345")
19 | })
20 |
21 | router.Run()
22 | }
23 |
24 | func main() {
25 | go server()
26 | time.Sleep(time.Millisecond)
27 |
28 | err := gout.
29 | POST(":8080").
30 | SetJSON(gout.H{"hello": "world"}). //设置请求body内容
31 | Filter(). //打开过滤器
32 | Bench(). //选择bench功能
33 | Rate(1000). //每秒发1000请求
34 | Concurrent(benchConcurrent). //并发数
35 | Number(benchNumber). //压测次数
36 | Do()
37 |
38 | if err != nil {
39 | fmt.Printf("%v\n", err)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/_example/16d-benchmark-vs-ab.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout"
7 | "log"
8 | "os"
9 | "os/exec"
10 | "time"
11 | )
12 |
13 | const (
14 | benchCount = 100000
15 | benchConcurrent = 30
16 | )
17 |
18 | func server() {
19 | router := gin.New()
20 | router.POST("/", func(c *gin.Context) {
21 | c.String(200, "hello world:gout")
22 | })
23 |
24 | router.Run()
25 | }
26 |
27 | func runGout() {
28 | fd, err := os.Open("../testdata/voice.pcm")
29 | if err != nil {
30 | fmt.Printf("%s\n", err)
31 | return
32 | }
33 | defer fd.Close()
34 |
35 | err = gout.
36 | POST(":8080").
37 | SetBody(fd). //设置请求body内容
38 | Filter(). //打开过滤器
39 | Bench(). //选择bench功能
40 | Concurrent(benchConcurrent). //并发数
41 | Number(benchCount). //压测次数
42 | Do()
43 |
44 | if err != nil {
45 | fmt.Printf("%v\n", err)
46 | }
47 | }
48 |
49 | // sudo apt install apache2-utils
50 | var abCmd = fmt.Sprintf(`ab -c %d -n %d -p ../testdata/voice.pcm http://127.0.0.1:8080/`, benchConcurrent, benchCount)
51 |
52 | func runAb() {
53 | out, err := exec.Command("bash", "-c", abCmd).Output()
54 | if err != nil {
55 | log.Fatal("%s\n", err)
56 | }
57 | fmt.Printf("%s\n", out)
58 |
59 | }
60 |
61 | func main() {
62 | go server()
63 | time.Sleep(300 * time.Millisecond)
64 |
65 | // 设为false,可看ab性能
66 | startGout := true
67 | if startGout {
68 | runGout()
69 | } else {
70 | runAb()
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/_example/16e-customize-bench.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/google/uuid"
7 | "github.com/guonaihong/gout"
8 | "github.com/guonaihong/gout/filter"
9 | "sync/atomic"
10 | "time"
11 | )
12 |
13 | const (
14 | benchNumber = 30000
15 | benchConcurrent = 30
16 | )
17 |
18 | func server() {
19 | router := gin.New()
20 | router.POST("/", func(c *gin.Context) {
21 | c.String(200, "hello world:gout")
22 | })
23 |
24 | router.Run()
25 | }
26 |
27 | func customize() {
28 | i := int32(0)
29 |
30 | err := filter.NewBench().
31 | Concurrent(benchConcurrent).
32 | Number(benchNumber).
33 | Loop(func(c *gout.Context) error {
34 |
35 | // 下面的代码,每次生成不一样的http body 用于压测
36 | uid := uuid.New()
37 | id := atomic.AddInt32(&i, 1)
38 |
39 | c.POST(":8080").SetJSON(gout.H{"sid": uid.String(),
40 | "appkey": fmt.Sprintf("ak:%d", id),
41 | "text": fmt.Sprintf("test text :%d", id)})
42 | return nil
43 |
44 | }).Do()
45 |
46 | if err != nil {
47 | fmt.Printf("err = %v\n", err)
48 | }
49 | }
50 |
51 | func main() {
52 | go server()
53 | time.Sleep(300 * time.Millisecond)
54 |
55 | customize()
56 | }
57 |
--------------------------------------------------------------------------------
/_example/17a-import-rawhttp.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout"
7 | "time"
8 | )
9 |
10 | func rawhttp() {
11 | s := `POST /colorjson HTTP/1.1
12 | Host: 127.0.0.1:8080
13 | User-Agent: Go-http-client/1.1
14 | Content-Length: 97
15 | Content-Type: application/json
16 | Accept-Encoding: gzip
17 |
18 | {"array":["foo","bar","baz"],"bool":false,"null":null,"num":100,"obj":{"a":1,"b":2},"str":"foo"}
19 | `
20 | err := gout.NewImport().RawText(s).Debug(true).SetHost(":1234").Do()
21 | if err != nil {
22 | fmt.Printf("err = %s\n", err)
23 | return
24 | }
25 | }
26 |
27 | func main() {
28 | go server()
29 | time.Sleep(time.Millisecond * 200)
30 | rawhttp()
31 | }
32 |
33 | func server() {
34 | router := gin.New()
35 | router.POST("/colorjson", func(c *gin.Context) {
36 | c.JSON(200, gin.H{"str2": "str2 val", "int2": 2})
37 | })
38 |
39 | router.Run(":1234")
40 | }
41 |
--------------------------------------------------------------------------------
/_example/18a-gen-curl.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/guonaihong/gout"
6 | )
7 |
8 | func genFormCurl() {
9 | // 1.formdata
10 | err := gout.GET(":1234").
11 | SetForm(gout.A{"text", "good", "mode", "A", "voice", gout.FormFile("./t8.go")}).
12 | Export().Curl().Do()
13 | // output:
14 | // curl -X GET -F "text=good" -F "mode=A" -F "voice=@./voice" "http://127.0.0.1:1234"
15 | }
16 | func genJSONCurl() {
17 | // 2.json body
18 | err = gout.GET(":1234").
19 | SetJSON(gout.H{"key1": "val1", "key2": "val2"}).
20 | Export().Curl().Do()
21 | // output:
22 | // curl -X GET -H "Content-Type:application/json" -d "{\"key1\":\"val1\",\"key2\":\"val2\"}" "http://127.0.0.1:1234"
23 |
24 | fmt.Printf("%v\n", err)
25 | }
26 |
27 | func main() {
28 | genFormCurl()
29 | genJSONCurl()
30 | }
31 |
--------------------------------------------------------------------------------
/_example/19a-retry.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/guonaihong/gout"
6 | "github.com/guonaihong/gout/core"
7 | "time"
8 | )
9 |
10 | func main() {
11 | // 获取一个没有绑定服务的端口
12 | port := core.GetNoPortExists()
13 | err := gout.HEAD("127.0.0.1:" + port).
14 | Debug(true). //打开debug模式
15 | Filter(). //打开过滤器.Filter()的简写是.F()
16 | Retry(). //打开重试模式
17 | Attempt(5). //最多重试5次
18 | WaitTime(500 * time.Millisecond). //基本等待时间
19 | MaxWaitTime(3 * time.Second). //最长等待时间
20 | Do()
21 |
22 | if err != nil {
23 | fmt.Printf("err = %v\n", err)
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/_example/19b-retry-customize-backup.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout"
7 | "github.com/guonaihong/gout/core"
8 | "github.com/guonaihong/gout/filter"
9 | "time"
10 | )
11 |
12 | func useRetryFunc() {
13 | // 获取一个没有服务绑定的端口
14 | port := core.GetNoPortExists()
15 | s := ""
16 |
17 | err := gout.GET(":" + port).Debug(true).BindBody(&s).F().
18 | Retry().Attempt(3).WaitTime(time.Millisecond * 10).MaxWaitTime(time.Millisecond * 50).
19 | Func(func(c *gout.Context) error {
20 | if c.Error != nil {
21 | c.SetHost(":1234") //必须是存在的端口
22 | return filter.ErrRetry
23 | }
24 | return nil
25 |
26 | }).Do()
27 | fmt.Printf("err = %v\n", err)
28 | }
29 |
30 | func main() {
31 | go server()
32 | time.Sleep(time.Millisecond * 200)
33 | useRetryFunc()
34 | }
35 |
36 | func server() {
37 | router := gin.New()
38 | router.GET("/", func(c *gin.Context) {
39 | c.JSON(200, gin.H{"result": "test ok"})
40 | })
41 |
42 | router.Run(":1234")
43 | }
44 |
--------------------------------------------------------------------------------
/_example/19c-retry-httpcode.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout"
7 | "github.com/guonaihong/gout/filter"
8 | "time"
9 | )
10 |
11 | var first bool
12 |
13 | func useRetryFuncCode() {
14 | s := ""
15 | err := gout.GET(":8080/code").Debug(true).BindBody(&s).F().
16 | Retry().Attempt(3).WaitTime(time.Millisecond * 10).MaxWaitTime(time.Millisecond * 50).
17 | Func(func(c *gout.Context) error {
18 | if c.Error != nil || c.Code == 209 {
19 | return filter.ErrRetry
20 | }
21 |
22 | return nil
23 |
24 | }).Do()
25 |
26 | fmt.Printf("err = %v\n", err)
27 | }
28 |
29 | func main() {
30 | first = true
31 | go server()
32 | time.Sleep(time.Millisecond * 200)
33 | useRetryFuncCode()
34 | }
35 |
36 | // mock 服务端函数
37 | func server() {
38 | router := gin.New()
39 |
40 | router.GET("/code", func(c *gin.Context) {
41 | if first {
42 | c.JSON(209, gout.H{"resutl": "1.return 209"})
43 | first = false
44 | } else {
45 | c.JSON(200, gout.H{"resut": "2.return 200"})
46 | }
47 | })
48 |
49 | router.Run()
50 | }
51 |
--------------------------------------------------------------------------------
/_example/20-socks5.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/guonaihong/gout"
6 | "log"
7 | "net/http"
8 | )
9 |
10 | func main() {
11 | c := &http.Client{}
12 | s := ""
13 | err := gout.
14 | New(c).
15 | GET("www.google.com").
16 | // 设置proxy服务地址
17 | SetSOCKS5("47.104.175.115:80").
18 | // 绑定返回数据到s里面
19 | BindBody(&s).
20 | Do()
21 |
22 | if err != nil {
23 | log.Println(err)
24 | return
25 | }
26 |
27 | fmt.Println(s)
28 | }
29 |
--------------------------------------------------------------------------------
/_example/README.md:
--------------------------------------------------------------------------------
1 | ## how to use gout
2 | ## 设置http body
3 | ### [写入JSON到http body里面](./01-color-json.go)
4 | ```SetJSON```接口,写入json至http.body里
5 |
6 | ### [设置和解析xml](./11-xml.go)
7 | 使用```SetXML```写入xml格式数据,使用```BindXML```读取
8 | ### [设置和解析yaml](./12-yaml.go)
9 | 使用```SetYAML```写入yaml格式数据,使用```BindYAML```读取
10 | ### [设置formdata](./13-form-data.go)
11 | 使用```SetForm```接口接入form-data格式数据,该接口支持多种数据类型。
12 | ### [upload file](./14-upload-file.go)
13 | 这里使用的是```SetBody```进行上传文件
14 |
15 | ### [设置非json/xml/yaml数据到body](./04a-set-body.go)
16 | 需要使用```SetBody```接口
17 |
18 | ### [解析非json/xml/yaml数据body到变量里面](./04b-bind-body.go)
19 | 需要使用```BindBody```接口
20 |
21 | ## 设置控制字段
22 | ### [设置Query Parameters](./02-query.go)
23 | 需要使用```SetQuery```接口,该接口支持多种数据类型
24 |
25 | ### [设置http header](./03a-set-header.go)
26 | 需要使用```SetHeader```接口,丰富的数据类型让你停不下来
27 |
28 | ### [解析http header](./03b-bind-header.go)
29 | 需要```BindHeader```接口,支持多类型自动绑定
30 |
31 |
32 | ### [设置timeout](./05a-timeout.go)
33 | 需使用```SetTimeout```接口
34 |
35 | ### [取消一个正在发送的请求](./05b-cancel.go)
36 | 使用```WithContext```接口可取消正在发送的请求
37 |
38 | ### [发送x-www-form-urlencoded格式数据](./06-x-www-form-urlencoded.go)
39 | 使用```SetWWWForm```接口
40 |
41 | ### [处理多种body,一个接口既可以处理json,也可以处理html 404](./07-callback.go)
42 | 使用```Callback```接口
43 |
44 | ### [设置cookie](./08-cookie.go)
45 | 使用```SetCookies```接口,可传一个或者多个cookie
46 |
47 | ### [修改传输成为unixsocket](./09-unix.go)
48 |
49 | ### debug 模式
50 | #### [打开debug模式or 打开debug关闭颜色高亮](./10a-debug.go)
51 | 都是使用```Debug()```接口,只是里面传递的策略函数不一样
52 |
53 | #### [自定义debug模式](./10b-debug-custom.go)
54 | debug接口具有强大的扩展性能,简单啪啪两下写个策略函数,就可以扩展该接口,比如设置某个环境变量才打开debug接口
55 | #### [trace 功能,主要诊断接口各个阶段的性能](./10c-debug-trace.go)
56 | ```Debug()```里面传递 ```gout.Trace()```策略函数就可以打开这个功能
57 |
58 | #### [保存debug信息](./15-debug-save-file.go)
59 | 自定义```Debug()```接口的策略函数
60 |
61 | ### 压测功能
62 | #### [压测一定次数](./16a-benchmark-number.go)
63 | ```Number()```控制次数
64 | #### [压测固定时间](./16b-benchmark-duration.go)
65 | ```Durations()```控制时间
66 | #### [已某个固定频率压测](./16c-benchmark-rate.go)
67 | ```Rate()```控制压测频率
68 | #### [压测功能和apache ab 的对比,远比ab性能要好](./16d-benchmark-vs-ab.go)
69 | 与apache ab的性能pk
70 | #### [基于回调函数的自定义压测模式](./16e-customize-bench.go)
71 | ```Loop()```接口可传递回调函数
72 | ### import
73 | #### [导入纯文本请求并发送](./17a-import-rawhttp.go)
74 | ```RawText()```接口可完成该功能
75 | ### export
76 | #### [生成curl命令](./18a-gen-curl.go)
77 | ```Curl().Do()```可实现
78 |
79 | ### 指数回退重试
80 | #### [重试](./19a-retry.go)
81 | ```Retry()```下面的接口
82 | #### [使用冷备地址进行重试](./19b-retry-customize-backup.go)
83 | ```Func()```可传回调函数进行自定义设置
84 | #### [基于某个http code进行重试,比如ES 返回209告知资源不可用](19c-retry-httpcode.go)
85 | ```Func()``` 的入参有code信息,使用```filter.ErrRetry```告知gout需要重试
86 | #### [socks5](./20-socks5.go)
87 |
--------------------------------------------------------------------------------
/_example/go.mod:
--------------------------------------------------------------------------------
1 | module main
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/gin-gonic/gin v1.9.0
7 | github.com/google/uuid v1.1.1
8 | github.com/guonaihong/gout v0.2.4
9 | )
10 |
--------------------------------------------------------------------------------
/bench/report_test.go:
--------------------------------------------------------------------------------
1 | package bench
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "github.com/gin-gonic/gin"
7 | "github.com/stretchr/testify/assert"
8 | "net/http"
9 | "net/http/httptest"
10 | "sync/atomic"
11 | "testing"
12 | "time"
13 | )
14 |
15 | func setup_report_server(total *int32) *gin.Engine {
16 | router := gin.New()
17 | router.GET("/", func(c *gin.Context) {
18 | atomic.AddInt32(total, 1)
19 | })
20 |
21 | return router
22 | }
23 |
24 | func runReport(p *Report, number int) {
25 | p.Init()
26 | work := make(chan struct{})
27 | go func() {
28 | for i := 0; i < number; i++ {
29 | work <- struct{}{}
30 | }
31 | close(work)
32 | }()
33 | quit := make(chan struct{})
34 | go func() {
35 | p.Process(work)
36 | close(quit)
37 | }()
38 |
39 | <-quit
40 | p.Cancel()
41 | p.WaitAll()
42 |
43 | }
44 |
45 | func newRequest(url string) (*http.Request, error) {
46 | b := bytes.NewBufferString("hello")
47 | return http.NewRequest("GET", url, b)
48 | }
49 |
50 | // 测试正常情况, 次数
51 | func Test_Bench_Report_number(t *testing.T) {
52 | const number = 1000
53 |
54 | total := int32(0)
55 | router := setup_report_server(&total)
56 | ts := httptest.NewServer(http.HandlerFunc(router.ServeHTTP))
57 |
58 | ctx := context.Background()
59 |
60 | getRequest := func() (*http.Request, error) {
61 | return newRequest(ts.URL)
62 | }
63 |
64 | p := NewReport(ctx, 1, number, time.Duration(0), getRequest, http.DefaultClient)
65 |
66 | runReport(p, number)
67 |
68 | assert.Equal(t, int32(p.CompleteRequest), int32(number))
69 | }
70 |
71 | // 测试正常情况, 时间
72 | func Test_Bench_Report_duration(t *testing.T) {
73 | const number = 1000
74 |
75 | total := int32(0)
76 | router := setup_report_server(&total)
77 | ts := httptest.NewServer(http.HandlerFunc(router.ServeHTTP))
78 |
79 | ctx := context.Background()
80 |
81 | getRequest := func() (*http.Request, error) {
82 | return newRequest(ts.URL)
83 | }
84 |
85 | p := NewReport(ctx, 1, 0, 300*time.Millisecond, getRequest, http.DefaultClient)
86 |
87 | runReport(p, number)
88 |
89 | assert.Equal(t, int32(p.CompleteRequest), int32(number))
90 | }
91 |
92 | // 测试异常情况
93 | func Test_Bench_Report_fail(t *testing.T) {
94 | const number = 1000
95 |
96 | ctx := context.Background()
97 |
98 | getRequest := func() (*http.Request, error) {
99 | return newRequest("fail url")
100 | }
101 |
102 | p := NewReport(ctx, 1, number, time.Duration(0), getRequest, http.DefaultClient)
103 |
104 | runReport(p, number)
105 |
106 | assert.Equal(t, int32(p.Failed), int32(number))
107 | }
108 |
--------------------------------------------------------------------------------
/bench/task.go:
--------------------------------------------------------------------------------
1 | package bench
2 |
3 | import (
4 | "os"
5 | "os/signal"
6 | "sync"
7 | "time"
8 | )
9 |
10 | // SubTasker 是task模块的核心接口
11 | type SubTasker interface {
12 | Init()
13 | Process(chan struct{})
14 | Cancel()
15 | WaitAll()
16 | }
17 |
18 | // Task Task模块的核心数据结构
19 | type Task struct {
20 | Duration time.Duration //压测时间
21 | Number int //压测次数
22 | Concurrent int //并发数
23 | Rate int //压测频率
24 |
25 | work chan struct{}
26 |
27 | ok bool
28 |
29 | wg sync.WaitGroup
30 | }
31 |
32 | func (t *Task) init() {
33 | t.work = make(chan struct{})
34 | if t.Concurrent == 0 {
35 | t.Concurrent = 1
36 | }
37 | t.ok = true
38 | }
39 |
40 | func (t *Task) producer() {
41 | if !t.ok {
42 | panic("task must be init")
43 | }
44 |
45 | work := t.work
46 | // 控制压测时间
47 | if t.Duration > 0 {
48 | tk := time.NewTicker(t.Duration)
49 | go func() {
50 | defer close(work)
51 | for {
52 | select {
53 | case <-tk.C:
54 | return
55 | case work <- struct{}{}:
56 | }
57 | }
58 | }()
59 |
60 | return
61 | }
62 |
63 | go func() {
64 | defer close(work)
65 |
66 | switch {
67 | case t.Number == 0:
68 | return
69 | case t.Number > 0:
70 | for i, n := 0, t.Number; i < n; i++ {
71 | work <- struct{}{}
72 | }
73 | default: // t.Number < 0
74 | for {
75 | work <- struct{}{}
76 | }
77 | }
78 |
79 | }()
80 |
81 | }
82 |
83 | func (t *Task) run(sub SubTasker) {
84 | sig := make(chan os.Signal, 1)
85 | signal.Notify(sig, os.Interrupt)
86 |
87 | interval := 0
88 | work := t.work
89 | wg := &t.wg
90 |
91 | allDone := make(chan struct{})
92 | if t.Rate > 0 {
93 | interval = int(time.Second) / t.Rate
94 | }
95 |
96 | begin := time.Now()
97 | if interval > 0 {
98 | oldwork := work
99 | count := 0
100 | work = make(chan struct{}, 1)
101 |
102 | wg.Add(1)
103 | go func() {
104 | defer func() {
105 | close(work)
106 | wg.Done()
107 | }()
108 |
109 | for {
110 | next := begin.Add(time.Duration(count * interval))
111 | time.Sleep(time.Until(next))
112 | //time.Sleep(next.Sub(time.Now()))
113 |
114 | _, ok := <-oldwork
115 | if !ok {
116 | return
117 | }
118 | //default:
119 |
120 | //select里面包含default:会产生一个bug,试问t.Rate如果是很大的值, time.Sleep这句相当于没有
121 | //决定消费者可以消费多少条是消费者自己决定,消费有多块,就可以产生多少令牌给消费者使用
122 | //这和一开始的设计初衷相悖,消费者消费多少条需由t.Number或 t.Duration决定
123 | //注释可以让t.Number 或 t.Duration更准确
124 |
125 | work <- struct{}{}
126 | count++
127 | }
128 | }()
129 |
130 | }
131 |
132 | wg.Add(t.Concurrent)
133 | for i, c := 0, t.Concurrent; i < c; i++ {
134 | go func() {
135 | defer wg.Done()
136 | sub.Process(work)
137 | }()
138 | }
139 |
140 | go func() {
141 | wg.Wait()
142 | close(allDone)
143 | }()
144 |
145 | select {
146 | case <-sig:
147 | sub.Cancel()
148 | sub.WaitAll()
149 | case <-allDone:
150 | sub.Cancel()
151 | sub.WaitAll()
152 | }
153 | }
154 |
155 | // Run Task模块的入口函数
156 | func (t *Task) Run(sub SubTasker) {
157 | t.init()
158 |
159 | sub.Init()
160 |
161 | t.producer()
162 |
163 | t.run(sub)
164 | }
165 |
--------------------------------------------------------------------------------
/bench/task_test.go:
--------------------------------------------------------------------------------
1 | package bench
2 |
3 | import (
4 | "sync/atomic"
5 | "testing"
6 | "time"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | type testTask struct {
12 | number int32
13 | }
14 |
15 | func (t *testTask) Init() {
16 | }
17 |
18 | func (t *testTask) Process(work chan struct{}) {
19 | for range work {
20 | atomic.AddInt32(&t.number, 1)
21 | }
22 | }
23 |
24 | func (t *testTask) Cancel() {
25 | }
26 |
27 | func (t *testTask) WaitAll() {
28 | }
29 |
30 | // 测试发送次数
31 | func Test_Bench_Task_number(t *testing.T) {
32 | task := Task{
33 | Number: 1000,
34 | }
35 |
36 | testTask := &testTask{}
37 | task.Run(testTask)
38 |
39 | assert.Equal(t, int32(task.Number), int32(testTask.number))
40 | }
41 |
42 | // 测试发送duration
43 | func Test_Bench_Task_duration(t *testing.T) {
44 | task := Task{
45 | Duration: time.Millisecond * 300,
46 | }
47 |
48 | s := time.Now()
49 | testTask := &testTask{}
50 | task.Run(testTask)
51 |
52 | e := time.Since(s)
53 |
54 | assert.LessOrEqual(t, int64(e), int64(task.Duration+100*time.Millisecond))
55 | assert.GreaterOrEqual(t, int64(e), int64(200*time.Millisecond))
56 | }
57 |
58 | // 测试发送频率
59 | func Test_Bench_Task_rate(t *testing.T) {
60 | task := Task{
61 | Number: 300,
62 | Rate: 1000,
63 | }
64 |
65 | s := time.Now()
66 | testTask := &testTask{}
67 | task.Run(testTask)
68 |
69 | e := time.Since(s)
70 |
71 | assert.LessOrEqual(t, int64(e), int64(400*time.Millisecond))
72 | assert.GreaterOrEqual(t, int64(e), int64(200*time.Millisecond))
73 | }
74 |
75 | // TODO:测试卡住的情况
76 | func Test_Bench_Task_block(t *testing.T) {
77 | }
78 |
--------------------------------------------------------------------------------
/bench/tmpl.go:
--------------------------------------------------------------------------------
1 | package bench
2 |
3 | import (
4 | "text/template"
5 | )
6 |
7 | var tmpl = `
8 | {{if gt (len .ErrMsg) 0}}Error message: {{range $errmsg, $num := .ErrMsg}} {{$errmsg}}:{{$num}} {{end}} [errmsg:count]{{end}}
9 | Status Codes: {{range $code, $num := .StatusCodes}} {{$num}}:{{$code}} {{end}} [count:code]
10 | Concurrency Level: {{.Concurrency}}
11 | Time taken for tests: {{.Duration}}
12 | Complete requests: {{.CompleteRequest}}
13 | Failed requests: {{.Failed}}
14 | Total Read Data: {{.TotalRead}} bytes
15 | Total Read body {{.TotalBody}} bytes
16 | Total Write Body {{.TotalWriteBody}} bytes
17 | Requests per second: {{.Tps}} [#/sec] (mean)
18 | Time per request: {{.Mean}} [ms] (mean)
19 | Time per request: {{.AllMean}} [ms] (mean, across all concurrent requests)
20 | Transfer rate: {{.Kbs}} [Kbytes/sec] received
21 | Percentage of the requests served within a certain time (ms)
22 | 50% {{.Percentage55}}
23 | 66% {{.Percentage66}}
24 | 75% {{.Percentage75}}
25 | 80% {{.Percentage80}}
26 | 90% {{.Percentage90}}
27 | 95% {{.Percentage95}}
28 | 98% {{.Percentage98}}
29 | 99% {{.Percentage99}}
30 | 100% {{.Percentage100}}
31 | `
32 |
33 | // 后面要加新的显示格式,只要加新的模版就行
34 | func newTemplate() *template.Template {
35 | return template.Must(template.New("text").Parse(tmpl))
36 | }
37 |
--------------------------------------------------------------------------------
/bench/tmpl_test.go:
--------------------------------------------------------------------------------
1 | package bench
2 |
3 | import (
4 | "github.com/stretchr/testify/assert"
5 | "os"
6 | "testing"
7 | "time"
8 | )
9 |
10 | func Test_Bench_newTemplate(t *testing.T) {
11 | tmpl := newTemplate()
12 |
13 | r := report{
14 | Failed: 3,
15 | Percentage55: time.Second,
16 | Percentage66: 2 * time.Second,
17 | Percentage75: 3 * time.Second,
18 | Percentage80: 4 * time.Second,
19 | Percentage90: 5 * time.Second,
20 | Percentage95: 6 * time.Second,
21 | Percentage98: 7 * time.Second,
22 | Percentage99: 8 * time.Second,
23 | Percentage100: 9 * time.Second,
24 | StatusCodes: map[int]int{
25 | 200: 100,
26 | 500: 3,
27 | },
28 | }
29 |
30 | err := tmpl.Execute(os.Stdout, r)
31 | assert.NoError(t, err)
32 | }
33 |
--------------------------------------------------------------------------------
/color/color.go:
--------------------------------------------------------------------------------
1 | package color
2 |
3 | import (
4 | "fmt"
5 | "github.com/mattn/go-isatty"
6 | "os"
7 | "strings"
8 | )
9 |
10 | var (
11 | // NoColor 关闭颜色开关 本行代码来自github.com/fatih/color, 感谢fatih的付出
12 | NoColor = os.Getenv("TERM") == "dumb" ||
13 | (!isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()))
14 | )
15 |
16 | type attr int
17 |
18 | const (
19 | // FgBlack 黑色
20 | FgBlack attr = iota + 30
21 | // FgRed 红色
22 | FgRed
23 | // FgGreen 绿色
24 | FgGreen
25 | // FgYellow 黄色
26 | FgYellow
27 | // FgBlue 蓝色
28 | FgBlue
29 | // FgMagenta 品红
30 | FgMagenta
31 | // FgCyan 青色
32 | FgCyan
33 | // FgWhite 白色
34 | FgWhite
35 | )
36 |
37 | const (
38 | // Purple 紫色
39 | Purple = 35
40 | // Blue 蓝色
41 | Blue = 34
42 | )
43 |
44 | // Color 着色核心数据结构
45 | type Color struct {
46 | openColor bool
47 | attr attr
48 | }
49 |
50 | // New 着色模块构造函数
51 | func New(openColor bool, c ...attr) *Color {
52 | attr := attr(30)
53 | if len(c) > 0 {
54 | attr = c[0]
55 | }
56 | return &Color{openColor: openColor, attr: attr}
57 | }
58 |
59 | func (c *Color) set(buf *strings.Builder, attr attr) {
60 | if NoColor || !c.openColor {
61 | return
62 | }
63 |
64 | fmt.Fprintf(buf, "\x1b[%d;1m", attr)
65 | }
66 |
67 | func (c *Color) unset(buf *strings.Builder) {
68 | if NoColor || !c.openColor {
69 | return
70 | }
71 |
72 | fmt.Fprintf(buf, "\x1b[0m")
73 | }
74 |
75 | func (c *Color) color(a ...interface{}) string {
76 | var buf strings.Builder
77 |
78 | c.set(&buf, c.attr)
79 |
80 | fmt.Fprint(&buf, a...)
81 | c.unset(&buf)
82 |
83 | return buf.String()
84 | }
85 |
86 | func (c *Color) colorf(format string, a ...interface{}) string {
87 | var buf strings.Builder
88 |
89 | c.set(&buf, c.attr)
90 |
91 | fmt.Fprintf(&buf, format, a...)
92 | c.unset(&buf)
93 |
94 | return buf.String()
95 | }
96 |
97 | // Sbluef 蓝色函数
98 | func (c *Color) Sbluef(format string, a ...interface{}) string {
99 | c.attr = Blue
100 | return c.colorf(format, a...)
101 | }
102 |
103 | // Sblue 蓝色函数
104 | func (c *Color) Sblue(a ...interface{}) string {
105 | c.attr = Blue
106 | return c.color(a...)
107 | }
108 |
109 | // Spurplef 紫色函数, TODO删除该函数
110 | func (c *Color) Spurplef(format string, a ...interface{}) string {
111 | c.attr = Purple
112 | return c.colorf(format, a...)
113 | }
114 |
115 | // Spurple 紫色函数
116 | func (c *Color) Spurple(a ...interface{}) string {
117 | c.attr = Purple
118 | return c.color(a...)
119 | }
120 |
--------------------------------------------------------------------------------
/color/color_core_test.go:
--------------------------------------------------------------------------------
1 | package color
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io"
7 | "io/ioutil"
8 | "strings"
9 | "testing"
10 |
11 | "github.com/guonaihong/gout/core"
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | type testFormat struct {
16 | r io.Reader
17 | openColor bool
18 | bodyType BodyType
19 | }
20 |
21 | func Test_ColorCore_NewFormat_Nil(t *testing.T) {
22 | data := []testFormat{
23 | {nil, false, JSONType},
24 | {nil, true, TxtType},
25 | {&core.ReadCloseFail{}, true, JSONType},
26 | {strings.NewReader("xxx"), true, JSONType},
27 | }
28 |
29 | for index, d := range data {
30 | assert.Nil(t, NewFormatEncoder(d.r, d.openColor, d.bodyType, true), fmt.Sprintf("fail index:%d", index))
31 | }
32 | }
33 |
34 | func Test_ColorCore_Read(t *testing.T) {
35 | j := `{
36 | "array": [
37 | "foo",
38 | "bar",
39 | "baz"
40 | ],
41 | "bool": false,
42 | "null": null,
43 | "num": 100,
44 | "obj": {
45 | "a": 1,
46 | "b": 2
47 | },
48 | "str": "foo"
49 | }`
50 |
51 | NoColor = false
52 | f := NewFormatEncoder(strings.NewReader(j), true /*open color*/, JSONType, true)
53 | var out bytes.Buffer
54 |
55 | _, err := io.Copy(&out, f)
56 | assert.NoError(t, err)
57 |
58 | all, err := ioutil.ReadFile("./testdata/color.data")
59 | if err != nil {
60 | return
61 | }
62 |
63 | assert.Equal(t, out.Bytes(), all)
64 | //ioutil.WriteFile("/tmp/color.data", out.Bytes(), 0644)
65 | }
66 |
--------------------------------------------------------------------------------
/color/color_test.go:
--------------------------------------------------------------------------------
1 | package color
2 |
3 | import (
4 | "fmt"
5 | "github.com/stretchr/testify/assert"
6 | "testing"
7 | )
8 |
9 | func Test_Color_Spurple(t *testing.T) {
10 | NoColor = false
11 |
12 | need := "\x1b[35;1mhello\x1b[0m"
13 | got := New(true).Spurple("hello")
14 |
15 | fmt.Printf("got(%s) need(%s)\n", got, need)
16 | assert.Equal(t, got, need)
17 | assert.Equal(t, New(false).Spurple("hello"), "hello")
18 | }
19 |
20 | func Test_Color_Spurplef(t *testing.T) {
21 | NoColor = false
22 |
23 | need := "\x1b[35;1mhello:world\x1b[0m"
24 | got := New(true).Spurplef("hello:%s", "world")
25 |
26 | fmt.Printf("got(%s) need(%s)\n", got, need)
27 | assert.Equal(t, got, need)
28 | assert.Equal(t, New(false).Spurplef("hello:%s", "world"), "hello:world")
29 | }
30 |
31 | func Test_Color_Sbluef(t *testing.T) {
32 | NoColor = false
33 |
34 | assert.Equal(t, New(true).Sbluef("hello"), "\x1b[34;1mhello\x1b[0m")
35 | assert.Equal(t, New(false).Sbluef("hello"), "hello")
36 | }
37 |
38 | func Test_Color_Sblue(t *testing.T) {
39 | NoColor = false
40 |
41 | assert.Equal(t, New(true).Sblue("hello"), "\x1b[34;1mhello\x1b[0m")
42 | assert.Equal(t, New(false).Sblue("hello"), "hello")
43 | }
44 |
45 | func Test_Color_color(t *testing.T) {
46 | NoColor = false
47 | assert.Equal(t, New(true, Blue).color("hello"), "\x1b[34;1mhello\x1b[0m")
48 | assert.Equal(t, New(false).color("hello"), "hello")
49 | }
50 |
51 | func Test_Color_New(t *testing.T) {
52 | n := New(true)
53 | assert.NotNil(t, n)
54 | }
55 |
--------------------------------------------------------------------------------
/core/core.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "errors"
5 | "net/http"
6 | "reflect"
7 | "unsafe"
8 | )
9 |
10 | // FormFile 用于formdata类型数据编码
11 | // 从文件读取数据流
12 | type FormFile string
13 |
14 | // FormMem 用于formdata类型数据编码
15 | // 从[]byte里面读取数据流
16 | type FormMem []byte
17 |
18 | // FormType 自定义formdata文件名和流的类型
19 | type FormType struct {
20 | FileName string //filename
21 | ContentType string //Content-Type:Mime-Type
22 | File interface{} //FromFile | FromMem (这里就是您的从文件地址中读取和从内存中读取)
23 | }
24 |
25 | // H 是map[string]interface{} 简写
26 | type H map[string]interface{}
27 |
28 | // A是[]interface{} 简写
29 | type A []interface{}
30 |
31 | // ErrUnknownType 未知错误类型
32 | var ErrUnknownType = errors.New("unknown type")
33 |
34 | // LoopElem 不停地对指针解引用
35 | func LoopElem(v reflect.Value) reflect.Value {
36 | for v.Kind() == reflect.Ptr {
37 | if v.IsNil() {
38 | return v
39 | }
40 | v = v.Elem()
41 | }
42 |
43 | return v
44 | }
45 |
46 | // BytesToString 没有内存开销的转换
47 | func BytesToString(b []byte) string {
48 | return *(*string)(unsafe.Pointer(&b))
49 | }
50 |
51 | // StringToBytes 没有内存开销的转换
52 | func StringToBytes(s string) (b []byte) {
53 | bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
54 | sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
55 | bh.Data = sh.Data
56 | bh.Len = sh.Len
57 | bh.Cap = sh.Len
58 | return b
59 | }
60 |
61 | // NewPtrVal 新建这个类型的指针变量并赋值
62 | func NewPtrVal(defValue interface{}) interface{} {
63 | p := reflect.New(reflect.TypeOf(defValue))
64 | p.Elem().Set(reflect.ValueOf(defValue))
65 | return p.Interface()
66 | }
67 |
68 | func CloneRequest(r *http.Request) (*http.Request, error) {
69 | var err error
70 |
71 | r0 := &http.Request{}
72 | *r0 = *r
73 |
74 | r0.Header = make(http.Header, len(r.Header))
75 |
76 | for k, h := range r.Header {
77 | r0.Header[k] = append([]string(nil), h...)
78 | }
79 |
80 | r0.Body, err = r.GetBody()
81 | return r0, err
82 | }
83 |
84 | func GetBytes(v interface{}) (b []byte, ok bool) {
85 | switch d := v.(type) {
86 | case []byte:
87 | return d, true
88 | case string:
89 | return StringToBytes(d), true
90 | }
91 | return nil, false
92 | }
93 |
94 | func GetString(v interface{}) (s string, ok bool) {
95 | switch s := v.(type) {
96 | case []byte:
97 | return BytesToString(s), true
98 | case string:
99 | return s, true
100 | }
101 | return "", false
102 | }
103 |
--------------------------------------------------------------------------------
/core/core_test.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | "net/http"
7 | "reflect"
8 | "testing"
9 |
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | type testCore struct {
14 | need interface{}
15 | set interface{}
16 | }
17 |
18 | func Test_Core_LoopElem(t *testing.T) {
19 | s := "hello world"
20 | p := &s
21 | pp := &p
22 | ppp := &pp
23 |
24 | rs := []reflect.Value{
25 | LoopElem(reflect.ValueOf(s)),
26 | LoopElem(reflect.ValueOf(p)),
27 | LoopElem(reflect.ValueOf(pp)),
28 | LoopElem(reflect.ValueOf(ppp)),
29 | }
30 |
31 | for _, v := range rs {
32 | assert.Equal(t, v.Interface().(string), s)
33 | }
34 |
35 | var pi *int
36 | assert.Equal(t, LoopElem(reflect.ValueOf(pi)), reflect.ValueOf(pi))
37 | }
38 |
39 | type testBytesToStr struct {
40 | set []byte
41 | need string
42 | }
43 |
44 | func Test_Core_BytesToString(t *testing.T) {
45 | tests := []testBytesToStr{
46 | {set: []byte("hello world"), need: "hello world"},
47 | {set: []byte("hello"), need: "hello"},
48 | }
49 |
50 | for _, test := range tests {
51 | assert.Equal(t, BytesToString(test.set), test.need)
52 | }
53 | }
54 |
55 | func Test_Core_StringToBytes(t *testing.T) {
56 | tests := []testCore{
57 | {StringToBytes("hello"), []byte("hello")},
58 | {StringToBytes("world"), []byte("world")},
59 | }
60 |
61 | for _, v := range tests {
62 | assert.Equal(t, v.need, v.set)
63 | }
64 | }
65 |
66 | func Test_Core_NewPtrVal(t *testing.T) {
67 | tests := []testCore{
68 | {NewPtrVal(1), 1},
69 | {NewPtrVal(11.11), 11.11},
70 | }
71 |
72 | for _, v := range tests {
73 | val := reflect.ValueOf(v.need)
74 | val = val.Elem()
75 | assert.Equal(t, val.Interface(), v.set)
76 | }
77 | }
78 |
79 | func Test_Bench_closeRequest(t *testing.T) {
80 | b := bytes.NewBuffer([]byte("hello"))
81 | req, err := http.NewRequest("GET", "hello", b)
82 | assert.NoError(t, err)
83 |
84 | req.Header.Add("h1", "h2")
85 | req.Header.Add("h2", "h2")
86 |
87 | req2, err := CloneRequest(req)
88 | assert.NoError(t, err)
89 |
90 | // 测试http header是否一样
91 | assert.Equal(t, req.Header, req2.Header)
92 |
93 | b2, err := req2.GetBody()
94 | assert.NoError(t, err)
95 |
96 | b3 := bytes.NewBuffer(nil)
97 | _, err = io.Copy(b3, b2)
98 | assert.NoError(t, err)
99 |
100 | // 测试body是否一样
101 | assert.Equal(t, b, b3)
102 | }
103 |
104 | func Test_Core_GetBytes(t *testing.T) {
105 | type getBytes struct {
106 | need []byte
107 | set interface{}
108 | }
109 |
110 | tests := []getBytes{
111 | {[]byte("ok"), "ok"},
112 | {[]byte("ok"), []byte("ok")},
113 | {nil, new(int)},
114 | }
115 |
116 | for _, test := range tests {
117 | v, _ := GetBytes(test.set)
118 | assert.Equal(t, test.need, v)
119 | }
120 | }
121 |
122 | func Test_Core_GetString(t *testing.T) {
123 | type getBytes struct {
124 | need string
125 | set interface{}
126 | }
127 |
128 | tests := []getBytes{
129 | {"ok", "ok"},
130 | {"ok", []byte("ok")},
131 | {"", new(int)},
132 | }
133 |
134 | for _, test := range tests {
135 | v, _ := GetString(test.set)
136 | assert.Equal(t, test.need, v)
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/core/port.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "fmt"
5 | "net"
6 | )
7 |
8 | // 获取没有绑定服务的端口
9 | func GetNoPortExists() string {
10 | startPort := 1000 //1000以下的端口很多时间需要root权限才能使用
11 | for port := startPort; port < 65535; port++ {
12 | l, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", port))
13 | if err != nil {
14 | continue
15 | }
16 | l.Close()
17 | return fmt.Sprintf("%d", port)
18 | }
19 |
20 | return "0"
21 | }
22 |
--------------------------------------------------------------------------------
/core/port_test.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "fmt"
5 | "github.com/stretchr/testify/assert"
6 | "net"
7 | "testing"
8 | )
9 |
10 | func TestGetNoPortExists(t *testing.T) {
11 | port := GetNoPortExists()
12 | l, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%s", port))
13 | assert.NoError(t, err)
14 | if l != nil {
15 | l.Close()
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/core/test.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | type Need struct {
4 | Need interface{}
5 | Got interface{}
6 | }
7 |
--------------------------------------------------------------------------------
/core/utils.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "errors"
5 | )
6 |
7 | // ReadCloseFail 内部测试使用
8 | type ReadCloseFail struct{}
9 |
10 | // Read 供测试使用
11 | func (r *ReadCloseFail) Read(p []byte) (n int, err error) {
12 | return 0, errors.New("must fail")
13 | }
14 |
15 | // Close 内部测试使用
16 | func (r *ReadCloseFail) Close() error {
17 | return nil
18 | }
19 |
--------------------------------------------------------------------------------
/core/utils_test.go:
--------------------------------------------------------------------------------
1 | package core
2 |
3 | import (
4 | "github.com/stretchr/testify/assert"
5 | "testing"
6 | )
7 |
8 | func Test_Core_Util_Close(t *testing.T) {
9 | assert.NoError(t, (&ReadCloseFail{}).Close())
10 | }
11 |
12 | func Test_Core_Util_Read(t *testing.T) {
13 | p := make([]byte, 1)
14 | _, err := (&ReadCloseFail{}).Read(p)
15 | assert.Error(t, err)
16 | }
17 |
--------------------------------------------------------------------------------
/dataflow/cleanpath.go:
--------------------------------------------------------------------------------
1 | package dataflow
2 |
3 | import (
4 | "path"
5 | "strings"
6 | )
7 |
8 | const (
9 | httpProto = "http://"
10 | httpsProto = "https://"
11 | )
12 |
13 | func cleanPaths(rv string) string {
14 | if rv == "" {
15 | return rv
16 | }
17 |
18 | if strings.HasPrefix(rv, httpProto) || strings.HasPrefix(rv, httpsProto) {
19 | var proto, domainPath, query string
20 | protoEndIdx := strings.Index(rv, "//") + 2
21 | queryIdx := strings.Index(rv, "?")
22 |
23 | proto = rv[:protoEndIdx]
24 | if queryIdx != -1 {
25 | domainPath = rv[protoEndIdx:queryIdx]
26 | query = rv[queryIdx:]
27 | } else if rv != proto {
28 | domainPath = rv[protoEndIdx:]
29 | }
30 | if domainPath != "" {
31 | appendSlash := len(domainPath) > 0 && domainPath[len(domainPath)-1] == '/'
32 | domainPath = path.Clean(domainPath)
33 | if appendSlash {
34 | domainPath += "/"
35 | }
36 | }
37 | return proto + domainPath + query
38 | }
39 |
40 | appendSlash := len(rv) > 0 && rv[len(rv)-1] == '/'
41 | cleanPath := path.Clean(rv)
42 | if appendSlash {
43 | return cleanPath + "/"
44 | }
45 |
46 | return cleanPath
47 | }
48 |
--------------------------------------------------------------------------------
/dataflow/cleanpath_test.go:
--------------------------------------------------------------------------------
1 | package dataflow
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | type cleanPathTest struct {
11 | path string
12 | need string
13 | }
14 |
15 | func TestUtil_cleanPaths(t *testing.T) {
16 | test := []cleanPathTest{
17 | {path: "", need: ""},
18 | {path: "https://www.aa.com/a", need: "https://www.aa.com/a"},
19 | {path: "http://www.bb.com/b", need: "http://www.bb.com/b"},
20 | {path: "www.bb.com/b", need: "www.bb.com/b"},
21 | {path: "www.bb.com", need: "www.bb.com"},
22 | {path: "/a", need: "/a"},
23 | {path: "/a/", need: "/a/"},
24 | {path: "http://", need: "http://"},
25 | {path: "https://", need: "https://"},
26 | {path: "http://www.aa.com/urls?", need: "http://www.aa.com/urls?"},
27 | {path: "https://www.aa.com/urls?bb", need: "https://www.aa.com/urls?bb"},
28 | {path: "http://www.aa.com/urls?site=https://bb.com", need: "http://www.aa.com/urls?site=https://bb.com"},
29 | {path: "https://www.aa.com/urls?site=http://bb.com", need: "https://www.aa.com/urls?site=http://bb.com"},
30 | {path: "http://www.aa.com/urls/./a?site=https://bb.com", need: "http://www.aa.com/urls/a?site=https://bb.com"},
31 | {path: "https://www.aa.com/urls/../a?site=https://bb.com", need: "https://www.aa.com/a?site=https://bb.com"},
32 | {path: "https://api.map.baidu.com/weather/v1/?district_id=310100&data_type=all&ak=ffyu0pP8P6Ao0KYr8FTZwDgsOFiA1oYA", need: "https://api.map.baidu.com/weather/v1/?district_id=310100&data_type=all&ak=ffyu0pP8P6Ao0KYr8FTZwDgsOFiA1oYA"},
33 | }
34 |
35 | for index, v := range test {
36 | rv := cleanPaths(v.path)
37 | assert.Equal(t, v.need, rv, fmt.Sprintf("index:%d", index))
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/dataflow/context.go:
--------------------------------------------------------------------------------
1 | package dataflow
2 |
3 | // Context struct
4 | type Context struct {
5 | Code int //http code
6 | Error error
7 |
8 | *DataFlow
9 | }
10 |
--------------------------------------------------------------------------------
/dataflow/dataflow_auto_decode_body_test.go:
--------------------------------------------------------------------------------
1 | package dataflow
2 |
3 | import (
4 | "bytes"
5 | "compress/gzip"
6 | "compress/zlib"
7 | "log"
8 | "net/http"
9 | "net/http/httptest"
10 | "testing"
11 | "time"
12 |
13 | "github.com/andybalholm/brotli"
14 | "github.com/gin-gonic/gin"
15 | "github.com/stretchr/testify/assert"
16 | )
17 |
18 | var test_autoDecodeBody_data = "test auto decode boyd function"
19 |
20 | // 测试服务
21 | func create_AutoDecodeBody() *httptest.Server {
22 | r := gin.New()
23 | r.GET("/gzip", func(c *gin.Context) {
24 |
25 | var buf bytes.Buffer
26 |
27 | zw := gzip.NewWriter(&buf)
28 | // Setting the Header fields is optional.
29 | zw.Name = "a-new-hope.txt"
30 | zw.Comment = "an epic space opera by George Lucas"
31 | zw.ModTime = time.Date(1977, time.May, 25, 0, 0, 0, 0, time.UTC)
32 | _, err := zw.Write([]byte(test_autoDecodeBody_data))
33 | if err != nil {
34 | log.Fatal(err)
35 | }
36 |
37 | if err := zw.Close(); err != nil {
38 | log.Fatal(err)
39 | }
40 | c.Header("Content-Encoding", "gzip")
41 | c.String(200, buf.String())
42 | })
43 |
44 | r.GET("/br", func(c *gin.Context) {
45 |
46 | c.Header("Content-Encoding", "br")
47 | var buf bytes.Buffer
48 | w := brotli.NewWriter(&buf)
49 | _, err := w.Write([]byte(test_autoDecodeBody_data))
50 | if err != nil {
51 | log.Fatal(err)
52 | }
53 | w.Flush()
54 | w.Close()
55 | c.String(200, buf.String())
56 | })
57 |
58 | r.GET("/deflate", func(c *gin.Context) {
59 |
60 | var buf bytes.Buffer
61 | w := zlib.NewWriter(&buf)
62 | _, err := w.Write([]byte(test_autoDecodeBody_data))
63 | if err != nil {
64 | log.Fatal(err)
65 | }
66 | w.Close()
67 | c.Header("Content-Encoding", "deflate")
68 | c.String(200, buf.String())
69 | })
70 |
71 | r.GET("/compress", func(c *gin.Context) {
72 | c.Header("Content-Encoding", "compress")
73 | })
74 | return httptest.NewServer(http.HandlerFunc(r.ServeHTTP))
75 | }
76 |
77 | func Test_AutoDecodeBody(t *testing.T) {
78 | ts := create_AutoDecodeBody()
79 | var err error
80 | for _, path := range []string{"/gzip", "/br", "/deflate"} {
81 | s := ""
82 | if path == "/gzip" {
83 | err = New().GET(ts.URL + path).Debug(true).BindBody(&s).Do()
84 | } else {
85 | err = New().GET(ts.URL + path).AutoDecodeBody().Debug(true).BindBody(&s).Do()
86 |
87 | }
88 | assert.NoError(t, err)
89 | assert.Equal(t, s, test_autoDecodeBody_data)
90 | }
91 | }
92 |
93 | func Test_AutoDecodeBody_Fail(t *testing.T) {
94 | ts := create_AutoDecodeBody()
95 | err := New().GET(ts.URL + "/compress").AutoDecodeBody().Do()
96 | assert.Error(t, err)
97 | }
98 |
--------------------------------------------------------------------------------
/dataflow/dataflow_json_test.go:
--------------------------------------------------------------------------------
1 | package dataflow
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "testing"
7 |
8 | "github.com/guonaihong/gout/debug"
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | func Test_SetJSONNotEscape(t *testing.T) {
13 | ts := createGeneral("")
14 | fmt.Println("url", ts.URL)
15 | var buf bytes.Buffer
16 | //POST(ts.URL).Debug(true).SetJSONNotEscape(map[string]any{"url": "http://www.com?a=b&c=d"}).Do()
17 | err := POST(ts.URL).Debug(debug.ToWriter(&buf, false)).SetJSONNotEscape(map[string]interface{}{"url": "http://www.com?a=b&c=d"}).Do()
18 | assert.NoError(t, err)
19 | assert.True(t, bytes.Contains(buf.Bytes(), []byte("&")), buf.String())
20 | buf.Reset()
21 | //POST(ts.URL).Debug(true).SetJSON(map[string]any{"url": "http://www.com?a=b&c=d"}).Do()
22 | err = POST(ts.URL).Debug(debug.ToWriter(&buf, false)).SetJSON(map[string]interface{}{"url": "http://www.com?a=b&c=d"}).Do()
23 | assert.NoError(t, err)
24 | assert.False(t, bytes.Contains(buf.Bytes(), []byte("&")), buf.String())
25 | }
26 |
--------------------------------------------------------------------------------
/dataflow/dataflow_middleware_test.go:
--------------------------------------------------------------------------------
1 | package dataflow
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io/ioutil"
7 | "net/http"
8 | "strings"
9 | "testing"
10 |
11 | "github.com/guonaihong/gout/json"
12 |
13 | core "github.com/guonaihong/gout/core"
14 |
15 | "github.com/guonaihong/gout/middler"
16 | "github.com/stretchr/testify/assert"
17 | )
18 |
19 | type demoRequestMiddler struct{}
20 |
21 | func (d *demoRequestMiddler) ModifyRequest(req *http.Request) error {
22 | req.Body = ioutil.NopCloser(strings.NewReader("demo"))
23 | req.ContentLength = 4
24 | return nil
25 | }
26 |
27 | func demoRequest() middler.RequestMiddler {
28 | return &demoRequestMiddler{}
29 | }
30 |
31 | func Test_RequestModify(t *testing.T) {
32 | ts := createGeneralEcho()
33 | s := ""
34 | err := New().POST(ts.URL).RequestUse(demoRequest()).SetBody("hello").BindBody(&s).Do()
35 | assert.NoError(t, err, fmt.Sprintf("test url:%s", ts.URL))
36 | assert.Equal(t, s, "demo")
37 | }
38 |
39 | // response拦截器修改示例
40 | type demoResponseMiddler struct{}
41 |
42 | func (d *demoResponseMiddler) ModifyResponse(response *http.Response) error {
43 | // 修改responseBody。 因为返回值大概率会有 { code, data,msg} 等字段,希望进行统一处理
44 | //这里想验证code. 如果不对就返回error。 对的话将 data中的内容写入body,这样后面BindJson的时候就可以直接处理业务了
45 | all, err := ioutil.ReadAll(response.Body)
46 | if err != nil {
47 | return err
48 | }
49 | obj := make(map[string]interface{})
50 |
51 | err = json.Unmarshal(all, &obj)
52 | if err != nil {
53 | return err
54 | }
55 | code := obj["code"]
56 | msg := obj["msg"]
57 | data := obj["data"]
58 |
59 | // Go中json中的数字经过反序列化会成为float64类型
60 | if float64(200) != code {
61 | return fmt.Errorf("请求失败, code %d msg %s", code, msg)
62 | } else {
63 | byt, _ := json.Marshal(&data)
64 | response.Body = ioutil.NopCloser(bytes.NewReader(byt))
65 | return nil
66 | }
67 | }
68 | func demoResponse() middler.ResponseMiddler {
69 | return &demoResponseMiddler{}
70 | }
71 |
72 | // 请求示例
73 | func Test_ResponseModify(t *testing.T) {
74 | ts := createGeneralEcho()
75 | arrs := core.A{
76 | core.H{
77 | "code": 200,
78 | "msg": "请求成功了",
79 | "data": core.H{
80 | "id": "1",
81 | "name": "张三",
82 | },
83 | },
84 | core.H{
85 | "code": 500,
86 | "msg": "查询数据库出错了",
87 | "data": nil,
88 | },
89 | }
90 |
91 | for i, arr := range arrs {
92 | // 返回值
93 | res := new(map[string]interface{})
94 | marshal, _ := json.Marshal(arr)
95 |
96 | err := New().POST(ts.URL).SetJSON(marshal).ResponseUse(demoResponse()).BindJSON(&res).Do()
97 |
98 | m := arr.(core.H)
99 | code := m["code"].(int)
100 | if code == 200 {
101 | assert.NoError(t, err, fmt.Sprintf("test case index:%d", i))
102 | } else {
103 | assert.Error(t, err, fmt.Sprintf("test case index:%d", i))
104 | }
105 | //log.Printf("请求 %d --> 参数 %s \n 响应 %s \n err %s \n ", i, marshal, res, err)
106 |
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/dataflow/dataflow_wwwform_test.go:
--------------------------------------------------------------------------------
1 | package dataflow
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "testing"
7 |
8 | "github.com/gin-gonic/gin"
9 | "github.com/guonaihong/gout/core"
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | type testWWWForm struct {
14 | Int int `form:"int" www-form:"int,omitempty"`
15 | Float64 float64 `form:"float64" www-form:"float64,omitempty"`
16 | String string `form:"string" www-form:"string,omitempty"`
17 | }
18 |
19 | func setupWWWForm(t *testing.T, need testWWWForm) *gin.Engine {
20 | r := gin.New()
21 |
22 | r.POST("/", func(c *gin.Context) {
23 | wf := testWWWForm{}
24 |
25 | err := c.ShouldBind(&wf)
26 |
27 | assert.NoError(t, err)
28 | //err := c.ShouldBind(&wf)
29 | assert.Equal(t, need, wf)
30 | })
31 |
32 | return r
33 | }
34 |
35 | func TestWWWForm(t *testing.T) {
36 | need := testWWWForm{
37 | Int: 3,
38 | Float64: 3.14,
39 | String: "test-www-Form",
40 | }
41 |
42 | router := setupWWWForm(t, need)
43 | ts := httptest.NewServer(http.HandlerFunc(router.ServeHTTP))
44 |
45 | for _, v := range [][]interface{}{
46 | // 测试使用一个结构体的情况
47 | []interface{}{
48 | testWWWForm{
49 | Int: 3,
50 | Float64: 3.14,
51 | String: "test-www-Form",
52 | },
53 | },
54 | // 测试两个结构体,同一个数据
55 | []interface{}{
56 | testWWWForm{
57 | Int: 3,
58 | },
59 | testWWWForm{
60 | Float64: 3.14,
61 | String: "test-www-Form",
62 | },
63 | },
64 | // 测试两种类型混用
65 | []interface{}{
66 | core.H{"int": 3},
67 | testWWWForm{
68 | Float64: 3.14,
69 | String: "test-www-Form",
70 | },
71 | },
72 | } {
73 | err := POST(ts.URL).Debug(true).SetWWWForm(v...).Do()
74 | assert.NoError(t, err)
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/dataflow/debug_trace_test.go:
--------------------------------------------------------------------------------
1 | package dataflow
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "fmt"
7 | "net/http"
8 | "net/http/httptest"
9 | "reflect"
10 | "strings"
11 | "testing"
12 |
13 | "github.com/gin-gonic/gin"
14 | "github.com/guonaihong/gout/debug"
15 | "github.com/stretchr/testify/assert"
16 | )
17 |
18 | func Test_Debug_Trace(t *testing.T) {
19 | router := func() *gin.Engine {
20 | router := gin.Default()
21 |
22 | router.POST("/test.json", func(c *gin.Context) {
23 | c.String(200, "ok")
24 | })
25 |
26 | return router
27 | }()
28 | errs := []error{
29 | func() error {
30 | ts := httptest.NewServer(http.HandlerFunc(router.ServeHTTP))
31 | return New().GET(ts.URL).Debug(debug.Trace()).Do()
32 | }(),
33 | func() error {
34 | ts := httptest.NewServer(http.HandlerFunc(router.ServeHTTP))
35 | var b bytes.Buffer
36 | custom := func() debug.Apply {
37 | return debug.Func(func(o *debug.Options) {
38 | o.Color = true
39 | o.Trace = true
40 | o.Write = &b
41 | })
42 | }
43 | err := New().GET(ts.URL).Debug(custom()).Do()
44 | if err != nil {
45 | return err
46 | }
47 |
48 | if !checkValue(&b) {
49 | return errors.New("No caring fields")
50 | }
51 | return nil
52 | }(),
53 | }
54 |
55 | for id, e := range errs {
56 | assert.NoError(t, e, fmt.Sprintf("test case id:%d", id))
57 | }
58 | }
59 |
60 | func checkValue(b *bytes.Buffer) bool {
61 | info := &debug.TraceInfo{}
62 | v := reflect.ValueOf(info)
63 | v = v.Elem()
64 |
65 | debugInfo := b.String()
66 | have := false
67 | typ := v.Type()
68 | for i := 0; i < v.NumField(); i++ {
69 | sf := typ.Field(i)
70 | if sf.PkgPath != "" {
71 | continue
72 | }
73 |
74 | name := sf.Name
75 | if !strings.Contains(debugInfo, name) {
76 | return false
77 | }
78 | have = true
79 | }
80 |
81 | if !have {
82 | return have
83 | }
84 |
85 | return true
86 | }
87 |
--------------------------------------------------------------------------------
/dataflow/export.go:
--------------------------------------------------------------------------------
1 | package dataflow
2 |
3 | type export struct {
4 | df *DataFlow
5 | }
6 |
7 | func (e *export) Curl() Curl {
8 | filterMu.RLock()
9 | defer filterMu.RUnlock()
10 |
11 | filter, ok := filters["curl"]
12 | if !ok {
13 | panic("export.curl: not found")
14 | }
15 |
16 | b := filter.New(e.df)
17 | curl, ok := b.(Curl)
18 | if !ok {
19 | panic("export.curl not found Curl")
20 | }
21 | return curl
22 | }
23 |
--------------------------------------------------------------------------------
/dataflow/export_test.go:
--------------------------------------------------------------------------------
1 | package dataflow
2 |
3 | import (
4 | "github.com/stretchr/testify/assert"
5 | "io"
6 | "testing"
7 | )
8 |
9 | type testCurl struct{}
10 |
11 | func (t *testCurl) New(df *DataFlow) interface{} {
12 | return &testCurl{}
13 | }
14 |
15 | func (t *testCurl) LongOption() Curl {
16 | return t
17 | }
18 |
19 | func (t *testCurl) GenAndSend() Curl {
20 | return t
21 | }
22 |
23 | func (t *testCurl) SetOutput(w io.Writer) Curl {
24 | return t
25 | }
26 |
27 | func (t *testCurl) Do() error {
28 | return nil
29 | }
30 |
31 | type testCurlFail struct{}
32 |
33 | func (t *testCurlFail) New(df *DataFlow) interface{} {
34 | return t
35 | }
36 |
37 | func Test_Export_curl(t *testing.T) {
38 | bkcurl, ok := filters["curl"]
39 | delete(filters, "curl")
40 | defer func() {
41 | if ok {
42 | filters["curl"] = bkcurl
43 | }
44 | }()
45 |
46 | // test panic
47 | for _, v := range []func(){
48 | func() {
49 | e := export{}
50 | e.Curl()
51 | },
52 | func() {
53 | filters["curl"] = &testCurlFail{}
54 | e := export{}
55 | e.Curl()
56 | },
57 | } {
58 | assert.Panics(t, v)
59 | }
60 |
61 | //test ok
62 | for _, v := range []func(){
63 | func() {
64 | filters["curl"] = &testCurl{}
65 | e := export{}
66 | e.Curl()
67 | },
68 | } {
69 | assert.NotPanics(t, v)
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/dataflow/filter.go:
--------------------------------------------------------------------------------
1 | package dataflow
2 |
3 | type filter struct {
4 | df *DataFlow
5 | }
6 |
7 | func (f *filter) Bench() Bencher {
8 | filterMu.RLock()
9 | defer filterMu.RUnlock()
10 |
11 | filter, ok := filters["bench"]
12 | if !ok {
13 | panic("filter.bench: not found")
14 | }
15 |
16 | b := filter.New(f.df)
17 | bench, ok := b.(Bencher)
18 | if !ok {
19 | panic("filter.bench not found Bencher interface")
20 | }
21 | return bench
22 |
23 | }
24 |
25 | func (f *filter) Retry() Retry {
26 | filterMu.RLock()
27 | defer filterMu.RUnlock()
28 |
29 | filter, ok := filters["retry"]
30 | if !ok {
31 | panic("filter.retry: not found interface")
32 | }
33 |
34 | b := filter.New(f.df)
35 | retry, ok := b.(Retry)
36 | if !ok {
37 | panic("filter.retry not found Retry")
38 | }
39 | return retry
40 | }
41 |
--------------------------------------------------------------------------------
/dataflow/filter_interface.go:
--------------------------------------------------------------------------------
1 | package dataflow
2 |
3 | import (
4 | "github.com/guonaihong/gout/bench"
5 | "io"
6 | "time"
7 | )
8 |
9 | type NewFilter interface {
10 | New(*DataFlow) interface{}
11 | }
12 |
13 | type Bencher interface {
14 | Concurrent(c int) Bencher
15 | Number(n int) Bencher
16 | Rate(rate int) Bencher
17 | Durations(d time.Duration) Bencher
18 | Loop(func(c *Context) error) Bencher
19 | GetReport(r *bench.Report) Bencher
20 | Do() error
21 | }
22 |
23 | type Retry interface {
24 | Attempt(attempt int) Retry
25 | WaitTime(waitTime time.Duration) Retry
26 | MaxWaitTime(maxWaitTime time.Duration) Retry
27 | Func(func(c *Context) error) Retry
28 | Do() error
29 | }
30 |
31 | type Filter interface {
32 | Bench() Bencher
33 | Retry() Retry
34 | }
35 |
36 | type Curl interface {
37 | LongOption() Curl
38 | GenAndSend() Curl
39 | SetOutput(w io.Writer) Curl
40 | Do() error
41 | }
42 |
43 | type Export interface {
44 | Curl()
45 | }
46 |
--------------------------------------------------------------------------------
/dataflow/filter_test.go:
--------------------------------------------------------------------------------
1 | package dataflow
2 |
3 | import (
4 | "github.com/guonaihong/gout/bench"
5 | "github.com/stretchr/testify/assert"
6 | "testing"
7 | "time"
8 | )
9 |
10 | type testBench struct{}
11 |
12 | func (t *testBench) New(df *DataFlow) interface{} {
13 | return &testBench{}
14 | }
15 |
16 | func (t *testBench) Concurrent(c int) Bencher {
17 | return t
18 | }
19 |
20 | func (t *testBench) Number(n int) Bencher {
21 | return t
22 | }
23 |
24 | func (t *testBench) Rate(rate int) Bencher {
25 | return t
26 | }
27 | func (t *testBench) Durations(d time.Duration) Bencher {
28 | return t
29 | }
30 |
31 | func (t *testBench) Loop(func(c *Context) error) Bencher {
32 | return t
33 | }
34 |
35 | func (t *testBench) GetReport(r *bench.Report) Bencher {
36 | return t
37 | }
38 |
39 | func (t *testBench) Do() error {
40 | return nil
41 | }
42 |
43 | type testRetry struct{}
44 |
45 | func (t *testRetry) New(df *DataFlow) interface{} {
46 | return &testRetry{}
47 | }
48 |
49 | func (t *testRetry) Attempt(attempt int) Retry {
50 | return t
51 | }
52 |
53 | func (t *testRetry) WaitTime(waitTime time.Duration) Retry {
54 | return t
55 | }
56 |
57 | func (t *testRetry) MaxWaitTime(maxWaitTime time.Duration) Retry {
58 | return t
59 | }
60 |
61 | func (t *testRetry) Func(func(c *Context) error) Retry {
62 | return t
63 | }
64 |
65 | func (t *testRetry) Do() error {
66 | return nil
67 | }
68 |
69 | const (
70 | benchName = "bench"
71 | retryName = "retry"
72 | )
73 |
74 | func Test_Filter_Bench(t *testing.T) {
75 | bkcurl, ok := filters[benchName]
76 | delete(filters, benchName)
77 | defer func() {
78 | if ok {
79 | filters[benchName] = bkcurl
80 | }
81 | }()
82 |
83 | // test panic
84 | for _, v := range []func(){
85 | func() {
86 | f := filter{}
87 | f.Bench()
88 | },
89 | func() {
90 | filters[benchName] = &testCurlFail{}
91 | f := filter{}
92 | f.Bench()
93 | },
94 | } {
95 | assert.Panics(t, v)
96 | }
97 |
98 | //test ok
99 | for _, v := range []func(){
100 | func() {
101 | filters[benchName] = &testBench{}
102 | f := filter{}
103 | f.Bench()
104 | },
105 | } {
106 | assert.NotPanics(t, v)
107 | }
108 | }
109 |
110 | func Test_Filter_Retry(t *testing.T) {
111 | bkcurl, ok := filters[retryName]
112 | delete(filters, retryName)
113 | defer func() {
114 | if ok {
115 | filters[retryName] = bkcurl
116 | }
117 | }()
118 |
119 | // test panic
120 | for _, v := range []func(){
121 | func() {
122 | f := filter{}
123 | f.Retry()
124 | },
125 | func() {
126 | filters[retryName] = &testCurlFail{}
127 | f := filter{}
128 | f.Retry()
129 | },
130 | } {
131 | assert.Panics(t, v)
132 | }
133 |
134 | //test ok
135 | for _, v := range []func(){
136 | func() {
137 | filters[retryName] = &testRetry{}
138 | f := filter{}
139 | f.Retry()
140 | },
141 | } {
142 | assert.NotPanics(t, v)
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/dataflow/global_config.go:
--------------------------------------------------------------------------------
1 | package dataflow
2 |
3 | import (
4 | "github.com/guonaihong/gout/setting"
5 | )
6 |
7 | // 存放全局配置选项
8 | var GlobalSetting = setting.Setting{
9 | NotIgnoreEmpty: true,
10 | }
11 |
--------------------------------------------------------------------------------
/dataflow/gout.go:
--------------------------------------------------------------------------------
1 | package dataflow
2 |
3 | import (
4 | "net/http"
5 | )
6 |
7 | // Gout is the data structure at the beginning of everything
8 | type Gout struct {
9 | *http.Client
10 | DataFlow // TODO 优化
11 | }
12 |
13 | var (
14 | // DefaultClient The default http client, which has a connection pool
15 | DefaultClient = http.Client{}
16 | // DefaultBenchClient is the default http client used by the benchmark,
17 | // which has a connection pool
18 | DefaultBenchClient = http.Client{
19 | Transport: &http.Transport{
20 | MaxIdleConnsPerHost: 10000,
21 | },
22 | }
23 | )
24 |
25 | // New function is mainly used when passing custom http client
26 | func New(c ...*http.Client) *Gout {
27 | out := &Gout{}
28 | if len(c) == 0 || c[0] == nil {
29 | out.Client = &DefaultClient
30 | } else {
31 | out.Client = c[0]
32 | }
33 |
34 | out.DataFlow.out = out
35 | out.DataFlow.Req.g = out
36 | return out
37 | }
38 |
39 | // TODO 这一层可以直接删除
40 | // v0.3.3版本开始算起, v0.3.7版本将会删除
41 | // GET send HTTP GET method
42 | func GET(url string) *DataFlow {
43 | return New().GET(url)
44 | }
45 |
46 | // POST send HTTP POST method
47 | // v0.3.3版本开始算起, v0.3.7版本将会删除
48 | func POST(url string) *DataFlow {
49 | return New().POST(url)
50 | }
51 |
52 | // PUT send HTTP PUT method
53 | // v0.3.3版本开始算起, v0.3.7版本将会删除
54 | func PUT(url string) *DataFlow {
55 | return New().PUT(url)
56 | }
57 |
58 | // DELETE send HTTP DELETE method
59 | // v0.3.3版本开始算起, v0.3.7版本将会删除
60 | func DELETE(url string) *DataFlow {
61 | return New().DELETE(url)
62 | }
63 |
64 | // PATCH send HTTP PATCH method
65 | // v0.3.3版本开始算起, v0.3.7版本将会删除
66 | func PATCH(url string) *DataFlow {
67 | return New().PATCH(url)
68 | }
69 |
70 | // HEAD send HTTP HEAD method
71 | // v0.3.3版本开始算起, v0.3.7版本将会删除
72 | func HEAD(url string) *DataFlow {
73 | return New().HEAD(url)
74 | }
75 |
76 | // OPTIONS send HTTP OPTIONS method
77 | // v0.3.3版本开始算起, v0.3.7版本将会删除
78 | func OPTIONS(url string) *DataFlow {
79 | return New().OPTIONS(url)
80 | }
81 |
--------------------------------------------------------------------------------
/dataflow/gout_test.go:
--------------------------------------------------------------------------------
1 | package dataflow
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/stretchr/testify/assert"
6 | "net/http"
7 | "net/http/httptest"
8 | "sync/atomic"
9 | "testing"
10 | )
11 |
12 | func TestNew(t *testing.T) {
13 | c := &http.Client{}
14 | tests := []*Gout{
15 | New(nil),
16 | New(),
17 | New(c),
18 | }
19 |
20 | for _, v := range tests {
21 | assert.NotNil(t, v)
22 | }
23 | }
24 |
25 | func setupMethod(total *int32) *gin.Engine {
26 |
27 | router := gin.Default()
28 |
29 | cb := func(c *gin.Context) {
30 | atomic.AddInt32(total, 1)
31 | }
32 |
33 | router.GET("/someGet", cb)
34 | router.POST("/somePost", cb)
35 | router.PUT("/somePut", cb)
36 | router.DELETE("/someDelete", cb)
37 | router.PATCH("/somePatch", cb)
38 | router.HEAD("/someHead", cb)
39 | router.OPTIONS("/someOptions", cb)
40 |
41 | return router
42 | }
43 |
44 | func TestTopMethod(t *testing.T) {
45 | var total int32
46 |
47 | router := setupMethod(&total)
48 |
49 | ts := httptest.NewServer(http.HandlerFunc(router.ServeHTTP))
50 | defer ts.Close()
51 |
52 | err := GET(ts.URL + "/someGet").Do()
53 | assert.NoError(t, err)
54 |
55 | err = POST(ts.URL + "/somePost").Do()
56 | assert.NoError(t, err)
57 |
58 | err = PUT(ts.URL + "/somePut").Do()
59 | assert.NoError(t, err)
60 |
61 | err = DELETE(ts.URL + "/someDelete").Do()
62 | assert.NoError(t, err)
63 |
64 | err = PATCH(ts.URL + "/somePatch").Do()
65 | assert.NoError(t, err)
66 |
67 | err = HEAD(ts.URL + "/someHead").Do()
68 | assert.NoError(t, err)
69 |
70 | err = OPTIONS(ts.URL + "/someOptions").Do()
71 | assert.NoError(t, err)
72 |
73 | assert.Equal(t, int(total), 7)
74 | }
75 |
--------------------------------------------------------------------------------
/dataflow/register.go:
--------------------------------------------------------------------------------
1 | package dataflow
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | // 所有的filter通过调用Register函数注册到filters变量里面
8 | // 这么做的目的是为了剪去DataFlow和filter之前的循环引用
9 | // 使用注册机制好处如下
10 | // DataFlow层(比如gout.GET()这种用法)可以通过.Bench().Retry调用filter层函数
11 | // Bench层也可以使用DataFlow层功能
12 |
13 | var (
14 | filterMu sync.RWMutex
15 | filters = make(map[string]NewFilter)
16 | )
17 |
18 | func Register(name string, filter NewFilter) {
19 | filterMu.Lock()
20 | defer filterMu.Unlock()
21 |
22 | if filters == nil {
23 | panic("dataflow: Register filters is nil")
24 | }
25 |
26 | if _, dup := filters[name]; dup {
27 | panic("dataflow: Register called twice for driver " + name)
28 | }
29 |
30 | filters[name] = filter
31 | }
32 |
--------------------------------------------------------------------------------
/dataflow/req_basic_auth_test.go:
--------------------------------------------------------------------------------
1 | package dataflow
2 |
3 | import (
4 | "errors"
5 | "net/http"
6 | "net/http/httptest"
7 | "testing"
8 |
9 | "github.com/gin-gonic/gin"
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | func testServerBasicAuth(t *testing.T, userName, password string) *gin.Engine {
14 | r := gin.New()
15 | r.GET("/basicauth", func(c *gin.Context) {
16 | u, p, ok := c.Request.BasicAuth()
17 | if !ok {
18 | assert.Error(t, errors.New("basicauth get fail"))
19 | c.String(500, "fail")
20 | return
21 | }
22 |
23 | if userName != u {
24 | assert.Error(t, errors.New("user name fail"))
25 | c.String(500, "user name fail")
26 | return
27 |
28 | }
29 |
30 | if password != p {
31 | assert.Error(t, errors.New("password fail"))
32 | c.String(500, "password fail")
33 | return
34 |
35 | }
36 |
37 | c.String(200, "ok")
38 | })
39 | return r
40 | }
41 |
42 | func Test_BasicAuth(t *testing.T) {
43 | const (
44 | userName = "test-name"
45 | password = "test-passowrd"
46 | )
47 |
48 | s := testServerBasicAuth(t, userName, password)
49 | ts := httptest.NewServer(http.HandlerFunc(s.ServeHTTP))
50 |
51 | code := 0
52 | err := New().GET(ts.URL+"/basicauth").SetBasicAuth(userName, password).Code(&code).Do()
53 | assert.NoError(t, err)
54 | assert.Equal(t, 200, code)
55 | }
56 |
--------------------------------------------------------------------------------
/dataflow/req_bench_test.go:
--------------------------------------------------------------------------------
1 | package dataflow
2 |
3 | import "testing"
4 |
5 | func Benchmark_URL_Template(b *testing.B) {
6 |
7 | ts := createMethodEcho()
8 | tc := testURLTemplateCase{Host: ts.URL, Method: "get"}
9 |
10 | for n := 0; n < b.N; n++ {
11 | code := 0
12 | err := New().GET("{{.Host}}/{{.Method}}", tc).Code(&code).Do()
13 | if err != nil {
14 | panic(err)
15 | }
16 | if code != 200 {
17 | panic("code != 200")
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/dataflow/req_url_template_test.go:
--------------------------------------------------------------------------------
1 | package dataflow
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "strings"
7 | "testing"
8 |
9 | "github.com/gin-gonic/gin"
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | type testURLTemplateCase struct {
14 | Host string
15 | Method string
16 | }
17 |
18 | func createMethodEcho() *httptest.Server {
19 | router := func() *gin.Engine {
20 | router := gin.New()
21 |
22 | router.GET("/get", func(c *gin.Context) {
23 | c.String(200, "get")
24 | })
25 |
26 | router.POST("/post", func(c *gin.Context) {
27 | c.String(200, "post")
28 | })
29 |
30 | router.PUT("/put", func(c *gin.Context) {
31 | c.String(200, "put")
32 | })
33 |
34 | router.PATCH("/patch", func(c *gin.Context) {
35 | c.String(200, "patch")
36 | })
37 |
38 | router.OPTIONS("/options", func(c *gin.Context) {
39 | c.String(200, "options")
40 | })
41 |
42 | router.HEAD("/head", func(c *gin.Context) {
43 | c.String(200, "head")
44 | })
45 |
46 | return router
47 | }()
48 |
49 | return httptest.NewServer(http.HandlerFunc(router.ServeHTTP))
50 | }
51 |
52 | func Test_URL_Template(t *testing.T) {
53 | ts := createMethodEcho()
54 | for _, tc := range []testURLTemplateCase{
55 | {Host: ts.URL, Method: "get"},
56 | {Host: ts.URL, Method: "post"},
57 | {Host: ts.URL, Method: "put"},
58 | {Host: ts.URL, Method: "patch"},
59 | {Host: ts.URL, Method: "options"},
60 | {Host: ts.URL, Method: "head"},
61 | } {
62 | body := ""
63 | body2 := ""
64 | code := 0
65 | switch tc.Method {
66 | case "get":
67 | err := New().GET("{{.Host}}/{{.Method}}", tc).Debug(true).BindBody(&body).Code(&code).Do()
68 | assert.NoError(t, err)
69 | case "post":
70 | err := New().POST("{{.Host}}/{{.Method}}", tc).BindBody(&body).Code(&code).Do()
71 | assert.NoError(t, err)
72 | case "put":
73 | err := New().PUT("{{.Host}}/{{.Method}}", tc).BindBody(&body).Code(&code).Do()
74 | assert.NoError(t, err)
75 | case "patch":
76 | err := New().PATCH("{{.Host}}/{{.Method}}", tc).BindBody(&body).Code(&code).Do()
77 | assert.NoError(t, err)
78 | case "options":
79 | err := New().OPTIONS("{{.Host}}/{{.Method}}", tc).BindBody(&body).Code(&code).Do()
80 | assert.NoError(t, err)
81 | case "head":
82 | code := 0
83 | err := New().HEAD("{{.Host}}/{{.Method}}", tc).Debug(true).BindBody(&body).Code(&code).Do()
84 | assert.NoError(t, err)
85 | err = New().SetMethod(strings.ToUpper(tc.Method)).SetURL("{{.Host}}/{{.Method}}", tc).Debug(true).BindBody(&body2).Code(&code).Do()
86 | assert.NoError(t, err)
87 | assert.Equal(t, code, 200)
88 | continue
89 | }
90 | assert.Equal(t, code, 200)
91 |
92 | err := New().SetMethod(strings.ToUpper(tc.Method)).SetURL("{{.Host}}/{{.Method}}", tc).Debug(true).BindBody(&body2).Do()
93 | assert.NoError(t, err)
94 | assert.Equal(t, body, tc.Method)
95 | b := assert.Equal(t, body2, tc.Method)
96 | if !b {
97 | return
98 | }
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/dataflow/response_test.go:
--------------------------------------------------------------------------------
1 | package dataflow
2 |
3 | import (
4 | "io"
5 | "net/http"
6 | "net/http/httptest"
7 | "strings"
8 | "testing"
9 |
10 | "github.com/gin-gonic/gin"
11 | "github.com/guonaihong/gout/core"
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | //本文件主要测试Response接口
16 | // 1.正确的情况
17 | // 2.错误的情况
18 |
19 | type testCase struct {
20 | json bool
21 | reqData string
22 | }
23 |
24 | // response 接口测试mock server
25 | func createResponseMock(t *testing.T) *httptest.Server {
26 | r := gin.New()
27 | r.POST("/", func(c *gin.Context) {
28 | _, err := io.Copy(c.Writer, c.Request.Body)
29 | assert.NoError(t, err)
30 | })
31 |
32 | ts := httptest.NewServer(http.HandlerFunc(r.ServeHTTP))
33 | return ts
34 | }
35 |
36 | // 测试正确的情况
37 | func TestResponse_Ok(t *testing.T) {
38 | ts := createResponseMock(t)
39 | for _, tc := range []testCase{
40 | {json: true, reqData: `{"a":"b"}`},
41 | } {
42 | g := New().POST(ts.URL)
43 | if tc.json {
44 | rsp, err := g.SetJSON(tc.reqData).Response()
45 | assert.NoError(t, err)
46 | if err != nil {
47 | return
48 | }
49 |
50 | var out strings.Builder
51 | _, err = io.Copy(&out, rsp.Body)
52 | assert.NoError(t, err)
53 | if err != nil {
54 | return
55 | }
56 |
57 | assert.NoError(t, rsp.Body.Close())
58 | assert.Equal(t, tc.reqData, out.String())
59 | }
60 | }
61 | }
62 |
63 | // 测试错误的情况
64 | func TestResponse_Fail(t *testing.T) {
65 | port := core.GetNoPortExists()
66 |
67 | rsp, err := New().POST(":" + port).Response()
68 | assert.Nil(t, rsp)
69 | assert.Error(t, err)
70 | }
71 |
--------------------------------------------------------------------------------
/dataflow/validator.go:
--------------------------------------------------------------------------------
1 | package dataflow
2 |
3 | import (
4 | "reflect"
5 | "sync"
6 |
7 | "github.com/go-playground/locales/en"
8 | ut "github.com/go-playground/universal-translator"
9 | en_translations "github.com/go-playground/validator/v10/translations/en"
10 |
11 | "github.com/go-playground/validator/v10"
12 | )
13 |
14 | var valid *defaultValidator = &defaultValidator{}
15 |
16 | type defaultValidator struct {
17 | once sync.Once
18 | validate *validator.Validate
19 | trans ut.Translator
20 | }
21 |
22 | func (v *defaultValidator) ValidateStruct(obj interface{}) error {
23 |
24 | if kindOfData(obj) == reflect.Struct {
25 |
26 | v.lazyinit()
27 |
28 | if err := v.validate.Struct(obj); err != nil {
29 | return err
30 | }
31 | }
32 |
33 | return nil
34 | }
35 |
36 | func (v *defaultValidator) Engine() interface{} {
37 | v.lazyinit()
38 | return v.validate
39 | }
40 |
41 | func (v *defaultValidator) lazyinit() {
42 | v.once.Do(func() {
43 | en := en.New()
44 | uni := ut.New(en, en)
45 |
46 | v.validate = validator.New()
47 | v.validate.SetTagName("valid")
48 | v.trans, _ = uni.GetTranslator("en")
49 | err := en_translations.RegisterDefaultTranslations(v.validate, v.trans)
50 | if err != nil {
51 | panic(err)
52 | }
53 |
54 | v.validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
55 | return "error: " + fld.Name
56 | })
57 |
58 | err = v.validate.RegisterTranslation("required", v.trans, func(ut ut.Translator) error {
59 | return ut.Add("required", "{0} must have a value!", true) // see universal-translator for details
60 | }, func(ut ut.Translator, fe validator.FieldError) string {
61 | t, _ := ut.T("required", fe.Field())
62 |
63 | return t
64 | })
65 | if err != nil {
66 | panic(err)
67 | }
68 |
69 | // add any custom validations etc. here
70 | })
71 | }
72 |
73 | func kindOfData(data interface{}) reflect.Kind {
74 |
75 | value := reflect.ValueOf(data)
76 | valueType := value.Kind()
77 |
78 | if valueType == reflect.Ptr {
79 | valueType = value.Elem().Kind()
80 | }
81 | return valueType
82 | }
83 |
--------------------------------------------------------------------------------
/debug/api.go:
--------------------------------------------------------------------------------
1 | package debug
2 |
3 | import (
4 | "io"
5 | "os"
6 | )
7 |
8 | // 默认,不需要调用
9 | func DefaultDebug(o *Options) {
10 | o.Color = true
11 | o.Debug = true
12 | o.Write = os.Stdout
13 | }
14 |
15 | // NoColor Turn off color highlight debug mode
16 | func NoColor() Apply {
17 | return Func(func(o *Options) {
18 | o.Color = false
19 | o.Debug = true
20 | o.Trace = true
21 | o.Write = os.Stdout
22 | })
23 | }
24 |
25 | type file struct{ fileName string }
26 |
27 | func (f *file) Write(p []byte) (n int, err error) {
28 | fd, err := os.OpenFile(f.fileName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
29 | if err != nil {
30 | return
31 | }
32 |
33 | defer fd.Close()
34 | return fd.Write(p)
35 | }
36 |
37 | // 第一个参数是要写入的文件名, 第二个参数是否颜色高亮
38 | func ToFile(fileName string, color bool) Apply {
39 | return Func(func(o *Options) {
40 | o.Color = color
41 | o.Debug = true
42 | o.Trace = false
43 | o.Write = &file{fileName: fileName}
44 | })
45 | }
46 |
47 | // 第一个参数是要写入的io.Writer对象, 第二个参数是否颜色高亮
48 | func ToWriter(w io.Writer, color bool) Apply {
49 | return Func(func(o *Options) {
50 | o.Color = color
51 | o.Debug = true
52 | o.Trace = false
53 | o.Write = w
54 | })
55 | }
56 |
57 | func OnlyTraceFlag() Apply {
58 | return Func(func(o *Options) {
59 | o.Trace = false
60 | })
61 | }
62 |
63 | // trace信息格式化成json输出至标准输出
64 | func TraceJSON() Apply {
65 | return TraceJSONToWriter(os.Stdout)
66 | }
67 |
68 | // trace信息格式化成json输出至w
69 | func TraceJSONToWriter(w io.Writer) Apply {
70 | return Func(func(o *Options) {
71 | o.Color = false
72 | o.Debug = false
73 | o.Trace = true
74 | o.FormatTraceJSON = true
75 | o.Write = w
76 | })
77 | }
78 |
79 | // 打开Trace()
80 | func Trace() Apply {
81 | return Func(func(o *Options) {
82 | o.Color = true
83 | o.Trace = true
84 | o.Write = os.Stdout
85 | })
86 | }
87 |
--------------------------------------------------------------------------------
/debug/debug.go:
--------------------------------------------------------------------------------
1 | package debug
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "io"
7 | "io/ioutil"
8 | "net/http"
9 | "os"
10 | "strings"
11 |
12 | "github.com/guonaihong/gout/color"
13 | )
14 |
15 | // ToBodyType Returns the http body type,
16 | // which mainly affects color highlighting
17 | func ToBodyType(s string) color.BodyType {
18 | switch strings.ToLower(s) {
19 | case "json":
20 | return color.JSONType
21 | case "xml":
22 | // return color.XmlType //TODO open
23 | case "yaml":
24 | // return color.YamlType //TODO open
25 | }
26 |
27 | return color.TxtType
28 | }
29 |
30 | // Options Debug mode core data structure
31 | type Options struct {
32 | EscapeHTML bool
33 | Write io.Writer
34 | Debug bool
35 | Color bool
36 | Trace bool
37 | FormatTraceJSON bool
38 | ReqBodyType string
39 | RspBodyType string
40 | TraceInfo
41 | }
42 |
43 | // Apply is an interface for operating Options
44 | type Apply interface {
45 | Apply(*Options)
46 | }
47 |
48 | // Func Apply is a function that manipulates core data structures
49 | type Func func(*Options)
50 |
51 | // Apply is an interface for operating Options
52 | func (f Func) Apply(o *Options) {
53 | f(o)
54 | }
55 |
56 | func (do *Options) ResetBodyAndPrint(req *http.Request, resp *http.Response) error {
57 | all, err := ioutil.ReadAll(resp.Body)
58 | if err != nil {
59 | return err
60 | }
61 |
62 | resp.Body.Close()
63 |
64 | resp.Body = ioutil.NopCloser(bytes.NewReader(all))
65 | err = do.debugPrint(req, resp)
66 | resp.Body = ioutil.NopCloser(bytes.NewReader(all))
67 | return err
68 | }
69 |
70 | func (do *Options) debugPrint(req *http.Request, rsp *http.Response) error {
71 | if t := req.Header.Get("Content-Type"); len(t) > 0 && strings.Contains(t, "json") {
72 | do.ReqBodyType = "json"
73 | }
74 |
75 | if t := rsp.Header.Get("Content-Type"); len(t) != 0 &&
76 | strings.Contains(t, "application/json") {
77 | do.RspBodyType = "json"
78 | }
79 |
80 | if do.Write == nil {
81 | do.Write = os.Stdout
82 | }
83 |
84 | var w io.Writer = do.Write
85 |
86 | cl := color.New(do.Color)
87 | path := "/"
88 | if len(req.URL.RequestURI()) > 0 {
89 | path = req.URL.RequestURI()
90 | }
91 |
92 | fmt.Fprintf(w, "> %s %s %s\r\n", req.Method, path, req.Proto)
93 |
94 | // write request header
95 | for k, v := range req.Header {
96 | fmt.Fprintf(w, "> %s: %s\r\n", cl.Spurple(k),
97 | cl.Sblue(strings.Join(v, ",")))
98 | }
99 |
100 | fmt.Fprint(w, ">\r\n")
101 | fmt.Fprint(w, "\n")
102 |
103 | // write req body
104 | if req.GetBody != nil {
105 | b, err := req.GetBody()
106 | if err != nil {
107 | return err
108 | }
109 |
110 | r := io.Reader(b)
111 | format := color.NewFormatEncoder(r, do.Color, ToBodyType(do.ReqBodyType), do.EscapeHTML)
112 | if format != nil {
113 | r = format
114 | }
115 |
116 | if _, err := io.Copy(w, r); err != nil {
117 | return err
118 | }
119 |
120 | fmt.Fprintf(w, "\r\n\r\n")
121 | }
122 |
123 | fmt.Fprintf(w, "< %s %s\r\n", rsp.Proto, rsp.Status)
124 | for k, v := range rsp.Header {
125 | fmt.Fprintf(w, "< %s: %s\r\n", cl.Spurple(k), cl.Sblue(strings.Join(v, ",")))
126 | }
127 |
128 | fmt.Fprintf(w, "\r\n\r\n")
129 | // write rsp body
130 | r := io.Reader(rsp.Body)
131 | format := color.NewFormatEncoder(r, do.Color, ToBodyType(do.RspBodyType), do.EscapeHTML)
132 | if format != nil {
133 | r = format
134 | }
135 | _, err := io.Copy(w, r)
136 |
137 | fmt.Fprintf(w, "\r\n\r\n")
138 |
139 | return err
140 | }
141 |
--------------------------------------------------------------------------------
/debug/debug_test.go:
--------------------------------------------------------------------------------
1 | package debug
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "fmt"
7 | "io"
8 | "io/ioutil"
9 | "net/http"
10 | "testing"
11 |
12 | "github.com/guonaihong/gout/color"
13 | "github.com/guonaihong/gout/core"
14 | "github.com/stretchr/testify/assert"
15 | )
16 |
17 | // 测试resetBodyAndPrint出错
18 | func TestResetBodyAndPrintFail(t *testing.T) {
19 |
20 | test := []func() (*http.Request, *http.Response){
21 | func() (*http.Request, *http.Response) {
22 | // GetBody为空的情况
23 | req, _ := http.NewRequest("GET", "/", nil)
24 | rsp := http.Response{}
25 | req.GetBody = func() (io.ReadCloser, error) { return nil, errors.New("fail") }
26 | rsp.Body = ioutil.NopCloser(bytes.NewReader(nil))
27 | return req, &rsp
28 | },
29 | func() (*http.Request, *http.Response) {
30 | // GetBody不为空的情况, 但是GetBody第二参数返回错误
31 | req, _ := http.NewRequest("GET", "/", nil)
32 | rsp := http.Response{}
33 | req.GetBody = func() (io.ReadCloser, error) { return &core.ReadCloseFail{}, errors.New("fail") }
34 | rsp.Body = ioutil.NopCloser(bytes.NewReader(nil))
35 | return req, &rsp
36 | },
37 | func() (*http.Request, *http.Response) {
38 | // GetBody不为空的情况, 但是io.Copy时候返回错误
39 | req, _ := http.NewRequest("GET", "/", nil)
40 | rsp := http.Response{}
41 | req.GetBody = func() (io.ReadCloser, error) { return &core.ReadCloseFail{}, nil }
42 | rsp.Body = ioutil.NopCloser(bytes.NewReader(nil))
43 | return req, &rsp
44 | },
45 | func() (*http.Request, *http.Response) {
46 | // rsp.Body出错的情况
47 | req, _ := http.NewRequest("GET", "/", &bytes.Buffer{})
48 | req.GetBody = func() (io.ReadCloser, error) { return ioutil.NopCloser(bytes.NewReader(nil)), nil }
49 | rsp := http.Response{}
50 | rsp.Body = ioutil.NopCloser(&core.ReadCloseFail{})
51 | return req, &rsp
52 | },
53 | }
54 |
55 | do := Options{}
56 | for i, c := range test {
57 | req, rsp := c()
58 | err := do.ResetBodyAndPrint(req, rsp)
59 | assert.Error(t, err, fmt.Sprintf("test index = %d", i))
60 | }
61 | }
62 |
63 | func TestResetBodyAndPrint(t *testing.T) {
64 |
65 | test := []func() (*http.Request, *http.Response){
66 | func() (*http.Request, *http.Response) {
67 | req, _ := http.NewRequest("GET", "/", nil)
68 | req.Header.Add("reqHeader1", "reqHeaderValue1")
69 | req.Header.Add("reqHeader2", "reqHeaderValue2")
70 | req.GetBody = func() (io.ReadCloser, error) { return ioutil.NopCloser(bytes.NewReader(nil)), nil }
71 |
72 | rsp := http.Response{}
73 | rsp.Body = ioutil.NopCloser(bytes.NewReader(nil))
74 | rsp.Header = http.Header{}
75 | rsp.Header.Add("rspHeader1", "rspHeaderValue1")
76 | rsp.Header.Add("rspHeader2", "rspHeaderValue2")
77 | return req, &rsp
78 | },
79 | }
80 |
81 | do := Options{}
82 | for i, c := range test {
83 | req, rsp := c()
84 | err := do.ResetBodyAndPrint(req, rsp)
85 | assert.NoError(t, err, fmt.Sprintf("test index = %d", i))
86 | }
87 | }
88 |
89 | func TestDebug_ToBodyType(t *testing.T) {
90 | type bodyTest struct {
91 | in string
92 | need color.BodyType
93 | }
94 |
95 | tests := []bodyTest{
96 | {"json", color.JSONType},
97 | {"", color.TxtType},
98 | }
99 |
100 | for _, test := range tests {
101 | got := ToBodyType(test.in)
102 | assert.Equal(t, test.need, got)
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/decode/body.go:
--------------------------------------------------------------------------------
1 | package decode
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "io/ioutil"
7 | "reflect"
8 |
9 | "github.com/guonaihong/gout/core"
10 | )
11 |
12 | // BodyDecode body decoder structure
13 | type BodyDecode struct {
14 | obj interface{}
15 | }
16 |
17 | // NewBodyDecode create a new body decoder
18 | func NewBodyDecode(obj interface{}) Decoder {
19 | if obj == nil {
20 | return nil
21 | }
22 |
23 | return &BodyDecode{obj: obj}
24 | }
25 |
26 | var convertBodyFunc = map[reflect.Kind]convert{
27 | reflect.Uint: {bitSize: 0, cb: setUintField},
28 | reflect.Uint8: {bitSize: 8, cb: setUintField},
29 | reflect.Uint16: {bitSize: 16, cb: setUintField},
30 | reflect.Uint32: {bitSize: 32, cb: setUintField},
31 | reflect.Uint64: {bitSize: 64, cb: setUintField},
32 | reflect.Int: {bitSize: 0, cb: setIntField},
33 | reflect.Int8: {bitSize: 8, cb: setIntField},
34 | reflect.Int16: {bitSize: 16, cb: setIntField},
35 | reflect.Int32: {bitSize: 32, cb: setIntField},
36 | reflect.Int64: {bitSize: 64, cb: setIntDurationField},
37 | reflect.Float32: {bitSize: 32, cb: setFloatField},
38 | reflect.Float64: {bitSize: 64, cb: setFloatField},
39 | }
40 |
41 | // Decode body decoder
42 | func (b *BodyDecode) Decode(r io.Reader) error {
43 | return Body(r, b.obj)
44 | }
45 |
46 | // Decode obj
47 | func (b *BodyDecode) Value() interface{} {
48 | return b.obj
49 | }
50 |
51 | // Body body decoder
52 | func Body(r io.Reader, obj interface{}) error {
53 | if w, ok := obj.(io.Writer); ok {
54 | _, err := io.Copy(w, r)
55 | return err
56 | }
57 |
58 | all, err := ioutil.ReadAll(r)
59 | if err != nil {
60 | return err
61 | }
62 |
63 | value := core.LoopElem(reflect.ValueOf(obj))
64 |
65 | if value.Kind() == reflect.String {
66 | value.SetString(core.BytesToString(all))
67 | return nil
68 | }
69 |
70 | if _, ok := value.Interface().([]byte); ok {
71 | value.SetBytes(all)
72 | return nil
73 | }
74 |
75 | fn, ok := convertBodyFunc[value.Kind()]
76 | if ok {
77 | return fn.cb(core.BytesToString(all), fn.bitSize, emptyField, value)
78 | }
79 |
80 | return fmt.Errorf("type (%T) %s", value, core.ErrUnknownType)
81 | }
82 |
--------------------------------------------------------------------------------
/decode/body_test.go:
--------------------------------------------------------------------------------
1 | package decode
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "testing"
7 | "time"
8 |
9 | "github.com/guonaihong/gout/core"
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | func TestNewBodyDecode(t *testing.T) {
14 | b := NewBodyDecode(nil)
15 | assert.Nil(t, b)
16 |
17 | b = NewBodyDecode(new(int))
18 | assert.NotNil(t, b)
19 | }
20 |
21 | type bodyTest struct {
22 | r *bytes.Buffer
23 | need interface{}
24 | got interface{}
25 | }
26 |
27 | type myFailRead struct{}
28 |
29 | func (m *myFailRead) Read(p []byte) (n int, err error) {
30 | return 0, errors.New("fail")
31 |
32 | }
33 | func TestDecodeBodyFail(t *testing.T) {
34 | var tm time.Time
35 | err := Body(bytes.NewBufferString("1"), &tm)
36 | assert.Error(t, err)
37 |
38 | err = Body(&myFailRead{}, new(int))
39 | assert.Error(t, err)
40 | }
41 |
42 | func TestDecode(t *testing.T) {
43 | testDecodeBody(t, "TestDecode")
44 | }
45 |
46 | func TestDecodeBody(t *testing.T) {
47 | testDecodeBody(t, "TestDecodeBody")
48 | }
49 |
50 | func testDecodeBody(t *testing.T, funcName string) {
51 | tests := []bodyTest{
52 | {r: bytes.NewBufferString("1"), need: core.NewPtrVal(int(1)), got: new(int)},
53 | {r: bytes.NewBufferString("2"), need: core.NewPtrVal(int8(2)), got: new(int8)},
54 | {r: bytes.NewBufferString("3"), need: core.NewPtrVal(int16(3)), got: new(int16)},
55 | {r: bytes.NewBufferString("4"), need: core.NewPtrVal(int32(4)), got: new(int32)},
56 | {r: bytes.NewBufferString("5"), need: core.NewPtrVal(int64(5)), got: new(int64)},
57 |
58 | {r: bytes.NewBufferString("11"), need: core.NewPtrVal(uint(11)), got: new(uint)},
59 | {r: bytes.NewBufferString("12"), need: core.NewPtrVal(uint8(12)), got: new(uint8)},
60 | {r: bytes.NewBufferString("13"), need: core.NewPtrVal(uint16(13)), got: new(uint16)},
61 | {r: bytes.NewBufferString("14"), need: core.NewPtrVal(uint32(14)), got: new(uint32)},
62 | {r: bytes.NewBufferString("15"), need: core.NewPtrVal(uint64(15)), got: new(uint64)},
63 |
64 | {r: bytes.NewBufferString("3.14"), need: core.NewPtrVal(float32(3.14)), got: new(float32)},
65 | {r: bytes.NewBufferString("3.1415"), need: core.NewPtrVal(float64(3.1415)), got: new(float64)},
66 |
67 | {r: bytes.NewBufferString("test string"), need: core.NewPtrVal("test string"), got: new(string)},
68 | {r: bytes.NewBuffer([]byte("test bytes")), need: core.NewPtrVal([]byte("test bytes")), got: new([]byte)},
69 |
70 | // test io.Writer
71 | {r: bytes.NewBuffer([]byte("test buffer")), need: bytes.NewBufferString("test buffer"), got: bytes.NewBufferString("")},
72 | }
73 |
74 | for _, v := range tests {
75 | if funcName == "TestDecode" {
76 | body := NewBodyDecode(v.got)
77 | assert.NoError(t, body.Decode(v.r))
78 | assert.Equal(t, v.got, body.Value())
79 | } else {
80 | assert.NoError(t, Body(v.r, v.got))
81 | }
82 | assert.Equal(t, v.need, v.got)
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/decode/decode.go:
--------------------------------------------------------------------------------
1 | package decode
2 |
3 | import (
4 | "net/http"
5 | )
6 |
7 | // Decoder Decoder interface
8 | type Decoder2 interface {
9 | Decode(*http.Request, interface{})
10 | }
11 |
12 | var (
13 | // Header is the http header decoder
14 | Header = headerDecode{}
15 | )
16 |
--------------------------------------------------------------------------------
/decode/decoder.go:
--------------------------------------------------------------------------------
1 | package decode
2 |
3 | import (
4 | "io"
5 | )
6 |
7 | // Decoder is the decoding interface
8 | type Decoder interface {
9 | Decode(r io.Reader) error
10 | Value() interface{}
11 | }
12 |
--------------------------------------------------------------------------------
/decode/header.go:
--------------------------------------------------------------------------------
1 | package decode
2 |
3 | import (
4 | "errors"
5 | "net/http"
6 | "net/textproto"
7 | "reflect"
8 | )
9 |
10 | var ErrWrongParam = errors.New("Wrong parameter")
11 |
12 | type headerDecode struct{}
13 |
14 | func (h *headerDecode) Decode(rsp *http.Response, obj interface{}) error {
15 | if obj == nil {
16 | return ErrWrongParam
17 | }
18 |
19 | // 如果是
20 | // http.Header
21 | // *http.Header
22 | // 等类型, 直接把rsp.Header的字段都拷贝下
23 | switch value := obj.(type) {
24 | case http.Header:
25 | for k, v := range rsp.Header {
26 | value[k] = v
27 | }
28 | return nil
29 | case *http.Header:
30 | *value = rsp.Header.Clone()
31 | return nil
32 | }
33 |
34 | return decode(headerSet(rsp.Header), obj, "header")
35 | }
36 |
37 | type headerSet map[string][]string
38 |
39 | var _ setter = headerSet(nil)
40 |
41 | func (h headerSet) Set(value reflect.Value, sf reflect.StructField, tagValue string) error {
42 | return setForm(h, value, sf, textproto.CanonicalMIMEHeaderKey(tagValue))
43 | }
44 |
--------------------------------------------------------------------------------
/decode/header_test.go:
--------------------------------------------------------------------------------
1 | package decode
2 |
3 | import (
4 | "github.com/stretchr/testify/assert"
5 | "net/http"
6 | "testing"
7 | )
8 |
9 | type rspHeader struct {
10 | Date string `header:"date"`
11 | Connection string `header:"connection"`
12 | ContentLength string `header:"Content-Length"`
13 | ContentType string `header:"Content-Type"`
14 | SID []string `header:"sid"`
15 | Max int `header:"max"`
16 | Rate float64 `header:"rate"`
17 | }
18 |
19 | type headerTest struct {
20 | in *http.Response
21 | need interface{}
22 | got interface{}
23 | }
24 |
25 | func Test_Header_Decode_Err(t *testing.T) {
26 | err := (&headerDecode{}).Decode(nil, nil)
27 | assert.Error(t, err)
28 | }
29 |
30 | func Test_Header_Decode(t *testing.T) {
31 |
32 | tests := []headerTest{
33 | {
34 | in: &http.Response{
35 | Header: http.Header{
36 | "Date": []string{"1234"},
37 | "Connection": []string{"close"},
38 | "Content-Length": []string{"1234"},
39 | "Content-Type": []string{"text"},
40 | "Sid": []string{"sid1", "sid2"},
41 | "Max": []string{"1000"},
42 | "Rate": []string{"16000"},
43 | },
44 | },
45 | need: &rspHeader{
46 | Date: "1234",
47 | Connection: "close",
48 | ContentLength: "1234",
49 | ContentType: "text",
50 | SID: []string{"sid1", "sid2"},
51 | Max: 1000,
52 | Rate: 16000,
53 | },
54 | got: &rspHeader{},
55 | },
56 | {
57 | in: &http.Response{
58 | Header: http.Header{
59 | "Date": []string{"1234"},
60 | "Connection": []string{"close"},
61 | "Content-Length": []string{"1234"},
62 | "Content-Type": []string{"text"},
63 | "Sid": []string{"sid1", "sid2"},
64 | "Max": []string{"1000"},
65 | "Rate": []string{"16000"},
66 | },
67 | },
68 | need: http.Header{
69 | "Date": []string{"1234"},
70 | "Connection": []string{"close"},
71 | "Content-Length": []string{"1234"},
72 | "Content-Type": []string{"text"},
73 | "Sid": []string{"sid1", "sid2"},
74 | "Max": []string{"1000"},
75 | "Rate": []string{"16000"},
76 | },
77 | got: http.Header{},
78 | },
79 | {
80 | in: &http.Response{
81 | Header: http.Header{
82 | "Date": []string{"1234"},
83 | "Connection": []string{"close"},
84 | "Content-Length": []string{"1234"},
85 | "Content-Type": []string{"text"},
86 | "Sid": []string{"sid1", "sid2"},
87 | "Max": []string{"1000"},
88 | "Rate": []string{"16000"},
89 | },
90 | },
91 | need: &http.Header{
92 | "Date": []string{"1234"},
93 | "Connection": []string{"close"},
94 | "Content-Length": []string{"1234"},
95 | "Content-Type": []string{"text"},
96 | "Sid": []string{"sid1", "sid2"},
97 | "Max": []string{"1000"},
98 | "Rate": []string{"16000"},
99 | },
100 | got: &http.Header{},
101 | },
102 | }
103 |
104 | for _, v := range tests {
105 | err := (&headerDecode{}).Decode(v.in, v.got)
106 | assert.NoError(t, err)
107 | if err != nil {
108 | return
109 | }
110 |
111 | assert.Equal(t, v.got, v.need)
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/decode/json.go:
--------------------------------------------------------------------------------
1 | package decode
2 |
3 | import (
4 | "github.com/guonaihong/gout/json"
5 | "io"
6 | )
7 |
8 | // JSONDecode json decoder core data structure
9 | type JSONDecode struct {
10 | obj interface{}
11 | }
12 |
13 | // NewJSONDecode create a new json decoder
14 | func NewJSONDecode(obj interface{}) Decoder {
15 | if obj == nil {
16 | return nil
17 | }
18 | return &JSONDecode{obj: obj}
19 | }
20 |
21 | // Decode json decoder
22 | func (j *JSONDecode) Decode(r io.Reader) error {
23 | decode := json.NewDecoder(r)
24 | return decode.Decode(j.obj)
25 | }
26 |
27 | // Decode obj
28 | func (j *JSONDecode) Value() interface{} {
29 | return j.obj
30 | }
31 |
32 | // JSON json decoder
33 | func JSON(r io.Reader, obj interface{}) error {
34 | decode := json.NewDecoder(r)
35 | return decode.Decode(obj)
36 | }
37 |
--------------------------------------------------------------------------------
/decode/json_test.go:
--------------------------------------------------------------------------------
1 | package decode
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestNewJSONDecode(t *testing.T) {
11 | j := NewJSONDecode(nil)
12 | assert.Nil(t, j)
13 | }
14 |
15 | type jsonTest struct {
16 | r *bytes.Buffer
17 | need interface{}
18 | got interface{}
19 | }
20 |
21 | func testDecodeJSON(t *testing.T, funcName string) {
22 | type jsonVal struct {
23 | A string `json:"a"`
24 | B string `json:"b"`
25 | }
26 |
27 | tests := []jsonTest{
28 | {r: bytes.NewBufferString(`{"a":"a", "b":"b"}`), need: &jsonVal{A: "a", B: "b"}, got: &jsonVal{}},
29 | {r: bytes.NewBufferString(`{"a":"aaa", "b":"bbb"}`), need: &jsonVal{A: "aaa", B: "bbb"}, got: &jsonVal{}},
30 | }
31 |
32 | for _, v := range tests {
33 | if funcName == "TestDecode" {
34 | j := NewJSONDecode(v.got)
35 | assert.NoError(t, j.Decode(v.r))
36 | assert.Equal(t, v.got, j.Value())
37 | } else {
38 | assert.NoError(t, JSON(v.r, v.got))
39 | }
40 | assert.Equal(t, v.need, v.got)
41 | }
42 | }
43 |
44 | func Test_json_DecodeJSON(t *testing.T) {
45 | testDecodeJSON(t, "TestDecodeJSON")
46 | }
47 |
48 | func Test_json_Decode(t *testing.T) {
49 | testDecodeJSON(t, "TestDecode")
50 | }
51 |
--------------------------------------------------------------------------------
/decode/xml.go:
--------------------------------------------------------------------------------
1 | package decode
2 |
3 | import (
4 | "encoding/xml"
5 | "io"
6 | )
7 |
8 | // XMLDecode xml decoder core data structure
9 | type XMLDecode struct {
10 | obj interface{}
11 | }
12 |
13 | // NewXMLDecode create a new xml decoder
14 | func NewXMLDecode(obj interface{}) Decoder {
15 | if obj == nil {
16 | return nil
17 | }
18 | return &XMLDecode{obj: obj}
19 | }
20 |
21 | // Decode xml decoder
22 | func (x *XMLDecode) Decode(r io.Reader) error {
23 | decode := xml.NewDecoder(r)
24 | return decode.Decode(x.obj)
25 | }
26 |
27 | // Decode object
28 | func (x *XMLDecode) Value() interface{} {
29 | return x.obj
30 | }
31 |
32 | // XML xml decoder
33 | func XML(r io.Reader, obj interface{}) error {
34 | decode := xml.NewDecoder(r)
35 | return decode.Decode(obj)
36 | }
37 |
--------------------------------------------------------------------------------
/decode/xml_test.go:
--------------------------------------------------------------------------------
1 | package decode
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestNewXMLDecode(t *testing.T) {
11 | x := NewXMLDecode(nil)
12 | assert.Nil(t, x)
13 | }
14 |
15 | type xmlTest struct {
16 | r *bytes.Buffer
17 | need interface{}
18 | got interface{}
19 | }
20 |
21 | func testDecodeXML(t *testing.T, funcName string) {
22 | type xmlVal struct {
23 | A string `xml:"a"`
24 | B string `xml:"b"`
25 | }
26 |
27 | tests := []xmlTest{
28 | {r: bytes.NewBufferString(`ab`), need: &xmlVal{A: "a", B: "b"}, got: &xmlVal{}},
29 | {r: bytes.NewBufferString(`aaabbb`), need: &xmlVal{A: "aaa", B: "bbb"}, got: &xmlVal{}},
30 | }
31 |
32 | for _, v := range tests {
33 | if funcName == "TestDecode" {
34 | x := NewXMLDecode(v.got)
35 | assert.NoError(t, x.Decode(v.r))
36 | assert.Equal(t, v.got, x.Value())
37 | } else {
38 | assert.NoError(t, XML(v.r, v.got))
39 | }
40 | assert.Equal(t, v.need, v.got)
41 | }
42 | }
43 |
44 | func Test_xml_DecodeXML(t *testing.T) {
45 | testDecodeXML(t, "TestDecodeXML")
46 | }
47 |
48 | func Test_xml_Decode(t *testing.T) {
49 | testDecodeXML(t, "TestDecode")
50 | }
51 |
--------------------------------------------------------------------------------
/decode/yaml.go:
--------------------------------------------------------------------------------
1 | package decode
2 |
3 | import (
4 | "io"
5 |
6 | "gopkg.in/yaml.v2"
7 | )
8 |
9 | // YAMLDecode yaml decoder core data structure
10 | type YAMLDecode struct {
11 | obj interface{}
12 | }
13 |
14 | // NewYAMLDecode create a new yaml decoder
15 | func NewYAMLDecode(obj interface{}) Decoder {
16 | if obj == nil {
17 | return nil
18 | }
19 | return &YAMLDecode{obj: obj}
20 | }
21 |
22 | // Decode yaml decoder
23 | func (y *YAMLDecode) Decode(r io.Reader) error {
24 | decode := yaml.NewDecoder(r)
25 | return decode.Decode(y.obj)
26 | }
27 |
28 | // Decode obj
29 | func (y *YAMLDecode) Value() interface{} {
30 | return y.obj
31 | }
32 |
33 | // YAML yaml decoder
34 | func YAML(r io.Reader, obj interface{}) error {
35 | decode := yaml.NewDecoder(r)
36 | return decode.Decode(obj)
37 | }
38 |
--------------------------------------------------------------------------------
/decode/yaml_test.go:
--------------------------------------------------------------------------------
1 | package decode
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | )
9 |
10 | func TestNewYAMLDecode(t *testing.T) {
11 | x := NewYAMLDecode(nil)
12 | assert.Nil(t, x)
13 | }
14 |
15 | type yamlTest struct {
16 | r *bytes.Buffer
17 | need interface{}
18 | got interface{}
19 | }
20 |
21 | func testDecodeYAML(t *testing.T, funcName string) {
22 | type yamlVal struct {
23 | A string `yaml:"a"`
24 | B string `yaml:"b"`
25 | }
26 |
27 | tests := []yamlTest{
28 | {r: bytes.NewBufferString(`a: a
29 | b: b
30 | `), need: &yamlVal{A: "a", B: "b"}, got: &yamlVal{}},
31 | {r: bytes.NewBufferString(`a: aaa
32 | b: bbb
33 | `), need: &yamlVal{A: "aaa", B: "bbb"}, got: &yamlVal{}},
34 | }
35 |
36 | for _, v := range tests {
37 | if funcName == "TestDecode" {
38 | x := NewYAMLDecode(v.got)
39 | assert.NoError(t, x.Decode(v.r))
40 | assert.Equal(t, v.got, x.Value())
41 | } else {
42 | assert.NoError(t, YAML(v.r, v.got))
43 | }
44 | assert.Equal(t, v.need, v.got)
45 | }
46 | }
47 |
48 | func Test_yaml_YAML(t *testing.T) {
49 | testDecodeYAML(t, "TestYAML")
50 | }
51 |
52 | func Test_yaml_Decode(t *testing.T) {
53 | testDecodeYAML(t, "TestDecode")
54 | }
55 |
--------------------------------------------------------------------------------
/encode/body.go:
--------------------------------------------------------------------------------
1 | package encode
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "reflect"
7 |
8 | "github.com/guonaihong/gout/core"
9 | "github.com/guonaihong/gout/encoder"
10 | )
11 |
12 | // BodyEncode body encoder structure
13 | type BodyEncode struct {
14 | obj interface{}
15 | }
16 |
17 | // NewBodyEncode create a new body encoder
18 | func NewBodyEncode(obj interface{}) encoder.Encoder {
19 | if obj == nil {
20 | return nil
21 | }
22 |
23 | return &BodyEncode{obj: obj}
24 | }
25 |
26 | // Encode Add Encoder core function, used to set io.Writer into the http body
27 | func (b *BodyEncode) Encode(w io.Writer) error {
28 | if r, ok := b.obj.(io.Reader); ok {
29 | _, err := io.Copy(w, r)
30 | return err
31 | }
32 |
33 | val := reflect.ValueOf(b.obj)
34 | val = core.LoopElem(val)
35 |
36 | switch t := val.Kind(); t {
37 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
38 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
39 | case reflect.Float32, reflect.Float64:
40 | case reflect.String:
41 | default:
42 | if _, ok := val.Interface().([]byte); !ok {
43 | return fmt.Errorf("type(%T) %s",
44 | b.obj,
45 | core.ErrUnknownType)
46 | }
47 | }
48 |
49 | v := valToStr(val, emptyField)
50 | _, err := io.WriteString(w, v)
51 | return err
52 | }
53 |
54 | // Name http body Encoder name
55 | func (b *BodyEncode) Name() string {
56 | return "body"
57 | }
58 |
--------------------------------------------------------------------------------
/encode/body_test.go:
--------------------------------------------------------------------------------
1 | package encode
2 |
3 | import (
4 | "bytes"
5 | "strings"
6 | "testing"
7 | "time"
8 |
9 | "github.com/guonaihong/gout/core"
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | func TestNewBodyEncode(t *testing.T) {
14 | b := NewBodyEncode(nil)
15 | assert.Nil(t, b)
16 | }
17 |
18 | type bodyTest struct {
19 | w *strings.Builder
20 | set interface{}
21 | need string
22 | }
23 |
24 | func Test_body_EncodeFail(t *testing.T) {
25 | b := NewBodyEncode(&time.Time{})
26 | err := b.Encode(bytes.NewBufferString("hello world"))
27 | assert.Error(t, err)
28 | }
29 |
30 | func Test_body_Encode(t *testing.T) {
31 |
32 | tests := []bodyTest{
33 | {w: &strings.Builder{}, set: int(1), need: "1"},
34 | {w: &strings.Builder{}, set: int8(2), need: "2"},
35 | {w: &strings.Builder{}, set: int16(3), need: "3"},
36 | {w: &strings.Builder{}, set: int32(4), need: "4"},
37 | {w: &strings.Builder{}, set: int64(5), need: "5"},
38 |
39 | {w: &strings.Builder{}, set: uint(11), need: "11"},
40 | {w: &strings.Builder{}, set: uint8(12), need: "12"},
41 | {w: &strings.Builder{}, set: uint16(13), need: "13"},
42 | {w: &strings.Builder{}, set: uint32(14), need: "14"},
43 | {w: &strings.Builder{}, set: uint64(15), need: "15"},
44 | {w: &strings.Builder{}, set: []byte("test bytes"), need: "test bytes"},
45 | {w: &strings.Builder{}, set: "test string", need: "test string"},
46 | {w: &strings.Builder{}, set: int(1), need: "1"},
47 | {w: &strings.Builder{}, set: core.NewPtrVal(1010), need: "1010"},
48 |
49 | // test io.Reader
50 | {w: &strings.Builder{}, set: bytes.NewBufferString("set body:hello world"), need: "set body:hello world"},
51 | }
52 |
53 | for _, v := range tests {
54 | b := NewBodyEncode(v.set)
55 | assert.NoError(t, b.Encode(v.w))
56 | assert.Equal(t, v.w.String(), v.need)
57 | }
58 | }
59 |
60 | func Test_body_Name(t *testing.T) {
61 | assert.Equal(t, NewBodyEncode("").Name(), "body")
62 | }
63 |
--------------------------------------------------------------------------------
/encode/header.go:
--------------------------------------------------------------------------------
1 | package encode
2 |
3 | import (
4 | "net/http"
5 | "reflect"
6 | )
7 |
8 | var _ Adder = (*HeaderEncode)(nil)
9 |
10 | // HeaderEncode http header encoder structure
11 | type HeaderEncode struct {
12 | r *http.Request
13 | rawHeader bool
14 | }
15 |
16 | // NewHeaderEncode create a new http header encoder
17 | func NewHeaderEncode(req *http.Request, rawHeader bool) *HeaderEncode {
18 | return &HeaderEncode{r: req, rawHeader: rawHeader}
19 | }
20 |
21 | // Add Encoder core function, used to set each key / value into the http header
22 | func (h *HeaderEncode) Add(key string, v reflect.Value, sf reflect.StructField) error {
23 | value := valToStr(v, sf)
24 | if !h.rawHeader {
25 | h.r.Header.Add(key, value)
26 | } else {
27 |
28 | h.r.Header[key] = append(h.r.Header[key], value)
29 | }
30 | return nil
31 | }
32 |
33 | // Name header Encoder name
34 | func (h *HeaderEncode) Name() string {
35 | return "header"
36 | }
37 |
--------------------------------------------------------------------------------
/encode/header_test.go:
--------------------------------------------------------------------------------
1 | package encode
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | type testH map[string]interface{}
12 |
13 | func TestHeaderStringSlice(t *testing.T) {
14 | req, _ := http.NewRequest("GET", "/", nil)
15 |
16 | err := Encode([]string{
17 | "header1", "value1",
18 | "header2", "value2",
19 | "header3", "value3",
20 | }, NewHeaderEncode(req, false))
21 |
22 | assert.NoError(t, err)
23 |
24 | needVal := []string{"value1", "value2", "value3"}
25 |
26 | for k, v := range needVal {
27 | s := fmt.Sprintf("header%d", k+1)
28 | assert.Equal(t, req.Header.Get(s), v)
29 | }
30 | }
31 |
32 | func TestHeaderMap(t *testing.T) {
33 |
34 | req, _ := http.NewRequest("GET", "/", nil)
35 |
36 | err := Encode(testH{
37 | "header1": 1,
38 | "header2": "value2",
39 | "header3": 3.14,
40 | }, NewHeaderEncode(req, false))
41 |
42 | assert.NoError(t, err)
43 |
44 | needVal := []string{"1", "value2", "3.14"}
45 |
46 | for k, v := range needVal {
47 | s := fmt.Sprintf("header%d", k+1)
48 | assert.Equal(t, req.Header.Get(s), v)
49 | }
50 | }
51 |
52 | type testHeader1 struct {
53 | H4 int64 `header:"h4"`
54 | H5 int32 `header:"h5"`
55 | H6 string `header:"-"`
56 | }
57 |
58 | type testHeader2 struct {
59 | H7 string `header:"h7"`
60 | }
61 |
62 | type testHeader struct {
63 | H1 string `header:"h1"`
64 | H2 int `header:"h2"`
65 | H3 float64 `header:"h3"`
66 | testHeader1
67 |
68 | H **testHeader2 // 测试多重指针
69 | H8 *testHeader2 //测试结构体空指针
70 | }
71 |
72 | func TestHeaderStruct(t *testing.T) {
73 | req, _ := http.NewRequest("GET", "/", nil)
74 |
75 | p := &testHeader2{H7: "h7"}
76 |
77 | err := Encode(testHeader{
78 | H1: "test-header-1",
79 | H2: 2,
80 | H3: 3.3,
81 | testHeader1: testHeader1{
82 | H4: int64(4),
83 | H5: int32(5),
84 | },
85 | H: &p,
86 | },
87 | NewHeaderEncode(req, false),
88 | )
89 |
90 | assert.NoError(t, err)
91 |
92 | needVal := []string{"test-header-1", "2", "3.3", "4", "5", "", "h7"}
93 |
94 | for k, v := range needVal {
95 | s := fmt.Sprintf("h%d", k+1)
96 | assert.Equal(t, req.Header.Get(s), v)
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/encode/protobuf.go:
--------------------------------------------------------------------------------
1 | package encode
2 |
3 | import (
4 | "errors"
5 | "io"
6 |
7 | "github.com/guonaihong/gout/core"
8 | "github.com/guonaihong/gout/encoder"
9 | "google.golang.org/protobuf/proto"
10 | )
11 |
12 | var ErrNotImplMessage = errors.New("The proto.Message interface is not implemented")
13 |
14 | type ProtoBufEncode struct {
15 | obj interface{}
16 | }
17 |
18 | func NewProtoBufEncode(obj interface{}) encoder.Encoder {
19 | if nil == obj {
20 | return nil
21 | }
22 | return &ProtoBufEncode{obj: obj}
23 | }
24 |
25 | func (p *ProtoBufEncode) Encode(w io.Writer) (err error) {
26 | if v, ok := core.GetBytes(p.obj); ok {
27 | //TODO找一个检测protobuf数据格式的函数
28 | _, err = w.Write(v)
29 | return err
30 | }
31 |
32 | var m proto.Message
33 | var ok bool
34 |
35 | m, ok = p.obj.(proto.Message)
36 | if !ok {
37 | // 这里如果能把普通结构体转成指针类型结构体就
38 | return ErrNotImplMessage
39 | }
40 |
41 | all, err := proto.Marshal(m)
42 | if err != nil {
43 | return err
44 | }
45 | _, err = w.Write(all)
46 | return err
47 | }
48 |
49 | func (p *ProtoBufEncode) Name() string {
50 | return "protobuf"
51 | }
52 |
--------------------------------------------------------------------------------
/encode/protobuf_test.go:
--------------------------------------------------------------------------------
1 | package encode
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "testing"
7 |
8 | "github.com/guonaihong/gout/testdata"
9 | "github.com/stretchr/testify/assert"
10 | "google.golang.org/protobuf/proto"
11 | )
12 |
13 | func TestNewProtoBufEncode(t *testing.T) {
14 | j := NewProtoBufEncode(nil)
15 | assert.Nil(t, j)
16 |
17 | }
18 |
19 | func TestProtoBuf_Name(t *testing.T) {
20 | assert.Equal(t, NewProtoBufEncode("").Name(), "protobuf")
21 | }
22 |
23 | func TestProtoBuf_Fail(t *testing.T) {
24 |
25 | out := bytes.Buffer{}
26 | for _, v := range []interface{}{testdata.Req{}} {
27 | p := NewProtoBufEncode(v)
28 | out.Reset()
29 |
30 | err := p.Encode(&out)
31 | assert.Error(t, err)
32 | }
33 |
34 | }
35 |
36 | func TestProtoBuf_Encode(t *testing.T) {
37 | data1, err1 := proto.Marshal(&testdata.Req{Seq: 1, Res: "fk"})
38 | assert.NoError(t, err1)
39 |
40 | data2 := &testdata.Req{Seq: 1, Res: "fk"}
41 | data := []interface{}{data1, data2, string(data1)}
42 |
43 | out := bytes.Buffer{}
44 |
45 | for i, v := range data {
46 | p := NewProtoBufEncode(v)
47 | out.Reset()
48 |
49 | assert.NoError(t, p.Encode(&out))
50 |
51 | got := testdata.Req{}
52 |
53 | err := proto.Unmarshal(out.Bytes(), &got)
54 | assert.NoError(t, err)
55 | assert.Equal(t, got.Seq, int32(1), fmt.Sprintf("fail index:%d", i))
56 | assert.Equal(t, got.Res, "fk")
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/encode/query.go:
--------------------------------------------------------------------------------
1 | package encode
2 |
3 | import (
4 | "net/url"
5 | "reflect"
6 |
7 | "github.com/guonaihong/gout/setting"
8 | )
9 |
10 | var _ Adder = (*QueryEncode)(nil)
11 |
12 | // QueryEncode URL query encoder structure
13 | type QueryEncode struct {
14 | values url.Values
15 | setting.Setting
16 | }
17 |
18 | // NewQueryEncode create a new URL query encoder
19 | func NewQueryEncode(s setting.Setting) *QueryEncode {
20 | return &QueryEncode{values: make(url.Values), Setting: s}
21 | }
22 |
23 | // Add Encoder core function, used to set each key / value into the http URL query
24 | func (q *QueryEncode) Add(key string, v reflect.Value, sf reflect.StructField) error {
25 | val := valToStr(v, sf)
26 | if !q.NotIgnoreEmpty && len(val) == 0 {
27 | return nil
28 | }
29 | q.values.Add(key, val)
30 | return nil
31 | }
32 |
33 | // End URL query structured data into strings
34 | func (q *QueryEncode) End() string {
35 | return q.values.Encode()
36 | }
37 |
38 | // Name URL query Encoder name
39 | func (q *QueryEncode) Name() string {
40 | return "query"
41 | }
42 |
--------------------------------------------------------------------------------
/encode/query_test.go:
--------------------------------------------------------------------------------
1 | package encode
2 |
3 | import (
4 | "strconv"
5 | "testing"
6 | "time"
7 |
8 | "github.com/guonaihong/gout/core"
9 | "github.com/guonaihong/gout/setting"
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | // 测试[]string类型
14 | func TestQueryStringSlice(t *testing.T) {
15 | q := NewQueryEncode(setting.Setting{})
16 |
17 | err := Encode([]string{"q1", "v1", "q2", "v2", "q3", "v3"}, q)
18 |
19 | assert.NoError(t, err)
20 |
21 | assert.Equal(t, q.End(), "q1=v1&q2=v2&q3=v3")
22 | }
23 |
24 | // 测试map[string]interface{}
25 | func TestQueryMap(t *testing.T) {
26 |
27 | q := NewQueryEncode(setting.Setting{})
28 |
29 | err := Encode(testH{"q1": "v1", "q2": "v2", "q3": "v3"}, q)
30 |
31 | assert.NoError(t, err)
32 |
33 | assert.Equal(t, q.End(), "q1=v1&q2=v2&q3=v3")
34 | }
35 |
36 | type testQuery struct {
37 | Q1 string `query:"q1"`
38 | Q2 string `query:"q2"`
39 | Q3 string `query:"q3"`
40 | Q4 time.Time `query:"q4" time_format:"unix"`
41 | Q5 time.Time `query:"q5" time_format:"unixNano"`
42 | }
43 |
44 | // 测试结构体
45 | func TestQueryStruct(t *testing.T) {
46 |
47 | q := NewQueryEncode(setting.Setting{})
48 |
49 | unixTime := time.Date(2019, 07, 27, 20, 42, 53, 0, time.Local)
50 | unixNano := time.Date(2019, 07, 27, 20, 42, 53, 1000, time.Local)
51 |
52 | err := Encode(testQuery{Q1: "v1", Q2: "v2", Q3: "v3", Q4: unixTime, Q5: unixNano}, q)
53 |
54 | assert.NoError(t, err)
55 |
56 | assert.Equal(t, q.End(), "q1=v1&q2=v2&q3=v3&q4="+strconv.FormatInt(unixTime.Unix(), 10)+"&q5="+strconv.FormatInt(unixNano.UnixNano(), 10))
57 | }
58 |
59 | // 结构体带[]string
60 | type queryWithSlice struct {
61 | A []string `query:"a"`
62 | B string `query:"b"`
63 | }
64 |
65 | func TestQueryFieldWithSlice(t *testing.T) {
66 |
67 | for _, v := range []interface{}{
68 | queryWithSlice{A: []string{"1", "2", "3"}, B: "b"},
69 | core.H{"a": []string{"1", "2", "3"}, "b": "b"},
70 | core.A{"a", []string{"1", "2", "3"}, "b", "b"},
71 | } {
72 |
73 | q := NewQueryEncode(setting.Setting{})
74 |
75 | err := Encode(v, q)
76 |
77 | assert.NoError(t, err)
78 |
79 | assert.Equal(t, q.End(), "a=1&a=2&a=3&b=b")
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/encode/www_form.go:
--------------------------------------------------------------------------------
1 | package encode
2 |
3 | import (
4 | "github.com/guonaihong/gout/core"
5 | "github.com/guonaihong/gout/setting"
6 | "io"
7 | "net/url"
8 | "reflect"
9 | )
10 |
11 | var _ Adder = (*WWWFormEncode)(nil)
12 |
13 | // WWWFormEncode x-www-form-urlencoded encoder structure
14 | type WWWFormEncode struct {
15 | values url.Values
16 | setting.Setting
17 | }
18 |
19 | // NewWWWFormEncode create a new x-www-form-urlencoded encoder
20 | func NewWWWFormEncode(s setting.Setting) *WWWFormEncode {
21 |
22 | return &WWWFormEncode{values: make(url.Values), Setting: s}
23 | }
24 |
25 | // Encode x-www-form-urlencoded encoder
26 | func (we *WWWFormEncode) Encode(obj interface{}) (err error) {
27 | return Encode(obj, we)
28 | }
29 |
30 | // Add Encoder core function, used to set each key / value into the http x-www-form-urlencoded
31 | // 这里value的设置暴露 reflect.Value和 reflect.StructField原因如下
32 | // reflect.Value把value转成字符串
33 | // reflect.StructField主要是可以在Add函数里面获取tag相关信息
34 | func (we *WWWFormEncode) Add(key string, v reflect.Value, sf reflect.StructField) error {
35 | val := valToStr(v, sf)
36 | if !we.NotIgnoreEmpty && len(val) == 0 {
37 | return nil
38 | }
39 | we.values.Add(key, val)
40 | return nil
41 | }
42 |
43 | func (we *WWWFormEncode) End(w io.Writer) error {
44 | _, err := w.Write(core.StringToBytes(we.values.Encode()))
45 | return err
46 | }
47 |
48 | // Name x-www-form-urlencoded Encoder name
49 | func (we *WWWFormEncode) Name() string {
50 | return "www-form"
51 | }
52 |
--------------------------------------------------------------------------------
/encode/www_form_test.go:
--------------------------------------------------------------------------------
1 | package encode
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/guonaihong/gout/core"
8 | "github.com/guonaihong/gout/setting"
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | type testWWWForm struct {
13 | w *WWWFormEncode
14 | in interface{}
15 | need string
16 | }
17 |
18 | func Test_WWWForm_Encode(t *testing.T) {
19 | var out bytes.Buffer
20 |
21 | tests := []testWWWForm{
22 | {NewWWWFormEncode(setting.Setting{}), core.A{"k1", "v1", "k2", 2, "k3", 3.14}, "k1=v1&k2=2&k3=3.14"},
23 | {NewWWWFormEncode(setting.Setting{}), core.H{"k1": "v1", "k2": 2, "k3": 3.14}, "k1=v1&k2=2&k3=3.14"},
24 | }
25 |
26 | for _, v := range tests {
27 | assert.NoError(t, v.w.Encode(v.in))
28 | assert.NoError(t, v.w.End(&out))
29 | assert.Equal(t, out.String(), v.need)
30 | out.Reset()
31 | }
32 |
33 | }
34 |
35 | func Test_WWWForm_Name(t *testing.T) {
36 | assert.Equal(t, NewWWWFormEncode(setting.Setting{}).Name(), "www-form")
37 | }
38 |
39 | type CreateUserMetadataReqBody struct {
40 | Avatarurl string `www-form:"avatarurl"`
41 | Nickname string `www-form:"nickname"`
42 | }
43 |
44 | func Test_WWWForm_Struct(t *testing.T) {
45 |
46 | data := CreateUserMetadataReqBody{Avatarurl: "www.hh.com", Nickname: "good"}
47 | enc := NewWWWFormEncode(setting.Setting{})
48 | err := enc.Encode(&data)
49 | assert.NoError(t, err)
50 |
51 | var out bytes.Buffer
52 | assert.NoError(t, enc.End(&out))
53 | pos := bytes.Index(out.Bytes(), []byte("avatarurl"))
54 | pos1 := bytes.Index(out.Bytes(), []byte("nickname"))
55 | assert.NotEqual(t, pos, -1)
56 | assert.NotEqual(t, pos1, -1)
57 | }
58 |
--------------------------------------------------------------------------------
/encode/xml.go:
--------------------------------------------------------------------------------
1 | package encode
2 |
3 | import (
4 | "bytes"
5 | "encoding/xml"
6 | "errors"
7 | "io"
8 |
9 | "github.com/guonaihong/gout/core"
10 | "github.com/guonaihong/gout/encoder"
11 | )
12 |
13 | var ErrNotXML = errors.New("Not xml data")
14 |
15 | // XMLEncode xml encoder structure
16 | type XMLEncode struct {
17 | obj interface{}
18 | }
19 |
20 | // NewXMLEncode create a new xml encoder
21 | func NewXMLEncode(obj interface{}) encoder.Encoder {
22 | if obj == nil {
23 | return nil
24 | }
25 |
26 | return &XMLEncode{obj: obj}
27 | }
28 |
29 | // Encode xml encoder
30 | func (x *XMLEncode) Encode(w io.Writer) (err error) {
31 | if v, ok := core.GetBytes(x.obj); ok {
32 | if b := XMLValid(v); !b {
33 | return ErrNotXML
34 | }
35 |
36 | _, err = w.Write(v)
37 | return err
38 | }
39 |
40 | encode := xml.NewEncoder(w)
41 | return encode.Encode(x.obj)
42 | }
43 |
44 | // Name xml Encoder name
45 | func (x *XMLEncode) Name() string {
46 | return "xml"
47 | }
48 |
49 | func XMLValid(b []byte) bool {
50 | dec := xml.NewDecoder(bytes.NewBuffer(b))
51 | for {
52 | _, err := dec.Token()
53 | if err != nil {
54 | if err == io.EOF {
55 | break
56 | }
57 | return false
58 | }
59 | }
60 |
61 | return true
62 | }
63 |
--------------------------------------------------------------------------------
/encode/xml_test.go:
--------------------------------------------------------------------------------
1 | package encode
2 |
3 | import (
4 | "bytes"
5 | "encoding/xml"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | type testXML struct {
12 | I int `xml:"i"`
13 | F float64 `xml:"f"`
14 | S string `xml:"s"`
15 | }
16 |
17 | func TestNewXMLEncode(t *testing.T) {
18 | x := NewXMLEncode(nil)
19 | assert.Nil(t, x)
20 | }
21 |
22 | func TestXMLEncode_Name(t *testing.T) {
23 | assert.Equal(t, NewXMLEncode("").Name(), "xml")
24 | }
25 |
26 | func TestXMLEncode_Encode(t *testing.T) {
27 | need := testXML{
28 | I: 100,
29 | F: 3.14,
30 | S: "test encode xml",
31 | }
32 |
33 | out := bytes.Buffer{}
34 |
35 | x := `
36 | 1003.14test encode xml
37 | `
38 | data := []interface{}{need, &need, x}
39 | for _, v := range data {
40 | x := NewXMLEncode(v)
41 | out.Reset()
42 |
43 | assert.NoError(t, x.Encode(&out))
44 |
45 | got := testXML{}
46 |
47 | err := xml.Unmarshal(out.Bytes(), &got)
48 | assert.NoError(t, err)
49 | assert.Equal(t, got, need)
50 | }
51 |
52 | // test fail
53 | for _, v := range []interface{}{``} {
54 | x := NewXMLEncode(v)
55 | out.Reset()
56 | err := x.Encode(&out)
57 | assert.Error(t, err)
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/encode/yaml.go:
--------------------------------------------------------------------------------
1 | package encode
2 |
3 | import (
4 | "errors"
5 | "io"
6 |
7 | "github.com/guonaihong/gout/core"
8 | "github.com/guonaihong/gout/encoder"
9 | "gopkg.in/yaml.v2"
10 | )
11 |
12 | var ErrNotYAML = errors.New("Not yaml data")
13 |
14 | // YAMLEncode yaml encoder structure
15 | type YAMLEncode struct {
16 | obj interface{}
17 | }
18 |
19 | // NewYAMLEncode create a new yaml encoder
20 | func NewYAMLEncode(obj interface{}) encoder.Encoder {
21 | if obj == nil {
22 | return nil
23 | }
24 |
25 | return &YAMLEncode{obj: obj}
26 | }
27 |
28 | // Encode yaml encoder
29 | func (y *YAMLEncode) Encode(w io.Writer) (err error) {
30 | if v, ok := core.GetBytes(y.obj); ok {
31 | if b := YAMLValid(v); !b {
32 | return ErrNotYAML
33 | }
34 | _, err = w.Write(v)
35 | return err
36 | }
37 | encode := yaml.NewEncoder(w)
38 | return encode.Encode(y.obj)
39 | }
40 |
41 | // Name yaml Encoder name
42 | func (y *YAMLEncode) Name() string {
43 | return "yaml"
44 | }
45 |
46 | func YAMLValid(b []byte) bool {
47 | var m map[string]interface{}
48 |
49 | err := yaml.Unmarshal(b, &m)
50 | return err == nil
51 | }
52 |
--------------------------------------------------------------------------------
/encode/yaml_test.go:
--------------------------------------------------------------------------------
1 | package encode
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/assert"
8 | "gopkg.in/yaml.v2"
9 | )
10 |
11 | type testYaml struct {
12 | I int `yaml:"i"`
13 | F float64 `yaml:"f"`
14 | S string `yaml:"s"`
15 | }
16 |
17 | func TestNewYAMLEncode(t *testing.T) {
18 | y := NewYAMLEncode(nil)
19 | assert.Nil(t, y)
20 | }
21 |
22 | func TestYAMLEncode_Name(t *testing.T) {
23 | assert.Equal(t, NewYAMLEncode("").Name(), "yaml")
24 | }
25 |
26 | func TestYAMLEncode_Encode(t *testing.T) {
27 | need := testYaml{
28 | I: 100,
29 | F: 3.14,
30 | S: "test encode yaml",
31 | }
32 |
33 | out := bytes.Buffer{}
34 |
35 | s := `
36 | i: 100
37 | f: 3.14
38 | s: test encode yaml`
39 | data := []interface{}{need, &need, s, []byte(s)}
40 | for _, v := range data {
41 | x := NewYAMLEncode(v)
42 | out.Reset()
43 |
44 | assert.NoError(t, x.Encode(&out))
45 |
46 | got := testYaml{}
47 |
48 | err := yaml.Unmarshal(out.Bytes(), &got)
49 | assert.NoError(t, err)
50 | assert.Equal(t, got, need)
51 | }
52 |
53 | // test fail
54 | for _, v := range []interface{}{`I:100 {}`} {
55 | y := NewYAMLEncode(v)
56 | out.Reset()
57 | err := y.Encode(&out)
58 | assert.Error(t, err)
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/encoder/encoder.go:
--------------------------------------------------------------------------------
1 | package encoder
2 |
3 | import (
4 | "io"
5 | )
6 |
7 | // Encoder is the encoding interface
8 | type Encoder interface {
9 | Encode(w io.Writer) error
10 | Name() string
11 | }
12 |
--------------------------------------------------------------------------------
/enjson/json.go:
--------------------------------------------------------------------------------
1 | package enjson
2 |
3 | import (
4 | "bytes"
5 | "errors"
6 | "io"
7 |
8 | "github.com/guonaihong/gout/core"
9 | "github.com/guonaihong/gout/encoder"
10 | "github.com/guonaihong/gout/json"
11 | )
12 |
13 | var ErrNotJSON = errors.New("Not json data")
14 |
15 | // JSONEncode json encoder structure
16 | type JSONEncode struct {
17 | obj interface{}
18 | escapeHTML bool
19 | }
20 |
21 | // NewJSONEncode create a new json encoder
22 | func NewJSONEncode(obj interface{}, escapeHTML bool) encoder.Encoder {
23 | if obj == nil {
24 | return nil
25 | }
26 |
27 | return &JSONEncode{obj: obj, escapeHTML: escapeHTML}
28 | }
29 |
30 | func Marshal(obj interface{}, escapeHTML bool) (all []byte, err error) {
31 |
32 | if !escapeHTML {
33 | var buf bytes.Buffer
34 | encode := json.NewEncoder(&buf)
35 | encode.SetEscapeHTML(escapeHTML)
36 | err = encode.Encode(obj)
37 | if err != nil {
38 | return
39 | }
40 | // encode结束之后会自作聪明的加'\n'
41 | // 为了保持和json.Marshal一样的形为,手动删除最后一个'\n'
42 | all = buf.Bytes()
43 | if buf.Len() > 0 && buf.Bytes()[buf.Len()-1] == '\n' {
44 | all = buf.Bytes()[:buf.Len()-1]
45 | }
46 | } else {
47 | all, err = json.Marshal(obj)
48 | if err != nil {
49 | return
50 | }
51 | }
52 | return
53 |
54 | }
55 |
56 | // Encode json encoder
57 | func (j *JSONEncode) Encode(w io.Writer) (err error) {
58 | if v, ok := core.GetBytes(j.obj); ok {
59 | if b := json.Valid(v); !b {
60 | return ErrNotJSON
61 | }
62 | _, err = w.Write(v)
63 | return err
64 | }
65 |
66 | var all []byte
67 | all, err = Marshal(j.obj, j.escapeHTML)
68 | if err != nil {
69 | return err
70 | }
71 | _, err = w.Write(all)
72 | return err
73 | }
74 |
75 | // Name json Encoder name
76 | func (j *JSONEncode) Name() string {
77 | return "json"
78 | }
79 |
--------------------------------------------------------------------------------
/enjson/json_test.go:
--------------------------------------------------------------------------------
1 | package enjson
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/guonaihong/gout/json"
7 | "testing"
8 |
9 | "github.com/stretchr/testify/assert"
10 | )
11 |
12 | type testJSON struct {
13 | I int `json:"i"`
14 | F float64 `json:"f"`
15 | S string `json:"s"`
16 | }
17 |
18 | func TestMarshal(t *testing.T) {
19 | all, err := Marshal(map[string]interface{}{"a": "a"}, true)
20 | assert.NoError(t, err)
21 | assert.False(t, bytes.Contains(all, []byte("\n")))
22 | }
23 |
24 | func TestNewJSONEncode(t *testing.T) {
25 | j := NewJSONEncode(nil, false)
26 | assert.Nil(t, j)
27 |
28 | j = NewJSONEncode(nil, true)
29 | assert.Nil(t, j)
30 | }
31 |
32 | func TestJSONEncode_Name(t *testing.T) {
33 | assert.Equal(t, NewJSONEncode("", false).Name(), "json")
34 | assert.Equal(t, NewJSONEncode("", true).Name(), "json")
35 | }
36 |
37 | func TestJSONEncode_Encode(t *testing.T) {
38 | need := testJSON{
39 | I: 100,
40 | F: 3.14,
41 | S: "test encode json",
42 | }
43 |
44 | out := bytes.Buffer{}
45 |
46 | s := `{"I" : 100, "F" : 3.14, "S":"test encode json"}`
47 | data := []interface{}{need, &need, s, []byte(s)}
48 | for _, v := range data {
49 | for _, on := range []bool{true, false} {
50 |
51 | j := NewJSONEncode(v, on)
52 | out.Reset()
53 |
54 | assert.NoError(t, j.Encode(&out))
55 |
56 | got := testJSON{}
57 |
58 | err := json.Unmarshal(out.Bytes(), &got)
59 | assert.NoError(t, err)
60 | assert.Equal(t, got, need)
61 | }
62 | }
63 |
64 | // test fail
65 | for _, v := range []interface{}{func() {}, `{"query":"value"`} {
66 | for _, on := range []bool{true, false} {
67 | j := NewJSONEncode(v, on)
68 | out.Reset()
69 | err := j.Encode(&out)
70 | assert.Error(t, err, fmt.Sprintf("on:%t", on))
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/export/curl.go:
--------------------------------------------------------------------------------
1 | package export
2 |
3 | import (
4 | "github.com/guonaihong/gout/dataflow"
5 | "io"
6 | "os"
7 | )
8 |
9 | var _ dataflow.Curl = (*Curl)(nil)
10 |
11 | type Curl struct {
12 | w io.Writer
13 | df *dataflow.DataFlow
14 | longOption bool
15 | generateAndSend bool
16 | }
17 |
18 | func (c *Curl) New(df *dataflow.DataFlow) interface{} {
19 | return &Curl{df: df}
20 | }
21 |
22 | func (c *Curl) LongOption() dataflow.Curl {
23 | c.longOption = true
24 | return c
25 | }
26 |
27 | func (c *Curl) GenAndSend() dataflow.Curl {
28 | c.generateAndSend = true
29 | return c
30 | }
31 |
32 | func (c *Curl) SetOutput(w io.Writer) dataflow.Curl {
33 | c.w = w
34 | return c
35 | }
36 |
37 | func (c *Curl) Do() (err error) {
38 | if c.w == nil {
39 | c.w = os.Stdout
40 | }
41 |
42 | w := c.w
43 |
44 | req, err := c.df.Request()
45 | if err != nil {
46 | return err
47 | }
48 |
49 | client := c.df.Client()
50 |
51 | if c.generateAndSend {
52 | // 清空状态,Setxxx函数拆开使用就不会有问题
53 | defer c.df.Reset()
54 | resp, err := client.Do(req)
55 | if err != nil {
56 | return err
57 | }
58 | defer resp.Body.Close()
59 |
60 | err = c.df.Bind(req, resp)
61 | if err != nil {
62 | return err
63 | }
64 | }
65 |
66 | return GenCurl(req, c.longOption, w)
67 | }
68 |
--------------------------------------------------------------------------------
/export/curl_core.go:
--------------------------------------------------------------------------------
1 | package export
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "io/ioutil"
7 | "mime/multipart"
8 | "net/http"
9 | "os"
10 | "path"
11 | "sort"
12 | "strings"
13 |
14 | "github.com/guonaihong/gout/core"
15 | )
16 |
17 | type curl struct {
18 | Header []string
19 | Method string
20 | Data string
21 | URL string
22 |
23 | FormData []string
24 | }
25 |
26 | const boundary = "boundary="
27 |
28 | func isExists(path string) bool {
29 | _, err := os.Stat(path)
30 | return err == nil
31 | }
32 |
33 | func getFileName(fName string) string {
34 | count := 0
35 | fileName := fName
36 | for ; ; count++ {
37 | if isExists(fileName) {
38 | fileName = fmt.Sprintf("%s.%d", fName, count)
39 | continue
40 | }
41 |
42 | break
43 | }
44 | return fileName
45 | }
46 |
47 | func (c *curl) formData(req *http.Request) error {
48 | contentType := req.Header.Get("Content-Type")
49 | if !strings.Contains(contentType, "multipart/form-data") {
50 | return nil
51 | }
52 | req.Header.Del("Content-Type")
53 |
54 | c.Data = ""
55 |
56 | boundaryValue := ""
57 |
58 | if pos := strings.Index(contentType, "boundary="); pos == -1 {
59 | return nil
60 | } else {
61 | boundaryValue = strings.TrimSpace(contentType[pos+len(boundary):])
62 | }
63 |
64 | body, err := req.GetBody()
65 | if err != nil {
66 | return err
67 | }
68 |
69 | mr := multipart.NewReader(body, boundaryValue)
70 | for {
71 | p, err := mr.NextPart()
72 | if err == io.EOF {
73 | break
74 | }
75 | if err != nil {
76 | return err
77 | }
78 |
79 | var buf strings.Builder
80 |
81 | buf.WriteString(p.FormName())
82 | buf.WriteByte('=')
83 |
84 | if p.FileName() != "" {
85 |
86 | fileName := path.Base(p.FileName())
87 | fileName = getFileName(fileName)
88 | fd, err := os.Create(fileName)
89 | if err != nil {
90 | return err
91 | }
92 | if _, err = io.Copy(fd, p); err != nil {
93 | return err
94 | }
95 |
96 | buf.WriteString("@./")
97 | buf.WriteString(fileName)
98 |
99 | if err = fd.Close(); err != nil {
100 | return err
101 | }
102 | } else {
103 | if _, err = io.Copy(&buf, p); err != nil {
104 | return err
105 | }
106 | }
107 |
108 | c.FormData = append(c.FormData, fmt.Sprintf("%q", buf.String()))
109 | }
110 |
111 | return nil
112 | }
113 |
114 | func (c *curl) header(req *http.Request) {
115 | header := make([]string, 0, len(req.Header))
116 |
117 | for k := range req.Header {
118 | header = append(header, k)
119 | }
120 |
121 | sort.Strings(header)
122 |
123 | for index, hKey := range header {
124 | hVal := req.Header[hKey]
125 |
126 | header[index] = fmt.Sprintf(`%s:%s`, hKey, strings.Join(hVal, ","))
127 | header[index] = fmt.Sprintf("%q", header[index])
128 | }
129 |
130 | c.Header = header
131 | }
132 |
133 | // GenCurl used to generate curl commands
134 | func GenCurl(req *http.Request, long bool, w io.Writer) error {
135 | c := curl{}
136 | body, err := req.GetBody()
137 | if err != nil {
138 | return err
139 | }
140 |
141 | all, err := ioutil.ReadAll(body)
142 | if err != nil {
143 | return err
144 | }
145 |
146 | c.URL = fmt.Sprintf(`%q`, req.URL.String())
147 | c.Method = req.Method
148 | if len(all) > 0 {
149 | c.Data = fmt.Sprintf(`%q`, core.BytesToString(all))
150 | }
151 | if err = c.formData(req); err != nil {
152 | return err
153 | }
154 |
155 | c.header(req)
156 | tp := newTemplate(long)
157 | return tp.Execute(w, c)
158 | }
159 |
--------------------------------------------------------------------------------
/export/curl_tmpl.go:
--------------------------------------------------------------------------------
1 | package export
2 |
3 | import (
4 | "text/template"
5 | )
6 |
7 | var shortTmpl = `
8 | curl{{if gt (len .Method) 0}} -X {{.Method}}{{end}}{{range $_, $header := .Header}} -H {{$header}}{{end}}{{if gt (len .Data) 0}} -d {{.Data}}{{end}}{{range $_, $formData := .FormData}} -F {{$formData}}{{end}} {{.URL}}
9 | `
10 |
11 | var longTmpl = `
12 | curl{{if gt (len .Method) 0}} --request {{.Method}}{{end}}{{range $_, $header := .Header}} --header {{$header}}{{end}}{{if gt (len .Data) 0}} --data {{.Data}}{{end}}{{range $_, $formData := .FormData}} --form {{$formData}}{{end}} --url {{.URL}}
13 | `
14 |
15 | func newTemplate(long bool) *template.Template {
16 | tmpl := shortTmpl
17 | if long {
18 | tmpl = longTmpl
19 | }
20 |
21 | return template.Must(template.New("generate-curl").Parse(tmpl))
22 | }
23 |
--------------------------------------------------------------------------------
/export/init.go:
--------------------------------------------------------------------------------
1 | package export
2 |
3 | import (
4 | "github.com/guonaihong/gout/dataflow"
5 | )
6 |
7 | var (
8 | defaultCurl = Curl{}
9 | )
10 |
11 | func init() {
12 | dataflow.Register("curl", &defaultCurl)
13 | }
14 |
--------------------------------------------------------------------------------
/filter/bench.go:
--------------------------------------------------------------------------------
1 | package filter
2 |
3 | import (
4 | "context"
5 | "net/http"
6 | "time"
7 |
8 | "github.com/guonaihong/gout/bench"
9 | "github.com/guonaihong/gout/core"
10 | "github.com/guonaihong/gout/dataflow"
11 | )
12 |
13 | // Bench provide benchmark features
14 | type Bench struct {
15 | bench.Task
16 |
17 | df *dataflow.DataFlow
18 |
19 | r *bench.Report
20 | getRequest func() (*http.Request, error)
21 | }
22 |
23 | func NewBench() *Bench {
24 | return &Bench{}
25 | }
26 |
27 | // New
28 | func (b *Bench) New(df *dataflow.DataFlow) interface{} {
29 | return &Bench{df: df}
30 | }
31 |
32 | // Concurrent set the number of benchmarks for concurrency
33 | func (b *Bench) Concurrent(c int) dataflow.Bencher {
34 | b.Task.Concurrent = c
35 | return b
36 | }
37 |
38 | // Number set the number of benchmarks
39 | func (b *Bench) Number(n int) dataflow.Bencher {
40 | b.Task.Number = n
41 | return b
42 | }
43 |
44 | // Rate set the frequency of the benchmark
45 | func (b *Bench) Rate(rate int) dataflow.Bencher {
46 | b.Task.Rate = rate
47 | return b
48 | }
49 |
50 | // Durations set the benchmark time
51 | func (b *Bench) Durations(d time.Duration) dataflow.Bencher {
52 | b.Task.Duration = d
53 | return b
54 | }
55 |
56 | func (b *Bench) Loop(cb func(c *dataflow.Context) error) dataflow.Bencher {
57 | b.getRequest = func() (*http.Request, error) {
58 | c := dataflow.Context{}
59 | // TODO 优化,这里创建了两个dataflow对象
60 | // c.SetGout和 c.getDataFlow 都依赖gout对象
61 | // 后面的版本要先梳理下gout对象的定位
62 | out := dataflow.New()
63 | c.DataFlow = &dataflow.DataFlow{}
64 | c.SetGout(out)
65 | err := cb(&c)
66 | if err != nil {
67 | return nil, err
68 | }
69 |
70 | return c.Request()
71 | }
72 |
73 | return b
74 | }
75 |
76 | func (b *Bench) GetReport(r *bench.Report) dataflow.Bencher {
77 | b.r = r
78 | return b
79 | }
80 |
81 | // Do benchmark startup function
82 | func (b *Bench) Do() error {
83 | // 报表插件
84 | if b.getRequest == nil {
85 | req, err := b.df.Req.Request()
86 | if err != nil {
87 | return err
88 | }
89 |
90 | b.getRequest = func() (*http.Request, error) {
91 | return core.CloneRequest(req)
92 | }
93 | }
94 |
95 | var client *http.Client
96 | if b.df != nil {
97 | client = b.df.Client()
98 | }
99 |
100 | if client == &dataflow.DefaultClient || client == nil {
101 | client = &dataflow.DefaultBenchClient
102 | }
103 |
104 | r := bench.NewReport(context.Background(),
105 | b.Task.Concurrent,
106 | b.Task.Number,
107 | b.Task.Duration,
108 | b.getRequest,
109 | client)
110 |
111 | // task是并发控制模块
112 | b.Run(r)
113 |
114 | if b.r != nil {
115 | b.r.ReportData = r.ReportData
116 | }
117 | return nil
118 | }
119 |
--------------------------------------------------------------------------------
/filter/init.go:
--------------------------------------------------------------------------------
1 | package filter
2 |
3 | import (
4 | "github.com/guonaihong/gout/dataflow"
5 | "math/rand"
6 | "time"
7 | )
8 |
9 | var (
10 | defaultBench = Bench{}
11 | defaultRetry = Retry{}
12 | )
13 |
14 | func init() {
15 | dataflow.Register("bench", &defaultBench)
16 | dataflow.Register("retry", &defaultRetry)
17 |
18 | rand.Seed(time.Now().UnixNano())
19 | }
20 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/guonaihong/gout
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/andybalholm/brotli v1.0.4
7 | github.com/bytedance/sonic v1.9.1
8 | github.com/gin-gonic/gin v1.9.1
9 | github.com/go-playground/locales v0.14.1
10 | github.com/go-playground/universal-translator v0.18.1
11 | github.com/go-playground/validator/v10 v10.14.0
12 | github.com/goccy/go-json v0.10.2
13 | github.com/google/uuid v1.1.1
14 | github.com/json-iterator/go v1.1.12
15 | github.com/mattn/go-isatty v0.0.19
16 | github.com/pkg/errors v0.9.1
17 | github.com/stretchr/testify v1.8.3
18 | golang.org/x/net v0.10.0
19 | google.golang.org/protobuf v1.30.0
20 | gopkg.in/yaml.v2 v2.2.8
21 | )
22 |
23 | require (
24 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
25 | github.com/davecgh/go-spew v1.1.1 // indirect
26 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect
27 | github.com/gin-contrib/sse v0.1.0 // indirect
28 | github.com/golang/protobuf v1.5.0 // indirect
29 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect
30 | github.com/leodido/go-urn v1.2.4 // indirect
31 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
32 | github.com/modern-go/reflect2 v1.0.2 // indirect
33 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect
34 | github.com/pmezard/go-difflib v1.0.0 // indirect
35 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
36 | github.com/ugorji/go/codec v1.2.11 // indirect
37 | golang.org/x/arch v0.3.0 // indirect
38 | golang.org/x/crypto v0.9.0 // indirect
39 | golang.org/x/sys v0.8.0 // indirect
40 | golang.org/x/text v0.9.0 // indirect
41 | gopkg.in/yaml.v3 v3.0.1 // indirect
42 | )
43 |
--------------------------------------------------------------------------------
/gout.go:
--------------------------------------------------------------------------------
1 | package gout
2 |
3 | import (
4 | "net/http"
5 | "time"
6 |
7 | "github.com/guonaihong/gout/dataflow"
8 | "github.com/guonaihong/gout/debug"
9 | _ "github.com/guonaihong/gout/export"
10 | _ "github.com/guonaihong/gout/filter"
11 | )
12 |
13 | // debug
14 | type DebugOption = debug.Options //不推荐gout.DebugOption方式引用, 推荐debug.Options引用
15 | type DebugOpt = debug.Apply //不推荐gout.DebugOpt方式引用,推荐debug.Apply方式引用
16 | type DebugFunc = debug.Func //不推荐gout.DebugFunc方式引用,推荐debug.Func方式引用
17 |
18 | func NoColor() DebugOpt {
19 | return debug.NoColor()
20 | }
21 |
22 | func Trace() DebugOpt {
23 | return debug.Trace()
24 | }
25 |
26 | type Context = dataflow.Context
27 |
28 | // New function is mainly used when passing custom http client
29 | func New(c ...*http.Client) *dataflow.Gout {
30 | return dataflow.New(c...)
31 | }
32 |
33 | // GET send HTTP GET method
34 | // 第一种情况
35 | // gout.GET("wwww.demo.xx/test-appkey")
36 | //
37 | // 第二种情况
38 | //
39 | // type host struct {
40 | // Host string
41 | // AppKey string
42 | // }
43 | //
44 | // gout.GET("http://{{.Host}/{{.AppKey}}}", &host{Host:"www.demo.xx", AppKey:"test-appkey"})
45 | func GET(url string, urlStruct ...interface{}) *dataflow.DataFlow {
46 | return dataflow.New().GET(url, urlStruct...)
47 | }
48 |
49 | // POST send HTTP POST method
50 | func POST(url string, urlStruct ...interface{}) *dataflow.DataFlow {
51 | return dataflow.New().POST(url, urlStruct...)
52 | }
53 |
54 | // PUT send HTTP PUT method
55 | func PUT(url string, urlStruct ...interface{}) *dataflow.DataFlow {
56 | return dataflow.New().PUT(url, urlStruct...)
57 | }
58 |
59 | // DELETE send HTTP DELETE method
60 | func DELETE(url string, urlStruct ...interface{}) *dataflow.DataFlow {
61 | return dataflow.New().DELETE(url, urlStruct...)
62 | }
63 |
64 | // PATCH send HTTP PATCH method
65 | func PATCH(url string, urlStruct ...interface{}) *dataflow.DataFlow {
66 | return dataflow.New().PATCH(url, urlStruct...)
67 | }
68 |
69 | // HEAD send HTTP HEAD method
70 | func HEAD(url string, urlStruct ...interface{}) *dataflow.DataFlow {
71 | return dataflow.New().HEAD(url, urlStruct...)
72 | }
73 |
74 | // OPTIONS send HTTP OPTIONS method
75 | func OPTIONS(url string, urlStruct ...interface{}) *dataflow.DataFlow {
76 | return dataflow.New().OPTIONS(url, urlStruct...)
77 | }
78 |
79 | // 设置不忽略空值
80 | func NotIgnoreEmpty() {
81 | dataflow.GlobalSetting.NotIgnoreEmpty = true
82 | }
83 |
84 | // 设置忽略空值
85 | func IgnoreEmpty() {
86 | dataflow.GlobalSetting.NotIgnoreEmpty = false
87 | }
88 |
89 | // 设置超时时间,
90 | // d > 0, 设置timeout
91 | // d == 0,取消全局变量
92 | func SetTimeout(d time.Duration) {
93 | dataflow.GlobalSetting.SetTimeout(d)
94 | }
95 |
96 | func SetDebug(b bool) {
97 | dataflow.GlobalSetting.SetDebug(b)
98 | }
99 |
--------------------------------------------------------------------------------
/gout_chunked_test.go:
--------------------------------------------------------------------------------
1 | package gout
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | "net"
7 | "testing"
8 | "time"
9 |
10 | "github.com/guonaihong/gout/core"
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | func testTcpSocket(out *bytes.Buffer, quit chan bool, t *testing.T) (addr string) {
15 | addr = core.GetNoPortExists()
16 |
17 | addr = ":" + addr
18 | go func() {
19 | defer close(quit)
20 | listener, err := net.Listen("tcp", addr)
21 | if err != nil {
22 | t.Errorf("%v\n", err)
23 | return
24 | }
25 | defer listener.Close()
26 |
27 | conn, err := listener.Accept()
28 | if err != nil {
29 | t.Errorf("%v\n", err)
30 | return
31 | }
32 |
33 | defer conn.Close()
34 | if _, err = io.Copy(out, conn); err != nil {
35 | t.Errorf("%v\n", err)
36 | return
37 | }
38 | }()
39 |
40 | return addr
41 |
42 | }
43 |
44 | func Test_Use_Chunked(t *testing.T) {
45 | var out bytes.Buffer
46 | quit := make(chan bool)
47 |
48 | addr := testTcpSocket(&out, quit, t)
49 | time.Sleep(time.Second / 100) //等待服务起好
50 |
51 | // 这里超时返回错误, 原因tcp服务没有构造http返回报文
52 | assert.Error(t, POST(addr).SetTimeout(time.Second/100).Chunked().SetBody("11111111111").Do())
53 | <-quit
54 | //time.Sleep(time.Second)
55 |
56 | assert.NotEqual(t, bytes.Index(out.Bytes(), []byte("Transfer-Encoding: chunked")), -1)
57 | }
58 |
--------------------------------------------------------------------------------
/gout_global_setdebug_test.go:
--------------------------------------------------------------------------------
1 | package gout
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | "net/http"
7 | "net/http/httptest"
8 | "os"
9 | "strings"
10 | "testing"
11 |
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | func Test_Global_SetDebug(t *testing.T) {
16 | router := setupDataFlow(t)
17 |
18 | ts := httptest.NewServer(http.HandlerFunc(router.ServeHTTP))
19 | defer ts.Close()
20 |
21 | old := os.Stdout // keep backup of the real stdout
22 | r, w, _ := os.Pipe()
23 | os.Stdout = w
24 |
25 | outC := make(chan string)
26 | // copy the output in a separate goroutine so printing can't block indefinitely
27 | go func() {
28 | var buf bytes.Buffer
29 | _, err := io.Copy(&buf, r)
30 | assert.NoError(t, err)
31 | outC <- buf.String()
32 | }()
33 |
34 | // reading our temp stdout
35 | // 只设置timeout
36 | SetDebug(true) //设置全局超时时间
37 | err := GET(ts.URL + "/setdebug").Do()
38 | // back to normal state
39 | w.Close()
40 | os.Stdout = old // restoring the real stdout
41 | out := <-outC
42 |
43 | assert.NoError(t, err)
44 | assert.NotEqual(t, strings.Index(out, "setdebug"), -1)
45 | }
46 |
--------------------------------------------------------------------------------
/gout_issue_389_test.go:
--------------------------------------------------------------------------------
1 | package gout
2 |
3 | import "testing"
4 |
5 | func Test_Issue_389(t *testing.T) {
6 | _ = NewWithOpt(WithProxy("http://127.0.0.1:7897"), WithInsecureSkipVerify())
7 |
8 | // client.GET("https://api.ipify.org?format=json").Debug(true).Do()
9 | }
10 |
--------------------------------------------------------------------------------
/gout_newopt.go:
--------------------------------------------------------------------------------
1 | package gout
2 |
3 | import (
4 | "net/http"
5 |
6 | "github.com/guonaihong/gout/dataflow"
7 | )
8 |
9 | type Client struct {
10 | options
11 | }
12 |
13 | // NewWithOpt 设计哲学
14 | // 1.一些不经常变化的配置放到NewWithOpt里面实现
15 | // 2.一些和http.Client深度绑定的放到NewWithOpt里面实现
16 | // 3.一些可以提升使用体验的放到NewWithOpt里面实现
17 | func NewWithOpt(opts ...Option) *Client {
18 | c := &Client{}
19 | c.hc = &http.Client{}
20 |
21 | for _, o := range opts {
22 | o.apply(&c.options)
23 | }
24 |
25 | return c
26 |
27 | }
28 |
29 | // GET send HTTP GET method
30 | func (c *Client) GET(url string) *dataflow.DataFlow {
31 | return dataflow.New(c.hc).GET(url).SetSetting(c.Setting)
32 | }
33 |
34 | // POST send HTTP POST method
35 | func (c *Client) POST(url string) *dataflow.DataFlow {
36 | return dataflow.New(c.hc).POST(url).SetSetting(c.Setting)
37 | }
38 |
39 | // PUT send HTTP PUT method
40 | func (c *Client) PUT(url string) *dataflow.DataFlow {
41 | return dataflow.New(c.hc).PUT(url).SetSetting(c.Setting)
42 | }
43 |
44 | // DELETE send HTTP DELETE method
45 | func (c *Client) DELETE(url string) *dataflow.DataFlow {
46 | return dataflow.New(c.hc).DELETE(url).SetSetting(c.Setting)
47 | }
48 |
49 | // PATCH send HTTP PATCH method
50 | func (c *Client) PATCH(url string) *dataflow.DataFlow {
51 | return dataflow.New(c.hc).PATCH(url).SetSetting(c.Setting)
52 | }
53 |
54 | // HEAD send HTTP HEAD method
55 | func (c *Client) HEAD(url string) *dataflow.DataFlow {
56 | return dataflow.New(c.hc).HEAD(url).SetSetting(c.Setting)
57 | }
58 |
59 | // OPTIONS send HTTP OPTIONS method
60 | func (c *Client) OPTIONS(url string) *dataflow.DataFlow {
61 | return dataflow.New(c.hc).OPTIONS(url).SetSetting(c.Setting)
62 | }
63 |
--------------------------------------------------------------------------------
/gout_newopt_method_test.go:
--------------------------------------------------------------------------------
1 | package gout
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func Test_NewWithOptTopMethod(t *testing.T) {
12 | var total int32
13 |
14 | router := setupMethod(&total)
15 |
16 | ts := httptest.NewServer(http.HandlerFunc(router.ServeHTTP))
17 | defer ts.Close()
18 |
19 | err := NewWithOpt().GET(ts.URL + "/someGet").Do()
20 | assert.NoError(t, err)
21 |
22 | err = NewWithOpt().POST(ts.URL + "/somePost").Do()
23 | assert.NoError(t, err)
24 |
25 | err = NewWithOpt().PUT(ts.URL + "/somePut").Do()
26 | assert.NoError(t, err)
27 |
28 | err = NewWithOpt().DELETE(ts.URL + "/someDelete").Do()
29 | assert.NoError(t, err)
30 |
31 | err = NewWithOpt().PATCH(ts.URL + "/somePatch").Do()
32 | assert.NoError(t, err)
33 |
34 | err = NewWithOpt().HEAD(ts.URL + "/someHead").Do()
35 | assert.NoError(t, err)
36 |
37 | err = NewWithOpt().OPTIONS(ts.URL + "/someOptions").Do()
38 | assert.NoError(t, err)
39 |
40 | assert.Equal(t, int(total), 7)
41 | }
42 |
--------------------------------------------------------------------------------
/gout_newopt_timeout_and_global_test.go:
--------------------------------------------------------------------------------
1 | package gout
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net/http"
7 | "net/http/httptest"
8 | "testing"
9 | "time"
10 |
11 | "github.com/gin-gonic/gin"
12 | "github.com/stretchr/testify/assert"
13 | )
14 |
15 | func setupDataFlow(t *testing.T) *gin.Engine {
16 | router := gin.New()
17 |
18 | router.GET("/timeout", func(c *gin.Context) {
19 | ctx := c.Request.Context()
20 | select {
21 | case <-ctx.Done():
22 | fmt.Printf("setTimeout done\n")
23 | case <-time.After(2 * time.Second):
24 | assert.Fail(t, "test timeout fail")
25 | }
26 | })
27 |
28 | router.GET("/setdebug", func(c *gin.Context) {
29 | c.String(200, "setdebug")
30 | })
31 |
32 | return router
33 | }
34 |
35 | func Test_Global_SetTimeout(t *testing.T) {
36 | router := setupDataFlow(t)
37 |
38 | const (
39 | longTimeout = 400
40 | middleTimeout = 300
41 | shortTimeout = 200
42 | )
43 |
44 | ts := httptest.NewServer(http.HandlerFunc(router.ServeHTTP))
45 | defer ts.Close()
46 |
47 | // 只设置timeout
48 | SetTimeout(shortTimeout * time.Millisecond) //设置全局超时时间
49 | err := GET(ts.URL + "/timeout").Do()
50 | // 期望的结果是返回错误
51 | assert.Error(t, err)
52 |
53 | ctx, cancel := context.WithTimeout(context.Background(), longTimeout*time.Millisecond)
54 | defer cancel()
55 |
56 | s := time.Now()
57 | SetTimeout(shortTimeout * time.Millisecond) // 设置全局超时时间
58 | err = GET(ts.URL + "/timeout").
59 | WithContext(ctx).
60 | Do()
61 |
62 | assert.Error(t, err)
63 | assert.LessOrEqual(t, time.Since(s), shortTimeout*time.Millisecond+time.Millisecond*50)
64 |
65 | SetTimeout(time.Duration(0))
66 | }
67 |
68 | func Test_NewWithOpt_Timeout(t *testing.T) {
69 | router := setupDataFlow(t)
70 |
71 | const (
72 | longTimeout = 400
73 | middleTimeout = 300
74 | shortTimeout = 200
75 | )
76 |
77 | ts := httptest.NewServer(http.HandlerFunc(router.ServeHTTP))
78 | defer ts.Close()
79 |
80 | // 只设置timeout
81 | greq := NewWithOpt(WithTimeout(shortTimeout * time.Millisecond)) //设置全局超时时间
82 | err := greq.GET(ts.URL + "/timeout").Do()
83 | // 期望的结果是返回错误
84 | assert.Error(t, err)
85 |
86 | // 使用互斥api的原则,后面的覆盖前面的
87 | // 这里是WithContext生效, 预期超时时间400ms
88 | ctx, cancel := context.WithTimeout(context.Background(), longTimeout*time.Millisecond)
89 | defer cancel()
90 | s := time.Now()
91 | greq = NewWithOpt(WithTimeout(shortTimeout * time.Millisecond)) // 设置全局超时时间
92 | err = greq.GET(ts.URL + "/timeout").
93 | WithContext(ctx).
94 | Do()
95 |
96 | assert.Error(t, err)
97 |
98 | assert.LessOrEqual(t, time.Since(s), shortTimeout*time.Millisecond+time.Millisecond*50)
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/gout_newopt_with_3xx_test.go:
--------------------------------------------------------------------------------
1 | package gout
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "strings"
7 | "testing"
8 |
9 | "github.com/gin-gonic/gin"
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | func createClose302() *httptest.Server {
14 | r := gin.New()
15 | r.GET("/302", func(c *gin.Context) {
16 | c.String(200, "done")
17 | })
18 |
19 | return httptest.NewServer(http.HandlerFunc(r.ServeHTTP))
20 | }
21 |
22 | func createClose301(url string) *httptest.Server {
23 | r := gin.New()
24 | r.GET("/301", func(c *gin.Context) {
25 | c.Redirect(302, url+"/302")
26 | })
27 |
28 | return httptest.NewServer(http.HandlerFunc(r.ServeHTTP))
29 | }
30 |
31 | func Test_Close3xx_True(t *testing.T) {
32 | ts := createClose301("")
33 |
34 | req := NewWithOpt(WithClose3xxJump())
35 | got := ""
36 | err := req.GET(ts.URL + "/301").BindBody(&got).Do()
37 | assert.NoError(t, err)
38 | assert.NotEqual(t, -2, strings.Index(got, "302"))
39 | }
40 |
41 | func Test_Close3xx_False(t *testing.T) {
42 | ts302 := createClose302()
43 | ts := createClose301(ts302.URL)
44 | req := NewWithOpt()
45 | got := ""
46 | err := req.GET(ts.URL + "/301").BindBody(&got).Do()
47 | assert.NoError(t, err)
48 | assert.Equal(t, got, "done")
49 | }
50 |
--------------------------------------------------------------------------------
/gout_newopt_with_skip_verify_test.go:
--------------------------------------------------------------------------------
1 | package gout
2 |
3 | import (
4 | "log"
5 | "net/http"
6 | "net/http/httptest"
7 | "strings"
8 | "testing"
9 | "time"
10 |
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | type chanWriter chan string
15 |
16 | func (w chanWriter) Write(p []byte) (n int, err error) {
17 | w <- string(p)
18 | return len(p), nil
19 | }
20 |
21 | func Test_WithInsecureSkipVerify(t *testing.T) {
22 |
23 | ts := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
24 | _, err := w.Write([]byte("Hello"))
25 | assert.NoError(t, err)
26 | }))
27 |
28 | errc := make(chanWriter, 10) // but only expecting 1
29 | ts.Config.ErrorLog = log.New(errc, "", 0)
30 |
31 | defer ts.Close()
32 |
33 | c := ts.Client()
34 | for _, insecure := range []bool{true, false} {
35 | var opts []Option
36 | if insecure {
37 | opts = []Option{WithClient(c), WithInsecureSkipVerify()}
38 | }
39 | client := NewWithOpt(opts...)
40 | err := client.GET(ts.URL).Do()
41 | if (err == nil) != insecure {
42 | t.Errorf("#insecure=%v: got unexpected err=%v", insecure, err)
43 | }
44 | }
45 |
46 | select {
47 | case v := <-errc:
48 | if !strings.Contains(v, "TLS handshake error") {
49 | t.Errorf("expected an error log message containing 'TLS handshake error'; got %q", v)
50 | }
51 | case <-time.After(5 * time.Second):
52 | t.Errorf("timeout waiting for logged error")
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/gout_options.go:
--------------------------------------------------------------------------------
1 | package gout
2 |
3 | import (
4 | "crypto/tls"
5 | "net/http"
6 | "time"
7 |
8 | "github.com/guonaihong/gout/hcutil"
9 | "github.com/guonaihong/gout/setting"
10 | )
11 |
12 | type options struct {
13 | hc *http.Client
14 | setting.Setting
15 | err error
16 | }
17 |
18 | type Option interface {
19 | apply(*options)
20 | }
21 |
22 | // 1.start
23 | type insecureSkipVerifyOption bool
24 |
25 | func (i insecureSkipVerifyOption) apply(opts *options) {
26 |
27 | if opts.hc.Transport == nil {
28 | opts.hc.Transport = &http.Transport{
29 | TLSClientConfig: &tls.Config{
30 | InsecureSkipVerify: true,
31 | },
32 | }
33 | return
34 | }
35 | opts.hc.Transport.(*http.Transport).TLSClientConfig = &tls.Config{
36 | InsecureSkipVerify: true,
37 | }
38 |
39 | }
40 |
41 | // 1.忽略ssl验证
42 | func WithInsecureSkipVerify() Option {
43 | b := true
44 | return insecureSkipVerifyOption(b)
45 | }
46 |
47 | // 2. start
48 | type client http.Client
49 |
50 | func (c *client) apply(opts *options) {
51 | opts.hc = (*http.Client)(c)
52 | }
53 |
54 | // 2.自定义http.Client
55 | func WithClient(c *http.Client) Option {
56 | return (*client)(c)
57 | }
58 |
59 | // 3.start
60 | type close3xx struct{}
61 |
62 | func (c close3xx) apply(opts *options) {
63 | opts.hc.CheckRedirect = func(req *http.Request, via []*http.Request) error {
64 | return http.ErrUseLastResponse
65 | }
66 | }
67 |
68 | // 3.关闭3xx自动跳转
69 | func WithClose3xxJump() Option {
70 | return close3xx{}
71 | }
72 |
73 | // 4.timeout
74 | type timeout time.Duration
75 |
76 | func WithTimeout(t time.Duration) Option {
77 | return (*timeout)(&t)
78 | }
79 |
80 | func (t *timeout) apply(opts *options) {
81 | opts.SetTimeout(time.Duration(*t))
82 | }
83 |
84 | // 5. 设置代理
85 | type proxy string
86 |
87 | func WithProxy(p string) Option {
88 | return (*proxy)(&p)
89 | }
90 |
91 | func (p *proxy) apply(opts *options) {
92 | if opts.hc == nil {
93 | opts.hc = &http.Client{}
94 | }
95 |
96 | opts.err = hcutil.SetProxy(opts.hc, string(*p))
97 | }
98 |
99 | // 6. 设置socks5代理
100 | type socks5 string
101 |
102 | func WithSocks5(s string) Option {
103 | return (*socks5)(&s)
104 | }
105 |
106 | func (s *socks5) apply(opts *options) {
107 | if opts.hc == nil {
108 | opts.hc = &http.Client{}
109 | }
110 |
111 | opts.err = hcutil.SetSOCKS5(opts.hc, string(*s))
112 | }
113 |
114 | // 7. 设置unix socket
115 | type unixSocket string
116 |
117 | func WithUnixSocket(u string) Option {
118 | return (*unixSocket)(&u)
119 | }
120 |
121 | func (u *unixSocket) apply(opts *options) {
122 | if opts.hc == nil {
123 | opts.hc = &http.Client{}
124 | }
125 |
126 | opts.err = hcutil.UnixSocket(opts.hc, string(*u))
127 | }
128 |
--------------------------------------------------------------------------------
/gout_test.go:
--------------------------------------------------------------------------------
1 | package gout
2 |
3 | import (
4 | "fmt"
5 | "github.com/gin-gonic/gin"
6 | "github.com/guonaihong/gout/dataflow"
7 | "github.com/stretchr/testify/assert"
8 | "io/ioutil"
9 | "net/http"
10 | "net/http/httptest"
11 | "sync/atomic"
12 | "testing"
13 | )
14 |
15 | func TestDebug(t *testing.T) {
16 | server := func() *gin.Engine {
17 | r := gin.New()
18 |
19 | r.GET("/", func(c *gin.Context) {
20 | all, err := ioutil.ReadAll(c.Request.Body)
21 |
22 | assert.NoError(t, err)
23 | c.String(200, string(all))
24 | })
25 |
26 | return r
27 | }()
28 |
29 | ts := httptest.NewServer(http.HandlerFunc(server.ServeHTTP))
30 | test := []func() DebugOpt{
31 | // 没有颜色输出
32 | NoColor,
33 | Trace,
34 | }
35 |
36 | s := ""
37 | for k, v := range test {
38 | s = ""
39 | err := GET(ts.URL).
40 | Debug(v()).
41 | SetBody(fmt.Sprintf("%d test debug.", k)).
42 | BindBody(&s).
43 | Do()
44 | assert.NoError(t, err)
45 |
46 | assert.Equal(t, fmt.Sprintf("%d test debug.", k), s)
47 | }
48 | }
49 |
50 | func TestNew(t *testing.T) {
51 | c := &http.Client{}
52 | tests := []*dataflow.Gout{
53 | New(nil),
54 | New(),
55 | New(c),
56 | }
57 |
58 | for _, v := range tests {
59 | assert.NotNil(t, v)
60 | }
61 | }
62 |
63 | func setupMethod(total *int32) *gin.Engine {
64 |
65 | router := gin.Default()
66 |
67 | cb := func(c *gin.Context) {
68 | atomic.AddInt32(total, 1)
69 | }
70 |
71 | router.GET("/someGet", cb)
72 | router.POST("/somePost", cb)
73 | router.PUT("/somePut", cb)
74 | router.DELETE("/someDelete", cb)
75 | router.PATCH("/somePatch", cb)
76 | router.HEAD("/someHead", cb)
77 | router.OPTIONS("/someOptions", cb)
78 |
79 | return router
80 | }
81 |
82 | func TestTopMethod(t *testing.T) {
83 | var total int32
84 |
85 | router := setupMethod(&total)
86 |
87 | ts := httptest.NewServer(http.HandlerFunc(router.ServeHTTP))
88 | defer ts.Close()
89 |
90 | err := GET(ts.URL + "/someGet").Do()
91 | assert.NoError(t, err)
92 |
93 | err = POST(ts.URL + "/somePost").Do()
94 | assert.NoError(t, err)
95 |
96 | err = PUT(ts.URL + "/somePut").Do()
97 | assert.NoError(t, err)
98 |
99 | err = DELETE(ts.URL + "/someDelete").Do()
100 | assert.NoError(t, err)
101 |
102 | err = PATCH(ts.URL + "/somePatch").Do()
103 | assert.NoError(t, err)
104 |
105 | err = HEAD(ts.URL + "/someHead").Do()
106 | assert.NoError(t, err)
107 |
108 | err = OPTIONS(ts.URL + "/someOptions").Do()
109 | assert.NoError(t, err)
110 |
111 | assert.Equal(t, int(total), 7)
112 | }
113 |
--------------------------------------------------------------------------------
/gout_valid_test.go:
--------------------------------------------------------------------------------
1 | package gout
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | type testValid struct {
12 | Val string `valid:"required"`
13 | }
14 |
15 | func Test_Valid(t *testing.T) {
16 | total := int32(1)
17 | router := setupMethod(&total)
18 |
19 | ts := httptest.NewServer(http.HandlerFunc(router.ServeHTTP))
20 | defer ts.Close()
21 |
22 | testCases := []string{"bindjson", "bindxml", "bindyaml", "bindheader"}
23 | for _, c := range testCases {
24 | val := testValid{}
25 | g := GET(ts.URL + "/someGet")
26 | var err error
27 | switch c {
28 | case "bindjson":
29 | err = g.BindJSON(&val).Do()
30 | case "bindxml":
31 | err = g.BindXML(&val).Do()
32 | case "bindyaml":
33 | err = g.BindYAML(&val).Do()
34 | case "bindheader":
35 | err = g.BindHeader(&val).Do()
36 | }
37 |
38 | //fmt.Printf("-->%v\n", err)
39 | assert.Error(t, err)
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/hcutil/README.md:
--------------------------------------------------------------------------------
1 | ##
2 | 本目录存入http.Client的辅助函数
3 |
--------------------------------------------------------------------------------
/hcutil/hcutil.go:
--------------------------------------------------------------------------------
1 | package hcutil
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "net"
7 | "net/http"
8 | "net/url"
9 | "strings"
10 |
11 | "golang.org/x/net/proxy"
12 | )
13 |
14 | func ModifyURL(url string) string {
15 | if strings.HasPrefix(url, "https://") || strings.HasPrefix(url, "http://") {
16 | return url
17 | }
18 |
19 | if strings.HasPrefix(url, ":") {
20 | return fmt.Sprintf("http://127.0.0.1%s", url)
21 | }
22 |
23 | if strings.HasPrefix(url, "/") {
24 | return fmt.Sprintf("http://127.0.0.1%s", url)
25 | }
26 |
27 | return fmt.Sprintf("http://%s", url)
28 | }
29 |
30 | // socks5://username:password@localhost:7890
31 | // socks5://localhost:7890
32 | // localhost:7890
33 | func SetSOCKS5(c *http.Client, socks5URL string) error {
34 | host := socks5URL
35 | var auth *proxy.Auth
36 | if strings.HasPrefix(socks5URL, "socks5://") {
37 | proxyURL, err := url.Parse(socks5URL)
38 | if err != nil {
39 | return err
40 | }
41 |
42 | if proxyURL.User != nil {
43 | password, _ := proxyURL.User.Password()
44 | auth = &proxy.Auth{
45 | User: proxyURL.User.Username(),
46 | Password: password,
47 | }
48 | }
49 |
50 | host = proxyURL.Host
51 | }
52 | dialer, err := proxy.SOCKS5("tcp", host, auth, proxy.Direct)
53 | if err != nil {
54 | return err
55 | }
56 |
57 | if c.Transport == nil {
58 | c.Transport = &http.Transport{}
59 | }
60 |
61 | transport, ok := c.Transport.(*http.Transport)
62 | if !ok {
63 | return fmt.Errorf("SetSOCKS5:not found http.transport:%T", c.Transport)
64 | }
65 |
66 | transport.Dial = dialer.Dial
67 | return nil
68 | }
69 |
70 | func SetProxy(c *http.Client, proxyURL string) error {
71 | proxy, err := url.Parse(ModifyURL(proxyURL))
72 | if err != nil {
73 | return err
74 | }
75 |
76 | if c.Transport == nil {
77 | c.Transport = &http.Transport{}
78 | }
79 |
80 | transport, ok := c.Transport.(*http.Transport)
81 | if !ok {
82 | return fmt.Errorf("SetProxy:not found http.transport:%T", c.Transport)
83 | }
84 |
85 | transport.Proxy = http.ProxyURL(proxy)
86 |
87 | return nil
88 | }
89 |
90 | func UnixSocket(c *http.Client, path string) error {
91 | if c.Transport == nil {
92 | c.Transport = &http.Transport{}
93 | }
94 |
95 | transport, ok := c.Transport.(*http.Transport)
96 | if !ok {
97 | return fmt.Errorf("UnixSocket:not found http.transport:%T", c.Transport)
98 | }
99 |
100 | transport.DialContext = func(ctx context.Context, proto, addr string) (conn net.Conn, err error) {
101 | var d net.Dialer
102 | return d.DialContext(ctx, "unix", path)
103 | }
104 |
105 | return nil
106 | }
107 |
--------------------------------------------------------------------------------
/import.go:
--------------------------------------------------------------------------------
1 | package gout
2 |
3 | import (
4 | "bufio"
5 | "bytes"
6 | "github.com/guonaihong/gout/core"
7 | "github.com/guonaihong/gout/dataflow"
8 | "io"
9 | "io/ioutil"
10 | "net/http"
11 | "strings"
12 | )
13 |
14 | type Import struct{}
15 |
16 | func NewImport() *Import {
17 | return &Import{}
18 | }
19 |
20 | const rawTextSpace = "\r\n "
21 |
22 | func (i *Import) RawText(text interface{}) *Text {
23 | var read io.Reader
24 | r := &Text{}
25 |
26 | out := dataflow.New()
27 | // TODO 函数
28 | r.SetGout(out)
29 |
30 | switch body := text.(type) {
31 | case string:
32 | body = strings.TrimLeft(body, rawTextSpace)
33 | read = strings.NewReader(body)
34 | case []byte:
35 | body = bytes.TrimLeft(body, rawTextSpace)
36 | read = bytes.NewReader(body)
37 | default:
38 | r.Err = core.ErrUnknownType
39 | return r
40 | }
41 |
42 | req, err := http.ReadRequest(bufio.NewReader(read))
43 | if err != nil {
44 | r.Err = err
45 | return r
46 | }
47 |
48 | // TODO 探索下能否支持https
49 | req.URL.Scheme = "http"
50 | req.URL.Host = req.Host
51 | req.RequestURI = ""
52 |
53 | if req.GetBody == nil {
54 | all, err := ioutil.ReadAll(req.Body)
55 | if err != nil {
56 | r.Err = err
57 | return r
58 | }
59 |
60 | req.GetBody = func() (io.ReadCloser, error) {
61 | return ioutil.NopCloser(bytes.NewReader(all)), nil
62 | }
63 |
64 | req.Body = ioutil.NopCloser(bytes.NewReader(all))
65 | }
66 |
67 | r.SetRequest(req)
68 |
69 | return r
70 | }
71 |
--------------------------------------------------------------------------------
/import_rawtext.go:
--------------------------------------------------------------------------------
1 | package gout
2 |
3 | import (
4 | "github.com/guonaihong/gout/dataflow"
5 | )
6 |
7 | type Text struct {
8 | dataflow.DataFlow
9 | }
10 |
--------------------------------------------------------------------------------
/interface/README.md:
--------------------------------------------------------------------------------
1 | ## 不建议使用这个目录下的代码
2 | 建议使用如下目录
3 | ```go
4 | github.com/guonaihong/gout/middler
5 | ```
6 |
7 | ## 这个目录的代码将在未来4-5个版本迭代之后删除
8 | 请做好迁移 v0.3.1版本开始算起,大约在v0.3.6版本移除
9 |
--------------------------------------------------------------------------------
/interface/do.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import "net/http"
4 |
5 | type Do interface {
6 | Do(*http.Request) (*http.Response, error)
7 | }
8 |
--------------------------------------------------------------------------------
/interface/request_use_interface.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import "net/http"
4 |
5 | type RequestMiddlerFunc func(req *http.Request) error
6 |
7 | type RequestMiddler interface {
8 | ModifyRequest(req *http.Request) error
9 | }
10 |
11 | func (f RequestMiddlerFunc) ModifyRequest(req *http.Request) error {
12 | return f(req)
13 | }
14 |
15 | // WithRequestMiddlerFunc 是创建一个 RequestMiddler 的helper
16 | // 如果我们只需要简单的逻辑,只关注闭包本身,则可以使用这个helper快速创建一个 RequestMiddler
17 | func WithRequestMiddlerFunc(f RequestMiddlerFunc) RequestMiddler {
18 | return f
19 | }
20 |
--------------------------------------------------------------------------------
/interface/response_use_interface.go:
--------------------------------------------------------------------------------
1 | package api
2 |
3 | import (
4 | "net/http"
5 | )
6 |
7 | type ResponseMiddlerFunc func(response *http.Response) error
8 |
9 | // ResponseMiddler 响应拦截器
10 | type ResponseMiddler interface {
11 | ModifyResponse(response *http.Response) error
12 | }
13 |
14 | func (f ResponseMiddlerFunc) ModifyResponse(response *http.Response) error {
15 | return f(response)
16 | }
17 |
18 | // WithResponseMiddlerFunc 是创建一个 ResponseMiddler 的helper
19 | // 如果我们只需要简单的逻辑,只关注闭包本身,则可以使用这个helper快速创建一个 ResponseMiddler
20 | func WithResponseMiddlerFunc(f ResponseMiddlerFunc) ResponseMiddler {
21 | return f
22 | }
23 |
--------------------------------------------------------------------------------
/json/go_json.go:
--------------------------------------------------------------------------------
1 | //go:build go_json
2 | // +build go_json
3 |
4 | package json
5 |
6 | import json "github.com/goccy/go-json"
7 |
8 | var (
9 | Marshal = json.Marshal
10 | Unmarshal = json.Unmarshal
11 | NewDecoder = json.NewDecoder
12 | NewEncoder = json.NewEncoder
13 | Valid = json.Valid
14 | )
15 |
--------------------------------------------------------------------------------
/json/json.go:
--------------------------------------------------------------------------------
1 | // 参考 https://github.com/gin-gonic/gin/blob/master/internal/json/jsoniter.go
2 |
3 | //go:build !jsoniter && !go_json && !(sonic && avx && (linux || windows || darwin) && amd64)
4 | // +build !jsoniter
5 | // +build !go_json
6 | // +build !sonic !avx !linux,!windows,!darwin !amd64
7 |
8 | package json
9 |
10 | import "encoding/json"
11 |
12 | var (
13 | Marshal = json.Marshal
14 | Unmarshal = json.Unmarshal
15 | NewDecoder = json.NewDecoder
16 | NewEncoder = json.NewEncoder
17 | Valid = json.Valid
18 | )
19 |
--------------------------------------------------------------------------------
/json/jsoniter.go:
--------------------------------------------------------------------------------
1 | //go:build jsoniter
2 | // +build jsoniter
3 |
4 | package json
5 |
6 | import jsoniter "github.com/json-iterator/go"
7 |
8 | var (
9 | json = jsoniter.ConfigCompatibleWithStandardLibrary
10 | Marshal = json.Marshal
11 | Unmarshal = json.Unmarshal
12 | NewDecoder = json.NewDecoder
13 | NewEncoder = json.NewEncoder
14 | Valid = json.Valid
15 | )
16 |
--------------------------------------------------------------------------------
/json/sonic.go:
--------------------------------------------------------------------------------
1 | //go:build sonic && avx && (linux || windows || darwin) && amd64
2 | // +build sonic
3 | // +build avx
4 | // +build linux windows darwin
5 | // +build amd64
6 |
7 | package json
8 |
9 | import "github.com/bytedance/sonic"
10 |
11 | var (
12 | json = sonic.ConfigStd
13 | Marshal = json.Marshal
14 | Unmarshal = json.Unmarshal
15 | NewDecoder = json.NewDecoder
16 | NewEncoder = json.NewEncoder
17 | Valid = json.Valid
18 | )
19 |
--------------------------------------------------------------------------------
/middler/do.go:
--------------------------------------------------------------------------------
1 | package middler
2 |
3 | import "net/http"
4 |
5 | type Do interface {
6 | Do(*http.Request) (*http.Response, error)
7 | }
8 |
--------------------------------------------------------------------------------
/middler/request_use_interface.go:
--------------------------------------------------------------------------------
1 | package middler
2 |
3 | import "net/http"
4 |
5 | type RequestMiddlerFunc func(req *http.Request) error
6 |
7 | type RequestMiddler interface {
8 | ModifyRequest(req *http.Request) error
9 | }
10 |
11 | func (f RequestMiddlerFunc) ModifyRequest(req *http.Request) error {
12 | return f(req)
13 | }
14 |
15 | // WithRequestMiddlerFunc 是创建一个 RequestMiddler 的helper
16 | // 如果我们只需要简单的逻辑,只关注闭包本身,则可以使用这个helper快速创建一个 RequestMiddler
17 | func WithRequestMiddlerFunc(f RequestMiddlerFunc) RequestMiddler {
18 | return f
19 | }
20 |
--------------------------------------------------------------------------------
/middler/response_use_interface.go:
--------------------------------------------------------------------------------
1 | package middler
2 |
3 | import (
4 | "net/http"
5 | )
6 |
7 | type ResponseMiddlerFunc func(response *http.Response) error
8 |
9 | // ResponseMiddler 响应拦截器
10 | type ResponseMiddler interface {
11 | ModifyResponse(response *http.Response) error
12 | }
13 |
14 | func (f ResponseMiddlerFunc) ModifyResponse(response *http.Response) error {
15 | return f(response)
16 | }
17 |
18 | // WithResponseMiddlerFunc 是创建一个 ResponseMiddler 的helper
19 | // 如果我们只需要简单的逻辑,只关注闭包本身,则可以使用这个helper快速创建一个 ResponseMiddler
20 | func WithResponseMiddlerFunc(f ResponseMiddlerFunc) ResponseMiddler {
21 | return f
22 | }
23 |
--------------------------------------------------------------------------------
/middleware/rsp/autodecodebody/autodecodebody.go:
--------------------------------------------------------------------------------
1 | package autodecodebody
2 |
3 | import (
4 | "bytes"
5 | "compress/zlib"
6 | "errors"
7 | "io"
8 | "io/ioutil"
9 | "net/http"
10 | "strings"
11 |
12 | "github.com/andybalholm/brotli"
13 | )
14 |
15 | func AutoDecodeBody(rsp *http.Response) (err error) {
16 | encoding := rsp.Header.Get("Content-Encoding")
17 |
18 | encoding = strings.ToLower(encoding)
19 | var out bytes.Buffer
20 | var rc io.ReadCloser
21 | switch encoding {
22 | //case "gzip": // net/http包里面已经做了gzip自动解码
23 | //rc, err = gzip.NewReader(rsp.Body)
24 | case "compress":
25 | // compress 是一种浏览器基本不使用的压缩格式,暂不考虑支持
26 | return errors.New("gout:There is currently no plan to support the compress format")
27 | case "deflate":
28 | rc, err = zlib.NewReader(rsp.Body)
29 | case "br":
30 | rc = ioutil.NopCloser(brotli.NewReader(rsp.Body))
31 | default:
32 | return nil
33 | }
34 |
35 | if err != nil {
36 | return err
37 | }
38 |
39 | if _, err = io.Copy(&out, rc); err != nil {
40 | return err
41 | }
42 |
43 | if err = rc.Close(); err != nil {
44 | return err
45 | }
46 | rsp.Body = ioutil.NopCloser(&out)
47 | return nil
48 | }
49 |
--------------------------------------------------------------------------------
/middleware/rsp/autodecodebody/autodecodebody_test.go:
--------------------------------------------------------------------------------
1 | package autodecodebody
2 |
3 | import (
4 | "bytes"
5 | "compress/zlib"
6 | "io/ioutil"
7 | "net/http"
8 | "testing"
9 |
10 | "github.com/andybalholm/brotli"
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | func TestAutoDecodeBody(t *testing.T) {
15 | // https://developer.mozilla.org/zh-CN/docs/web/http/headers/content-encoding
16 | data := "test auto decode boyd function"
17 | for _, rsp := range []http.Response{
18 | // gzip
19 | /*
20 | func() (rv http.Response) {
21 | var buf bytes.Buffer
22 |
23 | rv.Header = make(map[string][]string)
24 | rv.Header.Set("Content-Encoding", "gzip")
25 | zw := gzip.NewWriter(&buf)
26 | // Setting the Header fields is optional.
27 | zw.Name = "a-new-hope.txt"
28 | zw.Comment = "an epic space opera by George Lucas"
29 | zw.ModTime = time.Date(1977, time.May, 25, 0, 0, 0, 0, time.UTC)
30 | _, err := zw.Write([]byte(data))
31 | if err != nil {
32 | log.Fatal(err)
33 | }
34 |
35 | if err := zw.Close(); err != nil {
36 | log.Fatal(err)
37 | }
38 |
39 | rv.Body = ioutil.NopCloser(&buf)
40 | return
41 | }(),
42 | */
43 | // deflate
44 | func() (rv http.Response) {
45 |
46 | rv.Header = make(map[string][]string)
47 | rv.Header.Set("Content-Encoding", "deflate")
48 | var b bytes.Buffer
49 | w := zlib.NewWriter(&b)
50 | _, err := w.Write([]byte(data))
51 | assert.NoError(t, err)
52 | w.Close()
53 |
54 | rv.Body = ioutil.NopCloser(&b)
55 | return
56 | }(),
57 | // br
58 | func() (rv http.Response) {
59 | rv.Header = make(map[string][]string)
60 | rv.Header.Set("Content-Encoding", "br")
61 | var b bytes.Buffer
62 | w := brotli.NewWriter(&b)
63 | _, err := w.Write([]byte(data))
64 | assert.NoError(t, err)
65 | w.Flush()
66 | w.Close()
67 | rv.Body = ioutil.NopCloser(&b)
68 | return
69 | }(),
70 | } {
71 |
72 | err := AutoDecodeBody(&rsp)
73 | assert.NoError(t, err)
74 | all, err := ioutil.ReadAll(rsp.Body)
75 | assert.Equal(t, all, []byte(data))
76 | assert.NoError(t, err)
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/query_test.go:
--------------------------------------------------------------------------------
1 | package gout
2 |
3 | import (
4 | "bytes"
5 | "net/http"
6 | "net/http/httptest"
7 | "testing"
8 |
9 | "github.com/gin-gonic/gin"
10 | "github.com/guonaihong/gout/debug"
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | type queryWithSlice struct {
15 | A []string `query:"a" form:"a"`
16 | B string `query:"b" form:"b"`
17 | }
18 |
19 | func testQueryWithSliceServer(t *testing.T) *httptest.Server {
20 |
21 | r := gin.New()
22 |
23 | need := queryWithSlice{A: []string{"1", "2", "3"}, B: "b"}
24 | r.GET("/query", func(c *gin.Context) {
25 |
26 | got := queryWithSlice{}
27 | err := c.ShouldBindQuery(&got)
28 | assert.NoError(t, err)
29 | assert.Equal(t, need, got)
30 | })
31 |
32 | return httptest.NewServer(http.HandlerFunc(r.ServeHTTP))
33 | }
34 |
35 | // 测试query接口,带slice的情况
36 | func TestQuery_slice(t *testing.T) {
37 |
38 | ts := testQueryWithSliceServer(t)
39 |
40 | for _, v := range []interface{}{
41 | queryWithSlice{A: []string{"1", "2", "3"}, B: "b"},
42 | H{"a": []string{"1", "2", "3"}, "b": "b"},
43 | A{"a", []string{"1", "2", "3"}, "b", "b"},
44 | } {
45 |
46 | err := GET(ts.URL + "/query").Debug(true).SetQuery(v).Do()
47 | assert.NoError(t, err)
48 | }
49 | }
50 |
51 | func TestQuery_NotIgnoreEmpty(t *testing.T) {
52 |
53 | total := int32(0)
54 | router := setupMethod(&total)
55 |
56 | ts := httptest.NewServer(http.HandlerFunc(router.ServeHTTP))
57 | defer ts.Close()
58 |
59 | query := H{
60 | "t": 1296,
61 | "callback": "searchresult",
62 | "q": "美食",
63 | "stype": 1,
64 | "pagesize": 100,
65 | "pagenum": 1,
66 | "imageType": 2,
67 | "imageColor": "",
68 | "brand": "",
69 | "imageSType": "",
70 | "fr": 1,
71 | "sortFlag": 1,
72 | "imageUType": "",
73 | "btype": "",
74 | "authid": "",
75 | "_": int64(1611822443760),
76 | }
77 |
78 | var out bytes.Buffer
79 | SaveDebug := func() debug.Apply {
80 | return DebugFunc(func(o *DebugOption) {
81 | o.Write = &out
82 | o.Debug = true
83 | })
84 | }
85 |
86 | // 默认不忽略空值
87 | err := GET(ts.URL).Debug(SaveDebug()).SetQuery(query).Do()
88 | assert.NoError(t, err)
89 | // 有authid字段
90 | assert.NotEqual(t, bytes.Index(out.Bytes(), []byte("authid")), -1)
91 |
92 | // 重置bytes.Buffer
93 | out.Reset()
94 | // 忽略空值
95 | IgnoreEmpty()
96 | // 默认不忽略空值
97 | err = GET(ts.URL).Debug(SaveDebug()).SetQuery(query).Do()
98 | assert.NoError(t, err)
99 | // 没有authid字段
100 | assert.Equal(t, bytes.Index(out.Bytes(), []byte("authid")), -1)
101 |
102 | NotIgnoreEmpty()
103 | }
104 |
--------------------------------------------------------------------------------
/req_url_template_test.go:
--------------------------------------------------------------------------------
1 | package gout
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "strings"
7 | "testing"
8 |
9 | "github.com/gin-gonic/gin"
10 | "github.com/stretchr/testify/assert"
11 | )
12 |
13 | type testURLTemplateCase struct {
14 | Host string
15 | Method string
16 | }
17 |
18 | func createMethodEcho() *httptest.Server {
19 | router := func() *gin.Engine {
20 | router := gin.New()
21 |
22 | router.GET("/get", func(c *gin.Context) {
23 | c.String(200, "get")
24 | })
25 |
26 | router.POST("/post", func(c *gin.Context) {
27 | c.String(200, "post")
28 | })
29 |
30 | router.PUT("/put", func(c *gin.Context) {
31 | c.String(200, "put")
32 | })
33 |
34 | router.PATCH("/patch", func(c *gin.Context) {
35 | c.String(200, "patch")
36 | })
37 |
38 | router.OPTIONS("/options", func(c *gin.Context) {
39 | c.String(200, "options")
40 | })
41 |
42 | router.HEAD("/head", func(c *gin.Context) {
43 | c.String(200, "head")
44 | })
45 |
46 | return router
47 | }()
48 |
49 | return httptest.NewServer(http.HandlerFunc(router.ServeHTTP))
50 | }
51 |
52 | func Test_URL_Template(t *testing.T) {
53 | ts := createMethodEcho()
54 | for _, tc := range []testURLTemplateCase{
55 | {Host: ts.URL, Method: "get"},
56 | {Host: ts.URL, Method: "post"},
57 | {Host: ts.URL, Method: "put"},
58 | {Host: ts.URL, Method: "patch"},
59 | {Host: ts.URL, Method: "options"},
60 | {Host: ts.URL, Method: "head"},
61 | } {
62 | body := ""
63 | body2 := ""
64 | code := 0
65 | switch tc.Method {
66 | case "get":
67 | err := GET("{{.Host}}/{{.Method}}", tc).Debug(true).BindBody(&body).Code(&code).Do()
68 | assert.NoError(t, err)
69 | case "post":
70 | err := POST("{{.Host}}/{{.Method}}", tc).BindBody(&body).Code(&code).Do()
71 | assert.NoError(t, err)
72 | case "put":
73 | err := PUT("{{.Host}}/{{.Method}}", tc).BindBody(&body).Code(&code).Do()
74 | assert.NoError(t, err)
75 | case "patch":
76 | err := PATCH("{{.Host}}/{{.Method}}", tc).BindBody(&body).Code(&code).Do()
77 | assert.NoError(t, err)
78 | case "options":
79 | err := OPTIONS("{{.Host}}/{{.Method}}", tc).BindBody(&body).Code(&code).Do()
80 | assert.NoError(t, err)
81 | case "head":
82 | code := 0
83 | err := HEAD("{{.Host}}/{{.Method}}", tc).Debug(true).BindBody(&body).Code(&code).Do()
84 | assert.NoError(t, err)
85 | err = New().SetMethod(strings.ToUpper(tc.Method)).SetURL("{{.Host}}/{{.Method}}", tc).Debug(true).BindBody(&body2).Code(&code).Do()
86 | assert.NoError(t, err)
87 | assert.Equal(t, code, 200)
88 | continue
89 | }
90 | assert.Equal(t, code, 200)
91 |
92 | err := New().SetMethod(strings.ToUpper(tc.Method)).SetURL("{{.Host}}/{{.Method}}", tc).Debug(true).BindBody(&body2).Do()
93 | assert.NoError(t, err)
94 | assert.Equal(t, body, tc.Method)
95 | b := assert.Equal(t, body2, tc.Method)
96 | if !b {
97 | return
98 | }
99 | }
100 |
101 | }
102 |
--------------------------------------------------------------------------------
/setprotobuf_test.go:
--------------------------------------------------------------------------------
1 | package gout
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "net/http"
7 | "net/http/httptest"
8 | "sync/atomic"
9 | "testing"
10 |
11 | "github.com/guonaihong/gout/testdata"
12 | "github.com/stretchr/testify/assert"
13 | "google.golang.org/protobuf/proto"
14 |
15 | "github.com/gin-gonic/gin"
16 | )
17 |
18 | func setupEcho(total *int32, t *testing.T) *gin.Engine {
19 |
20 | router := gin.Default()
21 |
22 | cb := func(c *gin.Context) {
23 | atomic.AddInt32(total, 1)
24 | _, err := io.Copy(c.Writer, c.Request.Body)
25 | assert.NoError(t, err)
26 | }
27 |
28 | router.GET("/echo", cb)
29 |
30 | return router
31 | }
32 |
33 | func Test_SetProtoBuf(t *testing.T) {
34 | total := int32(0)
35 | router := setupEcho(&total, t)
36 | ts := httptest.NewServer(http.HandlerFunc(router.ServeHTTP))
37 | defer ts.Close()
38 |
39 | data1, err := proto.Marshal(&testdata.Req{Seq: 1, Res: "fk"})
40 | assert.NoError(t, err)
41 |
42 | testCount := 0
43 | for i, v := range []interface{}{
44 | data1,
45 | &testdata.Req{Seq: 1, Res: "fk"},
46 | } {
47 | code := 0
48 | var gotRes []byte
49 | err := GET(ts.URL + "/echo").SetProtoBuf(v).BindBody(&gotRes).Code(&code).Do()
50 | assert.NoError(t, err)
51 | assert.Equal(t, code, 200)
52 | got := &testdata.Req{}
53 |
54 | err = proto.Unmarshal(gotRes, got)
55 | assert.NoError(t, err)
56 |
57 | assert.Equal(t, got.Seq, int32(1), fmt.Sprintf("fail index:%d", i))
58 | assert.Equal(t, got.Res, "fk")
59 | testCount++
60 | }
61 |
62 | assert.Equal(t, total, int32(testCount))
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/setting/setting.go:
--------------------------------------------------------------------------------
1 | package setting
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/guonaihong/gout/debug"
7 | )
8 |
9 | // 设置
10 | type Setting struct {
11 | // debug相关字段
12 | debug.Options
13 | // 控制是否使用空值
14 | NotIgnoreEmpty bool
15 |
16 | //是否自动加ContentType
17 | NoAutoContentType bool
18 | //超时时间
19 | Timeout time.Duration
20 |
21 | UseChunked bool
22 | }
23 |
24 | // 使用chunked数据
25 | func (s *Setting) Chunked() {
26 | s.UseChunked = true
27 | }
28 |
29 | func (s *Setting) SetTimeout(d time.Duration) {
30 | s.Timeout = d
31 | }
32 |
33 | func (s *Setting) SetDebug(b bool) {
34 | s.Debug = b
35 | }
36 |
37 | func (s *Setting) Reset() {
38 | s.NotIgnoreEmpty = false
39 | s.NoAutoContentType = false
40 | //s.TimeoutIndex = 0
41 | s.Timeout = time.Duration(0)
42 | }
43 |
--------------------------------------------------------------------------------
/testdata/color.data:
--------------------------------------------------------------------------------
1 | {
2 | [37;1m"array": [0m[
3 | [32;1m"foo"[0m,
4 | [32;1m"bar"[0m,
5 | [32;1m"baz"[0m
6 | ],
7 | [37;1m"bool": [0m[33;1mfalse[0m,
8 | [37;1m"null": [0m[35;1mnull[0m,
9 | [37;1m"num": [0m[36;1m100[0m,
10 | [37;1m"obj": [0m{
11 | [37;1m"a": [0m[36;1m1[0m,
12 | [37;1m"b": [0m[36;1m2[0m
13 | },
14 | [37;1m"str": [0m[32;1m"foo"[0m
15 | }
--------------------------------------------------------------------------------
/testdata/raw-http-post-formdata.txt:
--------------------------------------------------------------------------------
1 | POST / HTTP/1.1
2 | Host: 127.0.0.1:1234
3 | User-Agent: gurl
4 | Transfer-Encoding: chunked
5 | Accept: */*
6 | Content-Type: multipart/form-data; boundary=873b07ff17a9dcaac708c5751bdb88168205fde8ef782593925f3b3d03a9
7 | Accept-Encoding: gzip
8 |
9 | 6c
10 | --873b07ff17a9dcaac708c5751bdb88168205fde8ef782593925f3b3d03a9
11 | Content-Disposition: form-data; name="a"
12 |
13 |
14 | 1
15 | a
16 | 6e
17 |
18 | --873b07ff17a9dcaac708c5751bdb88168205fde8ef782593925f3b3d03a9
19 | Content-Disposition: form-data; name="b"
20 |
21 |
22 | 1
23 | b
24 | 44
25 |
26 | --873b07ff17a9dcaac708c5751bdb88168205fde8ef782593925f3b3d03a9--
27 |
28 | 0
29 |
30 | HTTP/1.1 200 OK
31 | Server: gurl-server
32 | Content-Length: 0
33 |
34 |
--------------------------------------------------------------------------------
/testdata/raw-http-post-json.txt:
--------------------------------------------------------------------------------
1 | POST /colorjson HTTP/1.1
2 | Host: 127.0.0.1:8080
3 | User-Agent: Go-http-client/1.1
4 | Content-Length: 97
5 | Content-Type: application/json
6 | Accept-Encoding: gzip
7 |
8 | {"array":["foo","bar","baz"],"bool":false,"null":null,"num":100,"obj":{"a":1,"b":2},"str":"foo"}
9 | HTTP/1.1 200 OK
10 | Content-Type: application/json; charset=utf-8
11 | Content-Length: 29
12 |
13 | {"int2":2,"str2":"str2 val"}
14 |
--------------------------------------------------------------------------------
/testdata/test.pcm:
--------------------------------------------------------------------------------
1 | pcmpcmpcm
2 |
--------------------------------------------------------------------------------
/testdata/test.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | option go_package = "github.com/guonaihong/gout/testdata";
4 |
5 | package testdata;
6 |
7 | message req {
8 | int32 seq = 1;
9 | string res = 2;
10 | }
11 |
--------------------------------------------------------------------------------
/testdata/voice.pcm:
--------------------------------------------------------------------------------
1 | pcmpcmpcm
2 |
--------------------------------------------------------------------------------
/trace_test.go:
--------------------------------------------------------------------------------
1 | package gout
2 |
3 | import (
4 | "bytes"
5 | "testing"
6 |
7 | "github.com/guonaihong/gout/dataflow"
8 | "github.com/guonaihong/gout/debug"
9 | )
10 |
11 | // 测试trace的入口函数
12 | func Test_Trace(t *testing.T) {
13 | t.Run("TraceJSONToWriter", func(t *testing.T) {
14 | //大约是这样的字符串
15 | //{"DnsDuration":0,"ConnDuration":1490250,"TLSDuration":0,"RequestDuration":33166,"WaitResponeDuration":258084,"ResponseDuration":10708,"TotalDuration":1810834}
16 | ts := createMethodEcho()
17 | defer ts.Close()
18 | var buf bytes.Buffer
19 | err := dataflow.New().GET(ts.URL).Debug(debug.TraceJSONToWriter(&buf)).Do()
20 | if err != nil {
21 | t.Fatal(err)
22 | }
23 | pos := bytes.Index(buf.Bytes(), []byte("ConnDuration"))
24 | if pos == -1 {
25 | t.Fatal("not found ConnDuration")
26 | }
27 | })
28 | t.Run("TraceJSON", func(t *testing.T) {
29 | ts := createMethodEcho()
30 | defer ts.Close()
31 | err := dataflow.New().GET(ts.URL).Debug(debug.TraceJSON()).Do()
32 | if err != nil {
33 | t.Fatal(err)
34 | }
35 | })
36 | }
37 |
--------------------------------------------------------------------------------
/utils.go:
--------------------------------------------------------------------------------
1 | package gout
2 |
3 | import (
4 | "github.com/guonaihong/gout/core"
5 | )
6 |
7 | // ReadCloseFail required for internal testing, external can be ignored
8 | type ReadCloseFail = core.ReadCloseFail
9 |
10 | // H is short for map[string]interface{}
11 | type H = core.H
12 |
13 | // A variable is short for []interface{}
14 | type A = core.A
15 |
16 | // FormFile encounter the FormFile variable in the form encoder will open the file from the file
17 | type FormFile = core.FormFile
18 |
19 | // FormMem when encountering the FormMem variable in the form encoder, it will be read from memory
20 | type FormMem = core.FormMem
21 |
22 | // FormType custom form names and stream types are available
23 | type FormType = core.FormType
24 |
--------------------------------------------------------------------------------
/version.go:
--------------------------------------------------------------------------------
1 | package gout
2 |
3 | // Version show version
4 | const Version = "v0.3.2"
5 |
--------------------------------------------------------------------------------
/version_test.go:
--------------------------------------------------------------------------------
1 | package gout
2 |
3 | import (
4 | "github.com/stretchr/testify/assert"
5 | "testing"
6 | )
7 |
8 | func Test_Version(t *testing.T) {
9 | assert.NotEqual(t, len(Version), 0)
10 | }
11 |
--------------------------------------------------------------------------------