Skip to content

Commit dbb237a

Browse files
committed
Emit error when response stream closes before retrying connection
Refs https://html.spec.whatwg.org/multipage/server-sent-events.html#reestablish-the-connection
1 parent d2a0860 commit dbb237a

3 files changed

Lines changed: 39 additions & 3 deletions

File tree

examples/stream.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
var_dump($message);
1818
});
1919

20+
$es->on('open', function () {
21+
echo 'open' . PHP_EOL;
22+
});
23+
2024
$es->on('error', function (Exception $e) use ($es) {
2125
if ($es->readyState === EventSource::CLOSED) {
2226
echo 'Permanent error: ' . $e->getMessage() . PHP_EOL;

src/EventSource.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,12 @@ private function request()
155155
$this->request = null;
156156
if ($this->readyState === self::OPEN) {
157157
$this->readyState = self::CONNECTING;
158+
159+
$this->emit('error', [new \RuntimeException('Stream closed, reconnecting in ' . $this->reconnectTime . ' seconds')]);
160+
if ($this->readyState === self::CLOSED) {
161+
return;
162+
}
163+
158164
$this->timer = $this->loop->addTimer($this->reconnectTime, function () {
159165
$this->timer = null;
160166
$this->request();
@@ -170,7 +176,7 @@ private function request()
170176
return;
171177
}
172178

173-
$this->emit('error', array($e));
179+
$this->emit('error', [$e]);
174180
if ($this->readyState === self::CLOSED) {
175181
return;
176182
}

tests/EventSourceTest.php

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ public function testConstructorWillReportOpenWhenGetResponseResolvesWithValidRes
346346
$this->assertEquals(EventSource::OPEN, $readyState);
347347
}
348348

349-
public function testCloseResponseStreamWillStartRetryTimerWithoutErrorEvent()
349+
public function testCloseResponseStreamWillStartRetryTimerWithErrorEvent()
350350
{
351351
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
352352
$loop->expects($this->once())->method('addTimer')->with(
@@ -373,7 +373,33 @@ public function testCloseResponseStreamWillStartRetryTimerWithoutErrorEvent()
373373
$stream->close();
374374

375375
$this->assertEquals(EventSource::CONNECTING, $es->readyState);
376-
$this->assertNull($error);
376+
$this->assertInstanceOf('RuntimeException', $error);
377+
$this->assertEquals('Stream closed, reconnecting in 3 seconds', $error->getMessage());
378+
}
379+
380+
public function testCloseResponseStreamWillNotStartRetryTimerWhenEventSourceIsClosedFromErrorHandler()
381+
{
382+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
383+
$loop->expects($this->never())->method('addTimer');
384+
385+
$deferred = new Deferred();
386+
$browser = $this->getMockBuilder('React\Http\Browser')->disableOriginalConstructor()->getMock();
387+
$browser->expects($this->once())->method('withRejectErrorResponse')->willReturnSelf();
388+
$browser->expects($this->once())->method('requestStreaming')->willReturn($deferred->promise());
389+
390+
$es = new EventSource('http://example.com', $loop, $browser);
391+
392+
$stream = new ThroughStream();
393+
$response = new Response(200, array('Content-Type' => 'text/event-stream'), new ReadableBodyStream($stream));
394+
$deferred->resolve($response);
395+
396+
$es->on('error', function ($e) use ($es) {
397+
$es->close();
398+
});
399+
400+
$stream->close();
401+
402+
$this->assertEquals(EventSource::CLOSED, $es->readyState);
377403
}
378404

379405
public function testCloseFromOpenEventWillCloseResponseStreamAndCloseEventSource()

0 commit comments

Comments
 (0)