<?php
namespace React\Tests\Socket;
use Clue\React\Block;
use React\Dns\Resolver\Factory as ResolverFactory;
use React\EventLoop\Factory;
use React\Socket\Connector;
use React\Socket\DnsConnector;
use React\Socket\SecureConnector;
use React\Socket\TcpConnector;
/** @group internet */
class IntegrationTest extends TestCase
{
const TIMEOUT = 5.0;
/** @test */
public function gettingStuffFromGoogleShouldWork()
{
$loop = Factory::create();
$connector = new Connector($loop);
$conn = Block\await($connector->connect('google.com:80'), $loop);
$this->assertContains(':80', $conn->getRemoteAddress());
$this->assertNotEquals('google.com:80', $conn->getRemoteAddress());
$conn->write("GET / HTTP/1.0\r\n\r\n");
$response = $this->buffer($conn, $loop, self::TIMEOUT);
$this->assertRegExp('#^HTTP/1\.0#', $response);
}
/** @test */
public function gettingEncryptedStuffFromGoogleShouldWork()
{
if (!function_exists('stream_socket_enable_crypto')) {
$this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
}
$loop = Factory::create();
$secureConnector = new Connector($loop);
$conn = Block\await($secureConnector->connect('tls://google.com:443'), $loop);
$conn->write("GET / HTTP/1.0\r\n\r\n");
$response = $this->buffer($conn, $loop, self::TIMEOUT);
$this->assertRegExp('#^HTTP/1\.0#', $response);
}
/** @test */
public function gettingEncryptedStuffFromGoogleShouldWorkIfHostIsResolvedFirst()
{
if (!function_exists('stream_socket_enable_crypto')) {
$this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
}
$loop = Factory::create();
$factory = new ResolverFactory();
$dns = $factory->create('8.8.8.8', $loop);
$connector = new DnsConnector(
new SecureConnector(
new TcpConnector($loop),
$loop
),
$dns
);
$conn = Block\await($connector->connect('google.com:443'), $loop);
$conn->write("GET / HTTP/1.0\r\n\r\n");
$response = $this->buffer($conn, $loop, self::TIMEOUT);
$this->assertRegExp('#^HTTP/1\.0#', $response);
}
/** @test */
public function gettingPlaintextStuffFromEncryptedGoogleShouldNotWork()
{
$loop = Factory::create();
$connector = new Connector($loop);
$conn = Block\await($connector->connect('google.com:443'), $loop);
$this->assertContains(':443', $conn->getRemoteAddress());
$this->assertNotEquals('google.com:443', $conn->getRemoteAddress());
$conn->write("GET / HTTP/1.0\r\n\r\n");
$response = $this->buffer($conn, $loop, self::TIMEOUT);
$this->assertNotRegExp('#^HTTP/1\.0#', $response);
}
public function testConnectingFailsIfDnsUsesInvalidResolver()
{
$loop = Factory::create();
$factory = new ResolverFactory();
$dns = $factory->create('demo.invalid', $loop);
$connector = new Connector($loop, array(
'dns' => $dns
));
$this->setExpectedException('RuntimeException');
Block\await($connector->connect('google.com:80'), $loop, self::TIMEOUT);
}
public function testCancellingPendingConnectionWithoutTimeoutShouldNotCreateAnyGarbageReferences()
{
if (class_exists('React\Promise\When')) {
$this->markTestSkipped('Not supported on legacy Promise v1 API');
}
$loop = Factory::create();
$connector = new Connector($loop, array('timeout' => false));
gc_collect_cycles();
$promise = $connector->connect('8.8.8.8:80');
$promise->cancel();
unset($promise);
$this->assertEquals(0, gc_collect_cycles());
}
public function testCancellingPendingConnectionShouldNotCreateAnyGarbageReferences()
{
if (class_exists('React\Promise\When')) {
$this->markTestSkipped('Not supported on legacy Promise v1 API');
}
$loop = Factory::create();
$connector = new Connector($loop);
gc_collect_cycles();
$promise = $connector->connect('8.8.8.8:80');
$promise->cancel();
unset($promise);
$this->assertEquals(0, gc_collect_cycles());
}
public function testWaitingForRejectedConnectionShouldNotCreateAnyGarbageReferences()
{
if (class_exists('React\Promise\When')) {
$this->markTestSkipped('Not supported on legacy Promise v1 API');
}
$loop = Factory::create();
$connector = new Connector($loop, array('timeout' => false));
gc_collect_cycles();
$wait = true;
$promise = $connector->connect('127.0.0.1:1')->then(
null,
function ($e) use (&$wait) {
$wait = false;
throw $e;
}
);
// run loop for short period to ensure we detect connection refused error
Block\sleep(0.01, $loop);
if ($wait) {
Block\sleep(0.2, $loop);
if ($wait) {
$this->fail('Connection attempt did not fail');
}
}
unset($promise);
$this->assertEquals(0, gc_collect_cycles());
}
public function testWaitingForConnectionTimeoutDuringDnsLookupShouldNotCreateAnyGarbageReferences()
{
if (class_exists('React\Promise\When')) {
$this->markTestSkipped('Not supported on legacy Promise v1 API');
}
$loop = Factory::create();
$connector = new Connector($loop, array('timeout' => 0.001));
gc_collect_cycles();
$wait = true;
$promise = $connector->connect('google.com:80')->then(
null,
function ($e) use (&$wait) {
$wait = false;
throw $e;
}
);
// run loop for short period to ensure we detect connection timeout error
Block\sleep(0.01, $loop);
if ($wait) {
Block\sleep(0.2, $loop);
if ($wait) {
$this->fail('Connection attempt did not fail');
}
}
unset($promise);
$this->assertEquals(0, gc_collect_cycles());
}
public function testWaitingForConnectionTimeoutDuringTcpConnectionShouldNotCreateAnyGarbageReferences()
{
if (class_exists('React\Promise\When')) {
$this->markTestSkipped('Not supported on legacy Promise v1 API');
}
$loop = Factory::create();
$connector = new Connector($loop, array('timeout' => 0.000001));
gc_collect_cycles();
$wait = true;
$promise = $connector->connect('8.8.8.8:53')->then(
null,
function ($e) use (&$wait) {
$wait = false;
throw $e;
}
);
// run loop for short period to ensure we detect connection timeout error
Block\sleep(0.01, $loop);
if ($wait) {
Block\sleep(0.2, $loop);
if ($wait) {
$this->fail('Connection attempt did not fail');
}
}
unset($promise);
$this->assertEquals(0, gc_collect_cycles());
}
public function testWaitingForInvalidDnsConnectionShouldNotCreateAnyGarbageReferences()
{
if (class_exists('React\Promise\When')) {
$this->markTestSkipped('Not supported on legacy Promise v1 API');
}
$loop = Factory::create();
$connector = new Connector($loop, array('timeout' => false));
gc_collect_cycles();
$wait = true;
$promise = $connector->connect('example.invalid:80')->then(
null,
function ($e) use (&$wait) {
$wait = false;
throw $e;
}
);
// run loop for short period to ensure we detect DNS error
Block\sleep(0.01, $loop);
if ($wait) {
Block\sleep(0.2, $loop);
if ($wait) {
$this->fail('Connection attempt did not fail');
}
}
unset($promise);
$this->assertEquals(0, gc_collect_cycles());
}
/**
* @requires PHP 7
*/
public function testWaitingForInvalidTlsConnectionShouldNotCreateAnyGarbageReferences()
{
if (class_exists('React\Promise\When')) {
$this->markTestSkipped('Not supported on legacy Promise v1 API');
}
$loop = Factory::create();
$connector = new Connector($loop, array(
'tls' => array(
'verify_peer' => true
)
));
gc_collect_cycles();
$wait = true;
$promise = $connector->connect('tls://self-signed.badssl.com:443')->then(
null,
function ($e) use (&$wait) {
$wait = false;
throw $e;
}
);
// run loop for short period to ensure we detect DNS error
Block\sleep(0.1, $loop);
if ($wait) {
Block\sleep(0.4, $loop);
if ($wait) {
$this->fail('Connection attempt did not fail');
}
}
unset($promise);
$this->assertEquals(0, gc_collect_cycles());
}
public function testWaitingForSuccessfullyClosedConnectionShouldNotCreateAnyGarbageReferences()
{
if (class_exists('React\Promise\When')) {
$this->markTestSkipped('Not supported on legacy Promise v1 API');
}
$loop = Factory::create();
$connector = new Connector($loop, array('timeout' => false));
gc_collect_cycles();
$promise = $connector->connect('google.com:80')->then(
function ($conn) {
$conn->close();
}
);
Block\await($promise, $loop, self::TIMEOUT);
unset($promise);
$this->assertEquals(0, gc_collect_cycles());
}
public function testConnectingFailsIfTimeoutIsTooSmall()
{
$loop = Factory::create();
$connector = new Connector($loop, array(
'timeout' => 0.001
));
$this->setExpectedException('RuntimeException');
Block\await($connector->connect('google.com:80'), $loop, self::TIMEOUT);
}
public function testSelfSignedRejectsIfVerificationIsEnabled()
{
if (!function_exists('stream_socket_enable_crypto')) {
$this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
}
$loop = Factory::create();
$connector = new Connector($loop, array(
'tls' => array(
'verify_peer' => true
)
));
$this->setExpectedException('RuntimeException');
Block\await($connector->connect('tls://self-signed.badssl.com:443'), $loop, self::TIMEOUT);
}
public function testSelfSignedResolvesIfVerificationIsDisabled()
{
if (!function_exists('stream_socket_enable_crypto')) {
$this->markTestSkipped('Not supported on your platform (outdated HHVM?)');
}
$loop = Factory::create();
$connector = new Connector($loop, array(
'tls' => array(
'verify_peer' => false
)
));
$conn = Block\await($connector->connect('tls://self-signed.badssl.com:443'), $loop, self::TIMEOUT);
$conn->close();
// if we reach this, then everything is good
$this->assertNull(null);
}
}