diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..f2f51dd --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +/.gitattributes export-ignore +/.gitignore export-ignore +/.travis.yml export-ignore +/examples export-ignore +/phpunit.xml.dist export-ignore +/tests export-ignore diff --git a/.travis.yml b/.travis.yml index 46a0486..364429b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,5 @@ language: php -php: -# - 5.3 # requires old distro - - 5.4 - - 5.5 - - 5.6 - - 7.0 - - 7.1 - - 7.2 - - nightly # ignore errors, see below - - hhvm # ignore errors, see below - # lock distro so new future defaults will not break the build dist: trusty @@ -18,9 +7,21 @@ matrix: include: - php: 5.3 dist: precise + - php: 5.4 + - php: 5.5 + - php: 5.6 + - php: 7.0 + - php: 7.1 + - php: 7.2 + - php: 7.3 + - php: 7.4 + - php: nightly + - php: hhvm-3.18 + install: + - composer require phpunit/phpunit:^5 --dev --no-interaction # requires legacy phpunit allow_failures: - php: nightly - - php: hhvm + - php: hhvm-3.18 install: - composer install --no-interaction diff --git a/CHANGELOG.md b/CHANGELOG.md index a923861..5a71f99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,35 @@ # Changelog +## 0.5.11 (2021-04-07) + +* Fix: Minimal fix for PHP 8 + (#154 by @remicollet) + +* Documentation: Add deprecation notice to suggest HTTP component instead + (#153 by @clue) + +## 0.5.10 (2020-01-14) + +* Fix: Avoid unneeded warning when decoding invalid data on PHP 7.4. + (#150 by @clue) + +* Add `.gitattributes` to exclude dev files from exports. + (#149 by @reedy) + +* Link to clue/reactphp-buzz for higher-level HTTP client. + (#139 by @clue) + +* Improve test suite by simplifying test matrix and test setup. + (#151 by @clue) + +## 0.5.9 (2018-04-10) + +* Feature: Support legacy HTTP servers that use only `LF` instead of `CRLF`. + (#130 by @clue) + +* Improve test suite by applying maximum test timeouts for integration tests. + (#131 by @clue) + ## 0.5.8 (2018-02-09) * Support legacy PHP 5.3 through PHP 7.2 and HHVM diff --git a/README.md b/README.md index 6ae1f97..9ce6381 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,43 @@ -# HttpClient +# Deprecation notice + +This package has now been migrated over to +[react/http](https://github.com/reactphp/http) +and only exists for BC reasons. + +```bash +$ composer require react/http +``` + +If you've previously used this package, upgrading may take a moment or two. +The new API has been updated to use Promises and PSR-7 message abstractions. +This means it's now more powerful and easier to use than ever: + +```php +// old +$client = new React\HttpClient\Client($loop); +$request = $client->request('GET', 'https://example.com/'); +$request->on('response', function ($response) { + $response->on('data', function ($chunk) { + echo $chunk; + }); +}); +$request->end(); + +// new +$browser = new React\Http\Browser($loop); +$browser->get('https://example.com/')->then(function (Psr\Http\Message\ResponseInterface $response) { + echo $response->getBody(); +}); +``` + +See [react/http](https://github.com/reactphp/http#client-usage) for more details. + +The below documentation applies to the last release of this package. +Further development will take place in the updated +[react/http](https://github.com/reactphp/http), +so you're highly recommended to upgrade as soon as possible. + +# Deprecated HttpClient [![Build Status](https://travis-ci.org/reactphp/http-client.svg?branch=master)](https://travis-ci.org/reactphp/http-client) @@ -164,7 +203,7 @@ The recommended way to install this library is [through Composer](https://getcom This will install the latest supported version: ```bash -$ composer require react/http-client:^0.5.8 +$ composer require react/http-client:^0.5.10 ``` See also the [CHANGELOG](CHANGELOG.md) for details about version upgrades. diff --git a/composer.json b/composer.json index ecb19e5..9207639 100644 --- a/composer.json +++ b/composer.json @@ -13,11 +13,18 @@ "ringcentral/psr7": "^1.2" }, "require-dev": { - "phpunit/phpunit": "^6.4 || ^5.7 || ^4.8.35" + "clue/block-react": "^1.2", + "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35", + "react/promise-stream": "^1.1" }, "autoload": { "psr-4": { "React\\HttpClient\\": "src" } + }, + "autoload-dev": { + "psr-4": { + "React\\Tests\\HttpClient\\": "tests" + } } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index cba6d4d..04d426b 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -8,8 +8,7 @@ convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="false" - syntaxCheck="false" - bootstrap="tests/bootstrap.php" + bootstrap="vendor/autoload.php" > diff --git a/src/ChunkedStreamDecoder.php b/src/ChunkedStreamDecoder.php index a96592e..bc150ad 100644 --- a/src/ChunkedStreamDecoder.php +++ b/src/ChunkedStreamDecoder.php @@ -111,7 +111,7 @@ protected function iterateBuffer() } } $this->nextChunkIsLength = false; - if (dechex(hexdec($lengthChunk)) !== strtolower($lengthChunk)) { + if (dechex((int)@hexdec($lengthChunk)) !== strtolower($lengthChunk)) { $this->emit('error', array( new Exception('Unable to validate "' . $lengthChunk . '" as chunk length header'), )); diff --git a/src/Request.php b/src/Request.php index ea4d50b..caa242b 100644 --- a/src/Request.php +++ b/src/Request.php @@ -134,7 +134,8 @@ public function handleData($data) { $this->buffer .= $data; - if (false !== strpos($this->buffer, "\r\n\r\n")) { + // buffer until double CRLF (or double LF for compatibility with legacy servers) + if (false !== strpos($this->buffer, "\r\n\r\n") || false !== strpos($this->buffer, "\n\n")) { try { list($response, $bodyChunk) = $this->parseResponse($this->buffer); } catch (\InvalidArgumentException $exception) { diff --git a/tests/FunctionalIntegrationTest.php b/tests/FunctionalIntegrationTest.php index 1ed2228..cc8d880 100644 --- a/tests/FunctionalIntegrationTest.php +++ b/tests/FunctionalIntegrationTest.php @@ -2,14 +2,37 @@ namespace React\Tests\HttpClient; +use Clue\React\Block; use React\EventLoop\Factory; use React\HttpClient\Client; use React\HttpClient\Response; +use React\Promise\Deferred; +use React\Promise\Stream; use React\Socket\Server; use React\Socket\ConnectionInterface; class FunctionalIntegrationTest extends TestCase { + /** + * Test timeout to use for local tests. + * + * In practice this would be near 0.001s, but let's leave some time in case + * the local system is currently busy. + * + * @var float + */ + const TIMEOUT_LOCAL = 1.0; + + /** + * Test timeout to use for remote (internet) tests. + * + * In pratice this should be below 1s, but this relies on infrastructure + * outside our control, so consider this a maximum to avoid running for hours. + * + * @var float + */ + const TIMEOUT_REMOTE = 10.0; + public function testRequestToLocalhostEmitsSingleRemoteConnection() { $loop = Factory::create(); @@ -24,9 +47,35 @@ public function testRequestToLocalhostEmitsSingleRemoteConnection() $client = new Client($loop); $request = $client->request('GET', 'http://localhost:' . $port); + + $promise = Stream\first($request, 'close'); + $request->end(); + + Block\await($promise, $loop, self::TIMEOUT_LOCAL); + } + + public function testRequestLegacyHttpServerWithOnlyLineFeedReturnsSuccessfulResponse() + { + $loop = Factory::create(); + + $server = new Server(0, $loop); + $server->on('connection', function (ConnectionInterface $conn) use ($server) { + $conn->end("HTTP/1.0 200 OK\n\nbody"); + $server->close(); + }); + + $client = new Client($loop); + $request = $client->request('GET', str_replace('tcp:', 'http:', $server->getAddress())); + + $once = $this->expectCallableOnceWith('body'); + $request->on('response', function (Response $response) use ($once) { + $response->on('data', $once); + }); + + $promise = Stream\first($request, 'close'); $request->end(); - $loop->run(); + Block\await($promise, $loop, self::TIMEOUT_LOCAL); } /** @group internet */ @@ -42,9 +91,10 @@ public function testSuccessfulResponseEmitsEnd() $response->on('end', $once); }); + $promise = Stream\first($request, 'close'); $request->end(); - $loop->run(); + Block\await($promise, $loop, self::TIMEOUT_REMOTE); } /** @group internet */ @@ -56,11 +106,9 @@ public function testPostDataReturnsData() $data = str_repeat('.', 33000); $request = $client->request('POST', 'https://' . (mt_rand(0, 1) === 0 ? 'eu.' : '') . 'httpbin.org/post', array('Content-Length' => strlen($data))); - $buffer = ''; - $request->on('response', function (Response $response) use (&$buffer) { - $response->on('data', function ($chunk) use (&$buffer) { - $buffer .= $chunk; - }); + $deferred = new Deferred(); + $request->on('response', function (Response $response) use ($deferred) { + $deferred->resolve(Stream\buffer($response)); }); $request->on('error', 'printf'); @@ -68,7 +116,7 @@ public function testPostDataReturnsData() $request->end($data); - $loop->run(); + $buffer = Block\await($deferred->promise(), $loop, self::TIMEOUT_REMOTE); $this->assertNotEquals('', $buffer); @@ -87,11 +135,9 @@ public function testPostJsonReturnsData() $data = json_encode(array('numbers' => range(1, 50))); $request = $client->request('POST', 'https://httpbin.org/post', array('Content-Length' => strlen($data), 'Content-Type' => 'application/json')); - $buffer = ''; - $request->on('response', function (Response $response) use (&$buffer) { - $response->on('data', function ($chunk) use (&$buffer) { - $buffer .= $chunk; - }); + $deferred = new Deferred(); + $request->on('response', function (Response $response) use ($deferred) { + $deferred->resolve(Stream\buffer($response)); }); $request->on('error', 'printf'); @@ -99,7 +145,7 @@ public function testPostJsonReturnsData() $request->end($data); - $loop->run(); + $buffer = Block\await($deferred->promise(), $loop, self::TIMEOUT_REMOTE); $this->assertNotEquals('', $buffer); @@ -119,7 +165,5 @@ public function testCancelPendingConnectionEmitsClose() $request->on('close', $this->expectCallableOnce()); $request->end(); $request->close(); - - $loop->run(); } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 9e090bc..901f82f 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -26,6 +26,17 @@ protected function expectCallableOnce() return $mock; } + protected function expectCallableOnceWith($value) + { + $mock = $this->createCallableMock(); + $mock + ->expects($this->once()) + ->method('__invoke') + ->with($value); + + return $mock; + } + protected function expectCallableNever() { $mock = $this->createCallableMock(); diff --git a/tests/bootstrap.php b/tests/bootstrap.php deleted file mode 100644 index e3bed44..0000000 --- a/tests/bootstrap.php +++ /dev/null @@ -1,7 +0,0 @@ -addPsr4('React\\Tests\\HttpClient\\', __DIR__); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy