path = "/"; $this->credential = $credential; $this->region = $region; if ($profile) { $this->profile = $profile; } else { $this->profile = new ClientProfile(); } $this->getRefreshedEndpoint(); $this->sdkVersion = AbstractClient::$SDK_VERSION; $this->apiVersion = $version; if ($this->profile->enableRegionBreaker) { if (is_null($this->profile->getRegionBreakerProfile())) { throw new TencentCloudSDKException("ClientError", "RegionBreakerProfile have not been set yet."); } $this->circuitBreaker = new CircuitBreaker($this->profile->getRegionBreakerProfile()); } if (version_compare(phpversion(), $this->PHP_VERSION_MINIMUM, '<') && $profile->getCheckPHPVersion()) { throw new TencentCloudSDKException("ClientError", "PHP version must >= ".$this->PHP_VERSION_MINIMUM.", your current is ".phpversion()); } $this->httpConn = $this->createConnect(); } /** * 设置产品地域 * @param string $region 地域 */ public function setRegion($region) { $this->region = $region; } /** * 获取产品地域 * @return string */ public function getRegion() { return $this->region; } /** * 设置认证信息实例 * @param Credential $credential 认证信息实例 */ public function setCredential($credential) { $this->credential = $credential; } /** * 返回认证信息实例 * @return Credential */ public function getCredential() { return $this->credential; } /** * 设置配置实例 * @param ClientProfile $profile 配置实例 */ public function setClientProfile($profile) { $this->profile = $profile; } /** * 返回配置实例 * @return ClientProfile */ public function getClientProfile() { return $this->profile; } /** * @param string $action 方法名 * @param array $request 参数列表 * @return mixed * @throws TencentCloudSDKException */ public function __call($action, $request) { if ($this->profile->enableRegionBreaker) { return $this->doRequestWithOptionsOnRegionBreaker($action, $request[0], array()); } else { return $this->doRequestWithOptions($action, $request[0], array()); } } /** * @param string $action * @param array $headers * @param string $body json content * @return mixed * @throws TencentCloudSDKException */ public function callJson($action, $body, $headers=null) { try { $responseData = null; $options = array( "IsCommonJson" => true, ); if ($this->profile->getHttpProfile()->getReqMethod() == HttpProfile::$REQ_GET) { throw new TencentCloudSDKException("ClientError", "Common client call doesn't support GET method"); } if ($this->profile->getSignMethod() != ClientProfile::$SIGN_TC3_SHA256) { throw new TencentCloudSDKException("ClientError", "Common client call must use TC3-HMAC-SHA256"); } $responseData = $this->doRequestWithTC3($action, $body, $options, $headers, null); if ($responseData->getStatusCode() !== AbstractClient::$HTTP_RSP_OK) { throw new TencentCloudSDKException($responseData->getReasonPhrase(), $responseData->getBody()); } $resp = json_decode($responseData->getBody(), true)["Response"]; if (array_key_exists("Error", $resp)) { throw new TencentCloudSDKException($resp["Error"]["Code"], $resp["Error"]["Message"], $resp["RequestId"]); } return $resp; } catch (\Exception $e) { if (!($e instanceof TencentCloudSDKException)) { throw new TencentCloudSDKException("", $e->getMessage()); } else { throw $e; } } } /** * @param string $action 方法名 * @param array $headers 自定义headers * @param string $body content * @return mixed * @throws TencentCloudSDKException */ public function call_octet_stream($action, $headers, $body) { try { $responseData = null; $options = array( "IsOctetStream" => true, ); switch ($this->profile->getSignMethod()) { case ClientProfile::$SIGN_TC3_SHA256: $responseData = $this->doRequestWithTC3($action, Null, $options, $headers, $body); break; default: throw new TencentCloudSDKException("ClientError", "Invalid sign method"); break; } if ($responseData->getStatusCode() !== AbstractClient::$HTTP_RSP_OK) { throw new TencentCloudSDKException($responseData->getReasonPhrase(), $responseData->getBody()); } $tmpResp = json_decode($responseData->getBody(), true)["Response"]; if (array_key_exists("Error", $tmpResp)) { throw new TencentCloudSDKException($tmpResp["Error"]["Code"], $tmpResp["Error"]["Message"], $tmpResp["RequestId"]); } return $this->returnResponse($action, $tmpResp); } catch (\Exception $e) { if (!($e instanceof TencentCloudSDKException)) { throw new TencentCloudSDKException("", $e->getMessage()); } else { throw $e; } } } protected function doRequestWithOptions($action, $request, $options) { try { $responseData = null; $serializeRequest = $request->serialize(); $method = $this->getPrivateMethod($request, "arrayMerge"); $serializeRequest = $method->invoke($request, $serializeRequest); switch ($this->profile->getSignMethod()) { case ClientProfile::$SIGN_HMAC_SHA1: case ClientProfile::$SIGN_HMAC_SHA256: $responseData = $this->doRequest($action, $serializeRequest); break; case ClientProfile::$SIGN_TC3_SHA256: $responseData = $this->doRequestWithTC3($action, $request, $options, array(), ""); break; default: throw new TencentCloudSDKException("ClientError", "Invalid sign method"); break; } if ($responseData->getStatusCode() !== AbstractClient::$HTTP_RSP_OK) { throw new TencentCloudSDKException($responseData->getReasonPhrase(), $responseData->getBody()); } $tmpResp = json_decode($responseData->getBody(), true)["Response"]; if (array_key_exists("Error", $tmpResp)) { throw new TencentCloudSDKException($tmpResp["Error"]["Code"], $tmpResp["Error"]["Message"], $tmpResp["RequestId"]); } return $this->returnResponse($action, $tmpResp); } catch (\Exception $e) { if (!($e instanceof TencentCloudSDKException)) { throw new TencentCloudSDKException("", $e->getMessage()); } else { throw $e; } } } protected function doRequestWithOptionsOnRegionBreaker($action, $request, $options) { try { $endpoint = $this->profile->getRegionBreakerProfile()->masterEndpoint; list($generation, $need_break) = $this->circuitBreaker->beforeRequests(); if ($need_break) { $endpoint = $this->profile->getRegionBreakerProfile()->slaveEndpoint; } $this->profile->getHttpProfile()->setEndpoint($endpoint); $responseData = null; $serializeRequest = $request->serialize(); $method = $this->getPrivateMethod($request, "arrayMerge"); $serializeRequest = $method->invoke($request, $serializeRequest); switch ($this->profile->getSignMethod()) { case ClientProfile::$SIGN_HMAC_SHA1: case ClientProfile::$SIGN_HMAC_SHA256: $responseData = $this->doRequest($action, $serializeRequest); break; case ClientProfile::$SIGN_TC3_SHA256: $responseData = $this->doRequestWithTC3($action, $request, $options, array(), ""); break; default: throw new TencentCloudSDKException("ClientError", "Invalid sign method"); break; } if ($responseData->getStatusCode() !== AbstractClient::$HTTP_RSP_OK) { throw new TencentCloudSDKException($responseData->getReasonPhrase(), $responseData->getBody()); } $tmpResp = json_decode($responseData->getBody(), true)["Response"]; if (array_key_exists("Error", $tmpResp)) { $this->circuitBreaker->afterRequests($generation, True); throw new TencentCloudSDKException($tmpResp["Error"]["Code"], $tmpResp["Error"]["Message"], $tmpResp["RequestId"]); } $this->circuitBreaker->afterRequests($generation, True); return $this->returnResponse($action, $tmpResp); } catch (\Exception $e) { $this->circuitBreaker->afterRequests($generation, False); if (!($e instanceof TencentCloudSDKException)) { throw new TencentCloudSDKException("", $e->getMessage()); } else { throw $e; } } } private function doRequest($action, $request) { switch ($this->profile->getHttpProfile()->getReqMethod()) { case HttpProfile::$REQ_GET: return $this->getRequest($action, $request); break; case HttpProfile::$REQ_POST: return $this->postRequest($action, $request); break; default: throw new TencentCloudSDKException("", "Method only support (GET, POST)"); break; } } private function doRequestWithTC3($action, $request, $options, $headers, $payload) { $endpoint = $this->getRefreshedEndpoint(); $headers["Host"] = $endpoint; $headers["X-TC-Action"] = ucfirst($action); $headers["X-TC-RequestClient"] = $this->sdkVersion; $headers["X-TC-Timestamp"] = time(); $headers["X-TC-Version"] = $this->apiVersion; if ($this->region) { $headers["X-TC-Region"] = $this->region; } if ($this->credential->getToken()) { $headers["X-TC-Token"] = $this->credential->getToken(); } $language = $this->profile->getLanguage(); if ($language) { $headers["X-TC-Language"] = $language; } $canonicalUri = $this->path; $reqmethod = $this->profile->getHttpProfile()->getReqMethod(); if (HttpProfile::$REQ_GET == $reqmethod) { $headers["Content-Type"] = "application/x-www-form-urlencoded"; $rs = $request->serialize(); $am = $this->getPrivateMethod($request, "arrayMerge"); $rsam = $am->invoke($request, $rs); $canonicalQueryString = http_build_query($rsam); $payload = ""; } if (HttpProfile::$REQ_POST == $reqmethod) { if (isset($options["IsMultipart"]) && $options["IsMultipart"] === true) { $boundary = uniqid(); $headers["Content-Type"] = "multipart/form-data; boundary=" . $boundary; $canonicalQueryString = ""; $payload = $this->getMultipartPayload($request, $boundary, $options); } else if (isset($options["IsOctetStream"]) && $options["IsOctetStream"] === true) { $headers["Content-Type"] = "application/octet-stream"; $canonicalQueryString = ""; } else if (isset($options["IsCommonJson"]) && $options["IsCommonJson"] === true) { $headers["Content-Type"] = "application/json"; $canonicalQueryString = ""; $payload = json_encode($request); } else { $headers["Content-Type"] = "application/json"; $canonicalQueryString = ""; $payload = $request->toJsonString(); } } if ($this->profile->getUnsignedPayload() == true) { $headers["X-TC-Content-SHA256"] = "UNSIGNED-PAYLOAD"; $payloadHash = hash("SHA256", "UNSIGNED-PAYLOAD"); } else { $payloadHash = hash("SHA256", $payload); } $canonicalHeaders = "content-type:".$headers["Content-Type"]."\n". "host:".$headers["Host"]."\n"; $signedHeaders = "content-type;host"; $canonicalRequest = $reqmethod."\n". $canonicalUri."\n". $canonicalQueryString."\n". $canonicalHeaders."\n". $signedHeaders."\n". $payloadHash; $algo = "TC3-HMAC-SHA256"; // date_default_timezone_set('UTC'); // $date = date("Y-m-d", $headers["X-TC-Timestamp"]); $date = gmdate("Y-m-d", $headers["X-TC-Timestamp"]); $service = explode(".", $endpoint)[0]; $credentialScope = $date."/".$service."/tc3_request"; $hashedCanonicalRequest = hash("SHA256", $canonicalRequest); $str2sign = $algo."\n". $headers["X-TC-Timestamp"]."\n". $credentialScope."\n". $hashedCanonicalRequest; $skey = $this->credential->getSecretKey(); $signature = Sign::signTC3($skey, $date, $service, $str2sign); $sid = $this->credential->getSecretId(); $auth = $algo. " Credential=".$sid."/".$credentialScope. ", SignedHeaders=content-type;host, Signature=".$signature; $headers["Authorization"] = $auth; if (HttpProfile::$REQ_GET == $reqmethod) { $connect = $this->getConnect(); return $connect->getRequest($this->path, $canonicalQueryString, $headers); } else { $connect = $this->getConnect(); return $connect->postRequestRaw($this->path, $headers, $payload); } } private function getMultipartPayload($request, $boundary, $options) { $body = ""; $params = $request->serialize(); foreach ($params as $key => $value) { $body .= "--".$boundary."\r\n"; $body .= "Content-Disposition: form-data; name=\"".$key; if (in_array($key, $options["BinaryParams"])) { $body .= "\"; filename=\"".$key; } $body .= "\"\r\n"; if (is_array($value)) { $value = json_encode($value); $body .= "Content-Type: application/json\r\n"; } $body .= "\r\n".$value."\r\n"; } if ($body != "") { $body .= "--".$boundary."--\r\n"; } return $body; } /** * @throws TencentCloudSDKException */ private function getRequest($action, $request) { $query = $this->formatRequestData($action, $request, httpProfile::$REQ_GET); $connect = $this->getConnect(); return $connect->getRequest($this->path, $query, []); } /** * @throws TencentCloudSDKException */ private function postRequest($action, $request) { $body = $this->formatRequestData($action, $request, httpProfile::$REQ_POST); $connect = $this->getConnect(); return $connect->postRequest($this->path, [], $body); } /** * @throws TencentCloudSDKException */ private function formatRequestData($action, $request, $reqMethod) { $param = $request; $param["Action"] = ucfirst($action); $param["RequestClient"] = $this->sdkVersion; $param["Nonce"] = rand(); $param["Timestamp"] = time(); $param["Version"] = $this->apiVersion; if ($this->credential->getSecretId()) { $param["SecretId"] = $this->credential->getSecretId(); } if ($this->region) { $param["Region"] = $this->region; } if ($this->credential->getToken()) { $param["Token"] = $this->credential->getToken(); } if ($this->profile->getSignMethod()) { $param["SignatureMethod"] = $this->profile->getSignMethod(); } $language = $this->profile->getLanguage(); if ($language) { $param["Language"] = $language; } $signStr = $this->formatSignString($this->getRefreshedEndpoint(), $this->path, $param, $reqMethod); $param["Signature"] = Sign::sign($this->credential->getSecretKey(), $signStr, $this->profile->getSignMethod()); return $param; } private function createConnect() { $prot = $this->profile->getHttpProfile()->getProtocol(); return new HttpConnection($prot.$this->getRefreshedEndpoint(), $this->profile); } private function getConnect() { $keepAlive = $this->profile->getHttpProfile()->getKeepAlive(); if (true === $keepAlive) { return $this->httpConn; } else { return $this->createConnect(); } } private function formatSignString($host, $uri, $param, $requestMethod) { $tmpParam = []; ksort($param); foreach ($param as $key => $value) { array_push($tmpParam, $key . "=" . $value); } $strParam = join ("&", $tmpParam); $signStr = strtoupper($requestMethod) . $host . $uri ."?".$strParam; return $signStr; } private function getPrivateMethod($obj, $methodName) { $objReflectClass = new ReflectionClass(get_class($obj)); $method = $objReflectClass->getMethod($methodName); $method->setAccessible(true); return $method; } /** * User might call httpProfile.SetEndpoint after client is initialized, * so everytime we get the enpoint we need to check it. * Or we must find a way to disable such usage. */ private function getRefreshedEndpoint() { $this->endpoint = $this->profile->getHttpProfile()->getEndpoint(); if ($this->endpoint === null) { $this->endpoint = $this->service.".".$this->profile->getHttpProfile()->getRootDomain(); } return $this->endpoint; } }