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.

1164 lines
31 KiB

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