| @ -1,2 +1,2 @@ | |||
| github.com/streadway/amqp v1.0.0 h1:kuuDrUJFZL1QYL9hUNuCxNObNzB0bV/ZG5jV3RWAQgo= | |||
| github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= | |||
| github.com/rabbitmq/amqp091-go v0.0.0-20210823000215-c428a6150891 h1:13nv5f/LNJxNpvpYm/u0NqrlFebon342f9Xu9GpklKc= | |||
| github.com/rabbitmq/amqp091-go v0.0.0-20210823000215-c428a6150891/go.mod h1:ogQDLSOACsLPsIq0NpbtiifNZi2YOz0VTJ0kHRghqbM= | |||
| @ -1,12 +0,0 @@ | |||
| certs/* | |||
| spec/spec | |||
| examples/simple-consumer/simple-consumer | |||
| examples/simple-producer/simple-producer | |||
| .idea/**/workspace.xml | |||
| .idea/**/tasks.xml | |||
| .idea/**/usage.statistics.xml | |||
| .idea/**/dictionaries | |||
| .idea/**/shelf | |||
| .idea/**/contentModel.xml | |||
| @ -1,25 +0,0 @@ | |||
| language: go | |||
| go: | |||
| - 1.10.x | |||
| - 1.11.x | |||
| - 1.12.x | |||
| - 1.13.x | |||
| addons: | |||
| apt: | |||
| packages: | |||
| - rabbitmq-server | |||
| services: | |||
| - rabbitmq | |||
| env: | |||
| - GO111MODULE=on AMQP_URL=amqp://guest:guest@127.0.0.1:5672/ | |||
| before_install: | |||
| - go get -v golang.org/x/lint/golint | |||
| script: | |||
| - ./pre-commit | |||
| - go test -cpu=1,2 -v -tags integration ./... | |||
| @ -1,35 +0,0 @@ | |||
| ## Prequisites | |||
| 1. Go: [https://golang.org/dl/](https://golang.org/dl/) | |||
| 1. Golint `go get -u -v github.com/golang/lint/golint` | |||
| ## Contributing | |||
| The workflow is pretty standard: | |||
| 1. Fork github.com/streadway/amqp | |||
| 1. Add the pre-commit hook: `ln -s ../../pre-commit .git/hooks/pre-commit` | |||
| 1. Create your feature branch (`git checkout -b my-new-feature`) | |||
| 1. Run integration tests (see below) | |||
| 1. **Implement tests** | |||
| 1. Implement fixs | |||
| 1. Commit your changes (`git commit -am 'Add some feature'`) | |||
| 1. Push to a branch (`git push -u origin my-new-feature`) | |||
| 1. Submit a pull request | |||
| ## Running Tests | |||
| The test suite assumes that: | |||
| * A RabbitMQ node is running on localhost with all defaults: [https://www.rabbitmq.com/download.html](https://www.rabbitmq.com/download.html) | |||
| * `AMQP_URL` is exported to `amqp://guest:guest@127.0.0.1:5672/` | |||
| ### Integration Tests | |||
| After starting a local RabbitMQ, run integration tests with the following: | |||
| env AMQP_URL=amqp://guest:guest@127.0.0.1:5672/ go test -v -cpu 2 -tags integration -race | |||
| All integration tests should use the `integrationConnection(...)` test | |||
| helpers defined in `integration_test.go` to setup the integration environment | |||
| and logging. | |||
| @ -1,23 +0,0 @@ | |||
| Copyright (c) 2012-2019, Sean Treadway, SoundCloud Ltd. | |||
| All rights reserved. | |||
| Redistribution and use in source and binary forms, with or without | |||
| modification, are permitted provided that the following conditions are met: | |||
| Redistributions of source code must retain the above copyright notice, this | |||
| list of conditions and the following disclaimer. | |||
| Redistributions in binary form must reproduce the above copyright notice, this | |||
| list of conditions and the following disclaimer in the documentation and/or | |||
| other materials provided with the distribution. | |||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | |||
| ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | |||
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |||
| DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |||
| FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||
| DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |||
| SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |||
| CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |||
| OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |||
| @ -1,93 +0,0 @@ | |||
| [](http://travis-ci.org/streadway/amqp) [](http://godoc.org/github.com/streadway/amqp) | |||
| # Go RabbitMQ Client Library | |||
| This is an AMQP 0.9.1 client with RabbitMQ extensions in Go. | |||
| ## Project Maturity | |||
| This project has been used in production systems for many years. It is reasonably mature | |||
| and feature complete, and as of November 2016 has [a team of maintainers](https://github.com/streadway/amqp/issues/215). | |||
| Future API changes are unlikely but possible. They will be discussed on [Github | |||
| issues](https://github.com/streadway/amqp/issues) along with any bugs or | |||
| enhancements. | |||
| ## Supported Go Versions | |||
| This library supports two most recent Go release series, currently 1.10 and 1.11. | |||
| ## Supported RabbitMQ Versions | |||
| This project supports RabbitMQ versions starting with `2.0` but primarily tested | |||
| against reasonably recent `3.x` releases. Some features and behaviours may be | |||
| server version-specific. | |||
| ## Goals | |||
| Provide a functional interface that closely represents the AMQP 0.9.1 model | |||
| targeted to RabbitMQ as a server. This includes the minimum necessary to | |||
| interact the semantics of the protocol. | |||
| ## Non-goals | |||
| Things not intended to be supported. | |||
| * Auto reconnect and re-synchronization of client and server topologies. | |||
| * Reconnection would require understanding the error paths when the | |||
| topology cannot be declared on reconnect. This would require a new set | |||
| of types and code paths that are best suited at the call-site of this | |||
| package. AMQP has a dynamic topology that needs all peers to agree. If | |||
| this doesn't happen, the behavior is undefined. Instead of producing a | |||
| possible interface with undefined behavior, this package is designed to | |||
| be simple for the caller to implement the necessary connection-time | |||
| topology declaration so that reconnection is trivial and encapsulated in | |||
| the caller's application code. | |||
| * AMQP Protocol negotiation for forward or backward compatibility. | |||
| * 0.9.1 is stable and widely deployed. Versions 0.10 and 1.0 are divergent | |||
| specifications that change the semantics and wire format of the protocol. | |||
| We will accept patches for other protocol support but have no plans for | |||
| implementation ourselves. | |||
| * Anything other than PLAIN and EXTERNAL authentication mechanisms. | |||
| * Keeping the mechanisms interface modular makes it possible to extend | |||
| outside of this package. If other mechanisms prove to be popular, then | |||
| we would accept patches to include them in this package. | |||
| ## Usage | |||
| See the 'examples' subdirectory for simple producers and consumers executables. | |||
| If you have a use-case in mind which isn't well-represented by the examples, | |||
| please file an issue. | |||
| ## Documentation | |||
| Use [Godoc documentation](http://godoc.org/github.com/streadway/amqp) for | |||
| reference and usage. | |||
| [RabbitMQ tutorials in | |||
| Go](https://github.com/rabbitmq/rabbitmq-tutorials/tree/master/go) are also | |||
| available. | |||
| ## Contributing | |||
| Pull requests are very much welcomed. Create your pull request on a non-master | |||
| branch, make sure a test or example is included that covers your change and | |||
| your commits represent coherent changes that include a reason for the change. | |||
| To run the integration tests, make sure you have RabbitMQ running on any host, | |||
| export the environment variable `AMQP_URL=amqp://host/` and run `go test -tags | |||
| integration`. TravisCI will also run the integration tests. | |||
| Thanks to the [community of contributors](https://github.com/streadway/amqp/graphs/contributors). | |||
| ## External packages | |||
| * [Google App Engine Dialer support](https://github.com/soundtrackyourbrand/gaeamqp) | |||
| * [RabbitMQ examples in Go](https://github.com/rabbitmq/rabbitmq-tutorials/tree/master/go) | |||
| ## License | |||
| BSD 2 clause - see LICENSE for more details. | |||
| @ -1,106 +0,0 @@ | |||
| package amqp | |||
| import ( | |||
| "bytes" | |||
| "fmt" | |||
| "math/big" | |||
| ) | |||
| const ( | |||
| free = 0 | |||
| allocated = 1 | |||
| ) | |||
| // allocator maintains a bitset of allocated numbers. | |||
| type allocator struct { | |||
| pool *big.Int | |||
| last int | |||
| low int | |||
| high int | |||
| } | |||
| // NewAllocator reserves and frees integers out of a range between low and | |||
| // high. | |||
| // | |||
| // O(N) worst case space used, where N is maximum allocated, divided by | |||
| // sizeof(big.Word) | |||
| func newAllocator(low, high int) *allocator { | |||
| return &allocator{ | |||
| pool: big.NewInt(0), | |||
| last: low, | |||
| low: low, | |||
| high: high, | |||
| } | |||
| } | |||
| // String returns a string describing the contents of the allocator like | |||
| // "allocator[low..high] reserved..until" | |||
| // | |||
| // O(N) where N is high-low | |||
| func (a allocator) String() string { | |||
| b := &bytes.Buffer{} | |||
| fmt.Fprintf(b, "allocator[%d..%d]", a.low, a.high) | |||
| for low := a.low; low <= a.high; low++ { | |||
| high := low | |||
| for a.reserved(high) && high <= a.high { | |||
| high++ | |||
| } | |||
| if high > low+1 { | |||
| fmt.Fprintf(b, " %d..%d", low, high-1) | |||
| } else if high > low { | |||
| fmt.Fprintf(b, " %d", high-1) | |||
| } | |||
| low = high | |||
| } | |||
| return b.String() | |||
| } | |||
| // Next reserves and returns the next available number out of the range between | |||
| // low and high. If no number is available, false is returned. | |||
| // | |||
| // O(N) worst case runtime where N is allocated, but usually O(1) due to a | |||
| // rolling index into the oldest allocation. | |||
| func (a *allocator) next() (int, bool) { | |||
| wrapped := a.last | |||
| // Find trailing bit | |||
| for ; a.last <= a.high; a.last++ { | |||
| if a.reserve(a.last) { | |||
| return a.last, true | |||
| } | |||
| } | |||
| // Find preceding free'd pool | |||
| a.last = a.low | |||
| for ; a.last < wrapped; a.last++ { | |||
| if a.reserve(a.last) { | |||
| return a.last, true | |||
| } | |||
| } | |||
| return 0, false | |||
| } | |||
| // reserve claims the bit if it is not already claimed, returning true if | |||
| // successfully claimed. | |||
| func (a *allocator) reserve(n int) bool { | |||
| if a.reserved(n) { | |||
| return false | |||
| } | |||
| a.pool.SetBit(a.pool, n-a.low, allocated) | |||
| return true | |||
| } | |||
| // reserved returns true if the integer has been allocated | |||
| func (a *allocator) reserved(n int) bool { | |||
| return a.pool.Bit(n-a.low) == allocated | |||
| } | |||
| // release frees the use of the number for another allocation | |||
| func (a *allocator) release(n int) { | |||
| a.pool.SetBit(a.pool, n-a.low, free) | |||
| } | |||
| @ -1,62 +0,0 @@ | |||
| // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. | |||
| // Use of this source code is governed by a BSD-style | |||
| // license that can be found in the LICENSE file. | |||
| // Source code and contact info at http://github.com/streadway/amqp | |||
| package amqp | |||
| import ( | |||
| "fmt" | |||
| ) | |||
| // Authentication interface provides a means for different SASL authentication | |||
| // mechanisms to be used during connection tuning. | |||
| type Authentication interface { | |||
| Mechanism() string | |||
| Response() string | |||
| } | |||
| // PlainAuth is a similar to Basic Auth in HTTP. | |||
| type PlainAuth struct { | |||
| Username string | |||
| Password string | |||
| } | |||
| // Mechanism returns "PLAIN" | |||
| func (auth *PlainAuth) Mechanism() string { | |||
| return "PLAIN" | |||
| } | |||
| // Response returns the null character delimited encoding for the SASL PLAIN Mechanism. | |||
| func (auth *PlainAuth) Response() string { | |||
| return fmt.Sprintf("\000%s\000%s", auth.Username, auth.Password) | |||
| } | |||
| // AMQPlainAuth is similar to PlainAuth | |||
| type AMQPlainAuth struct { | |||
| Username string | |||
| Password string | |||
| } | |||
| // Mechanism returns "AMQPLAIN" | |||
| func (auth *AMQPlainAuth) Mechanism() string { | |||
| return "AMQPLAIN" | |||
| } | |||
| // Response returns the null character delimited encoding for the SASL PLAIN Mechanism. | |||
| func (auth *AMQPlainAuth) Response() string { | |||
| return fmt.Sprintf("LOGIN:%sPASSWORD:%s", auth.Username, auth.Password) | |||
| } | |||
| // Finds the first mechanism preferred by the client that the server supports. | |||
| func pickSASLMechanism(client []Authentication, serverMechanisms []string) (auth Authentication, ok bool) { | |||
| for _, auth = range client { | |||
| for _, mech := range serverMechanisms { | |||
| if auth.Mechanism() == mech { | |||
| return auth, true | |||
| } | |||
| } | |||
| } | |||
| return | |||
| } | |||
| @ -1,159 +0,0 @@ | |||
| #!/bin/sh | |||
| # | |||
| # Creates the CA, server and client certs to be used by tls_test.go | |||
| # http://www.rabbitmq.com/ssl.html | |||
| # | |||
| # Copy stdout into the const section of tls_test.go or use for RabbitMQ | |||
| # | |||
| root=$PWD/certs | |||
| if [ -f $root/ca/serial ]; then | |||
| echo >&2 "Previous installation found" | |||
| echo >&2 "Remove $root/ca and rerun to overwrite" | |||
| exit 1 | |||
| fi | |||
| mkdir -p $root/ca/private | |||
| mkdir -p $root/ca/certs | |||
| mkdir -p $root/server | |||
| mkdir -p $root/client | |||
| cd $root/ca | |||
| chmod 700 private | |||
| touch index.txt | |||
| echo 'unique_subject = no' > index.txt.attr | |||
| echo '01' > serial | |||
| echo >openssl.cnf ' | |||
| [ ca ] | |||
| default_ca = testca | |||
| [ testca ] | |||
| dir = . | |||
| certificate = $dir/cacert.pem | |||
| database = $dir/index.txt | |||
| new_certs_dir = $dir/certs | |||
| private_key = $dir/private/cakey.pem | |||
| serial = $dir/serial | |||
| default_crl_days = 7 | |||
| default_days = 3650 | |||
| default_md = sha1 | |||
| policy = testca_policy | |||
| x509_extensions = certificate_extensions | |||
| [ testca_policy ] | |||
| commonName = supplied | |||
| stateOrProvinceName = optional | |||
| countryName = optional | |||
| emailAddress = optional | |||
| organizationName = optional | |||
| organizationalUnitName = optional | |||
| [ certificate_extensions ] | |||
| basicConstraints = CA:false | |||
| [ req ] | |||
| default_bits = 2048 | |||
| default_keyfile = ./private/cakey.pem | |||
| default_md = sha1 | |||
| prompt = yes | |||
| distinguished_name = root_ca_distinguished_name | |||
| x509_extensions = root_ca_extensions | |||
| [ root_ca_distinguished_name ] | |||
| commonName = hostname | |||
| [ root_ca_extensions ] | |||
| basicConstraints = CA:true | |||
| keyUsage = keyCertSign, cRLSign | |||
| [ client_ca_extensions ] | |||
| basicConstraints = CA:false | |||
| keyUsage = digitalSignature | |||
| extendedKeyUsage = 1.3.6.1.5.5.7.3.2 | |||
| [ server_ca_extensions ] | |||
| basicConstraints = CA:false | |||
| keyUsage = keyEncipherment | |||
| extendedKeyUsage = 1.3.6.1.5.5.7.3.1 | |||
| subjectAltName = @alt_names | |||
| [ alt_names ] | |||
| IP.1 = 127.0.0.1 | |||
| ' | |||
| openssl req \ | |||
| -x509 \ | |||
| -nodes \ | |||
| -config openssl.cnf \ | |||
| -newkey rsa:2048 \ | |||
| -days 3650 \ | |||
| -subj "/CN=MyTestCA/" \ | |||
| -out cacert.pem \ | |||
| -outform PEM | |||
| openssl x509 \ | |||
| -in cacert.pem \ | |||
| -out cacert.cer \ | |||
| -outform DER | |||
| openssl genrsa -out $root/server/key.pem 2048 | |||
| openssl genrsa -out $root/client/key.pem 2048 | |||
| openssl req \ | |||
| -new \ | |||
| -nodes \ | |||
| -config openssl.cnf \ | |||
| -subj "/CN=127.0.0.1/O=server/" \ | |||
| -key $root/server/key.pem \ | |||
| -out $root/server/req.pem \ | |||
| -outform PEM | |||
| openssl req \ | |||
| -new \ | |||
| -nodes \ | |||
| -config openssl.cnf \ | |||
| -subj "/CN=127.0.0.1/O=client/" \ | |||
| -key $root/client/key.pem \ | |||
| -out $root/client/req.pem \ | |||
| -outform PEM | |||
| openssl ca \ | |||
| -config openssl.cnf \ | |||
| -in $root/server/req.pem \ | |||
| -out $root/server/cert.pem \ | |||
| -notext \ | |||
| -batch \ | |||
| -extensions server_ca_extensions | |||
| openssl ca \ | |||
| -config openssl.cnf \ | |||
| -in $root/client/req.pem \ | |||
| -out $root/client/cert.pem \ | |||
| -notext \ | |||
| -batch \ | |||
| -extensions client_ca_extensions | |||
| cat <<-END | |||
| const caCert = \` | |||
| `cat $root/ca/cacert.pem` | |||
| \` | |||
| const serverCert = \` | |||
| `cat $root/server/cert.pem` | |||
| \` | |||
| const serverKey = \` | |||
| `cat $root/server/key.pem` | |||
| \` | |||
| const clientCert = \` | |||
| `cat $root/client/cert.pem` | |||
| \` | |||
| const clientKey = \` | |||
| `cat $root/client/key.pem` | |||
| \` | |||
| END | |||
| @ -1,94 +0,0 @@ | |||
| package amqp | |||
| import "sync" | |||
| // confirms resequences and notifies one or multiple publisher confirmation listeners | |||
| type confirms struct { | |||
| m sync.Mutex | |||
| listeners []chan Confirmation | |||
| sequencer map[uint64]Confirmation | |||
| published uint64 | |||
| expecting uint64 | |||
| } | |||
| // newConfirms allocates a confirms | |||
| func newConfirms() *confirms { | |||
| return &confirms{ | |||
| sequencer: map[uint64]Confirmation{}, | |||
| published: 0, | |||
| expecting: 1, | |||
| } | |||
| } | |||
| func (c *confirms) Listen(l chan Confirmation) { | |||
| c.m.Lock() | |||
| defer c.m.Unlock() | |||
| c.listeners = append(c.listeners, l) | |||
| } | |||
| // publish increments the publishing counter | |||
| func (c *confirms) Publish() uint64 { | |||
| c.m.Lock() | |||
| defer c.m.Unlock() | |||
| c.published++ | |||
| return c.published | |||
| } | |||
| // confirm confirms one publishing, increments the expecting delivery tag, and | |||
| // removes bookkeeping for that delivery tag. | |||
| func (c *confirms) confirm(confirmation Confirmation) { | |||
| delete(c.sequencer, c.expecting) | |||
| c.expecting++ | |||
| for _, l := range c.listeners { | |||
| l <- confirmation | |||
| } | |||
| } | |||
| // resequence confirms any out of order delivered confirmations | |||
| func (c *confirms) resequence() { | |||
| for c.expecting <= c.published { | |||
| sequenced, found := c.sequencer[c.expecting] | |||
| if !found { | |||
| return | |||
| } | |||
| c.confirm(sequenced) | |||
| } | |||
| } | |||
| // one confirms one publishing and all following in the publishing sequence | |||
| func (c *confirms) One(confirmed Confirmation) { | |||
| c.m.Lock() | |||
| defer c.m.Unlock() | |||
| if c.expecting == confirmed.DeliveryTag { | |||
| c.confirm(confirmed) | |||
| } else { | |||
| c.sequencer[confirmed.DeliveryTag] = confirmed | |||
| } | |||
| c.resequence() | |||
| } | |||
| // multiple confirms all publishings up until the delivery tag | |||
| func (c *confirms) Multiple(confirmed Confirmation) { | |||
| c.m.Lock() | |||
| defer c.m.Unlock() | |||
| for c.expecting <= confirmed.DeliveryTag { | |||
| c.confirm(Confirmation{c.expecting, confirmed.Ack}) | |||
| } | |||
| c.resequence() | |||
| } | |||
| // Close closes all listeners, discarding any out of sequence confirmations | |||
| func (c *confirms) Close() error { | |||
| c.m.Lock() | |||
| defer c.m.Unlock() | |||
| for _, l := range c.listeners { | |||
| close(l) | |||
| } | |||
| c.listeners = nil | |||
| return nil | |||
| } | |||
| @ -1,847 +0,0 @@ | |||
| // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. | |||
| // Use of this source code is governed by a BSD-style | |||
| // license that can be found in the LICENSE file. | |||
| // Source code and contact info at http://github.com/streadway/amqp | |||
| package amqp | |||
| import ( | |||
| "bufio" | |||
| "crypto/tls" | |||
| "io" | |||
| "net" | |||
| "reflect" | |||
| "strconv" | |||
| "strings" | |||
| "sync" | |||
| "sync/atomic" | |||
| "time" | |||
| ) | |||
| const ( | |||
| maxChannelMax = (2 << 15) - 1 | |||
| defaultHeartbeat = 10 * time.Second | |||
| defaultConnectionTimeout = 30 * time.Second | |||
| defaultProduct = "https://github.com/streadway/amqp" | |||
| defaultVersion = "β" | |||
| // Safer default that makes channel leaks a lot easier to spot | |||
| // before they create operational headaches. See https://github.com/rabbitmq/rabbitmq-server/issues/1593. | |||
| defaultChannelMax = (2 << 10) - 1 | |||
| defaultLocale = "en_US" | |||
| ) | |||
| // Config is used in DialConfig and Open to specify the desired tuning | |||
| // parameters used during a connection open handshake. The negotiated tuning | |||
| // will be stored in the returned connection's Config field. | |||
| type Config struct { | |||
| // The SASL mechanisms to try in the client request, and the successful | |||
| // mechanism used on the Connection object. | |||
| // If SASL is nil, PlainAuth from the URL is used. | |||
| SASL []Authentication | |||
| // Vhost specifies the namespace of permissions, exchanges, queues and | |||
| // bindings on the server. Dial sets this to the path parsed from the URL. | |||
| Vhost string | |||
| ChannelMax int // 0 max channels means 2^16 - 1 | |||
| FrameSize int // 0 max bytes means unlimited | |||
| Heartbeat time.Duration // less than 1s uses the server's interval | |||
| // TLSClientConfig specifies the client configuration of the TLS connection | |||
| // when establishing a tls transport. | |||
| // If the URL uses an amqps scheme, then an empty tls.Config with the | |||
| // ServerName from the URL is used. | |||
| TLSClientConfig *tls.Config | |||
| // Properties is table of properties that the client advertises to the server. | |||
| // This is an optional setting - if the application does not set this, | |||
| // the underlying library will use a generic set of client properties. | |||
| Properties Table | |||
| // Connection locale that we expect to always be en_US | |||
| // Even though servers must return it as per the AMQP 0-9-1 spec, | |||
| // we are not aware of it being used other than to satisfy the spec requirements | |||
| Locale string | |||
| // Dial returns a net.Conn prepared for a TLS handshake with TSLClientConfig, | |||
| // then an AMQP connection handshake. | |||
| // If Dial is nil, net.DialTimeout with a 30s connection and 30s deadline is | |||
| // used during TLS and AMQP handshaking. | |||
| Dial func(network, addr string) (net.Conn, error) | |||
| } | |||
| // Connection manages the serialization and deserialization of frames from IO | |||
| // and dispatches the frames to the appropriate channel. All RPC methods and | |||
| // asynchronous Publishing, Delivery, Ack, Nack and Return messages are | |||
| // multiplexed on this channel. There must always be active receivers for | |||
| // every asynchronous message on this connection. | |||
| type Connection struct { | |||
| destructor sync.Once // shutdown once | |||
| sendM sync.Mutex // conn writer mutex | |||
| m sync.Mutex // struct field mutex | |||
| conn io.ReadWriteCloser | |||
| rpc chan message | |||
| writer *writer | |||
| sends chan time.Time // timestamps of each frame sent | |||
| deadlines chan readDeadliner // heartbeater updates read deadlines | |||
| allocator *allocator // id generator valid after openTune | |||
| channels map[uint16]*Channel | |||
| noNotify bool // true when we will never notify again | |||
| closes []chan *Error | |||
| blocks []chan Blocking | |||
| errors chan *Error | |||
| Config Config // The negotiated Config after connection.open | |||
| Major int // Server's major version | |||
| Minor int // Server's minor version | |||
| Properties Table // Server properties | |||
| Locales []string // Server locales | |||
| closed int32 // Will be 1 if the connection is closed, 0 otherwise. Should only be accessed as atomic | |||
| } | |||
| type readDeadliner interface { | |||
| SetReadDeadline(time.Time) error | |||
| } | |||
| // DefaultDial establishes a connection when config.Dial is not provided | |||
| func DefaultDial(connectionTimeout time.Duration) func(network, addr string) (net.Conn, error) { | |||
| return func(network, addr string) (net.Conn, error) { | |||
| conn, err := net.DialTimeout(network, addr, connectionTimeout) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| // Heartbeating hasn't started yet, don't stall forever on a dead server. | |||
| // A deadline is set for TLS and AMQP handshaking. After AMQP is established, | |||
| // the deadline is cleared in openComplete. | |||
| if err := conn.SetDeadline(time.Now().Add(connectionTimeout)); err != nil { | |||
| return nil, err | |||
| } | |||
| return conn, nil | |||
| } | |||
| } | |||
| // Dial accepts a string in the AMQP URI format and returns a new Connection | |||
| // over TCP using PlainAuth. Defaults to a server heartbeat interval of 10 | |||
| // seconds and sets the handshake deadline to 30 seconds. After handshake, | |||
| // deadlines are cleared. | |||
| // | |||
| // Dial uses the zero value of tls.Config when it encounters an amqps:// | |||
| // scheme. It is equivalent to calling DialTLS(amqp, nil). | |||
| func Dial(url string) (*Connection, error) { | |||
| return DialConfig(url, Config{ | |||
| Heartbeat: defaultHeartbeat, | |||
| Locale: defaultLocale, | |||
| }) | |||
| } | |||
| // DialTLS accepts a string in the AMQP URI format and returns a new Connection | |||
| // over TCP using PlainAuth. Defaults to a server heartbeat interval of 10 | |||
| // seconds and sets the initial read deadline to 30 seconds. | |||
| // | |||
| // DialTLS uses the provided tls.Config when encountering an amqps:// scheme. | |||
| func DialTLS(url string, amqps *tls.Config) (*Connection, error) { | |||
| return DialConfig(url, Config{ | |||
| Heartbeat: defaultHeartbeat, | |||
| TLSClientConfig: amqps, | |||
| Locale: defaultLocale, | |||
| }) | |||
| } | |||
| // DialConfig accepts a string in the AMQP URI format and a configuration for | |||
| // the transport and connection setup, returning a new Connection. Defaults to | |||
| // a server heartbeat interval of 10 seconds and sets the initial read deadline | |||
| // to 30 seconds. | |||
| func DialConfig(url string, config Config) (*Connection, error) { | |||
| var err error | |||
| var conn net.Conn | |||
| uri, err := ParseURI(url) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if config.SASL == nil { | |||
| config.SASL = []Authentication{uri.PlainAuth()} | |||
| } | |||
| if config.Vhost == "" { | |||
| config.Vhost = uri.Vhost | |||
| } | |||
| addr := net.JoinHostPort(uri.Host, strconv.FormatInt(int64(uri.Port), 10)) | |||
| dialer := config.Dial | |||
| if dialer == nil { | |||
| dialer = DefaultDial(defaultConnectionTimeout) | |||
| } | |||
| conn, err = dialer("tcp", addr) | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if uri.Scheme == "amqps" { | |||
| if config.TLSClientConfig == nil { | |||
| config.TLSClientConfig = new(tls.Config) | |||
| } | |||
| // If ServerName has not been specified in TLSClientConfig, | |||
| // set it to the URI host used for this connection. | |||
| if config.TLSClientConfig.ServerName == "" { | |||
| config.TLSClientConfig.ServerName = uri.Host | |||
| } | |||
| client := tls.Client(conn, config.TLSClientConfig) | |||
| if err := client.Handshake(); err != nil { | |||
| conn.Close() | |||
| return nil, err | |||
| } | |||
| conn = client | |||
| } | |||
| return Open(conn, config) | |||
| } | |||
| /* | |||
| Open accepts an already established connection, or other io.ReadWriteCloser as | |||
| a transport. Use this method if you have established a TLS connection or wish | |||
| to use your own custom transport. | |||
| */ | |||
| func Open(conn io.ReadWriteCloser, config Config) (*Connection, error) { | |||
| c := &Connection{ | |||
| conn: conn, | |||
| writer: &writer{bufio.NewWriter(conn)}, | |||
| channels: make(map[uint16]*Channel), | |||
| rpc: make(chan message), | |||
| sends: make(chan time.Time), | |||
| errors: make(chan *Error, 1), | |||
| deadlines: make(chan readDeadliner, 1), | |||
| } | |||
| go c.reader(conn) | |||
| return c, c.open(config) | |||
| } | |||
| /* | |||
| LocalAddr returns the local TCP peer address, or ":0" (the zero value of net.TCPAddr) | |||
| as a fallback default value if the underlying transport does not support LocalAddr(). | |||
| */ | |||
| func (c *Connection) LocalAddr() net.Addr { | |||
| if conn, ok := c.conn.(interface { | |||
| LocalAddr() net.Addr | |||
| }); ok { | |||
| return conn.LocalAddr() | |||
| } | |||
| return &net.TCPAddr{} | |||
| } | |||
| // ConnectionState returns basic TLS details of the underlying transport. | |||
| // Returns a zero value when the underlying connection does not implement | |||
| // ConnectionState() tls.ConnectionState. | |||
| func (c *Connection) ConnectionState() tls.ConnectionState { | |||
| if conn, ok := c.conn.(interface { | |||
| ConnectionState() tls.ConnectionState | |||
| }); ok { | |||
| return conn.ConnectionState() | |||
| } | |||
| return tls.ConnectionState{} | |||
| } | |||
| /* | |||
| NotifyClose registers a listener for close events either initiated by an error | |||
| accompanying a connection.close method or by a normal shutdown. | |||
| On normal shutdowns, the chan will be closed. | |||
| To reconnect after a transport or protocol error, register a listener here and | |||
| re-run your setup process. | |||
| */ | |||
| func (c *Connection) NotifyClose(receiver chan *Error) chan *Error { | |||
| c.m.Lock() | |||
| defer c.m.Unlock() | |||
| if c.noNotify { | |||
| close(receiver) | |||
| } else { | |||
| c.closes = append(c.closes, receiver) | |||
| } | |||
| return receiver | |||
| } | |||
| /* | |||
| NotifyBlocked registers a listener for RabbitMQ specific TCP flow control | |||
| method extensions connection.blocked and connection.unblocked. Flow control is | |||
| active with a reason when Blocking.Blocked is true. When a Connection is | |||
| blocked, all methods will block across all connections until server resources | |||
| become free again. | |||
| This optional extension is supported by the server when the | |||
| "connection.blocked" server capability key is true. | |||
| */ | |||
| func (c *Connection) NotifyBlocked(receiver chan Blocking) chan Blocking { | |||
| c.m.Lock() | |||
| defer c.m.Unlock() | |||
| if c.noNotify { | |||
| close(receiver) | |||
| } else { | |||
| c.blocks = append(c.blocks, receiver) | |||
| } | |||
| return receiver | |||
| } | |||
| /* | |||
| Close requests and waits for the response to close the AMQP connection. | |||
| It's advisable to use this message when publishing to ensure all kernel buffers | |||
| have been flushed on the server and client before exiting. | |||
| An error indicates that server may not have received this request to close but | |||
| the connection should be treated as closed regardless. | |||
| After returning from this call, all resources associated with this connection, | |||
| including the underlying io, Channels, Notify listeners and Channel consumers | |||
| will also be closed. | |||
| */ | |||
| func (c *Connection) Close() error { | |||
| if c.IsClosed() { | |||
| return ErrClosed | |||
| } | |||
| defer c.shutdown(nil) | |||
| return c.call( | |||
| &connectionClose{ | |||
| ReplyCode: replySuccess, | |||
| ReplyText: "kthxbai", | |||
| }, | |||
| &connectionCloseOk{}, | |||
| ) | |||
| } | |||
| func (c *Connection) closeWith(err *Error) error { | |||
| if c.IsClosed() { | |||
| return ErrClosed | |||
| } | |||
| defer c.shutdown(err) | |||
| return c.call( | |||
| &connectionClose{ | |||
| ReplyCode: uint16(err.Code), | |||
| ReplyText: err.Reason, | |||
| }, | |||
| &connectionCloseOk{}, | |||
| ) | |||
| } | |||
| // IsClosed returns true if the connection is marked as closed, otherwise false | |||
| // is returned. | |||
| func (c *Connection) IsClosed() bool { | |||
| return (atomic.LoadInt32(&c.closed) == 1) | |||
| } | |||
| func (c *Connection) send(f frame) error { | |||
| if c.IsClosed() { | |||
| return ErrClosed | |||
| } | |||
| c.sendM.Lock() | |||
| err := c.writer.WriteFrame(f) | |||
| c.sendM.Unlock() | |||
| if err != nil { | |||
| // shutdown could be re-entrant from signaling notify chans | |||
| go c.shutdown(&Error{ | |||
| Code: FrameError, | |||
| Reason: err.Error(), | |||
| }) | |||
| } else { | |||
| // Broadcast we sent a frame, reducing heartbeats, only | |||
| // if there is something that can receive - like a non-reentrant | |||
| // call or if the heartbeater isn't running | |||
| select { | |||
| case c.sends <- time.Now(): | |||
| default: | |||
| } | |||
| } | |||
| return err | |||
| } | |||
| func (c *Connection) shutdown(err *Error) { | |||
| atomic.StoreInt32(&c.closed, 1) | |||
| c.destructor.Do(func() { | |||
| c.m.Lock() | |||
| defer c.m.Unlock() | |||
| if err != nil { | |||
| for _, c := range c.closes { | |||
| c <- err | |||
| } | |||
| } | |||
| if err != nil { | |||
| c.errors <- err | |||
| } | |||
| // Shutdown handler goroutine can still receive the result. | |||
| close(c.errors) | |||
| for _, c := range c.closes { | |||
| close(c) | |||
| } | |||
| for _, c := range c.blocks { | |||
| close(c) | |||
| } | |||
| // Shutdown the channel, but do not use closeChannel() as it calls | |||
| // releaseChannel() which requires the connection lock. | |||
| // | |||
| // Ranging over c.channels and calling releaseChannel() that mutates | |||
| // c.channels is racy - see commit 6063341 for an example. | |||
| for _, ch := range c.channels { | |||
| ch.shutdown(err) | |||
| } | |||
| c.conn.Close() | |||
| c.channels = map[uint16]*Channel{} | |||
| c.allocator = newAllocator(1, c.Config.ChannelMax) | |||
| c.noNotify = true | |||
| }) | |||
| } | |||
| // All methods sent to the connection channel should be synchronous so we | |||
| // can handle them directly without a framing component | |||
| func (c *Connection) demux(f frame) { | |||
| if f.channel() == 0 { | |||
| c.dispatch0(f) | |||
| } else { | |||
| c.dispatchN(f) | |||
| } | |||
| } | |||
| func (c *Connection) dispatch0(f frame) { | |||
| switch mf := f.(type) { | |||
| case *methodFrame: | |||
| switch m := mf.Method.(type) { | |||
| case *connectionClose: | |||
| // Send immediately as shutdown will close our side of the writer. | |||
| c.send(&methodFrame{ | |||
| ChannelId: 0, | |||
| Method: &connectionCloseOk{}, | |||
| }) | |||
| c.shutdown(newError(m.ReplyCode, m.ReplyText)) | |||
| case *connectionBlocked: | |||
| for _, c := range c.blocks { | |||
| c <- Blocking{Active: true, Reason: m.Reason} | |||
| } | |||
| case *connectionUnblocked: | |||
| for _, c := range c.blocks { | |||
| c <- Blocking{Active: false} | |||
| } | |||
| default: | |||
| c.rpc <- m | |||
| } | |||
| case *heartbeatFrame: | |||
| // kthx - all reads reset our deadline. so we can drop this | |||
| default: | |||
| // lolwat - channel0 only responds to methods and heartbeats | |||
| c.closeWith(ErrUnexpectedFrame) | |||
| } | |||
| } | |||
| func (c *Connection) dispatchN(f frame) { | |||
| c.m.Lock() | |||
| channel := c.channels[f.channel()] | |||
| c.m.Unlock() | |||
| if channel != nil { | |||
| channel.recv(channel, f) | |||
| } else { | |||
| c.dispatchClosed(f) | |||
| } | |||
| } | |||
| // section 2.3.7: "When a peer decides to close a channel or connection, it | |||
| // sends a Close method. The receiving peer MUST respond to a Close with a | |||
| // Close-Ok, and then both parties can close their channel or connection. Note | |||
| // that if peers ignore Close, deadlock can happen when both peers send Close | |||
| // at the same time." | |||
| // | |||
| // When we don't have a channel, so we must respond with close-ok on a close | |||
| // method. This can happen between a channel exception on an asynchronous | |||
| // method like basic.publish and a synchronous close with channel.close. | |||
| // In that case, we'll get both a channel.close and channel.close-ok in any | |||
| // order. | |||
| func (c *Connection) dispatchClosed(f frame) { | |||
| // Only consider method frames, drop content/header frames | |||
| if mf, ok := f.(*methodFrame); ok { | |||
| switch mf.Method.(type) { | |||
| case *channelClose: | |||
| c.send(&methodFrame{ | |||
| ChannelId: f.channel(), | |||
| Method: &channelCloseOk{}, | |||
| }) | |||
| case *channelCloseOk: | |||
| // we are already closed, so do nothing | |||
| default: | |||
| // unexpected method on closed channel | |||
| c.closeWith(ErrClosed) | |||
| } | |||
| } | |||
| } | |||
| // Reads each frame off the IO and hand off to the connection object that | |||
| // will demux the streams and dispatch to one of the opened channels or | |||
| // handle on channel 0 (the connection channel). | |||
| func (c *Connection) reader(r io.Reader) { | |||
| buf := bufio.NewReader(r) | |||
| frames := &reader{buf} | |||
| conn, haveDeadliner := r.(readDeadliner) | |||
| for { | |||
| frame, err := frames.ReadFrame() | |||
| if err != nil { | |||
| c.shutdown(&Error{Code: FrameError, Reason: err.Error()}) | |||
| return | |||
| } | |||
| c.demux(frame) | |||
| if haveDeadliner { | |||
| c.deadlines <- conn | |||
| } | |||
| } | |||
| } | |||
| // Ensures that at least one frame is being sent at the tuned interval with a | |||
| // jitter tolerance of 1s | |||
| func (c *Connection) heartbeater(interval time.Duration, done chan *Error) { | |||
| const maxServerHeartbeatsInFlight = 3 | |||
| var sendTicks <-chan time.Time | |||
| if interval > 0 { | |||
| ticker := time.NewTicker(interval) | |||
| defer ticker.Stop() | |||
| sendTicks = ticker.C | |||
| } | |||
| lastSent := time.Now() | |||
| for { | |||
| select { | |||
| case at, stillSending := <-c.sends: | |||
| // When actively sending, depend on sent frames to reset server timer | |||
| if stillSending { | |||
| lastSent = at | |||
| } else { | |||
| return | |||
| } | |||
| case at := <-sendTicks: | |||
| // When idle, fill the space with a heartbeat frame | |||
| if at.Sub(lastSent) > interval-time.Second { | |||
| if err := c.send(&heartbeatFrame{}); err != nil { | |||
| // send heartbeats even after close/closeOk so we | |||
| // tick until the connection starts erroring | |||
| return | |||
| } | |||
| } | |||
| case conn := <-c.deadlines: | |||
| // When reading, reset our side of the deadline, if we've negotiated one with | |||
| // a deadline that covers at least 2 server heartbeats | |||
| if interval > 0 { | |||
| conn.SetReadDeadline(time.Now().Add(maxServerHeartbeatsInFlight * interval)) | |||
| } | |||
| case <-done: | |||
| return | |||
| } | |||
| } | |||
| } | |||
| // Convenience method to inspect the Connection.Properties["capabilities"] | |||
| // Table for server identified capabilities like "basic.ack" or | |||
| // "confirm.select". | |||
| func (c *Connection) isCapable(featureName string) bool { | |||
| capabilities, _ := c.Properties["capabilities"].(Table) | |||
| hasFeature, _ := capabilities[featureName].(bool) | |||
| return hasFeature | |||
| } | |||
| // allocateChannel records but does not open a new channel with a unique id. | |||
| // This method is the initial part of the channel lifecycle and paired with | |||
| // releaseChannel | |||
| func (c *Connection) allocateChannel() (*Channel, error) { | |||
| c.m.Lock() | |||
| defer c.m.Unlock() | |||
| if c.IsClosed() { | |||
| return nil, ErrClosed | |||
| } | |||
| id, ok := c.allocator.next() | |||
| if !ok { | |||
| return nil, ErrChannelMax | |||
| } | |||
| ch := newChannel(c, uint16(id)) | |||
| c.channels[uint16(id)] = ch | |||
| return ch, nil | |||
| } | |||
| // releaseChannel removes a channel from the registry as the final part of the | |||
| // channel lifecycle | |||
| func (c *Connection) releaseChannel(id uint16) { | |||
| c.m.Lock() | |||
| defer c.m.Unlock() | |||
| delete(c.channels, id) | |||
| c.allocator.release(int(id)) | |||
| } | |||
| // openChannel allocates and opens a channel, must be paired with closeChannel | |||
| func (c *Connection) openChannel() (*Channel, error) { | |||
| ch, err := c.allocateChannel() | |||
| if err != nil { | |||
| return nil, err | |||
| } | |||
| if err := ch.open(); err != nil { | |||
| c.releaseChannel(ch.id) | |||
| return nil, err | |||
| } | |||
| return ch, nil | |||
| } | |||
| // closeChannel releases and initiates a shutdown of the channel. All channel | |||
| // closures should be initiated here for proper channel lifecycle management on | |||
| // this connection. | |||
| func (c *Connection) closeChannel(ch *Channel, e *Error) { | |||
| ch.shutdown(e) | |||
| c.releaseChannel(ch.id) | |||
| } | |||
| /* | |||
| Channel opens a unique, concurrent server channel to process the bulk of AMQP | |||
| messages. Any error from methods on this receiver will render the receiver | |||
| invalid and a new Channel should be opened. | |||
| */ | |||
| func (c *Connection) Channel() (*Channel, error) { | |||
| return c.openChannel() | |||
| } | |||
| func (c *Connection) call(req message, res ...message) error { | |||
| // Special case for when the protocol header frame is sent insted of a | |||
| // request method | |||
| if req != nil { | |||
| if err := c.send(&methodFrame{ChannelId: 0, Method: req}); err != nil { | |||
| return err | |||
| } | |||
| } | |||
| select { | |||
| case err, ok := <-c.errors: | |||
| if !ok { | |||
| return ErrClosed | |||
| } | |||
| return err | |||
| case msg := <-c.rpc: | |||
| // Try to match one of the result types | |||
| for _, try := range res { | |||
| if reflect.TypeOf(msg) == reflect.TypeOf(try) { | |||
| // *res = *msg | |||
| vres := reflect.ValueOf(try).Elem() | |||
| vmsg := reflect.ValueOf(msg).Elem() | |||
| vres.Set(vmsg) | |||
| return nil | |||
| } | |||
| } | |||
| return ErrCommandInvalid | |||
| } | |||
| // unreachable | |||
| } | |||
| // Connection = open-Connection *use-Connection close-Connection | |||
| // open-Connection = C:protocol-header | |||
| // S:START C:START-OK | |||
| // *challenge | |||
| // S:TUNE C:TUNE-OK | |||
| // C:OPEN S:OPEN-OK | |||
| // challenge = S:SECURE C:SECURE-OK | |||
| // use-Connection = *channel | |||
| // close-Connection = C:CLOSE S:CLOSE-OK | |||
| // / S:CLOSE C:CLOSE-OK | |||
| func (c *Connection) open(config Config) error { | |||
| if err := c.send(&protocolHeader{}); err != nil { | |||
| return err | |||
| } | |||
| return c.openStart(config) | |||
| } | |||
| func (c *Connection) openStart(config Config) error { | |||
| start := &connectionStart{} | |||
| if err := c.call(nil, start); err != nil { | |||
| return err | |||
| } | |||
| c.Major = int(start.VersionMajor) | |||
| c.Minor = int(start.VersionMinor) | |||
| c.Properties = Table(start.ServerProperties) | |||
| c.Locales = strings.Split(start.Locales, " ") | |||
| // eventually support challenge/response here by also responding to | |||
| // connectionSecure. | |||
| auth, ok := pickSASLMechanism(config.SASL, strings.Split(start.Mechanisms, " ")) | |||
| if !ok { | |||
| return ErrSASL | |||
| } | |||
| // Save this mechanism off as the one we chose | |||
| c.Config.SASL = []Authentication{auth} | |||
| // Set the connection locale to client locale | |||
| c.Config.Locale = config.Locale | |||
| return c.openTune(config, auth) | |||
| } | |||
| func (c *Connection) openTune(config Config, auth Authentication) error { | |||
| if len(config.Properties) == 0 { | |||
| config.Properties = Table{ | |||
| "product": defaultProduct, | |||
| "version": defaultVersion, | |||
| } | |||
| } | |||
| config.Properties["capabilities"] = Table{ | |||
| "connection.blocked": true, | |||
| "consumer_cancel_notify": true, | |||
| } | |||
| ok := &connectionStartOk{ | |||
| ClientProperties: config.Properties, | |||
| Mechanism: auth.Mechanism(), | |||
| Response: auth.Response(), | |||
| Locale: config.Locale, | |||
| } | |||
| tune := &connectionTune{} | |||
| if err := c.call(ok, tune); err != nil { | |||
| // per spec, a connection can only be closed when it has been opened | |||
| // so at this point, we know it's an auth error, but the socket | |||
| // was closed instead. Return a meaningful error. | |||
| return ErrCredentials | |||
| } | |||
| // When the server and client both use default 0, then the max channel is | |||
| // only limited by uint16. | |||
| c.Config.ChannelMax = pick(config.ChannelMax, int(tune.ChannelMax)) | |||
| if c.Config.ChannelMax == 0 { | |||
| c.Config.ChannelMax = defaultChannelMax | |||
| } | |||
| c.Config.ChannelMax = min(c.Config.ChannelMax, maxChannelMax) | |||
| // Frame size includes headers and end byte (len(payload)+8), even if | |||
| // this is less than FrameMinSize, use what the server sends because the | |||
| // alternative is to stop the handshake here. | |||
| c.Config.FrameSize = pick(config.FrameSize, int(tune.FrameMax)) | |||
| // Save this off for resetDeadline() | |||
| c.Config.Heartbeat = time.Second * time.Duration(pick( | |||
| int(config.Heartbeat/time.Second), | |||
| int(tune.Heartbeat))) | |||
| // "The client should start sending heartbeats after receiving a | |||
| // Connection.Tune method" | |||
| go c.heartbeater(c.Config.Heartbeat, c.NotifyClose(make(chan *Error, 1))) | |||
| if err := c.send(&methodFrame{ | |||
| ChannelId: 0, | |||
| Method: &connectionTuneOk{ | |||
| ChannelMax: uint16(c.Config.ChannelMax), | |||
| FrameMax: uint32(c.Config.FrameSize), | |||
| Heartbeat: uint16(c.Config.Heartbeat / time.Second), | |||
| }, | |||
| }); err != nil { | |||
| return err | |||
| } | |||
| return c.openVhost(config) | |||
| } | |||
| func (c *Connection) openVhost(config Config) error { | |||
| req := &connectionOpen{VirtualHost: config.Vhost} | |||
| res := &connectionOpenOk{} | |||
| if err := c.call(req, res); err != nil { | |||
| // Cannot be closed yet, but we know it's a vhost problem | |||
| return ErrVhost | |||
| } | |||
| c.Config.Vhost = config.Vhost | |||
| return c.openComplete() | |||
| } | |||
| // openComplete performs any final Connection initialization dependent on the | |||
| // connection handshake and clears any state needed for TLS and AMQP handshaking. | |||
| func (c *Connection) openComplete() error { | |||
| // We clear the deadlines and let the heartbeater reset the read deadline if requested. | |||
| // RabbitMQ uses TCP flow control at this point for pushback so Writes can | |||
| // intentionally block. | |||
| if deadliner, ok := c.conn.(interface { | |||
| SetDeadline(time.Time) error | |||
| }); ok { | |||
| _ = deadliner.SetDeadline(time.Time{}) | |||
| } | |||
| c.allocator = newAllocator(1, c.Config.ChannelMax) | |||
| return nil | |||
| } | |||
| func max(a, b int) int { | |||
| if a > b { | |||
| return a | |||
| } | |||
| return b | |||
| } | |||
| func min(a, b int) int { | |||
| if a < b { | |||
| return a | |||
| } | |||
| return b | |||
| } | |||
| func pick(client, server int) int { | |||
| if client == 0 || server == 0 { | |||
| return max(client, server) | |||
| } | |||
| return min(client, server) | |||
| } | |||
| @ -1,142 +0,0 @@ | |||
| // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. | |||
| // Use of this source code is governed by a BSD-style | |||
| // license that can be found in the LICENSE file. | |||
| // Source code and contact info at http://github.com/streadway/amqp | |||
| package amqp | |||
| import ( | |||
| "os" | |||
| "strconv" | |||
| "sync" | |||
| "sync/atomic" | |||
| ) | |||
| var consumerSeq uint64 | |||
| const consumerTagLengthMax = 0xFF // see writeShortstr | |||
| func uniqueConsumerTag() string { | |||
| return commandNameBasedUniqueConsumerTag(os.Args[0]) | |||
| } | |||
| func commandNameBasedUniqueConsumerTag(commandName string) string { | |||
| tagPrefix := "ctag-" | |||
| tagInfix := commandName | |||
| tagSuffix := "-" + strconv.FormatUint(atomic.AddUint64(&consumerSeq, 1), 10) | |||
| if len(tagPrefix)+len(tagInfix)+len(tagSuffix) > consumerTagLengthMax { | |||
| tagInfix = "streadway/amqp" | |||
| } | |||
| return tagPrefix + tagInfix + tagSuffix | |||
| } | |||
| type consumerBuffers map[string]chan *Delivery | |||
| // Concurrent type that manages the consumerTag -> | |||
| // ingress consumerBuffer mapping | |||
| type consumers struct { | |||
| sync.WaitGroup // one for buffer | |||
| closed chan struct{} // signal buffer | |||
| sync.Mutex // protects below | |||
| chans consumerBuffers | |||
| } | |||
| func makeConsumers() *consumers { | |||
| return &consumers{ | |||
| closed: make(chan struct{}), | |||
| chans: make(consumerBuffers), | |||
| } | |||
| } | |||
| func (subs *consumers) buffer(in chan *Delivery, out chan Delivery) { | |||
| defer close(out) | |||
| defer subs.Done() | |||
| var inflight = in | |||
| var queue []*Delivery | |||
| for delivery := range in { | |||
| queue = append(queue, delivery) | |||
| for len(queue) > 0 { | |||
| select { | |||
| case <-subs.closed: | |||
| // closed before drained, drop in-flight | |||
| return | |||
| case delivery, consuming := <-inflight: | |||
| if consuming { | |||
| queue = append(queue, delivery) | |||
| } else { | |||
| inflight = nil | |||
| } | |||
| case out <- *queue[0]: | |||
| queue = queue[1:] | |||
| } | |||
| } | |||
| } | |||
| } | |||
| // On key conflict, close the previous channel. | |||
| func (subs *consumers) add(tag string, consumer chan Delivery) { | |||
| subs.Lock() | |||
| defer subs.Unlock() | |||
| if prev, found := subs.chans[tag]; found { | |||
| close(prev) | |||
| } | |||
| in := make(chan *Delivery) | |||
| subs.chans[tag] = in | |||
| subs.Add(1) | |||
| go subs.buffer(in, consumer) | |||
| } | |||
| func (subs *consumers) cancel(tag string) (found bool) { | |||
| subs.Lock() | |||
| defer subs.Unlock() | |||
| ch, found := subs.chans[tag] | |||
| if found { | |||
| delete(subs.chans, tag) | |||
| close(ch) | |||
| } | |||
| return found | |||
| } | |||
| func (subs *consumers) close() { | |||
| subs.Lock() | |||
| defer subs.Unlock() | |||
| close(subs.closed) | |||
| for tag, ch := range subs.chans { | |||
| delete(subs.chans, tag) | |||
| close(ch) | |||
| } | |||
| subs.Wait() | |||
| } | |||
| // Sends a delivery to a the consumer identified by `tag`. | |||
| // If unbuffered channels are used for Consume this method | |||
| // could block all deliveries until the consumer | |||
| // receives on the other end of the channel. | |||
| func (subs *consumers) send(tag string, msg *Delivery) bool { | |||
| subs.Lock() | |||
| defer subs.Unlock() | |||
| buffer, found := subs.chans[tag] | |||
| if found { | |||
| buffer <- msg | |||
| } | |||
| return found | |||
| } | |||
| @ -1,173 +0,0 @@ | |||
| // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. | |||
| // Use of this source code is governed by a BSD-style | |||
| // license that can be found in the LICENSE file. | |||
| // Source code and contact info at http://github.com/streadway/amqp | |||
| package amqp | |||
| import ( | |||
| "errors" | |||
| "time" | |||
| ) | |||
| var errDeliveryNotInitialized = errors.New("delivery not initialized") | |||
| // Acknowledger notifies the server of successful or failed consumption of | |||
| // delivieries via identifier found in the Delivery.DeliveryTag field. | |||
| // | |||
| // Applications can provide mock implementations in tests of Delivery handlers. | |||
| type Acknowledger interface { | |||
| Ack(tag uint64, multiple bool) error | |||
| Nack(tag uint64, multiple bool, requeue bool) error | |||
| Reject(tag uint64, requeue bool) error | |||
| } | |||
| // Delivery captures the fields for a previously delivered message resident in | |||
| // a queue to be delivered by the server to a consumer from Channel.Consume or | |||
| // Channel.Get. | |||
| type Delivery struct { | |||
| Acknowledger Acknowledger // the channel from which this delivery arrived | |||
| Headers Table // Application or header exchange table | |||
| // Properties | |||
| ContentType string // MIME content type | |||
| ContentEncoding string // MIME content encoding | |||
| DeliveryMode uint8 // queue implementation use - non-persistent (1) or persistent (2) | |||
| Priority uint8 // queue implementation use - 0 to 9 | |||
| CorrelationId string // application use - correlation identifier | |||
| ReplyTo string // application use - address to reply to (ex: RPC) | |||
| Expiration string // implementation use - message expiration spec | |||
| MessageId string // application use - message identifier | |||
| Timestamp time.Time // application use - message timestamp | |||
| Type string // application use - message type name | |||
| UserId string // application use - creating user - should be authenticated user | |||
| AppId string // application use - creating application id | |||
| // Valid only with Channel.Consume | |||
| ConsumerTag string | |||
| // Valid only with Channel.Get | |||
| MessageCount uint32 | |||
| DeliveryTag uint64 | |||
| Redelivered bool | |||
| Exchange string // basic.publish exchange | |||
| RoutingKey string // basic.publish routing key | |||
| Body []byte | |||
| } | |||
| func newDelivery(channel *Channel, msg messageWithContent) *Delivery { | |||
| props, body := msg.getContent() | |||
| delivery := Delivery{ | |||
| Acknowledger: channel, | |||
| Headers: props.Headers, | |||
| ContentType: props.ContentType, | |||
| ContentEncoding: props.ContentEncoding, | |||
| DeliveryMode: props.DeliveryMode, | |||
| Priority: props.Priority, | |||
| CorrelationId: props.CorrelationId, | |||
| ReplyTo: props.ReplyTo, | |||
| Expiration: props.Expiration, | |||
| MessageId: props.MessageId, | |||
| Timestamp: props.Timestamp, | |||
| Type: props.Type, | |||
| UserId: props.UserId, | |||
| AppId: props.AppId, | |||
| Body: body, | |||
| } | |||
| // Properties for the delivery types | |||
| switch m := msg.(type) { | |||
| case *basicDeliver: | |||
| delivery.ConsumerTag = m.ConsumerTag | |||
| delivery.DeliveryTag = m.DeliveryTag | |||
| delivery.Redelivered = m.Redelivered | |||
| delivery.Exchange = m.Exchange | |||
| delivery.RoutingKey = m.RoutingKey | |||
| case *basicGetOk: | |||
| delivery.MessageCount = m.MessageCount | |||
| delivery.DeliveryTag = m.DeliveryTag | |||
| delivery.Redelivered = m.Redelivered | |||
| delivery.Exchange = m.Exchange | |||
| delivery.RoutingKey = m.RoutingKey | |||
| } | |||
| return &delivery | |||
| } | |||
| /* | |||
| Ack delegates an acknowledgement through the Acknowledger interface that the | |||
| client or server has finished work on a delivery. | |||
| All deliveries in AMQP must be acknowledged. If you called Channel.Consume | |||
| with autoAck true then the server will be automatically ack each message and | |||
| this method should not be called. Otherwise, you must call Delivery.Ack after | |||
| you have successfully processed this delivery. | |||
| When multiple is true, this delivery and all prior unacknowledged deliveries | |||
| on the same channel will be acknowledged. This is useful for batch processing | |||
| of deliveries. | |||
| An error will indicate that the acknowledge could not be delivered to the | |||
| channel it was sent from. | |||
| Either Delivery.Ack, Delivery.Reject or Delivery.Nack must be called for every | |||
| delivery that is not automatically acknowledged. | |||
| */ | |||
| func (d Delivery) Ack(multiple bool) error { | |||
| if d.Acknowledger == nil { | |||
| return errDeliveryNotInitialized | |||
| } | |||
| return d.Acknowledger.Ack(d.DeliveryTag, multiple) | |||
| } | |||
| /* | |||
| Reject delegates a negatively acknowledgement through the Acknowledger interface. | |||
| When requeue is true, queue this message to be delivered to a consumer on a | |||
| different channel. When requeue is false or the server is unable to queue this | |||
| message, it will be dropped. | |||
| If you are batch processing deliveries, and your server supports it, prefer | |||
| Delivery.Nack. | |||
| Either Delivery.Ack, Delivery.Reject or Delivery.Nack must be called for every | |||
| delivery that is not automatically acknowledged. | |||
| */ | |||
| func (d Delivery) Reject(requeue bool) error { | |||
| if d.Acknowledger == nil { | |||
| return errDeliveryNotInitialized | |||
| } | |||
| return d.Acknowledger.Reject(d.DeliveryTag, requeue) | |||
| } | |||
| /* | |||
| Nack negatively acknowledge the delivery of message(s) identified by the | |||
| delivery tag from either the client or server. | |||
| When multiple is true, nack messages up to and including delivered messages up | |||
| until the delivery tag delivered on the same channel. | |||
| When requeue is true, request the server to deliver this message to a different | |||
| consumer. If it is not possible or requeue is false, the message will be | |||
| dropped or delivered to a server configured dead-letter queue. | |||
| This method must not be used to select or requeue messages the client wishes | |||
| not to handle, rather it is to inform the server that the client is incapable | |||
| of handling this message at this time. | |||
| Either Delivery.Ack, Delivery.Reject or Delivery.Nack must be called for every | |||
| delivery that is not automatically acknowledged. | |||
| */ | |||
| func (d Delivery) Nack(multiple, requeue bool) error { | |||
| if d.Acknowledger == nil { | |||
| return errDeliveryNotInitialized | |||
| } | |||
| return d.Acknowledger.Nack(d.DeliveryTag, multiple, requeue) | |||
| } | |||
| @ -1,108 +0,0 @@ | |||
| // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. | |||
| // Use of this source code is governed by a BSD-style | |||
| // license that can be found in the LICENSE file. | |||
| // Source code and contact info at http://github.com/streadway/amqp | |||
| /* | |||
| Package amqp is an AMQP 0.9.1 client with RabbitMQ extensions | |||
| Understand the AMQP 0.9.1 messaging model by reviewing these links first. Much | |||
| of the terminoLoggery in this library directly relates to AMQP concepts. | |||
| Resources | |||
| http://www.rabbitmq.com/tutorials/amqp-concepts.html | |||
| http://www.rabbitmq.com/getstarted.html | |||
| http://www.rabbitmq.com/amqp-0-9-1-reference.html | |||
| Design | |||
| Most other broker clients publish to queues, but in AMQP, clients publish | |||
| Exchanges instead. AMQP is programmable, meaning that both the producers and | |||
| consumers agree on the configuration of the broker, instead of requiring an | |||
| operator or system configuration that declares the logical topology in the | |||
| broker. The routing between producers and consumer queues is via Bindings. | |||
| These bindings form the logical topology of the broker. | |||
| In this library, a message sent from publisher is called a "Publishing" and a | |||
| message received to a consumer is called a "Delivery". The fields of | |||
| Publishings and Deliveries are close but not exact mappings to the underlying | |||
| wire format to maintain stronger types. Many other libraries will combine | |||
| message properties with message headers. In this library, the message well | |||
| known properties are strongly typed fields on the Publishings and Deliveries, | |||
| whereas the user defined headers are in the Headers field. | |||
| The method naming closely matches the protocol's method name with positional | |||
| parameters mapping to named protocol message fields. The motivation here is to | |||
| present a comprehensive view over all possible interactions with the server. | |||
| Generally, methods that map to protocol methods of the "basic" class will be | |||
| elided in this interface, and "select" methods of various channel mode selectors | |||
| will be elided for example Channel.Confirm and Channel.Tx. | |||
| The library is intentionally designed to be synchronous, where responses for | |||
| each protocol message are required to be received in an RPC manner. Some | |||
| methods have a noWait parameter like Channel.QueueDeclare, and some methods are | |||
| asynchronous like Channel.Publish. The error values should still be checked for | |||
| these methods as they will indicate IO failures like when the underlying | |||
| connection closes. | |||
| Asynchronous Events | |||
| Clients of this library may be interested in receiving some of the protocol | |||
| messages other than Deliveries like basic.ack methods while a channel is in | |||
| confirm mode. | |||
| The Notify* methods with Connection and Channel receivers model the pattern of | |||
| asynchronous events like closes due to exceptions, or messages that are sent out | |||
| of band from an RPC call like basic.ack or basic.flow. | |||
| Any asynchronous events, including Deliveries and Publishings must always have | |||
| a receiver until the corresponding chans are closed. Without asynchronous | |||
| receivers, the sychronous methods will block. | |||
| Use Case | |||
| It's important as a client to an AMQP topology to ensure the state of the | |||
| broker matches your expectations. For both publish and consume use cases, | |||
| make sure you declare the queues, exchanges and bindings you expect to exist | |||
| prior to calling Channel.Publish or Channel.Consume. | |||
| // Connections start with amqp.Dial() typically from a command line argument | |||
| // or environment variable. | |||
| connection, err := amqp.Dial(os.Getenv("AMQP_URL")) | |||
| // To cleanly shutdown by flushing kernel buffers, make sure to close and | |||
| // wait for the response. | |||
| defer connection.Close() | |||
| // Most operations happen on a channel. If any error is returned on a | |||
| // channel, the channel will no longer be valid, throw it away and try with | |||
| // a different channel. If you use many channels, it's useful for the | |||
| // server to | |||
| channel, err := connection.Channel() | |||
| // Declare your topology here, if it doesn't exist, it will be created, if | |||
| // it existed already and is not what you expect, then that's considered an | |||
| // error. | |||
| // Use your connection on this topology with either Publish or Consume, or | |||
| // inspect your queues with QueueInspect. It's unwise to mix Publish and | |||
| // Consume to let TCP do its job well. | |||
| SSL/TLS - Secure connections | |||
| When Dial encounters an amqps:// scheme, it will use the zero value of a | |||
| tls.Config. This will only perform server certificate and host verification. | |||
| Use DialTLS when you wish to provide a client certificate (recommended), | |||
| include a private certificate authority's certificate in the cert chain for | |||
| server validity, or run insecure by not verifying the server certificate dial | |||
| your own connection. DialTLS will use the provided tls.Config when it | |||
| encounters an amqps:// scheme and will dial a plain connection when it | |||
| encounters an amqp:// scheme. | |||
| SSL/TLS in RabbitMQ is documented here: http://www.rabbitmq.com/ssl.html | |||
| */ | |||
| package amqp | |||
| @ -1,17 +0,0 @@ | |||
| // +build gofuzz | |||
| package amqp | |||
| import "bytes" | |||
| func Fuzz(data []byte) int { | |||
| r := reader{bytes.NewReader(data)} | |||
| frame, err := r.ReadFrame() | |||
| if err != nil { | |||
| if frame != nil { | |||
| panic("frame is not nil") | |||
| } | |||
| return 0 | |||
| } | |||
| return 1 | |||
| } | |||
| @ -1,2 +0,0 @@ | |||
| #!/bin/sh | |||
| go run spec/gen.go < spec/amqp0-9-1.stripped.extended.xml | gofmt > spec091.go | |||
| @ -1,3 +0,0 @@ | |||
| module github.com/streadway/amqp | |||
| go 1.10 | |||
| @ -1,67 +0,0 @@ | |||
| #!/bin/sh | |||
| LATEST_STABLE_SUPPORTED_GO_VERSION="1.11" | |||
| main() { | |||
| if local_go_version_is_latest_stable | |||
| then | |||
| run_gofmt | |||
| run_golint | |||
| run_govet | |||
| fi | |||
| run_unit_tests | |||
| } | |||
| local_go_version_is_latest_stable() { | |||
| go version | grep -q $LATEST_STABLE_SUPPORTED_GO_VERSION | |||
| } | |||
| log_error() { | |||
| echo "$*" 1>&2 | |||
| } | |||
| run_gofmt() { | |||
| GOFMT_FILES=$(gofmt -l .) | |||
| if [ -n "$GOFMT_FILES" ] | |||
| then | |||
| log_error "gofmt failed for the following files: | |||
| $GOFMT_FILES | |||
| please run 'gofmt -w .' on your changes before committing." | |||
| exit 1 | |||
| fi | |||
| } | |||
| run_golint() { | |||
| GOLINT_ERRORS=$(golint ./... | grep -v "Id should be") | |||
| if [ -n "$GOLINT_ERRORS" ] | |||
| then | |||
| log_error "golint failed for the following reasons: | |||
| $GOLINT_ERRORS | |||
| please run 'golint ./...' on your changes before committing." | |||
| exit 1 | |||
| fi | |||
| } | |||
| run_govet() { | |||
| GOVET_ERRORS=$(go tool vet ./*.go 2>&1) | |||
| if [ -n "$GOVET_ERRORS" ] | |||
| then | |||
| log_error "go vet failed for the following reasons: | |||
| $GOVET_ERRORS | |||
| please run 'go tool vet ./*.go' on your changes before committing." | |||
| exit 1 | |||
| fi | |||
| } | |||
| run_unit_tests() { | |||
| if [ -z "$NOTEST" ] | |||
| then | |||
| log_error 'Running short tests...' | |||
| env AMQP_URL= go test -short | |||
| fi | |||
| } | |||
| main | |||
| @ -1,456 +0,0 @@ | |||
| // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. | |||
| // Use of this source code is governed by a BSD-style | |||
| // license that can be found in the LICENSE file. | |||
| // Source code and contact info at http://github.com/streadway/amqp | |||
| package amqp | |||
| import ( | |||
| "bytes" | |||
| "encoding/binary" | |||
| "errors" | |||
| "io" | |||
| "time" | |||
| ) | |||
| /* | |||
| Reads a frame from an input stream and returns an interface that can be cast into | |||
| one of the following: | |||
| methodFrame | |||
| PropertiesFrame | |||
| bodyFrame | |||
| heartbeatFrame | |||
| 2.3.5 frame Details | |||
| All frames consist of a header (7 octets), a payload of arbitrary size, and a | |||
| 'frame-end' octet that detects malformed frames: | |||
| 0 1 3 7 size+7 size+8 | |||
| +------+---------+-------------+ +------------+ +-----------+ | |||
| | type | channel | size | | payload | | frame-end | | |||
| +------+---------+-------------+ +------------+ +-----------+ | |||
| octet short long size octets octet | |||
| To read a frame, we: | |||
| 1. Read the header and check the frame type and channel. | |||
| 2. Depending on the frame type, we read the payload and process it. | |||
| 3. Read the frame end octet. | |||
| In realistic implementations where performance is a concern, we would use | |||
| “read-ahead buffering” or | |||
| “gathering reads” to avoid doing three separate system calls to read a frame. | |||
| */ | |||
| func (r *reader) ReadFrame() (frame frame, err error) { | |||
| var scratch [7]byte | |||
| if _, err = io.ReadFull(r.r, scratch[:7]); err != nil { | |||
| return | |||
| } | |||
| typ := uint8(scratch[0]) | |||
| channel := binary.BigEndian.Uint16(scratch[1:3]) | |||
| size := binary.BigEndian.Uint32(scratch[3:7]) | |||
| switch typ { | |||
| case frameMethod: | |||
| if frame, err = r.parseMethodFrame(channel, size); err != nil { | |||
| return | |||
| } | |||
| case frameHeader: | |||
| if frame, err = r.parseHeaderFrame(channel, size); err != nil { | |||
| return | |||
| } | |||
| case frameBody: | |||
| if frame, err = r.parseBodyFrame(channel, size); err != nil { | |||
| return nil, err | |||
| } | |||
| case frameHeartbeat: | |||
| if frame, err = r.parseHeartbeatFrame(channel, size); err != nil { | |||
| return | |||
| } | |||
| default: | |||
| return nil, ErrFrame | |||
| } | |||
| if _, err = io.ReadFull(r.r, scratch[:1]); err != nil { | |||
| return nil, err | |||
| } | |||
| if scratch[0] != frameEnd { | |||
| return nil, ErrFrame | |||
| } | |||
| return | |||
| } | |||
| func readShortstr(r io.Reader) (v string, err error) { | |||
| var length uint8 | |||
| if err = binary.Read(r, binary.BigEndian, &length); err != nil { | |||
| return | |||
| } | |||
| bytes := make([]byte, length) | |||
| if _, err = io.ReadFull(r, bytes); err != nil { | |||
| return | |||
| } | |||
| return string(bytes), nil | |||
| } | |||
| func readLongstr(r io.Reader) (v string, err error) { | |||
| var length uint32 | |||
| if err = binary.Read(r, binary.BigEndian, &length); err != nil { | |||
| return | |||
| } | |||
| // slices can't be longer than max int32 value | |||
| if length > (^uint32(0) >> 1) { | |||
| return | |||
| } | |||
| bytes := make([]byte, length) | |||
| if _, err = io.ReadFull(r, bytes); err != nil { | |||
| return | |||
| } | |||
| return string(bytes), nil | |||
| } | |||
| func readDecimal(r io.Reader) (v Decimal, err error) { | |||
| if err = binary.Read(r, binary.BigEndian, &v.Scale); err != nil { | |||
| return | |||
| } | |||
| if err = binary.Read(r, binary.BigEndian, &v.Value); err != nil { | |||
| return | |||
| } | |||
| return | |||
| } | |||
| func readFloat32(r io.Reader) (v float32, err error) { | |||
| if err = binary.Read(r, binary.BigEndian, &v); err != nil { | |||
| return | |||
| } | |||
| return | |||
| } | |||
| func readFloat64(r io.Reader) (v float64, err error) { | |||
| if err = binary.Read(r, binary.BigEndian, &v); err != nil { | |||
| return | |||
| } | |||
| return | |||
| } | |||
| func readTimestamp(r io.Reader) (v time.Time, err error) { | |||
| var sec int64 | |||
| if err = binary.Read(r, binary.BigEndian, &sec); err != nil { | |||
| return | |||
| } | |||
| return time.Unix(sec, 0), nil | |||
| } | |||
| /* | |||
| 'A': []interface{} | |||
| 'D': Decimal | |||
| 'F': Table | |||
| 'I': int32 | |||
| 'S': string | |||
| 'T': time.Time | |||
| 'V': nil | |||
| 'b': byte | |||
| 'd': float64 | |||
| 'f': float32 | |||
| 'l': int64 | |||
| 's': int16 | |||
| 't': bool | |||
| 'x': []byte | |||
| */ | |||
| func readField(r io.Reader) (v interface{}, err error) { | |||
| var typ byte | |||
| if err = binary.Read(r, binary.BigEndian, &typ); err != nil { | |||
| return | |||
| } | |||
| switch typ { | |||
| case 't': | |||
| var value uint8 | |||
| if err = binary.Read(r, binary.BigEndian, &value); err != nil { | |||
| return | |||
| } | |||
| return (value != 0), nil | |||
| case 'b': | |||
| var value [1]byte | |||
| if _, err = io.ReadFull(r, value[0:1]); err != nil { | |||
| return | |||
| } | |||
| return value[0], nil | |||
| case 's': | |||
| var value int16 | |||
| if err = binary.Read(r, binary.BigEndian, &value); err != nil { | |||
| return | |||
| } | |||
| return value, nil | |||
| case 'I': | |||
| var value int32 | |||
| if err = binary.Read(r, binary.BigEndian, &value); err != nil { | |||
| return | |||
| } | |||
| return value, nil | |||
| case 'l': | |||
| var value int64 | |||
| if err = binary.Read(r, binary.BigEndian, &value); err != nil { | |||
| return | |||
| } | |||
| return value, nil | |||
| case 'f': | |||
| var value float32 | |||
| if err = binary.Read(r, binary.BigEndian, &value); err != nil { | |||
| return | |||
| } | |||
| return value, nil | |||
| case 'd': | |||
| var value float64 | |||
| if err = binary.Read(r, binary.BigEndian, &value); err != nil { | |||
| return | |||
| } | |||
| return value, nil | |||
| case 'D': | |||
| return readDecimal(r) | |||
| case 'S': | |||
| return readLongstr(r) | |||
| case 'A': | |||
| return readArray(r) | |||
| case 'T': | |||
| return readTimestamp(r) | |||
| case 'F': | |||
| return readTable(r) | |||
| case 'x': | |||
| var len int32 | |||
| if err = binary.Read(r, binary.BigEndian, &len); err != nil { | |||
| return nil, err | |||
| } | |||
| value := make([]byte, len) | |||
| if _, err = io.ReadFull(r, value); err != nil { | |||
| return nil, err | |||
| } | |||
| return value, err | |||
| case 'V': | |||
| return nil, nil | |||
| } | |||
| return nil, ErrSyntax | |||
| } | |||
| /* | |||
| Field tables are long strings that contain packed name-value pairs. The | |||
| name-value pairs are encoded as short string defining the name, and octet | |||
| defining the values type and then the value itself. The valid field types for | |||
| tables are an extension of the native integer, bit, string, and timestamp | |||
| types, and are shown in the grammar. Multi-octet integer fields are always | |||
| held in network byte order. | |||
| */ | |||
| func readTable(r io.Reader) (table Table, err error) { | |||
| var nested bytes.Buffer | |||
| var str string | |||
| if str, err = readLongstr(r); err != nil { | |||
| return | |||
| } | |||
| nested.Write([]byte(str)) | |||
| table = make(Table) | |||
| for nested.Len() > 0 { | |||
| var key string | |||
| var value interface{} | |||
| if key, err = readShortstr(&nested); err != nil { | |||
| return | |||
| } | |||
| if value, err = readField(&nested); err != nil { | |||
| return | |||
| } | |||
| table[key] = value | |||
| } | |||
| return | |||
| } | |||
| func readArray(r io.Reader) ([]interface{}, error) { | |||
| var ( | |||
| size uint32 | |||
| err error | |||
| ) | |||
| if err = binary.Read(r, binary.BigEndian, &size); err != nil { | |||
| return nil, err | |||
| } | |||
| var ( | |||
| lim = &io.LimitedReader{R: r, N: int64(size)} | |||
| arr = []interface{}{} | |||
| field interface{} | |||
| ) | |||
| for { | |||
| if field, err = readField(lim); err != nil { | |||
| if err == io.EOF { | |||
| break | |||
| } | |||
| return nil, err | |||
| } | |||
| arr = append(arr, field) | |||
| } | |||
| return arr, nil | |||
| } | |||
| // Checks if this bit mask matches the flags bitset | |||
| func hasProperty(mask uint16, prop int) bool { | |||
| return int(mask)&prop > 0 | |||
| } | |||
| func (r *reader) parseHeaderFrame(channel uint16, size uint32) (frame frame, err error) { | |||
| hf := &headerFrame{ | |||
| ChannelId: channel, | |||
| } | |||
| if err = binary.Read(r.r, binary.BigEndian, &hf.ClassId); err != nil { | |||
| return | |||
| } | |||
| if err = binary.Read(r.r, binary.BigEndian, &hf.weight); err != nil { | |||
| return | |||
| } | |||
| if err = binary.Read(r.r, binary.BigEndian, &hf.Size); err != nil { | |||
| return | |||
| } | |||
| var flags uint16 | |||
| if err = binary.Read(r.r, binary.BigEndian, &flags); err != nil { | |||
| return | |||
| } | |||
| if hasProperty(flags, flagContentType) { | |||
| if hf.Properties.ContentType, err = readShortstr(r.r); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(flags, flagContentEncoding) { | |||
| if hf.Properties.ContentEncoding, err = readShortstr(r.r); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(flags, flagHeaders) { | |||
| if hf.Properties.Headers, err = readTable(r.r); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(flags, flagDeliveryMode) { | |||
| if err = binary.Read(r.r, binary.BigEndian, &hf.Properties.DeliveryMode); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(flags, flagPriority) { | |||
| if err = binary.Read(r.r, binary.BigEndian, &hf.Properties.Priority); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(flags, flagCorrelationId) { | |||
| if hf.Properties.CorrelationId, err = readShortstr(r.r); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(flags, flagReplyTo) { | |||
| if hf.Properties.ReplyTo, err = readShortstr(r.r); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(flags, flagExpiration) { | |||
| if hf.Properties.Expiration, err = readShortstr(r.r); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(flags, flagMessageId) { | |||
| if hf.Properties.MessageId, err = readShortstr(r.r); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(flags, flagTimestamp) { | |||
| if hf.Properties.Timestamp, err = readTimestamp(r.r); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(flags, flagType) { | |||
| if hf.Properties.Type, err = readShortstr(r.r); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(flags, flagUserId) { | |||
| if hf.Properties.UserId, err = readShortstr(r.r); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(flags, flagAppId) { | |||
| if hf.Properties.AppId, err = readShortstr(r.r); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(flags, flagReserved1) { | |||
| if hf.Properties.reserved1, err = readShortstr(r.r); err != nil { | |||
| return | |||
| } | |||
| } | |||
| return hf, nil | |||
| } | |||
| func (r *reader) parseBodyFrame(channel uint16, size uint32) (frame frame, err error) { | |||
| bf := &bodyFrame{ | |||
| ChannelId: channel, | |||
| Body: make([]byte, size), | |||
| } | |||
| if _, err = io.ReadFull(r.r, bf.Body); err != nil { | |||
| return nil, err | |||
| } | |||
| return bf, nil | |||
| } | |||
| var errHeartbeatPayload = errors.New("Heartbeats should not have a payload") | |||
| func (r *reader) parseHeartbeatFrame(channel uint16, size uint32) (frame frame, err error) { | |||
| hf := &heartbeatFrame{ | |||
| ChannelId: channel, | |||
| } | |||
| if size > 0 { | |||
| return nil, errHeartbeatPayload | |||
| } | |||
| return hf, nil | |||
| } | |||
| @ -1,64 +0,0 @@ | |||
| // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. | |||
| // Use of this source code is governed by a BSD-style | |||
| // license that can be found in the LICENSE file. | |||
| // Source code and contact info at http://github.com/streadway/amqp | |||
| package amqp | |||
| import ( | |||
| "time" | |||
| ) | |||
| // Return captures a flattened struct of fields returned by the server when a | |||
| // Publishing is unable to be delivered either due to the `mandatory` flag set | |||
| // and no route found, or `immediate` flag set and no free consumer. | |||
| type Return struct { | |||
| ReplyCode uint16 // reason | |||
| ReplyText string // description | |||
| Exchange string // basic.publish exchange | |||
| RoutingKey string // basic.publish routing key | |||
| // Properties | |||
| ContentType string // MIME content type | |||
| ContentEncoding string // MIME content encoding | |||
| Headers Table // Application or header exchange table | |||
| DeliveryMode uint8 // queue implementation use - non-persistent (1) or persistent (2) | |||
| Priority uint8 // queue implementation use - 0 to 9 | |||
| CorrelationId string // application use - correlation identifier | |||
| ReplyTo string // application use - address to to reply to (ex: RPC) | |||
| Expiration string // implementation use - message expiration spec | |||
| MessageId string // application use - message identifier | |||
| Timestamp time.Time // application use - message timestamp | |||
| Type string // application use - message type name | |||
| UserId string // application use - creating user id | |||
| AppId string // application use - creating application | |||
| Body []byte | |||
| } | |||
| func newReturn(msg basicReturn) *Return { | |||
| props, body := msg.getContent() | |||
| return &Return{ | |||
| ReplyCode: msg.ReplyCode, | |||
| ReplyText: msg.ReplyText, | |||
| Exchange: msg.Exchange, | |||
| RoutingKey: msg.RoutingKey, | |||
| Headers: props.Headers, | |||
| ContentType: props.ContentType, | |||
| ContentEncoding: props.ContentEncoding, | |||
| DeliveryMode: props.DeliveryMode, | |||
| Priority: props.Priority, | |||
| CorrelationId: props.CorrelationId, | |||
| ReplyTo: props.ReplyTo, | |||
| Expiration: props.Expiration, | |||
| MessageId: props.MessageId, | |||
| Timestamp: props.Timestamp, | |||
| Type: props.Type, | |||
| UserId: props.UserId, | |||
| AppId: props.AppId, | |||
| Body: body, | |||
| } | |||
| } | |||
| @ -1,428 +0,0 @@ | |||
| // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. | |||
| // Use of this source code is governed by a BSD-style | |||
| // license that can be found in the LICENSE file. | |||
| // Source code and contact info at http://github.com/streadway/amqp | |||
| package amqp | |||
| import ( | |||
| "fmt" | |||
| "io" | |||
| "time" | |||
| ) | |||
| // Constants for standard AMQP 0-9-1 exchange types. | |||
| const ( | |||
| ExchangeDirect = "direct" | |||
| ExchangeFanout = "fanout" | |||
| ExchangeTopic = "topic" | |||
| ExchangeHeaders = "headers" | |||
| ) | |||
| var ( | |||
| // ErrClosed is returned when the channel or connection is not open | |||
| ErrClosed = &Error{Code: ChannelError, Reason: "channel/connection is not open"} | |||
| // ErrChannelMax is returned when Connection.Channel has been called enough | |||
| // times that all channel IDs have been exhausted in the client or the | |||
| // server. | |||
| ErrChannelMax = &Error{Code: ChannelError, Reason: "channel id space exhausted"} | |||
| // ErrSASL is returned from Dial when the authentication mechanism could not | |||
| // be negoated. | |||
| ErrSASL = &Error{Code: AccessRefused, Reason: "SASL could not negotiate a shared mechanism"} | |||
| // ErrCredentials is returned when the authenticated client is not authorized | |||
| // to any vhost. | |||
| ErrCredentials = &Error{Code: AccessRefused, Reason: "username or password not allowed"} | |||
| // ErrVhost is returned when the authenticated user is not permitted to | |||
| // access the requested Vhost. | |||
| ErrVhost = &Error{Code: AccessRefused, Reason: "no access to this vhost"} | |||
| // ErrSyntax is hard protocol error, indicating an unsupported protocol, | |||
| // implementation or encoding. | |||
| ErrSyntax = &Error{Code: SyntaxError, Reason: "invalid field or value inside of a frame"} | |||
| // ErrFrame is returned when the protocol frame cannot be read from the | |||
| // server, indicating an unsupported protocol or unsupported frame type. | |||
| ErrFrame = &Error{Code: FrameError, Reason: "frame could not be parsed"} | |||
| // ErrCommandInvalid is returned when the server sends an unexpected response | |||
| // to this requested message type. This indicates a bug in this client. | |||
| ErrCommandInvalid = &Error{Code: CommandInvalid, Reason: "unexpected command received"} | |||
| // ErrUnexpectedFrame is returned when something other than a method or | |||
| // heartbeat frame is delivered to the Connection, indicating a bug in the | |||
| // client. | |||
| ErrUnexpectedFrame = &Error{Code: UnexpectedFrame, Reason: "unexpected frame received"} | |||
| // ErrFieldType is returned when writing a message containing a Go type unsupported by AMQP. | |||
| ErrFieldType = &Error{Code: SyntaxError, Reason: "unsupported table field type"} | |||
| ) | |||
| // Error captures the code and reason a channel or connection has been closed | |||
| // by the server. | |||
| type Error struct { | |||
| Code int // constant code from the specification | |||
| Reason string // description of the error | |||
| Server bool // true when initiated from the server, false when from this library | |||
| Recover bool // true when this error can be recovered by retrying later or with different parameters | |||
| } | |||
| func newError(code uint16, text string) *Error { | |||
| return &Error{ | |||
| Code: int(code), | |||
| Reason: text, | |||
| Recover: isSoftExceptionCode(int(code)), | |||
| Server: true, | |||
| } | |||
| } | |||
| func (e Error) Error() string { | |||
| return fmt.Sprintf("Exception (%d) Reason: %q", e.Code, e.Reason) | |||
| } | |||
| // Used by header frames to capture routing and header information | |||
| type properties struct { | |||
| ContentType string // MIME content type | |||
| ContentEncoding string // MIME content encoding | |||
| Headers Table // Application or header exchange table | |||
| DeliveryMode uint8 // queue implementation use - Transient (1) or Persistent (2) | |||
| Priority uint8 // queue implementation use - 0 to 9 | |||
| CorrelationId string // application use - correlation identifier | |||
| ReplyTo string // application use - address to to reply to (ex: RPC) | |||
| Expiration string // implementation use - message expiration spec | |||
| MessageId string // application use - message identifier | |||
| Timestamp time.Time // application use - message timestamp | |||
| Type string // application use - message type name | |||
| UserId string // application use - creating user id | |||
| AppId string // application use - creating application | |||
| reserved1 string // was cluster-id - process for buffer consumption | |||
| } | |||
| // DeliveryMode. Transient means higher throughput but messages will not be | |||
| // restored on broker restart. The delivery mode of publishings is unrelated | |||
| // to the durability of the queues they reside on. Transient messages will | |||
| // not be restored to durable queues, persistent messages will be restored to | |||
| // durable queues and lost on non-durable queues during server restart. | |||
| // | |||
| // This remains typed as uint8 to match Publishing.DeliveryMode. Other | |||
| // delivery modes specific to custom queue implementations are not enumerated | |||
| // here. | |||
| const ( | |||
| Transient uint8 = 1 | |||
| Persistent uint8 = 2 | |||
| ) | |||
| // The property flags are an array of bits that indicate the presence or | |||
| // absence of each property value in sequence. The bits are ordered from most | |||
| // high to low - bit 15 indicates the first property. | |||
| const ( | |||
| flagContentType = 0x8000 | |||
| flagContentEncoding = 0x4000 | |||
| flagHeaders = 0x2000 | |||
| flagDeliveryMode = 0x1000 | |||
| flagPriority = 0x0800 | |||
| flagCorrelationId = 0x0400 | |||
| flagReplyTo = 0x0200 | |||
| flagExpiration = 0x0100 | |||
| flagMessageId = 0x0080 | |||
| flagTimestamp = 0x0040 | |||
| flagType = 0x0020 | |||
| flagUserId = 0x0010 | |||
| flagAppId = 0x0008 | |||
| flagReserved1 = 0x0004 | |||
| ) | |||
| // Queue captures the current server state of the queue on the server returned | |||
| // from Channel.QueueDeclare or Channel.QueueInspect. | |||
| type Queue struct { | |||
| Name string // server confirmed or generated name | |||
| Messages int // count of messages not awaiting acknowledgment | |||
| Consumers int // number of consumers receiving deliveries | |||
| } | |||
| // Publishing captures the client message sent to the server. The fields | |||
| // outside of the Headers table included in this struct mirror the underlying | |||
| // fields in the content frame. They use native types for convenience and | |||
| // efficiency. | |||
| type Publishing struct { | |||
| // Application or exchange specific fields, | |||
| // the headers exchange will inspect this field. | |||
| Headers Table | |||
| // Properties | |||
| ContentType string // MIME content type | |||
| ContentEncoding string // MIME content encoding | |||
| DeliveryMode uint8 // Transient (0 or 1) or Persistent (2) | |||
| Priority uint8 // 0 to 9 | |||
| CorrelationId string // correlation identifier | |||
| ReplyTo string // address to to reply to (ex: RPC) | |||
| Expiration string // message expiration spec | |||
| MessageId string // message identifier | |||
| Timestamp time.Time // message timestamp | |||
| Type string // message type name | |||
| UserId string // creating user id - ex: "guest" | |||
| AppId string // creating application id | |||
| // The application specific payload of the message | |||
| Body []byte | |||
| } | |||
| // Blocking notifies the server's TCP flow control of the Connection. When a | |||
| // server hits a memory or disk alarm it will block all connections until the | |||
| // resources are reclaimed. Use NotifyBlock on the Connection to receive these | |||
| // events. | |||
| type Blocking struct { | |||
| Active bool // TCP pushback active/inactive on server | |||
| Reason string // Server reason for activation | |||
| } | |||
| // Confirmation notifies the acknowledgment or negative acknowledgement of a | |||
| // publishing identified by its delivery tag. Use NotifyPublish on the Channel | |||
| // to consume these events. | |||
| type Confirmation struct { | |||
| DeliveryTag uint64 // A 1 based counter of publishings from when the channel was put in Confirm mode | |||
| Ack bool // True when the server successfully received the publishing | |||
| } | |||
| // Decimal matches the AMQP decimal type. Scale is the number of decimal | |||
| // digits Scale == 2, Value == 12345, Decimal == 123.45 | |||
| type Decimal struct { | |||
| Scale uint8 | |||
| Value int32 | |||
| } | |||
| // Table stores user supplied fields of the following types: | |||
| // | |||
| // bool | |||
| // byte | |||
| // float32 | |||
| // float64 | |||
| // int | |||
| // int16 | |||
| // int32 | |||
| // int64 | |||
| // nil | |||
| // string | |||
| // time.Time | |||
| // amqp.Decimal | |||
| // amqp.Table | |||
| // []byte | |||
| // []interface{} - containing above types | |||
| // | |||
| // Functions taking a table will immediately fail when the table contains a | |||
| // value of an unsupported type. | |||
| // | |||
| // The caller must be specific in which precision of integer it wishes to | |||
| // encode. | |||
| // | |||
| // Use a type assertion when reading values from a table for type conversion. | |||
| // | |||
| // RabbitMQ expects int32 for integer values. | |||
| // | |||
| type Table map[string]interface{} | |||
| func validateField(f interface{}) error { | |||
| switch fv := f.(type) { | |||
| case nil, bool, byte, int, int16, int32, int64, float32, float64, string, []byte, Decimal, time.Time: | |||
| return nil | |||
| case []interface{}: | |||
| for _, v := range fv { | |||
| if err := validateField(v); err != nil { | |||
| return fmt.Errorf("in array %s", err) | |||
| } | |||
| } | |||
| return nil | |||
| case Table: | |||
| for k, v := range fv { | |||
| if err := validateField(v); err != nil { | |||
| return fmt.Errorf("table field %q %s", k, err) | |||
| } | |||
| } | |||
| return nil | |||
| } | |||
| return fmt.Errorf("value %T not supported", f) | |||
| } | |||
| // Validate returns and error if any Go types in the table are incompatible with AMQP types. | |||
| func (t Table) Validate() error { | |||
| return validateField(t) | |||
| } | |||
| // Heap interface for maintaining delivery tags | |||
| type tagSet []uint64 | |||
| func (set tagSet) Len() int { return len(set) } | |||
| func (set tagSet) Less(i, j int) bool { return (set)[i] < (set)[j] } | |||
| func (set tagSet) Swap(i, j int) { (set)[i], (set)[j] = (set)[j], (set)[i] } | |||
| func (set *tagSet) Push(tag interface{}) { *set = append(*set, tag.(uint64)) } | |||
| func (set *tagSet) Pop() interface{} { | |||
| val := (*set)[len(*set)-1] | |||
| *set = (*set)[:len(*set)-1] | |||
| return val | |||
| } | |||
| type message interface { | |||
| id() (uint16, uint16) | |||
| wait() bool | |||
| read(io.Reader) error | |||
| write(io.Writer) error | |||
| } | |||
| type messageWithContent interface { | |||
| message | |||
| getContent() (properties, []byte) | |||
| setContent(properties, []byte) | |||
| } | |||
| /* | |||
| The base interface implemented as: | |||
| 2.3.5 frame Details | |||
| All frames consist of a header (7 octets), a payload of arbitrary size, and a 'frame-end' octet that detects | |||
| malformed frames: | |||
| 0 1 3 7 size+7 size+8 | |||
| +------+---------+-------------+ +------------+ +-----------+ | |||
| | type | channel | size | | payload | | frame-end | | |||
| +------+---------+-------------+ +------------+ +-----------+ | |||
| octet short long size octets octet | |||
| To read a frame, we: | |||
| 1. Read the header and check the frame type and channel. | |||
| 2. Depending on the frame type, we read the payload and process it. | |||
| 3. Read the frame end octet. | |||
| In realistic implementations where performance is a concern, we would use | |||
| “read-ahead buffering” or “gathering reads” to avoid doing three separate | |||
| system calls to read a frame. | |||
| */ | |||
| type frame interface { | |||
| write(io.Writer) error | |||
| channel() uint16 | |||
| } | |||
| type reader struct { | |||
| r io.Reader | |||
| } | |||
| type writer struct { | |||
| w io.Writer | |||
| } | |||
| // Implements the frame interface for Connection RPC | |||
| type protocolHeader struct{} | |||
| func (protocolHeader) write(w io.Writer) error { | |||
| _, err := w.Write([]byte{'A', 'M', 'Q', 'P', 0, 0, 9, 1}) | |||
| return err | |||
| } | |||
| func (protocolHeader) channel() uint16 { | |||
| panic("only valid as initial handshake") | |||
| } | |||
| /* | |||
| Method frames carry the high-level protocol commands (which we call "methods"). | |||
| One method frame carries one command. The method frame payload has this format: | |||
| 0 2 4 | |||
| +----------+-----------+-------------- - - | |||
| | class-id | method-id | arguments... | |||
| +----------+-----------+-------------- - - | |||
| short short ... | |||
| To process a method frame, we: | |||
| 1. Read the method frame payload. | |||
| 2. Unpack it into a structure. A given method always has the same structure, | |||
| so we can unpack the method rapidly. 3. Check that the method is allowed in | |||
| the current context. | |||
| 4. Check that the method arguments are valid. | |||
| 5. Execute the method. | |||
| Method frame bodies are constructed as a list of AMQP data fields (bits, | |||
| integers, strings and string tables). The marshalling code is trivially | |||
| generated directly from the protocol specifications, and can be very rapid. | |||
| */ | |||
| type methodFrame struct { | |||
| ChannelId uint16 | |||
| ClassId uint16 | |||
| MethodId uint16 | |||
| Method message | |||
| } | |||
| func (f *methodFrame) channel() uint16 { return f.ChannelId } | |||
| /* | |||
| Heartbeating is a technique designed to undo one of TCP/IP's features, namely | |||
| its ability to recover from a broken physical connection by closing only after | |||
| a quite long time-out. In some scenarios we need to know very rapidly if a | |||
| peer is disconnected or not responding for other reasons (e.g. it is looping). | |||
| Since heartbeating can be done at a low level, we implement this as a special | |||
| type of frame that peers exchange at the transport level, rather than as a | |||
| class method. | |||
| */ | |||
| type heartbeatFrame struct { | |||
| ChannelId uint16 | |||
| } | |||
| func (f *heartbeatFrame) channel() uint16 { return f.ChannelId } | |||
| /* | |||
| Certain methods (such as Basic.Publish, Basic.Deliver, etc.) are formally | |||
| defined as carrying content. When a peer sends such a method frame, it always | |||
| follows it with a content header and zero or more content body frames. | |||
| A content header frame has this format: | |||
| 0 2 4 12 14 | |||
| +----------+--------+-----------+----------------+------------- - - | |||
| | class-id | weight | body size | property flags | property list... | |||
| +----------+--------+-----------+----------------+------------- - - | |||
| short short long long short remainder... | |||
| We place content body in distinct frames (rather than including it in the | |||
| method) so that AMQP may support "zero copy" techniques in which content is | |||
| never marshalled or encoded. We place the content properties in their own | |||
| frame so that recipients can selectively discard contents they do not want to | |||
| process | |||
| */ | |||
| type headerFrame struct { | |||
| ChannelId uint16 | |||
| ClassId uint16 | |||
| weight uint16 | |||
| Size uint64 | |||
| Properties properties | |||
| } | |||
| func (f *headerFrame) channel() uint16 { return f.ChannelId } | |||
| /* | |||
| Content is the application data we carry from client-to-client via the AMQP | |||
| server. Content is, roughly speaking, a set of properties plus a binary data | |||
| part. The set of allowed properties are defined by the Basic class, and these | |||
| form the "content header frame". The data can be any size, and MAY be broken | |||
| into several (or many) chunks, each forming a "content body frame". | |||
| Looking at the frames for a specific channel, as they pass on the wire, we | |||
| might see something like this: | |||
| [method] | |||
| [method] [header] [body] [body] | |||
| [method] | |||
| ... | |||
| */ | |||
| type bodyFrame struct { | |||
| ChannelId uint16 | |||
| Body []byte | |||
| } | |||
| func (f *bodyFrame) channel() uint16 { return f.ChannelId } | |||
| @ -1,176 +0,0 @@ | |||
| // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. | |||
| // Use of this source code is governed by a BSD-style | |||
| // license that can be found in the LICENSE file. | |||
| // Source code and contact info at http://github.com/streadway/amqp | |||
| package amqp | |||
| import ( | |||
| "errors" | |||
| "net" | |||
| "net/url" | |||
| "strconv" | |||
| "strings" | |||
| ) | |||
| var errURIScheme = errors.New("AMQP scheme must be either 'amqp://' or 'amqps://'") | |||
| var errURIWhitespace = errors.New("URI must not contain whitespace") | |||
| var schemePorts = map[string]int{ | |||
| "amqp": 5672, | |||
| "amqps": 5671, | |||
| } | |||
| var defaultURI = URI{ | |||
| Scheme: "amqp", | |||
| Host: "localhost", | |||
| Port: 5672, | |||
| Username: "guest", | |||
| Password: "guest", | |||
| Vhost: "/", | |||
| } | |||
| // URI represents a parsed AMQP URI string. | |||
| type URI struct { | |||
| Scheme string | |||
| Host string | |||
| Port int | |||
| Username string | |||
| Password string | |||
| Vhost string | |||
| } | |||
| // ParseURI attempts to parse the given AMQP URI according to the spec. | |||
| // See http://www.rabbitmq.com/uri-spec.html. | |||
| // | |||
| // Default values for the fields are: | |||
| // | |||
| // Scheme: amqp | |||
| // Host: localhost | |||
| // Port: 5672 | |||
| // Username: guest | |||
| // Password: guest | |||
| // Vhost: / | |||
| // | |||
| func ParseURI(uri string) (URI, error) { | |||
| builder := defaultURI | |||
| if strings.Contains(uri, " ") == true { | |||
| return builder, errURIWhitespace | |||
| } | |||
| u, err := url.Parse(uri) | |||
| if err != nil { | |||
| return builder, err | |||
| } | |||
| defaultPort, okScheme := schemePorts[u.Scheme] | |||
| if okScheme { | |||
| builder.Scheme = u.Scheme | |||
| } else { | |||
| return builder, errURIScheme | |||
| } | |||
| host := u.Hostname() | |||
| port := u.Port() | |||
| if host != "" { | |||
| builder.Host = host | |||
| } | |||
| if port != "" { | |||
| port32, err := strconv.ParseInt(port, 10, 32) | |||
| if err != nil { | |||
| return builder, err | |||
| } | |||
| builder.Port = int(port32) | |||
| } else { | |||
| builder.Port = defaultPort | |||
| } | |||
| if u.User != nil { | |||
| builder.Username = u.User.Username() | |||
| if password, ok := u.User.Password(); ok { | |||
| builder.Password = password | |||
| } | |||
| } | |||
| if u.Path != "" { | |||
| if strings.HasPrefix(u.Path, "/") { | |||
| if u.Host == "" && strings.HasPrefix(u.Path, "///") { | |||
| // net/url doesn't handle local context authorities and leaves that up | |||
| // to the scheme handler. In our case, we translate amqp:/// into the | |||
| // default host and whatever the vhost should be | |||
| if len(u.Path) > 3 { | |||
| builder.Vhost = u.Path[3:] | |||
| } | |||
| } else if len(u.Path) > 1 { | |||
| builder.Vhost = u.Path[1:] | |||
| } | |||
| } else { | |||
| builder.Vhost = u.Path | |||
| } | |||
| } | |||
| return builder, nil | |||
| } | |||
| // PlainAuth returns a PlainAuth structure based on the parsed URI's | |||
| // Username and Password fields. | |||
| func (uri URI) PlainAuth() *PlainAuth { | |||
| return &PlainAuth{ | |||
| Username: uri.Username, | |||
| Password: uri.Password, | |||
| } | |||
| } | |||
| // AMQPlainAuth returns a PlainAuth structure based on the parsed URI's | |||
| // Username and Password fields. | |||
| func (uri URI) AMQPlainAuth() *AMQPlainAuth { | |||
| return &AMQPlainAuth{ | |||
| Username: uri.Username, | |||
| Password: uri.Password, | |||
| } | |||
| } | |||
| func (uri URI) String() string { | |||
| authority, err := url.Parse("") | |||
| if err != nil { | |||
| return err.Error() | |||
| } | |||
| authority.Scheme = uri.Scheme | |||
| if uri.Username != defaultURI.Username || uri.Password != defaultURI.Password { | |||
| authority.User = url.User(uri.Username) | |||
| if uri.Password != defaultURI.Password { | |||
| authority.User = url.UserPassword(uri.Username, uri.Password) | |||
| } | |||
| } | |||
| authority.Host = net.JoinHostPort(uri.Host, strconv.Itoa(uri.Port)) | |||
| if defaultPort, found := schemePorts[uri.Scheme]; !found || defaultPort != uri.Port { | |||
| authority.Host = net.JoinHostPort(uri.Host, strconv.Itoa(uri.Port)) | |||
| } else { | |||
| // JoinHostPort() automatically add brackets to the host if it's | |||
| // an IPv6 address. | |||
| // | |||
| // If not port is specified, JoinHostPort() return an IP address in the | |||
| // form of "[::1]:", so we use TrimSuffix() to remove the extra ":". | |||
| authority.Host = strings.TrimSuffix(net.JoinHostPort(uri.Host, ""), ":") | |||
| } | |||
| if uri.Vhost != defaultURI.Vhost { | |||
| // Make sure net/url does not double escape, e.g. | |||
| // "%2F" does not become "%252F". | |||
| authority.Path = uri.Vhost | |||
| authority.RawPath = url.QueryEscape(uri.Vhost) | |||
| } else { | |||
| authority.Path = "/" | |||
| } | |||
| return authority.String() | |||
| } | |||
| @ -1,416 +0,0 @@ | |||
| // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. | |||
| // Use of this source code is governed by a BSD-style | |||
| // license that can be found in the LICENSE file. | |||
| // Source code and contact info at http://github.com/streadway/amqp | |||
| package amqp | |||
| import ( | |||
| "bufio" | |||
| "bytes" | |||
| "encoding/binary" | |||
| "errors" | |||
| "io" | |||
| "math" | |||
| "time" | |||
| ) | |||
| func (w *writer) WriteFrame(frame frame) (err error) { | |||
| if err = frame.write(w.w); err != nil { | |||
| return | |||
| } | |||
| if buf, ok := w.w.(*bufio.Writer); ok { | |||
| err = buf.Flush() | |||
| } | |||
| return | |||
| } | |||
| func (f *methodFrame) write(w io.Writer) (err error) { | |||
| var payload bytes.Buffer | |||
| if f.Method == nil { | |||
| return errors.New("malformed frame: missing method") | |||
| } | |||
| class, method := f.Method.id() | |||
| if err = binary.Write(&payload, binary.BigEndian, class); err != nil { | |||
| return | |||
| } | |||
| if err = binary.Write(&payload, binary.BigEndian, method); err != nil { | |||
| return | |||
| } | |||
| if err = f.Method.write(&payload); err != nil { | |||
| return | |||
| } | |||
| return writeFrame(w, frameMethod, f.ChannelId, payload.Bytes()) | |||
| } | |||
| // Heartbeat | |||
| // | |||
| // Payload is empty | |||
| func (f *heartbeatFrame) write(w io.Writer) (err error) { | |||
| return writeFrame(w, frameHeartbeat, f.ChannelId, []byte{}) | |||
| } | |||
| // CONTENT HEADER | |||
| // 0 2 4 12 14 | |||
| // +----------+--------+-----------+----------------+------------- - - | |||
| // | class-id | weight | body size | property flags | property list... | |||
| // +----------+--------+-----------+----------------+------------- - - | |||
| // short short long long short remainder... | |||
| // | |||
| func (f *headerFrame) write(w io.Writer) (err error) { | |||
| var payload bytes.Buffer | |||
| var zeroTime time.Time | |||
| if err = binary.Write(&payload, binary.BigEndian, f.ClassId); err != nil { | |||
| return | |||
| } | |||
| if err = binary.Write(&payload, binary.BigEndian, f.weight); err != nil { | |||
| return | |||
| } | |||
| if err = binary.Write(&payload, binary.BigEndian, f.Size); err != nil { | |||
| return | |||
| } | |||
| // First pass will build the mask to be serialized, second pass will serialize | |||
| // each of the fields that appear in the mask. | |||
| var mask uint16 | |||
| if len(f.Properties.ContentType) > 0 { | |||
| mask = mask | flagContentType | |||
| } | |||
| if len(f.Properties.ContentEncoding) > 0 { | |||
| mask = mask | flagContentEncoding | |||
| } | |||
| if f.Properties.Headers != nil && len(f.Properties.Headers) > 0 { | |||
| mask = mask | flagHeaders | |||
| } | |||
| if f.Properties.DeliveryMode > 0 { | |||
| mask = mask | flagDeliveryMode | |||
| } | |||
| if f.Properties.Priority > 0 { | |||
| mask = mask | flagPriority | |||
| } | |||
| if len(f.Properties.CorrelationId) > 0 { | |||
| mask = mask | flagCorrelationId | |||
| } | |||
| if len(f.Properties.ReplyTo) > 0 { | |||
| mask = mask | flagReplyTo | |||
| } | |||
| if len(f.Properties.Expiration) > 0 { | |||
| mask = mask | flagExpiration | |||
| } | |||
| if len(f.Properties.MessageId) > 0 { | |||
| mask = mask | flagMessageId | |||
| } | |||
| if f.Properties.Timestamp != zeroTime { | |||
| mask = mask | flagTimestamp | |||
| } | |||
| if len(f.Properties.Type) > 0 { | |||
| mask = mask | flagType | |||
| } | |||
| if len(f.Properties.UserId) > 0 { | |||
| mask = mask | flagUserId | |||
| } | |||
| if len(f.Properties.AppId) > 0 { | |||
| mask = mask | flagAppId | |||
| } | |||
| if err = binary.Write(&payload, binary.BigEndian, mask); err != nil { | |||
| return | |||
| } | |||
| if hasProperty(mask, flagContentType) { | |||
| if err = writeShortstr(&payload, f.Properties.ContentType); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(mask, flagContentEncoding) { | |||
| if err = writeShortstr(&payload, f.Properties.ContentEncoding); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(mask, flagHeaders) { | |||
| if err = writeTable(&payload, f.Properties.Headers); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(mask, flagDeliveryMode) { | |||
| if err = binary.Write(&payload, binary.BigEndian, f.Properties.DeliveryMode); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(mask, flagPriority) { | |||
| if err = binary.Write(&payload, binary.BigEndian, f.Properties.Priority); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(mask, flagCorrelationId) { | |||
| if err = writeShortstr(&payload, f.Properties.CorrelationId); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(mask, flagReplyTo) { | |||
| if err = writeShortstr(&payload, f.Properties.ReplyTo); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(mask, flagExpiration) { | |||
| if err = writeShortstr(&payload, f.Properties.Expiration); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(mask, flagMessageId) { | |||
| if err = writeShortstr(&payload, f.Properties.MessageId); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(mask, flagTimestamp) { | |||
| if err = binary.Write(&payload, binary.BigEndian, uint64(f.Properties.Timestamp.Unix())); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(mask, flagType) { | |||
| if err = writeShortstr(&payload, f.Properties.Type); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(mask, flagUserId) { | |||
| if err = writeShortstr(&payload, f.Properties.UserId); err != nil { | |||
| return | |||
| } | |||
| } | |||
| if hasProperty(mask, flagAppId) { | |||
| if err = writeShortstr(&payload, f.Properties.AppId); err != nil { | |||
| return | |||
| } | |||
| } | |||
| return writeFrame(w, frameHeader, f.ChannelId, payload.Bytes()) | |||
| } | |||
| // Body | |||
| // | |||
| // Payload is one byterange from the full body who's size is declared in the | |||
| // Header frame | |||
| func (f *bodyFrame) write(w io.Writer) (err error) { | |||
| return writeFrame(w, frameBody, f.ChannelId, f.Body) | |||
| } | |||
| func writeFrame(w io.Writer, typ uint8, channel uint16, payload []byte) (err error) { | |||
| end := []byte{frameEnd} | |||
| size := uint(len(payload)) | |||
| _, err = w.Write([]byte{ | |||
| byte(typ), | |||
| byte((channel & 0xff00) >> 8), | |||
| byte((channel & 0x00ff) >> 0), | |||
| byte((size & 0xff000000) >> 24), | |||
| byte((size & 0x00ff0000) >> 16), | |||
| byte((size & 0x0000ff00) >> 8), | |||
| byte((size & 0x000000ff) >> 0), | |||
| }) | |||
| if err != nil { | |||
| return | |||
| } | |||
| if _, err = w.Write(payload); err != nil { | |||
| return | |||
| } | |||
| if _, err = w.Write(end); err != nil { | |||
| return | |||
| } | |||
| return | |||
| } | |||
| func writeShortstr(w io.Writer, s string) (err error) { | |||
| b := []byte(s) | |||
| var length = uint8(len(b)) | |||
| if err = binary.Write(w, binary.BigEndian, length); err != nil { | |||
| return | |||
| } | |||
| if _, err = w.Write(b[:length]); err != nil { | |||
| return | |||
| } | |||
| return | |||
| } | |||
| func writeLongstr(w io.Writer, s string) (err error) { | |||
| b := []byte(s) | |||
| var length = uint32(len(b)) | |||
| if err = binary.Write(w, binary.BigEndian, length); err != nil { | |||
| return | |||
| } | |||
| if _, err = w.Write(b[:length]); err != nil { | |||
| return | |||
| } | |||
| return | |||
| } | |||
| /* | |||
| 'A': []interface{} | |||
| 'D': Decimal | |||
| 'F': Table | |||
| 'I': int32 | |||
| 'S': string | |||
| 'T': time.Time | |||
| 'V': nil | |||
| 'b': byte | |||
| 'd': float64 | |||
| 'f': float32 | |||
| 'l': int64 | |||
| 's': int16 | |||
| 't': bool | |||
| 'x': []byte | |||
| */ | |||
| func writeField(w io.Writer, value interface{}) (err error) { | |||
| var buf [9]byte | |||
| var enc []byte | |||
| switch v := value.(type) { | |||
| case bool: | |||
| buf[0] = 't' | |||
| if v { | |||
| buf[1] = byte(1) | |||
| } else { | |||
| buf[1] = byte(0) | |||
| } | |||
| enc = buf[:2] | |||
| case byte: | |||
| buf[0] = 'b' | |||
| buf[1] = byte(v) | |||
| enc = buf[:2] | |||
| case int16: | |||
| buf[0] = 's' | |||
| binary.BigEndian.PutUint16(buf[1:3], uint16(v)) | |||
| enc = buf[:3] | |||
| case int: | |||
| buf[0] = 'I' | |||
| binary.BigEndian.PutUint32(buf[1:5], uint32(v)) | |||
| enc = buf[:5] | |||
| case int32: | |||
| buf[0] = 'I' | |||
| binary.BigEndian.PutUint32(buf[1:5], uint32(v)) | |||
| enc = buf[:5] | |||
| case int64: | |||
| buf[0] = 'l' | |||
| binary.BigEndian.PutUint64(buf[1:9], uint64(v)) | |||
| enc = buf[:9] | |||
| case float32: | |||
| buf[0] = 'f' | |||
| binary.BigEndian.PutUint32(buf[1:5], math.Float32bits(v)) | |||
| enc = buf[:5] | |||
| case float64: | |||
| buf[0] = 'd' | |||
| binary.BigEndian.PutUint64(buf[1:9], math.Float64bits(v)) | |||
| enc = buf[:9] | |||
| case Decimal: | |||
| buf[0] = 'D' | |||
| buf[1] = byte(v.Scale) | |||
| binary.BigEndian.PutUint32(buf[2:6], uint32(v.Value)) | |||
| enc = buf[:6] | |||
| case string: | |||
| buf[0] = 'S' | |||
| binary.BigEndian.PutUint32(buf[1:5], uint32(len(v))) | |||
| enc = append(buf[:5], []byte(v)...) | |||
| case []interface{}: // field-array | |||
| buf[0] = 'A' | |||
| sec := new(bytes.Buffer) | |||
| for _, val := range v { | |||
| if err = writeField(sec, val); err != nil { | |||
| return | |||
| } | |||
| } | |||
| binary.BigEndian.PutUint32(buf[1:5], uint32(sec.Len())) | |||
| if _, err = w.Write(buf[:5]); err != nil { | |||
| return | |||
| } | |||
| if _, err = w.Write(sec.Bytes()); err != nil { | |||
| return | |||
| } | |||
| return | |||
| case time.Time: | |||
| buf[0] = 'T' | |||
| binary.BigEndian.PutUint64(buf[1:9], uint64(v.Unix())) | |||
| enc = buf[:9] | |||
| case Table: | |||
| if _, err = w.Write([]byte{'F'}); err != nil { | |||
| return | |||
| } | |||
| return writeTable(w, v) | |||
| case []byte: | |||
| buf[0] = 'x' | |||
| binary.BigEndian.PutUint32(buf[1:5], uint32(len(v))) | |||
| if _, err = w.Write(buf[0:5]); err != nil { | |||
| return | |||
| } | |||
| if _, err = w.Write(v); err != nil { | |||
| return | |||
| } | |||
| return | |||
| case nil: | |||
| buf[0] = 'V' | |||
| enc = buf[:1] | |||
| default: | |||
| return ErrFieldType | |||
| } | |||
| _, err = w.Write(enc) | |||
| return | |||
| } | |||
| func writeTable(w io.Writer, table Table) (err error) { | |||
| var buf bytes.Buffer | |||
| for key, val := range table { | |||
| if err = writeShortstr(&buf, key); err != nil { | |||
| return | |||
| } | |||
| if err = writeField(&buf, val); err != nil { | |||
| return | |||
| } | |||
| } | |||
| return writeLongstr(w, string(buf.Bytes())) | |||
| } | |||
| @ -1,3 +1,3 @@ | |||
| # github.com/streadway/amqp v1.0.0 | |||
| # github.com/rabbitmq/amqp091-go v0.0.0-20210823000215-c428a6150891 | |||
| ## explicit | |||
| github.com/streadway/amqp | |||
| github.com/rabbitmq/amqp091-go | |||