Skip to content

Commit

Permalink
Merge pull request #263 from clue-labs/socketserver
Browse files Browse the repository at this point in the history
Add new `SocketServer` and deprecate `Server` to avoid class name collisions
  • Loading branch information
WyriHaximus authored Jul 27, 2021
2 parents 92fb72d + 24f4abc commit 22d3b31
Show file tree
Hide file tree
Showing 9 changed files with 467 additions and 93 deletions.
100 changes: 52 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ handle multiple concurrent connections without blocking.
* [pause()](#pause)
* [resume()](#resume)
* [close()](#close)
* [Server](#server)
* [SocketServer](#socketserver)
* [Advanced server usage](#advanced-server-usage)
* [TcpServer](#tcpserver)
* [SecureServer](#secureserver)
Expand Down Expand Up @@ -58,7 +58,7 @@ handle multiple concurrent connections without blocking.
Here is a server that closes the connection if you send it anything:

```php
$socket = new React\Socket\Server('127.0.0.1:8080');
$socket = new React\Socket\SocketServer('127.0.0.1:8080');

$socket->on('connection', function (React\Socket\ConnectionInterface $connection) {
$connection->write("Hello " . $connection->getRemoteAddress() . "!\n");
Expand Down Expand Up @@ -214,7 +214,7 @@ The `connection` event will be emitted whenever a new connection has been
established, i.e. a new client connects to this server socket:

```php
$server->on('connection', function (React\Socket\ConnectionInterface $connection) {
$socket->on('connection', function (React\Socket\ConnectionInterface $connection) {
echo 'new connection' . PHP_EOL;
});
```
Expand All @@ -228,7 +228,7 @@ The `error` event will be emitted whenever there's an error accepting a new
connection from a client.

```php
$server->on('error', function (Exception $e) {
$socket->on('error', function (Exception $e) {
echo 'error: ' . $e->getMessage() . PHP_EOL;
});
```
Expand All @@ -243,7 +243,7 @@ The `getAddress(): ?string` method can be used to
return the full address (URI) this server is currently listening on.

```php
$address = $server->getAddress();
$address = $socket->getAddress();
echo 'Server listening on ' . $address . PHP_EOL;
```

Expand All @@ -260,7 +260,7 @@ If this is a TCP/IP based server and you only want the local port, you may
use something like this:

```php
$address = $server->getAddress();
$address = $socket->getAddress();
$port = parse_url($address, PHP_URL_PORT);
echo 'Server listening on port ' . $port . PHP_EOL;
```
Expand All @@ -284,9 +284,9 @@ Once the server is paused, no futher `connection` events SHOULD
be emitted.

```php
$server->pause();
$socket->pause();

$server->on('connection', assertShouldNeverCalled());
$socket->on('connection', assertShouldNeverCalled());
```

This method is advisory-only, though generally not recommended, the
Expand All @@ -309,10 +309,10 @@ resume accepting new incoming connections.
Re-attach the socket resource to the EventLoop after a previous `pause()`.

```php
$server->pause();
$socket->pause();

Loop::addTimer(1.0, function () use ($server) {
$server->resume();
Loop::addTimer(1.0, function () use ($socket) {
$socket->resume();
});
```

Expand All @@ -329,90 +329,86 @@ This will stop listening for new incoming connections on this socket.

```php
echo 'Shutting down server socket' . PHP_EOL;
$server->close();
$socket->close();
```

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

### Server
### SocketServer

The `Server` class is the main class in this package that implements the
<a id="server"></a> <!-- legacy id -->

The `SocketServer` class is the main class in this package that implements the
[`ServerInterface`](#serverinterface) and allows you to accept incoming
streaming connections, such as plaintext TCP/IP or secure TLS connection streams.
Connections can also be accepted on Unix domain sockets.

```php
$server = new React\Socket\Server(8080);
```

As above, the `$uri` parameter can consist of only a port, in which case the
server will default to listening on the localhost address `127.0.0.1`,
which means it will not be reachable from outside of this system.

In order to use a random port assignment, you can use the port `0`:
In order to accept plaintext TCP/IP connections, you can simply pass a host
and port combination like this:

```php
$server = new React\Socket\Server(0);
$address = $server->getAddress();
$socket = new React\Socket\SocketServer('127.0.0.1:8080');
```

Listening on the localhost address `127.0.0.1` means it will not be reachable from
outside of this system.
In order to change the host the socket is listening on, you can provide an IP
address through the first parameter provided to the constructor, optionally
preceded by the `tcp://` scheme:
address of an interface or use the special `0.0.0.0` address to listen on all
interfaces:

```php
$server = new React\Socket\Server('192.168.0.1:8080');
$socket = new React\Socket\SocketServer('0.0.0.0:8080');
```

If you want to listen on an IPv6 address, you MUST enclose the host in square
brackets:

```php
$server = new React\Socket\Server('[::1]:8080');
$socket = new React\Socket\SocketServer('[::1]:8080');
```

In order to use a random port assignment, you can use the port `0`:

```php
$socket = new React\Socket\SocketServer('127.0.0.1:0');
$address = $socket->getAddress();
```

To listen on a Unix domain socket (UDS) path, you MUST prefix the URI with the
`unix://` scheme:

```php
$server = new React\Socket\Server('unix:///tmp/server.sock');
$socket = new React\Socket\SocketServer('unix:///tmp/server.sock');
```

If the given URI is invalid, does not contain a port, any other scheme or if it
contains a hostname, it will throw an `InvalidArgumentException`:

```php
// throws InvalidArgumentException due to missing port
$server = new React\Socket\Server('127.0.0.1');
$socket = new React\Socket\SocketServer('127.0.0.1');
```

If the given URI appears to be valid, but listening on it fails (such as if port
is already in use or port below 1024 may require root access etc.), it will
throw a `RuntimeException`:

```php
$first = new React\Socket\Server(8080);
$first = new React\Socket\SocketServer('127.0.0.1:8080');

// throws RuntimeException because port is already in use
$second = new React\Socket\Server(8080);
$second = new React\Socket\SocketServer('127.0.0.1:8080');
```

> Note that these error conditions may vary depending on your system and/or
configuration.
See the exception message and code for more details about the actual error
condition.

This class takes an optional `LoopInterface|null $loop` parameter that can be used to
pass the event loop instance to use for this object. You can use a `null` value
here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
This value SHOULD NOT be given unless you're sure you want to explicitly use a
given event loop instance.

Optionally, you can specify [TCP socket context options](https://www.php.net/manual/en/context.socket.php)
for the underlying stream socket resource like this:

```php
$server = new React\Socket\Server('[::1]:8080', null, array(
$socket = new React\Socket\SocketServer('[::1]:8080', array(
'tcp' => array(
'backlog' => 200,
'so_reuseport' => true,
Expand All @@ -426,8 +422,6 @@ $server = new React\Socket\Server('[::1]:8080', null, array(
and/or PHP version.
Passing unknown context options has no effect.
The `backlog` context option defaults to `511` unless given explicitly.
For BC reasons, you can also pass the TCP socket context options as a simple
array without wrapping this in another array under the `tcp` key.

You can start a secure TLS (formerly known as SSL) server by simply prepending
the `tls://` URI scheme.
Expand All @@ -438,7 +432,7 @@ which in its most basic form may look something like this if you're using a
PEM encoded certificate file:

```php
$server = new React\Socket\Server('tls://127.0.0.1:8080', null, array(
$socket = new React\Socket\SocketServer('tls://127.0.0.1:8080', array(
'tls' => array(
'local_cert' => 'server.pem'
)
Expand All @@ -454,7 +448,7 @@ If your private key is encrypted with a passphrase, you have to specify it
like this:

```php
$server = new React\Socket\Server('tls://127.0.0.1:8000', null, array(
$socket = new React\Socket\SocketServer('tls://127.0.0.1:8000', array(
'tls' => array(
'local_cert' => 'server.pem',
'passphrase' => 'secret'
Expand All @@ -467,7 +461,7 @@ SSLv2/SSLv3. As of PHP 5.6+ you can also explicitly choose the TLS version you
want to negotiate with the remote side:

```php
$server = new React\Socket\Server('tls://127.0.0.1:8000', null, array(
$socket = new React\Socket\SocketServer('tls://127.0.0.1:8000', array(
'tls' => array(
'local_cert' => 'server.pem',
'crypto_method' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER
Expand All @@ -488,7 +482,7 @@ Whenever a client connects, it will emit a `connection` event with a connection
instance implementing [`ConnectionInterface`](#connectioninterface):

```php
$server->on('connection', function (React\Socket\ConnectionInterface $connection) {
$socket->on('connection', function (React\Socket\ConnectionInterface $connection) {
echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;

$connection->write('hello there!' . PHP_EOL);
Expand All @@ -498,10 +492,20 @@ $server->on('connection', function (React\Socket\ConnectionInterface $connection

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

> Note that the `Server` class is a concrete implementation for TCP/IP sockets.
This class takes an optional `LoopInterface|null $loop` parameter that can be used to
pass the event loop instance to use for this object. You can use a `null` value
here in order to use the [default loop](https://github.com/reactphp/event-loop#loop).
This value SHOULD NOT be given unless you're sure you want to explicitly use a
given event loop instance.

> Note that the `SocketServer` class is a concrete implementation for TCP/IP sockets.
If you want to typehint in your higher-level protocol implementation, you SHOULD
use the generic [`ServerInterface`](#serverinterface) instead.

> Changelog v1.9.0: This class has been added with an improved constructor signature
as a replacement for the previous `Server` class in order to avoid any ambiguities.
The previous name has been deprecated and should not be used anymore.

### Advanced server usage

#### TcpServer
Expand Down
13 changes: 5 additions & 8 deletions examples/01-echo-server.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Just start this server and connect to it. Everything you send to it will be
// sent back to you.
//
// $ php examples/01-echo-server.php 8000
// $ php examples/01-echo-server.php 127.0.0.1:8000
// $ telnet localhost 8000
//
// You can also run a secure TLS echo server like this:
Expand All @@ -16,22 +16,19 @@
// $ php examples/01-echo-server.php unix:///tmp/server.sock
// $ nc -U /tmp/server.sock

use React\Socket\Server;
use React\Socket\ConnectionInterface;

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

$server = new Server(isset($argv[1]) ? $argv[1] : 0, null, array(
$socket = new React\Socket\SocketServer(isset($argv[1]) ? $argv[1] : '127.0.0.1:0', array(
'tls' => array(
'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem')
)
));

$server->on('connection', function (ConnectionInterface $connection) {
$socket->on('connection', function (React\Socket\ConnectionInterface $connection) {
echo '[' . $connection->getRemoteAddress() . ' connected]' . PHP_EOL;
$connection->pipe($connection);
});

$server->on('error', 'printf');
$socket->on('error', 'printf');

echo 'Listening on ' . $server->getAddress() . PHP_EOL;
echo 'Listening on ' . $socket->getAddress() . PHP_EOL;
20 changes: 8 additions & 12 deletions examples/02-chat-server.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// Just start this server and connect with any number of clients to it.
// Everything a client sends will be broadcasted to all connected clients.
//
// $ php examples/02-chat-server.php 8000
// $ php examples/02-chat-server.php 127.0.0.1:8000
// $ telnet localhost 8000
//
// You can also run a secure TLS chat server like this:
Expand All @@ -16,23 +16,19 @@
// $ php examples/02-chat-server.php unix:///tmp/server.sock
// $ nc -U /tmp/server.sock

use React\Socket\Server;
use React\Socket\ConnectionInterface;
use React\Socket\LimitingServer;

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

$server = new Server(isset($argv[1]) ? $argv[1] : 0, null, array(
$socket = new React\Socket\SocketServer(isset($argv[1]) ? $argv[1] : '127.0.0.1:0', array(
'tls' => array(
'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem')
)
));

$server = new LimitingServer($server, null);
$socket = new React\Socket\LimitingServer($socket, null);

$server->on('connection', function (ConnectionInterface $client) use ($server) {
$socket->on('connection', function (React\Socket\ConnectionInterface $client) use ($socket) {
// whenever a new message comes in
$client->on('data', function ($data) use ($client, $server) {
$client->on('data', function ($data) use ($client, $socket) {
// remove any non-word characters (just for the demo)
$data = trim(preg_replace('/[^\w\d \.\,\-\!\?]/u', '', $data));

Expand All @@ -43,12 +39,12 @@

// prefix with client IP and broadcast to all connected clients
$data = trim(parse_url($client->getRemoteAddress(), PHP_URL_HOST), '[]') . ': ' . $data . PHP_EOL;
foreach ($server->getConnections() as $connection) {
foreach ($socket->getConnections() as $connection) {
$connection->write($data);
}
});
});

$server->on('error', 'printf');
$socket->on('error', 'printf');

echo 'Listening on ' . $server->getAddress() . PHP_EOL;
echo 'Listening on ' . $socket->getAddress() . PHP_EOL;
13 changes: 5 additions & 8 deletions examples/03-http-server.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//
// Just start this server and send a request to it:
//
// $ php examples/03-http-server.php 8000
// $ php examples/03-http-server.php 127.0.0.1:8000
// $ curl -v http://localhost:8000/
// $ ab -n1000 -c10 http://localhost:8000/
// $ docker run -it --rm --net=host jordi/ab ab -n1000 -c10 http://localhost:8000/
Expand All @@ -29,24 +29,21 @@
// $ php examples/03-http-server.php unix:///tmp/server.sock
// $ nc -U /tmp/server.sock

use React\Socket\Server;
use React\Socket\ConnectionInterface;

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

$server = new Server(isset($argv[1]) ? $argv[1] : 0, null, array(
$socket = new React\Socket\SocketServer(isset($argv[1]) ? $argv[1] : '127.0.0.1:0', array(
'tls' => array(
'local_cert' => isset($argv[2]) ? $argv[2] : (__DIR__ . '/localhost.pem')
)
));

$server->on('connection', function (ConnectionInterface $connection) {
$socket->on('connection', function (React\Socket\ConnectionInterface $connection) {
$connection->once('data', function () use ($connection) {
$body = "<html><h1>Hello world!</h1></html>\r\n";
$connection->end("HTTP/1.1 200 OK\r\nContent-Length: " . strlen($body) . "\r\nConnection: close\r\n\r\n" . $body);
});
});

$server->on('error', 'printf');
$socket->on('error', 'printf');

echo 'Listening on ' . strtr($server->getAddress(), array('tcp:' => 'http:', 'tls:' => 'https:')) . PHP_EOL;
echo 'Listening on ' . strtr($socket->getAddress(), array('tcp:' => 'http:', 'tls:' => 'https:')) . PHP_EOL;
Loading

0 comments on commit 22d3b31

Please sign in to comment.