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.

104 lines
2.5 KiB

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