diff --git a/src/HttpRequest.php b/src/HttpRequest.php index f88b134..106d190 100644 --- a/src/HttpRequest.php +++ b/src/HttpRequest.php @@ -5,6 +5,7 @@ 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\Psr7\Uri; use Yurun\Util\YurunHttp\Http\Request; class HttpRequest @@ -448,6 +449,10 @@ public function rawHeaders($headers) $thisHeaders = &$this->headers; foreach ($headers as $header) { + if (!str_contains($header, ':')) + { + continue; + } $list = explode(':', $header, 2); $thisHeaders[trim($list[0])] = trim($list[1]); } @@ -464,6 +469,10 @@ public function rawHeaders($headers) */ public function rawHeader($header) { + if (!str_contains($header, ':')) + { + return $this; + } $list = explode(':', $header, 2); $this->headers[trim($list[0])] = trim($list[1]); @@ -1014,15 +1023,18 @@ public function get($url = null, $requestBody = null) { if (!empty($requestBody)) { - if (strpos($url, '?')) + if (null === $url) { - $url .= '&'; + $url = $this->url; } - else + $uri = new Uri($url); + $query = $uri->getQuery(); + if ('' !== $query) { - $url .= '?'; + $query .= '&'; } - $url .= http_build_query($requestBody, '', '&'); + $query .= http_build_query($requestBody, '', '&'); + $url = (string) $uri->withQuery($query); } return $this->send($url, [], 'GET'); diff --git a/src/YurunHttp/Handler/Curl.php b/src/YurunHttp/Handler/Curl.php index 822ac48..9d2c40e 100644 --- a/src/YurunHttp/Handler/Curl.php +++ b/src/YurunHttp/Handler/Curl.php @@ -462,7 +462,7 @@ private function parseHeaderOneRequest($headers) for ($i = 1; $i < $count; ++$i) { $line = trim($headers[$i]); - if (empty($line) || false == strstr($line, ':')) + if (empty($line) || false === strstr($line, ':')) { continue; } @@ -570,6 +570,10 @@ private function parseOptions(&$request, &$options, &$headers = null, &$saveFile $saveFilePath .= basename($request->getUri()->__toString()); } $saveFileFp = fopen($saveFilePath, $request->getAttribute(Attributes::SAVE_FILE_MODE, 'w+')); + if (false === $saveFileFp) + { + throw new \RuntimeException(sprintf('fopen save file failed: %s', $saveFilePath)); + } $options[\CURLOPT_HEADER] = false; $options[\CURLOPT_RETURNTRANSFER] = false; $options[\CURLOPT_FILE] = $saveFileFp; @@ -670,7 +674,7 @@ private function parseNetwork(&$request, &$options) { // 用户名密码处理 $username = $request->getAttribute(Attributes::USERNAME); - if (null != $username) + if (null !== $username) { $userPwd = $username . ':' . $request->getAttribute(Attributes::PASSWORD, ''); } @@ -762,7 +766,10 @@ public function coBatch($requests, $timeout = null) { break; } - usleep(5000); // 每次延时 5 毫秒 + if (-1 === curl_multi_select($mh, 0.5)) + { + usleep(250); + } } else { diff --git a/src/YurunHttp/Handler/Swoole.php b/src/YurunHttp/Handler/Swoole.php index 61a0db4..ec68b01 100644 --- a/src/YurunHttp/Handler/Swoole.php +++ b/src/YurunHttp/Handler/Swoole.php @@ -810,7 +810,6 @@ public function coBatch($requests, $timeout = null) $request1 = $handler->sendDefer($request1); } unset($request1); - $beginTime = microtime(true); $recvTimeout = null; foreach ($requests as $i => $request2) { diff --git a/src/YurunHttp/Handler/Swoole/SwooleHttp2ConnectionPool.php b/src/YurunHttp/Handler/Swoole/SwooleHttp2ConnectionPool.php index c2ce25f..17c94f2 100644 --- a/src/YurunHttp/Handler/Swoole/SwooleHttp2ConnectionPool.php +++ b/src/YurunHttp/Handler/Swoole/SwooleHttp2ConnectionPool.php @@ -29,7 +29,9 @@ class SwooleHttp2ConnectionPool extends BaseConnectionPool public function __construct($config) { parent::__construct($config); - $this->channel = new Channel(1024); + $maxConnections = $config->getMaxConnections(); + $capacity = $maxConnections > 0 ? $maxConnections : 1024; + $this->channel = new Channel($capacity); } /** @@ -41,7 +43,9 @@ public function close() { $connections = $this->connections; $this->connections = []; - $this->channel = new Channel(1024); + $maxConnections = $this->config->getMaxConnections(); + $capacity = $maxConnections > 0 ? $maxConnections : 1024; + $this->channel = new Channel($capacity); foreach ($connections as $connection) { $connection->close(); diff --git a/src/YurunHttp/Handler/Swoole/SwooleHttpConnectionPool.php b/src/YurunHttp/Handler/Swoole/SwooleHttpConnectionPool.php index ddc908d..581a525 100644 --- a/src/YurunHttp/Handler/Swoole/SwooleHttpConnectionPool.php +++ b/src/YurunHttp/Handler/Swoole/SwooleHttpConnectionPool.php @@ -29,7 +29,9 @@ class SwooleHttpConnectionPool extends BaseConnectionPool public function __construct($config) { parent::__construct($config); - $this->channel = new Channel(1024); + $maxConnections = $config->getMaxConnections(); + $capacity = $maxConnections > 0 ? $maxConnections : 1024; + $this->channel = new Channel($capacity); } /** @@ -41,7 +43,9 @@ public function close() { $connections = $this->connections; $this->connections = []; - $this->channel = new Channel(1024); + $maxConnections = $this->config->getMaxConnections(); + $capacity = $maxConnections > 0 ? $maxConnections : 1024; + $this->channel = new Channel($capacity); foreach ($connections as $connection) { $connection->close(); diff --git a/tests/unit/HttpRequestTest/HttpRequestTest.php b/tests/unit/HttpRequestTest/HttpRequestTest.php index dcb56e9..38d4bb6 100644 --- a/tests/unit/HttpRequestTest/HttpRequestTest.php +++ b/tests/unit/HttpRequestTest/HttpRequestTest.php @@ -825,4 +825,97 @@ public function testCookieJar(): void $this->assertEquals('3', isset($data['cookie']['c']) ? $data['cookie']['c'] : null); }); } + + /** + * rawHeader/rawHeaders 跳过不含冒号的行(避免未定义索引警告). + * + * @return void + */ + public function testRawHeaderWithoutColon(): void + { + $this->call(function () { + $http = new HttpRequest(); + $http->rawHeader('X-NoColon') + ->rawHeaders([ + 'X-Valid: 1', + 'X-NoColon2', + ]); + $this->assertArrayHasKey('X-Valid', $http->headers); + $this->assertEquals('1', $http->headers['X-Valid']); + $this->assertArrayNotHasKey('X-NoColon', $http->headers); + $this->assertArrayNotHasKey('X-NoColon2', $http->headers); + }); + } + + /** + * get() 在已有查询参数时正确拼接请求体. + * + * @return void + */ + public function testGetWithExistingQueryAndBody(): void + { + $this->call(function () { + $http = new HttpRequest(); + $time = time(); + $response = $http->get($this->host . '?a=info', [ + 'time' => $time, + ]); + $this->assertResponse($response); + $data = $response->json(true); + $this->assertEquals('GET', isset($data['method']) ? $data['method'] : null); + $this->assertEquals($time, isset($data['get']['time']) ? $data['get']['time'] : null); + }); + } + + /** + * 保存文件 fopen 失败时抛出 RuntimeException. + * + * @return void + */ + public function testSaveFileFopenFailureThrows(): void + { + $this->expectException(\RuntimeException::class); + $this->call(function () { + YurunHttp::setDefaultHandler(\Yurun\Util\YurunHttp\Handler\Curl::class); + set_error_handler(function () { + return true; + }, \E_WARNING); + try + { + $http = new HttpRequest(); + $http->saveFile('/path/does/not/exist/yurunhttp_test_save.txt') + ->get($this->host); + } + finally + { + restore_error_handler(); + } + }); + } + + /** + * 连接池 Channel 容量等于最大连接数,未配置时为 1024. + * + * @return void + */ + public function testConnectionPoolChannelCapacity(): void + { + if (!\extension_loaded('swoole')) + { + $this->markTestSkipped('Requires ext/swoole'); + } + $config = new \Yurun\Util\YurunHttp\Pool\Config\PoolConfig($this->host, 5, 0.0); + $pool = new \Yurun\Util\YurunHttp\Handler\Swoole\SwooleHttpConnectionPool($config); + $ref = new \ReflectionProperty($pool, 'channel'); + $ref->setAccessible(true); + $channel = $ref->getValue($pool); + $this->assertEquals(5, $channel->capacity); + + $config2 = new \Yurun\Util\YurunHttp\Pool\Config\PoolConfig($this->host, 0, 0.0); + $pool2 = new \Yurun\Util\YurunHttp\Handler\Swoole\SwooleHttpConnectionPool($config2); + $ref2 = new \ReflectionProperty($pool2, 'channel'); + $ref2->setAccessible(true); + $channel2 = $ref2->getValue($pool2); + $this->assertEquals(1024, $channel2->capacity); + } }