SendRequestTrait.php 25 KB

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