PageRenderTime 40ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/Responder.php

https://github.com/jeremyFreeAgent/Fastcgi
PHP | 360 lines | 104 code | 65 blank | 191 comment | 5 complexity | a12473bc6c17c0b9c51dfa5f367b1137 MD5 | raw file
  1. <?php
  2. /**
  3. * Hoa
  4. *
  5. *
  6. * @license
  7. *
  8. * New BSD License
  9. *
  10. * Copyright © 2007-2013, Ivan Enderlin. All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or without
  13. * modification, are permitted provided that the following conditions are met:
  14. * * Redistributions of source code must retain the above copyright
  15. * notice, this list of conditions and the following disclaimer.
  16. * * Redistributions in binary form must reproduce the above copyright
  17. * notice, this list of conditions and the following disclaimer in the
  18. * documentation and/or other materials provided with the distribution.
  19. * * Neither the name of the Hoa nor the names of its contributors may be
  20. * used to endorse or promote products derived from this software without
  21. * specific prior written permission.
  22. *
  23. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  24. * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  25. * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  26. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
  27. * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  28. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  29. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  30. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  31. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  32. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  33. * POSSIBILITY OF SUCH DAMAGE.
  34. */
  35. namespace {
  36. from('Hoa')
  37. /**
  38. * \Hoa\Fastcgi\Exception
  39. */
  40. -> import('Fastcgi.Exception.~')
  41. /**
  42. * \Hoa\Fastcgi\Exception\CannotMultiplex
  43. */
  44. -> import('Fastcgi.Exception.CannotMultiplex')
  45. /**
  46. * \Hoa\Fastcgi\Exception\Overloaded
  47. */
  48. -> import('Fastcgi.Exception.Overloaded')
  49. /**
  50. * \Hoa\Fastcgi\Exception\UnknownRole
  51. */
  52. -> import('Fastcgi.Exception.UnknownRole')
  53. /**
  54. * \Hoa\Fastcgi\Connection
  55. */
  56. -> import('Fastcgi.Connection');
  57. }
  58. namespace Hoa\Fastcgi {
  59. /**
  60. * Class \Hoa\Fastcgi\Responder.
  61. *
  62. * A FastCGI client with a responder role.
  63. * Inspired by PHP SAPI code: php://sapi/cgi/fastcgi.*.
  64. *
  65. * @author Ivan Enderlin <ivan.enderlin@hoa-project.net>
  66. * @copyright Copyright © 2007-2013 Ivan Enderlin.
  67. * @license New BSD License
  68. */
  69. class Responder extends Connection {
  70. /**
  71. * Request: begin.
  72. *
  73. * @const int
  74. */
  75. const REQUEST_BEGIN = 1;
  76. /**
  77. * Request: abord.
  78. *
  79. * @const int
  80. */
  81. const REQUEST_ABORD = 2;
  82. /**
  83. * Request: end.
  84. *
  85. * @const int
  86. */
  87. const REQUEST_END = 3;
  88. /**
  89. * Request: parameters.
  90. *
  91. * @const int
  92. */
  93. const REQUEST_PARAMETERS = 4;
  94. /**
  95. * Stream: stdin.
  96. *
  97. * @const int
  98. */
  99. const STREAM_STDIN = 5;
  100. /**
  101. * Stream: stdout.
  102. *
  103. * @const int
  104. */
  105. const STREAM_STDOUT = 6;
  106. /**
  107. * Stream: stderr.
  108. *
  109. * @const int
  110. */
  111. const STREAM_STDERR = 7;
  112. /**
  113. * Request status: normal en of request.
  114. *
  115. * @const int
  116. */
  117. const STATUS_COMPLETE = 0;
  118. /**
  119. * Request status: rejecting a new request; this happens when a Web server
  120. * sends concurrent requests over one connection to an application that is
  121. * designed to process one request at a time per connection.
  122. *
  123. * @const int
  124. */
  125. const STATUS_CANNOT_MULTIPLEX = 1;
  126. /**
  127. * Request status: rejecting a new request; this happens when the
  128. * application runs out of some resource, e.g. database connections.
  129. *
  130. * @const int
  131. */
  132. const STATUS_OVERLOADED = 2;
  133. /**
  134. * Request status: rejecting a new request; this happens when the Web server
  135. * has specificied a role that is unknown to the application.
  136. *
  137. * @const int
  138. */
  139. const STATUS_UNKNOWN_ROLE = 3;
  140. /**
  141. * Client socket connection.
  142. *
  143. * @var \Hoa\Socket\Client object
  144. */
  145. protected $_client = null;
  146. /**
  147. * Response: content.
  148. *
  149. * @var \Hoa\Fastcgi\Client string
  150. */
  151. protected $_content = null;
  152. /**
  153. * Response: headers.
  154. *
  155. * @var \Hoa\Fastcgi\Client array
  156. */
  157. protected $_headers = array();
  158. /**
  159. * Constructor.
  160. *
  161. * @access public
  162. * @param \Hoa\Socket\Client $client Client connection.
  163. * @return void
  164. */
  165. public function __construct ( \Hoa\Socket\Client $client ) {
  166. $this->setClient($client);
  167. return;
  168. }
  169. /**
  170. * Send data on a FastCGI.
  171. *
  172. * @access public
  173. * @param array $headers Headers.
  174. * @param string $content Content (e.g. key=value for POST).
  175. * @return string
  176. * @throw \Hoa\Socket\Exception
  177. * @throw \Hoa\Fastcgi\Exception
  178. * @throw \Hoa\Fastcgi\Exception\CannotMultiplex
  179. * @throw \Hoa\Fastcgi\Exception\Overloaded
  180. * @throw \Hoa\Fastcgi\Exception\UnknownRole
  181. */
  182. public function send ( Array $headers, $content = null ) {
  183. $client = $this->getClient();
  184. $client->connect();
  185. $client->setStreamBlocking(true);
  186. $parameters = null;
  187. $response = null;
  188. $request = $this->pack(
  189. self::REQUEST_BEGIN,
  190. // ┌───────────────────┐
  191. // │ “I'm a responder” │
  192. // └─────────⌵─────────┘
  193. chr(0) . chr(1) . chr((int) $client->isPersistent()) .
  194. chr(0) . chr(0) . chr(0) . chr(0) . chr(0)
  195. );
  196. $parameters .= $this->packPairs($headers);
  197. if(null !== $parameters)
  198. $request .= $this->pack(self::REQUEST_PARAMETERS, $parameters);
  199. $request .= $this->pack(self::REQUEST_PARAMETERS, '');
  200. if(null !== $content)
  201. $request .= $this->pack(self::STREAM_STDIN, $content);
  202. $request .= $this->pack(self::STREAM_STDIN, '');
  203. $client->writeAll($request);
  204. $handle = null;
  205. do {
  206. if(false === $handle = $this->readPack())
  207. throw new Exception(
  208. 'Bad request (not a well-formed FastCGI request).', 0);
  209. if( self::STREAM_STDOUT === $handle[parent::HEADER_TYPE]
  210. || self::STREAM_STDERR === $handle[parent::HEADER_TYPE])
  211. $response .= $handle[parent::HEADER_CONTENT];
  212. } while(self::REQUEST_END !== $handle[parent::HEADER_TYPE]);
  213. $client->disconnect();
  214. switch(ord($handle[parent::HEADER_CONTENT][4])) {
  215. case self::STATUS_CANNOT_MULTIPLEX:
  216. throw new Exception\CannotMultiplex(
  217. 'Application %s that you are trying to reach does not ' .
  218. 'support multiplexing.',
  219. 0, $this->getClient()->getSocket()->__toString());
  220. break;
  221. case self::STATUS_OVERLOADED:
  222. throw new Exception\Overloaded(
  223. 'Application %s is too busy and rejects your request.',
  224. 1, $this->getClient()->getSocket()->__toString());
  225. break;
  226. case self::STATUS_UNKNOWN_ROLE:
  227. throw new Exception\UnknownRole(
  228. 'Server for the application %s returns an unknown role.',
  229. 2, $this->getClient()->getSocket()->__toString());
  230. break;
  231. }
  232. /**
  233. * default: // self::STATUS_COMPLETE
  234. * break;
  235. */
  236. $pos = strpos($response, "\r\n\r\n");
  237. $headers = substr($response, 0, $pos);
  238. foreach(explode("\r\n", $headers) as $header) {
  239. $semicolon = strpos($header, ':');
  240. $this->_headers[strtolower(trim(substr($header, 0, $semicolon)))]
  241. = trim(substr($header, $semicolon + 1));
  242. }
  243. return $this->_content = substr($response, $pos + 4);
  244. }
  245. /**
  246. * Get response content.
  247. *
  248. * @access public
  249. * @return string
  250. */
  251. public function getResponseContent ( ) {
  252. return $this->_content;
  253. }
  254. /**
  255. * Get response headers.
  256. *
  257. * @access public
  258. * @return array
  259. */
  260. public function getResponseHeaders ( ) {
  261. return $this->_headers;
  262. }
  263. /**
  264. * Read data.
  265. *
  266. * @access protected
  267. * @param int $length Length of data to read.
  268. * @return string
  269. */
  270. protected function read ( $length ) {
  271. return $this->getClient()->read($length);
  272. }
  273. /**
  274. * Set client.
  275. *
  276. * @access public
  277. * @param \Hoa\Socket\Client $client Client.
  278. * @return \Hoa\Socket\Client
  279. */
  280. public function setClient ( \Hoa\Socket\Client $client ) {
  281. $old = $this->_client;
  282. $this->_client = $client;
  283. return $old;
  284. }
  285. /**
  286. * Get client.
  287. *
  288. * @access public
  289. * @return \Hoa\Socket\Client
  290. */
  291. public function getClient ( ) {
  292. return $this->_client;
  293. }
  294. }
  295. }