pax_global_header00006660000000000000000000000064146404354200014514gustar00rootroot0000000000000052 comment=b6569cd4f83e59e772f0e860733b163335c70201 ruby-amqp-bunny-b6569cd/000077500000000000000000000000001464043542000152065ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/.github/000077500000000000000000000000001464043542000165465ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/.github/ISSUE_TEMPLATE.md000066400000000000000000000013271464043542000212560ustar00rootroot00000000000000## Does This Really Belong to GitHub issues? If you find a bug you understand well, poor default, incorrect or unclear piece of documentation, or missing feature, please [file an issue](http://github.com/ruby-amqp/bunny/issues) on GitHub. Please use [Bunny's mailing list](http://groups.google.com/group/ruby-amqp) for questions, investigations, and discussions. GitHub issues should be used for specific, well understood, actionable maintainers and contributors can work on. When filing an issue, please specify * Which Bunny and RabbitMQ versions are used * Recent RabbitMQ log file contents * Full exception stack traces * Steps to reproduce or a failing test case This would greatly help the maintainers help you. ruby-amqp-bunny-b6569cd/.gitignore000066400000000000000000000004131464043542000171740ustar00rootroot00000000000000.DS_Store .*.swp *.class *.rbc *.gem /doc/ .yardoc .rvmrc Gemfile.lock .rbx/* .tags .tags_sorted_by_file .Apple* /bin/* .bundle/* vendor/* playground/* *.org repl-* debug/* *.dump deploy.docs.sh .ruby-version .idea *.srl spec/tls/*.pem spec/tls/*.pem~ spec/tls/*.p12 ruby-amqp-bunny-b6569cd/.rspec000066400000000000000000000000071464043542000163200ustar00rootroot00000000000000-c -fp ruby-amqp-bunny-b6569cd/.travis.yml000066400000000000000000000011641464043542000173210ustar00rootroot00000000000000dist: bionic language: ruby bundler_args: --without development cache: bundler before_install: - gem install bundler before_script: - "./bin/ci/install_on_debian.sh" - until sudo lsof -i:5672; do echo "Waiting for RabbitMQ to start..."; sleep 1; done - "./bin/ci/before_build.sh" script: "bundle exec rake integration_without_recovery" rvm: - ruby-head - "2.7.1" - "2.6.6" - "2.5.8" notifications: email: michael@rabbitmq.com services: - rabbitmq branches: only: - master - 2.17.x-stable - 2.16.x-stable - 2.15.x-stable env: - CI=true matrix: allow_failures: rvm: - ruby-head ruby-amqp-bunny-b6569cd/.yardopts000066400000000000000000000001611464043542000170520ustar00rootroot00000000000000--no-private --protected --markup="markdown" lib/**/*.rb --main README.md --hide-tag todo - LICENSE ChangeLog.md ruby-amqp-bunny-b6569cd/CONTRIBUTING.md000066400000000000000000000133721464043542000174450ustar00rootroot00000000000000## Overview This project **does not** use GitHub issues for questions, investigations, discussions, and so on. Issues are appropriate for something specific enough for a maintainer or contributor to work on: * There should be enough information to reproduce the behavior observed in a reasonable amount of time * It should be reasonably clear why the behavior should be changed and why this cannot or should not be addressed in application code, a separate library and so on All issues that do not satisfy the above properties belong to the [Ruby RabbitMQ clients mailing list](http://groups.google.com/forum/#!forum/ruby-amqp). Pull request that do not satisfy them have a high chance of being closed. ## Submitting a Pull Request Please read the sections below to get an idea about how to run Bunny test suites first. Successfully running all tests, at least with `CI` environment variable exported to `true`, is an important first step for any contributor. Once you have a passing test suite, create a branch and make your changes on it. When you are done with your changes and all tests pass, write a [good, detailed commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) submit a pull request on GitHub. ## Pre-requisites The project uses Bundler for dependency management and requires RabbitMQ `3.5+` to be running locally with the `rabbitmq-management` and `rabbitmq_consistent_hash_exchange` plugins enabled. ### Running the Specs The specs require RabbitMQ to be running locally with a specific set of virtual hosts and users. RabbitMQ can be provisioned and started any way that's convenient to you as long as it has a suitable TLS keys configuration and management plugin enabled. Make sure you have a recent version of RabbitMQ (> `3.7.10`). The test suite can either use a locally available RabbitMQ node ([generic binary builds](http://www.rabbitmq.com/install-generic-unix.html) are an option that works well) or by running a RabbitMQ server in a Docker container. ### Using a locally installed RabbitMQ node It is possible to start a local RabbitMQ node from the repository root. It is not necessarily optimal but can be a good starting point but is a useful example: ``` RABBITMQ_NODENAME=bunny RABBITMQ_CONFIG_FILE=./spec/config/rabbitmq.conf RABBITMQ_ENABLED_PLUGINS_FILE=./spec/config/enabled_plugins rabbitmq-server ``` The specs need the RabbitMQ management plugin to be enabled and include TLS connectivity tests, so the node must be configured to use a [certificate and key pair](http://www.rabbitmq.com/ssl.html#certificates-and-keys). The config and enabled plugin files in the spec/config directory take care of that but certificates must be provisioned locally. By default there's a set of CA, server, and client certificates pre-generated at `spec/tls`. The `BUNNY_CERTIFICATE_DIR` environment variable can be used to a directory containing a CA certificate and a certificate/key pair to be used by the server. The directory can be generated using [tls-gen](https://github.com/michaelklishin/tls-gen)'s basic profile. This option is recommended. `BUNNY_RABBITMQ_HOSTNAME` can be used to override the expected server hostname for [peer verification](http://www.rabbitmq.com/ssl.html#peer-verification) in the TLS test suite: ``` BUNNY_CERTIFICATE_DIR="/path/to/tls-gen/basic/result" BUNNY_RABBITMQ_HOSTNAME="mayflower" bundle exec rspec ``` Certificates can be generated with [tls-gen](https://github.com/michaelklishin/tls-gen)'s basic profile. In that case they include a Subject Alternative Name of `localhost` for improved portability. ### Node Setup There is also a script that preconfigured the node for Bunny tests. It is sufficient to run it once but if RabbitMQ is reset it has to be executed again: ``` RABBITMQ_NODENAME=bunny ./bin/ci/before_build ``` The script uses `rabbitmqctl` and `rabbitmq-plugins` to set up RabbitMQ in a way that Bunny test suites expect. Two environment variables, `RABBITMQCTL` and `RABBITMQ_PLUGINS`, are available to control what `rabbitmqctl` and `rabbitmq-plugins` commands will be used. By default they are taken from `PATH` and prefixed with `sudo`. And then run the core integration suite: ``` RABBITMQ_NODENAME=bunny CI=true rspec ``` #### Running a RabbitMQ server in a Docker container First off you have to [install Docker Compose](https://docker.github.io/compose/install/) (and by proxy Docker). Version >= 1.6.0+ is required for compose version 2 syntax. After those have been installed (and the `docker-compose` command is available on your command line path), run ``` docker-compose build && docker-compose run --service-ports rabbitmq ``` The first time you do this, it will take some time, since it has to download everything it needs to build the Docker image. The RabbitMQ server will run in the foreground in the terminal where you started it. You can stop it by pressing CTRL+C. If you want to run it in the background, pass `-d` to `docker-compose`. ### Toxiproxy If Toxiproxy is running locally on standard ports or started via Docker: ``` docker-compose run --service-ports toxiproxy ``` then Bunny will run additional resiliency tests. ### Running Test Suites Prior to running the tests, configure the RabbitMQ permissions by running `./bin/ci/before_build` if you have RabbitMQ locally installed, if you are running RabbitMQ via Docker as above this step is not required as the setup is baked in. Make sure you have those two installed and then run integration tests: bundle install rake integration It is possible to run all tests: bundle exec rspec It is possible to run only integration and regression tests but exclude unit and stress tests: CI=true bundle exec rspec spec/higher_level_api/ spec/lower_level_api spec/issues spec/higher_level_api/integration/connection_recovery_spec.rb ruby-amqp-bunny-b6569cd/ChangeLog.md000066400000000000000000002100071464043542000173570ustar00rootroot00000000000000## Changes between Bunny 2.22.0 and 2.23.0 (in development) No changes yet. ## Changes between Bunny 2.21.0 and 2.22.0 (June 12, 2023) ### New Connection Callback: `:recovery_attempts_exhausted` A new connection callback, `:recovery_attempts_exhausted`, is invoked when all allowed recovery attempts have failed. Contributed by @Schmitze333. GitHub issue: [#666](https://github.com/ruby-amqp/bunny/pull/666) ### `Bunny::Channel#default_exchange` Caching `Bunny::Channel#default_exchange` now caches the `Bunny::Exchange` instance it returns. GitHub issue: [#661](https://github.com/ruby-amqp/bunny/issues/661) ## Changes between Bunny 2.20.3 and 2.21.0 (June 8, 2023) ### Fixed a Potential Deadlock During Consumer Work Pool Shutdown Contributed by @parhs. GitHub issue: [#664](https://github.com/ruby-amqp/bunny/pull/664) ### Connection Recovery Reliability Improvements Contributed by @womblep. GitHub issue: [#658](https://github.com/ruby-amqp/bunny/pull/658) ## Changes between Bunny 2.20.2 and 2.22.3 (January 25, 2023) ### Make sure Bunny can load in environments with older OpenSSL Bunny 2.20.x failed to load in environments that provide an old version of OpenSSL without TLS 1.3 support. GitHub issue: [#652](https://github.com/ruby-amqp/bunny/issues/652). ## Changes between Bunny 2.20.1 and 2.20.2 (January 12, 2023) ### Correctly propagate updated x-arguments when declaring a queue Contributed by @rene-muehlboeck. Github issue: [#650](https://github.com/ruby-amqp/bunny/issues/650). ## Changes between Bunny 2.20.0 and 2.20.1 (December 19, 2022) Starting with this release, Bunny **targets Ruby installations with TLSv1.3 support**. This means that some older distributions, e.g. Ubuntu 16.04, 18.04, CentOS 7 **will not longer be supported**. Those distributions have usually reached their end of general support (there won't be maintenance releases besides security patches for paying customers of those distributions), so the benefits of TLSv1.3 support outweigh the cons. ### Gracefully Handles a Race Condition Between Server-sent and Client Channel Closure Contributed by @milgner. GitHub issue: [#644](https://github.com/ruby-amqp/bunny/pull/644) ## Changes between Bunny 2.19.x and 2.20.0 (December 15, 2022) ### New `Bunny::Channel` helpers for declaring quorum queues and streams Introduce a few helpers for quorum queues, streams, and durable client-named queues in general, similar in spirit to `Bunny::Channel#temporary_queue` for temporary queues. #### `Bunny::Channel#quorum_queue` `Bunny::Channel#quorum_queue` accepts a name (server-generated names are not supported) and a set of [options arguments](https://www.rabbitmq.com/queues.html#optional-arguments), and declares a [quorum queue](https://www.rabbitmq.com/quorum-queues.html). Durability, exclusivity, and auto-delete properties will be ignored: it only makes sense for [quorum queues](https://www.rabbitmq.com/quorum-queues.html) to be durable, non-exclusive and non-auto-delete since they are [all about data safety](https://www.rabbitmq.com/quorum-queues.html#use-cases). #### `Bunny::Channel#stream` `Bunny::Channel#stream` accepts a name (server-generated names are not supported) and a set of [options arguments](https://www.rabbitmq.com/queues.html#optional-arguments), and declares a [stream](https://www.rabbitmq.com/streams.html) that Bunny can use over AMQP 0-9-1 as if it was a replicated queue (without any [stream-specific operations](https://rabbitmq.com/stream.html)). Durability, exclusivity, and auto-delete properties will be ignored: it only makes sense for [streams](https://www.rabbitmq.com/streams.html) to be durable, non-exclusive and non-auto-delete since they are by definition a durable replicated data structure for non-transient (or at least not entirely transient) data. #### `Bunny::Channel#durable_queue` `Bunny::Channel#durable_queue` accepts a name (server-generated names are not supported), a queue type (one of: `Bunny::Queue::Types::QUORUM`, `Bunny::Queue::Types::CLASSIC`, `Bunny::Queue::Types::STREAM`), and a set of [options arguments](https://www.rabbitmq.com/queues.html#optional-arguments), and declares a [quorum queue](https://www.rabbitmq.com/quorum-queues.html). Durability, exclusivity, and auto-delete properties will be ignored by design, just like `Bunny::Channel#temporary_queue` overrides them to declare transient queues. #### `Bunny::Queue::Types` `Bunny::Queue::Types` is a module with a few constants that represent currently available queue types: * `Bunny::Queue::Types::QUORUM` * `Bunny::Queue::Types::CLASSIC` * `Bunny::Queue::Types::STREAM` Their names are self-explanatory. ### Test Files Left Out of .gem File Test files (specs) are no longer included into the `.gem` file. Contributed by Alexey @alexeyschepin Schepin. GitHub issue: [#621](https://github.com/ruby-amqp/bunny/pull/621) ## Changes between Bunny 2.18.x and 2.19.0 (June 25, 2021) ### Correct Handling of Publisher Confirms with Multiple Flag Set Bunny was invoking a publisher confirms callback excessively when it encountered, wasting CPU cycles for no good reason and potentially resulting in incorrect or confusing publishing application behavior. Contributed by * Vladislav @yurusov Yurusov * Yuri @kinnalru Samoilenko GitHub issue: [#617](https://github.com/ruby-amqp/bunny/pull/617) ## Changes between Bunny 2.17.x and 2.18.0 (May 4, 2021) ### Ruby 3.0 Compatibility Bunny has switched to use a `SortedSet` from a standalone library. As of Ruby 3.0, it is no longer available in the standard library (`set`). ### New Option to Silence TLS-related Warnings A new connection option, `tls_silence_warnings`, silences two warnings: * When TLS is enabled but no client certificate/private key pair is provided * When [peer verification](https://www.rabbitmq.com/ssl.html#peer-verification) is disabled An example: ``` ruby c = Bunny.new("amqps://bunny_gem:bunny_password@hostname/vhost", tls: true, tls_ca_certificates: ["#{CERTIFICATE_DIR}/ca_certificate.pem"], tls_protocol: :TLSv1_2, verify_peer: false, tls_silence_warnings: true) c.start ``` GitHub issue: [#607](https://github.com/ruby-amqp/bunny/issues/607) ### Leaner Gem Bunny gem no longer includes TLS certificates and other Git repository files that are not library or test files. GitHub issue: [#612](https://github.com/ruby-amqp/bunny/issues/612) ## Changes between Bunny 2.16.x and 2.17.0 (Sep 11th, 2020) ### Easier to Specify a Client-Provided Connection Name It is now easier to provide a client-provided (custom) connection name that will be displayed in the RabbitMQ management UI and mentioned in [server logs](https://www.rabbitmq.com/logging.html). Instead of ``` ruby conn = Bunny.new(client_properties: {connection_name: "app ABC #{rand}"}) conn.start ``` a new top-level connection option now can be used: ``` ruby conn = Bunny.new(connection_name: "app ABC #{rand}") conn.start ``` Contributed by @brerx. GitHub issue: [ruby-amqp/bunny#600](https://github.com/ruby-amqp/bunny/pull/600) ## Changes between Bunny 2.15.0 and 2.16.0 (Aug 14th, 2020) ### Asynchronous Exception Delegate Bunny now can delete asynchronous connection (`Bunny::Session`) exception to an arbitrary delegate object. Use the `:session_error_handler` connection setting to pass it. The value defaults to `Thread.current`. Contributed by @bbascarevic. GitHub issue: [ruby-amqp/bunny#597](https://github.com/ruby-amqp/bunny/issues/597) ## Changes between Bunny 2.14.0 and 2.15.0 (Apr 8th, 2020) ### More Defensive Thread Join Operations Bunny is now more defensive around thread join operations which it performs when stopping its consumer work pool. `Thread#join` can cause an unhandled exception to be re-raised at a very surprising moment. This behavior can also be affected by 3rd party libraries, e.g. those that do connection pooling. While Bunny cannot fully avoid every possible surprising failure, it now avoids at least one such problematic interaction triggered by a custom [interrupt handler](https://ruby-doc.org/core-2.5.1/Thread.html#method-c-handle_interrupt) in a 3rd party library. GitHub issue: [#589](https://github.com/ruby-amqp/bunny/issues/589) Contributed by @fuegas. ### Dependency Updates `amq-protocol` dependency has been bumped to `2.3.1` to support `connection.update-secret` protocol extension. ### Gem Installation Fixed on Windows `bin/ci`, a directory with symlinks, is no longer included into the gem. Contributed by Jack Xiaosong Xu. ### Lazy Peer Certificate Chain Information Logging Peer certificate chain information is now logged lazily, which prevents an obscure exception originating ASN.1 parser and makes the logging code evaluate only when it is really necessary. GitHub issue: [#578](https://github.com/ruby-amqp/bunny/pull/578) Contributed by Garrett Thornburg. ## Changes between Bunny 2.13.0 and 2.14.0 (Feb 20th, 2019) ### Improved Peer Verification Failure Logging When [peer verification](https://www.rabbitmq.com/ssl.html#peer-verification) fails, the connection will now log some relevant peer certificate chain details. If Bunny log level is set to `debug`, the same information will be logged unconditionally. ### Closing Connections without Waiting for Response `Bunny::Session#close` now accepts a parameter that controls whether it waits for a `connection.close-ok` frame. Not waiting is useful when it is known for a fact that the node might not respond (it might be shutting down, connection is known to be interrupted or unrecoverable and so on) or waiting is irrelevant to the caller. ### Successful Connection Recovery Notification `Bunny::Session#after_recovery_completed` (accepts a block) and a new connection option, `:recovery_completed` (a callable object) can be used to react to successful connection and topology recovery. GitHub issue: [#573](https://github.com/ruby-amqp/bunny/pull/573). Contributed by Ionut Popa. ### effin_utf8 Dependency Dropped This library no longer supports Ruby 1.8 and thus doesn't need to depend on the `effin_utf8` gem. Contributed by Luciano Sousa. ## Changes between Bunny 2.12.0 and 2.13.0 (Dec 25th, 2018) ### More Defensive `Bunny::Channel` Method(s) `Bunny::Channel#queue` will now throw an `ArgumentError` if a `nil` is passed for queue name. GitHub issue: [#570](https://github.com/ruby-amqp/bunny/issues/570) ### Correct Logging of Recovery Attempts Left During connection recovery, if `recover_attempts` is not set (is `nil`) connection could produce confusing log messages. GitHub issue: [#569](https://github.com/ruby-amqp/bunny/issues/569) ## Changes between Bunny 2.11.0 and 2.12.0 (Sep 22nd, 2018) ### More Defensive Treatment of `queue.declare-ok` Responses Responses for `queue.declare` are now checked against a memoized queue name (but only if the queue is not server-named). This helps avoids scenarios with overlapping/concurrent requests due to high network latency as demonstrated in [#558](https://github.com/ruby-amqp/bunny/issues/558). "Mismatched" responses will be ignored: Bunny channel API would throw an exception for such declarations and there would be no way to "return to" even if a matching response arrived and was matched with one of the pending requests in a reasonable period of time. As part of this work a new Toxiproxy-based test suite was introduced to Bunny. GitHub issue: [#558](https://github.com/ruby-amqp/bunny/issues/558) Reproduction steps contributed by Brian Morton and Scott Bonebraker. ### I/O Exceptions from Heartbeat Sender are Now Silent Heartbeat sender's purpose is to notify the peer, not so much to detect local connectivity failures; those will be detected by the I/O loop and transport. For single threaded connection users that prefer to roll their own recovery strategies getting exceptions from the heartbeat sender was counterproductive and painful to deal with. As part of this work a new Toxiproxy-based test suite was introduced to Bunny. GitHub issue: [#559](https://github.com/ruby-amqp/bunny/issues/559) Contributed by Scott Bonebraker. ### Correct Connection State on Connections that Experienced Missed Heartbeat Connections that experienced connection closure did not always correctly transition to the closed state. `Bunny::ConnectionClosedError` will now be thrown when an operation is attempted on such connections. GitHub issue: [#561](https://github.com/ruby-amqp/bunny/issues/561) Contributed by Scott Bonebraker. ### Connection Recovery Will Fail When Max Retry Attempt Limit is Exceeded GitHub issue: [#549](https://github.com/ruby-amqp/bunny/issues/549) Contributed by Arlandis Word. ### Squashed Warnings Many warnings have been eliminated. GitHub issue: [#563](https://github.com/ruby-amqp/bunny/issues/563) Contributed by @dacto. ### API Reference Corrections GitHub issue: [#557](https://github.com/ruby-amqp/bunny/pull/557) Contributed by Bruno Costa. ## Changes between Bunny 2.10.0 and 2.11.0 (Jun 21st, 2018) ### More Reliable System-wide Trusted Certificate Directory Detection Bunny no longer tries to compile a list of trusted CA certificates on its own. Instead it uses an OpenSSL API method that makes OpenSSL set the path(s), which should cover more platforms and be forward- and backward-compatible. GitHub issue: [#555](https://github.com/ruby-amqp/bunny/issues/555). Contributed by Ana María Martínez Gómez. ## Changes between Bunny 2.9.0 and 2.10.0 (Jun 5th, 2018) `2.10.0` is a maintenance release that introduces a couple of **minor potentially breaking changes**. ### Disabling Heartbeats Also Disables TCP Socket Read Timeouts Disabling heartbeats will now disable TCP socket read timeouts. They go hand in hand and users who prefer TCP keepalives via kernel configuration previously had to also explicitly configure a zero read timeout. GitHub issue: [#551](https://github.com/ruby-amqp/bunny/pull/551). Contributed by Carl Hörberg. ### `verify_peer: false` Has the Expected Effect Again Make sure `verify_peer: false` has the expected effect again. Default value of connection's `:verify_peer` option to `true` only when all of `:verify_ssl`, `:verify_peer`, and `:verify` are `nil`. GitHub issue: [#541](https://github.com/ruby-amqp/bunny/issues/541). Contributed by Howard Ding. ### Maximum Number of Channels Limited to 2K by Default Default maximum number of channels is limited to 2047 to reduce the probability of severe channel leaks. See [rabbitmq/rabbitmq-server#1593](https://github.com/rabbitmq/rabbitmq-server/issues/1593) for details. Applications that want to use more channels per connection can still configure a higher value using the `channel_max` setting (for both Bunny and RabbitMQ server). GitHub issue: [#553](https://github.com/ruby-amqp/bunny/pull/553). ### Squashed Some Warnings GitHub issue: [#552](https://github.com/ruby-amqp/bunny/pull/552). Contributed by @utilum. ### Disabling Heartbeats Disables TCP Socket Read Timeouts Disabling heartbeats will also disable TCP socket read timeouts, since the two are effectively interconnected. In this case a mechanism such as [TCP keepalives](http://www.rabbitmq.com/heartbeats.html#tcp-keepalives) is assumed to be used. See [RabbitMQ heartbeats guide](http://www.rabbitmq.com/heartbeats.html) for a more detailed overview of the options. GH issue: [#519](https://github.com/ruby-amqp/bunny/issues/519). Contributed by Carl Hörberg. ## Changes between Bunny 2.8.0 and 2.9.0 (Jan 8th, 2018) ### Ruby 2.2 Requirement Bunny now requires Ruby 2.2. ### Connection Recovery Now Retries on Timeouts Connection recovery now will retry on TCP connection timeouts. GitHub issue: [#537](https://github.com/ruby-amqp/bunny/pull/537). ### More URI Query Parameters Bunny now supports more URI query parameters plus aliases that are identical to those of the server. Contributed by Andrew Babichev. GitHub issue: [#534](https://github.com/ruby-amqp/bunny/pull/534) ## Changes between Bunny 2.7.0 and 2.8.0 (Dec 18th, 2018) This release has **minor breaking public API changes**. ### `Bunny::Channel#close` on a Closed Channel Now Raises a Sensible Exception `Bunny::Channel#close` on an already closed channel will now raise a sensible exception. If the channel was closed due to a channel-level protocol exception, that exception will be mentioned. GitHub issue: [#528](https://github.com/ruby-amqp/bunny/issues/528), see [9df7cb](https://github.com/ruby-amqp/bunny/commit/9df7cb04d9ff12b1af62a11e239fd81e5472c872) for details. ### JRuby 9K Compatibility A JRuby 9K compatibility issue was corrected by Marian Posăceanu. Note that JRuby users are recommended to use [March Hare](http://rubymarchhare.info/), a JRuby-oriented client, instead of Bunny. GitHub issue: [#529](https://github.com/ruby-amqp/bunny/pull/529) ### Connection Exceptions are Logged as Warning with Automatic Recovery When automatic recovery is enabled, connection errors are now logged as warnings and not errors. Contributed by Merten Falk. GitHub issue: [#531](https://github.com/ruby-amqp/bunny/pull/531) ### Server Heartbeat Value as a String It is now possible to specify a server-defined heartbeat value as a string (`"server"`), not just a symbol. This makes it easier to load settings from YAML files. Contributed by Tyrone Wilson. GitHub issue: [#524](https://github.com/ruby-amqp/bunny/pull/524) ## Changes between Bunny 2.7.0 and 2.7.1 (Sep 25th, 2017) ### Sensible Socket Read Timeouts When RabbitMQ is Configured to Disabled Heartbeats Bunny now correctly handles scenarios where server is configured to disable heartbeats (which is a terrible idea, don't do it!) GitHub issue: [#519](https://github.com/ruby-amqp/bunny/issues/519). ### Bunny::Channel#basic_get Usability `Bunny::Channel#basic_get` invoked with a non-existent queue now throws a channel exception instead of a generic operation timeout. GitHub issue: [#518](https://github.com/ruby-amqp/bunny/issues/518). ### Spec Suite Improvements `BUNNY_CERTIFICATE_DIR` environment variable now can be used to override local CA and client certificate/key pair directory. The directory is expected to be the result directory generated by the basic [tls-gen](http://github.com/michaelklishin/tls-gen) profile. TLSv1.0 is no longer used in tests because it's being disabled by default by more and more installations as it has known vulnerabilities and is no longer considered to be acceptable by several compliance standards (e.g. PCI DSS). ### Improved Synchronisation for channel.close Handlers `channel.close` handler will now acquire a lock . This avoids concurrency hazards in some rare scenarios when a channel is closed due a protocol exception by the server and concurrently opened by user code at the same time. ### More Meaningful Error Messages in Bunny::Session#create_channel Sometimes users attempt to open a channel on a connection that isn't connected yet because `Bunny::Session#start` was never invoked. `Bunny::Session#create_channel` will now provide a more sensible exception message in those cases. ## Changes between Bunny 2.6.0 and 2.7.0 (May 11th, 2017) ### amq-protocol Update Minimum `amq-protocol` version is now [`2.2.0`](https://github.com/ruby-amqp/amq-protocol/blob/master/ChangeLog.md#changes-between-210-and-220-may-11th-2017) which includes a change in [how timestamps are encoded](https://github.com/ruby-amqp/amq-protocol/issues/64). ### `Bunny::ContinuationQueue#poll` Less Prone to Race Conditions `Bunny::ContinuationQueue#poll` was reworked with feedback from Joseph Wong. GitHub issue: [#462](https://github.com/ruby-amqp/bunny/issues/462) ### Recovery Attempt Counting Strategy Changed Previous behehavior is not unreasonable but is not what many users and even RabbitMQ team members come to expect. Therefore it can be considered a bug. Previously a reconnection counter was preserved between successful recoveries. This made the integration test that uses server-sent connection.close possible. With this change, the counter is reset after successful reconnection but there's an option to go back to the original behavior. We also do a hell of a lot more logging. GitHub issue: [#408](https://github.com/ruby-amqp/bunny/issues/408) ### Absolute Windows File Paths are No Longer treated as Inline Certs Contributed by Jared Smartt. GitHub issue: [#492](https://github.com/ruby-amqp/bunny/issues/492). ### Opening a Channel on an Intentionally Closed Connection Immediately Raises an Exception Contributed by Alessandro Verlato. GitHub issue: [#465](https://github.com/ruby-amqp/bunny/issues/465) ### Bunny::ConsumerWorkPool#shutdown Terminates Early When It's Safe to Do So `Bunny::ConsumerWorkPool#shutdown(true)` waited for consumer shutdown even if the pool wasn't active (there were no consumers on its channel). GitHub issue: [#438](https://github.com/ruby-amqp/bunny/issues/438). ### Retry on new Ruby 2.1+ variations of `EAGAIN`, `EWOULDBLOCK` GitHub issue: [#456](https://github.com/ruby-amqp/bunny/issues/456) ### Do Not Modify Host Arrays Bunny now can work with frozen host arrays. GitHub issue: [#446](https://github.com/ruby-amqp/bunny/issues/446) ## Changes between Bunny 2.5.0 and 2.6.0 (October 15th, 2016) ### Graceful Shutdown of Consumers Consumer work pool will now allow for a grace period before stopping pool threads so that delivery processing in progress can have a chance to finish. GitHub issue: [#437](https://github.com/ruby-amqp/bunny/pull/437) Contributed by Stefan Sedich. ### `Bunny::Channel#wait_for_confirms` Now Throws When Used on a Closed Channel GitHub issue: [#428](https://github.com/ruby-amqp/bunny/pull/428) Contributed by Dimitar Dimitrov. ### Race Condition Eliminated in `Bunny::Channel#wait_for_confirms` GitHub issue: [#424](https://github.com/ruby-amqp/bunny/issues/424) Contributed by Dimitar Dimitrov. ### More Defensive Consumer Work Pool `Bunny::ConsumerWorkPool#join` and `Bunny::ConsumerWorkPool#pause` no longer fails with a `NoMethodError` on nil when executed on a work pool that doesn't have active threads (consumers). This change is largely cosmetic and won't affect the majority of of projects in any way. ## Changes between Bunny 2.4.0 and 2.5.0 (July 20th, 2016) ### Exchange Bindings are Now Correctly Recovered GitHub issue: [#410](https://github.com/ruby-amqp/bunny/issues/410) Contributed by Andrew Bruce. ### `Bunny::Channel#wait_for_confirms` Awaits While There're Outstanding Unconfirmed Messages GitHub issue: [#424](https://github.com/ruby-amqp/bunny/issues/424) Contributed by Dimitar Dimitrov. ### Queue Recovery Respects the `:no_declare` Option Queue recovery now respects the `:no_declare` option. ### `Bunny::Channel#wait_for_confirms` Throws Early `Bunny::Channel#wait_for_confirms` now throws an exception early when invoked on a closed channel. GitHub issue: [#428](https://github.com/ruby-amqp/bunny/pull/428). Contributed by Dimitar Dimitrov. ## Changes between Bunny 2.3.0 and 2.4.0 (June 11th, 2016) **This release includes minor breaking API changes**. ### Unconfirmed Delivery Tag Set Reset on Network Recovery Channels will now reset their unconfirmed delivery tag set after recovery. GitHub issue: [#406](https://github.com/ruby-amqp/bunny/pull/406) Contributed by Bill Ruddock. ### Support (Quoted) IPv6 Addresses in Address Lists GitHub issue: [#383](https://github.com/ruby-amqp/bunny/issues/383). Contributed by Jeremy Heiler. ### Transport#read_fully Doesn't Try to Recover Since transport is replaced by a recovering connection anyway, and this produces confusing errors up the stack. GitHub issue: [#359](https://github.com/ruby-amqp/bunny/issues/359) Contributed by Donal McBreen. ### Client-Provided Session `:properties` Merged with Defaults Client-Provided Session `:properties` will now be merged with defaults instead of replacing them. This makes it much more convenient to override a single key. ### More Predictable RABBITMQ_URL Handling **This is a breaking API change**. `RABBITMQ_URL` no longer will be used if any other connection options are provided. This makes it possible to use `RABBITMQ_URL` for some connections and options for others in a single OS process. GitHub issue: [#403](https://github.com/ruby-amqp/bunny/pull/403) Contributed by Jimmy Petersen. ## Changes between Bunny 2.2.0 and 2.3.0 (Feb 26th, 2016) ### Thread#abort_on_exception Setting for Consumer Work Pool Threads `Bunny::Session#create_channel` now supports a 3rd argument that, when set to `true`, makes consumer work pool threads to have `Thread#abort_on_exception` set on them. GH issue: [#382](https://github.com/ruby-amqp/bunny/pull/382) Contributed by Seamus Abshere. ### Explicit Transport Closure on Recovery Bunny now will explicitly close previosly used transport before starting connection recovery. GitHub issue: [#377](https://github.com/ruby-amqp/bunny/pull/377). Contributed by bkanhoopla. ### No TLS Socket Double-init Makes sure that TLS sockets are not double-initialized. GH issue: [#345](https://github.com/ruby-amqp/bunny/issues/345). Contributed by Carl Hörberg. ### Lazily Evaluated Debug Log Strings GH issue: [#375](https://github.com/ruby-amqp/bunny/pull/375) Contributed by Omer Katz. ## Changes between Bunny 2.1.0 and 2.2.0 (Sep 6th, 2015) ### Add :addresses to connect options Before this the connection options only allowed multiple hosts, an address is a combination of a host and a port. This makes it possible to specify different hosts with different ports. Contributed by Bart van Zon (Tele2). ### Recover from connection.close by default Bunny will now try to reconnect also when server sent connection.close is received, e.g. when a server is restarting (but also when the connection is force closed by the server). This is in-line with how many other clients behave. The old default was `recover_from_connection_close: false`. Contributed by Carl Hörberg (CloudAMQP). ## Changes between Bunny 2.0.0 and 2.1.0 Bunny 2.1.0 has an **important breaking change**. It is highly advised that 2.1.0 is not mixed with earlier versions of Bunny in case your applications include **integers in message headers**. ### Integer Value Serialisation in Headers Integer values in headers are now serialised as signed 64-bit integers. Previously they were serialised as 32-bit unsigned integers, causing both underflows and overflows: incorrect values were observed by consumers. It is highly advised that 2.1.0 is not mixed with earlier versions of Bunny in case your applications include integers in message headers. If that's not the case, Bunny 2.1 will integeroperate with any earlier version starting with 0.9.0 just fine. Popular clients in other languages (e.g. Java and .NET) will interoperate with Bunny 2.1.0 without issues. ### Explicit Ruby 2.0 Requirement Bunny now requires Ruby 2.0 in the gemspec. Contributed by Carl Hörberg. ### JRuby Fix Bunny runs again on JRuby. Note that JRuby users are strongly advised to use March Hare instead. Contributed by Teodor Pripoae. ## Changes between Bunny 1.7.0 and 2.0.0 Bunny `2.0` doesn't have any breaking API changes but drops Ruby 1.8 and 1.9 (both EOL'ed) support, hence the version. ### Minimum Required Ruby Version is 2.0 Bunny `2.0` requires Ruby 2.0 or later. ## Non-Blocking Writes Bunny now uses non-blocking socket writes, uses a reduced number of writes for message publishing (frames are batched into a single write), and handles TCP back pressure from RabbitMQ better. Contributed by Irina Bednova and Michael Klishin. ### Reduced Timeout Use `Bunny::ContinuationQueue#poll` no longer relies on Ruby's `Timeout` which has numerous issues, including starting a new "interruptor" thread per operation, which is far from efficient. Contributed by Joe Eli McIlvain and Carl Hörberg. ### Capped Number of Connection Recovery Attempts `:recovery_attempts` is a new option that limits the number of connection recovery attempts performed by Bunny. `nil` means "no limit". Contributed by Irina Bednova. ### Bunny::Channel#basic_ack and Related Methods Improvements `Bunny::Channel#basic_ack`, `Bunny::Channel#basic_nack`, and `Bunny::Channel#basic_reject` now adjust delivery tags between connection recoveries, as well as have a default value for the second argument. Contributed by Wayne Conrad. ### Logger Output Remains Consistent Setting the `@logger.progname` attribute changes the output of the logger. This is not expected behaviour when the client provides a custom logger. Behaviour remains unchainged when the internally initialized logger is used. Contributed by Justin Carter. ### prefetch_count is Limited to 65535 Since `basic.qos`'s `prefetch_count` field is of type `short` in the protocol, Bunny must enforce its maximum allowed value to `2^16 - 1` to avoid confusing issues due to overflow. ### Per-Consumer and Per-Channel Prefetch Recent RabbitMQ versions support `basic.qos` `global` flag, controlling whether `prefetch` applies per-consumer or per-channel. Bunny `Channel#prefetch` now allows flag to be set as optional parameter, with the same default behaviour as before (per-consumer). Contributed by tiredpixel. ## Changes between Bunny 1.6.0 and 1.7.0 ### TLS Peer Verification Enabled by Default When using TLS, peer verification is now enabled by default. It is still possible to [disable verification](http://rubybunny.info/articles/tls.html), e.g. for convenient development locally. Peer verification is a means of protection against man-in-the-middle attacks and is highly recommended in production settings. However, it can be an inconvenience during local development. We believe it's time to have the default to be more secure. Contributed by Michael Klishin (Pivotal) and Andre Foeken (Nedap). ### Higher Default Connection Timeout Default connection timeout has been increased to 25 seconds. The older default of 5 seconds wasn't sufficient in some edge cases with DNS resolution (e.g. when primary DNS server is down). The value can be overriden at connection time. Contributed by Yury Batenko. ### Socket Read Timeout No Longer Set to 0 With Disabled Heartbeats GH issue: [#267](https://github.com/ruby-amqp/bunny/pull/267). ### JRuby Writes Fixes On JRuby, Bunny reverts back to using plain old `write(2)` for writes. The CRuby implementation on JRuby suffers from I/O incompatibilities. Until JRuby Bunny users who run on JRuby are highly recommended to switch to [March Hare](http://rubymarchhare.info), which has nearly identical API and is significantly more efficient. ### Bunny::Session#with_channel Synchornisation Improvements `Bunny::Session#with_channel` is now fully synchronised and won't run into `COMMAND_INVALID` errors when used from multiple threads that share a connection. ## Changes between Bunny 1.5.0 and 1.6.0 ### TLSv1 by Default TLS connections now prefer TLSv1 (or later, if available) due to the recently discovered [POODLE attack](https://www.openssl.org/~bodo/ssl-poodle.pdf) on SSLv3. Contributed by Michael Klishin (Pivotal) and Justin Powers (Desk.com). GH issues: * [#259](https://github.com/ruby-amqp/bunny/pull/259) * [#260](https://github.com/ruby-amqp/bunny/pull/260) * [#261](https://github.com/ruby-amqp/bunny/pull/261) ### Socket Read and Write Timeout Improvements Bunny now sets a read timeout on the sockets it opens, and uses `IO.select` timeouts as the most reliable option available on Ruby 1.9 and later. GH issue: [#254](https://github.com/ruby-amqp/bunny/pull/254). Contributed by Andre Foeken (Nedap). ### Inline TLS Certificates Support TLS certificate options now accept inline certificates as well as file paths. GH issues: [#255](https://github.com/ruby-amqp/bunny/pull/255), [#256](https://github.com/ruby-amqp/bunny/pull/256). Contributed by Will Barrett (Sqwiggle). ## Changes between Bunny 1.4.0 and 1.5.0 ### Improved Uncaught Exception Handler Uncaught exception handler now provides more information about the exception, including its caller (one more stack trace line). Contributed by Carl Hörberg (CloudAMQP). ### Convenience Method for Temporary (Server-named, Exclusive) Queue Declaration `Bunny::Channel#temporary_queue` is a convenience method that declares a new server-named exclusive queue: ``` ruby q = ch.temporary_queue ``` Contributed by Daniel Schierbeck (Zendesk). ### Recovery Reliability Improvements Automatic connection recovery robustness improvements. Contributed by Andre Foeken (Nedap). ### Host Lists It is now possible to pass the `:hosts` option to `Bunny.new`/`Bunny::Session#initialize`. When connection to RabbitMQ (including during connection recovery), a random host will be chosen from the list. Connection shuffling and robustness improvements. Contributed by Andre Foeken (Nedap). ### Default Channel Removed Breaks compatibility with Bunny 0.8.x. `Bunny:Session#default_channel` was removed. Please open channels explicitly now, as all the examples in the docs do. ## Changes between Bunny 1.3.0 and 1.4.0 ### Channel#wait_for_confirms Returns Immediately If All Publishes Confirmed Contributed by Matt Campbell. ### Publisher Confirms is In Sync After Recovery When a connection is recovered, the sequence counter resets on the broker, but not the client. To keep things in sync the client must store a confirmation offset after a recovery. Contributed by Devin Christensen. ### NoMethodError on Thread During Shutdown During abnormal termination, `Bunny::Session#close` no longer tries to call the non-existent `terminate_with` method on its origin thread. ## Changes between Bunny 1.2.0 and 1.3.0 ### TLS Can Be Explicitly Disabled TLS now can be explicitly disabled even when connecting (without TLS) to the default RabbitMQ TLS/amqps port (5671): ``` ruby conn = Bunny.new(:port => 5671, :tls => false) ``` Contributed by Muhan Zou. ### Single Threaded Connections Raise Shutdown Exceptions Single threaded Bunny connections will now raise exceptions that occur during shutdown as is (instead of trying to shut down I/O loop which only threaded ones have). Contributed by Carl Hörberg. ### Synchronization Improvements for Session#close `Bunny::Session#close` now better synchronizes state transitions, eliminating a few race condition scenarios with I/O reader thread. ### Bunny::Exchange.default Fix `Bunny::Exchange.default` no longer raises an exception. Note that it is a legacy compatibility method. Please use `Bunny::Channel#default_exchange` instead. Contributed by Justin Litchfield. GH issue [#211](https://github.com/ruby-amqp/bunny/pull/211). ### Bunny::Queue#pop_as_hash Removed `Bunny::Queue#pop_as_hash`, which was added to ease migration to Bunny 0.9, was removed. ### Bunny::Queue#pop Wraps Metadata `Bunny::Queue#pop` now wraps `basic.get-ok` and message properties into `Bunny::GetResponse` and `Bunny::MessageProperties`, just like `basic.consume` deliveries. GH issue: [#212](https://github.com/ruby-amqp/bunny/issues/212). ### Better Synchronization for Publisher Confirms Publisher confirms implementation now synchronizes unconfirmed set better. Contributed by Nicolas Viennot. ### Channel Allocation After Recovery Channel id allocator is no longer reset after recovery if there are channels open. Makes it possible to open channels on a recovered connection (in addition to the channels it already had). ## Changes between Bunny 1.1.0 and 1.2.0 ### :key Supported in Bunny::Channel#queue_bind It is now possible to use `:key` (which Bunny versions prior to 0.9 used) as well as `:routing_key` as an argument to `Bunny::Queue#bind`. ### System Exceptions Not Rescued by the Library Bunny now rescues `StandardError` instead of `Exception` where it automatically does so (e.g. when dispatching deliveries to consumers). Contributed by Alex Young. ### Initial Socket Connection Timeout Again Raises Bunny::TCPConnectionFailed Initial socket connection timeout again raises `Bunny::TCPConnectionFailed` on the connection origin thread. ### Thread Leaks Plugged `Bunny::Session#close` on connections that have experienced a network failure will correctly clean up I/O and heartbeat sender threads. Contributed by m-o-e. ### Bunny::Concurrent::ContinuationQueue#poll Rounding Fix `Bunny::Concurrent::ContinuationQueue#poll` no longer floors the argument to the nearest second. Contributed by Brian Abreu. ### Routing Key Limit Per AMQP 0-9-1 spec, routing keys cannot be longer than 255 characters. `Bunny::Channel#basic_publish` and `Bunny::Exchange#publish` now enforces this limit. ### Nagle's Algorithm Disabled Correctly Bunny now properly disables [Nagle's algorithm](http://boundary.com/blog/2012/05/02/know-a-delay-nagles-algorithm-and-you/) on the sockets it opens. This likely means significantly lower latency for workloads that involve sending a lot of small messages very frequently. [Contributed](https://github.com/ruby-amqp/bunny/pull/187) by Nelson Gauthier (AirBnB). ### Internal Exchanges Exchanges now can be declared as internal: ``` ruby ch = conn.create_channel x = ch.fanout("bunny.tests.exchanges.internal", :internal => true) ``` Internal exchanges cannot be published to by clients and are solely used for [Exchange-to-Exchange bindings](http://rabbitmq.com/e2e.html) and various plugins but apps may still need to bind them. Now it is possible to do so with Bunny. ### Uncaught Consumer Exceptions Uncaught consumer exceptions are now handled by uncaught exceptions handler that can be defined per channel: ``` ruby ch.on_uncaught_exception do |e, consumer| # ... end ``` ## Changes between Bunny 1.1.0.rc1 and 1.1.0 ### Synchronized Session#create_channel and Session#close_channel Full bodies of `Bunny::Session#create_channel` and `Bunny::Session#close_channel` are now synchronized, which makes sure concurrent `channel.open` and subsequent operations (e.g. `exchange.declare`) do not result in connection-level exceptions (incorrect connection state transitions). ### Corrected Recovery Log Message Bunny will now use actual recovery interval in the log. Contributed by Chad Fowler. ## Changes between Bunny 1.1.0.pre2 and 1.1.0.rc1 ### Full Channel State Recovery Channel recovery now involves recovery of publisher confirms and transaction modes. ### TLS Without Peer Verification Bunny now successfully performs TLS upgrade when peer verification is disabled. Contributed by Jordan Curzon. ### Bunny::Session#with_channel Ensures the Channel is Closed `Bunny::Session#with_channel` now makes sure the channel is closed even if provided block raises an exception Contributed by Carl Hoerberg. ### Channel Number = 0 is Rejected `Bunny::Session#create_channel` will now reject channel number 0. ### Single Threaded Mode Fixes Single threaded mode no longer fails with ``` undefined method `event_loop' ``` ## Changes between Bunny 1.1.0.pre1 and 1.1.0.pre2 ### connection.tune.channel_max No Longer Overflows `connection.tune.channel_max` could previously be configured to values greater than 2^16 - 1 (65535). This would result in a silent overflow during serialization. The issue was harmless in practice but is still a bug that can be quite confusing. Bunny now caps max number of channels to 65535. This allows it to be forward compatible with future RabbitMQ versions that may allow limiting total # of open channels via server configuration. ### amq-protocol Update Minimum `amq-protocol` version is now `1.9.0` which includes bug fixes and performance improvements for channel ID allocator. ### Thread Leaks Fixes Bunny will now correctly release heartbeat sender when allocating a new one (usually happens only when connection recovers from a network failure). ## Changes between Bunny 1.0.0 and 1.1.0.pre1 ### Versioned Delivery Tag Fix Versioned delivery tag now ensures all the arguments it operates (original delivery tag, atomic fixnum instances, etc) are coerced to `Integer` before comparison. GitHub issues: #171. ### User-Provided Loggers Bunny now can use any logger that provides the same API as Ruby standard library's `Logger`: ``` ruby require "logger" require "stringio" io = StringIO.new # will log to `io` Bunny.new(:logger => Logger.new(io)) ``` ### Default CA's Paths Are Disabled on JRuby Bunny uses OpenSSL provided CA certificate paths. This caused problems on some platforms on JRuby (see [jruby/jruby#155](https://github.com/jruby/jruby/issues/1055)). To avoid these issues, Bunny no longer uses default CA certificate paths on JRuby (there are no changes for other Rubies), so it's necessary to provide CA certificate explicitly. ### Fixes CPU Burn on JRuby Bunny now uses slightly different ways of continuously reading from the socket on CRuby and JRuby, to prevent abnormally high CPU usage on JRuby after a certain period of time (the frequency of `EWOULDBLOCK` being raised spiked sharply). ## Changes between Bunny 1.0.0.rc2 and 1.0.0.rc3 ### [Authentication Failure Notification](http://www.rabbitmq.com/auth-notification.html) Support `Bunny::AuthenticationFailureError` is a new auth failure exception that subclasses `Bunny::PossibleAuthenticationFailureError` for backwards compatibility. As such, `Bunny::PossibleAuthenticationFailureError`'s error message has changed. This extension is available in RabbitMQ 3.2+. ### Bunny::Session#exchange_exists? `Bunny::Session#exchange_exists?` is a new predicate that makes it easier to check if a exchange exists. It uses a one-off channel and `exchange.declare` with `passive` set to true under the hood. ### Bunny::Session#queue_exists? `Bunny::Session#queue_exists?` is a new predicate that makes it easier to check if a queue exists. It uses a one-off channel and `queue.declare` with `passive` set to true under the hood. ### Inline TLS Certificates and Keys It is now possible to provide inline client certificate and private key (as strings) instead of filesystem paths. The options are the same: * `:tls` which, when set to `true`, will set SSL context up and switch to TLS port (5671) * `:tls_cert` which now can be a client certificate (public key) in PEM format * `:tls_key` which now can be a client key (private key) in PEM format * `:tls_ca_certificates` which is an array of string paths to CA certificates in PEM format For example: ``` ruby conn = Bunny.new(:tls => true, :tls_cert => ENV["TLS_CERTIFICATE"], :tls_key => ENV["TLS_PRIVATE_KEY"], :tls_ca_certificates => ["./examples/tls/cacert.pem"]) ``` ## Changes between Bunny 1.0.0.rc1 and 1.0.0.rc2 ### Ruby 1.8.7 Compatibility Fixes Ruby 1.8.7 compatibility fixes around timeouts. ## Changes between Bunny 1.0.0.pre6 and 1.0.0.rc1 ### amq-protocol Update Minimum `amq-protocol` version is now `1.8.0` which includes a bug fix for messages exactly 128 Kb in size. ### Add timeout Bunny::ConsumerWorkPool#join `Bunny::ConsumerWorkPool#join` now accepts an optional timeout argument. ## Changes between Bunny 1.0.0.pre5 and 1.0.0.pre6 ### Respect RABBITMQ_URL value `RABBITMQ_URL` env variable will now have effect even if Bunny.new is invoked without arguments. ## Changes between Bunny 1.0.0.pre4 and 1.0.0.pre5 ### Ruby 1.8 Compatibility Bunny is Ruby 1.8-compatible again and no longer references `RUBY_ENGINE`. ### Bunny::Session.parse_uri `Bunny::Session.parse_uri` is a new method that parses connection URIs into hashes that `Bunny::Session#initialize` accepts. ``` ruby Bunny::Session.parse_uri("amqp://user:pwd@broker.eng.megacorp.local/myapp_qa") ``` ### Default Paths for TLS/SSL CA's on All OS'es Bunny now uses OpenSSL to detect default TLS/SSL CA's paths, extending this feature to OS'es other than Linux. Contributed by Jingwen Owen Ou. ## Changes between Bunny 1.0.0.pre3 and 1.0.0.pre4 ### Default Paths for TLS/SSL CA's on Linux Bunny now will use the following TLS/SSL CA's paths on Linux by default: * `/etc/ssl/certs/ca-certificates.crt` on Ubuntu/Debian * `/etc/ssl/certs/ca-bundle.crt` on Amazon Linux * `/etc/ssl/ca-bundle.pem` on OpenSUSE * `/etc/pki/tls/certs/ca-bundle.crt` on Fedora/RHEL and will log a warning if no CA files are available via default paths or `:tls_ca_certificates`. Contributed by Carl Hörberg. ### Consumers Can Be Re-Registered From Bunny::Consumer#handle_cancellation It is now possible to re-register a consumer (and use any other synchronous methods) from `Bunny::Consumer#handle_cancellation`, which is now invoked in the channel's thread pool. ### Bunny::Session#close Fixed for Single Threaded Connections `Bunny::Session#close` with single threaded connections no longer fails with a nil pointer exception. ## Changes between Bunny 1.0.0.pre2 and 1.0.0.pre3 This release has **breaking API changes**. ### Safe[r] basic.ack, basic.nack and basic.reject implementation Previously if a channel was recovered (reopened) by automatic connection recovery before a message was acknowledged or rejected, it would cause any operation on the channel that uses delivery tags to fail and cause the channel to be closed. To avoid this issue, every channel keeps a counter of how many times it has been reopened and marks delivery tags with them. Using a stale tag to ack or reject a message will produce no method sent to RabbitMQ. Note that unacknowledged messages will be requeued by RabbitMQ when connection goes down anyway. This involves an API change: `Bunny::DeliveryMetadata#delivery_tag` is now and instance of a class that responds to `#tag` and `#to_i` and is accepted by `Bunny::Channel#ack` and related methods. Integers are still accepted by the same methods. ## Changes between Bunny 1.0.0.pre1 and 1.0.0.pre2 ### Exclusivity Violation for Consumers Now Raises a Reasonable Exception When a second consumer is registered for the same queue on different channels, a reasonable exception (`Bunny::AccessRefused`) will be raised. ### Reentrant Mutex Implementation Bunny now allows mutex impl to be configurable, uses reentrant Monitor by default. Non-reentrant mutexes is a major PITA and may affect code that uses Bunny. Avg. publishing throughput with Monitor drops slightly from 5.73 Khz to 5.49 Khz (about 4% decrease), which is reasonable for Bunny. Apps that need these 4% can configure what mutex implementation is used on per-connection basis. ### Eliminated Race Condition in Bunny::Session#close `Bunny::Session#close` had a race condition that caused (non-deterministic) exceptions when connection transport was closed before connection reader loop was guaranteed to have stopped. ### connection.close Raises Exceptions on Connection Thread Connection-level exceptions (including when a connection is closed via management UI or `rabbitmqctl`) will now be raised on the connection thread so they * can be handled by applications * do not start connection recovery, which may be uncalled for ### Client TLS Certificates are Optional Bunny will no longer require client TLS certificates. Note that CA certificate list is still necessary. If RabbitMQ TLS configuration requires peer verification, client certificate and private key are mandatory. ## Changes between Bunny 0.9.0 and 1.0.0.pre1 ### Publishing Over Closed Connections Publishing a message over a closed connection (during a network outage, before the connection is open) will now correctly result in an exception. Contributed by Matt Campbell. ### Reliability Improvement in Automatic Network Failure Recovery Bunny now ensures a new connection transport (socket) is initialized before any recovery is attempted. ### Reliability Improvement in Bunny::Session#create_channel `Bunny::Session#create_channel` now uses two separate mutexes to avoid a (very rare) issue when the previous implementation would try to re-acquire the same mutex and fail (Ruby mutexes are non-reentrant). ## Changes between Bunny 0.9.0.rc1 and 0.9.0.rc2 ### Channel Now Properly Restarts Consumer Pool In a case when all consumers are cancelled, `Bunny::Channel` will shut down its consumer delivery thread pool. It will also now mark the pool as not running so that it can be started again successfully if new consumers are registered later. GH issue: #133. ### Bunny::Queue#pop_waiting is Removed A little bit of background: on MRI, the method raised `ThreadErrors` reliably. On JRuby, we used a different [internal] queue implementation from JDK so it wasn't an issue. `Timeout.timeout` uses `Thread#kill` and `Thread#join`, both of which eventually attempt to acquire a mutex used by Queue#pop, which Bunny currently uses for continuations. The mutex is already has an owner and so a ThreadError is raised. This is not a problem on JRuby because there we don't use Ruby's Timeout and Queue and instead rely on a JDK concurrency primitive which provides "poll with a timeout". [The issue with `Thread#kill` and `Thread#raise`](http://blog.headius.com/2008/02/ruby-threadraise-threadkill-timeoutrb.html) has been first investigated and blogged about by Ruby implementers in 2008. Finding a workaround will probably take a bit of time and may involve reimplementing standard library and core classes. We don't want this issue to block Bunny 0.9 release. Neither we want to ship a broken feature. So as a result, we will drop Bunny::Queue#pop_waiting since it cannot be reliably implemented in a reasonable amount of time on MRI. Per issue #131. ### More Flexible SSLContext Configuration Bunny will now upgrade connection to SSL in `Bunny::Session#start`, so it is possible to fine tune SSLContext and socket settings before that: ``` ruby require "bunny" conn = Bunny.new(:tls => true, :tls_cert => "examples/tls/client_cert.pem", :tls_key => "examples/tls/client_key.pem", :tls_ca_certificates => ["./examples/tls/cacert.pem"]) puts conn.transport.socket.inspect puts conn.transport.tls_context.inspect ``` This also means that `Bunny.new` will now open the socket. Previously it was only done when `Bunny::Session#start` was invoked. ## Changes between Bunny 0.9.0.pre13 and 0.9.0.rc1 ### TLS Support Bunny 0.9 finally supports TLS. There are 3 new options `Bunny.new` takes: * `:tls` which, when set to `true`, will set SSL context up and switch to TLS port (5671) * `:tls_cert` which is a string path to the client certificate (public key) in PEM format * `:tls_key` which is a string path to the client key (private key) in PEM format * `:tls_ca_certificates` which is an array of string paths to CA certificates in PEM format An example: ``` ruby conn = Bunny.new(:tls => true, :tls_cert => "examples/tls/client_cert.pem", :tls_key => "examples/tls/client_key.pem", :tls_ca_certificates => ["./examples/tls/cacert.pem"]) ``` ### Bunny::Queue#pop_waiting **This function was removed in v0.9.0.rc2** `Bunny::Queue#pop_waiting` is a new function that mimics `Bunny::Queue#pop` but will wait until a message is available. It uses a `:timeout` option and will raise an exception if the timeout is hit: ``` ruby # given 1 message in the queue, # works exactly as Bunny::Queue#get q.pop_waiting # given no messages in the queue, will wait for up to 0.5 seconds # for a message to become available. Raises an exception if the timeout # is hit q.pop_waiting(:timeout => 0.5) ``` This method only makes sense for collecting Request/Reply ("RPC") replies. ### Bunny::InvalidCommand is now Bunny::CommandInvalid `Bunny::InvalidCommand` is now `Bunny::CommandInvalid` (follows the exception class naming convention based on response status name). ## Changes between Bunny 0.9.0.pre12 and 0.9.0.pre13 ### Channels Without Consumers Now Tear Down Consumer Pools Channels without consumers left (when all consumers were cancelled) will now tear down their consumer work thread pools, thus making `HotBunnies::Queue#subscribe(:block => true)` calls unblock. This is typically the desired behavior. ### Consumer and Channel Available In Delivery Handlers Delivery handlers registered via `Bunny::Queue#subscribe` now will have access to the consumer and channel they are associated with via the `delivery_info` argument: ``` ruby q.subscribe do |delivery_info, properties, payload| delivery_info.consumer # => the consumer this delivery is for delivery_info.consumer # => the channel this delivery is on end ``` This allows using `Bunny::Queue#subscribe` for one-off consumers much easier, including when used with the `:block` option. ### Bunny::Exchange#wait_for_confirms `Bunny::Exchange#wait_for_confirms` is a convenience method on `Bunny::Exchange` that delegates to the method with the same name on exchange's channel. ## Changes between Bunny 0.9.0.pre11 and 0.9.0.pre12 ### Ruby 1.8 Compatibility Regression Fix `Bunny::Socket` no longer uses Ruby 1.9-specific constants. ### Bunny::Channel#wait_for_confirms Return Value Regression Fix `Bunny::Channel#wait_for_confirms` returns `true` or `false` again. ## Changes between Bunny 0.9.0.pre10 and 0.9.0.pre11 ### Bunny::Session#create_channel Now Accepts Consumer Work Pool Size `Bunny::Session#create_channel` now accepts consumer work pool size as the second argument: ``` ruby # nil means channel id will be allocated by Bunny. # 8 is the number of threads in the consumer work pool this channel will use. ch = conn.create_channel(nil, 8) ``` ### Heartbeat Fix For Long Running Consumers Long running consumers that don't send any data will no longer suffer from connections closed by RabbitMQ because of skipped heartbeats. Activity tracking now takes sent frames into account. ### Time-bound continuations If a network loop exception causes "main" session thread to never receive a response, methods such as `Bunny::Channel#queue` will simply time out and raise Timeout::Error now, which can be handled. It will not start automatic recovery for two reasons: * It will be started in the network activity loop anyway * It may do more damage than good Kicking off network recovery manually is a matter of calling `Bunny::Session#handle_network_failure`. The main benefit of this implementation is that it will never block the main app/session thread forever, and it is really efficient on JRuby thanks to a j.u.c. blocking queue. Fixes #112. ### Logging Support Every Bunny connection now has a logger. By default, Bunny will use STDOUT as logging device. This is configurable using the `:log_file` option: ``` ruby require "bunny" conn = Bunny.new(:log_level => :warn) ``` or the `BUNNY_LOG_LEVEL` environment variable that can take one of the following values: * `debug` (very verbose) * `info` * `warn` * `error` * `fatal` (least verbose) Severity is set to `warn` by default. To disable logging completely, set the level to `fatal`. To redirect logging to a file or any other object that can act as an I/O entity, pass it to the `:log_file` option. ## Changes between Bunny 0.9.0.pre9 and 0.9.0.pre10 This release contains a **breaking API change**. ### Concurrency Improvements On JRuby On JRuby, Bunny now will use `java.util.concurrent`-backed implementations of some of the concurrency primitives. This both improves client stability (JDK concurrency primitives has been around for 9 years and have well-defined, documented semantics) and opens the door to solving some tricky failure handling problems in the future. ### Explicitly Closed Sockets Bunny now will correctly close the socket previous connection had when recovering from network issues. ### Bunny::Exception Now Extends StandardError `Bunny::Exception` now inherits from `StandardError` and not `Exception`. Naked rescue like this ``` ruby begin # ... rescue => e # ... end ``` catches only descendents of `StandardError`. Most people don't know this and this is a very counter-intuitive practice, but apparently there is code out there that can't be changed that depends on this behavior. This is a **breaking API change**. ## Changes between Bunny 0.9.0.pre8 and 0.9.0.pre9 ### Bunny::Session#start Now Returns a Session `Bunny::Session#start` now returns a session instead of the default channel (which wasn't intentional, default channel is a backwards-compatibility implementation detail). `Bunny::Session#start` also no longer leaves dead threads behind if called multiple times on the same connection. ### More Reliable Heartbeat Sender Heartbeat sender no longer slips into an infinite loop if it encounters an exception. Instead, it will just stop (and presumably re-started when the network error recovery kicks in or the app reconnects manually). ### Network Recovery After Delay Network reconnection now kicks in after a delay to avoid aggressive reconnections in situations when we don't want to endlessly reconnect (e.g. when the connection was closed via the Management UI). The `:network_recovery_interval` option passed to `Bunny::Session#initialize` and `Bunny.new` controls the interval. Default is 5 seconds. ### Default Heartbeat Value Is Now Server-Defined Bunny will now use heartbeat value provided by RabbitMQ by default. ## Changes between Bunny 0.9.0.pre7 and 0.9.0.pre8 ### Stability Improvements Several stability improvements in the network layer, connection error handling, and concurrency hazards. ### Automatic Connection Recovery Can Be Disabled Automatic connection recovery now can be disabled by passing the `:automatically_recover => false` option to `Bunny#initialize`). When the recovery is disabled, network I/O-related exceptions will cause an exception to be raised in thee thread the connection was started on. ### No Timeout Control For Publishing `Bunny::Exchange#publish` and `Bunny::Channel#basic_publish` no longer perform timeout control (using the timeout module) which roughly increases throughput for flood publishing by 350%. Apps that need delivery guarantees should use publisher confirms. ## Changes between Bunny 0.9.0.pre6 and 0.9.0.pre7 ### Bunny::Channel#on_error `Bunny::Channel#on_error` is a new method that lets you define handlers for channel errors that are caused by methods that have no responses in the protocol (`basic.ack`, `basic.reject`, and `basic.nack`). This is rarely necessary but helps make sure no error goes unnoticed. Example: ``` ruby channel.on_error do |ch, channel_close| puts channel_close.inspect end ``` ### Fixed Framing of Larger Messages With Unicode Characters Larger (over 128K) messages with non-ASCII characters are now always encoded correctly with amq-protocol `1.2.0`. ### Efficiency Improvements Publishing of large messages is now done more efficiently. Contributed by Greg Brockman. ### API Reference [Bunny API reference](http://reference.rubybunny.info) is now up online. ### Bunny::Channel#basic_publish Support For :persistent `Bunny::Channel#basic_publish` now supports both `:delivery_mode` and `:persistent` options. ### Bunny::Channel#nacked_set `Bunny::Channel#nacked_set` is a counter-part to `Bunny::Channel#unacked_set` that contains `basic.nack`-ed (rejected) delivery tags. ### Single-threaded Network Activity Mode Passing `:threaded => false` to `Bunny.new` now will use the same thread for publisher confirmations (may be useful for retry logic implementation). Contributed by Greg Brockman. ## Changes between Bunny 0.9.0.pre5 and 0.9.0.pre6 ### Automatic Network Failure Recovery Automatic Network Failure Recovery is a new Bunny feature that was earlier impemented and vetted out in [amqp gem](http://rubyamqp.info). What it does is, when a network activity loop detects an issue, it will try to periodically recover [first TCP, then] AMQP 0.9.1 connection, reopen all channels, recover all exchanges, queues, bindings and consumers on those channels (to be clear: this only includes entities and consumers added via Bunny). Publishers and consumers will continue operating shortly after the network connection recovers. Learn more in the [Error Handling and Recovery](http://rubybunny.info/articles/error_handling.html) documentation guide. ### Confirms Listeners Bunny now supports listeners (callbacks) on ``` ruby ch.confirm_select do |delivery_tag, multiple, nack| # handle confirms (e.g. perform retries) here end ``` Contributed by Greg Brockman. ### Publisher Confirms Improvements Publisher confirms implementation now uses non-strict equality (`<=`) for cases when multiple messages are confirmed by RabbitMQ at once. `Bunny::Channel#unconfirmed_set` is now part of the public API that lets developers access unconfirmed delivery tags to perform retries and such. Contributed by Greg Brockman. ### Publisher Confirms Concurrency Fix `Bunny::Channel#wait_for_confirms` will now correctly block the calling thread until all pending confirms are received. ## Changes between Bunny 0.9.0.pre4 and 0.9.0.pre5 ### Channel Errors Reset Channel error information is now properly reset when a channel is (re)opened. GH issue: #83. ### Bunny::Consumer#initial Default Change the default value of `Bunny::Consumer` noack argument changed from false to true for consistency. ### Bunny::Session#prefetch Removed Global prefetch is not implemented in RabbitMQ, so `Bunny::Session#prefetch` is gone from the API. ### Queue Redeclaration Bug Fix Fixed a problem when a queue was not declared after being deleted and redeclared GH issue: #80 ### Channel Cache Invalidation Channel queue and exchange caches are now properly invalidated when queues and exchanges are deleted. ## Changes between Bunny 0.9.0.pre3 and 0.9.0.pre4 ### Heartbeats Support Fixes Heartbeats are now correctly sent at safe intervals (half of the configured interval). In addition, setting `:heartbeat => 0` (or `nil`) will disable heartbeats, just like in Bunny 0.8 and [amqp gem](http://rubyamqp.info). Default `:heartbeat` value is now `600` (seconds), the same as RabbitMQ 3.0 default. ### Eliminate Race Conditions When Registering Consumers Fixes a potential race condition between `basic.consume-ok` handler and delivery handler when a consumer is registered for a queue that has messages in it. GH issue: #78. ### Support for Alternative Authentication Mechanisms Bunny now supports two authentication mechanisms and can be extended to support more. The supported methods are `"PLAIN"` (username and password) and `"EXTERNAL"` (typically uses TLS, UNIX sockets or another mechanism that does not rely on username/challenge pairs). To use the `"EXTERNAL"` method, pass `:auth_mechanism => "EXTERNAL"` to `Bunny.new`: ``` ruby # uses the EXTERNAL authentication mechanism conn = Bunny.new(:auth_mechanism => "EXTERNAL") conn.start ``` ### Bunny::Consumer#cancel A new high-level API method: `Bunny::Consumer#cancel`, can be used to cancel a consumer. `Bunny::Queue#subscribe` will now return consumer instances when the `:block` option is passed in as `false`. ### Bunny::Exchange#delete Behavior Change `Bunny::Exchange#delete` will no longer delete pre-declared exchanges that cannot be declared by Bunny (`amq.*` and the default exchange). ### Bunny::DeliveryInfo#redelivered? `Bunny::DeliveryInfo#redelivered?` is a new method that is an alias to `Bunny::DeliveryInfo#redelivered` but follows the Ruby community convention about predicate method names. ### Corrected Bunny::DeliveryInfo#delivery_tag Name `Bunny::DeliveryInfo#delivery_tag` had a typo which is now fixed. ## Changes between Bunny 0.9.0.pre2 and 0.9.0.pre3 ### Client Capabilities Bunny now correctly lists RabbitMQ extensions it currently supports in client capabilities: * `basic.nack` * exchange-to-exchange bindings * consumer cancellation notifications * publisher confirms ### Publisher Confirms Support [Lightweight Publisher Confirms](http://www.rabbitmq.com/blog/2011/02/10/introducing-publisher-confirms/) is a RabbitMQ feature that lets publishers keep track of message routing without adding noticeable throughput degradation as it is the case with AMQP 0.9.1 transactions. Bunny `0.9.0.pre3` supports publisher confirms. Publisher confirms are enabled per channel, using the `Bunny::Channel#confirm_select` method. `Bunny::Channel#wait_for_confirms` is a method that blocks current thread until the client gets confirmations for all unconfirmed published messages: ``` ruby ch = connection.create_channel ch.confirm_select ch.using_publisher_confirmations? # => true q = ch.queue("", :exclusive => true) x = ch.default_exchange 5000.times do x.publish("xyzzy", :routing_key => q.name) end ch.next_publish_seq_no.should == 5001 ch.wait_for_confirms # waits until all 5000 published messages are acknowledged by RabbitMQ ``` ### Consumers as Objects It is now possible to register a consumer as an object instead of a block. Consumers that are class instances support cancellation notifications (e.g. when a queue they're registered with is deleted). To support this, Bunny introduces two new methods: `Bunny::Channel#basic_consume_with` and `Bunny::Queue#subscribe_with`, that operate on consumer objects. Objects are supposed to respond to three selectors: * `:handle_delivery` with 3 arguments * `:handle_cancellation` with 1 argument * `:consumer_tag=` with 1 argument An example: ``` ruby class ExampleConsumer < Bunny::Consumer def cancelled? @cancelled end def handle_cancellation(_) @cancelled = true end end # "high-level" API ch1 = connection.create_channel q1 = ch1.queue("", :auto_delete => true) consumer = ExampleConsumer.new(ch1, q) q1.subscribe_with(consumer) # "low-level" API ch2 = connection.create_channel q1 = ch2.queue("", :auto_delete => true) consumer = ExampleConsumer.new(ch2, q) ch2.basic_consume_with.(consumer) ``` ### RABBITMQ_URL ENV variable support If `RABBITMQ_URL` environment variable is set, Bunny will assume it contains a valid amqp URI string and will use it. This is convenient with some PaaS technologies such as Heroku. ## Changes between Bunny 0.9.0.pre1 and 0.9.0.pre2 ### Change Bunny::Queue#pop default for :ack to false It makes more sense for beginners that way. ### Bunny::Queue#subscribe now support the new :block option `Bunny::Queue#subscribe` support the new `:block` option (a boolean). It controls whether the current thread will be blocked by `Bunny::Queue#subscribe`. ### Bunny::Exchange#publish now supports :key again `Bunny::Exchange#publish` now supports `:key` as an alias for `:routing_key`. ### Bunny::Session#queue et al. `Bunny::Session#queue`, `Bunny::Session#direct`, `Bunny::Session#fanout`, `Bunny::Session#topic`, and `Bunny::Session#headers` were added to simplify migration. They all delegate to their respective `Bunny::Channel` methods on the default channel every connection has. ### Bunny::Channel#exchange, Bunny::Session#exchange `Bunny::Channel#exchange` and `Bunny::Session#exchange` were added to simplify migration: ``` ruby b = Bunny.new b.start # uses default connection channel x = b.exchange("logs.events", :topic) ``` ### Bunny::Queue#subscribe now properly takes 3 arguments ``` ruby q.subscribe(:exclusive => false, :ack => false) do |delivery_info, properties, payload| # ... end ``` ## Changes between Bunny 0.8.x and 0.9.0.pre1 ### New convenience functions: Bunny::Channel#fanout, Bunny::Channel#topic `Bunny::Channel#fanout`, `Bunny::Channel#topic`, `Bunny::Channel#direct`, `Bunny::Channel#headers`, and`Bunny::Channel#default_exchange` are new convenience methods to instantiate exchanges: ``` ruby conn = Bunny.new conn.start ch = conn.create_channel x = ch.fanout("logging.events", :durable => true) ``` ### Bunny::Queue#pop and consumer handlers (Bunny::Queue#subscribe) signatures have changed Bunny `< 0.9.x` example: ``` ruby h = queue.pop puts h[:delivery_info], h[:header], h[:payload] ``` Bunny `>= 0.9.x` example: ``` ruby delivery_info, properties, payload = queue.pop ``` The improve is both in that Ruby has positional destructuring, e.g. ``` ruby delivery_info, _, content = q.pop ``` but not hash destructuring, like, say, Clojure does. In addition we return nil for content when it should be nil (basic.get-empty) and unify these arguments betwee * Bunny::Queue#pop * Consumer (Bunny::Queue#subscribe, etc) handlers * Returned message handlers The unification moment was the driving factor. ### Bunny::Client#write now raises Bunny::ConnectionError Bunny::Client#write now raises `Bunny::ConnectionError` instead of `Bunny::ServerDownError` when network I/O operations fail. ### Bunny::Client.create_channel now uses a bitset-based allocator Instead of reusing channel instances, `Bunny::Client.create_channel` now opens new channels and uses bitset-based allocator to keep track of used channel ids. This avoids situations when channels are reused or shared without developer's explicit intent but also work well for long running applications that aggressively open and release channels. This is also how amqp gem and RabbitMQ Java client manage channel ids. ### Bunny::ServerDownError is now Bunny::TCPConnectionFailed `Bunny::ServerDownError` is now an alias for `Bunny::TCPConnectionFailed` ruby-amqp-bunny-b6569cd/Gemfile000066400000000000000000000025401464043542000165020ustar00rootroot00000000000000# encoding: utf-8 source "https://rubygems.org" # Use local clones if possible. # If you want to use your local copy, just symlink it to vendor. # See http://blog.101ideas.cz/posts/custom-gems-in-gemfile.html extend Module.new { def gem(name, *args) options = args.last.is_a?(Hash) ? args.last : Hash.new local_path = File.expand_path("../vendor/#{name}", __FILE__) if File.exist?(local_path) super name, options.merge(path: local_path). delete_if { |key, _| [:git, :branch].include?(key) } else super name, *args end end } gem "rake", ">= 12.3.1" group :development do gem "yard" gem "redcarpet", platform: :mri gem "ruby-prof", platform: :mri end group :test do gem "rspec", "~> 3.12.0" gem "rabbitmq_http_api_client", "~> 2.2.0", require: "rabbitmq/http/client" gem "toxiproxy", "~> 2" end gemspec # Use local clones if possible. # If you want to use your local copy, just symlink it to vendor. def custom_gem(name, options = Hash.new) local_path = File.expand_path("../vendor/#{name}", __FILE__) if File.exist?(local_path) puts "Using #{name} from #{local_path}..." gem name, options.merge(path: local_path).delete_if { |key, _| [:git, :branch].include?(key) } else gem name, options end end custom_gem "amq-protocol", git: "https://github.com/ruby-amqp/amq-protocol", branch: "master" ruby-amqp-bunny-b6569cd/LICENSE000066400000000000000000000022061464043542000162130ustar00rootroot00000000000000Copyright (c) 2009–2023 Chris Duncan, Jakub Stastny aka botanicus, Michael S. Klishin, Eric Lindvall, Stefan Kaes and contributors. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ruby-amqp-bunny-b6569cd/README.md000066400000000000000000000177041464043542000164760ustar00rootroot00000000000000# Bunny, a Ruby RabbitMQ Client Bunny is a RabbitMQ client that focuses on ease of use. It is feature complete, supports all recent RabbitMQ features and does not have any heavyweight dependencies. ## I Know What RabbitMQ and Bunny are, How Do I Get Started? [Right here](https://www.rabbitmq.com/getstarted.html)! ## What is Bunny Good For? One can use Bunny to make Ruby applications interoperate with other applications (both built in Ruby and not). Complexity and size may vary from simple work queues to complex multi-stage data processing workflows that involve many applications built with all kinds of technologies. Specific examples: * Events collectors, metrics & analytics applications can aggregate events produced by various applications (Web and not) in the company network. * A Web application may route messages to a Java app that works with SMS delivery gateways. * MMO games can use flexible routing RabbitMQ provides to propagate event notifications to players and locations. * Price updates from public markets or other sources can be distributed between interested parties, from trading systems to points of sale in a specific geographic region. * Content aggregators may update full-text search and geospatial search indexes by delegating actual indexing work to other applications over RabbitMQ. * Companies may provide streaming/push APIs to their customers, partners or just general public. * Continuous integration systems can distribute builds between multiple machines with various hardware and software configurations using advanced routing features of RabbitMQ. * An application that watches updates from a real-time stream (be it markets data or Twitter stream) can propagate updates to interested parties, including Web applications that display that information in the real time. ## Supported Ruby Versions Modern Bunny versions support * CRuby 2.6 through 3.1 (inclusive) * [TruffleRuby](https://www.graalvm.org/ruby/) For environments that use TLS, Bunny expects Ruby installations to use a recent enough OpenSSL version that **includes support for TLS 1.3**. ### JRuby Bunny works sufficiently well on JRuby but there are known JRuby bugs in versions prior to JRuby 9000 that cause high CPU burn. JRuby users should use [March Hare](http://rubymarchhare.info). Bunny `1.7.x` was the last version to support CRuby 1.9.3 and 1.8.7 ## Supported RabbitMQ Versions Modern Bunny releases target [currently supported RabbitMQ release series](https://www.rabbitmq.com/versions.html). ## Change Log Bunny is a mature library (started in early 2009) with a stable public API. Change logs per release series: * [main](https://github.com/ruby-amqp/bunny/blob/main/ChangeLog.md) (most notable changes for all release series) * [2.19.x](https://github.com/ruby-amqp/bunny/blob/2.19.x-stable/ChangeLog.md) ## Installation & Bundler Dependency ### Most Recent Release [![Gem Version](https://badge.fury.io/rb/bunny.svg)](http://badge.fury.io/rb/bunny) ### Bundler Dependency To use Bunny in a project managed with Bundler: ``` ruby gem "bunny", ">= 2.19.0" ``` ### With Rubygems To install Bunny with RubyGems: ``` gem install bunny ``` ## Quick Start Below is a small snippet that demonstrates how to publish and synchronously consume ("pull API") messages with Bunny. For a 15 minute tutorial using more practical examples, see [Getting Started with RabbitMQ and Ruby using Bunny](https://www.rabbitmq.com/tutorials/tutorial-one-ruby.html). ``` ruby require "bunny" # Start a communication session with RabbitMQ conn = Bunny.new conn.start # open a channel ch = conn.create_channel ch.confirm_select # declare a queue q = ch.queue("test1") q.subscribe(manual_ack: true) do |delivery_info, metadata, payload| puts "This is the message: #{payload}" # acknowledge the delivery so that RabbitMQ can mark it for deletion ch.ack(delivery_info.delivery_tag) end # publish a message to the default exchange which then gets routed to this queue q.publish("Hello, everybody!") # await confirmations from RabbitMQ, see # https://www.rabbitmq.com/publishers.html#data-safety for details ch.wait_for_confirms # give the above consumer some time consume the delivery and print out the message sleep 1 puts "Done" ch.close # close the connection conn.close ``` ## Documentation ### Getting Started For a 15 minute tutorial using more practical examples, see [Getting Started with RabbitMQ and Ruby using Bunny](http://rubybunny.info/articles/getting_started.html). ### Guides Bunny documentation guides are [under `docs/guides` in this repository](https://github.com/ruby-amqp/bunny/tree/main/docs/guides): * [Queues and Consumers](https://github.com/ruby-amqp/bunny/tree/main/docs/guides/queues.md) * [Exchanges and Publishers](https://github.com/ruby-amqp/bunny/tree/main/docs/guides/exchanges.md) * [AMQP 0.9.1 Model Explained](http://www.rabbitmq.com/tutorials/amqp-concepts.html) * [Connecting to RabbitMQ](https://github.com/ruby-amqp/bunny/tree/main/docs/guides/connecting.md) * [Error Handling and Recovery](https://github.com/ruby-amqp/bunny/tree/main/docs/guides/error_handling.md) * [TLS/SSL Support](https://github.com/ruby-amqp/bunny/tree/main/docs/guides/tls.md) * [Bindings](https://github.com/ruby-amqp/bunny/tree/main/docs/guides/bindings.md) * [Using RabbitMQ Extensions with Bunny](https://github.com/ruby-amqp/bunny/tree/main/docs/guides/extensions.md) * [Durability and Related Matters](https://github.com/ruby-amqp/bunny/tree/main/docs/guides/durability.md) Some highly relevant RabbitMQ documentation guides: * [Connections](https://www.rabbitmq.com/connections.html) * [Channels](https://www.rabbitmq.com/channels.html) * [Queues](https://www.rabbitmq.com/queues.html) * [Quorum queues](https://www.rabbitmq.com/quorum-queues.html) * [Streams](https://rabbitmq.com/streams.html) (Bunny can perform basic operations on streams even though it does not implement the [RabbitMQ Stream protocol](https://github.com/rabbitmq/rabbitmq-server/blob/v3.10.x/deps/rabbitmq_stream/docs/PROTOCOL.adoc)) * [Publishers](https://www.rabbitmq.com/publishers.html) * [Consumers](https://www.rabbitmq.com/consumers.html) * Data safety: publisher and consumer [Confirmations](https://www.rabbitmq.com/confirms.html) * [Production Checklist](https://www.rabbitmq.com/production-checklist.html) ### API Reference [Bunny API Reference](http://reference.rubybunny.info/). ## Community and Getting Help ### Mailing List [Bunny has a mailing list](http://groups.google.com/group/ruby-amqp). Please use it for all questions, investigations, and discussions. GitHub issues should be used for specific, well understood, actionable maintainers and contributors can work on. We encourage you to also join the [RabbitMQ mailing list](https://groups.google.com/forum/#!forum/rabbitmq-users) mailing list. Feel free to ask any questions that you may have. ## Continuous Integration [![Build Status](https://travis-ci.org/ruby-amqp/bunny.svg)](https://travis-ci.org/ruby-amqp/bunny/) ### Reporting Issues If you find a bug you understand well, poor default, incorrect or unclear piece of documentation, or missing feature, please [file an issue](http://github.com/ruby-amqp/bunny/issues) on GitHub. Please use [Bunny's mailing list](http://groups.google.com/group/ruby-amqp) for questions, investigations, and discussions. GitHub issues should be used for specific, well understood, actionable maintainers and contributors can work on. When filing an issue, please specify which Bunny and RabbitMQ versions you are using, provide recent RabbitMQ log file contents, full exception stack traces, and steps to reproduce (or failing test cases). ## Other Ruby RabbitMQ Clients The other widely used Ruby RabbitMQ client is [March Hare](http://rubymarchhare.info) (JRuby-only). It's a mature library that require RabbitMQ 3.3.x or later. ## Contributing See [CONTRIBUTING.md](./CONTRIBUTING.md) for more information about running various test suites. ## License Released under the MIT license. ruby-amqp-bunny-b6569cd/Rakefile000066400000000000000000000040771464043542000166630ustar00rootroot00000000000000require 'rake' require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:integration) do |t| # excludes unit tests as those involve many iterations # and sometimes suffer from obscure interference from integration tests (!) t.pattern = ["spec/higher_level_api/integration", "spec/lower_level_api/integration", "spec/issues"]. map { |dir| Dir.glob(File.join(dir, "**", "*_spec.rb")) }.reduce(&:+) - ["spec/higher_level_api/integration/tls_connection_spec.rb"] t.rspec_opts = "--format progress" end RSpec::Core::RakeTask.new(:integration_without_recovery) do |t| # same as :integration but excludes client connection recovery tests. # useful for sanity checking edge RabbitMQ builds, for instance. t.pattern = ["spec/higher_level_api/integration", "spec/lower_level_api/integration", "spec/issues"]. map { |dir| Dir.glob(File.join(dir, "**", "*_spec.rb")) }.reduce(&:+) - ["spec/higher_level_api/integration/tls_connection_spec.rb", "spec/higher_level_api/integration/connection_recovery_spec.rb"] t.rspec_opts = "--format progress" end RSpec::Core::RakeTask.new(:unit) do |t| t.pattern = Dir.glob("spec/unit/**/*_spec.rb") t.rspec_opts = "--format progress --backtrace" end RSpec::Core::RakeTask.new(:recovery_integration) do |t| # otherwise all examples will be skipped ENV.delete("CI") t.pattern = ["spec/higher_level_api/integration/connection_recovery_spec.rb"] t.rspec_opts = "--format progress --backtrace" end RSpec::Core::RakeTask.new(:stress) do |t| # excludes unit tests as those involve many iterations # and sometimes suffer from obscure interference from integration tests (!) t.pattern = ["spec/stress/**/*_spec.rb"] t.rspec_opts = "--format progress" end task :default => :integration namespace :tls do desc "Checks the certificates and keys in BUNNY_CERTIFICATE_DIR with openssl s_client" task :s_client do dir = ENV["BUNNY_CERTIFICATE_DIR"] sh "openssl s_client -tls1_2 -connect 127.0.0.1:5671 -cert #{dir}/client_certificate.pem -key #{dir}/client_key.pem -CAfile #{dir}/ca_certificate.pem" end end ruby-amqp-bunny-b6569cd/benchmarks/000077500000000000000000000000001464043542000173235ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/benchmarks/basic_publish/000077500000000000000000000000001464043542000221325ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/benchmarks/basic_publish/with_128K_messages.rb000077500000000000000000000012231464043542000260270ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" require "benchmark" conn = Bunny.new conn.start puts puts "-" * 80 puts "Benchmarking on #{RUBY_DESCRIPTION}" n = 50_000 ch = conn.create_channel x = ch.default_exchange s = "z" * (1024 * 128) # warm up the JIT, etc puts "Doing a warmup run..." 16000.times { x.publish(s, :routing_key => "anything") } # give OS, the server and so on some time to catch # up sleep 2.0 t = Benchmark.realtime do n.times { x.publish(s, :routing_key => "anything") } end r = (n.to_f/t.to_f) puts "Publishing rate with #{s.bytesize} bytes/msg: #{(r / 1000).round(2)} KGHz" puts puts "-" * 80 ruby-amqp-bunny-b6569cd/benchmarks/basic_publish/with_1k_messages.rb000077500000000000000000000012131464043542000257140ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" require "benchmark" conn = Bunny.new conn.start puts puts "-" * 80 puts "Benchmarking on #{RUBY_DESCRIPTION}" n = 50_000 ch = conn.create_channel x = ch.default_exchange s = "z" * 1024 # warm up the JIT, etc puts "Doing a warmup run..." 16000.times { x.publish(s, :routing_key => "anything") } # give OS, the server and so on some time to catch # up sleep 2.0 t = Benchmark.realtime do n.times { x.publish(s, :routing_key => "anything") } end r = (n.to_f/t.to_f) puts "Publishing rate with #{s.bytesize} bytes/msg: #{(r / 1000).round(2)} KGHz" puts puts "-" * 80 ruby-amqp-bunny-b6569cd/benchmarks/basic_publish/with_4K_messages.rb000077500000000000000000000012131464043542000256570ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" require "benchmark" conn = Bunny.new conn.start puts puts "-" * 80 puts "Benchmarking on #{RUBY_DESCRIPTION}" n = 50_000 ch = conn.create_channel x = ch.default_exchange s = "z" * 4096 # warm up the JIT, etc puts "Doing a warmup run..." 16000.times { x.publish(s, :routing_key => "anything") } # give OS, the server and so on some time to catch # up sleep 2.0 t = Benchmark.realtime do n.times { x.publish(s, :routing_key => "anything") } end r = (n.to_f/t.to_f) puts "Publishing rate with #{s.bytesize} bytes/msg: #{(r / 1000).round(2)} KGHz" puts puts "-" * 80 ruby-amqp-bunny-b6569cd/benchmarks/basic_publish/with_64K_messages.rb000077500000000000000000000012221464043542000257450ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" require "benchmark" conn = Bunny.new conn.start puts puts "-" * 80 puts "Benchmarking on #{RUBY_DESCRIPTION}" n = 50_000 ch = conn.create_channel x = ch.default_exchange s = "z" * (1024 * 64) # warm up the JIT, etc puts "Doing a warmup run..." 16000.times { x.publish(s, :routing_key => "anything") } # give OS, the server and so on some time to catch # up sleep 2.0 t = Benchmark.realtime do n.times { x.publish(s, :routing_key => "anything") } end r = (n.to_f/t.to_f) puts "Publishing rate with #{s.bytesize} bytes/msg: #{(r / 1000).round(2)} KGHz" puts puts "-" * 80 ruby-amqp-bunny-b6569cd/benchmarks/channel_open.rb000066400000000000000000000006751464043542000223110ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" require "benchmark" conn = Bunny.new conn.start puts puts "-" * 80 puts "Benchmarking on #{RUBY_DESCRIPTION}" n = 500 # warm up the JIT, etc puts "Doing a warmup run..." 1000.times { conn.create_channel } t = Benchmark.realtime do n.times { conn.create_channel } end r = (n.to_f/t.to_f) puts "channel.open rate: #{(r / 1000).round(2)} KGHz" puts puts "-" * 80 ruby-amqp-bunny-b6569cd/benchmarks/mutex_and_monitor.rb000066400000000000000000000013041464043542000234010ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "set" require "thread" require "benchmark" require "monitor" puts puts "-" * 80 puts "Benchmarking on #{RUBY_DESCRIPTION}" n = 2_000_000 mx = Mutex.new mt = Monitor.new # warm up the JIT, etc puts "Doing a warmup run..." n.times do |i| mx.synchronize { 1 } mt.synchronize { 1 } end t1 = Benchmark.realtime do n.times do |i| mx.synchronize { 1 } end end r1 = (n.to_f/t1.to_f) t2 = Benchmark.realtime do n.times do |i| mt.synchronize { 1 } end end r2 = (n.to_f/t2.to_f) puts "Mutex#synchronize, rate: #{(r1 / 1000).round(2)} KGHz" puts "Monitor#synchronize, rate: #{(r2 / 1000).round(2)} KGHz" puts puts "-" * 80 ruby-amqp-bunny-b6569cd/benchmarks/queue_declare.rb000066400000000000000000000010221464043542000224460ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" require "benchmark" conn = Bunny.new conn.start ch = conn.create_channel puts puts "-" * 80 puts "Benchmarking on #{RUBY_DESCRIPTION}" n = 4000 # warm up the JIT, etc puts "Doing a warmup run..." n.times { ch.queue("", :exclusive => true) } t = Benchmark.realtime do n.times { ch.queue("", :exclusive => true) } end r = (n.to_f/t.to_f) puts "queue.declare (server-named, exclusive = true) rate: #{(r / 1000).round(2)} KGHz" puts puts "-" * 80 ruby-amqp-bunny-b6569cd/benchmarks/queue_declare_and_bind.rb000066400000000000000000000010441464043542000242700ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" require "benchmark" conn = Bunny.new conn.start ch = conn.create_channel puts puts "-" * 80 puts "Benchmarking on #{RUBY_DESCRIPTION}" n = 4000 # warm up the JIT, etc puts "Doing a warmup run..." n.times { ch.queue("", :exclusive => true).bind("amq.fanout") } t = Benchmark.realtime do n.times { ch.queue("", :exclusive => true).bind("amq.fanout") } end r = (n.to_f/t.to_f) puts "queue.declare + queue.bind rate: #{(r / 1000).round(2)} KGHz" puts puts "-" * 80 ruby-amqp-bunny-b6569cd/benchmarks/queue_declare_bind_and_delete.rb000066400000000000000000000011011464043542000256040ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" require "benchmark" conn = Bunny.new conn.start ch = conn.create_channel puts puts "-" * 80 puts "Benchmarking on #{RUBY_DESCRIPTION}" n = 4000 # warm up the JIT, etc puts "Doing a warmup run..." n.times { ch.queue("", :exclusive => true).bind("amq.fanout").delete } t = Benchmark.realtime do n.times { ch.queue("", :exclusive => true).bind("amq.fanout").delete } end r = (n.to_f/t.to_f) puts "queue.declare + queue.bind + queue.delete rate: #{(r / 1000).round(2)} KGHz" puts puts "-" * 80 ruby-amqp-bunny-b6569cd/benchmarks/synchronized_sorted_set.rb000066400000000000000000000015271464043542000246270ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "sorted_set" require "thread" require "benchmark" require "bunny/concurrent/synchronized_sorted_set" puts puts "-" * 80 puts "Benchmarking on #{RUBY_DESCRIPTION}" n = 2_000_000 s = SortedSet.new # warm up the JIT, etc puts "Doing a warmup run..." n.times do |i| s << 1 s << i s.delete i s << i end t1 = Benchmark.realtime do n.times do |i| s << 1 s << i s.delete i s << i s.length end end r1 = (n.to_f/t1.to_f) s2 = SynchronizedSortedSet.new t2 = Benchmark.realtime do n.times do |i| s2 << 1 s2 << i s2.delete i s2 << i s2.length end end r2 = (n.to_f/t2.to_f) puts "Mixed sorted set ops, rate: #{(r1 / 1000).round(2)} KGHz" puts "Mixed synchronized sorted set ops, rate: #{(r2 / 1000).round(2)} KGHz" puts puts "-" * 80 ruby-amqp-bunny-b6569cd/benchmarks/write_vs_write_nonblock.rb000066400000000000000000000016321464043542000246130ustar00rootroot00000000000000#!/usr/bin/env ruby require "benchmark" # This tests demonstrates throughput difference of # IO#write and IO#write_nonblock. Note that the two # may not be equivalent depending on your r, w = IO.pipe # buffer size b = 65536 read_loop = Thread.new do loop do begin r.read_nonblock(b) rescue Errno::EWOULDBLOCK, Errno::EAGAIN => e IO.select([r]) retry end end end n = 10_000 # 7 KB s = "a" * (7 * 1024) Benchmark.bm do |meter| meter.report("write:") do n.times { w.write(s.dup) } end meter.report("write + flush:") do n.times { w.write(s.dup); w.flush } end meter.report("write_nonblock:") do n.times do s2 = s.dup begin while !s2.empty? written = w.write_nonblock(s2) s2.slice!(0, written) end rescue Errno::EWOULDBLOCK, Errno::EAGAIN IO.select([], [w]) retry end end end end ruby-amqp-bunny-b6569cd/bin/000077500000000000000000000000001464043542000157565ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/bin/ci/000077500000000000000000000000001464043542000163515ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/bin/ci/before_build000077700000000000000000000000001464043542000236652before_build.shustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/bin/ci/before_build.sh000077500000000000000000000045331464043542000213360ustar00rootroot00000000000000#!/usr/bin/env sh CTL=${BUNNY_RABBITMQCTL:-"sudo rabbitmqctl"} PLUGINS=${BUNNY_RABBITMQ_PLUGINS:-"sudo rabbitmq-plugins"} echo "Will use rabbitmqctl at ${CTL}" echo "Will use rabbitmq-plugins at ${PLUGINS}" $PLUGINS enable rabbitmq_management sleep 3 # guest:guest has full access to / $CTL add_vhost / $CTL add_user guest guest $CTL set_permissions -p / guest ".*" ".*" ".*" # # Virtual hosts # # a general purpose virtual host $CTL add_vhost bunny_testbed # these are used for testing default queue type (DQT) $CTL add_vhost bunny_dqt_classic --default-queue-type "classic" $CTL add_vhost bunny_dqt_quorum --default-queue-type "quorum" $CTL add_vhost bunny_dqt_stream --default-queue-type "stream" # # Users # $CTL add_user bunny_gem bunny_password # used for testing certain permissions $CTL add_user bunny_reader reader_password # # Permissions # # bunny_gem:bunny_password has full access to bunny_testbed $CTL set_permissions -p bunny_testbed bunny_gem ".*" ".*" ".*" # bunny_gem:bunny_password has full access to bunny_dqt_quorum $CTL set_permissions -p bunny_dqt_classic bunny_gem ".*" ".*" ".*" $CTL set_permissions -p bunny_dqt_quorum bunny_gem ".*" ".*" ".*" $CTL set_permissions -p bunny_dqt_stream bunny_gem ".*" ".*" ".*" $CTL set_permissions -p bunny_dqt_classic guest ".*" ".*" ".*" $CTL set_permissions -p bunny_dqt_quorum guest ".*" ".*" ".*" $CTL set_permissions -p bunny_dqt_stream guest ".*" ".*" ".*" $CTL add_user bunny_gem bunny_password $CTL set_permissions -p bunny_testbed bunny_gem ".*" ".*" ".*" # guest:guest has full access to bunny_testbed $CTL set_permissions -p bunny_testbed guest ".*" ".*" ".*" # bunny_reader:reader_password has read access to bunny_testbed $CTL set_permissions -p bunny_testbed bunny_reader "^---$" "^---$" ".*" # Reduce retention policy for faster publishing of stats $CTL eval 'supervisor2:terminate_child(rabbit_mgmt_sup_sup, rabbit_mgmt_sup), application:set_env(rabbitmq_management, sample_retention_policies, [{global, [{605, 1}]}, {basic, [{605, 1}]}, {detailed, [{10, 1}]}]), rabbit_mgmt_sup_sup:start_child().' || true $CTL eval 'supervisor2:terminate_child(rabbit_mgmt_agent_sup_sup, rabbit_mgmt_agent_sup), application:set_env(rabbitmq_management_agent, sample_retention_policies, [{global, [{605, 1}]}, {basic, [{605, 1}]}, {detailed, [{10, 1}]}]), rabbit_mgmt_agent_sup_sup:start_child().' || true ruby-amqp-bunny-b6569cd/bin/ci/install_on_debian.sh000077500000000000000000000032261464043542000223570ustar00rootroot00000000000000#!/bin/sh export DEBIAN_FRONTEND=noninteractive sudo apt-get install curl gnupg debian-keyring debian-archive-keyring apt-transport-https -y ## Team RabbitMQ's main signing key sudo apt-key adv --keyserver "hkps://keys.openpgp.org" --recv-keys "0x0A9AF2115F4687BD29803A206B73A36E6026DFCA" ## Modern Erlang repository signing key curl -1sLf 'https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-erlang/gpg.E495BB49CC4BBE5B.key' | sudo apt-key add - ## RabbitMQ repository signing key curl -1sLf 'https://dl.cloudsmith.io/public/rabbitmq/rabbitmq-server/gpg.9F4587F226208342.key' | sudo apt-key add - ## Add apt repositories maintained by Team RabbitMQ sudo tee /etc/apt/sources.list.d/rabbitmq.list <= 2.5") s.metadata = { "changelog_uri" => "https://github.com/ruby-amqp/bunny/blob/main/ChangeLog.md" } # Sorted alphabetically. s.authors = [ "Chris Duncan", "Eric Lindvall", "Jakub Stastny aka botanicus", "Michael S. Klishin", "Stefan Kaes"] s.email = ["michael.s.klishin@gmail.com"] # Dependencies s.add_runtime_dependency 'amq-protocol', '~> 2.3', '>= 2.3.1' s.add_runtime_dependency 'sorted_set', '~> 1', '>= 1.0.2' # Files. s.extra_rdoc_files = ["README.md"] s.files = `git ls-files -- lib/*`.split("\n").reject { |f| f.match(%r{^bin/ci/}) } s.require_paths = ["lib"] end ruby-amqp-bunny-b6569cd/docker-compose.yml000066400000000000000000000013021464043542000206370ustar00rootroot00000000000000version: '3.7' services: rabbitmq: build: ./docker container_name: bunny_rabbitmq environment: RABBITMQ_NODENAME: bunny # see CONTRIBUTING.md BUNNY_RABBITMQ_HOSTNAME: mercurio # link to spec specific configuration RABBITMQ_CONFIG_FILE: /spec/config/rabbitmq.conf RABBITMQ_ENABLED_PLUGINS_FILE: /spec/config/enabled_plugins # send logs to stdout RABBITMQ_LOGS: '-' RABBITMQ_SASL_LOGS: '-' ports: - 5671-5672:5671-5672 - 15672:15672 volumes: - ./spec:/spec:ro toxiproxy: container_name: toxiproxy image: shopify/toxiproxy ports: - 8474:8474 - 11111:11111 depends_on: - rabbitmq ruby-amqp-bunny-b6569cd/docker/000077500000000000000000000000001464043542000164555ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/docker/Dockerfile000066400000000000000000000016161464043542000204530ustar00rootroot00000000000000FROM ubuntu:18.04 RUN apt-get update -y RUN apt-get install -y gnupg2 wget RUN wget -O - "https://github.com/rabbitmq/signing-keys/releases/download/2.0/rabbitmq-release-signing-key.asc" | apt-key add - COPY apt/sources.list.d/bintray.rabbitmq.list /etc/apt/sources.list.d/bintray.rabbitmq.list COPY apt/preferences.d/erlang /etc/apt/preferences.d/erlang RUN apt-get update -y && apt-get upgrade -y RUN apt-get install -y erlang-base \ erlang-asn1 erlang-crypto erlang-eldap erlang-ftp erlang-inets \ erlang-mnesia erlang-os-mon erlang-parsetools erlang-public-key \ erlang-runtime-tools erlang-snmp erlang-ssl \ erlang-syntax-tools erlang-tftp erlang-tools erlang-xmerl RUN apt-get install -y rabbitmq-server COPY docker-entrypoint.sh / ENTRYPOINT /docker-entrypoint.sh EXPOSE 5671 5672 15672 ruby-amqp-bunny-b6569cd/docker/apt/000077500000000000000000000000001464043542000172415ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/docker/apt/preferences.d/000077500000000000000000000000001464043542000217645ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/docker/apt/preferences.d/erlang000066400000000000000000000000731464043542000231570ustar00rootroot00000000000000Package: erlang* Pin: release o=Bintray Pin-Priority: 1000 ruby-amqp-bunny-b6569cd/docker/apt/sources.list.d/000077500000000000000000000000001464043542000221205ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/docker/apt/sources.list.d/bintray.rabbitmq.list000066400000000000000000000001651464043542000262670ustar00rootroot00000000000000deb http://dl.bintray.com/rabbitmq-erlang/debian bionic erlang deb http://dl.bintray.com/rabbitmq/debian bionic main ruby-amqp-bunny-b6569cd/docker/docker-entrypoint.sh000077500000000000000000000012171464043542000224750ustar00rootroot00000000000000#!/bin/sh server=rabbitmq-server ctl=rabbitmqctl delay=5 echo 'Starting a RabbitMQ node' $server -detached echo "Waiting for RabbitMQ to finish startup..." $ctl await_startup --timeout 15 $ctl add_user bunny_gem bunny_password $ctl add_user bunny_reader reader_password $ctl add_vhost bunny_testbed $ctl set_permissions -p / guest '.*' '.*' '.*' $ctl set_permissions -p bunny_testbed bunny_gem '.*' '.*' '.*' $ctl set_permissions -p bunny_testbed guest '.*' '.*' '.*' $ctl set_permissions -p bunny_testbed bunny_reader '^---$' '^---$' '.*' $ctl shutdown --timeout 10 echo 'Starting a RabbitMQ node in foreground (use Ctrl-C to stop)' exec $server ruby-amqp-bunny-b6569cd/docker/rabbitmq.conf000066400000000000000000000021521464043542000211250ustar00rootroot00000000000000listeners.tcp.1 = 0.0.0.0:5672 listeners.ssl.default = 5671 ssl_options.cacertfile = /spec/tls/ca_certificate.pem ssl_options.certfile = /spec/tls/server_certificate.pem ssl_options.keyfile = /spec/tls/server_key.pem ssl_options.verify = verify_none ssl_options.fail_if_no_peer_cert = false ssl_options.honor_cipher_order = true ssl_options.honor_ecc_order = true ssl_options.client_renegotiation = false ssl_options.secure_renegotiate = true ssl_options.ciphers.1 = ECDHE-ECDSA-AES256-GCM-SHA384 ssl_options.ciphers.2 = ECDHE-RSA-AES256-GCM-SHA384 ssl_options.ciphers.3 = ECDH-ECDSA-AES256-GCM-SHA384 ssl_options.ciphers.4 = ECDH-RSA-AES256-GCM-SHA384 ssl_options.ciphers.5 = DHE-RSA-AES256-GCM-SHA384 ssl_options.ciphers.6 = DHE-DSS-AES256-GCM-SHA384 ssl_options.ciphers.7 = ECDHE-ECDSA-AES128-GCM-SHA256 ssl_options.ciphers.8 = ECDHE-RSA-AES128-GCM-SHA256 ssl_options.ciphers.9 = ECDH-ECDSA-AES128-GCM-SHA256 ssl_options.ciphers.10 = ECDH-RSA-AES128-GCM-SHA256 ssl_options.ciphers.11 = DHE-RSA-AES128-GCM-SHA256 ssl_options.ciphers.12 = DHE-DSS-AES128-GCM-SHA256 loopback_users = none ruby-amqp-bunny-b6569cd/docs/000077500000000000000000000000001464043542000161365ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/docs/guides/000077500000000000000000000000001464043542000174165ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/docs/guides/bindings.md000066400000000000000000000136051464043542000215420ustar00rootroot00000000000000--- title: "Working with RabbitMQ bindings from Ruby with Bunny" layout: article --- ## About This Guide This guide covers bindings in AMQP 0.9.1, what they are, what role they play and how to accomplish typical operations using Bunny. This work is licensed under a Creative Commons Attribution 3.0 Unported License (including images and stylesheets). The source is available [on GitHub](https://github.com/ruby-amqp/rubybunny.info). ## What version of Bunny does this guide cover? This guide covers Bunny 2.11.0 and later versions. ## Bindings in AMQP 0.9.1 Learn more about how bindings fit into the AMQP Model in the [AMQP 0.9.1 Model Concepts](http://www.rabbitmq.com/tutorials/amqp-concepts.html) guide. ## What Are AMQP 0.9.1 Bindings Bindings are rules that exchanges use (among other things) to route messages to queues. To instruct an exchange E to route messages to a queue Q, Q has to _be bound_ to E. Bindings may have an optional _routing key_ attribute used by some exchange types. The purpose of the routing key is to selectively match only specific (matching) messages published to an exchange to the bound queue. In other words, the routing key acts like a filter. To draw an analogy: * Queue is like your destination in New York city * Exchange is like JFK airport * Bindings are routes from JFK to your destination. There may be no way, or more than one way, to reach it Some exchange types use routing keys while some others do not (routing messages unconditionally or based on message metadata). If an AMQP message cannot be routed to any queue (for example, because there are no bindings for the exchange it was published to), it is either dropped or returned to the publisher, depending on the message attributes that the publisher has set. If an application wants to connect a queue to an exchange, it needs to _bind_ them. The opposite operation is called _unbinding_. ## Binding Queues to Exchanges In order to receive messages, a queue needs to be bound to at least one exchange. Most of the time binding is explicit (done by applications). To bind a queue to an exchange, use `Bunny::Queue#bind` where the argument passed can be either an `Bunny::Exchange` instance or a string. ``` ruby q.bind(x) ``` The same example using a string without a callback: ``` ruby q.bind("amq.fanout") ``` ## Unbinding queues from exchanges To unbind a queue from an exchange use `Bunny::Queue#unbind`: ``` ruby q.unbind(x) ``` Trying to unbind a queue from an exchange that the queue was never bound to will result in a channel-level exception. ## Exchange-to-Exchange Bindings Exchange-to-Exchange bindings is a RabbitMQ extension to AMQP 0.9.1. It is covered in the [RabbitMQ extensions guide](/articles/extensions.html). ## Bindings, Routing and Returned Messages ### How RabbitMQ Routes Messages After a message reaches RabbitMQ and before it reaches a consumer, several things happen: * RabbitMQ needs to find one or more queues that the message needs to be routed to, depending on type of exchange * RabbitMQ puts a copy of the message into each of those queues or decides to return the message to the publisher * RabbitMQ pushes message to consumers on those queues or waits for applications to fetch them on demand A more in-depth description is this: * RabbitMQ needs to consult bindings list for the exchange the message was published to in order to find one or more queues that the message needs to be routed to (step 1) * If there are no suitable queues found during step 1 and the message was published as mandatory, it is returned to the publisher (step 1b) * If there are suitable queues, a _copy_ of the message is placed into each one (step 2) * If the message was published as mandatory, but there are no active consumers for it, it is returned to the publisher (step 2b) * If there are active consumers on those queues and the basic.qos setting permits, message is pushed to those consumers (step 3) The important thing to take away from this is that messages may or may not be routed and it is important for applications to handle unroutable messages. ### Handling of Unroutable Messages Unroutable messages are either dropped or returned to producers. RabbitMQ extensions can provide additional ways of handling unroutable messages: for example, RabbitMQ's [Alternate Exchanges extension](http://www.rabbitmq.com/ae.html) makes it possible to route unroutable messages to another exchange. Bunny support for it is documented in the [RabbitMQ Extensions guide](/articles/extensions.html). Bunny provides a way to handle returned messages with the `Bunny::Exchange#on_return` method: ``` ruby x.on_return do |basic_return, properties, payload| puts "#{payload} was returned! reply_code = #{basic_return.reply_code}, reply_text = #{basic_return.reply_text}" end ``` [Exchanges and Publishing](/articles/exchanges.html) documentation guide provides more information on the subject, including full code examples. ## What to Read Next The documentation is organized as [a number of guides](/articles/guides.html), covering various topics. We recommend that you read the following guides first, if possible, in this order: * [RabbitMQ Extensions to AMQP 0.9.1](/articles/extensions.html) * [Durability and Related Matters](/articles/durability.html) * [Error Handling and Recovery](/articles/error_handling.html) * [Troubleshooting](/articles/troubleshooting.html) * [Using TLS (SSL) Connections](/articles/tls.html) ## Tell Us What You Think! Please take a moment to tell us what you think about this guide [on Twitter](http://twitter.com/rubyamqp) or the [Bunny mailing list](https://groups.google.com/forum/#!forum/ruby-amqp) Let us know what was unclear or what has not been covered. Maybe you do not like the guide style or grammar or discover spelling mistakes. Reader feedback is key to making the documentation better. ruby-amqp-bunny-b6569cd/docs/guides/community.md000066400000000000000000000024441464043542000217700ustar00rootroot00000000000000--- title: "Bunny: Community and Getting Help" layout: article --- ## Mailing List [Bunny has a mailing list](https://groups.google.com/group/ruby-amqp). We encourage you to also join the [Ruby RabbitMQ Libraries](https://groups.google.com/forum/#!forum/ruby-amqp) google group. Feel free to ask any questions that you may have. ## IRC For more immediate help, please join `#rabbitmq` on `irc.freenode.net`. ## News & Announcements on Twitter To subscribe for announcements of releases, important changes and so on, please follow [@rubyamqp](https://twitter.com/#!/rubyamqp) on Twitter. ## Reporting Issues If you find a bug, poor default, missing feature or find any part of the API inconvenient, please [file an issue](http://github.com/ruby-amqp/bunny/issues) on GitHub. When filing an issue, please specify which Bunny and RabbitMQ versions you are using, provide recent RabbitMQ log file contents if possible, and try to explain what behavior you expected and why. Bonus points for contributing failing test cases. ## Contributing First, clone the repository and run bundle install --binstubs and then run tests with ./bin/rspec -cfs spec After that create a branch and make your changes on it. Once you are done with your changes and all tests pass, submit a pull request on GitHub. ruby-amqp-bunny-b6569cd/docs/guides/concurrency.md000066400000000000000000000130211464043542000222670ustar00rootroot00000000000000--- title: "Concurrency in Bunny and Applications That Use It" layout: article --- ## About this guide This guide covers concurrency in Bunny, concurrency safety of key public API parts, potential for parallelism on Ruby runtimes that support it, and related issues. This work is licensed under a Creative Commons Attribution 3.0 Unported License (including images and stylesheets). The source is available [on GitHub](https://github.com/ruby-amqp/rubybunny.info). ## What version of Bunny does this guide cover? This guide covers Bunny 2.11.0 and later versions. ## Concurrency in Bunny Design Starting with Bunny 0.9, Bunny is developed with concurrency in mind. This means several things: * Bunny avoids some well known concurrency problems in [amqp gem](http://rubyamqp.info), most notably long running operations in message handlers blocking event loop and thus all I/O activity in the library. * Connection (`Bunny::Session`) assumes there will be concurrent publishers and consumers. * Parts of the library (most notably `Bunny::Channel`) are designed to assume they are **not shared between threads**. * Parts of the library can take advantage of parallelism on runtimes that provide it. * Parts of the library that previously were not concurrent now provide concurrency controls. ## Enabling Long Running Delivery Handlers Unlike [amqp gem](http://rubyamqp.info), Bunny does not depend on any opinionated networking library. Instead, it maintains its own I/O activity loop in a separate thread, one per connection. The loop is responsible for reading data from the socket, deserializing it and passing over to the connection that instantiated the loop. Not depending on a global event loop allows Bunny-based applications that consume messages to have long running delivery handlers that do not affect other network activity. Communication between I/O loop and connection is almost completely uni-directional. Writes do not happen in I/O loop thread. ## Synchronized Writes Connections in Bunny will synchronize writes both for messages and at the socket level. This means that publishing on a shared connection from multiple threads is safe but **only if every publishing thread uses a separate channel**. ## Sharing Channels Between Threads Channels **must not** be shared between threads. When client publishes a message, at least 2 (typically 3) frames are sent on the wire: * AMQP 0.9.1 method, `basic.publish` * Message metadata * Message payload This means that without synchronization on, publishing from multiple threads on a shared channel may result in frames being sent to RabbitMQ out of order, e.g.: ``` [basic.publish 1][basic.publish 2][content metadata 1][content body 1][content metadata 2][content metadata 2] ``` There are other potential conflicts arising from frame interleaving. It is, however, safe to process deliveries in multiple threads if multi-message acknowledgements are not used. ## Consumer Work Pools Every channel maintains a fixed size thread pool used to dispatch deliveries (messages pushed by RabbitMQ to consumers). By default every pool has size of 1 to guarantee ordered message processing by default. Applications can provide alternative consumer pool size: ``` ruby # nil will cause channel id to be allocated automatically. # 16 is consumer work pool size. ch = conn.create_channel(nil, 16) ``` Consumer work pool is not started by default and will be created when the first consumer is added on the channel. When the last consumer is cancelled, consumer work pool will be shut down. This ensures that channels that are only used to publish messages do not keep around threads that do nothing. It also reduces the amount of time it takes to open a channel, which is desirable for applications doing heavy request/reply (RPC) communication. ## Mutex Reentrancy Standard Ruby mutex implementation is not reentrant. This is highly annoying to many developers. Standard Ruby library provides a reentrant mutex implementation: `Monitor`. Monitors are reentrant at the cost of about 5-6% lower throughput on most workloads. It is possible to switch to the original mutex implementation, `Mutex`: ``` ruby conn = Bunny.new(:mutex_impl => Mutex) ``` ## Wrapping Up Bunny 0.9+ was created to be used in concurrent applications. While Bunny tries to do a reasonably well job of protecting the user from concurrency hazards in common scenarios, some usage scenarios (primarily sharing channels between publishing threads) should be avoided. Especially for message consumers, Bunny can take advantage of parallelism on runtimes that support it. More parts of the library may be parallized over time. ## What to Read Next The documentation is organized as [a number of guides](/articles/guides.html), covering various topics. We recommend that you read the following guides first, if possible, in this order: * [RabbitMQ Extensions to AMQP 0.9.1](/articles/extensions.html) * [Error Handling and Recovery](/articles/error_handling.html) * [Troubleshooting](/articles/troubleshooting.html) * [Using TLS (SSL) Connections](/articles/tls.html) ## Tell Us What You Think! Please take a moment to tell us what you think about this guide [on Twitter](http://twitter.com/rubyamqp) or the [Bunny mailing list](https://groups.google.com/forum/#!forum/ruby-amqp). Let us know what was unclear or what has not been covered. Maybe you do not like the guide style or grammar or discover spelling mistakes. Reader feedback is key to making the documentation better. ruby-amqp-bunny-b6569cd/docs/guides/connecting.md000066400000000000000000000340001464043542000220640ustar00rootroot00000000000000--- title: "Connecting to RabbitMQ from Ruby with Bunny" layout: article --- ## About this guide This guide covers connection to RabbitMQ with Bunny, connection error handling, authentication failure handling and related issues. This work is licensed under a Creative Commons Attribution 3.0 Unported License (including images and stylesheets). The source is available [on GitHub](https://github.com/ruby-amqp/rubybunny.info). ## What version of Bunny does this guide cover? This guide covers Bunny 2.11.0 and later versions. ## Two ways to specify connection parameters With Bunny, connection parameters (host, port, username, vhost and so on) can be passed in two forms: * As a map of attributes * As a connection URI string (à la JDBC) ### Using a Map of Parameters Map options that Bunny will recognize are * `:host` (string, default: `"127.0.0.1"`) * `:port` (integer, default: `5672`) * `:user` or `:username` (string, default: `"guest"`) * `:pass` or `:password` (string, default: `"guest"`) * `:vhost` or `virtual_host` (string, default: `'/'`) * `:heartbeat` or `:heartbeat_timeout` (string or integer, default: `:server`): the desired [heartbeat timeout](http://www.rabbitmq.com/heartbeats.html). `:server` means "use the value from RabbitMQ config". `0` means no heartbeats (**not recommended**). * `:logger` (Logger): The logger. If missing, one is created using `:log_file` and `:log_level`. * `:log_level` (symbol or integer, default: `Logger::WARN`): Log level to use when creating a logger. * `:log_file` (string or `IO`, default: `STDOUT`): log file or `IO` object to use when creating a logger * `:automatically_recover` (boolean, default: `true`): when `false`, will disable automatic network failure recovery * `:network_recovery_interval` (number, default: `5.0`): interval between reconnection attempts * `:threaded` (boolean, default: `true`): switches to single-threaded connections when set to `false`. Only recommended for apps that only publish messages. * `:continuation_timeout` (integer, default: `4000` ms): timeout for client operations that expect a response (e.g. `Bunny::Queue#get`), in *milliseconds*. * `:frame_max` (integer, default: `131072`): maximum permissible size of a frame (in bytes) to negotiate with clients. Setting to 0 means "unlimited" but will trigger a bug in some QPid clients. Setting a larger value may improve throughput; setting a smaller value may improve latency. * `:auth_mechanism` (string or array, default: `"PLAIN"`): Mechanism to authenticate with the server. Currently supporting `"PLAIN"` and `"EXTERNAL"`. plus TLS connection parameters covered in [Using TLS (SSL) Connections](/articles/tls.html). To connect to RabbitMQ with a map of parameters, pass them to `Bunny.new`. The connection will be established when `Bunny::Session#start` is called: ``` ruby conn = Bunny.new(:host => "localhost", :vhost => "myapp.production", :user => "bunny", :password => "t0ps3kret") conn.start ``` `Bunny.new` returns a connection instance that is used to open channels. More about channels later in this guide. #### Default parameters Default connection parameters are ``` ruby { :host => "127.0.0.1", :port => 5672, :ssl => false, :vhost => "/", :user => "guest", :pass => "guest", :heartbeat => :server, # will use RabbitMQ setting :frame_max => 131072, :auth_mechanism => "PLAIN" } ``` ### Single-Threaded Mode Bunny 0.9+ uses a separate thread for network (I/O) activity. In some cases, developers may want to disable it and handle network failure issues manually. This is possible by passing the `:threaded` option as `false`. ### Reconnection Interval When Bunny detects a network issue, it will by default try to reconnect. This behavior can be tweaked using two options: * `:network_recovery_interval` controls for how long Bunny will wait between reconnection attempts (including the first one). The value is in seconds, `5.0` is the default. * `:automatically_recover` can be set to `false` to completely disable network recovery. In such case, Bunny will raise exceptions on the thread `Bunny::Session` was instantiated on. ### Using Connection Strings It is also possible to specify connection parameters as a URI string: ``` ruby b = Bunny.new("amqp://guest:guest@vm188.dev.megacorp.com/profitd.qa") b.start ``` Unfortunately, there is no URI standard for AMQP URIs, so while several schemes used in the wild share the same basic idea, they differ in some details. The implementation used by Bunny aims to encourage URIs that work as widely as possible. Here are some examples of valid AMQP URIs: * amqp://dev.rabbitmq.com * amqp://dev.rabbitmq.com:5672 * amqp://guest:guest@dev.rabbitmq.com:5672 * amqp://hedgehog:t0ps3kr3t@hub.megacorp.internal/production * amqps://hub.megacorp.internal/%2Fvault * amqps://rabbitmq.com/staging?heartbeat=10&channel_max=1000 The URI scheme should be "amqp", or "amqps" if SSL is required. The host, port, username and password are represented in the authority component of the URI in the same way as in HTTP URIs. The vhost is obtained from the first segment of the path, with the leading slash removed. The path should contain only a single segment (i.e, the only slash in it should be the leading one). If the vhost is to include slashes or other reserved URI characters, these should be percent-escaped. Here are some examples that demonstrate how `AMQ::Settings.parse_amqp_url` parses out the vhost from connection URIs: ``` ruby AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com") # => vhost is nil, so default ("/") will be used AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/") # => vhost is an empty string AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/%2Fvault") # => vhost is "/vault" AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/production") # => vhost is "production" AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/a.b.c") # => vhost is "a.b.c" AMQ::Settings.parse_amqp_url("amqp://dev.rabbitmq.com/foo/bar") # => ArgumentError ``` Bunny is able to parse [RabbitMQ URI query parameters](https://www.rabbitmq.com/uri-query-parameters.html), where you may specify some common client connection attributes: * `auth_mechanism` * `heartbeat` * `connection_timout` * `channel_max` * `verify` * `fail_if_no_peer_cert` * `cacertfile` * `certfile` * `keyfile` Here is an example: ```ruby b = Bunny.new("amqps://rabbitmq?heartbeat=10&connection_timeout=100&channel_max=1000&verify=true&fail_if_no_peer_cert=true&cacertfile=/examples/tls/cacert.pem&certfile=/examples/tls/client_cert.pem&keyfile=/examples/tls/client_key.pem") b.start b.user #=> "guest" b.pass #=> "guest" b.hostname #=> "rabbitmq" b.port #=> 5671 b.vhost #=> "/" b.heartbeat #=> 10 b.transport.connect_timeout #=> 100 b.channel_max #=> 1000 b.ssl #=> true b.transport.verify_peer #=> false b.transport.tls_ca_certificates #=> "/examples/tls/cacert.pem" b.transport.tls_ca_certificates #=> "/examples/tls/client_cert.pem" b.transport.tls_key_path #=> "/examples/tls/client_key.pem" ``` Pay attention that some attibutes are not allowed for specific scheme, particularly TLS options don't make sense for "amqp" scheme. ### Connection Failures If a connection does not succeed, Bunny will raise one of the following exceptions: * `Bunny::PossibleAuthenticationFailureException` indicates an authentication issue or that connection to RabbitMQ was closed before successfully finishing connection negotiation * `Bunny::TCPConnectionFailed` indicates that connection to the host has failed. Either the address is not reachable or DNS entry does not exist. Often may suggest a misconfiguration. * `Bunny::NetworkFailure` for other exceptions in the I/O thread. When [automatic connection recovery mode](/articles/error_handling.html) is disabled, Bunny will raise exceptions on the thread `Bunny::Session` was instantiated. ## PaaS Environments ### The RABBITMQ_URL Environment Variable If no arguments are passed to `Bunny.new` but the `RABBITMQ_URL` environment variable is set, Bunny will use it as connection URI. ## Opening a Channel Some applications need multiple connections to RabbitMQ. However, it is undesirable to keep many TCP connections open at the same time because doing so consumes system resources and makes it more difficult to configure firewalls. AMQP 0-9-1 connections are multiplexed with channels that can be thought of as "lightweight connections that share a single TCP connection". To open a channel, use the `Bunny::Session#create_channel` method: ``` ruby conn = Bunny.new conn.start ch = conn.create_channel ``` Channels are typically long lived: you open one or more of them and use them for a period of time, as opposed to opening a new channel for each published message, for example. ## Closing Channels To close a channel, use the `Bunny::Channel#close` method. A closed channel can no longer be used. ``` ruby conn = Bunny.new conn.start ch = conn.create_channel ch.close ``` ## Connecting in Web applications (Ruby on Rails, Sinatra, etc) When connecting in Web apps, the rule of thumb is: do it in an initializer, not controller actions or request handlers. ### Using Bunny with Unicorn [Unicorn](http://unicorn.bogomips.org) is a pre-forking server. That means it forks worker processes that serve HTTP requests. The "[fork(2)](http://en.wikipedia.org/wiki/Fork_(operating_system)) system call has several gotchas associated with it: * Unintentional file descriptor sharing * The fact that a [forked child process only inherits one thread](http://bit.ly/fork-and-threads) and therefore the network I/O thread is not inherited To avoid both problems, connect to RabbitMQ *after* the master process forks workers. The master Unicorn process never serves HTTP requests and usually does not need to hold a RabbitMQ connection. Next, let us see how to connect to the broker after Unicorn forks a worker. Unicorn lets you specify a configuration file to use. In that file you define a callback that Unicorn runs after it forks worker process(es): ``` ruby preload_app true after_fork do |server, worker| require "bunny" # the following is *required* for Rails + "preload_app true", defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection $rabbitmq_connection = Bunny.new $rabbitmq_connection.start $rabbitmq_channel = $rabbitmq_connection.create_channel end end ``` In the example above we connect to RabbitMQ after Unicorn has forked off child processes. Note that a configuration file can easily be used in development environments because, other than the fact that Unicorn runs in the foreground, it gives you exactly the same application boot behavior as in QA and production environments. ### Using Bunny with Passenger [Phusion Passenger](http://www.modrails.com) is also a pre-forking server, and just as with Unicorn, clients should connect to RabbitMQ **after** it forks worker processes. The Passenger documentation has [a section](https://www.phusionpassenger.com/library/indepth/ruby/spawn_methods/#unintentional-file-descriptor-sharing) that explains how to avoid problems related to the behavior of the fork(2) system call, namely: * Unintentional file descriptor sharing * The fact that a [forked child process only inherits one thread](http://bit.ly/fork-and-threads) and therefore network I/O loop thread is not inherited. #### Using Event Handler to Spawn One Connection Per Worker Process Passenger provides a hook that you should use for spawning RabbitMQ connections: ``` ruby if defined?(PhusionPassenger) # otherwise it breaks rake commands if you put this in an initializer PhusionPassenger.on_event(:starting_worker_process) do |forked| if forked # We’re in a smart spawning mode # Now is a good time to connect to RabbitMQ $rabbitmq_connection = Bunny.new $rabbitmq_connection.start $rabbitmq_channel = $rabbitmq_connection.create_channel end end PhusionPassenger.on_event(:stopping_worker_process) do if $rabbitmq_connection $rabbitmq_connection.close end end end ``` Basically, the recommended default smart spawn mode works exactly the same as in Unicorn. ### Ruby on Rails Currently Bunny does not have integration points for Rails (e.g. a rail tie). ## Disconnecting To close a connection, use the `Bunny::Session#close` function. This will automatically close all channels of that connection first: ``` ruby conn = Bunny.new conn.start conn.close ``` ## Troubleshooting If you have read this guide and still have issues with connecting, check our [Troubleshooting guide](/articles/troubleshooting.html) and feel free to ask [on the mailing list](https://groups.google.com/forum/#!forum/ruby-amqp). ## Wrapping Up There are two ways to specify connection parameters with Bunny: with a map of parameters or via URI string. Connection issues are indicated by various exceptions. If the `RABBITMQ_URL` env variable is set, Bunny will use its value as RabbitMQ connection URI. ## What to Read Next The documentation is organized as [a number of guides](/articles/guides.html), covering various topics. We recommend that you read the following guides first, if possible, in this order: * [Queues and Consumers](/articles/queues.html) * [Exchanges and Publishing](/articles/exchanges.html) * [Bindings](/articles/bindings.html) * [RabbitMQ Extensions to AMQP 0.9.1](/articles/extensions.html) * [Durability and Related Matters](/articles/durability.html) * [Error Handling and Recovery](/articles/error_handling.html) * [Troubleshooting](/articles/troubleshooting.html) * [Using TLS (SSL) Connections](/articles/tls.html) ## Tell Us What You Think! Please take a moment to tell us what you think about this guide [on Twitter](http://twitter.com/rubyamqp) or the [Bunny mailing list](https://groups.google.com/forum/#!forum/ruby-amqp). Let us know what was unclear or what has not been covered. Maybe you do not like the guide style or grammar or discover spelling mistakes. Reader feedback is key to making the documentation better. ruby-amqp-bunny-b6569cd/docs/guides/durability.md000066400000000000000000000121611464043542000221110ustar00rootroot00000000000000--- title: "Durability and related matters" layout: article --- ## About This Guide This guide covers queue, exchange and message durability, as well as other topics related to durability, for example, durability in clustered environments. This work is licensed under a Creative Commons Attribution 3.0 Unported License (including images and stylesheets). The source is available [on GitHub](https://github.com/ruby-amqp/rubybunny.info). ## What version of Bunny does this guide cover? This guide covers Bunny 2.11.0 and later versions. ## Entity durability and message persistence ### Exchange Durability AMQP separates the concept of entity durability (queues, exchanges) from message persistence. Exchanges can be durable or transient. Durable exchanges survive broker restart, transient exchanges do not (they have to be redeclared when the broker comes back online), however, not all scenarios and use cases mandate exchanges to be durable. To create a durable exchange, declare it with the `:durable => true` argument: ``` ruby ch = conn.create_channel exch = ch.direct("my_direct_exchange", :durable => true) ``` ### Queue Durability Queues can be durable or transient. Durable queues survive broker restart, transient queues do not (they have to be redeclared when the broker comes back online), however, not all scenarios and use cases mandate queues to be durable. To create a durable queue, declare it with the `:durable => true` argument: ``` ruby ch = conn.create_channel q = ch.queue("my_queue", :durable => true) ``` Durability of a queue does not make _messages_ that are routed to that queue durable. If a broker is taken down and then brought back up, durable queues will be re-declared during broker startup, however, only _persistent_ messages will be recovered. ### Binding Durability Bindings of durable queues to durable exchanges are automatically durable and are restored after a broker restart. The AMQP 0.9.1 specification states that the binding of durable queues to transient exchanges must be allowed. In this case, since the exchange would not survive a broker restart, neither would any bindings to such and exchange. ### Message Persistence Messages may be published as persistent and this, in conjunction with queue durability, is what makes an AMQP broker persist them to disk. If the server is restarted, the system ensures that received persistent messages in durable queues are not lost. Simply publishing a message to a durable exchange or the fact that a queue to which a message is routed is durable does not make that message persistent. Message persistence depends on the persistence mode of the message itself. **Note** that the default mode is to publish messages as persistent, however publishing persistent messages affects performance (just like with data stores, durability comes at a certain cost to performance). If required you can pass the `:persistent => false` argument to the `Bunny::Exchange#publish` method to publish your message without persistence: ``` ruby exch.publish("My message", :persistent => false) ``` ### Clustering and High Availability To achieve the degree of durability that critical applications need, it is necessary but not enough to use durable queues, exchanges and persistent messages. You need to use a cluster of brokers because otherwise, a single hardware problem may bring a broker down completely. RabbitMQ offers a number of high availability features for both scenarios with more (LAN) and less (WAN) reliable network connections. See the [RabbitMQ clustering](http://www.rabbitmq.com/clustering.html) and [high availability](http://www.rabbitmq.com/ha.html) guides for in-depth discussion of this topic. ### Highly Available (Mirrored) Queues Whilst the use of clustering provides for greater durability of critical systems, in order to achieve the highest level of resilience for queues and messages, high availability configuration should be used. This is because although exchanges and bindings survive the loss of individual nodes by using clustering, messages do not. Without mirroring, queue contents reside on exactly one node, thus the loss of a node will cause message loss. See the [RabbitMQ high availability guide](http://www.rabbitmq.com/ha.html) for more information about mirrored queues. ## What to Read Next The documentation is organized as [a number of guides](/articles/guides.html), covering various topics. We recommend that you read the following guides first, if possible, in this order: * [Queues and Consumers](/articles/queues.html) * [Exchanges and Publishing](/articles/exchanges.html) * [Bindings](/articles/bindings.html) * [RabbitMQ Extensions to AMQP 0.9.1](/articles/extensions.html) ## Tell Us What You Think! Please take a moment to tell us what you think about this guide [on Twitter](http://twitter.com/rubyamqp) or the [Bunny mailing list](https://groups.google.com/forum/#!forum/ruby-amqp) Let us know what was unclear or what has not been covered. Maybe you do not like the guide style or grammar or discover spelling mistakes. Reader feedback is key to making the documentation better. ruby-amqp-bunny-b6569cd/docs/guides/error_handling.md000066400000000000000000000253311464043542000227410ustar00rootroot00000000000000--- title: "Error Handling and Recovery" layout: article --- ## About this guide Development of a robust application, be it message publisher or message consumer, involves dealing with multiple kinds of failures: protocol exceptions, network failures, broker failures and so on. Correct error handling and recovery is not easy. This guide explains how the library helps you in dealing with issues like * Client exceptions * Initial connection failures * Network connection failures * AMQP 0.9.1 connection-level exceptions * AMQP 0.9.1 channel-level exceptions * Broker failure * TLS (SSL) related issues as well as * How does the automatic recovery mode in Bunny 0.9+ work This work is licensed under a Creative Commons Attribution 3.0 Unported License (including images and stylesheets). The source is available [on GitHub](https://github.com/ruby-amqp/rubybunny.info). ## What version of Bunny does this guide cover? This guide covers Bunny 2.11.0 and later versions. ## Client Exceptions Here is the break-down of exceptions that can be raised by Bunny: StandardError Bunny::Exception Bunny::ChannelAlreadyClosed Bunny::ChannelLevelException Bunny::AccessRefused Bunny::ForcedChannelCloseError Bunny::NotFound Bunny::PreconditionFailed Bunny::ResourceLocked Bunny::ConnectionClosedError Bunny::ConnectionLevelException Bunny::ChannelError Bunny::CommandInvalid Bunny::ConnectionForced Bunny::ForcedConnectionCloseError Bunny::FrameError Bunny::InternalError Bunny::ResourceError Bunny::UnexpectedFrame Bunny::InconsistentDataError Bunny::BadLengthError Bunny::NoFinalOctetError Bunny::NetworkFailure Bunny::NotAllowedError Bunny::PossibleAuthenticationFailureError Bunny::AuthenticationFailureError Bunny::ShutdownSignal Bunny::TCPConnectionFailed Timeout::Error Bunny::ClientTimeout Bunny::ConnectionTimeout The rest of the document describes the most common ones. See [Bunny exception definitions](https://raw.githubusercontent.com/ruby-amqp/bunny/master/lib/bunny/exceptions.rb) for more details. ## Initial RabbitMQ Connection Failures When applications connect to the broker, they need to handle connection failures. Networks are not 100% reliable, even with modern system configuration tools like Chef or Puppet misconfigurations happen and the broker might also be down. Error detection should happen as early as possible. To handle TCP connection failure, catch the `Bunny::TCPConnectionFailure` exception: ``` ruby begin conn = Bunny.new("amqp://guest:guest@aksjhdkajshdkj.example82737.com") conn.start rescue Bunny::TCPConnectionFailed => e puts "Connection to aksjhdkajshdkj.example82737.com failed" end ``` `Bunny::Session#start` will raise `Bunny::TCPConnectionFailed` if a connection fails. Code that catches it can write to a log about the issue or use retry to execute the begin block one more time. Because initial connection failures are due to misconfiguration or network outage, reconnection to the same endpoint (hostname, port, vhost combination) may result in the same issue over and over. ## Authentication Failures Another reason why a connection may fail is authentication failure. Handling authentication failure is very similar to handling initial TCP connection failure: ``` ruby begin conn = Bunny.new("amqp://guest8we78w7e8:guest2378278@127.0.0.1") conn.start rescue Bunny::PossibleAuthenticationFailureError => e puts "Could not authenticate as #{conn.username}" end ``` In case you are wondering why the exception name has "possible" in it: [AMQP 0.9.1 spec](http://www.rabbitmq.com/resources/specs/amqp0-9-1.pdf) requires broker implementations to simply close TCP connection without sending any more data when an exception (such as authentication failure) occurs before AMQP connection is open. In practice, however, when broker closes TCP connection between successful TCP connection and before AMQP connection is open, it means that authentication has failed. RabbitMQ 3.2 introduces [authentication failure notifications](http://www.rabbitmq.com/auth-notification.html) which Bunny supports. When connecting to RabbitMQ 3.2 or later, Bunny will raise `Bunny::AuthenticationFailureError` when it receives a proper authentication failure notification. ## Network Connection Failures Detecting network connections is nearly useless if an application cannot recover from them. Recovery is the hard part in "error handling and recovery". Fortunately, the recovery process for many applications follows a common scheme that Bunny can perform automatically for you. When Bunny detects TCP connection failure, it will try to reconnect every 5 seconds. Currently there is no limit on the number of reconnection attempts. To disable automatic connection recovery, pass `:automatic_recovery => false` to `Bunny.new`. ### Server-Initiated `connection.close` Server-initiated `connection.close` (issued due to an unrecoverable client issue or when a connection is forced to close via RabbitMQ management UI/HTTP API or when a server is shutting down)will result in an exception on the thread `Bunny::Session` was instantiated. Bunny can be instructed from such exceptions (see Automatic Recovery below). ### Automatic Recovery Many applications use the same recovery strategy that consists of the following steps: * Re-open channels * For each channel, re-declare exchanges (except for predefined ones) * For each channel, re-declare queues * For each queue, recover all bindings * For each queue, recover all consumers Bunny provides a feature known as "automatic recovery" that performs these steps after connection recovery, while taking care of some of the more tricky details such as recovery of server-named queues with consumers. Currently the topology recovery strategy is not configurable. When automatic recovery is disabled, Bunny will raise exceptions on the thread `Bunny::Session` was instantiated on. Bunny will recover from server-sent `connection.close`, if you don't want it to do so then pass `recover_from_connection_close: false` to `Bunny.new`. ## Channel-level Exceptions Channel-level exceptions are more common than connection-level ones and often indicate issues applications can recover from (such as consuming from or trying to delete a queue that does not exist). With Bunny, channel-level exceptions are raised as Ruby exceptions, for example, `Bunny::NotFound`, that provide access to the underlying `channel.close` method information: ``` ruby begin ch.queue_delete("queue_that_should_not_exist#{rand}") rescue Bunny::NotFound => e puts "Channel-level exception! Code: #{e.channel_close.reply_code}, message: #{e.channel_close.reply_text}" end ``` ``` ruby begin ch2 = conn.create_channel q = "bunny.examples.recovery.q#{rand}" ch2.queue_declare(q, :durable => false) ch2.queue_declare(q, :durable => true) rescue Bunny::PreconditionFailed => e puts "Channel-level exception! Code: #{e.channel_close.reply_code}, message: #{e.channel_close.reply_text}" ensure conn.create_channel.queue_delete(q) end ``` ### Common channel-level exceptions and what they mean A few channel-level exceptions are common and deserve more attention. #### 406 Precondition Failed
Description
The client requested a method that was not allowed because some precondition failed.
What might cause it
  • AMQP entity (a queue or exchange) was re-declared with attributes different from original declaration. Maybe two applications or pieces of code declare the same entity with different attributes. Note that different RabbitMQ client libraries historically use slightly different defaults for entities and this may cause attribute mismatches.
  • `Bunny::Channel#tx_commit` or `Bunny::Channel#tx_rollback` might be run on a channel that wasn't previously made transactional with `Bunny::Channel#tx_select`
Example RabbitMQ error message
  • PRECONDITION_FAILED - parameters for queue 'bunny.examples.channel_exception' in vhost '/' not equivalent
  • PRECONDITION_FAILED - channel is not transactional
#### 405 Resource Locked
Description
The client attempted to work with a server entity to which it has no access because another client is working with it.
What might cause it
  • Multiple applications (or different pieces of code/threads/processes/routines within a single application) might try to declare queues with the same name as exclusive.
  • Multiple consumer across multiple or single app might be registered as exclusive for the same queue.
Example RabbitMQ error message
RESOURCE_LOCKED - cannot obtain exclusive access to locked queue 'bunny.examples.queue' in vhost '/'
#### 404 Not Found
Description
The client attempted to use (publish to, delete, etc) an entity (exchange, queue) that does not exist.
What might cause it
Application miscalculates queue or exchange name or tries to use an entity that was deleted earlier
Example RabbitMQ error message
NOT_FOUND - no queue 'queue_that_should_not_exist0.6798199937619038' in vhost '/'
#### 403 Access Refused
Description
The client attempted to work with a server entity to which it has no access due to security settings.
What might cause it
Application tries to access a queue or exchange it has no permissions for (or right kind of permissions, for example, write permissions)
Example RabbitMQ error message
ACCESS_REFUSED - access to queue 'bunny.examples.channel_exception' in vhost 'bunny_testbed' refused for user 'bunny_reader'
## What to Read Next The documentation is organized as [a number of guides](/articles/guides.html), covering various topics. We recommend that you read the following guides first, if possible, in this order: * [Troubleshooting](/articles/troubleshooting.html) * [Using TLS (SSL) Connections](/articles/tls.html) ## Tell Us What You Think! Please take a moment to tell us what you think about this guide [on Twitter](http://twitter.com/rubyamqp) or the [Bunny mailing list](https://groups.google.com/forum/#!forum/ruby-amqp) Let us know what was unclear or what has not been covered. Maybe you do not like the guide style or grammar or discover spelling mistakes. Reader feedback is key to making the documentation better. ruby-amqp-bunny-b6569cd/docs/guides/exchanges.md000066400000000000000000001250001464043542000217030ustar00rootroot00000000000000--- title: "Working with RabbitMQ exchanges and publishing messages from Ruby with Bunny" layout: article --- ## About this guide This guide covers the use of exchanges according to the AMQP 0.9.1 specification, including broader topics related to message publishing, common usage scenarios and how to accomplish typical operations using Bunny. This work is licensed under a Creative Commons Attribution 3.0 Unported License (including images and stylesheets). The source is available [on GitHub](https://github.com/ruby-amqp/rubybunny.info). ## What version of Bunny does this guide cover? This guide covers Bunny 2.11.0 and later versions. ## Exchanges in AMQP 0.9.1 — Overview ### What are AMQP exchanges? An *exchange* accepts messages from a producer application and routes them to message queues. They can be thought of as the "mailboxes" of the AMQP world. Unlike some other messaging middleware products and protocols, in AMQP, messages are *not* published directly to queues. Messages are published to exchanges that route them to queue(s) using pre-arranged criteria called *bindings*. There are multiple exchange types in the AMQP 0.9.1 specification, each with its own routing semantics. Custom exchange types can be created to deal with sophisticated routing scenarios (e.g. routing based on geolocation data or edge cases) or just for convenience. ### Concept of Bindings A *binding* is an association between a queue and an exchange. A queue must be bound to at least one exchange in order to receive messages from publishers. Learn more about bindings in the [Bindings guide](/articles/bindings.html). ### Exchange attributes Exchanges have several attributes associated with them: * Name * Type (direct, fanout, topic, headers or some custom type) * Durability * Whether the exchange is auto-deleted when no longer used * Other metadata (sometimes known as *X-arguments*) ## Exchange types There are four built-in exchange types in AMQP v0.9.1: * Direct * Fanout * Topic * Headers As stated previously, each exchange type has its own routing semantics and new exchange types can be added by extending brokers with plugins. Custom exchange types begin with "x-", much like custom HTTP headers, e.g. [x-consistent-hash exchange](https://github.com/rabbitmq/rabbitmq-consistent-hash-exchange) or [x-random exchange](https://github.com/jbrisbin/random-exchange). ## Message attributes Before we start looking at various exchange types and their routing semantics, we need to introduce message attributes. Every AMQP message has a number of *attributes*. Some attributes are important and used very often, others are rarely used. AMQP message attributes are metadata and are similar in purpose to HTTP request and response headers. Every AMQP 0.9.1 message has an attribute called *routing key*. The routing key is an "address" that the exchange may use to decide how to route the message. This is similar to, but more generic than, a URL in HTTP. Most exchange types use the routing key to implement routing logic, but some ignore it and use other criteria (e.g. message content). ## Fanout exchanges ### How fanout exchanges route messages A fanout exchange routes messages to all of the queues that are bound to it and the routing key is ignored. If N queues are bound to a fanout exchange, when a new message is published to that exchange a *copy of the message* is delivered to all N queues. Fanout exchanges are ideal for the [broadcast routing](http://en.wikipedia.org/wiki/Broadcasting_%28computing%29) of messages. Graphically this can be represented as: ![fanout exchange routing](https://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/004_fanout_exchange.png) ### Declaring a fanout exchange There are two ways to declare a fanout exchange: * Using the `Bunny::Channel#fanout` method * Instantiate `Bunny::Exchange` directly Here are two examples to demonstrate: ``` ruby require "bunny" conn = Bunny.new conn.start ch = conn.create_channel x = ch.fanout("activity.events") ``` ``` ruby require "bunny" conn = Bunny.new conn.start ch = conn.create_channel x = Bunny::Exchange.new(ch, :fanout, "activity.events") ``` ### Fanout routing example To demonstrate fanout routing behavior we can declare ten server-named exclusive queues, bind them all to one fanout exchange and then publish a message to the exchange: ``` ruby #!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" puts "=> Fanout exchange routing" puts conn = Bunny.new conn.start ch = conn.create_channel x = ch.fanout("examples.pings") 10.times do |i| q = ch.queue("", :auto_delete => true).bind(x) q.subscribe do |delivery_info, properties, payload| puts "[consumer] #{q.name} received a message: #{payload}" end end x.publish("Ping") sleep 0.5 x.delete puts "Disconnecting..." conn.close ``` When run, this example produces the following output:
=> Fanout exchange routing

[consumer] amq.gen-A8z-tj-n_0U39GdPGncV-A received a message: Ping
[consumer] amq.gen-jht-OtRwdD8LuHMxrA5SNQ received a message: Ping
[consumer] amq.gen-LQTh8IdojOCrvOnEuFog8w received a message: Ping
[consumer] amq.gen-PV-Dg8_gSvLO9eK6le6wwQ received a message: Ping
[consumer] amq.gen-ofAMc3FXRZIj3O55fXDSwA received a message: Ping
[consumer] amq.gen-TXJiZEjwZ0squ12_Z9mP0A received a message: Ping
[consumer] amq.gen-XQjh2xrC9khbMZMg_0Zzfw received a message: Ping
[consumer] amq.gen-XVSKsdWwhyxRiJn-jAFEGg received a message: Ping
[consumer] amq.gen-ZaY2pD_9NaOICxAMWPoIYw received a message: Ping
[consumer] amq.gen-oElfvP_crgASWkk6EhrJLA received a message: Ping
Disconnecting...
Each of the queues bound to the exchange receives a *copy* of the message. ### Fanout use cases Because a fanout exchange delivers a copy of a message to every queue bound to it, its use cases are quite similar: * Massively multiplayer online (MMO) games can use it for leaderboard updates or other global events * Sport news sites can use fanout exchanges for distributing score updates to mobile clients in near real-time * Distributed systems can broadcast various state and configuration updates * Group chats can distribute messages between participants using a fanout exchange (although AMQP does not have a built-in concept of presence, so [XMPP](http://xmpp.org) may be a better choice) ### Pre-declared fanout exchanges AMQP 0.9.1 brokers must implement a fanout exchange type and pre-declare one instance with the name of `"amq.fanout"`. Applications can rely on that exchange always being available to them. Each vhost has a separate instance of that exchange, it is *not shared across vhosts* for obvious reasons. ## Direct exchanges ### How direct exchanges route messages A direct exchange delivers messages to queues based on a *message routing key*, an attribute that every AMQP v0.9.1 message contains. Here is how it works: * A queue binds to the exchange with a routing key K * When a new message with routing key R arrives at the direct exchange, the exchange routes it to the queue if K = R A direct exchange is ideal for the [unicast routing](http://en.wikipedia.org/wiki/Unicast) of messages (although they can be used for [multicast routing](http://en.wikipedia.org/wiki/Multicast) as well). Here is a graphical representation: ![direct exchange routing](https://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/005_direct_exchange.png) ### Declaring a direct exchange * Using the `Bunny::Channel#direct` method * Instantiate `Bunny::Exchange` directly Here are two examples to demonstrate: ``` ruby require "bunny" conn = Bunny.new conn.start ch = conn.create_channel x = ch.direct("imaging") ``` ``` ruby require "bunny" conn = Bunny.new conn.start ch = conn.create_channel x = Bunny::Exchange.new(ch, :direct, "imaging") ``` ### Direct routing example Since direct exchanges use the *message routing key* for routing, message producers need to specify it: ``` ruby #!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" puts "=> Direct exchange routing" puts conn = Bunny.new conn.start ch = conn.create_channel x = ch.direct("examples.imaging") q1 = ch.queue("", :auto_delete => true).bind(x, :routing_key => "resize") q1.subscribe do |delivery_info, properties, payload| puts "[consumer] #{q1.name} received a 'resize' message" end q2 = ch.queue("", :auto_delete => true).bind(x, :routing_key => "watermark") q2.subscribe do |delivery_info, properties, payload| puts "[consumer] #{q2.name} received a 'watermark' message" end # just an example data = rand.to_s x.publish(data, :routing_key => "resize") x.publish(data, :routing_key => "watermark") sleep 0.5 x.delete q1.delete q2.delete puts "Disconnecting..." conn.close ``` The routing key will then be compared for equality with routing keys on bindings, and consumers that subscribed with the same routing key each get a copy of the message. Output for the example looks like this: ``` => Direct exchange routing [consumer] amq.gen-8XIeaBCmykwnJUtHVEkT5Q received a 'resize' message [consumer] amq.gen-Zht5YW3_MhK-YBLZouxp5Q received a 'watermark' message Disconnecting... ``` ### Direct Exchanges and Load Balancing of Messages Direct exchanges are often used to distribute tasks between multiple workers (instances of the same application) in a round robin manner. When doing so, it is important to understand that, in AMQP 0.9.1, *messages are load balanced between consumers and not between queues*. The [Queues and Consumers](/articles/queues.html) guide provides more information on this subject. ### Pre-declared direct exchanges AMQP 0.9.1 brokers must implement a direct exchange type and pre-declare two instances: * `amq.direct` * *""* exchange known as *default exchange* (unnamed, referred to as an empty string by many clients including Bunny) Applications can rely on those exchanges always being available to them. Each vhost has separate instances of those exchanges, they are *not shared across vhosts* for obvious reasons. ### Default exchange The default exchange is a direct exchange with no name (Bunny refers to it using an empty string) pre-declared by the broker. It has one special property that makes it very useful for smaller applications, namely that *every queue is automatically bound to it with a routing key which is the same as the queue name*. For example, when you declare a queue with the name of "search.indexing.online", RabbitMQ will bind it to the default exchange using "search.indexing.online" as the routing key. Therefore a message published to the default exchange with routing key = "search.indexing.online" will be routed to the queue "search.indexing.online". In other words, the default exchange makes it *seem like it is possible to deliver messages directly to queues*, even though that is not technically what is happening. The default exchange is used by the "Hello, World" example: ``` ruby #!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" conn = Bunny.new conn.start ch = conn.create_channel q = ch.queue("bunny.examples.hello_world", :auto_delete => true) q.subscribe do |delivery_info, properties, payload| puts "Received #{payload}" end q.publish("Hello!", :routing_key => q.name) sleep 1.0 conn.close ``` ### Direct Exchange Use Cases Direct exchanges can be used in a wide variety of cases: * Direct (near real-time) messages to individual players in an MMO game * Delivering notifications to specific geographic locations (for example, points of sale) * Distributing tasks between multiple instances of the same application all having the same function, for example, image processors * Passing data between workflow steps, each having an identifier (also consider using headers exchange) * Delivering notifications to individual software services in the network ## Topic Exchanges ### How Topic Exchanges Route Messages Topic exchanges route messages to one or many queues based on matching between a message routing key and the pattern that was used to bind a queue to an exchange. The topic exchange type is often used to implement various [publish/subscribe pattern](http://en.wikipedia.org/wiki/Publish/subscribe) variations. Topic exchanges are commonly used for the [multicast routing](http://en.wikipedia.org/wiki/Multicast) of messages. ![](http://upload.wikimedia.org/wikipedia/commons/thumb/3/30/Multicast.svg/500px-Multicast.svg.png) Topic exchanges can be used for [broadcast routing](http://en.wikipedia.org/wiki/Broadcasting_%28computing%29), but fanout exchanges are usually more efficient for this use case. ### Topic Exchange Routing Example Two classic examples of topic-based routing are stock price updates and location-specific data (for instance, weather broadcasts). Consumers indicate which topics they are interested in (think of it like subscribing to a feed for an individual tag of your favourite blog as opposed to the full feed). The routing is enabled by specifying a *routing pattern* to the `Bunny::Queue#bind` method, for example: ``` ruby x = ch.topic("weather", :auto_delete => true) q = ch.queue("americas.south", :auto_delete => true).bind(x, :routing_key => "americas.south.#") q.subscribe do |delivery_info, properties, payload| puts "An update for South America: #{payload}, routing key is #{delivery_info.routing_key}" end ``` In the example above we bind a queue with the name of "americas.south" to the topic exchange declared earlier using the `Bunny::Queue#bind` method. This means that only messages with a routing key matching "americas.south.#" will be routed to the "americas.south" queue. A routing pattern consists of several words separated by dots, in a similar way to URI path segments being joined by slash. A few of examples: * asia.southeast.thailand.bangkok * sports.basketball * usa.nasdaq.aapl * tasks.search.indexing.accounts The following routing keys match the "americas.south.#" pattern: * americas.south * americas.south.*brazil* * americas.south.*brazil.saopaulo* * americas.south.*chile.santiago* In other words, the "#" part of the pattern matches 0 or more words. For the pattern "americas.south.*", some matching routing keys are: * americas.south.*brazil* * americas.south.*chile* * americas.south.*peru* but not * americas.south * americas.south.chile.santiago As you can see, the "*" part of the pattern matches 1 word only. Full example: ``` ruby #!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" connection = Bunny.new connection.start channel = connection.create_channel # topic exchange name can be any string exchange = channel.topic("weather", :auto_delete => true) # Subscribers. channel.queue("americas.north").bind(exchange, :routing_key => "americas.north.#").subscribe do |delivery_info, properties, payload| puts "An update for North America: #{payload}, routing key is #{delivery_info.routing_key}" end channel.queue("americas.south").bind(exchange, :routing_key => "americas.south.#").subscribe do |delivery_info, properties, payload| puts "An update for South America: #{payload}, routing key is #{delivery_info.routing_key}" end channel.queue("us.california").bind(exchange, :routing_key => "americas.north.us.ca.*").subscribe do |delivery_info, properties, payload| puts "An update for US/California: #{payload}, routing key is #{delivery_info.routing_key}" end channel.queue("us.tx.austin").bind(exchange, :routing_key => "#.tx.austin").subscribe do |delivery_info, properties, payload| puts "An update for Austin, TX: #{payload}, routing key is #{delivery_info.routing_key}" end channel.queue("it.rome").bind(exchange, :routing_key => "europe.italy.rome").subscribe do |delivery_info, properties, payload| puts "An update for Rome, Italy: #{payload}, routing key is #{delivery_info.routing_key}" end channel.queue("asia.hk").bind(exchange, :routing_key => "asia.southeast.hk.#").subscribe do |delivery_info, properties, payload| puts "An update for Hong Kong: #{payload}, routing key is #{delivery_info.routing_key}" end exchange.publish("San Diego update", :routing_key => "americas.north.us.ca.sandiego"). publish("Berkeley update", :routing_key => "americas.north.us.ca.berkeley"). publish("San Francisco update", :routing_key => "americas.north.us.ca.sanfrancisco"). publish("New York update", :routing_key => "americas.north.us.ny.newyork"). publish("São Paulo update", :routing_key => "americas.south.brazil.saopaulo"). publish("Hong Kong update", :routing_key => "asia.southeast.hk.hongkong"). publish("Kyoto update", :routing_key => "asia.southeast.japan.kyoto"). publish("Shanghai update", :routing_key => "asia.southeast.prc.shanghai"). publish("Rome update", :routing_key => "europe.italy.roma"). publish("Paris update", :routing_key => "europe.france.paris") sleep 1.0 connection.close ``` ### Topic Exchange Use Cases Topic exchanges have a very broad set of use cases. Whenever a problem involves multiple consumers/applications that selectively choose which type of messages they want to receive, the use of topic exchanges should be considered. To name a few examples: * Distributing data relevant to specific geographic location, for example, points of sale * Background task processing done by multiple workers, each capable of handling specific set of tasks * Stocks price updates (and updates on other kinds of financial data) * News updates that involve categorization or tagging (for example, only for a particular sport or team) * Orchestration of services of different kinds in the cloud * Distributed architecture/OS-specific software builds or packaging where each builder can handle only one architecture or OS ## Declaring/Instantiating Exchanges With Bunny, exchanges can be declared in two ways: by instantiating `Bunny::Exchange` or by using a number of convenience methods on `Bunny::Channel`: * `Bunny::Channel#default_exchange` * `Bunny::Channel#direct` * `Bunny::Channel#topic` * `Bunny::Channel#fanout` * `Bunny::Channel#headers` The previous sections on specific exchange types (direct, fanout, headers, etc.) provide plenty of examples of how these methods can be used. ## Checking if an Exchange Exists Sometimes it's convenient to check if an exchange exists. To do so, at the protocol level you use `exchange.declare` with `passive` seto to `true`. In response RabbitMQ responds with a channel exception if the exchange does not exist. Bunny provides a convenience method, `Bunny::Session#exchange_exists?`, to do this: ``` ruby conn = Bunny.new conn.start conn.exchange_exists?("logs") ``` ## Publishing messages To publish a message to an exchange, use `Bunny::Exchange#publish`: ``` ruby x.publish("some data") ``` The method accepts message body and a number of message and delivery metadata options. Routing key can be blank (`""`) but never `nil`. The body needs to be a string. The message payload is completely opaque to the library and is not modified by Bunny or RabbitMQ in any way. ### Data serialization You are encouraged to take care of data serialization before publishing (i.e. by using JSON, Thrift, Protocol Buffers or some other serialization library). Note that because AMQP is a binary protocol, text formats like JSON largely lose their advantage of being easy to inspect as data travels across the network, so if bandwidth efficiency is important, consider using [MessagePack](http://msgpack.org/) or [Protocol Buffers](http://code.google.com/p/protobuf/). A few popular options for data serialization are: * JSON: [json gem](https://rubygems.org/gems/json) (part of standard Ruby library on Ruby 1.9) or [yajl-ruby](https://rubygems.org/gems/yajl-ruby) (Ruby bindings to YAJL) * BSON: [bson gem](https://rubygems.org/gems/bson) for JRuby (implemented as a Java extension) or [bson_ext](https://rubygems.org/bson_ext) for C-based Rubies * [Message Pack](http://msgpack.org) has Ruby bindings and provides a Java implementation for JRuby * XML: [Nokogiri](https://nokogiri.org) is a swiss army knife for XML processing with Ruby, built on top of libxml2 * Protocol Buffers: [beefcake](https://github.com/bmizerany/beefcake) ### Message metadata RabbitMQ messages have various metadata attributes that can be set when a message is published. Some of the attributes are well-known and mentioned in the AMQP 0.9.1 specification, others are specific to a particular application. Well-known attributes are listed here as options that `Bunny::Exchange#publish` takes: * `:persistent` * `:mandatory` * `:timestamp` * `:expiration` * `:type` * `:reply_to` * `:content_type` * `:content_encoding` * `:correlation_id` * `:priority` * `:message_id` * `:user_id` * `:app_id` All other attributes can be added to a *headers table* (in Ruby, a hash) that `Bunny::Exchange#publish` accepts as the `:headers` option. An example: ``` ruby # or Process.clock_gettime(Process::CLOCK_MONOTONIC) if using a monotonic clock is important now = Time.now x.publish("hello", :routing_key => queue_name, :app_id => "bunny.example", :priority => 8, :type => "kinda.checkin", # headers table keys can be anything :headers => { :coordinates => { :latitude => 59.35, :longitude => 18.066667 }, :time => now, :participants => 11, :venue => "Stockholm", :true_field => true, :false_field => false, :nil_field => nil, :ary_field => ["one", 2.0, 3, [{"abc" => 123}]] }, :timestamp => now.to_i, :reply_to => "a.sender", :correlation_id => "r-1", :message_id => "m-1") ```
:routing_key
Used for routing messages depending on the exchange type and configuration.
:persistent
When set to true, RabbitMQ will persist message to disk.
:mandatory
This flag tells the server how to react if the message cannot be routed to a queue. If this flag is set to true, the server will return an unroutable message to the producer with a `basic.return` AMQP method. If this flag is set to false, the server silently drops the message.
:content_type
MIME content type of message payload. Has the same purpose/semantics as HTTP Content-Type header.
:content_encoding
MIME content encoding of message payload. Has the same purpose/semantics as HTTP Content-Encoding header.
:priority
Message priority, from 0 to 9.
:message_id
Message identifier as a string. If applications need to identify messages, it is recommended that they use this attribute instead of putting it into the message payload.
:reply_to
Commonly used to name a reply queue (or any other identifier that helps a consumer application to direct its response). Applications are encouraged to use this attribute instead of putting this information into the message payload.
:correlation_id
ID of the message that this message is a reply to. Applications are encouraged to use this attribute instead of putting this information into the message payload.
:type
Message type as a string. Recommended to be used by applications instead of including this information into the message payload.
:user_id
Sender's identifier. Note that RabbitMQ will check that the value of this attribute is the same as username AMQP connection was authenticated with, it SHOULD NOT be used to transfer, for example, other application user ids or be used as a basis for some kind of Single Sign-On solution.
:app_id
Application identifier string, for example, "eventoverse" or "webcrawler"
:timestamp
Timestamp of the moment when message was sent, in seconds since the Epoch
:expiration
Message expiration specification as a string
:arguments
A map of any additional attributes that the application needs. Nested hashes are supported. Keys must be strings.
It is recommended that application authors use well-known message attributes when applicable instead of relying on custom headers or placing information in the message body. For example, if your application messages have priority, publishing timestamp, type and content type, you should use the respective AMQP message attributes instead of reinventing the wheel. ### Validated User ID In some scenarios it is useful for consumers to be able to know the identity of the user who published a message. RabbitMQ implements a feature known as [validated User ID](http://www.rabbitmq.com/extensions.html#validated-user-id). If this property is set by a publisher, its value must be the same as the name of the user used to open the connection. If the user-id property is not set, the publisher's identity is not validated and remains private. ### Publishing Callbacks and Reliable Delivery in Distributed Environments A commonly asked question about RabbitMQ clients is "how to execute a piece of code after a message is received". Message publishing with Bunny happens in several steps: * `Bunny::Exchange#publish` takes a payload and various metadata attributes * Resulting payload is staged for writing * On the next event loop tick, data is transferred to the OS kernel using one of the underlying NIO APIs * OS kernel buffers data before sending it * Network driver may also employ buffering
As you can see, "when data is sent" is a complicated issue and while methods to flush buffers exist, flushing buffers does not guarantee that the data was received by the broker because it might have crashed while data was travelling down the wire. The only way to reliably know whether data was received by the broker or a peer application is to use message acknowledgements. This is how TCP works and this approach is proven to work at the enormous scale of the modern Internet. AMQP 0.9.1 fully embraces this fact and Bunny follows.
In cases when you cannot afford to lose a single message, AMQP 0.9.1 applications can use one (or a combination of) the following protocol features: * Publisher confirms (a RabbitMQ-specific extension to AMQP 0.9.1) * Publishing messages as mandatory * Transactions (these introduce noticeable overhead and have a relatively narrow set of use cases) A more detailed overview of the pros and cons of each option can be found in a [blog post that introduces Publisher Confirms extension](http://bit.ly/rabbitmq-publisher-confirms) by the RabbitMQ team. The next sections of this guide will describe how the features above can be used with Bunny. ### Publishing messages as mandatory When publishing messages, it is possible to use the `:mandatory` option to publish a message as "mandatory". When a mandatory message cannot be *routed* to any queue (for example, there are no bindings or none of the bindings match), the message is returned to the producer. The following code example demonstrates a message that is published as mandatory but cannot be routed (no bindings) and thus is returned back to the producer: ``` ruby #!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" puts "=> Publishing messages as mandatory" puts conn = Bunny.new conn.start ch = conn.create_channel x = ch.default_exchange x.on_return do |return_info, properties, content| puts "Got a returned message: #{content}" end q = ch.queue("", :exclusive => true) q.subscribe do |delivery_info, properties, content| puts "Consumed a message: #{content}" end x.publish("This will NOT be returned", :mandatory => true, :routing_key => q.name) x.publish("This will be returned", :mandatory => true, :routing_key => "akjhdfkjsh#{rand}") sleep 0.5 puts "Disconnecting..." conn.close ``` ### Returned messages When a message is returned, the application that produced it can handle that message in different ways: * Store it for later redelivery in a persistent store * Publish it to a different destination * Log the event and discard the message Returned messages contain information about the exchange they were published to. Bunny associates returned message callbacks with consumers. To handle returned messages, use `Bunny::Exchange#on_return`: ``` ruby #!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" puts "=> Publishing messages as mandatory" puts conn = Bunny.new conn.start ch = conn.create_channel x = ch.default_exchange x.on_return do |return_info, properties, content| puts "Got a returned message: #{content}" end q = ch.queue("", :exclusive => true) q.subscribe do |delivery_info, properties, content| puts "Consumed a message: #{content}" end x.publish("This will NOT be returned", :mandatory => true, :routing_key => q.name) x.publish("This will be returned", :mandatory => true, :routing_key => "akjhdfkjsh#{rand}") sleep 0.5 puts "Disconnecting..." conn.close ``` A returned message handler has access to AMQP method (`basic.return`) information, message metadata and payload (as a byte array). The metadata and message body are returned without modifications so that the application can store the message for later redelivery. ### Publishing Persistent Messages Messages potentially spend some time in the queues to which they were routed before they are consumed. During this period of time, the broker may crash or experience a restart. To survive it, messages must be persisted to disk. This has a negative effect on performance, especially with network attached storage like NAS devices and Amazon EBS. AMQP 0.9.1 lets applications trade off performance for durability, or vice versa, on a message-by-message basis. To publish a persistent message, use the `:persistent` option that `Bunny::Exchange#publish` accepts: ``` ruby x.publish(data, :persistent => true) ``` **Note** that in order to survive a broker crash, the messages MUST be persistent and the queue that they were routed to MUST be durable. [Durability and Message Persistence](/articles/durability.html) provides more information on the subject. ### Message Priority Starting with RabbitMQ 3.5, queues can be [instructed to support message priorities](https://www.rabbitmq.com/priority.html). To specify a priority on a message, pass the `:priority` key to `Bunny::Exchange#publish`. Note that priority queues have certain [limitations listed in the RabbitMQ documentation](https://www.rabbitmq.com/priority.html). ### Publishing In Multi-threaded Environments
When using Bunny in multi-threaded environments, the rule of thumb is: avoid sharing channels across threads.
In other words, publishers in your application that publish from separate threads should use their own channels. The same is a good idea for consumers. ## Headers exchanges Now that message attributes and publishing have been introduced, it is time to take a look at one more core exchange type in AMQP 0.9.1. It is called the *headers exchange type* and is quite powerful. ### How headers exchanges route messages #### An Example Problem Definition The best way to explain headers-based routing is with an example. Imagine a distributed [continuous integration](http://martinfowler.com/articles/continuousIntegration.html) system that distributes builds across multiple machines with different hardware architectures (x86, IA-64, AMD64, ARM family and so on) and operating systems. It strives to provide a way for a community to contribute machines to run tests on and a nice build matrix like [the one WebKit uses](http://build.webkit.org/waterfall?category=core). One key problem such systems face is build distribution. It would be nice if a messaging broker could figure out which machine has which OS, architecture or combination of the two and route build request messages accordingly. A headers exchange is designed to help in situations like this by routing on multiple attributes that are more easily expressed as message metadata attributes (headers) rather than a routing key string. #### Routing on Multiple Message Attributes Headers exchanges route messages based on message header matching. Headers exchanges ignore the routing key attribute. Instead, the attributes used for routing are taken from the "headers" attribute. When a queue is bound to a headers exchange, the `:arguments` attribute is used to define matching rules: ``` ruby q = ch.queue("hosts.ip-172-37-11-56") x = ch.headers("requests") q.bind(x, :arguments => {"os" => "linux"}) ``` When matching on one header, a message is considered matching if the value of the header equals the value specified upon binding. An example that demonstrates headers routing: ``` ruby #!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" puts "=> Headers exchange routing" puts conn = Bunny.new conn.start ch = conn.create_channel x = ch.headers("headers") q1 = ch.queue("", :exclusive => true).bind(x, :arguments => {"os" => "linux", "cores" => 8, "x-match" => "all"}) q2 = ch.queue("", :exclusive => true).bind(x, :arguments => {"os" => "osx", "cores" => 4, "x-match" => "any"}) q1.subscribe do |delivery_info, properties, content| puts "#{q1.name} received #{content}" end q2.subscribe do |delivery_info, properties, content| puts "#{q2.name} received #{content}" end x.publish("8 cores/Linux", :headers => {"os" => "linux", "cores" => 8}) x.publish("8 cores/OS X", :headers => {"os" => "osx", "cores" => 8}) x.publish("4 cores/Linux", :headers => {"os" => "linux", "cores" => 4}) sleep 0.5 conn.close ``` When executed, it outputs ``` => Headers exchange routing amq.gen-xhIzykDAjfcC4orMsi0O6Q received 8 cores/Linux amq.gen-6O1oKjVd8QbKr7zyy7ssbg received 8 cores/OS X amq.gen-6O1oKjVd8QbKr7zyy7ssbg received 4 cores/Linux ``` #### Matching All vs Matching One It is possible to bind a queue to a headers exchange using more than one header for matching. In this case, the broker needs one more piece of information from the application developer, namely, should it consider messages with any of the headers matching, or all of them? This is what the "x-match" binding argument is for. When the `"x-match"` argument is set to `"any"`, just one matching header value is sufficient. So in the example above, any message with a "cores" header value equal to 8 will be considered matching. ### Declaring a Headers Exchange There are two ways to declare a headers exchange, either instantiate `Bunny::Exchange` directly: ``` ruby x = Bunny::Exchange.new(ch, :headers, "matching") ``` Or use the `Bunny::Channel#headers` method: ``` ruby x = ch.headers("matching") ``` ### Headers Exchange Routing When there is just one queue bound to a headers exchange, messages are routed to it if any or all of the message headers match those specified upon binding. Whether it is "any header" or "all of them" depends on the `"x-match"` header value. In the case of multiple queues, a headers exchange will deliver a copy of a message to each queue, just like direct exchanges do. Distribution rules between consumers on a particular queue are the same as for a direct exchange. ### Headers Exchange Use Cases Headers exchanges can be looked upon as "direct exchanges on steroids" and because they route based on header values, they can be used as direct exchanges where the routing key does not have to be a string; it could be an integer or a hash (dictionary) for example. Some specific use cases: * Transfer of work between stages in a multi-step workflow ([routing slip pattern](http://eaipatterns.com/RoutingTable.html)) * Distributed build/continuous integration systems can distribute builds based on multiple parameters (OS, CPU architecture, availability of a particular package). ### Pre-declared Headers Exchanges RabbitMQ implements a headers exchange type and pre-declares one instance with the name of `"amq.match"`. RabbitMQ also pre-declares one instance with the name of `"amq.headers"`. Applications can rely on those exchanges always being available to them. Each vhost has a separate instance of those exchanges and they are *not shared across vhosts* for obvious reasons. ## Custom Exchange Types ### consistent-hash The [consistent hashing AMQP exchange type](https://github.com/rabbitmq/rabbitmq-consistent-hash-exchange) is a custom exchange type developed as a RabbitMQ plugin. It uses [consistent hashing](http://michaelnielsen.org/blog/consistent-hashing/) to route messages to queues. This helps distribute messages between queues more or less evenly. A quote from the project README: > In various scenarios, you may wish to ensure that messages sent to an exchange are consistently and equally distributed across a number of different queues based on > the routing key of the message. You could arrange for this to occur yourself by using a direct or topic exchange, binding queues to that exchange and then publishing > messages to that exchange that match the various binding keys. > > However, arranging things this way can be problematic: > > It is difficult to ensure that all queues bound to the exchange will receive a (roughly) equal number of messages without baking in to the publishers quite a lot of > knowledge about the number of queues and their bindings. > > If the number of queues changes, it is not easy to ensure that the new topology still distributes messages between the different queues evenly. > > Consistent Hashing is a hashing technique whereby each bucket appears at multiple points throughout the hash space, and the bucket selected is the nearest > higher (or lower, it doesn't matter, provided it's consistent) bucket to the computed hash (and the hash space wraps around). The effect of this is that when a new > bucket is added or an existing bucket removed, only a very few hashes change which bucket they are routed to. > > In the case of Consistent Hashing as an exchange type, the hash is calculated from the hash of the routing key of each message received. Thus messages that have > the same routing key will have the same hash computed, and thus will be routed to the same queue, assuming no bindings have changed. ### x-random The [x-random AMQP exchange type](https://github.com/jbrisbin/random-exchange) is a custom exchange type developed as a RabbitMQ plugin by Jon Brisbin. A quote from the project README: > It is basically a direct exchange, with the exception that, instead of each consumer bound to that exchange with the same routing key > getting a copy of the message, the exchange type randomly selects a queue to route to. This plugin is licensed under [Mozilla Public License 1.1](http://www.mozilla.org/MPL/MPL-1.1.html), same as RabbitMQ. ## Using the Publisher Confirms Extension Please refer to [RabbitMQ Extensions guide](/articles/extensions.html) ### Message Acknowledgements and Their Relationship to Transactions and Publisher Confirms Consumer applications (applications that receive and process messages) may occasionally fail to process individual messages, or might just crash. Additionally, network issues might be experienced. This raises a question - "when should the RabbitMQ remove messages from queues?" This topic is covered in depth in the [Queues guide](/articles/queues.html), including prefetching and examples. In this guide, we will only mention how message acknowledgements are related to AMQP transactions and the Publisher Confirms extension. Let us consider a publisher application (P) that communications with a consumer (C) using AMQP 0.9.1. Their communication can be graphically represented like this:
-----       -----       -----
|   |   S1  |   |   S2  |   |
| P | ====> | B | ====> | C |
|   |       |   |       |   |
-----       -----       -----
We have two network segments, S1 and S2. Each of them may fail. A publisher (P) is concerned with making sure that messages cross S1, while the broker (B) and consumer (C) are concerned with ensuring that messages cross S2 and are only removed from the queue when they are processed successfully. Message acknowledgements cover reliable delivery over S2 as well as successful processing. For S1, P has to use transactions (a heavyweight solution) or the more lightweight Publisher Confirms, a RabbitMQ-specific extension. ## Binding Queues to Exchanges Queues are bound to exchanges using `Bunny::Queue#bind`. This topic is described in detail in the [Queues and Consumers guide](/articles/queues.html). ## Unbinding Queues from Exchanges Queues are unbound from exchanges using `Bunny::Queue#unbind`. This topic is described in detail in the [Queues and Consumers guide](/articles/queues.html). ## Deleting Exchanges ### Explicitly Deleting an Exchange Exchanges are deleted using the `Bunny::Exchange#delete`: ``` ruby x = ch.topic("groups.013c6a65a1de9b15658446c6570ec39ff615ba15") x.delete ``` ### Auto-deleted exchanges Exchanges can be *auto-deleted*. To declare an exchange as auto-deleted, use the `:auto_delete` option on declaration: ``` ruby ch.topic("groups.013c6a65a1de9b15658446c6570ec39ff615ba15", :auto_delete => true) ``` An auto-deleted exchange is removed when the last queue bound to it is unbound. ## Exchange durability vs Message durability See [Durability guide](/articles/durability.html) ## Wrapping Up Publishers publish messages to exchanges. Messages are then routed to queues according to rules called bindings that applications define. There are 4 built-in exchange types in RabbitMQ and it is possible to create custom types. Messages have a set of standard properties (e.g. type, content type) and can carry an arbitrary map of headers. Most functions related to exchanges and publishing are found in two Bunny classes: * `Bunny::Exchange` * `Bunny::Channel` ## What to Read Next The documentation is organized as [a number of guides](/articles/guides.html), covering various topics. We recommend that you read the following guides first, if possible, in this order: * [Bindings](/articles/bindings.html) * [RabbitMQ Extensions to AMQP 0.9.1](/articles/extensions.html) * [Durability and Related Matters](/articles/durability.html) * [Error Handling and Recovery](/articles/error_handling.html) * [Concurrency Considerations](/articles/concurrency.html) * [Troubleshooting](/articles/troubleshooting.html) * [Using TLS (SSL) Connections](/articles/tls.html) ## Tell Us What You Think! Please take a moment to tell us what you think about this guide [on Twitter](http://twitter.com/rubyamqp) or the [Bunny mailing list](https://groups.google.com/forum/#!forum/ruby-amqp) Let us know what was unclear or what has not been covered. Maybe you do not like the guide style or grammar or discover spelling mistakes. Reader feedback is key to making the documentation better. ruby-amqp-bunny-b6569cd/docs/guides/extensions.md000066400000000000000000000457221464043542000221510ustar00rootroot00000000000000--- title: "Working with RabbitMQ extensions from Ruby with Bunny" layout: article --- ## About This Guide Bunny 0.9 supports all [RabbitMQ extensions to AMQP 0.9.1](http://www.rabbitmq.com/extensions.html): * [Publisher confirms](http://www.rabbitmq.com/confirms.html) * [Negative acknowledgements](http://www.rabbitmq.com/nack.html) (basic.nack) * [Exchange-to-Exchange Bindings](http://www.rabbitmq.com/e2e.html) * [Alternate Exchanges](http://www.rabbitmq.com/ae.html) * [Per-queue Message Time-to-Live](http://www.rabbitmq.com/ttl.html#per-queue-message-ttl) * [Per-message Time-to-Live](http://www.rabbitmq.com/ttl.html#per-message-ttl) * [Queue Leases](http://www.rabbitmq.com/ttl.html#queue-ttl) * [Consumer Cancellation Notifications](http://www.rabbitmq.com/consumer-cancel.html) * [Sender-selected Distribution](http://www.rabbitmq.com/sender-selected.html) * [Dead Letter Exchanges](http://www.rabbitmq.com/dlx.html) * [Validated user_id](http://www.rabbitmq.com/validated-user-id.html) This guide briefly describes how to use these extensions with Bunny. This work is licensed under a Creative Commons Attribution 3.0 Unported License (including images and stylesheets). The source is available [on GitHub](https://github.com/ruby-amqp/rubybunny.info). ## What version of Bunny does this guide cover? This guide covers Bunny 0.9. ## Enabling RabbitMQ Extensions You don't need to require any additional files to make Bunny 0.9 support RabbitMQ extensions. The support is built into the core. ## Per-queue Message Time-to-Live Per-queue Message Time-to-Live (TTL) is a RabbitMQ extension to AMQP 0.9.1 that allows developers to control how long a message published to a queue can live before it is discarded. A message that has been in the queue for longer than the configured TTL is said to be dead. Dead messages will not be delivered to consumers and cannot be fetched using the *basic.get* operation (`Bunny::Queue#pop`). Message TTL is specified using the *x-message-ttl* argument on declaration. With Bunny, you pass it to `Bunny::Queue#initialize` or `Bunny::Channel#queue`: ``` ruby # 1000 milliseconds channel.queue("", :arguments => { "x-message-ttl" => 1000 }) ``` When a published message is routed to multiple queues, each of the queues gets a _copy of the message_. If the message subsequently dies in one of the queues, it has no effect on copies of the message in other queues. ### Example The example below sets the message TTL for a new server-named queue to be 1000 milliseconds. It then publishes several messages that are routed to the queue and tries to fetch messages using the *basic.get* AMQP 0.9.1 method (`Bunny::Queue#pop` after 0.7 and 1.5 seconds: ``` ruby #!/usr/bin/env ruby # encoding: utf-8 require "bunny" puts "=> Using per-queue message TTL" puts conn = Bunny.new conn.start ch = conn.create_channel x = ch.fanout("amq.fanout") q = ch.queue("", :exclusive => true, :arguments => {"x-message-ttl" => 1000}).bind(x) 10.times do |i| x.publish("Message #{i}") end sleep 0.7 _, _, content1 = q.pop puts "Fetched #{content1.inspect} after 0.7 second" sleep 0.8 _, _, content2 = q.pop msg = if content2 content2.inspect else "nothing" end puts "Fetched #{msg} after 1.5 second" sleep 0.7 puts "Closing..." conn.close ``` ### Learn More See also rabbitmq.com section on [Per-queue Message TTL](http://www.rabbitmq.com/ttl.html#per-queue-message-ttl) ## Publisher Confirms (Publisher Acknowledgements) In some situations it is essential that messages are reliably delivered to the RabbitMQ broker and not lost on the way. The only reliable ways of assuring message delivery are by using publisher confirms or [transactions](http://www.rabbitmq.com/semantics.html). The [Publisher Confirms AMQP extension](http://www.rabbitmq.com/blog/2011/02/10/introducing-publisher-confirms/) was designed to solve the reliable publishing problem in a more lightweight way compared to transactions. Publisher confirms are similar to message acknowledgements (documented in the [Queues and Consumers](/articles/queues.html) guide), but involve a publisher and a RabbitMQ node instead of a consumer and a RabbitMQ node. ![RabbitMQ Message Acknowledgements](https://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/006_amqp_091_message_acknowledgements.png) ![RabbitMQ Publisher Confirms](https://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/007_rabbitmq_publisher_confirms.png) ### How To Use It With Bunny 0.9+ To use publisher confirms, first put the channel into confirmation mode using the `Bunny::Channel#confirm_select` method: ``` channel.confirm_select ``` From this moment on, every message published on this channel will cause the channel's _publisher index_ (message counter) to be incremented. It is possible to access the index using `Bunny::Channel#next_publish_seq_no` method. To check whether the channel is in confirmation mode, use the `Bunny::Channel#using_publisher_confirmations?` method: ``` ruby ch.using_publisher_confirmations? # => false ch.confirm_select ch.using_publisher_confirmations? # => true ``` ### Example ``` ruby #!/usr/bin/env ruby # encoding: utf-8 require "bunny" puts "=> Using publisher confirms" puts conn = Bunny.new conn.start ch = conn.create_channel x = ch.fanout("amq.fanout") q = ch.queue("", :exclusive => true).bind(x) # Put channel in confirmation mode ch.confirm_select 1000.times do x.publish("") end # Block until all messages have been confirmed success = ch.wait_for_confirms if !success ch.nacked_set.each do |n| # Do something with the nacked message ID end end sleep 0.2 puts "Processed all published messages. #{q.name} now has #{q.message_count} messages." sleep 0.5 puts "Closing..." conn.close ``` In the example above, the `Bunny::Channel#wait_for_confirms` method blocks (waits) until all of the published messages are confirmed by the RabbitMQ broker. **Note** that a message may be nacked by the broker if, for some reason, it cannot take responsibility for the message. In that case, the `wait_for_confirms` method will return `false` and there is also a Ruby `Set` of nacked message IDs (`channel.nacked_set`) that can be inspected and dealt with as required. ### Learn More See also rabbitmq.com section on [Publisher Confirms](http://www.rabbitmq.com/confirms.html) ## basic.nack The AMQP 0.9.1 specification defines the basic.reject method that allows clients to reject individual, delivered messages, instructing the broker to either discard them or requeue them. Unfortunately, basic.reject provides no support for negatively acknowledging messages in bulk. To solve this, RabbitMQ supports the basic.nack method that provides all of the functionality of basic.reject whilst also allowing for bulk processing of messages. ### How To Use It With Bunny 0.9+ Bunny exposes `basic.nack` via the `Bunny::Channel#nack` method, similar to `Bunny::Channel#ack` and `Bunny::Channel#reject`: ``` ruby # nack multiple messages at once subject.nack(delivery_info.delivery_tag, false, true) # nack a single message at once, the same as ch.reject(delivery_info.delivery_tag, false) subject.nack(delivery_info.delivery_tag, false) ``` ### Example ``` ruby #!/usr/bin/env ruby # encoding: utf-8 require "bunny" puts "=> Using publisher confirms" puts conn = Bunny.new conn.start ch = conn.create_channel q = ch.queue("", :exclusive => true) 20.times do q.publish("") end 20.times do delivery_info, _, _ = q.pop(:manual_ack => true) if delivery_info.delivery_tag == 20 # requeue them all at once with basic.nack ch.nack(delivery_info.delivery_tag, true, true) end end puts "Queue #{q.name} still has #{q.message_count} messages in it" sleep 0.7 puts "Disconnecting..." conn.close ``` ### Learn More See also rabbitmq.com section on [basic.nack](http://www.rabbitmq.com/nack.html) ## Alternate Exchanges The Alternate Exchanges RabbitMQ extension to AMQP 0.9.1 allows developers to define "fallback" exchanges where unroutable messages will be sent. ### How To Use It With Bunny 0.9+ To specify exchange A as an alternate exchange to exchange B, specify the 'alternate-exchange' argument on declaration of B: ``` ruby ch = conn.create_channel x1 = ch.fanout("bunny.examples.ae.exchange1", :auto_delete => true, :durable => false) x2 = ch.fanout("bunny.examples.ae.exchange2", :auto_delete => true, :durable => false, :arguments => { "alternate-exchange" => x1.name }) ``` ### Example ``` ruby #!/usr/bin/env ruby # encoding: utf-8 require "bunny" puts "=> Using alternate exchanges" puts conn = Bunny.new conn.start ch = conn.create_channel x1 = ch.fanout("bunny.examples.ae.exchange1", :auto_delete => true, :durable => false) x2 = ch.fanout("bunny.examples.ae.exchange2", :auto_delete => true, :durable => false, :arguments => { "alternate-exchange" => x1.name }) q = ch.queue("", :exclusive => true) q.bind(x1) x2.publish("") sleep 0.2 puts "Queue #{q.name} now has #{q.message_count} message in it" sleep 0.7 puts "Disconnecting..." conn.close ``` ### Learn More See also rabbitmq.com section on [Alternate Exchanges](http://www.rabbitmq.com/ae.html) ## Exchange-To-Exchange Bindings RabbitMQ supports [exchange-to-exchange bindings](http://www.rabbitmq.com/e2e.html) to allow even richer routing topologies as well as a backbone for some other features (e.g. tracing). ### How To Use It With Bunny 0.9+ Bunny 0.9 exposes it via `Bunny::Exchange#bind` which is semantically the same as `Bunny::Queue#bind` but binds two exchanges: ``` ruby # x2 will be the source x1.bind(x2, :routing_key => "americas.north.us.ca.*") ``` ### Example ``` ruby #!/usr/bin/env ruby # encoding: utf-8 require "bunny" puts "=> Using exchange-to-exchange bindings" puts conn = Bunny.new conn.start ch = conn.create_channel x1 = ch.fanout("bunny.examples.e2e.exchange1", :auto_delete => true, :durable => false) x2 = ch.fanout("bunny.examples.e2e.exchange2", :auto_delete => true, :durable => false) # x1 will be the source x2.bind(x1) q = ch.queue("", :exclusive => true) q.bind(x2) x1.publish("") sleep 0.2 puts "Queue #{q.name} now has #{q.message_count} message in it" sleep 0.7 puts "Disconnecting..." conn.close ``` ### Learn More See also rabbitmq.com section on [Exchange-to-Exchange Bindings](http://www.rabbitmq.com/e2e.html) ## Consumer Cancellation Notifications ### How To Use It With Bunny 0.9+ In order to use consumer cancellation notifications, you need to use consumer objects (documented in the [Queues and Consumers guide](/articles/queues.html)). When a consumer is cancelled, the `#handle_cancellation` method will be called on it. To register a consumer that is an object and not just message handler block, use `Bunny::Queue#subscribe_with` instead of `Bunny::Queue#subscribe`: ``` ruby ch = conn.create_channel module Bunny module Examples class ExampleConsumer < Bunny::Consumer def cancelled? @cancelled end def handle_cancellation(basic_cancel) puts "#{@consumer_tag} was cancelled" @cancelled = true end end end end q = ch.queue("", :exclusive => true) c = Bunny::Examples::ExampleConsumer.new(ch, q) q.subscribe_with(c) ``` ### Example ``` ruby #!/usr/bin/env ruby # encoding: utf-8 require "bunny" puts "=> Using consumer cancellation" puts conn = Bunny.new conn.start ch = conn.create_channel module Bunny module Examples class ExampleConsumer < Bunny::Consumer def cancelled? @cancelled end def handle_cancellation(basic_cancel) puts "#{@consumer_tag} was cancelled" @cancelled = true end end end end q = ch.queue("", :exclusive => true) c = Bunny::Examples::ExampleConsumer.new(ch, q) q.subscribe_with(c) sleep 0.1 q.delete sleep 0.1 puts "Disconnecting..." conn.close ``` ### Learn More See also rabbitmq.com section on [Consumer Cancellation Notifications](http://www.rabbitmq.com/consumer-cancel.html) ## Queue Leases Queue Leases is a RabbitMQ feature that lets you set for how long a queue is allowed to be *unused*. After that moment, it will be deleted. *Unused* here means that the queue * has no consumers * is not redeclared * no message fetches happened (using `basic.get` AMQP 0.9.1 method, that is, `Bunny::Queue#pop` in Bunny) ### How To Use It With Bunny 0.9+ Use the `"x-expires"` optional queue argument to set how long the queue will be allowed to be unused in milliseconds. After that time, the queue will be removed by RabbitMQ. ``` ruby ch.queue("", :exclusive => true, :arguments => {"x-expires" => 300}) ``` ### Example ``` ruby #!/usr/bin/env ruby # encoding: utf-8 require "bunny" puts "=> Demonstrating queue TTL (queue leases)" puts conn = Bunny.new conn.start ch = conn.create_channel q = ch.queue("", :exclusive => true, :arguments => {"x-expires" => 300}) sleep 0.4 begin # this will raise because the queue is already deleted q.message_count rescue Bunny::NotFound => nfe puts "Got a 404 response: the queue has already been removed" end sleep 0.7 puts "Closing..." conn.close ``` ### Learn More See also rabbitmq.com section on [Queue Leases](http://www.rabbitmq.com/ttl.html#queue-ttl) ## Per-Message Time-to-Live A TTL can be specified on a per-message basis, by setting the `:expiration` property when publishing. ### How To Use It With Bunny 0.9+ `Bunny::Exchange#publish` recognizes the `:expiration` option that is message time-to-live (TTL) in milliseconds: ``` ruby # 1 second x.publish("", :expiration => 1000) # 5 minutes x.publish("", :expiration => (5 * 60 * 1000)) ``` ### Example ``` ruby #!/usr/bin/env ruby # encoding: utf-8 require "bunny" puts "=> Using per-message TTL" puts conn = Bunny.new conn.start ch = conn.create_channel x = ch.fanout("amq.fanout") q = ch.queue("", :exclusive => true).bind(x) 10.times do |i| x.publish("Message #{i}", :expiration => 1000) end sleep 0.7 _, _, content1 = q.pop puts "Fetched #{content1.inspect} after 0.7 second" sleep 0.8 _, _, content2 = q.pop msg = if content2 content2.inspect else "nothing" end puts "Fetched #{msg} after 1.5 second" sleep 0.7 puts "Closing..." conn.close ``` ### Learn More See also rabbitmq.com section on [Per-message TTL](http://www.rabbitmq.com/ttl.html#per-message-ttl) ## Sender-Selected Distribution Generally, the RabbitMQ model assumes that the broker will do the routing work. At times, however, it is useful for routing to happen in the publisher application. Sender-Selected Routing is a RabbitMQ feature that lets clients have extra control over routing. The values associated with the `"CC"` and `"BCC"` header keys will be added to the routing key if they are present. If neither of those headers is present, this extension has no effect. ### How To Use It With Bunny 0.9+ To use sender-selected distribution, set the `"CC"` and `"BCC"` headers like you would any other header: ``` ruby x.publish("Message #{i}", :routing_key => "one", :headers => {"CC" => ["two", "three"]}) ``` ### Example ``` ruby #!/usr/bin/env ruby # encoding: utf-8 require "bunny" puts "=> Using sender-selected distribution" puts conn = Bunny.new conn.start ch = conn.create_channel x = ch.direct("bunny.examples.ssd.exchange") q1 = ch.queue("", :exclusive => true).bind(x, :routing_key => "one") q2 = ch.queue("", :exclusive => true).bind(x, :routing_key => "two") q3 = ch.queue("", :exclusive => true).bind(x, :routing_key => "three") q4 = ch.queue("", :exclusive => true).bind(x, :routing_key => "four") 10.times do |i| x.publish("Message #{i}", :routing_key => "one", :headers => {"CC" => ["two", "three"]}) end sleep 0.2 puts "Queue #{q1.name} now has #{q1.message_count} messages in it" puts "Queue #{q2.name} now has #{q2.message_count} messages in it" puts "Queue #{q3.name} now has #{q3.message_count} messages in it" puts "Queue #{q4.name} now has #{q4.message_count} messages in it" sleep 0.7 puts "Closing..." conn.close ``` ### Learn More See also rabbitmq.com section on [Sender-Selected Distribution](http://www.rabbitmq.com/sender-selected.html) ## Dead Letter Exchange (DLX) The x-dead-letter-exchange argument to queue.declare controls the exchange to which messages from that queue are 'dead-lettered'. A message is dead-lettered when any of the following events occur: The message is rejected (basic.reject or basic.nack) with requeue=false; or the TTL for the message expires. ### How To Use It With Bunny 0.9+ Dead-letter Exchange is a feature that is used by specifying additional queue arguments: * `"x-dead-letter-exchange"` specifies the exchange that dead lettered messages should be published to by RabbitMQ * `"x-dead-letter-routing-key"` specifies the routing key that should be used (has to be a constant value) ``` ruby dlx = ch.fanout("bunny.examples.dlx.exchange") q = ch.queue("", :exclusive => true, :arguments => {"x-dead-letter-exchange" => dlx.name}).bind(x) ``` ### Example ``` ruby #!/usr/bin/env ruby # encoding: utf-8 require "bunny" puts "=> Using dead letter exchange" puts conn = Bunny.new conn.start ch = conn.create_channel x = ch.fanout("amq.fanout") dlx = ch.fanout("bunny.examples.dlx.exchange") q = ch.queue("", :exclusive => true, :arguments => {"x-dead-letter-exchange" => dlx.name}).bind(x) # dead letter queue dlq = ch.queue("", :exclusive => true).bind(dlx) x.publish("") sleep 0.2 delivery_info, _, _ = q.pop(:manual_ack => true) puts "#{dlq.message_count} messages dead lettered so far" puts "Rejecting a message" ch.nack(delivery_info.delivery_tag, false) sleep 0.2 puts "#{dlq.message_count} messages dead lettered so far" dlx.delete puts "Disconnecting..." conn.close ``` ### Learn More See also rabbitmq.com section on [Dead Letter Exchange](http://www.rabbitmq.com/dlx.html) ## Wrapping Up RabbitMQ provides a number of useful extensions to the AMQP 0.9.1 specification. Bunny 0.9 and later releases have RabbitMQ extensions support built into the core. Some features are based on optional arguments for queues, exchanges or messages, and some are Bunny public API features. Any future argument-based extensions are likely to be useful with Bunny immediately, without any library modifications. ## What to Read Next The documentation is organized as [a number of guides](/articles/guides.html), covering various topics. We recommend that you read the following guides first, if possible, in this order: * [Durability and Related Matters](/articles/durability.html) * [Error Handling and Recovery](/articles/error_handling.html) * [Troubleshooting](/articles/troubleshooting.html) * [Using TLS (SSL) Connections](/articles/tls.html) ## Tell Us What You Think! Please take a moment to tell us what you think about this guide [on Twitter](http://twitter.com/rubyamqp) or the [Bunny mailing list](https://groups.google.com/forum/#!forum/ruby-amqp) Let us know what was unclear or what has not been covered. Maybe you do not like the guide style or grammar or discover spelling mistakes. Reader feedback is key to making the documentation better. ruby-amqp-bunny-b6569cd/docs/guides/getting_started.md000066400000000000000000000463731464043542000231440ustar00rootroot00000000000000--- title: "Getting Started with Ruby and RabbitMQ with Bunny" layout: article --- ## About this guide This guide is a quick tutorial that helps you to get started with RabbitMQ and [Bunny](http://github.com/ruby-amqp/bunny). It should take about 20 minutes to read and study the provided code examples. This guide covers: * Installing RabbitMQ, a mature multi-protocol messaging broker. * Installing Bunny via [Rubygems](http://rubygems.org) and [Bundler](http://gembundler.com). * Running a "Hello, world" messaging example that is a demonstration of 1:1 communication between a [publisher](https://www.rabbitmq.com/publishers.html) and a [consumer](https://www.rabbitmq.com/consumers.html) * Creating a "Twitter-like" publish/subscribe example with one publisher and four subscribers that demonstrates 1:n communication. * Creating a topic routing example with two publishers and eight subscribers showcasing n:m communication when subscribers only receive messages that they are interested in. This work is licensed under a Creative Commons Attribution 3.0 Unported License (including images and stylesheets). The source is available [on GitHub](https://github.com/ruby-amqp/rubybunny.info). ## Which versions of Bunny does this guide cover? This guide covers Bunny 2.11.0 and later versions. ## Installing RabbitMQ The [RabbitMQ site](http://rabbitmq.com) has a good [installation guide](https://www.rabbitmq.com/download.html) that addresses many operating systems. On MacOS, the fastest way to install RabbitMQ is with [Homebrew](http://mxcl.github.com/homebrew/): brew install rabbitmq then run it (after ensuring that `/usr/local/sbin` is in your `$PATH`): rabbitmq-server On Debian and Ubuntu, you can either [download the RabbitMQ .deb package](http://www.rabbitmq.com/server.html) and install it with [dpkg](http://www.debian.org/doc/FAQ/ch-pkgtools.en.html) or use of the [apt repository](http://www.rabbitmq.com/debian.html#apt_) that the RabbitMQ team provides. For RPM-based distributions like RedHat or CentOS, the RabbitMQ team provides an [RPM package](http://www.rabbitmq.com/install.html#rpm). ## Installing Bunny ### Make sure that you have Ruby and [Rubygems](http://docs.rubygems.org/read/chapter/3) installed This guide assumes that you have installed one of the following supported Ruby implementations: * Ruby v2.6 * Ruby v2.5 * Ruby v2.4 Bunny works sufficiently well on JRuby but there are known JRuby 1.7 bugs that can cause high CPU burn and other issues. JRuby users should use [March Hare](http://rubymarchhare.info). ### You can use Rubygems to install Bunny gem install bunny ### Adding Bunny as a dependency with Bundler ``` ruby source "https://rubygems.org" gem "bunny", ">= 2.14.3" ``` ### Verifying your installation Verify your installation with a quick irb session: ``` irb -rubygems :001 > require "bunny" => true :002 > Bunny::VERSION => "2.14.3" ``` ## "Hello, world" example Let us begin with the classic "Hello, world" example. First, here is the code: ``` ruby #!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" STDOUT.sync = true conn = Bunny.new conn.start ch = conn.create_channel q = ch.queue("bunny.examples.hello_world", :auto_delete => true) x = ch.default_exchange q.subscribe do |delivery_info, metadata, payload| puts "Received #{payload}" end x.publish("Hello!", :routing_key => q.name) sleep 1.0 conn.close ``` This example demonstrates a very common communication scenario: *application A* wants to publish a message that will end up in a queue that *application B* listens on. In this case, the queue name is `"bunny.examples.hello_world"`. Let us go through the code step by step: ``` ruby require "rubygems" require "bunny" ``` is the simplest way to load Bunny if you have installed it with RubyGems, but remember that you can omit the rubygems line if your environment does not need it. The following piece of code ``` ruby conn = Bunny.new conn.start ``` connects to RabbitMQ running on localhost, with the default port (`5672`), username (`"guest"`), password (`"guest"`) and virtual host (`"/"`). The next line ``` ruby ch = conn.create_channel ``` opens a new _channel_. AMQP 0.9.1 is a multi-channeled protocol that uses channels to multiplex a TCP connection. Channels are opened on a connection. `Bunny::Session#create_channel` will return only when Bunny receives a confirmation that the channel is open from RabbitMQ. This line ``` ruby q = ch.queue("bunny.examples.hello_world", :auto_delete => true) ``` declares a **queue** on the channel that we have just opened. Consumer applications get messages from queues. We declared this queue with the "auto-delete" parameter. Basically, this means that the queue will be deleted when there are no more processes consuming messages from it. The next line ``` ruby x = ch.default_exchange ``` instantiates an **exchange**. Exchanges receive messages that are sent by producers. Exchanges route messages to queues according to rules called **bindings**. In this particular example, there are no explicitly defined bindings. The exchange that we use is known as the **default exchange** and it has implied bindings to all queues. Before we get into that, let us see how we define a handler for incoming messages ``` ruby q.subscribe do |delivery_info, metadata, payload| puts "Received #{payload}" end ``` `Bunny::Queue#subscribe` takes a block that will be called every time a message arrives. This will happen in a thread pool, so `Bunny::Queue#subscribe` does not block the thread that invokes it. Finally, we publish our message ``` ruby x.publish("Hello!", :routing_key => q.name) ``` Routing key is one of the **message properties**. The default exchange will route the message to a queue that has the same name as the message's routing key. This is how our message ends up in the "bunny.examples.hello_world" queue. This diagram demonstrates the "Hello, world" example data flow: ![Hello, World AMQP example data flow](http://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/001_hello_world_example_routing.png) For the sake of simplicity, both the message producer (publisher) and the consumer are running in the same Ruby process. Now let us move on to a little bit more sophisticated example. ## Blabblr: one-to-many publish/subscribe (pubsub) example The previous example demonstrated how a connection to a broker is made and how to do 1:1 communication using the default exchange. Now let us take a look at another common scenario: broadcast, or multiple consumers and one producer. A very well-known broadcast example is Twitter: every time a person tweets, followers receive a notification. Blabbr, our imaginary information network, models this scenario: every network member has a separate queue and publishes blabs to a separate exchange. Three Blabbr members, Joe, Aaron and Bob, follow the official NBA account on Blabbr to get updates about what is happening in the world of basketball. Here is the code: ``` ruby require "rubygems" require "bunny" STDOUT.sync = true conn = Bunny.new("amqp://guest:guest@localhost:5672") conn.start ch = conn.create_channel x = ch.fanout("nba.scores") ch.queue("joe", :auto_delete => true).bind(x).subscribe do |delivery_info, metadata, payload| puts "#{payload} => joe" end ch.queue("aaron", :auto_delete => true).bind(x).subscribe do |delivery_info, metadata, payload| puts "#{payload} => aaron" end ch.queue("bob", :auto_delete => true).bind(x).subscribe do |delivery_info, metadata, payload| puts "#{payload} => bob" end x.publish("BOS 101, NYK 89").publish("ORL 85, ALT 88") conn.close ``` Unlike the "Hello, world" example above, here we use a connection URI instead of the default arguments. In this example, opening a channel is no different to opening a channel in the previous example, however, the exchange is declared differently: ``` ruby x = ch.fanout("nba.scores") ``` The exchange that we declare above using `Bunny::Channel#fanout` is a **fanout exchange**. A fanout exchange delivers messages to all of the queues that are bound to it: exactly what we want in the case of Blabbr! This piece of code ``` ruby ch.queue("joe", :auto_delete => true).bind(x).subscribe do |delivery_info, metadata, payload| puts "#{payload} => joe" end ``` is similar to the subscription code that we used for message delivery previously, but what does that `Bunny::Queue#bind` method do? It sets up a binding between the queue and the exchange that you pass to it. We need to do this to make sure that our fanout exchange routes messages to the queues of any subscribed followers. ``` ruby x.publish("BOS 101, NYK 89").publish("ORL 85, ALT 88") ``` demonstrates `Bunny::Exchange#publish` call chaining. Blabbr members use a fanout exchange for publishing, so there is no need to specify a message routing key because every queue that is bound to the exchange will get its own copy of all messages, regardless of the queue name and routing key used. A diagram for Blabbr looks like this: ![Blabbr Data Flow](https://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/002_blabbr_example_routing.png) Blabbr is pretty unlikely to secure hundreds of millions of dollars in funding, but it does a pretty good job of demonstrating how one can use RabbitMQ fanout exchanges to do broadcasting. ## Weathr: many-to-many topic routing example So far, we have seen point-to-point communication and broadcasting. Those two communication styles are possible with many protocols, for instance, HTTP handles these scenarios just fine. You may ask "what differentiates RabbitMQ?" Well, next we are going to introduce you to **topic exchanges** and routing with patterns, one of the features that makes RabbitMQ very powerful. Our third example involves weather condition updates. What makes it different from the previous two examples is that not all of the consumers are interested in all of the messages. People who live in Portland usually do not care about the weather in Hong Kong (unless they are visiting soon). They are much more interested in weather conditions around Portland, possibly all of Oregon and sometimes a few neighbouring states. Our example features multiple consumer applications monitoring updates for different regions. Some are interested in updates for a specific city, others for a specific state and so on, all the way up to continents. Updates may overlap so that an update for San Diego, CA appears as an update for California, but also should show up on the North America updates list. Here is the code: ``` ruby #!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" STDOUT.sync = true connection = Bunny.new connection.start channel = connection.create_channel # topic exchange name can be any string exchange = channel.topic("weathr", :auto_delete => true) # Subscribers. channel.queue("", :exclusive => true).bind(exchange, :routing_key => "americas.north.#").subscribe do |delivery_info, metadata, payload| puts "An update for North America: #{payload}, routing key is #{delivery_info.routing_key}" end channel.queue("americas.south").bind(exchange, :routing_key => "americas.south.#").subscribe do |delivery_info, metadata, payload| puts "An update for South America: #{payload}, routing key is #{delivery_info.routing_key}" end channel.queue("us.california").bind(exchange, :routing_key => "americas.north.us.ca.*").subscribe do |delivery_info, metadata, payload| puts "An update for US/California: #{payload}, routing key is #{delivery_info.routing_key}" end channel.queue("us.tx.austin").bind(exchange, :routing_key => "#.tx.austin").subscribe do |delivery_info, metadata, payload| puts "An update for Austin, TX: #{payload}, routing key is #{delivery_info.routing_key}" end channel.queue("it.rome").bind(exchange, :routing_key => "europe.italy.rome").subscribe do |delivery_info, metadata, payload| puts "An update for Rome, Italy: #{payload}, routing key is #{delivery_info.routing_key}" end channel.queue("asia.hk").bind(exchange, :routing_key => "asia.southeast.hk.#").subscribe do |delivery_info, metadata, payload| puts "An update for Hong Kong: #{payload}, routing key is #{delivery_info.routing_key}" end exchange.publish("San Diego update", :routing_key => "americas.north.us.ca.sandiego"). publish("Berkeley update", :routing_key => "americas.north.us.ca.berkeley"). publish("San Francisco update", :routing_key => "americas.north.us.ca.sanfrancisco"). publish("New York update", :routing_key => "americas.north.us.ny.newyork"). publish("São Paulo update", :routing_key => "americas.south.brazil.saopaulo"). publish("Hong Kong update", :routing_key => "asia.southeast.hk.hongkong"). publish("Kyoto update", :routing_key => "asia.southeast.japan.kyoto"). publish("Shanghai update", :routing_key => "asia.southeast.prc.shanghai"). publish("Rome update", :routing_key => "europe.italy.rome"). publish("Paris update", :routing_key => "europe.france.paris") sleep 1.0 connection.close ``` The first line that is different from the Blabbr example is ``` ruby exchange = channel.topic("weathr", :auto_delete => true) ``` We use a topic exchange here. Topic exchanges are used for [multicast](http://en.wikipedia.org/wiki/Multicast) messaging where consumers indicate which topics they are interested in (think of it as subscribing to a feed for an individual tag in your favourite blog as opposed to the full feed). Routing with a topic exchange is done by specifying a **routing pattern** on binding, for example: ``` ruby channel.queue("americas.south").bind(exchange, :routing_key => "americas.south.#").subscribe do |delivery_info, metadata, payload| puts "An update for South America: #{payload}, routing key is #{delivery_info.routing_key}" end ``` Here we bind a queue with the name of "americas.south" to the topic exchange declared earlier using the `Bunny::Queue#bind` method. This means that only messages with a routing key matching "americas.south.#" will be routed to that queue. A routing pattern consists of several words separated by dots, in a similar way to URI path segments joined by slashes. Here are a few examples: * asia.southeast.thailand.bangkok * sports.basketball * usa.nasdaq.aapl * tasks.search.indexing.accounts Now let us take a look at a few routing keys that match the `"americas.south.#"` pattern: * `"americas.south"` * `"americas.south.*brazil*"` * `"americas.south.*brazil.saopaulo*"` * `"americas.south.*chile.santiago*"` In other words, the `"#"` part of the pattern matches 0 or more words. For a pattern like `"americas.south.*"`, some matching routing keys would be: * `"americas.south.*brazil*"` * `"americas.south.*chile*"` * `"americas.south.*peru*"` but not * `"americas.south"` * `"americas.south.chile.santiago"` so `"*"` only matches a single word. The AMQP 0.9.1 specification says that topic segments (words) may contain the letters A-Z and a-z and digits 0-9. A (very simplistic) diagram to demonstrate topic exchange in action: ![Weathr Data Flow](https://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/003_weathr_example_routing.png) As in the previous examples, the block that we pass to `Bunny::Queue#subscribe` takes multiple arguments: **delivery information**, **message metadata** (properties) and **message body** (often called the **payload**). Long story short, the metadata parameter lets you access metadata associated with the message. Some examples of message metadata attributes are: * message content type * message content encoding * message priority * message expiration time * message identifier * reply to (specifies which message this is a reply to) * application id (identifier of the application that produced the message) and so on. As the following binding demonstrates, `"#"` and `"*"` can also appear at the beginning of routing patterns: ``` ruby channel.queue("us.tx.austin").bind(exchange, :routing_key => "#.tx.austin").subscribe do |delivery_info, metadata, payload| puts "An update for Austin, TX: #{payload}, routing key is #{delivery_info.routing_key}" end ``` For this example the publishing of messages is no different from that of previous examples. If we were to run the program, a message published with a routing key of `"americas.north.us.ca.berkeley"` would be routed to 2 queues: `"us.california"` and the **server-named queue** that we declared by passing a blank string as the name: ``` ruby channel.queue("", :exclusive => true).bind(exchange, :routing_key => "americas.north.#").subscribe do |delivery_info, metadata, payload| puts "An update for North America: #{payload}, routing key is #{delivery_info.routing_key}" end ``` The name of the server-named queue is generated by the broker and sent back to the client with a queue declaration confirmation. ## Wrapping up This is the end of the tutorial. Congratulations! You have learned quite a bit about both AMQP 0.9.1 and Bunny. This is only the tip of the iceberg. RabbitMQ has many more features to offer: * Reliable delivery of messages * Message confirmations (a way to tell broker that a message was or was not processed successfully) * Message redelivery when consumer applications fail or crash * Load balancing of messages between multiple consumers * Message metadata attributes * High Availability features and so on. Other guides explain these features in depth, as well as use cases for them. To stay up to date with Bunny development, [follow @rubyamqp on Twitter](http://twitter.com/rubyamqp) and [join our mailing list](http://groups.google.com/group/ruby-amqp). ## What to read next Documentation is organized as a number of documentation guides, covering all kinds of topics including use cases for various exchange types, fault-tolerant message processing with acknowledgements and error handling. We recommend that you read the following guides next, if possible, in this order: * [AMQP 0.9.1 Model Explained](http://www.rabbitmq.com/tutorials/amqp-concepts.html). A 2 page long introduction to the AMQP 0-9-1 Model concepts and features. Understanding the AMQP 0.9.1 Model will make a lot of other documentation, both for Bunny and RabbitMQ itself, easier to follow. With this guide, you don't have to waste hours of time reading the whole specification. * [Connecting to the broker](/articles/connecting.html). This guide explains how to connect to an RabbitMQ and how to integrate Bunny into standalone and Web applications. * [Queues and Consumers](/articles/queues.html). This guide focuses on features that consumer applications use heavily. * [Exchanges and Publishers](/articles/exchanges.html). This guide focuses on features that producer applications use heavily. * [Error Handling and Recovery](/articles/error_handling.html). This guide explains how to handle protocol errors, network failures and other things that may go wrong in real world projects. * [Concurrency Considerations](/articles/concurrency.html) ## Tell Us What You Think! Please take a moment to tell us what you think about this guide [on Twitter](http://twitter.com/rubyamqp) or the [Bunny mailing list](https://groups.google.com/forum/#!forum/ruby-amqp) Let us know what was unclear or what has not been covered. Maybe you do not like the guide style or grammar or discover spelling mistakes. Reader feedback is key to making the documentation better. ruby-amqp-bunny-b6569cd/docs/guides/guides.md000066400000000000000000000075701464043542000212310ustar00rootroot00000000000000--- title: "Bunny: all documentation guides" layout: article --- ## Guide list [Bunny documentation](https://github.com/ruby-amqp/rubybunny.info) is organized as a number of guides, covering all kinds of topics. We recommend that you read these guides, if possible, in this order: ### [Getting started](/articles/getting_started.html) An overview of Bunny with a quick tutorial that helps you to get started with it. It should take about 20 minutes to read and study the provided code examples. ### [AMQP 0.9.1 Model Concepts](http://www.rabbitmq.com/tutorials/amqp-concepts.html) This guide covers: * AMQP 0.9.1 model overview * What are channels * What are vhosts * What are queues * What are exchanges * What are bindings * What are AMQP 0.9.1 classes and methods ### [Connecting To RabbitMQ](/articles/connecting.html) This guide covers: * How to connect to RabbitMQ with Bunny 0.9+ * How to use connection URI to connect to RabbitMQ (also: in PaaS environments such as Heroku and CloudFoundry) * How to open a channel * How to close a channel * How to disconnect ### [Queues and Consumers](/articles/queues.html) This guide covers: * How to declare AMQP queues with Bunny * Queue properties * How to declare server-named queues * How to declare temporary exclusive queues * How to consume messages ("push API") * How to fetch messages ("pull API") * Message and delivery properties * Message acknowledgements * How to purge queues * How to delete queues * Other topics related to queues ### [Exchanges and Publishing](/articles/exchanges.html) This guide covers: * Exchange types * How to declare AMQP exchanges with Bunny * How to publish messages * Exchange properties * Fanout exchanges * Direct exchanges * Topic exchanges * Default exchange * Message and delivery properties * Message routing * Bindings * How to delete exchanges * Other topics related to exchanges and publishing ### [Bindings](/articles/bindings.html) This guide covers: * How to bind exchanges to queues * How to unbind exchanges from queues * Other topics related to bindings ### [Durability and Related Matters](/articles/durability.html) This guide covers: * Topics related to durability of exchanges and queues * Durability of messages ### [Concurrency Considerations](/articles/concurrency.html) This guide covers: * Concurrency in Bunny * Correctness and concurrency safety of key public API classes and methods * Other topics related to concurrency ### [RabbitMQ Extensions to AMQP 0.9.1](/articles/extensions.html) This guide covers [RabbitMQ extensions](http://www.rabbitmq.com/extensions.html) and how they are used in Bunny: * How to use Publishing Confirms with Bunny * How to use exchange-to-exchange bindings * How to the alternate exchange extension * How to set per-queue message TTL * How to set per-message TTL * What are consumer cancellation notifications and how to use them * Message *dead lettering* and the dead letter exchange * How to use sender-selected routing (`CC` and `BCC` headers) ### [Error Handling and Recovery](/articles/error_handling.html) This guide covers: * AMQP 0.9.1 protocol exceptions * How to deal with network failures * Other things that may go wrong ### [Using TLS (SSL) Connections](/articles/tls.html) This guide covers: * How to use TLS (SSL) connections to RabbitMQ with Bunny ### [Troubleshooting](/articles/troubleshooting.html) This guide covers: * What to check when your apps that use Bunny and RabbitMQ misbehave ## Tell Us What You Think! Please take a moment to tell us what you think about this guide on Twitter or the [ruby-amqp mailing list](https://groups.google.com/forum/?fromgroups#!forum/ruby-amqp) Let us know what was unclear or what has not been covered. Maybe you do not like the guide style or grammar or discover spelling mistakes. Reader feedback is key to making the documentation better. ruby-amqp-bunny-b6569cd/docs/guides/queues.md000066400000000000000000001172151464043542000212560ustar00rootroot00000000000000--- title: "Working with RabbitMQ queues and consumers from Ruby with Bunny" layout: article --- ## About this guide This guide covers everything related to queues in the AMQP 0.9.1 specification, common usage scenarios and how to accomplish typical operations using Bunny. This work is licensed under a Creative Commons Attribution 3.0 Unported License (including images and stylesheets). The source is available [on GitHub](https://github.com/ruby-amqp/rubybunny.info). ## What version of Bunny does this guide cover? This guide covers Bunny 2.11.0 and later versions. ## Queues in AMQP 0.9.1: Overview ### What are AMQP Queues? *Queues* store and forward messages to consumers. They are similar to mailboxes in SMTP. Messages flow from producing applications to [exchanges](/articles/exchanges.html) that route them to queues and finally, queues deliver the messages to consumer applications (or consumer applications fetch messages as needed). **Note** that unlike some other messaging protocols/systems, messages are not delivered directly to queues. They are delivered to exchanges that route messages to queues using rules known as *bindings*. AMQP 0.9.1 is a programmable protocol, so queues and bindings alike are declared by applications. ### Concept of Bindings A *binding* is an association between a queue and an exchange. Queues must be bound to at least one exchange in order to receive messages from publishers. Learn more about bindings in the [Bindings guide](/articles/bindings.html). ### Queue Attributes Queues have several attributes associated with them: * Name * Exclusivity * Durability * Whether the queue is auto-deleted when no longer used * Other metadata (sometimes called *X-arguments*) These attributes define how queues can be used, their life-cycle, and other aspects of queue behavior. ## Queue Names and Declaring Queues Every AMQP queue has a name that identifies it. Queue names often contain several segments separated by a dot ".", in a similar fashion to URI path segments being separated by a slash "/", although almost any string can represent a segment (with some limitations - see below). Before a queue can be used, it has to be *declared*. Declaring a queue will cause it to be created if it does not already exist. The declaration will have no effect if the queue does already exist and its attributes are the *same as those in the declaration*. When the existing queue attributes are not the same as those in the declaration a channel-level exception is raised. This case is explained later in this guide. ### Explicitly Named Queues Applications may pick queue names or ask the broker to generate a name for them. To declare a queue with a particular name, for example, "images.resize", use the `Bunny::Channel#queue` method: ``` ruby ch.queue("images.resize", :exclusive => false, :auto_delete => true) ``` The same example in context: ``` ruby require "bunny" conn = Bunny.new conn.start ch = conn.create_channel q = ch.queue("images.resize", :exclusive => false, :auto_delete => true) ``` ### Server-named queues To ask an AMQP broker to generate a unique queue name for you, pass an *empty string* as the queue name argument. A generated queue name (like *amq.gen-JZ46KgZEOZWg-pAScMhhig*) will be assigned to the `Bunny::Queue` instance that the method returns: ``` ruby ch.queue("", :exclusive => true) ``` The same example in context: ``` ruby require "bunny" conn = Bunny.new conn.start ch = conn.create_channel q = ch.queue("", :exclusive => true) ``` **Note** that, while it is common to declare server-named queues as `:exclusive`, it is not necessary. ### Reserved Queue Name Prefix Queue names starting with "amq." are reserved for server-named queues and queues for internal use by the broker. Attempts to declare a queue with a name that violates this rule will result in a channel-level exception with reply code `403 (ACCESS_REFUSED)` and a reply message similar to this: ACCESS_REFUSED - queue name 'amq.queue' contains reserved prefix 'amq.*' This error results in the channel that was used for the declaration being forcibly closed by RabbitMQ. If the program subsequently tries to communicate with RabbitMQ using the same channel without re-opening it then Bunny will raise a `Bunny::ChannelAlreadyClosed` error. ### Queue Re-Declaration With Different Attributes When queue declaration attributes are different from those that the queue already has, a channel-level exception with code `406 (PRECONDITION_FAILED)` will be raised. The reply text will be similar to this: PRECONDITION_FAILED - parameters for queue 'bunny.examples.channel_exception' in vhost '/' not equivalent This error results in the channel that was used for the declaration being forcibly closed by RabbitMQ. If the program subsequently tries to communicate with RabbitMQ using the same channel without re-opening it then Bunny will raise a `Bunny::ChannelAlreadyClosed` error. In order to continue communications in the same program after such an error, a different channel would have to be used. ## Queue Life-cycle Patterns According to the AMQP 0.9.1 specification, there are two common message queue life-cycle patterns: * Durable queues that are shared by many consumers and have an independent existence: i.e. they will continue to exist and collect messages whether or not there are consumers to receive them. * Temporary queues that are private to one consumer and are tied to that consumer. When the consumer disconnects, the message queue is deleted. There are some variations of these, such as shared message queues that are deleted when the last of many consumers disconnects. Let us examine the example of a well-known service like an event collector (event logger). A logger is usually up and running regardless of the existence of services that want to log anything at a particular point in time. Other applications know which queues to use in order to communicate with the logger and can rely on those queues being available and able to survive broker restarts. In this case, explicitly named durable queues are optimal and the coupling that is created between applications is not an issue. Another example of a well-known long-lived service is a distributed metadata/directory/locking server like [Apache Zookeeper](http://zookeeper.apache.org), [Google's Chubby](https://research.google.com/archive/chubby.html) or DNS. Services like this benefit from using well-known, not server-generated, queue names and so do any other applications that use them. A different sort of scenario is in "a cloud setting" when some kind of worker/instance might start and stop at any time so that other applications cannot rely on it being available. In this case, it is possible to use well-known queue names, but a much better solution is to use server-generated, short-lived queues that are bound to topic or fanout exchanges in order to receive relevant messages. Imagine a service that processes an endless stream of events — Twitter is one example. When traffic increases, development operations may start additional application instances in the cloud to handle the load. Those new instances want to subscribe to receive messages to process, but the rest of the system does not know anything about them and cannot rely on them being online or try to address them directly. The new instances process events from a shared stream and are the same as their peers. In a case like this, there is no reason for message consumers not to use queue names generated by the broker. In general, use of explicitly named or server-named queues depends on the messaging pattern that your application needs. [Enterprise Integration Patterns](http://www.eaipatterns.com/) discusses many messaging patterns in depth and the RabbitMQ FAQ also has a section on [use cases](http://www.rabbitmq.com/faq.html#scenarios). ## Declaring a Durable Shared Queue To declare a durable shared queue, you pass a queue name that is a non-blank string and use the `:durable` option: ``` ruby ch.queue("images.resize", :durable => true, :auto_delete => false) ``` The same example in context: ``` ruby require "bunny" conn = Bunny.new conn.start ch = conn.create_channel q = ch.queue("images.resize", :durable => true, :auto_delete => false) ``` ## Declaring a Temporary Exclusive Queue To declare a server-named, exclusive, auto-deleted queue, pass "" (an empty string) as the queue name and use the `:exclusive` option: ``` ruby ch.queue("", :exclusive => true) ``` The same example in context: ``` ruby require "bunny" conn = Bunny.new conn.start ch = conn.create_channel q = ch.queue("", :exclusive => true) ``` Exclusive queues may only be accessed by the current connection and are deleted when that connection closes. The declaration of an exclusive queue by other connections is not allowed and will result in a channel-level exception with the code `405 (RESOURCE_LOCKED)` Exclusive queues will be deleted when the connection they were declared on is closed. ## Checking if a Queue Exists Sometimes it's convenient to check if a queue exists. To do so, at the protocol level you use `queue.declare` with `passive` seto to `true`. In response RabbitMQ responds with a channel exception if the queue does not exist. Bunny provides a convenience method, `Bunny::Session#queue_exists?`, to do this: ``` ruby conn = Bunny.new conn.start conn.queue_exists?("logs.info") ``` ## Binding Queues to Exchanges In order to receive messages, a queue needs to be bound to at least one exchange. Most of the time binding is explcit (done by applications). **Please note:** All queues are automatically bound to the default unnamed RabbitMQ direct exchange with a routing key that is the same as the queue name (see [Exchanges and Publishing](/articles/exchanges.html) guide for more details). To bind a queue to an exchange, use the `Bunny::Queue#bind` method: ``` ruby q = ch.queue("", :exclusive => true) x = ch.fanout("logging.events") q.bind(x) ``` The same example in context: ``` ruby require "bunny" conn = Bunny.new conn.start ch = conn.create_channel q = ch.queue("", :exclusive => true) x = ch.fanout("logging.events") q.bind(x) ``` ## Subscribing to receive messages ("push API") To request that the server starts a *consumer* (queue subscription) to enable an application to process messages as they arrive in a queue, one uses the `Bunny::Queue#subscribe` or `Bunny::Queue#subscribe_with` methods. Consumers last as long as the channel that they were declared on, or until the client cancels them (unsubscribes). Consumers have a number of events that they can react to: * Message delivery * Consumer registration confirmation * Consumer cancellation #### Consumer Tags Consumers are identified by unique strings called *consumer tags*. The `Bunny::Queue#subscribe` method can take a `:consumer_tag` argument or let RabbitMQ generate one ```ruby q.subscribe(:consumer_tag => "unique_consumer_001") ``` ### Handling Messages With a Block A message handler will process messages that RabbitMQ pushes to the consumer. One way to define a handler is: ``` ruby q = ch.queue("", :exclusive => true) q.subscribe(:manual_ack => true) do |delivery_info, properties, payload| puts "Received #{payload}, message properties are #{properties.inspect}" end ``` The same example in context: ``` ruby require "bunny" conn = Bunny.new conn.start ch = conn.create_channel q = ch.queue("", :exclusive => true) q.subscribe(:manual_ack => true) do |delivery_info, properties, payload| puts "Received #{payload}, message properties are #{properties.inspect}" end ``` The block should accept three arguments: * Delivery information (can be used to acknowledge messages, for example; will be covered later) * Message properties (metadata) * Message payload (body) Both delivery information and message properties can be treated as Hash-like objects or structures. For example, to get delivery tag, you can use either ``` ruby delivery_info[:delivery_tag] ``` or ``` ruby delivery_info.delivery_tag ``` #### Asynchronous Consumers Don't Run on the Caller Thread The subscribe method will not use the caller thread. If invoked from the main thread, it will not keep that thread running. That's a responsibility of application developer. It usually can be worked around with something like ``` ruby loop { sleep 5 } ``` ### Accessing Message Delivery Information The *delivery_info* parameter in the example above provides access to message delivery information: * Consumer tag this delivery is for * Delivery tag * Whether or not message is redelivered * Name of exchange message came from * Message routing key Message delivery information can be treated as a Hash-like object or structure. For example, to get routing key, you can use either ``` ruby delivery_info[:routing_key] ``` or ``` ruby delivery_info.routing_key ``` ### Accessing Message Properties (Metadata) The *properties* parameter in the example above provides access to message metadata: * Message content type * Message content encoding * Message delivery mode (persistent or not) * Message priority * Producer application id Message properties can be treated as a Hash-like object or structure. For example, to get message type, you can use either ``` ruby properties[:type] ``` or ``` ruby properties.type ``` An example to demonstrate how to access some of those attributes: ``` ruby require 'bunny' connection = Bunny.new connection.start ch = connection.create_channel q = ch.queue('', :exclusive => true) x = ch.default_exchange # set up the consumer q.subscribe(:exclusive => true, :manual_ack => false) do |delivery_info, properties, payload| puts properties.content_type # => "application/octet-stream" puts properties.priority # => 8 puts properties.headers["time"] # => a Time instance puts properties.headers["coordinates"]["latitude"] # => 59.35 puts properties.headers["participants"] # => 11 puts properties.headers["venue"] # => "Stockholm" puts properties.headers["true_field"] # => true puts properties.headers["false_field"] # => false puts properties.headers["nil_field"] # => nil puts properties.headers["ary_field"].inspect # => ["one", 2.0, 3, [{ "abc" => 123}]] puts properties.timestamp # => a Time instance puts properties.type # => "kinda.checkin" puts properties.reply_to # => "a.sender" puts properties.correlation_id # => "r-1" puts properties.message_id # => "m-1" puts properties.app_id # => "bunny.example" puts delivery_info.consumer_tag # => a string puts delivery_info.redelivered? # => false puts delivery_info.delivery_tag # => 1 puts delivery_info.routing_key # => server generated queue name prefixed with "amq.gen-" puts delivery_info.exchange # => "" end # publishing x.publish("hello", :routing_key => "#{q.name}", :app_id => "bunny.example", :priority => 8, :type => "kinda.checkin", # headers table keys can be anything :headers => { :coordinates => { :latitude => 59.35, :longitude => 18.066667 }, :time => Time.now, :participants => 11, :venue => "Stockholm", :true_field => true, :false_field => false, :nil_field => nil, :ary_field => ["one", 2.0, 3, [{"abc" => 123}]] }, :timestamp => Time.now.to_i, :reply_to => "a.sender", :correlation_id => "r-1", :message_id => "m-1") sleep 1.0 connection.close ``` The full list of message delivery information parameters is: * `:consumer_tag` * `:delivery_tag` * `:redelivered` * `:exchange` * `:routing_key` The full list of message properties parameters (**note** that most of them are optional and may not be present) is: * `:content_type` _(always present)_ * `:content_encoding` * `:headers` * `:delivery_mode` _(always present)_ * `:priority` _(always present)_ * `:correlation_id` * `:reply_to` * `:expiration` * `:message_id` * `:timestamp` * `:type` * `:user_id` * `:app_id` * `:cluster_id` ### Consumer Instances Starting with version 0.9, Bunny provides a new `Bunny::Consumer` class which takes the following positional arguments when instantiated: * `channel` _(mandatory)_ * `queue` _(mandatory)_ * `consumer_tag` _(default = "")_ * `no_ack` _(default = true)_ * `exclusive` _(default = false)_ * `arguments` _(default = {})_ To create a consumer object: ```ruby class ExampleConsumer < Bunny::Consumer def cancelled? @cancelled end def handle_cancellation(_) @cancelled = true end end connection = Bunny.new connection.start ch = connection.create_channel q = ch.queue("testq") consumer = ExampleConsumer.new(ch, q, "my_example_consumer", false, false, {:test_arg => 'test'}) ``` or ```ruby consumer = ExampleConsumer.new(ch, q) ``` If the `consumer_tag` is empty then Bunny will generate one that looks something like *bunny-1357204208000-17043847598*, but it can also be set in the code: ```ruby consumer.consumer_tag = "another_example_consumer" ``` `Bunny::Consumer` implements a *delivery handler* and when the consumer consumes a message then the delivery information, message properties (metadata) and body (payload) are passed to it. In order to process consumed messages a block is passed to the consumer: ```ruby consumer.on_delivery do |delivery_info, metadata, payload| puts payload end ``` Consumers may need to react to events other than message delivery. For example, consumers can be cancelled by RabbitMQ in some situations: * When a consumer is cancelled via the RabbitMQ Management UI * When the queue from which messages are consumed is deleted To handle these *consumer cancellation notification* events, consumers have a *cancellation handler* (see the `handle_cancellation` method in the example below). ### Registering Consumer Instances To register a consumer and start consuming messages, pass a consumer object to the `Bunny::Queue#subscribe_with` method. Here is a full example: ```ruby require 'bunny' # Define consumer subclass class ExampleConsumer < Bunny::Consumer def cancelled? @cancelled end def handle_cancellation(_) @cancelled = true end end connection = Bunny.new connection.start consumer = nil ch1 = connection.create_channel t = Thread.new do ch2 = connection.create_channel q = ch2.queue("testq") consumer = ExampleConsumer.new(ch2, q) # Pass block to consumer delivery handler consumer.on_delivery() do |delivery_info, metadata, payload| puts payload end # Register the consumer q.subscribe_with(consumer) end t.abort_on_exception = true sleep 0.5 x = ch1.default_exchange # Publish messages x.publish('Hello', :routing_key => "testq") x.publish('World', :routing_key => "testq") sleep 0.5 # Delete the queue triggering the consumer cancellation handler ch1.queue("testq").delete sleep 0.5 puts 'Consumer has been cancelled' if consumer.cancelled? sleep 2 connection.close ``` ### Using Multiple Consumers Per Queue It is possible to have multiple non-exclusive consumers on queues. In that case, messages will be distributed between them according to prefetch levels of their channels (more on this later in this guide). If prefetch values are equal for all consumers, each consumer will get about the same number of messages. ### Consumer Priorities As of RabbitMQ 3.2, [consumers that share a channel can have priorities](http://www.rabbitmq.com/consumer-priority.html). To specify a priority with Bunny, use the `:arguments` option that `Bunny::Queue#subscribe` and `Bunny::Queue#subscribe_with` take: ``` ruby q = ch.queue("a.queue") q.subscribe(:manual_ack => true, :arguments => {"x-priority" => 5}) do |delivery_info, properties, payload| # ... end q.subscribe(:manual_ack => true, :arguments => {"x-priority" => 2}) do |delivery_info, properties, payload| # ... end ``` ### Exception Handling in Consumers Consumers are expected to handle any exceptions that arise during handling of deliveries or any other consumer operations. Such exceptions should be logged, collected and ignored. Unhandled exceptions will kill consumer dispatch pool threads and eventually lead to Bunny being unable to dispatch/process deliveries. If a consumer cannot process deliveries due to a dependency not being available or similar reasons it should clearly log so and cancel itself until it is capable of processing deliveries again. This will make the consumer's unavailability visible to RabbitMQ and [monitoring systems](https://rabbitmq.com/monitoring.html). ### Exclusive Consumers Consumers can request exclusive access to the queue (meaning only this consumer can access the queue). This is useful when you want a long-lived shared queue to be temporarily accessible by just one application (or thread, or process). If the application employing the exclusive consumer crashes or loses the TCP connection to the broker, then the channel is closed and the exclusive consumer is cancelled. To exclusively receive messages from the queue, pass the `:exclusive` option to `Bunny::Queue#subscribe`: ``` ruby q = ch.queue("") q.subscribe(:manual_ack => true, :exclusive => true) do |delivery_info, properties, payload| # ... end ``` or the positional `exclusive` parameter to your `Bunny::Consumer` subclass: ``` ruby class ExampleConsumer < Bunny::Consumer def cancelled? @cancelled end def handle_cancellation(_) @cancelled = true end end # channel, queue, consumer tag, no_ack, exclusive consumer = ExampleConsumer.new(ch, q, "", false, true) q.subscribe_with(consumer) ``` Attempts to register another consumer on a queue that already has an exclusive consumer will result in a channel-level exception with reply code `403 (ACCESS_REFUSED)` and a reply message similar to this: ACCESS_REFUSED - queue 'queue name' in vhost '/' in exclusive use (Bunny::AccessRefused) It is not possible to register an exclusive consumer on a queue that already has consumers. ### Cancelling a Consumer Sometimes there may be a requirement to cancel a consumer directly without deleting the queue that it is subscribed to. In AMQP 0.9.1 parlance, "cancelling a consumer" is often referred to as "unsubscribing". The `Bunny::Consumer#cancel` method can be used to do this. Here is a usage example : ``` ruby require 'bunny' connection = Bunny.new connection.start ch = connection.create_channel q = ch.queue("", :auto_delete => true, :durable => false) consumer = q.subscribe(:block => false) do |_, _, payload| puts payload end puts "Consumer: #{consumer.consumer_tag} created" sleep 1 # Cancel consumer cancel_ok = consumer.cancel puts "Consumer: #{cancel_ok.consumer_tag} cancelled" ch.close ``` In the above example, you can see that the `Bunny::Consumer#cancel` method returns a *cancel_ok* reply from RabbitMQ which contains the consumer tag of the cancelled consumer. Once a consumer is cancelled, messages will no longer be delivered to it, however, due to the asynchronous nature of the protocol, it is possible for "in flight" messages to be received after this call completes. ### Message Acknowledgements Consumer applications — applications that receive and process messages ‚ may occasionally fail to process individual messages, or will just crash. There is also the possibility of network issues causing problems. This raises a question — "When should the AMQP broker remove messages from queues?" The AMQP 0.9.1 specification proposes two choices: * After broker sends a message to an application (using either basic.deliver or basic.get-ok methods). * After the application sends back an acknowledgement (using basic.ack AMQP method). The former choice is called the *automatic acknowledgement model*, while the latter is called the *explicit acknowledgement model*. With the explicit model, the application chooses when it is time to send an acknowledgement. It can be right after receiving a message, or after persisting it to a data store before processing, or after fully processing the message (for example, successfully fetching a Web page, processing and storing it into some persistent data store). ![Message Acknowledgements](https://github.com/ruby-amqp/amqp/raw/master/docs/diagrams/006_amqp_091_message_acknowledgements.png) If a consumer dies without sending an acknowledgement, the AMQP broker will redeliver it to another consumer, or, if none are available at the time, the broker will wait until at least one consumer is registered for the same queue before attempting redelivery. The acknowledgement model is chosen when a new consumer is registered for a queue. By default, `Bunny::Queue#subscribe` will use the *automatic* model. To switch to the *explicit* model, the `:manual_ack` option should be used: ``` ruby q = ch.queue("", :exclusive => true).subscribe(:manual_ack => true) do |delivery_info, properties, payload| # ... end ``` To demonstrate how redelivery works, let us have a look at the following code example: ``` ruby require "bunny" puts "=> Subscribing for messages using explicit acknowledgements model" puts connection1 = Bunny.new connection1.start connection2 = Bunny.new connection2.start connection3 = Bunny.new connection3.start ch1 = connection1.create_channel ch2 = connection2.create_channel ch3 = connection3.create_channel x = ch3.direct("amq.direct") q1 = ch1.queue("bunny.examples.acknowledgements.explicit", :auto_delete => false) q1.purge q1.bind(x).subscribe(:manual_ack => true, :block => false) do |delivery_info, properties, payload| # do some work sleep(0.2) # acknowledge some messages, they will be removed from the queue if rand > 0.5 # FYI: there is a shortcut, Bunny::Channel.ack ch1.acknowledge(delivery_info.delivery_tag, false) puts "[consumer1] Got message ##{properties.headers['i']}, redelivered?: #{delivery_info.redelivered?}, ack-ed" else # some messages are not ack-ed and will remain in the queue for redelivery # when app #1 connection is closed (either properly or due to a crash) puts "[consumer1] Got message ##{properties.headers['i']}, SKIPPED" end end q2 = ch2.queue("bunny.examples.acknowledgements.explicit", :auto_delete => false) q2.bind(x).subscribe(:manual_ack => true, :block => false) do |delivery_info, properties, payload| # do some work sleep(0.2) ch2.acknowledge(delivery_info.delivery_tag, false) puts "[consumer2] Got message ##{properties.headers['i']}, redelivered?: #{delivery_info.redelivered?}, ack-ed" end t1 = Thread.new do i = 0 loop do sleep 0.5 x.publish("Message ##{i}", :headers => { :i => i }) i += 1 end end t1.abort_on_exception = true t2 = Thread.new do sleep 4.0 connection1.close puts "----- Connection 1 is now closed (we pretend that it has crashed) -----" end t2.abort_on_exception = true sleep 10.0 connection2.close connection3.close ``` So what is going on here? This example uses three AMQP connections to imitate three applications, one producer and two consumers. Each connection opens a single channel. The consumers share a queue and the producer publishes messages to the queue periodically using an `amq.direct` exchange. Both "applications" subscribe to receive messages using the explicit acknowledgement model. The RabbitMQ broker by default will send each message to the next consumer in sequence (this kind of load balancing is known as *round-robin*). This means that some messages will be delivered to consumer #1 and some to consumer #2. To demonstrate message redelivery we make consumer #1 randomly select which messages to acknowledge. After 4 seconds we disconnect it (to imitate a crash). When that happens, the RabbitMQ broker redelivers unacknowledged messages to consumer #2 which acknowledges them unconditionally. After 10 seconds, this example closes all outstanding connections and exits. An extract of output produced by this example: ``` => Subscribing for messages using explicit acknowledgements model [consumer1] Got message #0, redelivered?: false, ack-ed [consumer2] Got message #1, redelivered?: false, ack-ed [consumer1] Got message #2, redelivered?: false, ack-ed [consumer2] Got message #3, redelivered?: false, ack-ed [consumer1] Got message #4, SKIPPED [consumer2] Got message #5, redelivered?: false, ack-ed [consumer1] Got message #6, SKIPPED ----- Connection 1 is now closed (we pretend that it has crashed) ----- [consumer2] Got message #4, redelivered?: true, ack-ed [consumer2] Got message #6, redelivered?: true, ack-ed [consumer2] Got message #7, redelivered?: false, ack-ed [consumer2] Got message #8, redelivered?: false, ack-ed [consumer2] Got message #9, redelivered?: false, ack-ed [consumer2] Got message #10, redelivered?: false, ack-ed [consumer2] Got message #11, redelivered?: false, ack-ed [consumer2] Got message #12, redelivered?: false, ack-ed ``` As we can see, consumer #1 did not acknowledge two messages (labelled 4 and 6): ``` [consumer1] Got message #4, SKIPPED [consumer1] Got message #6, SKIPPED ... ``` and then, once consumer #1 had "crashed", the messages were immediately redelivered to the consumer #2: ``` ----- Connection 1 is now closed (we pretend that it has crashed) ----- [consumer2] Got message #4, redelivered?: true, ack-ed [consumer2] Got message #6, redelivered?: true, ack-ed ``` To acknowledge a message use `Bunny::Channel#acknowledge`: ``` # FYI: there is a shortcut, Bunny::Channel.ack ch1.acknowledge(delivery_info.delivery_tag, false) ``` `Bunny::Channel#acknowledge` takes two arguments: a message *delivery tag* and a flag that indicates whether or not we want to acknowledge multiple messages at once. Delivery tag is simply a channel-specific increasing number that the server uses to identify deliveries. When acknowledging multiple messages at once, the delivery tag is treated as "up to and including". For example, if delivery tag = 5 that would mean "acknowledge messages 1, 2, 3, 4 and 5". **Please note:** Acknowledgements are channel-specific. Applications MUST NOT receive messages on one channel and acknowledge them on another. Also, a message MUST NOT be acknowledged more than once. Doing so will result in a channel-level exception with code `406 (PRECONDITION_FAILED)` being raised. The reply text will be similar to this: PRECONDITION_FAILED - unknown delivery tag ### Rejecting messages When a consumer application receives a message, processing of that message may or may not succeed. An application can indicate to the broker that message processing has failed (or cannot be accomplished at the time) by rejecting a message. When rejecting a message, an application can ask the broker to discard or requeue it. To reject a message use the `Bunny::Channel#reject` method: ``` ruby ch1.reject(delivery_info.delivery_tag) ``` in the example above, messages are rejected without requeueing (broker will simply discard them). To requeue a rejected message, use the second argument that `Bunny::Queue#reject` takes: ``` ruby ch1.reject(delivery_info.delivery_tag, true) ``` ### Negative acknowledgements Messages are rejected with the `basic.reject` AMQP method. However, there is one notable limitation that `basic.reject` has: there is no way to reject multiple messages, as you can do with acknowledgements. However, if you are using [RabbitMQ](http://rabbitmq.com), then there is a solution. RabbitMQ provides an AMQP 0.9.1 extension known as [negative acknowledgements](http://www.rabbitmq.com/extensions.html#negative-acknowledgements) (nacks) and Bunny supports this extension. For more information, please refer to the [RabbitMQ Extensions guide](/articles/extensions.html). ### QoS — Prefetching messages For cases when multiple consumers share a queue, it is useful to be able to specify how many messages each consumer can be sent at once before sending the next acknowledgement. This can be used as a sample load balancing technique to improve throughput if messages tend to be published in batches. For example, if a producing application sends messages every minute because of the nature of the work it is doing. Imagine a website that takes data from social media sources like Twitter or Facebook during the Champions League (European soccer) final (or The Super Bowl), and then calculates how many tweets mention a particular team in the last minute. The site could be structured as 3 applications: * A crawler that uses streaming APIs to fetch tweets/statuses, normalizes them and sends them in JSON for processing by other applications ("app A"). * A calculator that detects what team is mentioned in a message, updates statistics and pushes an update to the Web UI once a minute ("app B"). * A Web UI that fans visit to see the stats ("app C"). In this imaginary example, the "tweets per second" rate will vary, but to improve the throughput of the system and to decrease the maximum number of messages that the AMQP broker has to hold in memory at once, applications can be designed in such a way that application "app B", the "calculator", receives 5000 messages and then acknowledges them all at once. The broker will not send message 5001 unless it receives an acknowledgement. In AMQP 0.9.1 parlance this is known as *QoS* or *message prefetching*. Prefetching is configured on a per-channel basis. To configure prefetching use the `Bunny::Channel#prefetch` method like so: ``` ruby ch1 = connection1.create_channel ch1.prefetch(10) ``` **Note** that the prefetch setting is ignored for consumers that do not use explicit acknowledgements. ## How Message Acknowledgements Relate to Transactions and Publisher Confirms In cases where you cannot afford to lose a single message, AMQP 0.9.1 applications can use one or a combination of the following protocol features: * Publisher confirms (a RabbitMQ-specific extension to AMQP 0.9.1) * Publishing messages as immediate * Transactions (noticeable overhead) This topic is covered in depth in the [Working With Exchanges](/articles/exchanges.html) guide. In this guide, we will only mention how message acknowledgements are related to AMQP transactions and the Publisher Confirms extension. Let us consider a publisher application (P) that communications with a consumer (C) using AMQP 0.9.1. Their communication can be graphically represented like this:
-----       -----       -----
|   |   S1  |   |   S2  |   |
| P | ====> | B | ====> | C |
|   |       |   |       |   |
-----       -----       -----
We have two network segments, S1 and S2, either of which might fail. P is concerned with making sure that messages cross S1, while brokers B and C are concerned with ensuring that messages cross S2 and are only removed from the queue when they are processed successfully. Message acknowledgements cover reliable delivery over S2 as well as successful processing. For S1, P has to use transactions (a heavyweight solution) or the more lightweight Publisher Confirms RabbitMQ extension. ## Fetching messages when needed ("pull API") The AMQP 0.9.1 specification also provides a way for applications to fetch (pull) messages from the queue only when necessary. For that, use the `Bunny::Queue#pop` function which returns a triple of `[delivery_info, properties, payload]`: ``` ruby delivery_info, properties, payload = q.pop ``` The same example in context: ``` ruby require "bunny" conn = Bunny.new conn.start chann = conn.create_channel q = chann.queue("test1") exch = chann.default_exchange exch.publish("Hello, everybody!", :routing_key => 'test1') delivery_info, properties, payload = q.pop puts "This is the message: " + payload + "\n\n" conn.close ``` The message properties are the same as those provided for delivery handlers (see the "Push API" section above). If the queue is empty, then `[nil, nil, nil]` will be returned. ## Unbinding Queues From Exchanges To unbind a queue from an exchange use the `Bunny::Queue#unbind` function: ``` ruby q.unbind(x) ``` **Note** that trying to unbind a queue from an exchange that the queue was never bound to will result in a channel-level exception. ## Querying the Number of Messages and Consumers for a Queue It is possible to query the number of messages in a queue and the number of consumers it has by declaring the queue with the `:passive` attribute set. The response (`queue.declare-ok` AMQP method) will include the number of messages along with the number of consumers. However, Bunny provides a convenience method, `Bunny::Queue#status`, that returns a hash containing `:message_count` and `:consumer_count`. There are two further convenience methods that provide both pieces of information individually * `Bunny::Queue#message_count` * `Bunny::Queue#consumer_count` ``` ruby require "bunny" conn = Bunny.new conn.start ch = conn.channel q = ch.queue("testq") # Display message count puts q.message_count # Display consumer count puts q.consumer_count ``` **Please note:** The message count DOES NOT include unacknowledged messages. ## Purging queues It is possible to purge a queue (remove all of the messages from it) using the `Bunny::Queue#purge` method: ``` ruby require "bunny" conn = Bunny.new conn.start ch = conn.channel q = ch.queue("") q.purge ``` **Note** that this example purges a newly declared queue with a unique server-generated name. When a queue is declared, it is empty, so for server-named queues, there is no need to purge them before they are used. ## Deleting Queues Queues can be deleted either indirectly or directly. To delete a queue indirectly you can include either of the following two arguments in the queue declaration: * `:exclusive => true` * `:auto_delete => true` If the *exclusive* flag is set to true then the queue will be deleted when the connection that was used to declare it is closed. If the *auto_delete* flag is set to true then the queue will be deleted when there are no more consumers subscribed to it. The queue will remain in existence until at least one consumer accesses it. To delete a queue directly, use the `Bunny::Queue#delete` method: ``` ruby require "bunny" conn = Bunny.new conn.start ch = conn.channel q = ch.queue("") q.delete ``` When a queue is deleted, all of the messages in it are deleted as well. ## Queue Durability vs Message Durability See [Durability guide](/articles/durability.html) ## RabbitMQ Extensions Related to Queues See [RabbitMQ Extensions guide](/articles/extensions.html) ## Wrapping Up In RabbitMQ, queues can be client-named or server-named. It is possible to either subscribe for messages to be pushed to consumers (register a consumer) or pull messages from the client as needed. Consumers are identified by consumer tags. For messages to be routed to queues, queues need to be bound to exchanges. Most methods related to queues are found in three Bunny namespaces: * `Bunny::Channel` * `Bunny::Consumer` * `Bunny::Queue` ## What to Read Next The documentation is organized as [a number of guides](/articles/guides.html), covering various topics. We recommend that you read the following guides first, if possible, in this order: * [Exchanges and Publishing](/articles/exchanges.html) * [Bindings](/articles/bindings.html) * [RabbitMQ Extensions to AMQP 0.9.1](/articles/extensions.html) * [Durability and Related Matters](/articles/durability.html) * [Error Handling and Recovery](/articles/error_handling.html) * [Concurrency Considerations](/articles/concurrency.html) * [Troubleshooting](/articles/troubleshooting.html) * [Using TLS (SSL) Connections](/articles/tls.html) ## Tell Us What You Think! Please take a moment to tell us what you think about this guide [on Twitter](http://twitter.com/rubyamqp) or the [Bunny mailing list](https://groups.google.com/forum/#!forum/ruby-amqp) Let us know what was unclear or what has not been covered. Maybe you do not like the guide style or grammar or discover spelling mistakes. Reader feedback is key to making the documentation better. ruby-amqp-bunny-b6569cd/docs/guides/tls.md000066400000000000000000000321041464043542000205420ustar00rootroot00000000000000--- title: "TLS (SSL) connections to RabbitMQ from Ruby with Bunny" layout: article --- ## About This Guide This guide covers TLS (SSL) connections to RabbitMQ with Bunny. This work is licensed under a Creative Commons Attribution 3.0 Unported License (including images and stylesheets). The source is available [on GitHub](https://github.com/ruby-amqp/rubybunny.info). ## What version of Bunny does this guide cover? This guide covers Bunny 2.11.0 and later versions. ## TLS Support in RabbitMQ RabbitMQ version 3.x supports TLS/SSL on Erlang R16B03 or later. Using the most recent version (e.g. `17.1` or `17.1`) is recommended. To use TLS with RabbitMQ, you need a few things: * Server certificate and private key * [Configure RabbitMQ to use TLS](http://www.rabbitmq.com/ssl.html) * CA certificate that signs the server certificate * Client certificate and private (optional if peer verification is disabled) ## Generating Certificates For Development The easiest way to generate a CA, server and client keys and certificates is by using [tls-gen](https://github.com/ruby-amqp/tls-gen/). It requires `openssl` and `make` to be available. See [RabbitMQ TLS/SSL guide](http://www.rabbitmq.com/ssl.html) for more information about TLS support on various platforms. ## Enabling TLS/SSL Support in RabbitMQ TLS/SSL support is enabled using two arguments: * `ssl_listeners` (a list of ports TLS connections will use) * `ssl_options` (a proplist of options such as CA certificate file location, server key file location, and so on) An example that requires client certificate and performs client authentication: ``` erlang [ {rabbit, [ {ssl_listeners, [5671]}, {ssl_options, [{cacertfile,"/path/to/testca/cacert.pem"}, {certfile,"/path/to/server/cert.pem"}, {keyfile,"/path/to/server/key.pem"}, {verify,verify_peer}, {fail_if_no_peer_cert,true}]} ]} ]. ``` An example that requires no client certificate and performs no authentication (not recommended for production): ``` erlang [ {rabbit, [ {ssl_listeners, [5671]}, {ssl_options, [{cacertfile,"/path/to/testca/cacert.pem"}, {certfile,"/path/to/server/cert.pem"}, {keyfile,"/path/to/server/key.pem"}, {verify,verify_none}, {fail_if_no_peer_cert,false}]} ]} ]. ``` Note that all paths must be absolute (no `~` and other shell-isms) and be readable by the OS user RabbitMQ uses. Learn more in the [RabbitMQ TLS/SSL guide](http://www.rabbitmq.com/ssl.html). ## Connecting to RabbitMQ from Bunny Using TLS/SSL There are several options `Bunny.new` takes: * `:tls` which, when set to `true`, will set SSL context up and switch to TLS port (5671) * `:tls_cert` which is a string path to the client certificate (public key) in PEM format * `:tls_key` which is a string path to the client key (private key) in PEM format * `:tls_ca_certificates` which is an array of string paths to CA certificates in PEM format * `:verify_peer` which determines if TLS peer authentication (verification) is performed, `true` by default An example: ``` ruby conn = Bunny.new(:tls => true, :tls_cert => "examples/tls/client_cert.pem", :tls_key => "examples/tls/client_key.pem", :tls_ca_certificates => ["./examples/tls/cacert.pem"], # convenient for dev/QA, please enable in production :verify_peer => false) ``` If you configure RabbitMQ to accept TLS connections on a separate port, you need to specify both `:tls` and `:port` options: ``` ruby conn = Bunny.new(:tls => true, # custom port RabbitMQ TLS listener is configured to use :port => 6778, :tls_cert => "examples/tls/client_cert.pem", :tls_key => "examples/tls/client_key.pem", :tls_ca_certificates => ["./examples/tls/cacert.pem"], # convenient for dev/QA, please enable in production :verify_peer => false) ``` Paths can be relative but it's recommended to use absolute paths and no symlinks to avoid issues with path expansion. ### Inline Certificates and Keys In cases when reading files from the filesystem is not an option, it is possible to provide certificates and keys inline: ``` ruby cert = <<-EOS -----BEGIN CERTIFICATE----- MIIC8zCCAdugAwIBAgIBATANBgkqhkiG9w0BAQUFADAiMREwDwYDVQQDEwhNeVRl c3RDQTENMAsGA1UEBxMEMTgxNTAeFw0xMzA2MDIxNTU0MDBaFw0xNDA2MDIxNTU0 MDBaMCcxFDASBgNVBAMTC2dpb3ZlLmxvY2FsMQ8wDQYDVQQKEwZjbGllbnQwggEi MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDj70Z3cs873uTdONh/gK0vEI75 okTHOQBz9QPVUx0c+cdUvp1Ct1FXBYM4Jq47aaRW5vuki0SeML0gdrQouSVFtblX HnB8bYF1oMlkmTrIDvM9DT5H3AMiQbbypVkRjQBb/Rs97sr+P05jhK2ZWxTxzs3W kqdblJaxfMX7IXgvobnXDJO0PcN7tzOOlcD8dGFABLEtzWRmzqVvrJ7tZh0klsiB I2yuOjk9LZhNcgmSNUAln+MFkWiAQcwWvl77DSBVPqIi6w6Q0oJoS6gsT6jOfw3f ApX7Fjoib3UXLrZC2Fe7Sq03joZcpL7lMqsEZCZr0VqASQJHoTPqEknSMlpxAgMB AAGjLzAtMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUF BwMCMA0GCSqGSIb3DQEBBQUAA4IBAQCFpfTUD9CjkrlhJi8GrryRlBILah45HIH4 MuUEUaGE//glCTuKXHjUhgtFSkFaDr0Xq50ckUzMVdmsQpSZM3N1F/eTicIk1PzY b+7t86/XC5wct94I5yxPNX7S8VwHtK8yav0WwMwEGmduTxfjMPnJBDPdwIp6LgiF BqM4Hh8HxHdr+MxOg3JGiodM7MMsDs1A05RiBcR3RzMvbXn5eQIy7tHOJMnrdbj9 mOrfKAmRlWyNj3mhOVpae22sbtSxHYZ10b0Xp/KFusiZCfQvo4pERonjUoMLtaPE RtPrRrHy96dzmpVFnDVaA+CKZqyBncVAT0zQ3lIJdFIOEbE//s06 -----END CERTIFICATE----- EOS key = <<-EOS -----BEGIN RSA PRIVATE KEY----- MIIEowIBAAKCAQEA4+9Gd3LPO97k3TjYf4CtLxCO+aJExzkAc/UD1VMdHPnHVL6d QrdRVwWDOCauO2mkVub7pItEnjC9IHa0KLklRbW5Vx5wfG2BdaDJZJk6yA7zPQ0+ R9wDIkG28qVZEY0AW/0bPe7K/j9OY4StmVsU8c7N1pKnW5SWsXzF+yF4L6G51wyT tD3De7czjpXA/HRhQASxLc1kZs6lb6ye7WYdJJbIgSNsrjo5PS2YTXIJkjVAJZ/j BZFogEHMFr5e+w0gVT6iIusOkNKCaEuoLE+ozn8N3wKV+xY6Im91Fy62QthXu0qt N46GXKS+5TKrBGQma9FagEkCR6Ez6hJJ0jJacQIDAQABAoIBACfSlCMmYeJ57M5h siGEn71LTU979DxCTzvzILpSjRGU6ih6LQuM758ejXBwAZzLtjSgonJ7CoAAz+ou EwfSYRquxzTbUpfKogWlE8qJouV1BzYxbCIt5DZF+OqnzMnuMpPfwrStVbXZ5Z4p fhL/AMfGc9v7P1YWvcVAoW5gyJi5ejL4az82ZHqDltkkPBm7yXI1xaoAuAU+ir4X AArDQWqqD+lPVD8gtMfyRYek7xL/O2SZUAVNQC14Fi2gmh01FFW/gnPmoT7GutEL gfdEQ1KpyzquaSf1u/cka9jbdqf2fAhMj6UwasIJ1HF8dzblzO/nB+cTzIo9LzoK erwQs2ECgYEA/nTWap6M7InOeAkosEEeLcu0idnjxlT/OToRtfdNkKatvqAFpSDd 2IBzr3kH+qGToeF8B7uJBaWO69m28+yEngWNW1u0KICUTTzlKZqSqNy1nxWnCWVk Eg9LtEja4ncoWufbxBB6wwptk4RSqB9HUeZSQf8CG5MvDCLmEsMwwLUCgYEA5VFA FSZJ3X96nGHlrokq7yDNAQLVZ72B+X+SRt7b9FMVeTyT7fQCAjFSiZYR0Tuz8XEn STARp27K8OyFv0L1ZzHeywRcqICo9Eqa4Q/Juw+Waf3F40f1lxXb09OTHI/JedWz U+VMX/OgsFW4a/3/L+IatlnBKemTKvhd94E5VE0CgYEAsQtcMLz2cpIDrXM580Cr ndORXyTSnamAFzI3JnPWbSH725l9tAIlOUFOvLWqfpEzpju8T6kFUn957NIDwL49 G7HjQ8CPnmqwRPlsvUDGcGV4nSK0oQ4BzasE0oCqg03DL1UJjOamc9Rqn2w/EqkI t4xYiYDD16nV30zc5gsXfc0CgYBLlwvbrOJeXB4rnG2cqeR4LMTG14tHBgXpG285 Y07368dBToGox20+EcoWRlybLuXy6Yy8qFa5bWECJ8Uytby1BpBdNZPhi3+l/02s cIrb2ZiIWbm4YMkIw5DR84UjvhX4zkOtnQEfA+ztE2SWXISY4RxTDaUJzs/PM02u P2+JZQKBgFcOXsnCR/x1CQ6j90pqXjvAK6x/Aiwx0FFTtcPdDft/zuJzav1Co84u vUGvUADy1AVUB5ERz3z6us9gA4tUIeNwlQ0XFQXVT7I7GBXO3eF5PeiCXfThqnm9 dHgVP3fRaFosQv7mQe6BuuUHP3TJwT1qv/cWmiyyc1Xs7L2b4YU/ -----END RSA PRIVATE KEY----- EOS conn = Bunny.new(:tls => true, :tls_cert => cert, :tls_key => key, :tls_ca_certificates => ["./examples/tls/cacert.pem"], # convenient for dev/QA, please enable in production :verify_peer => false) ``` ### Providing Certificates & Keys When Using amqps:// URIs It is possible to use `amqps://` URIs in combination with additional options, e.g. to provide TLS certificate and key paths: ``` ruby c = Bunny.new("amqps://bunny_gem:bunny_password@127.0.0.1/bunny_testbed", :tls_cert => "spec/tls/client_cert.pem", :tls_key => "spec/tls/client_key.pem", :tls_ca_certificates => ["./spec/tls/cacert.pem"], # convenient for dev/QA, please enable in production :verify_peer => false) c.start ``` ### Peer Verification In some situations it is reasonable to disable peer verification (authentication), which is enabled by default. This means TLS will only be used for encryption and not authentication, enabling man-in-the-middle (MITM) attacks. This is a reasonable thing to do in development but **we highly recommend using peer verification in production environments**. To disable peer with Bunny, use `:verify_peer`: ``` ruby c = Bunny.new("amqps://bunny_gem:bunny_password@127.0.0.1/bunny_testbed", :tls_cert => "spec/tls/client_cert.pem", :tls_key => "spec/tls/client_key.pem", :tls_ca_certificates => ["./spec/tls/cacert.pem"], :verify_peer => false) c.start ``` When disabling peer verification, make sure RabbitMQ is also configured to not verify peer. In such case, it is possible to forego providing client certificate and private key: ``` ruby c = Bunny.new("amqps://bunny_gem:bunny_password@127.0.0.1/bunny_testbed", :tls_ca_certificates => ["./spec/tls/cacert.pem"], :verify_peer => false) c.start ``` Disabling peer verification is not recommended for production. ## Default Paths for TLS/SSL CA's ### CA Certificate Paths Inference Bunny will use CA certificate paths used by OpenSSL if OpenSSL can provide this information. When using self-signed certificates with a custom certificate authority, it is possible to place CA certificates to a system location. To detect the location, run the following code in the REPL after loading OpenSSL: ``` irb -ropenssl ``` ``` ruby ENV[OpenSSL::X509::DEFAULT_CERT_DIR_ENV] || OpenSSL::X509::DEFAULT_CERT_DIR # => "/usr/local/etc/openssl/certs" ``` ### On Linux Bunny will use the following TLS/SSL CA's paths on Linux by default: * `/etc/ssl/certs/ca-certificates.crt` on Ubuntu/Debian * `/etc/ssl/certs/ca-bundle.crt` on Amazon Linux * `/etc/ssl/ca-bundle.pem` on OpenSUSE * `/etc/pki/tls/certs/ca-bundle.crt` on Fedora/RHEL and will log a warning if no CA files are available via default paths or `:tls_ca_certificates`. ## TLS/SSL Versions Support Bunny will use TLSv1 through TLSv1.2 when available, and fall back to [insecure](https://www.openssl.org/~bodo/ssl-poodle.pdf) SSLv3 if that's the only version supported. Note that **RabbitMQ will reject SSLv3 connections** unless configured otherwise, starting with 3.4.0. ## Known TLS Vulnerabilities: POODLE, BEAST, etc ### POODLE [POODLE](https://www.openssl.org/~bodo/ssl-poodle.pdf) is a known SSL/TLS attack that originally compromised SSLv3. Starting with version 3.4.0, RabbitMQ server refuses to accept SSLv3 connections. In December 2014, a modified version of the POODLE attack that affects TLSv1.0 was [announced](https://www.imperialviolet.org/2014/12/08/poodleagain.html). It is therefore recommended to disable TLSv1.0 support (see below) when possible. ### BEAST [BEAST attack](http://en.wikipedia.org/wiki/Transport_Layer_Security#BEAST_attack) is a known vulnerability that affects TLSv1.0. To mitigate it, disable TLSv1.0 support (see below). ## Disabling SSL/TLS Versions via Configuration To limit enabled SSL/TLS protocol versions, use the `versions` option in RabbitMQ configuration: ``` %% Disable SSLv3.0 support, leaves TLSv1.0 enabled. [ {ssl, [{versions, ['tlsv1.2', 'tlsv1.1', tlsv1]}]}, {rabbit, [ {ssl_listeners, [5671]}, {ssl_options, [{cacertfile,"/path/to/ca_certificate.pem"}, {certfile, "/path/to/server_certificate.pem"}, {keyfile, "/path/to/server_key.pem"}, {versions, ['tlsv1.2', 'tlsv1.1', tlsv1]} ]} ]} ]. ``` ``` %% Disable SSLv3.0 and TLSv1.0 support. [ {ssl, [{versions, ['tlsv1.2', 'tlsv1.1']}]}, {rabbit, [ {ssl_listeners, [5671]}, {ssl_options, [{cacertfile,"/path/to/ca_certificate.pem"}, {certfile, "/path/to/server_certificate.pem"}, {keyfile, "/path/to/server_key.pem"}, {versions, ['tlsv1.2', 'tlsv1.1']} ]} ]} ]. ``` to verify, use `openssl s_client`: ``` # connect using SSLv3 openssl s_client -connect 127.0.0.1:5671 -ssl3 ``` ``` # connect using TLSv1.0 through v1.2 openssl s_client -connect 127.0.0.1:5671 -tls1 ``` and look for the following in the output: ``` SSL-Session: Protocol : TLSv1 ``` ## What to Read Next The documentation is organized as [a number of guides](/articles/guides.html), covering various topics. ## Tell Us What You Think! Please take a moment to tell us what you think about this guide [on Twitter](http://twitter.com/rubyamqp) or the [Bunny mailing list](https://groups.google.com/forum/#!forum/ruby-amqp) Let us know what was unclear or what has not been covered. Maybe you do not like the guide style or grammar or discover spelling mistakes. Reader feedback is key to making the documentation better. ruby-amqp-bunny-b6569cd/docs/guides/troubleshooting.md000066400000000000000000000120301464043542000231630ustar00rootroot00000000000000--- title: "Troubleshooting and debugging RabbitMQ applications" layout: article --- ## About this guide This guide describes tools and strategies that help in troubleshooting and debugging applications that use RabbitMQ in general and Bunny in particular. This work is licensed under a Creative Commons Attribution 3.0 Unported License (including images and stylesheets). The source is available [on GitHub](https://github.com/ruby-amqp/rubybunny.info). ## What version of Bunny does this guide cover? This guide covers Bunny 2.11.0 and later versions. ## First steps Whenever something doesn't work, check the following things before asking on the mailing list: * RabbitMQ log. * List of users in a particular vhost you are trying to connect. * Network connectivity, firewall settings, DNS host resolution. ## Inspecting RabbitMQ log file In this section we will cover typical problems that can be tracked down by reading RabbitMQ log. RabbitMQ logs abrupt TCP connection failures, timeouts, protocol version mismatches and so on. If you are running RabbitMQ, log file location depends on the operating systems and installation method. See [RabbitMQ installation guide](http://www.rabbitmq.com/install.html) for more information. ### OS X with Homebrew On Mac OS X, RabbitMQ installed via Homebrew logs to `$HOMEBREW_HOME/var/log/rabbitmq/rabbit@$HOSTNAME.log`. For example, if you have Homebrew installed at `/usr/local` and your hostname is `giove`, the log will be at `/usr/local/var/log/rabbitmq/rabbit@giove.log`. ### Authentication Failures Here is what authentication failure looks like in a RabbitMQ log: ``` =ERROR REPORT==== 12-Jul-2013::16:49:03 === closing AMQP connection <0.31567.1> (127.0.0.1:50458 -> 127.0.0.1:5672): {handshake_error,starting,0, {amqp_error,access_refused, "PLAIN login refused: user 'pipeline_agent' - invalid credentials", 'connection.start_ok'}} ``` This means that the connection attempt with the username `pipeline_agent` failed because the credentials were invalid. If you are seeing this message, make sure username, password *and vhost* are correct. The following entry: ``` =ERROR REPORT==== 17-May-2011::17:26:28 === exception on TCP connection <0.4201.62> from 10.8.0.30:57990 {bad_header,<<65,77,81,80,0,0,9,1>>} ``` means that an old RabbitMQ version (pre-`2.0`) is used. Those versions are not supported by Bunny 0.9+. It is recommended to use the [latest stable release](http://www.rabbitmq.com/download.html). Bunny will raise `Bunny::PossibleAuthenticationFailureError` in such cases. ## Handling Channel-level Exceptions A broad range of problems result in AMQP channel exceptions: an indication by the broker that there was an issue that the application needs to be aware of. Channel-level exceptions are typically not fatal and can be recovered from. Some examples are: * Exchange is re-declared with attributes different from the original declaration. For example, a non-durable exchange is being re-declared as durable. * Queue is re-declared with attributes different from the original declaration. For example, an auto-deletable queue is being re-declared as non-auto-deletable. * Queue is bound to an exchange that does not exist. and so on. These will result in a reasonably descriptive exception that subclasses `Bunny::ChannelLevelException`. Handling and logging them will likely reveal an issue when it arises. ## Network connection issues ### Testing Network Connection with RabbitMQ using Telnet One way to check network connection between a particular network node and a RabbitMQ node is to use `telnet`: ``` telnet [host or ip] 5672 ``` then enter any random string of text and hit Enter. RabbitMQ should immediately close down the connection. Here is an example session: ``` telnet localhost 5672 Connected to localhost. Escape character is '^]'. adjasd AMQP Connection closed by foreign host. ``` If Telnet exits after printing instead ``` telnet: connect to address [host or ip]: Connection refused telnet: Unable to connect to remote host ``` then the connection between the machine that you are running Telnet tests on and RabbitMQ fails. This can be due to many different reasons, but it is a good idea to check these two things first: * Firewall configuration for port 5672 or 5671 (if TLS/SSL is used) * DNS resolution (if hostname is used) ### Connecting to localhost on VPN Using VPN almost certainly changes your DNS server configuration which may affect connections to `localhost` as well as to remote hosts. If you keep getting `Got an exception when receiving data: IO timeout when reading 7 bytes (Timeout::Error)` errors and you're on VPN try switching VPN off. ## RabbitMQ Startup Issues ### Missing erlang-os-mon on Debian and Ubuntu The following error on RabbitMQ startup on Debian or Ubuntu ``` ERROR: failed to load application os_mon: {"no such file or directory","os_mon.app"} ``` suggests that the *erlang-os-mon* package is not installed. ruby-amqp-bunny-b6569cd/examples/000077500000000000000000000000001464043542000170245ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/examples/connection/000077500000000000000000000000001464043542000211635ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/examples/connection/authentication_failure.rb000066400000000000000000000005251464043542000262400ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "bundler" Bundler.setup $:.unshift(File.expand_path("../../../lib", __FILE__)) require 'bunny' begin conn = Bunny.new("amqp://guest8we78w7e8:guest2378278@127.0.0.1") conn.start rescue Bunny::PossibleAuthenticationFailureError => e puts "Could not authenticate as #{conn.username}" end ruby-amqp-bunny-b6569cd/examples/connection/automatic_recovery_with_basic_get.rb000066400000000000000000000014541464043542000304530ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "bundler" Bundler.setup $:.unshift(File.expand_path("../../../lib", __FILE__)) require 'bunny' conn = Bunny.new(:heartbeat_timeout => 8) conn.start ch = conn.create_channel x = ch.topic("bunny.examples.recovery.topic", :durable => false) q = ch.queue("bunny.examples.recovery.client_named_queue2", :durable => true) q.purge q.bind(x, :routing_key => "abc").bind(x, :routing_key => "def") loop do sleep 8 body = rand.to_s begin x.publish(body, :routing_key => ["abc", "def"].sample) puts "Published #{body}" # happens when a message is published before the connection # is recovered rescue Exception => e end sleep 1.5 _, _, payload = q.pop if payload puts "Consumed #{payload}" else puts "Consumed nothing" end end ruby-amqp-bunny-b6569cd/examples/connection/automatic_recovery_with_client_named_queues.rb000066400000000000000000000014341464043542000325420ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "bundler" Bundler.setup $:.unshift(File.expand_path("../../../lib", __FILE__)) require 'bunny' conn = Bunny.new(heartbeat_timeout: 8) conn.start ch = conn.create_channel x = ch.topic("bunny.examples.recovery.topic", :durable => false) q = ch.queue("bunny.examples.recovery.client_named_queue1", :durable => false) q.bind(x, :routing_key => "abc").bind(x, :routing_key => "def") q.subscribe do |delivery_info, metadata, payload| puts "Consumed #{payload}" end loop do sleep 2 data = rand.to_s rk = ["abc", "def"].sample begin x.publish(data, :routing_key => rk) puts "Published #{data}, routing key: #{rk}" # happens when a message is published before the connection # is recovered rescue Exception => e end end ruby-amqp-bunny-b6569cd/examples/connection/automatic_recovery_with_multiple_consumers.rb000066400000000000000000000022611464043542000324610ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "bundler" Bundler.setup $:.unshift(File.expand_path("../../../lib", __FILE__)) require 'bunny' conn = Bunny.new(heartbeat_timeout: 8) conn.start ch1 = conn.create_channel x1 = ch1.topic("bunny.examples.recovery.e1", :durable => false) q1 = ch1.queue("bunny.examples.recovery.q1", :durable => false) q1.bind(x1, :routing_key => "abc").bind(x1, :routing_key => "def") ch2 = conn.create_channel x2 = ch2.topic("bunny.examples.recovery.e2", :durable => false) q2 = ch2.queue("bunny.examples.recovery.q2", :durable => false) q2.bind(x2, :routing_key => "abc").bind(x2, :routing_key => "def") q1.subscribe do |delivery_info, metadata, payload| puts "Consumed #{payload} at stage one" x2.publish(payload, :routing_key => ["abc", "def", "xyz"].sample) end q2.subscribe do |delivery_info, metadata, payload| puts "Consumed #{payload} at stage two" end loop do sleep 2 rk = ["abc", "def", "ghi", "xyz"].sample puts "Publishing with routing key #{rk}" begin x1.publish(rand.to_s, :routing_key => rk) # happens when a message is published before the connection # is recovered rescue Bunny::ConnectionClosedError => e end end ruby-amqp-bunny-b6569cd/examples/connection/automatic_recovery_with_republishing.rb000066400000000000000000000055761464043542000312370ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "bundler" Bundler.setup $:.unshift(File.expand_path("../../../lib", __FILE__)) require 'Bunny' conn = Bunny.new(:heartbeat_timeout => 8) conn.start ch0 = conn.create_channel ch1 = conn.create_channel ch2 = conn.create_channel ch3 = conn.create_channel x = ch1.topic("hb.examples.recovery.topic", :durable => false) q1 = ch1.queue("hb.examples.recovery.client_named_queue1", :durable => false) q2 = ch2.queue("hb.examples.recovery.client_named_queue2", :durable => false) q3 = ch3.queue("hb.examples.recovery.client_named_queue3", :durable => false) q1.bind(x, :routing_key => "abc") q2.bind(x, :routing_key => "def") q3.bind(x, :routing_key => "xyz") x0 = ch0.fanout("hb.examples.recovery.fanout0") x1 = ch1.fanout("hb.examples.recovery.fanout1") x2 = ch2.fanout("hb.examples.recovery.fanout2") x3 = ch3.fanout("hb.examples.recovery.fanout3") q4 = ch1.queue("", :exclusive => true) q4.bind(x0) q5 = ch2.queue("", :exclusive => true) q5.bind(x1) q6 = ch3.queue("", :exclusive => true) q6.bind(x2) q6.bind(x3) q1.subscribe do |delivery_info, metadata, payload| puts "[Q1] Consumed #{payload} on channel #{q1.channel.id}" if ch0.open? puts "Publishing a reply on channel #{ch0.id} which is open" x0.publish(Bunny::Timestamp.now.to_i.to_s) end end q2.subscribe do |delivery_info, metadata, payload| puts "[Q2] Consumed #{payload} on channel #{q2.channel.id}" if ch1.open? puts "Publishing a reply on channel #{ch1.id} which is open" x1.publish(Bunny::Timestamp.now.to_i.to_s) end end q3.subscribe do |delivery_info, metadata, payload| puts "[Q3] Consumed #{payload} (consumer 1, channel #{q3.channel.id})" if ch2.open? puts "Publishing a reply on channel #{ch1.id} which is open" x2.publish(Bunny::Timestamp.now.to_i.to_s) end end q3.subscribe do |delivery_info, metadata, payload| puts "[Q3] Consumed #{payload} (consumer 2, channel #{q3.channel.id})" if ch3.open? puts "Publishing a reply on channel #{ch3.id} which is open" x3.publish(Bunny::Timestamp.now.to_i.to_s) end end q4.subscribe do |delivery_info, metadata, payload| puts "[Q4] Consumed #{payload} on channel #{q4.channel.id}" end q5.subscribe do |delivery_info, metadata, payload| puts "[Q5] Consumed #{payload} on channel #{q5.channel.id}" end q6.subscribe do |delivery_info, metadata, payload| puts "[Q6] Consumed #{payload} on channel #{q6.channel.id}" end loop do sleep 1 data = rand.to_s rk = ["abc", "def", "xyz", Bunny::Timestamp.now.to_i.to_s].sample begin 3.times do x.publish(rand.to_s, :routing_key => rk) puts "Published #{data}, routing key: #{rk} on channel #{x.channel.id}" end # happens when a message is published before the connection # is recovered rescue Exception => e puts "Exception: #{e.message}" # e.backtrace.each do |line| # puts "\t#{line}" # end end end ruby-amqp-bunny-b6569cd/examples/connection/automatic_recovery_with_server_named_queues.rb000066400000000000000000000013301464043542000325650ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "bundler" Bundler.setup $:.unshift(File.expand_path("../../../lib", __FILE__)) require 'bunny' conn = Bunny.new(heartbeat_timeout: 8) conn.start ch = conn.create_channel x = ch.topic("bunny.examples.recovery.topic", :durable => false) q = ch.queue("", :durable => false) q.bind(x, :routing_key => "abc").bind(x, :routing_key => "def") q.subscribe do |delivery_info, metadata, payload| puts "Consumed #{payload}" end loop do sleep 2 data = rand.to_s rk = ["abc", "def"].sample begin x.publish(rand.to_s, :routing_key => rk) # happens when a message is published before the connection # is recovered rescue Bunny::ConnectionClosedError => e end end ruby-amqp-bunny-b6569cd/examples/connection/channel_level_exception.rb000066400000000000000000000011161464043542000263640ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "bundler" Bundler.setup $:.unshift(File.expand_path("../../../lib", __FILE__)) require 'bunny' conn = Bunny.new(heartbeat_timeout: 8) conn.start begin ch2 = conn.create_channel q = "bunny.examples.recovery.q#{rand}" ch2.queue_declare(q, :durable => false) ch2.queue_declare(q, :durable => true) rescue Bunny::PreconditionFailed => e puts "Channel-level exception! Code: #{e.channel_close.reply_code}, message: #{e.channel_close.reply_text}" ensure conn.create_channel.queue_delete(q) end puts "Disconnecting..." conn.close ruby-amqp-bunny-b6569cd/examples/connection/disabled_automatic_recovery.rb000066400000000000000000000013171464043542000272450ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "bundler" Bundler.setup $:.unshift(File.expand_path("../../../lib", __FILE__)) require 'bunny' conn = Bunny.new(heartbeat_timeout: 8, automatically_recover: false) conn.start ch = conn.create_channel x = ch.topic("bunny.examples.recovery.topic", :durable => false) q = ch.queue("bunny.examples.recovery.client_named_queue2", :durable => true) q.purge q.bind(x, :routing_key => "abc").bind(x, :routing_key => "def") loop do sleep 1.5 body = rand.to_s puts "Published #{body}" x.publish(body, :routing_key => ["abc", "def"].sample) sleep 1.5 _, _, payload = q.pop if payload puts "Consumed #{payload}" else puts "Consumed nothing" end end ruby-amqp-bunny-b6569cd/examples/connection/heartbeat.rb000066400000000000000000000003521464043542000234470ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "bundler" Bundler.setup $:.unshift(File.expand_path("../../../lib", __FILE__)) require 'bunny' conn = Bunny.new(:heartbeat_timeout => 2) conn.start c = conn.create_channel sleep 10 ruby-amqp-bunny-b6569cd/examples/connection/manually_reconnecting_consumer.rb000066400000000000000000000006761464043542000300140ustar00rootroot00000000000000#!/usr/bin/env ruby require 'bunny' Bundler.setup begin connection = Bunny.new(:automatically_recover => false) connection.start ch = connection.channel q = ch.queue("manually_reconnecting_consumer", :exclusive => true) q.subscribe(:block => true) do |_, _, payload| puts "Consumed #{payload}" end rescue Bunny::NetworkFailure => e ch.maybe_kill_consumer_work_pool! sleep 10 puts "Recovering manually..." retry end ruby-amqp-bunny-b6569cd/examples/connection/manually_reconnecting_publisher.rb000066400000000000000000000006341464043542000301500ustar00rootroot00000000000000#!/usr/bin/env ruby require 'bunny' Bundler.setup begin connection = Bunny.new(:automatically_recover => false) connection.start ch = connection.channel x = ch.default_exchange loop do 10.times do |i| print "." x.publish("") end sleep 3.0 end rescue Bunny::NetworkFailure => e ch.maybe_kill_consumer_work_pool! sleep 10 puts "Recovering manually..." retry end ruby-amqp-bunny-b6569cd/examples/connection/unknown_host.rb000066400000000000000000000005071464043542000242460ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "bundler" Bundler.setup $:.unshift(File.expand_path("../../../lib", __FILE__)) require 'bunny' begin conn = Bunny.new("amqp://guest:guest@aksjhdkajshdkj.example82737.com") conn.start rescue Bunny::TCPConnectionFailed => e puts "Connection to #{conn.hostname} failed" end ruby-amqp-bunny-b6569cd/examples/consumers/000077500000000000000000000000001464043542000210425ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/examples/consumers/high_and_low_priority.rb000066400000000000000000000021461464043542000257550ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "bundler" Bundler.setup $:.unshift(File.expand_path("../../../lib", __FILE__)) require 'bunny' HIGH_PRIORITY_Q = "bunny.examples.priority.hilo.high" LOW_PRIORITY_Q = "bunny.examples.priority.hilo.low" conn = Bunny.new(heartbeat_timeout: 8) conn.start ch1 = conn.create_channel ch2 = conn.create_channel hi_q = ch1.queue(HIGH_PRIORITY_Q, :durable => false) lo_q = ch2.queue(LOW_PRIORITY_Q, :durable => false) ch3 = conn.create_channel x = ch3.default_exchange # create a backlog of low priority messages 30.times do x.publish(rand.to_s, :routing_key => LOW_PRIORITY_Q) end # and a much smaller one of high priority messages 3.times do x.publish(rand.to_s, :routing_key => HIGH_PRIORITY_Q) end hi_q.subscribe do |delivery_info, metadata, payload| puts "[high] Consumed #{payload}" end lo_q.subscribe do |delivery_info, metadata, payload| puts "[low] Consumed #{payload}" end loop do sleep 0.5 data = rand.to_s rk = [HIGH_PRIORITY_Q, LOW_PRIORITY_Q].sample x.publish(data, :routing_key => rk) puts "Published #{data}, routing key: #{rk}" end ruby-amqp-bunny-b6569cd/examples/guides/000077500000000000000000000000001464043542000203045ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/examples/guides/exchanges/000077500000000000000000000000001464043542000222515ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/examples/guides/exchanges/direct_exchange_routing.rb000066400000000000000000000014321464043542000274610ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" puts "=> Direct exchange routing" puts conn = Bunny.new conn.start ch = conn.create_channel x = ch.direct("examples.imaging") q1 = ch.queue("", :auto_delete => true).bind(x, :routing_key => "resize") q1.subscribe do |delivery_info, properties, payload| puts "[consumer] #{q1.name} received a 'resize' message" end q2 = ch.queue("", :auto_delete => true).bind(x, :routing_key => "watermark") q2.subscribe do |delivery_info, properties, payload| puts "[consumer] #{q2.name} received a 'watermark' message" end # just an example data = rand.to_s x.publish(data, :routing_key => "resize") x.publish(data, :routing_key => "watermark") sleep 0.5 x.delete q1.delete q2.delete puts "Disconnecting..." conn.close ruby-amqp-bunny-b6569cd/examples/guides/exchanges/fanout_exchange_routing.rb000066400000000000000000000007311464043542000275040ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" puts "=> Fanout exchange routing" puts conn = Bunny.new conn.start ch = conn.create_channel x = ch.fanout("examples.pings") 10.times do |i| q = ch.queue("", :auto_delete => true).bind(x) q.subscribe do |delivery_info, properties, payload| puts "[consumer] #{q.name} received a message: #{payload}" end end x.publish("Ping") sleep 0.5 x.delete puts "Disconnecting..." conn.close ruby-amqp-bunny-b6569cd/examples/guides/exchanges/headers_exchange_routing.rb000066400000000000000000000015421464043542000276240ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" puts "=> Headers exchange routing" puts conn = Bunny.new conn.start ch = conn.create_channel x = ch.headers("headers") q1 = ch.queue("", :exclusive => true).bind(x, :arguments => {"os" => "linux", "cores" => 8, "x-match" => "all"}) q2 = ch.queue("", :exclusive => true).bind(x, :arguments => {"os" => "osx", "cores" => 4, "x-match" => "any"}) q1.subscribe do |delivery_info, properties, content| puts "#{q1.name} received #{content}" end q2.subscribe do |delivery_info, properties, content| puts "#{q2.name} received #{content}" end x.publish("8 cores/Linux", :headers => {"os" => "linux", "cores" => 8}) x.publish("8 cores/OS X", :headers => {"os" => "osx", "cores" => 8}) x.publish("4 cores/Linux", :headers => {"os" => "linux", "cores" => 4}) sleep 0.5 conn.close ruby-amqp-bunny-b6569cd/examples/guides/exchanges/mandatory_messages.rb000066400000000000000000000012261464043542000264640ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" puts "=> Publishing messages as mandatory" puts conn = Bunny.new conn.start ch = conn.create_channel x = ch.default_exchange x.on_return do |return_info, properties, content| puts "Got a returned message: #{content}" end q = ch.queue("", :exclusive => true) q.subscribe do |delivery_info, properties, content| puts "Consumed a message: #{content}" end x.publish("This will NOT be returned", :mandatory => true, :routing_key => q.name) x.publish("This will be returned", :mandatory => true, :routing_key => "akjhdfkjsh#{rand}") sleep 0.5 puts "Disconnecting..." conn.close ruby-amqp-bunny-b6569cd/examples/guides/extensions/000077500000000000000000000000001464043542000225035ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/examples/guides/extensions/alternate_exchange.rb000066400000000000000000000012161464043542000266510ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" STDOUT.sync = true puts "=> Demonstrating alternate exchanges" puts conn = Bunny.new conn.start ch = conn.create_channel x1 = ch.fanout("bunny.examples.ae.exchange1", :auto_delete => true, :durable => false) x2 = ch.fanout("bunny.examples.ae.exchange2", :auto_delete => true, :durable => false, :arguments => { "alternate-exchange" => x1.name }) q = ch.queue("", :exclusive => true) q.bind(x1) x2.publish("") sleep 0.2 puts "Queue #{q.name} now has #{q.message_count} message in it" sleep 0.7 puts "Disconnecting..." conn.close ruby-amqp-bunny-b6569cd/examples/guides/extensions/basic_nack.rb000066400000000000000000000010731464043542000251060ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" puts "=> Demonstrating basic.nack" puts conn = Bunny.new conn.start ch = conn.create_channel q = ch.queue("", :exclusive => true) 20.times do q.publish("") end 20.times do delivery_info, _, _ = q.pop(:manual_ack => true) if delivery_info.delivery_tag == 20 # requeue them all at once with basic.nack ch.nack(delivery_info.delivery_tag, true, true) end end puts "Queue #{q.name} still has #{q.message_count} messages in it" sleep 0.7 puts "Disconnecting..." conn.close ruby-amqp-bunny-b6569cd/examples/guides/extensions/connection_blocked.rb000066400000000000000000000012241464043542000266510ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" puts "=> Demonstrating connection.blocked" puts conn = Bunny.new conn.start ch = conn.create_channel x = ch.fanout("amq.fanout") # This example requires high memory watermark to be set # really low to demonstrate blocking. # # rabbitmqctl set_vm_memory_high_watermark 0.00000001 # # should do it. conn.on_blocked do |connection_blocked| puts "Connection is blocked. Reason: #{connection_blocked.reason}" end conn.on_unblocked do |connection_unblocked| puts "Connection is unblocked." end x.publish("z" * 1024 * 1024 * 16) sleep 120.0 puts "Disconnecting..." conn.close ruby-amqp-bunny-b6569cd/examples/guides/extensions/consumer_cancellation_notification.rb000066400000000000000000000012051464043542000321430ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" puts "=> Demonstrating consumer cancellation notification" puts conn = Bunny.new conn.start ch = conn.create_channel module Bunny module Examples class ExampleConsumer < Bunny::Consumer def cancelled? @cancelled end def handle_cancellation(basic_cancel) puts "#{@consumer_tag} was cancelled" @cancelled = true end end end end q = ch.queue("", :exclusive => true) c = Bunny::Examples::ExampleConsumer.new(ch, q) q.subscribe_with(c) sleep 0.1 q.delete sleep 0.1 puts "Disconnecting..." conn.close ruby-amqp-bunny-b6569cd/examples/guides/extensions/dead_letter_exchange.rb000066400000000000000000000013501464043542000271450ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" puts "=> Demonstrating dead letter exchange" puts conn = Bunny.new conn.start ch = conn.create_channel x = ch.fanout("amq.fanout") dlx = ch.fanout("bunny.examples.dlx.exchange") q = ch.queue("", :exclusive => true, :arguments => {"x-dead-letter-exchange" => dlx.name}).bind(x) # dead letter queue dlq = ch.queue("", :exclusive => true).bind(dlx) x.publish("") sleep 0.2 delivery_info, _, _ = q.pop(:manual_ack => true) puts "#{dlq.message_count} messages dead lettered so far" puts "Rejecting a message" ch.nack(delivery_info.delivery_tag) sleep 0.2 puts "#{dlq.message_count} messages dead lettered so far" dlx.delete puts "Disconnecting..." conn.close ruby-amqp-bunny-b6569cd/examples/guides/extensions/exchange_to_exchange_bindings.rb000066400000000000000000000011241464043542000310310ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" puts "=> Demonstrating exchange-to-exchange bindings" puts conn = Bunny.new conn.start ch = conn.create_channel x1 = ch.fanout("bunny.examples.e2e.exchange1", :auto_delete => true, :durable => false) x2 = ch.fanout("bunny.examples.e2e.exchange2", :auto_delete => true, :durable => false) # x1 will be the source x2.bind(x1) q = ch.queue("", :exclusive => true) q.bind(x2) x1.publish("") sleep 0.2 puts "Queue #{q.name} now has #{q.message_count} message in it" sleep 0.7 puts "Disconnecting..." conn.close ruby-amqp-bunny-b6569cd/examples/guides/extensions/per_message_ttl.rb000066400000000000000000000011371464043542000262070ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" puts "=> Demonstrating per-message TTL" puts conn = Bunny.new conn.start ch = conn.create_channel x = ch.fanout("amq.fanout") q = ch.queue("", :exclusive => true).bind(x) 10.times do |i| x.publish("Message #{i}", :expiration => 1000) end sleep 0.7 _, _, content1 = q.pop puts "Fetched #{content1.inspect} after 0.7 second" sleep 0.8 _, _, content2 = q.pop msg = if content2 content2.inspect else "nothing" end puts "Fetched #{msg} after 1.5 second" sleep 0.7 puts "Closing..." conn.close ruby-amqp-bunny-b6569cd/examples/guides/extensions/per_queue_message_ttl.rb000066400000000000000000000011711464043542000274110ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" puts "=> Demonstrating per-queue message TTL" puts conn = Bunny.new conn.start ch = conn.create_channel x = ch.fanout("amq.fanout") q = ch.queue("", :exclusive => true, :arguments => {"x-message-ttl" => 1000}).bind(x) 10.times do |i| x.publish("Message #{i}") end sleep 0.7 _, _, content1 = q.pop puts "Fetched #{content1.inspect} after 0.7 second" sleep 0.8 _, _, content2 = q.pop msg = if content2 content2.inspect else "nothing" end puts "Fetched #{msg} after 1.5 second" sleep 0.7 puts "Closing..." conn.close ruby-amqp-bunny-b6569cd/examples/guides/extensions/publisher_confirms.rb000066400000000000000000000010321464043542000267210ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" puts "=> Demonstrating publisher confirms" puts conn = Bunny.new conn.start ch = conn.create_channel x = ch.fanout("amq.fanout") q = ch.queue("", :exclusive => true).bind(x) ch.confirm_select 1000.times do x.publish("") end ch.wait_for_confirms # blocks calling thread until all acks are received sleep 0.2 puts "Received acks for all published messages. #{q.name} now has #{q.message_count} messages." sleep 0.7 puts "Disconnecting..." conn.close ruby-amqp-bunny-b6569cd/examples/guides/extensions/queue_lease.rb000066400000000000000000000007541464043542000253330ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" puts "=> Demonstrating queue TTL (queue leases)" puts conn = Bunny.new conn.start ch = conn.create_channel q = ch.queue("", :exclusive => true, :arguments => {"x-expires" => 300}) sleep 0.4 begin # this will raise because the queue is already deleted q.message_count rescue Bunny::NotFound => nfe puts "Got a 404 response: the queue has already been removed" end sleep 0.7 puts "Closing..." conn.close ruby-amqp-bunny-b6569cd/examples/guides/extensions/sender_selected_distribution.rb000066400000000000000000000016741464043542000307670ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" puts "=> Demonstrating sender-selected distribution" puts conn = Bunny.new conn.start ch = conn.create_channel x = ch.direct("bunny.examples.ssd.exchange") q1 = ch.queue("", :exclusive => true).bind(x, :routing_key => "one") q2 = ch.queue("", :exclusive => true).bind(x, :routing_key => "two") q3 = ch.queue("", :exclusive => true).bind(x, :routing_key => "three") q4 = ch.queue("", :exclusive => true).bind(x, :routing_key => "four") 10.times do |i| x.publish("Message #{i}", :routing_key => "one", :headers => {"CC" => ["two", "three"]}) end sleep 0.2 puts "Queue #{q1.name} now has #{q1.message_count} messages in it" puts "Queue #{q2.name} now has #{q2.message_count} messages in it" puts "Queue #{q3.name} now has #{q3.message_count} messages in it" puts "Queue #{q4.name} now has #{q4.message_count} messages in it" sleep 0.7 puts "Closing..." conn.close ruby-amqp-bunny-b6569cd/examples/guides/getting_started/000077500000000000000000000000001464043542000234735ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/examples/guides/getting_started/blabbr.rb000066400000000000000000000011541464043542000252450ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" conn = Bunny.new conn.start ch = conn.create_channel x = ch.fanout("nba.scores") ch.queue("joe", :auto_delete => true).bind(x).subscribe do |delivery_info, properties, payload| puts "#{payload} => joe" end ch.queue("aaron", :auto_delete => true).bind(x).subscribe do |delivery_info, properties, payload| puts "#{payload} => aaron" end ch.queue("bob", :auto_delete => true).bind(x).subscribe do |delivery_info, properties, payload| puts "#{payload} => bob" end x.publish("BOS 101, NYK 89").publish("ORL 85, ALT 88") conn.close ruby-amqp-bunny-b6569cd/examples/guides/getting_started/hello_world.rb000066400000000000000000000005601464043542000263330ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" STDOUT.sync = true conn = Bunny.new conn.start ch = conn.create_channel q = ch.queue("bunny.examples.hello_world", :auto_delete => true) q.subscribe do |delivery_info, properties, payload| puts "Received #{payload}" end q.publish("Hello!", :routing_key => q.name) sleep 1.0 conn.close ruby-amqp-bunny-b6569cd/examples/guides/getting_started/weathr.rb000066400000000000000000000047431464043542000253220ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" STDOUT.sync = true connection = Bunny.new connection.start channel = connection.create_channel # topic exchange name can be any string exchange = channel.topic("weathr", :auto_delete => true) # Subscribers. channel.queue("", :exclusive => true).bind(exchange, :routing_key => "americas.north.#").subscribe do |delivery_info, properties, payload| puts "An update for North America: #{payload}, routing key is #{delivery_info.routing_key}" end channel.queue("americas.south").bind(exchange, :routing_key => "americas.south.#").subscribe do |delivery_info, properties, payload| puts "An update for South America: #{payload}, routing key is #{delivery_info.routing_key}" end channel.queue("us.california").bind(exchange, :routing_key => "americas.north.us.ca.*").subscribe do |delivery_info, properties, payload| puts "An update for US/California: #{payload}, routing key is #{delivery_info.routing_key}" end channel.queue("us.tx.austin").bind(exchange, :routing_key => "#.tx.austin").subscribe do |delivery_info, properties, payload| puts "An update for Austin, TX: #{payload}, routing key is #{delivery_info.routing_key}" end channel.queue("it.rome").bind(exchange, :routing_key => "europe.italy.rome").subscribe do |delivery_info, properties, payload| puts "An update for Rome, Italy: #{payload}, routing key is #{delivery_info.routing_key}" end channel.queue("asia.hk").bind(exchange, :routing_key => "asia.southeast.hk.#").subscribe do |delivery_info, properties, payload| puts "An update for Hong Kong: #{payload}, routing key is #{delivery_info.routing_key}" end exchange.publish("San Diego update", :routing_key => "americas.north.us.ca.sandiego"). publish("Berkeley update", :routing_key => "americas.north.us.ca.berkeley"). publish("San Francisco update", :routing_key => "americas.north.us.ca.sanfrancisco"). publish("New York update", :routing_key => "americas.north.us.ny.newyork"). publish("São Paolo update", :routing_key => "americas.south.brazil.saopaolo"). publish("Hong Kong update", :routing_key => "asia.southeast.hk.hongkong"). publish("Kyoto update", :routing_key => "asia.southeast.japan.kyoto"). publish("Shanghai update", :routing_key => "asia.southeast.prc.shanghai"). publish("Rome update", :routing_key => "europe.italy.roma"). publish("Paris update", :routing_key => "europe.france.paris") sleep 1.0 connection.close ruby-amqp-bunny-b6569cd/examples/guides/queues/000077500000000000000000000000001464043542000216135ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/examples/guides/queues/one_off_consumer.rb000066400000000000000000000010041464043542000254610ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" STDOUT.sync = true conn = Bunny.new conn.start ch = conn.create_channel q = ch.queue("bunny.examples.hello_world", :auto_delete => true) q.publish("Hello!", :routing_key => q.name) # demonstrates a blocking consumer that needs to cancel itself # in the message handler q.subscribe(:block => true) do |delivery_info, properties, payload| puts "Received #{payload}, cancelling" delivery_info.consumer.cancel end sleep 1.0 conn.close ruby-amqp-bunny-b6569cd/examples/guides/queues/redeliveries.rb000066400000000000000000000037371464043542000246340ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" STDOUT.sync = true puts "=> Subscribing for messages using explicit acknowledgements model" puts connection1 = Bunny.new connection1.start connection2 = Bunny.new connection2.start connection3 = Bunny.new connection3.start ch1 = connection1.create_channel ch1.prefetch(1) ch2 = connection2.create_channel ch2.prefetch(1) ch3 = connection3.create_channel ch3.prefetch(1) x = ch3.direct("amq.direct") q1 = ch1.queue("bunny.examples.acknowledgements.explicit", :auto_delete => false) q1.purge q1.bind(x).subscribe(:manual_ack => true, :block => false) do |delivery_info, properties, payload| # do some work sleep(0.2) # acknowledge some messages, they will be removed from the queue if rand > 0.5 # FYI: there is a shortcut, Bunny::Channel.ack ch1.acknowledge(delivery_info.delivery_tag, false) puts "[consumer1] Got message ##{properties.headers['i']}, redelivered?: #{delivery_info.redelivered?}, ack-ed" else # some messages are not ack-ed and will remain in the queue for redelivery # when app #1 connection is closed (either properly or due to a crash) puts "[consumer1] Got message ##{properties.headers['i']}, SKIPPED" end end q2 = ch2.queue("bunny.examples.acknowledgements.explicit", :auto_delete => false) q2.bind(x).subscribe(:manual_ack => true, :block => false) do |delivery_info, properties, payload| # do some work sleep(0.2) ch2.acknowledge(delivery_info.delivery_tag, false) puts "[consumer2] Got message ##{properties.headers['i']}, redelivered?: #{delivery_info.redelivered?}, ack-ed" end t1 = Thread.new do i = 0 loop do sleep 0.5 x.publish("Message ##{i}", :headers => { :i => i }) i += 1 end end t1.abort_on_exception = true t2 = Thread.new do sleep 4.0 connection1.close puts "----- Connection 1 is now closed (we pretend that it has crashed) -----" end t2.abort_on_exception = true sleep 7.0 connection2.close connection3.close ruby-amqp-bunny-b6569cd/lib/000077500000000000000000000000001464043542000157545ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/lib/amq/000077500000000000000000000000001464043542000165325ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/lib/amq/protocol/000077500000000000000000000000001464043542000203735ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/lib/amq/protocol/extensions.rb000066400000000000000000000004131464043542000231150ustar00rootroot00000000000000# @private module AMQ # @private module Protocol # @private class Basic # Extended to allow wrapping delivery tag into # a versioned one. # # @private class GetOk attr_writer :delivery_tag end end end end ruby-amqp-bunny-b6569cd/lib/bunny.rb000066400000000000000000000162201464043542000174350ustar00rootroot00000000000000# -*- encoding: utf-8; mode: ruby -*- require "timeout" require "bunny/version" require "amq/protocol/client" require "amq/protocol/extensions" require "bunny/framing" require "bunny/exceptions" require "bunny/socket" require "bunny/timestamp" require "bunny/timeout" begin require "openssl" require "bunny/ssl_socket" rescue LoadError # no-op end require "logger" # Core entities: connection, channel, exchange, queue, consumer require "bunny/session" require "bunny/channel" require "bunny/exchange" require "bunny/queue" require "bunny/consumer" # Bunny is a RabbitMQ client that focuses on ease of use. # @see http://rubybunny.info module Bunny # AMQP protocol version Bunny implements PROTOCOL_VERSION = AMQ::Protocol::PROTOCOL_VERSION # # API # # @return [String] Bunny version def self.version VERSION end # @return [String] AMQP protocol version Bunny implements def self.protocol_version AMQ::Protocol::PROTOCOL_VERSION end # Instantiates a new connection. The actual network # connection is started with {Bunny::Session#start} # # @param [String, Hash] connection_string_or_opts Connection string or a hash of connection options # @param [Hash] optz Extra options not related to connection # # @option connection_string_or_opts [String] :host ("127.0.0.1") Hostname or IP address to connect to # @option connection_string_or_opts [Array] :hosts (["127.0.0.1"]) list of hostname or IP addresses to select hostname from when connecting # @option connection_string_or_opts [Array] :addresses (["127.0.0.1:5672"]) list of addresses to select hostname and port from when connecting # @option connection_string_or_opts [Integer] :port (5672) Port RabbitMQ listens on # @option connection_string_or_opts [String] :username ("guest") Username # @option connection_string_or_opts [String] :password ("guest") Password # @option connection_string_or_opts [String] :vhost ("/") Virtual host to use # @option connection_string_or_opts [Integer, Symbol] :heartbeat (:server) Heartbeat timeout to offer to the server. :server means use the value suggested by RabbitMQ. 0 means heartbeats and socket read timeouts will be disabled (not recommended). # @option connection_string_or_opts [Integer] :network_recovery_interval (4) Recovery interval periodic network recovery will use. This includes initial pause after network failure. # @option connection_string_or_opts [Boolean] :tls (false) Should TLS/SSL be used? # @option connection_string_or_opts [String] :tls_cert (nil) Path to client TLS/SSL certificate file (.pem) # @option connection_string_or_opts [String] :tls_key (nil) Path to client TLS/SSL private key file (.pem) # @option connection_string_or_opts [Array] :tls_ca_certificates Array of paths to TLS/SSL CA files (.pem), by default detected from OpenSSL configuration # @option connection_string_or_opts [String] :verify_peer (true) Whether TLS peer verification should be performed # @option connection_string_or_opts [Symbol] :tls_protocol (negotiated) What TLS version should be used (:TLSv1, :TLSv1_1, or :TLSv1_2) # @option connection_string_or_opts [Integer] :channel_max (2047) Maximum number of channels allowed on this connection, minus 1 to account for the special channel 0. # @option connection_string_or_opts [Integer] :continuation_timeout (15000) Timeout for client operations that expect a response (e.g. {Bunny::Queue#get}), in milliseconds. # @option connection_string_or_opts [Integer] :connection_timeout (30) Timeout in seconds for connecting to the server. # @option connection_string_or_opts [Integer] :read_timeout (30) TCP socket read timeout in seconds. If heartbeats are disabled this will be ignored. # @option connection_string_or_opts [Integer] :write_timeout (30) TCP socket write timeout in seconds. # @option connection_string_or_opts [Proc] :hosts_shuffle_strategy a callable that reorders a list of host strings, defaults to Array#shuffle # @option connection_string_or_opts [Proc] :recovery_completed a callable that will be called when a network recovery is performed # @option connection_string_or_opts [Logger] :logger The logger. If missing, one is created using :log_file and :log_level. # @option connection_string_or_opts [IO, String] :log_file The file or path to use when creating a logger. Defaults to STDOUT. # @option connection_string_or_opts [IO, String] :logfile DEPRECATED: use :log_file instead. The file or path to use when creating a logger. Defaults to STDOUT. # @option connection_string_or_opts [Integer] :log_level The log level to use when creating a logger. Defaults to LOGGER::WARN # @option connection_string_or_opts [Boolean] :automatically_recover (true) Should automatically recover from network failures? # @option connection_string_or_opts [Integer] :recovery_attempts (nil) Max number of recovery attempts, nil means forever # @option connection_string_or_opts [Integer] :reset_recovery_attempts_after_reconnection (true) Should recovery attempt counter be reset after successful reconnection? When set to false, the attempt counter will last through the entire lifetime of the connection object. # @option connection_string_or_opts [Proc] :recovery_attempt_started (nil) Will be called before every connection recovery attempt # @option connection_string_or_opts [Proc] :recovery_completed (nil) Will be called after successful connection recovery # @option connection_string_or_opts [Boolean] :recover_from_connection_close (true) Should this connection recover after receiving a server-sent connection.close (e.g. connection was force closed)? # @option connection_string_or_opts [Object] :session_error_handler (Thread.current) Object which responds to #raise that will act as a session error handler. Defaults to Thread.current, which will raise asynchronous exceptions in the thread that created the session. # # @option optz [String] :auth_mechanism ("PLAIN") Authentication mechanism, PLAIN or EXTERNAL # @option optz [String] :locale ("PLAIN") Locale RabbitMQ should use # @option optz [String] :connection_name (nil) Client-provided connection name, if any. Note that the value returned does not uniquely identify a connection and cannot be used as a connection identifier in HTTP API requests. # # @return [Bunny::Session] # @see Bunny::Session#start # @see http://rubybunny.info/articles/getting_started.html # @see http://rubybunny.info/articles/connecting.html # @api public def self.new(connection_string_or_opts = ENV['RABBITMQ_URL'], optz = {}) if connection_string_or_opts.respond_to?(:keys) && optz.empty? optz = connection_string_or_opts end conn = Session.new(connection_string_or_opts, optz) @default_connection ||= conn conn end def self.run(connection_string_or_opts = ENV['RABBITMQ_URL'], opts = {}, &block) raise ArgumentError, 'Bunny#run requires a block' unless block if connection_string_or_opts.respond_to?(:keys) && opts.empty? opts = connection_string_or_opts end client = Session.new(connection_string_or_opts, opts) begin client.start block.call(client) ensure client.stop end # backwards compatibility :run_ok end end ruby-amqp-bunny-b6569cd/lib/bunny/000077500000000000000000000000001464043542000171075ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/lib/bunny/authentication/000077500000000000000000000000001464043542000221265ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/lib/bunny/authentication/credentials_encoder.rb000066400000000000000000000026061464043542000264530ustar00rootroot00000000000000module Bunny # Contains credentials encoding implementations for various # authentication strategies. module Authentication # Base credentials encoder. Subclasses implement credentials encoding for # a particular authentication mechanism (PLAIN, EXTERNAL, etc). # # @api plugin class CredentialsEncoder # # API # # Session that uses this encoder # @return [Bunny::Session] attr_reader :session # Instantiates a new encoder for the authentication mechanism # used by the provided session. # # @return [Bunny::CredentialsEncoder] def self.for_session(session) registry[session.mechanism].new(session) end # @private def self.registry @@registry ||= Hash.new { raise NotImplementedError } end # Registers an encoder for authentication mechanism # @api plugin def self.auth_mechanism(*mechanisms) mechanisms.each do |m| registry[m] = self end end # Encodes provided credentials according to the specific authentication # mechanism # @return [String] Encoded credentials def encode_credentials(username, challenge) raise NotImplementedError.new("Subclasses must override this method") end protected def initialize(session) @session = session end end end end ruby-amqp-bunny-b6569cd/lib/bunny/authentication/external_mechanism_encoder.rb000066400000000000000000000017201464043542000300200ustar00rootroot00000000000000require "bunny/authentication/credentials_encoder" module Bunny module Authentication # Encodes credentials using the EXTERNAL mechanism class ExternalMechanismEncoder < CredentialsEncoder auth_mechanism "EXTERNAL", "external" # Encodes a username and password for the EXTERNAL mechanism. Since # authentication is handled by an encapsulating protocol like SSL or # UNIX domain sockets, EXTERNAL doesn't pass along any username or # password information at all and this method always returns the # empty string. # # @param [String] username The username to encode. This parameter is # ignored. # @param [String] password The password to encode. This parameter is # ignored. # @return [String] The username and password, encoded for the # EXTERNAL mechanism. This is always the empty string. def encode_credentials(username, password) "" end end end end ruby-amqp-bunny-b6569cd/lib/bunny/authentication/plain_mechanism_encoder.rb000066400000000000000000000007531464043542000273060ustar00rootroot00000000000000require "bunny/authentication/credentials_encoder" module Bunny module Authentication # Encodes credentials using the PLAIN mechanism class PlainMechanismEncoder < CredentialsEncoder auth_mechanism "PLAIN", "plain" # Encodes provided credentials as described in RFC 2595 # @api public # @see http://tools.ietf.org/rfc/rfc2595.txt RFC 2595 def encode_credentials(username, password) "\0#{username}\0#{password}" end end end end ruby-amqp-bunny-b6569cd/lib/bunny/channel.rb000066400000000000000000002256331464043542000210570ustar00rootroot00000000000000# -*- coding: utf-8 -*- require "thread" require "monitor" require "set" require "bunny/concurrent/atomic_fixnum" require "bunny/consumer_work_pool" require "bunny/exchange" require "bunny/queue" require "bunny/delivery_info" require "bunny/return_info" require "bunny/message_properties" if defined?(JRUBY_VERSION) require "bunny/concurrent/linked_continuation_queue" else require "bunny/concurrent/continuation_queue" end module Bunny # ## Channels in RabbitMQ # # To quote {http://www.rabbitmq.com/resources/specs/amqp0-9-1.pdf AMQP 0.9.1 specification}: # # AMQP 0.9.1 is a multi-channelled protocol. Channels provide a way to multiplex # a heavyweight TCP/IP connection into several light weight connections. # This makes the protocol more “firewall friendly” since port usage is predictable. # It also means that traffic shaping and other network QoS features can be easily employed. # Channels are independent of each other and can perform different functions simultaneously # with other channels, the available bandwidth being shared between the concurrent activities. # # # ## Opening Channels # # Channels can be opened either via `Bunny::Session#create_channel` (sufficient in the majority # of cases) or by instantiating `Bunny::Channel` directly: # # conn = Bunny.new # conn.start # # ch = conn.create_channel # # This will automatically allocate a channel id. # # ## Closing Channels # # Channels are closed via {Bunny::Channel#close}. Channels that get a channel-level exception are # closed, too. Closed channels can no longer be used. Attempts to use them will raise # {Bunny::ChannelAlreadyClosed}. # # ch = conn.create_channel # ch.close # # ## Higher-level API # # Bunny offers two sets of methods on {Bunny::Channel}: known as higher-level and lower-level # APIs, respectively. Higher-level API mimics {http://rubyamqp.info amqp gem} API where # exchanges and queues are objects (instance of {Bunny::Exchange} and {Bunny::Queue}, respectively). # Lower-level API is built around AMQP 0.9.1 methods (commands), where queues and exchanges are # passed as strings (à la RabbitMQ Java client, {http://clojurerabbitmq.info Langohr} and Pika). # # ### Queue Operations In Higher-level API # # * {Bunny::Channel#queue} is used to declare queues. The rest of the API is in {Bunny::Queue}. # # # ### Exchange Operations In Higher-level API # # * {Bunny::Channel#topic} declares a topic exchange. The rest of the API is in {Bunny::Exchange}. # * {Bunny::Channel#direct} declares a direct exchange. # * {Bunny::Channel#fanout} declares a fanout exchange. # * {Bunny::Channel#headers} declares a headers exchange. # * {Bunny::Channel#default_exchange} # * {Bunny::Channel#exchange} is used to declare exchanges with type specified as a symbol or string. # # # ## Channel Qos (Prefetch Level) # # It is possible to control how many messages at most a consumer will be given (before it acknowledges # or rejects previously consumed ones). This setting is per channel and controlled via {Bunny::Channel#prefetch}. # # # ## Channel IDs # # Channels are identified by their ids which are integers. Bunny takes care of allocating and # releasing them as channels are opened and closed. It is almost never necessary to specify # channel ids explicitly. # # There is a limit on the maximum number of channels per connection, usually 65536. Note # that allocating channels is very cheap on both client and server so having tens, hundreds # or even thousands of channels is not a problem. # # ## Channels and Error Handling # # Channel-level exceptions are more common than connection-level ones and often indicate # issues applications can recover from (such as consuming from or trying to delete # a queue that does not exist). # # With Bunny, channel-level exceptions are raised as Ruby exceptions, for example, # {Bunny::NotFound}, that provide access to the underlying `channel.close` method # information. # # @example Handling 404 NOT_FOUND # begin # ch.queue_delete("queue_that_should_not_exist#{rand}") # rescue Bunny::NotFound => e # puts "Channel-level exception! Code: #{e.channel_close.reply_code}, message: #{e.channel_close.reply_text}" # end # # @example Handling 406 PRECONDITION_FAILED # begin # ch2 = conn.create_channel # q = "bunny.examples.recovery.q#{rand}" # # ch2.queue_declare(q, :durable => false) # ch2.queue_declare(q, :durable => true) # rescue Bunny::PreconditionFailed => e # puts "Channel-level exception! Code: #{e.channel_close.reply_code}, message: #{e.channel_close.reply_text}" # ensure # conn.create_channel.queue_delete(q) # end # # @see http://www.rabbitmq.com/tutorials/amqp-concepts.html AMQP 0.9.1 Model Concepts Guide # @see http://rubybunny.info/articles/getting_started.html Getting Started with RabbitMQ Using Bunny # @see http://rubybunny.info/articles/queues.html Queues and Consumers # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing # @see http://rubybunny.info/articles/error_handling.html Error Handling and Recovery Guide class Channel # # API # # @return [Integer] Channel id attr_accessor :id # @return [Bunny::Session] AMQP connection this channel was opened on attr_reader :connection # @return [Symbol] Channel status (:opening, :open, :closed) attr_reader :status # @return [Bunny::ConsumerWorkPool] Thread pool delivered messages are dispatched to. attr_reader :work_pool # @return [Integer] Next publisher confirmations sequence index attr_reader :next_publish_seq_no # @return [Integer] Offset for the confirmations sequence index. # This will be set to the current sequence index during automatic network failure recovery # to keep the sequence monotonic for the user and abstract the reset from the protocol attr_reader :delivery_tag_offset # @return [Hash] Queue instances declared on this channel attr_reader :queues # @return [Hash] Exchange instances declared on this channel attr_reader :exchanges # @return [Set] Set of published message indexes that are currently unconfirmed attr_reader :unconfirmed_set # @return [Set] Set of nacked message indexes that have been nacked attr_reader :nacked_set # @return [Hash] Consumer instances declared on this channel attr_reader :consumers # @return [Integer] active basic.qos prefetch value attr_reader :prefetch_count # @return [Integer] active basic.qos prefetch global mode attr_reader :prefetch_global DEFAULT_CONTENT_TYPE = "application/octet-stream".freeze SHORTSTR_LIMIT = 255 # @param [Bunny::Session] connection AMQP 0.9.1 connection # @param [Integer] id Channel id, pass nil to make Bunny automatically allocate it # @param [Bunny::ConsumerWorkPool] work_pool Thread pool for delivery processing, by default of size 1 def initialize(connection = nil, id = nil, work_pool = ConsumerWorkPool.new(1)) @connection = connection @logger = connection.logger @id = id || @connection.next_channel_id # channel allocator is exhausted if @id < 0 msg = "Cannot open a channel: max number of channels on connection reached. Connection channel_max value: #{@connection.channel_max}" @logger.error(msg) raise msg else @logger.debug { "Allocated channel id: #{@id}" } end @status = :opening @connection.register_channel(self) @queues = Hash.new @exchanges = Hash.new @consumers = Hash.new @work_pool = work_pool # synchronizes frameset delivery. MK. @publishing_mutex = @connection.mutex_impl.new @consumer_mutex = @connection.mutex_impl.new @queue_mutex = @connection.mutex_impl.new @exchange_mutex = @connection.mutex_impl.new @unconfirmed_set_mutex = @connection.mutex_impl.new self.reset_continuations # threads awaiting on continuations. Used to unblock # them when network connection goes down so that busy loops # that perform synchronous operations can work. MK. @threads_waiting_on_continuations = Set.new @threads_waiting_on_confirms_continuations = Set.new @threads_waiting_on_basic_get_continuations = Set.new @next_publish_seq_no = 0 @delivery_tag_offset = 0 @recoveries_counter = Bunny::Concurrent::AtomicFixnum.new(0) @uncaught_exception_handler = Proc.new do |e, consumer| @logger.error "Uncaught exception from consumer #{consumer.to_s}: #{e.inspect} @ #{e.backtrace[0]}" end end attr_reader :recoveries_counter # @private def wait_on_continuations_timeout @connection.transport_write_timeout end # Opens the channel and resets its internal state # @return [Bunny::Channel] Self # @api public def open @threads_waiting_on_continuations = Set.new @threads_waiting_on_confirms_continuations = Set.new @threads_waiting_on_basic_get_continuations = Set.new @connection.open_channel(self) # clear last channel error @last_channel_error = nil @status = :open self end # Closes the channel. Closed channels can no longer be used (this includes associated # {Bunny::Queue}, {Bunny::Exchange} and {Bunny::Consumer} instances. # @api public def close # see bunny#528 raise_if_no_longer_open! @connection.close_channel(self) @status = :closed @work_pool.shutdown maybe_kill_consumer_work_pool! end # @return [Boolean] true if this channel is open, false otherwise # @api public def open? @status == :open end # @return [Boolean] true if this channel is closed (manually or because of an exception), false otherwise # @api public def closed? @status == :closed end # # @group Backwards compatibility with 0.8.0 # # @return [Integer] Channel id def number self.id end # @return [Boolean] true if this channel is open def active open? end # @return [Bunny::Session] Connection this channel was opened on def client @connection end # @private def frame_size @connection.frame_max end # @endgroup # # Higher-level API, similar to amqp gem # # @group Higher-level API for exchange operations # Declares a fanout exchange or looks it up in the cache of previously # declared exchanges. # # @param [String] name Exchange name # @param [Hash] opts Exchange parameters # # @option opts [Boolean] :durable (false) Should the exchange be durable? # @option opts [Boolean] :auto_delete (false) Should the exchange be automatically deleted when no longer in use? # @option opts [Hash] :arguments ({}) Optional exchange arguments (used by RabbitMQ extensions) # # @return [Bunny::Exchange] Exchange instance # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide # @api public def fanout(name, opts = {}) find_exchange(name) || Exchange.new(self, :fanout, name, opts) end # Declares a direct exchange or looks it up in the cache of previously # declared exchanges. # # @param [String] name Exchange name # @param [Hash] opts Exchange parameters # # @option opts [Boolean] :durable (false) Should the exchange be durable? # @option opts [Boolean] :auto_delete (false) Should the exchange be automatically deleted when no longer in use? # @option opts [Hash] :arguments ({}) Optional exchange arguments (used by RabbitMQ extensions) # # @return [Bunny::Exchange] Exchange instance # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide # @api public def direct(name, opts = {}) find_exchange(name) || Exchange.new(self, :direct, name, opts) end # Declares a topic exchange or looks it up in the cache of previously # declared exchanges. # # @param [String] name Exchange name # @param [Hash] opts Exchange parameters # # @option opts [Boolean] :durable (false) Should the exchange be durable? # @option opts [Boolean] :auto_delete (false) Should the exchange be automatically deleted when no longer in use? # @option opts [Hash] :arguments ({}) Optional exchange arguments (used by RabbitMQ extensions) # # @return [Bunny::Exchange] Exchange instance # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide # @api public def topic(name, opts = {}) find_exchange(name) || Exchange.new(self, :topic, name, opts) end # Declares a headers exchange or looks it up in the cache of previously # declared exchanges. # # @param [String] name Exchange name # @param [Hash] opts Exchange parameters # # @option opts [Boolean] :durable (false) Should the exchange be durable? # @option opts [Boolean] :auto_delete (false) Should the exchange be automatically deleted when no longer in use? # @option opts [Hash] :arguments ({}) Optional exchange arguments # # @return [Bunny::Exchange] Exchange instance # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide # @api public def headers(name, opts = {}) find_exchange(name) || Exchange.new(self, :headers, name, opts) end # Provides access to the default exchange # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide # @api public def default_exchange @default_exchange ||= Exchange.default(self) end # Declares a headers exchange or looks it up in the cache of previously # declared exchanges. # # @param [String] name Exchange name # @param [Hash] opts Exchange parameters # # @option opts [String,Symbol] :type (:direct) Exchange type, e.g. :fanout or "x-consistent-hash" # @option opts [Boolean] :durable (false) Should the exchange be durable? # @option opts [Boolean] :auto_delete (false) Should the exchange be automatically deleted when no longer in use? # @option opts [Hash] :arguments ({}) Optional exchange arguments # # @return [Bunny::Exchange] Exchange instance # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions to AMQP 0.9.1 guide def exchange(name, opts = {}) find_exchange(name) || Exchange.new(self, opts.fetch(:type, :direct), name, opts) end # @endgroup # @group Higher-level API for queue operations # Declares a queue or looks it up in the per-channel cache. # # @param [String] name Queue name. Pass an empty string to declare a server-named queue (make RabbitMQ generate a unique name). # @param [Hash] opts Queue properties and other options # # @option opts [Boolean] :durable (false) Should this queue be durable? # @option opts [Boolean] :auto-delete (false) Should this queue be automatically deleted when the last consumer disconnects? # @option opts [Boolean] :exclusive (false) Should this queue be exclusive (only can be used by this connection, removed when the connection is closed)? # @option opts [Hash] :arguments ({}) Additional optional arguments (typically used by RabbitMQ extensions and plugins) # # @return [Bunny::Queue] Queue that was declared or looked up in the cache # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide # @api public def queue(name = AMQ::Protocol::EMPTY_STRING, opts = {}) throw ArgumentError.new("queue name must not be nil") if name.nil? q = find_queue(name) || Bunny::Queue.new(self, name, opts) register_queue(q) end # Declares a new client-named quorum queue. # # @param [String] name Queue name. Empty (server-generated) names are not supported by this method. # @param [Hash] opts Queue properties and other options. Durability, exclusivity, auto-deletion options will be ignored. # # @option opts [Hash] :arguments ({}) Additional optional arguments (typically used by RabbitMQ extensions and plugins) # # @return [Bunny::Queue] Queue that was declared # @see #durable_queue # @see #queue # @api public def quorum_queue(name, opts = {}) throw ArgumentError.new("quorum queue name must not be nil") if name.nil? throw ArgumentError.new("quorum queue name must not be empty (server-named QQs do not make sense)") if name.empty? durable_queue(name, Bunny::Queue::Types::QUORUM, opts) end # Declares a new client-named stream (that Bunny can use as if it was a queue). # Note that Bunny would still use AMQP 0-9-1 to perform operations on this "queue". # To use stream-specific operations and to gain from stream protocol efficiency and partitioning, # use a Ruby client for the RabbitMQ stream protocol. # # @param [String] name Stream name. Empty (server-generated) names are not supported by this method. # @param [Hash] opts Queue properties and other options. Durability, exclusivity, auto-deletion options will be ignored. # # @option opts [Hash] :arguments ({}) Additional optional arguments (typically used by RabbitMQ extensions and plugins) # # # @return [Bunny::Queue] Queue that was declared # @see #durable_queue # @see #queue # @api public def stream(name, opts = {}) throw ArgumentError.new("stream name must not be nil") if name.nil? throw ArgumentError.new("stream name must not be empty (server-named QQs do not make sense)") if name.empty? durable_queue(name, Bunny::Queue::Types::STREAM, opts) end # Declares a new server-named queue that is automatically deleted when the # connection is closed. # # @param [String] name Queue name. Empty (server-generated) names are not supported by this method. # @param [Hash] opts Queue properties and other options. Durability, exclusivity, auto-deletion options will be ignored. # # @option opts [Hash] :arguments ({}) Additional optional arguments (typically used by RabbitMQ extensions and plugins) # # @return [Bunny::Queue] Queue that was declared # @see #queue # @api public def durable_queue(name, type = "classic", opts = {}) throw ArgumentError.new("queue name must not be nil") if name.nil? throw ArgumentError.new("queue name must not be empty (server-named durable queues do not make sense)") if name.empty? final_opts = opts.merge({ :type => type, :durable => true, # exclusive or auto-delete QQs do not make much sense :exclusive => false, :auto_delete => false }) q = find_queue(name) || Bunny::Queue.new(self, name, final_opts) register_queue(q) end # Declares a new server-named queue that is automatically deleted when the # connection is closed. # # @return [Bunny::Queue] Queue that was declared # @see #queue # @api public def temporary_queue(opts = {}) queue("", opts.merge(:exclusive => true)) end # @endgroup # @group QoS and Flow Control # Flow control. When set to false, RabbitMQ will stop delivering messages on this # channel. # # @param [Boolean] active Should messages to consumers on this channel be delivered? # @api public def flow(active) channel_flow(active) end # Tells RabbitMQ to redeliver unacknowledged messages # @api public def recover(ignored = true) # RabbitMQ only supports basic.recover with requeue = true basic_recover(true) end # @endgroup # @group Message acknowledgements # Rejects a message. A rejected message can be requeued or # dropped by RabbitMQ. # # @param [Integer] delivery_tag Delivery tag to reject # @param [Boolean] requeue Should this message be requeued instead of dropping it? # @see Bunny::Channel#ack # @see Bunny::Channel#nack # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @api public def reject(delivery_tag, requeue = false) basic_reject(delivery_tag.to_i, requeue) end # Acknowledges a message. Acknowledged messages are completely removed from the queue. # # @param [Integer] delivery_tag Delivery tag to acknowledge # @param [Boolean] multiple (false) Should all unacknowledged messages up to this be acknowledged as well? # @see Bunny::Channel#nack # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @api public def ack(delivery_tag, multiple = false) basic_ack(delivery_tag.to_i, multiple) end alias acknowledge ack # Rejects a message. A rejected message can be requeued or # dropped by RabbitMQ. This method is similar to {Bunny::Channel#reject} but # supports rejecting multiple messages at once, and is usually preferred. # # @param [Integer] delivery_tag Delivery tag to reject # @param [Boolean] multiple (false) Should all unacknowledged messages up to this be rejected as well? # @param [Boolean] requeue (false) Should this message be requeued instead of dropping it? # @see Bunny::Channel#ack # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @api public def nack(delivery_tag, multiple = false, requeue = false) basic_nack(delivery_tag.to_i, multiple, requeue) end # @endgroup # # Lower-level API, exposes protocol operations as they are defined in the protocol, # without any OO sugar on top, by design. # # @group Consumer and Message operations (basic.*) # Publishes a message using basic.publish AMQP 0.9.1 method. # # @param [String] payload Message payload. It will never be modified by Bunny or RabbitMQ in any way. # @param [String] exchange Exchange to publish to # @param [String] routing_key Routing key # @param [Hash] opts Publishing options # # @option opts [Boolean] :persistent Should the message be persisted to disk? # @option opts [Boolean] :mandatory Should the message be returned if it cannot be routed to any queue? # @option opts [Integer] :timestamp A timestamp associated with this message # @option opts [Integer] :expiration Expiration time after which the message will be deleted # @option opts [String] :type Message type, e.g. what type of event or command this message represents. Can be any string # @option opts [String] :reply_to Queue name other apps should send the response to # @option opts [String] :content_type Message content type (e.g. application/json) # @option opts [String] :content_encoding Message content encoding (e.g. gzip) # @option opts [String] :correlation_id Message correlated to this one, e.g. what request this message is a reply for # @option opts [Integer] :priority Message priority, 0 to 9. Not used by RabbitMQ, only applications # @option opts [String] :message_id Any message identifier # @option opts [String] :user_id Optional user ID. Verified by RabbitMQ against the actual connection username # @option opts [String] :app_id Optional application ID # # @return [Bunny::Channel] Self # @api public def basic_publish(payload, exchange, routing_key, opts = {}) raise_if_no_longer_open! raise ArgumentError, "routing key cannot be longer than #{SHORTSTR_LIMIT} characters" if routing_key && routing_key.size > SHORTSTR_LIMIT exchange_name = if exchange.respond_to?(:name) exchange.name else exchange end mode = if opts.fetch(:persistent, true) 2 else 1 end opts[:delivery_mode] ||= mode opts[:content_type] ||= DEFAULT_CONTENT_TYPE opts[:priority] ||= 0 if @next_publish_seq_no > 0 @unconfirmed_set_mutex.synchronize do @unconfirmed_set.add(@next_publish_seq_no) @next_publish_seq_no += 1 end end frames = AMQ::Protocol::Basic::Publish.encode(@id, payload, opts, exchange_name, routing_key, opts[:mandatory], false, @connection.frame_max) @connection.send_frameset(frames, self) self end # Synchronously fetches a message from the queue, if there are any. This method is # for cases when the convenience of synchronous operations is more important than # throughput. # # @param [String] queue Queue name # @param [Hash] opts Options # # @option opts [Boolean] :ack (true) [DEPRECATED] Use :manual_ack instead # @option opts [Boolean] :manual_ack (true) Will this message be acknowledged manually? # # @return [Array] A triple of delivery info, message properties and message content # # @example Using Bunny::Channel#basic_get with manual acknowledgements # conn = Bunny.new # conn.start # ch = conn.create_channel # # here we assume the queue already exists and has messages # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue1", :manual_ack => true) # ch.acknowledge(delivery_info.delivery_tag) # @see Bunny::Queue#pop # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @api public def basic_get(queue, opts = {:manual_ack => true}) raise_if_no_longer_open! unless opts[:ack].nil? warn "[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead." opts[:manual_ack] = opts[:ack] end @connection.send_frame(AMQ::Protocol::Basic::Get.encode(@id, queue, !(opts[:manual_ack]))) # this is a workaround for the edge case when basic_get is called in a tight loop # and network goes down we need to perform recovery. The problem is, basic_get will # keep blocking the thread that calls it without clear way to constantly unblock it # from the network activity loop (where recovery happens) with the current continuations # implementation (and even more correct and convenient ones, such as wait/notify, should # we implement them). So we return a triple of nils immediately which apps should be # able to handle anyway as "got no message, no need to act". MK. last_basic_get_response = if @connection.open? begin wait_on_basic_get_continuations rescue Timeout::Error => e raise_if_continuation_resulted_in_a_channel_error! raise e end else [nil, nil, nil] end raise_if_continuation_resulted_in_a_channel_error! last_basic_get_response end # prefetch_count is of type short in the protocol. MK. MAX_PREFETCH_COUNT = (2 ** 16) - 1 # Controls message delivery rate using basic.qos AMQP 0.9.1 method. # # @param [Integer] prefetch_count How many messages can consumers on this channel be given at a time # (before they have to acknowledge or reject one of the earlier received messages) # @param [Boolean] global # Whether to use global mode for prefetch: # - +false+: per-consumer # - +true+: per-channel # Note that the default value (+false+) hasn't actually changed, but # previous documentation described that as meaning per-channel and # unsupported in RabbitMQ, whereas it now actually appears to mean # per-consumer and supported # (https://www.rabbitmq.com/consumer-prefetch.html). # @return [AMQ::Protocol::Basic::QosOk] RabbitMQ response # @see Bunny::Channel#prefetch # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @api public def basic_qos(count, global = false) raise ArgumentError.new("prefetch count must be a positive integer, given: #{count}") if count < 0 raise ArgumentError.new("prefetch count must be no greater than #{MAX_PREFETCH_COUNT}, given: #{count}") if count > MAX_PREFETCH_COUNT raise_if_no_longer_open! @connection.send_frame(AMQ::Protocol::Basic::Qos.encode(@id, 0, count, global)) with_continuation_timeout do @last_basic_qos_ok = wait_on_continuations end raise_if_continuation_resulted_in_a_channel_error! @prefetch_count = count @prefetch_global = global @last_basic_qos_ok end alias prefetch basic_qos # Redeliver unacknowledged messages # # @param [Boolean] requeue Should messages be requeued? # @return [AMQ::Protocol::Basic::RecoverOk] RabbitMQ response # @api public def basic_recover(requeue) raise_if_no_longer_open! @connection.send_frame(AMQ::Protocol::Basic::Recover.encode(@id, requeue)) with_continuation_timeout do @last_basic_recover_ok = wait_on_continuations end raise_if_continuation_resulted_in_a_channel_error! @last_basic_recover_ok end # Rejects or requeues a message. # # @param [Integer] delivery_tag Delivery tag obtained from delivery info # @param [Boolean] requeue Should the message be requeued? # @return [NilClass] nil # # @example Requeue a message # conn = Bunny.new # conn.start # # ch = conn.create_channel # q.subscribe do |delivery_info, properties, payload| # # requeue the message # ch.basic_reject(delivery_info.delivery_tag, true) # end # # @example Reject a message # conn = Bunny.new # conn.start # # ch = conn.create_channel # q.subscribe do |delivery_info, properties, payload| # # reject the message # ch.basic_reject(delivery_info.delivery_tag, false) # end # # @example Requeue a message fetched via basic.get # conn = Bunny.new # conn.start # # ch = conn.create_channel # # we assume the queue exists and has messages # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue3", :manual_ack => true) # ch.basic_reject(delivery_info.delivery_tag, true) # # @see Bunny::Channel#basic_nack # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @api public def basic_reject(delivery_tag, requeue = false) guarding_against_stale_delivery_tags(delivery_tag) do raise_if_no_longer_open! @connection.send_frame(AMQ::Protocol::Basic::Reject.encode(@id, delivery_tag, requeue)) nil end end # Acknowledges a delivery (message). # # @param [Integer] delivery_tag Delivery tag obtained from delivery info # @param [Boolean] multiple Should all deliveries up to this one be acknowledged? # @return [NilClass] nil # # @example Ack a message # conn = Bunny.new # conn.start # # ch = conn.create_channel # q.subscribe do |delivery_info, properties, payload| # # requeue the message # ch.basic_ack(delivery_info.delivery_tag.to_i) # end # # @example Ack a message fetched via basic.get # conn = Bunny.new # conn.start # # ch = conn.create_channel # # we assume the queue exists and has messages # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue3", :manual_ack => true) # ch.basic_ack(delivery_info.delivery_tag.to_i) # # @example Ack multiple messages fetched via basic.get # conn = Bunny.new # conn.start # # ch = conn.create_channel # # we assume the queue exists and has messages # _, _, payload1 = ch.basic_get("bunny.examples.queue3", :manual_ack => true) # _, _, payload2 = ch.basic_get("bunny.examples.queue3", :manual_ack => true) # delivery_info, properties, payload3 = ch.basic_get("bunny.examples.queue3", :manual_ack => true) # # ack all fetched messages up to payload3 # ch.basic_ack(delivery_info.delivery_tag.to_i, true) # # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @api public def basic_ack(delivery_tag, multiple = false) guarding_against_stale_delivery_tags(delivery_tag) do raise_if_no_longer_open! @connection.send_frame(AMQ::Protocol::Basic::Ack.encode(@id, delivery_tag, multiple)) nil end end # Rejects or requeues messages just like {Bunny::Channel#basic_reject} but can do so # with multiple messages at once. # # @param [Integer] delivery_tag Delivery tag obtained from delivery info # @param [Boolean] requeue Should the message be requeued? # @param [Boolean] multiple Should all deliveries up to this one be rejected/requeued? # @return [NilClass] nil # # @example Requeue a message # conn = Bunny.new # conn.start # # ch = conn.create_channel # q.subscribe do |delivery_info, properties, payload| # # requeue the message # ch.basic_nack(delivery_info.delivery_tag, false, true) # end # # @example Reject a message # conn = Bunny.new # conn.start # # ch = conn.create_channel # q.subscribe do |delivery_info, properties, payload| # # requeue the message # ch.basic_nack(delivery_info.delivery_tag) # end # # @example Requeue a message fetched via basic.get # conn = Bunny.new # conn.start # # ch = conn.create_channel # # we assume the queue exists and has messages # delivery_info, properties, payload = ch.basic_get("bunny.examples.queue3", :manual_ack => true) # ch.basic_nack(delivery_info.delivery_tag, false, true) # # # @example Requeue multiple messages fetched via basic.get # conn = Bunny.new # conn.start # # ch = conn.create_channel # # we assume the queue exists and has messages # _, _, payload1 = ch.basic_get("bunny.examples.queue3", :manual_ack => true) # _, _, payload2 = ch.basic_get("bunny.examples.queue3", :manual_ack => true) # delivery_info, properties, payload3 = ch.basic_get("bunny.examples.queue3", :manual_ack => true) # # requeue all fetched messages up to payload3 # ch.basic_nack(delivery_info.delivery_tag, true, true) # # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide # @api public def basic_nack(delivery_tag, multiple = false, requeue = false) guarding_against_stale_delivery_tags(delivery_tag) do raise_if_no_longer_open! @connection.send_frame(AMQ::Protocol::Basic::Nack.encode(@id, delivery_tag, multiple, requeue)) nil end end # Registers a consumer for queue. Delivered messages will be handled with the block # provided to this method. # # @param [String, Bunny::Queue] queue Queue to consume from # @param [String] consumer_tag Consumer tag (unique identifier), generated by Bunny by default # @param [Boolean] no_ack (false) If true, delivered messages will be automatically acknowledged. # If false, manual acknowledgements will be necessary. # @param [Boolean] exclusive (false) Should this consumer be exclusive? # @param [Hash] arguments (nil) Optional arguments that may be used by RabbitMQ extensions, etc # # @return [AMQ::Protocol::Basic::ConsumeOk] RabbitMQ response # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @api public def basic_consume(queue, consumer_tag = generate_consumer_tag, no_ack = false, exclusive = false, arguments = nil, &block) raise_if_no_longer_open! maybe_start_consumer_work_pool! queue_name = if queue.respond_to?(:name) queue.name else queue end # helps avoid race condition between basic.consume-ok and basic.deliver if there are messages # in the queue already. MK. if consumer_tag && consumer_tag.strip != AMQ::Protocol::EMPTY_STRING add_consumer(queue_name, consumer_tag, no_ack, exclusive, arguments, &block) end @connection.send_frame(AMQ::Protocol::Basic::Consume.encode(@id, queue_name, consumer_tag, false, no_ack, exclusive, false, arguments)) begin with_continuation_timeout do @last_basic_consume_ok = wait_on_continuations end rescue Exception => e # if basic.consume-ok never arrives, unregister the proactively # registered consumer. MK. unregister_consumer(@last_basic_consume_ok.consumer_tag) raise e end # in case there is another exclusive consumer and we get a channel.close # response here. MK. raise_if_channel_close!(@last_basic_consume_ok) # covers server-generated consumer tags add_consumer(queue_name, @last_basic_consume_ok.consumer_tag, no_ack, exclusive, arguments, &block) @last_basic_consume_ok end alias consume basic_consume # Registers a consumer for queue as {Bunny::Consumer} instance. # # @param [Bunny::Consumer] consumer Consumer to register. It should already have queue name, consumer tag # and other attributes set. # # @return [AMQ::Protocol::Basic::ConsumeOk] RabbitMQ response # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @api public def basic_consume_with(consumer) raise_if_no_longer_open! maybe_start_consumer_work_pool! # helps avoid race condition between basic.consume-ok and basic.deliver if there are messages # in the queue already. MK. if consumer.consumer_tag && consumer.consumer_tag.strip != AMQ::Protocol::EMPTY_STRING register_consumer(consumer.consumer_tag, consumer) end @connection.send_frame(AMQ::Protocol::Basic::Consume.encode(@id, consumer.queue_name, consumer.consumer_tag, false, consumer.no_ack, consumer.exclusive, false, consumer.arguments)) begin with_continuation_timeout do @last_basic_consume_ok = wait_on_continuations end rescue Exception => e # if basic.consume-ok never arrives, unregister the proactively # registered consumer. MK. unregister_consumer(@last_basic_consume_ok.consumer_tag) raise e end # in case there is another exclusive consumer and we get a channel.close # response here. MK. raise_if_channel_close!(@last_basic_consume_ok) # covers server-generated consumer tags register_consumer(@last_basic_consume_ok.consumer_tag, consumer) raise_if_continuation_resulted_in_a_channel_error! @last_basic_consume_ok end alias consume_with basic_consume_with # Removes a consumer. Messages for this consumer will no longer be delivered. If the queue # it was on is auto-deleted and this consumer was the last one, the queue will be deleted. # # @param [String] consumer_tag Consumer tag (unique identifier) to cancel # # @return [AMQ::Protocol::Basic::CancelOk] RabbitMQ response # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @api public def basic_cancel(consumer_tag) @connection.send_frame(AMQ::Protocol::Basic::Cancel.encode(@id, consumer_tag, false)) with_continuation_timeout do @last_basic_cancel_ok = wait_on_continuations end # reduces thread usage for channels that don't have any # consumers @work_pool.shutdown(true) unless self.any_consumers? @last_basic_cancel_ok end # @return [Boolean] true if there are consumers on this channel # @api public def any_consumers? @consumer_mutex.synchronize { @consumers.any? } end # @endgroup # @group Queue operations (queue.*) # Declares a queue using queue.declare AMQP 0.9.1 method. # # @param [String] name The name of the queue or an empty string to let RabbitMQ generate a name. # Note that LF and CR characters will be stripped from the value. # @param [Hash] opts Queue properties # # @option opts [Boolean] durable (false) Should information about this queue be persisted to disk so that it # can survive broker restarts? Typically set to true for long-lived queues. # @option opts [Boolean] auto_delete (false) Should this queue be deleted when the last consumer is cancelled? # @option opts [Boolean] exclusive (false) Should only this connection be able to use this queue? # If true, the queue will be automatically deleted when this # connection is closed # @option opts [Boolean] passive (false) If true, queue will be checked for existence. If it does not # exist, {Bunny::NotFound} will be raised. # # @return [AMQ::Protocol::Queue::DeclareOk] RabbitMQ response # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @api public def queue_declare(name, opts = {}) raise_if_no_longer_open! # strip trailing new line and carriage returns # just like RabbitMQ does safe_name = name.gsub(/[\r\n]/, "") @pending_queue_declare_name = safe_name @connection.send_frame( AMQ::Protocol::Queue::Declare.encode(@id, @pending_queue_declare_name, opts.fetch(:passive, false), opts.fetch(:durable, false), opts.fetch(:exclusive, false), opts.fetch(:auto_delete, false), false, opts[:arguments])) begin with_continuation_timeout do @last_queue_declare_ok = wait_on_continuations end ensure # clear pending continuation context if it belongs to us @pending_queue_declare_name = nil if @pending_queue_declare_name == safe_name end raise_if_continuation_resulted_in_a_channel_error! @last_queue_declare_ok end # Deletes a queue using queue.delete AMQP 0.9.1 method # # @param [String] name Queue name # @param [Hash] opts Options # # @option opts [Boolean] if_unused (false) Should this queue be deleted only if it has no consumers? # @option opts [Boolean] if_empty (false) Should this queue be deleted only if it has no messages? # # @return [AMQ::Protocol::Queue::DeleteOk] RabbitMQ response # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @api public def queue_delete(name, opts = {}) raise_if_no_longer_open! @connection.send_frame(AMQ::Protocol::Queue::Delete.encode(@id, name, opts[:if_unused], opts[:if_empty], false)) with_continuation_timeout do @last_queue_delete_ok = wait_on_continuations end raise_if_continuation_resulted_in_a_channel_error! @last_queue_delete_ok end # Purges a queue (removes all messages from it) using queue.purge AMQP 0.9.1 method. # # @param [String] name Queue name # # @return [AMQ::Protocol::Queue::PurgeOk] RabbitMQ response # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @api public def queue_purge(name, opts = {}) raise_if_no_longer_open! @connection.send_frame(AMQ::Protocol::Queue::Purge.encode(@id, name, false)) with_continuation_timeout do @last_queue_purge_ok = wait_on_continuations end raise_if_continuation_resulted_in_a_channel_error! @last_queue_purge_ok end # Binds a queue to an exchange using queue.bind AMQP 0.9.1 method # # @param [String] name Queue name # @param [String] exchange Exchange name # @param [Hash] opts Options # # @option opts [String] routing_key (nil) Routing key used for binding # @option opts [Hash] arguments ({}) Optional arguments # # @return [AMQ::Protocol::Queue::BindOk] RabbitMQ response # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @see http://rubybunny.info/articles/bindings.html Bindings guide # @api public def queue_bind(name, exchange, opts = {}) raise_if_no_longer_open! exchange_name = if exchange.respond_to?(:name) exchange.name else exchange end @connection.send_frame(AMQ::Protocol::Queue::Bind.encode(@id, name, exchange_name, (opts[:routing_key] || opts[:key]), false, opts[:arguments])) with_continuation_timeout do @last_queue_bind_ok = wait_on_continuations end raise_if_continuation_resulted_in_a_channel_error! @last_queue_bind_ok end # Unbinds a queue from an exchange using queue.unbind AMQP 0.9.1 method # # @param [String] name Queue name # @param [String] exchange Exchange name # @param [Hash] opts Options # # @option opts [String] routing_key (nil) Routing key used for binding # @option opts [Hash] arguments ({}) Optional arguments # # @return [AMQ::Protocol::Queue::UnbindOk] RabbitMQ response # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @see http://rubybunny.info/articles/bindings.html Bindings guide # @api public def queue_unbind(name, exchange, opts = {}) raise_if_no_longer_open! exchange_name = if exchange.respond_to?(:name) exchange.name else exchange end @connection.send_frame(AMQ::Protocol::Queue::Unbind.encode(@id, name, exchange_name, opts[:routing_key], opts[:arguments])) with_continuation_timeout do @last_queue_unbind_ok = wait_on_continuations end raise_if_continuation_resulted_in_a_channel_error! @last_queue_unbind_ok end # @endgroup # @group Exchange operations (exchange.*) # Declares a exchange using exchange.declare AMQP 0.9.1 method. # # @param [String] name The name of the exchange. Note that LF and CR characters # will be stripped from the value. # @param [String,Symbol] type Exchange type, e.g. :fanout or :topic # @param [Hash] opts Exchange properties # # @option opts [Boolean] durable (false) Should information about this exchange be persisted to disk so that it # can survive broker restarts? Typically set to true for long-lived exchanges. # @option opts [Boolean] auto_delete (false) Should this exchange be deleted when it is no longer used? # @option opts [Boolean] passive (false) If true, exchange will be checked for existence. If it does not # exist, {Bunny::NotFound} will be raised. # # @return [AMQ::Protocol::Exchange::DeclareOk] RabbitMQ response # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide # @api public def exchange_declare(name, type, opts = {}) raise_if_no_longer_open! # strip trailing new line and carriage returns # just like RabbitMQ does safe_name = name.gsub(/[\r\n]/, "") @connection.send_frame(AMQ::Protocol::Exchange::Declare.encode(@id, safe_name, type.to_s, opts.fetch(:passive, false), opts.fetch(:durable, false), opts.fetch(:auto_delete, false), opts.fetch(:internal, false), false, # nowait opts[:arguments])) with_continuation_timeout do @last_exchange_declare_ok = wait_on_continuations end raise_if_continuation_resulted_in_a_channel_error! @last_exchange_declare_ok end # Deletes a exchange using exchange.delete AMQP 0.9.1 method # # @param [String] name Exchange name # @param [Hash] opts Options # # @option opts [Boolean] if_unused (false) Should this exchange be deleted only if it is no longer used # # @return [AMQ::Protocol::Exchange::DeleteOk] RabbitMQ response # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide # @api public def exchange_delete(name, opts = {}) raise_if_no_longer_open! @connection.send_frame(AMQ::Protocol::Exchange::Delete.encode(@id, name, opts[:if_unused], false)) with_continuation_timeout do @last_exchange_delete_ok = wait_on_continuations end raise_if_continuation_resulted_in_a_channel_error! @last_exchange_delete_ok end # Binds an exchange to another exchange using exchange.bind AMQP 0.9.1 extension # that RabbitMQ provides. # # @param [String] source Source exchange name # @param [String] destination Destination exchange name # @param [Hash] opts Options # # @option opts [String] routing_key (nil) Routing key used for binding # @option opts [Hash] arguments ({}) Optional arguments # # @return [AMQ::Protocol::Exchange::BindOk] RabbitMQ response # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide # @see http://rubybunny.info/articles/bindings.html Bindings guide # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide # @api public def exchange_bind(source, destination, opts = {}) raise_if_no_longer_open! source_name = if source.respond_to?(:name) source.name else source end destination_name = if destination.respond_to?(:name) destination.name else destination end @connection.send_frame(AMQ::Protocol::Exchange::Bind.encode(@id, destination_name, source_name, opts[:routing_key], false, opts[:arguments])) with_continuation_timeout do @last_exchange_bind_ok = wait_on_continuations end raise_if_continuation_resulted_in_a_channel_error! @last_exchange_bind_ok end # Unbinds an exchange from another exchange using exchange.unbind AMQP 0.9.1 extension # that RabbitMQ provides. # # @param [String] source Source exchange name # @param [String] destination Destination exchange name # @param [Hash] opts Options # # @option opts [String] routing_key (nil) Routing key used for binding # @option opts [Hash] arguments ({}) Optional arguments # # @return [AMQ::Protocol::Exchange::UnbindOk] RabbitMQ response # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide # @see http://rubybunny.info/articles/bindings.html Bindings guide # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide # @api public def exchange_unbind(source, destination, opts = {}) raise_if_no_longer_open! source_name = if source.respond_to?(:name) source.name else source end destination_name = if destination.respond_to?(:name) destination.name else destination end @connection.send_frame(AMQ::Protocol::Exchange::Unbind.encode(@id, destination_name, source_name, opts[:routing_key], false, opts[:arguments])) with_continuation_timeout do @last_exchange_unbind_ok = wait_on_continuations end raise_if_continuation_resulted_in_a_channel_error! @last_exchange_unbind_ok end # @endgroup # @group Flow control (channel.*) # Enables or disables message flow for the channel. When message flow is disabled, # no new messages will be delivered to consumers on this channel. This is typically # used by consumers that cannot keep up with the influx of messages. # # @note Recent (e.g. 2.8.x., 3.x) RabbitMQ will employ TCP/IP-level back pressure on publishers if it detects # that consumers do not keep up with them. # # @return [AMQ::Protocol::Channel::FlowOk] RabbitMQ response # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @api public def channel_flow(active) raise_if_no_longer_open! @connection.send_frame(AMQ::Protocol::Channel::Flow.encode(@id, active)) with_continuation_timeout do @last_channel_flow_ok = wait_on_continuations end raise_if_continuation_resulted_in_a_channel_error! @last_channel_flow_ok end # @endgroup # @group Transactions (tx.*) # Puts the channel into transaction mode (starts a transaction) # @return [AMQ::Protocol::Tx::SelectOk] RabbitMQ response # @api public def tx_select raise_if_no_longer_open! @connection.send_frame(AMQ::Protocol::Tx::Select.encode(@id)) with_continuation_timeout do @last_tx_select_ok = wait_on_continuations end raise_if_continuation_resulted_in_a_channel_error! @tx_mode = true @last_tx_select_ok end # Commits current transaction # @return [AMQ::Protocol::Tx::CommitOk] RabbitMQ response # @api public def tx_commit raise_if_no_longer_open! @connection.send_frame(AMQ::Protocol::Tx::Commit.encode(@id)) with_continuation_timeout do @last_tx_commit_ok = wait_on_continuations end raise_if_continuation_resulted_in_a_channel_error! @last_tx_commit_ok end # Rolls back current transaction # @return [AMQ::Protocol::Tx::RollbackOk] RabbitMQ response # @api public def tx_rollback raise_if_no_longer_open! @connection.send_frame(AMQ::Protocol::Tx::Rollback.encode(@id)) with_continuation_timeout do @last_tx_rollback_ok = wait_on_continuations end raise_if_continuation_resulted_in_a_channel_error! @last_tx_rollback_ok end # @return [Boolean] true if this channel has transactions enabled def using_tx? !!@tx_mode end # @endgroup # @group Publisher Confirms (confirm.*) # @return [Boolean] true if this channel has Publisher Confirms enabled, false otherwise # @api public def using_publisher_confirmations? @next_publish_seq_no > 0 end alias using_publisher_confirms? using_publisher_confirmations? # Enables publisher confirms for the channel. # @return [AMQ::Protocol::Confirm::SelectOk] RabbitMQ response # @see #wait_for_confirms # @see #unconfirmed_set # @see #nacked_set # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide # @api public def confirm_select(callback = nil) raise_if_no_longer_open! if @next_publish_seq_no == 0 @confirms_continuations = new_continuation @unconfirmed_set = Set.new @nacked_set = Set.new @next_publish_seq_no = 1 @only_acks_received = true end @confirms_callback = callback @connection.send_frame(AMQ::Protocol::Confirm::Select.encode(@id, false)) with_continuation_timeout do @last_confirm_select_ok = wait_on_continuations end raise_if_continuation_resulted_in_a_channel_error! @last_confirm_select_ok end # Blocks calling thread until confirms are received for all # currently unacknowledged published messages. Returns immediately # if there are no outstanding confirms. # # @return [Boolean] true if all messages were acknowledged positively since the last time this method was called, false otherwise # @see #confirm_select # @see #unconfirmed_set # @see #nacked_set # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide # @api public def wait_for_confirms wait_on_confirms_continuations read_and_reset_only_acks_received end # @endgroup # @group Misc # Synchronizes given block using this channel's mutex. # @api public def synchronize(&block) @publishing_mutex.synchronize(&block) end # Unique string supposed to be used as a consumer tag. # # @return [String] Unique string. # @api plugin def generate_consumer_tag(name = "bunny") t = Bunny::Timestamp.now "#{name}-#{t.to_i * 1000}-#{Kernel.rand(999_999_999_999)}" end # @endgroup # # Error Handilng # # Defines a handler for errors that are not responses to a particular # operations (e.g. basic.ack, basic.reject, basic.nack). # # @api public def on_error(&block) @on_error = block end # Defines a handler for uncaught exceptions in consumers # (e.g. delivered message handlers). # # @api public def on_uncaught_exception(&block) @uncaught_exception_handler = block end # # Recovery # # @group Network Failure Recovery # Recovers basic.qos setting, exchanges, queues and consumers. Used by the Automatic Network Failure # Recovery feature. # # @api plugin def recover_from_network_failure @logger.debug { "Recovering channel #{@id} after network failure" } release_all_continuations recover_prefetch_setting recover_confirm_mode recover_tx_mode recover_exchanges # this includes recovering bindings recover_queues recover_consumers increment_recoveries_counter end # Recovers basic.qos setting. Used by the Automatic Network Failure # Recovery feature. # # @api plugin def recover_prefetch_setting basic_qos(@prefetch_count, @prefetch_global) if @prefetch_count end # Recovers publisher confirms mode. Used by the Automatic Network Failure # Recovery feature. # Set the offset to the previous publish sequence index as the protocol will reset the index to after recovery. # # @api plugin def recover_confirm_mode if using_publisher_confirmations? @unconfirmed_set_mutex.synchronize do @unconfirmed_set.clear @delivery_tag_offset = @next_publish_seq_no - 1 end confirm_select(@confirms_callback) end end # Recovers transaction mode. Used by the Automatic Network Failure # Recovery feature. # # @api plugin def recover_tx_mode tx_select if @tx_mode end # Recovers exchanges. Used by the Automatic Network Failure # Recovery feature. # # @api plugin def recover_exchanges @exchange_mutex.synchronize { @exchanges.values }.each do |x| x.recover_from_network_failure end end # Recovers queues and bindings. Used by the Automatic Network Failure # Recovery feature. # # @api plugin def recover_queues @queue_mutex.synchronize { @queues.values }.each do |q| @logger.debug { "Recovering queue #{q.name}" } q.recover_from_network_failure end end # Recovers consumers. Used by the Automatic Network Failure # Recovery feature. # # @api plugin def recover_consumers unless @consumers.empty? @work_pool = ConsumerWorkPool.new(@work_pool.size, @work_pool.abort_on_exception) @work_pool.start end @consumer_mutex.synchronize { @consumers.values }.each do |c| c.recover_from_network_failure end end # @private def increment_recoveries_counter @recoveries_counter.increment end # @api public def recover_cancelled_consumers! @recover_cancelled_consumers = true end # @api public def recovers_cancelled_consumers? !!@recover_cancelled_consumers end # @endgroup # @return [String] Brief human-readable representation of the channel def to_s "#<#{self.class.name}:#{object_id} @id=#{self.number} @connection=#{@connection.to_s} @open=#{open?}>" end def inspect to_s end # # Implementation # # @private def with_continuation_timeout(&block) Bunny::Timeout.timeout(wait_on_continuations_timeout, ClientTimeout, &block) end # @private def register_consumer(consumer_tag, consumer) @consumer_mutex.synchronize do @consumers[consumer_tag] = consumer end end # @private def unregister_consumer(consumer_tag) @consumer_mutex.synchronize do @consumers.delete(consumer_tag) end end # @private def add_consumer(queue, consumer_tag, no_ack, exclusive, arguments, &block) @consumer_mutex.synchronize do c = Consumer.new(self, queue, consumer_tag, no_ack, exclusive, arguments) c.on_delivery(&block) if block @consumers[consumer_tag] = c end end # @private def pending_server_named_queue_declaration? @pending_queue_declare_name && @pending_queue_declare_name.empty? end # @private def can_accept_queue_declare_ok?(method) @pending_queue_declare_name == method.queue || pending_server_named_queue_declaration? end # @private def handle_method(method) @logger.debug { "Channel#handle_frame on channel #{@id}: #{method.inspect}" } case method when AMQ::Protocol::Queue::DeclareOk then # safeguard against late arrivals of responses and # so on, see ruby-amqp/bunny#558 if can_accept_queue_declare_ok?(method) @continuations.push(method) else if !pending_server_named_queue_declaration? # this response is for an outdated/overwritten # queue.declare, drop it @logger.warn "Received a queue.declare-ok response for a mismatching queue (#{method.queue} instead of #{@pending_queue_declare_name}) on channel #{@id}, possibly due to concurrent channel use or a timeout, ignoring it" end end when AMQ::Protocol::Queue::DeleteOk then @continuations.push(method) when AMQ::Protocol::Queue::PurgeOk then @continuations.push(method) when AMQ::Protocol::Queue::BindOk then @continuations.push(method) when AMQ::Protocol::Queue::UnbindOk then @continuations.push(method) when AMQ::Protocol::Exchange::BindOk then @continuations.push(method) when AMQ::Protocol::Exchange::UnbindOk then @continuations.push(method) when AMQ::Protocol::Exchange::DeclareOk then @continuations.push(method) when AMQ::Protocol::Exchange::DeleteOk then @continuations.push(method) when AMQ::Protocol::Basic::QosOk then @continuations.push(method) when AMQ::Protocol::Basic::RecoverOk then @continuations.push(method) when AMQ::Protocol::Channel::FlowOk then @continuations.push(method) when AMQ::Protocol::Basic::ConsumeOk then @continuations.push(method) when AMQ::Protocol::Basic::Cancel then if consumer = @consumers[method.consumer_tag] @work_pool.submit do begin if recovers_cancelled_consumers? consumer.handle_cancellation(method) @logger.info "Automatically recovering cancelled consumer #{consumer.consumer_tag} on queue #{consumer.queue_name}" consume_with(consumer) else @consumers.delete(method.consumer_tag) consumer.handle_cancellation(method) end rescue Exception => e @logger.error "Got exception when notifying consumer #{method.consumer_tag} about cancellation!" @uncaught_exception_handler.call(e, consumer) if @uncaught_exception_handler end end else @logger.warn "No consumer for tag #{method.consumer_tag} on channel #{@id}!" end when AMQ::Protocol::Basic::CancelOk then @continuations.push(method) unregister_consumer(method.consumer_tag) when AMQ::Protocol::Tx::SelectOk, AMQ::Protocol::Tx::CommitOk, AMQ::Protocol::Tx::RollbackOk then @continuations.push(method) when AMQ::Protocol::Tx::SelectOk then @continuations.push(method) when AMQ::Protocol::Confirm::SelectOk then @continuations.push(method) when AMQ::Protocol::Basic::Ack then handle_ack_or_nack(method.delivery_tag, method.multiple, false) when AMQ::Protocol::Basic::Nack then handle_ack_or_nack(method.delivery_tag, method.multiple, true) when AMQ::Protocol::Channel::Close then closed! @connection.send_frame(AMQ::Protocol::Channel::CloseOk.encode(@id)) # basic.ack, basic.reject, basic.nack. MK. if channel_level_exception_after_operation_that_has_no_response?(method) @on_error.call(self, method) if @on_error else @last_channel_error = instantiate_channel_level_exception(method) @continuations.push(method) end when AMQ::Protocol::Channel::CloseOk then @continuations.push(method) else raise "Do not know how to handle #{method.inspect} in Bunny::Channel#handle_method" end end # @private def channel_level_exception_after_operation_that_has_no_response?(method) method.reply_code == 406 && (method.reply_text =~ /unknown delivery tag/ || method.reply_text =~ /delivery acknowledgement on channel \d+ timed out/) end # @private def handle_basic_get_ok(basic_get_ok, properties, content) basic_get_ok.delivery_tag = VersionedDeliveryTag.new(basic_get_ok.delivery_tag, @recoveries_counter.get) @basic_get_continuations.push([basic_get_ok, properties, content]) end # @private def handle_basic_get_empty(basic_get_empty) @basic_get_continuations.push([nil, nil, nil]) end # @private def handle_frameset(basic_deliver, properties, content) consumer = @consumers[basic_deliver.consumer_tag] if consumer @work_pool.submit do begin consumer.call(DeliveryInfo.new(basic_deliver, consumer, self), MessageProperties.new(properties), content) rescue StandardError => e @uncaught_exception_handler.call(e, consumer) if @uncaught_exception_handler end end else @logger.warn "No consumer for tag #{basic_deliver.consumer_tag} on channel #{@id}!" end end # @private def handle_basic_return(basic_return, properties, content) x = find_exchange(basic_return.exchange) if x x.handle_return(ReturnInfo.new(basic_return), MessageProperties.new(properties), content) else @logger.warn "Exchange #{basic_return.exchange} is not in channel #{@id}'s cache! Dropping returned message!" end end # Handle delivery tag offset calculations to keep the the delivery tag monotonic after a reset # due to automatic network failure recovery. @unconfirmed_set contains indices already offsetted. # @private def handle_ack_or_nack(delivery_tag_before_offset, multiple, nack) @unconfirmed_set_mutex.synchronize do delivery_tag = delivery_tag_before_offset + @delivery_tag_offset confirmed_range_start = multiple ? @unconfirmed_set.min : delivery_tag confirmed_range_end = delivery_tag confirmed_range = (confirmed_range_start..confirmed_range_end) if nack @nacked_set.merge(@unconfirmed_set & confirmed_range) end @unconfirmed_set.subtract(confirmed_range) @only_acks_received = (@only_acks_received && !nack) @confirms_continuations.push(true) if @unconfirmed_set.empty? if @confirms_callback confirmed_range.each { |tag| @confirms_callback.call(tag, false, nack) } end end end # @private def wait_on_continuations if @connection.threaded t = Thread.current @threads_waiting_on_continuations << t begin @continuations.poll(@connection.continuation_timeout) ensure @threads_waiting_on_continuations.delete(t) end else connection.reader_loop.run_once until @continuations.length > 0 @continuations.pop end end # @private def wait_on_basic_get_continuations if @connection.threaded t = Thread.current @threads_waiting_on_basic_get_continuations << t begin @basic_get_continuations.poll(@connection.continuation_timeout) ensure @threads_waiting_on_basic_get_continuations.delete(t) end else connection.reader_loop.run_once until @basic_get_continuations.length > 0 @basic_get_continuations.pop end end # @private def wait_on_confirms_continuations raise_if_no_longer_open! if @connection.threaded t = Thread.current @threads_waiting_on_confirms_continuations << t begin while @unconfirmed_set_mutex.synchronize { !@unconfirmed_set.empty? } @confirms_continuations.poll(@connection.continuation_timeout) end ensure @threads_waiting_on_confirms_continuations.delete(t) end else unless @unconfirmed_set.empty? connection.reader_loop.run_once until @confirms_continuations.length > 0 @confirms_continuations.pop end end end # @private def read_and_reset_only_acks_received @unconfirmed_set_mutex.synchronize do result = @only_acks_received @only_acks_received = true result end end # Releases all continuations. Used by automatic network recovery. # @private def release_all_continuations @threads_waiting_on_confirms_continuations.each do |t| t.run end @threads_waiting_on_continuations.each do |t| t.run end @threads_waiting_on_basic_get_continuations.each do |t| t.run end self.reset_continuations end # Starts consumer work pool. Lazily called by #basic_consume to avoid creating new threads # that won't do any real work for channels that do not register consumers (e.g. only used for # publishing). MK. # @private def maybe_start_consumer_work_pool! if @work_pool && !@work_pool.running? @work_pool.start end end # @private def maybe_pause_consumer_work_pool! @work_pool.pause if @work_pool && @work_pool.running? end # @private def maybe_kill_consumer_work_pool! if @work_pool && @work_pool.running? @work_pool.kill end end # @private def read_next_frame(options = {}) @connection.read_next_frame(options = {}) end # @private def deregister_queue(queue) @queue_mutex.synchronize { @queues.delete(queue.name) } end # @private def deregister_queue_named(name) @queue_mutex.synchronize { @queues.delete(name) } end # @private def register_queue(queue) @queue_mutex.synchronize { @queues[queue.name] = queue } end # @private def find_queue(name) @queue_mutex.synchronize { @queues[name] } end # @private def deregister_exchange(exchange) @exchange_mutex.synchronize { @exchanges.delete(exchange.name) } end # @private def register_exchange(exchange) @exchange_mutex.synchronize { @exchanges[exchange.name] = exchange } end # @private def find_exchange(name) @exchange_mutex.synchronize { @exchanges[name] } end protected # @private def closed! @status = :closed @work_pool.shutdown @connection.release_channel_id(@id) end # @private def instantiate_channel_level_exception(frame) case frame when AMQ::Protocol::Channel::Close then klass = case frame.reply_code when 403 then AccessRefused when 404 then NotFound when 405 then ResourceLocked when 406 then PreconditionFailed else ChannelLevelException end klass.new(frame.reply_text, self, frame) end end # @private def raise_if_continuation_resulted_in_a_channel_error! raise @last_channel_error if @last_channel_error end # @private def raise_if_no_longer_open! if closed? if @last_channel_error raise ChannelAlreadyClosed.new("cannot use a closed channel! Channel id: #{@id}, closed due to a server-reported channel error: #{@last_channel_error.message}", self) else raise ChannelAlreadyClosed.new("cannot use a closed channel! Channel id: #{@id}", self) end end end # @private def raise_if_channel_close!(method) if method && method.is_a?(AMQ::Protocol::Channel::Close) # basic.ack, basic.reject, basic.nack. MK. if channel_level_exception_after_operation_that_has_no_response?(method) @on_error.call(self, method) if @on_error else @last_channel_error = instantiate_channel_level_exception(method) raise @last_channel_error end end end # @private def reset_continuations @continuations = new_continuation @confirms_continuations = new_continuation @basic_get_continuations = new_continuation end if defined?(JRUBY_VERSION) # @private def new_continuation Concurrent::LinkedContinuationQueue.new end else # @private def new_continuation Concurrent::ContinuationQueue.new end end # if defined? # @private def guarding_against_stale_delivery_tags(tag, &block) case tag # if a fixnum was passed, execute unconditionally. MK. when Integer then block.call # versioned delivery tags should be checked to avoid # sending out stale (invalid) tags after channel was reopened # during network failure recovery. MK. when VersionedDeliveryTag then if !tag.stale?(@recoveries_counter.get) block.call end end end end # Channel end # Bunny ruby-amqp-bunny-b6569cd/lib/bunny/channel_id_allocator.rb000066400000000000000000000042101464043542000235550ustar00rootroot00000000000000require "thread" require "monitor" require "amq/int_allocator" module Bunny # Bitset-based channel id allocator. When channels are closed, # ids are released back to the pool. # # Every connection has its own allocator. # # Allocating and releasing ids is synchronized and can be performed # from multiple threads. class ChannelIdAllocator # # API # # @param [Integer] max_channel Max allowed channel id def initialize(max_channel = ((1 << 11) - 1)) # channel 0 has special meaning in the protocol, so start # allocator at 1 @allocator = AMQ::IntAllocator.new(1, max_channel) @mutex = Monitor.new end # Returns next available channel id. This method is thread safe. # # @return [Integer] # @api public # @see ChannelManager#release_channel_id # @see ChannelManager#reset_channel_id_allocator def next_channel_id @mutex.synchronize do @allocator.allocate end end # Releases previously allocated channel id. This method is thread safe. # # @param [Integer] i Channel id to release # @api public # @see ChannelManager#next_channel_id # @see ChannelManager#reset_channel_id_allocator def release_channel_id(i) @mutex.synchronize do @allocator.release(i) end end # Returns true if given channel id has been previously allocated and not yet released. # This method is thread safe. # # @param [Integer] i Channel id to check # @return [Boolean] true if given channel id has been previously allocated and not yet released # @api public # @see ChannelManager#next_channel_id # @see ChannelManager#release_channel_id def allocated_channel_id?(i) @mutex.synchronize do @allocator.allocated?(i) end end # Resets channel allocator. This method is thread safe. # @api public # @see Channel.next_channel_id # @see Channel.release_channel_id def reset_channel_id_allocator @mutex.synchronize do @allocator.reset end end # @private def synchronize(&block) @mutex.synchronize(&block) end end end ruby-amqp-bunny-b6569cd/lib/bunny/concurrent/000077500000000000000000000000001464043542000212715ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/lib/bunny/concurrent/atomic_fixnum.rb000066400000000000000000000025201464043542000244570ustar00rootroot00000000000000require "set" require "thread" require "monitor" module Bunny module Concurrent # Minimalistic implementation of a synchronized fixnum value, # designed after (but not implementing the entire API of!) # # @note Designed to be intentionally minimalistic and only cover Bunny's needs. # # @api public class AtomicFixnum def initialize(n = 0) @n = n @mutex = Monitor.new end def get @mutex.synchronize do @n end end alias to_i get def set(n) @mutex.synchronize do @n = n end end def increment @mutex.synchronize do @n = @n + 1 end end alias inc increment alias increment_and_get increment def get_and_add(i) @mutex.synchronize do v = @n @n = @n + i v end end def get_and_increment @mutex.synchronize do v = @n @n = @n + 1 v end end def decrement @mutex.synchronize do @n = @n - 1 end end alias dec decrement alias decrement_and_get decrement def ==(m) @mutex.synchronize { @n == m } end def ===(v) @mutex.synchronize { @n === v } end end end end ruby-amqp-bunny-b6569cd/lib/bunny/concurrent/condition.rb000066400000000000000000000030331464043542000236030ustar00rootroot00000000000000require "thread" require "monitor" module Bunny # @private module Concurrent # Akin to java.util.concurrent.Condition and intrinsic object monitors (Object#wait, Object#notify, Object#notifyAll) in Java: # threads can wait (block until notified) on a condition other threads notify them about. # Unlike the j.u.c. version, this one has a single waiting set. # # Conditions can optionally be annotated with a description string for ease of debugging. # @private class Condition attr_reader :waiting_threads, :description def initialize(description = nil) @mutex = Monitor.new @waiting_threads = [] @description = description end def wait @mutex.synchronize do t = Thread.current @waiting_threads.push(t) end Thread.stop end def notify @mutex.synchronize do t = @waiting_threads.shift begin t.run if t rescue ThreadError retry end end end def notify_all @mutex.synchronize do @waiting_threads.each do |t| t.run end @waiting_threads.clear end end def waiting_set_size @mutex.synchronize { @waiting_threads.size } end def any_threads_waiting? @mutex.synchronize { !@waiting_threads.empty? } end def none_threads_waiting? @mutex.synchronize { @waiting_threads.empty? } end end end end ruby-amqp-bunny-b6569cd/lib/bunny/concurrent/continuation_queue.rb000066400000000000000000000023461464043542000255410ustar00rootroot00000000000000require "thread" module Bunny module Concurrent # Continuation queue implementation for MRI and Rubinius # # @private class ContinuationQueue def initialize @q = [] @lock = ::Mutex.new @cond = ::ConditionVariable.new end def push(item) @lock.synchronize do @q.push(item) @cond.signal end end alias << push def pop poll end def poll(timeout_in_ms = nil) timeout_in_sec = timeout_in_ms ? timeout_in_ms / 1000.0 : nil @lock.synchronize do started_at = Bunny::Timestamp.monotonic while @q.empty? wait = !(timeout_in_sec.nil?) @cond.wait(@lock, timeout_in_sec) if wait ended_at = Bunny::Timestamp.monotonic elapsed = ended_at - started_at raise ::Timeout::Error if (elapsed > timeout_in_sec) end end item = @q.shift item end end def clear @lock.synchronize do @q.clear end end def empty? @q.empty? end def size @q.size end alias length size end end end ruby-amqp-bunny-b6569cd/lib/bunny/concurrent/linked_continuation_queue.rb000066400000000000000000000031421464043542000270620ustar00rootroot00000000000000if !defined?(JRUBY_VERSION) raise "Bunny::Concurrent::LinkedContinuationQueue can only be used on JRuby!" end require "java" java_import java.util.concurrent.LinkedBlockingQueue java_import java.util.concurrent.TimeUnit module Bunny module Concurrent # Continuation queue implementation for JRuby. # # On JRuby, we'd rather use reliable and heavily battle tested j.u.c. # primitives with well described semantics than informally specified, clumsy # and limited Ruby standard library parts. # # This is an implementation of the continuation queue on top of the linked blocking # queue in j.u.c. # # Compared to the Ruby standard library Queue, there is one limitation: you cannot # push a nil on the queue, it will fail with a null pointer exception. # @private class LinkedContinuationQueue def initialize(*args, &block) @q = LinkedBlockingQueue.new end def push(el, timeout_in_ms = nil) if timeout_in_ms @q.offer(el, timeout_in_ms, TimeUnit::MILLISECONDS) else @q.offer(el) end end alias << push def pop @q.take end def poll(timeout_in_ms = nil) if timeout_in_ms v = @q.poll(timeout_in_ms, TimeUnit::MILLISECONDS) raise ::Timeout::Error.new("operation did not finish in #{timeout_in_ms} ms") if v.nil? v else @q.poll end end def clear @q.clear end def method_missing(selector, *args, &block) @q.__send__(selector, *args, &block) end end end end ruby-amqp-bunny-b6569cd/lib/bunny/concurrent/synchronized_sorted_set.rb000066400000000000000000000021131464043542000265650ustar00rootroot00000000000000require "set" require "thread" module Bunny module Concurrent # A SortedSet variation that synchronizes key mutation operations. # # @note This is NOT a complete SortedSet replacement. It only synchronizes operations needed by Bunny. # @api public class SynchronizedSortedSet < SortedSet def initialize(enum = nil) @mutex = Mutex.new super end def add(o) # avoid using Mutex#synchronize because of a Ruby 1.8.7-specific # bug that prevents super from being called from within a block. MK. @mutex.lock begin super ensure @mutex.unlock end end def delete(o) @mutex.lock begin super ensure @mutex.unlock end end def delete_if(&block) @mutex.lock begin super ensure @mutex.unlock end end def include?(o) @mutex.lock begin super ensure @mutex.unlock end end end end end ruby-amqp-bunny-b6569cd/lib/bunny/consumer.rb000066400000000000000000000103761464043542000212760ustar00rootroot00000000000000module Bunny # Base class that represents consumer interface. Subclasses of this class implement # specific logic of handling consumer life cycle events. Note that when the only event # you are interested in is message deliveries, it is recommended to just use # {Bunny::Queue#subscribe} instead of subclassing this class. # # @see Bunny::Queue#subscribe # @see Bunny::Queue#subscribe_with # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @api public class Consumer # # API # attr_reader :channel attr_reader :queue attr_accessor :consumer_tag attr_reader :arguments attr_reader :no_ack attr_reader :exclusive # @param [Bunny::Channel] channel Channel this consumer will use # @param [Bunny::Queue,String] queue Queue messages will be consumed from # @param [String] consumer_tag Consumer tag (unique identifier). Generally it is better to let Bunny generate one. # Empty string means RabbitMQ will generate consumer tag. # @param [Boolean] no_ack (true) If true, delivered messages will be automatically acknowledged. # If false, manual acknowledgements will be necessary. # @param [Boolean] exclusive (false) Should this consumer be exclusive? # @param [Hash] arguments (nil) Optional arguments that may be used by RabbitMQ extensions, etc # @api public def initialize(channel, queue, consumer_tag = channel.generate_consumer_tag, no_ack = true, exclusive = false, arguments = {}) @channel = channel || raise(ArgumentError, "channel is nil") @queue = queue || raise(ArgumentError, "queue is nil") @consumer_tag = consumer_tag @exclusive = exclusive @arguments = arguments # no_ack set to true = no manual ack = automatic ack. MK. @no_ack = no_ack @on_cancellation = [] end # Defines message delivery handler # @api public def on_delivery(&block) @on_delivery = block self end # Invokes message delivery handler # @private def call(*args) @on_delivery.call(*args) if @on_delivery end alias handle_delivery call # Defines consumer cancellation notification handler # # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide # @api public def on_cancellation(&block) @on_cancellation << block self end # Invokes consumer cancellation notification handler # @private def handle_cancellation(basic_cancel) @on_cancellation.each do |fn| fn.call(basic_cancel) end end # Cancels this consumer. Messages for this consumer will no longer be delivered. If the queue # it was on is auto-deleted and this consumer was the last one, the queue will be deleted. # # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @api public def cancel @channel.basic_cancel(@consumer_tag) end # @return [String] More detailed human-readable string representation of this consumer def inspect "#<#{self.class.name}:#{object_id} @channel_id=#{@channel.number} @queue=#{self.queue_name} @consumer_tag=#{@consumer_tag} @exclusive=#{@exclusive} @no_ack=#{@no_ack}>" end # @return [String] Brief human-readable string representation of this consumer def to_s "#<#{self.class.name}:#{object_id} @channel_id=#{@channel.number} @queue=#{self.queue_name} @consumer_tag=#{@consumer_tag}>" end # @return [Boolean] true if this consumer uses automatic acknowledgement mode # @api public def automatic_acknowledgement? @no_ack == true end # @return [Boolean] true if this consumer uses manual (explicit) acknowledgement mode # @api public def manual_acknowledgement? @no_ack == false end # @return [String] Name of the queue this consumer is on # @api public def queue_name if @queue.respond_to?(:name) @queue.name else @queue end end # # Recovery # # @private def recover_from_network_failure @channel.basic_consume_with(self) end end end ruby-amqp-bunny-b6569cd/lib/bunny/consumer_tag_generator.rb000066400000000000000000000011041464043542000241640ustar00rootroot00000000000000module Bunny # Used to generate consumer tags in the client class ConsumerTagGenerator # # API # # @return [String] Generated consumer tag def generate t = Bunny::Timestamp.now "#{Kernel.rand}-#{t.to_i * 1000}-#{Kernel.rand(999_999_999_999)}" end # generate # Unique string supposed to be used as a consumer tag. # # @return [String] Unique string. # @api public def generate_prefixed(name = "bunny") t = Bunny::Timestamp.now "#{name}-#{t.to_i * 1000}-#{Kernel.rand(999_999_999_999)}" end end end ruby-amqp-bunny-b6569cd/lib/bunny/consumer_work_pool.rb000066400000000000000000000045061464043542000233670ustar00rootroot00000000000000require "thread" module Bunny # Thread pool that dispatches consumer deliveries. Not supposed to be shared between channels # or threads. # # Every channel its own consumer pool. # # @private class ConsumerWorkPool # # API # attr_reader :threads attr_reader :size attr_reader :abort_on_exception def initialize(size = 1, abort_on_exception = false, shutdown_timeout = 60) @size = size @abort_on_exception = abort_on_exception @shutdown_timeout = shutdown_timeout @shutdown_mutex = ::Mutex.new @shutdown_conditional = ::ConditionVariable.new @queue = ::Queue.new @paused = false @running = false end def submit(callable = nil, &block) @queue.push(callable || block) end def start @threads = [] @size.times do t = Thread.new(&method(:run_loop)) t.abort_on_exception = true if abort_on_exception @threads << t end @running = true end def running? @running end def backlog @queue.length end def busy? !@queue.empty? end def shutdown(wait_for_workers = false) was_running = running? @running = false @size.times do submit do |*args| throw :terminate end end return if !(wait_for_workers && @shutdown_timeout && was_running) @shutdown_mutex.synchronize do @shutdown_conditional.wait(@shutdown_mutex, @shutdown_timeout) if busy? end end def join(timeout = nil) (@threads || []).each { |t| t.join(timeout) } end def pause @running = false @paused = true end def resume @running = true @paused = false @threads.each { |t| t.run } end def kill @running = false (@threads || []).each { |t| t.kill } end protected def run_loop catch(:terminate) do loop do Thread.stop if @paused callable = @queue.pop begin callable.call rescue ::StandardError => e # TODO: use connection logger $stderr.puts e.class.name $stderr.puts e.message end end end @shutdown_mutex.synchronize do @shutdown_conditional.signal unless busy? end end end end ruby-amqp-bunny-b6569cd/lib/bunny/cruby/000077500000000000000000000000001464043542000202335ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/lib/bunny/cruby/socket.rb000066400000000000000000000073441464043542000220600ustar00rootroot00000000000000require "socket" module Bunny # TCP socket extension that uses TCP_NODELAY and supports reading # fully. # # Heavily inspired by Dalli by Mike Perham. # @private module Socket attr_accessor :options READ_RETRY_EXCEPTION_CLASSES = if defined?(IO::EAGAINWaitReadable) # Ruby 2.1+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable, IO::EAGAINWaitReadable, IO::EWOULDBLOCKWaitReadable] else # 2.0 [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable] end WRITE_RETRY_EXCEPTION_CLASSES = if defined?(IO::EAGAINWaitWritable) [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable, IO::EAGAINWaitWritable, IO::EWOULDBLOCKWaitWritable] else # 2.0 [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable] end def self.open(host, port, options = {}) socket = ::Socket.tcp(host, port, nil, nil, connect_timeout: options[:connect_timeout]) if ::Socket.constants.include?('TCP_NODELAY') || ::Socket.constants.include?(:TCP_NODELAY) socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true) end socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true) if options.fetch(:keepalive, true) socket.instance_eval do @__bunny_socket_eof_flag__ = false end socket.extend self socket.options = { :host => host, :port => port }.merge(options) socket rescue Errno::ETIMEDOUT raise ClientTimeout end # Reads given number of bytes with an optional timeout # # @param [Integer] count How many bytes to read # @param [Integer] timeout Timeout # # @return [String] Data read from the socket # @api public def read_fully(count, timeout = nil) return nil if @__bunny_socket_eof_flag__ value = '' begin loop do value << read_nonblock(count - value.bytesize) break if value.bytesize >= count end rescue EOFError # @eof will break Rubinius' TCPSocket implementation. MK. @__bunny_socket_eof_flag__ = true rescue *READ_RETRY_EXCEPTION_CLASSES if IO.select([self], nil, nil, timeout) retry else raise Timeout::Error, "IO timeout when reading #{count} bytes" end end value end # read_fully # Writes provided data using IO#write_nonblock, taking care of handling # of exceptions it raises when writing would fail (e.g. due to socket buffer # being full). # # IMPORTANT: this method will mutate (slice) the argument. Pass in duplicates # if this is not appropriate in your case. # # @param [String] data Data to write # @param [Integer] timeout Timeout # # @api public def write_nonblock_fully(data, timeout = nil) return nil if @__bunny_socket_eof_flag__ length = data.bytesize total_count = 0 count = 0 loop do begin count = self.write_nonblock(data) rescue *WRITE_RETRY_EXCEPTION_CLASSES if IO.select([], [self], nil, timeout) retry else raise Timeout::Error, "IO timeout when writing to socket" end end total_count += count return total_count if total_count >= length data = data.byteslice(count..-1) end end end end ruby-amqp-bunny-b6569cd/lib/bunny/cruby/ssl_socket.rb000066400000000000000000000075161464043542000227420ustar00rootroot00000000000000require "socket" module Bunny begin require "openssl" # TLS-enabled TCP socket that implements convenience # methods found in Bunny::Socket. class SSLSocket < OpenSSL::SSL::SSLSocket READ_RETRY_EXCEPTION_CLASSES = if defined?(IO::EAGAINWaitReadable) # Ruby 2.1+ [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable, IO::EAGAINWaitReadable, IO::EWOULDBLOCKWaitReadable] else # 2.0 [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable] end WRITE_RETRY_EXCEPTION_CLASSES = if defined?(IO::EAGAINWaitWritable) [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable, IO::EAGAINWaitWritable, IO::EWOULDBLOCKWaitWritable] else # 2.0 [Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable] end def initialize(*args) super @__bunny_socket_eof_flag__ = false end # Reads given number of bytes with an optional timeout # # @param [Integer] count How many bytes to read # @param [Integer] timeout Timeout # # @return [String] Data read from the socket # @api public def read_fully(count, timeout = nil) return nil if @__bunny_socket_eof_flag__ value = '' begin loop do value << read_nonblock(count - value.bytesize) break if value.bytesize >= count end rescue EOFError => e @__bunny_socket_eof_flag__ = true rescue OpenSSL::SSL::SSLError => e if e.message == "read would block" if IO.select([self], nil, nil, timeout) retry else raise Timeout::Error, "IO timeout when reading #{count} bytes" end else raise e end rescue *READ_RETRY_EXCEPTION_CLASSES => e if IO.select([self], nil, nil, timeout) retry else raise Timeout::Error, "IO timeout when reading #{count} bytes" end end value end # Writes provided data using IO#write_nonblock, taking care of handling # of exceptions it raises when writing would fail (e.g. due to socket buffer # being full). # # IMPORTANT: this method will mutate (slice) the argument. Pass in duplicates # if this is not appropriate in your case. # # @param [String] data Data to write # @param [Integer] timeout Timeout # # @api public def write_nonblock_fully(data, timeout = nil) return nil if @__bunny_socket_eof_flag__ length = data.bytesize total_count = 0 count = 0 loop do begin count = self.write_nonblock(data) rescue OpenSSL::SSL::SSLError => e if e.message == "write would block" if IO.select([], [self], nil, timeout) retry else raise Timeout::Error, "IO timeout when writing to socket" end end raise e rescue *WRITE_RETRY_EXCEPTION_CLASSES if IO.select([], [self], nil, timeout) retry else raise Timeout::Error, "IO timeout when writing to socket" end end total_count += count return total_count if total_count >= length data = data.byteslice(count..-1) end end end rescue LoadError puts "Could not load OpenSSL" end end ruby-amqp-bunny-b6569cd/lib/bunny/delivery_info.rb000066400000000000000000000043151464043542000222750ustar00rootroot00000000000000require "bunny/versioned_delivery_tag" module Bunny # Wraps {AMQ::Protocol::Basic::Deliver} to # provide access to the delivery properties as immutable hash as # well as methods. class DeliveryInfo # # Behaviors # include Enumerable # # API # # @return [Bunny::Consumer] Consumer this delivery is for attr_reader :consumer # @return [Bunny::Channel] Channel this delivery is on attr_reader :channel # @private def initialize(basic_deliver, consumer, channel) @basic_deliver = basic_deliver @hash = { :consumer_tag => basic_deliver.consumer_tag, :delivery_tag => VersionedDeliveryTag.new(basic_deliver.delivery_tag, channel.recoveries_counter), :redelivered => basic_deliver.redelivered, :exchange => basic_deliver.exchange, :routing_key => basic_deliver.routing_key, :consumer => consumer, :channel => channel } @consumer = consumer @channel = channel end # Iterates over delivery properties # @see Enumerable#each def each(*args, &block) @hash.each(*args, &block) end # Accesses delivery properties by key # @see Hash#[] def [](k) @hash[k] end # @return [Hash] Hash representation of this delivery info def to_hash @hash end # @private def to_s to_hash.to_s end # @private def inspect to_hash.inspect end # @return [String] Consumer tag this delivery is for def consumer_tag @basic_deliver.consumer_tag end # @return [String] Delivery identifier that is used to acknowledge, reject and nack deliveries def delivery_tag @basic_deliver.delivery_tag end # @return [Boolean] true if this delivery is a redelivery (the message was requeued at least once) def redelivered @basic_deliver.redelivered end alias redelivered? redelivered # @return [String] Name of the exchange this message was published to def exchange @basic_deliver.exchange end # @return [String] Routing key this message was published with def routing_key @basic_deliver.routing_key end end end ruby-amqp-bunny-b6569cd/lib/bunny/exceptions.rb000066400000000000000000000171261464043542000216240ustar00rootroot00000000000000module Bunny # Base class for all Bunny exceptions # @api public class Exception < ::StandardError end class HostListDepleted < Exception def initialize super("No more hosts to try in the supplied list of hosts") end end # Indicates a network failure. If automatic network # recovery mode is enabled, these will be typically handled # by the client itself. # # @api public class NetworkFailure < Exception attr_reader :cause def initialize(message, cause) super(message) @cause = cause end end # Base class for all channel level exceptions class ChannelLevelException < Exception attr_reader :channel, :channel_close def initialize(message, ch, channel_close) super(message) @channel = ch @channel_close = channel_close end end # Base class for all connection level exceptions class ConnectionLevelException < Exception attr_reader :connection, :connection_close def initialize(message, connection, connection_close) super(message) @connection = connection @connection_close = connection_close end end # Can indicate either a channel or connection-level issue class NotAllowedError < Exception attr_reader :connection, :connection_close def initialize(message, connection, connection_close = nil) super(message) @connection = connection @connection_close = connection_close end end # Raised when TCP connection to RabbitMQ fails because of an unresolved # hostname, connectivity problem, etc class TCPConnectionFailed < Exception attr_reader :hostname, :port def initialize(e, hostname=nil, port=nil) m = case e when String then e when ::Exception then e.message end if hostname && port super("Could not establish TCP connection to #{hostname}:#{port}: #{m}") else super(m) end end end class TCPConnectionFailedForAllHosts < TCPConnectionFailed def initialize super("Could not establish TCP connection to any of the configured hosts", nil, nil) end end # Raised when a frame is sent over an already closed connection class ConnectionClosedError < Exception def initialize(frame) if frame.respond_to?(:method_class) super("Trying to send frame through a closed connection. Frame is #{frame.inspect}, method class is #{frame.method_class}") else super("Trying to send frame through a closed connection. Frame is #{frame.inspect}") end end end class ConnectionAlreadyClosed < Exception def initialize super('Connection has been already closed') end end class ShutdownSignal < Exception end # Raised when RabbitMQ closes TCP connection before finishing connection # sequence properly. This typically indicates an authentication issue. class PossibleAuthenticationFailureError < Exception # # API # attr_reader :username, :vhost def initialize(username, vhost, password_length) @username = username @vhost = vhost super("Authentication with RabbitMQ failed. Please check your connection settings. Username: #{username}, vhost: #{vhost}, password length: #{password_length}") end # initialize(settings) end # PossibleAuthenticationFailureError # Raised when RabbitMQ closes TCP connection due to an authentication failure. # Relies on RabbitMQ 3.2 Authentication Failure Notifications extension: # http://www.rabbitmq.com/auth-notification.html class AuthenticationFailureError < PossibleAuthenticationFailureError # # API # attr_reader :username, :vhost def initialize(username, vhost, password_length) @username = username @vhost = vhost super(username, vhost, password_length) end # initialize(settings) end # AuthenticationFailureError # backwards compatibility # @private ConnectionError = TCPConnectionFailed # @private ServerDownError = TCPConnectionFailed # Raised when a channel is closed forcefully using rabbitmqctl # or the management UI plugin class ForcedChannelCloseError < ChannelLevelException; end # Raised when a connection is closed forcefully using rabbitmqctl # or the management UI plugin class ForcedConnectionCloseError < ConnectionLevelException; end # @private class MessageError < ConnectionLevelException; end # @private class ProtocolError < ConnectionLevelException; end # Raised when RabbitMQ reports and internal error class InternalError < ConnectionLevelException; end # Raised when read or write I/O operations time out (but only if # a connection is configured to use them) class ClientTimeout < Timeout::Error; end # Raised on initial TCP connection timeout class ConnectionTimeout < Timeout::Error; end # Base exception class for data consistency and framing errors. class InconsistentDataError < Exception end # Raised by adapters when frame does not end with {final octet AMQ::Protocol::Frame::FINAL_OCTET}. # This suggest that there is a bug in adapter or AMQ broker implementation. # # @see https://www.rabbitmq.com/resources/specs/amqp0-9-1.pdf AMQP 0.9.1 specification (Section 2.3) class NoFinalOctetError < InconsistentDataError def initialize super("Frame doesn't end with #{AMQ::Protocol::Frame::FINAL_OCTET} as it must, which means the size is miscalculated.") end end # Raised by adapters when actual frame payload size in bytes is not equal # to the size specified in that frame's header. # This suggest that there is a bug in adapter or AMQ broker implementation. # # @see https://www.rabbitmq.com/resources/specs/amqp0-9-1.pdf AMQP 0.9.1 specification (Section 2.3) class BadLengthError < InconsistentDataError def initialize(expected_length, actual_length) super("Frame payload should be #{expected_length} long, but it's #{actual_length} long.") end end # Raised when a closed channel is used class ChannelAlreadyClosed < Exception attr_reader :channel def initialize(message, ch) super(message) @channel = ch end end # Raised when RabbitMQ responds with 406 PRECONDITION_FAILED class PreconditionFailed < ChannelLevelException end # Raised when RabbitMQ responds with 404 NOT_FOUND class NotFound < ChannelLevelException end # Raised when RabbitMQ responds with 405 RESOUCE_LOCKED class ResourceLocked < ChannelLevelException end # Raised when RabbitMQ responds with 403 ACCESS_REFUSED class AccessRefused < ChannelLevelException end # Raised when RabbitMQ responds with 504 CHANNEL_ERROR class ChannelError < ConnectionLevelException end # Raised when RabbitMQ responds with 503 COMMAND_INVALID class CommandInvalid < ConnectionLevelException end # Raised when RabbitMQ responds with 501 FRAME_ERROR class FrameError < ConnectionLevelException end # Raised when RabbitMQ responds with 505 UNEXPECTED_FRAME class UnexpectedFrame < ConnectionLevelException end # Raised when RabbitMQ responds with 506 RESOURCE_ERROR class ResourceError < ConnectionLevelException end # @private class NetworkErrorWrapper < Exception attr_reader :other def initialize(other) super(other.message) @other = other end end # Raised when RabbitMQ responds with 302 CONNECTION_FORCED # (which means the connection was closed using rabbitmqctl or # RabbitMQ management UI) class ConnectionForced < ConnectionLevelException end # @private class MissingTLSCertificateFile < Exception end # @private class MissingTLSKeyFile < Exception end end ruby-amqp-bunny-b6569cd/lib/bunny/exchange.rb000066400000000000000000000236471464043542000212320ustar00rootroot00000000000000require 'amq/protocol' module Bunny # Represents AMQP 0.9.1 exchanges. # # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide class Exchange # # API # # @return [Bunny::Channel] attr_reader :channel # @return [String] attr_reader :name # Type of this exchange (one of: :direct, :fanout, :topic, :headers). # @return [Symbol] attr_reader :type # @return [Symbol] # @api plugin attr_reader :status # Options hash this exchange instance was instantiated with # @return [Hash] attr_accessor :opts # The default exchange. This exchange is a direct exchange that is predefined by the broker # and that cannot be removed. Every queue is bound to this exchange by default with # the following routing semantics: messages will be routed to the queue with the same # name as the message's routing key. In other words, if a message is published with # a routing key of "weather.usa.ca.sandiego" and there is a queue with this name, # the message will be routed to the queue. # # @param [Bunny::Channel] channel_or_connection Channel to use. {Bunny::Session} instances # are only supported for backwards compatibility. # # @example Publishing a messages to the tasks queue # channel = Bunny::Channel.new(connection) # tasks_queue = channel.queue("tasks") # Bunny::Exchange.default(channel).publish("make clean", :routing_key => "tasks") # # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide # @see http://www.rabbitmq.com/resources/specs/amqp0-9-1.pdf AMQP 0.9.1 specification (Section 2.1.2.4) # @note Do not confuse the default exchange with amq.direct: amq.direct is a pre-defined direct # exchange that doesn't have any special routing semantics. # @return [Exchange] An instance that corresponds to the default exchange (of type direct). # @api public def self.default(channel_or_connection) self.new(channel_or_connection, :direct, AMQ::Protocol::EMPTY_STRING, :no_declare => true) end # @param [Bunny::Channel] channel Channel this exchange will use. # @param [Symbol,String] type Exchange type # @param [String] name Exchange name # @param [Hash] opts Exchange properties # # @option opts [Boolean] :durable (false) Should this exchange be durable? # @option opts [Boolean] :auto_delete (false) Should this exchange be automatically deleted when it is no longer used? # @option opts [Boolean] :arguments ({}) Additional optional arguments (typically used by RabbitMQ extensions and plugins) # # @see Bunny::Channel#topic # @see Bunny::Channel#fanout # @see Bunny::Channel#direct # @see Bunny::Channel#headers # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide # @api public def initialize(channel, type, name, opts = {}) @channel = channel @name = name @type = type @options = self.class.add_default_options(name, opts) @durable = @options[:durable] @auto_delete = @options[:auto_delete] @internal = @options[:internal] @arguments = @options[:arguments] @bindings = Set.new declare! unless opts[:no_declare] || predeclared? || (@name == AMQ::Protocol::EMPTY_STRING) @channel.register_exchange(self) end # @return [Boolean] true if this exchange was declared as durable (will survive broker restart). # @api public def durable? @durable end # durable? # @return [Boolean] true if this exchange was declared as automatically deleted (deleted as soon as last consumer unbinds). # @api public def auto_delete? @auto_delete end # auto_delete? # @return [Boolean] true if this exchange is internal (used solely for exchange-to-exchange # bindings and cannot be published to by clients) def internal? @internal end # @return [Hash] Additional optional arguments (typically used by RabbitMQ extensions and plugins) # @api public def arguments @arguments end # Publishes a message # # @param [String] payload Message payload. It will never be modified by Bunny or RabbitMQ in any way. # @param [Hash] opts Message properties (metadata) and delivery settings # # @option opts [String] :routing_key Routing key # @option opts [Boolean] :persistent Should the message be persisted to disk? # @option opts [Boolean] :mandatory Should the message be returned if it cannot be routed to any queue? # @option opts [Integer] :timestamp A timestamp associated with this message # @option opts [Integer] :expiration Expiration time after which the message will be deleted # @option opts [String] :type Message type, e.g. what type of event or command this message represents. Can be any string # @option opts [String] :reply_to Queue name other apps should send the response to # @option opts [String] :content_type Message content type (e.g. application/json) # @option opts [String] :content_encoding Message content encoding (e.g. gzip) # @option opts [String] :correlation_id Message correlated to this one, e.g. what request this message is a reply for # @option opts [Integer] :priority Message priority, 0 to 9. Not used by RabbitMQ, only applications # @option opts [String] :message_id Any message identifier # @option opts [String] :user_id Optional user ID. Verified by RabbitMQ against the actual connection username # @option opts [String] :app_id Optional application ID # # @return [Bunny::Exchange] Self # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide # @api public def publish(payload, opts = {}) @channel.basic_publish(payload, self.name, (opts.delete(:routing_key) || opts.delete(:key)), opts) self end # Deletes the exchange unless it is predeclared # # @param [Hash] opts Options # # @option opts [Boolean] if_unused (false) Should this exchange be deleted only if it is no longer used # # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide # @api public def delete(opts = {}) @channel.deregister_exchange(self) @channel.exchange_delete(@name, opts) unless predeclared? end # Binds an exchange to another (source) exchange using exchange.bind AMQP 0.9.1 extension # that RabbitMQ provides. # # @param [String] source Source exchange name # @param [Hash] opts Options # # @option opts [String] routing_key (nil) Routing key used for binding # @option opts [Hash] arguments ({}) Optional arguments # # @return [Bunny::Exchange] Self # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide # @see http://rubybunny.info/articles/bindings.html Bindings guide # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide # @api public def bind(source, opts = {}) @channel.exchange_bind(source, self, opts) @bindings.add(source: source, opts: opts) self end # Unbinds an exchange from another (source) exchange using exchange.unbind AMQP 0.9.1 extension # that RabbitMQ provides. # # @param [String] source Source exchange name # @param [Hash] opts Options # # @option opts [String] routing_key (nil) Routing key used for binding # @option opts [Hash] arguments ({}) Optional arguments # # @return [Bunny::Exchange] Self # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide # @see http://rubybunny.info/articles/bindings.html Bindings guide # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide # @api public def unbind(source, opts = {}) @channel.exchange_unbind(source, self, opts) @bindings.delete(source: source, opts: opts) self end # Defines a block that will handle returned messages # @see http://rubybunny.info/articles/exchanges.html # @api public def on_return(&block) @on_return = block self end # Waits until all outstanding publisher confirms on the channel # arrive. # # This is a convenience method that delegates to {Bunny::Channel#wait_for_confirms} # # @api public def wait_for_confirms @channel.wait_for_confirms end # @private def recover_from_network_failure declare! unless @options[:no_declare] ||predefined? @bindings.each do |b| bind(b[:source], b[:opts]) end end # # Implementation # # @private def handle_return(basic_return, properties, content) if @on_return @on_return.call(basic_return, properties, content) else # TODO: log a warning end end # @return [Boolean] true if this exchange is a pre-defined one (amq.direct, amq.fanout, amq.match and so on) def predefined? (@name == AMQ::Protocol::EMPTY_STRING) || !!(@name =~ /^amq\.(direct|fanout|topic|headers|match)/i) end # predefined? alias predeclared? predefined? protected # @private def declare! @channel.exchange_declare(@name, @type, @options) end # @private def self.add_default_options(name, opts) # :nowait is always false for Bunny h = { :queue => name, :nowait => false }.merge(opts) if name.empty? { :passive => false, :durable => false, :auto_delete => false, :internal => false, :arguments => nil }.merge(h) else h end end end end ruby-amqp-bunny-b6569cd/lib/bunny/framing.rb000066400000000000000000000034051464043542000210610ustar00rootroot00000000000000module Bunny # @private module Framing ENCODINGS_SUPPORTED = defined? Encoding HEADER_SLICE = (0..6).freeze DATA_SLICE = (7..-1).freeze PAYLOAD_SLICE = (0..-2).freeze # @private module String class Frame < AMQ::Protocol::Frame def self.decode(string) header = string[HEADER_SLICE] type, channel, size = self.decode_header(header) data = string[DATA_SLICE] payload = data[PAYLOAD_SLICE] frame_end = data[-1, 1] frame_end.force_encoding(AMQ::Protocol::Frame::FINAL_OCTET.encoding) if ENCODINGS_SUPPORTED # 1) the size is miscalculated if payload.bytesize != size raise BadLengthError.new(size, payload.bytesize) end # 2) the size is OK, but the string doesn't end with FINAL_OCTET raise NoFinalOctetError.new if frame_end != AMQ::Protocol::Frame::FINAL_OCTET self.new(type, payload, channel) end end end # String # @private module IO class Frame < AMQ::Protocol::Frame def self.decode(io) header = io.read(7) type, channel, size = self.decode_header(header) data = io.read_fully(size + 1) payload, frame_end = data[PAYLOAD_SLICE], data[-1, 1] # 1) the size is miscalculated if payload.bytesize != size raise BadLengthError.new(size, payload.bytesize) end # 2) the size is OK, but the string doesn't end with FINAL_OCTET raise NoFinalOctetError.new if frame_end != AMQ::Protocol::Frame::FINAL_OCTET self.new(type, payload, channel) end # self.from end # Frame end # IO end # Framing end # Bunny ruby-amqp-bunny-b6569cd/lib/bunny/get_response.rb000066400000000000000000000034111464043542000221300ustar00rootroot00000000000000require "bunny/versioned_delivery_tag" module Bunny # Wraps {AMQ::Protocol::Basic::GetOk} to # provide access to the delivery properties as immutable hash as # well as methods. class GetResponse # # Behaviors # include Enumerable # # API # # @return [Bunny::Channel] Channel this basic.get-ok response is on attr_reader :channel # @private def initialize(get_ok, channel) @get_ok = get_ok @hash = { :delivery_tag => @get_ok.delivery_tag, :redelivered => @get_ok.redelivered, :exchange => @get_ok.exchange, :routing_key => @get_ok.routing_key, :channel => channel } @channel = channel end # Iterates over the delivery properties # @see Enumerable#each def each(*args, &block) @hash.each(*args, &block) end # Accesses delivery properties by key # @see Hash#[] def [](k) @hash[k] end # @return [Hash] Hash representation of this delivery info def to_hash @hash end # @private def to_s to_hash.to_s end # @private def inspect to_hash.inspect end # @return [String] Delivery identifier that is used to acknowledge, reject and nack deliveries def delivery_tag @get_ok.delivery_tag end # @return [Boolean] true if this delivery is a redelivery (the message was requeued at least once) def redelivered @get_ok.redelivered end alias redelivered? redelivered # @return [String] Name of the exchange this message was published to def exchange @get_ok.exchange end # @return [String] Routing key this message was published with def routing_key @get_ok.routing_key end end end ruby-amqp-bunny-b6569cd/lib/bunny/heartbeat_sender.rb000066400000000000000000000033441464043542000227370ustar00rootroot00000000000000require "thread" require "amq/protocol/client" require "amq/protocol/frame" module Bunny # Periodically sends heartbeats, keeping track of the last publishing activity. # # @private class HeartbeatSender # # API # def initialize(transport, logger) @transport = transport @logger = logger @mutex = Monitor.new @last_activity_time = Bunny::Timestamp.monotonic end def start(period = 30) @mutex.synchronize do # calculate interval as half the given period plus # some compensation for Ruby's implementation inaccuracy # (we cannot get at the nanos level the Java client uses, and # our approach is simplistic). MK. @interval = [(period / 2) - 1, 0.4].max @thread = Thread.new(&method(:run)) @thread.report_on_exception = false if @thread.respond_to?(:report_on_exception) end end def stop @mutex.synchronize { @thread.exit } end def signal_activity! @last_activity_time = Bunny::Timestamp.monotonic end protected def run begin loop do self.beat sleep @interval end rescue IOError => ioe @logger.error "I/O error in the hearbeat sender: #{ioe.message}" stop rescue ::Exception => e @logger.error "Error in the hearbeat sender: #{e.message}" stop end end def beat now = Bunny::Timestamp.monotonic if now > (@last_activity_time + @interval) @logger.debug { "Sending a heartbeat, last activity time: #{@last_activity_time}, interval (s): #{@interval}" } @transport.write_without_timeout(AMQ::Protocol::HeartbeatFrame.encode, true) end end end end ruby-amqp-bunny-b6569cd/lib/bunny/jruby/000077500000000000000000000000001464043542000202425ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/lib/bunny/jruby/socket.rb000066400000000000000000000034501464043542000220610ustar00rootroot00000000000000require "bunny/cruby/socket" module Bunny module JRuby # TCP socket extension that uses Socket#readpartial to avoid excessive CPU # burn after some time. See issue #165. # @private module Socket include Bunny::Socket def self.open(host, port, options = {}) socket = ::Socket.tcp(host, port, nil, nil, connect_timeout: options[:connect_timeout]) if ::Socket.constants.include?('TCP_NODELAY') || ::Socket.constants.include?(:TCP_NODELAY) socket.setsockopt(::Socket::IPPROTO_TCP, ::Socket::TCP_NODELAY, true) end socket.setsockopt(::Socket::SOL_SOCKET, ::Socket::SO_KEEPALIVE, true) if options.fetch(:keepalive, true) socket.extend self socket.options = { :host => host, :port => port }.merge(options) socket rescue Errno::ETIMEDOUT raise ClientTimeout end # Reads given number of bytes with an optional timeout # # @param [Integer] count How many bytes to read # @param [Integer] timeout Timeout # # @return [String] Data read from the socket # @api public def read_fully(count, timeout = nil) value = '' begin loop do value << read_nonblock(count - value.bytesize) break if value.bytesize >= count end rescue EOFError # JRuby specific fix via https://github.com/jruby/jruby/issues/1694#issuecomment-54873532 IO.select([self], nil, nil, timeout) retry rescue *READ_RETRY_EXCEPTION_CLASSES if IO.select([self], nil, nil, timeout) retry else raise Timeout::Error, "IO timeout when reading #{count} bytes" end end value end # read_fully end end end ruby-amqp-bunny-b6569cd/lib/bunny/jruby/ssl_socket.rb000066400000000000000000000032011464043542000227340ustar00rootroot00000000000000module Bunny module JRuby begin require "bunny/cruby/ssl_socket" require "openssl" # TLS-enabled TCP socket that implements convenience # methods found in Bunny::Socket. class SSLSocket < Bunny::SSLSocket def initialize(*args) super @__bunny_socket_eof_flag__ = false end # Reads given number of bytes with an optional timeout # # @param [Integer] count How many bytes to read # @param [Integer] timeout Timeout # # @return [String] Data read from the socket # @api public def read_fully(count, timeout = nil) return nil if @__bunny_socket_eof_flag__ value = '' begin loop do value << read_nonblock(count - value.bytesize) break if value.bytesize >= count end rescue EOFError => e @__bunny_socket_eof_flag__ = true rescue OpenSSL::SSL::SSLError => e if e.message == "read would block" if IO.select([self], nil, nil, timeout) retry else raise Timeout::Error, "IO timeout when reading #{count} bytes" end else raise e end rescue *READ_RETRY_EXCEPTION_CLASSES => e if IO.select([self], nil, nil, timeout) retry else raise Timeout::Error, "IO timeout when reading #{count} bytes" end end value end end rescue LoadError => le puts "Could not load OpenSSL" end end end ruby-amqp-bunny-b6569cd/lib/bunny/message_properties.rb000066400000000000000000000051221464043542000233340ustar00rootroot00000000000000module Bunny # Wraps basic properties hash as returned by amq-protocol to # provide access to the delivery properties as immutable hash as # well as methods. class MessageProperties # # Behaviors # include Enumerable # # API # # @private def initialize(properties) @properties = properties end # Iterates over the message properties # @see Enumerable#each def each(*args, &block) @properties.each(*args, &block) end # Accesses message properties by key # @see Hash#[] def [](k) @properties[k] end # @return [Hash] Hash representation of this delivery info def to_hash @properties end # @private def to_s to_hash.to_s end # @private def inspect to_hash.inspect end # @return [String] (Optional) content type of the message, as set by the publisher def content_type @properties[:content_type] end # @return [String] (Optional) content encoding of the message, as set by the publisher def content_encoding @properties[:content_encoding] end # @return [String] Message headers def headers @properties[:headers] end # @return [Integer] Delivery mode (persistent or transient) def delivery_mode @properties[:delivery_mode] end # @return [Integer] Message priority, as set by the publisher def priority @properties[:priority] end # @return [String] What message this message is a reply to (or corresponds to), as set by the publisher def correlation_id @properties[:correlation_id] end # @return [String] (Optional) How to reply to the publisher (usually a reply queue name) def reply_to @properties[:reply_to] end # @return [String] Message expiration, as set by the publisher def expiration @properties[:expiration] end # @return [String] Message ID, as set by the publisher def message_id @properties[:message_id] end # @return [Time] Message timestamp, as set by the publisher def timestamp @properties[:timestamp] end # @return [String] Message type, as set by the publisher def type @properties[:type] end # @return [String] Publishing user, as set by the publisher def user_id @properties[:user_id] end # @return [String] Publishing application, as set by the publisher def app_id @properties[:app_id] end # @return [String] Cluster ID, as set by the publisher def cluster_id @properties[:cluster_id] end end end ruby-amqp-bunny-b6569cd/lib/bunny/queue.rb000066400000000000000000000341151464043542000205640ustar00rootroot00000000000000require "bunny/get_response" module Bunny # Represents AMQP 0.9.1 queue. # # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide class Queue # # API # module Types QUORUM = "quorum" CLASSIC = "classic" STREAM = "stream" KNOWN = [CLASSIC, QUORUM, STREAM] def self.known?(q_type) KNOWN.include?(q_type) end end module XArgs MAX_LENGTH = "x-max-length", QUEUE_TYPE = "x-queue-type" end # @return [Bunny::Channel] Channel this queue uses attr_reader :channel # @return [String] Queue name attr_reader :name # @return [Hash] Options this queue was created with attr_reader :options # @param [Bunny::Channel] channel Channel this queue will use. # @param [String] name Queue name. Pass an empty string to make RabbitMQ generate a unique one. # @param [Hash] opts Queue properties # # @option opts [Boolean] :durable (false) Should this queue be durable? # @option opts [Boolean] :auto_delete (false) Should this queue be automatically deleted when the last consumer disconnects? # @option opts [Boolean] :exclusive (false) Should this queue be exclusive (only can be used by this connection, removed when the connection is closed)? # @option opts [String] :type (nil) Type of the declared queue (classic, quorum or stream) # @option opts [Hash] :arguments (nil) Additional optional arguments (typically used by RabbitMQ extensions and plugins) # # @see Bunny::Channel#queue # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @see http://rubybunny.info/articles/extensions.html RabbitMQ Extensions guide # @api public def initialize(channel, name = AMQ::Protocol::EMPTY_STRING, opts = {}) # old Bunny versions pass a connection here. In that case, # we just use default channel from it. MK. @channel = channel @name = name @options = self.class.add_default_options(name, opts) @durable = @options[:durable] @exclusive = @options[:exclusive] @server_named = @name.empty? @auto_delete = @options[:auto_delete] @type = @options[:type] @arguments = if @type and !@type.empty? then (@options[:arguments] || {}).merge({XArgs::QUEUE_TYPE => @type}) else @options[:arguments] end verify_type!(@arguments) # reassigns updated and verified arguments because Bunny::Channel#declare_queue # accepts a map of options @options[:arguments] = @arguments @bindings = Array.new @default_consumer = nil declare! unless opts[:no_declare] @channel.register_queue(self) end # @return [Boolean] true if this queue was declared as durable (will survive broker restart). # @api public # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide def durable? @durable end # durable? # @return [Boolean] true if this queue was declared as exclusive (limited to just one consumer) # @api public # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide def exclusive? @exclusive end # exclusive? # @return [Boolean] true if this queue was declared as automatically deleted (deleted as soon as last consumer unbinds). # @api public # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide def auto_delete? @auto_delete end # auto_delete? # @return [Boolean] true if this queue was declared as server named. # @api public # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide def server_named? @server_named end # server_named? # @return [Hash] Additional optional arguments (typically used by RabbitMQ extensions and plugins) # @api public def arguments @arguments end def to_s oid = ("0x%x" % (self.object_id << 1)) "<#{self.class.name}:#{oid} @name=\"#{name}\" channel=#{@channel.to_s} @durable=#{@durable} @auto_delete=#{@auto_delete} @exclusive=#{@exclusive} @arguments=#{@arguments}>" end def inspect to_s end # Binds queue to an exchange # # @param [Bunny::Exchange,String] exchange Exchange to bind to # @param [Hash] opts Binding properties # # @option opts [String] :routing_key Routing key # @option opts [Hash] :arguments ({}) Additional optional binding arguments # # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @see http://rubybunny.info/articles/bindings.html Bindings guide # @api public def bind(exchange, opts = {}) @channel.queue_bind(@name, exchange, opts) exchange_name = if exchange.respond_to?(:name) exchange.name else exchange end # store bindings for automatic recovery. We need to be very careful to # not cause an infinite rebinding loop here when we recover. MK. binding = { :exchange => exchange_name, :routing_key => (opts[:routing_key] || opts[:key]), :arguments => opts[:arguments] } @bindings.push(binding) unless @bindings.include?(binding) self end # Unbinds queue from an exchange # # @param [Bunny::Exchange,String] exchange Exchange to unbind from # @param [Hash] opts Binding properties # # @option opts [String] :routing_key Routing key # @option opts [Hash] :arguments ({}) Additional optional binding arguments # # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @see http://rubybunny.info/articles/bindings.html Bindings guide # @api public def unbind(exchange, opts = {}) @channel.queue_unbind(@name, exchange, opts) exchange_name = if exchange.respond_to?(:name) exchange.name else exchange end @bindings.delete_if { |b| b[:exchange] == exchange_name && b[:routing_key] == (opts[:routing_key] || opts[:key]) && b[:arguments] == opts[:arguments] } self end # Adds a consumer to the queue (subscribes for message deliveries). # # @param [Hash] opts Options # # @option opts [Boolean] :ack (false) [DEPRECATED] Use :manual_ack instead # @option opts [Boolean] :manual_ack (false) Will this consumer use manual acknowledgements? # @option opts [Boolean] :exclusive (false) Should this consumer be exclusive for this queue? # @option opts [#call] :on_cancellation Block to execute when this consumer is cancelled remotely (e.g. via the RabbitMQ Management plugin) # @option opts [String] :consumer_tag Unique consumer identifier. It is usually recommended to let Bunny generate it for you. # @option opts [Hash] :arguments ({}) Additional (optional) arguments, typically used by RabbitMQ extensions # # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @api public def subscribe(opts = { :consumer_tag => @channel.generate_consumer_tag, :manual_ack => false, :exclusive => false, :block => false, :on_cancellation => nil }, &block) unless opts[:ack].nil? warn "[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead." opts[:manual_ack] = opts[:ack] end ctag = opts.fetch(:consumer_tag, @channel.generate_consumer_tag) consumer = Consumer.new(@channel, self, ctag, !opts[:manual_ack], opts[:exclusive], opts[:arguments]) consumer.on_delivery(&block) consumer.on_cancellation(&opts[:on_cancellation]) if opts[:on_cancellation] @channel.basic_consume_with(consumer) if opts[:block] # joins current thread with the consumers pool, will block # the current thread for as long as the consumer pool is active @channel.work_pool.join end consumer end # Adds a consumer object to the queue (subscribes for message deliveries). # # @param [Bunny::Consumer] consumer a {Bunny::Consumer} subclass that implements consumer interface # @param [Hash] opts Options # # @option opts [Boolean] block (false) Should the call block calling thread? # # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @api public def subscribe_with(consumer, opts = {:block => false}) @channel.basic_consume_with(consumer) @channel.work_pool.join if opts[:block] consumer end # @param [Hash] opts Options # # @option opts [Boolean] :ack (false) [DEPRECATED] Use :manual_ack instead # @option opts [Boolean] :manual_ack (false) Will the message be acknowledged manually? # # @return [Array] Triple of delivery info, message properties and message content. # If the queue is empty, all three will be nils. # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @see Bunny::Queue#subscribe # @api public # # @example # conn = Bunny.new # conn.start # # ch = conn.create_channel # q = ch.queue("test1") # x = ch.default_exchange # x.publish("Hello, everybody!", :routing_key => 'test1') # # delivery_info, properties, payload = q.pop # # puts "This is the message: " + payload + "\n\n" # conn.close def pop(opts = {:manual_ack => false}, &block) unless opts[:ack].nil? warn "[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead." opts[:manual_ack] = opts[:ack] end get_response, properties, content = @channel.basic_get(@name, opts) if block if properties di = GetResponse.new(get_response, @channel) mp = MessageProperties.new(properties) block.call(di, mp, content) else block.call(nil, nil, nil) end else if properties di = GetResponse.new(get_response, @channel) mp = MessageProperties.new(properties) [di, mp, content] else [nil, nil, nil] end end end alias get pop # Publishes a message to the queue via default exchange. Takes the same arguments # as {Bunny::Exchange#publish} # # @see Bunny::Exchange#publish # @see Bunny::Channel#default_exchange # @see http://rubybunny.info/articles/exchanges.html Exchanges and Publishing guide def publish(payload, opts = {}) @channel.default_exchange.publish(payload, opts.merge(:routing_key => @name)) self end # Deletes the queue # # @param [Hash] opts Options # # @option opts [Boolean] if_unused (false) Should this queue be deleted only if it has no consumers? # @option opts [Boolean] if_empty (false) Should this queue be deleted only if it has no messages? # # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @api public def delete(opts = {}) @channel.deregister_queue(self) @channel.queue_delete(@name, opts) end # Purges a queue (removes all messages from it) # @see http://rubybunny.info/articles/queues.html Queues and Consumers guide # @api public def purge(opts = {}) @channel.queue_purge(@name, opts) self end # @return [Hash] A hash with information about the number of queue messages and consumers # @see #message_count # @see #consumer_count def status queue_declare_ok = @channel.queue_declare(@name, @options.merge(:passive => true)) {:message_count => queue_declare_ok.message_count, :consumer_count => queue_declare_ok.consumer_count} end # @return [Integer] How many messages the queue has ready (e.g. not delivered but not unacknowledged) def message_count s = self.status s[:message_count] end # @return [Integer] How many active consumers the queue has def consumer_count s = self.status s[:consumer_count] end # # Recovery # # @private def recover_from_network_failure if self.server_named? old_name = @name.dup @name = AMQ::Protocol::EMPTY_STRING @channel.deregister_queue_named(old_name) end # TODO: inject and use logger # puts "Recovering queue #{@name}" begin declare! unless @options[:no_declare] @channel.register_queue(self) rescue Exception => e # TODO: inject and use logger puts "Caught #{e.inspect} while redeclaring and registering #{@name}!" end recover_bindings end # @private def recover_bindings @bindings.each do |b| # TODO: inject and use logger # puts "Recovering binding #{b.inspect}" self.bind(b[:exchange], b) end end # # Implementation # # @private def declare! queue_declare_ok = @channel.queue_declare(@name, @options) @name = queue_declare_ok.queue end protected # @private def self.add_default_options(name, opts) # :nowait is always false for Bunny h = { :queue => name, :nowait => false }.merge(opts) if name.empty? { :passive => false, :durable => false, :exclusive => false, :auto_delete => false, :arguments => nil }.merge(h) else h end end def verify_type!(args) q_type = (args || {})["x-queue-type"] throw ArgumentError.new( "unsupported queue type #{q_type.inspect}, supported ones: #{Types::KNOWN.join(', ')}") if (q_type and !Types.known?(q_type)) end end end ruby-amqp-bunny-b6569cd/lib/bunny/reader_loop.rb000066400000000000000000000107731464043542000217370ustar00rootroot00000000000000require "thread" module Bunny # Network activity loop that reads and passes incoming AMQP 0.9.1 methods for # processing. They are dispatched further down the line in Bunny::Session and Bunny::Channel. # This loop uses a separate thread internally. # # This mimics the way RabbitMQ Java is designed quite closely. # @private class ReaderLoop def initialize(transport, session, session_error_handler) @transport = transport @session = session @session_error_handler = session_error_handler @logger = @session.logger @mutex = Mutex.new @stopping = false @stopped = false @network_is_down = false end def start @thread = Thread.new(&method(:run_loop)) end def resume start end def run_loop loop do begin break if @mutex.synchronize { @stopping || @stopped || @network_is_down } run_once rescue AMQ::Protocol::EmptyResponseError, IOError, SystemCallError, Timeout::Error, OpenSSL::OpenSSLError => e break if terminate? || @session.closing? || @session.closed? @network_is_down = true if @session.automatically_recover? log_exception(e, level: :warn) @session.handle_network_failure(e) else log_exception(e) @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e)) end rescue ShutdownSignal => _ @mutex.synchronize { @stopping = true } break rescue Exception => e break if terminate? if !(@session.closing? || @session.closed?) log_exception(e) @network_is_down = true @session_error_handler.raise(Bunny::NetworkFailure.new("caught an unexpected exception in the network loop: #{e.message}", e)) end rescue Errno::EBADF => _ebadf break if terminate? # ignored, happens when we loop after the transport has already been closed @mutex.synchronize { @stopping = true } end end @mutex.synchronize { @stopped = true } end def run_once frame = @transport.read_next_frame return if frame.is_a?(AMQ::Protocol::HeartbeatFrame) if !frame.final? || frame.method_class.has_content? header = @transport.read_next_frame content = '' if header.body_size > 0 loop do body_frame = @transport.read_next_frame content << body_frame.decode_payload break if content.bytesize >= header.body_size end end @session.handle_frameset(frame.channel, [frame.decode_payload, header.decode_payload, content]) else @session.handle_frame(frame.channel, frame.decode_payload) end end def stop @mutex.synchronize { @stopping = true } end def stopped? @mutex.synchronize { @stopped } end def stopping? @mutex.synchronize { @stopping } end def terminate_with(e) @mutex.synchronize { @stopping = true } self.raise(e) end def raise(e) @thread.raise(e) if @thread end def join # Thread#join can/would trigger a re-raise of an unhandled exception in this thread. # In addition, Thread.handle_interrupt can be used by other libraries or application code # that would make this join operation fail with an obscure exception. # So we try to save everyone some really unpleasant debugging time by introducing # this condition which typically would not evaluate to true anyway. # # See ruby-amqp/bunny#589 and ruby-amqp/bunny#590 for background. @thread.join if @thread && @thread != Thread.current end def kill if @thread @thread.kill @thread.join end end protected def log_exception(e, level: :error) if !(io_error?(e) && (@session.closing? || @session.closed?)) @logger.send level, "Exception in the reader loop: #{e.class.name}: #{e.message}" @logger.send level, "Backtrace: " e.backtrace.each do |line| @logger.send level, "\t#{line}" end end end def io_error?(e) [AMQ::Protocol::EmptyResponseError, IOError, SystemCallError].any? do |klazz| e.is_a?(klazz) end end def terminate? @mutex.synchronize { @stopping || @stopped } end end end ruby-amqp-bunny-b6569cd/lib/bunny/return_info.rb000066400000000000000000000030051464043542000217640ustar00rootroot00000000000000module Bunny # Wraps AMQ::Protocol::Basic::Return to # provide access to the delivery properties as immutable hash as # well as methods. class ReturnInfo # # Behaviors # include Enumerable # # API # def initialize(basic_return) @basic_return = basic_return @hash = { :reply_code => basic_return.reply_code, :reply_text => basic_return.reply_text, :exchange => basic_return.exchange, :routing_key => basic_return.routing_key } end # Iterates over the returned delivery properties # @see Enumerable#each def each(*args, &block) @hash.each(*args, &block) end # Accesses returned delivery properties by key # @see Hash#[] def [](k) @hash[k] end # @return [Hash] Hash representation of this returned delivery info def to_hash @hash end # @private def to_s to_hash.to_s end # @private def inspect to_hash.inspect end # @return [Integer] Reply (status) code of the cause def reply_code @basic_return.reply_code end # @return [Integer] Reply (status) text of the cause, explaining why the message was returned def reply_text @basic_return.reply_text end # @return [String] Exchange the message was published to def exchange @basic_return.exchange end # @return [String] Routing key the message has def routing_key @basic_return.routing_key end end end ruby-amqp-bunny-b6569cd/lib/bunny/session.rb000066400000000000000000001513501464043542000211240ustar00rootroot00000000000000require "socket" require "thread" require "monitor" require "bunny/transport" require "bunny/channel_id_allocator" require "bunny/heartbeat_sender" require "bunny/reader_loop" require "bunny/authentication/credentials_encoder" require "bunny/authentication/plain_mechanism_encoder" require "bunny/authentication/external_mechanism_encoder" if defined?(JRUBY_VERSION) require "bunny/concurrent/linked_continuation_queue" else require "bunny/concurrent/continuation_queue" end require "amq/protocol/client" require "amq/settings" module Bunny # Represents AMQP 0.9.1 connection (to a RabbitMQ node). # @see http://rubybunny.info/articles/connecting.html Connecting to RabbitMQ guide class Session # Default host used for connection DEFAULT_HOST = "127.0.0.1" # Default virtual host used for connection DEFAULT_VHOST = "/" # Default username used for connection DEFAULT_USER = "guest" # Default password used for connection DEFAULT_PASSWORD = "guest" # Default heartbeat interval, the same value as RabbitMQ 3.0 uses. DEFAULT_HEARTBEAT = :server # @private DEFAULT_FRAME_MAX = 131072 # Hard limit the user cannot go over regardless of server configuration. # @private CHANNEL_MAX_LIMIT = 65535 DEFAULT_CHANNEL_MAX = 2047 # backwards compatibility # @private CONNECT_TIMEOUT = Transport::DEFAULT_CONNECTION_TIMEOUT # @private DEFAULT_CONTINUATION_TIMEOUT = 15000 # RabbitMQ client metadata DEFAULT_CLIENT_PROPERTIES = { :capabilities => { :publisher_confirms => true, :consumer_cancel_notify => true, :exchange_exchange_bindings => true, :"basic.nack" => true, :"connection.blocked" => true, # See http://www.rabbitmq.com/auth-notification.html :authentication_failure_close => true }, :product => "Bunny", :platform => ::RUBY_DESCRIPTION, :version => Bunny::VERSION, :information => "http://rubybunny.info", } # @private DEFAULT_LOCALE = "en_GB" # Default reconnection interval for TCP connection failures DEFAULT_NETWORK_RECOVERY_INTERVAL = 5.0 DEFAULT_RECOVERABLE_EXCEPTIONS = [StandardError, TCPConnectionFailedForAllHosts, TCPConnectionFailed, AMQ::Protocol::EmptyResponseError, SystemCallError, Timeout::Error, Bunny::ConnectionLevelException, Bunny::ConnectionClosedError] # # API # # @return [Bunny::Transport] attr_reader :transport attr_reader :status, :heartbeat, :user, :pass, :vhost, :frame_max, :channel_max, :threaded attr_reader :server_capabilities, :server_properties, :server_authentication_mechanisms, :server_locales attr_reader :channel_id_allocator # Authentication mechanism, e.g. "PLAIN" or "EXTERNAL" # @return [String] attr_reader :mechanism # @return [Logger] attr_reader :logger # @return [Integer] Timeout for blocking protocol operations (queue.declare, queue.bind, etc), in milliseconds. Default is 15000. attr_reader :continuation_timeout attr_reader :network_recovery_interval attr_reader :connection_name attr_accessor :socket_configurator attr_accessor :recoverable_exceptions # @param [String, Hash] connection_string_or_opts Connection string or a hash of connection options # @param [Hash] optz Extra options not related to connection # # @option connection_string_or_opts [String] :host ("127.0.0.1") Hostname or IP address to connect to # @option connection_string_or_opts [Array] :hosts (["127.0.0.1"]) list of hostname or IP addresses to select hostname from when connecting # @option connection_string_or_opts [Array] :addresses (["127.0.0.1:5672"]) list of addresses to select hostname and port from when connecting # @option connection_string_or_opts [Integer] :port (5672) Port RabbitMQ listens on # @option connection_string_or_opts [String] :username ("guest") Username # @option connection_string_or_opts [String] :password ("guest") Password # @option connection_string_or_opts [String] :vhost ("/") Virtual host to use # @option connection_string_or_opts [Integer, Symbol] :heartbeat (:server) Heartbeat timeout to offer to the server. :server means use the value suggested by RabbitMQ. 0 means heartbeats and socket read timeouts will be disabled (not recommended). # @option connection_string_or_opts [Integer] :network_recovery_interval (4) Recovery interval periodic network recovery will use. This includes initial pause after network failure. # @option connection_string_or_opts [Boolean] :tls (false) Should TLS/SSL be used? # @option connection_string_or_opts [String] :tls_cert (nil) Path to client TLS/SSL certificate file (.pem) # @option connection_string_or_opts [String] :tls_key (nil) Path to client TLS/SSL private key file (.pem) # @option connection_string_or_opts [Array] :tls_ca_certificates Array of paths to TLS/SSL CA files (.pem), by default detected from OpenSSL configuration # @option connection_string_or_opts [String] :verify_peer (true) Whether TLS peer verification should be performed # @option connection_string_or_opts [Symbol] :tls_protocol (negotiated) What TLS version should be used (:TLSv1, :TLSv1_1, or :TLSv1_2) # @option connection_string_or_opts [Integer] :channel_max (2047) Maximum number of channels allowed on this connection, minus 1 to account for the special channel 0. # @option connection_string_or_opts [Integer] :continuation_timeout (15000) Timeout for client operations that expect a response (e.g. {Bunny::Queue#get}), in milliseconds. # @option connection_string_or_opts [Integer] :connection_timeout (30) Timeout in seconds for connecting to the server. # @option connection_string_or_opts [Integer] :read_timeout (30) TCP socket read timeout in seconds. If heartbeats are disabled this will be ignored. # @option connection_string_or_opts [Integer] :write_timeout (30) TCP socket write timeout in seconds. # @option connection_string_or_opts [Proc] :hosts_shuffle_strategy a callable that reorders a list of host strings, defaults to Array#shuffle # @option connection_string_or_opts [Proc] :recovery_completed a callable that will be called when a network recovery is performed # @option connection_string_or_opts [Logger] :logger The logger. If missing, one is created using :log_file and :log_level. # @option connection_string_or_opts [IO, String] :log_file The file or path to use when creating a logger. Defaults to STDOUT. # @option connection_string_or_opts [IO, String] :logfile DEPRECATED: use :log_file instead. The file or path to use when creating a logger. Defaults to STDOUT. # @option connection_string_or_opts [Integer] :log_level The log level to use when creating a logger. Defaults to LOGGER::WARN # @option connection_string_or_opts [Boolean] :automatically_recover (true) Should automatically recover from network failures? # @option connection_string_or_opts [Integer] :recovery_attempts (nil) Max number of recovery attempts, nil means forever # @option connection_string_or_opts [Integer] :reset_recovery_attempts_after_reconnection (true) Should recovery attempt counter be reset after successful reconnection? When set to false, the attempt counter will last through the entire lifetime of the connection object. # @option connection_string_or_opts [Proc] :recovery_attempt_started (nil) Will be called before every connection recovery attempt # @option connection_string_or_opts [Proc] :recovery_completed (nil) Will be called after successful connection recovery # @option connection_string_or_opts [Proc] :recovery_attempts_exhausted (nil) Will be called when the connection recovery failed after the specified amount of recovery attempts # @option connection_string_or_opts [Boolean] :recover_from_connection_close (true) Should this connection recover after receiving a server-sent connection.close (e.g. connection was force closed)? # @option connection_string_or_opts [Object] :session_error_handler (Thread.current) Object which responds to #raise that will act as a session error handler. Defaults to Thread.current, which will raise asynchronous exceptions in the thread that created the session. # # @option optz [String] :auth_mechanism ("PLAIN") Authentication mechanism, PLAIN or EXTERNAL # @option optz [String] :locale ("PLAIN") Locale RabbitMQ should use # @option optz [String] :connection_name (nil) Client-provided connection name, if any. Note that the value returned does not uniquely identify a connection and cannot be used as a connection identifier in HTTP API requests. # # @see http://rubybunny.info/articles/connecting.html Connecting to RabbitMQ guide # @see http://rubybunny.info/articles/tls.html TLS/SSL guide # @api public def initialize(connection_string_or_opts = ENV['RABBITMQ_URL'], optz = Hash.new) opts = case (connection_string_or_opts) when nil then Hash.new when String then self.class.parse_uri(connection_string_or_opts) when Hash then connection_string_or_opts end.merge(optz) @default_hosts_shuffle_strategy = Proc.new { |hosts| hosts.shuffle } @opts = opts log_file = opts[:log_file] || opts[:logfile] || STDOUT log_level = opts[:log_level] || ENV["BUNNY_LOG_LEVEL"] || Logger::WARN # we might need to log a warning about ill-formatted IPv6 address but # progname includes hostname, so init like this first @logger = opts.fetch(:logger, init_default_logger_without_progname(log_file, log_level)) @addresses = self.addresses_from(opts) @address_index = 0 @transport = nil @user = self.username_from(opts) @pass = self.password_from(opts) @vhost = self.vhost_from(opts) @threaded = opts.fetch(:threaded, true) # re-init, see above @logger = opts.fetch(:logger, init_default_logger(log_file, log_level)) validate_connection_options(opts) @last_connection_error = nil # should automatic recovery from network failures be used? @automatically_recover = if opts[:automatically_recover].nil? && opts[:automatic_recovery].nil? true else opts[:automatically_recover] | opts[:automatic_recovery] end @recovering_from_network_failure = false @max_recovery_attempts = opts[:recovery_attempts] @recovery_attempts = @max_recovery_attempts # When this is set, connection attempts won't be reset after # successful reconnection. Some find this behavior more sensible # than the per-failure attempt counter. MK. @reset_recovery_attempt_counter_after_reconnection = opts.fetch(:reset_recovery_attempts_after_reconnection, true) @network_recovery_interval = opts.fetch(:network_recovery_interval, DEFAULT_NETWORK_RECOVERY_INTERVAL) @recover_from_connection_close = opts.fetch(:recover_from_connection_close, true) # in ms @continuation_timeout = opts.fetch(:continuation_timeout, DEFAULT_CONTINUATION_TIMEOUT) @status = :not_connected @manually_closed = false @blocked = false # these are negotiated with the broker during the connection tuning phase @client_frame_max = opts.fetch(:frame_max, DEFAULT_FRAME_MAX) @client_channel_max = normalize_client_channel_max(opts.fetch(:channel_max, DEFAULT_CHANNEL_MAX)) # will be-renegotiated during connection tuning steps. MK. @channel_max = @client_channel_max @heartbeat_sender = nil @client_heartbeat = self.heartbeat_from(opts) client_props = opts[:properties] || opts[:client_properties] || {} @connection_name = client_props[:connection_name] || opts[:connection_name] @client_properties = DEFAULT_CLIENT_PROPERTIES.merge(client_props) .merge(connection_name: connection_name) @mechanism = normalize_auth_mechanism(opts.fetch(:auth_mechanism, "PLAIN")) @credentials_encoder = credentials_encoder_for(@mechanism) @locale = @opts.fetch(:locale, DEFAULT_LOCALE) @mutex_impl = @opts.fetch(:mutex_impl, Monitor) # mutex for the channel id => channel hash @channel_mutex = @mutex_impl.new # transport operations/continuations mutex. A workaround for # the non-reentrant Ruby mutexes. MK. @transport_mutex = @mutex_impl.new @status_mutex = @mutex_impl.new @address_index_mutex = @mutex_impl.new @channels = Hash.new @recovery_attempt_started = opts[:recovery_attempt_started] @recovery_completed = opts[:recovery_completed] @recovery_attempts_exhausted = opts[:recovery_attempts_exhausted] @session_error_handler = opts.fetch(:session_error_handler, Thread.current) @recoverable_exceptions = DEFAULT_RECOVERABLE_EXCEPTIONS.dup self.reset_continuations self.initialize_transport end def validate_connection_options(options) if options[:hosts] && options[:addresses] raise ArgumentError, "Connection options can't contain hosts and addresses at the same time" end if (options[:host] || options[:hostname]) && (options[:hosts] || options[:addresses]) @logger.warn "Connection options contain both a host and an array of hosts (addresses), please pick one." end end # @return [String] RabbitMQ hostname (or IP address) used def hostname; self.host; end # @return [String] Username used def username; self.user; end # @return [String] Password used def password; self.pass; end # @return [String] Virtual host used def virtual_host; self.vhost; end # @deprecated # @return [Integer] Heartbeat timeout (not interval) used def heartbeat_interval; self.heartbeat; end # @return [Integer] Heartbeat timeout used def heartbeat_timeout; self.heartbeat; end # @return [Boolean] true if this connection uses TLS (SSL) def uses_tls? @transport.uses_tls? end alias tls? uses_tls? # @return [Boolean] true if this connection uses TLS (SSL) def uses_ssl? @transport.uses_ssl? end alias ssl? uses_ssl? # @return [Boolean] true if this connection uses a separate thread for I/O activity def threaded? @threaded end def host @transport ? @transport.host : host_from_address(@addresses[@address_index]) end def port @transport ? @transport.port : port_from_address(@addresses[@address_index]) end def reset_address_index @address_index_mutex.synchronize { @address_index = 0 } end # @private attr_reader :mutex_impl # Provides a way to fine tune the socket used by connection. # Accepts a block that the socket will be yielded to. def configure_socket(&block) raise ArgumentError, "No block provided!" if block.nil? @transport.configure_socket(&block) end # @return [Integer] Client socket port def local_port @transport.local_address.ip_port end # Starts the connection process. # # @see http://rubybunny.info/articles/getting_started.html # @see http://rubybunny.info/articles/connecting.html # @api public def start return self if connected? @status_mutex.synchronize { @status = :connecting } # reset here for cases when automatic network recovery kicks in # when we were blocked. MK. @blocked = false self.reset_continuations begin begin # close existing transport if we have one, # to not leak sockets @transport.maybe_initialize_socket @transport.post_initialize_socket @transport.connect self.init_connection self.open_connection @reader_loop = nil self.start_reader_loop if threaded? rescue TCPConnectionFailed => e @logger.warn e.message self.initialize_transport @logger.warn "Will try to connect to the next endpoint in line: #{@transport.host}:#{@transport.port}" return self.start rescue @status_mutex.synchronize { @status = :not_connected } raise end rescue HostListDepleted self.reset_address_index @status_mutex.synchronize { @status = :not_connected } raise TCPConnectionFailedForAllHosts end @status_mutex.synchronize { @manually_closed = false } self end def update_secret(value, reason) @transport.send_frame(AMQ::Protocol::Connection::UpdateSecret.encode(value, reason)) @last_update_secret_ok = wait_on_continuations raise_if_continuation_resulted_in_a_connection_error! @last_update_secret_ok end # Socket operation write timeout used by this connection # @return [Integer] # @private def transport_write_timeout @transport.write_timeout end # Opens a new channel and returns it. This method will block the calling # thread until the response is received and the channel is guaranteed to be # opened (this operation is very fast and inexpensive). # # @return [Bunny::Channel] Newly opened channel def create_channel(n = nil, consumer_pool_size = 1, consumer_pool_abort_on_exception = false, consumer_pool_shutdown_timeout = 60) raise ArgumentError, "channel number 0 is reserved in the protocol and cannot be used" if 0 == n raise ConnectionAlreadyClosed if manually_closed? raise RuntimeError, "this connection is not open. Was Bunny::Session#start invoked? Is automatic recovery enabled?" if !connected? @channel_mutex.synchronize do if n && (ch = @channels[n]) ch else ch = Bunny::Channel.new(self, n, ConsumerWorkPool.new(consumer_pool_size || 1, consumer_pool_abort_on_exception, consumer_pool_shutdown_timeout)) ch.open ch end end end alias channel create_channel # Closes the connection. This involves closing all of its channels. def close(await_response = true) @status_mutex.synchronize { @status = :closing } ignoring_io_errors do if @transport.open? @logger.debug "Transport is still open..." close_all_channels @logger.debug "Will close all channels...." self.close_connection(await_response) end clean_up_on_shutdown end @status_mutex.synchronize do @status = :closed @manually_closed = true end @logger.debug "Connection is closed" true end alias stop close # Creates a temporary channel, yields it to the block given to this # method and closes it. # # @return [Bunny::Session] self def with_channel(n = nil) ch = create_channel(n) begin yield ch ensure ch.close if ch.open? end self end # @return [Boolean] true if this connection is still not fully open def connecting? status == :connecting end # @return [Boolean] true if this AMQP 0.9.1 connection is closing # @api private def closing? @status_mutex.synchronize { @status == :closing } end # @return [Boolean] true if this AMQP 0.9.1 connection is closed def closed? @status_mutex.synchronize { @status == :closed } end # @return [Boolean] true if this AMQP 0.9.1 connection has been closed by the user (as opposed to the server) def manually_closed? @status_mutex.synchronize { @manually_closed == true } end # @return [Boolean] true if this AMQP 0.9.1 connection is open def open? @status_mutex.synchronize do (status == :open || status == :connected || status == :connecting) && @transport.open? end end alias connected? open? # @return [Boolean] true if this connection has automatic recovery from network failure enabled def automatically_recover? @automatically_recover end # Defines a callback that will be executed when RabbitMQ blocks the connection # because it is running low on memory or disk space (as configured via config file # and/or rabbitmqctl). # # @yield [AMQ::Protocol::Connection::Blocked] connection.blocked method which provides a reason for blocking # # @api public def on_blocked(&block) @block_callback = block end # Defines a callback that will be executed when RabbitMQ unblocks the connection # that was previously blocked, e.g. because the memory or disk space alarm has cleared. # # @see #on_blocked # @api public def on_unblocked(&block) @unblock_callback = block end # @return [Boolean] true if the connection is currently blocked by RabbitMQ because it's running low on # RAM, disk space, or other resource; false otherwise # @see #on_blocked # @see #on_unblocked def blocked? @blocked end # Parses an amqp[s] URI into a hash that {Bunny::Session#initialize} accepts. # # @param [String] uri amqp or amqps URI to parse # @return [Hash] Parsed URI as a hash def self.parse_uri(uri) AMQ::Settings.configure(uri) end # Checks if a queue with given name exists. # # Implemented using queue.declare # with passive set to true and a one-off (short lived) channel # under the hood. # # @param [String] name Queue name # @return [Boolean] true if queue exists def queue_exists?(name) ch = create_channel begin ch.queue(name, :passive => true) true rescue Bunny::NotFound => _ false ensure ch.close if ch.open? end end # Checks if a exchange with given name exists. # # Implemented using exchange.declare # with passive set to true and a one-off (short lived) channel # under the hood. # # @param [String] name Exchange name # @return [Boolean] true if exchange exists def exchange_exists?(name) ch = create_channel begin ch.exchange(name, :passive => true) true rescue Bunny::NotFound => _ false ensure ch.close if ch.open? end end # Defines a callable (e.g. a block) that will be called # before every connection recovery attempt. def before_recovery_attempt_starts(&block) @recovery_attempt_started = block end # Defines a callable (e.g. a block) that will be called # after successful connection recovery. def after_recovery_completed(&block) @recovery_completed = block end # Defines a callable (e.g. a block) that will be called # when the connection recovery failed after the specified # numbers of recovery attempts. def after_recovery_attempts_exhausted(&block) @recovery_attempts_exhausted = block end # # Implementation # # @private def open_channel(ch) @channel_mutex.synchronize do n = ch.number self.register_channel(ch) @transport_mutex.synchronize do @transport.send_frame(AMQ::Protocol::Channel::Open.encode(n, AMQ::Protocol::EMPTY_STRING)) end @last_channel_open_ok = wait_on_continuations raise_if_continuation_resulted_in_a_connection_error! @last_channel_open_ok end end # @private def close_channel(ch) @channel_mutex.synchronize do n = ch.number @transport.send_frame(AMQ::Protocol::Channel::Close.encode(n, 200, "Goodbye", 0, 0)) @last_channel_close_ok = wait_on_continuations raise_if_continuation_resulted_in_a_connection_error! self.unregister_channel(ch) self.release_channel_id(ch.id) @last_channel_close_ok end end # @private def find_channel(number) @channels[number] end # @private def synchronised_find_channel(number) @channel_mutex.synchronize { @channels[number] } end # @private def close_all_channels @channel_mutex.synchronize do @channels.reject {|n, ch| n == 0 || !ch.open? }.each do |_, ch| Bunny::Timeout.timeout(@transport.disconnect_timeout, ClientTimeout) { ch.close } end end end # @private def close_connection(await_response = true) if @transport.open? @logger.debug "Transport is still open" @transport.send_frame(AMQ::Protocol::Connection::Close.encode(200, "Goodbye", 0, 0)) if await_response @logger.debug "Waiting for a connection.close-ok..." @last_connection_close_ok = wait_on_continuations end end shut_down_all_consumer_work_pools! maybe_shutdown_heartbeat_sender @status_mutex.synchronize { @status = :not_connected } end # Handles incoming frames and dispatches them. # # Channel methods (`channel.open-ok`, `channel.close-ok`) are # handled by the session itself. # Connection level errors result in exceptions being raised. # Deliveries and other methods are passed on to channels to dispatch. # # @private def handle_frame(ch_number, method) @logger.debug { "Session#handle_frame on #{ch_number}: #{method.inspect}" } case method when AMQ::Protocol::Channel::OpenOk then @continuations.push(method) when AMQ::Protocol::Channel::CloseOk then @continuations.push(method) when AMQ::Protocol::Connection::Close then if recover_from_connection_close? @logger.warn "Recovering from connection.close (#{method.reply_text})" clean_up_on_shutdown handle_network_failure(instantiate_connection_level_exception(method)) else clean_up_and_fail_on_connection_close!(method) end when AMQ::Protocol::Connection::CloseOk then @last_connection_close_ok = method begin @continuations.clear rescue StandardError => e @logger.error e.class.name @logger.error e.message @logger.error e.backtrace ensure @continuations.push(:__unblock__) end when AMQ::Protocol::Connection::Blocked then @blocked = true @block_callback.call(method) if @block_callback when AMQ::Protocol::Connection::Unblocked then @blocked = false @unblock_callback.call(method) if @unblock_callback when AMQ::Protocol::Connection::UpdateSecretOk then @continuations.push(method) when AMQ::Protocol::Channel::Close then begin ch = synchronised_find_channel(ch_number) # this includes sending a channel.close-ok and # potentially invoking a user-provided callback, # avoid doing that while holding a mutex lock. MK. ch.handle_method(method) ensure if ch.nil? @logger.warn "Received a server-sent channel.close but the channel was not found locally. Ignoring the frame." else # synchronises on @channel_mutex under the hood self.unregister_channel(ch) end end when AMQ::Protocol::Basic::GetEmpty then ch = find_channel(ch_number) ch.handle_basic_get_empty(method) else if ch = find_channel(ch_number) ch.handle_method(method) else @logger.warn "Channel #{ch_number} is not open on this connection!" end end end # @private def raise_if_continuation_resulted_in_a_connection_error! raise @last_connection_error if @last_connection_error end # @private def handle_frameset(ch_number, frames) method = frames.first case method when AMQ::Protocol::Basic::GetOk then @channels[ch_number].handle_basic_get_ok(*frames) when AMQ::Protocol::Basic::GetEmpty then @channels[ch_number].handle_basic_get_empty(*frames) when AMQ::Protocol::Basic::Return then @channels[ch_number].handle_basic_return(*frames) else @channels[ch_number].handle_frameset(*frames) end end # @private def recover_from_connection_close? @recover_from_connection_close end # @private def handle_network_failure(exception) raise NetworkErrorWrapper.new(exception) unless @threaded @status_mutex.synchronize { @status = :disconnected } if !recovering_from_network_failure? begin @recovering_from_network_failure = true if recoverable_network_failure?(exception) announce_network_failure_recovery @channel_mutex.synchronize do @channels.each do |n, ch| ch.maybe_kill_consumer_work_pool! end end @reader_loop.stop if @reader_loop maybe_shutdown_heartbeat_sender recover_from_network_failure else @logger.error "Exception #{exception.message} is considered unrecoverable..." end ensure @recovering_from_network_failure = false end end end # @private def recoverable_network_failure?(exception) @recoverable_exceptions.any? {|x| exception.kind_of? x} end # @private def recovering_from_network_failure? @recovering_from_network_failure end # @private def announce_network_failure_recovery if recovery_attempts_limited? @logger.warn "Will recover from a network failure (#{@recovery_attempts} out of #{@max_recovery_attempts} left)..." else @logger.warn "Will recover from a network failure (no retry limit)..." end end # @private def recover_from_network_failure sleep @network_recovery_interval @logger.debug "Will attempt connection recovery..." notify_of_recovery_attempt_start self.initialize_transport @logger.warn "Retrying connection on next host in line: #{@transport.host}:#{@transport.port}" self.start if open? @recovering_from_network_failure = false @logger.debug "Connection is now open" if @reset_recovery_attempt_counter_after_reconnection @logger.debug "Resetting recovery attempt counter after successful reconnection" reset_recovery_attempt_counter! else @logger.debug "Not resetting recovery attempt counter after successful reconnection, as configured" end recover_channels notify_of_recovery_completion end rescue HostListDepleted reset_address_index retry rescue => e if recoverable_network_failure?(e) @logger.warn "TCP connection failed" if should_retry_recovery? @logger.warn "Reconnecting in #{@network_recovery_interval} seconds" decrement_recovery_attemp_counter! announce_network_failure_recovery retry else @logger.error "Ran out of recovery attempts (limit set to #{@max_recovery_attempts}), giving up" @transport.close self.close(false) @manually_closed = false notify_of_recovery_attempts_exhausted end else raise e end end # @private def recovery_attempts_limited? !!@max_recovery_attempts end # @private def should_retry_recovery? !recovery_attempts_limited? || @recovery_attempts > 1 end # @private def decrement_recovery_attemp_counter! if @recovery_attempts @recovery_attempts -= 1 @logger.debug "#{@recovery_attempts} recovery attempts left" end @recovery_attempts end # @private def reset_recovery_attempt_counter! @recovery_attempts = @max_recovery_attempts end # @private def recover_channels @channel_mutex.synchronize do @channels.each do |n, ch| ch.open ch.recover_from_network_failure end end end # @private def notify_of_recovery_attempt_start @recovery_attempt_started.call if @recovery_attempt_started end # @private def notify_of_recovery_completion @recovery_completed.call if @recovery_completed end # @private def notify_of_recovery_attempts_exhausted @recovery_attempts_exhausted.call if @recovery_attempts_exhausted end # @private def instantiate_connection_level_exception(frame) case frame when AMQ::Protocol::Connection::Close then klass = case frame.reply_code when 320 then ConnectionForced when 501 then FrameError when 503 then CommandInvalid when 504 then ChannelError when 505 then UnexpectedFrame when 506 then ResourceError when 530 then NotAllowedError when 541 then InternalError else raise "Unknown reply code: #{frame.reply_code}, text: #{frame.reply_text}" end klass.new("Connection-level error: #{frame.reply_text}", self, frame) end end def clean_up_and_fail_on_connection_close!(method) @last_connection_error = instantiate_connection_level_exception(method) @continuations.push(method) clean_up_on_shutdown if threaded? @session_error_handler.raise(@last_connection_error) else raise @last_connection_error end end def clean_up_on_shutdown begin shut_down_all_consumer_work_pools! maybe_shutdown_reader_loop maybe_shutdown_heartbeat_sender rescue ShutdownSignal => _sse # no-op rescue Exception => e @logger.warn "Caught an exception when cleaning up after receiving connection.close: #{e.message}" ensure close_transport end end # @private def addresses_from(options) shuffle_strategy = options.fetch(:hosts_shuffle_strategy, @default_hosts_shuffle_strategy) addresses = options[:host] || options[:hostname] || options[:addresses] || options[:hosts] || ["#{DEFAULT_HOST}:#{port_from(options)}"] addresses = [addresses] unless addresses.is_a? Array addrs = addresses.map do |address| host_with_port?(address) ? address : "#{address}:#{port_from(@opts)}" end shuffle_strategy.call(addrs) end # @private def port_from(options) fallback = if options[:tls] || options[:ssl] AMQ::Protocol::TLS_PORT else AMQ::Protocol::DEFAULT_PORT end options.fetch(:port, fallback) end # @private def host_with_port?(address) # we need to handle cases such as [2001:db8:85a3:8d3:1319:8a2e:370:7348]:5671 last_colon = address.rindex(":") last_closing_square_bracket = address.rindex("]") if last_closing_square_bracket.nil? address.include?(":") else last_closing_square_bracket < last_colon end end # @private def host_from_address(address) # we need to handle cases such as [2001:db8:85a3:8d3:1319:8a2e:370:7348]:5671 last_colon = address.rindex(":") last_closing_square_bracket = address.rindex("]") if last_closing_square_bracket.nil? parts = address.split(":") # this looks like an unquoted IPv6 address, so emit a warning if parts.size > 2 @logger.warn "Address #{address} looks like an unquoted IPv6 address. Make sure you quote IPv6 addresses like so: [2001:db8:85a3:8d3:1319:8a2e:370:7348]" end return parts[0] end if last_closing_square_bracket < last_colon # there is a port address[0, last_colon] elsif last_closing_square_bracket > last_colon address end end # @private def port_from_address(address) # we need to handle cases such as [2001:db8:85a3:8d3:1319:8a2e:370:7348]:5671 last_colon = address.rindex(":") last_closing_square_bracket = address.rindex("]") if last_closing_square_bracket.nil? parts = address.split(":") # this looks like an unquoted IPv6 address, so emit a warning if parts.size > 2 @logger.warn "Address #{address} looks like an unquoted IPv6 address. Make sure you quote IPv6 addresses like so: [2001:db8:85a3:8d3:1319:8a2e:370:7348]" end return parts[1].to_i end if last_closing_square_bracket < last_colon # there is a port address[(last_colon + 1)..-1].to_i end end # @private def vhost_from(options) options[:virtual_host] || options[:vhost] || DEFAULT_VHOST end # @private def username_from(options) options[:username] || options[:user] || DEFAULT_USER end # @private def password_from(options) options[:password] || options[:pass] || options[:pwd] || DEFAULT_PASSWORD end # @private def heartbeat_from(options) options[:heartbeat] || options[:heartbeat_timeout] || options[:requested_heartbeat] || options[:heartbeat_interval] || DEFAULT_HEARTBEAT end # @private def next_channel_id @channel_id_allocator.next_channel_id end # @private def release_channel_id(i) @channel_id_allocator.release_channel_id(i) end # @private def register_channel(ch) @channel_mutex.synchronize do @channels[ch.number] = ch end end # @private def unregister_channel(ch) @channel_mutex.synchronize do n = ch.number self.release_channel_id(n) @channels.delete(ch.number) end end # @private def start_reader_loop reader_loop.start end # @private def reader_loop @reader_loop ||= ReaderLoop.new(@transport, self, @session_error_handler) end # @private def maybe_shutdown_reader_loop if @reader_loop @reader_loop.stop if threaded? # this is the easiest way to wait until the loop # is guaranteed to have terminated @reader_loop.terminate_with(ShutdownSignal) # joining the thread here may take forever # on JRuby because sun.nio.ch.KQueueArrayWrapper#kevent0 is # a native method that cannot be (easily) interrupted. # So we use this ugly hack or else our test suite takes forever # to run on JRuby (a new connection is opened/closed per example). MK. if defined?(JRUBY_VERSION) sleep 0.075 else @reader_loop.join end else # single threaded mode, nothing to do. MK. end end @reader_loop = nil end # @private def close_transport begin @transport.close rescue StandardError => e @logger.error "Exception when closing transport:" @logger.error e.class.name @logger.error e.message @logger.error e.backtrace end end # @private def signal_activity! @heartbeat_sender.signal_activity! if @heartbeat_sender end # Sends frame to the peer, checking that connection is open. # Exposed primarily for Bunny::Channel # # @raise [ConnectionClosedError] # @private def send_frame(frame, signal_activity = true) if open? # @transport_mutex.synchronize do # @transport.write(frame.encode) # end @transport.write(frame.encode) signal_activity! if signal_activity else raise ConnectionClosedError.new(frame) end end # Sends frame to the peer, checking that connection is open. # Uses transport implementation that does not perform # timeout control. Exposed primarily for Bunny::Channel. # # @raise [ConnectionClosedError] # @private def send_frame_without_timeout(frame, signal_activity = true) if open? @transport.write_without_timeout(frame.encode) signal_activity! if signal_activity else raise ConnectionClosedError.new(frame) end end # Sends multiple frames, in one go. For thread safety this method takes a channel # object and synchronizes on it. # # @private def send_frameset(frames, channel) # some developers end up sharing channels between threads and when multiple # threads publish on the same channel aggressively, at some point frames will be # delivered out of order and broker will raise 505 UNEXPECTED_FRAME exception. # If we synchronize on the channel, however, this is both thread safe and pretty fine-grained # locking. Note that "single frame" methods technically do not need this kind of synchronization # (no incorrect frame interleaving of the same kind as with basic.publish isn't possible) but we # still recommend not sharing channels between threads except for consumer-only cases in the docs. MK. channel.synchronize do # see rabbitmq/rabbitmq-server#156 if open? data = frames.reduce("") { |acc, frame| acc << frame.encode } @transport.write(data) signal_activity! else raise ConnectionClosedError.new(frames) end end end # send_frameset(frames) # Sends multiple frames, one by one. For thread safety this method takes a channel # object and synchronizes on it. Uses transport implementation that does not perform # timeout control. # # @private def send_frameset_without_timeout(frames, channel) # some developers end up sharing channels between threads and when multiple # threads publish on the same channel aggressively, at some point frames will be # delivered out of order and broker will raise 505 UNEXPECTED_FRAME exception. # If we synchronize on the channel, however, this is both thread safe and pretty fine-grained # locking. See a note about "single frame" methods in a comment in `send_frameset`. MK. channel.synchronize do if open? frames.each { |frame| self.send_frame_without_timeout(frame, false) } signal_activity! else raise ConnectionClosedError.new(frames) end end end # send_frameset_without_timeout(frames) # @private def send_raw_without_timeout(data, channel) # some developers end up sharing channels between threads and when multiple # threads publish on the same channel aggressively, at some point frames will be # delivered out of order and broker will raise 505 UNEXPECTED_FRAME exception. # If we synchronize on the channel, however, this is both thread safe and pretty fine-grained # locking. Note that "single frame" methods do not need this kind of synchronization. MK. channel.synchronize do @transport.write(data) signal_activity! end end # send_frameset_without_timeout(frames) # @return [String] # @api public def to_s oid = ("0x%x" % (self.object_id << 1)) "#<#{self.class.name}:#{oid} #{@user}@#{host}:#{port}, vhost=#{@vhost}, addresses=[#{@addresses.join(',')}]>" end def inspect to_s end protected # @private def init_connection self.send_preamble connection_start = @transport.read_next_frame.decode_payload @server_properties = connection_start.server_properties @server_capabilities = @server_properties["capabilities"] @server_authentication_mechanisms = (connection_start.mechanisms || "").split(" ") @server_locales = Array(connection_start.locales) @status_mutex.synchronize { @status = :connected } end # @private def open_connection @transport.send_frame(AMQ::Protocol::Connection::StartOk.encode(@client_properties, @mechanism, self.encode_credentials(username, password), @locale)) @logger.debug "Sent connection.start-ok" frame = begin fr = @transport.read_next_frame while fr.is_a?(AMQ::Protocol::HeartbeatFrame) fr = @transport.read_next_frame end fr # frame timeout means the broker has closed the TCP connection, which it # does per 0.9.1 spec. rescue nil end if frame.nil? raise TCPConnectionFailed.new('An empty frame was received while opening the connection. In RabbitMQ <= 3.1 this could mean an authentication issue.') end response = frame.decode_payload if response.is_a?(AMQ::Protocol::Connection::Close) @state = :closed @logger.error "Authentication with RabbitMQ failed: #{response.reply_code} #{response.reply_text}" raise Bunny::AuthenticationFailureError.new(self.user, self.vhost, self.password.size) end connection_tune = response @frame_max = negotiate_value(@client_frame_max, connection_tune.frame_max) @channel_max = negotiate_value(@client_channel_max, connection_tune.channel_max) # this allows for disabled heartbeats. MK. @heartbeat = if heartbeat_disabled?(@client_heartbeat) 0 else negotiate_value(@client_heartbeat, connection_tune.heartbeat) end @logger.debug { "Heartbeat interval negotiation: client = #{@client_heartbeat}, server = #{connection_tune.heartbeat}, result = #{@heartbeat}" } @logger.info "Heartbeat interval used (in seconds): #{@heartbeat}" # We set the read_write_timeout to twice the heartbeat value, # and then some padding for edge cases. # This allows us to miss a single heartbeat before we time out the socket. # If heartbeats are disabled, assume that TCP keepalives or a similar mechanism will be used # and disable socket read timeouts. See ruby-amqp/bunny#551. @transport.read_timeout = @heartbeat * 2.2 @logger.debug { "Will use socket read timeout of #{@transport.read_timeout.to_i} seconds" } # if there are existing channels we've just recovered from # a network failure and need to fix the allocated set. See issue 205. MK. if @channels.empty? @logger.debug { "Initializing channel ID allocator with channel_max = #{@channel_max}" } @channel_id_allocator = ChannelIdAllocator.new(@channel_max) end @transport.send_frame(AMQ::Protocol::Connection::TuneOk.encode(@channel_max, @frame_max, @heartbeat)) @logger.debug { "Sent connection.tune-ok with heartbeat interval = #{@heartbeat}, frame_max = #{@frame_max}, channel_max = #{@channel_max}" } @transport.send_frame(AMQ::Protocol::Connection::Open.encode(self.vhost)) @logger.debug { "Sent connection.open with vhost = #{self.vhost}" } frame2 = begin fr = @transport.read_next_frame while fr.is_a?(AMQ::Protocol::HeartbeatFrame) fr = @transport.read_next_frame end fr # frame timeout means the broker has closed the TCP connection, which it # does per 0.9.1 spec. rescue nil end if frame2.nil? raise TCPConnectionFailed.new('An empty frame was received while opening the connection. In RabbitMQ <= 3.1 this could mean an authentication issue.') end connection_open_ok = frame2.decode_payload @status_mutex.synchronize { @status = :open } if @heartbeat && @heartbeat > 0 initialize_heartbeat_sender end unless connection_open_ok.is_a?(AMQ::Protocol::Connection::OpenOk) if connection_open_ok.is_a?(AMQ::Protocol::Connection::Close) e = instantiate_connection_level_exception(connection_open_ok) begin shut_down_all_consumer_work_pools! maybe_shutdown_reader_loop rescue ShutdownSignal => _sse # no-op rescue Exception => e @logger.warn "Caught an exception when cleaning up after receiving connection.close: #{e.message}" ensure close_transport end if threaded? @session_error_handler.raise(e) else raise e end else raise "could not open connection: server did not respond with connection.open-ok but #{connection_open_ok.inspect} instead" end end end def heartbeat_disabled?(val) 0 == val || val.nil? end # @private def negotiate_value(client_value, server_value) return server_value if [:server, "server"].include?(client_value) if client_value == 0 || server_value == 0 [client_value, server_value].max else [client_value, server_value].min end end # @private def initialize_heartbeat_sender maybe_shutdown_heartbeat_sender @logger.debug "Initializing heartbeat sender..." @heartbeat_sender = HeartbeatSender.new(@transport, @logger) @heartbeat_sender.start(@heartbeat) end # @private def maybe_shutdown_heartbeat_sender @heartbeat_sender.stop if @heartbeat_sender end # @private def initialize_transport if address = @addresses[ @address_index ] @address_index_mutex.synchronize { @address_index += 1 } @transport.close rescue nil # Let's make sure the previous transport socket is closed @transport = Transport.new(self, host_from_address(address), port_from_address(address), @opts.merge(:session_error_handler => @session_error_handler) ) # Reset the cached progname for the logger only when no logger was provided @default_logger.progname = self.to_s @transport else raise HostListDepleted end end # @private def maybe_close_transport @transport.close if @transport end # Sends AMQ protocol header (also known as preamble). # @private def send_preamble @transport.write(AMQ::Protocol::PREAMBLE) @logger.debug "Sent protocol preamble" end # @private def encode_credentials(username, password) @credentials_encoder.encode_credentials(username, password) end # encode_credentials(username, password) # @private def credentials_encoder_for(mechanism) Authentication::CredentialsEncoder.for_session(self) end if defined?(JRUBY_VERSION) # @private def reset_continuations @continuations = Concurrent::LinkedContinuationQueue.new end else # @private def reset_continuations @continuations = Concurrent::ContinuationQueue.new end end # @private def wait_on_continuations unless @threaded reader_loop.run_once until @continuations.length > 0 end @continuations.poll(@continuation_timeout) end # @private def init_default_logger(logfile, level) @default_logger = begin lgr = ::Logger.new(logfile) lgr.level = normalize_log_level(level) lgr.progname = self.to_s lgr end end # @private def init_default_logger_without_progname(logfile, level) @default_logger = begin lgr = ::Logger.new(logfile) lgr.level = normalize_log_level(level) lgr end end # @private def normalize_log_level(level) case level when :debug, Logger::DEBUG, "debug" then Logger::DEBUG when :info, Logger::INFO, "info" then Logger::INFO when :warn, Logger::WARN, "warn" then Logger::WARN when :error, Logger::ERROR, "error" then Logger::ERROR when :fatal, Logger::FATAL, "fatal" then Logger::FATAL else Logger::WARN end end # @private def shut_down_all_consumer_work_pools! @channels.each do |_, ch| ch.maybe_kill_consumer_work_pool! end end def normalize_client_channel_max(n) return CHANNEL_MAX_LIMIT if n.nil? return CHANNEL_MAX_LIMIT if n > CHANNEL_MAX_LIMIT case n when 0 then CHANNEL_MAX_LIMIT else n end end def normalize_auth_mechanism(value) case value when [] then "PLAIN" when nil then "PLAIN" else value end end def ignoring_io_errors(&block) begin block.call rescue AMQ::Protocol::EmptyResponseError, IOError, SystemCallError, Bunny::NetworkFailure => _ # ignore end end end # Session # backwards compatibility Client = Session end ruby-amqp-bunny-b6569cd/lib/bunny/socket.rb000066400000000000000000000003251464043542000207240ustar00rootroot00000000000000# See #165. MK. if defined?(JRUBY_VERSION) require "bunny/jruby/socket" module Bunny SocketImpl = JRuby::Socket end else require "bunny/cruby/socket" module Bunny SocketImpl = Socket end end ruby-amqp-bunny-b6569cd/lib/bunny/ssl_socket.rb000066400000000000000000000003511464043542000216040ustar00rootroot00000000000000# See #165. MK. if defined?(JRUBY_VERSION) require "bunny/jruby/ssl_socket" module Bunny SSLSocketImpl = JRuby::SSLSocket end else require "bunny/cruby/ssl_socket" module Bunny SSLSocketImpl = SSLSocket end end ruby-amqp-bunny-b6569cd/lib/bunny/test_kit.rb000066400000000000000000000020221464043542000212560ustar00rootroot00000000000000# -*- coding: utf-8 -*- require "timeout" module Bunny # Unit, integration and stress testing toolkit class TestKit class << self def poll_while(timeout = 60, &probe) Timeout.timeout(timeout) { sleep 0.1 while probe.call } end def poll_until(timeout = 60, &probe) Timeout.timeout(timeout) { sleep 0.1 until probe.call } end # @return [Integer] Random integer in the range of [a, b] # @api private def random_in_range(a, b) Range.new(a, b).to_a.sample end # @param [Integer] a Lower bound of message size, in KB # @param [Integer] b Upper bound of message size, in KB # @param [Integer] i Random number to use in message generation # @return [String] Message payload of length in the given range, with non-ASCII characters # @api public def message_in_kb(a, b, i) s = "Ю#{i}" n = random_in_range(a, b) / s.bytesize s * n * 1024 end end end end ruby-amqp-bunny-b6569cd/lib/bunny/timeout.rb000066400000000000000000000001431464043542000211200ustar00rootroot00000000000000module Bunny Timeout = ::Timeout # Backwards compatibility # @private Timer = Timeout end ruby-amqp-bunny-b6569cd/lib/bunny/timestamp.rb000066400000000000000000000005641464043542000214440ustar00rootroot00000000000000module Bunny # Abstracts away the Ruby (OS) method of retriving timestamps. # # @private class Timestamp def self.now ::Time.now end def self.monotonic Process.clock_gettime(Process::CLOCK_MONOTONIC) end def self.non_monotonic ::Time.now end def self.non_monotonic_utc self.non_monotonic.utc end end endruby-amqp-bunny-b6569cd/lib/bunny/transport.rb000066400000000000000000000420001464043542000214640ustar00rootroot00000000000000require "socket" require "thread" require "monitor" begin require "openssl" rescue LoadError => _le $stderr.puts "Could not load OpenSSL" end require "bunny/exceptions" require "bunny/socket" module Bunny # @private class Transport # # API # # Default TCP connection timeout DEFAULT_CONNECTION_TIMEOUT = 30.0 DEFAULT_READ_TIMEOUT = 30.0 DEFAULT_WRITE_TIMEOUT = 30.0 # mimics METHODS_MAP in ssl.rb but also lists TLS 1.3 # and string constants TLS_VERSION_ALIASES = { TLSv1: OpenSSL::SSL::TLS1_VERSION, TLSv1_1: OpenSSL::SSL::TLS1_1_VERSION, TLSv1_2: OpenSSL::SSL::TLS1_2_VERSION, "1.0": OpenSSL::SSL::TLS1_VERSION, "1.1": OpenSSL::SSL::TLS1_1_VERSION, "1.2": OpenSSL::SSL::TLS1_2_VERSION, OpenSSL::SSL::TLS1_VERSION => OpenSSL::SSL::TLS1_VERSION, OpenSSL::SSL::TLS1_1_VERSION => OpenSSL::SSL::TLS1_1_VERSION, OpenSSL::SSL::TLS1_2_VERSION => OpenSSL::SSL::TLS1_2_VERSION } # older OpenSSL versions won't support for TLS 1.3 and won't # have this constant defined. if defined?(OpenSSL::SSL::TLS1_3_VERSION) TLS_VERSION_ALIASES["1.3"] = OpenSSL::SSL::TLS1_3_VERSION TLS_VERSION_ALIASES[:TLSv1_3] = OpenSSL::SSL::TLS1_3_VERSION TLS_VERSION_ALIASES[OpenSSL::SSL::TLS1_3_VERSION] = OpenSSL::SSL::TLS1_3_VERSION end TLS_VERSION_ALIASES.freeze attr_reader :session, :host, :port, :socket, :connect_timeout, :read_timeout, :write_timeout, :disconnect_timeout attr_reader :tls_context, :verify_peer, :tls_ca_certificates, :tls_certificate_path, :tls_key_path def read_timeout=(v) @read_timeout = v @read_timeout = nil if @read_timeout == 0 end def initialize(session, host, port, opts) @session = session @session_error_handler = opts[:session_error_handler] @host = host @port = port @opts = opts @logger = session.logger @tls_enabled = tls_enabled?(opts) @read_timeout = opts[:read_timeout] || DEFAULT_READ_TIMEOUT @read_timeout = nil if @read_timeout == 0 @write_timeout = opts[:socket_timeout] # Backwards compatability @write_timeout ||= opts[:write_timeout] || DEFAULT_WRITE_TIMEOUT @write_timeout = nil if @write_timeout == 0 @connect_timeout = self.timeout_from(opts) @connect_timeout = nil if @connect_timeout == 0 @disconnect_timeout = @write_timeout || @read_timeout || @connect_timeout @writes_mutex = @session.mutex_impl.new @socket = nil prepare_tls_context(opts) if @tls_enabled end def hostname @host end def local_address @socket.local_address end def uses_tls? @tls_enabled end alias tls? uses_tls? def uses_ssl? @tls_enabled end alias ssl? uses_ssl? def connect if uses_tls? begin @socket.connect rescue OpenSSL::SSL::SSLError => e @logger.error { "TLS connection failed: #{e.message}" } raise e end log_peer_certificate_info(Logger::DEBUG, @socket.peer_cert) log_peer_certificate_chain_info(Logger::DEBUG, @socket.peer_cert_chain) begin @socket.post_connection_check(host) if @verify_peer rescue OpenSSL::SSL::SSLError => e @logger.error do msg = "Peer verification of target server failed: #{e.message}. " msg += "Target hostname: #{hostname}, see peer certificate chain details below." msg end log_peer_certificate_info(Logger::ERROR, @socket.peer_cert) log_peer_certificate_chain_info(Logger::ERROR, @socket.peer_cert_chain) raise e end @status = :connected @socket else # no-op end end def connected? :connected == @status && open? end def configure_socket(&block) block.call(@socket) if @socket end def configure_tls_context(&block) block.call(@tls_context) if @tls_context end if defined?(JRUBY_VERSION) # Writes data to the socket. def write(data) return write_without_timeout(data) unless @write_timeout begin if open? @writes_mutex.synchronize do @socket.write(data) end end rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e @logger.error "Got an exception when sending data: #{e.message} (#{e.class.name})" close @status = :not_connected if @session.automatically_recover? @session.handle_network_failure(e) else @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e)) end end end else # Writes data to the socket. If read/write timeout was specified the operation will return after that # amount of time has elapsed waiting for the socket. def write(data) return write_without_timeout(data) unless @write_timeout begin if open? @writes_mutex.synchronize do @socket.write_nonblock_fully(data, @write_timeout) end end rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e @logger.error "Got an exception when sending data: #{e.message} (#{e.class.name})" close @status = :not_connected if @session.automatically_recover? @session.handle_network_failure(e) else @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e)) end end end end # Writes data to the socket without timeout checks def write_without_timeout(data, raise_exceptions = false) begin @writes_mutex.synchronize { @socket.write(data) } @socket.flush rescue SystemCallError, Bunny::ConnectionError, IOError => e close raise e if raise_exceptions if @session.automatically_recover? @session.handle_network_failure(e) else @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e)) end end end # Sends frame to the peer. # # @raise [ConnectionClosedError] # @private def send_frame(frame) if closed? @session.handle_network_failure(ConnectionClosedError.new(frame)) else write(frame.encode) end end # Sends frame to the peer without timeout control. # # @raise [ConnectionClosedError] # @private def send_frame_without_timeout(frame) if closed? @session.handle_network_failure(ConnectionClosedError.new(frame)) else write_without_timeout(frame.encode) end end def close(reason = nil) @socket.close if open? end def open? @socket && !@socket.closed? end def closed? !open? end def flush @socket.flush if @socket end def read_fully(count) begin @socket.read_fully(count, @read_timeout) rescue SystemCallError, Timeout::Error, Bunny::ConnectionError, IOError => e @logger.error "Got an exception when receiving data: #{e.message} (#{e.class.name})" close @status = :not_connected if @session.automatically_recover? raise else @session_error_handler.raise(Bunny::NetworkFailure.new("detected a network failure: #{e.message}", e)) end end end def read_ready?(timeout = nil) io = IO.select([@socket].compact, nil, nil, timeout) io && io[0].include?(@socket) end # Exposed primarily for Bunny::Channel # @private def read_next_frame(opts = {}) header = read_fully(7) type, channel, size = AMQ::Protocol::Frame.decode_header(header) payload = if size > 0 read_fully(size) else '' end frame_end = read_fully(1) # 1) the size is miscalculated if payload.bytesize != size raise BadLengthError.new(size, payload.bytesize) end # 2) the size is OK, but the string doesn't end with FINAL_OCTET raise NoFinalOctetError.new if frame_end != AMQ::Protocol::Frame::FINAL_OCTET AMQ::Protocol::Frame.new(type, payload, channel) end def self.reacheable?(host, port, timeout) begin s = Bunny::SocketImpl.open(host, port, :connect_timeout => timeout) true rescue SocketError, Timeout::Error => _e false ensure s.close if s end end def self.ping!(host, port, timeout) raise ConnectionTimeout.new("#{host}:#{port} is unreachable") if !reacheable?(host, port, timeout) end def initialize_socket begin @socket = Bunny::SocketImpl.open(@host, @port, :keepalive => @opts[:keepalive], :connect_timeout => @connect_timeout) rescue StandardError, ClientTimeout => e @status = :not_connected raise Bunny::TCPConnectionFailed.new(e, self.hostname, self.port) end @socket end def maybe_initialize_socket initialize_socket if !@socket || closed? end def post_initialize_socket @socket = if uses_tls? and !@socket.is_a?(Bunny::SSLSocketImpl) wrap_in_tls_socket(@socket) else @socket end end protected def tls_enabled?(opts) return !!opts[:tls] unless opts[:tls].nil? return !!opts[:ssl] unless opts[:ssl].nil? (opts[:port] == AMQ::Protocol::TLS_PORT) || false end def tls_ca_certificates_paths_from(opts) Array(opts[:cacertfile] || opts[:tls_ca_certificates] || opts[:ssl_ca_certificates]) end def tls_certificate_path_from(opts) opts[:certfile] || opts[:tls_cert] || opts[:ssl_cert] || opts[:tls_cert_path] || opts[:ssl_cert_path] || opts[:tls_certificate_path] || opts[:ssl_certificate_path] end def tls_key_path_from(opts) opts[:keyfile] || opts[:tls_key] || opts[:ssl_key] || opts[:tls_key_path] || opts[:ssl_key_path] end def tls_certificate_from(opts) begin read_client_certificate! rescue MissingTLSCertificateFile => _e inline_client_certificate_from(opts) end end def tls_key_from(opts) begin read_client_key! rescue MissingTLSKeyFile => _e inline_client_key_from(opts) end end def peer_certificate_info(peer_cert, prefix = "Peer's leaf certificate") exts = peer_cert.extensions.map { |x| x.value } # Subject Alternative Names sans = exts.select { |s| s =~ /^DNS/ }.map { |s| s.gsub(/^DNS:/, "") } msg = "#{prefix} subject: #{peer_cert.subject}, " msg += "subject alternative names: #{sans.join(', ')}, " msg += "issuer: #{peer_cert.issuer}, " msg += "not valid after: #{peer_cert.not_after}, " msg += "X.509 usage extensions: #{exts.join(', ')}" msg end def log_peer_certificate_info(severity, peer_cert, prefix = "Peer's leaf certificate") @logger.add(severity) { peer_certificate_info(peer_cert, prefix) } end def log_peer_certificate_chain_info(severity, chain) chain.each do |cert| self.log_peer_certificate_info(severity, cert, "Peer's certificate chain entry") end end def inline_client_certificate_from(opts) opts[:tls_certificate] || opts[:ssl_cert_string] || opts[:tls_cert] end def inline_client_key_from(opts) opts[:tls_key] || opts[:ssl_key_string] end def prepare_tls_context(opts) if opts.values_at(:verify_ssl, :verify_peer, :verify).all?(&:nil?) opts[:verify_peer] = true end # client cert/key paths @tls_certificate_path = tls_certificate_path_from(opts) @tls_key_path = tls_key_path_from(opts) # client cert/key @tls_certificate = tls_certificate_from(opts) @tls_key = tls_key_from(opts) @tls_certificate_store = opts[:tls_certificate_store] @verify_peer = as_boolean(opts[:verify_ssl] || opts[:verify_peer] || opts[:verify]) @tls_context = initialize_tls_context(OpenSSL::SSL::SSLContext.new, opts) end def as_boolean(val) case val when true then true when false then false when "true" then true when "false" then false else !!val end end def wrap_in_tls_socket(socket) raise ArgumentError, "cannot wrap nil into TLS socket, @tls_context is nil. This is a Bunny bug." unless socket raise "cannot wrap a socket into TLS socket, @tls_context is nil. This is a Bunny bug." unless @tls_context s = Bunny::SSLSocketImpl.new(socket, @tls_context) # always set the SNI server name if possible since RFC 3546 and RFC 6066 both state # that TLS clients supporting the extensions can talk to TLS servers that do not s.hostname = @host if s.respond_to?(:hostname) s.sync_close = true s end def check_local_certificate_path!(s) raise MissingTLSCertificateFile, "cannot read client TLS certificate from #{s}" unless File.file?(s) && File.readable?(s) end def check_local_key_path!(s) raise MissingTLSKeyFile, "cannot read client TLS private key from #{s}" unless File.file?(s) && File.readable?(s) end def read_client_certificate! if @tls_certificate_path check_local_certificate_path!(@tls_certificate_path) @tls_certificate = File.read(@tls_certificate_path) end end def read_client_key! if @tls_key_path check_local_key_path!(@tls_key_path) @tls_key = File.read(@tls_key_path) end end def initialize_tls_context(ctx, opts = {}) ctx.cert = OpenSSL::X509::Certificate.new(@tls_certificate) if @tls_certificate ctx.key = OpenSSL::PKey.read(@tls_key) if @tls_key ctx.cert_store = if @tls_certificate_store @tls_certificate_store else # this ivar exists so that this value can be exposed in the API @tls_ca_certificates = tls_ca_certificates_paths_from(opts) initialize_tls_certificate_store(@tls_ca_certificates) end should_silence_warnings = opts.fetch(:tls_silence_warnings, false) if !@tls_certificate && !should_silence_warnings @logger.warn <<-MSG Using TLS but no client certificate is provided. If RabbitMQ is configured to require & verify peer certificate, connection will be rejected. Learn more at https://www.rabbitmq.com/ssl.html MSG end if @tls_certificate && !@tls_key @logger.warn "Using TLS but no client private key is provided!" end verify_mode = if @verify_peer OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT else OpenSSL::SSL::VERIFY_NONE end @logger.debug { "Will use peer verification mode #{verify_mode}" } ctx.verify_mode = verify_mode if !@verify_peer && !should_silence_warnings @logger.warn <<-MSG Using TLS but peer hostname verification is disabled. This is convenient for local development but prone to man-in-the-middle attacks. Please set verify_peer: true in production. Learn more at https://www.rabbitmq.com/ssl.html MSG end ssl_version = opts[:tls_protocol] || opts[:ssl_version] || :TLSv1_2 if ssl_version v = tls_version_constant(ssl_version) ctx.min_version = v ctx.max_version = v end ctx end def initialize_tls_certificate_store(certs) cert_files = [] cert_inlines = [] certs.each do |cert| # if it starts with / or C:/ then it's a file path that may or may not # exist (e.g. a default OpenSSL path). MK. if File.readable?(cert) || cert =~ /\A([a-z]:?)?\//i cert_files.push(cert) else cert_inlines.push(cert) end end @logger.debug { "Using CA certificates at #{cert_files.join(', ')}" } @logger.debug { "Using #{cert_inlines.count} inline CA certificates" } OpenSSL::X509::Store.new.tap do |store| store.set_default_paths cert_files.select { |path| File.readable?(path) }. each { |path| store.add_file(path) } cert_inlines. each { |cert| store.add_cert(OpenSSL::X509::Certificate.new(cert)) } end end def tls_version_constant(value) # OpenSSL::SSL::TLS1_3_VERSION and similar constants # are just integers, so use the value itself as fallback since # there is no class to case switch on TLS_VERSION_ALIASES[value] || value end def timeout_from(options) options[:connect_timeout] || options[:connection_timeout] || options[:timeout] || DEFAULT_CONNECTION_TIMEOUT end end end ruby-amqp-bunny-b6569cd/lib/bunny/version.rb000066400000000000000000000001451464043542000211210ustar00rootroot00000000000000# encoding: utf-8 module Bunny # @return [String] Version of the library VERSION = "2.23.0" end ruby-amqp-bunny-b6569cd/lib/bunny/versioned_delivery_tag.rb000066400000000000000000000012001464043542000241610ustar00rootroot00000000000000module Bunny # Wraps a delivery tag (which is an integer) so that {Bunny::Channel} could # detect stale tags after connection recovery. # # @private class VersionedDeliveryTag attr_reader :tag attr_reader :version def initialize(tag, version) raise ArgumentError.new("tag cannot be nil") unless tag raise ArgumentError.new("version cannot be nil") unless version @tag = tag.to_i @version = version.to_i end def to_i @tag end def stale?(version) raise ArgumentError.new("version cannot be nil") unless version @version < version.to_i end end end ruby-amqp-bunny-b6569cd/profiling/000077500000000000000000000000001464043542000171775ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/profiling/basic_publish/000077500000000000000000000000001464043542000220065ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/profiling/basic_publish/with_4K_messages.rb000066400000000000000000000011321464043542000255300ustar00rootroot00000000000000#!/usr/bin/env ruby # encoding: utf-8 require "rubygems" require "bunny" require "ruby-prof" conn = Bunny.new conn.start puts puts "-" * 80 puts "Benchmarking on #{RUBY_DESCRIPTION}" n = 50_000 ch = conn.create_channel x = ch.default_exchange s = "z" * 4096 # warm up the JIT, etc puts "Doing a warmup run..." 16000.times { x.publish(s, :routing_key => "anything") } # give OS, the server and so on some time to catch # up sleep 2.0 result = RubyProf.profile do n.times { x.publish(s, :routing_key => "anything") } end printer = RubyProf::FlatPrinter.new(result) printer.print(STDOUT, {}) ruby-amqp-bunny-b6569cd/repl000077500000000000000000000000451464043542000160750ustar00rootroot00000000000000#!/bin/sh bundle exec irb -r'bunny' ruby-amqp-bunny-b6569cd/spec/000077500000000000000000000000001464043542000161405ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/spec/config/000077500000000000000000000000001464043542000174055ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/spec/config/enabled_plugins000066400000000000000000000000721464043542000224620ustar00rootroot00000000000000[rabbitmq_management, rabbitmq_consistent_hash_exchange]. ruby-amqp-bunny-b6569cd/spec/config/rabbitmq.conf000066400000000000000000000005541464043542000220610ustar00rootroot00000000000000listeners.tcp.1 = 0.0.0.0:5672 listeners.ssl.default = 5671 # mounted by docker-compose ssl_options.cacertfile = /spec/tls/ca_certificate.pem ssl_options.certfile = /spec/tls/server_certificate.pem ssl_options.keyfile = /spec/tls/server_key.pem ssl_options.verify = verify_none ssl_options.fail_if_no_peer_cert = false loopback_users = none ruby-amqp-bunny-b6569cd/spec/higher_level_api/000077500000000000000000000000001464043542000214265ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/000077500000000000000000000000001464043542000237515ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/basic_ack_spec.rb000066400000000000000000000160541464043542000272150ustar00rootroot00000000000000require "spec_helper" describe Bunny::Channel, "#ack" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end context "with a valid (known) delivery tag" do it "acknowledges a message" do ch = connection.create_channel q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true) x = ch.default_exchange x.publish("bunneth", routing_key: q.name) sleep 0.5 expect(q.message_count).to eq 1 delivery_details, properties, content = q.pop(manual_ack: true) ch.ack(delivery_details.delivery_tag, true) ch.close ch = connection.create_channel q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true) expect(q.message_count).to eq 0 ch.close end end context "with a valid (known) delivery tag (multiple = true)" do it "acknowledges a message" do ch = connection.create_channel q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true) x = ch.default_exchange x.publish("bunneth", routing_key: q.name) x.publish("bunneth", routing_key: q.name) sleep 0.5 expect(q.message_count).to eq 2 delivery_details_1, _properties, _content = q.pop(manual_ack: true) delivery_details_2, _properties, _content = q.pop(manual_ack: true) ch.ack(delivery_details_2.delivery_tag, true) ch.close ch = connection.create_channel q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true) expect(q.message_count).to eq 0 ch.close end end context "with a valid (known) delivery tag (multiple = false)" do it "acknowledges a message" do ch = connection.create_channel q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true) x = ch.default_exchange x.publish("bunneth", routing_key: q.name) x.publish("bunneth", routing_key: q.name) sleep 0.5 expect(q.message_count).to eq 2 delivery_details_1, _properties, _content = q.pop(manual_ack: true) delivery_details_2, _properties, _content = q.pop(manual_ack: true) ch.ack(delivery_details_2.delivery_tag, false) ch.close ch = connection.create_channel q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true) expect(q.message_count).to eq 1 ch.close end end context "with a valid (known) delivery tag and automatic ack mode" do it "results in a channel exception" do ch = connection.create_channel q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true) x = ch.default_exchange q.subscribe(manual_ack: false) do |delivery_info, properties, payload| ch.ack(delivery_info.delivery_tag, false) end x.publish("bunneth", routing_key: q.name) sleep 0.5 expect do q.message_count end.to raise_error(Bunny::ChannelAlreadyClosed) end end context "with an invalid (random) delivery tag" do it "causes a channel-level error" do ch = connection.create_channel q = ch.queue("bunny.basic.ack.unknown-delivery-tag", exclusive: true) x = ch.default_exchange x.publish("bunneth", routing_key: q.name) sleep 0.5 expect(q.message_count).to eq 1 _, _, content = q.pop(manual_ack: true) ch.on_error do |ch, channel_close| @channel_close = channel_close end ch.ack(82, true) sleep 0.25 expect(@channel_close.reply_code).to eq AMQ::Protocol::PreconditionFailed::VALUE end end context "with a valid (known) delivery tag" do it "gets a depricated message warning for using :ack" do ch = connection.create_channel q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true) x = ch.default_exchange x.publish("bunneth", routing_key: q.name) sleep 0.5 expect(q.message_count).to eq 1 orig_stderr = $stderr $stderr = StringIO.new delivery_details, properties, content = q.pop(ack: true) $stderr.rewind expect($stderr.string.chomp).to eq("[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead.\n[DEPRECATION] `:ack` is deprecated. Please use `:manual_ack` instead.") $stderr = orig_stderr ch.ack(delivery_details.delivery_tag, true) ch.close ch = connection.create_channel q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true) expect(q.message_count).to eq 0 ch.close end end end describe Bunny::Channel, "#basic_ack" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end context "with a valid (known) delivery tag (multiple = true)" do it "acknowledges a message" do ch = connection.create_channel q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true) x = ch.default_exchange x.publish("bunneth", routing_key: q.name) x.publish("bunneth", routing_key: q.name) sleep 0.5 expect(q.message_count).to eq 2 delivery_details_1, _properties, _content = q.pop(manual_ack: true) delivery_details_2, _properties, _content = q.pop(manual_ack: true) ch.basic_ack(delivery_details_2.delivery_tag.to_i, true) ch.close ch = connection.create_channel q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true) expect(q.message_count).to eq 0 ch.close end end context "with a valid (known) delivery tag (multiple = false)" do it "acknowledges a message" do ch = connection.create_channel q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true) x = ch.default_exchange x.publish("bunneth", routing_key: q.name) x.publish("bunneth", routing_key: q.name) sleep 0.5 expect(q.message_count).to eq 2 delivery_details_1, _properties, _content = q.pop(manual_ack: true) delivery_details_2, _properties, _content = q.pop(manual_ack: true) ch.basic_ack(delivery_details_2.delivery_tag.to_i, false) ch.close ch = connection.create_channel q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true) expect(q.message_count).to eq 1 ch.close end end context "with a valid (known) delivery tag (multiple = default)" do it "acknowledges a message" do ch = connection.create_channel q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true) x = ch.default_exchange x.publish("bunneth", routing_key: q.name) x.publish("bunneth", routing_key: q.name) sleep 0.5 expect(q.message_count).to eq 2 delivery_details_1, _properties, _content = q.pop(manual_ack: true) delivery_details_2, _properties, _content = q.pop(manual_ack: true) ch.basic_ack(delivery_details_2.delivery_tag.to_i) ch.close ch = connection.create_channel q = ch.queue("bunny.basic.ack.manual-acks", exclusive: true) expect(q.message_count).to eq 1 ch.close end end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/basic_cancel_spec.rb000066400000000000000000000070231464043542000277000ustar00rootroot00000000000000require "spec_helper" describe Bunny::Consumer, "#cancel" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end context "with a non-blocking consumer" do let(:queue_name) { "bunny.queues.#{rand}" } it "cancels the consumer" do delivered_data = [] t = Thread.new do ch = connection.create_channel q = ch.queue(queue_name, auto_delete: true, durable: false) consumer = q.subscribe do |_, _, payload| delivered_data << payload end expect(consumer.consumer_tag).not_to be_nil cancel_ok = consumer.cancel expect(cancel_ok.consumer_tag).to eq consumer.consumer_tag ch.close end t.abort_on_exception = true sleep 0.5 ch = connection.create_channel ch.default_exchange.publish("", routing_key: queue_name) sleep 0.7 expect(delivered_data).to be_empty end end context "with a blocking consumer" do let(:queue_name) { "bunny.queues.#{rand}" } it "cancels the consumer" do delivered_data = [] consumer = nil t = Thread.new do ch = connection.create_channel q = ch.queue(queue_name, auto_delete: true, durable: false) consumer = Bunny::Consumer.new(ch, q) consumer.on_delivery do |_, _, payload| delivered_data << payload end q.subscribe_with(consumer) end t.abort_on_exception = true sleep 1.0 consumer.cancel sleep 1.0 ch = connection.create_channel ch.default_exchange.publish("", routing_key: queue_name) sleep 0.7 expect(delivered_data).to be_empty end end context "with a worker pool shutdown timeout configured" do let(:queue_name) { "bunny.queues.#{rand}" } it "processes the message if processing completes within the timeout" do delivered_data = [] consumer = nil t = Thread.new do ch = connection.create_channel(nil, 1, false, 5) q = ch.queue(queue_name, auto_delete: true, durable: false) consumer = Bunny::Consumer.new(ch, q) consumer.on_delivery do |_, _, payload| sleep 2 delivered_data << payload end q.subscribe_with(consumer) end t.abort_on_exception = true sleep 1.0 ch = connection.create_channel ch.confirm_select ch.default_exchange.publish("", routing_key: queue_name) ch.wait_for_confirms sleep 0.5 consumer.cancel sleep 1.0 expect(delivered_data).to_not be_empty end it "kills the consumer if processing takes longer than the timeout" do delivered_data = [] consumer = nil t = Thread.new do ch = connection.create_channel(nil, 1, false, 1) q = ch.queue(queue_name, auto_delete: true, durable: false) consumer = Bunny::Consumer.new(ch, q) consumer.on_delivery do |_, _, payload| sleep 3 delivered_data << payload end q.subscribe_with(consumer) end t.abort_on_exception = true sleep 1.0 ch = connection.create_channel ch.confirm_select ch.default_exchange.publish("", routing_key: queue_name) ch.wait_for_confirms sleep 0.5 consumer.cancel sleep 1.0 expect(delivered_data).to be_empty end end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/basic_consume_spec.rb000066400000000000000000000235711464043542000301320ustar00rootroot00000000000000require "spec_helper" require "set" describe Bunny::Queue, "#subscribe" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end context "with automatic acknowledgement mode" do let(:queue_name) { "bunny.basic_consume#{rand}" } it "registers the consumer" do delivered_keys = [] delivered_data = [] t = Thread.new do ch = connection.create_channel q = ch.queue(queue_name, auto_delete: true, durable: false) q.subscribe(exclusive: false, manual_ack: false) do |delivery_info, properties, payload| delivered_keys << delivery_info.routing_key delivered_data << payload end end t.abort_on_exception = true sleep 0.5 ch = connection.create_channel x = ch.default_exchange x.publish("hello", routing_key: queue_name) sleep 0.7 expect(delivered_keys).to include(queue_name) expect(delivered_data).to include("hello") expect(ch.queue(queue_name, auto_delete: true, durable: false).message_count).to eq 0 ch.close end context "with a single consumer" do let(:queue_name) { "bunny.basic_consume#{rand}" } it "provides delivery tag access" do delivery_tags = SortedSet.new cch = connection.create_channel q = cch.queue(queue_name, auto_delete: true, durable: false) q.subscribe(exclusive: false, manual_ack: false) do |delivery_info, properties, payload| delivery_tags << delivery_info.delivery_tag end sleep 0.5 ch = connection.create_channel x = ch.default_exchange 100.times do x.publish("hello", routing_key: queue_name) end sleep 1.5 100.times do |i| expect(delivery_tags).to include(i + 1) end expect(ch.queue(queue_name, auto_delete: true, durable: false).message_count).to eq 0 ch.close end end context "with multiple consumers on the same channel" do let(:queue_name) { "bunny.basic_consume#{rand}" } it "provides delivery tag access" do delivery_tags = SortedSet.new cch = connection.create_channel q = cch.queue(queue_name, auto_delete: true, durable: false) 7.times do q.subscribe(exclusive: false, manual_ack: false) do |delivery_info, properties, payload| delivery_tags << delivery_info.delivery_tag end end sleep 1.0 ch = connection.create_channel x = ch.default_exchange 100.times do x.publish("hello", routing_key: queue_name) end sleep 1.5 100.times do |i| expect(delivery_tags).to include(i + 1) end expect(ch.queue(queue_name, auto_delete: true, durable: false).message_count).to eq 0 ch.close end end end context "with manual acknowledgement mode" do let(:queue_name) { "bunny.basic_consume#{rand}" } it "register a consumer with manual acknowledgements mode" do delivered_keys = [] delivered_data = [] t = Thread.new do ch = connection.create_channel q = ch.queue(queue_name, auto_delete: true, durable: false) q.subscribe(exclusive: false, manual_ack: true) do |delivery_info, properties, payload| delivered_keys << delivery_info.routing_key delivered_data << payload ch.ack(delivery_info.delivery_tag) end end t.abort_on_exception = true sleep 0.5 ch = connection.create_channel x = ch.default_exchange x.publish("hello", routing_key: queue_name) sleep 0.7 expect(delivered_keys).to include(queue_name) expect(delivered_data).to include("hello") expect(ch.queue(queue_name, auto_delete: true, durable: false).message_count).to eq 0 ch.close end end ENV.fetch("RUNS", 20).to_i.times do |i| context "with a queue that already has messages (take #{i})" do let(:queue_name) { "bunny.basic_consume#{rand}" } it "registers the consumer" do delivered_keys = [] delivered_data = [] ch = connection.create_channel q = ch.queue(queue_name, auto_delete: true, durable: false) x = ch.default_exchange 100.times do x.publish("hello", routing_key: queue_name) end sleep 0.7 expect(q.message_count).to be > 50 t = Thread.new do ch = connection.create_channel q = ch.queue(queue_name, auto_delete: true, durable: false) q.subscribe(exclusive: false, manual_ack: false) do |delivery_info, properties, payload| delivered_keys << delivery_info.routing_key delivered_data << payload end end t.abort_on_exception = true sleep 0.5 expect(delivered_keys).to include(queue_name) expect(delivered_data).to include("hello") expect(ch.queue(queue_name, auto_delete: true, durable: false).message_count).to eq 0 ch.close end end end # 20.times context "after consumer pool has already been shut down" do let(:queue_name) { "bunny.basic_consume#{rand}" } it "registers the consumer" do delivered_keys = [] delivered_data = [] t = Thread.new do ch = connection.create_channel q = ch.queue(queue_name) c1 = q.subscribe(exclusive: false, manual_ack: false) do |delivery_info, properties, payload| end c1.cancel c2 = q.subscribe(exclusive: false, manual_ack: false) do |delivery_info, properties, payload| delivered_keys << delivery_info.routing_key delivered_data << payload end c2.cancel q.subscribe(exclusive: false, manual_ack: false) do |delivery_info, properties, payload| delivered_keys << delivery_info.routing_key delivered_data << payload end end t.abort_on_exception = true sleep 0.5 ch = connection.create_channel x = ch.default_exchange x.publish("hello", routing_key: queue_name) sleep 0.7 expect(delivered_keys).to include(queue_name) expect(delivered_data).to include("hello") expect(ch.queue(queue_name).message_count).to eq 0 ch.queue_delete(queue_name) ch.close end end context "with uncaught exceptions in delivery handler" do context "and defined exception handler" do let(:queue_name) { "bunny.basic_consume#{rand}" } it "uses exception handler" do caught = nil t = Thread.new do ch = connection.create_channel q = ch.queue(queue_name, auto_delete: true, durable: false) ch.on_uncaught_exception do |e, consumer| caught = e end q.subscribe(exclusive: false, manual_ack: false) do |delivery_info, properties, payload| raise RuntimeError.new(queue_name) end end t.abort_on_exception = true sleep 0.5 ch = connection.create_channel x = ch.default_exchange x.publish("hello", routing_key: queue_name) sleep 0.5 expect(caught.message).to eq queue_name ch.close end end context "and default exception handler" do let(:queue_name) { "bunny.basic_consume#{rand}" } it "uses exception handler" do caughts = [] t = Thread.new do allow(connection.logger).to receive(:error) { |x| caughts << x } ch = connection.create_channel q = ch.queue(queue_name, auto_delete: true, durable: false) q.subscribe(exclusive: false, manual_ack: false) do |delivery_info, properties, payload| raise RuntimeError.new(queue_name) end end t.abort_on_exception = true sleep 0.5 ch = connection.create_channel x = ch.default_exchange 5.times { x.publish("hello", routing_key: queue_name) } sleep 1.5 expect(caughts.size).to eq(5) ch.close end end context "with a single consumer" do let(:queue_name) { "bunny.basic_consume#{rand}" } it "provides delivery tag access" do delivery_tags = SortedSet.new cch = connection.create_channel q = cch.queue(queue_name, auto_delete: true, durable: false) q.subscribe(exclusive: false, manual_ack: false) do |delivery_info, properties, payload| delivery_tags << delivery_info.delivery_tag end sleep 0.5 ch = connection.create_channel x = ch.default_exchange 100.times do x.publish("hello", routing_key: queue_name) end sleep 1.5 100.times do |i| expect(delivery_tags).to include(i + 1) end expect(ch.queue(queue_name, auto_delete: true, durable: false).message_count).to eq 0 ch.close end end context "with multiple consumers on the same channel" do let(:queue_name) { "bunny.basic_consume#{rand}" } it "provides delivery tag access" do delivery_tags = SortedSet.new cch = connection.create_channel q = cch.queue(queue_name, auto_delete: true, durable: false) 7.times do q.subscribe(exclusive: false, manual_ack: false) do |delivery_info, properties, payload| delivery_tags << delivery_info.delivery_tag end end sleep 1.0 ch = connection.create_channel x = ch.default_exchange 100.times do x.publish("hello", routing_key: queue_name) end sleep 1.5 100.times do |i| expect(delivery_tags).to include(i + 1) end expect(ch.queue(queue_name, auto_delete: true, durable: false).message_count).to eq 0 ch.close end end end end # describe ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/basic_consume_with_objects_spec.rb000066400000000000000000000024411464043542000326670ustar00rootroot00000000000000require "spec_helper" require "set" describe Bunny::Queue, "#subscribe_with" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end context "with explicit acknowledgements mode" do class ExampleConsumer < Bunny::Consumer def cancelled? @cancelled end def handle_cancellation(_) @cancelled = true end def call(delivery_info, metadata, payload) # no-op end end # demonstrates that manual acknowledgement mode is actually # used. MK. it "requeues messages on channel closure" do ch1 = connection.create_channel ch2 = connection.create_channel q1 = ch1.queue("bunny.tests.consumer_object1", exclusive: true) q2 = ch2.queue("bunny.tests.consumer_object1", exclusive: true) ec = ExampleConsumer.new(ch1, q1, "", false) x = ch2.default_exchange t = Thread.new do 50.times do x.publish("hello", routing_key: q2.name) end end t.abort_on_exception = true q1.subscribe_with(ec, manual_ack: true) sleep 2 ch1.close expect(q2.message_count).to eq 50 end end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/basic_get_spec.rb000066400000000000000000000037301464043542000272330ustar00rootroot00000000000000require "spec_helper" describe Bunny::Queue, "#pop" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", automatically_recover: false) c.start c end after :each do connection.close if connection.open? end context "with all defaults" do it "fetches a messages which is automatically acknowledged" do ch = connection.create_channel q = ch.queue("", exclusive: true) x = ch.default_exchange msg = "xyzzy" x.publish(msg, routing_key: q.name) sleep(0.5) get_ok, properties, content = q.pop expect(get_ok).to be_kind_of(Bunny::GetResponse) expect(properties).to be_kind_of(Bunny::MessageProperties) expect(properties.content_type).to eq("application/octet-stream") expect(get_ok.routing_key).to eq(q.name) expect(get_ok.delivery_tag).to be_kind_of(Bunny::VersionedDeliveryTag) expect(content).to eq(msg) expect(q.message_count).to eq 0 ch.close end end context "with an empty queue" do it "returns an empty response" do ch = connection.create_channel q = ch.queue("", exclusive: true) q.purge get_empty, properties, content = q.pop expect(get_empty).to eq(nil) expect(properties).to eq(nil) expect(content).to eq(nil) expect(q.message_count).to eq 0 ch.close end end end describe Bunny::Channel, "#basic_get" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", automatically_recover: false, continuation_timeout: 3000) c.start c end after :each do connection.close if connection.open? end context "with a non-existent queue" do it "throws a NOT_FOUND" do ch = connection.create_channel expect do ch.basic_get "non_existent_#{rand.to_s}" end.to raise_error(Bunny::NotFound) end end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/basic_nack_spec.rb000066400000000000000000000042011464043542000273620ustar00rootroot00000000000000require "spec_helper" describe Bunny::Channel, "#nack" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end subject do connection.create_channel end context "with requeue = false" do it "rejects a message" do q = subject.queue("bunny.basic.nack.with-requeue-false", exclusive: true) x = subject.default_exchange x.publish("bunneth", routing_key: q.name) sleep(0.5) expect(q.message_count).to eq 1 delivery_info, _, content = q.pop(manual_ack: true) subject.nack(delivery_info.delivery_tag, false, false) sleep(0.5) subject.close ch = connection.create_channel q = ch.queue("bunny.basic.nack.with-requeue-false", exclusive: true) expect(q.message_count).to eq 0 ch.close end end context "with multiple = true" do it "rejects multiple messages" do q = subject.queue("bunny.basic.nack.with-requeue-true-multi-true", exclusive: true) x = subject.default_exchange 3.times do x.publish("bunneth", routing_key: q.name) end sleep(0.5) expect(q.message_count).to eq 3 _, _, _ = q.pop(manual_ack: true) _, _, _ = q.pop(manual_ack: true) delivery_info, _, content = q.pop(manual_ack: true) subject.nack(delivery_info.delivery_tag, true, true) sleep(0.5) expect(q.message_count).to eq 3 subject.close end end context "with an invalid (random) delivery tag" do it "causes a channel-level error" do q = subject.queue("bunny.basic.nack.unknown-delivery-tag", exclusive: true) x = subject.default_exchange x.publish("bunneth", routing_key: q.name) sleep(0.25) expect(q.message_count).to eq 1 _, _, content = q.pop(manual_ack: true) subject.on_error do |ch, channel_close| @channel_close = channel_close end subject.nack(82, false, true) sleep 0.5 expect(@channel_close.reply_text).to eq "PRECONDITION_FAILED - unknown delivery tag 82" end end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/basic_publish_spec.rb000066400000000000000000000031421464043542000301170ustar00rootroot00000000000000require "spec_helper" describe "Published message" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end context "with all default delivery and a 254 character long routing key" do it "routes the messages" do ch = connection.create_channel q = ch.queue("", exclusive: true) x = ch.fanout("amq.fanout") q.bind(x) rk = "a" * 254 x.publish("xyzzy", routing_key: rk, persistent: true) sleep(1) expect(q.message_count).to eq 1 _, _, payload = q.pop expect(payload).to eq "xyzzy" ch.close end end context "with all default delivery and a 255 character long routing key" do it "routes the messages" do ch = connection.create_channel q = ch.queue("", exclusive: true) x = ch.fanout("amq.fanout") q.bind(x) rk = "a" * 255 x.publish("xyzzy", routing_key: rk, persistent: true) sleep(1) expect(q.message_count).to eq 1 _, _, payload = q.pop expect(payload).to eq "xyzzy" ch.close end end context "with all default delivery and a 256 character long routing key" do it "fails with a connection exception" do ch = connection.create_channel q = ch.queue("", exclusive: true) x = ch.fanout("amq.fanout") q.bind(x) rk = "a" * 256 expect do x.publish("xyzzy", routing_key: rk, persistent: true) end.to raise_error(ArgumentError) ch.close end end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/basic_qos_spec.rb000066400000000000000000000031321464043542000272520ustar00rootroot00000000000000require "spec_helper" describe Bunny::Channel, "#prefetch" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close end context "with a positive integer < 65535" do it "sets that prefetch level via basic.qos" do ch = connection.create_channel expect(ch.prefetch_count).not_to eq 10 expect(ch.prefetch_global).to be_nil expect(ch.prefetch(10)).to be_instance_of(AMQ::Protocol::Basic::QosOk) expect(ch.prefetch_count).to eq 10 expect(ch.prefetch_global).to be false end it "sets that prefetch global via basic.qos" do ch = connection.create_channel expect(ch.prefetch_count).not_to eq 42 expect(ch.prefetch_global).to be_nil expect(ch.prefetch(42, true)).to be_instance_of(AMQ::Protocol::Basic::QosOk) expect(ch.prefetch_count).to eq 42 expect(ch.prefetch_global).to be true end end context "with a positive integer > 65535" do it "raises an ArgumentError" do ch = connection.create_channel expect { ch.prefetch(100_000) }.to raise_error( ArgumentError, "prefetch count must be no greater than #{Bunny::Channel::MAX_PREFETCH_COUNT}, given: 100000" ) end end context "with a negative integer" do it "raises an ArgumentError" do ch = connection.create_channel expect { ch.prefetch(-2) }.to raise_error( ArgumentError, "prefetch count must be a positive integer, given: -2" ) end end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/basic_reject_spec.rb000066400000000000000000000077741464043542000277440ustar00rootroot00000000000000require "spec_helper" describe Bunny::Channel, "#reject" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end context "with requeue = true" do it "requeues a message" do ch = connection.create_channel q = ch.queue("bunny.basic.reject.manual-acks", exclusive: true) x = ch.default_exchange x.publish("bunneth", routing_key: q.name) sleep(0.5) expect(q.message_count).to eq 1 delivery_info, _, _ = q.pop(manual_ack: true) ch.reject(delivery_info.delivery_tag, true) sleep(0.5) expect(q.message_count).to eq 1 ch.close end end context "with requeue = false" do it "rejects a message" do ch = connection.create_channel q = ch.queue("bunny.basic.reject.with-requeue-false", exclusive: true) x = ch.default_exchange x.publish("bunneth", routing_key: q.name) sleep(0.5) expect(q.message_count).to eq 1 delivery_info, _, _ = q.pop(manual_ack: true) ch.reject(delivery_info.delivery_tag, false) sleep(0.5) ch.close ch = connection.create_channel q = ch.queue("bunny.basic.reject.with-requeue-false", exclusive: true) expect(q.message_count).to eq 0 ch.close end end context "with an invalid (random) delivery tag" do it "causes a channel-level error" do ch = connection.create_channel q = ch.queue("bunny.basic.reject.unknown-delivery-tag", exclusive: true) x = ch.default_exchange x.publish("bunneth", routing_key: q.name) sleep(0.25) expect(q.message_count).to eq 1 _, _, content = q.pop(manual_ack: true) ch.on_error do |ch, channel_close| @channel_close = channel_close end ch.reject(82, true) sleep 0.5 expect(@channel_close.reply_text).to eq "PRECONDITION_FAILED - unknown delivery tag 82" end end end describe Bunny::Channel, "#basic_reject" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end context "with requeue = true" do it "requeues a message" do ch = connection.create_channel q = ch.queue("bunny.basic.reject.manual-acks", exclusive: true) x = ch.default_exchange x.publish("bunneth", routing_key: q.name) sleep(0.5) expect(q.message_count).to eq 1 delivery_info, _, _ = q.pop(manual_ack: true) ch.basic_reject(delivery_info.delivery_tag.to_i, true) sleep(0.5) expect(q.message_count).to eq 1 ch.close end end context "with requeue = false" do it "rejects a message" do ch = connection.create_channel q = ch.queue("bunny.basic.reject.with-requeue-false", exclusive: true) x = ch.default_exchange x.publish("bunneth", routing_key: q.name) sleep(0.5) expect(q.message_count).to eq 1 delivery_info, _, _ = q.pop(manual_ack: true) ch.basic_reject(delivery_info.delivery_tag.to_i, false) sleep(0.5) ch.close ch = connection.create_channel q = ch.queue("bunny.basic.reject.with-requeue-false", exclusive: true) expect(q.message_count).to eq 0 ch.close end end context "with requeue = default" do it "rejects a message" do ch = connection.create_channel q = ch.queue("bunny.basic.reject.with-requeue-false", exclusive: true) x = ch.default_exchange x.publish("bunneth", routing_key: q.name) sleep(0.5) expect(q.message_count).to eq 1 delivery_info, _, _ = q.pop(manual_ack: true) ch.basic_reject(delivery_info.delivery_tag.to_i) sleep(0.5) ch.close ch = connection.create_channel q = ch.queue("bunny.basic.reject.with-requeue-false", exclusive: true) expect(q.message_count).to eq 0 ch.close end end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/basic_return_spec.rb000066400000000000000000000013311464043542000277660ustar00rootroot00000000000000require "spec_helper" describe Bunny::Exchange, "#publish" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end context "with mandatory: true and a bad [no routes] routing key" do it "causes a message to be returned" do ch = connection.create_channel x = ch.default_exchange returned = [] x.on_return do |basic_deliver, properties, content| returned << content end x.publish("xyzzy", routing_key: rand.to_s, mandatory: true) sleep 0.5 expect(returned).to include("xyzzy") ch.close end end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/channel_close_spec.rb000066400000000000000000000027441464043542000301140ustar00rootroot00000000000000require "spec_helper" describe Bunny::Channel do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close end context "when closed" do it "releases the id" do ch = connection.create_channel n = ch.number expect(ch).to be_open ch.close expect(ch).to be_closed # a new channel with the same id can be created connection.create_channel(n) end end context "when double closed" do # bunny#528 it "raises a meaningful exception" do ch = connection.create_channel expect(ch).to be_open ch.close expect(ch).to be_closed expect { ch.close }.to raise_error(Bunny::ChannelAlreadyClosed) end end context "when double closed after a channel-level protocol exception" do # bunny#528 it "raises a meaningful exception" do ch = connection.create_channel s = "bunny-temp-q-#{rand}" expect(ch).to be_open ch.queue_declare(s, durable: false) expect do ch.queue_declare(s, durable: true) end.to raise_error(Bunny::PreconditionFailed) # channel.close is sent and handled concurrently with the test sleep 1 expect(ch).to be_closed expect { ch.close }.to raise_error(Bunny::ChannelAlreadyClosed) cleanup_ch = connection.create_channel cleanup_ch.queue_delete(s) cleanup_ch.close end end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/channel_open_spec.rb000066400000000000000000000024561464043542000277500ustar00rootroot00000000000000require "spec_helper" describe Bunny::Channel, "when opened" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close end context "without explicitly provided id" do it "gets an allocated id and is successfully opened" do expect(connection).to be_connected ch = connection.create_channel expect(ch).to be_open expect(ch.id).to be > 0 end end context "with an explicitly provided id = 0" do it "raises ArgumentError" do expect(connection).to be_connected expect { connection.create_channel(0) }.to raise_error(ArgumentError) end end context "with explicitly provided id" do it "uses that id and is successfully opened" do ch = connection.create_channel(767) expect(connection).to be_connected expect(ch).to be_open expect(ch.id).to eq 767 end end context "with explicitly provided id that is already taken" do it "reuses the channel that is already opened" do ch = connection.create_channel(767) expect(connection).to be_connected expect(ch).to be_open expect(ch.id).to eq 767 expect(connection.create_channel(767)).to eq ch end end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/connection_recovery_spec.rb000066400000000000000000000313341464043542000313710ustar00rootroot00000000000000require "spec_helper" require "rabbitmq/http/client" require "bunny/concurrent/condition" describe "Connection recovery" do let(:http_client) { RabbitMQ::HTTP::Client.new("http://127.0.0.1:15672") } let(:logger) { Logger.new($stderr).tap {|logger| logger.level = ENV["BUNNY_LOG_LEVEL"] || Logger::WARN } } let(:recovery_interval) { 0.2 } it "reconnects after grace period" do with_open do |c| close_all_connections! wait_for_recovery_with { connections.any? } end end it "reconnects after grace period (with multiple hosts)" do with_open_multi_host do |c| close_all_connections! wait_for_recovery_with { connections.any? } end end it "reconnects after grace period (with multiple hosts, including a broken one)" do with_open_multi_broken_host do |c| close_all_connections! wait_for_recovery_with { connections.any? } end end it "recovers channels" do with_open do |c| ch1 = c.create_channel ch2 = c.create_channel sleep 1.5 close_all_connections! sleep 0.5 poll_until { channels.count == 2 } expect(ch1).to be_open expect(ch2).to be_open end end it "provides a recovery completion callback" do with_open do |c| latch = Bunny::Concurrent::Condition.new c.after_recovery_completed do latch.notify end ch = c.create_channel sleep 1.0 close_all_connections! poll_until { c.open? && ch.open? } poll_until { latch.none_threads_waiting? } end end it "recovers channels (with multiple hosts)" do with_open_multi_host do |c| ch1 = c.create_channel ch2 = c.create_channel sleep 1.5 close_all_connections! sleep 0.5 poll_until { channels.count == 2 } expect(ch1).to be_open expect(ch2).to be_open end end it "recovers channels (with multiple hosts, including a broken one)" do with_open_multi_broken_host do |c| ch1 = c.create_channel ch2 = c.create_channel sleep 1.5 close_all_connections! sleep 0.5 poll_until { channels.count == 2 } expect(ch1).to be_open expect(ch2).to be_open end end it "recovers basic.qos prefetch setting" do with_open do |c| ch = c.create_channel ch.prefetch(11) expect(ch.prefetch_count).to eq 11 expect(ch.prefetch_global).to be false sleep 1.5 close_all_connections! sleep 0.5 wait_for_recovery_with { connections.any? } expect(ch).to be_open expect(ch.prefetch_count).to eq 11 expect(ch.prefetch_global).to be false end end it "recovers basic.qos prefetch global setting" do with_open do |c| ch = c.create_channel ch.prefetch(42, true) expect(ch.prefetch_count).to eq 42 expect(ch.prefetch_global).to be true sleep 1.5 close_all_connections! sleep 0.5 wait_for_recovery_with { connections.any? } expect(ch).to be_open expect(ch.prefetch_count).to eq 42 expect(ch.prefetch_global).to be true end end it "recovers publisher confirms setting" do with_open do |c| ch = c.create_channel ch.confirm_select expect(ch).to be_using_publisher_confirms sleep 1.5 close_all_connections! sleep 0.5 wait_for_recovery_with { connections.any? } expect(ch).to be_open expect(ch).to be_using_publisher_confirms end end it "recovers transactionality setting" do with_open do |c| ch = c.create_channel ch.tx_select expect(ch).to be_using_tx sleep 1.5 close_all_connections! sleep 0.5 wait_for_recovery_with { connections.any? } expect(ch).to be_open expect(ch).to be_using_tx end end it "recovers client-named queues" do with_open do |c| ch = c.create_channel q = ch.queue("bunny.tests.recovery.client-named#{rand}") close_all_connections! wait_for_recovery_with { connections.any? } expect(ch).to be_open ensure_queue_recovery(ch, q) q.delete end end # a very simplistic test for queues inspired by #422 it "recovers client-named queues declared with no_declare: true" do with_open do |c| ch = c.create_channel ch2 = c.create_channel n = rand s = "bunny.tests.recovery.client-named#{n}" q = ch.queue(s) q2 = ch2.queue(s, no_declare: true) close_all_connections! wait_for_recovery_with { connections.any? } expect(ch).to be_open ensure_queue_recovery(ch, q) q.delete end end # a test for #422 it "recovers client-named queues declared with passive: true" do with_open do |c| ch = c.create_channel ch2 = c.create_channel n = rand s = "bunny.tests.recovery.client-named#{n}" q = ch.queue(s) q2 = ch2.queue(s, passive: true) close_all_connections! wait_for_recovery_with { connections.any? } expect(ch).to be_open ensure_queue_recovery(ch, q) ensure_queue_recovery(ch, q2) q.delete end end it "recovers server-named queues" do with_open do |c| ch = c.create_channel q = ch.queue("", exclusive: true) close_all_connections! wait_for_recovery_with { connections.any? } expect(ch).to be_open ensure_queue_recovery(ch, q) end end it "recovers queue bindings" do with_open do |c| ch = c.create_channel x = ch.fanout("amq.fanout") q = ch.queue("", exclusive: true) q.bind(x) close_all_connections! wait_for_recovery_with { connections.any? } expect(ch).to be_open ensure_queue_binding_recovery(ch, x, q) end end it "recovers exchanges and their bindings" do with_open do |c| ch = c.create_channel source = ch.fanout("source.exchange.recovery.example", auto_delete: true) destination = ch.fanout("destination.exchange.recovery.example", auto_delete: true) destination.bind(source) # Exchanges won't get auto-deleted on connection loss unless they have # had an exclusive queue bound to them. dst_queue = ch.queue("", exclusive: true) dst_queue.bind(destination, routing_key: "") src_queue = ch.queue("", exclusive: true) src_queue.bind(source, routing_key: "") close_all_connections! wait_for_recovery_with { connections.any? && exchange_names_in_vhost("/").include?(source.name) } ch.confirm_select source.publish("msg", routing_key: "") ch.wait_for_confirms expect(dst_queue.message_count).to eq 1 destination.delete end end it "recovers passively declared exchanges and their bindings" do with_open do |c| ch = c.create_channel ch.confirm_select source = ch.fanout("amq.fanout", passive: true) destination = ch.fanout("destination.exchange.recovery.example", auto_delete: true) destination.bind(source) # Exchanges won't get auto-deleted on connection loss unless they have # had an exclusive queue bound to them. dst_queue = ch.queue("", exclusive: true) dst_queue.bind(destination, routing_key: "") src_queue = ch.queue("", exclusive: true) src_queue.bind(source, routing_key: "") close_all_connections! wait_for_recovery_with { connections.any? } source.publish("msg", routing_key: "") ch.wait_for_confirms expect(dst_queue.message_count).to eq 1 destination.delete end end # this is a simplistic test that primarily execises the code path from #412 it "recovers exchanges that were declared with passive = true" do with_open do |c| ch = c.create_channel ch2 = c.create_channel source = ch.fanout("source.exchange.recovery.example", auto_delete: true) destination = ch.fanout("destination.exchange.recovery.example", auto_delete: true) source2 = ch2.fanout("source.exchange.recovery.example", no_declare: true) destination.bind(source) # Exchanges won't get auto-deleted on connection loss unless they have # had an exclusive queue bound to them. dst_queue = ch.queue("", exclusive: true) dst_queue.bind(destination, routing_key: "") src_queue = ch.queue("", exclusive: true) src_queue.bind(source, routing_key: "") close_all_connections! wait_for_recovery_with { connections.any? && exchange_names_in_vhost("/").include?(source.name) } ch2.confirm_select source2.publish("msg", routing_key: "") ch2.wait_for_confirms expect(dst_queue.message_count).to eq 1 end end it "recovers allocated channel ids" do with_open do |c| q = "queue#{Bunny::Timestamp.now.to_i}" 10.times { c.create_channel } expect(c.queue_exists?(q)).to eq false close_all_connections! wait_for_recovery_with { channels.any? } # make sure the connection isn't closed shortly after # due to "second 'channel.open' seen". MK. expect(c).to be_open sleep 0.1 expect(c).to be_open sleep 0.1 expect(c).to be_open end end it "recovers consumers" do with_open do |c| delivered = false ch = c.create_channel ch.confirm_select q = ch.queue("", exclusive: true) q.subscribe do |_, _, _| delivered = true end close_all_connections! wait_for_recovery_with { connections.any? } expect(ch).to be_open q.publish("") ch.wait_for_confirms poll_until { delivered } end end it "recovers all consumers" do n = 32 with_open do |c| ch = c.create_channel q = ch.queue("", exclusive: true) n.times { q.subscribe { |_, _, _| } } close_all_connections! wait_for_recovery_with { connections.any? } expect(ch).to be_open sleep 0.5 expect(q.consumer_count).to eq n end end it "recovers all queues" do n = 32 qs = [] with_open do |c| ch = c.create_channel n.times do qs << ch.queue("", exclusive: true) end close_all_connections! wait_for_recovery_with { queue_names.include?(qs.first.name) } sleep 0.5 expect(ch).to be_open qs.each do |q| ch.queue_declare(q.name, passive: true) end end end def exchange_names_in_vhost(vhost) http_client.list_exchanges(vhost).map {|e| e["name"]} end def connections http_client.list_connections end def channels http_client.list_channels end def queue_names http_client.list_queues.map {|q| q["name"]} end def close_all_connections! # let whatever actions were taken before # this call a chance to propagate, e.g. to make # sure that connections are accounted for in the # stats DB. # # See bin/ci/before_build for management plugin # pre-configuration. # # MK. sleep 1.1 connections.each do |conn_info| close_ignoring_permitted_exceptions(conn_info.name) end end def close_ignoring_permitted_exceptions(connection_name) http_client.close_connection(connection_name) rescue Bunny::ConnectionForced, Faraday::ResourceNotFound # ignored end def wait_for_recovery_with(&probe) poll_until &probe end def poll_while(&probe) Bunny::TestKit.poll_while(&probe) end def poll_until(&probe) Bunny::TestKit.poll_until(&probe) end def with_open(c = Bunny.new(network_recovery_interval: recovery_interval, recover_from_connection_close: true, logger: logger), &block) c.start block.call(c) ensure c.close(false) rescue nil end def with_open_multi_host(&block) c = Bunny.new(hosts: ["127.0.0.1", "localhost"], network_recovery_interval: recovery_interval, recover_from_connection_close: true, logger: logger) with_open(c, &block) end def with_open_multi_broken_host(&block) c = Bunny.new(hosts: ["broken", "127.0.0.1", "localhost"], hosts_shuffle_strategy: Proc.new { |hosts| hosts }, # We do not shuffle for these tests so we always hit the broken host network_recovery_interval: recovery_interval, recover_from_connection_close: true, logger: logger) with_open(c, &block) end def ensure_queue_recovery(ch, q) ch.confirm_select q.purge x = ch.default_exchange x.publish("msg", routing_key: q.name) ch.wait_for_confirms expect(q.message_count).to eq 1 q.purge end def ensure_queue_binding_recovery(ch, x, q, routing_key = "") ch.confirm_select q.purge x.publish("msg", routing_key: routing_key) ch.wait_for_confirms expect(q.message_count).to eq 1 q.purge end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/connection_spec.rb000066400000000000000000000403251464043542000274530ustar00rootroot00000000000000require "spec_helper" def local_hostname ENV.fetch("BUNNY_RABBITMQ_HOSTNAME", "127.0.0.1") end describe Bunny::Session do let(:port) { AMQ::Protocol::DEFAULT_PORT } let(:username) { "guest" } let(:tls_port) { AMQ::Protocol::TLS_PORT } context "initialized via connection URI" do after :each do subject.close if subject.open? end context "when schema is not one of [amqp, amqps]" do it "raises ArgumentError" do expect { described_class.new("http://127.0.0.1") }.to raise_error(ArgumentError, /amqp or amqps schema/) end end it "handles amqp:// URIs w/o path part" do session = described_class.new("amqp://127.0.0.1") session.start expect(session.vhost).to eq "/" expect(session.host).to eq "127.0.0.1" expect(session.port).to eq 5672 expect(session.ssl?).to eq false session.close end context "when URI ends in a slash" do it "parses vhost as an empty string" do session = described_class.new("amqp://127.0.0.1/") expect(session.hostname).to eq "127.0.0.1" expect(session.port).to eq 5672 expect(session.vhost).to eq "" end end context "when URI is amqp://dev.rabbitmq.com/a/path/with/slashes" do it "raises an ArgumentError" do expect { described_class.new("amqp://dev.rabbitmq.com/a/path/with/slashes") }.to raise_error(ArgumentError) end end end context "initialized with all defaults" do it "provides a way to fine tune socket options" do conn = Bunny.new conn.start expect(conn.transport.socket).to respond_to(:setsockopt) conn.close end it "successfully negotiates the connection" do conn = Bunny.new conn.start expect(conn).to be_connected expect(conn.server_properties).not_to be_nil expect(conn.server_capabilities).not_to be_nil props = conn.server_properties expect(props["product"]).not_to be_nil expect(props["platform"]).not_to be_nil expect(props["version"]).not_to be_nil conn.close end end unless ENV["CI"] context "initialized with TCP connection timeout = 5" do it "successfully connects" do conn = described_class.new(connection_timeout: 5) conn.start expect(conn).to be_connected expect(conn.server_properties).not_to be_nil expect(conn.server_capabilities).not_to be_nil props = conn.server_properties expect(props["product"]).not_to be_nil expect(props["platform"]).not_to be_nil expect(props["version"]).not_to be_nil conn.close end end context "initialized with hostname: 127.0.0.1" do after :each do subject.close if subject.open? end let(:host) { "127.0.0.1" } subject do described_class.new(hostname: host) end it "uses hostname = 127.0.0.1" do expect(subject.host).to eq host expect(subject.hostname).to eq host end it "uses port 5672" do expect(subject.port).to eq port end it "uses username = guest" do expect(subject.username).to eq username end end context "initialized with hostname: localhost" do after :each do subject.close if subject.open? end let(:host) { "localhost" } let(:subject) { described_class.new(hostname: host) } it "uses hostname = localhost" do expect(subject.host).to eq host expect(subject.hostname).to eq host end it "uses port 5672" do expect(subject.port).to eq port end it "uses username = guest" do expect(subject.username).to eq username expect(subject.user).to eq username end end context "initialized with a list of hosts" do after :each do subject.close if subject.open? end let(:host) { "192.168.1.10" } let(:hosts) { [host] } let(:subject) { described_class.new(hosts: hosts) } it "uses hostname = 192.168.1.10" do expect(subject.host).to eq host expect(subject.hostname).to eq host end it "uses port 5672" do expect(subject.port).to eq port end it "uses username = guest" do expect(subject.username).to eq username expect(subject.user).to eq username end end context "initialized with a list of addresses" do after :each do subject.close if subject.open? end let(:host) { "192.168.1.10" } let(:port) { 5673 } let(:address) { "#{host}:#{port}" } let(:addresses) { [address] } let(:subject) { described_class.new(addresses: addresses) } it "uses hostname = 192.168.1.10" do expect(subject.host).to eq host expect(subject.hostname).to eq host end it "uses port 5673" do expect(subject.port).to eq port end it "uses username = guest" do expect(subject.username).to eq username expect(subject.user).to eq username end end context "initialized with addresses: [...] with quoted IPv6 hostnames" do after :each do subject.close if subject.open? end let(:host) { "[2001:db8:85a3:8d3:1319:8a2e:370:7348]" } let(:port) { 5673 } let(:address) { "#{host}:#{port}" } let(:addresses) { [address] } let(:subject) { described_class.new(addresses: addresses) } it "uses correct hostname" do expect(subject.host).to eq host expect(subject.hostname).to eq host end it "uses port 5673" do expect(subject.port).to eq port end it "uses username = guest" do expect(subject.username).to eq username expect(subject.user).to eq username end end context "initialized with addresses: [...] with quoted IPv6 hostnames without ports" do after :each do subject.close if subject.open? end let(:host) { "[2001:db8:85a3:8d3:1319:8a2e:370:7348]" } let(:address) { host } let(:addresses) { [address] } let(:subject) { described_class.new(addresses: addresses) } it "uses correct hostname" do expect(subject.host).to eq host expect(subject.hostname).to eq host end it "uses port 5672" do expect(subject.port).to eq 5672 end it "uses username = guest" do expect(subject.username).to eq username expect(subject.user).to eq username end end context "initialized with addresses: [...] with an quoted IPv6 hostnames" do after :each do subject.close if subject.open? end let(:host) { "2001:db8:85a3:8d3:1319:8a2e:370:7348" } let(:port) { 5673 } let(:address) { "#{host}:#{port}" } let(:addresses) { [address] } let(:subject) { described_class.new(addresses: addresses) } it "fails to correctly parse the host (and emits a warning)" do expect(subject.host).to eq "2001" expect(subject.hostname).to eq "2001" end it "fails to correctly parse the port (and emits a warning)" do expect(subject.port).to eq 0 end it "uses username = guest" do expect(subject.username).to eq username expect(subject.user).to eq username end end context "initialized with conflicting hosts and addresses" do let(:host) { "192.168.1.10" } let(:port) { 5673 } let(:address) { "#{host}:#{port}" } let(:io) { StringIO.new } let(:logger) { ::Logger.new(io) } it "raises an argument error when there is are hosts and an address" do expect { described_class.new(addresses: [address], hosts: [host]) }.to raise_error(ArgumentError) end it "logs a warning when there is a single host and an array" do described_class.new(addresses: [address], host: host, logger: logger) expect(io.string).to match(/both a host and an array of hosts/) end it "converts hosts in addresses to addresses" do strategy = Proc.new { |addresses| addresses } session = described_class.new(addresses: [address,host ], hosts_shuffle_strategy: strategy) strategy = Proc.new { |addresses| addresses } expect(session.to_s).to include 'addresses=[192.168.1.10:5673,192.168.1.10:5672]' end end context "initialized with channel_max: 4096" do after :each do subject.close if subject.open? end let(:channel_max) { 1024 } let(:subject) { described_class.new(channel_max: channel_max) } # this assumes RabbitMQ has no lower value configured. In 3.2 # it is 0 (no limit) by default and 1024 is still a fairly low value # for future releases. MK. it "negotiates channel max to be 1024" do subject.start expect(subject.channel_max).to eq channel_max subject.close end end context "initialized with ssl: true" do let(:subject) do described_class.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", ssl: true, ssl_cert: "spec/tls/client_certificate.pem", ssl_key: "spec/tls/client_key.pem", ssl_ca_certificates: ["./spec/tls/ca_certificate.pem"]) end it "uses TLS port" do expect(subject.port).to eq tls_port end end context "initialized with tls: true" do let(:subject) do described_class.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", tls: true, tls_cert: "spec/tls/client_certificate.pem", tls_key: "spec/tls/client_key.pem", tls_ca_certificates: ["./spec/tls/ca_certificate.pem"]) end it "uses TLS port" do expect(subject.port).to eq tls_port end end end context "initialized with hostname: 127.0.0.1 and non-default credentials" do after :each do subject.close if subject.open? end let(:host) { "127.0.0.1" } # see ./bin/ci/before_build let(:username) { "bunny_gem" } let(:password) { "bunny_password" } let(:vhost) { "bunny_testbed" } subject do described_class.new(hostname: host, username: username, password: password, virtual_host: vhost) end it "successfully connects" do 5.times { subject.start } expect(subject).to be_connected expect(subject.server_properties).not_to be_nil expect(subject.server_capabilities).not_to be_nil props = subject.server_properties expect(props["product"]).not_to be_nil expect(props["platform"]).not_to be_nil expect(props["version"]).not_to be_nil end it "uses hostname = 127.0.0.1" do expect(subject.host).to eq host expect(subject.hostname).to eq host end it "uses port 5672" do expect(subject.port).to eq port end it "uses provided vhost" do expect(subject.vhost).to eq vhost expect(subject.virtual_host).to eq vhost end it "uses provided username" do expect(subject.username).to eq username end it "uses provided password" do expect(subject.password).to eq password end end context "initialized with hostname: 127.0.0.1 and non-default credentials (take 2)" do after :each do subject.close if subject.open? end let(:host) { "127.0.0.1" } # see ./bin/ci/before_build let(:username) { "bunny_gem" } let(:password) { "bunny_password" } let(:vhost) { "bunny_testbed" } subject do described_class.new(hostname: host, username: username, password: password, vhost: vhost) end it "successfully connects" do subject.start expect(subject).to be_connected expect(subject.server_properties).not_to be_nil expect(subject.server_capabilities).not_to be_nil props = subject.server_properties expect(props["product"]).not_to be_nil expect(props["platform"]).not_to be_nil expect(props["version"]).not_to be_nil end it "uses hostname = 127.0.0.1" do expect(subject.host).to eq host expect(subject.hostname).to eq host end it "uses port 5672" do expect(subject.port).to eq port end it "uses provided username" do expect(subject.username).to eq username end it "uses provided password" do expect(subject.password).to eq password end end context "initialized with hostname: 127.0.0.1 and non-default credentials (take 2)" do after :each do subject.close if subject.open? end let(:host) { "127.0.0.1" } # see ./bin/ci/before_build let(:username) { "bunny_gem" } let(:password) { "bunny_password" } let(:vhost) { "bunny_testbed" } let(:interval) { 1 } subject do described_class.new(hostname: host, username: username, password: password, vhost: vhost, heartbeat_timeout: interval) end it "successfully connects" do subject.start expect(subject).to be_connected expect(subject.server_properties).not_to be_nil expect(subject.server_capabilities).not_to be_nil props = subject.server_properties expect(props["product"]).not_to be_nil expect(props["platform"]).not_to be_nil expect(props["version"]).not_to be_nil expect(props["capabilities"]).not_to be_nil # this is negotiated with RabbitMQ, so we need to # establish the connection first expect(subject.heartbeat).to eq interval end end context "initialized with hostname: 127.0.0.1 and INVALID credentials" do let(:host) { "127.0.0.1" } # see ./bin/ci/before_build let(:username) { "bunny_gem#{Bunny::Timestamp.now.to_i}" } let(:password) { "sdjkfhsdf8ysd8fy8" } let(:vhost) { "___sd89aysd98789" } subject do described_class.new(hostname: host, username: username, password: password, vhost: vhost) end it "fails to connect" do expect do subject.start end.to raise_error(Bunny::PossibleAuthenticationFailureError) end it "uses provided username" do expect(subject.username).to eq username end it "uses provided password" do expect(subject.password).to eq password end end context "initialized with unreachable host or port" do it "fails to connect" do expect do c = described_class.new(port: 38000) c.start end.to raise_error(Bunny::TCPConnectionFailed) end it "is not connected" do begin c = described_class.new(port: 38000) c.start rescue Bunny::TCPConnectionFailed => e true end expect(subject.status).to eq :not_connected end it "is not open" do begin c = described_class.new(port: 38000) c.start rescue Bunny::TCPConnectionFailed => e true end expect(subject).not_to be_open end end context "initialized with a custom logger object" do let(:io) { StringIO.new } let(:logger) { ::Logger.new(io) } it "uses provided logger" do conn = described_class.new(logger: logger) conn.start expect(io.string.length).to be > 100 conn.close end it "doesn't reassign the logger's progname attribute" do expect(logger).not_to receive(:progname=) described_class.new(logger: logger) end end context "initialized with a custom connection name" do it "uses provided connection name with default connection string" do conn = Bunny.new(connection_name: 'test_name') expect(conn.connection_name).to eq 'test_name' end it "uses provided connection name from client property hash" do conn = Bunny.new(client_properties: {connection_name: 'cp/test_name'}) expect(conn.connection_name).to eq 'cp/test_name' end it "uses provided connection name with custom connection string" do conn = Bunny.new('amqp://guest:guest@rabbitmq:5672', connection_name: 'test_name3') expect(conn.connection_name).to eq 'test_name3' end it "uses provided connection name with hash options" do conn = Bunny.new(user: 'user', password: 'p455w0rd', connection_name: 'test_name4') expect(conn.connection_name).to eq 'test_name4' end end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/connection_stop_spec.rb000066400000000000000000000034731464043542000305230ustar00rootroot00000000000000require "spec_helper" describe Bunny::Session do let(:http_client) { RabbitMQ::HTTP::Client.new("http://127.0.0.1:15672") } def close_connection(client_port) # let whatever actions were taken before # this call a chance to propagate, e.g. to make # sure that connections are accounted for in the # stats DB. # # See bin/ci/before_build for management plugin # pre-configuration. # # MK. sleep 1.1 c = http_client. list_connections. find { |conn_info| conn_info && conn_info.peer_port.to_i == client_port } http_client.close_connection(c.name) if c end def wait_for_recovery sleep 1.5 end it "can be closed" do c = Bunny.new(automatically_recover: false) c.start ch = c.create_channel expect(c).to be_connected c.stop expect(c).to be_closed end it "can be closed twice (Session#close is idempotent)" do c = Bunny.new(automatically_recover: false) c.start ch = c.create_channel expect(c).to be_connected c.stop expect(c).to be_closed c.stop expect(c).to be_closed end describe "in a single threaded mode" do it "can be closed" do c = Bunny.new(automatically_recover: false, threaded: false) c.start ch = c.create_channel expect(c).to be_connected c.stop expect(c).to be_closed end end describe "that recovers from connection.close" do it "can be closed" do c = Bunny.new(automatically_recover: true, recover_from_connection_close: true, network_recovery_interval: 0.2) c.start ch = c.create_channel sleep 1.5 expect(c).to be_open sleep 1.5 close_connection(c.local_port) wait_for_recovery expect(c).to be_open expect(ch).to be_open c.close end end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/consumer_cancellation_notification_spec.rb000066400000000000000000000057021464043542000344310ustar00rootroot00000000000000require "spec_helper" describe Bunny::Channel do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end context "with implicit consumer construction" do let(:queue_name) { "basic.consume#{rand}" } it "supports consumer cancellation notifications" do cancelled = false ch = connection.create_channel t = Thread.new do ch2 = connection.create_channel q = ch2.queue(queue_name, auto_delete: true) q.subscribe(on_cancellation: Proc.new { cancelled = true }) end t.abort_on_exception = true sleep 0.5 x = ch.default_exchange x.publish("abc", routing_key: queue_name) sleep 0.5 ch.queue(queue_name, auto_delete: true).delete sleep 0.5 expect(cancelled).to eq true ch.close end end context "with explicit consumer construction" do class ExampleConsumer < Bunny::Consumer def cancelled? @cancelled end def handle_cancellation(_) @cancelled = true end end let(:queue_name) { "basic.consume#{rand}" } it "supports consumer cancellation notifications" do consumer = nil ch = connection.create_channel t = Thread.new do ch2 = connection.create_channel q = ch2.queue(queue_name, auto_delete: true) consumer = ExampleConsumer.new(ch2, q, "") q.subscribe_with(consumer) end t.abort_on_exception = true sleep 0.5 x = ch.default_exchange x.publish("abc", routing_key: queue_name) sleep 0.5 ch.queue(queue_name, auto_delete: true).delete sleep 0.5 expect(consumer).to be_cancelled ch.close end end context "with consumer re-registration" do class ExampleConsumerThatReregisters < Bunny::Consumer def handle_cancellation(_) @queue = @channel.queue("basic.consume.after_cancellation", auto_delete: true) @channel.basic_consume_with(self) end end let(:queue_name) { "basic.consume#{rand}" } it "works correctly" do consumer = nil xs = [] ch = connection.create_channel t = Thread.new do ch2 = connection.create_channel q = ch2.queue(queue_name, auto_delete: true) consumer = ExampleConsumerThatReregisters.new(ch2, q, "") consumer.on_delivery do |_, _, payload| xs << payload end q.subscribe_with(consumer) end t.abort_on_exception = true sleep 0.5 x = ch.default_exchange x.publish("abc", routing_key: queue_name) sleep 0.5 ch.queue(queue_name, auto_delete: true).delete x.publish("abc", routing_key: queue_name) sleep 0.5 q = ch.queue("basic.consume.after_cancellation", auto_delete: true) expect(xs).to eq ["abc"] ch.close end end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/dead_lettering_spec.rb000066400000000000000000000041141464043542000302620ustar00rootroot00000000000000require "spec_helper" describe "A message" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end it "is considered to be dead-lettered when it is rejected without requeueing" do ch = connection.create_channel x = ch.fanout("amq.fanout") dlx = ch.fanout("bunny.tests.dlx.exchange") q = ch.queue("", exclusive: true, arguments: {"x-dead-letter-exchange" => dlx.name}).bind(x) # dead letter queue dlq = ch.queue("", exclusive: true).bind(dlx) x.publish("") sleep 0.2 delivery_info, _, _ = q.pop(manual_ack: true) expect(dlq.message_count).to be_zero ch.nack(delivery_info.delivery_tag) sleep 0.2 expect(q.message_count).to be_zero delivery, properties, body = dlq.pop ds = properties.headers["x-death"] expect(ds).not_to be_empty expect(ds.first["reason"]).to eq("rejected") dlx.delete end it "is considered to be dead-lettered when it expires" do ch = connection.create_channel x = ch.fanout("amq.fanout") dlx = ch.fanout("bunny.tests.dlx.exchange") q = ch.queue("", exclusive: true, arguments: {"x-dead-letter-exchange" => dlx.name, "x-message-ttl" => 100}).bind(x) # dead letter queue dlq = ch.queue("", exclusive: true).bind(dlx) x.publish("") sleep 0.2 expect(q.message_count).to be_zero expect(dlq.message_count).to eq 1 dlx.delete end it "carries the x-death header" do ch = connection.create_channel x = ch.fanout("amq.fanout") dlx = ch.fanout("bunny.tests.dlx.exchange") q = ch.queue("", exclusive: true, arguments: {"x-dead-letter-exchange" => dlx.name, "x-message-ttl" => 100}).bind(x) # dead letter queue dlq = ch.queue("", exclusive: true).bind(dlx) x.publish("") sleep 0.2 delivery, properties, body = dlq.pop ds = properties.headers["x-death"] expect(ds).not_to be_empty expect(ds.first["reason"]).to eq("expired") dlx.delete end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/default_queue_type_spec.rb000066400000000000000000000036511464043542000312060ustar00rootroot00000000000000require "spec_helper" describe Bunny::Queue do %w(classic quorum stream).each do |qtype| context "when target virtual host's default queue type is '#{qtype}'" do let(:connection) do # see bin/ci/before_build c = Bunny.new(user: "bunny_gem", password: "bunny_password", vhost: "bunny_dqt_#{qtype}") c.start c end after :each do connection.close if connection.open? end let(:name) { "bunny.dqt.#{qtype}.#{rand}" } let(:x_args) do {"x-queue-type" => qtype} end context "and x-queue-type is omitted by the client" do it "declares a new queue with that name" do ch = connection.create_channel q = ch.queue(name, durable: true, exclusive: false) expect(q.name).to eq name q.delete ch.close end it "can re-declare the queue" do ch = connection.create_channel # this lower-level API bypasses queue object cache 50.times do ch.queue_declare(name, durable: true, exclusive: false) end expect(ch).to be_open ch.queue_delete(name) ch.close end end context "and x-queue-type is explicitly set by the client" do it "declares a new queue with that name" do ch = connection.create_channel q = ch.queue(name, durable: true, exclusive: false, arguments: x_args) expect(q.name).to eq name q.delete ch.close end it "can re-declare the queue" do ch = connection.create_channel # this lower-level API bypasses queue object cache 50.times do ch.queue_declare(name, durable: true, exclusive: false, arguments: x_args) end expect(ch).to be_open ch.queue_delete(name) ch.close end end end end endruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/exchange_bind_spec.rb000066400000000000000000000012761464043542000300740ustar00rootroot00000000000000require "spec_helper" describe Bunny::Exchange do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end it "binds two existing exchanges" do ch = connection.create_channel source = ch.fanout("bunny.exchanges.source", auto_delete: true) destination = ch.fanout("bunny.exchanges.destination", auto_delete: true) queue = ch.queue("", exclusive: true) queue.bind(destination) destination.bind(source) source.publish("") sleep 0.5 expect(queue.message_count).to be > 0 ch.close end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/exchange_declare_spec.rb000066400000000000000000000124101464043542000305470ustar00rootroot00000000000000require "spec_helper" describe Bunny::Exchange do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close end context "of default type" do it "is declared with an empty name" do ch = connection.create_channel x = Bunny::Exchange.default(ch) expect(x.name).to eq '' end end context "of type fanout" do context "with a non-predefined name" do it "is declared" do ch = connection.create_channel name = "bunny.tests.exchanges.fanout#{rand}" x = ch.fanout(name) expect(x.name).to eq name x.delete ch.close end end context "with a predefined name" do it "is NOT declared" do ch = connection.create_channel name = "amq.fanout" x = ch.fanout(name) expect(x.name).to eq name ch.close end end context "with a name prefixed with 'amq.'" do it "raises an exception" do ch = connection.create_channel expect { ch.fanout("amq.test") }.to raise_error(Bunny::AccessRefused) expect(ch).to be_closed expect { ch.fanout("amq.test") }.to raise_error(Bunny::ChannelAlreadyClosed) end end context "with the durable property" do it "is declared as durable" do ch = connection.create_channel name = "bunny.tests.exchanges.durable" x = ch.fanout(name, durable: true) expect(x.name).to eq name expect(x).to be_durable expect(x).not_to be_auto_delete x.delete ch.close end end context "with the auto-delete property" do it "is declared as auto-delete" do ch = connection.create_channel name = "bunny.tests.exchanges.auto-delete" x = ch.fanout(name, auto_delete: true) expect(x.name).to eq name expect(x).not_to be_durable expect(x).to be_auto_delete ch.exchange(name, type: :fanout, auto_delete: true) x.delete ch.close end end context "when declared with a different set of attributes" do it "raises an exception" do ch1 = connection.create_channel ch2 = connection.create_channel x = ch1.fanout("bunny.tests.exchanges.fanout", auto_delete: true, durable: false) expect { # force re-declaration ch2.exchange_declare("bunny.tests.exchanges.fanout", :direct, auto_delete: false, durable: true) }.to raise_error(Bunny::PreconditionFailed) expect(ch2).to be_closed expect { ch2.fanout("bunny.tests.exchanges.fanout", auto_delete: true, durable: false) }.to raise_error(Bunny::ChannelAlreadyClosed) end end end context "of type direct" do context "with a non-predefined name" do it "is declared" do ch = connection.create_channel name = "bunny.tests.exchanges.direct" x = ch.direct(name) expect(x.name).to eq name ch.exchange(name, type: :direct) x.delete ch.close end end context "with a predefined name" do it "is NOT declared" do ch = connection.create_channel name = "amq.direct" x = ch.direct(name) expect(x.name).to eq name ch.close end end end context "of type topic" do context "with a non-predefined name" do it "is declared" do ch = connection.create_channel name = "bunny.tests.exchanges.topic" x = ch.topic(name) expect(x.name).to eq name ch.exchange(name, type: :topic) x.delete ch.close end end context "with a predefined name" do it "is NOT declared" do ch = connection.create_channel name = "amq.topic" x = ch.topic(name) expect(x.name).to eq name ch.close end end end context "of type headers" do context "with a non-predefined name" do it "is declared" do ch = connection.create_channel name = "bunny.tests.exchanges.headers" x = ch.headers(name) expect(x.name).to eq name x.delete ch.close end end context "with a predefined name (amq.match)" do it "is NOT declared" do ch = connection.create_channel name = "amq.match" x = ch.headers(name) expect(x.name).to eq name ch.close end end context "with a predefined name (amq.headers)" do it "is NOT declared" do ch = connection.create_channel name = "amq.headers" x = ch.headers(name) expect(x.name).to eq name ch.close end end end context "that is internal" do it "can be declared" do ch = connection.create_channel x = ch.fanout("bunny.tests.exchanges.internal", internal: true) expect(x).to be_internal x.delete ch.close end end context "not declared as internal" do it "is not internal" do ch = connection.create_channel x = ch.fanout("bunny.tests.exchanges.non-internal") expect(x).not_to be_internal x.delete ch.close end end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/exchange_delete_spec.rb000066400000000000000000000051511464043542000304160ustar00rootroot00000000000000require "spec_helper" describe Bunny::Exchange, "#delete" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end context "with a name of an existing exchange" do it "deletes that exchange" do ch = connection.create_channel x = ch.fanout("bunny.tests.exchanges.fanout#{rand}") x.delete # no exception as of RabbitMQ 3.2. MK. x.delete expect(ch.exchanges.size).to eq 0 end end context "with a name of a non-existent exchange" do it "DOES NOT rais an exception" do ch = connection.create_channel # no exception as of RabbitMQ 3.2. MK. ch.exchange_delete("sdkhflsdjflskdjflsd#{rand}") ch.exchange_delete("sdkhflsdjflskdjflsd#{rand}") ch.exchange_delete("sdkhflsdjflskdjflsd#{rand}") ch.exchange_delete("sdkhflsdjflskdjflsd#{rand}") end end context "with a name of 'amq.direct'" do it "does not delete the exchange" do ch = connection.create_channel x = ch.direct('amq.direct') expect(x.delete).to eq nil end end context "with a name of 'amq.fanout'" do it "does not delete the exchange" do ch = connection.create_channel x = ch.fanout('amq.fanout') expect(x.delete).to eq nil end end context "with a name of 'amq.topic'" do it "does not delete the exchange" do ch = connection.create_channel x = ch.topic('amq.topic') expect(x.delete).to eq nil end end context "with a name of 'amq.headers'" do it "does not delete the exchange" do ch = connection.create_channel x = ch.headers('amq.headers') expect(x.delete).to eq nil end end context "with a name of 'amq.match'" do it "does not delete the exchange" do ch = connection.create_channel x = ch.headers('amq.match') expect(x.delete).to eq nil end end describe "#exchange_exists?" do context "when a exchange exists" do it "returns true" do ch = connection.create_channel expect(connection.exchange_exists?("amq.fanout")).to eq true expect(connection.exchange_exists?("amq.direct")).to eq true expect(connection.exchange_exists?("amq.topic")).to eq true expect(connection.exchange_exists?("amq.match")).to eq true end end context "when a exchange DOES NOT exist" do it "returns false" do expect(connection.exchange_exists?("suf89u9a4jo3ndnakls##{Bunny::Timestamp.now.to_i}")).to eq false end end end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/exchange_unbind_spec.rb000066400000000000000000000015351464043542000304350ustar00rootroot00000000000000require "spec_helper" describe Bunny::Exchange do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end it "unbinds two existing exchanges" do ch = connection.create_channel source = ch.fanout("bunny.exchanges.source#{rand}") destination = ch.fanout("bunny.exchanges.destination#{rand}") queue = ch.queue("", exclusive: true) queue.bind(destination) destination.bind(source) source.publish("") sleep 0.5 expect(queue.message_count).to eq 1 queue.pop(manual_ack: true) destination.unbind(source) source.publish("") sleep 0.5 expect(queue.message_count).to eq 0 source.delete destination.delete ch.close end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/exclusive_queue_spec.rb000066400000000000000000000012001464043542000305140ustar00rootroot00000000000000require "spec_helper" describe Bunny::Queue do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end it "is closed when the connection it was declared on is closed" do ch1 = connection.create_channel ch2 = connection.create_channel q = ch1.queue("", exclusive: true) ch1.queue_declare(q.name, passive: true) ch2.queue_declare(q.name, passive: true) ch1.close ch2.queue_declare(q.name, passive: true) ch2.queue_delete(q.name) ch2.close end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/heartbeat_spec.rb000066400000000000000000000020611464043542000272460ustar00rootroot00000000000000require "spec_helper" describe "Client-defined heartbeat interval" do context "with value > 0" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", heartbeat_timeout: 4) c.start c end it "can be enabled explicitly" do sleep 5.0 connection.close end end # issue 267 context "with value = 0" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", heartbeat_timeout: 0, automatically_recover: false) c.start c end it "disables heartbeats" do sleep 1.0 connection.close end end end describe "Server-defined heartbeat interval" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", heartbeat_timeout: :server) c.start c end it "can be enabled explicitly" do puts "Sleeping for 5 seconds with heartbeat interval of 4" sleep 5.0 connection.close end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/message_properties_access_spec.rb000066400000000000000000000057601464043542000325410ustar00rootroot00000000000000require "spec_helper" describe Bunny::Queue, "#subscribe" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end let(:queue_name) { "bunny.basic_consume#{rand}" } it "provides delivery handler access to message properties" do @now = Bunny::Timestamp.now metadata = {} envelope = {} t = Thread.new do ch = connection.create_channel q = ch.queue(queue_name, auto_delete: true, durable: false) q.subscribe(exclusive: true, manual_ack: false) do |delivery_info, properties, payload| metadata = properties envelope = delivery_info end end t.abort_on_exception = true sleep 0.5 ch = connection.create_channel x = ch.default_exchange x.publish("hello", routing_key: queue_name, app_id: "bunny.example", priority: 8, type: "kinda.checkin", # headers table keys can be anything headers: { coordinates: { latitude: 59.35, longitude: 18.066667 }, time: @now, participants: 11, venue: "Stockholm", true_field: true, false_field: false, nil_field: nil, ary_field: ["one", 2.0, 3, [{"abc" => 123}]] }, timestamp: @now.to_i, reply_to: "a.sender", correlation_id: "r-1", message_id: "m-1") sleep 0.7 expect(metadata.content_type).to eq "application/octet-stream" expect(metadata.priority).to eq 8 time = metadata.headers["time"] expect(time.year).to eq @now.year expect(time.month).to eq @now.month expect(time.day).to eq @now.day expect(time.hour).to eq @now.hour expect(time.min).to eq @now.min expect(time.sec).to eq @now.sec expect(metadata.headers["coordinates"]["latitude"]).to eq 59.35 expect(metadata.headers["participants"]).to eq 11 expect(metadata.headers["venue"]).to eq "Stockholm" expect(metadata.headers["true_field"]).to eq true expect(metadata.headers["false_field"]).to eq false expect(metadata.headers["nil_field"]).to be_nil expect(metadata.headers["ary_field"]).to eq ["one", 2.0, 3, [{ "abc" => 123}]] expect(metadata.timestamp).to eq Time.at(@now.to_i) expect(metadata.type).to eq "kinda.checkin" expect(metadata.reply_to).to eq "a.sender" expect(metadata.correlation_id).to eq "r-1" expect(metadata.message_id).to eq "m-1" expect(metadata.app_id).to eq "bunny.example" expect(envelope.consumer_tag).not_to be_nil expect(envelope.consumer_tag).not_to be_empty expect(envelope).not_to be_redelivered expect(envelope.delivery_tag).to eq 1 expect(envelope.routing_key).to eq queue_name expect(envelope.exchange).to eq "" ch.close end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/predeclared_exchanges_spec.rb000066400000000000000000000007611464043542000316130ustar00rootroot00000000000000require "spec_helper" describe "amq.* exchanges" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end it "are predeclared" do ch = connection.create_channel ["amq.fanout", "amq.direct", "amq.topic", "amq.match", "amq.headers"].each do |e| x = ch.exchange(e) expect(x).to be_predeclared end ch.close end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/publisher_confirms_spec.rb000066400000000000000000000120571464043542000312120ustar00rootroot00000000000000require "spec_helper" describe Bunny::Channel do after :each do connection.close if connection.open? end let(:n) { 200 } shared_examples "publish confirms" do context "when publishing with confirms enabled" do it "increments delivery index" do ch = connection.create_channel expect(ch).not_to be_using_publisher_confirmations ch.confirm_select expect(ch).to be_using_publisher_confirmations q = ch.queue("", exclusive: true) x = ch.default_exchange n.times do x.publish("xyzzy", routing_key: q.name) end expect(ch.next_publish_seq_no).to eq n + 1 expect(ch.wait_for_confirms).to eq true sleep 0.25 expect(q.message_count).to eq n q.purge ch.close end describe "#wait_for_confirms" do it "should not hang when all the publishes are confirmed" do ch = connection.create_channel expect(ch).not_to be_using_publisher_confirmations ch.confirm_select expect(ch).to be_using_publisher_confirmations q = ch.queue("", exclusive: true) x = ch.default_exchange n.times do x.publish("xyzzy", routing_key: q.name) end expect(ch.next_publish_seq_no).to eq n + 1 expect(ch.wait_for_confirms).to eq true sleep 0.25 expect { Bunny::Timeout.timeout(2) do expect(ch.wait_for_confirms).to eq true end }.not_to raise_error end it "raises an error when called on a closed channel" do ch = connection.create_channel ch.confirm_select ch.close expect { ch.wait_for_confirms }.to raise_error(Bunny::ChannelAlreadyClosed) end end context "when some of the messages get nacked" do it "puts the nacks in the nacked_set" do ch = connection.create_channel expect(ch).not_to be_using_publisher_confirmations ch.confirm_select expect(ch).to be_using_publisher_confirmations q = ch.queue("", exclusive: true) x = ch.default_exchange n.times do x.publish("xyzzy", routing_key: q.name) end #be sneaky to simulate a nack nacked_tag = nil ch.instance_variable_get(:@unconfirmed_set_mutex).synchronize do expect(ch.unconfirmed_set).to_not be_empty nacked_tag = ch.unconfirmed_set.reduce(ch.next_publish_seq_no - 1) { |lowest, i| i < lowest ? i : lowest } ch.handle_ack_or_nack(nacked_tag, false, true) end expect(ch.nacked_set).not_to be_empty expect(ch.nacked_set).to include(nacked_tag) expect(ch.next_publish_seq_no).to eq n + 1 expect(ch.wait_for_confirms).to eq false expect(ch.nacked_set).not_to be_empty expect(ch.nacked_set).to include(nacked_tag) sleep 0.25 expect(q.message_count).to eq n q.purge ch.close end end end end context "with a multi-threaded connection" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", continuation_timeout: 10000) c.start c end include_examples "publish confirms" it "returns only when all confirmations for publishes are received" do ch = connection.create_channel operations_log = [] operations_log_mutex = Mutex.new acks_received = Queue.new log_acks = proc do |tag, _, is_nack| operations_log_mutex.synchronize do operation = "#{'n' if is_nack}ack_#{tag}" operations_log << operation unless operations_log.include?(operation) end acks_received << true end ch.confirm_select(log_acks) x = ch.default_exchange q = ch.temporary_queue x.publish('msg', routing_key: q.name) # wait for the confirmation to arrive acks_received.pop # artificially simulate a slower ack. the test should work properly even # without this patch, but it's here just to be sure we catch it. def (x.channel).handle_ack_or_nack(delivery_tag_before_offset, multiple, nack) sleep 0.1 super end x.publish('msg', routing_key: q.name) x.publish('msg', routing_key: q.name) if x.wait_for_confirms operations_log_mutex.synchronize do operations_log << 'all_confirmed' end end # wait for all the confirmations to arrive acks_received.pop acks_received.pop expect(operations_log).to eq([ 'ack_1', 'ack_2', 'ack_3', 'all_confirmed', ]) end end context "with a single-threaded connection" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", continuation_timeout: 10000, threaded: false) c.start c end include_examples "publish confirms" end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/publishing_edge_cases_spec.rb000066400000000000000000000040431464043542000316170ustar00rootroot00000000000000# -*- coding: utf-8 -*- require "spec_helper" unless ENV["CI"] describe "Message framing implementation" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", port: ENV.fetch("RABBITMQ_PORT", 5672)) c.start c end after :each do connection.close if connection.open? end context "with payload exceeding 128 Kb (max frame size)" do it "successfully frames the message" do ch = connection.create_channel q = ch.queue("", exclusive: true) x = ch.default_exchange as = ("a" * (1024 * 1024 * 4 + 28237777)) x.publish(as, routing_key: q.name, persistent: true) sleep(1) expect(q.message_count).to eq 1 _, _, payload = q.pop expect(payload.bytesize).to eq as.bytesize ch.close end end context "with payload of several MBs of non-ASCII characters" do it "successfully frames the message" do ch = connection.create_channel q = ch.queue("", exclusive: true) x = ch.default_exchange as = "кириллца, йо" * (1024 * 1024) x.publish(as, routing_key: q.name, persistent: true) sleep(1) expect(q.message_count).to eq 1 _, _, payload = q.pop expect(payload.bytesize).to eq as.bytesize ch.close end end context "with empty message body" do it "successfully publishes the message" do ch = connection.create_channel q = ch.queue("", exclusive: true) x = ch.default_exchange x.publish("", routing_key: q.name, persistent: false, mandatory: true) sleep(0.5) expect(q.message_count).to eq 1 envelope, headers, payload = q.pop expect(payload).to eq "" expect(headers[:content_type]).to eq "application/octet-stream" expect(headers[:delivery_mode]).to eq 1 expect(headers[:priority]).to eq 0 ch.close end end end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/queue_bind_spec.rb000066400000000000000000000046771464043542000274460ustar00rootroot00000000000000require "spec_helper" describe "A client-named", Bunny::Queue do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end it "can be bound to a pre-declared exchange" do ch = connection.create_channel q = ch.queue("bunny.tests.queues.client-named#{rand}", exclusive: true) expect(q).not_to be_server_named expect(q.bind("amq.fanout")).to eq q ch.close end it "can be unbound from a pre-declared exchange" do ch = connection.create_channel q = ch.queue("bunny.tests.queues.client-named#{rand}", exclusive: true) expect(q).not_to be_server_named q.bind("amq.fanout") expect(q.unbind("amq.fanout")).to eq q ch.close end it "can be bound to a custom exchange" do ch = connection.create_channel q = ch.queue("bunny.tests.queues.client-named#{rand}", exclusive: true) x = ch.fanout("bunny.tests.exchanges.fanout#{rand}") expect(q.bind(x)).to eq q x.delete ch.close end it "can be unbound from a custom exchange" do ch = connection.create_channel q = ch.queue("bunny.tests.queues.client-named#{rand}", exclusive: true) expect(q).not_to be_server_named x = ch.fanout("bunny.tests.fanout", auto_delete: true, durable: false) q.bind(x) expect(q.unbind(x)).to eq q ch.close end end describe "A server-named", Bunny::Queue do let(:connection) do c = Bunny.new c.start c end it "can be bound to a pre-declared exchange" do ch = connection.create_channel q = ch.queue("", exclusive: true) expect(q).to be_server_named expect(q.bind("amq.fanout")).to eq q ch.close end it "can be unbound from a pre-declared exchange" do ch = connection.create_channel q = ch.queue("", exclusive: true) expect(q).to be_server_named q.bind("amq.fanout") expect(q.unbind("amq.fanout")).to eq q ch.close end it "can be bound to a custom exchange" do ch = connection.create_channel q = ch.queue("", exclusive: true) x = ch.fanout("bunny.tests.exchanges.fanout#{rand}") expect(q.bind(x)).to eq q x.delete ch.close end it "can be bound from a custom exchange" do ch = connection.create_channel q = ch.queue("", exclusive: true) name = "bunny.tests.exchanges.fanout#{rand}" x = ch.fanout(name) q.bind(x) expect(q.unbind(name)).to eq q x.delete ch.close end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/queue_declare_spec.rb000066400000000000000000000260411464043542000301160ustar00rootroot00000000000000require "spec_helper" describe Bunny::Queue do let(:connection) do c = Bunny.new(user: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end context "when queue name is specified" do let(:name) { "a queue declared at #{Bunny::Timestamp.now.to_i}" } it "declares a new queue with that name" do ch = connection.create_channel q = ch.queue(name) expect(q.name).to eq name q.delete ch.close end it "caches that queue" do ch = connection.create_channel q = ch.queue(name) expect(ch.queue(name).object_id).to eq q.object_id q.delete ch.close end end context "when queue name is passed as an empty string" do it "uses server-assigned queue name" do ch = connection.create_channel q = ch.queue("") expect(q.name).not_to be_empty expect(q.name).to match /^amq.gen.+/ expect(q).to be_server_named q.delete ch.close end end context "when a nil is passed for queue name" do it "throws an error" do ch = connection.create_channel expect { ch.queue(nil, durable: true, auto_delete: false) }.to raise_error(ArgumentError) end end context "when queue is declared as durable" do it "declares it as durable" do ch = connection.create_channel q = ch.queue("bunny.tests.queues.durable", durable: true) expect(q).to be_durable expect(q).not_to be_auto_delete expect(q).not_to be_exclusive expect(q.arguments).to be_nil q.delete ch.close end end context "when queue is declared as exclusive" do it "declares it as exclusive" do ch = connection.create_channel q = ch.queue("bunny.tests.queues.exclusive", exclusive: true) expect(q).to be_exclusive expect(q).not_to be_durable q.delete ch.close end end context "when queue is declared as auto-delete" do it "declares it as auto-delete" do ch = connection.create_channel q = ch.queue("bunny.tests.queues.auto-delete", auto_delete: true) expect(q).to be_auto_delete expect(q).not_to be_exclusive expect(q).not_to be_durable q.delete ch.close end end context "when queue is declared with optional arguments" do it "declares it with those arguments" do ch = connection.create_channel args = { Bunny::Queue::XArgs::MAX_LENGTH => 1000 } q = ch.queue("bunny.tests.queues.x-args.1", durable: true, arguments: args) expect(q).not_to be_auto_delete expect(q).not_to be_exclusive expect(q).to be_durable expect(q.arguments).to eq(args) q.delete ch.close end end context "when queue is declared with type using x-args and a literal string" do it "declares a queue of that type" do ch = connection.create_channel args = { Bunny::Queue::XArgs::QUEUE_TYPE => "quorum" } q = ch.queue("bunny.tests.queues.x-args.2.qq", durable: true, arguments: args) expect(q).not_to be_auto_delete expect(q).not_to be_exclusive expect(q).to be_durable expect(q.arguments).to eq(args) q.delete ch.close end end context "when queue is declared with type using x-args and a constant" do it "declares a queue of that type" do ch = connection.create_channel args = { Bunny::Queue::XArgs::QUEUE_TYPE => Bunny::Queue::Types::QUORUM } q = ch.queue("bunny.tests.queues.x-args.2.qq", durable: true, arguments: args) expect(q).not_to be_auto_delete expect(q).not_to be_exclusive expect(q).to be_durable expect(q.arguments).to eq(args) q.delete ch.close end end context "when queue is declared with type using :type and a literal string" do it "declares a queue of that type" do ch = connection.create_channel args = { Bunny::Queue::XArgs::QUEUE_TYPE => "quorum" } q = ch.queue("bunny.tests.queues.x-args.3.qq", durable: true, type: Bunny::Queue::Types::QUORUM) expect(q).not_to be_auto_delete expect(q).not_to be_exclusive expect(q).to be_durable expect(q.arguments).to eq(args) q.delete ch.close end end context "when queue is declared with type using :type and a constant" do it "declares a queue of that type" do ch = connection.create_channel args = { Bunny::Queue::XArgs::QUEUE_TYPE => Bunny::Queue::Types::QUORUM } q = ch.queue("bunny.tests.queues.x-args.3.qq", durable: true, type: Bunny::Queue::Types::QUORUM) expect(q).not_to be_auto_delete expect(q).not_to be_exclusive expect(q).to be_durable expect(q.arguments).to eq(args) q.delete ch.close end end context "when a quorum queue is declared using a helper" do it "declares a quorum queue" do ch = connection.create_channel args = { Bunny::Queue::XArgs::MAX_LENGTH => 1000, Bunny::Queue::XArgs::QUEUE_TYPE => Bunny::Queue::Types::QUORUM } q = ch.quorum_queue("bunny.tests.queues.qq.1", durable: false, exclusive: true, arguments: { Bunny::Queue::XArgs::MAX_LENGTH => 1000 }) expect(q).not_to be_auto_delete expect(q).not_to be_exclusive expect(q).to be_durable expect(q.arguments).to eq(args) q.delete ch.close end end context "when a stream 'queue' is declared using a helper" do it "declares a stream" do ch = connection.create_channel args = { Bunny::Queue::XArgs::MAX_LENGTH => 1000, Bunny::Queue::XArgs::QUEUE_TYPE => Bunny::Queue::Types::STREAM } q = ch.stream("bunny.tests.queues.sq.1", durable: false, exclusive: true, arguments: { Bunny::Queue::XArgs::MAX_LENGTH => 1000 }) expect(q).not_to be_auto_delete expect(q).not_to be_exclusive expect(q).to be_durable expect(q.arguments).to eq(args) q.delete ch.close end end context "when queue is declared with an unsupported :type" do it "raises an exception" do ch = connection.create_channel expect { ch.queue("bunny.tests.queues.unsupported.type", durable: true, type: "super-duper-q") }.to raise_error(ArgumentError) ch.close end end context "when queue is declared with a mismatching auto-delete property value" do it "raises an exception" do ch = connection.create_channel q = ch.queue("bunny.tests.queues.auto-delete", auto_delete: true, durable: false) expect { # force re-declaration ch.queue_declare(q.name, auto_delete: false, durable: false) }.to raise_error(Bunny::PreconditionFailed) expect(ch).to be_closed cleanup_ch = connection.create_channel cleanup_ch.queue_delete(q.name) end end context "when queue is declared with a mismatching durable property value" do it "raises an exception" do ch = connection.create_channel q = ch.queue("bunny.tests.queues.durable", durable: true) expect { # force re-declaration ch.queue_declare(q.name, durable: false) }.to raise_error(Bunny::PreconditionFailed) expect(ch).to be_closed cleanup_ch = connection.create_channel cleanup_ch.queue_delete(q.name) end end context "when queue is declared with a mismatching exclusive property value" do it "raises an exception" do ch = connection.create_channel q = ch.queue("bunny.tests.queues.exclusive.#{rand}", exclusive: true) # when there's an exclusivity property mismatch, a different error # (405 RESOURCE_LOCKED) is used. This is a leaked queue exclusivity/ownership # implementation detail that's now basically a feature. MK. expect { # force re-declaration ch.queue_declare(q.name, exclusive: false) }.to raise_error(Bunny::ResourceLocked) expect(ch).to be_closed end end context "when queue is declared with a set of mismatching values" do it "raises an exception" do ch = connection.create_channel q = ch.queue("bunny.tests.queues.proprty-equivalence", auto_delete: true, durable: false) expect { # force re-declaration ch.queue_declare(q.name, auto_delete: false, durable: true) }.to raise_error(Bunny::PreconditionFailed) expect(ch).to be_closed cleanup_ch = connection.create_channel cleanup_ch.queue_delete(q.name) end end context "when queue is declared with message TTL" do let(:args) do # in ms {"x-message-ttl" => 1000} end it "causes all messages in it to have a TTL" do ch = connection.create_channel q = ch.queue("bunny.tests.queues.with-arguments.ttl", arguments: args, exclusive: true) expect(q.arguments).to eq args q.publish("xyzzy") sleep 0.1 expect(q.message_count).to eq 1 sleep 1.5 expect(q.message_count).to eq 0 ch.close end end context "when queue is declared with priorities" do let(:args) do {"x-max-priority" => 5} end it "enables priority implementation" do c = Bunny.new(user: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start ch = c.create_channel ch.confirm_select q = ch.queue("bunny.tests.queues.with-arguments.priority #{rand}", arguments: args, exclusive: true) expect(q.arguments).to eq args q.publish("xyzzy") ch.wait_for_confirms sleep 0.1 # this test only does sanity checking, # without trying to actually test prioritisation. # # added to guard against issues such as # https://github.com/rabbitmq/rabbitmq-server/issues/488 expect(q.message_count).to eq 1 ch.close end end describe "#queue_exists?" do context "when a queue exists" do it "returns true" do ch = connection.create_channel q = ch.queue("", exlusive: true) expect(connection.queue_exists?(q.name)).to eq true end end context "when a queue DOES NOT exist" do it "returns false" do expect(connection.queue_exists?("suf89u9a4jo3ndnakls##{Bunny::Timestamp.now.to_i}")).to eq false end end end unless ENV["CI"] # requires RabbitMQ 3.1+ context "when queue is declared with bounded length" do let(:n) { 10 } let(:args) do # in ms {"x-max-length" => n} end # see http://www.rabbitmq.com/maxlength.html for more info it "causes the queue to be bounded" do ch = connection.create_channel q = ch.queue("bunny.tests.queues.with-arguments.max-length", arguments: args, exclusive: true) expect(q.arguments).to eq args (n * 10).times do q.publish("xyzzy") end expect(q.message_count).to eq n (n * 5).times do q.publish("xyzzy") end expect(q.message_count).to eq n q.delete ch.close end end end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/queue_delete_spec.rb000066400000000000000000000016211464043542000277560ustar00rootroot00000000000000require "spec_helper" describe Bunny::Queue, "#delete" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close end context "with a name of an existing queue" do it "deletes that queue" do ch = connection.create_channel q = ch.queue("") q.delete # no exception as of RabbitMQ 3.2. MK. q.delete expect(ch.queues.size).to eq 0 end end context "with a name of an existing queue" do it "DOES NOT raise an exception" do ch = connection.create_channel # no exception as of RabbitMQ 3.2. MK. ch.queue_delete("sdkhflsdjflskdjflsd#{rand}") ch.queue_delete("sdkhflsdjflskdjflsd#{rand}") ch.queue_delete("sdkhflsdjflskdjflsd#{rand}") ch.queue_delete("sdkhflsdjflskdjflsd#{rand}") end end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/queue_purge_spec.rb000066400000000000000000000010231464043542000276320ustar00rootroot00000000000000require "spec_helper" describe Bunny::Queue do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close end it "can be purged" do ch = connection.create_channel q = ch.queue("", exclusive: true) x = ch.default_exchange x.publish("xyzzy", routing_key: q.name) sleep(0.5) expect(q.message_count).to eq 1 q.purge expect(q.message_count).to eq 0 ch.close end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/queue_unbind_spec.rb000066400000000000000000000020341464043542000277720ustar00rootroot00000000000000require "spec_helper" describe Bunny::Queue, "bound to an exchange" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close end it "can be unbound from an exchange it was bound to" do ch = connection.create_channel x = ch.fanout("amq.fanout") q = ch.queue("", exclusive: true).bind(x) x.publish("") sleep 0.3 expect(q.message_count).to eq 1 q.unbind(x) x.publish("") expect(q.message_count).to eq 1 end end describe Bunny::Queue, "NOT bound to an exchange" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close end it "is idempotent (succeeds)" do ch = connection.create_channel x = ch.fanout("amq.fanout") q = ch.queue("", exclusive: true) # No exception as of RabbitMQ 3.2. MK. q.unbind(x) q.unbind(x) end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/read_only_consumer_spec.rb000066400000000000000000000034621464043542000312040ustar00rootroot00000000000000require "spec_helper" describe Bunny::Queue, "#subscribe" do let(:publisher_connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end let(:consumer_connection) do c = Bunny.new(username: "bunny_reader", password: "reader_password", vhost: "bunny_testbed") c.start c end after :each do publisher_connection.close if publisher_connection.open? consumer_connection.close if consumer_connection.open? end context "with automatic acknowledgement mode" do let(:queue_name) { "bunny.basic_consume#{rand}" } it "registers the consumer" do delivered_keys = [] delivered_data = [] ch = publisher_connection.create_channel # declare the queue because the read-only user won't be able to issue # queue.declare q = ch.queue(queue_name, auto_delete: true, durable: false) t = Thread.new do # give the main thread a bit of time to declare the queue sleep 0.5 ch = consumer_connection.create_channel # this connection is read only, use passive declare to only get # a reference to the queue q = ch.queue(queue_name, auto_delete: true, durable: false, passive: true) q.subscribe(exclusive: false) do |delivery_info, properties, payload| delivered_keys << delivery_info.routing_key delivered_data << payload end end t.abort_on_exception = true sleep 0.5 x = ch.default_exchange x.publish("hello", routing_key: queue_name) sleep 0.7 expect(delivered_keys).to include(queue_name) expect(delivered_data).to include("hello") expect(ch.queue(queue_name, auto_delete: true, durable: false).message_count).to eq 0 ch.close end end end # describe ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/sender_selected_distribution_spec.rb000066400000000000000000000020211464043542000332320ustar00rootroot00000000000000require "spec_helper" describe "Sender-selected distribution" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end it "lets publishers specify additional routing keys using CC and BCC headers" do ch = connection.create_channel x = ch.direct("bunny.tests.ssd.exchange") q1 = ch.queue("", exclusive: true).bind(x, routing_key: "one") q2 = ch.queue("", exclusive: true).bind(x, routing_key: "two") q3 = ch.queue("", exclusive: true).bind(x, routing_key: "three") q4 = ch.queue("", exclusive: true).bind(x, routing_key: "four") n = 10 n.times do |i| x.publish("Message #{i}", routing_key: "one", headers: {"CC" => ["two", "three"]}) end sleep 0.5 expect(q1.message_count).to eq n expect(q2.message_count).to eq n expect(q3.message_count).to eq n expect(q4.message_count).to be_zero x.delete end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/tls_connection_spec.rb000066400000000000000000000153621464043542000303400ustar00rootroot00000000000000# -*- coding: utf-8 -*- require "spec_helper" require "socket" CERTIFICATE_DIR = ENV.fetch("BUNNY_CERTIFICATE_DIR", "./spec/tls") puts "Will use certificates from #{CERTIFICATE_DIR}" shared_examples_for "successful TLS connection" do it "succeeds", skip: ENV["CI"] do expect(subject).to be_tls ch = subject.create_channel ch.confirm_select q = ch.queue("", exclusive: true) x = ch.default_exchange x.publish("xyzzy", routing_key: q.name). publish("xyzzy", routing_key: q.name). publish("xyzzy", routing_key: q.name). publish("xyzzy", routing_key: q.name) x.wait_for_confirms expect(q.message_count).to eq 4 i = 0 q.subscribe do |delivery_info, _, payload| i += 1 end sleep 1.0 expect(i).to eq 4 expect(q.message_count).to eq 0 ch.close end end def local_hostname ENV.fetch("BUNNY_RABBITMQ_HOSTNAME", "localhost") end context "initialized with tls: true", skip: ENV["CI"] do let(:subject) do Bunny.new( hostname: local_hostname(), user: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", tls: true, verify_peer: verify_peer, tls_cert: "#{CERTIFICATE_DIR}/client_certificate.pem", tls_key: "#{CERTIFICATE_DIR}/client_key.pem", tls_ca_certificates: ["#{CERTIFICATE_DIR}/ca_certificate.pem"]) end context "peer verification is off" do let(:verify_peer) { false } it "uses TLS port" do expect(subject.port).to eq AMQ::Protocol::TLS_PORT end it "sends the SNI details" do # https://github.com/ruby-amqp/bunny/issues/440 subject.start expect(subject.transport.socket.hostname).to_not be_empty end after :each do subject.close end end context "peer verification is on" do let(:verify_peer) { true } it "uses TLS port" do expect(subject.port).to eq AMQ::Protocol::TLS_PORT end end end describe "TLS connection to RabbitMQ with client certificates", skip: ENV["CI"] do let(:subject) do c = Bunny.new( hostname: local_hostname(), username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", tls: true, tls_protocol: :TLSv1_2, tls_cert: "#{CERTIFICATE_DIR}/client_certificate.pem", tls_key: "#{CERTIFICATE_DIR}/client_key.pem", tls_ca_certificates: ["#{CERTIFICATE_DIR}/ca_certificate.pem"], verify_peer: false) c.start c end after :each do subject.close end include_examples "successful TLS connection" end describe "TLS connection to RabbitMQ without client certificates", skip: ENV["CI"] do let(:subject) do c = Bunny.new( hostname: local_hostname(), username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", tls: true, tls_protocol: :TLSv1_2, tls_ca_certificates: ["#{CERTIFICATE_DIR}/ca_certificate.pem"], verify_peer: false) c.start c end after :each do subject.close end include_examples "successful TLS connection" end describe "TLS connection to RabbitMQ with a connection string", skip: ENV["CI"] do let(:subject) do c = Bunny.new("amqps://bunny_gem:bunny_password@#{local_hostname()}/bunny_testbed", tls_protocol: :TLSv1_2, tls_cert: "#{CERTIFICATE_DIR}/client_certificate.pem", tls_key: "#{CERTIFICATE_DIR}/client_key.pem", tls_ca_certificates: ["#{CERTIFICATE_DIR}/ca_certificate.pem"], verify_peer: false) c.start c end after :each do subject.close end include_examples "successful TLS connection" context "when URI contains query parameters" do subject(:session) do Bunny.new("amqps://bunny_gem:bunny_password@#{local_hostname()}/bunny_testbed?heartbeat=10&connection_timeout=100&channel_max=1000&verify=false&cacertfile=#{CERTIFICATE_DIR}/ca_certificate.pem&certfile=#{CERTIFICATE_DIR}/client_certificate.pem&keyfile=#{CERTIFICATE_DIR}/client_key.pem") end it "parses extra connection parameters" do session.start expect(session.uses_tls?).to eq(true) expect(session.transport.verify_peer).to eq(false) expect(session.transport.tls_ca_certificates).to eq(["#{CERTIFICATE_DIR}/ca_certificate.pem"]) expect(session.transport.tls_certificate_path).to eq("#{CERTIFICATE_DIR}/client_certificate.pem") expect(session.transport.tls_key_path).to eq("#{CERTIFICATE_DIR}/client_key.pem") end end end describe "TLS connection to RabbitMQ with a connection string and w/o client certificate and key", skip: ENV["CI"] do let(:subject) do c = Bunny.new("amqps://bunny_gem:bunny_password@#{local_hostname()}/bunny_testbed", tls_ca_certificates: ["#{CERTIFICATE_DIR}/ca_certificate.pem"], tls_protocol: :TLSv1_2, verify_peer: verify_peer) c.start c end after :each do subject.close end context "peer verification is off" do let(:verify_peer) { false } include_examples "successful TLS connection" it "sends the SNI details" do # https://github.com/ruby-amqp/bunny/issues/440 expect(subject.transport.socket.hostname).to_not be_empty end end context "peer verification is on" do let(:verify_peer) { true } include_examples "successful TLS connection" it "sends the SNI details" do # https://github.com/ruby-amqp/bunny/issues/440 expect(subject.transport.socket.hostname).to_not be_empty end end end describe "TLS connection to RabbitMQ w/o client certificate", skip: ENV["CI"] do let(:subject) do c = Bunny.new("amqps://bunny_gem:bunny_password@#{local_hostname()}/bunny_testbed", tls_ca_certificates: ["#{CERTIFICATE_DIR}/ca_certificate.pem"], tls_protocol: :TLSv1_2, verify_peer: false, tls_silence_warnings: should_silence_warnings) c.start c end after :each do subject.close end context "TLS-related warnings are enabled" do let(:should_silence_warnings) { false } include_examples "successful TLS connection" end context "TLS-related warnings are silenced" do let(:should_silence_warnings) { true } include_examples "successful TLS connection" end end describe "TLS connection to RabbitMQ with client certificates provided inline", skip: ENV["CI"] do let(:subject) do c = Bunny.new( hostname: local_hostname(), username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", tls: true, tls_cert: File.read("#{CERTIFICATE_DIR}/client_certificate.pem"), tls_key: File.read("#{CERTIFICATE_DIR}/client_key.pem"), tls_ca_certificates: ["#{CERTIFICATE_DIR}/ca_certificate.pem"], tls_protocol: :TLSv1_2, verify_peer: false) c.start c end after :each do subject.close end include_examples "successful TLS connection" end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/toxiproxy_spec.rb000066400000000000000000000047741464043542000274110ustar00rootroot00000000000000require "spec_helper" require_relative "../../toxiproxy_helper" if ::Toxiproxy.running? describe Bunny::Channel, "#basic_publish" do include RabbitMQ::Toxiproxy after :each do @connection.close if @connection.open? end context "when the the connection detects missed heartbeats with automatic recovery" do before(:each) do setup_toxiproxy @connection = Bunny.new(user: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", host: "localhost:11111", heartbeat_timeout: 1, automatically_recover: true) @connection.start end let(:queue_name) { "bunny.basic.publish.queue#{rand}" } it "raises a ConnectionClosedError" do ch = @connection.create_channel rabbitmq_toxiproxy.down do sleep 2 expect { ch.default_exchange.publish("", :routing_key => queue_name) }.to raise_error(Bunny::ConnectionClosedError) end end end context "when the the connection detects missed heartbeats without automatic recovery" do before(:each) do setup_toxiproxy @connection = Bunny.new(user: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", host: "localhost:11111", heartbeat_timeout: 1, automatically_recover: false, threaded: false) @connection.start end it "does not raise an exception on session thread" do rabbitmq_toxiproxy.down do sleep 5 end end end context "recovery attempt limit that's exceeded" do let(:dummy) { double(call: true) } before(:each) do setup_toxiproxy @connection = Bunny.new(user: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", host: "localhost:11111", heartbeat_timeout: 1, automatically_recover: true, network_recovery_interval: 1, recovery_attempts: 2, recovery_attempts_exhausted: Proc.new { dummy.call }, reset_recovery_attempts_after_reconnection: true, disconnect_timeout: 1) @connection.start end it "permanently closes connection" do expect(@connection.open?).to be(true) rabbitmq_toxiproxy.down do sleep 5 end # give the connection one last chance to recover sleep 3 expect(@connection.open?).to be(false) expect(@connection.closed?).to be(true) expect(dummy).to have_received(:call).once end end # context end # describe else puts "Toxiproxy isn't running, some examples will be skipped" end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/tx_commit_spec.rb000066400000000000000000000006011464043542000273100ustar00rootroot00000000000000require "spec_helper" describe Bunny::Channel, "#tx_commit" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end it "is supported" do ch = connection.create_channel ch.tx_select ch.tx_commit ch.close end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/tx_rollback_spec.rb000066400000000000000000000006051464043542000276150ustar00rootroot00000000000000require "spec_helper" describe Bunny::Channel, "#tx_rollback" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end it "is supported" do ch = connection.create_channel ch.tx_select ch.tx_rollback ch.close end end ruby-amqp-bunny-b6569cd/spec/higher_level_api/integration/with_channel_spec.rb000066400000000000000000000007721464043542000277610ustar00rootroot00000000000000require "spec_helper" describe Bunny::Channel, "#with_channel" do let(:connection) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed") c.start c end after :each do connection.close if connection.open? end it "closes if the block throws an exception" do ch = nil begin connection.with_channel do |wch| ch = wch raise Exception.new end rescue Exception end expect(ch).to be_closed end end ruby-amqp-bunny-b6569cd/spec/issues/000077500000000000000000000000001464043542000174535ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/spec/issues/issue100_spec.rb000066400000000000000000000017461464043542000223730ustar00rootroot00000000000000require "spec_helper" unless ENV["CI"] describe Bunny::Channel, "#basic_publish" do before :all do @connection = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", write_timeout: 0, read_timeout: 0) @connection.start end after :all do @connection.close if @connection.open? end context "when publishing thousands of messages" do let(:n) { 2_000 } let(:m) { 10 } it "successfully publishers them all" do ch = @connection.create_channel q = ch.queue("", exclusive: true) x = ch.default_exchange body = "x" * 1024 m.times do |i| n.times do x.publish(body, routing_key: q.name) end puts "Published #{i * n} 1K messages..." end q.purge ch.close end end end end ruby-amqp-bunny-b6569cd/spec/issues/issue141_spec.rb000066400000000000000000000017011464043542000223670ustar00rootroot00000000000000require "spec_helper" describe "Registering 2nd exclusive consumer on queue" do before :all do @connection = Bunny.new(:user => "bunny_gem", password: "bunny_password", :vhost => "bunny_testbed") @connection.start end after :each do @connection.close if @connection.open? end it "raises a meaningful exception" do xs = [] ch1 = @connection.create_channel ch2 = @connection.create_channel q1 = ch1.queue("", :auto_delete => true) q2 = ch2.queue(q1.name, :auto_delete => true, :passive => true) c1 = q1.subscribe(exclusive: true) do |_, _, payload| xs << payload end sleep 0.1 expect do q2.subscribe(exclusive: true) do |_, _, _| end end.to raise_error(Bunny::AccessRefused) expect(ch1).to be_open expect(ch2).to be_closed q1.publish("abc") sleep 0.1 # verify that the first consumer is fine expect(xs).to eq ["abc"] q1.delete end end ruby-amqp-bunny-b6569cd/spec/issues/issue202_spec.rb000066400000000000000000000005541464043542000223720ustar00rootroot00000000000000require "spec_helper" describe Bunny::Session do context "with unreachable host" do it "raises Bunny::TCPConnectionFailed" do begin conn = Bunny.new(:hostname => "127.0.0.254", :port => 1433) conn.start fail "expected 192.192.192.192 to be unreachable" rescue Bunny::TCPConnectionFailed => e end end end end ruby-amqp-bunny-b6569cd/spec/issues/issue224_spec.rb000066400000000000000000000016151464043542000223750ustar00rootroot00000000000000# -*- coding: utf-8 -*- require "spec_helper" unless ENV["CI"] describe "Message framing implementation" do let(:connection) do c = Bunny.new(:user => "bunny_gem", password: "bunny_password", :vhost => "bunny_testbed", :port => ENV.fetch("RABBITMQ_PORT", 5672)) c.start c end after :each do connection.close if connection.open? end context "with payload 272179 bytes in size" do it "successfully frames the message" do ch = connection.create_channel q = ch.queue("", exclusive: true) x = ch.default_exchange as = ("a" * 272179) x.publish(as, routing_key: q.name, persistent: true) sleep(1) expect(q.message_count).to eq 1 _, _, payload = q.pop expect(payload.bytesize).to eq as.bytesize ch.close end end end end ruby-amqp-bunny-b6569cd/spec/issues/issue465_spec.rb000066400000000000000000000012441464043542000224020ustar00rootroot00000000000000# -*- coding: utf-8 -*- require 'spec_helper' describe Bunny::Session do let(:connection) do c = Bunny.new( user: 'bunny_gem', password: 'bunny_password', vhost: 'bunny_testbed', port: ENV.fetch('RABBITMQ_PORT', 5672) ) c.start c end context 'after the connection has been manually closed' do before :each do connection.close end after :each do connection.close if connection.open? end describe '#create_channel' do it 'should raise an exception' do expect { connection.create_channel }.to raise_error(Bunny::ConnectionAlreadyClosed) end end end end ruby-amqp-bunny-b6569cd/spec/issues/issue549_spec.rb000066400000000000000000000014011464043542000224000ustar00rootroot00000000000000require 'spec_helper' describe Bunny::Session do context 'when retry attempts have been exhausted' do let(:io) { StringIO.new } # keep test output clear def create_session described_class.new( host: 'fake.host', recovery_attempts: 0, connection_timeout: 0, network_recovery_interval: 0, logfile: io, ) end it 'closes the session' do session = create_session session.handle_network_failure(StandardError.new) expect(session.closed?).to be true end it 'stops the reader loop' do session = create_session reader_loop = session.reader_loop session.handle_network_failure(StandardError.new) expect(reader_loop.stopping?).to be true end end end ruby-amqp-bunny-b6569cd/spec/issues/issue609_spec.rb000066400000000000000000000066751464043542000224170ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' describe Bunny::Session, 'TLS certificate store' do subject { described_class.new tls: true, tls_ca_certificates: [certificate], logger: logger } let(:logger) { Logger.new(io).tap { |l| l.level = :debug } } let(:io) { StringIO.new } context 'when a Windows path given' do let(:certificate) { 'C:/some/path/cacert.pem' } # skipping actual work with file by Windows path as this spec runs in Linux before do allow(File).to receive(:readable?).with(certificate).and_return(true) allow_any_instance_of(OpenSSL::X509::Store).to receive(:add_file) end it 'uses the certificate by path' do subject expect(log_content).to include('Using CA certificates at C:/some/path/cacert.pem') end end context 'when a POSIX path given' do let(:certificate) { './spec/tls/ca_certificate.pem' } it 'uses the certificate by path' do subject expect(log_content).to include('Using CA certificates at ./spec/tls/ca_certificate.pem') end end context 'when an inline certificate given' do let(:certificate) do <<~CERT -----BEGIN CERTIFICATE----- MIIFUDCCAzigAwIBAgIUSnyXq9nGYlkEvmGK0D/vfDW+B0QwDQYJKoZIhvcNAQEL BQAwMTEgMB4GA1UEAwwXVExTR2VuU2VsZlNpZ25lZHRSb290Q0ExDTALBgNVBAcM BCQkJCQwHhcNMjEwNDEyMTcwNzIxWhcNMzEwNDEwMTcwNzIxWjAxMSAwHgYDVQQD DBdUTFNHZW5TZWxmU2lnbmVkdFJvb3RDQTENMAsGA1UEBwwEJCQkJDCCAiIwDQYJ KoZIhvcNAQEBBQADggIPADCCAgoCggIBAKgPkKUIOi3RJZn0j/TkknErop/N1ylp qb4A6O9yMjRt7gSui+mouEe6SqcQubhm6n3cQzr0LxmtPO96ShHDUh8SJwzN419Q HS4x5IyCm4GMWvj4XPWb0LDVPdgbF9JdDSsv+zJPJ5oCh3wxbGrbzWIdClHE3ERm Fx59P4hgT5aa21LuB/Is/U5ybkUdrmYLP6ZiwYhH6A2mdL8VdqI53/tChrimNDcH I7dy9gqU75uDVF5DkchELFLPBTOYHS7OPS7sWHw4prQ6X+fZ/YG8Sql0+FAPf3Ro h2RSIiaGCLiuKpBjF3zK20ZvV+m6p+sYX3YyY3R9PDTctHYl6IW0iXpXjbStY//+ bBK56B74+OgRlWRqQDs/xfTDzm3GXRy6N/Z19ghoQYFST1FrHH04lBhoknNiSmRC Qf0AuwllLs9p1BLk/yEyoeAjMLA9ZDw0UjAvEaRgIwCFUJ3n2NZ/q+d2bTA00W4z 2pw6Hju//kkwWKBpAQBnPWRUhi4R3XDHKTa4lwkTjRzwfiyOM7y0JiPTj96WCKDo xIGEXbWwqZi/z7JTsXaxwxTnwC3ySStSz8SwgE4VjyK9DWeuT/4B6RWy+1sPLhOx ZXrCdUAd43ZGZp2SOZQrjPG89G3eUtfWQh2gigOabxEJoJmSDQW7LPXDQ64wElE5 I5vR6XcclCM7AgMBAAGjYDBeMAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUUdmmGXiv pidfAl85xXxPAFTDnUowHwYDVR0jBBgwFoAUUdmmGXivpidfAl85xXxPAFTDnUow DwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAobJS8Ej4ZUpqtjWs OVWGNXja0HdwuKP/IHmBWkcwrjJmMk5R8kGd+REIvCPz4ngofGMgYXLtj1omNkfh FDmUQWP0GRWZFgKCp5kNX+uaQk/KULEYc5W/+vYrpFPwOQi1uruRSyRKMddw3BJa I799hskkD8UFEfgHaAhdr9aZikYjCUYX0MIYHWef4e/H/ty/DYnKoGmUmVJEp45g JLXOUAQCD1EeZhUqkZddVckCR5oZQIfaJZbXRNKhQKcg9yllvDT2xY9tAty8D+v4 U/uoVcCbsXvE8BpEUYHuDYvLGutPYPLqTSGVxxTa4P/x/gEd1XlCtEoHThrv2YpF O5gME43xtBbwsEvBKWEaGl4hNLjlsTelM7uZsea58aIbs2nhUJQwcBUcugMa/Bxe KNgCiJ8M6ESa4FDV75Oe1LFZcC92Ie8zq5JTfvJJdEDqYdgAe05CH53USdRYKFRI QejzCS50kRx/wZgrokAXSSyuhXcEDoHqJ46Ufp5hnEVZCytCLRC58adPeBpcrLkN b4ZRbtyrZHDFkU/M49OxXUYBVaXztzK/EfkSXP4WHVNLlcb6U/fmlssfTaXu6ovg IOKPOq09C3id77JsXRwEb7hkkXpTp5i63bmVvCKRDKtHMUfxfnRiZkuu79fB4y8v eYEXqywYlmGZite4N3qb3qQnyGw= -----END CERTIFICATE----- CERT end it 'uses the inline certificate' do subject expect(log_content).to include('Using 1 inline CA certificates') end end def log_content io.tap(&:rewind).read end end ruby-amqp-bunny-b6569cd/spec/issues/issue656_spec.rb000066400000000000000000000032411464043542000224030ustar00rootroot00000000000000require 'spec_helper' describe Bunny::Session do context 'when created' do let(:io) { StringIO.new } # keep test output clear def create_session described_class.new( host: 'fake.host', recovery_attempts: 0, connection_timeout: 0, network_recovery_interval: 0, logfile: io, ) end it 'has default exceptions' do session = create_session expect(session.recoverable_exceptions).to eq(Bunny::Session::DEFAULT_RECOVERABLE_EXCEPTIONS) end it 'can override exceptions' do session = create_session session.recoverable_exceptions = [StandardError] expect(session.recoverable_exceptions).to eq([StandardError]) end it 'can append exceptions' do session = create_session session.recoverable_exceptions << StandardError expect(session.recoverable_exceptions).to eq(Bunny::Session::DEFAULT_RECOVERABLE_EXCEPTIONS.append(StandardError)) end end context 'when retry attempts have been exhausted' do let(:io) { StringIO.new } # keep test output clear def create_session described_class.new( host: 'fake.host', recovery_attempts: 0, connection_timeout: 0, network_recovery_interval: 0, logfile: io, ) end it 'closes the session' do session = create_session session.handle_network_failure(StandardError.new) expect(session.closed?).to be true end it 'stops the reader loop' do session = create_session reader_loop = session.reader_loop session.handle_network_failure(StandardError.new) expect(reader_loop.stopping?).to be true end end end ruby-amqp-bunny-b6569cd/spec/issues/issue78_spec.rb000066400000000000000000000034411464043542000223230ustar00rootroot00000000000000require "spec_helper" unless ENV["CI"] describe Bunny::Queue, "#subscribe" do before :all do @connection1 = Bunny.new(:user => "bunny_gem", password: "bunny_password", :vhost => "bunny_testbed") @connection1.start @connection2 = Bunny.new(:user => "bunny_gem", password: "bunny_password", :vhost => "bunny_testbed") @connection2.start end after :all do [@connection1, @connection2].select { |c| !!c }.each do |c| c.close if c.open? end end context "with an empty queue" do it "consumes messages" do delivered_data = [] ch1 = @connection1.create_channel ch2 = @connection1.create_channel q = ch1.queue("", exclusive: true) q.subscribe(manual_ack: false) do |delivery_info, properties, payload| delivered_data << payload end sleep 0.5 x = ch2.default_exchange x.publish("abc", routing_key: q.name) sleep 0.7 expect(delivered_data).to eq ["abc"] ch1.close ch2.close end end context "with a non-empty queue" do let(:queue_name) { "queue#{rand}" } it "consumes messages" do delivered_data = [] ch1 = @connection1.create_channel ch2 = @connection1.create_channel q = ch1.queue(queue_name, exclusive: true) x = ch2.default_exchange 3.times do |i| x.publish("data#{i}", routing_key: queue_name) end sleep 0.7 expect(q.message_count).to eq 3 q.subscribe(manual_ack: false) do |delivery_info, properties, payload| delivered_data << payload end sleep 0.7 expect(delivered_data).to eq ["data0", "data1", "data2"] ch1.close ch2.close end end end end ruby-amqp-bunny-b6569cd/spec/issues/issue83_spec.rb000066400000000000000000000011501464043542000223120ustar00rootroot00000000000000require "spec_helper" describe Bunny::Channel, "#open" do before :all do @connection = Bunny.new(:user => "bunny_gem", password: "bunny_password", :vhost => "bunny_testbed") @connection.start end after :all do @connection.close if @connection.open? end it "properly resets channel exception state" do ch = @connection.create_channel begin ch.queue("bunny.tests.does.not.exist", :passive => true) rescue Bunny::NotFound # expected end # reopen the channel ch.open # should not raise q = ch.queue("bunny.tests.my.queue") q.delete end end ruby-amqp-bunny-b6569cd/spec/issues/issue97_attachment.json000066400000000000000000007441261464043542000241040ustar00rootroot00000000000000[{"id":"353114740","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004489807740","authorlink":"FASky","date":"2013-01-06T13:40:17Z","topic":"Kia K9","topic_text":"Kia K9","text":"By gi l 20:35 ch cn vi tin na l bit kt w tri oy chc ti nay hok ng c w hjc c ai rnh hok 8 i cho mnh bt cng thng t hjx............cu mong mi chuyn s im p hjhjhj","language":"notdetect","sentiment":"Mixed","author":"FASky","author_text":"FASky","md5_text":"169378f23462ebff5ccc3d5e54c26af2","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757915848704},{"id":"353114054","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004385643326","authorlink":"VoianhElaNiemdau","date":"2013-01-06T13:59:36Z","topic":"Kia K9","topic_text":"Kia K9","text":"Buc eo triu dk lam ban 3 nam vi 1 kau ns di nha ngi vs a k ma ny no gen gio tn day.dung la og troi bat kong no treu ny minh thi dk minh treu thi...?tam biet van luong?tam biet ngoc kute?ka nguyen trang nua","language":"notdetect","sentiment":"Mixed","author":"VoianhElaNiemdau","author_text":"VoianhElaNiemdau","md5_text":"086ad717fbea5460656c3e50e07add08","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757917945856},{"id":"353114917","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004933687507","authorlink":"ÚaLệVìAi","date":"2013-01-06T13:33:07Z","topic":"Kia K9","topic_text":"Kia K9","text":"ak a ckac e pai dj thui di den mot noi nao no that xa va ko aj biet e la aj.e ckan cuoc song ntn lm rui bjo e ko the nao ckui dk nua rui e biet tc ak dank cko e va e kung vay e yeu ak nkiu lm .hay de cko mot ng nao do ckam lo cko ak mang den niem vui mva nu cuoi cko ak.e ngj ty cua e dank cko ak la lon rui nkug ckac con co mot ng iu ak nkiu hon bjo e ko bit pai lm tke nao ka e se ckopn cack am t5ham lang le ra dj de tham cau ckuc cko ak dk vui ve hank pkuc.dung buon vj e ak nke neu cu o alj day va mv ntn thi tha e cket dj kon hon e ko lm dk dieu do e ko du dung cam de nkin thay ng e iu o ben mot aj do ma ng do laj la....hay that hp ak nke","language":"notdetect","sentiment":"Mixed","author":"ÚaLệVìAi","author_text":"ÚaLệVìAi","md5_text":"7131beb265ff91d84c57e535b7991982","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757918994432},{"id":"353114665","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004120800210","authorlink":"HueNhu","date":"2013-01-06T13:46:17Z","topic":"Kia K9","topic_text":"Kia K9","text":"m lai dc nghi 3 ngay zui.ma lai chua co ke hoach j ca....ko pit lm j trog 3 ngay do nua...hay lai o nha ngu day..hjk","language":"notdetect","sentiment":"Mixed","author":"HueNhu","author_text":"HueNhu","md5_text":"b07b2568490c9a83716fee13ee2c2b89","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757921091584},{"id":"353114867","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004792712506","authorlink":"ToNyLinh","date":"2013-01-06T13:30:13Z","topic":"Kia K9","topic_text":"Kia K9","text":"m p khng phi khi ngi bn ng la, m l bn cnh ngi bn thng yu. m p khng phi khi bn mc mt lc hai, ba o, m l khi bn ng trc gi lnh, t pha sau n c ai khoc ln bn mt tm o. m p khng phi khi bn ni m qu, m l khi c ngi th thm vi bn: C lnh khng?. m p khng phi khi bn dng hai tay xut xoa, m l khi tay ai kia kh nm ly bn tay bn. m p khng phi khi bn i chic m len, m l khi u bn da vo mt b vai tin cy.----> Chc bn s m p bn cnh ngi bn yu thng!","language":"notdetect","sentiment":"Mixed","author":"ToNyLinh","author_text":"ToNyLinh","md5_text":"3cca6289c2e2203f2e1071d690e9d12d","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757922140160},{"id":"353114294","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=374815592610516","authorlink":"LặngthầmmộttìnhyêuX","date":"2013-01-06T13:49:01Z","topic":"Kia K9","topic_text":"Kia K9","text":"...Mt anh lc vo khu rng lc m khuya . Anh thy mt c gi\n- C em d thng , sao ng y 1 mnh vy ?\n- Em ang n li 1 k nim su sc ca em ni ny\n- K nim g vy ? K anh nghe vi.\n- Anh thy cnh cy ng kia khng?\n- Thy , ri sao na em?\n- Hi em treo c trn cnh cy =))\n\np/s: ng tim :))))\n\n--------------------------- like page Tng lai KHC hay CI ph thuc vo LI ca qu kh :\") cung oc nhng stt thuvi hn na ban nhe ------------------------------------------","language":"notdetect","sentiment":"Positive","author":"LặngthầmmộttìnhyêuX","author_text":"LặngthầmmộttìnhyêuX","md5_text":"d0c839685f789f5c9ba14d386ee61041","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757923188736},{"id":"353114462","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004526313931","authorlink":"TuyetNhuocLan","date":"2013-01-06T13:43:50Z","topic":"Kia K9","topic_text":"Kia K9","text":"Chan nh...co thi thi thi...a m...c n minh...?!","language":"notdetect","sentiment":"Mixed","author":"TuyetNhuocLan","author_text":"TuyetNhuocLan","md5_text":"bcde8c4e040000a27f0c7e5ee13a266e","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757924237312},{"id":"353114027","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100002813111176","authorlink":"BiMilano","date":"2013-01-06T13:54:58Z","topic":"Kia K9","topic_text":"Kia K9","text":"~* V ma ng n, c ni vi e rng, baby I miss U.. Mt mu hoa ri ri v ngy a khng th gi, Can u feel me??..\n~* ri thi gian tri qua, ting l ri theo bc chn ca e, Tm v ni yn bnh.. V ma xun n, c ni vi em::\n..::MU VNG CA L KIA KHNG TH XANH, TR LI!!~","language":"notdetect","sentiment":"Mixed","author":"BiMilano","author_text":"BiMilano","md5_text":"ab61caea56dbe8b8319fd15d204ea468","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757925285888},{"id":"353114333","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004992632504","authorlink":"TrangKhanh","date":"2013-01-06T13:48:43Z","topic":"Kia K9","topic_text":"Kia K9","text":"L Cay khe v mt ngi za i nay khng quay tr v\nnhng nc mt ngy vn c zi hoi v sao em li khc v sao em li nh\ndu bit zng anh khng cn yu em na\nnc mt cho anh l tha\nn n ci trn mi n chm mt cuc tnh mi","language":"notdetect","sentiment":"Mixed","author":"TrangKhanh","author_text":"TrangKhanh","md5_text":"91d023ed3567598f80c504f587722d40","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757926334464},{"id":"353114578","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=175703249174546","authorlink":"BLVAnhNgọc","date":"2013-01-06T13:44:14Z","topic":"Kia K9","topic_text":"Kia K9","text":"Chun b ln sng trc tip Milan-Siena trn Th thao TV khi va bit tin Inter thua Udinese n 0-3. Khng th tin ni...","language":"notdetect","sentiment":"Mixed","author":"BLVAnhNgọc","author_text":"BLVAnhNgọc","md5_text":"014132f84c5b7d48804642ebffb12153","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757927383040},{"id":"353114519","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003647311860","authorlink":"BomNổChậm","date":"2013-01-06T13:40:52Z","topic":"Kia K9","topic_text":"Kia K9","text":"ma bun,qun t khch cng thm nn......","language":"notdetect","sentiment":"Mixed","author":"BomNổChậm","author_text":"BomNổChậm","md5_text":"48ad1ca15cccb2b0cf6a3578139454d0","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757955694592},{"id":"353114525","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003761322735","authorlink":"KannaTran","date":"2013-01-06T13:44:30Z","topic":"Kia K9","topic_text":"Kia K9","text":"trn thin ng c 1 thin s ang nhn xung trn gian thy nhng cp tnh nhn ang iu nhau v bn nhau hp, cng c nhng cp au kh v ty. Bng Cha hin ra hi v thien s :\n- Con ang nhn g? V con nhn thy g?\n- Con nhn nhng ngi ang iu nhau di nhn gian. Con thy h c nim vui ln au kh. Ti sao tnh iu nam n li khin h nh dzy vy Cha? Ty l g? Sao iu nhau lm chi phi au kh?\nV thin s vn nhn xung th gian v hi Cha. Cha p:\n- Ty k th gii thik cho con hiu c, ch c bn thn t cm nhn v tm hiu con mi thu hiu. V v sao nh th con nn t tm hiu i.\nV thin s y vn im lng ng , Cha li ni:\n- Con sinh ra l 1 thin s, con nn dnh ty ca mnh cho ca th gian, ng v qu quan tam n 1 ch iu m nh mt nhng g mnh c.\nNi ri Cha bin mt li v thin s vn 1 mnh ng lng im nhn th gian. Khng bit qua bao lu v thin s y cui cng cng dang 4 ci cnh ln mu trng ca mnh ra v bay i. V thin s y bay i gp i thin s Mikaen, ti ni v thin s y qu xung ni:\n- Hi i thin s, ta mong ngi c th gip ta 1 vic, ta mun xung th gian, ta mun cm nhn ci m con ngi gi l tnh iu, ci m h sn sng hi sinh va bung b tt c, ci m h v n m vui bun ln ln.\n- Noah! Ngi ng qu kh, ngi l t vc thin thn (4 cnh), 1 thin thn y quyn nng, ng v t m nht thi m nh mt n. ng i theo con ng m t ph loi ngi lm. Ngi cng bit 1 khi ngi xung th gian ngi s kh m quay v ni ny, ngi cng chu rt nhiu au n khi t b thn phn mnh , ngi bit ch!\n- Ta bit, nhng ta vn mun th, ta y rt lu ri! qu lu, ta cng nhn thy rt nhiu th, nhng cui cng cng ch l nhn thy, ta mun bn thn mnh t cm nhn.\nB qua mi li khuyn ca i thin thn, Noah vn mun du tin nhn gian, lm 1 con ngi cm nhn tnh iu. Cui cng vi s chp nhn ca i thin thn, Noah chu kh hnh thp t gi ca thin ng, t b thn phn thin s, b i nhng chic cnh trng m bao ngi mong c mang 1 thn xc loi ngi yu ui. Noah tha mong c, anh n th gian vi thn phn 1 con ngi ngho hn v a gp v iu 1 c gi. Anh c nhng nim vui m trc kia ni thin ng anh khng c c, a iu c ta bng tt c, nhng 1 ngy kia:\n- Mnh chia tay i a!\n- Sao vy e, a lm g sai sao? \n- K, a rt tt, ch c u a ngho qu! a k th cung phng e nhng th e cn, e tm c 1 ngi tt hn a ri, ngi ta cho e c nhng th e cn m a k th cho...thi tm bit a e i y.\nc gi ra i k h nhn li, Noah thn th ng, tri ni gi v ma ko n....Noah au kh, a t b tt c i tm 1 tnh iu v cui cng l tm c 1 ty nh th....Thi gian tri qua, a vn khng t b hi vng v 1 ty p v ch thc, a vn i tm v i tm....a quen rt nhiu ngi con gi nhng cui cng cng ch 1 kt qu... au kh khin a suy sp hon ton, cui cng anh n 1 nh th v qu xung di chn Cha:\n- Con sai ri, con khng nn i tm, nhng con khng hi hn, con khng mong Cha tha th cho con, con ch mong Cha c th chc phc v ban bnh an cho nhng ai tht s iu nhau.\n- Ta sn sng b qua mi li lm ca con, no con chin ngoan ca ta, hy cng ta quay tr v nh no.\nNoah theo Cha quay v thin ng, nhng 4 chic cnh trng ngy no ca anh gi nhum 1 mu en bun b, i mt sng ngy no hay ngm nhn th gian gi mi mi khng cn m ra na, mi tc en huyn tung bay trong gi gi cng thay bng mu trng lnh lo...Noah quay v nhng mang theo y ni tht vng v con tim tan nt,....","language":"notdetect","sentiment":"Mixed","author":"KannaTran","author_text":"KannaTran","md5_text":"6dfe062f4db408218da17d3eccfa0fa1","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757956743168},{"id":"353114107","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003093224135","authorlink":"KhiconngayNgo","date":"2013-01-06T14:01:07Z","topic":"Kia K9","topic_text":"Kia K9","text":"Uj,then gan chet! sao da mat mjh co the day the ko bjt.t nay con xjn cha.............","language":"notdetect","sentiment":"Mixed","author":"KhiconngayNgo","author_text":"KhiconngayNgo","md5_text":"df28b7681a9d4108c33db2113040c838","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757958840320},{"id":"353114476","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100002726928574","authorlink":"NhocKute","date":"2013-01-06T13:41:09Z","topic":"Kia K9","topic_text":"Kia K9","text":"Nimamoto Shizuka bao gi ra Bc vy?\nV chng nh Meo Luoi b tr 1 bui ra HP cng ngy vs Nimamoto v t tp nh?\nLu lm oy chng ta hok c ng n m oy!!!\nThy c hok h G Simsimi, Bt Ti V Dng?????????\nKaraoke ch????????????????","language":"notdetect","sentiment":"Mixed","author":"NhocKute","author_text":"NhocKute","md5_text":"806f09fa5220f73e0af0c6cc4cceb7dd","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757959888896},{"id":"353114105","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003498456934","authorlink":"VângEmQuê","date":"2013-01-06T13:59:15Z","topic":"Kia K9","topic_text":"Kia K9","text":"Tui nh pa qua >.<","language":"notdetect","sentiment":"Mixed","author":"VângEmQuê","author_text":"VângEmQuê","md5_text":"b1b6eec00f399d161648f0d185a1e242","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757960937472},{"id":"353114347","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100000234857250","authorlink":"AndrewNguyễn","date":"2013-01-06T13:48:38Z","topic":"Kia K9","topic_text":"Kia K9","text":"v con tim vuii tr li ....","language":"notdetect","sentiment":"Mixed","author":"AndrewNguyễn","author_text":"AndrewNguyễn","md5_text":"902011d98d5d3db90eff05e133789e6a","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757961986048},{"id":"353114986","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003798982707","authorlink":"LoanLeThiThanh","date":"2013-01-06T13:27:33Z","topic":"Kia K9","topic_text":"Kia K9","text":"Taj sao trong tjeng vjet laj co 2 tu \"gia' nhu...\"","language":"unknown","sentiment":"Mixed","author":"LoanLeThiThanh","author_text":"LoanLeThiThanh","md5_text":"28d1d91e8fb8f18e6081cfb0b7f91f1d","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757971423232},{"id":"353114688","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100002419233214","authorlink":"LeThanhhuyen","date":"2013-01-06T13:40:43Z","topic":"Kia K9","topic_text":"Kia K9","text":"\"Nu ti khc......\n.......ai s l ngi lau nc mt cho ti?\nNu ti gc ng.....\n.......ai s l ngi ko ti ng dy?\nNu ti cn mt b vai.....\n... .......ai s sn sng cho ti mn b vai ?\nNu mt ngy ti bin mt... .\n.......c ai cn nh ti tng tn ti hay ko ???","language":"notdetect","sentiment":"Mixed","author":"LeThanhhuyen","author_text":"LeThanhhuyen","md5_text":"acd9459de2659c0b91187a425c2fadfa","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757972471808},{"id":"353114779","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003810898347","authorlink":"ChúTiểuLênSàn","date":"2013-01-06T13:34:20Z","topic":"Kia K9","topic_text":"Kia K9","text":"Cho anh xin em mt ln\nNgonh mt anh thi au 1 phn\nCho 1 ln nu ko 1 tnh yu than th dang gi nh mun ngn trang v\nAnh vit ln nh mun ngn th\nTrong anh 1 ni au o o nh sng m\nBy gi / em cn ni di anh lm g vy\nSau nhng g m anh nhn thy\nLi lm l tt c anh gy\nLi ni khi xa cng tri nh nh ng my\nXin em mt ln thi\nCho anh lm trn mi, hy ta ln vai anh th tho cho thi gian dn tri\nTa ln b vai, v ng trch anh hay em sai\nAnh phi lm sao khi anh vn thit tha mt cu cho anh lm li\nAnh bit l mt iu khng phi\nAnh u phi la` ngi tng tri\nNhng em lm n ngang tri\nAnh bit l anh sai, mt ln na hy cho anh lm li\nChia xa.\nV nhng g thong qua, thong qua trong bng gi\nDi tr - Mt mnh anh lng bc ma ng khng nh thng khng vn vng\nAnh i tm em mt m trong khi sng ..\n\nHook : Kendylee (X2)\n\nMa ri trong m c ri cho thm nh ai\ni chn trong m c l hi vng 1 sm mai\n1 ngy mi n em ci chu mn v\nAnh li c em nh xa.2 ta lm li cuc tnh di cn ma\n\nLil Kiz:\n\nKhc, ni yu nhau s mi khng ri nhng qua m nay thi mnh anh li ru ng trn mi\nMt mnh anh c l trong cn phng ti,anh lc li khng c bng ngi k bn,gi bn thm li lm anh thm bun thm\nQua m nay thi bu tri s thm hiu qunh,ma ng ny anh li gi lnh mnh anh\nEm,ngi con gi anh thng gi ch cn li trong tng tng\nEm i xa\nKhng cht suy ngh nh thng\nBn nhau anh ch mong nh bnh thng\nNhng tt cCh l tng tng m thi\nB mi anh thm 1 ln na khng ai xua i gi lnh,tnh yu bn nhau vi em sao n qu mng manh ?\nAnh\nKhc na,khc na , khc na,nhng em u c v cng anh\nNi na,ni na,ni na nhng cng ni cng hiu qunh\nMu anh v em, nc mt cng ri v em tt c cng ch v em .V hm nay anh au v em !\nQuen di em, quen di cuoc doi quen di tat ca quen di ngay mai, vinh biet em vinh biet cuoc doi vinh biet ngay mai\n\nHook : Kendylee (X2)\n\nMa ri trong m c ri cho thm nh ai\ni chn trong m c l hi vng 1 sm mai\n1 ngy mi n em ci chu mn v\nAnh li c em nh xa.2 ta lm li cuc tnh di cn ma\n\nYeah ! Out love .new life\ntun hon c ngy v m m ch hoi vn chng mong n ngy mai\n1 ngy c trng v en cuc sng kia quen c 1 ngi con gi\nLun lun bn cnh chm sc v nm tay vt trng gai\nMong lm tt c gip em khng mt mi ng n tng lai\nKh khn\nri 1 ngy kia\nc 1 ngi n mang em i tht xa\nc nhn\nni 1 li chya tay\nv anh bit hon cnh . vt cht hok mang li c hnh phc cho em\ntrong anh ch c 1 tnh yu ..1 ngi con gi gip anh c gng i ln\nanh bit mt em tri tim kia au bn b cng an i\n n nc ny th my bun chi man\nN la di .. oh no ..\nKhng fi u .. tao t thy tnh yu ny khng cn v tao bung cu","language":"notdetect","sentiment":"Mixed","author":"ChúTiểuLênSàn","author_text":"ChúTiểuLênSàn","md5_text":"09a7cc122868621c742c6cbb859643a8","dimension":["Extreme Positive (autos)"],"category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757973520384},{"id":"353113995","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=534945013187112","authorlink":"NgocTrinhTran","date":"2013-01-06T14:00:32Z","topic":"Kia K9","topic_text":"Kia K9","text":"Trong chuyn tnh cm, c bao ngi ngh rng mnh khn ln, chn chn ri c mong tnh yu ca mnh phi nh th ny, nh th ka... ngi mnh yu phi lm nhng vic ny, vic kia...V ri h qun mt mt iu rng: yu khng phi ch c cm xc, m cn phi c c ci u suy ngh. Chnh ci u kt ni vi tri tim ri h phi bit c iu quan trng nht gia hai ngi yu nhau chnh l s cm thng v khng nhng chp nhn m cn l yu c nhng khuyt im ca ngi yu. Bi ngi ta c to nn t chnh tt c nhng khuyt im v u im, ch khng phi ch c duy nht nhng khuyt im. S cm thng v chp nhn cng tnh cm chn thnh, v con mt hng v pha trc s gip h i c xa hn.Ko ph nhn l yu bng tt c tnh cm l sai,nhng i khi n li l m qung.^^","language":"notdetect","sentiment":"Mixed","author":"NgocTrinhTran","author_text":"NgocTrinhTran","md5_text":"18ae1fc4d04d55dba6cd745473781da0","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757974568960},{"id":"353114778","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004331557024","authorlink":"ThonWeYenBinh","date":"2013-01-06T13:37:15Z","topic":"Kia K9","topic_text":"Kia K9","text":"Vui cho nhug ai k co n y.mua dog lah nay m n y no doi dj ro thj met lam","language":"notdetect","sentiment":"Mixed","author":"ThonWeYenBinh","author_text":"ThonWeYenBinh","md5_text":"ae3100e77c7d9c34f3fb6ecafa36d2e9","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757987151872},{"id":"353114985","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003326481446","authorlink":"KhánhHuyền","date":"2013-01-06T13:32:59Z","topic":"Kia K9","topic_text":"Kia K9","text":"Hix.. Thay chi minh theo chong bo cuoc choi ma nghi sau nay minh se lay chong that muon... 30 tro len moi lay .. Lay nhu the thi ca nha ai cung mung roi nuoc mat chu ntn thay bun lam.. Nho bme va bme cung buon va cam giac mat con roi,n la con ngkhac roi..hix... Minh se doi bjo me minh bao \" lay chong di cho toi nho\".. luc day minh se lay...:D","language":"notdetect","sentiment":"Mixed","author":"KhánhHuyền","author_text":"KhánhHuyền","md5_text":"e03b5ea3b638a764e3ac8bb90701fc6a","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757989249024},{"id":"353114833","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003683228906","authorlink":"ThảoNgọc","date":"2013-01-06T13:38:15Z","topic":"Kia K9","topic_text":"Kia K9","text":"Ngi xp qun o chun b i hc qun s................ vic g phi mang nhiu nh? xung ngi ta pht cho 3 b + 1 m m. kaka","language":"notdetect","sentiment":"Mixed","author":"ThảoNgọc","author_text":"ThảoNgọc","md5_text":"bea629f9efab89d4fea40f0fa3c7b1f0","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757990297600},{"id":"353114434","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003933994307","authorlink":"CocoMy","date":"2013-01-06T13:48:10Z","topic":"Kia K9","topic_text":"Kia K9","text":"Phong Kieu mai dat kao voi ma Jury Doan di an bach tuoc nuong coi...kao them an ^^","language":"unknown","sentiment":"Mixed","author":"CocoMy","author_text":"CocoMy","md5_text":"c3609d20ce1c429451ac36ca0f16a085","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757991346176},{"id":"353114901","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004183323170","authorlink":"RùaChjp","date":"2013-01-06T13:28:08Z","topic":"Kia K9","topic_text":"Kia K9","text":"Lanh that day .ngay nao cug ve nha oy o nka 1mik .buon chet di dc .hjx","language":"unknown","sentiment":"Mixed","author":"RùaChjp","author_text":"RùaChjp","md5_text":"f8dd1c3aa03265983523af4b3cd872e8","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757992394752},{"id":"353114252","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004737960332","authorlink":"KhanhNguyen","date":"2013-01-06T13:49:21Z","topic":"Kia K9","topic_text":"Kia K9","text":"hoang sang,minh hai ak,mjh hop nhom ti di nak,hj","language":"unknown","sentiment":"Mixed","author":"KhanhNguyen","author_text":"KhanhNguyen","md5_text":"58fe9a4b5543dc1c3749bea1736ca214","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757993443328},{"id":"353114455","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004073981559","authorlink":"ThaoPhuong","date":"2013-01-06T13:47:30Z","topic":"Kia K9","topic_text":"Kia K9","text":"Qu kh din t ht cm gic by gi...c qu nhiu tin n,qu nhiu li ni kh nghe.lm cho ngi ta suy ngh v mnh cng phi suy ngh....ming i tht ng s...h lm nh vy c li ch g cho h kg ? Ti sau h kg cho mnh c yn,mnh u c lm g c li vi h ti sau kg bun tha cho mnh ch ! gn hai nm tri qua nhng m ti sau h vn kg bung tha cho mnh.mnh chng lm g sai c,mnh sng ng ngha,v u quan trng l mnh lm trn bn phn mt ngi v,mnh lun t tin v u ...! Nhng m kg hiu sau mnh c thy mt mi khi phi chng chi vi nhng tin n,nhng li ni kh nghe ny ch!!!!!!!!","language":"notdetect","sentiment":"Mixed","author":"ThaoPhuong","author_text":"ThaoPhuong","md5_text":"be5446c19eefabed07923287923c64ef","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757994491904},{"id":"353114479","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100001022547892","authorlink":"NguyễnThuHiền","date":"2013-01-06T13:47:12Z","topic":"Kia K9","topic_text":"Kia K9","text":"Ha Ni m lanh!\n\nLuc trc tng rt thich cai cam giac m lanh but c trum chn, suy nghi min man v nhng ngi minh yu thng va mim ci.... \n\nNhng ri bng ngay kia oc 1 truyn u o noi ai y rng: ban ngay co ma ret bao nhiu cung co th chp nhn c,nhng ban m xin ng ma hay ret, ti lm nhng ai ngu ngoai hin...\n\nThy long minh ri tom... Mong tri ng ma ret m! >:D<","language":"notdetect","sentiment":"Mixed","author":"NguyễnThuHiền","author_text":"NguyễnThuHiền","md5_text":"54b7078a8b05e6a4ad3c08b08dc5638e","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757995540480},{"id":"353114738","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003166805067","authorlink":"BéBaLê","date":"2013-01-06T13:40:17Z","topic":"Kia K9","topic_text":"Kia K9","text":"Co 1 iu cht chn la khng co cai gi la cht chn...","language":"notdetect","sentiment":"Mixed","author":"BéBaLê","author_text":"BéBaLê","md5_text":"6fdae4d5e30170f2d4bf76abb7fff88e","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757996589056},{"id":"353114915","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003752180005","authorlink":"NhímTâmTrạng","date":"2013-01-06T13:33:33Z","topic":"Kia K9","topic_text":"Kia K9","text":"Nhiu lc t ra tht v tnh, ri m v nhn ra \n... mnh ang khc ... \nNhiu lc t ra tht nhn tm, ri m v nhn ra \n... mnh ang au ... \nNhiu lc t ra tht cng ci, ri m v nhn ra \n... mnh tht yu mm ... \nNhiu lc ci tht ti cng ai, ri m v nhn ra \n... mnh c c...","language":"notdetect","sentiment":"Mixed","author":"NhímTâmTrạng","author_text":"NhímTâmTrạng","md5_text":"ba6271546db9f77444c6b857353f1854","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007757997637632},{"id":"353114021","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=435678259832132","authorlink":"Colorsoflife","date":"2013-01-06T13:58:19Z","topic":"Kia K9","topic_text":"Kia K9","text":"CHUYN TNH I DP\nAnh chng mun cng em lm i dp.\nDu song hnh nhng u c bn nha\nK trc ngi sau sut qung ng di.\nTuy mt hng m chng h nhn mt.\n\nAnh no mun mi khi ln pha trc.\nLi bt em t ln mt t th.\nAnh sao n khi ngng mt nhn tri\nLi bit rng t en em ang ta.\n\nAnh u mun chia phn bao nng nhc.\nCa sc ngi ca vinh nhc bon chen.\nNhng thm nhung kia, nhng ct bi i thng.\nNo phi th bt em cng gnh chu.\n\nAnh khng th pht no ht hng.\nRi c k dm nng bn em.\ni dp kia u phi mi song hnh.\nC bao gi dp t cng mt lc?\n\nAnh sao chu ni c k no trng ging.\n nhn vo em li bo ging anh.\nRi mt mai phi minh chng hng hn.\nRng c th s bit ngay khng phi!!\n\nThi em nh bi th i dp.\nChng th l hnh dng ca hai ta.\nTuy ni nh chng km phn da dit.\nCng phi ty hon cnh v von","language":"notdetect","sentiment":"Mixed","author":"Colorsoflife","author_text":"Colorsoflife","md5_text":"ef2a45415d0b68bd1b7e544770b8596e","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758042726400},{"id":"353114558","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=427508850651984","authorlink":"Hộinhữngngườithíchđọctruyệntìnhcảm","date":"2013-01-06T13:43:05Z","topic":"Kia K9","topic_text":"Kia K9","text":"<3~~>Gi ngi em yu:\nNh mt thoi quen khng th thiu, em lai tim v vi binkhi chiu xung vao dip cui tun kimcho long minh mt chut gi o binh yn, nhe nhom.Em ngn ng ngi ngm tng t song ni nhau anh manh vao my choma tung bot trng xoa, bng dng thy minh le loi va mun gp anh hn bao gi ht! Em m mang nghi v anh, mun noi vi anh tht nhiu!Anh bit khng? Ln u tin em xa vong tay cua cha me, mt minh lac long va n c ni t khach,thc s em cha u ban linh chng nhngva vp t cuc sng y mi me nay.. Ap lc cng vic, s e nen cua xa hi lam con be con 19 tui ngy ng, v t quen sng trong s ch che cua gia inh nh em thy nget th, c c, b tc ri lng le tim ni thanh tinh va v oa nc mt, nc n ngon lanh nh mt a tre. Luc y, em cn lm b vai vng chai cua anh.Gin anh lm, anh bit khng? Lai thm mt ngay 20/10 bun. Em cm ghet nhng ngay l. Tht ich ky ung khng anh? Nhngngay o, tui ban em c cht ngt trong hanh phuc yu thng, con em lai thui thui ra hang hoa mua my bng hng v nhe nhang cm vao lo nh rng, minh cung la con gai, cung yu hoaChc sinh nht nm nay em se bun lm y! Xa cha me, ban be, se chng coai ct tng em mt canh thip, mang n cho em mtmon qua, th nhng em vn nui hi vong rng co ng But la anh xut hinva cho em c toai nguyn gic m: Nghe mt bai hat trong ngay thi nn.Sinh nht em cung la luc ma ni ni trn khp th gii, ngi ta gi tng nhau nhng cu chuc an lanh ngay Chua giang sinh; Noel m ap va hanh phuc, vy ma tai sao em vn cam thy co nhng vt gio len loi qua phi?Sao lai khng gin anh ch!ng v, hai ban chn nhobe cua em c tim ngt, au nhc vi lanh. Gia nh em c anh mua cho nhng i tt in hinh my con kitty nho nho xinh xinhva nhc em thng xuyn mang giay gi m , chn em se bt au but o anh a.Em trach anh nhiu vi chiu th Bay nao anh cung bo mc em mt minh th thn ap xe quanh thanh ph nho be nay va ngm bao ban be cungtrang la qun quyt bn ngi yu. Trng ho hanh phuc lm c! Vy ma anh n em thui thui.My bc tng phong em cng lm! Nhiu ln em cong vai cai inh c treo , nhng cang ong, vi va ri ba bai ma inh thi cong ca ln. Nu co anh o, em se khng phai t m m hi ma vic cung chng xong.Em thich ma va khat khaocung anh ap xe rong rui trong ting go nhip u u cua ma it nht mt ln, thich hai a t sung ri pha ln ci vi vui sng. Vy ma cho n luc nay, o vn la mt gic m.Anh v tm mc em lang thang trn nhng con ng ngp nc v m li bi my ngay sau o khng ai chm soc.Em to mo qua cai cam giac e ngai, then thung khi ln u tin co mt ban tay rn chc nm ly ban tay nho be, yu t cua em, dt em qua ng, m ap va binh ynEm cung khng bit minh se nh th nao nu ln u tin co mt ngi con trai la anh m em vao long? Lm luc em thc mcqua v nhng nu hn kia,sao ngi ta bao la ngot ngao, la chay bong h anh?Em con co rt nhiu c mun c cung anh thc hin, mun c hen ho, bit yu thng, hn di nh ban be cua em nh bao ngi con gai khac.Em gin anh nhiu lm anh bit khng? n by gi vn em mon moi ngong ch nh mai vang i mua xun. Sm n vi em anh nhe! Em se ch, bi em cn co anh trong cuc sng nay!","language":"notdetect","sentiment":"Positive","author":"Hộinhữngngườithíchđọctruyệntìnhcảm","author_text":"Hộinhữngngườithíchđọctruyệntìnhcảm","md5_text":"f0dc114b917b496948e89c289c9d4998","dimension":["Year 2010"],"category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758044823552},{"id":"353114726","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003472176616","authorlink":"BánhBaoHấp","date":"2013-01-06T13:40:22Z","topic":"Kia K9","topic_text":"Kia K9","text":"Ngi nha ma trong ngi khng yn c..tinh hinh nay ngay kia phai ra Ha Ni thi...hix2..","language":"notdetect","sentiment":"Mixed","author":"BánhBaoHấp","author_text":"BánhBaoHấp","md5_text":"8e1ae27400b5abcaa4a8f5ae1e1e942b","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758046920704},{"id":"353114295","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003146732128","authorlink":"MaihoaNguyen","date":"2013-01-06T13:51:26Z","topic":"Kia K9","topic_text":"Kia K9","text":"Mt ngy mt mi v lo lng\nMa ng tht lnh,lnh tht!\nSp tt ri,ti i ci ln no\nNgy tn th c xy ra u?","language":"notdetect","sentiment":"Mixed","author":"MaihoaNguyen","author_text":"MaihoaNguyen","md5_text":"0a9ddc23847524e53a9c1a5c9c6479ef","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758047969280},{"id":"353114186","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003317640538","authorlink":"NguyệtNormal","date":"2013-01-06T13:53:38Z","topic":"Kia K9","topic_text":"Kia K9","text":"Mnh ni l ko c g ri,nhng m ngi nghe ko chu hiu,c hiu cng ko chu tin...Suy ngh ra th trn i ri ni cc kiu con iu :)) Tin hay ko ty bn *Trch ca anh Hng yu qu* th thi :))\n\n cn 1 vn na. Mnh ght nht my bn gi cho mnh tin nhn g m ''nu bn ko lm theo b m bn s cht'',''nu bn ko gi cho ngi ny ngi kia th s gp xui xo,tai nn, c i'' ...=))\na y! Cm phin cc bn ng bh gi my ci mnh na nh! ^^\n Cm n rt nhiu <3","language":"notdetect","sentiment":"Mixed","author":"NguyệtNormal","author_text":"NguyệtNormal","md5_text":"ae0cad4e87dbd23d611e1e66da99b959","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758049017856},{"id":"353114296","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100001243444698","authorlink":"PhiTrầnHoàng","date":"2013-01-06T13:49:00Z","topic":"Kia K9","topic_text":"Kia K9","text":"i git no sau bao ngy n chi xa a gi l lc quay li vi hin ti...nhn ng qun o ngn ngm qu.","language":"notdetect","sentiment":"Mixed","author":"PhiTrầnHoàng","author_text":"PhiTrầnHoàng","md5_text":"c27ee9aa9d4cdfbc9bb562a6976d19cf","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758050066432},{"id":"353114197","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003912466651","authorlink":"SởKhanh","date":"2013-01-06T13:55:05Z","topic":"Kia K9","topic_text":"Kia K9","text":"584-1314-520","language":"notdetect","sentiment":"Mixed","author":"SởKhanh","author_text":"SởKhanh","md5_text":"375d2e23113b2ea3b05db5d5f95b44bf","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758051115008},{"id":"353114598","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003901562749","authorlink":"CườngUno","date":"2013-01-06T13:37:47Z","topic":"Kia K9","topic_text":"Kia K9","text":"Anh c ngh chia tay anh s qun c em........................................................................................................................................................................................ai ng..................................................................................Anh qun sch...@@!","language":"notdetect","sentiment":"Mixed","author":"CườngUno","author_text":"CườngUno","md5_text":"6762f2d953844e02b9035578020b6713","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758052163584},{"id":"353114904","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003659575742","authorlink":"ĐạiLuciFer","date":"2013-01-06T13:28:02Z","topic":"Kia K9","topic_text":"Kia K9","text":"nhiu lc ngh nhn tc mnh # ch g ci bt m tm p ngc u , lc th ch # g ci t qu , lc th nh ci t ni . hc . nhng nhiu lc cng p pht . hahaahaha/ c th ni l s ta ca mnh hahaha. ngh m bn ci","language":"notdetect","sentiment":"Positive","author":"ĐạiLuciFer","author_text":"ĐạiLuciFer","md5_text":"8690ec4392170952eab18be56bb98101","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758053212160},{"id":"353114338","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004697798633","authorlink":"NguyễnGiang","date":"2013-01-06T13:50:00Z","topic":"Kia K9","topic_text":"Kia K9","text":"lieu cuoc song nay con dog luc gi de ta ton taj va song tiep???!!!","language":"english","sentiment":"Mixed","author":"NguyễnGiang","author_text":"NguyễnGiang","md5_text":"a3716facb99650c9570fdaa457ebca48","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758054260736},{"id":"353114927","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004989846587","authorlink":"TrầnKhánhNgọc","date":"2013-01-06T13:27:55Z","topic":"Kia K9","topic_text":"Kia K9","text":"Minh Quyt Phm nha..t nhin nhc ti Hu lm j ti hm kia e nm m thy n...","language":"notdetect","sentiment":"Mixed","author":"TrầnKhánhNgọc","author_text":"TrầnKhánhNgọc","md5_text":"c0caa80f9ad175c62c8a13355a17d3b4","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758078377984},{"id":"353114704","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=130520477098482","authorlink":"Hộinhữngngườithíchtâmsựvàmuốnđượcchiasẻ","date":"2013-01-06T13:37:34Z","topic":"Kia K9","topic_text":"Kia K9","text":"Cac mem i hin tai ad rat cn`s ung h cua cac mem.that s ad khng mun cu like 1chut' nao nhng Stt nay ad mun cac mem like that nhiu cho ad....ad cn`lm ' y...1like la th hin 1s quy mn' cua moi ngi ti Ban quan tri Page mong mun 2ad Michu va Trang Kull tr lai ...c khng a....like nao...thank cac ty nhiu nhe ....<3\nAi mun c oc Truyen hay thi hay like cho Michu....Stt c thi like cho Trang Kull......ok..cung page phat trin nhe .....iu lm'....:)))\n\nLuna","language":"notdetect","sentiment":"Mixed","author":"Hộinhữngngườithíchtâmsựvàmuốnđượcchiasẻ","author_text":"Hộinhữngngườithíchtâmsựvàmuốnđượcchiasẻ","md5_text":"9422cab41a575b76556976b3d757f425","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758079426560},{"id":"353114596","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100002899490366","authorlink":"DonaDang","date":"2013-01-06T13:35:10Z","topic":"Kia K9","topic_text":"Kia K9","text":"Em nh 1 ngi ko nh em .. Em nh 1 ci nm tay khi em khc ...em nh 1 ci m tht cht khi em git mnh tnh gic .... Nh ch l ci bit ci cm jiac l con gi ..... v nh bit ngy Mai ngi y lun khng thuc v mnh na .... Phi chng khi mc vy ri con gi lun Cn 1 b vai da ..... Cn 1 chic o m hn khoc ...... V ...^^*** ??","language":"notdetect","sentiment":"Mixed","author":"DonaDang","author_text":"DonaDang","md5_text":"6978a47dd05062b17429cdcd58a954fb","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758080475137},{"id":"353114727","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100002640856886","authorlink":"TimQuenLang","date":"2013-01-06T13:36:13Z","topic":"Kia K9","topic_text":"Kia K9","text":"....Thy nng lng mi ma xun v !","language":"notdetect","sentiment":"Mixed","author":"TimQuenLang","author_text":"TimQuenLang","md5_text":"c02e0a45d22922f5bcd9597744d0b7ec","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758081523713},{"id":"353114425","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100002828605429","authorlink":"HuongPham","date":"2013-01-06T13:41:43Z","topic":"Kia K9","topic_text":"Kia K9","text":"- Tnh hnh l. . .\n- ang nh 1 ngi\n- ang nh n ci\n- V ang li yu thng...","language":"notdetect","sentiment":"Mixed","author":"HuongPham","author_text":"HuongPham","md5_text":"61bcd8118b204ce9dfab145b0b374e0f","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758082572289},{"id":"353114457","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004948119605","authorlink":"TrangLươngMinh","date":"2013-01-06T13:45:35Z","topic":"Kia K9","topic_text":"Kia K9","text":"S rt au n khi bn yu mt ngi no m khng c p li.Nhng cn au n hn khi bn yu mt ai m khng dng cm ni cho ngi bit bn yu nh th no. \n\nC nhng khonh khc trong cuc i khin bn nh ngi ta tht nhiu , n ni bn ch mun chy n v m h tht cht . Hy cho ngi bit bn c suy ngh nh th !. \n\nTrao cho ai c con tim mnh khng bao gi l mt s m bo rng h cng yu bn, ng ch i iu \n\nngc li. Hy tnh yu ln dn trong tim h, nhng nu iu khng xy ra th hy hi lng v t ra n cng ln ln trong bn. \n\nTng lai ti sng thng da trn qu kh qun lng, bn khng th sng thanh thn nu bn khng vt b mi ni bun qua. \n\nMt iu ng bun trong cuc sng l khi bn gp mt ngi c ngha i vi bn , ri cui cng nhn ra rng h sinh ra khng phi cho bn v ch c th h i ....Nhng khi mt cnh ca ng li, mt cnh ca khc li m ra. iu bn cn lm l thi khng ch i ni cnh ca ng, hy tm mt cnh ca khc ang m ra cho mnh. \n\nng qun hy vng, s hy vng cho bn sc mnh tn ti ngay khi bn ang b b ri. \n\nng nh mt nim tin vo bn thn mnh. Ch cn tin l mnh c th lm c v bn li c l do c gng thc hin iu . \n\nng nhng kh khn nh gc bn, hy kin nhn ri bn s vt qua. \n\nng ch i nhng g bn mun m hy i tm kim chng. \n\nHy mm ci trong cuc sng. N ci ca bn mang li hnh phc cho ngi xung quanh v do cng mang li hnh phc cho chnh bn. \n\nng bao gi ni khng cn yu na nu nc mt ca ngi kia vn c th gi chn bn . \n\nng khc v mi vic qua, hy ci v mi vic ang ch pha trc. \n\nng chy theo v b ngoi ho nhong, n c th phai nht theo thi gian. \n\nng chy theo tin bc, mt ngy kia n cng s mt i. \n\nHy chy theo ngi no c th lm bn lun mm ci bi v ch c n ci xua tan mn m u ti trong bn. \n\nHy lun t mnh vo v tr ngi khc, nu iu lm tn thng bn th n cng s tn thng ngi khc. \n\nNgi hnh phc nht khng cn phi c mi th tt nht, h ch l ngi lm cho mi vic, mi chuyn u din ra theo h. \n\nHnh phc thng nh la nhng ai khc lc, nhng ai b tn thng, nhng ai tm kim v th. Nhng nh vy, h mi bit c gi tr ca nhng ngi chung quanh h. \n\nTnh yu bt u bng n ci, ln ln bng n hn v thng kt thc bng nc mt (Okies!). \n\nKhi bn c sinh ra , bn khc cn mi ngi xung quanh ci. Hy sng sao cho khi bn qua i, mi ngi khc cn bn, bn ci. \n\nHy gi nhng vt d nh nht ca ngi bn thn... bit u sau ny n s l mt k nim ca bn. \n\nHy ni nhng li yu thong nht n ngi m bn yu thng .... \n\nBn cha cn n 3 giy ni \"I love you\", cha n 3 pht gii thch cu ni y, cha n 3 ngy cm nhn c ngha ca n , nhng chng minh cu ni n gin y th c cuc i vn l cha . \n\nCng nh vy: Ch cn thi gian mt pht th bn c th cm thy thch mt ngi. Mt gi m thng mt ngi. Mt ngy m yu mt ngi. Nhng m bn s mt c i qun mt ngi. \n\nKhng ai ng gi bng nhng git nc mt ca bn. V nhng ngi ng gi s khng bao gi lm bn khc. \n\nCh khi bn tht s mong mun ai c hnh phc, thm ch hnh phc khng phi dnh cho bn, bn mi hiu rng bn yu ngi tht s mt ri. \n\nC mt s tht l bn s khng bit bn c g cho n khi nh mt n, nhng cng c mt s tht khc l bn cng s khng bit mnh ang tm kim ci g cho n khi c n. \n\nHy lm nhng g bn mun lm, m nhng g bn mun m , ti u bn mun ti , tr thnh nhng g bn mun , bi bn ch c mt cuc sng v mt c hi lm tt c nhng g bn mun . \n\nYu l mo him v c th b t chi. Nhng khng mo him th l tht bi ri v trong cuc sng iu nguy him nht l khng th thch iu g . \n\nTnh yu l con dao. N m nt con tim hay c khi n khc su vo tim ta nhng vt khc diu k v s theo ta n cui i. \n\nNgi ta v o bng kim, cn bn s v con tim bng g? \n\nTnh yu l mt mn qu m ch c th m chi ny lc khi c trao tng i. \n\nTi mong bn c hnh phc tr nn ngt ngo , c th thch tr nn vng mnh , c ni bun bn hiu cuc i , c nim tin bn bc ti v c tnh yu dng hin cho i","language":"notdetect","sentiment":"Mixed","author":"TrangLươngMinh","author_text":"TrangLươngMinh","md5_text":"393885d7c2b41bfa3c1a77961127ed4e","dimension":["Extreme Positive (autos)"],"category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758098300928},{"id":"353114659","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100001204830854","authorlink":"LyKhánhTrương","date":"2013-01-06T13:36:22Z","topic":"Kia K9","topic_text":"Kia K9","text":"Tr Tag cho NT Hiu\nAi oc status nay va nhng ai c tag stt ny mak ko tra tag hoc ko tag thm 10 ng # na o ng se phai gp en ui sut i. Ch c ngi iu lun =))\n1. Tn : trng khnh ly\n2.Mu sc yu thch: xanh da tri :x\n3.Ni di: ko ni di ko phi ngi VN :))\n4. Bi ht mi nghe: Yu anh\n5. Idols: noo~~\n6. Khi b stress: ng :\">\n7. That tinh: v ang y :((\n8.Hn ngi khc: noo~~\n9.Na m hm qa lm g: m thng em :\">\n10.Trang web hay vo: FB, mp3\n11.Nickname: ph >\"<\n12.Cung hong o : thn nng\n13.Vt nui: nooo~~\n14.Tc di hay ngn: di\n15.Chiu cao : 1m60 c m chc ch n =))\n16.Quen bn mi: nhiu\n17.Thay i h tn: never\n18.Thung ng dy lc: 8h :\">\n19.eo khuyn tai : ko~~\n20.Thun tay: phai\n21.Ung bia, ru: c 2\n22.S ma: na c na k\n23.i ra ng mt mnh vo bui ti: ko c cc tnh yu th iem ko dm i >\"<\n24.eo knh: thnh thong\n25.Thch lm bn cng nhng ngi: all\n26.Cng vic nh lm trong tng lai: mun c nh tin m\n27.Bun: i lc\n28.B m: cc t\n29.Mn th thao iu thch: ng\n31.Trng cy : noo~~\n32.Mun c bao nhiu a con: ang ngh xem ly c my thng chng :))\n33.Chn hc : often\n34.Hay tm s: ch vs BFF thi\n35. trong lp hay lm g: 1 l ng 2 l chp v 3 l chm gi :))\n36.Ght: ht tin\n37.Thch ra nc ngoi ko: c~~~\n38. nh thng lm g: ng\n39.Thch hc khng: ko~~\n40.Thch lm g: ch ng\n41.Thng i chi vs: c i u m thng vs ko\n42.Ght lm g: i ra ng vo ci tri ny. may m mnh c n.y\n43.T k: never\n44.Ght hc mn: anhhhhhhhh~~~~\n45. Nu bn tht bi: lm li t u\n46.Thch i chi : thin ng\n48.Ch nht thng lm g: nh ng\n49.Bn ang lm g: tra stt\n50.S: ht tin\n51.Nu mn g gii nht: rang tht =)))))\n52.Thch nht: chm gi\n53.Bit bi khng: no~~\n54.C nhiu tin khng: ko 1 ng xu dnh ti\n55.ang s ci g: s cht rt\n56.S trng l g: ng :))))\n58.Bui sng n g: 1 l nhn 2 l m tm\n59:Sng u: nh\n60.Trng thi ca bn by h: FA\n61. By h mn lm g: thoat khoi cai stt nay i tm >\"<\n62.nh tag nhng ai: mi ngi\n63.Hc gii khng: bt\n64.Bn gin ngi khc lu k: lu. nu c mi i n th nhanh =.=\n65.Bn c beautiful: sao n ko c ch very nh :))))\n67.Bn l ngi yu ui hay mnh m: mm yu :))))\n68.Thch i chi k: co\n69:Qu ai nht: bn thn\n70.By h bn ang t hi u j: i tm h ny chc rt lm nh :((\n71. Mun i u nht : i ng\n72. V mc in r ca bn : ci ny phi t kim chng\n73. Lc tm hay lm j : di nc :))\n74.La ngi khc: ch b\" na\n75. yu cha: cha. ch thc thi =))\n76.C thch b dnh tag k: ma n. in m thc. lu v!!!\n77.Ngi pm gn y nht: in thoi th l Phm Hng Mnh cn FB l Thang Nguyen \n78.C thch c truyn k: c. tr tnh cm th thi ri \n79.Thch hoa g: LY :))))\n80.Thng mc g: q ao\n81.Chi c nhc c g: chi chuyn :))\n82.Bn hay u: nh\n83.Bn thch ma no nht: thu\n84.Mun quay li vo thi im no trong qu kh: lc cn cha sinh ra. c g mnh l men :((\n85.Ngy bn thch nht: 28 thng 10 \n86.Mun gp ai nht: ng L Lc :))\n87.Bn thy mnh c c c ko : ko~~\n88.Nhc ch ca bn l: ch nh na\n89.Thch nghe bi ht no: Yu anh\n90.Bn c s cht k: ko th l ns di \n92.C hay nh bn k: hi b th c :))\n93.Thch n qu g: qu g cng thc\n94. lp c hay b bn b tru ko: often\n95.C chn bn chi k: c\n96.Ai t tn cho bn: bc Nng\n97.Mong mun ca bn: mai thi qua c mn anh :((\n98.Hm qua lm g: c gp ng l lc :))\n99.Cm xc hin ti: lnh v~~~\n100. Mun ni vs nhng ngi: t ti bay nn tag . xin li nha. t mun mi ng ging t by h :)))","language":"notdetect","sentiment":"Positive","author":"LyKhánhTrương","author_text":"LyKhánhTrương","md5_text":"d85b5c5ccd356d374d2e1658521e100c","dimension":["Styling","Extreme Positive (autos)","Vehicle Quality"],"category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758100398080},{"id":"353114124","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004763762576","authorlink":"ChiHieuHtv","date":"2013-01-06T13:52:11Z","topic":"Kia K9","topic_text":"Kia K9","text":"Tnh yu\nGig nh 1 Bi ton kh\nMn gii c\nth...\n.\n.\n.\n.\n.\n.\n.\n.\nth phi \"nhp\" trc vi ngi!\n=))\n... :v","language":"notdetect","sentiment":"Positive","author":"ChiHieuHtv","author_text":"ChiHieuHtv","md5_text":"19af1d2af7e9fdfe18b5383e77d97329","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758102495232},{"id":"353114199","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003899946089","authorlink":"ItachiSharigan","date":"2013-01-06T13:52:00Z","topic":"Kia K9","topic_text":"Kia K9","text":"cht ma ri ! ngy mai i hc qun s 1 thng m tt c qun o ca mjh cha kh nak ? phi lm sao y ?","language":"notdetect","sentiment":"Mixed","author":"ItachiSharigan","author_text":"ItachiSharigan","md5_text":"9267e077cac937a4ae35cbc4454d8fbc","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758103543808},{"id":"353114340","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004281160052","authorlink":"CôBéKhôngTên","date":"2013-01-06T13:49:58Z","topic":"Kia K9","topic_text":"Kia K9","text":"A a nhng gi e ha vi anh em a thc hjn ht rj cki con 1vjc na thj caj ngay y sp tj rj em se thc hjn nt lj ha cuj cung anh nk nke.....\nVa khj lj ha cuj cung c thc hjn tki em se maj maj bjn mt khoj c/s cua anh anh hay ck nke","language":"notdetect","sentiment":"Mixed","author":"CôBéKhôngTên","author_text":"CôBéKhôngTên","md5_text":"11b6d6752347ab79a68a34a468d189a3","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758104592384},{"id":"353114410","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=181636271961632","authorlink":"Anhakcolẽemyêuanhmấtrồiđừngbỏrơiemnhé","date":"2013-01-06T13:47:49Z","topic":"Kia K9","topic_text":"Kia K9","text":"Mt cu chuyn cm ng :((\n\nBt u 1 cu truyn \nLinh , Nam v Long l 3 ngi bn thn , cng hc 1 trng . Nam thch Linh lu m cha dm th l . Tnh yu ca Nam ch c giu kn trong 1 quyn nht k . nhiu ln Nam nh ni ht vs Linh nhng cha bao gi Nam dng cm ni ra chuyn c . Ln no cng th '' ln ny s c thi m '' nhng ..............\n\nMt ln th l tnh cm \nCho n 1 ngy , Nam quyt nh s ni cho bng c . Nam t nh '' nht nh s c , nht nh s c , nht nh.......''\n_ Linh , mnh...........Linh........-Nam p ng \n_G vy Nam ??? - Linh hi \n_.....mnh....mnh.....................k .....k c g c . \n1 khong lng gia 2 ngi , Nam vn t dn vt mnh , ti sao k ni nn li.....\n_Linh , tht ra th mnh ........\n_Ka Nam , v nh ri , chuyn g ni sau nha - Linh ci ti .\nBng Nam cm thy ht hng , mi chuyn ng l xong , ngy mai k bit th no y. Liu Nam cn can m ni vs Linh ??? Cu hi c dn vt Nam.\nNhng hm sau th Linh qun i , k hi li Nam na . V vy , Nam li k c c hi ...\nNgy Valentime \nHm nay l 14/2 , Nam chun b 1 thanh chocolate dnh cho Linh , v vn nh mi nm , Nam ch bit giu thanh thanh chocolate vo cp Linh. Nm no cng th , trng t chc i hi mng ngy l tnh nhn , mi bn s t chn cho mnh 1 ngi khc gii i tham d . Nam h hi mi Linh cng tham gia th \n_Nam , c ai i cng cha ??? mnh s i cng Long , vy nh , ti mnh i trc , lt Nam ti nha . - Linh vt chy mt , n bn Long .\nCuc i tht tr tru , k phi nh vy , r rng l Linh thch Long ri cn g na . Nam au n , dn vt nhn ra tnh cm ca Linh....V nh chp nhn '' chn i '' 1 bn n i cng ..........\nCui cng bui l kt thc , Nam v Linh vn cng nhau bc v nh trn con ng quen thuc.Gia 2 ngi vn l ci khong lng y , khong lng m Nam rt ght ......\n_Mnh...lm quen vi...vi...nhau nh , nh...ngi ta vn gi l ngi....y..u....y .\nNam git mnh , chuyn g th ny , Nam c tin ni k , c ng Linh va ni k vy , Nam qu sung sng , trong c hng tc cu ni '' ok '' m Nam vn mun ni , v Nam qu sung sng m k ni ni nn li..........\n_Lm g m cu suy ngh nhiu th , tt nhin l mnh phi pht cu , v sao cu c nh vy h ???? Mnh....mnh.......a.....a y.....hihi - Linh ci\n_Haha , hay tht , th ra l cu a , lm mnh c tng tht , ng th , mnh v cu th lm sao c th ........\nLinh gin gi :\n_Cu , sut i k trng thnh , ngc ......\nNi xong Ling gin gi b i , li chng Nam ngc xt ng ngy ng , k hiu chuyn .\n_G ?? Chnh cu bo l a m . Nh th , chng l , cu ni tht ?????\nNhng ngy sau \nNam v Linh vn c gp mt nhau , n gin thi , v 2 ngi hc chung lp . 2 ngi by gi c 1 khong cch rt ln . Nhiu lc Nam mun ni chuyn nhng Linh c trnh Nam hoi . Thi gian c th tri i ..... 2 ngi c th lng thm .....Nam rt au kh ....Nhng tt c nhng g Nam c th lm ch l : m thm trt ht vo cun nht k . \n'' Nam i , my tht hn h '' , Nam thng xuyn ngh mnh nh vy . \nV tip l 1 chui ngy Linh k i hc , Nam suy sp.........\nNgy ma....\nTht k may cho Nam , hm nay Nam ngh hc , Nam k c tm trng i hc na th Linh li xut hin trn lp . Tri ma nh trt , di mi hin c 2 ngi ang tr chuyn : \n_Mnh c th coi nhau nh ngi yu ??? - Long ni\n_C li g ko ??? - Linh tr li lnh bng\n_, mnh bit chuyn hai ngi , nu nh cu ng , Nam c th s ghen , v..........-Long ni.\n........................\n--------------------------------------------------------\nNgy hm sau , Nam nhn c tin nh st nh , Linh ng lm ngi yu ca Long . c th ni l ngy au kh nht ca Nam , Nam gc ng , Nam tht vng ,Nam k cn tin vo chnh bn thn , Nam gi tr thnh k '' tht bi '' \n1 iu ng ni hn , Nam k t bt c thi kh chu no : \n_Chc mng cho 2 ngi nh - Nam ci trng tht tu , mo m v gn nh chy nc mt \nng , Nam giu nc mt ca mnh vo su tn trong tim , tri tim ang r mu....\nNgy khai trng.......\nCui cng th cng kt thc nm hc cui cp , ngy chia tay bn b , thy c , Nam vn ng , trng v hng ca Linh , cn Linh ng bn Long . Nam ch mun nu ko , gi ly 1 cht g , d ch 1 cht thi , nhng giy pht c trng thy Linh . Bng , Linh tin v pha Nam \n_Nam n , sp kt thc bui chia tay ri , mnh chc Nam thi c 1 trng tht tt nh . Mnh cng phi i y , sau ny vn c th lin lc , nhng s t gp nhau , tm bit Nam nh - Linh ni , cho Nam 1 ci ri chy li bn Long\nHt tht ri, th l ht tht ri , tnh cm ca Nam by gi k c ch ng trong tim Linh , Linh mi hng n Long , cn Nam , Nam mi hng v Linh . \n'' Tm bit Nam nh '' sao c th ni ra 1 cch d dng nh th kia ch ????\nm ci bun t \nThi gian tri i , nhc Nam , Linh , Long nm no trng thnh , gi y , Nam nhn c giy mi d hn l ca Linh & Long . \nNam n d vi mt con tim cht , Nam c t v vui mng , ci ni , chc mng nhng c ai bit u tim Nam ang au tht . Tht nh 1 bi kch , Nam vn ci......\nNam ch cn 1 cht hi vng nh nhoi , mong Linh s n bn Nam , v 2 ngi s c 1 kt cc tt p , nh Nam vn thng mong mun , ri 1 iu k diu s n........\nNhng tt c kt thc thc s , l cid kt thc , gi y , Linh l ca Long.\nL tang ca ngi y....\nNhiu nm tri qua v gi th Nam ang ng trc nm m ca Linh . Nam sng 1 mnh vi tnh yu y , vn ch i 1 cch ngu ngc d Linh s k v , Nam ch c Linh .....\nKhi mi vic xong , gia nh Long mun xem li nhng k vt ca Linh li , trong c 1 cun nht k.Mi ngi m ra xem v kt thc l 1 chuyn tnh k ai ng ti....\nS tht v hi hn.......\nCho n by gi , b mt ca Linh c tit l , trong cun nht k , Ch 1 dng thi \n'' Linh yu Nam ''\nTh y , s tht bao gi cng nh th y , Nam a khc , a khc nh 1 a tr . Nam qu hi hn , qu cm ght mnh .\n'' Gi nh Nam can m hn....gi nh Nam c th ni Nam yu Linh....gi nh hm Nam tr li Linh - ok...gi nh Nam s gi Linh li khi Linh ng Long....gi nh Nam ngn li m ci trc khi qu mun...gi nh.....gi nh ........''\nNhng......tt c qu mun , ch cn li nhng '' gi nh .....'' ca Nam.","language":"notdetect","sentiment":"Mixed","author":"Anhakcolẽemyêuanhmấtrồiđừngbỏrơiemnhé","author_text":"Anhakcolẽemyêuanhmấtrồiđừngbỏrơiemnhé","md5_text":"dbf99623199e6e4dedaa6d756581e0ad","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758128709632},{"id":"353114202","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004498322069","authorlink":"KhoaRoyal","date":"2013-01-06T13:51:57Z","topic":"Kia K9","topic_text":"Kia K9","text":"Va tm xong .... phi cng nhn l khng ni lun","language":"notdetect","sentiment":"Mixed","author":"KhoaRoyal","author_text":"KhoaRoyal","md5_text":"2254d04911b6a9aa3e938c4ecb00edb1","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758131855360},{"id":"353114003","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004039278649","authorlink":"DuongMaiPhuong","date":"2013-01-06T13:58:26Z","topic":"Kia K9","topic_text":"Kia K9","text":"Bng dng mun khoc","language":"notdetect","sentiment":"Mixed","author":"DuongMaiPhuong","author_text":"DuongMaiPhuong","md5_text":"37317db1091f07e28a26c4593afad098","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758132903936},{"id":"353114307","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003205410288","authorlink":"DuyHải","date":"2013-01-06T13:53:14Z","topic":"Kia K9","topic_text":"Kia K9","text":"t nay b ngh like tc, khng like cho cc e xinh ti na =))","language":"notdetect","sentiment":"Positive","author":"DuyHải","author_text":"DuyHải","md5_text":"435594d6742f066640d3c9b8c1bc778e","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758133952512},{"id":"353114127","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004064767038","authorlink":"HàHọkGoBy","date":"2013-01-06T13:51:54Z","topic":"Kia K9","topic_text":"Kia K9","text":"Mai li phi xa con c ngy i ly hng:( nh lm:(","language":"notdetect","sentiment":"Mixed","author":"HàHọkGoBy","author_text":"HàHọkGoBy","md5_text":"243fcb9fd88881fac3c184c82eccd366","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758135001088},{"id":"353114147","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004007092739","authorlink":"MaianhNguyen","date":"2013-01-06T13:57:57Z","topic":"Kia K9","topic_text":"Kia K9","text":"Nh ai ai nh b.j nh ai y?c iiiiii","language":"notdetect","sentiment":"Mixed","author":"MaianhNguyen","author_text":"MaianhNguyen","md5_text":"684db1fc9d8b783edbd6543d2e20b1fe","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758135001089},{"id":"353114178","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=222748111159791","authorlink":"Cólẽnàomìnhxanhau","date":"2013-01-06T13:55:57Z","topic":"Kia K9","topic_text":"Kia K9","text":"Nh Nng Cnh Nh Ti\n\n[Truyn] Nh nng cnh nh ti (Cuc chin gia Nhp v Qun i Hoa)\n\nC5.\n\nSng nay mnh dy sm v mi au nhc qu. Chn cng au thu xng. Nm ngh mi v nh. T nhin mnh mun xin li, mun lm ha qu. Na v s nh, nh lm chiu nh th, bit u cn tm tr hi mnh di di. Na thy c li v em nh ra lm tr ci cho thin h.\n\n \n\nTing chung ca lm mnh git bn. Cm gic y nh ci hm b nh gi xung tt. Mnh tp tnh xung nh. Va l u ra, sut ng v nhn thy nh ng ca. Li g na y? Mnh khng dm bc thm bc no na, c ng trong nh ni vng ra:\n\n \n\n- G vy? Hm qua nh cha h ?\n\n \n\nNh th tay vo cng m then. e! M i lm khng kha cng. Chng hiu sao mnh run nh cy sy. S qui g nh ch, mnh to gp i nh, cng lm ng ca ra hp >< h. \n\n \n\nNi th nhng vn s. Nh mnh khng c ch. Nh c phm phm i vo, tay cm ng g bi nhi. Mnh lui lui vo bn trong cu thang.\n\n \n\n- Ny nha, nh na l ng ny khng nhn na u, ng ny nh li y!\n\n \n\nNh nhn mnh ci nhn mi. Mnh nh b in git, tay bung thng khng phng g na. hic.\n\n \n\n- M ti i cng tc v mua qu cho nh ng y y. Cn kia l xi sng nay m ti nu, n i cn i hc.\n\n \n\nNgi mnh mm nh bn. Nh i ra n ngoi sn ri m mnh vn l m xem ang mng du hay ang tnh. Lt t chy theo nh, n v c cu:\n\n- Mi ti th ny n sao gi? Mnh ch ch ln ci mi sng nh qu c chua\n\n- Khng n c h?\n\n \n\nNi xong nh li hm hm i vo. Mnh bit mnh li sai g ri. Li lui lui vo nh gi th th. Nh tin v pha bn, m ng va cho, ht hm vi mnh :\n\n- Ti mang v cho con Su nh ti n.\n\n- Khngggggggggg !\n\n \n\nMnh nhanh tay git ng li lun. G ch ? V l, cho ngi ta ri cn i li mang v cho ch n. C qu m khng lm g c. Nh d d ci nm m ln pha mi ti nt :\n\n- ng ti phi nh na nha. Lc y li trch ti khng yu thng sc vt.\n\ni c on nh cn quay li chc mnh :\n\n- Hm nay cha vn vai h ?\n\nCay c tht. Mnh ng gia nh, qun i ci trn, m ng bnh cu vi nm xi trng nh thng r.\n\n \n\nVo mt nm xi ri ln gc rt qun o. Mi ln nhai l mi ln mi au t ti. B nh con gi trng nh con nge m c mt pht m au nh hc u vo ct in. ang tru tro nhai, mnh sng mt khi nhn thy hai ci o ng phc trng ca nh treo pht ph ban cng. Mnh sung sng ng nghing xung quanh, cm ci gy phi qun o nh mnh khu khu hai ci o y v. Ln ny th cht nh nh. Hm nay anh cho nh ci trn i hc lun.\n\n \n\nTt xung phng, mnh treo hai ci o ca nh vo t. Mi con gi thm d man. T nhin ngi mnh cng . Kh gi! Thay qun o chun b i hc. Bng dng cht d. Khng bit sau v ny ci g s xy ra na. Nhng mnh cng ngoan c mun chin u n cng.\n\n \n\nVi vu chm chm trn ng v d g tit hai mi vo hc. C xe bus i qua lm mnh nhn mi vo tm poster dn trn thn xe. L n ng, ti khng nh ph n. ngha th c y nhng sao nghe slogan c chui th. T nhin thy bc bi. Li ngh ti nh. Mnh tr th mt a con gi th th c nh nhen qu khng?\n\n \n\n l ng c mt bng ngi quen thuc. L nh! Khng sai. Nh li hi bn l ng cnh chic xe p trng. Coi b hng xe ri. Mnh i chm chm ng r mt lc ri phng thng lun. Cho nh cht. Sng c qu nn tri pht y. i c on li thy kh chu. Vng xe li nhm nh. Nh vn lng tng, c v bt lc lm ri. Mnh nh tp xe vo l ng ch nh.\n\n \n\nNhn thy mnh nh git bn. Mt kiu hi hng hong s ch khng phi khun mt hm hm lc nh mnh u. Mnh trng xe ri bc n trc nh.\n\n \n\n- G? Th lng i. y khng nh con gi!\n\n- a! Vy ti y lm g?\n\n- Gip ng y ch lm ci g? Hi r c duyn.\n\n- H\n\n- H qu m. ng dp ra!\n\n- \n\n- qu x c, c mi ci xe sa mi khng xong. n hi!\n\n \n\nMnh va li ci tho hp xch ra. M thi bui ny ngi ta i xe p in ht ri, nh cn i ci xe mi ni xch hp ny lm chi? K quc. Va sa va qut mt hi. Mnh ang c ln mt, quay sang nh. Tri i! Mnh sut cht ng lun. Nh ng khc t bao gi. M khc khng ra ting g ht. C nhn mnh ri nc mt chy trn ra m. Khng hiu khc v gin mnh mng, hay khc v cm ng na. Run qu. Mnh m ra lng tng, ng gi u ct lc. Khng bit ni g cng khng bit lm g. May m nh bit , ci mt xung lau nc mt ri p th xe.\n\n \n\nXe quay tt l ri. Mnh sa m. Ng ng xem nh cn khc khng. Khng lm g t nhin khc chi vy m? May m khc nh ch khc to chc cng an phng ra bt mnh nht vo qu.\n\n \n\nNh lc ti sch a cho mnh ti khn giy mng. phi. Tay mnh en s du xch ri. Chm vo tay nh ang run run, thy lnh tot. Gi mi nhn nh, cui thu ri m mc ci o ng phc ma h mng dnh.\n\n \n\n- Sao tri lnh n mc th kia? B mun m ?\n\n- , qua tui phi qun khng kp o nn chc m gi to bay mt ri. Phi li o ma h ra mc .\n\n \n\nH? Mnh cng li ht ni lun. Gi thi vo t qun o mnh ch u. Nhn nh hin hin ti ti th ny m y ny n mc nga ht ngi.\n\n \n\n- Vy mc tm o ti c khng? Ngy xa cng hc cng trng y.\n\n \n\nChng ch nh tr li, mnh ra m cp xe, ly ci o ng phc phng ra a cho nh.\n\n \n\n- Chu kh mt cht nha. N hi to vi khng c thm.\n\n \n\nNh c chn ch mi, ngha ngha ci mi mnh. Chng i nh ng , mnh khoc o ln ngi nh lun ri i ra pha xe.\n\n \n\n- i hc i, mun bo v khng cho vo trng h?\n\n \n\nNh tnh ngi, li ci p chn trng xe ri tro ln phng trc ti. Nhn dng nh p xe vi vng m mnh . Trong lng mnh trng trng kiu g y, nh l nh i ri ht ht gi vi khng kh ch mnh theo.\nC6.\n\nTi ang ngi nhai bnh cu nh cho, online ln l facebook. Thy nh post ci stt mi: Cm n cu tinh qun i hoa. Mnh sng rm ran. Thm cm n ci xe p gip mnh lm ha. Ngi rung i thch ch mi. Nh tn Vi, nhn cng d thng, xinh xn gh. Ngi ln m xem ht ng nh ca nh, c note nh vit, cng thy c cm tnh. Mnh liu pm hm tin nhn ca nh.\n\n \n\n- Ny hng xm, ang lm g y?\n\n \n\n- Nh lng nch!\n\n \n\nB tay! a kiu g vy tri? M chc nh ni tht . Nh ny l m nh lng lm. Hi trc c ln mnh cn thy nh co lng chn c. Nh pm tip:\n\n \n\n- Th cn ng y?\n\n \n\n- ang un o luyn hng =))\n\n \n\nNh m i mt lc, mnh cng khng bit ni g. Lc sau nh li pm\n\n \n\n- Ny y i! y s g nht?\n\n- T ? Suy ngh 1 lc H, s rn!\n\n- Rn c, rn u ?\n\n- Khng, d ? Gin, con Gin . Con gin b lm ngm . Nhn gh cht\n\n- vy h ?\n\n- Hi chi ?\n\n- Thch th hi.\n\n \n\nRi nh li m i. Lc sau mnh thy ting nh gi ngoi ban cng. Mnh m ca i ra ban cng. Nh nhn thy mnh ci ti ri. Mnh ci li. Nh nh lm g vy tri ? Mnh ngi gh ln lan can. Mnh vi nh hi han nhau vi th, chuyn trng lp hc hnh. Thnh thong nh li ci tt mt, nhn mi. Nhn d thng khip c ngi. Ngi mt lc ngoi gi lnh, v mc mi ci o ba l nn mnh ht x lin vi ci. Nh lin chy v phng, lc ra cm ci o ng phc mnh cho nh mn hi sng, c gp gn gng, a cho mnh. Cn nh nhng dn d :\n\n \n\n- y mc i, lnh !\n\n \n\nMnh hn h n o t tay nh, khoc ln ngi lun. Nh nhn mnh ci ci. a sao nhn khng ging n ci hin hin vy ?\n\n \n\nMnh ang ng ngh xem nn ni g vi nh by gi. Tim mnh ang p thnh thch . Bng thy trong ngi nht nht nga nga. Hnh nh trong ngi bnh ang c ci g lc nhc. Rn dng tc gy. Ngi khng dm manh ng g trc mt nh, mnh c ng im, nhng cng ngy cng cm thy r rng c g trong o mnh. Nh Vi ng chng tay vo lan can chm ch nhn mnh lm mnh cng thy nng bc.\n\n \n\nM i... R rng l c ci g trong o mnh tht. Ngi mnh gai ht c ln. Vi lt o ra vt trc mt. u mai gt.\n\n \n\nCon ch.. Thi cht chi by... > < Con nh hng xm tri nh. Dm nht gin vo o ri a mnh mc. Khi phi ni, mnh nhy nhn ht m ln. Mnh khng s g trn i, ngoi gin. Hi b tu siu nc b con gin chui trong vi siu chy tt vo mm m mnh kinh hi m nh ti tn gi.\n\n \n\nSao tri t qu thn nn ra c con nh hng xm c nh con t gic kia vy ? Nh nhn mnh ci ngt ngho, trong khi mt mnh xanh nh t nhi. M mnh di nh nghe thy ting ht vi ting chn mnh dm cm cm xung nn nh, gi vi ln :\n\n \n\n- Li ln cn in h Hong ?\n\n \n\n- M i !!!!! Gin !!!!!\n\n \n\n \n\n- Tao ln b gin v mm my gi. Im i !\n\n \n\nMnh ch thiu nc khc thi. Cha bao gi thy thng con trai no khn nh mnh lc ny.\n\n \n\n- nh kia ? My chi g c vy ?\n\n \n\nNh vn m bng ci. Mnh khng th ngh ch vi pht trc tim mnh cn p tng tng v nh. Mt gi nai d s. Nhn nh ci nhe ht c hm rng nanh ra, gh gai ngi.\n\n \n\n- Thch vy ! c khng ?\n\n- \n\n- Th th ng m ng ny tr o nha.\n\n- \n\n- y mua hai ci mi ri. C gi lm ca tin trong nh i nha.\n\n \n\nMnh ! Thua tp th my ri mnh cng khng bit na. Vut vut vai vi lng xem cn con gin no khng. Hm hc bc bi. Ch mun x xc nh cho b ght.\n\n \n\n- Ny nha. Mi sng ng ny sa xe cho ng y nha. V n vy h ?\n\n- Tng ch hng tht h ? Ch la ku.\n\n \n\nNi chuyn vi nh ny lc na chc u mnh phi c ra mt. Mnh vo phng ng ca ci RM. Nm vt ra ging m vn nghe thy iu ci ca nh vang vang ngoi ca s.\nC7.\n\nNy gi m gi mnh m c ln. Mnh va l mt ra khi nh tm m qut mt chng hi mnh lm g trong nh tm lu vy. Mnh mu mo, mt nhn nh qu to tu ngm ru.\n\n \n\n- D con tm !\n\n- C nm c thy my tm u ? Hm nay d qu\n\n- Con b gin chui vo ngi.\n\n- Th th t nay ngy no tao cng bt gin nht v ngi my\n\n \n\nTrn i ny c hai ngi n b c nht th gian. L nh hng xm v m mnh. Vo ging trm chn kn u. Chn vn au but, mi vn sng tm. Hm nay li c l gin lm mn ht ngi. Mnh phi ni l thn tn ma di. Con nh ny c nha. Mnh khng tr th thin h khinh mnh. i tha nh ai mt thng con trai lm tr nh mnh b mt con nh km 2 tui troll kiu.\n\n \n\nC ngy i hc khng nghe c g lt tai. Ch mi kim cch tr th nh. C ngh n cm gic l gin b quanh mnh l c mnh li si lc bc. Nhng tr th kiu g y h tri. Nng trong ngi.\n\n \n\nChiu rong xe v. V hi sm nn ng khng tc. Ti gn nh, ang lm nhm ht lng mn t th nghe ting nh gi tru tro ng sau.\n\n \n\n- Hong i, i Vi vi !\n\n \n\nLi tr g y tri ? Li cn xng Hong vi Vi ? Nghe rn tn chn lng.\n\n \n\n- Cho Vi i cng nha. 1 on thi.\n\n \n\n- Khng cho. u thiu ng. Trnh xa tao ra\n\n \n\n- Tui ang b 2 tn d hi bm theo. Cha tay ra gip ngi ta i m !\n\n \n\nNgonh li thy hai tn u tru mt nga ga r r ngay gn mt on. Nhn cng khng nguy him lm, chc thy nh d thng bm theo lm quen xin s. Ch ng ny khng vng ngi bn ny khng dm cp ca hay hp dim gia ban ngy u. Quay ra nh, nh nhe rng ra ci. Trng pht ght. Mnh r ga i trc. B mc nh trn mt ng sau. Ln ny th cht vi anh nha nhc.\n\n \n\nV n nh, vt cp xung bn, ngi thn ra gh. Cng thy lo lo. mc nh cho hai thng mt ln, ngh cng gh. Nh u nh c mnh h g mnh cng n nn c i. Nhng nh ny cng lm tr, khng bit c thot thn c khng na. *Vng tc*. V gi m my ngy nay au ht u. Cn u nhng thng ngy yn hng th ca mnh ngy xa. Cuc sng ca mnh vn d m nh mt cun len. T nhin nh xut hin nh con mo in, v ri tung cun len y ln. Gi lm sao ngi cun li t u by gi ?\n\n \n\nRa cng hng, vn cha thy nh v. Chng nh li phng xe ra xem ? D ngi ! Nh th nh chi in. M hm nay li lm nh ln cn ri. Khng bit ti v c b nh hnh khng. i ti i lui vn cha thy nh hin hnh. Mu trong ngi mnh chun b si ln ri y.\n\n \n\nKia ri. Nh lng thng ng xa. a sao xe c khng i m dt b kia ? Mnh nu cng nhn nh. Thy c li qu. Cht nh n sng qua nhn thy nh khc lc mnh sa xe. L khc tht ch khng phi gi b g. Mt pht duy nht trong ngy thy nh b bng yu t. Gi mt pht y li quay li. Nh b nh mt con mo, con mo ny tm thi khng in. Nhn nh i chm chm di bao nhiu l l rng, tim mnh cng rng tng mng ri. Mong nh nhanh v ti cng ch tim mnh rng ht ly g m th.\n\n \n\nNh m cng i vo. Coi b b hai thng mt ln kia chn ng xe ri. Tay o bn ton t. ang ngha nh, t nhin nh quay ra lm mnh. Mnh git ngi ng thng ln, u cng v thnh tng au nt c. Nh lm mnh lu lm. Mnh lc u khng bit lm g, ng xoa u nhn nh. G ch ? Hi mnh tng y v, gi mnh p li mt ln m nhn nhau th hn th ? V l ! Nh dng xe i vo nh. B mc mnh ngoi sn, ng di bao nhiu l l dng.\n\n \n\n....\n\nC8.\n\nTi nay n cm chng ngon g ht. Tru tro nhai cm nh nhai vi. M quay sang k chuyn\n\n \n\n- Qua Hong ng say th ? Khng nghe ting g ?\n\n \n\n- Ting g m ?\n\n \n\n- Chng c Thi v gy s. Say kht. nh p hai m con n n kh.\n\n \n\n- D...\n\n \n\n- D thng mt dy n k n li hn mi khng c. m... C nh b my.. chnh ra li hay...\n\n \n\nMnh c khng nut c thm g lun. Ung xong hp nc canh, cho m i ln phng. Ngi online chng thy nh u. Mnh hi hn v chuyn hm qua qu. Mnh ng l thng n b. i chp a con gi, khng bit ngng.\n\n \n\nNg ng ra ban cng cng khng thy phng nh bt in. Mnh lang thang ln sn thng ht th.\n\n \n\nNh ang ngi trn . Ngi thu lu mt gc, tay b gi, gc mt xung. Chc ang khc ri. Mnh ng ngi ca sn thng. ng khng c m ngi cng khng xong. Thnh ra mnh va ng va ngi, c khum khum nh i nh rm.\n\n \n\nMnh chng bit lm g. Mi bc tc, k hoch tr th t nhin v ht. Con mo in khng v cun len na. Nhng n ... t ri ! Mnh mun lm g , nhng li khng bit lm g.\n\n \n\nB xung phng nm 1 lc. Thy ngoi ban cng c ting ng. Mnh bt dy d dm ra xem. M ca ra thy nh ngi ung a chn, vt vo trn lan can. Khc xong ri ? Thay i nhanh vy tri ?\n\n \n\n- , ngi ng ln c cht th sao ?\n\n \n\nNh quay sang nhn mnh. Mt hoe. Nhng ming th ci ci.\n\n \n\n- m qua s qu khng dm ly o vo h ?\n\n \n\n- Khng thm ! Vt lun !\n\n \n\n- y git cho ri. Cm vo ct i.\n\n \n\nNhn thy ci o gp gn m mnh mun khc tht. Nh ny li nh quy g na y.\n\n \n\n- ny, y khng a u nha. Chi c va thi. Trong o li c thn ln rn rt ch g ?\n\n \n\nNh c ci mnh hoi. R o ra phy phy cho mnh xem.\n\n \n\n- C cn y mc th cho lun khng ?\n\n \n\n- Thi khi, tng lun y ! Thy n l k c hi hng li a v c c. Ai dm mc.\n\n \n\n- Cho y lun y h ?\n\n \n\n- \n\n \n\n- May qu, t nhin c ci gi lau !\n\n \n\nNh ci. Ting ci vang vang trong gi. Mnh vn cnh gic ng xa xa nh. Nh ny nguy him lm. ng gn th no cng b quy nh chi. Nh hm nay cng khng ni chuyn nhiu. Ging cn ngt ngt do khc. Mun hi xem hm qua b ng xe c lm sao khng m li cng . Mun xin li mt cu m tc c. Khng ni ni.\n\n \n\nNh khng nhn mnh na. C ngi ung a chn, nhn ra xa. Mnh t nhin thnh ra tha thi. ng y hi v duyn, vo phng cng khng tin. nh gh mng ngi lan can nh mnh, tay dt my ngn tng vi pht ph trc mt.\n\n \n\n- Tng Vi sp n ri !\n\n \n\n- H ? .. ... M t sau nh ni g th ho mt ci. Git mnh t ng\n\n \n\n- Ha ha. Nht cht !\n\n \n\nLi im mt lc. Nh ny hi. Ni th ni mt chng i xem no. Thi thong tht ra cu ri im bt.\n\n- ng y c sch hc tt Ton khng ?\n\n \n\n- Khng ! y ch c sch hc tt mn Cho c thi.\n\n \n\nNh li ci. Mnh cng ci. Ln ny khng im theo nh na. Mnh t ng bt chuyn tip\n\n- Sao ? Hi sch lm g ?\n\n- Mai nhiu bi tp. Nay khng hng lm\n\n phi ri ! Tm trng khng tt ly u hng hc.\n\n- Th a y lm cho !\n\n- Lm c khng ?\n\n- Ny ! 2 nm trc y cng hc lp 11 .\n\n- By t !\n\nNh i vo phng, m ra tp v a cho mnh. Mnh cm tp v, ang nh i vo phng th nh ra, vi t tp v ln thnh lan can, ht hong hi :\n\n- Ny, trong m c nht gin l tui x v lun nha !\n\n \n\nNh li ci ri. m bng ci ngt ngho. Ti nh thng khng ng gia ban cng nhn nh. m ng v vo phng m lng mt nh c gi. Ch nh p gh. L Hong Vi. ! Tn m ca nh l tn mnh. Hong Vi ! Nghe hay qu !\n\n \n\n***\n\n \n\nB mnh v thm nh vo sng sm. Va vn lc m mnh mi ri khi nh. B vn th. Vn t ni v nh nhng nh hi cn vi hai m con.\n\n \n\n- Hong do ny hc hnh th no ?\n\n \n\n- D c !\n\n \n\n \n\n- L Hong Vi ! Ca ai y con ?\n\n \n\nB nhn tp v trn bn ung nc ri hi mnh. Mnh va nhai bnh m va tr li ly l.\n\n \n\n- Bn con b, nh con ging bi gip.\n\n \n\nB va xp vo t lnh, va hi\n\n \n\n- Con c bn gi ri h ?\n\n \n\n- B hi k vy, khng ng u !\n\n \n\nB ci !\n\n- Khng sao ! Con vui l c.\n\n \n\nB cho tin nhng mnh khng cm. B cho mnh. Mnh cng khng tin. T ngy b m ly hn, mnh mong b ng v y na. C thnh thong v, mua cho nh mt vi th, dn d mnh mt vi cu ri i. Ht kh t.\n\n \n\nNh ng i mnh cng. Mnh a tp v cho nh. Tm trng kh chu t khi b v, mnh cng chng mun ni g lun. Dt xe ra cng vn thy nh ng i. Mnh hi gt :\n\n \n\n- Cn chi na ? B mun lm cho c bi ngy mai h ?\n\n \n\n- Khng ! Cho i nh nha ! Nh nhn mnh ci nhn mi.\n\n \n\n- Khng cho! Xe u?\n\n \n\n- Ba trc b ng hng ri!\n\n \n\nNi xong nh tro ln xe mnh lun. Chong! M gh nha! Cn chun b m bo him na. R rng nh lp k hoch chn mnh t trc ri.\n\n \n\no nh nn chy chm chm. Ny nhn thy mt nh vn cn . Chc m qua vn khc. Nh ngi im re ng sau xe. C lc lng ti mc mnh phi ngonh li xem nh cn ngi khng.\n\n \n\n- Nhn g ? Tp trung li xe i\n\n \n\n- , thnh thong ng xem ri cha. C im m thy s.\n\n \n\n \n\nNh li ci. N ci nhn nht. Mnh khng bt chuyn na. mc mnh v nh qun quanh vi nhng suy ngh ca ring mi ngi. Ai ng u pha trc kia, s c nhng cn bo p ti nng ln vai ca mnh v nh.\n\n \n\nNh nng cnh nh ti\n\nCch nhau mt giu mng ti xanh rn\n\nHai ngi sng gia c n\n\nHnh nh nng c ni bun ging ti\nC9.\n\nChiu tan hc, theo li nh dn hi sng, mnh ln qua trng n nh. Trng dng nh ln cn o vy ng phc i ra cng trng xinh qu. Vy m c ti ci vi my thng bn nhn thy ght. Nh tr mt khi thy mnh ng .\n\n \n\n- n y chi vy?\n\n \n\n! Thi g th khng bit?\n\n \n\n- n thm trng c! Sao khng?\n\n \n\n- Khng sao! Thm i, tui v trc y!\n\n \n\nNh ny b khng sao vy? Hay mt tr nh ngn hn? Mi sng i nh mnh, bo mnh chiu n n. M mnh cht d, ngm ngha k nh. Sao lc sng nhn yu t nai nai, chiu gp li hin nguyn hnh con qu hay by tr. Mnh nn n ni nh ln xe hay b mc nh phng v trc? R d hi. ng v con khng rc ri khng t c. Bc c mnh.\n\n \n\nang r r ga n pha nh th nh quay pht li. Mnh p phanh t ng. Trng nh lc d nh mo in sp cn ngi.\n\n \n\n- Thi mi chn lm. o tui v!\n\n \n\nNi xong nh tro ln lun. Ln ny th mnh hi rn rn. Nhn nh nguy him khng ngn t no t xit. Mnh va i va gh. Nh ngi ng sau ng nghing, thnh thong li cn ht. Nh m c tip din nhng trng thi tm l bt thng th ny. Mnh s kin vi c Thi a nh sang Tru Qu iu tr lun.\n\n \n\nVa dng trc cng, nh nhy huch xung xe, i thng v nh, khng thm mt li cm n. Mnh ang ng ngi, nh quay li, mt li nai:\n\n \n\n- , nay li lm bi cho tui nha!\n\n \n\n- Ngh ! Tui khng rnh !\n\n \n\nNh nhn trn ng nhn mnh. Mnh cng hi bc bc ci thi ri. Nhng li nh lc nh m gi ngi khc nh thi. Nhn nh m kh kh ci hp ba cc tng t lc trng v, thy t m nn ht hm hi :\n\n \n\n- Ci hp g kia ?\n\n \n\n- Hp ny ngi nh ng y khng xem c u !\n\n \n\nXong nh b vo nh lun. C qu ! Ch nh li r ga m cng nh nh ? Thi, nh th con bc gi xi ti mnh mt. Hm hc dt xe v nh. T hi dnh ti nh n by gi, ngy no mnh cng lm vo tnh trng bc bi. Mnh th t nay nh c lm g, c khc lc hay bun b g th mnh mc k. Th con gi g u, khng khng thuc cha.\n\n \n\nTi n cm xong, m ln phng. Do ny v nh lng v luyn hng ca mnh trn facebook chm xung t bao gi. Chng c ch g chm gi c. Chn qu li m ra ngoi ban cng.\n\n \n\nCa s phng nh m, n bn ht ra sng mt gc. Chc ang lm bi tp ri. Mnh da vo lan can, dt dt my l tng vi nghch chi. Cht thy ci hp hi chiu nh m kh kh bn b lan can nh nh. Dm du khng cho mnh xem .\n\n \n\nMnh vi tay cm ci hp ba mang v. Xem bn trong c ngc ng chu bu hay b mt g m nh ni ngi nh mnh khng xem c. Tch c ming bng dnh, mnh m hp ra.\n\n \n\nM i, huhuhu. Ng t gh. Gin trong hp b lm ngm khp phng. Phi n gn chc con. Mnh m khng th kp chc ngt m n ra sn. Con qu kia b in sao m bt gin nht vo hp lm g ? Li cn em v nh t trn lan can. Troll mnh ? Con ch.\n\nMnh h ht mt hi ri chy ra ban cng, thy nh ng t bao gi, mnh c qu qut m ln:\n\n \n\n- Con nh kia, c tr c rch sao my chi hoi vy?\n\n \n\n- Tr g? Hp tui y, ng y mang v lm g cn trch tui.\n\n \n\nCng hng lun! T nhin ngu m hp gin v m ra. Thit l r ht bit. Mnh hn con nh thu tri. ang kim chi, quay sang thy nh tro sang ban cng nh mnh.\n\n \n\n- G na ? Hi tui th ny cn cha ?\n\n \n\n- Im mm i, tui bt chng n.\n\n \n\n- ng y i cha in trc i. C khng mi i nui gin.\n\n \n\n- Tui nui hi no ? Tui lm th nghim .\n\n \n\n in. Th nghim con kh. Ti ! p nhng m in. Chi vi nh ch t hi ngi. Mnh lt o vt i ri chy v ngay nh tm. M mnh t ny gi nghe ting ht vi ci c gi mi chy ln.\n\n \n\n- Ci g y Hong ? Lm g trong y y ?\n\n \n\n- Con tm !\n\n \n\n- Nay li d qu\n\n \n\n- M hi nh Vi nh c Thi y. N vt chc con gin vo phng con .\n\n \n\nM mnh ci h h vi nh Vi :\n\n- T mai c chiu chiu my vt gin vo ngi n cho bc. Thng ny c nm n khng tm. Phi th mi tr c.\n\n \n\nMnh x nc tht mnh vo ngi. Khng ch bnh nng lnh chy na. Ch tht ! Ln ny khng tr th nh mnh khng lm ngi lun.\nC10.\n\nLc t mi mi tm thy ci mn trng m gi t hi b m ci nhau. Nhng ly k vt ca m i ph th ny liu c mt dy qu khng ? Thi khi ngh. Ngh nhiu mt u lm, m gi mnh ch mun tr th nh hng xm dng ngi tm qu kia thi.\n\n \n\n- M i cho con mn cy son vi b tc gi\n\n \n\n- Lm g ?\n\n \n\n- Con mn ng kch. Lp con tp vn ngh.\n\n \n\n- Trong ngn ko bn trang im. Tp xong nh mang v khng tao git\n\n \n\nLi ci em ng ln gc. Thi son ny m bi chot m cng phi ht na, ri cn b ra pha nc lm gi mu na, hng l ci chc. Chuyn ny mnh mt vi m ri. B tc gi i xong cn gi c ch son chc nh a bn mua thi mi.\n\n \n\nVa ngi ct mn ra lm o khoc trng, va tng tng ra mt con nh lc m m b mnh da ma, sng rung i. Sau ln ny th ht troll mnh. M cn dm troll na, mnh da tr c hn.\n\nTrang im th ri ng ngm trc gng. Thy gh. Mnh trng cn pht khip. Khng bit c nn trt trt thm t nc son vo ngc lm mu cho khip thm t na khng. M tc gi ca m mt qu, mun lm x trng cho d tng m s khng chi li c th m co u mnh.\n\n \n\nL d xung cu thang, tnh bng da th m xem hiu ng, tm mi chng thy m u. Li l d i ln. Va m ca phng i vo th thy m. M tht ln che tai ri v cho mnh mt pht p mt vo cnh ca. Khi ni ch trn u ton sao l sao.\n\n \n\n- M i, con, con Hong m !\n\n \n\n- Tin s cha my, my nghch tr g mt dy th ?\n\n \n\n- Con ha trang th\n\n \n\n- Lp my nh ng v Ngha a ni dy ? Tin s b my. i ra mt i h tao ci. Lm tao t cht.\n\n \n\nMnh gi xui ngy xo thng en nm i hay sao y. Lm g cng phi b m chm vi nht mi c. Ra mt xong thy m phi sng v ln. M mnh v th th ny, b b l phi.hic. Nhc n l bun thi c rut.\n\n \n\nm m ra ging, chc ng mt gic i n m dy da nh. M ch lo m nay nh khng m ca s th mnh bit da sao gi ? i n m mai th c cht. Ci cc tc ca mnh phi nut ngay khng nght th mt. Thi nm xung , t lc b m v p mt vo tng, u c mnh chong vng, ong ong ln ri.\n\n \n\nKhng ng c, vi in thoi vo facebook. Ln l lc thy nh Vi up stt mi. Qun i hoa , xin li nha !. a xin li g vy ? Xin li v tt mnh, v ly qun i mnh, v th gin vo o mnh, hay v g ? M li nh gi tr g y ? y khng mc by ln na u. Mnh th khng tin nh, khng nng tay vi nh bao gi na. ang suy ngh min man, nh Vi gi mnh ra ban cng. Mnh lc t ly o khoc ra mc, hm nay phi phng k cng. L u ra, thy nh ngi , nhn mnh ci ci. Mnh bit iu ci gian gian ny khng cha ng ci g hay ho, nhng c tin n. Ti gn mi thy mt nh . a, li c chuyn g m khc nh na tri ? Nh a mnh vi ht mai, mnh lch s cng cm hai bin b mm ngm.\n\n \n\n- Ra y lm g ?\n\n- Bun, ra chi\n\n- Gi ngi ta ra y chi ?\n\n- Khng thch 1 mnh th gi.\n\n \n\nXong ri nh nn lun, khng ni thm g na, mnh vn cng nht chuyn, thy nh im cng im re. Lc sau n ht mai thy thm, bm bng khen :\n\n \n\n- mai ngon th !\n\n- , mi tp lm !\n\n- mai g ? V l l !\n\n- Gin, lm bng l gin hm qua bt b hp . Trn gng rang ln, nn trn trn li. Ngon gh ha.\n\n- AAAAAAAAAAAAAAAAAAAAAAA\n\n \n\nTnh dy ! M hi t mt. a ny gi mnh m. Hu hu. May qu l m ch nu tht mnh th t nay ung nc sng qua ngy ch khng n. Ng qua ng h. Mnh ng gn 2 ting ri. Hn 11h cht xu. Chun b k hoch thi.\n\n \n\nChy tt ra ban cng ng sang nh nh. H h. Hm nay ca s m. K hoch ca mnh bc u thun li ri. Mi bc v nh, bng nghe thy ting p ca rm rm, ri c ri long xong. Ting ng ny nghe quen th ? Lm mnh nh hi b vn cn cng hai m con, mi khi ci nhau m u ht ht c, ri long xong, cc chn v bn c nhng mnh thy tinh vo gm bn ch mnh hay ngi b gi np b m. Sao li c nhng ting ng lm mnh thy t ti th ny ?\n\n \n\n- Hong !!! Hong i !!!\n\n \n\nM gi mnh. M l, ting gi nghe thm thit th ? di ng cn c ting m ng gi nhau na. My m thanh qu qui c din ra ln lt lm mnh thy nng bc trong lng qu. Vi chy xung nh. M ht hi tm ly vai mnh.\n\n \n\n- My xem lm th no sang p ca gip c Thi cu con Vi ra vi. Thng Tng say ru v nht con b trong phng ri nh n mt hi. Khng cu n ra th n cht mt.\n\n \n\nCi g vy tri ? Mnh nh b ai cht ct chn tay lun. Mnh ght nh lm. Nhng sao nghe thy nh b b nh m lng mnh au vy tri. Chn tay lung cung chy qua chy li ct lc m khng bit lm g.\n\n \n\n- M i lm no gi ? Sc con o l ny sao ph c ca ?\n\n \n\n- My sang h c Thi ph i, tao i ln cng an phng gi cc bc xung x l.\n\n \n\nTrc khi i m cn ht li cho mnh cu : - Nhanh ln, li con b ra khng n b nh cht !\n\n \n\nHu hu, nh sp cht ri. Mnh mun ko m li, bt m nh lo chuyn vi mnh qu. Mnh nht t b, li cha p ph nh nhau bao gi. Gi p ca ra cng khng lm no li ng Tng ra, nht l ng cn ang say ru. ng in ln ng cn p nh Vi mt pht cht ti th sao ?\n\n \n\nc g b nh th hay bit my. B va to va khe. B s gii quyt xong chuyn t lu ri. Chuyn ca nh Vi lm mnh nh li bao nhiu mnh k c mnh c gng p v vn t lu, gi t nhin bay v chp ni li mt cch lch lc. Ngy b b i mnh mi l thng b con, ch bit ng ca khc nhn bng b khut dn. Ri hng ngy c lm li n vi hc. Sut my nm tri ch bit n trng ri v nh rc u vo sch v vi game. C l khng c b, con ng ca mnh b li cn 1 na. Va i va l c, thnh thong khng gi c thng bng b ng di di, li ng dy chi mi ri i tip. Mi khi xc xt, ch c m lau chn cho, khi nng u c m vnh o p trn mnh vo bng m. Ri dn dn mnh qun mt b. Ri dn dn mnh qun cch yu b. Ngh k mi thy b mnh con tt hn b nh Vi gp vn ln.\n\n \n\nNhc n nh Vi li thy ht hong. Chy ln phng ra ban cng, nhy sang ban cng nh nh nhn vo. Trong khung ca s c mt cnh tng m mnh c mnh khng bao gi nhn thy ln th hai. Nh Vi b dn mt gc, mt m a mu mi v nc mt. Ch Tng c lo o nhng khe nh mt con tru in, ch cm tht lng da qut vo ngi nh tng hi. Tri i, nh lm g ch m ch li nh nh th kia? Ting c Thi vi my ngi p v h ht ngoi ca. Ci ca g dy v cng th kia c p n sng mai cng khng long ra c.\n\n \n\nNhy v nh, tim mnh lc ny sp v mt ri. Cht thy ng mn trng vi tc gi trn ging. Mnh tnh phi dng cch ny thi. B mnh bo nhng thng say ru yu bng va lm. i m tc gi ln u, khoc mn bng nhng vo ngi. Ly cc nc son pha, ch kp trt trt qua mt vi vo c, khng cn kp soi gng na. Mnh phi ra ban cng, li nhy sang nh nh.\n\n \n\nHt mt hi di ly dng cm, mnh ng ca s, dang tay ra, c trn mt nhe rng, ht ht c ln\n\n \n\n- Gaoooooooooooooo!!!!!!!\n\n \n\nu tin l nh Vi nhn thy mnh, v nh ngi gc hng ra ca s. Nh tht ln kinh hi. Ch Tng nhn ra ri ng nga ch nh Vi, mt tht kinh, ct khng cn mt git mu. Nhng vt nc mnh ht ln mt c chy chy v c mnh nga nga m khng dm gi.\n\n \n\nCh Tng ht hong m ngay ca chy tho thn. Mi ngi ngoi ca ui theo ht. Ch cn nh Vi ngi . Nh vn khc, nhn mnh s lm, nhng khng chy c, chc l v au qu khng ng ni. Mnh b b tc gi xung, vut vut mt ri gi\n\n \n\n- Vi i, tui Hong n. ng ca vo i khng b Vi quay li nh tip gi.\n\n \n\nNh tr mt nhn mnh, ng dy cht ca, ri chy ra m ca ban cng cho mnh. Mnh lao vo nn nn vai vi tay nh xem b gy ch no cha. Thy mi th vn cng cng m mnh cng cng theo.hic\n\n \n\n- Sao Vi ngu vy? Khng bit chng c, khng bit nh li ? Bnh thng tt tui nh tui kinh khng lm m. Hm nay n phi thuc lit h?\n\n \n\nBP! Nh tt mnh mt ci nh tri ging. Mnh lo o da vo ca, khng hiu ti sao li b n tt v c.\n\n \n\n- in? Sao tt tui? Tui va cu ng y , khng c cm n cn tt? ng y ng l b khng.\n\n \n\nBP! Mt ci tt na. Nh qu qut lm. Mnh au v cng, nhng khng au mt u. Mnh thy nhc tim, khp mi ni trn c th. Ngy hm nay mi th i vi mnh qu nng n ri. Mnh ght nh, mnh cm th nh. Cha kp ni g. Nh y mnh bn ra ban cng ri ng ca li. Mnh chy ra ca s. nh khuyn nh vi cu cho bnh tm li ch khng phi chi nh, th m nh cng sp ca s nt. Nh tt n ti um. Cn mnh ng bn ngoi, cnh my khm tng vi ang gic nhau n.\n\n \n\nV phng, ng ca, tt in, nm vt ra ging. Mnh c th tng tng nh in cnh nh ngi gc mt khc. Thi th c khc cng c. Min l ng c lm g di dt, ng t t nha nh. D g nh cng ng thng. Mnh cha bao gi b nh au nh nh. M nhn mt nh lc hong s, m a mu vi nc mt m tim mnh li tht li.\n\n \n\nPhng ti en! Bnh thng mnh hay n ng v nht t b. Nhng hm nay mnh mun cng nh tri qua ci m en ti ny. Cn cht sc lc cui cng trong ngy, mnh ln m in thoi vo facebook, inbox cho nh vi tin nhn.\n\n \n\nThi, khc ri ng i nha. Ngy mai tri li ti sng. Tui bit nh li ci nhn mi, kim tr quy tui thui m! Khc nhiu vo nha, khc cho ht au n ti h i nha. Mai cn gp c nhau, tui nguyn cho nh nh p tui c ngy!\n\nCopy Mng X Hi Vn Hc\n\nSNow","language":"notdetect","sentiment":"Mixed","author":"Cólẽnàomìnhxanhau","author_text":"Cólẽnàomìnhxanhau","md5_text":"3cab1e75850273e7faa99e88a50710c4","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758137098240},{"id":"353114322","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100001359451427","authorlink":"PopoNguyen","date":"2013-01-06T13:48:46Z","topic":"Kia K9","topic_text":"Kia K9","text":"Sc ton tp l l ca 1 t in: ly v v gnh vc trch nhim v ngha v, ly v v tr n nh ck. ng ngha ra th v y c khc g 1osin ng ngha k c tr lng. Sao li thy coi thng v kinh tm n th k bit t in,ngh g m khn th.","language":"notdetect","sentiment":"Mixed","author":"PopoNguyen","author_text":"PopoNguyen","md5_text":"4f8c0495c7c7edc155f6a1010fe4ab38","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758150729728},{"id":"353114644","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100001834868308","authorlink":"LêThànhNhân","date":"2013-01-06T13:42:34Z","topic":"Kia K9","topic_text":"Kia K9","text":"V con gi ^^\n\nNghe v nghe ve\nNghe v con gi....\nSut ngy li nhi\nLin khc tnh yu\n ri chiu chiu\nT nm,t by\nAnh ny ht xy\nAnh kia d thng\n\nNghe v nghe ve\nTip v con gi\nT nhn xinh gi\nSut ngy Phn son\nLi th bon bon\nCi mm khng ngh\nNhng c xu x\nVn nhn l xinh\nNgi to chnh nh\nVn cho...Eo p\nNgi m dp lp\nLi bo gi eo\n\nBo i vt bo\nTh ku au c\nBo i nh c\nTh bo au tay\ni chi sut ngy\nLm g bit mt !\n\nc th c st\nVn nhn thng minh\nQua 5 mi tnh\nVn cho l t\n\nNgi nh qu mt\nQu vt khng tha\nQuen ngi la c\nHng ny qun n\nn nhiu to s\nc ch g u\nNi lm au u\nKo thm ni na !!!! \n=))","language":"notdetect","sentiment":"Mixed","author":"LêThànhNhân","author_text":"LêThànhNhân","md5_text":"44110fdee1183e2a5fef14ef8f16c103","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758151778304},{"id":"353114061","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100000211110284","authorlink":"TríTrọng","date":"2013-01-06T14:01:24Z","topic":"Kia K9","topic_text":"Kia K9","text":"Chuyn Thin : \" i Vng Sanh i !!! \"\n\nC v cao Tng, bnh nng lu ngy, coi chng thuyn gim cht no ! V lng y thm mch s, khuyn chng rng:\n- V S bnh lu ngy, sc lc cn kit lm ri ! Cc ng nn nu cho tht, hm xng cho s n vi ngy, may ra mi hi phc li c !\n chng lm theo li dn ca ngi thy thuc ! Qu nhin, vi ngy sau, v cao Tng hi phc sc khe v tnh li !!!\nS hi :\n- Cc con cho ta n ung g m ta thy ngon ming vy ? Li nghe rt khe trong mnh !!!\n chng khng dm giu chuyn, lin k cho S nghe s tht :\n- V Thy qu kit sc nn chng con nghe theo li dn ca v lng y, bng cch nu cho v canh sp tht xng cho thy dng mau lnh bnh v phc hi sc lc ! Khng th chc gi ny Thy ..... vin tch ri !!!\nV Thy nghe vy, qu au bun, than trch :\n- Sao cc con lm vy ? Ta gi gii, trng chay gn c i ngi, hm nay v tnh cc con ph i gii hnh trong sch ca ta ri ! Th l ta cht i cn hn phm ci gii m ta hng gn gi sut i !\nV cao Tng v u ut nn .... tr bnh nng ri .... tch !!!\n\nV k t ngy y, mi m, mi ngi ng cha u thy hnh bng v tng hin v, vo ra .... m hng chu .... thng thit lun !\nPht t v mn u s, mi khi hong hn bung ph v ..... hnh bng ma qui kia c i i li li, khi th trong sn cha, khi th ngoi hng nin ..... !!! H khn vi bao nhiu, hnh bng ma qui cng chng chu .... thng !!!\nChuyn n tai v Thin s ..... nn v Thin s lin tm n ngi cha ny tm thm .... hn ma ny, hi cho .... ra chuyn !!!\nTri va chng vng, v thin s ngi xung trc hin nh hnh thin ! Khong gn na m, thin s cht nghe ting than vng vng t ni cn phng m v s tch trc kia ..... !!! Li than nghe no nng, ai on, cha y mun phin ......\nThin s cht nghe vng vng ting than th no nng:\n\" Lm bnh nm lit ging\nNn phi hp cho xng ....\nV chay lt bt tnh ....\nNn nghip mi .... cn vng \"\n\nThin s chm ri dng k p li:\n\n\" Lm bnh nm ... lit ging\nC vic .... ung ... cho xng,\nTam nghip ng m Tnh\nng Pht vng Ty phng !!! \"\n\nNghe bi k i p, hn v Tng cht t u tr ti, qu xung trc mt v thin s, ci u ly t :\n\" Con rt bit n Ngi khai th cho con ! T y, con thy c ch phi i ca mnh ri ! \"\nHn v Tng ni xong lin bin mt ! V Thin s khoan thai ng dy, chm ri bc ra khi cng cha !\nCng k t , khng cn ai nghe hay thy hn ma ca v Tng hin v mi m !!!","language":"notdetect","sentiment":"Mixed","author":"TríTrọng","author_text":"TríTrọng","md5_text":"decc2b547273d8cd0f5e1b8593b89c79","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758153875456},{"id":"353114650","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100000269828945","authorlink":"HươngẾch","date":"2013-01-06T13:35:00Z","topic":"Kia K9","topic_text":"Kia K9","text":"1 ngy hi mt nhng cc k vui:xxxx rt cm n cc anh ch v cc bn qua ng h gian hng ca lp em/t 1 cch t nguyn hay hi b p buc mt cch thoi mi h h:\"> v cng cm n cc bn Bo Him thn yu cng mnh lm u bp kim bn hng kim qung co cho ko khch du lch ngy hm nay h h:dddd cng k th qun gi li cm n chn thnh nht n thy Anh Tuan ga lng ng h rt nhit tnh lp e <3 <3 <3 chc mn khng b khn c nh^^","language":"notdetect","sentiment":"Mixed","author":"HươngẾch","author_text":"HươngẾch","md5_text":"fe274f0838043a3e6f1e02d881d75682","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758298578944},{"id":"353114166","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100002837937364","authorlink":"NguyễnHương","date":"2013-01-06T13:57:51Z","topic":"Kia K9","topic_text":"Kia K9","text":"Ln ny mnh s ch...........ko vi vng na!","language":"notdetect","sentiment":"Mixed","author":"NguyễnHương","author_text":"NguyễnHương","md5_text":"0964d8bdff81e0ab64b1065743e8d154","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758299627520},{"id":"353114803","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003985628988","authorlink":"NguyễnXuânHòa","date":"2013-01-06T13:34:09Z","topic":"Kia K9","topic_text":"Kia K9","text":"Phai lam th nao","language":"notdetect","sentiment":"Mixed","author":"NguyễnXuânHòa","author_text":"NguyễnXuânHòa","md5_text":"18c7a937a642788267e64bb26c4e34c0","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758300676096},{"id":"353114452","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004393961240","authorlink":"MèoMun","date":"2013-01-06T13:43:50Z","topic":"Kia K9","topic_text":"Kia K9","text":"Ngy xa c i v chng n c mt cu con trai 12 tui v mt con la nh. Mt hm h quyt nh i chu du thin h xem nhn tnh th thi.\n\nKhi i qua mt lng u tin h nghe thy nhng ngi y th thm: \"Xem thng b trn lng la ka, ng l th khng c dy bo n ni n chn Ai li ngi th khi cha m phi li b bn cnh.\"\nNghe vy ngi v lin ni vi chng: \"Khng th h ni xu v con mnh nh vy c\".\nNgi chng bn nhc cu b xung v nhy ln lng la ngi.\nKhi qua xm th hai h li nghe mi ngi y x xm: \"Xem ka, thng chng kia qu l khng bit xu h, khe mnh th m li ngi trn lng la v v con i b.\"\nAnh chng lin nhy xung khi lng la v ch v ngi ln. Hai cha con i bn cnh.\nQua xm th ba h li nghe thy ngi ta x xm: \"Ti nghip anh chng, lm lng vt v c ngy kim cm o v cho gia nh li phi i b, cn xem con v ka! C thng con na, ng l v phc mi c c b m nh vy.\"\nNghe vy c ba quyt nh tt c cng ngi ln lng la ri i tip.\nKhi i qua mt xm na h nghe thy mi ngi ni vi nhau: \"ng l l v cm, c c chng khc th vt. Ba ngi ngi trn lng co vt nh nhn th kia th n gy lng mt ch.\"\nNghe vy ba ngi lin tt khi lng la v i bn cnh con vt.\nn xm tip theo mi ngi cm thy khng th tin vo tai mnh na khi nghe thy ngi dn y ci nho bng: \"Nhn ka, ng l l ngu. C ba lch thch i b trong khi con con la chng c g trn lng.\"\n\nKt lun: ngi ta s lun lun tm cch ch trch bn, nho bng bn v hon ton khng n gin tm c mt ngi chp nhn bn nh bn vn c.\n\nCho nn: hy sng nh bn cm thy l ng n v hy i n nhng min m tri tim bn ch li\n\n\"Cuc sng nh mt mn kch khng c phn tp dt trc. Bi vy: Hy ht ca, nhy ma v yu mi mt giy pht ca cuc i bn trc khi v kch h mn khng mt ting v tay\"","language":"notdetect","sentiment":"Mixed","author":"MèoMun","author_text":"MèoMun","md5_text":"82c7f68c304fabf337ea8ab279ea6297","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758301724672},{"id":"353114907","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100001568604880","authorlink":"NguyễnHảiBình","date":"2013-01-06T13:28:01Z","topic":"Kia K9","topic_text":"Kia K9","text":"Long An Long An Long An...........................ng nm m cng thy, ln tp ri cng cn thy, khi no mi ht m t y,gruuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu","language":"notdetect","sentiment":"Mixed","author":"NguyễnHảiBình","author_text":"NguyễnHảiBình","md5_text":"64e297227463b6d5b23f7c67650924ee","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758302773248},{"id":"353114284","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100002574373387","authorlink":"ShOnlyone","date":"2013-01-06T13:50:05Z","topic":"Kia K9","topic_text":"Kia K9","text":"- Thng ngu v thng u, e chn ai?\n- Ng chy theo em v ng e chy theo, em yu ai?(t y ng ma t chay theo)\n\nC L BN ANG MI CHY THEO 1 BNG HNH MI MI KHNG THUC V MNH , V ANG NH MT 1 CON NGOI HIN HU SAU LNG\nKhi nhng ht ma ma ng ang vi v ri , gieo ci but lnh n tng ni trn th gian ny , trn ng nhng i tnh nhn ang tm ni tr n . N vn lm li , lng im di theo 1 bng hnh pha trc , nhng ht ma vn l ch ri vo mt n , qun i ci but lnh ca to ha , mt n vn ang rng rng nhng git noc mt mn ng . Pha trc nh vn ang khc , nhng git nc mt nh ang v thc ri ,ha cng cn ma kia , gia khong khng bao la , gia nhng ht ma ri u , nc mt nh tr nn qu nh b , bc nhng bc chn v hn nh ang c tm v 1 ni v nh , qut ngang nhng git nc mt ang gin gia trn mi , nh kh nhch ming coi chn nn .\n\n_ anh lm n ng i theo ti na c c khng ? ( nh ni )\n\n_ xin li anh khng th !\n\n_ ti sao anh phi lm vy ?\n\n_ ti v anh yu em!\n\n_ ngu ngc anh c cm thy tic nui 2 nm qua mi theo ui 1 ngoi con gi nh ti khng ?\n\n_ khng !\n\n_ mc d bit ti c ngoi yu ???\n\n_ phi .. tnh yu m em , mnh khng th bt tri tim mnh lm khc uc !\n\nNh li tip tc buc , li ha mnh vo cn ma , nguc mt ln nhn tri nh nh ang c che i ci ni au gin x tm cang mnh t t 1 cu hi cho chnh bn thn \" ti sao ngoi ti yu li khng yu ti , cn ngoi ..\" hj, nh nh coi ngoi nhn n pha sau , nhn thng vo i mt bng t lu , noc mt nh vn khng ngng ri li tip tc bc nhng vng nc ly li vng vi khp mt ung , b nhng i chn n v nh lm v tan \n\n_ ti nh ti ri anh c th v!\n\n_ uhm , anh bit ri , em nh gi sc khe!\n\nKhng 1 li , khng 1 ng tc ngoi nhn , nh ng sm cnh ca n lng nhn , ri buc nhng boc git li , mt khng ri v pha cn nh nh , n n 1 n coi mn nguyn . Lng bc \n\n2h sng , nh vn ang dn mt vo ci mn hnh sng trng trn ci my vi tnh , di nhn theo 1 ci nick ang sng , di c theo nhng dng status ca 1 ai , khng ginh cho nh , chc chc nh li ci mt bt khc Sn , ngoi con trai nh yu mi ri xa nh chy theo 1 nim vui khc , nghe nh but ng t trong tng nhp th , nh vn cha tin v cha bao gi chp nhn ci tht ti au xt , nhn nhng dng yu thung ca Sn ginh cho ngoi con gi khc nh li khc 2 nm yu nhau gi kt thc nh vy sao ???\n\n_\" buzz\" em cha ng , em phi gi gn sc khe ch , ng ngi online sut nh vy khng tt u !\n\n_ ng lm phin ti !\n\n_ anh .. uhm anh s thc cng em vy \n\nN li di theo nh qua ci nick yahoo vn lng im ch mong lm nh vi i ci ni au hin ti , nh u bit khi nh ang ri nhng git noc mt cho 1 ngoi khc l lc n ang c gng nut tri nhng git nc mt chy nguc vo tim mnh v nh .. c l ng tri lun th .. lun thch to ra nhng bi kch cho cuc i ny .. ti sao n li yu nh ch , yu ngay ci ln u tin gp g , ti sao n li phi au kh v 1 ngoi con gi mi mi khng thuc v n.\n\nSau cn ma tri li sng , 1 m tri qua vi nh tht di , nuc mt ginh cho m hm qua nay gn nh cn , nh un o thn hnh nh mun tm 1 cht thoi mi , m toang cnh ca , nh n ly nhng tia nng u tin ca nh ban mai , bt cht nh thy 1 chic hp oc gi xinh xn t truc ca , m vi n ra , bn trong l 1 i bao tay nh nhn .. cha hiu chuyn g .. nhng nh vn em n vo nh v ct gi . Hm nay nhn nh kh hn ngy hm qua rt nhiu , nh bit coi vi nhng s vt xung quanh , d ch l nhng n coi gung . Din cho mnh 1 chic vy tht p , chn cho mnh 1 gc ngi khut trong 1 qun c ph yn tnh , nh ang c tm li cuc sng ca ring mnh \n\n_ em c cn anh ngi cnh tr chuyn khng ?\n\n_ li l anh , sao anh bit ti y .\n\n_ khng l do anh i theo em t sng n gi !\n\n_ anh ng xem ti nh 1 a con nt oc khng ? ( nh gt )\n\n_ anh\n\n_ v anh cng ng gh l ti s chn anh khi ti chia tay , n ng cc anh .. khng ai tt c khng ai n vi ph n chng ti m khng c mc ch ! Tnh dc ng khng anh m i \n\nNh ng pht dy , b i , li n vi ni au dng tro , trong mt nh n l ngoi n ng t n th sao ?\n\nK t ngy hm .. cuc sng ca nh tr nn bnh lng hn , pha sau nh khng cn n di theo na , nh ngh trong u , chc l n b nh lt ty ri , nn khng dm vc mt ti gp nh na , nh li nhch mp coi \" n ng lun th\" hng m nh vn tip tc di theo ci nick ca Sn , dung nh l 1 cng vic quen thuc vi nh , d bit l nhng ln nh th tim nh s li r mu \n\n_\" buzz\" em khe khng? .\n\nSn ang pm n ???\n\nThc hay l m y nhn vo dng status trn nick Sn , nh khng cn thy nhng li yu thung dnh cho ngoi con gi kia na , thay vo ch vn vn \" Th anh ch em !\" l tn nh .. ng ngng bng hong .. v xen ln ci nim vui sung ci cm gic m lu ri nh khng c tn hung i bn tay nh nhn .. run run \n\n_ em khe . Cn anh ?\n\n_ anh th khng c khe .. hjhj nhng by gi th anh li ang rt nh em \n\n_ anh ni vy l sao ?\n\n_ anh sp phi phu thut thay tim ri by gi anh mi tht s nui tic khong thi gian qua b ri em em c th cho anh 1 c hi c khng ?\n\n_ anh anh phi thay tim ??? anh b bnh tim ?\n\n_ h ng ri ca phu thut rt quan trng vi anh \n\n_ em s n cng anh ngay by gi !!! i em nh.\n\nMc vi 1 chic o khoc nh lt i trong m \" kinh koong\" nh bm chung nh Sn inh i , cnh ca uc m , truc mt nh l Sn , lu khng gp, nay bao yu thung li tr v , ko vi tay nh vo nh . Sn t ln mi nh 1 n hn , tay Sn nh ang c tm n nhng phn da tht ca nh h n i \n\n_ em vn nh nga no vn quyn r anh mi ln bn nhau \n\n_ by gi th anh c th ni cho em nghe uc khng bnh ca anh nh th no ?\n\n_ anh b bnh tim giai an cui ri .. may m c 1 ngoi hin tim cho anh na thng na anh s phu thut v anh tht s cn em lc ny bi anh nhn ra .. ngoi anh yu chnh l em \n\n_ nh kh coi \" em s bn anh mi mi\" h li lao vo nhau nh ang c tm kim thin ung \n\nNgy hm sau , Sn dt nh n nhng ni ngy xa thung n , nh nh cht i sng li , ci cm gic bnh yn v hnh phc ngy no gi li cht a v , n cn mn nng thm thit hn truc , Sn m nh t pha sau , hn ln m nh nh nhng , m du v dng tro , nh xoay ngoi m cht ly Sn : \" anh ng xa e na nh\".\n\n_ anh ha n , s khng bao gi xa em na , anh yu em !\n\nSn t ln mi nh 1 n hn thm thit . nh v Sn qun ly nhau nhng ngy lin tc sau , nhng g c th lm nh thch v coi Sn u c gng thc hin , cha bao gi nh cm thy hnh phc nh by gi , nh nghe tri tim mnh nh ang ha chung nhp p cng Sn \n\n_ em n , em c thch ci o khac ny khng , anh mua tng em y .\n\n_ em thch nhng sao anh li tng em o khoc \n\n_ hj, ngc th ng n ri , anh s em lnh \n\n_ khng phi ma ng ny anh bn cnh em sao?\n\n_ d nhin ri nhng c ci o ny v vng tay ca anh na ..em s khng bao gi lnh !\n\nNh tt mt coi hnh phc . Tri ng gi vn thi tng cn se lnh , ci lnh nh mun ng bng mi vt m n i qua , ngi cnh Sn , nh gh cht , ci m t 2 c th lan ta , ci m t chic o khoc Sn mua , v ci m t i bn tay sn chc ca Sn lm nh qun i rng ngoi kia ang l ma ng, ngoi tri tnh lng , nhng chic cy kh tr l nay ang c gng m chi gia ci but , khng c nhng ting chim ru rt ht vui tai , khng c nhng nh nng lung linh cho cuc sng thm tui sng nhng vi nh .. ch cn c Sn th ri cuc sng ca nh y p sc sng ,n cn mnh lit hn nhng chic l non vun mnh kia , nh thy cuc sng qu nhn nhp ri khi tri tim nh c th nghe tng nhp p ca Sn c l nh ang hnh phc .. v nh u bit ci hnh phc y uc nh i nh th no \n\n_ anh mai l anh phi i phu thut ri .\n\n_ uhm .. em yn tm anh s khng sao m \n\n_ ri anh s li v bn em ch .\n\n_ anh ha n ! ngc ca anh.\n\nRi ci ngy khng ai mun cng n , ngi trn chic xe taxi ang lm li , nh li m cht ly Sn , nh ang lo s 1 iu g Sn uc chuyn ln phng m , nh lng im , run ry , tim nh nh mun n tung ra ngoi , c ci g ang bn chn trong nh , ht ng ln ri li ngi xung , sut 6 ting ng h tri qua , m hi nh cng trn trn trn nh gc xung gh v qu mt mi \n\n_ chc mng c chng ti thay tim thnh cng cho ngui yu c \n\nNh git mnh bi 1 ging ni l , ngc nhn ln nh thy l v bc s trc tip phu thut cho Sn , nuc mt v a trong nim vui sng , nh nh 1 a tr , coi vui hn h .\n\n_ tha bc s , ti c th gp anh y c ch \n\n_ bay gi th cha th , anh y cn m man , ngy mai c c th quay li \n\nSut m hm nh khng ng , nh mong tri mau sng , mong ci thi gian chng qua nh c gp Sn , tri tim nh khng cn lo s . Mn m bung xung bao ph tt c nh ngi co ro trong phng , ht suy ngh ny li ti suy ngh khc nh iu ginh cho Sn , nh ang nh li nhng thng ny qua , nh bt coi sung sung v nh bit ci hh phc s cn theo nh ch ngy mai na thi ngy mai na thi .\n\n_anh Sn i , emn ri n!\n\nNh ht ln nh qun mt y l bnh vin , l ni cn s yn tnh, v ri ci khng kh yn tnh cng oc nh tr li . Truc mt nh Sn ang oc 1 c gi chm sc .. tng ming n tng ngm noc tay chn qu qung nh da hn thn mnh vo bc tng gn nht mt nh nhe cay ming nh lp bp\n\n_ Anh Sn .\n\n_ em n ri .\n\n_ c gi ny l ???\n\n_ l bn gi ca anh .\n\n_ cn em .\n\n_ hj em sao vy th em cng l bn gi ca anh nhng ch na thng troc thi .\n\n_ anh l \n\n_ em tht l phin phc v em m anh phi xa ngoi yu ca anh na thng xem nh na thng qua em n b cho anh . Vy l khng ai n ai .\n\n_ anh . Anh vy anh tm v ti lm g .\n\n_ lm g . thc hin li ha cho 1 thng ngu thng ngu mun anh lm cho em hnh phc nhng tic l n khng ni khong thi gian bao lu nn anh ngh na thng l qu nhiu vi em ri kaka ( Sn coi ln, em theo l ci nheo mt ca c gi ngi cnh Sn )\n\nTai nh , chn nh ng khng vng , .. gc ..\n\n_ li ha thng ngu . L sao ?\n\nNh ni trong din di .\n\n_ ny em t coi i !\n\nSn thy vi 1 sp h s v pha nh .\n\n*** ***\n\nH S BNH N\n\nNgi hin tim : Trn Cao Nhn\n\nNgoi nhn tim : Dan Nam Sn\n\nCa phu thut thnh cng .\n\n*** ***\n\nNh nh ang ri gia tng khng , ngoi hin tim cho Sn li l n ngoi con trai m thm lng l di theo nh sut hn 2 nm qua .. \"au\". Nh go khc trong v vng tay bu cht chic o khoc nh mun x toang n ra \n\n_ ny ny em ng lm rch o thng ngu n tng em ch d sao anh cng c cng chuyn gip dm em cn na i bao tay trc nh na .. ton b l n mua cho em y thng ny hay o n bit th no em cng lnh nn mua y \n\nGing Sn dt km theo ting coi khanh khch , nh ang mun tru tc nh \n\n_ anh anh l u.\n\n_ hihi i m em , anh khng u th thng khc cng u . Trn i ch c 1 thng ngu thi anh khng mun lm thng th 2 u . Kaka\n\nSn coi ln , nh vt chy khi cn phng , nuc mt nh theo tng cn gi nguc chiu thi nguc li pha sau bu cht ly ngc nh ang c kim hm nhng cn au ang dn x , nh vn chy , chy khng ai kp nhn nh khc , chy li pha sau nhng cn au m , chy tm 1 ai c ng ch nh pha truc khng v chy khi nh ngoi nhn cn c \"ai\" di theo nh khng vn v..\n\nNh ng lng cy cu cao nht ca thnh ph .. giang tht rng i cnh tay , nh m trn nhng cn gi ng vo lng , dng sng vn ang sit chy khng bao gi dng li , nhng ngi sao khng ngng nhp nhy t im cho ci bu tri m huyn du nhng i tnh nhn tay trong tay cnh nhau n ch nhng giy pht m m ca m ging sinh an lnh .. u ngoi ta li thy 1 c gi ang giang rng i cnh tay ca mnh , khe mi c ang tun tro nhng git nc mt , i b mi run run , thu i cnh tay li nh sit cht ly chic o khoc, c bung mnh xung dng sng ang chy sit , ai ny iu sng s , ngoi ta vi tay theo ci th xc c ang dn xa khi th gian ngoi ta thy nuc mt c theo nhng cn gi by ngoc ln khng trung , n nh nhng ht tuyt him hoi ca vng nhit i, \"lung linh\" ngoi ta thy mt c nhm nghin , ming c n 1 n coi chua cht , ngoi ta thy tay c sit cht ly chic o khoc nh khng mun ri xa c l l th duy nht trn ci i ny lm c m p .\n\n***\n\nNgy 22 /12\n\nVy l ngy mai anh khng cn c hi gp em na\n\nAnh khng cn c hi oc lng l di bc theo em\n\nKhng oc nhn tht sau i mt lng lnh ca em\n\nKhng cn oc nghe nhng ting em qut mng anh na\n\nAnh bit , anh sinh ra la yu em\n\nCh cn thy em ci toi trong hanh phc th anh mn nguyn ri.\n\nHy vng tri tim anh trong th xc Sn s 1 ln na li c yu em\n\nEm li bnh yn . Ngoi anh yu nht ci i ny......\n\nNhng dng tin nhn cui cng t yahoo n m nh s khng bao gi c oc..\n\nTheo bn n v nh ai l ngoi hnh phc\n\nCc chng trai c bao gi cc bn lng 1 giy suy ngh v cch mnh yu cha ???\n\nC L BN ANG MI CHY THEO 1 BNG HNH MI MI KHNG THUC V MNH , V ANG NH MT 1 CON NGOI HIN HU SAU LNG","language":"notdetect","sentiment":"Mixed","author":"ShOnlyone","author_text":"ShOnlyone","md5_text":"4f385919d555cf74dc0373c276884ea1","dimension":["Vehicle Quality"],"category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758303821824},{"id":"353114560","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004755180549","authorlink":"ChíThanhKute","date":"2013-01-06T13:46:48Z","topic":"Kia K9","topic_text":"Kia K9","text":"like gim minh nha\n\nChng : V yu i anh v ri ny.\nV : Cht ri anh i,mt ht ri.\nChng : Mt ci g c ?\nV : Mt.. mt cimt ht ri.\nChng : Nhng m l ci g c ch ? Hay l em lm mt tin.\nV : Mt tin th cn . Ci ny cn qu hn tin y.\nChng : C ci g m qu hn tin nh ?\nV : Ci m ngi ta gi l ng gi ngn vng y.\nChng : , anh hiu ri. Ch trinh ng gi ngn vng. Th ai b mt trinh?\nV : Em ch cn ai na.\nChng : (Ci) Em m ng y ? Em trao n cho anh t hi no ri,anh vn cn gi trong ti qun y ny. T nhin li m ln,lm anh tng c chuyn g nghim trng.\nV : Em nhm. Ci ny khng gi l mt trinh ,m gi lgi lhp dim.\nChng : Em b hp dim ? Bao gi ? u ? Thng no ? Gi n ra y anh cho n mt trn.\nV : N ch chun t lu ri, ngu g m ngi y i anh v.\nChng : Ci thng ch hn h. Chuyn nh th no em k ngay i. \nV : Lc ny c mt thng trm n m vo nh mnh.\nChng : Ti sao thng trm y li vo c nh mnh ?\nV : Th cng ti anh y.\nChng : sao li l tai anh ?\nV : Th ti anh v mun ch cn sao na? Em ca ri nm xem tivi i anh nhng ng qun lc no khng bit.\nChng : Sao em nh ong th ? ca m li ng qun. M anh i lm ch c i chi u.\nV : Anh th lc no cng i lm. Em chng bit ci cng ty anh th no. Cng ty a bn em, nhn ngy tn th, sp n cn cho ngh c ngy nhn vin c nh on t vi gia nh.\nChng : Thi, em ng ng n ni au ca anh na. Ci ng sp ca anh,bnh thng th qut nhn vin xi xi. Th m n ngy ny th t nhin li bo : Chng ta l mt gia nh. Chng ta phi bn nhau trong gi pht thing ling, trng i ny. Th l bt tt c nhn vin n cng ty n lin hoan vi nhau. Bon anh cng kh lm. n trong nghn ngo v nc mt ch c sung sng g. Hu.. Hu..\nV : Hu Hu Hu\nChng : Mi khc m qun mt c chuyn thng trm. Th lc n m vo nh sao em khng h hon ln ?\nV : Em ng qun c bit g u. T nhin nghe thy ting ng em cht tnh dy,ho ra n va vo gc bn. Em nghe thy n lm bm chi ra : chng c ci cc kh g ng gi li con b n mt qu r au. Th ri em nhn thy trn tay n cm mt con dao bu rt to. Lc em s qu lin ko chn che ci mt\nChng : Tt.\nV : Nhng xui xo l li h mt ci i.\nChng : i gii.\nV : N nghe thy ting st sot lin quay ra pha ging. Nhn thy em ang nm n ku ln: Cng nh an i. Th l n tin n chic ging, lt chn ra v n nhn thy em khng mc ci g trn ngi.\nChng : Th th chiu i thin h ? Tai sao li khng mc g ?\nV : Th chnh anh bo em phi ci khi i ng cho n thong, nh th mi khoa hc m.\nChng : Anh bo em, nhng ch lc no c anh bn cnh thi. Ch bnh thng ai cho php em lm nh vy ?\nV : Th ti em quen ri.\nChng : Th sau mi chuyn din ra th no?\nV : N nhn thy em thch qu nh xng vo. Em lin ht tong ln. \nChng : Tt. Th sau c ai nghe thy v n cu khng ?\nV : Khng Em va mi ht ln c mt ting th n lao ti, mt tay bt ming, tay kia d ngay con dao ang cm trn tay vo c em v do nu cn ht na l c. Cui cng em nh phi im lng.\nChng : Em nm im cho n mun lm g th lm ?\nV : Khng. Nm im l th no? Lc n lt ht v lao v pha em, em lin giy gia chng c quyt lit. Hai bn t xung hu t mt lc thi n tt em my pht. Em mt qu nh nm ph mc cho s phn quyt nh.\nChng : Cht ti ri. Th l xong tht ri. Mt ht khng cn g na ri.\nV : Anh i cha mt ht u, vn cn.\nChng : Cn ci g?\nV : Cng may l em cn thng minh y.\nChng : Thng minh th no?\nV : ng lc n ang chun bEm mi nhanh tr, em bo : khoan khoan, em c mt cch ny cc hay, m bo thch lun.\nChng : Cch g ?\nV : Em mi n dng o ma.\nChng : o ma \nV : L ci o ma bo v bn mi khi tri ma bt cht .\nChng : , hiu ri. Th n c dng khng ?\nV : Ci thng ny ngu lm. Lc em mi n cn hi y l ci g ? Th l sn c ci u ging, em li ngay ra cho n xem. Sau em cn phi mt cng gii thch cho n mi y. no l va thm va hiu quTm li l rt nhiu cng dng.\nChng : N c dng khng?\nV : Anh bit khng, cng may l n t m nn cng thch th dng th. Ch ci loi u ng x ch ny quen lang ch lung tung. N m c bnh tt t ic ri truyn sang cho mnh th coi nh l ht i.\nChng : ng ng. May m v nhanh tr. Em tht l tuyt vi. Th tip theo th no ?\nV : Lc ang d th c ting xe my ca anh. N vi vng bung em ra ri m ng qun o chy bin lun. \nChng : Tri i, vy l ht tht ri. Cn u mt ngi v nt na trong trng.\nV : Anh va khen em thng minh c m.\nChng : y l ti ni th an i mnh thi. Ch lm sao ti c th chp nhn mt ngi v nh c.\nV : Anh c phi li ti em u. \nChng : Nguyn nhn th quan trng g. Ti ch cn bit kt qu l c khng cn l ca ring ti na.\nV : Anh i, anh ng bi quan nh th. Em pht hin ra ci thng trong lc vi vng chy qun chic qun i. Nu mnh giao np n cho cng an, bit u h c th ln ra manh mi m tm c n.\nChng : Sao m ngu th? C nh thng bo cho c thin h bit mnh va b hp dim ?\n(Cm chic qun i) Ci thng ch cht. Tao cho my mt trn.\nTrm : Khoan. nguyn hin trng. Cm s vo hin vt.\nV : Anh i thng trm .\nChng : Th ra l my, thng lo tot. Dm ng vo v ng. ng quyt liu vi my.\nTrm : C gii th vo y (Chia con dao bu ra) a tr tao ci qun ngay.\nV : Anh i em s lm. Thi anh tr ci qun cho n n v i.\nChng : A. Anh bit ri.Em ng s, c bnh tnh nghe anh ni y. Ci qun ny chng ng gi g. Nhng ti sao n phi quay li xin. Chc chn ci qun ny phi rt quan trng.\nV : Ci qun c rich ny th c g m quan trng ? Chng l l hng hiu.\nChng : Khng, ci thng trm kh rch ny ly u ra rin m mua hang hiu. Em dt th. Nu khng c ci qun ny th th no v nh, thng trm ny cng b v n tut xc.\nV : i ng ri. Sao anh thng minh vy?\nChng : Cng cnh n ng vi nhau anh cn l g. My khng s thin h nhng cng bit s v y. th, tao khng lm g c my th tao s x tan ci qun ny, v nh v my s dy cho my mt trn.\nTrm : Khng c x. Nu khng tao s xin cho my mt nht.\nChng : Tao cc s. C gii th my c xin.\nTrm : Thi c. My khng bit s con dao ny th tao cng cho i my ra ng ra khoai lun. My thch v tao dy tao th tao s cho v my dy my.\nV : C chuyn g vy?\nTrm : Em i, em c bit v sao chng em li on ra ngay l do anh cn chic qun khng ? hn chng thng minh g u. Ch v hn cng va i n vng v thi. Ch lm g c ci cng ty no bt lm vic n tn gi ny.\nV : C tht th khng anh.\nChng : Em ng tin thng . N ba ra chuyn ny nhm chia r ni b . ng mc mu n.\nTrm : Anh l mt thng trm rt c lng tm. th khng bao gi ba t hay di tr. Ni c sch, mach c chng. Nu em khng tin th em hy xem ci ny i.\nV : Ci ny l th no?\nTrm : Lc ny khi anh np trong lm cy mc v pht hin thy thiu chic qun i. M y li l chic qun v anh mua tng. Anh nh np trong lm cy nh ch khi no v chng em ng th li ln vo ly qun. ng lc anh thy chng em i v. Hn ng trc cng chnh n li trang phc, lau vt son trn m. Ri anh thy hn lc ti, ly ra mt mu giy ri vo vin vt i. Tnh anh vn t m nh em bit. Vy nn\nChng : Thng trm ch cht. My dm ng vo v tao, gi cn quay li ph hoi hnh phc gia nh tao ? Tao cho my cht.\nTrm : xem ai cht trc ai.\nV : Hai ngi thi i. Anh kia, mu giy vit ci g ? \nTrm : Th tt nhin n phi l ci g khng tt,khng cn thit hoc khng mun cho em bit th hn mi vt i. Nh ? Tao cng mi suy lun ra thi. n ng chng mnh u thng minh m. ng khng ?\nChng : Khng c g u em. hn ba .\nV : Anh a cho ti xem.\nTrm : C a tr cho ti chic qun, ri ti s a c mu giy.\nChng : Khng i no.\nV : Anh a cho em chic qun.\nChng : Th cht ch quyt khng bao gi a.\nTrm : Sp cht n ni m cn kin cng gm.\nV : Cc anh im i. Ti khng cn ci g na c. Cc anh c gi ly m lm k nim. n ng cc anh l l u gi. (Chng) Ti th nh mn mi ch anh, trong khi , anh li dm i n vi gi. Vy m khi v nh, bit v b nh vy, anh chng an i ng vin ti ly na li. Li cn quay ra trch mc khinh mit ti.\nTrm : Qu t.\nV : Cn c anh na. Anh ln vo nh ti n trm, li cn d tr i bi vi ti. M anh cng c v nh y ch. Anh th ngh xem, nu v anh cng nh ti. Gi ny c y cng ang mn mi ch anh v b mt k xu ln vo lm nhc th anh ngh sao?\nTrm : Ti s cho n cht.\nV : Vy th anh hy t kt liu i mnh trc i.\nTrm : Ngu g m lm th ? N ng vo mnh th n cht,ch mnh ng vo ai l quyn ca mnh. ng khng h ng?\nChng : Qu ng. \nTrm : Bt tay nhau mt ci. Ch c n ng l hiu nhau.\nChng : Nhng my ng vo v tao.Tao cho my mt trn.\nTrm : My ng vo qun ca tao. Tao quyt khng tha cho my.\nV : Hai anh c y m nh nhau, ti i y. \nChng : Em i u.\nTrm : Mun th ny ri. \nV : i u cng c. Min l ni khng c nhng ngi n ng ch k, xu xa nh cc anh. Cng lm l cht thi. M cht cng cn sung sng hn sng nhc nh th ny.\nChng : Anh xin em, em ng b anh m cht. \nTrm : ng no hm nay cng l ngy tn th. Nu c cht th tt c s cng cht m ?\nChng : Em i anh bit li ri. Anh cng t thy xu h vi bn thn lm. \nTrm : ng y c . Ch v n ng c s din cao nn khng ni ra thi. Ti cng thy c li vi c v c v ti lm. Bi th ti mi phi xin li bng c ci qun y.\nChng : Em tha li cho anh nh. Bn anh xin th : Khng i lng nhng, khng i linh tinh, khng i b bch","language":"notdetect","sentiment":"Mixed","author":"ChíThanhKute","author_text":"ChíThanhKute","md5_text":"108cf342a07e9ce66e83f5af68093911","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758308016128},{"id":"353114551","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100000178370400","authorlink":"GiangThuHuyen","date":"2013-01-06T13:41:02Z","topic":"Kia K9","topic_text":"Kia K9","text":"hanh phuc i. tam bit nhe :(","language":"notdetect","sentiment":"Negative","author":"GiangThuHuyen","author_text":"GiangThuHuyen","md5_text":"f99e50296292eca51a5ce527a10e1549","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758311161856},{"id":"353114881","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003585004016","authorlink":"CuaBốnCàng","date":"2013-01-06T13:28:19Z","topic":"Kia K9","topic_text":"Kia K9","text":"Tri i!, va i, va mt, va rt run cm cp, ko u li cn b c 1 con airblade thng vo chn..... k gi c 1 ci bia tn l \"ngi yu du\" mnh phi my ci tiu vo mi ht bi ai th ny ko h gii?","language":"notdetect","sentiment":"Mixed","author":"CuaBốnCàng","author_text":"CuaBốnCàng","md5_text":"39763cc06c61ece835a27ad98bd01817","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758312210432},{"id":"353114076","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100002966278587","authorlink":"XuanPham","date":"2013-01-06T13:58:08Z","topic":"Kia K9","topic_text":"Kia K9","text":"-''''''Hnh phc tng nh l bt tn...bng mt ngy thay th mt nim au\n(\".\")","language":"notdetect","sentiment":"Mixed","author":"XuanPham","author_text":"XuanPham","md5_text":"87969245b04a051e70e840c6eb8f7b75","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758313259008},{"id":"353114281","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003614649808","authorlink":"HoàngThu","date":"2013-01-06T13:53:08Z","topic":"Kia K9","topic_text":"Kia K9","text":"nh mi tay mi xong mt cu kim ton :( hu hu hu, c ti liu nhng khng ctrl c vi ctrl v c au tht, Suri Kem my lm my cu sau c kh nng cop py c ","language":"notdetect","sentiment":"Negative","author":"HoàngThu","author_text":"HoàngThu","md5_text":"cd9b24d426e175d83c8cb3de561e2887","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758328987648},{"id":"353114084","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100000199004806","authorlink":"ZymZyn","date":"2013-01-06T13:56:16Z","topic":"Kia K9","topic_text":"Kia K9","text":".Xin li anh khng c tin \n\n- Tng e con gu bng kia i a....\n\n. . . . . . . . . . . . . . . . . .\n- Mua cho e con gu kia i a. Con kia ka...\nVa ni con b va dng ln ch vo con gu to nht c t trong cng. Con gu rt to,phj gn bng mt ngi. Trng cng kh p...\n- A lm g c tin. To lm,chc l t. Sinh vin ngo a kim u ra...\nChng trai ni vi c b v chn chn v xu h na.\n- Mua cho e i a, a chng bgi tg e ci g to tt vy. E thch con gu y. . .\n- Nhg anh ni tht m. A xin li. Sau ny c tin ri a tg e c ko ??\n- Nhg e mun by gj m.\n- ...\n,ri a mua c ko ??\n. . . . . . . . . . . . . . . . . . .\n- A i,chiu nay i chi c ko\n- A bn ri. Khi khc c ko ?\n- . . . . .\n- A i,hm nay i cm tri,a i ko?\n- a ko i u.a c vic\n.... . . . . . . . . . . . . . . .\n- Sao k thg nay a ko i chi vi e. A trnh e h?\n\nCon b tc ti hi...\n- Ko! Anh bn tht.,\n- A bn g? Bn g m lm th? Bn ti ni ko dnh c 30p cho e ?\n- A xin li...\n- Ri a tr hn, tht ha,a ha my ln nhg a chg ti l sao. Em c phi ch i a l sao ?\nEm ko chu c u...A ko cho e c bt c th gj li cn trnh mt e...\nA qu ng lm\nV con b b i ,vi vng.\nB li chng trai ng nh tri trng gia ci ng gay gt ca buj tra gn h...\nTi con b nt : \" e mun chia tay\"\n\" sao th, a xin li e ri m.e gin a \"\n\"e cm thy ht tnh cm ri.ko y a na.vi li e cn nhiu hn nhng g a c th cho e.quyt nh vy i.chia tay\"\n\"uk\". . .\nV chia tay,con b bt u quen ri yu 1 ng khc qua s gii thiu ca bn n.thnh thog n c gp la ng yu c.\nNhg nhg g n lm l quay i v bc tip...\nC ln n nhn ng yu c ca mnh ag nh nhi m hi trog qun cf vo gj g khch nht.\nN t nh : i lm kim tjn . Chc l n tin nh...2 thng.\nN c ngi yu mi c 2 thng,cg g nga vi vic chia tay ng yu c 1 khog thi gian gn nh vy\nMy t c tn\"Em ra ngoi c ko?\"\"\n\" ang ma. A tm gp lm g\"\n\" em ra cng thi. A c th mun a cho e\"\nN vi ci ,ri lt t ra cg...\n- A tg e.Nh a ha. Mn qu cui cg\nN lg ng. L con gu y. Con gu n i mua. Ngi yu n t sg trog ma,nhg con gu th c bc kn li trog giy bg..\n- A ha tg e,khi no a tin,gi a mi c ... Tg mun 1 t. E sg hp nh\nN km kon gu to. Ng trch ko bit v nc ma hay v ki g na.\nN g lg i nhn ng yu c ca n i khut sau nhng git nc ma but t ti.\nTh ra ny n ko i chi vi n v a i lm thm. V a i lm thm l v n,v s ch k,ngu xun ca n.\nNgi yu n bug tay n th y,ko nu gj iu g,ch duy nht 1 li ha vi n.\nAnh s mua tg e khi a c tin...\nN khc...\nGja con ma cui cg m nhg git nc ma kia n bit rg ko bao gj n tm li c na...","language":"notdetect","sentiment":"Mixed","author":"ZymZyn","author_text":"ZymZyn","md5_text":"75d7ac657d2a18a11f36af8f6a022312","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758330036224},{"id":"353114973","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003726429568","authorlink":"TítGà","date":"2013-01-06T13:33:25Z","topic":"Kia K9","topic_text":"Kia K9","text":"c l no................c mng(^_*)..thi vs ch c","language":"notdetect","sentiment":"Mixed","author":"TítGà","author_text":"TítGà","md5_text":"1835a638b128e8d6bfb40da494d644b4","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758332133376},{"id":"353114216","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003940602845","authorlink":"LươngTrương","date":"2013-01-06T13:53:27Z","topic":"Kia K9","topic_text":"Kia K9","text":"V con gi ^^\nNghe v nghe ve\nNghe v con gi....\nSut ngy li nhi\nLin khc tnh yu\n ri chiu chiu\nT nm,t by\nAnh ny ht xy\nAnh kia d thng\nNghe v nghe ve\nTip v con gi\nT nhn xinh gi\nSut ngy Phn son\nLi th bon bon\nCi mm khng ngh\nNhng c xu x\nVn nhn l xinh\nNgi to chnh nh\nVn cho...Eo p\nNgi m dp lp\nLi bo gi eo\nBo i vt bo\nTh ku au c\nBo i nh c\nTh bo au tay\ni chi sut ngy\nLm g bit mt !\nc th c st\nVn nhn thng minh\nQua 5 mi tnh\nVn cho l t\nNgi nh qu mt\nQu vt khng tha\nQuen ngi la c\nHng ny qun n\nn nhiu to s\nc ch g u\nNi lm au u\nKo thm ni na !!!...","language":"notdetect","sentiment":"Mixed","author":"LươngTrương","author_text":"LươngTrương","md5_text":"5184b5f0ca07df3130c838552366ef52","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758333181952},{"id":"353114533","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100002752098761","authorlink":"MaiMinhLuân","date":"2013-01-06T13:45:12Z","topic":"Kia K9","topic_text":"Kia K9","text":"Da bo nhiu thoi gian cong suc ra tap thi toi ngay dien co gang nha moi nguoi (@@)","language":"notdetect","sentiment":"Mixed","author":"MaiMinhLuân","author_text":"MaiMinhLuân","md5_text":"62dedea8a22ebab35da19b51b597ef87","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758334230528},{"id":"353114239","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003128000579","authorlink":"SúnSuSi","date":"2013-01-06T13:50:19Z","topic":"Kia K9","topic_text":"Kia K9","text":"Lnh qu lnh qu.... nh 1 vng tay :(\n Lnh nh ny m mai phi dy sm i thi ri, ch c nh b t ny ch thi t 2 cho n m...\n Nhng thi ta s c. CHC CC TNH YU CA TA NGY MAI THI TT, MAY MN NHE ! <3\np/s: Ai chc t ngy mai thi may mn iiiiiiiiiiiiiiiii >.<","language":"notdetect","sentiment":"Mixed","author":"SúnSuSi","author_text":"SúnSuSi","md5_text":"6787000a95eac740b1d56583a35ee99b","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758335279104},{"id":"353114934","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003784477135","authorlink":"MưaLuna","date":"2013-01-06T13:36:43Z","topic":"Kia K9","topic_text":"Kia K9","text":"ang pun ang ckan aj tan yu lun.hajzzz","language":"notdetect","sentiment":"Mixed","author":"MưaLuna","author_text":"MưaLuna","md5_text":"2cd670db82d435068c4852d5a64d6fd7","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758336327680},{"id":"353114207","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100002596000851","authorlink":"KenvinCuong","date":"2013-01-06T13:53:27Z","topic":"Kia K9","topic_text":"Kia K9","text":"h qu ca 2 ngy banh --> 1 chn bi bng gn, chn cn li bi sng !","language":"notdetect","sentiment":"Mixed","author":"KenvinCuong","author_text":"KenvinCuong","md5_text":"15bde20498f6f48466cfaff6ed3f0200","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758337376256},{"id":"353114884","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004369124409","authorlink":"ThùyDung","date":"2013-01-06T13:28:19Z","topic":"Kia K9","topic_text":"Kia K9","text":"Ti h ri, tp trung no cc tnh yu <3","language":"notdetect","sentiment":"Positive","author":"ThùyDung","author_text":"ThùyDung","md5_text":"18b22320952fa34461234401d82f4008","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758338424832},{"id":"353114150","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004059572557","authorlink":"ThaoGa","date":"2013-01-06T13:53:57Z","topic":"Kia K9","topic_text":"Kia K9","text":"Tri lnh k ngh hc k i u ch n vs ng m cng k bo ln k t no.?Chn........","language":"notdetect","sentiment":"Mixed","author":"ThaoGa","author_text":"ThaoGa","md5_text":"2697d44514f752242fcdfda7892d3ccc","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758338424833},{"id":"353114368","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004291569248","authorlink":"NắngNhạtNhòa","date":"2013-01-06T13:48:16Z","topic":"Kia K9","topic_text":"Kia K9","text":"Maj t j thi...ca nha co aj mun ns j ko ? ... T nghe naj...0_0","language":"notdetect","sentiment":"Mixed","author":"NắngNhạtNhòa","author_text":"NắngNhạtNhòa","md5_text":"b356cd59cc0405c9f7cce086d3706e52","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758377222144},{"id":"353114811","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003760542931","authorlink":"ĐàoBằngĐược","date":"2013-01-06T13:40:04Z","topic":"Kia K9","topic_text":"Kia K9","text":"Em l ai...<3...m lm anh phi khc v yu em...<3....qu nht.!!!","language":"notdetect","sentiment":"Positive","author":"ĐàoBằngĐược","author_text":"ĐàoBằngĐược","md5_text":"63dad8adbdbc0d267eebfdcbe2bf926f","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758378270720},{"id":"353114935","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=261122294004578","authorlink":"Thơtìnhbuồnaihợptâmtrạngthìvào","date":"2013-01-06T13:27:51Z","topic":"Kia K9","topic_text":"Kia K9","text":"V th l....\n\nV th l... hai nm ri y nh\nNgy chia tay em chng ni iu g\nKhi anh hi v sao nh i ng\nNgoi mt cu rng:\"S phn vy, anh i!\"\n\nV th l... ch nh nh th thi\nEm ra i... sau nm nm cht y k nim\nC iu g em cn giu gim\nPha sau ni bun v tnh lm chng gai\n\nV th l... cng l nm th hai\nNgy em nm tay ai... thnh c du nh ngi khc\nHm em i ma ngu bun tan tc\nGi lnh la v, se tht tri tim ci\n\nV th l... khng gp na th thi\nEm tm mua nim vui gia ch i no lon\nCh ring mnh anh... ngi by bn\nnh gi... tim mnh, nh gi... mt nim au!!!\n\nNa bu ru, na ti th\nCng ngng nghnh na giang h nh ai\nThng nm cp na i trai\nTnh kia au cng phai i na phn\n\nCt bi che na ng trn\nC nh m na l thn khng nh.\nTc xanh na nht nha\nQu mnh xa qu na ra qu ngi\n\nGi ma t na cuc i\nRu no say kht na li th non.\nng i na kip cn con\nNa say h ru, na bun cu th \n\nGiang h na tnh, na m\nNa trng vi na cu th ang... chm.","language":"notdetect","sentiment":"Mixed","author":"Thơtìnhbuồnaihợptâmtrạngthìvào","author_text":"Thơtìnhbuồnaihợptâmtrạngthìvào","md5_text":"2fe5b60147ec4c81fa69eb99eb7d8aea","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758379319296},{"id":"353114325","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=112562602433","authorlink":"ArsenalFCinVietNam","date":"2013-01-06T13:55:24Z","topic":"Kia K9","topic_text":"Kia K9","text":"Arsenal va c hng pha pht , nhng Walcott st bng st khng qua c hng ro ca Swansea.","language":"notdetect","sentiment":"Mixed","author":"ArsenalFCinVietNam","author_text":"ArsenalFCinVietNam","md5_text":"0c857dc4790a4b035452aeec688ae3b0","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758380367872},{"id":"353114798","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003174079656","authorlink":"TiểuChiChi","date":"2013-01-06T13:34:15Z","topic":"Kia K9","topic_text":"Kia K9","text":"T dng c mt thng p giai(?!?),cao ku,tc vng cho,trng nh my anh Hn Xng cc k ngoan(nh cn ) mnh sai u lm y,bo i ng k dm sang ty,bo con mo l con ch cng inh ninh l con ch k dm ci :\"> *^^*\ni chi th mnh ngi trn ging nhm nhom n ch ng ch ty bo ly ci ny mc ci kia,qut nt nh m cng toe tot i lm cho mnh cng hay hay vui vui ra pht nh! =))))))~ thng th thng tht nhng cx ng m :\"3 :))\n~> hiu c cm gic ca my b n vng th trong am m ru ha :) :\"3 <3","language":"notdetect","sentiment":"Mixed","author":"TiểuChiChi","author_text":"TiểuChiChi","md5_text":"61edaf76e3451a93bf6765fd6ac87b48","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758381416448},{"id":"353114187","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003478713514","authorlink":"KietKu","date":"2013-01-06T13:50:40Z","topic":"Kia K9","topic_text":"Kia K9","text":"trj uj!, sao lj chn w j th.., .^:!::\"\"\"\"\"\"\"\"","language":"unknown","sentiment":"Mixed","author":"KietKu","author_text":"KietKu","md5_text":"537f716bdc529751d417356dc05ff161","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758382465024},{"id":"353114877","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003587397367","authorlink":"TranTuanDung","date":"2013-01-06T13:30:02Z","topic":"Kia K9","topic_text":"Kia K9","text":" Cu bit khng????\nMi ln ai nhc n tn cu\nT ch bit ci nh\nV c l i\nCi cm gic \n T mong rng !!!\nng ai nhc n cu na!\nBi v.................\nT.........................\nVn cha qun c cu !!!!!! th gii bn kia cu co nh t khng \np/s bun","language":"notdetect","sentiment":"Mixed","author":"TranTuanDung","author_text":"TranTuanDung","md5_text":"86ed771a1945b638b0ffc103405bb766","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758383513600},{"id":"353114887","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003682337944","authorlink":"ThíchChiếnKhông","date":"2013-01-06T13:36:58Z","topic":"Kia K9","topic_text":"Kia K9","text":"o aj inbox t tnh mk nh . . . .","language":"unknown","sentiment":"Mixed","author":"ThíchChiếnKhông","author_text":"ThíchChiếnKhông","md5_text":"ac9051a3f4a6ffd60dd36bb1729a51c7","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758384562176},{"id":"353114344","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100002568630111","authorlink":"LeHong","date":"2013-01-06T13:53:13Z","topic":"Kia K9","topic_text":"Kia K9","text":"Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","language":"unknown","sentiment":"Mixed","author":"LeHong","author_text":"LeHong","md5_text":"3f7335b99ca68466fd9c5f5737cecca1","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758385610752},{"id":"353114262","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=1213725111","authorlink":"PhạmNguyệtThuQuỳnh","date":"2013-01-06T13:49:09Z","topic":"Kia K9","topic_text":"Kia K9","text":"i a R ct ch + tim thuc i v em n trng tc ca m v i hoi k ra :|\ni mua ht Me-O cho 2 con mi + ra ci hnh ang lm profile, photo ny n lm h s nhp hc :|\n\ni v gp c 2 em 1 en 1 trng nm thu lu gn nh Jim Anh. My nh z Tuyet Huynh v k thu hi c, ln lt gi cho ch nh v Ann L mn my, ng c r k ko tr treo vn k c chuyn g xy ra [v vn k mn c my!]. \nTi hi v n ch Tn Bnh th 1 em nhoi khi bal, buc phi tp vo ven ng ci khch sn Bel Ami g nht m vo th gp 1 thng la o/n cp/dt gi/tm gi.. gh st vo gi hi em i ng Trng Chinh ny n, mnh bo n chy ra L Thng Kit m hi n vn ko no hi mnh ang ch ai, mnh gt ' ch ai ht xong quay i n bt u gi ging chi th dn mt: \"m tao ni chuyn ng hong m my lm ci g vy? By gi tao ni ng hong c nghe k?\" [ging ngp ngng nn chc k fi cp SG]. Mnh lic n pht xong t cha kha ko ga ci o. Nhn qua gng thy n k d theo.\n\nv ti nh m thy ci gi mo, k ni nng g m nm ngay cho ci nhn: \"Con ngu ny ni mi vn k nghe thi t b tay vi m ri!\"\n\nD c em dao trong ngi, nhng vi m thc n + h s + gi mo, thm con xe lnh knh na cng nh k. Cng may thng kia n g/di tnh mi ln v i mt mnh!\n\nChuyn cng ch c g, nghe cng 1001 chuyn ri nhng my c gi cng nn nghe thm, cn thn th k tha u! y l mnh, ra ng b quy ri, b cp git, b la o [k thnh v khi chng pht hin mnh ngho v xu nn b qua cho] t hi bit i xe mt mnh ra ng ti gi ri m cn lng khng c th ai bit chuyn g s xy ra..\n\nNi chung thi bui ny i trn ng th lo m i ti ni v cng ng c tin ai ht! :|","language":"notdetect","sentiment":"Mixed","author":"PhạmNguyệtThuQuỳnh","author_text":"PhạmNguyệtThuQuỳnh","md5_text":"7cae98b7cdc581c54fd3cb9363b8afe2","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758386659328},{"id":"353114769","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=179287798876948","authorlink":"CốGượngCườiĐểNgườiTaThấyMìnhKhôngĐau","date":"2013-01-06T13:37:16Z","topic":"Kia K9","topic_text":"Kia K9","text":"Anh i ! Em Cn Lm Mt . . . !!!\n\nNgi con gi mnh m trong em bit lnh khi thiu anh, yu mm khi khng c anh bn cnh.\n\nKhi em bun, khi em mun c khc tht to, em c c anh bn. Em s trt ni bc dc, nhng kh chu m trong lng, em da vo vai anh m khc cho tha, cho cn ni bun, cho vi i nhng m c\n\nEm cn mt im ta, mi lc tm hn em trng rng, chnh vnh, c anh bn, s chia, cho em mt b vai vng chc, trao cho em cm gic an ton v em c th khc ngon lnh trong vng tay y... bnh yn...\n\nEm cn mt im ta, nhng lc tng chng nh em s gc ng trc nhng tht bi, trc sng gi ca cuc i, em c anh, nng em dy. Nm ly bn tay th rp m p y, em bit mnh khng l loi, c c. Anh mang n cho em s du dng, truyn cho em nim tin v hi m. Em bit c mt i tay dt em i bo t ca cuc i.\n\nEm cn mt im ta, sau nhng giy pht c thc hin ti, bon chen vi cuc sng gp gp, x b, c bn anh yn bnh lng ngm chiu hong hn thm, th chn trn trn bi ct mn mng, mc cho sng v v, mc cho gi ht \n\nEm cn mt im ta, nhng khonh khc yu lng, trong chc lt thi mt ai c th lm em i sai ng, em bit mnh vn c anh, , ch i v sn sng giang rng cnh tay n em vo lng. c nng nu, c anh nng bng nhng li c cnh, em thy mnh tht nh b v em bit mnh ang c ch che.\n\nEm cn mt im ta, nhng khi thy nh, lc lng th gii ca mnh, em c anh, in thoi cho anh, nghe ging ni ca anh, lm phin gic ng m anh chng h cu gin, ch ci hin ni chuyn vi em, an i v ru em gic ng m m.\n\nEm cn mt im ta, khi nhng bui nh ma chiu nay, lnh but ton thn, c chi c mt ci m tht cht lng em li m, khng cn s ma gi bo bng ngoi kia. em bit m p trong tri tim anh!\n\n th, em vn c chp, em mnh m, em chng cn ai bn mnh...\n\n th, em bit cch kim ch cm xc, em bit tm nhng th vui cuc sng ca em chng bao gi b nhm chn bi nhng lo toan tm thng nhng x b no nhit ngoi kia\n\n th, em chng phi bun nhiu u. Nu mt ngy kia anh khng cn bn em na, mc cho nhng nn n, mc cho nhng gin hn, mc cho anh nu ko, em bung tay v anh ra i...\n\nNhng ri, c mt lc no , trong khonh khc ngn ngi mt mnh, em li thy mnh c n lc lng. Em s lm g y, khi bn mnh khng c anh, em cng ch l mt c gi yu ui s st ci cm gic c c...\n\nEm tr v nh b v vi bn 4 tng im lng nh t, trng rng trong th gii ca chnh mnh v ri nc n nh mt a tr b b ri\n\nNgi con gi mnh m trong em bit lnh khi thiu anh, yu mm khi khng c anh bn cnh. Vn nh, v yu anh nhiu lm! V em bit \n\nAnh i, em cn mt im ta. Em cn lm mt ngi yu em!\n Lanh ","language":"notdetect","sentiment":"Mixed","author":"CốGượngCườiĐểNgườiTaThấyMìnhKhôngĐau","author_text":"CốGượngCườiĐểNgườiTaThấyMìnhKhôngĐau","md5_text":"429ab30f5ec809f5162e01f16cebc311","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758405533696},{"id":"353114506","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004663350570","authorlink":"NguyenvanVu","date":"2013-01-06T13:41:07Z","topic":"Kia K9","topic_text":"Kia K9","text":"Ti bt khc khi bit mnh thua cuc\n- Vng tay ny khng gi c ai kia\n- Bao ngy qua ti lm tin tng\n- V 1ngi khng phi ca ring ti","language":"notdetect","sentiment":"Mixed","author":"NguyenvanVu","author_text":"NguyenvanVu","md5_text":"b2dfb6d09d5c4a1fa92ff9a3c56e1ea3","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758407630848},{"id":"353114767","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003997290940","authorlink":"KtBuôngTay","date":"2013-01-06T13:37:19Z","topic":"Kia K9","topic_text":"Kia K9","text":"Ni au ny sao kh vt qua qu zy tri!\nNghe n0ns0p v hc th0y c ln no a e like ci ng vin tau i ngy kia tau li thi roi 1 mn tht bi nhc qu","language":"notdetect","sentiment":"Mixed","author":"KtBuôngTay","author_text":"KtBuôngTay","md5_text":"f3e759b7b60e1d7ed70392a9bc33deab","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758408679424},{"id":"353114959","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100001184643124","authorlink":"NhuTran","date":"2013-01-06T13:32:24Z","topic":"Kia K9","topic_text":"Kia K9","text":"KHNG KH SI GN LM CHO NHNG NGI CON XA S PHI NO LNG","language":"notdetect","sentiment":"Mixed","author":"NhuTran","author_text":"NhuTran","md5_text":"7e2db88f0d38b9e596e24797903d6239","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758409728000},{"id":"353114750","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=179271718793765","authorlink":"Nếutráitimtôilạnhhơnmộtchút","date":"2013-01-06T13:39:08Z","topic":"Kia K9","topic_text":"Kia K9","text":"Ny cc chng trai:\n\n- Khi mt c gi tm mi cch c ni chuyn vi bn ...\n\n- Khi mt c gi ni xin li mc d c y chng lm g qu ng c\n\n- Khi mt c gi khc rt nhiu v c y vn cn yu hoc nh bn...\n\n- Khi mt c gi vn c gng bn quay li ...\n\n- Khi mt c gi khng quan tm bn lm tn thng c y bao nhiu ln m vn yu bn ...\n\nTh ... ng bao gi bung tay v c y i ......\n\nV ... c th bn s chng bao gi tm li c mt ngi yu bn nh th ..!!! \n\nMiMi<3","language":"notdetect","sentiment":"Mixed","author":"Nếutráitimtôilạnhhơnmộtchút","author_text":"Nếutráitimtôilạnhhơnmộtchút","md5_text":"60d4fa712b58a9f2b8b4c9a375b2ea39","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758409728001},{"id":"353114273","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100002731612612","authorlink":"HuyềnAnhTrần","date":"2013-01-06T13:50:07Z","topic":"Kia K9","topic_text":"Kia K9","text":"T hay ngh ngi, lo lng, v k t tin vo nhng g t c th lm, k tin tng vo kt qu t c th t c, cng nhiu ln lm ri hay trch mnh v k th ny, k th kia, hi hn v nhng quyt nh bng bt ca m, nhng ln ny th cho d c l bng bt hay bc ng, cho d n c qu kh so vi kh nng ca m th chng m cng nhau c gng hon thnh nh!!!... CHNG TA C NGH CHNG TA KHNG LM C TH S KHNG BAO GI LM C...!","language":"notdetect","sentiment":"Mixed","author":"HuyềnAnhTrần","author_text":"HuyềnAnhTrần","md5_text":"2f8470749df08d88e247a45576b687a6","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758410776576},{"id":"353114581","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100000523264494","authorlink":"NhutPham","date":"2013-01-06T13:37:58Z","topic":"Kia K9","topic_text":"Kia K9","text":"Khi minh hien wa co fai luon bi ng ta an hiep ko!? Minh luon de cao tinh nghia nhung rat de bi loi dung. Minh muon di that xa that xa chon do thi bon chen va day su loi dung nay. Ve LongAn o voi Noi la tot nhat.","language":"notdetect","sentiment":"Mixed","author":"NhutPham","author_text":"NhutPham","md5_text":"8cec6d55e4dcf1e48ebda5176802bb19","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758411825152},{"id":"353114850","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003543974450","authorlink":"KeoNgocBich","date":"2013-01-06T13:36:59Z","topic":"Kia K9","topic_text":"Kia K9","text":"ng Hi V Sao Ti Bun. H h h. Nnh","language":"notdetect","sentiment":"Mixed","author":"KeoNgocBich","author_text":"KeoNgocBich","md5_text":"0c1e65812b43f02960a5598017e26af1","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758412873728},{"id":"353114851","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003510718115","authorlink":"HoangKimGiap","date":"2013-01-06T13:35:14Z","topic":"Kia K9","topic_text":"Kia K9","text":"m p khng phi khi ngi bn ng la, m l bn cnh ngi bn thng yu. m p khng phi khi bn mc mt lc hai, ba o, m l khi bn ng trc gi lnh, t pha sau n c ai khoc ln bn mt tm o. m p khng phi khi bn ni m qu, m l khi c ngi th thm vi bn: C lnh khng?. m p khng phi khi bn dng hai tay xut xoa, m l khi tay ai kia kh nm ly bn tay bn. m p khng phi khi bn i chic m len, m l khi u bn da vo mt b vai tin cy.Nu yu ai xin hy yu bng c con tim c th s ch i ca tri tim yu chn thnh rt mong manh nhng t nht n cng khin tnh yu ca bn khng tr nn v ngha.","language":"notdetect","sentiment":"Mixed","author":"HoangKimGiap","author_text":"HoangKimGiap","md5_text":"45b8f4fd4fb0dee6c9d04ec52b646fee","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758413922304},{"id":"353114315","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003904438536","authorlink":"VìEmLàNắng","date":"2013-01-06T13:51:18Z","topic":"Kia K9","topic_text":"Kia K9","text":"M bo:\n Khi yu ng nhn sc, nhn v hay nhn xe. Hy nhn s c gng ca n. Lc trc b con chng c g trong tay c. V b bit c gng trong lm n v yu thng, m nh cc mt ln cuc i m v cng v m yu b con. V nh thy, b con khng m thua trong cuc nh cc, sau 5 nm, b con dn dn hi c mi th. Hy yu ngi v con m c gng lm mi th, ch ng yu ngi dng tin chiu con.\n\n Con gi h, i, phi dng tin ra c ny mt kia. Nhng trong tnh yu m dng tin ra th gi li lm g xc lng tin. Tnh yu cn th hin bng hnh ng ch khng phi bng li. Yu l phi hy sinh ch khng phi dng tin chim ot. Ngi n ng yu thng con l ngi dt tay con i n hnh phc ch khng phi ngi ni co con bit th no l hnh phc","language":"notdetect","sentiment":"Mixed","author":"VìEmLàNắng","author_text":"VìEmLàNắng","md5_text":"932b5a0cf22fce7265d066b84936830c","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758414970880},{"id":"353114628","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004441782964","authorlink":"QuânLừaTềnh","date":"2013-01-06T13:42:25Z","topic":"Kia K9","topic_text":"Kia K9","text":"C ng lo ang i ti mt cy cu nh\n- N ch c th dnh cho mt ngi duy nht i qua\n- Bn kia cy cu l mt cu trai\n- Va thy ng lo cu lin ht to: ti khng c thi quen nhng ng cho nhng k ngu ngc u lo gi\n- ng lo dng li v ni: ti th lun c thi quen nh vy ..!","language":"notdetect","sentiment":"Mixed","author":"QuânLừaTềnh","author_text":"QuânLừaTềnh","md5_text":"628173f8f1ea48c74b9c1e45a8023b4c","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758421262336},{"id":"353114835","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004408231301","authorlink":"NgọcTrân","date":"2013-01-06T13:38:14Z","topic":"Kia K9","topic_text":"Kia K9","text":"i ch c phi l hnh phc ko....thy iu tht s rt nhm chn...i ng thui....mai bt u 1 tun hc tp mi vs nhu tri nghim cm xc mi....g9 c nh!!!","language":"notdetect","sentiment":"Mixed","author":"NgọcTrân","author_text":"NgọcTrân","md5_text":"1fef3dea171f71bab71b4bcada3a503e","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758422310912},{"id":"353114743","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004741564448","authorlink":"QuangNam","date":"2013-01-06T13:37:26Z","topic":"Kia K9","topic_text":"Kia K9","text":"Anh Hng khng gp thi.......","language":"notdetect","sentiment":"Mixed","author":"QuangNam","author_text":"QuangNam","md5_text":"31c9ea2d670ab6ccf0bb4c360a8c3caa","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758423359488},{"id":"353114666","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004024039288","authorlink":"DuongNguyen","date":"2013-01-06T13:42:20Z","topic":"Kia K9","topic_text":"Kia K9","text":"Troj mat ma moj ng keu lah hj","language":"unknown","sentiment":"Mixed","author":"DuongNguyen","author_text":"DuongNguyen","md5_text":"ab505b029474c58c5529d8b299769d2b","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758424408064},{"id":"353114301","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003579633886","authorlink":"DanhNguyễn","date":"2013-01-06T13:53:05Z","topic":"Kia K9","topic_text":"Kia K9","text":"Q tr li =))nh A7 vs A18 w :x:x:x:x:x:xc ai nh Q k :\">","language":"unknown","sentiment":"Mixed","author":"DanhNguyễn","author_text":"DanhNguyễn","md5_text":"4de2ee7700b334eb9c925c9a88ff6923","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758425456640},{"id":"353114034","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003650645047","authorlink":"TôiLàTôi","date":"2013-01-06T13:56:40Z","topic":"Kia K9","topic_text":"Kia K9","text":"http://mp3.zing.vn/bai-hat/Dia-Nguc-Tran-Gian-Andy-Nguyen/ZWZDACBA.html","language":"unknown","sentiment":"Mixed","author":"TôiLàTôi","author_text":"TôiLàTôi","md5_text":"73d0988d3f978d13c307a3521ea4af0a","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758426505216},{"id":"353114044","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004177121391","authorlink":"LờichiatayAnhnóiHaynhưhátEmtặngAnhpháttátĐẹpnhưphim","date":"2013-01-06T13:54:49Z","topic":"Kia K9","topic_text":"Kia K9","text":"HU QU CA MT CN GIN\n( c ngm.. ai cng nn c 1 ln.. au tht y.. rt ngha )\n* * *\nHu qu ca mt cn ginTrong khi mt ngi n ng ang nh bng chic xe ca ng ta, th a con trai ln 4 tui ca ng ta nht ln mt vin si v v nhiu ng ln ln pha bn kia cnh chic xe ca ng ta. Trong lc gin d, ngi n ng nm ly bn tay ca a con v nh mnh nhiu m khng nhn rng ng ta ang dng mt ci c l vn vt nh.\nKt qu l trong bnh vin, a con trai ca ng ta mt i ht cc ngn tay ca mnh do qu nhiu ch gy. Khi a con trai nhn thy i mt b mnh biu l s au n, a b bn hi: \"B i ! Khi no cc ngn tay ca con mi c th mc tr li ?\" Ngi b cm thy rt au n v khng ni c li no; ng ta tr li chic xe ca mnh v n tht nhiu.\nTrong khi ang b lng tm dn vt v ang ngi i din pha hng ca chic xe , ng ta cht nhn thy nhng vt xc do chnh a con trai ca ng ta v rng: \"B i ! Con yu B nhiu lm !\"\nV mt ngy sau , ngi n ng quyt nh t st\nCn gin v Tnh yu khng bao gi c gii hn, nn xin hy chn Tnh Yu c mt cuc sng ti p v ng yu, v xin hy nh iu ny:\n vt th s dng, cn con ngi th yu thng.\nVn ca th gii ngy nay th ngc li: con ngi th s dng, cn vt th yu thng.\nHy lun c nh nhng ngha ny :\n- Hy cn thn vi nhng ngh ca bn, v bn s ni chng.\n- Hy cn thn vi nhng li ni ca bn, v bn s thc hin chng.\n- Hy cn thn vi nhng hnh ng ca bn, v chng s l thi quen ca bn.\n- Hy cn thn vi nhng thi quen ca bn, v chng s l c tnh ca bn.\n- Hy cn thn vi nhng c tnh ca bn, v chng s quyt nh s mnh ca bn.","language":"notdetect","sentiment":"Positive","author":"LờichiatayAnhnóiHaynhưhátEmtặngAnhpháttátĐẹpnhưphim","author_text":"LờichiatayAnhnóiHaynhưhátEmtặngAnhpháttátĐẹpnhưphim","md5_text":"27de1af92522f1786cf1627d27dceaeb","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758426505217},{"id":"353114391","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100001584148864","authorlink":"PhucNguyen","date":"2013-01-06T13:47:58Z","topic":"Kia K9","topic_text":"Kia K9","text":"\"... Tht kh khn \nLm git nc tht kh khn, gian kh \nTi bo mn cc b \nVa vo , ti t mnh x nh \n\nBng mi hi th \nBng mi t bo, ti nh \nRng ti ang lm vic ca ti \nNgha l ang lm ngi! \n\nV cng bng cch \nTi sng v vinh quang trn i \nD mt ngy kia, gi \nMang ti i khp ni...\"\n\n[ Eduardas Mieelaitis ]","language":"notdetect","sentiment":"Mixed","author":"PhucNguyen","author_text":"PhucNguyen","md5_text":"d9b0b4e4a769e5e34f741c5439da6da8","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758428602368},{"id":"353114349","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=271506896300577","authorlink":"CámơnTìnhyêutôi","date":"2013-01-06T13:48:07Z","topic":"Kia K9","topic_text":"Kia K9","text":"Lm mi hn nhn ca bn\n\n- Thay v th s gim 5 cn hay kim c nhiu tin hn trong nm ti, hy cn nhc n vic dnh thi gian nng cao cht lng tnh cm v chng, bn s thy mnh l ngi hnh phc.\n\n1. Hnh ng vui v, n gin l vui\n\n1. Hnh ng vui v, n gin l vui\n\nMi ngi thng thng ngh rng, cm xc iu khin hnh ng. Song hy ngh mt cp cao hn: Chnh hnh ng mi l k lo li tm trng bn.\n\nNu bn chn lm g cng ng yu, kin nhn, y quan tm th cui cng s to c thi quen lun c tm trng tt, kin nhn, ng yu v bit quan tm nh vy.\n\n2. Ng nhiu hn\n\nMt ng, thiu ng c th hy hoi sc khe v c mi quan h tnh cm ca bn. Gic ng c vai tr quan trng, ngha c bn lin quan n hnh vi. Nu bn khng c ng , hu qu dn n c th l nhng trn ci v v m khi tinh thn tr nn qu mt mi.\n\n3. Gii quyt vn theo cch i bn cng thng\n\nHy n lc cng vi nhau gii quyt mi vn c th xy n, v ng bao gi tranh ci v 3 ch ch c th lm hng hn nhn ca bn ch chng mang li tc dng g hn: Tin bc, Tnh dc v Con ci.\n\nCh mc kt trong vic phn nh ai ng ai sai, hy tp trung vo tm ra gii php gii quyt c vn . Khi mi ngi u cm thy ngi kia c quan tm n li ch ca mnh, th vn c gii quyt khng phi theo cch ca anh, ca ti m l theo cch i bn cng vui v.\n\n4. Bit n nhiu hn\n\nTrong i sng la i, ngha lm nu bn bit by t vi na kia rng bn thc s bit n nhng g h lm cho mnh, d l nh nht. Nu anh y ra bt gip bn, nu anh y khng tic cng t lm mt ba ti tht c bit cho hai ngi, hy by t s cm kch ca bn bng c li ni v hnh ng. Nh th na kia s cm nhn c rng, mi iu h lm, d nh nht vn rt c ngha.\n\n5. i vai\n\nLn ti, nu bn bt u thy nui tic rng mnh tht sai lm trong cuc hn nhn ny th th i vai vi na kia bnh vc anh y xem. Ph n c v s v d a ra chng minh rng ng chng chng bao gi ng chn ng tay gip mnh vic nh, song nu bn ngh li rng anh y c ch nh th no trong chuyn du lch ca nh c, t ng gi hnh l, t phng khch sn, t v v.v. th cu gin s qua i v khng cn cm thy kh chu v nhng iu vn vt trong cuc sng hng ngy na.\n\n6. t tnh cm v chng ln u tin hng u\n\nHn nhin l bn lun ngp nga trong ng danh sch nhng vic phi lm, nhng hy chc chn rng t ra, mnh c thi gian u t cho tnh cm v chng. Bi l nn tng, l ch da vng chc cho mi vn khc ca bn trong cuc sng.\n\nHy hc hi cch sng thun v thun chng, ng xem nh tnh cm thing ling y, bn s tm c ng hng t ti hn nhn cht lng mi ngy.","language":"notdetect","sentiment":"Mixed","author":"CámơnTìnhyêutôi","author_text":"CámơnTìnhyêutôi","md5_text":"1e246f3517e257e6978df89811040f93","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758429650944},{"id":"353114390","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003732283968","authorlink":"YkdnChimCút","date":"2013-01-06T13:42:06Z","topic":"Kia K9","topic_text":"Kia K9","text":"Thi xong. Mai cc em i hc bnh thng. Th mi khm ch. Ch cho ngh bui no. =((","language":"notdetect","sentiment":"Mixed","author":"YkdnChimCút","author_text":"YkdnChimCút","md5_text":"851c84074096e456c71d83d3d1e3d7d7","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758430699520},{"id":"353114819","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003273911378","authorlink":"ViênĐáNhỏ","date":"2013-01-06T13:40:00Z","topic":"Kia K9","topic_text":"Kia K9","text":"Truyn ngn: c i mem i. thy hay thid chia s nh\n\n TNG NGI N NG LM EM KHC BNG MU!\n\nMt cu chuyn rt rt hay !\n\nTrc khi tr thnh v ti , em l n b , oan nghit thay , chng phi l ngi n b ca ti , em dng hin s trong trng ca mnh cho ngi n ng u tin , sau hn phi tay , bin mt dng . theo ui em , ti phi mt mt thi gian kh lu v em l ngi nhy cm v v cng mc cm , khi em chng cn nim tin vo tnh yu . Ti phi lm rt , rt nhiu nhng iu c th em hiu , lc tht s ti yu em qu nhiu , tnh yu c l c cuc i ti chng th qun c .Th ri ti cng ci c em , vo mt ngy ma h p tri . Cuc sng lc qu vin mn , em hin lnh , m ang , lun nh nhng vi chng , lo cho m chng v c a em chng li bing . Th nhng nhng g qu m m li d mang li cm gic nhm chn . Em hin lnh qu mc , m ang qu tay v du dng qu th , i khi ti cn mt cht bng bnh c chu chung em , cn mt cht gi tnh khi ln ging tnh yu ca ti nng chy hn .Ri ti gp c c y - tnh nhn ca ti , c ta quyn r , hp dn v c bit rt bit cch ht nh nhn v s quan tm ca n ng . Th l ti lao vo cuc tnh ngoi lung , say m v in o . Ti qun mt mnh l n ng c v , thi gian dnh cho gia nh trong tun ch tnh bng pht bng giy , cn i vi tnh nhn ti gn nh dnh trn . Ri chuyn g n n , em pht hin , trong mt ln tnh c c c tin nhn trong my ti . Nhng ngi ni in chng phi em , m l ti , m ti tt em v ci ti t tin ng vo ca ti .Ti i khi nh khi tri ang m , m ti thy n o , chy ln th ch thy em khc , ngoi ra ... em chng ni g thm . Chuyn gy g gia ti v v ngy cng nhiu , m ton l do ti , ti say xn , v p ph v chi ra em , trong mt ln khng cn thn , ti ay nghin em v ci chuyn em -l-n-b trc khi ly ti , m ti nghe thy v t b chng cn thng em nh trc . M ti ngh rng l nguyn nhn ca vic v chng ti xung t , cch i x ca b thay i chng mt . Em trng gy hn trc ....Th ri tnh nhn xinh p ti bt ti li d em rt c ta v , ti khng chu , th l c ta ngng nguy , hn gin . Nhng iu cha bao gi ti thy em , ngi n b ti nghip ca ti .Sau mt thi gian di , ti dn ra ring , v nh ca lc no cng n o , em th ch m thm chu ng . T khi ra ring , ti ngy cng chng tn trng em , hn mt ln ti ni chuyn tnh t vi tnh nhn trc mt em , hn mt ln em thy ti i cng c ta , v chng phn ng g , ch cui mt v i thng . Dng nh em lun cm thy mnh l ngi c ti , th nn chng bao gi em dm phn khng li , cha mt ln ... Th ri em c thai , em ni vi ti vi ging vui mng m lu ri ti chng nghe c , ti m em vo lng , bt cht kho mt cay cay , ti chng bit v l do g , vui v ti sp c con , hay v ti cm thy thng cho tm thn em sao gy qu . Ti dt em i mua , khoc vai em gia ph ng ngi v trao em ci hn nh m p , th l em ci ...... in thoi ti reo ln tng chp , c tnh nhn ti gi .\nN ci ang trn mi em bng tt ngm , nh mt vui mng , sng long lanh cht tr v vi ni bun nh lc trc .\n- Anh ... c vic t xut .\n- Anh i i .\nEm chng ngn ti li , d em bit ti sp i u , sp bn ai , sp lm nhng g . Vy l ti b em gia ng ph ng ngi - khi em ang mang trong mnh a con ca ti ...\" Anh mun dng li \" , sau ngy hm y , ti ni v tnh nhn nh th . C ta khc lc , nu ko , th sng th cht ... th l ti khng n . V th khi v ti bu b n thng th nm , mi chuyn vn nh c .\n\" Em v nh m hai hm nh \" - Em nhn nh th khi ti ang cng ti , cng ng thi , lu lm ri em chng v thm gia nh , ti vui v ng . Ngy hm y , ti cng quyt dt vi c ta - tnh nhn xinh p ca ti .Ting chung ca bm in i , ti ng em v , vi ra m vi lng ngp trn nim vui - ti tr v vi em theo ng ngha . Th nhng trc mt ti chng phi ngi v vi n ci hin ho m l mt con n b nng nc mi ru , c ta lao vo khc v hn ti ti tp . Ti y ra v sau l ting tht , tru tro ... th l buc lng , ti phi ko c ta vo nh .Tnh nhn ca ti sau mt hi khc lc thm thit , cui cng cng bnh tnh .\n- Anh b em v sp c con ?\n- .\n- Vy em s i , khi no con anh ra i , chng ta s quay li vi nhau , phi khng ?\n- Xin li , anh ngh mnh nn dt hn.\n-...\n- Anh mun quay v vi v mnh , c y kh qu nhiu . Em s tm c ngi n ng khc yu em hn anh , cn anh , anh ngh mnh i xa qu ri .\n- Anh hi hn v yu em sao , ti sao th ? R rng em p , em gii giang hn c ta m.\n- , nhng bnh yn l cm gic c cuc i ch c c y l c th mang li cho anh.\n- Em khng tin ... EM KHNG TINNNN- Em bnh tnh i , em v c ri , anh a em v.- K ... khng .-...- C kt thc , anh cng phi cho mt ci kt nh hai k yu nhau ch , khng c ph nh vy.\n- Em mun g ?\n- Em mn li vi anh m nay , ch m nay thi.- Khng c, c th v anh s v.\n- Em xin anh, y l iu cui cng em mun .Th ri c ta li khc v ti chnh lng . Ti ngh v s khng bit u v ch m nay thi , ngy mai ... c tnh nhn ti nghip kia s khng cn bn ti na . V th l ... ti ng . m bng ma gi ... c hai k ti trong nh .\n--------------\nCHNG II : MT\n\nTing ca m .... Ti bng hong . Em cht trn , i mt em ang dn cht ly hai thn th lo l . Chng mt git nc mt no kp chy , em ng mnh xung nn nh , bch thc n cn nng hi t tay em vng ra , vung vi khp .Ci xe cp cu reo ln in i , nhi tai v dng nh bp nt c tri tim ti . Mu t chic vy hng em mc chy ra ngy mt nhiu ... Con ti !!!Cha y mt ting sau , m em v m ti u c mt ti bnh vin . Mt m em xanh mt , lo cho a con bng mang d cha , m ti th lun ming hi ti sao , ti sao , ti sao ......Ti ni c v c hai ngi u thng tht , h no bit con ngi ca ti li khn nn n vy . Th l m ti khc , c l b hi hn v tn nhn vi a con du qu i ti nghip ca mnh , v b chng thi ngng chi ra ti - thng n ng ng kinh tm .M em chng ni c g , b tht thn hng mt v pha cnh ca mu trng , cnh ca m pha sau ch c mt mnh con gi b va gnh gng ni au n qu ln , va i chi vi li hi t thn .\n- Xin li gia nh . Chng ti c gng ... nhng ch c th cu c ngi m , a tr khng qua khi.\n_ g bc s gi , nh mt va lng tng , va au xt , ngp ngng ni .Hai b m o khc , cn ti ... ti ch bit ng , ti va git i da con ca mnh ...\n- V ... d tim thuc gy m , nhng nc mt ca bnh nhn khng ngng chy ... rt nhiu . C l c y phi chu ng mt c shock qu ln m ngay bn thn mnh khng lng ni , vic y gy nh hng nghim trng trong qu trnh mang thai .... Ti hy vng gia inh hy ng vin v gip bnh nhn vt qua ni au mt con ny , c th khi tnh dy , nu bit mt a con .... ti e tm l ca c y s khng c n nh ... Ti xin chia bun cng gia nh.M ti lao vo nh , chi , ra ti ...\n- My cht i , thng chng n mt , my hi cht con my , my git tri tim v my .... my khng phi con tao ... KHNG PHI.... KHNG PHIIIIIIII...Ging m ti khn i , ri qu mt , b khuu xung ...ng vy , gi nh b ng sinh ra a con nh ti , ti s khng phi t tay p nt gia nh mnh nh th ny ....\n---------------\nNgy hm sau , em tnh !M em khng cho ti vo v s em s khng chu c . Th l ti ng ngoi , bn trong ting nc au kh ca em vang ln bn bc , nhng cu hi v a con xu s t ming em tung ra lin hi , em cn hy vng , em mun mt cu ni khng sao , nhng mi th ch l mt s tht au n ... M em v m ti ch bit khc , ch bit an i ... Khng cm lng c , ti bc vo , ti mun vut ve ni au ca em .Th nhng , va thy ti , i mt m nc ca em long ln ng s , trong cha t ni au , chu ng n ni on hn , cm th ... cha bao gi em nhn ti nh th , cha bao gi . Em go tht , ting tht x c khng gian im lng ca bnh vin , ting tht au n , ting tht hn th , ting tht i con ... Mt em ln v em giy gia , em mun tung ra khi ging , em mun git ti ...... Cc bc s a ti , ko ti ra , ti khng mun ra , th ti em git i cn hn phi i mt vi chnh mnh lc ny .Cnh ca ng sm li , au ing , ch cn ting tht ca em , th ri ngui dn ... ngui dn . H li tim cho em thuc an thn , thuc gy m.\n- N khng cn ni c na .M em ni gn ln nh th . nh mt ca b khng on khng hn nhng li cha c i dng au thng v xt con .\n- V sao ... h m?\n- Bc s ni do chn ng tm l ... c l trong tim thc , con b khng mun tip xc vi ai na .\n-...\n- Khi xut vin , n s v vi m ...\n- Vng .\n- T y cho n khi tm l n n nh , con hy chun b n li hn i .Cu ni m tung ra lnh lng , bt gic ti khng ng ni . Qu thp di chn b , ti van xin.\n- M , xin m ng bt con lm th , con mun chuc li.\n- C nhng li lm c th tha th , tuy nhin c nhng li lm c ngn i cng khng th no ra sch c , v con gy ra mt li lm vt qu s bao dung v th tha ca con gi m .\n- Con khng cn tha th , con ch xin m cho con bn c y , con ch c y t xa cng c , xin m ng bt con lm n li hn ....\n- Nu con yu n , th lc u ng i x vi n nh vy , m ngh mi chuyn mun qu ri ...Ti nghe ting tim ti t on , mt ti nc c chy , ti khng mun , khng mun .... khng mun ........\n-------------------\nNgy em ra vin , ti ch c th ng t xa nhn em , trng em nh mt cy kh sp gy , yu t , phi nh s gip ca cc c y t kia mi c th vo xe c . Em ca ti - Ngi n b ca ti ....Nhng ngy sau , ti nh mt cc xc rng khng hn , lang thang u ny y kia chng ngng ngh . Cng vic ca ti b tr tr ti cng chng quan tm , ti gn nh mt ht , th nn , thi mun mt ht ... cho trn vn ... Th ri tnh nhn khn nn ca ti li n ...\n- Anh ...\n- C i i.\n- Khng ... em s y , bn anh.\n- Cha sao.\n- ...\n- V hai k ng nguyn ra nh ti v c m mt ngi ph n phi chu ni au tt cng cuc sng , m mt a tr cha kp cho i phi ra i , th cha sao .\n- ...\n- Cha sao , chnh tay ti bp nt hnh phc gia nh ti .... CHA SAO ... CHA SAO ...Ti go khc nh mt k in , c ta chy n , m cht v tht ln mt cu ng m trm nht .\n- Ta s xy dng mt hnh phc khc . Anh s qun i ngi ph n , a tr thi ...Ci x mnh lm c ta t bt gng , ti ci ma mai cho ci cu \" mt hnh phc khc \"\n- Th n b nh c ... tn c v v lng tm .... hnh phc khc ? Ti s git nu c dm ni thm mt ln no vi ti cu na.Th ri ti quay lng siu vo bc i , b li ting khc chua cht pha sau .-----------------------------------Trong men say , c mt ngi n b gi nh nhng du ti vo nh . un nc m v lau mnh cho ti . Mt ti h m , v thy tc b y bc hn kh nhiu\n.- M ...\n- , ch ai na .\n- M ...con bit li ri....M ti au lng , ch xoa u nh khi ti cn nh ch chng bit ni g hn .\n- M ...\n- , m y.\n- Ti con khng tha th c phi khng.\n-...\n- M ... con khng mun mt thm c y ...\n- C ngn li lm trong nhn gian c tha th bng s khoan dung c trong nhng tri tim cha y tnh yu thng ca con ngi , con . M khng dm chc con du s tha th cho con , nhng m cng khng n dp tt nim hy vng cui cng ca con , con hy lm nhng iu m con ngh nn lm , nu khng c chp nhn , th du g cng ra c phn no ti li ca con .\n--------------------\nCnh ca m dn ra :\n- Con n y lm g ?\n- Con mun ... nhn c y mt cht thi m.\n- N vn vy thi , cha kh hn c , con vo s ch lm n ti t hn.\n- D m ... vy m chuyn b hoa ny dm con c khng. C y rt thch hoa hng vng ...\n- , a cho m , thi con i lm i .Ngy th nht , ti mang b 99 bng hoa hng vng n , loi hoa m em thch nht , nhng ch nhn em mt t thi cng khng th ...Tng ngy , tng ngy c tri qua mt cch lng l , em xa ti vn vn gn mt nm , trong mt nm y , ti khng bit gi cho em bit bao nhiu bng hng vng , vy m cnh ca y vn ng sm vi ti . Hm nay , khun mt quen thuc vn ra m ca :\n- C y sao ri m ?\n- N kh hn cht ri .\n- Vng , vy tt qu .\n- Con cng bn nhiu vic , khng cn ngy no cng em hoa n u .\n- Khng sao m , con i nh .\n- \nNi on , m em nhanh tay khp ca , vn nh mi hm thi . Ti bc i , chm ri nh mun c ai nu chn nh v ... tht nh thi , c ting ca bc nh ra . Em - Ti quay u li v thy em , ti vui mng n qun mnh ang u , ri dng nh em mun ni iu g , em ang chy ra .....RM !!!!!!Ch cn mt on na thi ... nhng cui cng ti vn cha chm em c.\n----------\nChic xe ti khng trnh c , tt c u bt ng qu .Bn quan ti ti , m khc thm thit , au n tt cng . Ai cng xt thng , mt ai cng ma . Ring em - vi nhng mc vt ngng ni au , em lng cm .Sau ngy y , m no ti cng c ngm em , nhng em ng rt t . Thi gian chnh , em ch nhn bc nh lc chng ti mi ci , rng r - v chm ngp trong hnh phc .Mi khi em khp mt , ti li xt , nc mt nh nhng git au vo thc, c tung mi . Kh nh ti vut m em ... bt gic em tnh dy , trong y ming hai nm cha bao gi m mp my :\n- Anh ...Ti sp tan bin , tri ma to , mt em tm kim hong lon . Th ri ... em nhn thy ti , i mt nhn ti au kh . Hnh bng ti m nht , iu cui cng ti nghe c l :\n- ng bin mt ...Gi ng mi thi .... Ti xin ngng\nng lm lnh thm - Em ang mang tang\nTang ngi n ng ... lm em khc bng mu\nTang a con th ... cha ln no gi em bng m...\nGi ng ti xin ti xin .... hy ngng i\nng go tht ... Tri tim em v ri!","language":"notdetect","sentiment":"Mixed","author":"ViênĐáNhỏ","author_text":"ViênĐáNhỏ","md5_text":"8dd2cbf872e66e37baa108d25d8847de","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758455865344},{"id":"353114966","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100000133275771","authorlink":"TâmCùiBắp","date":"2013-01-06T13:29:31Z","topic":"Kia K9","topic_text":"Kia K9","text":"T l Tm in _ Nh Tm thn. Xin hy yu ti, ti khng mun c thn. Ti mun ly chng, hy n yu ti i.","language":"notdetect","sentiment":"Mixed","author":"TâmCùiBắp","author_text":"TâmCùiBắp","md5_text":"f4aacd21a91e4c277564c2ea4e9bd312","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758466351104},{"id":"353114059","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003224990854","authorlink":"ChayKhetLet","date":"2013-01-06T13:56:33Z","topic":"Kia K9","topic_text":"Kia K9","text":"Bit u bt ng anh ln bn th n xi,\nAi cn ng di ma ngn nga cu gi hn\nV bn tay to tng m ly ci body\nTh l anh thng ri\n\nS l di lng khi em chng ngi u lo,\nLo em s mt anh trong lc ang u ym\nV tnh yu mong manh,tay em qu manly\nNgi yu i,anh c bit?\n\nChorus:\nEm yu anh hn th,nhiu hn li em vn ni.\nEm c ng m anh cht l s bnh yn\nm bung xui v c n,cn ring em vi ci xe\nAi biu ku em m chi...\n\nAnh a em theo vi, vt wa c th gii\nn bn kia em c th bn anh trn i,\nNi thng yu khng phi phai, c bn nhau mi sm mai.\nC au khng, anh i ?\nNi thng yu khng phi phai, c bn nhau mi sm mai.\nS m anh c sut i..","language":"notdetect","sentiment":"Mixed","author":"ChayKhetLet","author_text":"ChayKhetLet","md5_text":"a02f1fcb0d7b04bcd6818f29b3bc4a66","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758466351105},{"id":"353114211","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=128677373953642","authorlink":"HéLộBíMậtVề12CungHoàngĐạo","date":"2013-01-06T13:55:02Z","topic":"Kia K9","topic_text":"Kia K9","text":"B MT TNH YU - QUA NGY SINH CA BN\n\n Ngy 1, 10, 19 v 28 \n\nBn khng ch tin vo ting st i tnh, m bn tng b st nh trng. Bn c tnh c thm chng chn y nh, hm nay thy rung ng bi nh mt ny, n mai bng thy trng rng v chuyn sang tng t n ci khc. Trong tnh yu, bn th hin hai thi cc yu-ght rt r rng. \n\nYu tht s: 2 ngi \n\nTri tim tan v: 2 ln \n\n Ngy 2, 11, 20 v 29 \n\nBn thng nhm i tng t xa mi quyt nh c nn bn tn hay khng. Bn mt nhiu thi gian v cng sc trong chin dch cm ca ai . Tm li, bn thch ch ng i tm tnh yu. Chng may cho ai c lao vo bn th ch c v ch m thi. \n\nYu tht s: 3 ngi \n\nTri tim tan v: 4 ln \n\n Ngy 3, 12, 21 v 30 \n\nBn chc chn v t tin trong vic chn na kia cho mnh. Thm ch s la chn ca bn l v iu kin. Bn lun ng pha sau theo di v bo v ngi mnh yu. Bn b bn khng bao gi hiu bn bng\nngi y. \n\nYu tht s: 3 ngi \n\nTri tim tan v: 5 ln \n\n Ngy 4, 13, 22 v 31 \n\nBn khng thch yu hi ht, hoa l cnh. Theo bn tnh yu l mt phm tr lun phi nghim tc. Tuy nhin, bn c th hp vi nhiu tup ngi. Bn tm thy nhng u im hu ht mi ngi. Tnh yu hay mang li cho bn nhng bt ng m bn khng bit trc c n s xut hin khi no v u. \n\nYu tht s: 2 ngi \n\nTri tim tan v: 2 ln \n\n Ngy 5, 14 v 23 \n\nMi ngi ni bn khng c am m, tht l mt sai lm. Thc t bn rt su sc v a cm. Cng v th m bn c th yu cht m cht mt ngi ta ngay sau vi bui h hn. \n\nYu tht s: 5 ngi \n\nTri tim tan v: 5 ln \n\n Ngy 6, 15 v 24 \n\nVi bn, yu l mt h qu tt yu ca tnh bn. Trong tnh yu bn rt trung thnh, chn chn v chn tht. Bn khng phi l k la di cng khng phi tn tnh cho vui. Bn thng gi c tnh bn tt p vi ngi c, ng thi sn sng nhen nhm li ngn la tnh yu trong tng lai. \n\nYu tht s: 4 ngi \n\nTri tim tan v: 5 ln \n\n Ngy 7, 16 v 25 \n\nBnh tnh v t ch, bn tm cch by t tnh yu bng li ni. Nhiu lc, na kia phi hc nhn bit tnh cm ca bn thng qua c ch, vic lm ca bn. Bn c ti ghm cng ngi ta m khng b pht hin ra y. \n\nTnh yu thc s: 1 \n\nTri tim tan v: 2 \n\n Ngy sinh: 8, 17 v 26 \n\nBn thch cm gic ang yu, thch n ni ban khng mun mnh trong tnh trng single cht no. Tht khng may, tnh yu khng d dng chiu theo mun bn c. Nhiu ngi c m mu li dng bn y, ch c tnh yu thc s mi mang li cho bn nim vui m thi. \n\nTnh yu thc s: 2 ngi \n\nTri tim tan v: 3 ln \n\n Ngy 9, 18 v 27 \n\nVi bn, yu l th tnh cm vng vn day dt mi, d chia tay nhau. Thc t, mi tnh u lun li cm xc thing ling v mnh lit trong bn. Bn\nthng khng dt khot trong tnh cm. Chnh v th bn d l ngi b chia tay ch khng phi l ngi ch ng chia tay. \n\nTnh yu tht s: 5 \n\nTri tim tan v: 4","language":"notdetect","sentiment":"Mixed","author":"HéLộBíMậtVề12CungHoàngĐạo","author_text":"HéLộBíMậtVề12CungHoàngĐạo","md5_text":"01e231f793d3a8f2ca4dba8385c36a47","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758468448256},{"id":"353114883","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004340227805","authorlink":"HaiYenVu","date":"2013-01-06T13:32:53Z","topic":"Kia K9","topic_text":"Kia K9","text":"tinh yeu la gi ma the gioi phai khoc?","language":"notdetect","sentiment":"Mixed","author":"HaiYenVu","author_text":"HaiYenVu","md5_text":"10e54414f94392086f8d66d0d040d117","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758469496832},{"id":"353114755","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004062563828","authorlink":"ChípHô","date":"2013-01-06T13:34:33Z","topic":"Kia K9","topic_text":"Kia K9","text":"quyt inh hoc an len.... Nhng an cho ai? Hajza....","language":"notdetect","sentiment":"Mixed","author":"ChípHô","author_text":"ChípHô","md5_text":"403aff141758e93a2faf9ab6f1d4b410","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758470545408},{"id":"353114914","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100002547705449","authorlink":"VijetaGupta","date":"2013-01-06T13:27:58Z","topic":"Kia K9","topic_text":"Kia K9","text":"hotho mein dabee bat h koi,aankho mein hai sapna,ajnabee sa ek dard h magr lag raha h apna...mein na janu mere humrahi kesa hai y kesa pyar hai..jaan leta jo bina bole kya dhadkan ka ikrar hai..<3<3","language":"notdetect","sentiment":"Mixed","author":"VijetaGupta","author_text":"VijetaGupta","md5_text":"6f2871274f2ac5e955cc704eff79f881","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758471593984},{"id":"353114909","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004258653188","authorlink":"GàGôChânTo","date":"2013-01-06T13:29:58Z","topic":"Kia K9","topic_text":"Kia K9","text":"xin li ,m i qua en :(((((((((((((((((((((((((((((((((","language":"notdetect","sentiment":"Mixed","author":"GàGôChânTo","author_text":"GàGôChânTo","md5_text":"fbce7bf2da5544827684b5f86bfa7f52","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758472642560},{"id":"353114839","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100001556886634","authorlink":"AnhKuBi","date":"2013-01-06T13:35:17Z","topic":"Kia K9","topic_text":"Kia K9","text":"Em i em dng li no ng ng kia c ma ri, \ntrng kia xem ng ngp bn trt chn em bit ku ai...\nl la la la la l la la...\nl la la la la l la la... \nl la la la la l la la ...\nng khc em gi b i!\n@@@@@@@@@@@@@@@@@@@@@@@@@@@","language":"notdetect","sentiment":"Mixed","author":"AnhKuBi","author_text":"AnhKuBi","md5_text":"c82bcea7f177dffc954f121512f398b5","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758473691136},{"id":"353114642","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004943352089","authorlink":"KuckithudoanHt","date":"2013-01-06T13:35:56Z","topic":"Kia K9","topic_text":"Kia K9","text":"pc mn my khm khm w. .\n. z kn m woi.wo tht ck","language":"notdetect","sentiment":"Mixed","author":"KuckithudoanHt","author_text":"KuckithudoanHt","md5_text":"7a2f78cdc2e46762d2efabaaacfd73f8","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758473691137},{"id":"353114731","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004721886293","authorlink":"ĐỗQuyên","date":"2013-01-06T13:40:22Z","topic":"Kia K9","topic_text":"Kia K9","text":"hung nen dj cat toc .pjo hoj han. . .huhu","language":"unknown","sentiment":"Mixed","author":"ĐỗQuyên","author_text":"ĐỗQuyên","md5_text":"0e772ca12e36aa2e2be893e40ead3912","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758483128320},{"id":"353114062","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=279538412081342","authorlink":"KhámPháBíẨn12ChòmSao","date":"2013-01-06T13:58:11Z","topic":"Kia K9","topic_text":"Kia K9","text":"[chm sao no sng quy c nguyn tc]\n\nKim Ngu\n\nThn Nng\n\nThin Bnh\n\nBo Bnh\n\nMa Kt","language":"notdetect","sentiment":"Mixed","author":"KhámPháBíẨn12ChòmSao","author_text":"KhámPháBíẨn12ChòmSao","md5_text":"87590322872734b2d3cd8f46672f4db5","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758484176896},{"id":"353114448","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100000130474382","authorlink":"LươngNgọcDiệp","date":"2013-01-06T13:41:26Z","topic":"Kia K9","topic_text":"Kia K9","text":"cai s iem no kh! Dinh vao nha co 3 a nn n! Con ca con ut no thik film kinh di! Iem-a gia thi s ma n ai ca ra qun, h iem ang ngi trong chn c gng hoc t mi mai kim tra con 2 con mu kia lai ngi xem film kinh di (2 mu y co cai thoi quen xem film kinh di vao cui tun) iem hoi moi ngi! Lam th nao hoc y?","language":"notdetect","sentiment":"Mixed","author":"LươngNgọcDiệp","author_text":"LươngNgọcDiệp","md5_text":"cc6647afb3cc9a4e95ef45d000bad7d4","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758485225472},{"id":"353114080","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003737403437","authorlink":"ThiênThầnNhỏ","date":"2013-01-06T13:58:08Z","topic":"Kia K9","topic_text":"Kia K9","text":"Chng ai hiu minh bng 9 pan thm minh. ung nhi","language":"notdetect","sentiment":"Mixed","author":"ThiênThầnNhỏ","author_text":"ThiênThầnNhỏ","md5_text":"9df874455fdd2d5475b40083bd560041","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758486274048},{"id":"353114728","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003956412467","authorlink":"TrungNguyen","date":"2013-01-06T13:39:17Z","topic":"Kia K9","topic_text":"Kia K9","text":"Cac ban ne, neu co 3.900.000 d. Ban se mua may anh hieu j (Canon, Sony,....) hjhj","language":"unknown","sentiment":"Mixed","author":"TrungNguyen","author_text":"TrungNguyen","md5_text":"6c5447e591ba022ffb7011fb0894cc97","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758487322624},{"id":"353114634","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004337319527","authorlink":"LuyenPhung","date":"2013-01-06T13:40:55Z","topic":"Kia K9","topic_text":"Kia K9","text":"Ti.\n* Khng trnh ua vi\ni.\n* V khng mun ph li vi loi\nngi khn nn !\n* Cuc i ti khng phi dng\nsng cn. . .\n* Ai cng khng lm hn\nhn cuc i ca ti.!\n*ng gi v lm bn ri khn\nnn sau lng.\n* ng quan tm ht mnh ri bt\nthnh lnh b mc.\n*ng ni yu n pht in ri\nvn c hn nhin phn bi.\n*ng ua theo x hi sng bng\nb mt v ti gi nai.\n*ng hoi vi sao ti ac!\n* i no bac,sng nhat cho bt\nau.\nVy thi.","language":"notdetect","sentiment":"Mixed","author":"LuyenPhung","author_text":"LuyenPhung","md5_text":"3e12923502b8da3896fafe8f6475cf91","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758488371200},{"id":"353114069","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004600962712","authorlink":"PhamtheVinh","date":"2013-01-06T14:01:22Z","topic":"Kia K9","topic_text":"Kia K9","text":"Mt b xng ngi ri ngha a lang thang ngoi ng th gp mt b xng khc bn hi:\n- Cu cht nm no vy?\n- T cht i nm t Du.\nang ni chuyn th c b xng na i ti. \n\n- Cu cht nm no vy? \n\n- T cht v thin tai. \n\nBa b xng cng nhau i tip, c mt lc th gp b xng th t.\n\n- Tri t! Cu cht nm no m b dng t ti vy? \n\n- ng c tr o ti - B xng kia cu - ti ang sng s ra y.\n\n- Vy cu lm ngh g?\n\n- Sinh vin nm cui, ti va i gia s v!","language":"notdetect","sentiment":"Mixed","author":"PhamtheVinh","author_text":"PhamtheVinh","md5_text":"d5e3238459533c96216f7d30a9043031","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758489419776},{"id":"353114616","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003658073388","authorlink":"NgôViếtKhôi","date":"2013-01-06T13:43:55Z","topic":"Kia K9","topic_text":"Kia K9","text":"Cu ton hc 2\nCng l ti tin nhng ch c 8 ti....ging nhau v hnh dng bn ngoi....trong c 1 ti nng hn 7 ti cn li...\n????? : Dc php cn 2 ln tm ra c ti ??\n\nCu ny d hn nhiu!!!!!!!!!!!!!!!!!!!","language":"notdetect","sentiment":"Mixed","author":"NgôViếtKhôi","author_text":"NgôViếtKhôi","md5_text":"15ca4f8875861e914df624f912e218b6","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758490468352},{"id":"353114658","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003884559137","authorlink":"NốiThoátChoNgười","date":"2013-01-06T13:46:26Z","topic":"Kia K9","topic_text":"Kia K9","text":"nu ngy sa bc i nhanh qua con ng ma th anh khng gp ngi.nu trc kia anh khng ? em l ai th gi y u phi nh th ny","language":"notdetect","sentiment":"Mixed","author":"NốiThoátChoNgười","author_text":"NốiThoátChoNgười","md5_text":"1994e4137b48217389a88844fc44318f","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758490468353},{"id":"353114435","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003837226895","authorlink":"BaoKhangNguyenHua","date":"2013-01-06T13:52:15Z","topic":"Kia K9","topic_text":"Kia K9","text":"ch mun z SG nhng m vn phi i o.","language":"notdetect","sentiment":"Mixed","author":"BaoKhangNguyenHua","author_text":"BaoKhangNguyenHua","md5_text":"c22234da5391ebe78ed1f56f7424caad","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758491516928},{"id":"353114940","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100000327706184","authorlink":"LiênTrần","date":"2013-01-06T13:27:49Z","topic":"Kia K9","topic_text":"Kia K9","text":"Mt khi mu th ng hi b chu l ai. i xem phim 1 mnh nh! \n12 con gip. Keke","language":"notdetect","sentiment":"Mixed","author":"LiênTrần","author_text":"LiênTrần","md5_text":"3f33284e46b0bd9f06b21c7411628e75","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758534508544},{"id":"353114210","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004101645210","authorlink":"BillyTran","date":"2013-01-06T13:51:54Z","topic":"Kia K9","topic_text":"Kia K9","text":"Co ba chj an choj wa dj.","language":"unknown","sentiment":"Mixed","author":"BillyTran","author_text":"BillyTran","md5_text":"b80249e00b93df0f12ed6e4590c5b8a8","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758535557120},{"id":"353114900","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100002983531547","authorlink":"XecoKute","date":"2013-01-06T13:33:13Z","topic":"Kia K9","topic_text":"Kia K9","text":"Hai ngi n ng ang trong hnh lang phng khm. ng gi hn trng c v bn chn. Ngi kia hi thm:\n\n- Trng bc rt lo lng, chc b bnh nng lm?\n\n- Ti n th mu.\n\n- Th ? Khng bit ngi ta s lm g khi th mu nh?\n\n- th mu, h s ct tay ti. i, mi ngh n rng mnh ri.\n\nNghe n vy, anh chng kia mt mi ti mt, c thc tay vo ti qun ri run bn ln. ng th mu bn hi:\n\n- Anh lm sao th? Sao t nhin li hong s vy? \n\n- Ti n y th nc tiu...","language":"notdetect","sentiment":"Mixed","author":"XecoKute","author_text":"XecoKute","md5_text":"bff4cd9b94e80c5f77083a69aa41da6a","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758535557121},{"id":"353114447","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100001102514164","authorlink":"DoLa","date":"2013-01-06T13:45:36Z","topic":"Kia K9","topic_text":"Kia K9","text":"- BA TIC M TRONG NH V SINH : Ch l Oshin ngi gip vic nh cho mt ng ch ngoi ng tun, rt giu c. m xung, xong vic, vi vng v vi a con trai nh 5 tui sut ngy ngng i trong cn nh ti tn ...\n\nHm y, ch nh c l ln, mi rt nhiu bn b quan khch n d tic m. ng ch bo : Hm nay vic nhiu, ch c th v mun hn khng? Tha c , c iu a con trai nh qu, nh ti mt mnh lu s s hi. ng ch n cn: Vy ch hy mang chu n cng nh.\nCh mang theo con trai n. i ng ni vi n rng : M s cho con i d tic m. Thng b rt ho hc. N u bit l m lm Oshin l nh th no kia ch! V li, ch cng khng mun cho tr tu non nt ca n phi sm hiu s khc bit gia ngi giu k ngho. Ch m thm mua 2 chic xc xch.\nKhch kha n mi lc mi ng. Ai cng lch s. Ngi nh rng v trng l Nhiu ngi tham quan, i li, tr chuyn. Ch rt bn khng thng xuyn mt c n a con nhch nhc ca mnh. Ch s hnh nh n lm hng bui l ca mi ngi. Cui cng ch cng tm ra c cch : a n vo ngi trong phng v sinh ca ch c v nh l ni yn tnh v khng ai dng ti trong bui tic m nay.\nt 2 ming xc xch va mua vo chic a s, ch c ly ging vui v ni vi Con : y l phng dnh ring cho con y, no tic m bt u! Ch dn con c ngi yn trong i ch n v. Thng b nhn cn phng dnh cho n tht sch s thm tho, p qu mc m cha tng c bit. N thch th v cng, ngi xung sn, bt u n xc xch c t trn bn c gng, v m ht t mng cho mnh.\nTic m bt u. Ngi ch nh nh n con trai ch, gp ch ang trong bp hi. Ch tr li p ng: Khng bit n chy i ng no ng ch nhn ch lm thu nh c v giu dim kh ni. ng lng l i tm Qua phng v sinh thy ting tr con ht vng ra, ng m ca, ngy ngi: Chu np y lm g ? Chu bit y l ch no khng ? Thng b h hi : y l phng ng ch nh dnh ring cho chu d tic m, m chu bo th, nhng chu mun c ai cng vi chu ngi y cng n c!\nng ch nh thy sng mi mnh cay x, c km nc mt chy ra, ng r tt c, nh nhng ngi xung ni m p: Con hy i ta nh. Ri ng quay li bn tic ni vi mi ngi hy t nhin vui v, cn ng s bn tip mt ngi khc c bit ca bui ti hm nay. ng mt cht thc n trn ci a to, v mang xung phng v sinh. ng g ca phng lch s Thng b m ca ng bc vo: No chng ta cng n tic trong cn phng tuyt vi ny nh.\nThng b vui sng lm. Hai ngi ngi xung sn va n ngon lnh va chuyn tr r rch, li cn cng nhau nghu ngao ht na ch Mi ngi cng bit. Lin tc c khch n n cn g ca phng v sinh, cho hi hai ngi rt lch s v chc h ngon ming, thm ch nhiu ngi cng ngi xung sn ht nhng bi ht vui ca tr nh Tt c u tht chn thnh, m p!\nNhiu nm thng qua i Cu b rt thnh t, tr nn giu c, vn ln tng lp thng lu trong x hi. Nhng khng bao gi qun gip nhng ngi ngho kh chm ch. Mt iu quan trng hnh thnh trong nhn cch ca anh: ng ch nh nm xa v cng nhn i v cn trng bo v tnh cm v s t tn ca mt a b 5 tui nh th no ","language":"notdetect","sentiment":"Mixed","author":"DoLa","author_text":"DoLa","md5_text":"cb6481980d1e46f62afb27e431c6d71b","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758537654272},{"id":"353114932","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004923124387","authorlink":"NiNiLe","date":"2013-01-06T13:36:44Z","topic":"Kia K9","topic_text":"Kia K9","text":" Khng nhn tin, khng t khng phi l \nqun!\n Khng hi han, khng quan tm cng cha\nchc l khng nh...\n M ch l...\n\" S...\"\n S cch ngi ta tr li 1 cch min\ncng...\n S s xut hin ca mnh lm phin cuc\nsng ca ai kia...\n...nho...","language":"notdetect","sentiment":"Mixed","author":"NiNiLe","author_text":"NiNiLe","md5_text":"13fce432c32e85a1477459051a94b1c3","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758538702848},{"id":"353114274","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100001263778163","authorlink":"VickMin","date":"2013-01-06T13:49:07Z","topic":"Kia K9","topic_text":"Kia K9","text":"10. Em bnh thng. (n ng phi lm g v nn lm ngay i. ng mi chuyn bt bnh thng l cho i lun ).\n9. Anh thy c b kia xinh khng? (Em ang mun th xem anh c phi loi con trai ho sc hay i tn tnh cc c gi xinh p khng y!).\n8a. em ngi mt mnh c khng, em ang bun! (Hy ngi bn cnh, v v em v l mt b vai vng chc).\n8b. Cui tun em bn ri anh i, khng i c u! (Khng bit anh c thc lng mi khng, c c gng r em bng c hay khng, ch cui tun em bn xem TV, bn nh ngi khng, bn xem dng ngi qua li trc nh c ng khng...)\n7. Anh l mt ngi con trai tt. (Tht ra: em khng th yu anh).\nCc cu ng ngha v u t du chm ht cho hy vng ca chng: \"Em coi anh l ngi anh/ngi bn\".\n6. em tr cho, anh tr nhiu ln ri. (Mc d khng ng vi tt c ph n, nhng phn ln h vn ch i nam gii \"cover\" mi th, c bit nu l ln hn h u tin).\n5. Em khng bn tm chuyn anh vn lm bn vi ngi yu c u. (Em chng thch iu t no).\n4. Em khng bn tm n thu nhp ca anh u. (Nhng anh khng khai bo thnh khn th kh c hng \"khoan hng ca cch mng\").\n3. Em s khng thay i iu g anh c. (ng y, ch thay i mt cht v n mc, mt cht xu v tnh cch, vi mt cht v th ny, cht na v th kia. n gin phi khng no?)\n2. Em lun ng h anh giao lu vi bn b. (Anh coi trng bn b hn th dn qua sng vi my ng bn vng lun i. Gi lut php cn cho ng gii ci nhau ri ).\n1. Em khng phi l ngi sn u. (H, my con bn em, bn trai n lng mn qu tri. Hoa qu u, n ni nh nhng, lt tai, u ra y c. Anh tng em n ng chc, sinh nht cng ch ku i n, Valentine cng n, n n n).","language":"notdetect","sentiment":"Mixed","author":"VickMin","author_text":"VickMin","md5_text":"86babc6f0387ddd6aabeb7a4876882c7","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758539751424},{"id":"353114377","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100000302681621","authorlink":"ZôThánhThiện","date":"2013-01-06T13:48:29Z","topic":"Kia K9","topic_text":"Kia K9","text":"Cn 1 ngi lm tim mnh rung ng =))\nChc phi tm ngi ta trong tiu thuyt q :v","language":"notdetect","sentiment":"Positive","author":"ZôThánhThiện","author_text":"ZôThánhThiện","md5_text":"706400724815f62397ac8eb487af278a","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758540800000},{"id":"353114408","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100001612712448","authorlink":"ĐặngHoàiDuyên","date":"2013-01-06T13:48:25Z","topic":"Kia K9","topic_text":"Kia K9","text":"\"\"gi th t ty cu\"\"","language":"notdetect","sentiment":"Mixed","author":"ĐặngHoàiDuyên","author_text":"ĐặngHoàiDuyên","md5_text":"903fcbfdc082d59a05e5c634807ad06d","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758541848576},{"id":"353114492","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004874616124","authorlink":"ThiênNguyễn","date":"2013-01-06T13:47:05Z","topic":"Kia K9","topic_text":"Kia K9","text":"Trong nh giam n c i ca ni ting hung hng. Phm nhn mi no lm mch lng hn u b \"iu tr\" ra tr. \nMt hm ngi nh phm nhn mi mang vo mt con g bo thm ngon. i ca thm lm nhng chng l li ra cp g n th mt th din qu, lin gi tn n em ra.\n- My xem thng kia n lm g vi con g u tin th my cng lm y nh th vi n. N b cnh my b tay, n b i g my cng b i n... Nu my khng lm th vi n th tao s lm th vi my - i ca gn ging.\n- Vng, tha i ca.\nCh phm nhn mi tot ht m hi ht... Nhng mt lc sau... tn n em quay v bm bo i ca, va i va khc:\n- Em xin chu ti vi i ca em khng lm c.\n- Thng ny nhu nhc qu, th n lm g vi con g u tin?\n- N lim phao cu con g.","language":"notdetect","sentiment":"Mixed","author":"ThiênNguyễn","author_text":"ThiênNguyễn","md5_text":"2ba96d8dc4bdbfa512c34cacb085c311","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758542897152},{"id":"353114683","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004635822652","authorlink":"DoTanTai","date":"2013-01-06T13:34:57Z","topic":"Kia K9","topic_text":"Kia K9","text":"cb9 v ich rj ha ha","language":"english","sentiment":"Mixed","author":"DoTanTai","author_text":"DoTanTai","md5_text":"f89ce0a5bf3310f70f6d80beeda3066c","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758543945728},{"id":"353114431","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003929565212","authorlink":"CuTun","date":"2013-01-06T13:48:13Z","topic":"Kia K9","topic_text":"Kia K9","text":"tng cc nng...^_^\n\nNghe v nghe ve\nNghe v con gi....\nSut ngy li nhi\nLin khc tnh yu\n ri chiu chiu\nT nm,t by\nAnh ny ht xy\nAnh kia d thng\n\nNghe v nghe ve\nTip v con gi\nT nhn xinh gi\nSut ngy Phn son\nLi th bon bon\nCi mm khng ngh\nNhng c xu x\nVn nhn l xinh\nNgi to chnh nh\nVn cho...Eo p\nNgi m dp lp\nLi bo gi eo\n\nBo i vt bo\nTh ku au c\nBo i nh c\nTh bo au tay\ni chi sut ngy\nLm g bit mt !\n\nc th c st\nVn nhn thng minh\nQua 5 mi tnh\nVn cho l t\n\nNgi nh qu mt\nQu vt khng tha\nQuen ngi la c\nHng ny qun n\nn nhiu to s\nc ch g u\nNi lm au u\nKo thm ni na !!!...","language":"notdetect","sentiment":"Mixed","author":"CuTun","author_text":"CuTun","md5_text":"1e3cc3c71e45f16bf6a11c53cff5ae01","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758727446528},{"id":"353114031","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003823741460","authorlink":"BiênBuônBoom","date":"2013-01-06T14:01:39Z","topic":"Kia K9","topic_text":"Kia K9","text":"***Con ngi l th m , ai ch c lng cm ght k , ai ch c khuyt im , ai ch c s din... Quan trng l n mc no m thi....!!! \n~> t v va phi th s khng gy ra nhng chuyn ngi khc phi khinh b cn nhiu th ng nhin l ngc li ri\n***Bc no dm ni mnh khng c khuyt im...???\n***V th ng li nhng ci quy tc , nhng ci thit ch , nhng ci gi l l tng ch c trong l thuyt hoc tn ti mt vi ngi ( m nhng ngi li mc cc khuyt im khc ) ra p dng cho ngi khc trong khi mnh cng khng chc l s thc hin c...!!! n tht nc ci v l bch ng khng cc bc.\n***V cng ng c t ra th ny th kia hay lng m ngi khc , n khng nhng s khng lm p thm hnh nh ca bn m cn lm ngi khc nhn bn vi con mt khinh thng m thi....!!!\n~> V th em xin mt s ngi ng nh gi em khi cha nh gi c c bn thn mnh :\">","language":"notdetect","sentiment":"Mixed","author":"BiênBuônBoom","author_text":"BiênBuônBoom","md5_text":"4824c2e84bb5aa6e4de608c83fbdc07b","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758728495104},{"id":"353114793","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003024868483","authorlink":"ThanhHuyền","date":"2013-01-06T13:37:10Z","topic":"Kia K9","topic_text":"Kia K9","text":"khi chiu chp c ht cng sut khng cc tnh yu Huyen Nhung Nguyen, LaZy Nguyn, Tr Cn, Thu Trn, K Su, Ngn Nguyn, Nhung Cc,,,","language":"notdetect","sentiment":"Negative","author":"ThanhHuyền","author_text":"ThanhHuyền","md5_text":"a7bd7b50c43fba894fb95ea1717b48a3","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758729543680},{"id":"353114777","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=401692139866690","authorlink":"Truyệncườicấmtrẻemdưới18T","date":"2013-01-06T13:36:02Z","topic":"Kia K9","topic_text":"Kia K9","text":"C gi kia dn b v nh ng. H\nleo ln tng trn ca ci ging\ntng. Anh chng sa son leo ln\nngi c gi th c ta ngn li ni:\n- Ch em ng pha di ! Em\nkhng mun cho ch y bit l\nmnh ang lm g! Cho nn khi em\nni \"Em mun n nem c\" th anh\n\"y\" mnh hn, cn khi em ni\n\"Em mun n c rem c\" th anh\n\"y\" t t nhe!\nTh l h bt u \"nhp cuc\".\nNgi con gi rn ln \"Em mun\nn nem c! \"Em mun n nem c!\n\"Em mun n nem c!\" V khi\nnghe ci ging ku ct kt\nnhiu qu c vi th tho \"Em\nmun n c rem c! Em mun n\nc rem c! Em mun n c rem\nc!\"\nBng c ting ni ca c ch \ndi vng ln:\n- Ti my c n nem hay n c\nrem g th k ti my, nhng phi\ncn thn ch \"c rem\" n chy\nxung y ngi tao ri y\nn!!!!! =))))\n-4-","language":"notdetect","sentiment":"Mixed","author":"Truyệncườicấmtrẻemdưới18T","author_text":"Truyệncườicấmtrẻemdưới18T","md5_text":"49a9e99a181670e96bb3a2f00498e61a","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758730592256},{"id":"353114077","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004979554581","authorlink":"HạtCátVôDanh","date":"2013-01-06T13:56:31Z","topic":"Kia K9","topic_text":"Kia K9","text":"HU QU CA MT CN GIN\n( c ngm.. ai cng nn c 1 ln.. au tht y.. rt ngha )\n* * *\nHu qu ca mt cn ginTrong khi mt ngi n ng ang nh bng chic xe ca ng ta, th a con trai ln 4 tui ca ng ta nht ln mt vin si v v nhiu ng ln ln pha bn kia cnh chic xe ca ng ta. Trong lc gin d, ngi n ng nm ly bn tay ca a con v nh mnh nhiu m khng nhn rng ng ta ang dng mt ci c l vn vt nh.\nKt qu l trong bnh vin, a con trai ca ng ta mt i ht cc ngn tay ca mnh do qu nhiu ch gy. Khi a con trai nhn thy i mt b mnh biu l s au n, a b bn hi: \"B i ! Khi no cc ngn tay ca con mi c th mc tr li ?\" Ngi b cm thy rt au n v khng ni c li no; ng ta tr li chic xe ca mnh v n tht nhiu.\nTrong khi ang b lng tm dn vt v ang ngi i din pha hng ca chic xe , ng ta cht nhn thy nhng vt xc do chnh a con trai ca ng ta v rng: \"B i ! Con yu B nhiu lm !\"\nV mt ngy sau , ngi n ng quyt nh t st\nCn gin v Tnh yu khng bao gi c gii hn, nn xin hy chn Tnh Yu c mt cuc sng ti p v ng yu, v xin hy nh iu ny:\n vt th s dng, cn con ngi th yu thng.\nVn ca th gii ngy nay th ngc li: con ngi th s dng, cn vt th yu thng.\nHy lun c nh nhng ngha ny :\n- Hy cn thn vi nhng ngh ca bn, v bn s ni chng.\n- Hy cn thn vi nhng li ni ca bn, v bn s thc hin chng.\n- Hy cn thn vi nhng hnh ng ca bn, v chng s l thi quen ca bn.\n- Hy cn thn vi nhng thi quen ca bn, v chng s l c tnh ca bn.\n- Hy cn thn vi nhng c tnh ca bn, v chng s quyt nh s mnh ca bn.","language":"notdetect","sentiment":"Positive","author":"HạtCátVôDanh","author_text":"HạtCátVôDanh","md5_text":"27de1af92522f1786cf1627d27dceaeb","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758731640832},{"id":"353114784","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004920481393","authorlink":"XinlộiVìAnhnghèo","date":"2013-01-06T13:35:56Z","topic":"Kia K9","topic_text":"Kia K9","text":"Git l kh ri, t b mi xinh\nong m chi tnh mnh\nRi ngy bun s qua, bit tnh yu n ni xa vi \nCn li k nim th thi.\nNhn li pht giy, i mnh bn nhau\nAi ng u ngy sau\nT hi lng th sao, tri tim mt cm gic yu\nXin li anh khng l m\nNc mt anh ri, chc khng ni ln li\nCh cu xin em ng nu ko\nXa ht i em, nh cha tng c bao gi \nng qua nhng ni ta hn h\n\nNhn li pht giy, i mnh bn nhau\nai ng u ngy sau\nT hi lng th sao, tri tim mt cm gic yu\nXin li anh khng l m\nNc mt anh ri, chc khng ni ln li\nCh cu xin em ng khc na\nHy nn i em, xin em qun n au ny\n ri xa tn anh i ngi i!\n \nBng Ti Ly Caf","language":"notdetect","sentiment":"Mixed","author":"XinlộiVìAnhnghèo","author_text":"XinlộiVìAnhnghèo","md5_text":"e89efad61d991ad050064a3e149da1b8","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758732689408},{"id":"353114005","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003892451136","authorlink":"QuangThanh","date":"2013-01-06T13:56:58Z","topic":"Kia K9","topic_text":"Kia K9","text":"chng c ai nc chn qu...........","language":"notdetect","sentiment":"Mixed","author":"QuangThanh","author_text":"QuangThanh","md5_text":"14dabea287feac079cc6814c40403812","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758733737984},{"id":"353114271","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100002905367578","authorlink":"TranHuy","date":"2013-01-06T13:55:47Z","topic":"Kia K9","topic_text":"Kia K9","text":"Chay xe bang 1 con mat.","language":"english","sentiment":"Mixed","author":"TranHuy","author_text":"TranHuy","md5_text":"30105b65ab063f31d12a74346967c404","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758733737985},{"id":"353114547","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003229282587","authorlink":"ToànMinhTrần","date":"2013-01-06T13:45:00Z","topic":"Kia K9","topic_text":"Kia K9","text":"Tht nghip lai sp tt ri.ai bit ch nao tuyn nv k?pao minh vi.","language":"notdetect","sentiment":"Mixed","author":"ToànMinhTrần","author_text":"ToànMinhTrần","md5_text":"38213b0750582ab2adfb69fddfe4d74b","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758734786560},{"id":"353114686","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003075821893","authorlink":"AdilLotangLatogZa","date":"2013-01-06T13:34:56Z","topic":"Kia K9","topic_text":"Kia K9","text":"Aducc digin bagedc ujann","language":"unknown","sentiment":"Mixed","author":"AdilLotangLatogZa","author_text":"AdilLotangLatogZa","md5_text":"281bd98a7027577bae57c8463e66a667","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007758735835136},{"id":"353115077","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003242037368","authorlink":"KhánhNguyễn","date":"2013-01-06T13:31:16Z","topic":"Kia K9","topic_text":"Kia K9","text":"sau 7 nm mi c ba cm gia nh y ","language":"notdetect","sentiment":"Mixed","author":"KhánhNguyễn","author_text":"KhánhNguyễn","md5_text":"abfd41443620d07726f9c828b0082482","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768286265344},{"id":"353115753","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004562795610","authorlink":"DungDung","date":"2013-01-06T13:02:36Z","topic":"Kia K9","topic_text":"Kia K9","text":"t hng cui cng trc Tt v n nh...... chm bi nhung, da bo lt bng cc cc dy, leg dc ren thm mu mi, c bit l qun ha tit Houndstooth hot nht nm nay ln u tin v shop vs kiu dng mi :x\nHn cc gi mai chp hnh r ngy kia up hnh naz :*\nCh c 1 tin bun l qun tt lt n ht sch mu m, n da chn cng k cn 1c... ch vt vt c 2c lng chut vs 2c mu nu, 1c mu tm thi :((.............. gi no qua nhanh th cn naz :x","language":"notdetect","sentiment":"Mixed","author":"DungDung","author_text":"DungDung","md5_text":"8bb2a051467aad3847e7487fc97c726f","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768288362496},{"id":"353115179","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100002925488379","authorlink":"TrinhNgocDuy","date":"2013-01-06T13:28:36Z","topic":"Kia K9","topic_text":"Kia K9","text":"M SUY T\nKHI MN M BUNG XUNG L LC NGI TA SNG THT VI LNG MNH NHT ,NHM NHI MT TCH TR THY NG NGT U MI ,NHN RA BU TRI RNG LN BAO LA NGM NHNG V SAO V T HI KHNG BIT MNH L NGI SAO NO NH ?MNH KHNG L NGI SAO NO TRN BU TRI KIA V MNH C N V L LOI ,MNH CNG M NHT CH U C SNG LP LNH TH KIA.\nKHI MN M BUNG XUNG MNH TR V L NG MNH KHNG S VIN NG THNG ,KHNG QUN L O LT ,KHNG PHI GNG MNH LN SNG ,KHNG PHI CI CI NI NI KHI M TM HN LNG TRU NI BUN \nM L LC YN TNH NHT TRONG MT NGY KHNG N O VI V ,KHNG TP LP CH C TING GI THI VI VU V TING NHNG L KH RI LNG L \nM L LC TI C L CHNH TI ,C BUN ,C LM NHNG G MNH THCH M KHNG PHI NHN NHNG NGI XUNG QUANH XEM H NGH G \nM CNG L LC TI THY C N NHT ,TI QUEN VI BNG M V S YN TNH CA N NHNG TI LI S NI C N ANG M GIT CHT TI TNG NGY","language":"notdetect","sentiment":"Mixed","author":"TrinhNgocDuy","author_text":"TrinhNgocDuy","md5_text":"1659efd0873a3f42ad12be30e22b491c","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768289411072},{"id":"353115464","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=326788774078382","authorlink":"NamThanhNữTúThanhHàHảiDương","date":"2013-01-06T13:16:17Z","topic":"Kia K9","topic_text":"Kia K9","text":"hnay ad sang ha bc :) nhiu ban vui tinh lm nhe mem nao ha bc kg :)\n-bi<3-","language":"notdetect","sentiment":"Positive","author":"NamThanhNữTúThanhHàHảiDương","author_text":"NamThanhNữTúThanhHàHảiDương","md5_text":"9293a9c38022a4c51eb33b22d32cd4c5","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768297799680},{"id":"353115918","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003111704258","authorlink":"VênhVênhTiểuThư","date":"2013-01-06T13:03:43Z","topic":"Kia K9","topic_text":"Kia K9","text":"Ai chi Garena vi minh khng? :x","language":"notdetect","sentiment":"Mixed","author":"VênhVênhTiểuThư","author_text":"VênhVênhTiểuThư","md5_text":"a4b402cf94e3bebee5d54ba59d46f38a","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768297799681},{"id":"353115418","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003192500471","authorlink":"NyNyNguyễn","date":"2013-01-06T13:18:19Z","topic":"Kia K9","topic_text":"Kia K9","text":"cuc sng vui tr li sau nhng ngy lng i........... :D","language":"notdetect","sentiment":"Positive","author":"NyNyNguyễn","author_text":"NyNyNguyễn","md5_text":"01d16441e050593c8160a866edec094d","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768298848256},{"id":"353115979","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004419942705","authorlink":"HoaBồCôngAnh","date":"2013-01-06T13:06:10Z","topic":"Kia K9","topic_text":"Kia K9","text":"Tr tag cho Zt Ln nek\nNhng Ai Lit Qua u Phi Lm Ht Nghen :\"> Hem Lm Xui C Nm (2013) =))\nTui b tag nn hem bt g ht thng cm dm tui :)) ng cho gch - nh nhu qu i =))\n-----------------------------------------------------------------------------\n1/ Tr li thnh tht 100 cu hi di y\n2/ Send list cho nhng ngi m bn yu qu ^^\n~~~START~~~\n1. Tn:TT ^^\n2. Mu sc yu thch : xanh,do,tim,vang oi tat ca cac mau :\">\n3. Ni di: luon luon nen coi chung:))\n4. Bi ht mi nghe: qu cho anh -miu le\n5. Idols: nobody\n6. Khi b stress: tu ki :))\n7. Tht tnh: chac roi :))\n8. Hn ngi khc: roi ak :\">\n9. Na m hm qua lm g: ngu chu lam gi choy >.<\n10. Trang web hay vo: Faceboo,mp3,zingme :\")\n11. Nickname: heo.,mat hi\n12. Cung hong o : xu nu\n13. Vt nui: hk thix con j hjt >.<\n14. Tc di hay ngn: tat nhien la` dai` roi....:\")*chop' chop'*\n15. Chiu cao:1m60\n16. Quen bn mi: thy gap nhau nc vs nhau hop thy mjnh toi vs nhau zay ak :\">\n17. Thay i h tn : khong:(\n18. Thng ng dy lc : con ga` no' bat' dau` gay' o` o' ooooooooooooooo >.<\n19. Thch n cm : khong an com thj an j :(( oaoaoaoa\n20. Thun tay: phai O.o\n21. Ung bia, ru: khong :((\n22. S ma : con phai hoy :\"(\n23. i ra ng mt mnh vo bui ti : it khi:)\n24. eo knh: it khi deo kinh,dj duong moi deo\n25. Thch lm bn cng nhng ngi: zui tinh,hoa dong\n26. Cng vic nh lm trong tng lai: hk bjt nua,chac co the la 1 osin cao cap\n27. Bun: hien dang rat buon,vay day':((\n28. B m: khong\n29. Mn th thao iu thch: hui nao gio rat get the thao,thjx danh cau long\n30. Ngi bn thn nht: ai cung than hjk ak:))\n31. Trng cy: khong co>.<\n32. Mun c bao nhiu a con: 1 dua chac cug dc roy,s kug dc ,nhug dug nhju wa,chjt xm\n33. Chn hc: dag rat chan\n34. Hay tm s: nhju nguoi\n35. trong lp hay lm g: an,ngu\n36. Ght: ghet j la ghet j?:((\n37. Thch ra nc ngoi ko: thjk chu...thjk ms zo kai lop 12CLH01 nek :\">*iu lam co*\n38. nh thng lm g:an,ngu,phu nau kum:((\n39. Thch hc mn: thix mon nao ma khong co gi lien wan toi mon toan do\n40. Thch lm gi : lam nhung gi mjnh thjk,nhu di choi zoi bb,ng iu,dac bjet la an uong\n41. Thng i chi vs: ban tui a.:\")\n42. Ght lm g: lam nhung gi tui hk tjk\n43. T k:khi bun:(\n44. Ght hc mn:mon ton,get nhu dien lun >.<\n45. Nu bn tht bi: co len,lam laj tu dau\n46. Lm chc g trong lp:...hk bjt\n47. Hay nt vs: ban tui :))\n48. Ch nht thng lm g:an,ngu,phu nau kum,cu nzay ma phat huy\n49. Bn ang lm g: tra tag nak>.<\n50. S: ma,ran,chuot\n51. Nu mn g gii nht :hk bjk nua,\n52. Thch nht: tien vang kim cuong va cac' laoi chau bau' khac'...v...v\n53. Bit bi khng: khong,so chit duoi lam>.<\n54. C nhiu tin khng :khong ,dag ngheo,khong 1 xu dnh ti\n55. ang s ci g: thi hok du diem:(( so hoc lai:((\n56. S trng l g: an,va ngu:))\n57. Ti m gi ng: tuy bua...\n58. Bui sng n g: tuy bua,aj nau gj thi an do:))\n59. Sng u: bjh trung tay ,q2\n60.Trng thi ca bn by h : met moi...\n61. By h mn lm: can' ai kia\n62. nh tag nhng ai: cac' t.y cua tui\n63. Hc gii khng: hk bjt nua,chac la rat do:))\n64. Bn gin ngi khc lu ko : vai phut' la het:))\n65. Bn c beautiful/handsome ko: khong:))\n66. Chu au c ko: dau long thi dc...con co the thi hk\n67. Bn l ngi yu ui hay mnh m: tuy m.n nghi thui\n68. Thch i chi ko: thich^^\n69. Qu ai nht: ai cung qui\n70. By h bn ang t hi iu g: rat nhju:((\n71. Mun i u nht:di dau ma tui co the wn tat ca:((\n72. V mc in r ca bn: bay h,hien tai la 100%\n73. Lc tm hay lm g:hat,khog hju tai s,tam la lai thix hat:))\n74. La ngi khc: rat' nhiu roi:))nhung hok y' xau'\n75. yu cha: luc trc co,pay gio thi chua:(\n76. C thch b dnh tag k: 50/100\n77. Ngi pm gn y nht: ban trong lop CLH01\n78. C thch c truyn ko:khong,cam no la mun ngu lun:))\n79. Thch hoa g: hong,phong lan,...\n80. Thng mc g : bat cu cai' gi thay' hop:))\n81. Chi c nhc c g:khong bjt choi ji hjt\n82. Bn hay u: phong tui\n83. Bn thch ma no nht: thu - ng - xuan\n84. Mun quay li vo thi im no trong qa kh:thoi hoc sinh,kai thoi ao trang mong mo:(\n85. Ngy bn thch nht: 5\n86. Mun gp ai nht: nobody\n87. Bn thy mnh c c c k : doi khi:))\n88. Nhc ch ca bn l: tri ky,khong the khong thay nho,yeu anh lam\n89. Thch nghe bi ht no: tuy tam trang:))\n90. Bn c s cht k: so chu:((\n91. Thch l hi no nht: tet\n92. C hay nh bn : co',hui cap ba hay an hjp ngkhac:))kakak\n95. C chn bn chi k : khong,thich thi mjh den voi nhau:))\n96. Ai t tn cho bn:hui nho lam s bjt dc aj da dat ten cho mjh:\">\n97. Mong mun ca bn: co' 1 cuoc song' tot' sau nay:\"> >.<\n98. Hm qua lm g: di dam cuoi o nha Ngoc,di cung Zt Ln,Zino Hung Ozawa,Phm Quang Thang, Phuong Thao Nguyen Thi, Huy Doan\n99. Cm xc hin ti: buon` nat' long`:((\n100. Mun ni vs nhng ngi dnh tag: lam on tra tag dum,co' moi cai' tag de tra cho zui ma cung luoi>.<","language":"notdetect","sentiment":"Positive","author":"HoaBồCôngAnh","author_text":"HoaBồCôngAnh","md5_text":"2fc8f98d64a026e62ffa8bfd90cb425d","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768299896832},{"id":"353115112","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100002894563703","authorlink":"ÚtThủyNgôNguyễn","date":"2013-01-06T13:30:54Z","topic":"Kia K9","topic_text":"Kia K9","text":"Tra nay chng kin cnh 2 ngi ph n cu x ln nhau, ph bng nhc m nhau ginh cm tha ca cng ty, tht s rt xt xa...\nC th ngi ta ang nh mt i nhn phm ca chnh mnh ch v cuc sng cn tt qu kh khn, c th ...ni ngn gn trong 2 t CM - O...\nCm thy cuc sng ca mnh nh vy l d chu ri, thi th c gi gn Nhn phm chng phi chu h ly ca cuc sng qu i x b i ....\nThng thay nhng kip ngi ngoi tri ging bo kia...\nTic thay tay mnh mm yu qu...","language":"notdetect","sentiment":"Mixed","author":"ÚtThủyNgôNguyễn","author_text":"ÚtThủyNgôNguyễn","md5_text":"4d45b0aa6d9a0802eda40e919bf56a2d","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768303042560},{"id":"353115329","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004013799752","authorlink":"VũTiếnNam","date":"2013-01-06T13:23:28Z","topic":"Kia K9","topic_text":"Kia K9","text":"Hihi gap duoc Hoai Thuong ngoai doi rui. Hihi xinh ghe. Hiiii","language":"unknown","sentiment":"Mixed","author":"VũTiếnNam","author_text":"VũTiếnNam","md5_text":"639c4dd84cc10458687d66280286c926","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768303042561},{"id":"353115845","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004136529644","authorlink":"ĐạiTiểuThư","date":"2013-01-06T13:03:59Z","topic":"Kia K9","topic_text":"Kia K9","text":"Sao anh khng la di em\nKhng cho em mt A CON\nA CON ca anh v em m thi\n.\n.\n.\nAnh, lm em cung in v yu\nLm em \"tt\" v nh\nLm em \"tt\" v khc\nLm em \"tt\" v...\n( Ngi in yu-MH) \np.s: \"tt\" l j y=]]]","language":"notdetect","sentiment":"Mixed","author":"ĐạiTiểuThư","author_text":"ĐạiTiểuThư","md5_text":"fb1e3f46897b574996777dd4d42b4964","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768304091136},{"id":"353115359","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100000993945327","authorlink":"HoaThienTam","date":"2013-01-06T13:23:14Z","topic":"Kia K9","topic_text":"Kia K9","text":"thoi cong nghe co khac. minh di chua ve toi nha giang ho da post hinh cuoi day mang roi hjhj","language":"notdetect","sentiment":"Mixed","author":"HoaThienTam","author_text":"HoaThienTam","md5_text":"ef1b8046d0aee0538b086996232e65a6","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768321916928},{"id":"353115916","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100002928744466","authorlink":"HoaHoahy","date":"2013-01-06T13:04:34Z","topic":"Kia K9","topic_text":"Kia K9","text":"in mt!","language":"notdetect","sentiment":"Mixed","author":"HoaHoahy","author_text":"HoaHoahy","md5_text":"79f3297b71eb1a2ef13b2daf7b8df159","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768322965504},{"id":"353115759","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100001899398143","authorlink":"TrungNguyễn","date":"2013-01-06T13:12:06Z","topic":"Kia K9","topic_text":"Kia K9","text":"Lnh! T dng mun cm gic t t u li.. m lng m d xua i ci lnh gi ^^","language":"notdetect","sentiment":"Mixed","author":"TrungNguyễn","author_text":"TrungNguyễn","md5_text":"29709c87d51f41c152cfbb625da90e4d","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768322965505},{"id":"353115099","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004632631866","authorlink":"NguyễnTrúcLy","date":"2013-01-06T13:31:28Z","topic":"Kia K9","topic_text":"Kia K9","text":"th l ngy cui cng trong chui ngy chn tri qua..................ni chung hm nay cng tm c khng nh 2 ngy kia cng an i..................v ngy mai l ta c i hc ri......................mong rng mi chuyn s tt p..................i hc ng ngha l i hc","language":"notdetect","sentiment":"Mixed","author":"NguyễnTrúcLy","author_text":"NguyễnTrúcLy","md5_text":"0cac5cf87f7544830c4f45b089d948bb","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768324014080},{"id":"353115236","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100000014765184","authorlink":"TuyếnĐỗ","date":"2013-01-06T13:30:16Z","topic":"Kia K9","topic_text":"Kia K9","text":"ZZZZ. Gi i cn mt in thoi ch.:(.\nMi ngi cho mnh xin li s nh Lan Anh, Bluewave Pham, Lin Trn, Phm Trung Hu, Nhn Hong, Giang Pham, Anh Khoa, Nguyn Khi, Bi i, Linh Hn","language":"notdetect","sentiment":"Negative","author":"TuyếnĐỗ","author_text":"TuyếnĐỗ","md5_text":"2b7e4924a44ff87308f321590bf4fde6","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768325062656},{"id":"353115896","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100000193383583","authorlink":"ThànhCông","date":"2013-01-06T13:04:50Z","topic":"Kia K9","topic_text":"Kia K9","text":"Ngi ta thng thch ngh v nhng k nim v ch c nhng k nim y l khng thay i theo thi gian...\n( copied by Thnh Cng)","language":"notdetect","sentiment":"Mixed","author":"ThànhCông","author_text":"ThànhCông","md5_text":"f17592631321ccffad835a247f9065b2","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768325062657},{"id":"353115980","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004299339873","authorlink":"LuacojNguyen","date":"2013-01-06T13:03:03Z","topic":"Kia K9","topic_text":"Kia K9","text":"Chung tjnh la faj chap nhan tjnh chung.\nTo vua dj h0c m0t' do.ba c0n thay co dung ko.hjhj.","language":"unknown","sentiment":"Mixed","author":"LuacojNguyen","author_text":"LuacojNguyen","md5_text":"39821e4d04ce6bd3c7908c204d7dc3b2","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768326111232},{"id":"353115338","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003224657283","authorlink":"LongNguyen","date":"2013-01-06T13:24:47Z","topic":"Kia K9","topic_text":"Kia K9","text":"c e no cha c ngi yu cn c ngi yu trong ma ng lnh ny khng :v ?","language":"notdetect","sentiment":"Mixed","author":"LongNguyen","author_text":"LongNguyen","md5_text":"64aacf5be84d65cbf0a7a10a43705171","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768327159808},{"id":"353115183","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100002138301139","authorlink":"HạtĐỗ","date":"2013-01-06T13:21:31Z","topic":"Kia K9","topic_text":"Kia K9","text":"Bng dng thy nh b v nhn nht...bng dng mun khc...ri bng dng thy chn..i khi chnh bn thn cng khng hiu ni triu chng \"bng dng\" ny...C l mi khi mnh ln ln thm mt cht, cm xc ca mnh vn khng nhiu thay i, vn l phn khch, xao xuyn, bi hi, nhng trong cm xc y c t nhiu nhng trn tr, suy t...i khi thy mnh gi trc tui , i khi li thy mnh nh mt a con nt mi khng thnh ngi ln c...","language":"notdetect","sentiment":"Mixed","author":"HạtĐỗ","author_text":"HạtĐỗ","md5_text":"ae34d1cd4d91fefaf3a6ffd9b2f6e640","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768328208384},{"id":"353115702","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100001964883001","authorlink":"DuyThông","date":"2013-01-06T13:10:25Z","topic":"Kia K9","topic_text":"Kia K9","text":"Tm t tn t tnh tan tc\nMan mc mnh mng mi mng m\n n u a y i n c\nThao thc thn thng thy thm thng\n..........................$.......................................","language":"notdetect","sentiment":"Mixed","author":"DuyThông","author_text":"DuyThông","md5_text":"ee232eea0b7191b1e479a2c59c2ab60e","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768329256960},{"id":"353115842","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004094078764","authorlink":"ChuanKim","date":"2013-01-06T13:07:30Z","topic":"Kia K9","topic_text":"Kia K9","text":"Hey, chn, chn, chn hc m ch hnh, khng bit th no na y mai thi ri m :((\nTm li l my c chu hc khng th bo x(","language":"notdetect","sentiment":"Mixed","author":"ChuanKim","author_text":"ChuanKim","md5_text":"bde196c30fdf5a34adfe9b77ef8a83f1","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768492834816},{"id":"353115885","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003966534377","authorlink":"ThanhLanPhan","date":"2013-01-06T13:00:03Z","topic":"Kia K9","topic_text":"Kia K9","text":"1 ngi chn 1 cuc sng t nht, khng n o, khng vi v, khng nhiu bn b...ch cn 1 ngi nhng l bn thn thit, cng nhau vt qua nhng vui bun l ri...nhng n 1 lc no khi khng cn ngi bn na th li tr nn c n, l loi v cm thy tht nng n v hnh nh quen vi s c mt ca ngi bn mi khi vui bun ri.............","language":"notdetect","sentiment":"Mixed","author":"ThanhLanPhan","author_text":"ThanhLanPhan","md5_text":"a62b5e0b3e6758c64aab5e9ba3edcad6","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768493883392},{"id":"353115035","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003639574319","authorlink":"HanghipLuong","date":"2013-01-06T13:32:30Z","topic":"Kia K9","topic_text":"Kia K9","text":"t nhin nh ci gi l qu kh...................lm t au :((((((((","language":"notdetect","sentiment":"Mixed","author":"HanghipLuong","author_text":"HanghipLuong","md5_text":"8af7a51f20f1e1b258a1bd3d1b496b9d","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768494931968},{"id":"353115971","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004556945271","authorlink":"MaxĐặng","date":"2013-01-06T13:03:18Z","topic":"Kia K9","topic_text":"Kia K9","text":"Va i ni chuyn vi thy v chuyn ngi\nLiu ngi l c tht ?\nTht u ko thy b ng kia chy xe qut 1 ci z chn by h au qu\nHix t nhin thy cuc i xui xo qu ~\nMn khc gh","language":"notdetect","sentiment":"Mixed","author":"MaxĐặng","author_text":"MaxĐặng","md5_text":"23f30f7d8b2dde5c9ea919c0121e17ac","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768494931969},{"id":"353115342","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003297717175","authorlink":"BồcônganhTrongGió","date":"2013-01-06T13:18:40Z","topic":"Kia K9","topic_text":"Kia K9","text":"m ngc time v nh thui. nng chn wa ri. \nhaizzzzz","language":"notdetect","sentiment":"Mixed","author":"BồcônganhTrongGió","author_text":"BồcônganhTrongGió","md5_text":"2141740c33a99142e8f29f2a545a5b40","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768495980544},{"id":"353115937","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003919573500","authorlink":"LinhTrần","date":"2013-01-06T13:04:18Z","topic":"Kia K9","topic_text":"Kia K9","text":"Ta noi dao nay tich cc ln face chi ...thy ban mt cua 1 ngi. Haizzz yu ri chng? c ma hng mun vy u nha! huhuhu....","language":"notdetect","sentiment":"Mixed","author":"LinhTrần","author_text":"LinhTrần","md5_text":"5af6a5f4dbb260b41a6a3153d8b3f852","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768497029120},{"id":"353115717","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004467967493","authorlink":"PhuongNguyen","date":"2013-01-06T13:08:35Z","topic":"Kia K9","topic_text":"Kia K9","text":"i gn 2 thng ri hay sao, s qu, tgian i chm chm thi, ko mun phi i na cht no ht","language":"notdetect","sentiment":"Mixed","author":"PhuongNguyen","author_text":"PhuongNguyen","md5_text":"e7ee7665aa271caa4c1adf479f7e87ad","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768502272000},{"id":"353115147","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100002848488315","authorlink":"CopedangiuSokkon","date":"2013-01-06T13:21:42Z","topic":"Kia K9","topic_text":"Kia K9","text":"Cuc i nay bt cng qua y. Co nhiu ngi giau n chi, mua sm k pit tin la gi. Con qua nhiu ngi ngheo ang c gng vn ln..., vi tin...vi kai n...\nVi gia inh. Sau nay minh nht inh se giau co. Nhng k bao gi minh ging nhng con ngi giau kia.","language":"notdetect","sentiment":"Mixed","author":"CopedangiuSokkon","author_text":"CopedangiuSokkon","md5_text":"4cf8ed1e80e06478f54938309ac57d6c","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768502272001},{"id":"353115271","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100001658615342","authorlink":"XuânLiêmNgô","date":"2013-01-06T13:25:07Z","topic":"Kia K9","topic_text":"Kia K9","text":"Chiu nay , em bit khng , anh li mt mnh lang thang trn ph , do qua nhng con ng mt thi gn vi k nim ca hai chng ta Anh t nh vi lng, con trai phi mnh m Vy m bao k c xa hin v, nc mt li ri Anh t hi, c phi tnh yu ca chng mnh khng th vt qua ci quy lut nghit ng tnh ch p khi cn dang d hay l s yu ui , bt lc ca hai chng ta ? D th no th gi y mnh cng xa nhau ri . Em nh ng my v tnh , mang ma n ti mt tm hn anh , ri gi li mang em i , li cho anh mt khong tri c n , vng lng \n\nAnh khng trch em , ch trch chng mnh c duyn m khng n","language":"notdetect","sentiment":"Mixed","author":"XuânLiêmNgô","author_text":"XuânLiêmNgô","md5_text":"9f49827ceb28066426d3a4b161dadb52","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768503320576},{"id":"353115653","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003902638805","authorlink":"PekeoHit","date":"2013-01-06T13:14:29Z","topic":"Kia K9","topic_text":"Kia K9","text":"bun lam rat bun bun mun khoc lam","language":"english","sentiment":"Mixed","author":"PekeoHit","author_text":"PekeoHit","md5_text":"3afe5a058dac62396a6ecb9ffa0a2252","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768504369152},{"id":"353115019","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004265680853","authorlink":"NeymarVàTôi","date":"2013-01-06T13:32:41Z","topic":"Kia K9","topic_text":"Kia K9","text":"Cn ma ngang qua khin em nht nha.\nChng mt li cho ngi vi ri b i khng chia li cho con tim anh thm bao yu mm!\nCn ma ngang qua cun i bao yu thng.\nCn ma ngang qua khin con tim mt phng hng\nCn ma Kia nng ht,i ma thm nng ht..\nEm ri xa i bn tay trong con tim ca anh.","language":"notdetect","sentiment":"Mixed","author":"NeymarVàTôi","author_text":"NeymarVàTôi","md5_text":"327158191735c23b8207ebca73f2f19b","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768519049216},{"id":"353115241","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004584600174","authorlink":"HanHuy","date":"2013-01-06T13:26:04Z","topic":"Kia K9","topic_text":"Kia K9","text":"Dkm. kaj nay lm tao ngj vl. tao chuj ng tao ju thuog nhat. nhug den bjo doj voj tao thj kaj l0n j kung x0ng het. chung may thjh the l0n nao kug dc.","language":"unknown","sentiment":"Mixed","author":"HanHuy","author_text":"HanHuy","md5_text":"d257cb8097b47d24b4b77435ed5298b0","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768520097792},{"id":"353115502","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004581546789","authorlink":"CôngTửThànhVinh","date":"2013-01-06T13:17:36Z","topic":"Kia K9","topic_text":"Kia K9","text":"Nc mt ca...con trai ???\n \nL khi h bun rt nhiu, bao tm trng dn nn ri mt lc no o khc nh a tr! Con ngi ai cng c tri tim, ai cng c suy ngh v cm nhn ca ring mnh nn h c th khc cho tho mn bn thn khng cn bit mnh l ai!\n \nu ch ring con gi mi c khc, d cho c mnh m v cng rn n mc no cng s c lc tan chy v git l h tun ri\n \nBn thn con trai sinh ra mang tnh cht l tr ct, l bn lnhs mnh m trong con ngi h l mt ci mc nhng trong tm h cng yu ui nh con gi vy thi.\n \nH khc khi yu ai mt cch m su m khng c p tr, khc khi khng c bn cnh ngi m mnh yu thng, khc khi khng th lm g cho h, v khi ngi thn h ri xa tht xa ni ny.\n \nSuy cho cng th ai cng c tm trng nhng che y cm xc th con trai l ngi gii nht...V h cng l con ngi,m l ngi th ai chng c...nc mt....","language":"notdetect","sentiment":"Mixed","author":"CôngTửThànhVinh","author_text":"CôngTửThànhVinh","md5_text":"8c9d199bd0ef1f8b8bb050e4dcac0f4c","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768520097793},{"id":"353115334","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100000290186182","authorlink":"RosMagma","date":"2013-01-06T13:17:32Z","topic":"Kia K9","topic_text":"Kia K9","text":"her her t nhin t xi li c vui g :))","language":"notdetect","sentiment":"Mixed","author":"RosMagma","author_text":"RosMagma","md5_text":"3f4a7aa784ceb1e020d7fb51f967ad0b","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768521146368},{"id":"353115767","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=736215222","authorlink":"AnhHai","date":"2013-01-06T13:02:28Z","topic":"Kia K9","topic_text":"Kia K9","text":"Mt du khch i v mt vng qu n thy c mt nng tri. Du khch n gn v hi bc nng dn:\n- Ny bc hai con b n mt ngy bao nhiu kg c?\nBc nng dn hi:\n- ng hi con trng hay con en?\nDu khch:\n- V d con en i.\n- Ht mt ngy 10kg c\n- Th cn con trng?\n- , con trng cng ht 10kg c\nDu khch hi tip\n- Th mt ngy bc vt c bao nhiu sa?\n- Con trng hay con en?\n- Con trng chng hn.\n- con trng 1 ngy 5 lt sa\n- Th con en?\n- Cng 5 lt.\nDu khch tn ngn v hi li\nNy bc, sao ny gi hai con ging nhau m bc c hi con trng hay en, sao khng tr li gp chung li cho khe.\nBc nng dn ni.\n- ng ko bit thi, ti v con trng l ca ti.\nDu khch ln \n- ra th. Vy th con en l ca ai?\n- Con en cng ca ti =))))))))))","language":"notdetect","sentiment":"Mixed","author":"AnhHai","author_text":"AnhHai","md5_text":"b44e805e16c5be05284633ae8c4587fa","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768522194944},{"id":"353115844","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003111969456","authorlink":"TiếnLợi","date":"2013-01-06T13:07:07Z","topic":"Kia K9","topic_text":"Kia K9","text":"Cht tht sp n tt ri... tt 2013 vn cha c ngi yu khng bit tt 2014 s th no y... ang ri vo tnh trng ri ;)) =))","language":"notdetect","sentiment":"Positive","author":"TiếnLợi","author_text":"TiếnLợi","md5_text":"4c61db8d47588adbc44cbd2d997bc4ee","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768523243520},{"id":"353115941","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004742893702","authorlink":"HeoTocxoan","date":"2013-01-06T13:06:25Z","topic":"Kia K9","topic_text":"Kia K9","text":"Thank you for leaving me.themselves as never before what happened.I will walk on my feet.I will go my way.go somewhere can find your pictures just like the old days.just stay with your happiness.goodbye.","language":"english","sentiment":"Positive","author":"HeoTocxoan","author_text":"HeoTocxoan","md5_text":"b53caaf77ad0b2ec1c043053f2565de0","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768524292096},{"id":"353115086","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004645720680","authorlink":"ĐặngNgọcNhưPhước","date":"2013-01-06T13:31:06Z","topic":"Kia K9","topic_text":"Kia K9","text":"Tm Li\nng gp: \nChng mun nghe g\nChng mun nhn khc\ni mt lun tm...tm v\nNgi nht mi ci\nNgi nh mt n hng\nNgi khc trong lng\nHt ri...\nTm li i hy tm li trong mi ngi \n ta khng thy ta nh lc ny\nng xa qu di ri ta mt nhoi v ta khng c bn nhau na ri.\n\nNgi m sao tn\nNgy y ng n\nng n vi vng...ht ri\nNgi xung y bn\nCn chn ru kia\nCn ht m ngy...ht ri\nTm li i hy tm li trong mi ngi ta khng thy ta nh lc ny \nng xa qu di ri ta mt nhoi v ta khng c bn nhau na ri...\n \n\nTm li i hy tm li trong mi ngi ta khng thy ta nh lc ny\nng xa qu di ri ta mt nhoi v ta khng c bn nhau na...\nTm li i hy tm li trong mi ngi ta khng thy ta nh lc ny\nng xa qu di ri ta mt nhoi v ta khng c bn nhau na...\nTm li i hy tm li trong mi ngi ta khng thy ta nh lc ny \nng xa qu di ri ta mt nhoi v ta khng c bn nhau na ri...na ri...na ri i i i...","language":"notdetect","sentiment":"Mixed","author":"ĐặngNgọcNhưPhước","author_text":"ĐặngNgọcNhưPhước","md5_text":"3b904893822e521f4875a731ef050680","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768525340672},{"id":"353115635","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=258124047544025","authorlink":"HộiDotAViệtNamAntiHackmap","date":"2013-01-06T13:12:30Z","topic":"Kia K9","topic_text":"Kia K9","text":"Test cc tnh hung cho mem ci nh :3\n1.Bn cm thy c ch nht l khi?\nA) ang dota m bng dng cp in\nB) Combat d th b b m bt i sai lm vic nh\nC) ang late th bng nhin anh ch e nhy ra ginh my c vic\n\n2. Th loi game th no m bn ght nht ? \nA) nh theo phong cch \"siu nhn\" ( Thch i 1 mnh lm siu nhn nhng n khi team combat th ch thy u ! n khi b team n p do i l th li hi sao team k chu ln cu )\nB) Th loi hay chi team :-j thua cng chi thng th ni l do mnh nn mi thng\nC) HM\n\n3. Bn thch chi dota u nht\nA) nh qun cng m bn :3\nB) Reg gold member ri chi nh ( li i lm )\nC) Mang lap qua nh thng bn nh dota vi n :\">\n\nNh test ad check li kt qu nh :3\n-Zu-","language":"notdetect","sentiment":"Positive","author":"HộiDotAViệtNamAntiHackmap","author_text":"HộiDotAViệtNamAntiHackmap","md5_text":"8f733837c776cbdb8d4c7b45478266c8","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768526389248},{"id":"353115436","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100000471782174","authorlink":"ThanhHaiTrieu","date":"2013-01-06T13:16:29Z","topic":"Kia K9","topic_text":"Kia K9","text":"v nh bnh an ri hi cc chin hu Phan Thanh Ton Nguyt D Tran Duc Linh bla..bla... Noi chung l Ton bnh ni xng xo wa', \" e ch c cho\", ra m i c on b i x lui ngi sau gt g gt g ch m s rt khn ik =)) Noi chung l bun ng ri y :))","language":"notdetect","sentiment":"Positive","author":"ThanhHaiTrieu","author_text":"ThanhHaiTrieu","md5_text":"a2ef9217af4bd62f4112a6f0e853e2c6","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768526389249},{"id":"353115770","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003809841354","authorlink":"VũTrần","date":"2013-01-06T13:04:58Z","topic":"Kia K9","topic_text":"Kia K9","text":"2 ngy na l cho x yu v n tt sm ri ........ 25 fiive club tp hp........!","language":"notdetect","sentiment":"Mixed","author":"VũTrần","author_text":"VũTrần","md5_text":"2bbd77b09738a99de5ad1be9f5150aff","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768547360768},{"id":"353115154","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003996409355","authorlink":"SulyDoll","date":"2013-01-06T13:28:49Z","topic":"Kia K9","topic_text":"Kia K9","text":"Chg nao c ngi tt y ha tri ??! Chan qa =((","language":"unknown","sentiment":"Mixed","author":"SulyDoll","author_text":"SulyDoll","md5_text":"5b960dcc7528f75f4e75a9d10a41c895","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768548409344},{"id":"353115401","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003697947658","authorlink":"BíchThủyHoàng","date":"2013-01-06T13:16:54Z","topic":"Kia K9","topic_text":"Kia K9","text":"Hm ni ngy chi m en i ra....","language":"notdetect","sentiment":"Mixed","author":"BíchThủyHoàng","author_text":"BíchThủyHoàng","md5_text":"9eddee27e55f96cc8064f74f5d3f856d","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768548409345},{"id":"353115659","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100004528651915","authorlink":"PhamAn","date":"2013-01-06T13:10:45Z","topic":"Kia K9","topic_text":"Kia K9","text":"tnh hnh l mnh khu B i my a i...\nTrn Nguyn Phng Tr Pu Kina H L Nga Luong Kem Kem ng Th ThaoVi Tran Tran Thao Jade Nguyen Zenny Le,,,, tag thm gip An vs....","language":"notdetect","sentiment":"Mixed","author":"PhamAn","author_text":"PhamAn","md5_text":"6ac38b64ac5d0a271cc176791e6801ff","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768550506496},{"id":"353115862","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100000867159535","authorlink":"TùngDokusin","date":"2013-01-06T13:00:19Z","topic":"Kia K9","topic_text":"Kia K9","text":"nh mt ngoi qu :(","language":"notdetect","sentiment":"Negative","author":"TùngDokusin","author_text":"TùngDokusin","md5_text":"758ba937d5dc9f2dcc150db12945647c","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768550506497},{"id":"353115120","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100002847330491","authorlink":"LongVu","date":"2013-01-06T13:22:06Z","topic":"Kia K9","topic_text":"Kia K9","text":"Xe a con vo trng n Ka 3\nc 3 thng con nhn lnh ra ta \nng quan ta tht ln 1 ting\nn t hnh git cht i con\nXin cha m tha th cho con \nPhn lm con ch hiu cha trn\nCha m gi nay ai phng dng\nCon du hin sm chiu qunh hiu\nThng cho n em b th ngy\nVnh khn tang chng qun ln u\na t nht cm tay m hi\n\"M i m bao gi anh v?\"\n\"Anh khng v c na u con\nV ta kia bn anh ri\nVo ngy ny nm sau khi lm gi\nAnh li v qua ln khi hng bay\"\nDi sui vng con quyt ch n nn\np tn my xanh con quyt ch tr v\nCon tr v t trn cao con nhn xung\nDng m gy lng tht con au\nEm! C phi em khng em?\nang gc u khc thng cho ngi tnh\nNgi tnh gi y khng cn na \nn tnh xin tr li cho em....\nXin Vnh Bit Th Gii Mu Xanh........!","language":"notdetect","sentiment":"Mixed","author":"LongVu","author_text":"LongVu","md5_text":"d33c12397913d688a2f07a9ba5ac8a2f","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768551555072},{"id":"353115713","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=187955667946831","authorlink":"ĐạiHọcKinhTếKỹThuậtCôngNghiệp","date":"2013-01-06T13:08:20Z","topic":"Kia K9","topic_text":"Kia K9","text":"Hu qu ca mt cn gin\n\nTrong\nkhi mt ngi n ng ang\nnh bng chic xe ca ng ta,\nth a con trai ln 4 tui ca ng\nta nht ln mt vin si v v\nnhiu ng ln ln pha bn kia\ncnh chic xe ca ng ta. Trong\nlc gin d, ngi n ng \nnm ly bn tay ca a con v\nnh mnh nhiu m khng nhn\nrng ng ta ang dng mt ci c\nl vn vt nh.\nKt qu l trong bnh vin, a\ncon trai ca ng ta mt i ht\ncc ngn tay ca mnh do qu\nnhiu ch gy. Khi a con trai\nnhn thy i mt b mnh biu l\ns au n, a b bn hi: \"B\ni ! Khi no cc ngn tay ca con\nmi c th mc tr li ?\" Ngi\nb cm thy rt au n v\nkhng ni c li no; ng ta\ntr li chic xe ca mnh v n\ntht nhiu.\nTrong khi ang b lng tm dn\nvt v ang ngi i din pha\nhng ca chic xe , ng ta cht\nnhn thy nhng vt xc do\nchnh a con trai ca ng ta \nv rng: \"B i ! Con yu B nhiu\nlm !\"\nV mt ngy sau , ngi n\nng quyt nh t st\nCn gin v Tnh yu khng bao\ngi c gii hn, nn xin hy chn\nTnh Yu c mt cuc sng\nti p v ng yu, v xin hy\nnh iu ny:\n vt th s dng, cn con\nngi th yu thng.\nVn ca th gii ngy nay th\nngc li: con ngi th s\ndng, cn vt th yu\nthng.\nHy lun c nh nhng ngha\nny :\n- Hy cn thn vi nhng ngh\nca bn, v bn s ni chng.\n- Hy cn thn vi nhng li ni\nca bn, v bn s thc hin\nchng.\n- Hy cn thn vi nhng hnh\nng ca bn, v chng s l thi\nquen ca bn.\n- Hy cn thn vi nhng thi\nquen ca bn, v chng s l c\ntnh ca bn.\n- Hy cn thn vi nhng c tnh\nca bn, v chng s quyt nh\ns mnh ca bn.","language":"notdetect","sentiment":"Positive","author":"ĐạiHọcKinhTếKỹThuậtCôngNghiệp","author_text":"ĐạiHọcKinhTếKỹThuậtCôngNghiệp","md5_text":"57a8410818280a75a7df06dd17271c45","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768552603648},{"id":"353115505","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=1633577234","authorlink":"NhưTrang","date":"2013-01-06T13:15:18Z","topic":"Kia K9","topic_text":"Kia K9","text":"Trc khi noi xu ngi khac thi nn nhin lai ban thn minh co ep e hay khng a :))\nSng th ngi ta bit thi kha la nhuc y <3 =))","language":"notdetect","sentiment":"Positive","author":"NhưTrang","author_text":"NhưTrang","md5_text":"07247045e2812f3ac92ceb5f5d95e679","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768553652224},{"id":"353115240","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100000308699269","authorlink":"MaMútSiêuKool","date":"2013-01-06T13:23:43Z","topic":"Kia K9","topic_text":"Kia K9","text":"ci ngy g m xui vi c linh hn>.<\nSng th ng qun, ti chu i thi tr\nV gp ci thi th b trt t ( lm c ti n nh ci my)\nThi xong th b ng kia chi\" sao b thy tui cha i thi m hok kiu tui\"(mo con cng thi tr cha i_._)\nMi rn xe ra khi cng trng thn iu l b 1 chic xe ti d sau t xe=.=\nBn roi, quyt tm i mua tr sa n, ti qun \" ht tr sa r em\"=.=mi 3h m ht....\nHok mn v nh, i lng vng 4h n 1 t m qung, chy lng xng 6h n h tu, v ti nh li mn n thy l 8h 1 t cho lng vs 1 ly tr sa( tng thit hi l 49k)...n xong ci bn n nh cng lun\nV ti nh ng m cng cn gp 1 thng bn thi, ni g m\" em gi p p g \" (m i!!!) vt v nh cp tc...\nTm li 1 cu\" i khng nh l m\"","language":"notdetect","sentiment":"Positive","author":"MaMútSiêuKool","author_text":"MaMútSiêuKool","md5_text":"9e8491184daca93c83f13c46d37329cb","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768554700800},{"id":"353115463","title":"Kia K9","search_source":"Facebook","source_domain":"www.facebook.com","source_domain_text":"www.facebook.com","url":"http://www.facebook.com/profile.php?id=100003214484845","authorlink":"TinTỸTỞn","date":"2013-01-06T13:19:17Z","topic":"Kia K9","topic_text":"Kia K9","text":"tui hag tui vi da~ wa iu e \nj phai? de e roi xa tui the 1 lan nua~","language":"notdetect","sentiment":"Mixed","author":"TinTỸTỞn","author_text":"TinTỸTỞn","md5_text":"a83fd75bfdcae3bded8c6807787ac411","category":["Kia","Car"],"query_id":[60528109],"_version_":1425007768555749376}] ruby-amqp-bunny-b6569cd/spec/issues/issue97_spec.rb000066400000000000000000000076371464043542000223370ustar00rootroot00000000000000require "spec_helper" describe "Message framing implementation" do before :all do @connection = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", port: ENV.fetch("RABBITMQ_PORT", 5672)) @connection.start end after :all do @connection.close if @connection.open? end unless ENV["CI"] context "with payload ~ 248K in size including non-ASCII characters" do it "successfully frames the message" do ch = @connection.create_channel q = ch.queue("", exclusive: true) x = ch.default_exchange body = IO.read("spec/issues/issue97_attachment.json") x.publish(body, routing_key: q.name, persistent: true) sleep(1) expect(q.message_count).to eq 1 q.purge ch.close end end end context "with payload of several MBs in size" do it "successfully frames the message" do ch = @connection.create_channel q = ch.queue("", exclusive: true) x = ch.default_exchange as = ("a" * (1024 * 1024 * 4 + 2823777)) x.publish(as, routing_key: q.name, persistent: true) sleep(1) expect(q.message_count).to eq 1 _, _, payload = q.pop expect(payload.bytesize).to eq as.bytesize ch.close end end context "with empty message body" do it "successfully publishes the message" do ch = @connection.create_channel q = ch.queue("", exclusive: true) x = ch.default_exchange x.publish("", routing_key: q.name, persistent: true) sleep(1) expect(q.message_count).to eq 1 envelope, headers, payload = q.pop expect(payload).to eq "" expect(headers[:content_type]).to eq "application/octet-stream" expect(headers[:delivery_mode]).to eq 2 expect(headers[:priority]).to eq 0 ch.close end end context "with payload being 2 bytes less than 128K bytes in size" do it "successfully frames the message" do ch = @connection.create_channel q = ch.queue("", exclusive: true) x = ch.default_exchange as = "a" * (1024 * 128 - 2) x.publish(as, routing_key: q.name, persistent: true) sleep(1) expect(q.message_count).to eq 1 q.purge ch.close end end context "with payload being 1 byte less than 128K bytes in size" do it "successfully frames the message" do ch = @connection.create_channel q = ch.queue("", exclusive: true) x = ch.default_exchange as = "a" * (1024 * 128 - 1) x.publish(as, routing_key: q.name, persistent: true) sleep(1) expect(q.message_count).to eq 1 q.purge ch.close end end context "with payload being exactly 128K bytes in size" do it "successfully frames the message" do ch = @connection.create_channel q = ch.queue("", exclusive: true) x = ch.default_exchange as = "a" * (1024 * 128) x.publish(as, routing_key: q.name, persistent: true) sleep(1) expect(q.message_count).to eq 1 q.purge ch.close end end context "with payload being 1 byte greater than 128K bytes in size" do it "successfully frames the message" do ch = @connection.create_channel q = ch.queue("", exclusive: true) x = ch.default_exchange as = "a" * (1024 * 128 + 1) x.publish(as, routing_key: q.name, persistent: true) sleep(1) expect(q.message_count).to eq 1 q.purge ch.close end end context "with payload being 2 bytes greater than 128K bytes in size" do it "successfully frames the message" do ch = @connection.create_channel q = ch.queue("", exclusive: true) x = ch.default_exchange as = "a" * (1024 * 128 + 2) x.publish(as, routing_key: q.name, persistent: true) sleep(1) expect(q.message_count).to eq 1 q.purge ch.close end end end ruby-amqp-bunny-b6569cd/spec/lower_level_api/000077500000000000000000000000001464043542000213105ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/spec/lower_level_api/integration/000077500000000000000000000000001464043542000236335ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/spec/lower_level_api/integration/basic_cancel_spec.rb000066400000000000000000000044201464043542000275600ustar00rootroot00000000000000require "spec_helper" describe Bunny::Channel, "#basic_cancel" do before(:all) do @connection = Bunny.new(:user => "bunny_gem", password: "bunny_password", :vhost => "bunny_testbed") @connection.start end after :all do @connection.close if @connection.open? end let(:queue_name) { "bunny.queues.#{rand}" } it "returns basic.cancel-ok" do ch = @connection.create_channel q = ch.queue("", :exclusive => true) consume_ok = ch.basic_consume(q, "") cancel_ok = ch.basic_cancel(consume_ok.consumer_tag) expect(cancel_ok).to be_instance_of AMQ::Protocol::Basic::CancelOk expect(cancel_ok.consumer_tag).to eq consume_ok.consumer_tag ch.close end context "when the given consumer tag is valid" do let(:queue_name) { "bunny.basic.cancel.queue#{rand}" } it "cancels the consumer" do delivered_data = [] t = Thread.new do ch = @connection.create_channel q = ch.queue(queue_name, :auto_delete => true, :durable => false) consume_ok = ch.basic_consume(q, "", true, false) do |_, _, payload| delivered_data << payload end expect(consume_ok.consumer_tag).not_to be_nil cancel_ok = ch.basic_cancel(consume_ok.consumer_tag) expect(cancel_ok.consumer_tag).to eq consume_ok.consumer_tag ch.close end t.abort_on_exception = true sleep 0.5 ch = @connection.create_channel ch.default_exchange.publish("", :routing_key => queue_name) sleep 0.7 expect(delivered_data).to be_empty end end context "when the given consumer tag is invalid (was never registered)" do it "DOES NOT cause a channel error" do ch = @connection.create_channel # RabbitMQ 3.1 does not raise an exception w/ unknown consumer tag. MK. ch.basic_cancel("878798s7df89#{rand}#{Bunny::Timestamp.now.to_i}") ch.close end end context "when the given consumer tag belongs to a different channel" do it "DOES NOT cause a channel error" do ch1 = @connection.create_channel ch2 = @connection.create_channel q = ch1.queue("", :exclusive => true) cons = q.subscribe do |_, _, _| end ch2.basic_cancel(cons.consumer_tag) ch1.close ch2.close end end end ruby-amqp-bunny-b6569cd/spec/lower_level_api/integration/basic_consume_spec.rb000066400000000000000000000054101464043542000300040ustar00rootroot00000000000000require "spec_helper" describe Bunny::Channel, "#basic_consume" do before(:all) do @connection = Bunny.new(:user => "bunny_gem", password: "bunny_password", :vhost => "bunny_testbed") @connection.start end after :all do @connection.close if @connection.open? end it "returns basic.consume-ok when it is received" do ch = @connection.create_channel q = ch.queue("", exclusive: true) consume_ok = ch.basic_consume(q) expect(consume_ok).to be_instance_of AMQ::Protocol::Basic::ConsumeOk expect(consume_ok.consumer_tag).not_to be_nil ch.close end it "carries server-generated consumer tag with basic.consume-ok" do ch = @connection.create_channel q = ch.queue("", exclusive: true) consume_ok = ch.basic_consume(q, "") expect(consume_ok.consumer_tag).to match /amq\.ctag.*/ ch.close end context "with automatic acknowledgement mode" do let(:queue_name) { "bunny.basic_consume#{rand}" } it "causes messages to be automatically removed from the queue after delivery" do delivered_keys = [] delivered_data = [] t = Thread.new do ch = @connection.create_channel q = ch.queue(queue_name, :auto_delete => true, :durable => false) ch.basic_consume(q, "", true, false) do |delivery_info, properties, payload| delivered_keys << delivery_info.routing_key delivered_data << payload end end t.abort_on_exception = true sleep 0.5 ch = @connection.create_channel x = ch.default_exchange x.publish("hello", routing_key: queue_name) sleep 0.7 expect(delivered_keys).to include queue_name expect(delivered_data).to include "hello" expect(ch.queue(queue_name, auto_delete: true, durable: false).message_count).to eq 0 ch.close end end context "with manual acknowledgement mode" do let(:queue_name) { "bunny.basic_consume#{rand}" } it "waits for an explicit acknowledgement" do delivered_keys = [] delivered_data = [] t = Thread.new do ch = @connection.create_channel q = ch.queue(queue_name, auto_delete: true, durable: false) ch.basic_consume(q, "", false, false) do |delivery_info, properties, payload| delivered_keys << delivery_info.routing_key delivered_data << payload ch.close end end t.abort_on_exception = true sleep 0.5 ch = @connection.create_channel x = ch.default_exchange x.publish("hello", routing_key: queue_name) sleep 0.7 expect(delivered_keys).to include queue_name expect(delivered_data).to include "hello" expect(ch.queue(queue_name, auto_delete: true, durable: false).message_count).to eq 0 ch.close end end end ruby-amqp-bunny-b6569cd/spec/spec_helper.rb000066400000000000000000000021621464043542000207570ustar00rootroot00000000000000# -*- encoding: utf-8; mode: ruby -*- $LOAD_PATH.unshift File.expand_path("../lib", __FILE__) require 'bundler' Bundler.require(:default, :test) require "bunny" require "bunny/test_kit" puts "Using Ruby #{RUBY_VERSION}, amq-protocol #{AMQ::Protocol::VERSION}" module RabbitMQ module Control RABBITMQ_NODENAME = ENV['RABBITMQ_NODENAME'] || 'rabbit' def rabbitmq_pid $1.to_i if `rabbitmqctl -n #{RABBITMQ_NODENAME} status` =~ /\{pid,(\d+)\}/ end def start_rabbitmq(delay = 1.0) # this is Homebrew-specific :( `RABBITMQ_NODENAME=#{RABBITMQ_NODENAME} rabbitmq-server > /dev/null 2>&1 &`; sleep(delay) end def stop_rabbitmq(pid = rabbitmq_pid, delay = 1.0) `rabbitmqctl -n #{RABBITMQ_NODENAME} stop`; sleep(delay) end def kill_rabbitmq(pid = rabbitmq_pid, delay = 1.0) # tango is down, tango is down! Process.kill("KILL", pid); sleep(delay) end end end module PlatformDetection def mri? !defined?(RUBY_ENGINE) || (defined?(RUBY_ENGINE) && ("ruby" == RUBY_ENGINE)) end def rubinius? defined?(RUBY_ENGINE) && (RUBY_ENGINE == 'rbx') end end ruby-amqp-bunny-b6569cd/spec/stress/000077500000000000000000000000001464043542000174635ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/spec/stress/channel_close_stress_spec.rb000066400000000000000000000024401464043542000252220ustar00rootroot00000000000000require "spec_helper" describe "Rapidly closing lots of temporary channels" do before :all do @connection = Bunny.new(automatic_recovery: false).tap do |c| c.start end end after :all do @connection.close end 100.times do |i| context "in a multi-threaded scenario A (take #{i})" do let(:n) { 20 } it "works correctly" do ts = [] n.times do t = Thread.new do @connection.with_channel do |ch1| q = ch1.queue("", exclusive: true) q.delete ch1.close end ch2 = @connection.create_channel ch2.close end t.abort_on_exception = true ts << t end ts.each { |t| t.join } end end end 100.times do |i| context "in a multi-threaded scenario B (take #{i})" do let(:n) { 20 } it "works correctly" do ts = [] n.times do t = Thread.new do 3.times do @connection.with_channel do |ch| x = ch.topic('bunny.stress.topics.t2', durable: false) end end end t.abort_on_exception = true ts << t end ts.each { |t| t.join } end end end end ruby-amqp-bunny-b6569cd/spec/stress/channel_open_stress_spec.rb000066400000000000000000000037251464043542000250650ustar00rootroot00000000000000require "spec_helper" describe "Rapidly opening and closing lots of channels" do before :all do @connection = Bunny.new(automatic_recovery: false).tap do |c| c.start end end after :all do @connection.close end context "in a single-threaded scenario" do let(:n) { 500 } it "works correctly" do xs = Array.new(n) { @connection.create_channel } puts "Opened #{n} channels" expect(xs.size).to eq n xs.each do |ch| ch.close end end end 100.times do |i| context "in a multi-threaded scenario A (take #{i})" do # actually, on MRI values greater than ~100 will eventually cause write # operations to fail with a timeout (1 second is not enough) # which will cause recovery to re-acquire @channel_mutex in Session. # Because Ruby's mutexes are not re-entrant, it will raise a ThreadError. # # But this already demonstrates that within these platform constraints, # Bunny is safe to use in such scenarios. let(:n) { 20 } it "works correctly" do ts = [] n.times do t = Thread.new do ch1 = @connection.create_channel q = ch1.queue("", exclusive: true) q.delete ch1.close ch2 = @connection.create_channel ch2.close end t.abort_on_exception = true ts << t end ts.each { |t| t.join } end end end 100.times do |i| context "in a multi-threaded scenario B (take #{i})" do let(:n) { 20 } it "works correctly" do ts = [] n.times do t = Thread.new do 3.times do ch = @connection.create_channel x = ch.topic('bunny.stress.topics.t2', durable: false) ch.close end end t.abort_on_exception = true ts << t end ts.each { |t| t.join } end end end end ruby-amqp-bunny-b6569cd/spec/stress/channel_open_stress_with_single_threaded_connection_spec.rb000066400000000000000000000012271464043542000335330ustar00rootroot00000000000000require "spec_helper" unless ENV["CI"] describe "Rapidly opening and closing lots of channels on a non-threaded connection" do before :all do @connection = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", automatic_recovery: false, threaded: false) @connection.start end after :all do @connection.close end context "in a single-threaded scenario" do let(:n) { 500 } it "works correctly" do xs = Array.new(n) { @connection.create_channel } expect(xs.size).to eq n xs.each do |ch| ch.close end end end end end ruby-amqp-bunny-b6569cd/spec/stress/concurrent_consumers_stress_spec.rb000066400000000000000000000033621464043542000267110ustar00rootroot00000000000000# -*- coding: utf-8 -*- require "spec_helper" unless ENV["CI"] describe "Concurrent consumers sharing a connection" do before :all do @connection = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", automatic_recovery: false, continuation_timeout: 45000) @connection.start end after :all do @connection.close end def any_not_drained?(qs) qs.any? { |q| !q.message_count.zero? } end context "when publishing thousands of messages over 128K in size" do let(:colors) { ["red", "blue", "white"] } let(:n) { 16 } let(:m) { 5000 } it "successfully drain all queues" do ch0 = @connection.create_channel ch0.confirm_select body = "абвг" x = ch0.topic("bunny.stress.concurrent.consumers.topic", durable: true) chs = {} n.times do |i| chs[i] = @connection.create_channel end qs = [] n.times do |i| t = Thread.new do cht = chs[i] q = cht.queue("", exclusive: true) q.bind(x.name, routing_key: colors.sample).subscribe do |delivery_info, meta, payload| # no-op end qs << q end t.abort_on_exception = true end sleep 1.0 5.times do |i| m.times do x.publish(body, routing_key: colors.sample) end puts "Published #{(i + 1) * m} messages..." ch0.wait_for_confirms end while any_not_drained?(qs) sleep 1.0 end puts "Drained all queues, winding down..." ch0.close chs.each { |_, ch| ch.close } end end end end ruby-amqp-bunny-b6569cd/spec/stress/concurrent_publishers_stress_spec.rb000066400000000000000000000021401464043542000270440ustar00rootroot00000000000000# -*- coding: utf-8 -*- require "spec_helper" unless ENV["CI"] describe "Concurrent publishers sharing a connection" do before :all do @connection = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", automatically_recover: false) @connection.start end after :all do @connection.close end let(:concurrency) { 24 } let(:messages) { 5_000 } it "successfully finish publishing" do body = "сообщение" chs = {} concurrency.times do |i| ch = @connection.create_channel ch.confirm_select chs[i] = ch end ts = [] concurrency.times do |i| t = Thread.new do cht = chs[i] x = cht.default_exchange messages.times do x.publish(body) end puts "Published #{messages} messages..." cht.wait_for_confirms end t.abort_on_exception = true ts << t end ts.each do |t| t.join end chs.each { |_, ch| ch.close } end end end ruby-amqp-bunny-b6569cd/spec/stress/connection_open_close_spec.rb000066400000000000000000000024161464043542000253720ustar00rootroot00000000000000require "spec_helper" unless defined?(JRUBY_VERSION) && !ENV["FORCE_JRUBY_RUN"] describe Bunny::Session do # creating thousands of connections means creating # twice as many threads and this won't fly with the JVM # in CI containers. MK. n = if defined?(JRUBY_VERSION) 250 else 2500 end n.times do |i| it "can be closed (automatic recovery disabled, take #{i})" do c = Bunny.new(automatically_recover: false) c.start ch = c.create_channel expect(c).to be_connected c.stop expect(c).to be_closed end end n.times do |i| it "can be closed (automatic recovery enabled, take #{i})" do c = Bunny.new(automatically_recover: true) c.start ch = c.create_channel expect(c).to be_connected c.stop expect(c).to be_closed end end context "in the single threaded mode" do n.times do |i| it "can be closed (single threaded mode, take #{i})" do c = Bunny.new(automatically_recover: false, threaded: false) c.start ch = c.create_channel expect(c).to be_connected c.stop expect(c).to be_closed end end end end end ruby-amqp-bunny-b6569cd/spec/stress/merry_go_round_spec.rb000066400000000000000000000047631464043542000240660ustar00rootroot00000000000000require "spec_helper" describe "A batch of messages proxied by multiple intermediate consumers" do let(:c1) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", heartbeat_timeout: 6) c.start c end let(:c2) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", heartbeat_timeout: 6) c.start c end let(:c3) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", heartbeat_timeout: 6) c.start c end let(:c4) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", heartbeat_timeout: 6) c.start c end let(:c5) do c = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", heartbeat_timeout: 6) c.start c end after :each do [c1, c2, c3, c4, c5].each do |c| c.close if c.open? end end [1000, 5_000, 10_000, 20_000, 30_000, 50_000].each do |n| # message flow is as follows: # # x => q4 => q3 => q2 => q1 => xs (results) it "successfully reaches its final destination (batch size: #{n})" do xs = [] ch1 = c1.create_channel q1 = ch1.queue("", exclusive: true) q1.subscribe(manual_ack: true) do |delivery_info, _, payload| xs << payload ch1.ack(delivery_info.delivery_tag) end ch2 = c2.create_channel q2 = ch2.queue("", exclusive: true) q2.subscribe do |_, _, payload| q1.publish(payload) end ch3 = c3.create_channel(nil, 2) q3 = ch3.queue("", exclusive: true) q3.subscribe(manual_ack: true) do |delivery_info, _, payload| q2.publish(payload) ch3.ack(delivery_info.delivery_tag) end ch4 = c4.create_channel(nil, 4) q4 = ch4.queue("", exclusive: true) q4.subscribe do |_, _, payload| q3.publish(payload) end ch5 = c5.create_channel(nil, 8) ch5.confirm_select x = ch5.default_exchange n.times do |i| x.publish(i.to_s, routing_key: q4.name) end ch5.wait_for_confirms Bunny::TestKit.poll_until(120) do xs.size >= n end expect(xs.size).to eq n expect(xs).to include (n - 1).to_s [q1, q2, q3, q4].each { |q| q.delete } [ch1, ch2, ch3, ch4, ch5].each do |ch| ch.close if ch.open? end end end end ruby-amqp-bunny-b6569cd/spec/stress/multi_message_ack_spec.rb000066400000000000000000000021311464043542000244730ustar00rootroot00000000000000require "spec_helper" unless ENV["CI"] describe "Subscription acknowledging multi-messages" do before :all do @connection = Bunny.new(username: "bunny_gem", password: "bunny_password", vhost: "bunny_testbed", automatically_recover: false) @connection.start end let(:max_messages) { 100_000 } it "successfully completes" do body = "." ch = @connection.create_channel ch.confirm_select q = ch.queue("multi-messages") m = Mutex.new acks = 0 pubs = 0 last = Bunny::Timestamp.now q.subscribe(manual_ack: true) do |delivery_info, _, _| sleep(0) if rand < 0.01 ch.ack(delivery_info.delivery_tag) m.synchronize do acks += 1 now = Bunny::Timestamp.now if now - last > 0.5 puts "Ack multi-message: acks=#{acks} pubs=#{pubs}" last = now end end end (1..max_messages).each do q.publish(".") m.synchronize { pubs += 1 } end sleep 0.1 while m.synchronize { acks < pubs } end end end ruby-amqp-bunny-b6569cd/spec/tls/000077500000000000000000000000001464043542000167425ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/spec/tls/ca_certificate.pem000066400000000000000000000024011464043542000223670ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDhjCCAm6gAwIBAgIUILK3ZJjHksdxRF1zyuQN/RpIIF0wDQYJKoZIhvcNAQEL BQAwTDE7MDkGA1UEAwwyVExTR2VuU2VsZlNpZ25lZHRSb290Q0EgMjAyMi0wMy0y MVQyMTozMDoyMi45MDAzOTQxDTALBgNVBAcMBCQkJCQwHhcNMjIwMzIxMTczMDIz WhcNMzIwMzE4MTczMDIzWjBMMTswOQYDVQQDDDJUTFNHZW5TZWxmU2lnbmVkdFJv b3RDQSAyMDIyLTAzLTIxVDIxOjMwOjIyLjkwMDM5NDENMAsGA1UEBwwEJCQkJDCC ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALYH1p9NuPnsqAXjbHMK167d MZVpQHzvcImahFfnA/dt974GBl3CWjWwdo+z9M8pQoDUeeLwRkDmU1YV4edLQjRK 6LJCkVI8kXaFYUxlhbadMuHRtkhFPrFs80vYvawu9uehYnJAOMSfa9vqd9e5S96b /Q+Y0iDnfykgXHi2yd4IhxGmFksYPi4LTtcphvo1ZZenPjJhxYa77uBG/aw1PTHP NgjpIoSGu3Kppu+1vnGdZqpn446gKBd173lnxF8zahbpMxGCK2YUf3K3l1YXYO9g xqWpOAmtbq/SBTmG0WkeZXF3i/rh3Ygthi3fRv4ZnHReb/gpa+V/KZeGsCWzIREC AwEAAaNgMF4wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBSbNlVS9wYDg37lqCvXAPG/ 4NyueTAfBgNVHSMEGDAWgBSbNlVS9wYDg37lqCvXAPG/4NyueTAPBgNVHRMBAf8E BTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQAaUtIJQMAsrHH9P9syJj20KM0Ur9CE Ntz7avqiJfFV4xRYXkVBH0U8hwB9H5fpFUBDC6MD1259EMxDwU/SuTChHksej5mC IVWE2X4fwZ7YO2j45C0i/AXhFZMtBq596uc9vxuMUBPzJ4Ff0HzG68c2UXot5z2g BwxmULGme2Bb7OP0lbnAN3f++fILKfbMbwM9BMsETYDg22HuvUL3+PwvRCo288mx fcr9FNNBJhAgIhYfRWKl+NEz7mCMPS2NhvP6cNaQ5apRvrmILBP5Lx1ox/ZSIgOz yBiNknL1M3qUdRTzJHo7V6abAdT2yinNe8YDsPQxBbG+zZ9lNO6vqlpA -----END CERTIFICATE----- ruby-amqp-bunny-b6569cd/spec/tls/ca_key.pem000066400000000000000000000032501464043542000207000ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC2B9afTbj57KgF 42xzCteu3TGVaUB873CJmoRX5wP3bfe+BgZdwlo1sHaPs/TPKUKA1Hni8EZA5lNW FeHnS0I0SuiyQpFSPJF2hWFMZYW2nTLh0bZIRT6xbPNL2L2sLvbnoWJyQDjEn2vb 6nfXuUvem/0PmNIg538pIFx4tsneCIcRphZLGD4uC07XKYb6NWWXpz4yYcWGu+7g Rv2sNT0xzzYI6SKEhrtyqabvtb5xnWaqZ+OOoCgXde95Z8RfM2oW6TMRgitmFH9y t5dWF2DvYMalqTgJrW6v0gU5htFpHmVxd4v64d2ILYYt30b+GZx0Xm/4KWvlfymX hrAlsyERAgMBAAECggEBAIdfrvKMV8uuf4rZhpbL8nk+URi/zNDdNUFwo4nH6YBC zSMJ0gQ6fOI+2P2oJKFIGwg6fGcUr/qmEEYOQhHEO3kuWH3x2adiGgAd062ANwAH Sob8cmau8J/82beHeg+qYb8Dm3ZCYrV/pjRH3FaXnF/yfDH0QhlscHpCceLodZpk Vaj30aUNk6ONgNRYE2XIIDTQL5Tr46qBPhqR3kIUBlXupoPyfQg+oyhWnLebgh2Z hDyEFaJ2zDH+5ccW511xiOd31z2LDW/YkUJxT58FbP76k8lwjVbitZMw+CI0XVuV YH/Pq9SMIiybXapz+q4ET1AF3dhhItFElav+lYwln+0CgYEA4wg8twN6+CLyauRz snMgG6IpKdqUQQ4JqANWdH/vSbjsi9mZ09Cboh3cEzGKRa3DoeL+KVDkZzpPxTYp AP2A8+wBlRh+OkaxmQRNXDheM5maJD4jXCBvd0gwv1sI1xHplctSUlK99dGmqFcw y9/VLrkhgBpccugzSOtvWVuZfi8CgYEAzUGsbAXc4JoNia9r4c6A4sDocfJWoqJi vaIAdlPOnOKw8UzRcVK+g46qWTlVb2Y6NG+MTGdnerUuvQGUm232T6a3nca/bG4Y MvBONBN4ZKArOcJnkF+BoFR1+vqAOm54io/z7W01L+zfCyW3oUm15AU9NfbsXu61 S4J2yg0OxL8CgYEAmGOxDU9CvUgH3AQYPNHV3XaNltBm7vvTM3l6EJzHK377GCwp euntbXYJgMCiBX9Lb2CEJYtspHWZkdB22XlPk2r47PER2WAWWZVvr9UONiiGNImZ Bn+nEjPctLUQS/x0A94EFcoAQ/5DlX9g+5f06nXNrMUFBQQjWHDfvuUjNHUCgYBg 2SsNMVr+E1Jt8/q4aiLAW3uSQGGGjY5/odAMBRFJT2FkCfYNPZitJITWYsogLSEr mrKdXnEiIhlyjytah3EgNi8PYDb0Z6I9bsvHoKQ+/qBGuab6JUZZq2Cb0tTsPTHd V30mO1BMU70OWnahwND1TU+Lhf4T8RhUD7QTNEQSqwKBgG2p4jlv1mLlv/Bl4YJC Xp50qW4ivTFNHmx+vQDhsHAramPmFL9kn394UgDJ8awhEWnm0KnHfirhY3aW6zdH oWnNrexNEDXG7hoGZIe3ShFpIrTJQnke9JTTIYFEYFsaGYfMHNzfeVKN1HxP88qm BJ3jDZKxdDUuoew/7KcW7GXd -----END PRIVATE KEY----- ruby-amqp-bunny-b6569cd/spec/tls/client_certificate.pem000066400000000000000000000024561464043542000232740ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIDpTCCAo2gAwIBAgIBAjANBgkqhkiG9w0BAQsFADBMMTswOQYDVQQDDDJUTFNH ZW5TZWxmU2lnbmVkdFJvb3RDQSAyMDIyLTA1LTIzVDE1OjQ0OjMwLjk5OTU1MDEN MAsGA1UEBwwEJCQkJDAeFw0yMjA1MjMxMTQ0MzFaFw0zMjA1MjAxMTQ0MzFaMDMx IDAeBgNVBAMMF2tsaXNoaW5tLWEwMS52bXdhcmUuY29tMQ8wDQYDVQQKDAZjbGll bnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDSVnB/t1mILztmTiZe g98YdtbdyESd82uT4lwng03fU598jvXJaCo9lSLqVlNzD/3SR+rMsbQ9LQLWAGJ8 EFYiaTHdB9aj0MxcoGMX6HqRac4sUeD8HDKdin/5wnVnkokwcyRVGAjlOZ7PdPO/ ToZlks+IpB9evpNhepGkVC+9oDIPuIW1NPt3U+l6v14Se02QNZQSuJChofD8D4dI vuRtvcA2UfUi+T1tteuy/GxZjX7V0SgQhDoFtGU7qhpxId8PN8/F4dz7a9vl2vT9 613CrPR0Hy/rfNexG1er2H1tLV6OgbJJw1CWWwSs918WoQ8FgB7qNMAAQMtKmaZn tpApAgMBAAGjgaowgacwCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwEwYDVR0lBAww CgYIKwYBBQUHAwIwRgYDVR0RBD8wPYIXa2xpc2hpbm0tYTAxLnZtd2FyZS5jb22C F2tsaXNoaW5tLWEwMS52bXdhcmUuY29tgglsb2NhbGhvc3QwMAYDVR0fBCkwJzAl oCOgIYYfaHR0cDovL2xvY2FsaG9zdDo4MDAwL2Jhc2ljLmNybDANBgkqhkiG9w0B AQsFAAOCAQEAicVbOiS8zUZ1Oh3PAfm/cFeuA2H0qglSeYWmgkeAmNLFCke/Fl2k mgjSz6pW7wzA1xVBJQL6tTYnT7iPW0OMSCj4ACA/Z8DOD1dLYCfT4yV1plVDtSXX kEI2qjvBaixkfp0ZtsSxEL5d1mmiIhnRqJZ35l2hCRbJAkosy7HR4zNbe29PLsqw mCDI+pWmFUeUP+JfsOHAPlsGiWZg07n8j97oXWyQbaUENoCPJ8Z7Vlz5xj6aqHb0 MntAfNXAdAz3YZtb3EM820GydAh/B/DIEBrEEP8s/uk0B6Ou1qqhdXG/IEylPWwI rgiopMTonblIrur4oYnve+XCbkwHlXT5Hw== -----END CERTIFICATE----- ruby-amqp-bunny-b6569cd/spec/tls/client_key.pem000066400000000000000000000032541464043542000215770ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDSVnB/t1mILztm TiZeg98YdtbdyESd82uT4lwng03fU598jvXJaCo9lSLqVlNzD/3SR+rMsbQ9LQLW AGJ8EFYiaTHdB9aj0MxcoGMX6HqRac4sUeD8HDKdin/5wnVnkokwcyRVGAjlOZ7P dPO/ToZlks+IpB9evpNhepGkVC+9oDIPuIW1NPt3U+l6v14Se02QNZQSuJChofD8 D4dIvuRtvcA2UfUi+T1tteuy/GxZjX7V0SgQhDoFtGU7qhpxId8PN8/F4dz7a9vl 2vT9613CrPR0Hy/rfNexG1er2H1tLV6OgbJJw1CWWwSs918WoQ8FgB7qNMAAQMtK maZntpApAgMBAAECggEBALArfABMpi2dOcgfTp29H9+SsbaCZY5R8lmjrwHdRRz3 Ik6mQpDVsQNxZRQXB8AItdQpLi0R7d+LUqPBILu+xxX9O6lgRlNgr7opD7ZXnkkq 8GdIkQM37uDqvvOG/uSn5EXzOowXHGQ5lZ023GzwLTrn8vEEXc3yGuj7Sv7zECVm gArkbLCNynFyDpG2tof9DwOYdbMKbYvAT2Y0+WHx4lxfKC3QLZEqLqmbow+YaZIr 2Gw+ASTcFKXYfbF+fjLKFbszPZK+Abuw4KFmP2XBA+m3iXhEBTIezUjivo8wd9oV v/4zXFmZoiPMerUc4YMYjAkWyvJrg2gIXSVBDt4Ccq0CgYEA/mzwYZNL7DnBeqHl k7pwXbgueoK3TJIYdZt/VpkNmwFzU2yCTiJjGdayZXabFww09sYL9iN13GmsG4LY gT/SqaIGjCwrd27dLarpzQzCehT2HQXpGsb8JczLzCHbjC3RWt8gcx91Nyniu1q2 RCBHgtHZwce63zhgfrOdlNDz8YsCgYEA06OoCtAXYCkqZKTOZBZLZ4hCwMQtmz6l ieERSSclF4dTBIsgz1CBnoFIpd1VrEdwhO1YSgKr9kdpdcyqvbybKlbhiaQrsFDO hFuCg2TtUNEvVZPcF7diUomYYc2W6/vyPBIMzAVrVBeXOA2eiDYuy0pfZQ/nGaE6 ejQS9nICE5sCgYEAijp5pym1uBPw8EWBS2+hmOl2Jw1ZtPoy28KtvkjNQYfaNlrf uL+qA+HHbqSvwifZprEW0pWVdTjcTt4Z7q7UL2FeGU3uF6dd92/CMqtaBaXZyQ7r BKdTWt4XY0KJ23ERK0PKh4Fx15SkIJI6MG0xLPwCSOvQtXIVywCe/rk/r90CgYAV ypgW5O5DPHnc+ws1SwZglqso5go8Hes1kzMQcTNkhM6LfTfXoNZ315//VBbqMkDn rviURkomhZvaQUSjXLQtW9zFK15EPU3GWxXJKa81zxQsn5jd6Ef7b6d9Coorqjl6 7fDbYQN4GSW848obBFk1HWdNJKILBYrb4ljBf2OvWQKBgQD5JjWljmZJW42uVFH3 qYH+g5KDHBzwTVxszVybAUrdxUXhYypWJYipypI1HrBcRfMwcm2ViGeZ4KPsrkHX cEpQlAIQ7H+Qo1hDTJabOtiFNHWNGGrYp6SKsFUdjRNrNRZ9TWAjfV0i4JpHamLP X/GtzFj3VrUXxJLZkbz0xXiLZQ== -----END PRIVATE KEY----- ruby-amqp-bunny-b6569cd/spec/tls/generate-server-cert.sh000077500000000000000000000007311464043542000233330ustar00rootroot00000000000000#!/bin/sh DIRNAME=$(dirname "$0") openssl req -sha256 -new -key $DIRNAME/server_key.pem -out $DIRNAME/server.csr -subj "/CN=mercurio" -config $DIRNAME/server-openssl.cnf openssl x509 -sha256 -req -in $DIRNAME/server.csr -CA $DIRNAME/ca_certificate.pem -CAkey $DIRNAME/ca_key.pem -CAcreateserial -CAserial $DIRNAME/ca.srl -out $DIRNAME/server_certificate.pem -days 3650 -extensions v3_req -extfile $DIRNAME/server-openssl.cnf echo "Written new server CSR and certificate"ruby-amqp-bunny-b6569cd/spec/tls/server-openssl.cnf000066400000000000000000000004001464043542000224130ustar00rootroot00000000000000[req] req_extensions = v3_req distinguished_name = req_distinguished_name [req_distinguished_name] [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment subjectAltName = @alt_names [alt_names] IP.1 = 127.0.0.1 ruby-amqp-bunny-b6569cd/spec/tls/server.csr000066400000000000000000000017101464043542000207600ustar00rootroot00000000000000-----BEGIN CERTIFICATE REQUEST----- MIICkjCCAXoCAQAwEzERMA8GA1UEAwwIbWVyY3VyaW8wggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQCkvvzm5nB3CDqTFEYQnfOy8DwJLm34MvdrKQAThzDy qa37sb4IC0YclBFbZfsw8+paK+Rpo2Vlhzclb66z0cGs9SvxuKkJ45w0fk0ctxMg tvWISRGZR7LMw5u0q2m61dK0FTGSl+qzJohb5Dklb6BApoGoIPH+eYraVxHR29x2 x8hqzBt5TpiUW8bu7LPQJbX0mYGhKQDf86kao+sptRQ2045D7vB3jrkPhq0XZVJi QymzOSSejYSN1oZ464DtT+dpLBYHEVJoJPu4r4kY/8A7v+93PWQaDERKBfvwWHfV U44xn7R/aojSvNT7kQILsf2BnfJlMwoedWQLxPddmB3PAgMBAAGgOjA4BgkqhkiG 9w0BCQ4xKzApMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgXgMA8GA1UdEQQIMAaHBH8A AAEwDQYJKoZIhvcNAQELBQADggEBAFyfaotajM/h2dyodKJqO6stAIpxiQXTds8V 5ZHDozBxzLZkeBIY+hsqh/owmqomk56swui+336WAKIBwIJyJrtIl8C/lupGaTbR BouWWbyZOQAE2ExHcUgdGEOVoCN2ieBR4RVQ8Id4GAlHvlFGPqakaLMV6Zc7VqDh vxdOLgATEE+MhebTo9yOHj14qdvzhi5w3ZEg1kdfOuGN9I4gJcv4PWwudBhn4wE7 oHAIP2nixROI7cZcZ9fBrimcdGQsXNZLTXiGzNra4utOXuQ7w5qoiEhHoxXalowE KvEA9otLadjtULg6DRd3zYuIyrUiBIRUHZ1p2xSnd/lLekbMfCQ= -----END CERTIFICATE REQUEST----- ruby-amqp-bunny-b6569cd/spec/tls/server_certificate.pem000066400000000000000000000026031464043542000233160ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIID5TCCAs2gAwIBAgIBATANBgkqhkiG9w0BAQsFADBMMTswOQYDVQQDDDJUTFNH ZW5TZWxmU2lnbmVkdFJvb3RDQSAyMDIyLTA1LTIzVDE1OjQ0OjMwLjk5OTU1MDEN MAsGA1UEBwwEJCQkJDAeFw0yMjA1MjMxMTQ0MzFaFw0zMjA1MjAxMTQ0MzFaMDMx IDAeBgNVBAMMF2tsaXNoaW5tLWEwMS52bXdhcmUuY29tMQ8wDQYDVQQKDAZzZXJ2 ZXIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC1PWRIjteA+9HU98h9 hx/wNqVGS1rqbo7WuhVnZvaYiZn+siE6GUZz5vVRDziLaL26xMjLSmhlVz63+E4t KnkYSx+lrLH7HG8neIdlPUF8go01hs644/aE4VRIl4qLJgFFAVflqtVJhmkO8vHL hROAQxZiRoA7jMRsPssZ5GqRM5rhro1h1uLiVifxxqu+z9Zuxwaw/DuJBw2evXsl D6X4csjmR8Rrq5/NeWHFzT40UJwYZ0Dlzp86DPDVw6tz+YdxZH8GyCiUalSPSwR+ DlwZ3efQce2Ff76VV1Y4l63dTZPtBc7wntSpdshBb9KjGkLGmWc42bXiG4sz4Qu+ vNKrAgMBAAGjgeowgecwCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwEwYDVR0lBAww CgYIKwYBBQUHAwEwRgYDVR0RBD8wPYIXa2xpc2hpbm0tYTAxLnZtd2FyZS5jb22C F2tsaXNoaW5tLWEwMS52bXdhcmUuY29tgglsb2NhbGhvc3QwHQYDVR0OBBYEFHkh oqkZjlmDqzHZUkIxKoKSLpwKMB8GA1UdIwQYMBaAFMLoh3zZVzQcDwPjBcBc8AwS p5dLMDAGA1UdHwQpMCcwJaAjoCGGH2h0dHA6Ly9sb2NhbGhvc3Q6ODAwMC9iYXNp Yy5jcmwwDQYJKoZIhvcNAQELBQADggEBAAqrgzKPL9yfGWGhEKBSYiS0FXdr2KHM iUpZ3v6BzQh0FE2e/lbYwFsfLLx10Ct6unP4HCClLn5BEYc7bN29AzBoeBWP8V+Y T2PkT0AVVh9l6gqCpqivyQpsv7DR7cja18Ca6Q5df+EIzJGK97LjPG3+ZniI2zhu PNb1KiOOfCnQNBIqZQFEuOHviO4GbU3tFAxs4qca8JyJbe0a0BYD8HUIwrkvBLTy dvc3Q2gnrjmQmYvgvUZcNx5ykAiSrQMVJcEeSlFDDFBMfPYS+zOct4VXEYA789L/ pSia7whqRhDbGKwMt626P2DiRc6fEGVyO8jCAyP7F8Yq5u54cebZb7A= -----END CERTIFICATE----- ruby-amqp-bunny-b6569cd/spec/tls/server_key.pem000066400000000000000000000032501464043542000216230ustar00rootroot00000000000000-----BEGIN PRIVATE KEY----- MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQC1PWRIjteA+9HU 98h9hx/wNqVGS1rqbo7WuhVnZvaYiZn+siE6GUZz5vVRDziLaL26xMjLSmhlVz63 +E4tKnkYSx+lrLH7HG8neIdlPUF8go01hs644/aE4VRIl4qLJgFFAVflqtVJhmkO 8vHLhROAQxZiRoA7jMRsPssZ5GqRM5rhro1h1uLiVifxxqu+z9Zuxwaw/DuJBw2e vXslD6X4csjmR8Rrq5/NeWHFzT40UJwYZ0Dlzp86DPDVw6tz+YdxZH8GyCiUalSP SwR+DlwZ3efQce2Ff76VV1Y4l63dTZPtBc7wntSpdshBb9KjGkLGmWc42bXiG4sz 4Qu+vNKrAgMBAAECggEASAa+3OsPj+4DxOw4HFLgCXneEuKNng8T6xgGu5I6vAIr VgH6nHlA+3y0z2MKKLUWeawM1GWcuXGDtl8SynZPq6znRdMs5Eu3jOske8JtTQsh DT2m1+mYpdRax4KaB5Sx+nd/NAHBQ7gL9mnsV+JqDpYaxjGYcpIN2arW0lJpW4yH rBGCVo+OJx6mMU6pFeQM9wofJJHHeKunidIn/DSY5oN6uHKoz1wFVwJS/GG034LH 2QhskcfbJYujGXhZf8uHN09ZiLd79c9aAMxrkD6U4++DWvcnpn5cELdvISkUYvD8 QNWVQe3yUzpjTMrbPJ5VtIbGT4+LkDSrabTmSZNUeQKBgQDs6weXLSNihpBlU/Mm 7uU/uZ3YPB1vgsieJ5blJsaOdnBy6vFMqAHv9/sZHF49nxyiprUVNwzT8BABC0Fs JWz9aA3xhP9ysZdo3RujSpab4BZsLeVJ8AJrk0LuNu25t8lEp2dJjjV80lt3nHjh XJjh2KDJmBQd0EKlYGQQEhedPwKBgQDD1leO1QP5s9LrGml87H0DzOg/lPkZby22 x/B/gCwBoIjievIenSJfPBVWkCp4kyl2qM4oefDbU4O8ra9xJiQfxnR+baxlWlzk 6ZZ5WD87Y7xiC4UD7sH5rh1BBzIU+cYD+4TYrm2WOV8XcmRjwrXsCvPcKuRyTHxS ITbIjuVzlQKBgH+JxnxLgFD5v6HMn4jF8gNsGNze/7mX3gfFqCWTyi/G8qHqeBuc Fe7ov/O0ocbqcK/ernzNNcqh4Puu43kkbJe9/EOYJSvJDokU/lvZO1qON4Pk8Rns amEe1VkHmNHxbhfXRnMCayjW1QZ+5VPsVSC/TU843xttkXMVgMhl9PBhAoGAX7lL fxN8J/fP5v84BMoLqWnlM/77a4U+kRNV6Nybpgf0IM/7vR8NiAN0YqWBPQKhx9Bn RL2mD27Y+8bh9KqyCZ4Vnx64n6n1sibDWHjcH4l2sW30DKINyp9iyUBdKIeWVKgC nW9VPZK5elp92413IvOPk1Sb6YqdUf/OKWY/nakCgYAiyNKdzPM5VjCNzrrnjP0S eApSP9xjXrub7fCw0VNH4an145V32dhUGrQCk3jgvdnTEXAr6zH5KAkC5geLVmSq Rm11DfKKDjtkXEcZWHCnSaVz9FzxLYDx5UM+9Uo7G8vGnJefbwz1KZ8DvcYlpyEv 5JAaGnEsFOcYCvyTFc3GxA== -----END PRIVATE KEY----- ruby-amqp-bunny-b6569cd/spec/toxiproxy_helper.rb000066400000000000000000000012261464043542000221120ustar00rootroot00000000000000module RabbitMQ module Toxiproxy RABBITMQ_UPSTREAM_HOST = if !ENV["LOCAL_RABBITMQ"].nil? # a local Toxiproxy/RabbitMQ combination "localhost" else # docker-compose "rabbitmq" end def setup_toxiproxy ::Toxiproxy.populate([{ name: "rabbitmq", listen: "0.0.0.0:11111", upstream: "#{RABBITMQ_UPSTREAM_HOST}:5672" }]) rabbitmq_toxiproxy.enable end def rabbitmq_toxiproxy ::Toxiproxy[/rabbitmq/] end end end ruby-amqp-bunny-b6569cd/spec/unit/000077500000000000000000000000001464043542000171175ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/spec/unit/bunny_spec.rb000066400000000000000000000005531464043542000216140ustar00rootroot00000000000000require "spec_helper" describe Bunny do it "has library version" do expect(Bunny::VERSION).not_to be_nil expect(Bunny.version).not_to be_nil end it "has AMQP protocol version" do expect(Bunny::PROTOCOL_VERSION).to eq "0.9.1" expect(AMQ::Protocol::PROTOCOL_VERSION).to eq "0.9.1" expect(Bunny.protocol_version).to eq "0.9.1" end end ruby-amqp-bunny-b6569cd/spec/unit/concurrent/000077500000000000000000000000001464043542000213015ustar00rootroot00000000000000ruby-amqp-bunny-b6569cd/spec/unit/concurrent/atomic_fixnum_spec.rb000066400000000000000000000011751464043542000255060ustar00rootroot00000000000000require "spec_helper" require "bunny/concurrent/atomic_fixnum" describe Bunny::Concurrent::AtomicFixnum do it "allows retrieving the current value" do af = described_class.new(0) expect(af.get).to eq 0 expect(af).to eq 0 end it "can be updated" do af = described_class.new(0) expect(af.get).to eq 0 Thread.new do af.set(10) end sleep 0.6 expect(af.get).to eq 10 end it "can be incremented" do af = described_class.new(0) expect(af.get).to eq 0 10.times do Thread.new do af.increment end end sleep 0.6 expect(af.get).to eq 10 end end ruby-amqp-bunny-b6569cd/spec/unit/concurrent/condition_spec.rb000066400000000000000000000035121464043542000246270ustar00rootroot00000000000000require "spec_helper" require "bunny/concurrent/condition" describe Bunny::Concurrent::Condition do describe "#wait" do 50.times do |i| it "blocks current thread until notified (take #{i})" do condition = described_class.new xs = [] t = Thread.new do xs << :notified sleep 0.2 condition.notify end t.abort_on_exception = true condition.wait expect(xs).to eq [:notified] end end end describe "#notify" do 50.times do |i| it "notifies a single thread waiting on the latch (take #{i})" do mutex = Mutex.new condition = described_class.new xs = [] t1 = Thread.new do condition.wait mutex.synchronize { xs << :notified1 } end t1.abort_on_exception = true t2 = Thread.new do condition.wait mutex.synchronize { xs << :notified2 } end t2.abort_on_exception = true sleep 0.2 condition.notify sleep 0.5 expect(xs).to satisfy { |ys| ys.size == 1 && (ys.include?(:notified1) || ys.include?(:notified2)) } end end end describe "#notify_all" do let(:n) { 30 } 50.times do |i| it "notifies all the threads waiting on the latch (take #{i})" do mutex = Mutex.new condition = described_class.new @xs = [] n.times do |i| t = Thread.new do condition.wait mutex.synchronize { @xs << "notified#{i + 1}".to_sym } end t.abort_on_exception = true end sleep 0.5 condition.notify_all sleep 0.5 n.times do |i| item = "notified#{i + 1}".to_sym expect(@xs).to include item end end end end end ruby-amqp-bunny-b6569cd/spec/unit/concurrent/linked_continuation_queue_spec.rb000066400000000000000000000015601464043542000301060ustar00rootroot00000000000000require "spec_helper" if defined?(JRUBY_VERSION) require "bunny/concurrent/linked_continuation_queue" describe Bunny::Concurrent::LinkedContinuationQueue do describe "#poll with a timeout that is never reached" do it "blocks until the value is available, then returns it" do # force subject evaluation cq = subject t = Thread.new do cq.push(10) end t.abort_on_exception = true v = subject.poll(500) expect(v).to eq 10 end end describe "#poll with a timeout that is reached" do it "raises an exception" do # force subject evaluation cq = subject t = Thread.new do sleep 1.5 cq.push(10) end t.abort_on_exception = true expect { subject.poll(500) }.to raise_error(::Timeout::Error) end end end end ruby-amqp-bunny-b6569cd/spec/unit/concurrent/synchronized_sorted_set_spec.rb000066400000000000000000000032021464043542000276070ustar00rootroot00000000000000require "spec_helper" require "bunny/concurrent/synchronized_sorted_set" unless ENV["CI"] describe Bunny::Concurrent::SynchronizedSortedSet do 50.times do |i| it "provides the same API as SortedSet for key operations (take #{i})" do s = described_class.new expect(s.length).to eq 0 s << 1 expect(s.length).to eq 1 s << 1 expect(s.length).to eq 1 s << 2 expect(s.length).to eq 2 s << 3 expect(s.length).to eq 3 s << 4 expect(s.length).to eq 4 s << 4 s << 4 s << 4 expect(s.length).to eq 4 s << 5 expect(s.length).to eq 5 s << 5 s << 5 s << 5 expect(s.length).to eq 5 s << 6 expect(s.length).to eq 6 s << 7 expect(s.length).to eq 7 s << 8 expect(s.length).to eq 8 s.delete 8 expect(s.length).to eq 7 s.delete_if { |i| i == 1 } expect(s.length).to eq 6 end it "synchronizes common operations needed by Bunny (take #{i})" do s = described_class.new expect(s.length).to eq 0 10.times do Thread.new do s << 1 s << 1 s << 2 s << 3 s << 4 s << 4 s << 4 s << 4 s << 5 s << 5 s << 5 s << 5 s << 6 s << 7 s << 8 s.delete 8 s.delete_if { |i| i == 1 } end end sleep 0.5 expect(s.length).to eq 6 end end end end ruby-amqp-bunny-b6569cd/spec/unit/heartbeat_sender_spec.rb000066400000000000000000000014051464043542000237550ustar00rootroot00000000000000require "spec_helper" describe Bunny::HeartbeatSender do let(:transport) { instance_double("Bunny::Transport") } # let(:logger) { StringIO.new } # keep test output clear let(:logger) { Logger.new(STDOUT) } let(:heartbeat_sender) do allow(Bunny::Transport).to receive(:new).and_return(transport) described_class.new(Bunny::Transport.new, logger) end it "raises an error when standard error is raised" do allow(logger).to receive(:error) # This simulates a transport that raises an error. allow(heartbeat_sender).to receive(:beat).and_raise(StandardError.new("This error should be logged")) heartbeat_sender.start expect(logger).to have_received(:error).with("Error in the hearbeat sender: This error should be logged") end end ruby-amqp-bunny-b6569cd/spec/unit/version_delivery_tag_spec.rb000066400000000000000000000013071464043542000247020ustar00rootroot00000000000000require "spec_helper" require "bunny/concurrent/atomic_fixnum" require "bunny/versioned_delivery_tag" describe Bunny::VersionedDeliveryTag, "#stale?" do subject { described_class.new(2, 1) } context "when delivery tag version < provided version" do it "returns true" do expect(subject.stale?(2)).to eq true end end context "when delivery tag version = provided version" do it "returns false" do expect(subject.stale?(1)).to eq false end end context "when delivery tag version > provided version" do it "returns true" do # this scenario is unrealistic but we still can # unit test it. MK. expect(subject.stale?(0)).to eq false end end end