Skip to content

Commit af233a7

Browse files
authored
Merge pull request #97 from clue-labs/facade
Add Server facade for existing server classes
2 parents 94b4e89 + 1cb3bed commit af233a7

File tree

7 files changed

+371
-50
lines changed

7 files changed

+371
-50
lines changed

README.md

Lines changed: 153 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@ handle multiple concurrent connections without blocking.
3030
* [pause()](#pause)
3131
* [resume()](#resume)
3232
* [close()](#close)
33-
* [TcpServer](#tcpserver)
34-
* [SecureServer](#secureserver)
35-
* [LimitingServer](#limitingserver)
36-
* [getConnections()](#getconnections)
33+
* [Server](#server)
34+
* [Advanced server usage](#advanced-server-usage)
35+
* [TcpServer](#tcpserver)
36+
* [SecureServer](#secureserver)
37+
* [LimitingServer](#limitingserver)
38+
* [getConnections()](#getconnections)
3739
* [Client usage](#client-usage)
3840
* [ConnectorInterface](#connectorinterface)
3941
* [connect()](#connect)
@@ -54,8 +56,8 @@ Here is a server that closes the connection if you send it anything:
5456

5557
```php
5658
$loop = React\EventLoop\Factory::create();
59+
$socket = new React\Socket\Server('127.0.0.1:8080', $loop);
5760

58-
$socket = new React\Socket\TcpServer(8080, $loop);
5961
$socket->on('connection', function (ConnectionInterface $conn) {
6062
$conn->write("Hello " . $conn->getRemoteAddress() . "!\n");
6163
$conn->write("Welcome to this amazing server!\n");
@@ -319,7 +321,149 @@ $server->close();
319321

320322
Calling this method more than once on the same instance is a NO-OP.
321323

322-
### TcpServer
324+
### Server
325+
326+
The `Server` class is the main class in this package that implements the
327+
[`ServerInterface`](#serverinterface) and allows you to accept incoming
328+
streaming connections, such as plaintext TCP/IP or secure TLS connection streams.
329+
330+
```php
331+
$server = new Server(8080, $loop);
332+
```
333+
334+
As above, the `$uri` parameter can consist of only a port, in which case the
335+
server will default to listening on the localhost address `127.0.0.1`,
336+
which means it will not be reachable from outside of this system.
337+
338+
In order to use a random port assignment, you can use the port `0`:
339+
340+
```php
341+
$server = new Server(0, $loop);
342+
$address = $server->getAddress();
343+
```
344+
345+
In order to change the host the socket is listening on, you can provide an IP
346+
address through the first parameter provided to the constructor, optionally
347+
preceded by the `tcp://` scheme:
348+
349+
```php
350+
$server = new Server('192.168.0.1:8080', $loop);
351+
```
352+
353+
If you want to listen on an IPv6 address, you MUST enclose the host in square
354+
brackets:
355+
356+
```php
357+
$server = new Server('[::1]:8080', $loop);
358+
```
359+
360+
If the given URI is invalid, does not contain a port, any other scheme or if it
361+
contains a hostname, it will throw an `InvalidArgumentException`:
362+
363+
```php
364+
// throws InvalidArgumentException due to missing port
365+
$server = new Server('127.0.0.1', $loop);
366+
```
367+
368+
If the given URI appears to be valid, but listening on it fails (such as if port
369+
is already in use or port below 1024 may require root access etc.), it will
370+
throw a `RuntimeException`:
371+
372+
```php
373+
$first = new Server(8080, $loop);
374+
375+
// throws RuntimeException because port is already in use
376+
$second = new Server(8080, $loop);
377+
```
378+
379+
> Note that these error conditions may vary depending on your system and/or
380+
configuration.
381+
See the exception message and code for more details about the actual error
382+
condition.
383+
384+
Optionally, you can specify [TCP socket context options](http://php.net/manual/en/context.socket.php)
385+
for the underlying stream socket resource like this:
386+
387+
```php
388+
$server = new Server('[::1]:8080', $loop, array(
389+
'tcp' => array(
390+
'backlog' => 200,
391+
'so_reuseport' => true,
392+
'ipv6_v6only' => true
393+
)
394+
));
395+
```
396+
397+
> Note that available [socket context options](http://php.net/manual/en/context.socket.php),
398+
their defaults and effects of changing these may vary depending on your system
399+
and/or PHP version.
400+
Passing unknown context options has no effect.
401+
For BC reasons, you can also pass the TCP socket context options as a simple
402+
array without wrapping this in another array under the `tcp` key.
403+
404+
You can start a secure TLS (formerly known as SSL) server by simply prepending
405+
the `tls://` URI scheme.
406+
Internally, it will wait for plaintext TCP/IP connections and then performs a
407+
TLS handshake for each connection.
408+
It thus requires valid [TLS context options](http://php.net/manual/en/context.ssl.php),
409+
which in its most basic form may look something like this if you're using a
410+
PEM encoded certificate file:
411+
412+
```php
413+
$server = new Server('tls://127.0.0.1:8080', $loop, array(
414+
'tls' => array(
415+
'local_cert' => 'server.pem'
416+
)
417+
));
418+
```
419+
420+
> Note that the certificate file will not be loaded on instantiation but when an
421+
incoming connection initializes its TLS context.
422+
This implies that any invalid certificate file paths or contents will only cause
423+
an `error` event at a later time.
424+
425+
If your private key is encrypted with a passphrase, you have to specify it
426+
like this:
427+
428+
```php
429+
$server = new Server('tls://127.0.0.1:8000', $loop, array(
430+
'tls' => array(
431+
'local_cert' => 'server.pem',
432+
'passphrase' => 'secret'
433+
)
434+
));
435+
```
436+
437+
> Note that available [TLS context options](http://php.net/manual/en/context.ssl.php),
438+
their defaults and effects of changing these may vary depending on your system
439+
and/or PHP version.
440+
The outer context array allows you to also use `tcp` (and possibly more)
441+
context options at the same time.
442+
Passing unknown context options has no effect.
443+
If you do not use the `tls://` scheme, then passing `tls` context options
444+
has no effect.
445+
446+
Whenever a client connects, it will emit a `connection` event with a connection
447+
instance implementing [`ConnectionInterface`](#connectioninterface):
448+
449+
```php
450+
$server->on('connection', function (ConnectionInterface $connection) {
451+
echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
452+
453+
$connection->write('hello there!' . PHP_EOL);
454+
455+
});
456+
```
457+
458+
See also the [`ServerInterface`](#serverinterface) for more details.
459+
460+
> Note that the `Server` class is a concrete implementation for TCP/IP sockets.
461+
If you want to typehint in your higher-level protocol implementation, you SHOULD
462+
use the generic [`ServerInterface`](#serverinterface) instead.
463+
464+
### Advanced server usage
465+
466+
#### TcpServer
323467

324468
The `TcpServer` class implements the [`ServerInterface`](#serverinterface) and
325469
is responsible for accepting plaintext TCP/IP connections.
@@ -408,11 +552,7 @@ $server->on('connection', function (ConnectionInterface $connection) {
408552

409553
See also the [`ServerInterface`](#serverinterface) for more details.
410554

411-
Note that the `TcpServer` class is a concrete implementation for TCP/IP sockets.
412-
If you want to typehint in your higher-level protocol implementation, you SHOULD
413-
use the generic [`ServerInterface`](#serverinterface) instead.
414-
415-
### SecureServer
555+
#### SecureServer
416556

417557
The `SecureServer` class implements the [`ServerInterface`](#serverinterface)
418558
and is responsible for providing a secure TLS (formerly known as SSL) server.
@@ -492,7 +632,7 @@ If you use a custom `ServerInterface` and its `connection` event does not
492632
meet this requirement, the `SecureServer` will emit an `error` event and
493633
then close the underlying connection.
494634

495-
### LimitingServer
635+
#### LimitingServer
496636

497637
The `LimitingServer` decorator wraps a given `ServerInterface` and is responsible
498638
for limiting and keeping track of open connections to this server instance.
@@ -559,7 +699,7 @@ $server->on('connection', function (ConnectionInterface $connection) {
559699
});
560700
```
561701

562-
#### getConnections()
702+
##### getConnections()
563703

564704
The `getConnections(): ConnectionInterface[]` method can be used to
565705
return an array with all currently active connections.

examples/01-echo.php

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,22 @@
88
//
99
// You can also run a secure TLS echo server like this:
1010
//
11-
// $ php examples/01-echo.php 8000 examples/localhost.pem
11+
// $ php examples/01-echo.php tls://127.0.0.1:8000 examples/localhost.pem
1212
// $ openssl s_client -connect localhost:8000
1313

1414
use React\EventLoop\Factory;
15-
use React\Socket\TcpServer;
15+
use React\Socket\Server;
1616
use React\Socket\ConnectionInterface;
17-
use React\Socket\SecureServer;
1817

1918
require __DIR__ . '/../vendor/autoload.php';
2019

2120
$loop = Factory::create();
2221

23-
$server = new TcpServer(isset($argv[1]) ? $argv[1] : 0, $loop);
24-
25-
// secure TLS mode if certificate is given as second parameter
26-
if (isset($argv[2])) {
27-
$server = new SecureServer($server, $loop, array(
28-
'local_cert' => $argv[2]
29-
));
30-
}
22+
$server = new Server(isset($argv[1]) ? $argv[1] : 0, $loop, array(
23+
'tls' => array(
24+
'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem')
25+
)
26+
));
3127

3228
$server->on('connection', function (ConnectionInterface $conn) {
3329
echo '[connected]' . PHP_EOL;

examples/02-chat-server.php

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,23 @@
88
//
99
// You can also run a secure TLS chat server like this:
1010
//
11-
// $ php examples/02-chat-server.php 8000 examples/localhost.pem
11+
// $ php examples/02-chat-server.php tls://127.0.0.1:8000 examples/localhost.pem
1212
// $ openssl s_client -connect localhost:8000
1313

1414
use React\EventLoop\Factory;
15-
use React\Socket\TcpServer;
15+
use React\Socket\Server;
1616
use React\Socket\ConnectionInterface;
17-
use React\Socket\SecureServer;
1817
use React\Socket\LimitingServer;
1918

2019
require __DIR__ . '/../vendor/autoload.php';
2120

2221
$loop = Factory::create();
2322

24-
$server = new TcpServer(isset($argv[1]) ? $argv[1] : 0, $loop);
25-
26-
// secure TLS mode if certificate is given as second parameter
27-
if (isset($argv[2])) {
28-
$server = new SecureServer($server, $loop, array(
29-
'local_cert' => $argv[2]
30-
));
31-
}
23+
$server = new Server(isset($argv[1]) ? $argv[1] : 0, $loop, array(
24+
'tls' => array(
25+
'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem')
26+
)
27+
));
3228

3329
$server = new LimitingServer($server, null);
3430

examples/03-benchmark.php

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,24 @@
1111
//
1212
// You can also run a secure TLS benchmarking server like this:
1313
//
14-
// $ php examples/03-benchmark.php 8000 examples/localhost.pem
14+
// $ php examples/03-benchmark.php tls://127.0.0.1:8000 examples/localhost.pem
1515
// $ openssl s_client -connect localhost:8000
1616
// $ echo hello world | openssl s_client -connect localhost:8000
1717
// $ dd if=/dev/zero bs=1M count=1000 | openssl s_client -connect localhost:8000
1818

1919
use React\EventLoop\Factory;
20-
use React\Socket\TcpServer;
20+
use React\Socket\Server;
2121
use React\Socket\ConnectionInterface;
22-
use React\Socket\SecureServer;
2322

2423
require __DIR__ . '/../vendor/autoload.php';
2524

2625
$loop = Factory::create();
2726

28-
$server = new TcpServer(isset($argv[1]) ? $argv[1] : 0, $loop);
29-
30-
// secure TLS mode if certificate is given as second parameter
31-
if (isset($argv[2])) {
32-
$server = new SecureServer($server, $loop, array(
33-
'local_cert' => $argv[2]
34-
));
35-
}
27+
$server = new Server(isset($argv[1]) ? $argv[1] : 0, $loop, array(
28+
'tls' => array(
29+
'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem')
30+
)
31+
));
3632

3733
$server->on('connection', function (ConnectionInterface $conn) use ($loop) {
3834
echo '[connected]' . PHP_EOL;

src/Server.php

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<?php
2+
3+
namespace React\Socket;
4+
5+
use Evenement\EventEmitter;
6+
use React\EventLoop\LoopInterface;
7+
8+
final class Server extends EventEmitter implements ServerInterface
9+
{
10+
private $server;
11+
12+
public function __construct($uri, LoopInterface $loop, array $context = array())
13+
{
14+
// sanitize TCP context options if not properly wrapped
15+
if ($context && (!isset($context['tcp']) && !isset($context['tls']))) {
16+
$context = array('tcp' => $context);
17+
}
18+
19+
// apply default options if not explicitly given
20+
$context += array(
21+
'tcp' => array(),
22+
'tls' => array(),
23+
);
24+
25+
$scheme = 'tcp';
26+
$pos = strpos($uri, '://');
27+
if ($pos !== false) {
28+
$scheme = substr($uri, 0, $pos);
29+
}
30+
31+
$server = new TcpServer(str_replace('tls://', '', $uri), $loop, $context['tcp']);
32+
33+
if ($scheme === 'tls') {
34+
$server = new SecureServer($server, $loop, $context['tls']);
35+
}
36+
37+
$this->server = $server;
38+
39+
$that = $this;
40+
$server->on('connection', function (ConnectionInterface $conn) use ($that) {
41+
$that->emit('connection', array($conn));
42+
});
43+
$server->on('error', function (\Exception $error) use ($that) {
44+
$that->emit('error', array($error));
45+
});
46+
}
47+
48+
public function getAddress()
49+
{
50+
return $this->server->getAddress();
51+
}
52+
53+
public function pause()
54+
{
55+
$this->server->pause();
56+
}
57+
58+
public function resume()
59+
{
60+
$this->server->resume();
61+
}
62+
63+
public function close()
64+
{
65+
$this->server->close();
66+
}
67+
}

src/TcpServer.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,6 @@
2828
*
2929
* See also the `ServerInterface` for more details.
3030
*
31-
* Note that the `TcpServer` class is a concrete implementation for TCP/IP sockets.
32-
* If you want to typehint in your higher-level protocol implementation, you SHOULD
33-
* use the generic `ServerInterface` instead.
34-
*
3531
* @see ServerInterface
3632
* @see ConnectionInterface
3733
*/

0 commit comments

Comments
 (0)