gcs.go 2.1 KB

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