redistore.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. package sessions
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "net/http"
  7. "time"
  8. "github.com/go-redis/redis"
  9. "github.com/gorilla/securecookie"
  10. gsessions "github.com/gorilla/sessions"
  11. "github.com/teris-io/shortid"
  12. )
  13. // RediStore stores sessions in a redis backend.
  14. type RediStore struct {
  15. RedisClient *redis.Client
  16. Codecs []securecookie.Codec
  17. SessionOptions *gsessions.Options // default configuration
  18. DefaultMaxAge int // default Redis TTL for a MaxAge == 0 session
  19. maxLength int
  20. keyPrefix string
  21. serializer SessionSerializer
  22. }
  23. func NewRediStore(client *redis.Client, keyPrefix string, keyPairs ...[]byte) *RediStore {
  24. if keyPrefix == "" {
  25. keyPrefix = "session_"
  26. }
  27. rs := &RediStore{
  28. RedisClient: client,
  29. Codecs: securecookie.CodecsFromPairs(keyPairs...),
  30. SessionOptions: &gsessions.Options{
  31. Path: defaultPath,
  32. MaxAge: defaultMaxAge,
  33. },
  34. DefaultMaxAge: 60 * 20, // 20 minutes seems like a reasonable default
  35. maxLength: 4096,
  36. keyPrefix: keyPrefix,
  37. serializer: JSONSerializer{},
  38. }
  39. return rs
  40. }
  41. // SessionSerializer provides an interface hook for alternative serializers
  42. type SessionSerializer interface {
  43. Deserialize(d []byte, ss *gsessions.Session) error
  44. Serialize(ss *gsessions.Session) ([]byte, error)
  45. }
  46. // JSONSerializer encode the session map to JSON.
  47. type JSONSerializer struct{}
  48. // Serialize to JSON. Will err if there are unmarshalable key values
  49. func (s JSONSerializer) Serialize(ss *gsessions.Session) ([]byte, error) {
  50. m := make(map[string]interface{}, len(ss.Values))
  51. for k, v := range ss.Values {
  52. ks, ok := k.(string)
  53. if !ok {
  54. err := fmt.Errorf("Non-string key value, cannot serialize session to JSON: %v", k)
  55. fmt.Printf("redistore.JSONSerializer.serialize() Error: %v", err)
  56. return nil, err
  57. }
  58. m[ks] = v
  59. }
  60. return json.Marshal(m)
  61. }
  62. // Deserialize back to map[string]interface{}
  63. func (s JSONSerializer) Deserialize(d []byte, ss *gsessions.Session) error {
  64. m := make(map[string]interface{})
  65. err := json.Unmarshal(d, &m)
  66. if err != nil {
  67. fmt.Printf("redistore.JSONSerializer.deserialize() Error: %v", err)
  68. return err
  69. }
  70. for k, v := range m {
  71. ss.Values[k] = v
  72. }
  73. return nil
  74. }
  75. // SetMaxLength sets RediStore.maxLength if the `l` argument is greater or equal 0
  76. // maxLength restricts the maximum length of new sessions to l.
  77. // If l is 0 there is no limit to the size of a session, use with caution.
  78. // The default for a new RediStore is 4096. Redis allows for max.
  79. // value sizes of up to 512MB (http://redis.io/topics/data-types)
  80. // Default: 4096,
  81. func (s *RediStore) SetMaxLength(l int) {
  82. if l >= 0 {
  83. s.maxLength = l
  84. }
  85. }
  86. func (s *RediStore) Options(options Options) {
  87. s.SessionOptions = &gsessions.Options{
  88. Path: options.Path,
  89. Domain: options.Domain,
  90. MaxAge: options.MaxAge,
  91. Secure: options.Secure,
  92. HttpOnly: options.HttpOnly,
  93. }
  94. }
  95. // SetSerializer sets the serializer
  96. func (s *RediStore) SetSerializer(ss SessionSerializer) {
  97. s.serializer = ss
  98. }
  99. // SetMaxAge restricts the maximum age, in seconds, of the session record
  100. // both in database and a browser. This is to change session storage configuration.
  101. // If you want just to remove session use your session `s` object and change it's
  102. // `Options.MaxAge` to -1, as specified in
  103. // http://godoc.org/github.com/gorilla/sessions#Options
  104. //
  105. // Default is the one provided by this package value - `sessionExpire`.
  106. // Set it to 0 for no restriction.
  107. // Because we use `MaxAge` also in SecureCookie crypting algorithm you should
  108. // use this function to change `MaxAge` value.
  109. func (s *RediStore) SetMaxAge(v int) {
  110. var c *securecookie.SecureCookie
  111. var ok bool
  112. s.SessionOptions.MaxAge = v
  113. for i := range s.Codecs {
  114. if c, ok = s.Codecs[i].(*securecookie.SecureCookie); ok {
  115. c.MaxAge(v)
  116. } else {
  117. fmt.Printf("Can't change MaxAge on codec %v\n", s.Codecs[i])
  118. }
  119. }
  120. }
  121. // Get returns a session for the given name after adding it to the registry.
  122. //
  123. // See gorilla/sessions FilesystemStore.Get().
  124. func (s *RediStore) Get(r *http.Request, name string) (*gsessions.Session, error) {
  125. return gsessions.GetRegistry(r).Get(s, name)
  126. }
  127. // New returns a session for the given name without adding it to the registry.
  128. //
  129. // See gorilla/sessions FilesystemStore.New().
  130. func (s *RediStore) New(r *http.Request, name string) (*gsessions.Session, error) {
  131. var (
  132. err error
  133. ok bool
  134. )
  135. session := gsessions.NewSession(s, name)
  136. // make a copy
  137. options := *s.SessionOptions
  138. session.Options = &options
  139. session.IsNew = true
  140. if c, errCookie := r.Cookie(name); errCookie == nil {
  141. session.ID = c.Value
  142. ok, err = s.load(session)
  143. session.IsNew = !(err == nil && ok)
  144. } else {
  145. session.ID = shortid.MustGenerate()
  146. }
  147. return session, err
  148. }
  149. func (s *RediStore) RenewID(r *http.Request, w http.ResponseWriter, session *gsessions.Session) error {
  150. _id := session.ID
  151. data, err := s.RedisClient.Get(s.keyPrefix + _id).Result()
  152. if err != nil {
  153. return err
  154. }
  155. session.ID = shortid.MustGenerate()
  156. age := session.Options.MaxAge
  157. if age == 0 {
  158. age = s.DefaultMaxAge
  159. }
  160. _, err = s.RedisClient.Set(s.keyPrefix+session.ID, data, time.Duration(age)*time.Second).Result()
  161. if err != nil {
  162. return err
  163. }
  164. http.SetCookie(w, gsessions.NewCookie(session.Name(), session.ID, session.Options))
  165. return nil
  166. }
  167. // Save adds a single session to the response.
  168. func (s *RediStore) Save(r *http.Request, w http.ResponseWriter, session *gsessions.Session) error {
  169. // Marked for deletion.
  170. if session.Options.MaxAge < 0 || len(session.Values) == 0 {
  171. if err := s.delete(session); err != nil {
  172. return err
  173. }
  174. return nil
  175. } else {
  176. // Build an alphanumeric key for the redis store.
  177. if session.ID == "" {
  178. session.ID = shortid.MustGenerate()
  179. }
  180. if err := s.save(session); err != nil {
  181. return err
  182. }
  183. }
  184. return nil
  185. }
  186. // save stores the session in redis.
  187. func (s *RediStore) save(session *gsessions.Session) error {
  188. b, err := s.serializer.Serialize(session)
  189. if err != nil {
  190. return err
  191. }
  192. if s.maxLength != 0 && len(b) > s.maxLength {
  193. return errors.New("SessionStore: the value to store is too big")
  194. }
  195. age := session.Options.MaxAge
  196. if age == 0 {
  197. age = s.DefaultMaxAge
  198. }
  199. _, err = s.RedisClient.Set(s.keyPrefix+session.ID, b, time.Duration(age)*time.Second).Result()
  200. return err
  201. }
  202. // load reads the session from redis.
  203. // returns true if there is a sessoin data in DB
  204. func (s *RediStore) load(session *gsessions.Session) (bool, error) {
  205. data, err := s.RedisClient.Get(s.keyPrefix + session.ID).Result()
  206. if err != nil {
  207. return false, err
  208. }
  209. if data == "" {
  210. return false, nil // no data was associated with this key
  211. }
  212. return true, s.serializer.Deserialize([]byte(data), session)
  213. }
  214. // delete removes keys from redis if MaxAge<0
  215. func (s *RediStore) delete(session *gsessions.Session) error {
  216. _, err := s.RedisClient.Del(s.keyPrefix + session.ID).Result()
  217. return err
  218. }