swift.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. package swift
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "strings"
  9. "time"
  10. "github.com/ncw/swift/v2"
  11. "github.com/imgproxy/imgproxy/v3/config"
  12. "github.com/imgproxy/imgproxy/v3/ctxreader"
  13. )
  14. type transport struct {
  15. con *swift.Connection
  16. }
  17. func New() (http.RoundTripper, error) {
  18. c := &swift.Connection{
  19. UserName: config.SwiftUsername,
  20. ApiKey: config.SwiftAPIKey,
  21. AuthUrl: config.SwiftAuthURL,
  22. AuthVersion: config.SwiftAuthVersion,
  23. Domain: config.SwiftDomain, // v3 auth only
  24. Tenant: config.SwiftTenant, // v2 auth only
  25. Timeout: time.Duration(config.SwiftTimeoutSeconds) * time.Second,
  26. ConnectTimeout: time.Duration(config.SwiftConnectTimeoutSeconds) * time.Second,
  27. }
  28. ctx := context.Background()
  29. err := c.Authenticate(ctx)
  30. if err != nil {
  31. return nil, fmt.Errorf("swift authentication error: %s", err)
  32. }
  33. return transport{con: c}, nil
  34. }
  35. func (t transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
  36. // Users should have converted the object storage URL in the format of swift://{container}/{object}
  37. container := req.URL.Host
  38. objectName := strings.TrimPrefix(req.URL.Path, "/")
  39. reqHeaders := make(swift.Headers)
  40. if r := req.Header.Get("Range"); len(r) > 0 {
  41. reqHeaders["Range"] = r
  42. }
  43. object, objectHeaders, err := t.con.ObjectOpen(req.Context(), container, objectName, false, reqHeaders)
  44. header := make(http.Header)
  45. if err != nil {
  46. if errors.Is(err, swift.ObjectNotFound) || errors.Is(err, swift.ContainerNotFound) {
  47. return &http.Response{
  48. StatusCode: http.StatusNotFound,
  49. Proto: "HTTP/1.0",
  50. ProtoMajor: 1,
  51. ProtoMinor: 0,
  52. Header: header,
  53. Body: io.NopCloser(strings.NewReader(err.Error())),
  54. Close: false,
  55. Request: req,
  56. }, nil
  57. }
  58. return nil, fmt.Errorf("error opening object: %v", err)
  59. }
  60. if config.ETagEnabled {
  61. if etag, ok := objectHeaders["Etag"]; ok {
  62. header.Set("ETag", etag)
  63. if len(etag) > 0 && etag == req.Header.Get("If-None-Match") {
  64. object.Close()
  65. return &http.Response{
  66. StatusCode: http.StatusNotModified,
  67. Proto: "HTTP/1.0",
  68. ProtoMajor: 1,
  69. ProtoMinor: 0,
  70. Header: header,
  71. ContentLength: 0,
  72. Body: nil,
  73. Close: false,
  74. Request: req,
  75. }, nil
  76. }
  77. }
  78. }
  79. for k, v := range objectHeaders {
  80. header.Set(k, v)
  81. }
  82. return &http.Response{
  83. Status: "200 OK",
  84. StatusCode: 200,
  85. Proto: "HTTP/1.0",
  86. ProtoMajor: 1,
  87. ProtoMinor: 0,
  88. Header: header,
  89. Body: ctxreader.New(req.Context(), object, true),
  90. Close: true,
  91. Request: req,
  92. }, nil
  93. }