PageRenderTime 24ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/app-servers/FastCGI.php

https://github.com/sergeym/phpdaemon
PHP | 396 lines | 300 code | 60 blank | 36 comment | 54 complexity | e481018dd24bbafb73339f871c95f09d MD5 | raw file
  1. <?php
  2. /**
  3. * @package Applications
  4. * @subpackage FastCGI
  5. *
  6. * @author Zorin Vasily <kak.serpom.po.yaitsam@gmail.com>
  7. */
  8. class FastCGI extends AsyncServer {
  9. protected $initialLowMark = 8; // initial value of the minimal amout of bytes in buffer
  10. protected $initialHighMark = 0xFFFFFF; // initial value of the maximum amout of bytes in buffer
  11. protected $queuedReads = true;
  12. private $variablesOrder;
  13. const FCGI_BEGIN_REQUEST = 1;
  14. const FCGI_ABORT_REQUEST = 2;
  15. const FCGI_END_REQUEST = 3;
  16. const FCGI_PARAMS = 4;
  17. const FCGI_STDIN = 5;
  18. const FCGI_STDOUT = 6;
  19. const FCGI_STDERR = 7;
  20. const FCGI_DATA = 8;
  21. const FCGI_GET_VALUES = 9;
  22. const FCGI_GET_VALUES_RESULT = 10;
  23. const FCGI_UNKNOWN_TYPE = 11;
  24. const FCGI_RESPONDER = 1;
  25. const FCGI_AUTHORIZER = 2;
  26. const FCGI_FILTER = 3;
  27. private static $roles = array(
  28. self::FCGI_RESPONDER => 'FCGI_RESPONDER',
  29. self::FCGI_AUTHORIZER => 'FCGI_AUTHORIZER',
  30. self::FCGI_FILTER => 'FCGI_FILTER',
  31. );
  32. private static $requestTypes = array(
  33. self::FCGI_BEGIN_REQUEST => 'FCGI_BEGIN_REQUEST',
  34. self::FCGI_ABORT_REQUEST => 'FCGI_ABORT_REQUEST',
  35. self::FCGI_END_REQUEST => 'FCGI_END_REQUEST',
  36. self::FCGI_PARAMS => 'FCGI_PARAMS',
  37. self::FCGI_STDIN => 'FCGI_STDIN',
  38. self::FCGI_STDOUT => 'FCGI_STDOUT',
  39. self::FCGI_STDERR => 'FCGI_STDERR',
  40. self::FCGI_DATA => 'FCGI_DATA',
  41. self::FCGI_GET_VALUES => 'FCGI_GET_VALUES',
  42. self::FCGI_GET_VALUES_RESULT => 'FCGI_GET_VALUES_RESULT',
  43. self::FCGI_UNKNOWN_TYPE => 'FCGI_UNKNOWN_TYPE',
  44. );
  45. /**
  46. * Setting default config options
  47. * Overriden from AppInstance::getConfigDefaults
  48. * @return array|false
  49. */
  50. protected function getConfigDefaults() {
  51. return array(
  52. // @todo add description strings
  53. 'expose' => 1,
  54. 'auto-read-body-file' => 1,
  55. 'listen' => 'tcp://127.0.0.1,unix:/tmp/phpdaemon.fcgi.sock',
  56. 'listen-port' => 9000,
  57. 'allowed-clients' => '127.0.0.1',
  58. 'log-records' => 0,
  59. 'log-records-miss' => 0,
  60. 'log-events' => 0,
  61. 'log-queue' => 0,
  62. 'send-file' => 0,
  63. 'send-file-dir' => '/dev/shm',
  64. 'send-file-prefix' => 'fcgi-',
  65. 'send-file-onlybycommand' => 0,
  66. 'keepalive' => new Daemon_ConfigEntryTime('0s'),
  67. 'chunksize' => new Daemon_ConfigEntrySize('8k'),
  68. 'defaultcharset' => 'utf-8',
  69. // disabled by default
  70. 'enable' => 0
  71. );
  72. }
  73. /**
  74. * Constructor.
  75. * @return void
  76. */
  77. public function init() {
  78. if ($this->config->enable) {
  79. if (
  80. ($order = ini_get('request_order'))
  81. || ($order = ini_get('variables_order'))
  82. ) {
  83. $this->variablesOrder = $order;
  84. } else {
  85. $this->variablesOrder = null;
  86. }
  87. $this->allowedClients = explode(',', $this->config->allowedclients->value);
  88. $this->bindSockets(
  89. $this->config->listen->value,
  90. $this->config->listenport->value
  91. );
  92. }
  93. }
  94. /**
  95. * Called when remote host is trying to establish the connection.
  96. * @return boolean If true then we can accept new connections, else we can't.
  97. */
  98. public function checkAccept() {
  99. if (Daemon::$process->reload) {
  100. return false;
  101. }
  102. return Daemon::$config->maxconcurrentrequestsperworker->value >= sizeof($this->queue);
  103. }
  104. /**
  105. * Handles the output from downstream requests.
  106. * @param object Request.
  107. * @param string The output.
  108. * @return void
  109. */
  110. public function requestOut($request, $output) {
  111. $outlen = strlen($output);
  112. if ($this->config->logrecords->value) {
  113. Daemon::log('[DEBUG] requestOut(' . $request->attrs->id . ',[...' . $outlen . '...])');
  114. }
  115. if (!isset(Daemon::$process->pool[$request->attrs->connId])) {
  116. if (
  117. $this->config->logrecordsmiss->value
  118. || $this->config->logrecords->value
  119. ) {
  120. Daemon::log('[DEBUG] requestOut(' . $request->attrs->id . ',[...' . $outlen . '...]) connId ' . $connId . ' not found.');
  121. }
  122. return false;
  123. }
  124. for ($o = 0; $o < $outlen;) {
  125. $c = min($this->config->chunksize->value, $outlen - $o);
  126. Daemon::$process->writePoolState[$request->attrs->connId] = true;
  127. $w = event_buffer_write($this->buf[$request->attrs->connId],
  128. "\x01" // protocol version
  129. . "\x06" // record type (STDOUT)
  130. . pack('nn', $request->attrs->id, $c) // id, content length
  131. . "\x00" // padding length
  132. . "\x00" // reserved
  133. . ($c === $outlen ? $output : binarySubstr($output, $o, $c)) // content
  134. );
  135. if ($w === false) {
  136. $request->abort();
  137. return false;
  138. }
  139. $o += $c;
  140. }
  141. }
  142. /**
  143. * Handles the output from downstream requests.
  144. * @return void
  145. */
  146. public function endRequest($req, $appStatus, $protoStatus) {
  147. $connId = $req->attrs->connId;
  148. if ($this->config->logevents->value) {
  149. Daemon::$process->log('endRequest(' . implode(',', func_get_args()) . '): connId = ' . $connId . '.');
  150. };
  151. $c = pack('NC', $appStatus, $protoStatus) // app status, protocol status
  152. . "\x00\x00\x00";
  153. if (!isset($this->buf[$connId])) {return;}
  154. Daemon::$process->writePoolState[$connId] = true;
  155. $w = event_buffer_write($this->buf[$connId],
  156. "\x01" // protocol version
  157. . "\x03" // record type (END_REQUEST)
  158. . pack('nn', $req->attrs->id, strlen($c)) // id, content length
  159. . "\x00" // padding length
  160. . "\x00" // reserved
  161. . $c // content
  162. );
  163. if ($protoStatus === -1) {
  164. $this->closeConnection($connId);
  165. }
  166. elseif (!$this->config->keepalive->value) {
  167. $this->finishConnection($connId);
  168. }
  169. }
  170. /**
  171. * Reads data from the connection's buffer.
  172. * @param integer Connection's ID.
  173. * @return void
  174. */
  175. public function readConn($connId) {
  176. $state = sizeof($this->poolState[$connId]);
  177. if ($state === 0) {
  178. $header = $this->read($connId, 8);
  179. if ($header === false) {
  180. return;
  181. }
  182. $r = unpack('Cver/Ctype/nreqid/nconlen/Cpadlen/Creserved', $header);
  183. if ($r['conlen'] > 0) {
  184. event_buffer_watermark_set($this->buf[$connId], EV_READ, $r['conlen'], 0xFFFFFF);
  185. }
  186. $this->poolState[$connId][0] = $r;
  187. ++$state;
  188. } else {
  189. $r = $this->poolState[$connId][0];
  190. }
  191. if ($state === 1) {
  192. $c = ($r['conlen'] === 0) ? '' : $this->read($connId, $r['conlen']);
  193. if ($c === false) {
  194. return;
  195. }
  196. if ($r['padlen'] > 0) {
  197. event_buffer_watermark_set($this->buf[$connId], EV_READ, $r['padlen'], 0xFFFFFF);
  198. }
  199. $this->poolState[$connId][1] = $c;
  200. ++$state;
  201. } else {
  202. $c = $this->poolState[$connId][1];
  203. }
  204. if ($state === 2) {
  205. $pad = ($r['padlen'] === 0) ? '' : $this->read($connId, $r['padlen']);
  206. if ($pad === false) {
  207. return;
  208. }
  209. $this->poolState[$connId][2] = $pad;
  210. } else {
  211. $pad = $this->poolState[$connId][2];
  212. }
  213. $this->poolState[$connId] = array();
  214. $type = &$r['type'];
  215. $r['ttype'] = isset(self::$requestTypes[$type]) ? self::$requestTypes[$type] : $type;
  216. $rid = $connId . '-' . $r['reqid'];
  217. if ($this->config->logrecords->value) {
  218. Daemon::log('[DEBUG] FastCGI-record #' . $r['type'] . ' (' . $r['ttype'] . '). Request ID: ' . $rid
  219. . '. Content length: ' . $r['conlen'] . ' (' . strlen($c) . ') Padding length: ' . $r['padlen']
  220. . ' (' . strlen($pad) . ')');
  221. }
  222. if ($type == self::FCGI_BEGIN_REQUEST) {
  223. ++Daemon::$process->reqCounter;
  224. $rr = unpack('nrole/Cflags',$c);
  225. $req = new stdClass();
  226. $req->attrs = new stdClass();
  227. $req->attrs->request = array();
  228. $req->attrs->get = array();
  229. $req->attrs->post = array();
  230. $req->attrs->cookie = array();
  231. $req->attrs->server = array();
  232. $req->attrs->files = array();
  233. $req->attrs->session = null;
  234. $req->attrs->connId = $connId;
  235. $req->attrs->trole = self::$roles[$rr['role']];
  236. $req->attrs->flags = $rr['flags'];
  237. $req->attrs->id = $r['reqid'];
  238. $req->attrs->params_done = false;
  239. $req->attrs->stdin_done = false;
  240. $req->attrs->stdinbuf = '';
  241. $req->attrs->stdinlen = 0;
  242. $req->attrs->chunked = false;
  243. $req->attrs->noHttpVer = true;
  244. $req->queueId = $rid;
  245. if ($this->config->logqueue->value) {
  246. Daemon::$process->log('new request queued.');
  247. }
  248. Daemon::$process->queue[$rid] = $req;
  249. $this->poolQueue[$connId][$req->attrs->id] = $req;
  250. }
  251. elseif (isset(Daemon::$process->queue[$rid])) {
  252. $req = Daemon::$process->queue[$rid];
  253. } else {
  254. Daemon::log('Unexpected FastCGI-record #' . $r['type'] . ' (' . $r['ttype'] . '). Request ID: ' . $rid . '.');
  255. return;
  256. }
  257. if ($type === self::FCGI_ABORT_REQUEST) {
  258. $req->abort();
  259. }
  260. elseif ($type === self::FCGI_PARAMS) {
  261. if ($c === '') {
  262. $req->attrs->params_done = true;
  263. $req = Daemon::$appResolver->getRequest($req, $this);
  264. if ($req instanceof stdClass) {
  265. $this->endRequest($req, 0, 0);
  266. unset(Daemon::$process->queue[$rid]);
  267. } else {
  268. if (
  269. $this->config->sendfile->value
  270. && (
  271. !$this->config->sendfileonlybycommand->value
  272. || isset($req->attrs->server['USE_SENDFILE'])
  273. )
  274. && !isset($req->attrs->server['DONT_USE_SENDFILE'])
  275. ) {
  276. $fn = tempnam(
  277. $this->config->sendfiledir->value,
  278. $this->config->sendfileprefix->value
  279. );
  280. $req->sendfp = fopen($fn, 'wb');
  281. $req->header('X-Sendfile: ' . $fn);
  282. }
  283. Daemon::$process->queue[$rid] = $req;
  284. }
  285. } else {
  286. $p = 0;
  287. while ($p < $r['conlen']) {
  288. if (($namelen = ord($c{$p})) < 128) {
  289. ++$p;
  290. } else {
  291. $u = unpack('Nlen', chr(ord($c{$p}) & 0x7f) . binarySubstr($c, $p + 1, 3));
  292. $namelen = $u['len'];
  293. $p += 4;
  294. }
  295. if (($vlen = ord($c{$p})) < 128) {
  296. ++$p;
  297. } else {
  298. $u = unpack('Nlen', chr(ord($c{$p}) & 0x7f) . binarySubstr($c, $p + 1, 3));
  299. $vlen = $u['len'];
  300. $p += 4;
  301. }
  302. $req->attrs->server[binarySubstr($c, $p, $namelen)] = binarySubstr($c, $p + $namelen, $vlen);
  303. $p += $namelen + $vlen;
  304. }
  305. }
  306. }
  307. elseif ($type === self::FCGI_STDIN) {
  308. if ($c === '') {
  309. $req->attrs->stdin_done = true;
  310. }
  311. $req->stdin($c);
  312. }
  313. if (
  314. $req->attrs->stdin_done
  315. && $req->attrs->params_done
  316. ) {
  317. if ($this->variablesOrder === null) {
  318. $req->attrs->request = $req->attrs->get + $req->attrs->post + $req->attrs->cookie;
  319. } else {
  320. for ($i = 0, $s = strlen($this->variablesOrder); $i < $s; ++$i) {
  321. $char = $this->variablesOrder[$i];
  322. if ($char == 'G') {
  323. $req->attrs->request += $req->attrs->get;
  324. }
  325. elseif ($char == 'P') {
  326. $req->attrs->request += $req->attrs->post;
  327. }
  328. elseif ($char == 'C') {
  329. $req->attrs->request += $req->attrs->cookie;
  330. }
  331. }
  332. }
  333. Daemon::$process->timeLastReq = time();
  334. }
  335. }
  336. }