├── .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 |
39 | {{range $i, $r := .Routers}}
40 | - {{$r}}
41 | {{end}}
42 |
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 | Url Path |
96 | Count |
97 | Sum Elapse |
98 | AvgTime Elapse |
99 |
100 | {{range $stat := .Stats}}
101 |
102 | {{$stat.Url}} |
103 | {{$stat.Cnt}} |
104 | {{$stat.SumTime}} |
105 | {{$stat.AvgTime}} |
106 |
107 | {{end}}
108 |
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 |
--------------------------------------------------------------------------------