code.gitea.io/gitea@v1.19.3/modules/session/redis.go (about) 1 // Copyright 2013 Beego Authors 2 // Copyright 2014 The Macaron Authors 3 // Copyright 2020 The Gitea Authors. All rights reserved. 4 // 5 // Licensed under the Apache License, Version 2.0 (the "License"): you may 6 // not use this file except in compliance with the License. You may obtain 7 // a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, software 12 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 // License for the specific language governing permissions and limitations 15 // under the License. 16 // SPDX-License-Identifier: Apache-2.0 17 18 package session 19 20 import ( 21 "fmt" 22 "sync" 23 "time" 24 25 "code.gitea.io/gitea/modules/graceful" 26 "code.gitea.io/gitea/modules/nosql" 27 28 "gitea.com/go-chi/session" 29 "github.com/redis/go-redis/v9" 30 ) 31 32 // RedisStore represents a redis session store implementation. 33 type RedisStore struct { 34 c redis.UniversalClient 35 prefix, sid string 36 duration time.Duration 37 lock sync.RWMutex 38 data map[interface{}]interface{} 39 } 40 41 // NewRedisStore creates and returns a redis session store. 42 func NewRedisStore(c redis.UniversalClient, prefix, sid string, dur time.Duration, kv map[interface{}]interface{}) *RedisStore { 43 return &RedisStore{ 44 c: c, 45 prefix: prefix, 46 sid: sid, 47 duration: dur, 48 data: kv, 49 } 50 } 51 52 // Set sets value to given key in session. 53 func (s *RedisStore) Set(key, val interface{}) error { 54 s.lock.Lock() 55 defer s.lock.Unlock() 56 57 s.data[key] = val 58 return nil 59 } 60 61 // Get gets value by given key in session. 62 func (s *RedisStore) Get(key interface{}) interface{} { 63 s.lock.RLock() 64 defer s.lock.RUnlock() 65 66 return s.data[key] 67 } 68 69 // Delete delete a key from session. 70 func (s *RedisStore) Delete(key interface{}) error { 71 s.lock.Lock() 72 defer s.lock.Unlock() 73 74 delete(s.data, key) 75 return nil 76 } 77 78 // ID returns current session ID. 79 func (s *RedisStore) ID() string { 80 return s.sid 81 } 82 83 // Release releases resource and save data to provider. 84 func (s *RedisStore) Release() error { 85 // Skip encoding if the data is empty 86 if len(s.data) == 0 { 87 return nil 88 } 89 90 data, err := session.EncodeGob(s.data) 91 if err != nil { 92 return err 93 } 94 95 return s.c.Set(graceful.GetManager().HammerContext(), s.prefix+s.sid, string(data), s.duration).Err() 96 } 97 98 // Flush deletes all session data. 99 func (s *RedisStore) Flush() error { 100 s.lock.Lock() 101 defer s.lock.Unlock() 102 103 s.data = make(map[interface{}]interface{}) 104 return nil 105 } 106 107 // RedisProvider represents a redis session provider implementation. 108 type RedisProvider struct { 109 c redis.UniversalClient 110 duration time.Duration 111 prefix string 112 } 113 114 // Init initializes redis session provider. 115 // configs: network=tcp,addr=:6379,password=macaron,db=0,pool_size=100,idle_timeout=180,prefix=session; 116 func (p *RedisProvider) Init(maxlifetime int64, configs string) (err error) { 117 p.duration, err = time.ParseDuration(fmt.Sprintf("%ds", maxlifetime)) 118 if err != nil { 119 return err 120 } 121 122 uri := nosql.ToRedisURI(configs) 123 124 for k, v := range uri.Query() { 125 switch k { 126 case "prefix": 127 p.prefix = v[0] 128 } 129 } 130 131 p.c = nosql.GetManager().GetRedisClient(uri.String()) 132 return p.c.Ping(graceful.GetManager().ShutdownContext()).Err() 133 } 134 135 // Read returns raw session store by session ID. 136 func (p *RedisProvider) Read(sid string) (session.RawStore, error) { 137 psid := p.prefix + sid 138 if !p.Exist(sid) { 139 if err := p.c.Set(graceful.GetManager().HammerContext(), psid, "", p.duration).Err(); err != nil { 140 return nil, err 141 } 142 } 143 144 var kv map[interface{}]interface{} 145 kvs, err := p.c.Get(graceful.GetManager().HammerContext(), psid).Result() 146 if err != nil { 147 return nil, err 148 } 149 if len(kvs) == 0 { 150 kv = make(map[interface{}]interface{}) 151 } else { 152 kv, err = session.DecodeGob([]byte(kvs)) 153 if err != nil { 154 return nil, err 155 } 156 } 157 158 return NewRedisStore(p.c, p.prefix, sid, p.duration, kv), nil 159 } 160 161 // Exist returns true if session with given ID exists. 162 func (p *RedisProvider) Exist(sid string) bool { 163 v, err := p.c.Exists(graceful.GetManager().HammerContext(), p.prefix+sid).Result() 164 return err == nil && v == 1 165 } 166 167 // Destroy deletes a session by session ID. 168 func (p *RedisProvider) Destroy(sid string) error { 169 return p.c.Del(graceful.GetManager().HammerContext(), p.prefix+sid).Err() 170 } 171 172 // Regenerate regenerates a session store from old session ID to new one. 173 func (p *RedisProvider) Regenerate(oldsid, sid string) (_ session.RawStore, err error) { 174 poldsid := p.prefix + oldsid 175 psid := p.prefix + sid 176 177 if p.Exist(sid) { 178 return nil, fmt.Errorf("new sid '%s' already exists", sid) 179 } else if !p.Exist(oldsid) { 180 // Make a fake old session. 181 if err = p.c.Set(graceful.GetManager().HammerContext(), poldsid, "", p.duration).Err(); err != nil { 182 return nil, err 183 } 184 } 185 186 // do not use Rename here, because the old sid and new sid may be in different redis cluster slot. 187 kvs, err := p.c.Get(graceful.GetManager().HammerContext(), poldsid).Result() 188 if err != nil { 189 return nil, err 190 } 191 192 if err = p.c.Del(graceful.GetManager().HammerContext(), poldsid).Err(); err != nil { 193 return nil, err 194 } 195 196 if err = p.c.Set(graceful.GetManager().HammerContext(), psid, kvs, p.duration).Err(); err != nil { 197 return nil, err 198 } 199 200 var kv map[interface{}]interface{} 201 if len(kvs) == 0 { 202 kv = make(map[interface{}]interface{}) 203 } else { 204 kv, err = session.DecodeGob([]byte(kvs)) 205 if err != nil { 206 return nil, err 207 } 208 } 209 210 return NewRedisStore(p.c, p.prefix, sid, p.duration, kv), nil 211 } 212 213 // Count counts and returns number of sessions. 214 func (p *RedisProvider) Count() int { 215 size, err := p.c.DBSize(graceful.GetManager().HammerContext()).Result() 216 if err != nil { 217 return 0 218 } 219 return int(size) 220 } 221 222 // GC calls GC to clean expired sessions. 223 func (*RedisProvider) GC() {} 224 225 func init() { 226 session.Register("redis", &RedisProvider{}) 227 }