| @ -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 | ## explicit | ||||
| github.com/streadway/amqp | |||||
| github.com/rabbitmq/amqp091-go | |||||