You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1053 lines
27 KiB

4 years ago
4 years ago
  1. package cos
  2. import (
  3. "bytes"
  4. "context"
  5. "crypto/rand"
  6. "encoding/hex"
  7. "encoding/xml"
  8. "errors"
  9. "fmt"
  10. "hash/crc64"
  11. "io"
  12. "io/ioutil"
  13. math_rand "math/rand"
  14. "net"
  15. "net/http"
  16. "net/url"
  17. "os"
  18. "reflect"
  19. "strconv"
  20. "strings"
  21. "testing"
  22. "time"
  23. )
  24. func TestObjectService_Get(t *testing.T) {
  25. setup()
  26. defer teardown()
  27. name := "test/hello.txt"
  28. contentLength := 1024 * 1024 * 10
  29. data := make([]byte, contentLength)
  30. mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) {
  31. testMethod(t, r, "GET")
  32. vs := values{
  33. "response-content-type": "text/html",
  34. }
  35. testFormValues(t, r, vs)
  36. strRange := r.Header.Get("Range")
  37. slice1 := strings.Split(strRange, "=")
  38. slice2 := strings.Split(slice1[1], "-")
  39. start, _ := strconv.ParseInt(slice2[0], 10, 64)
  40. end, _ := strconv.ParseInt(slice2[1], 10, 64)
  41. io.Copy(w, bytes.NewBuffer(data[start:end+1]))
  42. })
  43. for i := 0; i < 3; i++ {
  44. math_rand.Seed(time.Now().UnixNano())
  45. rangeStart := math_rand.Intn(contentLength)
  46. rangeEnd := rangeStart + math_rand.Intn(contentLength-rangeStart)
  47. if rangeEnd == rangeStart || rangeStart >= contentLength-1 {
  48. continue
  49. }
  50. opt := &ObjectGetOptions{
  51. ResponseContentType: "text/html",
  52. Range: fmt.Sprintf("bytes=%v-%v", rangeStart, rangeEnd),
  53. }
  54. resp, err := client.Object.Get(context.Background(), name, opt)
  55. if err != nil {
  56. t.Fatalf("Object.Get returned error: %v", err)
  57. }
  58. b, _ := ioutil.ReadAll(resp.Body)
  59. if bytes.Compare(b, data[rangeStart:rangeEnd+1]) != 0 {
  60. t.Errorf("Object.Get Failed")
  61. }
  62. }
  63. }
  64. func TestObjectService_GetToFile(t *testing.T) {
  65. setup()
  66. defer teardown()
  67. name := "test/hello.txt"
  68. data := make([]byte, 1024*1024*10)
  69. rand.Read(data)
  70. mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) {
  71. testMethod(t, r, "GET")
  72. vs := values{
  73. "response-content-type": "text/html",
  74. }
  75. testFormValues(t, r, vs)
  76. testHeader(t, r, "Range", "bytes=0-3")
  77. io.Copy(w, bytes.NewReader(data))
  78. })
  79. opt := &ObjectGetOptions{
  80. ResponseContentType: "text/html",
  81. Range: "bytes=0-3",
  82. }
  83. filePath := "test.file" + time.Now().Format(time.RFC3339)
  84. _, err := client.Object.GetToFile(context.Background(), name, filePath, opt)
  85. if err != nil {
  86. t.Fatalf("Object.Get returned error: %v", err)
  87. }
  88. defer os.Remove(filePath)
  89. fd, err := os.Open(filePath)
  90. if err != nil {
  91. }
  92. defer fd.Close()
  93. bs, _ := ioutil.ReadAll(fd)
  94. if bytes.Compare(bs, data) != 0 {
  95. t.Errorf("Object.GetToFile data isn't consistent")
  96. }
  97. }
  98. func TestObjectService_GetRetry(t *testing.T) {
  99. setup()
  100. defer teardown()
  101. u, _ := url.Parse(server.URL)
  102. client := NewClient(&BaseURL{u, u, u, u}, &http.Client{
  103. Transport: &http.Transport{
  104. Proxy: http.ProxyFromEnvironment,
  105. DialContext: (&net.Dialer{
  106. Timeout: 30 * time.Second,
  107. KeepAlive: 30 * time.Second,
  108. DualStack: true,
  109. }).DialContext,
  110. IdleConnTimeout: 90 * time.Second,
  111. TLSHandshakeTimeout: 10 * time.Second,
  112. ExpectContinueTimeout: 1 * time.Second,
  113. ResponseHeaderTimeout: 1 * time.Second,
  114. },
  115. })
  116. name := "test/hello.txt"
  117. contentLength := 1024 * 1024 * 10
  118. data := make([]byte, contentLength)
  119. index := 0
  120. mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) {
  121. testMethod(t, r, "GET")
  122. vs := values{
  123. "response-content-type": "text/html",
  124. }
  125. index++
  126. if index%3 != 0 {
  127. time.Sleep(time.Second * 2)
  128. }
  129. testFormValues(t, r, vs)
  130. strRange := r.Header.Get("Range")
  131. slice1 := strings.Split(strRange, "=")
  132. slice2 := strings.Split(slice1[1], "-")
  133. start, _ := strconv.ParseInt(slice2[0], 10, 64)
  134. end, _ := strconv.ParseInt(slice2[1], 10, 64)
  135. io.Copy(w, bytes.NewBuffer(data[start:end+1]))
  136. })
  137. for i := 0; i < 3; i++ {
  138. math_rand.Seed(time.Now().UnixNano())
  139. rangeStart := math_rand.Intn(contentLength)
  140. rangeEnd := rangeStart + math_rand.Intn(contentLength-rangeStart)
  141. if rangeEnd == rangeStart || rangeStart >= contentLength-1 {
  142. continue
  143. }
  144. opt := &ObjectGetOptions{
  145. ResponseContentType: "text/html",
  146. Range: fmt.Sprintf("bytes=%v-%v", rangeStart, rangeEnd),
  147. }
  148. resp, err := client.Object.Get(context.Background(), name, opt)
  149. if err != nil {
  150. t.Fatalf("Object.Get returned error: %v", err)
  151. }
  152. b, _ := ioutil.ReadAll(resp.Body)
  153. if bytes.Compare(b, data[rangeStart:rangeEnd+1]) != 0 {
  154. t.Errorf("Object.Get Failed")
  155. }
  156. }
  157. }
  158. func TestObjectService_GetPresignedURL(t *testing.T) {
  159. setup()
  160. defer teardown()
  161. exceptSign := "q-sign-algorithm=sha1&q-ak=QmFzZTY0IGlzIGEgZ*******&q-sign-time=1622702557;1622706157&q-key-time=1622702557;1622706157&q-header-list=&q-url-param-list=&q-signature=0f359fe9d29e7fa0c738ce6c8feaf4ed1e84f287"
  162. exceptURL := &url.URL{
  163. Scheme: "http",
  164. Host: client.Host,
  165. Path: "/test.jpg",
  166. RawQuery: exceptSign,
  167. }
  168. c := context.Background()
  169. name := "test.jpg"
  170. ak := "QmFzZTY0IGlzIGEgZ*******"
  171. sk := "ZfbOA78asKUYBcXFrJD0a1I*******"
  172. startTime := time.Unix(int64(1622702557), 0)
  173. endTime := time.Unix(int64(1622706157), 0)
  174. opt := presignedURLTestingOptions{
  175. authTime: &AuthTime{
  176. SignStartTime: startTime,
  177. SignEndTime: endTime,
  178. KeyStartTime: startTime,
  179. KeyEndTime: endTime,
  180. },
  181. }
  182. presignedURL, err := client.Object.GetPresignedURL(c, http.MethodPut, name, ak, sk, time.Hour, opt)
  183. if err != nil {
  184. t.Fatal(err)
  185. }
  186. if reflect.DeepEqual(exceptURL, presignedURL) {
  187. t.Fatalf("Wrong PreSignedURL!")
  188. }
  189. }
  190. func TestObjectService_Put(t *testing.T) {
  191. setup()
  192. defer teardown()
  193. opt := &ObjectPutOptions{
  194. ObjectPutHeaderOptions: &ObjectPutHeaderOptions{
  195. ContentType: "text/html",
  196. },
  197. ACLHeaderOptions: &ACLHeaderOptions{
  198. XCosACL: "private",
  199. },
  200. }
  201. name := "test/hello.txt"
  202. retry := 0
  203. final := 10
  204. mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) {
  205. testMethod(t, r, http.MethodPut)
  206. testHeader(t, r, "x-cos-acl", "private")
  207. testHeader(t, r, "Content-Type", "text/html")
  208. if retry%2 == 0 {
  209. b, _ := ioutil.ReadAll(r.Body)
  210. tb := crc64.MakeTable(crc64.ECMA)
  211. crc := crc64.Update(0, tb, b)
  212. v := string(b)
  213. want := "hello"
  214. if !reflect.DeepEqual(v, want) {
  215. t.Errorf("Object.Put request body: %#v, want %#v", v, want)
  216. }
  217. realcrc := crc64.Update(0, tb, []byte("hello"))
  218. if !reflect.DeepEqual(crc, realcrc) {
  219. t.Errorf("Object.Put crc: %v, want: %v", crc, realcrc)
  220. }
  221. w.Header().Add("x-cos-hash-crc64ecma", strconv.FormatUint(crc, 10))
  222. if retry != final {
  223. w.WriteHeader(http.StatusGatewayTimeout)
  224. }
  225. } else {
  226. w.Header().Add("x-cos-hash-crc64ecma", "123456789")
  227. }
  228. })
  229. for retry <= final {
  230. r := bytes.NewReader([]byte("hello"))
  231. _, err := client.Object.Put(context.Background(), name, r, opt)
  232. if retry < final && err == nil {
  233. t.Fatalf("Error must not nil when retry < final")
  234. }
  235. if retry == final && err != nil {
  236. t.Fatalf("Put Error: %v", err)
  237. }
  238. retry++
  239. }
  240. }
  241. func TestObjectService_PutFromFile(t *testing.T) {
  242. setup()
  243. defer teardown()
  244. filePath := "tmpfile" + time.Now().Format(time.RFC3339)
  245. newfile, err := os.Create(filePath)
  246. if err != nil {
  247. t.Fatalf("create tmp file failed")
  248. }
  249. defer os.Remove(filePath)
  250. // 源文件内容
  251. b := make([]byte, 1024*1024*3)
  252. _, err = rand.Read(b)
  253. newfile.Write(b)
  254. newfile.Close()
  255. tb := crc64.MakeTable(crc64.ECMA)
  256. realcrc := crc64.Update(0, tb, b)
  257. opt := &ObjectPutOptions{
  258. ObjectPutHeaderOptions: &ObjectPutHeaderOptions{
  259. ContentType: "text/html",
  260. Listener: &DefaultProgressListener{},
  261. },
  262. ACLHeaderOptions: &ACLHeaderOptions{
  263. XCosACL: "private",
  264. },
  265. }
  266. name := "test/hello.txt"
  267. retry := 0
  268. final := 4
  269. mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) {
  270. testMethod(t, r, http.MethodPut)
  271. testHeader(t, r, "x-cos-acl", "private")
  272. testHeader(t, r, "Content-Type", "text/html")
  273. if retry%2 == 0 {
  274. bs, _ := ioutil.ReadAll(r.Body)
  275. crc := crc64.Update(0, tb, bs)
  276. if !reflect.DeepEqual(bs, b) {
  277. t.Errorf("Object.Put request body Error")
  278. }
  279. if !reflect.DeepEqual(crc, realcrc) {
  280. t.Errorf("Object.Put crc: %v, want: %v", crc, realcrc)
  281. }
  282. w.Header().Add("x-cos-hash-crc64ecma", strconv.FormatUint(crc, 10))
  283. if retry != final {
  284. w.WriteHeader(http.StatusGatewayTimeout)
  285. }
  286. } else {
  287. w.Header().Add("x-cos-hash-crc64ecma", "123456789")
  288. }
  289. })
  290. for retry <= final {
  291. _, err := client.Object.PutFromFile(context.Background(), name, filePath, opt)
  292. if retry < final && err == nil {
  293. t.Fatalf("Error must not nil when retry < final")
  294. }
  295. if retry == final && err != nil {
  296. t.Fatalf("Put Error: %v", err)
  297. }
  298. retry++
  299. }
  300. }
  301. func TestObjectService_Delete(t *testing.T) {
  302. setup()
  303. defer teardown()
  304. name := "test/hello.txt"
  305. mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) {
  306. testMethod(t, r, http.MethodDelete)
  307. w.WriteHeader(http.StatusNoContent)
  308. })
  309. _, err := client.Object.Delete(context.Background(), name)
  310. if err != nil {
  311. t.Fatalf("Object.Delete returned error: %v", err)
  312. }
  313. }
  314. func TestObjectService_Head(t *testing.T) {
  315. setup()
  316. defer teardown()
  317. name := "test/hello.txt"
  318. mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) {
  319. testMethod(t, r, "HEAD")
  320. testHeader(t, r, "If-Modified-Since", "Mon, 12 Jun 2017 05:36:19 GMT")
  321. })
  322. opt := &ObjectHeadOptions{
  323. IfModifiedSince: "Mon, 12 Jun 2017 05:36:19 GMT",
  324. }
  325. _, err := client.Object.Head(context.Background(), name, opt)
  326. if err != nil {
  327. t.Fatalf("Object.Head returned error: %v", err)
  328. }
  329. }
  330. func TestObjectService_Options(t *testing.T) {
  331. setup()
  332. defer teardown()
  333. name := "test/hello.txt"
  334. mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) {
  335. testMethod(t, r, http.MethodOptions)
  336. testHeader(t, r, "Access-Control-Request-Method", "PUT")
  337. testHeader(t, r, "Origin", "www.qq.com")
  338. })
  339. opt := &ObjectOptionsOptions{
  340. Origin: "www.qq.com",
  341. AccessControlRequestMethod: "PUT",
  342. }
  343. _, err := client.Object.Options(context.Background(), name, opt)
  344. if err != nil {
  345. t.Fatalf("Object.Options returned error: %v", err)
  346. }
  347. }
  348. func TestObjectService_PostRestore(t *testing.T) {
  349. setup()
  350. defer teardown()
  351. name := "test/hello.txt"
  352. wantBody := "<RestoreRequest><Days>3</Days><CASJobParameters><Tier>Expedited</Tier></CASJobParameters></RestoreRequest>"
  353. mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) {
  354. testMethod(t, r, http.MethodPost)
  355. testHeader(t, r, "Content-Type", "application/xml")
  356. testHeader(t, r, "Content-Length", "106")
  357. //b, _ := ioutil.ReadAll(r.Body)
  358. //fmt.Printf("%s", string(b))
  359. testBody(t, r, wantBody)
  360. })
  361. opt := &ObjectRestoreOptions{
  362. Days: 3,
  363. Tier: &CASJobParameters{
  364. Tier: "Expedited",
  365. },
  366. }
  367. _, err := client.Object.PostRestore(context.Background(), name, opt)
  368. if err != nil {
  369. t.Fatalf("Object.PostRestore returned error: %v", err)
  370. }
  371. }
  372. func TestObjectService_Append_Simple(t *testing.T) {
  373. setup()
  374. defer teardown()
  375. opt := &ObjectPutOptions{
  376. ObjectPutHeaderOptions: &ObjectPutHeaderOptions{
  377. ContentType: "text/html",
  378. },
  379. ACLHeaderOptions: &ACLHeaderOptions{
  380. XCosACL: "private",
  381. },
  382. }
  383. name := "test/hello.txt"
  384. position := 0
  385. mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) {
  386. vs := values{
  387. "append": "",
  388. "position": "0",
  389. }
  390. testFormValues(t, r, vs)
  391. testMethod(t, r, http.MethodPost)
  392. testHeader(t, r, "x-cos-acl", "private")
  393. testHeader(t, r, "Content-Type", "text/html")
  394. b, _ := ioutil.ReadAll(r.Body)
  395. v := string(b)
  396. want := "hello"
  397. if !reflect.DeepEqual(v, want) {
  398. t.Errorf("Object.Append request body: %#v, want %#v", v, want)
  399. }
  400. w.Header().Add("x-cos-content-sha1", hex.EncodeToString(calMD5Digest(b)))
  401. w.Header().Add("x-cos-next-append-position", strconv.FormatInt(int64(len(b)), 10))
  402. })
  403. r := bytes.NewReader([]byte("hello"))
  404. p, _, err := client.Object.Append(context.Background(), name, position, r, opt)
  405. if err != nil {
  406. t.Fatalf("Object.Append returned error: %v", err)
  407. }
  408. if p != len("hello") {
  409. t.Fatalf("Object.Append position error, want: %v, return: %v", len("hello"), p)
  410. }
  411. }
  412. func TestObjectService_DeleteMulti(t *testing.T) {
  413. setup()
  414. defer teardown()
  415. mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  416. testMethod(t, r, http.MethodPost)
  417. vs := values{
  418. "delete": "",
  419. }
  420. testFormValues(t, r, vs)
  421. fmt.Fprint(w, `<DeleteResult>
  422. <Deleted>
  423. <Key>test1</Key>
  424. </Deleted>
  425. <Deleted>
  426. <Key>test3</Key>
  427. </Deleted>
  428. <Deleted>
  429. <Key>test2</Key>
  430. </Deleted>
  431. </DeleteResult>`)
  432. })
  433. opt := &ObjectDeleteMultiOptions{
  434. Objects: []Object{
  435. {
  436. Key: "test1",
  437. },
  438. {
  439. Key: "test3",
  440. },
  441. {
  442. Key: "test2",
  443. },
  444. },
  445. }
  446. ref, _, err := client.Object.DeleteMulti(context.Background(), opt)
  447. if err != nil {
  448. t.Fatalf("Object.DeleteMulti returned error: %v", err)
  449. }
  450. want := &ObjectDeleteMultiResult{
  451. XMLName: xml.Name{Local: "DeleteResult"},
  452. DeletedObjects: []Object{
  453. {
  454. Key: "test1",
  455. },
  456. {
  457. Key: "test3",
  458. },
  459. {
  460. Key: "test2",
  461. },
  462. },
  463. }
  464. if !reflect.DeepEqual(ref, want) {
  465. t.Errorf("Object.DeleteMulti returned %+v, want %+v", ref, want)
  466. }
  467. }
  468. func TestObiectService_Read_and_Close(t *testing.T) {
  469. data := make([]byte, 1024*10)
  470. rand.Read(data)
  471. body := bytes.NewReader(data)
  472. r, _ := http.NewRequest(http.MethodGet, "test", body)
  473. drc := DiscardReadCloser{
  474. RC: r.Body,
  475. Discard: 10,
  476. }
  477. res := make([]byte, 1024*10)
  478. readLen, err := drc.Read(res)
  479. if err != nil {
  480. t.Fatalf("Object.Read returned %v", err)
  481. }
  482. if readLen != 10230 {
  483. t.Fatalf("Object.Read returned %#v, excepted %#v", readLen, 10230)
  484. }
  485. if drc.Discard != 0 {
  486. t.Fatalf("Object.Read: drc.Discard = %v, excepted %v", drc.Discard, 0)
  487. }
  488. if !reflect.DeepEqual(res[:10230], data[10:]) {
  489. t.Fatalf("Object.Read: Wrong data!")
  490. }
  491. err = drc.Close()
  492. if err != nil {
  493. t.Fatal(err)
  494. }
  495. }
  496. func TestObjectService_Copy(t *testing.T) {
  497. setup()
  498. defer teardown()
  499. mux.HandleFunc("/test.go.copy", func(w http.ResponseWriter, r *http.Request) {
  500. testMethod(t, r, http.MethodPut)
  501. fmt.Fprint(w, `<CopyObjectResult>
  502. <ETag>"098f6bcd4621d373cade4e832627b4f6"</ETag>
  503. <LastModified>2017-12-13T14:53:12</LastModified>
  504. </CopyObjectResult>`)
  505. })
  506. wrongURL := "wrongURL"
  507. _, _, err := client.Object.Copy(context.Background(), "test.go.copy", wrongURL, nil)
  508. exceptedErr := errors.New(fmt.Sprintf("x-cos-copy-source format error: %s", wrongURL))
  509. if !reflect.DeepEqual(err, exceptedErr) {
  510. t.Fatalf("Object.Copy returned %#v, excepted %#v", err, exceptedErr)
  511. }
  512. sourceURL := "test-1253846586.cos.ap-guangzhou.myqcloud.com/test.source"
  513. ref, _, err := client.Object.Copy(context.Background(), "test.go.copy", sourceURL, nil)
  514. if err != nil {
  515. t.Fatalf("Object.Copy returned error: %v", err)
  516. }
  517. want := &ObjectCopyResult{
  518. XMLName: xml.Name{Local: "CopyObjectResult"},
  519. ETag: `"098f6bcd4621d373cade4e832627b4f6"`,
  520. LastModified: "2017-12-13T14:53:12",
  521. }
  522. if !reflect.DeepEqual(ref, want) {
  523. t.Errorf("Object.Copy returned %+v, want %+v", ref, want)
  524. }
  525. }
  526. func TestObjectService_Append(t *testing.T) {
  527. setup()
  528. defer teardown()
  529. size := 1111 * 1111 * 63
  530. b := make([]byte, size)
  531. p := int(math_rand.Int31n(int32(size)))
  532. var buf bytes.Buffer
  533. mux.HandleFunc("/test.append", func(w http.ResponseWriter, r *http.Request) {
  534. testMethod(t, r, "POST")
  535. bs, _ := ioutil.ReadAll(r.Body)
  536. buf.Write(bs)
  537. w.Header().Add("x-cos-content-sha1", hex.EncodeToString(calMD5Digest(bs)))
  538. w.Header().Add("x-cos-next-append-position", strconv.FormatInt(int64(buf.Len()), 10))
  539. })
  540. pos, _, err := client.Object.Append(context.Background(), "test.append", 0, bytes.NewReader(b[:p]), nil)
  541. if err != nil {
  542. t.Fatalf("Object.Append return error %v", err)
  543. }
  544. if pos != p {
  545. t.Fatalf("Object.Append pos error, returned:%v, wanted:%v", pos, p)
  546. }
  547. opt := &ObjectPutOptions{
  548. ObjectPutHeaderOptions: &ObjectPutHeaderOptions{
  549. ContentType: "text/html",
  550. Listener: &DefaultProgressListener{},
  551. },
  552. ACLHeaderOptions: &ACLHeaderOptions{
  553. XCosACL: "private",
  554. },
  555. }
  556. pos, _, err = client.Object.Append(context.Background(), "test.append", pos, bytes.NewReader(b[p:]), opt)
  557. if err != nil {
  558. t.Fatalf("Object.Append return error %v", err)
  559. }
  560. if pos != size {
  561. t.Fatalf("Object.Append pos error, returned:%v, wanted:%v", pos, size)
  562. }
  563. if bytes.Compare(b, buf.Bytes()) != 0 {
  564. t.Fatalf("Object.Append Compare failed")
  565. }
  566. }
  567. func TestObjectService_Upload(t *testing.T) {
  568. setup()
  569. defer teardown()
  570. filePath := "tmpfile" + time.Now().Format(time.RFC3339)
  571. newfile, err := os.Create(filePath)
  572. if err != nil {
  573. t.Fatalf("create tmp file failed")
  574. }
  575. defer os.Remove(filePath)
  576. // 源文件内容
  577. b := make([]byte, 1024*1024*33)
  578. _, err = rand.Read(b)
  579. newfile.Write(b)
  580. newfile.Close()
  581. // 已上传内容, 10个分块
  582. rb := make([][]byte, 33)
  583. uploadid := "test-cos-multiupload-uploadid"
  584. partmap := make(map[int64]int)
  585. mux.HandleFunc("/test.go.upload", func(w http.ResponseWriter, r *http.Request) {
  586. if r.Method == http.MethodPut { // 分块上传
  587. r.ParseForm()
  588. part, _ := strconv.ParseInt(r.Form.Get("partNumber"), 10, 64)
  589. if partmap[part] == 0 {
  590. // 重试检验1
  591. partmap[part]++
  592. ioutil.ReadAll(r.Body)
  593. w.WriteHeader(http.StatusGatewayTimeout)
  594. } else if partmap[part] == 1 {
  595. // 重试校验2
  596. partmap[part]++
  597. w.Header().Add("x-cos-hash-crc64ecma", "123456789")
  598. } else { // 正确上传
  599. bs, _ := ioutil.ReadAll(r.Body)
  600. rb[part-1] = bs
  601. md := hex.EncodeToString(calMD5Digest(bs))
  602. crc := crc64.Update(0, crc64.MakeTable(crc64.ECMA), bs)
  603. w.Header().Add("ETag", md)
  604. w.Header().Add("x-cos-hash-crc64ecma", strconv.FormatUint(crc, 10))
  605. }
  606. } else {
  607. testMethod(t, r, http.MethodPost)
  608. initreq := url.Values{}
  609. initreq.Set("uploads", "")
  610. compreq := url.Values{}
  611. compreq.Set("uploadId", uploadid)
  612. r.ParseForm()
  613. if reflect.DeepEqual(r.Form, initreq) {
  614. // 初始化分块上传
  615. fmt.Fprintf(w, `<InitiateMultipartUploadResult>
  616. <Bucket></Bucket>
  617. <Key>%v</Key>
  618. <UploadId>%v</UploadId>
  619. </InitiateMultipartUploadResult>`, "test.go.upload", uploadid)
  620. } else if reflect.DeepEqual(r.Form, compreq) {
  621. // 完成分块上传
  622. tb := crc64.MakeTable(crc64.ECMA)
  623. crc := uint64(0)
  624. for _, v := range rb {
  625. crc = crc64.Update(crc, tb, v)
  626. }
  627. w.Header().Add("x-cos-hash-crc64ecma", strconv.FormatUint(crc, 10))
  628. fmt.Fprintf(w, `<CompleteMultipartUploadResult>
  629. <Location>/test.go.upload</Location>
  630. <Bucket></Bucket>
  631. <Key>test.go.upload</Key>
  632. <ETag>&quot;%v&quot;</ETag>
  633. </CompleteMultipartUploadResult>`, hex.EncodeToString(calMD5Digest(b)))
  634. } else {
  635. t.Errorf("TestObjectService_Upload Unknown Request")
  636. }
  637. }
  638. })
  639. opt := &MultiUploadOptions{
  640. ThreadPoolSize: 3,
  641. PartSize: 1,
  642. }
  643. _, _, err = client.Object.Upload(context.Background(), "test.go.upload", filePath, opt)
  644. if err != nil {
  645. t.Fatalf("Object.Upload returned error: %v", err)
  646. }
  647. }
  648. func TestObjectService_Upload2(t *testing.T) {
  649. setup()
  650. defer teardown()
  651. filePath := "tmpfile" + time.Now().Format(time.RFC3339)
  652. newfile, err := os.Create(filePath)
  653. if err != nil {
  654. t.Fatalf("create tmp file failed")
  655. }
  656. defer os.Remove(filePath)
  657. // 源文件内容
  658. b := make([]byte, 1024*1024*3)
  659. _, err = rand.Read(b)
  660. newfile.Write(b)
  661. newfile.Close()
  662. tb := crc64.MakeTable(crc64.ECMA)
  663. realcrc := crc64.Update(0, tb, b)
  664. name := "test/hello.txt"
  665. retry := 0
  666. final := 4
  667. mux.HandleFunc("/test/hello.txt", func(w http.ResponseWriter, r *http.Request) {
  668. testMethod(t, r, http.MethodPut)
  669. testHeader(t, r, "x-cos-acl", "private")
  670. testHeader(t, r, "Content-Type", "text/html")
  671. if retry%2 == 0 {
  672. bs, _ := ioutil.ReadAll(r.Body)
  673. crc := crc64.Update(0, tb, bs)
  674. if !reflect.DeepEqual(bs, b) {
  675. t.Errorf("Object.Put request body Error")
  676. }
  677. if !reflect.DeepEqual(crc, realcrc) {
  678. t.Errorf("Object.Put crc: %v, want: %v", crc, realcrc)
  679. }
  680. w.Header().Add("x-cos-hash-crc64ecma", strconv.FormatUint(crc, 10))
  681. if retry != final {
  682. w.WriteHeader(http.StatusGatewayTimeout)
  683. }
  684. } else {
  685. w.Header().Add("x-cos-hash-crc64ecma", "123456789")
  686. }
  687. })
  688. mopt := &MultiUploadOptions{
  689. OptIni: &InitiateMultipartUploadOptions{
  690. ObjectPutHeaderOptions: &ObjectPutHeaderOptions{
  691. ContentType: "text/html",
  692. },
  693. ACLHeaderOptions: &ACLHeaderOptions{
  694. XCosACL: "private",
  695. },
  696. },
  697. }
  698. for retry <= final {
  699. _, _, err := client.Object.Upload(context.Background(), name, filePath, mopt)
  700. if retry < final && err == nil {
  701. t.Fatalf("Error must not nil when retry < final")
  702. }
  703. if retry == final && err != nil {
  704. t.Fatalf("Put Error: %v", err)
  705. }
  706. retry++
  707. }
  708. }
  709. func TestObjectService_Download(t *testing.T) {
  710. setup()
  711. defer teardown()
  712. filePath := "rsp.file" + time.Now().Format(time.RFC3339)
  713. newfile, err := os.Create(filePath)
  714. if err != nil {
  715. t.Fatalf("create tmp file failed")
  716. }
  717. defer os.Remove(filePath)
  718. // 源文件内容
  719. totalBytes := int64(1024*1024*9 + 1230)
  720. b := make([]byte, totalBytes)
  721. _, err = rand.Read(b)
  722. newfile.Write(b)
  723. newfile.Close()
  724. tb := crc64.MakeTable(crc64.ECMA)
  725. localcrc := strconv.FormatUint(crc64.Update(0, tb, b), 10)
  726. retryMap := make(map[int64]int)
  727. mux.HandleFunc("/test.go.download", func(w http.ResponseWriter, r *http.Request) {
  728. if r.Method == http.MethodHead {
  729. w.Header().Add("Content-Length", strconv.FormatInt(totalBytes, 10))
  730. w.Header().Add("x-cos-hash-crc64ecma", localcrc)
  731. return
  732. }
  733. strRange := r.Header.Get("Range")
  734. slice1 := strings.Split(strRange, "=")
  735. slice2 := strings.Split(slice1[1], "-")
  736. start, _ := strconv.ParseInt(slice2[0], 10, 64)
  737. end, _ := strconv.ParseInt(slice2[1], 10, 64)
  738. if retryMap[start] == 0 {
  739. // SDK 内部重试
  740. retryMap[start]++
  741. w.WriteHeader(http.StatusGatewayTimeout)
  742. } else if retryMap[start] == 1 {
  743. // SDK Download 做重试
  744. retryMap[start]++
  745. io.Copy(w, bytes.NewBuffer(b[start:end]))
  746. } else if retryMap[start] == 2 {
  747. // SDK Download 做重试
  748. retryMap[start]++
  749. st := start
  750. et := st + math_rand.Int63n(1024)
  751. io.Copy(w, bytes.NewBuffer(b[st:et+1]))
  752. } else {
  753. // SDK Download 成功
  754. io.Copy(w, bytes.NewBuffer(b[start:end+1]))
  755. }
  756. })
  757. opt := &MultiDownloadOptions{
  758. ThreadPoolSize: 3,
  759. PartSize: 1,
  760. }
  761. downPath := "down.file" + time.Now().Format(time.RFC3339)
  762. defer os.Remove(downPath)
  763. _, err = client.Object.Download(context.Background(), "test.go.download", downPath, opt)
  764. if err != nil {
  765. t.Fatalf("Object.Upload returned error: %v", err)
  766. }
  767. _, err = client.Object.Download(context.Background(), "test.go.download", downPath, opt)
  768. if err != nil {
  769. t.Fatalf("Object.Upload returned error: %v", err)
  770. }
  771. _, err = client.Object.Download(context.Background(), "test.go.download", downPath, opt)
  772. if err != nil {
  773. t.Fatalf("Object.Upload returned error: %v", err)
  774. }
  775. }
  776. func TestObjectService_DownloadWithCheckPoint(t *testing.T) {
  777. setup()
  778. defer teardown()
  779. filePath := "rsp.file" + time.Now().Format(time.RFC3339)
  780. newfile, err := os.Create(filePath)
  781. if err != nil {
  782. t.Fatalf("create tmp file failed")
  783. }
  784. defer os.Remove(filePath)
  785. // 源文件内容
  786. totalBytes := int64(1024*1024*9 + 123)
  787. partSize := 1024 * 1024
  788. b := make([]byte, totalBytes)
  789. _, err = rand.Read(b)
  790. newfile.Write(b)
  791. newfile.Close()
  792. tb := crc64.MakeTable(crc64.ECMA)
  793. localcrc := strconv.FormatUint(crc64.Update(0, tb, b), 10)
  794. oddok := false
  795. var oddcount, evencount int
  796. mux.HandleFunc("/test.go.download", func(w http.ResponseWriter, r *http.Request) {
  797. if r.Method == http.MethodHead {
  798. w.Header().Add("Content-Length", strconv.FormatInt(totalBytes, 10))
  799. w.Header().Add("x-cos-hash-crc64ecma", localcrc)
  800. return
  801. }
  802. strRange := r.Header.Get("Range")
  803. slice1 := strings.Split(strRange, "=")
  804. slice2 := strings.Split(slice1[1], "-")
  805. start, _ := strconv.ParseInt(slice2[0], 10, 64)
  806. end, _ := strconv.ParseInt(slice2[1], 10, 64)
  807. if (start/int64(partSize))%2 == 1 {
  808. if oddok {
  809. io.Copy(w, bytes.NewBuffer(b[start:end+1]))
  810. } else {
  811. // 数据校验失败, Download做3次重试
  812. io.Copy(w, bytes.NewBuffer(b[start:end]))
  813. }
  814. oddcount++
  815. } else {
  816. io.Copy(w, bytes.NewBuffer(b[start:end+1]))
  817. evencount++
  818. }
  819. })
  820. opt := &MultiDownloadOptions{
  821. ThreadPoolSize: 3,
  822. PartSize: 1,
  823. CheckPoint: true,
  824. }
  825. downPath := "down.file" + time.Now().Format(time.RFC3339)
  826. defer os.Remove(downPath)
  827. _, err = client.Object.Download(context.Background(), "test.go.download", downPath, opt)
  828. if err == nil {
  829. // 偶数块下载完成,奇数块下载失败
  830. t.Fatalf("Object.Download returned error: %v", err)
  831. }
  832. fd, err := os.Open(downPath)
  833. if err != nil {
  834. t.Fatalf("Object Download Open File Failed:%v", err)
  835. }
  836. offset := 0
  837. for i := 0; i < 10; i++ {
  838. bs, _ := ioutil.ReadAll(io.LimitReader(fd, int64(partSize)))
  839. offset += len(bs)
  840. if i%2 == 1 {
  841. bs[len(bs)-1] = b[offset-1]
  842. }
  843. if bytes.Compare(bs, b[i*partSize:offset]) != 0 {
  844. t.Fatalf("Compare Error, index:%v, len:%v, offset:%v", i, len(bs), offset)
  845. }
  846. }
  847. fd.Close()
  848. if oddcount != 15 || evencount != 5 {
  849. t.Fatalf("Object.Download failed, odd:%v, even:%v", oddcount, evencount)
  850. }
  851. // 设置奇数块OK
  852. oddok = true
  853. _, err = client.Object.Download(context.Background(), "test.go.download", downPath, opt)
  854. if err != nil {
  855. // 下载成功
  856. t.Fatalf("Object.Download returned error: %v", err)
  857. }
  858. if oddcount != 20 || evencount != 5 {
  859. t.Fatalf("Object.Download failed, odd:%v, even:%v", oddcount, evencount)
  860. }
  861. }
  862. func TestObjectService_GetTagging(t *testing.T) {
  863. setup()
  864. defer teardown()
  865. mux.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
  866. testMethod(t, r, "GET")
  867. vs := values{
  868. "tagging": "",
  869. }
  870. testFormValues(t, r, vs)
  871. fmt.Fprint(w, `<Tagging>
  872. <TagSet>
  873. <Tag>
  874. <Key>test_k2</Key>
  875. <Value>test_v2</Value>
  876. </Tag>
  877. <Tag>
  878. <Key>test_k3</Key>
  879. <Value>test_vv</Value>
  880. </Tag>
  881. </TagSet>
  882. </Tagging>`)
  883. })
  884. res, _, err := client.Object.GetTagging(context.Background(), "test")
  885. if err != nil {
  886. t.Fatalf("Object.GetTagging returned error %v", err)
  887. }
  888. want := &ObjectGetTaggingResult{
  889. XMLName: xml.Name{Local: "Tagging"},
  890. TagSet: []ObjectTaggingTag{
  891. {"test_k2", "test_v2"},
  892. {"test_k3", "test_vv"},
  893. },
  894. }
  895. if !reflect.DeepEqual(res, want) {
  896. t.Errorf("Object.GetTagging returned %+v, want %+v", res, want)
  897. }
  898. }
  899. func TestObjectService_PutTagging(t *testing.T) {
  900. setup()
  901. defer teardown()
  902. opt := &ObjectPutTaggingOptions{
  903. TagSet: []ObjectTaggingTag{
  904. {
  905. Key: "test_k2",
  906. Value: "test_v2",
  907. },
  908. {
  909. Key: "test_k3",
  910. Value: "test_v3",
  911. },
  912. },
  913. }
  914. mux.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
  915. v := new(ObjectPutTaggingOptions)
  916. xml.NewDecoder(r.Body).Decode(v)
  917. testMethod(t, r, "PUT")
  918. vs := values{
  919. "tagging": "",
  920. }
  921. testFormValues(t, r, vs)
  922. want := opt
  923. want.XMLName = xml.Name{Local: "Tagging"}
  924. if !reflect.DeepEqual(v, want) {
  925. t.Errorf("Object.PutTagging request body: %+v, want %+v", v, want)
  926. }
  927. })
  928. _, err := client.Object.PutTagging(context.Background(), "test", opt)
  929. if err != nil {
  930. t.Fatalf("Object.PutTagging returned error: %v", err)
  931. }
  932. }
  933. func TestObjectService_DeleteTagging(t *testing.T) {
  934. setup()
  935. defer teardown()
  936. mux.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
  937. testMethod(t, r, http.MethodDelete)
  938. vs := values{
  939. "tagging": "",
  940. }
  941. testFormValues(t, r, vs)
  942. w.WriteHeader(http.StatusNoContent)
  943. })
  944. _, err := client.Object.DeleteTagging(context.Background(), "test")
  945. if err != nil {
  946. t.Fatalf("Object.DeleteTagging returned error: %v", err)
  947. }
  948. }