admin-api/vendor/yurunsoft/yurun-http/src/HttpRequest.php

1172 lines
28 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Yurun\Util;
use Yurun\Util\YurunHttp\Attributes;
use Yurun\Util\YurunHttp\Http\Psr7\Consts\MediaType;
use Yurun\Util\YurunHttp\Http\Psr7\UploadedFile;
use Yurun\Util\YurunHttp\Http\Request;
class HttpRequest
{
/**
* 处理器.
*
* @var \Yurun\Util\YurunHttp\Handler\IHandler|null
*/
private $handler;
/**
* 需要请求的Url地址
*
* @var string
*/
public $url;
/**
* 发送内容可以是字符串、数组支持键值、Yurun\Util\YurunHttp\Http\Psr7\UploadedFile其中键值会作为html编码文件则是上传.
*
* @var mixed
*/
public $content;
/**
* `curl_setopt_array()`所需要的第二个参数.
*
* @var array
*/
public $options = [];
/**
* 请求头.
*
* @var array
*/
public $headers = [];
/**
* Cookies.
*
* @var array
*/
public $cookies = [];
/**
* 失败重试次数默认为0.
*
* @var int
*/
public $retry = 0;
/**
* 是否使用代理默认false.
*
* @var bool
*/
public $useProxy = false;
/**
* 代理设置.
*
* @var array
*/
public $proxy = [];
/**
* 是否验证证书.
*
* @var bool
*/
public $isVerifyCA = false;
/**
* CA根证书路径.
*
* @var string|null
*/
public $caCert;
/**
* 连接超时时间,单位:毫秒.
*
* @var int
*/
public $connectTimeout = 30000;
/**
* 总超时时间,单位:毫秒.
*
* @var int
*/
public $timeout = 30000;
/**
* 下载限速为0则不限制单位字节
*
* @var int|null
*/
public $downloadSpeed;
/**
* 上传限速为0则不限制单位字节
*
* @var int|null
*/
public $uploadSpeed;
/**
* 用于连接中需要的用户名.
*
* @var string|null
*/
public $username;
/**
* 用于连接中需要的密码
*
* @var string|null
*/
public $password;
/**
* 请求结果保存至文件的配置.
*
* @var mixed
*/
public $saveFileOption = [];
/**
* 是否启用重定向.
*
* @var bool
*/
public $followLocation = true;
/**
* 最大重定向次数.
*
* @var int
*/
public $maxRedirects = 10;
/**
* 证书类型
* 支持的格式有"PEM" (默认值), "DER"和"ENG".
*
* @var string
*/
public $certType = 'pem';
/**
* 一个包含 PEM 格式证书的文件名.
*
* @var string
*/
public $certPath = '';
/**
* 使用证书需要的密码
*
* @var string
*/
public $certPassword = null;
/**
* certType规定的私钥的加密类型支持的密钥类型为"PEM"(默认值)、"DER"和"ENG".
*
* @var string
*/
public $keyType = 'pem';
/**
* 包含 SSL 私钥的文件名.
*
* @var string
*/
public $keyPath = '';
/**
* SSL私钥的密码
*
* @var string
*/
public $keyPassword = null;
/**
* 请求方法.
*
* @var string
*/
public $method = 'GET';
/**
* Http 协议版本.
*
* @var string
*/
public $protocolVersion = '1.1';
/**
* 是否启用连接池,默认为 null 时取全局设置.
*
* @var bool|null
*/
public $connectionPool = null;
/**
* 代理认证方式.
*
* @var array
*/
public static $proxyAuths = [];
/**
* 代理类型.
*
* @var array
*/
public static $proxyType = [];
/**
* 自动扩展名标志.
*/
const AUTO_EXT_FLAG = '.*';
/**
* 自动扩展名用的临时文件名.
*/
const AUTO_EXT_TEMP_EXT = '.tmp';
/**
* 构造方法.
*
* @return mixed
*/
public function __construct()
{
$this->open();
}
/**
* 析构方法.
*/
public function __destruct()
{
$this->close();
}
/**
* 打开一个新连接,初始化所有参数。一般不需要手动调用。
*
* @return void
*/
public function open()
{
$this->handler = YurunHttp::getHandler();
$this->retry = 0;
$this->headers = $this->options = [];
$this->url = $this->content = '';
$this->useProxy = false;
$this->proxy = [
'auth' => 'basic',
'type' => 'http',
];
$this->isVerifyCA = false;
$this->caCert = null;
$this->connectTimeout = 30000;
$this->timeout = 30000;
$this->downloadSpeed = null;
$this->uploadSpeed = null;
$this->username = null;
$this->password = null;
$this->saveFileOption = [];
}
/**
* 关闭连接。一般不需要手动调用。
*
* @return void
*/
public function close()
{
if ($this->handler)
{
$handler = $this->handler;
$this->handler = null;
$handler->close();
}
}
/**
* 创建一个新会话等同于new.
*
* @return static
*/
public static function newSession()
{
return new static();
}
/**
* 获取处理器.
*
* @return \Yurun\Util\YurunHttp\Handler\IHandler|null
*/
public function getHandler()
{
return $this->handler;
}
/**
* 设置请求地址
*
* @param string $url 请求地址
*
* @return static
*/
public function url($url)
{
$this->url = $url;
return $this;
}
/**
* 设置发送内容requestBody的别名.
*
* @param mixed $content 发送内容,可以是字符串、数组
*
* @return static
*/
public function content($content)
{
return $this->requestBody($content);
}
/**
* 设置参数requestBody的别名.
*
* @param mixed $params 发送内容,可以是字符串、数组
*
* @return static
*/
public function params($params)
{
return $this->requestBody($params);
}
/**
* 设置请求主体.
*
* @param string|string|array $requestBody 发送内容,可以是字符串、数组
*
* @return static
*/
public function requestBody($requestBody)
{
$this->content = $requestBody;
return $this;
}
/**
* 批量设置CURL的Option.
*
* @param array $options curl_setopt_array()所需要的第二个参数
*
* @return static
*/
public function options($options)
{
$thisOptions = &$this->options;
foreach ($options as $key => $value)
{
$thisOptions[$key] = $value;
}
return $this;
}
/**
* 设置CURL的Option.
*
* @param int $option 需要设置的CURLOPT_XXX选项
* @param mixed $value 值
*
* @return static
*/
public function option($option, $value)
{
$this->options[$option] = $value;
return $this;
}
/**
* 批量设置请求头.
*
* @param array $headers 键值数组
*
* @return static
*/
public function headers($headers)
{
$thisHeaders = &$this->headers;
$thisHeaders = array_merge($thisHeaders, $headers);
return $this;
}
/**
* 设置请求头.
*
* @param string $header 请求头名称
* @param string $value 值
*
* @return static
*/
public function header($header, $value)
{
$this->headers[$header] = $value;
return $this;
}
/**
* 批量设置请求头,.
*
* @param array $headers 纯文本 header 数组
*
* @return static
*/
public function rawHeaders($headers)
{
$thisHeaders = &$this->headers;
foreach ($headers as $header)
{
$list = explode(':', $header, 2);
$thisHeaders[trim($list[0])] = trim($list[1]);
}
return $this;
}
/**
* 设置请求头.
*
* @param string $header 纯文本 header
*
* @return static
*/
public function rawHeader($header)
{
$list = explode(':', $header, 2);
$this->headers[trim($list[0])] = trim($list[1]);
return $this;
}
/**
* 设置Accept.
*
* @param string $accept
*
* @return static
*/
public function accept($accept)
{
$this->headers['Accept'] = $accept;
return $this;
}
/**
* 设置Accept-Language.
*
* @param string $acceptLanguage
*
* @return static
*/
public function acceptLanguage($acceptLanguage)
{
$this->headers['Accept-Language'] = $acceptLanguage;
return $this;
}
/**
* 设置Accept-Encoding.
*
* @param string $acceptEncoding
*
* @return static
*/
public function acceptEncoding($acceptEncoding)
{
$this->headers['Accept-Encoding'] = $acceptEncoding;
return $this;
}
/**
* 设置Accept-Ranges.
*
* @param string $acceptRanges
*
* @return static
*/
public function acceptRanges($acceptRanges)
{
$this->headers['Accept-Ranges'] = $acceptRanges;
return $this;
}
/**
* 设置Cache-Control.
*
* @param string $cacheControl
*
* @return static
*/
public function cacheControl($cacheControl)
{
$this->headers['Cache-Control'] = $cacheControl;
return $this;
}
/**
* 批量设置Cookies.
*
* @param array $cookies 键值对应数组
*
* @return static
*/
public function cookies($cookies)
{
$this->cookies = array_merge($this->cookies, $cookies);
return $this;
}
/**
* 设置Cookie.
*
* @param string $name 名称
* @param string $value 值
*
* @return static
*/
public function cookie($name, $value)
{
$this->cookies[$name] = $value;
return $this;
}
/**
* 设置Content-Type.
*
* @param string $contentType
*
* @return static
*/
public function contentType($contentType)
{
$this->headers['Content-Type'] = $contentType;
return $this;
}
/**
* 设置Range.
*
* @param string $range
*
* @return static
*/
public function range($range)
{
$this->headers['Range'] = $range;
return $this;
}
/**
* 设置Referer.
*
* @param string $referer
*
* @return static
*/
public function referer($referer)
{
$this->headers['Referer'] = $referer;
return $this;
}
/**
* 设置User-Agent.
*
* @param string $userAgent
*
* @return static
*/
public function userAgent($userAgent)
{
$this->headers['User-Agent'] = $userAgent;
return $this;
}
/**
* 设置User-AgentuserAgent的别名.
*
* @param string $userAgent
*
* @return static
*/
public function ua($userAgent)
{
return $this->userAgent($userAgent);
}
/**
* 设置失败重试次数状态码为5XX或者0才需要重试.
*
* @param string $retry
*
* @return static
*/
public function retry($retry)
{
$this->retry = $retry < 0 ? 0 : $retry; //至少请求1次即重试0次
return $this;
}
/**
* 代理.
*
* @param string $server 代理服务器地址
* @param int $port 代理服务器端口
* @param string $type 代理类型支持http、socks4、socks4a、socks5
* @param string $auth 代理认证方式支持basic、ntlm。一般默认basic
*
* @return static
*/
public function proxy($server, $port, $type = 'http', $auth = 'basic')
{
$this->useProxy = true;
$this->proxy = [
'server' => $server,
'port' => $port,
'type' => $type,
'auth' => $auth,
];
return $this;
}
/**
* 代理认证
*
* @param string $username
* @param string $password
*
* @return static
*/
public function proxyAuth($username, $password)
{
$this->proxy['username'] = $username;
$this->proxy['password'] = $password;
return $this;
}
/**
* 设置超时时间.
*
* @param int $timeout 总超时时间,单位:毫秒
* @param int $connectTimeout 连接超时时间,单位:毫秒
*
* @return static
*/
public function timeout($timeout = null, $connectTimeout = null)
{
if (null !== $timeout)
{
$this->timeout = $timeout;
}
if (null !== $connectTimeout)
{
$this->connectTimeout = $connectTimeout;
}
return $this;
}
/**
* 限速
*
* @param int $download 下载速度为0则不限制单位字节
* @param int $upload 上传速度为0则不限制单位字节
*
* @return static
*/
public function limitRate($download = 0, $upload = 0)
{
$this->downloadSpeed = $download;
$this->uploadSpeed = $upload;
return $this;
}
/**
* 设置用于连接中需要的用户名和密码
*
* @param string $username 用户名
* @param string $password 密码
*
* @return static
*/
public function userPwd($username, $password)
{
$this->username = $username;
$this->password = $password;
return $this;
}
/**
* 保存至文件的设置.
*
* @param string $filePath 文件路径
* @param string $fileMode 文件打开方式默认w+
*
* @return static
*/
public function saveFile($filePath, $fileMode = 'w+')
{
$this->saveFileOption['filePath'] = $filePath;
$this->saveFileOption['fileMode'] = $fileMode;
return $this;
}
/**
* 获取文件保存路径.
*
* @return string|null
*/
public function getSavePath()
{
$saveFileOption = $this->saveFileOption;
return isset($saveFileOption['filePath']) ? $saveFileOption['filePath'] : null;
}
/**
* 设置SSL证书.
*
* @param string $path 一个包含 PEM 格式证书的文件名
* @param string $type 证书类型支持的格式有”PEM”(默认值),“DER”和”ENG”
* @param string $password 使用证书需要的密码
*
* @return static
*/
public function sslCert($path, $type = null, $password = null)
{
$this->certPath = $path;
if (null !== $type)
{
$this->certType = $type;
}
if (null !== $password)
{
$this->certPassword = $password;
}
return $this;
}
/**
* 设置SSL私钥.
*
* @param string $path 包含 SSL 私钥的文件名
* @param string $type certType规定的私钥的加密类型支持的密钥类型为”PEM”(默认值)、”DER”和”ENG”
* @param string $password SSL私钥的密码
*
* @return static
*/
public function sslKey($path, $type = null, $password = null)
{
$this->keyPath = $path;
if (null !== $type)
{
$this->keyType = $type;
}
if (null !== $password)
{
$this->keyPassword = $password;
}
return $this;
}
/**
* 设置请求方法.
*
* @param string $method
*
* @return static
*/
public function method($method)
{
$this->method = $method;
return $this;
}
/**
* 设置是否启用连接池.
*
* @param bool $connectionPool
*
* @return static
*/
public function connectionPool($connectionPool)
{
$this->connectionPool = $connectionPool;
return $this;
}
/**
* 处理请求主体.
*
* @param string|string|array $requestBody
* @param string|null $contentType 内容类型支持null/json为null时不处理
*
* @return array
*/
protected function parseRequestBody($requestBody, $contentType)
{
$body = $files = [];
if (\is_string($requestBody))
{
$body = $requestBody;
}
elseif (\is_array($requestBody))
{
switch ($contentType)
{
case 'json':
$body = json_encode($requestBody);
$this->header('Content-Type', MediaType::APPLICATION_JSON);
break;
default:
foreach ($requestBody as $k => $v)
{
if ($v instanceof UploadedFile)
{
$files[$k] = $v;
}
else
{
$body[$k] = $v;
}
}
$body = http_build_query($body, '', '&');
}
}
else
{
throw new \InvalidArgumentException('$requestBody only can be string or array');
}
return [$body, $files];
}
/**
* 构建请求类.
*
* @param string $url 请求地址如果为null则取url属性值
* @param string|array $requestBody 发送内容可以是字符串、数组如果为空则取content属性值
* @param string|null $method 请求方法GET、POST等
* @param string|null $contentType 内容类型支持null/json为null时不处理
*
* @return \Yurun\Util\YurunHttp\Http\Request
*/
public function buildRequest($url = null, $requestBody = null, $method = null, $contentType = null)
{
if (null === $url)
{
$url = $this->url;
}
if (null === $method)
{
$method = $this->method;
}
list($body, $files) = $this->parseRequestBody(null === $requestBody ? $this->content : $requestBody, $contentType);
$request = new Request($url, $this->headers, $body, $method);
$saveFileOption = $this->saveFileOption;
$request = $request->withUploadedFiles($files)
->withCookieParams($this->cookies)
->withAttribute(Attributes::MAX_REDIRECTS, $this->maxRedirects)
->withAttribute(Attributes::IS_VERIFY_CA, $this->isVerifyCA)
->withAttribute(Attributes::CA_CERT, $this->caCert)
->withAttribute(Attributes::CERT_PATH, $this->certPath)
->withAttribute(Attributes::CERT_PASSWORD, $this->certPassword)
->withAttribute(Attributes::CERT_TYPE, $this->certType)
->withAttribute(Attributes::KEY_PATH, $this->keyPath)
->withAttribute(Attributes::KEY_PASSWORD, $this->keyPassword)
->withAttribute(Attributes::KEY_TYPE, $this->keyType)
->withAttribute(Attributes::OPTIONS, $this->options)
->withAttribute(Attributes::SAVE_FILE_PATH, isset($saveFileOption['filePath']) ? $saveFileOption['filePath'] : null)
->withAttribute(Attributes::USE_PROXY, $this->useProxy)
->withAttribute(Attributes::USERNAME, $this->username)
->withAttribute(Attributes::PASSWORD, $this->password)
->withAttribute(Attributes::CONNECT_TIMEOUT, $this->connectTimeout)
->withAttribute(Attributes::TIMEOUT, $this->timeout)
->withAttribute(Attributes::DOWNLOAD_SPEED, $this->downloadSpeed)
->withAttribute(Attributes::UPLOAD_SPEED, $this->uploadSpeed)
->withAttribute(Attributes::FOLLOW_LOCATION, $this->followLocation)
->withAttribute(Attributes::CONNECTION_POOL, $this->connectionPool)
->withProtocolVersion($this->protocolVersion)
;
foreach ($this->proxy as $name => $value)
{
$request = $request->withAttribute('proxy.' . $name, $value);
}
return $request;
}
/**
* 发送请求,所有请求的老祖宗.
*
* @param string|null $url 请求地址如果为null则取url属性值
* @param string|array|null $requestBody 发送内容可以是字符串、数组如果为空则取content属性值
* @param string $method 请求方法GET、POST等
* @param string|null $contentType 内容类型支持null/json为null时不处理
*
* @return \Yurun\Util\YurunHttp\Http\Response|null
*/
public function send($url = null, $requestBody = null, $method = null, $contentType = null)
{
$request = $this->buildRequest($url, $requestBody, $method, $contentType);
return YurunHttp::send($request, $this->handler);
}
/**
* 发送 Http2 请求不调用 recv().
*
* @param string|null $url 请求地址如果为null则取url属性值
* @param string|array|null $requestBody 发送内容可以是字符串、数组如果为空则取content属性值
* @param string $method 请求方法GET、POST等
* @param string|null $contentType 内容类型支持null/json为null时不处理
*
* @return \Yurun\Util\YurunHttp\Http\Response|null
*/
public function sendHttp2WithoutRecv($url = null, $requestBody = null, $method = 'GET', $contentType = null)
{
$request = $this->buildRequest($url, $requestBody, $method, $contentType)
->withProtocolVersion('2.0')
->withAttribute(Attributes::HTTP2_NOT_RECV, true);
return YurunHttp::send($request, $this->handler);
}
/**
* GET请求
*
* @param string $url 请求地址如果为null则取url属性值
* @param string|array $requestBody 发送内容可以是字符串、数组如果为空则取content属性值
*
* @return \Yurun\Util\YurunHttp\Http\Response|null
*/
public function get($url = null, $requestBody = null)
{
if (!empty($requestBody))
{
if (strpos($url, '?'))
{
$url .= '&';
}
else
{
$url .= '?';
}
$url .= http_build_query($requestBody, '', '&');
}
return $this->send($url, [], 'GET');
}
/**
* POST请求
*
* @param string $url 请求地址如果为null则取url属性值
* @param string|array $requestBody 发送内容可以是字符串、数组如果为空则取content属性值
* @param string|null $contentType 内容类型支持null/json为null时不处理
*
* @return \Yurun\Util\YurunHttp\Http\Response|null
*/
public function post($url = null, $requestBody = null, $contentType = null)
{
return $this->send($url, $requestBody, 'POST', $contentType);
}
/**
* HEAD请求
*
* @param string $url 请求地址如果为null则取url属性值
* @param string|array $requestBody 发送内容可以是字符串、数组如果为空则取content属性值
*
* @return \Yurun\Util\YurunHttp\Http\Response|null
*/
public function head($url = null, $requestBody = null)
{
return $this->send($url, $requestBody, 'HEAD');
}
/**
* PUT请求
*
* @param string $url 请求地址如果为null则取url属性值
* @param string|array $requestBody 发送内容可以是字符串、数组如果为空则取content属性值
* @param string|null $contentType 内容类型支持null/json为null时不处理
*
* @return \Yurun\Util\YurunHttp\Http\Response|null
*/
public function put($url = null, $requestBody = null, $contentType = null)
{
return $this->send($url, $requestBody, 'PUT', $contentType);
}
/**
* PATCH请求
*
* @param string $url 请求地址如果为null则取url属性值
* @param string|array $requestBody 发送内容可以是字符串、数组如果为空则取content属性值
* @param string|null $contentType 内容类型支持null/json为null时不处理
*
* @return \Yurun\Util\YurunHttp\Http\Response|null
*/
public function patch($url = null, $requestBody = null, $contentType = null)
{
return $this->send($url, $requestBody, 'PATCH', $contentType);
}
/**
* DELETE请求
*
* @param string $url 请求地址如果为null则取url属性值
* @param string|array $requestBody 发送内容可以是字符串、数组如果为空则取content属性值
* @param string|null $contentType 内容类型支持null/json为null时不处理
*
* @return \Yurun\Util\YurunHttp\Http\Response|null
*/
public function delete($url = null, $requestBody = null, $contentType = null)
{
return $this->send($url, $requestBody, 'DELETE', $contentType);
}
/**
* 直接下载文件.
*
* @param string $fileName 保存路径,如果以 .* 结尾,则根据 Content-Type 自动决定扩展名
* @param string $url 下载文件地址
* @param string|array $requestBody 发送内容可以是字符串、数组如果为空则取content属性值
* @param string $method 请求方法GET、POST等一般用GET
*
* @return \Yurun\Util\YurunHttp\Http\Response|null
*/
public function download($fileName, $url = null, $requestBody = null, $method = 'GET')
{
$isAutoExt = self::checkDownloadIsAutoExt($fileName, $fileName);
$result = $this->saveFile($fileName)->send($url, $requestBody, $method);
if ($isAutoExt)
{
self::parseDownloadAutoExt($result, $fileName);
}
$this->saveFileOption = [];
return $result;
}
/**
* WebSocket.
*
* @param string $url
*
* @return \Yurun\Util\YurunHttp\WebSocket\IWebSocketClient
*/
public function websocket($url = null)
{
$request = $this->buildRequest($url);
return YurunHttp::websocket($request, $this->handler);
}
/**
* 检查下载文件名是否要自动扩展名.
*
* @param string $fileName
* @param string $tempFileName
*
* @return bool
*/
public static function checkDownloadIsAutoExt($fileName, &$tempFileName)
{
$flagLength = \strlen(self::AUTO_EXT_FLAG);
if (self::AUTO_EXT_FLAG !== substr($fileName, -$flagLength))
{
return false;
}
$tempFileName = substr($fileName, 0, -$flagLength) . self::AUTO_EXT_TEMP_EXT;
return true;
}
/**
* 处理下载的自动扩展名.
*
* @param \Yurun\Util\YurunHttp\Http\Response $response
* @param string $tempFileName
*
* @return void
*/
public static function parseDownloadAutoExt(&$response, $tempFileName)
{
$ext = MediaType::getExt($response->getHeaderLine('Content-Type'));
if (null === $ext)
{
$ext = 'file';
}
$savedFileName = substr($tempFileName, 0, -\strlen(self::AUTO_EXT_TEMP_EXT)) . '.' . $ext;
rename($tempFileName, $savedFileName);
$response = $response->withSavedFileName($savedFileName);
}
}
if (\extension_loaded('curl'))
{
// 代理认证方式
HttpRequest::$proxyAuths = [
'basic' => \CURLAUTH_BASIC,
'ntlm' => \CURLAUTH_NTLM,
];
// 代理类型
HttpRequest::$proxyType = [
'http' => \CURLPROXY_HTTP,
'socks4' => \CURLPROXY_SOCKS4,
'socks4a' => 6, // CURLPROXY_SOCKS4A
'socks5' => \CURLPROXY_SOCKS5,
];
}