SendRequestTrait.php 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815
  1. <?php
  2. /**
  3. * Copyright 2019 Huawei Technologies Co.,Ltd.
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
  5. * this file except in compliance with the License. You may obtain a copy of the
  6. * License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software distributed
  11. * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
  12. * CONDITIONS OF ANY KIND, either express or implied. See the License for the
  13. * specific language governing permissions and limitations under the License.
  14. *
  15. */
  16. namespace Obs\Internal;
  17. use GuzzleHttp\Psr7;
  18. use GuzzleHttp\Client;
  19. use GuzzleHttp\Exception\ConnectException;
  20. use GuzzleHttp\Exception\RequestException;
  21. use GuzzleHttp\Psr7\Request;
  22. use GuzzleHttp\Psr7\Response;
  23. use Obs\Internal\Common\Model;
  24. use Obs\Internal\Resource\Constants;
  25. use Obs\Internal\Resource\V2Constants;
  26. use Obs\Internal\Signature\DefaultSignature;
  27. use Obs\Internal\Signature\V4Signature;
  28. use Obs\Log\ObsLog;
  29. use Obs\ObsException;
  30. use Obs\ObsClient;
  31. use Psr\Http\Message\StreamInterface;
  32. const INVALID_HTTP_METHOD_MSG =
  33. "Method param must be specified, allowed values: GET | PUT | HEAD | POST | DELETE | OPTIONS";
  34. trait SendRequestTrait
  35. {
  36. protected $ak;
  37. protected $sk;
  38. protected $securityToken = false;
  39. protected $endpoint = '';
  40. protected $pathStyle = false;
  41. protected $region = 'region';
  42. protected $signature = 'obs';
  43. protected $sslVerify = false;
  44. protected $maxRetryCount = 3;
  45. protected $timeout = 0;
  46. protected $socketTimeout = 60;
  47. protected $connectTimeout = 60;
  48. protected $isCname = false;
  49. /** @var Client */
  50. protected $httpClient;
  51. public function createSignedUrl(array $args = [])
  52. {
  53. if (strcasecmp($this->signature, 'v4') === 0) {
  54. return $this->createV4SignedUrl($args);
  55. }
  56. return $this->createCommonSignedUrl($this->signature, $args);
  57. }
  58. public function createV2SignedUrl(array $args = [])
  59. {
  60. return $this->createCommonSignedUrl('v2', $args);
  61. }
  62. private function createCommonSignedUrl(string $signature, array $args = [])
  63. {
  64. if (!isset($args['Method'])) {
  65. $obsException = new ObsException(INVALID_HTTP_METHOD_MSG);
  66. $obsException->setExceptionType('client');
  67. throw $obsException;
  68. }
  69. $method = strval($args['Method']);
  70. $bucketName = isset($args['Bucket']) ? strval($args['Bucket']) : null;
  71. $objectKey = isset($args['Key']) ? strval($args['Key']) : null;
  72. $specialParam = isset($args['SpecialParam']) ? strval($args['SpecialParam']) : null;
  73. $expires = isset($args['Expires']) && is_numeric($args['Expires']) ? intval($args['Expires']) : 300;
  74. $headers = [];
  75. if (isset($args['Headers']) && is_array($args['Headers'])) {
  76. foreach ($args['Headers'] as $key => $val) {
  77. if (is_string($key) && $key !== '') {
  78. $headers[$key] = $val;
  79. }
  80. }
  81. }
  82. $queryParams = [];
  83. if (isset($args['QueryParams']) && is_array($args['QueryParams'])) {
  84. foreach ($args['QueryParams'] as $key => $val) {
  85. if (is_string($key) && $key !== '') {
  86. $queryParams[$key] = $val;
  87. }
  88. }
  89. }
  90. $constants = Constants::selectConstants($signature);
  91. if ($this->securityToken && !isset($queryParams[$constants::SECURITY_TOKEN_HEAD])) {
  92. $queryParams[$constants::SECURITY_TOKEN_HEAD] = $this->securityToken;
  93. }
  94. $sign = new DefaultSignature(
  95. $this->ak,
  96. $this->sk,
  97. $this->pathStyle,
  98. $this->endpoint,
  99. $method,
  100. $this->signature,
  101. $this->securityToken,
  102. $this->isCname
  103. );
  104. $url = parse_url($this->endpoint);
  105. $host = $url['host'];
  106. $result = '';
  107. if ($bucketName) {
  108. if ($this->pathStyle) {
  109. $result = '/' . $bucketName;
  110. } else {
  111. $host = $this->isCname ? $host : $bucketName . '.' . $host;
  112. }
  113. }
  114. $headers['Host'] = $host;
  115. if ($objectKey) {
  116. $objectKey = $sign->urlencodeWithSafe($objectKey);
  117. $result .= '/' . $objectKey;
  118. }
  119. $result .= '?';
  120. if ($specialParam) {
  121. $queryParams[$specialParam] = '';
  122. }
  123. $queryParams[$constants::TEMPURL_AK_HEAD] = $this->ak;
  124. if (!is_numeric($expires) || $expires < 0) {
  125. $expires = 300;
  126. }
  127. $expires = intval($expires) + intval(microtime(true));
  128. $queryParams['Expires'] = strval($expires);
  129. $queryParamsResult = [];
  130. foreach ($queryParams as $key => $val) {
  131. $key = $sign->urlencodeWithSafe($key);
  132. $val = $sign->urlencodeWithSafe($val);
  133. $queryParamsResult[$key] = $val;
  134. $result .= $key;
  135. if ($val) {
  136. $result .= '=' . $val;
  137. }
  138. $result .= '&';
  139. }
  140. $canonicalstring = $sign->makeCanonicalstring(
  141. $method,
  142. $headers,
  143. $queryParamsResult,
  144. $bucketName,
  145. $objectKey,
  146. $expires
  147. );
  148. $signatureContent = base64_encode(hash_hmac('sha1', $canonicalstring, $this->sk, true));
  149. $result .= 'Signature=' . $sign->urlencodeWithSafe($signatureContent);
  150. $model = new Model();
  151. $model['ActualSignedRequestHeaders'] = $headers;
  152. $defaultPort = strtolower($url['scheme']) === 'https' ? '443' : '80';
  153. $port = isset($url['port']) ? $url['port'] : $defaultPort;
  154. $model['SignedUrl'] = $url['scheme'] . '://' . $host . ':' . $port . $result;
  155. return $model;
  156. }
  157. public function createV4SignedUrl(array $args = [])
  158. {
  159. if (!isset($args['Method'])) {
  160. $obsException = new ObsException(INVALID_HTTP_METHOD_MSG);
  161. $obsException->setExceptionType('client');
  162. throw $obsException;
  163. }
  164. $method = strval($args['Method']);
  165. $bucketName = isset($args['Bucket']) ? strval($args['Bucket']) : null;
  166. $objectKey = isset($args['Key']) ? strval($args['Key']) : null;
  167. $specialParam = isset($args['SpecialParam']) ? strval($args['SpecialParam']) : null;
  168. $expires = isset($args['Expires']) && is_numeric($args['Expires']) ? intval($args['Expires']) : 300;
  169. $headers = [];
  170. if (isset($args['Headers']) && is_array($args['Headers'])) {
  171. foreach ($args['Headers'] as $key => $val) {
  172. if (is_string($key) && $key !== '') {
  173. $headers[$key] = $val;
  174. }
  175. }
  176. }
  177. $queryParams = [];
  178. if (isset($args['QueryParams']) && is_array($args['QueryParams'])) {
  179. foreach ($args['QueryParams'] as $key => $val) {
  180. if (is_string($key) && $key !== '') {
  181. $queryParams[$key] = $val;
  182. }
  183. }
  184. }
  185. if ($this->securityToken && !isset($queryParams['x-amz-security-token'])) {
  186. $queryParams['x-amz-security-token'] = $this->securityToken;
  187. }
  188. $utcTimeZone = new \DateTimeZone('UTC');
  189. $v4 = new V4Signature(
  190. $this->ak,
  191. $this->sk,
  192. $this->pathStyle,
  193. $this->endpoint,
  194. $this->region,
  195. $method,
  196. $utcTimeZone,
  197. $this->signature,
  198. $this->securityToken,
  199. $this->isCname
  200. );
  201. $url = parse_url($this->endpoint);
  202. $host = $url['host'];
  203. $result = '';
  204. if ($bucketName) {
  205. if ($this->pathStyle) {
  206. $result = '/' . $bucketName;
  207. } else {
  208. $host = $this->isCname ? $host : $bucketName . '.' . $host;
  209. }
  210. }
  211. $headers['Host'] = $host;
  212. if ($objectKey) {
  213. $objectKey = $v4->urlencodeWithSafe($objectKey);
  214. $result .= '/' . $objectKey;
  215. }
  216. $result .= '?';
  217. if ($specialParam) {
  218. $queryParams[$specialParam] = '';
  219. }
  220. if (!is_numeric($expires) || $expires < 0) {
  221. $expires = 300;
  222. }
  223. $expires = strval($expires);
  224. $date = $headers['date'];
  225. if (!isset($date)) {
  226. $date = $headers['Date'];
  227. }
  228. if (!isset($date)) {
  229. $date = null;
  230. }
  231. $timestamp = time();
  232. if (isset($date)) {
  233. $timestamp = date_create_from_format('D, d M Y H:i:s \G\M\T', $date, new \DateTimeZone('UTC'))->getTimestamp();
  234. }
  235. $longDate = gmdate('Ymd\THis\Z', $timestamp);
  236. $shortDate = substr($longDate, 0, 8);
  237. $headers['host'] = $host;
  238. if (isset($url['port'])) {
  239. $port = $url['port'];
  240. if ($port !== 443 && $port !== 80) {
  241. $headers['host'] = $headers['host'] . ':' . $port;
  242. }
  243. }
  244. $signedHeaders = $v4->getSignedHeaders($headers);
  245. $queryParams['X-Amz-Algorithm'] = 'AWS4-HMAC-SHA256';
  246. $queryParams['X-Amz-Credential'] = $v4->getCredential($shortDate);
  247. $queryParams['X-Amz-Date'] = $longDate;
  248. $queryParams['X-Amz-Expires'] = $expires;
  249. $queryParams['X-Amz-SignedHeaders'] = $signedHeaders;
  250. $queryParamsResult = [];
  251. foreach ($queryParams as $key => $val) {
  252. $key = rawurlencode($key);
  253. $val = rawurlencode($val);
  254. $queryParamsResult[$key] = $val;
  255. $result .= $key;
  256. if ($val) {
  257. $result .= '=' . $val;
  258. }
  259. $result .= '&';
  260. }
  261. $canonicalstring = $v4->makeCanonicalstring(
  262. $method,
  263. $headers,
  264. $queryParamsResult,
  265. $bucketName,
  266. $objectKey,
  267. $signedHeaders,
  268. 'UNSIGNED-PAYLOAD'
  269. );
  270. $signatureContent = $v4->getSignature($canonicalstring, $longDate, $shortDate);
  271. $result .= 'X-Amz-Signature=' . $v4->urlencodeWithSafe($signatureContent);
  272. $model = new Model();
  273. $model['ActualSignedRequestHeaders'] = $headers;
  274. $defaultPort = strtolower($url['scheme']) === 'https' ? '443' : '80';
  275. $port = isset($url['port']) ? $url['port'] : $defaultPort;
  276. $model['SignedUrl'] = $url['scheme'] . '://' . $host . ':' . $port . $result;
  277. return $model;
  278. }
  279. public function createPostSignature(array $args = [])
  280. {
  281. if (strcasecmp($this->signature, 'v4') === 0) {
  282. return $this->createV4PostSignature($args);
  283. }
  284. $bucketName = isset($args['Bucket']) ? strval($args['Bucket']) : null;
  285. $objectKey = isset($args['Key']) ? strval($args['Key']) : null;
  286. $expires = isset($args['Expires']) && is_numeric($args['Expires']) ? intval($args['Expires']) : 300;
  287. $formParams = [];
  288. if (isset($args['FormParams']) && is_array($args['FormParams'])) {
  289. foreach ($args['FormParams'] as $key => $val) {
  290. $formParams[$key] = $val;
  291. }
  292. }
  293. $constants = Constants::selectConstants($this->signature);
  294. if ($this->securityToken && !isset($formParams[$constants::SECURITY_TOKEN_HEAD])) {
  295. $formParams[$constants::SECURITY_TOKEN_HEAD] = $this->securityToken;
  296. }
  297. $timestamp = time();
  298. $expires = gmdate('Y-m-d\TH:i:s\Z', $timestamp + $expires);
  299. if ($bucketName) {
  300. $formParams['bucket'] = $bucketName;
  301. }
  302. if ($objectKey) {
  303. $formParams['key'] = $objectKey;
  304. }
  305. $policy = [];
  306. $policy[] = '{"expiration":"';
  307. $policy[] = $expires;
  308. $policy[] = '", "conditions":[';
  309. $matchAnyBucket = true;
  310. $matchAnyKey = true;
  311. $conditionAllowKeys = ['acl', 'bucket', 'key', 'success_action_redirect', 'redirect', 'success_action_status'];
  312. foreach ($formParams as $key => $val) {
  313. if ($key) {
  314. $key = strtolower(strval($key));
  315. if ($key === 'bucket') {
  316. $matchAnyBucket = false;
  317. } elseif ($key === 'key') {
  318. $matchAnyKey = false;
  319. } else {
  320. // nothing handle
  321. }
  322. if (!in_array($key, Constants::ALLOWED_REQUEST_HTTP_HEADER_METADATA_NAMES)
  323. && strpos($key, $constants::HEADER_PREFIX) !== 0
  324. && !in_array($key, $conditionAllowKeys)
  325. ) {
  326. $key = $constants::METADATA_PREFIX . $key;
  327. }
  328. $policy[] = '{"';
  329. $policy[] = $key;
  330. $policy[] = '":"';
  331. $policy[] = $val !== null ? strval($val) : '';
  332. $policy[] = '"},';
  333. }
  334. }
  335. if ($matchAnyBucket) {
  336. $policy[] = '["starts-with", "$bucket", ""],';
  337. }
  338. if ($matchAnyKey) {
  339. $policy[] = '["starts-with", "$key", ""],';
  340. }
  341. $policy[] = ']}';
  342. $originPolicy = implode('', $policy);
  343. $policy = base64_encode($originPolicy);
  344. $signatureContent = base64_encode(hash_hmac('sha1', $policy, $this->sk, true));
  345. $model = new Model();
  346. $model['OriginPolicy'] = $originPolicy;
  347. $model['Policy'] = $policy;
  348. $model['Signature'] = $signatureContent;
  349. return $model;
  350. }
  351. public function createV4PostSignature(array $args = [])
  352. {
  353. $bucketName = isset($args['Bucket']) ? strval($args['Bucket']) : null;
  354. $objectKey = isset($args['Key']) ? strval($args['Key']) : null;
  355. $expires = isset($args['Expires']) && is_numeric($args['Expires']) ? intval($args['Expires']) : 300;
  356. $formParams = [];
  357. if (isset($args['FormParams']) && is_array($args['FormParams'])) {
  358. foreach ($args['FormParams'] as $key => $val) {
  359. $formParams[$key] = $val;
  360. }
  361. }
  362. if ($this->securityToken && !isset($formParams['x-amz-security-token'])) {
  363. $formParams['x-amz-security-token'] = $this->securityToken;
  364. }
  365. $timestamp = time();
  366. $longDate = gmdate('Ymd\THis\Z', $timestamp);
  367. $shortDate = substr($longDate, 0, 8);
  368. $credential = sprintf('%s/%s/%s/s3/aws4_request', $this->ak, $shortDate, $this->region);
  369. $expires = gmdate('Y-m-d\TH:i:s\Z', $timestamp + $expires);
  370. $formParams['X-Amz-Algorithm'] = 'AWS4-HMAC-SHA256';
  371. $formParams['X-Amz-Date'] = $longDate;
  372. $formParams['X-Amz-Credential'] = $credential;
  373. if ($bucketName) {
  374. $formParams['bucket'] = $bucketName;
  375. }
  376. if ($objectKey) {
  377. $formParams['key'] = $objectKey;
  378. }
  379. $policy = [];
  380. $policy[] = '{"expiration":"';
  381. $policy[] = $expires;
  382. $policy[] = '", "conditions":[';
  383. $matchAnyBucket = true;
  384. $matchAnyKey = true;
  385. $conditionAllowKeys = ['acl', 'bucket', 'key', 'success_action_redirect', 'redirect', 'success_action_status'];
  386. foreach ($formParams as $key => $val) {
  387. if ($key) {
  388. $key = strtolower(strval($key));
  389. if ($key === 'bucket') {
  390. $matchAnyBucket = false;
  391. }
  392. if ($key === 'key') {
  393. $matchAnyKey = false;
  394. }
  395. if (!in_array($key, Constants::ALLOWED_REQUEST_HTTP_HEADER_METADATA_NAMES)
  396. && strpos($key, V2Constants::HEADER_PREFIX) !== 0
  397. && !in_array($key, $conditionAllowKeys)
  398. ) {
  399. $key = V2Constants::METADATA_PREFIX . $key;
  400. }
  401. $policy[] = '{"';
  402. $policy[] = $key;
  403. $policy[] = '":"';
  404. $policy[] = $val !== null ? strval($val) : '';
  405. $policy[] = '"},';
  406. }
  407. }
  408. if ($matchAnyBucket) {
  409. $policy[] = '["starts-with", "$bucket", ""],';
  410. }
  411. if ($matchAnyKey) {
  412. $policy[] = '["starts-with", "$key", ""],';
  413. }
  414. $policy[] = ']}';
  415. $originPolicy = implode('', $policy);
  416. $policy = base64_encode($originPolicy);
  417. $dateKey = hash_hmac('sha256', $shortDate, 'AWS4' . $this->sk, true);
  418. $regionKey = hash_hmac('sha256', $this->region, $dateKey, true);
  419. $serviceKey = hash_hmac('sha256', 's3', $regionKey, true);
  420. $signingKey = hash_hmac('sha256', 'aws4_request', $serviceKey, true);
  421. $signatureContent = hash_hmac('sha256', $policy, $signingKey);
  422. $model = new Model();
  423. $model['OriginPolicy'] = $originPolicy;
  424. $model['Policy'] = $policy;
  425. $model['Algorithm'] = $formParams['X-Amz-Algorithm'];
  426. $model['Credential'] = $formParams['X-Amz-Credential'];
  427. $model['Date'] = $formParams['X-Amz-Date'];
  428. $model['Signature'] = $signatureContent;
  429. return $model;
  430. }
  431. public function __call($originMethod, $args)
  432. {
  433. $method = $originMethod;
  434. $contents = Constants::selectRequestResource($this->signature);
  435. $resource = &$contents::$resourceArray;
  436. $async = false;
  437. if (strpos($method, 'Async') === (strlen($method) - 5)) {
  438. $method = substr($method, 0, strlen($method) - 5);
  439. $async = true;
  440. }
  441. if (isset($resource['aliases'][$method])) {
  442. $method = $resource['aliases'][$method];
  443. }
  444. $method = lcfirst($method);
  445. $operation = isset($resource['operations'][$method]) ?
  446. $resource['operations'][$method] : null;
  447. if (!$operation) {
  448. ObsLog::commonLog(WARNING, 'unknow method ' . $originMethod);
  449. $obsException = new ObsException('unknow method ' . $originMethod);
  450. $obsException->setExceptionType('client');
  451. throw $obsException;
  452. }
  453. $start = microtime(true);
  454. if (!$async) {
  455. ObsLog::commonLog(INFO, 'enter method ' . $originMethod . '...');
  456. $model = new Model();
  457. $model['method'] = $method;
  458. $params = empty($args) ? [] : $args[0];
  459. $this->checkMimeType($method, $params);
  460. $this->doRequest($model, $operation, $params);
  461. ObsLog::commonLog(INFO, 'obsclient cost ' . round(microtime(true) - $start, 3) * 1000 . ' ms to execute ' . $originMethod);
  462. unset($model['method']);
  463. return $model;
  464. } else {
  465. if (empty($args) || !(is_callable($callback = $args[count($args) - 1]))) {
  466. ObsLog::commonLog(WARNING, 'async method ' . $originMethod . ' must pass a CallbackInterface as param');
  467. $obsException = new ObsException('async method ' . $originMethod . ' must pass a CallbackInterface as param');
  468. $obsException->setExceptionType('client');
  469. throw $obsException;
  470. }
  471. ObsLog::commonLog(INFO, 'enter method ' . $originMethod . '...');
  472. $params = count($args) === 1 ? [] : $args[0];
  473. $this->checkMimeType($method, $params);
  474. $model = new Model();
  475. $model['method'] = $method;
  476. return $this->doRequestAsync($model, $operation, $params, $callback, $start, $originMethod);
  477. }
  478. }
  479. private function hasContentType(&$params)
  480. {
  481. return isset($params['ContentType']) && $params['ContentType'] !== null;
  482. }
  483. private function checkMimeType($method, &$params)
  484. {
  485. // fix bug that guzzlehttp lib will add the content-type if not set
  486. $uploadMehods = array('putObject', 'initiateMultipartUpload', 'uploadPart');
  487. $hasContentTypeFlag = $this->hasContentType($params);
  488. if (in_array($method, $uploadMehods) && !$hasContentTypeFlag) {
  489. if (isset($params['Key'])) {
  490. try {
  491. $params['ContentType'] = Psr7\mimetype_from_filename($params['Key']);
  492. } catch (\Throwable $e) {
  493. $params['ContentType'] = Psr7\MimeType::fromFilename($params['Key']);
  494. }
  495. }
  496. if (!$hasContentTypeFlag && isset($params['SourceFile'])) {
  497. try {
  498. $params['ContentType'] = Psr7\mimetype_from_filename($params['SourceFile']);
  499. } catch (\Throwable $e) {
  500. $params['ContentType'] = Psr7\MimeType::fromFilename($params['SourceFile']);
  501. }
  502. }
  503. if (!$hasContentTypeFlag) {
  504. $params['ContentType'] = 'binary/octet-stream';
  505. }
  506. }
  507. }
  508. protected function makeRequest($model, &$operation, $params, $endpoint = null)
  509. {
  510. if ($endpoint === null) {
  511. $endpoint = $this->endpoint;
  512. }
  513. $utcTimeZone = new \DateTimeZone('UTC');
  514. $signatureInterface = strcasecmp($this->signature, 'v4') === 0
  515. ? new V4Signature(
  516. $this->ak,
  517. $this->sk,
  518. $this->pathStyle,
  519. $endpoint,
  520. $this->region,
  521. $model['method'],
  522. $this->signature,
  523. $utcTimeZone,
  524. $this->securityToken,
  525. $this->isCname
  526. )
  527. : new DefaultSignature(
  528. $this->ak,
  529. $this->sk,
  530. $this->pathStyle,
  531. $endpoint,
  532. $model['method'],
  533. $this->signature,
  534. $this->securityToken,
  535. $this->isCname
  536. );
  537. $authResult = $signatureInterface->doAuth($operation, $params, $model);
  538. $httpMethod = $authResult['method'];
  539. ObsLog::commonLog(DEBUG, 'perform ' . strtolower($httpMethod) . ' request with url ' . $authResult['requestUrl']);
  540. ObsLog::commonLog(DEBUG, 'cannonicalRequest:' . $authResult['cannonicalRequest']);
  541. ObsLog::commonLog(DEBUG, 'request headers ' . var_export($authResult['headers'], true));
  542. $authResult['headers']['User-Agent'] = ObsClient::getDefaultUserAgent();
  543. if ($model['method'] === 'putObject') {
  544. $model['ObjectURL'] = ['value' => $authResult['requestUrl']];
  545. }
  546. return new Request($httpMethod, $authResult['requestUrl'], $authResult['headers'], $authResult['body']);
  547. }
  548. protected function doRequest($model, &$operation, $params, $endpoint = null)
  549. {
  550. $request = $this->makeRequest($model, $operation, $params, $endpoint);
  551. $this->sendRequest($model, $operation, $params, $request);
  552. }
  553. protected function sendRequest($model, &$operation, $params, $request, $requestCount = 1)
  554. {
  555. $start = microtime(true);
  556. $saveAsStream = false;
  557. if (isset($operation['stream']) && $operation['stream']) {
  558. $saveAsStream = isset($params['SaveAsStream']) ? $params['SaveAsStream'] : false;
  559. if (isset($params['SaveAsFile'])) {
  560. if ($saveAsStream) {
  561. $obsException = new ObsException('SaveAsStream cannot be used with SaveAsFile together');
  562. $obsException->setExceptionType('client');
  563. throw $obsException;
  564. }
  565. $saveAsStream = true;
  566. }
  567. if (isset($params['FilePath'])) {
  568. if ($saveAsStream) {
  569. $obsException = new ObsException('SaveAsStream cannot be used with FilePath together');
  570. $obsException->setExceptionType('client');
  571. throw $obsException;
  572. }
  573. $saveAsStream = true;
  574. }
  575. if (isset($params['SaveAsFile']) && isset($params['FilePath'])) {
  576. $obsException = new ObsException('SaveAsFile cannot be used with FilePath together');
  577. $obsException->setExceptionType('client');
  578. throw $obsException;
  579. }
  580. }
  581. $promise = $this->httpClient->sendAsync($request, ['stream' => $saveAsStream])->then(
  582. function (Response $response) use ($model, $operation, $params, $request, $requestCount, $start) {
  583. ObsLog::commonLog(INFO, 'http request cost ' . round(microtime(true) - $start, 3) * 1000 . ' ms');
  584. $statusCode = $response->getStatusCode();
  585. $readable = isset($params['Body']) && ($params['Body'] instanceof StreamInterface || is_resource($params['Body']));
  586. $isRetryRequest = $statusCode >= 300 && $statusCode < 400 && $statusCode !== 304 && !$readable && $requestCount <= $this->maxRetryCount;
  587. $location = $response->getHeaderLine('location');
  588. if ($isRetryRequest && $location) {
  589. $url = parse_url($this->endpoint);
  590. $newUrl = parse_url($location);
  591. $scheme = (isset($newUrl['scheme']) ? $newUrl['scheme'] : $url['scheme']);
  592. $defaultPort = strtolower($scheme) === 'https' ? '443' : '80';
  593. $port = isset($newUrl['port']) ? $newUrl['port'] : $defaultPort;
  594. $newEndpoint = $scheme . '://' . $newUrl['host'] . ':' . $port;
  595. $this->doRequest($model, $operation, $params, $newEndpoint);
  596. return;
  597. }
  598. $this->parseResponse($model, $request, $response, $operation);
  599. },
  600. function (RequestException $exception) use ($model, $operation, $params, $request, $requestCount, $start) {
  601. ObsLog::commonLog(INFO, 'http request cost ' . round(microtime(true) - $start, 3) * 1000 . ' ms');
  602. $message = null;
  603. if ($exception instanceof ConnectException) {
  604. if ($requestCount <= $this->maxRetryCount) {
  605. $this->sendRequest($model, $operation, $params, $request, $requestCount + 1);
  606. return;
  607. } else {
  608. $message = 'Exceeded retry limitation, max retry count:' . $this->maxRetryCount . ', error message:' . $exception->getMessage();
  609. }
  610. }
  611. $this->parseException($model, $request, $exception, $message);
  612. });
  613. $promise->wait();
  614. }
  615. protected function doRequestAsync($model, &$operation, $params, $callback, $startAsync, $originMethod, $endpoint = null)
  616. {
  617. $request = $this->makeRequest($model, $operation, $params, $endpoint);
  618. return $this->sendRequestAsync($model, $operation, $params, $callback, $startAsync, $originMethod, $request);
  619. }
  620. protected function sendRequestAsync($model, &$operation, $params, $callback, $startAsync, $originMethod, $request, $requestCount = 1)
  621. {
  622. $start = microtime(true);
  623. $saveAsStream = false;
  624. if (isset($operation['stream']) && $operation['stream']) {
  625. $saveAsStream = isset($params['SaveAsStream']) ? $params['SaveAsStream'] : false;
  626. if ($saveAsStream) {
  627. if (isset($params['SaveAsFile'])) {
  628. $obsException = new ObsException('SaveAsStream cannot be used with SaveAsFile together');
  629. $obsException->setExceptionType('client');
  630. throw $obsException;
  631. }
  632. if (isset($params['FilePath'])) {
  633. $obsException = new ObsException('SaveAsStream cannot be used with FilePath together');
  634. $obsException->setExceptionType('client');
  635. throw $obsException;
  636. }
  637. }
  638. if (isset($params['SaveAsFile']) && isset($params['FilePath'])) {
  639. $obsException = new ObsException('SaveAsFile cannot be used with FilePath together');
  640. $obsException->setExceptionType('client');
  641. throw $obsException;
  642. }
  643. }
  644. return $this->httpClient->sendAsync($request, ['stream' => $saveAsStream])->then(
  645. function (Response $response) use ($model, $operation, $params, $callback, $startAsync, $originMethod, $request, $start) {
  646. ObsLog::commonLog(INFO, 'http request cost ' . round(microtime(true) - $start, 3) * 1000 . ' ms');
  647. $statusCode = $response->getStatusCode();
  648. $readable = isset($params['Body']) && ($params['Body'] instanceof StreamInterface || is_resource($params['Body']));
  649. if ($statusCode === 307 && !$readable) {
  650. $location = $response->getHeaderLine('location');
  651. if ($location) {
  652. $url = parse_url($this->endpoint);
  653. $newUrl = parse_url($location);
  654. $scheme = (isset($newUrl['scheme']) ? $newUrl['scheme'] : $url['scheme']);
  655. $defaultPort = strtolower($scheme) === 'https' ? '443' : '80';
  656. $port = isset($newUrl['port']) ? $newUrl['port'] : $defaultPort;
  657. $newEndpoint = $scheme . '://' . $newUrl['host'] . ':' . $port;
  658. return $this->doRequestAsync($model, $operation, $params, $callback, $startAsync, $originMethod, $newEndpoint);
  659. }
  660. }
  661. $this->parseResponse($model, $request, $response, $operation);
  662. ObsLog::commonLog(INFO, 'obsclient cost ' . round(microtime(true) - $startAsync, 3) * 1000 . ' ms to execute ' . $originMethod);
  663. unset($model['method']);
  664. $callback(null, $model);
  665. },
  666. function (RequestException $exception) use ($model, $operation, $params, $callback, $startAsync, $originMethod, $request, $start, $requestCount) {
  667. ObsLog::commonLog(INFO, 'http request cost ' . round(microtime(true) - $start, 3) * 1000 . ' ms');
  668. $message = null;
  669. if ($exception instanceof ConnectException) {
  670. if ($requestCount <= $this->maxRetryCount) {
  671. return $this->sendRequestAsync($model, $operation, $params, $callback, $startAsync, $originMethod, $request, $requestCount + 1);
  672. } else {
  673. $message = 'Exceeded retry limitation, max retry count:' . $this->maxRetryCount . ', error message:' . $exception->getMessage();
  674. }
  675. }
  676. $obsException = $this->parseExceptionAsync($request, $exception, $message);
  677. ObsLog::commonLog(INFO, 'obsclient cost ' . round(microtime(true) - $startAsync, 3) * 1000 . ' ms to execute ' . $originMethod);
  678. $callback($obsException, null);
  679. }
  680. );
  681. }
  682. }