├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── _examples ├── pubsub │ └── pubsub.go ├── simple-consumer │ └── consumer.go └── simple-producer │ └── producer.go ├── allocator.go ├── allocator_test.go ├── auth.go ├── certs.sh ├── channel.go ├── client_test.go ├── confirms.go ├── confirms_test.go ├── connection.go ├── connection_test.go ├── consumers.go ├── consumers_test.go ├── delivery.go ├── delivery_test.go ├── doc.go ├── example_client_test.go ├── examples_test.go ├── fuzz.go ├── gen.sh ├── go.mod ├── integration_test.go ├── pre-commit ├── read.go ├── read_test.go ├── reconnect_test.go ├── return.go ├── shared_test.go ├── spec ├── amqp0-9-1.stripped.extended.xml └── gen.go ├── spec091.go ├── tls_test.go ├── types.go ├── uri.go ├── uri_test.go └── write.go /.gitignore: -------------------------------------------------------------------------------- 1 | certs/* 2 | spec/spec 3 | examples/simple-consumer/simple-consumer 4 | examples/simple-producer/simple-producer 5 | 6 | .idea/**/workspace.xml 7 | .idea/**/tasks.xml 8 | .idea/**/usage.statistics.xml 9 | .idea/**/dictionaries 10 | .idea/**/shelf 11 | 12 | .idea/**/contentModel.xml 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.10.x 5 | - 1.11.x 6 | - 1.12.x 7 | - 1.13.x 8 | 9 | addons: 10 | apt: 11 | packages: 12 | - rabbitmq-server 13 | 14 | services: 15 | - rabbitmq 16 | 17 | env: 18 | - GO111MODULE=on AMQP_URL=amqp://guest:guest@127.0.0.1:5672/ 19 | 20 | before_install: 21 | - go get -v golang.org/x/lint/golint 22 | 23 | script: 24 | - ./pre-commit 25 | - go test -cpu=1,2 -v -tags integration ./... 26 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Prequisites 2 | 3 | 1. Go: [https://golang.org/dl/](https://golang.org/dl/) 4 | 1. Golint `go get -u -v github.com/golang/lint/golint` 5 | 6 | ## Contributing 7 | 8 | The workflow is pretty standard: 9 | 10 | 1. Fork github.com/streadway/amqp 11 | 1. Add the pre-commit hook: `ln -s ../../pre-commit .git/hooks/pre-commit` 12 | 1. Create your feature branch (`git checkout -b my-new-feature`) 13 | 1. Run integration tests (see below) 14 | 1. **Implement tests** 15 | 1. Implement fixs 16 | 1. Commit your changes (`git commit -am 'Add some feature'`) 17 | 1. Push to a branch (`git push -u origin my-new-feature`) 18 | 1. Submit a pull request 19 | 20 | ## Running Tests 21 | 22 | The test suite assumes that: 23 | 24 | * A RabbitMQ node is running on localhost with all defaults: [https://www.rabbitmq.com/download.html](https://www.rabbitmq.com/download.html) 25 | * `AMQP_URL` is exported to `amqp://guest:guest@127.0.0.1:5672/` 26 | 27 | ### Integration Tests 28 | 29 | After starting a local RabbitMQ, run integration tests with the following: 30 | 31 | env AMQP_URL=amqp://guest:guest@127.0.0.1:5672/ go test -v -cpu 2 -tags integration -race 32 | 33 | All integration tests should use the `integrationConnection(...)` test 34 | helpers defined in `integration_test.go` to setup the integration environment 35 | and logging. 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2019, Sean Treadway, SoundCloud Ltd. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | Redistributions in binary form must reproduce the above copyright notice, this 11 | list of conditions and the following disclaimer in the documentation and/or 12 | other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://api.travis-ci.org/streadway/amqp.svg)](http://travis-ci.org/streadway/amqp) [![GoDoc](https://godoc.org/github.com/streadway/amqp?status.svg)](http://godoc.org/github.com/streadway/amqp) 2 | 3 | # Go RabbitMQ Client Library (Unmaintained Fork) 4 | 5 | ## Beware of Abandonware 6 | 7 | This repository is **NOT ACTIVELY MAINTAINED**. Consider using 8 | a different fork instead: [rabbitmq/amqp091-go](https://github.com/rabbitmq/amqp091-go). 9 | In case of questions, start a discussion in that repo or [use other RabbitMQ community resources](https://rabbitmq.com/contact.html). 10 | 11 | 12 | 13 | ## Project Maturity 14 | 15 | This project has been used in production systems for many years. As of 2022, 16 | this repository is **NOT ACTIVELY MAINTAINED**. 17 | 18 | This repository is **very strict** about any potential public API changes. 19 | You may want to consider [rabbitmq/amqp091-go](https://github.com/rabbitmq/amqp091-go) which 20 | is more willing to adapt the API. 21 | 22 | 23 | ## Supported Go Versions 24 | 25 | This library supports two most recent Go release series, currently 1.10 and 1.11. 26 | 27 | 28 | ## Supported RabbitMQ Versions 29 | 30 | This project supports RabbitMQ versions starting with `2.0` but primarily tested 31 | against reasonably recent `3.x` releases. Some features and behaviours may be 32 | server version-specific. 33 | 34 | ## Goals 35 | 36 | Provide a functional interface that closely represents the AMQP 0.9.1 model 37 | targeted to RabbitMQ as a server. This includes the minimum necessary to 38 | interact the semantics of the protocol. 39 | 40 | ## Non-goals 41 | 42 | Things not intended to be supported. 43 | 44 | * Auto reconnect and re-synchronization of client and server topologies. 45 | * Reconnection would require understanding the error paths when the 46 | topology cannot be declared on reconnect. This would require a new set 47 | of types and code paths that are best suited at the call-site of this 48 | package. AMQP has a dynamic topology that needs all peers to agree. If 49 | this doesn't happen, the behavior is undefined. Instead of producing a 50 | possible interface with undefined behavior, this package is designed to 51 | be simple for the caller to implement the necessary connection-time 52 | topology declaration so that reconnection is trivial and encapsulated in 53 | the caller's application code. 54 | * AMQP Protocol negotiation for forward or backward compatibility. 55 | * 0.9.1 is stable and widely deployed. Versions 0.10 and 1.0 are divergent 56 | specifications that change the semantics and wire format of the protocol. 57 | We will accept patches for other protocol support but have no plans for 58 | implementation ourselves. 59 | * Anything other than PLAIN and EXTERNAL authentication mechanisms. 60 | * Keeping the mechanisms interface modular makes it possible to extend 61 | outside of this package. If other mechanisms prove to be popular, then 62 | we would accept patches to include them in this package. 63 | 64 | ## Usage 65 | 66 | See the 'examples' subdirectory for simple producers and consumers executables. 67 | If you have a use-case in mind which isn't well-represented by the examples, 68 | please file an issue. 69 | 70 | ## Documentation 71 | 72 | Use [Godoc documentation](http://godoc.org/github.com/streadway/amqp) for 73 | reference and usage. 74 | 75 | [RabbitMQ tutorials in 76 | Go](https://github.com/rabbitmq/rabbitmq-tutorials/tree/master/go) are also 77 | available. 78 | 79 | ## Contributing 80 | 81 | Pull requests are very much welcomed. Create your pull request on a non-master 82 | branch, make sure a test or example is included that covers your change and 83 | your commits represent coherent changes that include a reason for the change. 84 | 85 | To run the integration tests, make sure you have RabbitMQ running on any host, 86 | export the environment variable `AMQP_URL=amqp://host/` and run `go test -tags 87 | integration`. TravisCI will also run the integration tests. 88 | 89 | Thanks to the [community of contributors](https://github.com/streadway/amqp/graphs/contributors). 90 | 91 | ## External packages 92 | 93 | * [Google App Engine Dialer support](https://github.com/soundtrackyourbrand/gaeamqp) 94 | * [RabbitMQ examples in Go](https://github.com/rabbitmq/rabbitmq-tutorials/tree/master/go) 95 | 96 | ## License 97 | 98 | BSD 2 clause - see LICENSE for more details. 99 | 100 | 101 | -------------------------------------------------------------------------------- /_examples/pubsub/pubsub.go: -------------------------------------------------------------------------------- 1 | // Command pubsub is an example of a fanout exchange with dynamic reliable 2 | // membership, reading from stdin, writing to stdout. 3 | // 4 | // This example shows how to implement reconnect logic independent from a 5 | // publish/subscribe loop with bridges to application types. 6 | 7 | package main 8 | 9 | import ( 10 | "bufio" 11 | "crypto/sha1" 12 | "flag" 13 | "fmt" 14 | "io" 15 | "log" 16 | "os" 17 | 18 | "github.com/streadway/amqp" 19 | "golang.org/x/net/context" 20 | ) 21 | 22 | var url = flag.String("url", "amqp:///", "AMQP url for both the publisher and subscriber") 23 | 24 | // exchange binds the publishers to the subscribers 25 | const exchange = "pubsub" 26 | 27 | // message is the application type for a message. This can contain identity, 28 | // or a reference to the recevier chan for further demuxing. 29 | type message []byte 30 | 31 | // session composes an amqp.Connection with an amqp.Channel 32 | type session struct { 33 | *amqp.Connection 34 | *amqp.Channel 35 | } 36 | 37 | // Close tears the connection down, taking the channel with it. 38 | func (s session) Close() error { 39 | if s.Connection == nil { 40 | return nil 41 | } 42 | return s.Connection.Close() 43 | } 44 | 45 | // redial continually connects to the URL, exiting the program when no longer possible 46 | func redial(ctx context.Context, url string) chan chan session { 47 | sessions := make(chan chan session) 48 | 49 | go func() { 50 | sess := make(chan session) 51 | defer close(sessions) 52 | 53 | for { 54 | select { 55 | case sessions <- sess: 56 | case <-ctx.Done(): 57 | log.Println("shutting down session factory") 58 | return 59 | } 60 | 61 | conn, err := amqp.Dial(url) 62 | if err != nil { 63 | log.Fatalf("cannot (re)dial: %v: %q", err, url) 64 | } 65 | 66 | ch, err := conn.Channel() 67 | if err != nil { 68 | log.Fatalf("cannot create channel: %v", err) 69 | } 70 | 71 | if err := ch.ExchangeDeclare(exchange, "fanout", false, true, false, false, nil); err != nil { 72 | log.Fatalf("cannot declare fanout exchange: %v", err) 73 | } 74 | 75 | select { 76 | case sess <- session{conn, ch}: 77 | case <-ctx.Done(): 78 | log.Println("shutting down new session") 79 | return 80 | } 81 | } 82 | }() 83 | 84 | return sessions 85 | } 86 | 87 | // publish publishes messages to a reconnecting session to a fanout exchange. 88 | // It receives from the application specific source of messages. 89 | func publish(sessions chan chan session, messages <-chan message) { 90 | for session := range sessions { 91 | var ( 92 | running bool 93 | reading = messages 94 | pending = make(chan message, 1) 95 | confirm = make(chan amqp.Confirmation, 1) 96 | ) 97 | 98 | pub := <-session 99 | 100 | // publisher confirms for this channel/connection 101 | if err := pub.Confirm(false); err != nil { 102 | log.Printf("publisher confirms not supported") 103 | close(confirm) // confirms not supported, simulate by always nacking 104 | } else { 105 | pub.NotifyPublish(confirm) 106 | } 107 | 108 | log.Printf("publishing...") 109 | 110 | Publish: 111 | for { 112 | var body message 113 | select { 114 | case confirmed, ok := <-confirm: 115 | if !ok { 116 | break Publish 117 | } 118 | if !confirmed.Ack { 119 | log.Printf("nack message %d, body: %q", confirmed.DeliveryTag, string(body)) 120 | } 121 | reading = messages 122 | 123 | case body = <-pending: 124 | routingKey := "ignored for fanout exchanges, application dependent for other exchanges" 125 | err := pub.Publish(exchange, routingKey, false, false, amqp.Publishing{ 126 | Body: body, 127 | }) 128 | // Retry failed delivery on the next session 129 | if err != nil { 130 | pending <- body 131 | pub.Close() 132 | break Publish 133 | } 134 | 135 | case body, running = <-reading: 136 | // all messages consumed 137 | if !running { 138 | return 139 | } 140 | // work on pending delivery until ack'd 141 | pending <- body 142 | reading = nil 143 | } 144 | } 145 | } 146 | } 147 | 148 | // identity returns the same host/process unique string for the lifetime of 149 | // this process so that subscriber reconnections reuse the same queue name. 150 | func identity() string { 151 | hostname, err := os.Hostname() 152 | h := sha1.New() 153 | fmt.Fprint(h, hostname) 154 | fmt.Fprint(h, err) 155 | fmt.Fprint(h, os.Getpid()) 156 | return fmt.Sprintf("%x", h.Sum(nil)) 157 | } 158 | 159 | // subscribe consumes deliveries from an exclusive queue from a fanout exchange and sends to the application specific messages chan. 160 | func subscribe(sessions chan chan session, messages chan<- message) { 161 | queue := identity() 162 | 163 | for session := range sessions { 164 | sub := <-session 165 | 166 | if _, err := sub.QueueDeclare(queue, false, true, true, false, nil); err != nil { 167 | log.Printf("cannot consume from exclusive queue: %q, %v", queue, err) 168 | return 169 | } 170 | 171 | routingKey := "application specific routing key for fancy toplogies" 172 | if err := sub.QueueBind(queue, routingKey, exchange, false, nil); err != nil { 173 | log.Printf("cannot consume without a binding to exchange: %q, %v", exchange, err) 174 | return 175 | } 176 | 177 | deliveries, err := sub.Consume(queue, "", false, true, false, false, nil) 178 | if err != nil { 179 | log.Printf("cannot consume from: %q, %v", queue, err) 180 | return 181 | } 182 | 183 | log.Printf("subscribed...") 184 | 185 | for msg := range deliveries { 186 | messages <- message(msg.Body) 187 | sub.Ack(msg.DeliveryTag, false) 188 | } 189 | } 190 | } 191 | 192 | // read is this application's translation to the message format, scanning from 193 | // stdin. 194 | func read(r io.Reader) <-chan message { 195 | lines := make(chan message) 196 | go func() { 197 | defer close(lines) 198 | scan := bufio.NewScanner(r) 199 | for scan.Scan() { 200 | lines <- message(scan.Bytes()) 201 | } 202 | }() 203 | return lines 204 | } 205 | 206 | // write is this application's subscriber of application messages, printing to 207 | // stdout. 208 | func write(w io.Writer) chan<- message { 209 | lines := make(chan message) 210 | go func() { 211 | for line := range lines { 212 | fmt.Fprintln(w, string(line)) 213 | } 214 | }() 215 | return lines 216 | } 217 | 218 | func main() { 219 | flag.Parse() 220 | 221 | ctx, done := context.WithCancel(context.Background()) 222 | 223 | go func() { 224 | publish(redial(ctx, *url), read(os.Stdin)) 225 | done() 226 | }() 227 | 228 | go func() { 229 | subscribe(redial(ctx, *url), write(os.Stdout)) 230 | done() 231 | }() 232 | 233 | <-ctx.Done() 234 | } 235 | -------------------------------------------------------------------------------- /_examples/simple-consumer/consumer.go: -------------------------------------------------------------------------------- 1 | // This example declares a durable Exchange, an ephemeral (auto-delete) Queue, 2 | // binds the Queue to the Exchange with a binding key, and consumes every 3 | // message published to that Exchange with that routing key. 4 | // 5 | package main 6 | 7 | import ( 8 | "flag" 9 | "fmt" 10 | "github.com/streadway/amqp" 11 | "log" 12 | "time" 13 | ) 14 | 15 | var ( 16 | uri = flag.String("uri", "amqp://guest:guest@localhost:5672/", "AMQP URI") 17 | exchange = flag.String("exchange", "test-exchange", "Durable, non-auto-deleted AMQP exchange name") 18 | exchangeType = flag.String("exchange-type", "direct", "Exchange type - direct|fanout|topic|x-custom") 19 | queue = flag.String("queue", "test-queue", "Ephemeral AMQP queue name") 20 | bindingKey = flag.String("key", "test-key", "AMQP binding key") 21 | consumerTag = flag.String("consumer-tag", "simple-consumer", "AMQP consumer tag (should not be blank)") 22 | lifetime = flag.Duration("lifetime", 5*time.Second, "lifetime of process before shutdown (0s=infinite)") 23 | ) 24 | 25 | func init() { 26 | flag.Parse() 27 | } 28 | 29 | func main() { 30 | c, err := NewConsumer(*uri, *exchange, *exchangeType, *queue, *bindingKey, *consumerTag) 31 | if err != nil { 32 | log.Fatalf("%s", err) 33 | } 34 | 35 | if *lifetime > 0 { 36 | log.Printf("running for %s", *lifetime) 37 | time.Sleep(*lifetime) 38 | } else { 39 | log.Printf("running forever") 40 | select {} 41 | } 42 | 43 | log.Printf("shutting down") 44 | 45 | if err := c.Shutdown(); err != nil { 46 | log.Fatalf("error during shutdown: %s", err) 47 | } 48 | } 49 | 50 | type Consumer struct { 51 | conn *amqp.Connection 52 | channel *amqp.Channel 53 | tag string 54 | done chan error 55 | } 56 | 57 | func NewConsumer(amqpURI, exchange, exchangeType, queueName, key, ctag string) (*Consumer, error) { 58 | c := &Consumer{ 59 | conn: nil, 60 | channel: nil, 61 | tag: ctag, 62 | done: make(chan error), 63 | } 64 | 65 | var err error 66 | 67 | log.Printf("dialing %q", amqpURI) 68 | c.conn, err = amqp.Dial(amqpURI) 69 | if err != nil { 70 | return nil, fmt.Errorf("Dial: %s", err) 71 | } 72 | 73 | go func() { 74 | fmt.Printf("closing: %s", <-c.conn.NotifyClose(make(chan *amqp.Error))) 75 | }() 76 | 77 | log.Printf("got Connection, getting Channel") 78 | c.channel, err = c.conn.Channel() 79 | if err != nil { 80 | return nil, fmt.Errorf("Channel: %s", err) 81 | } 82 | 83 | log.Printf("got Channel, declaring Exchange (%q)", exchange) 84 | if err = c.channel.ExchangeDeclare( 85 | exchange, // name of the exchange 86 | exchangeType, // type 87 | true, // durable 88 | false, // delete when complete 89 | false, // internal 90 | false, // noWait 91 | nil, // arguments 92 | ); err != nil { 93 | return nil, fmt.Errorf("Exchange Declare: %s", err) 94 | } 95 | 96 | log.Printf("declared Exchange, declaring Queue %q", queueName) 97 | queue, err := c.channel.QueueDeclare( 98 | queueName, // name of the queue 99 | true, // durable 100 | false, // delete when unused 101 | false, // exclusive 102 | false, // noWait 103 | nil, // arguments 104 | ) 105 | if err != nil { 106 | return nil, fmt.Errorf("Queue Declare: %s", err) 107 | } 108 | 109 | log.Printf("declared Queue (%q %d messages, %d consumers), binding to Exchange (key %q)", 110 | queue.Name, queue.Messages, queue.Consumers, key) 111 | 112 | if err = c.channel.QueueBind( 113 | queue.Name, // name of the queue 114 | key, // bindingKey 115 | exchange, // sourceExchange 116 | false, // noWait 117 | nil, // arguments 118 | ); err != nil { 119 | return nil, fmt.Errorf("Queue Bind: %s", err) 120 | } 121 | 122 | log.Printf("Queue bound to Exchange, starting Consume (consumer tag %q)", c.tag) 123 | deliveries, err := c.channel.Consume( 124 | queue.Name, // name 125 | c.tag, // consumerTag, 126 | false, // noAck 127 | false, // exclusive 128 | false, // noLocal 129 | false, // noWait 130 | nil, // arguments 131 | ) 132 | if err != nil { 133 | return nil, fmt.Errorf("Queue Consume: %s", err) 134 | } 135 | 136 | go handle(deliveries, c.done) 137 | 138 | return c, nil 139 | } 140 | 141 | func (c *Consumer) Shutdown() error { 142 | // will close() the deliveries channel 143 | if err := c.channel.Cancel(c.tag, true); err != nil { 144 | return fmt.Errorf("Consumer cancel failed: %s", err) 145 | } 146 | 147 | if err := c.conn.Close(); err != nil { 148 | return fmt.Errorf("AMQP connection close error: %s", err) 149 | } 150 | 151 | defer log.Printf("AMQP shutdown OK") 152 | 153 | // wait for handle() to exit 154 | return <-c.done 155 | } 156 | 157 | func handle(deliveries <-chan amqp.Delivery, done chan error) { 158 | for d := range deliveries { 159 | log.Printf( 160 | "got %dB delivery: [%v] %q", 161 | len(d.Body), 162 | d.DeliveryTag, 163 | d.Body, 164 | ) 165 | d.Ack(false) 166 | } 167 | log.Printf("handle: deliveries channel closed") 168 | done <- nil 169 | } 170 | -------------------------------------------------------------------------------- /_examples/simple-producer/producer.go: -------------------------------------------------------------------------------- 1 | // This example declares a durable Exchange, and publishes a single message to 2 | // that Exchange with a given routing key. 3 | // 4 | package main 5 | 6 | import ( 7 | "flag" 8 | "fmt" 9 | "log" 10 | 11 | "github.com/streadway/amqp" 12 | ) 13 | 14 | var ( 15 | uri = flag.String("uri", "amqp://guest:guest@localhost:5672/", "AMQP URI") 16 | exchangeName = flag.String("exchange", "test-exchange", "Durable AMQP exchange name") 17 | exchangeType = flag.String("exchange-type", "direct", "Exchange type - direct|fanout|topic|x-custom") 18 | routingKey = flag.String("key", "test-key", "AMQP routing key") 19 | body = flag.String("body", "foobar", "Body of message") 20 | reliable = flag.Bool("reliable", true, "Wait for the publisher confirmation before exiting") 21 | ) 22 | 23 | func init() { 24 | flag.Parse() 25 | } 26 | 27 | func main() { 28 | if err := publish(*uri, *exchangeName, *exchangeType, *routingKey, *body, *reliable); err != nil { 29 | log.Fatalf("%s", err) 30 | } 31 | log.Printf("published %dB OK", len(*body)) 32 | } 33 | 34 | func publish(amqpURI, exchange, exchangeType, routingKey, body string, reliable bool) error { 35 | 36 | // This function dials, connects, declares, publishes, and tears down, 37 | // all in one go. In a real service, you probably want to maintain a 38 | // long-lived connection as state, and publish against that. 39 | 40 | log.Printf("dialing %q", amqpURI) 41 | connection, err := amqp.Dial(amqpURI) 42 | if err != nil { 43 | return fmt.Errorf("Dial: %s", err) 44 | } 45 | defer connection.Close() 46 | 47 | log.Printf("got Connection, getting Channel") 48 | channel, err := connection.Channel() 49 | if err != nil { 50 | return fmt.Errorf("Channel: %s", err) 51 | } 52 | 53 | log.Printf("got Channel, declaring %q Exchange (%q)", exchangeType, exchange) 54 | if err := channel.ExchangeDeclare( 55 | exchange, // name 56 | exchangeType, // type 57 | true, // durable 58 | false, // auto-deleted 59 | false, // internal 60 | false, // noWait 61 | nil, // arguments 62 | ); err != nil { 63 | return fmt.Errorf("Exchange Declare: %s", err) 64 | } 65 | 66 | // Reliable publisher confirms require confirm.select support from the 67 | // connection. 68 | if reliable { 69 | log.Printf("enabling publishing confirms.") 70 | if err := channel.Confirm(false); err != nil { 71 | return fmt.Errorf("Channel could not be put into confirm mode: %s", err) 72 | } 73 | 74 | confirms := channel.NotifyPublish(make(chan amqp.Confirmation, 1)) 75 | 76 | defer confirmOne(confirms) 77 | } 78 | 79 | log.Printf("declared Exchange, publishing %dB body (%q)", len(body), body) 80 | if err = channel.Publish( 81 | exchange, // publish to an exchange 82 | routingKey, // routing to 0 or more queues 83 | false, // mandatory 84 | false, // immediate 85 | amqp.Publishing{ 86 | Headers: amqp.Table{}, 87 | ContentType: "text/plain", 88 | ContentEncoding: "", 89 | Body: []byte(body), 90 | DeliveryMode: amqp.Transient, // 1=non-persistent, 2=persistent 91 | Priority: 0, // 0-9 92 | // a bunch of application/implementation-specific fields 93 | }, 94 | ); err != nil { 95 | return fmt.Errorf("Exchange Publish: %s", err) 96 | } 97 | 98 | return nil 99 | } 100 | 101 | // One would typically keep a channel of publishings, a sequence number, and a 102 | // set of unacknowledged sequence numbers and loop until the publishing channel 103 | // is closed. 104 | func confirmOne(confirms <-chan amqp.Confirmation) { 105 | log.Printf("waiting for confirmation of one publishing") 106 | 107 | if confirmed := <-confirms; confirmed.Ack { 108 | log.Printf("confirmed delivery with delivery tag: %d", confirmed.DeliveryTag) 109 | } else { 110 | log.Printf("failed delivery of delivery tag: %d", confirmed.DeliveryTag) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /allocator.go: -------------------------------------------------------------------------------- 1 | package amqp 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "math/big" 7 | ) 8 | 9 | const ( 10 | free = 0 11 | allocated = 1 12 | ) 13 | 14 | // allocator maintains a bitset of allocated numbers. 15 | type allocator struct { 16 | pool *big.Int 17 | last int 18 | low int 19 | high int 20 | } 21 | 22 | // NewAllocator reserves and frees integers out of a range between low and 23 | // high. 24 | // 25 | // O(N) worst case space used, where N is maximum allocated, divided by 26 | // sizeof(big.Word) 27 | func newAllocator(low, high int) *allocator { 28 | return &allocator{ 29 | pool: big.NewInt(0), 30 | last: low, 31 | low: low, 32 | high: high, 33 | } 34 | } 35 | 36 | // String returns a string describing the contents of the allocator like 37 | // "allocator[low..high] reserved..until" 38 | // 39 | // O(N) where N is high-low 40 | func (a allocator) String() string { 41 | b := &bytes.Buffer{} 42 | fmt.Fprintf(b, "allocator[%d..%d]", a.low, a.high) 43 | 44 | for low := a.low; low <= a.high; low++ { 45 | high := low 46 | for a.reserved(high) && high <= a.high { 47 | high++ 48 | } 49 | 50 | if high > low+1 { 51 | fmt.Fprintf(b, " %d..%d", low, high-1) 52 | } else if high > low { 53 | fmt.Fprintf(b, " %d", high-1) 54 | } 55 | 56 | low = high 57 | } 58 | return b.String() 59 | } 60 | 61 | // Next reserves and returns the next available number out of the range between 62 | // low and high. If no number is available, false is returned. 63 | // 64 | // O(N) worst case runtime where N is allocated, but usually O(1) due to a 65 | // rolling index into the oldest allocation. 66 | func (a *allocator) next() (int, bool) { 67 | wrapped := a.last 68 | 69 | // Find trailing bit 70 | for ; a.last <= a.high; a.last++ { 71 | if a.reserve(a.last) { 72 | return a.last, true 73 | } 74 | } 75 | 76 | // Find preceding free'd pool 77 | a.last = a.low 78 | 79 | for ; a.last < wrapped; a.last++ { 80 | if a.reserve(a.last) { 81 | return a.last, true 82 | } 83 | } 84 | 85 | return 0, false 86 | } 87 | 88 | // reserve claims the bit if it is not already claimed, returning true if 89 | // successfully claimed. 90 | func (a *allocator) reserve(n int) bool { 91 | if a.reserved(n) { 92 | return false 93 | } 94 | a.pool.SetBit(a.pool, n-a.low, allocated) 95 | return true 96 | } 97 | 98 | // reserved returns true if the integer has been allocated 99 | func (a *allocator) reserved(n int) bool { 100 | return a.pool.Bit(n-a.low) == allocated 101 | } 102 | 103 | // release frees the use of the number for another allocation 104 | func (a *allocator) release(n int) { 105 | a.pool.SetBit(a.pool, n-a.low, free) 106 | } 107 | -------------------------------------------------------------------------------- /allocator_test.go: -------------------------------------------------------------------------------- 1 | package amqp 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | ) 7 | 8 | func TestAllocatorFirstShouldBeTheLow(t *testing.T) { 9 | n, ok := newAllocator(1, 2).next() 10 | if !ok { 11 | t.Fatalf("expected to allocate between 1 and 2") 12 | } 13 | 14 | if want, got := 1, n; want != got { 15 | t.Fatalf("expected to first allocation to be 1") 16 | } 17 | } 18 | 19 | func TestAllocatorShouldBeBoundByHigh(t *testing.T) { 20 | a := newAllocator(1, 2) 21 | 22 | if n, ok := a.next(); n != 1 || !ok { 23 | t.Fatalf("expected to allocate between 1 and 2, got %d, %v", n, ok) 24 | } 25 | if n, ok := a.next(); n != 2 || !ok { 26 | t.Fatalf("expected to allocate between 1 and 2, got %d, %v", n, ok) 27 | } 28 | if _, ok := a.next(); ok { 29 | t.Fatalf("expected not to allocate outside of 1 and 2") 30 | } 31 | } 32 | 33 | func TestAllocatorStringShouldIncludeAllocatedRanges(t *testing.T) { 34 | a := newAllocator(1, 10) 35 | a.reserve(1) 36 | a.reserve(2) 37 | a.reserve(3) 38 | a.reserve(5) 39 | a.reserve(6) 40 | a.reserve(8) 41 | a.reserve(10) 42 | 43 | if want, got := "allocator[1..10] 1..3 5..6 8 10", a.String(); want != got { 44 | t.Fatalf("expected String of %q, got %q", want, got) 45 | } 46 | } 47 | 48 | func TestAllocatorShouldReuseReleased(t *testing.T) { 49 | a := newAllocator(1, 2) 50 | 51 | first, _ := a.next() 52 | if want, got := 1, first; want != got { 53 | t.Fatalf("expected allocation to be %d, got: %d", want, got) 54 | } 55 | 56 | second, _ := a.next() 57 | if want, got := 2, second; want != got { 58 | t.Fatalf("expected allocation to be %d, got: %d", want, got) 59 | } 60 | 61 | a.release(first) 62 | 63 | third, _ := a.next() 64 | if want, got := first, third; want != got { 65 | t.Fatalf("expected third allocation to be %d, got: %d", want, got) 66 | } 67 | 68 | _, ok := a.next() 69 | if want, got := false, ok; want != got { 70 | t.Fatalf("expected fourth allocation to saturate the pool") 71 | } 72 | } 73 | 74 | func TestAllocatorReleasesKeepUpWithAllocationsForAllSizes(t *testing.T) { 75 | if testing.Short() { 76 | t.Skip() 77 | } 78 | 79 | const runs = 5 80 | const max = 13 81 | 82 | for lim := 1; lim < 2<= lim { // fills the allocator 87 | a.release(int(rand.Int63n(int64(lim)))) 88 | } 89 | if _, ok := a.next(); !ok { 90 | t.Fatalf("expected %d runs of random release of size %d not to fail on allocation %d", runs, lim, i) 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /auth.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // Source code and contact info at http://github.com/streadway/amqp 5 | 6 | package amqp 7 | 8 | import ( 9 | "fmt" 10 | ) 11 | 12 | // Authentication interface provides a means for different SASL authentication 13 | // mechanisms to be used during connection tuning. 14 | type Authentication interface { 15 | Mechanism() string 16 | Response() string 17 | } 18 | 19 | // PlainAuth is a similar to Basic Auth in HTTP. 20 | type PlainAuth struct { 21 | Username string 22 | Password string 23 | } 24 | 25 | // Mechanism returns "PLAIN" 26 | func (auth *PlainAuth) Mechanism() string { 27 | return "PLAIN" 28 | } 29 | 30 | // Response returns the null character delimited encoding for the SASL PLAIN Mechanism. 31 | func (auth *PlainAuth) Response() string { 32 | return fmt.Sprintf("\000%s\000%s", auth.Username, auth.Password) 33 | } 34 | 35 | // AMQPlainAuth is similar to PlainAuth 36 | type AMQPlainAuth struct { 37 | Username string 38 | Password string 39 | } 40 | 41 | // Mechanism returns "AMQPLAIN" 42 | func (auth *AMQPlainAuth) Mechanism() string { 43 | return "AMQPLAIN" 44 | } 45 | 46 | // Response returns the null character delimited encoding for the SASL PLAIN Mechanism. 47 | func (auth *AMQPlainAuth) Response() string { 48 | return fmt.Sprintf("LOGIN:%sPASSWORD:%s", auth.Username, auth.Password) 49 | } 50 | 51 | // Finds the first mechanism preferred by the client that the server supports. 52 | func pickSASLMechanism(client []Authentication, serverMechanisms []string) (auth Authentication, ok bool) { 53 | for _, auth = range client { 54 | for _, mech := range serverMechanisms { 55 | if auth.Mechanism() == mech { 56 | return auth, true 57 | } 58 | } 59 | } 60 | 61 | return 62 | } 63 | -------------------------------------------------------------------------------- /certs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Creates the CA, server and client certs to be used by tls_test.go 4 | # http://www.rabbitmq.com/ssl.html 5 | # 6 | # Copy stdout into the const section of tls_test.go or use for RabbitMQ 7 | # 8 | root=$PWD/certs 9 | 10 | if [ -f $root/ca/serial ]; then 11 | echo >&2 "Previous installation found" 12 | echo >&2 "Remove $root/ca and rerun to overwrite" 13 | exit 1 14 | fi 15 | 16 | mkdir -p $root/ca/private 17 | mkdir -p $root/ca/certs 18 | mkdir -p $root/server 19 | mkdir -p $root/client 20 | 21 | cd $root/ca 22 | 23 | chmod 700 private 24 | touch index.txt 25 | echo 'unique_subject = no' > index.txt.attr 26 | echo '01' > serial 27 | echo >openssl.cnf ' 28 | [ ca ] 29 | default_ca = testca 30 | 31 | [ testca ] 32 | dir = . 33 | certificate = $dir/cacert.pem 34 | database = $dir/index.txt 35 | new_certs_dir = $dir/certs 36 | private_key = $dir/private/cakey.pem 37 | serial = $dir/serial 38 | 39 | default_crl_days = 7 40 | default_days = 3650 41 | default_md = sha1 42 | 43 | policy = testca_policy 44 | x509_extensions = certificate_extensions 45 | 46 | [ testca_policy ] 47 | commonName = supplied 48 | stateOrProvinceName = optional 49 | countryName = optional 50 | emailAddress = optional 51 | organizationName = optional 52 | organizationalUnitName = optional 53 | 54 | [ certificate_extensions ] 55 | basicConstraints = CA:false 56 | 57 | [ req ] 58 | default_bits = 2048 59 | default_keyfile = ./private/cakey.pem 60 | default_md = sha1 61 | prompt = yes 62 | distinguished_name = root_ca_distinguished_name 63 | x509_extensions = root_ca_extensions 64 | 65 | [ root_ca_distinguished_name ] 66 | commonName = hostname 67 | 68 | [ root_ca_extensions ] 69 | basicConstraints = CA:true 70 | keyUsage = keyCertSign, cRLSign 71 | 72 | [ client_ca_extensions ] 73 | basicConstraints = CA:false 74 | keyUsage = digitalSignature 75 | extendedKeyUsage = 1.3.6.1.5.5.7.3.2 76 | 77 | [ server_ca_extensions ] 78 | basicConstraints = CA:false 79 | keyUsage = keyEncipherment 80 | extendedKeyUsage = 1.3.6.1.5.5.7.3.1 81 | subjectAltName = @alt_names 82 | 83 | [ alt_names ] 84 | IP.1 = 127.0.0.1 85 | ' 86 | 87 | openssl req \ 88 | -x509 \ 89 | -nodes \ 90 | -config openssl.cnf \ 91 | -newkey rsa:2048 \ 92 | -days 3650 \ 93 | -subj "/CN=MyTestCA/" \ 94 | -out cacert.pem \ 95 | -outform PEM 96 | 97 | openssl x509 \ 98 | -in cacert.pem \ 99 | -out cacert.cer \ 100 | -outform DER 101 | 102 | openssl genrsa -out $root/server/key.pem 2048 103 | openssl genrsa -out $root/client/key.pem 2048 104 | 105 | openssl req \ 106 | -new \ 107 | -nodes \ 108 | -config openssl.cnf \ 109 | -subj "/CN=127.0.0.1/O=server/" \ 110 | -key $root/server/key.pem \ 111 | -out $root/server/req.pem \ 112 | -outform PEM 113 | 114 | openssl req \ 115 | -new \ 116 | -nodes \ 117 | -config openssl.cnf \ 118 | -subj "/CN=127.0.0.1/O=client/" \ 119 | -key $root/client/key.pem \ 120 | -out $root/client/req.pem \ 121 | -outform PEM 122 | 123 | openssl ca \ 124 | -config openssl.cnf \ 125 | -in $root/server/req.pem \ 126 | -out $root/server/cert.pem \ 127 | -notext \ 128 | -batch \ 129 | -extensions server_ca_extensions 130 | 131 | openssl ca \ 132 | -config openssl.cnf \ 133 | -in $root/client/req.pem \ 134 | -out $root/client/cert.pem \ 135 | -notext \ 136 | -batch \ 137 | -extensions client_ca_extensions 138 | 139 | cat <<-END 140 | const caCert = \` 141 | `cat $root/ca/cacert.pem` 142 | \` 143 | 144 | const serverCert = \` 145 | `cat $root/server/cert.pem` 146 | \` 147 | 148 | const serverKey = \` 149 | `cat $root/server/key.pem` 150 | \` 151 | 152 | const clientCert = \` 153 | `cat $root/client/cert.pem` 154 | \` 155 | 156 | const clientKey = \` 157 | `cat $root/client/key.pem` 158 | \` 159 | END 160 | -------------------------------------------------------------------------------- /client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // Source code and contact info at http://github.com/streadway/amqp 5 | 6 | package amqp 7 | 8 | import ( 9 | "bytes" 10 | "io" 11 | "reflect" 12 | "testing" 13 | "time" 14 | ) 15 | 16 | type server struct { 17 | *testing.T 18 | r reader // framer <- client 19 | w writer // framer -> client 20 | S io.ReadWriteCloser // Server IO 21 | C io.ReadWriteCloser // Client IO 22 | 23 | // captured client frames 24 | start connectionStartOk 25 | tune connectionTuneOk 26 | } 27 | 28 | func defaultConfig() Config { 29 | return Config{ 30 | SASL: []Authentication{&PlainAuth{"guest", "guest"}}, 31 | Vhost: "/", 32 | Locale: defaultLocale, 33 | } 34 | } 35 | 36 | func newServer(t *testing.T, serverIO, clientIO io.ReadWriteCloser) *server { 37 | return &server{ 38 | T: t, 39 | r: reader{serverIO}, 40 | w: writer{serverIO}, 41 | S: serverIO, 42 | C: clientIO, 43 | } 44 | } 45 | 46 | func newSession(t *testing.T) (io.ReadWriteCloser, *server) { 47 | rs, wc := io.Pipe() 48 | rc, ws := io.Pipe() 49 | 50 | rws := &logIO{t, "server", pipe{rs, ws}} 51 | rwc := &logIO{t, "client", pipe{rc, wc}} 52 | 53 | return rwc, newServer(t, rws, rwc) 54 | } 55 | 56 | func (t *server) expectBytes(b []byte) { 57 | in := make([]byte, len(b)) 58 | if _, err := io.ReadFull(t.S, in); err != nil { 59 | t.Fatalf("io error expecting bytes: %v", err) 60 | } 61 | 62 | if bytes.Compare(b, in) != 0 { 63 | t.Fatalf("failed bytes: expected: %s got: %s", string(b), string(in)) 64 | } 65 | } 66 | 67 | func (t *server) send(channel int, m message) { 68 | defer time.AfterFunc(time.Second, func() { panic("send deadlock") }).Stop() 69 | 70 | if msg, ok := m.(messageWithContent); ok { 71 | props, body := msg.getContent() 72 | class, _ := msg.id() 73 | t.w.WriteFrame(&methodFrame{ 74 | ChannelId: uint16(channel), 75 | Method: msg, 76 | }) 77 | t.w.WriteFrame(&headerFrame{ 78 | ChannelId: uint16(channel), 79 | ClassId: class, 80 | Size: uint64(len(body)), 81 | Properties: props, 82 | }) 83 | t.w.WriteFrame(&bodyFrame{ 84 | ChannelId: uint16(channel), 85 | Body: body, 86 | }) 87 | } else { 88 | t.w.WriteFrame(&methodFrame{ 89 | ChannelId: uint16(channel), 90 | Method: m, 91 | }) 92 | } 93 | } 94 | 95 | // drops all but method frames expected on the given channel 96 | func (t *server) recv(channel int, m message) message { 97 | defer time.AfterFunc(time.Second, func() { panic("recv deadlock") }).Stop() 98 | 99 | var remaining int 100 | var header *headerFrame 101 | var body []byte 102 | 103 | for { 104 | frame, err := t.r.ReadFrame() 105 | if err != nil { 106 | t.Fatalf("frame err, read: %s", err) 107 | } 108 | 109 | if frame.channel() != uint16(channel) { 110 | t.Fatalf("expected frame on channel %d, got channel %d", channel, frame.channel()) 111 | } 112 | 113 | switch f := frame.(type) { 114 | case *heartbeatFrame: 115 | // drop 116 | 117 | case *headerFrame: 118 | // start content state 119 | header = f 120 | remaining = int(header.Size) 121 | if remaining == 0 { 122 | m.(messageWithContent).setContent(header.Properties, nil) 123 | return m 124 | } 125 | 126 | case *bodyFrame: 127 | // continue until terminated 128 | body = append(body, f.Body...) 129 | remaining -= len(f.Body) 130 | if remaining <= 0 { 131 | m.(messageWithContent).setContent(header.Properties, body) 132 | return m 133 | } 134 | 135 | case *methodFrame: 136 | if reflect.TypeOf(m) == reflect.TypeOf(f.Method) { 137 | wantv := reflect.ValueOf(m).Elem() 138 | havev := reflect.ValueOf(f.Method).Elem() 139 | wantv.Set(havev) 140 | if _, ok := m.(messageWithContent); !ok { 141 | return m 142 | } 143 | } else { 144 | t.Fatalf("expected method type: %T, got: %T", m, f.Method) 145 | } 146 | 147 | default: 148 | t.Fatalf("unexpected frame: %+v", f) 149 | } 150 | } 151 | } 152 | 153 | func (t *server) expectAMQP() { 154 | t.expectBytes([]byte{'A', 'M', 'Q', 'P', 0, 0, 9, 1}) 155 | } 156 | 157 | func (t *server) connectionStart() { 158 | t.send(0, &connectionStart{ 159 | VersionMajor: 0, 160 | VersionMinor: 9, 161 | Mechanisms: "PLAIN", 162 | Locales: "en_US", 163 | }) 164 | 165 | t.recv(0, &t.start) 166 | } 167 | 168 | func (t *server) connectionTune() { 169 | t.send(0, &connectionTune{ 170 | ChannelMax: 11, 171 | FrameMax: 20000, 172 | Heartbeat: 10, 173 | }) 174 | 175 | t.recv(0, &t.tune) 176 | } 177 | 178 | func (t *server) connectionOpen() { 179 | t.expectAMQP() 180 | t.connectionStart() 181 | t.connectionTune() 182 | 183 | t.recv(0, &connectionOpen{}) 184 | t.send(0, &connectionOpenOk{}) 185 | } 186 | 187 | func (t *server) connectionClose() { 188 | t.recv(0, &connectionClose{}) 189 | t.send(0, &connectionCloseOk{}) 190 | } 191 | 192 | func (t *server) channelOpen(id int) { 193 | t.recv(id, &channelOpen{}) 194 | t.send(id, &channelOpenOk{}) 195 | } 196 | 197 | func TestDefaultClientProperties(t *testing.T) { 198 | rwc, srv := newSession(t) 199 | 200 | go func() { 201 | srv.connectionOpen() 202 | rwc.Close() 203 | }() 204 | 205 | if c, err := Open(rwc, defaultConfig()); err != nil { 206 | t.Fatalf("could not create connection: %v (%s)", c, err) 207 | } 208 | 209 | if want, got := defaultProduct, srv.start.ClientProperties["product"]; want != got { 210 | t.Errorf("expected product %s got: %s", want, got) 211 | } 212 | 213 | if want, got := defaultVersion, srv.start.ClientProperties["version"]; want != got { 214 | t.Errorf("expected version %s got: %s", want, got) 215 | } 216 | 217 | if want, got := defaultLocale, srv.start.Locale; want != got { 218 | t.Errorf("expected locale %s got: %s", want, got) 219 | } 220 | } 221 | 222 | func TestCustomClientProperties(t *testing.T) { 223 | rwc, srv := newSession(t) 224 | 225 | config := defaultConfig() 226 | config.Properties = Table{ 227 | "product": "foo", 228 | "version": "1.0", 229 | } 230 | 231 | go func() { 232 | srv.connectionOpen() 233 | rwc.Close() 234 | }() 235 | 236 | if c, err := Open(rwc, config); err != nil { 237 | t.Fatalf("could not create connection: %v (%s)", c, err) 238 | } 239 | 240 | if want, got := config.Properties["product"], srv.start.ClientProperties["product"]; want != got { 241 | t.Errorf("expected product %s got: %s", want, got) 242 | } 243 | 244 | if want, got := config.Properties["version"], srv.start.ClientProperties["version"]; want != got { 245 | t.Errorf("expected version %s got: %s", want, got) 246 | } 247 | } 248 | 249 | func TestOpen(t *testing.T) { 250 | rwc, srv := newSession(t) 251 | go func() { 252 | srv.connectionOpen() 253 | rwc.Close() 254 | }() 255 | 256 | if c, err := Open(rwc, defaultConfig()); err != nil { 257 | t.Fatalf("could not create connection: %v (%s)", c, err) 258 | } 259 | } 260 | 261 | func TestChannelOpen(t *testing.T) { 262 | rwc, srv := newSession(t) 263 | 264 | go func() { 265 | srv.connectionOpen() 266 | srv.channelOpen(1) 267 | 268 | rwc.Close() 269 | }() 270 | 271 | c, err := Open(rwc, defaultConfig()) 272 | if err != nil { 273 | t.Fatalf("could not create connection: %v (%s)", c, err) 274 | } 275 | 276 | ch, err := c.Channel() 277 | if err != nil { 278 | t.Fatalf("could not open channel: %v (%s)", ch, err) 279 | } 280 | } 281 | 282 | func TestOpenFailedSASLUnsupportedMechanisms(t *testing.T) { 283 | rwc, srv := newSession(t) 284 | 285 | go func() { 286 | srv.expectAMQP() 287 | srv.send(0, &connectionStart{ 288 | VersionMajor: 0, 289 | VersionMinor: 9, 290 | Mechanisms: "KERBEROS NTLM", 291 | Locales: "en_US", 292 | }) 293 | }() 294 | 295 | c, err := Open(rwc, defaultConfig()) 296 | if err != ErrSASL { 297 | t.Fatalf("expected ErrSASL got: %+v on %+v", err, c) 298 | } 299 | } 300 | 301 | func TestOpenFailedCredentials(t *testing.T) { 302 | rwc, srv := newSession(t) 303 | 304 | go func() { 305 | srv.expectAMQP() 306 | srv.connectionStart() 307 | // Now kill/timeout the connection indicating bad auth 308 | rwc.Close() 309 | }() 310 | 311 | c, err := Open(rwc, defaultConfig()) 312 | if err != ErrCredentials { 313 | t.Fatalf("expected ErrCredentials got: %+v on %+v", err, c) 314 | } 315 | } 316 | 317 | func TestOpenFailedVhost(t *testing.T) { 318 | rwc, srv := newSession(t) 319 | 320 | go func() { 321 | srv.expectAMQP() 322 | srv.connectionStart() 323 | srv.connectionTune() 324 | srv.recv(0, &connectionOpen{}) 325 | 326 | // Now kill/timeout the connection on bad Vhost 327 | rwc.Close() 328 | }() 329 | 330 | c, err := Open(rwc, defaultConfig()) 331 | if err != ErrVhost { 332 | t.Fatalf("expected ErrVhost got: %+v on %+v", err, c) 333 | } 334 | } 335 | 336 | func TestConfirmMultipleOrdersDeliveryTags(t *testing.T) { 337 | rwc, srv := newSession(t) 338 | defer rwc.Close() 339 | 340 | go func() { 341 | srv.connectionOpen() 342 | srv.channelOpen(1) 343 | 344 | srv.recv(1, &confirmSelect{}) 345 | srv.send(1, &confirmSelectOk{}) 346 | 347 | srv.recv(1, &basicPublish{}) 348 | srv.recv(1, &basicPublish{}) 349 | srv.recv(1, &basicPublish{}) 350 | srv.recv(1, &basicPublish{}) 351 | 352 | // Single tag, plus multiple, should produce 353 | // 2, 1, 3, 4 354 | srv.send(1, &basicAck{DeliveryTag: 2}) 355 | srv.send(1, &basicAck{DeliveryTag: 1}) 356 | srv.send(1, &basicAck{DeliveryTag: 4, Multiple: true}) 357 | 358 | srv.recv(1, &basicPublish{}) 359 | srv.recv(1, &basicPublish{}) 360 | srv.recv(1, &basicPublish{}) 361 | srv.recv(1, &basicPublish{}) 362 | 363 | // And some more, but in reverse order, multiple then one 364 | // 5, 6, 7, 8 365 | srv.send(1, &basicAck{DeliveryTag: 6, Multiple: true}) 366 | srv.send(1, &basicAck{DeliveryTag: 8}) 367 | srv.send(1, &basicAck{DeliveryTag: 7}) 368 | }() 369 | 370 | c, err := Open(rwc, defaultConfig()) 371 | if err != nil { 372 | t.Fatalf("could not create connection: %v (%s)", c, err) 373 | } 374 | 375 | ch, err := c.Channel() 376 | if err != nil { 377 | t.Fatalf("could not open channel: %v (%s)", ch, err) 378 | } 379 | 380 | confirm := ch.NotifyPublish(make(chan Confirmation)) 381 | 382 | ch.Confirm(false) 383 | 384 | go func() { 385 | ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 1")}) 386 | ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 2")}) 387 | ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 3")}) 388 | ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 4")}) 389 | }() 390 | 391 | // received out of order, consumed in order 392 | for i, tag := range []uint64{1, 2, 3, 4} { 393 | if ack := <-confirm; tag != ack.DeliveryTag { 394 | t.Fatalf("failed ack, expected ack#%d to be %d, got %d", i, tag, ack.DeliveryTag) 395 | } 396 | } 397 | 398 | go func() { 399 | ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 5")}) 400 | ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 6")}) 401 | ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 7")}) 402 | ch.Publish("", "q", false, false, Publishing{Body: []byte("pub 8")}) 403 | }() 404 | 405 | for i, tag := range []uint64{5, 6, 7, 8} { 406 | if ack := <-confirm; tag != ack.DeliveryTag { 407 | t.Fatalf("failed ack, expected ack#%d to be %d, got %d", i, tag, ack.DeliveryTag) 408 | } 409 | } 410 | 411 | } 412 | 413 | func TestNotifyClosesReusedPublisherConfirmChan(t *testing.T) { 414 | rwc, srv := newSession(t) 415 | 416 | go func() { 417 | srv.connectionOpen() 418 | srv.channelOpen(1) 419 | 420 | srv.recv(1, &confirmSelect{}) 421 | srv.send(1, &confirmSelectOk{}) 422 | 423 | srv.recv(0, &connectionClose{}) 424 | srv.send(0, &connectionCloseOk{}) 425 | }() 426 | 427 | c, err := Open(rwc, defaultConfig()) 428 | if err != nil { 429 | t.Fatalf("could not create connection: %v (%s)", c, err) 430 | } 431 | 432 | ch, err := c.Channel() 433 | if err != nil { 434 | t.Fatalf("could not open channel: %v (%s)", ch, err) 435 | } 436 | 437 | ackAndNack := make(chan uint64) 438 | ch.NotifyConfirm(ackAndNack, ackAndNack) 439 | 440 | if err := ch.Confirm(false); err != nil { 441 | t.Fatalf("expected to enter confirm mode: %v", err) 442 | } 443 | 444 | if err := c.Close(); err != nil { 445 | t.Fatalf("could not close connection: %v (%s)", c, err) 446 | } 447 | } 448 | 449 | func TestNotifyClosesAllChansAfterConnectionClose(t *testing.T) { 450 | rwc, srv := newSession(t) 451 | 452 | go func() { 453 | srv.connectionOpen() 454 | srv.channelOpen(1) 455 | 456 | srv.recv(0, &connectionClose{}) 457 | srv.send(0, &connectionCloseOk{}) 458 | }() 459 | 460 | c, err := Open(rwc, defaultConfig()) 461 | if err != nil { 462 | t.Fatalf("could not create connection: %v (%s)", c, err) 463 | } 464 | 465 | ch, err := c.Channel() 466 | if err != nil { 467 | t.Fatalf("could not open channel: %v (%s)", ch, err) 468 | } 469 | 470 | if err := c.Close(); err != nil { 471 | t.Fatalf("could not close connection: %v (%s)", c, err) 472 | } 473 | 474 | select { 475 | case <-c.NotifyClose(make(chan *Error)): 476 | case <-time.After(time.Millisecond): 477 | t.Errorf("expected to close NotifyClose chan after Connection.Close") 478 | } 479 | 480 | select { 481 | case <-ch.NotifyClose(make(chan *Error)): 482 | case <-time.After(time.Millisecond): 483 | t.Errorf("expected to close Connection.NotifyClose chan after Connection.Close") 484 | } 485 | 486 | select { 487 | case <-ch.NotifyFlow(make(chan bool)): 488 | case <-time.After(time.Millisecond): 489 | t.Errorf("expected to close Channel.NotifyFlow chan after Connection.Close") 490 | } 491 | 492 | select { 493 | case <-ch.NotifyCancel(make(chan string)): 494 | case <-time.After(time.Millisecond): 495 | t.Errorf("expected to close Channel.NofityCancel chan after Connection.Close") 496 | } 497 | 498 | select { 499 | case <-ch.NotifyReturn(make(chan Return)): 500 | case <-time.After(time.Millisecond): 501 | t.Errorf("expected to close Channel.NotifyReturn chan after Connection.Close") 502 | } 503 | 504 | confirms := ch.NotifyPublish(make(chan Confirmation)) 505 | 506 | select { 507 | case <-confirms: 508 | case <-time.After(time.Millisecond): 509 | t.Errorf("expected to close confirms on Channel.NotifyPublish chan after Connection.Close") 510 | } 511 | } 512 | 513 | // Should not panic when sending bodies split at different boundaries 514 | func TestPublishBodySliceIssue74(t *testing.T) { 515 | rwc, srv := newSession(t) 516 | defer rwc.Close() 517 | 518 | const frameSize = 100 519 | const publishings = frameSize * 3 520 | 521 | done := make(chan bool) 522 | base := make([]byte, publishings) 523 | 524 | go func() { 525 | srv.connectionOpen() 526 | srv.channelOpen(1) 527 | 528 | for i := 0; i < publishings; i++ { 529 | srv.recv(1, &basicPublish{}) 530 | } 531 | 532 | done <- true 533 | }() 534 | 535 | cfg := defaultConfig() 536 | cfg.FrameSize = frameSize 537 | 538 | c, err := Open(rwc, cfg) 539 | if err != nil { 540 | t.Fatalf("could not create connection: %v (%s)", c, err) 541 | } 542 | 543 | ch, err := c.Channel() 544 | if err != nil { 545 | t.Fatalf("could not open channel: %v (%s)", ch, err) 546 | } 547 | 548 | for i := 0; i < publishings; i++ { 549 | go ch.Publish("", "q", false, false, Publishing{Body: base[0:i]}) 550 | } 551 | 552 | <-done 553 | } 554 | 555 | // Should not panic when server and client have frame_size of 0 556 | func TestPublishZeroFrameSizeIssue161(t *testing.T) { 557 | rwc, srv := newSession(t) 558 | defer rwc.Close() 559 | 560 | const frameSize = 0 561 | const publishings = 1 562 | done := make(chan bool) 563 | 564 | go func() { 565 | srv.connectionOpen() 566 | srv.channelOpen(1) 567 | 568 | for i := 0; i < publishings; i++ { 569 | srv.recv(1, &basicPublish{}) 570 | } 571 | 572 | done <- true 573 | }() 574 | 575 | cfg := defaultConfig() 576 | cfg.FrameSize = frameSize 577 | 578 | c, err := Open(rwc, cfg) 579 | 580 | // override the tuned framesize with a hard 0, as would happen when rabbit is configured with 0 581 | c.Config.FrameSize = frameSize 582 | 583 | if err != nil { 584 | t.Fatalf("could not create connection: %v (%s)", c, err) 585 | } 586 | 587 | ch, err := c.Channel() 588 | if err != nil { 589 | t.Fatalf("could not open channel: %v (%s)", ch, err) 590 | } 591 | 592 | for i := 0; i < publishings; i++ { 593 | go ch.Publish("", "q", false, false, Publishing{Body: []byte("anything")}) 594 | } 595 | 596 | <-done 597 | } 598 | 599 | func TestPublishAndShutdownDeadlockIssue84(t *testing.T) { 600 | rwc, srv := newSession(t) 601 | defer rwc.Close() 602 | 603 | go func() { 604 | srv.connectionOpen() 605 | srv.channelOpen(1) 606 | srv.recv(1, &basicPublish{}) 607 | // Mimic a broken io pipe so that Publish catches the error and goes into shutdown 608 | srv.S.Close() 609 | }() 610 | 611 | c, err := Open(rwc, defaultConfig()) 612 | if err != nil { 613 | t.Fatalf("couldn't create connection: %v (%s)", c, err) 614 | } 615 | 616 | ch, err := c.Channel() 617 | if err != nil { 618 | t.Fatalf("couldn't open channel: %v (%s)", ch, err) 619 | } 620 | 621 | defer time.AfterFunc(500*time.Millisecond, func() { panic("Publish deadlock") }).Stop() 622 | for { 623 | if err := ch.Publish("exchange", "q", false, false, Publishing{Body: []byte("test")}); err != nil { 624 | t.Log("successfully caught disconnect error", err) 625 | return 626 | } 627 | } 628 | } 629 | 630 | // TestChannelReturnsCloseRace ensures that receiving a basicReturn frame and 631 | // sending the notification to the bound channel does not race with 632 | // channel.shutdown() which closes all registered notification channels - checks 633 | // for a "send on closed channel" panic 634 | func TestChannelReturnsCloseRace(t *testing.T) { 635 | defer time.AfterFunc(5*time.Second, func() { panic("Shutdown deadlock") }).Stop() 636 | ch := newChannel(&Connection{}, 1) 637 | 638 | // Register a channel to close in channel.shutdown() 639 | notify := make(chan Return, 1) 640 | ch.NotifyReturn(notify) 641 | 642 | go func() { 643 | for range notify { 644 | // Drain notifications 645 | } 646 | }() 647 | 648 | // Simulate receiving a load of returns (triggering a write to the above 649 | // channel) while we call shutdown concurrently 650 | go func() { 651 | for i := 0; i < 100; i++ { 652 | ch.dispatch(&basicReturn{}) 653 | } 654 | }() 655 | 656 | ch.shutdown(nil) 657 | } 658 | 659 | // TestLeakClosedConsumersIssue264 ensures that closing a consumer with 660 | // prefetched messages does not leak the buffering goroutine. 661 | func TestLeakClosedConsumersIssue264(t *testing.T) { 662 | const tag = "consumer-tag" 663 | 664 | rwc, srv := newSession(t) 665 | defer rwc.Close() 666 | 667 | go func() { 668 | srv.connectionOpen() 669 | srv.channelOpen(1) 670 | 671 | srv.recv(1, &basicQos{}) 672 | srv.send(1, &basicQosOk{}) 673 | 674 | srv.recv(1, &basicConsume{}) 675 | srv.send(1, &basicConsumeOk{ConsumerTag: tag}) 676 | 677 | // This delivery is intended to be consumed 678 | srv.send(1, &basicDeliver{ConsumerTag: tag, DeliveryTag: 1}) 679 | 680 | // This delivery is intended to be dropped 681 | srv.send(1, &basicDeliver{ConsumerTag: tag, DeliveryTag: 2}) 682 | 683 | srv.recv(0, &connectionClose{}) 684 | srv.send(0, &connectionCloseOk{}) 685 | srv.C.Close() 686 | }() 687 | 688 | c, err := Open(rwc, defaultConfig()) 689 | if err != nil { 690 | t.Fatalf("could not create connection: %v (%s)", c, err) 691 | } 692 | 693 | ch, err := c.Channel() 694 | if err != nil { 695 | t.Fatalf("could not open channel: %v (%s)", ch, err) 696 | } 697 | ch.Qos(2, 0, false) 698 | 699 | consumer, err := ch.Consume("queue", tag, false, false, false, false, nil) 700 | if err != nil { 701 | t.Fatalf("unexpected error during consumer: %v", err) 702 | } 703 | 704 | first := <-consumer 705 | if want, got := uint64(1), first.DeliveryTag; want != got { 706 | t.Fatalf("unexpected delivery tag: want: %d, got: %d", want, got) 707 | } 708 | 709 | if err := c.Close(); err != nil { 710 | t.Fatalf("unexpected error during connection close: %v", err) 711 | } 712 | 713 | if _, open := <-consumer; open { 714 | t.Fatalf("expected deliveries channel to be closed immediately when the connection is closed so not to leak the bufferDeliveries goroutine") 715 | } 716 | } 717 | -------------------------------------------------------------------------------- /confirms.go: -------------------------------------------------------------------------------- 1 | package amqp 2 | 3 | import "sync" 4 | 5 | // confirms resequences and notifies one or multiple publisher confirmation listeners 6 | type confirms struct { 7 | m sync.Mutex 8 | listeners []chan Confirmation 9 | sequencer map[uint64]Confirmation 10 | published uint64 11 | expecting uint64 12 | } 13 | 14 | // newConfirms allocates a confirms 15 | func newConfirms() *confirms { 16 | return &confirms{ 17 | sequencer: map[uint64]Confirmation{}, 18 | published: 0, 19 | expecting: 1, 20 | } 21 | } 22 | 23 | func (c *confirms) Listen(l chan Confirmation) { 24 | c.m.Lock() 25 | defer c.m.Unlock() 26 | 27 | c.listeners = append(c.listeners, l) 28 | } 29 | 30 | // publish increments the publishing counter 31 | func (c *confirms) Publish() uint64 { 32 | c.m.Lock() 33 | defer c.m.Unlock() 34 | 35 | c.published++ 36 | return c.published 37 | } 38 | 39 | // confirm confirms one publishing, increments the expecting delivery tag, and 40 | // removes bookkeeping for that delivery tag. 41 | func (c *confirms) confirm(confirmation Confirmation) { 42 | delete(c.sequencer, c.expecting) 43 | c.expecting++ 44 | for _, l := range c.listeners { 45 | l <- confirmation 46 | } 47 | } 48 | 49 | // resequence confirms any out of order delivered confirmations 50 | func (c *confirms) resequence() { 51 | for c.expecting <= c.published { 52 | sequenced, found := c.sequencer[c.expecting] 53 | if !found { 54 | return 55 | } 56 | c.confirm(sequenced) 57 | } 58 | } 59 | 60 | // one confirms one publishing and all following in the publishing sequence 61 | func (c *confirms) One(confirmed Confirmation) { 62 | c.m.Lock() 63 | defer c.m.Unlock() 64 | 65 | if c.expecting == confirmed.DeliveryTag { 66 | c.confirm(confirmed) 67 | } else { 68 | c.sequencer[confirmed.DeliveryTag] = confirmed 69 | } 70 | c.resequence() 71 | } 72 | 73 | // multiple confirms all publishings up until the delivery tag 74 | func (c *confirms) Multiple(confirmed Confirmation) { 75 | c.m.Lock() 76 | defer c.m.Unlock() 77 | 78 | for c.expecting <= confirmed.DeliveryTag { 79 | c.confirm(Confirmation{c.expecting, confirmed.Ack}) 80 | } 81 | c.resequence() 82 | } 83 | 84 | // Close closes all listeners, discarding any out of sequence confirmations 85 | func (c *confirms) Close() error { 86 | c.m.Lock() 87 | defer c.m.Unlock() 88 | 89 | for _, l := range c.listeners { 90 | close(l) 91 | } 92 | c.listeners = nil 93 | return nil 94 | } 95 | -------------------------------------------------------------------------------- /confirms_test.go: -------------------------------------------------------------------------------- 1 | package amqp 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestConfirmOneResequences(t *testing.T) { 9 | var ( 10 | fixtures = []Confirmation{ 11 | {1, true}, 12 | {2, false}, 13 | {3, true}, 14 | } 15 | c = newConfirms() 16 | l = make(chan Confirmation, len(fixtures)) 17 | ) 18 | 19 | c.Listen(l) 20 | 21 | for i := range fixtures { 22 | if want, got := uint64(i+1), c.Publish(); want != got { 23 | t.Fatalf("expected publish to return the 1 based delivery tag published, want: %d, got: %d", want, got) 24 | } 25 | } 26 | 27 | c.One(fixtures[1]) 28 | c.One(fixtures[2]) 29 | 30 | select { 31 | case confirm := <-l: 32 | t.Fatalf("expected to wait in order to properly resequence results, got: %+v", confirm) 33 | default: 34 | } 35 | 36 | c.One(fixtures[0]) 37 | 38 | for i, fix := range fixtures { 39 | if want, got := fix, <-l; want != got { 40 | t.Fatalf("expected to return confirmations in sequence for %d, want: %+v, got: %+v", i, want, got) 41 | } 42 | } 43 | } 44 | 45 | func TestConfirmMixedResequences(t *testing.T) { 46 | var ( 47 | fixtures = []Confirmation{ 48 | {1, true}, 49 | {2, true}, 50 | {3, true}, 51 | } 52 | c = newConfirms() 53 | l = make(chan Confirmation, len(fixtures)) 54 | ) 55 | c.Listen(l) 56 | 57 | for range fixtures { 58 | c.Publish() 59 | } 60 | 61 | c.One(fixtures[0]) 62 | c.One(fixtures[2]) 63 | c.Multiple(fixtures[1]) 64 | 65 | for i, fix := range fixtures { 66 | want := fix 67 | var got Confirmation 68 | select { 69 | case got = <-l: 70 | case <-time.After(1 * time.Second): 71 | t.Fatalf("timeout on reading confirmations") 72 | } 73 | if want != got { 74 | t.Fatalf("expected to confirm in sequence for %d, want: %+v, got: %+v", i, want, got) 75 | } 76 | } 77 | } 78 | 79 | func TestConfirmMultipleResequences(t *testing.T) { 80 | var ( 81 | fixtures = []Confirmation{ 82 | {1, true}, 83 | {2, true}, 84 | {3, true}, 85 | {4, true}, 86 | } 87 | c = newConfirms() 88 | l = make(chan Confirmation, len(fixtures)) 89 | ) 90 | c.Listen(l) 91 | 92 | for range fixtures { 93 | c.Publish() 94 | } 95 | 96 | c.Multiple(fixtures[len(fixtures)-1]) 97 | 98 | for i, fix := range fixtures { 99 | if want, got := fix, <-l; want != got { 100 | t.Fatalf("expected to confirm multiple in sequence for %d, want: %+v, got: %+v", i, want, got) 101 | } 102 | } 103 | } 104 | 105 | func BenchmarkSequentialBufferedConfirms(t *testing.B) { 106 | var ( 107 | c = newConfirms() 108 | l = make(chan Confirmation, 10) 109 | ) 110 | 111 | c.Listen(l) 112 | 113 | for i := 0; i < t.N; i++ { 114 | if i > cap(l)-1 { 115 | <-l 116 | } 117 | c.One(Confirmation{c.Publish(), true}) 118 | } 119 | } 120 | 121 | func TestConfirmsIsThreadSafe(t *testing.T) { 122 | const count = 1000 123 | const timeout = 5 * time.Second 124 | var ( 125 | c = newConfirms() 126 | l = make(chan Confirmation) 127 | pub = make(chan Confirmation) 128 | done = make(chan Confirmation) 129 | late = time.After(timeout) 130 | ) 131 | 132 | c.Listen(l) 133 | 134 | for i := 0; i < count; i++ { 135 | go func() { pub <- Confirmation{c.Publish(), true} }() 136 | } 137 | 138 | for i := 0; i < count; i++ { 139 | go func() { c.One(<-pub) }() 140 | } 141 | 142 | for i := 0; i < count; i++ { 143 | go func() { done <- <-l }() 144 | } 145 | 146 | for i := 0; i < count; i++ { 147 | select { 148 | case <-done: 149 | case <-late: 150 | t.Fatalf("expected all publish/confirms to finish after %s", timeout) 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /connection_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016, Sean Treadway, SoundCloud Ltd. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // Source code and contact info at http://github.com/streadway/amqp 5 | 6 | // +build integration 7 | 8 | package amqp 9 | 10 | import ( 11 | "crypto/tls" 12 | "net" 13 | "sync" 14 | "testing" 15 | "time" 16 | ) 17 | 18 | func TestRequiredServerLocale(t *testing.T) { 19 | conn := integrationConnection(t, "AMQP 0-9-1 required server locale") 20 | requiredServerLocale := defaultLocale 21 | 22 | for _, locale := range conn.Locales { 23 | if locale == requiredServerLocale { 24 | return 25 | } 26 | } 27 | 28 | t.Fatalf("AMQP 0-9-1 server must support at least the %s locale, server sent the following locales: %#v", requiredServerLocale, conn.Locales) 29 | } 30 | 31 | func TestDefaultConnectionLocale(t *testing.T) { 32 | conn := integrationConnection(t, "client default locale") 33 | 34 | if conn.Config.Locale != defaultLocale { 35 | t.Fatalf("Expected default connection locale to be %s, is was: %s", defaultLocale, conn.Config.Locale) 36 | } 37 | } 38 | 39 | func TestChannelOpenOnAClosedConnectionFails(t *testing.T) { 40 | conn := integrationConnection(t, "channel on close") 41 | 42 | conn.Close() 43 | 44 | if _, err := conn.Channel(); err != ErrClosed { 45 | t.Fatalf("channel.open on a closed connection %#v is expected to fail", conn) 46 | } 47 | } 48 | 49 | // TestChannelOpenOnAClosedConnectionFails_ReleasesAllocatedChannel ensures the 50 | // channel allocated is released if opening the channel fails. 51 | func TestChannelOpenOnAClosedConnectionFails_ReleasesAllocatedChannel(t *testing.T) { 52 | conn := integrationConnection(t, "releases channel allocation") 53 | conn.Close() 54 | 55 | before := len(conn.channels) 56 | 57 | if _, err := conn.Channel(); err != ErrClosed { 58 | t.Fatalf("channel.open on a closed connection %#v is expected to fail", conn) 59 | } 60 | 61 | if len(conn.channels) != before { 62 | t.Fatalf("channel.open failed, but the allocated channel was not released") 63 | } 64 | } 65 | 66 | // TestRaceBetweenChannelAndConnectionClose ensures allocating a new channel 67 | // does not race with shutting the connection down. 68 | // 69 | // See https://github.com/streadway/amqp/issues/251 - thanks to jmalloc for the 70 | // test case. 71 | func TestRaceBetweenChannelAndConnectionClose(t *testing.T) { 72 | defer time.AfterFunc(10*time.Second, func() { panic("Close deadlock") }).Stop() 73 | 74 | conn := integrationConnection(t, "allocation/shutdown race") 75 | 76 | go conn.Close() 77 | for i := 0; i < 10; i++ { 78 | go func() { 79 | ch, err := conn.Channel() 80 | if err == nil { 81 | ch.Close() 82 | } 83 | }() 84 | } 85 | } 86 | 87 | // TestRaceBetweenChannelShutdownAndSend ensures closing a channel 88 | // (channel.shutdown) does not race with calling channel.send() from any other 89 | // goroutines. 90 | // 91 | // See https://github.com/streadway/amqp/pull/253#issuecomment-292464811 for 92 | // more details - thanks to jmalloc again. 93 | func TestRaceBetweenChannelShutdownAndSend(t *testing.T) { 94 | defer time.AfterFunc(10*time.Second, func() { panic("Close deadlock") }).Stop() 95 | 96 | conn := integrationConnection(t, "channel close/send race") 97 | defer conn.Close() 98 | 99 | ch, _ := conn.Channel() 100 | 101 | go ch.Close() 102 | for i := 0; i < 10; i++ { 103 | go func() { 104 | // ch.Ack calls ch.send() internally. 105 | ch.Ack(42, false) 106 | }() 107 | } 108 | } 109 | 110 | func TestQueueDeclareOnAClosedConnectionFails(t *testing.T) { 111 | conn := integrationConnection(t, "queue declare on close") 112 | ch, _ := conn.Channel() 113 | 114 | conn.Close() 115 | 116 | if _, err := ch.QueueDeclare("an example", false, false, false, false, nil); err != ErrClosed { 117 | t.Fatalf("queue.declare on a closed connection %#v is expected to return ErrClosed, returned: %#v", conn, err) 118 | } 119 | } 120 | 121 | func TestConcurrentClose(t *testing.T) { 122 | const concurrency = 32 123 | 124 | conn := integrationConnection(t, "concurrent close") 125 | defer conn.Close() 126 | 127 | wg := sync.WaitGroup{} 128 | wg.Add(concurrency) 129 | for i := 0; i < concurrency; i++ { 130 | go func() { 131 | defer wg.Done() 132 | 133 | err := conn.Close() 134 | 135 | if err == nil { 136 | t.Log("first concurrent close was successful") 137 | return 138 | } 139 | 140 | if err == ErrClosed { 141 | t.Log("later concurrent close were successful and returned ErrClosed") 142 | return 143 | } 144 | 145 | // BUG(st) is this really acceptable? we got a net.OpError before the 146 | // connection was marked as closed means a race condition between the 147 | // network connection and handshake state. It should be a package error 148 | // returned. 149 | if _, neterr := err.(*net.OpError); neterr { 150 | t.Logf("unknown net.OpError during close, ignoring: %+v", err) 151 | return 152 | } 153 | 154 | // A different/protocol error occurred indicating a race or missed condition 155 | if _, other := err.(*Error); other { 156 | t.Fatalf("Expected no error, or ErrClosed, or a net.OpError from conn.Close(), got %#v (%s) of type %T", err, err, err) 157 | } 158 | }() 159 | } 160 | wg.Wait() 161 | } 162 | 163 | // TestPlaintextDialTLS esnures amqp:// connections succeed when using DialTLS. 164 | func TestPlaintextDialTLS(t *testing.T) { 165 | uri, err := ParseURI(integrationURLFromEnv()) 166 | if err != nil { 167 | t.Fatalf("parse URI error: %s", err) 168 | } 169 | 170 | // We can only test when we have a plaintext listener 171 | if uri.Scheme != "amqp" { 172 | t.Skip("requires server listening for plaintext connections") 173 | } 174 | 175 | conn, err := DialTLS(uri.String(), &tls.Config{MinVersion: tls.VersionTLS12}) 176 | if err != nil { 177 | t.Fatalf("unexpected dial error, got %v", err) 178 | } 179 | conn.Close() 180 | } 181 | 182 | // TestIsClosed will test the public method IsClosed on a connection. 183 | func TestIsClosed(t *testing.T) { 184 | conn := integrationConnection(t, "public IsClosed()") 185 | 186 | if conn.IsClosed() { 187 | t.Fatalf("connection expected to not be marked as closed") 188 | } 189 | 190 | conn.Close() 191 | 192 | if !conn.IsClosed() { 193 | t.Fatal("connection expected to be marked as closed") 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /consumers.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // Source code and contact info at http://github.com/streadway/amqp 5 | 6 | package amqp 7 | 8 | import ( 9 | "os" 10 | "strconv" 11 | "sync" 12 | "sync/atomic" 13 | ) 14 | 15 | var consumerSeq uint64 16 | 17 | const consumerTagLengthMax = 0xFF // see writeShortstr 18 | 19 | func uniqueConsumerTag() string { 20 | return commandNameBasedUniqueConsumerTag(os.Args[0]) 21 | } 22 | 23 | func commandNameBasedUniqueConsumerTag(commandName string) string { 24 | tagPrefix := "ctag-" 25 | tagInfix := commandName 26 | tagSuffix := "-" + strconv.FormatUint(atomic.AddUint64(&consumerSeq, 1), 10) 27 | 28 | if len(tagPrefix)+len(tagInfix)+len(tagSuffix) > consumerTagLengthMax { 29 | tagInfix = "streadway/amqp" 30 | } 31 | 32 | return tagPrefix + tagInfix + tagSuffix 33 | } 34 | 35 | type consumerBuffers map[string]chan *Delivery 36 | 37 | // Concurrent type that manages the consumerTag -> 38 | // ingress consumerBuffer mapping 39 | type consumers struct { 40 | sync.WaitGroup // one for buffer 41 | closed chan struct{} // signal buffer 42 | 43 | sync.Mutex // protects below 44 | chans consumerBuffers 45 | } 46 | 47 | func makeConsumers() *consumers { 48 | return &consumers{ 49 | closed: make(chan struct{}), 50 | chans: make(consumerBuffers), 51 | } 52 | } 53 | 54 | func (subs *consumers) buffer(in chan *Delivery, out chan Delivery) { 55 | defer close(out) 56 | defer subs.Done() 57 | 58 | var inflight = in 59 | var queue []*Delivery 60 | 61 | for delivery := range in { 62 | queue = append(queue, delivery) 63 | 64 | for len(queue) > 0 { 65 | select { 66 | case <-subs.closed: 67 | // closed before drained, drop in-flight 68 | return 69 | 70 | case delivery, consuming := <-inflight: 71 | if consuming { 72 | queue = append(queue, delivery) 73 | } else { 74 | inflight = nil 75 | } 76 | 77 | case out <- *queue[0]: 78 | queue = queue[1:] 79 | } 80 | } 81 | } 82 | } 83 | 84 | // On key conflict, close the previous channel. 85 | func (subs *consumers) add(tag string, consumer chan Delivery) { 86 | subs.Lock() 87 | defer subs.Unlock() 88 | 89 | if prev, found := subs.chans[tag]; found { 90 | close(prev) 91 | } 92 | 93 | in := make(chan *Delivery) 94 | subs.chans[tag] = in 95 | 96 | subs.Add(1) 97 | go subs.buffer(in, consumer) 98 | } 99 | 100 | func (subs *consumers) cancel(tag string) (found bool) { 101 | subs.Lock() 102 | defer subs.Unlock() 103 | 104 | ch, found := subs.chans[tag] 105 | 106 | if found { 107 | delete(subs.chans, tag) 108 | close(ch) 109 | } 110 | 111 | return found 112 | } 113 | 114 | func (subs *consumers) close() { 115 | subs.Lock() 116 | defer subs.Unlock() 117 | 118 | close(subs.closed) 119 | 120 | for tag, ch := range subs.chans { 121 | delete(subs.chans, tag) 122 | close(ch) 123 | } 124 | 125 | subs.Wait() 126 | } 127 | 128 | // Sends a delivery to a the consumer identified by `tag`. 129 | // If unbuffered channels are used for Consume this method 130 | // could block all deliveries until the consumer 131 | // receives on the other end of the channel. 132 | func (subs *consumers) send(tag string, msg *Delivery) bool { 133 | subs.Lock() 134 | defer subs.Unlock() 135 | 136 | buffer, found := subs.chans[tag] 137 | if found { 138 | buffer <- msg 139 | } 140 | 141 | return found 142 | } 143 | -------------------------------------------------------------------------------- /consumers_test.go: -------------------------------------------------------------------------------- 1 | package amqp 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestGeneratedUniqueConsumerTagDoesNotExceedMaxLength(t *testing.T) { 9 | assertCorrectLength := func(commandName string) { 10 | tag := commandNameBasedUniqueConsumerTag(commandName) 11 | if len(tag) > consumerTagLengthMax { 12 | t.Error("Generated unique consumer tag exceeds maximum length:", tag) 13 | } 14 | } 15 | 16 | assertCorrectLength("test") 17 | assertCorrectLength(strings.Repeat("z", 249)) 18 | assertCorrectLength(strings.Repeat("z", 256)) 19 | assertCorrectLength(strings.Repeat("z", 1024)) 20 | } 21 | -------------------------------------------------------------------------------- /delivery.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // Source code and contact info at http://github.com/streadway/amqp 5 | 6 | package amqp 7 | 8 | import ( 9 | "errors" 10 | "time" 11 | ) 12 | 13 | var errDeliveryNotInitialized = errors.New("delivery not initialized") 14 | 15 | // Acknowledger notifies the server of successful or failed consumption of 16 | // delivieries via identifier found in the Delivery.DeliveryTag field. 17 | // 18 | // Applications can provide mock implementations in tests of Delivery handlers. 19 | type Acknowledger interface { 20 | Ack(tag uint64, multiple bool) error 21 | Nack(tag uint64, multiple bool, requeue bool) error 22 | Reject(tag uint64, requeue bool) error 23 | } 24 | 25 | // Delivery captures the fields for a previously delivered message resident in 26 | // a queue to be delivered by the server to a consumer from Channel.Consume or 27 | // Channel.Get. 28 | type Delivery struct { 29 | Acknowledger Acknowledger // the channel from which this delivery arrived 30 | 31 | Headers Table // Application or header exchange table 32 | 33 | // Properties 34 | ContentType string // MIME content type 35 | ContentEncoding string // MIME content encoding 36 | DeliveryMode uint8 // queue implementation use - non-persistent (1) or persistent (2) 37 | Priority uint8 // queue implementation use - 0 to 9 38 | CorrelationId string // application use - correlation identifier 39 | ReplyTo string // application use - address to reply to (ex: RPC) 40 | Expiration string // implementation use - message expiration spec 41 | MessageId string // application use - message identifier 42 | Timestamp time.Time // application use - message timestamp 43 | Type string // application use - message type name 44 | UserId string // application use - creating user - should be authenticated user 45 | AppId string // application use - creating application id 46 | 47 | // Valid only with Channel.Consume 48 | ConsumerTag string 49 | 50 | // Valid only with Channel.Get 51 | MessageCount uint32 52 | 53 | DeliveryTag uint64 54 | Redelivered bool 55 | Exchange string // basic.publish exchange 56 | RoutingKey string // basic.publish routing key 57 | 58 | Body []byte 59 | } 60 | 61 | func newDelivery(channel *Channel, msg messageWithContent) *Delivery { 62 | props, body := msg.getContent() 63 | 64 | delivery := Delivery{ 65 | Acknowledger: channel, 66 | 67 | Headers: props.Headers, 68 | ContentType: props.ContentType, 69 | ContentEncoding: props.ContentEncoding, 70 | DeliveryMode: props.DeliveryMode, 71 | Priority: props.Priority, 72 | CorrelationId: props.CorrelationId, 73 | ReplyTo: props.ReplyTo, 74 | Expiration: props.Expiration, 75 | MessageId: props.MessageId, 76 | Timestamp: props.Timestamp, 77 | Type: props.Type, 78 | UserId: props.UserId, 79 | AppId: props.AppId, 80 | 81 | Body: body, 82 | } 83 | 84 | // Properties for the delivery types 85 | switch m := msg.(type) { 86 | case *basicDeliver: 87 | delivery.ConsumerTag = m.ConsumerTag 88 | delivery.DeliveryTag = m.DeliveryTag 89 | delivery.Redelivered = m.Redelivered 90 | delivery.Exchange = m.Exchange 91 | delivery.RoutingKey = m.RoutingKey 92 | 93 | case *basicGetOk: 94 | delivery.MessageCount = m.MessageCount 95 | delivery.DeliveryTag = m.DeliveryTag 96 | delivery.Redelivered = m.Redelivered 97 | delivery.Exchange = m.Exchange 98 | delivery.RoutingKey = m.RoutingKey 99 | } 100 | 101 | return &delivery 102 | } 103 | 104 | /* 105 | Ack delegates an acknowledgement through the Acknowledger interface that the 106 | client or server has finished work on a delivery. 107 | 108 | All deliveries in AMQP must be acknowledged. If you called Channel.Consume 109 | with autoAck true then the server will be automatically ack each message and 110 | this method should not be called. Otherwise, you must call Delivery.Ack after 111 | you have successfully processed this delivery. 112 | 113 | When multiple is true, this delivery and all prior unacknowledged deliveries 114 | on the same channel will be acknowledged. This is useful for batch processing 115 | of deliveries. 116 | 117 | An error will indicate that the acknowledge could not be delivered to the 118 | channel it was sent from. 119 | 120 | Either Delivery.Ack, Delivery.Reject or Delivery.Nack must be called for every 121 | delivery that is not automatically acknowledged. 122 | */ 123 | func (d Delivery) Ack(multiple bool) error { 124 | if d.Acknowledger == nil { 125 | return errDeliveryNotInitialized 126 | } 127 | return d.Acknowledger.Ack(d.DeliveryTag, multiple) 128 | } 129 | 130 | /* 131 | Reject delegates a negatively acknowledgement through the Acknowledger interface. 132 | 133 | When requeue is true, queue this message to be delivered to a consumer on a 134 | different channel. When requeue is false or the server is unable to queue this 135 | message, it will be dropped. 136 | 137 | If you are batch processing deliveries, and your server supports it, prefer 138 | Delivery.Nack. 139 | 140 | Either Delivery.Ack, Delivery.Reject or Delivery.Nack must be called for every 141 | delivery that is not automatically acknowledged. 142 | */ 143 | func (d Delivery) Reject(requeue bool) error { 144 | if d.Acknowledger == nil { 145 | return errDeliveryNotInitialized 146 | } 147 | return d.Acknowledger.Reject(d.DeliveryTag, requeue) 148 | } 149 | 150 | /* 151 | Nack negatively acknowledge the delivery of message(s) identified by the 152 | delivery tag from either the client or server. 153 | 154 | When multiple is true, nack messages up to and including delivered messages up 155 | until the delivery tag delivered on the same channel. 156 | 157 | When requeue is true, request the server to deliver this message to a different 158 | consumer. If it is not possible or requeue is false, the message will be 159 | dropped or delivered to a server configured dead-letter queue. 160 | 161 | This method must not be used to select or requeue messages the client wishes 162 | not to handle, rather it is to inform the server that the client is incapable 163 | of handling this message at this time. 164 | 165 | Either Delivery.Ack, Delivery.Reject or Delivery.Nack must be called for every 166 | delivery that is not automatically acknowledged. 167 | */ 168 | func (d Delivery) Nack(multiple, requeue bool) error { 169 | if d.Acknowledger == nil { 170 | return errDeliveryNotInitialized 171 | } 172 | return d.Acknowledger.Nack(d.DeliveryTag, multiple, requeue) 173 | } 174 | -------------------------------------------------------------------------------- /delivery_test.go: -------------------------------------------------------------------------------- 1 | package amqp 2 | 3 | import "testing" 4 | 5 | func shouldNotPanic(t *testing.T) { 6 | if err := recover(); err != nil { 7 | t.Fatalf("should not panic, got: %s", err) 8 | } 9 | } 10 | 11 | // A closed delivery chan could produce zero value. Ack/Nack/Reject on these 12 | // deliveries can produce a nil pointer panic. Instead return an error when 13 | // the method can never be successful. 14 | func TestAckZeroValueAcknowledgerDoesNotPanic(t *testing.T) { 15 | defer shouldNotPanic(t) 16 | if err := (Delivery{}).Ack(false); err == nil { 17 | t.Errorf("expected Delivery{}.Ack to error") 18 | } 19 | } 20 | 21 | func TestNackZeroValueAcknowledgerDoesNotPanic(t *testing.T) { 22 | defer shouldNotPanic(t) 23 | if err := (Delivery{}).Nack(false, false); err == nil { 24 | t.Errorf("expected Delivery{}.Ack to error") 25 | } 26 | } 27 | 28 | func TestRejectZeroValueAcknowledgerDoesNotPanic(t *testing.T) { 29 | defer shouldNotPanic(t) 30 | if err := (Delivery{}).Reject(false); err == nil { 31 | t.Errorf("expected Delivery{}.Ack to error") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // Source code and contact info at http://github.com/streadway/amqp 5 | 6 | /* 7 | Package amqp is an AMQP 0.9.1 client with RabbitMQ extensions 8 | 9 | Understand the AMQP 0.9.1 messaging model by reviewing these links first. Much 10 | of the terminology in this library directly relates to AMQP concepts. 11 | 12 | Resources 13 | 14 | http://www.rabbitmq.com/tutorials/amqp-concepts.html 15 | http://www.rabbitmq.com/getstarted.html 16 | http://www.rabbitmq.com/amqp-0-9-1-reference.html 17 | 18 | Design 19 | 20 | Most other broker clients publish to queues, but in AMQP, clients publish 21 | Exchanges instead. AMQP is programmable, meaning that both the producers and 22 | consumers agree on the configuration of the broker, instead of requiring an 23 | operator or system configuration that declares the logical topology in the 24 | broker. The routing between producers and consumer queues is via Bindings. 25 | These bindings form the logical topology of the broker. 26 | 27 | In this library, a message sent from publisher is called a "Publishing" and a 28 | message received to a consumer is called a "Delivery". The fields of 29 | Publishings and Deliveries are close but not exact mappings to the underlying 30 | wire format to maintain stronger types. Many other libraries will combine 31 | message properties with message headers. In this library, the message well 32 | known properties are strongly typed fields on the Publishings and Deliveries, 33 | whereas the user defined headers are in the Headers field. 34 | 35 | The method naming closely matches the protocol's method name with positional 36 | parameters mapping to named protocol message fields. The motivation here is to 37 | present a comprehensive view over all possible interactions with the server. 38 | 39 | Generally, methods that map to protocol methods of the "basic" class will be 40 | elided in this interface, and "select" methods of various channel mode selectors 41 | will be elided for example Channel.Confirm and Channel.Tx. 42 | 43 | The library is intentionally designed to be synchronous, where responses for 44 | each protocol message are required to be received in an RPC manner. Some 45 | methods have a noWait parameter like Channel.QueueDeclare, and some methods are 46 | asynchronous like Channel.Publish. The error values should still be checked for 47 | these methods as they will indicate IO failures like when the underlying 48 | connection closes. 49 | 50 | Asynchronous Events 51 | 52 | Clients of this library may be interested in receiving some of the protocol 53 | messages other than Deliveries like basic.ack methods while a channel is in 54 | confirm mode. 55 | 56 | The Notify* methods with Connection and Channel receivers model the pattern of 57 | asynchronous events like closes due to exceptions, or messages that are sent out 58 | of band from an RPC call like basic.ack or basic.flow. 59 | 60 | Any asynchronous events, including Deliveries and Publishings must always have 61 | a receiver until the corresponding chans are closed. Without asynchronous 62 | receivers, the sychronous methods will block. 63 | 64 | Use Case 65 | 66 | It's important as a client to an AMQP topology to ensure the state of the 67 | broker matches your expectations. For both publish and consume use cases, 68 | make sure you declare the queues, exchanges and bindings you expect to exist 69 | prior to calling Channel.Publish or Channel.Consume. 70 | 71 | // Connections start with amqp.Dial() typically from a command line argument 72 | // or environment variable. 73 | connection, err := amqp.Dial(os.Getenv("AMQP_URL")) 74 | 75 | // To cleanly shutdown by flushing kernel buffers, make sure to close and 76 | // wait for the response. 77 | defer connection.Close() 78 | 79 | // Most operations happen on a channel. If any error is returned on a 80 | // channel, the channel will no longer be valid, throw it away and try with 81 | // a different channel. If you use many channels, it's useful for the 82 | // server to 83 | channel, err := connection.Channel() 84 | 85 | // Declare your topology here, if it doesn't exist, it will be created, if 86 | // it existed already and is not what you expect, then that's considered an 87 | // error. 88 | 89 | // Use your connection on this topology with either Publish or Consume, or 90 | // inspect your queues with QueueInspect. It's unwise to mix Publish and 91 | // Consume to let TCP do its job well. 92 | 93 | SSL/TLS - Secure connections 94 | 95 | When Dial encounters an amqps:// scheme, it will use the zero value of a 96 | tls.Config. This will only perform server certificate and host verification. 97 | 98 | Use DialTLS when you wish to provide a client certificate (recommended), 99 | include a private certificate authority's certificate in the cert chain for 100 | server validity, or run insecure by not verifying the server certificate dial 101 | your own connection. DialTLS will use the provided tls.Config when it 102 | encounters an amqps:// scheme and will dial a plain connection when it 103 | encounters an amqp:// scheme. 104 | 105 | SSL/TLS in RabbitMQ is documented here: http://www.rabbitmq.com/ssl.html 106 | 107 | */ 108 | package amqp 109 | -------------------------------------------------------------------------------- /example_client_test.go: -------------------------------------------------------------------------------- 1 | package amqp_test 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "log" 7 | "os" 8 | "time" 9 | 10 | "github.com/streadway/amqp" 11 | ) 12 | 13 | // This exports a Session object that wraps this library. It 14 | // automatically reconnects when the connection fails, and 15 | // blocks all pushes until the connection succeeds. It also 16 | // confirms every outgoing message, so none are lost. 17 | // It doesn't automatically ack each message, but leaves that 18 | // to the parent process, since it is usage-dependent. 19 | // 20 | // Try running this in one terminal, and `rabbitmq-server` in another. 21 | // Stop & restart RabbitMQ to see how the queue reacts. 22 | func Example() { 23 | name := "job_queue" 24 | addr := "amqp://guest:guest@localhost:5672/" 25 | queue := New(name, addr) 26 | message := []byte("message") 27 | // Attempt to push a message every 2 seconds 28 | for { 29 | time.Sleep(time.Second * 2) 30 | if err := queue.Push(message); err != nil { 31 | fmt.Printf("Push failed: %s\n", err) 32 | } else { 33 | fmt.Println("Push succeeded!") 34 | } 35 | } 36 | } 37 | 38 | type Session struct { 39 | name string 40 | logger *log.Logger 41 | connection *amqp.Connection 42 | channel *amqp.Channel 43 | done chan bool 44 | notifyConnClose chan *amqp.Error 45 | notifyChanClose chan *amqp.Error 46 | notifyConfirm chan amqp.Confirmation 47 | isReady bool 48 | } 49 | 50 | const ( 51 | // When reconnecting to the server after connection failure 52 | reconnectDelay = 5 * time.Second 53 | 54 | // When setting up the channel after a channel exception 55 | reInitDelay = 2 * time.Second 56 | 57 | // When resending messages the server didn't confirm 58 | resendDelay = 5 * time.Second 59 | ) 60 | 61 | var ( 62 | errNotConnected = errors.New("not connected to a server") 63 | errAlreadyClosed = errors.New("already closed: not connected to the server") 64 | errShutdown = errors.New("session is shutting down") 65 | ) 66 | 67 | // New creates a new consumer state instance, and automatically 68 | // attempts to connect to the server. 69 | func New(name string, addr string) *Session { 70 | session := Session{ 71 | logger: log.New(os.Stdout, "", log.LstdFlags), 72 | name: name, 73 | done: make(chan bool), 74 | } 75 | go session.handleReconnect(addr) 76 | return &session 77 | } 78 | 79 | // handleReconnect will wait for a connection error on 80 | // notifyConnClose, and then continuously attempt to reconnect. 81 | func (session *Session) handleReconnect(addr string) { 82 | for { 83 | session.isReady = false 84 | log.Println("Attempting to connect") 85 | 86 | conn, err := session.connect(addr) 87 | 88 | if err != nil { 89 | log.Println("Failed to connect. Retrying...") 90 | 91 | select { 92 | case <-session.done: 93 | return 94 | case <-time.After(reconnectDelay): 95 | } 96 | continue 97 | } 98 | 99 | if done := session.handleReInit(conn); done { 100 | break 101 | } 102 | } 103 | } 104 | 105 | // connect will create a new AMQP connection 106 | func (session *Session) connect(addr string) (*amqp.Connection, error) { 107 | conn, err := amqp.Dial(addr) 108 | 109 | if err != nil { 110 | return nil, err 111 | } 112 | 113 | session.changeConnection(conn) 114 | log.Println("Connected!") 115 | return conn, nil 116 | } 117 | 118 | // handleReconnect will wait for a channel error 119 | // and then continuously attempt to re-initialize both channels 120 | func (session *Session) handleReInit(conn *amqp.Connection) bool { 121 | for { 122 | session.isReady = false 123 | 124 | err := session.init(conn) 125 | 126 | if err != nil { 127 | log.Println("Failed to initialize channel. Retrying...") 128 | 129 | select { 130 | case <-session.done: 131 | return true 132 | case <-time.After(reInitDelay): 133 | } 134 | continue 135 | } 136 | 137 | select { 138 | case <-session.done: 139 | return true 140 | case <-session.notifyConnClose: 141 | log.Println("Connection closed. Reconnecting...") 142 | return false 143 | case <-session.notifyChanClose: 144 | log.Println("Channel closed. Re-running init...") 145 | } 146 | } 147 | } 148 | 149 | // init will initialize channel & declare queue 150 | func (session *Session) init(conn *amqp.Connection) error { 151 | ch, err := conn.Channel() 152 | 153 | if err != nil { 154 | return err 155 | } 156 | 157 | err = ch.Confirm(false) 158 | 159 | if err != nil { 160 | return err 161 | } 162 | _, err = ch.QueueDeclare( 163 | session.name, 164 | false, // Durable 165 | false, // Delete when unused 166 | false, // Exclusive 167 | false, // No-wait 168 | nil, // Arguments 169 | ) 170 | 171 | if err != nil { 172 | return err 173 | } 174 | 175 | session.changeChannel(ch) 176 | session.isReady = true 177 | log.Println("Setup!") 178 | 179 | return nil 180 | } 181 | 182 | // changeConnection takes a new connection to the queue, 183 | // and updates the close listener to reflect this. 184 | func (session *Session) changeConnection(connection *amqp.Connection) { 185 | session.connection = connection 186 | session.notifyConnClose = make(chan *amqp.Error) 187 | session.connection.NotifyClose(session.notifyConnClose) 188 | } 189 | 190 | // changeChannel takes a new channel to the queue, 191 | // and updates the channel listeners to reflect this. 192 | func (session *Session) changeChannel(channel *amqp.Channel) { 193 | session.channel = channel 194 | session.notifyChanClose = make(chan *amqp.Error) 195 | session.notifyConfirm = make(chan amqp.Confirmation, 1) 196 | session.channel.NotifyClose(session.notifyChanClose) 197 | session.channel.NotifyPublish(session.notifyConfirm) 198 | } 199 | 200 | // Push will push data onto the queue, and wait for a confirm. 201 | // If no confirms are received until within the resendTimeout, 202 | // it continuously re-sends messages until a confirm is received. 203 | // This will block until the server sends a confirm. Errors are 204 | // only returned if the push action itself fails, see UnsafePush. 205 | func (session *Session) Push(data []byte) error { 206 | if !session.isReady { 207 | return errors.New("failed to push: not connected") 208 | } 209 | for { 210 | err := session.UnsafePush(data) 211 | if err != nil { 212 | session.logger.Println("Push failed. Retrying...") 213 | select { 214 | case <-session.done: 215 | return errShutdown 216 | case <-time.After(resendDelay): 217 | } 218 | continue 219 | } 220 | select { 221 | case confirm := <-session.notifyConfirm: 222 | if confirm.Ack { 223 | session.logger.Println("Push confirmed!") 224 | return nil 225 | } 226 | case <-time.After(resendDelay): 227 | } 228 | session.logger.Println("Push didn't confirm. Retrying...") 229 | } 230 | } 231 | 232 | // UnsafePush will push to the queue without checking for 233 | // confirmation. It returns an error if it fails to connect. 234 | // No guarantees are provided for whether the server will 235 | // recieve the message. 236 | func (session *Session) UnsafePush(data []byte) error { 237 | if !session.isReady { 238 | return errNotConnected 239 | } 240 | return session.channel.Publish( 241 | "", // Exchange 242 | session.name, // Routing key 243 | false, // Mandatory 244 | false, // Immediate 245 | amqp.Publishing{ 246 | ContentType: "text/plain", 247 | Body: data, 248 | }, 249 | ) 250 | } 251 | 252 | // Stream will continuously put queue items on the channel. 253 | // It is required to call delivery.Ack when it has been 254 | // successfully processed, or delivery.Nack when it fails. 255 | // Ignoring this will cause data to build up on the server. 256 | func (session *Session) Stream() (<-chan amqp.Delivery, error) { 257 | if !session.isReady { 258 | return nil, errNotConnected 259 | } 260 | return session.channel.Consume( 261 | session.name, 262 | "", // Consumer 263 | false, // Auto-Ack 264 | false, // Exclusive 265 | false, // No-local 266 | false, // No-Wait 267 | nil, // Args 268 | ) 269 | } 270 | 271 | // Close will cleanly shutdown the channel and connection. 272 | func (session *Session) Close() error { 273 | if !session.isReady { 274 | return errAlreadyClosed 275 | } 276 | err := session.channel.Close() 277 | if err != nil { 278 | return err 279 | } 280 | err = session.connection.Close() 281 | if err != nil { 282 | return err 283 | } 284 | close(session.done) 285 | session.isReady = false 286 | return nil 287 | } 288 | -------------------------------------------------------------------------------- /examples_test.go: -------------------------------------------------------------------------------- 1 | package amqp_test 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "io/ioutil" 7 | "log" 8 | "net" 9 | "runtime" 10 | "time" 11 | 12 | "github.com/streadway/amqp" 13 | ) 14 | 15 | func ExampleConfig_timeout() { 16 | // Provide your own anonymous Dial function that delgates to net.DialTimout 17 | // for custom timeouts 18 | 19 | conn, err := amqp.DialConfig("amqp:///", amqp.Config{ 20 | Dial: func(network, addr string) (net.Conn, error) { 21 | return net.DialTimeout(network, addr, 2*time.Second) 22 | }, 23 | }) 24 | 25 | log.Printf("conn: %v, err: %v", conn, err) 26 | } 27 | 28 | func ExampleDialTLS() { 29 | // This example assume you have a RabbitMQ node running on localhost 30 | // with TLS enabled. 31 | // 32 | // The easiest way to create the CA, certificates and keys required for these 33 | // examples is by using tls-gen: https://github.com/michaelklishin/tls-gen 34 | // 35 | // A comprehensive RabbitMQ TLS guide can be found at 36 | // http://www.rabbitmq.com/ssl.html 37 | // 38 | // Once you have the required TLS files in place, use the following 39 | // rabbitmq.config example for the RabbitMQ node that you will run on 40 | // localhost: 41 | // 42 | // [ 43 | // {rabbit, [ 44 | // {tcp_listeners, []}, % listens on 127.0.0.1:5672 45 | // {ssl_listeners, [5671]}, % listens on 0.0.0.0:5671 46 | // {ssl_options, [{cacertfile,"/path/to/your/testca/cacert.pem"}, 47 | // {certfile,"/path/to/your/server/cert.pem"}, 48 | // {keyfile,"/path/to/your/server/key.pem"}, 49 | // {verify,verify_peer}, 50 | // {fail_if_no_peer_cert,true}]} 51 | // ]} 52 | // ]. 53 | // 54 | // 55 | // In the above rabbitmq.config example, we are disabling the plain AMQP port 56 | // and verifying that clients and fail if no certificate is presented. 57 | // 58 | // The self-signing certificate authority's certificate (cacert.pem) must be 59 | // included in the RootCAs to be trusted, otherwise the server certificate 60 | // will fail certificate verification. 61 | // 62 | // Alternatively to adding it to the tls.Config. you can add the CA's cert to 63 | // your system's root CAs. The tls package will use the system roots 64 | // specific to each support OS. Under OS X, add (drag/drop) cacert.pem 65 | // file to the 'Certificates' section of KeyChain.app to add and always 66 | // trust. You can also add it via the command line: 67 | // 68 | // security add-certificate testca/cacert.pem 69 | // security add-trusted-cert testca/cacert.pem 70 | // 71 | // If you depend on the system root CAs, then use nil for the RootCAs field 72 | // so the system roots will be loaded instead. 73 | // 74 | // Server names are validated by the crypto/tls package, so the server 75 | // certificate must be made for the hostname in the URL. Find the commonName 76 | // (CN) and make sure the hostname in the URL matches this common name. Per 77 | // the RabbitMQ instructions (or tls-gen) for a self-signed cert, this defaults to the 78 | // current hostname. 79 | // 80 | // openssl x509 -noout -in /path/to/certificate.pem -subject 81 | // 82 | // If your server name in your certificate is different than the host you are 83 | // connecting to, set the hostname used for verification in 84 | // ServerName field of the tls.Config struct. 85 | cfg := new(tls.Config) 86 | 87 | // see at the top 88 | cfg.RootCAs = x509.NewCertPool() 89 | 90 | if ca, err := ioutil.ReadFile("testca/cacert.pem"); err == nil { 91 | cfg.RootCAs.AppendCertsFromPEM(ca) 92 | } 93 | 94 | // Move the client cert and key to a location specific to your application 95 | // and load them here. 96 | 97 | if cert, err := tls.LoadX509KeyPair("client/cert.pem", "client/key.pem"); err == nil { 98 | cfg.Certificates = append(cfg.Certificates, cert) 99 | } 100 | 101 | // see a note about Common Name (CN) at the top 102 | conn, err := amqp.DialTLS("amqps://server-name-from-certificate/", cfg) 103 | 104 | log.Printf("conn: %v, err: %v", conn, err) 105 | } 106 | 107 | func ExampleChannel_Confirm_bridge() { 108 | // This example acts as a bridge, shoveling all messages sent from the source 109 | // exchange "log" to destination exchange "log". 110 | 111 | // Confirming publishes can help from overproduction and ensure every message 112 | // is delivered. 113 | 114 | // Setup the source of the store and forward 115 | source, err := amqp.Dial("amqp://source/") 116 | if err != nil { 117 | log.Fatalf("connection.open source: %s", err) 118 | } 119 | defer source.Close() 120 | 121 | chs, err := source.Channel() 122 | if err != nil { 123 | log.Fatalf("channel.open source: %s", err) 124 | } 125 | 126 | if err := chs.ExchangeDeclare("log", "topic", true, false, false, false, nil); err != nil { 127 | log.Fatalf("exchange.declare destination: %s", err) 128 | } 129 | 130 | if _, err := chs.QueueDeclare("remote-tee", true, true, false, false, nil); err != nil { 131 | log.Fatalf("queue.declare source: %s", err) 132 | } 133 | 134 | if err := chs.QueueBind("remote-tee", "#", "logs", false, nil); err != nil { 135 | log.Fatalf("queue.bind source: %s", err) 136 | } 137 | 138 | shovel, err := chs.Consume("remote-tee", "shovel", false, false, false, false, nil) 139 | if err != nil { 140 | log.Fatalf("basic.consume source: %s", err) 141 | } 142 | 143 | // Setup the destination of the store and forward 144 | destination, err := amqp.Dial("amqp://destination/") 145 | if err != nil { 146 | log.Fatalf("connection.open destination: %s", err) 147 | } 148 | defer destination.Close() 149 | 150 | chd, err := destination.Channel() 151 | if err != nil { 152 | log.Fatalf("channel.open destination: %s", err) 153 | } 154 | 155 | if err := chd.ExchangeDeclare("log", "topic", true, false, false, false, nil); err != nil { 156 | log.Fatalf("exchange.declare destination: %s", err) 157 | } 158 | 159 | // Buffer of 1 for our single outstanding publishing 160 | confirms := chd.NotifyPublish(make(chan amqp.Confirmation, 1)) 161 | 162 | if err := chd.Confirm(false); err != nil { 163 | log.Fatalf("confirm.select destination: %s", err) 164 | } 165 | 166 | // Now pump the messages, one by one, a smarter implementation 167 | // would batch the deliveries and use multiple ack/nacks 168 | for { 169 | msg, ok := <-shovel 170 | if !ok { 171 | log.Fatalf("source channel closed, see the reconnect example for handling this") 172 | } 173 | 174 | err = chd.Publish("logs", msg.RoutingKey, false, false, amqp.Publishing{ 175 | // Copy all the properties 176 | ContentType: msg.ContentType, 177 | ContentEncoding: msg.ContentEncoding, 178 | DeliveryMode: msg.DeliveryMode, 179 | Priority: msg.Priority, 180 | CorrelationId: msg.CorrelationId, 181 | ReplyTo: msg.ReplyTo, 182 | Expiration: msg.Expiration, 183 | MessageId: msg.MessageId, 184 | Timestamp: msg.Timestamp, 185 | Type: msg.Type, 186 | UserId: msg.UserId, 187 | AppId: msg.AppId, 188 | 189 | // Custom headers 190 | Headers: msg.Headers, 191 | 192 | // And the body 193 | Body: msg.Body, 194 | }) 195 | 196 | if err != nil { 197 | msg.Nack(false, false) 198 | log.Fatalf("basic.publish destination: %+v", msg) 199 | } 200 | 201 | // only ack the source delivery when the destination acks the publishing 202 | if confirmed := <-confirms; confirmed.Ack { 203 | msg.Ack(false) 204 | } else { 205 | msg.Nack(false, false) 206 | } 207 | } 208 | } 209 | 210 | func ExampleChannel_Consume() { 211 | // Connects opens an AMQP connection from the credentials in the URL. 212 | conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") 213 | if err != nil { 214 | log.Fatalf("connection.open: %s", err) 215 | } 216 | defer conn.Close() 217 | 218 | c, err := conn.Channel() 219 | if err != nil { 220 | log.Fatalf("channel.open: %s", err) 221 | } 222 | 223 | // We declare our topology on both the publisher and consumer to ensure they 224 | // are the same. This is part of AMQP being a programmable messaging model. 225 | // 226 | // See the Channel.Publish example for the complimentary declare. 227 | err = c.ExchangeDeclare("logs", "topic", true, false, false, false, nil) 228 | if err != nil { 229 | log.Fatalf("exchange.declare: %s", err) 230 | } 231 | 232 | // Establish our queue topologies that we are responsible for 233 | type bind struct { 234 | queue string 235 | key string 236 | } 237 | 238 | bindings := []bind{ 239 | {"page", "alert"}, 240 | {"email", "info"}, 241 | {"firehose", "#"}, 242 | } 243 | 244 | for _, b := range bindings { 245 | _, err = c.QueueDeclare(b.queue, true, false, false, false, nil) 246 | if err != nil { 247 | log.Fatalf("queue.declare: %v", err) 248 | } 249 | 250 | err = c.QueueBind(b.queue, b.key, "logs", false, nil) 251 | if err != nil { 252 | log.Fatalf("queue.bind: %v", err) 253 | } 254 | } 255 | 256 | // Set our quality of service. Since we're sharing 3 consumers on the same 257 | // channel, we want at least 3 messages in flight. 258 | err = c.Qos(3, 0, false) 259 | if err != nil { 260 | log.Fatalf("basic.qos: %v", err) 261 | } 262 | 263 | // Establish our consumers that have different responsibilities. Our first 264 | // two queues do not ack the messages on the server, so require to be acked 265 | // on the client. 266 | 267 | pages, err := c.Consume("page", "pager", false, false, false, false, nil) 268 | if err != nil { 269 | log.Fatalf("basic.consume: %v", err) 270 | } 271 | 272 | go func() { 273 | for log := range pages { 274 | // ... this consumer is responsible for sending pages per log 275 | log.Ack(false) 276 | } 277 | }() 278 | 279 | // Notice how the concern for which messages arrive here are in the AMQP 280 | // topology and not in the queue. We let the server pick a consumer tag this 281 | // time. 282 | 283 | emails, err := c.Consume("email", "", false, false, false, false, nil) 284 | if err != nil { 285 | log.Fatalf("basic.consume: %v", err) 286 | } 287 | 288 | go func() { 289 | for log := range emails { 290 | // ... this consumer is responsible for sending emails per log 291 | log.Ack(false) 292 | } 293 | }() 294 | 295 | // This consumer requests that every message is acknowledged as soon as it's 296 | // delivered. 297 | 298 | firehose, err := c.Consume("firehose", "", true, false, false, false, nil) 299 | if err != nil { 300 | log.Fatalf("basic.consume: %v", err) 301 | } 302 | 303 | // To show how to process the items in parallel, we'll use a work pool. 304 | for i := 0; i < runtime.NumCPU(); i++ { 305 | go func(work <-chan amqp.Delivery) { 306 | for range work { 307 | // ... this consumer pulls from the firehose and doesn't need to acknowledge 308 | } 309 | }(firehose) 310 | } 311 | 312 | // Wait until you're ready to finish, could be a signal handler here. 313 | time.Sleep(10 * time.Second) 314 | 315 | // Cancelling a consumer by name will finish the range and gracefully end the 316 | // goroutine 317 | err = c.Cancel("pager", false) 318 | if err != nil { 319 | log.Fatalf("basic.cancel: %v", err) 320 | } 321 | 322 | // deferred closing the Connection will also finish the consumer's ranges of 323 | // their delivery chans. If you need every delivery to be processed, make 324 | // sure to wait for all consumers goroutines to finish before exiting your 325 | // process. 326 | } 327 | 328 | func ExampleChannel_Publish() { 329 | // Connects opens an AMQP connection from the credentials in the URL. 330 | conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") 331 | if err != nil { 332 | log.Fatalf("connection.open: %s", err) 333 | } 334 | 335 | // This waits for a server acknowledgment which means the sockets will have 336 | // flushed all outbound publishings prior to returning. It's important to 337 | // block on Close to not lose any publishings. 338 | defer conn.Close() 339 | 340 | c, err := conn.Channel() 341 | if err != nil { 342 | log.Fatalf("channel.open: %s", err) 343 | } 344 | 345 | // We declare our topology on both the publisher and consumer to ensure they 346 | // are the same. This is part of AMQP being a programmable messaging model. 347 | // 348 | // See the Channel.Consume example for the complimentary declare. 349 | err = c.ExchangeDeclare("logs", "topic", true, false, false, false, nil) 350 | if err != nil { 351 | log.Fatalf("exchange.declare: %v", err) 352 | } 353 | 354 | // Prepare this message to be persistent. Your publishing requirements may 355 | // be different. 356 | msg := amqp.Publishing{ 357 | DeliveryMode: amqp.Persistent, 358 | Timestamp: time.Now(), 359 | ContentType: "text/plain", 360 | Body: []byte("Go Go AMQP!"), 361 | } 362 | 363 | // This is not a mandatory delivery, so it will be dropped if there are no 364 | // queues bound to the logs exchange. 365 | err = c.Publish("logs", "info", false, false, msg) 366 | if err != nil { 367 | // Since publish is asynchronous this can happen if the network connection 368 | // is reset or if the server has run out of resources. 369 | log.Fatalf("basic.publish: %v", err) 370 | } 371 | } 372 | 373 | func publishAllTheThings(conn *amqp.Connection) { 374 | // ... snarf snarf, barf barf 375 | } 376 | 377 | func ExampleConnection_NotifyBlocked() { 378 | // Simply logs when the server throttles the TCP connection for publishers 379 | 380 | // Test this by tuning your server to have a low memory watermark: 381 | // rabbitmqctl set_vm_memory_high_watermark 0.00000001 382 | 383 | conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") 384 | if err != nil { 385 | log.Fatalf("connection.open: %s", err) 386 | } 387 | defer conn.Close() 388 | 389 | blockings := conn.NotifyBlocked(make(chan amqp.Blocking)) 390 | go func() { 391 | for b := range blockings { 392 | if b.Active { 393 | log.Printf("TCP blocked: %q", b.Reason) 394 | } else { 395 | log.Printf("TCP unblocked") 396 | } 397 | } 398 | }() 399 | 400 | // Your application domain channel setup publishings 401 | publishAllTheThings(conn) 402 | } 403 | -------------------------------------------------------------------------------- /fuzz.go: -------------------------------------------------------------------------------- 1 | // +build gofuzz 2 | 3 | package amqp 4 | 5 | import "bytes" 6 | 7 | func Fuzz(data []byte) int { 8 | r := reader{bytes.NewReader(data)} 9 | frame, err := r.ReadFrame() 10 | if err != nil { 11 | if frame != nil { 12 | panic("frame is not nil") 13 | } 14 | return 0 15 | } 16 | return 1 17 | } 18 | -------------------------------------------------------------------------------- /gen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | go run spec/gen.go < spec/amqp0-9-1.stripped.extended.xml | gofmt > spec091.go 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | // Deprecated: Consider using the github.com/rabbitmq/amqp091-go package instead. 2 | module github.com/streadway/amqp 3 | 4 | go 1.10 5 | -------------------------------------------------------------------------------- /pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | LATEST_STABLE_SUPPORTED_GO_VERSION="1.11" 4 | 5 | main() { 6 | if local_go_version_is_latest_stable 7 | then 8 | run_gofmt 9 | run_golint 10 | run_govet 11 | fi 12 | run_unit_tests 13 | } 14 | 15 | local_go_version_is_latest_stable() { 16 | go version | grep -q $LATEST_STABLE_SUPPORTED_GO_VERSION 17 | } 18 | 19 | log_error() { 20 | echo "$*" 1>&2 21 | } 22 | 23 | run_gofmt() { 24 | GOFMT_FILES=$(gofmt -l .) 25 | if [ -n "$GOFMT_FILES" ] 26 | then 27 | log_error "gofmt failed for the following files: 28 | $GOFMT_FILES 29 | 30 | please run 'gofmt -w .' on your changes before committing." 31 | exit 1 32 | fi 33 | } 34 | 35 | run_golint() { 36 | GOLINT_ERRORS=$(golint ./... | grep -v "Id should be") 37 | if [ -n "$GOLINT_ERRORS" ] 38 | then 39 | log_error "golint failed for the following reasons: 40 | $GOLINT_ERRORS 41 | 42 | please run 'golint ./...' on your changes before committing." 43 | exit 1 44 | fi 45 | } 46 | 47 | run_govet() { 48 | GOVET_ERRORS=$(go tool vet ./*.go 2>&1) 49 | if [ -n "$GOVET_ERRORS" ] 50 | then 51 | log_error "go vet failed for the following reasons: 52 | $GOVET_ERRORS 53 | 54 | please run 'go tool vet ./*.go' on your changes before committing." 55 | exit 1 56 | fi 57 | } 58 | 59 | run_unit_tests() { 60 | if [ -z "$NOTEST" ] 61 | then 62 | log_error 'Running short tests...' 63 | env AMQP_URL= go test -short 64 | fi 65 | } 66 | 67 | main 68 | -------------------------------------------------------------------------------- /read.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // Source code and contact info at http://github.com/streadway/amqp 5 | 6 | package amqp 7 | 8 | import ( 9 | "bytes" 10 | "encoding/binary" 11 | "errors" 12 | "io" 13 | "time" 14 | ) 15 | 16 | /* 17 | Reads a frame from an input stream and returns an interface that can be cast into 18 | one of the following: 19 | 20 | methodFrame 21 | PropertiesFrame 22 | bodyFrame 23 | heartbeatFrame 24 | 25 | 2.3.5 frame Details 26 | 27 | All frames consist of a header (7 octets), a payload of arbitrary size, and a 28 | 'frame-end' octet that detects malformed frames: 29 | 30 | 0 1 3 7 size+7 size+8 31 | +------+---------+-------------+ +------------+ +-----------+ 32 | | type | channel | size | | payload | | frame-end | 33 | +------+---------+-------------+ +------------+ +-----------+ 34 | octet short long size octets octet 35 | 36 | To read a frame, we: 37 | 1. Read the header and check the frame type and channel. 38 | 2. Depending on the frame type, we read the payload and process it. 39 | 3. Read the frame end octet. 40 | 41 | In realistic implementations where performance is a concern, we would use 42 | “read-ahead buffering” or 43 | 44 | “gathering reads” to avoid doing three separate system calls to read a frame. 45 | */ 46 | func (r *reader) ReadFrame() (frame frame, err error) { 47 | var scratch [7]byte 48 | 49 | if _, err = io.ReadFull(r.r, scratch[:7]); err != nil { 50 | return 51 | } 52 | 53 | typ := uint8(scratch[0]) 54 | channel := binary.BigEndian.Uint16(scratch[1:3]) 55 | size := binary.BigEndian.Uint32(scratch[3:7]) 56 | 57 | switch typ { 58 | case frameMethod: 59 | if frame, err = r.parseMethodFrame(channel, size); err != nil { 60 | return 61 | } 62 | 63 | case frameHeader: 64 | if frame, err = r.parseHeaderFrame(channel, size); err != nil { 65 | return 66 | } 67 | 68 | case frameBody: 69 | if frame, err = r.parseBodyFrame(channel, size); err != nil { 70 | return nil, err 71 | } 72 | 73 | case frameHeartbeat: 74 | if frame, err = r.parseHeartbeatFrame(channel, size); err != nil { 75 | return 76 | } 77 | 78 | default: 79 | return nil, ErrFrame 80 | } 81 | 82 | if _, err = io.ReadFull(r.r, scratch[:1]); err != nil { 83 | return nil, err 84 | } 85 | 86 | if scratch[0] != frameEnd { 87 | return nil, ErrFrame 88 | } 89 | 90 | return 91 | } 92 | 93 | func readShortstr(r io.Reader) (v string, err error) { 94 | var length uint8 95 | if err = binary.Read(r, binary.BigEndian, &length); err != nil { 96 | return 97 | } 98 | 99 | bytes := make([]byte, length) 100 | if _, err = io.ReadFull(r, bytes); err != nil { 101 | return 102 | } 103 | return string(bytes), nil 104 | } 105 | 106 | func readLongstr(r io.Reader) (v string, err error) { 107 | var length uint32 108 | if err = binary.Read(r, binary.BigEndian, &length); err != nil { 109 | return 110 | } 111 | 112 | // slices can't be longer than max int32 value 113 | if length > (^uint32(0) >> 1) { 114 | return 115 | } 116 | 117 | bytes := make([]byte, length) 118 | if _, err = io.ReadFull(r, bytes); err != nil { 119 | return 120 | } 121 | return string(bytes), nil 122 | } 123 | 124 | func readDecimal(r io.Reader) (v Decimal, err error) { 125 | if err = binary.Read(r, binary.BigEndian, &v.Scale); err != nil { 126 | return 127 | } 128 | if err = binary.Read(r, binary.BigEndian, &v.Value); err != nil { 129 | return 130 | } 131 | return 132 | } 133 | 134 | func readFloat32(r io.Reader) (v float32, err error) { 135 | if err = binary.Read(r, binary.BigEndian, &v); err != nil { 136 | return 137 | } 138 | return 139 | } 140 | 141 | func readFloat64(r io.Reader) (v float64, err error) { 142 | if err = binary.Read(r, binary.BigEndian, &v); err != nil { 143 | return 144 | } 145 | return 146 | } 147 | 148 | func readTimestamp(r io.Reader) (v time.Time, err error) { 149 | var sec int64 150 | if err = binary.Read(r, binary.BigEndian, &sec); err != nil { 151 | return 152 | } 153 | return time.Unix(sec, 0), nil 154 | } 155 | 156 | /* 157 | 'A': []interface{} 158 | 'D': Decimal 159 | 'F': Table 160 | 'I': int32 161 | 'S': string 162 | 'T': time.Time 163 | 'V': nil 164 | 'b': byte 165 | 'd': float64 166 | 'f': float32 167 | 'l': int64 168 | 's': int16 169 | 't': bool 170 | 'x': []byte 171 | */ 172 | func readField(r io.Reader) (v interface{}, err error) { 173 | var typ byte 174 | if err = binary.Read(r, binary.BigEndian, &typ); err != nil { 175 | return 176 | } 177 | 178 | switch typ { 179 | case 't': 180 | var value uint8 181 | if err = binary.Read(r, binary.BigEndian, &value); err != nil { 182 | return 183 | } 184 | return (value != 0), nil 185 | 186 | case 'b': 187 | var value [1]byte 188 | if _, err = io.ReadFull(r, value[0:1]); err != nil { 189 | return 190 | } 191 | return value[0], nil 192 | 193 | case 's': 194 | var value int16 195 | if err = binary.Read(r, binary.BigEndian, &value); err != nil { 196 | return 197 | } 198 | return value, nil 199 | 200 | case 'I': 201 | var value int32 202 | if err = binary.Read(r, binary.BigEndian, &value); err != nil { 203 | return 204 | } 205 | return value, nil 206 | 207 | case 'l': 208 | var value int64 209 | if err = binary.Read(r, binary.BigEndian, &value); err != nil { 210 | return 211 | } 212 | return value, nil 213 | 214 | case 'f': 215 | var value float32 216 | if err = binary.Read(r, binary.BigEndian, &value); err != nil { 217 | return 218 | } 219 | return value, nil 220 | 221 | case 'd': 222 | var value float64 223 | if err = binary.Read(r, binary.BigEndian, &value); err != nil { 224 | return 225 | } 226 | return value, nil 227 | 228 | case 'D': 229 | return readDecimal(r) 230 | 231 | case 'S': 232 | return readLongstr(r) 233 | 234 | case 'A': 235 | return readArray(r) 236 | 237 | case 'T': 238 | return readTimestamp(r) 239 | 240 | case 'F': 241 | return readTable(r) 242 | 243 | case 'x': 244 | var len int32 245 | if err = binary.Read(r, binary.BigEndian, &len); err != nil { 246 | return nil, err 247 | } 248 | 249 | value := make([]byte, len) 250 | if _, err = io.ReadFull(r, value); err != nil { 251 | return nil, err 252 | } 253 | return value, err 254 | 255 | case 'V': 256 | return nil, nil 257 | } 258 | 259 | return nil, ErrSyntax 260 | } 261 | 262 | /* 263 | Field tables are long strings that contain packed name-value pairs. The 264 | name-value pairs are encoded as short string defining the name, and octet 265 | defining the values type and then the value itself. The valid field types for 266 | tables are an extension of the native integer, bit, string, and timestamp 267 | types, and are shown in the grammar. Multi-octet integer fields are always 268 | held in network byte order. 269 | */ 270 | func readTable(r io.Reader) (table Table, err error) { 271 | var nested bytes.Buffer 272 | var str string 273 | 274 | if str, err = readLongstr(r); err != nil { 275 | return 276 | } 277 | 278 | nested.Write([]byte(str)) 279 | 280 | table = make(Table) 281 | 282 | for nested.Len() > 0 { 283 | var key string 284 | var value interface{} 285 | 286 | if key, err = readShortstr(&nested); err != nil { 287 | return 288 | } 289 | 290 | if value, err = readField(&nested); err != nil { 291 | return 292 | } 293 | 294 | table[key] = value 295 | } 296 | 297 | return 298 | } 299 | 300 | func readArray(r io.Reader) ([]interface{}, error) { 301 | var ( 302 | size uint32 303 | err error 304 | ) 305 | 306 | if err = binary.Read(r, binary.BigEndian, &size); err != nil { 307 | return nil, err 308 | } 309 | 310 | var ( 311 | lim = &io.LimitedReader{R: r, N: int64(size)} 312 | arr = []interface{}{} 313 | field interface{} 314 | ) 315 | 316 | for { 317 | if field, err = readField(lim); err != nil { 318 | if err == io.EOF { 319 | break 320 | } 321 | return nil, err 322 | } 323 | arr = append(arr, field) 324 | } 325 | 326 | return arr, nil 327 | } 328 | 329 | // Checks if this bit mask matches the flags bitset 330 | func hasProperty(mask uint16, prop int) bool { 331 | return int(mask)&prop > 0 332 | } 333 | 334 | func (r *reader) parseHeaderFrame(channel uint16, size uint32) (frame frame, err error) { 335 | hf := &headerFrame{ 336 | ChannelId: channel, 337 | } 338 | 339 | if err = binary.Read(r.r, binary.BigEndian, &hf.ClassId); err != nil { 340 | return 341 | } 342 | 343 | if err = binary.Read(r.r, binary.BigEndian, &hf.weight); err != nil { 344 | return 345 | } 346 | 347 | if err = binary.Read(r.r, binary.BigEndian, &hf.Size); err != nil { 348 | return 349 | } 350 | 351 | var flags uint16 352 | 353 | if err = binary.Read(r.r, binary.BigEndian, &flags); err != nil { 354 | return 355 | } 356 | 357 | if hasProperty(flags, flagContentType) { 358 | if hf.Properties.ContentType, err = readShortstr(r.r); err != nil { 359 | return 360 | } 361 | } 362 | if hasProperty(flags, flagContentEncoding) { 363 | if hf.Properties.ContentEncoding, err = readShortstr(r.r); err != nil { 364 | return 365 | } 366 | } 367 | if hasProperty(flags, flagHeaders) { 368 | if hf.Properties.Headers, err = readTable(r.r); err != nil { 369 | return 370 | } 371 | } 372 | if hasProperty(flags, flagDeliveryMode) { 373 | if err = binary.Read(r.r, binary.BigEndian, &hf.Properties.DeliveryMode); err != nil { 374 | return 375 | } 376 | } 377 | if hasProperty(flags, flagPriority) { 378 | if err = binary.Read(r.r, binary.BigEndian, &hf.Properties.Priority); err != nil { 379 | return 380 | } 381 | } 382 | if hasProperty(flags, flagCorrelationId) { 383 | if hf.Properties.CorrelationId, err = readShortstr(r.r); err != nil { 384 | return 385 | } 386 | } 387 | if hasProperty(flags, flagReplyTo) { 388 | if hf.Properties.ReplyTo, err = readShortstr(r.r); err != nil { 389 | return 390 | } 391 | } 392 | if hasProperty(flags, flagExpiration) { 393 | if hf.Properties.Expiration, err = readShortstr(r.r); err != nil { 394 | return 395 | } 396 | } 397 | if hasProperty(flags, flagMessageId) { 398 | if hf.Properties.MessageId, err = readShortstr(r.r); err != nil { 399 | return 400 | } 401 | } 402 | if hasProperty(flags, flagTimestamp) { 403 | if hf.Properties.Timestamp, err = readTimestamp(r.r); err != nil { 404 | return 405 | } 406 | } 407 | if hasProperty(flags, flagType) { 408 | if hf.Properties.Type, err = readShortstr(r.r); err != nil { 409 | return 410 | } 411 | } 412 | if hasProperty(flags, flagUserId) { 413 | if hf.Properties.UserId, err = readShortstr(r.r); err != nil { 414 | return 415 | } 416 | } 417 | if hasProperty(flags, flagAppId) { 418 | if hf.Properties.AppId, err = readShortstr(r.r); err != nil { 419 | return 420 | } 421 | } 422 | if hasProperty(flags, flagReserved1) { 423 | if hf.Properties.reserved1, err = readShortstr(r.r); err != nil { 424 | return 425 | } 426 | } 427 | 428 | return hf, nil 429 | } 430 | 431 | func (r *reader) parseBodyFrame(channel uint16, size uint32) (frame frame, err error) { 432 | bf := &bodyFrame{ 433 | ChannelId: channel, 434 | Body: make([]byte, size), 435 | } 436 | 437 | if _, err = io.ReadFull(r.r, bf.Body); err != nil { 438 | return nil, err 439 | } 440 | 441 | return bf, nil 442 | } 443 | 444 | var errHeartbeatPayload = errors.New("Heartbeats should not have a payload") 445 | 446 | func (r *reader) parseHeartbeatFrame(channel uint16, size uint32) (frame frame, err error) { 447 | hf := &heartbeatFrame{ 448 | ChannelId: channel, 449 | } 450 | 451 | if size > 0 { 452 | return nil, errHeartbeatPayload 453 | } 454 | 455 | return hf, nil 456 | } 457 | -------------------------------------------------------------------------------- /read_test.go: -------------------------------------------------------------------------------- 1 | package amqp 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func TestGoFuzzCrashers(t *testing.T) { 9 | if testing.Short() { 10 | t.Skip("excessive allocation") 11 | } 12 | 13 | testData := []string{ 14 | "\b000000", 15 | "\x02\x16\x10�[��\t\xbdui�" + "\x10\x01\x00\xff\xbf\xef\xbfサn\x99\x00\x10r", 16 | "\x0300\x00\x00\x00\x040000", 17 | } 18 | 19 | for idx, testStr := range testData { 20 | r := reader{strings.NewReader(testStr)} 21 | frame, err := r.ReadFrame() 22 | if err != nil && frame != nil { 23 | t.Errorf("%d. frame is not nil: %#v err = %v", idx, frame, err) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /reconnect_test.go: -------------------------------------------------------------------------------- 1 | package amqp_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/streadway/amqp" 6 | "os" 7 | ) 8 | 9 | // Every connection should declare the topology they expect 10 | func setup(url, queue string) (*amqp.Connection, *amqp.Channel, error) { 11 | conn, err := amqp.Dial(url) 12 | if err != nil { 13 | return nil, nil, err 14 | } 15 | 16 | ch, err := conn.Channel() 17 | if err != nil { 18 | return nil, nil, err 19 | } 20 | 21 | if _, err := ch.QueueDeclare(queue, false, true, false, false, nil); err != nil { 22 | return nil, nil, err 23 | } 24 | 25 | return conn, ch, nil 26 | } 27 | 28 | func consume(url, queue string) (*amqp.Connection, <-chan amqp.Delivery, error) { 29 | conn, ch, err := setup(url, queue) 30 | if err != nil { 31 | return nil, nil, err 32 | } 33 | 34 | // Indicate we only want 1 message to acknowledge at a time. 35 | if err := ch.Qos(1, 0, false); err != nil { 36 | return nil, nil, err 37 | } 38 | 39 | // Exclusive consumer 40 | deliveries, err := ch.Consume(queue, "", false, true, false, false, nil) 41 | 42 | return conn, deliveries, err 43 | } 44 | 45 | func ExampleConnection_reconnect() { 46 | if url := os.Getenv("AMQP_URL"); url != "" { 47 | queue := "example.reconnect" 48 | 49 | // The connection/channel for publishing to interleave the ingress messages 50 | // between reconnects, shares the same topology as the consumer. If we rather 51 | // sent all messages up front, the first consumer would receive every message. 52 | // We would rather show how the messages are not lost between reconnects. 53 | _, pub, err := setup(url, queue) 54 | if err != nil { 55 | fmt.Println("err publisher setup:", err) 56 | return 57 | } 58 | 59 | // Purge the queue from the publisher side to establish initial state 60 | if _, err := pub.QueuePurge(queue, false); err != nil { 61 | fmt.Println("err purge:", err) 62 | return 63 | } 64 | 65 | // Reconnect simulation, should be for { ... } in production 66 | for i := 1; i <= 3; i++ { 67 | fmt.Println("connect") 68 | 69 | conn, deliveries, err := consume(url, queue) 70 | if err != nil { 71 | fmt.Println("err consume:", err) 72 | return 73 | } 74 | 75 | // Simulate a producer on a different connection showing that consumers 76 | // continue where they were left off after each reconnect. 77 | if err := pub.Publish("", queue, false, false, amqp.Publishing{ 78 | Body: []byte(fmt.Sprintf("%d", i)), 79 | }); err != nil { 80 | fmt.Println("err publish:", err) 81 | return 82 | } 83 | 84 | // Simulates a consumer that when the range finishes, will setup a new 85 | // session and begin ranging over the deliveries again. 86 | for msg := range deliveries { 87 | fmt.Println(string(msg.Body)) 88 | msg.Ack(false) 89 | 90 | // Simulate an error like a server restart, loss of route or operator 91 | // intervention that results in the connection terminating 92 | go conn.Close() 93 | } 94 | } 95 | } else { 96 | // pass with expected output when not running in an integration 97 | // environment. 98 | fmt.Println("connect") 99 | fmt.Println("1") 100 | fmt.Println("connect") 101 | fmt.Println("2") 102 | fmt.Println("connect") 103 | fmt.Println("3") 104 | } 105 | 106 | // Output: 107 | // connect 108 | // 1 109 | // connect 110 | // 2 111 | // connect 112 | // 3 113 | } 114 | -------------------------------------------------------------------------------- /return.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // Source code and contact info at http://github.com/streadway/amqp 5 | 6 | package amqp 7 | 8 | import ( 9 | "time" 10 | ) 11 | 12 | // Return captures a flattened struct of fields returned by the server when a 13 | // Publishing is unable to be delivered either due to the `mandatory` flag set 14 | // and no route found, or `immediate` flag set and no free consumer. 15 | type Return struct { 16 | ReplyCode uint16 // reason 17 | ReplyText string // description 18 | Exchange string // basic.publish exchange 19 | RoutingKey string // basic.publish routing key 20 | 21 | // Properties 22 | ContentType string // MIME content type 23 | ContentEncoding string // MIME content encoding 24 | Headers Table // Application or header exchange table 25 | DeliveryMode uint8 // queue implementation use - non-persistent (1) or persistent (2) 26 | Priority uint8 // queue implementation use - 0 to 9 27 | CorrelationId string // application use - correlation identifier 28 | ReplyTo string // application use - address to to reply to (ex: RPC) 29 | Expiration string // implementation use - message expiration spec 30 | MessageId string // application use - message identifier 31 | Timestamp time.Time // application use - message timestamp 32 | Type string // application use - message type name 33 | UserId string // application use - creating user id 34 | AppId string // application use - creating application 35 | 36 | Body []byte 37 | } 38 | 39 | func newReturn(msg basicReturn) *Return { 40 | props, body := msg.getContent() 41 | 42 | return &Return{ 43 | ReplyCode: msg.ReplyCode, 44 | ReplyText: msg.ReplyText, 45 | Exchange: msg.Exchange, 46 | RoutingKey: msg.RoutingKey, 47 | 48 | Headers: props.Headers, 49 | ContentType: props.ContentType, 50 | ContentEncoding: props.ContentEncoding, 51 | DeliveryMode: props.DeliveryMode, 52 | Priority: props.Priority, 53 | CorrelationId: props.CorrelationId, 54 | ReplyTo: props.ReplyTo, 55 | Expiration: props.Expiration, 56 | MessageId: props.MessageId, 57 | Timestamp: props.Timestamp, 58 | Type: props.Type, 59 | UserId: props.UserId, 60 | AppId: props.AppId, 61 | 62 | Body: body, 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /shared_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // Source code and contact info at http://github.com/streadway/amqp 5 | 6 | package amqp 7 | 8 | import ( 9 | "encoding/hex" 10 | "io" 11 | "testing" 12 | ) 13 | 14 | type pipe struct { 15 | r *io.PipeReader 16 | w *io.PipeWriter 17 | } 18 | 19 | func (p pipe) Read(b []byte) (int, error) { 20 | return p.r.Read(b) 21 | } 22 | 23 | func (p pipe) Write(b []byte) (int, error) { 24 | return p.w.Write(b) 25 | } 26 | 27 | func (p pipe) Close() error { 28 | p.r.Close() 29 | p.w.Close() 30 | return nil 31 | } 32 | 33 | type logIO struct { 34 | t *testing.T 35 | prefix string 36 | proxy io.ReadWriteCloser 37 | } 38 | 39 | func (log *logIO) Read(p []byte) (n int, err error) { 40 | log.t.Logf("%s reading %d\n", log.prefix, len(p)) 41 | n, err = log.proxy.Read(p) 42 | if err != nil { 43 | log.t.Logf("%s read %x: %v\n", log.prefix, p[0:n], err) 44 | } else { 45 | log.t.Logf("%s read:\n%s\n", log.prefix, hex.Dump(p[0:n])) 46 | //fmt.Printf("%s read:\n%s\n", log.prefix, hex.Dump(p[0:n])) 47 | } 48 | return 49 | } 50 | 51 | func (log *logIO) Write(p []byte) (n int, err error) { 52 | log.t.Logf("%s writing %d\n", log.prefix, len(p)) 53 | n, err = log.proxy.Write(p) 54 | if err != nil { 55 | log.t.Logf("%s write %d, %x: %v\n", log.prefix, len(p), p[0:n], err) 56 | } else { 57 | log.t.Logf("%s write %d:\n%s", log.prefix, len(p), hex.Dump(p[0:n])) 58 | //fmt.Printf("%s write %d:\n%s", log.prefix, len(p), hex.Dump(p[0:n])) 59 | } 60 | return 61 | } 62 | 63 | func (log *logIO) Close() (err error) { 64 | err = log.proxy.Close() 65 | if err != nil { 66 | log.t.Logf("%s close : %v\n", log.prefix, err) 67 | } else { 68 | log.t.Logf("%s close\n", log.prefix) 69 | } 70 | return 71 | } 72 | -------------------------------------------------------------------------------- /spec/amqp0-9-1.stripped.extended.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | 18 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | Errata: Section 1.2 ought to define an exception 312 "No route", which used to 56 | exist in 0-9 and is what RabbitMQ sends back with 'basic.return' when a 57 | 'mandatory' message cannot be delivered to any queue. 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | -------------------------------------------------------------------------------- /spec/gen.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // Source code and contact info at http://github.com/streadway/amqp 5 | 6 | // +build ignore 7 | 8 | package main 9 | 10 | import ( 11 | "bytes" 12 | "encoding/xml" 13 | "errors" 14 | "fmt" 15 | "io/ioutil" 16 | "log" 17 | "os" 18 | "regexp" 19 | "strings" 20 | "text/template" 21 | ) 22 | 23 | var ( 24 | ErrUnknownType = errors.New("Unknown field type in gen") 25 | ErrUnknownDomain = errors.New("Unknown domain type in gen") 26 | ) 27 | 28 | var amqpTypeToNative = map[string]string{ 29 | "bit": "bool", 30 | "octet": "byte", 31 | "shortshort": "uint8", 32 | "short": "uint16", 33 | "long": "uint32", 34 | "longlong": "uint64", 35 | "timestamp": "time.Time", 36 | "table": "Table", 37 | "shortstr": "string", 38 | "longstr": "string", 39 | } 40 | 41 | type Rule struct { 42 | Name string `xml:"name,attr"` 43 | Docs []string `xml:"doc"` 44 | } 45 | 46 | type Doc struct { 47 | Type string `xml:"type,attr"` 48 | Body string `xml:",innerxml"` 49 | } 50 | 51 | type Chassis struct { 52 | Name string `xml:"name,attr"` 53 | Implement string `xml:"implement,attr"` 54 | } 55 | 56 | type Assert struct { 57 | Check string `xml:"check,attr"` 58 | Value string `xml:"value,attr"` 59 | Method string `xml:"method,attr"` 60 | } 61 | 62 | type Field struct { 63 | Name string `xml:"name,attr"` 64 | Domain string `xml:"domain,attr"` 65 | Type string `xml:"type,attr"` 66 | Label string `xml:"label,attr"` 67 | Reserved bool `xml:"reserved,attr"` 68 | Docs []Doc `xml:"doc"` 69 | Asserts []Assert `xml:"assert"` 70 | } 71 | 72 | type Response struct { 73 | Name string `xml:"name,attr"` 74 | } 75 | 76 | type Method struct { 77 | Name string `xml:"name,attr"` 78 | Response Response `xml:"response"` 79 | Synchronous bool `xml:"synchronous,attr"` 80 | Content bool `xml:"content,attr"` 81 | Index string `xml:"index,attr"` 82 | Label string `xml:"label,attr"` 83 | Docs []Doc `xml:"doc"` 84 | Rules []Rule `xml:"rule"` 85 | Fields []Field `xml:"field"` 86 | Chassis []Chassis `xml:"chassis"` 87 | } 88 | 89 | type Class struct { 90 | Name string `xml:"name,attr"` 91 | Handler string `xml:"handler,attr"` 92 | Index string `xml:"index,attr"` 93 | Label string `xml:"label,attr"` 94 | Docs []Doc `xml:"doc"` 95 | Methods []Method `xml:"method"` 96 | Chassis []Chassis `xml:"chassis"` 97 | } 98 | 99 | type Domain struct { 100 | Name string `xml:"name,attr"` 101 | Type string `xml:"type,attr"` 102 | Label string `xml:"label,attr"` 103 | Rules []Rule `xml:"rule"` 104 | Docs []Doc `xml:"doc"` 105 | } 106 | 107 | type Constant struct { 108 | Name string `xml:"name,attr"` 109 | Value int `xml:"value,attr"` 110 | Class string `xml:"class,attr"` 111 | Doc string `xml:"doc"` 112 | } 113 | 114 | type Amqp struct { 115 | Major int `xml:"major,attr"` 116 | Minor int `xml:"minor,attr"` 117 | Port int `xml:"port,attr"` 118 | Comment string `xml:"comment,attr"` 119 | 120 | Constants []Constant `xml:"constant"` 121 | Domains []Domain `xml:"domain"` 122 | Classes []Class `xml:"class"` 123 | } 124 | 125 | type renderer struct { 126 | Root Amqp 127 | bitcounter int 128 | } 129 | 130 | type fieldset struct { 131 | AmqpType string 132 | NativeType string 133 | Fields []Field 134 | *renderer 135 | } 136 | 137 | var ( 138 | helpers = template.FuncMap{ 139 | "public": public, 140 | "private": private, 141 | "clean": clean, 142 | } 143 | 144 | packageTemplate = template.Must(template.New("package").Funcs(helpers).Parse(` 145 | // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. 146 | // Use of this source code is governed by a BSD-style 147 | // license that can be found in the LICENSE file. 148 | // Source code and contact info at http://github.com/streadway/amqp 149 | 150 | /* GENERATED FILE - DO NOT EDIT */ 151 | /* Rebuild from the spec/gen.go tool */ 152 | 153 | {{with .Root}} 154 | package amqp 155 | 156 | import ( 157 | "fmt" 158 | "encoding/binary" 159 | "io" 160 | ) 161 | 162 | // Error codes that can be sent from the server during a connection or 163 | // channel exception or used by the client to indicate a class of error like 164 | // ErrCredentials. The text of the error is likely more interesting than 165 | // these constants. 166 | const ( 167 | {{range $c := .Constants}} 168 | {{if $c.IsError}}{{.Name | public}}{{else}}{{.Name | private}}{{end}} = {{.Value}}{{end}} 169 | ) 170 | 171 | func isSoftExceptionCode(code int) bool { 172 | switch code { 173 | {{range $c := .Constants}} {{if $c.IsSoftError}} case {{$c.Value}}: 174 | return true 175 | {{end}}{{end}} 176 | } 177 | return false 178 | } 179 | 180 | {{range .Classes}} 181 | {{$class := .}} 182 | {{range .Methods}} 183 | {{$method := .}} 184 | {{$struct := $.StructName $class.Name $method.Name}} 185 | {{if .Docs}}/* {{range .Docs}} {{.Body | clean}} {{end}} */{{end}} 186 | type {{$struct}} struct { 187 | {{range .Fields}} 188 | {{$.FieldName .}} {{$.FieldType . | $.NativeType}} {{if .Label}}// {{.Label}}{{end}}{{end}} 189 | {{if .Content}}Properties properties 190 | Body []byte{{end}} 191 | } 192 | 193 | func (msg *{{$struct}}) id() (uint16, uint16) { 194 | return {{$class.Index}}, {{$method.Index}} 195 | } 196 | 197 | func (msg *{{$struct}}) wait() (bool) { 198 | return {{.Synchronous}}{{if $.HasField "NoWait" .}} && !msg.NoWait{{end}} 199 | } 200 | 201 | {{if .Content}} 202 | func (msg *{{$struct}}) getContent() (properties, []byte) { 203 | return msg.Properties, msg.Body 204 | } 205 | 206 | func (msg *{{$struct}}) setContent(props properties, body []byte) { 207 | msg.Properties, msg.Body = props, body 208 | } 209 | {{end}} 210 | func (msg *{{$struct}}) write(w io.Writer) (err error) { 211 | {{if $.HasType "bit" $method}}var bits byte{{end}} 212 | {{.Fields | $.Fieldsets | $.Partial "enc-"}} 213 | return 214 | } 215 | 216 | func (msg *{{$struct}}) read(r io.Reader) (err error) { 217 | {{if $.HasType "bit" $method}}var bits byte{{end}} 218 | {{.Fields | $.Fieldsets | $.Partial "dec-"}} 219 | return 220 | } 221 | {{end}} 222 | {{end}} 223 | 224 | func (r *reader) parseMethodFrame(channel uint16, size uint32) (f frame, err error) { 225 | mf := &methodFrame { 226 | ChannelId: channel, 227 | } 228 | 229 | if err = binary.Read(r.r, binary.BigEndian, &mf.ClassId); err != nil { 230 | return 231 | } 232 | 233 | if err = binary.Read(r.r, binary.BigEndian, &mf.MethodId); err != nil { 234 | return 235 | } 236 | 237 | switch mf.ClassId { 238 | {{range .Classes}} 239 | {{$class := .}} 240 | case {{.Index}}: // {{.Name}} 241 | switch mf.MethodId { 242 | {{range .Methods}} 243 | case {{.Index}}: // {{$class.Name}} {{.Name}} 244 | //fmt.Println("NextMethod: class:{{$class.Index}} method:{{.Index}}") 245 | method := &{{$.StructName $class.Name .Name}}{} 246 | if err = method.read(r.r); err != nil { 247 | return 248 | } 249 | mf.Method = method 250 | {{end}} 251 | default: 252 | return nil, fmt.Errorf("Bad method frame, unknown method %d for class %d", mf.MethodId, mf.ClassId) 253 | } 254 | {{end}} 255 | default: 256 | return nil, fmt.Errorf("Bad method frame, unknown class %d", mf.ClassId) 257 | } 258 | 259 | return mf, nil 260 | } 261 | {{end}} 262 | 263 | {{define "enc-bit"}} 264 | {{range $off, $field := .Fields}} 265 | if msg.{{$field | $.FieldName}} { bits |= 1 << {{$off}} } 266 | {{end}} 267 | if err = binary.Write(w, binary.BigEndian, bits); err != nil { return } 268 | {{end}} 269 | {{define "enc-octet"}} 270 | {{range .Fields}} if err = binary.Write(w, binary.BigEndian, msg.{{. | $.FieldName}}); err != nil { return } 271 | {{end}} 272 | {{end}} 273 | {{define "enc-shortshort"}} 274 | {{range .Fields}} if err = binary.Write(w, binary.BigEndian, msg.{{. | $.FieldName}}); err != nil { return } 275 | {{end}} 276 | {{end}} 277 | {{define "enc-short"}} 278 | {{range .Fields}} if err = binary.Write(w, binary.BigEndian, msg.{{. | $.FieldName}}); err != nil { return } 279 | {{end}} 280 | {{end}} 281 | {{define "enc-long"}} 282 | {{range .Fields}} if err = binary.Write(w, binary.BigEndian, msg.{{. | $.FieldName}}); err != nil { return } 283 | {{end}} 284 | {{end}} 285 | {{define "enc-longlong"}} 286 | {{range .Fields}} if err = binary.Write(w, binary.BigEndian, msg.{{. | $.FieldName}}); err != nil { return } 287 | {{end}} 288 | {{end}} 289 | {{define "enc-timestamp"}} 290 | {{range .Fields}} if err = writeTimestamp(w, msg.{{. | $.FieldName}}); err != nil { return } 291 | {{end}} 292 | {{end}} 293 | {{define "enc-shortstr"}} 294 | {{range .Fields}} if err = writeShortstr(w, msg.{{. | $.FieldName}}); err != nil { return } 295 | {{end}} 296 | {{end}} 297 | {{define "enc-longstr"}} 298 | {{range .Fields}} if err = writeLongstr(w, msg.{{. | $.FieldName}}); err != nil { return } 299 | {{end}} 300 | {{end}} 301 | {{define "enc-table"}} 302 | {{range .Fields}} if err = writeTable(w, msg.{{. | $.FieldName}}); err != nil { return } 303 | {{end}} 304 | {{end}} 305 | 306 | {{define "dec-bit"}} 307 | if err = binary.Read(r, binary.BigEndian, &bits); err != nil { 308 | return 309 | } 310 | {{range $off, $field := .Fields}} msg.{{$field | $.FieldName}} = (bits & (1 << {{$off}}) > 0) 311 | {{end}} 312 | {{end}} 313 | {{define "dec-octet"}} 314 | {{range .Fields}} if err = binary.Read(r, binary.BigEndian, &msg.{{. | $.FieldName}}); err != nil { return } 315 | {{end}} 316 | {{end}} 317 | {{define "dec-shortshort"}} 318 | {{range .Fields}} if err = binary.Read(r, binary.BigEndian, &msg.{{. | $.FieldName}}); err != nil { return } 319 | {{end}} 320 | {{end}} 321 | {{define "dec-short"}} 322 | {{range .Fields}} if err = binary.Read(r, binary.BigEndian, &msg.{{. | $.FieldName}}); err != nil { return } 323 | {{end}} 324 | {{end}} 325 | {{define "dec-long"}} 326 | {{range .Fields}} if err = binary.Read(r, binary.BigEndian, &msg.{{. | $.FieldName}}); err != nil { return } 327 | {{end}} 328 | {{end}} 329 | {{define "dec-longlong"}} 330 | {{range .Fields}} if err = binary.Read(r, binary.BigEndian, &msg.{{. | $.FieldName}}); err != nil { return } 331 | {{end}} 332 | {{end}} 333 | {{define "dec-timestamp"}} 334 | {{range .Fields}} if msg.{{. | $.FieldName}}, err = readTimestamp(r); err != nil { return } 335 | {{end}} 336 | {{end}} 337 | {{define "dec-shortstr"}} 338 | {{range .Fields}} if msg.{{. | $.FieldName}}, err = readShortstr(r); err != nil { return } 339 | {{end}} 340 | {{end}} 341 | {{define "dec-longstr"}} 342 | {{range .Fields}} if msg.{{. | $.FieldName}}, err = readLongstr(r); err != nil { return } 343 | {{end}} 344 | {{end}} 345 | {{define "dec-table"}} 346 | {{range .Fields}} if msg.{{. | $.FieldName}}, err = readTable(r); err != nil { return } 347 | {{end}} 348 | {{end}} 349 | 350 | `)) 351 | ) 352 | 353 | func (c *Constant) IsError() bool { 354 | return strings.Contains(c.Class, "error") 355 | } 356 | 357 | func (c *Constant) IsSoftError() bool { 358 | return c.Class == "soft-error" 359 | } 360 | 361 | func (renderer *renderer) Partial(prefix string, fields []fieldset) (s string, err error) { 362 | var buf bytes.Buffer 363 | for _, set := range fields { 364 | name := prefix + set.AmqpType 365 | t := packageTemplate.Lookup(name) 366 | if t == nil { 367 | return "", errors.New(fmt.Sprintf("Missing template: %s", name)) 368 | } 369 | if err = t.Execute(&buf, set); err != nil { 370 | return 371 | } 372 | } 373 | return string(buf.Bytes()), nil 374 | } 375 | 376 | // Groups the fields so that the right encoder/decoder can be called 377 | func (renderer *renderer) Fieldsets(fields []Field) (f []fieldset, err error) { 378 | if len(fields) > 0 { 379 | for _, field := range fields { 380 | cur := fieldset{} 381 | cur.AmqpType, err = renderer.FieldType(field) 382 | if err != nil { 383 | return 384 | } 385 | 386 | cur.NativeType, err = renderer.NativeType(cur.AmqpType) 387 | if err != nil { 388 | return 389 | } 390 | cur.Fields = append(cur.Fields, field) 391 | f = append(f, cur) 392 | } 393 | 394 | i, j := 0, 1 395 | for j < len(f) { 396 | if f[i].AmqpType == f[j].AmqpType { 397 | f[i].Fields = append(f[i].Fields, f[j].Fields...) 398 | } else { 399 | i++ 400 | f[i] = f[j] 401 | } 402 | j++ 403 | } 404 | return f[:i+1], nil 405 | } 406 | 407 | return 408 | } 409 | 410 | func (renderer *renderer) HasType(typ string, method Method) bool { 411 | for _, f := range method.Fields { 412 | name, _ := renderer.FieldType(f) 413 | if name == typ { 414 | return true 415 | } 416 | } 417 | return false 418 | } 419 | 420 | func (renderer *renderer) HasField(field string, method Method) bool { 421 | for _, f := range method.Fields { 422 | name := renderer.FieldName(f) 423 | if name == field { 424 | return true 425 | } 426 | } 427 | return false 428 | } 429 | 430 | func (renderer *renderer) Domain(field Field) (domain Domain, err error) { 431 | for _, domain = range renderer.Root.Domains { 432 | if field.Domain == domain.Name { 433 | return 434 | } 435 | } 436 | return domain, nil 437 | //return domain, ErrUnknownDomain 438 | } 439 | 440 | func (renderer *renderer) FieldName(field Field) (t string) { 441 | t = public(field.Name) 442 | 443 | if field.Reserved { 444 | t = strings.ToLower(t) 445 | } 446 | 447 | return 448 | } 449 | 450 | func (renderer *renderer) FieldType(field Field) (t string, err error) { 451 | t = field.Type 452 | 453 | if t == "" { 454 | var domain Domain 455 | domain, err = renderer.Domain(field) 456 | if err != nil { 457 | return "", err 458 | } 459 | t = domain.Type 460 | } 461 | 462 | return 463 | } 464 | 465 | func (renderer *renderer) NativeType(amqpType string) (t string, err error) { 466 | if t, ok := amqpTypeToNative[amqpType]; ok { 467 | return t, nil 468 | } 469 | return "", ErrUnknownType 470 | } 471 | 472 | func (renderer *renderer) Tag(d Domain) string { 473 | label := "`" 474 | 475 | label += `domain:"` + d.Name + `"` 476 | 477 | if len(d.Type) > 0 { 478 | label += `,type:"` + d.Type + `"` 479 | } 480 | 481 | label += "`" 482 | 483 | return label 484 | } 485 | 486 | func (renderer *renderer) StructName(parts ...string) string { 487 | return parts[0] + public(parts[1:]...) 488 | } 489 | 490 | func clean(body string) (res string) { 491 | return strings.Replace(body, "\r", "", -1) 492 | } 493 | 494 | func private(parts ...string) string { 495 | return export(regexp.MustCompile(`[-_]\w`), parts...) 496 | } 497 | 498 | func public(parts ...string) string { 499 | return export(regexp.MustCompile(`^\w|[-_]\w`), parts...) 500 | } 501 | 502 | func export(delim *regexp.Regexp, parts ...string) (res string) { 503 | for _, in := range parts { 504 | 505 | res += delim.ReplaceAllStringFunc(in, func(match string) string { 506 | switch len(match) { 507 | case 1: 508 | return strings.ToUpper(match) 509 | case 2: 510 | return strings.ToUpper(match[1:]) 511 | } 512 | panic("unreachable") 513 | }) 514 | } 515 | 516 | return 517 | } 518 | 519 | func main() { 520 | var r renderer 521 | 522 | spec, err := ioutil.ReadAll(os.Stdin) 523 | if err != nil { 524 | log.Fatalln("Please pass spec on stdin", err) 525 | } 526 | 527 | err = xml.Unmarshal(spec, &r.Root) 528 | 529 | if err != nil { 530 | log.Fatalln("Could not parse XML:", err) 531 | } 532 | 533 | if err = packageTemplate.Execute(os.Stdout, &r); err != nil { 534 | log.Fatalln("Generate error: ", err) 535 | } 536 | } 537 | -------------------------------------------------------------------------------- /tls_test.go: -------------------------------------------------------------------------------- 1 | package amqp 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "fmt" 7 | "net" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func tlsServerConfig() *tls.Config { 13 | cfg := new(tls.Config) 14 | 15 | cfg.ClientCAs = x509.NewCertPool() 16 | cfg.ClientCAs.AppendCertsFromPEM([]byte(caCert)) 17 | 18 | cert, err := tls.X509KeyPair([]byte(serverCert), []byte(serverKey)) 19 | if err != nil { 20 | panic(err) 21 | } 22 | 23 | cfg.Certificates = append(cfg.Certificates, cert) 24 | cfg.ClientAuth = tls.RequireAndVerifyClientCert 25 | 26 | return cfg 27 | } 28 | 29 | func tlsClientConfig() *tls.Config { 30 | cfg := new(tls.Config) 31 | cfg.RootCAs = x509.NewCertPool() 32 | cfg.RootCAs.AppendCertsFromPEM([]byte(caCert)) 33 | 34 | cert, err := tls.X509KeyPair([]byte(clientCert), []byte(clientKey)) 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | cfg.Certificates = append(cfg.Certificates, cert) 40 | 41 | return cfg 42 | } 43 | 44 | type tlsServer struct { 45 | net.Listener 46 | URL string 47 | Config *tls.Config 48 | Sessions chan *server 49 | } 50 | 51 | // Captures the header for each accepted connection 52 | func (s *tlsServer) Serve(t *testing.T) { 53 | for { 54 | c, err := s.Accept() 55 | if err != nil { 56 | return 57 | } 58 | s.Sessions <- newServer(t, c, c) 59 | } 60 | } 61 | 62 | func startTLSServer(t *testing.T, cfg *tls.Config) tlsServer { 63 | l, err := tls.Listen("tcp", "127.0.0.1:0", cfg) 64 | if err != nil { 65 | panic(err) 66 | } 67 | 68 | s := tlsServer{ 69 | Listener: l, 70 | Config: cfg, 71 | URL: fmt.Sprintf("amqps://%s/", l.Addr().String()), 72 | Sessions: make(chan *server), 73 | } 74 | go s.Serve(t) 75 | 76 | return s 77 | } 78 | 79 | // Tests opening a connection of a TLS enabled socket server 80 | func TestTLSHandshake(t *testing.T) { 81 | srv := startTLSServer(t, tlsServerConfig()) 82 | defer srv.Close() 83 | go func() { 84 | select { 85 | case <-time.After(10 * time.Millisecond): 86 | t.Fatalf("server timeout waiting for TLS handshake from client") 87 | case session := <-srv.Sessions: 88 | session.connectionOpen() 89 | session.connectionClose() 90 | session.S.Close() 91 | } 92 | }() 93 | 94 | c, err := DialTLS(srv.URL, tlsClientConfig()) 95 | if err != nil { 96 | t.Fatalf("expected to open a TLS connection, got err: %v", err) 97 | } 98 | defer c.Close() 99 | 100 | if st := c.ConnectionState(); !st.HandshakeComplete { 101 | t.Errorf("expected to complete a TLS handshake, TLS connection state: %+v", st) 102 | } 103 | } 104 | 105 | const caCert = ` 106 | -----BEGIN CERTIFICATE----- 107 | MIICxjCCAa6gAwIBAgIJANWuMWMQSxvdMA0GCSqGSIb3DQEBBQUAMBMxETAPBgNV 108 | BAMTCE15VGVzdENBMB4XDTE0MDEyNzE5NTIyMloXDTI0MDEyNTE5NTIyMlowEzER 109 | MA8GA1UEAxMITXlUZXN0Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB 110 | AQDBsIrkW4ob9Z/gzR2/Maa2stbutry6/vvz8eiJwIKIbaHGwqtFOUGiWeKw7H76 111 | IH3SjTAhNQY2hoKPyH41D36sDJkYBRyHFJTK/6ffvOhpyLnuXJAnoS62eKPSNUAx 112 | 5i/lkHj42ESutYAH9qbHCI/gBm9G4WmhGAyA16xzC1n07JObl6KFoY1PqHKl823z 113 | mvF47I24DzemEfjdwC9nAAX/pGYOg9FA9nQv7NnhlsJMxueCx55RNU1ADRoqsbfE 114 | T0CQTOT4ryugGrUp9J4Cwen6YbXZrS6+Kff5SQCAns0Qu8/bwj0DKkuBGLF+Mnwe 115 | mq9bMzyZPUrPM3Gu48ao8YAfAgMBAAGjHTAbMAwGA1UdEwQFMAMBAf8wCwYDVR0P 116 | BAQDAgEGMA0GCSqGSIb3DQEBBQUAA4IBAQCBwXGblRxIEOlEP6ANZ1C8AHWyG8lR 117 | CQduFclc0tmyCCz5fnyLK0aGu9LhXXe6/HSKqgs4mJqeqYOojdjkfOme/YdwDzjK 118 | WIf0kRYQHcB6NeyEZwW8C7subTP1Xw6zbAmjvQrtCGvRM+fi3/cs1sSSkd/EoRk4 119 | 7GM9qQl/JIIoCOGncninf2NQm5YSpbit6/mOQD7EhqXsw+bX+IRh3DHC1Apv/PoA 120 | HlDNeM4vjWaBxsmvRSndrIvew1czboFM18oRSSIqAkU7dKZ0SbC11grzmNxMG2aD 121 | f9y8FIG6RK/SEaOZuc+uBGXx7tj7dczpE/2puqYcaVGwcv4kkrC/ZuRm 122 | -----END CERTIFICATE----- 123 | ` 124 | 125 | const serverCert = ` 126 | -----BEGIN CERTIFICATE----- 127 | MIIC8zCCAdugAwIBAgIBATANBgkqhkiG9w0BAQUFADATMREwDwYDVQQDEwhNeVRl 128 | c3RDQTAeFw0xNDAxMjcxOTUyMjNaFw0yNDAxMjUxOTUyMjNaMCUxEjAQBgNVBAMT 129 | CTEyNy4wLjAuMTEPMA0GA1UEChMGc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOC 130 | AQ8AMIIBCgKCAQEAxYAKbeGyg0gP0xwVsZsufzk/SUCtD44Gp3lQYQ9QumQ1IVZu 131 | PmZWwPWrzI93a1Abruz6ZhXaB3jcL5QPAy1N44IiFgVN45CZXBsqkpJe/abzRFOV 132 | DRnHxattPDHdgwML5d3nURKGUM/7+ACj5E4pZEDlM3RIjIKVd+doJsL7n6myO8FE 133 | tIpt4vTz1MFp3F+ntPnHU3BZ/VZ1UjSlFWnCjT0CR0tnXsPmlIaC98HThS8x5zNB 134 | fvvSN+Zln8RWdNLnEVHVdqYtOQ828QbCx8s1HfClGgaVoSDrzz+qQgtZFO4wW264 135 | 2CWkNd8DSJUJ/HlPNXmbXsrRMgvGaL7YUz2yRQIDAQABo0AwPjAJBgNVHRMEAjAA 136 | MAsGA1UdDwQEAwIFIDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHREECDAGhwR/ 137 | AAABMA0GCSqGSIb3DQEBBQUAA4IBAQAE2g+wAFf9Xg5svcnb7+mfseYV16k9l5WG 138 | onrmR3FLsbTxfbr4PZJMHrswPbi2NRk0+ETPUpcv1RP7pUB7wSEvuS1NPGcU92iP 139 | 58ycP3dYtLzmuu6BkgToZqwsCU8fC2zM0wt3+ifzPpDMffWWOioVuA3zdM9WPQYz 140 | +Ofajd0XaZwFZS8uTI5WXgObz7Xqfmln4tF3Sq1CTyuJ44qK4p83XOKFq+L04aD0 141 | d0c8w3YQNUENny/vMP9mDu3FQ3SnDz2GKl1LSjGe2TUnkoMkDfdk4wSzndTz/ecb 142 | QiCPKijwVPWNOWV3NDE2edMxDPxDoKoEm5F4UGfGjxSRnYCIoZLh 143 | -----END CERTIFICATE----- 144 | ` 145 | 146 | const serverKey = ` 147 | -----BEGIN RSA PRIVATE KEY----- 148 | MIIEowIBAAKCAQEAxYAKbeGyg0gP0xwVsZsufzk/SUCtD44Gp3lQYQ9QumQ1IVZu 149 | PmZWwPWrzI93a1Abruz6ZhXaB3jcL5QPAy1N44IiFgVN45CZXBsqkpJe/abzRFOV 150 | DRnHxattPDHdgwML5d3nURKGUM/7+ACj5E4pZEDlM3RIjIKVd+doJsL7n6myO8FE 151 | tIpt4vTz1MFp3F+ntPnHU3BZ/VZ1UjSlFWnCjT0CR0tnXsPmlIaC98HThS8x5zNB 152 | fvvSN+Zln8RWdNLnEVHVdqYtOQ828QbCx8s1HfClGgaVoSDrzz+qQgtZFO4wW264 153 | 2CWkNd8DSJUJ/HlPNXmbXsrRMgvGaL7YUz2yRQIDAQABAoIBAGsyEvcPAGg3DbfE 154 | z5WFp9gPx2TIAOanbL8rnlAAEw4H47qDgfTGcSHsdeHioKuTYGMyZrpP8/YISGJe 155 | l0NfLJ5mfH+9Q0hXrJWMfS/u2DYOjo0wXH8u1fpZEEISwqsgVS3fonSjfFmSea1j 156 | E5GQRvEONBkYbWQuYFgjNqmLPS2r5lKbWCQvc1MB/vvVBwOTiO0ON7m/EkM5RKt9 157 | cDT5ZhhVjBpdmd9HpVbKTdBj8Q0l5/ZHZUEgZA6FDZEwYxTd9l87Z4YT+5SR0z9t 158 | k8/Z0CHd3x3Rv891t7m66ZJkaOda8NC65/432MQEQwJltmrKnc22dS8yI26rrmpp 159 | g3tcbSUCgYEA5nMXdQKS4vF+Kp10l/HqvGz2sU8qQaWYZQIg7Th3QJPo6N52po/s 160 | nn3UF0P5mT1laeZ5ZQJKx4gnmuPnIZ2ZtJQDyFhIbRPcZ+2hSNSuLYVcrumOC3EP 161 | 3OZyFtFE1THO73aFe5e1jEdtoOne3Bds/Hq6NF45fkVdL+M9e8pfXIsCgYEA22W8 162 | zGjbWyrFOYvKknMQVtHnMx8BJEtsvWRknP6CWAv/8WyeZpE128Pve1m441AQnopS 163 | CuOF5wFK0iUXBFbS3Pe1/1j3em6yfVznuUHqJ7Qc+dNzxVvkTK8jGB6x+vm+M9Hg 164 | muHUM726IUxckoSNXbPNAVPIZab1NdSxam7F9m8CgYEAx55QZmIJXJ41XLKxqWC7 165 | peZ5NpPNlbncrTpPzUzJN94ntXfmrVckbxGt401VayEctMQYyZ9XqUlOjUP3FU5Q 166 | M3S3Zhba/eljVX8o406fZf0MkNLs4QpZ5E6V6x/xEP+pMhKng6yhbVb+JpIPIvUD 167 | yhyBKRWplbB+DRo5Sv685gsCgYA7l5m9h+m1DJv/cnn2Z2yTuHXtC8namuYRV1iA 168 | 0ByFX9UINXGc+GpBpCnDPm6ax5+MAJQiQwSW52H0TIDA+/hQbrQvhHHL/o9av8Zt 169 | Kns4h5KrRQUYIUqUjamhnozHV9iS6LnyN87Usv8AlmY6oehoADN53dD702qdUYVT 170 | HH2G3wKBgCdvqyw78FR/n8cUWesTPnxx5HCeWJ1J+2BESnUnPmKZ71CV1H7uweja 171 | vPUxuuuGLKfNx84OKCfRDbtOgMOeyh9T1RmXry6Srz/7/udjlF0qmFiRXfBNAgoR 172 | tNb0+Ri/vY0AHrQ7UnCbl12qPVaqhEXLr+kCGNEPFqpMJPPEeMK0 173 | -----END RSA PRIVATE KEY----- 174 | ` 175 | 176 | const clientCert = ` 177 | -----BEGIN CERTIFICATE----- 178 | MIIC4jCCAcqgAwIBAgIBAjANBgkqhkiG9w0BAQUFADATMREwDwYDVQQDEwhNeVRl 179 | c3RDQTAeFw0xNDAxMjcxOTUyMjNaFw0yNDAxMjUxOTUyMjNaMCUxEjAQBgNVBAMT 180 | CTEyNy4wLjAuMTEPMA0GA1UEChMGY2xpZW50MIIBIjANBgkqhkiG9w0BAQEFAAOC 181 | AQ8AMIIBCgKCAQEAu7LMqd+agoH168Bsi0WJ36ulYqDypq+GZPF7uWOo2pE0raKH 182 | B++31/hjnkt6yC5kLKVZZ0EfolBa9q4Cy6swfGaEMafy44ZCRneLnt1azL1N6Kfz 183 | +U0KsOqyQDoMxYJG1gVTEZN19/U/ew2eazcxKyERI3oGCQ4SbpkxBTbfxtAFk49e 184 | xIB3obsuMVUrmtXE4FkUkvG7NgpPUgrhp0yxYpj9zruZGzGGT1zNhcarbQ/4i7It 185 | ZMbnv6pqQWtYDgnGX2TDRcEiXGeO+KrzhfpTRLfO3K4np8e8cmTyXM+4lMlWUgma 186 | KrRdu1QXozGqRs47u2prGKGdSQWITpqNVCY8fQIDAQABoy8wLTAJBgNVHRMEAjAA 187 | MAsGA1UdDwQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAjANBgkqhkiG9w0BAQUF 188 | AAOCAQEAhCuBCLznPc4O96hT3P8Fx19L3ltrWbc/pWrx8JjxUaGk8kNmjMjY+/Mt 189 | JBbjUBx2kJwaY0EHMAfw7D1f1wcCeNycx/0dyb0E6xzhmPw5fY15GGNg8rzWwqSY 190 | +i/1iqU0IRkmRHV7XCF+trd2H0Ec+V1Fd/61E2ccJfOL5aSAyWbMCUtWxS3QMnqH 191 | FBfKdVEiY9WNht5hnvsXQBRaNhowJ6Cwa7/1/LZjmhcXiJ0xrc1Hggj3cvS+4vll 192 | Ew+20a0tPKjD/v/2oSQL+qkeYKV4fhCGkaBHCpPlSJrqorb7B6NmPy3nS26ETKE/ 193 | o2UCfZc5g2MU1ENa31kT1iuhKZapsA== 194 | -----END CERTIFICATE----- 195 | ` 196 | 197 | const clientKey = ` 198 | -----BEGIN RSA PRIVATE KEY----- 199 | MIIEowIBAAKCAQEAu7LMqd+agoH168Bsi0WJ36ulYqDypq+GZPF7uWOo2pE0raKH 200 | B++31/hjnkt6yC5kLKVZZ0EfolBa9q4Cy6swfGaEMafy44ZCRneLnt1azL1N6Kfz 201 | +U0KsOqyQDoMxYJG1gVTEZN19/U/ew2eazcxKyERI3oGCQ4SbpkxBTbfxtAFk49e 202 | xIB3obsuMVUrmtXE4FkUkvG7NgpPUgrhp0yxYpj9zruZGzGGT1zNhcarbQ/4i7It 203 | ZMbnv6pqQWtYDgnGX2TDRcEiXGeO+KrzhfpTRLfO3K4np8e8cmTyXM+4lMlWUgma 204 | KrRdu1QXozGqRs47u2prGKGdSQWITpqNVCY8fQIDAQABAoIBAGSEn3hFyEAmCyYi 205 | 2b5IEksXaC2GlgxQKb/7Vs/0oCPU6YonZPsKFMFzQx4tu+ZiecEzF8rlJGTPdbdv 206 | fw3FcuTcHeVd1QSmDO4h7UK5tnu40XVMJKsY6CXQun8M13QajYbmORNLjjypOULU 207 | C0fNueYoAj6mhX7p61MRdSAev/5+0+bVQQG/tSVDQzdngvKpaCunOphiB2VW2Aa0 208 | 7aYPOFCoPB2uo0DwUmBB0yfx9x4hXX9ovQI0YFou7bq6iYJ0vlZBvYQ9YrVdxjKL 209 | avcz1N5xM3WFAkZJSVT/Ho5+uTbZx4RrJ8b5T+t2spOKmXyAjwS2rL/XMAh8YRZ1 210 | u44duoECgYEA4jpK2qshgQ0t49rjVHEDKX5x7ElEZefl0rHZ/2X/uHUDKpKj2fTq 211 | 3TQzHquiQ4Aof7OEB9UE3DGrtpvo/j/PYxL5Luu5VR4AIEJm+CA8GYuE96+uIL0Z 212 | M2r3Lux6Bp30Z47Eit2KiY4fhrWs59WB3NHHoFxgzHSVbnuA02gcX2ECgYEA1GZw 213 | iXIVYaK07ED+q/0ObyS5hD1cMhJ7ifSN9BxuG0qUpSigbkTGj09fUDS4Fqsz9dvz 214 | F0P93fZvyia242TIfDUwJEsDQCgHk7SGa4Rx/p/3x/obIEERk7K76Hdg93U5NXhV 215 | NvczvgL0HYxnb+qtumwMgGPzncB4lGcTnRyOfp0CgYBTIsDnYwRI/KLknUf1fCKB 216 | WSpcfwBXwsS+jQVjygQTsUyclI8KResZp1kx6DkVPT+kzj+y8SF8GfTUgq844BJC 217 | gnJ4P8A3+3JoaH6WqKHtcUxICZOgDF36e1CjOdwOGnX6qIipz4hdzJDhXFpSSDAV 218 | CjKmR8x61k0j8NcC2buzgQKBgFr7eo9VwBTvpoJhIPY5UvqHB7S+uAR26FZi3H/J 219 | wdyM6PmKWpaBfXCb9l8cBhMnyP0y94FqzY9L5fz48nSbkkmqWvHg9AaCXySFOuNJ 220 | e68vhOszlnUNimLzOAzPPkkh/JyL7Cy8XXyyNTGHGDPXmg12BTDmH8/eR4iCUuOE 221 | /QD9AoGBALQ/SkvfO3D5+k9e/aTHRuMJ0+PWdLUMTZ39oJQxUx+qj7/xpjDvWTBn 222 | eDmF/wjnIAg+020oXyBYo6plEZfDz3EYJQZ+3kLLEU+O/A7VxCakPYPwCr7N/InL 223 | Ccg/TVSIXxw/6uJnojoAjMIEU45NoP6RMp0mWYYb2OlteEv08Ovp 224 | -----END RSA PRIVATE KEY----- 225 | ` 226 | -------------------------------------------------------------------------------- /types.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // Source code and contact info at http://github.com/streadway/amqp 5 | 6 | package amqp 7 | 8 | import ( 9 | "fmt" 10 | "io" 11 | "time" 12 | ) 13 | 14 | // Constants for standard AMQP 0-9-1 exchange types. 15 | const ( 16 | ExchangeDirect = "direct" 17 | ExchangeFanout = "fanout" 18 | ExchangeTopic = "topic" 19 | ExchangeHeaders = "headers" 20 | ) 21 | 22 | var ( 23 | // ErrClosed is returned when the channel or connection is not open 24 | ErrClosed = &Error{Code: ChannelError, Reason: "channel/connection is not open"} 25 | 26 | // ErrChannelMax is returned when Connection.Channel has been called enough 27 | // times that all channel IDs have been exhausted in the client or the 28 | // server. 29 | ErrChannelMax = &Error{Code: ChannelError, Reason: "channel id space exhausted"} 30 | 31 | // ErrSASL is returned from Dial when the authentication mechanism could not 32 | // be negoated. 33 | ErrSASL = &Error{Code: AccessRefused, Reason: "SASL could not negotiate a shared mechanism"} 34 | 35 | // ErrCredentials is returned when the authenticated client is not authorized 36 | // to any vhost. 37 | ErrCredentials = &Error{Code: AccessRefused, Reason: "username or password not allowed"} 38 | 39 | // ErrVhost is returned when the authenticated user is not permitted to 40 | // access the requested Vhost. 41 | ErrVhost = &Error{Code: AccessRefused, Reason: "no access to this vhost"} 42 | 43 | // ErrSyntax is hard protocol error, indicating an unsupported protocol, 44 | // implementation or encoding. 45 | ErrSyntax = &Error{Code: SyntaxError, Reason: "invalid field or value inside of a frame"} 46 | 47 | // ErrFrame is returned when the protocol frame cannot be read from the 48 | // server, indicating an unsupported protocol or unsupported frame type. 49 | ErrFrame = &Error{Code: FrameError, Reason: "frame could not be parsed"} 50 | 51 | // ErrCommandInvalid is returned when the server sends an unexpected response 52 | // to this requested message type. This indicates a bug in this client. 53 | ErrCommandInvalid = &Error{Code: CommandInvalid, Reason: "unexpected command received"} 54 | 55 | // ErrUnexpectedFrame is returned when something other than a method or 56 | // heartbeat frame is delivered to the Connection, indicating a bug in the 57 | // client. 58 | ErrUnexpectedFrame = &Error{Code: UnexpectedFrame, Reason: "unexpected frame received"} 59 | 60 | // ErrFieldType is returned when writing a message containing a Go type unsupported by AMQP. 61 | ErrFieldType = &Error{Code: SyntaxError, Reason: "unsupported table field type"} 62 | ) 63 | 64 | // Error captures the code and reason a channel or connection has been closed 65 | // by the server. 66 | type Error struct { 67 | Code int // constant code from the specification 68 | Reason string // description of the error 69 | Server bool // true when initiated from the server, false when from this library 70 | Recover bool // true when this error can be recovered by retrying later or with different parameters 71 | } 72 | 73 | func newError(code uint16, text string) *Error { 74 | return &Error{ 75 | Code: int(code), 76 | Reason: text, 77 | Recover: isSoftExceptionCode(int(code)), 78 | Server: true, 79 | } 80 | } 81 | 82 | func (e Error) Error() string { 83 | return fmt.Sprintf("Exception (%d) Reason: %q", e.Code, e.Reason) 84 | } 85 | 86 | // Used by header frames to capture routing and header information 87 | type properties struct { 88 | ContentType string // MIME content type 89 | ContentEncoding string // MIME content encoding 90 | Headers Table // Application or header exchange table 91 | DeliveryMode uint8 // queue implementation use - Transient (1) or Persistent (2) 92 | Priority uint8 // queue implementation use - 0 to 9 93 | CorrelationId string // application use - correlation identifier 94 | ReplyTo string // application use - address to to reply to (ex: RPC) 95 | Expiration string // implementation use - message expiration spec 96 | MessageId string // application use - message identifier 97 | Timestamp time.Time // application use - message timestamp 98 | Type string // application use - message type name 99 | UserId string // application use - creating user id 100 | AppId string // application use - creating application 101 | reserved1 string // was cluster-id - process for buffer consumption 102 | } 103 | 104 | // DeliveryMode. Transient means higher throughput but messages will not be 105 | // restored on broker restart. The delivery mode of publishings is unrelated 106 | // to the durability of the queues they reside on. Transient messages will 107 | // not be restored to durable queues, persistent messages will be restored to 108 | // durable queues and lost on non-durable queues during server restart. 109 | // 110 | // This remains typed as uint8 to match Publishing.DeliveryMode. Other 111 | // delivery modes specific to custom queue implementations are not enumerated 112 | // here. 113 | const ( 114 | Transient uint8 = 1 115 | Persistent uint8 = 2 116 | ) 117 | 118 | // The property flags are an array of bits that indicate the presence or 119 | // absence of each property value in sequence. The bits are ordered from most 120 | // high to low - bit 15 indicates the first property. 121 | const ( 122 | flagContentType = 0x8000 123 | flagContentEncoding = 0x4000 124 | flagHeaders = 0x2000 125 | flagDeliveryMode = 0x1000 126 | flagPriority = 0x0800 127 | flagCorrelationId = 0x0400 128 | flagReplyTo = 0x0200 129 | flagExpiration = 0x0100 130 | flagMessageId = 0x0080 131 | flagTimestamp = 0x0040 132 | flagType = 0x0020 133 | flagUserId = 0x0010 134 | flagAppId = 0x0008 135 | flagReserved1 = 0x0004 136 | ) 137 | 138 | // Queue captures the current server state of the queue on the server returned 139 | // from Channel.QueueDeclare or Channel.QueueInspect. 140 | type Queue struct { 141 | Name string // server confirmed or generated name 142 | Messages int // count of messages not awaiting acknowledgment 143 | Consumers int // number of consumers receiving deliveries 144 | } 145 | 146 | // Publishing captures the client message sent to the server. The fields 147 | // outside of the Headers table included in this struct mirror the underlying 148 | // fields in the content frame. They use native types for convenience and 149 | // efficiency. 150 | type Publishing struct { 151 | // Application or exchange specific fields, 152 | // the headers exchange will inspect this field. 153 | Headers Table 154 | 155 | // Properties 156 | ContentType string // MIME content type 157 | ContentEncoding string // MIME content encoding 158 | DeliveryMode uint8 // Transient (0 or 1) or Persistent (2) 159 | Priority uint8 // 0 to 9 160 | CorrelationId string // correlation identifier 161 | ReplyTo string // address to to reply to (ex: RPC) 162 | Expiration string // message expiration spec 163 | MessageId string // message identifier 164 | Timestamp time.Time // message timestamp 165 | Type string // message type name 166 | UserId string // creating user id - ex: "guest" 167 | AppId string // creating application id 168 | 169 | // The application specific payload of the message 170 | Body []byte 171 | } 172 | 173 | // Blocking notifies the server's TCP flow control of the Connection. When a 174 | // server hits a memory or disk alarm it will block all connections until the 175 | // resources are reclaimed. Use NotifyBlock on the Connection to receive these 176 | // events. 177 | type Blocking struct { 178 | Active bool // TCP pushback active/inactive on server 179 | Reason string // Server reason for activation 180 | } 181 | 182 | // Confirmation notifies the acknowledgment or negative acknowledgement of a 183 | // publishing identified by its delivery tag. Use NotifyPublish on the Channel 184 | // to consume these events. 185 | type Confirmation struct { 186 | DeliveryTag uint64 // A 1 based counter of publishings from when the channel was put in Confirm mode 187 | Ack bool // True when the server successfully received the publishing 188 | } 189 | 190 | // Decimal matches the AMQP decimal type. Scale is the number of decimal 191 | // digits Scale == 2, Value == 12345, Decimal == 123.45 192 | type Decimal struct { 193 | Scale uint8 194 | Value int32 195 | } 196 | 197 | // Table stores user supplied fields of the following types: 198 | // 199 | // bool 200 | // byte 201 | // float32 202 | // float64 203 | // int 204 | // int16 205 | // int32 206 | // int64 207 | // nil 208 | // string 209 | // time.Time 210 | // amqp.Decimal 211 | // amqp.Table 212 | // []byte 213 | // []interface{} - containing above types 214 | // 215 | // Functions taking a table will immediately fail when the table contains a 216 | // value of an unsupported type. 217 | // 218 | // The caller must be specific in which precision of integer it wishes to 219 | // encode. 220 | // 221 | // Use a type assertion when reading values from a table for type conversion. 222 | // 223 | // RabbitMQ expects int32 for integer values. 224 | // 225 | type Table map[string]interface{} 226 | 227 | func validateField(f interface{}) error { 228 | switch fv := f.(type) { 229 | case nil, bool, byte, int, int16, int32, int64, float32, float64, string, []byte, Decimal, time.Time: 230 | return nil 231 | 232 | case []interface{}: 233 | for _, v := range fv { 234 | if err := validateField(v); err != nil { 235 | return fmt.Errorf("in array %s", err) 236 | } 237 | } 238 | return nil 239 | 240 | case Table: 241 | for k, v := range fv { 242 | if err := validateField(v); err != nil { 243 | return fmt.Errorf("table field %q %s", k, err) 244 | } 245 | } 246 | return nil 247 | } 248 | 249 | return fmt.Errorf("value %T not supported", f) 250 | } 251 | 252 | // Validate returns and error if any Go types in the table are incompatible with AMQP types. 253 | func (t Table) Validate() error { 254 | return validateField(t) 255 | } 256 | 257 | // Heap interface for maintaining delivery tags 258 | type tagSet []uint64 259 | 260 | func (set tagSet) Len() int { return len(set) } 261 | func (set tagSet) Less(i, j int) bool { return (set)[i] < (set)[j] } 262 | func (set tagSet) Swap(i, j int) { (set)[i], (set)[j] = (set)[j], (set)[i] } 263 | func (set *tagSet) Push(tag interface{}) { *set = append(*set, tag.(uint64)) } 264 | func (set *tagSet) Pop() interface{} { 265 | val := (*set)[len(*set)-1] 266 | *set = (*set)[:len(*set)-1] 267 | return val 268 | } 269 | 270 | type message interface { 271 | id() (uint16, uint16) 272 | wait() bool 273 | read(io.Reader) error 274 | write(io.Writer) error 275 | } 276 | 277 | type messageWithContent interface { 278 | message 279 | getContent() (properties, []byte) 280 | setContent(properties, []byte) 281 | } 282 | 283 | /* 284 | The base interface implemented as: 285 | 286 | 2.3.5 frame Details 287 | 288 | All frames consist of a header (7 octets), a payload of arbitrary size, and a 'frame-end' octet that detects 289 | malformed frames: 290 | 291 | 0 1 3 7 size+7 size+8 292 | +------+---------+-------------+ +------------+ +-----------+ 293 | | type | channel | size | | payload | | frame-end | 294 | +------+---------+-------------+ +------------+ +-----------+ 295 | octet short long size octets octet 296 | 297 | To read a frame, we: 298 | 299 | 1. Read the header and check the frame type and channel. 300 | 2. Depending on the frame type, we read the payload and process it. 301 | 3. Read the frame end octet. 302 | 303 | In realistic implementations where performance is a concern, we would use 304 | “read-ahead buffering” or “gathering reads” to avoid doing three separate 305 | system calls to read a frame. 306 | 307 | */ 308 | type frame interface { 309 | write(io.Writer) error 310 | channel() uint16 311 | } 312 | 313 | type reader struct { 314 | r io.Reader 315 | } 316 | 317 | type writer struct { 318 | w io.Writer 319 | } 320 | 321 | // Implements the frame interface for Connection RPC 322 | type protocolHeader struct{} 323 | 324 | func (protocolHeader) write(w io.Writer) error { 325 | _, err := w.Write([]byte{'A', 'M', 'Q', 'P', 0, 0, 9, 1}) 326 | return err 327 | } 328 | 329 | func (protocolHeader) channel() uint16 { 330 | panic("only valid as initial handshake") 331 | } 332 | 333 | /* 334 | Method frames carry the high-level protocol commands (which we call "methods"). 335 | One method frame carries one command. The method frame payload has this format: 336 | 337 | 0 2 4 338 | +----------+-----------+-------------- - - 339 | | class-id | method-id | arguments... 340 | +----------+-----------+-------------- - - 341 | short short ... 342 | 343 | To process a method frame, we: 344 | 1. Read the method frame payload. 345 | 2. Unpack it into a structure. A given method always has the same structure, 346 | so we can unpack the method rapidly. 3. Check that the method is allowed in 347 | the current context. 348 | 4. Check that the method arguments are valid. 349 | 5. Execute the method. 350 | 351 | Method frame bodies are constructed as a list of AMQP data fields (bits, 352 | integers, strings and string tables). The marshalling code is trivially 353 | generated directly from the protocol specifications, and can be very rapid. 354 | */ 355 | type methodFrame struct { 356 | ChannelId uint16 357 | ClassId uint16 358 | MethodId uint16 359 | Method message 360 | } 361 | 362 | func (f *methodFrame) channel() uint16 { return f.ChannelId } 363 | 364 | /* 365 | Heartbeating is a technique designed to undo one of TCP/IP's features, namely 366 | its ability to recover from a broken physical connection by closing only after 367 | a quite long time-out. In some scenarios we need to know very rapidly if a 368 | peer is disconnected or not responding for other reasons (e.g. it is looping). 369 | Since heartbeating can be done at a low level, we implement this as a special 370 | type of frame that peers exchange at the transport level, rather than as a 371 | class method. 372 | */ 373 | type heartbeatFrame struct { 374 | ChannelId uint16 375 | } 376 | 377 | func (f *heartbeatFrame) channel() uint16 { return f.ChannelId } 378 | 379 | /* 380 | Certain methods (such as Basic.Publish, Basic.Deliver, etc.) are formally 381 | defined as carrying content. When a peer sends such a method frame, it always 382 | follows it with a content header and zero or more content body frames. 383 | 384 | A content header frame has this format: 385 | 386 | 0 2 4 12 14 387 | +----------+--------+-----------+----------------+------------- - - 388 | | class-id | weight | body size | property flags | property list... 389 | +----------+--------+-----------+----------------+------------- - - 390 | short short long long short remainder... 391 | 392 | We place content body in distinct frames (rather than including it in the 393 | method) so that AMQP may support "zero copy" techniques in which content is 394 | never marshalled or encoded. We place the content properties in their own 395 | frame so that recipients can selectively discard contents they do not want to 396 | process 397 | */ 398 | type headerFrame struct { 399 | ChannelId uint16 400 | ClassId uint16 401 | weight uint16 402 | Size uint64 403 | Properties properties 404 | } 405 | 406 | func (f *headerFrame) channel() uint16 { return f.ChannelId } 407 | 408 | /* 409 | Content is the application data we carry from client-to-client via the AMQP 410 | server. Content is, roughly speaking, a set of properties plus a binary data 411 | part. The set of allowed properties are defined by the Basic class, and these 412 | form the "content header frame". The data can be any size, and MAY be broken 413 | into several (or many) chunks, each forming a "content body frame". 414 | 415 | Looking at the frames for a specific channel, as they pass on the wire, we 416 | might see something like this: 417 | 418 | [method] 419 | [method] [header] [body] [body] 420 | [method] 421 | ... 422 | */ 423 | type bodyFrame struct { 424 | ChannelId uint16 425 | Body []byte 426 | } 427 | 428 | func (f *bodyFrame) channel() uint16 { return f.ChannelId } 429 | -------------------------------------------------------------------------------- /uri.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // Source code and contact info at http://github.com/streadway/amqp 5 | 6 | package amqp 7 | 8 | import ( 9 | "errors" 10 | "net" 11 | "net/url" 12 | "strconv" 13 | "strings" 14 | ) 15 | 16 | var errURIScheme = errors.New("AMQP scheme must be either 'amqp://' or 'amqps://'") 17 | var errURIWhitespace = errors.New("URI must not contain whitespace") 18 | 19 | var schemePorts = map[string]int{ 20 | "amqp": 5672, 21 | "amqps": 5671, 22 | } 23 | 24 | var defaultURI = URI{ 25 | Scheme: "amqp", 26 | Host: "localhost", 27 | Port: 5672, 28 | Username: "guest", 29 | Password: "guest", 30 | Vhost: "/", 31 | } 32 | 33 | // URI represents a parsed AMQP URI string. 34 | type URI struct { 35 | Scheme string 36 | Host string 37 | Port int 38 | Username string 39 | Password string 40 | Vhost string 41 | } 42 | 43 | // ParseURI attempts to parse the given AMQP URI according to the spec. 44 | // See http://www.rabbitmq.com/uri-spec.html. 45 | // 46 | // Default values for the fields are: 47 | // 48 | // Scheme: amqp 49 | // Host: localhost 50 | // Port: 5672 51 | // Username: guest 52 | // Password: guest 53 | // Vhost: / 54 | // 55 | func ParseURI(uri string) (URI, error) { 56 | builder := defaultURI 57 | 58 | if strings.Contains(uri, " ") == true { 59 | return builder, errURIWhitespace 60 | } 61 | 62 | u, err := url.Parse(uri) 63 | if err != nil { 64 | return builder, err 65 | } 66 | 67 | defaultPort, okScheme := schemePorts[u.Scheme] 68 | 69 | if okScheme { 70 | builder.Scheme = u.Scheme 71 | } else { 72 | return builder, errURIScheme 73 | } 74 | 75 | host := u.Hostname() 76 | port := u.Port() 77 | 78 | if host != "" { 79 | builder.Host = host 80 | } 81 | 82 | if port != "" { 83 | port32, err := strconv.ParseInt(port, 10, 32) 84 | if err != nil { 85 | return builder, err 86 | } 87 | builder.Port = int(port32) 88 | } else { 89 | builder.Port = defaultPort 90 | } 91 | 92 | if u.User != nil { 93 | builder.Username = u.User.Username() 94 | if password, ok := u.User.Password(); ok { 95 | builder.Password = password 96 | } 97 | } 98 | 99 | if u.Path != "" { 100 | if strings.HasPrefix(u.Path, "/") { 101 | if u.Host == "" && strings.HasPrefix(u.Path, "///") { 102 | // net/url doesn't handle local context authorities and leaves that up 103 | // to the scheme handler. In our case, we translate amqp:/// into the 104 | // default host and whatever the vhost should be 105 | if len(u.Path) > 3 { 106 | builder.Vhost = u.Path[3:] 107 | } 108 | } else if len(u.Path) > 1 { 109 | builder.Vhost = u.Path[1:] 110 | } 111 | } else { 112 | builder.Vhost = u.Path 113 | } 114 | } 115 | 116 | return builder, nil 117 | } 118 | 119 | // PlainAuth returns a PlainAuth structure based on the parsed URI's 120 | // Username and Password fields. 121 | func (uri URI) PlainAuth() *PlainAuth { 122 | return &PlainAuth{ 123 | Username: uri.Username, 124 | Password: uri.Password, 125 | } 126 | } 127 | 128 | // AMQPlainAuth returns a PlainAuth structure based on the parsed URI's 129 | // Username and Password fields. 130 | func (uri URI) AMQPlainAuth() *AMQPlainAuth { 131 | return &AMQPlainAuth{ 132 | Username: uri.Username, 133 | Password: uri.Password, 134 | } 135 | } 136 | 137 | func (uri URI) String() string { 138 | authority, err := url.Parse("") 139 | if err != nil { 140 | return err.Error() 141 | } 142 | 143 | authority.Scheme = uri.Scheme 144 | 145 | if uri.Username != defaultURI.Username || uri.Password != defaultURI.Password { 146 | authority.User = url.User(uri.Username) 147 | 148 | if uri.Password != defaultURI.Password { 149 | authority.User = url.UserPassword(uri.Username, uri.Password) 150 | } 151 | } 152 | 153 | authority.Host = net.JoinHostPort(uri.Host, strconv.Itoa(uri.Port)) 154 | 155 | if defaultPort, found := schemePorts[uri.Scheme]; !found || defaultPort != uri.Port { 156 | authority.Host = net.JoinHostPort(uri.Host, strconv.Itoa(uri.Port)) 157 | } else { 158 | // JoinHostPort() automatically add brackets to the host if it's 159 | // an IPv6 address. 160 | // 161 | // If not port is specified, JoinHostPort() return an IP address in the 162 | // form of "[::1]:", so we use TrimSuffix() to remove the extra ":". 163 | authority.Host = strings.TrimSuffix(net.JoinHostPort(uri.Host, ""), ":") 164 | } 165 | 166 | if uri.Vhost != defaultURI.Vhost { 167 | // Make sure net/url does not double escape, e.g. 168 | // "%2F" does not become "%252F". 169 | authority.Path = uri.Vhost 170 | authority.RawPath = url.QueryEscape(uri.Vhost) 171 | } else { 172 | authority.Path = "/" 173 | } 174 | 175 | return authority.String() 176 | } 177 | -------------------------------------------------------------------------------- /uri_test.go: -------------------------------------------------------------------------------- 1 | package amqp 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | // Test matrix defined on http://www.rabbitmq.com/uri-spec.html 8 | type testURI struct { 9 | url string 10 | username string 11 | password string 12 | host string 13 | port int 14 | vhost string 15 | canon string 16 | } 17 | 18 | var uriTests = []testURI{ 19 | { 20 | url: "amqp://user:pass@host:10000/vhost", 21 | username: "user", 22 | password: "pass", 23 | host: "host", 24 | port: 10000, 25 | vhost: "vhost", 26 | canon: "amqp://user:pass@host:10000/vhost", 27 | }, 28 | 29 | { 30 | url: "amqp://", 31 | username: defaultURI.Username, 32 | password: defaultURI.Password, 33 | host: defaultURI.Host, 34 | port: defaultURI.Port, 35 | vhost: defaultURI.Vhost, 36 | canon: "amqp://localhost/", 37 | }, 38 | 39 | { 40 | url: "amqp://:@/", 41 | username: "", 42 | password: "", 43 | host: defaultURI.Host, 44 | port: defaultURI.Port, 45 | vhost: defaultURI.Vhost, 46 | canon: "amqp://:@localhost/", 47 | }, 48 | 49 | { 50 | url: "amqp://user@", 51 | username: "user", 52 | password: defaultURI.Password, 53 | host: defaultURI.Host, 54 | port: defaultURI.Port, 55 | vhost: defaultURI.Vhost, 56 | canon: "amqp://user@localhost/", 57 | }, 58 | 59 | { 60 | url: "amqp://user:pass@", 61 | username: "user", 62 | password: "pass", 63 | host: defaultURI.Host, 64 | port: defaultURI.Port, 65 | vhost: defaultURI.Vhost, 66 | canon: "amqp://user:pass@localhost/", 67 | }, 68 | 69 | { 70 | url: "amqp://guest:pass@", 71 | username: "guest", 72 | password: "pass", 73 | host: defaultURI.Host, 74 | port: defaultURI.Port, 75 | vhost: defaultURI.Vhost, 76 | canon: "amqp://guest:pass@localhost/", 77 | }, 78 | 79 | { 80 | url: "amqp://host", 81 | username: defaultURI.Username, 82 | password: defaultURI.Password, 83 | host: "host", 84 | port: defaultURI.Port, 85 | vhost: defaultURI.Vhost, 86 | canon: "amqp://host/", 87 | }, 88 | 89 | { 90 | url: "amqp://:10000", 91 | username: defaultURI.Username, 92 | password: defaultURI.Password, 93 | host: defaultURI.Host, 94 | port: 10000, 95 | vhost: defaultURI.Vhost, 96 | canon: "amqp://localhost:10000/", 97 | }, 98 | 99 | { 100 | url: "amqp:///vhost", 101 | username: defaultURI.Username, 102 | password: defaultURI.Password, 103 | host: defaultURI.Host, 104 | port: defaultURI.Port, 105 | vhost: "vhost", 106 | canon: "amqp://localhost/vhost", 107 | }, 108 | 109 | { 110 | url: "amqp://host/", 111 | username: defaultURI.Username, 112 | password: defaultURI.Password, 113 | host: "host", 114 | port: defaultURI.Port, 115 | vhost: defaultURI.Vhost, 116 | canon: "amqp://host/", 117 | }, 118 | 119 | { 120 | url: "amqp://host/%2F", 121 | username: defaultURI.Username, 122 | password: defaultURI.Password, 123 | host: "host", 124 | port: defaultURI.Port, 125 | vhost: "/", 126 | canon: "amqp://host/", 127 | }, 128 | 129 | { 130 | url: "amqp://host/%2F%2F", 131 | username: defaultURI.Username, 132 | password: defaultURI.Password, 133 | host: "host", 134 | port: defaultURI.Port, 135 | vhost: "//", 136 | canon: "amqp://host/%2F%2F", 137 | }, 138 | 139 | { 140 | url: "amqp://host/%2Fslash%2F", 141 | username: defaultURI.Username, 142 | password: defaultURI.Password, 143 | host: "host", 144 | port: defaultURI.Port, 145 | vhost: "/slash/", 146 | canon: "amqp://host/%2Fslash%2F", 147 | }, 148 | 149 | { 150 | url: "amqp://192.168.1.1:1000/", 151 | username: defaultURI.Username, 152 | password: defaultURI.Password, 153 | host: "192.168.1.1", 154 | port: 1000, 155 | vhost: defaultURI.Vhost, 156 | canon: "amqp://192.168.1.1:1000/", 157 | }, 158 | 159 | { 160 | url: "amqp://[::1]", 161 | username: defaultURI.Username, 162 | password: defaultURI.Password, 163 | host: "::1", 164 | port: defaultURI.Port, 165 | vhost: defaultURI.Vhost, 166 | canon: "amqp://[::1]/", 167 | }, 168 | 169 | { 170 | url: "amqp://[::1]:1000", 171 | username: defaultURI.Username, 172 | password: defaultURI.Password, 173 | host: "::1", 174 | port: 1000, 175 | vhost: defaultURI.Vhost, 176 | canon: "amqp://[::1]:1000/", 177 | }, 178 | 179 | { 180 | url: "amqp://[fe80::1]", 181 | username: defaultURI.Username, 182 | password: defaultURI.Password, 183 | host: "fe80::1", 184 | port: defaultURI.Port, 185 | vhost: defaultURI.Vhost, 186 | canon: "amqp://[fe80::1]/", 187 | }, 188 | 189 | { 190 | url: "amqp://[fe80::1]", 191 | username: defaultURI.Username, 192 | password: defaultURI.Password, 193 | host: "fe80::1", 194 | port: defaultURI.Port, 195 | vhost: defaultURI.Vhost, 196 | canon: "amqp://[fe80::1]/", 197 | }, 198 | 199 | { 200 | url: "amqp://[fe80::1%25en0]", 201 | username: defaultURI.Username, 202 | password: defaultURI.Password, 203 | host: "fe80::1%en0", 204 | port: defaultURI.Port, 205 | vhost: defaultURI.Vhost, 206 | canon: "amqp://[fe80::1%25en0]/", 207 | }, 208 | 209 | { 210 | url: "amqp://[fe80::1]:5671", 211 | username: defaultURI.Username, 212 | password: defaultURI.Password, 213 | host: "fe80::1", 214 | port: 5671, 215 | vhost: defaultURI.Vhost, 216 | canon: "amqp://[fe80::1]:5671/", 217 | }, 218 | 219 | { 220 | url: "amqps:///", 221 | username: defaultURI.Username, 222 | password: defaultURI.Password, 223 | host: defaultURI.Host, 224 | port: schemePorts["amqps"], 225 | vhost: defaultURI.Vhost, 226 | canon: "amqps://localhost/", 227 | }, 228 | 229 | { 230 | url: "amqps://host:1000/", 231 | username: defaultURI.Username, 232 | password: defaultURI.Password, 233 | host: "host", 234 | port: 1000, 235 | vhost: defaultURI.Vhost, 236 | canon: "amqps://host:1000/", 237 | }, 238 | } 239 | 240 | func TestURISpec(t *testing.T) { 241 | for _, test := range uriTests { 242 | u, err := ParseURI(test.url) 243 | if err != nil { 244 | t.Fatal("Could not parse spec URI: ", test.url, " err: ", err) 245 | } 246 | 247 | if test.username != u.Username { 248 | t.Error("For: ", test.url, " usernames do not match. want: ", test.username, " got: ", u.Username) 249 | } 250 | 251 | if test.password != u.Password { 252 | t.Error("For: ", test.url, " passwords do not match. want: ", test.password, " got: ", u.Password) 253 | } 254 | 255 | if test.host != u.Host { 256 | t.Error("For: ", test.url, " hosts do not match. want: ", test.host, " got: ", u.Host) 257 | } 258 | 259 | if test.port != u.Port { 260 | t.Error("For: ", test.url, " ports do not match. want: ", test.port, " got: ", u.Port) 261 | } 262 | 263 | if test.vhost != u.Vhost { 264 | t.Error("For: ", test.url, " vhosts do not match. want: ", test.vhost, " got: ", u.Vhost) 265 | } 266 | 267 | if test.canon != u.String() { 268 | t.Error("For: ", test.url, " canonical string does not match. want: ", test.canon, " got: ", u.String()) 269 | } 270 | } 271 | } 272 | 273 | func TestURIUnknownScheme(t *testing.T) { 274 | if _, err := ParseURI("http://example.com/"); err == nil { 275 | t.Fatal("Expected error when parsing non-amqp scheme") 276 | } 277 | } 278 | 279 | func TestURIScheme(t *testing.T) { 280 | if _, err := ParseURI("amqp://example.com/"); err != nil { 281 | t.Fatalf("Expected to parse amqp scheme, got %v", err) 282 | } 283 | 284 | if _, err := ParseURI("amqps://example.com/"); err != nil { 285 | t.Fatalf("Expected to parse amqps scheme, got %v", err) 286 | } 287 | } 288 | 289 | func TestURIWhitespace(t *testing.T) { 290 | if _, err := ParseURI("amqp://admin:PASSWORD@rabbitmq-service/ -http_port=8080"); err == nil { 291 | t.Fatal("Expected to fail if URI contains whitespace") 292 | } 293 | } 294 | 295 | func TestURIDefaults(t *testing.T) { 296 | url := "amqp://" 297 | uri, err := ParseURI(url) 298 | if err != nil { 299 | t.Fatal("Could not parse") 300 | } 301 | 302 | if uri.String() != "amqp://localhost/" { 303 | t.Fatal("Defaults not encoded properly got:", uri.String()) 304 | } 305 | } 306 | 307 | func TestURIComplete(t *testing.T) { 308 | url := "amqp://bob:dobbs@foo.bar:5678/private" 309 | uri, err := ParseURI(url) 310 | if err != nil { 311 | t.Fatal("Could not parse") 312 | } 313 | 314 | if uri.String() != url { 315 | t.Fatal("Defaults not encoded properly want:", url, " got:", uri.String()) 316 | } 317 | } 318 | 319 | func TestURIDefaultPortAmqpNotIncluded(t *testing.T) { 320 | url := "amqp://foo.bar:5672/" 321 | uri, err := ParseURI(url) 322 | if err != nil { 323 | t.Fatal("Could not parse") 324 | } 325 | 326 | if uri.String() != "amqp://foo.bar/" { 327 | t.Fatal("Defaults not encoded properly got:", uri.String()) 328 | } 329 | } 330 | 331 | func TestURIDefaultPortAmqp(t *testing.T) { 332 | url := "amqp://foo.bar/" 333 | uri, err := ParseURI(url) 334 | if err != nil { 335 | t.Fatal("Could not parse") 336 | } 337 | 338 | if uri.Port != 5672 { 339 | t.Fatal("Default port not correct for amqp, got:", uri.Port) 340 | } 341 | } 342 | 343 | func TestURIDefaultPortAmqpsNotIncludedInString(t *testing.T) { 344 | url := "amqps://foo.bar:5671/" 345 | uri, err := ParseURI(url) 346 | if err != nil { 347 | t.Fatal("Could not parse") 348 | } 349 | 350 | if uri.String() != "amqps://foo.bar/" { 351 | t.Fatal("Defaults not encoded properly got:", uri.String()) 352 | } 353 | } 354 | 355 | func TestURIDefaultPortAmqps(t *testing.T) { 356 | url := "amqps://foo.bar/" 357 | uri, err := ParseURI(url) 358 | if err != nil { 359 | t.Fatal("Could not parse") 360 | } 361 | 362 | if uri.Port != 5671 { 363 | t.Fatal("Default port not correct for amqps, got:", uri.Port) 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /write.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2012, Sean Treadway, SoundCloud Ltd. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | // Source code and contact info at http://github.com/streadway/amqp 5 | 6 | package amqp 7 | 8 | import ( 9 | "bufio" 10 | "bytes" 11 | "encoding/binary" 12 | "errors" 13 | "io" 14 | "math" 15 | "time" 16 | ) 17 | 18 | func (w *writer) WriteFrame(frame frame) (err error) { 19 | if err = frame.write(w.w); err != nil { 20 | return 21 | } 22 | 23 | if buf, ok := w.w.(*bufio.Writer); ok { 24 | err = buf.Flush() 25 | } 26 | 27 | return 28 | } 29 | 30 | func (f *methodFrame) write(w io.Writer) (err error) { 31 | var payload bytes.Buffer 32 | 33 | if f.Method == nil { 34 | return errors.New("malformed frame: missing method") 35 | } 36 | 37 | class, method := f.Method.id() 38 | 39 | if err = binary.Write(&payload, binary.BigEndian, class); err != nil { 40 | return 41 | } 42 | 43 | if err = binary.Write(&payload, binary.BigEndian, method); err != nil { 44 | return 45 | } 46 | 47 | if err = f.Method.write(&payload); err != nil { 48 | return 49 | } 50 | 51 | return writeFrame(w, frameMethod, f.ChannelId, payload.Bytes()) 52 | } 53 | 54 | // Heartbeat 55 | // 56 | // Payload is empty 57 | func (f *heartbeatFrame) write(w io.Writer) (err error) { 58 | return writeFrame(w, frameHeartbeat, f.ChannelId, []byte{}) 59 | } 60 | 61 | // CONTENT HEADER 62 | // 0 2 4 12 14 63 | // +----------+--------+-----------+----------------+------------- - - 64 | // | class-id | weight | body size | property flags | property list... 65 | // +----------+--------+-----------+----------------+------------- - - 66 | // short short long long short remainder... 67 | // 68 | func (f *headerFrame) write(w io.Writer) (err error) { 69 | var payload bytes.Buffer 70 | var zeroTime time.Time 71 | 72 | if err = binary.Write(&payload, binary.BigEndian, f.ClassId); err != nil { 73 | return 74 | } 75 | 76 | if err = binary.Write(&payload, binary.BigEndian, f.weight); err != nil { 77 | return 78 | } 79 | 80 | if err = binary.Write(&payload, binary.BigEndian, f.Size); err != nil { 81 | return 82 | } 83 | 84 | // First pass will build the mask to be serialized, second pass will serialize 85 | // each of the fields that appear in the mask. 86 | 87 | var mask uint16 88 | 89 | if len(f.Properties.ContentType) > 0 { 90 | mask = mask | flagContentType 91 | } 92 | if len(f.Properties.ContentEncoding) > 0 { 93 | mask = mask | flagContentEncoding 94 | } 95 | if f.Properties.Headers != nil && len(f.Properties.Headers) > 0 { 96 | mask = mask | flagHeaders 97 | } 98 | if f.Properties.DeliveryMode > 0 { 99 | mask = mask | flagDeliveryMode 100 | } 101 | if f.Properties.Priority > 0 { 102 | mask = mask | flagPriority 103 | } 104 | if len(f.Properties.CorrelationId) > 0 { 105 | mask = mask | flagCorrelationId 106 | } 107 | if len(f.Properties.ReplyTo) > 0 { 108 | mask = mask | flagReplyTo 109 | } 110 | if len(f.Properties.Expiration) > 0 { 111 | mask = mask | flagExpiration 112 | } 113 | if len(f.Properties.MessageId) > 0 { 114 | mask = mask | flagMessageId 115 | } 116 | if f.Properties.Timestamp != zeroTime { 117 | mask = mask | flagTimestamp 118 | } 119 | if len(f.Properties.Type) > 0 { 120 | mask = mask | flagType 121 | } 122 | if len(f.Properties.UserId) > 0 { 123 | mask = mask | flagUserId 124 | } 125 | if len(f.Properties.AppId) > 0 { 126 | mask = mask | flagAppId 127 | } 128 | 129 | if err = binary.Write(&payload, binary.BigEndian, mask); err != nil { 130 | return 131 | } 132 | 133 | if hasProperty(mask, flagContentType) { 134 | if err = writeShortstr(&payload, f.Properties.ContentType); err != nil { 135 | return 136 | } 137 | } 138 | if hasProperty(mask, flagContentEncoding) { 139 | if err = writeShortstr(&payload, f.Properties.ContentEncoding); err != nil { 140 | return 141 | } 142 | } 143 | if hasProperty(mask, flagHeaders) { 144 | if err = writeTable(&payload, f.Properties.Headers); err != nil { 145 | return 146 | } 147 | } 148 | if hasProperty(mask, flagDeliveryMode) { 149 | if err = binary.Write(&payload, binary.BigEndian, f.Properties.DeliveryMode); err != nil { 150 | return 151 | } 152 | } 153 | if hasProperty(mask, flagPriority) { 154 | if err = binary.Write(&payload, binary.BigEndian, f.Properties.Priority); err != nil { 155 | return 156 | } 157 | } 158 | if hasProperty(mask, flagCorrelationId) { 159 | if err = writeShortstr(&payload, f.Properties.CorrelationId); err != nil { 160 | return 161 | } 162 | } 163 | if hasProperty(mask, flagReplyTo) { 164 | if err = writeShortstr(&payload, f.Properties.ReplyTo); err != nil { 165 | return 166 | } 167 | } 168 | if hasProperty(mask, flagExpiration) { 169 | if err = writeShortstr(&payload, f.Properties.Expiration); err != nil { 170 | return 171 | } 172 | } 173 | if hasProperty(mask, flagMessageId) { 174 | if err = writeShortstr(&payload, f.Properties.MessageId); err != nil { 175 | return 176 | } 177 | } 178 | if hasProperty(mask, flagTimestamp) { 179 | if err = binary.Write(&payload, binary.BigEndian, uint64(f.Properties.Timestamp.Unix())); err != nil { 180 | return 181 | } 182 | } 183 | if hasProperty(mask, flagType) { 184 | if err = writeShortstr(&payload, f.Properties.Type); err != nil { 185 | return 186 | } 187 | } 188 | if hasProperty(mask, flagUserId) { 189 | if err = writeShortstr(&payload, f.Properties.UserId); err != nil { 190 | return 191 | } 192 | } 193 | if hasProperty(mask, flagAppId) { 194 | if err = writeShortstr(&payload, f.Properties.AppId); err != nil { 195 | return 196 | } 197 | } 198 | 199 | return writeFrame(w, frameHeader, f.ChannelId, payload.Bytes()) 200 | } 201 | 202 | // Body 203 | // 204 | // Payload is one byterange from the full body who's size is declared in the 205 | // Header frame 206 | func (f *bodyFrame) write(w io.Writer) (err error) { 207 | return writeFrame(w, frameBody, f.ChannelId, f.Body) 208 | } 209 | 210 | func writeFrame(w io.Writer, typ uint8, channel uint16, payload []byte) (err error) { 211 | end := []byte{frameEnd} 212 | size := uint(len(payload)) 213 | 214 | _, err = w.Write([]byte{ 215 | byte(typ), 216 | byte((channel & 0xff00) >> 8), 217 | byte((channel & 0x00ff) >> 0), 218 | byte((size & 0xff000000) >> 24), 219 | byte((size & 0x00ff0000) >> 16), 220 | byte((size & 0x0000ff00) >> 8), 221 | byte((size & 0x000000ff) >> 0), 222 | }) 223 | 224 | if err != nil { 225 | return 226 | } 227 | 228 | if _, err = w.Write(payload); err != nil { 229 | return 230 | } 231 | 232 | if _, err = w.Write(end); err != nil { 233 | return 234 | } 235 | 236 | return 237 | } 238 | 239 | func writeShortstr(w io.Writer, s string) (err error) { 240 | b := []byte(s) 241 | 242 | var length = uint8(len(b)) 243 | 244 | if err = binary.Write(w, binary.BigEndian, length); err != nil { 245 | return 246 | } 247 | 248 | if _, err = w.Write(b[:length]); err != nil { 249 | return 250 | } 251 | 252 | return 253 | } 254 | 255 | func writeLongstr(w io.Writer, s string) (err error) { 256 | b := []byte(s) 257 | 258 | var length = uint32(len(b)) 259 | 260 | if err = binary.Write(w, binary.BigEndian, length); err != nil { 261 | return 262 | } 263 | 264 | if _, err = w.Write(b[:length]); err != nil { 265 | return 266 | } 267 | 268 | return 269 | } 270 | 271 | /* 272 | 'A': []interface{} 273 | 'D': Decimal 274 | 'F': Table 275 | 'I': int32 276 | 'S': string 277 | 'T': time.Time 278 | 'V': nil 279 | 'b': byte 280 | 'd': float64 281 | 'f': float32 282 | 'l': int64 283 | 's': int16 284 | 't': bool 285 | 'x': []byte 286 | */ 287 | func writeField(w io.Writer, value interface{}) (err error) { 288 | var buf [9]byte 289 | var enc []byte 290 | 291 | switch v := value.(type) { 292 | case bool: 293 | buf[0] = 't' 294 | if v { 295 | buf[1] = byte(1) 296 | } else { 297 | buf[1] = byte(0) 298 | } 299 | enc = buf[:2] 300 | 301 | case byte: 302 | buf[0] = 'b' 303 | buf[1] = byte(v) 304 | enc = buf[:2] 305 | 306 | case int16: 307 | buf[0] = 's' 308 | binary.BigEndian.PutUint16(buf[1:3], uint16(v)) 309 | enc = buf[:3] 310 | 311 | case int: 312 | buf[0] = 'I' 313 | binary.BigEndian.PutUint32(buf[1:5], uint32(v)) 314 | enc = buf[:5] 315 | 316 | case int32: 317 | buf[0] = 'I' 318 | binary.BigEndian.PutUint32(buf[1:5], uint32(v)) 319 | enc = buf[:5] 320 | 321 | case int64: 322 | buf[0] = 'l' 323 | binary.BigEndian.PutUint64(buf[1:9], uint64(v)) 324 | enc = buf[:9] 325 | 326 | case float32: 327 | buf[0] = 'f' 328 | binary.BigEndian.PutUint32(buf[1:5], math.Float32bits(v)) 329 | enc = buf[:5] 330 | 331 | case float64: 332 | buf[0] = 'd' 333 | binary.BigEndian.PutUint64(buf[1:9], math.Float64bits(v)) 334 | enc = buf[:9] 335 | 336 | case Decimal: 337 | buf[0] = 'D' 338 | buf[1] = byte(v.Scale) 339 | binary.BigEndian.PutUint32(buf[2:6], uint32(v.Value)) 340 | enc = buf[:6] 341 | 342 | case string: 343 | buf[0] = 'S' 344 | binary.BigEndian.PutUint32(buf[1:5], uint32(len(v))) 345 | enc = append(buf[:5], []byte(v)...) 346 | 347 | case []interface{}: // field-array 348 | buf[0] = 'A' 349 | 350 | sec := new(bytes.Buffer) 351 | for _, val := range v { 352 | if err = writeField(sec, val); err != nil { 353 | return 354 | } 355 | } 356 | 357 | binary.BigEndian.PutUint32(buf[1:5], uint32(sec.Len())) 358 | if _, err = w.Write(buf[:5]); err != nil { 359 | return 360 | } 361 | 362 | if _, err = w.Write(sec.Bytes()); err != nil { 363 | return 364 | } 365 | 366 | return 367 | 368 | case time.Time: 369 | buf[0] = 'T' 370 | binary.BigEndian.PutUint64(buf[1:9], uint64(v.Unix())) 371 | enc = buf[:9] 372 | 373 | case Table: 374 | if _, err = w.Write([]byte{'F'}); err != nil { 375 | return 376 | } 377 | return writeTable(w, v) 378 | 379 | case []byte: 380 | buf[0] = 'x' 381 | binary.BigEndian.PutUint32(buf[1:5], uint32(len(v))) 382 | if _, err = w.Write(buf[0:5]); err != nil { 383 | return 384 | } 385 | if _, err = w.Write(v); err != nil { 386 | return 387 | } 388 | return 389 | 390 | case nil: 391 | buf[0] = 'V' 392 | enc = buf[:1] 393 | 394 | default: 395 | return ErrFieldType 396 | } 397 | 398 | _, err = w.Write(enc) 399 | 400 | return 401 | } 402 | 403 | func writeTable(w io.Writer, table Table) (err error) { 404 | var buf bytes.Buffer 405 | 406 | for key, val := range table { 407 | if err = writeShortstr(&buf, key); err != nil { 408 | return 409 | } 410 | if err = writeField(&buf, val); err != nil { 411 | return 412 | } 413 | } 414 | 415 | return writeLongstr(w, string(buf.Bytes())) 416 | } 417 | --------------------------------------------------------------------------------