人人商城

slice_uploading.php 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. <?php
  2. namespace qcloudcos;
  3. require_once(__DIR__ . DIRECTORY_SEPARATOR . 'conf.php');
  4. require_once(__DIR__ . DIRECTORY_SEPARATOR . 'error_code.php');
  5. require_once(__DIR__ . DIRECTORY_SEPARATOR . 'http_client.php');
  6. require_once(__DIR__ . DIRECTORY_SEPARATOR . 'libcurl_helper.php');
  7. require_once(__DIR__ . DIRECTORY_SEPARATOR . 'libcurl_wrapper.php');
  8. /**
  9. * Uploading file to cos slice by slice.
  10. */
  11. class SliceUploading {
  12. // default task number for concurrently uploading slices.
  13. const DEFAULT_CONCURRENT_TASK_NUMBER = 3;
  14. private $timeoutMs; // int: timeout in milliseconds for each http request.
  15. private $maxRetryCount; // int: max retry count on failure.
  16. private $errorCode; // int: last error code.
  17. private $errorMessage; // string: last error message.
  18. private $requestId; // string: request id for last http request.
  19. private $signature; // string: signature for auth.
  20. private $srcFpath; // string: source file path for uploading.
  21. private $url; // string: destination url for uploading.
  22. private $fileSize; // int: source file size.
  23. private $sliceSize; // int: slice size for each upload.
  24. private $session; // string: session for each upload transaction.
  25. private $concurrentTaskNumber; // int: concurrent uploading task number.
  26. private $offset; // int: current uploading offset.
  27. private $libcurlWrapper; // LibcurlWrapper: curl wrapper for sending multi http request concurrently.
  28. private $accessUrl; // string: access url.
  29. private $resourcePath; // string: resource path.
  30. private $sourceUrl; // string: source url.
  31. /**
  32. * timeoutMs: max timeout in milliseconds for each http request.
  33. * maxRetryCount: max retry count for uploading each slice on error.
  34. */
  35. public function __construct($timeoutMs, $maxRetryCount) {
  36. $this->timeoutMs = $timeoutMs;
  37. $this->maxRetryCount = $maxRetryCount;
  38. $this->errorCode = COSAPI_SUCCESS;
  39. $this->errorMessage = '';
  40. $this->concurrentTaskNumber = self::DEFAULT_CONCURRENT_TASK_NUMBER;
  41. $this->offset = 0;
  42. $this->libcurlWrapper = new LibcurlWrapper();
  43. }
  44. public function __destruct() {
  45. }
  46. public function getLastErrorCode() {
  47. return $this->errorCode;
  48. }
  49. public function getLastErrorMessage() {
  50. return $this->errorMessage;
  51. }
  52. public function getRequestId() {
  53. return $this->requestId;
  54. }
  55. public function getAccessUrl() {
  56. return $this->accessUrl;
  57. }
  58. public function getResourcePath() {
  59. return $this->resourcePath;
  60. }
  61. public function getSourceUrl() {
  62. return $this->sourceUrl;
  63. }
  64. /**
  65. * Return true on success and return false on failure.
  66. */
  67. public function initUploading(
  68. $signature, $srcFpath, $url, $fileSize, $sliceSize, $bizAttr, $insertOnly) {
  69. $this->signature = $signature;
  70. $this->srcFpath = $srcFpath;
  71. $this->url = $url;
  72. $this->fileSize = $fileSize;
  73. $this->sliceSize = $sliceSize;
  74. // Clear error so caller can successfully retry.
  75. $this->clearError();
  76. $request = array(
  77. 'url' => $url,
  78. 'method' => 'post',
  79. 'timeout' => $this->timeoutMs / 1000,
  80. 'data' => array(
  81. 'op' => 'upload_slice_init',
  82. 'filesize' => $fileSize,
  83. 'slice_size' => $sliceSize,
  84. 'insertOnly' => $insertOnly,
  85. ),
  86. 'header' => array(
  87. 'Authorization: ' . $signature,
  88. ),
  89. );
  90. if (isset($bizAttr) && strlen($bizAttr)) {
  91. $request['data']['biz_attr'] = $bizAttr;
  92. }
  93. $response = $this->sendRequest($request);
  94. if ($response === false) {
  95. return false;
  96. }
  97. $this->session = $response['data']['session'];
  98. if (isset($response['data']['slice_size'])) {
  99. $this->sliceSize = $response['data']['slice_size'];
  100. }
  101. if (isset($response['data']['serial_upload']) && $response['data']['serial_upload'] == 1) {
  102. $this->concurrentTaskNumber = 1;
  103. }
  104. return true;
  105. }
  106. /**
  107. * Return true on success and return false on failure.
  108. */
  109. public function performUploading() {
  110. for ($i = 0; $i < $this->concurrentTaskNumber; ++$i) {
  111. if ($this->offset >= $this->fileSize) {
  112. break;
  113. }
  114. $sliceContent = file_get_contents($this->srcFpath, false, null, $this->offset, $this->sliceSize);
  115. if ($sliceContent === false) {
  116. $this->setError(COSAPI_PARAMS_ERROR, 'read file ' . $this->srcFpath . ' error');
  117. return false;
  118. }
  119. $request = new HttpRequest();
  120. $request->timeoutMs = $this->timeoutMs;
  121. $request->url = $this->url;
  122. $request->method = 'POST';
  123. $request->customHeaders = array(
  124. 'Authorization: ' . $this->signature,
  125. );
  126. $request->dataToPost = array(
  127. 'op' => 'upload_slice_data',
  128. 'session' => $this->session,
  129. 'offset' => $this->offset,
  130. 'filecontent' => $sliceContent,
  131. 'datamd5' => md5($sliceContent),
  132. );
  133. $request->userData = array(
  134. 'retryCount' => 0,
  135. );
  136. $this->libcurlWrapper->startSendingRequest($request, array($this, 'uploadCallback'));
  137. $this->offset += $this->sliceSize;
  138. }
  139. $this->libcurlWrapper->performSendingRequest();
  140. if ($this->errorCode !== COSAPI_SUCCESS) {
  141. return false;
  142. }
  143. return true;
  144. }
  145. /**
  146. * Return true on success and return false on failure.
  147. */
  148. public function finishUploading() {
  149. $request = array(
  150. 'url' => $this->url,
  151. 'method' => 'post',
  152. 'timeout' => $this->timeoutMs / 1000,
  153. 'data' => array(
  154. 'op' => 'upload_slice_finish',
  155. 'session' => $this->session,
  156. 'filesize' => $this->fileSize,
  157. ),
  158. 'header' => array(
  159. 'Authorization: ' . $this->signature,
  160. ),
  161. );
  162. $response = $this->sendRequest($request);
  163. if ($response === false) {
  164. return false;
  165. }
  166. $this->accessUrl = $response['data']['access_url'];
  167. $this->resourcePath = $response['data']['resource_path'];
  168. $this->sourceUrl = $response['data']['source_url'];
  169. return true;
  170. }
  171. private function sendRequest($request) {
  172. $response = HttpClient::sendRequest($request);
  173. if ($response === false) {
  174. $this->setError(COSAPI_NETWORK_ERROR, 'network error');
  175. return false;
  176. }
  177. $responseJson = json_decode($response, true);
  178. if ($responseJson === NULL) {
  179. $this->setError(COSAPI_NETWORK_ERROR, 'network error');
  180. return false;
  181. }
  182. $this->requestId = $responseJson['request_id'];
  183. if ($responseJson['code'] != 0) {
  184. $this->setError($responseJson['code'], $responseJson['message']);
  185. return false;
  186. }
  187. return $responseJson;
  188. }
  189. private function clearError() {
  190. $this->errorCode = COSAPI_SUCCESS;
  191. $this->errorMessage = 'success';
  192. }
  193. private function setError($errorCode, $errorMessage) {
  194. $this->errorCode = $errorCode;
  195. $this->errorMessage = $errorMessage;
  196. }
  197. public function uploadCallback($request, $response) {
  198. if ($this->errorCode !== COSAPI_SUCCESS) {
  199. return;
  200. }
  201. $requestErrorCode = COSAPI_SUCCESS;
  202. $requestErrorMessage = 'success';
  203. $retryCount = $request->userData['retryCount'];
  204. $responseJson = json_decode($response->body, true);
  205. if ($responseJson === NULL) {
  206. $requestErrorCode = COSAPI_NETWORK_ERROR;
  207. $requestErrorMessage = 'network error';
  208. }
  209. if ($response->curlErrorCode !== CURLE_OK) {
  210. $requestErrorCode = COSAPI_NETWORK_ERROR;
  211. $requestErrorMessage = 'network error: curl errno ' . $response->curlErrorCode;
  212. }
  213. $this->requestId = $responseJson['request_id'];
  214. if ($responseJson['code'] != 0) {
  215. $requestErrorCode = $responseJson['code'];
  216. $requestErrorMessage = $responseJson['message'];
  217. }
  218. if (isset($responseJson['data']['datamd5']) &&
  219. $responseJson['data']['datamd5'] !== $request->dataToPost['datamd5']) {
  220. $requestErrorCode = COSAPI_INTEGRITY_ERROR;
  221. $requestErrorMessage = 'cosapi integrity error';
  222. }
  223. if ($requestErrorCode !== COSAPI_SUCCESS) {
  224. if ($retryCount >= $this->maxRetryCount) {
  225. $this->setError($requestErrorCode, $requestErrorMessage);
  226. } else {
  227. $request->userData['retryCount'] += 1;
  228. $this->libcurlWrapper->startSendingRequest($request, array($this, 'uploadCallback'));
  229. }
  230. return;
  231. }
  232. if ($this->offset >= $this->fileSize) {
  233. return;
  234. }
  235. // Send next slice.
  236. $nextSliceContent = file_get_contents($this->srcFpath, false, null, $this->offset, $this->sliceSize);
  237. if ($nextSliceContent === false) {
  238. $this->setError(COSAPI_PARAMS_ERROR, 'read file ' . $this->srcFpath . ' error');
  239. return;
  240. }
  241. $nextSliceRequest = new HttpRequest();
  242. $nextSliceRequest->timeoutMs = $this->timeoutMs;
  243. $nextSliceRequest->url = $this->url;
  244. $nextSliceRequest->method = 'POST';
  245. $nextSliceRequest->customHeaders = array(
  246. 'Authorization: ' . $this->signature,
  247. );
  248. $nextSliceRequest->dataToPost = array(
  249. 'op' => 'upload_slice_data',
  250. 'session' => $this->session,
  251. 'offset' => $this->offset,
  252. 'filecontent' => $nextSliceContent,
  253. 'datamd5' => md5($nextSliceContent),
  254. );
  255. $nextSliceRequest->userData = array(
  256. 'retryCount' => 0,
  257. );
  258. $this->libcurlWrapper->startSendingRequest($nextSliceRequest, array($this, 'uploadCallback'));
  259. $this->offset += $this->sliceSize;
  260. }
  261. }