From bb36a58552ac190081750faaf7efe49a7ab2dcc9 Mon Sep 17 00:00:00 2001 From: Quentin Harnay Date: Mon, 6 Apr 2020 14:52:09 +0200 Subject: [PATCH 1/5] 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 5760913..1d43578 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->withRejectErrorResponse(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 ecf7f2b..4f67aa8 100644 --- a/tests/EventSourceTest.php +++ b/tests/EventSourceTest.php @@ -47,6 +47,43 @@ public function testConstructorCanBeCalledWithoutBrowser() $this->assertInstanceOf('React\Http\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 7ec7fc96c6c2e6b018c19fa5c63d7a1312eef53b Mon Sep 17 00:00:00 2001 From: Quentin Harnay Date: Mon, 27 Apr 2020 17:32:05 +0200 Subject: [PATCH 2/5] 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 4f67aa8..e191321 100644 --- a/tests/EventSourceTest.php +++ b/tests/EventSourceTest.php @@ -69,7 +69,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); @@ -79,7 +79,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 af4b8e155bd805a09acce77716d7c900bbfc9eec Mon Sep 17 00:00:00 2001 From: Quentin Harnay Date: Mon, 27 Apr 2020 17:34:05 +0200 Subject: [PATCH 3/5] 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 e191321..e152740 100644 --- a/tests/EventSourceTest.php +++ b/tests/EventSourceTest.php @@ -79,7 +79,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 2bf536e75eb1aa4b96e8b8b7dcecb7c086c1bef6 Mon Sep 17 00:00:00 2001 From: Quentin Harnay Date: Tue, 28 Apr 2020 15:13:25 +0200 Subject: [PATCH 4/5] 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 1d43578..94e3137 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->withRejectErrorResponse(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 e152740..008ec8f 100644 --- a/tests/EventSourceTest.php +++ b/tests/EventSourceTest.php @@ -69,7 +69,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); From a6499394de994fef3d8b8e1b6f616f03615c89fb Mon Sep 17 00:00:00 2001 From: Beno!t POLASZEK Date: Tue, 22 Sep 2020 15:22:08 +0200 Subject: [PATCH 5/5] feat: custom headers - rebased on master --- tests/EventSourceTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/EventSourceTest.php b/tests/EventSourceTest.php index 008ec8f..5749d53 100644 --- a/tests/EventSourceTest.php +++ b/tests/EventSourceTest.php @@ -6,9 +6,9 @@ use PHPUnit\Framework\TestCase; use React\Promise\Promise; use React\Promise\Deferred; -use React\Http\Browser; use React\Http\Io\ReadableBodyStream; use React\Stream\ThroughStream; +use ReflectionProperty; use RingCentral\Psr7\Response; class EventSourceTest extends TestCase @@ -47,7 +47,7 @@ public function testConstructorCanBeCalledWithoutBrowser() $this->assertInstanceOf('React\Http\Browser', $browser); } - + public function testConstructorCanBeCalledWithoutCustomHeaders() { $loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock(); @@ -61,7 +61,7 @@ public function testConstructorCanBeCalledWithoutCustomHeaders() $ref = new ReflectionProperty($es, 'defaultHeaders'); $ref->setAccessible(true); $defaultHeaders = $ref->getValue($es); - + $this->assertEquals($defaultHeaders, $headers); } @@ -80,8 +80,8 @@ public function testConstructorCanBeCalledWithCustomHeaders() $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 + // 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',