swift.go 3.1 KB

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