├── .chglog ├── CHANGELOG.tpl.md └── config.yml ├── .env ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── app ├── app.go ├── app_test.go ├── config.go ├── context.go ├── debug_handler.go ├── doc.go ├── env.go ├── example │ └── server_example.go ├── example_app_test.go ├── handler.go ├── handler │ └── func_to_handler │ │ ├── func_to_handler.go │ │ └── func_to_handler_test.go ├── router.go ├── seo_handler.go └── stat.go ├── doc.go ├── go.mod ├── go.sum ├── logger ├── doc.go ├── example_logger_test.go └── level_logger.go ├── panicer ├── doc.go └── panic.go ├── util ├── doc.go ├── env_util.go ├── http_util.go ├── http_util_test.go ├── retry_util.go └── retry_util_test.go ├── xreflect ├── decodestruct │ ├── decodestruct.go │ └── decodestruct_test.go └── type_conversion_reflect.go └── xtest ├── racetest.go └── racetest_test.go /.chglog/CHANGELOG.tpl.md: -------------------------------------------------------------------------------- 1 | {{ if .Versions -}} 2 | 3 | ## [Unreleased] 4 | 5 | {{ if .Unreleased.CommitGroups -}} 6 | {{ range .Unreleased.CommitGroups -}} 7 | ### {{ .Title }} 8 | {{ range .Commits -}} 9 | - {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} 10 | {{ end }} 11 | {{ end -}} 12 | {{ end -}} 13 | {{ end -}} 14 | 15 | {{ range .Versions }} 16 | 17 | ## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }} 18 | {{ range .CommitGroups -}} 19 | ### {{ .Title }} 20 | {{ range .Commits -}} 21 | - {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }} 22 | {{ end }} 23 | {{ end -}} 24 | 25 | {{- if .NoteGroups -}} 26 | {{ range .NoteGroups -}} 27 | ### {{ .Title }} 28 | {{ range .Notes }} 29 | {{ .Body }} 30 | {{ end }} 31 | {{ end -}} 32 | {{ end -}} 33 | {{ end -}} 34 | 35 | {{- if .Versions }} 36 | [Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD 37 | {{ range .Versions -}} 38 | {{ if .Tag.Previous -}} 39 | [{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }} 40 | {{ end -}} 41 | {{ end -}} 42 | {{ end -}} -------------------------------------------------------------------------------- /.chglog/config.yml: -------------------------------------------------------------------------------- 1 | style: github 2 | template: CHANGELOG.tpl.md 3 | info: 4 | title: CHANGELOG 5 | repository_url: https://github.com/mnhkahn/gogogo 6 | options: 7 | commits: 8 | # filters: 9 | # Type: 10 | # - feat 11 | # - fix 12 | # - perf 13 | # - refactor 14 | commit_groups: 15 | # title_maps: 16 | # feat: Features 17 | # fix: Bug Fixes 18 | # perf: Performance Improvements 19 | # refactor: Code Refactoring 20 | header: 21 | pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$" 22 | pattern_maps: 23 | - Type 24 | - Scope 25 | - Subject 26 | notes: 27 | keywords: 28 | - BREAKING CHANGE -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | BINARY=gogogo 2 | TEST_PACKAGES=github.com/mnhkahn/gogogo/util 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | .idea/ -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## [Unreleased] 3 | 4 | 5 | 6 | ## [v1.0.9] - 2018-10-23 7 | ### Feat 8 | - **stat:** add statistics for app handler. 9 | 10 | 11 | 12 | ## [v1.0.8] - 2018-10-18 13 | ### Fix 14 | - **logger:** Fix logger.Info can't set flag bug. 15 | 16 | 17 | 18 | ## [v1.0.7] - 2018-09-29 19 | ### Feat 20 | - **decodestruct:** Parse struct field and auto call FuncMap and set value to it. 21 | 22 | 23 | 24 | ## [v1.0.6] - 2018-09-28 25 | ### Fix 26 | - **panic:** Add defer to catch func panic error. 27 | 28 | 29 | 30 | ## [v1.0.5] - 2018-09-26 31 | ### Feat 32 | - **func_to_handler:** add support for post method. 33 | 34 | 35 | 36 | ## [v1.0.4] - 2018-09-21 37 | ### Feat 38 | - **func to handler:** A handler that can auto handle request to call register func. 39 | 40 | 41 | 42 | ## [v1.0.3] - 2018-09-03 43 | ### Refactor 44 | - **gogogo:** add document and comments. 45 | 46 | 47 | 48 | ## [v1.0.2] - 2018-08-31 49 | ### Refactor 50 | - **logger:** support logger file and line info. 51 | 52 | 53 | 54 | ## [v1.0.1] - 2018-08-28 55 | ### Chore 56 | - **chglog:** add chglog config 57 | 58 | ### Refactor 59 | - **go:** support go1.11 60 | 61 | 62 | 63 | ## v1.0.0 - 2018-08-28 64 | ### Feat 65 | - **app:** add app package to support http server. 66 | - **logger:** add logger package. doc: http://blog.cyeam.com/golang/2017/07/14/go-log. 67 | - **panicer:** add panicer package to help to defer panic 68 | - **util:** add a retry and http util. 69 | 70 | 71 | [Unreleased]: https://github.com/mnhkahn/gogogo/compare/v1.0.9...HEAD 72 | [v1.0.9]: https://github.com/mnhkahn/gogogo/compare/v1.0.8...v1.0.9 73 | [v1.0.8]: https://github.com/mnhkahn/gogogo/compare/v1.0.7...v1.0.8 74 | [v1.0.7]: https://github.com/mnhkahn/gogogo/compare/v1.0.6...v1.0.7 75 | [v1.0.6]: https://github.com/mnhkahn/gogogo/compare/v1.0.5...v1.0.6 76 | [v1.0.5]: https://github.com/mnhkahn/gogogo/compare/v1.0.4...v1.0.5 77 | [v1.0.4]: https://github.com/mnhkahn/gogogo/compare/v1.0.3...v1.0.4 78 | [v1.0.3]: https://github.com/mnhkahn/gogogo/compare/v1.0.2...v1.0.3 79 | [v1.0.2]: https://github.com/mnhkahn/gogogo/compare/v1.0.1...v1.0.2 80 | [v1.0.1]: https://github.com/mnhkahn/gogogo/compare/v1.0.0...v1.0.1 81 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include .env 2 | 3 | d := $(shell date) 4 | branch := $(shell git branch | grep \* | cut -d ' ' -f2) 5 | 6 | .PHONY: help 7 | all: help 8 | help: Makefile 9 | @echo 10 | @echo " Choose a command run in ${BINARY}:" 11 | @echo 12 | @sed -n 's/^##//p' $< | column -t -s ':' | sed -e 's/^/ /' 13 | @echo 14 | 15 | ## pull: Revert & pull code from remote by current branch. 16 | pull: 17 | git reset --hard 18 | git pull origin ${branch} 19 | 20 | ## push: Auto commit & push code to remote by current branch. 21 | push: version 22 | git commit -am "chore: date is $d." 23 | git push origin ${branch} 24 | 25 | ## cover: Run all test code & print cover profile. 26 | cover: 27 | go test -coverprofile=/tmp/${BINARY}-makecover.out ${TEST_PACKAGES} && go tool cover -html=/tmp/${BINARY}-makecover.out 28 | 29 | ## version: Show current code version. 30 | version: 31 | @git remote -v 32 | @echo branch: $(branch) 33 | 34 | ## chglog: Run git-chglog & save to CHANGELOG 35 | chglog: 36 | @git-chglog -o CHANGELOG.md 37 | git commit -am "add chglog: date is $d." 38 | git push origin ${branch} 39 | 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gogogo 2 | 3 | ### app 4 | 5 | A simple web framework compatible with `net/http`. 6 | 7 | It also has a predefined 'standard' app engine server called GoEngine accessible through helper functions Handle, Server and so on. 8 | 9 | ``` 10 | func A(c *Context) error { 11 | c.JSON([]byte("a")) 12 | return nil 13 | } 14 | 15 | func main() { 16 | app.Handle("/", &app.Got{A}) 17 | 18 | l, err := net.Listen("tcp", ":"+port) 19 | if err != nil { 20 | logger.Errorf("Listen: %v", err) 21 | return 22 | } 23 | app.Serve(l) 24 | } 25 | ``` 26 | 27 | A example site: [Cyeam](http://cyeam.com). 28 | 29 | ### logger 30 | 31 | A logger package that compatible with sdk log package. It supports level logging and file auto rotating. 32 | 33 | It also has a predefined 'standard' Logger called StdLogger accessible through helper functions Debug[f], Info[f], Warn[f], Error[f], LogLevel and SetJack. 34 | 35 | It supports 4 level: 36 | 37 | ``` 38 | LevelError = iota 39 | LevelWarning 40 | LevelInformational 41 | LevelDebug 42 | ``` 43 | 44 | You can use LogLevel to handle the log level. 45 | 46 | File rotating based on package gopkg.in/natefinch/lumberjack.v2, you can control file settings by using SetJack. 47 | 48 | Quick start 49 | 50 | ``` 51 | import "github.com/mnhkahn/gogogo/logger" 52 | 53 | logger.Info("hello, world.") 54 | ``` 55 | 56 | Defined our own logger: 57 | 58 | ``` 59 | l := logger.NewWriterLogger(w, flag, 3) 60 | l.Info("hello, world") 61 | ``` 62 | 63 | For more information, goto [godoc](https://godoc.org/github.com/mnhkahn/gogogo/logger). 64 | 65 | Chinese details, goto [link](http://blog.cyeam.com//golang/2017/07/14/go-log?utm_source=github). 66 | 67 | ### panicer 68 | 69 | A help package that catch panic error and print the panic stack. 70 | 71 | Quick start 72 | 73 | import "github.com/mnhkahn/gogogo/panicer" 74 | 75 | func main() { 76 | defer Recover() 77 | } 78 | 79 | For more information, goto godoc https://godoc.org/github.com/mnhkahn/gogogo/panicer 80 | 81 | 82 | ### util 83 | 84 | A util package collection. 85 | 86 | For more information, goto godoc https://godoc.org/github.com/mnhkahn/gogogo/util 87 | 88 | Chinese details, goto http://blog.cyeam.com//golang/2018/08/27/retry?utm_source=github 89 | -------------------------------------------------------------------------------- /app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/mnhkahn/gogogo/logger" 9 | "github.com/newrelic/go-agent/v3/newrelic" 10 | "golang.org/x/net/netutil" 11 | ) 12 | 13 | // Engine ... 14 | type Engine struct { 15 | mux *http.ServeMux 16 | server *http.Server 17 | l net.Listener 18 | } 19 | 20 | // NewEngine ... 21 | func NewEngine() *Engine { 22 | e := new(Engine) 23 | e.mux = http.NewServeMux() 24 | 25 | if err := InitAppConf(); err != nil { 26 | // panic(err) 27 | logger.Warn(err) 28 | } 29 | return e 30 | } 31 | 32 | // NewEngine2 ... 33 | func NewEngine2() *Engine { 34 | e := new(Engine) 35 | e.mux = http.NewServeMux() 36 | 37 | return e 38 | } 39 | 40 | // Serve ... 41 | func (e *Engine) Serve(l net.Listener) { 42 | e.l = l 43 | e.server = &http.Server{ 44 | ReadTimeout: 10 * time.Second, 45 | WriteTimeout: 10 * time.Second, 46 | Handler: e.mux, 47 | } 48 | logger.Infof("Listening and serving HTTP on %s", e.l.Addr().String()) 49 | logger.Error(e.server.Serve(e.l)) 50 | } 51 | 52 | // ServeDefault ... 53 | func (e *Engine) ServeDefault(l net.Listener) { 54 | e.l = l 55 | e.server = &http.Server{ 56 | Handler: e.mux, 57 | } 58 | logger.Infof("Listening and serving HTTP on %s", e.l.Addr().String()) 59 | logger.Error(e.server.Serve(e.l)) 60 | } 61 | 62 | // ServeMux ... 63 | func (e *Engine) ServeMux(l net.Listener, handler http.Handler) { 64 | e.l = l 65 | e.server = &http.Server{ 66 | ReadTimeout: 1 * time.Second, 67 | WriteTimeout: 1 * time.Second, 68 | Handler: handler, 69 | } 70 | logger.Infof("Listening and serving HTTP on %s", e.l.Addr().String()) 71 | logger.Error(e.server.Serve(e.l)) 72 | } 73 | 74 | // Handle ... 75 | func (e *Engine) Handle(pattern string, h http.Handler) { 76 | if DefaultHandler.TimeOut > 0 { 77 | h = http.TimeoutHandler(h, DefaultHandler.TimeOut, "") 78 | } 79 | if DefaultHandler.Metrics != nil { 80 | p, q := newrelic.WrapHandle(DefaultHandler.Metrics, pattern, h) 81 | e.mux.Handle(p, q) 82 | } else { 83 | e.mux.Handle(pattern, h) 84 | } 85 | } 86 | 87 | // Default predefined engine 88 | var GoEngine = NewEngine() 89 | 90 | // Handle ... 91 | func Handle(pattern string, h http.Handler) { 92 | GoEngine.Handle(pattern, h) 93 | } 94 | 95 | func HandleGot(pattern string, h func(c *Context) error) { 96 | GoEngine.Handle(pattern, &Got{h}) 97 | } 98 | 99 | // Serve ... 100 | func Serve(l net.Listener) { 101 | InitRouter() 102 | GoEngine.Serve(l) 103 | } 104 | 105 | // ServeDefault ... 106 | func ServeDefault(l net.Listener) { 107 | InitRouter() 108 | GoEngine.ServeDefault(l) 109 | } 110 | 111 | // ServeMux ... 112 | func ServeMux(l net.Listener, handler http.Handler) { 113 | GoEngine.ServeMux(l, handler) 114 | } 115 | 116 | // DefaultLimit ... 117 | const DefaultLimit = 20 118 | 119 | // LimitServe ... 120 | func LimitServe(limit int) { 121 | if limit <= 0 { 122 | limit = DefaultLimit 123 | } 124 | logger.Info("start limit server with limit:", limit) 125 | 126 | l, err := net.Listen("tcp", ":"+appconfig.String("port", "1031")) 127 | if err != nil { 128 | logger.Errorf("Listen: %v", err) 129 | return 130 | } 131 | // defer l.Close() 132 | 133 | l = netutil.LimitListener(l, limit) 134 | 135 | Serve(l) 136 | } 137 | -------------------------------------------------------------------------------- /app/app_test.go: -------------------------------------------------------------------------------- 1 | // Package app 2 | package app 3 | 4 | import ( 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestStat(t *testing.T) { 12 | for i := 0; i < 10; i++ { 13 | DefaultHandler.Cost("a", time.Now().Add(-50*time.Millisecond)) 14 | } 15 | assert.EqualValues(t, 10, DefaultHandler.Stats["a"].Cnt) 16 | //assert.EqualValues(t, time.Duration(50*time.Millisecond).String(), time.Duration(DefaultHandler.Stats["a"].AvgTime).String()) 17 | } 18 | -------------------------------------------------------------------------------- /app/config.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/sasbury/mini" 7 | ) 8 | 9 | var ( 10 | appconfig = new(mini.Config) 11 | 12 | ErrConfNotInit = errors.New("appconfig is not init, forgot to call InitAppConf()? ") 13 | ) 14 | 15 | // InitAppConf ... 16 | func InitAppConf() error { 17 | err := appconfig.InitializeFromPath("./conf/app.conf") 18 | if err != nil { // Handle errors reading the config file 19 | return err 20 | } 21 | 22 | return nil 23 | } 24 | 25 | // Int ... 26 | func Int(key string) int { 27 | return int(appconfig.Integer(key, 0)) 28 | } 29 | 30 | // String ... 31 | func String(key string) string { 32 | return appconfig.String(key, "") 33 | } 34 | 35 | // DefaultString ... 36 | func DefaultString(key, def string) string { 37 | return appconfig.String(key, def) 38 | } 39 | 40 | // Strings ... 41 | func Strings(key string) []string { 42 | return appconfig.Strings(key) 43 | } 44 | 45 | // GetConfigAuth ... 46 | func GetConfigAuth() (string, string, error) { 47 | if appconfig == nil { 48 | return "", "", ErrConfNotInit 49 | } 50 | 51 | user := String("auth.user") 52 | pwd := String("auth.pwd") 53 | 54 | if user == "" { 55 | return "jia", "jia", nil 56 | } 57 | return user, pwd, nil 58 | } 59 | -------------------------------------------------------------------------------- /app/context.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "html/template" 7 | "net/http" 8 | "net/url" 9 | "strconv" 10 | "sync" 11 | 12 | "github.com/Masterminds/sprig" 13 | "github.com/mnhkahn/gogogo/logger" 14 | ) 15 | 16 | // Context ... 17 | type Context struct { 18 | Params url.Values 19 | ResponseWriter http.ResponseWriter 20 | Request *http.Request 21 | } 22 | 23 | // NewContext ... 24 | func NewContext(rw http.ResponseWriter, req *http.Request) *Context { 25 | c := new(Context) 26 | c.ResponseWriter = rw 27 | c.Request = req 28 | 29 | return c 30 | } 31 | 32 | var ctxPool = sync.Pool{ 33 | New: func() interface{} { 34 | return new(Context) 35 | }, 36 | } 37 | 38 | // AllocContext ... 39 | func AllocContext(rw http.ResponseWriter, req *http.Request) *Context { 40 | c := ctxPool.Get().(*Context) 41 | c.ResponseWriter = rw 42 | c.Request = req 43 | 44 | return c 45 | } 46 | 47 | // FreeContext ... 48 | func FreeContext(c *Context) { 49 | ctxPool.Put(c) 50 | } 51 | 52 | // URL ... 53 | func (c *Context) URL() string { 54 | return c.Request.URL.String() 55 | } 56 | 57 | // Query ... 58 | func (c *Context) Query() url.Values { 59 | return c.Params 60 | } 61 | 62 | func (c *Context) get(key string) string { 63 | return c.Query().Get(key) 64 | } 65 | 66 | func (c *Context) gets(key string) []string { 67 | return c.Query()[key] 68 | } 69 | 70 | // GetBool ... 71 | func (c *Context) GetBool(key string) (bool, error) { 72 | return strconv.ParseBool(c.get(key)) 73 | } 74 | 75 | // GetInt ... 76 | func (c *Context) GetInt(key string, def ...int) (int, error) { 77 | strv := c.get(key) 78 | if len(strv) == 0 && len(def) > 0 { 79 | return def[0], nil 80 | } 81 | return strconv.Atoi(strv) 82 | } 83 | 84 | // GetUInt8 ... 85 | func (c *Context) GetUInt8(key string, def ...uint8) (uint8, error) { 86 | strv := c.get(key) 87 | if len(strv) == 0 && len(def) > 0 { 88 | return def[0], nil 89 | } 90 | ui, err := strconv.ParseUint(strv, 10, 0) 91 | return uint8(ui), err 92 | } 93 | 94 | // GetUInt ... 95 | func (c *Context) GetUInt(key string, def ...uint) (uint, error) { 96 | strv := c.get(key) 97 | if len(strv) == 0 && len(def) > 0 { 98 | return def[0], nil 99 | } 100 | ui, err := strconv.ParseUint(strv, 10, 0) 101 | return uint(ui), err 102 | } 103 | 104 | // GetUInt32 ... 105 | func (c *Context) GetUInt32(key string, def ...uint32) (uint32, error) { 106 | strv := c.get(key) 107 | if len(strv) == 0 && len(def) > 0 { 108 | return def[0], nil 109 | } 110 | ui, err := strconv.ParseUint(strv, 10, 0) 111 | return uint32(ui), err 112 | } 113 | 114 | // GetUInt64 ... 115 | func (c *Context) GetUInt64(key string, def ...uint64) (uint64, error) { 116 | strv := c.get(key) 117 | if len(strv) == 0 && len(def) > 0 { 118 | return def[0], nil 119 | } 120 | ui, err := strconv.ParseUint(strv, 10, 0) 121 | return ui, err 122 | } 123 | 124 | // GetInt64 ... 125 | func (c *Context) GetInt64(key string, def ...int64) (int64, error) { 126 | strv := c.get(key) 127 | if len(strv) == 0 && len(def) > 0 { 128 | return def[0], nil 129 | } 130 | return strconv.ParseInt(strv, 10, 64) 131 | } 132 | 133 | // GetString ... 134 | func (c *Context) GetString(key string, def ...string) string { 135 | if v := c.get(key); v != "" { 136 | return v 137 | } 138 | if len(def) > 0 { 139 | return def[0] 140 | } 141 | return "" 142 | } 143 | 144 | // GetStrings ... 145 | func (c *Context) GetStrings(key string) []string { 146 | if v := c.gets(key); len(v) > 0 { 147 | return v 148 | } 149 | return nil 150 | } 151 | 152 | // Serve ... 153 | func (c *Context) Serve(v interface{}) { 154 | json, err := json.Marshal(v) 155 | if err != nil { 156 | c.Error(err.Error()) 157 | } 158 | c.WriteBytes(json) 159 | } 160 | 161 | // WriteBytes ... 162 | func (c *Context) WriteBytes(raw []byte) { 163 | c.ResponseWriter.Write(raw) 164 | } 165 | 166 | // WriteString ... 167 | func (c *Context) WriteString(str string) { 168 | c.ResponseWriter.Write([]byte(str)) 169 | } 170 | 171 | // JSON ... 172 | func (c *Context) JSON(v interface{}) { 173 | c.ResponseWriter.Header().Set("Content-Type", "application/json; charset=utf-8") 174 | 175 | content, err := json.Marshal(v) 176 | if err != nil { 177 | content = []byte(err.Error()) 178 | } 179 | 180 | callback := c.GetString("callback") 181 | if callback == "" { 182 | c.ResponseWriter.Write(content) 183 | } else { 184 | callback_content := bytes.NewBufferString(" " + template.JSEscapeString(callback)) 185 | callback_content.WriteString("(") 186 | callback_content.Write(content) 187 | callback_content.WriteString(");\r\n") 188 | c.ResponseWriter.Write(callback_content.Bytes()) 189 | } 190 | } 191 | 192 | // HTML ... 193 | func (c *Context) HTML(filenames []string, data interface{}) { 194 | tmpl := template.Must(template.ParseFiles(filenames...)) 195 | 196 | err := tmpl.Execute(c.ResponseWriter, data) 197 | if err != nil { 198 | DefaultHandler.ErrorMsgFunc(c, err.Error()) 199 | c.Error(err.Error()) 200 | } 201 | } 202 | 203 | // HTMLFunc ... 204 | func (c *Context) HTMLFunc(filenames []string, data interface{}, funcs template.FuncMap) { 205 | tmpl := template.Must(template.New("base").Funcs(sprig.FuncMap()).ParseFiles(filenames...)).Funcs(funcs) 206 | 207 | err := tmpl.Execute(c.ResponseWriter, data) 208 | if err != nil { 209 | DefaultHandler.ErrorMsgFunc(c, err.Error()) 210 | } 211 | } 212 | 213 | // Error ... 214 | func (c *Context) Error(msg string) { 215 | logger.Warn("got error", msg) 216 | 217 | c.ResponseWriter.Header().Set("Content-Type", "text/plain; charset=utf-8") 218 | c.ResponseWriter.Header().Set("X-Content-Type-Options", "nosniff") 219 | if c.Debug() { 220 | c.WriteString(msg) 221 | } 222 | } 223 | 224 | // Debug ... 225 | func (c *Context) Debug() bool { 226 | if c.get("debug") == "yes" { 227 | return true 228 | } 229 | return false 230 | } 231 | -------------------------------------------------------------------------------- /app/debug_handler.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "bytes" 5 | "html/template" 6 | "os" 7 | "runtime" 8 | "sort" 9 | 10 | "github.com/mnhkahn/gogogo/logger" 11 | "github.com/mnhkahn/gogogo/util" 12 | ) 13 | 14 | // GoAppHandler is a handler to show debug information. 15 | func GoAppHandler(c *Context) error { 16 | res := make(map[string]interface{}, 4) 17 | res["app.VERSION"] = VERSION 18 | res["app.BUILD"] = BUILD 19 | res["app.BRANCH"] = BRANCH 20 | res["go.version"] = runtime.Version() 21 | res["os.args"] = os.Args 22 | res["os.env"] = os.Environ() 23 | res["os.goroutine"] = util.Goroutine() 24 | res["os.pwd"], _ = os.Getwd() 25 | res["log.level"] = logger.StdLogger.GetLevel() 26 | c.JSON(res) 27 | 28 | return nil 29 | } 30 | 31 | var debugRouterTpl = ` 32 | 33 | 34 | Router 35 | 36 | 37 |

