fs.go 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. package fs
  2. import (
  3. "crypto/md5"
  4. "encoding/base64"
  5. "fmt"
  6. "io"
  7. "io/fs"
  8. "net/http"
  9. "os"
  10. "strings"
  11. "github.com/imgproxy/imgproxy/v3/config"
  12. )
  13. type transport struct {
  14. fs http.Dir
  15. }
  16. func New() transport {
  17. return transport{fs: http.Dir(config.LocalFileSystemRoot)}
  18. }
  19. func (t transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
  20. header := make(http.Header)
  21. f, err := t.fs.Open(req.URL.Path)
  22. if err != nil {
  23. if os.IsNotExist(err) {
  24. return &http.Response{
  25. StatusCode: http.StatusNotFound,
  26. Proto: "HTTP/1.0",
  27. ProtoMajor: 1,
  28. ProtoMinor: 0,
  29. Header: header,
  30. ContentLength: 0,
  31. Body: io.NopCloser(strings.NewReader(
  32. fmt.Sprintf("%s doesn't exist", req.URL.Path),
  33. )),
  34. Close: false,
  35. Request: req,
  36. }, nil
  37. }
  38. return nil, err
  39. }
  40. fi, err := f.Stat()
  41. if err != nil {
  42. return nil, err
  43. }
  44. if fi.IsDir() {
  45. return &http.Response{
  46. StatusCode: http.StatusNotFound,
  47. Proto: "HTTP/1.0",
  48. ProtoMajor: 1,
  49. ProtoMinor: 0,
  50. Header: header,
  51. ContentLength: 0,
  52. Body: io.NopCloser(strings.NewReader(
  53. fmt.Sprintf("%s is directory", req.URL.Path),
  54. )),
  55. Close: false,
  56. Request: req,
  57. }, nil
  58. }
  59. if config.ETagEnabled {
  60. etag := BuildEtag(req.URL.Path, fi)
  61. header.Set("ETag", etag)
  62. if etag == req.Header.Get("If-None-Match") {
  63. f.Close()
  64. return &http.Response{
  65. StatusCode: http.StatusNotModified,
  66. Proto: "HTTP/1.0",
  67. ProtoMajor: 1,
  68. ProtoMinor: 0,
  69. Header: header,
  70. ContentLength: 0,
  71. Body: nil,
  72. Close: false,
  73. Request: req,
  74. }, nil
  75. }
  76. }
  77. return &http.Response{
  78. Status: "200 OK",
  79. StatusCode: 200,
  80. Proto: "HTTP/1.0",
  81. ProtoMajor: 1,
  82. ProtoMinor: 0,
  83. Header: header,
  84. ContentLength: fi.Size(),
  85. Body: f,
  86. Close: true,
  87. Request: req,
  88. }, nil
  89. }
  90. func BuildEtag(path string, fi fs.FileInfo) string {
  91. tag := fmt.Sprintf("%s__%d__%d", path, fi.Size(), fi.ModTime().UnixNano())
  92. hash := md5.Sum([]byte(tag))
  93. return `"` + string(base64.RawURLEncoding.EncodeToString(hash[:])) + `"`
  94. }