Skip to content

Commit 2084961

Browse files
committed
New Server class acts as a facade for underlying TcpServer
1 parent 52f414e commit 2084961

4 files changed

Lines changed: 225 additions & 8 deletions

File tree

README.md

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ handle multiple concurrent connections without blocking.
3030
* [pause()](#pause)
3131
* [resume()](#resume)
3232
* [close()](#close)
33+
* [Server](#server)
3334
* [TcpServer](#tcpserver)
3435
* [SecureServer](#secureserver)
3536
* [LimitingServer](#limitingserver)
@@ -319,6 +320,101 @@ $server->close();
319320

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

323+
### Server
324+
325+
The `Server` class implements the [`ServerInterface`](#serverinterface) and
326+
is responsible for accepting plaintext TCP/IP connections.
327+
It acts as a facade for the underlying [`TcpServer`](#tcpserver) and follows
328+
its exact semantics.
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 [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+
'backlog' => 200,
390+
'so_reuseport' => true,
391+
'ipv6_v6only' => true
392+
));
393+
```
394+
395+
> Note that available [socket context options](http://php.net/manual/en/context.socket.php),
396+
their defaults and effects of changing these may vary depending on your system
397+
and/or PHP version.
398+
Passing unknown context options has no effect.
399+
400+
Whenever a client connects, it will emit a `connection` event with a connection
401+
instance implementing [`ConnectionInterface`](#connectioninterface):
402+
403+
```php
404+
$server->on('connection', function (ConnectionInterface $connection) {
405+
echo 'Plaintext connection from ' . $connection->getRemoteAddress() . PHP_EOL;
406+
407+
$connection->write('hello there!' . PHP_EOL);
408+
409+
});
410+
```
411+
412+
See also the [`ServerInterface`](#serverinterface) for more details.
413+
414+
> Note that the `Server` class is a concrete implementation for TCP/IP sockets.
415+
If you want to typehint in your higher-level protocol implementation, you SHOULD
416+
use the generic [`ServerInterface`](#serverinterface) instead.
417+
322418
### TcpServer
323419

324420
The `TcpServer` class implements the [`ServerInterface`](#serverinterface) and
@@ -408,10 +504,6 @@ $server->on('connection', function (ConnectionInterface $connection) {
408504

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

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-
415507
### SecureServer
416508

417509
The `SecureServer` class implements the [`ServerInterface`](#serverinterface)

src/Server.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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+
$server = new TcpServer($uri, $loop, $context);
15+
$this->server = $server;
16+
17+
$that = $this;
18+
$server->on('connection', function (ConnectionInterface $conn) use ($that) {
19+
$that->emit('connection', array($conn));
20+
});
21+
$server->on('error', function (\Exception $error) use ($that) {
22+
$that->emit('error', array($error));
23+
});
24+
}
25+
26+
public function getAddress()
27+
{
28+
return $this->server->getAddress();
29+
}
30+
31+
public function pause()
32+
{
33+
$this->server->pause();
34+
}
35+
36+
public function resume()
37+
{
38+
$this->server->resume();
39+
}
40+
41+
public function close()
42+
{
43+
$this->server->close();
44+
}
45+
}

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
*/

tests/ServerTest.php

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
namespace React\Tests\Socket;
4+
5+
use React\EventLoop\Factory;
6+
use React\Socket\Server;
7+
use Clue\React\Block;
8+
9+
class ServerTest extends TestCase
10+
{
11+
public function testCreateServer()
12+
{
13+
$loop = Factory::create();
14+
15+
$server = new Server(0, $loop);
16+
}
17+
18+
/**
19+
* @expectedException InvalidArgumentException
20+
*/
21+
public function testConstructorThrowsForInvalidUri()
22+
{
23+
$loop = $this->getMockBuilder('React\EventLoop\LoopInterface')->getMock();
24+
25+
$server = new Server('invalid URI', $loop);
26+
}
27+
28+
public function testEmitsConnectionForNewConnection()
29+
{
30+
$loop = Factory::create();
31+
32+
$server = new Server(0, $loop);
33+
$server->on('connection', $this->expectCallableOnce());
34+
35+
$client = stream_socket_client($server->getAddress());
36+
37+
Block\sleep(0.1, $loop);
38+
}
39+
40+
public function testDoesNotEmitConnectionForNewConnectionToPausedServer()
41+
{
42+
$loop = Factory::create();
43+
44+
$server = new Server(0, $loop);
45+
$server->pause();
46+
47+
48+
$client = stream_socket_client($server->getAddress());
49+
50+
Block\sleep(0.1, $loop);
51+
}
52+
53+
public function testDoesEmitConnectionForNewConnectionToResumedServer()
54+
{
55+
$loop = Factory::create();
56+
57+
$server = new Server(0, $loop);
58+
$server->pause();
59+
$server->on('connection', $this->expectCallableOnce());
60+
61+
$client = stream_socket_client($server->getAddress());
62+
63+
Block\sleep(0.1, $loop);
64+
65+
$server->resume();
66+
Block\sleep(0.1, $loop);
67+
}
68+
69+
public function testDoesNotAllowConnectionToClosedServer()
70+
{
71+
$loop = Factory::create();
72+
73+
$server = new Server(0, $loop);
74+
$server->on('connection', $this->expectCallableNever());
75+
$address = $server->getAddress();
76+
$server->close();
77+
78+
$client = @stream_socket_client($address);
79+
80+
Block\sleep(0.1, $loop);
81+
82+
$this->assertFalse($client);
83+
}
84+
}

0 commit comments

Comments
 (0)