php curl常见错误:SSL错误、bool(false)

症状:php curl调用https出错
排查方法:在命令行中使用curl调用试试。
原因:服务器所在机房无法验证SSL证书。
解决办法:跳过SSL证书检查。
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);

症状:php curl调用curl_exec返回bool(false),命令行curl调用正常。
排查方法:
var_dump(curl_error($ch));
返回:
string(23) "Empty reply from server"
再排查:
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
返回:
HTTP/1.1 100 Continue
Connection: close
原因:php curl接收到HTTP 100就结束了,应该继续接收HTTP 200
解决方案:
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:'));

PHP and cURL: Disabling 100-continue header
Published June 15th, 2006
I've been using cURL (through PHP) to build a sort of proxy for a project I'm working on. I need to parse the returned headers (to recover the HTTP status), so had included a very simple script to do so. It had worked fine in the past, but for some reason barfed in this case. A closer look at what was being returned revealed that for some reason, Apache was prepending the ‘normal' headers with an extra response header:

HTTP/1.1 100 Continue

HTTP/1.1 200 OK Date: Fri, 09 Jun 2006 15:23:42 GMT
Server: Apache
...A bit of Googling revealed that this was to do with a header that cURL sends by default:

Expect: 100-continue

…which in turns tells Apache to send the extra header. I poked around a fair bit but couldn't quite find a workable solution short of manually removing the header in PHP, which seemed a bit clumsy. Finally, on a hunch I tried this:

curl_setopt( $curl_handle, CURLOPT_HTTPHEADER, array( 'Expect:' ) );

…which basically overrides the original ‘Expect:' header with an empty one.

