gcs.go 1.8 KB

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