Skip to content

Commit

Permalink
added support for default headers in Browser PHP and moved default he…
Browse files Browse the repository at this point in the history
…ader user-agent to the default headers.
  • Loading branch information
51imyyy committed Aug 30, 2022
1 parent cf6b150 commit 443fdf3
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 14 deletions.
33 changes: 33 additions & 0 deletions README.md
Expand Up @@ -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)
Expand Down Expand Up @@ -2381,6 +2383,37 @@ 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.
```php
var_dump($response);
}, function (Exception $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
```

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).

```php
var_dump($response);
}, function (Exception $e) {
echo 'Error: ' . $e->getMessage() . PHP_EOL;
});
```

Note that this method only affects the headers which were set with the
method `withHeader(string $header, string $value): Browser`

### React\Http\Message

#### Response
Expand Down
78 changes: 78 additions & 0 deletions src/Browser.php
Expand Up @@ -23,6 +23,9 @@ class Browser
private $transaction;
private $baseUrl;
private $protocolVersion = '1.1';
private $defaultHeaders = array(
'User-Agent' => 'ReactPHP/1',
);

/**
* The `Browser` is responsible for sending HTTP requests to your HTTP server
Expand Down Expand Up @@ -725,6 +728,64 @@ public function withResponseBuffer($maximumSize)
));
}

/**
* Add a request header for all following requests.
*
* ```php
* var_dump($response);
* }, function (Exception $e) {
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
* });
* ```
*
* 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 = $this->withoutHeader($header);
$browser->defaultHeaders[$header] = $value;

return $browser;
}

/**
* Remove any default request headers previously set via
* the [`withHeader()` method](#withheader).
*
* ```php
* var_dump($response);
* }, function (Exception $e) {
* echo 'Error: ' . $e->getMessage() . PHP_EOL;
* });
* ```
*
* 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;

/** @var string|int $key */
foreach (\array_keys($browser->defaultHeaders) as $key) {
if (\strcasecmp($key, $header) === 0) {
unset($browser->defaultHeaders[$key]);
break;
}
}

return $browser;
}

/**
* Changes the [options](#options) to use:
*
Expand Down Expand Up @@ -783,6 +844,23 @@ 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;
}

$explicitHeaderExists = false;
foreach (\array_keys($headers) as $headerKey) {
if (\strcasecmp($headerKey, $key) === 0) {
$explicitHeaderExists = true;
}
}
if (!$explicitHeaderExists) {
$headers[$key] = $value;
}
}

return $this->transaction->send(
new Request($method, $url, $headers, $body, $this->protocolVersion)
);
Expand Down
1 change: 0 additions & 1 deletion src/Client/RequestData.php
Expand Up @@ -29,7 +29,6 @@ private function mergeDefaultheaders(array $headers)
$defaults = array_merge(
array(
'Host' => $this->getHost().$port,
'User-Agent' => 'ReactPHP/1',
),
$connectionHeaders,
$authHeaders
Expand Down
110 changes: 110 additions & 0 deletions tests/BrowserTest.php
Expand Up @@ -503,4 +503,114 @@ public function testCancelGetRequestShouldCancelUnderlyingSocketConnection()
$promise = $this->browser->get('http://example.com/');
$promise->cancel();
}

public function testWithElseHeader()
{
$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('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 testWithMultipleHeadersShouldBeMergedCorrectlyWithMultipleDefaultHeaders()
{
$this->browser = $this->browser->withHeader('User-Agent', 'ACMC');
$this->browser = $this->browser->withHeader('User-Test', 'Test');
$this->browser = $this->browser->withHeader('Custom-HEADER', 'custom');
$this->browser = $this->browser->withHeader('just-a-header', 'header-value');

$that = $this;
$this->sender->expects($this->once())->method('send')->with($this->callback(function (RequestInterface $request) use ($that) {
$expectedHeaders = array(
'Host' => array('example.com'),

'User-Test' => array('Test'),
'just-a-header' => array('header-value'),

'user-Agent' => array('ABC'),
'another-header' => array('value'),
'custom-header' => array('data'),
);

$that->assertEquals($expectedHeaders, $request->getHeaders());
return true;
}))->willReturn(new Promise(function () { }));

$headers = array(
'user-Agent' => 'ABC', //should overwrite: 'User-Agent', 'ACMC'
'another-header' => 'value',
'custom-header' => 'data', //should overwrite: 'Custom-header', 'custom'
);
$this->browser->get('http://example.com/', $headers);
}

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 () { }));

$this->browser->get('http://example.com/');
}
}
8 changes: 0 additions & 8 deletions tests/Client/RequestDataTest.php
Expand Up @@ -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());
Expand All @@ -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());
Expand All @@ -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());
Expand All @@ -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());
Expand All @@ -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());
Expand All @@ -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";

Expand Down Expand Up @@ -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";

Expand All @@ -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";

Expand Down
10 changes: 5 additions & 5 deletions tests/Client/RequestTest.php
Expand Up @@ -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');

Expand All @@ -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"))
);
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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"))
);
Expand Down

0 comments on commit 443fdf3

Please sign in to comment.