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: ®ion.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 }