├── .gitignore ├── CREDITS ├── LICENSE ├── README.md ├── amqp.inc ├── amqp_test.php ├── amqp_wire.inc ├── benchmark ├── config.php ├── consumer.php └── producer.php ├── demo ├── amqp_consumer.php ├── amqp_publisher.php ├── config.php └── ssl_connection.php └── hexdump.inc /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .buildpath 3 | .settings/ 4 | -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- 1 | 2 | Following people contributed to this project: 3 | 4 | Barry Pederson - author of original Python lib 5 | Vadim Zaliva - PHP paort 6 | taavi013@gmail.com patches - patches 7 | Sean Murphy http://code.google.com/u/sgmurphy/ - patches 8 | spiderbill http://code.google.com/u/spiderbill/ - patches 9 | 10 | 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MOVED # 2 | 3 | The development of this project has moved here: [https://github.com/videlalvaro/php-amqplib](https://github.com/videlalvaro/php-amqplib). 4 | 5 | # NOTE # 6 | 7 | This library is a fork of the [php-amqplib](http://code.google.com/p/php-amqplib/) library. 8 | 9 | We modified that library in order to work with PHP 5.3 Strict. 10 | 11 | Also we improved the debug method to increase performance. 12 | 13 | We use it daily in prod for sending/consuming 600K + messages per day. 14 | 15 | Below is the original README file content. Credits goes to the original authors. 16 | 17 | ## Usage ## 18 | 19 | Start your RabbitMQ server, then get the source: 20 | 21 | $ git clone git://github.com/tnc/php-amqplib.git 22 | 23 | Open two Terminals and on the first one execute the following commands to start the consumer: 24 | 25 | $ cd php-amqplib/demo 26 | $ php amqp_consumer.php 27 | 28 | Then on the other Terminal do: 29 | 30 | $ cd php-amqplib/demo 31 | $ php amqp_publisher.php some text to publish 32 | 33 | You should see the message arriving to the process on the other Terminal 34 | 35 | Then to stop the consumer, send to it the `quit` message: 36 | 37 | $ php amqp_publisher.php quit 38 | 39 | # Debugging # 40 | 41 | If you want to know what's going on at a protocol level then add the following constant to your code: 42 | 43 | 49 | 50 | 51 | # Original README: # 52 | 53 | PHP library implementing Advanced Message Queuing Protocol (AMQP). 54 | 55 | The library is port of python code of py-amqplib 56 | http://barryp.org/software/py-amqplib/ 57 | 58 | It have been tested with RabbitMQ server. 59 | 60 | Project home page: http://code.google.com/p/php-amqplib/ 61 | 62 | For discussion, please join the group: 63 | 64 | http://groups.google.com/group/php-amqplib-devel 65 | 66 | For bug reports, please use bug tracking system at the project page. 67 | 68 | Patched are very welcome! 69 | 70 | Author: Vadim Zaliva 71 | 72 | 73 | -------------------------------------------------------------------------------- /amqp.inc: -------------------------------------------------------------------------------- 1 | 7 | * 8 | */ 9 | 10 | require_once('amqp_wire.inc'); 11 | require_once('hexdump.inc'); 12 | 13 | function debug_msg($s) 14 | { 15 | echo $s, "\n"; 16 | } 17 | 18 | function methodSig($a) 19 | { 20 | if(is_string($a)) 21 | return $a; 22 | else 23 | return sprintf("%d,%d",$a[0] ,$a[1]); 24 | } 25 | 26 | 27 | class AMQPException extends Exception 28 | { 29 | public function __construct($reply_code, $reply_text, $method_sig) 30 | { 31 | parent::__construct($reply_text,$reply_code); 32 | 33 | $this->amqp_reply_code = $reply_code; // redundant, but kept for BC 34 | $this->amqp_reply_text = $reply_text; // redundant, but kept for BC 35 | $this->amqp_method_sig = $method_sig; 36 | 37 | $ms=methodSig($method_sig); 38 | if(array_key_exists($ms, AbstractChannel::$GLOBAL_METHOD_NAMES)) 39 | $mn = AbstractChannel::$GLOBAL_METHOD_NAMES[$ms]; 40 | else 41 | $mn = ""; 42 | $this->args = array( 43 | $reply_code, 44 | $reply_text, 45 | $method_sig, 46 | $mn 47 | ); 48 | } 49 | } 50 | 51 | class AMQPConnectionException extends AMQPException 52 | { 53 | public function __construct($reply_code, $reply_text, $method_sig) 54 | { 55 | parent::__construct($reply_code, $reply_text, $method_sig); 56 | } 57 | } 58 | 59 | class AMQPChannelException extends AMQPException 60 | { 61 | public function __construct($reply_code, $reply_text, $method_sig) 62 | { 63 | parent::__construct($reply_code, $reply_text, $method_sig); 64 | } 65 | 66 | } 67 | 68 | class AbstractChannel 69 | { 70 | private static $CONTENT_METHODS = array( 71 | "60,60", // Basic.deliver 72 | "60,71", // Basic.get_ok 73 | ); 74 | 75 | private static $CLOSE_METHODS = array( 76 | "10,60", // Connection.close 77 | "20,40", // Channel.close 78 | ); 79 | 80 | // All the method names 81 | public static $GLOBAL_METHOD_NAMES = array( 82 | "10,10" => "Connection.start", 83 | "10,11" => "Connection.start_ok", 84 | "10,20" => "Connection.secure", 85 | "10,21" => "Connection.secure_ok", 86 | "10,30" => "Connection.tune", 87 | "10,31" => "Connection.tune_ok", 88 | "10,40" => "Connection.open", 89 | "10,41" => "Connection.open_ok", 90 | "10,50" => "Connection.redirect", 91 | "10,60" => "Connection.close", 92 | "10,61" => "Connection.close_ok", 93 | "20,10" => "Channel.open", 94 | "20,11" => "Channel.open_ok", 95 | "20,20" => "Channel.flow", 96 | "20,21" => "Channel.flow_ok", 97 | "20,30" => "Channel.alert", 98 | "20,40" => "Channel.close", 99 | "20,41" => "Channel.close_ok", 100 | "30,10" => "Channel.access_request", 101 | "30,11" => "Channel.access_request_ok", 102 | "40,10" => "Channel.exchange_declare", 103 | "40,11" => "Channel.exchange_declare_ok", 104 | "40,20" => "Channel.exchange_delete", 105 | "40,21" => "Channel.exchange_delete_ok", 106 | "50,10" => "Channel.queue_declare", 107 | "50,11" => "Channel.queue_declare_ok", 108 | "50,20" => "Channel.queue_bind", 109 | "50,21" => "Channel.queue_bind_ok", 110 | "50,30" => "Channel.queue_purge", 111 | "50,31" => "Channel.queue_purge_ok", 112 | "50,40" => "Channel.queue_delete", 113 | "50,41" => "Channel.queue_delete_ok", 114 | "50,50" => "Channel.queue_unbind", 115 | "50,51" => "Channel.queue_unbind_ok", 116 | "60,10" => "Channel.basic_qos", 117 | "60,11" => "Channel.basic_qos_ok", 118 | "60,20" => "Channel.basic_consume", 119 | "60,21" => "Channel.basic_consume_ok", 120 | "60,30" => "Channel.basic_cancel", 121 | "60,31" => "Channel.basic_cancel_ok", 122 | "60,40" => "Channel.basic_publish", 123 | "60,50" => "Channel.basic_return", 124 | "60,60" => "Channel.basic_deliver", 125 | "60,70" => "Channel.basic_get", 126 | "60,71" => "Channel.basic_get_ok", 127 | "60,72" => "Channel.basic_get_empty", 128 | "60,80" => "Channel.basic_ack", 129 | "60,90" => "Channel.basic_reject", 130 | "60,100" => "Channel.basic_recover", 131 | "90,10" => "Channel.tx_select", 132 | "90,11" => "Channel.tx_select_ok", 133 | "90,20" => "Channel.tx_commit", 134 | "90,21" => "Channel.tx_commit_ok", 135 | "90,30" => "Channel.tx_rollback", 136 | "90,31" => "Channel.tx_rollback_ok" 137 | ); 138 | 139 | protected $debug; 140 | 141 | public function __construct($connection, $channel_id) 142 | { 143 | $this->connection = $connection; 144 | $this->channel_id = $channel_id; 145 | $connection->channels[$channel_id] = $this; 146 | $this->frame_queue = array(); // Lower level queue for frames 147 | $this->method_queue = array(); // Higher level queue for methods 148 | $this->auto_decode = false; 149 | $this->debug = defined('AMQP_DEBUG') ? AMQP_DEBUG : false; 150 | } 151 | 152 | public function getChannelId() 153 | { 154 | return $this->channel_id; 155 | } 156 | 157 | 158 | function dispatch($method_sig, $args, $content) 159 | { 160 | if(!array_key_exists($method_sig, $this->method_map)) 161 | throw new Exception("Unknown AMQP method $method_sig"); 162 | 163 | $amqp_method = $this->method_map[$method_sig]; 164 | if($content == NULL) 165 | return call_user_func(array($this,$amqp_method), $args); 166 | else 167 | return call_user_func(array($this,$amqp_method), $args, $content); 168 | } 169 | 170 | function next_frame() 171 | { 172 | if($this->debug) 173 | { 174 | debug_msg("waiting for a new frame"); 175 | } 176 | if($this->frame_queue != NULL) 177 | return array_pop($this->frame_queue); 178 | return $this->connection->wait_channel($this->channel_id); 179 | } 180 | 181 | protected function send_method_frame($method_sig, $args="") 182 | { 183 | $this->connection->send_channel_method_frame($this->channel_id, $method_sig, $args); 184 | } 185 | 186 | function wait_content() 187 | { 188 | $frm = $this->next_frame(); 189 | $frame_type = $frm[0]; 190 | $payload = $frm[1]; 191 | if($frame_type != 2) 192 | throw new Exception("Expecting Content header"); 193 | 194 | $payload_reader = new AMQPReader(substr($payload,0,12)); 195 | $class_id = $payload_reader->read_short(); 196 | $weight = $payload_reader->read_short(); 197 | 198 | $body_size = $payload_reader->read_longlong(); 199 | $msg = new AMQPMessage(); 200 | $msg->load_properties(substr($payload,12)); 201 | 202 | $body_parts = array(); 203 | $body_received = 0; 204 | while(bccomp($body_size,$body_received)==1) 205 | { 206 | $frm = $this->next_frame(); 207 | $frame_type = $frm[0]; 208 | $payload = $frm[1]; 209 | if($frame_type != 3) 210 | throw new Exception("Expecting Content body, received frame type $frame_type"); 211 | $body_parts[] = $payload; 212 | $body_received = bcadd($body_received, strlen($payload)); 213 | } 214 | 215 | $msg->body = implode("",$body_parts); 216 | 217 | if($this->auto_decode and isset($msg->content_encoding)) 218 | { 219 | try 220 | { 221 | $msg->body = $msg->body->decode($msg->content_encoding); 222 | } catch (Exception $e) { 223 | if($this->debug) 224 | { 225 | debug_msg("Ignoring body decoding exception: " . $e->getMessage()); 226 | } 227 | } 228 | } 229 | 230 | return $msg; 231 | } 232 | 233 | /** 234 | * Wait for some expected AMQP methods and dispatch to them. 235 | * Unexpected methods are queued up for later calls to this Python 236 | * method. 237 | */ 238 | public function wait($allowed_methods=NULL) 239 | { 240 | if($allowed_methods) 241 | { 242 | if($this->debug) 243 | { 244 | debug_msg("waiting for " . implode(", ", $allowed_methods)); 245 | } 246 | } 247 | else 248 | { 249 | if($this->debug) 250 | { 251 | debug_msg("waiting for any method"); 252 | } 253 | } 254 | 255 | //Process deferred methods 256 | foreach($this->method_queue as $qk=>$queued_method) 257 | { 258 | if($this->debug) 259 | { 260 | debug_msg("checking queue method " . $qk); 261 | } 262 | 263 | $method_sig = $queued_method[0]; 264 | if($allowed_methods==NULL || in_array($method_sig, $allowed_methods)) 265 | { 266 | unset($this->method_queue[$qk]); 267 | 268 | if($this->debug) 269 | { 270 | debug_msg("Executing queued method: $method_sig: " . AbstractChannel::$GLOBAL_METHOD_NAMES[methodSig($method_sig)]); 271 | } 272 | 273 | return $this->dispatch($queued_method[0], 274 | $queued_method[1], 275 | $queued_method[2]); 276 | } 277 | } 278 | 279 | // No deferred methods? wait for new ones 280 | while(true) 281 | { 282 | $frm = $this->next_frame(); 283 | $frame_type = $frm[0]; 284 | $payload = $frm[1]; 285 | 286 | if($frame_type != 1) 287 | throw new Exception("Expecting AMQP method, received frame type: $frame_type"); 288 | 289 | if(strlen($payload) < 4) 290 | throw new Exception("Method frame too short"); 291 | 292 | $method_sig_array = unpack("n2", substr($payload,0,4)); 293 | $method_sig = "" . $method_sig_array[1] . "," . $method_sig_array[2]; 294 | $args = new AMQPReader(substr($payload,4)); 295 | 296 | if($this->debug) 297 | { 298 | debug_msg("> $method_sig: " . AbstractChannel::$GLOBAL_METHOD_NAMES[methodSig($method_sig)]); 299 | } 300 | 301 | 302 | if(in_array($method_sig, AbstractChannel::$CONTENT_METHODS)) 303 | $content = $this->wait_content(); 304 | else 305 | $content = NULL; 306 | 307 | if($allowed_methods==NULL || 308 | in_array($method_sig,$allowed_methods) || 309 | in_array($method_sig,AbstractChannel::$CLOSE_METHODS)) 310 | { 311 | return $this->dispatch($method_sig, $args, $content); 312 | } 313 | 314 | // Wasn't what we were looking for? save it for later 315 | if($this->debug) 316 | { 317 | debug_msg("Queueing for later: $method_sig: " . AbstractChannel::$GLOBAL_METHOD_NAMES[methodSig($method_sig)]); 318 | } 319 | array_push($this->method_queue,array($method_sig, $args, $content)); 320 | } 321 | } 322 | 323 | } 324 | 325 | class AMQPSSLConnection extends AMQPConnection 326 | { 327 | public function __construct($host, $port, 328 | $user, $password, 329 | $vhost="/", $ssl_options = array(), $options = array()) 330 | { 331 | $ssl_context = empty($ssl_options) ? null : $this->create_ssl_context($ssl_options); 332 | 333 | parent::__construct($host, $port, $user, $password, $vhost="/", 334 | isset($options['insist']) ? $options['insist'] : false, 335 | isset($options['login_method']) ? $options['login_method'] : "AMQPLAIN", 336 | isset($options['login_response']) ? $options['login_response'] : null, 337 | isset($options['locale']) ? $options['locale'] : "en_US", 338 | isset($options['connection_timeout']) ? $options['connection_timeout'] : 3, 339 | isset($options['read_write_timeout']) ? $options['read_write_timeout'] : 3, 340 | $ssl_context); 341 | } 342 | 343 | private function create_ssl_context($options) 344 | { 345 | $ssl_context = stream_context_create(); 346 | foreach ($options as $k => $v) { 347 | stream_context_set_option($ssl_context, 'ssl', $k, $v); 348 | } 349 | return $ssl_context; 350 | } 351 | } 352 | 353 | class AMQPConnection extends AbstractChannel 354 | { 355 | public static $AMQP_PROTOCOL_HEADER = "AMQP\x01\x01\x09\x01"; 356 | 357 | public static $LIBRARY_PROPERTIES = array( 358 | "library" => array('S', "PHP Simple AMQP lib"), 359 | "library_version" => array('S', "0.1") 360 | ); 361 | 362 | protected $method_map = array( 363 | "10,10" => "start", 364 | "10,20" => "secure", 365 | "10,30" => "tune", 366 | "10,41" => "open_ok", 367 | "10,50" => "redirect", 368 | "10,60" => "_close", 369 | "10,61" => "close_ok" 370 | ); 371 | 372 | public function __construct($host, $port, 373 | $user, $password, 374 | $vhost="/",$insist=false, 375 | $login_method="AMQPLAIN", 376 | $login_response=NULL, 377 | $locale="en_US", 378 | $connection_timeout = 3, 379 | $read_write_timeout = 3, 380 | $context = null) 381 | { 382 | 383 | if($user && $password) 384 | { 385 | $login_response = new AMQPWriter(); 386 | $login_response->write_table(array("LOGIN" => array('S',$user), 387 | "PASSWORD" => array('S',$password))); 388 | $login_response = substr($login_response->getvalue(),4); //Skip the length 389 | } else 390 | $login_response = NULL; 391 | 392 | 393 | $d = AMQPConnection::$LIBRARY_PROPERTIES; 394 | while(true) 395 | { 396 | $this->channels = array(); 397 | // The connection object itself is treated as channel 0 398 | parent::__construct($this, 0); 399 | 400 | $this->channel_max = 65535; 401 | $this->frame_max = 131072; 402 | 403 | $errstr = $errno = NULL; 404 | $this->sock = NULL; 405 | 406 | //TODO clean up 407 | if($context) 408 | { 409 | $remote = sprintf('ssl://%s:%s', $host, $port); 410 | $this->sock = stream_socket_client($remote, $errno, $errstr, 60, STREAM_CLIENT_CONNECT, $context); 411 | } 412 | else 413 | { 414 | $remote = sprintf('tcp://%s:%s', $host, $port); 415 | $this->sock = stream_socket_client($remote, $errno, $errstr, 60, STREAM_CLIENT_CONNECT); 416 | } 417 | 418 | if (!$this->sock) 419 | { 420 | throw new Exception ("Error Connecting to server($errno): $errstr "); 421 | } 422 | 423 | stream_set_timeout($this->sock, $read_write_timeout); 424 | stream_set_blocking($this->sock, 1); 425 | $this->input = new AMQPReader(null, $this->sock); 426 | 427 | $this->write(AMQPConnection::$AMQP_PROTOCOL_HEADER); 428 | $this->wait(array("10,10")); 429 | $this->x_start_ok($d, $login_method, $login_response, $locale); 430 | 431 | $this->wait_tune_ok = true; 432 | while($this->wait_tune_ok) 433 | { 434 | $this->wait(array( 435 | "10,20", // secure 436 | "10,30", // tune 437 | )); 438 | } 439 | 440 | $host = $this->x_open($vhost,"", $insist); 441 | if(!$host) 442 | return; // we weren't redirected 443 | 444 | // we were redirected, close the socket, loop and try again 445 | if($this->debug) 446 | { 447 | debug_msg("closing socket"); 448 | } 449 | 450 | @fclose($this->sock); $this->sock=NULL; 451 | } 452 | } 453 | 454 | public function __destruct() 455 | { 456 | if(isset($this->input)) 457 | if($this->input) 458 | $this->close(); 459 | 460 | if(is_resource($this->sock)) 461 | { 462 | if($this->debug) 463 | { 464 | debug_msg("closing socket"); 465 | } 466 | 467 | @fclose($this->sock); 468 | } 469 | } 470 | 471 | protected function write($data) 472 | { 473 | if($this->debug) 474 | { 475 | debug_msg("< [hex]:\n" . hexdump($data, $htmloutput = false, $uppercase = true, $return = true)); 476 | } 477 | 478 | $len = strlen($data); 479 | while(true) 480 | { 481 | if(false === ($written = fwrite($this->sock, $data))) 482 | { 483 | throw new Exception ("Error sending data"); 484 | } 485 | $len = $len - $written; 486 | if($len>0) 487 | $data=substr($data,0-$len); 488 | else 489 | break; 490 | } 491 | } 492 | 493 | protected function do_close() 494 | { 495 | if(isset($this->input)) 496 | if($this->input) 497 | { 498 | $this->input->close(); 499 | $this->input = NULL; 500 | } 501 | 502 | if(is_resource($this->sock)) 503 | { 504 | if($this->debug) 505 | { 506 | debug_msg("closing socket"); 507 | } 508 | 509 | @fclose($this->sock); 510 | $this->sock = NULL; 511 | } 512 | } 513 | 514 | public function get_free_channel_id() 515 | { 516 | for($i=1;$i<=$this->channel_max;$i++) 517 | if(!array_key_exists($i,$this->channels)) 518 | return $i; 519 | throw new Exception("No free channel ids"); 520 | } 521 | 522 | public function send_content($channel, $class_id, $weight, $body_size, 523 | $packed_properties, $body) 524 | { 525 | $pkt = new AMQPWriter(); 526 | 527 | $pkt->write_octet(2); 528 | $pkt->write_short($channel); 529 | $pkt->write_long(strlen($packed_properties)+12); 530 | 531 | $pkt->write_short($class_id); 532 | $pkt->write_short($weight); 533 | $pkt->write_longlong($body_size); 534 | $pkt->write($packed_properties); 535 | 536 | $pkt->write_octet(0xCE); 537 | $pkt = $pkt->getvalue(); 538 | $this->write($pkt); 539 | 540 | while($body) 541 | { 542 | $payload = substr($body,0, $this->frame_max-8); 543 | $body = substr($body,$this->frame_max-8); 544 | $pkt = new AMQPWriter(); 545 | 546 | $pkt->write_octet(3); 547 | $pkt->write_short($channel); 548 | $pkt->write_long(strlen($payload)); 549 | 550 | $pkt->write($payload); 551 | 552 | $pkt->write_octet(0xCE); 553 | $pkt = $pkt->getvalue(); 554 | $this->write($pkt); 555 | } 556 | } 557 | 558 | protected function send_channel_method_frame($channel, $method_sig, $args="") 559 | { 560 | if($args instanceof AMQPWriter) 561 | $args = $args->getvalue(); 562 | 563 | $pkt = new AMQPWriter(); 564 | 565 | $pkt->write_octet(1); 566 | $pkt->write_short($channel); 567 | $pkt->write_long(strlen($args)+4); // 4 = length of class_id and method_id 568 | // in payload 569 | 570 | $pkt->write_short($method_sig[0]); // class_id 571 | $pkt->write_short($method_sig[1]); // method_id 572 | $pkt->write($args); 573 | 574 | $pkt->write_octet(0xCE); 575 | $pkt = $pkt->getvalue(); 576 | $this->write($pkt); 577 | 578 | if($this->debug) 579 | { 580 | debug_msg("< " . methodSig($method_sig) . ": " . AbstractChannel::$GLOBAL_METHOD_NAMES[methodSig($method_sig)]); 581 | } 582 | 583 | } 584 | 585 | /** 586 | * Wait for a frame from the server 587 | */ 588 | protected function wait_frame() 589 | { 590 | $frame_type = $this->input->read_octet(); 591 | $channel = $this->input->read_short(); 592 | $size = $this->input->read_long(); 593 | $payload = $this->input->read($size); 594 | 595 | $ch = $this->input->read_octet(); 596 | if($ch != 0xCE) 597 | throw new Exception(sprintf("Framing error, unexpected byte: %x", $ch)); 598 | 599 | return array($frame_type, $channel, $payload); 600 | } 601 | 602 | /** 603 | * Wait for a frame from the server destined for 604 | * a particular channel. 605 | */ 606 | protected function wait_channel($channel_id) 607 | { 608 | while(true) 609 | { 610 | list($frame_type, $frame_channel, $payload) = $this->wait_frame(); 611 | if($frame_channel == $channel_id) 612 | return array($frame_type, $payload); 613 | 614 | // Not the channel we were looking for. Queue this frame 615 | //for later, when the other channel is looking for frames. 616 | array_push($this->channels[$frame_channel]->frame_queue, 617 | array($frame_type, $payload)); 618 | 619 | // If we just queued up a method for channel 0 (the Connection 620 | // itself) it's probably a close method in reaction to some 621 | // error, so deal with it right away. 622 | if(($frame_type == 1) && ($frame_channel == 0)) 623 | $this->wait(); 624 | } 625 | } 626 | 627 | /** 628 | * Fetch a Channel object identified by the numeric channel_id, or 629 | * create that object if it doesn't already exist. 630 | */ 631 | public function channel($channel_id=NULL) 632 | { 633 | if(array_key_exists($channel_id,$this->channels)) 634 | return $this->channels[$channel_id]; 635 | 636 | return new AMQPChannel($this->connection, $channel_id); 637 | } 638 | 639 | /** 640 | * request a connection close 641 | */ 642 | public function close($reply_code=0, $reply_text="", $method_sig=array(0, 0)) 643 | { 644 | $args = new AMQPWriter(); 645 | $args->write_short($reply_code); 646 | $args->write_shortstr($reply_text); 647 | $args->write_short($method_sig[0]); // class_id 648 | $args->write_short($method_sig[1]); // method_id 649 | $this->send_method_frame(array(10, 60), $args); 650 | return $this->wait(array( 651 | "10,61", // Connection.close_ok 652 | )); 653 | } 654 | 655 | public static function dump_table($table) 656 | { 657 | $tokens = array(); 658 | foreach ($table as $name => $value) 659 | { 660 | switch ($value[0]) 661 | { 662 | case 'D': 663 | $val = $value[1]->n . 'E' . $value[1]->e; 664 | break; 665 | case 'F': 666 | $val = '(' . self::dump_table($value[1]) . ')'; 667 | break; 668 | case 'T': 669 | $val = date('Y-m-d H:i:s', $value[1]); 670 | break; 671 | default: 672 | $val = $value[1]; 673 | } 674 | $tokens[] = $name . '=' . $val; 675 | } 676 | return implode(', ', $tokens); 677 | 678 | } 679 | 680 | protected function _close($args) 681 | { 682 | $reply_code = $args->read_short(); 683 | $reply_text = $args->read_shortstr(); 684 | $class_id = $args->read_short(); 685 | $method_id = $args->read_short(); 686 | 687 | $this->x_close_ok(); 688 | 689 | throw new AMQPConnectionException($reply_code, $reply_text, array($class_id, $method_id)); 690 | } 691 | 692 | 693 | /** 694 | * confirm a connection close 695 | */ 696 | protected function x_close_ok() 697 | { 698 | $this->send_method_frame(array(10, 61)); 699 | $this->do_close(); 700 | } 701 | 702 | /** 703 | * confirm a connection close 704 | */ 705 | protected function close_ok($args) 706 | { 707 | $this->do_close(); 708 | } 709 | 710 | protected function x_open($virtual_host, $capabilities="", $insist=false) 711 | { 712 | $args = new AMQPWriter(); 713 | $args->write_shortstr($virtual_host); 714 | $args->write_shortstr($capabilities); 715 | $args->write_bit($insist); 716 | $this->send_method_frame(array(10, 40), $args); 717 | return $this->wait(array( 718 | "10,41", // Connection.open_ok 719 | "10,50" // Connection.redirect 720 | )); 721 | } 722 | 723 | 724 | /** 725 | * signal that the connection is ready 726 | */ 727 | protected function open_ok($args) 728 | { 729 | $this->known_hosts = $args->read_shortstr(); 730 | if($this->debug) 731 | { 732 | debug_msg("Open OK! known_hosts: " . $this->known_hosts); 733 | } 734 | 735 | return NULL; 736 | } 737 | 738 | 739 | /** 740 | * asks the client to use a different server 741 | */ 742 | protected function redirect($args) 743 | { 744 | $host = $args->read_shortstr(); 745 | $this->known_hosts = $args->read_shortstr(); 746 | if($this->debug) 747 | { 748 | debug_msg("Redirected to [". $host . "], known_hosts [" . $this->known_hosts . "]" ); 749 | } 750 | return $host; 751 | } 752 | 753 | /** 754 | * security mechanism challenge 755 | */ 756 | protected function secure($args) 757 | { 758 | $challenge = $args->read_longstr(); 759 | } 760 | 761 | /** 762 | * security mechanism response 763 | */ 764 | protected function x_secure_ok($response) 765 | { 766 | $args = new AMQPWriter(); 767 | $args->write_longstr($response); 768 | $this->send_method_frame(array(10, 21), $args); 769 | } 770 | 771 | /** 772 | * start connection negotiation 773 | */ 774 | protected function start($args) 775 | { 776 | $this->version_major = $args->read_octet(); 777 | $this->version_minor = $args->read_octet(); 778 | $this->server_properties = $args->read_table(); 779 | $this->mechanisms = explode(" ", $args->read_longstr()); 780 | $this->locales = explode(" ", $args->read_longstr()); 781 | 782 | if($this->debug) 783 | { 784 | debug_msg(sprintf("Start from server, version: %d.%d, properties: %s, mechanisms: %s, locales: %s", 785 | $this->version_major, 786 | $this->version_minor, 787 | self::dump_table($this->server_properties), 788 | implode(', ', $this->mechanisms), 789 | implode(', ', $this->locales))); 790 | } 791 | 792 | } 793 | 794 | 795 | protected function x_start_ok($client_properties, $mechanism, $response, $locale) 796 | { 797 | $args = new AMQPWriter(); 798 | $args->write_table($client_properties); 799 | $args->write_shortstr($mechanism); 800 | $args->write_longstr($response); 801 | $args->write_shortstr($locale); 802 | $this->send_method_frame(array(10, 11), $args); 803 | } 804 | 805 | /** 806 | * propose connection tuning parameters 807 | */ 808 | protected function tune($args) 809 | { 810 | $v=$args->read_short(); 811 | if($v) 812 | $this->channel_max = $v; 813 | $v=$args->read_long(); 814 | if($v) 815 | $this->frame_max = $v; 816 | $this->heartbeat = $args->read_short(); 817 | 818 | $this->x_tune_ok($this->channel_max, $this->frame_max, 0); 819 | } 820 | 821 | /** 822 | * negotiate connection tuning parameters 823 | */ 824 | protected function x_tune_ok($channel_max, $frame_max, $heartbeat) 825 | { 826 | $args = new AMQPWriter(); 827 | $args->write_short($channel_max); 828 | $args->write_long($frame_max); 829 | $args->write_short($heartbeat); 830 | $this->send_method_frame(array(10, 31), $args); 831 | $this->wait_tune_ok = False; 832 | } 833 | 834 | } 835 | 836 | class AMQPChannel extends AbstractChannel 837 | { 838 | protected $method_map = array( 839 | "20,11" => "open_ok", 840 | "20,20" => "flow", 841 | "20,21" => "flow_ok", 842 | "20,30" => "alert", 843 | "20,40" => "_close", 844 | "20,41" => "close_ok", 845 | "30,11" => "access_request_ok", 846 | "40,11" => "exchange_declare_ok", 847 | "40,21" => "exchange_delete_ok", 848 | "50,11" => "queue_declare_ok", 849 | "50,21" => "queue_bind_ok", 850 | "50,31" => "queue_purge_ok", 851 | "50,41" => "queue_delete_ok", 852 | "50,51" => "queue_unbind_ok", 853 | "60,11" => "basic_qos_ok", 854 | "60,21" => "basic_consume_ok", 855 | "60,31" => "basic_cancel_ok", 856 | "60,50" => "basic_return", 857 | "60,60" => "basic_deliver", 858 | "60,71" => "basic_get_ok", 859 | "60,72" => "basic_get_empty", 860 | "90,11" => "tx_select_ok", 861 | "90,21" => "tx_commit_ok", 862 | "90,31" => "tx_rollback_ok" 863 | ); 864 | 865 | public function __construct($connection, 866 | $channel_id=NULL, 867 | $auto_decode=true) 868 | { 869 | 870 | if($channel_id == NULL) 871 | $channel_id = $connection->get_free_channel_id(); 872 | 873 | parent::__construct($connection, $channel_id); 874 | 875 | if($this->debug) 876 | { 877 | debug_msg("using channel_id: " . $channel_id); 878 | } 879 | 880 | $this->default_ticket = 0; 881 | $this->is_open = false; 882 | $this->active = true; // Flow control 883 | $this->alerts = array(); 884 | $this->callbacks = array(); 885 | $this->auto_decode = $auto_decode; 886 | 887 | $this->x_open(); 888 | } 889 | 890 | public function __destruct() 891 | { 892 | //TODO:???if($this->connection) 893 | // $this->close("destroying channel"); 894 | } 895 | 896 | /** 897 | * Tear down this object, after we've agreed to close with the server. 898 | */ 899 | protected function do_close() 900 | { 901 | $this->is_open = false; 902 | unset($this->connection->channels[$this->channel_id]); 903 | $this->channel_id = $this->connection = NULL; 904 | } 905 | 906 | /** 907 | * This method allows the server to send a non-fatal warning to 908 | * the client. This is used for methods that are normally 909 | * asynchronous and thus do not have confirmations, and for which 910 | * the server may detect errors that need to be reported. Fatal 911 | * errors are handled as channel or connection exceptions; non- 912 | * fatal errors are sent through this method. 913 | */ 914 | protected function alert($args) 915 | { 916 | $reply_code = $args->read_short(); 917 | $reply_text = $args->read_shortstr(); 918 | $details = $args->read_table(); 919 | 920 | array_push($this->alerts,array($reply_code, $reply_text, $details)); 921 | } 922 | 923 | /** 924 | * request a channel close 925 | */ 926 | public function close($reply_code=0, 927 | $reply_text="", 928 | $method_sig=array(0, 0)) 929 | { 930 | $args = new AMQPWriter(); 931 | $args->write_short($reply_code); 932 | $args->write_shortstr($reply_text); 933 | $args->write_short($method_sig[0]); // class_id 934 | $args->write_short($method_sig[1]); // method_id 935 | $this->send_method_frame(array(20, 40), $args); 936 | return $this->wait(array( 937 | "20,41" // Channel.close_ok 938 | )); 939 | } 940 | 941 | 942 | protected function _close($args) 943 | { 944 | $reply_code = $args->read_short(); 945 | $reply_text = $args->read_shortstr(); 946 | $class_id = $args->read_short(); 947 | $method_id = $args->read_short(); 948 | 949 | $this->send_method_frame(array(20, 41)); 950 | $this->do_close(); 951 | 952 | throw new AMQPChannelException($reply_code, $reply_text, 953 | array($class_id, $method_id)); 954 | } 955 | 956 | /** 957 | * confirm a channel close 958 | */ 959 | protected function close_ok($args) 960 | { 961 | $this->do_close(); 962 | } 963 | 964 | /** 965 | * enable/disable flow from peer 966 | */ 967 | public function flow($active) 968 | { 969 | $args = new AMQPWriter(); 970 | $args->write_bit($active); 971 | $this->send_method_frame(array(20, 20), $args); 972 | return $this->wait(array( 973 | "20,21" //Channel.flow_ok 974 | )); 975 | } 976 | 977 | protected function _flow($args) 978 | { 979 | $this->active = $args->read_bit(); 980 | $this->x_flow_ok($this->active); 981 | } 982 | 983 | protected function x_flow_ok($active) 984 | { 985 | $args = new AMQPWriter(); 986 | $args->write_bit($active); 987 | $this->send_method_frame(array(20, 21), $args); 988 | } 989 | 990 | protected function flow_ok($args) 991 | { 992 | return $args->read_bit(); 993 | } 994 | 995 | protected function x_open($out_of_band="") 996 | { 997 | if($this->is_open) 998 | return; 999 | 1000 | $args = new AMQPWriter(); 1001 | $args->write_shortstr($out_of_band); 1002 | $this->send_method_frame(array(20, 10), $args); 1003 | return $this->wait(array( 1004 | "20,11" //Channel.open_ok 1005 | )); 1006 | } 1007 | 1008 | protected function open_ok($args) 1009 | { 1010 | $this->is_open = true; 1011 | if($this->debug) 1012 | { 1013 | debug_msg("Channel open"); 1014 | } 1015 | } 1016 | 1017 | /** 1018 | * request an access ticket 1019 | */ 1020 | public function access_request($realm, $exclusive=false, 1021 | $passive=false, $active=false, $write=false, $read=false) 1022 | { 1023 | $args = new AMQPWriter(); 1024 | $args->write_shortstr($realm); 1025 | $args->write_bit($exclusive); 1026 | $args->write_bit($passive); 1027 | $args->write_bit($active); 1028 | $args->write_bit($write); 1029 | $args->write_bit($read); 1030 | $this->send_method_frame(array(30, 10), $args); 1031 | return $this->wait(array( 1032 | "30,11" //Channel.access_request_ok 1033 | )); 1034 | } 1035 | 1036 | /** 1037 | * grant access to server resources 1038 | */ 1039 | protected function access_request_ok($args) 1040 | { 1041 | $this->default_ticket = $args->read_short(); 1042 | return $this->default_ticket; 1043 | } 1044 | 1045 | 1046 | /** 1047 | * declare exchange, create if needed 1048 | */ 1049 | public function exchange_declare($exchange, 1050 | $type, 1051 | $passive=false, 1052 | $durable=false, 1053 | $auto_delete=true, 1054 | $internal=false, 1055 | $nowait=false, 1056 | $arguments=NULL, 1057 | $ticket=NULL) 1058 | { 1059 | if($arguments==NULL) 1060 | $arguments = array(); 1061 | 1062 | $args = new AMQPWriter(); 1063 | if($ticket != NULL) 1064 | $args->write_short($ticket); 1065 | else 1066 | $args->write_short($this->default_ticket); 1067 | $args->write_shortstr($exchange); 1068 | $args->write_shortstr($type); 1069 | $args->write_bit($passive); 1070 | $args->write_bit($durable); 1071 | $args->write_bit($auto_delete); 1072 | $args->write_bit($internal); 1073 | $args->write_bit($nowait); 1074 | $args->write_table($arguments); 1075 | $this->send_method_frame(array(40, 10), $args); 1076 | 1077 | if(!$nowait) 1078 | return $this->wait(array( 1079 | "40,11" //Channel.exchange_declare_ok 1080 | )); 1081 | } 1082 | 1083 | /** 1084 | * confirms an exchange declaration 1085 | */ 1086 | protected function exchange_declare_ok($args) 1087 | { 1088 | } 1089 | 1090 | /** 1091 | * delete an exchange 1092 | */ 1093 | public function exchange_delete($exchange, $if_unused=false, 1094 | $nowait=false, $ticket=NULL) 1095 | { 1096 | $args = new AMQPWriter(); 1097 | if($ticket != NULL) 1098 | $args->write_short($ticket); 1099 | else 1100 | $args->write_short($this->default_ticket); 1101 | $args->write_shortstr($exchange); 1102 | $args->write_bit($if_unused); 1103 | $args->write_bit($nowait); 1104 | $this->send_method_frame(array(40, 20), $args); 1105 | 1106 | if(!$nowait) 1107 | return $this->wait(array( 1108 | "40,21" //Channel.exchange_delete_ok 1109 | )); 1110 | } 1111 | 1112 | /** 1113 | * confirm deletion of an exchange 1114 | */ 1115 | protected function exchange_delete_ok($args) 1116 | { 1117 | } 1118 | 1119 | 1120 | /** 1121 | * bind queue to an exchange 1122 | */ 1123 | public function queue_bind($queue, $exchange, $routing_key="", 1124 | $nowait=false, $arguments=NULL, $ticket=NULL) 1125 | { 1126 | if($arguments == NULL) 1127 | $arguments = array(); 1128 | 1129 | $args = new AMQPWriter(); 1130 | if($ticket != NULL) 1131 | $args->write_short($ticket); 1132 | else 1133 | $args->write_short($this->default_ticket); 1134 | $args->write_shortstr($queue); 1135 | $args->write_shortstr($exchange); 1136 | $args->write_shortstr($routing_key); 1137 | $args->write_bit($nowait); 1138 | $args->write_table($arguments); 1139 | $this->send_method_frame(array(50, 20), $args); 1140 | 1141 | if(!$nowait) 1142 | return $this->wait(array( 1143 | "50,21" // Channel.queue_bind_ok 1144 | )); 1145 | } 1146 | 1147 | /** 1148 | * confirm bind successful 1149 | */ 1150 | protected function queue_bind_ok($args) 1151 | { 1152 | } 1153 | 1154 | /** 1155 | * unbind queue from an exchange 1156 | */ 1157 | public function queue_unbind($queue, $exchange, $routing_key="", 1158 | $arguments=NULL, $ticket=NULL) 1159 | { 1160 | if($arguments == NULL) 1161 | $arguments = array(); 1162 | 1163 | $args = new AMQPWriter(); 1164 | if($ticket != NULL) 1165 | $args->write_short($ticket); 1166 | else 1167 | $args->write_short($this->default_ticket); 1168 | $args->write_shortstr($queue); 1169 | $args->write_shortstr($exchange); 1170 | $args->write_shortstr($routing_key); 1171 | $args->write_table($arguments); 1172 | $this->send_method_frame(array(50, 50), $args); 1173 | 1174 | return $this->wait(array( 1175 | "50,51" // Channel.queue_unbind_ok 1176 | )); 1177 | } 1178 | 1179 | /** 1180 | * confirm unbind successful 1181 | */ 1182 | protected function queue_unbind_ok($args) 1183 | { 1184 | } 1185 | 1186 | /** 1187 | * declare queue, create if needed 1188 | */ 1189 | public function queue_declare($queue="", 1190 | $passive=false, 1191 | $durable=false, 1192 | $exclusive=false, 1193 | $auto_delete=true, 1194 | $nowait=false, 1195 | $arguments=NULL, 1196 | $ticket=NULL) 1197 | { 1198 | if($arguments == NULL) 1199 | $arguments = array(); 1200 | 1201 | $args = new AMQPWriter(); 1202 | if($ticket != NULL) 1203 | $args->write_short($ticket); 1204 | else 1205 | $args->write_short($this->default_ticket); 1206 | $args->write_shortstr($queue); 1207 | $args->write_bit($passive); 1208 | $args->write_bit($durable); 1209 | $args->write_bit($exclusive); 1210 | $args->write_bit($auto_delete); 1211 | $args->write_bit($nowait); 1212 | $args->write_table($arguments); 1213 | $this->send_method_frame(array(50, 10), $args); 1214 | 1215 | if(!$nowait) 1216 | return $this->wait(array( 1217 | "50,11" // Channel.queue_declare_ok 1218 | )); 1219 | } 1220 | 1221 | /** 1222 | * confirms a queue definition 1223 | */ 1224 | protected function queue_declare_ok($args) 1225 | { 1226 | $queue = $args->read_shortstr(); 1227 | $message_count = $args->read_long(); 1228 | $consumer_count = $args->read_long(); 1229 | 1230 | return array($queue, $message_count, $consumer_count); 1231 | } 1232 | 1233 | /** 1234 | * delete a queue 1235 | */ 1236 | public function queue_delete($queue="", $if_unused=false, $if_empty=false, 1237 | $nowait=false, $ticket=NULL) 1238 | { 1239 | $args = new AMQPWriter(); 1240 | if($ticket != NULL) 1241 | $args->write_short($ticket); 1242 | else 1243 | $args->write_short($this->default_ticket); 1244 | 1245 | $args->write_shortstr($queue); 1246 | $args->write_bit($if_unused); 1247 | $args->write_bit($if_empty); 1248 | $args->write_bit($nowait); 1249 | $this->send_method_frame(array(50, 40), $args); 1250 | 1251 | if(!$nowait) 1252 | return $this->wait(array( 1253 | "50,41" //Channel.queue_delete_ok 1254 | )); 1255 | } 1256 | 1257 | /** 1258 | * confirm deletion of a queue 1259 | */ 1260 | protected function queue_delete_ok($args) 1261 | { 1262 | return $args->read_long(); 1263 | } 1264 | 1265 | /** 1266 | * purge a queue 1267 | */ 1268 | public function queue_purge($queue="", $nowait=false, $ticket=NULL) 1269 | { 1270 | $args = new AMQPWriter(); 1271 | if($ticket != NULL) 1272 | $args->write_short($ticket); 1273 | else 1274 | $args->write_short($this->default_ticket); 1275 | $args->write_shortstr($queue); 1276 | $args->write_bit($nowait); 1277 | $this->send_method_frame(array(50, 30), $args); 1278 | 1279 | if(!$nowait) 1280 | return $this->wait(array( 1281 | "50,31" //Channel.queue_purge_ok 1282 | )); 1283 | } 1284 | 1285 | /** 1286 | * confirms a queue purge 1287 | */ 1288 | protected function queue_purge_ok($args) 1289 | { 1290 | return $args->read_long(); 1291 | } 1292 | 1293 | /** 1294 | * acknowledge one or more messages 1295 | */ 1296 | public function basic_ack($delivery_tag, $multiple=false) 1297 | { 1298 | $args = new AMQPWriter(); 1299 | $args->write_longlong($delivery_tag); 1300 | $args->write_bit($multiple); 1301 | $this->send_method_frame(array(60, 80), $args); 1302 | } 1303 | 1304 | /** 1305 | * end a queue consumer 1306 | */ 1307 | public function basic_cancel($consumer_tag, $nowait=false) 1308 | { 1309 | $args = new AMQPWriter(); 1310 | $args->write_shortstr($consumer_tag); 1311 | $args->write_bit($nowait); 1312 | $this->send_method_frame(array(60, 30), $args); 1313 | return $this->wait(array( 1314 | "60,31" // Channel.basic_cancel_ok 1315 | )); 1316 | } 1317 | 1318 | /** 1319 | * confirm a cancelled consumer 1320 | */ 1321 | protected function basic_cancel_ok($args) 1322 | { 1323 | $consumer_tag = $args->read_shortstr(); 1324 | unset($this->callbacks[$consumer_tag]); 1325 | } 1326 | 1327 | /** 1328 | * start a queue consumer 1329 | */ 1330 | public function basic_consume($queue="", $consumer_tag="", $no_local=false, 1331 | $no_ack=false, $exclusive=false, $nowait=false, 1332 | $callback=NULL, $ticket=NULL) 1333 | { 1334 | $args = new AMQPWriter(); 1335 | if($ticket != NULL) 1336 | $args->write_short($ticket); 1337 | else 1338 | $args->write_short($this->default_ticket); 1339 | $args->write_shortstr($queue); 1340 | $args->write_shortstr($consumer_tag); 1341 | $args->write_bit($no_local); 1342 | $args->write_bit($no_ack); 1343 | $args->write_bit($exclusive); 1344 | $args->write_bit($nowait); 1345 | $this->send_method_frame(array(60, 20), $args); 1346 | 1347 | if(!$nowait) 1348 | $consumer_tag = $this->wait(array( 1349 | "60,21" //Channel.basic_consume_ok 1350 | )); 1351 | 1352 | $this->callbacks[$consumer_tag] = $callback; 1353 | return $consumer_tag; 1354 | } 1355 | 1356 | /** 1357 | * confirm a new consumer 1358 | */ 1359 | protected function basic_consume_ok($args) 1360 | { 1361 | return $args->read_shortstr(); 1362 | } 1363 | 1364 | /** 1365 | * notify the client of a consumer message 1366 | */ 1367 | protected function basic_deliver($args, $msg) 1368 | { 1369 | $consumer_tag = $args->read_shortstr(); 1370 | $delivery_tag = $args->read_longlong(); 1371 | $redelivered = $args->read_bit(); 1372 | $exchange = $args->read_shortstr(); 1373 | $routing_key = $args->read_shortstr(); 1374 | 1375 | $msg->delivery_info = array( 1376 | "channel" => $this, 1377 | "consumer_tag" => $consumer_tag, 1378 | "delivery_tag" => $delivery_tag, 1379 | "redelivered" => $redelivered, 1380 | "exchange" => $exchange, 1381 | "routing_key" => $routing_key 1382 | ); 1383 | 1384 | if(array_key_exists($consumer_tag, $this->callbacks)) 1385 | $func = $this->callbacks[$consumer_tag]; 1386 | else 1387 | $func = NULL; 1388 | 1389 | if($func!=NULL) 1390 | call_user_func($func, $msg); 1391 | } 1392 | 1393 | /** 1394 | * direct access to a queue 1395 | */ 1396 | public function basic_get($queue="", $no_ack=false, $ticket=NULL) 1397 | { 1398 | $args = new AMQPWriter(); 1399 | if($ticket != NULL) 1400 | $args->write_short($ticket); 1401 | else 1402 | $args->write_short($this->default_ticket); 1403 | $args->write_shortstr($queue); 1404 | $args->write_bit($no_ack); 1405 | $this->send_method_frame(array(60, 70), $args); 1406 | return $this->wait(array( 1407 | "60,71", //Channel.basic_get_ok 1408 | "60,72" // Channel.basic_get_empty 1409 | )); 1410 | } 1411 | 1412 | /** 1413 | * indicate no messages available 1414 | */ 1415 | protected function basic_get_empty($args) 1416 | { 1417 | $cluster_id = $args->read_shortstr(); 1418 | } 1419 | 1420 | /** 1421 | * provide client with a message 1422 | */ 1423 | protected function basic_get_ok($args, $msg) 1424 | { 1425 | $delivery_tag = $args->read_longlong(); 1426 | $redelivered = $args->read_bit(); 1427 | $exchange = $args->read_shortstr(); 1428 | $routing_key = $args->read_shortstr(); 1429 | $message_count = $args->read_long(); 1430 | 1431 | $msg->delivery_info = array( 1432 | "delivery_tag" => $delivery_tag, 1433 | "redelivered" => $redelivered, 1434 | "exchange" => $exchange, 1435 | "routing_key" => $routing_key, 1436 | "message_count" => $message_count 1437 | ); 1438 | return $msg; 1439 | } 1440 | 1441 | /** 1442 | * publish a message 1443 | */ 1444 | public function basic_publish($msg, $exchange="", $routing_key="", 1445 | $mandatory=false, $immediate=false, 1446 | $ticket=NULL) 1447 | { 1448 | $args = new AMQPWriter(); 1449 | if($ticket != NULL) 1450 | $args->write_short($ticket); 1451 | else 1452 | $args->write_short($this->default_ticket); 1453 | $args->write_shortstr($exchange); 1454 | $args->write_shortstr($routing_key); 1455 | $args->write_bit($mandatory); 1456 | $args->write_bit($immediate); 1457 | $this->send_method_frame(array(60, 40), $args); 1458 | 1459 | $this->connection->send_content($this->channel_id, 60, 0, 1460 | strlen($msg->body), 1461 | $msg->serialize_properties(), 1462 | $msg->body); 1463 | } 1464 | 1465 | 1466 | /** 1467 | * specify quality of service 1468 | */ 1469 | public function basic_qos($prefetch_size, $prefetch_count, $a_global) 1470 | { 1471 | $args = new AMQPWriter(); 1472 | $args->write_long($prefetch_size); 1473 | $args->write_short($prefetch_count); 1474 | $args->write_bit($a_global); 1475 | $this->send_method_frame(array(60, 10), $args); 1476 | return $this->wait(array( 1477 | "60,11" //Channel.basic_qos_ok 1478 | )); 1479 | } 1480 | 1481 | 1482 | /** 1483 | * confirm the requested qos 1484 | */ 1485 | protected function basic_qos_ok($args) 1486 | { 1487 | } 1488 | 1489 | /** 1490 | * redeliver unacknowledged messages 1491 | */ 1492 | public function basic_recover($requeue=false) 1493 | { 1494 | $args = new AMQPWriter(); 1495 | $args->write_bit($requeue); 1496 | $this->send_method_frame(array(60, 100), $args); 1497 | } 1498 | 1499 | /** 1500 | * reject an incoming message 1501 | */ 1502 | public function basic_reject($delivery_tag, $requeue) 1503 | { 1504 | $args = new AMQPWriter(); 1505 | $args->write_longlong($delivery_tag); 1506 | $args->write_bit($requeue); 1507 | $this->send_method_frame(array(60, 90), $args); 1508 | } 1509 | 1510 | /** 1511 | * return a failed message 1512 | */ 1513 | protected function basic_return($args) 1514 | { 1515 | $reply_code = $args->read_short(); 1516 | $reply_text = $args->read_shortstr(); 1517 | $exchange = $args->read_shortstr(); 1518 | $routing_key = $args->read_shortstr(); 1519 | $msg = $this->wait(); 1520 | } 1521 | 1522 | 1523 | public function tx_commit() 1524 | { 1525 | $this->send_method_frame(array(90, 20)); 1526 | return $this->wait(array( 1527 | "90,21" //Channel.tx_commit_ok 1528 | )); 1529 | } 1530 | 1531 | /** 1532 | * confirm a successful commit 1533 | */ 1534 | protected function tx_commit_ok($args) 1535 | { 1536 | } 1537 | 1538 | 1539 | /** 1540 | * abandon the current transaction 1541 | */ 1542 | public function tx_rollback() 1543 | { 1544 | $this->send_method_frame(array(90, 30)); 1545 | return $this->wait(array( 1546 | "90,31" //Channel.tx_rollback_ok 1547 | )); 1548 | } 1549 | 1550 | /** 1551 | * confirm a successful rollback 1552 | */ 1553 | protected function tx_rollback_ok($args) 1554 | { 1555 | } 1556 | 1557 | /** 1558 | * select standard transaction mode 1559 | */ 1560 | public function tx_select() 1561 | { 1562 | $this->send_method_frame(array(90, 10)); 1563 | return $this->wait(array( 1564 | "90,11" //Channel.tx_select_ok 1565 | )); 1566 | } 1567 | 1568 | /** 1569 | * confirm transaction mode 1570 | */ 1571 | protected function tx_select_ok($args) 1572 | { 1573 | } 1574 | 1575 | } 1576 | 1577 | /** 1578 | * A Message for use with the Channnel.basic_* methods. 1579 | */ 1580 | class AMQPMessage extends GenericContent 1581 | { 1582 | protected static $PROPERTIES = array( 1583 | "content_type" => "shortstr", 1584 | "content_encoding" => "shortstr", 1585 | "application_headers" => "table", 1586 | "delivery_mode" => "octet", 1587 | "priority" => "octet", 1588 | "correlation_id" => "shortstr", 1589 | "reply_to" => "shortstr", 1590 | "expiration" => "shortstr", 1591 | "message_id" => "shortstr", 1592 | "timestamp" => "timestamp", 1593 | "type" => "shortstr", 1594 | "user_id" => "shortstr", 1595 | "app_id" => "shortstr", 1596 | "cluster_id" => "shortst" 1597 | ); 1598 | 1599 | public function __construct($body = '', $properties = null) 1600 | { 1601 | $this->body = $body; 1602 | 1603 | parent::__construct($properties, $prop_types=AMQPMessage::$PROPERTIES); 1604 | } 1605 | } 1606 | 1607 | ?> 1608 | -------------------------------------------------------------------------------- /amqp_test.php: -------------------------------------------------------------------------------- 1 | channel(); 25 | echo "Requesting access\n"; 26 | $ch->access_request('/data', false, false, true, true); 27 | 28 | echo "Declaring exchange\n"; 29 | $ch->exchange_declare($EXCHANGE, 'direct', false, false, false); 30 | echo "Creating message\n"; 31 | $msg = new AMQPMessage($msg_body, array('content_type' => 'text/plain')); 32 | 33 | echo "Publishing message\n"; 34 | $ch->basic_publish($msg, $EXCHANGE, $QUEUE); 35 | 36 | echo "Closing channel\n"; 37 | $ch->close(); 38 | echo "Closing connection\n"; 39 | $conn->close(); 40 | echo "Done.\n"; 41 | } catch (Exception $e) { 42 | echo 'Caught exception: ', $e->getMessage(); 43 | echo "\nTrace:\n" . $e->getTraceAsString(); 44 | } 45 | ?> 46 | -------------------------------------------------------------------------------- /amqp_wire.inc: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * 10 | * To understand all signed/unsinged and 32/64 bit madness in this 11 | * code, please read first the following article: 12 | * 13 | * http://www.mysqlperformanceblog.com/2007/03/27/integers-in-php-running-with-scissors-and-portability/ 14 | */ 15 | 16 | require_once('hexdump.inc'); 17 | 18 | /** 19 | * AMQP protocol decimal value. 20 | * 21 | * Values are represented as (n,e) pairs. The actual value 22 | * is n * 10^(-e). 23 | * 24 | * From 0.8 spec: Decimal values are 25 | * not intended to support floating point values, but rather 26 | * business values such as currency rates and amounts. The 27 | * 'decimals' octet is not signed. 28 | */ 29 | class AMQPDecimal 30 | { 31 | public function __construct($n, $e) 32 | { 33 | if($e < 0) 34 | throw new Exception("Decimal exponent value must be unsigned!"); 35 | $this->n = $n; 36 | $this->e = $e; 37 | } 38 | 39 | public function asBCvalue() 40 | { 41 | return bcdiv($this->n, bcpow(10,$this->e)); 42 | } 43 | } 44 | 45 | class AMQPWriter 46 | { 47 | public function __construct() 48 | { 49 | $this->out = ""; 50 | $this->bits = array(); 51 | $this->bitcount = 0; 52 | } 53 | 54 | private static function chrbytesplit($x, $bytes) 55 | { 56 | return array_map('chr', AMQPWriter::bytesplit($x,$bytes)); 57 | } 58 | 59 | /** 60 | * Splits number (could be either int or string) into array of byte 61 | * values (represented as integers) in big-endian byte order. 62 | */ 63 | private static function bytesplit($x, $bytes) 64 | { 65 | if(is_int($x)) 66 | { 67 | if($x<0) 68 | $x = sprintf("%u", $x); 69 | } 70 | 71 | $res = array(); 72 | for($i=0;$i<$bytes;$i++) 73 | { 74 | $b = bcmod($x,'256'); 75 | array_unshift($res,(int)$b); 76 | $x=bcdiv($x,'256', 0); 77 | } 78 | if($x!=0) 79 | throw new Exception("Value too big!"); 80 | return $res; 81 | } 82 | 83 | private function flushbits() 84 | { 85 | if(count($this->bits)) 86 | { 87 | $this->out .= implode("", array_map('chr',$this->bits)); 88 | $this->bits = array(); 89 | $this->bitcount = 0; 90 | } 91 | } 92 | 93 | /** 94 | * Get what's been encoded so far. 95 | */ 96 | public function getvalue() 97 | { 98 | $this->flushbits(); 99 | return $this->out; 100 | } 101 | 102 | /** 103 | * Write a plain Python string, with no special encoding. 104 | */ 105 | public function write($s) 106 | { 107 | $this->flushbits(); 108 | $this->out .= $s; 109 | } 110 | 111 | /** 112 | * Write a boolean value. 113 | */ 114 | public function write_bit($b) 115 | { 116 | if($b) 117 | $b = 1; 118 | else 119 | $b = 0; 120 | $shift = $this->bitcount % 8; 121 | if($shift == 0) 122 | $last = 0; 123 | else 124 | $last = array_pop($this->bits); 125 | 126 | $last |= ($b << $shift); 127 | array_push($this->bits, $last); 128 | 129 | $this->bitcount += 1; 130 | } 131 | 132 | /** 133 | * Write an integer as an unsigned 8-bit value. 134 | */ 135 | public function write_octet($n) 136 | { 137 | if($n < 0 || $n > 255) 138 | throw new Exception('Octet out of range 0..255'); 139 | $this->flushbits(); 140 | $this->out .= chr($n); 141 | } 142 | 143 | /** 144 | * Write an integer as an unsigned 16-bit value. 145 | */ 146 | public function write_short($n) 147 | { 148 | if($n < 0 || $n > 65535) 149 | throw new Exception('Octet out of range 0..65535'); 150 | $this->flushbits(); 151 | $this->out .= pack('n', $n); 152 | } 153 | 154 | /** 155 | * Write an integer as an unsigned 32-bit value. 156 | */ 157 | public function write_long($n) 158 | { 159 | $this->flushbits(); 160 | $this->out .= implode("", AMQPWriter::chrbytesplit($n,4)); 161 | } 162 | 163 | private function write_signed_long($n) 164 | { 165 | $this->flushbits(); 166 | // although format spec for 'N' mentions unsigned 167 | // it will deal with sinned integers as well. tested. 168 | $this->out .= pack('N', $n); 169 | } 170 | 171 | /** 172 | * Write an integer as an unsigned 64-bit value. 173 | */ 174 | public function write_longlong($n) 175 | { 176 | $this->flushbits(); 177 | $this->out .= implode("", AMQPWriter::chrbytesplit($n,8)); 178 | } 179 | 180 | /** 181 | * Write a string up to 255 bytes long after encoding. 182 | * Assume UTF-8 encoding. 183 | */ 184 | public function write_shortstr($s) 185 | { 186 | $this->flushbits(); 187 | if(strlen($s) > 255) 188 | throw new Exception('String too long'); 189 | $this->write_octet(strlen($s)); 190 | $this->out .= $s; 191 | } 192 | 193 | 194 | /* 195 | * Write a string up to 2**32 bytes long. Assume UTF-8 encoding. 196 | */ 197 | public function write_longstr($s) 198 | { 199 | $this->flushbits(); 200 | $this->write_long(strlen($s)); 201 | $this->out .= $s; 202 | } 203 | 204 | 205 | /** 206 | * Write unix time_t value as 64 bit timestamp. 207 | */ 208 | public function write_timestamp($v) 209 | { 210 | $this->write_longlong($v); 211 | } 212 | 213 | /** 214 | * Write PHP array, as table. Input array format: keys are strings, 215 | * values are (type,value) tuples. 216 | */ 217 | public function write_table($d) 218 | { 219 | $this->flushbits(); 220 | $table_data = new AMQPWriter(); 221 | foreach($d as $k=>$va) 222 | { 223 | list($ftype,$v) = $va; 224 | $table_data->write_shortstr($k); 225 | if($ftype=='S') 226 | { 227 | $table_data->write('S'); 228 | $table_data->write_longstr($v); 229 | } else if($ftype=='I') 230 | { 231 | $table_data->write('I'); 232 | $table_data->write_signed_long($v); 233 | } else if($ftype=='D') 234 | { 235 | // 'D' type values are passed AMQPDecimal instances. 236 | $table_data->write('D'); 237 | $table_data->write_octet($v->e); 238 | $table_data->write_signed_long($v->n); 239 | } else if($ftype=='T') 240 | { 241 | $table_data->write('T'); 242 | $table_data->write_timestamp($v); 243 | } else if($ftype=='F') 244 | { 245 | $table_data->write('F'); 246 | $table_data->write_table($v); 247 | } 248 | } 249 | $table_data = $table_data->getvalue(); 250 | $this->write_long(strlen($table_data)); 251 | $this->write($table_data); 252 | } 253 | } 254 | 255 | class AMQPReader 256 | { 257 | public function __construct($str, $sock=NULL) 258 | { 259 | $this->str = $str; 260 | if ($sock !== NULL) 261 | { 262 | $this->sock = new BufferedInput($sock); 263 | } else 264 | { 265 | $this->sock = NULL; 266 | } 267 | $this->offset = 0; 268 | 269 | $this->bitcount = $this->bits = 0; 270 | 271 | if(((int)4294967296)!=0) 272 | $this->is64bits = true; 273 | else 274 | $this->is64bits = false; 275 | 276 | if(!function_exists("bcmul")) 277 | throw new Exception("'bc math' module required"); 278 | 279 | $this->buffer_read_timeout = 5; // in seconds 280 | } 281 | 282 | public function close() 283 | { 284 | if($this->sock) 285 | $this->sock->close(); 286 | } 287 | 288 | public function read($n) 289 | { 290 | $this->bitcount = $this->bits = 0; 291 | return $this->rawread($n); 292 | } 293 | 294 | private function rawread($n) 295 | { 296 | if($this->sock) 297 | { 298 | $res = ''; 299 | $read = 0; 300 | 301 | $start = time(); 302 | while($read < $n && !feof($this->sock->real_sock()) && 303 | (false !== ($buf = fread($this->sock->real_sock(), $n - $read)))) 304 | { 305 | if ($buf == '') 306 | { 307 | usleep(100); 308 | } 309 | else 310 | $start = time(); 311 | 312 | $read += strlen($buf); 313 | $res .= $buf; 314 | } 315 | 316 | if(strlen($res)!=$n) 317 | throw new Exception ("Error reading data. Recevived " . 318 | strlen($res) . " instead of expected $n bytes"); 319 | $this->offset += $n; 320 | } else 321 | { 322 | if(strlen($this->str) < $n) 323 | throw new Exception ("Error reading data. Requested $n bytes while string buffer has only " . 324 | strlen($this->str)); 325 | $res = substr($this->str,0,$n); 326 | $this->str = substr($this->str,$n); 327 | $this->offset += $n; 328 | } 329 | return $res; 330 | } 331 | 332 | public function read_bit() 333 | { 334 | if(!$this->bitcount) 335 | { 336 | $this->bits = ord($this->rawread(1)); 337 | $this->bitcount = 8; 338 | } 339 | $result = ($this->bits & 1) == 1; 340 | $this->bits >>= 1; 341 | $this->bitcount -= 1; 342 | return $result; 343 | } 344 | 345 | public function read_octet() 346 | { 347 | $this->bitcount = $this->bits = 0; 348 | list(,$res) = unpack('C', $this->rawread(1)); 349 | return $res; 350 | } 351 | 352 | public function read_short() 353 | { 354 | $this->bitcount = $this->bits = 0; 355 | list(,$res) = unpack('n', $this->rawread(2)); 356 | return $res; 357 | } 358 | 359 | /** 360 | * Reads 32 bit integer in big-endian byte order. 361 | * 362 | * On 64 bit systems it will return always usngined int 363 | * value in 0..2^32 range. 364 | * 365 | * On 32 bit systems it will return signed int value in 366 | * -2^31...+2^31 range. 367 | * 368 | * Use with caution! 369 | */ 370 | public function read_php_int() 371 | { 372 | list(,$res) = unpack('N', $this->rawread(4)); 373 | if($this->is64bits) 374 | { 375 | $sres = sprintf ( "%u", $res ); 376 | return (int)$sres; 377 | } else { 378 | return $res; 379 | } 380 | } 381 | 382 | // PHP does not have unsigned 32 bit int, 383 | // so we return it as a string 384 | public function read_long() 385 | { 386 | $this->bitcount = $this->bits = 0; 387 | list(,$res) = unpack('N', $this->rawread(4)); 388 | $sres = sprintf ( "%u", $res ); 389 | return $sres; 390 | } 391 | 392 | private function read_signed_long() 393 | { 394 | $this->bitcount = $this->bits = 0; 395 | // In PHP unpack('N') always return signed value, 396 | // on both 32 and 64 bit systems! 397 | list(,$res) = unpack('N', $this->rawread(4)); 398 | return $res; 399 | } 400 | 401 | // Even on 64 bit systems PHP integers are singed. 402 | // Since we need an unsigned value here we return it 403 | // as a string. 404 | public function read_longlong() 405 | { 406 | $this->bitcount = $this->bits = 0; 407 | $hi = unpack('N', $this->rawread(4)); 408 | $lo = unpack('N', $this->rawread(4)); 409 | 410 | // workaround signed/unsigned braindamage in php 411 | $hi = sprintf ( "%u", $hi[1] ); 412 | $lo = sprintf ( "%u", $lo[1] ); 413 | 414 | return bcadd(bcmul($hi, "4294967296" ), $lo); 415 | } 416 | 417 | /** 418 | * Read a utf-8 encoded string that's stored in up to 419 | * 255 bytes. Return it decoded as a Python unicode object. 420 | */ 421 | public function read_shortstr() 422 | { 423 | $this->bitcount = $this->bits = 0; 424 | list(,$slen) = unpack('C', $this->rawread(1)); 425 | return $this->rawread($slen); 426 | } 427 | 428 | /** 429 | * Read a string that's up to 2**32 bytes, the encoding 430 | * isn't specified in the AMQP spec, so just return it as 431 | * a plain PHP string. 432 | */ 433 | public function read_longstr() 434 | { 435 | $this->bitcount = $this->bits = 0; 436 | $slen = $this->read_php_int(); 437 | if($slen<0) 438 | throw new Exception("Strings longer than supported on this platform"); 439 | return $this->rawread($slen); 440 | } 441 | 442 | /** 443 | * Read and AMQP timestamp, which is a 64-bit integer representing 444 | * seconds since the Unix epoch in 1-second resolution. 445 | */ 446 | function read_timestamp() 447 | { 448 | return $this->read_longlong(); 449 | } 450 | 451 | /** 452 | * Read an AMQP table, and return as a PHP array. keys are strings, 453 | * values are (type,value) tuples. 454 | */ 455 | public function read_table() 456 | { 457 | $this->bitcount = $this->bits = 0; 458 | $tlen = $this->read_php_int(); 459 | if($tlen<0) 460 | throw new Exception("Table is longer than supported"); 461 | $table_data = new AMQPReader($this->rawread($tlen)); 462 | $result = array(); 463 | while($table_data->tell() < $tlen) 464 | { 465 | $name = $table_data->read_shortstr(); 466 | $ftype = $table_data->rawread(1); 467 | if($ftype == 'S') { 468 | $val = $table_data->read_longstr(); 469 | } else if($ftype == 'I') { 470 | $val = $table_data->read_signed_long(); 471 | } else if($ftype == 'D') 472 | { 473 | $e = $table_data->read_octet(); 474 | $n = $table_data->read_signed_long(); 475 | $val = new AMQPDecimal($n, $e); 476 | } else if($ftype == 'T') 477 | { 478 | $val = $table_data->read_timestamp(); 479 | } else if($ftype == 'F') 480 | { 481 | $val = $table_data->read_table(); // recursion 482 | } else { 483 | error_log("Usupported table field type $ftype"); 484 | $val = NULL; 485 | } 486 | $result[$name] = array($ftype,$val); 487 | } 488 | return $result; 489 | } 490 | 491 | 492 | protected function tell() 493 | { 494 | return $this->offset; 495 | } 496 | 497 | } 498 | 499 | 500 | /** 501 | * Abstract base class for AMQP content. Subclasses should override 502 | * the PROPERTIES attribute. 503 | */ 504 | class GenericContent 505 | { 506 | protected static $PROPERTIES = array( 507 | "dummy" => "shortstr" 508 | ); 509 | 510 | public function __construct($props, $prop_types=NULL) 511 | { 512 | if($prop_types) 513 | $this->prop_types = $prop_types; 514 | else 515 | $this->prop_types = GenericContent::$PROPERTIES; 516 | $d = array(); 517 | if ($props) 518 | $d = array_intersect_key($props, $this->prop_types); 519 | else 520 | $d = array(); 521 | $this->properties = $d; 522 | } 523 | 524 | 525 | /** 526 | * Look for additional properties in the 'properties' dictionary, 527 | * and if present - the 'delivery_info' dictionary. 528 | */ 529 | public function get($name) 530 | { 531 | if(array_key_exists($name,$this->properties)) 532 | return $this->properties[$name]; 533 | 534 | if(isset($this->delivery_info)) 535 | if(array_key_exists($name,$this->delivery_info)) 536 | return $this->delivery_info[$name]; 537 | 538 | throw new Exception("No such property"); 539 | } 540 | 541 | 542 | /** 543 | * Given the raw bytes containing the property-flags and 544 | * property-list from a content-frame-header, parse and insert 545 | * into a dictionary stored in this object as an attribute named 546 | * 'properties'. 547 | */ 548 | public function load_properties($raw_bytes) 549 | { 550 | $r = new AMQPReader($raw_bytes); 551 | 552 | // Read 16-bit shorts until we get one with a low bit set to zero 553 | $flags = array(); 554 | while(true) 555 | { 556 | $flag_bits = $r->read_short(); 557 | array_push($flags, $flag_bits); 558 | if(($flag_bits & 1) == 0) 559 | break; 560 | } 561 | 562 | $shift = 0; 563 | $d = array(); 564 | foreach ($this->prop_types as $key => $proptype) 565 | { 566 | if($shift == 0) { 567 | if(!$flags) { 568 | break; 569 | } 570 | $flag_bits = array_shift($flags); 571 | $shift = 15; 572 | } 573 | if($flag_bits & (1 << $shift)) 574 | $d[$key] = call_user_func(array($r,"read_".$proptype)); 575 | $shift -= 1; 576 | } 577 | $this->properties = $d; 578 | } 579 | 580 | 581 | /** 582 | * serialize the 'properties' attribute (a dictionary) into the 583 | * raw bytes making up a set of property flags and a property 584 | * list, suitable for putting into a content frame header. 585 | */ 586 | public function serialize_properties() 587 | { 588 | $shift = 15; 589 | $flag_bits = 0; 590 | $flags = array(); 591 | $raw_bytes = new AMQPWriter(); 592 | foreach ($this->prop_types as $key => $proptype) 593 | { 594 | if(array_key_exists($key,$this->properties)) 595 | $val = $this->properties[$key]; 596 | else 597 | $val = NULL; 598 | if($val != NULL) 599 | { 600 | if($shift == 0) 601 | { 602 | array_push($flags, $flag_bits); 603 | $flag_bits = 0; 604 | $shift = 15; 605 | } 606 | 607 | $flag_bits |= (1 << $shift); 608 | if($proptype != "bit") 609 | call_user_func(array($raw_bytes, "write_" . $proptype), 610 | $val); 611 | } 612 | $shift -= 1; 613 | } 614 | array_push($flags, $flag_bits); 615 | $result = new AMQPWriter(); 616 | foreach($flags as $flag_bits) 617 | $result->write_short($flag_bits); 618 | $result->write($raw_bytes->getvalue()); 619 | 620 | return $result->getvalue(); 621 | } 622 | } 623 | 624 | class BufferedInput 625 | { 626 | public function __construct($sock) 627 | { 628 | $this->block_size = 8192; 629 | 630 | $this->sock = $sock; 631 | $this->reset(""); 632 | 633 | } 634 | 635 | public function real_sock() 636 | { 637 | return $this->sock; 638 | } 639 | 640 | public function read($n) 641 | { 642 | if ($this->offset >= strlen($this->buffer)) 643 | { 644 | if (!($rv = $this->populate_buffer())) 645 | { 646 | return $rv; 647 | } 648 | } 649 | return $this->read_buffer($n); 650 | } 651 | 652 | public function close() 653 | { 654 | fclose($this->sock); 655 | $this->reset(""); 656 | } 657 | 658 | private function read_buffer($n) 659 | { 660 | $n = min($n, strlen($this->buffer) - $this->offset); 661 | if ($n === 0) 662 | { 663 | // substr("", 0, 0) => FALSE, which screws up read loops that are 664 | // expecting non-blocking reads to return "". This avoids that edge 665 | // case when the buffer is empty/used up. 666 | return ""; 667 | } 668 | $block = substr($this->buffer, $this->offset, $n); 669 | $this->offset += $n; 670 | return $block; 671 | } 672 | 673 | private function reset($block) 674 | { 675 | $this->buffer = $block; 676 | $this->offset = 0; 677 | } 678 | 679 | private function populate_buffer() 680 | { 681 | if(feof($this->sock)) 682 | { 683 | $this->reset(""); 684 | return FALSE; 685 | } 686 | 687 | $block = fread($this->sock, $this->block_size); 688 | if ($block !== FALSE) 689 | { 690 | $this->reset($block); 691 | return TRUE; 692 | } else 693 | { 694 | return $block; 695 | } 696 | } 697 | } 698 | ?> -------------------------------------------------------------------------------- /benchmark/config.php: -------------------------------------------------------------------------------- 1 | channel(); 12 | 13 | $ch->queue_declare($queue, false, false, false, false); 14 | $ch->exchange_declare($exchange, 'direct', false, false, false); 15 | $ch->queue_bind($queue, $exchange); 16 | 17 | class Consumer 18 | { 19 | protected $msgCount = 0; 20 | protected $startTime = null; 21 | 22 | public function process_message($msg) 23 | { 24 | if($this->startTime === null) { 25 | $this->startTime = microtime(true); 26 | } 27 | 28 | if ($msg->body == 'quit') { 29 | echo sprintf("Pid: %s, Count: %s, Time: %.4f\n", getmypid(), $this->msgCount, microtime(true) - $this->startTime); 30 | die; 31 | } 32 | $this->msgCount++; 33 | } 34 | } 35 | 36 | $ch->basic_consume($queue, '', false, true, false, false, array(new Consumer(), 'process_message')); 37 | 38 | function shutdown($ch, $conn){ 39 | $ch->close(); 40 | $conn->close(); 41 | } 42 | register_shutdown_function('shutdown', $ch, $conn); 43 | 44 | while(count($ch->callbacks)) { 45 | $ch->wait(); 46 | } 47 | ?> -------------------------------------------------------------------------------- /benchmark/producer.php: -------------------------------------------------------------------------------- 1 | channel(); 17 | 18 | $ch->queue_declare($queue, false, false, false, false); 19 | 20 | $ch->exchange_declare($exchange, 'direct', false, false, false); 21 | $ch->exchange_declare('control', 'fanout', false, false, false); 22 | 23 | $ch->queue_bind($queue, $exchange); 24 | 25 | 26 | $msg_body = <<basic_publish($msg, $exchange); 48 | } 49 | 50 | echo microtime(true) - $time, "\n"; 51 | 52 | $ch->basic_publish(new AMQPMessage('quit'), $exchange); 53 | 54 | $ch->close(); 55 | $conn->close(); 56 | ?> -------------------------------------------------------------------------------- /demo/amqp_consumer.php: -------------------------------------------------------------------------------- 1 | channel(); 12 | 13 | /* 14 | The following code is the same both in the consumer and the producer. 15 | In this way we are sure we always have a queue to consume from and an 16 | exchange where to publish messages. 17 | */ 18 | 19 | /* 20 | name: $queue 21 | passive: false 22 | durable: true // the queue will survive server restarts 23 | exclusive: false // the queue can be accessed in other channels 24 | auto_delete: false //the queue won't be deleted once the channel is closed. 25 | */ 26 | $ch->queue_declare($queue, false, true, false, false); 27 | 28 | /* 29 | name: $exchange 30 | type: direct 31 | passive: false 32 | durable: true // the exchange will survive server restarts 33 | auto_delete: false //the exchange won't be deleted once the channel is closed. 34 | */ 35 | 36 | $ch->exchange_declare($exchange, 'direct', false, true, false); 37 | 38 | $ch->queue_bind($queue, $exchange); 39 | 40 | function process_message($msg) { 41 | 42 | echo "\n--------\n"; 43 | echo $msg->body; 44 | echo "\n--------\n"; 45 | 46 | $msg->delivery_info['channel']-> 47 | basic_ack($msg->delivery_info['delivery_tag']); 48 | 49 | // Send a message with the string "quit" to cancel the consumer. 50 | if ($msg->body === 'quit') { 51 | $msg->delivery_info['channel']-> 52 | basic_cancel($msg->delivery_info['consumer_tag']); 53 | } 54 | } 55 | 56 | /* 57 | queue: Queue from where to get the messages 58 | consumer_tag: Consumer identifier 59 | no_local: Don't receive messages published by this consumer. 60 | no_ack: Tells the server if the consumer will acknowledge the messages. 61 | exclusive: Request exclusive consumer access, meaning only this consumer can access the queue 62 | nowait: 63 | callback: A PHP Callback 64 | */ 65 | 66 | $ch->basic_consume($queue, $consumer_tag, false, false, false, false, 'process_message'); 67 | 68 | function shutdown($ch, $conn){ 69 | $ch->close(); 70 | $conn->close(); 71 | } 72 | register_shutdown_function('shutdown', $ch, $conn); 73 | 74 | // Loop as long as the channel has callbacks registered 75 | while(count($ch->callbacks)) { 76 | $ch->wait(); 77 | } 78 | ?> -------------------------------------------------------------------------------- /demo/amqp_publisher.php: -------------------------------------------------------------------------------- 1 | channel(); 11 | 12 | /* 13 | The following code is the same both in the consumer and the producer. 14 | In this way we are sure we always have a queue to consume from and an 15 | exchange where to publish messages. 16 | */ 17 | 18 | /* 19 | name: $queue 20 | passive: false 21 | durable: true // the queue will survive server restarts 22 | exclusive: false // the queue can be accessed in other channels 23 | auto_delete: false //the queue won't be deleted once the channel is closed. 24 | */ 25 | $ch->queue_declare($queue, false, true, false, false); 26 | 27 | /* 28 | name: $exchange 29 | type: direct 30 | passive: false 31 | durable: true // the exchange will survive server restarts 32 | auto_delete: false //the exchange won't be deleted once the channel is closed. 33 | */ 34 | 35 | $ch->exchange_declare($exchange, 'direct', false, true, false); 36 | 37 | $ch->queue_bind($queue, $exchange); 38 | 39 | $msg_body = implode(' ', array_slice($argv, 1)); 40 | $msg = new AMQPMessage($msg_body, array('content_type' => 'text/plain', 'delivery-mode' => 2)); 41 | $ch->basic_publish($msg, $exchange); 42 | 43 | $ch->close(); 44 | $conn->close(); 45 | ?> -------------------------------------------------------------------------------- /demo/config.php: -------------------------------------------------------------------------------- 1 | CERTS_PATH . '/rmqca/cacert.pem', 19 | 'local_cert' => CERTS_PATH . '/phpcert.pem', 20 | 'verify_peer' => true 21 | ); 22 | 23 | $conn = new AMQPSSLConnection(HOST, PORT, USER, PASS, VHOST, $ssl_options); 24 | 25 | function shutdown($conn){ 26 | $conn->close(); 27 | } 28 | 29 | register_shutdown_function('shutdown', $conn); 30 | 31 | while(1){} -------------------------------------------------------------------------------- /hexdump.inc: -------------------------------------------------------------------------------- 1 | 11 | * @author Peter Waller 12 | * @link http://aidanlister.com/repos/v/function.hexdump.php 13 | * @param string $data The string to be dumped 14 | * @param bool $htmloutput Set to false for non-HTML output 15 | * @param bool $uppercase Set to true for uppercase hex 16 | * @param bool $return Set to true to return the dump 17 | */ 18 | function hexdump ($data, $htmloutput = true, $uppercase = false, $return = false) 19 | { 20 | // Init 21 | $hexi = ''; 22 | $ascii = ''; 23 | $dump = ($htmloutput === true) ? '
' : '';
24 |     $offset = 0;
25 |     $len    = strlen($data);
26 | 
27 |     // Upper or lower case hexidecimal
28 |     $x = ($uppercase === false) ? 'x' : 'X';
29 | 
30 |     // Iterate string
31 |     for ($i = $j = 0; $i < $len; $i++)
32 |     {
33 |         // Convert to hexidecimal
34 |         $hexi .= sprintf("%02$x ", ord($data[$i]));
35 | 
36 |         // Replace non-viewable bytes with '.'
37 |         if (ord($data[$i]) >= 32) {
38 |             $ascii .= ($htmloutput === true) ?
39 |                             htmlentities($data[$i]) :
40 |                             $data[$i];
41 |         } else {
42 |             $ascii .= '.';
43 |         }
44 | 
45 |         // Add extra column spacing
46 |         if ($j === 7) {
47 |             $hexi  .= ' ';
48 |             $ascii .= ' ';
49 |         }
50 | 
51 |         // Add row
52 |         if (++$j === 16 || $i === $len - 1) {
53 |             // Join the hexi / ascii output
54 |             $dump .= sprintf("%04$x  %-49s  %s", $offset, $hexi, $ascii);
55 |             
56 |             // Reset vars
57 |             $hexi   = $ascii = '';
58 |             $offset += 16;
59 |             $j      = 0;
60 |             
61 |             // Add newline            
62 |             if ($i !== $len - 1) {
63 |                 $dump .= "\n";
64 |             }
65 |         }
66 |     }
67 | 
68 |     // Finish dump
69 |     $dump .= $htmloutput === true ?
70 |                 '
' : 71 | ''; 72 | $dump .= "\n"; 73 | 74 | // Output method 75 | if ($return === false) { 76 | echo $dump; 77 | } else { 78 | return $dump; 79 | } 80 | } 81 | 82 | ?> --------------------------------------------------------------------------------