resource.go 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. package resource
  2. import (
  3. "crypto/md5"
  4. "encoding/hex"
  5. "fmt"
  6. "net/http"
  7. "time"
  8. )
  9. type OneResource struct {
  10. Path string
  11. Ctype string
  12. Bytes []byte
  13. MTime int64
  14. }
  15. type Resource struct {
  16. list map[string]OneResource
  17. }
  18. func New() *Resource {
  19. r := Resource{}
  20. r.list = map[string]OneResource{}
  21. return &r
  22. }
  23. func etag(str string) string {
  24. hasher := md5.New()
  25. hasher.Write([]byte(str))
  26. return hex.EncodeToString(hasher.Sum(nil))
  27. }
  28. func modified(p string, s int, v int64, w http.ResponseWriter, r *http.Request) bool {
  29. w.Header().Set("Cache-Control", "no-cache")
  30. // Set: ETag
  31. ehash := etag(fmt.Sprintf("%s-%d-%d", p, s, v))
  32. w.Header().Set("ETag", fmt.Sprintf("%s", ehash))
  33. // Set: Last-Modified
  34. w.Header().Set(
  35. "Last-Modified",
  36. time.Unix(v, 0).In(time.FixedZone("GMT", 0)).Format("Wed, 01 Oct 2006 15:04:05 GMT"),
  37. )
  38. // Check: ETag
  39. if cc := r.Header.Get("Cache-Control"); cc != "no-cache" {
  40. if inm := r.Header.Get("If-None-Match"); inm == ehash {
  41. w.WriteHeader(http.StatusNotModified)
  42. return false
  43. }
  44. }
  45. // Check: Last-Modified
  46. if cc := r.Header.Get("Cache-Control"); cc != "no-cache" {
  47. if ims := r.Header.Get("If-Modified-Since"); ims != "" {
  48. if t, err := time.Parse("Wed, 01 Oct 2006 15:04:05 GMT", ims); err == nil {
  49. if time.Unix(v, 0).In(time.FixedZone("GMT", 0)).Unix() <= t.In(time.FixedZone("GMT", 0)).Unix() {
  50. w.WriteHeader(http.StatusNotModified)
  51. return false
  52. }
  53. }
  54. }
  55. }
  56. return true
  57. }
  58. func (this *Resource) Add(path string, ctype string, bytes []byte, mtime int64) {
  59. // Do not add if already in resources list
  60. if _, ok := this.list[path]; ok == true {
  61. return
  62. }
  63. // Add to resources list
  64. this.list[path] = OneResource{
  65. Path: path,
  66. Ctype: ctype,
  67. Bytes: bytes,
  68. MTime: mtime,
  69. }
  70. }
  71. func (this *Resource) Response(w http.ResponseWriter, r *http.Request, before func(w http.ResponseWriter, r *http.Request, i *OneResource), after func(w http.ResponseWriter, r *http.Request, i *OneResource)) bool {
  72. // Do not process if this is not necessary
  73. if len(r.URL.Path) <= 1 {
  74. return false
  75. }
  76. // Check for resource
  77. res, ok := this.list[r.URL.Path[1:]]
  78. if ok == false {
  79. return false
  80. }
  81. // Cache headers
  82. w.Header().Set("Content-Type", res.Ctype)
  83. if !modified(r.URL.Path, len(res.Bytes), res.MTime, w, r) {
  84. return true
  85. }
  86. // Call `before` callback
  87. if before != nil {
  88. before(w, r, &res)
  89. }
  90. // Send resource
  91. w.Write(res.Bytes)
  92. // Call `after` callback
  93. if after != nil {
  94. after(w, r, &res)
  95. }
  96. return true
  97. }