diff --git a/client.go b/client.go index bbd5537..9ded0d5 100644 --- a/client.go +++ b/client.go @@ -22,11 +22,6 @@ type transport struct { } func (t transport) RoundTrip(request *http.Request) (*http.Response, error) { - for headerName, values := range t.header { - for _, val := range values { - request.Header.Add(headerName, val) - } - } request.URL = t.baseUrl.ResolveReference(request.URL) return http.DefaultTransport.RoundTrip(request) } @@ -80,6 +75,15 @@ func (c *Client) NewRequest(method, url string, body ...interface{}) (*http.Requ if err != nil { return nil, err } + + // Copy transport headers to request headers. + // This prevents concurrent modification of headers if you have multiple requests. + for k, headers := range c.clientTransport.header { + for _, v := range headers { + req.Header.Add(k, v) + } + } + return req, nil } diff --git a/go.mod b/go.mod index b4c7217..7f835d1 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module github.com/supabase-community/storage-go -go 1.17 +go 1.18 diff --git a/storage.go b/storage.go index fe703ec..059e3f6 100644 --- a/storage.go +++ b/storage.go @@ -29,18 +29,6 @@ func (c *Client) UploadOrUpdateFile( path := removeEmptyFolderName(bucketId + "/" + relativePath) uploadURL := c.clientTransport.baseUrl.String() + "/object/" + path - // Check on file options - if len(options) > 0 { - if options[0].CacheControl != nil { - c.clientTransport.header.Set("cache-control", *options[0].CacheControl) - } - if options[0].ContentType != nil { - c.clientTransport.header.Set("content-type", *options[0].ContentType) - } - if options[0].Upsert != nil { - c.clientTransport.header.Set("x-upsert", strconv.FormatBool(*options[0].Upsert)) - } - } method := http.MethodPost if update { method = http.MethodPut @@ -51,12 +39,22 @@ func (c *Client) UploadOrUpdateFile( return FileUploadResponse{}, err } + // Check on file options + if len(options) > 0 { + if options[0].CacheControl != nil { + req.Header.Set("cache-control", *options[0].CacheControl) + } + if options[0].ContentType != nil { + req.Header.Set("content-type", *options[0].ContentType) + } + if options[0].Upsert != nil { + req.Header.Set("x-upsert", strconv.FormatBool(*options[0].Upsert)) + } + } + var response FileUploadResponse _, err = c.Do(req, &response) - // set content-type back to default after request - c.clientTransport.header.Set("content-type", "application/json") - if err != nil { return FileUploadResponse{}, err } diff --git a/test/fileupload_test.go b/test/fileupload_test.go index c8bd272..c36f7f7 100644 --- a/test/fileupload_test.go +++ b/test/fileupload_test.go @@ -3,6 +3,7 @@ package test import ( "fmt" "os" + "sync" "testing" storage_go "github.com/supabase-community/storage-go" @@ -109,3 +110,31 @@ func TestDownloadFile(t *testing.T) { t.Fatalf("WriteFile failed: %v", err) } } + +// TestConcurrentFileUpload tests concurrent file uploads to a bucket. +// To correctly ensure this catches bugs run with the race detector. +func TestConcurrentFileUpload(t *testing.T) { + c := storage_go.NewClient(rawUrl, token, map[string]string{}) + file, _ := os.Open("dummy.txt") + defer file.Close() + + const concurrent = 8 + wg := sync.WaitGroup{} + wg.Add(concurrent) + + for i := 0; i < concurrent; i++ { + go func(index int) { + c.UploadFile("test", fmt.Sprintf("test%d.txt", index), file, storage_go.FileOptions{ + CacheControl: ptr("public, max-age=3600"), + ContentType: ptr("text/plain"), + Upsert: ptr(true), + }) + wg.Done() + }(i) + } + wg.Wait() +} + +func ptr[T any](v T) *T { + return &v +} diff --git a/test/storage_test.go b/test/storage_test.go index fe58ea1..9403127 100644 --- a/test/storage_test.go +++ b/test/storage_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/supabase-community/storage-go" + storage_go "github.com/supabase-community/storage-go" ) func TestBucketListAll(t *testing.T) {