mirror of https://github.com/grafana/grafana
parent
8334b24b42
commit
dea631bedc
@ -1,150 +0,0 @@ |
|||||||
package eventpublisher |
|
||||||
|
|
||||||
import ( |
|
||||||
"encoding/json" |
|
||||||
"fmt" |
|
||||||
"log" |
|
||||||
"time" |
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/bus" |
|
||||||
"github.com/grafana/grafana/pkg/events" |
|
||||||
"github.com/grafana/grafana/pkg/setting" |
|
||||||
"github.com/streadway/amqp" |
|
||||||
) |
|
||||||
|
|
||||||
var ( |
|
||||||
url string |
|
||||||
exchange string |
|
||||||
conn *amqp.Connection |
|
||||||
channel *amqp.Channel |
|
||||||
) |
|
||||||
|
|
||||||
func getConnection() (*amqp.Connection, error) { |
|
||||||
c, err := amqp.Dial(url) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
return c, err |
|
||||||
} |
|
||||||
|
|
||||||
func getChannel() (*amqp.Channel, error) { |
|
||||||
ch, err := conn.Channel() |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
|
|
||||||
err = ch.ExchangeDeclare( |
|
||||||
exchange, // name
|
|
||||||
"topic", // type
|
|
||||||
true, // durable
|
|
||||||
false, // auto-deleted
|
|
||||||
false, // internal
|
|
||||||
false, // no-wait
|
|
||||||
nil, // arguments
|
|
||||||
) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
return ch, err |
|
||||||
} |
|
||||||
|
|
||||||
func Init() { |
|
||||||
sec := setting.Cfg.Section("event_publisher") |
|
||||||
|
|
||||||
if !sec.Key("enabled").MustBool(false) { |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
url = sec.Key("rabbitmq_url").String() |
|
||||||
exchange = sec.Key("exchange").String() |
|
||||||
bus.AddWildcardListener(eventListener) |
|
||||||
|
|
||||||
if err := Setup(); err != nil { |
|
||||||
log.Fatal(4, "Failed to connect to notification queue: %v", err) |
|
||||||
return |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Every connection should declare the topology they expect
|
|
||||||
func Setup() error { |
|
||||||
c, err := getConnection() |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
conn = c |
|
||||||
ch, err := getChannel() |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
channel = ch |
|
||||||
|
|
||||||
// listen for close events so we can reconnect.
|
|
||||||
errChan := channel.NotifyClose(make(chan *amqp.Error)) |
|
||||||
go func() { |
|
||||||
for e := range errChan { |
|
||||||
fmt.Println("connection to rabbitmq lost.") |
|
||||||
fmt.Println(e) |
|
||||||
fmt.Println("attempting to create new rabbitmq channel.") |
|
||||||
ch, err := getChannel() |
|
||||||
if err == nil { |
|
||||||
channel = ch |
|
||||||
break |
|
||||||
} |
|
||||||
|
|
||||||
//could not create channel, so lets close the connection
|
|
||||||
// and re-create.
|
|
||||||
_ = conn.Close() |
|
||||||
|
|
||||||
for err != nil { |
|
||||||
time.Sleep(2 * time.Second) |
|
||||||
fmt.Println("attempting to reconnect to rabbitmq.") |
|
||||||
err = Setup() |
|
||||||
} |
|
||||||
fmt.Println("Connected to rabbitmq again.") |
|
||||||
} |
|
||||||
}() |
|
||||||
|
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
func publish(routingKey string, msgString []byte) { |
|
||||||
for { |
|
||||||
err := channel.Publish( |
|
||||||
exchange, //exchange
|
|
||||||
routingKey, // routing key
|
|
||||||
false, // mandatory
|
|
||||||
false, // immediate
|
|
||||||
amqp.Publishing{ |
|
||||||
ContentType: "application/json", |
|
||||||
Body: msgString, |
|
||||||
}, |
|
||||||
) |
|
||||||
if err == nil { |
|
||||||
return |
|
||||||
} |
|
||||||
// failures are most likely because the connection was lost.
|
|
||||||
// the connection will be re-established, so just keep
|
|
||||||
// retrying every 2seconds until we successfully publish.
|
|
||||||
time.Sleep(2 * time.Second) |
|
||||||
fmt.Println("publish failed, retrying.") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func eventListener(event interface{}) error { |
|
||||||
wireEvent, err := events.ToOnWriteEvent(event) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
msgString, err := json.Marshal(wireEvent) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
routingKey := fmt.Sprintf("%s.%s", wireEvent.Priority, wireEvent.EventType) |
|
||||||
// this is run in a greenthread and we expect that publish will keep
|
|
||||||
// retrying until the message gets sent.
|
|
||||||
go publish(routingKey, msgString) |
|
||||||
return nil |
|
||||||
} |
|
||||||
@ -1,3 +0,0 @@ |
|||||||
spec/spec |
|
||||||
examples/simple-consumer/simple-consumer |
|
||||||
examples/simple-producer/simple-producer |
|
||||||
@ -1,13 +0,0 @@ |
|||||||
language: go |
|
||||||
|
|
||||||
go: |
|
||||||
- 1.1 |
|
||||||
- tip |
|
||||||
|
|
||||||
services: |
|
||||||
- rabbitmq |
|
||||||
|
|
||||||
env: |
|
||||||
- AMQP_URL=amqp://guest:guest@127.0.0.1:5672/ GOMAXPROCS=2 |
|
||||||
|
|
||||||
script: go test -tags integration ./... |
|
||||||
@ -1,23 +0,0 @@ |
|||||||
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. |
|
||||||
@ -1,76 +0,0 @@ |
|||||||
# AMQP |
|
||||||
|
|
||||||
AMQP 0.9.1 client with RabbitMQ extensions in Go. |
|
||||||
|
|
||||||
# Status |
|
||||||
|
|
||||||
*Beta* |
|
||||||
|
|
||||||
[](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). |
|
||||||
|
|
||||||
# License |
|
||||||
|
|
||||||
BSD 2 clause - see LICENSE for more details. |
|
||||||
|
|
||||||
|
|
||||||
@ -1,106 +0,0 @@ |
|||||||
package amqp |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
"fmt" |
|
||||||
"math/big" |
|
||||||
) |
|
||||||
|
|
||||||
const ( |
|
||||||
free = 0 |
|
||||||
allocated = 1 |
|
||||||
) |
|
||||||
|
|
||||||
// allocator maintains a bitset of allocated numbers.
|
|
||||||
type allocator struct { |
|
||||||
pool *big.Int |
|
||||||
last int |
|
||||||
low int |
|
||||||
high int |
|
||||||
} |
|
||||||
|
|
||||||
// NewAllocator reserves and frees integers out of a range between low and
|
|
||||||
// high.
|
|
||||||
//
|
|
||||||
// O(N) worst case space used, where N is maximum allocated, divided by
|
|
||||||
// sizeof(big.Word)
|
|
||||||
func newAllocator(low, high int) *allocator { |
|
||||||
return &allocator{ |
|
||||||
pool: big.NewInt(0), |
|
||||||
last: low, |
|
||||||
low: low, |
|
||||||
high: high, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// String returns a string describing the contents of the allocator like
|
|
||||||
// "allocator[low..high] reserved..until"
|
|
||||||
//
|
|
||||||
// O(N) where N is high-low
|
|
||||||
func (a allocator) String() string { |
|
||||||
b := &bytes.Buffer{} |
|
||||||
fmt.Fprintf(b, "allocator[%d..%d]", a.low, a.high) |
|
||||||
|
|
||||||
for low := a.low; low <= a.high; low++ { |
|
||||||
high := low |
|
||||||
for a.reserved(high) && high <= a.high { |
|
||||||
high++ |
|
||||||
} |
|
||||||
|
|
||||||
if high > low+1 { |
|
||||||
fmt.Fprintf(b, " %d..%d", low, high-1) |
|
||||||
} else if high > low { |
|
||||||
fmt.Fprintf(b, " %d", high-1) |
|
||||||
} |
|
||||||
|
|
||||||
low = high |
|
||||||
} |
|
||||||
return b.String() |
|
||||||
} |
|
||||||
|
|
||||||
// Next reserves and returns the next available number out of the range between
|
|
||||||
// low and high. If no number is available, false is returned.
|
|
||||||
//
|
|
||||||
// O(N) worst case runtime where N is allocated, but usually O(1) due to a
|
|
||||||
// rolling index into the oldest allocation.
|
|
||||||
func (a *allocator) next() (int, bool) { |
|
||||||
wrapped := a.last |
|
||||||
|
|
||||||
// Find trailing bit
|
|
||||||
for ; a.last <= a.high; a.last++ { |
|
||||||
if a.reserve(a.last) { |
|
||||||
return a.last, true |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Find 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) |
|
||||||
} |
|
||||||
@ -1,90 +0,0 @@ |
|||||||
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) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,44 +0,0 @@ |
|||||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// Source code and contact info at http://github.com/streadway/amqp
|
|
||||||
|
|
||||||
package amqp |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
) |
|
||||||
|
|
||||||
// Authentication interface provides a means for different SASL authentication
|
|
||||||
// mechanisms to be used during connection tuning.
|
|
||||||
type Authentication interface { |
|
||||||
Mechanism() string |
|
||||||
Response() string |
|
||||||
} |
|
||||||
|
|
||||||
// PlainAuth is a similar to Basic Auth in HTTP.
|
|
||||||
type PlainAuth struct { |
|
||||||
Username string |
|
||||||
Password string |
|
||||||
} |
|
||||||
|
|
||||||
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 |
|
||||||
} |
|
||||||
@ -1,159 +0,0 @@ |
|||||||
#!/bin/sh |
|
||||||
# |
|
||||||
# Creates the CA, server and client certs to be used by tls_test.go |
|
||||||
# http://www.rabbitmq.com/ssl.html |
|
||||||
# |
|
||||||
# Copy stdout into the const section of tls_test.go or use for RabbitMQ |
|
||||||
# |
|
||||||
root=$PWD/certs |
|
||||||
|
|
||||||
if [ -f $root/ca/serial ]; then |
|
||||||
echo >&2 "Previous installation found" |
|
||||||
echo >&2 "Remove $root/ca and rerun to overwrite" |
|
||||||
exit 1 |
|
||||||
fi |
|
||||||
|
|
||||||
mkdir -p $root/ca/private |
|
||||||
mkdir -p $root/ca/certs |
|
||||||
mkdir -p $root/server |
|
||||||
mkdir -p $root/client |
|
||||||
|
|
||||||
cd $root/ca |
|
||||||
|
|
||||||
chmod 700 private |
|
||||||
touch index.txt |
|
||||||
echo 'unique_subject = no' > index.txt.attr |
|
||||||
echo '01' > serial |
|
||||||
echo >openssl.cnf ' |
|
||||||
[ ca ] |
|
||||||
default_ca = testca |
|
||||||
|
|
||||||
[ testca ] |
|
||||||
dir = . |
|
||||||
certificate = $dir/cacert.pem |
|
||||||
database = $dir/index.txt |
|
||||||
new_certs_dir = $dir/certs |
|
||||||
private_key = $dir/private/cakey.pem |
|
||||||
serial = $dir/serial |
|
||||||
|
|
||||||
default_crl_days = 7 |
|
||||||
default_days = 3650 |
|
||||||
default_md = sha1 |
|
||||||
|
|
||||||
policy = testca_policy |
|
||||||
x509_extensions = certificate_extensions |
|
||||||
|
|
||||||
[ testca_policy ] |
|
||||||
commonName = supplied |
|
||||||
stateOrProvinceName = optional |
|
||||||
countryName = optional |
|
||||||
emailAddress = optional |
|
||||||
organizationName = optional |
|
||||||
organizationalUnitName = optional |
|
||||||
|
|
||||||
[ certificate_extensions ] |
|
||||||
basicConstraints = CA:false |
|
||||||
|
|
||||||
[ req ] |
|
||||||
default_bits = 2048 |
|
||||||
default_keyfile = ./private/cakey.pem |
|
||||||
default_md = sha1 |
|
||||||
prompt = yes |
|
||||||
distinguished_name = root_ca_distinguished_name |
|
||||||
x509_extensions = root_ca_extensions |
|
||||||
|
|
||||||
[ root_ca_distinguished_name ] |
|
||||||
commonName = hostname |
|
||||||
|
|
||||||
[ root_ca_extensions ] |
|
||||||
basicConstraints = CA:true |
|
||||||
keyUsage = keyCertSign, cRLSign |
|
||||||
|
|
||||||
[ client_ca_extensions ] |
|
||||||
basicConstraints = CA:false |
|
||||||
keyUsage = digitalSignature |
|
||||||
extendedKeyUsage = 1.3.6.1.5.5.7.3.2 |
|
||||||
|
|
||||||
[ server_ca_extensions ] |
|
||||||
basicConstraints = CA:false |
|
||||||
keyUsage = keyEncipherment |
|
||||||
extendedKeyUsage = 1.3.6.1.5.5.7.3.1 |
|
||||||
subjectAltName = @alt_names |
|
||||||
|
|
||||||
[ alt_names ] |
|
||||||
IP.1 = 127.0.0.1 |
|
||||||
' |
|
||||||
|
|
||||||
openssl req \ |
|
||||||
-x509 \ |
|
||||||
-nodes \ |
|
||||||
-config openssl.cnf \ |
|
||||||
-newkey rsa:2048 \ |
|
||||||
-days 3650 \ |
|
||||||
-subj "/CN=MyTestCA/" \ |
|
||||||
-out cacert.pem \ |
|
||||||
-outform PEM |
|
||||||
|
|
||||||
openssl x509 \ |
|
||||||
-in cacert.pem \ |
|
||||||
-out cacert.cer \ |
|
||||||
-outform DER |
|
||||||
|
|
||||||
openssl genrsa -out $root/server/key.pem 2048 |
|
||||||
openssl genrsa -out $root/client/key.pem 2048 |
|
||||||
|
|
||||||
openssl req \ |
|
||||||
-new \ |
|
||||||
-nodes \ |
|
||||||
-config openssl.cnf \ |
|
||||||
-subj "/CN=127.0.0.1/O=server/" \ |
|
||||||
-key $root/server/key.pem \ |
|
||||||
-out $root/server/req.pem \ |
|
||||||
-outform PEM |
|
||||||
|
|
||||||
openssl req \ |
|
||||||
-new \ |
|
||||||
-nodes \ |
|
||||||
-config openssl.cnf \ |
|
||||||
-subj "/CN=127.0.0.1/O=client/" \ |
|
||||||
-key $root/client/key.pem \ |
|
||||||
-out $root/client/req.pem \ |
|
||||||
-outform PEM |
|
||||||
|
|
||||||
openssl ca \ |
|
||||||
-config openssl.cnf \ |
|
||||||
-in $root/server/req.pem \ |
|
||||||
-out $root/server/cert.pem \ |
|
||||||
-notext \ |
|
||||||
-batch \ |
|
||||||
-extensions server_ca_extensions |
|
||||||
|
|
||||||
openssl ca \ |
|
||||||
-config openssl.cnf \ |
|
||||||
-in $root/client/req.pem \ |
|
||||||
-out $root/client/cert.pem \ |
|
||||||
-notext \ |
|
||||||
-batch \ |
|
||||||
-extensions client_ca_extensions |
|
||||||
|
|
||||||
cat <<-END |
|
||||||
const caCert = \` |
|
||||||
`cat $root/ca/cacert.pem` |
|
||||||
\` |
|
||||||
|
|
||||||
const serverCert = \` |
|
||||||
`cat $root/server/cert.pem` |
|
||||||
\` |
|
||||||
|
|
||||||
const serverKey = \` |
|
||||||
`cat $root/server/key.pem` |
|
||||||
\` |
|
||||||
|
|
||||||
const clientCert = \` |
|
||||||
`cat $root/client/cert.pem` |
|
||||||
\` |
|
||||||
|
|
||||||
const clientKey = \` |
|
||||||
`cat $root/client/key.pem` |
|
||||||
\` |
|
||||||
END |
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,559 +0,0 @@ |
|||||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// Source code and contact info at http://github.com/streadway/amqp
|
|
||||||
|
|
||||||
package amqp |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
"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: 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) |
|
||||||
} |
|
||||||
|
|
||||||
acks, _ := ch.NotifyConfirm(make(chan uint64), make(chan uint64)) |
|
||||||
|
|
||||||
ch.Confirm(false) |
|
||||||
|
|
||||||
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")}) |
|
||||||
|
|
||||||
for i, tag := range []uint64{2, 1, 3, 4} { |
|
||||||
if ack := <-acks; tag != ack { |
|
||||||
t.Fatalf("failed ack, expected ack#%d to be %d, got %d", i, tag, ack) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
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, 8, 7} { |
|
||||||
if ack := <-acks; tag != ack { |
|
||||||
t.Fatalf("failed ack, expected ack#%d to be %d, got %d", i, tag, ack) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
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") |
|
||||||
} |
|
||||||
|
|
||||||
ack, nack := ch.NotifyConfirm(make(chan uint64), make(chan uint64)) |
|
||||||
|
|
||||||
select { |
|
||||||
case <-ack: |
|
||||||
case <-time.After(time.Millisecond): |
|
||||||
t.Errorf("expected to close acks on Channel.NotifyConfirm chan after Connection.Close") |
|
||||||
} |
|
||||||
|
|
||||||
select { |
|
||||||
case <-nack: |
|
||||||
case <-time.After(time.Millisecond): |
|
||||||
t.Errorf("expected to close nacks Channel.NotifyConfirm 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 |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,764 +0,0 @@ |
|||||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// Source code and contact info at http://github.com/streadway/amqp
|
|
||||||
|
|
||||||
package amqp |
|
||||||
|
|
||||||
import ( |
|
||||||
"bufio" |
|
||||||
"crypto/tls" |
|
||||||
"io" |
|
||||||
"net" |
|
||||||
"reflect" |
|
||||||
"strconv" |
|
||||||
"strings" |
|
||||||
"sync" |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
const ( |
|
||||||
defaultHeartbeat = 10 * time.Second |
|
||||||
defaultConnectionTimeout = 30 * time.Second |
|
||||||
defaultProduct = "https://github.com/streadway/amqp" |
|
||||||
defaultVersion = "β" |
|
||||||
defaultChannelMax = (2 << 16) - 1 |
|
||||||
) |
|
||||||
|
|
||||||
// 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 |
|
||||||
} |
|
||||||
|
|
||||||
// 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 pick(client, server int) int { |
|
||||||
if client == 0 || server == 0 { |
|
||||||
// max
|
|
||||||
if client > server { |
|
||||||
return client |
|
||||||
} else { |
|
||||||
return server |
|
||||||
} |
|
||||||
} else { |
|
||||||
// min
|
|
||||||
if client > server { |
|
||||||
return server |
|
||||||
} else { |
|
||||||
return client |
|
||||||
} |
|
||||||
} |
|
||||||
panic("unreachable") |
|
||||||
} |
|
||||||
@ -1,118 +0,0 @@ |
|||||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// Source code and contact info at http://github.com/streadway/amqp
|
|
||||||
|
|
||||||
package amqp |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"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 |
|
||||||
} |
|
||||||
@ -1,173 +0,0 @@ |
|||||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// Source code and contact info at http://github.com/streadway/amqp
|
|
||||||
|
|
||||||
package amqp |
|
||||||
|
|
||||||
import ( |
|
||||||
"errors" |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
var errDeliveryNotInitialized = errors.New("delivery not initialized") |
|
||||||
|
|
||||||
// Acknowledger notifies the server of successful or failed consumption of
|
|
||||||
// delivieries via identifier found in the Delivery.DeliveryTag field.
|
|
||||||
//
|
|
||||||
// Applications can provide mock implementations in tests of Delivery handlers.
|
|
||||||
type Acknowledger interface { |
|
||||||
Ack(tag uint64, multiple bool) error |
|
||||||
Nack(tag uint64, multiple bool, requeue bool) error |
|
||||||
Reject(tag uint64, requeue bool) error |
|
||||||
} |
|
||||||
|
|
||||||
// Delivery captures the fields for a previously delivered message resident in
|
|
||||||
// a queue to be delivered by the server to a consumer from Channel.Consume or
|
|
||||||
// Channel.Get.
|
|
||||||
type Delivery struct { |
|
||||||
Acknowledger Acknowledger // the channel from which this delivery arrived
|
|
||||||
|
|
||||||
Headers Table // Application or header exchange table
|
|
||||||
|
|
||||||
// Properties
|
|
||||||
ContentType string // MIME content type
|
|
||||||
ContentEncoding string // MIME content encoding
|
|
||||||
DeliveryMode uint8 // queue 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) |
|
||||||
} |
|
||||||
@ -1,33 +0,0 @@ |
|||||||
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") |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,108 +0,0 @@ |
|||||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// Source code and contact info at http://github.com/streadway/amqp
|
|
||||||
|
|
||||||
/* |
|
||||||
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 |
|
||||||
@ -1,395 +0,0 @@ |
|||||||
package amqp_test |
|
||||||
|
|
||||||
import ( |
|
||||||
"crypto/tls" |
|
||||||
"crypto/x509" |
|
||||||
"github.com/streadway/amqp" |
|
||||||
"io/ioutil" |
|
||||||
"log" |
|
||||||
"net" |
|
||||||
"runtime" |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
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
|
|
||||||
pubAcks, pubNacks := chd.NotifyConfirm(make(chan uint64, 1), make(chan uint64, 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
|
|
||||||
// here you could check for delivery order by keeping a local state of
|
|
||||||
// expected delivery tags
|
|
||||||
select { |
|
||||||
case <-pubAcks: |
|
||||||
msg.Ack(false) |
|
||||||
case <-pubNacks: |
|
||||||
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) |
|
||||||
} |
|
||||||
@ -1,2 +0,0 @@ |
|||||||
#!/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
@ -1,444 +0,0 @@ |
|||||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// Source code and contact info at http://github.com/streadway/amqp
|
|
||||||
|
|
||||||
package amqp |
|
||||||
|
|
||||||
import ( |
|
||||||
"bytes" |
|
||||||
"encoding/binary" |
|
||||||
"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 |
|
||||||
} |
|
||||||
|
|
||||||
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 |
|
||||||
} |
|
||||||
|
|
||||||
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 |
|
||||||
} |
|
||||||
|
|
||||||
return bf, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (me *reader) parseHeartbeatFrame(channel uint16, size uint32) (frame frame, err error) { |
|
||||||
hf := &heartbeatFrame{ |
|
||||||
ChannelId: channel, |
|
||||||
} |
|
||||||
|
|
||||||
if size > 0 { |
|
||||||
panic("Heartbeats should not have a payload") |
|
||||||
} |
|
||||||
|
|
||||||
return hf, nil |
|
||||||
} |
|
||||||
@ -1,113 +0,0 @@ |
|||||||
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
|
|
||||||
} |
|
||||||
@ -1,64 +0,0 @@ |
|||||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// Source code and contact info at http://github.com/streadway/amqp
|
|
||||||
|
|
||||||
package amqp |
|
||||||
|
|
||||||
import ( |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
// Return captures a flattened struct of fields returned by the server when a
|
|
||||||
// Publishing is unable to be delivered either due to the `mandatory` flag set
|
|
||||||
// and no route found, or `immediate` flag set and no free consumer.
|
|
||||||
type Return struct { |
|
||||||
ReplyCode uint16 // reason
|
|
||||||
ReplyText string // description
|
|
||||||
Exchange string // basic.publish exchange
|
|
||||||
RoutingKey string // basic.publish routing key
|
|
||||||
|
|
||||||
// Properties
|
|
||||||
ContentType string // MIME content type
|
|
||||||
ContentEncoding string // MIME content encoding
|
|
||||||
Headers Table // Application or header exchange table
|
|
||||||
DeliveryMode uint8 // queue 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, |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,71 +0,0 @@ |
|||||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// Source code and contact info at http://github.com/streadway/amqp
|
|
||||||
|
|
||||||
package amqp |
|
||||||
|
|
||||||
import ( |
|
||||||
"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 |
|
||||||
} |
|
||||||
@ -1,537 +0,0 @@ |
|||||||
<?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> |
|
||||||
@ -1,536 +0,0 @@ |
|||||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// Source code and contact info at http://github.com/streadway/amqp
|
|
||||||
|
|
||||||
// +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
@ -1,218 +0,0 @@ |
|||||||
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----- |
|
||||||
` |
|
||||||
@ -1,382 +0,0 @@ |
|||||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// Source code and contact info at http://github.com/streadway/amqp
|
|
||||||
|
|
||||||
package amqp |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"io" |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
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
|
|
||||||
} |
|
||||||
|
|
||||||
// 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 } |
|
||||||
@ -1,170 +0,0 @@ |
|||||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// Source code and contact info at http://github.com/streadway/amqp
|
|
||||||
|
|
||||||
package amqp |
|
||||||
|
|
||||||
import ( |
|
||||||
"errors" |
|
||||||
"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)) |
|
||||||
} |
|
||||||
@ -1,328 +0,0 @@ |
|||||||
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) |
|
||||||
} |
|
||||||
} |
|
||||||
@ -1,411 +0,0 @@ |
|||||||
// Copyright (c) 2012, Sean Treadway, SoundCloud Ltd.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
// Source code and contact info at http://github.com/streadway/amqp
|
|
||||||
|
|
||||||
package amqp |
|
||||||
|
|
||||||
import ( |
|
||||||
"bufio" |
|
||||||
"bytes" |
|
||||||
"encoding/binary" |
|
||||||
"errors" |
|
||||||
"io" |
|
||||||
"math" |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
func (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())) |
|
||||||
} |
|
||||||
Loading…
Reference in new issue