From eb1d0003d66093636a42c5818519e9ae66dd1e9a Mon Sep 17 00:00:00 2001 From: Yurun Date: Wed, 8 Jul 2026 21:01:31 +0800 Subject: [PATCH 01/10] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20rawHeader/rawHeaders?= =?UTF-8?q?=20=E5=A4=84=E7=90=86=E4=B8=8D=E5=90=AB=E5=86=92=E5=8F=B7?= =?UTF-8?q?=E7=9A=84=E8=A1=8C=E6=97=B6=E4=BA=A7=E7=94=9F=E6=9C=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E7=B4=A2=E5=BC=95=E8=AD=A6=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/HttpRequest.php | 8 +++++++ .../unit/HttpRequestTest/HttpRequestTest.php | 21 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/HttpRequest.php b/src/HttpRequest.php index f88b134..83bd14c 100644 --- a/src/HttpRequest.php +++ b/src/HttpRequest.php @@ -448,6 +448,10 @@ public function rawHeaders($headers) $thisHeaders = &$this->headers; foreach ($headers as $header) { + if (false === strpos($header, ':')) + { + continue; + } $list = explode(':', $header, 2); $thisHeaders[trim($list[0])] = trim($list[1]); } @@ -464,6 +468,10 @@ public function rawHeaders($headers) */ public function rawHeader($header) { + if (false === strpos($header, ':')) + { + return $this; + } $list = explode(':', $header, 2); $this->headers[trim($list[0])] = trim($list[1]); diff --git a/tests/unit/HttpRequestTest/HttpRequestTest.php b/tests/unit/HttpRequestTest/HttpRequestTest.php index dcb56e9..b51d056 100644 --- a/tests/unit/HttpRequestTest/HttpRequestTest.php +++ b/tests/unit/HttpRequestTest/HttpRequestTest.php @@ -825,4 +825,25 @@ 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); + }); + } } From 88a51ecc9538edf6873effa376ccacb3a3f27475 Mon Sep 17 00:00:00 2001 From: Yurun Date: Wed, 8 Jul 2026 21:02:01 +0800 Subject: [PATCH 02/10] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20get()=20=E5=9C=A8?= =?UTF-8?q?=E5=B7=B2=E6=9C=89=E6=9F=A5=E8=AF=A2=E5=8F=82=E6=95=B0=E6=97=B6?= =?UTF-8?q?=E6=8B=BC=E6=8E=A5=E8=AF=B7=E6=B1=82=E4=BD=93=E7=9A=84=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/HttpRequest.php | 14 ++++++++----- .../unit/HttpRequestTest/HttpRequestTest.php | 20 +++++++++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/HttpRequest.php b/src/HttpRequest.php index 83bd14c..13072bc 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 @@ -1022,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/tests/unit/HttpRequestTest/HttpRequestTest.php b/tests/unit/HttpRequestTest/HttpRequestTest.php index b51d056..4cf534e 100644 --- a/tests/unit/HttpRequestTest/HttpRequestTest.php +++ b/tests/unit/HttpRequestTest/HttpRequestTest.php @@ -846,4 +846,24 @@ public function testRawHeaderWithoutColon(): void $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); + }); + } } From e38cac1944a4950615a3538b6a0548d8b504a776 Mon Sep 17 00:00:00 2001 From: Yurun Date: Wed, 8 Jul 2026 21:02:18 +0800 Subject: [PATCH 03/10] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E6=97=B6=20fopen=20=E5=A4=B1=E8=B4=A5?= =?UTF-8?q?=E6=9C=AA=E6=8A=9B=E5=87=BA=E5=BC=82=E5=B8=B8=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/YurunHttp/Handler/Curl.php | 4 ++++ .../unit/HttpRequestTest/HttpRequestTest.php | 23 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/YurunHttp/Handler/Curl.php b/src/YurunHttp/Handler/Curl.php index 822ac48..7a795c1 100644 --- a/src/YurunHttp/Handler/Curl.php +++ b/src/YurunHttp/Handler/Curl.php @@ -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; diff --git a/tests/unit/HttpRequestTest/HttpRequestTest.php b/tests/unit/HttpRequestTest/HttpRequestTest.php index 4cf534e..29f87de 100644 --- a/tests/unit/HttpRequestTest/HttpRequestTest.php +++ b/tests/unit/HttpRequestTest/HttpRequestTest.php @@ -866,4 +866,27 @@ public function testGetWithExistingQueryAndBody(): void $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(); + } + }); + } } From 1c5267055f5dcfa5c457bef8302082f79d18f1bf Mon Sep 17 00:00:00 2001 From: Yurun Date: Wed, 8 Jul 2026 21:02:33 +0800 Subject: [PATCH 04/10] =?UTF-8?q?=E4=BC=98=E5=8C=96=20Curl=20=E5=B9=B6?= =?UTF-8?q?=E5=8F=91=E8=AF=B7=E6=B1=82=E8=BD=AE=E8=AF=A2=EF=BC=8C=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=20curl=5Fmulti=5Fselect=20=E6=9B=BF=E4=BB=A3=E5=9B=BA?= =?UTF-8?q?=E5=AE=9A=E5=BB=B6=E6=97=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/YurunHttp/Handler/Curl.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/YurunHttp/Handler/Curl.php b/src/YurunHttp/Handler/Curl.php index 7a795c1..168663d 100644 --- a/src/YurunHttp/Handler/Curl.php +++ b/src/YurunHttp/Handler/Curl.php @@ -766,7 +766,7 @@ public function coBatch($requests, $timeout = null) { break; } - usleep(5000); // 每次延时 5 毫秒 + curl_multi_select($mh, 0.5); } else { From 03eb9f76fab12e0dcbebf25659ef064114958457 Mon Sep 17 00:00:00 2001 From: Yurun Date: Wed, 8 Jul 2026 21:02:46 +0800 Subject: [PATCH 05/10] =?UTF-8?q?=E5=B0=86=20Curl=20Handler=20=E4=B8=AD?= =?UTF-8?q?=E7=9A=84=E6=9D=BE=E6=95=A3=E6=AF=94=E8=BE=83=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E4=B8=A5=E6=A0=BC=E6=AF=94=E8=BE=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/YurunHttp/Handler/Curl.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/YurunHttp/Handler/Curl.php b/src/YurunHttp/Handler/Curl.php index 168663d..2be5238 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; } @@ -674,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, ''); } From 8fdf0b5f0dbcd5f9ce0bd614755bce92e6515db4 Mon Sep 17 00:00:00 2001 From: Yurun Date: Wed, 8 Jul 2026 21:02:53 +0800 Subject: [PATCH 06/10] =?UTF-8?q?=E7=A7=BB=E9=99=A4=20Swoole=20=E5=B9=B6?= =?UTF-8?q?=E5=8F=91=E8=AF=B7=E6=B1=82=E4=B8=AD=E9=87=8D=E5=A4=8D=E7=9A=84?= =?UTF-8?q?=20beginTime=20=E8=B5=8B=E5=80=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/YurunHttp/Handler/Swoole.php | 1 - 1 file changed, 1 deletion(-) 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) { From 74b23135551282efbd69a25a6b0c9ca53636e416 Mon Sep 17 00:00:00 2001 From: Yurun Date: Wed, 8 Jul 2026 21:03:24 +0800 Subject: [PATCH 07/10] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20Swoole=20=E8=BF=9E?= =?UTF-8?q?=E6=8E=A5=E6=B1=A0=20Channel=20=E5=AE=B9=E9=87=8F=E6=9C=AA?= =?UTF-8?q?=E6=A0=B9=E6=8D=AE=E6=9C=80=E5=A4=A7=E8=BF=9E=E6=8E=A5=E6=95=B0?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Swoole/SwooleHttp2ConnectionPool.php | 8 +++++-- .../Swoole/SwooleHttpConnectionPool.php | 8 +++++-- .../unit/HttpRequestTest/HttpRequestTest.php | 22 +++++++++++++++++++ 3 files changed, 34 insertions(+), 4 deletions(-) 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 29f87de..135d5ca 100644 --- a/tests/unit/HttpRequestTest/HttpRequestTest.php +++ b/tests/unit/HttpRequestTest/HttpRequestTest.php @@ -889,4 +889,26 @@ public function testSaveFileFopenFailureThrows(): void } }); } + + /** + * 连接池 Channel 容量等于最大连接数,未配置时为 1024. + * + * @return void + */ + public function testConnectionPoolChannelCapacity(): void + { + $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); + } } From a51bfa496b54da69d2c333cfda30a294dfe120f3 Mon Sep 17 00:00:00 2001 From: Yurun Date: Wed, 8 Jul 2026 21:19:33 +0800 Subject: [PATCH 08/10] =?UTF-8?q?curl=5Fmulti=5Fselect=20=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=20-1=20=E6=97=B6=E5=9B=9E=E9=80=80=E4=B8=BA=20usleep(?= =?UTF-8?q?250)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/YurunHttp/Handler/Curl.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/YurunHttp/Handler/Curl.php b/src/YurunHttp/Handler/Curl.php index 2be5238..9d2c40e 100644 --- a/src/YurunHttp/Handler/Curl.php +++ b/src/YurunHttp/Handler/Curl.php @@ -766,7 +766,10 @@ public function coBatch($requests, $timeout = null) { break; } - curl_multi_select($mh, 0.5); + if (-1 === curl_multi_select($mh, 0.5)) + { + usleep(250); + } } else { From 5ca49d6a830417be14f014e54ef5f6bd090d4671 Mon Sep 17 00:00:00 2001 From: Yurun Date: Wed, 8 Jul 2026 21:24:08 +0800 Subject: [PATCH 09/10] =?UTF-8?q?=E6=A0=BC=E5=BC=8F=E5=8C=96=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/HttpRequest.php | 4 ++-- tests/unit/HttpRequestTest/HttpRequestTest.php | 7 +++++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/HttpRequest.php b/src/HttpRequest.php index 13072bc..106d190 100644 --- a/src/HttpRequest.php +++ b/src/HttpRequest.php @@ -449,7 +449,7 @@ public function rawHeaders($headers) $thisHeaders = &$this->headers; foreach ($headers as $header) { - if (false === strpos($header, ':')) + if (!str_contains($header, ':')) { continue; } @@ -469,7 +469,7 @@ public function rawHeaders($headers) */ public function rawHeader($header) { - if (false === strpos($header, ':')) + if (!str_contains($header, ':')) { return $this; } diff --git a/tests/unit/HttpRequestTest/HttpRequestTest.php b/tests/unit/HttpRequestTest/HttpRequestTest.php index 135d5ca..ca5a755 100644 --- a/tests/unit/HttpRequestTest/HttpRequestTest.php +++ b/tests/unit/HttpRequestTest/HttpRequestTest.php @@ -880,11 +880,14 @@ public function testSaveFileFopenFailureThrows(): void set_error_handler(function () { return true; }, \E_WARNING); - try { + try + { $http = new HttpRequest(); $http->saveFile('/path/does/not/exist/yurunhttp_test_save.txt') ->get($this->host); - } finally { + } + finally + { restore_error_handler(); } }); From c15e4be87d5b7e52479bb12772d9f7e679f41a72 Mon Sep 17 00:00:00 2001 From: Yurun Date: Wed, 8 Jul 2026 21:29:06 +0800 Subject: [PATCH 10/10] =?UTF-8?q?=E8=BF=9E=E6=8E=A5=E6=B1=A0=E5=AE=B9?= =?UTF-8?q?=E9=87=8F=E6=B5=8B=E8=AF=95=E5=9C=A8=E6=97=A0=20Swoole=20?= =?UTF-8?q?=E7=8E=AF=E5=A2=83=EF=BC=88=E5=A6=82=20Windows=EF=BC=89?= =?UTF-8?q?=E4=B8=8B=E8=B7=B3=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unit/HttpRequestTest/HttpRequestTest.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/unit/HttpRequestTest/HttpRequestTest.php b/tests/unit/HttpRequestTest/HttpRequestTest.php index ca5a755..38d4bb6 100644 --- a/tests/unit/HttpRequestTest/HttpRequestTest.php +++ b/tests/unit/HttpRequestTest/HttpRequestTest.php @@ -900,6 +900,10 @@ public function testSaveFileFopenFailureThrows(): 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');