Router

38 | 43 | 44 | ` 45 | 46 | // LogLevelHandler is a handler to set log level for StdLogger. 47 | func LogLevelHandler(c *Context) error { 48 | l, err := c.GetInt("level") 49 | if err != nil { 50 | return err 51 | } 52 | res := logger.StdLogger.SetLevel(l) 53 | c.JSON(res) 54 | 55 | return nil 56 | } 57 | 58 | func StatHandler(c *Context) error { 59 | var buf bytes.Buffer 60 | tpl := template.New("statTpl") 61 | tpl = template.Must(tpl.Parse(statTpl)) 62 | stats := make([]*Stat, 0, len(DefaultHandler.Stats)) 63 | for _, v := range DefaultHandler.Stats { 64 | stats = append(stats, v) 65 | } 66 | sort.Slice(stats, func(i, j int) bool { 67 | if stats[i].Cnt == stats[j].Cnt { 68 | return stats[i].Url > stats[j].Url 69 | } 70 | return stats[i].Cnt > stats[j].Cnt 71 | }) 72 | err := tpl.ExecuteTemplate(&buf, "statTpl", struct { 73 | Stats []*Stat 74 | }{ 75 | stats, 76 | }) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | c.WriteBytes(buf.Bytes()) 82 | 83 | return nil 84 | } 85 | 86 | var statTpl = ` 87 | 88 | 89 | Statistics 90 | 91 | 92 |

