Skip to content

Commit

Permalink
Merge pull request #7 from vtrifonov/master
Browse files Browse the repository at this point in the history
Added support for sending messages to AMQP
  • Loading branch information
jmartin82 authored Nov 15, 2016
2 parents 713e5ae + d9b9704 commit f111158
Show file tree
Hide file tree
Showing 16 changed files with 291 additions and 39 deletions.
55 changes: 49 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ Built with Go - Mmock runs without installation on multiple platforms.

* Easy mock definition via JSON or YAML
* Variables in response (fake or request data, including regex support)
* Persist request body to file and load request from file
* Persist request body to file and load response from file
* Ability to send message to AMQP server
* Glob matching ( /a/b/* )
* Match request by method, URL params, headers, cookies and bodies.
* Mock definitions hot replace (edit your mocks without restart)
Expand Down Expand Up @@ -157,7 +158,26 @@ Mock definition:
},
"persist" : {
"name" : "/users/user-{{request.url./your/path/(?P<value>\\d+)}}.json",
"delete": false
"delete": false,
"amqp": {
"url": "amqp://guest:guest@localhost:5672/myVHost",
"body": "{{ response.body }}",
"delay": 2,
"bodyAppend": "{ \"itemToAppend\": 5}",
"exchange": "myExchange",
"type": "MockType",
"correlationId": "9782b88f-0c6e-4879-8c23-4699785e6a95",
"routingKey": "routing",
"contentType": "application/json",
"contentEncoding": "",
"priority": 0,
"replyTo": "",
"expiration": "",
"messageId": "",
"timestamp": "2016-01-01T00:00:00Z",
"userId": "",
"appId": ""
}
},
"control": {
"proxyBaseURL": "string (original URL endpoint)
Expand Down Expand Up @@ -194,9 +214,9 @@ To do a match with queryStringParameters, headers, cookies. All defined keys in
##### Persisted

* *name*: The relative path from config-persist-path to the file where the response body to be loaded from. It allows vars.
* *notFound*: The status code and body which will be returned if the file does not exist. The default values are statusCode: **404** and body: **Not Found**
* *notFound.statusCode*: The status code to be returned if the file is not found. The default value is **404**
* *notFound.body*: The body to be returned if the file is not found. It allows vars. The default value is **Not Found**
* *notFound*: The status code and body which will be returned if the file does not exist. The default values are statusCode: **404** and body: **Not Found**.
* *notFound.statusCode*: The status code to be returned if the file is not found. The default value is **404**.
* *notFound.body*: The body to be returned if the file is not found. It allows vars. The default value is **Not Found**.
* *notFound.bodyAppend*: Additional text or json object to be appended to the body if the file is not found. It allows vars.
* *bodyAppend*: Additional text or json object to be appended to the body loaded from the file. It allows vars.
* *persisted*: Configuration for reading the body content from file.
Expand All @@ -205,6 +225,27 @@ To do a match with queryStringParameters, headers, cookies. All defined keys in

* *name*: The relative path from config-persist-path to the file where the ressponse body will be persisted. It allows vars.
* *delete*: True or false. This is useful for making **DELETE** verb to delete the file.
* *amqp*: Configuration for sending message to AMQP server. If such configuration is present a message will be sent to the configured server.

##### AMQP

* *url*: Url to the amqp server e.g. amqp://guest:guest@localhost:5672/vhost **Mandatory**.
* *exchange*: The name of the exchange to post to **Mandatory**.
* *delay*: message send delay in seconds.
* *routingKey*: The routing key for posting the message.
* *body*: Payload of the message. It allows vars.
* *bodyAppend*: Text or JSON to be appended to the body. It allows vars.
* *contentType*: MIME content type.
* *contentEncoding*: MIME content encoding.
* *priority*: Priority from 0 to 9.
* *correlationId*: Correlation identifier.
* *replyTo*: Address to to reply to (ex: RPC).
* *expiration*: Message expiration spec.
* *messageId*: Message identifier.
* *timestamp*: Message timestamp.
* *type*: Message type name.
* *userId*: Creating user id - ex: "guest".
* *appId*: Creating application id.

#### Control

Expand All @@ -223,10 +264,12 @@ Request data:
- request.cookie."*key*"
- request.url
- request.body
- response.body
- request.url."regex to match value"
- request.body."regex to match value"
- response.body."regex to match value"

> Regex: The regex should contain a group named **value** which will be matched and its value will be returned. E.g. if we want to match the id from this url **`/your/path/4`** the regex should look like **`/your/path/(?P<value>\\d+)`**. Note that in *golang* the named regex group match need to contain a **P** symbol after the question mark. The regex should be prefixed either with **request.url.** or **request.body.** considering your input.
> Regex: The regex should contain a group named **value** which will be matched and its value will be returned. E.g. if we want to match the id from this url **`/your/path/4`** the regex should look like **`/your/path/(?P<value>\\d+)`**. Note that in *golang* the named regex group match need to contain a **P** symbol after the question mark. The regex should be prefixed either with **request.url.**, **request.body.** or **response.body.** considering your input.

Fake data:
Expand Down
96 changes: 96 additions & 0 deletions amqp/message_sender.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package amqp

import (
"log"
"time"

"fmt"

"github.com/jmartin82/mmock/definition"
"github.com/jmartin82/mmock/parse"
"github.com/streadway/amqp"
)

//MessageSender sends message to RabbitMQ
type MessageSender struct {
Parser parse.ResponseParser
}

//Send message to rabbitMQ if needed
func (msender MessageSender) Send(per *definition.Persist, req *definition.Request, res *definition.Response) bool {
if per.AMQP.URL == "" {
return true
}

per.AMQP.Body = msender.Parser.ParseBody(req, res, per.AMQP.Body, per.AMQP.BodyAppend)

if per.AMQP.Delay > 0 {
log.Printf("Adding a delay before sending message")
time.Sleep(time.Duration(per.AMQP.Delay) * time.Second)
}

return sendMessage(per.AMQP, res)
}

//NewMessageSender creates a new MessageSender
func NewMessageSender(parser parse.ResponseParser) *MessageSender {
result := MessageSender{Parser: parser}
return &result
}

func sendMessage(publishInfo definition.AMQPPublishing, res *definition.Response) bool {
conn, err := amqp.Dial(publishInfo.URL)

if hasError(err, "Failed to connect to RabbitMQ", res) {
return false
}
defer conn.Close()

ch, err := conn.Channel()
if hasError(err, "Failed to open a channel", res) {
return false
}
defer ch.Close()

err = ch.Publish(
publishInfo.Exchange, // exchange
publishInfo.RoutingKey, // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
Body: []byte(publishInfo.Body),
ContentType: publishInfo.ContentType,
ContentEncoding: publishInfo.ContentEncoding,
Priority: publishInfo.Priority,
CorrelationId: publishInfo.CorrelationID,
ReplyTo: publishInfo.ReplyTo,
Expiration: publishInfo.Expiration,
MessageId: publishInfo.Expiration,
Timestamp: publishInfo.Timestamp,
Type: publishInfo.Type,
UserId: publishInfo.UserID,
AppId: publishInfo.AppID,
DeliveryMode: 2,
})

if hasError(err, "Failed to publish a message", res) {
return false
}
log.Printf(" [x] Sent %s", publishInfo.Body)
return true
}

func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
}
}

func hasError(err error, msg string, res *definition.Response) bool {
if err != nil {
log.Print(err)
res.Body = fmt.Errorf("%s: %s", msg, err).Error()
res.StatusCode = 500
}
return err != nil
}
9 changes: 9 additions & 0 deletions amqp/sender.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package amqp

import "github.com/jmartin82/mmock/definition"

//Sender sends messages to AMQP server
type Sender interface {
//Send sends to amqp
Send(per *definition.Persist, req *definition.Request, res *definition.Response) bool
}
26 changes: 26 additions & 0 deletions config/amqp-post.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"request": {
"method": "POST",
"path": "/amqp/*",
"body": "*"
},
"response": {
"statusCode": 202,
"headers": {
"Content-Type":["application/json"]
},
"body": "{{ request.body }}",
"bodyAppend": "{ \"id\": {{request.url./amqp/(?P<value>\\d+)}}, \"city\": \"{{ fake.City }}\" }"
},
"persist" : {
"name" : "/user-{{request.url./amqp/(?P<value>\\d+)}}.json",
"amqp": {
"url": "amqp://guest:guest@localhost:5672/myVHost",
"body": "{{ response.body }}",
"delay": 2,
"exchange": "myExchange",
"type": "MockType",
"correlationId": "9782b88f-0c6e-4879-8c23-4699785e6a95"
}
}
}
Loading

0 comments on commit f111158

Please sign in to comment.