Skip to content

Commit c15f458

Browse files
authored
Merge pull request #87 from clue-labs/merge
Merge SocketClient component into this component
2 parents 8bcba56 + 1529ce8 commit c15f458

27 files changed

+2441
-116
lines changed

README.md

Lines changed: 609 additions & 71 deletions
Large diffs are not rendered by default.

composer.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
{
22
"name": "react/socket",
3-
"description": "Async, streaming plaintext TCP/IP and secure TLS socket server for React PHP",
4-
"keywords": ["socket"],
3+
"description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP",
4+
"keywords": ["async", "socket", "stream", "connection", "ReactPHP"],
55
"license": "MIT",
66
"require": {
77
"php": ">=5.3.0",
88
"evenement/evenement": "~2.0|~1.0",
9+
"react/dns": "0.4.*|0.3.*",
910
"react/event-loop": "0.4.*|0.3.*",
1011
"react/stream": "^0.6 || ^0.5 || ^0.4.5",
11-
"react/promise": "^2.0 || ^1.1"
12+
"react/promise": "^2.1 || ^1.2",
13+
"react/promise-timer": "~1.0"
1214
},
1315
"require-dev": {
14-
"react/socket-client": "^0.6",
1516
"clue/block-react": "^1.1",
16-
"phpunit/phpunit": "~4.8"
17+
"phpunit/phpunit": "~4.8",
18+
"react/stream": "^0.6"
1719
},
1820
"autoload": {
1921
"psr-4": {

examples/11-http.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
use React\EventLoop\Factory;
4+
use React\Socket\Connector;
5+
use React\Socket\ConnectionInterface;
6+
7+
$target = isset($argv[1]) ? $argv[1] : 'www.google.com:80';
8+
9+
require __DIR__ . '/../vendor/autoload.php';
10+
11+
$loop = Factory::create();
12+
$connector = new Connector($loop);
13+
14+
$connector->connect($target)->then(function (ConnectionInterface $connection) use ($target) {
15+
$connection->on('data', function ($data) {
16+
echo $data;
17+
});
18+
$connection->on('close', function () {
19+
echo '[CLOSED]' . PHP_EOL;
20+
});
21+
22+
$connection->write("GET / HTTP/1.0\r\nHost: $target\r\n\r\n");
23+
}, 'printf');
24+
25+
$loop->run();

examples/12-https.php

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
use React\EventLoop\Factory;
4+
use React\Socket\Connector;
5+
use React\Socket\ConnectionInterface;
6+
7+
$target = isset($argv[1]) ? $argv[1] : 'www.google.com:443';
8+
9+
require __DIR__ . '/../vendor/autoload.php';
10+
11+
$loop = Factory::create();
12+
$connector = new Connector($loop);
13+
14+
$connector->connect('tls://' . $target)->then(function (ConnectionInterface $connection) use ($target) {
15+
$connection->on('data', function ($data) {
16+
echo $data;
17+
});
18+
$connection->on('close', function () {
19+
echo '[CLOSED]' . PHP_EOL;
20+
});
21+
22+
$connection->write("GET / HTTP/1.0\r\nHost: $target\r\n\r\n");
23+
}, 'printf');
24+
25+
$loop->run();

examples/13-netcat.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
use React\EventLoop\Factory;
4+
use React\Socket\Connector;
5+
use React\Socket\ConnectionInterface;
6+
use React\Stream\ReadableResourceStream;
7+
use React\Stream\WritableResourceStream;
8+
9+
require __DIR__ . '/../vendor/autoload.php';
10+
11+
if (!isset($argv[1])) {
12+
fwrite(STDERR, 'Usage error: required argument <host:port>' . PHP_EOL);
13+
exit(1);
14+
}
15+
16+
$loop = Factory::create();
17+
$connector = new Connector($loop);
18+
19+
$stdin = new ReadableResourceStream(STDIN, $loop);
20+
$stdin->pause();
21+
$stdout = new WritableResourceStream(STDOUT, $loop);
22+
$stderr = new WritableResourceStream(STDERR, $loop);
23+
24+
$stderr->write('Connecting' . PHP_EOL);
25+
26+
$connector->connect($argv[1])->then(function (ConnectionInterface $connection) use ($stdin, $stdout, $stderr) {
27+
// pipe everything from STDIN into connection
28+
$stdin->resume();
29+
$stdin->pipe($connection);
30+
31+
// pipe everything from connection to STDOUT
32+
$connection->pipe($stdout);
33+
34+
// report errors to STDERR
35+
$connection->on('error', function ($error) use ($stderr) {
36+
$stderr->write('Stream ERROR: ' . $error . PHP_EOL);
37+
});
38+
39+
// report closing and stop reading from input
40+
$connection->on('close', function () use ($stderr, $stdin) {
41+
$stderr->write('[CLOSED]' . PHP_EOL);
42+
$stdin->close();
43+
});
44+
45+
$stderr->write('Connected' . PHP_EOL);
46+
}, function ($error) use ($stderr) {
47+
$stderr->write('Connection ERROR: ' . $error . PHP_EOL);
48+
});
49+
50+
$loop->run();

examples/14-web.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<?php
2+
3+
use React\EventLoop\Factory;
4+
use React\Socket\ConnectionInterface;
5+
use React\Socket\Connector;
6+
use React\Stream\WritableResourceStream;
7+
8+
require __DIR__ . '/../vendor/autoload.php';
9+
10+
$uri = isset($argv[1]) ? $argv[1] : 'www.google.com';
11+
12+
if (strpos($uri, '://') === false) {
13+
$uri = 'http://' . $uri;
14+
}
15+
$parts = parse_url($uri);
16+
17+
if (!$parts || !isset($parts['scheme'], $parts['host'])) {
18+
fwrite(STDERR, 'Usage error: required argument <host:port>' . PHP_EOL);
19+
exit(1);
20+
}
21+
22+
$loop = Factory::create();
23+
$connector = new Connector($loop);
24+
25+
if (!isset($parts['port'])) {
26+
$parts['port'] = $parts['scheme'] === 'https' ? 443 : 80;
27+
}
28+
29+
$host = $parts['host'];
30+
if (($parts['scheme'] === 'http' && $parts['port'] !== 80) || ($parts['scheme'] === 'https' && $parts['port'] !== 443)) {
31+
$host .= ':' . $parts['port'];
32+
}
33+
$target = ($parts['scheme'] === 'https' ? 'tls' : 'tcp') . '://' . $parts['host'] . ':' . $parts['port'];
34+
$resource = isset($parts['path']) ? $parts['path'] : '/';
35+
if (isset($parts['query'])) {
36+
$resource .= '?' . $parts['query'];
37+
}
38+
39+
$stdout = new WritableResourceStream(STDOUT, $loop);
40+
41+
$connector->connect($target)->then(function (ConnectionInterface $connection) use ($resource, $host, $stdout) {
42+
$connection->pipe($stdout);
43+
44+
$connection->write("GET $resource HTTP/1.0\r\nHost: $host\r\n\r\n");
45+
}, 'printf');
46+
47+
$loop->run();

src/ConnectionInterface.php

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,22 @@
55
use React\Stream\DuplexStreamInterface;
66

77
/**
8-
* Any incoming connection is represented by this interface.
8+
* Any incoming and outgoing connection is represented by this interface,
9+
* such as a normal TCP/IP connection.
910
*
10-
* An incoming connection is a duplex stream (both readable and writable) that
11-
* implements React's DuplexStreamInterface.
11+
* An incoming or outgoing connection is a duplex stream (both readable and
12+
* writable) that implements React's
13+
* [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
1214
* It contains additional properties for the local and remote address (client IP)
13-
* where this connection has been established from.
15+
* where this connection has been established to/from.
1416
*
15-
* Note that this interface is only to be used to represent the server-side end
16-
* of an incoming connection.
17-
* It MUST NOT be used to represent an outgoing connection in a client-side
18-
* context.
19-
* If you want to establish an outgoing connection,
20-
* use React's SocketClient component instead.
17+
* Most commonly, instances implementing this `ConnectionInterface` are emitted
18+
* by all classes implementing the [`ServerInterface`](#serverinterface) and
19+
* used by all classes implementing the [`ConnectorInterface`](#connectorinterface).
2120
*
2221
* Because the `ConnectionInterface` implements the underlying
23-
* `DuplexStreamInterface` you can use any of its events and methods as usual:
22+
* [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface)
23+
* you can use any of its events and methods as usual:
2424
*
2525
* ```php
2626
* $connection->on('data', function ($chunk) {
@@ -49,15 +49,17 @@
4949
* [`DuplexStreamInterface`](https://github.com/reactphp/stream#duplexstreaminterface).
5050
*
5151
* @see DuplexStreamInterface
52+
* @see ServerInterface
53+
* @see ConnectorInterface
5254
*/
5355
interface ConnectionInterface extends DuplexStreamInterface
5456
{
5557
/**
56-
* Returns the remote address (client IP and port) where this connection has been established from
58+
* Returns the remote address (client IP and port) where this connection has been established with
5759
*
5860
* ```php
5961
* $address = $connection->getRemoteAddress();
60-
* echo 'Connection from ' . $address . PHP_EOL;
62+
* echo 'Connection with ' . $address . PHP_EOL;
6163
* ```
6264
*
6365
* If the remote address can not be determined or is unknown at this time (such as
@@ -70,19 +72,19 @@ interface ConnectionInterface extends DuplexStreamInterface
7072
* ```php
7173
* $address = $connection->getRemoteAddress();
7274
* $ip = trim(parse_url('tcp://' . $address, PHP_URL_HOST), '[]');
73-
* echo 'Connection from ' . $ip . PHP_EOL;
75+
* echo 'Connection with ' . $ip . PHP_EOL;
7476
* ```
7577
*
7678
* @return ?string remote address (client IP and port) or null if unknown
7779
*/
7880
public function getRemoteAddress();
7981

8082
/**
81-
* Returns the full local address (client IP and port) where this connection has been established to
83+
* Returns the full local address (client IP and port) where this connection has been established with
8284
*
8385
* ```php
8486
* $address = $connection->getLocalAddress();
85-
* echo 'Connection to ' . $address . PHP_EOL;
87+
* echo 'Connection with ' . $address . PHP_EOL;
8688
* ```
8789
*
8890
* If the local address can not be determined or is unknown at this time (such as
@@ -97,6 +99,10 @@ public function getRemoteAddress();
9799
* the address `0.0.0.0`), you can use this method to find out which interface
98100
* actually accepted this connection (such as a public or local interface).
99101
*
102+
* If your system has multiple interfaces (e.g. a WAN and a LAN interface),
103+
* you can use this method to find out which interface was actually
104+
* used for this connection.
105+
*
100106
* @return ?string local address (client IP and port) or null if unknown
101107
* @see self::getRemoteAddress()
102108
*/

src/Connector.php

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
<?php
2+
3+
namespace React\Socket;
4+
5+
use React\EventLoop\LoopInterface;
6+
use React\Dns\Resolver\Resolver;
7+
use React\Dns\Resolver\Factory;
8+
use React\Promise;
9+
use RuntimeException;
10+
11+
/**
12+
* The `Connector` class is the main class in this package that implements the
13+
* `ConnectorInterface` and allows you to create streaming connections.
14+
*
15+
* You can use this connector to create any kind of streaming connections, such
16+
* as plaintext TCP/IP, secure TLS or local Unix connection streams.
17+
*
18+
* Under the hood, the `Connector` is implemented as a *higher-level facade*
19+
* or the lower-level connectors implemented in this package. This means it
20+
* also shares all of their features and implementation details.
21+
* If you want to typehint in your higher-level protocol implementation, you SHOULD
22+
* use the generic [`ConnectorInterface`](#connectorinterface) instead.
23+
*
24+
* @see ConnectorInterface for the base interface
25+
*/
26+
final class Connector implements ConnectorInterface
27+
{
28+
private $connectors = array();
29+
30+
public function __construct(LoopInterface $loop, array $options = array())
31+
{
32+
// apply default options if not explicitly given
33+
$options += array(
34+
'tcp' => true,
35+
'tls' => true,
36+
'unix' => true,
37+
38+
'dns' => true,
39+
'timeout' => true,
40+
);
41+
42+
if ($options['timeout'] === true) {
43+
$options['timeout'] = (float)ini_get("default_socket_timeout");
44+
}
45+
46+
if ($options['tcp'] instanceof ConnectorInterface) {
47+
$tcp = $options['tcp'];
48+
} else {
49+
$tcp = new TcpConnector(
50+
$loop,
51+
is_array($options['tcp']) ? $options['tcp'] : array()
52+
);
53+
}
54+
55+
if ($options['dns'] !== false) {
56+
if ($options['dns'] instanceof Resolver) {
57+
$resolver = $options['dns'];
58+
} else {
59+
$factory = new Factory();
60+
$resolver = $factory->create(
61+
$options['dns'] === true ? '8.8.8.8' : $options['dns'],
62+
$loop
63+
);
64+
}
65+
66+
$tcp = new DnsConnector($tcp, $resolver);
67+
}
68+
69+
if ($options['tcp'] !== false) {
70+
$options['tcp'] = $tcp;
71+
72+
if ($options['timeout'] !== false) {
73+
$options['tcp'] = new TimeoutConnector(
74+
$options['tcp'],
75+
$options['timeout'],
76+
$loop
77+
);
78+
}
79+
80+
$this->connectors['tcp'] = $options['tcp'];
81+
}
82+
83+
if ($options['tls'] !== false) {
84+
if (!$options['tls'] instanceof ConnectorInterface) {
85+
$options['tls'] = new SecureConnector(
86+
$tcp,
87+
$loop,
88+
is_array($options['tls']) ? $options['tls'] : array()
89+
);
90+
}
91+
92+
if ($options['timeout'] !== false) {
93+
$options['tls'] = new TimeoutConnector(
94+
$options['tls'],
95+
$options['timeout'],
96+
$loop
97+
);
98+
}
99+
100+
$this->connectors['tls'] = $options['tls'];
101+
}
102+
103+
if ($options['unix'] !== false) {
104+
if (!$options['unix'] instanceof ConnectorInterface) {
105+
$options['unix'] = new UnixConnector($loop);
106+
}
107+
$this->connectors['unix'] = $options['unix'];
108+
}
109+
}
110+
111+
public function connect($uri)
112+
{
113+
$scheme = 'tcp';
114+
if (strpos($uri, '://') !== false) {
115+
$scheme = (string)substr($uri, 0, strpos($uri, '://'));
116+
}
117+
118+
if (!isset($this->connectors[$scheme])) {
119+
return Promise\reject(new RuntimeException(
120+
'No connector available for URI scheme "' . $scheme . '"'
121+
));
122+
}
123+
124+
return $this->connectors[$scheme]->connect($uri);
125+
}
126+
}
127+

0 commit comments

Comments
 (0)