swift.go 2.5 KB

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