store.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. package captcha
  2. import (
  3. "container/list"
  4. "sync"
  5. "time"
  6. )
  7. // An object implementing Store interface can be registered with SetCustomStore
  8. // function to handle storage and retrieval of captcha ids and solutions for
  9. // them, replacing the default memory store.
  10. type Store interface {
  11. // Set sets the digits for the captcha id.
  12. Set(id string, digits []byte)
  13. // Get returns stored digits for the captcha id. Clear indicates
  14. // whether the captcha must be deleted from the store.
  15. Get(id string, clear bool) (digits []byte)
  16. // Collect deletes expired captchas from the store. For custom stores
  17. // this method is not called automatically, it is only wired to the
  18. // package's Collect function. Custom stores must implement their own
  19. // procedure for calling Collect, for example, in Set method.
  20. Collect()
  21. }
  22. // expValue stores timestamp and id of captchas. It is used in the list inside
  23. // memoryStore for indexing generated captchas by timestamp to enable garbage
  24. // collection of expired captchas.
  25. type idByTimeValue struct {
  26. timestamp int64
  27. id string
  28. }
  29. // memoryStore is an internal store for captcha ids and their values.
  30. type memoryStore struct {
  31. mu sync.RWMutex
  32. digitsById map[string][]byte
  33. idByTime *list.List
  34. // Number of items stored since last collection.
  35. numStored int
  36. // Number of saved items that triggers collection.
  37. collectNum int
  38. // Expiration time of captchas.
  39. expiration int64
  40. }
  41. // NewMemoryStore returns a new standard memory store for captchas with the
  42. // given collection threshold and expiration time in seconds. The returned
  43. // store must be registered with SetCustomStore to replace the default one.
  44. func NewMemoryStore(collectNum int, expiration int64) Store {
  45. s := new(memoryStore)
  46. s.digitsById = make(map[string][]byte)
  47. s.idByTime = list.New()
  48. s.collectNum = collectNum
  49. s.expiration = expiration
  50. return s
  51. }
  52. func (s *memoryStore) Set(id string, digits []byte) {
  53. s.mu.Lock()
  54. s.digitsById[id] = digits
  55. s.idByTime.PushBack(idByTimeValue{time.Seconds(), id})
  56. s.numStored++
  57. s.mu.Unlock()
  58. if s.numStored > s.collectNum {
  59. go s.Collect()
  60. }
  61. }
  62. func (s *memoryStore) Get(id string, clear bool) (digits []byte) {
  63. if !clear {
  64. // When we don't need to clear captcha, acquire read lock.
  65. s.mu.RLock()
  66. defer s.mu.RUnlock()
  67. } else {
  68. s.mu.Lock()
  69. defer s.mu.Unlock()
  70. }
  71. digits, ok := s.digitsById[id]
  72. if !ok {
  73. return
  74. }
  75. if clear {
  76. s.digitsById[id] = nil, false
  77. // XXX(dchest) Index (s.idByTime) will be cleaned when
  78. // collecting expired captchas. Can't clean it here, because
  79. // we don't store reference to expValue in the map.
  80. // Maybe store it?
  81. }
  82. return
  83. }
  84. func (s *memoryStore) Collect() {
  85. now := time.Seconds()
  86. s.mu.Lock()
  87. defer s.mu.Unlock()
  88. s.numStored = 0
  89. for e := s.idByTime.Front(); e != nil; {
  90. ev, ok := e.Value.(idByTimeValue)
  91. if !ok {
  92. return
  93. }
  94. if ev.timestamp+s.expiration < now {
  95. s.digitsById[ev.id] = nil, false
  96. next := e.Next()
  97. s.idByTime.Remove(e)
  98. e = next
  99. } else {
  100. return
  101. }
  102. }
  103. }