| 3 |
liveuser |
1 |
<?php
|
|
|
2 |
|
|
|
3 |
namespace React\Socket;
|
|
|
4 |
|
|
|
5 |
use Evenement\EventEmitter;
|
|
|
6 |
use Exception;
|
|
|
7 |
use OverflowException;
|
|
|
8 |
|
|
|
9 |
/**
|
|
|
10 |
* The `LimitingServer` decorator wraps a given `ServerInterface` and is responsible
|
|
|
11 |
* for limiting and keeping track of open connections to this server instance.
|
|
|
12 |
*
|
|
|
13 |
* Whenever the underlying server emits a `connection` event, it will check its
|
|
|
14 |
* limits and then either
|
|
|
15 |
* - keep track of this connection by adding it to the list of
|
|
|
16 |
* open connections and then forward the `connection` event
|
|
|
17 |
* - or reject (close) the connection when its limits are exceeded and will
|
|
|
18 |
* forward an `error` event instead.
|
|
|
19 |
*
|
|
|
20 |
* Whenever a connection closes, it will remove this connection from the list of
|
|
|
21 |
* open connections.
|
|
|
22 |
*
|
|
|
23 |
* ```php
|
|
|
24 |
* $server = new React\Socket\LimitingServer($server, 100);
|
|
|
25 |
* $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
|
|
|
26 |
* $connection->write('hello there!' . PHP_EOL);
|
|
|
27 |
* …
|
|
|
28 |
* });
|
|
|
29 |
* ```
|
|
|
30 |
*
|
|
|
31 |
* See also the `ServerInterface` for more details.
|
|
|
32 |
*
|
|
|
33 |
* @see ServerInterface
|
|
|
34 |
* @see ConnectionInterface
|
|
|
35 |
*/
|
|
|
36 |
class LimitingServer extends EventEmitter implements ServerInterface
|
|
|
37 |
{
|
|
|
38 |
private $connections = array();
|
|
|
39 |
private $server;
|
|
|
40 |
private $limit;
|
|
|
41 |
|
|
|
42 |
private $pauseOnLimit = false;
|
|
|
43 |
private $autoPaused = false;
|
|
|
44 |
private $manuPaused = false;
|
|
|
45 |
|
|
|
46 |
/**
|
|
|
47 |
* Instantiates a new LimitingServer.
|
|
|
48 |
*
|
|
|
49 |
* You have to pass a maximum number of open connections to ensure
|
|
|
50 |
* the server will automatically reject (close) connections once this limit
|
|
|
51 |
* is exceeded. In this case, it will emit an `error` event to inform about
|
|
|
52 |
* this and no `connection` event will be emitted.
|
|
|
53 |
*
|
|
|
54 |
* ```php
|
|
|
55 |
* $server = new React\Socket\LimitingServer($server, 100);
|
|
|
56 |
* $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
|
|
|
57 |
* $connection->write('hello there!' . PHP_EOL);
|
|
|
58 |
* …
|
|
|
59 |
* });
|
|
|
60 |
* ```
|
|
|
61 |
*
|
|
|
62 |
* You MAY pass a `null` limit in order to put no limit on the number of
|
|
|
63 |
* open connections and keep accepting new connection until you run out of
|
|
|
64 |
* operating system resources (such as open file handles). This may be
|
|
|
65 |
* useful if you do not want to take care of applying a limit but still want
|
|
|
66 |
* to use the `getConnections()` method.
|
|
|
67 |
*
|
|
|
68 |
* You can optionally configure the server to pause accepting new
|
|
|
69 |
* connections once the connection limit is reached. In this case, it will
|
|
|
70 |
* pause the underlying server and no longer process any new connections at
|
|
|
71 |
* all, thus also no longer closing any excessive connections.
|
|
|
72 |
* The underlying operating system is responsible for keeping a backlog of
|
|
|
73 |
* pending connections until its limit is reached, at which point it will
|
|
|
74 |
* start rejecting further connections.
|
|
|
75 |
* Once the server is below the connection limit, it will continue consuming
|
|
|
76 |
* connections from the backlog and will process any outstanding data on
|
|
|
77 |
* each connection.
|
|
|
78 |
* This mode may be useful for some protocols that are designed to wait for
|
|
|
79 |
* a response message (such as HTTP), but may be less useful for other
|
|
|
80 |
* protocols that demand immediate responses (such as a "welcome" message in
|
|
|
81 |
* an interactive chat).
|
|
|
82 |
*
|
|
|
83 |
* ```php
|
|
|
84 |
* $server = new React\Socket\LimitingServer($server, 100, true);
|
|
|
85 |
* $server->on('connection', function (React\Socket\ConnectionInterface $connection) {
|
|
|
86 |
* $connection->write('hello there!' . PHP_EOL);
|
|
|
87 |
* …
|
|
|
88 |
* });
|
|
|
89 |
* ```
|
|
|
90 |
*
|
|
|
91 |
* @param ServerInterface $server
|
|
|
92 |
* @param int|null $connectionLimit
|
|
|
93 |
* @param bool $pauseOnLimit
|
|
|
94 |
*/
|
|
|
95 |
public function __construct(ServerInterface $server, $connectionLimit, $pauseOnLimit = false)
|
|
|
96 |
{
|
|
|
97 |
$this->server = $server;
|
|
|
98 |
$this->limit = $connectionLimit;
|
|
|
99 |
if ($connectionLimit !== null) {
|
|
|
100 |
$this->pauseOnLimit = $pauseOnLimit;
|
|
|
101 |
}
|
|
|
102 |
|
|
|
103 |
$this->server->on('connection', array($this, 'handleConnection'));
|
|
|
104 |
$this->server->on('error', array($this, 'handleError'));
|
|
|
105 |
}
|
|
|
106 |
|
|
|
107 |
/**
|
|
|
108 |
* Returns an array with all currently active connections
|
|
|
109 |
*
|
|
|
110 |
* ```php
|
|
|
111 |
* foreach ($server->getConnection() as $connection) {
|
|
|
112 |
* $connection->write('Hi!');
|
|
|
113 |
* }
|
|
|
114 |
* ```
|
|
|
115 |
*
|
|
|
116 |
* @return ConnectionInterface[]
|
|
|
117 |
*/
|
|
|
118 |
public function getConnections()
|
|
|
119 |
{
|
|
|
120 |
return $this->connections;
|
|
|
121 |
}
|
|
|
122 |
|
|
|
123 |
public function getAddress()
|
|
|
124 |
{
|
|
|
125 |
return $this->server->getAddress();
|
|
|
126 |
}
|
|
|
127 |
|
|
|
128 |
public function pause()
|
|
|
129 |
{
|
|
|
130 |
if (!$this->manuPaused) {
|
|
|
131 |
$this->manuPaused = true;
|
|
|
132 |
|
|
|
133 |
if (!$this->autoPaused) {
|
|
|
134 |
$this->server->pause();
|
|
|
135 |
}
|
|
|
136 |
}
|
|
|
137 |
}
|
|
|
138 |
|
|
|
139 |
public function resume()
|
|
|
140 |
{
|
|
|
141 |
if ($this->manuPaused) {
|
|
|
142 |
$this->manuPaused = false;
|
|
|
143 |
|
|
|
144 |
if (!$this->autoPaused) {
|
|
|
145 |
$this->server->resume();
|
|
|
146 |
}
|
|
|
147 |
}
|
|
|
148 |
}
|
|
|
149 |
|
|
|
150 |
public function close()
|
|
|
151 |
{
|
|
|
152 |
$this->server->close();
|
|
|
153 |
}
|
|
|
154 |
|
|
|
155 |
/** @internal */
|
|
|
156 |
public function handleConnection(ConnectionInterface $connection)
|
|
|
157 |
{
|
|
|
158 |
// close connection if limit exceeded
|
|
|
159 |
if ($this->limit !== null && \count($this->connections) >= $this->limit) {
|
|
|
160 |
$this->handleError(new \OverflowException('Connection closed because server reached connection limit'));
|
|
|
161 |
$connection->close();
|
|
|
162 |
return;
|
|
|
163 |
}
|
|
|
164 |
|
|
|
165 |
$this->connections[] = $connection;
|
|
|
166 |
$that = $this;
|
|
|
167 |
$connection->on('close', function () use ($that, $connection) {
|
|
|
168 |
$that->handleDisconnection($connection);
|
|
|
169 |
});
|
|
|
170 |
|
|
|
171 |
// pause accepting new connections if limit exceeded
|
|
|
172 |
if ($this->pauseOnLimit && !$this->autoPaused && \count($this->connections) >= $this->limit) {
|
|
|
173 |
$this->autoPaused = true;
|
|
|
174 |
|
|
|
175 |
if (!$this->manuPaused) {
|
|
|
176 |
$this->server->pause();
|
|
|
177 |
}
|
|
|
178 |
}
|
|
|
179 |
|
|
|
180 |
$this->emit('connection', array($connection));
|
|
|
181 |
}
|
|
|
182 |
|
|
|
183 |
/** @internal */
|
|
|
184 |
public function handleDisconnection(ConnectionInterface $connection)
|
|
|
185 |
{
|
|
|
186 |
unset($this->connections[\array_search($connection, $this->connections)]);
|
|
|
187 |
|
|
|
188 |
// continue accepting new connection if below limit
|
|
|
189 |
if ($this->autoPaused && \count($this->connections) < $this->limit) {
|
|
|
190 |
$this->autoPaused = false;
|
|
|
191 |
|
|
|
192 |
if (!$this->manuPaused) {
|
|
|
193 |
$this->server->resume();
|
|
|
194 |
}
|
|
|
195 |
}
|
|
|
196 |
}
|
|
|
197 |
|
|
|
198 |
/** @internal */
|
|
|
199 |
public function handleError(\Exception $error)
|
|
|
200 |
{
|
|
|
201 |
$this->emit('error', array($error));
|
|
|
202 |
}
|
|
|
203 |
}
|