├── LICENSE.md ├── README.md └── rabbitmq-http.go /LICENSE.md: -------------------------------------------------------------------------------- 1 | Software License Agreement (BSD License) 2 | 3 | Copyright (C) 2013, by Chen "smallfish" Xiaoyu (陈小玉) . 4 | 5 | All rights reserved. 6 | 7 | Redistribution and use of this software in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above 11 | copyright notice, this list of conditions and the 12 | following disclaimer. 13 | 14 | * Redistributions in binary form must reproduce the above 15 | copyright notice, this list of conditions and the 16 | following disclaimer in the documentation and/or other 17 | materials provided with the distribution. 18 | 19 | * Neither the name of Mozilla Foundation nor the names of its 20 | contributors may be used to endorse or promote products 21 | derived from this software without specific prior 22 | written permission of Mozilla Foundation. 23 | 24 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 25 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 26 | FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 27 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 30 | IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 31 | OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### RabbitMQ HTTP API 2 | 3 | 4 | REST HTTP API for RabbitMQ, it's not [RabbitMQ Management Plugin](http://www.rabbitmq.com/management.html). 5 | 6 | ##### Status: 7 | 8 | Under active development. 9 | 10 | ##### Required: 11 | 12 | * RabbitMQ (2.8+) 13 | * Go(lang) (1.0.3) 14 | 15 | ##### Install: 16 | 17 | $ go get github.com/streadway/amqp 18 | $ go get github.com/smallfish/rabbitmq-http 19 | 20 | ##### Usage 21 | 22 | * Start HTTP Server (see your $GOPATH/bin): 23 | 24 | # $GOPATH/bin/rabbitmq-http -address="127.0.0.1:8080" -amqp="amqp://guest:guest@localhost:5672/" 25 | 26 | ##### HTTP Response 27 | 28 | 200 OK 29 | 405 Method Not Allowed 30 | 500 Internal Server Error 31 | 32 | ##### API List 33 | 34 | ###### Exchange 35 | 36 | * create new exchange: 37 | 38 | $ curl -i -X POST http://127.0.0.1:8080/exchange -d \ 39 | '{"name": "e1", "type": "topic", "durable": true, "autodelete": false}' 40 | 41 | HTTP/1.1 200 OK 42 | Date: Thu, 21 Mar 2013 05:45:47 GMT 43 | Transfer-Encoding: chunked 44 | Content-Type: text/plain; charset=utf-8 45 | 46 | declare exchange ok 47 | 48 | * delete exchange: 49 | 50 | $ curl -i -X DELETE http://127.0.0.1:8080/exchange -d \ 51 | '{"name": "e1"}' 52 | 53 | HTTP/1.1 200 OK 54 | Date: Thu, 21 Mar 2013 05:46:21 GMT 55 | Transfer-Encoding: chunked 56 | Content-Type: text/plain; charset=utf-8 57 | 58 | delete exchange ok 59 | 60 | ###### Message 61 | 62 | * publish new message: 63 | 64 | $ curl -i -X POST "http://127.0.0.1:8080/publish" -d \ 65 | '{"exchange": "e1", "key": "bb", "deliverymode": 1, "priority": 99, "body": "hahaha"}' 66 | 67 | HTTP/1.1 200 OK 68 | Date: Mon, 25 Mar 2013 11:56:22 GMT 69 | Transfer-Encoding: chunked 70 | Content-Type: text/plain; charset=utf-8 71 | 72 | publish message ok 73 | 74 | ###### Queue 75 | 76 | * create new queue: 77 | 78 | $ curl -i -X POST http://127.0.0.1:8080/queue -d \ 79 | '{"name": "q1"}' 80 | 81 | HTTP/1.1 200 OK 82 | Date: Thu, 21 Mar 2013 05:47:11 GMT 83 | Transfer-Encoding: chunked 84 | Content-Type: text/plain; charset=utf-8 85 | 86 | declare queue ok 87 | 88 | 89 | * delete queue: 90 | 91 | $ curl -i -X DELETE http://127.0.0.1:8080/queue -d \ 92 | '{"name": "q1"}' 93 | 94 | HTTP/1.1 200 OK 95 | Date: Thu, 21 Mar 2013 05:48:05 GMT 96 | Transfer-Encoding: chunked 97 | Content-Type: text/plain; charset=utf-8 98 | 99 | delete queue ok 100 | 101 | * bind keys to queue: 102 | 103 | $ curl -i -X POST http://127.0.0.1:8080/queue/bind -d \ 104 | '{"queue": "q1", "exchange": "e1", "keys": ["aa", "bb", "cc"]}' 105 | 106 | HTTP/1.1 200 OK 107 | Date: Thu, 21 Mar 2013 05:48:43 GMT 108 | Transfer-Encoding: chunked 109 | Content-Type: text/plain; charset=utf-8 110 | 111 | bind queue ok 112 | 113 | * unbind keys to queue: 114 | 115 | $ curl -i -X DELETE http://127.0.0.1:8080/queue/bind -d \ 116 | '{"queue": "q1", "exchange": "e1", "keys": ["aa", "cc"]}' 117 | 118 | HTTP/1.1 200 OK 119 | Date: Thu, 21 Mar 2013 05:49:05 GMT 120 | Transfer-Encoding: chunked 121 | Content-Type: text/plain; charset=utf-8 122 | 123 | unbind queue ok 124 | 125 | * consume queue: 126 | 127 | $ curl -i -X GET "http://127.0.0.1:8080/queue?name=q1" # more queues: "/queue?name=q1&name&q2" 128 | 129 | HTTP/1.1 200 OK 130 | Date: Fri, 22 Mar 2013 04:11:59 GMT 131 | Transfer-Encoding: chunked 132 | Content-Type: text/plain; charset=utf-8 133 | 134 | \n 135 | \n 136 | ... 137 | 138 | ##### Copyright and License 139 | 140 | rabbitmq-http is licensed under the [BSD license](https://github.com/smallfish/rabbitmq-http/blob/master/LICENSE.md). 141 | -------------------------------------------------------------------------------- /rabbitmq-http.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2013 Chen "smallfish" Xiaoyu (陈小玉) 2 | 3 | package main 4 | 5 | import ( 6 | "encoding/json" 7 | "flag" 8 | "fmt" 9 | "github.com/streadway/amqp" 10 | "io/ioutil" 11 | "log" 12 | "net/http" 13 | ) 14 | 15 | var ( 16 | address = flag.String("address", "127.0.0.1:8080", "bind host:port") 17 | amqpUri = flag.String("amqp", "amqp://guest:guest@127.0.0.1:5672/", "amqp uri") 18 | ) 19 | 20 | func init() { 21 | flag.Parse() 22 | } 23 | 24 | // Entity for HTTP Request Body: Message/Exchange/Queue/QueueBind JSON Input 25 | type MessageEntity struct { 26 | Exchange string `json:"exchange"` 27 | Key string `json:"key"` 28 | DeliveryMode uint8 `json:"deliverymode"` 29 | Priority uint8 `json:"priority"` 30 | Body string `json:"body"` 31 | } 32 | 33 | type ExchangeEntity struct { 34 | Name string `json:"name"` 35 | Type string `json:"type"` 36 | Durable bool `json:"durable"` 37 | AutoDelete bool `json:"autodelete"` 38 | NoWait bool `json:"nowait"` 39 | } 40 | 41 | type QueueEntity struct { 42 | Name string `json:"name"` 43 | Durable bool `json:"durable"` 44 | AutoDelete bool `json:"autodelete"` 45 | Exclusive bool `json:"exclusive"` 46 | NoWait bool `json:"nowait"` 47 | } 48 | 49 | type QueueBindEntity struct { 50 | Queue string `json:"queue"` 51 | Exchange string `json:"exchange"` 52 | NoWait bool `json:"nowait"` 53 | Keys []string `json:"keys"` // bind/routing keys 54 | } 55 | 56 | // RabbitMQ Operate Wrapper 57 | type RabbitMQ struct { 58 | conn *amqp.Connection 59 | channel *amqp.Channel 60 | done chan error 61 | } 62 | 63 | func (r *RabbitMQ) Connect() (err error) { 64 | r.conn, err = amqp.Dial(*amqpUri) 65 | if err != nil { 66 | log.Printf("[amqp] connect error: %s\n", err) 67 | return err 68 | } 69 | r.channel, err = r.conn.Channel() 70 | if err != nil { 71 | log.Printf("[amqp] get channel error: %s\n", err) 72 | return err 73 | } 74 | r.done = make(chan error) 75 | return nil 76 | } 77 | 78 | func (r *RabbitMQ) Publish(exchange, key string, deliverymode, priority uint8, body string) (err error) { 79 | err = r.channel.Publish(exchange, key, false, false, 80 | amqp.Publishing{ 81 | Headers: amqp.Table{}, 82 | ContentType: "text/plain", 83 | ContentEncoding: "", 84 | DeliveryMode: deliverymode, 85 | Priority: priority, 86 | Body: []byte(body), 87 | }, 88 | ) 89 | if err != nil { 90 | log.Printf("[amqp] publish message error: %s\n", err) 91 | return err 92 | } 93 | return nil 94 | } 95 | 96 | func (r *RabbitMQ) DeclareExchange(name, typ string, durable, autodelete, nowait bool) (err error) { 97 | err = r.channel.ExchangeDeclare(name, typ, durable, autodelete, false, nowait, nil) 98 | if err != nil { 99 | log.Printf("[amqp] declare exchange error: %s\n", err) 100 | return err 101 | } 102 | return nil 103 | } 104 | 105 | func (r *RabbitMQ) DeleteExchange(name string) (err error) { 106 | err = r.channel.ExchangeDelete(name, false, false) 107 | if err != nil { 108 | log.Printf("[amqp] delete exchange error: %s\n", err) 109 | return err 110 | } 111 | return nil 112 | } 113 | 114 | func (r *RabbitMQ) DeclareQueue(name string, durable, autodelete, exclusive, nowait bool) (err error) { 115 | _, err = r.channel.QueueDeclare(name, durable, autodelete, exclusive, nowait, nil) 116 | if err != nil { 117 | log.Printf("[amqp] declare queue error: %s\n", err) 118 | return err 119 | } 120 | return nil 121 | } 122 | 123 | func (r *RabbitMQ) DeleteQueue(name string) (err error) { 124 | // TODO: other property wrapper 125 | _, err = r.channel.QueueDelete(name, false, false, false) 126 | if err != nil { 127 | log.Printf("[amqp] delete queue error: %s\n", err) 128 | return err 129 | } 130 | return nil 131 | } 132 | 133 | func (r *RabbitMQ) BindQueue(queue, exchange string, keys []string, nowait bool) (err error) { 134 | for _, key := range keys { 135 | if err = r.channel.QueueBind(queue, key, exchange, nowait, nil); err != nil { 136 | log.Printf("[amqp] bind queue error: %s\n", err) 137 | return err 138 | } 139 | } 140 | return nil 141 | } 142 | 143 | func (r *RabbitMQ) UnBindQueue(queue, exchange string, keys []string) (err error) { 144 | for _, key := range keys { 145 | if err = r.channel.QueueUnbind(queue, key, exchange, nil); err != nil { 146 | log.Printf("[amqp] unbind queue error: %s\n", err) 147 | return err 148 | } 149 | } 150 | return nil 151 | } 152 | 153 | func (r *RabbitMQ) ConsumeQueue(queue string, message chan []byte) (err error) { 154 | deliveries, err := r.channel.Consume(queue, "", true, false, false, false, nil) 155 | if err != nil { 156 | log.Printf("[amqp] consume queue error: %s\n", err) 157 | return err 158 | } 159 | go func(deliveries <-chan amqp.Delivery, done chan error, message chan []byte) { 160 | for d := range deliveries { 161 | message <- d.Body 162 | } 163 | done <- nil 164 | }(deliveries, r.done, message) 165 | return nil 166 | } 167 | 168 | func (r *RabbitMQ) Close() (err error) { 169 | err = r.conn.Close() 170 | if err != nil { 171 | log.Printf("[amqp] close error: %s\n", err) 172 | return err 173 | } 174 | return nil 175 | } 176 | 177 | // HTTP Handlers 178 | func QueueHandler(w http.ResponseWriter, r *http.Request) { 179 | if r.Method == "POST" || r.Method == "DELETE" { 180 | body, err := ioutil.ReadAll(r.Body) 181 | if err != nil { 182 | http.Error(w, err.Error(), http.StatusInternalServerError) 183 | return 184 | } 185 | 186 | entity := new(QueueEntity) 187 | if err = json.Unmarshal(body, entity); err != nil { 188 | http.Error(w, err.Error(), http.StatusInternalServerError) 189 | return 190 | } 191 | 192 | rabbit := new(RabbitMQ) 193 | if err = rabbit.Connect(); err != nil { 194 | http.Error(w, err.Error(), http.StatusInternalServerError) 195 | return 196 | } 197 | defer rabbit.Close() 198 | 199 | if r.Method == "POST" { 200 | if err = rabbit.DeclareQueue(entity.Name, entity.Durable, entity.AutoDelete, entity.Exclusive, entity.NoWait); err != nil { 201 | http.Error(w, err.Error(), http.StatusInternalServerError) 202 | return 203 | } 204 | w.Write([]byte("declare queue ok")) 205 | } else if r.Method == "DELETE" { 206 | if err = rabbit.DeleteQueue(entity.Name); err != nil { 207 | http.Error(w, err.Error(), http.StatusInternalServerError) 208 | return 209 | } 210 | w.Write([]byte("delete queue ok")) 211 | } 212 | } else if r.Method == "GET" { 213 | r.ParseForm() 214 | 215 | rabbit := new(RabbitMQ) 216 | if err := rabbit.Connect(); err != nil { 217 | http.Error(w, err.Error(), http.StatusInternalServerError) 218 | return 219 | } 220 | defer rabbit.Close() 221 | 222 | message := make(chan []byte) 223 | 224 | for _, name := range r.Form["name"] { 225 | if err := rabbit.ConsumeQueue(name, message); err != nil { 226 | http.Error(w, err.Error(), http.StatusInternalServerError) 227 | return 228 | } 229 | } 230 | 231 | w.Write([]byte("")) 232 | w.(http.Flusher).Flush() 233 | 234 | for { 235 | fmt.Fprintf(w, "%s\n", <-message) 236 | w.(http.Flusher).Flush() 237 | } 238 | } else { 239 | w.WriteHeader(http.StatusMethodNotAllowed) 240 | } 241 | } 242 | 243 | func QueueBindHandler(w http.ResponseWriter, r *http.Request) { 244 | if r.Method == "POST" || r.Method == "DELETE" { 245 | body, err := ioutil.ReadAll(r.Body) 246 | if err != nil { 247 | http.Error(w, err.Error(), http.StatusInternalServerError) 248 | return 249 | } 250 | 251 | entity := new(QueueBindEntity) 252 | if err = json.Unmarshal(body, entity); err != nil { 253 | http.Error(w, err.Error(), http.StatusInternalServerError) 254 | return 255 | } 256 | 257 | rabbit := new(RabbitMQ) 258 | if err = rabbit.Connect(); err != nil { 259 | http.Error(w, err.Error(), http.StatusInternalServerError) 260 | return 261 | } 262 | defer rabbit.Close() 263 | 264 | if r.Method == "POST" { 265 | if err = rabbit.BindQueue(entity.Queue, entity.Exchange, entity.Keys, entity.NoWait); err != nil { 266 | http.Error(w, err.Error(), http.StatusInternalServerError) 267 | return 268 | } 269 | w.Write([]byte("bind queue ok")) 270 | } else if r.Method == "DELETE" { 271 | if err = rabbit.UnBindQueue(entity.Queue, entity.Exchange, entity.Keys); err != nil { 272 | http.Error(w, err.Error(), http.StatusInternalServerError) 273 | return 274 | } 275 | w.Write([]byte("unbind queue ok")) 276 | } 277 | } else { 278 | w.WriteHeader(http.StatusMethodNotAllowed) 279 | } 280 | } 281 | 282 | func PublishHandler(w http.ResponseWriter, r *http.Request) { 283 | if r.Method == "POST" { 284 | body, err := ioutil.ReadAll(r.Body) 285 | if err != nil { 286 | http.Error(w, err.Error(), http.StatusInternalServerError) 287 | return 288 | } 289 | 290 | entity := new(MessageEntity) 291 | if err = json.Unmarshal(body, entity); err != nil { 292 | http.Error(w, err.Error(), http.StatusInternalServerError) 293 | return 294 | } 295 | 296 | rabbit := new(RabbitMQ) 297 | if err = rabbit.Connect(); err != nil { 298 | http.Error(w, err.Error(), http.StatusInternalServerError) 299 | return 300 | } 301 | defer rabbit.Close() 302 | 303 | if err = rabbit.Publish(entity.Exchange, entity.Key, entity.DeliveryMode, entity.Priority, entity.Body); err != nil { 304 | http.Error(w, err.Error(), http.StatusInternalServerError) 305 | return 306 | } 307 | w.Write([]byte("publish message ok")) 308 | } else { 309 | w.WriteHeader(http.StatusMethodNotAllowed) 310 | } 311 | } 312 | 313 | func ExchangeHandler(w http.ResponseWriter, r *http.Request) { 314 | if r.Method == "POST" || r.Method == "DELETE" { 315 | body, err := ioutil.ReadAll(r.Body) 316 | if err != nil { 317 | http.Error(w, err.Error(), http.StatusInternalServerError) 318 | return 319 | } 320 | 321 | entity := new(ExchangeEntity) 322 | if err = json.Unmarshal(body, entity); err != nil { 323 | http.Error(w, err.Error(), http.StatusInternalServerError) 324 | return 325 | } 326 | 327 | rabbit := new(RabbitMQ) 328 | if err = rabbit.Connect(); err != nil { 329 | http.Error(w, err.Error(), http.StatusInternalServerError) 330 | return 331 | } 332 | defer rabbit.Close() 333 | 334 | if r.Method == "POST" { 335 | if err = rabbit.DeclareExchange(entity.Name, entity.Type, entity.Durable, entity.AutoDelete, entity.NoWait); err != nil { 336 | http.Error(w, err.Error(), http.StatusInternalServerError) 337 | return 338 | } 339 | w.Write([]byte("declare exchange ok")) 340 | } else if r.Method == "DELETE" { 341 | if err = rabbit.DeleteExchange(entity.Name); err != nil { 342 | http.Error(w, err.Error(), http.StatusInternalServerError) 343 | return 344 | } 345 | w.Write([]byte("delete exchange ok")) 346 | } 347 | } else { 348 | w.WriteHeader(http.StatusMethodNotAllowed) 349 | } 350 | } 351 | 352 | func main() { 353 | // Register HTTP Handlers 354 | http.HandleFunc("/exchange", ExchangeHandler) 355 | http.HandleFunc("/queue/bind", QueueBindHandler) 356 | http.HandleFunc("/queue", QueueHandler) 357 | http.HandleFunc("/publish", PublishHandler) 358 | 359 | // Start HTTP Server 360 | log.Printf("server run %s (listen %s)\n", *address, *amqpUri) 361 | err := http.ListenAndServe(*address, nil) 362 | if err != nil { 363 | log.Fatal(err) 364 | } 365 | } 366 | --------------------------------------------------------------------------------