gcs.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. package gcs
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "net/http"
  7. "strconv"
  8. "strings"
  9. "cloud.google.com/go/storage"
  10. "google.golang.org/api/option"
  11. "github.com/imgproxy/imgproxy/v3/config"
  12. )
  13. // For tests
  14. var noAuth bool = false
  15. type transport struct {
  16. client *storage.Client
  17. }
  18. func New() (http.RoundTripper, error) {
  19. var (
  20. client *storage.Client
  21. err error
  22. )
  23. opts := []option.ClientOption{}
  24. if len(config.GCSKey) > 0 {
  25. opts = append(opts, option.WithCredentialsJSON([]byte(config.GCSKey)))
  26. }
  27. if len(config.GCSEndpoint) > 0 {
  28. opts = append(opts, option.WithEndpoint(config.GCSEndpoint))
  29. }
  30. if noAuth {
  31. opts = append(opts, option.WithoutAuthentication())
  32. }
  33. client, err = storage.NewClient(context.Background(), opts...)
  34. if err != nil {
  35. return nil, fmt.Errorf("Can't create GCS client: %s", err)
  36. }
  37. return transport{client}, nil
  38. }
  39. func (t transport) RoundTrip(req *http.Request) (*http.Response, error) {
  40. bkt := t.client.Bucket(req.URL.Host)
  41. obj := bkt.Object(strings.TrimPrefix(req.URL.Path, "/"))
  42. if g, err := strconv.ParseInt(req.URL.RawQuery, 10, 64); err == nil && g > 0 {
  43. obj = obj.Generation(g)
  44. }
  45. header := make(http.Header)
  46. if config.ETagEnabled {
  47. attrs, err := obj.Attrs(req.Context())
  48. if err != nil {
  49. return handleError(req, err)
  50. }
  51. header.Set("ETag", attrs.Etag)
  52. if etag := req.Header.Get("If-None-Match"); len(etag) > 0 && attrs.Etag == etag {
  53. return &http.Response{
  54. StatusCode: http.StatusNotModified,
  55. Proto: "HTTP/1.0",
  56. ProtoMajor: 1,
  57. ProtoMinor: 0,
  58. Header: header,
  59. ContentLength: 0,
  60. Body: nil,
  61. Close: false,
  62. Request: req,
  63. }, nil
  64. }
  65. }
  66. reader, err := obj.NewReader(req.Context())
  67. if err != nil {
  68. return handleError(req, err)
  69. }
  70. header.Set("Cache-Control", reader.Attrs.CacheControl)
  71. return &http.Response{
  72. Status: "200 OK",
  73. StatusCode: 200,
  74. Proto: "HTTP/1.0",
  75. ProtoMajor: 1,
  76. ProtoMinor: 0,
  77. Header: header,
  78. ContentLength: reader.Attrs.Size,
  79. Body: reader,
  80. Close: true,
  81. Request: req,
  82. }, nil
  83. }
  84. func handleError(req *http.Request, err error) (*http.Response, error) {
  85. if err != storage.ErrBucketNotExist && err != storage.ErrObjectNotExist {
  86. return nil, err
  87. }
  88. return &http.Response{
  89. StatusCode: http.StatusNotFound,
  90. Proto: "HTTP/1.0",
  91. ProtoMajor: 1,
  92. ProtoMinor: 0,
  93. Header: make(http.Header),
  94. ContentLength: int64(len(err.Error())),
  95. Body: io.NopCloser(strings.NewReader(err.Error())),
  96. Close: false,
  97. Request: req,
  98. }, nil
  99. }