Skip to content

Commit 09866db

Browse files
committed
Replace listen() call with URIs passed to constructor
This fixes the temporal dependency design flaw for the most part, because a Server now always represents a listening Socket (which may possibly be in a closed state)
1 parent cfd2217 commit 09866db

13 files changed

Lines changed: 104 additions & 132 deletions

README.md

Lines changed: 18 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ and [`Stream`](https://github.com/reactphp/stream) components.
1515
* [ServerInterface](#serverinterface)
1616
* [connection event](#connection-event)
1717
* [error event](#error-event)
18-
* [listen()](#listen)
1918
* [getPort()](#getport)
2019
* [shutdown()](#shutdown)
2120
* [Server](#server)
@@ -33,7 +32,7 @@ Here is a server that closes the connection if you send it anything:
3332
```php
3433
$loop = React\EventLoop\Factory::create();
3534

36-
$socket = new React\Socket\Server($loop);
35+
$socket = new React\Socket\Server(8080, $loop);
3736
$socket->on('connection', function (ConnectionInterface $conn) {
3837
$conn->write("Hello " . $conn->getRemoteAddress() . "!\n");
3938
$conn->write("Welcome to this amazing server!\n");
@@ -43,7 +42,6 @@ $socket->on('connection', function (ConnectionInterface $conn) {
4342
$conn->close();
4443
});
4544
});
46-
$socket->listen(1337);
4745

4846
$loop->run();
4947
```
@@ -58,7 +56,7 @@ For anything more complex, consider using the
5856
```php
5957
$loop = React\EventLoop\Factory::create();
6058

61-
$client = stream_socket_client('tcp://127.0.0.1:1337');
59+
$client = stream_socket_client('tcp://127.0.0.1:8080');
6260
$conn = new React\Stream\Stream($client, $loop);
6361
$conn->pipe(new React\Stream\Stream(STDOUT, $loop));
6462
$conn->write("Hello World!\n");
@@ -112,28 +110,6 @@ $server->on('error', function (Exception $e) {
112110
Note that this is not a fatal error event, i.e. the server keeps listening for
113111
new connections even after this event.
114112

115-
#### listen()
116-
117-
The `listen(int $port, string $host = '127.0.0.1'): void` method can be used to
118-
start listening on the given address.
119-
120-
This starts accepting new incoming connections on the given address.
121-
See also the [connection event](#connection-event) for more details.
122-
123-
```php
124-
$server->listen(8080);
125-
```
126-
127-
By default, the server will listen on the localhost address and will not be
128-
reachable from the outside.
129-
You can change the host the socket is listening on through a second parameter
130-
provided to the listen method:
131-
132-
```php
133-
$socket->listen(1337, '192.168.0.1');
134-
```
135-
136-
This method MUST NOT be called more than once on the same instance.
137113

138114
#### getPort()
139115

@@ -145,7 +121,6 @@ $port = $server->getPort();
145121
echo 'Server listening on port ' . $port . PHP_EOL;
146122
```
147123

148-
This method MUST NOT be called before calling [`listen()`](#listen).
149124
This method MUST NOT be called after calling [`shutdown()`](#shutdown).
150125

151126
#### shutdown()
@@ -160,14 +135,26 @@ echo 'Shutting down server socket' . PHP_EOL;
160135
$server->shutdown();
161136
```
162137

163-
This method MUST NOT be called before calling [`listen()`](#listen).
164-
This method MUST NOT be called after calling [`shutdown()`](#shutdown).
138+
This method MUST NOT be called more than once on the same instance.
165139

166140
### Server
167141

168142
The `Server` class implements the [`ServerInterface`](#serverinterface) and
169143
is responsible for accepting plaintext TCP/IP connections.
170144

145+
```php
146+
$server = new Server(8080, $loop);
147+
```
148+
149+
By default, the server will listen on the localhost address and will not be
150+
reachable from the outside.
151+
You can change the host the socket is listening on through a first parameter
152+
provided to the constructor:
153+
154+
```php
155+
$server = new Server('192.168.0.1:8080', $loop);
156+
```
157+
171158
Whenever a client connects, it will emit a `connection` event with a connection
172159
instance implementing [`ConnectionInterface`](#connectioninterface):
173160

@@ -198,13 +185,10 @@ which in its most basic form may look something like this if you're using a
198185
PEM encoded certificate file:
199186

200187
```php
201-
$server = new Server($loop);
202-
188+
$server = new Server(8000, $loop);
203189
$server = new SecureServer($server, $loop, array(
204190
'local_cert' => 'server.pem'
205191
));
206-
207-
$server->listen(8000);
208192
```
209193

210194
> Note that the certificate file will not be loaded on instantiation but when an
@@ -216,6 +200,7 @@ If your private key is encrypted with a passphrase, you have to specify it
216200
like this:
217201

218202
```php
203+
$server = new Server(8000, $loop);
219204
$server = new SecureServer($server, $loop, array(
220205
'local_cert' => 'server.pem',
221206
'passphrase' => 'secret'

examples/01-echo.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
$loop = Factory::create();
2222

23-
$server = new Server($loop);
23+
$server = new Server(isset($argv[1]) ? $argv[1] : 0, $loop);
2424

2525
// secure TLS mode if certificate is given as second parameter
2626
if (isset($argv[2])) {
@@ -29,8 +29,6 @@
2929
));
3030
}
3131

32-
$server->listen(isset($argv[1]) ? $argv[1] : 0);
33-
3432
$server->on('connection', function (ConnectionInterface $conn) use ($loop) {
3533
echo '[connected]' . PHP_EOL;
3634
$conn->pipe($conn);

examples/02-chat-server.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
$loop = Factory::create();
2222

23-
$server = new Server($loop);
23+
$server = new Server(isset($argv[1]) ? $argv[1] : 0, $loop);
2424

2525
// secure TLS mode if certificate is given as second parameter
2626
if (isset($argv[2])) {
@@ -29,8 +29,6 @@
2929
));
3030
}
3131

32-
$server->listen(isset($argv[1]) ? $argv[1] : 0, '0.0.0.0');
33-
3432
$clients = array();
3533

3634
$server->on('connection', function (ConnectionInterface $client) use (&$clients) {

examples/03-benchmark.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
$loop = Factory::create();
2727

28-
$server = new Server($loop);
28+
$server = new Server(isset($argv[1]) ? $argv[1] : 0, $loop);
2929

3030
// secure TLS mode if certificate is given as second parameter
3131
if (isset($argv[2])) {
@@ -34,8 +34,6 @@
3434
));
3535
}
3636

37-
$server->listen(isset($argv[1]) ? $argv[1] : 0);
38-
3937
$server->on('connection', function (ConnectionInterface $conn) use ($loop) {
4038
echo '[connected]' . PHP_EOL;
4139

src/SecureServer.php

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@
3939
class SecureServer extends EventEmitter implements ServerInterface
4040
{
4141
private $tcp;
42-
private $context;
43-
private $loop;
4442
private $encryption;
4543

4644
public function __construct(Server $tcp, LoopInterface $loop, array $context)
@@ -50,9 +48,11 @@ public function __construct(Server $tcp, LoopInterface $loop, array $context)
5048
'passphrase' => ''
5149
);
5250

51+
foreach ($context as $name => $value) {
52+
stream_context_set_option($tcp->master, 'ssl', $name, $value);
53+
}
54+
5355
$this->tcp = $tcp;
54-
$this->context = $context;
55-
$this->loop = $loop;
5656
$this->encryption = new StreamEncryption($loop);
5757

5858
$that = $this;
@@ -64,15 +64,6 @@ public function __construct(Server $tcp, LoopInterface $loop, array $context)
6464
});
6565
}
6666

67-
public function listen($port, $host = '127.0.0.1')
68-
{
69-
$this->tcp->listen($port, $host);
70-
71-
foreach ($this->context as $name => $value) {
72-
stream_context_set_option($this->tcp->master, 'ssl', $name, $value);
73-
}
74-
}
75-
7667
public function getPort()
7768
{
7869
return $this->tcp->getPort();

src/Server.php

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,16 @@
44

55
use Evenement\EventEmitter;
66
use React\EventLoop\LoopInterface;
7+
use InvalidArgumentException;
78

89
/**
910
* The `Server` class implements the `ServerInterface` and
1011
* is responsible for accepting plaintext TCP/IP connections.
1112
*
13+
* ```php
14+
* $server = new Server(8080, $loop);
15+
* ```
16+
*
1217
* Whenever a client connects, it will emit a `connection` event with a connection
1318
* instance implementing `ConnectionInterface`:
1419
*
@@ -34,27 +39,69 @@ class Server extends EventEmitter implements ServerInterface
3439
public $master;
3540
private $loop;
3641

37-
public function __construct(LoopInterface $loop)
42+
/**
43+
* Creates a plaintext TCP/IP socket server and starts listening on the given address
44+
*
45+
* This starts accepting new incoming connections on the given address.
46+
* See also the `connection event` documented in the `ServerInterface`
47+
* for more details.
48+
*
49+
* ```php
50+
* $server = new Server(8080, $loop);
51+
* ```
52+
*
53+
* By default, the server will listen on the localhost address and will not be
54+
* reachable from the outside.
55+
* You can change the host the socket is listening on through the first parameter
56+
* provided to the constructor.
57+
*
58+
* ```php
59+
* $server = new Server('192.168.0.1:8080', $loop);
60+
* ```
61+
*
62+
* @param string $uri
63+
* @param LoopInterface $loop
64+
* @throws InvalidArgumentException if the listening address is invalid
65+
* @throws ConnectionException if listening on this address fails (already in use etc.)
66+
*/
67+
public function __construct($uri, LoopInterface $loop)
3868
{
3969
$this->loop = $loop;
40-
}
4170

42-
public function listen($port, $host = '127.0.0.1')
43-
{
44-
if (strpos($host, ':') !== false) {
45-
// enclose IPv6 addresses in square brackets before appending port
46-
$host = '[' . $host . ']';
71+
// a single port has been given => assume localhost
72+
if ((string)(int)$uri === (string)$uri) {
73+
$uri = '127.0.0.1:' . $uri;
74+
}
75+
76+
// assume default scheme if none has been given
77+
if (strpos($uri, '://') === false) {
78+
$uri = 'tcp://' . $uri;
79+
}
80+
81+
// parse_url() does not accept null ports (random port assignment) => manually remove
82+
if (substr($uri, -2) === ':0') {
83+
$parts = parse_url(substr($uri, 0, -2));
84+
if ($parts) {
85+
$parts['port'] = 0;
86+
}
87+
} else {
88+
$parts = parse_url($uri);
89+
}
90+
91+
// ensure URI contains TCP scheme, host and port
92+
if (!$parts || !isset($parts['scheme'], $parts['host'], $parts['port']) || $parts['scheme'] !== 'tcp') {
93+
throw new InvalidArgumentException('Invalid URI "' . $uri . '" given');
4794
}
4895

4996
$this->master = @stream_socket_server(
50-
"tcp://$host:$port",
97+
$uri,
5198
$errno,
5299
$errstr,
53100
STREAM_SERVER_BIND | STREAM_SERVER_LISTEN,
54101
stream_context_create()
55102
);
56103
if (false === $this->master) {
57-
$message = "Could not bind to tcp://$host:$port: $errstr";
104+
$message = "Could not bind to $uri: $errstr";
58105
throw new ConnectionException($message, $errno);
59106
}
60107
stream_set_blocking($this->master, 0);

src/ServerInterface.php

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -48,30 +48,9 @@
4848
*/
4949
interface ServerInterface extends EventEmitterInterface
5050
{
51-
/**
52-
* Starts listening on the given address
53-
*
54-
* This starts accepting new incoming connections on the given address.
55-
* See also the `connection event` above for more details.
56-
*
57-
* By default, the server will listen on the localhost address and will not be
58-
* reachable from the outside.
59-
* You can change the host the socket is listening on through a second parameter
60-
* provided to the listen method.
61-
*
62-
* This method MUST NOT be called more than once on the same instance.
63-
*
64-
* @param int $port port to listen on
65-
* @param string $host optional host to listen on, defaults to localhost IP
66-
* @return void
67-
* @throws Exception if listening on this address fails (invalid or already in use etc.)
68-
*/
69-
public function listen($port, $host = '127.0.0.1');
70-
7151
/**
7252
* Returns the port this server is currently listening on
7353
*
74-
* This method MUST NOT be called before calling listen().
7554
* This method MUST NOT be called after calling shutdown().
7655
*
7756
* @return int the port number
@@ -83,8 +62,7 @@ public function getPort();
8362
*
8463
* This will stop listening for new incoming connections on this socket.
8564
*
86-
* This method MUST NOT be called before calling listen().
87-
* This method MUST NOT be called after calling shutdown().
65+
* This method MUST NOT be called more than once on the same instance.
8866
*
8967
* @return void
9068
*/

tests/ConnectionTest.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ class ConnectionTest extends TestCase
1414
public function testGetRemoteAddress()
1515
{
1616
$loop = new StreamSelectLoop();
17-
$server = new Server($loop);
18-
$server->listen(0);
17+
$server = new Server(0, $loop);
1918

2019
$class = new \ReflectionClass('React\\Socket\\Server');
2120
$master = $class->getProperty('master');

0 commit comments

Comments
 (0)