Merge branch '4.4' into 5.0

* 4.4:
  Use ">=" for the "php" requirement
  [HttpClient] Fix promise behavior in HttplugClient
This commit is contained in:
Nicolas Grekas 2020-07-05 11:43:09 +02:00
commit 085e66cfc2
3 changed files with 124 additions and 3 deletions

View File

@ -12,6 +12,7 @@
namespace Symfony\Component\HttpClient;
use GuzzleHttp\Promise\Promise as GuzzlePromise;
use GuzzleHttp\Promise\RejectedPromise;
use Http\Client\Exception\NetworkException;
use Http\Client\Exception\RequestException;
use Http\Client\HttpAsyncClient;
@ -22,7 +23,6 @@ use Http\Message\RequestFactory;
use Http\Message\StreamFactory;
use Http\Message\UriFactory;
use Http\Promise\Promise;
use Http\Promise\RejectedPromise;
use Nyholm\Psr7\Factory\Psr17Factory;
use Nyholm\Psr7\Request;
use Nyholm\Psr7\Uri;
@ -114,7 +114,7 @@ final class HttplugClient implements HttplugInterface, HttpAsyncClient, RequestF
try {
$response = $this->sendPsr7Request($request, true);
} catch (NetworkException $e) {
return new RejectedPromise($e);
return new HttplugPromise(new RejectedPromise($e));
}
$waitLoop = $this->waitLoop;

View File

@ -54,6 +54,12 @@ final class HttplugPromise implements HttplugPromiseInterface
*/
public function wait($unwrap = true)
{
return $this->promise->wait($unwrap);
$result = $this->promise->wait($unwrap);
while ($result instanceof HttplugPromiseInterface || $result instanceof GuzzlePromiseInterface) {
$result = $result->wait($unwrap);
}
return $result;
}
}

View File

@ -11,13 +11,18 @@
namespace Symfony\Component\HttpClient\Tests;
use GuzzleHttp\Promise\FulfilledPromise as GuzzleFulfilledPromise;
use Http\Client\Exception\NetworkException;
use Http\Client\Exception\RequestException;
use Http\Promise\FulfilledPromise;
use Http\Promise\Promise;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Symfony\Component\HttpClient\Exception\TransportException;
use Symfony\Component\HttpClient\HttplugClient;
use Symfony\Component\HttpClient\MockHttpClient;
use Symfony\Component\HttpClient\NativeHttpClient;
use Symfony\Component\HttpClient\Response\MockResponse;
use Symfony\Contracts\HttpClient\Test\TestHttpServer;
class HttplugClientTest extends TestCase
@ -152,4 +157,114 @@ class HttplugClientTest extends TestCase
$this->expectException(RequestException::class);
$client->sendRequest($client->createRequest('BAD.METHOD', 'http://localhost:8057'));
}
public function testRetry404()
{
$client = new HttplugClient(new NativeHttpClient());
$successCallableCalled = false;
$failureCallableCalled = false;
$promise = $client
->sendAsyncRequest($client->createRequest('GET', 'http://localhost:8057/404'))
->then(
function (ResponseInterface $response) use (&$successCallableCalled, $client) {
$this->assertSame(404, $response->getStatusCode());
$successCallableCalled = true;
return $client->sendAsyncRequest($client->createRequest('GET', 'http://localhost:8057'));
},
function (\Exception $exception) use (&$failureCallableCalled) {
$failureCallableCalled = true;
throw $exception;
}
)
;
$response = $promise->wait(true);
$this->assertTrue($successCallableCalled);
$this->assertFalse($failureCallableCalled);
$this->assertSame(200, $response->getStatusCode());
}
public function testRetryNetworkError()
{
$client = new HttplugClient(new NativeHttpClient());
$successCallableCalled = false;
$failureCallableCalled = false;
$promise = $client
->sendAsyncRequest($client->createRequest('GET', 'http://localhost:8057/chunked-broken'))
->then(function (ResponseInterface $response) use (&$successCallableCalled) {
$successCallableCalled = true;
return $response;
}, function (\Exception $exception) use (&$failureCallableCalled, $client) {
$this->assertSame(NetworkException::class, \get_class($exception));
$this->assertSame(TransportException::class, \get_class($exception->getPrevious()));
$failureCallableCalled = true;
return $client->sendAsyncRequest($client->createRequest('GET', 'http://localhost:8057'));
})
;
$response = $promise->wait(true);
$this->assertFalse($successCallableCalled);
$this->assertTrue($failureCallableCalled);
$this->assertSame(200, $response->getStatusCode());
}
public function testRetryEarlierError()
{
$isFirstRequest = true;
$errorMessage = 'Error occurred before making the actual request.';
$client = new HttplugClient(new MockHttpClient(function () use (&$isFirstRequest, $errorMessage) {
if ($isFirstRequest) {
$isFirstRequest = false;
throw new TransportException($errorMessage);
}
return new MockResponse('OK', ['http_code' => 200]);
}));
$request = $client->createRequest('GET', 'http://test');
$successCallableCalled = false;
$failureCallableCalled = false;
$promise = $client
->sendAsyncRequest($request)
->then(
function (ResponseInterface $response) use (&$successCallableCalled) {
$successCallableCalled = true;
return $response;
},
function (\Exception $exception) use ($errorMessage, &$failureCallableCalled, $client, $request) {
$this->assertSame(NetworkException::class, \get_class($exception));
$this->assertSame($errorMessage, $exception->getMessage());
$failureCallableCalled = true;
// Ensure arbitrary levels of promises work.
return (new FulfilledPromise(null))->then(function () use ($client, $request) {
return (new GuzzleFulfilledPromise(null))->then(function () use ($client, $request) {
return $client->sendAsyncRequest($request);
});
});
}
)
;
$response = $promise->wait(true);
$this->assertFalse($successCallableCalled);
$this->assertTrue($failureCallableCalled);
$this->assertSame(200, $response->getStatusCode());
$this->assertSame('OK', (string) $response->getBody());
}
}