diff --git a/src/EventSource.php b/src/EventSource.php index 67507be..f07f349 100644 --- a/src/EventSource.php +++ b/src/EventSource.php @@ -82,7 +82,8 @@ private function send() return; } - if ($response->getHeaderLine('Content-Type') !== 'text/event-stream') { + // match `Content-Type: text/event-stream` (case insensitve and ignore additional parameters) + if (!preg_match('/^text\/event-stream(?:$|;)/i', $response->getHeaderLine('Content-Type'))) { $this->readyState = self::CLOSED; $this->emit('error', array(new \UnexpectedValueException('Unexpected Content-Type'))); $this->close(); diff --git a/tests/EventSourceTest.php b/tests/EventSourceTest.php index eb27808..f0621f7 100644 --- a/tests/EventSourceTest.php +++ b/tests/EventSourceTest.php @@ -420,6 +420,68 @@ public function testConstructorWillReportOpenWhenGetResponseResolvesWithValidRes $this->assertEquals(EventSource::OPEN, $readyState); } + public function testConstructorWillReportOpenWhenGetResponseResolvesWithValidResponseWithCaseInsensitiveContentTypeAfterTimer() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $timer = null; + $loop->expects($this->at(0))->method('addTimer')->with(0, $this->callback(function ($cb) use (&$timer) { + $timer = $cb; + return true; + })); + + $stream = new ThroughStream(); + $response = new Response(200, array('CONTENT-type' => 'TEXT/Event-Stream'), new ReadableBodyStream($stream)); + $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('get')->with('http://example.com')->willReturn(\React\Promise\resolve($response)); + + $es = new EventSource('http://example.com', $loop); + + $ref = new ReflectionProperty($es, 'browser'); + $ref->setAccessible(true); + $ref->setValue($es, $browser); + + $readyState = null; + $es->on('open', function () use ($es, &$readyState) { + $readyState = $es->readyState; + }); + + $this->assertNotNull($timer); + $timer(); + + $this->assertEquals(EventSource::OPEN, $readyState); + } + + public function testConstructorWillReportOpenWhenGetResponseResolvesWithValidResponseAndSuperfluousParametersAfterTimer() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + $timer = null; + $loop->expects($this->at(0))->method('addTimer')->with(0, $this->callback(function ($cb) use (&$timer) { + $timer = $cb; + return true; + })); + + $stream = new ThroughStream(); + $response = new Response(200, array('Content-Type' => 'text/event-stream;charset=utf-8;foo=bar'), new ReadableBodyStream($stream)); + $browser = $this->getMockBuilder('Clue\React\Buzz\Browser')->disableOriginalConstructor()->getMock(); + $browser->expects($this->once())->method('get')->with('http://example.com')->willReturn(\React\Promise\resolve($response)); + + $es = new EventSource('http://example.com', $loop); + + $ref = new ReflectionProperty($es, 'browser'); + $ref->setAccessible(true); + $ref->setValue($es, $browser); + + $readyState = null; + $es->on('open', function () use ($es, &$readyState) { + $readyState = $es->readyState; + }); + + $this->assertNotNull($timer); + $timer(); + + $this->assertEquals(EventSource::OPEN, $readyState); + } + public function testCloseResponseStreamWillReturnToStartTimerToReconnectWithoutErrorEvent() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();