github.com/qiniu/go-sdk/v7@v7.27.0/storagev2/uploader/form_uploader_test.go (about)

     1  //go:build unit
     2  // +build unit
     3  
     4  package uploader
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"crypto/md5"
    10  	"io"
    11  	"math/rand"
    12  	"net/http"
    13  	"net/http/httptest"
    14  	"os"
    15  	"strings"
    16  	"sync/atomic"
    17  	"testing"
    18  	"time"
    19  
    20  	"github.com/qiniu/go-sdk/v7/client"
    21  	"github.com/qiniu/go-sdk/v7/storagev2/credentials"
    22  	"github.com/qiniu/go-sdk/v7/storagev2/http_client"
    23  	"github.com/qiniu/go-sdk/v7/storagev2/region"
    24  )
    25  
    26  func TestFormUploader(t *testing.T) {
    27  	tmpFile, err := os.CreateTemp("", "form-uploader-test-*")
    28  	if err != nil {
    29  		t.Fatal(err)
    30  	}
    31  	defer os.Remove(tmpFile.Name())
    32  	defer tmpFile.Close()
    33  
    34  	r := rand.New(rand.NewSource(time.Now().UnixNano()))
    35  	hasher := md5.New()
    36  	if _, err = io.CopyN(tmpFile, io.TeeReader(r, hasher), 1024*1024); err != nil {
    37  		t.Fatal(err)
    38  	}
    39  	if _, err = tmpFile.Seek(0, io.SeekStart); err != nil {
    40  		t.Fatal(err)
    41  	}
    42  	expectedMd5 := hasher.Sum(nil)
    43  
    44  	serveMux := http.NewServeMux()
    45  	serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    46  		if r.Method != http.MethodPost {
    47  			t.Fatalf("unexpected method: %s", r.Method)
    48  		}
    49  		if err := r.ParseMultipartForm(2 * 1024 * 1024); err != nil {
    50  			t.Fatal(err)
    51  		}
    52  		if values := r.MultipartForm.Value["key"]; len(values) != 1 || values[0] != "testkey" {
    53  			t.Fatalf("unexpected key")
    54  		}
    55  		if values := r.MultipartForm.Value["token"]; len(values) != 1 || !strings.HasPrefix(values[0], "testak:") {
    56  			t.Fatalf("unexpected token")
    57  		}
    58  		if values := r.MultipartForm.Value["x-qn-meta-a"]; len(values) != 1 || values[0] != "b" {
    59  			t.Fatalf("unexpected x-qn-meta-a")
    60  		}
    61  		if values := r.MultipartForm.Value["x-qn-meta-c"]; len(values) != 1 || values[0] != "d" {
    62  			t.Fatalf("unexpected x-qn-meta-c")
    63  		}
    64  		if values := r.MultipartForm.Value["x:a"]; len(values) != 1 || values[0] != "b" {
    65  			t.Fatalf("unexpected x:a")
    66  		}
    67  		if values := r.MultipartForm.Value["x:c"]; len(values) != 1 || values[0] != "d" {
    68  			t.Fatalf("unexpected x:c")
    69  		}
    70  		if files := r.MultipartForm.File["file"]; len(files) != 1 || files[0].Filename != "testfilename" || files[0].Size != 1024*1024 {
    71  			t.Fatalf("unexpected file")
    72  		} else if contentType := files[0].Header.Get("Content-Type"); contentType != "application/json" {
    73  			t.Fatalf("unexpected file content-type")
    74  		} else if file, err := files[0].Open(); err != nil {
    75  			t.Fatal(err)
    76  		} else {
    77  			defer file.Close()
    78  			hasher := md5.New()
    79  			if _, err = io.Copy(hasher, file); err != nil {
    80  				t.Fatal(err)
    81  			}
    82  			if !bytes.Equal(hasher.Sum(nil), expectedMd5) {
    83  				t.Fatalf("unexpected file content")
    84  			}
    85  		}
    86  		w.Header().Add("x-reqid", "fakereqid")
    87  		w.Write([]byte(`{"ok":true}`))
    88  	})
    89  	server := httptest.NewServer(serveMux)
    90  	defer server.Close()
    91  
    92  	formUploader := NewFormUploader(&FormUploaderOptions{
    93  		Options: http_client.Options{
    94  			Regions:     &region.Region{Up: region.Endpoints{Preferred: []string{server.URL}}},
    95  			Credentials: credentials.NewCredentials("testak", "testsk"),
    96  		},
    97  	})
    98  	var (
    99  		returnValue struct {
   100  			Ok bool `json:"ok"`
   101  		}
   102  		key          = "testkey"
   103  		lastUploaded uint64
   104  	)
   105  	if err = formUploader.UploadFile(context.Background(), tmpFile.Name(), &ObjectOptions{
   106  		BucketName:  "testbucket",
   107  		ObjectName:  &key,
   108  		FileName:    "testfilename",
   109  		ContentType: "application/json",
   110  		Metadata:    map[string]string{"a": "b", "c": "d"},
   111  		CustomVars:  map[string]string{"a": "b", "c": "d"},
   112  		OnUploadingProgress: func(progress *UploadingProgress) {
   113  			if progress.TotalSize != 1024*1024 {
   114  				t.Fatalf("unexpected file size")
   115  			} else if progress.Uploaded > progress.TotalSize {
   116  				t.Fatalf("unexpected uploaded")
   117  			} else if lu := atomic.SwapUint64(&lastUploaded, progress.Uploaded); lu > progress.Uploaded || lu > progress.TotalSize {
   118  				t.Fatalf("unexpected uploaded")
   119  			}
   120  		},
   121  	}, &returnValue); err != nil {
   122  		t.Fatal(err)
   123  	}
   124  	if !returnValue.Ok {
   125  		t.Fatalf("unexpected response value")
   126  	}
   127  }
   128  
   129  func TestFormUploaderRetry(t *testing.T) {
   130  	tmpFile, err := os.CreateTemp("", "form-uploader-test-*")
   131  	if err != nil {
   132  		t.Fatal(err)
   133  	}
   134  	defer os.Remove(tmpFile.Name())
   135  	defer tmpFile.Close()
   136  
   137  	r := rand.New(rand.NewSource(time.Now().UnixNano()))
   138  	hasher := md5.New()
   139  	if _, err = io.CopyN(tmpFile, io.TeeReader(r, hasher), 1024*1024); err != nil {
   140  		t.Fatal(err)
   141  	}
   142  	if _, err = tmpFile.Seek(0, io.SeekStart); err != nil {
   143  		t.Fatal(err)
   144  	}
   145  	expectedMd5 := hasher.Sum(nil)
   146  
   147  	var handlerCalled_1, handlerCalled_2, handlerCalled_3 uint64
   148  
   149  	serveMux_1 := http.NewServeMux()
   150  	serveMux_1.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
   151  		atomic.AddUint64(&handlerCalled_1, 1)
   152  		if r.Method != http.MethodPost {
   153  			t.Fatalf("unexpected method: %s", r.Method)
   154  		}
   155  		if err := r.ParseMultipartForm(2 * 1024 * 1024); err != nil {
   156  			t.Fatal(err)
   157  		}
   158  		if values := r.MultipartForm.Value["key"]; len(values) != 1 || values[0] != "testkey" {
   159  			t.Fatalf("unexpected key")
   160  		}
   161  		if values := r.MultipartForm.Value["token"]; len(values) != 1 || !strings.HasPrefix(values[0], "testak:") {
   162  			t.Fatalf("unexpected token")
   163  		}
   164  		if values := r.MultipartForm.Value["x-qn-meta-a"]; len(values) != 1 || values[0] != "b" {
   165  			t.Fatalf("unexpected x-qn-meta-a")
   166  		}
   167  		if values := r.MultipartForm.Value["x-qn-meta-c"]; len(values) != 1 || values[0] != "d" {
   168  			t.Fatalf("unexpected x-qn-meta-c")
   169  		}
   170  		if values := r.MultipartForm.Value["x:a"]; len(values) != 1 || values[0] != "b" {
   171  			t.Fatalf("unexpected x:a")
   172  		}
   173  		if values := r.MultipartForm.Value["x:c"]; len(values) != 1 || values[0] != "d" {
   174  			t.Fatalf("unexpected x:c")
   175  		}
   176  		if files := r.MultipartForm.File["file"]; len(files) != 1 || files[0].Filename != "testfilename" || files[0].Size != 1024*1024 {
   177  			t.Fatalf("unexpected file")
   178  		} else if contentType := files[0].Header.Get("Content-Type"); contentType != "application/json" {
   179  			t.Fatalf("unexpected file content-type")
   180  		} else if file, err := files[0].Open(); err != nil {
   181  			t.Fatal(err)
   182  		} else {
   183  			defer file.Close()
   184  			hasher := md5.New()
   185  			if _, err = io.Copy(hasher, file); err != nil {
   186  				t.Fatal(err)
   187  			}
   188  			if !bytes.Equal(hasher.Sum(nil), expectedMd5) {
   189  				t.Fatalf("unexpected file content")
   190  			}
   191  		}
   192  		w.Header().Add("x-reqid", "fakereqid")
   193  		w.Write([]byte(`{"ok":true}`))
   194  	})
   195  	server_1 := httptest.NewServer(serveMux_1)
   196  	defer server_1.Close()
   197  
   198  	serveMux_2 := http.NewServeMux()
   199  	serveMux_2.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
   200  		atomic.AddUint64(&handlerCalled_2, 1)
   201  		if r.Method != http.MethodPost {
   202  			t.Fatalf("unexpected method: %s", r.Method)
   203  		}
   204  		w.Header().Add("x-reqid", "fakereqid")
   205  		w.WriteHeader(599)
   206  	})
   207  	server_2 := httptest.NewServer(serveMux_2)
   208  	defer server_2.Close()
   209  
   210  	serveMux_3 := http.NewServeMux()
   211  	serveMux_3.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
   212  		atomic.AddUint64(&handlerCalled_3, 1)
   213  		if r.Method != http.MethodPost {
   214  			t.Fatalf("unexpected method: %s", r.Method)
   215  		}
   216  		w.Header().Add("x-reqid", "fakereqid")
   217  		w.WriteHeader(504)
   218  	})
   219  	server_3 := httptest.NewServer(serveMux_3)
   220  	defer server_3.Close()
   221  
   222  	handlerCalled_4 := uint64(0)
   223  	serveMux_4 := http.NewServeMux()
   224  	serveMux_4.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
   225  		atomic.AddUint64(&handlerCalled_4, 1)
   226  		if r.Method != http.MethodPost {
   227  			t.Fatalf("unexpected method: %s", r.Method)
   228  		}
   229  		w.Header().Add("x-reqid", "fakereqid")
   230  		w.WriteHeader(612)
   231  	})
   232  	server_4 := httptest.NewServer(serveMux_4)
   233  	defer server_4.Close()
   234  
   235  	var (
   236  		returnValue struct {
   237  			Ok bool `json:"ok"`
   238  		}
   239  		key = "testkey"
   240  	)
   241  
   242  	formUploader := NewFormUploader(&FormUploaderOptions{
   243  		Options: http_client.Options{
   244  			Regions: regions{[]*region.Region{
   245  				{Up: region.Endpoints{Preferred: []string{server_3.URL}}},
   246  				{Up: region.Endpoints{Preferred: []string{server_2.URL}}},
   247  				{Up: region.Endpoints{Preferred: []string{server_1.URL}}},
   248  			}},
   249  			Credentials: credentials.NewCredentials("testak", "testsk"),
   250  		},
   251  	})
   252  	if err = formUploader.UploadFile(context.Background(), tmpFile.Name(), &ObjectOptions{
   253  		BucketName:  "testbucket",
   254  		ObjectName:  &key,
   255  		FileName:    "testfilename",
   256  		ContentType: "application/json",
   257  		Metadata:    map[string]string{"a": "b", "c": "d"},
   258  		CustomVars:  map[string]string{"a": "b", "c": "d"},
   259  	}, &returnValue); err != nil {
   260  		t.Fatal(err)
   261  	}
   262  	if !returnValue.Ok {
   263  		t.Fatalf("unexpected response value")
   264  	}
   265  	if count := atomic.LoadUint64(&handlerCalled_1); count != 1 {
   266  		t.Fatalf("unexpected handler call count: %d", count)
   267  	}
   268  	if count := atomic.LoadUint64(&handlerCalled_2); count != 4 {
   269  		t.Fatalf("unexpected handler call count: %d", count)
   270  	}
   271  	if count := atomic.LoadUint64(&handlerCalled_3); count != 4 {
   272  		t.Fatalf("unexpected handler call count: %d", count)
   273  	}
   274  	atomic.StoreUint64(&handlerCalled_1, 0)
   275  	atomic.StoreUint64(&handlerCalled_2, 0)
   276  	atomic.StoreUint64(&handlerCalled_3, 0)
   277  
   278  	formUploader = NewFormUploader(&FormUploaderOptions{
   279  		Options: http_client.Options{
   280  			Regions: regions{[]*region.Region{
   281  				{Up: region.Endpoints{Preferred: []string{server_3.URL}}},
   282  				{Up: region.Endpoints{Preferred: []string{server_2.URL}}},
   283  				{Up: region.Endpoints{Preferred: []string{server_4.URL}}},
   284  			}},
   285  			Credentials: credentials.NewCredentials("testak", "testsk"),
   286  		},
   287  	})
   288  	if err = formUploader.UploadFile(context.Background(), tmpFile.Name(), &ObjectOptions{
   289  		RegionsProvider: nil,
   290  		BucketName:      "testbucket",
   291  		ObjectName:      &key,
   292  		FileName:        "testfilename",
   293  		ContentType:     "application/json",
   294  		Metadata:        map[string]string{"a": "b", "c": "d"},
   295  		CustomVars:      map[string]string{"a": "b", "c": "d"},
   296  	}, &returnValue); err != nil {
   297  		if errInfo, ok := err.(*client.ErrorInfo); !ok || errInfo.Code != 612 {
   298  			t.Fatal(err)
   299  		}
   300  	}
   301  	if count := atomic.LoadUint64(&handlerCalled_4); count != 1 {
   302  		t.Fatalf("unexpected handler call count: %d", count)
   303  	}
   304  	if count := atomic.LoadUint64(&handlerCalled_2); count != 4 {
   305  		t.Fatalf("unexpected handler call count: %d", count)
   306  	}
   307  	if count := atomic.LoadUint64(&handlerCalled_3); count != 4 {
   308  		t.Fatalf("unexpected handler call count: %d", count)
   309  	}
   310  }
   311  
   312  func TestFormUploaderAccelaratedUploading(t *testing.T) {
   313  	tmpFile, err := os.CreateTemp("", "form-uploader-test-*")
   314  	if err != nil {
   315  		t.Fatal(err)
   316  	}
   317  	defer os.Remove(tmpFile.Name())
   318  	defer tmpFile.Close()
   319  
   320  	r := rand.New(rand.NewSource(time.Now().UnixNano()))
   321  	hasher := md5.New()
   322  	if _, err = io.CopyN(tmpFile, io.TeeReader(r, hasher), 1024*1024); err != nil {
   323  		t.Fatal(err)
   324  	}
   325  	if _, err = tmpFile.Seek(0, io.SeekStart); err != nil {
   326  		t.Fatal(err)
   327  	}
   328  	expectedMd5 := hasher.Sum(nil)
   329  
   330  	var handlerCalled_1, handlerCalled_2 uint64
   331  
   332  	serveMux_1 := http.NewServeMux()
   333  	serveMux_1.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
   334  		atomic.AddUint64(&handlerCalled_1, 1)
   335  		if r.Method != http.MethodPost {
   336  			t.Fatalf("unexpected method: %s", r.Method)
   337  		}
   338  		if err := r.ParseMultipartForm(2 * 1024 * 1024); err != nil {
   339  			t.Fatal(err)
   340  		}
   341  		if values := r.MultipartForm.Value["key"]; len(values) != 1 || values[0] != "testkey" {
   342  			t.Fatalf("unexpected key")
   343  		}
   344  		if values := r.MultipartForm.Value["token"]; len(values) != 1 || !strings.HasPrefix(values[0], "testak:") {
   345  			t.Fatalf("unexpected token")
   346  		}
   347  		if values := r.MultipartForm.Value["x-qn-meta-a"]; len(values) != 1 || values[0] != "b" {
   348  			t.Fatalf("unexpected x-qn-meta-a")
   349  		}
   350  		if values := r.MultipartForm.Value["x-qn-meta-c"]; len(values) != 1 || values[0] != "d" {
   351  			t.Fatalf("unexpected x-qn-meta-c")
   352  		}
   353  		if values := r.MultipartForm.Value["x:a"]; len(values) != 1 || values[0] != "b" {
   354  			t.Fatalf("unexpected x:a")
   355  		}
   356  		if values := r.MultipartForm.Value["x:c"]; len(values) != 1 || values[0] != "d" {
   357  			t.Fatalf("unexpected x:c")
   358  		}
   359  		if files := r.MultipartForm.File["file"]; len(files) != 1 || files[0].Filename != "testfilename" || files[0].Size != 1024*1024 {
   360  			t.Fatalf("unexpected file")
   361  		} else if contentType := files[0].Header.Get("Content-Type"); contentType != "application/json" {
   362  			t.Fatalf("unexpected file content-type")
   363  		} else if file, err := files[0].Open(); err != nil {
   364  			t.Fatal(err)
   365  		} else {
   366  			defer file.Close()
   367  			hasher := md5.New()
   368  			if _, err = io.Copy(hasher, file); err != nil {
   369  				t.Fatal(err)
   370  			}
   371  			if !bytes.Equal(hasher.Sum(nil), expectedMd5) {
   372  				t.Fatalf("unexpected file content")
   373  			}
   374  		}
   375  		w.Header().Add("x-reqid", "fakereqid")
   376  		w.Write([]byte(`{"ok":true}`))
   377  	})
   378  	server_1 := httptest.NewServer(serveMux_1)
   379  	defer server_1.Close()
   380  
   381  	serveMux_2 := http.NewServeMux()
   382  	serveMux_2.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
   383  		atomic.AddUint64(&handlerCalled_2, 1)
   384  		if r.Method != http.MethodPost {
   385  			t.Fatalf("unexpected method: %s", r.Method)
   386  		}
   387  		if err := r.ParseMultipartForm(2 * 1024 * 1024); err != nil {
   388  			t.Fatal(err)
   389  		}
   390  		w.Header().Set("Content-Type", "application/json")
   391  		w.Header().Add("x-reqid", "fakereqid")
   392  		w.WriteHeader(http.StatusBadRequest)
   393  		w.Write([]byte(`{"error":"transfer acceleration is not configured on this bucket"}`))
   394  	})
   395  	server_2 := httptest.NewServer(serveMux_2)
   396  	defer server_2.Close()
   397  
   398  	var (
   399  		returnValue struct {
   400  			Ok bool `json:"ok"`
   401  		}
   402  		key = "testkey"
   403  	)
   404  
   405  	formUploader := NewFormUploader(&FormUploaderOptions{
   406  		Options: http_client.Options{
   407  			Regions: regions{[]*region.Region{
   408  				{Up: region.Endpoints{Accelerated: []string{server_2.URL}, Preferred: []string{server_1.URL}}},
   409  			}},
   410  			Credentials: credentials.NewCredentials("testak", "testsk"),
   411  		},
   412  	})
   413  	if err = formUploader.UploadFile(context.Background(), tmpFile.Name(), &ObjectOptions{
   414  		BucketName:  "testbucket",
   415  		ObjectName:  &key,
   416  		FileName:    "testfilename",
   417  		ContentType: "application/json",
   418  		Metadata:    map[string]string{"a": "b", "c": "d"},
   419  		CustomVars:  map[string]string{"a": "b", "c": "d"},
   420  	}, &returnValue); err != nil {
   421  		t.Fatal(err)
   422  	}
   423  	if !returnValue.Ok {
   424  		t.Fatalf("unexpected response value")
   425  	}
   426  	if count := atomic.LoadUint64(&handlerCalled_1); count != 1 {
   427  		t.Fatalf("unexpected handler call count: %d", count)
   428  	}
   429  	if count := atomic.LoadUint64(&handlerCalled_2); count != 1 {
   430  		t.Fatalf("unexpected handler call count: %d", count)
   431  	}
   432  }
   433  
   434  type regions struct {
   435  	regions []*region.Region
   436  }
   437  
   438  func (group regions) GetRegions(context.Context) ([]*region.Region, error) {
   439  	return group.regions, nil
   440  }