session.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. package session
  2. import (
  3. "crypto/sha1"
  4. "encoding/json"
  5. "fmt"
  6. "math/rand"
  7. "net/http"
  8. "os"
  9. "strings"
  10. "sync"
  11. "time"
  12. )
  13. // vars in memory storage for session variables
  14. type vars struct {
  15. sync.RWMutex
  16. Bool map[string]bool
  17. Int map[string]int
  18. Int64 map[string]int64
  19. String map[string]string
  20. }
  21. // Session session for managing session variables
  22. type Session struct {
  23. w http.ResponseWriter
  24. r *http.Request
  25. tmpdir string
  26. varlist *vars
  27. changed bool
  28. hash string
  29. }
  30. // New to create new or load saved session,
  31. // returns error if can't load saved session
  32. func New(w http.ResponseWriter, r *http.Request, tmpdir string) (*Session, error) {
  33. s := Session{
  34. w: w,
  35. r: r,
  36. tmpdir: tmpdir,
  37. varlist: &vars{
  38. Bool: map[string]bool{},
  39. Int: map[string]int{},
  40. Int64: map[string]int64{},
  41. String: map[string]string{},
  42. },
  43. changed: false,
  44. hash: "",
  45. }
  46. s.varlist.Lock()
  47. defer s.varlist.Unlock()
  48. cookie, err := r.Cookie("session")
  49. if err == nil && len(cookie.Value) == 40 {
  50. // Load from file
  51. s.hash = cookie.Value
  52. fname := strings.Join([]string{s.tmpdir, s.hash}, string(os.PathSeparator))
  53. info, err := os.Stat(fname)
  54. if err != nil {
  55. return &s, err
  56. }
  57. var f *os.File
  58. f, err = os.Open(fname)
  59. if err != nil {
  60. return &s, err
  61. }
  62. defer f.Close()
  63. dec := json.NewDecoder(f)
  64. err = dec.Decode(&s.varlist)
  65. if err != nil {
  66. return &s, err
  67. }
  68. // Update file last modify time
  69. if time.Since(info.ModTime()) > 30*time.Minute {
  70. if err := os.Chtimes(fname, time.Now(), time.Now()); err != nil {
  71. return &s, err
  72. }
  73. }
  74. } else {
  75. // Create new
  76. rand.Seed(time.Now().Unix())
  77. // Real remote IP for proxy servers
  78. rRemoteAddr := r.RemoteAddr
  79. if r.Header.Get("X-Real-IP") != "" && len(r.Header.Get("X-Real-IP")) <= 25 {
  80. rRemoteAddr = rRemoteAddr + ", " + strings.TrimSpace(r.Header.Get("X-Real-IP"))
  81. } else if r.Header.Get("X-Forwarded-For") != "" && len(r.Header.Get("X-Forwarded-For")) <= 25 {
  82. rRemoteAddr = rRemoteAddr + ", " + strings.TrimSpace(r.Header.Get("X-Forwarded-For"))
  83. }
  84. sign := rRemoteAddr + r.Header.Get("User-Agent") + fmt.Sprintf("%d", int64(time.Now().Unix())) + fmt.Sprintf("%d", int64(rand.Intn(9999999-99)+99))
  85. s.hash = fmt.Sprintf("%x", sha1.Sum([]byte(sign)))
  86. http.SetCookie(w, &http.Cookie{
  87. Name: "session",
  88. Value: s.hash,
  89. Path: "/",
  90. Expires: time.Now().Add(7 * 24 * time.Hour),
  91. HttpOnly: true,
  92. })
  93. }
  94. return &s, nil
  95. }
  96. // Close to close session and save data to local file
  97. func (s *Session) Close() bool {
  98. if !s.changed {
  99. return false
  100. }
  101. s.varlist.Lock()
  102. defer s.varlist.Unlock()
  103. r, err := json.Marshal(s.varlist)
  104. if err == nil {
  105. f, err := os.Create(strings.Join([]string{s.tmpdir, s.hash}, string(os.PathSeparator)))
  106. if err == nil {
  107. defer f.Close()
  108. _, err = f.Write(r)
  109. if err == nil {
  110. s.changed = false
  111. return true
  112. }
  113. }
  114. }
  115. return false
  116. }
  117. // Destroy to remove session local file
  118. func (s *Session) Destroy() error {
  119. if s.tmpdir == "" || s.hash == "" {
  120. return nil
  121. }
  122. err := os.Remove(strings.Join([]string{s.tmpdir, s.hash}, string(os.PathSeparator)))
  123. if err == nil {
  124. s.changed = false
  125. }
  126. return err
  127. }