diff --git a/HttpClientTrait.php b/HttpClientTrait.php index 8d270c0..f469280 100644 --- a/HttpClientTrait.php +++ b/HttpClientTrait.php @@ -627,7 +627,10 @@ private static function resolveUrl(array $url, ?array $base, array $queryDefault private static function parseUrl(string $url, array $query = [], array $allowedSchemes = ['http' => 80, 'https' => 443]): array { if (false === $parts = parse_url($url)) { - throw new InvalidArgumentException(sprintf('Malformed URL "%s".', $url)); + if ('/' !== ($url[0] ?? '') || false === $parts = parse_url($url.'#')) { + throw new InvalidArgumentException(sprintf('Malformed URL "%s".', $url)); + } + unset($parts['fragment']); } if ($query) { diff --git a/Internal/CurlClientState.php b/Internal/CurlClientState.php index ee0bafc..2a15248 100644 --- a/Internal/CurlClientState.php +++ b/Internal/CurlClientState.php @@ -49,8 +49,8 @@ public function __construct(int $maxHostConnections, int $maxPendingPushes) if (\defined('CURLPIPE_MULTIPLEX')) { curl_multi_setopt($this->handle, \CURLMOPT_PIPELINING, \CURLPIPE_MULTIPLEX); } - if (\defined('CURLMOPT_MAX_HOST_CONNECTIONS')) { - $maxHostConnections = curl_multi_setopt($this->handle, \CURLMOPT_MAX_HOST_CONNECTIONS, 0 < $maxHostConnections ? $maxHostConnections : \PHP_INT_MAX) ? 0 : $maxHostConnections; + if (\defined('CURLMOPT_MAX_HOST_CONNECTIONS') && 0 < $maxHostConnections) { + $maxHostConnections = curl_multi_setopt($this->handle, \CURLMOPT_MAX_HOST_CONNECTIONS, $maxHostConnections) ? 4294967295 : $maxHostConnections; } if (\defined('CURLMOPT_MAXCONNECTS') && 0 < $maxHostConnections) { curl_multi_setopt($this->handle, \CURLMOPT_MAXCONNECTS, $maxHostConnections); diff --git a/NativeHttpClient.php b/NativeHttpClient.php index db5cee6..5dad1d2 100644 --- a/NativeHttpClient.php +++ b/NativeHttpClient.php @@ -414,7 +414,7 @@ private static function createRedirectResolver(array $options, string $host, str [$host, $port] = self::parseHostPort($url, $info); - if (false !== (parse_url($location, \PHP_URL_HOST) ?? false)) { + if (false !== (parse_url($location.'#', \PHP_URL_HOST) ?? false)) { // Authorization and Cookie headers MUST NOT follow except for the initial host name $requestHeaders = $redirectHeaders['host'] === $host && $redirectHeaders['port'] === $port ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth']; $requestHeaders[] = 'Host: '.$host.$port; diff --git a/Tests/CurlHttpClientTest.php b/Tests/CurlHttpClientTest.php index 03a939b..53307bf 100644 --- a/Tests/CurlHttpClientTest.php +++ b/Tests/CurlHttpClientTest.php @@ -138,4 +138,34 @@ public function testKeepAuthorizationHeaderOnRedirectToSameHostWithConfiguredHos $this->assertSame(200, $response->getStatusCode()); $this->assertSame('/302', $response->toArray()['REQUEST_URI'] ?? null); } + + /** + * @group integration + */ + public function testMaxConnections() + { + foreach ($ports = [80, 8681, 8682, 8683, 8684] as $port) { + if (!($fp = @fsockopen('localhost', $port, $errorCode, $errorMessage, 2))) { + self::markTestSkipped('FrankenPHP is not running'); + } + fclose($fp); + } + + $httpClient = $this->getHttpClient(__FUNCTION__); + + $expectedResults = [ + [false, false, false, false, false], + [true, true, true, true, true], + [true, true, true, true, true], + ]; + + foreach ($expectedResults as $expectedResult) { + foreach ($ports as $i => $port) { + $response = $httpClient->request('GET', \sprintf('http://localhost:%s/http-client', $port)); + $response->getContent(); + + self::assertSame($expectedResult[$i], str_contains($response->getInfo('debug'), 'Re-using existing connection')); + } + } + } } diff --git a/Tests/Fixtures/response-functional/index.php b/Tests/Fixtures/response-functional/index.php new file mode 100644 index 0000000..7a8076a --- /dev/null +++ b/Tests/Fixtures/response-functional/index.php @@ -0,0 +1,12 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +echo 'Success'; diff --git a/Tests/ScopingHttpClientTest.php b/Tests/ScopingHttpClientTest.php index 3e02111..0fbda4e 100644 --- a/Tests/ScopingHttpClientTest.php +++ b/Tests/ScopingHttpClientTest.php @@ -49,16 +49,16 @@ public function testMatchingUrls(string $regexp, string $url, array $options) $this->assertSame($options[$regexp]['case'], $requestedOptions['case']); } - public static function provideMatchingUrls() + public static function provideMatchingUrls(): iterable { $defaultOptions = [ '.*/foo-bar' => ['case' => 1], '.*' => ['case' => 2], ]; - yield ['regexp' => '.*/foo-bar', 'url' => 'http://example.com/foo-bar', 'default_options' => $defaultOptions]; - yield ['regexp' => '.*', 'url' => 'http://example.com/bar-foo', 'default_options' => $defaultOptions]; - yield ['regexp' => '.*', 'url' => 'http://example.com/foobar', 'default_options' => $defaultOptions]; + yield ['regexp' => '.*/foo-bar', 'url' => 'http://example.com/foo-bar', 'options' => $defaultOptions]; + yield ['regexp' => '.*', 'url' => 'http://example.com/bar-foo', 'options' => $defaultOptions]; + yield ['regexp' => '.*', 'url' => 'http://example.com/foobar', 'options' => $defaultOptions]; } public function testMatchingUrlsAndOptions()