Browse Source

IMG-49: Instance requires transport.Config (#1513)

* transport.Config

* Removed redaundant LocalEnaled
Victor Sokolov 1 month ago
parent
commit
caffdcd72b

+ 16 - 4
handlers/stream/handler_test.go

@@ -49,7 +49,10 @@ func (s *HandlerTestSuite) SetupTest() {
 	config.Reset()
 	config.AllowLoopbackSourceAddresses = true
 
-	tr, err := transport.NewTransport()
+	trc, err := transport.LoadFromEnv(transport.NewDefaultConfig())
+	s.Require().NoError(err)
+
+	tr, err := transport.New(trc)
 	s.Require().NoError(err)
 
 	fc := imagefetcher.NewDefaultConfig()
@@ -346,8 +349,11 @@ func (s *HandlerTestSuite) TestHandlerCacheControl() {
 			}))
 			defer ts.Close()
 
+			trc, err := transport.LoadFromEnv(transport.NewDefaultConfig())
+			s.Require().NoError(err)
+
 			// Create new handler with updated config for each test
-			tr, err := transport.NewTransport()
+			tr, err := transport.New(trc)
 			s.Require().NoError(err)
 
 			fc := imagefetcher.NewDefaultConfig()
@@ -440,8 +446,11 @@ func (s *HandlerTestSuite) TestHandlerErrorResponse() {
 
 // TestHandlerCookiePassthrough tests the cookie passthrough behavior of the streaming service.
 func (s *HandlerTestSuite) TestHandlerCookiePassthrough() {
+	trc, err := transport.LoadFromEnv(transport.NewDefaultConfig())
+	s.Require().NoError(err)
+
 	// Create new handler with updated config
-	tr, err := transport.NewTransport()
+	tr, err := transport.New(trc)
 	s.Require().NoError(err)
 
 	fc := imagefetcher.NewDefaultConfig()
@@ -497,8 +506,11 @@ func (s *HandlerTestSuite) TestHandlerCanonicalHeader() {
 	defer ts.Close()
 
 	for _, sc := range []bool{true, false} {
+		trc, err := transport.LoadFromEnv(transport.NewDefaultConfig())
+		s.Require().NoError(err)
+
 		// Create new handler with updated config
-		tr, err := transport.NewTransport()
+		tr, err := transport.New(trc)
 		s.Require().NoError(err)
 
 		fc := imagefetcher.NewDefaultConfig()

+ 6 - 1
imagedata/download.go

@@ -35,7 +35,12 @@ func DefaultDownloadOptions() DownloadOptions {
 }
 
 func initDownloading() error {
-	ts, err := transport.NewTransport()
+	trc, err := transport.LoadFromEnv(transport.NewDefaultConfig())
+	if err != nil {
+		return err
+	}
+
+	ts, err := transport.New(trc)
 	if err != nil {
 		return err
 	}

+ 22 - 28
transport/azure/azure.go

@@ -1,7 +1,6 @@
 package azure
 
 import (
-	"errors"
 	"fmt"
 	"io"
 	"net/http"
@@ -16,10 +15,9 @@ import (
 	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
 	"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blockblob"
 
-	"github.com/imgproxy/imgproxy/v3/config"
+	"github.com/imgproxy/imgproxy/v3/httpheaders"
 	"github.com/imgproxy/imgproxy/v3/httprange"
 	"github.com/imgproxy/imgproxy/v3/transport/common"
-	"github.com/imgproxy/imgproxy/v3/transport/generichttp"
 	"github.com/imgproxy/imgproxy/v3/transport/notmodified"
 )
 
@@ -27,7 +25,11 @@ type transport struct {
 	client *azblob.Client
 }
 
-func New() (http.RoundTripper, error) {
+func New(config *Config, trans *http.Transport) (http.RoundTripper, error) {
+	if err := config.Validate(); err != nil {
+		return nil, err
+	}
+
 	var (
 		client                 *azblob.Client
 		sharedKeyCredential    *azblob.SharedKeyCredential
@@ -35,13 +37,9 @@ func New() (http.RoundTripper, error) {
 		err                    error
 	)
 
-	if len(config.ABSName) == 0 {
-		return nil, errors.New("IMGPROXY_ABS_NAME must be set")
-	}
-
-	endpoint := config.ABSEndpoint
+	endpoint := config.Endpoint
 	if len(endpoint) == 0 {
-		endpoint = fmt.Sprintf("https://%s.blob.core.windows.net", config.ABSName)
+		endpoint = fmt.Sprintf("https://%s.blob.core.windows.net", config.Name)
 	}
 
 	endpointURL, err := url.Parse(endpoint)
@@ -49,19 +47,14 @@ func New() (http.RoundTripper, error) {
 		return nil, err
 	}
 
-	trans, err := generichttp.New(false)
-	if err != nil {
-		return nil, err
-	}
-
 	opts := azblob.ClientOptions{
 		ClientOptions: policy.ClientOptions{
 			Transport: &http.Client{Transport: trans},
 		},
 	}
 
-	if len(config.ABSKey) > 0 {
-		sharedKeyCredential, err = azblob.NewSharedKeyCredential(config.ABSName, config.ABSKey)
+	if len(config.Key) > 0 {
+		sharedKeyCredential, err = azblob.NewSharedKeyCredential(config.Name, config.Key)
 		if err != nil {
 			return nil, err
 		}
@@ -93,7 +86,7 @@ func (t transport) RoundTrip(req *http.Request) (*http.Response, error) {
 			Proto:         "HTTP/1.0",
 			ProtoMajor:    1,
 			ProtoMinor:    0,
-			Header:        http.Header{"Content-Type": {"text/plain"}},
+			Header:        http.Header{httpheaders.ContentType: {"text/plain"}},
 			ContentLength: int64(body.Len()),
 			Body:          io.NopCloser(body),
 			Close:         false,
@@ -106,7 +99,7 @@ func (t transport) RoundTrip(req *http.Request) (*http.Response, error) {
 	header := make(http.Header)
 	opts := &blob.DownloadStreamOptions{}
 
-	if r := req.Header.Get("Range"); len(r) != 0 {
+	if r := req.Header.Get(httpheaders.Range); len(r) != 0 {
 		start, end, err := httprange.Parse(r)
 		if err != nil {
 			return httprange.InvalidHTTPRangeResponse(req), nil
@@ -147,13 +140,14 @@ func (t transport) RoundTrip(req *http.Request) (*http.Response, error) {
 		}
 	}
 
-	if config.ETagEnabled && result.ETag != nil {
+	if result.ETag != nil {
 		etag := string(*result.ETag)
-		header.Set("ETag", etag)
+		header.Set(httpheaders.Etag, etag)
 	}
-	if config.LastModifiedEnabled && result.LastModified != nil {
+
+	if result.LastModified != nil {
 		lastModified := result.LastModified.Format(http.TimeFormat)
-		header.Set("Last-Modified", lastModified)
+		header.Set(httpheaders.LastModified, lastModified)
 	}
 
 	if resp := notmodified.Response(req, header); resp != nil {
@@ -163,24 +157,24 @@ func (t transport) RoundTrip(req *http.Request) (*http.Response, error) {
 		return resp, nil
 	}
 
-	header.Set("Accept-Ranges", "bytes")
+	header.Set(httpheaders.AcceptRanges, "bytes")
 
 	contentLength := int64(0)
 	if result.ContentLength != nil {
 		contentLength = *result.ContentLength
-		header.Set("Content-Length", strconv.FormatInt(*result.ContentLength, 10))
+		header.Set(httpheaders.ContentLength, strconv.FormatInt(*result.ContentLength, 10))
 	}
 
 	if result.ContentType != nil {
-		header.Set("Content-Type", *result.ContentType)
+		header.Set(httpheaders.ContentType, *result.ContentType)
 	}
 
 	if result.ContentRange != nil {
-		header.Set("Content-Range", *result.ContentRange)
+		header.Set(httpheaders.ContentRange, *result.ContentRange)
 	}
 
 	if result.CacheControl != nil {
-		header.Set("Cache-Control", *result.CacheControl)
+		header.Set(httpheaders.CacheControl, *result.CacheControl)
 	}
 
 	return &http.Response{

+ 23 - 44
transport/azure/azure_test.go

@@ -11,6 +11,8 @@ import (
 	"github.com/stretchr/testify/suite"
 
 	"github.com/imgproxy/imgproxy/v3/config"
+	"github.com/imgproxy/imgproxy/v3/httpheaders"
+	"github.com/imgproxy/imgproxy/v3/transport/generichttp"
 )
 
 type AzureTestSuite struct {
@@ -27,27 +29,31 @@ func (s *AzureTestSuite) SetupSuite() {
 
 	logrus.SetOutput(os.Stdout)
 
-	config.IgnoreSslVerification = true
-
 	s.etag = "testetag"
 	s.lastModified, _ = time.Parse(http.TimeFormat, "Wed, 21 Oct 2015 07:28:00 GMT")
 
 	s.server = httptest.NewTLSServer(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
 		s.Equal("/test/foo/test.png", r.URL.Path)
 
-		rw.Header().Set("Etag", s.etag)
-		rw.Header().Set("Last-Modified", s.lastModified.Format(http.TimeFormat))
+		rw.Header().Set(httpheaders.Etag, s.etag)
+		rw.Header().Set(httpheaders.LastModified, s.lastModified.Format(http.TimeFormat))
 		rw.WriteHeader(200)
 		rw.Write(data)
 	}))
 
-	config.ABSEnabled = true
-	config.ABSEndpoint = s.server.URL
-	config.ABSName = "testname"
-	config.ABSKey = "dGVzdGtleQ=="
+	config := NewDefaultConfig()
+	config.Endpoint = s.server.URL
+	config.Name = "testname"
+	config.Key = "dGVzdGtleQ=="
+
+	tc := generichttp.NewDefaultConfig()
+	tc.IgnoreSslVerification = true
+
+	trans, gerr := generichttp.New(false, tc)
+	s.Require().NoError(gerr)
 
 	var err error
-	s.transport, err = New()
+	s.transport, err = New(config, trans)
 	s.Require().NoError(err)
 }
 
@@ -56,30 +62,18 @@ func (s *AzureTestSuite) TearDownSuite() {
 	config.IgnoreSslVerification = false
 }
 
-func (s *AzureTestSuite) TestRoundTripWithETagDisabledReturns200() {
-	config.ETagEnabled = false
+func (s *AzureTestSuite) TestRoundTripWithETag() {
 	request, _ := http.NewRequest("GET", "abs://test/foo/test.png", nil)
 
 	response, err := s.transport.RoundTrip(request)
 	s.Require().NoError(err)
 	s.Require().Equal(200, response.StatusCode)
-}
-
-func (s *AzureTestSuite) TestRoundTripWithETagEnabled() {
-	config.ETagEnabled = true
-	request, _ := http.NewRequest("GET", "abs://test/foo/test.png", nil)
-
-	response, err := s.transport.RoundTrip(request)
-	s.Require().NoError(err)
-	s.Require().Equal(200, response.StatusCode)
-	s.Require().Equal(s.etag, response.Header.Get("ETag"))
+	s.Require().Equal(s.etag, response.Header.Get(httpheaders.Etag))
 }
 
 func (s *AzureTestSuite) TestRoundTripWithIfNoneMatchReturns304() {
-	config.ETagEnabled = true
-
 	request, _ := http.NewRequest("GET", "abs://test/foo/test.png", nil)
-	request.Header.Set("If-None-Match", s.etag)
+	request.Header.Set(httpheaders.IfNoneMatch, s.etag)
 
 	response, err := s.transport.RoundTrip(request)
 	s.Require().NoError(err)
@@ -87,40 +81,26 @@ func (s *AzureTestSuite) TestRoundTripWithIfNoneMatchReturns304() {
 }
 
 func (s *AzureTestSuite) TestRoundTripWithUpdatedETagReturns200() {
-	config.ETagEnabled = true
-
 	request, _ := http.NewRequest("GET", "abs://test/foo/test.png", nil)
-	request.Header.Set("If-None-Match", s.etag+"_wrong")
+	request.Header.Set(httpheaders.IfNoneMatch, s.etag+"_wrong")
 
 	response, err := s.transport.RoundTrip(request)
 	s.Require().NoError(err)
 	s.Require().Equal(http.StatusOK, response.StatusCode)
 }
 
-func (s *AzureTestSuite) TestRoundTripWithLastModifiedDisabledReturns200() {
-	config.LastModifiedEnabled = false
-	request, _ := http.NewRequest("GET", "abs://test/foo/test.png", nil)
-
-	response, err := s.transport.RoundTrip(request)
-	s.Require().NoError(err)
-	s.Require().Equal(200, response.StatusCode)
-}
-
 func (s *AzureTestSuite) TestRoundTripWithLastModifiedEnabled() {
-	config.LastModifiedEnabled = true
 	request, _ := http.NewRequest("GET", "abs://test/foo/test.png", nil)
 
 	response, err := s.transport.RoundTrip(request)
 	s.Require().NoError(err)
 	s.Require().Equal(200, response.StatusCode)
-	s.Require().Equal(s.lastModified.Format(http.TimeFormat), response.Header.Get("Last-Modified"))
+	s.Require().Equal(s.lastModified.Format(http.TimeFormat), response.Header.Get(httpheaders.LastModified))
 }
 
 func (s *AzureTestSuite) TestRoundTripWithIfModifiedSinceReturns304() {
-	config.LastModifiedEnabled = true
-
 	request, _ := http.NewRequest("GET", "abs://test/foo/test.png", nil)
-	request.Header.Set("If-Modified-Since", s.lastModified.Format(http.TimeFormat))
+	request.Header.Set(httpheaders.IfModifiedSince, s.lastModified.Format(http.TimeFormat))
 
 	response, err := s.transport.RoundTrip(request)
 	s.Require().NoError(err)
@@ -128,15 +108,14 @@ func (s *AzureTestSuite) TestRoundTripWithIfModifiedSinceReturns304() {
 }
 
 func (s *AzureTestSuite) TestRoundTripWithUpdatedLastModifiedReturns200() {
-	config.LastModifiedEnabled = true
-
 	request, _ := http.NewRequest("GET", "abs://test/foo/test.png", nil)
-	request.Header.Set("If-Modified-Since", s.lastModified.Add(-24*time.Hour).Format(http.TimeFormat))
+	request.Header.Set(httpheaders.IfModifiedSince, s.lastModified.Add(-24*time.Hour).Format(http.TimeFormat))
 
 	response, err := s.transport.RoundTrip(request)
 	s.Require().NoError(err)
 	s.Require().Equal(http.StatusOK, response.StatusCode)
 }
+
 func TestAzureTransport(t *testing.T) {
 	suite.Run(t, new(AzureTestSuite))
 }

+ 41 - 0
transport/azure/config.go

@@ -0,0 +1,41 @@
+package azure
+
+import (
+	"fmt"
+
+	"github.com/imgproxy/imgproxy/v3/config"
+)
+
+// Config holds the configuration for Azure Blob Storage transport
+type Config struct {
+	Name     string // Azure storage account name
+	Endpoint string // Azure Blob Storage endpoint URL
+	Key      string // Azure storage account key
+}
+
+// NewDefaultConfig returns a new default configuration for Azure Blob Storage transport
+func NewDefaultConfig() *Config {
+	return &Config{
+		Name:     "",
+		Endpoint: "",
+		Key:      "",
+	}
+}
+
+// LoadFromEnv loads configuration from the global config package
+func LoadFromEnv(c *Config) (*Config, error) {
+	c.Name = config.ABSName
+	c.Endpoint = config.ABSEndpoint
+	c.Key = config.ABSKey
+
+	return c, nil
+}
+
+// Validate checks if the configuration is valid
+func (c *Config) Validate() error {
+	if len(c.Name) == 0 {
+		return fmt.Errorf("azure account name must be set")
+	}
+
+	return nil
+}

+ 89 - 0
transport/config.go

@@ -0,0 +1,89 @@
+// config.go is just a shortcut for common.Config which helps to
+// avoid importing of the `common` package directly.
+package transport
+
+import (
+	"github.com/imgproxy/imgproxy/v3/config"
+	"github.com/imgproxy/imgproxy/v3/transport/azure"
+	"github.com/imgproxy/imgproxy/v3/transport/fs"
+	"github.com/imgproxy/imgproxy/v3/transport/gcs"
+	"github.com/imgproxy/imgproxy/v3/transport/generichttp"
+	"github.com/imgproxy/imgproxy/v3/transport/s3"
+	"github.com/imgproxy/imgproxy/v3/transport/swift"
+)
+
+// Config represents configuration of the transport package
+type Config struct {
+	HTTP *generichttp.Config
+
+	Local *fs.Config
+
+	ABSEnabled bool
+	ABS        *azure.Config
+
+	GCSEnabled bool
+	GCS        *gcs.Config
+
+	S3Enabled bool
+	S3        *s3.Config
+
+	SwiftEnabled bool
+	Swift        *swift.Config
+}
+
+// NewDefaultConfig returns a new default transport configuration
+func NewDefaultConfig() *Config {
+	return &Config{
+		HTTP:         generichttp.NewDefaultConfig(),
+		Local:        fs.NewDefaultConfig(),
+		ABSEnabled:   false,
+		ABS:          azure.NewDefaultConfig(),
+		GCSEnabled:   false,
+		GCS:          gcs.NewDefaultConfig(),
+		S3Enabled:    false,
+		S3:           s3.NewDefaultConfig(),
+		SwiftEnabled: false,
+		Swift:        swift.NewDefaultConfig(),
+	}
+}
+
+// LoadFromEnv loads transport configuration from environment variables
+func LoadFromEnv(c *Config) (*Config, error) {
+	var err error
+
+	if c.HTTP, err = generichttp.LoadFromEnv(c.HTTP); err != nil {
+		return nil, err
+	}
+
+	if c.Local, err = fs.LoadFromEnv(c.Local); err != nil {
+		return nil, err
+	}
+
+	if c.ABS, err = azure.LoadFromEnv(c.ABS); err != nil {
+		return nil, err
+	}
+
+	if c.GCS, err = gcs.LoadFromEnv(c.GCS); err != nil {
+		return nil, err
+	}
+
+	if c.S3, err = s3.LoadFromEnv(c.S3); err != nil {
+		return nil, err
+	}
+
+	if c.Swift, err = swift.LoadFromEnv(c.Swift); err != nil {
+		return nil, err
+	}
+
+	c.ABSEnabled = config.ABSEnabled
+	c.GCSEnabled = config.GCSEnabled
+	c.S3Enabled = config.S3Enabled
+	c.SwiftEnabled = config.SwiftEnabled
+
+	return c, nil
+}
+
+func (c *Config) Validate() error {
+	// We won't validate upstream config here: they might not be used
+	return nil
+}

+ 52 - 0
transport/fs/config.go

@@ -0,0 +1,52 @@
+package fs
+
+import (
+	"errors"
+	"fmt"
+	"os"
+
+	"github.com/imgproxy/imgproxy/v3/config"
+)
+
+// Config holds the configuration for local file system transport
+type Config struct {
+	Root string // Root directory for the local file system transport
+}
+
+// NewDefaultConfig returns a new default configuration for local file system transport
+func NewDefaultConfig() *Config {
+	return &Config{
+		Root: "",
+	}
+}
+
+// LoadFromEnv loads configuration from the global config package
+func LoadFromEnv(c *Config) (*Config, error) {
+	c.Root = config.LocalFileSystemRoot
+
+	return c, nil
+}
+
+// Validate checks if the configuration is valid
+func (c *Config) Validate() error {
+	if c.Root == "" {
+		return errors.New("local file system root shold not be blank")
+	}
+
+	stat, err := os.Stat(c.Root)
+	if err != nil {
+		return fmt.Errorf("cannot use local directory: %s", err)
+	}
+
+	if !stat.IsDir() {
+		return fmt.Errorf("cannot use local directory: not a directory")
+	}
+
+	if c.Root == "/" {
+		// Warning: exposing root is unsafe
+		// TODO: Move this somewhere to the instance checks (?)
+		fmt.Println("Warning: Exposing root via IMGPROXY_LOCAL_FILESYSTEM_ROOT is unsafe")
+	}
+
+	return nil
+}

+ 14 - 17
transport/fs/fs.go

@@ -13,7 +13,7 @@ import (
 	"strconv"
 	"strings"
 
-	"github.com/imgproxy/imgproxy/v3/config"
+	"github.com/imgproxy/imgproxy/v3/httpheaders"
 	"github.com/imgproxy/imgproxy/v3/httprange"
 	"github.com/imgproxy/imgproxy/v3/transport/common"
 	"github.com/imgproxy/imgproxy/v3/transport/notmodified"
@@ -23,8 +23,9 @@ type transport struct {
 	fs http.Dir
 }
 
-func New() transport {
-	return transport{fs: http.Dir(config.LocalFileSystemRoot)}
+func New(config *Config) transport {
+	// TODO: VALIDATE HERE
+	return transport{fs: http.Dir(config.Root)}
 }
 
 func (t transport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
@@ -55,11 +56,11 @@ func (t transport) RoundTrip(req *http.Request) (resp *http.Response, err error)
 	body := io.ReadCloser(f)
 
 	if mimetype := detectContentType(f, fi); len(mimetype) > 0 {
-		header.Set("Content-Type", mimetype)
+		header.Set(httpheaders.ContentType, mimetype)
 	}
 	f.Seek(0, io.SeekStart)
 
-	start, end, err := httprange.Parse(req.Header.Get("Range"))
+	start, end, err := httprange.Parse(req.Header.Get(httpheaders.Range))
 	switch {
 	case err != nil:
 		f.Close()
@@ -75,18 +76,14 @@ func (t transport) RoundTrip(req *http.Request) (resp *http.Response, err error)
 		statusCode = http.StatusPartialContent
 		size = end - start + 1
 		body = &fileLimiter{f: f, left: int(size)}
-		header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, fi.Size()))
+		header.Set(httpheaders.ContentRange, fmt.Sprintf("bytes %d-%d/%d", start, end, fi.Size()))
 
 	default:
-		if config.ETagEnabled {
-			etag := BuildEtag(path, fi)
-			header.Set("ETag", etag)
-		}
+		etag := BuildEtag(path, fi)
+		header.Set(httpheaders.Etag, etag)
 
-		if config.LastModifiedEnabled {
-			lastModified := fi.ModTime().Format(http.TimeFormat)
-			header.Set("Last-Modified", lastModified)
-		}
+		lastModified := fi.ModTime().Format(http.TimeFormat)
+		header.Set(httpheaders.LastModified, lastModified)
 	}
 
 	if resp := notmodified.Response(req, header); resp != nil {
@@ -94,8 +91,8 @@ func (t transport) RoundTrip(req *http.Request) (resp *http.Response, err error)
 		return resp, nil
 	}
 
-	header.Set("Accept-Ranges", "bytes")
-	header.Set("Content-Length", strconv.Itoa(int(size)))
+	header.Set(httpheaders.AcceptRanges, "bytes")
+	header.Set(httpheaders.ContentLength, strconv.Itoa(int(size)))
 
 	return &http.Response{
 		StatusCode:    statusCode,
@@ -122,7 +119,7 @@ func respNotFound(req *http.Request, msg string) *http.Response {
 		Proto:         "HTTP/1.0",
 		ProtoMajor:    1,
 		ProtoMinor:    0,
-		Header:        http.Header{"Content-Type": {"text/plain"}},
+		Header:        http.Header{httpheaders.ContentType: {"text/plain"}},
 		ContentLength: int64(len(msg)),
 		Body:          io.NopCloser(strings.NewReader(msg)),
 		Close:         false,

+ 12 - 38
transport/fs/fs_test.go

@@ -9,7 +9,7 @@ import (
 
 	"github.com/stretchr/testify/suite"
 
-	"github.com/imgproxy/imgproxy/v3/config"
+	"github.com/imgproxy/imgproxy/v3/httpheaders"
 )
 
 type FsTestSuite struct {
@@ -24,39 +24,27 @@ func (s *FsTestSuite) SetupSuite() {
 	wd, err := os.Getwd()
 	s.Require().NoError(err)
 
-	config.LocalFileSystemRoot = filepath.Join(wd, "..", "..", "testdata")
+	fsRoot := filepath.Join(wd, "..", "..", "testdata")
 
-	fi, err := os.Stat(filepath.Join(config.LocalFileSystemRoot, "test1.png"))
+	fi, err := os.Stat(filepath.Join(fsRoot, "test1.png"))
 	s.Require().NoError(err)
 
 	s.etag = BuildEtag("/test1.png", fi)
 	s.modTime = fi.ModTime()
-	s.transport = New()
-}
-
-func (s *FsTestSuite) TestRoundTripWithETagDisabledReturns200() {
-	config.ETagEnabled = false
-	request, _ := http.NewRequest("GET", "local:///test1.png", nil)
-
-	response, err := s.transport.RoundTrip(request)
-	s.Require().NoError(err)
-	s.Require().Equal(200, response.StatusCode)
+	s.transport = New(&Config{Root: fsRoot})
 }
 
 func (s *FsTestSuite) TestRoundTripWithETagEnabled() {
-	config.ETagEnabled = true
 	request, _ := http.NewRequest("GET", "local:///test1.png", nil)
 
 	response, err := s.transport.RoundTrip(request)
 	s.Require().NoError(err)
 	s.Require().Equal(200, response.StatusCode)
-	s.Require().Equal(s.etag, response.Header.Get("ETag"))
+	s.Require().Equal(s.etag, response.Header.Get(httpheaders.Etag))
 }
 func (s *FsTestSuite) TestRoundTripWithIfNoneMatchReturns304() {
-	config.ETagEnabled = true
-
 	request, _ := http.NewRequest("GET", "local:///test1.png", nil)
-	request.Header.Set("If-None-Match", s.etag)
+	request.Header.Set(httpheaders.IfNoneMatch, s.etag)
 
 	response, err := s.transport.RoundTrip(request)
 	s.Require().NoError(err)
@@ -64,39 +52,26 @@ func (s *FsTestSuite) TestRoundTripWithIfNoneMatchReturns304() {
 }
 
 func (s *FsTestSuite) TestRoundTripWithUpdatedETagReturns200() {
-	config.ETagEnabled = true
-
 	request, _ := http.NewRequest("GET", "local:///test1.png", nil)
-	request.Header.Set("If-None-Match", s.etag+"_wrong")
+	request.Header.Set(httpheaders.IfNoneMatch, s.etag+"_wrong")
 
 	response, err := s.transport.RoundTrip(request)
 	s.Require().NoError(err)
 	s.Require().Equal(http.StatusOK, response.StatusCode)
 }
-func (s *FsTestSuite) TestRoundTripWithLastModifiedDisabledReturns200() {
-	config.LastModifiedEnabled = false
-	request, _ := http.NewRequest("GET", "local:///test1.png", nil)
-
-	response, err := s.transport.RoundTrip(request)
-	s.Require().NoError(err)
-	s.Require().Equal(200, response.StatusCode)
-}
 
 func (s *FsTestSuite) TestRoundTripWithLastModifiedEnabledReturns200() {
-	config.LastModifiedEnabled = true
 	request, _ := http.NewRequest("GET", "local:///test1.png", nil)
 
 	response, err := s.transport.RoundTrip(request)
 	s.Require().NoError(err)
 	s.Require().Equal(200, response.StatusCode)
-	s.Require().Equal(s.modTime.Format(http.TimeFormat), response.Header.Get("Last-Modified"))
+	s.Require().Equal(s.modTime.Format(http.TimeFormat), response.Header.Get(httpheaders.LastModified))
 }
 
 func (s *FsTestSuite) TestRoundTripWithIfModifiedSinceReturns304() {
-	config.LastModifiedEnabled = true
-
 	request, _ := http.NewRequest("GET", "local:///test1.png", nil)
-	request.Header.Set("If-Modified-Since", s.modTime.Format(http.TimeFormat))
+	request.Header.Set(httpheaders.IfModifiedSince, s.modTime.Format(http.TimeFormat))
 
 	response, err := s.transport.RoundTrip(request)
 	s.Require().NoError(err)
@@ -104,15 +79,14 @@ func (s *FsTestSuite) TestRoundTripWithIfModifiedSinceReturns304() {
 }
 
 func (s *FsTestSuite) TestRoundTripWithUpdatedLastModifiedReturns200() {
-	config.LastModifiedEnabled = true
-
 	request, _ := http.NewRequest("GET", "local:///test1.png", nil)
-	request.Header.Set("If-Modified-Since", s.modTime.Add(-time.Minute).Format(http.TimeFormat))
+	request.Header.Set(httpheaders.IfModifiedSince, s.modTime.Add(-time.Minute).Format(http.TimeFormat))
 
 	response, err := s.transport.RoundTrip(request)
 	s.Require().NoError(err)
 	s.Require().Equal(http.StatusOK, response.StatusCode)
 }
-func TestS3Transport(t *testing.T) {
+
+func TestFSTransport(t *testing.T) {
 	suite.Run(t, new(FsTestSuite))
 }

+ 29 - 0
transport/gcs/config.go

@@ -0,0 +1,29 @@
+package gcs
+
+import "github.com/imgproxy/imgproxy/v3/config"
+
+// Config holds the configuration for Google Cloud Storage transport
+type Config struct {
+	Key      string // Google Cloud Storage service account key
+	Endpoint string // Google Cloud Storage endpoint URL
+}
+
+// NewDefaultConfig returns a new default configuration for Google Cloud Storage transport
+func NewDefaultConfig() *Config {
+	return &Config{
+		Key:      "",
+		Endpoint: "",
+	}
+}
+
+// LoadFromEnv loads configuration from the global config package
+func LoadFromEnv(c *Config) (*Config, error) {
+	c.Key = config.GCSKey
+	c.Endpoint = config.GCSEndpoint
+	return c, nil
+}
+
+// Validate checks the configuration for errors
+func (c *Config) Validate() error {
+	return nil
+}

+ 21 - 30
transport/gcs/gcs.go

@@ -14,11 +14,10 @@ import (
 	raw "google.golang.org/api/storage/v1"
 	htransport "google.golang.org/api/transport/http"
 
-	"github.com/imgproxy/imgproxy/v3/config"
+	"github.com/imgproxy/imgproxy/v3/httpheaders"
 	"github.com/imgproxy/imgproxy/v3/httprange"
 	"github.com/imgproxy/imgproxy/v3/ierrors"
 	"github.com/imgproxy/imgproxy/v3/transport/common"
-	"github.com/imgproxy/imgproxy/v3/transport/generichttp"
 	"github.com/imgproxy/imgproxy/v3/transport/notmodified"
 )
 
@@ -29,9 +28,8 @@ type transport struct {
 	client *storage.Client
 }
 
-func buildHTTPClient(opts ...option.ClientOption) (*http.Client, error) {
-	trans, err := generichttp.New(false)
-	if err != nil {
+func buildHTTPClient(config *Config, trans *http.Transport, opts ...option.ClientOption) (*http.Client, error) {
+	if err := config.Validate(); err != nil {
 		return nil, err
 	}
 
@@ -43,27 +41,26 @@ func buildHTTPClient(opts ...option.ClientOption) (*http.Client, error) {
 	return &http.Client{Transport: htrans}, nil
 }
 
-func New() (http.RoundTripper, error) {
+func New(config *Config, trans *http.Transport) (http.RoundTripper, error) {
 	var client *storage.Client
 
 	opts := []option.ClientOption{
 		option.WithScopes(raw.DevstorageReadOnlyScope),
-		option.WithUserAgent(config.UserAgent),
 	}
 
-	if len(config.GCSKey) > 0 {
-		opts = append(opts, option.WithCredentialsJSON([]byte(config.GCSKey)))
+	if len(config.Key) > 0 {
+		opts = append(opts, option.WithCredentialsJSON([]byte(config.Key)))
 	}
 
-	if len(config.GCSEndpoint) > 0 {
-		opts = append(opts, option.WithEndpoint(config.GCSEndpoint))
+	if len(config.Endpoint) > 0 {
+		opts = append(opts, option.WithEndpoint(config.Endpoint))
 	}
 
 	if noAuth {
 		opts = append(opts, option.WithoutAuthentication())
 	}
 
-	httpClient, err := buildHTTPClient(opts...)
+	httpClient, err := buildHTTPClient(config, trans, opts...)
 	if err != nil {
 		return nil, err
 	}
@@ -111,7 +108,7 @@ func (t transport) RoundTrip(req *http.Request) (*http.Response, error) {
 
 	header := make(http.Header)
 
-	if r := req.Header.Get("Range"); len(r) != 0 {
+	if r := req.Header.Get(httpheaders.Range); len(r) != 0 {
 		start, end, err := httprange.Parse(r)
 		if err != nil {
 			return httprange.InvalidHTTPRangeResponse(req), nil
@@ -135,24 +132,18 @@ func (t transport) RoundTrip(req *http.Request) (*http.Response, error) {
 			size = end - reader.Attrs.StartOffset + 1
 
 			statusCode = http.StatusPartialContent
-			header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", reader.Attrs.StartOffset, end, reader.Attrs.Size))
+			header.Set(httpheaders.ContentRange, fmt.Sprintf("bytes %d-%d/%d", reader.Attrs.StartOffset, end, reader.Attrs.Size))
 		}
 	}
 
 	// We haven't initialize reader yet, this means that we need non-ranged reader
 	if reader == nil {
-		if config.ETagEnabled || config.LastModifiedEnabled {
-			attrs, err := obj.Attrs(req.Context())
-			if err != nil {
-				return handleError(req, err)
-			}
-			if config.ETagEnabled {
-				header.Set("ETag", attrs.Etag)
-			}
-			if config.LastModifiedEnabled {
-				header.Set("Last-Modified", attrs.Updated.Format(http.TimeFormat))
-			}
+		attrs, aerr := obj.Attrs(req.Context())
+		if aerr != nil {
+			return handleError(req, aerr)
 		}
+		header.Set(httpheaders.Etag, attrs.Etag)
+		header.Set(httpheaders.LastModified, attrs.Updated.Format(http.TimeFormat))
 
 		if resp := notmodified.Response(req, header); resp != nil {
 			return resp, nil
@@ -168,10 +159,10 @@ func (t transport) RoundTrip(req *http.Request) (*http.Response, error) {
 		size = reader.Attrs.Size
 	}
 
-	header.Set("Accept-Ranges", "bytes")
-	header.Set("Content-Length", strconv.Itoa(int(size)))
-	header.Set("Content-Type", reader.Attrs.ContentType)
-	header.Set("Cache-Control", reader.Attrs.CacheControl)
+	header.Set(httpheaders.AcceptRanges, "bytes")
+	header.Set(httpheaders.ContentLength, strconv.Itoa(int(size)))
+	header.Set(httpheaders.ContentType, reader.Attrs.ContentType)
+	header.Set(httpheaders.CacheControl, reader.Attrs.CacheControl)
 
 	return &http.Response{
 		StatusCode:    statusCode,
@@ -196,7 +187,7 @@ func handleError(req *http.Request, err error) (*http.Response, error) {
 		Proto:         "HTTP/1.0",
 		ProtoMajor:    1,
 		ProtoMinor:    0,
-		Header:        http.Header{"Content-Type": {"text/plain"}},
+		Header:        http.Header{httpheaders.ContentType: {"text/plain"}},
 		ContentLength: int64(len(err.Error())),
 		Body:          io.NopCloser(strings.NewReader(err.Error())),
 		Close:         false,

+ 18 - 37
transport/gcs/gcs_test.go

@@ -10,7 +10,8 @@ import (
 	"github.com/fsouza/fake-gcs-server/fakestorage"
 	"github.com/stretchr/testify/suite"
 
-	"github.com/imgproxy/imgproxy/v3/config"
+	"github.com/imgproxy/imgproxy/v3/httpheaders"
+	"github.com/imgproxy/imgproxy/v3/transport/generichttp"
 )
 
 func getFreePort() (int, error) {
@@ -67,10 +68,16 @@ func (s *GCSTestSuite) SetupSuite() {
 	s.Require().NoError(err)
 	s.etag = obj.Etag
 
-	config.GCSEnabled = true
-	config.GCSEndpoint = s.server.PublicURL() + "/storage/v1/"
+	config := NewDefaultConfig()
+	config.Endpoint = s.server.PublicURL() + "/storage/v1/"
 
-	s.transport, err = New()
+	tc := generichttp.NewDefaultConfig()
+	tc.IgnoreSslVerification = true
+
+	trans, gerr := generichttp.New(false, tc)
+	s.Require().NoError(gerr)
+
+	s.transport, err = New(config, trans)
 	s.Require().NoError(err)
 }
 
@@ -78,30 +85,18 @@ func (s *GCSTestSuite) TearDownSuite() {
 	s.server.Stop()
 }
 
-func (s *GCSTestSuite) TestRoundTripWithETagDisabledReturns200() {
-	config.ETagEnabled = false
-	request, _ := http.NewRequest("GET", "gcs://test/foo/test.png", nil)
-
-	response, err := s.transport.RoundTrip(request)
-	s.Require().NoError(err)
-	s.Require().Equal(200, response.StatusCode)
-}
-
 func (s *GCSTestSuite) TestRoundTripWithETagEnabled() {
-	config.ETagEnabled = true
 	request, _ := http.NewRequest("GET", "gcs://test/foo/test.png", nil)
 
 	response, err := s.transport.RoundTrip(request)
 	s.Require().NoError(err)
 	s.Require().Equal(200, response.StatusCode)
-	s.Require().Equal(s.etag, response.Header.Get("ETag"))
+	s.Require().Equal(s.etag, response.Header.Get(httpheaders.Etag))
 }
 
 func (s *GCSTestSuite) TestRoundTripWithIfNoneMatchReturns304() {
-	config.ETagEnabled = true
-
 	request, _ := http.NewRequest("GET", "gcs://test/foo/test.png", nil)
-	request.Header.Set("If-None-Match", s.etag)
+	request.Header.Set(httpheaders.IfNoneMatch, s.etag)
 
 	response, err := s.transport.RoundTrip(request)
 	s.Require().NoError(err)
@@ -109,48 +104,34 @@ func (s *GCSTestSuite) TestRoundTripWithIfNoneMatchReturns304() {
 }
 
 func (s *GCSTestSuite) TestRoundTripWithUpdatedETagReturns200() {
-	config.ETagEnabled = true
-
 	request, _ := http.NewRequest("GET", "gcs://test/foo/test.png", nil)
-	request.Header.Set("If-None-Match", s.etag+"_wrong")
+	request.Header.Set(httpheaders.IfNoneMatch, s.etag+"_wrong")
 
 	response, err := s.transport.RoundTrip(request)
 	s.Require().NoError(err)
 	s.Require().Equal(http.StatusOK, response.StatusCode)
 }
 
-func (s *GCSTestSuite) TestRoundTripWithLastModifiedDisabledReturns200() {
-	config.LastModifiedEnabled = false
-	request, _ := http.NewRequest("GET", "gcs://test/foo/test.png", nil)
-
-	response, err := s.transport.RoundTrip(request)
-	s.Require().NoError(err)
-	s.Require().Equal(200, response.StatusCode)
-}
 func (s *GCSTestSuite) TestRoundTripWithLastModifiedEnabled() {
-	config.LastModifiedEnabled = true
 	request, _ := http.NewRequest("GET", "gcs://test/foo/test.png", nil)
 
 	response, err := s.transport.RoundTrip(request)
 	s.Require().NoError(err)
 	s.Require().Equal(200, response.StatusCode)
-	s.Require().Equal(s.lastModified.Format(http.TimeFormat), response.Header.Get("Last-Modified"))
+	s.Require().Equal(s.lastModified.Format(http.TimeFormat), response.Header.Get(httpheaders.LastModified))
 }
 func (s *GCSTestSuite) TestRoundTripWithIfModifiedSinceReturns304() {
-	config.LastModifiedEnabled = true
-
 	request, _ := http.NewRequest("GET", "gcs://test/foo/test.png", nil)
-	request.Header.Set("If-Modified-Since", s.lastModified.Format(http.TimeFormat))
+	request.Header.Set(httpheaders.IfModifiedSince, s.lastModified.Format(http.TimeFormat))
 
 	response, err := s.transport.RoundTrip(request)
 	s.Require().NoError(err)
 	s.Require().Equal(http.StatusNotModified, response.StatusCode)
 }
-func (s *GCSTestSuite) TestRoundTripWithUpdatedLastModifiedReturns200() {
-	config.LastModifiedEnabled = true
 
+func (s *GCSTestSuite) TestRoundTripWithUpdatedLastModifiedReturns200() {
 	request, _ := http.NewRequest("GET", "gcs://test/foo/test.png", nil)
-	request.Header.Set("If-Modified-Sicne", s.lastModified.Add(-24*time.Hour).Format(http.TimeFormat))
+	request.Header.Set(httpheaders.IfModifiedSince, s.lastModified.Add(-24*time.Hour).Format(http.TimeFormat))
 
 	response, err := s.transport.RoundTrip(request)
 	s.Require().NoError(err)

+ 39 - 0
transport/generichttp/config.go

@@ -0,0 +1,39 @@
+package generichttp
+
+import (
+	"fmt"
+	"time"
+
+	"github.com/imgproxy/imgproxy/v3/config"
+)
+
+// Config holds the configuration for the generic HTTP transport
+type Config struct {
+	ClientKeepAliveTimeout time.Duration
+	IgnoreSslVerification  bool
+}
+
+// NewDefaultConfig returns a new default configuration for the generic HTTP transport
+func NewDefaultConfig() *Config {
+	return &Config{
+		ClientKeepAliveTimeout: 90 * time.Second,
+		IgnoreSslVerification:  false,
+	}
+}
+
+// LoadFromEnv loads configuration from the global config package
+func LoadFromEnv(c *Config) (*Config, error) {
+	c.ClientKeepAliveTimeout = time.Duration(config.ClientKeepAliveTimeout) * time.Second
+	c.IgnoreSslVerification = config.IgnoreSslVerification
+
+	return c, nil
+}
+
+// Validate checks the configuration for errors
+func (c *Config) Validate() error {
+	if c.ClientKeepAliveTimeout < 0 {
+		return fmt.Errorf("client KeepAlive timeout should be greater than or equal to 0, now - %d", c.ClientKeepAliveTimeout)
+	}
+
+	return nil
+}

+ 6 - 3
transport/generichttp/generic_http.go

@@ -8,12 +8,15 @@ import (
 	"syscall"
 	"time"
 
-	"github.com/imgproxy/imgproxy/v3/config"
 	"github.com/imgproxy/imgproxy/v3/security"
 	"golang.org/x/net/http2"
 )
 
-func New(verifyNetworks bool) (*http.Transport, error) {
+func New(verifyNetworks bool, config *Config) (*http.Transport, error) {
+	if err := config.Validate(); err != nil {
+		return nil, err
+	}
+
 	dialer := &net.Dialer{
 		Timeout:   30 * time.Second,
 		KeepAlive: 30 * time.Second,
@@ -30,7 +33,7 @@ func New(verifyNetworks bool) (*http.Transport, error) {
 		Proxy:                 http.ProxyFromEnvironment,
 		DialContext:           dialer.DialContext,
 		MaxIdleConns:          100,
-		MaxIdleConnsPerHost:   config.Workers + 1,
+		MaxIdleConnsPerHost:   100,
 		IdleConnTimeout:       time.Duration(config.ClientKeepAliveTimeout) * time.Second,
 		TLSHandshakeTimeout:   10 * time.Second,
 		ExpectContinueTimeout: 1 * time.Second,

+ 24 - 27
transport/notmodified/notmodified.go

@@ -4,38 +4,35 @@ import (
 	"net/http"
 	"time"
 
-	"github.com/imgproxy/imgproxy/v3/config"
+	"github.com/imgproxy/imgproxy/v3/httpheaders"
 )
 
 func Response(req *http.Request, header http.Header) *http.Response {
-	if config.ETagEnabled {
-		etag := header.Get("ETag")
-		ifNoneMatch := req.Header.Get("If-None-Match")
+	etag := header.Get(httpheaders.Etag)
+	ifNoneMatch := req.Header.Get(httpheaders.IfNoneMatch)
 
-		if len(ifNoneMatch) > 0 && ifNoneMatch == etag {
-			return response(req, header)
-		}
+	if len(ifNoneMatch) > 0 && ifNoneMatch == etag {
+		return response(req, header)
 	}
-	if config.LastModifiedEnabled {
-		lastModifiedRaw := header.Get("Last-Modified")
-		if len(lastModifiedRaw) == 0 {
-			return nil
-		}
-		ifModifiedSinceRaw := req.Header.Get("If-Modified-Since")
-		if len(ifModifiedSinceRaw) == 0 {
-			return nil
-		}
-		lastModified, err := time.Parse(http.TimeFormat, lastModifiedRaw)
-		if err != nil {
-			return nil
-		}
-		ifModifiedSince, err := time.Parse(http.TimeFormat, ifModifiedSinceRaw)
-		if err != nil {
-			return nil
-		}
-		if !ifModifiedSince.Before(lastModified) {
-			return response(req, header)
-		}
+
+	lastModifiedRaw := header.Get(httpheaders.LastModified)
+	if len(lastModifiedRaw) == 0 {
+		return nil
+	}
+	ifModifiedSinceRaw := req.Header.Get(httpheaders.IfModifiedSince)
+	if len(ifModifiedSinceRaw) == 0 {
+		return nil
+	}
+	lastModified, err := time.Parse(http.TimeFormat, lastModifiedRaw)
+	if err != nil {
+		return nil
+	}
+	ifModifiedSince, err := time.Parse(http.TimeFormat, ifModifiedSinceRaw)
+	if err != nil {
+		return nil
+	}
+	if !ifModifiedSince.Before(lastModified) {
+		return response(req, header)
 	}
 
 	return nil

+ 42 - 0
transport/s3/config.go

@@ -0,0 +1,42 @@
+package s3
+
+import "github.com/imgproxy/imgproxy/v3/config"
+
+// Config holds the configuration for S3 transport
+type Config struct {
+	Region                  string // AWS region for S3 (default: "")
+	Endpoint                string // Custom endpoint for S3 (default: "")
+	EndpointUsePathStyle    bool   // Use path-style URLs for S3 (default: true)
+	AssumeRoleArn           string // ARN for assuming an AWS role (default: "")
+	AssumeRoleExternalID    string // External ID for assuming an AWS role (default: "")
+	DecryptionClientEnabled bool   // Enables S3 decryption client (default: false)
+}
+
+// NewDefaultConfig returns a new default configuration for S3 transport
+func NewDefaultConfig() *Config {
+	return &Config{
+		Region:                  "",
+		Endpoint:                "",
+		EndpointUsePathStyle:    true,
+		AssumeRoleArn:           "",
+		AssumeRoleExternalID:    "",
+		DecryptionClientEnabled: false,
+	}
+}
+
+// LoadFromEnv loads configuration from the global config package
+func LoadFromEnv(c *Config) (*Config, error) {
+	c.Region = config.S3Region
+	c.Endpoint = config.S3Endpoint
+	c.EndpointUsePathStyle = config.S3EndpointUsePathStyle
+	c.AssumeRoleArn = config.S3AssumeRoleArn
+	c.AssumeRoleExternalID = config.S3AssumeRoleExternalID
+	c.DecryptionClientEnabled = config.S3DecryptionClientEnabled
+
+	return c, nil
+}
+
+// Validate checks the configuration for errors
+func (c *Config) Validate() error {
+	return nil
+}

+ 40 - 42
transport/s3/s3.go

@@ -20,10 +20,9 @@ import (
 	"github.com/aws/aws-sdk-go-v2/service/s3"
 	"github.com/aws/aws-sdk-go-v2/service/sts"
 
-	"github.com/imgproxy/imgproxy/v3/config"
+	"github.com/imgproxy/imgproxy/v3/httpheaders"
 	"github.com/imgproxy/imgproxy/v3/ierrors"
 	"github.com/imgproxy/imgproxy/v3/transport/common"
-	"github.com/imgproxy/imgproxy/v3/transport/generichttp"
 )
 
 type s3Client interface {
@@ -41,33 +40,34 @@ type transport struct {
 	clientsByBucket map[string]s3Client
 
 	mu sync.RWMutex
+
+	config *Config
 }
 
-func New() (http.RoundTripper, error) {
-	conf, err := awsConfig.LoadDefaultConfig(context.Background())
-	if err != nil {
-		return nil, ierrors.Wrap(err, 0, ierrors.WithPrefix("can't load AWS S3 config"))
+func New(config *Config, trans *http.Transport) (http.RoundTripper, error) {
+	if err := config.Validate(); err != nil {
+		return nil, err
 	}
 
-	trans, err := generichttp.New(false)
+	conf, err := awsConfig.LoadDefaultConfig(context.Background())
 	if err != nil {
-		return nil, err
+		return nil, ierrors.Wrap(err, 0, ierrors.WithPrefix("can't load AWS S3 config"))
 	}
 
 	conf.HTTPClient = &http.Client{Transport: trans}
 
-	if len(config.S3Region) != 0 {
-		conf.Region = config.S3Region
+	if len(config.Region) != 0 {
+		conf.Region = config.Region
 	}
 
 	if len(conf.Region) == 0 {
 		conf.Region = "us-west-1"
 	}
 
-	if len(config.S3AssumeRoleArn) != 0 {
-		creds := stscreds.NewAssumeRoleProvider(sts.NewFromConfig(conf), config.S3AssumeRoleArn, func(o *stscreds.AssumeRoleOptions) {
-			if len(config.S3AssumeRoleExternalID) != 0 {
-				o.ExternalID = aws.String(config.S3AssumeRoleExternalID)
+	if len(config.AssumeRoleArn) != 0 {
+		creds := stscreds.NewAssumeRoleProvider(sts.NewFromConfig(conf), config.AssumeRoleArn, func(o *stscreds.AssumeRoleOptions) {
+			if len(config.AssumeRoleExternalID) != 0 {
+				o.ExternalID = aws.String(config.AssumeRoleExternalID)
 			}
 		})
 		conf.Credentials = creds
@@ -79,18 +79,18 @@ func New() (http.RoundTripper, error) {
 		},
 	}
 
-	if len(config.S3Endpoint) != 0 {
-		endpoint := config.S3Endpoint
+	if len(config.Endpoint) != 0 {
+		endpoint := config.Endpoint
 		if !strings.HasPrefix(endpoint, "http://") && !strings.HasPrefix(endpoint, "https://") {
 			endpoint = "http://" + endpoint
 		}
 		clientOptions = append(clientOptions, func(o *s3.Options) {
 			o.BaseEndpoint = aws.String(endpoint)
-			o.UsePathStyle = config.S3EndpointUsePathStyle
+			o.UsePathStyle = config.EndpointUsePathStyle
 		})
 	}
 
-	client, err := createClient(conf, clientOptions)
+	client, err := createClient(conf, clientOptions, config)
 	if err != nil {
 		return nil, ierrors.Wrap(err, 0, ierrors.WithPrefix("can't create S3 client"))
 	}
@@ -101,6 +101,7 @@ func New() (http.RoundTripper, error) {
 		defaultConfig:   conf,
 		clientsByRegion: map[string]s3Client{conf.Region: client},
 		clientsByBucket: make(map[string]s3Client),
+		config:          config,
 	}, nil
 }
 
@@ -114,7 +115,7 @@ func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
 			Proto:         "HTTP/1.0",
 			ProtoMajor:    1,
 			ProtoMinor:    0,
-			Header:        http.Header{"Content-Type": {"text/plain"}},
+			Header:        http.Header{httpheaders.ContentType: {"text/plain"}},
 			ContentLength: int64(body.Len()),
 			Body:          io.NopCloser(body),
 			Close:         false,
@@ -136,17 +137,14 @@ func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
 	if r := req.Header.Get("Range"); len(r) != 0 {
 		input.Range = aws.String(r)
 	} else {
-		if config.ETagEnabled {
-			if ifNoneMatch := req.Header.Get("If-None-Match"); len(ifNoneMatch) > 0 {
-				input.IfNoneMatch = aws.String(ifNoneMatch)
-			}
+		if ifNoneMatch := req.Header.Get("If-None-Match"); len(ifNoneMatch) > 0 {
+			input.IfNoneMatch = aws.String(ifNoneMatch)
 		}
-		if config.LastModifiedEnabled {
-			if ifModifiedSince := req.Header.Get("If-Modified-Since"); len(ifModifiedSince) > 0 {
-				parsedIfModifiedSince, err := time.Parse(http.TimeFormat, ifModifiedSince)
-				if err == nil {
-					input.IfModifiedSince = &parsedIfModifiedSince
-				}
+
+		if ifModifiedSince := req.Header.Get("If-Modified-Since"); len(ifModifiedSince) > 0 {
+			parsedIfModifiedSince, err := time.Parse(http.TimeFormat, ifModifiedSince)
+			if err == nil {
+				input.IfModifiedSince = &parsedIfModifiedSince
 			}
 		}
 	}
@@ -183,7 +181,7 @@ func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
 		contentLength = *output.ContentLength
 	}
 
-	if config.S3DecryptionClientEnabled {
+	if t.config.DecryptionClientEnabled {
 		if unencryptedContentLength := output.Metadata["X-Amz-Meta-X-Amz-Unencrypted-Content-Length"]; len(unencryptedContentLength) != 0 {
 			cl, err := strconv.ParseInt(unencryptedContentLength, 10, 64)
 			if err != nil {
@@ -195,31 +193,31 @@ func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
 
 	header := make(http.Header)
 	if contentLength > 0 {
-		header.Set("Content-Length", strconv.FormatInt(contentLength, 10))
+		header.Set(httpheaders.ContentLength, strconv.FormatInt(contentLength, 10))
 	}
 	if output.ContentType != nil {
-		header.Set("Content-Type", *output.ContentType)
+		header.Set(httpheaders.ContentType, *output.ContentType)
 	}
 	if output.ContentEncoding != nil {
-		header.Set("Content-Encoding", *output.ContentEncoding)
+		header.Set(httpheaders.ContentEncoding, *output.ContentEncoding)
 	}
 	if output.CacheControl != nil {
-		header.Set("Cache-Control", *output.CacheControl)
+		header.Set(httpheaders.CacheControl, *output.CacheControl)
 	}
 	if output.ExpiresString != nil {
-		header.Set("Expires", *output.ExpiresString)
+		header.Set(httpheaders.Expires, *output.ExpiresString)
 	}
 	if output.ETag != nil {
-		header.Set("ETag", *output.ETag)
+		header.Set(httpheaders.Etag, *output.ETag)
 	}
 	if output.LastModified != nil {
-		header.Set("Last-Modified", output.LastModified.Format(http.TimeFormat))
+		header.Set(httpheaders.LastModified, output.LastModified.Format(http.TimeFormat))
 	}
 	if output.AcceptRanges != nil {
-		header.Set("Accept-Ranges", *output.AcceptRanges)
+		header.Set(httpheaders.AcceptRanges, *output.AcceptRanges)
 	}
 	if output.ContentRange != nil {
-		header.Set("Content-Range", *output.ContentRange)
+		header.Set(httpheaders.ContentRange, *output.ContentRange)
 		statusCode = http.StatusPartialContent
 	}
 
@@ -269,7 +267,7 @@ func (t *transport) createBucketClient(bucket, region string) (s3Client, error)
 	conf := t.defaultConfig.Copy()
 	conf.Region = region
 
-	client, err := createClient(conf, t.clientOptions)
+	client, err := createClient(conf, t.clientOptions, t.config)
 	if err != nil {
 		return nil, ierrors.Wrap(err, 0, ierrors.WithPrefix("can't create regional S3 client"))
 	}
@@ -280,10 +278,10 @@ func (t *transport) createBucketClient(bucket, region string) (s3Client, error)
 	return client, nil
 }
 
-func createClient(conf aws.Config, opts []func(*s3.Options)) (s3Client, error) {
+func createClient(conf aws.Config, opts []func(*s3.Options), config *Config) (s3Client, error) {
 	client := s3.NewFromConfig(conf, opts...)
 
-	if config.S3DecryptionClientEnabled {
+	if config.DecryptionClientEnabled {
 		kmsClient := kms.NewFromConfig(conf)
 		keyring := s3CryptoMaterials.NewKmsDecryptOnlyAnyKeyKeyring(kmsClient)
 

+ 10 - 33
transport/s3/s3_test.go

@@ -15,7 +15,7 @@ import (
 	"github.com/johannesboyne/gofakes3/backend/s3mem"
 	"github.com/stretchr/testify/suite"
 
-	"github.com/imgproxy/imgproxy/v3/config"
+	"github.com/imgproxy/imgproxy/v3/transport/generichttp"
 )
 
 type S3TestSuite struct {
@@ -32,15 +32,21 @@ func (s *S3TestSuite) SetupSuite() {
 	faker := gofakes3.New(backend)
 	s.server = httptest.NewServer(faker.Server())
 
-	config.S3Enabled = true
-	config.S3Endpoint = s.server.URL
+	config := NewDefaultConfig()
+	config.Endpoint = s.server.URL
 
 	os.Setenv("AWS_REGION", "eu-central-1")
 	os.Setenv("AWS_ACCESS_KEY_ID", "Foo")
 	os.Setenv("AWS_SECRET_ACCESS_KEY", "Bar")
 
+	tc := generichttp.NewDefaultConfig()
+	tc.IgnoreSslVerification = true
+
+	trans, gerr := generichttp.New(false, tc)
+	s.Require().NoError(gerr)
+
 	var err error
-	s.transport, err = New()
+	s.transport, err = New(config, trans)
 	s.Require().NoError(err)
 
 	err = backend.CreateBucket("test")
@@ -72,20 +78,9 @@ func (s *S3TestSuite) SetupSuite() {
 
 func (s *S3TestSuite) TearDownSuite() {
 	s.server.Close()
-	config.Reset()
-}
-
-func (s *S3TestSuite) TestRoundTripWithETagDisabledReturns200() {
-	config.ETagEnabled = false
-	request, _ := http.NewRequest("GET", "s3://test/foo/test.png", nil)
-
-	response, err := s.transport.RoundTrip(request)
-	s.Require().NoError(err)
-	s.Require().Equal(200, response.StatusCode)
 }
 
 func (s *S3TestSuite) TestRoundTripWithETagEnabled() {
-	config.ETagEnabled = true
 	request, _ := http.NewRequest("GET", "s3://test/foo/test.png", nil)
 
 	response, err := s.transport.RoundTrip(request)
@@ -95,8 +90,6 @@ func (s *S3TestSuite) TestRoundTripWithETagEnabled() {
 }
 
 func (s *S3TestSuite) TestRoundTripWithIfNoneMatchReturns304() {
-	config.ETagEnabled = true
-
 	request, _ := http.NewRequest("GET", "s3://test/foo/test.png", nil)
 	request.Header.Set("If-None-Match", s.etag)
 
@@ -106,8 +99,6 @@ func (s *S3TestSuite) TestRoundTripWithIfNoneMatchReturns304() {
 }
 
 func (s *S3TestSuite) TestRoundTripWithUpdatedETagReturns200() {
-	config.ETagEnabled = true
-
 	request, _ := http.NewRequest("GET", "s3://test/foo/test.png", nil)
 	request.Header.Set("If-None-Match", s.etag+"_wrong")
 
@@ -116,17 +107,7 @@ func (s *S3TestSuite) TestRoundTripWithUpdatedETagReturns200() {
 	s.Require().Equal(http.StatusOK, response.StatusCode)
 }
 
-func (s *S3TestSuite) TestRoundTripWithLastModifiedDisabledReturns200() {
-	config.LastModifiedEnabled = false
-	request, _ := http.NewRequest("GET", "s3://test/foo/test.png", nil)
-
-	response, err := s.transport.RoundTrip(request)
-	s.Require().NoError(err)
-	s.Require().Equal(200, response.StatusCode)
-}
-
 func (s *S3TestSuite) TestRoundTripWithLastModifiedEnabled() {
-	config.LastModifiedEnabled = true
 	request, _ := http.NewRequest("GET", "s3://test/foo/test.png", nil)
 
 	response, err := s.transport.RoundTrip(request)
@@ -136,8 +117,6 @@ func (s *S3TestSuite) TestRoundTripWithLastModifiedEnabled() {
 }
 
 func (s *S3TestSuite) TestRoundTripWithIfModifiedSinceReturns304() {
-	config.LastModifiedEnabled = true
-
 	request, _ := http.NewRequest("GET", "s3://test/foo/test.png", nil)
 	request.Header.Set("If-Modified-Since", s.lastModified.Format(http.TimeFormat))
 
@@ -147,8 +126,6 @@ func (s *S3TestSuite) TestRoundTripWithIfModifiedSinceReturns304() {
 }
 
 func (s *S3TestSuite) TestRoundTripWithUpdatedLastModifiedReturns200() {
-	config.LastModifiedEnabled = true
-
 	request, _ := http.NewRequest("GET", "s3://test/foo/test.png", nil)
 	request.Header.Set("If-Modified-Since", s.lastModified.Add(-24*time.Hour).Format(http.TimeFormat))
 

+ 52 - 0
transport/swift/config.go

@@ -0,0 +1,52 @@
+package swift
+
+import (
+	"time"
+
+	"github.com/imgproxy/imgproxy/v3/config"
+)
+
+// Config holds the configuration for Swift transport
+type Config struct {
+	Username       string        // Username for Swift authentication
+	APIKey         string        // API key for Swift authentication
+	AuthURL        string        // Authentication URL for Swift
+	Domain         string        // Domain for Swift authentication
+	Tenant         string        // Tenant for Swift authentication
+	AuthVersion    int           // Authentication version for Swift
+	ConnectTimeout time.Duration // Connection timeout for Swift
+	Timeout        time.Duration // Request timeout for Swift
+}
+
+// NewDefaultConfig returns a new default configuration for Swift transport
+func NewDefaultConfig() *Config {
+	return &Config{
+		Username:       "",
+		APIKey:         "",
+		AuthURL:        "",
+		Domain:         "",
+		Tenant:         "",
+		AuthVersion:    0,
+		ConnectTimeout: 10 * time.Second,
+		Timeout:        60 * time.Second,
+	}
+}
+
+// LoadFromEnv loads configuration from the global config package
+func LoadFromEnv(c *Config) (*Config, error) {
+	c.Username = config.SwiftUsername
+	c.APIKey = config.SwiftAPIKey
+	c.AuthURL = config.SwiftAuthURL
+	c.Domain = config.SwiftDomain
+	c.Tenant = config.SwiftTenant
+	c.AuthVersion = config.SwiftAuthVersion
+	c.ConnectTimeout = time.Duration(config.SwiftConnectTimeoutSeconds) * time.Second
+	c.Timeout = time.Duration(config.SwiftTimeoutSeconds) * time.Second
+
+	return c, nil
+}
+
+// Validate checks the configuration for errors
+func (c *Config) Validate() error {
+	return nil
+}

+ 11 - 14
transport/swift/swift.go

@@ -6,14 +6,12 @@ import (
 	"io"
 	"net/http"
 	"strings"
-	"time"
 
 	"github.com/ncw/swift/v2"
 
 	"github.com/imgproxy/imgproxy/v3/config"
 	"github.com/imgproxy/imgproxy/v3/ierrors"
 	"github.com/imgproxy/imgproxy/v3/transport/common"
-	"github.com/imgproxy/imgproxy/v3/transport/generichttp"
 	"github.com/imgproxy/imgproxy/v3/transport/notmodified"
 )
 
@@ -21,27 +19,26 @@ type transport struct {
 	con *swift.Connection
 }
 
-func New() (http.RoundTripper, error) {
-	trans, err := generichttp.New(false)
-	if err != nil {
+func New(config *Config, trans *http.Transport) (http.RoundTripper, error) {
+	if err := config.Validate(); err != nil {
 		return nil, err
 	}
 
 	c := &swift.Connection{
-		UserName:       config.SwiftUsername,
-		ApiKey:         config.SwiftAPIKey,
-		AuthUrl:        config.SwiftAuthURL,
-		AuthVersion:    config.SwiftAuthVersion,
-		Domain:         config.SwiftDomain, // v3 auth only
-		Tenant:         config.SwiftTenant, // v2 auth only
-		Timeout:        time.Duration(config.SwiftTimeoutSeconds) * time.Second,
-		ConnectTimeout: time.Duration(config.SwiftConnectTimeoutSeconds) * time.Second,
+		UserName:       config.Username,
+		ApiKey:         config.APIKey,
+		AuthUrl:        config.AuthURL,
+		AuthVersion:    config.AuthVersion,
+		Domain:         config.Domain, // v3 auth only
+		Tenant:         config.Tenant, // v2 auth only
+		Timeout:        config.Timeout,
+		ConnectTimeout: config.ConnectTimeout,
 		Transport:      trans,
 	}
 
 	ctx := context.Background()
 
-	err = c.Authenticate(ctx)
+	err := c.Authenticate(ctx)
 
 	if err != nil {
 		return nil, ierrors.Wrap(err, 0, ierrors.WithPrefix("swift authentication error"))

+ 19 - 43
transport/swift/swift_test.go

@@ -10,7 +10,7 @@ import (
 	"github.com/ncw/swift/v2/swifttest"
 	"github.com/stretchr/testify/suite"
 
-	"github.com/imgproxy/imgproxy/v3/config"
+	"github.com/imgproxy/imgproxy/v3/transport/generichttp"
 )
 
 const (
@@ -29,26 +29,32 @@ type SwiftTestSuite struct {
 func (s *SwiftTestSuite) SetupSuite() {
 	s.server, _ = swifttest.NewSwiftServer("localhost")
 
-	config.Reset()
+	config := NewDefaultConfig()
 
-	config.SwiftAuthURL = s.server.AuthURL
-	config.SwiftUsername = swifttest.TEST_ACCOUNT
-	config.SwiftAPIKey = swifttest.TEST_ACCOUNT
-	config.SwiftAuthVersion = 1
+	config.AuthURL = s.server.AuthURL
+	config.Username = swifttest.TEST_ACCOUNT
+	config.APIKey = swifttest.TEST_ACCOUNT
+	config.AuthVersion = 1
 
-	s.setupTestFile()
+	s.setupTestFile(config)
+
+	tc := generichttp.NewDefaultConfig()
+	tc.IgnoreSslVerification = true
+
+	trans, gerr := generichttp.New(false, tc)
+	s.Require().NoError(gerr)
 
 	var err error
-	s.transport, err = New()
+	s.transport, err = New(config, trans)
 	s.Require().NoError(err, "failed to initialize swift transport")
 }
 
-func (s *SwiftTestSuite) setupTestFile() {
+func (s *SwiftTestSuite) setupTestFile(config *Config) {
 	c := &swift.Connection{
-		UserName:    config.SwiftUsername,
-		ApiKey:      config.SwiftAPIKey,
-		AuthUrl:     config.SwiftAuthURL,
-		AuthVersion: config.SwiftAuthVersion,
+		UserName:    config.Username,
+		ApiKey:      config.APIKey,
+		AuthUrl:     config.AuthURL,
+		AuthVersion: config.AuthVersion,
 	}
 
 	ctx := context.Background()
@@ -83,17 +89,7 @@ func (s *SwiftTestSuite) TearDownSuite() {
 	s.server.Close()
 }
 
-func (s *SwiftTestSuite) TestRoundTripWithETagDisabledReturns200() {
-	config.ETagEnabled = false
-	request, _ := http.NewRequest("GET", "swift://test/foo/test.png", nil)
-
-	response, err := s.transport.RoundTrip(request)
-	s.Require().NoError(err)
-	s.Require().Equal(200, response.StatusCode)
-}
-
 func (s *SwiftTestSuite) TestRoundTripReturns404WhenObjectNotFound() {
-	config.ETagEnabled = true
 	request, _ := http.NewRequest("GET", "swift://test/foo/not-here.png", nil)
 
 	response, err := s.transport.RoundTrip(request)
@@ -102,7 +98,6 @@ func (s *SwiftTestSuite) TestRoundTripReturns404WhenObjectNotFound() {
 }
 
 func (s *SwiftTestSuite) TestRoundTripReturns404WhenContainerNotFound() {
-	config.ETagEnabled = true
 	request, _ := http.NewRequest("GET", "swift://invalid/foo/test.png", nil)
 
 	response, err := s.transport.RoundTrip(request)
@@ -111,7 +106,6 @@ func (s *SwiftTestSuite) TestRoundTripReturns404WhenContainerNotFound() {
 }
 
 func (s *SwiftTestSuite) TestRoundTripWithETagEnabled() {
-	config.ETagEnabled = true
 	request, _ := http.NewRequest("GET", "swift://test/foo/test.png", nil)
 
 	response, err := s.transport.RoundTrip(request)
@@ -121,8 +115,6 @@ func (s *SwiftTestSuite) TestRoundTripWithETagEnabled() {
 }
 
 func (s *SwiftTestSuite) TestRoundTripWithIfNoneMatchReturns304() {
-	config.ETagEnabled = true
-
 	request, _ := http.NewRequest("GET", "swift://test/foo/test.png", nil)
 	request.Header.Set("If-None-Match", s.etag)
 
@@ -132,8 +124,6 @@ func (s *SwiftTestSuite) TestRoundTripWithIfNoneMatchReturns304() {
 }
 
 func (s *SwiftTestSuite) TestRoundTripWithUpdatedETagReturns200() {
-	config.ETagEnabled = true
-
 	request, _ := http.NewRequest("GET", "swift://test/foo/test.png", nil)
 	request.Header.Set("If-None-Match", s.etag+"_wrong")
 
@@ -142,17 +132,7 @@ func (s *SwiftTestSuite) TestRoundTripWithUpdatedETagReturns200() {
 	s.Require().Equal(http.StatusOK, response.StatusCode)
 }
 
-func (s *SwiftTestSuite) TestRoundTripWithLastModifiedDisabledReturns200() {
-	config.LastModifiedEnabled = false
-	request, _ := http.NewRequest("GET", "swift://test/foo/test.png", nil)
-
-	response, err := s.transport.RoundTrip(request)
-	s.Require().NoError(err)
-	s.Require().Equal(200, response.StatusCode)
-}
-
 func (s *SwiftTestSuite) TestRoundTripWithLastModifiedEnabled() {
-	config.LastModifiedEnabled = true
 	request, _ := http.NewRequest("GET", "swift://test/foo/test.png", nil)
 
 	response, err := s.transport.RoundTrip(request)
@@ -162,8 +142,6 @@ func (s *SwiftTestSuite) TestRoundTripWithLastModifiedEnabled() {
 }
 
 func (s *SwiftTestSuite) TestRoundTripWithIfModifiedSinceReturns304() {
-	config.LastModifiedEnabled = true
-
 	request, _ := http.NewRequest("GET", "swift://test/foo/test.png", nil)
 	request.Header.Set("If-Modified-Since", s.lastModified.Format(http.TimeFormat))
 
@@ -173,8 +151,6 @@ func (s *SwiftTestSuite) TestRoundTripWithIfModifiedSinceReturns304() {
 }
 
 func (s *SwiftTestSuite) TestRoundTripWithUpdatedLastModifiedReturns200() {
-	config.LastModifiedEnabled = true
-
 	request, _ := http.NewRequest("GET", "swift://test/foo/test.png", nil)
 	request.Header.Set("If-Modified-Since", s.lastModified.Add(-24*time.Hour).Format(http.TimeFormat))
 

+ 26 - 16
transport/transport.go

@@ -5,7 +5,6 @@ package transport
 import (
 	"net/http"
 
-	"github.com/imgproxy/imgproxy/v3/config"
 	"github.com/imgproxy/imgproxy/v3/transport/generichttp"
 
 	azureTransport "github.com/imgproxy/imgproxy/v3/transport/azure"
@@ -17,13 +16,18 @@ import (
 
 // Transport is a wrapper around http.Transport which allows to track registered protocols
 type Transport struct {
+	config    *Config
 	transport *http.Transport
 	schemes   map[string]struct{}
 }
 
-// NewTransport creates a new HTTP transport with no protocols registered
-func NewTransport() (*Transport, error) {
-	transport, err := generichttp.New(true)
+// New creates a new HTTP transport with no protocols registered
+func New(config *Config) (*Transport, error) {
+	if err := config.Validate(); err != nil {
+		return nil, err
+	}
+
+	transport, err := generichttp.New(true, config.HTTP)
 	if err != nil {
 		return nil, err
 	}
@@ -35,8 +39,9 @@ func NewTransport() (*Transport, error) {
 	}
 
 	t := &Transport{
-		transport,
-		schemes,
+		config:    config,
+		transport: transport,
+		schemes:   schemes,
 	}
 
 	err = t.registerAllProtocols()
@@ -66,36 +71,41 @@ func (t *Transport) IsProtocolRegistered(scheme string) bool {
 
 // RegisterAllProtocols registers all enabled protocols in the given transport
 func (t *Transport) registerAllProtocols() error {
-	if config.LocalFileSystemRoot != "" {
-		t.RegisterProtocol("local", fsTransport.New())
+	transp, err := generichttp.New(false, t.config.HTTP)
+	if err != nil {
+		return err
+	}
+
+	if t.config.Local.Root != "" {
+		t.RegisterProtocol("local", fsTransport.New(t.config.Local))
 	}
 
-	if config.S3Enabled {
-		if tr, err := s3Transport.New(); err != nil {
+	if t.config.S3Enabled {
+		if tr, err := s3Transport.New(t.config.S3, transp); err != nil {
 			return err
 		} else {
 			t.RegisterProtocol("s3", tr)
 		}
 	}
 
-	if config.GCSEnabled {
-		if tr, err := gcsTransport.New(); err != nil {
+	if t.config.GCSEnabled {
+		if tr, err := gcsTransport.New(t.config.GCS, transp); err != nil {
 			return err
 		} else {
 			t.RegisterProtocol("gs", tr)
 		}
 	}
 
-	if config.ABSEnabled {
-		if tr, err := azureTransport.New(); err != nil {
+	if t.config.ABSEnabled {
+		if tr, err := azureTransport.New(t.config.ABS, transp); err != nil {
 			return err
 		} else {
 			t.RegisterProtocol("abs", tr)
 		}
 	}
 
-	if config.SwiftEnabled {
-		if tr, err := swiftTransport.New(); err != nil {
+	if t.config.SwiftEnabled {
+		if tr, err := swiftTransport.New(t.config.Swift, transp); err != nil {
 			return err
 		} else {
 			t.RegisterProtocol("swift", tr)