Skip to content

Commit ebaf6f1

Browse files
committed
Add Connection: close default header to allow toggling keep-alive
1 parent 28943f4 commit ebaf6f1

File tree

6 files changed

+84
-64
lines changed

6 files changed

+84
-64
lines changed

src/Browser.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class Browser
2323
private $baseUrl;
2424
private $protocolVersion = '1.1';
2525
private $defaultHeaders = array(
26+
'Connection' => 'close',
2627
'User-Agent' => 'ReactPHP/1'
2728
);
2829

src/Io/Sender.php

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,6 @@ public function send(RequestInterface $request)
9898
$size = 0;
9999
}
100100

101-
// automatically add `Connection: close` request header for HTTP/1.1 requests to avoid connection reuse
102-
if ($request->getProtocolVersion() === '1.1') {
103-
$request = $request->withHeader('Connection', 'close');
104-
} else {
105-
$request = $request->withoutHeader('Connection');
106-
}
107-
108101
// automatically add `Authorization: Basic …` request header if URL includes `user:pass@host`
109102
if ($request->getUri()->getUserInfo() !== '' && !$request->hasHeader('Authorization')) {
110103
$request = $request->withHeader('Authorization', 'Basic ' . \base64_encode($request->getUri()->getUserInfo()));

src/Io/Transaction.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,7 @@ private function makeRedirectRequest(RequestInterface $request, UriInterface $lo
302302
->withMethod($request->getMethod() === 'HEAD' ? 'HEAD' : 'GET')
303303
->withoutHeader('Content-Type')
304304
->withoutHeader('Content-Length')
305-
->withBody(new EmptyBodyStream());
305+
->withBody(new BufferedBody(''));
306306
}
307307

308308
return $request;

tests/BrowserTest.php

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,8 @@ public function testWithMultipleHeadersShouldBeMergedCorrectlyWithMultipleDefaul
556556
'user-Agent' => array('ABC'),
557557
'another-header' => array('value'),
558558
'custom-header' => array('data'),
559+
560+
'Connection' => array('close')
559561
);
560562

561563
$that->assertEquals($expectedHeaders, $request->getHeaders());
@@ -584,6 +586,32 @@ public function testWithoutHeaderShouldRemoveExistingHeader()
584586
$this->browser->get('http://example.com/');
585587
}
586588

589+
public function testWithoutHeaderConnectionShouldRemoveDefaultConnectionHeader()
590+
{
591+
$this->browser = $this->browser->withoutHeader('Connection');
592+
593+
$that = $this;
594+
$this->sender->expects($this->once())->method('send')->with($this->callback(function (RequestInterface $request) use ($that) {
595+
$that->assertEquals(array(), $request->getHeader('Connection'));
596+
return true;
597+
}))->willReturn(new Promise(function () { }));
598+
599+
$this->browser->get('http://example.com/');
600+
}
601+
602+
public function testWithHeaderConnectionShouldOverwriteDefaultConnectionHeader()
603+
{
604+
$this->browser = $this->browser->withHeader('Connection', 'keep-alive');
605+
606+
$that = $this;
607+
$this->sender->expects($this->once())->method('send')->with($this->callback(function (RequestInterface $request) use ($that) {
608+
$that->assertEquals(array('keep-alive'), $request->getHeader('Connection'));
609+
return true;
610+
}))->willReturn(new Promise(function () { }));
611+
612+
$this->browser->get('http://example.com/');
613+
}
614+
587615
public function testBrowserShouldSendDefaultUserAgentHeader()
588616
{
589617
$that = $this;

tests/FunctionalBrowserTest.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,60 @@ public function testReceiveStreamAndExplicitlyCloseConnectionEvenWhenServerKeeps
553553
$socket->close();
554554
}
555555

