pax_global_header 0000666 0000000 0000000 00000000064 14262463342 0014520 g ustar 00root root 0000000 0000000 52 comment=9e8b7c0264f477f31cdf28dadc783532eb2f2f6f gokit-0.25.9/ 0000775 0000000 0000000 00000000000 14262463342 0012732 5 ustar 00root root 0000000 0000000 gokit-0.25.9/.github/ 0000775 0000000 0000000 00000000000 14262463342 0014272 5 ustar 00root root 0000000 0000000 gokit-0.25.9/.github/workflows/ 0000775 0000000 0000000 00000000000 14262463342 0016327 5 ustar 00root root 0000000 0000000 gokit-0.25.9/.github/workflows/gotest.yaml 0000664 0000000 0000000 00000002706 14262463342 0020525 0 ustar 00root root 0000000 0000000 # gotest.yaml # Maintainer: https://www.likexian.com # Licensed under the Apache License 2.0 name: GoTest on: push: branches: - '**' tags-ignore: - '**' pull_request: types: [opened, synchronize, reopened] jobs: lint: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v3 - name: Golangci lint uses: golangci/golangci-lint-action@v3 with: version: latest args: -v test: strategy: fail-fast: false matrix: go: [1.13.x, 1.14.x, 1.15.x, 1.16.x, 1.17.x, 1.18.x] os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - name: Set up Go uses: actions/setup-go@v2 with: go-version: ${{ matrix.go }} - name: Checkout code uses: actions/checkout@v2 - name: GoTest code run: | go mod download # make go 1.16.x happy sudo go test -race -coverprofile="coverage.txt" -covermode=atomic ./... coverage=$(go tool cover -func=coverage.txt | grep total | grep -Eo '[0-9]+\.[0-9]+') echo "{\"coverage\": $coverage}" if [[ "${{ matrix.go }}" == "1.18.x" ]]; then repository=$(echo '${{ github.repository }}' | awk -F'/' '{print $2}') curl -A "coverage client/1.0.0" -H "X-Release-Token:${{ secrets.RELEASE_TOKEN }}" -F "coverage=$coverage" https://release.likexian.com/$repository/coverage fi gokit-0.25.9/.gitignore 0000664 0000000 0000000 00000000012 14262463342 0014713 0 ustar 00root root 0000000 0000000 .DS_Store gokit-0.25.9/.golangci.yml 0000664 0000000 0000000 00000001623 14262463342 0015320 0 ustar 00root root 0000000 0000000 # .golangci.yml # Maintainer: https://www.likexian.com # Licensed under the Apache License 2.0 run: timeout: 5m skip-dirs-use-default: true linters: disable-all: true enable: - deadcode - errcheck - gosimple - govet - ineffassign - staticcheck - structcheck - typecheck - unused - varcheck - cyclop - durationcheck - errname - errorlint - exportloopref - gofmt - goimports - lll - misspell - nilerr - nolintlint - prealloc - revive - rowserrcheck - sqlclosecheck - stylecheck - unconvert - wastedassign linters-settings: cyclop: max-complexity: 20 package-average: 10.0 skip-tests: true issues: max-issues-per-linter: 0 max-same-issues: 0 exclude-rules: - path: _test\.go linters: - errcheck - source: "^//go:generate " linters: - lll gokit-0.25.9/LICENSE 0000664 0000000 0000000 00000026236 14262463342 0013750 0 ustar 00root root 0000000 0000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2012-2022 Li Kexian Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. APPENDIX: Copyright 2012-2022 Li Kexian https://www.likexian.com/ gokit-0.25.9/README.md 0000664 0000000 0000000 00000002220 14262463342 0014205 0 ustar 00root root 0000000 0000000 # GoKit [](LICENSE) [](https://pkg.go.dev/github.com/likexian/gokit) [](https://goreportcard.com/report/github.com/likexian/gokit) [](https://github.com/likexian/gokit/actions/workflows/gotest.yaml) [](https://github.com/likexian/gokit/actions/workflows/gotest.yaml) A toolkit for Golang development. ## Installation ```shell go get -u github.com/likexian/gokit ``` ## Importing ```go import ( "github.com/likexian/gokit" ) ``` ## Documentation Visit the docs on [GoDoc](https://pkg.go.dev/github.com/likexian/gokit) ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/assert/ 0000775 0000000 0000000 00000000000 14262463342 0014233 5 ustar 00root root 0000000 0000000 gokit-0.25.9/assert/README.md 0000664 0000000 0000000 00000003103 14262463342 0015507 0 ustar 00root root 0000000 0000000 # GoKit - assert Assert kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/assert" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/assert) ## Example ### assert panic ```go func willItPanic() { panic("failed") } assert.Panic(t, willItPanic) ``` ### assert err is nil ```go fp, err := os.Open("/data/dev/gokit/LICENSE") assert.Nil(t, err) ``` ### assert equal ```go x := map[string]int{"a": 1, "b": 2} y := map[string]int{"a": 1, "b": 2} assert.Equal(t, x, y, "x shall equal to y") ``` ### check string in array ```go ok := assert.IsContains([]string{"a", "b", "c"}, "b") if ok { fmt.Println("value in array") } else { fmt.Println("value not in array") } ``` ### check string in interface array ```go ok := assert.IsContains([]interface{}{0, "1", 2}, "1") if ok { fmt.Println("value in array") } else { fmt.Println("value not in array") } ``` ### check object in struct array ```go ok := assert.IsContains([]A{A{0, 1}, A{1, 2}, A{1, 3}}, A{1, 2}) if ok { fmt.Println("value in array") } else { fmt.Println("value not in array") } ``` ### a := c ? x : y ```go a := 1 // b := a == 1 ? true : false b := assert.If(a == 1, true, false) ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/assert/assert.go 0000664 0000000 0000000 00000012331 14262463342 0016063 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package assert import ( "fmt" "reflect" "runtime" "testing" ) // Version returns package version func Version() string { return "0.12.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // Equal assert test value to be equal func Equal(t *testing.T, got, exp interface{}, args ...interface{}) { equal(t, got, exp, 1, args...) } // NotEqual assert test value to be not equal func NotEqual(t *testing.T, got, exp interface{}, args ...interface{}) { notEqual(t, got, exp, 1, args...) } // Nil assert test value to be nil func Nil(t *testing.T, got interface{}, args ...interface{}) { equal(t, got, nil, 1, args...) } // NotNil assert test value to be not nil func NotNil(t *testing.T, got interface{}, args ...interface{}) { notEqual(t, got, nil, 1, args...) } // True assert test value to be true func True(t *testing.T, got interface{}, args ...interface{}) { equal(t, got, true, 1, args...) } // False assert test value to be false func False(t *testing.T, got interface{}, args ...interface{}) { notEqual(t, got, true, 1, args...) } // Zero assert test value to be zero value func Zero(t *testing.T, got interface{}, args ...interface{}) { equal(t, IsZero(got), true, 1, args...) } // NotZero assert test value to be not zero value func NotZero(t *testing.T, got interface{}, args ...interface{}) { notEqual(t, IsZero(got), true, 1, args...) } // Len assert length of test vaue to be exp func Len(t *testing.T, got interface{}, exp int, args ...interface{}) { equal(t, Length(got), exp, 1, args...) } // NotLen assert length of test vaue to be not exp func NotLen(t *testing.T, got interface{}, exp int, args ...interface{}) { notEqual(t, Length(got), exp, 1, args...) } // Contains assert test value to be contains func Contains(t *testing.T, got, exp interface{}, args ...interface{}) { equal(t, IsContains(got, exp), true, 1, args...) } // NotContains assert test value to be contains func NotContains(t *testing.T, got, exp interface{}, args ...interface{}) { notEqual(t, IsContains(got, exp), true, 1, args...) } // Match assert test value match exp pattern func Match(t *testing.T, got, exp interface{}, args ...interface{}) { equal(t, IsMatch(got, exp), true, 1, args...) } // NotMatch assert test value not match exp pattern func NotMatch(t *testing.T, got, exp interface{}, args ...interface{}) { notEqual(t, IsMatch(got, exp), true, 1, args...) } // Lt assert test value less than exp func Lt(t *testing.T, got, exp interface{}, args ...interface{}) { equal(t, IsLt(got, exp), true, 1, args...) } // Le assert test value less than exp or equal func Le(t *testing.T, got, exp interface{}, args ...interface{}) { equal(t, IsLe(got, exp), true, 1, args...) } // Gt assert test value greater than exp func Gt(t *testing.T, got, exp interface{}, args ...interface{}) { equal(t, IsGt(got, exp), true, 1, args...) } // Ge assert test value greater than exp or equal func Ge(t *testing.T, got, exp interface{}, args ...interface{}) { equal(t, IsGe(got, exp), true, 1, args...) } // Panic assert testing to be panic func Panic(t *testing.T, fn func(), args ...interface{}) { defer func() { ff := func() { t.Error("! -", "assert expected to be panic") if len(args) > 0 { t.Error("! -", fmt.Sprint(args...)) } } ok := recover() != nil assert(t, ok, ff, 2) }() fn() } // NotPanic assert testing to be panic func NotPanic(t *testing.T, fn func(), args ...interface{}) { defer func() { ff := func() { t.Error("! -", "assert expected to be not panic") if len(args) > 0 { t.Error("! -", fmt.Sprint(args...)) } } ok := recover() == nil assert(t, ok, ff, 3) }() fn() } func equal(t *testing.T, got, exp interface{}, step int, args ...interface{}) { fn := func() { switch got.(type) { case error: t.Errorf("! unexpected error: \"%s\"", got) default: t.Errorf("! expected %#v, but got %#v", exp, got) } if len(args) > 0 { t.Error("! -", fmt.Sprint(args...)) } } ok := reflect.DeepEqual(exp, got) assert(t, ok, fn, step+1) } func notEqual(t *testing.T, got, exp interface{}, step int, args ...interface{}) { fn := func() { t.Errorf("! unexpected: %#v", got) if len(args) > 0 { t.Error("! -", fmt.Sprint(args...)) } } ok := !reflect.DeepEqual(exp, got) assert(t, ok, fn, step+1) } func assert(t *testing.T, pass bool, fn func(), step int) { if !pass { _, file, line, ok := runtime.Caller(step + 1) if ok { t.Errorf("%s:%d", file, line) } fn() t.FailNow() } } gokit-0.25.9/assert/assert_test.go 0000664 0000000 0000000 00000010714 14262463342 0017125 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package assert import ( "testing" ) type a struct { x, y int } type b struct { x, y int } func TestVersion(t *testing.T) { Contains(t, Version(), ".") Contains(t, Author(), "likexian") Contains(t, License(), "Apache License") } func TestEqual(t *testing.T) { Equal(t, nil, nil, "testing Equal failed") Equal(t, true, true, "testing Equal failed") Equal(t, "string", "string", "testing Equal failed") Equal(t, int(1.0), int(1.0), "testing Equal failed") Equal(t, int64(1.0), int64(1.0), "testing Equal failed") Equal(t, uint64(1.0), uint64(1.0), "testing Equal failed") Equal(t, float64(1.0), float64(1.0), "testing Equal failed") Equal(t, []string{"a", "b", "c"}, []string{"a", "b", "c"}, "testing Equal failed") Equal(t, []int{1, 2, 3}, []int{1, 2, 3}, "testing Equal failed") Equal(t, []float64{1.0, 2.0, 3.0}, []float64{1.0, 2.0, 3.0}, "testing Equal failed") Equal(t, map[string]int{"a": 1, "b": 2}, map[string]int{"a": 1, "b": 2}, "testing Equal failed") Equal(t, map[string]interface{}{"a": 1, "b": "2"}, map[string]interface{}{"a": 1, "b": "2"}, "testing Equal failed") Equal(t, map[string]interface{}{"a": []int{1, 2}}, map[string]interface{}{"a": []int{1, 2}}, "testing Equal failed") Equal(t, a{1, 2}, a{1, 2}, "testing Equal failed") } func TestNotEqual(t *testing.T) { NotEqual(t, nil, "", "testing NotEqual failed") NotEqual(t, true, false, "testing NotEqual failed") NotEqual(t, "string", "strings", "testing NotEqual failed") NotEqual(t, int(1.0), int(2.0), "testing NotEqual failed") NotEqual(t, int64(1.0), int64(2.0), "testing NotEqual failed") NotEqual(t, uint64(1.0), uint64(2.0), "testing NotEqual failed") NotEqual(t, float64(1.0), float64(2.0), "testing NotEqual failed") NotEqual(t, []string{"a", "b", "c"}, []string{"a", "b", "d"}, "testing NotEqual failed") NotEqual(t, []int{1, 2, 3}, []int{1, 2, 4}, "testing NotEqual failed") NotEqual(t, []float64{1.0, 2.0, 3.0}, []float64{1.0, 2.0, 4.0}, "testing NotEqual failed") NotEqual(t, map[string]int{"a": 1, "b": 2}, map[string]int{"a": 1, "b": 3}, "testing NotEqual failed") NotEqual(t, map[string]interface{}{"a": 1, "b": "2"}, map[string]interface{}{"a": 1, "b": "3"}, "testing NotEqual failed") NotEqual(t, map[string]interface{}{"a": []int{1, 2}}, map[string]interface{}{"a": []int{1, 3}}, "testing NotEqual failed") NotEqual(t, a{1, 1}, a{1, 2}, "testing NotEqual failed") NotEqual(t, a{1, 2}, b{1, 2}, "testing NotEqual failed") } func TestNil(t *testing.T) { Nil(t, nil, "testing expect to be nil") NotNil(t, true, "testing expect to be not nil") } func TestTrue(t *testing.T) { True(t, true, "testing expect to be true") False(t, false, "testing expect to be false") } func TestZero(t *testing.T) { Zero(t, []interface{}{}, "testing expect to be zero") NotZero(t, true, "testing expect to be not zero") } func TestContains(t *testing.T) { Contains(t, []int{1, 2, 3}, 2, "testing expect to be contains") NotContains(t, []string{"a", "b", "c"}, "d", "testing expect to be not contains") } func TestMatch(t *testing.T) { Match(t, "li*", "likexian", "testing expect to be match") NotMatch(t, "li.kexian", "likexian", "testing expect to be not match") } func TestLen(t *testing.T) { Len(t, []int{0, 1, 2}, 3, "length expect to be 3") NotLen(t, []int{0, 1, 2}, 1, "length expect to be not 1") } func TestLtGt(t *testing.T) { Lt(t, 1, 2, "testing expect to be less") Le(t, 1, 2, "testing expect to be less or equal") Le(t, 1, 1, "testing expect to be less or equal") Gt(t, 2, 1, "testing expect to be greater") Ge(t, 2, 1, "testing expect to be greater or equal") Ge(t, 1, 1, "testing expect to be greater or equal") } func TestPanic(t *testing.T) { Panic(t, func() { panic("failed") }) Panic(t, func() { panic("failed") }, "why not panic") } func TestNotPanic(t *testing.T) { NotPanic(t, func() {}) NotPanic(t, func() {}, "why panic") } gokit-0.25.9/assert/values.go 0000664 0000000 0000000 00000020117 14262463342 0016062 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package assert import ( "errors" "fmt" "reflect" "regexp" "strconv" "strings" ) var ( // ErrInvalid is value invalid for operation ErrInvalid = errors.New("assert: value if invalid") // ErrLess is expect to be greater error ErrLess = errors.New("assert: left is less the right") // ErrGreater is expect to be less error ErrGreater = errors.New("assert: left is greater then right") ) // Comparer is compare operator var Comparer = struct { LT string LE string GT string GE string }{ "<", "<=", ">", ">=", } // IsZero returns value is zero value func IsZero(v interface{}) bool { vv := reflect.ValueOf(v) switch vv.Kind() { case reflect.Invalid: return true case reflect.Bool: return !vv.Bool() case reflect.Ptr, reflect.Interface: return vv.IsNil() case reflect.Array, reflect.Slice, reflect.Map, reflect.String: return vv.Len() == 0 case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return vv.Int() == 0 case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return vv.Uint() == 0 case reflect.Float32, reflect.Float64: return vv.Float() == 0 default: return false } } // IsContains returns whether value is within array func IsContains(array interface{}, value interface{}) bool { vv := reflect.ValueOf(array) if vv.Kind() == reflect.Ptr || vv.Kind() == reflect.Interface { if vv.IsNil() { return false } vv = vv.Elem() } switch vv.Kind() { case reflect.Invalid: return false case reflect.Slice: for i := 0; i < vv.Len(); i++ { if reflect.DeepEqual(value, vv.Index(i).Interface()) { return true } } return false case reflect.Map: s := vv.MapKeys() for i := 0; i < len(s); i++ { if reflect.DeepEqual(value, s[i].Interface()) { return true } } return false case reflect.String: ss := reflect.ValueOf(value) switch ss.Kind() { case reflect.String: return strings.Contains(vv.String(), ss.String()) } return false default: return reflect.DeepEqual(array, value) } } // IsMatch returns if value v contains any match of pattern r // IsMatch(regexp.MustCompile("v\d+"), "v100") // IsMatch("v\d+", "v100") // IsMatch("\d+\.\d+", 100.1) func IsMatch(r interface{}, v interface{}) bool { var re *regexp.Regexp if v, ok := r.(*regexp.Regexp); ok { re = v } else { re = regexp.MustCompile(fmt.Sprint(r)) } return re.MatchString(fmt.Sprint(v)) } // Length returns length of value func Length(v interface{}) int { vv := reflect.ValueOf(v) if vv.Kind() == reflect.Ptr || vv.Kind() == reflect.Interface { if vv.IsNil() { return 0 } vv = vv.Elem() } switch vv.Kind() { case reflect.Invalid: return 0 case reflect.Ptr, reflect.Interface: return 0 case reflect.Array, reflect.Slice, reflect.Map, reflect.String: return vv.Len() default: return len(fmt.Sprintf("%#v", v)) } } // IsLt returns if x less than y, value invalid will returns false func IsLt(x, y interface{}) bool { return Compare(x, y, Comparer.LT) == nil } // IsLe returns if x less than or equal to y, value invalid will returns false func IsLe(x, y interface{}) bool { return Compare(x, y, Comparer.LE) == nil } // IsGt returns if x greater than y, value invalid will returns false func IsGt(x, y interface{}) bool { return Compare(x, y, Comparer.GT) == nil } // IsGe returns if x greater than or equal to y, value invalid will returns false func IsGe(x, y interface{}) bool { return Compare(x, y, Comparer.GE) == nil } // Compare compare x and y, by operation // It returns nil for true, ErrInvalid for invalid operation, err for false // Compare(1, 2, ">") // number compare -> true // Compare("a", "a", ">=") // string compare -> true // Compare([]string{"a", "b"}, []string{"a"}, "<") // slice len compare -> false func Compare(x, y interface{}, op string) error { //nolint:cyclop if !IsContains([]string{Comparer.LT, Comparer.LE, Comparer.GT, Comparer.GE}, op) { return ErrInvalid } vv := reflect.ValueOf(x) if vv.Kind() == reflect.Ptr || vv.Kind() == reflect.Interface { if vv.IsNil() { return ErrInvalid } vv = vv.Elem() } var c float64 switch vv.Kind() { case reflect.Invalid: return ErrInvalid case reflect.String: yy := reflect.ValueOf(y) switch yy.Kind() { case reflect.String: c = float64(strings.Compare(vv.String(), yy.String())) default: return ErrInvalid } case reflect.Slice, reflect.Map, reflect.Array: yy := reflect.ValueOf(y) switch yy.Kind() { case reflect.Slice, reflect.Map, reflect.Array: c = float64(vv.Len() - yy.Len()) default: return ErrInvalid } case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: yy, err := ToInt64(y) if err != nil { return ErrInvalid } c = float64(vv.Int() - yy) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: yy, err := ToUint64(y) if err != nil { return ErrInvalid } c = float64(vv.Uint()) - float64(yy) case reflect.Float32, reflect.Float64: yy, err := ToFloat64(y) if err != nil { return ErrInvalid } c = vv.Float() - yy default: return ErrInvalid } switch { case c < 0: switch op { case Comparer.LT, Comparer.LE: return nil default: return ErrLess } case c > 0: switch op { case Comparer.GT, Comparer.GE: return nil default: return ErrGreater } default: switch op { case Comparer.LT: return ErrGreater case Comparer.GT: return ErrLess default: return nil } } } // ToInt64 returns int value for int or uint or float func ToInt64(v interface{}) (int64, error) { vv := reflect.ValueOf(v) switch vv.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return vv.Int(), nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return int64(vv.Uint()), nil case reflect.Float32, reflect.Float64: return int64(vv.Float()), nil case reflect.String: r, err := strconv.ParseInt(vv.String(), 10, 64) if err != nil { return 0, ErrInvalid } return r, nil default: return 0, ErrInvalid } } // ToUint64 returns uint value for int or uint or float func ToUint64(v interface{}) (uint64, error) { vv := reflect.ValueOf(v) switch vv.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return uint64(vv.Int()), nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return vv.Uint(), nil case reflect.Float32, reflect.Float64: return uint64(vv.Float()), nil case reflect.String: r, err := strconv.ParseUint(vv.String(), 10, 64) if err != nil { return 0, ErrInvalid } return r, nil default: return 0, ErrInvalid } } // ToFloat64 returns float64 value for int or uint or float func ToFloat64(v interface{}) (float64, error) { vv := reflect.ValueOf(v) switch vv.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return float64(vv.Int()), nil case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: return float64(vv.Uint()), nil case reflect.Float32, reflect.Float64: return vv.Float(), nil case reflect.String: r, err := strconv.ParseFloat(vv.String(), 64) if err != nil { return 0, ErrInvalid } return r, nil default: return 0, ErrInvalid } } // If returns x if c is true, else y // z = If(c, x, y) // equal to: // z = c ? x : y func If(c bool, x, y interface{}) interface{} { if c { return x } return y } gokit-0.25.9/assert/values_test.go 0000664 0000000 0000000 00000027722 14262463342 0017132 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package assert import ( "regexp" "testing" ) func TestIsZero(t *testing.T) { var i interface{} tests := []interface{}{ i, "", false, []byte{}, []int{}, []string{}, map[string]int{}, map[string]string{}, map[string]interface{}{}, 0, int(0), int8(0), int32(0), int64(0), uint(0), uint8(0), uint32(0), uint64(0), float32(0), float64(0), } for _, v := range tests { True(t, IsZero(v)) } i = "a" tests = []interface{}{ i, &i, "a", true, []byte{0}, []int{0}, []string{"a"}, map[string]int{"a": 1}, map[string]string{"a": ""}, map[string]interface{}{"a": "b"}, 1, int(1), int8(1), int32(1), int64(1), uint(1), uint8(1), uint32(1), uint64(1), float32(0.1), float64(0.1), struct{ x int }{1}, } for _, v := range tests { False(t, IsZero(v)) } } func TestIsContains(t *testing.T) { var s *string var i interface{} = s tests := []struct { x interface{} y interface{} z bool }{ {nil, nil, false}, {s, s, false}, {&i, &i, true}, {[]int{0, 1, 2}, 1, true}, {[]int{0, 1, 2}, 3, false}, {[]int{0, 1, 2}, int64(1), false}, {[]int{0, 1, 2}, "1", false}, {[]int{0, 1, 2}, true, false}, {[]int64{0, 1, 2}, int64(1), true}, {[]int64{0, 1, 2}, int64(3), false}, {[]int64{0, 1, 2}, 1, false}, {[]int64{0, 1, 2}, "1", false}, {[]int64{0, 1, 2}, true, false}, {[]float64{0.0, 1.0, 2.0}, 1.0, true}, {[]float64{0.0, 1.0, 2.0}, float64(1), true}, {[]float64{0.0, 1.0, 2.0}, 3.0, false}, {[]float64{0.0, 1.0, 2.0}, 1, false}, {[]string{"a", "b", "c"}, "a", true}, {[]string{"a", "b", "c"}, "d", false}, {[]string{"a", "b", "c"}, 1, false}, {[]string{"a", "b", "c"}, true, false}, {[]interface{}{0, "1", 2}, "1", true}, {[]interface{}{0, "1", 2}, 1, false}, {[]interface{}{0, 1, 2}, true, false}, {[]interface{}{0, true, 2}, true, true}, {[]interface{}{0, false, 2}, true, false}, {[]interface{}{[]int{0, 1}, []int{1, 2}}, []int{1, 2}, true}, {[]interface{}{[]int{0, 1}, []int{1, 2, 3}}, []int{1, 2}, false}, {[]a{{0, 1}, {1, 2}, {1, 3}}, a{1, 2}, true}, {[]interface{}{a{0, 1}, b{1, 2}, a{1, 3}}, b{1, 2}, true}, {[]interface{}{a{0, 1}, b{1, 2}, a{1, 3}}, a{1, 2}, false}, {map[string]int{"a": 1}, "a", true}, {map[string]int{"a": 1}, "d", false}, {map[string]int{"a": 1}, 1, false}, {map[string]int{"a": 1}, true, false}, {"abc", "a", true}, {"abc", "d", false}, {"abc", 1, false}, {"abc", true, false}, {"a", "a", true}, {1, 1, true}, {-1, -1, true}, {1.0, 1.0, true}, {true, true, true}, {false, false, true}, } for _, v := range tests { Equal(t, IsContains(v.x, v.y), v.z) } } func TestIsMatch(t *testing.T) { var i interface{} tests := []struct { x interface{} y interface{} z bool }{ {regexp.MustCompile(`v\d+`), "v100", true}, {`v\d+`, "v100", true}, {`\d+\.\d+`, 100.1, true}, {regexp.MustCompile(`v\d+`), "x100", false}, {`v\d+`, "x100", false}, {`\d+\.\d+`, "x100", false}, {`v\d+`, i, false}, {i, 100.1, false}, } for _, v := range tests { vv := IsMatch(v.x, v.y) Equal(t, vv, v.z, v) } } func TestLength(t *testing.T) { var s *string var i interface{} = s tests := []struct { in interface{} out int }{ {nil, 0}, {s, 0}, {&i, 0}, {"", 0}, {"1", 1}, {true, 4}, {false, 5}, {[]byte{}, 0}, {[]byte{0}, 1}, {[]int{}, 0}, {[]int{0}, 1}, {[]string{}, 0}, {[]string{"a"}, 1}, {map[string]int{}, 0}, {map[string]int{"a": 1}, 1}, {map[string]string{}, 0}, {map[string]string{"a": "b"}, 1}, {map[string]interface{}{}, 0}, {map[string]interface{}{"a": i}, 1}, {0, 1}, {1, 1}, {int(0), 1}, {int(1), 1}, {int8(0), 1}, {int8(1), 1}, {int32(0), 1}, {int32(1), 1}, {int64(0), 1}, {int64(1), 1}, {uint(0), 3}, {uint(1), 3}, {uint8(0), 3}, {uint8(1), 3}, {uint32(0), 3}, {uint32(1), 3}, {uint64(0), 3}, {uint64(1), 3}, {float32(0), 1}, {float32(1), 1}, {float32(0.1), 3}, {float64(0), 1}, {float64(1), 1}, {float64(0.1), 3}, } for _, v := range tests { Equal(t, Length(v.in), v.out) } } func TestCompare(t *testing.T) { var s *string var i interface{} = s tests := []struct { x interface{} y interface{} op string err error }{ {nil, nil, "", ErrInvalid}, {nil, nil, Comparer.LT, ErrInvalid}, {s, s, Comparer.LT, ErrInvalid}, {&i, &i, Comparer.LT, ErrInvalid}, {"a", "b", Comparer.LT, nil}, {"b", "a", Comparer.LT, ErrGreater}, {"a", "a", Comparer.LT, ErrGreater}, {"a", 1, Comparer.LT, ErrInvalid}, {"a", "b", Comparer.LE, nil}, {"b", "a", Comparer.LE, ErrGreater}, {"a", "a", Comparer.LE, nil}, {"a", 1, Comparer.LE, ErrInvalid}, {"b", "a", Comparer.GT, nil}, {"a", "b", Comparer.GT, ErrLess}, {"a", "a", Comparer.GT, ErrLess}, {"a", 1, Comparer.GT, ErrInvalid}, {"b", "a", Comparer.GE, nil}, {"a", "b", Comparer.GE, ErrLess}, {"a", "a", Comparer.GE, nil}, {"a", 1, Comparer.GE, ErrInvalid}, {int(1), int(2), Comparer.LT, nil}, {int(2), int(1), Comparer.LT, ErrGreater}, {int(1), int(1), Comparer.LT, ErrGreater}, {int(1), "1", Comparer.LT, ErrGreater}, {int(1), "a", Comparer.LT, ErrInvalid}, {int(1), int(2), Comparer.LE, nil}, {int(2), int(1), Comparer.LE, ErrGreater}, {int(1), int(1), Comparer.LE, nil}, {int(1), "1", Comparer.LE, nil}, {int(1), "a", Comparer.LE, ErrInvalid}, {int(2), int(1), Comparer.GT, nil}, {int(1), int(2), Comparer.GT, ErrLess}, {int(1), int(1), Comparer.GT, ErrLess}, {int(1), "1", Comparer.GT, ErrLess}, {int(1), "a", Comparer.GT, ErrInvalid}, {int(2), int(1), Comparer.GE, nil}, {int(1), int(2), Comparer.GE, ErrLess}, {int(1), int(1), Comparer.GE, nil}, {int(1), "1", Comparer.GE, nil}, {int(1), "a", Comparer.GE, ErrInvalid}, {uint(1), uint(2), Comparer.LT, nil}, {uint(2), uint(1), Comparer.LT, ErrGreater}, {uint(1), uint(1), Comparer.LT, ErrGreater}, {uint(1), "1", Comparer.LT, ErrGreater}, {uint(1), "a", Comparer.LT, ErrInvalid}, {uint(1), uint(2), Comparer.LE, nil}, {uint(2), uint(1), Comparer.LE, ErrGreater}, {uint(1), uint(1), Comparer.LE, nil}, {uint(1), "1", Comparer.LE, nil}, {uint(1), "a", Comparer.LE, ErrInvalid}, {uint(2), uint(1), Comparer.GT, nil}, {uint(1), uint(2), Comparer.GT, ErrLess}, {uint(1), uint(1), Comparer.GT, ErrLess}, {uint(1), "1", Comparer.GT, ErrLess}, {uint(1), "a", Comparer.GT, ErrInvalid}, {uint(2), uint(1), Comparer.GE, nil}, {uint(1), uint(2), Comparer.GE, ErrLess}, {uint(1), uint(1), Comparer.GE, nil}, {uint(1), "1", Comparer.GE, nil}, {uint(1), "a", Comparer.GE, ErrInvalid}, {float64(1), float64(2), Comparer.LT, nil}, {float64(2), float64(1), Comparer.LT, ErrGreater}, {float64(1), float64(1), Comparer.LT, ErrGreater}, {float64(1), "1", Comparer.LT, ErrGreater}, {float64(1), "a", Comparer.LT, ErrInvalid}, {float64(1), float64(2), Comparer.LE, nil}, {float64(2), float64(1), Comparer.LE, ErrGreater}, {float64(1), float64(1), Comparer.LE, nil}, {float64(1), "1", Comparer.LE, nil}, {float64(1), "a", Comparer.LE, ErrInvalid}, {float64(2), float64(1), Comparer.GT, nil}, {float64(1), float64(2), Comparer.GT, ErrLess}, {float64(1), float64(1), Comparer.GT, ErrLess}, {float64(1), "1", Comparer.GT, ErrLess}, {float64(1), "a", Comparer.GT, ErrInvalid}, {float64(2), float64(1), Comparer.GE, nil}, {float64(1), float64(2), Comparer.GE, ErrLess}, {float64(1), float64(1), Comparer.GE, nil}, {float64(1), "1", Comparer.GE, nil}, {float64(1), "a", Comparer.GE, ErrInvalid}, {[]int{1}, []int{1, 2}, Comparer.LT, nil}, {[]int{1, 2}, []int{1}, Comparer.LT, ErrGreater}, {[]int{1}, []int{1}, Comparer.LT, ErrGreater}, {[]int{1}, "1", Comparer.LT, ErrInvalid}, {[]int{1}, []int{1, 2}, Comparer.LE, nil}, {[]int{1, 2}, []int{1}, Comparer.LE, ErrGreater}, {[]int{1}, []int{1}, Comparer.LE, nil}, {[]int{1}, "1", Comparer.LE, ErrInvalid}, {[]int{1, 2}, []int{1}, Comparer.GT, nil}, {[]int{1}, []int{1, 2}, Comparer.GT, ErrLess}, {[]int{1}, []int{1}, Comparer.GT, ErrLess}, {[]int{1}, "1", Comparer.GT, ErrInvalid}, {[]int{1, 2}, []int{1}, Comparer.GE, nil}, {[]int{1}, []int{1, 2}, Comparer.GE, ErrLess}, {[]int{1}, []int{1}, Comparer.GE, nil}, {[]int{1}, "1", Comparer.GE, ErrInvalid}, {map[string]int{"a": 1}, map[string]int{"a": 1, "b": 2}, Comparer.LT, nil}, {map[string]int{"a": 1, "b": 2}, map[string]int{"a": 1}, Comparer.LT, ErrGreater}, {map[string]int{"a": 1}, map[string]int{"a": 1}, Comparer.LT, ErrGreater}, {map[string]int{"a": 1}, "1", Comparer.LT, ErrInvalid}, {map[string]int{"a": 1}, map[string]int{"a": 1, "b": 2}, Comparer.LE, nil}, {map[string]int{"a": 1, "b": 2}, map[string]int{"a": 1}, Comparer.LE, ErrGreater}, {map[string]int{"a": 1}, map[string]int{"a": 1}, Comparer.LE, nil}, {map[string]int{"a": 1}, "1", Comparer.LE, ErrInvalid}, {map[string]int{"a": 1, "b": 2}, map[string]int{"a": 1}, Comparer.GT, nil}, {map[string]int{"a": 1}, map[string]int{"a": 1, "b": 2}, Comparer.GT, ErrLess}, {map[string]int{"a": 1}, map[string]int{"a": 1}, Comparer.GT, ErrLess}, {map[string]int{"a": 1}, "1", Comparer.GT, ErrInvalid}, {map[string]int{"a": 1, "b": 2}, map[string]int{"a": 1}, Comparer.GE, nil}, {map[string]int{"a": 1}, map[string]int{"a": 1, "b": 2}, Comparer.GE, ErrLess}, {map[string]int{"a": 1}, map[string]int{"a": 1}, Comparer.GE, nil}, {map[string]int{"a": 1}, "1", Comparer.GE, ErrInvalid}, } for _, v := range tests { vv := Compare(v.x, v.y, v.op) Equal(t, vv, v.err) if v.op == Comparer.LT { Equal(t, IsLt(v.x, v.y), v.err == nil) } if v.op == Comparer.LE { Equal(t, IsLe(v.x, v.y), v.err == nil) } if v.op == Comparer.GT { Equal(t, IsGt(v.x, v.y), v.err == nil) } if v.op == Comparer.GE { Equal(t, IsGe(v.x, v.y), v.err == nil) } } } func TestToInt64(t *testing.T) { tests := []struct { in interface{} out interface{} err error }{ {int64(1), int64(1), nil}, {uint64(1), int64(1), nil}, {float64(1), int64(1), nil}, {"1", int64(1), nil}, {"1a", int64(0), ErrInvalid}, {"aa", int64(0), ErrInvalid}, {true, int64(0), ErrInvalid}, {[]int{1}, int64(0), ErrInvalid}, {map[string]int{"a": 1}, int64(0), ErrInvalid}, } for _, v := range tests { vv, err := ToInt64(v.in) Equal(t, err, v.err) Equal(t, vv, v.out) } } func TestToUint64(t *testing.T) { tests := []struct { in interface{} out interface{} err error }{ {int64(1), uint64(1), nil}, {uint64(1), uint64(1), nil}, {float64(1), uint64(1), nil}, {"1", uint64(1), nil}, {"1a", uint64(0), ErrInvalid}, {"aa", uint64(0), ErrInvalid}, {true, uint64(0), ErrInvalid}, {[]int{1}, uint64(0), ErrInvalid}, {map[string]int{"a": 1}, uint64(0), ErrInvalid}, } for _, v := range tests { vv, err := ToUint64(v.in) Equal(t, err, v.err) Equal(t, vv, v.out) } } func TestToFloat64(t *testing.T) { tests := []struct { in interface{} out interface{} err error }{ {int64(1), float64(1), nil}, {uint64(1), float64(1), nil}, {float64(1), float64(1), nil}, {"1", float64(1), nil}, {"1a", float64(0), ErrInvalid}, {"aa", float64(0), ErrInvalid}, {true, float64(0), ErrInvalid}, {[]int{1}, float64(0), ErrInvalid}, {map[string]int{"a": 1}, float64(0), ErrInvalid}, } for _, v := range tests { vv, err := ToFloat64(v.in) Equal(t, err, v.err) Equal(t, vv, v.out) } } func TestIf(t *testing.T) { n := 50 z := If(n >= 60, "pass", "fail") Equal(t, z, "fail") n = 80 z = If(n >= 60, "pass", "fail") Equal(t, z, "pass") } gokit-0.25.9/go.mod 0000664 0000000 0000000 00000000114 14262463342 0014034 0 ustar 00root root 0000000 0000000 module github.com/likexian/gokit go 1.18 require golang.org/x/text v0.3.7 gokit-0.25.9/go.sum 0000664 0000000 0000000 00000000231 14262463342 0014061 0 ustar 00root root 0000000 0000000 golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= gokit-0.25.9/xaes/ 0000775 0000000 0000000 00000000000 14262463342 0013672 5 ustar 00root root 0000000 0000000 gokit-0.25.9/xaes/README.md 0000664 0000000 0000000 00000001523 14262463342 0015152 0 ustar 00root root 0000000 0000000 # GoKit - xaes AES kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xaes" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xaes) ## Example ```go // Encrypt ciphertext, err := xaes.CBCEncrypt(plaintext, key, iv) if err != nil { panic(err) } fmt.Printf("Encrypted: %x", ciphertext) // Decrypt plaintext, err := CBCDecrypt(ciphertext, key, iv) if err != nil { panic(err) } fmt.Printf("Decrypted: %s", plaintext) ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xaes/cbc.go 0000664 0000000 0000000 00000012006 14262463342 0014747 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package xaes import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/sha256" "errors" "fmt" "github.com/likexian/gokit/xhash" "github.com/likexian/gokit/xrand" ) var ( // ErrMissingEncryptKey is missing encrypt key error ErrMissingEncryptKey = errors.New("xaes: key for encrypting is missing") // ErrMissingDecryptKey is missing decrypt key error ErrMissingDecryptKey = errors.New("xaes: key for decrypting is missing") // ErrInvalidIVSize is invalid IV size error ErrInvalidIVSize = fmt.Errorf("xaes: length of iv must be %d", aes.BlockSize) // ErrInvalidCiphertextSize is invalid ciphertext size error ErrInvalidCiphertextSize = fmt.Errorf("xaes: length of ciphertext must be greater than %d", aes.BlockSize+sha256.Size) // ErrInvalidHmac is invalid hmac error ErrInvalidHmac = fmt.Errorf("xaes: hmac is invalid") // ErrInvalidPKCS7Padding is invalid pkcs7 padding ErrInvalidPKCS7Padding = fmt.Errorf("xaes: invalid PKCS7 padding") ) // CBCEncryptWithHmac do AES CBC encrypt then HMAC func CBCEncryptWithHmac(plaintext, key, iv []byte) ([]byte, error) { if plaintext == nil { return nil, nil } if key == nil { return nil, ErrMissingEncryptKey } if iv == nil { var err error iv, err = xrand.Bytes(aes.BlockSize) if err != nil { return nil, err } } hashKey := xhash.Sha256(key).Bytes() ciphertext, err := CBCEncrypt(plaintext, hashKey[:sha256.Size/2], iv) if err != nil { return nil, err } hmactext := xhash.HmacSha256(string(hashKey[sha256.Size/2:]), iv).Bytes() hmactext = xhash.HmacSha256(string(hmactext), ciphertext).Bytes() ciphertext = append(ciphertext, iv...) ciphertext = append(ciphertext, hmactext...) return ciphertext, nil } // CBCDecryptWithHmac do AES CBC decrypt with HMAC func CBCDecryptWithHmac(ciphertext, key []byte) ([]byte, error) { if ciphertext == nil { return nil, nil } if len(ciphertext) <= aes.BlockSize+sha256.Size { return nil, ErrInvalidCiphertextSize } if key == nil { return nil, ErrMissingDecryptKey } hashKey := xhash.Sha256(key).Bytes() hmactextIn := ciphertext[len(ciphertext)-sha256.Size:] iv := ciphertext[len(ciphertext)-sha256.Size-aes.BlockSize : len(ciphertext)-sha256.Size] ciphertext = ciphertext[:len(ciphertext)-sha256.Size-aes.BlockSize] hmactext := xhash.HmacSha256(string(hashKey[sha256.Size/2:]), iv).Bytes() hmactext = xhash.HmacSha256(string(hmactext), ciphertext).Bytes() if !bytes.Equal(hmactext, hmactextIn) { return nil, ErrInvalidHmac } return CBCDecrypt(ciphertext, hashKey[:sha256.Size/2], iv) } // CBCEncrypt do AES CBC encrypt func CBCEncrypt(plaintext, key, iv []byte) ([]byte, error) { if plaintext == nil { return nil, nil } if key == nil { return nil, ErrMissingEncryptKey } if iv == nil { iv = make([]byte, aes.BlockSize) } if len(iv) != aes.BlockSize { return nil, ErrInvalidIVSize } block, err := aes.NewCipher(key) if err != nil { return nil, err } blockSize := block.BlockSize() plaintext, err = PKCS7Padding(plaintext, blockSize) if err != nil { return nil, err } ciphertext := make([]byte, len(plaintext)) blockMode := cipher.NewCBCEncrypter(block, iv) blockMode.CryptBlocks(ciphertext, plaintext) return ciphertext, nil } // CBCDecrypt do AES CBC decrypt func CBCDecrypt(ciphertext, key, iv []byte) ([]byte, error) { if ciphertext == nil { return nil, nil } if key == nil { return nil, ErrMissingDecryptKey } if iv == nil { iv = make([]byte, aes.BlockSize) } if len(iv) != aes.BlockSize { return nil, ErrInvalidIVSize } block, err := aes.NewCipher(key) if err != nil { return nil, err } plaintext := make([]byte, len(ciphertext)) blockMode := cipher.NewCBCDecrypter(block, iv) blockMode.CryptBlocks(plaintext, ciphertext) plaintext, err = PKCS7Unpadding(plaintext) if err != nil { return nil, err } return plaintext, nil } // PKCS7Padding do PKCS7 padding func PKCS7Padding(data []byte, blockSize int) ([]byte, error) { if len(data) == 0 { return nil, ErrInvalidPKCS7Padding } paddingSize := blockSize - len(data)%blockSize paddingText := bytes.Repeat([]byte{byte(paddingSize)}, paddingSize) return append(data, paddingText...), nil } // PKCS7Unpadding do PKCS7 unpadding func PKCS7Unpadding(data []byte) ([]byte, error) { dataSize := len(data) paddingSize := int(data[dataSize-1]) if dataSize < paddingSize { return nil, ErrInvalidPKCS7Padding } return data[:(dataSize - paddingSize)], nil } gokit-0.25.9/xaes/cbc_test.go 0000664 0000000 0000000 00000010404 14262463342 0016006 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package xaes import ( "crypto/aes" "strconv" "testing" "github.com/likexian/gokit/assert" ) var ( cbcAESKey = []byte("1234567812345678") cbcPlaintext = []byte("hello xaes!") cbcCiphertext = []byte{32, 73, 238, 61, 249, 194, 179, 122, 136, 105, 227, 59, 55, 89, 10, 97} cbcHmacCiphertext = []byte{56, 91, 201, 6, 75, 116, 161, 43, 218, 129, 203, 149, 23, 197, 144, 175, 49, 50, 51, 52, 53, 54, 55, 56, 49, 50, 51, 52, 53, 54, 55, 56, 183, 182, 120, 158, 253, 33, 84, 16, 240, 33, 44, 163, 38, 26, 103, 57, 96, 131, 128, 47, 251, 7, 180, 234, 107, 134, 0, 126, 1, 1, 227, 15, } ) func TestCBCEncrypt(t *testing.T) { ciphertext, err := CBCEncrypt(nil, cbcAESKey, nil) assert.Nil(t, err) assert.Equal(t, ciphertext, []byte(nil)) _, err = CBCEncrypt(cbcPlaintext, nil, nil) assert.NotNil(t, err) _, err = CBCEncrypt(cbcPlaintext, cbcAESKey[:1], nil) assert.NotNil(t, err) _, err = CBCEncrypt(cbcPlaintext, cbcAESKey, cbcAESKey[:1]) assert.NotNil(t, err) ciphertext, err = CBCEncrypt(cbcPlaintext, cbcAESKey, nil) assert.Nil(t, err) assert.Equal(t, ciphertext, cbcCiphertext) } func TestCBCDecrypt(t *testing.T) { plaintext, err := CBCDecrypt(nil, cbcAESKey, nil) assert.Nil(t, err) assert.Equal(t, plaintext, []byte(nil)) _, err = CBCDecrypt(cbcCiphertext, nil, nil) assert.NotNil(t, err) _, err = CBCDecrypt(cbcCiphertext, cbcAESKey[:1], nil) assert.NotNil(t, err) _, err = CBCDecrypt(cbcCiphertext, cbcAESKey, cbcAESKey[:1]) assert.NotNil(t, err) _, err = CBCDecrypt(cbcCiphertext, []byte("1234567812345677"), nil) assert.NotNil(t, err) plaintext, err = CBCDecrypt(cbcCiphertext, cbcAESKey, nil) assert.Nil(t, err) assert.Equal(t, plaintext, cbcPlaintext) } func TestCBC(t *testing.T) { data := "" for i := 0; i < 1000; i++ { data += strconv.Itoa(i) ciphertext, err := CBCEncrypt([]byte(data), cbcAESKey, nil) assert.Nil(t, err) plaintext, err := CBCDecrypt(ciphertext, cbcAESKey, nil) assert.Nil(t, err) assert.Equal(t, plaintext, []byte(data)) } } func TestCBCEncryptWithHmacWithHmac(t *testing.T) { ciphertext, err := CBCEncryptWithHmac(nil, cbcAESKey, nil) assert.Nil(t, err) assert.Equal(t, ciphertext, []byte(nil)) _, err = CBCEncryptWithHmac(cbcPlaintext, nil, nil) assert.NotNil(t, err) _, err = CBCEncryptWithHmac(cbcPlaintext, cbcAESKey[:1], nil) assert.Nil(t, err) _, err = CBCEncryptWithHmac(cbcPlaintext, cbcAESKey, cbcAESKey[:1]) assert.NotNil(t, err) ciphertext, err = CBCEncryptWithHmac(cbcPlaintext, cbcAESKey, cbcAESKey) assert.Nil(t, err) assert.Equal(t, ciphertext, cbcHmacCiphertext) } func TestCBCDecryptWithHmac(t *testing.T) { plaintext, err := CBCDecryptWithHmac(nil, cbcAESKey) assert.Nil(t, err) assert.Equal(t, plaintext, []byte(nil)) _, err = CBCDecryptWithHmac(cbcHmacCiphertext, nil) assert.NotNil(t, err) _, err = CBCDecryptWithHmac(cbcHmacCiphertext, cbcAESKey[:1]) assert.NotNil(t, err) _, err = CBCDecryptWithHmac(cbcHmacCiphertext[:1], cbcAESKey) assert.NotNil(t, err) _, err = CBCDecryptWithHmac(append(cbcHmacCiphertext, '1'), cbcAESKey) assert.NotNil(t, err) plaintext, err = CBCDecryptWithHmac(cbcHmacCiphertext, cbcAESKey) assert.Nil(t, err) assert.Equal(t, plaintext, cbcPlaintext) } func TestCBCWithHmac(t *testing.T) { data := "" for i := 0; i < 1000; i++ { data += strconv.Itoa(i) ciphertext, err := CBCEncryptWithHmac([]byte(data), cbcAESKey, nil) assert.Nil(t, err) plaintext, err := CBCDecryptWithHmac(ciphertext, cbcAESKey) assert.Nil(t, err) assert.Equal(t, plaintext, []byte(data)) } } func TestPKCS7Padding(t *testing.T) { _, err := PKCS7Padding(nil, aes.BlockSize) assert.NotNil(t, err) } gokit-0.25.9/xaes/xaes.go 0000664 0000000 0000000 00000001720 14262463342 0015161 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package xaes // Version returns package version func Version() string { return "0.3.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } gokit-0.25.9/xaes/xaes_test.go 0000664 0000000 0000000 00000001607 14262463342 0016224 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package xaes import ( "testing" "github.com/likexian/gokit/assert" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } gokit-0.25.9/xcache/ 0000775 0000000 0000000 00000000000 14262463342 0014165 5 ustar 00root root 0000000 0000000 gokit-0.25.9/xcache/README.md 0000664 0000000 0000000 00000002037 14262463342 0015446 0 ustar 00root root 0000000 0000000 # GoKit - xcache Cache kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xcache" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xcache) ## Example ### Use memory cache ```go // init memory cache c := xcache.New(xcache.MemoryCache) // set gc param, gc every 60s, once clean max 100 c.SetGC(60, 100) // set key value cache with no expire c.Set("key", "value", 0) // set key value cache with ttl, expire after 30s c.Set("key", "value", 30) // check key exists c.Has("key") // get value c.Get("key") // remove key c.Del("key") // get multiple once c.MGet("k1", "k2", "k3") // do not forget stop the service c.Close() ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xcache/memory/ 0000775 0000000 0000000 00000000000 14262463342 0015475 5 ustar 00root root 0000000 0000000 gokit-0.25.9/xcache/memory/memory.go 0000664 0000000 0000000 00000011534 14262463342 0017340 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package memory import ( "errors" "sync" "time" ) var ( // ErrKeyNotExists is key not exists error ErrKeyNotExists = errors.New("xcache: the key is not exists") // ErrDataTypeNotSupported is data type not supported error ErrDataTypeNotSupported = errors.New("xcache: data type is not supported") // ErrValueLessThanZero is value less than zero error ErrValueLessThanZero = errors.New("xcache: object value is less than zero") ) // Object is storing single object type Object struct { value interface{} expire int64 } // Objects is storing all object type Objects struct { values map[string]*Object gcInterval int gcMaxOnce int gcExit chan int sync.RWMutex } // Version returns package version func Version() string { return "0.2.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // New init a new cache func New() *Objects { o := &Objects{ values: map[string]*Object{}, gcInterval: 60, gcMaxOnce: 100, gcExit: make(chan int), } go o.gc() return o } // Set set key value to cache func (o *Objects) Set(key string, val interface{}, ttl int64) error { if ttl > 0 { ttl = time.Now().Add(time.Duration(ttl) * time.Second).Unix() } o.Lock() defer o.Unlock() o.values[key] = &Object{val, ttl} return nil } // Get get value from cache func (o *Objects) Get(key string) interface{} { o.RLock() defer o.RUnlock() v, ok := o.values[key] if !ok { return nil } if v.expired() { return nil } return v.value } // MGet get multiple value from cache func (o *Objects) MGet(key ...string) []interface{} { r := []interface{}{} for _, k := range key { r = append(r, o.Get(k)) } return r } // Has returns key is exists func (o *Objects) Has(key string) bool { o.RLock() defer o.RUnlock() v, ok := o.values[key] if !ok { return false } return !v.expired() } // Del remove key from cache func (o *Objects) Del(key string) error { o.Lock() defer o.Unlock() delete(o.values, key) return nil } // Incr increase cache counter func (o *Objects) Incr(key string) error { o.Lock() defer o.Unlock() v, ok := o.values[key] if !ok { return ErrKeyNotExists } switch vv := v.value.(type) { case int: v.value = vv + 1 case int32: v.value = vv + 1 case int64: v.value = vv + 1 case uint: v.value = vv + 1 case uint32: v.value = vv + 1 case uint64: v.value = vv + 1 default: return ErrDataTypeNotSupported } return nil } // Decr decrease cache counter func (o *Objects) Decr(key string) error { o.Lock() defer o.Unlock() v, ok := o.values[key] if !ok { return ErrKeyNotExists } switch vv := v.value.(type) { case int: v.value = vv - 1 case int32: v.value = vv - 1 case int64: v.value = vv - 1 case uint: if vv <= 0 { return ErrValueLessThanZero } v.value = vv - 1 case uint32: if vv <= 0 { return ErrValueLessThanZero } v.value = vv - 1 case uint64: if vv <= 0 { return ErrValueLessThanZero } v.value = vv - 1 default: return ErrDataTypeNotSupported } return nil } // Flush empty the cache func (o *Objects) Flush() error { o.Lock() defer o.Unlock() o.values = map[string]*Object{} return nil } // Close stop the cache service func (o *Objects) Close() error { o.gcExit <- 1 o.Flush() return nil } // SetGC set gc interval and max once func (o *Objects) SetGC(gcInterval, gcMaxOnce int) { o.Lock() o.gcInterval = gcInterval o.gcMaxOnce = gcMaxOnce o.Unlock() o.gcExit <- 1 go o.gc() } // gc do gc check func (o *Objects) gc() { o.RLock() gcInterval := o.gcInterval o.RUnlock() t := time.NewTicker(time.Duration(gcInterval) * time.Second) for { select { case <-o.gcExit: t.Stop() return case <-t.C: o.RLock() e := []string{} for k, v := range o.values { if v.expired() { e = append(e, k) if len(e) >= o.gcMaxOnce { break } } } o.RUnlock() o.Lock() for _, k := range e { delete(o.values, k) } o.Unlock() } } } // expired returns object is expired func (b *Object) expired() bool { if b.expire <= 0 { return false } return time.Now().Unix() >= b.expire } gokit-0.25.9/xcache/memory/memory_test.go 0000664 0000000 0000000 00000006233 14262463342 0020377 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package memory import ( "fmt" "testing" "time" "github.com/likexian/gokit/assert" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestBase(t *testing.T) { c := New() defer c.Close() // has nx := c.Has("x") assert.False(t, nx) // set err := c.Set("x", 1, -1) assert.Nil(t, err) // check set nx = c.Has("x") assert.True(t, nx) // get v := c.Get("x") assert.Equal(t, v, 1) // del err = c.Del("x") assert.Nil(t, err) // check del nx = c.Has("x") assert.False(t, nx) v = c.Get("x") assert.Equal(t, v, nil) for i := 0; i < 10; i++ { k := fmt.Sprintf("%d", i) err = c.Set(k, i, 0) assert.Nil(t, err) assert.True(t, c.Has(k)) } // get multiple key vs := c.MGet("1", "2", "3") assert.Len(t, vs, 3) assert.Equal(t, vs[0], 1) assert.Equal(t, vs[1], 2) assert.Equal(t, vs[2], 3) // flush cache c.Flush() v = c.Get("1") assert.Equal(t, v, nil) // get on expired key err = c.Set("xx", 1, 1) assert.Nil(t, err) time.Sleep(1 * time.Second) v = c.Get("xx") assert.Equal(t, v, nil) } func TestGC(t *testing.T) { c := New() defer c.Close() c.SetGC(1, 1) err := c.Set("x", 1, 1) assert.Nil(t, err) nx := c.Has("x") assert.True(t, nx) time.Sleep(1 * time.Second) nx = c.Has("x") assert.False(t, nx) } func TestIncr(t *testing.T) { c := New() defer c.Close() tests := []struct { in interface{} out interface{} }{ {int(0), int(1)}, {int32(0), int32(1)}, {int64(0), int64(1)}, {uint(0), uint(1)}, {uint32(0), uint32(1)}, {uint64(0), uint64(1)}, } for _, v := range tests { _ = c.Set("k", v.in, 0) _ = c.Incr("k") assert.Equal(t, c.Get("k"), v.out) } err := c.Incr("x") assert.NotNil(t, err) err = c.Set("x", "o", 0) assert.Nil(t, err) err = c.Incr("x") assert.NotNil(t, err) } func TestDecr(t *testing.T) { c := New() defer c.Close() tests := []struct { in interface{} out interface{} }{ {int(1), int(0)}, {int32(1), int32(0)}, {int64(1), int64(0)}, {uint(1), uint(0)}, {uint32(1), uint32(0)}, {uint64(1), uint64(0)}, } for _, v := range tests { _ = c.Set("k", v.in, 0) _ = c.Decr("k") assert.Equal(t, c.Get("k"), v.out) } err := c.Decr("x") assert.NotNil(t, err) err = c.Set("x", "o", 0) assert.Nil(t, err) err = c.Decr("x") assert.NotNil(t, err) for _, v := range []interface{}{ uint(0), uint32(0), uint64(0), } { _ = c.Set("k", v, 0) err = c.Decr("k") assert.NotNil(t, err) } } gokit-0.25.9/xcache/xcache.go 0000664 0000000 0000000 00000002760 14262463342 0015754 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package xcache import ( "github.com/likexian/gokit/xcache/memory" ) // Cacher list const ( MemoryCache = iota ) // Cachex is cache interface type Cachex interface { Get(key string) interface{} MGet(key ...string) []interface{} Set(key string, val interface{}, ttl int64) error Has(key string) bool Del(key string) error Incr(key string) error Decr(key string) error SetGC(gcInterval, gcMaxOnce int) Flush() error Close() error } // Version returns package version func Version() string { return "0.2.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // New returns a new cacher func New(cacher int) Cachex { switch cacher { default: return memory.New() } } gokit-0.25.9/xcache/xcache_test.go 0000664 0000000 0000000 00000002422 14262463342 0017006 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package xcache import ( "testing" "github.com/likexian/gokit/assert" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestNew(t *testing.T) { c := New(MemoryCache) defer c.Close() b := c.Has("x") assert.False(t, b) v := c.Get("x") assert.Equal(t, v, nil) err := c.Set("x", 1, 0) assert.Nil(t, err) b = c.Has("x") assert.True(t, b) v = c.Get("x") assert.Equal(t, v, 1) err = c.Del("x") assert.Nil(t, err) b = c.Has("x") assert.False(t, b) v = c.Get("x") assert.Equal(t, v, nil) } gokit-0.25.9/xcron/ 0000775 0000000 0000000 00000000000 14262463342 0014063 5 ustar 00root root 0000000 0000000 gokit-0.25.9/xcron/README.md 0000664 0000000 0000000 00000005443 14262463342 0015350 0 ustar 00root root 0000000 0000000 # GoKit - xcron Cron kits for Golang development. ## Features - Thread safe and Easy to use - Fractional precision to Seconds - Compatible with Standard cron expression - Support Nonstandard macros definitions - Extense Support @every N duration - Dynamic addã€updateã€removeã€empty cron job ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xcron" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xcron) ## Field of rule ``` Field name | Mandatory | Allowed values | Allowed special characters ------------ | ---------- | --------------- | -------------------------- Seconds | No | 0-59 | * / , - Minutes | Yes | 0-59 | * / , - Hours | Yes | 0-23 | * / , - Day of month | Yes | 1-31 | * / , - Month | Yes | 1–12 or JAN–DEC | * / , - Day of week | Yes | 0–6 or SUN–SAT | * / , - ``` ## Predefined rule ``` Entry | Description | Equivalent To ---------------------- | ------------------------------------------ | ------------- @yearly (or @annually) | Run once a year, midnight, Jan. 1st | 0 0 0 1 1 * @monthly | Run once a month, midnight, first of month | 0 0 0 1 * * @weekly | Run once a week, midnight between Sat/Sun | 0 0 0 * * 0 @daily (or @midnight) | Run once a day, midnight | 0 0 0 * * * @hourly | Run once an hour, beginning of hour | 0 0 * * * * ``` ## Example ### Cron service ```go // start a cron service service := xcron.New() // add a job to service, specify the rule and loop func id, err := service.Add("@every second", func(){fmt.Println("add a echo")}) // update exists job by job id err = service.Set(id, "@every second", func(){fmt.Println("set a echo")}) // delete exists job from service, job will stop service.Del(id) // clear all jobs, jobs will stop service.Empty() // wait for all job exit service.Wait() ``` ### Parse cron rule ```go // standard cron rule, every hour rule, err := xcron.Parse("0 * * * *") // standard cron rule, every half hour rule, err := xcron.Parse("0,30 * * * *") // standard cron rule, every half hour rule, err := xcron.Parse("0/30 * * * *") // every second, six fields rule, err := xcron.Parse("* * * * * *") // every hour rule, err := xcron.Parse("@every hour") // every 6 hour rule, err := xcron.Parse("@every 6 hour") ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xcron/xcron.go 0000664 0000000 0000000 00000023473 14262463342 0015554 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package xcron import ( "context" "fmt" "strconv" "strings" "sync" "time" "github.com/likexian/gokit/assert" "github.com/likexian/gokit/xhash" "github.com/likexian/gokit/xtime" ) // Field type of rule const ( Second = iota Minute Hour DayOfMonth Month DayOfWeek ) var ( // MonthsMap is month string to int map MonthsMap = map[string]int{ "jan": 1, "feb": 2, "mar": 3, "apr": 4, "may": 5, "jun": 6, "jul": 7, "aug": 8, "sep": 9, "oct": 10, "nov": 11, "dec": 12, } // DayOfWeekMap is day of week string to int map DayOfWeekMap = map[string]int{ "sun": 0, "mon": 1, "tue": 2, "wed": 3, "thu": 4, "fri": 5, "sat": 6, } ) // Rule is parsed cron rule type Rule struct { Second []int Minute []int Hour []int DayOfMonth []int Month []int DayOfWeek []int } // Job is a cron job type Job struct { rule string loop func() tidy func() stop chan bool } // Service is cron service type Service struct { jobs map[string]Job ctx context.Context cancel context.CancelFunc wg *sync.WaitGroup sync.RWMutex } // Version returns package version func Version() string { return "0.7.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // MustParse do parse and returns rule, panic if error func MustParse(s string) Rule { r, err := Parse(s) if err != nil { panic(err) } return r } // Parse parse single cron rule // Base on https://en.wikipedia.org/wiki/Cron and extensed // Fields: second minute hour dayOfMonth month dayOfWeek // * * * * * * func Parse(s string) (r Rule, err error) { r = Rule{ []int{}, []int{}, []int{}, []int{}, []int{}, []int{}, } s = strings.TrimSpace(s) if s == "" || s == "*" { return } s = strings.ToLower(s) if s[0] == '@' { s, err = parseMacros(s) if err != nil { return } } fs := strings.Fields(s) if len(fs) < 6 { fs = append([]string{"0"}, fs...) } if len(fs) != 6 { return r, fmt.Errorf("xcron: unrecognized rule: %s", s) } for i := 0; i < len(fs); i++ { err := r.parseField(fs[i], i) if err != nil { return r, err } } return } // New returns new cron service func New() *Service { ctx, cancel := context.WithCancel(context.Background()) return &Service{ jobs: map[string]Job{}, ctx: ctx, cancel: cancel, wg: &sync.WaitGroup{}, } } // Add add new cron job to service func (s *Service) Add(rule string, loop func(), tidy ...func()) (string, error) { id := xhash.Sha1("xcron", rule, xtime.Ns()).Hex() return id, s.Set(id, rule, loop, tidy...) } // Set update service cron job func (s *Service) Set(id, rule string, loop func(), tidy ...func()) error { rules, err := Parse(rule) if err != nil { return err } if s.Has(id) { s.Del(id) } done := func() {} if len(tidy) > 0 { done = tidy[0] } j := Job{ rule: rule, loop: loop, tidy: done, stop: make(chan bool, 1), } s.Lock() s.jobs[id] = j s.Unlock() s.wg.Add(1) go func() { t := time.NewTicker(time.Second) for { select { case <-j.stop: t.Stop() j.tidy() s.wg.Done() return case <-s.ctx.Done(): s.Del(id) case v := <-t.C: if isDue(v, rules) { j.loop() } } } }() return nil } // Del del cron job from service by id func (s *Service) Del(id string) { s.Lock() defer s.Unlock() if j, ok := s.jobs[id]; ok { delete(s.jobs, id) close(j.stop) } } // Has returns if cron job is running func (s *Service) Has(id string) bool { s.RLock() defer s.RUnlock() _, ok := s.jobs[id] return ok } // Len returns running cron job number func (s *Service) Len() int { s.RLock() defer s.RUnlock() return len(s.jobs) } // Empty empty the cron job service func (s *Service) Empty() { s.cancel() } // Wait wait for all cron job exit func (s *Service) Wait() { s.wg.Wait() } // isDue check if is due with rule func isDue(now time.Time, rule Rule) bool { rules := [][]int{ rule.Second, rule.Minute, rule.Hour, rule.DayOfMonth, rule.Month, rule.DayOfWeek, } toCheck := []int{} for k, v := range rules { if len(v) > 0 { toCheck = append(toCheck, k) } } if len(toCheck) == 0 { return true } _, m, d := now.Date() h, i, s := now.Clock() w := now.Weekday() nows := []int{s, i, h, d, int(m), int(w)} for _, k := range toCheck { if !assert.IsContains(rules[k], nows[k]) { return false } } return true } // parseField parse every fields func (r *Rule) parseField(s string, t int) (err error) { switch t { case Second: if strings.Contains(s, ",") { r.Second, err = getField(s, t, 0, 59) } else { r.Second, err = getRange(s, t, 0, 59) } case Minute: if strings.Contains(s, ",") { r.Minute, err = getField(s, t, 0, 59) } else { r.Minute, err = getRange(s, t, 0, 59) } case Hour: if strings.Contains(s, ",") { r.Hour, err = getField(s, t, 0, 23) } else { r.Hour, err = getRange(s, t, 0, 23) } case DayOfMonth: if strings.Contains(s, ",") { r.DayOfMonth, err = getField(s, t, 1, 31) } else { r.DayOfMonth, err = getRange(s, t, 1, 31) } case Month: if strings.Contains(s, ",") { r.Month, err = getField(s, t, 1, 12) } else { r.Month, err = getRange(s, t, 1, 12) } case DayOfWeek: if strings.Contains(s, ",") { r.DayOfWeek, err = getField(s, t, 0, 6) } else { r.DayOfWeek, err = getRange(s, t, 0, 6) } } return err } // getRange get int array from string range, for example 3, 0-23, */3 func getRange(s string, t, min, max int) ([]int, error) { r := []int{} if s == "*" { return r, nil } if strings.Contains(s, "-") { ss := strings.Split(s, "-") sl, err := fieldToi(ss[0], t) if err != nil { return r, fmt.Errorf("xcron: unrecognized charset: %s", ss[0]) } sr, err := fieldToi(ss[1], t) if err != nil { return r, fmt.Errorf("xcron: unrecognized charset: %s", ss[1]) } if sl > sr { st := sr sr = sl sl = st } if sl < min || sr > max { return r, fmt.Errorf("xcron: %d is not in [%d, %d]", sr, min, max) } for i := sl; i <= sr; i++ { r = append(r, i) } } else if strings.Contains(s, "/") { ss := strings.Split(s, "/") sr, err := fieldToi(ss[1], t) if err != nil { return r, fmt.Errorf("xcron: unrecognized charset: %s", ss[1]) } if sr < min || sr > max { return r, fmt.Errorf("xcron: %d is not in [%d, %d]", sr, min, max) } for i := min; i <= max; i++ { if i%sr == 0 { r = append(r, i) } } } else { sr, err := fieldToi(s, t) if err != nil { return r, fmt.Errorf("xcron: unrecognized charset: %s", s) } if sr < min || sr > max { return r, fmt.Errorf("xcron: %d is not in [%d, %d]", sr, min, max) } r = append(r, sr) } return r, nil } // getField get int array from string fields, for example 0,1,2 func getField(s string, t, min, max int) ([]int, error) { r := []int{} for _, v := range strings.Split(s, ",") { v = strings.TrimSpace(v) if v != "" { vv, err := fieldToi(v, t) if err != nil { return r, fmt.Errorf("xcron: unrecognized charset: %s", v) } if vv < min || vv > max { return r, fmt.Errorf("xcron: %d is not in [%d, %d]", vv, min, max) } r = append(r, vv) } } return r, nil } // fieldToi get field value as int func fieldToi(s string, t int) (int, error) { vv, err := strconv.Atoi(s) if err == nil { return vv, nil } if t == Month { if v, ok := MonthsMap[s]; ok { return v, nil } } if t == DayOfWeek { if v, ok := DayOfWeekMap[s]; ok { return v, nil } } return 0, fmt.Errorf("xcron: unrecognized charset: %s", s) } // parseMacros parse nonstandard predefined scheduling definitions // returns as standard scheduling definitions func parseMacros(s string) (string, error) { //nolint:cyclop switch s { case "@yearly", "@annually": return "0 0 1 1 *", nil case "@monthly": return "0 0 1 * *", nil case "@daily", "@midnight": return "0 0 * * *", nil case "@hourly": return "0 * * * *", nil case "@weekly": return "0 0 * * 0", nil default: every := "@every " if strings.HasPrefix(s, every) { s = strings.TrimSpace(s[len(every):]) ss := strings.Fields(s) // @every hour ev := "*" vv := 0 if len(ss) > 1 { vv, err := strconv.Atoi(ss[0]) if err == nil && vv > 0 { // @every 2 hour -> */2 ev = "*/" + ss[0] ss[0] = ss[1] } else { return "", fmt.Errorf("xcron: unrecognized macros: %s", s) } } switch ss[0] { case "year": if ev == "*" { return "0 0 1 1 *", nil } case "month": if vv <= 12 { return fmt.Sprintf("0 0 1 %s *", ev), nil } case "day": if vv <= 31 { return fmt.Sprintf("0 0 %s * *", ev), nil } case "hour": if vv < 24 { return fmt.Sprintf("0 %s * * *", ev), nil } case "minute": if vv < 60 { return fmt.Sprintf("%s * * * *", ev), nil } case "second": if vv < 60 { return fmt.Sprintf("%s * * * * *", ev), nil } case "week": if ev == "*" { return "0 0 0 * * 0", nil } case "dayofweek": if vv < 7 { return fmt.Sprintf("0 0 0 * * %s", ev), nil } } } return "", fmt.Errorf("xcron: unrecognized macros: %s", s) } } gokit-0.25.9/xcron/xcron_test.go 0000664 0000000 0000000 00000020721 14262463342 0016604 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package xcron import ( "testing" "time" "github.com/likexian/gokit/assert" "github.com/likexian/gokit/xtime" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestAddSet(t *testing.T) { c := New() _, err := c.Add("@error", func() {}) assert.NotNil(t, err) id, err := c.Add("@every second", func() { t.Log("add a echo") }) assert.Nil(t, err) assert.NotEqual(t, id, "") assert.Equal(t, c.Len(), 1) time.Sleep(1 * time.Second) err = c.Set(id, "@every second", func() { t.Log("set a echo") }) assert.Nil(t, err) assert.NotEqual(t, id, "") assert.Equal(t, c.Len(), 1) time.AfterFunc(3*time.Second, func() { c.Del(id) }) c.Wait() assert.Equal(t, c.Len(), 0) } func TestEmpty(t *testing.T) { c := New() for i := 0; i < 3; i++ { id, err := c.Add("@every second", func() { t.Log("add 3 echo") }) assert.Nil(t, err) assert.NotEqual(t, id, "") } assert.Equal(t, c.Len(), 3) time.AfterFunc(3*time.Second, func() { c.Empty() }) c.Wait() assert.Equal(t, c.Len(), 0) } func TestAddEchoChannel(t *testing.T) { r := make(chan interface{}, 10) echo := func(i int) int { return i } c := New() id, err := c.Add("@every 3 second", func() { r <- echo(int(xtime.S())) }, func() { close(r) }) assert.Nil(t, err) assert.NotEqual(t, id, "") time.AfterFunc(3*time.Second, func() { c.Del(id) }) d := []interface{}{} for { i, ok := <-r if !ok { break } d = append(d, i) } assert.Len(t, d, 1) } func TestParse(t *testing.T) { tests := []struct { in string out Rule err error }{ {"", Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{}}, nil}, {"*", Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{}}, nil}, {"* * * * *", Rule{[]int{0}, []int{}, []int{}, []int{}, []int{}, []int{}}, nil}, {"* * * * * *", Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{}}, nil}, {"* * * * jan *", Rule{[]int{}, []int{}, []int{}, []int{}, []int{1}, []int{}}, nil}, {"* * * * jan-mar *", Rule{[]int{}, []int{}, []int{}, []int{}, []int{1, 2, 3}, []int{}}, nil}, {"* * * * jan,feb,mar *", Rule{[]int{}, []int{}, []int{}, []int{}, []int{1, 2, 3}, []int{}}, nil}, {"* * * * * sun", Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{0}}, nil}, {"* * * * * sun-tue", Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{0, 1, 2}}, nil}, {"* * * * * sun,mon,tue", Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{0, 1, 2}}, nil}, {"1 * * * * *", Rule{[]int{1}, []int{}, []int{}, []int{}, []int{}, []int{}}, nil}, {"* 1 * * * *", Rule{[]int{}, []int{1}, []int{}, []int{}, []int{}, []int{}}, nil}, {"* * 1 * * *", Rule{[]int{}, []int{}, []int{1}, []int{}, []int{}, []int{}}, nil}, {"* * * 1 * *", Rule{[]int{}, []int{}, []int{}, []int{1}, []int{}, []int{}}, nil}, {"* * * * 1 *", Rule{[]int{}, []int{}, []int{}, []int{}, []int{1}, []int{}}, nil}, {"* * * * * 1", Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{1}}, nil}, {"1,2,3 * * * * *", Rule{[]int{1, 2, 3}, []int{}, []int{}, []int{}, []int{}, []int{}}, nil}, {"* 1,2,3 * * * *", Rule{[]int{}, []int{1, 2, 3}, []int{}, []int{}, []int{}, []int{}}, nil}, {"* * 1,2,3 * * *", Rule{[]int{}, []int{}, []int{1, 2, 3}, []int{}, []int{}, []int{}}, nil}, {"* * * 1,2,3 * *", Rule{[]int{}, []int{}, []int{}, []int{1, 2, 3}, []int{}, []int{}}, nil}, {"* * * * 1,2,3 *", Rule{[]int{}, []int{}, []int{}, []int{}, []int{1, 2, 3}, []int{}}, nil}, {"* * * * * 1,2,3", Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{1, 2, 3}}, nil}, {"1-3 * * * * *", Rule{[]int{1, 2, 3}, []int{}, []int{}, []int{}, []int{}, []int{}}, nil}, {"* 1-3 * * * *", Rule{[]int{}, []int{1, 2, 3}, []int{}, []int{}, []int{}, []int{}}, nil}, {"* * 1-3 * * *", Rule{[]int{}, []int{}, []int{1, 2, 3}, []int{}, []int{}, []int{}}, nil}, {"* * * 1-3 * *", Rule{[]int{}, []int{}, []int{}, []int{1, 2, 3}, []int{}, []int{}}, nil}, {"* * * * 1-3 *", Rule{[]int{}, []int{}, []int{}, []int{}, []int{1, 2, 3}, []int{}}, nil}, {"* * * * * 1-3", Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{1, 2, 3}}, nil}, {"*/20 * * * * *", Rule{[]int{0, 20, 40}, []int{}, []int{}, []int{}, []int{}, []int{}}, nil}, {"* */30 * * * *", Rule{[]int{}, []int{0, 30}, []int{}, []int{}, []int{}, []int{}}, nil}, {"* * */6 * * *", Rule{[]int{}, []int{}, []int{0, 6, 12, 18}, []int{}, []int{}, []int{}}, nil}, {"* * * */10 * *", Rule{[]int{}, []int{}, []int{}, []int{10, 20, 30}, []int{}, []int{}}, nil}, {"* * * * */4 *", Rule{[]int{}, []int{}, []int{}, []int{}, []int{4, 8, 12}, []int{}}, nil}, {"* * * * * */2", Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{0, 2, 4, 6}}, nil}, {"@weekly", Rule{[]int{0}, []int{0}, []int{0}, []int{}, []int{}, []int{0}}, nil}, {"@hourly", Rule{[]int{0}, []int{0}, []int{}, []int{}, []int{}, []int{}}, nil}, {"@daily", Rule{[]int{0}, []int{0}, []int{0}, []int{}, []int{}, []int{}}, nil}, {"@monthly", Rule{[]int{0}, []int{0}, []int{0}, []int{1}, []int{}, []int{}}, nil}, {"@yearly", Rule{[]int{0}, []int{0}, []int{0}, []int{1}, []int{1}, []int{}}, nil}, {"@every second", Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{}}, nil}, {"@every minute", Rule{[]int{0}, []int{}, []int{}, []int{}, []int{}, []int{}}, nil}, {"@every hour", Rule{[]int{0}, []int{0}, []int{}, []int{}, []int{}, []int{}}, nil}, {"@every day", Rule{[]int{0}, []int{0}, []int{0}, []int{}, []int{}, []int{}}, nil}, {"@every month", Rule{[]int{0}, []int{0}, []int{0}, []int{1}, []int{}, []int{}}, nil}, {"@every week", Rule{[]int{0}, []int{0}, []int{0}, []int{}, []int{}, []int{0}}, nil}, {"@every year", Rule{[]int{0}, []int{0}, []int{0}, []int{1}, []int{1}, []int{}}, nil}, {"@every 20 second", Rule{[]int{0, 20, 40}, []int{}, []int{}, []int{}, []int{}, []int{}}, nil}, {"@every 30 minute", Rule{[]int{0}, []int{0, 30}, []int{}, []int{}, []int{}, []int{}}, nil}, {"@every 6 hour", Rule{[]int{0}, []int{0}, []int{0, 6, 12, 18}, []int{}, []int{}, []int{}}, nil}, {"@every 10 day", Rule{[]int{0}, []int{0}, []int{0}, []int{10, 20, 30}, []int{}, []int{}}, nil}, {"@every 4 month", Rule{[]int{0}, []int{0}, []int{0}, []int{1}, []int{4, 8, 12}, []int{}}, nil}, {"@every 2 dayofweek", Rule{[]int{0}, []int{0}, []int{0}, []int{}, []int{}, []int{0, 2, 4, 6}}, nil}, } for _, v := range tests { vv, err := Parse(v.in) assert.Equal(t, err, v.err, v) assert.Equal(t, vv, v.out, v) } fails := []string{ "@likexian", "0 0", "-3 * * * *", "3- * * * *", "x-3 * * * *", "3-x * * * *", "100-1000 * * * *", "1000-100 * * * *", "1/x * * * *", "100/x * * * *", "x/100 * * * *", "x * * * *", "1000 * * * *", "1,1000 * * * *", "1,x,3 * * * *", "@every x", "@every x second", "* * * * janx *", "* * * * janx-mar *", "* * * * janx,feb,mar *", "* * * * * sunx", "* * * * * sunx-tue", "* * * * * sunx,mon,tue", } for _, v := range fails { _, err := Parse(v) assert.NotNil(t, err, v) } } func TestMustParse(t *testing.T) { assert.Panic(t, func() { MustParse("@every night") }) assert.NotPanic(t, func() { MustParse("@every second") }) } func TestIsDue(t *testing.T) { tests := []struct { now time.Time rule Rule out bool }{ {time.Date(2019, 04, 10, 0, 0, 0, 0, time.UTC), Rule{[]int{}, []int{}, []int{}, []int{}, []int{}, []int{}}, true}, {time.Date(2019, 04, 10, 0, 0, 0, 0, time.UTC), Rule{[]int{0}, []int{}, []int{}, []int{}, []int{}, []int{}}, true}, {time.Date(2019, 04, 10, 0, 0, 0, 0, time.UTC), Rule{[]int{0, 1}, []int{}, []int{}, []int{}, []int{}, []int{}}, true}, {time.Date(2019, 04, 10, 0, 0, 0, 0, time.UTC), Rule{[]int{1}, []int{}, []int{}, []int{}, []int{}, []int{}}, false}, {time.Date(2019, 04, 10, 0, 0, 0, 0, time.UTC), Rule{[]int{1, 2}, []int{}, []int{}, []int{}, []int{}, []int{}}, false}, } for _, v := range tests { assert.Equal(t, isDue(v.now, v.rule), v.out) } } gokit-0.25.9/xdaemon/ 0000775 0000000 0000000 00000000000 14262463342 0014365 5 ustar 00root root 0000000 0000000 gokit-0.25.9/xdaemon/README.md 0000664 0000000 0000000 00000001611 14262463342 0015643 0 ustar 00root root 0000000 0000000 # GoKit - xdaemon Daemon kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xdaemon" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xdaemon) ## Example ### Do deamon ```go c := xdaemon.Config { Pid: "/tmp/test.pid", // the pid file name Log: "/tmp/test.log", // the log file name User: "nobody", // run daemon as user, if set, ROOT is required Chdir: "/", // change working dir } err := c.Daemon() if err != nil { panic(err) } ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xdaemon/xdaemon.go 0000664 0000000 0000000 00000004554 14262463342 0016357 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package xdaemon import ( "os" "syscall" "github.com/likexian/gokit/xfile" "github.com/likexian/gokit/xos" ) // Config storing config for daemon type Config struct { Pid string Log string User string Chdir string } // Version returns package version func Version() string { return "0.7.2" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // Daemon start to daemon func (c *Config) Daemon() (err error) { var pid *xos.Pidx if c.Pid != "" { pid = xos.Pid(c.Pid) _, err := pid.Alive() if err == nil { return xos.ErrPidExists } } err = c.doDaemon() if err != nil { return } if c.Pid != "" { _, err = pid.Create() if err != nil { return } } if c.User != "" { err = xos.SetUser(c.User) if err != nil { return } } return } // Doing the daemon func (c *Config) doDaemon() (err error) { syscall.Umask(0) if c.Chdir != "" { err = os.Chdir(c.Chdir) if err != nil { return } } if syscall.Getppid() == 1 { return } files := make([]*os.File, 3, 6) if c.Log != "" { fp, err := xfile.NewFile(c.Log, true) if err != nil { return err } files[0], files[1], files[2] = fp, fp, fp } else { files[0], files[1], files[2] = os.Stdin, os.Stdout, os.Stderr } dir, _ := os.Getwd() sysattrs := syscall.SysProcAttr{Setsid: true} prcattrs := os.ProcAttr{ Dir: dir, Env: os.Environ(), Files: files, Sys: &sysattrs, } proc, err := os.StartProcess(os.Args[0], os.Args, &prcattrs) if err != nil { return } err = proc.Release() if err != nil { return } os.Exit(0) return } gokit-0.25.9/xdaemon/xdaemon_fail_test.go 0000664 0000000 0000000 00000002612 14262463342 0020402 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package xdaemon import ( "os" "testing" "github.com/likexian/gokit/assert" "github.com/likexian/gokit/xfile" ) func TestFailDaemon(t *testing.T) { v := os.Args[0] os.Args[0] = "xx" c := Config{ Pid: "", Log: "", User: "", Chdir: "", } err := c.Daemon() assert.NotNil(t, err) c = Config{ Pid: "/tmp/test.pid", Log: "/tmp/test.log", User: "nobody", Chdir: "/", } err = c.Daemon() assert.NotNil(t, err) os.Args[0] = v } func TestIsRunning(t *testing.T) { pidFile := "/tmp/test.pid" defer os.Remove(pidFile) err := xfile.WriteText(pidFile, "1") assert.Nil(t, err) c := Config{ Pid: pidFile, Log: "/tmp/test.log", User: "nobody", Chdir: "/", } err = c.Daemon() assert.NotNil(t, err) } gokit-0.25.9/xdaemon/xdaemon_hasuser_test.go 0000664 0000000 0000000 00000002457 14262463342 0021150 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package xdaemon import ( "os" "os/exec" "testing" "github.com/likexian/gokit/assert" ) func testHasUser(t *testing.T) { c := Config{ Pid: "/tmp/test.pid", Log: "/tmp/test.log", User: "nobody", Chdir: "/", } err := c.Daemon() assert.Nil(t, err) } func TestHasUser(t *testing.T) { if os.Getenv("TestCase") != "" && os.Getenv("TestCase") != "TestHasUser" { return } if os.Getenv("TestCase") != "" { testHasUser(t) return } cmd := exec.Command(os.Args[0], "-test.run=TestHasUser") cmd.Env = append(os.Environ(), "TestCase=TestHasUser") err := cmd.Run() if err == nil { return } t.Errorf("Test expect to be daemon") } gokit-0.25.9/xdaemon/xdaemon_nouser_test.go 0000664 0000000 0000000 00000002410 14262463342 0020776 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package xdaemon import ( "os" "os/exec" "testing" "github.com/likexian/gokit/assert" ) func testNoUser(t *testing.T) { c := Config{ Pid: "", Log: "", User: "", Chdir: "", } err := c.Daemon() assert.Nil(t, err) } func TestNoUser(t *testing.T) { if os.Getenv("TestCase") != "" && os.Getenv("TestCase") != "TestNoUser" { return } if os.Getenv("TestCase") != "" { testNoUser(t) return } cmd := exec.Command(os.Args[0], "-test.run=TestNoUser") cmd.Env = append(os.Environ(), "TestCase=TestNoUser") err := cmd.Run() if err == nil { return } t.Errorf("Test expect to be daemon") } gokit-0.25.9/xdaemon/xdaemon_test.go 0000664 0000000 0000000 00000001612 14262463342 0017406 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package xdaemon import ( "testing" "github.com/likexian/gokit/assert" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } gokit-0.25.9/xfile/ 0000775 0000000 0000000 00000000000 14262463342 0014041 5 ustar 00root root 0000000 0000000 gokit-0.25.9/xfile/README.md 0000664 0000000 0000000 00000002141 14262463342 0015316 0 ustar 00root root 0000000 0000000 # GoKit - xfile File kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xfile" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xfile) ## Example ### check file is exists ```go exists := xfile.Exists("/data/dev/gokit/LICENSE") if exists { fmt.Println("file is exists") } else { fmt.Println("file not exists") } ``` ### get file size ```go size, err := xfile.Size("/data/dev/gokit/LICENSE") if err != nil { panic(err) } else { fmt.Println("file size is", size) } ``` ### write text to file ```go err := xfile.WriteText("/tmp/not-exists-dir/LICENSE", "Copyright 2012-2022 Li Kexian\n") if err != nil { panic(err) } else { fmt.Println("write to file successful") } ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xfile/xfile.go 0000664 0000000 0000000 00000022731 14262463342 0015504 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package xfile import ( "bufio" "errors" "io" "io/ioutil" "os" "path/filepath" "strings" ) const ( // TypeAll list dir and file TypeAll int = iota // TypeDir list only dir TypeDir // TypeFile list only file TypeFile ) var ( // ErrNotExists file is exists error ErrNotExists = errors.New("xfile: file is not exists") // ErrHasExists file is exists error ErrHasExists = errors.New("xfile: the file is exists") ) // LsFile is list file info type LsFile struct { Type int Path string Name string } // Version returns package version func Version() string { return "0.15.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // Exists returns path is exists, symbolic link will check the target func Exists(fpath string) bool { _, err := os.Stat(fpath) return !os.IsNotExist(err) } // Lexists returns path is exists, symbolic link will not follow func Lexists(fpath string) bool { _, err := os.Lstat(fpath) return !os.IsNotExist(err) } // IsFile returns path is a file, symbolic link will check the target func IsFile(fpath string) bool { f, err := os.Stat(fpath) return err == nil && f.Mode().IsRegular() } // IsDir returns path is a dir, symbolic link will check the target func IsDir(fpath string) bool { f, err := os.Stat(fpath) return err == nil && f.Mode().IsDir() } // IsSymlink returns path is a symbolic link func IsSymlink(fpath string) bool { f, err := os.Lstat(fpath) return err == nil && f.Mode()&os.ModeSymlink != 0 } // Size returns the file size of path, symbolic link will check the target func Size(fpath string) (int64, error) { f, err := os.Stat(fpath) if err != nil { return -1, err } return f.Size(), nil } // MTime returns the file mtime of path, symbolic link will check the target func MTime(fpath string) (int64, error) { f, err := os.Stat(fpath) if err != nil { return -1, err } return f.ModTime().Unix(), nil } // Copy copys file and folder from src to dst func Copy(src, dst string) error { if src == "" { src = "." } if dst == "" { dst = "." } if strings.TrimRight(src, "/") == strings.TrimRight(dst, "/") { return ErrHasExists } if Exists(dst) { return ErrHasExists } f, err := os.Lstat(src) if err != nil { return err } if f.Mode()&os.ModeSymlink != 0 { target, err := os.Readlink(src) if err != nil { return err } return os.Symlink(target, dst) } else if f.Mode().IsDir() { if !strings.HasSuffix(src, "/") { src += "/" } if !strings.HasSuffix(dst, "/") { dst += "/" } err = os.MkdirAll(dst, 0755) if err != nil { return err } ls, err := ListDir(src, TypeAll, -1) if err != nil { return err } for _, v := range ls { err = Copy(src+v.Name, dst+v.Name) if err != nil { return err } } if err = os.Chtimes(dst, f.ModTime(), f.ModTime()); err != nil { return err } return os.Chmod(dst, f.Mode()) } else { fd, err := os.Open(src) if err != nil { return err } defer fd.Close() td, err := New(dst) if err != nil { return err } defer td.Close() if _, err = io.Copy(td, fd); err != nil { return err } } if err = os.Chtimes(dst, f.ModTime(), f.ModTime()); err != nil { return err } return os.Chmod(dst, f.Mode()) } // New opens a file for new and return fd func New(fpath string) (*os.File, error) { return NewFile(fpath, false) } // NewFile opens a file and return fd func NewFile(fpath string, isAppend bool) (*os.File, error) { dir, _ := filepath.Split(fpath) if dir != "" && !IsDir(dir) { err := os.MkdirAll(dir, 0755) if err != nil { return nil, err } } if isAppend { return os.OpenFile(fpath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) } return os.OpenFile(fpath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) } // WriteText writes string data to file func WriteText(fpath, text string) (err error) { return Write(fpath, []byte(text)) } // AppendText appends string data to file func AppendText(fpath, text string) (err error) { return Append(fpath, []byte(text)) } // Write writes bytes data to file func Write(fpath string, data []byte) (err error) { return writeFile(fpath, data, false) } // Append appends bytes data to file func Append(fpath string, data []byte) (err error) { return writeFile(fpath, data, true) } // writeFile writes bytes data to file func writeFile(fpath string, data []byte, isAppend bool) (err error) { fd, err := NewFile(fpath, isAppend) if err != nil { return } n, err := fd.Write(data) if err == nil && n < len(data) { err = io.ErrShortWrite } if e := fd.Close(); err == nil { err = e } return } // Read returns bytes of file func Read(fpath string) ([]byte, error) { return ioutil.ReadFile(fpath) } // ReadText returns text of file func ReadText(fpath string) (string, error) { b, err := Read(fpath) if err != nil { return "", err } return string(b), nil } // ReadLines returns N lines of file func ReadLines(fpath string, n int) (lines []string, err error) { fd, err := os.Open(fpath) if err != nil { return } defer fd.Close() nRead := 0 scanner := bufio.NewScanner(fd) for scanner.Scan() { lines = append(lines, scanner.Text()) nRead++ if n > 0 && nRead >= n { break } } err = scanner.Err() return } // ReadFirstLine returns first NOT empty line func ReadFirstLine(fpath string) (line string, err error) { fd, err := os.Open(fpath) if err != nil { return } defer fd.Close() scanner := bufio.NewScanner(fd) for scanner.Scan() { line = strings.TrimSpace(scanner.Text()) if line != "" { return } } err = scanner.Err() return } // ReadLastLine returns last NOT empty line func ReadLastLine(fpath string) (line string, err error) { fd, err := os.Open(fpath) if err != nil { return } defer fd.Close() stat, err := fd.Stat() if err != nil { return } size := stat.Size() if size == 0 { return } var cursor int64 data := make([]byte, 0) for { cursor-- _, err = fd.Seek(cursor, io.SeekEnd) if err != nil { return } buf := make([]byte, 1) _, err = fd.Read(buf) if err != nil { return } if buf[0] != '\r' && buf[0] != '\n' { data = append([]byte{buf[0]}, data...) } else { if cursor != -1 && strings.TrimSpace(string(data)) != "" { break } data = make([]byte, 0) } if cursor == -size { break } } return string(data), nil } // ListDir lists dir without recursion func ListDir(fpath string, ftype, n int) (ls []LsFile, err error) { if fpath == "" { fpath = "." } if !strings.HasSuffix(fpath, "/") { fpath += "/" } fd, err := os.Open(fpath) if err != nil { return } defer fd.Close() fs, err := fd.Readdir(-1) if err != nil { return } for _, f := range fs { tpath := fpath + f.Name() if f.IsDir() { if ftype == TypeAll || ftype == TypeDir { ls = append(ls, LsFile{TypeDir, tpath, f.Name()}) if n > 0 && len(ls) >= n { return } } } else { if ftype == TypeAll || ftype == TypeFile { ls = append(ls, LsFile{TypeFile, tpath, f.Name()}) if n > 0 && len(ls) >= n { return } } } } return } // ListDirAll lists dir and children, filter by type, returns up to n func ListDirAll(fpath string, ftype, n int) (ls []LsFile, err error) { if fpath == "" { fpath = "." } if !strings.HasSuffix(fpath, "/") { fpath += "/" } fd, err := os.Open(fpath) if err != nil { return } defer fd.Close() fs, err := fd.Readdir(-1) if err != nil { return } for _, f := range fs { tpath := fpath + f.Name() if f.IsDir() { if ftype == TypeAll || ftype == TypeDir { ls = append(ls, LsFile{TypeDir, tpath, f.Name()}) if n > 0 && len(ls) >= n { return } } tls, err := ListDirAll(tpath, ftype, n-len(ls)) if err != nil { return ls, err } ls = append(ls, tls...) if n > 0 && len(ls) >= n { return ls, nil } } else { if ftype == TypeAll || ftype == TypeFile { ls = append(ls, LsFile{TypeFile, tpath, f.Name()}) if n > 0 && len(ls) >= n { return } } } } return } // Chmod chmods to path without recursion func Chmod(fpath string, mode os.FileMode) error { return os.Chmod(fpath, mode) } // ChmodAll chmods to path and children, returns the first error it encounters func ChmodAll(root string, mode os.FileMode) error { return filepath.Walk(root, func(fpath string, info os.FileInfo, err error) error { if err != nil { return err } return Chmod(fpath, mode) }) } // Chown chowns to path without recursion func Chown(fpath string, uid, gid int) error { return os.Chown(fpath, uid, gid) } // ChownAll chowns to path and children, returns the first error it encounters func ChownAll(root string, uid, gid int) error { return filepath.Walk(root, func(fpath string, info os.FileInfo, err error) error { if err != nil { return err } return Chown(fpath, uid, gid) }) } gokit-0.25.9/xfile/xfile_test.go 0000664 0000000 0000000 00000021121 14262463342 0016533 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package xfile import ( "fmt" "os" "testing" "github.com/likexian/gokit/assert" ) const ( testDir = "tmp" testFile = "tmp/file" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestFile(t *testing.T) { defer os.RemoveAll(testDir) err := os.Mkdir(testDir, 0755) assert.Nil(t, err) ok := Exists(testDir + "/dir") assert.False(t, ok, "file expect to be not exists") err = os.Mkdir(testDir+"/dir", 0755) assert.Nil(t, err) ok = Exists(testDir + "/dir") assert.True(t, ok, "file expect to be exists") ok = IsDir(testDir + "/dir") assert.True(t, ok, "file expect to be dir") ok = IsFile(testDir + "/dir") assert.False(t, ok, "file expect to be not file") ok = IsFile(testDir + "/file") assert.False(t, ok, "file expect to be not file") _, err = New(testDir + "/dir/test/") assert.NotNil(t, err) fd, err := New(testFile) assert.Nil(t, err) err = fd.Close() assert.Nil(t, err) _, err = New(testFile + "/test") assert.NotNil(t, err) ok = IsFile(testFile) assert.True(t, ok, "file expect to be file") err = Write(testFile, []byte("likexian")) assert.Nil(t, err) err = Write(testFile+"/test", []byte("likexian")) assert.NotNil(t, err) text, err := ReadText(testFile) assert.Nil(t, err) assert.Equal(t, text, "likexian") err = WriteText(testFile, "1\n2\n3\n4\n5") assert.Nil(t, err) lines, err := ReadLines(testFile, 0) assert.Nil(t, err) assert.Equal(t, len(lines), 5) lines, err = ReadLines(testFile, 1) assert.Nil(t, err) assert.Equal(t, len(lines), 1) _, err = ReadText(testDir + "/not-exists") assert.NotNil(t, err) _, err = ReadLines(testDir+"/not-exists", 0) assert.NotNil(t, err) err = WriteText(testFile, "likexian") assert.Nil(t, err) text, err = ReadText(testFile) assert.Nil(t, err) assert.Equal(t, text, "likexian") ok = IsFile(testFile) assert.True(t, ok, "file expect to be file") ok = IsDir(testFile) assert.False(t, ok, "file expect to be not dir") n, err := Size(testFile) assert.Nil(t, err) assert.Equal(t, n, int64(8)) m, err := MTime(testFile) assert.Nil(t, err) assert.True(t, m > 0) _, err = Size(testDir + "/not-exists") assert.NotNil(t, err) _, err = MTime(testDir + "/not-exists") assert.NotNil(t, err) ok = IsSymlink(testDir + "/link") assert.False(t, ok, "file expect to be not expect") err = os.Symlink("file", testDir+"/link") assert.Nil(t, err) ok = IsSymlink(testDir + "/link") assert.True(t, ok, "file expect to be symbolic link") ok = IsFile(testDir + "/link") assert.True(t, ok, "file expect to be file") ok = IsDir(testDir + "/link") assert.False(t, ok, "file expect to be not dir") err = Chmod(testDir, 0777) assert.Nil(t, err) err = ChmodAll(testDir, 0777) assert.Nil(t, err) err = Chown(testDir, 0, 0) assert.Nil(t, err) err = ChownAll(testDir, 0, 0) assert.Nil(t, err) err = ChmodAll(testDir+"/not-exists", 0777) assert.NotNil(t, err) err = ChownAll(testDir+"/not-exists", 0, 0) assert.NotNil(t, err) } func TestNewAndAppend(t *testing.T) { defer os.RemoveAll(testDir) // init test file fd, err := New(testFile) assert.Nil(t, err) _, _ = fd.Write([]byte("1")) text, err := ReadText(testFile) assert.Nil(t, err) assert.Equal(t, text, "1") // test new mode fd, err = New(testFile) assert.Nil(t, err) _, _ = fd.Write([]byte("1")) text, err = ReadText(testFile) assert.Nil(t, err) assert.Equal(t, text, "1") // test append mode fd, err = NewFile(testFile, true) assert.Nil(t, err) _, _ = fd.Write([]byte("1")) text, err = ReadText(testFile) assert.Nil(t, err) assert.Equal(t, text, "11") // test write text err = WriteText(testFile, "1") assert.Nil(t, err) text, err = ReadText(testFile) assert.Nil(t, err) assert.Equal(t, text, "1") // test append text err = AppendText(testFile, "1") assert.Nil(t, err) text, err = ReadText(testFile) assert.Nil(t, err) assert.Equal(t, text, "11") } func TestReadFirstLine(t *testing.T) { defer os.RemoveAll(testDir) err := os.Mkdir(testDir, 0755) assert.Nil(t, err) _, err = ReadFirstLine(testFile) assert.NotNil(t, err) tests := []struct { in string out string }{ {"", ""}, {"\n", ""}, {"\n\n", ""}, {"\n\n\n", ""}, {"abc\ndef\nghi", "abc"}, {"\nabc\ndef\nghi", "abc"}, {"\n\nabc\ndef\nghi", "abc"}, {"\n\n\nabc\ndef\nghi", "abc"}, } for _, v := range tests { err = WriteText(testFile, v.in) assert.Nil(t, err) line, err := ReadFirstLine(testFile) assert.Nil(t, err) assert.Equal(t, line, v.out) } } func TestReadLastLine(t *testing.T) { defer os.RemoveAll(testDir) err := os.Mkdir(testDir, 0755) assert.Nil(t, err) _, err = ReadLastLine(testFile) assert.NotNil(t, err) tests := []struct { in string out string }{ {"", ""}, {"\n", ""}, {"\n\n", ""}, {"\n\n\n", ""}, {"abc\ndef\nghi", "ghi"}, {"abc\ndef\nghi\n", "ghi"}, {"abc\ndef\nghi\n\n", "ghi"}, {"abc\ndef\nghi\n\n\n", "ghi"}, } for _, v := range tests { err = WriteText(testFile, v.in) assert.Nil(t, err) line, err := ReadLastLine(testFile) assert.Nil(t, err) assert.Equal(t, line, v.out) } } func TestListDir(t *testing.T) { defer os.RemoveAll(testDir) ls, err := ListDir("", TypeAll, -1) assert.Nil(t, err) assert.Equal(t, len(ls), 3) ls, err = ListDir(testDir, TypeAll, -1) assert.NotNil(t, err) assert.Equal(t, len(ls), 0) for i := 0; i < 10; i++ { _ = WriteText(fmt.Sprintf("%s/%d.txt", testDir, i), ".") for j := 0; j < 10; j++ { _ = WriteText(fmt.Sprintf("%s/%d/%d.txt", testDir, i, j), ".") } } ls, err = ListDir(testDir, TypeAll, -1) assert.Nil(t, err) assert.Equal(t, len(ls), 20) ls, err = ListDir(testDir, TypeDir, -1) assert.Nil(t, err) assert.Equal(t, len(ls), 10) ls, err = ListDir(testDir, TypeFile, -1) assert.Nil(t, err) assert.Equal(t, len(ls), 10) ls, err = ListDir(testDir, TypeAll, 5) assert.Nil(t, err) assert.Equal(t, len(ls), 5) ls, err = ListDir(testDir, TypeDir, 5) assert.Nil(t, err) assert.Equal(t, len(ls), 5) ls, err = ListDir(testDir, TypeFile, 5) assert.Nil(t, err) assert.Equal(t, len(ls), 5) } func TestListDirAll(t *testing.T) { defer os.RemoveAll(testDir) ls, err := ListDirAll("", TypeAll, -1) assert.Nil(t, err) assert.Equal(t, len(ls), 3) ls, err = ListDirAll(testDir, TypeAll, -1) assert.NotNil(t, err) assert.Equal(t, len(ls), 0) for i := 0; i < 10; i++ { _ = WriteText(fmt.Sprintf("%s/%d.txt", testDir, i), ".") for j := 0; j < 10; j++ { _ = WriteText(fmt.Sprintf("%s/%d/%d.txt", testDir, i, j), ".") } } ls, err = ListDirAll(testDir, TypeAll, -1) assert.Nil(t, err) assert.Equal(t, len(ls), 120) ls, err = ListDirAll(testDir, TypeDir, -1) assert.Nil(t, err) assert.Equal(t, len(ls), 10) ls, err = ListDirAll(testDir, TypeFile, -1) assert.Nil(t, err) assert.Equal(t, len(ls), 110) ls, err = ListDirAll(testDir, TypeAll, 5) assert.Nil(t, err) assert.Equal(t, len(ls), 5) ls, err = ListDirAll(testDir, TypeDir, 5) assert.Nil(t, err) assert.Equal(t, len(ls), 5) ls, err = ListDirAll(testDir, TypeFile, 5) assert.Nil(t, err) assert.Equal(t, len(ls), 5) } func TestCopy(t *testing.T) { defer os.RemoveAll(testDir) for i := 0; i < 10; i++ { for j := 0; j < 10; j++ { _ = WriteText(fmt.Sprintf("%s/%d/%d.txt", testDir, i, j), fmt.Sprintf("%d", i+j)) } } _ = os.Symlink(testDir+"/0", testDir+"/100") err := Copy("", "") assert.Equal(t, err, ErrHasExists) err = Copy(testDir+"/0", testDir+"/1") assert.Equal(t, err, ErrHasExists) err = Copy(testDir+"/10", testDir+"/11") assert.NotNil(t, err) err = Copy(testDir+"/100", testDir+"/101") assert.Nil(t, err) assert.True(t, Lexists(testDir+"/101")) err = Copy(testDir+"/0/0.txt", testDir+"/0/10.txt") assert.Nil(t, err) assert.True(t, Exists(testDir+"/0/10.txt")) err = Copy(testDir+"/0", testDir+"/102") assert.Nil(t, err) assert.True(t, Exists(testDir+"/102")) ls, err := ListDir(testDir+"/0", TypeAll, -1) assert.Nil(t, err) for _, v := range ls { assert.True(t, Exists(testDir+"/102/"+v.Name)) } } gokit-0.25.9/xhash/ 0000775 0000000 0000000 00000000000 14262463342 0014045 5 ustar 00root root 0000000 0000000 gokit-0.25.9/xhash/README.md 0000664 0000000 0000000 00000001647 14262463342 0015334 0 ustar 00root root 0000000 0000000 # GoKit - xhash Hash kits for Golang development. ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xhash" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xhash) ## Example ### Get md5 of string ```go h := xhash.Md5("12345678") fmt.Println(h.Hex()) fmt.Println(h.B64()) ``` ## Get Hmac Md5 of string ```go h := xhash.HmacMd5("key", "12345678") fmt.Println(h.Hex()) fmt.Println(h.B64()) ``` ### Get md5 of file ```go h, err := xhash.FileMd5("xhash.go") if err != nil { panic(err) } fmt.Println(h.Hex()) fmt.Println(h.B64()) ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xhash/xhash.go 0000664 0000000 0000000 00000010053 14262463342 0015506 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package xhash import ( "crypto/hmac" "crypto/md5" "crypto/sha1" "crypto/sha256" "crypto/sha512" "encoding/base64" "encoding/hex" "hash" "io" "os" "github.com/likexian/gokit/xstring" ) // Hashx storing hash object type Hashx struct { Hash hash.Hash } // Version returns package version func Version() string { return "0.9.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // Md5 returns md5 hash of string func Md5(s ...interface{}) (h Hashx) { h.Hash = md5.New() h.writeString(s...) return } // Sha1 returns sha1 hash of string func Sha1(s ...interface{}) (h Hashx) { h.Hash = sha1.New() h.writeString(s...) return } // Sha256 returns sha256 hash of string func Sha256(s ...interface{}) (h Hashx) { h.Hash = sha256.New() h.writeString(s...) return } // Sha512 returns sha512 hash of string func Sha512(s ...interface{}) (h Hashx) { h.Hash = sha512.New() h.writeString(s...) return } // HmacMd5 returns hmac md5 hash of string with key func HmacMd5(key string, s ...interface{}) (h Hashx) { h.Hash = hmac.New(md5.New, []byte(key)) h.writeString(s...) return } // HmacSha1 returns hmac sha1 hash of string with key func HmacSha1(key string, s ...interface{}) (h Hashx) { h.Hash = hmac.New(sha1.New, []byte(key)) h.writeString(s...) return } // HmacSha256 returns hmac sha256 hash of string with key func HmacSha256(key string, s ...interface{}) (h Hashx) { h.Hash = hmac.New(sha256.New, []byte(key)) h.writeString(s...) return } // HmacSha512 returns hmac sha512 hash of string with key func HmacSha512(key string, s ...interface{}) (h Hashx) { h.Hash = hmac.New(sha512.New, []byte(key)) h.writeString(s...) return } // FileMd5 returns md5 hash of file func FileMd5(f interface{}) (h Hashx, err error) { h.Hash = md5.New() err = h.writeFile(f) return } // FileSha1 returns sha1 hash of file func FileSha1(f interface{}) (h Hashx, err error) { h.Hash = sha1.New() err = h.writeFile(f) return } // FileSha256 returns sha256 hash of file func FileSha256(f interface{}) (h Hashx, err error) { h.Hash = sha256.New() err = h.writeFile(f) return } // FileSha512 returns sha512 hash of file func FileSha512(f interface{}) (h Hashx, err error) { h.Hash = sha512.New() err = h.writeFile(f) return } // Bytes returns hash sum as bytes func (h Hashx) Bytes() []byte { return h.Hash.Sum(nil) } // Hex encoding hash sum as hex string func (h Hashx) Hex() string { return hex.EncodeToString(h.Hash.Sum(nil)) } // Base64 encoding hash sum as base64 string func (h Hashx) Base64() string { return base64.StdEncoding.EncodeToString(h.Hash.Sum(nil)) } // writeString write string content to hash func (h Hashx) writeString(s ...interface{}) { length := len(s) for _, v := range s { switch v := v.(type) { case []byte: _, _ = h.Hash.Write(v) default: _, _ = h.Hash.Write([]byte(xstring.ToString(v))) } if length > 1 { _, _ = h.Hash.Write([]byte("\n")) } } } // writeFile write file content to hash func (h Hashx) writeFile(f interface{}) error { switch f := f.(type) { case string: fd, err := os.Open(f) if err != nil { return err } defer fd.Close() _, err = io.Copy(h.Hash, fd) return err case *os.File: _, err := io.Copy(h.Hash, f) return err default: panic("xhash: not supported file type") } } gokit-0.25.9/xhash/xhash_test.go 0000664 0000000 0000000 00000032521 14262463342 0016551 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package xhash import ( "encoding/base64" "os" "testing" "github.com/likexian/gokit/assert" ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestMd5(t *testing.T) { tests := []struct { in []interface{} hex string b64 string }{ { []interface{}{[]byte("12345678")}, "25d55ad283aa400af464c76d713c07ad", "JdVa0oOqQAr0ZMdtcTwHrQ==", }, { []interface{}{"12345678"}, "25d55ad283aa400af464c76d713c07ad", "JdVa0oOqQAr0ZMdtcTwHrQ==", }, { []interface{}{"1234", "5678"}, "9053253e972cf40443a4083f452f24d4", "kFMlPpcs9ARDpAg/RS8k1A==", }, { []interface{}{1234, 5678}, "9053253e972cf40443a4083f452f24d4", "kFMlPpcs9ARDpAg/RS8k1A==", }, { []interface{}{123, 456, 78}, "37b07f6264727bdd5818a3093e91e6bd", "N7B/YmRye91YGKMJPpHmvQ==", }, } for _, v := range tests { h := Md5(v.in...) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } } func TestSha1(t *testing.T) { tests := []struct { in []interface{} hex string b64 string }{ { []interface{}{[]byte("12345678")}, "7c222fb2927d828af22f592134e8932480637c0d", "fCIvspJ9goryL1khNOiTJIBjfA0=", }, { []interface{}{"12345678"}, "7c222fb2927d828af22f592134e8932480637c0d", "fCIvspJ9goryL1khNOiTJIBjfA0=", }, { []interface{}{"1234", "5678"}, "495b10744ee2c4d5423d7e25e7632d31db08fc00", "SVsQdE7ixNVCPX4l52MtMdsI/AA=", }, { []interface{}{1234, 5678}, "495b10744ee2c4d5423d7e25e7632d31db08fc00", "SVsQdE7ixNVCPX4l52MtMdsI/AA=", }, { []interface{}{123, 456, 78}, "d00e00aca9d085d6b53a363c7a95e112b3064270", "0A4ArKnQhda1OjY8epXhErMGQnA=", }, } for _, v := range tests { h := Sha1(v.in...) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } } func TestSha256(t *testing.T) { tests := []struct { in []interface{} hex string b64 string }{ { []interface{}{[]byte("12345678")}, "ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f", "73l8gRjwLftklgfdXT+MdiMEjJwGPVMsyVxe16iYpk8=", }, { []interface{}{"12345678"}, "ef797c8118f02dfb649607dd5d3f8c7623048c9c063d532cc95c5ed7a898a64f", "73l8gRjwLftklgfdXT+MdiMEjJwGPVMsyVxe16iYpk8=", }, { []interface{}{"1234", "5678"}, "15b4d8e3c2d7987b16be2cfba411d5fbd980340d3736ad61913aba0ffaf3608c", "FbTY48LXmHsWviz7pBHV+9mANA03Nq1hkTq6D/rzYIw=", }, { []interface{}{1234, 5678}, "15b4d8e3c2d7987b16be2cfba411d5fbd980340d3736ad61913aba0ffaf3608c", "FbTY48LXmHsWviz7pBHV+9mANA03Nq1hkTq6D/rzYIw=", }, { []interface{}{123, 456, 78}, "ac817a17155f86736d8389b3943149f54da90ff16f6376c00b5aef47ed868ea9", "rIF6FxVfhnNtg4mzlDFJ9U2pD/FvY3bAC1rvR+2Gjqk=", }, } for _, v := range tests { h := Sha256(v.in...) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } } func TestSha512(t *testing.T) { tests := []struct { in []interface{} hex string b64 string }{ { []interface{}{[]byte("12345678")}, "fa585d89c851dd338a70dcf535aa2a92fee7836dd6aff1226583e88e0996293f16bc009c652826e0fc5c70669" + "5a03cddce372f139eff4d13959da6f1f5d3eabe", "+lhdichR3TOKcNz1Naoqkv7ng23Wr/EiZYPojgmWKT8WvACcZSgm4PxccGaVoDzdzjcvE57/TROVnabx9dPqvg==", }, { []interface{}{"12345678"}, "fa585d89c851dd338a70dcf535aa2a92fee7836dd6aff1226583e88e0996293f16bc009c652826e0fc5c70669" + "5a03cddce372f139eff4d13959da6f1f5d3eabe", "+lhdichR3TOKcNz1Naoqkv7ng23Wr/EiZYPojgmWKT8WvACcZSgm4PxccGaVoDzdzjcvE57/TROVnabx9dPqvg==", }, { []interface{}{"1234", "5678"}, "5a3d898311876b67bffacdd911908301a6ceb0f1256ddc1bbdbf130beed31f3db726c9cc7eac9dc7f70911df3" + "19c30a4509db302a271e72bf8d64c0d73718c7e", "Wj2JgxGHa2e/+s3ZEZCDAabOsPElbdwbvb8TC+7THz23JsnMfqydx/cJEd8xnDCkUJ2zAqJx5yv41kwNc3GMfg==", }, { []interface{}{1234, 5678}, "5a3d898311876b67bffacdd911908301a6ceb0f1256ddc1bbdbf130beed31f3db726c9cc7eac9dc7f70911df3" + "19c30a4509db302a271e72bf8d64c0d73718c7e", "Wj2JgxGHa2e/+s3ZEZCDAabOsPElbdwbvb8TC+7THz23JsnMfqydx/cJEd8xnDCkUJ2zAqJx5yv41kwNc3GMfg==", }, { []interface{}{123, 456, 78}, "778475d192b6effb468014c31863e98fdb7b18be556f79940c4a0430866246beaf52e4075f7484a1b15cf5c10" + "16d98e244b07293b0cd314cecf08a8d5ae0383e", "d4R10ZK27/tGgBTDGGPpj9t7GL5Vb3mUDEoEMIZiRr6vUuQHX3SEobFc9cEBbZjiRLByk7DNMUzs8IqNWuA4Pg==", }, } for _, v := range tests { h := Sha512(v.in...) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } } func TestHmacMd5(t *testing.T) { key := "87654321" tests := []struct { in []interface{} hex string b64 string }{ { []interface{}{[]byte("12345678")}, "2589a5e790d014bf42e049126624cbdd", "JYml55DQFL9C4EkSZiTL3Q==", }, { []interface{}{"12345678"}, "2589a5e790d014bf42e049126624cbdd", "JYml55DQFL9C4EkSZiTL3Q==", }, { []interface{}{"1234", "5678"}, "b13909276f8ff40ee0acfc845ebe2b70", "sTkJJ2+P9A7grPyEXr4rcA==", }, { []interface{}{1234, 5678}, "b13909276f8ff40ee0acfc845ebe2b70", "sTkJJ2+P9A7grPyEXr4rcA==", }, { []interface{}{123, 456, 78}, "b3dc572ce8fd87c48d51c303ae315307", "s9xXLOj9h8SNUcMDrjFTBw==", }, } for _, v := range tests { h := HmacMd5(key, v.in...) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } } func TestHmacSha1(t *testing.T) { key := "87654321" tests := []struct { in []interface{} hex string b64 string }{ { []interface{}{[]byte("12345678")}, "3f271885b5503055cf2b93facc5cde88f94f7708", "PycYhbVQMFXPK5P6zFzeiPlPdwg=", }, { []interface{}{"12345678"}, "3f271885b5503055cf2b93facc5cde88f94f7708", "PycYhbVQMFXPK5P6zFzeiPlPdwg=", }, { []interface{}{"1234", "5678"}, "49498ad44ae78c3da4ce326648c7c2bb6e7838cc", "SUmK1ErnjD2kzjJmSMfCu254OMw=", }, { []interface{}{1234, 5678}, "49498ad44ae78c3da4ce326648c7c2bb6e7838cc", "SUmK1ErnjD2kzjJmSMfCu254OMw=", }, { []interface{}{123, 456, 78}, "74c84407f881334f889a0a0ec8ba43a1ae9d9509", "dMhEB/iBM0+ImgoOyLpDoa6dlQk=", }, } for _, v := range tests { h := HmacSha1(key, v.in...) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } } func TestHmacSha256(t *testing.T) { key := "87654321" tests := []struct { in []interface{} hex string b64 string }{ { []interface{}{[]byte("12345678")}, "18cef3462f052e9fad5f4198f4ef397783189c6e25ab9dafbc7071401065ac76", "GM7zRi8FLp+tX0GY9O85d4MYnG4lq52vvHBxQBBlrHY=", }, { []interface{}{"12345678"}, "18cef3462f052e9fad5f4198f4ef397783189c6e25ab9dafbc7071401065ac76", "GM7zRi8FLp+tX0GY9O85d4MYnG4lq52vvHBxQBBlrHY=", }, { []interface{}{"1234", "5678"}, "fb8c94cc6c4e7a2c3651160db8f96fd26cb05a230a204610a5a0498bc5e2a6f9", "+4yUzGxOeiw2URYNuPlv0mywWiMKIEYQpaBJi8Xipvk=", }, { []interface{}{1234, 5678}, "fb8c94cc6c4e7a2c3651160db8f96fd26cb05a230a204610a5a0498bc5e2a6f9", "+4yUzGxOeiw2URYNuPlv0mywWiMKIEYQpaBJi8Xipvk=", }, { []interface{}{123, 456, 78}, "821ab013ab6e38ac94d1b9ed996f019e2180f66d7c61be22660711ec29a34c6b", "ghqwE6tuOKyU0bntmW8BniGA9m18Yb4iZgcR7CmjTGs=", }, } for _, v := range tests { h := HmacSha256(key, v.in...) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } } func TestHmacSha512(t *testing.T) { key := "87654321" tests := []struct { in []interface{} hex string b64 string }{ { []interface{}{[]byte("12345678")}, "defdfafdbdbd488d40691246cffca688c75255ce9bbc7260f63b6e00f5fc4453aff465e6430cb7c7303fb523d" + "bf80b99e1f8ea890fe8ab1de19a33d3da497dce", "3v36/b29SI1AaRJGz/ymiMdSVc6bvHJg9jtuAPX8RFOv9GXmQwy3xzA/tSPb+AuZ4fjqiQ/oqx3hmjPT2kl9zg==", }, { []interface{}{"12345678"}, "defdfafdbdbd488d40691246cffca688c75255ce9bbc7260f63b6e00f5fc4453aff465e6430cb7c7303fb523d" + "bf80b99e1f8ea890fe8ab1de19a33d3da497dce", "3v36/b29SI1AaRJGz/ymiMdSVc6bvHJg9jtuAPX8RFOv9GXmQwy3xzA/tSPb+AuZ4fjqiQ/oqx3hmjPT2kl9zg==", }, { []interface{}{"1234", "5678"}, "d5f6dcb8107a7f1059185380e5205b206fb22b5e3996c2999e01ae2bda3a1a1d017d4eb6e0be799b6a8d97e68" + "d1516e15ab3a9da323af561edd5b635d98a5df6", "1fbcuBB6fxBZGFOA5SBbIG+yK145lsKZngGuK9o6Gh0BfU624L55m2qNl+aNFRbhWrOp2jI69WHt1bY12Ypd9g==", }, { []interface{}{1234, 5678}, "d5f6dcb8107a7f1059185380e5205b206fb22b5e3996c2999e01ae2bda3a1a1d017d4eb6e0be799b6a8d97e68" + "d1516e15ab3a9da323af561edd5b635d98a5df6", "1fbcuBB6fxBZGFOA5SBbIG+yK145lsKZngGuK9o6Gh0BfU624L55m2qNl+aNFRbhWrOp2jI69WHt1bY12Ypd9g==", }, { []interface{}{123, 456, 78}, "4c859bcf0ff932b34a4dc0d8b5f1aa537d67ad2be26f67c18de4626a503d28cb69525538efd0f3c363b2b6f9d" + "9a7ac121bfd64389ca2d94abf68867acf1b2c45", "TIWbzw/5MrNKTcDYtfGqU31nrSvib2fBjeRialA9KMtpUlU479Dzw2OytvnZp6wSG/1kOJyi2Uq/aIZ6zxssRQ==", }, } for _, v := range tests { h := HmacSha512(key, v.in...) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } } func TestFileMd5(t *testing.T) { fd, err := os.Open("/dev/null") assert.Nil(t, err) defer fd.Close() tests := []struct { in interface{} hex string b64 string }{ {"/dev/null", "d41d8cd98f00b204e9800998ecf8427e", "1B2M2Y8AsgTpgAmY7PhCfg=="}, {fd, "d41d8cd98f00b204e9800998ecf8427e", "1B2M2Y8AsgTpgAmY7PhCfg=="}, } for _, v := range tests { h, err := FileMd5(v.in) assert.Nil(t, err) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } _, err = FileMd5("/i-am-not-exists") assert.NotNil(t, err) assert.Panic(t, func() { _, _ = FileMd5(true) }) } func TestFileSha1(t *testing.T) { fd, err := os.Open("/dev/null") assert.Nil(t, err) defer fd.Close() tests := []struct { in interface{} hex string b64 string }{ {"/dev/null", "da39a3ee5e6b4b0d3255bfef95601890afd80709", "2jmj7l5rSw0yVb/vlWAYkK/YBwk="}, {fd, "da39a3ee5e6b4b0d3255bfef95601890afd80709", "2jmj7l5rSw0yVb/vlWAYkK/YBwk="}, } for _, v := range tests { h, err := FileSha1(v.in) assert.Nil(t, err) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } _, err = FileSha1("/i-am-not-exists") assert.NotNil(t, err) assert.Panic(t, func() { _, _ = FileSha1(true) }) } func TestFileSha256(t *testing.T) { fd, err := os.Open("/dev/null") assert.Nil(t, err) defer fd.Close() tests := []struct { in interface{} hex string b64 string }{ { "/dev/null", "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", }, { fd, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", }, } for _, v := range tests { h, err := FileSha256(v.in) assert.Nil(t, err) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } _, err = FileSha256("/i-am-not-exists") assert.NotNil(t, err) assert.Panic(t, func() { _, _ = FileSha256(true) }) } func TestFileSha512(t *testing.T) { fd, err := os.Open("/dev/null") assert.Nil(t, err) defer fd.Close() tests := []struct { in interface{} hex string b64 string }{ { "/dev/null", "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d28" + "77eec2f63b931bd47417a81a538327af927da3e", "z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGlODJ6+SfaPg==", }, { fd, "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d28" + "77eec2f63b931bd47417a81a538327af927da3e", "z4PhNX7vuL3xVChQ1m2AB9Yg5AULVxXcg/SpIdNs6c5H0NE8XYXysP+DGNKHfuwvY7kxvUdBeoGlODJ6+SfaPg==", }, } for _, v := range tests { h, err := FileSha512(v.in) assert.Nil(t, err) bs, _ := base64.StdEncoding.DecodeString(v.b64) assert.Equal(t, h.Bytes(), bs) assert.Equal(t, h.Hex(), v.hex) assert.Equal(t, h.Base64(), v.b64) } _, err = FileSha512("/i-am-not-exists") assert.NotNil(t, err) assert.Panic(t, func() { _, _ = FileSha512(true) }) } gokit-0.25.9/xhttp/ 0000775 0000000 0000000 00000000000 14262463342 0014101 5 ustar 00root root 0000000 0000000 gokit-0.25.9/xhttp/README.md 0000664 0000000 0000000 00000005620 14262463342 0015363 0 ustar 00root root 0000000 0000000 # GoKit - xhttp HTTP kits for Golang development. ## Features - Light weight and Easy to use - Cookies and Proxy are support - Easy use with friendly JSON api - Upload and Download file support - Debug and Trace info are open - Retry request is possible - Cache request by method ## Installation go get -u github.com/likexian/gokit ## Importing import ( "github.com/likexian/gokit/xhttp" ) ## Documentation Visit the docs on [GoDoc](https://godoc.org/github.com/likexian/gokit/xhttp) ## Example ### The Most easy way ```go rsp, err := xhttp.Get(context.Background(), "https://www.likexian.com/") if err != nil { panic(err) } defer rsp.Close() text, err := rsp.String() if err == nil { fmt.Println("http status code:", rsp.StatusCode) fmt.Println("http response body:", text) } ``` ### Do a Post with form and files ```go // xhttp.FormParam is form, xhttp.FormFile is file rsp, err := xhttp.Post(context.Background(), "https://www.likexian.com/", xhttp.FormParam{"name": "likexian", "age": 18}, xhttp.FormFile{"file": "README.md"}) if err != nil { panic(err) } defer rsp.Close() json, err := rsp.JSON() if err == nil { // http response {"status": {"code": 1, "message": "ok"}} code, _ := json.Get("status.code").Int() fmt.Println("json status code:", code) } ``` ### Use as Interactive mode ```go req := xhttp.New() // set ua and referer req.SetUA("the new ua") req.SetReferer("http://the-referer-url.com") // set tcp connect timeout and client total timeout req.SetConnectTimeout(3) req.SetClientTimeout(30) // not follow 302 and use cookies req.FollowRedirect(false) req.EnableCookie(true) // will send get to https://www.likexian.com/?v=1.0.0 rsp, err := req.Get(context.Background(), "https://www.likexian.com/", xhttp.QueryParam{"v", "1.0.0"}) if err != nil { panic(err) } // save file as index.html defer rsp.Close() _, err := rsp.File("index.html") if err == nil { fmt.Println("Url download as index.html") } // use the request param as above rsp, err := req.Get(context.Background(), "https://www.likexian.com/page/") if err != nil { panic(err) } defer rsp.Close() ... ``` ### xhttp.Request not thread-safe This version of xhttp.Request is not thread-safe, please New every thread when doing concurrent ```go for i := 0; i < 100; i++ { go func() { // always New one req := New() rsp, err := req.Do(context.Background(), "GET", LOCALURL) if err != nil { fmt.Println(err) return } defer rsp.Close() str, err := rsp.String() if err == nil { fmt.Println(str) } }() } ``` ## License Copyright 2012-2022 [Li Kexian](https://www.likexian.com/) Licensed under the Apache License 2.0 ## Donation If this project is helpful, please share it with friends. If you want to thank me, you can [give me a cup of coffee](https://www.likexian.com/donate/). gokit-0.25.9/xhttp/wrap.go 0000664 0000000 0000000 00000004345 14262463342 0015407 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package xhttp import ( "compress/gzip" "io" "io/ioutil" "net/http" "strings" "sync" ) // gzPool is gzip writer pool var gzPool = sync.Pool{ New: func() interface{} { return gzip.NewWriter(ioutil.Discard) }, } // gzResponseWriter is gzip ResponseWriter type gzResponseWriter struct { io.Writer http.ResponseWriter } // Write write body byte func (w *gzResponseWriter) Write(b []byte) (int, error) { return w.Writer.Write(b) } // WriteString write body string func (w *gzResponseWriter) WriteString(s string) (int, error) { return w.Writer.Write([]byte(s)) } // WriteHeader write http status code header func (w *gzResponseWriter) WriteHeader(status int) { w.Header().Del("Content-Length") w.ResponseWriter.WriteHeader(status) } // GzWrap is http gzip transparent compression middleware func GzWrap(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Vary", "Accept-Encoding") if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") { next.ServeHTTP(w, r) return } gz := gzPool.Get().(*gzip.Writer) defer func() { gz.Reset(ioutil.Discard) gzPool.Put(gz) }() gz.Reset(w) defer gz.Close() w.Header().Set("Content-Encoding", "gzip") next.ServeHTTP(&gzResponseWriter{Writer: gz, ResponseWriter: w}, r) }) } // SetHeaderWrap is http set header middleware func SetHeaderWrap(next http.Handler, header Header) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { for k, v := range header { w.Header().Set(k, v) } next.ServeHTTP(w, r) }) } gokit-0.25.9/xhttp/xhttp.go 0000664 0000000 0000000 00000050473 14262463342 0015610 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package xhttp import ( "bytes" "context" "crypto/tls" "fmt" "io" "io/ioutil" "mime/multipart" "net" "net/http" "net/http/cookiejar" "net/http/httputil" "net/url" "os" "path/filepath" "strings" "time" "github.com/likexian/gokit/assert" "github.com/likexian/gokit/xcache" "github.com/likexian/gokit/xfile" "github.com/likexian/gokit/xhash" "github.com/likexian/gokit/xjson" "github.com/likexian/gokit/xrand" "github.com/likexian/gokit/xtime" ) var ( // DefaultRequest is default request DefaultRequest = New() // Caching is http request cache caching = xcache.New(xcache.MemoryCache) // supportMethod list all supported http method supportMethod = []string{ "GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", } ) // Timeout storing timeout setting type Timeout struct { ConnectTimeout int TLSHandshakeTimeout int ResponseHeaderTimeout int ExpectContinueTimeout int ClientTimeout int KeepAliveTimeout int } // Retries storing retry setting type Retries struct { Times int Sleep time.Duration } // Dumping storing http dump setting type Dumping struct { DumpHTTP bool DumpBody bool } // Caching storing cache method and ttl type Caching struct { Method map[string]int64 } // Request storing request data type Request struct { ClientID string Request *http.Request Client *http.Client ClientKey string Timeout Timeout Caching Caching Retries Retries Dumping Dumping } // Tracing storing tracing data type Tracing struct { ClientID string RequestID string Timestamp string Nonce string SendTime int64 RecvTime int64 Retries int } // Response storing response data type Response struct { Method string URL *url.URL Response *http.Response StatusCode int ContentLength int64 CacheKey string Tracing Tracing Dumping [][]byte } // Host is http host type Host string // Header is http request header type Header map[string]string // QueryParam is query param map pass to xhttp type QueryParam map[string]interface{} // FormParam is form param map pass to xhttp type FormParam map[string]interface{} // JSONParam is json param map pass to xhttp type JSONParam map[string]interface{} // FormFile is form file for upload, formfield: filename type FormFile map[string]string // param storing QueryParam and FormParam data set by Do type param struct { url.Values } // getValues return values pointer func (p *param) getValues() url.Values { if p.Values == nil { p.Values = make(url.Values) } return p.Values } func (p *param) Update(m param) { if m.Values == nil { return } vv := p.getValues() for m, n := range m.Values { for _, nn := range n { vv.Set(m, nn) } } } // Adds add map data to param func (p *param) Adds(m map[string]interface{}) { if len(m) == 0 { return } vv := p.getValues() for k, v := range m { vv.Add(k, fmt.Sprint(v)) } } // IsEmpty returns param is empty func (p *param) IsEmpty() bool { return p.Values == nil } // Version returns package version func Version() string { return "0.19.0" } // Author returns package author func Author() string { return "[Li Kexian](https://www.likexian.com/)" } // License returns package license func License() string { return "Licensed under the Apache License 2.0" } // New init a new xhttp client func New() (r *Request) { timeout := Timeout{ ConnectTimeout: 15, TLSHandshakeTimeout: 5, ResponseHeaderTimeout: 30, ExpectContinueTimeout: 5, ClientTimeout: 120, KeepAliveTimeout: 60, } request := &http.Request{ Header: http.Header{ "User-Agent": []string{fmt.Sprintf("GoKit XHTTP Client/%s", Version())}, }, } client := &http.Client{ Transport: &http.Transport{ TLSClientConfig: &tls.Config{InsecureSkipVerify: false}, DisableCompression: false, }, } cache := Caching{ Method: map[string]int64{}, } r = &Request{ ClientID: xhash.Sha1("xhttp", xtime.Ns()).Hex(), Request: request, Client: client, ClientKey: "", Timeout: timeout, Caching: cache, Retries: Retries{}, Dumping: Dumping{}, } return } // Get do http GET request and returns response func Get(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return DefaultRequest.Do(ctx, "GET", surl, args...) } // Head do http HEAD request and returns response func Head(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return DefaultRequest.Do(ctx, "HEAD", surl, args...) } // Post do http POST request and returns response func Post(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return DefaultRequest.Do(ctx, "POST", surl, args...) } // Put do http PUT request and returns response func Put(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return DefaultRequest.Do(ctx, "PUT", surl, args...) } // Patch do http PATCH request and returns response func Patch(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return DefaultRequest.Do(ctx, "PATCH", surl, args...) } // Delete do http DELETE request and returns response func Delete(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return DefaultRequest.Do(ctx, "DELETE", surl, args...) } // Options do http OPTIONS request and returns response func Options(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return DefaultRequest.Do(ctx, "OPTIONS", surl, args...) } // Get do http GET request and returns response func (r *Request) Get(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return r.Do(ctx, "GET", surl, args...) } // Head do http HEAD request and returns response func (r *Request) Head(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return r.Do(ctx, "HEAD", surl, args...) } // Post do http POST request and returns response func (r *Request) Post(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return r.Do(ctx, "POST", surl, args...) } // Put do http PUT request and returns response func (r *Request) Put(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return r.Do(ctx, "PUT", surl, args...) } // Patch do http PATCH request and returns response func (r *Request) Patch(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return r.Do(ctx, "PATCH", surl, args...) } // Delete do http DELETE request and returns response func (r *Request) Delete(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return r.Do(ctx, "DELETE", surl, args...) } // Options do http OPTIONS request and returns response func (r *Request) Options(ctx context.Context, surl string, args ...interface{}) (s *Response, err error) { return r.Do(ctx, "OPTIONS", surl, args...) } // GetHeader return request header value by name func (r *Request) GetHeader(name string) string { return r.Request.Header.Get(name) } // SetClientKey set key for signing requestid func (r *Request) SetClientKey(key string) *Request { r.ClientKey = key return r } // SetHost set http request host func (r *Request) SetHost(host string) *Request { r.Request.Host = host return r } // SetHeader set http request header func (r *Request) SetHeader(key, value string) *Request { r.Request.Header.Set(key, value) return r } // SetUA set http request user-agent func (r *Request) SetUA(ua string) *Request { r.SetHeader("User-Agent", ua) return r } // SetReferer set http request referer func (r *Request) SetReferer(referer string) *Request { r.SetHeader("Referer", referer) return r } // SetGzip set http request gzip func (r *Request) SetGzip(gzip bool) *Request { r.Client.Transport.(*http.Transport).DisableCompression = !gzip return r } // SetVerifyTLS set http request tls verify func (r *Request) SetVerifyTLS(verify bool) *Request { r.Client.Transport.(*http.Transport).TLSClientConfig.InsecureSkipVerify = !verify return r } // SetKeepAliveTimeout set http keepalive timeout func (r *Request) SetKeepAliveTimeout(timeout int) *Request { r.Timeout.KeepAliveTimeout = timeout r.SetTimeout(r.Timeout) return r } // SetConnectTimeout set http connect timeout func (r *Request) SetConnectTimeout(timeout int) *Request { r.Timeout.ConnectTimeout = timeout r.SetTimeout(r.Timeout) return r } // SetClientTimeout set http client timeout func (r *Request) SetClientTimeout(timeout int) *Request { r.Timeout.ClientTimeout = timeout r.SetTimeout(r.Timeout) return r } // SetTimeout set http request timeout func (r *Request) SetTimeout(timeout Timeout) *Request { r.Timeout = timeout if r.Timeout.KeepAliveTimeout <= 0 { r.Client.Transport.(*http.Transport).DisableKeepAlives = true } else { r.Client.Transport.(*http.Transport).DisableKeepAlives = false } r.Client.Transport.(*http.Transport).DialContext = (&net.Dialer{ Timeout: time.Duration(r.Timeout.ConnectTimeout) * time.Second, KeepAlive: time.Duration(r.Timeout.KeepAliveTimeout) * time.Second, }).DialContext r.Client.Transport.(*http.Transport).TLSHandshakeTimeout = time.Duration(r.Timeout.TLSHandshakeTimeout) * time.Second r.Client.Transport.(*http.Transport).ResponseHeaderTimeout = time.Duration(r.Timeout.ResponseHeaderTimeout) * time.Second r.Client.Transport.(*http.Transport).ExpectContinueTimeout = time.Duration(r.Timeout.ExpectContinueTimeout) * time.Second r.Client.Timeout = time.Duration(r.Timeout.ClientTimeout) * time.Second return r } // GetTimeout get http request timeout func (r *Request) GetTimeout() Timeout { return r.Timeout } // SetProxy set http request proxy func (r *Request) SetProxy(proxy func(*http.Request) (*url.URL, error)) *Request { r.Client.Transport.(*http.Transport).Proxy = proxy return r } // SetProxyURL set http request proxy url func (r *Request) SetProxyURL(proxy string) *Request { if !strings.HasPrefix(proxy, "http://") && !strings.HasPrefix(proxy, "https://") && !strings.HasPrefix(proxy, "socks5://") { proxy = "http://" + proxy } r.SetProxy(func(req *http.Request) (*url.URL, error) { return url.ParseRequestURI(proxy) }) return r } // FollowRedirect set http request follow redirect func (r *Request) FollowRedirect(follow bool) *Request { if follow { r.Client.CheckRedirect = nil } else { r.Client.CheckRedirect = func(req *http.Request, via []*http.Request) error { return http.ErrUseLastResponse } } return r } // EnableCookie set http request enable cookie func (r *Request) EnableCookie(enable bool) *Request { if enable { if r.Client.Jar == nil { r.Client.Jar, _ = cookiejar.New(nil) } } else { if r.Client.Jar != nil { r.Client.Jar = nil } } return r } // EnableCache enable http client cache func (r *Request) EnableCache(method string, ttl int64) *Request { method = strings.ToUpper(strings.TrimSpace(method)) if assert.IsContains(supportMethod, method) { r.Caching.Method[method] = ttl } return r } // SetRetries set retry param // int arg is setting retry times, time.Duration is setting retry sleep duration // 0: no retry (default), -1: retry until success, > 1: retry x times func (r *Request) SetRetries(args ...interface{}) *Request { if len(args) == 0 { panic("xhttp: the arguments is empty") } for i := 0; i < len(args); i++ { switch args[i].(type) { case int: r.Retries.Times = args[i].(int) case time.Duration: r.Retries.Sleep = args[i].(time.Duration) } } return r } // SetDump set http dump func (r *Request) SetDump(dumpHTTP, dumpBody bool) *Request { r.Dumping.DumpHTTP = dumpHTTP r.Dumping.DumpBody = dumpBody return r } // Do send http request and return response func (r *Request) Do(ctx context.Context, //nolint:cyclop method, surl string, args ...interface{}) (s *Response, err error) { r.Request.Host = "" r.Request.Header.Del("Cookie") r.Request.Header.Del("Content-Type") method = strings.ToUpper(strings.TrimSpace(method)) if !assert.IsContains(supportMethod, method) { return nil, fmt.Errorf("xhttp: not supported method: %s", method) } r.Request.Method = method surl = strings.TrimSpace(surl) if surl == "" { return nil, fmt.Errorf("xhttp: no request url specify") } var formBody string var formParam param var queryParam param formFile := FormFile{} for _, v := range args { switch vv := v.(type) { case Host: r.SetHost(string(vv)) case Header: for k, v := range vv { r.SetHeader(k, v) } case http.Header: for k, v := range vv { for _, vv := range v { r.SetHeader(k, vv) } } case *http.Client: r.Client = vv case *http.Cookie: r.Request.AddCookie(vv) case FormParam: formParam.Adds(vv) case QueryParam: queryParam.Adds(vv) case url.Values: if assert.IsContains([]string{"POST", "PUT", "PATCH"}, method) { formParam.Update(param{vv}) } else { queryParam.Update(param{vv}) } case JSONParam: formBody, err = xjson.Dumps(vv) if err != nil { return nil, fmt.Errorf("xhttp: encode json param failed: %w", err) } r.Request.Header.Set("Content-Type", "application/json") case string: formBody = vv case []byte: formBody = string(vv) case bytes.Buffer: formBody = vv.String() case FormFile: for k, v := range vv { formFile[k] = v } } } r.Request = r.Request.WithContext(ctx) r.Request.Body = nil r.Request.ContentLength = 0 if assert.IsContains([]string{"POST", "PUT", "PATCH"}, method) { if len(formFile) > 0 { pr, pw := io.Pipe() bw := multipart.NewWriter(pw) go func() { for k, v := range formFile { fw, err := bw.CreateFormFile(k, v) if err != nil { continue } fd, err := os.Open(v) if err != nil { continue } _, err = io.Copy(fw, fd) fd.Close() if err != nil { continue } } for k, v := range formParam.Values { for _, vv := range v { _ = bw.WriteField(k, vv) } } bw.Close() pw.Close() }() r.SetHeader("Content-Type", bw.FormDataContentType()) r.Request.Body = ioutil.NopCloser(pr) } else { if !formParam.IsEmpty() { formBody += formParam.Encode() } if formBody != "" { r.Request.Body = ioutil.NopCloser(bytes.NewReader([]byte(formBody))) r.Request.ContentLength = int64(len(formBody)) if r.Request.Header.Get("Content-Type") == "" { r.Request.Header.Set("Content-Type", "application/x-www-form-urlencoded") } } } } if !queryParam.IsEmpty() { q := queryParam.Encode() if strings.Contains(surl, "?") { surl += "&" + q } else { surl += "?" + q } } u, err := url.Parse(surl) if err != nil { return nil, fmt.Errorf("xhttp: parse url failed: %w", err) } r.Request.URL = u s = &Response{ Method: r.Request.Method, URL: r.Request.URL, Tracing: Tracing{ Timestamp: fmt.Sprintf("%d", xtime.S()), Nonce: fmt.Sprintf("%d", xrand.IntRange(1000000, 9999999)), ClientID: r.ClientID, Retries: -1, }, } startAt := xtime.Ms() defer func() { s.Tracing.SendTime = xtime.Ms() - startAt }() s.Tracing.RequestID = xhash.Sha1("xhttp", s.Tracing.Timestamp, s.Tracing.Nonce, s.Method, s.URL.Path, s.URL.RawQuery, r.ClientKey).Hex() r.Request.Header.Set("X-HTTP-GoKit-RequestId", fmt.Sprintf("%s-%s-%s", s.Tracing.Timestamp, s.Tracing.Nonce, s.Tracing.RequestID)) cacheTTL, cacheEnabled := r.Caching.Method[s.Method] if cacheEnabled || r.Dumping.DumpHTTP { dumpBody := r.Dumping.DumpBody if cacheEnabled { dumpBody = true } d, err := httputil.DumpRequestOut(r.Request, dumpBody) if err == nil { s.Dumping = append(s.Dumping, d) } } if cacheEnabled { body := "" if len(s.Dumping) > 0 { d := strings.Split(string(s.Dumping[0]), "\r\n\r\n") if len(d) > 1 { body = d[1] } s.CacheKey = xhash.Sha1(s.Method, s.URL.String(), body).Hex() cacheVal := caching.Get(s.CacheKey) if cacheVal != nil { s = cacheVal.(*Response) return } } } for i := 0; r.Retries.Times == -1 || i <= r.Retries.Times; i++ { s.Tracing.Retries++ s.Response, err = r.Client.Do(r.Request) if err == nil { break } if r.Retries.Sleep > 0 { time.Sleep(r.Retries.Sleep) } } if err == nil { s.StatusCode = s.Response.StatusCode s.ContentLength = s.Response.ContentLength } if r.Dumping.DumpHTTP { d, err := httputil.DumpResponse(s.Response, r.Dumping.DumpBody) if err == nil { s.Dumping = append(s.Dumping, d) } } if s.CacheKey != "" { _ = caching.Set(s.CacheKey, s, cacheTTL) } return } // Close close response body func (r *Response) Close() { r.Response.Body.Close() } // GetHeader return response header value by name func (r *Response) GetHeader(name string) string { return r.Response.Header.Get(name) } // File save response body to file func (r *Response) File(paths ...string) (size int64, err error) { fpath := "" if len(paths) > 0 { fpath = paths[0] } fpath = strings.TrimSpace(fpath) if fpath == "" { _, fpath = filepath.Split(r.URL.String()) if fpath == "" { fpath = "index.html" } } else { dir, name := filepath.Split(fpath) if name == "" { fpath = dir + "index.html" } if dir != "" && !xfile.Exists(dir) { err = os.MkdirAll(dir, 0755) if err != nil { return } } } if xfile.Exists(fpath) { return 0, fmt.Errorf("xhttp: file %s is exists", fpath) } defer r.Response.Body.Close() if r.Response.StatusCode != http.StatusOK { return 0, fmt.Errorf("xhttp: bad status code: %d", r.Response.StatusCode) } startAt := xtime.Ms() defer func() { r.Tracing.RecvTime = xtime.Ms() - startAt }() fd, err := xfile.New(fpath) if err != nil { return } defer fd.Close() size, err = io.Copy(fd, r.Response.Body) return } // Bytes returns response body as bytes func (r *Response) Bytes() (b []byte, err error) { startAt := xtime.Ms() defer func() { r.Tracing.RecvTime = xtime.Ms() - startAt }() b, err = ioutil.ReadAll(r.Response.Body) r.Response.Body.Close() if r.CacheKey != "" { r.Response.Body = ioutil.NopCloser(bytes.NewBuffer(b)) } return } // String returns response body as string func (r *Response) String() (s string, err error) { b, err := r.Bytes() if err != nil { return } return string(b), nil } // JSON returns response body as *xjson.JSON // For more please refer to gokit/xjson func (r *Response) JSON() (*xjson.JSON, error) { s, err := r.String() if err != nil { return &xjson.JSON{}, err } return xjson.Loads(s) } // Dump returns http dump of request and response // [bytes[request], bytes[response]] func (r *Response) Dump() [][]byte { return r.Dumping } // CheckClient returns is a valid client request // used by http server, it will check the requestId func CheckClient(r *http.Request, ClientKey string) error { id := r.Header.Get("X-Http-Gokit-Requestid") if id == "" { return fmt.Errorf("xhttp: missing request id") } ids := strings.Split(id, "-") if len(ids) != 3 { return fmt.Errorf("xhttp: request id invalid") } tm, err := assert.ToInt64(ids[0]) if err != nil { return fmt.Errorf("xhttp: time stamp invalid") } now := xtime.S() if tm-now > 300 || tm-now < -300 { return fmt.Errorf("xhttp: time stamp expired") } nonce, err := assert.ToInt64(ids[1]) if err != nil { return fmt.Errorf("xhttp: request nonce invalid") } sum := xhash.Sha1("xhttp", tm, nonce, r.Method, r.URL.Path, r.URL.RawQuery, ClientKey).Hex() if sum != ids[2] { return fmt.Errorf("xhttp: hash value not matched") } return nil } // GetClientIPs returns all ips from http client func GetClientIPs(r *http.Request) []string { ips := []string{} for _, h := range []string{"X-Real-Ip", "X-Forwarded-For"} { ip := r.Header.Get(h) if ip != "" { for _, v := range strings.Split(ip, ",") { v = strings.TrimSpace(v) if v != "" { ips = append(ips, v) } } } } ips = append(ips, strings.Split(r.RemoteAddr, ":")[0]) return ips } gokit-0.25.9/xhttp/xhttp_test.go 0000664 0000000 0000000 00000077074 14262463342 0016655 0 ustar 00root root 0000000 0000000 /* * Copyright 2012-2022 Li Kexian * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * A toolkit for Golang development * https://www.likexian.com/ */ package xhttp import ( "bytes" "context" "fmt" "io/ioutil" "net/http" "net/url" "os" "strings" "sync" "testing" "time" "github.com/likexian/gokit/assert" "github.com/likexian/gokit/xfile" "github.com/likexian/gokit/xjson" "github.com/likexian/gokit/xtime" ) var ( LOCALURL = ServerForTesting("6666") ) func TestVersion(t *testing.T) { assert.Contains(t, Version(), ".") assert.Contains(t, Author(), "likexian") assert.Contains(t, License(), "Apache License") } func TestNew(t *testing.T) { req := New() ctx := context.Background() _, err := req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) assert.Equal(t, req.Request.Method, "GET") assert.Equal(t, req.Request.URL.String(), LOCALURL) _, err = req.Do(ctx, "CODE", LOCALURL) assert.NotNil(t, err) _, err = req.Do(ctx, "GET", "") assert.NotNil(t, err) _, err = req.Do(ctx, "GET", "::") assert.NotNil(t, err) _, err = req.Do(ctx, "get", LOCALURL) assert.Nil(t, err) assert.Equal(t, req.Request.Method, "GET") assert.Equal(t, req.Request.URL.String(), LOCALURL) _, err = req.Do(ctx, "POST", LOCALURL+"post") assert.Nil(t, err) assert.Equal(t, req.Request.Method, "POST") assert.Equal(t, req.Request.URL.String(), LOCALURL+"post") clientID := req.ClientID req = New() _, err = req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) assert.NotEqual(t, req.ClientID, clientID) } func TestMethod(t *testing.T) { ctx := context.Background() rsp, err := Get(ctx, LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = Head(ctx, LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = Post(ctx, LOCALURL+"post") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = Put(ctx, LOCALURL+"put") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = Patch(ctx, LOCALURL+"patch") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = Delete(ctx, LOCALURL+"delete") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = Options(ctx, LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) req := New() rsp, err = req.Get(ctx, LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = req.Head(ctx, LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = req.Post(ctx, LOCALURL+"post") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = req.Put(ctx, LOCALURL+"put") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = req.Patch(ctx, LOCALURL+"patch") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = req.Delete(ctx, LOCALURL+"delete") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) rsp, err = req.Options(ctx, LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) } func TestSetClientKey(t *testing.T) { req := New() assert.Equal(t, req.ClientKey, "") req.SetClientKey(LOCALURL) assert.Equal(t, req.ClientKey, LOCALURL) } func TestSetHost(t *testing.T) { req := New() ctx := context.Background() host := req.Request.Host req.SetHost("likexian.com") assert.Equal(t, req.Request.Host, "likexian.com") assert.NotEqual(t, req.Request.Host, host) var h Host = "likexian.com" _, _ = req.Do(ctx, "GET", LOCALURL, h) assert.Equal(t, req.Request.Host, "likexian.com") } func TestSetHeader(t *testing.T) { req := New() ctx := context.Background() author := req.GetHeader("X-Author") assert.Equal(t, author, "") req.SetHeader("X-Author", "likexian") assert.Equal(t, req.GetHeader("X-Author"), "likexian") h1 := Header{ "X-Version": Version(), } _, _ = req.Do(ctx, "GET", LOCALURL, h1) assert.Equal(t, req.GetHeader("X-Author"), "likexian") assert.Equal(t, req.GetHeader("X-Version"), Version()) h2 := http.Header{ "X-License": []string{License()}, } _, _ = req.Do(ctx, "GET", LOCALURL, h2) assert.Equal(t, req.GetHeader("X-Author"), "likexian") assert.Equal(t, req.GetHeader("X-Version"), Version()) assert.Equal(t, req.GetHeader("X-License"), License()) } func TestSetUA(t *testing.T) { req := New() ua := req.GetHeader("User-Agent") assert.Equal(t, ua, fmt.Sprintf("GoKit XHTTP Client/%s", Version())) req.SetUA("HTTP Client by likexian") assert.Equal(t, req.GetHeader("User-Agent"), "HTTP Client by likexian") } func TestSetReferer(t *testing.T) { req := New() referer := req.GetHeader("referer") assert.Equal(t, referer, "") req.SetHeader("referer", LOCALURL) assert.Equal(t, req.GetHeader("referer"), LOCALURL) req.SetReferer(LOCALURL + "test") assert.Equal(t, req.GetHeader("referer"), LOCALURL+"test") } func TestGetHeader(t *testing.T) { req := New() ctx := context.Background() rsp, err := req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) for _, k := range []string{"Content-Type", "Date", "Server"} { h := rsp.GetHeader(k) assert.NotEqual(t, h, "") } } func TestSetClient(t *testing.T) { req := New() ctx := context.Background() rsp, err := req.Do(ctx, "GET", LOCALURL, &http.Client{}) assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) } func TestBytes(t *testing.T) { req := New() ctx := context.Background() rsp, err := req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) b, err := rsp.Bytes() assert.Nil(t, err) assert.NotEqual(t, len(b), 0) assert.Equal(t, string(b[0:1]), "<") tracing := rsp.Tracing assert.NotEqual(t, tracing.Timestamp, "") assert.NotEqual(t, tracing.Nonce, "") rsp, err = req.Do(ctx, "GET", LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) b, err = rsp.Bytes() assert.Nil(t, err) assert.NotEqual(t, len(b), 0) assert.Equal(t, string(b[0:1]), "{") assert.NotEqual(t, rsp.Tracing.Timestamp, "") assert.NotEqual(t, rsp.Tracing.Nonce, tracing.Nonce) assert.Equal(t, rsp.Tracing.ClientID, tracing.ClientID) assert.NotEqual(t, rsp.Tracing.RequestID, tracing.RequestID) tracing = rsp.Tracing req = New() rsp, err = req.Do(ctx, "GET", LOCALURL+"status/404") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 404) assert.NotEqual(t, rsp.Tracing.ClientID, tracing.ClientID) assert.NotEqual(t, rsp.Tracing.RequestID, tracing.RequestID) } func TestString(t *testing.T) { req := New() ctx := context.Background() rsp, err := req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) s, err := rsp.String() assert.Nil(t, err) assert.NotEqual(t, len(s), 0) assert.Equal(t, s[0:1], "<") rsp, err = req.Do(ctx, "GET", LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) s, err = rsp.String() assert.Nil(t, err) assert.NotEqual(t, len(s), 0) assert.Equal(t, s[0:1], "{") } func TestJSON(t *testing.T) { req := New() ctx := context.Background() rsp, err := req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) _, err = rsp.JSON() assert.NotNil(t, err) rsp, err = req.Do(ctx, "GET", LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) s, err := rsp.JSON() assert.Nil(t, err) assert.Equal(t, s.Get("url").MustString(""), LOCALURL+"get") } func TestFile(t *testing.T) { defer func() { os.Remove("index.html") os.Remove("get.html") os.RemoveAll("tmp") }() req := New() ctx := context.Background() rsp, err := req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) ss, err := rsp.File() assert.Nil(t, err) fs, err := xfile.Size("index.html") assert.Nil(t, err) assert.Equal(t, fs, ss) rsp, err = req.Do(ctx, "GET", LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) ss, err = rsp.File("tmp/") assert.Nil(t, err) fs, err = xfile.Size("tmp/index.html") assert.Nil(t, err) assert.Equal(t, fs, ss) rsp, err = req.Do(ctx, "GET", LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) ss, err = rsp.File("get.html") assert.Nil(t, err) fs, err = xfile.Size("get.html") assert.Nil(t, err) assert.Equal(t, fs, ss) rsp, err = req.Do(ctx, "GET", LOCALURL+"get") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) _, err = rsp.File("get.html") assert.NotNil(t, err) rsp, err = req.Do(ctx, "GET", LOCALURL+"status/404") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 404) _, err = rsp.File("404.html") assert.NotNil(t, err) } func TestFollowRedirect(t *testing.T) { req := New() ctx := context.Background() rsp, err := req.Do(ctx, "GET", LOCALURL+"redirect/3") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) loc := rsp.GetHeader("Location") assert.Equal(t, loc, "") req.FollowRedirect(false) rsp, err = req.Do(ctx, "GET", LOCALURL+"redirect/3") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 302) loc = rsp.GetHeader("Location") assert.Equal(t, loc, "/redirect/2") req.FollowRedirect(true) rsp, err = req.Do(ctx, "GET", LOCALURL+"redirect/3") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) loc = rsp.GetHeader("Location") assert.Equal(t, loc, "") } func TestSetGzip(t *testing.T) { req := New() ctx := context.Background() rsp, err := req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) s, err := rsp.String() assert.Nil(t, err) assert.NotEqual(t, len(s), 0) assert.Equal(t, s[0:1], "<") req.SetGzip(false) rsp, err = req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) s, err = rsp.String() assert.Nil(t, err) assert.NotEqual(t, len(s), 0) assert.Equal(t, s[0:1], "<") req.SetGzip(true) rsp, err = req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) s, err = rsp.String() assert.Nil(t, err) assert.NotEqual(t, len(s), 0) assert.Equal(t, s[0:1], "<") } func TestSetVerifyTls(t *testing.T) { req := New() assert.False(t, req.Client.Transport.(*http.Transport).TLSClientConfig.InsecureSkipVerify) req.SetVerifyTLS(false) assert.True(t, req.Client.Transport.(*http.Transport).TLSClientConfig.InsecureSkipVerify) req.SetVerifyTLS(true) assert.False(t, req.Client.Transport.(*http.Transport).TLSClientConfig.InsecureSkipVerify) } func TestSetKeepAliveTimeout(t *testing.T) { req := New() assert.False(t, req.Client.Transport.(*http.Transport).DisableKeepAlives) req.SetKeepAliveTimeout(0) assert.True(t, req.Client.Transport.(*http.Transport).DisableKeepAlives) req.SetKeepAliveTimeout(30) assert.False(t, req.Client.Transport.(*http.Transport).DisableKeepAlives) } func TestSetConnectTimeout(t *testing.T) { req := New() req.SetConnectTimeout(3) assert.Equal(t, req.GetTimeout().ConnectTimeout, 3) } func TestSetClientTimeout(t *testing.T) { req := New() req.SetClientTimeout(30) assert.Equal(t, req.Client.Timeout, time.Duration(30)*time.Second) } func TestSetTimeout(t *testing.T) { req := New() timeout := req.GetTimeout() timeout.ClientTimeout = 10 timeout.ResponseHeaderTimeout = 3 req.SetTimeout(timeout) assert.Equal(t, req.Client.Timeout, time.Duration(10)*time.Second) assert.Equal(t, req.Client.Transport.(*http.Transport).ResponseHeaderTimeout, time.Duration(3)*time.Second) } func TestSetProxy(t *testing.T) { req := New().SetProxy(func(req *http.Request) (*url.URL, error) { return url.ParseRequestURI("http://127.0.0.1:8080") }) ctx := context.Background() _, err := req.Do(ctx, "GET", LOCALURL) assert.NotNil(t, err) } func TestSetProxyUrl(t *testing.T) { req := New().SetProxyURL("127.0.0.1:8080") ctx := context.Background() _, err := req.Do(ctx, "GET", LOCALURL) assert.NotNil(t, err) } func TestEnableCookie(t *testing.T) { // not enable cookies req := New() ctx := context.Background() req.FollowRedirect(false) rsp, err := req.Do(ctx, "GET", LOCALURL+"cookies/set/k/v") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, len(req.Request.Cookies()), 0) rsp, err = req.Do(ctx, "GET", LOCALURL+"cookies") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, len(req.Request.Cookies()), 0) // enable cookies req.EnableCookie(true) rsp, err = req.Do(ctx, "GET", LOCALURL+"cookies/set/k/v") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, len(req.Request.Cookies()), 0) rsp, err = req.Do(ctx, "GET", LOCALURL+"cookies") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, len(req.Request.Cookies()), 1) // delete cookies rsp, err = req.Do(ctx, "GET", LOCALURL+"cookies/delete?k=") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, len(req.Request.Cookies()), 1) rsp, err = req.Do(ctx, "GET", LOCALURL+"cookies") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, len(req.Request.Cookies()), 0) // set cookie again req.EnableCookie(true) rsp, err = req.Do(ctx, "GET", LOCALURL+"cookies/set/k/v") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, len(req.Request.Cookies()), 0) rsp, err = req.Do(ctx, "GET", LOCALURL+"cookies") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, len(req.Request.Cookies()), 1) // disable cookies req.EnableCookie(false) rsp, err = req.Do(ctx, "GET", LOCALURL+"cookies/set/k/v") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, len(req.Request.Cookies()), 0) rsp, err = req.Do(ctx, "GET", LOCALURL+"cookies") assert.Nil(t, err) defer rsp.Close() assert.Equal(t, len(req.Request.Cookies()), 0) // set cookies by args cookie := &http.Cookie{Name: "k", Value: "likexian"} req.EnableCookie(true) rsp, err = req.Do(ctx, "GET", LOCALURL, cookie) assert.Nil(t, err) defer rsp.Close() assert.Equal(t, len(req.Request.Cookies()), 1) } func TestQueryParam(t *testing.T) { req := New() ctx := context.Background() query := QueryParam{"k": "v"} _, err := req.Do(ctx, "GET", LOCALURL+"get", query) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"get?k=v") query = QueryParam{"a": "1", "b": 2, "c": 3} _, err = req.Do(ctx, "GET", req.Request.URL.String(), query) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"get?k=v&a=1&b=2&c=3") query = QueryParam{} _, err = req.Do(ctx, "GET", LOCALURL+"get", query) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"get") } func TestFormParam(t *testing.T) { req := New() ctx := context.Background() form := FormParam{"k": "v"} rsp, err := req.Do(ctx, "POST", LOCALURL+"post", form) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"post") json, err := rsp.JSON() assert.Nil(t, err) assert.Equal(t, json.Get("form").Get("k.0").MustString(""), "v") form = FormParam{"a": "1", "b": 2, "c": 3} rsp, err = req.Do(ctx, "POST", req.Request.URL.String(), form) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"post") json, err = rsp.JSON() assert.Nil(t, err) assert.Equal(t, json.Get("form").Get("a.0").MustString(""), "1") assert.Equal(t, json.Get("form").Get("b.0").MustString(""), "2") assert.Equal(t, json.Get("form").Get("c.0").MustString(""), "3") form = FormParam{} rsp, err = req.Do(ctx, "POST", LOCALURL+"post", form) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"post") json, err = rsp.JSON() assert.Nil(t, err) m, _ := json.Get("form").Map() assert.Equal(t, m, map[string]interface{}{}) data := map[string]interface{}{"a": "1", "b": 2, "c": 3} rsp, err = req.Do(ctx, "POST", LOCALURL+"post", FormParam(data)) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"post") json, err = rsp.JSON() assert.Nil(t, err) assert.Equal(t, json.Get("form").Get("a.0").MustString(""), "1") assert.Equal(t, json.Get("form").Get("b.0").MustString(""), "2") assert.Equal(t, json.Get("form").Get("c.0").MustString(""), "3") } func TestValuesParam(t *testing.T) { req := New() ctx := context.Background() values := url.Values{"k": []string{"v"}} // url.Values as query string _, err := req.Do(ctx, "GET", LOCALURL+"get", values) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"get?k=v") // url.Values as form data rsp, err := req.Do(ctx, "POST", LOCALURL+"post", values) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"post") json, err := rsp.JSON() assert.Nil(t, err) assert.Equal(t, json.Get("form").Get("k.0").MustString(""), "v") } func TestPostBody(t *testing.T) { req := New() ctx := context.Background() // Post string rsp, err := req.Do(ctx, "POST", LOCALURL+"post", "k=v") assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"post") json, err := rsp.JSON() assert.Nil(t, err) assert.Equal(t, json.Get("form").Get("k.0").MustString(""), "v") // Post []byte rsp, err = req.Do(ctx, "POST", req.Request.URL.String(), []byte("a=1&b=2&c=3")) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"post") json, err = rsp.JSON() assert.Nil(t, err) assert.Equal(t, json.Get("form").Get("a.0").MustString(""), "1") assert.Equal(t, json.Get("form").Get("b.0").MustString(""), "2") assert.Equal(t, json.Get("form").Get("c.0").MustString(""), "3") // Post bytes.Buffer var b bytes.Buffer b.Write([]byte("k=v")) rsp, err = req.Do(ctx, "POST", LOCALURL+"post", b) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"post") json, err = rsp.JSON() assert.Nil(t, err) assert.Equal(t, json.Get("form").Get("k.0").MustString(""), "v") // Post json string rsp, err = req.Do(ctx, "POST", LOCALURL+"post", `{"k": "v"}`, Header{"Content-Type": "application/json"}) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"post") json, err = rsp.JSON() assert.Nil(t, err) assert.Equal(t, json.Get("json").Get("k").MustString(""), "v") // Post map as json data := map[string]interface{}{"a": "1", "b": 2, "c": 3} rsp, err = req.Do(ctx, "POST", LOCALURL+"post", JSONParam(data)) assert.Nil(t, err) assert.Equal(t, req.Request.URL.String(), LOCALURL+"post") j, err := rsp.JSON() assert.Nil(t, err) assert.Equal(t, j.Get("url").MustString(""), LOCALURL+"post") assert.Equal(t, j.Get("json.a").MustString(""), "1") assert.Equal(t, j.Get("json.b").MustInt(0), 2) assert.Equal(t, j.Get("json.c").MustInt(0), 3) } func TestPostFile(t *testing.T) { req := New() ctx := context.Background() // Test post one file rsp, err := req.Do(ctx, "POST", LOCALURL+"post", FormFile{"file": "../go.mod"}) assert.Nil(t, err) defer rsp.Close() json, err := rsp.JSON() assert.Nil(t, err) assert.Contains(t, json.Get("headers.Content-Type.0").MustString(""), "multipart/form-data") assert.Contains(t, json.Get("file").Get("file").MustString(""), "module github.com/likexian/gokit") // Test post more files rsp, err = req.Do(ctx, "POST", LOCALURL+"post", FormFile{"file_0": "../go.mod"}, FormFile{"file_1": "../go.sum"}) assert.Nil(t, err) defer rsp.Close() json, err = rsp.JSON() assert.Nil(t, err) assert.Contains(t, json.Get("headers.Content-Type.0").MustString(""), "multipart/form-data") assert.Contains(t, json.Get("file").Get("file_0").MustString(""), "module github.com/likexian/gokit") assert.Contains(t, json.Get("file").Get("file_1").MustString(""), "") // Test post file and form rsp, err = req.Do(ctx, "POST", LOCALURL+"post", FormParam{"k": "v"}, FormFile{"file": "../go.mod", "404": "404.md"}) assert.Nil(t, err) defer rsp.Close() json, err = rsp.JSON() assert.Nil(t, err) assert.Contains(t, json.Get("headers.Content-Type.0").MustString(""), "multipart/form-data") assert.Contains(t, json.Get("file").Get("file").MustString(""), "module github.com/likexian/gokit") assert.Equal(t, json.Get("form").Get("k.0").MustString(""), "v") } func TestWithCancel(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) go func() { time.AfterFunc(100*time.Millisecond, cancel) }() req := New() _, err := req.Do(ctx, "GET", LOCALURL+"sleep") assert.NotNil(t, err) } func TestSetRetries(t *testing.T) { req := New() ctx := context.Background() assert.Panic(t, func() { req.SetRetries() }) // no retry (default) rsp, err := req.Do(ctx, "Get", "http://127.0.0.1:5555/") assert.NotNil(t, err) assert.Equal(t, rsp.Tracing.Retries, 0) // retry 3 times req.SetRetries(3) rsp, err = req.Do(ctx, "Get", "http://127.0.0.1:5555/") assert.NotNil(t, err) assert.Equal(t, rsp.Tracing.Retries, 3) // start http server after 3 second, then request shall success go func() { time.AfterFunc(100*time.Millisecond, func() { http.HandleFunc("/after3/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello!") }) _ = http.ListenAndServe("127.0.0.1:5555", nil) }) }() // retry until success, sleep 1 second per request req.SetRetries(-1, 100*time.Millisecond) rsp, err = req.Do(ctx, "Get", "http://127.0.0.1:5555/after3/") assert.Nil(t, err) defer rsp.Close() text, err := rsp.String() assert.Nil(t, err) assert.Equal(t, text, "Hello!") assert.Gt(t, rsp.Tracing.Retries, 0) } func TestDump(t *testing.T) { req := New() ctx := context.Background() req.SetDump(true, false) rsp, err := req.Do(ctx, "POST", LOCALURL+"post", "k=v") assert.Nil(t, err) defer rsp.Close() dump := rsp.Dump() assert.NotContains(t, string(dump[0]), "k=v") req.SetDump(true, true) rsp, err = req.Do(ctx, "POST", LOCALURL+"post", "k=v") assert.Nil(t, err) defer rsp.Close() dump = rsp.Dump() assert.Contains(t, string(dump[0]), "k=v") } func TestEnableCache(t *testing.T) { req := New() ctx := context.Background() rsp, err := req.Do(ctx, "GET", LOCALURL+"time") assert.Nil(t, err) defer rsp.Close() text, err := rsp.String() assert.Nil(t, err) assert.NotEqual(t, text, "") newRsp, err := req.Do(ctx, "GET", LOCALURL+"time") assert.Nil(t, err) defer newRsp.Close() newText, err := newRsp.String() assert.Nil(t, err) assert.NotEqual(t, newText, text) assert.NotEqual(t, newRsp.Tracing.RequestID, rsp.Tracing.RequestID) // enable get cache req.EnableCache("GET", 300) rsp, err = req.Do(ctx, "GET", LOCALURL+"time") assert.Nil(t, err) defer rsp.Close() text, err = rsp.String() assert.Nil(t, err) assert.NotEqual(t, text, "") newRsp, err = req.Do(ctx, "GET", LOCALURL+"time") assert.Nil(t, err) defer newRsp.Close() newText, err = newRsp.String() assert.Nil(t, err) assert.Equal(t, newText, text) assert.Equal(t, newRsp.Tracing.RequestID, rsp.Tracing.RequestID) newRsp, err = req.Do(ctx, "GET", LOCALURL+"time", QueryParam{"q": "a"}) assert.Nil(t, err) defer newRsp.Close() newText, err = newRsp.String() assert.Nil(t, err) assert.NotEqual(t, newText, text) assert.NotEqual(t, newRsp.Tracing.RequestID, rsp.Tracing.RequestID) // enable post cache req.EnableCache("post", 300) rsp, err = req.Do(ctx, "POST", LOCALURL+"time", QueryParam{"q": "a"}, FormParam{"d": "v", "x": "likexian"}) assert.Nil(t, err) defer rsp.Close() text, err = rsp.String() assert.Nil(t, err) assert.NotEqual(t, text, "") newRsp, err = req.Do(ctx, "POST", LOCALURL+"time", QueryParam{"q": "a"}, FormParam{"d": "v", "x": "likexian"}) assert.Nil(t, err) defer newRsp.Close() newText, err = newRsp.String() assert.Nil(t, err) assert.Equal(t, newText, text) assert.Equal(t, newRsp.Tracing.RequestID, rsp.Tracing.RequestID) newRsp, err = req.Do(ctx, "POST", LOCALURL+"time", QueryParam{"q": "a"}, FormParam{"d": "v", "x": "likexian", "q": "a"}) assert.Nil(t, err) defer newRsp.Close() newText, err = newRsp.String() assert.Nil(t, err) assert.NotEqual(t, newText, text) assert.NotEqual(t, newRsp.Tracing.RequestID, rsp.Tracing.RequestID) } func TestCheckClient(t *testing.T) { u, _ := url.Parse(LOCALURL) r := &http.Request{ Header: http.Header{}, Method: "POST", URL: u, } err := CheckClient(r, "") assert.NotNil(t, err) r.Header.Set("X-Http-Gokit-Requestid", "test") err = CheckClient(r, "") assert.NotNil(t, err) r.Header.Set("X-Http-Gokit-Requestid", "test-test-test") err = CheckClient(r, "") assert.NotNil(t, err) r.Header.Set("X-Http-Gokit-Requestid", "1234-test-test") err = CheckClient(r, "") assert.NotNil(t, err) r.Header.Set("X-Http-Gokit-Requestid", fmt.Sprintf("%d-test-test", xtime.S())) err = CheckClient(r, "") assert.NotNil(t, err) r.Header.Set("X-Http-Gokit-Requestid", fmt.Sprintf("%d-1234-test", xtime.S())) err = CheckClient(r, "") assert.NotNil(t, err) req := New() ctx := context.Background() rsp, err := req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) defer rsp.Close() err = CheckClient(req.Request, "") assert.Nil(t, err) } func TestConcurrent(t *testing.T) { var wg sync.WaitGroup ctx := context.Background() for i := 0; i < 100; i++ { wg.Add(1) go func() { defer wg.Done() req := New() req.SetHeader("X-Test-Value", "Test") rsp, err := req.Do(ctx, "GET", LOCALURL) assert.Nil(t, err) defer rsp.Close() assert.Equal(t, rsp.StatusCode, 200) str, err := rsp.String() assert.Nil(t, err) assert.Equal(t, len(str), 128) }() } wg.Wait() } func TestGetClientIPs(t *testing.T) { u, _ := url.Parse(LOCALURL) r := &http.Request{ RemoteAddr: "127.0.0.1:1234", Header: http.Header{}, Method: "POST", URL: u, } ips := GetClientIPs(r) assert.Equal(t, ips, []string{"127.0.0.1"}) r.Header.Set("X-Real-Ip", "1.1.1.1") ips = GetClientIPs(r) assert.Equal(t, ips, []string{"1.1.1.1", "127.0.0.1"}) r.Header.Set("X-Forwarded-For", "2.2.2.2") ips = GetClientIPs(r) assert.Equal(t, ips, []string{"1.1.1.1", "2.2.2.2", "127.0.0.1"}) r.Header.Set("X-Forwarded-For", "2.2.2.2, 3.3.3.3") ips = GetClientIPs(r) assert.Equal(t, ips, []string{"1.1.1.1", "2.2.2.2", "3.3.3.3", "127.0.0.1"}) } func ServerForTesting(listen string) string { defaultListenIP := "127.0.0.1" defaultListenPort := "8080" listen = strings.TrimSpace(strings.Replace(listen, " ", "", -1)) listen = strings.Trim(listen, ":") if !strings.Contains(listen, ":") { if len(listen) == 0 { listen = fmt.Sprintf("%s:%s", defaultListenIP, defaultListenPort) } else if len(listen) < 5 { listen = fmt.Sprintf("%s:%s", defaultListenIP, listen) } else { listen = fmt.Sprintf("%s:%s", listen, defaultListenPort) } } go func() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html") fmt.Fprintf(w, `
`+ `