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.

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