PageRenderTime 44ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/PHPDaemon/SockJS/Session.php

http://github.com/kakserpom/phpdaemon
PHP | 367 lines | 235 code | 36 blank | 96 comment | 38 complexity | cf8ed980710bba54ec7f71259e2d1d5e MD5 | raw file
Possible License(s): LGPL-3.0
  1. <?php
  2. namespace PHPDaemon\SockJS;
  3. use PHPDaemon\Core\Daemon;
  4. use PHPDaemon\Core\Timer;
  5. use PHPDaemon\HTTPRequest\Generic;
  6. use PHPDaemon\Structures\StackCallbacks;
  7. /**
  8. * @package Libraries
  9. * @subpackage SockJS
  10. * @author Vasily Zorin <maintainer@daemon.io>
  11. */
  12. class Session
  13. {
  14. use \PHPDaemon\Traits\ClassWatchdog;
  15. use \PHPDaemon\Traits\StaticObjectWatchdog;
  16. /**
  17. * @var \PHPDaemon\Request\Generic
  18. */
  19. public $route;
  20. /**
  21. * @var \PHPDaemon\Structures\StackCallbacks
  22. */
  23. public $onWrite;
  24. public $id;
  25. public $appInstance;
  26. public $addr;
  27. /**
  28. * @var array
  29. */
  30. public $buffer = [];
  31. public $framesBuffer = [];
  32. /**
  33. * @var boolean
  34. */
  35. public $finished = false;
  36. protected $onFinishedCalled = false;
  37. /**
  38. * @var boolean
  39. */
  40. public $flushing = false;
  41. /**
  42. * @var integer
  43. */
  44. public $timeout = 60;
  45. public $server;
  46. public $get;
  47. public $cookie;
  48. public $post;
  49. protected $pollMode;
  50. protected $running = false;
  51. protected $finishTimer;
  52. /**
  53. * toJson
  54. * @param string $m
  55. * @return string
  56. */
  57. protected function toJson($m)
  58. {
  59. return json_encode($m, JSON_UNESCAPED_SLASHES);
  60. }
  61. /**
  62. * __construct
  63. * @param Application $appInstance [@todo description]
  64. * @param string $id [@todo description]
  65. * @param array $server [@todo description]
  66. * @return void
  67. */
  68. public function __construct($appInstance, $id, $server)
  69. {
  70. $this->onWrite = new StackCallbacks;
  71. $this->id = $id;
  72. $this->appInstance = $appInstance;
  73. $this->server = $server;
  74. if (isset($this->server['HTTP_COOKIE'])) {
  75. Generic::parseStr(strtr($this->server['HTTP_COOKIE'], Generic::$hvaltr), $this->cookie);
  76. }
  77. if (isset($this->server['QUERY_STRING'])) {
  78. Generic::parseStr($this->server['QUERY_STRING'], $this->get);
  79. }
  80. $this->addr = $server['REMOTE_ADDR'];
  81. $this->finishTimer = setTimeout(function ($timer) {
  82. $this->finish();
  83. }, $this->timeout * 1e6);
  84. $this->appInstance->subscribe('c2s:' . $this->id, [$this, 'c2s']);
  85. $this->appInstance->subscribe('poll:' . $this->id, [$this, 'poll'], function ($redis) {
  86. $this->appInstance->publish('state:' . $this->id, 'started', function ($redis) {
  87. // @TODO: remove this callback
  88. });
  89. });
  90. }
  91. /**
  92. * Uncaught exception handler
  93. * @param object $e
  94. * @return boolean|null Handled?
  95. */
  96. public function handleException($e)
  97. {
  98. if (!isset($this->route)) {
  99. return false;
  100. }
  101. return $this->route->handleException($e);
  102. }
  103. /**
  104. * onHandshake
  105. * @return void
  106. */
  107. public function onHandshake()
  108. {
  109. if (!isset($this->route)) {
  110. return;
  111. }
  112. $this->route->onWakeup();
  113. try {
  114. $this->route->onHandshake();
  115. } catch (\Exception $e) {
  116. Daemon::uncaughtExceptionHandler($e);
  117. }
  118. $this->route->onSleep();
  119. }
  120. /**
  121. * c2s
  122. * @param object $redis
  123. * @return void
  124. */
  125. public function c2s($redis)
  126. {
  127. if (!$redis) {
  128. return;
  129. }
  130. if ($this->finished) {
  131. return;
  132. }
  133. list(, $chan, $msg) = $redis->result;
  134. if ($msg === '') {
  135. return;
  136. }
  137. $this->onFrame($msg, \PHPDaemon\Servers\WebSocket\Pool::STRING);
  138. }
  139. /**
  140. * onFrame
  141. * @param string $msg [@todo description]
  142. * @param integer $type [@todo description]
  143. * @return void
  144. */
  145. public function onFrame($msg, $type)
  146. {
  147. $frames = json_decode($msg, true);
  148. if (!is_array($frames)) {
  149. return;
  150. }
  151. $this->route->onWakeup();
  152. foreach ($frames as $frame) {
  153. try {
  154. $this->route->onFrame($frame, \PHPDaemon\Servers\WebSocket\Pool::STRING);
  155. } catch (\Exception $e) {
  156. Daemon::uncaughtExceptionHandler($e);
  157. }
  158. }
  159. $this->route->onSleep();
  160. }
  161. /**
  162. * poll
  163. * @param object $redis
  164. * @return void
  165. */
  166. public function poll($redis)
  167. {
  168. if (!$redis) {
  169. return;
  170. }
  171. list(, $chan, $msg) = $redis->result;
  172. $this->pollMode = json_decode($msg, true);
  173. Timer::setTimeout($this->finishTimer);
  174. $this->flush();
  175. }
  176. /**
  177. * @TODO DESCR
  178. * @return void
  179. */
  180. public function onWrite()
  181. {
  182. $this->onWrite->executeAll($this->route);
  183. if (method_exists($this->route, 'onWrite')) {
  184. $this->route->onWrite();
  185. }
  186. if ($this->finished) {
  187. if (!sizeof($this->buffer) && !sizeof($this->framesBuffer)) {
  188. $this->onFinish();
  189. }
  190. }
  191. Timer::setTimeout($this->finishTimer);
  192. }
  193. /**
  194. * @TODO DESCR
  195. * @return void
  196. */
  197. public function finish()
  198. {
  199. if ($this->finished) {
  200. return;
  201. }
  202. $this->finished = true;
  203. $this->onFinish();
  204. $this->sendPacket('c' . json_encode([3000, 'Go away!']));
  205. }
  206. /*public function __destruct() {
  207. D('destructed session '.$this->id);
  208. }*/
  209. /**
  210. * @TODO DESCR
  211. * @return void
  212. */
  213. public function onFinish()
  214. {
  215. if ($this->onFinishedCalled) {
  216. return;
  217. }
  218. $this->onFinishedCalled = true;
  219. $this->appInstance->unsubscribe('c2s:' . $this->id, [$this, 'c2s']);
  220. $this->appInstance->unsubscribe('poll:' . $this->id, [$this, 'poll']);
  221. if (isset($this->route)) {
  222. $this->route->onFinish();
  223. }
  224. $this->onWrite->reset();
  225. $this->route = null;
  226. Timer::remove($this->finishTimer);
  227. $this->appInstance->endSession($this);
  228. }
  229. /**
  230. * Flushes buffered packets
  231. * @return void
  232. */
  233. public function flush()
  234. {
  235. if ($this->pollMode === null) { // first polling request is not there yet
  236. return;
  237. }
  238. if ($this->flushing) {
  239. return;
  240. }
  241. $bsize = sizeof($this->buffer);
  242. $fbsize = sizeof($this->framesBuffer);
  243. if ($bsize === 0 && $fbsize === 0) {
  244. return;
  245. }
  246. $this->flushing = true;
  247. if (in_array('one-by-one', $this->pollMode)) {
  248. $b = array_slice($this->buffer, 0, 1);
  249. $bsize = sizeof($b);
  250. } else {
  251. $b = $this->buffer;
  252. }
  253. if ($fbsize > 0) {
  254. if (!in_array('one-by-one', $this->pollMode) || !sizeof($b)) {
  255. $b[] = 'a' . $this->toJson($this->framesBuffer);
  256. } else {
  257. $fbsize = 0;
  258. }
  259. }
  260. $this->appInstance->publish(
  261. 's2c:' . $this->id,
  262. $this->toJson($b),
  263. function ($redis) use ($bsize, $fbsize, $b) {
  264. $this->flushing = false;
  265. if (!$redis) {
  266. return;
  267. }
  268. //D(['b' => $b, $redis->result]);
  269. if ($redis->result === 0) {
  270. return;
  271. }
  272. $reflush = false;
  273. if (sizeof($this->buffer) > $bsize) {
  274. $this->buffer = array_slice($this->buffer, $bsize);
  275. $reflush = true;
  276. } else {
  277. $this->buffer = [];
  278. }
  279. if (sizeof($this->framesBuffer) > $fbsize) {
  280. $this->framesBuffer = array_slice($this->framesBuffer, $fbsize);
  281. $reflush = true;
  282. } else {
  283. $this->framesBuffer = [];
  284. }
  285. $this->onWrite();
  286. if ($reflush && in_array('stream', $this->pollMode)) {
  287. $this->flush();
  288. }
  289. }
  290. );
  291. }
  292. /**
  293. * sendPacket
  294. * @param object $pct [@todo description]
  295. * @param callable $cb [@todo description]
  296. * @callback $cb ( )
  297. * @return void
  298. */
  299. public function sendPacket($pct, $cb = null)
  300. {
  301. if (sizeof($this->framesBuffer)) {
  302. $this->buffer[] = 'a' . $this->toJson($this->framesBuffer);
  303. $this->framesBuffer = [];
  304. }
  305. $this->buffer[] = $pct;
  306. if ($cb !== null) {
  307. $this->onWrite->push($cb);
  308. }
  309. $this->flush();
  310. }
  311. /**
  312. * Sends a frame.
  313. * @param string $data Frame's data.
  314. * @param integer $type Frame's type. See the constants.
  315. * @param callback $cb Optional. Callback called when the frame is received by client.
  316. * @callback $cb ( )
  317. * @return boolean Success.
  318. */
  319. public function sendFrame($data, $type = 0x00, $cb = null)
  320. {
  321. if ($this->finished) {
  322. return false;
  323. }
  324. $this->framesBuffer[] = $data;
  325. if ($cb !== null) {
  326. $this->onWrite->push($cb);
  327. }
  328. $this->flush();
  329. return true;
  330. }
  331. }