Added amqp output

This commit is contained in:
Eugene Dementiev 2015-09-15 21:16:53 +03:00
parent bd00f46d8b
commit 34e16f03e6
38 changed files with 13005 additions and 0 deletions

4
Godeps/Godeps.json generated
View File

@ -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"

View File

@ -0,0 +1,3 @@
spec/spec
examples/simple-consumer/simple-consumer
examples/simple-producer/simple-producer

View File

@ -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 ./...

View File

@ -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.

View File

@ -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.

View File

@ -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)
}

View File

@ -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<<max; lim <<= 1 {
a := newAllocator(0, lim)
for i := 0; i < runs*lim; i++ {
if i >= 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)
}
}
}
}

View File

@ -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
}

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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
}
}
}

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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")
}
}

108
Godeps/_workspace/src/github.com/streadway/amqp/doc.go generated vendored Normal file
View File

@ -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

View File

@ -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)
}

View File

@ -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
}

View File

@ -0,0 +1,2 @@
#!/bin/sh
go run spec/gen.go < spec/amqp0-9-1.stripped.extended.xml | gofmt > spec091.go

File diff suppressed because it is too large Load Diff

447
Godeps/_workspace/src/github.com/streadway/amqp/read.go generated vendored Normal file
View File

@ -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
}

View File

@ -0,0 +1,22 @@
package amqp
import (
"strings"
"testing"
)
func TestGoFuzzCrashers(t *testing.T) {
testData := []string{
"\b000000",
"\x02\x16\x10<31>[<5B><>\t\xbdui<75>" + "\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)
}
}
}

View File

@ -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
}

View File

@ -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,
}
}

View File

@ -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
}

View File

