Files
2021-07-05 09:56:27 +08:00

475 lines
14 KiB
PHP
Executable File

<?php
/**
* User: timevale
* Date: 2016/12/7
*/
namespace tech\core;
use tech\core\Log;
use tech\core\Util;
class HttpUtils
{
/**
* curl超时时间 单位:秒
*/
const CURL_TIMEOUT = 60;
/**
* 重试次数
*/
const TRY_NUMBER = 3;
const CONTENT_TYPE_JSON = 'application/json';
const CONTENT_TYPE_STREAM = 'application/octet-stream';
const CONTENT_TYPE_FORM_URLENCODE = 'application/x-www-form-urlencoded';
const CONTENT_TYPE_FORM_DATA = 'multipart/form-data';
//const CONTENT_TYPE_TEXT_XML = 'text/xml';
//const CONTENT_TYPE_TEXT_PLAIN = 'text/plain';
/**
* 配置文件
*/
private $_config = array();
/**
* 请求头
*/
private $requestHeader = array();
/**
* 设置curl是否返回header信息,签名验签必须返回,设置为 true 或 1
*/
private $outHeader = false;
private $proxy = null;
/**
* 响应状态
*/
public $responseStatus;
/**
* 响应头
*/
public $responseHeader;
/**
* 响应内容
*/
public $responseBody;
/**
* @return HttpUtils
*/
public static function request()
{
static $req = false;
if ($req === false) {
$_config = include ESIGN_ROOT . '/comm/initConfig.php';
$req = new HttpUtils($_config);
}
return $req;
}
/*
* 构建
*
**/
public function __construct($config)
{
$this->_config = $config;
$this->requestHeader = array(
'X-timevale-mode: package',
'X-timevale-project-id:' . $config['project_id'],
'X-timevale-signature-algorithm:' . strtolower($config['sign_algorithm']),
);
if (!empty($config['proxy_ip'])) {
$this->proxy['host'] = $config['proxy_ip'];
$this->proxy['port'] = $config['proxy_port'];
}
}
/**
* 建立HTTP的POST请求,需要构建签名验签
* @param string $url
* @param array $data
* @param bool|true $isJson
* @return array|mixed
* @throws \Exception
*/
public function buildSignHttpRequest($url, array $data, $isJson)
{
if (empty($url)) {
return array(
'errCode' => 102,
'msg' => '请求地址为空',
'errShow' => false
);
}
if ($isJson === true) {
$contentType = 'Content-Type:' . self::CONTENT_TYPE_JSON . ";charset=UTF-8";
//转出json字符串
$data = Util::jsonEncode($data);
} else {
$contentType = 'Content-Type:' . self::CONTENT_TYPE_FORM_URLENCODE . ";charset=UTF-8";
//把数组所有元素,按照“参数=参数值”的模式用“&”字符拼接成字符串
$data = $this->toQueryString($data);
}
//生成签名结果
$mySign = $this->buildRequestSign($data);
//构建请求头
$header = $this->requestHeader;
$header[] = 'X-timevale-signature:' . $mySign;
$header[] = $contentType;
//输出响应头
$this->outHeader = true;
//远程获取数据
$this->sendHttpRequestPost($url, $data, $header, $this->proxy);
unset($header);
return $this->getResultByVerify($url);
}
/**
* 建立HTTP的POST请求,没有签名验签
* @param $url 请求地址
* @param array $data 请求参数
* @param bool|true $postJson 以json方式发送post参数
* @param bool $proxy 是否使用代理
* @return array|mixed
* @throws Exception
*/
public function noSignHttpPost($url, $data, $postJson, $proxy)
{
if (empty($url)) {
return array(
'errCode' => 102,
'msg' => '请求地址为空',
'errShow' => false
);
}
//构建请求头
$header = $this->requestHeader;
//请求参数提交方式
if ($postJson === true) {
$contentType = 'Content-Type:' . self::CONTENT_TYPE_JSON . ";charset=UTF-8";
$postStr = Util::jsonEncode($data);
$header[] = $contentType;
} else {
//$contentType = 'Content-Type:' . self::CONTENT_TYPE_FORM_DATA . ";charset=UTF-8";
$postStr = $data;
}
//不输出响应头
$this->outHeader = false;
//发送请求
if ($proxy === true) {
$this->sendHttpRequestPost($url, $postStr, $header, $this->proxy);
} else {
$this->sendHttpRequestPost($url, $postStr, $header, array());
}
return $this->getResult($url);
}
/**
* 解析请求结果,不需要验证服务端返回的签名信息
*
* @param string $url
* @return array|mixed
*/
protected function getResult($url = '')
{
$status = $this->responseStatus;
//请求失败直接返回
if ($status !== 200) {
return array(
'errCode' => $status,
'msg' => '服务接口调用失败! ' . $status,
'httpUrl' => $url,
'httpContent' => $this->responseBody,
'errShow' => false
);
}
//decode body
$res = json_decode($this->responseBody, TRUE);
return $res;
}
/**
* 解析结果,需要验证服务端返回的签名信息
* @param string $url
* @return array|mixed
*/
protected function getResultByVerify($url = '')
{
$status = $this->responseStatus;
$header = $this->responseHeader;
$body = $this->responseBody;
//请求失败直接返回
if ($status !== 200) {
return array(
'errCode' => $status,
'msg' => '调用服务接口失败!' . $status,
'httpUrl' => $url,
//'httpContent' => $body,
'errShow' => false
);
}
//解析头
$headerArray = $this->parseHeader($header);
$signature = isset($headerArray['x-timevale-signature']) ? $headerArray['x-timevale-signature'] : '';
//相应数据签名验证
$verify = $this->verifyResponse($body, $signature);
if (false == $verify) {
//验签失败
return array(
'errCode' => $status,
'msg' => '返回信息签名验签失败',
'errShow' => false,
'header' => $header,
//'body' => $body
);
}
//decode body
$res = json_decode(trim($body), TRUE);
return $res;
}
/**
* 发送CURL请求,POST模式
* @param string $url 指定URL完整路径地址
* @param string|array $postStr 请求的数据
* @param array $header 请求头
* @return string 远程输出的数据
* @throws \Exception
*/
public function sendHttpRequestPost($url, $postStr, $requestHeader = array(), $proxy = array())
{
$result = '';
$requestHeader[] = 'Expect:';
for ($i = 0; $i < self::TRY_NUMBER; $i++) {
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);//SSL证书认证
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($curl, CURLOPT_HEADER, $this->outHeader); // 输出HTTP头 true
curl_setopt($curl, CURLOPT_TIMEOUT, self::CURL_TIMEOUT);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);// 显示输出结果
if (!empty($proxy)) {
$host = $proxy['host'];
$host .= ($proxy['port']) ? ':' . $proxy['port'] : '';
//curl_setopt($curl, CURLOPT_HTTPPROXYTUNNEL, true);
curl_setopt($curl, CURLOPT_PROXY, $host);
}
curl_setopt($curl, CURLOPT_POST, true); // post传输数据
curl_setopt($curl, CURLOPT_POSTFIELDS, $postStr);// post传输数据
curl_setopt($curl, CURLOPT_HTTPHEADER, $requestHeader);
$result = curl_exec($curl);
$httpCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if (false !== $result) {
$headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
break;
} else {
$result = curl_error($curl);
}
curl_close($curl);
sleep(1);
}
if ($this->outHeader == true) {
$responseHeader = substr($result, 0, $headerSize);
$responseBody = substr($result, $headerSize);
} else {
$responseHeader = '';
$responseBody = $result;
}
$this->responseStatus = empty($httpCode) ? 101 : $httpCode;
$this->responseHeader = $responseHeader;
$this->responseBody = $responseBody;
//打印日志
$this->debug($url, $postStr, $requestHeader, $result);
}
private function debug($url, $postStr, $requestHeader, $result)
{
//$ret = json_decode($this->responseBody, true);
//$isError = (isset($ret['errCode']) && $ret['errCode'] === 0) ? true : false;
//if (ESIGN_DEBUGE && ($isError || $this->responseStatus !== 200)) {
if (ESIGN_DEBUGE) {
$str = PHP_EOL . '【请求地址】:' . $url;
$str .= PHP_EOL . '【请求参数】:' . (is_array($postStr) ? Util::jsonEncode($postStr) : $postStr);
$str .= PHP_EOL . '【请求头】:' . json_encode($requestHeader);
$str .= PHP_EOL . '【返回】 : ' . $result . PHP_EOL;
Log::debug($str);
}
}
/**
* 兼容不同版本的文件上传
* @param $filePath
* @return string|CURLFile
*/
public function getRealFileIgnore($filePath)
{
if (class_exists('\CURLFile')) {
return new \CURLFile($filePath);
} else {
return '@' . $filePath;
}
}
/**
* 生成签名结果
* @param string $query 要签名的参数
* @return string 签名结果字符串
*/
private function buildRequestSign($query)
{
switch (strtoupper(trim($this->_config['sign_algorithm']))) {
case 'HMACSHA256' :
$mySign = $this->hmacSHA256Sign($query, $this->_config['project_secret']);
break;
case 'RSA' :
$mySign = $this->rsaSign($query, $this->_config['rsa_private_key']);
break;
default :
$mySign = '';
}
return $mySign;
}
/**
* 验证服务端返回的签名结果
* @param string $str 要验证的字符串
* @param string $sign 签名字符串
* @return bool 验证结果
*/
private function verifyResponse($str, $sign)
{
//验证服务端返回的签名
switch (strtoupper(trim($this->_config['sign_algorithm']))) {
case 'HMACSHA256':
return $this->hmacSHA256Verify($str, $sign, $this->_config['project_secret']);
case 'RSA':
return $this->rsaVerify($str, $sign, $this->_config['esign_public_key']);
break;
}
return false;
}
/**
* 签名字符串
* @param string $str 需要签名的字符串
* @param string $key 私钥
* @return string 签名结果
*/
private function hmacSHA256Sign($str, $key)
{
return hash_hmac('sha256', $str, $key);
}
/**
* 验证签名
* @param string $str 需要签名的字符串
* @param string $sign 签名结果
* @param string $key 私钥
* @return bool
*/
private function hmacSHA256Verify($str, $sign, $key)
{
$mySign = $this->hmacSHA256Sign($str, $key);
return ($mySign === $sign);
}
/**
* RSA签名
* @param string $str 待签名数据
* @param string $priKey 接入平台私钥文件路径
* @return string 签名结果
*/
private function rsaSign($str, $priKey)
{
//$priKey = file_get_contents($privateKeyPath);
//$res = openssl_get_privatekey($priKey);
$res = openssl_pkey_get_private($priKey);
openssl_sign($str, $sign, $res);
openssl_free_key($res);
return bin2hex($sign);
}
/**
* RSA验签
* @param string $str 待签名数据
* @param string $sign 要校对的的签名结果
* @param string $pubKey e签宝的公钥文件路径
* @return bool 验证结果
*/
private function rsaVerify($str, $sign, $pubKey)
{
//$pubKey = file_get_contents($publicKeyPath);
//$res = openssl_get_publickey($pubKey);
$res = openssl_pkey_get_public($pubKey);
$result = openssl_verify($str, pack("H*", $sign), $res);
openssl_free_key($res);
return (1 == $result);
}
/**
* 解析响应头
* @param $headerStr 响应头字符串
* @return array
*/
private function parseHeader($headerStr)
{
//切割字符串
$headerStr = explode("\r\n\r\n", trim($headerStr));
$headerStr = array_pop($headerStr);
$headerStr = explode("\r\n", $headerStr);
array_shift($headerStr);
$headerArray = array();
foreach ($headerStr as $item) {
$res = explode(':', $item);
$res[1] = trim($res[1]);
$headerArray[strtolower($res[0])] = isset($res[1]) ? $res[1] : '';
}
return $headerArray;
}
/**
* 把数组所有元素用“&”字符拼接成字符串
*
* @param array $data 需要拼接的数组
* @return string 返回诸如 key1=value1&key2=value2
*/
private function toQueryString(array $data)
{
$result = http_build_query($data);
return $result;
}
}