Skip to content

Commit 99d458f

Browse files
committed
added support for default headers in Browser PHP and moved default header user-agent to the default headers.
1 parent cf6b150 commit 99d458f

File tree

6 files changed

+219
-14
lines changed

6 files changed

+219
-14
lines changed

README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ multiple concurrent HTTP requests without blocking.
6969
* [withBase()](#withbase)
7070
* [withProtocolVersion()](#withprotocolversion)
7171
* [withResponseBuffer()](#withresponsebuffer)
72+
* [withHeader()](#withheader)
73+
* [withoutHeader()](#withoutheader)
7274
* [React\Http\Message](#reacthttpmessage)
7375
* [Response](#response)
7476
* [html()](#html)
@@ -2381,6 +2383,42 @@ Notice that the [`Browser`](#browser) is an immutable object, i.e. this
23812383
method actually returns a *new* [`Browser`](#browser) instance with the
23822384
given setting applied.
23832385

2386+
#### withHeader()
2387+
2388+
The `withHeader(string $header, string $value): Browser` method can be used to
2389+
add a request header for all following requests.
2390+
2391+
```php
2392+
$browser = $browser->withHeader('User-Agent', 'ACME');
2393+
2394+
$browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
2395+
//will contain the header User-Agent with the value ACME
2396+
var_dump($response->getHeaders());
2397+
}, function (Exception $e) {
2398+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
2399+
});
2400+
```
2401+
2402+
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.
2403+
2404+
#### withoutHeader()
2405+
2406+
The `withoutHeader(string $header): Browser` method can be used to
2407+
remove any default request headers previously set via the [`withHeader()` method](#withheader).
2408+
2409+
```php
2410+
$browser = $browser->withoutHeader('User-Agent');
2411+
2412+
$browser->get($url)->then(function (Psr\Http\Message\ResponseInterface $response) {
2413+
//won't contain the User-Agent header
2414+
var_dump($response->getHeaders());
2415+
}, function (Exception $e) {
2416+
echo 'Error: ' . $e->getMessage() . PHP_EOL;
2417+
});
2418+
```
2419+
2420+
Note that this method only affects the headers which were set with the method `withHeader(string $header, string $value): Browser`
2421+
23842422
### React\Http\Message
23852423

23862424
#### Response

src/Browser.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ class Browser
2323
private $transaction;
2424
private $baseUrl;
2525
private $protocolVersion = '1.1';
26+
private $defaultHeaders = array(
27+
'User-Agent' => 'ReactPHP/1',
28+
);
2629

2730
/**
2831
* The `Browser` is responsible for sending HTTP requests to your HTTP server
@@ -725,6 +728,52 @@ public function withResponseBuffer($maximumSize)
725728
));
726729
}
727730

731+
/**
732+
* The `withHeader(string $header, string $value): Browser` method can be
733+
* used to add a request header for all following requests.
734+
*
735+
* Note that the new header will overwrite any headers previously set with
736+
* the same name (case-insensitive). Following requests will use these headers
737+
* by default unless they are explicitly set for any requests.
738+
*
739+
* @param string $header
740+
* @param string $value
741+
* @return Browser
742+
*/
743+
public function withHeader($header, $value)
744+
{
745+
$browser = $this->withoutHeader($header);
746+
$browser->defaultHeaders[$header] = $value;
747+
748+
return $browser;
749+
}
750+
751+
/**
752+
* The `withoutHeader(string $header): Browser` method can be used to
753+
* remove any default request headers previously set via
754+
* the [`withHeader()` method](#withheader).
755+
*
756+
* Note that this method only affects the headers which were set with the
757+
* method `withHeader(string $header, string $value): Browser`
758+
*
759+
* @param string $header
760+
* @return Browser
761+
*/
762+
public function withoutHeader($header)
763+
{
764+
$browser = clone $this;
765+
766+
/** @var string|int $key */
767+
foreach (\array_keys($browser->defaultHeaders) as $key) {
768+
if (\strcasecmp($key, $header) === 0) {
769+
unset($browser->defaultHeaders[$key]);
770+
break;
771+
}
772+
}
773+
774+
return $browser;
775+
}
776+
728777
/**
729778
* Changes the [options](#options) to use:
730779
*
@@ -783,6 +832,23 @@ private function requestMayBeStreaming($method, $url, array $headers = array(),
783832
$body = new ReadableBodyStream($body);
784833
}
785834

835+
foreach ($this->defaultHeaders as $key => $value) {
836+
if ($headers === array()) {
837+
$headers = $this->defaultHeaders;
838+
break;
839+
}
840+
841+
$explicitHeaderExists = false;
842+
foreach (\array_keys($headers) as $headerKey) {
843+
if (\strcasecmp($headerKey, $key) === 0) {
844+
$explicitHeaderExists = true;
845+
}
846+
}
847+
if (!$explicitHeaderExists) {
848+
$headers[$key] = $value;
849+
}
850+
}
851+
786852
return $this->transaction->send(
787853
new Request($method, $url, $headers, $body, $this->protocolVersion)
788854
);

src/Client/RequestData.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ private function mergeDefaultheaders(array $headers)
2929
$defaults = array_merge(
3030
array(
3131
'Host' => $this->getHost().$port,
32-
'User-Agent' => 'ReactPHP/1',
3332
),
3433
$connectionHeaders,
3534
$authHeaders

tests/BrowserTest.php

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,4 +503,114 @@ public function testCancelGetRequestShouldCancelUnderlyingSocketConnection()
503503
$promise = $this->browser->get('http://example.com/');
504504
$promise->cancel();
505505
}
506+
507+
public function testWithElseHeader()
508+
{
509+
$this->browser = $this->browser->withHeader('User-Agent', 'ACMC');
510+
511+
$that = $this;
512+
$this->sender->expects($this->once())->method('send')->with($this->callback(function (RequestInterface $request) use ($that) {
513+
$that->assertEquals(array('ACMC'), $request->getHeader('User-Agent'));
514+
return true;
515+
}))->willReturn(new Promise(function () { }));
516+
517+
$this->browser->get('http://example.com/');
518+
}
519+
520+
public function testWithHeaderShouldOverwriteExistingHeader()
521+
{
522+
$this->browser = $this->browser->withHeader('User-Agent', 'ACMC'); //should be overwritten
523+
$this->browser = $this->browser->withHeader('user-agent', 'ABC'); //should be the user-agent
524+
525+
$that = $this;
526+
$this->sender->expects($this->once())->method('send')->with($this->callback(function (RequestInterface $request) use ($that) {
527+
$that->assertEquals(array('ABC'), $request->getHeader('UsEr-AgEnT'));
528+
return true;
529+
}))->willReturn(new Promise(function () { }));
530+
531+
$this->browser->get('http://example.com/');
532+
}
533+
534+
public function testWithHeadersShouldBeMergedCorrectlyWithDefaultHeaders()
535+
{
536+
$this->browser = $this->browser->withHeader('User-Agent', 'ACMC');
537+
538+
$that = $this;
539+
$this->sender->expects($this->once())->method('send')->with($this->callback(function (RequestInterface $request) use ($that) {
540+
$that->assertEquals(array('ABC'), $request->getHeader('UsEr-AgEnT'));
541+
return true;
542+
}))->willReturn(new Promise(function () { }));
543+
544+
$this->browser->get('http://example.com/', array('user-Agent' => 'ABC')); //should win
545+
}
546+
547+
public function testWithMultipleHeadersShouldBeMergedCorrectlyWithMultipleDefaultHeaders()
548+
{
549+
$this->browser = $this->browser->withHeader('User-Agent', 'ACMC');
550+
$this->browser = $this->browser->withHeader('User-Test', 'Test');
551+
$this->browser = $this->browser->withHeader('Custom-HEADER', 'custom');
552+
$this->browser = $this->browser->withHeader('just-a-header', 'header-value');
553+
554+
$that = $this;
555+
$this->sender->expects($this->once())->method('send')->with($this->callback(function (RequestInterface $request) use ($that) {
556+
$expectedHeaders = array(
557+
'Host' => array('example.com'),
558+
559+
'User-Test' => array('Test'),
560+
'just-a-header' => array('header-value'),
561+
562+
'user-Agent' => array('ABC'),
563+
'another-header' => array('value'),
564+
'custom-header' => array('data'),
565+
);
566+
567+
$that->assertEquals($expectedHeaders, $request->getHeaders());
568+
return true;
569+
}))->willReturn(new Promise(function () { }));
570+
571+
$headers = array(
572+
'user-Agent' => 'ABC', //should overwrite: 'User-Agent', 'ACMC'
573+
'another-header' => 'value',
574+
'custom-header' => 'data', //should overwrite: 'Custom-header', 'custom'
575+
);
576+
$this->browser->get('http://example.com/', $headers);
577+
}
578+
579+
public function testWithoutHeaderShouldRemoveExistingHeader()
580+
{
581+
$this->browser = $this->browser->withHeader('User-Agent', 'ACMC');
582+
$this->browser = $this->browser->withoutHeader('UsEr-AgEnT'); //should remove case-insensitive header
583+
584+
$that = $this;
585+
$this->sender->expects($this->once())->method('send')->with($this->callback(function (RequestInterface $request) use ($that) {
586+
$that->assertEquals(array(), $request->getHeader('user-agent'));
587+
return true;
588+
}))->willReturn(new Promise(function () { }));
589+
590+
$this->browser->get('http://example.com/');
591+
}
592+
593+
public function testBrowserShouldHaveDefaultHeaderReactPHP()
594+
{
595+
$that = $this;
596+
$this->sender->expects($this->once())->method('send')->with($this->callback(function (RequestInterface $request) use ($that) {
597+
$that->assertEquals(array(0 => 'ReactPHP/1'), $request->getHeader('user-agent'));
598+
return true;
599+
}))->willReturn(new Promise(function () { }));
600+
601+
$this->browser->get('http://example.com/');
602+
}
603+
604+
public function testWithoutHeaderShouldRemoveDefaultHeader()
605+
{
606+
$this->browser = $this->browser->withoutHeader('UsEr-AgEnT');
607+
608+
$that = $this;
609+
$this->sender->expects($this->once())->method('send')->with($this->callback(function (RequestInterface $request) use ($that) {
610+
$that->assertEquals(array(), $request->getHeader('User-Agent'));
611+
return true;
612+
}))->willReturn(new Promise(function () { }));
613+
614+
$this->browser->get('http://example.com/');
615+
}
506616
}

tests/Client/RequestDataTest.php

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ public function toStringReturnsHTTPRequestMessage()
1414

1515
$expected = "GET / HTTP/1.0\r\n" .
1616
"Host: www.example.com\r\n" .
17-
"User-Agent: ReactPHP/1\r\n" .
1817
"\r\n";
1918

2019
$this->assertSame($expected, $requestData->__toString());
@@ -27,7 +26,6 @@ public function toStringReturnsHTTPRequestMessageWithEmptyQueryString()
2726

2827
$expected = "GET /path?hello=world HTTP/1.0\r\n" .
2928
"Host: www.example.com\r\n" .
30-
"User-Agent: ReactPHP/1\r\n" .
3129
"\r\n";
3230

3331
$this->assertSame($expected, $requestData->__toString());
@@ -40,7 +38,6 @@ public function toStringReturnsHTTPRequestMessageWithZeroQueryStringAndRootPath(
4038

4139
$expected = "GET /?0 HTTP/1.0\r\n" .
4240
"Host: www.example.com\r\n" .
43-
"User-Agent: ReactPHP/1\r\n" .
4441
"\r\n";
4542

4643
$this->assertSame($expected, $requestData->__toString());
@@ -53,7 +50,6 @@ public function toStringReturnsHTTPRequestMessageWithOptionsAbsoluteRequestForm(
5350

5451
$expected = "OPTIONS / HTTP/1.0\r\n" .
5552
"Host: www.example.com\r\n" .
56-
"User-Agent: ReactPHP/1\r\n" .
5753
"\r\n";
5854

5955
$this->assertSame($expected, $requestData->__toString());
@@ -66,7 +62,6 @@ public function toStringReturnsHTTPRequestMessageWithOptionsAsteriskRequestForm(
6662

6763
$expected = "OPTIONS * HTTP/1.0\r\n" .
6864
"Host: www.example.com\r\n" .
69-
"User-Agent: ReactPHP/1\r\n" .
7065
"\r\n";
7166

7267
$this->assertSame($expected, $requestData->__toString());
@@ -80,7 +75,6 @@ public function toStringReturnsHTTPRequestMessageWithProtocolVersion()
8075

8176
$expected = "GET / HTTP/1.1\r\n" .
8277
"Host: www.example.com\r\n" .
83-
"User-Agent: ReactPHP/1\r\n" .
8478
"Connection: close\r\n" .
8579
"\r\n";
8680

@@ -131,7 +125,6 @@ public function toStringReturnsHTTPRequestMessageWithProtocolVersionThroughConst
131125

132126
$expected = "GET / HTTP/1.1\r\n" .
133127
"Host: www.example.com\r\n" .
134-
"User-Agent: ReactPHP/1\r\n" .
135128
"Connection: close\r\n" .
136129
"\r\n";
137130

@@ -145,7 +138,6 @@ public function toStringUsesUserPassFromURL()
145138

146139
$expected = "GET / HTTP/1.0\r\n" .
147140
"Host: www.example.com\r\n" .
148-
"User-Agent: ReactPHP/1\r\n" .
149141
"Authorization: Basic am9objpkdW1teQ==\r\n" .
150142
"\r\n";
151143

tests/Client/RequestTest.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ public function postRequestShouldSendAPostRequest()
181181
$this->stream
182182
->expects($this->once())
183183
->method('write')
184-
->with($this->matchesRegularExpression("#^POST / HTTP/1\.0\r\nHost: www.example.com\r\nUser-Agent:.*\r\n\r\nsome post data$#"));
184+
->with($this->matchesRegularExpression("#^POST / HTTP/1\.0\r\nHost: www.example.com\r\n\r\nsome post data$#"));
185185

186186
$request->end('some post data');
187187

@@ -199,7 +199,7 @@ public function writeWithAPostRequestShouldSendToTheStream()
199199
$this->successfulConnectionMock();
200200

201201
$this->stream->expects($this->exactly(3))->method('write')->withConsecutive(
202-
array($this->matchesRegularExpression("#^POST / HTTP/1\.0\r\nHost: www.example.com\r\nUser-Agent:.*\r\n\r\nsome$#")),
202+
array($this->matchesRegularExpression("#^POST / HTTP/1\.0\r\nHost: www.example.com\r\n\r\nsome$#")),
203203
array($this->identicalTo("post")),
204204
array($this->identicalTo("data"))
205205
);
@@ -222,7 +222,7 @@ public function writeWithAPostRequestShouldSendBodyAfterHeadersAndEmitDrainEvent
222222
$resolveConnection = $this->successfulAsyncConnectionMock();
223223

224224
$this->stream->expects($this->exactly(2))->method('write')->withConsecutive(
225-
array($this->matchesRegularExpression("#^POST / HTTP/1\.0\r\nHost: www.example.com\r\nUser-Agent:.*\r\n\r\nsomepost$#")),
225+
array($this->matchesRegularExpression("#^POST / HTTP/1\.0\r\nHost: www.example.com\r\n\r\nsomepost$#")),
226226
array($this->identicalTo("data"))
227227
)->willReturn(
228228
true
@@ -258,7 +258,7 @@ public function writeWithAPostRequestShouldForwardDrainEventIfFirstChunkExceedsB
258258
$resolveConnection = $this->successfulAsyncConnectionMock();
259259

260260
$this->stream->expects($this->exactly(2))->method('write')->withConsecutive(
261-
array($this->matchesRegularExpression("#^POST / HTTP/1\.0\r\nHost: www.example.com\r\nUser-Agent:.*\r\n\r\nsomepost$#")),
261+
array($this->matchesRegularExpression("#^POST / HTTP/1\.0\r\nHost: www.example.com\r\n\r\nsomepost$#")),
262262
array($this->identicalTo("data"))
263263
)->willReturn(
264264
false
@@ -290,7 +290,7 @@ public function pipeShouldPipeDataIntoTheRequestBody()
290290
$this->successfulConnectionMock();
291291

292292
$this->stream->expects($this->exactly(3))->method('write')->withConsecutive(
293-
array($this->matchesRegularExpression("#^POST / HTTP/1\.0\r\nHost: www.example.com\r\nUser-Agent:.*\r\n\r\nsome$#")),
293+
array($this->matchesRegularExpression("#^POST / HTTP/1\.0\r\nHost: www.example.com\r\n\r\nsome$#")),
294294
array($this->identicalTo("post")),
295295
array($this->identicalTo("data"))
296296
);

0 commit comments

Comments
 (0)