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