a simple url shortener in Go (check it out at qurl.org)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

103 lines
2.3 KiB

  1. package static
  2. import (
  3. "bytes"
  4. "compress/gzip"
  5. "crypto/md5"
  6. "fmt"
  7. "mime"
  8. "net/http"
  9. "path"
  10. "strings"
  11. )
  12. type StaticContent struct {
  13. Content string
  14. ContentType string
  15. ETag string
  16. GZIPContent []byte
  17. GZIPETag string
  18. DisableGZIP bool
  19. }
  20. func (ctx *StaticContent) Init() error {
  21. // Do we have a content type? If not, initialize it
  22. if ctx.ContentType == "" {
  23. ext := path.Ext(ctx.Content)
  24. ctx.ContentType = mime.TypeByExtension(ext)
  25. // Fallback to default mime type
  26. if ctx.ContentType == "" {
  27. ctx.ContentType = "application/octet-stream"
  28. }
  29. }
  30. // Do we have an etag? If not, generate one
  31. if ctx.ETag == "" {
  32. ctx.ETag = fmt.Sprintf("%x", md5.Sum(Assets[ctx.Content]))
  33. }
  34. // If gzip is allowed and we have no gzip etag, generate
  35. // gzip content and etag
  36. if !ctx.DisableGZIP && ctx.GZIPETag == "" {
  37. var buf bytes.Buffer
  38. gz, _ := gzip.NewWriterLevel(&buf, gzip.BestCompression)
  39. defer gz.Close()
  40. if _, err := gz.Write(Assets[ctx.Content]); err != nil {
  41. return err
  42. }
  43. if err := gz.Flush(); err != nil {
  44. return err
  45. }
  46. // Check if GZIP actually resulted in a smaller file
  47. if buf.Len() < len(Assets[ctx.Content]) {
  48. ctx.GZIPContent = buf.Bytes()
  49. ctx.GZIPETag = fmt.Sprintf("%x", md5.Sum(Assets[ctx.Content]))
  50. } else {
  51. // If gzip turns out to be ineffective, disable it
  52. ctx.DisableGZIP = true
  53. }
  54. }
  55. return nil
  56. }
  57. func (ctx *StaticContent) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  58. // By default, don't use gzip
  59. useGZIP := false
  60. // If gzip isn't explicitly disabled, test for it
  61. if !ctx.DisableGZIP {
  62. useGZIP = strings.Contains(r.Header.Get("Accept-Encoding"), "gzip")
  63. }
  64. // Use the correct etag
  65. var localETag string
  66. if useGZIP {
  67. localETag = ctx.GZIPETag
  68. } else {
  69. localETag = ctx.ETag
  70. }
  71. // Check the etag, maybe we don't need to send content
  72. remoteETag := r.Header.Get("If-None-Match")
  73. if localETag == remoteETag {
  74. w.WriteHeader(http.StatusNotModified)
  75. return
  76. }
  77. w.Header().Set("ETag", localETag)
  78. w.Header().Set("Content-Type", ctx.ContentType)
  79. // Finally, write our content
  80. if useGZIP {
  81. w.Header().Set("Content-Encoding", "gzip")
  82. w.Header().Set("Content-Length", fmt.Sprintf("%d", len(ctx.GZIPContent)))
  83. w.Write(ctx.GZIPContent)
  84. } else {
  85. w.Header().Set("Content-Length", fmt.Sprintf("%d", len(Assets[ctx.Content])))
  86. w.Write(Assets[ctx.Content])
  87. }
  88. }