1<?php 2namespace Ratchet\Server; 3use Ratchet\MessageComponentInterface; 4use React\EventLoop\LoopInterface; 5use React\Socket\ServerInterface; 6use React\EventLoop\Factory as LoopFactory; 7use React\Socket\Server as Reactor; 8use React\Socket\SecureServer as SecureReactor; 9 10/** 11 * Creates an open-ended socket to listen on a port for incoming connections. 12 * Events are delegated through this to attached applications 13 */ 14class IoServer { 15 /** 16 * @var \React\EventLoop\LoopInterface 17 */ 18 public $loop; 19 20 /** 21 * @var \Ratchet\MessageComponentInterface 22 */ 23 public $app; 24 25 /** 26 * The socket server the Ratchet Application is run off of 27 * @var \React\Socket\ServerInterface 28 */ 29 public $socket; 30 31 /** 32 * @param \Ratchet\MessageComponentInterface $app The Ratchet application stack to host 33 * @param \React\Socket\ServerInterface $socket The React socket server to run the Ratchet application off of 34 * @param \React\EventLoop\LoopInterface|null $loop The React looper to run the Ratchet application off of 35 */ 36 public function __construct(MessageComponentInterface $app, ServerInterface $socket, LoopInterface $loop = null) { 37 if (false === strpos(PHP_VERSION, "hiphop")) { 38 gc_enable(); 39 } 40 41 set_time_limit(0); 42 ob_implicit_flush(); 43 44 $this->loop = $loop; 45 $this->app = $app; 46 $this->socket = $socket; 47 48 $socket->on('connection', array($this, 'handleConnect')); 49 } 50 51 /** 52 * @param \Ratchet\MessageComponentInterface $component The application that I/O will call when events are received 53 * @param int $port The port to server sockets on 54 * @param string $address The address to receive sockets on (0.0.0.0 means receive connections from any) 55 * @return IoServer 56 */ 57 public static function factory(MessageComponentInterface $component, $port = 80, $address = '0.0.0.0') { 58 $loop = LoopFactory::create(); 59 $socket = new Reactor($address . ':' . $port, $loop); 60 61 return new static($component, $socket, $loop); 62 } 63 64 /** 65 * Run the application by entering the event loop 66 * @throws \RuntimeException If a loop was not previously specified 67 */ 68 public function run() { 69 if (null === $this->loop) { 70 throw new \RuntimeException("A React Loop was not provided during instantiation"); 71 } 72 73 // @codeCoverageIgnoreStart 74 $this->loop->run(); 75 // @codeCoverageIgnoreEnd 76 } 77 78 /** 79 * Triggered when a new connection is received from React 80 * @param \React\Socket\ConnectionInterface $conn 81 */ 82 public function handleConnect($conn) { 83 $conn->decor = new IoConnection($conn); 84 $conn->decor->resourceId = (int)$conn->stream; 85 86 $uri = $conn->getRemoteAddress(); 87 $conn->decor->remoteAddress = trim( 88 parse_url((strpos($uri, '://') === false ? 'tcp://' : '') . $uri, PHP_URL_HOST), 89 '[]' 90 ); 91 92 $this->app->onOpen($conn->decor); 93 94 $conn->on('data', function ($data) use ($conn) { 95 $this->handleData($data, $conn); 96 }); 97 $conn->on('close', function () use ($conn) { 98 $this->handleEnd($conn); 99 }); 100 $conn->on('error', function (\Exception $e) use ($conn) { 101 $this->handleError($e, $conn); 102 }); 103 } 104 105 /** 106 * Data has been received from React 107 * @param string $data 108 * @param \React\Socket\ConnectionInterface $conn 109 */ 110 public function handleData($data, $conn) { 111 try { 112 $this->app->onMessage($conn->decor, $data); 113 } catch (\Exception $e) { 114 $this->handleError($e, $conn); 115 } 116 } 117 118 /** 119 * A connection has been closed by React 120 * @param \React\Socket\ConnectionInterface $conn 121 */ 122 public function handleEnd($conn) { 123 try { 124 $this->app->onClose($conn->decor); 125 } catch (\Exception $e) { 126 $this->handleError($e, $conn); 127 } 128 129 unset($conn->decor); 130 } 131 132 /** 133 * An error has occurred, let the listening application know 134 * @param \Exception $e 135 * @param \React\Socket\ConnectionInterface $conn 136 */ 137 public function handleError(\Exception $e, $conn) { 138 $this->app->onError($conn->decor, $e); 139 } 140} 141