diff --git a/.gitignore b/.gitignore index b3efc39..429f1af 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,14 @@ -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, build with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + .idea \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 3b90841..eb28f53 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,13 @@ -language: go -sudo: false -go: - - tip - -services: - - redis-server - -before_install: - - go get github.com/mattn/goveralls - -script: - - $GOPATH/bin/goveralls -service=travis-ci +language: go +sudo: false +go: + - tip + +services: + - redis-server + +before_install: + - go get github.com/mattn/goveralls + +script: + - $GOPATH/bin/goveralls -service=travis-ci diff --git a/LICENSE b/LICENSE index 261eeb9..29f81d8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,201 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 3ad7690..1b7cc06 100644 --- a/README.md +++ b/README.md @@ -1,465 +1,484 @@ -

- ThinkGo -

- -

- ThinkGo is a lightweight MVC framework written in Go (Golang). -

-

- - Build Status - - - Coverage Status - -

-

- - Go Report Card - - - - - - GoDoc - - - Open Source Helpers - - - Join the chat - - - Latest Stable Version - - - License - -

- - -## Installation - -The only requirement is the [Go Programming Language](https://golang.org/dl/) - -``` -go get -u github.com/thinkoner/thinkgo -``` - -## Quick start - -```go -package main - -import ( - "github.com/thinkoner/thinkgo" - "fmt" - "github.com/thinkoner/thinkgo/router" - "github.com/thinkoner/thinkgo/context" -) - -func main() { - app := thinkgo.BootStrap() - app.RegisterRoute(func(route *router.Route) { - - route.Get("/", func(req *context.Request) *context.Response { - return thinkgo.Text("Hello ThinkGo !") - }) - - route.Get("/ping", func(req *context.Request) *context.Response { - return thinkgo.Json(map[string]string{ - "message": "pong", - }) - }) - - // Dependency injection - route.Get("/user/{name}", func(req *context.Request, name string) *context.Response { - return thinkgo.Text(fmt.Sprintf("Hello %s !", name)) - }) - }) - // listen and serve on 0.0.0.0:9011 - app.Run() -} -``` - -## Features - -- [Routing](#routing) -- [Middleware](#middleware) -- [Controller](#controller) -- [Request](#http-request) -- [Response](#http-response) -- [View](#view) -- [HTTP Session](#http-session) -- [Logging](#logging) -- [Cache](#cache) -- [ORM](#orm) - -## Routing - -#### Basic Routing - -The most basic routes accept a URI and a Closure, providing a very simple and expressive method of defining routes: - -```go -app.RegisterRoute(func(route *router.Route) { - route.Get("/foo", func(req *context.Request) *context.Response { - return thinkgo.Text("Hello ThinkGo !") - }) -}) -``` - -#### Available Router Methods - -The router allows you to register routes that respond to any HTTP verb: - -```go -route.Get("/someGet", getting) -route.Post("/somePost", posting) -route.Put("/somePut", putting) -route.Delete("/someDelete", deleting) -route.Patch("/somePatch", patching) -route.Options("/someOptions", options) -``` - -Sometimes you may need to register a route that responds to multiple HTTP verbs. You may even register a route that responds to all HTTP verbs using the `Any` method: - -```go -route.Any("/someAny", any) -``` - -#### Parameters in path - -Of course, sometimes you will need to capture segments of the URI within your route. For example, you may need to capture a user's ID from the URL. You may do so by defining route parameters: - -```go -route.Get("/user/{id}", func(req *context.Request, id string) *context.Response { - return thinkgo.Text(fmt.Sprintf("User %s", id)) -}) -``` - -You may define as many route parameters as required by your route: - -```go -route.Get("/posts/{post}/comments/{comment}", func(req *context.Request, postId, commentId string) *context.Response { - // -}) -``` - -#### Route Prefixes - -The prefix method may be used to prefix each route in the group with a given URI. For example, you may want to prefix all route URIs within the group with `admin`: - -```go -route.Prefix("/admin").Group(func(group *router.Route) { - group.Prefix("user").Group(func(group *router.Route) { - // ... - }) - group.Prefix("posts").Group(func(group *router.Route) { - // ... - }) -}) -``` - -#### Route Groups - -Route groups allow you to share route attributes, such as middleware or prefix, across a large number of routes without needing to define those attributes on each individual route. - -```go -route.Prefix("/admin").Group(func(group *router.Route) { - group.Prefix("user").Group(func(group *router.Route) { - group.Get("", func(request *context.Request) *context.Response { - return thinkgo.Text("admin user !") - }).Middleware(func(request *context.Request, next router.Closure) interface{} { - if _, err := request.Input("id"); err != nil { - return thinkgo.Text("Invalid parameters") - } - return next(request) - }) - group.Get("edit", func(request *context.Request) *context.Response { - return thinkgo.Text("admin user edit !") - }) - }).Middleware(func(request *context.Request, next router.Closure) interface{} { - if _, err := request.Input("user"); err != nil { - return thinkgo.Text("Invalid parameters") - } - return next(request) - }) -}).Middleware(func(request *context.Request, next router.Closure) interface{} { - if _, err := request.Input("token"); err != nil { - return thinkgo.Text("Invalid parameters") - } - return next(request) -}) -``` - -## Middleware - -Middleware provide a convenient mechanism for filtering HTTP requests entering your application. You only need to implement the `Middleware` interface. - -```go -route.Get("/foo", func(request *context.Request) *context.Response { - return thinkgo.Text("Hello ThinkGo !") -}).Middleware(func(request *context.Request, next router.Closure) interface{} { - if _, err := request.Input("name"); err != nil { - return thinkgo.Text("Invalid parameters") - } - return next(request) -}) -``` - -#### Before Middleware - -Whether a middleware runs before or after a request depends on the middleware itself. For example, the following middleware would perform some task `before` the request is handled by the application: - -```go -func(request *context.Request, next router.Closure) interface{} { - - // Perform action - // ... - - return next(request) -} -``` - -#### After Middleware - -However, this middleware would perform its task `after` the request is handled by the application: - -```go -func(request *context.Request, next router.Closure) interface{} { - - response := next(request) - - // Perform action - // ... - - return response -} -``` - -## Controller - -#### Basic Controller - -Below is an example of a basic controller class. - -```go -package controller - -import ( - "github.com/thinkoner/thinkgo" - "github.com/thinkoner/thinkgo/context" -) - -func Index(req *context.Request) *context.Response { - return thinkgo.Text("Hello ThinkGo !") -} - -``` - -You can define a route to this controller like so: - -```go -route.Get("/", controller.Index) -``` - -#### Resource Controller - -This feature will be supported in a future release. - -## HTTP Request - -#### Accessing The Request - -To obtain an instance of the current HTTP request via dependency injection - -```go -func Handler(req *context.Request) *context.Response { - name := req.Input("name") -} -``` - -#### Dependency Injection & Route Parameters - -If your controller method is also expecting input from a route parameter you should list your route parameters after the request dependencies. For example, you can access your route parameter `name` like so: - -```go -route.Put("/user/{name}", func(req *context.Request, name string) *context.Response { - // -}) -``` - -#### Request Path & Method - -The `path` method returns the request's path information. So, if the incoming request is targeted at `http://domain.com/foo/bar`, the `path` method will return `foo/bar`: - -```go -uri := req.GetPath() -``` - -The `method` method will return the HTTP verb for the request. - -```go -method := req.GetMethod(); -``` - -#### Retrieving Cookies From Requests - -```go -name, _ := request.Cookie("name") -``` - -## HTTP Response - -an HTTP Response Must implement the `*context.Response` interface - -#### Creating Responses - -a simple strings or json Response: - -```go -thinkgo.Text("Hello ThinkGo !") - -thinkgo.Json(map[string]string{ - "message": "pong", - }) -``` - -#### Attaching Cookies To Responses - -```go -response.Cookie("name", "alice") -``` - -#### Redirects - -```go -route.Get("/redirect", func(request *context.Request) *context.Response { - return context.Redirect("https://www.google.com") -}) -``` - -## View - -views are stored in the `views` directory, A simple view might look something like this: - -`views/layout.html` like this: - -```html -{{ define "layout" }} - - - - - {{ .Title }} - - - {{ template "content" .}} - - -{{ end }} -``` - -`views/tpl.html` like this: - -```html -{{ define "content" }} -

{{ .Message }}

-{{ end }} -{{ template "layout" . }} -``` - -we may return it using the `Render` function like so: - -```go -route.Get("/tpl", func(request *context.Request) *context.Response { - data := map[string]interface{}{"Title": "ThinkGo", "Message": "Hello ThinkGo !"} - return thinkgo.Render("tpl.html", data) -}) -``` - -## HTTP Session - -retrieving Data like this: - -```go -request.Session().Get("user") -``` - -storing Data like this: - -```go -request.Session().Set("user", "alice") -``` - -## Logging - -The logger provides the eight logging levels defined in [RFC 5424]( https://tools.ietf.org/html/rfc5424 ): **emergency**, **alert**, **critical**, **error**, **warning**, **notice**, **info** and **debug**. - -```go -import "github.com/thinkoner/thinkgo/log" - -log.Debug("log with Debug") -log.Info("log with Info") -log.Notice("log with Notice") -log.Warn("log with Warn") -log.Error("log with Error") -log.Crit("log with Crit") -log.Alert("log with Alert") -log.Emerg("log with Emerg") -``` - -## Cache - -ThinkGo Cache Currently supports redis, memory, and can customize the store adapter. - -#### Basic Usage - -```go -import ( - "github.com/thinkoner/thinkgo/cache" - "time" -) - - -var foo string - -// Create a cache with memory store -c, _ := cache.Cache(cache.NewMemoryStore("thinkgo")) - -// Set the value -c.Put("foo", "thinkgo", 10 * time.Minute) - -// Get the string associated with the key "foo" from the cache -c.Get("foo", &foo) - -``` - -#### Retrieve & Store - -Sometimes you may wish to retrieve an item from the cache, but also store a default value if the requested item doesn't exist. For example, you may wish to retrieve all users from the cache or, if they don't exist, retrieve them from the callback and add them to the cache. You may do this using the `Remember` method: - -```go -var foo int - -cache.Remember("foo", &a, 1*time.Minute, func() interface{} { - return "thinkgo" -}) -``` - -refer to [ThinkGo Cache]( https://github.com/thinkoner/thinkgo/tree/master/cache ) - -## ORM - -refer to [ThinkORM]( https://github.com/thinkoner/thinkorm ) - -## License - -This project is licensed under the [Apache 2.0 license](LICENSE). - -## Contact - -If you have any issues or feature requests, please contact us. PR is welcomed. -- https://github.com/thinkoner/thinkgo/issues -- duanpier@gmail.com +

+ ThinkGo +

+ +

+ ThinkGo is a lightweight MVC framework written in Go (Golang). +

+

+ + Build Status + + + Coverage Status + +

+

+ + Go Report Card + + + + + + GoDoc + + + Open Source Helpers + + + Join the chat + + + Latest Stable Version + + + License + +

+ + +## Installation + +The only requirement is the [Go Programming Language](https://golang.org/dl/) + +``` +go get -u github.com/thinkoner/thinkgo +``` + +## Quick start + +```go +package main + +import ( + "github.com/thinkoner/thinkgo" + "fmt" + "github.com/thinkoner/thinkgo/router" + "github.com/thinkoner/thinkgo/context" +) + +func main() { + app := thinkgo.BootStrap() + app.RegisterRoute(func(route *router.Route) { + + route.Get("/", func(req *context.Request) *context.Response { + return thinkgo.Text("Hello ThinkGo !") + }) + + route.Get("/ping", func(req *context.Request) *context.Response { + return thinkgo.Json(map[string]string{ + "message": "pong", + }) + }) + + // Dependency injection + route.Get("/user/{name}", func(req *context.Request, name string) *context.Response { + return thinkgo.Text(fmt.Sprintf("Hello %s !", name)) + }) + }) + // listen and serve on 0.0.0.0:9011 + app.Run() +} +``` + +## Features + +- [Routing](#routing) +- [Middleware](#middleware) +- [Controller](#controller) +- [Request](#http-request) +- [Response](#http-response) +- [View](#view) +- [HTTP Session](#http-session) +- [Logging](#logging) +- [Cache](#cache) +- [ORM](#orm) + +## Routing + +#### Basic Routing + +The most basic routes accept a URI and a Closure, providing a very simple and expressive method of defining routes: + +```go +app.RegisterRoute(func(route *router.Route) { + route.Get("/foo", func(req *context.Request) *context.Response { + return thinkgo.Text("Hello ThinkGo !") + }) +}) +``` + +#### Available Router Methods + +The router allows you to register routes that respond to any HTTP verb: + +```go +route.Get("/someGet", getting) +route.Post("/somePost", posting) +route.Put("/somePut", putting) +route.Delete("/someDelete", deleting) +route.Patch("/somePatch", patching) +route.Options("/someOptions", options) +``` + +Sometimes you may need to register a route that responds to multiple HTTP verbs. You may even register a route that responds to all HTTP verbs using the `Any` method: + +```go +route.Any("/someAny", any) +``` + +#### Parameters in path + +Of course, sometimes you will need to capture segments of the URI within your route. For example, you may need to capture a user's ID from the URL. You may do so by defining route parameters: + +```go +route.Get("/user/{id}", func(req *context.Request, id string) *context.Response { + return thinkgo.Text(fmt.Sprintf("User %s", id)) +}) +``` + +You may define as many route parameters as required by your route: + +```go +route.Get("/posts/{post}/comments/{comment}", func(req *context.Request, postId, commentId string) *context.Response { + // +}) +``` + +#### Route Prefixes + +The prefix method may be used to prefix each route in the group with a given URI. For example, you may want to prefix all route URIs within the group with `admin`: + +```go +route.Prefix("/admin").Group(func(group *router.Route) { + group.Prefix("user").Group(func(group *router.Route) { + // ... + }) + group.Prefix("posts").Group(func(group *router.Route) { + // ... + }) +}) +``` + +#### Route Groups + +Route groups allow you to share route attributes, such as middleware or prefix, across a large number of routes without needing to define those attributes on each individual route. + +```go +route.Prefix("/admin").Group(func(group *router.Route) { + group.Prefix("user").Group(func(group *router.Route) { + group.Get("", func(request *context.Request) *context.Response { + return thinkgo.Text("admin user !") + }).Middleware(func(request *context.Request, next router.Closure) interface{} { + if _, err := request.Input("id"); err != nil { + return thinkgo.Text("Invalid parameters") + } + return next(request) + }) + group.Get("edit", func(request *context.Request) *context.Response { + return thinkgo.Text("admin user edit !") + }) + }).Middleware(func(request *context.Request, next router.Closure) interface{} { + if _, err := request.Input("user"); err != nil { + return thinkgo.Text("Invalid parameters") + } + return next(request) + }) +}).Middleware(func(request *context.Request, next router.Closure) interface{} { + if _, err := request.Input("token"); err != nil { + return thinkgo.Text("Invalid parameters") + } + return next(request) +}) +``` + +## Middleware + +Middleware provide a convenient mechanism for filtering HTTP requests entering your application. You only need to implement the `Middleware` interface. + +```go +route.Get("/foo", func(request *context.Request) *context.Response { + return thinkgo.Text("Hello ThinkGo !") +}).Middleware(func(request *context.Request, next router.Closure) interface{} { + if _, err := request.Input("name"); err != nil { + return thinkgo.Text("Invalid parameters") + } + return next(request) +}) +``` + +#### Before Middleware + +Whether a middleware runs before or after a request depends on the middleware itself. For example, the following middleware would perform some task `before` the request is handled by the application: + +```go +func(request *context.Request, next router.Closure) interface{} { + + // Perform action + // ... + + return next(request) +} +``` + +#### After Middleware + +However, this middleware would perform its task `after` the request is handled by the application: + +```go +func(request *context.Request, next router.Closure) interface{} { + + response := next(request) + + // Perform action + // ... + + return response +} +``` + +## Controller + +#### Basic Controller + +Below is an example of a basic controller class. + +```go +package controller + +import ( + "github.com/thinkoner/thinkgo" + "github.com/thinkoner/thinkgo/context" +) + +func Index(req *context.Request) *context.Response { + return thinkgo.Text("Hello ThinkGo !") +} + +``` + +You can define a route to this controller like so: + +```go +route.Get("/", controller.Index) +``` + +#### Resource Controller + +This feature will be supported in a future release. + +## HTTP Request + +#### Accessing The Request + +To obtain an instance of the current HTTP request via dependency injection + +```go +func Handler(req *context.Request) *context.Response { + name := req.Input("name") +} +``` + +#### Dependency Injection & Route Parameters + +If your controller method is also expecting input from a route parameter you should list your route parameters after the request dependencies. For example, you can access your route parameter `name` like so: + +```go +route.Put("/user/{name}", func(req *context.Request, name string) *context.Response { + // +}) +``` + +#### Request Path & Method + +The `path` method returns the request's path information. So, if the incoming request is targeted at `http://domain.com/foo/bar`, the `path` method will return `foo/bar`: + +```go +uri := req.GetPath() +``` + +The `method` method will return the HTTP verb for the request. + +```go +method := req.GetMethod(); +``` + +#### Retrieving Cookies From Requests + +```go +name, _ := request.Cookie("name") +``` + +## HTTP Response + +an HTTP Response Must implement the `*context.Response` interface + +#### Creating Responses + +a simple strings or json Response: + +```go +thinkgo.Text("Hello ThinkGo !") + +thinkgo.Json(map[string]string{ + "message": "pong", + }) +``` + +#### Attaching Cookies To Responses + +```go +response.Cookie("name", "alice") +``` + +#### Redirects + +```go +route.Get("/redirect", func(request *context.Request) *context.Response { + return context.Redirect("https://www.google.com") +}) +``` + +## View + +views are stored in the `views` directory, A simple view might look something like this: + +`views/layout.html` like this: + +```html +{{ define "layout" }} + + + + + {{ .Title }} + + + {{ template "content" .}} + + +{{ end }} +``` + +`views/tpl.html` like this: + +```html +{{ define "content" }} +

{{ .Message }}

