From 6cd325bebfd8d776601413f96c804407a98ff8e5 Mon Sep 17 00:00:00 2001 From: Quentin Harnay Date: Mon, 6 Apr 2020 14:52:09 +0200 Subject: [PATCH 1/4] Support for custom headers gievn to the browser's requests --- src/EventSource.php | 16 +++++++++++----- tests/EventSourceTest.php | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/EventSource.php b/src/EventSource.php index 6ec3c88..cda1584 100644 --- a/src/EventSource.php +++ b/src/EventSource.php @@ -77,8 +77,13 @@ class EventSource extends EventEmitter private $request; private $timer; private $reconnectTime = 3.0; + private $headers; + private $defaultHeaders = [ + 'Accept' => 'text/event-stream', + 'Cache-Control' => 'no-cache' + ]; - public function __construct($url, LoopInterface $loop, Browser $browser = null) + public function __construct($url, LoopInterface $loop, Browser $browser = null, array $headers = []) { $parts = parse_url($url); if (!isset($parts['scheme'], $parts['host']) || !in_array($parts['scheme'], array('http', 'https'))) { @@ -91,6 +96,10 @@ public function __construct($url, LoopInterface $loop, Browser $browser = null) $this->browser = $browser->withOptions(array('streaming' => true, 'obeySuccessCode' => false)); $this->loop = $loop; $this->url = $url; + $this->headers = array_merge( + $headers, + $this->defaultHeaders + ); $this->readyState = self::CONNECTING; $this->request(); @@ -98,10 +107,7 @@ public function __construct($url, LoopInterface $loop, Browser $browser = null) private function request() { - $headers = array( - 'Accept' => 'text/event-stream', - 'Cache-Control' => 'no-cache' - ); + $headers = $this->headers; if ($this->lastEventId !== '') { $headers['Last-Event-ID'] = $this->lastEventId; } diff --git a/tests/EventSourceTest.php b/tests/EventSourceTest.php index e698a61..2fd39b0 100644 --- a/tests/EventSourceTest.php +++ b/tests/EventSourceTest.php @@ -51,6 +51,43 @@ public function testConstructorCanBeCalledWithoutBrowser() $this->assertInstanceOf('Clue\React\Buzz\Browser', $browser); } + + public function testConstructorCanBeCalledWithoutCustomHeaders() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $es = new EventSource('http://example.valid', $loop); + + $ref = new ReflectionProperty($es, 'headers'); + $ref->setAccessible(true); + $headers = $ref->getValue($es); + + $ref = new ReflectionProperty($es, 'defaultHeaders'); + $ref->setAccessible(true); + $defaultHeaders = $ref->getValue($es); + + $this->assertEquals($defaultHeaders, $headers); + } + + public function testConstructorCanBeCalledWithCustomHeaders() + { + $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); + + $es = new EventSource('http://example.valid', $loop, null, ['x-custom' => '1234']); + + $ref = new ReflectionProperty($es, 'headers'); + $ref->setAccessible(true); + $headers = $ref->getValue($es); + + // Could have used the defaultHeaders property on EventSource, + // but this ensures the defaults are not altered by hardcoding their values in this test + $this->assertEquals(array( + 'Accept' => 'text/event-stream', + 'Cache-Control' => 'no-cache', + 'x-custom' => '1234' + ), $headers); + } + public function testConstructorWillSendGetRequestThroughGivenBrowser() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); From 7e1ab742e80759cbffc52b63c931e78d721833d8 Mon Sep 17 00:00:00 2001 From: Quentin Harnay Date: Mon, 27 Apr 2020 17:32:05 +0200 Subject: [PATCH 2/4] Ensuring default headers can be overriden --- tests/EventSourceTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/EventSourceTest.php b/tests/EventSourceTest.php index 2fd39b0..f32a3e1 100644 --- a/tests/EventSourceTest.php +++ b/tests/EventSourceTest.php @@ -73,7 +73,7 @@ public function testConstructorCanBeCalledWithCustomHeaders() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $es = new EventSource('http://example.valid', $loop, null, ['x-custom' => '1234']); + $es = new EventSource('http://example.valid', $loop, null, ['x-custom' => '1234', 'Cache-Control' => 'only-if-cached']); $ref = new ReflectionProperty($es, 'headers'); $ref->setAccessible(true); @@ -83,7 +83,7 @@ public function testConstructorCanBeCalledWithCustomHeaders() // but this ensures the defaults are not altered by hardcoding their values in this test $this->assertEquals(array( 'Accept' => 'text/event-stream', - 'Cache-Control' => 'no-cache', + 'Cache-Control' => 'only-if-cached', 'x-custom' => '1234' ), $headers); } From fb2e2bc0aef84a77e973874443bc379e95fbed05 Mon Sep 17 00:00:00 2001 From: Quentin Harnay Date: Mon, 27 Apr 2020 17:34:05 +0200 Subject: [PATCH 3/4] Inverted test. defualt headers can not be overriden --- tests/EventSourceTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/EventSourceTest.php b/tests/EventSourceTest.php index f32a3e1..b853ec9 100644 --- a/tests/EventSourceTest.php +++ b/tests/EventSourceTest.php @@ -83,7 +83,7 @@ public function testConstructorCanBeCalledWithCustomHeaders() // but this ensures the defaults are not altered by hardcoding their values in this test $this->assertEquals(array( 'Accept' => 'text/event-stream', - 'Cache-Control' => 'only-if-cached', + 'Cache-Control' => 'no-cache', 'x-custom' => '1234' ), $headers); } From 6523d9fdea90f053680f824c33dc2f89a05d665d Mon Sep 17 00:00:00 2001 From: Quentin Harnay Date: Tue, 28 Apr 2020 15:13:25 +0200 Subject: [PATCH 4/4] Custom and default headers are now handled in a mergeHeaders function. As HTTP headers are case insensitive, this function ensures default headers are not overriden, even by another case --- src/EventSource.php | 26 ++++++++++++++++++++++---- tests/EventSourceTest.php | 7 ++++++- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/EventSource.php b/src/EventSource.php index cda1584..fd7d38c 100644 --- a/src/EventSource.php +++ b/src/EventSource.php @@ -96,15 +96,33 @@ public function __construct($url, LoopInterface $loop, Browser $browser = null, $this->browser = $browser->withOptions(array('streaming' => true, 'obeySuccessCode' => false)); $this->loop = $loop; $this->url = $url; - $this->headers = array_merge( - $headers, - $this->defaultHeaders - ); + + $this->headers = $this->mergeHeaders($headers); $this->readyState = self::CONNECTING; $this->request(); } + private function mergeHeaders(array $headers = []) + { + if ($headers === []) { + return $this->defaultHeaders; + } + + // HTTP headers are case insensitive, we do not want to have different cases for the same (default) header + // Convert default headers to lowercase, to ease the custom headers potential override comparison + $loweredDefaults = array_change_key_case($this->defaultHeaders, CASE_LOWER); + foreach($headers as $k => $v) { + if (array_key_exists(strtolower($k), $loweredDefaults)) { + unset($headers[$k]); + } + } + return array_merge( + $headers, + $this->defaultHeaders + ); + } + private function request() { $headers = $this->headers; diff --git a/tests/EventSourceTest.php b/tests/EventSourceTest.php index b853ec9..ad7fc2c 100644 --- a/tests/EventSourceTest.php +++ b/tests/EventSourceTest.php @@ -73,7 +73,12 @@ public function testConstructorCanBeCalledWithCustomHeaders() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); - $es = new EventSource('http://example.valid', $loop, null, ['x-custom' => '1234', 'Cache-Control' => 'only-if-cached']); + $es = new EventSource('http://example.valid', $loop, null, array( + 'x-custom' => '1234', + 'Cache-Control' => 'only-if-cached', + 'ACCEPT' => 'no-store', + 'cache-control' => 'none' + )); $ref = new ReflectionProperty($es, 'headers'); $ref->setAccessible(true);