From f7f9bf0d860463f50b8ab1added357c7db805d33 Mon Sep 17 00:00:00 2001 From: 51imyyy Date: Sat, 13 Aug 2022 19:49:11 +0200 Subject: [PATCH] added support for default headers in Browser PHP and moved default header user-agent to the default headers. --- README.md | 16 ++++++++ src/Browser.php | 56 +++++++++++++++++++------- src/Client/RequestData.php | 1 - tests/BrowserTest.php | 67 +++++++++++++++++++++++++++++++- tests/Client/RequestDataTest.php | 8 ---- tests/Client/RequestTest.php | 10 ++--- 6 files changed, 128 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index d3a93ceb..82774d90 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,8 @@ multiple concurrent HTTP requests without blocking. * [withBase()](#withbase) * [withProtocolVersion()](#withprotocolversion) * [withResponseBuffer()](#withresponsebuffer) + * [withHeader()](#withheader) + * [withoutHeader()](#withoutheader) * [React\Http\Message](#reacthttpmessage) * [Response](#response) * [html()](#html) @@ -2381,6 +2383,20 @@ Notice that the [`Browser`](#browser) is an immutable object, i.e. this method actually returns a *new* [`Browser`](#browser) instance with the given setting applied. +#### withHeader() + +The `withHeader(string $header, string $value): Browser` method can be used to +add a request header for all following requests. + +Note that the new header will overwrite any headers previously set with the same name (case-insensitive). Following requests will use these headers by default unless they are explicitly set for any requests. + +#### withoutHeader() + +The `withoutHeader(string $header): Browser` method can be used to +remove any default request headers previously set via the [`withHeader()` method](#withheader). + +Note that this method only affects the headers which were set with the method `withHeader(string $header, string $value): Browser` + ### React\Http\Message #### Response diff --git a/src/Browser.php b/src/Browser.php index c70e8e9d..65c180e2 100644 --- a/src/Browser.php +++ b/src/Browser.php @@ -23,7 +23,9 @@ class Browser private $transaction; private $baseUrl; private $protocolVersion = '1.1'; - private $headers = array(); + private $defaultHeaders = array( + 'User-Agent' => 'ReactPHP/1', + ); /** * The `Browser` is responsible for sending HTTP requests to your HTTP server @@ -727,26 +729,43 @@ public function withResponseBuffer($maximumSize) } /** - * @param $header - * @param $value - * @return Browser - *@todo documentation + * The `withHeader(string $header, string $value): Browser` method can be used to + * add a request header for all following requests. + * + * Note that the new header will overwrite any headers previously set with the same name (case-insensitive). Following requests will use these headers by default unless they are explicitly set for any requests. * + * @param string $header + * @param string $value + * @return Browser */ public function withHeader($header, $value) { - $browser = clone $this; - $browser->headers[$header] = $value; + $browser = $this->withoutHeader($header); + $browser->defaultHeaders[$header] = $value; return $browser; } + /** + * + * The `withoutHeader(string $header): Browser` method can be used to + * remove any default request headers previously set via the [`withHeader()` method](#withheader). + * + * Note that this method only affects the headers which were set with the method `withHeader(string $header, string $value): Browser` + * + * @param string $header + * @return Browser + */ public function withoutHeader($header) { $browser = clone $this; - if (array_key_exists($header, $browser->headers)) { - unset($browser->headers[$header]); + /** @var string|int $key */ + foreach (\array_keys($browser->defaultHeaders) as $key) { + if (\strcasecmp($key, $header) === 0) { + unset($browser->defaultHeaders[$key]); + break; + } } return $browser; @@ -810,13 +829,20 @@ private function requestMayBeStreaming($method, $url, array $headers = array(), $body = new ReadableBodyStream($body); } + foreach ($this->defaultHeaders as $key => $value) { + if ($headers === array()) { + $headers = $this->defaultHeaders; + break; + } + foreach (\array_keys($headers) as $headerKey) { + if (\strcasecmp($headerKey, $key) !== 0) { + $headers[$key] = $value; + } + } + } + return $this->transaction->send( - new Request($method, $url, $this->mergeHeaders($headers), $body, $this->protocolVersion) + new Request($method, $url, $headers, $body, $this->protocolVersion) ); } - - private function mergeHeaders(array $headers) - { - return array_merge($headers, $this->headers); - } } diff --git a/src/Client/RequestData.php b/src/Client/RequestData.php index a5908a08..04bb4cad 100644 --- a/src/Client/RequestData.php +++ b/src/Client/RequestData.php @@ -29,7 +29,6 @@ private function mergeDefaultheaders(array $headers) $defaults = array_merge( array( 'Host' => $this->getHost().$port, - 'User-Agent' => 'ReactPHP/1', ), $connectionHeaders, $authHeaders diff --git a/tests/BrowserTest.php b/tests/BrowserTest.php index 8019d967..b821ab48 100644 --- a/tests/BrowserTest.php +++ b/tests/BrowserTest.php @@ -510,7 +510,72 @@ public function testWithElseHeader() $that = $this; $this->sender->expects($this->once())->method('send')->with($this->callback(function (RequestInterface $request) use ($that) { - $that->assertEquals('ACMC', $request->getHeader('User-Agent')); + $that->assertEquals(array('ACMC'), $request->getHeader('User-Agent')); + return true; + }))->willReturn(new Promise(function () { })); + + $this->browser->get('http://example.com/'); + } + + public function testWithHeaderShouldOverwriteExistingHeader() + { + $this->browser = $this->browser->withHeader('User-Agent', 'ACMC'); //should be overwritten + $this->browser = $this->browser->withHeader('user-agent', 'ABC'); //should be the user-agent + + $that = $this; + $this->sender->expects($this->once())->method('send')->with($this->callback(function (RequestInterface $request) use ($that) { + $that->assertEquals(array('ABC'), $request->getHeader('UsEr-AgEnT')); + return true; + }))->willReturn(new Promise(function () { })); + + $this->browser->get('http://example.com/'); + } + + public function testWithHeadersShouldBeMergedCorrectlyWithDefaultHeaders() + { + $this->browser = $this->browser->withHeader('User-Agent', 'ACMC'); + + $that = $this; + $this->sender->expects($this->once())->method('send')->with($this->callback(function (RequestInterface $request) use ($that) { + $that->assertEquals(array('ABC'), $request->getHeader('UsEr-AgEnT')); + return true; + }))->willReturn(new Promise(function () { })); + + $this->browser->get('http://example.com/', array('user-Agent' => 'ABC')); //should win + } + + public function testWithoutHeaderShouldRemoveExistingHeader() + { + $this->browser = $this->browser->withHeader('User-Agent', 'ACMC'); + $this->browser = $this->browser->withoutHeader('UsEr-AgEnT'); //should remove case-insensitive header + + $that = $this; + $this->sender->expects($this->once())->method('send')->with($this->callback(function (RequestInterface $request) use ($that) { + $that->assertEquals(array(), $request->getHeader('UsEr-AgEnT')); + return true; + }))->willReturn(new Promise(function () { })); + + $this->browser->get('http://example.com/'); + } + + public function testBrowserShouldHaveDefaultHeaderReactPHP() + { + $that = $this; + $this->sender->expects($this->once())->method('send')->with($this->callback(function (RequestInterface $request) use ($that) { + $that->assertEquals(array(0 => 'ReactPHP/1'), $request->getHeader('UsEr-AgEnT')); + return true; + }))->willReturn(new Promise(function () { })); + + $this->browser->get('http://example.com/'); + } + + public function testWithoutHeaderShouldRemoveDefaultHeader() + { + $this->browser = $this->browser->withoutHeader('UsEr-AgEnT'); + + $that = $this; + $this->sender->expects($this->once())->method('send')->with($this->callback(function (RequestInterface $request) use ($that) { + $that->assertEquals(array(), $request->getHeader('User-Agent')); return true; }))->willReturn(new Promise(function () { })); diff --git a/tests/Client/RequestDataTest.php b/tests/Client/RequestDataTest.php index 7f96e152..f6713e85 100644 --- a/tests/Client/RequestDataTest.php +++ b/tests/Client/RequestDataTest.php @@ -14,7 +14,6 @@ public function toStringReturnsHTTPRequestMessage() $expected = "GET / HTTP/1.0\r\n" . "Host: www.example.com\r\n" . - "User-Agent: ReactPHP/1\r\n" . "\r\n"; $this->assertSame($expected, $requestData->__toString()); @@ -27,7 +26,6 @@ public function toStringReturnsHTTPRequestMessageWithEmptyQueryString() $expected = "GET /path?hello=world HTTP/1.0\r\n" . "Host: www.example.com\r\n" . - "User-Agent: ReactPHP/1\r\n" . "\r\n"; $this->assertSame($expected, $requestData->__toString()); @@ -40,7 +38,6 @@ public function toStringReturnsHTTPRequestMessageWithZeroQueryStringAndRootPath( $expected = "GET /?0 HTTP/1.0\r\n" . "Host: www.example.com\r\n" . - "User-Agent: ReactPHP/1\r\n" . "\r\n"; $this->assertSame($expected, $requestData->__toString()); @@ -53,7 +50,6 @@ public function toStringReturnsHTTPRequestMessageWithOptionsAbsoluteRequestForm( $expected = "OPTIONS / HTTP/1.0\r\n" . "Host: www.example.com\r\n" . - "User-Agent: ReactPHP/1\r\n" . "\r\n"; $this->assertSame($expected, $requestData->__toString()); @@ -66,7 +62,6 @@ public function toStringReturnsHTTPRequestMessageWithOptionsAsteriskRequestForm( $expected = "OPTIONS * HTTP/1.0\r\n" . "Host: www.example.com\r\n" . - "User-Agent: ReactPHP/1\r\n" . "\r\n"; $this->assertSame($expected, $requestData->__toString()); @@ -80,7 +75,6 @@ public function toStringReturnsHTTPRequestMessageWithProtocolVersion() $expected = "GET / HTTP/1.1\r\n" . "Host: www.example.com\r\n" . - "User-Agent: ReactPHP/1\r\n" . "Connection: close\r\n" . "\r\n"; @@ -131,7 +125,6 @@ public function toStringReturnsHTTPRequestMessageWithProtocolVersionThroughConst $expected = "GET / HTTP/1.1\r\n" . "Host: www.example.com\r\n" . - "User-Agent: ReactPHP/1\r\n" . "Connection: close\r\n" . "\r\n"; @@ -145,7 +138,6 @@ public function toStringUsesUserPassFromURL() $expected = "GET / HTTP/1.0\r\n" . "Host: www.example.com\r\n" . - "User-Agent: ReactPHP/1\r\n" . "Authorization: Basic am9objpkdW1teQ==\r\n" . "\r\n"; diff --git a/tests/Client/RequestTest.php b/tests/Client/RequestTest.php index fb2dc884..cdb209cf 100644 --- a/tests/Client/RequestTest.php +++ b/tests/Client/RequestTest.php @@ -181,7 +181,7 @@ public function postRequestShouldSendAPostRequest() $this->stream ->expects($this->once()) ->method('write') - ->with($this->matchesRegularExpression("#^POST / HTTP/1\.0\r\nHost: www.example.com\r\nUser-Agent:.*\r\n\r\nsome post data$#")); + ->with($this->matchesRegularExpression("#^POST / HTTP/1\.0\r\nHost: www.example.com\r\n\r\nsome post data$#")); $request->end('some post data'); @@ -199,7 +199,7 @@ public function writeWithAPostRequestShouldSendToTheStream() $this->successfulConnectionMock(); $this->stream->expects($this->exactly(3))->method('write')->withConsecutive( - array($this->matchesRegularExpression("#^POST / HTTP/1\.0\r\nHost: www.example.com\r\nUser-Agent:.*\r\n\r\nsome$#")), + array($this->matchesRegularExpression("#^POST / HTTP/1\.0\r\nHost: www.example.com\r\n\r\nsome$#")), array($this->identicalTo("post")), array($this->identicalTo("data")) ); @@ -222,7 +222,7 @@ public function writeWithAPostRequestShouldSendBodyAfterHeadersAndEmitDrainEvent $resolveConnection = $this->successfulAsyncConnectionMock(); $this->stream->expects($this->exactly(2))->method('write')->withConsecutive( - array($this->matchesRegularExpression("#^POST / HTTP/1\.0\r\nHost: www.example.com\r\nUser-Agent:.*\r\n\r\nsomepost$#")), + array($this->matchesRegularExpression("#^POST / HTTP/1\.0\r\nHost: www.example.com\r\n\r\nsomepost$#")), array($this->identicalTo("data")) )->willReturn( true @@ -258,7 +258,7 @@ public function writeWithAPostRequestShouldForwardDrainEventIfFirstChunkExceedsB $resolveConnection = $this->successfulAsyncConnectionMock(); $this->stream->expects($this->exactly(2))->method('write')->withConsecutive( - array($this->matchesRegularExpression("#^POST / HTTP/1\.0\r\nHost: www.example.com\r\nUser-Agent:.*\r\n\r\nsomepost$#")), + array($this->matchesRegularExpression("#^POST / HTTP/1\.0\r\nHost: www.example.com\r\n\r\nsomepost$#")), array($this->identicalTo("data")) )->willReturn( false @@ -290,7 +290,7 @@ public function pipeShouldPipeDataIntoTheRequestBody() $this->successfulConnectionMock(); $this->stream->expects($this->exactly(3))->method('write')->withConsecutive( - array($this->matchesRegularExpression("#^POST / HTTP/1\.0\r\nHost: www.example.com\r\nUser-Agent:.*\r\n\r\nsome$#")), + array($this->matchesRegularExpression("#^POST / HTTP/1\.0\r\nHost: www.example.com\r\n\r\nsome$#")), array($this->identicalTo("post")), array($this->identicalTo("data")) );