Skip to content

Commit 32e0b33

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 32e0b33

File tree

6 files changed

+157
-14
lines changed

6 files changed

+157
-14
lines changed

README.md

Lines changed: 16 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,20 @@ 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+
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.
2392+
2393+
#### withoutHeader()
2394+
2395+
The `withoutHeader(string $header): Browser` method can be used to
2396+
remove any default request headers previously set via the [`withHeader()` method](#withheader).
2397+
2398+
Note that this method only affects the headers which were set with the method `withHeader(string $header, string $value): Browser`
2399+
23842400
### React\Http\Message
23852401

23862402
#### Response

src/Browser.php

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

731+
/**
732+
* The `withHeader(string $header, string $value): Browser` method can be used to
733+
* add a request header for all following requests.
734+
*
735+
* 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.
736+
*
737+
* @param string $header
738+
* @param string $value
739+
* @return Browser
740+
*/
741+
public function withHeader($header, $value)
742+
{
743+
$browser = $this->withoutHeader($header);
744+
$browser->defaultHeaders[$header] = $value;
745+
746+
return $browser;
747+
}
748+
749+
/**
750+
*
751+
* The `withoutHeader(string $header): Browser` method can be used to
752+
* remove any default request headers previously set via the [`withHeader()` method](#withheader).
753+
*
754+
* Note that this method only affects the headers which were set with the method `withHeader(string $header, string $value): Browser`
755+
*
756+
* @param string $header
757+
* @return Browser
758+
*/
759+
public function withoutHeader($header)
760+
{
761+
$browser = clone $this;
762+
763+
/** @var string|int $key */
764+
foreach (\array_keys($browser->defaultHeaders) as $key) {
765+
if (\strcasecmp($key, $header) === 0) {
766+
unset($browser->defaultHeaders[$key]);
767+
break;
768+
}
769+
}
770+
771+
return $browser;
772+
}
773+
728774
/**
729775
* Changes the [options](#options) to use:
730776
*
@@ -783,6 +829,18 @@ private function requestMayBeStreaming($method, $url, array $headers = array(),
783829
$body = new ReadableBodyStream($body);
784830
}
785831

832+
foreach ($this->defaultHeaders as $key => $value) {
833+
if ($headers === array()) {
834+
$headers = $this->defaultHeaders;
835+
break;
836+
}
837+
foreach (\array_keys($headers) as $headerKey) {
838+
if (\strcasecmp($headerKey, $key) !== 0) {
839+
$headers[$key] = $value;
840+
}
841+
}
842+
}
843+
786844
return $this->transaction->send(
787845
new Request($method, $url, $headers, $body, $this->protocolVersion)
788846
);

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: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,4 +503,82 @@ 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 testWithoutHeaderShouldRemoveExistingHeader()
548+
{
549+
$this->browser = $this->browser->withHeader('User-Agent', 'ACMC');
550+
$this->browser = $this->browser->withoutHeader('UsEr-AgEnT'); //should remove case-insensitive header
551+
552+
$that = $this;
553+
$this->sender->expects($this->once())->method('send')->with($this->callback(function (RequestInterface $request) use ($that) {
554+
$that->assertEquals(array(), $request->getHeader('UsEr-AgEnT'));
555+
return true;
556+
}))->willReturn(new Promise(function () { }));
557+
558+
$this->browser->get('http://example.com/');
559+
}
560+
561+
public function testBrowserShouldHaveDefaultHeaderReactPHP()
562+
{
563+
$that = $this;
564+
$this->sender->expects($this->once())->method('send')->with($this->callback(function (RequestInterface $request) use ($that) {
565+
$that->assertEquals(array(0 => 'ReactPHP/1'), $request->getHeader('UsEr-AgEnT'));
566+
return true;
567+
}))->willReturn(new Promise(function () { }));
568+
569+
$this->browser->get('http://example.com/');
570+
}
571+
572+
public function testWithoutHeaderShouldRemoveDefaultHeader()
573+
{
574+
$this->browser = $this->browser->withoutHeader('UsEr-AgEnT');
575+
576+
$that = $this;
577+
$this->sender->expects($this->once())->method('send')->with($this->callback(function (RequestInterface $request) use ($that) {
578+
$that->assertEquals(array(), $request->getHeader('User-Agent'));
579+
return true;
580+
}))->willReturn(new Promise(function () { }));
581+
582+
$this->browser->get('http://example.com/');
583+
}
506584
}

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)