@ -0,0 +1,537 @@
<?xml version="1.0"?>
<!--
WARNING: Modified from the official 0-9-1 specification XML by
the addition of:
confirm.select and confirm.select-ok,
exchange.bind and exchange.bind-ok,
exchange.unbind and exchange.unbind-ok,
basic.nack,
the ability for the Server to send basic.ack, basic.nack and
basic.cancel to the client, and
the un-deprecation of exchange.declare{auto-delete} and exchange.declare{internal}
Modifications are (c) 2010-2013 VMware, Inc. and may be distributed
under the same BSD license as below.
-->
<!--
Copyright (c) 2009 AMQP Working Group.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. 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.
3. The name of the author may not be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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.
-->
<amqp major="0" minor="9" revision="1" port="5672">
<constant name="frame-method" value="1"/>
<constant name="frame-header" value="2"/>
<constant name="frame-body" value="3"/>
<constant name="frame-heartbeat" value="8"/>
<constant name="frame-min-size" value="4096"/>
<constant name="frame-end" value="206"/>
<constant name="reply-success" value="200"/>
<constant name="content-too-large" value="311" class="soft-error"/>
<constant name="no-route" value="312" class = "soft-error">
<doc>
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.
</doc>
</constant>
<constant name="no-consumers" value="313" class="soft-error"/>
<constant name="connection-forced" value="320" class="hard-error"/>
<constant name="invalid-path" value="402" class="hard-error"/>
<constant name="access-refused" value="403" class="soft-error"/>
<constant name="not-found" value="404" class="soft-error"/>
<constant name="resource-locked" value="405" class="soft-error"/>
<constant name="precondition-failed" value="406" class="soft-error"/>
<constant name="frame-error" value="501" class="hard-error"/>
<constant name="syntax-error" value="502" class="hard-error"/>
<constant name="command-invalid" value="503" class="hard-error"/>
<constant name="channel-error" value="504" class="hard-error"/>
<constant name="unexpected-frame" value="505" class="hard-error"/>
<constant name="resource-error" value="506" class="hard-error"/>
<constant name="not-allowed" value="530" class="hard-error"/>
<constant name="not-implemented" value="540" class="hard-error"/>
<constant name="internal-error" value="541" class="hard-error"/>
<domain name="class-id" type="short"/>
<domain name="consumer-tag" type="shortstr"/>
<domain name="delivery-tag" type="longlong"/>
<domain name="exchange-name" type="shortstr">
<assert check="length" value="127"/>
<assert check="regexp" value="^[a-zA-Z0-9-_.:]*$"/>
</domain>
<domain name="method-id" type="short"/>
<domain name="no-ack" type="bit"/>
<domain name="no-local" type="bit"/>
<domain name="no-wait" type="bit"/>
<domain name="path" type="shortstr">
<assert check="notnull"/>
<assert check="length" value="127"/>
</domain>
<domain name="peer-properties" type="table"/>
<domain name="queue-name" type="shortstr">
<assert check="length" value="127"/>
<assert check="regexp" value="^[a-zA-Z0-9-_.:]*$"/>
</domain>
<domain name="redelivered" type="bit"/>
<domain name="message-count" type="long"/>
<domain name="reply-code" type="short">
<assert check="notnull"/>
</domain>
<domain name="reply-text" type="shortstr">
<assert check="notnull"/>
</domain>
<domain name="bit" type="bit"/>
<domain name="octet" type="octet"/>
<domain name="short" type="short"/>
<domain name="long" type="long"/>
<domain name="longlong" type="longlong"/>
<domain name="shortstr" type="shortstr"/>
<domain name="longstr" type="longstr"/>
<domain name="timestamp" type="timestamp"/>
<domain name="table" type="table"/>
<class name="connection" handler="connection" index="10">
<chassis name="server" implement="MUST"/>
<chassis name="client" implement="MUST"/>
<method name="start" synchronous="1" index="10">
<chassis name="client" implement="MUST"/>
<response name="start-ok"/>
<field name="version-major" domain="octet"/>
<field name="version-minor" domain="octet"/>
<field name="server-properties" domain="peer-properties"/>
<field name="mechanisms" domain="longstr">
<assert check="notnull"/>
</field>
<field name="locales" domain="longstr">
<assert check="notnull"/>
</field>
</method>
<method name="start-ok" synchronous="1" index="11">
<chassis name="server" implement="MUST"/>
<field name="client-properties" domain="peer-properties"/>
<field name="mechanism" domain="shortstr">
<assert check="notnull"/>
</field>
<field name="response" domain="longstr">
<assert check="notnull"/>
</field>
<field name="locale" domain="shortstr">
<assert check="notnull"/>
</field>
</method>
<method name="secure" synchronous="1" index="20">
<chassis name="client" implement="MUST"/>
<response name="secure-ok"/>
<field name="challenge" domain="longstr"/>
</method>
<method name="secure-ok" synchronous="1" index="21">
<chassis name="server" implement="MUST"/>
<field name="response" domain="longstr">
<assert check="notnull"/>
</field>
</method>
<method name="tune" synchronous="1" index="30">
<chassis name="client" implement="MUST"/>
<response name="tune-ok"/>
<field name="channel-max" domain="short"/>
<field name="frame-max" domain="long"/>
<field name="heartbeat" domain="short"/>
</method>
<method name="tune-ok" synchronous="1" index="31">
<chassis name="server" implement="MUST"/>
<field name="channel-max" domain="short">
<assert check="notnull"/>
<assert check="le" method="tune" field="channel-max"/>
</field>
<field name="frame-max" domain="long"/>
<field name="heartbeat" domain="short"/>
</method>
<method name="open" synchronous="1" index="40">
<chassis name="server" implement="MUST"/>
<response name="open-ok"/>
<field name="virtual-host" domain="path"/>
<field name="reserved-1" type="shortstr" reserved="1"/>
<field name="reserved-2" type="bit" reserved="1"/>
</method>
<method name="open-ok" synchronous="1" index="41">
<chassis name="client" implement="MUST"/>
<field name="reserved-1" type="shortstr" reserved="1"/>
</method>
<method name="close" synchronous="1" index="50">
<chassis name="client" implement="MUST"/>
<chassis name="server" implement="MUST"/>
<response name="close-ok"/>
<field name="reply-code" domain="reply-code"/>
<field name="reply-text" domain="reply-text"/>
<field name="class-id" domain="class-id"/>
<field name="method-id" domain="method-id"/>
</method>
<method name="close-ok" synchronous="1" index="51">
<chassis name="client" implement="MUST"/>
<chassis name="server" implement="MUST"/>
</method>
<method name="blocked" index="60">
<chassis name="server" implement="MAY"/>
<field name="reason" type="shortstr"/>
</method>
<method name="unblocked" index="61">
<chassis name="server" implement="MAY"/>
</method>
</class>
<class name="channel" handler="channel" index="20">
<chassis name="server" implement="MUST"/>
<chassis name="client" implement="MUST"/>
<method name="open" synchronous="1" index="10">
<chassis name="server" implement="MUST"/>
<response name="open-ok"/>
<field name="reserved-1" type="shortstr" reserved="1"/>
</method>
<method name="open-ok" synchronous="1" index="11">
<chassis name="client" implement="MUST"/>
<field name="reserved-1" type="longstr" reserved="1"/>
</method>
<method name="flow" synchronous="1" index="20">
<chassis name="server" implement="MUST"/>
<chassis name="client" implement="MUST"/>
<response name="flow-ok"/>
<field name="active" domain="bit"/>
</method>
<method name="flow-ok" index="21">
<chassis name="server" implement="MUST"/>
<chassis name="client" implement="MUST"/>
<field name="active" domain="bit"/>
</method>
<method name="close" synchronous="1" index="40">
<chassis name="client" implement="MUST"/>
<chassis name="server" implement="MUST"/>
<response name="close-ok"/>
<field name="reply-code" domain="reply-code"/>
<field name="reply-text" domain="reply-text"/>
<field name="class-id" domain="class-id"/>
<field name="method-id" domain="method-id"/>
</method>
<method name="close-ok" synchronous="1" index="41">
<chassis name="client" implement="MUST"/>
<chassis name="server" implement="MUST"/>
</method>
</class>
<class name="exchange" handler="channel" index="40">
<chassis name="server" implement="MUST"/>
<chassis name="client" implement="MUST"/>
<method name="declare" synchronous="1" index="10">
<chassis name="server" implement="MUST"/>
<response name="declare-ok"/>
<field name="reserved-1" type="short" reserved="1"/>
<field name="exchange" domain="exchange-name">
<assert check="notnull"/>
</field>
<field name="type" domain="shortstr"/>
<field name="passive" domain="bit"/>
<field name="durable" domain="bit"/>
<field name="auto-delete" domain="bit"/>
<field name="internal" domain="bit"/>
<field name="no-wait" domain="no-wait"/>
<field name="arguments" domain="table"/>
</method>
<method name="declare-ok" synchronous="1" index="11">
<chassis name="client" implement="MUST"/>
</method>
<method name="delete" synchronous="1" index="20">
<chassis name="server" implement="MUST"/>
<response name="delete-ok"/>
<field name="reserved-1" type="short" reserved="1"/>
<field name="exchange" domain="exchange-name">
<assert check="notnull"/>
</field>
<field name="if-unused" domain="bit"/>
<field name="no-wait" domain="no-wait"/>
</method>
<method name="delete-ok" synchronous="1" index="21">
<chassis name="client" implement="MUST"/>
</method>
<method name="bind" synchronous="1" index="30">
<chassis name="server" implement="MUST"/>
<response name="bind-ok"/>
<field name="reserved-1" type="short" reserved="1"/>
<field name="destination" domain="exchange-name"/>
<field name="source" domain="exchange-name"/>
<field name="routing-key" domain="shortstr"/>
<field name="no-wait" domain="no-wait"/>
<field name="arguments" domain="table"/>
</method>
<method name="bind-ok" synchronous="1" index="31">
<chassis name="client" implement="MUST"/>
</method>
<method name="unbind" synchronous="1" index="40">
<chassis name="server" implement="MUST"/>
<response name="unbind-ok"/>
<field name="reserved-1" type="short" reserved="1"/>
<field name="destination" domain="exchange-name"/>
<field name="source" domain="exchange-name"/>
<field name="routing-key" domain="shortstr"/>
<field name="no-wait" domain="no-wait"/>
<field name="arguments" domain="table"/>
</method>
<method name="unbind-ok" synchronous="1" index="51">
<chassis name="client" implement="MUST"/>
</method>
</class>
<class name="queue" handler="channel" index="50">
<chassis name="server" implement="MUST"/>
<chassis name="client" implement="MUST"/>
<method name="declare" synchronous="1" index="10">
<chassis name="server" implement="MUST"/>
<response name="declare-ok"/>
<field name="reserved-1" type="short" reserved="1"/>
<field name="queue" domain="queue-name"/>
<field name="passive" domain="bit"/>
<field name="durable" domain="bit"/>
<field name="exclusive" domain="bit"/>
<field name="auto-delete" domain="bit"/>
<field name="no-wait" domain="no-wait"/>
<field name="arguments" domain="table"/>
</method>
<method name="declare-ok" synchronous="1" index="11">
<chassis name="client" implement="MUST"/>
<field name="queue" domain="queue-name">
<assert check="notnull"/>
</field>
<field name="message-count" domain="message-count"/>
<field name="consumer-count" domain="long"/>
</method>
<method name="bind" synchronous="1" index="20">
<chassis name="server" implement="MUST"/>
<response name="bind-ok"/>
<field name="reserved-1" type="short" reserved="1"/>
<field name="queue" domain="queue-name"/>
<field name="exchange" domain="exchange-name"/>
<field name="routing-key" domain="shortstr"/>
<field name="no-wait" domain="no-wait"/>
<field name="arguments" domain="table"/>
</method>
<method name="bind-ok" synchronous="1" index="21">
<chassis name="client" implement="MUST"/>
</method>
<method name="unbind" synchronous="1" index="50">
<chassis name="server" implement="MUST"/>
<response name="unbind-ok"/>
<field name="reserved-1" type="short" reserved="1"/>
<field name="queue" domain="queue-name"/>
<field name="exchange" domain="exchange-name"/>
<field name="routing-key" domain="shortstr"/>
<field name="arguments" domain="table"/>
</method>
<method name="unbind-ok" synchronous="1" index="51">
<chassis name="client" implement="MUST"/>
</method>
<method name="purge" synchronous="1" index="30">
<chassis name="server" implement="MUST"/>
<response name="purge-ok"/>
<field name="reserved-1" type="short" reserved="1"/>
<field name="queue" domain="queue-name"/>
<field name="no-wait" domain="no-wait"/>
</method>
<method name="purge-ok" synchronous="1" index="31">
<chassis name="client" implement="MUST"/>
<field name="message-count" domain="message-count"/>
</method>
<method name="delete" synchronous="1" index="40">
<chassis name="server" implement="MUST"/>
<response name="delete-ok"/>
<field name="reserved-1" type="short" reserved="1"/>
<field name="queue" domain="queue-name"/>
<field name="if-unused" domain="bit"/>
<field name="if-empty" domain="bit"/>
<field name="no-wait" domain="no-wait"/>
</method>
<method name="delete-ok" synchronous="1" index="41">
<chassis name="client" implement="MUST"/>
<field name="message-count" domain="message-count"/>
</method>
</class>
<class name="basic" handler="channel" index="60">
<chassis name="server" implement="MUST"/>
<chassis name="client" implement="MAY"/>
<field name="content-type" domain="shortstr"/>
<field name="content-encoding" domain="shortstr"/>
<field name="headers" domain="table"/>
<field name="delivery-mode" domain="octet"/>
<field name="priority" domain="octet"/>
<field name="correlation-id" domain="shortstr"/>
<field name="reply-to" domain="shortstr"/>
<field name="expiration" domain="shortstr"/>
<field name="message-id" domain="shortstr"/>
<field name="timestamp" domain="timestamp"/>
<field name="type" domain="shortstr"/>
<field name="user-id" domain="shortstr"/>
<field name="app-id" domain="shortstr"/>
<field name="reserved" domain="shortstr"/>
<method name="qos" synchronous="1" index="10">
<chassis name="server" implement="MUST"/>
<response name="qos-ok"/>
<field name="prefetch-size" domain="long"/>
<field name="prefetch-count" domain="short"/>
<field name="global" domain="bit"/>
</method>
<method name="qos-ok" synchronous="1" index="11">
<chassis name="client" implement="MUST"/>
</method>
<method name="consume" synchronous="1" index="20">
<chassis name="server" implement="MUST"/>
<response name="consume-ok"/>
<field name="reserved-1" type="short" reserved="1"/>
<field name="queue" domain="queue-name"/>
<field name="consumer-tag" domain="consumer-tag"/>
<field name="no-local" domain="no-local"/>
<field name="no-ack" domain="no-ack"/>
<field name="exclusive" domain="bit"/>
<field name="no-wait" domain="no-wait"/>
<field name="arguments" domain="table"/>
</method>
<method name="consume-ok" synchronous="1" index="21">
<chassis name="client" implement="MUST"/>
<field name="consumer-tag" domain="consumer-tag"/>
</method>
<method name="cancel" synchronous="1" index="30">
<chassis name="server" implement="MUST"/>
<chassis name="client" implement="SHOULD"/>
<response name="cancel-ok"/>
<field name="consumer-tag" domain="consumer-tag"/>
<field name="no-wait" domain="no-wait"/>
</method>
<method name="cancel-ok" synchronous="1" index="31">
<chassis name="client" implement="MUST"/>
<chassis name="server" implement="MAY"/>
<field name="consumer-tag" domain="consumer-tag"/>
</method>
<method name="publish" content="1" index="40">
<chassis name="server" implement="MUST"/>
<field name="reserved-1" type="short" reserved="1"/>
<field name="exchange" domain="exchange-name"/>
<field name="routing-key" domain="shortstr"/>
<field name="mandatory" domain="bit"/>
<field name="immediate" domain="bit"/>
</method>
<method name="return" content="1" index="50">
<chassis name="client" implement="MUST"/>
<field name="reply-code" domain="reply-code"/>
<field name="reply-text" domain="reply-text"/>
<field name="exchange" domain="exchange-name"/>
<field name="routing-key" domain="shortstr"/>
</method>
<method name="deliver" content="1" index="60">
<chassis name="client" implement="MUST"/>
<field name="consumer-tag" domain="consumer-tag"/>
<field name="delivery-tag" domain="delivery-tag"/>
<field name="redelivered" domain="redelivered"/>
<field name="exchange" domain="exchange-name"/>
<field name="routing-key" domain="shortstr"/>
</method>
<method name="get" synchronous="1" index="70">
<response name="get-ok"/>
<response name="get-empty"/>
<chassis name="server" implement="MUST"/>
<field name="reserved-1" type="short" reserved="1"/>
<field name="queue" domain="queue-name"/>
<field name="no-ack" domain="no-ack"/>
</method>
<method name="get-ok" synchronous="1" content="1" index="71">
<chassis name="client" implement="MAY"/>
<field name="delivery-tag" domain="delivery-tag"/>
<field name="redelivered" domain="redelivered"/>
<field name="exchange" domain="exchange-name"/>
<field name="routing-key" domain="shortstr"/>
<field name="message-count" domain="message-count"/>
</method>
<method name="get-empty" synchronous="1" index="72">
<chassis name="client" implement="MAY"/>
<field name="reserved-1" type="shortstr" reserved="1"/>
</method>
<method name="ack" index="80">
<chassis name="server" implement="MUST"/>
<chassis name="client" implement="MUST"/>
<field name="delivery-tag" domain="delivery-tag"/>
<field name="multiple" domain="bit"/>
</method>
<method name="reject" index="90">
<chassis name="server" implement="MUST"/>
<field name="delivery-tag" domain="delivery-tag"/>
<field name="requeue" domain="bit"/>
</method>
<method name="recover-async" index="100" deprecated="1">
<chassis name="server" implement="MAY"/>
<field name="requeue" domain="bit"/>
</method>
<method name="recover" synchronous="1" index="110">
<chassis name="server" implement="MUST"/>
<field name="requeue" domain="bit"/>
</method>
<method name="recover-ok" synchronous="1" index="111">
<chassis name="client" implement="MUST"/>
</method>
<method name="nack" index="120">
<chassis name="server" implement="MUST"/>
<chassis name="client" implement="MUST"/>
<field name="delivery-tag" domain="delivery-tag"/>
<field name="multiple" domain="bit"/>
<field name="requeue" domain="bit"/>
</method>
</class>
<class name="tx" handler="channel" index="90">
<chassis name="server" implement="SHOULD"/>
<chassis name="client" implement="MAY"/>
<method name="select" synchronous="1" index="10">
<chassis name="server" implement="MUST"/>
<response name="select-ok"/>
</method>
<method name="select-ok" synchronous="1" index="11">
<chassis name="client" implement="MUST"/>
</method>
<method name="commit" synchronous="1" index="20">
<chassis name="server" implement="MUST"/>
<response name="commit-ok"/>
</method>
<method name="commit-ok" synchronous="1" index="21">
<chassis name="client" implement="MUST"/>
</method>
<method name="rollback" synchronous="1" index="30">
<chassis name="server" implement="MUST"/>
<response name="rollback-ok"/>
</method>
<method name="rollback-ok" synchronous="1" index="31">
<chassis name="client" implement="MUST"/>
</method>
</class>
<class name="confirm" handler="channel" index="85">
<chassis name="server" implement="SHOULD"/>
<chassis name="client" implement="MAY"/>
<method name="select" synchronous="1" index="10">
<chassis name="server" implement="MUST"/>
<response name="select-ok"/>
<field name="nowait" type="bit"/>
</method>
<method name="select-ok" synchronous="1" index="11">
<chassis name="client" implement="MUST"/>
</method>
</class>
</amqp>

View File

@ -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)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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-----
`

View File

@ -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 }

170
Godeps/_workspace/src/github.com/streadway/amqp/uri.go generated vendored Normal file
View File

@ -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))
}

View File

@ -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)
}
}

View File

@ -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()))
}

View File

@ -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"

112
outputs/amqp/amqp.go Normal file
View File

@ -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{}
})
}

28
outputs/amqp/amqp_test.go Normal file
View File

@ -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)
}