diff --git a/README.md b/README.md index c22ff6d..6762a18 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,9 @@ from any Server-Sent Events (SSE) server endpoint: ```php $loop = Factory::create(); -$es = new EventSource('https://example.com/stream.php', $loop); +$es = new Clue\React\EventSource\EventSource('https://example.com/stream.php', $loop); -$es->on('message', function (MessageEvent $message) { +$es->on('message', function (Clue\React\EventSource\MessageEvent $message) { //$data = json_decode($message->data); var_dump($message); }); @@ -48,19 +48,19 @@ registers everything with the main [`EventLoop`](https://github.com/reactphp/eve in order to handle async HTTP requests. ```php -$loop = \React\EventLoop\Factory::create(); +$loop = React\EventLoop\Factory::create(); -$es = new \Clue\React\EventSource\EventSource('https://example.com/stream.php', $loop); +$es = new Clue\React\EventSource\EventSource('https://example.com/stream.php', $loop); ``` If you need custom connector settings (DNS resolution, TLS parameters, timeouts, proxy servers etc.), you can explicitly pass a custom instance of the [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface) -to the [`Browser`](https://github.com/clue/reactphp-buzz#browser) instance +to the [`Browser`](https://github.com/reactphp/http#browser) instance and pass it as an additional argument to the `EventSource` like this: ```php -$connector = new \React\Socket\Connector($loop, array( +$connector = new React\Socket\Connector($loop, array( 'dns' => '127.0.0.1', 'tcp' => array( 'bindto' => '192.168.10.1:0' @@ -70,9 +70,9 @@ $connector = new \React\Socket\Connector($loop, array( 'verify_peer_name' => false ) )); -$browser = new \Clue\React\Buzz\Browser($loop, $connector); +$browser = new React\Http\Browser($loop, $connector); -$es = new \Clue\React\EventSource\EventSource('https://example.com/stream.php', $loop, $browser); +$es = new Clue\React\EventSource\EventSource('https://example.com/stream.php', $loop, $browser); ``` ## Install diff --git a/composer.json b/composer.json index 3bda530..2e77766 100644 --- a/composer.json +++ b/composer.json @@ -18,8 +18,9 @@ }, "require": { "php": ">=5.4", - "clue/buzz-react": "^2.5", - "evenement/evenement": "^3.0 || ^2.0" + "evenement/evenement": "^3.0 || ^2.0", + "react/event-loop": "^1.0", + "react/http": "^1.0" }, "require-dev": { "phpunit/phpunit": "^7.0 || ^6.4 || ^5.7 || ^4.8.35" diff --git a/src/EventSource.php b/src/EventSource.php index 6ec3c88..d90f767 100644 --- a/src/EventSource.php +++ b/src/EventSource.php @@ -2,10 +2,10 @@ namespace Clue\React\EventSource; -use Clue\React\Buzz\Browser; use Evenement\EventEmitter; use Psr\Http\Message\ResponseInterface; use React\EventLoop\LoopInterface; +use React\Http\Browser; use React\Stream\ReadableStreamInterface; /** @@ -20,19 +20,19 @@ * in order to handle async HTTP requests. * * ```php - * $loop = \React\EventLoop\Factory::create(); + * $loop = React\EventLoop\Factory::create(); * - * $es = new \Clue\React\EventSource\EventSource('https://example.com/stream.php', $loop); + * $es = new Clue\React\EventSource\EventSource('https://example.com/stream.php', $loop); * ``` * * If you need custom connector settings (DNS resolution, TLS parameters, timeouts, * proxy servers etc.), you can explicitly pass a custom instance of the * [`ConnectorInterface`](https://github.com/reactphp/socket#connectorinterface) - * to the [`Browser`](https://github.com/clue/reactphp-buzz#browser) instance + * to the [`Browser`](https://github.com/reactphp/http#browser) instance * and pass it as an additional argument to the `EventSource` like this: * * ```php - * $connector = new \React\Socket\Connector($loop, array( + * $connector = new React\Socket\Connector($loop, array( * 'dns' => '127.0.0.1', * 'tcp' => array( * 'bindto' => '192.168.10.1:0' @@ -42,9 +42,9 @@ * 'verify_peer_name' => false * ) * )); - * $browser = new \Clue\React\Buzz\Browser($loop, $connector); + * $browser = new React\Http\Browser($loop, $connector); * - * $es = new \Clue\React\EventSource\EventSource('https://example.com/stream.php', $loop, $browser); + * $es = new Clue\React\EventSource\EventSource('https://example.com/stream.php', $loop, $browser); * ``` */ class EventSource extends EventEmitter @@ -88,7 +88,7 @@ public function __construct($url, LoopInterface $loop, Browser $browser = null) if ($browser === null) { $browser = new Browser($loop); } - $this->browser = $browser->withOptions(array('streaming' => true, 'obeySuccessCode' => false)); + $this->browser = $browser->withRejectErrorResponse(false); $this->loop = $loop; $this->url = $url; @@ -106,7 +106,8 @@ private function request() $headers['Last-Event-ID'] = $this->lastEventId; } - $this->request = $this->browser->get( + $this->request = $this->browser->requestStreaming( + 'GET', $this->url, $headers ); diff --git a/tests/EventSourceTest.php b/tests/EventSourceTest.php index 889042b..da314e5 100644 --- a/tests/EventSourceTest.php +++ b/tests/EventSourceTest.php @@ -2,14 +2,14 @@ namespace Clue\Tests\React\EventSource; -use PHPUnit\Framework\TestCase; use Clue\React\EventSource\EventSource; +use PHPUnit\Framework\TestCase; use React\Promise\Promise; use React\Promise\Deferred; -use RingCentral\Psr7\Response; +use React\Http\Browser; +use React\Http\Io\ReadableBodyStream; use React\Stream\ThroughStream; -use Clue\React\Buzz\Message\ReadableBodyStream; -use Clue\React\Buzz\Browser; +use RingCentral\Psr7\Response; class EventSourceTest extends TestCase { @@ -50,7 +50,7 @@ public function testConstructorCanBeCalledWithoutBrowser() $ref->setAccessible(true); $browser = $ref->getValue($es); - $this->assertInstanceOf('Clue\React\Buzz\Browser', $browser); + $this->assertInstanceOf('React\Http\Browser', $browser); } public function testConstructorWillSendGetRequestThroughGivenBrowser() @@ -58,9 +58,9 @@ public function testConstructorWillSendGetRequestThroughGivenBrowser() $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $pending = new Promise(function () { }); - $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); - $browser->expects($this->once())->method('withOptions')->willReturnSelf(); - $browser->expects($this->once())->method('get')->with('http://example.com')->willReturn($pending); + $browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('withRejectErrorResponse')->with(false)->willReturnSelf(); + $browser->expects($this->once())->method('requestStreaming')->with('GET', 'http://example.com')->willReturn($pending); $es = new EventSource('http://example.com', $loop, $browser); } @@ -70,9 +70,9 @@ public function testConstructorWillSendGetRequestThroughGivenBrowserWithHttpsSch $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $pending = new Promise(function () { }); - $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); - $browser->expects($this->once())->method('withOptions')->willReturnSelf(); - $browser->expects($this->once())->method('get')->with('https://example.com')->willReturn($pending); + $browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('withRejectErrorResponse')->with(false)->willReturnSelf(); + $browser->expects($this->once())->method('requestStreaming')->with('GET', 'https://example.com')->willReturn($pending); $es = new EventSource('https://example.com', $loop, $browser); } @@ -85,9 +85,9 @@ public function testCloseWillCancelPendingGetRequest() $pending = new Promise(function () { }, function () use (&$cancelled) { ++$cancelled; }); - $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); - $browser->expects($this->once())->method('withOptions')->willReturnSelf(); - $browser->expects($this->once())->method('get')->with('http://example.com')->willReturn($pending); + $browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('withRejectErrorResponse')->willReturnSelf(); + $browser->expects($this->once())->method('requestStreaming')->willReturn($pending); $es = new EventSource('http://example.com', $loop, $browser); $es->close(); @@ -102,9 +102,9 @@ public function testCloseWillNotEmitErrorEventWhenGetRequestCancellationHandlerR $pending = new Promise(function () { }, function () { throw new \RuntimeException(); }); - $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); - $browser->expects($this->once())->method('withOptions')->willReturnSelf(); - $browser->expects($this->once())->method('get')->with('http://example.com')->willReturn($pending); + $browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('withRejectErrorResponse')->willReturnSelf(); + $browser->expects($this->once())->method('requestStreaming')->willReturn($pending); $es = new EventSource('http://example.com', $loop, $browser); @@ -127,9 +127,9 @@ public function testConstructorWillStartGetRequestThatWillStartRetryTimerWhenGet ); $deferred = new Deferred(); - $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); - $browser->expects($this->once())->method('withOptions')->willReturnSelf(); - $browser->expects($this->once())->method('get')->willReturn($deferred->promise()); + $browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('withRejectErrorResponse')->willReturnSelf(); + $browser->expects($this->once())->method('requestStreaming')->willReturn($deferred->promise()); $es = new EventSource('http://example.com', $loop, $browser); @@ -149,9 +149,9 @@ public function testConstructorWillStartGetRequestThatWillStartRetryTimerThatWil ); $deferred = new Deferred(); - $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); - $browser->expects($this->once())->method('withOptions')->willReturnSelf(); - $browser->expects($this->exactly(2))->method('get')->willReturnOnConsecutiveCalls( + $browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('withRejectErrorResponse')->willReturnSelf(); + $browser->expects($this->exactly(2))->method('requestStreaming')->willReturnOnConsecutiveCalls( $deferred->promise(), new Promise(function () { }) ); @@ -173,9 +173,9 @@ public function testConstructorWillStartGetRequestThatWillEmitErrorWhenGetReques ); $deferred = new Deferred(); - $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); - $browser->expects($this->once())->method('withOptions')->willReturnSelf(); - $browser->expects($this->once())->method('get')->willReturn($deferred->promise()); + $browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('withRejectErrorResponse')->willReturnSelf(); + $browser->expects($this->once())->method('requestStreaming')->willReturn($deferred->promise()); $es = new EventSource('http://example.com', $loop, $browser); @@ -193,9 +193,9 @@ public function testConstructorWillStartGetRequestThatWillNotStartRetryTimerWhen $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $deferred = new Deferred(); - $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); - $browser->expects($this->once())->method('withOptions')->willReturnSelf(); - $browser->expects($this->once())->method('get')->with('http://example.com')->willReturn($deferred->promise()); + $browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('withRejectErrorResponse')->willReturnSelf(); + $browser->expects($this->once())->method('requestStreaming')->willReturn($deferred->promise()); $es = new EventSource('http://example.com', $loop, $browser); @@ -216,9 +216,9 @@ public function testCloseAfterGetRequestFromConstructorFailsWillCancelPendingRet $loop->expects($this->once())->method('cancelTimer')->with($timer); $deferred = new Deferred(); - $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); - $browser->expects($this->once())->method('withOptions')->willReturnSelf(); - $browser->expects($this->once())->method('get')->with('http://example.com')->willReturn($deferred->promise()); + $browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('withRejectErrorResponse')->willReturnSelf(); + $browser->expects($this->once())->method('requestStreaming')->willReturn($deferred->promise()); $es = new EventSource('http://example.com', $loop, $browser); @@ -232,9 +232,9 @@ public function testConstructorWillReportFatalErrorWhenGetResponseResolvesWithIn $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $deferred = new Deferred(); - $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); - $browser->expects($this->once())->method('withOptions')->willReturnSelf(); - $browser->expects($this->once())->method('get')->with('http://example.com')->willReturn($deferred->promise()); + $browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('withRejectErrorResponse')->willReturnSelf(); + $browser->expects($this->once())->method('requestStreaming')->willReturn($deferred->promise()); $es = new EventSource('http://example.com', $loop, $browser); @@ -257,9 +257,9 @@ public function testConstructorWillReportFatalErrorWhenGetResponseResolvesWithIn $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $deferred = new Deferred(); - $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); - $browser->expects($this->once())->method('withOptions')->willReturnSelf(); - $browser->expects($this->once())->method('get')->with('http://example.com')->willReturn($deferred->promise()); + $browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('withRejectErrorResponse')->willReturnSelf(); + $browser->expects($this->once())->method('requestStreaming')->willReturn($deferred->promise()); $es = new EventSource('http://example.com', $loop, $browser); @@ -282,9 +282,9 @@ public function testConstructorWillReportOpenWhenGetResponseResolvesWithValidRes $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $deferred = new Deferred(); - $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); - $browser->expects($this->once())->method('withOptions')->willReturnSelf(); - $browser->expects($this->once())->method('get')->with('http://example.com')->willReturn($deferred->promise()); + $browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('withRejectErrorResponse')->willReturnSelf(); + $browser->expects($this->once())->method('requestStreaming')->willReturn($deferred->promise()); $es = new EventSource('http://example.com', $loop, $browser); @@ -305,9 +305,9 @@ public function testConstructorWillReportOpenWhenGetResponseResolvesWithValidRes $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $deferred = new Deferred(); - $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); - $browser->expects($this->once())->method('withOptions')->willReturnSelf(); - $browser->expects($this->once())->method('get')->with('http://example.com')->willReturn($deferred->promise()); + $browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('withRejectErrorResponse')->willReturnSelf(); + $browser->expects($this->once())->method('requestStreaming')->willReturn($deferred->promise()); $es = new EventSource('http://example.com', $loop, $browser); @@ -328,9 +328,9 @@ public function testConstructorWillReportOpenWhenGetResponseResolvesWithValidRes $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $deferred = new Deferred(); - $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); - $browser->expects($this->once())->method('withOptions')->willReturnSelf(); - $browser->expects($this->once())->method('get')->with('http://example.com')->willReturn($deferred->promise()); + $browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('withRejectErrorResponse')->willReturnSelf(); + $browser->expects($this->once())->method('requestStreaming')->willReturn($deferred->promise()); $es = new EventSource('http://example.com', $loop, $browser); @@ -355,9 +355,9 @@ public function testCloseResponseStreamWillStartRetryTimerWithoutErrorEvent() ); $deferred = new Deferred(); - $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); - $browser->expects($this->once())->method('withOptions')->willReturnSelf(); - $browser->expects($this->once())->method('get')->with('http://example.com')->willReturn($deferred->promise()); + $browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('withRejectErrorResponse')->willReturnSelf(); + $browser->expects($this->once())->method('requestStreaming')->willReturn($deferred->promise()); $es = new EventSource('http://example.com', $loop, $browser); @@ -381,9 +381,9 @@ public function testCloseFromOpenEventWillCloseResponseStreamAndCloseEventSource $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $deferred = new Deferred(); - $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); - $browser->expects($this->once())->method('withOptions')->willReturnSelf(); - $browser->expects($this->once())->method('get')->with('http://example.com')->willReturn($deferred->promise()); + $browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('withRejectErrorResponse')->willReturnSelf(); + $browser->expects($this->once())->method('requestStreaming')->willReturn($deferred->promise()); $es = new EventSource('http://example.com', $loop, $browser); @@ -404,9 +404,9 @@ public function testEmitMessageWithParsedDataFromEventStream() $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $deferred = new Deferred(); - $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); - $browser->expects($this->once())->method('withOptions')->willReturnSelf(); - $browser->expects($this->once())->method('get')->with('http://example.com')->willReturn($deferred->promise()); + $browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('withRejectErrorResponse')->willReturnSelf(); + $browser->expects($this->once())->method('requestStreaming')->willReturn($deferred->promise()); $es = new EventSource('http://example.com', $loop, $browser); @@ -431,9 +431,9 @@ public function testEmitMessageWithParsedIdAndDataOverMultipleRowsFromEventStrea $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $deferred = new Deferred(); - $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); - $browser->expects($this->once())->method('withOptions')->willReturnSelf(); - $browser->expects($this->once())->method('get')->with('http://example.com')->willReturn($deferred->promise()); + $browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('withRejectErrorResponse')->willReturnSelf(); + $browser->expects($this->once())->method('requestStreaming')->willReturn($deferred->promise()); $es = new EventSource('http://example.com', $loop, $browser); @@ -458,9 +458,9 @@ public function testEmitMessageWithParsedEventTypeAndDataWithTrailingWhitespaceF $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $deferred = new Deferred(); - $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); - $browser->expects($this->once())->method('withOptions')->willReturnSelf(); - $browser->expects($this->once())->method('get')->with('http://example.com')->willReturn($deferred->promise()); + $browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('withRejectErrorResponse')->willReturnSelf(); + $browser->expects($this->once())->method('requestStreaming')->willReturn($deferred->promise()); $es = new EventSource('http://example.com', $loop, $browser); @@ -484,9 +484,9 @@ public function testDoesNotEmitMessageWhenParsedEventStreamHasNoData() $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $deferred = new Deferred(); - $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); - $browser->expects($this->once())->method('withOptions')->willReturnSelf(); - $browser->expects($this->once())->method('get')->with('http://example.com')->willReturn($deferred->promise()); + $browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('withRejectErrorResponse')->willReturnSelf(); + $browser->expects($this->once())->method('requestStreaming')->willReturn($deferred->promise()); $es = new EventSource('http://example.com', $loop, $browser); @@ -509,9 +509,9 @@ public function testEmitMessageWithParsedDataAndPreviousIdWhenNotGivenAgainFromE $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); $deferred = new Deferred(); - $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); - $browser->expects($this->once())->method('withOptions')->willReturnSelf(); - $browser->expects($this->once())->method('get')->with('http://example.com')->willReturn($deferred->promise()); + $browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('withRejectErrorResponse')->willReturnSelf(); + $browser->expects($this->once())->method('requestStreaming')->willReturn($deferred->promise()); $es = new EventSource('http://example.com', $loop, $browser); @@ -544,11 +544,11 @@ public function testReconnectAfterStreamClosesUsesLastEventIdFromParsedEventStre ); $deferred = new Deferred(); - $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); - $browser->expects($this->once())->method('withOptions')->willReturnSelf(); - $browser->expects($this->exactly(2))->method('get')->withConsecutive( - ['http://example.com', ['Accept' => 'text/event-stream', 'Cache-Control' => 'no-cache']], - ['http://example.com', ['Accept' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'Last-Event-ID' => '123']] + $browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('withRejectErrorResponse')->willReturnSelf(); + $browser->expects($this->exactly(2))->method('requestStreaming')->withConsecutive( + ['GET', 'http://example.com', ['Accept' => 'text/event-stream', 'Cache-Control' => 'no-cache']], + ['GET', 'http://example.com', ['Accept' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'Last-Event-ID' => '123']] )->willReturnOnConsecutiveCalls( $deferred->promise(), new Promise(function () { })