| 1 |
liveuser |
1 |
<?php
|
|
|
2 |
|
|
|
3 |
namespace React\Socket;
|
|
|
4 |
|
|
|
5 |
use Evenement\EventEmitter;
|
|
|
6 |
use React\EventLoop\LoopInterface;
|
|
|
7 |
use React\Stream\DuplexResourceStream;
|
|
|
8 |
use React\Stream\Util;
|
|
|
9 |
use React\Stream\WritableResourceStream;
|
|
|
10 |
use React\Stream\WritableStreamInterface;
|
|
|
11 |
|
|
|
12 |
/**
|
|
|
13 |
* The actual connection implementation for ConnectionInterface
|
|
|
14 |
*
|
|
|
15 |
* This class should only be used internally, see ConnectionInterface instead.
|
|
|
16 |
*
|
|
|
17 |
* @see ConnectionInterface
|
|
|
18 |
* @internal
|
|
|
19 |
*/
|
|
|
20 |
class Connection extends EventEmitter implements ConnectionInterface
|
|
|
21 |
{
|
|
|
22 |
/**
|
|
|
23 |
* Internal flag whether this is a Unix domain socket (UDS) connection
|
|
|
24 |
*
|
|
|
25 |
* @internal
|
|
|
26 |
*/
|
|
|
27 |
public $unix = false;
|
|
|
28 |
|
|
|
29 |
/**
|
|
|
30 |
* Internal flag whether encryption has been enabled on this connection
|
|
|
31 |
*
|
|
|
32 |
* Mostly used by internal StreamEncryption so that connection returns
|
|
|
33 |
* `tls://` scheme for encrypted connections instead of `tcp://`.
|
|
|
34 |
*
|
|
|
35 |
* @internal
|
|
|
36 |
*/
|
|
|
37 |
public $encryptionEnabled = false;
|
|
|
38 |
|
|
|
39 |
/** @internal */
|
|
|
40 |
public $stream;
|
|
|
41 |
|
|
|
42 |
private $input;
|
|
|
43 |
|
|
|
44 |
public function __construct($resource, LoopInterface $loop)
|
|
|
45 |
{
|
|
|
46 |
// PHP < 7.3.3 (and PHP < 7.2.15) suffers from a bug where feof() might
|
|
|
47 |
// block with 100% CPU usage on fragmented TLS records.
|
|
|
48 |
// We try to work around this by always consuming the complete receive
|
|
|
49 |
// buffer at once to avoid stale data in TLS buffers. This is known to
|
|
|
50 |
// work around high CPU usage for well-behaving peers, but this may
|
|
|
51 |
// cause very large data chunks for high throughput scenarios. The buggy
|
|
|
52 |
// behavior can still be triggered due to network I/O buffers or
|
|
|
53 |
// malicious peers on affected versions, upgrading is highly recommended.
|
|
|
54 |
// @link https://bugs.php.net/bug.php?id=77390
|
|
|
55 |
$clearCompleteBuffer = \PHP_VERSION_ID < 70215 || (\PHP_VERSION_ID >= 70300 && \PHP_VERSION_ID < 70303);
|
|
|
56 |
|
|
|
57 |
// PHP < 7.1.4 (and PHP < 7.0.18) suffers from a bug when writing big
|
|
|
58 |
// chunks of data over TLS streams at once.
|
|
|
59 |
// We try to work around this by limiting the write chunk size to 8192
|
|
|
60 |
// bytes for older PHP versions only.
|
|
|
61 |
// This is only a work-around and has a noticable performance penalty on
|
|
|
62 |
// affected versions. Please update your PHP version.
|
|
|
63 |
// This applies to all streams because TLS may be enabled later on.
|
|
|
64 |
// See https://github.com/reactphp/socket/issues/105
|
|
|
65 |
$limitWriteChunks = (\PHP_VERSION_ID < 70018 || (\PHP_VERSION_ID >= 70100 && \PHP_VERSION_ID < 70104));
|
|
|
66 |
|
|
|
67 |
$this->input = new DuplexResourceStream(
|
|
|
68 |
$resource,
|
|
|
69 |
$loop,
|
|
|
70 |
$clearCompleteBuffer ? -1 : null,
|
|
|
71 |
new WritableResourceStream($resource, $loop, null, $limitWriteChunks ? 8192 : null)
|
|
|
72 |
);
|
|
|
73 |
|
|
|
74 |
$this->stream = $resource;
|
|
|
75 |
|
|
|
76 |
Util::forwardEvents($this->input, $this, array('data', 'end', 'error', 'close', 'pipe', 'drain'));
|
|
|
77 |
|
|
|
78 |
$this->input->on('close', array($this, 'close'));
|
|
|
79 |
}
|
|
|
80 |
|
|
|
81 |
public function isReadable()
|
|
|
82 |
{
|
|
|
83 |
return $this->input->isReadable();
|
|
|
84 |
}
|
|
|
85 |
|
|
|
86 |
public function isWritable()
|
|
|
87 |
{
|
|
|
88 |
return $this->input->isWritable();
|
|
|
89 |
}
|
|
|
90 |
|
|
|
91 |
public function pause()
|
|
|
92 |
{
|
|
|
93 |
$this->input->pause();
|
|
|
94 |
}
|
|
|
95 |
|
|
|
96 |
public function resume()
|
|
|
97 |
{
|
|
|
98 |
$this->input->resume();
|
|
|
99 |
}
|
|
|
100 |
|
|
|
101 |
public function pipe(WritableStreamInterface $dest, array $options = array())
|
|
|
102 |
{
|
|
|
103 |
return $this->input->pipe($dest, $options);
|
|
|
104 |
}
|
|
|
105 |
|
|
|
106 |
public function write($data)
|
|
|
107 |
{
|
|
|
108 |
return $this->input->write($data);
|
|
|
109 |
}
|
|
|
110 |
|
|
|
111 |
public function end($data = null)
|
|
|
112 |
{
|
|
|
113 |
$this->input->end($data);
|
|
|
114 |
}
|
|
|
115 |
|
|
|
116 |
public function close()
|
|
|
117 |
{
|
|
|
118 |
$this->input->close();
|
|
|
119 |
$this->handleClose();
|
|
|
120 |
$this->removeAllListeners();
|
|
|
121 |
}
|
|
|
122 |
|
|
|
123 |
public function handleClose()
|
|
|
124 |
{
|
|
|
125 |
if (!\is_resource($this->stream)) {
|
|
|
126 |
return;
|
|
|
127 |
}
|
|
|
128 |
|
|
|
129 |
// Try to cleanly shut down socket and ignore any errors in case other
|
|
|
130 |
// side already closed. Shutting down may return to blocking mode on
|
|
|
131 |
// some legacy versions, so reset to non-blocking just in case before
|
|
|
132 |
// continuing to close the socket resource.
|
|
|
133 |
// Underlying Stream implementation will take care of closing file
|
|
|
134 |
// handle, so we otherwise keep this open here.
|
|
|
135 |
@\stream_socket_shutdown($this->stream, \STREAM_SHUT_RDWR);
|
|
|
136 |
\stream_set_blocking($this->stream, false);
|
|
|
137 |
}
|
|
|
138 |
|
|
|
139 |
public function getRemoteAddress()
|
|
|
140 |
{
|
|
|
141 |
if (!\is_resource($this->stream)) {
|
|
|
142 |
return null;
|
|
|
143 |
}
|
|
|
144 |
|
|
|
145 |
return $this->parseAddress(\stream_socket_get_name($this->stream, true));
|
|
|
146 |
}
|
|
|
147 |
|
|
|
148 |
public function getLocalAddress()
|
|
|
149 |
{
|
|
|
150 |
if (!\is_resource($this->stream)) {
|
|
|
151 |
return null;
|
|
|
152 |
}
|
|
|
153 |
|
|
|
154 |
return $this->parseAddress(\stream_socket_get_name($this->stream, false));
|
|
|
155 |
}
|
|
|
156 |
|
|
|
157 |
private function parseAddress($address)
|
|
|
158 |
{
|
|
|
159 |
if ($address === false) {
|
|
|
160 |
return null;
|
|
|
161 |
}
|
|
|
162 |
|
|
|
163 |
if ($this->unix) {
|
|
|
164 |
// remove trailing colon from address for HHVM < 3.19: https://3v4l.org/5C1lo
|
|
|
165 |
// note that technically ":" is a valid address, so keep this in place otherwise
|
|
|
166 |
if (\substr($address, -1) === ':' && \defined('HHVM_VERSION_ID') && \HHVM_VERSION_ID < 31900) {
|
|
|
167 |
$address = (string)\substr($address, 0, -1); // @codeCoverageIgnore
|
|
|
168 |
}
|
|
|
169 |
|
|
|
170 |
// work around unknown addresses should return null value: https://3v4l.org/5C1lo and https://bugs.php.net/bug.php?id=74556
|
|
|
171 |
// PHP uses "\0" string and HHVM uses empty string (colon removed above)
|
|
|
172 |
if ($address === '' || $address[0] === "\x00" ) {
|
|
|
173 |
return null; // @codeCoverageIgnore
|
|
|
174 |
}
|
|
|
175 |
|
|
|
176 |
return 'unix://' . $address;
|
|
|
177 |
}
|
|
|
178 |
|
|
|
179 |
// check if this is an IPv6 address which includes multiple colons but no square brackets
|
|
|
180 |
$pos = \strrpos($address, ':');
|
|
|
181 |
if ($pos !== false && \strpos($address, ':') < $pos && \substr($address, 0, 1) !== '[') {
|
|
|
182 |
$address = '[' . \substr($address, 0, $pos) . ']:' . \substr($address, $pos + 1); // @codeCoverageIgnore
|
|
|
183 |
}
|
|
|
184 |
|
|
|
185 |
return ($this->encryptionEnabled ? 'tls' : 'tcp') . '://' . $address;
|
|
|
186 |
}
|
|
|
187 |
}
|