556+
public function testRequestWillCreateNewConnectionForSecondRequestByDefaultEvenWhenServerKeepsConnectionOpen()
557+
{
558+
$twice = $this->expectCallableOnce();
559+
$socket = new SocketServer('127.0.0.1:0');
560+
$socket->on('connection', function (\React\Socket\ConnectionInterface $connection) use ($socket, $twice) {
561+
$connection->on('data', function () use ($connection) {
562+
$connection->write("HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello");
563+
});
564+
565+
$socket->on('connection', $twice);
566+
$socket->on('connection', function () use ($socket) {
567+
$socket->close();
568+
});
569+
});
570+
571+
$this->base = str_replace('tcp:', 'http:', $socket->getAddress()) . '/';
572+
573+
$response = \React\Async\await($this->browser->get($this->base . 'get'));
574+
assert($response instanceof ResponseInterface);
575+
$this->assertEquals('hello', (string)$response->getBody());
576+
577+
$response = \React\Async\await($this->browser->get($this->base . 'get'));
578+
assert($response instanceof ResponseInterface);
579+
$this->assertEquals('hello', (string)$response->getBody());
580+
}
581+
582+
public function testRequestWithoutConnectionHeaderWillReuseExistingConnectionForSecondRequest()
583+
{
584+
$this->socket->on('connection', $this->expectCallableOnce());
585+
586+
// remove default `Connection: close` request header to enable keep-alive
587+
$this->browser = $this->browser->withoutHeader('Connection');
588+
589+
$response = \React\Async\await($this->browser->get($this->base . 'get'));
590+
assert($response instanceof ResponseInterface);
591+
$this->assertEquals('hello', (string)$response->getBody());
592+
593+
$response = \React\Async\await($this->browser->get($this->base . 'get'));
594+
assert($response instanceof ResponseInterface);
595+
$this->assertEquals('hello', (string)$response->getBody());
596+
}
597+
598+
public function testRequestWithoutConnectionHeaderWillReuseExistingConnectionForRedirectedRequest()
599+
{
600+
$this->socket->on('connection', $this->expectCallableOnce());
601+
602+
// remove default `Connection: close` request header to enable keep-alive
603+
$this->browser = $this->browser->withoutHeader('Connection');
604+
605+
$response = \React\Async\await($this->browser->get($this->base . 'redirect-to?url=get'));
606+
assert($response instanceof ResponseInterface);
607+
$this->assertEquals('hello', (string)$response->getBody());
608+
}
609+
556610
public function testPostStreamChunked()
557611
{
558612
$stream = new ThroughStream();

tests/Io/SenderTest.php

Lines changed: 0 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -290,62 +290,6 @@ public function testSendCustomMethodWithExplicitContentLengthZeroWillBePassedAsI
290290
$sender->send($request);
291291
}
292292

293-
/** @test */
294-
public function getHttp10RequestShouldSendAGetRequestWithoutConnectionHeaderByDefault()
295-
{
296-
$client = $this->getMockBuilder('React\Http\Client\Client')->disableOriginalConstructor()->getMock();
297-
$client->expects($this->once())->method('request')->with($this->callback(function (RequestInterface $request) {
298-
return !$request->hasHeader('Connection');
299-
}))->willReturn($this->getMockBuilder('React\Http\Io\ClientRequestStream')->disableOriginalConstructor()->getMock());
300-
301-
$sender = new Sender($client);
302-
303-
$request = new Request('GET', 'http://www.example.com', array(), '', '1.0');
304-
$sender->send($request);
305-
}
306-
307-
/** @test */
308-
public function getHttp10RequestShouldSendAGetRequestWithoutConnectionHeaderEvenWhenConnectionKeepAliveHeaderIsSpecified()
309-
{
310-
$client = $this->getMockBuilder('React\Http\Client\Client')->disableOriginalConstructor()->getMock();
311-
$client->expects($this->once())->method('request')->with($this->callback(function (RequestInterface $request) {
312-
return !$request->hasHeader('Connection');
313-
}))->willReturn($this->getMockBuilder('React\Http\Io\ClientRequestStream')->disableOriginalConstructor()->getMock());
314-
315-
$sender = new Sender($client);
316-
317-
$request = new Request('GET', 'http://www.example.com', array('Connection' => 'keep-alive'), '', '1.0');
318-
$sender->send($request);
319-
}
320-
321-
/** @test */
322-
public function getHttp11RequestShouldSendAGetRequestWithConnectionCloseHeaderByDefault()
323-
{
324-
$client = $this->getMockBuilder('React\Http\Client\Client')->disableOriginalConstructor()->getMock();
325-
$client->expects($this->once())->method('request')->with($this->callback(function (RequestInterface $request) {
326-
return $request->getHeaderLine('Connection') === 'close';
327-
}))->willReturn($this->getMockBuilder('React\Http\Io\ClientRequestStream')->disableOriginalConstructor()->getMock());
328-
329-
$sender = new Sender($client);
330-
331-
$request = new Request('GET', 'http://www.example.com', array(), '', '1.1');
332-
$sender->send($request);
333-
}
334-
335-
/** @test */
336-
public function getHttp11RequestShouldSendAGetRequestWithConnectionCloseHeaderEvenWhenConnectionKeepAliveHeaderIsSpecified()
337-
{
338-
$client = $this->getMockBuilder('React\Http\Client\Client')->disableOriginalConstructor()->getMock();
339-
$client->expects($this->once())->method('request')->with($this->callback(function (RequestInterface $request) {
340-
return $request->getHeaderLine('Connection') === 'close';
341-
}))->willReturn($this->getMockBuilder('React\Http\Io\ClientRequestStream')->disableOriginalConstructor()->getMock());
342-
343-
$sender = new Sender($client);
344-
345-
$request = new Request('GET', 'http://www.example.com', array('Connection' => 'keep-alive'), '', '1.1');
346-
$sender->send($request);
347-
}
348-
349293
/** @test */
350294
public function getRequestWithUserAndPassShouldSendAGetRequestWithBasicAuthorizationHeader()
351295
{

0 commit comments

Comments
 (0)