forked from kataras/iris
-
Notifications
You must be signed in to change notification settings - Fork 0
/
database.go
176 lines (154 loc) · 5.86 KB
/
database.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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
package sessions
import (
"bytes"
"encoding/gob"
"io"
"sync"
"github.com/kataras/iris/core/memstore"
)
func init() {
gob.Register(RemoteStore{})
}
// Database is the interface which all session databases should implement
// By design it doesn't support any type of cookie session like other frameworks.
// I want to protect you, believe me.
// The scope of the database is to store somewhere the sessions in order to
// keep them after restarting the server, nothing more.
//
// Synchronization are made automatically, you can register more than one session database
// but the first non-empty Load return data will be used as the session values.
//
//
// Note: Expiration on Load is up to the database, meaning that:
// the database can decide how to retrieve and parse the expiration datetime
//
// I'll try to explain you the flow:
//
// .Start -> if session database attached then load from that storage and save to the memory, otherwise load from memory. The load from database is done once on the initialize of each session.
// .Get (important) -> load from memory,
// if database attached then it already loaded the values
// from database on the .Start action, so it will
// retrieve the data from the memory (fast)
// .Set -> set to the memory, if database attached then update the storage
// .Delete -> clear from memory, if database attached then update the storage
// .Destroy -> destroy from memory and client cookie,
// if database attached then update the storage with empty values,
// empty values means delete the storage with that specific session id.
// Using everything else except memory is slower than memory but database is
// fetched once at each session and its updated on every Set, Delete,
// Destroy at call-time.
// All other external sessions managers out there work different than Iris one as far as I know,
// you may find them more suited to your application, it depends.
type Database interface {
Load(sid string) RemoteStore
Sync(p SyncPayload)
}
// New Idea, it should work faster for the most databases needs
// the only minus is that the databases is coupled with this package, they
// should import the kataras/iris/sessions package, but we don't use any
// database by-default so that's ok here.
// Action reports the specific action that the memory store
// sends to the database.
type Action uint32
const (
// ActionCreate occurs when add a key-value pair
// on the database session entry for the first time.
ActionCreate Action = iota
// ActionInsert occurs when add a key-value pair
// on the database session entry.
ActionInsert
// ActionUpdate occurs when modify an existing key-value pair
// on the database session entry.
ActionUpdate
// ActionDelete occurs when delete a specific value from
// a specific key from the database session entry.
ActionDelete
// ActionClear occurs when clear all values but keep the database session entry.
ActionClear
// ActionDestroy occurs when destroy,
// destroy is the action when clear all and remove the session entry from the database.
ActionDestroy
)
// SyncPayload reports the state of the session inside a database sync action.
type SyncPayload struct {
SessionID string
Action Action
// on insert it contains the new key and the value
// on update it contains the existing key and the new value
// on delete it contains the key (the value is nil)
// on clear it contains nothing (empty key, value is nil)
// on destroy it contains nothing (empty key, value is nil)
Value memstore.Entry
// Store contains the whole memory store, this store
// contains the current, updated from memory calls,
// session data (keys and values). This way
// the database has access to the whole session's data
// every time.
Store RemoteStore
}
var spPool = sync.Pool{New: func() interface{} { return SyncPayload{} }}
func acquireSyncPayload(session *Session, action Action) SyncPayload {
p := spPool.Get().(SyncPayload)
p.SessionID = session.sid
// clone the life time, except the timer.
// lifetime := LifeTime{
// Time: session.lifetime.Time,
// OriginalDuration: session.lifetime.OriginalDuration,
// }
// lifetime := acquireLifetime(session.lifetime.OriginalDuration, nil)
p.Store = RemoteStore{
Values: session.values,
Lifetime: session.lifetime,
}
p.Action = action
return p
}
func releaseSyncPayload(p SyncPayload) {
p.Value.Key = ""
p.Value.ValueRaw = nil
// releaseLifetime(p.Store.Lifetime)
spPool.Put(p)
}
func syncDatabases(databases []Database, payload SyncPayload) {
for i, n := 0, len(databases); i < n; i++ {
databases[i].Sync(payload)
}
releaseSyncPayload(payload)
}
// RemoteStore is a helper which is a wrapper
// for the store, it can be used as the session "table" which will be
// saved to the session database.
type RemoteStore struct {
// Values contains the whole memory store, this store
// contains the current, updated from memory calls,
// session data (keys and values). This way
// the database has access to the whole session's data
// every time.
Values memstore.Store
// on insert it contains the expiration datetime
// on update it contains the new expiration datetime(if updated or the old one)
// on delete it will be zero
// on clear it will be zero
// on destroy it will be zero
Lifetime LifeTime
}
// Serialize returns the byte representation of this RemoteStore.
func (s RemoteStore) Serialize() ([]byte, error) {
w := new(bytes.Buffer)
err := encode(s, w)
return w.Bytes(), err
}
// encode accepts a store and writes
// as series of bytes to the "w" writer.
func encode(s RemoteStore, w io.Writer) error {
enc := gob.NewEncoder(w)
err := enc.Encode(s)
return err
}
// DecodeRemoteStore accepts a series of bytes and returns
// the store.
func DecodeRemoteStore(b []byte) (store RemoteStore, err error) {
dec := gob.NewDecoder(bytes.NewBuffer(b))
err = dec.Decode(&store)
return
}