├── .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 | "array": [ 3 | "foo", 4 | "bar", 5 | "baz" 6 | ], 7 | "bool": false, 8 | "null": null, 9 | "num": 100, 10 | "obj": { 11 | "a": 1, 12 | "b": 2 13 | }, 14 | "str": "foo" 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 | --------------------------------------------------------------------------------