+{{ end }} +{{ template "layout" . }} +``` + +we may return it using the `Render` function like so: + +```go +route.Get("/tpl", func(request *context.Request) *context.Response { + data := map[string]interface{}{"Title": "ThinkGo", "Message": "Hello ThinkGo !"} + return thinkgo.Render("tpl.html", data) +}) +``` + +## HTTP Session + +retrieving Data like this: + +```go +request.Session().Get("user") +``` + +storing Data like this: + +```go +request.Session().Set("user", "alice") +``` + +## Logging + +The logger provides the eight logging levels defined in [RFC 5424]( https://tools.ietf.org/html/rfc5424 ): **emergency**, **alert**, **critical**, **error**, **warning**, **notice**, **info** and **debug**. + +#### Basic Usage + +```go +import "github.com/thinkoner/thinkgo/log" + +log.Debug("log with Debug") +log.Info("log with Info") +log.Notice("log with Notice") +log.Warn("log with Warn") +log.Error("log with Error") +log.Crit("log with Crit") +log.Alert("log with Alert") +log.Emerg("log with Emerg") +``` + +#### Log Storage + +Out of the box, ThinkGo supports writing log information to `daily` files, the `console`. + +For example, if you wish to use `daily` log files, you can do this: + +```go +import ( + "github.com/thinkoner/thinkgo/log" + "github.com/thinkoner/thinkgo/log/handler" + "github.com/thinkoner/thinkgo/log/record" +) + +fh := handler.NewFileHandler("path/to/thinkgo.log", record.INFO) +log.GetLogger().PushHandler(fh) +``` + +## Cache + +ThinkGo Cache Currently supports redis, memory, and can customize the store adapter. + +#### Basic Usage + +```go +import ( + "github.com/thinkoner/thinkgo/cache" + "time" +) + + +var foo string + +// Create a cache with memory store +c, _ := cache.Cache(cache.NewMemoryStore("thinkgo")) + +// Set the value +c.Put("foo", "thinkgo", 10 * time.Minute) + +// Get the string associated with the key "foo" from the cache +c.Get("foo", &foo) + +``` + +#### Retrieve & Store + +Sometimes you may wish to retrieve an item from the cache, but also store a default value if the requested item doesn't exist. For example, you may wish to retrieve all users from the cache or, if they don't exist, retrieve them from the callback and add them to the cache. You may do this using the `Remember` method: + +```go +var foo int + +cache.Remember("foo", &a, 1*time.Minute, func() interface{} { + return "thinkgo" +}) +``` + +refer to [ThinkGo Cache]( https://github.com/thinkoner/thinkgo/tree/master/cache ) + +## ORM + +refer to [ThinkORM]( https://github.com/thinkoner/thinkorm ) + +## License + +This project is licensed under the [Apache 2.0 license](LICENSE). + +## Contact + +If you have any issues or feature requests, please contact us. PR is welcomed. +- https://github.com/thinkoner/thinkgo/issues +- duanpier@gmail.com diff --git a/app/app.go b/app/app.go index faba32d..9cb2864 100644 --- a/app/app.go +++ b/app/app.go @@ -1,37 +1,37 @@ -package app - -import ( - "github.com/thinkoner/thinkgo/router" - "github.com/thinkoner/thinkgo/view" -) - -// Application the ThinkGo Application -type Application struct { - view *view.View - route *router.Route -} - -// NewApplication returns a new ThinkGo Application -func NewApplication() *Application { - return &Application{} -} - -// RegisterRoute Register Route for Application -func (a *Application) RegisterRoute(r *router.Route) { - a.route = r -} - -// RegisterView Register View for Application -func (a *Application) RegisterView(v *view.View) { - a.view = v -} - -// GetRoute Get the router of the application -func (a *Application) GetRoute() *router.Route { - return a.route -} - -// GetView Get the view of the application -func (a *Application) GetView() *view.View { - return a.view -} +package app + +import ( + "github.com/thinkoner/thinkgo/router" + "github.com/thinkoner/thinkgo/view" +) + +// Application the ThinkGo Application +type Application struct { + view *view.View + route *router.Route +} + +// NewApplication returns a new ThinkGo Application +func NewApplication() *Application { + return &Application{} +} + +// RegisterRoute Register Route for Application +func (a *Application) RegisterRoute(r *router.Route) { + a.route = r +} + +// RegisterView Register View for Application +func (a *Application) RegisterView(v *view.View) { + a.view = v +} + +// GetRoute Get the router of the application +func (a *Application) GetRoute() *router.Route { + return a.route +} + +// GetView Get the view of the application +func (a *Application) GetView() *view.View { + return a.view +} diff --git a/app/handler.go b/app/handler.go index df7b8f5..7e00e4f 100644 --- a/app/handler.go +++ b/app/handler.go @@ -1,16 +1,16 @@ -package app - -import ( - "github.com/thinkoner/thinkgo/context" -) - -// HandlerFunc Handle the application. -type HandlerFunc func(app *Application) Handler - -// Closure Anonymous function, Used in Middleware Handler -type Closure func(req *context.Request) interface{} - -// Handler Middleware Handler interface -type Handler interface { - Process(request *context.Request, next Closure) interface{} -} +package app + +import ( + "github.com/thinkoner/thinkgo/context" +) + +// HandlerFunc Handle the application. +type HandlerFunc func(app *Application) Handler + +// Closure Anonymous function, Used in Middleware Handler +type Closure func(req *context.Request) interface{} + +// Handler Middleware Handler interface +type Handler interface { + Process(request *context.Request, next Closure) interface{} +} diff --git a/app/route_hendler.go b/app/route_hendler.go index 7871d38..e28ff72 100644 --- a/app/route_hendler.go +++ b/app/route_hendler.go @@ -1,28 +1,28 @@ -package app - -import ( - "github.com/thinkoner/thinkgo/context" - "github.com/thinkoner/thinkgo/router" -) - -type RouteHandler struct { - Route *router.Route -} - -// NewRouteHandler The default RouteHandler -func NewRouteHandler(app *Application) Handler { - return &RouteHandler{ - Route: app.GetRoute(), - } -} - -// Process Process the request to a router and return the response. -func (h *RouteHandler) Process(request *context.Request, next Closure) interface{} { - rule, err := h.Route.Dispatch(request) - - if err != nil { - return context.NotFoundResponse() - } - - return router.RunRoute(request, rule) -} +package app + +import ( + "github.com/thinkoner/thinkgo/context" + "github.com/thinkoner/thinkgo/router" +) + +type RouteHandler struct { + Route *router.Route +} + +// NewRouteHandler The default RouteHandler +func NewRouteHandler(app *Application) Handler { + return &RouteHandler{ + Route: app.GetRoute(), + } +} + +// Process Process the request to a router and return the response. +func (h *RouteHandler) Process(request *context.Request, next Closure) interface{} { + rule, err := h.Route.Dispatch(request) + + if err != nil { + return context.NotFoundResponse() + } + + return router.RunRoute(request, rule) +} diff --git a/app/session_handler.go b/app/session_handler.go index d84c378..709db20 100644 --- a/app/session_handler.go +++ b/app/session_handler.go @@ -1,50 +1,50 @@ -package app - -import ( - "github.com/thinkoner/thinkgo/config" - "github.com/thinkoner/thinkgo/context" - "github.com/thinkoner/thinkgo/session" -) - -type SessionHandler struct { - manager *session.Manager - app *Application -} - -// SessionHandler The default SessionHandler -func NewSessionHandler(app *Application) Handler { - handler := &SessionHandler{} - handler.manager = session.NewManager(&session.Config{ - Driver: config.Session.Driver, - CookieName: config.Session.CookieName, - Lifetime: config.Session.Lifetime, - Encrypt: config.Session.Encrypt, - Files: config.Session.Files, - }) - - handler.app = app - - return handler -} - -func (h *SessionHandler) Process(req *context.Request, next Closure) interface{} { - store := h.startSession(req) - - req.SetSession(store) - - result := next(req) - - if res, ok := result.(session.Response); ok { - h.saveSession(res) - } - - return result -} - -func (h *SessionHandler) startSession(req *context.Request) *session.Store { - return h.manager.SessionStart(req) -} - -func (h *SessionHandler) saveSession(res session.Response) { - h.manager.SessionSave(res) -} +package app + +import ( + "github.com/thinkoner/thinkgo/config" + "github.com/thinkoner/thinkgo/context" + "github.com/thinkoner/thinkgo/session" +) + +type SessionHandler struct { + manager *session.Manager + app *Application +} + +// SessionHandler The default SessionHandler +func NewSessionHandler(app *Application) Handler { + handler := &SessionHandler{} + handler.manager = session.NewManager(&session.Config{ + Driver: config.Session.Driver, + CookieName: config.Session.CookieName, + Lifetime: config.Session.Lifetime, + Encrypt: config.Session.Encrypt, + Files: config.Session.Files, + }) + + handler.app = app + + return handler +} + +func (h *SessionHandler) Process(req *context.Request, next Closure) interface{} { + store := h.startSession(req) + + req.SetSession(store) + + result := next(req) + + if res, ok := result.(session.Response); ok { + h.saveSession(res) + } + + return result +} + +func (h *SessionHandler) startSession(req *context.Request) *session.Store { + return h.manager.SessionStart(req) +} + +func (h *SessionHandler) saveSession(res session.Response) { + h.manager.SessionSave(res) +} diff --git a/cache/README.md b/cache/README.md index 02b1268..c225feb 100644 --- a/cache/README.md +++ b/cache/README.md @@ -1,49 +1,49 @@ -# ThinkGo-Cache - -`ThinkGo-Cache` is a cache library for Golang,it currently supports redis, memory, and can customize the store adapter. - -## Installation - -``` -go get github.com/thinkoner/thinkgo/cache -``` - -## Usage - -#### Basic Usage - -```go -import ( - "github.com/thinkoner/thinkgo/cache" - "time" -) - - -var foo string - -// Create a cache with memory store -c, _ := cache.Cache(cache.NewMemoryStore("thinkgo")) - -// Set the value -c.Put("foo", "thinkgo", 10 * time.Minute) - -// Get the string associated with the key "foo" from the cache -c.Get("foo", &foo) - -``` - -#### Retrieve & Store - -Sometimes you may wish to retrieve an item from the cache, but also store a default value if the requested item doesn't exist. For example, you may wish to retrieve all users from the cache or, if they don't exist, retrieve them from the callback and add them to the cache. You may do this using the `Remember` method: - -```go -var foo int - -cache.Remember("foo", &a, 1*time.Minute, func() interface{} { - return "thinkgo" -}) -``` - -## License - +# ThinkGo-Cache + +`ThinkGo-Cache` is a cache library for Golang,it currently supports redis, memory, and can customize the store adapter. + +## Installation + +``` +go get github.com/thinkoner/thinkgo/cache +``` + +## Usage + +#### Basic Usage + +```go +import ( + "github.com/thinkoner/thinkgo/cache" + "time" +) + + +var foo string + +// Create a cache with memory store +c, _ := cache.Cache(cache.NewMemoryStore("thinkgo")) + +// Set the value +c.Put("foo", "thinkgo", 10 * time.Minute) + +// Get the string associated with the key "foo" from the cache +c.Get("foo", &foo) + +``` + +#### Retrieve & Store + +Sometimes you may wish to retrieve an item from the cache, but also store a default value if the requested item doesn't exist. For example, you may wish to retrieve all users from the cache or, if they don't exist, retrieve them from the callback and add them to the cache. You may do this using the `Remember` method: + +```go +var foo int + +cache.Remember("foo", &a, 1*time.Minute, func() interface{} { + return "thinkgo" +}) +``` + +## License + This project is licensed under the `Apache 2.0 license`. \ No newline at end of file diff --git a/cache/cache.go b/cache/cache.go index 834912b..570dffa 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -1,38 +1,38 @@ -package cache - -import ( - "errors" - "fmt" -) - -var adapters = make(map[string]Store) - -// Register Register a cache adapter available by the adapter name. -func Register(name string, adapter Store) error { - if adapter == nil { - return errors.New("cache: Register adapter is nil") - } - if _, ok := adapters[name]; ok { - return errors.New("cache: Register called twice for adapter " + name) - } - adapters[name] = adapter - return nil -} - -// NewCache Create a new cache by adapter name. -func Cache(adapter interface{}) (*Repository, error) { - var store Store - switch adapter.(type) { - case string: - var ok bool - store, ok = adapters[adapter.(string)] - if !ok { - err := fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapter.(string)) - return nil, err - } - case Store: - store = adapter.(Store) - } - - return NewRepository(store), nil -} +package cache + +import ( + "errors" + "fmt" +) + +var adapters = make(map[string]Store) + +// Register Register a cache adapter available by the adapter name. +func Register(name string, adapter Store) error { + if adapter == nil { + return errors.New("cache: Register adapter is nil") + } + if _, ok := adapters[name]; ok { + return errors.New("cache: Register called twice for adapter " + name) + } + adapters[name] = adapter + return nil +} + +// NewCache Create a new cache by adapter name. +func Cache(adapter interface{}) (*Repository, error) { + var store Store + switch adapter.(type) { + case string: + var ok bool + store, ok = adapters[adapter.(string)] + if !ok { + err := fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapter.(string)) + return nil, err + } + case Store: + store = adapter.(Store) + } + + return NewRepository(store), nil +} diff --git a/cache/cache_test.go b/cache/cache_test.go index f8f88d3..5533c4f 100644 --- a/cache/cache_test.go +++ b/cache/cache_test.go @@ -1,116 +1,116 @@ -package cache - -import ( - "fmt" - "testing" - "time" - - "github.com/gomodule/redigo/redis" - "github.com/stretchr/testify/assert" -) - -type Foo struct { - Name string `json:"name"` - Age int `json:"age"` -} - -func testCache(t *testing.T, cache *Repository) { - var a int - var b string - var c Foo - - cache.Clear() - - assert.Error(t, cache.Get("a", &a)) - assert.Error(t, cache.Get("b", &b)) - - assert.NoError(t, cache.Put("a", 1, 10*time.Minute)) - assert.NoError(t, cache.Put("b", "thinkgo", 10*time.Minute)) - - assert.True(t, cache.Has("a")) - assert.True(t, cache.Has("b")) - - assert.NoError(t, cache.Get("a", &a)) - assert.Equal(t, a, 1) - assert.NoError(t, cache.Get("b", &b)) - assert.Equal(t, b, "thinkgo") - - assert.NoError(t, cache.Pull("b", &b)) - assert.Equal(t, b, "thinkgo") - assert.False(t, cache.Has("b")) - - assert.NoError(t, cache.Set("b", "think go", 10*time.Minute)) - assert.Error(t, cache.Add("b", "think go", 10*time.Minute)) - - assert.True(t, cache.Has("b")) - assert.NoError(t, cache.Forget("b")) - assert.False(t, cache.Has("b")) - - assert.NoError(t, cache.Put("c", Foo{ - Name: "thinkgo", - Age:100, - }, 10*time.Minute)) - assert.NoError(t,cache.Get("c", &c)) - fmt.Println(c) - assert.Equal(t, c.Name , "thinkgo") - assert.Equal(t, c.Age , 100) - assert.NoError(t, cache.Delete("c")) - assert.False(t, cache.Has("c")) - - _, ok := cache.GetStore().(Store) - assert.True(t, ok) - - assert.NoError(t, cache.Clear()) - assert.False(t, cache.Has("a")) - assert.False(t, cache.Has("b")) - - assert.NoError(t, cache.Remember("a", &a, 1*time.Minute, func() interface{} { - return 1000 - })) - - assert.Equal(t, a, 1000) - - assert.NoError(t,cache.Remember("b", &b, 1*time.Minute, func() interface{} { - return "hello thinkgo" - })) - - assert.Equal(t, b, "hello thinkgo") -} - -func TestMemoryCache(t *testing.T) { - Register("memory", NewMemoryStore("thinkgo")) - - cache, err := Cache("memory") - - if err != nil { - t.Error(err) - } - testCache(t, cache) -} - -func TestRedisCache(t *testing.T) { - pool := &redis.Pool{ - MaxIdle: 5, - MaxActive: 1000, - IdleTimeout: 300 * time.Second, - Wait: true, - // Other pool configuration not shown in this example. - Dial: func() (redis.Conn, error) { - c, err := redis.Dial("tcp", "127.0.0.1:6379") - if err != nil { - return nil, err - } - // if _, err := c.Do("AUTH", "123456"); err != nil { - // c.Close() - // return nil, err - // } - return c, nil - }, - } - - cache, err := Cache(NewRedisStore(pool, "thinkgo")) - if err != nil { - t.Error(err) - } - testCache(t, cache) +package cache + +import ( + "fmt" + "testing" + "time" + + "github.com/gomodule/redigo/redis" + "github.com/stretchr/testify/assert" +) + +type Foo struct { + Name string `json:"name"` + Age int `json:"age"` +} + +func testCache(t *testing.T, cache *Repository) { + var a int + var b string + var c Foo + + cache.Clear() + + assert.Error(t, cache.Get("a", &a)) + assert.Error(t, cache.Get("b", &b)) + + assert.NoError(t, cache.Put("a", 1, 10*time.Minute)) + assert.NoError(t, cache.Put("b", "thinkgo", 10*time.Minute)) + + assert.True(t, cache.Has("a")) + assert.True(t, cache.Has("b")) + + assert.NoError(t, cache.Get("a", &a)) + assert.Equal(t, a, 1) + assert.NoError(t, cache.Get("b", &b)) + assert.Equal(t, b, "thinkgo") + + assert.NoError(t, cache.Pull("b", &b)) + assert.Equal(t, b, "thinkgo") + assert.False(t, cache.Has("b")) + + assert.NoError(t, cache.Set("b", "think go", 10*time.Minute)) + assert.Error(t, cache.Add("b", "think go", 10*time.Minute)) + + assert.True(t, cache.Has("b")) + assert.NoError(t, cache.Forget("b")) + assert.False(t, cache.Has("b")) + + assert.NoError(t, cache.Put("c", Foo{ + Name: "thinkgo", + Age:100, + }, 10*time.Minute)) + assert.NoError(t,cache.Get("c", &c)) + fmt.Println(c) + assert.Equal(t, c.Name , "thinkgo") + assert.Equal(t, c.Age , 100) + assert.NoError(t, cache.Delete("c")) + assert.False(t, cache.Has("c")) + + _, ok := cache.GetStore().(Store) + assert.True(t, ok) + + assert.NoError(t, cache.Clear()) + assert.False(t, cache.Has("a")) + assert.False(t, cache.Has("b")) + + assert.NoError(t, cache.Remember("a", &a, 1*time.Minute, func() interface{} { + return 1000 + })) + + assert.Equal(t, a, 1000) + + assert.NoError(t,cache.Remember("b", &b, 1*time.Minute, func() interface{} { + return "hello thinkgo" + })) + + assert.Equal(t, b, "hello thinkgo") +} + +func TestMemoryCache(t *testing.T) { + Register("memory", NewMemoryStore("thinkgo")) + + cache, err := Cache("memory") + + if err != nil { + t.Error(err) + } + testCache(t, cache) +} + +func TestRedisCache(t *testing.T) { + pool := &redis.Pool{ + MaxIdle: 5, + MaxActive: 1000, + IdleTimeout: 300 * time.Second, + Wait: true, + // Other pool configuration not shown in this example. + Dial: func() (redis.Conn, error) { + c, err := redis.Dial("tcp", "127.0.0.1:6379") + if err != nil { + return nil, err + } + // if _, err := c.Do("AUTH", "123456"); err != nil { + // c.Close() + // return nil, err + // } + return c, nil + }, + } + + cache, err := Cache(NewRedisStore(pool, "thinkgo")) + if err != nil { + t.Error(err) + } + testCache(t, cache) } \ No newline at end of file diff --git a/cache/memory_store.go b/cache/memory_store.go index e3637bb..d571153 100644 --- a/cache/memory_store.go +++ b/cache/memory_store.go @@ -1,169 +1,169 @@ -package cache - -import ( - "errors" - "fmt" - "reflect" - "sync" - "time" -) - -type item struct { - Object interface{} - Expiration int64 -} - -// Expired Returns true if the item has expired. -func (item item) Expired() bool { - if item.Expiration == 0 { - return false - } - return time.Now().UnixNano() > item.Expiration -} - -type MemoryStore struct { - prefix string - items map[string]item - mu sync.RWMutex - cleanupTimer *time.Timer -} - -// NewStore Create a memory cache store -func NewMemoryStore(prefix string) *MemoryStore { - s := &MemoryStore{ - items: make(map[string]item), - } - return s.SetPrefix(prefix) -} - -// Get get cached value by key. -// func (s *Store) Get(key string) (interface{}, error) { -func (s *MemoryStore) Get(key string, val interface{}) error { - s.mu.RLock() - defer s.mu.RUnlock() - - item, ok := s.items[s.prefix+key] - if !ok { - return errors.New("not found") - } - - if item.Expired() { - return errors.New("expired") - } - - rv := reflect.ValueOf(val) - if rv.Kind() != reflect.Ptr || rv.IsNil() { - return errors.New("invalid unmarshal") - } - - rv = rv.Elem() - - rv.Set(reflect.ValueOf(item.Object)) - - return nil -} - -// Put set cached value with key and expire time. -func (s *MemoryStore) Put(key string, val interface{}, timeout time.Duration) error { - var e int64 - if timeout > 0 { - e = time.Now().Add(timeout).UnixNano() - } - - s.mu.RLock() - defer s.mu.RUnlock() - - s.items[s.prefix+key] = item{ - Object: val, - Expiration: e, - } - - if e > 0 { - s.DeleteExpired() - } - - return nil -} - -// Exist check cache's existence in memory. -func (s *MemoryStore) Exist(key string) bool { - s.mu.RLock() - defer s.mu.RUnlock() - - item, ok := s.items[s.prefix+key] - - if item.Expired() { - return false - } - - return ok -} - -// Forget Remove an item from the cache. -func (s *MemoryStore) Forget(key string) error { - delete(s.items, s.prefix+key) - return nil -} - -// Remove all items from the cache. -func (s *MemoryStore) Flush() error { - s.mu.RLock() - defer s.mu.RUnlock() - - s.items = map[string]item{} - - return nil -} - -// GetPrefix Get the cache key prefix. -func (s *MemoryStore) GetPrefix() string { - return s.prefix -} - -// SetPrefix Set the cache key prefix. -func (s *MemoryStore) SetPrefix(prefix string) *MemoryStore { - if len(prefix) != 0 { - s.prefix = fmt.Sprintf("%s:", prefix) - } else { - s.prefix = "" - } - return s -} - -// Delete all expired items from the cache. -func (s *MemoryStore) DeleteExpired() { - s.mu.RLock() - defer s.mu.RUnlock() - - if s.cleanupTimer != nil { - s.cleanupTimer.Stop() - } - - smallestDuration := 0 * time.Nanosecond - for key, item := range s.items { - if item.Expiration == 0 { - continue - } - // "Inlining" of expired - if item.Expired() { - delete(s.items, key) - } else { - // Find the item chronologically closest to its end-of-lifespan. - sub := item.Expiration - time.Now().UnixNano() - - if smallestDuration == 0{ - smallestDuration = time.Duration(sub)*time.Nanosecond - }else{ - if time.Duration(sub)*time.Nanosecond < smallestDuration { - smallestDuration = time.Duration(sub) * time.Nanosecond - } - } - } - } - - if smallestDuration > 0 { - s.cleanupTimer = time.AfterFunc(smallestDuration, func() { - go s.DeleteExpired() - }) - } -} +package cache + +import ( + "errors" + "fmt" + "reflect" + "sync" + "time" +) + +type item struct { + Object interface{} + Expiration int64 +} + +// Expired Returns true if the item has expired. +func (item item) Expired() bool { + if item.Expiration == 0 { + return false + } + return time.Now().UnixNano() > item.Expiration +} + +type MemoryStore struct { + prefix string + items map[string]item + mu sync.RWMutex + cleanupTimer *time.Timer +} + +// NewStore Create a memory cache store +func NewMemoryStore(prefix string) *MemoryStore { + s := &MemoryStore{ + items: make(map[string]item), + } + return s.SetPrefix(prefix) +} + +// Get get cached value by key. +// func (s *Store) Get(key string) (interface{}, error) { +func (s *MemoryStore) Get(key string, val interface{}) error { + s.mu.RLock() + defer s.mu.RUnlock() + + item, ok := s.items[s.prefix+key] + if !ok { + return errors.New("not found") + } + + if item.Expired() { + return errors.New("expired") + } + + rv := reflect.ValueOf(val) + if rv.Kind() != reflect.Ptr || rv.IsNil() { + return errors.New("invalid unmarshal") + } + + rv = rv.Elem() + + rv.Set(reflect.ValueOf(item.Object)) + + return nil +} + +// Put set cached value with key and expire time. +func (s *MemoryStore) Put(key string, val interface{}, timeout time.Duration) error { + var e int64 + if timeout > 0 { + e = time.Now().Add(timeout).UnixNano() + } + + s.mu.RLock() + defer s.mu.RUnlock() + + s.items[s.prefix+key] = item{ + Object: val, + Expiration: e, + } + + if e > 0 { + s.DeleteExpired() + } + + return nil +} + +// Exist check cache's existence in memory. +func (s *MemoryStore) Exist(key string) bool { + s.mu.RLock() + defer s.mu.RUnlock() + + item, ok := s.items[s.prefix+key] + + if item.Expired() { + return false + } + + return ok +} + +// Forget Remove an item from the cache. +func (s *MemoryStore) Forget(key string) error { + delete(s.items, s.prefix+key) + return nil +} + +// Remove all items from the cache. +func (s *MemoryStore) Flush() error { + s.mu.RLock() + defer s.mu.RUnlock() + + s.items = map[string]item{} + + return nil +} + +// GetPrefix Get the cache key prefix. +func (s *MemoryStore) GetPrefix() string { + return s.prefix +} + +// SetPrefix Set the cache key prefix. +func (s *MemoryStore) SetPrefix(prefix string) *MemoryStore { + if len(prefix) != 0 { + s.prefix = fmt.Sprintf("%s:", prefix) + } else { + s.prefix = "" + } + return s +} + +// Delete all expired items from the cache. +func (s *MemoryStore) DeleteExpired() { + s.mu.RLock() + defer s.mu.RUnlock() + + if s.cleanupTimer != nil { + s.cleanupTimer.Stop() + } + + smallestDuration := 0 * time.Nanosecond + for key, item := range s.items { + if item.Expiration == 0 { + continue + } + // "Inlining" of expired + if item.Expired() { + delete(s.items, key) + } else { + // Find the item chronologically closest to its end-of-lifespan. + sub := item.Expiration - time.Now().UnixNano() + + if smallestDuration == 0{ + smallestDuration = time.Duration(sub)*time.Nanosecond + }else{ + if time.Duration(sub)*time.Nanosecond < smallestDuration { + smallestDuration = time.Duration(sub) * time.Nanosecond + } + } + } + } + + if smallestDuration > 0 { + s.cleanupTimer = time.AfterFunc(smallestDuration, func() { + go s.DeleteExpired() + }) + } +} diff --git a/cache/memory_store_test.go b/cache/memory_store_test.go index af1fb87..7d0097e 100644 --- a/cache/memory_store_test.go +++ b/cache/memory_store_test.go @@ -1,131 +1,131 @@ -package cache - -import ( - "errors" - "testing" - "time" -) - -type CacheUser struct { - Name string - Age int -} - -func getMemoryStore() *MemoryStore { - return NewMemoryStore("cache") -} - -func TestMemoryStore(t *testing.T) { - s := getMemoryStore() - var a int - var b string - var c CacheUser - - err := s.Get("a", &a) - if err == nil { - t.Error("Getting A found value that shouldn't exist:", a) - } - - err = s.Get("b", &b) - if err == nil { - t.Error("Getting B found value that shouldn't exist:", b) - } - - s.Put("a", 1, 10*time.Minute) - s.Put("b", "thinkgo", 2*time.Minute) - - err = s.Get("a", &a) - if err != nil { - t.Error(err) - } - - if a != 1 { - t.Error("Expect: ", 1) - } - - err = s.Get("b", &b) - if err != nil { - t.Error(err) - } - - if b != "thinkgo" { - t.Error("Expect: ", "thinkgo") - } - - err = s.Put( - "user", CacheUser{ - Name: "alice", - Age: 16, - }, - 10*time.Minute, - ) - if err != nil { - t.Error(err) - } - - err = s.Get("user", &c) - if err != nil { - t.Error(err) - } - - t.Logf("user:name=%s,age=%d", c.Name, c.Age) -} - -func TestMemoryStoreDuration(t *testing.T) { - s := getMemoryStore() - var a int - - s.Put("a", 3, 20*time.Millisecond) - - <-time.After(21 * time.Millisecond) - err := s.Get("a", &a) - if err == nil { - t.Error("Found a when it should have been automatically deleted") - } -} - -func TestMemoryStoreForgetAndExist(t *testing.T) { - s := getMemoryStore() - err := s.Put("forget", "Forget me", 10*time.Minute) - if err != nil { - t.Error(err) - } - - exist := s.Exist("forget") - if exist != true { - t.Error(errors.New("Expect true")) - } - - err = s.Forget("forget") - if err != nil { - t.Error(err) - } - - exist = s.Exist("forget") - if exist == true { - t.Error(errors.New("Expect false")) - } -} - -func TestMemoryStoreFlush(t *testing.T) { - s := getMemoryStore() - err := s.Put("Flush", "Flush all", 10*time.Minute) - if err != nil { - t.Error(err) - } - - exist := s.Exist("Flush") - if exist != true { - t.Error(errors.New("Expect true")) - } - - err = s.Flush() - if err != nil { - t.Error(err) - } - - exist = s.Exist("Flush") - if exist == true { - t.Error(errors.New("Expect false")) - } -} +package cache + +import ( + "errors" + "testing" + "time" +) + +type CacheUser struct { + Name string + Age int +} + +func getMemoryStore() *MemoryStore { + return NewMemoryStore("cache") +} + +func TestMemoryStore(t *testing.T) { + s := getMemoryStore() + var a int + var b string + var c CacheUser + + err := s.Get("a", &a) + if err == nil { + t.Error("Getting A found value that shouldn't exist:", a) + } + + err = s.Get("b", &b) + if err == nil { + t.Error("Getting B found value that shouldn't exist:", b) + } + + s.Put("a", 1, 10*time.Minute) + s.Put("b", "thinkgo", 2*time.Minute) + + err = s.Get("a", &a) + if err != nil { + t.Error(err) + } + + if a != 1 { + t.Error("Expect: ", 1) + } + + err = s.Get("b", &b) + if err != nil { + t.Error(err) + } + + if b != "thinkgo" { + t.Error("Expect: ", "thinkgo") + } + + err = s.Put( + "user", CacheUser{ + Name: "alice", + Age: 16, + }, + 10*time.Minute, + ) + if err != nil { + t.Error(err) + } + + err = s.Get("user", &c) + if err != nil { + t.Error(err) + } + + t.Logf("user:name=%s,age=%d", c.Name, c.Age) +} + +func TestMemoryStoreDuration(t *testing.T) { + s := getMemoryStore() + var a int + + s.Put("a", 3, 20*time.Millisecond) + + <-time.After(21 * time.Millisecond) + err := s.Get("a", &a) + if err == nil { + t.Error("Found a when it should have been automatically deleted") + } +} + +func TestMemoryStoreForgetAndExist(t *testing.T) { + s := getMemoryStore() + err := s.Put("forget", "Forget me", 10*time.Minute) + if err != nil { + t.Error(err) + } + + exist := s.Exist("forget") + if exist != true { + t.Error(errors.New("Expect true")) + } + + err = s.Forget("forget") + if err != nil { + t.Error(err) + } + + exist = s.Exist("forget") + if exist == true { + t.Error(errors.New("Expect false")) + } +} + +func TestMemoryStoreFlush(t *testing.T) { + s := getMemoryStore() + err := s.Put("Flush", "Flush all", 10*time.Minute) + if err != nil { + t.Error(err) + } + + exist := s.Exist("Flush") + if exist != true { + t.Error(errors.New("Expect true")) + } + + err = s.Flush() + if err != nil { + t.Error(err) + } + + exist = s.Exist("Flush") + if exist == true { + t.Error(errors.New("Expect false")) + } +} diff --git a/cache/redis_store.go b/cache/redis_store.go index 6c0e599..e7a8121 100644 --- a/cache/redis_store.go +++ b/cache/redis_store.go @@ -1,116 +1,116 @@ -package cache - -import ( - "encoding/json" - "fmt" - "time" - - "github.com/gomodule/redigo/redis" -) - -type RedisStore struct { - pool *redis.Pool // redis connection pool - prefix string -} - -// NewStore Create a redis cache store -func NewRedisStore(pool *redis.Pool, prefix string) *RedisStore { - s := RedisStore{} - return s.SetPool(pool).SetPrefix(prefix) -} - -// Get get cached value by key. -func (s *RedisStore) Get(key string, val interface{}) error { - c := s.pool.Get() - defer c.Close() - - b, err := redis.Bytes(c.Do("GET", s.prefix+key)) - if err != nil { - return err - } - - return json.Unmarshal(b, val) -} - -// Put set cached value with key and expire time. -func (s *RedisStore) Put(key string, val interface{}, timeout time.Duration) error { - b, err := json.Marshal(val) - if err != nil { - return err - } - c := s.pool.Get() - defer c.Close() - _, err = c.Do("SETEX", s.prefix+key, int64(timeout/time.Second), string(b)) - return err -} - -// Exist check cache's existence in redis. -func (s *RedisStore) Exist(key string) bool { - c := s.pool.Get() - defer c.Close() - v, err := redis.Bool(c.Do("EXISTS", s.prefix+key)) - if err != nil { - return false - } - return v -} - -// Forget Remove an item from the cache. -func (s *RedisStore) Forget(key string) error { - c := s.pool.Get() - defer c.Close() - _, err := c.Do("DEL", s.prefix+key) - return err -} - -// Remove all items from the cache. -func (s *RedisStore) Flush() error { - c := s.pool.Get() - defer c.Close() - - var err error - iter := 0 - keys := []string{} - - for { - arr, err := redis.Values(c.Do("SCAN", iter, "MATCH", s.prefix+"*")) - if err != nil { - return err - } - - iter, _ = redis.Int(arr[0], nil) - k, _ := redis.Strings(arr[1], nil) - keys = append(keys, k...) - - if iter == 0 { - break - } - } - for _, key := range keys { - if _, err = c.Do("DEL", key); err != nil { - return err - } - } - return err -} - -// SetPool Get the redis pool. -func (s *RedisStore) SetPool(pool *redis.Pool) *RedisStore { - s.pool = pool - return s -} - -// GetPrefix Get the cache key prefix. -func (s *RedisStore) GetPrefix() string { - return s.prefix -} - -// SetPrefix Set the cache key prefix. -func (s *RedisStore) SetPrefix(prefix string) *RedisStore { - if len(prefix) != 0 { - s.prefix = fmt.Sprintf("%s:", prefix) - } else { - s.prefix = "" - } - return s -} +package cache + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/gomodule/redigo/redis" +) + +type RedisStore struct { + pool *redis.Pool // redis connection pool + prefix string +} + +// NewStore Create a redis cache store +func NewRedisStore(pool *redis.Pool, prefix string) *RedisStore { + s := RedisStore{} + return s.SetPool(pool).SetPrefix(prefix) +} + +// Get get cached value by key. +func (s *RedisStore) Get(key string, val interface{}) error { + c := s.pool.Get() + defer c.Close() + + b, err := redis.Bytes(c.Do("GET", s.prefix+key)) + if err != nil { + return err + } + + return json.Unmarshal(b, val) +} + +// Put set cached value with key and expire time. +func (s *RedisStore) Put(key string, val interface{}, timeout time.Duration) error { + b, err := json.Marshal(val) + if err != nil { + return err + } + c := s.pool.Get() + defer c.Close() + _, err = c.Do("SETEX", s.prefix+key, int64(timeout/time.Second), string(b)) + return err +} + +// Exist check cache's existence in redis. +func (s *RedisStore) Exist(key string) bool { + c := s.pool.Get() + defer c.Close() + v, err := redis.Bool(c.Do("EXISTS", s.prefix+key)) + if err != nil { + return false + } + return v +} + +// Forget Remove an item from the cache. +func (s *RedisStore) Forget(key string) error { + c := s.pool.Get() + defer c.Close() + _, err := c.Do("DEL", s.prefix+key) + return err +} + +// Remove all items from the cache. +func (s *RedisStore) Flush() error { + c := s.pool.Get() + defer c.Close() + + var err error + iter := 0 + keys := []string{} + + for { + arr, err := redis.Values(c.Do("SCAN", iter, "MATCH", s.prefix+"*")) + if err != nil { + return err + } + + iter, _ = redis.Int(arr[0], nil) + k, _ := redis.Strings(arr[1], nil) + keys = append(keys, k...) + + if iter == 0 { + break + } + } + for _, key := range keys { + if _, err = c.Do("DEL", key); err != nil { + return err + } + } + return err +} + +// SetPool Get the redis pool. +func (s *RedisStore) SetPool(pool *redis.Pool) *RedisStore { + s.pool = pool + return s +} + +// GetPrefix Get the cache key prefix. +func (s *RedisStore) GetPrefix() string { + return s.prefix +} + +// SetPrefix Set the cache key prefix. +func (s *RedisStore) SetPrefix(prefix string) *RedisStore { + if len(prefix) != 0 { + s.prefix = fmt.Sprintf("%s:", prefix) + } else { + s.prefix = "" + } + return s +} diff --git a/cache/redis_store_test.go b/cache/redis_store_test.go index c5cea15..174442a 100644 --- a/cache/redis_store_test.go +++ b/cache/redis_store_test.go @@ -1,134 +1,134 @@ -package cache - -import ( - "errors" - "testing" - "time" - - "github.com/gomodule/redigo/redis" -) - -func GetPool() *redis.Pool { - return &redis.Pool{ - MaxIdle: 5, - MaxActive: 1000, - IdleTimeout: 300 * time.Second, - Wait: true, - // Other pool configuration not shown in this example. - Dial: func() (redis.Conn, error) { - c, err := redis.Dial("tcp", "127.0.0.1:6379") - if err != nil { - return nil, err - } - return c, nil - }, - } -} - -func getRedisStore() *RedisStore { - pool := GetPool() - return NewRedisStore(pool, "cache") -} - -func TestRedisStoreInt(t *testing.T) { - s := getRedisStore() - err := s.Put("int", 9811, 10*time.Minute) - if err != nil { - t.Error(err) - } - - var v int - - err = s.Get("int", &v) - if err != nil { - t.Error(err) - } - - t.Logf("int:%d", v) -} - -func TestRedisStoreString(t *testing.T) { - s := getRedisStore() - err := s.Put("str", "this is a string", 10*time.Minute) - if err != nil { - t.Error(err) - } - - var str string - - err = s.Get("str", &str) - if err != nil { - t.Error(err) - } - - t.Logf("str:%s", str) -} - -func TestStoreStruct(t *testing.T) { - s := getRedisStore() - err := s.Put( - "user", CacheUser{ - Name: "alice", - Age: 16, - }, - 10*time.Minute, - ) - if err != nil { - t.Error(err) - } - - user := &CacheUser{} - - err = s.Get("user", user) - if err != nil { - t.Error(err) - } - - t.Logf("user:name=%s,age=%d", user.Name, user.Age) -} - -func TestRedisStoreForgetAndExist(t *testing.T) { - s := getRedisStore() - err := s.Put("forget", "Forget me", 10*time.Minute) - if err != nil { - t.Error(err) - } - - exist := s.Exist("forget") - if exist != true { - t.Error(errors.New("Expect true")) - } - - err = s.Forget("forget") - if err != nil { - t.Error(err) - } - - exist = s.Exist("forget") - if exist == true { - t.Error(errors.New("Expect false")) - } -} - -func TestRedisStoreFlush(t *testing.T) { - s := getRedisStore() - err := s.Put("Flush", "Flush all", 10*time.Minute) - if err != nil { - t.Error(err) - } - - exist := s.Exist("Flush") - if exist != true { - t.Error(errors.New("Expect true")) - } - - err = s.Flush() - if err != nil { - t.Error(err) - } - - exist = s.Exist("Flush") - if exist == true { - t.Error(errors.New("Expect false")) - } -} +package cache + +import ( + "errors" + "testing" + "time" + + "github.com/gomodule/redigo/redis" +) + +func GetPool() *redis.Pool { + return &redis.Pool{ + MaxIdle: 5, + MaxActive: 1000, + IdleTimeout: 300 * time.Second, + Wait: true, + // Other pool configuration not shown in this example. + Dial: func() (redis.Conn, error) { + c, err := redis.Dial("tcp", "127.0.0.1:6379") + if err != nil { + return nil, err + } + return c, nil + }, + } +} + +func getRedisStore() *RedisStore { + pool := GetPool() + return NewRedisStore(pool, "cache") +} + +func TestRedisStoreInt(t *testing.T) { + s := getRedisStore() + err := s.Put("int", 9811, 10*time.Minute) + if err != nil { + t.Error(err) + } + + var v int + + err = s.Get("int", &v) + if err != nil { + t.Error(err) + } + + t.Logf("int:%d", v) +} + +func TestRedisStoreString(t *testing.T) { + s := getRedisStore() + err := s.Put("str", "this is a string", 10*time.Minute) + if err != nil { + t.Error(err) + } + + var str string + + err = s.Get("str", &str) + if err != nil { + t.Error(err) + } + + t.Logf("str:%s", str) +} + +func TestStoreStruct(t *testing.T) { + s := getRedisStore() + err := s.Put( + "user", CacheUser{ + Name: "alice", + Age: 16, + }, + 10*time.Minute, + ) + if err != nil { + t.Error(err) + } + + user := &CacheUser{} + + err = s.Get("user", user) + if err != nil { + t.Error(err) + } + + t.Logf("user:name=%s,age=%d", user.Name, user.Age) +} + +func TestRedisStoreForgetAndExist(t *testing.T) { + s := getRedisStore() + err := s.Put("forget", "Forget me", 10*time.Minute) + if err != nil { + t.Error(err) + } + + exist := s.Exist("forget") + if exist != true { + t.Error(errors.New("Expect true")) + } + + err = s.Forget("forget") + if err != nil { + t.Error(err) + } + + exist = s.Exist("forget") + if exist == true { + t.Error(errors.New("Expect false")) + } +} + +func TestRedisStoreFlush(t *testing.T) { + s := getRedisStore() + err := s.Put("Flush", "Flush all", 10*time.Minute) + if err != nil { + t.Error(err) + } + + exist := s.Exist("Flush") + if exist != true { + t.Error(errors.New("Expect true")) + } + + err = s.Flush() + if err != nil { + t.Error(err) + } + + exist = s.Exist("Flush") + if exist == true { + t.Error(errors.New("Expect false")) + } +} diff --git a/cache/repository.go b/cache/repository.go index af637a0..6f9b444 100644 --- a/cache/repository.go +++ b/cache/repository.go @@ -1,90 +1,90 @@ -package cache - -import ( - "time" - - "github.com/pkg/errors" -) - -type Repository struct { - store Store -} - -func NewRepository(store Store) *Repository { - s := &Repository{ - store: store, - } - return s -} - -// Has Determine if an item exists in the cache. -func (r *Repository) Has(key string) bool { - return r.store.Exist(key) -} - -// Get Retrieve an item from the cache by key. -func (r *Repository) Get(key string, val interface{}) error { - err := r.store.Get(key, val) - return err -} - -// Pull Retrieve an item from the cache and delete it. -func (r *Repository) Pull(key string, val interface{}) error { - err := r.store.Get(key, val) - if err != nil { - return err - } - r.store.Forget(key) - return nil -} - -// Put Store an item in the cache. -func (r *Repository) Put(key string, val interface{}, timeout time.Duration) error { - return r.store.Put(key, val, timeout) -} - -// Set Store an item in the cache. -func (r *Repository) Set(key string, val interface{}, timeout time.Duration) error { - return r.Put(key, val, timeout) -} - -// Add Store an item in the cache if the key does not exist. -func (r *Repository) Add(key string, val interface{}, timeout time.Duration) error { - if r.store.Exist(key) { - return errors.New("the key already exists:" + key) - } - return r.store.Put(key, val, timeout) -} - -// Remember Get an item from the cache, or store the default value. -func (r *Repository) Remember(key string, val interface{}, timeout time.Duration, callback func() interface{}) error { - err := r.Get(key, val) - if err == nil { - return nil - } - - value := callback() - r.Put(key, value, timeout) - - return r.Get(key, val) -} - -// Forget Remove an item from the cache. -func (r *Repository) Forget(key string) error { - return r.store.Forget(key) -} - -// Delete Alias for the "Delete" method. -func (r *Repository) Delete(key string) error { - return r.Forget(key) -} - -// Clear Remove all items from the cache. -func (r *Repository) Clear() error { - return r.store.Flush() -} - -// GetStore Get the cache store implementation. -func (r *Repository) GetStore() Store { - return r.store -} +package cache + +import ( + "time" + + "github.com/pkg/errors" +) + +type Repository struct { + store Store +} + +func NewRepository(store Store) *Repository { + s := &Repository{ + store: store, + } + return s +} + +// Has Determine if an item exists in the cache. +func (r *Repository) Has(key string) bool { + return r.store.Exist(key) +} + +// Get Retrieve an item from the cache by key. +func (r *Repository) Get(key string, val interface{}) error { + err := r.store.Get(key, val) + return err +} + +// Pull Retrieve an item from the cache and delete it. +func (r *Repository) Pull(key string, val interface{}) error { + err := r.store.Get(key, val) + if err != nil { + return err + } + r.store.Forget(key) + return nil +} + +// Put Store an item in the cache. +func (r *Repository) Put(key string, val interface{}, timeout time.Duration) error { + return r.store.Put(key, val, timeout) +} + +// Set Store an item in the cache. +func (r *Repository) Set(key string, val interface{}, timeout time.Duration) error { + return r.Put(key, val, timeout) +} + +// Add Store an item in the cache if the key does not exist. +func (r *Repository) Add(key string, val interface{}, timeout time.Duration) error { + if r.store.Exist(key) { + return errors.New("the key already exists:" + key) + } + return r.store.Put(key, val, timeout) +} + +// Remember Get an item from the cache, or store the default value. +func (r *Repository) Remember(key string, val interface{}, timeout time.Duration, callback func() interface{}) error { + err := r.Get(key, val) + if err == nil { + return nil + } + + value := callback() + r.Put(key, value, timeout) + + return r.Get(key, val) +} + +// Forget Remove an item from the cache. +func (r *Repository) Forget(key string) error { + return r.store.Forget(key) +} + +// Delete Alias for the "Delete" method. +func (r *Repository) Delete(key string) error { + return r.Forget(key) +} + +// Clear Remove all items from the cache. +func (r *Repository) Clear() error { + return r.store.Flush() +} + +// GetStore Get the cache store implementation. +func (r *Repository) GetStore() Store { + return r.store +} diff --git a/cache/store.go b/cache/store.go index 0ac5d75..1e0aa6c 100644 --- a/cache/store.go +++ b/cache/store.go @@ -1,23 +1,23 @@ -package cache - -import ( - "time" -) - -type Store interface { - // Get get cached value by key. - Get(key string, val interface{}) error - - // Put set cached value with key and expire time. - Put(key string, val interface{}, timeout time.Duration) error - - // Exist check cache's existence in redis. - Exist(key string) bool - - // Forget Remove an item from the cache. - Forget(key string) error - - // Flush Remove all items from the cache. - Flush() error -} - +package cache + +import ( + "time" +) + +type Store interface { + // Get get cached value by key. + Get(key string, val interface{}) error + + // Put set cached value with key and expire time. + Put(key string, val interface{}, timeout time.Duration) error + + // Exist check cache's existence in redis. + Exist(key string) bool + + // Forget Remove an item from the cache. + Forget(key string) error + + // Flush Remove all items from the cache. + Flush() error +} + diff --git a/config/app.go b/config/app.go index ff7ca07..ee8126f 100644 --- a/config/app.go +++ b/config/app.go @@ -1,7 +1,7 @@ -package config - -type AppConfig struct { - Name string - Env string - Debug bool -} +package config + +type AppConfig struct { + Name string + Env string + Debug bool +} diff --git a/config/config.go b/config/config.go index ea19fb1..6b8696a 100644 --- a/config/config.go +++ b/config/config.go @@ -1,64 +1,64 @@ -package config - -import "time" - -var App *AppConfig -var Route *RouteConfig -var View *ViewConfig - -//var Database *DatabaseConfig -var Cookie *CookieConfig -var Session *SessionConfig - -func init() { - loadAppConfig() - loadViewConfig() - loadRouteConfig() - loadCookieConfig() - loadSessionConfig() -} - -func loadAppConfig() { - App = &AppConfig{ - Name: "ThinkGo", - Env: "production", - Debug: false, - } -} - -func loadRouteConfig() { - Route = &RouteConfig{ - Static: map[string]string{ - "static": "public", - "upload": "public", - }, - } -} - -func loadViewConfig() { - View = &ViewConfig{ - Path: "view", - } -} - -func loadCookieConfig() { - Cookie = &CookieConfig{ - Prefix: "", - Expires: time.Hour * 4, - Path: "/", - Domain: "", - MaxAge: 0, - Secure: false, - HttpOnly: true, - } -} - -func loadSessionConfig() { - Session = &SessionConfig{ - Driver: "file", - Lifetime: time.Hour * 4, - Encrypt: false, - Files: "temp/sessions", - CookieName: "thinkgo_sessions", - } -} +package config + +import "time" + +var App *AppConfig +var Route *RouteConfig +var View *ViewConfig + +//var Database *DatabaseConfig +var Cookie *CookieConfig +var Session *SessionConfig + +func init() { + loadAppConfig() + loadViewConfig() + loadRouteConfig() + loadCookieConfig() + loadSessionConfig() +} + +func loadAppConfig() { + App = &AppConfig{ + Name: "ThinkGo", + Env: "production", + Debug: false, + } +} + +func loadRouteConfig() { + Route = &RouteConfig{ + Static: map[string]string{ + "static": "public", + "upload": "public", + }, + } +} + +func loadViewConfig() { + View = &ViewConfig{ + Path: "view", + } +} + +func loadCookieConfig() { + Cookie = &CookieConfig{ + Prefix: "", + Expires: time.Hour * 4, + Path: "/", + Domain: "", + MaxAge: 0, + Secure: false, + HttpOnly: true, + } +} + +func loadSessionConfig() { + Session = &SessionConfig{ + Driver: "file", + Lifetime: time.Hour * 4, + Encrypt: false, + Files: "temp/sessions", + CookieName: "thinkgo_sessions", + } +} diff --git a/config/cookie.go b/config/cookie.go index b6e916c..b73602c 100644 --- a/config/cookie.go +++ b/config/cookie.go @@ -1,13 +1,13 @@ -package config - -import "time" - -type CookieConfig struct { - Prefix string - Expires time.Duration - Path string - Domain string - MaxAge int - Secure bool - HttpOnly bool -} +package config + +import "time" + +type CookieConfig struct { + Prefix string + Expires time.Duration + Path string + Domain string + MaxAge int + Secure bool + HttpOnly bool +} diff --git a/config/database.go b/config/database.go index 1a88c49..25b4ec1 100644 --- a/config/database.go +++ b/config/database.go @@ -1,18 +1,18 @@ -package config - -type DatabaseConfig struct { - Connection string - Connections map[string]Connection -} - -type Connection struct { - Driver string - Host string - Port string - Database string - Username string - Password string - Charset string - Prefix string - Engine string -} +package config + +type DatabaseConfig struct { + Connection string + Connections map[string]Connection +} + +type Connection struct { + Driver string + Host string + Port string + Database string + Username string + Password string + Charset string + Prefix string + Engine string +} diff --git a/config/route.go b/config/route.go index f7297a2..914bcc5 100644 --- a/config/route.go +++ b/config/route.go @@ -1,5 +1,5 @@ -package config - -type RouteConfig struct { - Static map[string]string -} +package config + +type RouteConfig struct { + Static map[string]string +} diff --git a/config/session.go b/config/session.go index e3864d7..a3a864d 100644 --- a/config/session.go +++ b/config/session.go @@ -1,20 +1,20 @@ -package config - -import "time" - -type SessionConfig struct { - //Default Session Driver - Driver string - - //Session Cookie Name - CookieName string - - //Session Lifetime - Lifetime time.Duration - - //Session Encryption - Encrypt bool - - //Session File Location - Files string -} +package config + +import "time" + +type SessionConfig struct { + //Default Session Driver + Driver string + + //Session Cookie Name + CookieName string + + //Session Lifetime + Lifetime time.Duration + + //Session Encryption + Encrypt bool + + //Session File Location + Files string +} diff --git a/config/view.go b/config/view.go index 0c1675f..a5533bc 100644 --- a/config/view.go +++ b/config/view.go @@ -1,5 +1,5 @@ -package config - -type ViewConfig struct { - Path string -} +package config + +type ViewConfig struct { + Path string +} diff --git a/context/cookie.go b/context/cookie.go index e5b059f..1bd0e91 100644 --- a/context/cookie.go +++ b/context/cookie.go @@ -1,109 +1,109 @@ -package context - -import ( - "errors" - "net/http" - "net/url" - "time" - - "github.com/thinkoner/thinkgo/config" -) - -type CookieConfig struct { - Prefix string - - Path string // optional - Domain string // optional - Expires time.Time // optional - RawExpires string // for reading cookies only - - // MaxAge=0 means no 'Max-Age' attribute specified. - // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0' - // MaxAge>0 means Max-Age attribute present and given in seconds - MaxAge int - Secure bool - HttpOnly bool - Raw string - Unparsed []string // Raw text of unparsed attribute-value pairs -} - -type Cookie struct { - Config *CookieConfig -} - -func (c *Cookie) Set(name interface{}, params ...interface{}) (*http.Cookie, error) { - var cookie *http.Cookie - - switch name.(type) { - case *http.Cookie: - cookie = name.(*http.Cookie) - case string: - if len(params) == 0 { - return nil, errors.New("Invalid parameters for Cookie.") - } - - value := params[0] - if _, ok := value.(string); !ok { - return nil, errors.New("Invalid parameters for Cookie.") - } - cookie = &http.Cookie{ - Name: c.Config.Prefix + name.(string), - Value: url.QueryEscape(value.(string)), - Path: c.Config.Path, - Domain: c.Config.Domain, - Expires: c.Config.Expires, - MaxAge: c.Config.MaxAge, - Secure: c.Config.Secure, - HttpOnly: c.Config.HttpOnly, - } - - if len(params) > 1 { - maxAge := params[1] - if _, ok := maxAge.(int); !ok { - return nil, errors.New("Invalid parameters for Cookie.") - } - cookie.MaxAge = maxAge.(int) - } - - if len(params) > 2 { - path := params[2] - if _, ok := path.(string); !ok { - return nil, errors.New("Invalid parameters for Cookie.") - } - cookie.Path = path.(string) - } - - if len(params) > 3 { - domain := params[3] - if _, ok := domain.(string); !ok { - return nil, errors.New("Invalid parameters for Cookie.") - } - cookie.Domain = domain.(string) - } - - if len(params) > 4 { - secure := params[4] - if _, ok := secure.(bool); !ok { - return nil, errors.New("Invalid parameters for Cookie.") - } - cookie.Secure = secure.(bool) - } - default: - return nil, errors.New("Invalid parameters for Cookie.") - } - return cookie, nil -} - -func ParseCookieHandler() *Cookie { - return &Cookie{ - Config: &CookieConfig{ - Prefix: config.Cookie.Prefix, - Path: config.Cookie.Path, - Domain: config.Cookie.Domain, - Expires: time.Now().Add(config.Cookie.Expires), - MaxAge: config.Cookie.MaxAge, - Secure: config.Cookie.Secure, - HttpOnly: config.Cookie.HttpOnly, - }, - } -} +package context + +import ( + "errors" + "net/http" + "net/url" + "time" + + "github.com/thinkoner/thinkgo/config" +) + +type CookieConfig struct { + Prefix string + + Path string // optional + Domain string // optional + Expires time.Time // optional + RawExpires string // for reading cookies only + + // MaxAge=0 means no 'Max-Age' attribute specified. + // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0' + // MaxAge>0 means Max-Age attribute present and given in seconds + MaxAge int + Secure bool + HttpOnly bool + Raw string + Unparsed []string // Raw text of unparsed attribute-value pairs +} + +type Cookie struct { + Config *CookieConfig +} + +func (c *Cookie) Set(name interface{}, params ...interface{}) (*http.Cookie, error) { + var cookie *http.Cookie + + switch name.(type) { + case *http.Cookie: + cookie = name.(*http.Cookie) + case string: + if len(params) == 0 { + return nil, errors.New("Invalid parameters for Cookie.") + } + + value := params[0] + if _, ok := value.(string); !ok { + return nil, errors.New("Invalid parameters for Cookie.") + } + cookie = &http.Cookie{ + Name: c.Config.Prefix + name.(string), + Value: url.QueryEscape(value.(string)), + Path: c.Config.Path, + Domain: c.Config.Domain, + Expires: c.Config.Expires, + MaxAge: c.Config.MaxAge, + Secure: c.Config.Secure, + HttpOnly: c.Config.HttpOnly, + } + + if len(params) > 1 { + maxAge := params[1] + if _, ok := maxAge.(int); !ok { + return nil, errors.New("Invalid parameters for Cookie.") + } + cookie.MaxAge = maxAge.(int) + } + + if len(params) > 2 { + path := params[2] + if _, ok := path.(string); !ok { + return nil, errors.New("Invalid parameters for Cookie.") + } + cookie.Path = path.(string) + } + + if len(params) > 3 { + domain := params[3] + if _, ok := domain.(string); !ok { + return nil, errors.New("Invalid parameters for Cookie.") + } + cookie.Domain = domain.(string) + } + + if len(params) > 4 { + secure := params[4] + if _, ok := secure.(bool); !ok { + return nil, errors.New("Invalid parameters for Cookie.") + } + cookie.Secure = secure.(bool) + } + default: + return nil, errors.New("Invalid parameters for Cookie.") + } + return cookie, nil +} + +func ParseCookieHandler() *Cookie { + return &Cookie{ + Config: &CookieConfig{ + Prefix: config.Cookie.Prefix, + Path: config.Cookie.Path, + Domain: config.Cookie.Domain, + Expires: time.Now().Add(config.Cookie.Expires), + MaxAge: config.Cookie.MaxAge, + Secure: config.Cookie.Secure, + HttpOnly: config.Cookie.HttpOnly, + }, + } +} diff --git a/context/file.go b/context/file.go index c6f1d5f..03d300a 100644 --- a/context/file.go +++ b/context/file.go @@ -1,38 +1,38 @@ -package context - -import ( - "io" - "mime/multipart" - "os" - "path" -) - -type File struct { - FileHeader *multipart.FileHeader -} - -func (f *File) Move(directory string, name ...string) (bool, error) { - src, err := f.FileHeader.Open() - if err != nil { - return false, err - } - defer src.Close() - - fname := f.FileHeader.Filename - - if len(name) > 0 { - fname = name[0] - } - - dst := path.Join(directory, fname) - - out, err := os.Create(dst) - if err != nil { - return false, err - } - defer out.Close() - - io.Copy(out, src) - - return true, nil -} +package context + +import ( + "io" + "mime/multipart" + "os" + "path" +) + +type File struct { + FileHeader *multipart.FileHeader +} + +func (f *File) Move(directory string, name ...string) (bool, error) { + src, err := f.FileHeader.Open() + if err != nil { + return false, err + } + defer src.Close() + + fname := f.FileHeader.Filename + + if len(name) > 0 { + fname = name[0] + } + + dst := path.Join(directory, fname) + + out, err := os.Create(dst) + if err != nil { + return false, err + } + defer out.Close() + + io.Copy(out, src) + + return true, nil +} diff --git a/context/request.go b/context/request.go index ff6dea2..9b3f813 100644 --- a/context/request.go +++ b/context/request.go @@ -1,281 +1,281 @@ -package context - -import ( - "errors" - "net/http" - "net/url" - "strings" -) - -//Request HTTP request -type Request struct { - Request *http.Request - method string - path string - query map[string]string - post map[string]string - files map[string]*File - session Session - CookieHandler *Cookie -} - -// NewRequest create a new HTTP request from *http.Request -func NewRequest(req *http.Request) *Request { - - return &Request{ - Request: req, - method: req.Method, - path: req.URL.Path, - query: parseQuery(req.URL.Query()), - post: parsePost(req), - } -} - -// GetMethod get the request method. -func (r *Request) GetMethod() string { - return r.method -} - -// GetPath get the request path. -func (r *Request) GetPath() string { - return r.path -} - -// GetHttpRequest get Current *http.Request -func (r *Request) GetHttpRequest() *http.Request { - return r.Request -} - -//IsMethod checks if the request method is of specified type. -func (r *Request) IsMethod(m string) bool { - return strings.ToUpper(m) == r.GetMethod() -} - -// Query returns a query string item from the request. -func (r *Request) Query(key string, value ...string) (string, error) { - if v, ok := r.query[key]; ok { - return v, nil - } - if len(value) > 0 { - return value[0], nil - } - return "", errors.New("named query not present") -} - -// Input returns a input item from the request. -func (r *Request) Input(key string, value ...string) (string, error) { - if v, ok := r.post[key]; ok { - return v, nil - } - - if v, ok := r.query[key]; ok { - return v, nil - } - - if len(value) > 0 { - return value[0], nil - } - return "", errors.New("named input not present") -} - -// Input returns a post item from the request. -func (r *Request) Post(key string, value ...string) (string, error) { - if v, ok := r.post[key]; ok { - return v, nil - } - - if len(value) > 0 { - return value[0], nil - } - return "", errors.New("named post not present") -} - -//Cookie Retrieve a cookie from the request. -func (r *Request) Cookie(key string, value ...string) (string, error) { - var err error - key = r.CookieHandler.Config.Prefix + key - cookie, err := r.Request.Cookie(key) - if err == nil { - c, _ := url.QueryUnescape(cookie.Value) - return c, err - } - if len(value) > 0 { - return value[0], nil - } - return "", err -} - -// File returns a file from the request. -func (r *Request) File(key string) (*File, error) { - if f, ok := r.files[key]; ok { - return f, nil - } - - _, fh, err := r.Request.FormFile(key) - if err != nil { - return nil, err - } - r.files[key] = &File{fh} - - return r.files[key], nil -} - -// AllFiles returns all files from the request. -func (r *Request) AllFiles() (map[string]*File, error) { - err := r.Request.ParseMultipartForm(32 << 20) - if err != nil { - return nil, err - } - if r.Request.MultipartForm != nil || r.Request.MultipartForm.File != nil { - for key, fh := range r.Request.MultipartForm.File { - r.files[key] = &File{fh[0]} - } - } - return r.files, nil -} - -// All get all of the input and query for the request. -func (r *Request) All(keys ...string) map[string]string { - all := mergeForm(r.query, r.post) - - if len(keys) == 0 { - return all - } - - result := make(map[string]string) - - for _, key := range keys { - if v, ok := all[key]; ok { - result[key] = v - } else { - result[key] = "" - } - } - - return result -} - -//Only get a subset of the items from the input data. -func (r *Request) Only(keys ...string) map[string]string { - all := r.All() - - result := make(map[string]string) - - for _, key := range keys { - if v, ok := all[key]; ok { - result[key] = v - } - } - - return result -} - -//Except Get all of the input except for a specified array of items. -func (r *Request) Except(keys ...string) map[string]string { - all := r.All() - - for _, key := range keys { - if _, ok := all[key]; ok { - delete(all, key) - } - } - - return all -} - -// Has Determine if the request contains a given input item key. -func (r *Request) Exists(keys ...string) bool { - all := r.All() - - for _, key := range keys { - if _, ok := all[key]; !ok { - return false - } - } - - return true -} - -// Filled Determine if the request contains a non-empty value for an input item. -func (r *Request) Has(keys ...string) bool { - all := r.All() - - for _, key := range keys { - if _, ok := all[key]; !ok { - return false - } - if len(all[key]) == 0 { - return false - } - } - - return true -} - -//Url get the URL (no query string) for the request. -func (r *Request) Url() string { - return r.Request.URL.Path -} - -// FullUrl get the full URL for the request. -func (r *Request) FullUrl() string { - return r.Url() + "?" + r.Request.URL.RawQuery -} - -// Path get the current path info for the request. -func (r *Request) Path() string { - return r.path -} - -// Method get the current method for the request. -func (r *Request) Method() string { - return r.method -} - -// Session get the session associated with the request. -func (r *Request) Session() Session { - return r.session -} - -// Session set the session associated with the request. -func (r *Request) SetSession(s Session) { - r.session = s -} - -func parseQuery(q url.Values) map[string]string { - query := make(map[string]string) - for k, v := range q { - query[k] = v[0] - } - return query -} - -func parsePost(r *http.Request) map[string]string { - post := make(map[string]string) - - r.ParseForm() - for k, v := range r.PostForm { - post[k] = v[0] - } - - r.ParseMultipartForm(32 << 20) - if r.MultipartForm != nil { - for k, v := range r.MultipartForm.Value { - post[k] = v[0] - } - } - - return post -} - -func mergeForm(slices ...map[string]string) map[string]string { - r := make(map[string]string) - - for _, slice := range slices { - for k, v := range slice { - r[k] = v - } - } - - return r -} +package context + +import ( + "errors" + "net/http" + "net/url" + "strings" +) + +//Request HTTP request +type Request struct { + Request *http.Request + method string + path string + query map[string]string + post map[string]string + files map[string]*File + session Session + CookieHandler *Cookie +} + +// NewRequest create a new HTTP request from *http.Request +func NewRequest(req *http.Request) *Request { + + return &Request{ + Request: req, + method: req.Method, + path: req.URL.Path, + query: parseQuery(req.URL.Query()), + post: parsePost(req), + } +} + +// GetMethod get the request method. +func (r *Request) GetMethod() string { + return r.method +} + +// GetPath get the request path. +func (r *Request) GetPath() string { + return r.path +} + +// GetHttpRequest get Current *http.Request +func (r *Request) GetHttpRequest() *http.Request { + return r.Request +} + +//IsMethod checks if the request method is of specified type. +func (r *Request) IsMethod(m string) bool { + return strings.ToUpper(m) == r.GetMethod() +} + +// Query returns a query string item from the request. +func (r *Request) Query(key string, value ...string) (string, error) { + if v, ok := r.query[key]; ok { + return v, nil + } + if len(value) > 0 { + return value[0], nil + } + return "", errors.New("named query not present") +} + +// Input returns a input item from the request. +func (r *Request) Input(key string, value ...string) (string, error) { + if v, ok := r.post[key]; ok { + return v, nil + } + + if v, ok := r.query[key]; ok { + return v, nil + } + + if len(value) > 0 { + return value[0], nil + } + return "", errors.New("named input not present") +} + +// Input returns a post item from the request. +func (r *Request) Post(key string, value ...string) (string, error) { + if v, ok := r.post[key]; ok { + return v, nil + } + + if len(value) > 0 { + return value[0], nil + } + return "", errors.New("named post not present") +} + +//Cookie Retrieve a cookie from the request. +func (r *Request) Cookie(key string, value ...string) (string, error) { + var err error + key = r.CookieHandler.Config.Prefix + key + cookie, err := r.Request.Cookie(key) + if err == nil { + c, _ := url.QueryUnescape(cookie.Value) + return c, err + } + if len(value) > 0 { + return value[0], nil + } + return "", err +} + +// File returns a file from the request. +func (r *Request) File(key string) (*File, error) { + if f, ok := r.files[key]; ok { + return f, nil + } + + _, fh, err := r.Request.FormFile(key) + if err != nil { + return nil, err + } + r.files[key] = &File{fh} + + return r.files[key], nil +} + +// AllFiles returns all files from the request. +func (r *Request) AllFiles() (map[string]*File, error) { + err := r.Request.ParseMultipartForm(32 << 20) + if err != nil { + return nil, err + } + if r.Request.MultipartForm != nil || r.Request.MultipartForm.File != nil { + for key, fh := range r.Request.MultipartForm.File { + r.files[key] = &File{fh[0]} + } + } + return r.files, nil +} + +// All get all of the input and query for the request. +func (r *Request) All(keys ...string) map[string]string { + all := mergeForm(r.query, r.post) + + if len(keys) == 0 { + return all + } + + result := make(map[string]string) + + for _, key := range keys { + if v, ok := all[key]; ok { + result[key] = v + } else { + result[key] = "" + } + } + + return result +} + +//Only get a subset of the items from the input data. +func (r *Request) Only(keys ...string) map[string]string { + all := r.All() + + result := make(map[string]string) + + for _, key := range keys { + if v, ok := all[key]; ok { + result[key] = v + } + } + + return result +} + +//Except Get all of the input except for a specified array of items. +func (r *Request) Except(keys ...string) map[string]string { + all := r.All() + + for _, key := range keys { + if _, ok := all[key]; ok { + delete(all, key) + } + } + + return all +} + +// Has Determine if the request contains a given input item key. +func (r *Request) Exists(keys ...string) bool { + all := r.All() + + for _, key := range keys { + if _, ok := all[key]; !ok { + return false + } + } + + return true +} + +// Filled Determine if the request contains a non-empty value for an input item. +func (r *Request) Has(keys ...string) bool { + all := r.All() + + for _, key := range keys { + if _, ok := all[key]; !ok { + return false + } + if len(all[key]) == 0 { + return false + } + } + + return true +} + +//Url get the URL (no query string) for the request. +func (r *Request) Url() string { + return r.Request.URL.Path +} + +// FullUrl get the full URL for the request. +func (r *Request) FullUrl() string { + return r.Url() + "?" + r.Request.URL.RawQuery +} + +// Path get the current path info for the request. +func (r *Request) Path() string { + return r.path +} + +// Method get the current method for the request. +func (r *Request) Method() string { + return r.method +} + +// Session get the session associated with the request. +func (r *Request) Session() Session { + return r.session +} + +// Session set the session associated with the request. +func (r *Request) SetSession(s Session) { + r.session = s +} + +func parseQuery(q url.Values) map[string]string { + query := make(map[string]string) + for k, v := range q { + query[k] = v[0] + } + return query +} + +func parsePost(r *http.Request) map[string]string { + post := make(map[string]string) + + r.ParseForm() + for k, v := range r.PostForm { + post[k] = v[0] + } + + r.ParseMultipartForm(32 << 20) + if r.MultipartForm != nil { + for k, v := range r.MultipartForm.Value { + post[k] = v[0] + } + } + + return post +} + +func mergeForm(slices ...map[string]string) map[string]string { + r := make(map[string]string) + + for _, slice := range slices { + for k, v := range slice { + r[k] = v + } + } + + return r +} diff --git a/context/response.go b/context/response.go index 39e6978..8a78de9 100644 --- a/context/response.go +++ b/context/response.go @@ -1,126 +1,126 @@ -package context - -import ( - "net/http" -) - -type Response struct { - // Writer context.ResponseWriter - contentType string - charset string - code int - content string - cookies map[string]*http.Cookie - CookieHandler *Cookie - Header *http.Header -} - -// GetContentType sets the Content-Type on the response. -func (r *Response) SetContentType(val string) *Response { - r.contentType = val - return r -} - -// GetContentType sets the Charset on the response. -func (r *Response) SetCharset(val string) *Response { - r.charset = val - return r -} - -// SetCode sets the status code on the response. -func (r *Response) SetCode(val int) *Response { - r.code = val - return r -} - -// SetContent sets the content on the response. -func (r *Response) SetContent(val string) *Response { - r.content = val - return r -} - -// GetContentType get the Content-Type on the response. -func (r *Response) GetContentType() string { - return r.contentType -} - -// GetContentType get the Charset on the response. -func (r *Response) GetCharset() string { - return r.charset -} - -// GetCode get the response status code. -func (r *Response) GetCode() int { - return r.code -} - -// GetCode get the response content. -func (r *Response) GetContent() string { - return r.content -} - -// Cookie Add a cookie to the response. -func (r *Response) Cookie(name interface{}, params ...interface{}) error { - cookie, err := r.CookieHandler.Set(name, params...) - - if err != nil { - if r.cookies == nil { - r.cookies = make(map[string]*http.Cookie) - } - r.cookies[cookie.Name] = cookie - } - - return err -} - -// Send Sends HTTP headers and content. -func (r *Response) Send(w http.ResponseWriter) { - for _, cookie := range r.cookies { - http.SetCookie(w, cookie) - } - for key, value := range *r.Header { - for _, val := range value { - w.Header().Add(key, val) - } - } - w.Header().Set("Content-Type", r.GetContentType()+";"+" charset="+r.GetCharset()) - // r.Header.Write(w) - w.WriteHeader(r.GetCode()) - w.Write([]byte(r.GetContent())) -} - -// NewResponse Create a new HTTP Response -func NewResponse() *Response { - r := &Response{ - Header: &http.Header{}, - } - r.SetContentType("text/html") - r.SetCharset("utf-8") - r.SetCode(http.StatusOK) - r.CookieHandler = ParseCookieHandler() - return r -} - -// NotFoundResponse Create a new HTTP NotFoundResponse -func NotFoundResponse() *Response { - r := NewResponse() - r.SetContent("Not Found") - r.SetCode(http.StatusNotFound) - return r -} - -// NotFoundResponse Create a new HTTP Error Response -func ErrorResponse() *Response { - r := NewResponse() - r.SetContent("Server Error") - r.SetCode(http.StatusInternalServerError) - return r -} - -// Redirect Create a new HTTP Redirect Response -func Redirect(to string) *Response { - r := NewResponse() - r.Header.Set("Location", to) - r.SetCode(http.StatusMovedPermanently) - return r -} +package context + +import ( + "net/http" +) + +type Response struct { + // Writer context.ResponseWriter + contentType string + charset string + code int + content string + cookies map[string]*http.Cookie + CookieHandler *Cookie + Header *http.Header +} + +// GetContentType sets the Content-Type on the response. +func (r *Response) SetContentType(val string) *Response { + r.contentType = val + return r +} + +// GetContentType sets the Charset on the response. +func (r *Response) SetCharset(val string) *Response { + r.charset = val + return r +} + +// SetCode sets the status code on the response. +func (r *Response) SetCode(val int) *Response { + r.code = val + return r +} + +// SetContent sets the content on the response. +func (r *Response) SetContent(val string) *Response { + r.content = val + return r +} + +// GetContentType get the Content-Type on the response. +func (r *Response) GetContentType() string { + return r.contentType +} + +// GetContentType get the Charset on the response. +func (r *Response) GetCharset() string { + return r.charset +} + +// GetCode get the response status code. +func (r *Response) GetCode() int { + return r.code +} + +// GetCode get the response content. +func (r *Response) GetContent() string { + return r.content +} + +// Cookie Add a cookie to the response. +func (r *Response) Cookie(name interface{}, params ...interface{}) error { + cookie, err := r.CookieHandler.Set(name, params...) + + if err != nil { + if r.cookies == nil { + r.cookies = make(map[string]*http.Cookie) + } + r.cookies[cookie.Name] = cookie + } + + return err +} + +// Send Sends HTTP headers and content. +func (r *Response) Send(w http.ResponseWriter) { + for _, cookie := range r.cookies { + http.SetCookie(w, cookie) + } + for key, value := range *r.Header { + for _, val := range value { + w.Header().Add(key, val) + } + } + w.Header().Set("Content-Type", r.GetContentType()+";"+" charset="+r.GetCharset()) + // r.Header.Write(w) + w.WriteHeader(r.GetCode()) + w.Write([]byte(r.GetContent())) +} + +// NewResponse Create a new HTTP Response +func NewResponse() *Response { + r := &Response{ + Header: &http.Header{}, + } + r.SetContentType("text/html") + r.SetCharset("utf-8") + r.SetCode(http.StatusOK) + r.CookieHandler = ParseCookieHandler() + return r +} + +// NotFoundResponse Create a new HTTP NotFoundResponse +func NotFoundResponse() *Response { + r := NewResponse() + r.SetContent("Not Found") + r.SetCode(http.StatusNotFound) + return r +} + +// NotFoundResponse Create a new HTTP Error Response +func ErrorResponse() *Response { + r := NewResponse() + r.SetContent("Server Error") + r.SetCode(http.StatusInternalServerError) + return r +} + +// Redirect Create a new HTTP Redirect Response +func Redirect(to string) *Response { + r := NewResponse() + r.Header.Set("Location", to) + r.SetCode(http.StatusMovedPermanently) + return r +} diff --git a/context/session.go b/context/session.go index 04763fb..30c4724 100644 --- a/context/session.go +++ b/context/session.go @@ -1,17 +1,17 @@ -package context - -type Session interface { - Get(name string, value ...interface{}) interface{} - - Set(name string, value interface{}) - - All() map[string]interface{} - - Remove(name string) interface{} - - Forget(names ...string) - - Clear() - - Save() -} +package context + +type Session interface { + Get(name string, value ...interface{}) interface{} + + Set(name string, value interface{}) + + All() map[string]interface{} + + Remove(name string) interface{} + + Forget(names ...string) + + Clear() + + Save() +} diff --git a/coverage.txt b/coverage.txt index 6fa8972..ce5d6b1 100644 --- a/coverage.txt +++ b/coverage.txt @@ -1,120 +1,120 @@ -mode: atomic -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:17.33,18.26 1 17 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:21.2,21.48 1 15 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:18.26,20.3 1 2 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:32.37,37.2 2 4 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:41.56,46.9 4 6 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:50.2,50.20 1 4 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:54.2,55.44 2 3 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:59.2,63.12 3 3 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:46.9,48.3 1 2 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:50.20,52.3 1 1 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:55.44,57.3 1 0 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:67.79,69.17 2 6 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:73.2,81.11 4 6 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:85.2,85.12 1 6 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:69.17,71.3 1 6 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:81.11,83.3 1 6 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:89.40,95.20 4 4 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:99.2,99.11 1 4 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:95.20,97.3 1 0 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:103.42,106.2 2 1 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:109.31,116.2 4 1 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:119.36,121.2 1 0 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:124.49,125.22 1 4 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:130.2,130.10 1 4 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:125.22,127.3 1 4 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:127.8,129.3 1 0 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:134.33,138.27 3 6 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:142.2,143.33 2 6 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:159.2,159.26 1 6 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:138.27,140.3 1 0 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:143.33,144.27 1 9 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:148.3,148.21 1 9 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:144.27,145.12 1 0 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:148.21,150.4 1 0 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:150.9,153.61 2 9 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:153.61,155.5 1 0 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:159.26,160.60 1 0 -github.com\thinkoner\thinkgo\cache\memory\memory_store.go:160.60,162.4 1 0 -github.com\thinkoner\thinkgo\log\log.go:5.13,7.2 1 1 -github.com\thinkoner\thinkgo\log\log.go:10.59,12.2 1 1 -github.com\thinkoner\thinkgo\log\log.go:15.58,17.2 1 1 -github.com\thinkoner\thinkgo\log\log.go:20.60,22.2 1 1 -github.com\thinkoner\thinkgo\log\log.go:25.58,27.2 1 1 -github.com\thinkoner\thinkgo\log\log.go:30.59,32.2 1 1 -github.com\thinkoner\thinkgo\log\log.go:35.58,37.2 1 1 -github.com\thinkoner\thinkgo\log\log.go:40.59,42.2 1 1 -github.com\thinkoner\thinkgo\log\log.go:45.59,47.2 1 1 -github.com\thinkoner\thinkgo\log\logger.go:23.37,29.2 2 1 -github.com\thinkoner\thinkgo\log\logger.go:32.51,34.2 1 0 -github.com\thinkoner\thinkgo\log\logger.go:37.52,40.2 2 0 -github.com\thinkoner\thinkgo\log\logger.go:43.60,46.2 2 1 -github.com\thinkoner\thinkgo\log\logger.go:49.36,51.18 2 0 -github.com\thinkoner\thinkgo\log\logger.go:51.18,53.3 1 0 -github.com\thinkoner\thinkgo\log\logger.go:57.63,59.40 2 0 -github.com\thinkoner\thinkgo\log\logger.go:62.2,62.15 1 0 -github.com\thinkoner\thinkgo\log\logger.go:59.40,61.3 1 0 -github.com\thinkoner\thinkgo\log\logger.go:66.47,68.59 2 0 -github.com\thinkoner\thinkgo\log\logger.go:71.2,71.16 1 0 -github.com\thinkoner\thinkgo\log\logger.go:68.59,70.3 1 0 -github.com\thinkoner\thinkgo\log\logger.go:74.91,75.32 1 8 -github.com\thinkoner\thinkgo\log\logger.go:79.2,80.16 2 8 -github.com\thinkoner\thinkgo\log\logger.go:84.2,85.59 2 8 -github.com\thinkoner\thinkgo\log\logger.go:92.2,92.17 1 8 -github.com\thinkoner\thinkgo\log\logger.go:96.2,96.16 1 8 -github.com\thinkoner\thinkgo\log\logger.go:100.2,108.59 2 8 -github.com\thinkoner\thinkgo\log\logger.go:115.2,115.18 1 8 -github.com\thinkoner\thinkgo\log\logger.go:75.32,77.3 1 1 -github.com\thinkoner\thinkgo\log\logger.go:80.16,82.3 1 0 -github.com\thinkoner\thinkgo\log\logger.go:85.59,87.48 2 8 -github.com\thinkoner\thinkgo\log\logger.go:87.48,89.9 2 8 -github.com\thinkoner\thinkgo\log\logger.go:92.17,94.3 1 0 -github.com\thinkoner\thinkgo\log\logger.go:96.16,98.3 1 0 -github.com\thinkoner\thinkgo\log\logger.go:108.59,110.18 2 8 -github.com\thinkoner\thinkgo\log\logger.go:110.18,111.9 1 0 -github.com\thinkoner\thinkgo\log\logger.go:119.76,121.2 1 1 -github.com\thinkoner\thinkgo\log\logger.go:124.75,126.2 1 1 -github.com\thinkoner\thinkgo\log\logger.go:129.77,131.2 1 1 -github.com\thinkoner\thinkgo\log\logger.go:134.75,136.2 1 1 -github.com\thinkoner\thinkgo\log\logger.go:139.76,141.2 1 1 -github.com\thinkoner\thinkgo\log\logger.go:144.75,146.2 1 1 -github.com\thinkoner\thinkgo\log\logger.go:149.76,151.2 1 1 -github.com\thinkoner\thinkgo\log\logger.go:154.76,156.2 1 1 -github.com\thinkoner\thinkgo\log\logger.go:159.46,162.9 3 8 -github.com\thinkoner\thinkgo\log\logger.go:165.2,165.15 1 8 -github.com\thinkoner\thinkgo\log\logger.go:162.9,164.3 1 0 -github.com\thinkoner\thinkgo\cache\store.go:26.49,27.20 1 1 -github.com\thinkoner\thinkgo\cache\store.go:30.2,30.33 1 1 -github.com\thinkoner\thinkgo\cache\store.go:33.2,34.12 2 1 -github.com\thinkoner\thinkgo\cache\store.go:27.20,29.3 1 0 -github.com\thinkoner\thinkgo\cache\store.go:30.33,32.3 1 0 -github.com\thinkoner\thinkgo\cache\store.go:38.51,40.24 2 2 -github.com\thinkoner\thinkgo\cache\store.go:52.2,52.19 1 1 -github.com\thinkoner\thinkgo\cache\store.go:41.14,43.10 2 1 -github.com\thinkoner\thinkgo\cache\store.go:47.3,47.20 1 1 -github.com\thinkoner\thinkgo\cache\store.go:48.13,49.26 1 1 -github.com\thinkoner\thinkgo\cache\store.go:43.10,46.4 2 0 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:17.55,20.2 2 5 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:23.56,28.16 4 3 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:32.2,32.31 1 0 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:28.16,30.3 1 3 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:36.79,38.16 2 5 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:41.2,44.12 4 5 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:38.16,40.3 1 0 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:48.40,52.16 4 4 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:55.2,55.10 1 0 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:52.16,54.3 1 4 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:59.42,64.2 4 1 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:67.31,71.16 4 1 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:74.2,74.27 1 0 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:79.2,79.12 1 0 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:71.16,73.3 1 1 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:74.27,75.44 1 0 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:75.44,77.4 1 0 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:83.50,86.2 2 5 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:89.36,91.2 1 0 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:94.49,95.22 1 5 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:100.2,100.10 1 5 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:95.22,97.3 1 5 -github.com\thinkoner\thinkgo\cache\redis\redis_store.go:97.8,99.3 1 0 +mode: atomic +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:17.33,18.26 1 17 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:21.2,21.48 1 15 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:18.26,20.3 1 2 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:32.37,37.2 2 4 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:41.56,46.9 4 6 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:50.2,50.20 1 4 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:54.2,55.44 2 3 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:59.2,63.12 3 3 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:46.9,48.3 1 2 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:50.20,52.3 1 1 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:55.44,57.3 1 0 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:67.79,69.17 2 6 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:73.2,81.11 4 6 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:85.2,85.12 1 6 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:69.17,71.3 1 6 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:81.11,83.3 1 6 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:89.40,95.20 4 4 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:99.2,99.11 1 4 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:95.20,97.3 1 0 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:103.42,106.2 2 1 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:109.31,116.2 4 1 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:119.36,121.2 1 0 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:124.49,125.22 1 4 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:130.2,130.10 1 4 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:125.22,127.3 1 4 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:127.8,129.3 1 0 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:134.33,138.27 3 6 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:142.2,143.33 2 6 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:159.2,159.26 1 6 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:138.27,140.3 1 0 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:143.33,144.27 1 9 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:148.3,148.21 1 9 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:144.27,145.12 1 0 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:148.21,150.4 1 0 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:150.9,153.61 2 9 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:153.61,155.5 1 0 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:159.26,160.60 1 0 +github.com\thinkoner\thinkgo\cache\memory\memory_store.go:160.60,162.4 1 0 +github.com\thinkoner\thinkgo\log\log.go:5.13,7.2 1 1 +github.com\thinkoner\thinkgo\log\log.go:10.59,12.2 1 1 +github.com\thinkoner\thinkgo\log\log.go:15.58,17.2 1 1 +github.com\thinkoner\thinkgo\log\log.go:20.60,22.2 1 1 +github.com\thinkoner\thinkgo\log\log.go:25.58,27.2 1 1 +github.com\thinkoner\thinkgo\log\log.go:30.59,32.2 1 1 +github.com\thinkoner\thinkgo\log\log.go:35.58,37.2 1 1 +github.com\thinkoner\thinkgo\log\log.go:40.59,42.2 1 1 +github.com\thinkoner\thinkgo\log\log.go:45.59,47.2 1 1 +github.com\thinkoner\thinkgo\log\logger.go:23.37,29.2 2 1 +github.com\thinkoner\thinkgo\log\logger.go:32.51,34.2 1 0 +github.com\thinkoner\thinkgo\log\logger.go:37.52,40.2 2 0 +github.com\thinkoner\thinkgo\log\logger.go:43.60,46.2 2 1 +github.com\thinkoner\thinkgo\log\logger.go:49.36,51.18 2 0 +github.com\thinkoner\thinkgo\log\logger.go:51.18,53.3 1 0 +github.com\thinkoner\thinkgo\log\logger.go:57.63,59.40 2 0 +github.com\thinkoner\thinkgo\log\logger.go:62.2,62.15 1 0 +github.com\thinkoner\thinkgo\log\logger.go:59.40,61.3 1 0 +github.com\thinkoner\thinkgo\log\logger.go:66.47,68.59 2 0 +github.com\thinkoner\thinkgo\log\logger.go:71.2,71.16 1 0 +github.com\thinkoner\thinkgo\log\logger.go:68.59,70.3 1 0 +github.com\thinkoner\thinkgo\log\logger.go:74.91,75.32 1 8 +github.com\thinkoner\thinkgo\log\logger.go:79.2,80.16 2 8 +github.com\thinkoner\thinkgo\log\logger.go:84.2,85.59 2 8 +github.com\thinkoner\thinkgo\log\logger.go:92.2,92.17 1 8 +github.com\thinkoner\thinkgo\log\logger.go:96.2,96.16 1 8 +github.com\thinkoner\thinkgo\log\logger.go:100.2,108.59 2 8 +github.com\thinkoner\thinkgo\log\logger.go:115.2,115.18 1 8 +github.com\thinkoner\thinkgo\log\logger.go:75.32,77.3 1 1 +github.com\thinkoner\thinkgo\log\logger.go:80.16,82.3 1 0 +github.com\thinkoner\thinkgo\log\logger.go:85.59,87.48 2 8 +github.com\thinkoner\thinkgo\log\logger.go:87.48,89.9 2 8 +github.com\thinkoner\thinkgo\log\logger.go:92.17,94.3 1 0 +github.com\thinkoner\thinkgo\log\logger.go:96.16,98.3 1 0 +github.com\thinkoner\thinkgo\log\logger.go:108.59,110.18 2 8 +github.com\thinkoner\thinkgo\log\logger.go:110.18,111.9 1 0 +github.com\thinkoner\thinkgo\log\logger.go:119.76,121.2 1 1 +github.com\thinkoner\thinkgo\log\logger.go:124.75,126.2 1 1 +github.com\thinkoner\thinkgo\log\logger.go:129.77,131.2 1 1 +github.com\thinkoner\thinkgo\log\logger.go:134.75,136.2 1 1 +github.com\thinkoner\thinkgo\log\logger.go:139.76,141.2 1 1 +github.com\thinkoner\thinkgo\log\logger.go:144.75,146.2 1 1 +github.com\thinkoner\thinkgo\log\logger.go:149.76,151.2 1 1 +github.com\thinkoner\thinkgo\log\logger.go:154.76,156.2 1 1 +github.com\thinkoner\thinkgo\log\logger.go:159.46,162.9 3 8 +github.com\thinkoner\thinkgo\log\logger.go:165.2,165.15 1 8 +github.com\thinkoner\thinkgo\log\logger.go:162.9,164.3 1 0 +github.com\thinkoner\thinkgo\cache\store.go:26.49,27.20 1 1 +github.com\thinkoner\thinkgo\cache\store.go:30.2,30.33 1 1 +github.com\thinkoner\thinkgo\cache\store.go:33.2,34.12 2 1 +github.com\thinkoner\thinkgo\cache\store.go:27.20,29.3 1 0 +github.com\thinkoner\thinkgo\cache\store.go:30.33,32.3 1 0 +github.com\thinkoner\thinkgo\cache\store.go:38.51,40.24 2 2 +github.com\thinkoner\thinkgo\cache\store.go:52.2,52.19 1 1 +github.com\thinkoner\thinkgo\cache\store.go:41.14,43.10 2 1 +github.com\thinkoner\thinkgo\cache\store.go:47.3,47.20 1 1 +github.com\thinkoner\thinkgo\cache\store.go:48.13,49.26 1 1 +github.com\thinkoner\thinkgo\cache\store.go:43.10,46.4 2 0 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:17.55,20.2 2 5 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:23.56,28.16 4 3 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:32.2,32.31 1 0 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:28.16,30.3 1 3 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:36.79,38.16 2 5 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:41.2,44.12 4 5 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:38.16,40.3 1 0 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:48.40,52.16 4 4 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:55.2,55.10 1 0 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:52.16,54.3 1 4 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:59.42,64.2 4 1 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:67.31,71.16 4 1 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:74.2,74.27 1 0 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:79.2,79.12 1 0 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:71.16,73.3 1 1 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:74.27,75.44 1 0 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:75.44,77.4 1 0 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:83.50,86.2 2 5 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:89.36,91.2 1 0 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:94.49,95.22 1 5 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:100.2,100.10 1 5 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:95.22,97.3 1 5 +github.com\thinkoner\thinkgo\cache\redis\redis_store.go:97.8,99.3 1 0 diff --git a/filesystem/fs.go b/filesystem/fs.go index 0a813ed..e139ad0 100644 --- a/filesystem/fs.go +++ b/filesystem/fs.go @@ -1,63 +1,63 @@ -package filesystem - -import ( - "io" - "net/http" - "os" -) - -type JustFileSystem struct { - fs http.FileSystem - readDirBatchSize int -} - -func NewFileFileSystem(root string, listDirectory bool) http.FileSystem { - fs := http.Dir(root) - if listDirectory { - return fs - } - return &JustFileSystem{fs, 2} -} - -func (fs JustFileSystem) Open(name string) (http.File, error) { - f, err := fs.fs.Open(name) - if err != nil { - return nil, err - } - return neuteredStatFile{ - File: f, - readDirBatchSize: fs.readDirBatchSize, - }, nil -} - -type neuteredStatFile struct { - http.File - readDirBatchSize int -} - -func (e neuteredStatFile) Stat() (os.FileInfo, error) { - s, err := e.File.Stat() - if err != nil { - return nil, err - } - if s.IsDir() { - LOOP: - for { - fl, err := e.File.Readdir(e.readDirBatchSize) - switch err { - case io.EOF: - break LOOP - case nil: - for _, f := range fl { - if f.Name() == "index.html" { - return s, err - } - } - default: - return nil, err - } - } - return nil, os.ErrNotExist - } - return s, err -} +package filesystem + +import ( + "io" + "net/http" + "os" +) + +type JustFileSystem struct { + fs http.FileSystem + readDirBatchSize int +} + +func NewFileFileSystem(root string, listDirectory bool) http.FileSystem { + fs := http.Dir(root) + if listDirectory { + return fs + } + return &JustFileSystem{fs, 2} +} + +func (fs JustFileSystem) Open(name string) (http.File, error) { + f, err := fs.fs.Open(name) + if err != nil { + return nil, err + } + return neuteredStatFile{ + File: f, + readDirBatchSize: fs.readDirBatchSize, + }, nil +} + +type neuteredStatFile struct { + http.File + readDirBatchSize int +} + +func (e neuteredStatFile) Stat() (os.FileInfo, error) { + s, err := e.File.Stat() + if err != nil { + return nil, err + } + if s.IsDir() { + LOOP: + for { + fl, err := e.File.Readdir(e.readDirBatchSize) + switch err { + case io.EOF: + break LOOP + case nil: + for _, f := range fl { + if f.Name() == "index.html" { + return s, err + } + } + default: + return nil, err + } + } + return nil, os.ErrNotExist + } + return s, err +} diff --git a/filesystem/utils.go b/filesystem/utils.go index 211e909..f6e6c23 100644 --- a/filesystem/utils.go +++ b/filesystem/utils.go @@ -1,74 +1,74 @@ -package filesystem - -import ( - "io/ioutil" - "os" - "path" - "path/filepath" - "sync" - "time" -) - -var lock sync.RWMutex - -func Exists(p ...string) (bool, error) { - _, err := os.Stat(path.Join(p...)) - if err == nil { - return true, nil - } - if os.IsNotExist(err) { - return false, nil - } - return false, err -} - -func Get(path string) ([]byte, error) { - lock.Lock() - defer lock.Unlock() - - var b []byte - - f, err := os.OpenFile(path, os.O_RDWR, 0600) - defer f.Close() - - if err != nil { - return b, err - } - - b, err = ioutil.ReadAll(f) - if err != nil { - return b, err - } - return b, nil -} - -func Put(path string, data string) error { - lock.Lock() - defer lock.Unlock() - - dir := filepath.Dir(path) - if ok, _ := Exists(dir); !ok { - err := os.MkdirAll(dir, 0600) - if err != nil { - return err - } - } - - f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0600) - defer f.Close() - - if err != nil { - return err - } - _, err = f.WriteString(data) - return err -} - -func ModTime(path string) (time.Time, error) { - var modTime time.Time - fileInfo, err := os.Stat(path) - if err != nil { - return modTime, err - } - return fileInfo.ModTime(), nil -} +package filesystem + +import ( + "io/ioutil" + "os" + "path" + "path/filepath" + "sync" + "time" +) + +var lock sync.RWMutex + +func Exists(p ...string) (bool, error) { + _, err := os.Stat(path.Join(p...)) + if err == nil { + return true, nil + } + if os.IsNotExist(err) { + return false, nil + } + return false, err +} + +func Get(path string) ([]byte, error) { + lock.Lock() + defer lock.Unlock() + + var b []byte + + f, err := os.OpenFile(path, os.O_RDWR, 0600) + defer f.Close() + + if err != nil { + return b, err + } + + b, err = ioutil.ReadAll(f) + if err != nil { + return b, err + } + return b, nil +} + +func Put(path string, data string) error { + lock.Lock() + defer lock.Unlock() + + dir := filepath.Dir(path) + if ok, _ := Exists(dir); !ok { + err := os.MkdirAll(dir, 0600) + if err != nil { + return err + } + } + + f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0600) + defer f.Close() + + if err != nil { + return err + } + _, err = f.WriteString(data) + return err +} + +func ModTime(path string) (time.Time, error) { + var modTime time.Time + fileInfo, err := os.Stat(path) + if err != nil { + return modTime, err + } + return fileInfo.ModTime(), nil +} diff --git a/helper/helper.go b/helper/helper.go index 835d846..6efeae7 100644 --- a/helper/helper.go +++ b/helper/helper.go @@ -1,58 +1,58 @@ -package helper - -import ( - "errors" - "os" - "path/filepath" - "runtime" - "strings" -) - -func ThinkGoPath(args ...string) string { - _, file, _, ok := runtime.Caller(0) - if !ok { - panic(errors.New("Can not load ThinkGo path info")) - } - - dir := filepath.Dir(filepath.Dir(file)) + "/" - - if 1 == len(args) { - dir = dir + strings.TrimLeft(args[0], "/") - } - - return dir -} - -func WorkPath(args ...string) string { - dir, err := os.Getwd() - if err != nil { - panic(err) - } - - if 1 == len(args) { - dir = filepath.Join(dir, strings.TrimLeft(args[0], "/")) - } - - return dir -} - -func AppPath(args ...string) string { - - dir := WorkPath("app") - - if 1 == len(args) { - dir = filepath.Join(dir, strings.TrimLeft(args[0], "/")) - } - - return dir -} - -func ConfigPath(args ...string) string { - dir := WorkPath("config") - - if 1 == len(args) { - dir = filepath.Join(dir, strings.TrimLeft(args[0], "/")) - } - - return dir -} +package helper + +import ( + "errors" + "os" + "path/filepath" + "runtime" + "strings" +) + +func ThinkGoPath(args ...string) string { + _, file, _, ok := runtime.Caller(0) + if !ok { + panic(errors.New("Can not load ThinkGo path info")) + } + + dir := filepath.Dir(filepath.Dir(file)) + "/" + + if 1 == len(args) { + dir = dir + strings.TrimLeft(args[0], "/") + } + + return dir +} + +func WorkPath(args ...string) string { + dir, err := os.Getwd() + if err != nil { + panic(err) + } + + if 1 == len(args) { + dir = filepath.Join(dir, strings.TrimLeft(args[0], "/")) + } + + return dir +} + +func AppPath(args ...string) string { + + dir := WorkPath("app") + + if 1 == len(args) { + dir = filepath.Join(dir, strings.TrimLeft(args[0], "/")) + } + + return dir +} + +func ConfigPath(args ...string) string { + dir := WorkPath("config") + + if 1 == len(args) { + dir = filepath.Join(dir, strings.TrimLeft(args[0], "/")) + } + + return dir +} diff --git a/helper/utils.go b/helper/utils.go index c061ea1..7464e4a 100644 --- a/helper/utils.go +++ b/helper/utils.go @@ -1,31 +1,31 @@ -package helper - -import ( - "os" - "strings" -) - -func ParseAddr(addrs ...string) string { - var addr = "0.0.0.0" - var port = "9011" - switch len(addrs) { - case 0: - if a := os.Getenv("THINKGO_ADDR"); a != "" { - addr = a - } - if p := os.Getenv("THINKGO_PORT"); p != "" { - port = p - } - case 1: - strs := strings.Split(addrs[0], ":") - if len(strs) > 0 && strs[0] != "" { - addr = strs[0] - } - if len(strs) > 1 && strs[1] != "" { - port = strs[1] - } - default: - - } - return addr + ":" + port -} +package helper + +import ( + "os" + "strings" +) + +func ParseAddr(addrs ...string) string { + var addr = "0.0.0.0" + var port = "9011" + switch len(addrs) { + case 0: + if a := os.Getenv("THINKGO_ADDR"); a != "" { + addr = a + } + if p := os.Getenv("THINKGO_PORT"); p != "" { + port = p + } + case 1: + strs := strings.Split(addrs[0], ":") + if len(strs) > 0 && strs[0] != "" { + addr = strs[0] + } + if len(strs) > 1 && strs[1] != "" { + port = strs[1] + } + default: + + } + return addr + ":" + port +} diff --git a/log/formatter/formatter.go b/log/formatter/formatter.go index 7bdfd70..30e21d5 100644 --- a/log/formatter/formatter.go +++ b/log/formatter/formatter.go @@ -1,8 +1,8 @@ -package formatter - -import "github.com/thinkoner/thinkgo/log/record" - -type Formatter interface { - Format(r record.Record) string - FormatBatch(rs []record.Record) string -} +package formatter + +import "github.com/thinkoner/thinkgo/log/record" + +type Formatter interface { + Format(r record.Record) string + FormatBatch(rs []record.Record) string +} diff --git a/log/formatter/line_formatter.go b/log/formatter/line_formatter.go index ffd8501..5b72626 100644 --- a/log/formatter/line_formatter.go +++ b/log/formatter/line_formatter.go @@ -1,23 +1,23 @@ -package formatter - -import "github.com/thinkoner/thinkgo/log/record" - -type LineFormatter struct { -} - -func NewLineFormatter() *LineFormatter { - f := &LineFormatter{} - return f -} - -func (f *LineFormatter) Format(r record.Record) string { - return "[" + r.Datetime.Local().Format("2006-01-02 15:04:05.000") + "]" + r.Channel + "." + r.LevelName + ": " + r.Message + "\n" -} - -func (f *LineFormatter) FormatBatch(rs []record.Record) string { - message := "" - for _, r := range rs { - message = message + f.Format(r) - } - return message -} +package formatter + +import "github.com/thinkoner/thinkgo/log/record" + +type LineFormatter struct { +} + +func NewLineFormatter() *LineFormatter { + f := &LineFormatter{} + return f +} + +func (f *LineFormatter) Format(r record.Record) string { + return "[" + r.Datetime.Local().Format("2006-01-02 15:04:05.000") + "]" + r.Channel + "." + r.LevelName + ": " + r.Message + "\n" +} + +func (f *LineFormatter) FormatBatch(rs []record.Record) string { + message := "" + for _, r := range rs { + message = message + f.Format(r) + } + return message +} diff --git a/log/handler/console.go b/log/handler/console.go index b1ec6c7..88024e9 100644 --- a/log/handler/console.go +++ b/log/handler/console.go @@ -4,7 +4,6 @@ import ( "os" "sync" - "github.com/thinkoner/thinkgo/log/formatter" "github.com/thinkoner/thinkgo/log/record" ) @@ -18,7 +17,7 @@ func newBrush(color string) brush { } } -var colors = map[int]brush{ +var colors = map[record.Level]brush{ record.EMERGENCY: newBrush("1;41"), // Emergency Red background record.ALERT: newBrush("1;35"), // Alert purple record.CRITICAL: newBrush("1;34"), // Critical blue @@ -30,15 +29,16 @@ var colors = map[int]brush{ } type ConsoleHandler struct { + Handler sync.Mutex - level int - formatter formatter.Formatter - bubble bool + level record.Level + + bubble bool } -func NewConsoleHandler() *ConsoleHandler { +func NewConsoleHandler(level record.Level) *ConsoleHandler { return &ConsoleHandler{ - level: record.DEBUG, + level: level, bubble: true, } } @@ -67,21 +67,3 @@ func (h *ConsoleHandler) write(r record.Record) { message := colors[r.Level](r.Formatted) os.Stdout.Write(append([]byte(message))) } - -// GetFormatter Gets the formatter. -func (h *ConsoleHandler) GetFormatter() formatter.Formatter { - if h.formatter == nil { - h.formatter = h.getDefaultFormatter() - } - return h.formatter -} - -// SetFormatter Sets the formatter. -func (h *ConsoleHandler) SetFormatter(f formatter.Formatter) *ConsoleHandler { - h.formatter = f - return h -} - -func (h *ConsoleHandler) getDefaultFormatter() formatter.Formatter { - return formatter.NewLineFormatter() -} diff --git a/log/handler/file.go b/log/handler/file.go new file mode 100644 index 0000000..c6fd579 --- /dev/null +++ b/log/handler/file.go @@ -0,0 +1,83 @@ +package handler + +import ( + "os" + "path" + "strings" + "sync" + "time" + + "github.com/thinkoner/thinkgo/log/record" +) + +type FileHandler struct { + Handler + sync.Mutex + level record.Level + bubble bool + filename string + + filenameFormat string + dateFormat string + timedFilename string +} + +func NewFileHandler(filename string, level record.Level) *FileHandler { + h := &FileHandler{ + level: level, + bubble: true, + filename: filename, + filenameFormat: "{filename}-{date}", + dateFormat: "2006-01-02", + } + h.timedFilename = h.GetTimedFilename() + return h +} + +// IsHandling Checks whether the given record will be handled by this handler. +func (h *FileHandler) IsHandling(r record.Record) bool { + return r.Level >= h.level +} + +// Handle Handles a record. +func (h *FileHandler) Handle(r record.Record) bool { + if !h.IsHandling(r) { + return false + } + + r.Formatted = h.GetFormatter().Format(r) + + h.write(r) + + return false == h.bubble +} + +// SetLevel Sets minimum logging level at which this handler will be triggered. +func (h *FileHandler) SetLevel(level record.Level) { + h.level = level +} + +func (h *FileHandler) write(r record.Record) { + h.Lock() + defer h.Unlock() + file, _ := os.OpenFile(h.timedFilename, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) + defer file.Close() + file.Write([]byte(r.Formatted)) +} + +// GetTimedFilename Gets the Timed filename. +func (h *FileHandler) GetTimedFilename() string { + dirname := path.Dir(h.filename) + filename := path.Base(h.filename) + fileExt := path.Ext(h.filename) + filename = strings.TrimSuffix(filename, fileExt) + + timedFilename := strings.Replace(path.Join(dirname, h.filenameFormat), "{filename}", filename, -1) + timedFilename = strings.Replace(timedFilename, "{date}", time.Now().Local().Format(h.dateFormat), -1) + + if len(fileExt) > 0 { + timedFilename = timedFilename + fileExt + } + + return timedFilename +} diff --git a/log/handler/file_test.go b/log/handler/file_test.go new file mode 100644 index 0000000..d1e2245 --- /dev/null +++ b/log/handler/file_test.go @@ -0,0 +1,46 @@ +package handler + +import ( + "io/ioutil" + "os" + "path" + "strings" + "testing" + "time" + + "github.com/thinkoner/thinkgo/log/record" +) + +func TestNewFileHandler(t *testing.T) { + filename := path.Join(os.TempDir(), "thinkgo.log") + + h := NewFileHandler(filename, record.DEBUG) + filename = h.GetTimedFilename() + + os.Remove(filename) + + message := "Log write to file" + r := getRecord(message) + h.Handle(r) + + b, err := ioutil.ReadFile(filename) + if err != nil { + t.Error(err) + } + content := string(b) + + if !strings.Contains(content, message) { + t.Error("test FileHandler error") + } + +} + +func getRecord(message string) record.Record { + return record.Record{ + Level: 200, + Message: message, + LevelName: "INFO", + Channel: "testing", + Datetime: time.Now(), + } +} diff --git a/log/handler/handler.go b/log/handler/handler.go new file mode 100644 index 0000000..a589d89 --- /dev/null +++ b/log/handler/handler.go @@ -0,0 +1,29 @@ +package handler + +import ( + "github.com/thinkoner/thinkgo/log/formatter" + "github.com/thinkoner/thinkgo/log/record" +) + +type Handler struct { + formatter formatter.Formatter + level record.Level +} + +// GetFormatter Gets the formatter. +func (h *Handler) GetFormatter() formatter.Formatter { + if h.formatter == nil { + h.formatter = h.getDefaultFormatter() + } + return h.formatter +} + +// SetFormatter Sets the formatter. +func (h *Handler) SetFormatter(f formatter.Formatter) *Handler { + h.formatter = f + return h +} + +func (h *Handler) getDefaultFormatter() formatter.Formatter { + return formatter.NewLineFormatter() +} diff --git a/log/log.go b/log/log.go index d1f74cc..819447b 100644 --- a/log/log.go +++ b/log/log.go @@ -1,9 +1,13 @@ package log +import ( + "github.com/thinkoner/thinkgo/log/record" +) + var logger *Logger func init() { - logger = NewLogger("develop") + logger = NewLogger("develop", record.DEBUG) } // Debug Adds a log record at the DEBUG level. @@ -45,3 +49,13 @@ func Alert(format string, v ...interface{}) (bool, error) { func Emerg(format string, v ...interface{}) (bool, error) { return logger.Emerg(format, v...) } + +// GetLogger Get the default Logger +func GetLogger() *Logger { + return logger +} + +// SetLogger Set the default Logger +func SetLogger(l *Logger) { + logger = l +} diff --git a/log/log_test.go b/log/log_test.go index 2038ef4..b0c6220 100644 --- a/log/log_test.go +++ b/log/log_test.go @@ -1,7 +1,15 @@ package log import ( + "errors" + "io/ioutil" + "os" + "path" + "strings" "testing" + + "github.com/thinkoner/thinkgo/log/handler" + "github.com/thinkoner/thinkgo/log/record" ) func TestLog(t *testing.T) { @@ -14,3 +22,43 @@ func TestLog(t *testing.T) { Alert("log with Alert") Emerg("log with Emerg") } + +func TestLogWithFileHandler(t *testing.T) { + + filename := path.Join(os.TempDir(), "thinkgo.log") + + h := handler.NewFileHandler(filename, record.INFO) + + l := NewLogger("testing", record.INFO) + l.PushHandler(h) + + filename = h.GetTimedFilename() + + os.Remove(filename) + + message := "Log write to file" + + l.Debug(message) + + _, err := ioutil.ReadFile(filename) + if err == nil { + t.Error(errors.New("test FileHandler error")) + } + + + h.SetLevel(record.DEBUG) + l = NewLogger("testing", record.DEBUG) + l.PushHandler(h) + l.Debug(message) + + b, err := ioutil.ReadFile(filename) + if err != nil { + t.Error(errors.New("test FileHandler error")) + } + content := string(b) + + if !strings.Contains(content, message) { + t.Error("test FileHandler error") + } + +} diff --git a/log/logger.go b/log/logger.go index 5bd63eb..21b09f7 100644 --- a/log/logger.go +++ b/log/logger.go @@ -17,13 +17,16 @@ type Handler interface { type Logger struct { name string + level record.Level handlers *list.List } -func NewLogger(name string) *Logger { +// NewLogger New a Logger instance +func NewLogger(name string, level record.Level) *Logger { logger := &Logger{ name: name, handlers: list.New(), + level: level, } return logger } @@ -71,9 +74,10 @@ func (logger *Logger) GetHandlers() []Handler { return handler } -func (logger *Logger) AddRecord(level int, format string, v ...interface{}) (bool, error) { +// AddRecord Adds a log record. +func (logger *Logger) AddRecord(level record.Level, format string, v ...interface{}) (bool, error) { if logger.handlers.Len() == 0 { - logger.PushHandler(handler.NewConsoleHandler()) + logger.PushHandler(handler.NewConsoleHandler(logger.level)) } levelName, err := GetLevelName(level) @@ -156,7 +160,7 @@ func (logger *Logger) Emerg(format string, v ...interface{}) (bool, error) { } // Gets the name of the logging level. -func GetLevelName(level int) (string, error) { +func GetLevelName(level record.Level) (string, error) { levels := record.GetLevels() l, ok := levels[level] if !ok { diff --git a/log/record/record.go b/log/record/record.go index f9cc6b7..8bd6a23 100644 --- a/log/record/record.go +++ b/log/record/record.go @@ -2,6 +2,8 @@ package record import "time" +type Level int + const ( // Detailed debug information DEBUG = 100 @@ -22,7 +24,7 @@ const ( ) // Logging levels from syslog protocol defined in RFC 5424 -var levels = map[int]string{ +var levels = map[Level]string{ DEBUG: "DEBUG", INFO: "INFO", NOTICE: "NOTICE", @@ -34,7 +36,7 @@ var levels = map[int]string{ } type Record struct { - Level int + Level Level Message string LevelName string Channel string @@ -43,6 +45,6 @@ type Record struct { } // GetLevels returns levels map -func GetLevels() map[int]string { +func GetLevels() map[Level]string { return levels } diff --git a/pipeline.go b/pipeline.go index 20d7ca9..fa67342 100644 --- a/pipeline.go +++ b/pipeline.go @@ -1,88 +1,88 @@ -package thinkgo - -import ( - "container/list" - "net/http" - - "github.com/thinkoner/thinkgo/app" - "github.com/thinkoner/thinkgo/context" - "github.com/thinkoner/thinkgo/router" -) - -type Pipeline struct { - handlers []app.Handler - pipeline *list.List - passable *context.Request -} - -// Pipeline returns a new Pipeline -func NewPipeline() *Pipeline { - p := &Pipeline{ - pipeline: list.New(), - } - return p -} - -// Pipe Push a Middleware Handler to the pipeline -func (p *Pipeline) Pipe(m app.Handler) *Pipeline { - p.pipeline.PushBack(m) - return p -} - -// Pipe Batch push Middleware Handlers to the pipeline -func (p *Pipeline) Through(hls []app.Handler) *Pipeline { - for _, hl := range hls { - p.Pipe(hl) - } - return p -} - -// Passable set the request being sent through the pipeline. -func (p *Pipeline) Passable(passable *context.Request) *Pipeline { - p.passable = passable - return p -} - -// Run run the pipeline -func (p *Pipeline) Run() interface{} { - var result interface{} - e := p.pipeline.Front() - if e != nil { - result = p.handler(p.passable, e) - } - return result -} - -// ServeHTTP -func (p *Pipeline) ServeHTTP(w http.ResponseWriter, r *http.Request) { - request := context.NewRequest(r) - request.CookieHandler = context.ParseCookieHandler() - p.Passable(request) - - result := p.Run() - - switch result.(type) { - case router.Response: - result.(router.Response).Send(w) - return - case http.Handler: - result.(http.Handler).ServeHTTP(w, r) - return - } -} - -func (p *Pipeline) handler(passable *context.Request, e *list.Element) interface{} { - if e == nil { - return nil - } - hl := e.Value.(app.Handler) - result := hl.Process(passable, p.closure(e)) - return result -} - -func (p *Pipeline) closure(e *list.Element) app.Closure { - return func(req *context.Request) interface{} { - e = e.Next() - return p.handler(req, e) - } -} +package thinkgo + +import ( + "container/list" + "net/http" + + "github.com/thinkoner/thinkgo/app" + "github.com/thinkoner/thinkgo/context" + "github.com/thinkoner/thinkgo/router" +) + +type Pipeline struct { + handlers []app.Handler + pipeline *list.List + passable *context.Request +} + +// Pipeline returns a new Pipeline +func NewPipeline() *Pipeline { + p := &Pipeline{ + pipeline: list.New(), + } + return p +} + +// Pipe Push a Middleware Handler to the pipeline +func (p *Pipeline) Pipe(m app.Handler) *Pipeline { + p.pipeline.PushBack(m) + return p +} + +// Pipe Batch push Middleware Handlers to the pipeline +func (p *Pipeline) Through(hls []app.Handler) *Pipeline { + for _, hl := range hls { + p.Pipe(hl) + } + return p +} + +// Passable set the request being sent through the pipeline. +func (p *Pipeline) Passable(passable *context.Request) *Pipeline { + p.passable = passable + return p +} + +// Run run the pipeline +func (p *Pipeline) Run() interface{} { + var result interface{} + e := p.pipeline.Front() + if e != nil { + result = p.handler(p.passable, e) + } + return result +} + +// ServeHTTP +func (p *Pipeline) ServeHTTP(w http.ResponseWriter, r *http.Request) { + request := context.NewRequest(r) + request.CookieHandler = context.ParseCookieHandler() + p.Passable(request) + + result := p.Run() + + switch result.(type) { + case router.Response: + result.(router.Response).Send(w) + return + case http.Handler: + result.(http.Handler).ServeHTTP(w, r) + return + } +} + +func (p *Pipeline) handler(passable *context.Request, e *list.Element) interface{} { + if e == nil { + return nil + } + hl := e.Value.(app.Handler) + result := hl.Process(passable, p.closure(e)) + return result +} + +func (p *Pipeline) closure(e *list.Element) app.Closure { + return func(req *context.Request) interface{} { + e = e.Next() + return p.handler(req, e) + } +} diff --git a/response.go b/response.go index 6617e24..eb2269f 100644 --- a/response.go +++ b/response.go @@ -1,37 +1,37 @@ -package thinkgo - -import ( - "encoding/json" - - "github.com/thinkoner/thinkgo/context" -) - -// Json Create a new HTTP Response with JSON data -func Json(v interface{}) *context.Response { - r := context.NewResponse() - r.SetContentType("application/json") - c, _ := json.Marshal(v) - r.SetContent(string(c)) - return r -} - -// Text Create a new HTTP Response with TEXT data -func Text(s string) *context.Response { - r := context.NewResponse() - r.SetContentType("text/plain") - r.SetContent(s) - return r -} - -// Text Create a new HTTP Response with HTML data -func Html(s string) *context.Response { - r := context.NewResponse() - r.SetContent(s) - return r -} - -// Render Create a new HTTP Response with the template -func Render(name string, data interface{}) *context.Response { - b := application.GetView().Render(name, data) - return Html(string(b)) -} +package thinkgo + +import ( + "encoding/json" + + "github.com/thinkoner/thinkgo/context" +) + +// Json Create a new HTTP Response with JSON data +func Json(v interface{}) *context.Response { + r := context.NewResponse() + r.SetContentType("application/json") + c, _ := json.Marshal(v) + r.SetContent(string(c)) + return r +} + +// Text Create a new HTTP Response with TEXT data +func Text(s string) *context.Response { + r := context.NewResponse() + r.SetContentType("text/plain") + r.SetContent(s) + return r +} + +// Text Create a new HTTP Response with HTML data +func Html(s string) *context.Response { + r := context.NewResponse() + r.SetContent(s) + return r +} + +// Render Create a new HTTP Response with the template +func Render(name string, data interface{}) *context.Response { + b := application.GetView().Render(name, data) + return Html(string(b)) +} diff --git a/router/metch.go b/router/metch.go index b4332ce..98a367f 100644 --- a/router/metch.go +++ b/router/metch.go @@ -1,37 +1,37 @@ -package router - -import ( - "regexp" -) - -func matchMethod(method, target string) bool { - if "*" == target { - return true - } - - if target == method { - return true - } - - return false -} - -func matchMethods(method string, target []string) bool { - for _, v := range target { - if matchMethod(method, v) { - return true - } - } - return false -} - -func matchPath(path, target string) bool { - - res, err := regexp.MatchString(target, path) - - if err != nil { - return false - } - - return res -} +package router + +import ( + "regexp" +) + +func matchMethod(method, target string) bool { + if "*" == target { + return true + } + + if target == method { + return true + } + + return false +} + +func matchMethods(method string, target []string) bool { + for _, v := range target { + if matchMethod(method, v) { + return true + } + } + return false +} + +func matchPath(path, target string) bool { + + res, err := regexp.MatchString(target, path) + + if err != nil { + return false + } + + return res +} diff --git a/router/middleware.go b/router/middleware.go index 513c784..1afdca8 100644 --- a/router/middleware.go +++ b/router/middleware.go @@ -1,19 +1,19 @@ -package router - -import ( - "net/http" - - "github.com/thinkoner/thinkgo/context" -) - -// Response an HTTP response interface -type Response interface { - Send(w http.ResponseWriter) -} - -// Closure Anonymous function, Used in Middleware Handler -type Closure func(req *context.Request) interface { -} - -// MiddlewareFunc Handle an incoming request. -type Middleware func(request *context.Request, next Closure) interface{} +package router + +import ( + "net/http" + + "github.com/thinkoner/thinkgo/context" +) + +// Response an HTTP response interface +type Response interface { + Send(w http.ResponseWriter) +} + +// Closure Anonymous function, Used in Middleware Handler +type Closure func(req *context.Request) interface { +} + +// MiddlewareFunc Handle an incoming request. +type Middleware func(request *context.Request, next Closure) interface{} diff --git a/router/pipeline.go b/router/pipeline.go index eecf9a7..049846d 100644 --- a/router/pipeline.go +++ b/router/pipeline.go @@ -1,70 +1,70 @@ -package router - -import ( - "container/list" - - "github.com/thinkoner/thinkgo/context" -) - -type Pipeline struct { - handlers []Middleware - pipeline *list.List - passable *context.Request -} - -// Pipeline returns a new Pipeline -func NewPipeline() *Pipeline { - p := &Pipeline{ - pipeline: list.New(), - } - return p -} - -// Pipe Push a Middleware Handler to the pipeline -func (p *Pipeline) Pipe(m Middleware) *Pipeline { - p.pipeline.PushBack(m) - return p -} - -// Pipe Batch push Middleware Handlers to the pipeline -func (p *Pipeline) Through(hls []Middleware) *Pipeline { - for _, hl := range hls { - p.Pipe(hl) - } - return p -} - -// Passable set the request being sent through the pipeline. -func (p *Pipeline) Passable(passable *context.Request) *Pipeline { - p.passable = passable - return p -} - -// Run run the pipeline -func (p *Pipeline) Run(destination Middleware) interface{} { - var result interface{} - - p.Pipe(destination) - - e := p.pipeline.Front() - if e != nil { - result = p.handler(p.passable, e) - } - return result -} - -func (p *Pipeline) handler(passable *context.Request, e *list.Element) interface{} { - if e == nil { - return nil - } - middleware := e.Value.(Middleware) - result := middleware(passable, p.closure(e)) - return result -} - -func (p *Pipeline) closure(e *list.Element) Closure { - return func(req *context.Request) interface{} { - e = e.Next() - return p.handler(req, e) - } -} +package router + +import ( + "container/list" + + "github.com/thinkoner/thinkgo/context" +) + +type Pipeline struct { + handlers []Middleware + pipeline *list.List + passable *context.Request +} + +// Pipeline returns a new Pipeline +func NewPipeline() *Pipeline { + p := &Pipeline{ + pipeline: list.New(), + } + return p +} + +// Pipe Push a Middleware Handler to the pipeline +func (p *Pipeline) Pipe(m Middleware) *Pipeline { + p.pipeline.PushBack(m) + return p +} + +// Pipe Batch push Middleware Handlers to the pipeline +func (p *Pipeline) Through(hls []Middleware) *Pipeline { + for _, hl := range hls { + p.Pipe(hl) + } + return p +} + +// Passable set the request being sent through the pipeline. +func (p *Pipeline) Passable(passable *context.Request) *Pipeline { + p.passable = passable + return p +} + +// Run run the pipeline +func (p *Pipeline) Run(destination Middleware) interface{} { + var result interface{} + + p.Pipe(destination) + + e := p.pipeline.Front() + if e != nil { + result = p.handler(p.passable, e) + } + return result +} + +func (p *Pipeline) handler(passable *context.Request, e *list.Element) interface{} { + if e == nil { + return nil + } + middleware := e.Value.(Middleware) + result := middleware(passable, p.closure(e)) + return result +} + +func (p *Pipeline) closure(e *list.Element) Closure { + return func(req *context.Request) interface{} { + e = e.Next() + return p.handler(req, e) + } +} diff --git a/router/resource_controller.go b/router/resource_controller.go index 7ef135b..6ddb207 100644 --- a/router/resource_controller.go +++ b/router/resource_controller.go @@ -1 +1 @@ -package router +package router diff --git a/router/route.go b/router/route.go index 799a6c5..ea05514 100644 --- a/router/route.go +++ b/router/route.go @@ -1,233 +1,233 @@ -package router - -import ( - "errors" - "path" - "strings" - - "github.com/thinkoner/thinkgo/log" -) - -var verbs = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"} - -type Request interface { - GetMethod() string - GetPath() string -} - -type Route struct { - inited bool - - method []string - prefix string - pattern string - handler interface{} - middlewares []Middleware - group *Route - - collects []*Route - - rules map[string][]*Rule - // Current *Rule -} - -// NewRoute Create a new Route instance. -func NewRoute() *Route { - route := &Route{ - rules: make(map[string][]*Rule), - } - return route -} - -// Dispatch Dispatch the request -func (r *Route) Dispatch(request Request) (*Rule, error) { - rule, err := r.Match(request) - if err != nil { - return nil, err - } - - rule.Bind(request.GetPath()) - - return rule, nil -} - -// Match Find the first rule matching a given request. -func (r *Route) Match(request Request) (*Rule, error) { - for _, rule := range r.rules[request.GetMethod()] { - if true == rule.Matches(request.GetMethod(), request.GetPath()) { - return rule, nil - } - } - return nil, errors.New("Not Found") -} - -// AddRule Add a Rule to the Router.Rules -func (r *Route) AddRule(rule *Rule) *Rule { - var rules []*Rule - - for _, m := range rule.method { - if _, ok := r.rules[m]; ok { - rules = r.rules[m] - } - r.rules[m] = append(rules, rule) - } - return rule -} - -// Add Add a router -func (r *Route) Add(method []string, pattern string, handler interface{}) *Route { - route := r.initRoute() - route.method = method - route.pattern = r.getPrefix(pattern) - route.handler = handler - return route -} - -// Get Register a new GET rule with the router. -func (r *Route) Get(pattern string, handler interface{}) *Route { - return r.Add(Method("GET", "HEAD"), pattern, handler) -} - -// Head Register a new Head rule with the router. -func (r *Route) Head(pattern string, handler interface{}) *Route { - return r.Add(Method("HEAD"), pattern, handler) -} - -// Post Register a new POST rule with the router. -func (r *Route) Post(pattern string, handler interface{}) *Route { - return r.Add(Method("POST"), pattern, handler) -} - -// Put Register a new PUT rule with the router. -func (r *Route) Put(pattern string, handler interface{}) *Route { - return r.Add(Method("PUT"), pattern, handler) -} - -// Patch Register a new PATCH rule with the router. -func (r *Route) Patch(pattern string, handler interface{}) *Route { - return r.Add(Method("PATCH"), pattern, handler) -} - -// Delete Register a new DELETE rule with the router. -func (r *Route) Delete(pattern string, handler interface{}) *Route { - return r.Add(Method("DELETE"), pattern, handler) -} - -// Options Register a new OPTIONS rule with the router. -func (r *Route) Options(pattern string, handler interface{}) *Route { - return r.Add(Method("OPTIONS"), pattern, handler) -} - -// Any Register a new rule responding to all verbs. -func (r *Route) Any(pattern string, handler interface{}) *Route { - return r.Add(verbs, pattern, handler) -} - -// Static Register a new Static rule. -func (r *Route) Static(path, root string) { - path = "/" + strings.Trim(path, "/") + "/*" - - h := NewStaticHandle(root) - - r.Get(path, h) - r.Head(path, h) -} - -// Statics Bulk register Static rule. -func (r *Route) Statics(statics map[string]string) { - for path, root := range statics { - r.Static(path, root) - } -} - -// Prefix Add a prefix to the route URI. -func (r *Route) Prefix(prefix string) *Route { - route := r.initRoute() - route.prefix = route.getPrefix(prefix) - return route -} - -// Group Create a route group -func (r *Route) Group(callback func(group *Route)) *Route { - route := r.initRoute() - group := route.cloneRoute() - group.Middleware(route.middlewares...) - callback(group) - return route -} - -// Middleware Set the middleware attached to the route. -func (r *Route) Middleware(middlewares ...Middleware) *Route { - route := r.initRoute() - for _, m := range middlewares { - route.middlewares = append(route.middlewares, m) - } - return route -} - -// Register Register route from the collect. -func (r *Route) Register() { - r.register(r) -} - -func (r *Route) register(root *Route) { - for _, route := range r.collects { - route.prefix = r.getPrefix(route.prefix) - - var middlewares []Middleware - for _, m := range r.middlewares { - middlewares = append(middlewares, m) - } - for _, m := range route.middlewares { - middlewares = append(middlewares, m) - } - route.middlewares = middlewares - - route.register(root) - - if route.handler == nil { - continue - } - rule := &Rule{ - method: route.method, - pattern: route.getPrefix(route.pattern), - handler: route.handler, - middlewares: route.middlewares, - } - - root.AddRule(rule) - log.Debug(rule.toString()) - } - r.collects = r.collects[0:0] -} - -// initRoute Initialize a new Route if not initialized -func (r *Route) initRoute() *Route { - route := r - if !r.inited { - route = &Route{ - inited: true, - rules: make(map[string][]*Rule), - // prefix: r.prefix, - // middlewares: r.middlewares, - } - r.collects = append(r.collects, route) - } - return route -} - -func (r *Route) cloneRoute() *Route { - route := &Route{ - inited: false, - rules: make(map[string][]*Rule), - // prefix: r.prefix, - // middlewares: r.middlewares, - } - r.collects = append(r.collects, route) - - return route -} - -func (r *Route) getPrefix(pattern string) string { - return path.Join("/", r.prefix, pattern) -} +package router + +import ( + "errors" + "path" + "strings" + + "github.com/thinkoner/thinkgo/log" +) + +var verbs = []string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"} + +type Request interface { + GetMethod() string + GetPath() string +} + +type Route struct { + inited bool + + method []string + prefix string + pattern string + handler interface{} + middlewares []Middleware + group *Route + + collects []*Route + + rules map[string][]*Rule + // Current *Rule +} + +// NewRoute Create a new Route instance. +func NewRoute() *Route { + route := &Route{ + rules: make(map[string][]*Rule), + } + return route +} + +// Dispatch Dispatch the request +func (r *Route) Dispatch(request Request) (*Rule, error) { + rule, err := r.Match(request) + if err != nil { + return nil, err + } + + rule.Bind(request.GetPath()) + + return rule, nil +} + +// Match Find the first rule matching a given request. +func (r *Route) Match(request Request) (*Rule, error) { + for _, rule := range r.rules[request.GetMethod()] { + if true == rule.Matches(request.GetMethod(), request.GetPath()) { + return rule, nil + } + } + return nil, errors.New("Not Found") +} + +// AddRule Add a Rule to the Router.Rules +func (r *Route) AddRule(rule *Rule) *Rule { + var rules []*Rule + + for _, m := range rule.method { + if _, ok := r.rules[m]; ok { + rules = r.rules[m] + } + r.rules[m] = append(rules, rule) + } + return rule +} + +// Add Add a router +func (r *Route) Add(method []string, pattern string, handler interface{}) *Route { + route := r.initRoute() + route.method = method + route.pattern = r.getPrefix(pattern) + route.handler = handler + return route +} + +// Get Register a new GET rule with the router. +func (r *Route) Get(pattern string, handler interface{}) *Route { + return r.Add(Method("GET", "HEAD"), pattern, handler) +} + +// Head Register a new Head rule with the router. +func (r *Route) Head(pattern string, handler interface{}) *Route { + return r.Add(Method("HEAD"), pattern, handler) +} + +// Post Register a new POST rule with the router. +func (r *Route) Post(pattern string, handler interface{}) *Route { + return r.Add(Method("POST"), pattern, handler) +} + +// Put Register a new PUT rule with the router. +func (r *Route) Put(pattern string, handler interface{}) *Route { + return r.Add(Method("PUT"), pattern, handler) +} + +// Patch Register a new PATCH rule with the router. +func (r *Route) Patch(pattern string, handler interface{}) *Route { + return r.Add(Method("PATCH"), pattern, handler) +} + +// Delete Register a new DELETE rule with the router. +func (r *Route) Delete(pattern string, handler interface{}) *Route { + return r.Add(Method("DELETE"), pattern, handler) +} + +// Options Register a new OPTIONS rule with the router. +func (r *Route) Options(pattern string, handler interface{}) *Route { + return r.Add(Method("OPTIONS"), pattern, handler) +} + +// Any Register a new rule responding to all verbs. +func (r *Route) Any(pattern string, handler interface{}) *Route { + return r.Add(verbs, pattern, handler) +} + +// Static Register a new Static rule. +func (r *Route) Static(path, root string) { + path = "/" + strings.Trim(path, "/") + "/*" + + h := NewStaticHandle(root) + + r.Get(path, h) + r.Head(path, h) +} + +// Statics Bulk register Static rule. +func (r *Route) Statics(statics map[string]string) { + for path, root := range statics { + r.Static(path, root) + } +} + +// Prefix Add a prefix to the route URI. +func (r *Route) Prefix(prefix string) *Route { + route := r.initRoute() + route.prefix = route.getPrefix(prefix) + return route +} + +// Group Create a route group +func (r *Route) Group(callback func(group *Route)) *Route { + route := r.initRoute() + group := route.cloneRoute() + group.Middleware(route.middlewares...) + callback(group) + return route +} + +// Middleware Set the middleware attached to the route. +func (r *Route) Middleware(middlewares ...Middleware) *Route { + route := r.initRoute() + for _, m := range middlewares { + route.middlewares = append(route.middlewares, m) + } + return route +} + +// Register Register route from the collect. +func (r *Route) Register() { + r.register(r) +} + +func (r *Route) register(root *Route) { + for _, route := range r.collects { + route.prefix = r.getPrefix(route.prefix) + + var middlewares []Middleware + for _, m := range r.middlewares { + middlewares = append(middlewares, m) + } + for _, m := range route.middlewares { + middlewares = append(middlewares, m) + } + route.middlewares = middlewares + + route.register(root) + + if route.handler == nil { + continue + } + rule := &Rule{ + method: route.method, + pattern: route.getPrefix(route.pattern), + handler: route.handler, + middlewares: route.middlewares, + } + + root.AddRule(rule) + log.Debug(rule.toString()) + } + r.collects = r.collects[0:0] +} + +// initRoute Initialize a new Route if not initialized +func (r *Route) initRoute() *Route { + route := r + if !r.inited { + route = &Route{ + inited: true, + rules: make(map[string][]*Rule), + // prefix: r.prefix, + // middlewares: r.middlewares, + } + r.collects = append(r.collects, route) + } + return route +} + +func (r *Route) cloneRoute() *Route { + route := &Route{ + inited: false, + rules: make(map[string][]*Rule), + // prefix: r.prefix, + // middlewares: r.middlewares, + } + r.collects = append(r.collects, route) + + return route +} + +func (r *Route) getPrefix(pattern string) string { + return path.Join("/", r.prefix, pattern) +} diff --git a/router/router.go b/router/router.go index 2e64c36..e300f28 100644 --- a/router/router.go +++ b/router/router.go @@ -1,58 +1,58 @@ -package router - -import ( - "fmt" - "net/http" - - "github.com/thinkoner/thinkgo/context" -) - -// RunRoute Return the response for the given rule. -func RunRoute(request *context.Request, rule *Rule) interface{} { - return PrepareResponse( - request, - rule, - runMiddlewares(request, rule), - ) -} - -// PrepareResponse Create a response instance from the given value. -func PrepareResponse(request *context.Request, rule *Rule, result interface{}) interface{} { - var response Response - switch result.(type) { - case Response: - return result.(Response) - // case string: - case http.Handler: - return response - default: - // t := reflect.TypeOf(response) - // switch t.Kind() { - // case reflect.Func: - // v := reflect.ValueOf(response).Call( - // parseParams(request, rule.Parameters), - // ) - // response = v[0].Interface() - // default: - // - // } - response = context.NewResponse().SetContent(fmt.Sprint(result)) - } - return response -} - -// runMiddlewares Run the given route within Middlewares instance. -func runMiddlewares(request *context.Request, rule *Rule) interface{} { - pipeline := NewPipeline() - - for _, m := range rule.GatherRouteMiddleware() { - pipeline.Pipe(m) - } - return pipeline.Passable(request).Run(func(request *context.Request, next Closure) interface{} { - return PrepareResponse( - request, - rule, - rule.Run(request), - ) - }) -} +package router + +import ( + "fmt" + "net/http" + + "github.com/thinkoner/thinkgo/context" +) + +// RunRoute Return the response for the given rule. +func RunRoute(request *context.Request, rule *Rule) interface{} { + return PrepareResponse( + request, + rule, + runMiddlewares(request, rule), + ) +} + +// PrepareResponse Create a response instance from the given value. +func PrepareResponse(request *context.Request, rule *Rule, result interface{}) interface{} { + var response Response + switch result.(type) { + case Response: + return result.(Response) + // case string: + case http.Handler: + return response + default: + // t := reflect.TypeOf(response) + // switch t.Kind() { + // case reflect.Func: + // v := reflect.ValueOf(response).Call( + // parseParams(request, rule.Parameters), + // ) + // response = v[0].Interface() + // default: + // + // } + response = context.NewResponse().SetContent(fmt.Sprint(result)) + } + return response +} + +// runMiddlewares Run the given route within Middlewares instance. +func runMiddlewares(request *context.Request, rule *Rule) interface{} { + pipeline := NewPipeline() + + for _, m := range rule.GatherRouteMiddleware() { + pipeline.Pipe(m) + } + return pipeline.Passable(request).Run(func(request *context.Request, next Closure) interface{} { + return PrepareResponse( + request, + rule, + rule.Run(request), + ) + }) +} diff --git a/router/rule.go b/router/rule.go index 189703a..edebeef 100644 --- a/router/rule.go +++ b/router/rule.go @@ -1,145 +1,145 @@ -package router - -import ( - "fmt" - "reflect" - "regexp" - "strings" - - "github.com/thinkoner/thinkgo/context" -) - -// Rule Route rule -type Rule struct { - middlewares []Middleware - method []string - pattern string - handler interface{} - parameterNames []string - parameters map[string]string - Compiled *Compiled -} - -type Compiled struct { - Regex string -} - -// Matches Determine if the rule matches given request. -func (r *Rule) Matches(method, path string) bool { - r.compile() - - if false == matchMethods(method, r.method) { - return false - } - - if false == matchPath(path, r.Compiled.Regex) { - return false - } - - return true -} - -// Bind Bind the router to a given request for execution. -func (r *Rule) Bind(path string) { - r.compile() - - path = "/" + strings.TrimLeft(path, "/") - reg := regexp.MustCompile(r.Compiled.Regex) - matches := reg.FindStringSubmatch(path)[1:] - - parameterNames := r.getParameterNames() - - if len(parameterNames) == 0 { - return - } - - parameters := make(map[string]string) - - for k, v := range parameterNames { - parameters[v] = matches[k] - } - - r.parameters = parameters -} - -// Middleware Set the middleware attached to the rule. -func (r *Rule) Middleware(middlewares ...Middleware) *Rule { - for _, m := range middlewares { - r.middlewares = append(r.middlewares, m) - } - return r -} - -// GatherRouteMiddleware Get all middleware, including the ones from the controller. -func (r *Rule) GatherRouteMiddleware() []Middleware { - return r.middlewares -} - -// Run Run the route action and return the response. -func (r *Rule) Run(request *context.Request) interface{} { - handler := r.handler - - if handler != nil { - t := reflect.TypeOf(handler) - switch t.Kind() { - case reflect.Func: - v := reflect.ValueOf(handler).Call( - parseParams(request, r.parameters), - ) - handler = v[0].Interface() - } - } - return handler -} - -// getParameterNames Get all of the parameter names for the rule. -func (r *Rule) getParameterNames() []string { - if r.parameterNames != nil { - return r.parameterNames - } - r.parameterNames = r.compileParameterNames() - - return r.parameterNames -} - -func (r *Rule) compile() { - if r.Compiled != nil { - return - } - - r.pattern = strings.Replace(r.pattern, "/*", "/.*", -1) - - reg, _ := regexp.Compile(`\{\w+\}`) - regex := reg.ReplaceAllString(r.pattern, "([^/]+)") - - r.Compiled = &Compiled{ - Regex: "^" + regex + "$", - } -} - -func (r *Rule) compileParameterNames() []string { - reg := regexp.MustCompile(`\{(.*?)\}`) - matches := reg.FindAllStringSubmatch(r.pattern, -1) - - var result []string - for _, v := range matches { - result = append(result, v[1]) - } - - return result -} - -func (r *Rule) toString() string { - return fmt.Sprint(r.method) + ": " + r.pattern -} - -func parseParams(request *context.Request, parameters map[string]string) []reflect.Value { - var params []reflect.Value - params = append(params, reflect.ValueOf(request)) - - for _, v := range parameters { - params = append(params, reflect.ValueOf(v)) - } - - return params -} +package router + +import ( + "fmt" + "reflect" + "regexp" + "strings" + + "github.com/thinkoner/thinkgo/context" +) + +// Rule Route rule +type Rule struct { + middlewares []Middleware + method []string + pattern string + handler interface{} + parameterNames []string + parameters map[string]string + Compiled *Compiled +} + +type Compiled struct { + Regex string +} + +// Matches Determine if the rule matches given request. +func (r *Rule) Matches(method, path string) bool { + r.compile() + + if false == matchMethods(method, r.method) { + return false + } + + if false == matchPath(path, r.Compiled.Regex) { + return false + } + + return true +} + +// Bind Bind the router to a given request for execution. +func (r *Rule) Bind(path string) { + r.compile() + + path = "/" + strings.TrimLeft(path, "/") + reg := regexp.MustCompile(r.Compiled.Regex) + matches := reg.FindStringSubmatch(path)[1:] + + parameterNames := r.getParameterNames() + + if len(parameterNames) == 0 { + return + } + + parameters := make(map[string]string) + + for k, v := range parameterNames { + parameters[v] = matches[k] + } + + r.parameters = parameters +} + +// Middleware Set the middleware attached to the rule. +func (r *Rule) Middleware(middlewares ...Middleware) *Rule { + for _, m := range middlewares { + r.middlewares = append(r.middlewares, m) + } + return r +} + +// GatherRouteMiddleware Get all middleware, including the ones from the controller. +func (r *Rule) GatherRouteMiddleware() []Middleware { + return r.middlewares +} + +// Run Run the route action and return the response. +func (r *Rule) Run(request *context.Request) interface{} { + handler := r.handler + + if handler != nil { + t := reflect.TypeOf(handler) + switch t.Kind() { + case reflect.Func: + v := reflect.ValueOf(handler).Call( + parseParams(request, r.parameters), + ) + handler = v[0].Interface() + } + } + return handler +} + +// getParameterNames Get all of the parameter names for the rule. +func (r *Rule) getParameterNames() []string { + if r.parameterNames != nil { + return r.parameterNames + } + r.parameterNames = r.compileParameterNames() + + return r.parameterNames +} + +func (r *Rule) compile() { + if r.Compiled != nil { + return + } + + r.pattern = strings.Replace(r.pattern, "/*", "/.*", -1) + + reg, _ := regexp.Compile(`\{\w+\}`) + regex := reg.ReplaceAllString(r.pattern, "([^/]+)") + + r.Compiled = &Compiled{ + Regex: "^" + regex + "$", + } +} + +func (r *Rule) compileParameterNames() []string { + reg := regexp.MustCompile(`\{(.*?)\}`) + matches := reg.FindAllStringSubmatch(r.pattern, -1) + + var result []string + for _, v := range matches { + result = append(result, v[1]) + } + + return result +} + +func (r *Rule) toString() string { + return fmt.Sprint(r.method) + ": " + r.pattern +} + +func parseParams(request *context.Request, parameters map[string]string) []reflect.Value { + var params []reflect.Value + params = append(params, reflect.ValueOf(request)) + + for _, v := range parameters { + params = append(params, reflect.ValueOf(v)) + } + + return params +} diff --git a/router/static.go b/router/static.go index 8675e83..585c8a1 100644 --- a/router/static.go +++ b/router/static.go @@ -1,26 +1,26 @@ -package router - -import ( - "net/http" - - "github.com/thinkoner/thinkgo/filesystem" -) - -type staticHandle struct { - fileServer http.Handler - fs http.FileSystem -} - -// NewStaticHandle A Handler responds to a Static HTTP request. -func NewStaticHandle(root string) http.Handler { - fs := filesystem.NewFileFileSystem(root, false) - return &staticHandle{ - fileServer: http.FileServer(fs), - fs: fs, - } -} - -// ServeHTTP responds to an Static HTTP request. -func (s *staticHandle) ServeHTTP(w http.ResponseWriter, r *http.Request) { - s.fileServer.ServeHTTP(w, r) -} +package router + +import ( + "net/http" + + "github.com/thinkoner/thinkgo/filesystem" +) + +type staticHandle struct { + fileServer http.Handler + fs http.FileSystem +} + +// NewStaticHandle A Handler responds to a Static HTTP request. +func NewStaticHandle(root string) http.Handler { + fs := filesystem.NewFileFileSystem(root, false) + return &staticHandle{ + fileServer: http.FileServer(fs), + fs: fs, + } +} + +// ServeHTTP responds to an Static HTTP request. +func (s *staticHandle) ServeHTTP(w http.ResponseWriter, r *http.Request) { + s.fileServer.ServeHTTP(w, r) +} diff --git a/router/utils.go b/router/utils.go index ebdeb6c..383ac9b 100644 --- a/router/utils.go +++ b/router/utils.go @@ -1,17 +1,17 @@ -package router - -import ( - "strings" -) - -// Method Convert multiple method strings to an slice -func Method(method ...string) []string { - var methods []string - if len(method) == 0 { - return methods - } - for _, m := range method { - methods = append(methods, strings.ToUpper(m)) - } - return methods -} +package router + +import ( + "strings" +) + +// Method Convert multiple method strings to an slice +func Method(method ...string) []string { + var methods []string + if len(method) == 0 { + return methods + } + for _, m := range method { + methods = append(methods, strings.ToUpper(m)) + } + return methods +} diff --git a/session/cookie-handler.go b/session/cookie-handler.go index 10ce172..e90173a 100644 --- a/session/cookie-handler.go +++ b/session/cookie-handler.go @@ -1,28 +1,28 @@ -package session - -type CookieHandler struct { - request Request - response Response -} - -func NewCookieHandler() *CookieHandler { - return &CookieHandler{} -} - -func (c *CookieHandler) SetRequest(req Request) { - c.request = req -} - -func (c *CookieHandler) SetResponse(res Response) { - c.response = res -} - -func (c *CookieHandler) Read(id string) string { - value, _ := c.request.Cookie(id) - - return value -} - -func (c *CookieHandler) Write(id string, data string) { - c.response.Cookie(id, data) -} +package session + +type CookieHandler struct { + request Request + response Response +} + +func NewCookieHandler() *CookieHandler { + return &CookieHandler{} +} + +func (c *CookieHandler) SetRequest(req Request) { + c.request = req +} + +func (c *CookieHandler) SetResponse(res Response) { + c.response = res +} + +func (c *CookieHandler) Read(id string) string { + value, _ := c.request.Cookie(id) + + return value +} + +func (c *CookieHandler) Write(id string, data string) { + c.response.Cookie(id, data) +} diff --git a/session/file-handler.go b/session/file-handler.go index 6ac31c8..f15104a 100644 --- a/session/file-handler.go +++ b/session/file-handler.go @@ -1,40 +1,40 @@ -package session - -import ( - "path" - "time" - - "github.com/thinkoner/thinkgo/filesystem" -) - -type FileHandler struct { - Path string - Lifetime time.Duration -} - -func (c *FileHandler) Read(id string) string { - - savePath := path.Join(c.Path, id) - - if ok, _ := filesystem.Exists(savePath); ok { - modTime, _ := filesystem.ModTime(savePath) - if modTime.After(time.Now().Add(-c.Lifetime)) { - data, err := filesystem.Get(savePath) - if err != nil { - panic(err) - } - return string(data) - } - } - return "" -} - -func (c *FileHandler) Write(id string, data string) { - savePath := path.Join(c.Path, id) - - err := filesystem.Put(savePath, data) - - if err != nil { - panic(err) - } -} +package session + +import ( + "path" + "time" + + "github.com/thinkoner/thinkgo/filesystem" +) + +type FileHandler struct { + Path string + Lifetime time.Duration +} + +func (c *FileHandler) Read(id string) string { + + savePath := path.Join(c.Path, id) + + if ok, _ := filesystem.Exists(savePath); ok { + modTime, _ := filesystem.ModTime(savePath) + if modTime.After(time.Now().Add(-c.Lifetime)) { + data, err := filesystem.Get(savePath) + if err != nil { + panic(err) + } + return string(data) + } + } + return "" +} + +func (c *FileHandler) Write(id string, data string) { + savePath := path.Join(c.Path, id) + + err := filesystem.Put(savePath, data) + + if err != nil { + panic(err) + } +} diff --git a/session/handler.go b/session/handler.go index bac0da3..34f8dd3 100644 --- a/session/handler.go +++ b/session/handler.go @@ -1,6 +1,6 @@ -package session - -type Handler interface { - Read(id string) string - Write(id string, data string) -} +package session + +type Handler interface { + Read(id string) string + Write(id string, data string) +} diff --git a/session/http.go b/session/http.go index ed31e05..5dc9021 100644 --- a/session/http.go +++ b/session/http.go @@ -1,9 +1,9 @@ -package session - -type Request interface { - Cookie(key string, value ...string) (string, error) -} - -type Response interface { - Cookie(name interface{}, params ...interface{}) error -} +package session + +type Request interface { + Cookie(key string, value ...string) (string, error) +} + +type Response interface { + Cookie(name interface{}, params ...interface{}) error +} diff --git a/session/manager.go b/session/manager.go index 0dcd79d..805c791 100644 --- a/session/manager.go +++ b/session/manager.go @@ -1,82 +1,82 @@ -package session - -import ( - "time" -) - -type Config struct { - //Default Session Driver - Driver string - - CookieName string - - //Session Lifetime - Lifetime time.Duration - - //Session Encryption - Encrypt bool - - //Session File Location - Files string -} - -type Manager struct { - store *Store - Config *Config -} - -func NewManager(config *Config) *Manager { - m := &Manager{ - Config: config, - } - m.store = m.buildSession( - m.parseStoreHandler(), - ) - return m -} - -func (m *Manager) SessionStart(req Request) *Store { - if handler, ok := m.usingCookieSessions(); ok { - handler.SetRequest(req) - } - name, _ := req.Cookie(m.store.GetName()) - m.store.SetId(name) - m.store.Start() - return m.store -} - -func (m *Manager) SessionSave(res Response) *Store { - if handler, ok := m.usingCookieSessions(); ok { - handler.SetResponse(res) - } - res.Cookie(m.store.GetName(), m.store.GetId()) - m.store.Save() - return m.store -} - -func (m *Manager) buildSession(handler Handler) *Store { - store := NewStore(m.Config.CookieName, handler) - return store -} - -func (m *Manager) usingCookieSessions() (handler *CookieHandler, ok bool) { - handler, ok = m.store.GetHandler().(*CookieHandler) - return -} - -func (m *Manager) parseStoreHandler() Handler { - var storeHandler Handler - switch m.Config.Driver { - case "cookie": - storeHandler = &CookieHandler{} - case "file": - storeHandler = &FileHandler{ - Path: m.Config.Files, - Lifetime: m.Config.Lifetime, - } - default: - panic("Unsupported session driver: " + m.Config.Driver) - } - - return storeHandler -} +package session + +import ( + "time" +) + +type Config struct { + //Default Session Driver + Driver string + + CookieName string + + //Session Lifetime + Lifetime time.Duration + + //Session Encryption + Encrypt bool + + //Session File Location + Files string +} + +type Manager struct { + store *Store + Config *Config +} + +func NewManager(config *Config) *Manager { + m := &Manager{ + Config: config, + } + m.store = m.buildSession( + m.parseStoreHandler(), + ) + return m +} + +func (m *Manager) SessionStart(req Request) *Store { + if handler, ok := m.usingCookieSessions(); ok { + handler.SetRequest(req) + } + name, _ := req.Cookie(m.store.GetName()) + m.store.SetId(name) + m.store.Start() + return m.store +} + +func (m *Manager) SessionSave(res Response) *Store { + if handler, ok := m.usingCookieSessions(); ok { + handler.SetResponse(res) + } + res.Cookie(m.store.GetName(), m.store.GetId()) + m.store.Save() + return m.store +} + +func (m *Manager) buildSession(handler Handler) *Store { + store := NewStore(m.Config.CookieName, handler) + return store +} + +func (m *Manager) usingCookieSessions() (handler *CookieHandler, ok bool) { + handler, ok = m.store.GetHandler().(*CookieHandler) + return +} + +func (m *Manager) parseStoreHandler() Handler { + var storeHandler Handler + switch m.Config.Driver { + case "cookie": + storeHandler = &CookieHandler{} + case "file": + storeHandler = &FileHandler{ + Path: m.Config.Files, + Lifetime: m.Config.Lifetime, + } + default: + panic("Unsupported session driver: " + m.Config.Driver) + } + + return storeHandler +} diff --git a/session/store.go b/session/store.go index 940f416..f76afa8 100644 --- a/session/store.go +++ b/session/store.go @@ -1,115 +1,115 @@ -package session - -import ( - "crypto/rand" - "crypto/sha1" - "encoding/base64" - "encoding/json" - "fmt" - "io" - "strconv" - "time" -) - -type Store struct { - name string - id string - handler Handler - attributes map[string]interface{} -} - -func NewStore(name string, handler Handler) *Store { - s := &Store{ - name: name, - handler: handler, - attributes: make(map[string]interface{}), - } - return s -} - -func (s *Store) GetHandler() Handler { - return s.handler -} - -func (s *Store) GetId() string { - return s.id -} - -func (s *Store) SetId(id string) { - if len(id) < 1 { - id = generateSessionId() - } - - s.id = id -} - -func (s *Store) GetName() string { - return s.name -} - -func (s *Store) Start() { - data := s.handler.Read(s.GetId()) - - decodeData, _ := base64.StdEncoding.DecodeString(data) - - udata := make(map[string]interface{}) - - json.Unmarshal(decodeData, &udata) - - for k, v := range udata { - s.attributes[k] = v - } -} - -func (s *Store) Get(name string, value ...interface{}) interface{} { - if v, ok := s.attributes[name]; ok { - return v - } - if len(value) > 0 { - return value[0] - } - return nil -} - -func (s *Store) Set(name string, value interface{}) { - s.attributes[name] = value -} - -func (s *Store) All() map[string]interface{} { - return s.attributes -} - -func (s *Store) Remove(name string) interface{} { - value := s.Get(name) - delete(s.attributes, name) - return value -} - -func (s *Store) Forget(names ...string) { - for _, name := range names { - delete(s.attributes, name) - } -} - -func (s *Store) Clear() { - s.attributes = nil -} - -func (s *Store) Save() { - data, _ := json.Marshal(s.attributes) - - encodeData := base64.StdEncoding.EncodeToString(data) - s.handler.Write(s.GetId(), encodeData) -} - -func generateSessionId() string { - id := strconv.FormatInt(time.Now().UnixNano(), 10) - b := make([]byte, 48) - io.ReadFull(rand.Reader, b) - id = id + base64.URLEncoding.EncodeToString(b) - - h := sha1.New() - h.Write([]byte(id)) - - return fmt.Sprintf("%x", h.Sum(nil)) -} +package session + +import ( + "crypto/rand" + "crypto/sha1" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "strconv" + "time" +) + +type Store struct { + name string + id string + handler Handler + attributes map[string]interface{} +} + +func NewStore(name string, handler Handler) *Store { + s := &Store{ + name: name, + handler: handler, + attributes: make(map[string]interface{}), + } + return s +} + +func (s *Store) GetHandler() Handler { + return s.handler +} + +func (s *Store) GetId() string { + return s.id +} + +func (s *Store) SetId(id string) { + if len(id) < 1 { + id = generateSessionId() + } + + s.id = id +} + +func (s *Store) GetName() string { + return s.name +} + +func (s *Store) Start() { + data := s.handler.Read(s.GetId()) + + decodeData, _ := base64.StdEncoding.DecodeString(data) + + udata := make(map[string]interface{}) + + json.Unmarshal(decodeData, &udata) + + for k, v := range udata { + s.attributes[k] = v + } +} + +func (s *Store) Get(name string, value ...interface{}) interface{} { + if v, ok := s.attributes[name]; ok { + return v + } + if len(value) > 0 { + return value[0] + } + return nil +} + +func (s *Store) Set(name string, value interface{}) { + s.attributes[name] = value +} + +func (s *Store) All() map[string]interface{} { + return s.attributes +} + +func (s *Store) Remove(name string) interface{} { + value := s.Get(name) + delete(s.attributes, name) + return value +} + +func (s *Store) Forget(names ...string) { + for _, name := range names { + delete(s.attributes, name) + } +} + +func (s *Store) Clear() { + s.attributes = nil +} + +func (s *Store) Save() { + data, _ := json.Marshal(s.attributes) + + encodeData := base64.StdEncoding.EncodeToString(data) + s.handler.Write(s.GetId(), encodeData) +} + +func generateSessionId() string { + id := strconv.FormatInt(time.Now().UnixNano(), 10) + b := make([]byte, 48) + io.ReadFull(rand.Reader, b) + id = id + base64.URLEncoding.EncodeToString(b) + + h := sha1.New() + h.Write([]byte(id)) + + return fmt.Sprintf("%x", h.Sum(nil)) +} diff --git a/thinkgo.go b/thinkgo.go index 4b64946..8b31f2b 100644 --- a/thinkgo.go +++ b/thinkgo.go @@ -1,100 +1,100 @@ -package thinkgo - -import ( - "fmt" - "net/http" - - "time" - - "github.com/thinkoner/thinkgo/app" - "github.com/thinkoner/thinkgo/config" - "github.com/thinkoner/thinkgo/helper" - "github.com/thinkoner/thinkgo/router" - "github.com/thinkoner/thinkgo/view" -) - -var application *app.Application - -type registerRouteFunc func(route *router.Route) - -type registerConfigFunc func() - -type ThinkGo struct { - App *app.Application - handlers []app.HandlerFunc -} - -// BootStrap Create The Application -func BootStrap() *ThinkGo { - application = app.NewApplication() - think := &ThinkGo{ - App: application, - } - think.bootView() - think.bootRoute() - return think -} - -// RegisterRoute Register Route -func (think *ThinkGo) RegisterRoute(register registerRouteFunc) { - route := think.App.GetRoute() - defer route.Register() - register(route) -} - -// RegisterConfig Register Config -func (think *ThinkGo) RegisterConfig(register registerConfigFunc) { - register() -} - -// RegisterConfig Register Config -func (think *ThinkGo) RegisterHandler(handler app.HandlerFunc) { - think.handlers = append(think.handlers, handler) -} - -// Run thinkgo application. -// Run() default run on HttpPort -// Run("localhost") -// Run(":9011") -// Run("127.0.0.1:9011") -func (think *ThinkGo) Run(params ...string) { - var err error - var endRunning = make(chan bool, 1) - - var addrs = helper.ParseAddr(params...) - - // register route handler - think.RegisterHandler(app.NewRouteHandler) - - pipeline := NewPipeline() - for _, h := range think.handlers { - pipeline.Pipe(h(think.App)) - } - - go func() { - - fmt.Printf("context server Running on http://%s", addrs) - - err = http.ListenAndServe(addrs, pipeline) - - if err != nil { - fmt.Println(err.Error()) - time.Sleep(100 * time.Microsecond) - endRunning <- true - } - }() - - <-endRunning -} - -func (think *ThinkGo) bootView() { - v := view.NewView() - v.SetPath(config.View.Path) - think.App.RegisterView(v) -} - -func (think *ThinkGo) bootRoute() { - r := router.NewRoute() - r.Statics(config.Route.Static) - think.App.RegisterRoute(r) -} +package thinkgo + +import ( + "fmt" + "net/http" + + "time" + + "github.com/thinkoner/thinkgo/app" + "github.com/thinkoner/thinkgo/config" + "github.com/thinkoner/thinkgo/helper" + "github.com/thinkoner/thinkgo/router" + "github.com/thinkoner/thinkgo/view" +) + +var application *app.Application + +type registerRouteFunc func(route *router.Route) + +type registerConfigFunc func() + +type ThinkGo struct { + App *app.Application + handlers []app.HandlerFunc +} + +// BootStrap Create The Application +func BootStrap() *ThinkGo { + application = app.NewApplication() + think := &ThinkGo{ + App: application, + } + think.bootView() + think.bootRoute() + return think +} + +// RegisterRoute Register Route +func (think *ThinkGo) RegisterRoute(register registerRouteFunc) { + route := think.App.GetRoute() + defer route.Register() + register(route) +} + +// RegisterConfig Register Config +func (think *ThinkGo) RegisterConfig(register registerConfigFunc) { + register() +} + +// RegisterConfig Register Config +func (think *ThinkGo) RegisterHandler(handler app.HandlerFunc) { + think.handlers = append(think.handlers, handler) +} + +// Run thinkgo application. +// Run() default run on HttpPort +// Run("localhost") +// Run(":9011") +// Run("127.0.0.1:9011") +func (think *ThinkGo) Run(params ...string) { + var err error + var endRunning = make(chan bool, 1) + + var addrs = helper.ParseAddr(params...) + + // register route handler + think.RegisterHandler(app.NewRouteHandler) + + pipeline := NewPipeline() + for _, h := range think.handlers { + pipeline.Pipe(h(think.App)) + } + + go func() { + + fmt.Printf("context server Running on http://%s", addrs) + + err = http.ListenAndServe(addrs, pipeline) + + if err != nil { + fmt.Println(err.Error()) + time.Sleep(100 * time.Microsecond) + endRunning <- true + } + }() + + <-endRunning +} + +func (think *ThinkGo) bootView() { + v := view.NewView() + v.SetPath(config.View.Path) + think.App.RegisterView(v) +} + +func (think *ThinkGo) bootRoute() { + r := router.NewRoute() + r.Statics(config.Route.Static) + think.App.RegisterRoute(r) +} diff --git a/thinkgo_test.go b/thinkgo_test.go index 1b28aab..cb604e2 100644 --- a/thinkgo_test.go +++ b/thinkgo_test.go @@ -1,67 +1,67 @@ -package thinkgo - -import ( - "crypto/tls" - "io/ioutil" - "net/http" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/thinkoner/thinkgo/context" - "github.com/thinkoner/thinkgo/router" -) - -func testRequest(t *testing.T, url string) { - tr := &http.Transport{ - TLSClientConfig: &tls.Config{ - InsecureSkipVerify: true, - }, - } - client := &http.Client{Transport: tr} - - resp, err := client.Get(url) - assert.NoError(t, err) - defer resp.Body.Close() - - body, ioerr := ioutil.ReadAll(resp.Body) - assert.NoError(t, ioerr) - assert.Equal(t, "it worked", string(body), "resp body should match") - assert.Equal(t, "200 OK", resp.Status, "should get a 200") -} - -func TestRunEmpty(t *testing.T) { - app := BootStrap() - - go func() { - app.RegisterRoute(func(route *router.Route) { - route.Get("/", func(req *context.Request) *context.Response { - return Text("it worked") - }) - }) - // listen and serve on 0.0.0.0:9011 - app.Run() - }() - - time.Sleep(5 * time.Millisecond) - - testRequest(t, "http://localhost:9011/") -} - -func TestRunWithPort(t *testing.T) { - app := BootStrap() - - go func() { - app.RegisterRoute(func(route *router.Route) { - route.Get("/", func(req *context.Request) *context.Response { - return Text("it worked") - }) - }) - // listen and serve on 0.0.0.0:9011 - app.Run(":8100") - }() - - time.Sleep(5 * time.Millisecond) - - testRequest(t, "http://localhost:8100/") -} +package thinkgo + +import ( + "crypto/tls" + "io/ioutil" + "net/http" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/thinkoner/thinkgo/context" + "github.com/thinkoner/thinkgo/router" +) + +func testRequest(t *testing.T, url string) { + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + client := &http.Client{Transport: tr} + + resp, err := client.Get(url) + assert.NoError(t, err) + defer resp.Body.Close() + + body, ioerr := ioutil.ReadAll(resp.Body) + assert.NoError(t, ioerr) + assert.Equal(t, "it worked", string(body), "resp body should match") + assert.Equal(t, "200 OK", resp.Status, "should get a 200") +} + +func TestRunEmpty(t *testing.T) { + app := BootStrap() + + go func() { + app.RegisterRoute(func(route *router.Route) { + route.Get("/", func(req *context.Request) *context.Response { + return Text("it worked") + }) + }) + // listen and serve on 0.0.0.0:9011 + app.Run() + }() + + time.Sleep(5 * time.Millisecond) + + testRequest(t, "http://localhost:9011/") +} + +func TestRunWithPort(t *testing.T) { + app := BootStrap() + + go func() { + app.RegisterRoute(func(route *router.Route) { + route.Get("/", func(req *context.Request) *context.Response { + return Text("it worked") + }) + }) + // listen and serve on 0.0.0.0:9011 + app.Run(":8100") + }() + + time.Sleep(5 * time.Millisecond) + + testRequest(t, "http://localhost:8100/") +} diff --git a/utils.go b/utils.go index 204dc00..434b6d7 100644 --- a/utils.go +++ b/utils.go @@ -1 +1 @@ -package thinkgo +package thinkgo diff --git a/view.go b/view.go index 6f10b64..0350142 100644 --- a/view.go +++ b/view.go @@ -1,5 +1,5 @@ -package thinkgo - -type View interface { - Render(name string, data interface{}) []byte -} +package thinkgo + +type View interface { + Render(name string, data interface{}) []byte +} diff --git a/view/template.go b/view/template.go index 380a855..a4fe3c3 100644 --- a/view/template.go +++ b/view/template.go @@ -1,28 +1,28 @@ -package view - -//import ( -// "strings" -// "github.com/thinkoner/thinkgo/app" -//) -// -//type Template struct { -// ViewPath string -//} -// -//func NewTemplate() *Template { -// t := &Template{} -// t.loadDefault() -// -// return t -//} -// -//func (t *Template) loadDefault() { -// path, err := app.Config.GetString("view.path") -// if err != nil { -// panic(err) -// } -// -// path = WorkPath(path) -// -// t.ViewPath = strings.TrimRight(path, "/") + "/" -//} +package view + +//import ( +// "strings" +// "github.com/thinkoner/thinkgo/app" +//) +// +//type Template struct { +// ViewPath string +//} +// +//func NewTemplate() *Template { +// t := &Template{} +// t.loadDefault() +// +// return t +//} +// +//func (t *Template) loadDefault() { +// path, err := app.Config.GetString("view.path") +// if err != nil { +// panic(err) +// } +// +// path = WorkPath(path) +// +// t.ViewPath = strings.TrimRight(path, "/") + "/" +//} diff --git a/view/view.go b/view/view.go index 18fe2d9..5e83aec 100644 --- a/view/view.go +++ b/view/view.go @@ -1,56 +1,56 @@ -package view - -import ( - "bytes" - "html/template" - "path" - "strings" - - "github.com/thinkoner/thinkgo/filesystem" - "github.com/thinkoner/thinkgo/helper" -) - -type View struct { - path string - // Engine -} - -func NewView() *View { - v := &View{} - return v -} - -func (v *View) SetPath(path string) { - path = helper.WorkPath(path) - - v.path = strings.TrimRight(path, "/") + "/" -} - -func (v *View) loadEngine() { - -} - -func (v *View) Render(name string, data interface{}) []byte { - file := name - - if e, _ := filesystem.Exists(file); !e { - file = path.Join(v.path, name) - } - - t := template.New("") - if _, err := t.ParseGlob(path.Join(v.path, "*")); err != nil { - panic(err) - } - - tmpl, err := t.ParseFiles(file) - if err != nil { - panic(err) - // return context.ErrorResponse() - } - - var buf bytes.Buffer - - tmpl.ExecuteTemplate(&buf, name, data) - - return buf.Bytes() -} +package view + +import ( + "bytes" + "html/template" + "path" + "strings" + + "github.com/thinkoner/thinkgo/filesystem" + "github.com/thinkoner/thinkgo/helper" +) + +type View struct { + path string + // Engine +} + +func NewView() *View { + v := &View{} + return v +} + +func (v *View) SetPath(path string) { + path = helper.WorkPath(path) + + v.path = strings.TrimRight(path, "/") + "/" +} + +func (v *View) loadEngine() { + +} + +func (v *View) Render(name string, data interface{}) []byte { + file := name + + if e, _ := filesystem.Exists(file); !e { + file = path.Join(v.path, name) + } + + t := template.New("") + if _, err := t.ParseGlob(path.Join(v.path, "*")); err != nil { + panic(err) + } + + tmpl, err := t.ParseFiles(file) + if err != nil { + panic(err) + // return context.ErrorResponse() + } + + var buf bytes.Buffer + + tmpl.ExecuteTemplate(&buf, name, data) + + return buf.Bytes() +}