/library/Aspamia/Http/Server.php

https://github.com/shevron/aspamia · PHP · 288 lines · 163 code · 38 blank · 87 comment · 11 complexity · 8ecfead36f71d7c35e90014d66d805c5 MD5 · raw file

  1. <?php
  2. require_once 'Aspamia/Http/Request.php';
  3. require_once 'Aspamia/Http/Response.php';
  4. require_once 'Aspamia/Http/Server/Handler/Abstract.php';
  5. class Aspamia_Http_Server
  6. {
  7. const ASPAMIA_VERSION = '0.0.1';
  8. const DEFAULT_ADDR = '127.0.0.1';
  9. const DEFAULT_PORT = 8000;
  10. protected $_config = array(
  11. 'bind_addr' => self::DEFAULT_ADDR,
  12. 'bind_port' => self::DEFAULT_PORT,
  13. 'stream_wrapper' => 'tcp',
  14. 'handler' => 'Aspamia_Http_Server_Handler_Mock'
  15. );
  16. /**
  17. * Array of registered plugins
  18. *
  19. * @var unknown_type
  20. */
  21. protected $_plugins = array();
  22. /**
  23. * Main listening socket
  24. *
  25. * @var resource
  26. */
  27. protected $_socket = null;
  28. /**
  29. * Stream context (if set)
  30. *
  31. * @var resource
  32. */
  33. protected $_context = null;
  34. /**
  35. * Request handler object
  36. *
  37. * @var Aspamia_Http_Server_Handler_Abstract
  38. */
  39. protected $_handler = null;
  40. public function __construct($config = array())
  41. {
  42. $this->setConfig($config);
  43. // Initialize handler
  44. if ($this->_config['handler'] instanceof Aspamia_Http_Server_Handler_Abstract) {
  45. $this->setHandler($handler);
  46. } elseif (is_string($this->_config['handler'])) {
  47. require_once 'Zend/Loader.php';
  48. Zend_Loader::loadClass($this->_config['handler']);
  49. $handler = new $this->_config['handler'];
  50. if (! $handler instanceof Aspamia_Http_Server_Handler_Abstract) {
  51. require_once 'Aspamia/Http/Server/Exception.php';
  52. throw new Aspamia_Http_Server_Exception("Provded handler is not a Aspamia_Http_Server_Handler_Abstract object");
  53. }
  54. $this->setHandler($handler);
  55. }
  56. }
  57. /**
  58. * Set the configuration data for this server object
  59. *
  60. * @param Zend_Config | array $config
  61. */
  62. public function setConfig($config)
  63. {
  64. if ($config instanceof Zend_Config) {
  65. $config = $config->toArray();
  66. }
  67. if (! is_array($config)) {
  68. require_once 'Aspamia/Http/Server/Exception.php';
  69. throw new Aspamia_Http_Server_Exception("\$config is expected to be an array or a Zend_Config object, got " . gettype($config));
  70. }
  71. foreach($config as $k => $v) {
  72. $this->_config[$k] = $v;
  73. }
  74. }
  75. /**
  76. * Get a configuration option value or the entire configuration array
  77. *
  78. * @param null|string $key
  79. * @return mixed
  80. */
  81. public function getConfig($key = null)
  82. {
  83. if ($key === null) {
  84. return $this->_config;
  85. } elseif (isset($this->_config[$key])) {
  86. return $this->_config[$key];
  87. } else {
  88. return null;
  89. }
  90. }
  91. /**
  92. * Register a plug-in
  93. *
  94. * @param Aspamia_Http_Server_Plugin_Abstract $plugin
  95. */
  96. public function registerPlugin(Aspamia_Http_Server_Plugin_Abstract $plugin)
  97. {
  98. $plugin->setServer($this);
  99. $this->_plugins[] = $plugin;
  100. }
  101. /**
  102. * Get the bind address for the server
  103. *
  104. * @return string
  105. */
  106. public function getBindAddr()
  107. {
  108. return $this->_config['stream_wrapper'] . '://' .
  109. $this->_config['bind_addr'] . ':' .
  110. $this->_config['bind_port'];
  111. }
  112. /**
  113. * TODO: Should this be adapter based?
  114. *
  115. */
  116. public function run()
  117. {
  118. $flags = STREAM_SERVER_BIND | STREAM_SERVER_LISTEN;
  119. if (! $this->_context) {
  120. $this->_context = stream_context_create();
  121. }
  122. $errno = 0;
  123. $errstr = null;
  124. $this->_socket = stream_socket_server($this->getBindAddr(), $errno, $errstr, $flags, $this->_context);
  125. if (! $this->_socket) {
  126. require_once 'Aspamia/Http/Server/Exception.php';
  127. $message = "Unable to bind to '{$this->getBindAddr()}'";
  128. if ($errno || $errstr) $message .= ": [#$errno] $errstr";
  129. throw new Aspamia_Http_Server_Exception($message);
  130. }
  131. $this->_callServerStartupPlugins();
  132. while(true) {
  133. if (($conn = @stream_socket_accept($this->_socket))) {
  134. try {
  135. $this->_handle($conn);
  136. } catch (Aspamia_Http_Exception $ex) {
  137. $this->_callOnErrorPlugins($ex);
  138. // Supress exception and continue looping
  139. } catch (Exception $ex) {
  140. $this->_callOnErrorPlugins($ex);
  141. throw $ex;
  142. }
  143. }
  144. }
  145. fclose($this->_socket);
  146. $this->_callServerShutdownPlugins();
  147. }
  148. /**
  149. * Set the handler object
  150. *
  151. * @param Aspamia_Http_Server_Handler_Abstract $handler
  152. */
  153. public function setHandler(Aspamia_Http_Server_Handler_Abstract $handler)
  154. {
  155. $this->_handler = $handler;
  156. $handler->setServer($this);
  157. }
  158. protected function _handle($connection)
  159. {
  160. // Read and parse the HTTP request line
  161. $this->_callPreRequestPlugins($connection);
  162. $request = $this->_readRequest($connection);
  163. $this->_callPostRequestPlugins($request);
  164. $response = $this->_handler->handle($request);
  165. $this->_callPreResponsePlugins($response);
  166. $serverSignature = 'Aspamia/' . self::ASPAMIA_VERSION . ' ' .
  167. 'PHP/' . PHP_VERSION;
  168. $response->setHeader(array(
  169. 'Server' => $serverSignature,
  170. 'Date' => date(DATE_RFC1123)
  171. ));
  172. // TODO: Right now only 'close' is working, make keep-alive work too.
  173. $response->setHeader('connection', 'close');
  174. fwrite($connection, (string) $response);
  175. fclose($conn);
  176. $this->_callPostResponsePlugins($connection);
  177. }
  178. protected function _readRequest($connection)
  179. {
  180. return Aspamia_Http_Request::read($connection);
  181. }
  182. /**
  183. * Call the server startup hook of all plugins
  184. *
  185. */
  186. protected function _callServerStartupPlugins()
  187. {
  188. foreach ($this->_plugins as $plugin) /* @var $plugin Aspamia_Http_Server_Plugin_Abstract */
  189. $plugin->serverStartup();
  190. }
  191. /**
  192. * Call the server shutdown hook of all plugins
  193. *
  194. */
  195. protected function _callServerShutdownPlugins()
  196. {
  197. foreach ($this->_plugins as $plugin) /* @var $plugin Aspamia_Http_Server_Plugin_Abstract */
  198. $plugin->serverShutdown();
  199. }
  200. /**
  201. * Call the on-error hook of all plugins
  202. *
  203. * @param Exception $ex
  204. */
  205. protected function _callOnErrorPlugins(Exception $ex)
  206. {
  207. foreach ($this->_plugins as $plugin) /* @var $plugin Aspamia_Http_Server_Plugin_Abstract */
  208. $plugin->onError($ex);
  209. }
  210. /**
  211. * Call the pre-request hook of all plugins
  212. *
  213. * @param resource $conn
  214. */
  215. protected function _callPreRequestPlugins($conn)
  216. {
  217. foreach ($this->_plugins as $plugin) /* @var $plugin Aspamia_Http_Server_Plugin_Abstract */
  218. $plugin->preRequest($conn);
  219. }
  220. /**
  221. * Call the post-request hook of all plugins
  222. *
  223. * @param Aspamia_Http_Request $request
  224. */
  225. protected function _callPostRequestPlugins(Aspamia_Http_Request $request)
  226. {
  227. foreach ($this->_plugins as $plugin) /* @var $plugin Aspamia_Http_Server_Plugin_Abstract */
  228. $plugin->postRequest($request);
  229. }
  230. /**
  231. * Call the pre-response hook of all plugins
  232. *
  233. * @param Aspamia_Http_Response $response
  234. */
  235. protected function _callPreResponsePlugins(Aspamia_Http_Response $response)
  236. {
  237. foreach ($this->_plugins as $plugin) /* @var $plugin Aspamia_Http_Server_Plugin_Abstract */
  238. $plugin->preResponse($response);
  239. }
  240. /**
  241. * Call the post-response hook of all plugins
  242. *
  243. * @param resource $conn
  244. */
  245. protected function _callPostResponsePlugins($conn)
  246. {
  247. foreach ($this->_plugins as $plugin) /* @var $plugin Aspamia_Http_Server_Plugin_Abstract */
  248. $plugin->postResponse($conn);
  249. }
  250. }