Skip to content

Commit 53e769c

Browse files
committed
Add socket fcgi transport layer to FPM tester
1 parent 2b09cda commit 53e769c

File tree

1 file changed

+146
-21
lines changed

1 file changed

+146
-21
lines changed

sapi/fpm/tests/fcgi.inc

Lines changed: 146 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -91,24 +91,13 @@ interface Transport
9191
public function close(): bool;
9292
}
9393

94-
abstract class AbstractTransport implements Transport
95-
{
96-
/**
97-
* @inheritDoc
98-
*/
99-
protected int $dataTimeoutMs;
100-
101-
/**
102-
* @inheritDoc
103-
*/
104-
public function setDataTimeout(int $timeoutMs): bool
105-
{
106-
$this->dataTimeoutMs = $timeoutMs;
107-
return true;
108-
}
109-
}
110-
111-
class StreamTransport extends AbstractTransport
94+
/**
95+
* Stream transport.
96+
*
97+
* Iis based on PHP streams and should be more reliable as it automatically handles timeouts and
98+
* other features.
99+
*/
100+
class StreamTransport implements Transport
112101
{
113102
/**
114103
* @var resource|null|false
@@ -156,8 +145,13 @@ class StreamTransport extends AbstractTransport
156145
*/
157146
public function setKeepAlive(bool $keepAlive): void
158147
{
159-
if (!$keepAlive && $this->stream) {
160-
fclose($this->stream);
148+
if ($keepAlive) {
149+
$socket = socket_import_stream($this->stream);
150+
if ($socket) {
151+
socket_set_option($socket, SOL_SOCKET, SO_KEEPALIVE, 1);
152+
}
153+
} else {
154+
$this->close();
161155
}
162156
}
163157

@@ -210,6 +204,136 @@ class StreamTransport extends AbstractTransport
210204
}
211205
}
212206

207+
/**
208+
* Socket transport.
209+
*
210+
* This transport is more low level than stream and supports some extra socket options like
211+
* SO_KEEPALIVE. However, it is currently less robust and missing some stream features like
212+
* connection timeout. It should be used only for specific use cases.
213+
*/
214+
class SocketTransport implements Transport
215+
{
216+
/**
217+
* @var \Socket
218+
*/
219+
private ?\Socket $socket = null;
220+
221+
/**
222+
* @inheritDoc
223+
*/
224+
protected int $dataTimeoutMs;
225+
226+
/**
227+
* @inheritDoc
228+
*/
229+
public function connect(string $host, int $port, ?int $connectionTimeout = 5000): void
230+
{
231+
if ($this->socket) {
232+
return;
233+
}
234+
$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
235+
if (!$this->socket) {
236+
throw new TransportException('Unable to create socket: ' . socket_strerror(socket_last_error()));
237+
}
238+
239+
$ip = filter_var($host, FILTER_VALIDATE_IP) ? $host : gethostbyname($host);
240+
241+
if (!socket_connect($this->socket, $ip, $port)) {
242+
$error = socket_strerror(socket_last_error($this->socket));
243+
throw new TransportException('Unable to connect to FastCGI application: ' . $error);
244+
}
245+
}
246+
247+
/**
248+
* @inheritDoc
249+
*/
250+
public function setDataTimeout(int $timeoutMs): bool
251+
{
252+
$this->dataTimeoutMs = $timeoutMs;
253+
return true;
254+
}
255+
256+
/**
257+
* @inheritDoc
258+
*/
259+
public function setKeepAlive(bool $keepAlive): void
260+
{
261+
if (!$this->socket) {
262+
return;
263+
}
264+
if ($keepAlive) {
265+
socket_set_option($this->socket, SOL_SOCKET, SO_KEEPALIVE, 1);
266+
} else {
267+
$this->close();
268+
}
269+
}
270+
271+
private function select(array $read, array $write = [], array $except = []): bool
272+
{
273+
return socket_select(
274+
$read,
275+
$write,
276+
$except,
277+
floor($this->dataTimeoutMs / 1000),
278+
($this->dataTimeoutMs % 1000) * 1000
279+
);
280+
}
281+
282+
/**
283+
* @inheritDoc
284+
*/
285+
public function read(int $numBytes): string
286+
{
287+
if ($this->select([$this->socket]) === false) {
288+
throw new TimedOutException('Reading timeout');
289+
}
290+
$result = socket_read($this->socket, $numBytes);
291+
if ($result === false) {
292+
throw new TransportException('Reading from the stream failed');
293+
}
294+
return $result;
295+
}
296+
297+
/**
298+
* @inheritDoc
299+
*/
300+
public function write(string $bytes): int
301+
{
302+
if ($this->select([], [$this->socket]) === false) {
303+
throw new TimedOutException('Writing timeout');
304+
}
305+
$result = socket_write($this->socket, $bytes);
306+
if ($result === false) {
307+
throw new TransportException('Writing to the stream failed');
308+
}
309+
return $result;
310+
}
311+
312+
public function getMetaData(): array
313+
{
314+
return [];
315+
}
316+
317+
/**
318+
* @inheritDoc
319+
*/
320+
public function flush(): bool
321+
{
322+
return true;
323+
}
324+
325+
public function close(): bool
326+
{
327+
if ($this->socket) {
328+
socket_close($this->socket);
329+
$this->socket = null;
330+
return true;
331+
}
332+
333+
return false;
334+
}
335+
}
336+
213337
/**
214338
* Handles communication with a FastCGI application
215339
*
@@ -312,7 +436,8 @@ class Client
312436
$this->_port = $port;
313437

314438
$this->transport = match ($transport) {
315-
'stream' => new StreamTransport()
439+
'stream' => new StreamTransport(),
440+
'socket' => new SocketTransport(),
316441
};
317442
}
318443

0 commit comments

Comments
 (0)