1
0

swift.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. package swift
  2. import (
  3. "context"
  4. "errors"
  5. "io"
  6. "net/http"
  7. "strings"
  8. "github.com/ncw/swift/v2"
  9. "github.com/imgproxy/imgproxy/v3/config"
  10. "github.com/imgproxy/imgproxy/v3/fetcher/transport/common"
  11. "github.com/imgproxy/imgproxy/v3/fetcher/transport/notmodified"
  12. "github.com/imgproxy/imgproxy/v3/ierrors"
  13. )
  14. type transport struct {
  15. con *swift.Connection
  16. }
  17. func New(config *Config, trans *http.Transport) (http.RoundTripper, error) {
  18. if err := config.Validate(); err != nil {
  19. return nil, err
  20. }
  21. c := &swift.Connection{
  22. UserName: config.Username,
  23. ApiKey: config.APIKey,
  24. AuthUrl: config.AuthURL,
  25. AuthVersion: config.AuthVersion,
  26. Domain: config.Domain, // v3 auth only
  27. Tenant: config.Tenant, // v2 auth only
  28. Timeout: config.Timeout,
  29. ConnectTimeout: config.ConnectTimeout,
  30. Transport: trans,
  31. }
  32. ctx := context.Background()
  33. err := c.Authenticate(ctx)
  34. if err != nil {
  35. return nil, ierrors.Wrap(err, 0, ierrors.WithPrefix("swift authentication error"))
  36. }
  37. return transport{con: c}, nil
  38. }
  39. func (t transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
  40. container, objectName, _ := common.GetBucketAndKey(req.URL)
  41. if len(container) == 0 || len(objectName) == 0 {
  42. body := strings.NewReader("Invalid Swift URL: container name or object name is empty")
  43. return &http.Response{
  44. StatusCode: http.StatusNotFound,
  45. Proto: "HTTP/1.0",
  46. ProtoMajor: 1,
  47. ProtoMinor: 0,
  48. Header: http.Header{"Content-Type": {"text/plain"}},
  49. ContentLength: int64(body.Len()),
  50. Body: io.NopCloser(body),
  51. Close: false,
  52. Request: req,
  53. }, nil
  54. }
  55. reqHeaders := make(swift.Headers)
  56. if r := req.Header.Get("Range"); len(r) > 0 {
  57. reqHeaders["Range"] = r
  58. }
  59. object, objectHeaders, err := t.con.ObjectOpen(req.Context(), container, objectName, false, reqHeaders)
  60. header := make(http.Header)
  61. if err != nil {
  62. if errors.Is(err, swift.ObjectNotFound) || errors.Is(err, swift.ContainerNotFound) {
  63. return &http.Response{
  64. StatusCode: http.StatusNotFound,
  65. Proto: "HTTP/1.0",
  66. ProtoMajor: 1,
  67. ProtoMinor: 0,
  68. Header: http.Header{"Content-Type": {"text/plain"}},
  69. ContentLength: int64(len(err.Error())),
  70. Body: io.NopCloser(strings.NewReader(err.Error())),
  71. Close: false,
  72. Request: req,
  73. }, nil
  74. }
  75. return nil, ierrors.Wrap(err, 0, ierrors.WithPrefix("error opening object"))
  76. }
  77. if config.ETagEnabled {
  78. if etag, ok := objectHeaders["Etag"]; ok {
  79. header.Set("ETag", etag)
  80. }
  81. }
  82. if config.LastModifiedEnabled {
  83. if lastModified, ok := objectHeaders["Last-Modified"]; ok {
  84. header.Set("Last-Modified", lastModified)
  85. }
  86. }
  87. if resp := notmodified.Response(req, header); resp != nil {
  88. object.Close()
  89. return resp, nil
  90. }
  91. for k, v := range objectHeaders {
  92. header.Set(k, v)
  93. }
  94. return &http.Response{
  95. Status: "200 OK",
  96. StatusCode: 200,
  97. Proto: "HTTP/1.0",
  98. ProtoMajor: 1,
  99. ProtoMinor: 0,
  100. Header: header,
  101. Body: object,
  102. Close: true,
  103. Request: req,
  104. }, nil
  105. }