Hope this helps someone.
<?php declare(strict_types=1); namespace App\Services\Platform; use App\Models\Shop; use CURLFile; use Exception; use Illuminate\Support\Facades\Redis; class KfzClient { private string $appKey; private string $appSecret; private string $method; private Shop $shop; public function __construct(Shop $shop) { $this->appKey = "448"; $this->appSecret = "82da9ff496e4244717f149d7e671fbd9bb0e0bbfc8d1041429a9244254cf2b3a"; $this->shop = $shop; if ($shop['app_key'] && $shop['app_secret']) { $this->appKey = $shop['app_key']; $this->appSecret = $shop['app_secret']; } } public function setAccessToken(string $accessToken): void { $this->shop['access_token'] = $accessToken; } protected function getCommonParams() { return [ 'appId' => $this->appKey, 'format' => 'json', 'v' => '1.0', 'signMethod' => 'md5', 'simplify' => 1, 'accessToken' => $this->shop['access_token'], 'method' => $this->method, 'datetime' => date('Y-m-d H:i:s', time()), ]; } /** * 获取授权链接 * * @param $state * @return string */ public function getAuthorizedUrl($state): string { $redirectUri = urlencode('https://token.shumaibao.com/index/kfz'); return "https://open.kongfz.com/v1/oauth2/authorize?appId=$this->appKey&responseType=code&state=$state&redirectUri=$redirectUri"; } public function decrApiNumOptimized($adminKey, $adminId, $num) { $lua = <<<LUA local adminKey = KEYS[1] local adminField = KEYS[2] local num = tonumber(KEYS[3]) local total = tonumber(redis.call('HGET', adminKey, adminField) or "0") if total < -num then return -1 end redis.call('HINCRBY', adminKey, adminField, num) return 1 LUA; $result = Redis::eval( $lua, 3, [$adminKey, $adminId, $num] ); return $result == 1; } /** * 请求接口 * @param string $method * @param array $paramMap * @return array * @throws Exception */ public function request(string $method, array $paramMap = []): array { $this->method = $method; $url = 'https://open.kongfz.com/router/rest'; $data = array_merge($this->getCommonParams(), $paramMap); $data['sign'] = $this->getSign($data); try { $response = $this->sendRequest($url, $data); if ($method === 'kongfz.image.upload') { $num = -4; if (!$this->decrApiNumOptimized('admin_api_num', $this->shop['admin_id'], $num)) { throw new Exception('接口请求次数不足'); } Redis::zadd('api_log',time(), json_encode([ 'admin_id' => $this->shop['admin_id'], 'title'=> '孔夫子-'.$this->shop['name'].'上传图片', 'num' => $num, 'createtime'=> time() ])); } } catch (Exception $e) { if ($e->getMessage() === "接口请求次数不足") { Shop::where('id', $this->shop['id'])->update([ 'status' => "0", 'warning_msg' => '接口请求次数不足' ]); throw new Exception('接口请求次数不足'); } else { return [ 'errorResponse' => [ 'code' => 500, 'subMsg' => '接口请求失败' . $e->getMessage() ], 'successResponse' => null ]; } } return $this->returnResult($response); } /** * 发送请求 * * @param string $url * @param array $params * @param bool $hasFile * * @return array * @throws Exception */ protected function sendRequest(string $url, array $params, bool $hasFile = false): array { $ch = curl_init($url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 禁用SSL证书验证 if ($hasFile) { curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: multipart/form-data']); curl_setopt($ch, CURLOPT_POSTFIELDS, $params); } else { curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/x-www-form-urlencoded']); curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params)); } //超时 curl_setopt($ch, CURLOPT_TIMEOUT, 30); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $ret = curl_exec($ch); if (false === $ret) { $err = curl_error($ch); $errno = curl_errno($ch); $info = curl_getinfo($ch); curl_close($ch); throw new Exception("请求失败,错误:{$errno},错误信息:{$err},curl信息:{$info['url']}"); } $err = curl_error($ch); if (!empty($err)) { $errno = curl_errno($ch); $info = curl_getinfo($ch); curl_close($ch); return [ 'ret' => false, 'errno' => $errno, 'msg' => $err, 'info' => $info, ]; } curl_close($ch); return [ 'ret' => true, 'msg' => $ret, ]; } /** * 上传图片 * * @param string $imagePath * * @return array * @throws Exception */ public function upload(string $imagePath): array { $this->method = 'kongfz.image.upload'; $postData = $this->getCommonParams(); $postData['bucket'] = 'book'; $postData['sign'] = $this->getSign($postData); $postData['image'] = new CURLFile($imagePath); $uri = 'https://open.kongfz.com/router/image/upload'; $response = $this->sendRequest($uri, $postData, true); return $this->returnResult($response); } /** * 返回结果 * * @param $response * @return array */ protected function returnResult($response): array { if ($response['ret']) { $body = json_decode($response['msg'], true); if (empty($body)) { return [ 'errorResponse' => [ 'code' => 500, 'subMsg' => '接口请求失败:' . $response['msg'] ], 'successResponse' => null ]; } if (!empty($body['errorResponse']['code']) && in_array($body['errorResponse']['code'], [2000, 2001])) { //授权过期 // 2000 => '无效的Access Token', // 2001 => 'Access Token已经过期', Shop::where('id', $this->shop['id']) ->update([ 'status' => 0, 'warning_msg' => '授权过期或失效' ]); } return $body; } else { return [ 'errorResponse' => [ 'code' => 500, 'subMsg' => '接口请求失败:' . $response['msg'] ], 'successResponse' => null ]; } } /** * 刷新token * @param $refreshToken * @return array * @throws \Exception */ public function refreshToken($refreshToken): array { $api = 'https://open.kongfz.com/v1/oauth2/refresh'; $data = [ 'grantType' => 'refresh_token', 'appId' => $this->appKey, 'appSecret' => $this->appSecret, 'refreshToken' => $refreshToken ]; return $this->returnResult($this->sendRequest($api, $data)); } /** * 获取access_token * @param $code * @return array[] * @throws \Exception */ public function getAccessToken($code): array { $data = [ 'code' => $code, 'grantType' => 'authorization_code', 'appId' => $this->appKey, 'appSecret' => $this->appSecret, 'redirectUri' => 'https://token.shumaibao.com/index/kfz' ]; $api = 'https://open.kongfz.com/v1/oauth2/token'; return $this->returnResult($this->sendRequest($api, $data)); } /** * 获取店铺分类 * @return array * @throws Exception */ public function getShopCategory() { $shopCategoryResponse = $this->request('kongfz.shop.category.name.list'); $shopCategoryList = []; if ($shopCategoryResponse['successResponse']) { foreach ($shopCategoryResponse['successResponse'] as $value) { $shopCategoryList[$value['value']] = $value['name']; } } return $shopCategoryList; } /** * 获取店铺运费模板 * @return array * @throws Exception */ public function getDeliveryTemplate() { $shopDeliveryResponse = $this->request('kongfz.delivery.template.name.list'); $shopDeliveryList = []; if ($shopDeliveryResponse['successResponse']) { foreach ($shopDeliveryResponse['successResponse'] as $value) { $shopDeliveryList[$value['mouldId']] = $value['mouldName']; } } return $shopDeliveryList; } // /** // * 获取错误信息 // * @param $code // * @param $msg // * @return string // */ /*private function getErrorMessage($code, $msg) { $error = [ 1000 => '授权码错误或已经过期', 1001 => '授权码不存在', // 1002 => '系统错误,建议清理浏览器缓存,稍后重试', // 1003 => '参数错误,xx参数必须', 1004 => '无效的appId', 1005 => 'appId对应的应用目前不可用', 1006 => '权限错误', 1007 => '应用回调地址错误,请检查回调地址是否为空,或含有非法字符', 1008 => '用户没有同意授权', 1009 => '调用次数受限', 1010 => '用户没有登录', 1011 => '无效的App Secret', 1012 => '无效的scope', 1013 => '无效的Refresh Token', 2000 => '无效的Access Token', 2001 => 'Access Token已经过期', // 2002 => '参数错误,缺少xx参数或xx参数错误', 2003 => '无效的签名', 2004 => '编码错误', 2005 => '用户权限不足', 2006 => '不存在的方法', 2007 => '服务不可用', // 3000 => '参数错误,缺少xx参数或xx参数错误', 3001 => '权限不足,非法访问', 3002 => '查询无结果', 3003 => '系统错误,业务逻辑错误', 500 => '接口请求失败' ]; return $error[$code] ?? $msg; }*/ public function getSign($paramMap): string { //按字母顺序排序索引 ksort($paramMap); $signString = ''; foreach ($paramMap as $key => $value) { if ($key != 'sign' && !is_array($value)) { $signString .= "$key$value"; } } switch ($paramMap['signMethod']) { case 'hmac': return strtoupper(hash_hmac('md5', $signString, $this->appSecret)); case 'md5': default: return strtoupper(hash('md5', "$this->appSecret$signString{$this->appSecret}")); } } } 改为node实现
最新发布
10-02
<think>问题:cURL 错误: SSL certificate problem: self signed certificate 原因:这个错误是因为cURL在验证SSL证书时发现该证书是自签名的(即不是由受信任的证书颁发机构CA签发的),因此cURL默认会拒绝连接。 解决方案: 1. 如果是测试环境,可以选择忽略证书验证(不推荐生产环境使用)。 2. 将自签名证书添加到受信任的根证书列表中。 3. 或者,在请求中指定该自签名证书为信任证书。 下面分别给出几种解决方案的代码示例:</think>### 解决 "SSL certificate problem: self signed certificate" 错误 这个错误表示 cURL 遇到了自签名证书(不是由受信任的证书颁发机构 CA 签发的)。以下是几种解决方案,根据您的使用场景选择: --- #### 方案 1:临时禁用 SSL 验证(仅限测试环境) ```php <?php $ch = curl_init("https://your-api-url.com"); // 禁用 SSL 验证(仅测试环境使用) curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); // 0 = 不验证主机名 // 其他设置 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 10); $response = curl_exec($ch); if (curl_errno($ch)) { die("cURL 错误: " . curl_error($ch)); } curl_close($ch); echo $response; ?> ``` > **警告**:此方法会禁用所有 SSL/TLS 安全检查,使连接容易受到中间人攻击。**切勿在生产环境中使用**。 --- #### 方案 2:信任特定自签名证书(推荐) 1. **获取服务器证书**: ```bash openssl s_client -connect your-api-url.com:443 -showcerts </dev/null 2>/dev/null | sed -n '/BEGIN CERTIFICATE/,/END CERTIFICATE/p' > server-cert.pem ``` 2. **在 PHP 代码中指定证书**: ```php <?php $ch = curl_init("https://your-api-url.com"); // 指定自签名证书路径 curl_setopt($ch, CURLOPT_CAINFO, __DIR__ . '/server-cert.pem'); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); // 严格验证主机名 // 其他设置 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); if (curl_errno($ch)) { die("cURL 错误: " . curl_error($ch)); } curl_close($ch); echo $response; ?> ``` --- #### 方案 3:将证书添加到系统信任库(永久解决方案) 1. **查找系统 CA 证书路径**: ```php <?php phpinfo(); // 查找 "curl.cainfo" 或 "openssl.cafile" ?> ``` 2. **将自签名证书添加到 CA 包**: ```bash # 将自签名证书附加到 CA 包 cat server-cert.pem >> /etc/ssl/certs/ca-certificates.crt # 或指定到 PHP 配置 # 在 php.ini 中添加: # curl.cainfo = "/path/to/your/ca-bundle.crt" # openssl.cafile = "/path/to/your/ca-bundle.crt" ``` --- #### 方案 4:使用 Guzzle HTTP 客户端(更灵活) ```php <?php require 'vendor/autoload.php'; use GuzzleHttp\Client; use GuzzleHttp\Exception\RequestException; // 创建自定义处理自签名证书的客户端 $client = new Client([ 'verify' => false, // 禁用验证(测试环境) // 或指定证书路径 // 'verify' => __DIR__ . '/server-cert.pem', 'timeout' => 10, 'headers' => [ 'User-Agent' => 'MyApp/1.0', ] ]); try { $response = $client->get('https://your-api-url.com'); echo $response->getBody(); } catch (RequestException $e) { die("请求失败: " . $e->getMessage()); } ?> ``` --- ### 最佳实践建议 1. **开发/测试环境**: - 使用方案 1(临时禁用验证) - 或使用方案 2(信任特定证书) 2. **生产环境**: - 使用方案 3(添加到系统信任库) - 或使用受信任的 CA 证书替代自签名证书(如 Let's Encrypt) 3. **证书管理**: - 定期更新自签名证书(通常有效期 1 年) - 使用 `openssl x509 -in server-cert.pem -text -noout` 检查证书详情 4. **安全注意事项**: - 永远不要在生产环境中禁用 SSL 验证 - 自签名证书提供加密但不提供身份验证 - 考虑使用 mTLS(双向 TLS)进行更严格的身份验证 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值