PageRenderTime 54ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 1ms

/src/main/php/Rabbit/AMQP/Connection.php

https://github.com/TimLangley/Zend_RabbitMQ
PHP | 455 lines | 312 code | 72 blank | 71 comment | 32 complexity | 58175408a6789367eb23e85460209653 MD5 | raw file
  1. <?php
  2. /**
  3. * Rabbit
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://github.com/canddi/Zend_RabbitMQ/blob/master/LICENSE.txt
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to hello@canddi.com so we can send you a copy immediately.
  14. *
  15. **/
  16. /**
  17. * @category
  18. * @package
  19. * @copyright 2011-01-01, Campaign and Digital Intelligence Ltd
  20. * @license
  21. * @author Tim Langley
  22. **/
  23. class Rabbit_AMQP_Connection extends Rabbit_AMQP_Abstract
  24. {
  25. public static $AMQP_PROTOCOL_HEADER = "AMQP\x01\x01\x09\x01";
  26. public static $LIBRARY_PROPERTIES =
  27. array(
  28. "library" => array(
  29. 'S', "PHP Simple AMQP lib"
  30. ), "library_version" => array(
  31. 'S', "0.1"
  32. )
  33. );
  34. protected $method_map =
  35. array(
  36. "10,10" => "start", "10,20" => "secure", "10,30" => "tune",
  37. "10,41" => "open_ok", "10,50" => "redirect", "10,60" => "_close",
  38. "10,61" => "close_ok"
  39. );
  40. public function __construct($host, $port, $user, $password, $vhost = "/",
  41. $insist = false, $login_method = "AMQPLAIN", $login_response = null,
  42. $locale = "en_US", $connection_timeout = 3, $read_write_timeout = 3)
  43. {
  44. if ($user && $password) {
  45. $login_response = new Rabbit_AMQP_Serialize_Write();
  46. $login_response
  47. ->write_table(
  48. array(
  49. "LOGIN" => array(
  50. 'S', $user
  51. ), "PASSWORD" => array(
  52. 'S', $password
  53. )
  54. ));
  55. $login_response = substr($login_response->getvalue(), 4); //Skip the length
  56. } else
  57. $login_response = null;
  58. $d = Rabbit_AMQP_Connection::$LIBRARY_PROPERTIES;
  59. while (true) {
  60. $this->channels = array();
  61. // The connection object itself is treated as channel 0
  62. parent::__construct($this, 0);
  63. $this->channel_max = 65535;
  64. $this->frame_max = 131072;
  65. $errstr = $errno = null;
  66. $this->sock = null;
  67. if (!($this->sock =
  68. fsockopen($host, $port, $errno, $errstr, $connection_timeout))) {
  69. throw new Exception(
  70. "Error Connecting to server($errno): $errstr ");
  71. }
  72. stream_set_timeout($this->sock, $read_write_timeout);
  73. stream_set_blocking($this->sock, 1);
  74. $this->input = new Rabbit_AMQP_Serialize_Read(null, $this->sock);
  75. $this->write(Rabbit_AMQP_Connection::$AMQP_PROTOCOL_HEADER);
  76. $this->wait(array(
  77. "10,10"
  78. ));
  79. $this->x_start_ok($d, $login_method, $login_response, $locale);
  80. $this->wait_tune_ok = true;
  81. while ($this->wait_tune_ok) {
  82. $this
  83. ->wait(
  84. array(
  85. "10,20", // secure
  86. "10,30", // tune
  87. ));
  88. }
  89. $host = $this->x_open($vhost, "", $insist);
  90. if (!$host)
  91. return;
  92. // we weren't redirected
  93. @fclose($this->sock);
  94. $this->sock = null;
  95. }
  96. }
  97. public function __destruct()
  98. {
  99. if (isset($this->input))
  100. if ($this->input)
  101. $this->close();
  102. if ($this->sock)
  103. @fclose($this->sock);
  104. }
  105. protected function write($data)
  106. {
  107. $len = strlen($data);
  108. while (true) {
  109. if (false == ($written = fwrite($this->sock, $data))) {
  110. throw new Exception("Error sending data");
  111. }
  112. $len = $len - $written;
  113. if ($len > 0)
  114. $data = substr($data, 0 - $len);
  115. else
  116. break;
  117. }
  118. }
  119. protected function do_close()
  120. {
  121. if (isset($this->input))
  122. if ($this->input) {
  123. $this->input->close();
  124. $this->input = null;
  125. }
  126. if ($this->sock) {
  127. @fclose($this->sock);
  128. $this->sock = null;
  129. }
  130. }
  131. public function get_free_channel_id()
  132. {
  133. for ($i = 1; $i <= $this->channel_max; $i++)
  134. if (!array_key_exists($i, $this->channels))
  135. return $i;
  136. throw new Exception("No free channel ids");
  137. }
  138. public function send_content($channel, $class_id, $weight, $body_size,
  139. $packed_properties, $body)
  140. {
  141. $pkt = new Rabbit_AMQP_Serialize_Write();
  142. $pkt->write_octet(2);
  143. $pkt->write_short($channel);
  144. $pkt->write_long(strlen($packed_properties) + 12);
  145. $pkt->write_short($class_id);
  146. $pkt->write_short($weight);
  147. $pkt->write_longlong($body_size);
  148. $pkt->write($packed_properties);
  149. $pkt->write_octet(0xCE);
  150. $pkt = $pkt->getvalue();
  151. $this->write($pkt);
  152. while ($body) {
  153. $payload = substr($body, 0, $this->frame_max - 8);
  154. $body = substr($body, $this->frame_max - 8);
  155. $pkt = new Rabbit_AMQP_Serialize_Write();
  156. $pkt->write_octet(3);
  157. $pkt->write_short($channel);
  158. $pkt->write_long(strlen($payload));
  159. $pkt->write($payload);
  160. $pkt->write_octet(0xCE);
  161. $pkt = $pkt->getvalue();
  162. $this->write($pkt);
  163. }
  164. }
  165. protected function send_channel_method_frame($channel, $method_sig,
  166. $args = "")
  167. {
  168. if ($args instanceof Rabbit_AMQP_Serialize_Write)
  169. $args = $args->getvalue();
  170. $pkt = new Rabbit_AMQP_Serialize_Write();
  171. $pkt->write_octet(1);
  172. $pkt->write_short($channel);
  173. $pkt->write_long(strlen($args) + 4); // 4 = length of class_id and method_id
  174. // in payload
  175. $pkt->write_short($method_sig[0]); // class_id
  176. $pkt->write_short($method_sig[1]); // method_id
  177. $pkt->write($args);
  178. $pkt->write_octet(0xCE);
  179. $pkt = $pkt->getvalue();
  180. $this->write($pkt);
  181. }
  182. /**
  183. * Wait for a frame from the server
  184. **/
  185. protected function wait_frame()
  186. {
  187. $frame_type = $this->input->read_octet();
  188. $channel = $this->input->read_short();
  189. $size = $this->input->read_long();
  190. $payload = $this->input->read($size);
  191. $ch = $this->input->read_octet();
  192. if ($ch != 0xCE)
  193. throw new Exception(
  194. sprintf("Framing error, unexpected byte: %x", $ch));
  195. return array(
  196. $frame_type, $channel, $payload
  197. );
  198. }
  199. /**
  200. * Wait for a frame from the server destined for
  201. * a particular channel.
  202. **/
  203. protected function wait_channel($channel_id)
  204. {
  205. while (true) {
  206. list($frame_type, $frame_channel, $payload) = $this->wait_frame();
  207. if ($frame_channel == $channel_id)
  208. return array(
  209. $frame_type, $payload
  210. );
  211. // Not the channel we were looking for. Queue this frame
  212. //for later, when the other channel is looking for frames.
  213. array_push($this->channels[$frame_channel]->frame_queue,
  214. array(
  215. $frame_type, $payload
  216. ));
  217. // If we just queued up a method for channel 0 (the Connection
  218. // itself) it's probably a close method in reaction to some
  219. // error, so deal with it right away.
  220. if (($frame_type == 1) && ($frame_channel == 0))
  221. $this->wait();
  222. }
  223. }
  224. /**
  225. * Fetch a Channel object identified by the numeric channel_id, or
  226. * create that object if it doesn't already exist.
  227. **/
  228. public function channel($channel_id = null)
  229. {
  230. if (array_key_exists($channel_id, $this->channels))
  231. return $this->channels[$channel_id];
  232. return new Rabbit_AMQP_Channel($this->connection, $channel_id);
  233. }
  234. /**
  235. * request a connection close
  236. **/
  237. public function close($reply_code = 0, $reply_text = "",
  238. $method_sig = array(
  239. 0, 0
  240. ))
  241. {
  242. $args = new Rabbit_AMQP_Serialize_Write();
  243. $args->write_short($reply_code);
  244. $args->write_shortstr($reply_text);
  245. $args->write_short($method_sig[0]); // class_id
  246. $args->write_short($method_sig[1]); // method_id
  247. $this->send_method_frame(array(
  248. 10, 60
  249. ), $args);
  250. return $this->wait(array(
  251. "10,61"
  252. ));
  253. }
  254. protected function _close($args)
  255. {
  256. $reply_code = $args->read_short();
  257. $reply_text = $args->read_shortstr();
  258. $class_id = $args->read_short();
  259. $method_id = $args->read_short();
  260. $this->x_close_ok();
  261. throw new Exception($reply_text);
  262. //Rabbit_AMQP_ConnectionException($reply_code, $reply_text, array($class_id, $method_id));
  263. }
  264. /**
  265. * confirm a connection close
  266. **/
  267. protected function x_close_ok()
  268. {
  269. $this->send_method_frame(array(
  270. 10, 61
  271. ));
  272. $this->do_close();
  273. }
  274. /**
  275. * confirm a connection close
  276. **/
  277. protected function close_ok($args)
  278. {
  279. $this->do_close();
  280. }
  281. protected function x_open($virtual_host, $capabilities = "", $insist = false)
  282. {
  283. $args = new Rabbit_AMQP_Serialize_Write();
  284. $args->write_shortstr($virtual_host);
  285. $args->write_shortstr($capabilities);
  286. $args->write_bit($insist);
  287. $this->send_method_frame(array(
  288. 10, 40
  289. ), $args);
  290. return $this
  291. ->wait(
  292. array(
  293. "10,41", // Connection.open_ok
  294. "10,50" // Connection.redirect
  295. ));
  296. }
  297. /**
  298. * signal that the connection is ready
  299. **/
  300. protected function open_ok($args)
  301. {
  302. $this->known_hosts = $args->read_shortstr();
  303. return null;
  304. }
  305. /**
  306. * asks the client to use a different server
  307. **/
  308. protected function redirect($args)
  309. {
  310. $host = $args->read_shortstr();
  311. $this->known_hosts = $args->read_shortstr();
  312. return $host;
  313. }
  314. /**
  315. * security mechanism challenge
  316. **/
  317. protected function secure($args)
  318. {
  319. $challenge = $args->read_longstr();
  320. }
  321. /**
  322. * security mechanism response
  323. **/
  324. protected function x_secure_ok($response)
  325. {
  326. $args = new Rabbit_AMQP_Serialize_Write();
  327. $args->write_longstr($response);
  328. $this->send_method_frame(array(
  329. 10, 21
  330. ), $args);
  331. }
  332. /**
  333. * start connection negotiation
  334. **/
  335. protected function start($args)
  336. {
  337. $this->version_major = $args->read_octet();
  338. $this->version_minor = $args->read_octet();
  339. $this->server_properties = $args->read_table();
  340. $this->mechanisms = explode(" ", $args->read_longstr());
  341. $this->locales = explode(" ", $args->read_longstr());
  342. }
  343. protected function x_start_ok($client_properties, $mechanism, $response,
  344. $locale)
  345. {
  346. $args = new Rabbit_AMQP_Serialize_Write();
  347. $args->write_table($client_properties);
  348. $args->write_shortstr($mechanism);
  349. $args->write_longstr($response);
  350. $args->write_shortstr($locale);
  351. $this->send_method_frame(array(
  352. 10, 11
  353. ), $args);
  354. }
  355. /**
  356. * propose connection tuning parameters
  357. **/
  358. protected function tune($args)
  359. {
  360. $v = $args->read_short();
  361. if ($v)
  362. $this->channel_max = $v;
  363. $v = $args->read_long();
  364. if ($v)
  365. $this->frame_max = $v;
  366. $this->heartbeat = $args->read_short();
  367. $this->x_tune_ok($this->channel_max, $this->frame_max, 0);
  368. }
  369. /**
  370. * negotiate connection tuning parameters
  371. **/
  372. protected function x_tune_ok($channel_max, $frame_max, $heartbeat)
  373. {
  374. $args = new Rabbit_AMQP_Serialize_Write();
  375. $args->write_short($channel_max);
  376. $args->write_long($frame_max);
  377. $args->write_short($heartbeat);
  378. $this->send_method_frame(array(
  379. 10, 31
  380. ), $args);
  381. $this->wait_tune_ok = False;
  382. }
  383. }