diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 8ffab7faa..f6a196913 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -166,6 +166,10 @@ "ImportPath": "github.com/samuel/go-zookeeper/zk", "Rev": "5bb5cfc093ad18a28148c578f8632cfdb4d802e4" }, + { + "ImportPath": "github.com/streadway/amqp", + "Rev": "f4879ba28fffbb576743b03622a9ff20461826b2" + }, { "ImportPath": "github.com/stretchr/objx", "Rev": "cbeaeb16a013161a98496fad62933b1d21786672" diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/.gitignore b/Godeps/_workspace/src/github.com/streadway/amqp/.gitignore new file mode 100644 index 000000000..58b0e8f32 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/.gitignore @@ -0,0 +1,3 @@ +spec/spec +examples/simple-consumer/simple-consumer +examples/simple-producer/simple-producer diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/.travis.yml b/Godeps/_workspace/src/github.com/streadway/amqp/.travis.yml new file mode 100644 index 000000000..f1c275a2c --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/.travis.yml @@ -0,0 +1,14 @@ +language: go + +go: + - 1.1 + - 1.4 + - 1.5 + +services: + - rabbitmq + +env: + - AMQP_URL=amqp://guest:guest@127.0.0.1:5672/ GOMAXPROCS=2 + +script: go test -v -tags integration ./... diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/LICENSE b/Godeps/_workspace/src/github.com/streadway/amqp/LICENSE new file mode 100644 index 000000000..243c0ce7c --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2012, 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. diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/README.md b/Godeps/_workspace/src/github.com/streadway/amqp/README.md new file mode 100644 index 000000000..c4291fb68 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/README.md @@ -0,0 +1,81 @@ +# AMQP + +AMQP 0.9.1 client with RabbitMQ extensions in Go. + +# Status + +*Beta* + +[![Build Status](https://secure.travis-ci.org/streadway/amqp.png)](http://travis-ci.org/streadway/amqp) + +API changes unlikely and will be discussed on [Github +issues](https://github.com/streadway/amqp/issues) along with any bugs or +enhancements. + +# Goals + +Provide an 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 pacakge. + +# 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](https://github.com/soundtrackyourbrand/gaeamqp) + * RabbitMQ examples in Go: [https://github.com/rabbitmq/rabbitmq-tutorials/tree/master/go](https://github.com/rabbitmq/rabbitmq-tutorials/tree/master/go) + +# License + +BSD 2 clause - see LICENSE for more details. + + diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/allocator.go b/Godeps/_workspace/src/github.com/streadway/amqp/allocator.go new file mode 100644 index 000000000..928418826 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/allocator.go @@ -0,0 +1,106 @@ +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 preceeding 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 +// succesfully 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) +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/allocator_test.go b/Godeps/_workspace/src/github.com/streadway/amqp/allocator_test.go new file mode 100644 index 000000000..2d6fd5dba --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/allocator_test.go @@ -0,0 +1,90 @@ +package amqp + +import ( + "math/rand" + "testing" +) + +func TestAllocatorFirstShouldBeTheLow(t *testing.T) { + n, ok := newAllocator(1, 2).next() + if !ok { + t.Fatalf("expected to allocate between 1 and 2") + } + + if want, got := 1, n; want != got { + t.Fatalf("expected to first allocation to be 1") + } +} + +func TestAllocatorShouldBeBoundByHigh(t *testing.T) { + a := newAllocator(1, 2) + + if n, ok := a.next(); n != 1 || !ok { + t.Fatalf("expected to allocate between 1 and 2, got %d, %v", n, ok) + } + if n, ok := a.next(); n != 2 || !ok { + t.Fatalf("expected to allocate between 1 and 2, got %d, %v", n, ok) + } + if _, ok := a.next(); ok { + t.Fatalf("expected not to allocate outside of 1 and 2") + } +} + +func TestAllocatorStringShouldIncludeAllocatedRanges(t *testing.T) { + a := newAllocator(1, 10) + a.reserve(1) + a.reserve(2) + a.reserve(3) + a.reserve(5) + a.reserve(6) + a.reserve(8) + a.reserve(10) + + if want, got := "allocator[1..10] 1..3 5..6 8 10", a.String(); want != got { + t.Fatalf("expected String of %q, got %q", want, got) + } +} + +func TestAllocatorShouldReuseReleased(t *testing.T) { + a := newAllocator(1, 2) + + first, _ := a.next() + if want, got := 1, first; want != got { + t.Fatalf("expected allocation to be %d, got: %d", want, got) + } + + second, _ := a.next() + if want, got := 2, second; want != got { + t.Fatalf("expected allocation to be %d, got: %d", want, got) + } + + a.release(first) + + third, _ := a.next() + if want, got := first, third; want != got { + t.Fatalf("expected third allocation to be %d, got: %d", want, got) + } + + _, ok := a.next() + if want, got := false, ok; want != got { + t.Fatalf("expected fourth allocation to saturate the pool") + } +} + +func TestAllocatorReleasesKeepUpWithAllocationsForAllSizes(t *testing.T) { + const runs = 5 + const max = 13 + + for lim := 1; lim < 2<= lim { // fills the allocator + a.release(int(rand.Int63n(int64(lim)))) + } + if _, ok := a.next(); !ok { + t.Fatalf("expected %d runs of random release of size %d not to fail on allocation %d", runs, lim, i) + } + } + } +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/auth.go b/Godeps/_workspace/src/github.com/streadway/amqp/auth.go new file mode 100644 index 000000000..bff7d7948 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/auth.go @@ -0,0 +1,44 @@ +// 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 +} + +func (me *PlainAuth) Mechanism() string { + return "PLAIN" +} + +func (me *PlainAuth) Response() string { + return fmt.Sprintf("\000%s\000%s", me.Username, me.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 +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/certs.sh b/Godeps/_workspace/src/github.com/streadway/amqp/certs.sh new file mode 100644 index 000000000..834f42242 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/certs.sh @@ -0,0 +1,159 @@ +#!/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 diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/channel.go b/Godeps/_workspace/src/github.com/streadway/amqp/channel.go new file mode 100644 index 000000000..9cf93b4d2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/channel.go @@ -0,0 +1,1548 @@ +// 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 ( + "reflect" + "sync" +) + +// 0 1 3 7 size+7 size+8 +// +------+---------+-------------+ +------------+ +-----------+ +// | type | channel | size | | payload | | frame-end | +// +------+---------+-------------+ +------------+ +-----------+ +// octet short long size octets octet +const frameHeaderSize = 1 + 2 + 4 + 1 + +/* +Channel represents an AMQP channel. Used as a context for valid message +exchange. Errors on methods with this Channel as a receiver means this channel +should be discarded and a new channel established. + +*/ +type Channel struct { + destructor sync.Once + sendM sync.Mutex // sequence channel frames + m sync.Mutex // struct field mutex + + connection *Connection + + rpc chan message + consumers *consumers + + id uint16 + + // true when we will never notify again + noNotify bool + + // Channel and Connection exceptions will be broadcast on these listeners. + closes []chan *Error + + // Listeners for active=true flow control. When true is sent to a listener, + // publishing should pause until false is sent to listeners. + flows []chan bool + + // Listeners for returned publishings for unroutable messages on mandatory + // publishings or undeliverable messages on immediate publishings. + returns []chan Return + + // Listeners for when the server notifies the client that + // a consumer has been cancelled. + cancels []chan string + + // Allocated when in confirm mode in order to track publish counter and order confirms + confirms *confirms + confirming bool + + // Selects on any errors from shutdown during RPC + errors chan *Error + + // State machine that manages frame order, must only be mutated by the connection + recv func(*Channel, frame) error + + // State that manages the send behavior after before and after shutdown, must + // only be mutated in shutdown() + send func(*Channel, message) error + + // Current state for frame re-assembly, only mutated from recv + message messageWithContent + header *headerFrame + body []byte +} + +// Constructs a new channel with the given framing rules +func newChannel(c *Connection, id uint16) *Channel { + return &Channel{ + connection: c, + id: id, + rpc: make(chan message), + consumers: makeConsumers(), + confirms: newConfirms(), + recv: (*Channel).recvMethod, + send: (*Channel).sendOpen, + errors: make(chan *Error, 1), + } +} + +// shutdown is called by Connection after the channel has been removed from the +// connection registry. +func (me *Channel) shutdown(e *Error) { + me.destructor.Do(func() { + me.m.Lock() + defer me.m.Unlock() + + // Broadcast abnormal shutdown + if e != nil { + for _, c := range me.closes { + c <- e + } + } + + me.send = (*Channel).sendClosed + + // Notify RPC if we're selecting + if e != nil { + me.errors <- e + } + + me.consumers.closeAll() + + for _, c := range me.closes { + close(c) + } + + for _, c := range me.flows { + close(c) + } + + for _, c := range me.returns { + close(c) + } + + for _, c := range me.cancels { + close(c) + } + + if me.confirms != nil { + me.confirms.Close() + } + + me.noNotify = true + }) +} + +func (me *Channel) open() error { + return me.call(&channelOpen{}, &channelOpenOk{}) +} + +// Performs a request/response call for when the message is not NoWait and is +// specified as Synchronous. +func (me *Channel) call(req message, res ...message) error { + if err := me.send(me, req); err != nil { + return err + } + + if req.wait() { + select { + case e := <-me.errors: + return e + + case msg := <-me.rpc: + if msg != nil { + 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 + } else { + // RPC channel has been closed without an error, likely due to a hard + // error on the Connection. This indicates we have already been + // shutdown and if were waiting, will have returned from the errors chan. + return ErrClosed + } + } + } + + return nil +} + +func (me *Channel) sendClosed(msg message) (err error) { + me.sendM.Lock() + defer me.sendM.Unlock() + + // After a 'channel.close' is sent or received the only valid response is + // channel.close-ok + if _, ok := msg.(*channelCloseOk); ok { + return me.connection.send(&methodFrame{ + ChannelId: me.id, + Method: msg, + }) + } + + return ErrClosed +} + +func (me *Channel) sendOpen(msg message) (err error) { + me.sendM.Lock() + defer me.sendM.Unlock() + + if content, ok := msg.(messageWithContent); ok { + props, body := content.getContent() + class, _ := content.id() + size := me.connection.Config.FrameSize - frameHeaderSize + + if err = me.connection.send(&methodFrame{ + ChannelId: me.id, + Method: content, + }); err != nil { + return + } + + if err = me.connection.send(&headerFrame{ + ChannelId: me.id, + ClassId: class, + Size: uint64(len(body)), + Properties: props, + }); err != nil { + return + } + + for i, j := 0, size; i < len(body); i, j = j, j+size { + if j > len(body) { + j = len(body) + } + + if err = me.connection.send(&bodyFrame{ + ChannelId: me.id, + Body: body[i:j], + }); err != nil { + return + } + } + } else { + err = me.connection.send(&methodFrame{ + ChannelId: me.id, + Method: msg, + }) + } + + return +} + +// Eventually called via the state machine from the connection's reader +// goroutine, so assumes serialized access. +func (me *Channel) dispatch(msg message) { + switch m := msg.(type) { + case *channelClose: + me.connection.closeChannel(me, newError(m.ReplyCode, m.ReplyText)) + me.send(me, &channelCloseOk{}) + + case *channelFlow: + for _, c := range me.flows { + c <- m.Active + } + me.send(me, &channelFlowOk{Active: m.Active}) + + case *basicCancel: + for _, c := range me.cancels { + c <- m.ConsumerTag + } + me.send(me, &basicCancelOk{ConsumerTag: m.ConsumerTag}) + + case *basicReturn: + ret := newReturn(*m) + for _, c := range me.returns { + c <- *ret + } + + case *basicAck: + if me.confirming { + if m.Multiple { + me.confirms.Multiple(Confirmation{m.DeliveryTag, true}) + } else { + me.confirms.One(Confirmation{m.DeliveryTag, true}) + } + } + + case *basicNack: + if me.confirming { + if m.Multiple { + me.confirms.Multiple(Confirmation{m.DeliveryTag, false}) + } else { + me.confirms.One(Confirmation{m.DeliveryTag, false}) + } + } + + case *basicDeliver: + me.consumers.send(m.ConsumerTag, newDelivery(me, m)) + // TODO log failed consumer and close channel, this can happen when + // deliveries are in flight and a no-wait cancel has happened + + default: + me.rpc <- msg + } +} + +func (me *Channel) transition(f func(*Channel, frame) error) error { + me.recv = f + return nil +} + +func (me *Channel) recvMethod(f frame) error { + switch frame := f.(type) { + case *methodFrame: + if msg, ok := frame.Method.(messageWithContent); ok { + me.body = make([]byte, 0) + me.message = msg + return me.transition((*Channel).recvHeader) + } + + me.dispatch(frame.Method) // termination state + return me.transition((*Channel).recvMethod) + + case *headerFrame: + // drop + return me.transition((*Channel).recvMethod) + + case *bodyFrame: + // drop + return me.transition((*Channel).recvMethod) + + default: + panic("unexpected frame type") + } + + panic("unreachable") +} + +func (me *Channel) recvHeader(f frame) error { + switch frame := f.(type) { + case *methodFrame: + // interrupt content and handle method + return me.recvMethod(f) + + case *headerFrame: + // start collecting if we expect body frames + me.header = frame + + if frame.Size == 0 { + me.message.setContent(me.header.Properties, me.body) + me.dispatch(me.message) // termination state + return me.transition((*Channel).recvMethod) + } else { + return me.transition((*Channel).recvContent) + } + + case *bodyFrame: + // drop and reset + return me.transition((*Channel).recvMethod) + + default: + panic("unexpected frame type") + } + + panic("unreachable") +} + +// state after method + header and before the length +// defined by the header has been reached +func (me *Channel) recvContent(f frame) error { + switch frame := f.(type) { + case *methodFrame: + // interrupt content and handle method + return me.recvMethod(f) + + case *headerFrame: + // drop and reset + return me.transition((*Channel).recvMethod) + + case *bodyFrame: + me.body = append(me.body, frame.Body...) + + if uint64(len(me.body)) >= me.header.Size { + me.message.setContent(me.header.Properties, me.body) + me.dispatch(me.message) // termination state + return me.transition((*Channel).recvMethod) + } + + return me.transition((*Channel).recvContent) + + default: + panic("unexpected frame type") + } + + panic("unreachable") +} + +/* +Close initiate a clean channel closure by sending a close message with the error +code set to '200'. + +It is safe to call this method multiple times. + +*/ +func (me *Channel) Close() error { + defer me.connection.closeChannel(me, nil) + return me.call( + &channelClose{ReplyCode: replySuccess}, + &channelCloseOk{}, + ) +} + +/* +NotifyClose registers a listener for when the server sends a channel or +connection exception in the form of a Connection.Close or Channel.Close method. +Connection exceptions will be broadcast to all open channels and all channels +will be closed, where channel exceptions will only be broadcast to listeners to +this channel. + +The chan provided will be closed when the Channel is closed and on a +graceful close, no error will be sent. + +*/ +func (me *Channel) NotifyClose(c chan *Error) chan *Error { + me.m.Lock() + defer me.m.Unlock() + + if me.noNotify { + close(c) + } else { + me.closes = append(me.closes, c) + } + + return c +} + +/* +NotifyFlow registers a listener for basic.flow methods sent by the server. +When `true` is sent on one of the listener channels, all publishers should +pause until a `false` is sent. + +The server may ask the producer to pause or restart the flow of Publishings +sent by on a channel. This is a simple flow-control mechanism that a server can +use to avoid overflowing its queues or otherwise finding itself receiving more +messages than it can process. Note that this method is not intended for window +control. It does not affect contents returned by basic.get-ok methods. + +When a new channel is opened, it is active (flow is active). Some +applications assume that channels are inactive until started. To emulate +this behavior a client MAY open the channel, then pause it. + +Publishers should respond to a flow messages as rapidly as possible and the +server may disconnect over producing channels that do not respect these +messages. + +basic.flow-ok methods will always be returned to the server regardless of +the number of listeners there are. + +To control the flow of deliveries from the server. Use the Channel.Flow() +method instead. + +Note: RabbitMQ will rather use TCP pushback on the network connection instead +of sending basic.flow. This means that if a single channel is producing too +much on the same connection, all channels using that connection will suffer, +including acknowledgments from deliveries. Use different Connections if you +desire to interleave consumers and producers in the same process to avoid your +basic.ack messages from getting rate limited with your basic.publish messages. + +*/ +func (me *Channel) NotifyFlow(c chan bool) chan bool { + me.m.Lock() + defer me.m.Unlock() + + if me.noNotify { + close(c) + } else { + me.flows = append(me.flows, c) + } + + return c +} + +/* +NotifyReturn registers a listener for basic.return methods. These can be sent +from the server when a publish is undeliverable either from the mandatory or +immediate flags. + +A return struct has a copy of the Publishing along with some error +information about why the publishing failed. + +*/ +func (me *Channel) NotifyReturn(c chan Return) chan Return { + me.m.Lock() + defer me.m.Unlock() + + if me.noNotify { + close(c) + } else { + me.returns = append(me.returns, c) + } + + return c +} + +/* +NotifyCancel registers a listener for basic.cancel methods. These can be sent +from the server when a queue is deleted or when consuming from a mirrored queue +where the master has just failed (and was moved to another node) + +The subscription tag is returned to the listener. + +*/ +func (me *Channel) NotifyCancel(c chan string) chan string { + me.m.Lock() + defer me.m.Unlock() + + if me.noNotify { + close(c) + } else { + me.cancels = append(me.cancels, c) + } + + return c +} + +/* +NotifyConfirm calls NotifyPublish and starts a goroutines sending +ordered Ack and Nack DeliveryTag to the respective channels. + +For strict ordering, use NotifyPublish instead. +*/ +func (me *Channel) NotifyConfirm(ack, nack chan uint64) (chan uint64, chan uint64) { + confirms := me.NotifyPublish(make(chan Confirmation, len(ack)+len(nack))) + + go func() { + for c := range confirms { + if c.Ack { + ack <- c.DeliveryTag + } else { + nack <- c.DeliveryTag + } + } + close(ack) + if nack != ack { + close(nack) + } + }() + + return ack, nack +} + +/* +NotifyPublish registers a listener for reliable publishing. Receives from this +chan for every publish after Channel.Confirm will be in order starting with +DeliveryTag 1. + +There will be one and only one Confimration Publishing starting with the +delviery tag of 1 and progressing sequentially until the total number of +Publishings have been seen by the server. + +Acknowledgments will be received in the order of delivery from the +NotifyPublish channels even if the server acknowledges them out of order. + +The listener chan will be closed when the Channel is closed. + +The capacity of the chan Confirmation must be at least as large as the +number of outstanding publishings. Not having enough buffered chans will +create a deadlock if you attempt to perform other operations on the Connection +or Channel while confirms are in-flight. + +It's advisable to wait for all Confirmations to arrive before calling +Channel.Close() or Connection.Close(). + +*/ +func (me *Channel) NotifyPublish(confirm chan Confirmation) chan Confirmation { + me.m.Lock() + defer me.m.Unlock() + + if me.noNotify { + close(confirm) + } else { + me.confirms.Listen(confirm) + } + + return confirm + +} + +/* +Qos controls how many messages or how many bytes the server will try to keep on +the network for consumers before receiving delivery acks. The intent of Qos is +to make sure the network buffers stay full between the server and client. + +With a prefetch count greater than zero, the server will deliver that many +messages to consumers before acknowledgments are received. The server ignores +this option when consumers are started with noAck because no acknowledgments +are expected or sent. + +With a prefetch size greater than zero, the server will try to keep at least +that many bytes of deliveries flushed to the network before receiving +acknowledgments from the consumers. This option is ignored when consumers are +started with noAck. + +When global is true, these Qos settings apply to all existing and future +consumers on all channels on the same connection. When false, the Channel.Qos +settings will apply to all existing and future consumers on this channel. +RabbitMQ does not implement the global flag. + +To get round-robin behavior between consumers consuming from the same queue on +different connections, set the prefetch count to 1, and the next available +message on the server will be delivered to the next available consumer. + +If your consumer work time is reasonably consistent and not much greater +than two times your network round trip time, you will see significant +throughput improvements starting with a prefetch count of 2 or slightly +greater as described by benchmarks on RabbitMQ. + +http://www.rabbitmq.com/blog/2012/04/25/rabbitmq-performance-measurements-part-2/ +*/ +func (me *Channel) Qos(prefetchCount, prefetchSize int, global bool) error { + return me.call( + &basicQos{ + PrefetchCount: uint16(prefetchCount), + PrefetchSize: uint32(prefetchSize), + Global: global, + }, + &basicQosOk{}, + ) +} + +/* +Cancel stops deliveries to the consumer chan established in Channel.Consume and +identified by consumer. + +Only use this method to cleanly stop receiving deliveries from the server and +cleanly shut down the consumer chan identified by this tag. Using this method +and waiting for remaining messages to flush from the consumer chan will ensure +all messages received on the network will be delivered to the receiver of your +consumer chan. + +Continue consuming from the chan Delivery provided by Channel.Consume until the +chan closes. + +When noWait is true, do not wait for the server to acknowledge the cancel. +Only use this when you are certain there are no deliveries requiring +acknowledgment are in-flight otherwise they will arrive and be dropped in the +client without an ack and will not be redelivered to other consumers. + +*/ +func (me *Channel) Cancel(consumer string, noWait bool) error { + req := &basicCancel{ + ConsumerTag: consumer, + NoWait: noWait, + } + res := &basicCancelOk{} + + if err := me.call(req, res); err != nil { + return err + } + + if req.wait() { + me.consumers.close(res.ConsumerTag) + } else { + // Potentially could drop deliveries in flight + me.consumers.close(consumer) + } + + return nil +} + +/* +QueueDeclare declares a queue to hold messages and deliver to consumers. +Declaring creates a queue if it doesn't already exist, or ensures that an +existing queue matches the same parameters. + +Every queue declared gets a default binding to the empty exchange "" which has +the type "direct" with the routing key matching the queue's name. With this +default binding, it is possible to publish messages that route directly to +this queue by publishing to "" with the routing key of the queue name. + + QueueDeclare("alerts", true, false, false false, false, nil) + Publish("", "alerts", false, false, Publishing{Body: []byte("...")}) + + Delivery Exchange Key Queue + ----------------------------------------------- + key: alerts -> "" -> alerts -> alerts + +The queue name may be empty, in which the server will generate a unique name +which will be returned in the Name field of Queue struct. + +Durable and Non-Auto-Deleted queues will survive server restarts and remain +when there are no remaining consumers or bindings. Persistent publishings will +be restored in this queue on server restart. These queues are only able to be +bound to durable exchanges. + +Non-Durable and Auto-Deleted queues will not be redeclared on server restart +and will be deleted by the server after a short time when the last consumer is +canceled or the last consumer's channel is closed. Queues with this lifetime +can also be deleted normally with QueueDelete. These durable queues can only +be bound to non-durable exchanges. + +Non-Durable and Non-Auto-Deleted queues will remain declared as long as the +server is running regardless of how many consumers. This lifetime is useful +for temporary topologies that may have long delays between consumer activity. +These queues can only be bound to non-durable exchanges. + +Durable and Auto-Deleted queues will be restored on server restart, but without +active consumers, will not survive and be removed. This Lifetime is unlikely +to be useful. + +Exclusive queues are only accessible by the connection that declares them and +will be deleted when the connection closes. Channels on other connections +will receive an error when attempting declare, bind, consume, purge or delete a +queue with the same name. + +When noWait is true, the queue will assume to be declared on the server. A +channel exception will arrive if the conditions are met for existing queues +or attempting to modify an existing queue from a different connection. + +When the error return value is not nil, you can assume the queue could not be +declared with these parameters and the channel will be closed. + +*/ +func (me *Channel) QueueDeclare(name string, durable, autoDelete, exclusive, noWait bool, args Table) (Queue, error) { + if err := args.Validate(); err != nil { + return Queue{}, err + } + + req := &queueDeclare{ + Queue: name, + Passive: false, + Durable: durable, + AutoDelete: autoDelete, + Exclusive: exclusive, + NoWait: noWait, + Arguments: args, + } + res := &queueDeclareOk{} + + if err := me.call(req, res); err != nil { + return Queue{}, err + } + + if req.wait() { + return Queue{ + Name: res.Queue, + Messages: int(res.MessageCount), + Consumers: int(res.ConsumerCount), + }, nil + } + + return Queue{ + Name: name, + }, nil + + panic("unreachable") +} + +/* + +QueueDeclarePassive is functionally and parametrically equivalent to +QueueDeclare, except that it sets the "passive" attribute to true. A passive +queue is assumed by RabbitMQ to already exist, and attempting to connect to a +non-existent queue will cause RabbitMQ to throw an exception. This function +can be used to test for the existence of a queue. + +*/ +func (me *Channel) QueueDeclarePassive(name string, durable, autoDelete, exclusive, noWait bool, args Table) (Queue, error) { + if err := args.Validate(); err != nil { + return Queue{}, err + } + + req := &queueDeclare{ + Queue: name, + Passive: true, + Durable: durable, + AutoDelete: autoDelete, + Exclusive: exclusive, + NoWait: noWait, + Arguments: args, + } + res := &queueDeclareOk{} + + if err := me.call(req, res); err != nil { + return Queue{}, err + } + + if req.wait() { + return Queue{ + Name: res.Queue, + Messages: int(res.MessageCount), + Consumers: int(res.ConsumerCount), + }, nil + } + + return Queue{ + Name: name, + }, nil + + panic("unreachable") +} + +/* +QueueInspect passively declares a queue by name to inspect the current message +count, consumer count. + +Use this method to check how many unacknowledged messages reside in the queue +and how many consumers are receiving deliveries and whether a queue by this +name already exists. + +If the queue by this name exists, use Channel.QueueDeclare check if it is +declared with specific parameters. + +If a queue by this name does not exist, an error will be returned and the +channel will be closed. + +*/ +func (me *Channel) QueueInspect(name string) (Queue, error) { + req := &queueDeclare{ + Queue: name, + Passive: true, + } + res := &queueDeclareOk{} + + err := me.call(req, res) + + state := Queue{ + Name: name, + Messages: int(res.MessageCount), + Consumers: int(res.ConsumerCount), + } + + return state, err +} + +/* +QueueBind binds an exchange to a queue so that publishings to the exchange will +be routed to the queue when the publishing routing key matches the binding +routing key. + + QueueBind("pagers", "alert", "log", false, nil) + QueueBind("emails", "info", "log", false, nil) + + Delivery Exchange Key Queue + ----------------------------------------------- + key: alert --> log ----> alert --> pagers + key: info ---> log ----> info ---> emails + key: debug --> log (none) (dropped) + +If a binding with the same key and arguments already exists between the +exchange and queue, the attempt to rebind will be ignored and the existing +binding will be retained. + +In the case that multiple bindings may cause the message to be routed to the +same queue, the server will only route the publishing once. This is possible +with topic exchanges. + + QueueBind("pagers", "alert", "amq.topic", false, nil) + QueueBind("emails", "info", "amq.topic", false, nil) + QueueBind("emails", "#", "amq.topic", false, nil) // match everything + + Delivery Exchange Key Queue + ----------------------------------------------- + key: alert --> amq.topic ----> alert --> pagers + key: info ---> amq.topic ----> # ------> emails + \---> info ---/ + key: debug --> amq.topic ----> # ------> emails + +It is only possible to bind a durable queue to a durable exchange regardless of +whether the queue or exchange is auto-deleted. Bindings between durable queues +and exchanges will also be restored on server restart. + +If the binding could not complete, an error will be returned and the channel +will be closed. + +When noWait is true and the queue could not be bound, the channel will be +closed with an error. + +*/ +func (me *Channel) QueueBind(name, key, exchange string, noWait bool, args Table) error { + if err := args.Validate(); err != nil { + return err + } + + return me.call( + &queueBind{ + Queue: name, + Exchange: exchange, + RoutingKey: key, + NoWait: noWait, + Arguments: args, + }, + &queueBindOk{}, + ) +} + +/* +QueueUnbind removes a binding between an exchange and queue matching the key and +arguments. + +It is possible to send and empty string for the exchange name which means to +unbind the queue from the default exchange. + +*/ +func (me *Channel) QueueUnbind(name, key, exchange string, args Table) error { + if err := args.Validate(); err != nil { + return err + } + + return me.call( + &queueUnbind{ + Queue: name, + Exchange: exchange, + RoutingKey: key, + Arguments: args, + }, + &queueUnbindOk{}, + ) +} + +/* +QueuePurge removes all messages from the named queue which are not waiting to +be acknowledged. Messages that have been delivered but have not yet been +acknowledged will not be removed. + +When successful, returns the number of messages purged. + +If noWait is true, do not wait for the server response and the number of +messages purged will not be meaningful. +*/ +func (me *Channel) QueuePurge(name string, noWait bool) (int, error) { + req := &queuePurge{ + Queue: name, + NoWait: noWait, + } + res := &queuePurgeOk{} + + err := me.call(req, res) + + return int(res.MessageCount), err +} + +/* +QueueDelete removes the queue from the server including all bindings then +purges the messages based on server configuration, returning the number of +messages purged. + +When ifUnused is true, the queue will not be deleted if there are any +consumers on the queue. If there are consumers, an error will be returned and +the channel will be closed. + +When ifEmpty is true, the queue will not be deleted if there are any messages +remaining on the queue. If there are messages, an error will be returned and +the channel will be closed. + +When noWait is true, the queue will be deleted without waiting for a response +from the server. The purged message count will not be meaningful. If the queue +could not be deleted, a channel exception will be raised and the channel will +be closed. + +*/ +func (me *Channel) QueueDelete(name string, ifUnused, ifEmpty, noWait bool) (int, error) { + req := &queueDelete{ + Queue: name, + IfUnused: ifUnused, + IfEmpty: ifEmpty, + NoWait: noWait, + } + res := &queueDeleteOk{} + + err := me.call(req, res) + + return int(res.MessageCount), err +} + +/* +Consume immediately starts delivering queued messages. + +Begin receiving on the returned chan Delivery before any other operation on the +Connection or Channel. + +Continues deliveries to the returned chan Delivery until Channel.Cancel, +Connection.Close, Channel.Close, or an AMQP exception occurs. Consumers must +range over the chan to ensure all deliveries are received. Unreceived +deliveries will block all methods on the same connection. + +All deliveries in AMQP must be acknowledged. It is expected of the consumer to +call Delivery.Ack after it has successfully processed the delivery. If the +consumer is cancelled or the channel or connection is closed any unacknowledged +deliveries will be requeued at the end of the same queue. + +The consumer is identified by a string that is unique and scoped for all +consumers on this channel. If you wish to eventually cancel the consumer, use +the same non-empty idenfitier in Channel.Cancel. An empty string will cause +the library to generate a unique identity. The consumer identity will be +included in every Delivery in the ConsumerTag field + +When autoAck (also known as noAck) is true, the server will acknowledge +deliveries to this consumer prior to writing the delivery to the network. When +autoAck is true, the consumer should not call Delivery.Ack. Automatically +acknowledging deliveries means that some deliveries may get lost if the +consumer is unable to process them after the server delivers them. + +When exclusive is true, the server will ensure that this is the sole consumer +from this queue. When exclusive is false, the server will fairly distribute +deliveries across multiple consumers. + +When noLocal is true, the server will not deliver publishing sent from the same +connection to this consumer. It's advisable to use separate connections for +Channel.Publish and Channel.Consume so not to have TCP pushback on publishing +affect the ability to consume messages, so this parameter is here mostly for +completeness. + +When noWait is true, do not wait for the server to confirm the request and +immediately begin deliveries. If it is not possible to consume, a channel +exception will be raised and the channel will be closed. + +Optional arguments can be provided that have specific semantics for the queue +or server. + +When the channel or connection closes, all delivery chans will also close. + +Deliveries on the returned chan will be buffered indefinitely. To limit memory +of this buffer, use the Channel.Qos method to limit the amount of +unacknowledged/buffered deliveries the server will deliver on this Channel. + +*/ +func (me *Channel) Consume(queue, consumer string, autoAck, exclusive, noLocal, noWait bool, args Table) (<-chan Delivery, error) { + // When we return from me.call, there may be a delivery already for the + // consumer that hasn't been added to the consumer hash yet. Because of + // this, we never rely on the server picking a consumer tag for us. + + if err := args.Validate(); err != nil { + return nil, err + } + + if consumer == "" { + consumer = uniqueConsumerTag() + } + + req := &basicConsume{ + Queue: queue, + ConsumerTag: consumer, + NoLocal: noLocal, + NoAck: autoAck, + Exclusive: exclusive, + NoWait: noWait, + Arguments: args, + } + res := &basicConsumeOk{} + + deliveries := make(chan Delivery) + + me.consumers.add(consumer, deliveries) + + if err := me.call(req, res); err != nil { + me.consumers.close(consumer) + return nil, err + } + + return (<-chan Delivery)(deliveries), nil +} + +/* +ExchangeDeclare declares an exchange on the server. If the exchange does not +already exist, the server will create it. If the exchange exists, the server +verifies that it is of the provided type, durability and auto-delete flags. + +Errors returned from this method will close the channel. + +Exchange names starting with "amq." are reserved for pre-declared and +standardized exchanges. The client MAY declare an exchange starting with +"amq." if the passive option is set, or the exchange already exists. Names can +consists of a non-empty sequence of letters, digits, hyphen, underscore, +period, or colon. + +Each exchange belongs to one of a set of exchange kinds/types implemented by +the server. The exchange types define the functionality of the exchange - i.e. +how messages are routed through it. Once an exchange is declared, its type +cannot be changed. The common types are "direct", "fanout", "topic" and +"headers". + +Durable and Non-Auto-Deleted exchanges will survive server restarts and remain +declared when there are no remaining bindings. This is the best lifetime for +long-lived exchange configurations like stable routes and default exchanges. + +Non-Durable and Auto-Deleted exchanges will be deleted when there are no +remaining bindings and not restored on server restart. This lifetime is +useful for temporary topologies that should not pollute the virtual host on +failure or after the consumers have completed. + +Non-Durable and Non-Auto-deleted exchanges will remain as long as the server is +running including when there are no remaining bindings. This is useful for +temporary topologies that may have long delays between bindings. + +Durable and Auto-Deleted exchanges will survive server restarts and will be +removed before and after server restarts when there are no remaining bindings. +These exchanges are useful for robust temporary topologies or when you require +binding durable queues to auto-deleted exchanges. + +Note: RabbitMQ declares the default exchange types like 'amq.fanout' as +durable, so queues that bind to these pre-declared exchanges must also be +durable. + +Exchanges declared as `internal` do not accept accept publishings. Internal +exchanges are useful for when you wish to implement inter-exchange topologies +that should not be exposed to users of the broker. + +When noWait is true, declare without waiting for a confirmation from the server. +The channel may be closed as a result of an error. Add a NotifyClose listener +to respond to any exceptions. + +Optional amqp.Table of arguments that are specific to the server's implementation of +the exchange can be sent for exchange types that require extra parameters. +*/ +func (me *Channel) ExchangeDeclare(name, kind string, durable, autoDelete, internal, noWait bool, args Table) error { + if err := args.Validate(); err != nil { + return err + } + + return me.call( + &exchangeDeclare{ + Exchange: name, + Type: kind, + Passive: false, + Durable: durable, + AutoDelete: autoDelete, + Internal: internal, + NoWait: noWait, + Arguments: args, + }, + &exchangeDeclareOk{}, + ) +} + +/* + +ExchangeDeclarePassive is functionally and parametrically equivalent to +ExchangeDeclare, except that it sets the "passive" attribute to true. A passive +exchange is assumed by RabbitMQ to already exist, and attempting to connect to a +non-existent exchange will cause RabbitMQ to throw an exception. This function +can be used to detect the existence of an exchange. + +*/ +func (me *Channel) ExchangeDeclarePassive(name, kind string, durable, autoDelete, internal, noWait bool, args Table) error { + if err := args.Validate(); err != nil { + return err + } + + return me.call( + &exchangeDeclare{ + Exchange: name, + Type: kind, + Passive: true, + Durable: durable, + AutoDelete: autoDelete, + Internal: internal, + NoWait: noWait, + Arguments: args, + }, + &exchangeDeclareOk{}, + ) +} + +/* +ExchangeDelete removes the named exchange from the server. When an exchange is +deleted all queue bindings on the exchange are also deleted. If this exchange +does not exist, the channel will be closed with an error. + +When ifUnused is true, the server will only delete the exchange if it has no queue +bindings. If the exchange has queue bindings the server does not delete it +but close the channel with an exception instead. Set this to true if you are +not the sole owner of the exchange. + +When noWait is true, do not wait for a server confirmation that the exchange has +been deleted. Failing to delete the channel could close the channel. Add a +NotifyClose listener to respond to these channel exceptions. +*/ +func (me *Channel) ExchangeDelete(name string, ifUnused, noWait bool) error { + return me.call( + &exchangeDelete{ + Exchange: name, + IfUnused: ifUnused, + NoWait: noWait, + }, + &exchangeDeleteOk{}, + ) +} + +/* +ExchangeBind binds an exchange to another exchange to create inter-exchange +routing topologies on the server. This can decouple the private topology and +routing exchanges from exchanges intended solely for publishing endpoints. + +Binding two exchanges with identical arguments will not create duplicate +bindings. + +Binding one exchange to another with multiple bindings will only deliver a +message once. For example if you bind your exchange to `amq.fanout` with two +different binding keys, only a single message will be delivered to your +exchange even though multiple bindings will match. + +Given a message delivered to the source exchange, the message will be forwarded +to the destination exchange when the routing key is matched. + + ExchangeBind("sell", "MSFT", "trade", false, nil) + ExchangeBind("buy", "AAPL", "trade", false, nil) + + Delivery Source Key Destination + example exchange exchange + ----------------------------------------------- + key: AAPL --> trade ----> MSFT sell + \---> AAPL --> buy + +When noWait is true, do not wait for the server to confirm the binding. If any +error occurs the channel will be closed. Add a listener to NotifyClose to +handle these errors. + +Optional arguments specific to the exchanges bound can also be specified. +*/ +func (me *Channel) ExchangeBind(destination, key, source string, noWait bool, args Table) error { + if err := args.Validate(); err != nil { + return err + } + + return me.call( + &exchangeBind{ + Destination: destination, + Source: source, + RoutingKey: key, + NoWait: noWait, + Arguments: args, + }, + &exchangeBindOk{}, + ) +} + +/* +ExchangeUnbind unbinds the destination exchange from the source exchange on the +server by removing the routing key between them. This is the inverse of +ExchangeBind. If the binding does not currently exist, an error will be +returned. + +When noWait is true, do not wait for the server to confirm the deletion of the +binding. If any error occurs the channel will be closed. Add a listener to +NotifyClose to handle these errors. + +Optional arguments that are specific to the type of exchanges bound can also be +provided. These must match the same arguments specified in ExchangeBind to +identify the binding. +*/ +func (me *Channel) ExchangeUnbind(destination, key, source string, noWait bool, args Table) error { + if err := args.Validate(); err != nil { + return err + } + + return me.call( + &exchangeUnbind{ + Destination: destination, + Source: source, + RoutingKey: key, + NoWait: noWait, + Arguments: args, + }, + &exchangeUnbindOk{}, + ) +} + +/* +Publish sends a Publishing from the client to an exchange on the server. + +When you want a single message to be delivered to a single queue, you can +publish to the default exchange with the routingKey of the queue name. This is +because every declared queue gets an implicit route to the default exchange. + +Since publishings are asynchronous, any undeliverable message will get returned +by the server. Add a listener with Channel.NotifyReturn to handle any +undeliverable message when calling publish with either the mandatory or +immediate parameters as true. + +Publishings can be undeliverable when the mandatory flag is true and no queue is +bound that matches the routing key, or when the immediate flag is true and no +consumer on the matched queue is ready to accept the delivery. + +This can return an error when the channel, connection or socket is closed. The +error or lack of an error does not indicate whether the server has received this +publishing. + +It is possible for publishing to not reach the broker if the underlying socket +is shutdown without pending publishing packets being flushed from the kernel +buffers. The easy way of making it probable that all publishings reach the +server is to always call Connection.Close before terminating your publishing +application. The way to ensure that all publishings reach the server is to add +a listener to Channel.NotifyPublish and put the channel in confirm mode with +Channel.Confirm. Publishing delivery tags and their corresponding +confirmations start at 1. Exit when all publishings are confirmed. + +When Publish does not return an error and the channel is in confirm mode, the +internal counter for DeliveryTags with the first confirmation starting at 1. + +*/ +func (me *Channel) Publish(exchange, key string, mandatory, immediate bool, msg Publishing) error { + if err := msg.Headers.Validate(); err != nil { + return err + } + + me.m.Lock() + defer me.m.Unlock() + + if err := me.send(me, &basicPublish{ + Exchange: exchange, + RoutingKey: key, + Mandatory: mandatory, + Immediate: immediate, + Body: msg.Body, + Properties: properties{ + Headers: msg.Headers, + ContentType: msg.ContentType, + ContentEncoding: msg.ContentEncoding, + DeliveryMode: msg.DeliveryMode, + Priority: msg.Priority, + CorrelationId: msg.CorrelationId, + ReplyTo: msg.ReplyTo, + Expiration: msg.Expiration, + MessageId: msg.MessageId, + Timestamp: msg.Timestamp, + Type: msg.Type, + UserId: msg.UserId, + AppId: msg.AppId, + }, + }); err != nil { + return err + } + + if me.confirming { + me.confirms.Publish() + } + + return nil +} + +/* +Get synchronously receives a single Delivery from the head of a queue from the +server to the client. In almost all cases, using Channel.Consume will be +preferred. + +If there was a delivery waiting on the queue and that delivery was received the +second return value will be true. If there was no delivery waiting or an error +occured, the ok bool will be false. + +All deliveries must be acknowledged including those from Channel.Get. Call +Delivery.Ack on the returned delivery when you have fully processed this +delivery. + +When autoAck is true, the server will automatically acknowledge this message so +you don't have to. But if you are unable to fully process this message before +the channel or connection is closed, the message will not get requeued. + +*/ +func (me *Channel) Get(queue string, autoAck bool) (msg Delivery, ok bool, err error) { + req := &basicGet{Queue: queue, NoAck: autoAck} + res := &basicGetOk{} + empty := &basicGetEmpty{} + + if err := me.call(req, res, empty); err != nil { + return Delivery{}, false, err + } + + if res.DeliveryTag > 0 { + return *(newDelivery(me, res)), true, nil + } + + return Delivery{}, false, nil +} + +/* +Tx puts the channel into transaction mode on the server. All publishings and +acknowledgments following this method will be atomically committed or rolled +back for a single queue. Call either Channel.TxCommit or Channel.TxRollback to +leave a this transaction and immediately start a new transaction. + +The atomicity across multiple queues is not defined as queue declarations and +bindings are not included in the transaction. + +The behavior of publishings that are delivered as mandatory or immediate while +the channel is in a transaction is not defined. + +Once a channel has been put into transaction mode, it cannot be taken out of +transaction mode. Use a different channel for non-transactional semantics. + +*/ +func (me *Channel) Tx() error { + return me.call( + &txSelect{}, + &txSelectOk{}, + ) +} + +/* +TxCommit atomically commits all publishings and acknowledgments for a single +queue and immediately start a new transaction. + +Calling this method without having called Channel.Tx is an error. + +*/ +func (me *Channel) TxCommit() error { + return me.call( + &txCommit{}, + &txCommitOk{}, + ) +} + +/* +TxRollback atomically rolls back all publishings and acknowledgments for a +single queue and immediately start a new transaction. + +Calling this method without having called Channel.Tx is an error. + +*/ +func (me *Channel) TxRollback() error { + return me.call( + &txRollback{}, + &txRollbackOk{}, + ) +} + +/* +Flow pauses the delivery of messages to consumers on this channel. Channels +are opened with flow control not active, to open a channel with paused +deliveries immediately call this method with true after calling +Connection.Channel. + +When active is true, this method asks the server to temporarily pause deliveries +until called again with active as false. + +Channel.Get methods will not be affected by flow control. + +This method is not intended to act as window control. Use Channel.Qos to limit +the number of unacknowledged messages or bytes in flight instead. + +The server may also send us flow methods to throttle our publishings. A well +behaving publishing client should add a listener with Channel.NotifyFlow and +pause its publishings when true is sent on that channel. + +Note: RabbitMQ prefers to use TCP push back to control flow for all channels on +a connection, so under high volume scenarios, it's wise to open separate +Connections for publishings and deliveries. + +*/ +func (me *Channel) Flow(active bool) error { + return me.call( + &channelFlow{Active: active}, + &channelFlowOk{}, + ) +} + +/* +Confirm puts this channel into confirm mode so that the client can ensure all +publishings have successfully been received by the server. After entering this +mode, the server will send a basic.ack or basic.nack message with the deliver +tag set to a 1 based incrementing index corresponding to every publishing +received after the this method returns. + +Add a listener to Channel.NotifyPublish to respond to the Confirmations. If +Channel.NotifyPublish is not called, the Confirmations will be silently +ignored. + +The order of acknowledgments is not bound to the order of deliveries. + +Ack and Nack confirmations will arrive at some point in the future. + +Unroutable mandatory or immediate messages are acknowledged immediately after +any Channel.NotifyReturn listeners have been notified. Other messages are +acknowledged when all queues that should have the message routed to them have +either have received acknowledgment of delivery or have enqueued the message, +persisting the message if necessary. + +When noWait is true, the client will not wait for a response. A channel +exception could occur if the server does not support this method. + +*/ +func (me *Channel) Confirm(noWait bool) error { + me.m.Lock() + defer me.m.Unlock() + + if err := me.call( + &confirmSelect{Nowait: noWait}, + &confirmSelectOk{}, + ); err != nil { + return err + } + + me.confirming = true + + return nil +} + +/* +Recover redelivers all unacknowledged deliveries on this channel. + +When requeue is false, messages will be redelivered to the original consumer. + +When requeue is true, messages will be redelivered to any available consumer, +potentially including the original. + +If the deliveries cannot be recovered, an error will be returned and the channel +will be closed. + +Note: this method is not implemented on RabbitMQ, use Delivery.Nack instead +*/ +func (me *Channel) Recover(requeue bool) error { + return me.call( + &basicRecover{Requeue: requeue}, + &basicRecoverOk{}, + ) +} + +/* +Ack acknowledges a delivery by its delivery tag when having been consumed with +Channel.Consume or Channel.Get. + +Ack acknowledges all message received prior to the delivery tag when multiple +is true. + +See also Delivery.Ack +*/ +func (me *Channel) Ack(tag uint64, multiple bool) error { + return me.send(me, &basicAck{ + DeliveryTag: tag, + Multiple: multiple, + }) +} + +/* +Nack negatively acknowledges a delivery by its delivery tag. Prefer this +method to notify the server that you were not able to process this delivery and +it must be redelivered or dropped. + +See also Delivery.Nack +*/ +func (me *Channel) Nack(tag uint64, multiple bool, requeue bool) error { + return me.send(me, &basicNack{ + DeliveryTag: tag, + Multiple: multiple, + Requeue: requeue, + }) +} + +/* +Reject negatively acknowledges a delivery by its delivery tag. Prefer Nack +over Reject when communicating with a RabbitMQ server because you can Nack +multiple messages, reducing the amount of protocol messages to exchange. + +See also Delivery.Reject +*/ +func (me *Channel) Reject(tag uint64, requeue bool) error { + return me.send(me, &basicReject{ + DeliveryTag: tag, + Requeue: requeue, + }) +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/client_test.go b/Godeps/_workspace/src/github.com/streadway/amqp/client_test.go new file mode 100644 index 000000000..be816bc38 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/client_test.go @@ -0,0 +1,559 @@ +// 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" + "io" + "reflect" + "testing" + "time" +) + +type server struct { + *testing.T + r reader // framer <- client + w writer // framer -> client + S io.ReadWriteCloser // Server IO + C io.ReadWriteCloser // Client IO + + // captured client frames + start connectionStartOk + tune connectionTuneOk +} + +func defaultConfig() Config { + return Config{SASL: []Authentication{&PlainAuth{"guest", "guest"}}, Vhost: "/"} +} + +func newSession(t *testing.T) (io.ReadWriteCloser, *server) { + rs, wc := io.Pipe() + rc, ws := io.Pipe() + + rws := &logIO{t, "server", pipe{rs, ws}} + rwc := &logIO{t, "client", pipe{rc, wc}} + + server := server{ + T: t, + r: reader{rws}, + w: writer{rws}, + S: rws, + C: rwc, + } + + return rwc, &server +} + +func (t *server) expectBytes(b []byte) { + in := make([]byte, len(b)) + if _, err := io.ReadFull(t.S, in); err != nil { + t.Fatalf("io error expecting bytes: %v", err) + } + + if bytes.Compare(b, in) != 0 { + t.Fatalf("failed bytes: expected: %s got: %s", string(b), string(in)) + } +} + +func (t *server) send(channel int, m message) { + defer time.AfterFunc(time.Second, func() { panic("send deadlock") }).Stop() + + if err := t.w.WriteFrame(&methodFrame{ + ChannelId: uint16(channel), + Method: m, + }); err != nil { + t.Fatalf("frame err, write: %s", err) + } +} + +// drops all but method frames expected on the given channel +func (t *server) recv(channel int, m message) message { + defer time.AfterFunc(time.Second, func() { panic("recv deadlock") }).Stop() + + var remaining int + var header *headerFrame + var body []byte + + for { + frame, err := t.r.ReadFrame() + if err != nil { + t.Fatalf("frame err, read: %s", err) + } + + if frame.channel() != uint16(channel) { + t.Fatalf("expected frame on channel %d, got channel %d", channel, frame.channel()) + } + + switch f := frame.(type) { + case *heartbeatFrame: + // drop + + case *headerFrame: + // start content state + header = f + remaining = int(header.Size) + if remaining == 0 { + m.(messageWithContent).setContent(header.Properties, nil) + return m + } + + case *bodyFrame: + // continue until terminated + body = append(body, f.Body...) + remaining -= len(f.Body) + if remaining <= 0 { + m.(messageWithContent).setContent(header.Properties, body) + return m + } + + case *methodFrame: + if reflect.TypeOf(m) == reflect.TypeOf(f.Method) { + wantv := reflect.ValueOf(m).Elem() + havev := reflect.ValueOf(f.Method).Elem() + wantv.Set(havev) + if _, ok := m.(messageWithContent); !ok { + return m + } + } else { + t.Fatalf("expected method type: %T, got: %T", m, f.Method) + } + + default: + t.Fatalf("unexpected frame: %+v", f) + } + } + + panic("unreachable") +} + +func (t *server) expectAMQP() { + t.expectBytes([]byte{'A', 'M', 'Q', 'P', 0, 0, 9, 1}) +} + +func (t *server) connectionStart() { + t.send(0, &connectionStart{ + VersionMajor: 0, + VersionMinor: 9, + Mechanisms: "PLAIN", + Locales: "en-us", + }) + + t.recv(0, &t.start) +} + +func (t *server) connectionTune() { + t.send(0, &connectionTune{ + ChannelMax: 11, + FrameMax: 20000, + Heartbeat: 10, + }) + + t.recv(0, &t.tune) +} + +func (t *server) connectionOpen() { + t.expectAMQP() + t.connectionStart() + t.connectionTune() + + t.recv(0, &connectionOpen{}) + t.send(0, &connectionOpenOk{}) +} + +func (t *server) connectionClose() { + t.recv(0, &connectionClose{}) + t.send(0, &connectionCloseOk{}) +} + +func (t *server) channelOpen(id int) { + t.recv(id, &channelOpen{}) + t.send(id, &channelOpenOk{}) +} + +func TestDefaultClientProperties(t *testing.T) { + rwc, srv := newSession(t) + + go func() { + srv.connectionOpen() + rwc.Close() + }() + + if c, err := Open(rwc, defaultConfig()); err != nil { + t.Fatalf("could not create connection: %v (%s)", c, err) + } + + if want, got := defaultProduct, srv.start.ClientProperties["product"]; want != got { + t.Errorf("expected product %s got: %s", want, got) + } + + if want, got := defaultVersion, srv.start.ClientProperties["version"]; want != got { + t.Errorf("expected version %s got: %s", want, got) + } +} + +func TestCustomClientProperties(t *testing.T) { + rwc, srv := newSession(t) + + config := defaultConfig() + config.Properties = Table{ + "product": "foo", + "version": "1.0", + } + + go func() { + srv.connectionOpen() + rwc.Close() + }() + + if c, err := Open(rwc, config); err != nil { + t.Fatalf("could not create connection: %v (%s)", c, err) + } + + if want, got := config.Properties["product"], srv.start.ClientProperties["product"]; want != got { + t.Errorf("expected product %s got: %s", want, got) + } + + if want, got := config.Properties["version"], srv.start.ClientProperties["version"]; want != got { + t.Errorf("expected version %s got: %s", want, got) + } +} + +func TestOpen(t *testing.T) { + rwc, srv := newSession(t) + go func() { + srv.connectionOpen() + rwc.Close() + }() + + if c, err := Open(rwc, defaultConfig()); err != nil { + t.Fatalf("could not create connection: %v (%s)", c, err) + } +} + +func TestChannelOpen(t *testing.T) { + rwc, srv := newSession(t) + + go func() { + srv.connectionOpen() + srv.channelOpen(1) + + rwc.Close() + }() + + c, err := Open(rwc, defaultConfig()) + if err != nil { + t.Fatalf("could not create connection: %v (%s)", c, err) + } + + ch, err := c.Channel() + if err != nil { + t.Fatalf("could not open channel: %v (%s)", ch, err) + } +} + +func TestOpenFailedSASLUnsupportedMechanisms(t *testing.T) { + rwc, srv := newSession(t) + + go func() { + srv.expectAMQP() + srv.send(0, &connectionStart{ + VersionMajor: 0, + VersionMinor: 9, + Mechanisms: "KERBEROS NTLM", + Locales: "en-us", + }) + }() + + c, err := Open(rwc, defaultConfig()) + if err != ErrSASL { + t.Fatalf("expected ErrSASL got: %+v on %+v", err, c) + } +} + +func TestOpenFailedCredentials(t *testing.T) { + rwc, srv := newSession(t) + + go func() { + srv.expectAMQP() + srv.connectionStart() + // Now kill/timeout the connection indicating bad auth + rwc.Close() + }() + + c, err := Open(rwc, defaultConfig()) + if err != ErrCredentials { + t.Fatalf("expected ErrCredentials got: %+v on %+v", err, c) + } +} + +func TestOpenFailedVhost(t *testing.T) { + rwc, srv := newSession(t) + + go func() { + srv.expectAMQP() + srv.connectionStart() + srv.connectionTune() + srv.recv(0, &connectionOpen{}) + + // Now kill/timeout the connection on bad Vhost + rwc.Close() + }() + + c, err := Open(rwc, defaultConfig()) + if err != ErrVhost { + t.Fatalf("expected ErrVhost got: %+v on %+v", err, c) + } +} + +func TestConfirmMultipleOrdersDeliveryTags(t *testing.T) { + rwc, srv := newSession(t) + defer rwc.Close() + + go func() { + srv.connectionOpen() + srv.channelOpen(1) + + srv.recv(1, &confirmSelect{}) + srv.send(1, &confirmSelectOk{}) + + srv.recv(1, &basicPublish{}) + srv.recv(1, &basicPublish{}) + srv.recv(1, &basicPublish{}) + srv.recv(1, &basicPublish{}) + + // Single tag, plus multiple, should produce + // 2, 1, 3, 4 + srv.send(1, &basicAck{DeliveryTag: 2}) + srv.send(1, &basicAck{DeliveryTag: 1}) + srv.send(1, &basicAck{DeliveryTag: 4, Multiple: true}) + + srv.recv(1, &basicPublish{}) + srv.recv(1, &basicPublish{}) + srv.recv(1, &basicPublish{}) + srv.recv(1, &basicPublish{}) + + // And some more, but in reverse order, multiple then one + // 5, 6, 7, 8 + srv.send(1, &basicAck{DeliveryTag: 6, Multiple: true}) + srv.send(1, &basicAck{DeliveryTag: 8}) + srv.send(1, &basicAck{DeliveryTag: 7}) + }() + + c, err := Open(rwc, defaultConfig()) + if err != nil { + t.Fatalf("could not create connection: %v (%s)", c, err) + } + + ch, err := c.Channel() + if err != nil { + t.Fatalf("could not open channel: %v (%s)", ch, err) + } + + confirm := ch.NotifyPublish(make(chan Confirmation)) + + ch.Confirm(false) + + go func() { + ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 1")}) + ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 2")}) + ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 3")}) + ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 4")}) + }() + + // received out of order, consumed in order + for i, tag := range []uint64{1, 2, 3, 4} { + if ack := <-confirm; tag != ack.DeliveryTag { + t.Fatalf("failed ack, expected ack#%d to be %d, got %d", i, tag, ack.DeliveryTag) + } + } + + go func() { + ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 5")}) + ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 6")}) + ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 7")}) + ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 8")}) + }() + + for i, tag := range []uint64{5, 6, 7, 8} { + if ack := <-confirm; tag != ack.DeliveryTag { + t.Fatalf("failed ack, expected ack#%d to be %d, got %d", i, tag, ack.DeliveryTag) + } + } + +} + +func TestNotifyClosesReusedPublisherConfirmChan(t *testing.T) { + rwc, srv := newSession(t) + + go func() { + srv.connectionOpen() + srv.channelOpen(1) + + srv.recv(1, &confirmSelect{}) + srv.send(1, &confirmSelectOk{}) + + srv.recv(0, &connectionClose{}) + srv.send(0, &connectionCloseOk{}) + }() + + c, err := Open(rwc, defaultConfig()) + if err != nil { + t.Fatalf("could not create connection: %v (%s)", c, err) + } + + ch, err := c.Channel() + if err != nil { + t.Fatalf("could not open channel: %v (%s)", ch, err) + } + + ackAndNack := make(chan uint64) + ch.NotifyConfirm(ackAndNack, ackAndNack) + + if err := ch.Confirm(false); err != nil { + t.Fatalf("expected to enter confirm mode: %v", err) + } + + if err := c.Close(); err != nil { + t.Fatalf("could not close connection: %v (%s)", c, err) + } +} + +func TestNotifyClosesAllChansAfterConnectionClose(t *testing.T) { + rwc, srv := newSession(t) + + go func() { + srv.connectionOpen() + srv.channelOpen(1) + + srv.recv(0, &connectionClose{}) + srv.send(0, &connectionCloseOk{}) + }() + + c, err := Open(rwc, defaultConfig()) + if err != nil { + t.Fatalf("could not create connection: %v (%s)", c, err) + } + + ch, err := c.Channel() + if err != nil { + t.Fatalf("could not open channel: %v (%s)", ch, err) + } + + if err := c.Close(); err != nil { + t.Fatalf("could not close connection: %v (%s)", c, err) + } + + select { + case <-c.NotifyClose(make(chan *Error)): + case <-time.After(time.Millisecond): + t.Errorf("expected to close NotifyClose chan after Connection.Close") + } + + select { + case <-ch.NotifyClose(make(chan *Error)): + case <-time.After(time.Millisecond): + t.Errorf("expected to close Connection.NotifyClose chan after Connection.Close") + } + + select { + case <-ch.NotifyFlow(make(chan bool)): + case <-time.After(time.Millisecond): + t.Errorf("expected to close Channel.NotifyFlow chan after Connection.Close") + } + + select { + case <-ch.NotifyCancel(make(chan string)): + case <-time.After(time.Millisecond): + t.Errorf("expected to close Channel.NofityCancel chan after Connection.Close") + } + + select { + case <-ch.NotifyReturn(make(chan Return)): + case <-time.After(time.Millisecond): + t.Errorf("expected to close Channel.NotifyReturn chan after Connection.Close") + } + + confirms := ch.NotifyPublish(make(chan Confirmation)) + + select { + case <-confirms: + case <-time.After(time.Millisecond): + t.Errorf("expected to close confirms on Channel.NotifyPublish chan after Connection.Close") + } +} + +// Should not panic when sending bodies split at differnet boundaries +func TestPublishBodySliceIssue74(t *testing.T) { + rwc, srv := newSession(t) + defer rwc.Close() + + const frameSize = 100 + const publishings = frameSize * 3 + + done := make(chan bool) + base := make([]byte, publishings) + + go func() { + srv.connectionOpen() + srv.channelOpen(1) + + for i := 0; i < publishings; i++ { + srv.recv(1, &basicPublish{}) + } + + done <- true + }() + + cfg := defaultConfig() + cfg.FrameSize = frameSize + + c, err := Open(rwc, cfg) + if err != nil { + t.Fatalf("could not create connection: %v (%s)", c, err) + } + + ch, err := c.Channel() + if err != nil { + t.Fatalf("could not open channel: %v (%s)", ch, err) + } + + for i := 0; i < publishings; i++ { + go ch.Publish("", "q", false, false, Publishing{Body: base[0:i]}) + } + + <-done +} + +func TestPublishAndShutdownDeadlockIssue84(t *testing.T) { + rwc, srv := newSession(t) + defer rwc.Close() + + go func() { + srv.connectionOpen() + srv.channelOpen(1) + srv.recv(1, &basicPublish{}) + // Mimic a broken io pipe so that Publish catches the error and goes into shutdown + srv.S.Close() + }() + + c, err := Open(rwc, defaultConfig()) + if err != nil { + t.Fatalf("couldn't create connection: %v (%s)", c, err) + } + + ch, err := c.Channel() + if err != nil { + t.Fatalf("couldn't open channel: %v (%s)", ch, err) + } + + defer time.AfterFunc(500*time.Millisecond, func() { panic("Publish deadlock") }).Stop() + for { + if err := ch.Publish("exchange", "q", false, false, Publishing{Body: []byte("test")}); err != nil { + t.Log("successfully caught disconnect error", err) + return + } + } +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/confirms.go b/Godeps/_workspace/src/github.com/streadway/amqp/confirms.go new file mode 100644 index 000000000..ebee9368b --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/confirms.go @@ -0,0 +1,93 @@ +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}) + } +} + +// 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 +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/confirms_test.go b/Godeps/_workspace/src/github.com/streadway/amqp/confirms_test.go new file mode 100644 index 000000000..7eb2acc06 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/confirms_test.go @@ -0,0 +1,119 @@ +package amqp + +import ( + "testing" + "time" +) + +func TestConfirmOneResequences(t *testing.T) { + var ( + fixtures = []Confirmation{ + {1, true}, + {2, false}, + {3, true}, + } + c = newConfirms() + l = make(chan Confirmation, len(fixtures)) + ) + + c.Listen(l) + + for i, _ := range fixtures { + if want, got := uint64(i+1), c.Publish(); want != got { + t.Fatalf("expected publish to return the 1 based delivery tag published, want: %d, got: %d", want, got) + } + } + + c.One(fixtures[1]) + c.One(fixtures[2]) + + select { + case confirm := <-l: + t.Fatalf("expected to wait in order to properly resequence results, got: %+v", confirm) + default: + } + + c.One(fixtures[0]) + + for i, fix := range fixtures { + if want, got := fix, <-l; want != got { + t.Fatalf("expected to return confirmations in sequence for %d, want: %+v, got: %+v", i, want, got) + } + } +} + +func TestConfirmMultipleResequences(t *testing.T) { + var ( + fixtures = []Confirmation{ + {1, true}, + {2, true}, + {3, true}, + {4, true}, + } + c = newConfirms() + l = make(chan Confirmation, len(fixtures)) + ) + c.Listen(l) + + for _, _ = range fixtures { + c.Publish() + } + + c.Multiple(fixtures[len(fixtures)-1]) + + for i, fix := range fixtures { + if want, got := fix, <-l; want != got { + t.Fatalf("expected to confirm multiple in sequence for %d, want: %+v, got: %+v", i, want, got) + } + } +} + +func BenchmarkSequentialBufferedConfirms(t *testing.B) { + var ( + c = newConfirms() + l = make(chan Confirmation, 10) + ) + + c.Listen(l) + + for i := 0; i < t.N; i++ { + if i > cap(l)-1 { + <-l + } + c.One(Confirmation{c.Publish(), true}) + } +} + +func TestConfirmsIsThreadSafe(t *testing.T) { + const count = 1000 + const timeout = 5 * time.Second + var ( + c = newConfirms() + l = make(chan Confirmation) + pub = make(chan Confirmation) + done = make(chan Confirmation) + late = time.After(timeout) + ) + + c.Listen(l) + + for i := 0; i < count; i++ { + go func() { pub <- Confirmation{c.Publish(), true} }() + } + + for i := 0; i < count; i++ { + go func() { c.One(<-pub) }() + } + + for i := 0; i < count; i++ { + go func() { done <- <-l }() + } + + for i := 0; i < count; i++ { + select { + case <-done: + case <-late: + t.Fatalf("expected all publish/confirms to finish after %s", timeout) + } + } +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/connection.go b/Godeps/_workspace/src/github.com/streadway/amqp/connection.go new file mode 100644 index 000000000..ad4007978 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/connection.go @@ -0,0 +1,769 @@ +// 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" + "time" +) + +const ( + maxChannelMax = (2 << 15) - 1 + + defaultHeartbeat = 10 * time.Second + defaultConnectionTimeout = 30 * time.Second + defaultProduct = "https://github.com/streadway/amqp" + defaultVersion = "β" + defaultChannelMax = maxChannelMax +) + +// 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 + + // 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 read + // deadline is used. + 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 +// asyncronous 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 +} + +type readDeadliner interface { + SetReadDeadline(time.Time) error +} + +type localNetAddr interface { + LocalAddr() net.Addr +} + +// defaultDial establishes a connection when config.Dial is not provided +func defaultDial(network, addr string) (net.Conn, error) { + conn, err := net.DialTimeout(network, addr, defaultConnectionTimeout) + if err != nil { + return nil, err + } + + // Heartbeating hasn't started yet, don't stall forever on a dead server. + if err := conn.SetReadDeadline(time.Now().Add(defaultConnectionTimeout)); 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 initial read deadline to 30 seconds. +// +// 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, + }) +} + +// 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, + }) +} + +// 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 + } + + if uri.Scheme == "amqps" && config.TLSClientConfig == nil { + config.TLSClientConfig = new(tls.Config) + } + + addr := net.JoinHostPort(uri.Host, strconv.FormatInt(int64(uri.Port), 10)) + + dialer := config.Dial + if dialer == nil { + dialer = defaultDial + } + + conn, err = dialer("tcp", addr) + if err != nil { + return nil, err + } + + if config.TLSClientConfig != nil { + // Use the URI's host for hostname validation unless otherwise set. Make a + // copy so not to modify the caller's reference when the caller reuses a + // tls.Config for a different URL. + if config.TLSClientConfig.ServerName == "" { + c := *config.TLSClientConfig + c.ServerName = uri.Host + config.TLSClientConfig = &c + } + + 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) { + me := &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 me.reader(conn) + return me, me.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 (me *Connection) LocalAddr() net.Addr { + if c, ok := me.conn.(localNetAddr); ok { + return c.LocalAddr() + } + return &net.TCPAddr{} +} + +/* +NotifyClose registers a listener for close events either initiated by an error +accompaning 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 (me *Connection) NotifyClose(c chan *Error) chan *Error { + me.m.Lock() + defer me.m.Unlock() + + if me.noNotify { + close(c) + } else { + me.closes = append(me.closes, c) + } + + return c +} + +/* +NotifyBlock 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 (me *Connection) NotifyBlocked(c chan Blocking) chan Blocking { + me.m.Lock() + defer me.m.Unlock() + + if me.noNotify { + close(c) + } else { + me.blocks = append(me.blocks, c) + } + + return c +} + +/* +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 (me *Connection) Close() error { + defer me.shutdown(nil) + return me.call( + &connectionClose{ + ReplyCode: replySuccess, + ReplyText: "kthxbai", + }, + &connectionCloseOk{}, + ) +} + +func (me *Connection) closeWith(err *Error) error { + defer me.shutdown(err) + return me.call( + &connectionClose{ + ReplyCode: uint16(err.Code), + ReplyText: err.Reason, + }, + &connectionCloseOk{}, + ) +} + +func (me *Connection) send(f frame) error { + me.sendM.Lock() + err := me.writer.WriteFrame(f) + me.sendM.Unlock() + + if err != nil { + // shutdown could be re-entrant from signaling notify chans + go me.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 me.sends <- time.Now(): + default: + } + } + + return err +} + +func (me *Connection) shutdown(err *Error) { + me.destructor.Do(func() { + if err != nil { + for _, c := range me.closes { + c <- err + } + } + + for _, ch := range me.channels { + me.closeChannel(ch, err) + } + + if err != nil { + me.errors <- err + } + + me.conn.Close() + + for _, c := range me.closes { + close(c) + } + + for _, c := range me.blocks { + close(c) + } + + me.m.Lock() + me.noNotify = true + me.m.Unlock() + }) +} + +// All methods sent to the connection channel should be synchronous so we +// can handle them directly without a framing component +func (me *Connection) demux(f frame) { + if f.channel() == 0 { + me.dispatch0(f) + } else { + me.dispatchN(f) + } +} + +func (me *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. + me.send(&methodFrame{ + ChannelId: 0, + Method: &connectionCloseOk{}, + }) + + me.shutdown(newError(m.ReplyCode, m.ReplyText)) + case *connectionBlocked: + for _, c := range me.blocks { + c <- Blocking{Active: true, Reason: m.Reason} + } + case *connectionUnblocked: + for _, c := range me.blocks { + c <- Blocking{Active: false} + } + default: + me.rpc <- m + } + case *heartbeatFrame: + // kthx - all reads reset our deadline. so we can drop this + default: + // lolwat - channel0 only responds to methods and heartbeats + me.closeWith(ErrUnexpectedFrame) + } +} + +func (me *Connection) dispatchN(f frame) { + me.m.Lock() + channel := me.channels[f.channel()] + me.m.Unlock() + + if channel != nil { + channel.recv(channel, f) + } else { + me.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 (me *Connection) dispatchClosed(f frame) { + // Only consider method frames, drop content/header frames + if mf, ok := f.(*methodFrame); ok { + switch mf.Method.(type) { + case *channelClose: + me.send(&methodFrame{ + ChannelId: f.channel(), + Method: &channelCloseOk{}, + }) + case *channelCloseOk: + // we are already closed, so do nothing + default: + // unexpected method on closed channel + me.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 (me *Connection) reader(r io.Reader) { + buf := bufio.NewReader(r) + frames := &reader{buf} + conn, haveDeadliner := r.(readDeadliner) + + for { + frame, err := frames.ReadFrame() + + if err != nil { + me.shutdown(&Error{Code: FrameError, Reason: err.Error()}) + return + } + + me.demux(frame) + + if haveDeadliner { + me.deadlines <- conn + } + } +} + +// Ensures that at least one frame is being sent at the tuned interval with a +// jitter tolerance of 1s +func (me *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 := <-me.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 := me.send(&heartbeatFrame{}); err != nil { + // send heartbeats even after close/closeOk so we + // tick until the connection starts erroring + return + } + } + + case conn := <-me.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 (me *Connection) isCapable(featureName string) bool { + capabilities, _ := me.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 (me *Connection) allocateChannel() (*Channel, error) { + me.m.Lock() + defer me.m.Unlock() + + id, ok := me.allocator.next() + if !ok { + return nil, ErrChannelMax + } + + ch := newChannel(me, uint16(id)) + me.channels[uint16(id)] = ch + + return ch, nil +} + +// releaseChannel removes a channel from the registry as the final part of the +// channel lifecycle +func (me *Connection) releaseChannel(id uint16) { + me.m.Lock() + defer me.m.Unlock() + + delete(me.channels, id) + me.allocator.release(int(id)) +} + +// openChannel allocates and opens a channel, must be paired with closeChannel +func (me *Connection) openChannel() (*Channel, error) { + ch, err := me.allocateChannel() + if err != nil { + return nil, err + } + + if err := ch.open(); err != nil { + 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 (me *Connection) closeChannel(ch *Channel, e *Error) { + ch.shutdown(e) + me.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 (me *Connection) Channel() (*Channel, error) { + return me.openChannel() +} + +func (me *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 := me.send(&methodFrame{ChannelId: 0, Method: req}); err != nil { + return err + } + } + + select { + case err := <-me.errors: + return err + + case msg := <-me.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 + } + + panic("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 (me *Connection) open(config Config) error { + if err := me.send(&protocolHeader{}); err != nil { + return err + } + + return me.openStart(config) +} + +func (me *Connection) openStart(config Config) error { + start := &connectionStart{} + + if err := me.call(nil, start); err != nil { + return err + } + + me.Major = int(start.VersionMajor) + me.Minor = int(start.VersionMinor) + me.Properties = Table(start.ServerProperties) + + // 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 + me.Config.SASL = []Authentication{auth} + + return me.openTune(config, auth) +} + +func (me *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{ + Mechanism: auth.Mechanism(), + Response: auth.Response(), + ClientProperties: config.Properties, + } + tune := &connectionTune{} + + if err := me.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. + me.Config.ChannelMax = pick(config.ChannelMax, int(tune.ChannelMax)) + if me.Config.ChannelMax == 0 { + me.Config.ChannelMax = defaultChannelMax + } + me.Config.ChannelMax = min(me.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. + me.Config.FrameSize = pick(config.FrameSize, int(tune.FrameMax)) + + // Save this off for resetDeadline() + me.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 me.heartbeater(me.Config.Heartbeat, me.NotifyClose(make(chan *Error, 1))) + + if err := me.send(&methodFrame{ + ChannelId: 0, + Method: &connectionTuneOk{ + ChannelMax: uint16(me.Config.ChannelMax), + FrameMax: uint32(me.Config.FrameSize), + Heartbeat: uint16(me.Config.Heartbeat / time.Second), + }, + }); err != nil { + return err + } + + return me.openVhost(config) +} + +func (me *Connection) openVhost(config Config) error { + req := &connectionOpen{VirtualHost: config.Vhost} + res := &connectionOpenOk{} + + if err := me.call(req, res); err != nil { + // Cannot be closed yet, but we know it's a vhost problem + return ErrVhost + } + + me.Config.Vhost = config.Vhost + + return me.openComplete() +} + +// openComplete performs any final Connection initialization dependent on the +// connection handshake. +func (me *Connection) openComplete() error { + me.allocator = newAllocator(1, me.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) +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/consumers.go b/Godeps/_workspace/src/github.com/streadway/amqp/consumers.go new file mode 100644 index 000000000..b6bd60575 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/consumers.go @@ -0,0 +1,118 @@ +// 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" + "os" + "sync" + "sync/atomic" +) + +var consumerSeq uint64 + +func uniqueConsumerTag() string { + return fmt.Sprintf("ctag-%s-%d", os.Args[0], atomic.AddUint64(&consumerSeq, 1)) +} + +type consumerBuffers map[string]chan *Delivery + +// Concurrent type that manages the consumerTag -> +// ingress consumerBuffer mapping +type consumers struct { + sync.Mutex + chans consumerBuffers +} + +func makeConsumers() *consumers { + return &consumers{chans: make(consumerBuffers)} +} + +func bufferDeliveries(in chan *Delivery, out chan Delivery) { + var queue []*Delivery + var queueIn = in + + for delivery := range in { + select { + case out <- *delivery: + // delivered immediately while the consumer chan can receive + default: + queue = append(queue, delivery) + } + + for len(queue) > 0 { + select { + case out <- *queue[0]: + queue = queue[1:] + case delivery, open := <-queueIn: + if open { + queue = append(queue, delivery) + } else { + // stop receiving to drain the queue + queueIn = nil + } + } + } + } + + close(out) +} + +// On key conflict, close the previous channel. +func (me *consumers) add(tag string, consumer chan Delivery) { + me.Lock() + defer me.Unlock() + + if prev, found := me.chans[tag]; found { + close(prev) + } + + in := make(chan *Delivery) + go bufferDeliveries(in, consumer) + + me.chans[tag] = in +} + +func (me *consumers) close(tag string) (found bool) { + me.Lock() + defer me.Unlock() + + ch, found := me.chans[tag] + + if found { + delete(me.chans, tag) + close(ch) + } + + return found +} + +func (me *consumers) closeAll() { + me.Lock() + defer me.Unlock() + + for _, ch := range me.chans { + close(ch) + } + + me.chans = make(consumerBuffers) +} + +// 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 (me *consumers) send(tag string, msg *Delivery) bool { + me.Lock() + defer me.Unlock() + + buffer, found := me.chans[tag] + if found { + buffer <- msg + } + + return found +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/delivery.go b/Godeps/_workspace/src/github.com/streadway/amqp/delivery.go new file mode 100644 index 000000000..f84ae4592 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/delivery.go @@ -0,0 +1,173 @@ +// 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 implemention 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 - 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 exhange + 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 (me Delivery) Ack(multiple bool) error { + if me.Acknowledger == nil { + return errDeliveryNotInitialized + } + return me.Acknowledger.Ack(me.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 (me Delivery) Reject(requeue bool) error { + if me.Acknowledger == nil { + return errDeliveryNotInitialized + } + return me.Acknowledger.Reject(me.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 (me Delivery) Nack(multiple, requeue bool) error { + if me.Acknowledger == nil { + return errDeliveryNotInitialized + } + return me.Acknowledger.Nack(me.DeliveryTag, multiple, requeue) +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/delivery_test.go b/Godeps/_workspace/src/github.com/streadway/amqp/delivery_test.go new file mode 100644 index 000000000..f126f87d8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/delivery_test.go @@ -0,0 +1,33 @@ +package amqp + +import "testing" + +func shouldNotPanic(t *testing.T) { + if err := recover(); err != nil { + t.Fatalf("should not panic, got: %s", err) + } +} + +// A closed delivery chan could produce zero value. Ack/Nack/Reject on these +// deliveries can produce a nil pointer panic. Instead return an error when +// the method can never be successful. +func TestAckZeroValueAcknowledgerDoesNotPanic(t *testing.T) { + defer shouldNotPanic(t) + if err := (Delivery{}).Ack(false); err == nil { + t.Errorf("expected Delivery{}.Ack to error") + } +} + +func TestNackZeroValueAcknowledgerDoesNotPanic(t *testing.T) { + defer shouldNotPanic(t) + if err := (Delivery{}).Nack(false, false); err == nil { + t.Errorf("expected Delivery{}.Ack to error") + } +} + +func TestRejectZeroValueAcknowledgerDoesNotPanic(t *testing.T) { + defer shouldNotPanic(t) + if err := (Delivery{}).Reject(false); err == nil { + t.Errorf("expected Delivery{}.Ack to error") + } +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/doc.go b/Godeps/_workspace/src/github.com/streadway/amqp/doc.go new file mode 100644 index 000000000..94c29f825 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/doc.go @@ -0,0 +1,108 @@ +// 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 + +/* +AMQP 0.9.1 client with RabbitMQ extensions + +Understand the AMQP 0.9.1 messaging model by reviewing these links first. Much +of the terminology 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 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 diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/examples_test.go b/Godeps/_workspace/src/github.com/streadway/amqp/examples_test.go new file mode 100644 index 000000000..8be53f427 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/examples_test.go @@ -0,0 +1,393 @@ +package amqp_test + +import ( + "crypto/tls" + "crypto/x509" + "io/ioutil" + "log" + "net" + "runtime" + "time" + + "github.com/streadway/amqp" +) + +func ExampleConfig_timeout() { + // Provide your own anonymous Dial function that delgates to net.DialTimout + // for custom timeouts + + conn, err := amqp.DialConfig("amqp:///", amqp.Config{ + Dial: func(network, addr string) (net.Conn, error) { + return net.DialTimeout(network, addr, 2*time.Second) + }, + }) + + log.Printf("conn: %v, err: %v", conn, err) +} + +func ExampleDialTLS() { + // To get started with SSL/TLS follow the instructions for adding SSL/TLS + // support in RabbitMQ with a private certificate authority here: + // + // http://www.rabbitmq.com/ssl.html + // + // Then in your rabbitmq.config, disable the plain AMQP port, verify clients + // and fail if no certificate is presented with the following: + // + // [ + // {rabbit, [ + // {tcp_listeners, []}, % listens on 127.0.0.1:5672 + // {ssl_listeners, [5671]}, % listens on 0.0.0.0:5671 + // {ssl_options, [{cacertfile,"/path/to/your/testca/cacert.pem"}, + // {certfile,"/path/to/your/server/cert.pem"}, + // {keyfile,"/path/to/your/server/key.pem"}, + // {verify,verify_peer}, + // {fail_if_no_peer_cert,true}]} + // ]} + // ]. + + cfg := new(tls.Config) + + // The self-signing certificate authority's certificate must be included in + // the RootCAs to be trusted so that the server certificate can be verified. + // + // Alternatively to adding it to the tls.Config you can add the CA's cert to + // your system's root CAs. The tls package will use the system roots + // specific to each support OS. Under OS X, add (drag/drop) your cacert.pem + // file to the 'Certificates' section of KeyChain.app to add and always + // trust. + // + // Or with the command line add and trust the DER encoded certificate: + // + // security add-certificate testca/cacert.cer + // security add-trusted-cert testca/cacert.cer + // + // If you depend on the system root CAs, then use nil for the RootCAs field + // so the system roots will be loaded. + + cfg.RootCAs = x509.NewCertPool() + + if ca, err := ioutil.ReadFile("testca/cacert.pem"); err == nil { + cfg.RootCAs.AppendCertsFromPEM(ca) + } + + // Move the client cert and key to a location specific to your application + // and load them here. + + if cert, err := tls.LoadX509KeyPair("client/cert.pem", "client/key.pem"); err == nil { + cfg.Certificates = append(cfg.Certificates, cert) + } + + // Server names are validated by the crypto/tls package, so the server + // certificate must be made for the hostname in the URL. Find the commonName + // (CN) and make sure the hostname in the URL matches this common name. Per + // the RabbitMQ instructions for a self-signed cert, this defautls to the + // current hostname. + // + // openssl x509 -noout -in server/cert.pem -subject + // + // If your server name in your certificate is different than the host you are + // connecting to, set the hostname used for verification in + // ServerName field of the tls.Config struct. + + conn, err := amqp.DialTLS("amqps://server-name-from-certificate/", cfg) + + log.Printf("conn: %v, err: %v", conn, err) +} + +func ExampleChannel_Confirm_bridge() { + // This example acts as a bridge, shoveling all messages sent from the source + // exchange "log" to destination exchange "log". + + // Confirming publishes can help from overproduction and ensure every message + // is delivered. + + // Setup the source of the store and forward + source, err := amqp.Dial("amqp://source/") + if err != nil { + log.Fatalf("connection.open source: %s", err) + } + defer source.Close() + + chs, err := source.Channel() + if err != nil { + log.Fatalf("channel.open source: %s", err) + } + + if err := chs.ExchangeDeclare("log", "topic", true, false, false, false, nil); err != nil { + log.Fatalf("exchange.declare destination: %s", err) + } + + if _, err := chs.QueueDeclare("remote-tee", true, true, false, false, nil); err != nil { + log.Fatalf("queue.declare source: %s", err) + } + + if err := chs.QueueBind("remote-tee", "#", "logs", false, nil); err != nil { + log.Fatalf("queue.bind source: %s", err) + } + + shovel, err := chs.Consume("remote-tee", "shovel", false, false, false, false, nil) + if err != nil { + log.Fatalf("basic.consume source: %s", err) + } + + // Setup the destination of the store and forward + destination, err := amqp.Dial("amqp://destination/") + if err != nil { + log.Fatalf("connection.open destination: %s", err) + } + defer destination.Close() + + chd, err := destination.Channel() + if err != nil { + log.Fatalf("channel.open destination: %s", err) + } + + if err := chd.ExchangeDeclare("log", "topic", true, false, false, false, nil); err != nil { + log.Fatalf("exchange.declare destination: %s", err) + } + + // Buffer of 1 for our single outstanding publishing + confirms := chd.NotifyPublish(make(chan amqp.Confirmation, 1)) + + if err := chd.Confirm(false); err != nil { + log.Fatalf("confirm.select destination: %s", err) + } + + // Now pump the messages, one by one, a smarter implementation + // would batch the deliveries and use multiple ack/nacks + for { + msg, ok := <-shovel + if !ok { + log.Fatalf("source channel closed, see the reconnect example for handling this") + } + + err = chd.Publish("logs", msg.RoutingKey, false, false, amqp.Publishing{ + // Copy all the properties + ContentType: msg.ContentType, + ContentEncoding: msg.ContentEncoding, + DeliveryMode: msg.DeliveryMode, + Priority: msg.Priority, + CorrelationId: msg.CorrelationId, + ReplyTo: msg.ReplyTo, + Expiration: msg.Expiration, + MessageId: msg.MessageId, + Timestamp: msg.Timestamp, + Type: msg.Type, + UserId: msg.UserId, + AppId: msg.AppId, + + // Custom headers + Headers: msg.Headers, + + // And the body + Body: msg.Body, + }) + + if err != nil { + msg.Nack(false, false) + log.Fatalf("basic.publish destination: %s", msg) + } + + // only ack the source delivery when the destination acks the publishing + if confirmed := <-confirms; confirmed.Ack { + msg.Ack(false) + } else { + msg.Nack(false, false) + } + } +} + +func ExampleChannel_Consume() { + // Connects opens an AMQP connection from the credentials in the URL. + conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") + if err != nil { + log.Fatalf("connection.open: %s", err) + } + defer conn.Close() + + c, err := conn.Channel() + if err != nil { + log.Fatalf("channel.open: %s", err) + } + + // We declare our topology on both the publisher and consumer to ensure they + // are the same. This is part of AMQP being a programmable messaging model. + // + // See the Channel.Publish example for the complimentary declare. + err = c.ExchangeDeclare("logs", "topic", true, false, false, false, nil) + if err != nil { + log.Fatalf("exchange.declare: %s", err) + } + + // Establish our queue topologies that we are responsible for + type bind struct { + queue string + key string + } + + bindings := []bind{ + bind{"page", "alert"}, + bind{"email", "info"}, + bind{"firehose", "#"}, + } + + for _, b := range bindings { + _, err = c.QueueDeclare(b.queue, true, false, false, false, nil) + if err != nil { + log.Fatalf("queue.declare: %v", err) + } + + err = c.QueueBind(b.queue, b.key, "logs", false, nil) + if err != nil { + log.Fatalf("queue.bind: %v", err) + } + } + + // Set our quality of service. Since we're sharing 3 consumers on the same + // channel, we want at least 3 messages in flight. + err = c.Qos(3, 0, false) + if err != nil { + log.Fatalf("basic.qos: %v", err) + } + + // Establish our consumers that have different responsibilities. Our first + // two queues do not ack the messages on the server, so require to be acked + // on the client. + + pages, err := c.Consume("page", "pager", false, false, false, false, nil) + if err != nil { + log.Fatalf("basic.consume: %v", err) + } + + go func() { + for log := range pages { + // ... this consumer is responsible for sending pages per log + log.Ack(false) + } + }() + + // Notice how the concern for which messages arrive here are in the AMQP + // topology and not in the queue. We let the server pick a consumer tag this + // time. + + emails, err := c.Consume("email", "", false, false, false, false, nil) + if err != nil { + log.Fatalf("basic.consume: %v", err) + } + + go func() { + for log := range emails { + // ... this consumer is responsible for sending emails per log + log.Ack(false) + } + }() + + // This consumer requests that every message is acknowledged as soon as it's + // delivered. + + firehose, err := c.Consume("firehose", "", true, false, false, false, nil) + if err != nil { + log.Fatalf("basic.consume: %v", err) + } + + // To show how to process the items in parallel, we'll use a work pool. + for i := 0; i < runtime.NumCPU(); i++ { + go func(work <-chan amqp.Delivery) { + for _ = range work { + // ... this consumer pulls from the firehose and doesn't need to acknowledge + } + }(firehose) + } + + // Wait until you're ready to finish, could be a signal handler here. + time.Sleep(10 * time.Second) + + // Cancelling a consumer by name will finish the range and gracefully end the + // goroutine + err = c.Cancel("pager", false) + if err != nil { + log.Fatalf("basic.cancel: %v", err) + } + + // deferred closing the Connection will also finish the consumer's ranges of + // their delivery chans. If you need every delivery to be processed, make + // sure to wait for all consumers goroutines to finish before exiting your + // process. +} + +func ExampleChannel_Publish() { + // Connects opens an AMQP connection from the credentials in the URL. + conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") + if err != nil { + log.Fatalf("connection.open: %s", err) + } + + // This waits for a server acknowledgment which means the sockets will have + // flushed all outbound publishings prior to returning. It's important to + // block on Close to not lose any publishings. + defer conn.Close() + + c, err := conn.Channel() + if err != nil { + log.Fatalf("channel.open: %s", err) + } + + // We declare our topology on both the publisher and consumer to ensure they + // are the same. This is part of AMQP being a programmable messaging model. + // + // See the Channel.Consume example for the complimentary declare. + err = c.ExchangeDeclare("logs", "topic", true, false, false, false, nil) + if err != nil { + log.Fatalf("exchange.declare: %v", err) + } + + // Prepare this message to be persistent. Your publishing requirements may + // be different. + msg := amqp.Publishing{ + DeliveryMode: amqp.Persistent, + Timestamp: time.Now(), + ContentType: "text/plain", + Body: []byte("Go Go AMQP!"), + } + + // This is not a mandatory delivery, so it will be dropped if there are no + // queues bound to the logs exchange. + err = c.Publish("logs", "info", false, false, msg) + if err != nil { + // Since publish is asynchronous this can happen if the network connection + // is reset or if the server has run out of resources. + log.Fatalf("basic.publish: %v", err) + } +} + +func publishAllTheThings(conn *amqp.Connection) { + // ... snarf snarf, barf barf +} + +func ExampleConnection_NotifyBlocked() { + // Simply logs when the server throttles the TCP connection for publishers + + // Test this by tuning your server to have a low memory watermark: + // rabbitmqctl set_vm_memory_high_watermark 0.00000001 + + conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") + if err != nil { + log.Fatalf("connection.open: %s", err) + } + defer conn.Close() + + blockings := conn.NotifyBlocked(make(chan amqp.Blocking)) + go func() { + for b := range blockings { + if b.Active { + log.Printf("TCP blocked: %q", b.Reason) + } else { + log.Printf("TCP unblocked") + } + } + }() + + // Your application domain channel setup publishings + publishAllTheThings(conn) +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/fuzz.go b/Godeps/_workspace/src/github.com/streadway/amqp/fuzz.go new file mode 100644 index 000000000..bf7c7689b --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/fuzz.go @@ -0,0 +1,16 @@ +// +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 +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/gen.sh b/Godeps/_workspace/src/github.com/streadway/amqp/gen.sh new file mode 100644 index 000000000..d46e19bd8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/gen.sh @@ -0,0 +1,2 @@ +#!/bin/sh +go run spec/gen.go < spec/amqp0-9-1.stripped.extended.xml | gofmt > spec091.go diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/integration_test.go b/Godeps/_workspace/src/github.com/streadway/amqp/integration_test.go new file mode 100644 index 000000000..ec839f221 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/integration_test.go @@ -0,0 +1,1796 @@ +// 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 + +// +build integration + +package amqp + +import ( + "bytes" + devrand "crypto/rand" + "encoding/binary" + "fmt" + "hash/crc32" + "io" + "math/rand" + "net" + "os" + "reflect" + "strconv" + "sync" + "testing" + "testing/quick" + "time" +) + +func TestIntegrationOpenClose(t *testing.T) { + if c := integrationConnection(t, "open-close"); c != nil { + t.Logf("have connection, calling connection close") + if err := c.Close(); err != nil { + t.Fatalf("connection close: %s", err) + } + t.Logf("connection close OK") + } +} + +func TestIntegrationOpenCloseChannel(t *testing.T) { + if c := integrationConnection(t, "channel"); c != nil { + defer c.Close() + + if _, err := c.Channel(); err != nil { + t.Errorf("Channel could not be opened: %s", err) + } + } +} + +func TestIntegrationOpenConfig(t *testing.T) { + config := Config{} + + c, err := DialConfig(integrationURLFromEnv(), config) + if err != nil { + t.Fatalf("expected to dial with config %+v integration server: %s", config, err) + } + + if _, err := c.Channel(); err != nil { + t.Fatalf("expected to open channel: %s", err) + } + + if err := c.Close(); err != nil { + t.Fatalf("connection close: %s", err) + } +} + +func TestIntegrationOpenConfigWithNetDial(t *testing.T) { + config := Config{Dial: net.Dial} + + c, err := DialConfig(integrationURLFromEnv(), config) + if err != nil { + t.Errorf("expected to dial with config %+v integration server: %s", config, err) + } + + if _, err := c.Channel(); err != nil { + t.Fatalf("expected to open channel: %s", err) + } + + if err := c.Close(); err != nil { + t.Fatalf("connection close: %s", err) + } +} + +func TestIntegrationLocalAddr(t *testing.T) { + config := Config{} + + c, err := DialConfig(integrationURLFromEnv(), config) + defer c.Close() + if err != nil { + t.Errorf("expected to dial with config %+v integration server: %s", config, err) + } + + a := c.LocalAddr() + _, portString, err := net.SplitHostPort(a.String()) + if err != nil { + t.Errorf("expected to get a local network address with config %+v integration server: %s", config, a.String()) + } + + port, err := strconv.Atoi(portString) + if err != nil { + t.Errorf("expected to get a TCP port number with config %+v integration server: %s", config, err) + } + t.Logf("Connected to port %d\n", port) +} + +// https://github.com/streadway/amqp/issues/94 +func TestExchangePassiveOnMissingExchangeShouldError(t *testing.T) { + c := integrationConnection(t, "exch") + if c != nil { + defer c.Close() + + ch, err := c.Channel() + if err != nil { + t.Fatalf("create channel 1: %s", err) + } + defer ch.Close() + + if err := ch.ExchangeDeclarePassive( + "test-integration-missing-passive-exchange", + "direct", // type + false, // duration (note: is durable) + true, // auto-delete + false, // internal + false, // nowait + nil, // args + ); err == nil { + t.Fatal("ExchangeDeclarePassive of a missing exchange should return error") + } + } +} + +// https://github.com/streadway/amqp/issues/94 +func TestIntegrationExchangeDeclarePassiveOnDeclaredShouldNotError(t *testing.T) { + c := integrationConnection(t, "exch") + if c != nil { + defer c.Close() + + exchange := "test-integration-decalred-passive-exchange" + + ch, err := c.Channel() + if err != nil { + t.Fatalf("create channel: %s", err) + } + defer ch.Close() + + if err := ch.ExchangeDeclare( + exchange, // name + "direct", // type + false, // durable + true, // auto-delete + false, // internal + false, // nowait + nil, // args + ); err != nil { + t.Fatalf("declare exchange: %s", err) + } + + if err := ch.ExchangeDeclarePassive( + exchange, // name + "direct", // type + false, // durable + true, // auto-delete + false, // internal + false, // nowait + nil, // args + ); err != nil { + t.Fatalf("ExchangeDeclarePassive on a declared exchange should not error, got: %q", err) + } + } +} + +func TestIntegrationExchange(t *testing.T) { + c := integrationConnection(t, "exch") + if c != nil { + defer c.Close() + + channel, err := c.Channel() + if err != nil { + t.Fatalf("create channel: %s", err) + } + t.Logf("create channel OK") + + exchange := "test-integration-exchange" + + if err := channel.ExchangeDeclare( + exchange, // name + "direct", // type + false, // duration + true, // auto-delete + false, // internal + false, // nowait + nil, // args + ); err != nil { + t.Fatalf("declare exchange: %s", err) + } + t.Logf("declare exchange OK") + + if err := channel.ExchangeDelete(exchange, false, false); err != nil { + t.Fatalf("delete exchange: %s", err) + } + t.Logf("delete exchange OK") + + if err := channel.Close(); err != nil { + t.Fatalf("close channel: %s", err) + } + t.Logf("close channel OK") + } +} + +// https://github.com/streadway/amqp/issues/94 +func TestIntegrationQueueDeclarePassiveOnMissingExchangeShouldError(t *testing.T) { + c := integrationConnection(t, "queue") + if c != nil { + defer c.Close() + + ch, err := c.Channel() + if err != nil { + t.Fatalf("create channel1: %s", err) + } + defer ch.Close() + + if _, err := ch.QueueDeclarePassive( + "test-integration-missing-passive-queue", // name + false, // duration (note: not durable) + true, // auto-delete + false, // exclusive + false, // noWait + nil, // arguments + ); err == nil { + t.Fatal("QueueDeclarePassive of a missing queue should error") + } + } +} + +// https://github.com/streadway/amqp/issues/94 +func TestIntegrationPassiveQueue(t *testing.T) { + c := integrationConnection(t, "queue") + if c != nil { + defer c.Close() + + name := "test-integration-declared-passive-queue" + + ch, err := c.Channel() + if err != nil { + t.Fatalf("create channel1: %s", err) + } + defer ch.Close() + + if _, err := ch.QueueDeclare( + name, // name + false, // durable + true, // auto-delete + false, // exclusive + false, // noWait + nil, // arguments + ); err != nil { + t.Fatalf("queue declare: %s", err) + } + + if _, err := ch.QueueDeclarePassive( + name, // name + false, // durable + true, // auto-delete + false, // exclusive + false, // noWait + nil, // arguments + ); err != nil { + t.Fatalf("QueueDeclarePassive on declared queue should not error, got: %q", err) + } + + if _, err := ch.QueueDeclarePassive( + name, // name + true, // durable (note: differs) + true, // auto-delete + false, // exclusive + false, // noWait + nil, // arguments + ); err != nil { + t.Fatalf("QueueDeclarePassive on declared queue with different flags should error") + } + } +} + +func TestIntegrationBasicQueueOperations(t *testing.T) { + c := integrationConnection(t, "queue") + if c != nil { + defer c.Close() + + channel, err := c.Channel() + if err != nil { + t.Fatalf("create channel: %s", err) + } + t.Logf("create channel OK") + + exchangeName := "test-basic-ops-exchange" + queueName := "test-basic-ops-queue" + + deleteQueueFirstOptions := []bool{true, false} + for _, deleteQueueFirst := range deleteQueueFirstOptions { + + if err := channel.ExchangeDeclare( + exchangeName, // name + "direct", // type + true, // duration (note: is durable) + false, // auto-delete + false, // internal + false, // nowait + nil, // args + ); err != nil { + t.Fatalf("declare exchange: %s", err) + } + t.Logf("declare exchange OK") + + if _, err := channel.QueueDeclare( + queueName, // name + true, // duration (note: durable) + false, // auto-delete + false, // exclusive + false, // noWait + nil, // arguments + ); err != nil { + t.Fatalf("queue declare: %s", err) + } + t.Logf("declare queue OK") + + if err := channel.QueueBind( + queueName, // name + "", // routingKey + exchangeName, // sourceExchange + false, // noWait + nil, // arguments + ); err != nil { + t.Fatalf("queue bind: %s", err) + } + t.Logf("queue bind OK") + + if deleteQueueFirst { + if _, err := channel.QueueDelete( + queueName, // name + false, // ifUnused (false=be aggressive) + false, // ifEmpty (false=be aggressive) + false, // noWait + ); err != nil { + t.Fatalf("delete queue (first): %s", err) + } + t.Logf("delete queue (first) OK") + + if err := channel.ExchangeDelete(exchangeName, false, false); err != nil { + t.Fatalf("delete exchange (after delete queue): %s", err) + } + t.Logf("delete exchange (after delete queue) OK") + + } else { // deleteExchangeFirst + if err := channel.ExchangeDelete(exchangeName, false, false); err != nil { + t.Fatalf("delete exchange (first): %s", err) + } + t.Logf("delete exchange (first) OK") + + if _, err := channel.QueueInspect(queueName); err != nil { + t.Fatalf("inspect queue state after deleting exchange: %s", err) + } + t.Logf("queue properly remains after exchange is deleted") + + if _, err := channel.QueueDelete( + queueName, + false, // ifUnused + false, // ifEmpty + false, // noWait + ); err != nil { + t.Fatalf("delete queue (after delete exchange): %s", err) + } + t.Logf("delete queue (after delete exchange) OK") + } + } + + if err := channel.Close(); err != nil { + t.Fatalf("close channel: %s", err) + } + t.Logf("close channel OK") + } +} + +func TestIntegrationConnectionNegotiatesMaxChannels(t *testing.T) { + config := Config{ChannelMax: 0} + + c, err := DialConfig(integrationURLFromEnv(), config) + if err != nil { + t.Errorf("expected to dial with config %+v integration server: %s", config, err) + } + defer c.Close() + + if want, got := defaultChannelMax, c.Config.ChannelMax; want != got { + t.Fatalf("expected connection to negotiate uint16 (%d) channels, got: %d", want, got) + } +} + +func TestIntegrationConnectionNegotiatesClientMaxChannels(t *testing.T) { + config := Config{ChannelMax: 16} + + c, err := DialConfig(integrationURLFromEnv(), config) + if err != nil { + t.Errorf("expected to dial with config %+v integration server: %s", config, err) + } + defer c.Close() + + if want, got := config.ChannelMax, c.Config.ChannelMax; want != got { + t.Fatalf("expected client specified channel limit after handshake %d, got: %d", want, got) + } +} + +func TestIntegrationChannelIDsExhausted(t *testing.T) { + config := Config{ChannelMax: 16} + + c, err := DialConfig(integrationURLFromEnv(), config) + if err != nil { + t.Errorf("expected to dial with config %+v integration server: %s", config, err) + } + defer c.Close() + + for i := 1; i <= c.Config.ChannelMax; i++ { + if _, err := c.Channel(); err != nil { + t.Fatalf("expected allocating all channel ids to succed, failed on %d with %v", i, err) + } + } + + if _, err := c.Channel(); err != ErrChannelMax { + t.Fatalf("expected allocating all channels to produce the client side error %#v, got: %#v", ErrChannelMax, err) + } +} + +func TestIntegrationChannelClosing(t *testing.T) { + c := integrationConnection(t, "closings") + if c != nil { + defer c.Close() + + // This function is run on every channel after it is successfully + // opened. It can do something to verify something. It should be + // quick; many channels may be opened! + f := func(t *testing.T, c *Channel) { + return + } + + // open and close + channel, err := c.Channel() + if err != nil { + t.Fatalf("basic create channel: %s", err) + } + t.Logf("basic create channel OK") + + if err := channel.Close(); err != nil { + t.Fatalf("basic close channel: %s", err) + } + t.Logf("basic close channel OK") + + // deferred close + signal := make(chan bool) + go func() { + channel, err := c.Channel() + if err != nil { + t.Fatalf("second create channel: %s", err) + } + t.Logf("second create channel OK") + + <-signal // a bit of synchronization + f(t, channel) + + defer func() { + if err := channel.Close(); err != nil { + t.Fatalf("deferred close channel: %s", err) + } + t.Logf("deferred close channel OK") + signal <- true + }() + }() + signal <- true + select { + case <-signal: + t.Logf("(got close signal OK)") + break + case <-time.After(250 * time.Millisecond): + t.Fatalf("deferred close: timeout") + } + + // multiple channels + for _, n := range []int{2, 4, 8, 16, 32, 64, 128, 256} { + channels := make([]*Channel, n) + for i := 0; i < n; i++ { + var err error + if channels[i], err = c.Channel(); err != nil { + t.Fatalf("create channel %d/%d: %s", i+1, n, err) + } + } + f(t, channel) + for i, channel := range channels { + if err := channel.Close(); err != nil { + t.Fatalf("close channel %d/%d: %s", i+1, n, err) + } + } + t.Logf("created/closed %d channels OK", n) + } + + } +} + +func TestIntegrationMeaningfulChannelErrors(t *testing.T) { + c := integrationConnection(t, "pub") + if c != nil { + defer c.Close() + + ch, err := c.Channel() + if err != nil { + t.Fatalf("Could not create channel") + } + + queue := "test.integration.channel.error" + + _, err = ch.QueueDeclare(queue, false, true, false, false, nil) + if err != nil { + t.Fatalf("Could not declare") + } + + _, err = ch.QueueDeclare(queue, true, false, false, false, nil) + if err == nil { + t.Fatalf("Expected error, got nil") + } + + e, ok := err.(*Error) + if !ok { + t.Fatalf("Expected type Error response, got %T", err) + } + + if e.Code != PreconditionFailed { + t.Fatalf("Expected PreconditionFailed, got: %+v", e) + } + + _, err = ch.QueueDeclare(queue, false, true, false, false, nil) + if err != ErrClosed { + t.Fatalf("Expected channel to be closed, got: %T", err) + } + } +} + +// https://github.com/streadway/amqp/issues/6 +func TestIntegrationNonBlockingClose(t *testing.T) { + c := integrationConnection(t, "#6") + if c != nil { + defer c.Close() + + ch, err := c.Channel() + if err != nil { + t.Fatalf("Could not create channel") + } + + queue := "test.integration.blocking.close" + + _, err = ch.QueueDeclare(queue, false, true, false, false, nil) + if err != nil { + t.Fatalf("Could not declare") + } + + msgs, err := ch.Consume(queue, "", false, false, false, false, nil) + if err != nil { + t.Fatalf("Could not consume") + } + + // Simulate a consumer + go func() { + for _ = range msgs { + t.Logf("Oh my, received message on an empty queue") + } + }() + + succeed := make(chan bool) + + go func() { + if err = ch.Close(); err != nil { + t.Fatalf("Close produced an error when it shouldn't") + } + succeed <- true + }() + + select { + case <-succeed: + break + case <-time.After(1 * time.Second): + t.Fatalf("Close timed out after 1s") + } + } +} + +func TestIntegrationPublishConsume(t *testing.T) { + queue := "test.integration.publish.consume" + + c1 := integrationConnection(t, "pub") + c2 := integrationConnection(t, "sub") + + if c1 != nil && c2 != nil { + defer c1.Close() + defer c2.Close() + + pub, _ := c1.Channel() + sub, _ := c2.Channel() + + pub.QueueDeclare(queue, false, true, false, false, nil) + sub.QueueDeclare(queue, false, true, false, false, nil) + defer pub.QueueDelete(queue, false, false, false) + + messages, _ := sub.Consume(queue, "", false, false, false, false, nil) + + pub.Publish("", queue, false, false, Publishing{Body: []byte("pub 1")}) + pub.Publish("", queue, false, false, Publishing{Body: []byte("pub 2")}) + pub.Publish("", queue, false, false, Publishing{Body: []byte("pub 3")}) + + assertConsumeBody(t, messages, []byte("pub 1")) + assertConsumeBody(t, messages, []byte("pub 2")) + assertConsumeBody(t, messages, []byte("pub 3")) + } +} + +func TestIntegrationConsumeFlow(t *testing.T) { + queue := "test.integration.consumer-flow" + + c1 := integrationConnection(t, "pub-flow") + c2 := integrationConnection(t, "sub-flow") + + if c1 != nil && c2 != nil { + defer c1.Close() + defer c2.Close() + + pub, _ := c1.Channel() + sub, _ := c2.Channel() + + pub.QueueDeclare(queue, false, true, false, false, nil) + sub.QueueDeclare(queue, false, true, false, false, nil) + defer pub.QueueDelete(queue, false, false, false) + + sub.Qos(1, 0, false) + + messages, _ := sub.Consume(queue, "", false, false, false, false, nil) + + pub.Publish("", queue, false, false, Publishing{Body: []byte("pub 1")}) + pub.Publish("", queue, false, false, Publishing{Body: []byte("pub 2")}) + + msg := assertConsumeBody(t, messages, []byte("pub 1")) + + if err := sub.Flow(false); err.(*Error).Code == NotImplemented { + t.Log("flow control is not supported on this version of rabbitmq") + return + } + + msg.Ack(false) + + select { + case <-messages: + t.Fatalf("message was delivered when flow was not active") + default: + } + + sub.Flow(true) + + msg = assertConsumeBody(t, messages, []byte("pub 2")) + msg.Ack(false) + } +} + +func TestIntegrationRecoverNotImplemented(t *testing.T) { + queue := "test.recover" + + if c, ch := integrationQueue(t, queue); c != nil { + if product, ok := c.Properties["product"]; ok && product.(string) == "RabbitMQ" { + defer c.Close() + + err := ch.Recover(false) + + if ex, ok := err.(*Error); !ok || ex.Code != 540 { + t.Fatalf("Expected NOT IMPLEMENTED got: %v", ex) + } + } + } +} + +// This test is driven by a private API to simulate the server sending a channelFlow message +func TestIntegrationPublishFlow(t *testing.T) { + // TODO - no idea how to test without affecting the server or mucking internal APIs + // i'd like to make sure the RW lock can be held by multiple publisher threads + // and that multiple channelFlow messages do not block the dispatch thread +} + +func TestIntegrationConsumeCancel(t *testing.T) { + queue := "test.integration.consume-cancel" + + c := integrationConnection(t, "pub") + + if c != nil { + defer c.Close() + + ch, _ := c.Channel() + + ch.QueueDeclare(queue, false, true, false, false, nil) + defer ch.QueueDelete(queue, false, false, false) + + messages, _ := ch.Consume(queue, "integration-tag", false, false, false, false, nil) + + ch.Publish("", queue, false, false, Publishing{Body: []byte("1")}) + + assertConsumeBody(t, messages, []byte("1")) + + err := ch.Cancel("integration-tag", false) + if err != nil { + t.Fatalf("error cancelling the consumer: %v", err) + } + + ch.Publish("", queue, false, false, Publishing{Body: []byte("2")}) + + select { + case <-time.After(100 * time.Millisecond): + t.Fatalf("Timeout on Close") + case _, ok := <-messages: + if ok { + t.Fatalf("Extra message on consumer when consumer should have been closed") + } + } + } +} + +func (c *Connection) Generate(r *rand.Rand, _ int) reflect.Value { + urlStr := os.Getenv("AMQP_URL") + if urlStr == "" { + return reflect.ValueOf(nil) + } + + conn, err := Dial(urlStr) + if err != nil { + return reflect.ValueOf(nil) + } + + return reflect.ValueOf(conn) +} + +func (c Publishing) Generate(r *rand.Rand, _ int) reflect.Value { + var ok bool + var t reflect.Value + + p := Publishing{} + //p.DeliveryMode = uint8(r.Intn(3)) + //p.Priority = uint8(r.Intn(8)) + + if r.Intn(2) > 0 { + p.ContentType = "application/octet-stream" + } + + if r.Intn(2) > 0 { + p.ContentEncoding = "gzip" + } + + if r.Intn(2) > 0 { + p.CorrelationId = fmt.Sprintf("%d", r.Int()) + } + + if r.Intn(2) > 0 { + p.ReplyTo = fmt.Sprintf("%d", r.Int()) + } + + if r.Intn(2) > 0 { + p.MessageId = fmt.Sprintf("%d", r.Int()) + } + + if r.Intn(2) > 0 { + p.Type = fmt.Sprintf("%d", r.Int()) + } + + if r.Intn(2) > 0 { + p.AppId = fmt.Sprintf("%d", r.Int()) + } + + if r.Intn(2) > 0 { + p.Timestamp = time.Unix(r.Int63(), r.Int63()) + } + + if t, ok = quick.Value(reflect.TypeOf(p.Body), r); ok { + p.Body = t.Bytes() + } + + return reflect.ValueOf(p) +} + +func TestQuickPublishOnly(t *testing.T) { + if c := integrationConnection(t, "quick"); c != nil { + defer c.Close() + pub, err := c.Channel() + queue := "test-publish" + + if _, err = pub.QueueDeclare(queue, false, true, false, false, nil); err != nil { + t.Errorf("Failed to declare: %s", err) + return + } + + defer pub.QueueDelete(queue, false, false, false) + + quick.Check(func(msg Publishing) bool { + return pub.Publish("", queue, false, false, msg) == nil + }, nil) + } +} + +func TestPublishEmptyBody(t *testing.T) { + c := integrationConnection(t, "empty") + if c != nil { + defer c.Close() + + ch, err := c.Channel() + if err != nil { + t.Errorf("Failed to create channel") + return + } + + queue := "test-TestPublishEmptyBody" + + if _, err := ch.QueueDeclare(queue, false, true, false, false, nil); err != nil { + t.Fatalf("Could not declare") + } + + messages, err := ch.Consume(queue, "", false, false, false, false, nil) + if err != nil { + t.Fatalf("Could not consume") + } + + err = ch.Publish("", queue, false, false, Publishing{}) + if err != nil { + t.Fatalf("Could not publish") + } + + select { + case msg := <-messages: + if len(msg.Body) != 0 { + t.Errorf("Received non empty body") + } + case <-time.After(200 * time.Millisecond): + t.Errorf("Timeout on receive") + } + } +} + +func TestPublishEmptyBodyWithHeadersIssue67(t *testing.T) { + c := integrationConnection(t, "issue67") + if c != nil { + defer c.Close() + + ch, err := c.Channel() + if err != nil { + t.Errorf("Failed to create channel") + return + } + + queue := "test-TestPublishEmptyBodyWithHeaders" + + if _, err := ch.QueueDeclare(queue, false, true, false, false, nil); err != nil { + t.Fatalf("Could not declare") + } + + messages, err := ch.Consume(queue, "", false, false, false, false, nil) + if err != nil { + t.Fatalf("Could not consume") + } + + headers := Table{ + "ham": "spam", + } + + err = ch.Publish("", queue, false, false, Publishing{Headers: headers}) + if err != nil { + t.Fatalf("Could not publish") + } + + select { + case msg := <-messages: + if msg.Headers["ham"] == nil { + t.Fatalf("Headers aren't sent") + } + if msg.Headers["ham"] != "spam" { + t.Fatalf("Headers are wrong") + } + case <-time.After(200 * time.Millisecond): + t.Errorf("Timeout on receive") + } + } +} + +func TestQuickPublishConsumeOnly(t *testing.T) { + c1 := integrationConnection(t, "quick-pub") + c2 := integrationConnection(t, "quick-sub") + + if c1 != nil && c2 != nil { + defer c1.Close() + defer c2.Close() + + pub, err := c1.Channel() + sub, err := c2.Channel() + + queue := "TestPublishConsumeOnly" + + if _, err = pub.QueueDeclare(queue, false, true, false, false, nil); err != nil { + t.Errorf("Failed to declare: %s", err) + return + } + + if _, err = sub.QueueDeclare(queue, false, true, false, false, nil); err != nil { + t.Errorf("Failed to declare: %s", err) + return + } + + defer sub.QueueDelete(queue, false, false, false) + + ch, err := sub.Consume(queue, "", false, false, false, false, nil) + if err != nil { + t.Errorf("Could not sub: %s", err) + } + + quick.CheckEqual( + func(msg Publishing) []byte { + empty := Publishing{Body: msg.Body} + if pub.Publish("", queue, false, false, empty) != nil { + return []byte{'X'} + } + return msg.Body + }, + func(msg Publishing) []byte { + out := <-ch + out.Ack(false) + return out.Body + }, + nil) + } +} + +func TestQuickPublishConsumeBigBody(t *testing.T) { + c1 := integrationConnection(t, "big-pub") + c2 := integrationConnection(t, "big-sub") + + if c1 != nil && c2 != nil { + defer c1.Close() + defer c2.Close() + + pub, err := c1.Channel() + sub, err := c2.Channel() + + queue := "test-pubsub" + + if _, err = sub.QueueDeclare(queue, false, true, false, false, nil); err != nil { + t.Errorf("Failed to declare: %s", err) + return + } + + ch, err := sub.Consume(queue, "", false, false, false, false, nil) + if err != nil { + t.Errorf("Could not sub: %s", err) + } + + fixture := Publishing{ + Body: make([]byte, 1e4+1000), + } + + if _, err = pub.QueueDeclare(queue, false, true, false, false, nil); err != nil { + t.Errorf("Failed to declare: %s", err) + return + } + + err = pub.Publish("", queue, false, false, fixture) + if err != nil { + t.Errorf("Could not publish big body") + } + + select { + case msg := <-ch: + if bytes.Compare(msg.Body, fixture.Body) != 0 { + t.Errorf("Consumed big body didn't match") + } + case <-time.After(200 * time.Millisecond): + t.Errorf("Timeout on receive") + } + } +} + +func TestIntegrationGetOk(t *testing.T) { + if c := integrationConnection(t, "getok"); c != nil { + defer c.Close() + + queue := "test.get-ok" + ch, _ := c.Channel() + + ch.QueueDeclare(queue, false, true, false, false, nil) + ch.Publish("", queue, false, false, Publishing{Body: []byte("ok")}) + + msg, ok, err := ch.Get(queue, false) + + if err != nil { + t.Fatalf("Failed get: %v", err) + } + + if !ok { + t.Fatalf("Get on a queued message did not find the message") + } + + if string(msg.Body) != "ok" { + t.Fatalf("Get did not get the correct message") + } + } +} + +func TestIntegrationGetEmpty(t *testing.T) { + if c := integrationConnection(t, "getok"); c != nil { + defer c.Close() + + queue := "test.get-ok" + ch, _ := c.Channel() + + ch.QueueDeclare(queue, false, true, false, false, nil) + + _, ok, err := ch.Get(queue, false) + + if err != nil { + t.Fatalf("Failed get: %v", err) + } + + if !ok { + t.Fatalf("Get on a queued message retrieved a message when it shouldn't have") + } + } +} + +func TestIntegrationTxCommit(t *testing.T) { + if c := integrationConnection(t, "txcommit"); c != nil { + defer c.Close() + + queue := "test.tx.commit" + ch, _ := c.Channel() + + ch.QueueDeclare(queue, false, true, false, false, nil) + + if err := ch.Tx(); err != nil { + t.Fatalf("tx.select failed") + } + + ch.Publish("", queue, false, false, Publishing{Body: []byte("ok")}) + + if err := ch.TxCommit(); err != nil { + t.Fatalf("tx.commit failed") + } + + msg, ok, err := ch.Get(queue, false) + + if err != nil || !ok { + t.Fatalf("Failed get: %v", err) + } + + if string(msg.Body) != "ok" { + t.Fatalf("Get did not get the correct message from the transaction") + } + } +} + +func TestIntegrationTxRollback(t *testing.T) { + if c := integrationConnection(t, "txrollback"); c != nil { + defer c.Close() + + queue := "test.tx.rollback" + ch, _ := c.Channel() + + ch.QueueDeclare(queue, false, true, false, false, nil) + + if err := ch.Tx(); err != nil { + t.Fatalf("tx.select failed") + } + + ch.Publish("", queue, false, false, Publishing{Body: []byte("ok")}) + + if err := ch.TxRollback(); err != nil { + t.Fatalf("tx.rollback failed") + } + + _, ok, err := ch.Get(queue, false) + + if err != nil { + t.Fatalf("Failed get: %v", err) + } + + if ok { + t.Fatalf("message was published when it should have been rolled back") + } + } +} + +func TestIntegrationReturn(t *testing.T) { + if c, ch := integrationQueue(t, "return"); c != nil { + defer c.Close() + + ret := make(chan Return, 1) + + ch.NotifyReturn(ret) + + // mandatory publish to an exchange without a binding should be returned + ch.Publish("", "return-without-binding", true, false, Publishing{Body: []byte("mandatory")}) + + select { + case res := <-ret: + if string(res.Body) != "mandatory" { + t.Fatalf("expected return of the same message") + } + + if res.ReplyCode != NoRoute { + t.Fatalf("expected no consumers reply code on the Return result, got: %v", res.ReplyCode) + } + + case <-time.After(200 * time.Millisecond): + t.Fatalf("no return was received within 200ms") + } + } +} + +func TestIntegrationCancel(t *testing.T) { + queue := "cancel" + consumerTag := "test.cancel" + + if c, ch := integrationQueue(t, queue); c != nil { + defer c.Close() + + cancels := ch.NotifyCancel(make(chan string, 1)) + + go func() { + if _, err := ch.Consume(queue, consumerTag, false, false, false, false, nil); err != nil { + t.Fatalf("cannot consume from %q to test NotifyCancel: %v", queue, err) + } + if _, err := ch.QueueDelete(queue, false, false, false); err != nil { + t.Fatalf("cannot delete integration queue: %v", err) + } + }() + + select { + case tag := <-cancels: + if want, got := consumerTag, tag; want != got { + t.Fatalf("expected to be notified of deleted queue with consumer tag, got: %q", got) + } + case <-time.After(200 * time.Millisecond): + t.Fatalf("expected to be notified of deleted queue with 200ms") + } + } +} + +func TestIntegrationConfirm(t *testing.T) { + if c, ch := integrationQueue(t, "confirm"); c != nil { + defer c.Close() + + confirms := ch.NotifyPublish(make(chan Confirmation, 1)) + + if err := ch.Confirm(false); err != nil { + t.Fatalf("could not confirm") + } + + ch.Publish("", "confirm", false, false, Publishing{Body: []byte("confirm")}) + + select { + case confirmed := <-confirms: + if confirmed.DeliveryTag != 1 { + t.Fatalf("expected ack starting with delivery tag of 1") + } + case <-time.After(200 * time.Millisecond): + t.Fatalf("no ack was received within 200ms") + } + } +} + +// https://github.com/streadway/amqp/issues/61 +func TestRoundTripAllFieldValueTypes61(t *testing.T) { + if conn := integrationConnection(t, "issue61"); conn != nil { + defer conn.Close() + timestamp := time.Unix(100000000, 0) + + headers := Table{ + "A": []interface{}{ + []interface{}{"nested array", int32(3)}, + Decimal{2, 1}, + Table{"S": "nested table in array"}, + int32(2 << 20), + string("array string"), + timestamp, + nil, + byte(2), + float64(2.64), + float32(2.32), + int64(2 << 60), + int16(2 << 10), + bool(true), + []byte{'b', '2'}, + }, + "D": Decimal{1, 1}, + "F": Table{"S": "nested table in table"}, + "I": int32(1 << 20), + "S": string("string"), + "T": timestamp, + "V": nil, + "b": byte(1), + "d": float64(1.64), + "f": float32(1.32), + "l": int64(1 << 60), + "s": int16(1 << 10), + "t": bool(true), + "x": []byte{'b', '1'}, + } + + queue := "test.issue61-roundtrip" + ch, _ := conn.Channel() + + if _, err := ch.QueueDeclare(queue, false, true, false, false, nil); err != nil { + t.Fatalf("Could not declare") + } + + msgs, err := ch.Consume(queue, "", false, false, false, false, nil) + if err != nil { + t.Fatalf("Could not consume") + } + + err = ch.Publish("", queue, false, false, Publishing{Body: []byte("ignored"), Headers: headers}) + if err != nil { + t.Fatalf("Could not publish: %v", err) + } + + msg, ok := <-msgs + + if !ok { + t.Fatalf("Channel closed prematurely likely due to publish exception") + } + + for k, v := range headers { + if !reflect.DeepEqual(v, msg.Headers[k]) { + t.Errorf("Round trip header not the same for key %q: expected: %#v, got %#v", k, v, msg.Headers[k]) + } + } + } +} + +// Declares a queue with the x-message-ttl extension to exercise integer +// serialization. +// +// Relates to https://github.com/streadway/amqp/issues/60 +// +func TestDeclareArgsXMessageTTL(t *testing.T) { + if conn := integrationConnection(t, "declareTTL"); conn != nil { + defer conn.Close() + + ch, _ := conn.Channel() + args := Table{"x-message-ttl": int32(9000000)} + + // should not drop the connection + if _, err := ch.QueueDeclare("declareWithTTL", false, true, false, false, args); err != nil { + t.Fatalf("cannot declare with TTL: got: %v", err) + } + } +} + +// Sets up the topology where rejected messages will be forwarded +// to a fanout exchange, with a single queue bound. +// +// Relates to https://github.com/streadway/amqp/issues/56 +// +func TestDeclareArgsRejectToDeadLetterQueue(t *testing.T) { + if conn := integrationConnection(t, "declareArgs"); conn != nil { + defer conn.Close() + + ex, q := "declareArgs", "declareArgs-deliveries" + dlex, dlq := ex+"-dead-letter", q+"-dead-letter" + + ch, _ := conn.Channel() + + if err := ch.ExchangeDeclare(ex, "fanout", false, true, false, false, nil); err != nil { + t.Fatalf("cannot declare %v: got: %v", ex, err) + } + + if err := ch.ExchangeDeclare(dlex, "fanout", false, true, false, false, nil); err != nil { + t.Fatalf("cannot declare %v: got: %v", dlex, err) + } + + if _, err := ch.QueueDeclare(dlq, false, true, false, false, nil); err != nil { + t.Fatalf("cannot declare %v: got: %v", dlq, err) + } + + if err := ch.QueueBind(dlq, "#", dlex, false, nil); err != nil { + t.Fatalf("cannot bind %v to %v: got: %v", dlq, dlex, err) + } + + if _, err := ch.QueueDeclare(q, false, true, false, false, Table{ + "x-dead-letter-exchange": dlex, + }); err != nil { + t.Fatalf("cannot declare %v with dlq %v: got: %v", q, dlex, err) + } + + if err := ch.QueueBind(q, "#", ex, false, nil); err != nil { + t.Fatalf("cannot bind %v: got: %v", ex, err) + } + + fails, err := ch.Consume(q, "", false, false, false, false, nil) + if err != nil { + t.Fatalf("cannot consume %v: got: %v", q, err) + } + + // Reject everything consumed + go func() { + for d := range fails { + d.Reject(false) + } + }() + + // Publish the 'poison' + if err := ch.Publish(ex, q, true, false, Publishing{Body: []byte("ignored")}); err != nil { + t.Fatalf("publishing failed") + } + + // spin-get until message arrives on the dead-letter queue with a + // synchronous parse to exercise the array field (x-death) set by the + // server relating to issue-56 + for i := 0; i < 10; i++ { + d, got, err := ch.Get(dlq, false) + if !got && err == nil { + continue + } else if err != nil { + t.Fatalf("expected success in parsing reject, got: %v", err) + } else { + // pass if we've parsed an array + if v, ok := d.Headers["x-death"]; ok { + if _, ok := v.([]interface{}); ok { + return + } + } + t.Fatalf("array field x-death expected in the headers, got: %v (%T)", d.Headers, d.Headers["x-death"]) + } + } + + t.Fatalf("expectd dead-letter after 10 get attempts") + } +} + +// https://github.com/streadway/amqp/issues/48 +func TestDeadlockConsumerIssue48(t *testing.T) { + if conn := integrationConnection(t, "issue48"); conn != nil { + defer conn.Close() + + deadline := make(chan bool) + go func() { + select { + case <-time.After(5 * time.Second): + panic("expected to receive 2 deliveries while in an RPC, got a deadlock") + case <-deadline: + // pass + } + }() + + ch, err := conn.Channel() + if err != nil { + t.Fatalf("got error on channel.open: %v", err) + } + + queue := "test-issue48" + + if _, err := ch.QueueDeclare(queue, false, true, false, false, nil); err != nil { + t.Fatalf("expected to declare a queue: %v", err) + } + + if err := ch.Confirm(false); err != nil { + t.Fatalf("got error on confirm: %v", err) + } + + confirms := ch.NotifyPublish(make(chan Confirmation, 2)) + + for i := 0; i < cap(confirms); i++ { + // Fill the queue with some new or remaining publishings + ch.Publish("", queue, false, false, Publishing{Body: []byte("")}) + } + + for i := 0; i < cap(confirms); i++ { + // Wait for them to land on the queue so they'll be delivered on consume + <-confirms + } + + // Consuming should send them all on the wire + msgs, err := ch.Consume(queue, "", false, false, false, false, nil) + if err != nil { + t.Fatalf("got error on consume: %v", err) + } + + // We pop one off the chan, the other is on the wire + <-msgs + + // Opening a new channel (any RPC) while another delivery is on the wire + if _, err := conn.Channel(); err != nil { + t.Fatalf("got error on consume: %v", err) + } + + // We pop the next off the chan + <-msgs + + deadline <- true + } +} + +// https://github.com/streadway/amqp/issues/46 +func TestRepeatedChannelExceptionWithPublishAndMaxProcsIssue46(t *testing.T) { + conn := integrationConnection(t, "issue46") + if conn != nil { + for i := 0; i < 100; i++ { + ch, err := conn.Channel() + if err != nil { + t.Fatalf("expected error only on publish, got error on channel.open: %v", err) + } + + for j := 0; j < 10; j++ { + err = ch.Publish("not-existing-exchange", "some-key", false, false, Publishing{Body: []byte("some-data")}) + if err, ok := err.(Error); ok { + if err.Code != 504 { + t.Fatalf("expected channel only exception, got: %v", err) + } + } + } + } + } +} + +// https://github.com/streadway/amqp/issues/43 +func TestChannelExceptionWithCloseIssue43(t *testing.T) { + conn := integrationConnection(t, "issue43") + if conn != nil { + go func() { + for err := range conn.NotifyClose(make(chan *Error)) { + t.Log(err.Error()) + } + }() + + c1, err := conn.Channel() + if err != nil { + panic(err) + } + + go func() { + for err := range c1.NotifyClose(make(chan *Error)) { + t.Log("Channel1 Close: " + err.Error()) + } + }() + + c2, err := conn.Channel() + if err != nil { + panic(err) + } + + go func() { + for err := range c2.NotifyClose(make(chan *Error)) { + t.Log("Channel2 Close: " + err.Error()) + } + }() + + // Cause an asynchronous channel exception causing the server + // to send a "channel.close" method either before or after the next + // asynchronous method. + err = c1.Publish("nonexisting-exchange", "", false, false, Publishing{}) + if err != nil { + panic(err) + } + + // Receive or send the channel close method, the channel shuts down + // but this expects a channel.close-ok to be received. + c1.Close() + + // This ensures that the 2nd channel is unaffected by the channel exception + // on channel 1. + err = c2.ExchangeDeclare("test-channel-still-exists", "direct", false, true, false, false, nil) + if err != nil { + panic(err) + } + } +} + +// https://github.com/streadway/amqp/issues/7 +func TestCorruptedMessageIssue7(t *testing.T) { + messageCount := 1024 + + c1 := integrationConnection(t, "") + c2 := integrationConnection(t, "") + + if c1 != nil && c2 != nil { + defer c1.Close() + defer c2.Close() + + pub, err := c1.Channel() + if err != nil { + t.Fatalf("Cannot create Channel") + } + + sub, err := c2.Channel() + if err != nil { + t.Fatalf("Cannot create Channel") + } + + queue := "test-corrupted-message-regression" + + if _, err := pub.QueueDeclare(queue, false, true, false, false, nil); err != nil { + t.Fatalf("Cannot declare") + } + + if _, err := sub.QueueDeclare(queue, false, true, false, false, nil); err != nil { + t.Fatalf("Cannot declare") + } + + msgs, err := sub.Consume(queue, "", false, false, false, false, nil) + if err != nil { + t.Fatalf("Cannot consume") + } + + for i := 0; i < messageCount; i++ { + err := pub.Publish("", queue, false, false, Publishing{ + Body: generateCrc32Random(7 * i), + }) + + if err != nil { + t.Fatalf("Failed to publish") + } + } + + for i := 0; i < messageCount; i++ { + select { + case msg := <-msgs: + assertMessageCrc32(t, msg.Body, fmt.Sprintf("missed match at %d", i)) + case <-time.After(200 * time.Millisecond): + t.Fatalf("Timeout on recv") + } + } + } +} + +// https://github.com/streadway/amqp/issues/136 +func TestChannelCounterShouldNotPanicIssue136(t *testing.T) { + if c := integrationConnection(t, "issue136"); c != nil { + defer c.Close() + var wg sync.WaitGroup + + // exceeds 65535 channels + for i := 0; i < 8; i++ { + wg.Add(1) + go func(i int) { + for j := 0; j < 10000; j++ { + ch, err := c.Channel() + if err != nil { + t.Fatalf("failed to create channel %d:%d, got: %v", i, j, err) + } + if err := ch.Close(); err != nil { + t.Fatalf("failed to close channel %d:%d, got: %v", i, j, err) + } + } + wg.Done() + }(i) + } + wg.Wait() + } +} + +func TestExchangeDeclarePrecondition(t *testing.T) { + c1 := integrationConnection(t, "exchange-double-declare") + c2 := integrationConnection(t, "exchange-double-declare-cleanup") + if c1 != nil && c2 != nil { + defer c1.Close() + defer c2.Close() + + ch, err := c1.Channel() + if err != nil { + t.Fatalf("Create channel") + } + + exchange := "test-mismatched-redeclare" + + err = ch.ExchangeDeclare( + exchange, + "direct", // exchangeType + false, // durable + true, // auto-delete + false, // internal + false, // noWait + nil, // arguments + ) + if err != nil { + t.Fatalf("Could not initially declare exchange") + } + + err = ch.ExchangeDeclare( + exchange, + "direct", + true, // different durability + true, + false, + false, + nil, + ) + + if err == nil { + t.Fatalf("Expected to fail a redeclare with different durability, didn't receive an error") + } + + if err, ok := err.(Error); ok { + if err.Code != PreconditionFailed { + t.Fatalf("Expected precondition error") + } + if !err.Recover { + t.Fatalf("Expected to be able to recover") + } + } + + ch2, _ := c2.Channel() + if err = ch2.ExchangeDelete(exchange, false, false); err != nil { + t.Fatalf("Could not delete exchange: %v", err) + } + } +} + +func TestRabbitMQQueueTTLGet(t *testing.T) { + if c := integrationRabbitMQ(t, "ttl"); c != nil { + defer c.Close() + + queue := "test.rabbitmq-message-ttl" + channel, err := c.Channel() + if err != nil { + t.Fatalf("channel: %v", err) + } + + if _, err = channel.QueueDeclare( + queue, + false, + true, + false, + false, + Table{"x-message-ttl": int32(100)}, // in ms + ); err != nil { + t.Fatalf("queue declare: %s", err) + } + + channel.Publish("", queue, false, false, Publishing{Body: []byte("ttl")}) + + time.Sleep(200 * time.Millisecond) + + _, ok, err := channel.Get(queue, false) + + if ok { + t.Fatalf("Expected the message to expire in 100ms, it didn't expire after 200ms") + } + + if err != nil { + t.Fatalf("Failed to get on ttl queue") + } + } +} + +func TestRabbitMQQueueNackMultipleRequeue(t *testing.T) { + if c := integrationRabbitMQ(t, "nack"); c != nil { + defer c.Close() + + if c.isCapable("basic.nack") { + queue := "test.rabbitmq-basic-nack" + channel, err := c.Channel() + if err != nil { + t.Fatalf("channel: %v", err) + } + + if _, err = channel.QueueDeclare(queue, false, true, false, false, nil); err != nil { + t.Fatalf("queue declare: %s", err) + } + + channel.Publish("", queue, false, false, Publishing{Body: []byte("1")}) + channel.Publish("", queue, false, false, Publishing{Body: []byte("2")}) + + m1, ok, err := channel.Get(queue, false) + if !ok || err != nil || m1.Body[0] != '1' { + t.Fatalf("could not get message %v", m1) + } + + m2, ok, err := channel.Get(queue, false) + if !ok || err != nil || m2.Body[0] != '2' { + t.Fatalf("could not get message %v", m2) + } + + m2.Nack(true, true) + + m1, ok, err = channel.Get(queue, false) + if !ok || err != nil || m1.Body[0] != '1' { + t.Fatalf("could not get message %v", m1) + } + + m2, ok, err = channel.Get(queue, false) + if !ok || err != nil || m2.Body[0] != '2' { + t.Fatalf("could not get message %v", m2) + } + } + } +} + +/* + * Support for integration tests + */ + +func integrationURLFromEnv() string { + url := os.Getenv("AMQP_URL") + if url == "" { + url = "amqp://" + } + return url +} + +func loggedConnection(t *testing.T, conn *Connection, name string) *Connection { + if name != "" { + conn.conn = &logIO{t, name, conn.conn} + } + return conn +} + +// Returns a conneciton to the AMQP if the AMQP_URL environment +// variable is set and a connnection can be established. +func integrationConnection(t *testing.T, name string) *Connection { + conn, err := Dial(integrationURLFromEnv()) + if err != nil { + t.Errorf("dial integration server: %s", err) + return nil + } + return loggedConnection(t, conn, name) +} + +// Returns a connection, channel and delcares a queue when the AMQP_URL is in the environment +func integrationQueue(t *testing.T, name string) (*Connection, *Channel) { + if conn := integrationConnection(t, name); conn != nil { + if channel, err := conn.Channel(); err == nil { + if _, err = channel.QueueDeclare(name, false, true, false, false, nil); err == nil { + return conn, channel + } + } + } + return nil, nil +} + +// Delegates to integrationConnection and only returns a connection if the +// product is RabbitMQ +func integrationRabbitMQ(t *testing.T, name string) *Connection { + if conn := integrationConnection(t, "connect"); conn != nil { + if server, ok := conn.Properties["product"]; ok && server == "RabbitMQ" { + return conn + } + } + + return nil +} + +func assertConsumeBody(t *testing.T, messages <-chan Delivery, want []byte) (msg *Delivery) { + select { + case got := <-messages: + if bytes.Compare(want, got.Body) != 0 { + t.Fatalf("Message body does not match want: %v, got: %v, for: %+v", want, got.Body, got) + } + msg = &got + case <-time.After(200 * time.Millisecond): + t.Fatalf("Timeout waiting for %v", want) + } + + return msg +} + +// Pulls out the CRC and verifies the remaining content against the CRC +func assertMessageCrc32(t *testing.T, msg []byte, assert string) { + size := binary.BigEndian.Uint32(msg[:4]) + + crc := crc32.NewIEEE() + crc.Write(msg[8:]) + + if binary.BigEndian.Uint32(msg[4:8]) != crc.Sum32() { + t.Fatalf("Message does not match CRC: %s", assert) + } + + if int(size) != len(msg)-8 { + t.Fatalf("Message does not match size, should=%d, is=%d: %s", size, len(msg)-8, assert) + } +} + +// Creates a random body size with a leading 32-bit CRC in network byte order +// that verifies the remaining slice +func generateCrc32Random(size int) []byte { + msg := make([]byte, size+8) + if _, err := io.ReadFull(devrand.Reader, msg); err != nil { + panic(err) + } + + crc := crc32.NewIEEE() + crc.Write(msg[8:]) + + binary.BigEndian.PutUint32(msg[0:4], uint32(size)) + binary.BigEndian.PutUint32(msg[4:8], crc.Sum32()) + + return msg +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/read.go b/Godeps/_workspace/src/github.com/streadway/amqp/read.go new file mode 100644 index 000000000..74e90ef8f --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/read.go @@ -0,0 +1,447 @@ +// 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 (me *reader) ReadFrame() (frame frame, err error) { + var scratch [7]byte + + if _, err = io.ReadFull(me.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 = me.parseMethodFrame(channel, size); err != nil { + return + } + + case frameHeader: + if frame, err = me.parseHeaderFrame(channel, size); err != nil { + return + } + + case frameBody: + if frame, err = me.parseBodyFrame(channel, size); err != nil { + return nil, err + } + + case frameHeartbeat: + if frame, err = me.parseHeartbeatFrame(channel, size); err != nil { + return + } + + default: + return nil, ErrFrame + } + + if _, err = io.ReadFull(me.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 + } + + 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 + var err error + + if err = binary.Read(r, binary.BigEndian, &size); err != nil { + return nil, err + } + + lim := &io.LimitedReader{R: r, N: int64(size)} + arr := make([]interface{}, 0) + var 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 (me *reader) parseHeaderFrame(channel uint16, size uint32) (frame frame, err error) { + hf := &headerFrame{ + ChannelId: channel, + } + + if err = binary.Read(me.r, binary.BigEndian, &hf.ClassId); err != nil { + return + } + + if err = binary.Read(me.r, binary.BigEndian, &hf.weight); err != nil { + return + } + + if err = binary.Read(me.r, binary.BigEndian, &hf.Size); err != nil { + return + } + + var flags uint16 + + if err = binary.Read(me.r, binary.BigEndian, &flags); err != nil { + return + } + + if hasProperty(flags, flagContentType) { + if hf.Properties.ContentType, err = readShortstr(me.r); err != nil { + return + } + } + if hasProperty(flags, flagContentEncoding) { + if hf.Properties.ContentEncoding, err = readShortstr(me.r); err != nil { + return + } + } + if hasProperty(flags, flagHeaders) { + if hf.Properties.Headers, err = readTable(me.r); err != nil { + return + } + } + if hasProperty(flags, flagDeliveryMode) { + if err = binary.Read(me.r, binary.BigEndian, &hf.Properties.DeliveryMode); err != nil { + return + } + } + if hasProperty(flags, flagPriority) { + if err = binary.Read(me.r, binary.BigEndian, &hf.Properties.Priority); err != nil { + return + } + } + if hasProperty(flags, flagCorrelationId) { + if hf.Properties.CorrelationId, err = readShortstr(me.r); err != nil { + return + } + } + if hasProperty(flags, flagReplyTo) { + if hf.Properties.ReplyTo, err = readShortstr(me.r); err != nil { + return + } + } + if hasProperty(flags, flagExpiration) { + if hf.Properties.Expiration, err = readShortstr(me.r); err != nil { + return + } + } + if hasProperty(flags, flagMessageId) { + if hf.Properties.MessageId, err = readShortstr(me.r); err != nil { + return + } + } + if hasProperty(flags, flagTimestamp) { + if hf.Properties.Timestamp, err = readTimestamp(me.r); err != nil { + return + } + } + if hasProperty(flags, flagType) { + if hf.Properties.Type, err = readShortstr(me.r); err != nil { + return + } + } + if hasProperty(flags, flagUserId) { + if hf.Properties.UserId, err = readShortstr(me.r); err != nil { + return + } + } + if hasProperty(flags, flagAppId) { + if hf.Properties.AppId, err = readShortstr(me.r); err != nil { + return + } + } + if hasProperty(flags, flagReserved1) { + if hf.Properties.reserved1, err = readShortstr(me.r); err != nil { + return + } + } + + return hf, nil +} + +func (me *reader) parseBodyFrame(channel uint16, size uint32) (frame frame, err error) { + bf := &bodyFrame{ + ChannelId: channel, + Body: make([]byte, size), + } + + if _, err = io.ReadFull(me.r, bf.Body); err != nil { + return nil, err + } + + return bf, nil +} + +var errHeartbeatPayload = errors.New("Heartbeats should not have a payload") + +func (me *reader) parseHeartbeatFrame(channel uint16, size uint32) (frame frame, err error) { + hf := &heartbeatFrame{ + ChannelId: channel, + } + + if size > 0 { + return nil, errHeartbeatPayload + } + + return hf, nil +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/read_test.go b/Godeps/_workspace/src/github.com/streadway/amqp/read_test.go new file mode 100644 index 000000000..bb0e30f02 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/read_test.go @@ -0,0 +1,22 @@ +package amqp + +import ( + "strings" + "testing" +) + +func TestGoFuzzCrashers(t *testing.T) { + testData := []string{ + "\b000000", + "\x02\x16\x10�[��\t\xbdui�" + "\x10\x01\x00\xff\xbf\xef\xbfサn\x99\x00\x10r", + "\x0300\x00\x00\x00\x040000", + } + + for idx, testStr := range testData { + r := reader{strings.NewReader(testStr)} + frame, err := r.ReadFrame() + if err != nil && frame != nil { + t.Errorf("%d. frame is not nil: %#v err = %v", idx, frame, err) + } + } +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/reconnect_test.go b/Godeps/_workspace/src/github.com/streadway/amqp/reconnect_test.go new file mode 100644 index 000000000..5a06cb7ae --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/reconnect_test.go @@ -0,0 +1,113 @@ +package amqp_test + +import ( + "fmt" + "github.com/streadway/amqp" + "os" +) + +// Every connection should declare the topology they expect +func setup(url, queue string) (*amqp.Connection, *amqp.Channel, error) { + conn, err := amqp.Dial(url) + if err != nil { + return nil, nil, err + } + + ch, err := conn.Channel() + if err != nil { + return nil, nil, err + } + + if _, err := ch.QueueDeclare(queue, false, true, false, false, nil); err != nil { + return nil, nil, err + } + + return conn, ch, nil +} + +func consume(url, queue string) (*amqp.Connection, <-chan amqp.Delivery, error) { + conn, ch, err := setup(url, queue) + if err != nil { + return nil, nil, err + } + + // Indicate we only want 1 message to acknowledge at a time. + if err := ch.Qos(1, 0, false); err != nil { + return nil, nil, err + } + + // Exclusive consumer + deliveries, err := ch.Consume(queue, "", false, true, false, false, nil) + + return conn, deliveries, err +} + +func ExampleConnection_reconnect() { + if url := os.Getenv("AMQP_URL"); url != "" { + queue := "example.reconnect" + + // The connection/channel for publishing to interleave the ingress messages + // between reconnects, shares the same topology as the consumer. If we rather + // sent all messages up front, the first consumer would receive every message. + // We would rather show how the messages are not lost between reconnects. + _, pub, err := setup(url, queue) + if err != nil { + fmt.Println("err publisher setup:", err) + return + } + + // Purge the queue from the publisher side to establish initial state + if _, err := pub.QueuePurge(queue, false); err != nil { + fmt.Println("err purge:", err) + return + } + + // Reconnect simulation, should be for { ... } in production + for i := 1; i <= 3; i++ { + fmt.Println("connect") + + conn, deliveries, err := consume(url, queue) + if err != nil { + fmt.Println("err consume:", err) + return + } + + // Simulate a producer on a different connection showing that consumers + // continue where they were left off after each reconnect. + if err := pub.Publish("", queue, false, false, amqp.Publishing{ + Body: []byte(fmt.Sprintf("%d", i)), + }); err != nil { + fmt.Println("err publish:", err) + return + } + + // Simulates a consumer that when the range finishes, will setup a new + // session and begin ranging over the deliveries again. + for msg := range deliveries { + fmt.Println(string(msg.Body)) + msg.Ack(false) + + // Simulate an error like a server restart, loss of route or operator + // intervention that results in the connection terminating + go conn.Close() + } + } + } else { + // pass with expected output when not running in an integration + // environment. + fmt.Println("connect") + fmt.Println("1") + fmt.Println("connect") + fmt.Println("2") + fmt.Println("connect") + fmt.Println("3") + } + + // Output: + // connect + // 1 + // connect + // 2 + // connect + // 3 +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/return.go b/Godeps/_workspace/src/github.com/streadway/amqp/return.go new file mode 100644 index 000000000..dfebd635d --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/return.go @@ -0,0 +1,64 @@ +// 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 implemention 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, + } +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/shared_test.go b/Godeps/_workspace/src/github.com/streadway/amqp/shared_test.go new file mode 100644 index 000000000..2e4715fa0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/shared_test.go @@ -0,0 +1,71 @@ +// 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 ( + "encoding/hex" + "io" + "testing" +) + +type pipe struct { + r *io.PipeReader + w *io.PipeWriter +} + +func (p pipe) Read(b []byte) (int, error) { + return p.r.Read(b) +} + +func (p pipe) Write(b []byte) (int, error) { + return p.w.Write(b) +} + +func (p pipe) Close() error { + p.r.Close() + p.w.Close() + return nil +} + +type logIO struct { + t *testing.T + prefix string + proxy io.ReadWriteCloser +} + +func (me *logIO) Read(p []byte) (n int, err error) { + me.t.Logf("%s reading %d\n", me.prefix, len(p)) + n, err = me.proxy.Read(p) + if err != nil { + me.t.Logf("%s read %x: %v\n", me.prefix, p[0:n], err) + } else { + me.t.Logf("%s read:\n%s\n", me.prefix, hex.Dump(p[0:n])) + //fmt.Printf("%s read:\n%s\n", me.prefix, hex.Dump(p[0:n])) + } + return +} + +func (me *logIO) Write(p []byte) (n int, err error) { + me.t.Logf("%s writing %d\n", me.prefix, len(p)) + n, err = me.proxy.Write(p) + if err != nil { + me.t.Logf("%s write %d, %x: %v\n", me.prefix, len(p), p[0:n], err) + } else { + me.t.Logf("%s write %d:\n%s", me.prefix, len(p), hex.Dump(p[0:n])) + //fmt.Printf("%s write %d:\n%s", me.prefix, len(p), hex.Dump(p[0:n])) + } + return +} + +func (me *logIO) Close() (err error) { + err = me.proxy.Close() + if err != nil { + me.t.Logf("%s close : %v\n", me.prefix, err) + } else { + me.t.Logf("%s close\n", me.prefix, err) + } + return +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/spec/amqp0-9-1.stripped.extended.xml b/Godeps/_workspace/src/github.com/streadway/amqp/spec/amqp0-9-1.stripped.extended.xml new file mode 100644 index 000000000..fbddb93a3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/spec/amqp0-9-1.stripped.extended.xml @@ -0,0 +1,537 @@ + + + + + + + + + + + + + + + + + Errata: Section 1.2 ought to define an exception 312 "No route", which used to + exist in 0-9 and is what RabbitMQ sends back with 'basic.return' when a + 'mandatory' message cannot be delivered to any queue. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/spec/gen.go b/Godeps/_workspace/src/github.com/streadway/amqp/spec/gen.go new file mode 100644 index 000000000..1861b9ebb --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/spec/gen.go @@ -0,0 +1,536 @@ +// 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 + +// +build ignore + +package main + +import ( + "bytes" + "encoding/xml" + "errors" + "fmt" + "io/ioutil" + "log" + "os" + "regexp" + "strings" + "text/template" +) + +var ( + ErrUnknownType = errors.New("Unknown field type in gen") + ErrUnknownDomain = errors.New("Unknown domain type in gen") +) + +var amqpTypeToNative = map[string]string{ + "bit": "bool", + "octet": "byte", + "shortshort": "uint8", + "short": "uint16", + "long": "uint32", + "longlong": "uint64", + "timestamp": "time.Time", + "table": "Table", + "shortstr": "string", + "longstr": "string", +} + +type Rule struct { + Name string `xml:"name,attr"` + Docs []string `xml:"doc"` +} + +type Doc struct { + Type string `xml:"type,attr"` + Body string `xml:",innerxml"` +} + +type Chassis struct { + Name string `xml:"name,attr"` + Implement string `xml:"implement,attr"` +} + +type Assert struct { + Check string `xml:"check,attr"` + Value string `xml:"value,attr"` + Method string `xml:"method,attr"` +} + +type Field struct { + Name string `xml:"name,attr"` + Domain string `xml:"domain,attr"` + Type string `xml:"type,attr"` + Label string `xml:"label,attr"` + Reserved bool `xml:"reserved,attr"` + Docs []Doc `xml:"doc"` + Asserts []Assert `xml:"assert"` +} + +type Response struct { + Name string `xml:"name,attr"` +} + +type Method struct { + Name string `xml:"name,attr"` + Response Response `xml:"response"` + Synchronous bool `xml:"synchronous,attr"` + Content bool `xml:"content,attr"` + Index string `xml:"index,attr"` + Label string `xml:"label,attr"` + Docs []Doc `xml:"doc"` + Rules []Rule `xml:"rule"` + Fields []Field `xml:"field"` + Chassis []Chassis `xml:"chassis"` +} + +type Class struct { + Name string `xml:"name,attr"` + Handler string `xml:"handler,attr"` + Index string `xml:"index,attr"` + Label string `xml:"label,attr"` + Docs []Doc `xml:"doc"` + Methods []Method `xml:"method"` + Chassis []Chassis `xml:"chassis"` +} + +type Domain struct { + Name string `xml:"name,attr"` + Type string `xml:"type,attr"` + Label string `xml:"label,attr"` + Rules []Rule `xml:"rule"` + Docs []Doc `xml:"doc"` +} + +type Constant struct { + Name string `xml:"name,attr"` + Value int `xml:"value,attr"` + Class string `xml:"class,attr"` + Doc string `xml:"doc"` +} + +type Amqp struct { + Major int `xml:"major,attr"` + Minor int `xml:"minor,attr"` + Port int `xml:"port,attr"` + Comment string `xml:"comment,attr"` + + Constants []Constant `xml:"constant"` + Domains []Domain `xml:"domain"` + Classes []Class `xml:"class"` +} + +type renderer struct { + Root Amqp + bitcounter int +} + +type fieldset struct { + AmqpType string + NativeType string + Fields []Field + *renderer +} + +var ( + helpers = template.FuncMap{ + "public": public, + "private": private, + "clean": clean, + } + + packageTemplate = template.Must(template.New("package").Funcs(helpers).Parse(` + // 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 + + /* GENERATED FILE - DO NOT EDIT */ + /* Rebuild from the spec/gen.go tool */ + + {{with .Root}} + package amqp + + import ( + "fmt" + "encoding/binary" + "io" + ) + + // Error codes that can be sent from the server during a connection or + // channel exception or used by the client to indicate a class of error like + // ErrCredentials. The text of the error is likely more interesting than + // these constants. + const ( + {{range $c := .Constants}} + {{if $c.IsError}}{{.Name | public}}{{else}}{{.Name | private}}{{end}} = {{.Value}}{{end}} + ) + + func isSoftExceptionCode(code int) bool { + switch code { + {{range $c := .Constants}} {{if $c.IsSoftError}} case {{$c.Value}}: + return true + {{end}}{{end}} + } + return false + } + + {{range .Classes}} + {{$class := .}} + {{range .Methods}} + {{$method := .}} + {{$struct := $.StructName $class.Name $method.Name}} + {{if .Docs}}/* {{range .Docs}} {{.Body | clean}} {{end}} */{{end}} + type {{$struct}} struct { + {{range .Fields}} + {{$.FieldName .}} {{$.FieldType . | $.NativeType}} {{if .Label}}// {{.Label}}{{end}}{{end}} + {{if .Content}}Properties properties + Body []byte{{end}} + } + + func (me *{{$struct}}) id() (uint16, uint16) { + return {{$class.Index}}, {{$method.Index}} + } + + func (me *{{$struct}}) wait() (bool) { + return {{.Synchronous}}{{if $.HasField "NoWait" .}} && !me.NoWait{{end}} + } + + {{if .Content}} + func (me *{{$struct}}) getContent() (properties, []byte) { + return me.Properties, me.Body + } + + func (me *{{$struct}}) setContent(props properties, body []byte) { + me.Properties, me.Body = props, body + } + {{end}} + func (me *{{$struct}}) write(w io.Writer) (err error) { + {{if $.HasType "bit" $method}}var bits byte{{end}} + {{.Fields | $.Fieldsets | $.Partial "enc-"}} + return + } + + func (me *{{$struct}}) read(r io.Reader) (err error) { + {{if $.HasType "bit" $method}}var bits byte{{end}} + {{.Fields | $.Fieldsets | $.Partial "dec-"}} + return + } + {{end}} + {{end}} + + func (me *reader) parseMethodFrame(channel uint16, size uint32) (f frame, err error) { + mf := &methodFrame { + ChannelId: channel, + } + + if err = binary.Read(me.r, binary.BigEndian, &mf.ClassId); err != nil { + return + } + + if err = binary.Read(me.r, binary.BigEndian, &mf.MethodId); err != nil { + return + } + + switch mf.ClassId { + {{range .Classes}} + {{$class := .}} + case {{.Index}}: // {{.Name}} + switch mf.MethodId { + {{range .Methods}} + case {{.Index}}: // {{$class.Name}} {{.Name}} + //fmt.Println("NextMethod: class:{{$class.Index}} method:{{.Index}}") + method := &{{$.StructName $class.Name .Name}}{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + {{end}} + default: + return nil, fmt.Errorf("Bad method frame, unknown method %d for class %d", mf.MethodId, mf.ClassId) + } + {{end}} + default: + return nil, fmt.Errorf("Bad method frame, unknown class %d", mf.ClassId) + } + + return mf, nil + } + {{end}} + + {{define "enc-bit"}} + {{range $off, $field := .Fields}} + if me.{{$field | $.FieldName}} { bits |= 1 << {{$off}} } + {{end}} + if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } + {{end}} + {{define "enc-octet"}} + {{range .Fields}} if err = binary.Write(w, binary.BigEndian, me.{{. | $.FieldName}}); err != nil { return } + {{end}} + {{end}} + {{define "enc-shortshort"}} + {{range .Fields}} if err = binary.Write(w, binary.BigEndian, me.{{. | $.FieldName}}); err != nil { return } + {{end}} + {{end}} + {{define "enc-short"}} + {{range .Fields}} if err = binary.Write(w, binary.BigEndian, me.{{. | $.FieldName}}); err != nil { return } + {{end}} + {{end}} + {{define "enc-long"}} + {{range .Fields}} if err = binary.Write(w, binary.BigEndian, me.{{. | $.FieldName}}); err != nil { return } + {{end}} + {{end}} + {{define "enc-longlong"}} + {{range .Fields}} if err = binary.Write(w, binary.BigEndian, me.{{. | $.FieldName}}); err != nil { return } + {{end}} + {{end}} + {{define "enc-timestamp"}} + {{range .Fields}} if err = writeTimestamp(w, me.{{. | $.FieldName}}); err != nil { return } + {{end}} + {{end}} + {{define "enc-shortstr"}} + {{range .Fields}} if err = writeShortstr(w, me.{{. | $.FieldName}}); err != nil { return } + {{end}} + {{end}} + {{define "enc-longstr"}} + {{range .Fields}} if err = writeLongstr(w, me.{{. | $.FieldName}}); err != nil { return } + {{end}} + {{end}} + {{define "enc-table"}} + {{range .Fields}} if err = writeTable(w, me.{{. | $.FieldName}}); err != nil { return } + {{end}} + {{end}} + + {{define "dec-bit"}} + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + {{range $off, $field := .Fields}} me.{{$field | $.FieldName}} = (bits & (1 << {{$off}}) > 0) + {{end}} + {{end}} + {{define "dec-octet"}} + {{range .Fields}} if err = binary.Read(r, binary.BigEndian, &me.{{. | $.FieldName}}); err != nil { return } + {{end}} + {{end}} + {{define "dec-shortshort"}} + {{range .Fields}} if err = binary.Read(r, binary.BigEndian, &me.{{. | $.FieldName}}); err != nil { return } + {{end}} + {{end}} + {{define "dec-short"}} + {{range .Fields}} if err = binary.Read(r, binary.BigEndian, &me.{{. | $.FieldName}}); err != nil { return } + {{end}} + {{end}} + {{define "dec-long"}} + {{range .Fields}} if err = binary.Read(r, binary.BigEndian, &me.{{. | $.FieldName}}); err != nil { return } + {{end}} + {{end}} + {{define "dec-longlong"}} + {{range .Fields}} if err = binary.Read(r, binary.BigEndian, &me.{{. | $.FieldName}}); err != nil { return } + {{end}} + {{end}} + {{define "dec-timestamp"}} + {{range .Fields}} if me.{{. | $.FieldName}}, err = readTimestamp(r); err != nil { return } + {{end}} + {{end}} + {{define "dec-shortstr"}} + {{range .Fields}} if me.{{. | $.FieldName}}, err = readShortstr(r); err != nil { return } + {{end}} + {{end}} + {{define "dec-longstr"}} + {{range .Fields}} if me.{{. | $.FieldName}}, err = readLongstr(r); err != nil { return } + {{end}} + {{end}} + {{define "dec-table"}} + {{range .Fields}} if me.{{. | $.FieldName}}, err = readTable(r); err != nil { return } + {{end}} + {{end}} + + `)) +) + +func (me *Constant) IsError() bool { + return strings.Contains(me.Class, "error") +} + +func (me *Constant) IsSoftError() bool { + return me.Class == "soft-error" +} + +func (me *renderer) Partial(prefix string, fields []fieldset) (s string, err error) { + var buf bytes.Buffer + for _, set := range fields { + name := prefix + set.AmqpType + t := packageTemplate.Lookup(name) + if t == nil { + return "", errors.New(fmt.Sprintf("Missing template: %s", name)) + } + if err = t.Execute(&buf, set); err != nil { + return + } + } + return string(buf.Bytes()), nil +} + +// Groups the fields so that the right encoder/decoder can be called +func (me *renderer) Fieldsets(fields []Field) (f []fieldset, err error) { + if len(fields) > 0 { + for _, field := range fields { + cur := fieldset{} + cur.AmqpType, err = me.FieldType(field) + if err != nil { + return + } + + cur.NativeType, err = me.NativeType(cur.AmqpType) + if err != nil { + return + } + cur.Fields = append(cur.Fields, field) + f = append(f, cur) + } + + i, j := 0, 1 + for j < len(f) { + if f[i].AmqpType == f[j].AmqpType { + f[i].Fields = append(f[i].Fields, f[j].Fields...) + } else { + i++ + f[i] = f[j] + } + j++ + } + return f[:i+1], nil + } + + return +} + +func (me *renderer) HasType(typ string, method Method) bool { + for _, f := range method.Fields { + name, _ := me.FieldType(f) + if name == typ { + return true + } + } + return false +} + +func (me *renderer) HasField(field string, method Method) bool { + for _, f := range method.Fields { + name := me.FieldName(f) + if name == field { + return true + } + } + return false +} + +func (me *renderer) Domain(field Field) (domain Domain, err error) { + for _, domain = range me.Root.Domains { + if field.Domain == domain.Name { + return + } + } + return domain, nil + //return domain, ErrUnknownDomain +} + +func (me *renderer) FieldName(field Field) (t string) { + t = public(field.Name) + + if field.Reserved { + t = strings.ToLower(t) + } + + return +} + +func (me *renderer) FieldType(field Field) (t string, err error) { + t = field.Type + + if t == "" { + var domain Domain + domain, err = me.Domain(field) + if err != nil { + return "", err + } + t = domain.Type + } + + return +} + +func (me *renderer) NativeType(amqpType string) (t string, err error) { + if t, ok := amqpTypeToNative[amqpType]; ok { + return t, nil + } + return "", ErrUnknownType +} + +func (me *renderer) Tag(d Domain) string { + label := "`" + + label += `domain:"` + d.Name + `"` + + if len(d.Type) > 0 { + label += `,type:"` + d.Type + `"` + } + + label += "`" + + return label +} + +func (me *renderer) StructName(parts ...string) string { + return parts[0] + public(parts[1:]...) +} + +func clean(body string) (res string) { + return strings.Replace(body, "\r", "", -1) +} + +func private(parts ...string) string { + return export(regexp.MustCompile(`[-_]\w`), parts...) +} + +func public(parts ...string) string { + return export(regexp.MustCompile(`^\w|[-_]\w`), parts...) +} + +func export(delim *regexp.Regexp, parts ...string) (res string) { + for _, in := range parts { + + res += delim.ReplaceAllStringFunc(in, func(match string) string { + switch len(match) { + case 1: + return strings.ToUpper(match) + case 2: + return strings.ToUpper(match[1:]) + } + panic("unreachable") + }) + } + + return +} + +func main() { + var r renderer + + spec, err := ioutil.ReadAll(os.Stdin) + if err != nil { + log.Fatalln("Please pass spec on stdin", err) + } + + err = xml.Unmarshal(spec, &r.Root) + + if err != nil { + log.Fatalln("Could not parse XML:", err) + } + + if err = packageTemplate.Execute(os.Stdout, &r); err != nil { + log.Fatalln("Generate error: ", err) + } +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/spec091.go b/Godeps/_workspace/src/github.com/streadway/amqp/spec091.go new file mode 100644 index 000000000..a95380303 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/spec091.go @@ -0,0 +1,3306 @@ +// 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 + +/* GENERATED FILE - DO NOT EDIT */ +/* Rebuild from the spec/gen.go tool */ + +package amqp + +import ( + "encoding/binary" + "fmt" + "io" +) + +// Error codes that can be sent from the server during a connection or +// channel exception or used by the client to indicate a class of error like +// ErrCredentials. The text of the error is likely more interesting than +// these constants. +const ( + frameMethod = 1 + frameHeader = 2 + frameBody = 3 + frameHeartbeat = 8 + frameMinSize = 4096 + frameEnd = 206 + replySuccess = 200 + ContentTooLarge = 311 + NoRoute = 312 + NoConsumers = 313 + ConnectionForced = 320 + InvalidPath = 402 + AccessRefused = 403 + NotFound = 404 + ResourceLocked = 405 + PreconditionFailed = 406 + FrameError = 501 + SyntaxError = 502 + CommandInvalid = 503 + ChannelError = 504 + UnexpectedFrame = 505 + ResourceError = 506 + NotAllowed = 530 + NotImplemented = 540 + InternalError = 541 +) + +func isSoftExceptionCode(code int) bool { + switch code { + case 311: + return true + case 312: + return true + case 313: + return true + case 403: + return true + case 404: + return true + case 405: + return true + case 406: + return true + + } + return false +} + +type connectionStart struct { + VersionMajor byte + VersionMinor byte + ServerProperties Table + Mechanisms string + Locales string +} + +func (me *connectionStart) id() (uint16, uint16) { + return 10, 10 +} + +func (me *connectionStart) wait() bool { + return true +} + +func (me *connectionStart) write(w io.Writer) (err error) { + + if err = binary.Write(w, binary.BigEndian, me.VersionMajor); err != nil { + return + } + if err = binary.Write(w, binary.BigEndian, me.VersionMinor); err != nil { + return + } + + if err = writeTable(w, me.ServerProperties); err != nil { + return + } + + if err = writeLongstr(w, me.Mechanisms); err != nil { + return + } + if err = writeLongstr(w, me.Locales); err != nil { + return + } + + return +} + +func (me *connectionStart) read(r io.Reader) (err error) { + + if err = binary.Read(r, binary.BigEndian, &me.VersionMajor); err != nil { + return + } + if err = binary.Read(r, binary.BigEndian, &me.VersionMinor); err != nil { + return + } + + if me.ServerProperties, err = readTable(r); err != nil { + return + } + + if me.Mechanisms, err = readLongstr(r); err != nil { + return + } + if me.Locales, err = readLongstr(r); err != nil { + return + } + + return +} + +type connectionStartOk struct { + ClientProperties Table + Mechanism string + Response string + Locale string +} + +func (me *connectionStartOk) id() (uint16, uint16) { + return 10, 11 +} + +func (me *connectionStartOk) wait() bool { + return true +} + +func (me *connectionStartOk) write(w io.Writer) (err error) { + + if err = writeTable(w, me.ClientProperties); err != nil { + return + } + + if err = writeShortstr(w, me.Mechanism); err != nil { + return + } + + if err = writeLongstr(w, me.Response); err != nil { + return + } + + if err = writeShortstr(w, me.Locale); err != nil { + return + } + + return +} + +func (me *connectionStartOk) read(r io.Reader) (err error) { + + if me.ClientProperties, err = readTable(r); err != nil { + return + } + + if me.Mechanism, err = readShortstr(r); err != nil { + return + } + + if me.Response, err = readLongstr(r); err != nil { + return + } + + if me.Locale, err = readShortstr(r); err != nil { + return + } + + return +} + +type connectionSecure struct { + Challenge string +} + +func (me *connectionSecure) id() (uint16, uint16) { + return 10, 20 +} + +func (me *connectionSecure) wait() bool { + return true +} + +func (me *connectionSecure) write(w io.Writer) (err error) { + + if err = writeLongstr(w, me.Challenge); err != nil { + return + } + + return +} + +func (me *connectionSecure) read(r io.Reader) (err error) { + + if me.Challenge, err = readLongstr(r); err != nil { + return + } + + return +} + +type connectionSecureOk struct { + Response string +} + +func (me *connectionSecureOk) id() (uint16, uint16) { + return 10, 21 +} + +func (me *connectionSecureOk) wait() bool { + return true +} + +func (me *connectionSecureOk) write(w io.Writer) (err error) { + + if err = writeLongstr(w, me.Response); err != nil { + return + } + + return +} + +func (me *connectionSecureOk) read(r io.Reader) (err error) { + + if me.Response, err = readLongstr(r); err != nil { + return + } + + return +} + +type connectionTune struct { + ChannelMax uint16 + FrameMax uint32 + Heartbeat uint16 +} + +func (me *connectionTune) id() (uint16, uint16) { + return 10, 30 +} + +func (me *connectionTune) wait() bool { + return true +} + +func (me *connectionTune) write(w io.Writer) (err error) { + + if err = binary.Write(w, binary.BigEndian, me.ChannelMax); err != nil { + return + } + + if err = binary.Write(w, binary.BigEndian, me.FrameMax); err != nil { + return + } + + if err = binary.Write(w, binary.BigEndian, me.Heartbeat); err != nil { + return + } + + return +} + +func (me *connectionTune) read(r io.Reader) (err error) { + + if err = binary.Read(r, binary.BigEndian, &me.ChannelMax); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &me.FrameMax); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &me.Heartbeat); err != nil { + return + } + + return +} + +type connectionTuneOk struct { + ChannelMax uint16 + FrameMax uint32 + Heartbeat uint16 +} + +func (me *connectionTuneOk) id() (uint16, uint16) { + return 10, 31 +} + +func (me *connectionTuneOk) wait() bool { + return true +} + +func (me *connectionTuneOk) write(w io.Writer) (err error) { + + if err = binary.Write(w, binary.BigEndian, me.ChannelMax); err != nil { + return + } + + if err = binary.Write(w, binary.BigEndian, me.FrameMax); err != nil { + return + } + + if err = binary.Write(w, binary.BigEndian, me.Heartbeat); err != nil { + return + } + + return +} + +func (me *connectionTuneOk) read(r io.Reader) (err error) { + + if err = binary.Read(r, binary.BigEndian, &me.ChannelMax); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &me.FrameMax); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &me.Heartbeat); err != nil { + return + } + + return +} + +type connectionOpen struct { + VirtualHost string + reserved1 string + reserved2 bool +} + +func (me *connectionOpen) id() (uint16, uint16) { + return 10, 40 +} + +func (me *connectionOpen) wait() bool { + return true +} + +func (me *connectionOpen) write(w io.Writer) (err error) { + var bits byte + + if err = writeShortstr(w, me.VirtualHost); err != nil { + return + } + if err = writeShortstr(w, me.reserved1); err != nil { + return + } + + if me.reserved2 { + bits |= 1 << 0 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + return +} + +func (me *connectionOpen) read(r io.Reader) (err error) { + var bits byte + + if me.VirtualHost, err = readShortstr(r); err != nil { + return + } + if me.reserved1, err = readShortstr(r); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.reserved2 = (bits&(1<<0) > 0) + + return +} + +type connectionOpenOk struct { + reserved1 string +} + +func (me *connectionOpenOk) id() (uint16, uint16) { + return 10, 41 +} + +func (me *connectionOpenOk) wait() bool { + return true +} + +func (me *connectionOpenOk) write(w io.Writer) (err error) { + + if err = writeShortstr(w, me.reserved1); err != nil { + return + } + + return +} + +func (me *connectionOpenOk) read(r io.Reader) (err error) { + + if me.reserved1, err = readShortstr(r); err != nil { + return + } + + return +} + +type connectionClose struct { + ReplyCode uint16 + ReplyText string + ClassId uint16 + MethodId uint16 +} + +func (me *connectionClose) id() (uint16, uint16) { + return 10, 50 +} + +func (me *connectionClose) wait() bool { + return true +} + +func (me *connectionClose) write(w io.Writer) (err error) { + + if err = binary.Write(w, binary.BigEndian, me.ReplyCode); err != nil { + return + } + + if err = writeShortstr(w, me.ReplyText); err != nil { + return + } + + if err = binary.Write(w, binary.BigEndian, me.ClassId); err != nil { + return + } + if err = binary.Write(w, binary.BigEndian, me.MethodId); err != nil { + return + } + + return +} + +func (me *connectionClose) read(r io.Reader) (err error) { + + if err = binary.Read(r, binary.BigEndian, &me.ReplyCode); err != nil { + return + } + + if me.ReplyText, err = readShortstr(r); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &me.ClassId); err != nil { + return + } + if err = binary.Read(r, binary.BigEndian, &me.MethodId); err != nil { + return + } + + return +} + +type connectionCloseOk struct { +} + +func (me *connectionCloseOk) id() (uint16, uint16) { + return 10, 51 +} + +func (me *connectionCloseOk) wait() bool { + return true +} + +func (me *connectionCloseOk) write(w io.Writer) (err error) { + + return +} + +func (me *connectionCloseOk) read(r io.Reader) (err error) { + + return +} + +type connectionBlocked struct { + Reason string +} + +func (me *connectionBlocked) id() (uint16, uint16) { + return 10, 60 +} + +func (me *connectionBlocked) wait() bool { + return false +} + +func (me *connectionBlocked) write(w io.Writer) (err error) { + + if err = writeShortstr(w, me.Reason); err != nil { + return + } + + return +} + +func (me *connectionBlocked) read(r io.Reader) (err error) { + + if me.Reason, err = readShortstr(r); err != nil { + return + } + + return +} + +type connectionUnblocked struct { +} + +func (me *connectionUnblocked) id() (uint16, uint16) { + return 10, 61 +} + +func (me *connectionUnblocked) wait() bool { + return false +} + +func (me *connectionUnblocked) write(w io.Writer) (err error) { + + return +} + +func (me *connectionUnblocked) read(r io.Reader) (err error) { + + return +} + +type channelOpen struct { + reserved1 string +} + +func (me *channelOpen) id() (uint16, uint16) { + return 20, 10 +} + +func (me *channelOpen) wait() bool { + return true +} + +func (me *channelOpen) write(w io.Writer) (err error) { + + if err = writeShortstr(w, me.reserved1); err != nil { + return + } + + return +} + +func (me *channelOpen) read(r io.Reader) (err error) { + + if me.reserved1, err = readShortstr(r); err != nil { + return + } + + return +} + +type channelOpenOk struct { + reserved1 string +} + +func (me *channelOpenOk) id() (uint16, uint16) { + return 20, 11 +} + +func (me *channelOpenOk) wait() bool { + return true +} + +func (me *channelOpenOk) write(w io.Writer) (err error) { + + if err = writeLongstr(w, me.reserved1); err != nil { + return + } + + return +} + +func (me *channelOpenOk) read(r io.Reader) (err error) { + + if me.reserved1, err = readLongstr(r); err != nil { + return + } + + return +} + +type channelFlow struct { + Active bool +} + +func (me *channelFlow) id() (uint16, uint16) { + return 20, 20 +} + +func (me *channelFlow) wait() bool { + return true +} + +func (me *channelFlow) write(w io.Writer) (err error) { + var bits byte + + if me.Active { + bits |= 1 << 0 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + return +} + +func (me *channelFlow) read(r io.Reader) (err error) { + var bits byte + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.Active = (bits&(1<<0) > 0) + + return +} + +type channelFlowOk struct { + Active bool +} + +func (me *channelFlowOk) id() (uint16, uint16) { + return 20, 21 +} + +func (me *channelFlowOk) wait() bool { + return false +} + +func (me *channelFlowOk) write(w io.Writer) (err error) { + var bits byte + + if me.Active { + bits |= 1 << 0 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + return +} + +func (me *channelFlowOk) read(r io.Reader) (err error) { + var bits byte + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.Active = (bits&(1<<0) > 0) + + return +} + +type channelClose struct { + ReplyCode uint16 + ReplyText string + ClassId uint16 + MethodId uint16 +} + +func (me *channelClose) id() (uint16, uint16) { + return 20, 40 +} + +func (me *channelClose) wait() bool { + return true +} + +func (me *channelClose) write(w io.Writer) (err error) { + + if err = binary.Write(w, binary.BigEndian, me.ReplyCode); err != nil { + return + } + + if err = writeShortstr(w, me.ReplyText); err != nil { + return + } + + if err = binary.Write(w, binary.BigEndian, me.ClassId); err != nil { + return + } + if err = binary.Write(w, binary.BigEndian, me.MethodId); err != nil { + return + } + + return +} + +func (me *channelClose) read(r io.Reader) (err error) { + + if err = binary.Read(r, binary.BigEndian, &me.ReplyCode); err != nil { + return + } + + if me.ReplyText, err = readShortstr(r); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &me.ClassId); err != nil { + return + } + if err = binary.Read(r, binary.BigEndian, &me.MethodId); err != nil { + return + } + + return +} + +type channelCloseOk struct { +} + +func (me *channelCloseOk) id() (uint16, uint16) { + return 20, 41 +} + +func (me *channelCloseOk) wait() bool { + return true +} + +func (me *channelCloseOk) write(w io.Writer) (err error) { + + return +} + +func (me *channelCloseOk) read(r io.Reader) (err error) { + + return +} + +type exchangeDeclare struct { + reserved1 uint16 + Exchange string + Type string + Passive bool + Durable bool + AutoDelete bool + Internal bool + NoWait bool + Arguments Table +} + +func (me *exchangeDeclare) id() (uint16, uint16) { + return 40, 10 +} + +func (me *exchangeDeclare) wait() bool { + return true && !me.NoWait +} + +func (me *exchangeDeclare) write(w io.Writer) (err error) { + var bits byte + + if err = binary.Write(w, binary.BigEndian, me.reserved1); err != nil { + return + } + + if err = writeShortstr(w, me.Exchange); err != nil { + return + } + if err = writeShortstr(w, me.Type); err != nil { + return + } + + if me.Passive { + bits |= 1 << 0 + } + + if me.Durable { + bits |= 1 << 1 + } + + if me.AutoDelete { + bits |= 1 << 2 + } + + if me.Internal { + bits |= 1 << 3 + } + + if me.NoWait { + bits |= 1 << 4 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + if err = writeTable(w, me.Arguments); err != nil { + return + } + + return +} + +func (me *exchangeDeclare) read(r io.Reader) (err error) { + var bits byte + + if err = binary.Read(r, binary.BigEndian, &me.reserved1); err != nil { + return + } + + if me.Exchange, err = readShortstr(r); err != nil { + return + } + if me.Type, err = readShortstr(r); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.Passive = (bits&(1<<0) > 0) + me.Durable = (bits&(1<<1) > 0) + me.AutoDelete = (bits&(1<<2) > 0) + me.Internal = (bits&(1<<3) > 0) + me.NoWait = (bits&(1<<4) > 0) + + if me.Arguments, err = readTable(r); err != nil { + return + } + + return +} + +type exchangeDeclareOk struct { +} + +func (me *exchangeDeclareOk) id() (uint16, uint16) { + return 40, 11 +} + +func (me *exchangeDeclareOk) wait() bool { + return true +} + +func (me *exchangeDeclareOk) write(w io.Writer) (err error) { + + return +} + +func (me *exchangeDeclareOk) read(r io.Reader) (err error) { + + return +} + +type exchangeDelete struct { + reserved1 uint16 + Exchange string + IfUnused bool + NoWait bool +} + +func (me *exchangeDelete) id() (uint16, uint16) { + return 40, 20 +} + +func (me *exchangeDelete) wait() bool { + return true && !me.NoWait +} + +func (me *exchangeDelete) write(w io.Writer) (err error) { + var bits byte + + if err = binary.Write(w, binary.BigEndian, me.reserved1); err != nil { + return + } + + if err = writeShortstr(w, me.Exchange); err != nil { + return + } + + if me.IfUnused { + bits |= 1 << 0 + } + + if me.NoWait { + bits |= 1 << 1 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + return +} + +func (me *exchangeDelete) read(r io.Reader) (err error) { + var bits byte + + if err = binary.Read(r, binary.BigEndian, &me.reserved1); err != nil { + return + } + + if me.Exchange, err = readShortstr(r); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.IfUnused = (bits&(1<<0) > 0) + me.NoWait = (bits&(1<<1) > 0) + + return +} + +type exchangeDeleteOk struct { +} + +func (me *exchangeDeleteOk) id() (uint16, uint16) { + return 40, 21 +} + +func (me *exchangeDeleteOk) wait() bool { + return true +} + +func (me *exchangeDeleteOk) write(w io.Writer) (err error) { + + return +} + +func (me *exchangeDeleteOk) read(r io.Reader) (err error) { + + return +} + +type exchangeBind struct { + reserved1 uint16 + Destination string + Source string + RoutingKey string + NoWait bool + Arguments Table +} + +func (me *exchangeBind) id() (uint16, uint16) { + return 40, 30 +} + +func (me *exchangeBind) wait() bool { + return true && !me.NoWait +} + +func (me *exchangeBind) write(w io.Writer) (err error) { + var bits byte + + if err = binary.Write(w, binary.BigEndian, me.reserved1); err != nil { + return + } + + if err = writeShortstr(w, me.Destination); err != nil { + return + } + if err = writeShortstr(w, me.Source); err != nil { + return + } + if err = writeShortstr(w, me.RoutingKey); err != nil { + return + } + + if me.NoWait { + bits |= 1 << 0 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + if err = writeTable(w, me.Arguments); err != nil { + return + } + + return +} + +func (me *exchangeBind) read(r io.Reader) (err error) { + var bits byte + + if err = binary.Read(r, binary.BigEndian, &me.reserved1); err != nil { + return + } + + if me.Destination, err = readShortstr(r); err != nil { + return + } + if me.Source, err = readShortstr(r); err != nil { + return + } + if me.RoutingKey, err = readShortstr(r); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.NoWait = (bits&(1<<0) > 0) + + if me.Arguments, err = readTable(r); err != nil { + return + } + + return +} + +type exchangeBindOk struct { +} + +func (me *exchangeBindOk) id() (uint16, uint16) { + return 40, 31 +} + +func (me *exchangeBindOk) wait() bool { + return true +} + +func (me *exchangeBindOk) write(w io.Writer) (err error) { + + return +} + +func (me *exchangeBindOk) read(r io.Reader) (err error) { + + return +} + +type exchangeUnbind struct { + reserved1 uint16 + Destination string + Source string + RoutingKey string + NoWait bool + Arguments Table +} + +func (me *exchangeUnbind) id() (uint16, uint16) { + return 40, 40 +} + +func (me *exchangeUnbind) wait() bool { + return true && !me.NoWait +} + +func (me *exchangeUnbind) write(w io.Writer) (err error) { + var bits byte + + if err = binary.Write(w, binary.BigEndian, me.reserved1); err != nil { + return + } + + if err = writeShortstr(w, me.Destination); err != nil { + return + } + if err = writeShortstr(w, me.Source); err != nil { + return + } + if err = writeShortstr(w, me.RoutingKey); err != nil { + return + } + + if me.NoWait { + bits |= 1 << 0 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + if err = writeTable(w, me.Arguments); err != nil { + return + } + + return +} + +func (me *exchangeUnbind) read(r io.Reader) (err error) { + var bits byte + + if err = binary.Read(r, binary.BigEndian, &me.reserved1); err != nil { + return + } + + if me.Destination, err = readShortstr(r); err != nil { + return + } + if me.Source, err = readShortstr(r); err != nil { + return + } + if me.RoutingKey, err = readShortstr(r); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.NoWait = (bits&(1<<0) > 0) + + if me.Arguments, err = readTable(r); err != nil { + return + } + + return +} + +type exchangeUnbindOk struct { +} + +func (me *exchangeUnbindOk) id() (uint16, uint16) { + return 40, 51 +} + +func (me *exchangeUnbindOk) wait() bool { + return true +} + +func (me *exchangeUnbindOk) write(w io.Writer) (err error) { + + return +} + +func (me *exchangeUnbindOk) read(r io.Reader) (err error) { + + return +} + +type queueDeclare struct { + reserved1 uint16 + Queue string + Passive bool + Durable bool + Exclusive bool + AutoDelete bool + NoWait bool + Arguments Table +} + +func (me *queueDeclare) id() (uint16, uint16) { + return 50, 10 +} + +func (me *queueDeclare) wait() bool { + return true && !me.NoWait +} + +func (me *queueDeclare) write(w io.Writer) (err error) { + var bits byte + + if err = binary.Write(w, binary.BigEndian, me.reserved1); err != nil { + return + } + + if err = writeShortstr(w, me.Queue); err != nil { + return + } + + if me.Passive { + bits |= 1 << 0 + } + + if me.Durable { + bits |= 1 << 1 + } + + if me.Exclusive { + bits |= 1 << 2 + } + + if me.AutoDelete { + bits |= 1 << 3 + } + + if me.NoWait { + bits |= 1 << 4 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + if err = writeTable(w, me.Arguments); err != nil { + return + } + + return +} + +func (me *queueDeclare) read(r io.Reader) (err error) { + var bits byte + + if err = binary.Read(r, binary.BigEndian, &me.reserved1); err != nil { + return + } + + if me.Queue, err = readShortstr(r); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.Passive = (bits&(1<<0) > 0) + me.Durable = (bits&(1<<1) > 0) + me.Exclusive = (bits&(1<<2) > 0) + me.AutoDelete = (bits&(1<<3) > 0) + me.NoWait = (bits&(1<<4) > 0) + + if me.Arguments, err = readTable(r); err != nil { + return + } + + return +} + +type queueDeclareOk struct { + Queue string + MessageCount uint32 + ConsumerCount uint32 +} + +func (me *queueDeclareOk) id() (uint16, uint16) { + return 50, 11 +} + +func (me *queueDeclareOk) wait() bool { + return true +} + +func (me *queueDeclareOk) write(w io.Writer) (err error) { + + if err = writeShortstr(w, me.Queue); err != nil { + return + } + + if err = binary.Write(w, binary.BigEndian, me.MessageCount); err != nil { + return + } + if err = binary.Write(w, binary.BigEndian, me.ConsumerCount); err != nil { + return + } + + return +} + +func (me *queueDeclareOk) read(r io.Reader) (err error) { + + if me.Queue, err = readShortstr(r); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &me.MessageCount); err != nil { + return + } + if err = binary.Read(r, binary.BigEndian, &me.ConsumerCount); err != nil { + return + } + + return +} + +type queueBind struct { + reserved1 uint16 + Queue string + Exchange string + RoutingKey string + NoWait bool + Arguments Table +} + +func (me *queueBind) id() (uint16, uint16) { + return 50, 20 +} + +func (me *queueBind) wait() bool { + return true && !me.NoWait +} + +func (me *queueBind) write(w io.Writer) (err error) { + var bits byte + + if err = binary.Write(w, binary.BigEndian, me.reserved1); err != nil { + return + } + + if err = writeShortstr(w, me.Queue); err != nil { + return + } + if err = writeShortstr(w, me.Exchange); err != nil { + return + } + if err = writeShortstr(w, me.RoutingKey); err != nil { + return + } + + if me.NoWait { + bits |= 1 << 0 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + if err = writeTable(w, me.Arguments); err != nil { + return + } + + return +} + +func (me *queueBind) read(r io.Reader) (err error) { + var bits byte + + if err = binary.Read(r, binary.BigEndian, &me.reserved1); err != nil { + return + } + + if me.Queue, err = readShortstr(r); err != nil { + return + } + if me.Exchange, err = readShortstr(r); err != nil { + return + } + if me.RoutingKey, err = readShortstr(r); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.NoWait = (bits&(1<<0) > 0) + + if me.Arguments, err = readTable(r); err != nil { + return + } + + return +} + +type queueBindOk struct { +} + +func (me *queueBindOk) id() (uint16, uint16) { + return 50, 21 +} + +func (me *queueBindOk) wait() bool { + return true +} + +func (me *queueBindOk) write(w io.Writer) (err error) { + + return +} + +func (me *queueBindOk) read(r io.Reader) (err error) { + + return +} + +type queueUnbind struct { + reserved1 uint16 + Queue string + Exchange string + RoutingKey string + Arguments Table +} + +func (me *queueUnbind) id() (uint16, uint16) { + return 50, 50 +} + +func (me *queueUnbind) wait() bool { + return true +} + +func (me *queueUnbind) write(w io.Writer) (err error) { + + if err = binary.Write(w, binary.BigEndian, me.reserved1); err != nil { + return + } + + if err = writeShortstr(w, me.Queue); err != nil { + return + } + if err = writeShortstr(w, me.Exchange); err != nil { + return + } + if err = writeShortstr(w, me.RoutingKey); err != nil { + return + } + + if err = writeTable(w, me.Arguments); err != nil { + return + } + + return +} + +func (me *queueUnbind) read(r io.Reader) (err error) { + + if err = binary.Read(r, binary.BigEndian, &me.reserved1); err != nil { + return + } + + if me.Queue, err = readShortstr(r); err != nil { + return + } + if me.Exchange, err = readShortstr(r); err != nil { + return + } + if me.RoutingKey, err = readShortstr(r); err != nil { + return + } + + if me.Arguments, err = readTable(r); err != nil { + return + } + + return +} + +type queueUnbindOk struct { +} + +func (me *queueUnbindOk) id() (uint16, uint16) { + return 50, 51 +} + +func (me *queueUnbindOk) wait() bool { + return true +} + +func (me *queueUnbindOk) write(w io.Writer) (err error) { + + return +} + +func (me *queueUnbindOk) read(r io.Reader) (err error) { + + return +} + +type queuePurge struct { + reserved1 uint16 + Queue string + NoWait bool +} + +func (me *queuePurge) id() (uint16, uint16) { + return 50, 30 +} + +func (me *queuePurge) wait() bool { + return true && !me.NoWait +} + +func (me *queuePurge) write(w io.Writer) (err error) { + var bits byte + + if err = binary.Write(w, binary.BigEndian, me.reserved1); err != nil { + return + } + + if err = writeShortstr(w, me.Queue); err != nil { + return + } + + if me.NoWait { + bits |= 1 << 0 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + return +} + +func (me *queuePurge) read(r io.Reader) (err error) { + var bits byte + + if err = binary.Read(r, binary.BigEndian, &me.reserved1); err != nil { + return + } + + if me.Queue, err = readShortstr(r); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.NoWait = (bits&(1<<0) > 0) + + return +} + +type queuePurgeOk struct { + MessageCount uint32 +} + +func (me *queuePurgeOk) id() (uint16, uint16) { + return 50, 31 +} + +func (me *queuePurgeOk) wait() bool { + return true +} + +func (me *queuePurgeOk) write(w io.Writer) (err error) { + + if err = binary.Write(w, binary.BigEndian, me.MessageCount); err != nil { + return + } + + return +} + +func (me *queuePurgeOk) read(r io.Reader) (err error) { + + if err = binary.Read(r, binary.BigEndian, &me.MessageCount); err != nil { + return + } + + return +} + +type queueDelete struct { + reserved1 uint16 + Queue string + IfUnused bool + IfEmpty bool + NoWait bool +} + +func (me *queueDelete) id() (uint16, uint16) { + return 50, 40 +} + +func (me *queueDelete) wait() bool { + return true && !me.NoWait +} + +func (me *queueDelete) write(w io.Writer) (err error) { + var bits byte + + if err = binary.Write(w, binary.BigEndian, me.reserved1); err != nil { + return + } + + if err = writeShortstr(w, me.Queue); err != nil { + return + } + + if me.IfUnused { + bits |= 1 << 0 + } + + if me.IfEmpty { + bits |= 1 << 1 + } + + if me.NoWait { + bits |= 1 << 2 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + return +} + +func (me *queueDelete) read(r io.Reader) (err error) { + var bits byte + + if err = binary.Read(r, binary.BigEndian, &me.reserved1); err != nil { + return + } + + if me.Queue, err = readShortstr(r); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.IfUnused = (bits&(1<<0) > 0) + me.IfEmpty = (bits&(1<<1) > 0) + me.NoWait = (bits&(1<<2) > 0) + + return +} + +type queueDeleteOk struct { + MessageCount uint32 +} + +func (me *queueDeleteOk) id() (uint16, uint16) { + return 50, 41 +} + +func (me *queueDeleteOk) wait() bool { + return true +} + +func (me *queueDeleteOk) write(w io.Writer) (err error) { + + if err = binary.Write(w, binary.BigEndian, me.MessageCount); err != nil { + return + } + + return +} + +func (me *queueDeleteOk) read(r io.Reader) (err error) { + + if err = binary.Read(r, binary.BigEndian, &me.MessageCount); err != nil { + return + } + + return +} + +type basicQos struct { + PrefetchSize uint32 + PrefetchCount uint16 + Global bool +} + +func (me *basicQos) id() (uint16, uint16) { + return 60, 10 +} + +func (me *basicQos) wait() bool { + return true +} + +func (me *basicQos) write(w io.Writer) (err error) { + var bits byte + + if err = binary.Write(w, binary.BigEndian, me.PrefetchSize); err != nil { + return + } + + if err = binary.Write(w, binary.BigEndian, me.PrefetchCount); err != nil { + return + } + + if me.Global { + bits |= 1 << 0 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + return +} + +func (me *basicQos) read(r io.Reader) (err error) { + var bits byte + + if err = binary.Read(r, binary.BigEndian, &me.PrefetchSize); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &me.PrefetchCount); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.Global = (bits&(1<<0) > 0) + + return +} + +type basicQosOk struct { +} + +func (me *basicQosOk) id() (uint16, uint16) { + return 60, 11 +} + +func (me *basicQosOk) wait() bool { + return true +} + +func (me *basicQosOk) write(w io.Writer) (err error) { + + return +} + +func (me *basicQosOk) read(r io.Reader) (err error) { + + return +} + +type basicConsume struct { + reserved1 uint16 + Queue string + ConsumerTag string + NoLocal bool + NoAck bool + Exclusive bool + NoWait bool + Arguments Table +} + +func (me *basicConsume) id() (uint16, uint16) { + return 60, 20 +} + +func (me *basicConsume) wait() bool { + return true && !me.NoWait +} + +func (me *basicConsume) write(w io.Writer) (err error) { + var bits byte + + if err = binary.Write(w, binary.BigEndian, me.reserved1); err != nil { + return + } + + if err = writeShortstr(w, me.Queue); err != nil { + return + } + if err = writeShortstr(w, me.ConsumerTag); err != nil { + return + } + + if me.NoLocal { + bits |= 1 << 0 + } + + if me.NoAck { + bits |= 1 << 1 + } + + if me.Exclusive { + bits |= 1 << 2 + } + + if me.NoWait { + bits |= 1 << 3 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + if err = writeTable(w, me.Arguments); err != nil { + return + } + + return +} + +func (me *basicConsume) read(r io.Reader) (err error) { + var bits byte + + if err = binary.Read(r, binary.BigEndian, &me.reserved1); err != nil { + return + } + + if me.Queue, err = readShortstr(r); err != nil { + return + } + if me.ConsumerTag, err = readShortstr(r); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.NoLocal = (bits&(1<<0) > 0) + me.NoAck = (bits&(1<<1) > 0) + me.Exclusive = (bits&(1<<2) > 0) + me.NoWait = (bits&(1<<3) > 0) + + if me.Arguments, err = readTable(r); err != nil { + return + } + + return +} + +type basicConsumeOk struct { + ConsumerTag string +} + +func (me *basicConsumeOk) id() (uint16, uint16) { + return 60, 21 +} + +func (me *basicConsumeOk) wait() bool { + return true +} + +func (me *basicConsumeOk) write(w io.Writer) (err error) { + + if err = writeShortstr(w, me.ConsumerTag); err != nil { + return + } + + return +} + +func (me *basicConsumeOk) read(r io.Reader) (err error) { + + if me.ConsumerTag, err = readShortstr(r); err != nil { + return + } + + return +} + +type basicCancel struct { + ConsumerTag string + NoWait bool +} + +func (me *basicCancel) id() (uint16, uint16) { + return 60, 30 +} + +func (me *basicCancel) wait() bool { + return true && !me.NoWait +} + +func (me *basicCancel) write(w io.Writer) (err error) { + var bits byte + + if err = writeShortstr(w, me.ConsumerTag); err != nil { + return + } + + if me.NoWait { + bits |= 1 << 0 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + return +} + +func (me *basicCancel) read(r io.Reader) (err error) { + var bits byte + + if me.ConsumerTag, err = readShortstr(r); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.NoWait = (bits&(1<<0) > 0) + + return +} + +type basicCancelOk struct { + ConsumerTag string +} + +func (me *basicCancelOk) id() (uint16, uint16) { + return 60, 31 +} + +func (me *basicCancelOk) wait() bool { + return true +} + +func (me *basicCancelOk) write(w io.Writer) (err error) { + + if err = writeShortstr(w, me.ConsumerTag); err != nil { + return + } + + return +} + +func (me *basicCancelOk) read(r io.Reader) (err error) { + + if me.ConsumerTag, err = readShortstr(r); err != nil { + return + } + + return +} + +type basicPublish struct { + reserved1 uint16 + Exchange string + RoutingKey string + Mandatory bool + Immediate bool + Properties properties + Body []byte +} + +func (me *basicPublish) id() (uint16, uint16) { + return 60, 40 +} + +func (me *basicPublish) wait() bool { + return false +} + +func (me *basicPublish) getContent() (properties, []byte) { + return me.Properties, me.Body +} + +func (me *basicPublish) setContent(props properties, body []byte) { + me.Properties, me.Body = props, body +} + +func (me *basicPublish) write(w io.Writer) (err error) { + var bits byte + + if err = binary.Write(w, binary.BigEndian, me.reserved1); err != nil { + return + } + + if err = writeShortstr(w, me.Exchange); err != nil { + return + } + if err = writeShortstr(w, me.RoutingKey); err != nil { + return + } + + if me.Mandatory { + bits |= 1 << 0 + } + + if me.Immediate { + bits |= 1 << 1 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + return +} + +func (me *basicPublish) read(r io.Reader) (err error) { + var bits byte + + if err = binary.Read(r, binary.BigEndian, &me.reserved1); err != nil { + return + } + + if me.Exchange, err = readShortstr(r); err != nil { + return + } + if me.RoutingKey, err = readShortstr(r); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.Mandatory = (bits&(1<<0) > 0) + me.Immediate = (bits&(1<<1) > 0) + + return +} + +type basicReturn struct { + ReplyCode uint16 + ReplyText string + Exchange string + RoutingKey string + Properties properties + Body []byte +} + +func (me *basicReturn) id() (uint16, uint16) { + return 60, 50 +} + +func (me *basicReturn) wait() bool { + return false +} + +func (me *basicReturn) getContent() (properties, []byte) { + return me.Properties, me.Body +} + +func (me *basicReturn) setContent(props properties, body []byte) { + me.Properties, me.Body = props, body +} + +func (me *basicReturn) write(w io.Writer) (err error) { + + if err = binary.Write(w, binary.BigEndian, me.ReplyCode); err != nil { + return + } + + if err = writeShortstr(w, me.ReplyText); err != nil { + return + } + if err = writeShortstr(w, me.Exchange); err != nil { + return + } + if err = writeShortstr(w, me.RoutingKey); err != nil { + return + } + + return +} + +func (me *basicReturn) read(r io.Reader) (err error) { + + if err = binary.Read(r, binary.BigEndian, &me.ReplyCode); err != nil { + return + } + + if me.ReplyText, err = readShortstr(r); err != nil { + return + } + if me.Exchange, err = readShortstr(r); err != nil { + return + } + if me.RoutingKey, err = readShortstr(r); err != nil { + return + } + + return +} + +type basicDeliver struct { + ConsumerTag string + DeliveryTag uint64 + Redelivered bool + Exchange string + RoutingKey string + Properties properties + Body []byte +} + +func (me *basicDeliver) id() (uint16, uint16) { + return 60, 60 +} + +func (me *basicDeliver) wait() bool { + return false +} + +func (me *basicDeliver) getContent() (properties, []byte) { + return me.Properties, me.Body +} + +func (me *basicDeliver) setContent(props properties, body []byte) { + me.Properties, me.Body = props, body +} + +func (me *basicDeliver) write(w io.Writer) (err error) { + var bits byte + + if err = writeShortstr(w, me.ConsumerTag); err != nil { + return + } + + if err = binary.Write(w, binary.BigEndian, me.DeliveryTag); err != nil { + return + } + + if me.Redelivered { + bits |= 1 << 0 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + if err = writeShortstr(w, me.Exchange); err != nil { + return + } + if err = writeShortstr(w, me.RoutingKey); err != nil { + return + } + + return +} + +func (me *basicDeliver) read(r io.Reader) (err error) { + var bits byte + + if me.ConsumerTag, err = readShortstr(r); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &me.DeliveryTag); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.Redelivered = (bits&(1<<0) > 0) + + if me.Exchange, err = readShortstr(r); err != nil { + return + } + if me.RoutingKey, err = readShortstr(r); err != nil { + return + } + + return +} + +type basicGet struct { + reserved1 uint16 + Queue string + NoAck bool +} + +func (me *basicGet) id() (uint16, uint16) { + return 60, 70 +} + +func (me *basicGet) wait() bool { + return true +} + +func (me *basicGet) write(w io.Writer) (err error) { + var bits byte + + if err = binary.Write(w, binary.BigEndian, me.reserved1); err != nil { + return + } + + if err = writeShortstr(w, me.Queue); err != nil { + return + } + + if me.NoAck { + bits |= 1 << 0 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + return +} + +func (me *basicGet) read(r io.Reader) (err error) { + var bits byte + + if err = binary.Read(r, binary.BigEndian, &me.reserved1); err != nil { + return + } + + if me.Queue, err = readShortstr(r); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.NoAck = (bits&(1<<0) > 0) + + return +} + +type basicGetOk struct { + DeliveryTag uint64 + Redelivered bool + Exchange string + RoutingKey string + MessageCount uint32 + Properties properties + Body []byte +} + +func (me *basicGetOk) id() (uint16, uint16) { + return 60, 71 +} + +func (me *basicGetOk) wait() bool { + return true +} + +func (me *basicGetOk) getContent() (properties, []byte) { + return me.Properties, me.Body +} + +func (me *basicGetOk) setContent(props properties, body []byte) { + me.Properties, me.Body = props, body +} + +func (me *basicGetOk) write(w io.Writer) (err error) { + var bits byte + + if err = binary.Write(w, binary.BigEndian, me.DeliveryTag); err != nil { + return + } + + if me.Redelivered { + bits |= 1 << 0 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + if err = writeShortstr(w, me.Exchange); err != nil { + return + } + if err = writeShortstr(w, me.RoutingKey); err != nil { + return + } + + if err = binary.Write(w, binary.BigEndian, me.MessageCount); err != nil { + return + } + + return +} + +func (me *basicGetOk) read(r io.Reader) (err error) { + var bits byte + + if err = binary.Read(r, binary.BigEndian, &me.DeliveryTag); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.Redelivered = (bits&(1<<0) > 0) + + if me.Exchange, err = readShortstr(r); err != nil { + return + } + if me.RoutingKey, err = readShortstr(r); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &me.MessageCount); err != nil { + return + } + + return +} + +type basicGetEmpty struct { + reserved1 string +} + +func (me *basicGetEmpty) id() (uint16, uint16) { + return 60, 72 +} + +func (me *basicGetEmpty) wait() bool { + return true +} + +func (me *basicGetEmpty) write(w io.Writer) (err error) { + + if err = writeShortstr(w, me.reserved1); err != nil { + return + } + + return +} + +func (me *basicGetEmpty) read(r io.Reader) (err error) { + + if me.reserved1, err = readShortstr(r); err != nil { + return + } + + return +} + +type basicAck struct { + DeliveryTag uint64 + Multiple bool +} + +func (me *basicAck) id() (uint16, uint16) { + return 60, 80 +} + +func (me *basicAck) wait() bool { + return false +} + +func (me *basicAck) write(w io.Writer) (err error) { + var bits byte + + if err = binary.Write(w, binary.BigEndian, me.DeliveryTag); err != nil { + return + } + + if me.Multiple { + bits |= 1 << 0 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + return +} + +func (me *basicAck) read(r io.Reader) (err error) { + var bits byte + + if err = binary.Read(r, binary.BigEndian, &me.DeliveryTag); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.Multiple = (bits&(1<<0) > 0) + + return +} + +type basicReject struct { + DeliveryTag uint64 + Requeue bool +} + +func (me *basicReject) id() (uint16, uint16) { + return 60, 90 +} + +func (me *basicReject) wait() bool { + return false +} + +func (me *basicReject) write(w io.Writer) (err error) { + var bits byte + + if err = binary.Write(w, binary.BigEndian, me.DeliveryTag); err != nil { + return + } + + if me.Requeue { + bits |= 1 << 0 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + return +} + +func (me *basicReject) read(r io.Reader) (err error) { + var bits byte + + if err = binary.Read(r, binary.BigEndian, &me.DeliveryTag); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.Requeue = (bits&(1<<0) > 0) + + return +} + +type basicRecoverAsync struct { + Requeue bool +} + +func (me *basicRecoverAsync) id() (uint16, uint16) { + return 60, 100 +} + +func (me *basicRecoverAsync) wait() bool { + return false +} + +func (me *basicRecoverAsync) write(w io.Writer) (err error) { + var bits byte + + if me.Requeue { + bits |= 1 << 0 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + return +} + +func (me *basicRecoverAsync) read(r io.Reader) (err error) { + var bits byte + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.Requeue = (bits&(1<<0) > 0) + + return +} + +type basicRecover struct { + Requeue bool +} + +func (me *basicRecover) id() (uint16, uint16) { + return 60, 110 +} + +func (me *basicRecover) wait() bool { + return true +} + +func (me *basicRecover) write(w io.Writer) (err error) { + var bits byte + + if me.Requeue { + bits |= 1 << 0 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + return +} + +func (me *basicRecover) read(r io.Reader) (err error) { + var bits byte + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.Requeue = (bits&(1<<0) > 0) + + return +} + +type basicRecoverOk struct { +} + +func (me *basicRecoverOk) id() (uint16, uint16) { + return 60, 111 +} + +func (me *basicRecoverOk) wait() bool { + return true +} + +func (me *basicRecoverOk) write(w io.Writer) (err error) { + + return +} + +func (me *basicRecoverOk) read(r io.Reader) (err error) { + + return +} + +type basicNack struct { + DeliveryTag uint64 + Multiple bool + Requeue bool +} + +func (me *basicNack) id() (uint16, uint16) { + return 60, 120 +} + +func (me *basicNack) wait() bool { + return false +} + +func (me *basicNack) write(w io.Writer) (err error) { + var bits byte + + if err = binary.Write(w, binary.BigEndian, me.DeliveryTag); err != nil { + return + } + + if me.Multiple { + bits |= 1 << 0 + } + + if me.Requeue { + bits |= 1 << 1 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + return +} + +func (me *basicNack) read(r io.Reader) (err error) { + var bits byte + + if err = binary.Read(r, binary.BigEndian, &me.DeliveryTag); err != nil { + return + } + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.Multiple = (bits&(1<<0) > 0) + me.Requeue = (bits&(1<<1) > 0) + + return +} + +type txSelect struct { +} + +func (me *txSelect) id() (uint16, uint16) { + return 90, 10 +} + +func (me *txSelect) wait() bool { + return true +} + +func (me *txSelect) write(w io.Writer) (err error) { + + return +} + +func (me *txSelect) read(r io.Reader) (err error) { + + return +} + +type txSelectOk struct { +} + +func (me *txSelectOk) id() (uint16, uint16) { + return 90, 11 +} + +func (me *txSelectOk) wait() bool { + return true +} + +func (me *txSelectOk) write(w io.Writer) (err error) { + + return +} + +func (me *txSelectOk) read(r io.Reader) (err error) { + + return +} + +type txCommit struct { +} + +func (me *txCommit) id() (uint16, uint16) { + return 90, 20 +} + +func (me *txCommit) wait() bool { + return true +} + +func (me *txCommit) write(w io.Writer) (err error) { + + return +} + +func (me *txCommit) read(r io.Reader) (err error) { + + return +} + +type txCommitOk struct { +} + +func (me *txCommitOk) id() (uint16, uint16) { + return 90, 21 +} + +func (me *txCommitOk) wait() bool { + return true +} + +func (me *txCommitOk) write(w io.Writer) (err error) { + + return +} + +func (me *txCommitOk) read(r io.Reader) (err error) { + + return +} + +type txRollback struct { +} + +func (me *txRollback) id() (uint16, uint16) { + return 90, 30 +} + +func (me *txRollback) wait() bool { + return true +} + +func (me *txRollback) write(w io.Writer) (err error) { + + return +} + +func (me *txRollback) read(r io.Reader) (err error) { + + return +} + +type txRollbackOk struct { +} + +func (me *txRollbackOk) id() (uint16, uint16) { + return 90, 31 +} + +func (me *txRollbackOk) wait() bool { + return true +} + +func (me *txRollbackOk) write(w io.Writer) (err error) { + + return +} + +func (me *txRollbackOk) read(r io.Reader) (err error) { + + return +} + +type confirmSelect struct { + Nowait bool +} + +func (me *confirmSelect) id() (uint16, uint16) { + return 85, 10 +} + +func (me *confirmSelect) wait() bool { + return true +} + +func (me *confirmSelect) write(w io.Writer) (err error) { + var bits byte + + if me.Nowait { + bits |= 1 << 0 + } + + if err = binary.Write(w, binary.BigEndian, bits); err != nil { + return + } + + return +} + +func (me *confirmSelect) read(r io.Reader) (err error) { + var bits byte + + if err = binary.Read(r, binary.BigEndian, &bits); err != nil { + return + } + me.Nowait = (bits&(1<<0) > 0) + + return +} + +type confirmSelectOk struct { +} + +func (me *confirmSelectOk) id() (uint16, uint16) { + return 85, 11 +} + +func (me *confirmSelectOk) wait() bool { + return true +} + +func (me *confirmSelectOk) write(w io.Writer) (err error) { + + return +} + +func (me *confirmSelectOk) read(r io.Reader) (err error) { + + return +} + +func (me *reader) parseMethodFrame(channel uint16, size uint32) (f frame, err error) { + mf := &methodFrame{ + ChannelId: channel, + } + + if err = binary.Read(me.r, binary.BigEndian, &mf.ClassId); err != nil { + return + } + + if err = binary.Read(me.r, binary.BigEndian, &mf.MethodId); err != nil { + return + } + + switch mf.ClassId { + + case 10: // connection + switch mf.MethodId { + + case 10: // connection start + //fmt.Println("NextMethod: class:10 method:10") + method := &connectionStart{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 11: // connection start-ok + //fmt.Println("NextMethod: class:10 method:11") + method := &connectionStartOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 20: // connection secure + //fmt.Println("NextMethod: class:10 method:20") + method := &connectionSecure{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 21: // connection secure-ok + //fmt.Println("NextMethod: class:10 method:21") + method := &connectionSecureOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 30: // connection tune + //fmt.Println("NextMethod: class:10 method:30") + method := &connectionTune{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 31: // connection tune-ok + //fmt.Println("NextMethod: class:10 method:31") + method := &connectionTuneOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 40: // connection open + //fmt.Println("NextMethod: class:10 method:40") + method := &connectionOpen{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 41: // connection open-ok + //fmt.Println("NextMethod: class:10 method:41") + method := &connectionOpenOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 50: // connection close + //fmt.Println("NextMethod: class:10 method:50") + method := &connectionClose{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 51: // connection close-ok + //fmt.Println("NextMethod: class:10 method:51") + method := &connectionCloseOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 60: // connection blocked + //fmt.Println("NextMethod: class:10 method:60") + method := &connectionBlocked{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 61: // connection unblocked + //fmt.Println("NextMethod: class:10 method:61") + method := &connectionUnblocked{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + default: + return nil, fmt.Errorf("Bad method frame, unknown method %d for class %d", mf.MethodId, mf.ClassId) + } + + case 20: // channel + switch mf.MethodId { + + case 10: // channel open + //fmt.Println("NextMethod: class:20 method:10") + method := &channelOpen{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 11: // channel open-ok + //fmt.Println("NextMethod: class:20 method:11") + method := &channelOpenOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 20: // channel flow + //fmt.Println("NextMethod: class:20 method:20") + method := &channelFlow{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 21: // channel flow-ok + //fmt.Println("NextMethod: class:20 method:21") + method := &channelFlowOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 40: // channel close + //fmt.Println("NextMethod: class:20 method:40") + method := &channelClose{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 41: // channel close-ok + //fmt.Println("NextMethod: class:20 method:41") + method := &channelCloseOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + default: + return nil, fmt.Errorf("Bad method frame, unknown method %d for class %d", mf.MethodId, mf.ClassId) + } + + case 40: // exchange + switch mf.MethodId { + + case 10: // exchange declare + //fmt.Println("NextMethod: class:40 method:10") + method := &exchangeDeclare{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 11: // exchange declare-ok + //fmt.Println("NextMethod: class:40 method:11") + method := &exchangeDeclareOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 20: // exchange delete + //fmt.Println("NextMethod: class:40 method:20") + method := &exchangeDelete{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 21: // exchange delete-ok + //fmt.Println("NextMethod: class:40 method:21") + method := &exchangeDeleteOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 30: // exchange bind + //fmt.Println("NextMethod: class:40 method:30") + method := &exchangeBind{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 31: // exchange bind-ok + //fmt.Println("NextMethod: class:40 method:31") + method := &exchangeBindOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 40: // exchange unbind + //fmt.Println("NextMethod: class:40 method:40") + method := &exchangeUnbind{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 51: // exchange unbind-ok + //fmt.Println("NextMethod: class:40 method:51") + method := &exchangeUnbindOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + default: + return nil, fmt.Errorf("Bad method frame, unknown method %d for class %d", mf.MethodId, mf.ClassId) + } + + case 50: // queue + switch mf.MethodId { + + case 10: // queue declare + //fmt.Println("NextMethod: class:50 method:10") + method := &queueDeclare{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 11: // queue declare-ok + //fmt.Println("NextMethod: class:50 method:11") + method := &queueDeclareOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 20: // queue bind + //fmt.Println("NextMethod: class:50 method:20") + method := &queueBind{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 21: // queue bind-ok + //fmt.Println("NextMethod: class:50 method:21") + method := &queueBindOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 50: // queue unbind + //fmt.Println("NextMethod: class:50 method:50") + method := &queueUnbind{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 51: // queue unbind-ok + //fmt.Println("NextMethod: class:50 method:51") + method := &queueUnbindOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 30: // queue purge + //fmt.Println("NextMethod: class:50 method:30") + method := &queuePurge{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 31: // queue purge-ok + //fmt.Println("NextMethod: class:50 method:31") + method := &queuePurgeOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 40: // queue delete + //fmt.Println("NextMethod: class:50 method:40") + method := &queueDelete{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 41: // queue delete-ok + //fmt.Println("NextMethod: class:50 method:41") + method := &queueDeleteOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + default: + return nil, fmt.Errorf("Bad method frame, unknown method %d for class %d", mf.MethodId, mf.ClassId) + } + + case 60: // basic + switch mf.MethodId { + + case 10: // basic qos + //fmt.Println("NextMethod: class:60 method:10") + method := &basicQos{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 11: // basic qos-ok + //fmt.Println("NextMethod: class:60 method:11") + method := &basicQosOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 20: // basic consume + //fmt.Println("NextMethod: class:60 method:20") + method := &basicConsume{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 21: // basic consume-ok + //fmt.Println("NextMethod: class:60 method:21") + method := &basicConsumeOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 30: // basic cancel + //fmt.Println("NextMethod: class:60 method:30") + method := &basicCancel{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 31: // basic cancel-ok + //fmt.Println("NextMethod: class:60 method:31") + method := &basicCancelOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 40: // basic publish + //fmt.Println("NextMethod: class:60 method:40") + method := &basicPublish{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 50: // basic return + //fmt.Println("NextMethod: class:60 method:50") + method := &basicReturn{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 60: // basic deliver + //fmt.Println("NextMethod: class:60 method:60") + method := &basicDeliver{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 70: // basic get + //fmt.Println("NextMethod: class:60 method:70") + method := &basicGet{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 71: // basic get-ok + //fmt.Println("NextMethod: class:60 method:71") + method := &basicGetOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 72: // basic get-empty + //fmt.Println("NextMethod: class:60 method:72") + method := &basicGetEmpty{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 80: // basic ack + //fmt.Println("NextMethod: class:60 method:80") + method := &basicAck{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 90: // basic reject + //fmt.Println("NextMethod: class:60 method:90") + method := &basicReject{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 100: // basic recover-async + //fmt.Println("NextMethod: class:60 method:100") + method := &basicRecoverAsync{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 110: // basic recover + //fmt.Println("NextMethod: class:60 method:110") + method := &basicRecover{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 111: // basic recover-ok + //fmt.Println("NextMethod: class:60 method:111") + method := &basicRecoverOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 120: // basic nack + //fmt.Println("NextMethod: class:60 method:120") + method := &basicNack{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + default: + return nil, fmt.Errorf("Bad method frame, unknown method %d for class %d", mf.MethodId, mf.ClassId) + } + + case 90: // tx + switch mf.MethodId { + + case 10: // tx select + //fmt.Println("NextMethod: class:90 method:10") + method := &txSelect{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 11: // tx select-ok + //fmt.Println("NextMethod: class:90 method:11") + method := &txSelectOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 20: // tx commit + //fmt.Println("NextMethod: class:90 method:20") + method := &txCommit{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 21: // tx commit-ok + //fmt.Println("NextMethod: class:90 method:21") + method := &txCommitOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 30: // tx rollback + //fmt.Println("NextMethod: class:90 method:30") + method := &txRollback{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 31: // tx rollback-ok + //fmt.Println("NextMethod: class:90 method:31") + method := &txRollbackOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + default: + return nil, fmt.Errorf("Bad method frame, unknown method %d for class %d", mf.MethodId, mf.ClassId) + } + + case 85: // confirm + switch mf.MethodId { + + case 10: // confirm select + //fmt.Println("NextMethod: class:85 method:10") + method := &confirmSelect{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + case 11: // confirm select-ok + //fmt.Println("NextMethod: class:85 method:11") + method := &confirmSelectOk{} + if err = method.read(me.r); err != nil { + return + } + mf.Method = method + + default: + return nil, fmt.Errorf("Bad method frame, unknown method %d for class %d", mf.MethodId, mf.ClassId) + } + + default: + return nil, fmt.Errorf("Bad method frame, unknown class %d", mf.ClassId) + } + + return mf, nil +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/tls_test.go b/Godeps/_workspace/src/github.com/streadway/amqp/tls_test.go new file mode 100644 index 000000000..a0795b641 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/tls_test.go @@ -0,0 +1,218 @@ +package amqp_test + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "github.com/streadway/amqp" + "io" + "net" + "testing" + "time" +) + +type tlsServer struct { + net.Listener + URL string + Config *tls.Config + Header chan []byte +} + +// Captures the header for each accepted connection +func (s *tlsServer) Serve() { + for { + c, err := s.Accept() + if err != nil { + return + } + + header := make([]byte, 4) + io.ReadFull(c, header) + s.Header <- header + c.Write([]byte{'A', 'M', 'Q', 'P', 0, 0, 0, 0}) + c.Close() + } +} + +func tlsConfig() *tls.Config { + cfg := new(tls.Config) + + cfg.ClientCAs = x509.NewCertPool() + cfg.ClientCAs.AppendCertsFromPEM([]byte(caCert)) + + cert, err := tls.X509KeyPair([]byte(serverCert), []byte(serverKey)) + if err != nil { + panic(err) + } + + cfg.Certificates = append(cfg.Certificates, cert) + cfg.ClientAuth = tls.RequireAndVerifyClientCert + + return cfg +} + +func startTlsServer() tlsServer { + cfg := tlsConfig() + + l, err := tls.Listen("tcp", "127.0.0.1:0", cfg) + if err != nil { + panic(err) + } + + s := tlsServer{ + Listener: l, + Config: cfg, + URL: fmt.Sprintf("amqps://%s/", l.Addr().String()), + Header: make(chan []byte, 1), + } + + go s.Serve() + return s +} + +// Tests that the server has handshaked the connection and seen the client +// protocol announcement. Does not nest that the connection.open is successful. +func TestTLSHandshake(t *testing.T) { + srv := startTlsServer() + defer srv.Close() + + cfg := new(tls.Config) + cfg.RootCAs = x509.NewCertPool() + cfg.RootCAs.AppendCertsFromPEM([]byte(caCert)) + + cert, _ := tls.X509KeyPair([]byte(clientCert), []byte(clientKey)) + cfg.Certificates = append(cfg.Certificates, cert) + + _, err := amqp.DialTLS(srv.URL, cfg) + + select { + case <-time.After(10 * time.Millisecond): + t.Fatalf("did not succeed to handshake the TLS connection after 10ms") + case header := <-srv.Header: + if string(header) != "AMQP" { + t.Fatalf("expected to handshake a TLS connection, got err: %v", err) + } + } +} + +const caCert = ` +-----BEGIN CERTIFICATE----- +MIICxjCCAa6gAwIBAgIJANWuMWMQSxvdMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV +BAMTCE15VGVzdENBMB4XDTE0MDEyNzE5NTIyMloXDTI0MDEyNTE5NTIyMlowEzER +MA8GA1UEAxMITXlUZXN0Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQDBsIrkW4ob9Z/gzR2/Maa2stbutry6/vvz8eiJwIKIbaHGwqtFOUGiWeKw7H76 +IH3SjTAhNQY2hoKPyH41D36sDJkYBRyHFJTK/6ffvOhpyLnuXJAnoS62eKPSNUAx +5i/lkHj42ESutYAH9qbHCI/gBm9G4WmhGAyA16xzC1n07JObl6KFoY1PqHKl823z +mvF47I24DzemEfjdwC9nAAX/pGYOg9FA9nQv7NnhlsJMxueCx55RNU1ADRoqsbfE +T0CQTOT4ryugGrUp9J4Cwen6YbXZrS6+Kff5SQCAns0Qu8/bwj0DKkuBGLF+Mnwe +mq9bMzyZPUrPM3Gu48ao8YAfAgMBAAGjHTAbMAwGA1UdEwQFMAMBAf8wCwYDVR0P +BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQCBwXGblRxIEOlEP6ANZ1C8AHWyG8lR +CQduFclc0tmyCCz5fnyLK0aGu9LhXXe6/HSKqgs4mJqeqYOojdjkfOme/YdwDzjK +WIf0kRYQHcB6NeyEZwW8C7subTP1Xw6zbAmjvQrtCGvRM+fi3/cs1sSSkd/EoRk4 +7GM9qQl/JIIoCOGncninf2NQm5YSpbit6/mOQD7EhqXsw+bX+IRh3DHC1Apv/PoA +HlDNeM4vjWaBxsmvRSndrIvew1czboFM18oRSSIqAkU7dKZ0SbC11grzmNxMG2aD +f9y8FIG6RK/SEaOZuc+uBGXx7tj7dczpE/2puqYcaVGwcv4kkrC/ZuRm +-----END CERTIFICATE----- +` + +const serverCert = ` +-----BEGIN CERTIFICATE----- +MIIC8zCCAdugAwIBAgIBATANBgkqhkiG9w0BAQUFADATMREwDwYDVQQDEwhNeVRl +c3RDQTAeFw0xNDAxMjcxOTUyMjNaFw0yNDAxMjUxOTUyMjNaMCUxEjAQBgNVBAMT +CTEyNy4wLjAuMTEPMA0GA1UEChMGc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAxYAKbeGyg0gP0xwVsZsufzk/SUCtD44Gp3lQYQ9QumQ1IVZu +PmZWwPWrzI93a1Abruz6ZhXaB3jcL5QPAy1N44IiFgVN45CZXBsqkpJe/abzRFOV +DRnHxattPDHdgwML5d3nURKGUM/7+ACj5E4pZEDlM3RIjIKVd+doJsL7n6myO8FE +tIpt4vTz1MFp3F+ntPnHU3BZ/VZ1UjSlFWnCjT0CR0tnXsPmlIaC98HThS8x5zNB +fvvSN+Zln8RWdNLnEVHVdqYtOQ828QbCx8s1HfClGgaVoSDrzz+qQgtZFO4wW264 +2CWkNd8DSJUJ/HlPNXmbXsrRMgvGaL7YUz2yRQIDAQABo0AwPjAJBgNVHRMEAjAA +MAsGA1UdDwQEAwIFIDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHREECDAGhwR/ +AAABMA0GCSqGSIb3DQEBBQUAA4IBAQAE2g+wAFf9Xg5svcnb7+mfseYV16k9l5WG +onrmR3FLsbTxfbr4PZJMHrswPbi2NRk0+ETPUpcv1RP7pUB7wSEvuS1NPGcU92iP +58ycP3dYtLzmuu6BkgToZqwsCU8fC2zM0wt3+ifzPpDMffWWOioVuA3zdM9WPQYz ++Ofajd0XaZwFZS8uTI5WXgObz7Xqfmln4tF3Sq1CTyuJ44qK4p83XOKFq+L04aD0 +d0c8w3YQNUENny/vMP9mDu3FQ3SnDz2GKl1LSjGe2TUnkoMkDfdk4wSzndTz/ecb +QiCPKijwVPWNOWV3NDE2edMxDPxDoKoEm5F4UGfGjxSRnYCIoZLh +-----END CERTIFICATE----- +` + +const serverKey = ` +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAxYAKbeGyg0gP0xwVsZsufzk/SUCtD44Gp3lQYQ9QumQ1IVZu +PmZWwPWrzI93a1Abruz6ZhXaB3jcL5QPAy1N44IiFgVN45CZXBsqkpJe/abzRFOV +DRnHxattPDHdgwML5d3nURKGUM/7+ACj5E4pZEDlM3RIjIKVd+doJsL7n6myO8FE +tIpt4vTz1MFp3F+ntPnHU3BZ/VZ1UjSlFWnCjT0CR0tnXsPmlIaC98HThS8x5zNB +fvvSN+Zln8RWdNLnEVHVdqYtOQ828QbCx8s1HfClGgaVoSDrzz+qQgtZFO4wW264 +2CWkNd8DSJUJ/HlPNXmbXsrRMgvGaL7YUz2yRQIDAQABAoIBAGsyEvcPAGg3DbfE +z5WFp9gPx2TIAOanbL8rnlAAEw4H47qDgfTGcSHsdeHioKuTYGMyZrpP8/YISGJe +l0NfLJ5mfH+9Q0hXrJWMfS/u2DYOjo0wXH8u1fpZEEISwqsgVS3fonSjfFmSea1j +E5GQRvEONBkYbWQuYFgjNqmLPS2r5lKbWCQvc1MB/vvVBwOTiO0ON7m/EkM5RKt9 +cDT5ZhhVjBpdmd9HpVbKTdBj8Q0l5/ZHZUEgZA6FDZEwYxTd9l87Z4YT+5SR0z9t +k8/Z0CHd3x3Rv891t7m66ZJkaOda8NC65/432MQEQwJltmrKnc22dS8yI26rrmpp +g3tcbSUCgYEA5nMXdQKS4vF+Kp10l/HqvGz2sU8qQaWYZQIg7Th3QJPo6N52po/s +nn3UF0P5mT1laeZ5ZQJKx4gnmuPnIZ2ZtJQDyFhIbRPcZ+2hSNSuLYVcrumOC3EP +3OZyFtFE1THO73aFe5e1jEdtoOne3Bds/Hq6NF45fkVdL+M9e8pfXIsCgYEA22W8 +zGjbWyrFOYvKknMQVtHnMx8BJEtsvWRknP6CWAv/8WyeZpE128Pve1m441AQnopS +CuOF5wFK0iUXBFbS3Pe1/1j3em6yfVznuUHqJ7Qc+dNzxVvkTK8jGB6x+vm+M9Hg +muHUM726IUxckoSNXbPNAVPIZab1NdSxam7F9m8CgYEAx55QZmIJXJ41XLKxqWC7 +peZ5NpPNlbncrTpPzUzJN94ntXfmrVckbxGt401VayEctMQYyZ9XqUlOjUP3FU5Q +M3S3Zhba/eljVX8o406fZf0MkNLs4QpZ5E6V6x/xEP+pMhKng6yhbVb+JpIPIvUD +yhyBKRWplbB+DRo5Sv685gsCgYA7l5m9h+m1DJv/cnn2Z2yTuHXtC8namuYRV1iA +0ByFX9UINXGc+GpBpCnDPm6ax5+MAJQiQwSW52H0TIDA+/hQbrQvhHHL/o9av8Zt +Kns4h5KrRQUYIUqUjamhnozHV9iS6LnyN87Usv8AlmY6oehoADN53dD702qdUYVT +HH2G3wKBgCdvqyw78FR/n8cUWesTPnxx5HCeWJ1J+2BESnUnPmKZ71CV1H7uweja +vPUxuuuGLKfNx84OKCfRDbtOgMOeyh9T1RmXry6Srz/7/udjlF0qmFiRXfBNAgoR +tNb0+Ri/vY0AHrQ7UnCbl12qPVaqhEXLr+kCGNEPFqpMJPPEeMK0 +-----END RSA PRIVATE KEY----- +` + +const clientCert = ` +-----BEGIN CERTIFICATE----- +MIIC4jCCAcqgAwIBAgIBAjANBgkqhkiG9w0BAQUFADATMREwDwYDVQQDEwhNeVRl +c3RDQTAeFw0xNDAxMjcxOTUyMjNaFw0yNDAxMjUxOTUyMjNaMCUxEjAQBgNVBAMT +CTEyNy4wLjAuMTEPMA0GA1UEChMGY2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAu7LMqd+agoH168Bsi0WJ36ulYqDypq+GZPF7uWOo2pE0raKH +B++31/hjnkt6yC5kLKVZZ0EfolBa9q4Cy6swfGaEMafy44ZCRneLnt1azL1N6Kfz ++U0KsOqyQDoMxYJG1gVTEZN19/U/ew2eazcxKyERI3oGCQ4SbpkxBTbfxtAFk49e +xIB3obsuMVUrmtXE4FkUkvG7NgpPUgrhp0yxYpj9zruZGzGGT1zNhcarbQ/4i7It +ZMbnv6pqQWtYDgnGX2TDRcEiXGeO+KrzhfpTRLfO3K4np8e8cmTyXM+4lMlWUgma +KrRdu1QXozGqRs47u2prGKGdSQWITpqNVCY8fQIDAQABoy8wLTAJBgNVHRMEAjAA +MAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQUF +AAOCAQEAhCuBCLznPc4O96hT3P8Fx19L3ltrWbc/pWrx8JjxUaGk8kNmjMjY+/Mt +JBbjUBx2kJwaY0EHMAfw7D1f1wcCeNycx/0dyb0E6xzhmPw5fY15GGNg8rzWwqSY ++i/1iqU0IRkmRHV7XCF+trd2H0Ec+V1Fd/61E2ccJfOL5aSAyWbMCUtWxS3QMnqH +FBfKdVEiY9WNht5hnvsXQBRaNhowJ6Cwa7/1/LZjmhcXiJ0xrc1Hggj3cvS+4vll +Ew+20a0tPKjD/v/2oSQL+qkeYKV4fhCGkaBHCpPlSJrqorb7B6NmPy3nS26ETKE/ +o2UCfZc5g2MU1ENa31kT1iuhKZapsA== +-----END CERTIFICATE----- +` + +const clientKey = ` +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAu7LMqd+agoH168Bsi0WJ36ulYqDypq+GZPF7uWOo2pE0raKH +B++31/hjnkt6yC5kLKVZZ0EfolBa9q4Cy6swfGaEMafy44ZCRneLnt1azL1N6Kfz ++U0KsOqyQDoMxYJG1gVTEZN19/U/ew2eazcxKyERI3oGCQ4SbpkxBTbfxtAFk49e +xIB3obsuMVUrmtXE4FkUkvG7NgpPUgrhp0yxYpj9zruZGzGGT1zNhcarbQ/4i7It +ZMbnv6pqQWtYDgnGX2TDRcEiXGeO+KrzhfpTRLfO3K4np8e8cmTyXM+4lMlWUgma +KrRdu1QXozGqRs47u2prGKGdSQWITpqNVCY8fQIDAQABAoIBAGSEn3hFyEAmCyYi +2b5IEksXaC2GlgxQKb/7Vs/0oCPU6YonZPsKFMFzQx4tu+ZiecEzF8rlJGTPdbdv +fw3FcuTcHeVd1QSmDO4h7UK5tnu40XVMJKsY6CXQun8M13QajYbmORNLjjypOULU +C0fNueYoAj6mhX7p61MRdSAev/5+0+bVQQG/tSVDQzdngvKpaCunOphiB2VW2Aa0 +7aYPOFCoPB2uo0DwUmBB0yfx9x4hXX9ovQI0YFou7bq6iYJ0vlZBvYQ9YrVdxjKL +avcz1N5xM3WFAkZJSVT/Ho5+uTbZx4RrJ8b5T+t2spOKmXyAjwS2rL/XMAh8YRZ1 +u44duoECgYEA4jpK2qshgQ0t49rjVHEDKX5x7ElEZefl0rHZ/2X/uHUDKpKj2fTq +3TQzHquiQ4Aof7OEB9UE3DGrtpvo/j/PYxL5Luu5VR4AIEJm+CA8GYuE96+uIL0Z +M2r3Lux6Bp30Z47Eit2KiY4fhrWs59WB3NHHoFxgzHSVbnuA02gcX2ECgYEA1GZw +iXIVYaK07ED+q/0ObyS5hD1cMhJ7ifSN9BxuG0qUpSigbkTGj09fUDS4Fqsz9dvz +F0P93fZvyia242TIfDUwJEsDQCgHk7SGa4Rx/p/3x/obIEERk7K76Hdg93U5NXhV +NvczvgL0HYxnb+qtumwMgGPzncB4lGcTnRyOfp0CgYBTIsDnYwRI/KLknUf1fCKB +WSpcfwBXwsS+jQVjygQTsUyclI8KResZp1kx6DkVPT+kzj+y8SF8GfTUgq844BJC +gnJ4P8A3+3JoaH6WqKHtcUxICZOgDF36e1CjOdwOGnX6qIipz4hdzJDhXFpSSDAV +CjKmR8x61k0j8NcC2buzgQKBgFr7eo9VwBTvpoJhIPY5UvqHB7S+uAR26FZi3H/J +wdyM6PmKWpaBfXCb9l8cBhMnyP0y94FqzY9L5fz48nSbkkmqWvHg9AaCXySFOuNJ +e68vhOszlnUNimLzOAzPPkkh/JyL7Cy8XXyyNTGHGDPXmg12BTDmH8/eR4iCUuOE +/QD9AoGBALQ/SkvfO3D5+k9e/aTHRuMJ0+PWdLUMTZ39oJQxUx+qj7/xpjDvWTBn +eDmF/wjnIAg+020oXyBYo6plEZfDz3EYJQZ+3kLLEU+O/A7VxCakPYPwCr7N/InL +Ccg/TVSIXxw/6uJnojoAjMIEU45NoP6RMp0mWYYb2OlteEv08Ovp +-----END RSA PRIVATE KEY----- +` diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/types.go b/Godeps/_workspace/src/github.com/streadway/amqp/types.go new file mode 100644 index 000000000..8071bf7cd --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/types.go @@ -0,0 +1,390 @@ +// 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" +) + +var ( + // Errors that this library could return/emit from a channel or connection + ErrClosed = &Error{Code: ChannelError, Reason: "channel/connection is not open"} + ErrChannelMax = &Error{Code: ChannelError, Reason: "channel id space exhausted"} + ErrSASL = &Error{Code: AccessRefused, Reason: "SASL could not negotiate a shared mechanism"} + ErrCredentials = &Error{Code: AccessRefused, Reason: "username or password not allowed"} + ErrVhost = &Error{Code: AccessRefused, Reason: "no access to this vhost"} + ErrSyntax = &Error{Code: SyntaxError, Reason: "invalid field or value inside of a frame"} + ErrFrame = &Error{Code: FrameError, Reason: "frame could not be parsed"} + ErrCommandInvalid = &Error{Code: CommandInvalid, Reason: "unexpected command received"} + ErrUnexpectedFrame = &Error{Code: UnexpectedFrame, Reason: "unexpected frame received"} + 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 differnet parameters +} + +func newError(code uint16, text string) *Error { + return &Error{ + Code: int(code), + Reason: text, + Recover: isSoftExceptionCode(int(code)), + Server: true, + } +} + +func (me Error) Error() string { + return fmt.Sprintf("Exception (%d) Reason: %q", me.Code, me.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 implemention 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 succesfully 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 +// 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 converstion. +// +// RabbitMQ expects int32 for integer values. +// +type Table map[string]interface{} + +func validateField(f interface{}) error { + switch fv := f.(type) { + case nil, bool, byte, 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) +} + +func (t Table) Validate() error { + return validateField(t) +} + +// Heap interface for maintaining delivery tags +type tagSet []uint64 + +func (me tagSet) Len() int { return len(me) } +func (me tagSet) Less(i, j int) bool { return (me)[i] < (me)[j] } +func (me tagSet) Swap(i, j int) { (me)[i], (me)[j] = (me)[j], (me)[i] } +func (me *tagSet) Push(tag interface{}) { *me = append(*me, tag.(uint64)) } +func (me *tagSet) Pop() interface{} { + val := (*me)[len(*me)-1] + *me = (*me)[:len(*me)-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 (me *methodFrame) channel() uint16 { return me.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 (me *heartbeatFrame) channel() uint16 { return me.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 (me *headerFrame) channel() uint16 { return me.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 (me *bodyFrame) channel() uint16 { return me.ChannelId } diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/uri.go b/Godeps/_workspace/src/github.com/streadway/amqp/uri.go new file mode 100644 index 000000000..582464db5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/uri.go @@ -0,0 +1,170 @@ +// 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" + "fmt" + "net/url" + "strconv" + "strings" +) + +var errURIScheme = errors.New("AMQP scheme must be either 'amqp://' or 'amqps://'") + +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) { + me := defaultURI + + u, err := url.Parse(uri) + if err != nil { + return me, err + } + + defaultPort, okScheme := schemePorts[u.Scheme] + + if okScheme { + me.Scheme = u.Scheme + } else { + return me, errURIScheme + } + + host, port := splitHostPort(u.Host) + + if host != "" { + me.Host = host + } + + if port != "" { + port32, err := strconv.ParseInt(port, 10, 32) + if err != nil { + return me, err + } + me.Port = int(port32) + } else { + me.Port = defaultPort + } + + if u.User != nil { + me.Username = u.User.Username() + if password, ok := u.User.Password(); ok { + me.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 { + me.Vhost = u.Path[3:] + } + } else if len(u.Path) > 1 { + me.Vhost = u.Path[1:] + } + } else { + me.Vhost = u.Path + } + } + + return me, nil +} + +// Splits host:port, host, [ho:st]:port, or [ho:st]. Unlike net.SplitHostPort +// which splits :port, host:port or [host]:port +// +// Handles hosts that have colons that are in brackets like [::1]:http +func splitHostPort(addr string) (host, port string) { + i := strings.LastIndex(addr, ":") + + if i >= 0 { + host, port = addr[:i], addr[i+1:] + + if len(port) > 0 && port[len(port)-1] == ']' && addr[0] == '[' { + // we've split on an inner colon, the port was missing outside of the + // brackets so use the full addr. We could assert that host should not + // contain any colons here + host, port = addr, "" + } + } else { + host = addr + } + + return +} + +// PlainAuth returns a PlainAuth structure based on the parsed URI's +// Username and Password fields. +func (me URI) PlainAuth() *PlainAuth { + return &PlainAuth{ + Username: me.Username, + Password: me.Password, + } +} + +func (me URI) String() string { + var authority string + + if me.Username != defaultURI.Username || me.Password != defaultURI.Password { + authority += me.Username + + if me.Password != defaultURI.Password { + authority += ":" + me.Password + } + + authority += "@" + } + + authority += me.Host + + if defaultPort, found := schemePorts[me.Scheme]; !found || defaultPort != me.Port { + authority += ":" + strconv.FormatInt(int64(me.Port), 10) + } + + var vhost string + if me.Vhost != defaultURI.Vhost { + vhost = me.Vhost + } + + return fmt.Sprintf("%s://%s/%s", me.Scheme, authority, url.QueryEscape(vhost)) +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/uri_test.go b/Godeps/_workspace/src/github.com/streadway/amqp/uri_test.go new file mode 100644 index 000000000..5d93e0bc7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/uri_test.go @@ -0,0 +1,328 @@ +package amqp + +import ( + "testing" +) + +// Test matrix defined on http://www.rabbitmq.com/uri-spec.html +type testURI struct { + url string + username string + password string + host string + port int + vhost string + canon string +} + +var uriTests = []testURI{ + { + url: "amqp://user:pass@host:10000/vhost", + username: "user", + password: "pass", + host: "host", + port: 10000, + vhost: "vhost", + canon: "amqp://user:pass@host:10000/vhost", + }, + + // this fails due to net/url not parsing pct-encoding in host + // testURI{url: "amqp://user%61:%61pass@ho%61st:10000/v%2Fhost", + // username: "usera", + // password: "apass", + // host: "hoast", + // port: 10000, + // vhost: "v/host", + // }, + + { + url: "amqp://", + username: defaultURI.Username, + password: defaultURI.Password, + host: defaultURI.Host, + port: defaultURI.Port, + vhost: defaultURI.Vhost, + canon: "amqp://localhost/", + }, + + { + url: "amqp://:@/", + username: "", + password: "", + host: defaultURI.Host, + port: defaultURI.Port, + vhost: defaultURI.Vhost, + canon: "amqp://:@localhost/", + }, + + { + url: "amqp://user@", + username: "user", + password: defaultURI.Password, + host: defaultURI.Host, + port: defaultURI.Port, + vhost: defaultURI.Vhost, + canon: "amqp://user@localhost/", + }, + + { + url: "amqp://user:pass@", + username: "user", + password: "pass", + host: defaultURI.Host, + port: defaultURI.Port, + vhost: defaultURI.Vhost, + canon: "amqp://user:pass@localhost/", + }, + + { + url: "amqp://guest:pass@", + username: "guest", + password: "pass", + host: defaultURI.Host, + port: defaultURI.Port, + vhost: defaultURI.Vhost, + canon: "amqp://guest:pass@localhost/", + }, + + { + url: "amqp://host", + username: defaultURI.Username, + password: defaultURI.Password, + host: "host", + port: defaultURI.Port, + vhost: defaultURI.Vhost, + canon: "amqp://host/", + }, + + { + url: "amqp://:10000", + username: defaultURI.Username, + password: defaultURI.Password, + host: defaultURI.Host, + port: 10000, + vhost: defaultURI.Vhost, + canon: "amqp://localhost:10000/", + }, + + { + url: "amqp:///vhost", + username: defaultURI.Username, + password: defaultURI.Password, + host: defaultURI.Host, + port: defaultURI.Port, + vhost: "vhost", + canon: "amqp://localhost/vhost", + }, + + { + url: "amqp://host/", + username: defaultURI.Username, + password: defaultURI.Password, + host: "host", + port: defaultURI.Port, + vhost: defaultURI.Vhost, + canon: "amqp://host/", + }, + + { + url: "amqp://host/%2F", + username: defaultURI.Username, + password: defaultURI.Password, + host: "host", + port: defaultURI.Port, + vhost: "/", + canon: "amqp://host/", + }, + + { + url: "amqp://host/%2F%2F", + username: defaultURI.Username, + password: defaultURI.Password, + host: "host", + port: defaultURI.Port, + vhost: "//", + canon: "amqp://host/%2F%2F", + }, + + { + url: "amqp://host/%2Fslash%2F", + username: defaultURI.Username, + password: defaultURI.Password, + host: "host", + port: defaultURI.Port, + vhost: "/slash/", + canon: "amqp://host/%2Fslash%2F", + }, + + { + url: "amqp://192.168.1.1:1000/", + username: defaultURI.Username, + password: defaultURI.Password, + host: "192.168.1.1", + port: 1000, + vhost: defaultURI.Vhost, + canon: "amqp://192.168.1.1:1000/", + }, + + { + url: "amqp://[::1]", + username: defaultURI.Username, + password: defaultURI.Password, + host: "[::1]", + port: defaultURI.Port, + vhost: defaultURI.Vhost, + canon: "amqp://[::1]/", + }, + + { + url: "amqp://[::1]:1000", + username: defaultURI.Username, + password: defaultURI.Password, + host: "[::1]", + port: 1000, + vhost: defaultURI.Vhost, + canon: "amqp://[::1]:1000/", + }, + + { + url: "amqps:///", + username: defaultURI.Username, + password: defaultURI.Password, + host: defaultURI.Host, + port: schemePorts["amqps"], + vhost: defaultURI.Vhost, + canon: "amqps://localhost/", + }, + + { + url: "amqps://host:1000/", + username: defaultURI.Username, + password: defaultURI.Password, + host: "host", + port: 1000, + vhost: defaultURI.Vhost, + canon: "amqps://host:1000/", + }, +} + +func TestURISpec(t *testing.T) { + for _, test := range uriTests { + u, err := ParseURI(test.url) + if err != nil { + t.Fatal("Could not parse spec URI: ", test.url, " err: ", err) + } + + if test.username != u.Username { + t.Error("For: ", test.url, " usernames do not match. want: ", test.username, " got: ", u.Username) + } + + if test.password != u.Password { + t.Error("For: ", test.url, " passwords do not match. want: ", test.password, " got: ", u.Password) + } + + if test.host != u.Host { + t.Error("For: ", test.url, " hosts do not match. want: ", test.host, " got: ", u.Host) + } + + if test.port != u.Port { + t.Error("For: ", test.url, " ports do not match. want: ", test.port, " got: ", u.Port) + } + + if test.vhost != u.Vhost { + t.Error("For: ", test.url, " vhosts do not match. want: ", test.vhost, " got: ", u.Vhost) + } + + if test.canon != u.String() { + t.Error("For: ", test.url, " canonical string does not match. want: ", test.canon, " got: ", u.String()) + } + } +} + +func TestURIUnknownScheme(t *testing.T) { + if _, err := ParseURI("http://example.com/"); err == nil { + t.Fatal("Expected error when parsing non-amqp scheme") + } +} + +func TestURIScheme(t *testing.T) { + if _, err := ParseURI("amqp://example.com/"); err != nil { + t.Fatalf("Expected to parse amqp scheme, got %v", err) + } + + if _, err := ParseURI("amqps://example.com/"); err != nil { + t.Fatalf("Expected to parse amqps scheme, got %v", err) + } +} + +func TestURIDefaults(t *testing.T) { + url := "amqp://" + uri, err := ParseURI(url) + if err != nil { + t.Fatal("Could not parse") + } + + if uri.String() != "amqp://localhost/" { + t.Fatal("Defaults not encoded properly got:", uri.String()) + } +} + +func TestURIComplete(t *testing.T) { + url := "amqp://bob:dobbs@foo.bar:5678/private" + uri, err := ParseURI(url) + if err != nil { + t.Fatal("Could not parse") + } + + if uri.String() != url { + t.Fatal("Defaults not encoded properly want:", url, " got:", uri.String()) + } +} + +func TestURIDefaultPortAmqpNotIncluded(t *testing.T) { + url := "amqp://foo.bar:5672/" + uri, err := ParseURI(url) + if err != nil { + t.Fatal("Could not parse") + } + + if uri.String() != "amqp://foo.bar/" { + t.Fatal("Defaults not encoded properly got:", uri.String()) + } +} + +func TestURIDefaultPortAmqp(t *testing.T) { + url := "amqp://foo.bar/" + uri, err := ParseURI(url) + if err != nil { + t.Fatal("Could not parse") + } + + if uri.Port != 5672 { + t.Fatal("Default port not correct for amqp, got:", uri.Port) + } +} + +func TestURIDefaultPortAmqpsNotIncludedInString(t *testing.T) { + url := "amqps://foo.bar:5671/" + uri, err := ParseURI(url) + if err != nil { + t.Fatal("Could not parse") + } + + if uri.String() != "amqps://foo.bar/" { + t.Fatal("Defaults not encoded properly got:", uri.String()) + } +} + +func TestURIDefaultPortAmqps(t *testing.T) { + url := "amqps://foo.bar/" + uri, err := ParseURI(url) + if err != nil { + t.Fatal("Could not parse") + } + + if uri.Port != 5671 { + t.Fatal("Default port not correct for amqps, got:", uri.Port) + } +} diff --git a/Godeps/_workspace/src/github.com/streadway/amqp/write.go b/Godeps/_workspace/src/github.com/streadway/amqp/write.go new file mode 100644 index 000000000..d392ca237 --- /dev/null +++ b/Godeps/_workspace/src/github.com/streadway/amqp/write.go @@ -0,0 +1,411 @@ +// 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 (me *writer) WriteFrame(frame frame) (err error) { + if err = frame.write(me.w); err != nil { + return + } + + if buf, ok := me.w.(*bufio.Writer); ok { + err = buf.Flush() + } + + return +} + +func (me *methodFrame) write(w io.Writer) (err error) { + var payload bytes.Buffer + + if me.Method == nil { + return errors.New("malformed frame: missing method") + } + + class, method := me.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 = me.Method.write(&payload); err != nil { + return + } + + return writeFrame(w, frameMethod, me.ChannelId, payload.Bytes()) +} + +// Heartbeat +// +// Payload is empty +func (me *heartbeatFrame) write(w io.Writer) (err error) { + return writeFrame(w, frameHeartbeat, me.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 (me *headerFrame) write(w io.Writer) (err error) { + var payload bytes.Buffer + var zeroTime time.Time + + if err = binary.Write(&payload, binary.BigEndian, me.ClassId); err != nil { + return + } + + if err = binary.Write(&payload, binary.BigEndian, me.weight); err != nil { + return + } + + if err = binary.Write(&payload, binary.BigEndian, me.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(me.Properties.ContentType) > 0 { + mask = mask | flagContentType + } + if len(me.Properties.ContentEncoding) > 0 { + mask = mask | flagContentEncoding + } + if me.Properties.Headers != nil && len(me.Properties.Headers) > 0 { + mask = mask | flagHeaders + } + if me.Properties.DeliveryMode > 0 { + mask = mask | flagDeliveryMode + } + if me.Properties.Priority > 0 { + mask = mask | flagPriority + } + if len(me.Properties.CorrelationId) > 0 { + mask = mask | flagCorrelationId + } + if len(me.Properties.ReplyTo) > 0 { + mask = mask | flagReplyTo + } + if len(me.Properties.Expiration) > 0 { + mask = mask | flagExpiration + } + if len(me.Properties.MessageId) > 0 { + mask = mask | flagMessageId + } + if me.Properties.Timestamp != zeroTime { + mask = mask | flagTimestamp + } + if len(me.Properties.Type) > 0 { + mask = mask | flagType + } + if len(me.Properties.UserId) > 0 { + mask = mask | flagUserId + } + if len(me.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, me.Properties.ContentType); err != nil { + return + } + } + if hasProperty(mask, flagContentEncoding) { + if err = writeShortstr(&payload, me.Properties.ContentEncoding); err != nil { + return + } + } + if hasProperty(mask, flagHeaders) { + if err = writeTable(&payload, me.Properties.Headers); err != nil { + return + } + } + if hasProperty(mask, flagDeliveryMode) { + if err = binary.Write(&payload, binary.BigEndian, me.Properties.DeliveryMode); err != nil { + return + } + } + if hasProperty(mask, flagPriority) { + if err = binary.Write(&payload, binary.BigEndian, me.Properties.Priority); err != nil { + return + } + } + if hasProperty(mask, flagCorrelationId) { + if err = writeShortstr(&payload, me.Properties.CorrelationId); err != nil { + return + } + } + if hasProperty(mask, flagReplyTo) { + if err = writeShortstr(&payload, me.Properties.ReplyTo); err != nil { + return + } + } + if hasProperty(mask, flagExpiration) { + if err = writeShortstr(&payload, me.Properties.Expiration); err != nil { + return + } + } + if hasProperty(mask, flagMessageId) { + if err = writeShortstr(&payload, me.Properties.MessageId); err != nil { + return + } + } + if hasProperty(mask, flagTimestamp) { + if err = binary.Write(&payload, binary.BigEndian, uint64(me.Properties.Timestamp.Unix())); err != nil { + return + } + } + if hasProperty(mask, flagType) { + if err = writeShortstr(&payload, me.Properties.Type); err != nil { + return + } + } + if hasProperty(mask, flagUserId) { + if err = writeShortstr(&payload, me.Properties.UserId); err != nil { + return + } + } + if hasProperty(mask, flagAppId) { + if err = writeShortstr(&payload, me.Properties.AppId); err != nil { + return + } + } + + return writeFrame(w, frameHeader, me.ChannelId, payload.Bytes()) +} + +// Body +// +// Payload is one byterange from the full body who's size is declared in the +// Header frame +func (me *bodyFrame) write(w io.Writer) (err error) { + return writeFrame(w, frameBody, me.ChannelId, me.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 = 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 = 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 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())) +} diff --git a/outputs/all/all.go b/outputs/all/all.go index 8586174a5..4d1369f69 100644 --- a/outputs/all/all.go +++ b/outputs/all/all.go @@ -1,6 +1,7 @@ package all import ( + _ "github.com/influxdb/telegraf/outputs/amqp" _ "github.com/influxdb/telegraf/outputs/datadog" _ "github.com/influxdb/telegraf/outputs/influxdb" _ "github.com/influxdb/telegraf/outputs/kafka" diff --git a/outputs/amqp/amqp.go b/outputs/amqp/amqp.go new file mode 100644 index 000000000..9a657311c --- /dev/null +++ b/outputs/amqp/amqp.go @@ -0,0 +1,112 @@ +package amqp + +import ( + "fmt" + + "github.com/influxdb/influxdb/client" + "github.com/influxdb/telegraf/outputs" + "github.com/streadway/amqp" +) + +type AMQP struct { + // AMQP brokers to send metrics to + URL string + // AMQP exchange + Exchange string + // Routing key + RoutingKey string + + channel *amqp.Channel +} + +var sampleConfig = ` + # AMQP url + url = "amqp://localhost:5672/influxdb" + # AMQP exchange + exchange = "telegraf" +` + +func (q *AMQP) Connect() error { + connection, err := amqp.Dial(q.URL) + if err != nil { + return err + } + channel, err := connection.Channel() + if err != nil { + return fmt.Errorf("Failed to open a channel: %s", err) + } + + err = channel.ExchangeDeclare( + q.Exchange, // name + "topic", // type + true, // durable + false, // delete when unused + false, // internal + false, // no-wait + nil, // arguments + ) + if err != nil { + return fmt.Errorf("Failed to declare an exchange: %s", err) + } + q.channel = channel + return nil +} + +func (q *AMQP) Close() error { + return q.channel.Close() +} + +func (q *AMQP) SampleConfig() string { + return sampleConfig +} + +func (q *AMQP) Description() string { + return "Configuration for the AMQP server to send metrics to" +} + +func (q *AMQP) Write(bp client.BatchPoints) error { + if len(bp.Points) == 0 { + return nil + } + + for _, p := range bp.Points { + // Combine tags from Point and BatchPoints and grab the resulting + // line-protocol output string to write to Kafka + var value, key string + if p.Raw != "" { + value = p.Raw + } else { + for k, v := range bp.Tags { + if p.Tags == nil { + p.Tags = make(map[string]string, len(bp.Tags)) + } + p.Tags[k] = v + } + value = p.MarshalString() + } + + if h, ok := p.Tags["dc"]; ok { + key = h + } + + err := q.channel.Publish( + q.Exchange, // exchange + key, // routing key + false, // mandatory + false, // immediate + amqp.Publishing{ + ContentType: "text/plain", + Body: []byte(value), + }) + if err != nil { + return fmt.Errorf("FAILED to send amqp message: %s\n", err) + } + } + return nil +} + +func init() { + outputs.Add("amqp", func() outputs.Output { + return &AMQP{} + }) +} diff --git a/outputs/amqp/amqp_test.go b/outputs/amqp/amqp_test.go new file mode 100644 index 000000000..15781fbc9 --- /dev/null +++ b/outputs/amqp/amqp_test.go @@ -0,0 +1,28 @@ +package amqp + +import ( + "testing" + + "github.com/influxdb/telegraf/testutil" + "github.com/stretchr/testify/require" +) + +func TestConnectAndWrite(t *testing.T) { + if testing.Short() { + t.Skip("Skipping integration test in short mode") + } + + var url = "amqp://" + testutil.GetLocalHost() + ":5672/" + q := &AMQP{ + URL: url, + Exchange: "telegraf_test", + } + + // Verify that we can connect to the Kafka broker + err := q.Connect() + require.NoError(t, err) + + // Verify that we can successfully write data to the kafka broker + err = q.Write(testutil.MockBatchPoints()) + require.NoError(t, err) +}