PageRenderTime 41ms CodeModel.GetById 13ms RepoModel.GetById 1ms app.codeStats 0ms

/fcgi.php

https://github.com/lucciano/eventerator
PHP | 368 lines | 315 code | 50 blank | 3 comment | 34 complexity | 58c6fa76c0b23ff7e3a6e0f012b22174 MD5 | raw file
  1. <?php
  2. require_once('async.php');
  3. class FastCGI {
  4. const LISTENSOCK_FILENO = 0;
  5. const VERSION_1 = 1;
  6. const HEADER_LEN = 8;
  7. const BEGIN_REQUEST = 1;
  8. const ABORT_REQUEST = 2;
  9. const END_REQUEST = 3;
  10. const PARAMS = 4;
  11. const STDIN = 5;
  12. const STDOUT = 6;
  13. const STDERR = 7;
  14. const DATA = 8;
  15. const GET_VALUES = 9;
  16. const GET_VALUES_RESULT = 10;
  17. const UNKNOWN_TYPE = 11;
  18. const MAXTYPE = UNKNOWN_TYPE;
  19. const NULL_REQUEST_ID = 0;
  20. const KEEP_CONN = 1;
  21. const RESPONDER = 1;
  22. const AUTHORIZER = 2;
  23. const FILTER = 3;
  24. const REQUEST_COMPLETE = 0;
  25. const CANT_MPX_CONN = 1;
  26. const OVERLOADED = 2;
  27. const UNKNOWN_ROLE = 3;
  28. const MAX_CONNS = 'FCGI_MAX_CONNS';
  29. const MAX_REQS = 'FCGI_MAX_REQS';
  30. const MPXS_CONNS = 'FCGI_MPXS_CONNS';
  31. private $application;
  32. private $app_class;
  33. private $events;
  34. // Default settings
  35. private $max_conns = 10;
  36. private $max_reqs = 10;
  37. private $mpxs_conns = 1;
  38. public function __construct($events, $address, $app_class, $application = null) {
  39. $this->app_class = $app_class;
  40. $this->application = $application;
  41. $this->events = $events;
  42. $fcgi =& $this;
  43. $events->listen($address, function ($stream) use (&$fcgi) {
  44. // Callback loop for handling incoming FastCGI records.
  45. call_user_func($handle_record = function () use (&$fcgi, &$handle_record, &$stream) {
  46. $fcgi->receive_record($stream, $handle_record);
  47. });
  48. });
  49. }
  50. static function decode_string_length($content, &$offset) {
  51. $result = ord($content[$offset]);
  52. if ($result >> 7) {
  53. $result = (($result & 0x7f) << 24) |
  54. (ord($content[$offset + 1]) << 16) |
  55. (ord($content[$offset + 2]) << 8) |
  56. ord($content[$offset + 3]);
  57. $offset += 3;
  58. }
  59. ++$offset;
  60. return $result;
  61. }
  62. static function decode_string($content, $length, &$offset) {
  63. $result = substr($content, $offset, $length);
  64. $offset += $length;
  65. return $result;
  66. }
  67. static function encode_string_length(&$str) {
  68. $length = strlen($str);
  69. if ($length >= (1 << 31)) {
  70. throw new Exception('name/value string too long: ' . substr($str, 0, 16) . '...');
  71. }
  72. elseif ($length >= (1 << 7)) {
  73. return chr((1 << 8) | ($length >> 24)) . chr($length >> 16) . chr($length >> 8) . chr($length);
  74. }
  75. else {
  76. return chr($length);
  77. }
  78. }
  79. static function decode_name_values($content) {
  80. $result = array();
  81. $content_length = strlen($content);
  82. $offset = 0;
  83. while ($offset < $content_length) {
  84. $name_length = self::decode_string_length($content, $offset);
  85. $value_length = self::decode_string_length($content, $offset);
  86. $name = self::decode_string($content, $name_length, $offset);
  87. $value = self::decode_string($content, $value_length, $offset);
  88. $result[$name] = $value;
  89. }
  90. return $result;
  91. }
  92. static function encode_name_values($name_to_value) {
  93. $result = array();
  94. foreach ($name_to_value as $name => $value) {
  95. $result[] = self::encode_string_length($name) .
  96. self::encode_string_length($value) .
  97. $name . $value;
  98. }
  99. return implode($result);
  100. }
  101. function send_get_values_result($stream, $values, $callback = null) {
  102. $result = array();
  103. foreach ($values as $key => $empty) {
  104. switch ($key) {
  105. case self::MAX_CONNS:
  106. $result[$key] = $this->max_conns;
  107. break;
  108. case self::MAX_REQS:
  109. $result[$key] = $this->max_reqs;
  110. break;
  111. case self::MPXS_CONNS:
  112. $result[$key] = $this->mpxs_conns ? 1 : 0;
  113. break;
  114. }
  115. }
  116. $content = self::encode_name_values($result);
  117. $this->send_record($stream, self::GET_VALUES_RESULT, self::NULL_REQUEST_ID, $content, $callback);
  118. }
  119. function send_unknown_type($stream, $type, $callback = null) {
  120. $content = chr($type) . str_repeat(chr(0), 7);
  121. self::send_record($stream, self::UNKNOWN_TYPE, self::NULL_REQUEST_ID, $content, $callback);
  122. }
  123. function send_end_request($stream, $request_id, $app_status, $protocol_status, $should_keep_conn, $callback = null) {
  124. $content = chr($app_status >> 24) . chr($app_status >> 16) . chr($app_status >> 8) . chr($app_status) . chr($protocol_status) . str_repeat(chr(0), 3);
  125. $this->send_record($stream, self::END_REQUEST, $request_id, $content, function () use (&$callback, &$stream, $should_keep_conn) {
  126. if (!$should_keep_conn) {
  127. $stream->close();
  128. }
  129. if (isset($callback)) {
  130. call_user_func($callback);
  131. }
  132. });
  133. }
  134. function send_stdout($stream, $request_id, $data, $callback = null) {
  135. $this->send_record($stream, self::STDOUT, $request_id, $data, $callback);
  136. }
  137. function send_stderr($stream, $request_id, $data, $callback = null) {
  138. $this->send_record($stream, self::STDERR, $request_id, $data, $callback);
  139. }
  140. function send_record($stream, $type, $request_id, $content, $callback = null) {
  141. $content_length = strlen($content);
  142. $padding_length = $content_length % 8;
  143. $stream->write(chr(self::VERSION_1) .
  144. chr($type) .
  145. chr($request_id >> 8) . chr($request_id) .
  146. chr($content_length >> 8) . chr($content_length) .
  147. chr($padding_length) . chr(0) .
  148. $content .
  149. str_repeat(chr(0), $padding_length),
  150. $callback);
  151. }
  152. function abort_request($stream, $request_id) {
  153. if ($request = $this->get_request($stream, $request_id)) {
  154. $flags = $request->flags();
  155. $fcgi =& $this;
  156. $request->abort(function ($app_status) use (&$stream, &$fcgi, $request_id, $flags) {
  157. $fcgi->send_end_request($stream, $request_id, $app_status, self::REQUEST_COMPLETE, $flags & self::KEEP_CONN);
  158. });
  159. }
  160. }
  161. function add_request($stream, $request_id, $flags) {
  162. $key = spl_object_hash($stream) . $request_id;
  163. $requests =& $this->requests;
  164. $requests[$key] = new $this->app_class($this, $stream, $request_id, $flags, $this->events, $this->application);
  165. $stream->on_close(function () use (&$requests, $key) {
  166. unset($requests[$key]);
  167. });
  168. }
  169. function get_request($stream, $request_id) {
  170. $key = spl_object_hash($stream) . $request_id;
  171. if (array_key_exists($key, $this->requests)) {
  172. return $this->requests[$key];
  173. }
  174. }
  175. function receive_record($stream, $callback) {
  176. $fcgi =& $this;
  177. $stream->read(self::HEADER_LEN, function($header) use (&$stream, &$callback, &$fcgi) {
  178. if (strlen($header) < FastCGI::HEADER_LEN) {
  179. return;
  180. }
  181. $version = ord($header[0]);
  182. if ($version != FastCGI::VERSION_1) {
  183. throw new Exception('FCGI version ' . $version . ' not recognized');
  184. }
  185. $type = ord($header[1]);
  186. if ($type > FastCGI::MAXTYPE) {
  187. throw new Exception('Unknown type ' . $type);
  188. }
  189. $request_id = (ord($header[2]) << 8) | ord($header[3]);
  190. $content_length = (ord($header[4]) << 8) | ord($header[5]);
  191. $padding_length = ord($header[6]);
  192. $stream->read($content_length + $padding_length, function ($content) use ($type, $request_id, $content_length, &$callback, &$fcgi, &$stream) {
  193. $content = substr($content, 0, $content_length);
  194. if (strlen($content) < $content_length) {
  195. return; // premature end of input. give up on it.
  196. }
  197. switch ($type) {
  198. case FastCGI::GET_VALUES:
  199. if ($request_id != self::NULL_REQUEST_ID) {
  200. throw new Exception('GET_VALUES for non-NULL request ID');
  201. }
  202. $names = self::decode_name_values($content);
  203. $fcgi->send_get_values_result($stream, $names);
  204. break;
  205. case FastCGI::BEGIN_REQUEST:
  206. $role = (ord($content[0]) << 8) | ord($content[1]);
  207. $flags = ord($content[2]);
  208. if ($role != FastCGI::RESPONDER) {
  209. $this->send_end_request($stream, $request_id, 0, self::UNKNOWN_ROLE, $flags & FastCGI::KEEP_CONN);
  210. }
  211. else {
  212. $fcgi->add_request($stream, $request_id, $flags);
  213. }
  214. break;
  215. case FastCGI::ABORT_REQUEST:
  216. $fcgi->abort_request($stream, $request_id);
  217. break;
  218. case FastCGI::PARAMS:
  219. $values = FastCGI::decode_name_values($content);
  220. if ($request = $fcgi->get_request($stream, $request_id)) {
  221. $request->params($values);
  222. if (count($values) == 0) {
  223. $request->respond();
  224. }
  225. }
  226. break;
  227. case FastCGI::STDIN:
  228. if ($request = $fcgi->get_request($stream, $request_id)) {
  229. $request->stdin($content);
  230. }
  231. break;
  232. case FastCGI::DATA:
  233. // Theoretically, treat this similarly to STDIN, but we don't use it.
  234. throw new Exception('unexpected DATA FastCGI message');
  235. break;
  236. default:
  237. if ($request_id == FastCGI::NULL_REQUEST_ID) {
  238. $fcgi->send_unknown_type($stream, $type);
  239. }
  240. throw new Exception('Bad application record type ' . $type);
  241. break;
  242. }
  243. call_user_func($callback, $type, $request_id, $content);
  244. });
  245. });
  246. }
  247. }
  248. abstract class Request {
  249. protected $application = null;
  250. protected $events;
  251. protected $params = array();
  252. private $fcgi;
  253. private $stream;
  254. private $request_id;
  255. private $flags;
  256. private $stdin = '';
  257. private $reads = array();
  258. function __construct($fcgi, $stream, $request_id, $flags, $events, $application) {
  259. $this->fcgi = $fcgi;
  260. $this->stream = $stream;
  261. $this->request_id = $request_id;
  262. $this->flags = $flags;
  263. $this->events = $events;
  264. $this->application = $application;
  265. }
  266. function flags() {
  267. return $this->flags;
  268. }
  269. function params($params) {
  270. $this->params = array_merge($this->params, $params);
  271. }
  272. function complete($app_status = 0, $callback = null) {
  273. $this->fcgi->send_end_request($this->stream, $this->request_id, $app_status, FastCGI::REQUEST_COMPLETE, $this->flags & FastCGI::KEEP_CONN, $callback);
  274. }
  275. abstract function respond();
  276. abstract function abort($callback);
  277. function stdin($data) {
  278. $this->stdin .= $data;
  279. $read = null;
  280. while (count($this->reads)) {
  281. $read = array_shift($this->reads);
  282. $condition = $read[0];
  283. $callback = $read[1];
  284. if (is_string($condition) && (false !== ($pos = strpos($this->stdin, $condition)))) {
  285. call_user_func($callback, substr($this->stdin, 0, $pos));
  286. $this->stdin = substr($this->stdin, $pos + strlen($condition));
  287. }
  288. elseif (!is_string($condition) && strlen($this->stdin) >= $condition) {
  289. call_user_func($callback, substr($this->stdin, 0, $condition));
  290. $this->stdin = substr($this->stdin, $condition);
  291. }
  292. else {
  293. break;
  294. }
  295. }
  296. if ($read !== null) {
  297. array_unshift($this->reads, $read);
  298. }
  299. }
  300. function readline($callback, $endline = "\r\n") {
  301. $this->read($endline, $callback);
  302. }
  303. function read($length, $callback) {
  304. $this->reads[] = array($length, $callback);
  305. if (count($this->reads) == 1) {
  306. $this->stdin('');
  307. }
  308. }
  309. function write($data, $callback = null) {
  310. $this->fcgi->send_stdout($this->stream, $this->request_id, $data, $callback);
  311. }
  312. function flush() {
  313. $this->stream->flush();
  314. }
  315. function error($data, $callback = null) {
  316. $this->fcgi->send_stderr($this->stream, $this->request_id, $data, $callback);
  317. }
  318. }