forked from kataras/go-sessions
-
Notifications
You must be signed in to change notification settings - Fork 3
/
provider.go
143 lines (124 loc) · 3.93 KB
/
provider.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package sessions
import (
"container/list"
"sync"
"time"
)
type (
// Provider contains the sessions memory store and any external databases
Provider struct {
mu sync.Mutex
sessions map[string]*list.Element // underline TEMPORARY memory store used to give advantage on sessions used more times than others
list *list.List // for GC
databases []Database
Expires time.Duration
}
)
// NewProvider returns a new sessions provider
func NewProvider(expires time.Duration) *Provider {
return &Provider{list: list.New(), sessions: make(map[string]*list.Element, 0), databases: make([]Database, 0), Expires: expires}
}
// RegisterDatabase adds a session database
// a session db doesn't have write access
func (p *Provider) RegisterDatabase(db Database) {
p.mu.Lock() // for any case
p.databases = append(p.databases, db)
p.mu.Unlock()
}
// NewSession returns a new session from sessionid
func (p *Provider) NewSession(sid string) Session {
sess := &session{
sid: sid,
provider: p,
lastAccessedTime: time.Now(),
values: p.loadSessionValues(sid),
}
if p.Expires > 0 { // if not unlimited life duration and no -1 (cookie remove action is based on browser's session)
time.AfterFunc(p.Expires, func() {
// the destroy makes the check if this session is exists then or not,
// this is used to destroy the session from the server-side also
// it's good to have here for security reasons, I didn't add it on the gc function to separate its action
p.Destroy(sid)
})
}
return sess
}
func (p *Provider) loadSessionValues(sid string) map[string]interface{} {
for i, n := 0, len(p.databases); i < n; i++ {
if dbValues := p.databases[i].Load(sid); dbValues != nil && len(dbValues) > 0 {
return dbValues // return the first non-empty from the registered stores.
}
}
values := make(map[string]interface{})
return values
}
func (p *Provider) updateDatabases(sid string, newValues map[string]interface{}) {
for i, n := 0, len(p.databases); i < n; i++ {
p.databases[i].Update(sid, newValues)
}
}
// Init creates the session and returns it
func (p *Provider) Init(sid string) Session {
newSession := p.NewSession(sid)
elem := p.list.PushBack(newSession)
p.mu.Lock()
p.sessions[sid] = elem
p.mu.Unlock()
return newSession
}
// Read returns the store which sid parameter is belongs
func (p *Provider) Read(sid string) Session {
p.mu.Lock()
if elem, found := p.sessions[sid]; found {
p.mu.Unlock() // yes defer is slow
elem.Value.(*session).lastAccessedTime = time.Now()
return elem.Value.(*session)
}
p.mu.Unlock()
// if not found create new
sess := p.Init(sid)
return sess
}
// Destroy destroys the session, removes all sessions values, the session itself and updates the registered session databases, this called from sessionManager which removes the client's cookie also.
func (p *Provider) Destroy(sid string) {
p.mu.Lock()
if elem, found := p.sessions[sid]; found {
sess := elem.Value.(*session)
sess.values = nil
p.updateDatabases(sid, nil)
delete(p.sessions, sid)
p.list.Remove(elem)
}
p.mu.Unlock()
}
// Update updates the lastAccessedTime, and moves the memory place element to the front
// always returns a nil error, for now
func (p *Provider) update(sid string) {
p.mu.Lock()
if elem, found := p.sessions[sid]; found {
sess := elem.Value.(*session)
sess.lastAccessedTime = time.Now()
p.list.MoveToFront(elem)
p.updateDatabases(sid, sess.values)
}
p.mu.Unlock()
}
// GC clears the memory
func (p *Provider) GC(duration time.Duration) {
p.mu.Lock()
defer p.mu.Unlock()
for {
elem := p.list.Back()
if elem == nil {
break
}
// if the time has passed. session was expired, then delete the session and its memory place
// we are not destroy the session completely for the case this is re-used after
sess := elem.Value.(*session)
if time.Now().After(sess.lastAccessedTime.Add(duration)) {
p.list.Remove(elem)
} else {
break
}
}
}