Skip to content

Commit 0c27d67

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 0c27d67

File tree

6 files changed

+206
-14
lines changed

6 files changed

+206
-14
lines changed

README.md

Lines changed: 32 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,36 @@ 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(…);
2395+
```
2396+
2397+
Note that the new header will overwrite any headers previously set with
2398+
the same name (case-insensitive). Following requests will use these headers
2399+
by default unless they are explicitly set for any requests.
2400+
2401+
#### withoutHeader()
2402+
2403+
The `withoutHeader(string $header): Browser` method can be used to
2404+
remove any default request headers previously set via
2405+
the [`withHeader()` method](#withheader).
2406+
2407+
```php
2408+
$browser = $browser->withoutHeader('User-Agent');
2409+
2410+
$browser->get($url)->then(…);
2411+
```
2412+
2413+
Note that this method only affects the headers which were set with the
2414+
method `withHeader(string $header, string $value): Browser`
2415+
23842416
### React\Http\Message
23852417

23862418
#### Response

src/Browser.php

Lines changed: 72 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,62 @@ public function withResponseBuffer($maximumSize)
725728
));
726729
}
727730

731+
/**
732+
* Add a request header for all following requests.
733+
*
734+
* ```php
735+
* $browser = $browser->withHeader('User-Agent', 'ACME');
736+
*
737+
* $browser->get($url)->then(…);
738+
* ```
739+
*
740+
* Note that the new header will overwrite any headers previously set with
741+
* the same name (case-insensitive). Following requests will use these headers
742+
* by default unless they are explicitly set for any requests.
743+
*
744+
* @param string $header
745+
* @param string $value
746+
* @return Browser
747+
*/
748+
public function withHeader($header, $value)
749+
{
750+
$browser = $this->withoutHeader($header);
751+
$browser->defaultHeaders[$header] = $value;
752+
753+
return $browser;
754+
}
755+
756+
/**
757+
* Remove any default request headers previously set via
758+
* the [`withHeader()` method](#withheader).
759+
*
760+
* ```php
761+
* $browser = $browser->withoutHeader('User-Agent');
762+
*
763+
* $browser->get($url)->then(…);
764+
* ```
765+
*
766+
* Note that this method only affects the headers which were set with the
767+
* method `withHeader(string $header, string $value): Browser`
768+
*
769+
* @param string $header
770+
* @return Browser
771+
*/
772+
public function withoutHeader($header)
773+
{
774+
$browser = clone $this;
775+
776+
/** @var string|int $key */
777+
foreach (\array_keys($browser->defaultHeaders) as $key) {
778+
if (\strcasecmp($key, $header) === 0) {
779+
unset($browser->defaultHeaders[$key]);
780+
break;
781+
}
782+
}
783+
784+
return $browser;
785+
}
786+
728787
/**
729788
* Changes the [options](#options) to use:
730789
*
@@ -783,6 +842,19 @@ private function requestMayBeStreaming($method, $url, array $headers = array(),
783842
$body = new ReadableBodyStream($body);
784843
}
785844

845+
foreach ($this->defaultHeaders as $key => $value) {
846+
$explicitHeaderExists = false;
847+
foreach (\array_keys($headers) as $headerKey) {
848+
if (\strcasecmp($headerKey, $key) === 0) {
849+
$explicitHeaderExists = true;
850+
break;
851+
}
852+
}
853+
if (!$explicitHeaderExists) {
854+
$headers[$key] = $value;
855+
}
856+
}
857+
786858
return $this->transaction->send(
787859
new Request($method, $url, $headers, $body, $this->protocolVersion)
788860
);

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

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)