Statistics

93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | {{range $stat := .Stats}} 101 | 102 | 103 | 104 | 105 | 106 | 107 | {{end}} 108 |
Url PathCountSum ElapseAvgTime Elapse
{{$stat.Url}}{{$stat.Cnt}}{{$stat.SumTime}}{{$stat.AvgTime}}
109 | 110 | 111 | ` 112 | -------------------------------------------------------------------------------- /app/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | This package is a web framework that compatible with sdk http package. 3 | It also has a predefined 'standard' app engine server called GoEngine accessible through helper functions Handle, Server and so on. 4 | 5 | Quick start: 6 | 7 | import "github.com/mnhkahn/gogogo/app" 8 | import "github.com/mnhkahn/gogogo/logger" 9 | 10 | func Index(c *app.Context) error { 11 | c.WriteString("hello, world") 12 | return nil 13 | } 14 | 15 | app.Handle("/", &app.Got{Index}) 16 | 17 | l, err := net.Listen("tcp", ":"+port) 18 | if err != nil { 19 | logger.Errorf("Listen: %v", err) 20 | return 21 | } 22 | app.Serve(l) 23 | 24 | 25 | For more information, goto godoc https://godoc.org/github.com/mnhkahn/gogogo/app. 26 | 27 | A example site: http://cyeam.com. 28 | 29 | */ 30 | package app 31 | -------------------------------------------------------------------------------- /app/env.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "runtime" 5 | 6 | "github.com/mnhkahn/gogogo/logger" 7 | ) 8 | 9 | // go build version, use build flags to assign value. -ldflags='-X "github.com/mnhkahn/gogogo/app.VERSION=$(VERSION)" -X "github.com/mnhkahn/gogogo/app.BRANCH=$(branch)" -X "github.com/mnhkahn/gogogo/app.BUILD=$(DATE)"' 10 | var ( 11 | VERSION = "N/A" 12 | BRANCH = "N/A" 13 | BUILD = "N/A" 14 | ) 15 | 16 | // AppInfo prints app info with git version, git branch, build date and go version. 17 | func AppInfo() { 18 | logger.Info("Git Version:", VERSION) 19 | logger.Info("Build Branch:", BRANCH) 20 | logger.Info("Build Date:", BUILD) 21 | logger.Info("Go Version:", runtime.Version(), runtime.GOOS, runtime.GOARCH) 22 | } 23 | -------------------------------------------------------------------------------- /app/example/server_example.go: -------------------------------------------------------------------------------- 1 | // Package example 2 | package main 3 | 4 | import ( 5 | "net" 6 | 7 | "github.com/mnhkahn/gogogo/app" 8 | "github.com/mnhkahn/gogogo/logger" 9 | ) 10 | 11 | func main() { 12 | l, err := net.Listen("tcp", ":1031") 13 | if err != nil { 14 | logger.Errorf("Listen: %v", err) 15 | return 16 | } 17 | app.Serve(l) 18 | } 19 | -------------------------------------------------------------------------------- /app/example_app_test.go: -------------------------------------------------------------------------------- 1 | // Package app_test 2 | 3 | package app_test 4 | 5 | import ( 6 | "github.com/mnhkahn/gogogo/app" 7 | "github.com/mnhkahn/gogogo/logger" 8 | "net" 9 | ) 10 | 11 | func Index(c *app.Context) error { 12 | c.WriteString("hello, world") 13 | return nil 14 | } 15 | 16 | func Example() { 17 | app.Handle("/", &app.Got{Index}) 18 | 19 | l, err := net.Listen("tcp", ":1031") 20 | if err != nil { 21 | logger.Errorf("Listen: %v", err) 22 | return 23 | } 24 | app.Serve(l) 25 | } 26 | -------------------------------------------------------------------------------- /app/handler.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | "sync" 7 | "time" 8 | 9 | "github.com/mnhkahn/gogogo/logger" 10 | "github.com/mnhkahn/gogogo/panicer" 11 | "github.com/newrelic/go-agent/v3/newrelic" 12 | ) 13 | 14 | // Handler ... 15 | type Handler struct { 16 | TimeOut time.Duration 17 | Metrics *newrelic.Application 18 | RecoverFunc func(c *Context) `json:"-"` 19 | ErrorMsgFunc func(c *Context, msg string) `json:"-"` 20 | Stats map[string]*Stat 21 | statLock sync.RWMutex 22 | } 23 | 24 | // NewHandler ... 25 | func NewHandler() *Handler { 26 | return &Handler{ 27 | RecoverFunc: func(c *Context) { 28 | panicer.RecoverHandler(c.ResponseWriter, c.Request) 29 | }, 30 | ErrorMsgFunc: func(c *Context, msg string) { 31 | logger.Warn("ErrorMsgFunc", msg) 32 | }, 33 | Stats: make(map[string]*Stat), 34 | } 35 | } 36 | 37 | func (h *Handler) Cost(u string, st time.Time) { 38 | h.statLock.Lock() 39 | defer h.statLock.Unlock() 40 | 41 | d := time.Now().Sub(st).Nanoseconds() 42 | if stat, e := h.Stats[u]; e { 43 | stat.Cnt++ 44 | stat.SumTime += time.Duration(d) 45 | stat.AvgTime = stat.SumTime / time.Duration(stat.Cnt) 46 | } else { 47 | h.Stats[u] = &Stat{u, 1, time.Duration(d), time.Duration(d)} 48 | } 49 | } 50 | 51 | // DefaultHandler ... 52 | var DefaultHandler = NewHandler() 53 | 54 | // SetTimeout ... 55 | func SetTimeout(d time.Duration) { 56 | DefaultHandler.TimeOut = d 57 | } 58 | 59 | // SetRecoverFunc ... 60 | func SetRecoverFunc(recoverFunc func(c *Context)) { 61 | DefaultHandler.RecoverFunc = recoverFunc 62 | } 63 | 64 | func SetErrorMsgFunc(fn func(c *Context, msg string)) { 65 | DefaultHandler.ErrorMsgFunc = fn 66 | } 67 | 68 | // Got ... 69 | type Got struct { 70 | H func(c *Context) error 71 | } 72 | 73 | // ServeHTTP ... 74 | func (h Got) ServeHTTP(w http.ResponseWriter, r *http.Request) { 75 | st := time.Now() 76 | 77 | c := AllocContext(w, r) 78 | defer FreeContext(c) 79 | 80 | // https://golang.dbwu.tech/traps/defer_with_recover/#:~:text=错误的原因在于%3A%20defer%20以匿名函数的方式运行,本身就等于包装了一层函数,%20内部的%20myRecover%20函数包装了%20recover%20函数,等于又加了一层包装,变成了两,panic%20就无法被捕获了%E3%80%82%20defer%20直接调用%20myRecover%20函数,这样减去了一层包装,%20panic%20就可以被捕获了%E3%80%82 81 | if DefaultHandler.RecoverFunc != nil { 82 | defer DefaultHandler.RecoverFunc(c) 83 | } 84 | 85 | if String("seo") == "true" { 86 | if seo(c) { 87 | return 88 | } 89 | } 90 | 91 | var err error 92 | c.Params, err = url.ParseQuery(c.Request.URL.RawQuery) 93 | if err != nil { 94 | c.Error(err.Error()) 95 | go DefaultHandler.Cost(c.Request.URL.Path, st) 96 | return 97 | } 98 | 99 | err = h.H(c) 100 | logger.Infof("%s %s", c.Request.Method, c.Request.URL.String()) 101 | if err != nil { 102 | c.Error(err.Error()) 103 | go DefaultHandler.Cost(c.Request.URL.Path, st) 104 | return 105 | } 106 | 107 | go DefaultHandler.Cost(c.Request.URL.Path, st) 108 | } 109 | 110 | type basicAuthHandler struct { 111 | handler http.Handler 112 | user string 113 | pwd string 114 | } 115 | 116 | // ServeHTTP ... 117 | func (h basicAuthHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 118 | u, p, ok := r.BasicAuth() 119 | if !ok || !(u == h.user && p == h.pwd) { 120 | w.Header().Set("WWW-Authenticate", "Basic realm=Restricted") 121 | http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) 122 | return 123 | } 124 | h.handler.ServeHTTP(w, r) 125 | } 126 | 127 | // BasicAuthHandler ... 128 | func BasicAuthHandler(handler http.Handler, user, pwd string) http.Handler { 129 | return basicAuthHandler{handler: handler, user: user, pwd: pwd} 130 | } 131 | 132 | var AuthHandler = func(handler http.Handler) http.Handler { 133 | usr, pwd, _ := GetConfigAuth() 134 | return BasicAuthHandler(handler, usr, pwd) 135 | } 136 | -------------------------------------------------------------------------------- /app/handler/func_to_handler/func_to_handler.go: -------------------------------------------------------------------------------- 1 | // Package func_to_handler ... 2 | package func_to_handler 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "reflect" 9 | 10 | "github.com/mnhkahn/gogogo/app" 11 | "github.com/mnhkahn/gogogo/xreflect" 12 | ) 13 | 14 | type FuncToHandler struct { 15 | paramIns []reflect.Type 16 | paramOuts []reflect.Type 17 | method reflect.Value 18 | } 19 | 20 | func NewFuncToHandler(fn interface{}) *FuncToHandler { 21 | f := new(FuncToHandler) 22 | //rt := reflect.TypeOf(fn) 23 | rv := reflect.ValueOf(fn) 24 | 25 | for i := 0; i < rv.Type().NumIn(); i++ { 26 | t := rv.Type().In(i) 27 | f.paramIns = append(f.paramIns, t) 28 | } 29 | for i := 0; i < rv.Type().NumOut(); i++ { 30 | t := rv.Type().Out(i) 31 | f.paramOuts = append(f.paramOuts, t) 32 | } 33 | f.method = rv 34 | return f 35 | } 36 | 37 | func (f *FuncToHandler) ServeFunc(c *app.Context) error { 38 | if app.DefaultHandler.RecoverFunc != nil { 39 | defer app.DefaultHandler.RecoverFunc(c) 40 | } 41 | f.ServeHTTP(c.ResponseWriter, c.Request) 42 | return nil 43 | } 44 | 45 | func (f *FuncToHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 46 | // get parameter in 47 | query := r.URL.Query() 48 | var inValues []reflect.Value 49 | for i, in := range f.paramIns { 50 | key := fmt.Sprintf("in%d", i) 51 | value := "" 52 | 53 | // if request is post, get value from body 54 | if r.Method == http.MethodPost { 55 | body := make(map[string]string, len(f.paramIns)) 56 | decoder := json.NewDecoder(r.Body) 57 | err := decoder.Decode(&body) 58 | if err != nil { 59 | http.Error(w, err.Error(), http.StatusBadRequest) 60 | return 61 | } 62 | value = body[key] 63 | } else { 64 | // if request is get, get value from query 65 | value = query.Get(key) 66 | } 67 | 68 | v, err := xreflect.StringToReflectValue(value, in.Kind().String()) 69 | if err != nil { 70 | http.Error(w, err.Error(), http.StatusBadRequest) 71 | return 72 | } 73 | inValues = append(inValues, v) 74 | } 75 | 76 | // call function 77 | outValues := f.method.Call(inValues) 78 | 79 | res := make(map[string]interface{}, len(outValues)) 80 | for i, o := range outValues { 81 | inter := o.Interface() 82 | if o.Kind() == reflect.Slice { 83 | if o.Type().Elem().Kind() == reflect.Uint8 { 84 | inter = string(o.Interface().([]byte)) 85 | } 86 | } 87 | res[fmt.Sprintf("out%d", i)] = inter 88 | } 89 | 90 | buf, err := json.Marshal(res) 91 | if err != nil { 92 | http.Error(w, err.Error(), http.StatusBadRequest) 93 | return 94 | } 95 | w.Write(buf) 96 | } 97 | -------------------------------------------------------------------------------- /app/handler/func_to_handler/func_to_handler_test.go: -------------------------------------------------------------------------------- 1 | // Package func_to_handler 2 | package func_to_handler 3 | 4 | import ( 5 | "bytes" 6 | "encoding/json" 7 | "github.com/ChimeraCoder/gojson" 8 | "github.com/stretchr/testify/assert" 9 | "net/http" 10 | "net/http/httptest" 11 | "net/url" 12 | "strconv" 13 | "testing" 14 | ) 15 | 16 | func TestFuncToHandlerAtoi(t *testing.T) { 17 | fn := NewFuncToHandler(strconv.Atoi) 18 | 19 | req, err := http.NewRequest("GET", "/health-check?in0=1", nil) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | 24 | rr := httptest.NewRecorder() 25 | 26 | fn.ServeHTTP(rr, req) 27 | 28 | assert.Equal(t, http.StatusOK, rr.Code) 29 | 30 | expected := `{"out0":1,"out1":null}` 31 | assert.Equal(t, expected, rr.Body.String()) 32 | } 33 | 34 | func TestFuncToHandlerJsonFormat(t *testing.T) { 35 | fn := NewFuncToHandler(func(data string) ([]byte, error) { 36 | var out bytes.Buffer 37 | err := json.Indent(&out, []byte(data), "", " ") 38 | if err != nil { 39 | return nil, err 40 | } 41 | return out.Bytes(), nil 42 | }) 43 | 44 | req, err := http.NewRequest("GET", `/health-check?in0={"a":1}`, nil) 45 | if err != nil { 46 | t.Fatal(err) 47 | } 48 | 49 | rr := httptest.NewRecorder() 50 | 51 | fn.ServeHTTP(rr, req) 52 | 53 | assert.Equal(t, http.StatusOK, rr.Code) 54 | 55 | expected := `{"out0":"{\n \"a\": 1\n}","out1":null}` 56 | assert.Equal(t, expected, rr.Body.String()) 57 | } 58 | 59 | func TestFuncToHandlerEscape(t *testing.T) { 60 | fn := NewFuncToHandler(url.QueryEscape) 61 | 62 | req, err := http.NewRequest("GET", "/health-check?in0=a b", nil) 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | 67 | rr := httptest.NewRecorder() 68 | 69 | fn.ServeHTTP(rr, req) 70 | 71 | assert.Equal(t, http.StatusOK, rr.Code) 72 | 73 | expected := `{"out0":"a+b"}` 74 | assert.Equal(t, expected, rr.Body.String()) 75 | } 76 | 77 | func TestFuncToHandlerJsonToStruct(t *testing.T) { 78 | fn := NewFuncToHandler(func(data string) (string, error) { 79 | var parser gojson.Parser = gojson.ParseJson 80 | if output, err := gojson.Generate(bytes.NewBufferString(data), parser, "Foo", "main", []string{"json"}, false); err != nil { 81 | return "", err 82 | } else { 83 | return string(output), nil 84 | } 85 | }) 86 | 87 | req, err := http.NewRequest("GET", `/health-check?in0={"a":1}`, nil) 88 | if err != nil { 89 | t.Fatal(err) 90 | } 91 | 92 | rr := httptest.NewRecorder() 93 | 94 | fn.ServeHTTP(rr, req) 95 | 96 | assert.Equal(t, http.StatusOK, rr.Code) 97 | 98 | expected := "{\"out0\":\"package main\\n\\ntype Foo struct {\\n\\tA int64 `json:\\\"a\\\"`\\n}\\n\",\"out1\":null}" 99 | assert.Equal(t, expected, rr.Body.String()) 100 | } 101 | 102 | func TestNewFuncToHandlerPanic(t *testing.T) { 103 | fn := NewFuncToHandler(func(data string) (string, error) { 104 | panic(1) 105 | return "", nil 106 | }) 107 | 108 | req, err := http.NewRequest("GET", `/health-check?in0={"a":1}`, nil) 109 | if err != nil { 110 | t.Fatal(err) 111 | } 112 | 113 | rr := httptest.NewRecorder() 114 | 115 | fn.ServeHTTP(rr, req) 116 | 117 | assert.Equal(t, http.StatusServiceUnavailable, rr.Code) 118 | assert.Equal(t, "", rr.Body.String()) 119 | } 120 | -------------------------------------------------------------------------------- /app/router.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | // InitRouter ... 4 | func InitRouter() { 5 | Handle("/debug/goapp", AuthHandler(&Got{H: GoAppHandler})) 6 | Handle("/debug/log/level", AuthHandler(Got{LogLevelHandler})) 7 | Handle("/debug/stat", AuthHandler(Got{StatHandler})) 8 | } 9 | -------------------------------------------------------------------------------- /app/seo_handler.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "net/http" 5 | ) 6 | 7 | func seo(c *Context) bool { 8 | if c.Request.Host == "localhost" { 9 | return false 10 | } 11 | if c.Request.Header.Get("X-Forwarded-Proto") == "http" { 12 | http.Redirect(c.ResponseWriter, c.Request, "https://"+c.Request.Host+c.Request.RequestURI, http.StatusMovedPermanently) 13 | return true 14 | } 15 | host := String("host") 16 | if c.Request.Host == String("redirect_host") && host != "" { 17 | http.Redirect(c.ResponseWriter, c.Request, "https://"+host+c.Request.RequestURI, http.StatusMovedPermanently) 18 | return true 19 | } 20 | return false 21 | } 22 | -------------------------------------------------------------------------------- /app/stat.go: -------------------------------------------------------------------------------- 1 | // Package app 2 | package app 3 | 4 | import "time" 5 | 6 | type Stat struct { 7 | Url string 8 | Cnt int64 9 | SumTime time.Duration 10 | AvgTime time.Duration 11 | } 12 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // package gogogo 2 | 3 | /* 4 | A web framework includes app server, logger, panicer, util and so on. 5 | */ 6 | package gogogo 7 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mnhkahn/gogogo 2 | 3 | go 1.23.1 4 | 5 | require ( 6 | github.com/ChimeraCoder/gojson v1.0.0 7 | github.com/Masterminds/sprig v2.22.0+incompatible 8 | github.com/newrelic/go-agent/v3 v3.38.0 9 | github.com/sasbury/mini v0.0.0-20161224193750-64bd399395db 10 | github.com/sony/sonyflake v0.0.0-20181109022403-6d5bd6181009 11 | github.com/stretchr/testify v1.2.2 12 | golang.org/x/net v0.25.0 13 | gopkg.in/natefinch/lumberjack.v2 v2.0.0-20170531160350-a96e63847dc3 14 | ) 15 | 16 | require ( 17 | github.com/BurntSushi/toml v0.3.0 // indirect 18 | github.com/Masterminds/goutils v1.1.1 // indirect 19 | github.com/Masterminds/semver v1.5.0 // indirect 20 | github.com/davecgh/go-spew v1.1.1 // indirect 21 | github.com/deckarep/golang-set v1.7.1 // indirect 22 | github.com/google/uuid v1.6.0 // indirect 23 | github.com/huandu/xstrings v1.5.0 // indirect 24 | github.com/imdario/mergo v0.3.16 // indirect 25 | github.com/mitchellh/copystructure v1.2.0 // indirect 26 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 27 | github.com/pmezard/go-difflib v1.0.0 // indirect 28 | golang.org/x/crypto v0.37.0 // indirect 29 | golang.org/x/sys v0.32.0 // indirect 30 | golang.org/x/text v0.24.0 // indirect 31 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect 32 | google.golang.org/grpc v1.65.0 // indirect 33 | google.golang.org/protobuf v1.34.2 // indirect 34 | gopkg.in/yaml.v2 v2.2.1 // indirect 35 | ) 36 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY= 2 | github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 3 | github.com/ChimeraCoder/gojson v1.0.0 h1:zzCVZvLjz60sRf7LUIPt4VRz1WnYqnyA9pXfyrAyFCw= 4 | github.com/ChimeraCoder/gojson v1.0.0/go.mod h1:nYbTQlu6hv8PETM15J927yM0zGj3njIldp72UT1MqSw= 5 | github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= 6 | github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= 7 | github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= 8 | github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= 9 | github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= 10 | github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= 11 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 12 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ= 14 | github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= 15 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 16 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 17 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 18 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 19 | github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= 20 | github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 21 | github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= 22 | github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= 23 | github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= 24 | github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= 25 | github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= 26 | github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= 27 | github.com/newrelic/go-agent/v3 v3.38.0 h1:Oms49R8NpCQ007UMm26dZq6qpHXGq/uDeyxlHEZFsnE= 28 | github.com/newrelic/go-agent/v3 v3.38.0/go.mod h1:4QXvru0vVy/iu7mfkNHT7T2+9TC9zPGO8aUEdKqY138= 29 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 30 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 31 | github.com/sasbury/mini v0.0.0-20161224193750-64bd399395db h1:ErtDAPpJQs1emBGgDGjZp74+ZYeDgdP8cPzyt2uDWTM= 32 | github.com/sasbury/mini v0.0.0-20161224193750-64bd399395db/go.mod h1:yOzd9gzygiw8QvoQeezToFEojl93FdFfDD2d64ZV6u8= 33 | github.com/sony/sonyflake v0.0.0-20181109022403-6d5bd6181009 h1:3wBL/e/qjpSYaXacpbIV+Bsj/nwQ4UO1llG/av54zzw= 34 | github.com/sony/sonyflake v0.0.0-20181109022403-6d5bd6181009/go.mod h1:dVvZuWJd174umvm5g8CmZD6S2GWwHKtpK/0ZPHswuNo= 35 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 36 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 37 | golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= 38 | golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= 39 | golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= 40 | golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= 41 | golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= 42 | golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 43 | golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= 44 | golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= 45 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8= 46 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= 47 | google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= 48 | google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= 49 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 50 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 51 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 52 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 53 | gopkg.in/natefinch/lumberjack.v2 v2.0.0-20170531160350-a96e63847dc3 h1:AFxeG48hTWHhDTQDk/m2gorfVHUEa9vo3tp3D7TzwjI= 54 | gopkg.in/natefinch/lumberjack.v2 v2.0.0-20170531160350-a96e63847dc3/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= 55 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 56 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 57 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 58 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 59 | -------------------------------------------------------------------------------- /logger/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | A logger package that compatible with sdk log package. It supports level logging and file auto rotating. 3 | 4 | It also has a predefined 'standard' Logger called StdLogger accessible through helper functions Debug[f], Info[f], Warn[f], Error[f], LogLevel and SetJack. 5 | 6 | It supports 4 level: 7 | 8 | LevelError = iota 9 | LevelWarning 10 | LevelInformational 11 | LevelDebug 12 | 13 | You can use LogLevel to handle the log level. 14 | 15 | File rotating based on package gopkg.in/natefinch/lumberjack.v2, you can control file settings by using SetJack. 16 | 17 | Quick start 18 | 19 | Use StdLogger: 20 | 21 | import "github.com/mnhkahn/gogogo/logger" 22 | 23 | logger.Info("hello, world.") 24 | 25 | Defined our own logger: 26 | 27 | l := logger.NewWriterLogger(w, flag, 3) 28 | l.Info("hello, world") 29 | 30 | Log flags compatible with sdk log package, https://golang.org/pkg/log/#pkg-constants. 31 | 32 | See also 33 | 34 | For more information, goto godoc https://godoc.org/github.com/mnhkahn/gogogo/logger 35 | 36 | Chinese details, goto http://blog.cyeam.com//golang/2017/07/14/go-log?utm_source=github 37 | 38 | */ 39 | package logger -------------------------------------------------------------------------------- /logger/example_logger_test.go: -------------------------------------------------------------------------------- 1 | // Package logger_test 2 | 3 | package logger_test 4 | 5 | import ( 6 | "github.com/mnhkahn/gogogo/logger" 7 | "log" 8 | "os" 9 | ) 10 | 11 | func Example() { 12 | logger.Info("hello, world.") 13 | } 14 | 15 | func ExampleNewLogger() { 16 | w := os.Stdout 17 | flag := log.Llongfile 18 | l := logger.NewWriterLogger(w, flag, 3) 19 | l.Info("hello, world") 20 | } 21 | -------------------------------------------------------------------------------- /logger/level_logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "os" 8 | "strings" 9 | 10 | "gopkg.in/natefinch/lumberjack.v2" 11 | ) 12 | 13 | // logger supports 4 levels. Default level is LevelInformational. 14 | const ( 15 | LevelError = iota 16 | LevelWarning 17 | LevelInformational 18 | LevelDebug 19 | ) 20 | 21 | // Logger ... 22 | type Logger struct { 23 | // level stores log level. 24 | level int 25 | 26 | // 4 levels of logger. 27 | err *log.Logger 28 | warn *log.Logger 29 | info *log.Logger 30 | debug *log.Logger 31 | 32 | // depth is the count of the number of frames to skip when computing the file name and line number if Llongfile or Lshortfile is set; a value of 1 will print the details for the caller of Output. 33 | depth int 34 | } 35 | 36 | // NewLogger makes a new logger prints to stdout. 37 | func NewLogger(flag int, depth int) *Logger { 38 | Logger := NewWriterLogger(os.Stdout, flag, depth) 39 | return Logger 40 | } 41 | 42 | // NewFileLogger makes a new file logger, it prints to file lfn. File will auto rotate by size maxsize. 43 | // maxsize is the maximum size in megabytes of the log file 44 | func NewFileLogger(lfn string, maxsize int, flag int, depth int) *Logger { 45 | jack := &lumberjack.Logger{ 46 | Filename: lfn, 47 | MaxSize: maxsize, // megabytes 48 | } 49 | 50 | logger := NewWriterLogger(jack, flag, depth) 51 | 52 | return logger 53 | } 54 | 55 | // NewWriterLogger makes a new writer file, it prints to writer. 56 | func NewWriterLogger(w io.Writer, flag int, depth int) *Logger { 57 | logger := new(Logger) 58 | logger.depth = depth 59 | if logger.depth <= 0 { 60 | logger.depth = 2 61 | } 62 | 63 | logger.err = log.New(w, "[E] ", flag) 64 | logger.warn = log.New(w, "[W] ", flag) 65 | logger.info = log.New(w, "[I] ", flag) 66 | logger.debug = log.New(w, "[D] ", flag) 67 | 68 | logger.SetLevel(LevelInformational) 69 | 70 | return logger 71 | } 72 | 73 | // Deprecated 74 | // NewLogger2 use NewFileLogger instead. 75 | func NewLogger2(lfn string, maxsize int, flag int, numWorkers int, jobQueueLen int, depth int) *Logger { 76 | jack := &lumberjack.Logger{ 77 | Filename: lfn, 78 | MaxSize: maxsize, // megabytes 79 | } 80 | 81 | logger := NewLogger3(jack, flag, numWorkers, jobQueueLen, depth) 82 | 83 | return logger 84 | } 85 | 86 | // Deprecated 87 | // NewLogger3 use NewWriterLogger instead. 88 | func NewLogger3(w io.Writer, flag int, numWorkers int, jobQueueLen int, depth int) *Logger { 89 | logger := new(Logger) 90 | logger.depth = depth 91 | if logger.depth <= 0 { 92 | logger.depth = 2 93 | } 94 | 95 | logger.err = log.New(w, "[E] ", flag) 96 | logger.warn = log.New(w, "[W] ", flag) 97 | logger.info = log.New(w, "[I] ", flag) 98 | logger.debug = log.New(w, "[D] ", flag) 99 | 100 | logger.SetLevel(LevelInformational) 101 | 102 | return logger 103 | } 104 | 105 | // SetLevel sets the log level. 106 | func (ll *Logger) SetLevel(l int) int { 107 | ll.level = l 108 | return ll.level 109 | } 110 | 111 | // GetLevel gets the current log level name. 112 | func (ll *Logger) GetLevel() string { 113 | switch ll.level { 114 | case LevelDebug: 115 | return "Debug" 116 | case LevelError: 117 | return "Error" 118 | case LevelInformational: 119 | return "Info" 120 | case LevelWarning: 121 | return "Warn" 122 | } 123 | return "" 124 | } 125 | 126 | // SetPrefix set the logger prefix. Default prefix is [D] for Debug, [I] for Info, [W] for Warn and [E] for Error. 127 | func (ll *Logger) SetPrefix(prefix string) { 128 | ll.err.SetPrefix(prefix) 129 | ll.warn.SetPrefix(prefix) 130 | ll.info.SetPrefix(prefix) 131 | ll.debug.SetPrefix(prefix) 132 | } 133 | 134 | // Error print log with level Error. 135 | func (ll *Logger) Error(format string, v ...interface{}) { 136 | if LevelError > ll.level { 137 | return 138 | } 139 | ll.err.Output(ll.depth, fmt.Sprintf(format, v...)) 140 | } 141 | 142 | // Warn print log with level Warn. 143 | func (ll *Logger) Warn(format string, v ...interface{}) { 144 | if LevelWarning > ll.level { 145 | return 146 | } 147 | ll.warn.Output(ll.depth, fmt.Sprintf(format, v...)) 148 | } 149 | 150 | // Info print log with level Info. 151 | func (ll *Logger) Info(format string, v ...interface{}) { 152 | if LevelInformational > ll.level { 153 | return 154 | } 155 | ll.info.Output(ll.depth, fmt.Sprintf(format, v...)) 156 | } 157 | 158 | // Debug print log with level Debug. 159 | func (ll *Logger) Debug(format string, v ...interface{}) { 160 | if LevelDebug > ll.level { 161 | return 162 | } 163 | ll.debug.Output(ll.depth, fmt.Sprintf(format, v...)) 164 | } 165 | 166 | // SetJack makes logger writes to new file lfn. 167 | func (ll *Logger) SetJack(lfn string, maxsize int) { 168 | jack := &lumberjack.Logger{ 169 | Filename: lfn, 170 | MaxSize: maxsize, // megabytes 171 | } 172 | 173 | ll.err.SetOutput(jack) 174 | ll.warn.SetOutput(jack) 175 | ll.info.SetOutput(jack) 176 | ll.debug.SetOutput(jack) 177 | } 178 | 179 | // SetFlag sets log flags. For more information, see the sdk https://golang.org/pkg/log/#pkg-constants. 180 | func (ll *Logger) SetFlag(flag int) { 181 | ll.err.SetFlags(flag) 182 | ll.warn.SetFlags(flag) 183 | ll.info.SetFlags(flag) 184 | ll.debug.SetFlags(flag) 185 | } 186 | 187 | // StdLogger is a predefined logger prints to stdout. 188 | var ( 189 | StdLogger = NewLogger(log.LstdFlags|log.Lshortfile, 3) 190 | ) 191 | 192 | // SetJack sets the StdLogger's writer to file lfn. 193 | func SetJack(lfn string, maxsize int) { 194 | StdLogger.SetJack(lfn, maxsize) 195 | } 196 | 197 | // Errorf print log with level Error. 198 | func Errorf(format string, v ...interface{}) { 199 | StdLogger.Error(format, v...) 200 | } 201 | 202 | // Warnf print log with level Warn. 203 | func Warnf(format string, v ...interface{}) { 204 | StdLogger.Warn(format, v...) 205 | } 206 | 207 | // Infof print log with level Info. 208 | func Infof(format string, v ...interface{}) { 209 | StdLogger.Info(format, v...) 210 | } 211 | 212 | // Debugf print log with level Debug. 213 | func Debugf(format string, v ...interface{}) { 214 | StdLogger.Debug(format, v...) 215 | } 216 | 217 | // Error print log with level Error. 218 | func Error(v ...interface{}) { 219 | StdLogger.Error(GenerateFmtStr(len(v)), v...) 220 | } 221 | 222 | // Warn print log with level Warn. 223 | func Warn(v ...interface{}) { 224 | StdLogger.Warn(GenerateFmtStr(len(v)), v...) 225 | } 226 | 227 | // Info print log with level Info. 228 | func Info(v ...interface{}) { 229 | StdLogger.Info(GenerateFmtStr(len(v)), v...) 230 | } 231 | 232 | // Debug print log with level Debug. 233 | func Debug(v ...interface{}) { 234 | StdLogger.Debug(GenerateFmtStr(len(v)), v...) 235 | } 236 | 237 | // LogLevel sets the log level. 238 | func LogLevel(logLevel string) string { 239 | logLevel = strings.ToLower(logLevel) 240 | if len(logLevel) == 0 { 241 | logLevel = "info" 242 | } 243 | updateLevel(logLevel) 244 | Warn("Set Log Level as", logLevel) 245 | return logLevel 246 | } 247 | 248 | func updateLevel(logLevel string) { 249 | switch strings.ToLower(logLevel) { 250 | case "debug": 251 | StdLogger.SetLevel(LevelDebug) 252 | case "info": 253 | StdLogger.SetLevel(LevelInformational) 254 | case "warn": 255 | StdLogger.SetLevel(LevelWarning) 256 | case "error": 257 | StdLogger.SetLevel(LevelError) 258 | default: 259 | StdLogger.SetLevel(LevelInformational) 260 | } 261 | } 262 | 263 | // GenerateFmtStr is a helper function to construct formatter string. 264 | func GenerateFmtStr(n int) string { 265 | return strings.Repeat("%v ", n) 266 | } 267 | -------------------------------------------------------------------------------- /panicer/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | A help package that catch panic error and print the panic stack. 3 | 4 | Quick start 5 | 6 | import "github.com/mnhkahn/gogogo/panicer" 7 | 8 | func main() { 9 | defer Recover() 10 | } 11 | 12 | For more information, goto godoc https://godoc.org/github.com/mnhkahn/gogogo/panicer 13 | 14 | */ 15 | package panicer 16 | -------------------------------------------------------------------------------- /panicer/panic.go: -------------------------------------------------------------------------------- 1 | package panicer 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net/http" 7 | "net/http/httputil" 8 | "os" 9 | "runtime" 10 | "time" 11 | ) 12 | 13 | // RecoverHandler help to recover in the handler. 14 | func RecoverHandler(w http.ResponseWriter, r *http.Request) { 15 | if err := recover(); err != nil { 16 | res := bytes.NewBuffer(nil) 17 | rec := fmt.Sprintf("Recover %s %v %s\n", time.Now().Format(time.RFC3339), err, r.URL.String()) 18 | os.Stderr.WriteString(rec) 19 | 20 | dump, _ := httputil.DumpRequest(r, true) 21 | _, _ = fmt.Fprintf(os.Stderr, "request: %s", r.RequestURI) 22 | 23 | stack := printStack() 24 | 25 | res.WriteString(rec) 26 | res.WriteString(stack) 27 | 28 | _, _ = res.Write(dump) 29 | 30 | os.Stderr.Write(res.Bytes()) 31 | 32 | w.WriteHeader(http.StatusServiceUnavailable) 33 | } 34 | } 35 | 36 | // RecoverDebug help to recover and print a debug info to stderr. 37 | func RecoverDebug(v interface{}) { 38 | if err := recover(); err != nil { 39 | fmt.Fprintf(os.Stderr, "Recover: %s %v %+v\n", time.Now().Format(time.RFC3339), err, v) 40 | printStack() 41 | } 42 | } 43 | 44 | // Recover recover panic and print time info to stderr. 45 | func Recover() { 46 | if err := recover(); err != nil { 47 | fmt.Fprintf(os.Stderr, "Recover: %s %v\n", time.Now().Format(time.RFC3339), err) 48 | printStack() 49 | } 50 | } 51 | 52 | func DumpStack(r *http.Request, err interface{}) []byte { 53 | res := bytes.NewBuffer(nil) 54 | rec := fmt.Sprintf("Recover %s %v %s\n", time.Now().Format(time.RFC3339), err, r.URL.String()) 55 | 56 | dump, _ := httputil.DumpRequest(r, true) 57 | 58 | stack := printStack() 59 | 60 | res.WriteString(rec) 61 | res.WriteString(stack) 62 | 63 | _, _ = res.Write(dump) 64 | 65 | return res.Bytes() 66 | } 67 | 68 | // print panic's stack. 69 | func printStack() string { 70 | res := bytes.NewBuffer(nil) 71 | for i := 1; ; i++ { 72 | pc, file, line, ok := runtime.Caller(i) 73 | if !ok { 74 | break 75 | } 76 | d := fmt.Sprintf("%d %s:%d\n", pc, file, line) 77 | res.WriteString(d) 78 | } 79 | return res.String() 80 | } 81 | -------------------------------------------------------------------------------- /util/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | A util package collection. 3 | 4 | For more information, goto godoc https://godoc.org/github.com/mnhkahn/gogogo/util 5 | 6 | Chinese details, goto http://blog.cyeam.com//golang/2018/08/27/retry?utm_source=github 7 | 8 | */ 9 | package util 10 | -------------------------------------------------------------------------------- /util/env_util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "runtime" 5 | "runtime/pprof" 6 | ) 7 | 8 | // Goroutine returns app detail, including numbers of goroutines, threads, CPU and GOMAXPROCS. 9 | func Goroutine() map[string]interface{} { 10 | res := map[string]interface{}{} 11 | res["goroutines"] = runtime.NumGoroutine() 12 | res["OS threads"] = pprof.Lookup("threadcreate").Count() 13 | res["GOMAXPROCS"] = runtime.GOMAXPROCS(0) 14 | res["num CPU"] = runtime.NumCPU() 15 | return res 16 | } 17 | -------------------------------------------------------------------------------- /util/http_util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/json" 5 | "encoding/xml" 6 | "fmt" 7 | "io" 8 | "io/ioutil" 9 | "net/http" 10 | ) 11 | 12 | // HttpJson sends a http request and unmarshal json response to v to return. 13 | func HttpJson(method, url string, contentType string, body io.Reader, v interface{}) error { 14 | req, err := http.NewRequest(method, url, body) 15 | if err != nil { 16 | return err 17 | } 18 | 19 | if contentType != "" { 20 | req.Header.Add("Content-Type", contentType) 21 | } 22 | 23 | resp, err := http.DefaultClient.Do(req) 24 | if err != nil { 25 | return err 26 | } 27 | defer resp.Body.Close() 28 | 29 | if resp.StatusCode == 200 { 30 | data, err := ioutil.ReadAll(resp.Body) 31 | if err != nil { 32 | return err 33 | } 34 | err = json.Unmarshal(data, v) 35 | if err != nil { 36 | return fmt.Errorf("%s %s", string(data), err.Error()) 37 | } 38 | } 39 | 40 | return nil 41 | } 42 | 43 | // HttpXml sends a http request and unmarshal xml response to v to return. 44 | func HttpXml(method, url string, contentType string, body io.Reader, v interface{}) error { 45 | req, err := http.NewRequest(method, url, body) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | if contentType != "" { 51 | req.Header.Add("Content-Type", contentType) 52 | } 53 | 54 | resp, err := http.DefaultClient.Do(req) 55 | if err != nil { 56 | return err 57 | } 58 | defer resp.Body.Close() 59 | 60 | if resp.StatusCode == 200 { 61 | data, err := ioutil.ReadAll(resp.Body) 62 | if err != nil { 63 | return err 64 | } 65 | err = xml.Unmarshal(data, v) 66 | if err != nil { 67 | return fmt.Errorf("%s %s", string(data), err.Error()) 68 | } 69 | } 70 | 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /util/http_util_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "encoding/xml" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestHttpJson(t *testing.T) { 10 | v := new(cyeamSearch) 11 | err := HttpJson("GET", cyeamSearchURL, "", nil, &v) 12 | assert.Nil(t, err) 13 | assert.True(t, len(v.Docs) > 0) 14 | } 15 | 16 | const cyeamSearchURL = `http://www.cyeam.com/s?t=golang` 17 | 18 | type cyeamSearch struct { 19 | Docs []struct { 20 | Des string `json:"des"` 21 | Figure string `json:"figure"` 22 | Link string `json:"link"` 23 | Title string `json:"title"` 24 | } `json:"docs"` 25 | Summary struct { 26 | D int64 `json:"d"` 27 | Num int64 `json:"num"` 28 | Q string `json:"q"` 29 | } `json:"summary"` 30 | } 31 | 32 | func TestHttpXml(t *testing.T) { 33 | v := new(cyeamBlog) 34 | err := HttpXml("GET", cyeamBlogURL, "", nil, &v) 35 | assert.Nil(t, err) 36 | assert.True(t, len(v.Channel.Item) > 0) 37 | } 38 | 39 | const cyeamBlogURL = `http://blog.cyeam.com/rss.xml` 40 | 41 | type cyeamBlog struct { 42 | XMLName xml.Name `xml:"rss"` 43 | Text string `xml:",chardata"` 44 | Version string `xml:"version,attr"` 45 | Channel struct { 46 | Text string `xml:",chardata"` 47 | Title struct { 48 | Text string `xml:",chardata"` 49 | } `xml:"title"` 50 | Description struct { 51 | Text string `xml:",chardata"` 52 | } `xml:"description"` 53 | Link []struct { 54 | Text string `xml:",chardata"` 55 | } `xml:"link"` 56 | LastBuildDate struct { 57 | Text string `xml:",chardata"` 58 | } `xml:"lastBuildDate"` 59 | PubDate struct { 60 | Text string `xml:",chardata"` 61 | } `xml:"pubDate"` 62 | Ttl struct { 63 | Text string `xml:",chardata"` 64 | } `xml:"ttl"` 65 | Item []struct { 66 | Text string `xml:",chardata"` 67 | Title struct { 68 | Text string `xml:",chardata"` 69 | } `xml:"title"` 70 | Figure struct { 71 | Text string `xml:",chardata"` 72 | } `xml:"figure"` 73 | Info struct { 74 | Text string `xml:",chardata"` 75 | } `xml:"info"` 76 | Description struct { 77 | Text string `xml:",chardata"` 78 | } `xml:"description"` 79 | Link struct { 80 | Text string `xml:",chardata"` 81 | } `xml:"link"` 82 | Guid struct { 83 | Text string `xml:",chardata"` 84 | } `xml:"guid"` 85 | PubDate struct { 86 | Text string `xml:",chardata"` 87 | } `xml:"pubDate"` 88 | } `xml:"item"` 89 | } `xml:"channel"` 90 | } 91 | -------------------------------------------------------------------------------- /util/retry_util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "github.com/mnhkahn/gogogo/logger" 5 | "time" 6 | ) 7 | 8 | // Retry function. fn is the retry function. 9 | func Retry(attempts int, sleep time.Duration, fn func() error) error { 10 | if err := fn(); err != nil { 11 | if s, ok := err.(stop); ok { 12 | return s.error 13 | } 14 | 15 | if attempts--; attempts > 0 { 16 | logger.Warnf("retry func error: %s. attemps #%d after %s.", err.Error(), attempts, sleep) 17 | time.Sleep(sleep) 18 | return Retry(attempts, 2*sleep, fn) 19 | } 20 | return err 21 | } 22 | return nil 23 | } 24 | 25 | type stop struct { 26 | error 27 | } 28 | 29 | func NoRetryError(err error) stop { 30 | return stop{err} 31 | } 32 | -------------------------------------------------------------------------------- /util/retry_util_test.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "fmt" 5 | "github.com/stretchr/testify/assert" 6 | "log" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | // TestNoRetry ... 12 | func TestNoRetry(t *testing.T) { 13 | a := func() error { 14 | log.Println("hello, world") 15 | return nil 16 | } 17 | err := Retry(3, 1*time.Millisecond, a) 18 | assert.Nil(t, err) 19 | } 20 | 21 | // TestRetryEveryTime ... 22 | func TestRetryEveryTime(t *testing.T) { 23 | cnt := 0 24 | err := fmt.Errorf("test error every time") 25 | a := func() error { 26 | cnt++ 27 | return err 28 | } 29 | errFn := Retry(3, 1*time.Millisecond, a) 30 | assert.Equal(t, err, errFn) 31 | assert.Equal(t, 3, cnt) 32 | } 33 | 34 | // TestRetryOnce ... 35 | func TestRetryOnce(t *testing.T) { 36 | cnt := 0 37 | err := fmt.Errorf("test error every time") 38 | a := func() error { 39 | if cnt == 0 { 40 | cnt++ 41 | return err 42 | } else { 43 | cnt++ 44 | return nil 45 | } 46 | } 47 | errFn := Retry(3, 1*time.Millisecond, a) 48 | assert.Nil(t, errFn) 49 | assert.Equal(t, 2, cnt) 50 | } 51 | 52 | // TestRetryStop test stop retry after first call fn. 53 | func TestRetryStop(t *testing.T) { 54 | cnt := 0 55 | err := fmt.Errorf("test error every time") 56 | a := func() error { 57 | cnt++ 58 | return NoRetryError(err) 59 | } 60 | errFn := Retry(3, 1*time.Millisecond, a) 61 | assert.Equal(t, errFn, err) 62 | assert.Equal(t, 1, cnt) 63 | } 64 | -------------------------------------------------------------------------------- /xreflect/decodestruct/decodestruct.go: -------------------------------------------------------------------------------- 1 | // Package decodestruct 2 | package decodestruct 3 | 4 | import ( 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | // FuncMap is the name with func. 10 | type FuncMap map[string]func() (interface{}, error) 11 | 12 | // Decode dto by tagName. It will auto call func in FuncMap by tag name and will auto set value by dto's field. 13 | func Decode(dto interface{}, tagName string, funcMap FuncMap) (interface{}, error) { 14 | rv := reflect.ValueOf(dto).Elem() 15 | rt := reflect.TypeOf(dto).Elem() 16 | for i := 0; i < rv.NumField(); i++ { 17 | fieldName := rt.Field(i).Name 18 | // if it has a tag, use tag instead. 19 | if tagName != "" { 20 | if tag := strings.TrimSpace(rt.Field(i).Tag.Get(tagName)); tag != "" { 21 | fieldName = tag 22 | } 23 | } 24 | 25 | // get func by field name 26 | var fn func() (interface{}, error) 27 | var ok bool 28 | if fn, ok = funcMap[fieldName]; !ok { 29 | continue 30 | } 31 | 32 | // call tag function 33 | res, err := fn() 34 | if err != nil { 35 | return dto, err 36 | } 37 | 38 | // save value 39 | if res != nil { 40 | rv.Field(i).Set(reflect.ValueOf(res)) 41 | } 42 | } 43 | return nil, nil 44 | } 45 | -------------------------------------------------------------------------------- /xreflect/decodestruct/decodestruct_test.go: -------------------------------------------------------------------------------- 1 | // Package decodestruct 2 | package decodestruct 3 | 4 | import ( 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | type Config struct { 10 | Link string `tag:"link"` 11 | NotExist int `tag:"not_exist"` 12 | } 13 | 14 | func TestDecode(t *testing.T) { 15 | c := new(Config) 16 | _, err := Decode(c, "tag", FuncMap{ 17 | "link": func() (interface{}, error) { 18 | return "http://cyeam.com", nil 19 | }, 20 | }) 21 | assert.Nil(t, err) 22 | assert.Equal(t, c.Link, "http://cyeam.com") 23 | assert.Equal(t, c.NotExist, 0) 24 | } 25 | -------------------------------------------------------------------------------- /xreflect/type_conversion_reflect.go: -------------------------------------------------------------------------------- 1 | // Package xreflect 2 | package xreflect 3 | 4 | import ( 5 | "errors" 6 | "reflect" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | func StringToReflectValue(value string, ntype string) (reflect.Value, error) { 12 | if ntype == "string" { 13 | return reflect.ValueOf(value), nil 14 | } else if ntype == "time.Time" { 15 | t, err := time.ParseInLocation("2006-01-02 15:04:05", value, time.Local) 16 | return reflect.ValueOf(t), err 17 | } else if ntype == "Time" { 18 | t, err := time.ParseInLocation("2006-01-02 15:04:05", value, time.Local) 19 | return reflect.ValueOf(t), err 20 | } else if ntype == "int" { 21 | i, err := strconv.Atoi(value) 22 | return reflect.ValueOf(i), err 23 | } else if ntype == "int8" { 24 | i, err := strconv.ParseInt(value, 10, 64) 25 | return reflect.ValueOf(int8(i)), err 26 | } else if ntype == "int32" { 27 | i, err := strconv.ParseInt(value, 10, 64) 28 | return reflect.ValueOf(int64(i)), err 29 | } else if ntype == "int64" { 30 | i, err := strconv.ParseInt(value, 10, 64) 31 | return reflect.ValueOf(i), err 32 | } else if ntype == "float32" { 33 | i, err := strconv.ParseFloat(value, 64) 34 | return reflect.ValueOf(float32(i)), err 35 | } else if ntype == "float64" { 36 | i, err := strconv.ParseFloat(value, 64) 37 | return reflect.ValueOf(i), err 38 | } else if ntype == "interface" { 39 | return reflect.ValueOf(value), nil 40 | } 41 | 42 | return reflect.ValueOf(value), errors.New("unknown type:" + ntype) 43 | } 44 | -------------------------------------------------------------------------------- /xtest/racetest.go: -------------------------------------------------------------------------------- 1 | // Package xtest 2 | package xtest 3 | 4 | import ( 5 | "sync" 6 | "testing" 7 | ) 8 | 9 | func RaceTestCounter(t *testing.T, count int, f func(i int) interface{}) { 10 | counterMap := sync.Map{} 11 | 12 | RaceTest(t, count, func(i int) { 13 | key := f(i) 14 | a, _ := counterMap.Load(key) 15 | if a == nil { 16 | counterMap.Store(key, 1) 17 | } else { 18 | counterMap.Store(key, a.(int)+1) 19 | } 20 | }) 21 | 22 | _count := 0 23 | counterMap.Range(func(key, value interface{}) bool { 24 | _count++ 25 | return true 26 | }) 27 | if _count != count { 28 | t.Errorf("race test error, want len %d, but got %d", count, _count) 29 | counterMap.Range(func(key, value interface{}) bool { 30 | t.Log("key =", key, "value =", value) 31 | return true 32 | }) 33 | } 34 | } 35 | 36 | func RaceTest(t *testing.T, count int, f func(i int)) { 37 | if count <= 0 { 38 | count = 10000 39 | t.Logf("count use %d instead\n", count) 40 | } 41 | var wait sync.WaitGroup 42 | 43 | defer func() { 44 | if err := recover(); err != nil { 45 | t.Error(err) 46 | } 47 | }() 48 | 49 | for i := 0; i < count; i++ { 50 | wait.Add(1) 51 | go func() { 52 | f(i) 53 | wait.Done() 54 | }() 55 | } 56 | 57 | wait.Wait() 58 | } 59 | -------------------------------------------------------------------------------- /xtest/racetest_test.go: -------------------------------------------------------------------------------- 1 | // Package xtest 2 | package xtest 3 | 4 | import ( 5 | "strings" 6 | "testing" 7 | 8 | "github.com/sony/sonyflake" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestRaceTest(t *testing.T) { 13 | var b strings.Builder 14 | RaceTest(t, 10000, func(i int) { 15 | b.WriteString("1") 16 | }) 17 | assert.NotEqual(t, 10000, b.Len()) 18 | } 19 | 20 | func TestRaceTestMap(t *testing.T) { 21 | m := make(map[string]string, 0) 22 | go func() { 23 | for { 24 | _ = m["a"] 25 | } 26 | }() 27 | RaceTest(t, 10000, func(i int) { 28 | m["a"] = "a" 29 | }) 30 | } 31 | 32 | func TestRaceTestCounter(t *testing.T) { 33 | sf := sonyflake.NewSonyflake(sonyflake.Settings{ 34 | //StartTime: time.Now(), 35 | MachineID: func() (u uint16, e error) { 36 | return 0, nil 37 | }, 38 | }) 39 | sf2 := sonyflake.NewSonyflake(sonyflake.Settings{ 40 | //StartTime: time.Now(), 41 | MachineID: func() (u uint16, e error) { 42 | return 0, nil 43 | }, 44 | }) 45 | 46 | RaceTestCounter(t, 1000, func(i int) interface{} { 47 | var unique_id uint64 48 | if i%2 == 0 { 49 | unique_id, _ = sf.NextID() 50 | return unique_id 51 | } else { 52 | unique_id, _ = sf2.NextID() 53 | } 54 | return unique_id 55 | }) 56 | } 57 | --------------------------------------------------------------------------------