123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309 |
- <?php
- namespace PHPSocketIO\Engine;
- use \PHPSocketIO\Engine\Transports\Polling;
- use \PHPSocketIO\Engine\Transports\PollingXHR;
- use \PHPSocketIO\Engine\Transports\WebSocket;
- use \PHPSocketIO\Event\Emitter;
- use \PHPSocketIO\Debug;
- class Engine extends Emitter
- {
- public $pingTimeout = 60;
- public $pingInterval = 25;
- public $upgradeTimeout = 5;
- public $transports = array();
- public $allowUpgrades = array();
- public $allowRequest = array();
- public $clients = array();
- public $origins = '*:*';
- public static $allowTransports = array(
- 'polling' => 'polling',
- 'websocket' => 'websocket'
- );
- public static $errorMessages = array(
- 'Transport unknown',
- 'Session ID unknown',
- 'Bad handshake method',
- 'Bad request'
- );
- const ERROR_UNKNOWN_TRANSPORT = 0;
- const ERROR_UNKNOWN_SID = 1;
- const ERROR_BAD_HANDSHAKE_METHOD = 2;
- const ERROR_BAD_REQUEST = 3;
- public function __construct($opts = array())
- {
- $ops_map = array(
- 'pingTimeout',
- 'pingInterval',
- 'upgradeTimeout',
- 'transports',
- 'allowUpgrades',
- 'allowRequest'
- );
- foreach($ops_map as $key)
- {
- if(isset($opts[$key]))
- {
- $this->$key = $opts[$key];
- }
- }
- Debug::debug('Engine __construct');
- }
- public function __destruct()
- {
- Debug::debug('Engine __destruct');
- }
- public function handleRequest($req, $res)
- {
- $this->prepare($req);
- $req->res = $res;
- $this->verify($req, $res, false, array($this, 'dealRequest'));
- }
- public function dealRequest($err, $success, $req)
- {
- if (!$success)
- {
- self::sendErrorMessage($req, $req->res, $err);
- return;
- }
- if(isset($req->_query['sid']))
- {
- $this->clients[$req->_query['sid']]->transport->onRequest($req);
- }
- else
- {
- $this->handshake($req->_query['transport'], $req);
- }
- }
- protected function sendErrorMessage($req, $res, $code)
- {
- $headers = array('Content-Type'=> 'application/json');
- if(isset($req->headers['origin']))
- {
- $headers['Access-Control-Allow-Credentials'] = 'true';
- $headers['Access-Control-Allow-Origin'] = $req->headers['origin'];
- }
- else
- {
- $headers['Access-Control-Allow-Origin'] = '*';
- }
- $res->writeHead(403, '', $headers);
- $res->end(json_encode(array(
- 'code' => $code,
- 'message' => isset(self::$errorMessages[$code]) ? self::$errorMessages[$code] : $code
- )));
- }
- protected function verify($req, $res, $upgrade, $fn)
- {
- if(!isset($req->_query['transport']) || !isset(self::$allowTransports[$req->_query['transport']]))
- {
- return call_user_func($fn, self::ERROR_UNKNOWN_TRANSPORT, false, $req, $res);
- }
- $transport = $req->_query['transport'];
- $sid = isset($req->_query['sid']) ? $req->_query['sid'] : '';
- if ($transport === 'websocket' && empty($sid)) {
- return call_user_func($fn, self::ERROR_UNKNOWN_TRANSPORT, false, $req, $res);
- }
- if($sid)
- {
- if(!isset($this->clients[$sid]))
- {
- return call_user_func($fn, self::ERROR_UNKNOWN_SID, false, $req, $res);
- }
- if(!$upgrade && $this->clients[$sid]->transport->name !== $transport)
- {
- return call_user_func($fn, self::ERROR_BAD_REQUEST, false, $req, $res);
- }
- }
- else
- {
- if('GET' !== $req->method)
- {
- return call_user_func($fn, self::ERROR_BAD_HANDSHAKE_METHOD, false, $req, $res);
- }
- return $this->checkRequest($req, $res, $fn);
- }
- call_user_func($fn, null, true, $req, $res);
- }
- public function checkRequest($req, $res, $fn)
- {
- if ($this->origins === "*:*" || empty($this->origins))
- {
- return call_user_func($fn, null, true, $req, $res);
- }
- $origin = null;
- if (isset($req->headers['origin']))
- {
- $origin = $req->headers['origin'];
- }
- else if(isset($req->headers['referer']))
- {
- $origin = $req->headers['referer'];
- }
- // file:// URLs produce a null Origin which can't be authorized via echo-back
- if ('null' === $origin || null === $origin) {
- return call_user_func($fn, null, true, $req, $res);
- }
- if ($origin)
- {
- $parts = parse_url($origin);
- $defaultPort = 'https:' === $parts['scheme'] ? 443 : 80;
- $parts['port'] = isset($parts['port']) ? $parts['port'] : $defaultPort;
- $allowed_origins = explode(' ', $this->origins);
- foreach( $allowed_origins as $allow_origin ){
- $ok =
- $allow_origin === $parts['scheme'] . '://' . $parts['host'] . ':' . $parts['port'] ||
- $allow_origin === $parts['scheme'] . '://' . $parts['host'] ||
- $allow_origin === $parts['scheme'] . '://' . $parts['host'] . ':*' ||
- $allow_origin === '*:' . $parts['port'];
- if($ok){
- # 只需要有一个白名单通过,则都通过
- return call_user_func($fn, null, $ok, $req, $res);
- }
-
- }
- }
- call_user_func($fn, null, false, $req, $res);
- }
- protected function prepare($req)
- {
- if(!isset($req->_query))
- {
- $info = parse_url($req->url);
- if(isset($info['query']))
- {
- parse_str($info['query'], $req->_query);
- }
- }
- }
- public function handshake($transport, $req)
- {
- $id = bin2hex(pack('d', microtime(true)).pack('N', function_exists('random_int') ? random_int(1, 100000000): rand(1, 100000000)));
- if ($transport == 'websocket') {
- $transport = '\\PHPSocketIO\\Engine\\Transports\\WebSocket';
- }
- elseif (isset($req->_query['j']))
- {
- $transport = '\\PHPSocketIO\\Engine\\Transports\\PollingJsonp';
- }
- else
- {
- $transport = '\\PHPSocketIO\\Engine\\Transports\\PollingXHR';
- }
- $transport = new $transport($req);
-
- $transport->supportsBinary = !isset($req->_query['b64']);
- $socket = new Socket($id, $this, $transport, $req);
-
- /* $transport->on('headers', function(&$headers)use($id)
- {
- $headers['Set-Cookie'] = "io=$id";
- }); */
- $transport->onRequest($req);
- $this->clients[$id] = $socket;
- $socket->once('close', array($this, 'onSocketClose'));
- $this->emit('connection', $socket);
- }
- public function onSocketClose($id)
- {
- unset($this->clients[$id]);
- }
- public function attach($worker)
- {
- $this->server = $worker;
- $worker->onConnect = array($this, 'onConnect');
- }
-
- public function onConnect($connection)
- {
- $connection->onRequest = array($this, 'handleRequest');
- $connection->onWebSocketConnect = array($this, 'onWebSocketConnect');
- // clean
- $connection->onClose = function($connection)
- {
- if(!empty($connection->httpRequest))
- {
- $connection->httpRequest->destroy();
- $connection->httpRequest = null;
- }
- if(!empty($connection->httpResponse))
- {
- $connection->httpResponse->destroy();
- $connection->httpResponse = null;
- }
- if(!empty($connection->onRequest))
- {
- $connection->onRequest = null;
- }
- if(!empty($connection->onWebSocketConnect))
- {
- $connection->onWebSocketConnect = null;
- }
- };
- }
-
- public function onWebSocketConnect($connection, $req, $res)
- {
- $this->prepare($req);
- $this->verify($req, $res, true, array($this, 'dealWebSocketConnect'));
- }
- public function dealWebSocketConnect($err, $success, $req, $res)
- {
- if (!$success)
- {
- self::sendErrorMessage($req, $res, $err);
- return;
- }
- if(isset($req->_query['sid']))
- {
- if(!isset($this->clients[$req->_query['sid']]))
- {
- self::sendErrorMessage($req, $res, 'upgrade attempt for closed client');
- return;
- }
- $client = $this->clients[$req->_query['sid']];
- if($client->upgrading)
- {
- self::sendErrorMessage($req, $res, 'transport has already been trying to upgrade');
- return;
- }
- if($client->upgraded)
- {
- self::sendErrorMessage($req, $res, 'transport had already been upgraded');
- return;
- }
- $transport = new WebSocket($req);
- $client->maybeUpgrade($transport);
- }
- else
- {
- $this->handshake($req->_query['transport'], $req);
- }
- }
- }
|