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-Agent,userAgent的别名. * * @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, ]; }