├── .github └── workflows │ └── go.yml ├── .gitignore ├── LICENSE ├── README.md ├── codecov.yml ├── couchbase └── couchbase.go ├── file.go ├── file_test.go ├── flash.go ├── flash_test.go ├── go.mod ├── go.sum ├── ledis ├── ledis.go ├── ledis.goconvey └── ledis_test.go ├── memcache ├── memcache.go ├── memcache.goconvey └── memcache_test.go ├── memory.go ├── memory_test.go ├── mysql ├── mysql.go ├── mysql.goconvey └── mysql_test.go ├── nodb ├── nodb.go ├── nodb.goconvey └── nodb_test.go ├── postgres ├── postgres.go ├── postgres.goconvey └── postgres_test.go ├── redis ├── redis.go ├── redis.goconvey └── redis_test.go ├── session.go ├── session_test.go └── utils.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | env: 7 | GOPROXY: "https://proxy.golang.org" 8 | 9 | jobs: 10 | lint: 11 | name: Lint 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v3 16 | - name: Run golangci-lint 17 | uses: golangci/golangci-lint-action@v3 18 | with: 19 | version: latest 20 | args: --timeout=30m 21 | 22 | test: 23 | name: Test 24 | strategy: 25 | matrix: 26 | go-version: [ 1.22.x, 1.23.x ] 27 | platform: [ubuntu-latest, macos-latest, windows-latest] 28 | runs-on: ${{ matrix.platform }} 29 | steps: 30 | - name: Checkout code 31 | uses: actions/checkout@v3 32 | - name: Install Go 33 | uses: actions/setup-go@v4 34 | with: 35 | go-version: ${{ matrix.go-version }} 36 | - name: Run tests with coverage 37 | run: go test -shuffle=on -v -race -coverprofile=coverage -covermode=atomic 38 | - name: Upload coverage report to Codecov 39 | uses: codecov/codecov-action@v1.5.0 40 | with: 41 | file: ./coverage 42 | flags: unittests 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ledis/tmp.db 2 | nodb/tmp.db 3 | /vendor 4 | /.idea 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # session 2 | 3 | [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/go-macaron/session/Go?logo=github&style=for-the-badge)](https://github.com/go-macaron/session/actions?query=workflow%3AGo) 4 | [![codecov](https://img.shields.io/codecov/c/github/go-macaron/session/master?logo=codecov&style=for-the-badge)](https://codecov.io/gh/go-macaron/session) 5 | [![GoDoc](https://img.shields.io/badge/GoDoc-Reference-blue?style=for-the-badge&logo=go)](https://pkg.go.dev/github.com/go-macaron/session?tab=doc) 6 | [![Sourcegraph](https://img.shields.io/badge/view%20on-Sourcegraph-brightgreen.svg?style=for-the-badge&logo=sourcegraph)](https://sourcegraph.com/github.com/go-macaron/session) 7 | 8 | Middleware session provides session management for [Macaron](https://github.com/go-macaron/macaron). It can use many session providers, including memory, file, Redis, Memcache, PostgreSQL, MySQL, Couchbase, Ledis and Nodb. 9 | 10 | ### Installation 11 | 12 | The minimum requirement of Go is 1.6 (*1.13 if using Redis, 1.10 if using MySQL*). 13 | 14 | go get github.com/go-macaron/session 15 | 16 | ## Getting Help 17 | 18 | - [API Reference](https://gowalker.org/github.com/go-macaron/session) 19 | - [Documentation](https://go-macaron.com/middlewares/session) 20 | 21 | ## Credits 22 | 23 | This package is a modified version of [beego/session](https://github.com/astaxie/beego/tree/master/session). 24 | 25 | ## License 26 | 27 | This project is under the Apache License, Version 2.0. See the [LICENSE](LICENSE) file for the full license text. 28 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: "60...95" 3 | status: 4 | project: 5 | default: 6 | threshold: 1% 7 | 8 | comment: 9 | layout: 'diff, files' 10 | -------------------------------------------------------------------------------- /couchbase/couchbase.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Beego Authors 2 | // Copyright 2014 The Macaron Authors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 5 | // not use this file except in compliance with the License. You may obtain 6 | // a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations 14 | // under the License. 15 | 16 | package session 17 | 18 | import ( 19 | "strings" 20 | "sync" 21 | 22 | "github.com/couchbase/go-couchbase" 23 | 24 | "github.com/go-macaron/session" 25 | ) 26 | 27 | // CouchbaseSessionStore represents a couchbase session store implementation. 28 | type CouchbaseSessionStore struct { 29 | b *couchbase.Bucket 30 | sid string 31 | lock sync.RWMutex 32 | data map[interface{}]interface{} 33 | maxlifetime int64 34 | } 35 | 36 | // Set sets value to given key in session. 37 | func (s *CouchbaseSessionStore) Set(key, val interface{}) error { 38 | s.lock.Lock() 39 | defer s.lock.Unlock() 40 | 41 | s.data[key] = val 42 | return nil 43 | } 44 | 45 | // Get gets value by given key in session. 46 | func (s *CouchbaseSessionStore) Get(key interface{}) interface{} { 47 | s.lock.RLock() 48 | defer s.lock.RUnlock() 49 | 50 | return s.data[key] 51 | } 52 | 53 | // Delete delete a key from session. 54 | func (s *CouchbaseSessionStore) Delete(key interface{}) error { 55 | s.lock.Lock() 56 | defer s.lock.Unlock() 57 | 58 | delete(s.data, key) 59 | return nil 60 | } 61 | 62 | // ID returns current session ID. 63 | func (s *CouchbaseSessionStore) ID() string { 64 | return s.sid 65 | } 66 | 67 | // Release releases resource and save data to provider. 68 | func (s *CouchbaseSessionStore) Release() error { 69 | defer s.b.Close() 70 | 71 | // Skip encoding if the data is empty 72 | if len(s.data) == 0 { 73 | return nil 74 | } 75 | 76 | data, err := session.EncodeGob(s.data) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | return s.b.Set(s.sid, int(s.maxlifetime), data) 82 | } 83 | 84 | // Flush deletes all session data. 85 | func (s *CouchbaseSessionStore) Flush() error { 86 | s.lock.Lock() 87 | defer s.lock.Unlock() 88 | 89 | s.data = make(map[interface{}]interface{}) 90 | return nil 91 | } 92 | 93 | // CouchbaseProvider represents a couchbase session provider implementation. 94 | type CouchbaseProvider struct { 95 | maxlifetime int64 96 | connStr string 97 | pool string 98 | bucket string 99 | b *couchbase.Bucket 100 | } 101 | 102 | func (cp *CouchbaseProvider) getBucket() *couchbase.Bucket { 103 | c, err := couchbase.Connect(cp.connStr) 104 | if err != nil { 105 | return nil 106 | } 107 | 108 | pool, err := c.GetPool(cp.pool) 109 | if err != nil { 110 | return nil 111 | } 112 | 113 | bucket, err := pool.GetBucket(cp.bucket) 114 | if err != nil { 115 | return nil 116 | } 117 | 118 | return bucket 119 | } 120 | 121 | // Init initializes memory session provider. 122 | // connStr is couchbase server REST/JSON URL 123 | // e.g. http://host:port/, Pool, Bucket 124 | func (p *CouchbaseProvider) Init(maxlifetime int64, connStr string) error { 125 | p.maxlifetime = maxlifetime 126 | configs := strings.Split(connStr, ",") 127 | if len(configs) > 0 { 128 | p.connStr = configs[0] 129 | } 130 | if len(configs) > 1 { 131 | p.pool = configs[1] 132 | } 133 | if len(configs) > 2 { 134 | p.bucket = configs[2] 135 | } 136 | 137 | return nil 138 | } 139 | 140 | // Read returns raw session store by session ID. 141 | func (p *CouchbaseProvider) Read(sid string) (session.RawStore, error) { 142 | p.b = p.getBucket() 143 | 144 | var doc []byte 145 | 146 | err := p.b.Get(sid, &doc) 147 | if err != nil { 148 | return nil, err 149 | } 150 | 151 | var kv map[interface{}]interface{} 152 | if doc == nil { 153 | kv = make(map[interface{}]interface{}) 154 | } else { 155 | kv, err = session.DecodeGob(doc) 156 | if err != nil { 157 | return nil, err 158 | } 159 | } 160 | 161 | cs := &CouchbaseSessionStore{b: p.b, sid: sid, data: kv, maxlifetime: p.maxlifetime} 162 | return cs, nil 163 | } 164 | 165 | // Exist returns true if session with given ID exists. 166 | func (p *CouchbaseProvider) Exist(sid string) bool { 167 | p.b = p.getBucket() 168 | defer p.b.Close() 169 | 170 | var doc []byte 171 | 172 | if err := p.b.Get(sid, &doc); err != nil || doc == nil { 173 | return false 174 | } else { 175 | return true 176 | } 177 | } 178 | 179 | // Destory deletes a session by session ID. 180 | func (p *CouchbaseProvider) Destory(sid string) error { 181 | p.b = p.getBucket() 182 | defer p.b.Close() 183 | 184 | return p.b.Delete(sid) 185 | } 186 | 187 | // Regenerate regenerates a session store from old session ID to new one. 188 | func (p *CouchbaseProvider) Regenerate(oldsid, sid string) (session.RawStore, error) { 189 | p.b = p.getBucket() 190 | 191 | var doc []byte 192 | if err := p.b.Get(oldsid, &doc); err != nil || doc == nil { 193 | err := p.b.Set(sid, int(p.maxlifetime), "") 194 | if err != nil { 195 | return nil, err 196 | } 197 | } else { 198 | err := p.b.Delete(oldsid) 199 | if err != nil { 200 | return nil, err 201 | } 202 | _, _ = p.b.Add(sid, int(p.maxlifetime), doc) 203 | } 204 | 205 | err := p.b.Get(sid, &doc) 206 | if err != nil { 207 | return nil, err 208 | } 209 | var kv map[interface{}]interface{} 210 | if doc == nil { 211 | kv = make(map[interface{}]interface{}) 212 | } else { 213 | kv, err = session.DecodeGob(doc) 214 | if err != nil { 215 | return nil, err 216 | } 217 | } 218 | 219 | cs := &CouchbaseSessionStore{b: p.b, sid: sid, data: kv, maxlifetime: p.maxlifetime} 220 | return cs, nil 221 | } 222 | 223 | // Count counts and returns number of sessions. 224 | func (p *CouchbaseProvider) Count() int { 225 | // FIXME 226 | return 0 227 | } 228 | 229 | // GC calls GC to clean expired sessions. 230 | func (p *CouchbaseProvider) GC() {} 231 | 232 | func init() { 233 | session.Register("couchbase", &CouchbaseProvider{}) 234 | } 235 | -------------------------------------------------------------------------------- /file.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Beego Authors 2 | // Copyright 2014 The Macaron Authors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 5 | // not use this file except in compliance with the License. You may obtain 6 | // a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations 14 | // under the License. 15 | 16 | package session 17 | 18 | import ( 19 | "fmt" 20 | "io/ioutil" 21 | "log" 22 | "os" 23 | "path" 24 | "path/filepath" 25 | "sync" 26 | "time" 27 | 28 | "github.com/unknwon/com" 29 | ) 30 | 31 | // FileStore represents a file session store implementation. 32 | type FileStore struct { 33 | p *FileProvider 34 | sid string 35 | lock sync.RWMutex 36 | data map[interface{}]interface{} 37 | } 38 | 39 | // NewFileStore creates and returns a file session store. 40 | func NewFileStore(p *FileProvider, sid string, kv map[interface{}]interface{}) *FileStore { 41 | return &FileStore{ 42 | p: p, 43 | sid: sid, 44 | data: kv, 45 | } 46 | } 47 | 48 | // Set sets value to given key in session. 49 | func (s *FileStore) Set(key, val interface{}) error { 50 | s.lock.Lock() 51 | defer s.lock.Unlock() 52 | 53 | s.data[key] = val 54 | return nil 55 | } 56 | 57 | // Get gets value by given key in session. 58 | func (s *FileStore) Get(key interface{}) interface{} { 59 | s.lock.RLock() 60 | defer s.lock.RUnlock() 61 | 62 | return s.data[key] 63 | } 64 | 65 | // Delete delete a key from session. 66 | func (s *FileStore) Delete(key interface{}) error { 67 | s.lock.Lock() 68 | defer s.lock.Unlock() 69 | 70 | delete(s.data, key) 71 | return nil 72 | } 73 | 74 | // ID returns current session ID. 75 | func (s *FileStore) ID() string { 76 | return s.sid 77 | } 78 | 79 | // Release releases resource and save data to provider. 80 | func (s *FileStore) Release() error { 81 | s.p.lock.Lock() 82 | defer s.p.lock.Unlock() 83 | 84 | // Skip encoding if the data is empty 85 | if len(s.data) == 0 { 86 | return nil 87 | } 88 | 89 | data, err := EncodeGob(s.data) 90 | if err != nil { 91 | return err 92 | } 93 | 94 | return ioutil.WriteFile(s.p.filepath(s.sid), data, 0600) 95 | } 96 | 97 | // Flush deletes all session data. 98 | func (s *FileStore) Flush() error { 99 | s.lock.Lock() 100 | defer s.lock.Unlock() 101 | 102 | s.data = make(map[interface{}]interface{}) 103 | return nil 104 | } 105 | 106 | // FileProvider represents a file session provider implementation. 107 | type FileProvider struct { 108 | lock sync.RWMutex 109 | maxlifetime int64 110 | rootPath string 111 | } 112 | 113 | // Init initializes file session provider with given root path. 114 | func (p *FileProvider) Init(maxlifetime int64, rootPath string) error { 115 | p.lock.Lock() 116 | p.maxlifetime = maxlifetime 117 | p.rootPath = rootPath 118 | p.lock.Unlock() 119 | return nil 120 | } 121 | 122 | func (p *FileProvider) filepath(sid string) string { 123 | return path.Join(p.rootPath, string(sid[0]), string(sid[1]), sid) 124 | } 125 | 126 | // Read returns raw session store by session ID. 127 | func (p *FileProvider) Read(sid string) (_ RawStore, err error) { 128 | filename := p.filepath(sid) 129 | if err = os.MkdirAll(path.Dir(filename), 0700); err != nil { 130 | return nil, err 131 | } 132 | p.lock.RLock() 133 | defer p.lock.RUnlock() 134 | 135 | var f *os.File 136 | expired := true 137 | if com.IsFile(filename) { 138 | modTime, err := com.FileMTime(filename) 139 | if err != nil { 140 | return nil, err 141 | } 142 | expired = (modTime + p.maxlifetime) < time.Now().Unix() 143 | } 144 | if !expired { 145 | f, err = os.OpenFile(filename, os.O_RDONLY, 0600) 146 | } else { 147 | f, err = os.Create(filename) 148 | } 149 | if err != nil { 150 | return nil, err 151 | } 152 | defer f.Close() 153 | 154 | if err = os.Chtimes(filename, time.Now(), time.Now()); err != nil { 155 | return nil, err 156 | } 157 | 158 | var kv map[interface{}]interface{} 159 | data, err := ioutil.ReadAll(f) 160 | if err != nil { 161 | return nil, err 162 | } 163 | if len(data) == 0 { 164 | kv = make(map[interface{}]interface{}) 165 | } else { 166 | kv, err = DecodeGob(data) 167 | if err != nil { 168 | return nil, err 169 | } 170 | } 171 | return NewFileStore(p, sid, kv), nil 172 | } 173 | 174 | // Exist returns true if session with given ID exists. 175 | func (p *FileProvider) Exist(sid string) bool { 176 | p.lock.RLock() 177 | defer p.lock.RUnlock() 178 | return com.IsFile(p.filepath(sid)) 179 | } 180 | 181 | // Destory deletes a session by session ID. 182 | func (p *FileProvider) Destory(sid string) error { 183 | p.lock.Lock() 184 | defer p.lock.Unlock() 185 | return os.Remove(p.filepath(sid)) 186 | } 187 | 188 | func (p *FileProvider) regenerate(oldsid, sid string) (err error) { 189 | p.lock.Lock() 190 | defer p.lock.Unlock() 191 | 192 | filename := p.filepath(sid) 193 | if com.IsExist(filename) { 194 | return fmt.Errorf("new sid '%s' already exists", sid) 195 | } 196 | 197 | oldname := p.filepath(oldsid) 198 | if !com.IsFile(oldname) { 199 | data, err := EncodeGob(make(map[interface{}]interface{})) 200 | if err != nil { 201 | return err 202 | } 203 | if err = os.MkdirAll(path.Dir(oldname), 0700); err != nil { 204 | return err 205 | } 206 | if err = ioutil.WriteFile(oldname, data, 0600); err != nil { 207 | return err 208 | } 209 | } 210 | 211 | if err = os.MkdirAll(path.Dir(filename), 0700); err != nil { 212 | return err 213 | } 214 | if err = os.Rename(oldname, filename); err != nil { 215 | return err 216 | } 217 | return nil 218 | } 219 | 220 | // Regenerate regenerates a session store from old session ID to new one. 221 | func (p *FileProvider) Regenerate(oldsid, sid string) (_ RawStore, err error) { 222 | if err := p.regenerate(oldsid, sid); err != nil { 223 | return nil, err 224 | } 225 | 226 | return p.Read(sid) 227 | } 228 | 229 | // Count counts and returns number of sessions. 230 | func (p *FileProvider) Count() int { 231 | count := 0 232 | if err := filepath.Walk(p.rootPath, func(path string, fi os.FileInfo, err error) error { 233 | if err != nil { 234 | return err 235 | } 236 | 237 | if !fi.IsDir() { 238 | count++ 239 | } 240 | return nil 241 | }); err != nil { 242 | log.Printf("error counting session files: %v", err) 243 | return 0 244 | } 245 | return count 246 | } 247 | 248 | // GC calls GC to clean expired sessions. 249 | func (p *FileProvider) GC() { 250 | p.lock.RLock() 251 | defer p.lock.RUnlock() 252 | 253 | if !com.IsExist(p.rootPath) { 254 | return 255 | } 256 | 257 | if err := filepath.Walk(p.rootPath, func(path string, fi os.FileInfo, err error) error { 258 | if err != nil { 259 | return err 260 | } 261 | 262 | if !fi.IsDir() && 263 | (fi.ModTime().Unix()+p.maxlifetime) < time.Now().Unix() { 264 | return os.Remove(path) 265 | } 266 | return nil 267 | }); err != nil { 268 | log.Printf("error garbage collecting session files: %v", err) 269 | } 270 | } 271 | 272 | func init() { 273 | Register("file", &FileProvider{}) 274 | } 275 | -------------------------------------------------------------------------------- /file_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Macaron Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package session 16 | 17 | import ( 18 | "os" 19 | "path" 20 | "testing" 21 | 22 | . "github.com/smartystreets/goconvey/convey" 23 | ) 24 | 25 | func Test_FileProvider(t *testing.T) { 26 | Convey("Test file session provider", t, func() { 27 | dir := path.Join(os.TempDir(), "data/sessions") 28 | os.RemoveAll(dir) 29 | testProvider(Options{ 30 | Provider: "file", 31 | ProviderConfig: dir, 32 | }) 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /flash.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Macaron Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package session 16 | 17 | import ( 18 | "net/url" 19 | 20 | "gopkg.in/macaron.v1" 21 | ) 22 | 23 | type Flash struct { 24 | ctx *macaron.Context 25 | url.Values 26 | ErrorMsg, WarningMsg, InfoMsg, SuccessMsg string 27 | } 28 | 29 | func (f *Flash) set(name, msg string, current ...bool) { 30 | isShow := false 31 | if (len(current) == 0 && macaron.FlashNow) || 32 | (len(current) > 0 && current[0]) { 33 | isShow = true 34 | } 35 | 36 | if isShow { 37 | f.ctx.Data["Flash"] = f 38 | } else { 39 | f.Set(name, msg) 40 | } 41 | } 42 | 43 | func (f *Flash) Error(msg string, current ...bool) { 44 | f.ErrorMsg = msg 45 | f.set("error", msg, current...) 46 | } 47 | 48 | func (f *Flash) Warning(msg string, current ...bool) { 49 | f.WarningMsg = msg 50 | f.set("warning", msg, current...) 51 | } 52 | 53 | func (f *Flash) Info(msg string, current ...bool) { 54 | f.InfoMsg = msg 55 | f.set("info", msg, current...) 56 | } 57 | 58 | func (f *Flash) Success(msg string, current ...bool) { 59 | f.SuccessMsg = msg 60 | f.set("success", msg, current...) 61 | } 62 | -------------------------------------------------------------------------------- /flash_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Macaron Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package session 16 | 17 | import ( 18 | "net/http" 19 | "net/http/httptest" 20 | "testing" 21 | 22 | . "github.com/smartystreets/goconvey/convey" 23 | "gopkg.in/macaron.v1" 24 | ) 25 | 26 | func Test_Flash(t *testing.T) { 27 | Convey("Test flash", t, func() { 28 | m := macaron.New() 29 | m.Use(Sessioner()) 30 | m.Get("/set", func(f *Flash) string { 31 | f.Success("success") 32 | f.Error("error") 33 | f.Warning("warning") 34 | f.Info("info") 35 | return "" 36 | }) 37 | m.Get("/get", func() {}) 38 | 39 | resp := httptest.NewRecorder() 40 | req, err := http.NewRequest("GET", "/set", nil) 41 | So(err, ShouldBeNil) 42 | m.ServeHTTP(resp, req) 43 | 44 | resp = httptest.NewRecorder() 45 | req, err = http.NewRequest("GET", "/get", nil) 46 | So(err, ShouldBeNil) 47 | req.Header.Set("Cookie", "macaron_flash=error%3Derror%26info%3Dinfo%26success%3Dsuccess%26warning%3Dwarning; Path=/") 48 | m.ServeHTTP(resp, req) 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/go-macaron/session 2 | 3 | go 1.12 4 | 5 | require ( 6 | gitea.com/lunny/nodb v0.0.0-20200923032308-3238c4655727 7 | github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 8 | github.com/couchbase/go-couchbase v0.0.0-20201026062457-7b3be89bbd89 9 | github.com/couchbase/gomemcached v0.1.1 // indirect 10 | github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67 // indirect 11 | github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 // indirect 12 | github.com/edsrzf/mmap-go v1.0.0 // indirect 13 | github.com/go-macaron/inject v0.0.0-20200308113650-138e5925c53b // indirect 14 | github.com/go-redis/redis/v8 v8.11.5 15 | github.com/go-sql-driver/mysql v1.4.1 16 | github.com/lib/pq v1.2.0 17 | github.com/pkg/errors v0.9.1 // indirect 18 | github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 // indirect 19 | github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92 20 | github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d // indirect 21 | github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 22 | github.com/stretchr/testify v1.6.1 // indirect 23 | github.com/unknwon/com v1.0.1 24 | golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 // indirect 25 | google.golang.org/appengine v1.6.7 // indirect 26 | gopkg.in/ini.v1 v1.62.0 27 | gopkg.in/macaron.v1 v1.4.0 28 | ) 29 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e h1:r1en/D7xJmcY24VkHkjkcJFa+7ZWubVWPBrvsHkmHxk= 2 | gitea.com/lunny/log v0.0.0-20190322053110-01b5df579c4e/go.mod h1:uJEsN4LQpeGYRCjuPXPZBClU7N5pWzGuyF4uqLpE/e0= 3 | gitea.com/lunny/nodb v0.0.0-20200923032308-3238c4655727 h1:ZF2Bd6rqVlwhIDhYiS0uGYcT+GaVNGjuKVJkTNqWMIs= 4 | gitea.com/lunny/nodb v0.0.0-20200923032308-3238c4655727/go.mod h1:h0OwsgcpJLSYtHcM5+Xciw9OEeuxi6ty4HDiO8C7aIY= 5 | github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 h1:U/lr3Dgy4WK+hNk4tyD+nuGjpVLPEHuJSFXMw11/HPA= 6 | github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA= 7 | github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= 8 | github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 9 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 10 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 11 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 12 | github.com/couchbase/go-couchbase v0.0.0-20201026062457-7b3be89bbd89 h1:uNLXQ6QO1TocD8BaN/KkRki0Xw0brCM1PKl/ZA5pgfs= 13 | github.com/couchbase/go-couchbase v0.0.0-20201026062457-7b3be89bbd89/go.mod h1:+/bddYDxXsf9qt0xpDUtRR47A2GjaXmGGAqQ/k3GJ8A= 14 | github.com/couchbase/gomemcached v0.1.1 h1:xCS8ZglJDhrlQg3jmK7Rn1V8f7bPjXABLC05CgLQauc= 15 | github.com/couchbase/gomemcached v0.1.1/go.mod h1:mxliKQxOv84gQ0bJWbI+w9Wxdpt9HjDvgW9MjCym5Vo= 16 | github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67 h1:NCqJ6fwen6YP0WlV/IyibaT0kPt3JEI1rA62V/UPKT4= 17 | github.com/couchbase/goutils v0.0.0-20201030094643-5e82bb967e67/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs= 18 | github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76 h1:Lgdd/Qp96Qj8jqLpq2cI1I1X7BJnu06efS+XkhRoLUQ= 19 | github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY= 20 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 21 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 22 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 23 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 24 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 25 | github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw= 26 | github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= 27 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 28 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= 29 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 30 | github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191/go.mod h1:VFI2o2q9kYsC4o7VP1HrEVosiZZTd+MVT3YZx4gqvJw= 31 | github.com/go-macaron/inject v0.0.0-20200308113650-138e5925c53b h1:/aWj44HoEycE4MDi2HZf4t+XI7hKwZRltZf4ih5tB2c= 32 | github.com/go-macaron/inject v0.0.0-20200308113650-138e5925c53b/go.mod h1:VFI2o2q9kYsC4o7VP1HrEVosiZZTd+MVT3YZx4gqvJw= 33 | github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= 34 | github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= 35 | github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= 36 | github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 37 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 38 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 39 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 40 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 41 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 42 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 43 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 44 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 45 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 46 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 47 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= 48 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 49 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= 50 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 51 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 52 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 53 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 54 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 55 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 56 | github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 57 | github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 58 | github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 59 | github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4= 60 | github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= 61 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 62 | github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 63 | github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 64 | github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= 65 | github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= 66 | github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= 67 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 68 | github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 69 | github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= 70 | github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 71 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= 72 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= 73 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= 74 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 75 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 76 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= 77 | github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= 78 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= 79 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= 80 | github.com/onsi/ginkgo/v2 v2.0.0 h1:CcuG/HvWNkkaqCUpJifQY8z7qEMBJya6aLPx6ftGyjQ= 81 | github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= 82 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 83 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= 84 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= 85 | github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= 86 | github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= 87 | github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= 88 | github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM= 89 | github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= 90 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 91 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 92 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 93 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 94 | github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8iUrN18JYed2TvG9yN5ULG2jATM= 95 | github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= 96 | github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d h1:qQWKKOvHN7Q9c6GdmUteCef2F9ubxMpxY1IKwpIKz68= 97 | github.com/siddontang/go-snappy v0.0.0-20140704025258-d8f7bb82a96d/go.mod h1:vq0tzqLRu6TS7Id0wMo2N5QzJoKedVeovOpHjnykSzY= 98 | github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92 h1:qvsJwGToa8rxb42cDRhkbKeX2H5N8BH+s2aUikGt8mI= 99 | github.com/siddontang/ledisdb v0.0.0-20190202134119-8ceb77e66a92/go.mod h1:mF1DpOSOUiJRMR+FDqaqu3EBqrybQtrDDszLUZ6oxPg= 100 | github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d h1:NVwnfyR3rENtlz62bcrkXME3INVUa4lcdGt+opvxExs= 101 | github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA= 102 | github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 103 | github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= 104 | github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= 105 | github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= 106 | github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= 107 | github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8= 108 | github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= 109 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 110 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 111 | github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= 112 | github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 113 | github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= 114 | github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= 115 | github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= 116 | github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= 117 | github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= 118 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 119 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 120 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 121 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 122 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 123 | golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392 h1:xYJJ3S178yv++9zXV/hnr29plCAGO9vAFG9dorqaFQc= 124 | golang.org/x/crypto v0.0.0-20201124201722-c8d3bf9c5392/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= 125 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 126 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 127 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 128 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 129 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 130 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 131 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 132 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 133 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= 134 | golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= 135 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 136 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 137 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 138 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 139 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 140 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 141 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 142 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 143 | golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 144 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 145 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 146 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 147 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 148 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 149 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 150 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 151 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= 152 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 153 | golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= 154 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 155 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 156 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 157 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 158 | golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= 159 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 160 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 161 | golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 162 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 163 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 164 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 165 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 166 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 167 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 168 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 169 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 170 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 171 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 172 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 173 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 174 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 175 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 176 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 177 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 178 | google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= 179 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 180 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 181 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 182 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 183 | gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 184 | gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= 185 | gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 186 | gopkg.in/macaron.v1 v1.4.0 h1:RJHC09fAnQ8tuGUiZNjG0uyL1BWSdSWd9SpufIcEArQ= 187 | gopkg.in/macaron.v1 v1.4.0/go.mod h1:uMZCFccv9yr5TipIalVOyAyZQuOH3OkmXvgcWwhJuP4= 188 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 189 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 190 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 191 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 192 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 193 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 194 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 195 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 196 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 197 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 198 | -------------------------------------------------------------------------------- /ledis/ledis.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Beego Authors 2 | // Copyright 2014 The Macaron Authors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 5 | // not use this file except in compliance with the License. You may obtain 6 | // a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations 14 | // under the License. 15 | 16 | package session 17 | 18 | import ( 19 | "fmt" 20 | "strings" 21 | "sync" 22 | 23 | "github.com/siddontang/ledisdb/config" 24 | "github.com/siddontang/ledisdb/ledis" 25 | "github.com/unknwon/com" 26 | "gopkg.in/ini.v1" 27 | 28 | "github.com/go-macaron/session" 29 | ) 30 | 31 | // LedisStore represents a ledis session store implementation. 32 | type LedisStore struct { 33 | c *ledis.DB 34 | sid string 35 | expire int64 36 | lock sync.RWMutex 37 | data map[interface{}]interface{} 38 | } 39 | 40 | // NewLedisStore creates and returns a ledis session store. 41 | func NewLedisStore(c *ledis.DB, sid string, expire int64, kv map[interface{}]interface{}) *LedisStore { 42 | return &LedisStore{ 43 | c: c, 44 | expire: expire, 45 | sid: sid, 46 | data: kv, 47 | } 48 | } 49 | 50 | // Set sets value to given key in session. 51 | func (s *LedisStore) Set(key, val interface{}) error { 52 | s.lock.Lock() 53 | defer s.lock.Unlock() 54 | 55 | s.data[key] = val 56 | return nil 57 | } 58 | 59 | // Get gets value by given key in session. 60 | func (s *LedisStore) Get(key interface{}) interface{} { 61 | s.lock.RLock() 62 | defer s.lock.RUnlock() 63 | 64 | return s.data[key] 65 | } 66 | 67 | // Delete delete a key from session. 68 | func (s *LedisStore) Delete(key interface{}) error { 69 | s.lock.Lock() 70 | defer s.lock.Unlock() 71 | 72 | delete(s.data, key) 73 | return nil 74 | } 75 | 76 | // ID returns current session ID. 77 | func (s *LedisStore) ID() string { 78 | return s.sid 79 | } 80 | 81 | // Release releases resource and save data to provider. 82 | func (s *LedisStore) Release() error { 83 | // Skip encoding if the data is empty 84 | if len(s.data) == 0 { 85 | return nil 86 | } 87 | 88 | data, err := session.EncodeGob(s.data) 89 | if err != nil { 90 | return err 91 | } 92 | 93 | if err = s.c.Set([]byte(s.sid), data); err != nil { 94 | return err 95 | } 96 | _, err = s.c.Expire([]byte(s.sid), s.expire) 97 | return err 98 | } 99 | 100 | // Flush deletes all session data. 101 | func (s *LedisStore) Flush() error { 102 | s.lock.Lock() 103 | defer s.lock.Unlock() 104 | 105 | s.data = make(map[interface{}]interface{}) 106 | return nil 107 | } 108 | 109 | // LedisProvider represents a ledis session provider implementation. 110 | type LedisProvider struct { 111 | c *ledis.DB 112 | expire int64 113 | } 114 | 115 | // Init initializes ledis session provider. 116 | // configs: data_dir=./app.db,db=0 117 | func (p *LedisProvider) Init(expire int64, configs string) error { 118 | p.expire = expire 119 | 120 | cfg, err := ini.Load([]byte(strings.Replace(configs, ",", "\n", -1))) 121 | if err != nil { 122 | return err 123 | } 124 | 125 | db := 0 126 | opt := new(config.Config) 127 | for k, v := range cfg.Section("").KeysHash() { 128 | switch k { 129 | case "data_dir": 130 | opt.DataDir = v 131 | case "db": 132 | db = com.StrTo(v).MustInt() 133 | default: 134 | return fmt.Errorf("session/ledis: unsupported option '%s'", k) 135 | } 136 | } 137 | 138 | l, err := ledis.Open(opt) 139 | if err != nil { 140 | return fmt.Errorf("session/ledis: error opening db: %v", err) 141 | } 142 | p.c, err = l.Select(db) 143 | return err 144 | } 145 | 146 | // Read returns raw session store by session ID. 147 | func (p *LedisProvider) Read(sid string) (session.RawStore, error) { 148 | if !p.Exist(sid) { 149 | if err := p.c.Set([]byte(sid), []byte("")); err != nil { 150 | return nil, err 151 | } 152 | } 153 | 154 | var kv map[interface{}]interface{} 155 | kvs, err := p.c.Get([]byte(sid)) 156 | if err != nil { 157 | return nil, err 158 | } 159 | if len(kvs) == 0 { 160 | kv = make(map[interface{}]interface{}) 161 | } else { 162 | kv, err = session.DecodeGob(kvs) 163 | if err != nil { 164 | return nil, err 165 | } 166 | } 167 | 168 | return NewLedisStore(p.c, sid, p.expire, kv), nil 169 | } 170 | 171 | // Exist returns true if session with given ID exists. 172 | func (p *LedisProvider) Exist(sid string) bool { 173 | count, err := p.c.Exists([]byte(sid)) 174 | return err == nil && count > 0 175 | } 176 | 177 | // Destory deletes a session by session ID. 178 | func (p *LedisProvider) Destory(sid string) error { 179 | _, err := p.c.Del([]byte(sid)) 180 | return err 181 | } 182 | 183 | // Regenerate regenerates a session store from old session ID to new one. 184 | func (p *LedisProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) { 185 | if p.Exist(sid) { 186 | return nil, fmt.Errorf("new sid '%s' already exists", sid) 187 | } 188 | 189 | kvs := make([]byte, 0) 190 | if p.Exist(oldsid) { 191 | if kvs, err = p.c.Get([]byte(oldsid)); err != nil { 192 | return nil, err 193 | } else if _, err = p.c.Del([]byte(oldsid)); err != nil { 194 | return nil, err 195 | } 196 | } 197 | if err = p.c.SetEX([]byte(sid), p.expire, kvs); err != nil { 198 | return nil, err 199 | } 200 | 201 | var kv map[interface{}]interface{} 202 | if len(kvs) == 0 { 203 | kv = make(map[interface{}]interface{}) 204 | } else { 205 | kv, err = session.DecodeGob([]byte(kvs)) 206 | if err != nil { 207 | return nil, err 208 | } 209 | } 210 | 211 | return NewLedisStore(p.c, sid, p.expire, kv), nil 212 | } 213 | 214 | // Count counts and returns number of sessions. 215 | func (p *LedisProvider) Count() int { 216 | // FIXME: how come this library does not have DbSize() method? 217 | return -1 218 | } 219 | 220 | // GC calls GC to clean expired sessions. 221 | func (p *LedisProvider) GC() { 222 | // FIXME: wtf??? 223 | } 224 | 225 | func init() { 226 | session.Register("ledis", &LedisProvider{}) 227 | } 228 | -------------------------------------------------------------------------------- /ledis/ledis.goconvey: -------------------------------------------------------------------------------- 1 | ignore -------------------------------------------------------------------------------- /ledis/ledis_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Macaron Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package session 16 | 17 | import ( 18 | "net/http" 19 | "net/http/httptest" 20 | "testing" 21 | 22 | . "github.com/smartystreets/goconvey/convey" 23 | "gopkg.in/macaron.v1" 24 | 25 | "github.com/go-macaron/session" 26 | ) 27 | 28 | func Test_LedisProvider(t *testing.T) { 29 | Convey("Test ledis session provider", t, func() { 30 | opt := session.Options{ 31 | Provider: "ledis", 32 | ProviderConfig: "data_dir=./tmp.db", 33 | } 34 | 35 | Convey("Basic operation", func() { 36 | m := macaron.New() 37 | m.Use(session.Sessioner(opt)) 38 | 39 | m.Get("/", func(ctx *macaron.Context, sess session.Store) { 40 | So(sess.Set("uname", "unknwon"), ShouldBeNil) 41 | }) 42 | m.Get("/reg", func(ctx *macaron.Context, sess session.Store) { 43 | raw, err := sess.RegenerateId(ctx) 44 | So(err, ShouldBeNil) 45 | So(raw, ShouldNotBeNil) 46 | 47 | uname := raw.Get("uname") 48 | So(uname, ShouldNotBeNil) 49 | So(uname, ShouldEqual, "unknwon") 50 | }) 51 | m.Get("/get", func(ctx *macaron.Context, sess session.Store) { 52 | sid := sess.ID() 53 | So(sid, ShouldNotBeEmpty) 54 | 55 | raw, err := sess.Read(sid) 56 | So(err, ShouldBeNil) 57 | So(raw, ShouldNotBeNil) 58 | 59 | uname := sess.Get("uname") 60 | So(uname, ShouldNotBeNil) 61 | So(uname, ShouldEqual, "unknwon") 62 | 63 | So(sess.Delete("uname"), ShouldBeNil) 64 | So(sess.Get("uname"), ShouldBeNil) 65 | 66 | So(sess.Destory(ctx), ShouldBeNil) 67 | }) 68 | 69 | resp := httptest.NewRecorder() 70 | req, err := http.NewRequest("GET", "/", nil) 71 | So(err, ShouldBeNil) 72 | m.ServeHTTP(resp, req) 73 | 74 | cookie := resp.Header().Get("Set-Cookie") 75 | 76 | resp = httptest.NewRecorder() 77 | req, err = http.NewRequest("GET", "/reg", nil) 78 | So(err, ShouldBeNil) 79 | req.Header.Set("Cookie", cookie) 80 | m.ServeHTTP(resp, req) 81 | 82 | cookie = resp.Header().Get("Set-Cookie") 83 | 84 | resp = httptest.NewRecorder() 85 | req, err = http.NewRequest("GET", "/get", nil) 86 | So(err, ShouldBeNil) 87 | req.Header.Set("Cookie", cookie) 88 | m.ServeHTTP(resp, req) 89 | 90 | Convey("Regenrate empty session", func() { 91 | m.Get("/empty", func(ctx *macaron.Context, sess session.Store) { 92 | raw, err := sess.RegenerateId(ctx) 93 | So(err, ShouldBeNil) 94 | So(raw, ShouldNotBeNil) 95 | }) 96 | 97 | resp = httptest.NewRecorder() 98 | req, err = http.NewRequest("GET", "/empty", nil) 99 | So(err, ShouldBeNil) 100 | req.Header.Set("Cookie", "MacaronSession=ad2c7e3cbecfcf486; Path=/;") 101 | m.ServeHTTP(resp, req) 102 | }) 103 | }) 104 | }) 105 | } 106 | -------------------------------------------------------------------------------- /memcache/memcache.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Beego Authors 2 | // Copyright 2014 The Macaron Authors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 5 | // not use this file except in compliance with the License. You may obtain 6 | // a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations 14 | // under the License. 15 | 16 | package session 17 | 18 | import ( 19 | "fmt" 20 | "strings" 21 | "sync" 22 | 23 | "github.com/bradfitz/gomemcache/memcache" 24 | 25 | "github.com/go-macaron/session" 26 | ) 27 | 28 | // MemcacheStore represents a memcache session store implementation. 29 | type MemcacheStore struct { 30 | c *memcache.Client 31 | sid string 32 | expire int32 33 | lock sync.RWMutex 34 | data map[interface{}]interface{} 35 | } 36 | 37 | // NewMemcacheStore creates and returns a memcache session store. 38 | func NewMemcacheStore(c *memcache.Client, sid string, expire int32, kv map[interface{}]interface{}) *MemcacheStore { 39 | return &MemcacheStore{ 40 | c: c, 41 | sid: sid, 42 | expire: expire, 43 | data: kv, 44 | } 45 | } 46 | 47 | func NewItem(sid string, data []byte, expire int32) *memcache.Item { 48 | return &memcache.Item{ 49 | Key: sid, 50 | Value: data, 51 | Expiration: expire, 52 | } 53 | } 54 | 55 | // Set sets value to given key in session. 56 | func (s *MemcacheStore) Set(key, val interface{}) error { 57 | s.lock.Lock() 58 | defer s.lock.Unlock() 59 | 60 | s.data[key] = val 61 | return nil 62 | } 63 | 64 | // Get gets value by given key in session. 65 | func (s *MemcacheStore) Get(key interface{}) interface{} { 66 | s.lock.RLock() 67 | defer s.lock.RUnlock() 68 | 69 | return s.data[key] 70 | } 71 | 72 | // Delete delete a key from session. 73 | func (s *MemcacheStore) Delete(key interface{}) error { 74 | s.lock.Lock() 75 | defer s.lock.Unlock() 76 | 77 | delete(s.data, key) 78 | return nil 79 | } 80 | 81 | // ID returns current session ID. 82 | func (s *MemcacheStore) ID() string { 83 | return s.sid 84 | } 85 | 86 | // Release releases resource and save data to provider. 87 | func (s *MemcacheStore) Release() error { 88 | // Skip encoding if the data is empty 89 | if len(s.data) == 0 { 90 | return nil 91 | } 92 | 93 | data, err := session.EncodeGob(s.data) 94 | if err != nil { 95 | return err 96 | } 97 | 98 | return s.c.Set(NewItem(s.sid, data, s.expire)) 99 | } 100 | 101 | // Flush deletes all session data. 102 | func (s *MemcacheStore) Flush() error { 103 | s.lock.Lock() 104 | defer s.lock.Unlock() 105 | 106 | s.data = make(map[interface{}]interface{}) 107 | return nil 108 | } 109 | 110 | // MemcacheProvider represents a memcache session provider implementation. 111 | type MemcacheProvider struct { 112 | c *memcache.Client 113 | expire int32 114 | } 115 | 116 | // Init initializes memcache session provider. 117 | // connStrs: 127.0.0.1:9090;127.0.0.1:9091 118 | func (p *MemcacheProvider) Init(expire int64, connStrs string) error { 119 | p.expire = int32(expire) 120 | p.c = memcache.New(strings.Split(connStrs, ";")...) 121 | return nil 122 | } 123 | 124 | // Read returns raw session store by session ID. 125 | func (p *MemcacheProvider) Read(sid string) (session.RawStore, error) { 126 | if !p.Exist(sid) { 127 | if err := p.c.Set(NewItem(sid, []byte(""), p.expire)); err != nil { 128 | return nil, err 129 | } 130 | } 131 | 132 | var kv map[interface{}]interface{} 133 | item, err := p.c.Get(sid) 134 | if err != nil { 135 | return nil, err 136 | } 137 | if len(item.Value) == 0 { 138 | kv = make(map[interface{}]interface{}) 139 | } else { 140 | kv, err = session.DecodeGob(item.Value) 141 | if err != nil { 142 | return nil, err 143 | } 144 | } 145 | 146 | return NewMemcacheStore(p.c, sid, p.expire, kv), nil 147 | } 148 | 149 | // Exist returns true if session with given ID exists. 150 | func (p *MemcacheProvider) Exist(sid string) bool { 151 | _, err := p.c.Get(sid) 152 | return err == nil 153 | } 154 | 155 | // Destory deletes a session by session ID. 156 | func (p *MemcacheProvider) Destory(sid string) error { 157 | return p.c.Delete(sid) 158 | } 159 | 160 | // Regenerate regenerates a session store from old session ID to new one. 161 | func (p *MemcacheProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) { 162 | if p.Exist(sid) { 163 | return nil, fmt.Errorf("new sid '%s' already exists", sid) 164 | } 165 | 166 | item := NewItem(sid, []byte(""), p.expire) 167 | if p.Exist(oldsid) { 168 | item, err = p.c.Get(oldsid) 169 | if err != nil { 170 | return nil, err 171 | } else if err = p.c.Delete(oldsid); err != nil { 172 | return nil, err 173 | } 174 | item.Key = sid 175 | } 176 | if err = p.c.Set(item); err != nil { 177 | return nil, err 178 | } 179 | 180 | var kv map[interface{}]interface{} 181 | if len(item.Value) == 0 { 182 | kv = make(map[interface{}]interface{}) 183 | } else { 184 | kv, err = session.DecodeGob(item.Value) 185 | if err != nil { 186 | return nil, err 187 | } 188 | } 189 | 190 | return NewMemcacheStore(p.c, sid, p.expire, kv), nil 191 | } 192 | 193 | // Count counts and returns number of sessions. 194 | func (p *MemcacheProvider) Count() int { 195 | // FIXME: how come this library does not have Stats method? 196 | return -1 197 | } 198 | 199 | // GC calls GC to clean expired sessions. 200 | func (p *MemcacheProvider) GC() {} 201 | 202 | func init() { 203 | session.Register("memcache", &MemcacheProvider{}) 204 | } 205 | -------------------------------------------------------------------------------- /memcache/memcache.goconvey: -------------------------------------------------------------------------------- 1 | ignore -------------------------------------------------------------------------------- /memcache/memcache_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Macaron Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package session 16 | 17 | import ( 18 | "net/http" 19 | "net/http/httptest" 20 | "testing" 21 | 22 | . "github.com/smartystreets/goconvey/convey" 23 | "gopkg.in/macaron.v1" 24 | 25 | "github.com/go-macaron/session" 26 | ) 27 | 28 | func Test_MemcacheProvider(t *testing.T) { 29 | Convey("Test memcache session provider", t, func() { 30 | opt := session.Options{ 31 | Provider: "memcache", 32 | ProviderConfig: "127.0.0.1:9090", 33 | } 34 | 35 | Convey("Basic operation", func() { 36 | m := macaron.New() 37 | m.Use(session.Sessioner(opt)) 38 | 39 | m.Get("/", func(ctx *macaron.Context, sess session.Store) { 40 | So(sess.Set("uname", "unknwon"), ShouldBeNil) 41 | }) 42 | m.Get("/reg", func(ctx *macaron.Context, sess session.Store) { 43 | raw, err := sess.RegenerateId(ctx) 44 | So(err, ShouldBeNil) 45 | So(raw, ShouldNotBeNil) 46 | 47 | uname := raw.Get("uname") 48 | So(uname, ShouldNotBeNil) 49 | So(uname, ShouldEqual, "unknwon") 50 | }) 51 | m.Get("/get", func(ctx *macaron.Context, sess session.Store) { 52 | sid := sess.ID() 53 | So(sid, ShouldNotBeEmpty) 54 | 55 | raw, err := sess.Read(sid) 56 | So(err, ShouldBeNil) 57 | So(raw, ShouldNotBeNil) 58 | 59 | uname := sess.Get("uname") 60 | So(uname, ShouldNotBeNil) 61 | So(uname, ShouldEqual, "unknwon") 62 | 63 | So(sess.Delete("uname"), ShouldBeNil) 64 | So(sess.Get("uname"), ShouldBeNil) 65 | 66 | So(sess.Destory(ctx), ShouldBeNil) 67 | }) 68 | 69 | resp := httptest.NewRecorder() 70 | req, err := http.NewRequest("GET", "/", nil) 71 | So(err, ShouldBeNil) 72 | m.ServeHTTP(resp, req) 73 | 74 | cookie := resp.Header().Get("Set-Cookie") 75 | 76 | resp = httptest.NewRecorder() 77 | req, err = http.NewRequest("GET", "/reg", nil) 78 | So(err, ShouldBeNil) 79 | req.Header.Set("Cookie", cookie) 80 | m.ServeHTTP(resp, req) 81 | 82 | cookie = resp.Header().Get("Set-Cookie") 83 | 84 | resp = httptest.NewRecorder() 85 | req, err = http.NewRequest("GET", "/get", nil) 86 | So(err, ShouldBeNil) 87 | req.Header.Set("Cookie", cookie) 88 | m.ServeHTTP(resp, req) 89 | }) 90 | 91 | Convey("Regenrate empty session", func() { 92 | m := macaron.New() 93 | m.Use(session.Sessioner(opt)) 94 | m.Get("/", func(ctx *macaron.Context, sess session.Store) { 95 | raw, err := sess.RegenerateId(ctx) 96 | So(err, ShouldBeNil) 97 | So(raw, ShouldNotBeNil) 98 | }) 99 | 100 | resp := httptest.NewRecorder() 101 | req, err := http.NewRequest("GET", "/", nil) 102 | So(err, ShouldBeNil) 103 | req.Header.Set("Cookie", "MacaronSession=ad2c7e3cbecfcf486; Path=/;") 104 | m.ServeHTTP(resp, req) 105 | }) 106 | }) 107 | } 108 | -------------------------------------------------------------------------------- /memory.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Beego Authors 2 | // Copyright 2014 The Macaron Authors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 5 | // not use this file except in compliance with the License. You may obtain 6 | // a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations 14 | // under the License. 15 | 16 | package session 17 | 18 | import ( 19 | "container/list" 20 | "fmt" 21 | "sync" 22 | "time" 23 | ) 24 | 25 | // MemStore represents a in-memory session store implementation. 26 | type MemStore struct { 27 | sid string 28 | lock sync.RWMutex 29 | data map[interface{}]interface{} 30 | lastAccess time.Time 31 | } 32 | 33 | // NewMemStore creates and returns a memory session store. 34 | func NewMemStore(sid string) *MemStore { 35 | return &MemStore{ 36 | sid: sid, 37 | data: make(map[interface{}]interface{}), 38 | lastAccess: time.Now(), 39 | } 40 | } 41 | 42 | // Set sets value to given key in session. 43 | func (s *MemStore) Set(key, val interface{}) error { 44 | s.lock.Lock() 45 | defer s.lock.Unlock() 46 | 47 | s.data[key] = val 48 | return nil 49 | } 50 | 51 | // Get gets value by given key in session. 52 | func (s *MemStore) Get(key interface{}) interface{} { 53 | s.lock.RLock() 54 | defer s.lock.RUnlock() 55 | 56 | return s.data[key] 57 | } 58 | 59 | // Delete deletes a key from session. 60 | func (s *MemStore) Delete(key interface{}) error { 61 | s.lock.Lock() 62 | defer s.lock.Unlock() 63 | 64 | delete(s.data, key) 65 | return nil 66 | } 67 | 68 | // ID returns current session ID. 69 | func (s *MemStore) ID() string { 70 | return s.sid 71 | } 72 | 73 | // Release releases resource and save data to provider. 74 | func (_ *MemStore) Release() error { 75 | return nil 76 | } 77 | 78 | // Flush deletes all session data. 79 | func (s *MemStore) Flush() error { 80 | s.lock.Lock() 81 | defer s.lock.Unlock() 82 | 83 | s.data = make(map[interface{}]interface{}) 84 | return nil 85 | } 86 | 87 | // MemProvider represents a in-memory session provider implementation. 88 | type MemProvider struct { 89 | lock sync.RWMutex 90 | maxLifetime int64 91 | data map[string]*list.Element 92 | // A priority list whose lastAccess newer gets higer priority. 93 | list *list.List 94 | } 95 | 96 | // Init initializes memory session provider. 97 | func (p *MemProvider) Init(maxLifetime int64, _ string) error { 98 | p.lock.Lock() 99 | p.list = list.New() 100 | p.data = make(map[string]*list.Element) 101 | p.maxLifetime = maxLifetime 102 | p.lock.Unlock() 103 | return nil 104 | } 105 | 106 | // update expands time of session store by given ID. 107 | func (p *MemProvider) update(sid string) error { 108 | p.lock.Lock() 109 | defer p.lock.Unlock() 110 | 111 | if e, ok := p.data[sid]; ok { 112 | e.Value.(*MemStore).lastAccess = time.Now() 113 | p.list.MoveToFront(e) 114 | return nil 115 | } 116 | return nil 117 | } 118 | 119 | // Read returns raw session store by session ID. 120 | func (p *MemProvider) Read(sid string) (_ RawStore, err error) { 121 | p.lock.RLock() 122 | e, ok := p.data[sid] 123 | p.lock.RUnlock() 124 | 125 | // Only restore if the session is still alive. 126 | if ok && (e.Value.(*MemStore).lastAccess.Unix()+p.maxLifetime) >= time.Now().Unix() { 127 | if err = p.update(sid); err != nil { 128 | return nil, err 129 | } 130 | return e.Value.(*MemStore), nil 131 | } 132 | 133 | // Create a new session. 134 | p.lock.Lock() 135 | defer p.lock.Unlock() 136 | if ok { 137 | p.list.Remove(e) 138 | } 139 | s := NewMemStore(sid) 140 | p.data[sid] = p.list.PushBack(s) 141 | return s, nil 142 | } 143 | 144 | // Exist returns true if session with given ID exists. 145 | func (p *MemProvider) Exist(sid string) bool { 146 | p.lock.RLock() 147 | defer p.lock.RUnlock() 148 | 149 | _, ok := p.data[sid] 150 | return ok 151 | } 152 | 153 | // Destory deletes a session by session ID. 154 | func (p *MemProvider) Destory(sid string) error { 155 | p.lock.Lock() 156 | defer p.lock.Unlock() 157 | 158 | e, ok := p.data[sid] 159 | if !ok { 160 | return nil 161 | } 162 | 163 | p.list.Remove(e) 164 | delete(p.data, sid) 165 | return nil 166 | } 167 | 168 | // Regenerate regenerates a session store from old session ID to new one. 169 | func (p *MemProvider) Regenerate(oldsid, sid string) (RawStore, error) { 170 | if p.Exist(sid) { 171 | return nil, fmt.Errorf("new sid '%s' already exists", sid) 172 | } 173 | 174 | s, err := p.Read(oldsid) 175 | if err != nil { 176 | return nil, err 177 | } 178 | 179 | if err = p.Destory(oldsid); err != nil { 180 | return nil, err 181 | } 182 | 183 | s.(*MemStore).sid = sid 184 | 185 | p.lock.Lock() 186 | defer p.lock.Unlock() 187 | p.data[sid] = p.list.PushBack(s) 188 | return s, nil 189 | } 190 | 191 | // Count counts and returns number of sessions. 192 | func (p *MemProvider) Count() int { 193 | return p.list.Len() 194 | } 195 | 196 | // GC calls GC to clean expired sessions. 197 | func (p *MemProvider) GC() { 198 | p.lock.RLock() 199 | for { 200 | // No session in the list. 201 | e := p.list.Back() 202 | if e == nil { 203 | break 204 | } 205 | 206 | if (e.Value.(*MemStore).lastAccess.Unix() + p.maxLifetime) < time.Now().Unix() { 207 | p.lock.RUnlock() 208 | p.lock.Lock() 209 | p.list.Remove(e) 210 | delete(p.data, e.Value.(*MemStore).sid) 211 | p.lock.Unlock() 212 | p.lock.RLock() 213 | } else { 214 | break 215 | } 216 | } 217 | p.lock.RUnlock() 218 | } 219 | 220 | func init() { 221 | Register("memory", &MemProvider{}) 222 | } 223 | -------------------------------------------------------------------------------- /memory_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Macaron Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package session 16 | 17 | import ( 18 | "testing" 19 | 20 | . "github.com/smartystreets/goconvey/convey" 21 | ) 22 | 23 | func Test_MemProvider(t *testing.T) { 24 | Convey("Test memory session provider", t, func() { 25 | testProvider(Options{}) 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /mysql/mysql.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Beego Authors 2 | // Copyright 2014 The Macaron Authors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 5 | // not use this file except in compliance with the License. You may obtain 6 | // a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations 14 | // under the License. 15 | 16 | package session 17 | 18 | import ( 19 | "database/sql" 20 | "fmt" 21 | "log" 22 | "sync" 23 | "time" 24 | 25 | _ "github.com/go-sql-driver/mysql" 26 | 27 | "github.com/go-macaron/session" 28 | ) 29 | 30 | // MysqlStore represents a mysql session store implementation. 31 | type MysqlStore struct { 32 | c *sql.DB 33 | sid string 34 | lock sync.RWMutex 35 | data map[interface{}]interface{} 36 | } 37 | 38 | // NewMysqlStore creates and returns a mysql session store. 39 | func NewMysqlStore(c *sql.DB, sid string, kv map[interface{}]interface{}) *MysqlStore { 40 | return &MysqlStore{ 41 | c: c, 42 | sid: sid, 43 | data: kv, 44 | } 45 | } 46 | 47 | // Set sets value to given key in session. 48 | func (s *MysqlStore) Set(key, val interface{}) error { 49 | s.lock.Lock() 50 | defer s.lock.Unlock() 51 | 52 | s.data[key] = val 53 | return nil 54 | } 55 | 56 | // Get gets value by given key in session. 57 | func (s *MysqlStore) Get(key interface{}) interface{} { 58 | s.lock.RLock() 59 | defer s.lock.RUnlock() 60 | 61 | return s.data[key] 62 | } 63 | 64 | // Delete delete a key from session. 65 | func (s *MysqlStore) Delete(key interface{}) error { 66 | s.lock.Lock() 67 | defer s.lock.Unlock() 68 | 69 | delete(s.data, key) 70 | return nil 71 | } 72 | 73 | // ID returns current session ID. 74 | func (s *MysqlStore) ID() string { 75 | return s.sid 76 | } 77 | 78 | // Release releases resource and save data to provider. 79 | func (s *MysqlStore) Release() error { 80 | // Skip encoding if the data is empty 81 | if len(s.data) == 0 { 82 | return nil 83 | } 84 | 85 | data, err := session.EncodeGob(s.data) 86 | if err != nil { 87 | return err 88 | } 89 | 90 | _, err = s.c.Exec("UPDATE session SET data=?, expiry=? WHERE `key`=?", 91 | data, time.Now().Unix(), s.sid) 92 | return err 93 | } 94 | 95 | // Flush deletes all session data. 96 | func (s *MysqlStore) Flush() error { 97 | s.lock.Lock() 98 | defer s.lock.Unlock() 99 | 100 | s.data = make(map[interface{}]interface{}) 101 | return nil 102 | } 103 | 104 | // MysqlProvider represents a mysql session provider implementation. 105 | type MysqlProvider struct { 106 | c *sql.DB 107 | expire int64 108 | } 109 | 110 | // Init initializes mysql session provider. 111 | // connStr: username:password@protocol(address)/dbname?param=value 112 | func (p *MysqlProvider) Init(expire int64, connStr string) (err error) { 113 | p.expire = expire 114 | 115 | p.c, err = sql.Open("mysql", connStr) 116 | if err != nil { 117 | return err 118 | } 119 | return p.c.Ping() 120 | } 121 | 122 | // Read returns raw session store by session ID. 123 | func (p *MysqlProvider) Read(sid string) (session.RawStore, error) { 124 | now := time.Now().Unix() 125 | var data []byte 126 | expiry := now 127 | err := p.c.QueryRow("SELECT data, expiry FROM session WHERE `key`=?", sid).Scan(&data, &expiry) 128 | if err == sql.ErrNoRows { 129 | _, err = p.c.Exec("INSERT INTO session(`key`,data,expiry) VALUES(?,?,?)", 130 | sid, "", now) 131 | } 132 | if err != nil { 133 | return nil, err 134 | } 135 | 136 | var kv map[interface{}]interface{} 137 | if len(data) == 0 || expiry+p.expire <= now { 138 | kv = make(map[interface{}]interface{}) 139 | } else { 140 | kv, err = session.DecodeGob(data) 141 | if err != nil { 142 | return nil, err 143 | } 144 | } 145 | 146 | return NewMysqlStore(p.c, sid, kv), nil 147 | } 148 | 149 | // Exist returns true if session with given ID exists. 150 | func (p *MysqlProvider) Exist(sid string) bool { 151 | var data []byte 152 | err := p.c.QueryRow("SELECT data FROM session WHERE `key`=?", sid).Scan(&data) 153 | if err != nil && err != sql.ErrNoRows { 154 | panic("session/mysql: error checking existence: " + err.Error()) 155 | } 156 | return err != sql.ErrNoRows 157 | } 158 | 159 | // Destory deletes a session by session ID. 160 | func (p *MysqlProvider) Destory(sid string) error { 161 | _, err := p.c.Exec("DELETE FROM session WHERE `key`=?", sid) 162 | return err 163 | } 164 | 165 | // Regenerate regenerates a session store from old session ID to new one. 166 | func (p *MysqlProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) { 167 | if p.Exist(sid) { 168 | return nil, fmt.Errorf("new sid '%s' already exists", sid) 169 | } 170 | 171 | if !p.Exist(oldsid) { 172 | if _, err = p.c.Exec("INSERT INTO session(`key`,data,expiry) VALUES(?,?,?)", 173 | oldsid, "", time.Now().Unix()); err != nil { 174 | return nil, err 175 | } 176 | } 177 | 178 | if _, err = p.c.Exec("UPDATE session SET `key`=? WHERE `key`=?", sid, oldsid); err != nil { 179 | return nil, err 180 | } 181 | 182 | return p.Read(sid) 183 | } 184 | 185 | // Count counts and returns number of sessions. 186 | func (p *MysqlProvider) Count() (total int) { 187 | if err := p.c.QueryRow("SELECT COUNT(*) AS NUM FROM session").Scan(&total); err != nil { 188 | panic("session/mysql: error counting records: " + err.Error()) 189 | } 190 | return total 191 | } 192 | 193 | // GC calls GC to clean expired sessions. 194 | func (p *MysqlProvider) GC() { 195 | if _, err := p.c.Exec("DELETE FROM session WHERE expiry + ? <= UNIX_TIMESTAMP(NOW())", p.expire); err != nil { 196 | log.Printf("session/mysql: error garbage collecting: %v", err) 197 | } 198 | } 199 | 200 | func init() { 201 | session.Register("mysql", &MysqlProvider{}) 202 | } 203 | -------------------------------------------------------------------------------- /mysql/mysql.goconvey: -------------------------------------------------------------------------------- 1 | ignore -------------------------------------------------------------------------------- /mysql/mysql_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Macaron Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package session 16 | 17 | import ( 18 | "net/http" 19 | "net/http/httptest" 20 | "testing" 21 | "time" 22 | 23 | . "github.com/smartystreets/goconvey/convey" 24 | "gopkg.in/macaron.v1" 25 | 26 | "github.com/go-macaron/session" 27 | ) 28 | 29 | func Test_MysqlProvider(t *testing.T) { 30 | Convey("Test mysql session provider", t, func() { 31 | opt := session.Options{ 32 | Provider: "mysql", 33 | ProviderConfig: "root:@tcp(localhost:3306)/macaron?charset=utf8", 34 | } 35 | 36 | Convey("Basic operation", func() { 37 | m := macaron.New() 38 | m.Use(session.Sessioner(opt)) 39 | 40 | m.Get("/", func(ctx *macaron.Context, sess session.Store) { 41 | So(sess.Set("uname", "unknwon"), ShouldBeNil) 42 | }) 43 | m.Get("/reg", func(ctx *macaron.Context, sess session.Store) { 44 | raw, err := sess.RegenerateId(ctx) 45 | So(err, ShouldBeNil) 46 | So(raw, ShouldNotBeNil) 47 | 48 | uname := raw.Get("uname") 49 | So(uname, ShouldNotBeNil) 50 | So(uname, ShouldEqual, "unknwon") 51 | }) 52 | m.Get("/get", func(ctx *macaron.Context, sess session.Store) { 53 | sid := sess.ID() 54 | So(sid, ShouldNotBeEmpty) 55 | 56 | raw, err := sess.Read(sid) 57 | So(err, ShouldBeNil) 58 | So(raw, ShouldNotBeNil) 59 | So(raw.Release(), ShouldBeNil) 60 | 61 | uname := sess.Get("uname") 62 | So(uname, ShouldNotBeNil) 63 | So(uname, ShouldEqual, "unknwon") 64 | 65 | So(sess.Delete("uname"), ShouldBeNil) 66 | So(sess.Get("uname"), ShouldBeNil) 67 | 68 | So(sess.Destory(ctx), ShouldBeNil) 69 | }) 70 | 71 | resp := httptest.NewRecorder() 72 | req, err := http.NewRequest("GET", "/", nil) 73 | So(err, ShouldBeNil) 74 | m.ServeHTTP(resp, req) 75 | 76 | cookie := resp.Header().Get("Set-Cookie") 77 | 78 | resp = httptest.NewRecorder() 79 | req, err = http.NewRequest("GET", "/reg", nil) 80 | So(err, ShouldBeNil) 81 | req.Header.Set("Cookie", cookie) 82 | m.ServeHTTP(resp, req) 83 | 84 | cookie = resp.Header().Get("Set-Cookie") 85 | 86 | resp = httptest.NewRecorder() 87 | req, err = http.NewRequest("GET", "/get", nil) 88 | So(err, ShouldBeNil) 89 | req.Header.Set("Cookie", cookie) 90 | m.ServeHTTP(resp, req) 91 | }) 92 | 93 | Convey("Regenrate empty session", func() { 94 | m := macaron.New() 95 | m.Use(session.Sessioner(opt)) 96 | m.Get("/", func(ctx *macaron.Context, sess session.Store) { 97 | raw, err := sess.RegenerateId(ctx) 98 | So(err, ShouldBeNil) 99 | So(raw, ShouldNotBeNil) 100 | 101 | So(sess.Destory(ctx), ShouldBeNil) 102 | }) 103 | 104 | resp := httptest.NewRecorder() 105 | req, err := http.NewRequest("GET", "/", nil) 106 | So(err, ShouldBeNil) 107 | req.Header.Set("Cookie", "MacaronSession=ad2c7e3cbecfcf48; Path=/;") 108 | m.ServeHTTP(resp, req) 109 | }) 110 | 111 | Convey("GC session", func() { 112 | m := macaron.New() 113 | opt2 := opt 114 | opt2.Gclifetime = 1 115 | m.Use(session.Sessioner(opt2)) 116 | 117 | m.Get("/", func(sess session.Store) { 118 | So(sess.Set("uname", "unknwon"), ShouldBeNil) 119 | So(sess.ID(), ShouldNotBeEmpty) 120 | uname := sess.Get("uname") 121 | So(uname, ShouldNotBeNil) 122 | So(uname, ShouldEqual, "unknwon") 123 | 124 | So(sess.Flush(), ShouldBeNil) 125 | So(sess.Get("uname"), ShouldBeNil) 126 | 127 | time.Sleep(2 * time.Second) 128 | sess.GC() 129 | So(sess.Count(), ShouldEqual, 0) 130 | }) 131 | 132 | resp := httptest.NewRecorder() 133 | req, err := http.NewRequest("GET", "/", nil) 134 | So(err, ShouldBeNil) 135 | m.ServeHTTP(resp, req) 136 | }) 137 | }) 138 | } 139 | -------------------------------------------------------------------------------- /nodb/nodb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Macaron Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package session 16 | 17 | import ( 18 | "fmt" 19 | "sync" 20 | 21 | "gitea.com/lunny/nodb" 22 | "gitea.com/lunny/nodb/config" 23 | 24 | "github.com/go-macaron/session" 25 | ) 26 | 27 | // NodbStore represents a nodb session store implementation. 28 | type NodbStore struct { 29 | c *nodb.DB 30 | sid string 31 | expire int64 32 | lock sync.RWMutex 33 | data map[interface{}]interface{} 34 | } 35 | 36 | // NewNodbStore creates and returns a ledis session store. 37 | func NewNodbStore(c *nodb.DB, sid string, expire int64, kv map[interface{}]interface{}) *NodbStore { 38 | return &NodbStore{ 39 | c: c, 40 | expire: expire, 41 | sid: sid, 42 | data: kv, 43 | } 44 | } 45 | 46 | // Set sets value to given key in session. 47 | func (s *NodbStore) Set(key, val interface{}) error { 48 | s.lock.Lock() 49 | defer s.lock.Unlock() 50 | 51 | s.data[key] = val 52 | return nil 53 | } 54 | 55 | // Get gets value by given key in session. 56 | func (s *NodbStore) Get(key interface{}) interface{} { 57 | s.lock.RLock() 58 | defer s.lock.RUnlock() 59 | 60 | return s.data[key] 61 | } 62 | 63 | // Delete delete a key from session. 64 | func (s *NodbStore) Delete(key interface{}) error { 65 | s.lock.Lock() 66 | defer s.lock.Unlock() 67 | 68 | delete(s.data, key) 69 | return nil 70 | } 71 | 72 | // ID returns current session ID. 73 | func (s *NodbStore) ID() string { 74 | return s.sid 75 | } 76 | 77 | // Release releases resource and save data to provider. 78 | func (s *NodbStore) Release() error { 79 | // Skip encoding if the data is empty 80 | if len(s.data) == 0 { 81 | return nil 82 | } 83 | 84 | data, err := session.EncodeGob(s.data) 85 | if err != nil { 86 | return err 87 | } 88 | 89 | if err = s.c.Set([]byte(s.sid), data); err != nil { 90 | return err 91 | } 92 | _, err = s.c.Expire([]byte(s.sid), s.expire) 93 | return err 94 | } 95 | 96 | // Flush deletes all session data. 97 | func (s *NodbStore) Flush() error { 98 | s.lock.Lock() 99 | defer s.lock.Unlock() 100 | 101 | s.data = make(map[interface{}]interface{}) 102 | return nil 103 | } 104 | 105 | // NodbProvider represents a ledis session provider implementation. 106 | type NodbProvider struct { 107 | c *nodb.DB 108 | expire int64 109 | } 110 | 111 | // Init initializes nodb session provider. 112 | func (p *NodbProvider) Init(expire int64, configs string) error { 113 | p.expire = expire 114 | 115 | cfg := new(config.Config) 116 | cfg.DataDir = configs 117 | dbs, err := nodb.Open(cfg) 118 | if err != nil { 119 | return fmt.Errorf("session/nodb: error opening db: %v", err) 120 | } 121 | 122 | p.c, err = dbs.Select(0) 123 | return err 124 | } 125 | 126 | // Read returns raw session store by session ID. 127 | func (p *NodbProvider) Read(sid string) (session.RawStore, error) { 128 | if !p.Exist(sid) { 129 | if err := p.c.Set([]byte(sid), []byte("")); err != nil { 130 | return nil, err 131 | } 132 | } 133 | 134 | var kv map[interface{}]interface{} 135 | kvs, err := p.c.Get([]byte(sid)) 136 | if err != nil { 137 | return nil, err 138 | } 139 | if len(kvs) == 0 { 140 | kv = make(map[interface{}]interface{}) 141 | } else { 142 | kv, err = session.DecodeGob(kvs) 143 | if err != nil { 144 | return nil, err 145 | } 146 | } 147 | 148 | return NewNodbStore(p.c, sid, p.expire, kv), nil 149 | } 150 | 151 | // Exist returns true if session with given ID exists. 152 | func (p *NodbProvider) Exist(sid string) bool { 153 | count, err := p.c.Exists([]byte(sid)) 154 | return err == nil && count > 0 155 | } 156 | 157 | // Destory deletes a session by session ID. 158 | func (p *NodbProvider) Destory(sid string) error { 159 | _, err := p.c.Del([]byte(sid)) 160 | return err 161 | } 162 | 163 | // Regenerate regenerates a session store from old session ID to new one. 164 | func (p *NodbProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) { 165 | if p.Exist(sid) { 166 | return nil, fmt.Errorf("new sid '%s' already exists", sid) 167 | } 168 | 169 | kvs := make([]byte, 0) 170 | if p.Exist(oldsid) { 171 | if kvs, err = p.c.Get([]byte(oldsid)); err != nil { 172 | return nil, err 173 | } else if _, err = p.c.Del([]byte(oldsid)); err != nil { 174 | return nil, err 175 | } 176 | } 177 | 178 | if err = p.c.Set([]byte(sid), kvs); err != nil { 179 | return nil, err 180 | } else if _, err = p.c.Expire([]byte(sid), p.expire); err != nil { 181 | return nil, err 182 | } 183 | 184 | var kv map[interface{}]interface{} 185 | if len(kvs) == 0 { 186 | kv = make(map[interface{}]interface{}) 187 | } else { 188 | kv, err = session.DecodeGob([]byte(kvs)) 189 | if err != nil { 190 | return nil, err 191 | } 192 | } 193 | 194 | return NewNodbStore(p.c, sid, p.expire, kv), nil 195 | } 196 | 197 | // Count counts and returns number of sessions. 198 | func (p *NodbProvider) Count() int { 199 | // FIXME: how come this library does not have DbSize() method? 200 | return -1 201 | } 202 | 203 | // GC calls GC to clean expired sessions. 204 | func (p *NodbProvider) GC() {} 205 | 206 | func init() { 207 | session.Register("nodb", &NodbProvider{}) 208 | } 209 | -------------------------------------------------------------------------------- /nodb/nodb.goconvey: -------------------------------------------------------------------------------- 1 | ignore -------------------------------------------------------------------------------- /nodb/nodb_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The Macaron Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package session 16 | 17 | import ( 18 | "net/http" 19 | "net/http/httptest" 20 | "testing" 21 | 22 | . "github.com/smartystreets/goconvey/convey" 23 | "gopkg.in/macaron.v1" 24 | 25 | "github.com/go-macaron/session" 26 | ) 27 | 28 | func Test_LedisProvider(t *testing.T) { 29 | Convey("Test nodb session provider", t, func() { 30 | opt := session.Options{ 31 | Provider: "nodb", 32 | ProviderConfig: "./tmp.db", 33 | } 34 | 35 | Convey("Basic operation", func() { 36 | m := macaron.New() 37 | m.Use(session.Sessioner(opt)) 38 | 39 | m.Get("/", func(ctx *macaron.Context, sess session.Store) { 40 | So(sess.Set("uname", "unknwon"), ShouldBeNil) 41 | }) 42 | m.Get("/reg", func(ctx *macaron.Context, sess session.Store) { 43 | raw, err := sess.RegenerateId(ctx) 44 | So(err, ShouldBeNil) 45 | So(raw, ShouldNotBeNil) 46 | 47 | uname := raw.Get("uname") 48 | So(uname, ShouldNotBeNil) 49 | So(uname, ShouldEqual, "unknwon") 50 | }) 51 | m.Get("/get", func(ctx *macaron.Context, sess session.Store) { 52 | sid := sess.ID() 53 | So(sid, ShouldNotBeEmpty) 54 | 55 | raw, err := sess.Read(sid) 56 | So(err, ShouldBeNil) 57 | So(raw, ShouldNotBeNil) 58 | 59 | uname := sess.Get("uname") 60 | So(uname, ShouldNotBeNil) 61 | So(uname, ShouldEqual, "unknwon") 62 | 63 | So(sess.Delete("uname"), ShouldBeNil) 64 | So(sess.Get("uname"), ShouldBeNil) 65 | 66 | So(sess.Destory(ctx), ShouldBeNil) 67 | }) 68 | 69 | resp := httptest.NewRecorder() 70 | req, err := http.NewRequest("GET", "/", nil) 71 | So(err, ShouldBeNil) 72 | m.ServeHTTP(resp, req) 73 | 74 | cookie := resp.Header().Get("Set-Cookie") 75 | 76 | resp = httptest.NewRecorder() 77 | req, err = http.NewRequest("GET", "/reg", nil) 78 | So(err, ShouldBeNil) 79 | req.Header.Set("Cookie", cookie) 80 | m.ServeHTTP(resp, req) 81 | 82 | cookie = resp.Header().Get("Set-Cookie") 83 | 84 | resp = httptest.NewRecorder() 85 | req, err = http.NewRequest("GET", "/get", nil) 86 | So(err, ShouldBeNil) 87 | req.Header.Set("Cookie", cookie) 88 | m.ServeHTTP(resp, req) 89 | 90 | Convey("Regenrate empty session", func() { 91 | m.Get("/empty", func(ctx *macaron.Context, sess session.Store) { 92 | raw, err := sess.RegenerateId(ctx) 93 | So(err, ShouldBeNil) 94 | So(raw, ShouldNotBeNil) 95 | }) 96 | 97 | resp = httptest.NewRecorder() 98 | req, err = http.NewRequest("GET", "/empty", nil) 99 | So(err, ShouldBeNil) 100 | req.Header.Set("Cookie", "MacaronSession=ad2c7e3cbecfcf486; Path=/;") 101 | m.ServeHTTP(resp, req) 102 | }) 103 | }) 104 | }) 105 | } 106 | -------------------------------------------------------------------------------- /postgres/postgres.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Beego Authors 2 | // Copyright 2014 The Macaron Authors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 5 | // not use this file except in compliance with the License. You may obtain 6 | // a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations 14 | // under the License. 15 | 16 | package session 17 | 18 | import ( 19 | "database/sql" 20 | "fmt" 21 | "log" 22 | "sync" 23 | "time" 24 | 25 | _ "github.com/lib/pq" 26 | 27 | "github.com/go-macaron/session" 28 | ) 29 | 30 | // PostgresStore represents a postgres session store implementation. 31 | type PostgresStore struct { 32 | c *sql.DB 33 | sid string 34 | lock sync.RWMutex 35 | data map[interface{}]interface{} 36 | } 37 | 38 | // NewPostgresStore creates and returns a postgres session store. 39 | func NewPostgresStore(c *sql.DB, sid string, kv map[interface{}]interface{}) *PostgresStore { 40 | return &PostgresStore{ 41 | c: c, 42 | sid: sid, 43 | data: kv, 44 | } 45 | } 46 | 47 | // Set sets value to given key in session. 48 | func (s *PostgresStore) Set(key, value interface{}) error { 49 | s.lock.Lock() 50 | defer s.lock.Unlock() 51 | 52 | s.data[key] = value 53 | return nil 54 | } 55 | 56 | // Get gets value by given key in session. 57 | func (s *PostgresStore) Get(key interface{}) interface{} { 58 | s.lock.RLock() 59 | defer s.lock.RUnlock() 60 | 61 | return s.data[key] 62 | } 63 | 64 | // Delete delete a key from session. 65 | func (s *PostgresStore) Delete(key interface{}) error { 66 | s.lock.Lock() 67 | defer s.lock.Unlock() 68 | 69 | delete(s.data, key) 70 | return nil 71 | } 72 | 73 | // ID returns current session ID. 74 | func (s *PostgresStore) ID() string { 75 | return s.sid 76 | } 77 | 78 | // save postgres session values to database. 79 | // must call this method to save values to database. 80 | func (s *PostgresStore) Release() error { 81 | // Skip encoding if the data is empty 82 | if len(s.data) == 0 { 83 | return nil 84 | } 85 | 86 | data, err := session.EncodeGob(s.data) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | _, err = s.c.Exec("UPDATE session SET data=$1, expiry=$2 WHERE key=$3", 92 | data, time.Now().Unix(), s.sid) 93 | return err 94 | } 95 | 96 | // Flush deletes all session data. 97 | func (s *PostgresStore) Flush() error { 98 | s.lock.Lock() 99 | defer s.lock.Unlock() 100 | 101 | s.data = make(map[interface{}]interface{}) 102 | return nil 103 | } 104 | 105 | // PostgresProvider represents a postgres session provider implementation. 106 | type PostgresProvider struct { 107 | c *sql.DB 108 | maxlifetime int64 109 | } 110 | 111 | // Init initializes postgres session provider. 112 | // connStr: user=a password=b host=localhost port=5432 dbname=c sslmode=disable 113 | func (p *PostgresProvider) Init(maxlifetime int64, connStr string) (err error) { 114 | p.maxlifetime = maxlifetime 115 | 116 | p.c, err = sql.Open("postgres", connStr) 117 | if err != nil { 118 | return err 119 | } 120 | return p.c.Ping() 121 | } 122 | 123 | // Read returns raw session store by session ID. 124 | func (p *PostgresProvider) Read(sid string) (session.RawStore, error) { 125 | now := time.Now().Unix() 126 | var data []byte 127 | expiry := now 128 | err := p.c.QueryRow("SELECT data, expiry FROM session WHERE key=$1", sid).Scan(&data, &expiry) 129 | if err == sql.ErrNoRows { 130 | _, err = p.c.Exec("INSERT INTO session(key,data,expiry) VALUES($1,$2,$3)", 131 | sid, "", now) 132 | } 133 | if err != nil { 134 | return nil, err 135 | } 136 | 137 | var kv map[interface{}]interface{} 138 | if len(data) == 0 || expiry+p.maxlifetime <= now { 139 | kv = make(map[interface{}]interface{}) 140 | } else { 141 | kv, err = session.DecodeGob(data) 142 | if err != nil { 143 | return nil, err 144 | } 145 | } 146 | 147 | return NewPostgresStore(p.c, sid, kv), nil 148 | } 149 | 150 | // Exist returns true if session with given ID exists. 151 | func (p *PostgresProvider) Exist(sid string) bool { 152 | var data []byte 153 | err := p.c.QueryRow("SELECT data FROM session WHERE key=$1", sid).Scan(&data) 154 | if err != nil && err != sql.ErrNoRows { 155 | panic("session/postgres: error checking existence: " + err.Error()) 156 | } 157 | return err != sql.ErrNoRows 158 | } 159 | 160 | // Destory deletes a session by session ID. 161 | func (p *PostgresProvider) Destory(sid string) error { 162 | _, err := p.c.Exec("DELETE FROM session WHERE key=$1", sid) 163 | return err 164 | } 165 | 166 | // Regenerate regenerates a session store from old session ID to new one. 167 | func (p *PostgresProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) { 168 | if p.Exist(sid) { 169 | return nil, fmt.Errorf("new sid '%s' already exists", sid) 170 | } 171 | 172 | if !p.Exist(oldsid) { 173 | if _, err = p.c.Exec("INSERT INTO session(key,data,expiry) VALUES($1,$2,$3)", 174 | oldsid, "", time.Now().Unix()); err != nil { 175 | return nil, err 176 | } 177 | } 178 | 179 | if _, err = p.c.Exec("UPDATE session SET key=$1 WHERE key=$2", sid, oldsid); err != nil { 180 | return nil, err 181 | } 182 | 183 | return p.Read(sid) 184 | } 185 | 186 | // Count counts and returns number of sessions. 187 | func (p *PostgresProvider) Count() (total int) { 188 | if err := p.c.QueryRow("SELECT COUNT(*) AS NUM FROM session").Scan(&total); err != nil { 189 | panic("session/postgres: error counting records: " + err.Error()) 190 | } 191 | return total 192 | } 193 | 194 | // GC calls GC to clean expired sessions. 195 | func (p *PostgresProvider) GC() { 196 | if _, err := p.c.Exec("DELETE FROM session WHERE EXTRACT(EPOCH FROM NOW()) - expiry > $1", p.maxlifetime); err != nil { 197 | log.Printf("session/postgres: error garbage collecting: %v", err) 198 | } 199 | } 200 | 201 | func init() { 202 | session.Register("postgres", &PostgresProvider{}) 203 | } 204 | -------------------------------------------------------------------------------- /postgres/postgres.goconvey: -------------------------------------------------------------------------------- 1 | ignore -------------------------------------------------------------------------------- /postgres/postgres_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Macaron Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package session 16 | 17 | import ( 18 | "net/http" 19 | "net/http/httptest" 20 | "testing" 21 | "time" 22 | 23 | . "github.com/smartystreets/goconvey/convey" 24 | "gopkg.in/macaron.v1" 25 | 26 | "github.com/go-macaron/session" 27 | ) 28 | 29 | func Test_PostgresProvider(t *testing.T) { 30 | Convey("Test postgres session provider", t, func() { 31 | opt := session.Options{ 32 | Provider: "postgres", 33 | ProviderConfig: "user=jiahuachen dbname=macaron port=5432 sslmode=disable", 34 | } 35 | 36 | Convey("Basic operation", func() { 37 | m := macaron.New() 38 | m.Use(session.Sessioner(opt)) 39 | 40 | m.Get("/", func(ctx *macaron.Context, sess session.Store) { 41 | So(sess.Set("uname", "unknwon"), ShouldBeNil) 42 | }) 43 | m.Get("/reg", func(ctx *macaron.Context, sess session.Store) { 44 | raw, err := sess.RegenerateId(ctx) 45 | So(err, ShouldBeNil) 46 | So(raw, ShouldNotBeNil) 47 | 48 | uname := raw.Get("uname") 49 | So(uname, ShouldNotBeNil) 50 | So(uname, ShouldEqual, "unknwon") 51 | }) 52 | m.Get("/get", func(ctx *macaron.Context, sess session.Store) { 53 | sid := sess.ID() 54 | So(sid, ShouldNotBeEmpty) 55 | 56 | raw, err := sess.Read(sid) 57 | So(err, ShouldBeNil) 58 | So(raw, ShouldNotBeNil) 59 | So(raw.Release(), ShouldBeNil) 60 | 61 | uname := sess.Get("uname") 62 | So(uname, ShouldNotBeNil) 63 | So(uname, ShouldEqual, "unknwon") 64 | 65 | So(sess.Delete("uname"), ShouldBeNil) 66 | So(sess.Get("uname"), ShouldBeNil) 67 | 68 | So(sess.Destory(ctx), ShouldBeNil) 69 | }) 70 | 71 | resp := httptest.NewRecorder() 72 | req, err := http.NewRequest("GET", "/", nil) 73 | So(err, ShouldBeNil) 74 | m.ServeHTTP(resp, req) 75 | 76 | cookie := resp.Header().Get("Set-Cookie") 77 | 78 | resp = httptest.NewRecorder() 79 | req, err = http.NewRequest("GET", "/reg", nil) 80 | So(err, ShouldBeNil) 81 | req.Header.Set("Cookie", cookie) 82 | m.ServeHTTP(resp, req) 83 | 84 | cookie = resp.Header().Get("Set-Cookie") 85 | 86 | resp = httptest.NewRecorder() 87 | req, err = http.NewRequest("GET", "/get", nil) 88 | So(err, ShouldBeNil) 89 | req.Header.Set("Cookie", cookie) 90 | m.ServeHTTP(resp, req) 91 | }) 92 | 93 | Convey("Regenrate empty session", func() { 94 | m := macaron.New() 95 | m.Use(session.Sessioner(opt)) 96 | m.Get("/", func(ctx *macaron.Context, sess session.Store) { 97 | raw, err := sess.RegenerateId(ctx) 98 | So(err, ShouldBeNil) 99 | So(raw, ShouldNotBeNil) 100 | 101 | So(sess.Destory(ctx), ShouldBeNil) 102 | }) 103 | 104 | resp := httptest.NewRecorder() 105 | req, err := http.NewRequest("GET", "/", nil) 106 | So(err, ShouldBeNil) 107 | req.Header.Set("Cookie", "MacaronSession=ad2c7e3cbecfcf48; Path=/;") 108 | m.ServeHTTP(resp, req) 109 | }) 110 | 111 | Convey("GC session", func() { 112 | m := macaron.New() 113 | opt2 := opt 114 | opt2.Gclifetime = 1 115 | m.Use(session.Sessioner(opt2)) 116 | 117 | m.Get("/", func(sess session.Store) { 118 | So(sess.Set("uname", "unknwon"), ShouldBeNil) 119 | So(sess.ID(), ShouldNotBeEmpty) 120 | uname := sess.Get("uname") 121 | So(uname, ShouldNotBeNil) 122 | So(uname, ShouldEqual, "unknwon") 123 | 124 | So(sess.Flush(), ShouldBeNil) 125 | So(sess.Get("uname"), ShouldBeNil) 126 | 127 | time.Sleep(2 * time.Second) 128 | sess.GC() 129 | So(sess.Count(), ShouldEqual, 0) 130 | }) 131 | 132 | resp := httptest.NewRecorder() 133 | req, err := http.NewRequest("GET", "/", nil) 134 | So(err, ShouldBeNil) 135 | m.ServeHTTP(resp, req) 136 | }) 137 | }) 138 | } 139 | -------------------------------------------------------------------------------- /redis/redis.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Beego Authors 2 | // Copyright 2014 The Macaron Authors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 5 | // not use this file except in compliance with the License. You may obtain 6 | // a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations 14 | // under the License. 15 | 16 | package session 17 | 18 | import ( 19 | "context" 20 | "crypto/tls" 21 | "fmt" 22 | "strings" 23 | "sync" 24 | "time" 25 | 26 | "github.com/go-redis/redis/v8" 27 | "github.com/unknwon/com" 28 | "gopkg.in/ini.v1" 29 | 30 | "github.com/go-macaron/session" 31 | ) 32 | 33 | // since we do not use context define global once 34 | var ctx = context.TODO() 35 | 36 | // RedisStore represents a redis session store implementation. 37 | type RedisStore struct { 38 | c *redis.Client 39 | prefix, sid string 40 | duration time.Duration 41 | lock sync.RWMutex 42 | data map[interface{}]interface{} 43 | } 44 | 45 | // NewRedisStore creates and returns a redis session store. 46 | func NewRedisStore(c *redis.Client, prefix, sid string, dur time.Duration, kv map[interface{}]interface{}) *RedisStore { 47 | return &RedisStore{ 48 | c: c, 49 | prefix: prefix, 50 | sid: sid, 51 | duration: dur, 52 | data: kv, 53 | } 54 | } 55 | 56 | // Set sets value to given key in session. 57 | func (s *RedisStore) Set(key, val interface{}) error { 58 | s.lock.Lock() 59 | defer s.lock.Unlock() 60 | 61 | s.data[key] = val 62 | return nil 63 | } 64 | 65 | // Get gets value by given key in session. 66 | func (s *RedisStore) Get(key interface{}) interface{} { 67 | s.lock.RLock() 68 | defer s.lock.RUnlock() 69 | 70 | return s.data[key] 71 | } 72 | 73 | // Delete delete a key from session. 74 | func (s *RedisStore) Delete(key interface{}) error { 75 | s.lock.Lock() 76 | defer s.lock.Unlock() 77 | 78 | delete(s.data, key) 79 | return nil 80 | } 81 | 82 | // ID returns current session ID. 83 | func (s *RedisStore) ID() string { 84 | return s.sid 85 | } 86 | 87 | // Release releases resource and save data to provider. 88 | func (s *RedisStore) Release() error { 89 | // Skip encoding if the data is empty 90 | if len(s.data) == 0 { 91 | return nil 92 | } 93 | 94 | data, err := session.EncodeGob(s.data) 95 | if err != nil { 96 | return err 97 | } 98 | 99 | return s.c.Set(ctx, s.prefix+s.sid, string(data), s.duration).Err() 100 | } 101 | 102 | // Flush deletes all session data. 103 | func (s *RedisStore) Flush() error { 104 | s.lock.Lock() 105 | defer s.lock.Unlock() 106 | 107 | s.data = make(map[interface{}]interface{}) 108 | return nil 109 | } 110 | 111 | // RedisProvider represents a redis session provider implementation. 112 | type RedisProvider struct { 113 | c *redis.Client 114 | duration time.Duration 115 | prefix string 116 | } 117 | 118 | // Init initializes redis session provider. 119 | // configs: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180,prefix=session,tls=true 120 | func (p *RedisProvider) Init(maxlifetime int64, configs string) (err error) { 121 | p.duration, err = time.ParseDuration(fmt.Sprintf("%ds", maxlifetime)) 122 | if err != nil { 123 | return err 124 | } 125 | 126 | cfg, err := ini.Load([]byte(strings.Replace(configs, ",", "\n", -1))) 127 | if err != nil { 128 | return err 129 | } 130 | 131 | section, err := cfg.GetSection("") 132 | if err == nil && section != nil && section.Key("ha_mode").Value() == "sentinel" { 133 | return p.initSentinel(cfg) 134 | } 135 | 136 | opt := &redis.Options{ 137 | Network: "tcp", 138 | } 139 | for k, v := range cfg.Section("").KeysHash() { 140 | switch k { 141 | case "network": 142 | opt.Network = v 143 | case "addr": 144 | opt.Addr = v 145 | case "password": 146 | opt.Password = v 147 | case "db": 148 | opt.DB = com.StrTo(v).MustInt() 149 | case "pool_size": 150 | opt.PoolSize = com.StrTo(v).MustInt() 151 | case "idle_timeout": 152 | opt.IdleTimeout, err = time.ParseDuration(v + "s") 153 | if err != nil { 154 | return fmt.Errorf("error parsing idle timeout: %v", err) 155 | } 156 | case "prefix": 157 | p.prefix = v 158 | case "ha_mode": 159 | // avoid panic 160 | case "tls": 161 | opt.TLSConfig = &tls.Config{ 162 | InsecureSkipVerify: true, 163 | } 164 | default: 165 | return fmt.Errorf("session/redis: unsupported option '%s'", k) 166 | } 167 | } 168 | 169 | p.c = redis.NewClient(opt) 170 | return p.c.Ping(ctx).Err() 171 | } 172 | 173 | func (p *RedisProvider) initSentinel(cfg *ini.File) (err error) { 174 | opt := &redis.FailoverOptions{} 175 | 176 | for k, v := range cfg.Section("").KeysHash() { 177 | switch k { 178 | case "master_name": 179 | opt.MasterName = v 180 | case "sentinel_Addrs": 181 | opt.SentinelAddrs = strings.Split(v, "|") 182 | case "password": 183 | opt.Password = v 184 | case "db": 185 | opt.DB = com.StrTo(v).MustInt() 186 | case "pool_size": 187 | opt.PoolSize = com.StrTo(v).MustInt() 188 | case "dial_timeout": 189 | opt.DialTimeout, err = time.ParseDuration(v + "s") 190 | if err != nil { 191 | return fmt.Errorf("error parsing dial timeout: %v", err) 192 | } 193 | case "read_timeout": 194 | opt.ReadTimeout, err = time.ParseDuration(v + "s") 195 | if err != nil { 196 | return fmt.Errorf("error parsing read timeout: %v", err) 197 | } 198 | case "write_timeout": 199 | opt.WriteTimeout, err = time.ParseDuration(v + "s") 200 | if err != nil { 201 | return fmt.Errorf("error parsing write timeout: %v", err) 202 | } 203 | case "idle_timeout": 204 | opt.IdleTimeout, err = time.ParseDuration(v + "s") 205 | if err != nil { 206 | return fmt.Errorf("error parsing idle timeout: %v", err) 207 | } 208 | case "prefix": 209 | p.prefix = v 210 | } 211 | } 212 | p.c = redis.NewFailoverClient(opt) 213 | return p.c.Ping(ctx).Err() 214 | } 215 | 216 | // Read returns raw session store by session ID. 217 | func (p *RedisProvider) Read(sid string) (session.RawStore, error) { 218 | psid := p.prefix + sid 219 | if !p.Exist(sid) { 220 | if err := p.c.Set(ctx, psid, "", p.duration).Err(); err != nil { 221 | return nil, err 222 | } 223 | } 224 | 225 | var kv map[interface{}]interface{} 226 | kvs, err := p.c.Get(ctx, psid).Result() 227 | if err != nil { 228 | return nil, err 229 | } 230 | if len(kvs) == 0 { 231 | kv = make(map[interface{}]interface{}) 232 | } else { 233 | kv, err = session.DecodeGob([]byte(kvs)) 234 | if err != nil { 235 | return nil, err 236 | } 237 | } 238 | 239 | return NewRedisStore(p.c, p.prefix, sid, p.duration, kv), nil 240 | } 241 | 242 | // Exist returns true if session with given ID exists. 243 | func (p *RedisProvider) Exist(sid string) bool { 244 | count, err := p.c.Exists(ctx, p.prefix+sid).Result() 245 | return err == nil && count == 1 246 | } 247 | 248 | // Destory deletes a session by session ID. 249 | func (p *RedisProvider) Destory(sid string) error { 250 | return p.c.Del(ctx, p.prefix+sid).Err() 251 | } 252 | 253 | // Regenerate regenerates a session store from old session ID to new one. 254 | func (p *RedisProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) { 255 | poldsid := p.prefix + oldsid 256 | psid := p.prefix + sid 257 | 258 | if p.Exist(sid) { 259 | return nil, fmt.Errorf("new sid '%s' already exists", sid) 260 | } else if !p.Exist(oldsid) { 261 | // Make a fake old session. 262 | if err = p.c.Set(ctx, poldsid, "", p.duration).Err(); err != nil { 263 | return nil, err 264 | } 265 | } 266 | 267 | if err = p.c.Rename(ctx, poldsid, psid).Err(); err != nil { 268 | return nil, err 269 | } 270 | 271 | var kv map[interface{}]interface{} 272 | kvs, err := p.c.Get(ctx, psid).Result() 273 | if err != nil { 274 | return nil, err 275 | } 276 | 277 | if len(kvs) == 0 { 278 | kv = make(map[interface{}]interface{}) 279 | } else { 280 | kv, err = session.DecodeGob([]byte(kvs)) 281 | if err != nil { 282 | return nil, err 283 | } 284 | } 285 | 286 | return NewRedisStore(p.c, p.prefix, sid, p.duration, kv), nil 287 | } 288 | 289 | // Count counts and returns number of sessions. 290 | func (p *RedisProvider) Count() int { 291 | count, err := p.c.DBSize(ctx).Result() 292 | if err != nil { 293 | return 0 294 | } 295 | return int(count) 296 | } 297 | 298 | // GC calls GC to clean expired sessions. 299 | func (_ *RedisProvider) GC() {} 300 | 301 | func init() { 302 | session.Register("redis", &RedisProvider{}) 303 | } 304 | -------------------------------------------------------------------------------- /redis/redis.goconvey: -------------------------------------------------------------------------------- 1 | ignore -------------------------------------------------------------------------------- /redis/redis_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Macaron Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package session 16 | 17 | import ( 18 | "net/http" 19 | "net/http/httptest" 20 | "testing" 21 | 22 | . "github.com/smartystreets/goconvey/convey" 23 | "gopkg.in/macaron.v1" 24 | 25 | "github.com/go-macaron/session" 26 | ) 27 | 28 | func Test_RedisProvider(t *testing.T) { 29 | Convey("Test redis session provider", t, func() { 30 | opt := session.Options{ 31 | Provider: "redis", 32 | ProviderConfig: "addr=:6379,prefix=session:", 33 | } 34 | 35 | Convey("Basic operation", func() { 36 | m := macaron.New() 37 | m.Use(session.Sessioner(opt)) 38 | 39 | m.Get("/", func(ctx *macaron.Context, sess session.Store) { 40 | So(sess.Set("uname", "unknwon"), ShouldBeNil) 41 | }) 42 | m.Get("/reg", func(ctx *macaron.Context, sess session.Store) { 43 | raw, err := sess.RegenerateId(ctx) 44 | So(err, ShouldBeNil) 45 | So(raw, ShouldNotBeNil) 46 | 47 | uname := raw.Get("uname") 48 | So(uname, ShouldNotBeNil) 49 | So(uname, ShouldEqual, "unknwon") 50 | }) 51 | m.Get("/get", func(ctx *macaron.Context, sess session.Store) { 52 | sid := sess.ID() 53 | So(sid, ShouldNotBeEmpty) 54 | 55 | raw, err := sess.Read(sid) 56 | So(err, ShouldBeNil) 57 | So(raw, ShouldNotBeNil) 58 | 59 | uname := sess.Get("uname") 60 | So(uname, ShouldNotBeNil) 61 | So(uname, ShouldEqual, "unknwon") 62 | 63 | So(sess.Delete("uname"), ShouldBeNil) 64 | So(sess.Get("uname"), ShouldBeNil) 65 | 66 | So(sess.Destory(ctx), ShouldBeNil) 67 | }) 68 | 69 | resp := httptest.NewRecorder() 70 | req, err := http.NewRequest("GET", "/", nil) 71 | So(err, ShouldBeNil) 72 | m.ServeHTTP(resp, req) 73 | 74 | cookie := resp.Header().Get("Set-Cookie") 75 | 76 | resp = httptest.NewRecorder() 77 | req, err = http.NewRequest("GET", "/reg", nil) 78 | So(err, ShouldBeNil) 79 | req.Header.Set("Cookie", cookie) 80 | m.ServeHTTP(resp, req) 81 | 82 | cookie = resp.Header().Get("Set-Cookie") 83 | 84 | resp = httptest.NewRecorder() 85 | req, err = http.NewRequest("GET", "/get", nil) 86 | So(err, ShouldBeNil) 87 | req.Header.Set("Cookie", cookie) 88 | m.ServeHTTP(resp, req) 89 | }) 90 | 91 | Convey("Regenrate empty session", func() { 92 | m := macaron.New() 93 | m.Use(session.Sessioner(opt)) 94 | m.Get("/", func(ctx *macaron.Context, sess session.Store) { 95 | raw, err := sess.RegenerateId(ctx) 96 | So(err, ShouldBeNil) 97 | So(raw, ShouldNotBeNil) 98 | }) 99 | 100 | resp := httptest.NewRecorder() 101 | req, err := http.NewRequest("GET", "/", nil) 102 | So(err, ShouldBeNil) 103 | req.Header.Set("Cookie", "MacaronSession=ad2c7e3cbecfcf486; Path=/;") 104 | m.ServeHTTP(resp, req) 105 | }) 106 | }) 107 | } 108 | -------------------------------------------------------------------------------- /session.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Beego Authors 2 | // Copyright 2014 The Macaron Authors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 5 | // not use this file except in compliance with the License. You may obtain 6 | // a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations 14 | // under the License. 15 | 16 | // Package session a middleware that provides the session management of Macaron. 17 | package session 18 | 19 | import ( 20 | "encoding/hex" 21 | "errors" 22 | "fmt" 23 | "net/http" 24 | "net/url" 25 | "time" 26 | 27 | "gopkg.in/macaron.v1" 28 | ) 29 | 30 | const _VERSION = "0.6.0" 31 | 32 | func Version() string { 33 | return _VERSION 34 | } 35 | 36 | // RawStore is the interface that operates the session data. 37 | type RawStore interface { 38 | // Set sets value to given key in session. 39 | Set(interface{}, interface{}) error 40 | // Get gets value by given key in session. 41 | Get(interface{}) interface{} 42 | // Delete deletes a key from session. 43 | Delete(interface{}) error 44 | // ID returns current session ID. 45 | ID() string 46 | // Release releases session resource and save data to provider. 47 | Release() error 48 | // Flush deletes all session data. 49 | Flush() error 50 | } 51 | 52 | // Store is the interface that contains all data for one session process with specific ID. 53 | type Store interface { 54 | RawStore 55 | // Read returns raw session store by session ID. 56 | Read(string) (RawStore, error) 57 | // Destory deletes a session. 58 | Destory(*macaron.Context) error 59 | // RegenerateId regenerates a session store from old session ID to new one. 60 | RegenerateId(*macaron.Context) (RawStore, error) 61 | // Count counts and returns number of sessions. 62 | Count() int 63 | // GC calls GC to clean expired sessions. 64 | GC() 65 | } 66 | 67 | type store struct { 68 | RawStore 69 | *Manager 70 | } 71 | 72 | var _ Store = &store{} 73 | 74 | // Options represents a struct for specifying configuration options for the session middleware. 75 | type Options struct { 76 | // Name of provider. Default is "memory". 77 | Provider string 78 | // Provider configuration, it's corresponding to provider. 79 | ProviderConfig string 80 | // Cookie name to save session ID. Default is "MacaronSession". 81 | CookieName string 82 | // Cookie path to store. Default is "/". 83 | CookiePath string 84 | // GC interval time in seconds. Default is 3600. 85 | Gclifetime int64 86 | // Max life time in seconds. Default is whatever GC interval time is. 87 | Maxlifetime int64 88 | // Use HTTPS only. Default is false. 89 | Secure bool 90 | // Cookie life time. Default is 0. 91 | CookieLifeTime int 92 | // Cookie SameSite default is false (Lax), can be set to true (Strict) 93 | CookieSameSite bool 94 | // Cookie domain name. Default is empty. 95 | Domain string 96 | // Session ID length. Default is 16. 97 | IDLength int 98 | // Configuration section name. Default is "session". 99 | Section string 100 | // Ignore release for websocket. Default is false. 101 | IgnoreReleaseForWebSocket bool 102 | } 103 | 104 | func prepareOptions(options []Options) Options { 105 | var opt Options 106 | if len(options) > 0 { 107 | opt = options[0] 108 | } 109 | if len(opt.Section) == 0 { 110 | opt.Section = "session" 111 | } 112 | sec := macaron.Config().Section(opt.Section) 113 | 114 | if len(opt.Provider) == 0 { 115 | opt.Provider = sec.Key("PROVIDER").MustString("memory") 116 | } 117 | if len(opt.ProviderConfig) == 0 { 118 | opt.ProviderConfig = sec.Key("PROVIDER_CONFIG").MustString("data/sessions") 119 | } 120 | if len(opt.CookieName) == 0 { 121 | opt.CookieName = sec.Key("COOKIE_NAME").MustString("MacaronSession") 122 | } 123 | if len(opt.CookiePath) == 0 { 124 | opt.CookiePath = sec.Key("COOKIE_PATH").MustString("/") 125 | } 126 | if opt.Gclifetime == 0 { 127 | opt.Gclifetime = sec.Key("GC_INTERVAL_TIME").MustInt64(3600) 128 | } 129 | if opt.Maxlifetime == 0 { 130 | opt.Maxlifetime = sec.Key("MAX_LIFE_TIME").MustInt64(opt.Gclifetime) 131 | } 132 | if !opt.Secure { 133 | opt.Secure = sec.Key("SECURE").MustBool() 134 | } 135 | if !opt.CookieSameSite { 136 | opt.CookieSameSite = sec.Key("COOKIE_SAME_SITE").MustBool() 137 | } 138 | if opt.CookieLifeTime == 0 { 139 | opt.CookieLifeTime = sec.Key("COOKIE_LIFE_TIME").MustInt() 140 | } 141 | if len(opt.Domain) == 0 { 142 | opt.Domain = sec.Key("DOMAIN").String() 143 | } 144 | if opt.IDLength == 0 { 145 | opt.IDLength = sec.Key("ID_LENGTH").MustInt(16) 146 | } 147 | if !opt.IgnoreReleaseForWebSocket { 148 | opt.IgnoreReleaseForWebSocket = sec.Key("IGNORE_RELEASE_FOR_WEBSOCKET").MustBool() 149 | } 150 | 151 | return opt 152 | } 153 | 154 | // Sessioner is a middleware that maps a session.SessionStore service into the Macaron handler chain. 155 | // An single variadic session.Options struct can be optionally provided to configure. 156 | func Sessioner(options ...Options) macaron.Handler { 157 | opt := prepareOptions(options) 158 | manager, err := NewManager(opt.Provider, opt) 159 | if err != nil { 160 | panic(err) 161 | } 162 | go manager.startGC() 163 | 164 | return func(ctx *macaron.Context) { 165 | sess, err := manager.Start(ctx) 166 | if err != nil { 167 | panic("session(start): " + err.Error()) 168 | } 169 | 170 | // Get flash. 171 | vals, _ := url.ParseQuery(ctx.GetCookie("macaron_flash")) 172 | if len(vals) > 0 { 173 | f := &Flash{Values: vals} 174 | f.ErrorMsg = f.Get("error") 175 | f.SuccessMsg = f.Get("success") 176 | f.InfoMsg = f.Get("info") 177 | f.WarningMsg = f.Get("warning") 178 | ctx.Data["Flash"] = f 179 | ctx.SetCookie("macaron_flash", "", -1, opt.CookiePath) 180 | } 181 | 182 | f := &Flash{ctx, url.Values{}, "", "", "", ""} 183 | ctx.Resp.Before(func(macaron.ResponseWriter) { 184 | if flash := f.Encode(); len(flash) > 0 { 185 | ctx.SetCookie("macaron_flash", flash, 0, opt.CookiePath) 186 | } 187 | }) 188 | 189 | ctx.Map(f) 190 | s := store{ 191 | RawStore: sess, 192 | Manager: manager, 193 | } 194 | 195 | ctx.MapTo(s, (*Store)(nil)) 196 | 197 | ctx.Next() 198 | 199 | if manager.opt.IgnoreReleaseForWebSocket && ctx.Req.Header.Get("Upgrade") == "websocket" { 200 | return 201 | } 202 | 203 | if err = sess.Release(); err != nil { 204 | panic("session(release): " + err.Error()) 205 | } 206 | } 207 | } 208 | 209 | // Provider is the interface that provides session manipulations. 210 | type Provider interface { 211 | // Init initializes session provider. 212 | Init(gclifetime int64, config string) error 213 | // Read returns raw session store by session ID. 214 | Read(sid string) (RawStore, error) 215 | // Exist returns true if session with given ID exists. 216 | Exist(sid string) bool 217 | // Destory deletes a session by session ID. 218 | Destory(sid string) error 219 | // Regenerate regenerates a session store from old session ID to new one. 220 | Regenerate(oldsid, sid string) (RawStore, error) 221 | // Count counts and returns number of sessions. 222 | Count() int 223 | // GC calls GC to clean expired sessions. 224 | GC() 225 | } 226 | 227 | var providers = make(map[string]Provider) 228 | 229 | // Register registers a provider. 230 | func Register(name string, provider Provider) { 231 | if provider == nil { 232 | panic("session: cannot register provider with nil value") 233 | } 234 | if _, dup := providers[name]; dup { 235 | panic(fmt.Errorf("session: cannot register provider '%s' twice", name)) 236 | } 237 | providers[name] = provider 238 | } 239 | 240 | // _____ 241 | // / \ _____ ____ _____ ____ ___________ 242 | // / \ / \\__ \ / \\__ \ / ___\_/ __ \_ __ \ 243 | // / Y \/ __ \| | \/ __ \_/ /_/ > ___/| | \/ 244 | // \____|__ (____ /___| (____ /\___ / \___ >__| 245 | // \/ \/ \/ \//_____/ \/ 246 | 247 | // Manager represents a struct that contains session provider and its configuration. 248 | type Manager struct { 249 | provider Provider 250 | opt Options 251 | } 252 | 253 | // NewManager creates and returns a new session manager by given provider name and configuration. 254 | // It panics when given provider isn't registered. 255 | func NewManager(name string, opt Options) (*Manager, error) { 256 | p, ok := providers[name] 257 | if !ok { 258 | return nil, fmt.Errorf("session: unknown provider '%s'(forgotten import?)", name) 259 | } 260 | return &Manager{p, opt}, p.Init(opt.Maxlifetime, opt.ProviderConfig) 261 | } 262 | 263 | // sessionID generates a new session ID with rand string, unix nano time, remote addr by hash function. 264 | func (m *Manager) sessionID() string { 265 | return hex.EncodeToString(generateRandomKey(m.opt.IDLength / 2)) 266 | } 267 | 268 | // validSessionID tests whether a provided session ID is a valid session ID. 269 | func (m *Manager) validSessionID(sid string) (bool, error) { 270 | if len(sid) != m.opt.IDLength { 271 | return false, errors.New("invalid 'sid': " + sid) 272 | } 273 | 274 | for i := range sid { 275 | switch { 276 | case '0' <= sid[i] && sid[i] <= '9': 277 | case 'a' <= sid[i] && sid[i] <= 'f': 278 | default: 279 | return false, errors.New("invalid 'sid': " + sid) 280 | } 281 | } 282 | return true, nil 283 | } 284 | 285 | // Start starts a session by generating new one 286 | // or retrieve existence one by reading session ID from HTTP request if it's valid. 287 | func (m *Manager) Start(ctx *macaron.Context) (RawStore, error) { 288 | sid := ctx.GetCookie(m.opt.CookieName) 289 | valid, _ := m.validSessionID(sid) 290 | if len(sid) > 0 && valid && m.provider.Exist(sid) { 291 | return m.provider.Read(sid) 292 | } 293 | 294 | sid = m.sessionID() 295 | sess, err := m.provider.Read(sid) 296 | if err != nil { 297 | return nil, err 298 | } 299 | 300 | sameSite := http.SameSiteLaxMode 301 | if m.opt.CookieSameSite { 302 | sameSite = http.SameSiteStrictMode 303 | } 304 | 305 | cookie := &http.Cookie{ 306 | Name: m.opt.CookieName, 307 | Value: sid, 308 | Path: m.opt.CookiePath, 309 | HttpOnly: true, 310 | Secure: m.opt.Secure, 311 | Domain: m.opt.Domain, 312 | SameSite: sameSite, 313 | } 314 | if m.opt.CookieLifeTime >= 0 { 315 | cookie.MaxAge = m.opt.CookieLifeTime 316 | } 317 | http.SetCookie(ctx.Resp, cookie) 318 | ctx.Req.AddCookie(cookie) 319 | return sess, nil 320 | } 321 | 322 | // Read returns raw session store by session ID. 323 | func (m *Manager) Read(sid string) (RawStore, error) { 324 | // Ensure we're trying to read a valid session ID 325 | if _, err := m.validSessionID(sid); err != nil { 326 | return nil, err 327 | } 328 | 329 | return m.provider.Read(sid) 330 | } 331 | 332 | // Destory deletes a session by given ID. 333 | func (m *Manager) Destory(ctx *macaron.Context) error { 334 | sid := ctx.GetCookie(m.opt.CookieName) 335 | if len(sid) == 0 { 336 | return nil 337 | } 338 | 339 | if _, err := m.validSessionID(sid); err != nil { 340 | return err 341 | } 342 | 343 | if err := m.provider.Destory(sid); err != nil { 344 | return err 345 | } 346 | cookie := &http.Cookie{ 347 | Name: m.opt.CookieName, 348 | Path: m.opt.CookiePath, 349 | HttpOnly: true, 350 | Expires: time.Now(), 351 | MaxAge: -1, 352 | } 353 | http.SetCookie(ctx.Resp, cookie) 354 | return nil 355 | } 356 | 357 | // RegenerateId regenerates a session store from old session ID to new one. 358 | func (m *Manager) RegenerateId(ctx *macaron.Context) (sess RawStore, err error) { 359 | sid := m.sessionID() 360 | oldsid := ctx.GetCookie(m.opt.CookieName) 361 | _, err = m.validSessionID(oldsid) 362 | if err != nil { 363 | return nil, err 364 | } 365 | sess, err = m.provider.Regenerate(oldsid, sid) 366 | if err != nil { 367 | return nil, err 368 | } 369 | cookie := &http.Cookie{ 370 | Name: m.opt.CookieName, 371 | Value: sid, 372 | Path: m.opt.CookiePath, 373 | HttpOnly: true, 374 | Secure: m.opt.Secure, 375 | Domain: m.opt.Domain, 376 | } 377 | if m.opt.CookieLifeTime >= 0 { 378 | cookie.MaxAge = m.opt.CookieLifeTime 379 | } 380 | http.SetCookie(ctx.Resp, cookie) 381 | ctx.Req.AddCookie(cookie) 382 | return sess, nil 383 | } 384 | 385 | // Count counts and returns number of sessions. 386 | func (m *Manager) Count() int { 387 | return m.provider.Count() 388 | } 389 | 390 | // GC starts GC job in a certain period. 391 | func (m *Manager) GC() { 392 | m.provider.GC() 393 | } 394 | 395 | // startGC starts GC job in a certain period. 396 | func (m *Manager) startGC() { 397 | m.GC() 398 | time.AfterFunc(time.Duration(m.opt.Gclifetime)*time.Second, func() { m.startGC() }) 399 | } 400 | 401 | // SetSecure indicates whether to set cookie with HTTPS or not. 402 | func (m *Manager) SetSecure(secure bool) { 403 | m.opt.Secure = secure 404 | } 405 | -------------------------------------------------------------------------------- /session_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The Macaron Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package session 16 | 17 | import ( 18 | "net/http" 19 | "net/http/httptest" 20 | "strings" 21 | "testing" 22 | "time" 23 | 24 | . "github.com/smartystreets/goconvey/convey" 25 | "gopkg.in/macaron.v1" 26 | ) 27 | 28 | func Test_Version(t *testing.T) { 29 | Convey("Check package version", t, func() { 30 | So(Version(), ShouldEqual, _VERSION) 31 | }) 32 | } 33 | 34 | func Test_Sessioner(t *testing.T) { 35 | Convey("Use session middleware", t, func() { 36 | m := macaron.New() 37 | m.Use(Sessioner()) 38 | m.Get("/", func() {}) 39 | 40 | resp := httptest.NewRecorder() 41 | req, err := http.NewRequest("GET", "/", nil) 42 | So(err, ShouldBeNil) 43 | m.ServeHTTP(resp, req) 44 | }) 45 | 46 | Convey("Register invalid provider", t, func() { 47 | Convey("Provider not exists", func() { 48 | defer func() { 49 | So(recover(), ShouldNotBeNil) 50 | }() 51 | 52 | m := macaron.New() 53 | m.Use(Sessioner(Options{ 54 | Provider: "fake", 55 | })) 56 | }) 57 | 58 | Convey("Provider value is nil", func() { 59 | defer func() { 60 | So(recover(), ShouldNotBeNil) 61 | }() 62 | 63 | Register("fake", nil) 64 | }) 65 | 66 | Convey("Register twice", func() { 67 | defer func() { 68 | So(recover(), ShouldNotBeNil) 69 | }() 70 | 71 | Register("memory", &MemProvider{}) 72 | }) 73 | }) 74 | } 75 | 76 | func testProvider(opt Options) { 77 | Convey("Basic operation", func() { 78 | m := macaron.New() 79 | m.Use(Sessioner(opt)) 80 | 81 | m.Get("/", func(ctx *macaron.Context, sess Store) { 82 | So(sess.Set("uname", "unknwon"), ShouldBeNil) 83 | }) 84 | m.Get("/reg", func(ctx *macaron.Context, sess Store) { 85 | raw, err := sess.RegenerateId(ctx) 86 | So(err, ShouldBeNil) 87 | So(raw, ShouldNotBeNil) 88 | 89 | uname := raw.Get("uname") 90 | So(uname, ShouldNotBeNil) 91 | So(uname, ShouldEqual, "unknwon") 92 | }) 93 | m.Get("/get", func(ctx *macaron.Context, sess Store) { 94 | sid := sess.ID() 95 | So(sid, ShouldNotBeEmpty) 96 | 97 | raw, err := sess.Read(sid) 98 | So(err, ShouldBeNil) 99 | So(raw, ShouldNotBeNil) 100 | 101 | uname := sess.Get("uname") 102 | So(uname, ShouldNotBeNil) 103 | So(uname, ShouldEqual, "unknwon") 104 | 105 | So(sess.Delete("uname"), ShouldBeNil) 106 | So(sess.Get("uname"), ShouldBeNil) 107 | 108 | So(sess.Destory(ctx), ShouldBeNil) 109 | }) 110 | 111 | resp := httptest.NewRecorder() 112 | req, err := http.NewRequest("GET", "/", nil) 113 | So(err, ShouldBeNil) 114 | m.ServeHTTP(resp, req) 115 | 116 | cookie := resp.Header().Get("Set-Cookie") 117 | 118 | resp = httptest.NewRecorder() 119 | req, err = http.NewRequest("GET", "/reg", nil) 120 | So(err, ShouldBeNil) 121 | req.Header.Set("Cookie", cookie) 122 | m.ServeHTTP(resp, req) 123 | 124 | cookie = resp.Header().Get("Set-Cookie") 125 | 126 | resp = httptest.NewRecorder() 127 | req, err = http.NewRequest("GET", "/get", nil) 128 | So(err, ShouldBeNil) 129 | req.Header.Set("Cookie", cookie) 130 | m.ServeHTTP(resp, req) 131 | }) 132 | 133 | Convey("Regenrate empty session", func() { 134 | m := macaron.New() 135 | m.Use(Sessioner(opt)) 136 | m.Get("/", func(ctx *macaron.Context, sess Store) { 137 | raw, err := sess.RegenerateId(ctx) 138 | So(err, ShouldBeNil) 139 | So(raw, ShouldNotBeNil) 140 | }) 141 | 142 | resp := httptest.NewRecorder() 143 | req, err := http.NewRequest("GET", "/", nil) 144 | So(err, ShouldBeNil) 145 | req.Header.Set("Cookie", "MacaronSession=ad2c7e3cbecfcf48; Path=/;") 146 | m.ServeHTTP(resp, req) 147 | }) 148 | 149 | Convey("GC session", func() { 150 | m := macaron.New() 151 | opt2 := opt 152 | opt2.Gclifetime = 1 153 | m.Use(Sessioner(opt2)) 154 | 155 | m.Get("/", func(sess Store) { 156 | So(sess.Set("uname", "unknwon"), ShouldBeNil) 157 | So(sess.ID(), ShouldNotBeEmpty) 158 | uname := sess.Get("uname") 159 | So(uname, ShouldNotBeNil) 160 | So(uname, ShouldEqual, "unknwon") 161 | 162 | So(sess.Flush(), ShouldBeNil) 163 | So(sess.Get("uname"), ShouldBeNil) 164 | 165 | time.Sleep(2 * time.Second) 166 | sess.GC() 167 | So(sess.Count(), ShouldEqual, 0) 168 | }) 169 | 170 | resp := httptest.NewRecorder() 171 | req, err := http.NewRequest("GET", "/", nil) 172 | So(err, ShouldBeNil) 173 | m.ServeHTTP(resp, req) 174 | }) 175 | 176 | Convey("Detect invalid sid", func() { 177 | m := macaron.New() 178 | m.Use(Sessioner(opt)) 179 | m.Get("/", func(ctx *macaron.Context, sess Store) { 180 | raw, err := sess.Read("../session/ad2c7e3cbecfcf486") 181 | So(strings.Contains(err.Error(), "invalid 'sid': "), ShouldBeTrue) 182 | So(raw, ShouldBeNil) 183 | }) 184 | 185 | resp := httptest.NewRecorder() 186 | req, err := http.NewRequest("GET", "/", nil) 187 | So(err, ShouldBeNil) 188 | m.ServeHTTP(resp, req) 189 | }) 190 | } 191 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Beego Authors 2 | // Copyright 2014 The Macaron Authors 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 5 | // not use this file except in compliance with the License. You may obtain 6 | // a copy of the License at 7 | // 8 | // http://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | // License for the specific language governing permissions and limitations 14 | // under the License. 15 | 16 | package session 17 | 18 | import ( 19 | "bytes" 20 | "crypto/rand" 21 | "encoding/gob" 22 | "io" 23 | 24 | "github.com/unknwon/com" 25 | ) 26 | 27 | func init() { 28 | gob.Register([]interface{}{}) 29 | gob.Register(map[int]interface{}{}) 30 | gob.Register(map[string]interface{}{}) 31 | gob.Register(map[interface{}]interface{}{}) 32 | gob.Register(map[string]string{}) 33 | gob.Register(map[int]string{}) 34 | gob.Register(map[int]int{}) 35 | gob.Register(map[int]int64{}) 36 | } 37 | 38 | func EncodeGob(obj map[interface{}]interface{}) ([]byte, error) { 39 | for _, v := range obj { 40 | gob.Register(v) 41 | } 42 | buf := bytes.NewBuffer(nil) 43 | err := gob.NewEncoder(buf).Encode(obj) 44 | return buf.Bytes(), err 45 | } 46 | 47 | func DecodeGob(encoded []byte) (out map[interface{}]interface{}, err error) { 48 | buf := bytes.NewBuffer(encoded) 49 | err = gob.NewDecoder(buf).Decode(&out) 50 | return out, err 51 | } 52 | 53 | // NOTE: A local copy in case of underlying package change 54 | var alphanum = []byte("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz") 55 | 56 | // generateRandomKey creates a random key with the given strength. 57 | func generateRandomKey(strength int) []byte { 58 | k := make([]byte, strength) 59 | if n, err := io.ReadFull(rand.Reader, k); n != strength || err != nil { 60 | return com.RandomCreateBytes(strength, alphanum...) 61 | } 62 | return k 63 | } 64 | --------------------------------------------------------------------------------