@@ -91,24 +91,13 @@ interface Transport
91
91
public function close (): bool ;
92
92
}
93
93
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
112
101
{
113
102
/**
114
103
* @var resource|null|false
@@ -156,8 +145,13 @@ class StreamTransport extends AbstractTransport
156
145
*/
157
146
public function setKeepAlive (bool $ keepAlive ): void
158
147
{
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 ();
161
155
}
162
156
}
163
157
@@ -210,6 +204,136 @@ class StreamTransport extends AbstractTransport
210
204
}
211
205
}
212
206
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
+
213
337
/**
214
338
* Handles communication with a FastCGI application
215
339
*
@@ -312,7 +436,8 @@ class Client
312
436
$ this ->_port = $ port ;
313
437
314
438
$ this ->transport = match ($ transport ) {
315
- 'stream ' => new StreamTransport ()
439
+ 'stream ' => new StreamTransport (),
440
+ 'socket ' => new SocketTransport (),
316
441
};
317
442
}
318
443
0 commit comments