You can use several types of extensions to add dynamic content to response headers and response body.
For example, if you create a response with body { "Number": "{{randomInteger}}", "Date": "{{httpNow}}" } it will be rendered as { "Number": "165007562", "Date": "Sat, 02 Apr 2017 18:41:35 GMT" }
Below you will find all available extensions.
For a basic introduction to dynamic responses with examples have a look at Dynamic responses tutorial.
Save your data in trafficparrot-x.y.z/data/data.csv in CSV format.
To load the data in the response use {{csvDataFile 'row,col'}} in the response definition.
For example, {{csvDataFile '2,4'}} will read file trafficparrot-x.y.z/data/data.csv then load the 2nd row in the file and then lookup the 4th column value.
For more advanced CSV data loading, you can put your CSV file with any name in the trafficparrot-x.y.z/data directory.
To select data from a CSV use {{select 'a from x.csv where b equals' value}} in the response definition.
For example, {{select 'title from jobs.csv where name equals' 'example' }} will read the file trafficparrot-x.y.z/data/jobs.csv then load the value of the column title where the value of the column name is equal to the value example.
The value parameter passed to the {{select}} can be a dynamic value that is taken from the request e.g. xPath or jsonPath. For example:
{{select 'Age from UserData.csv where Username equals' (xPath request.body '//User/Username/text()')}}
You can enable the "select" helper CSV file indexing and caching for performance improvement by setting the property:
trafficparrot.virtualservice.handlebars.select.indexAndCacheCsvFiles=true
After loading a value from a CSV file, you can include it in an arithmetic operation to compute a value. For example:
{ {{#with (jsonPath request.body '$.id') as |id|}} "id": {{ id }}, "total": {{ math (select 'quantity from sales.csv where id equals' id) '*' (select 'price from sales.csv where id equals' id) }} {{/with}} }
To generate a random value in the response use one of the following:
To use HTTP request attributes in the response:
* Implemented with ResponseTemplateTransformer
To use JMS request message attributes in the JMS response message:
To use Native IBM® MQ request message attributes in the IBM® MQ response message:
To use request file attributes in the response file:
To use gRPC request attributes in the response:
All of jknack Handlebars string helpers are available for use in both HTTP and JMS response headers and body.
The math helper can be used to perform arithmetic operations:
The following additional helpers are available in both HTTP, JMS and File response templates.
You can use it to extract a value from a field using an XPath.
Syntax:{{xPath sourceAttribute 'TheXPathValue'}}For example, if you use the following handlebar in a HTTP response body:
Request foobar value was: {{xPath request.body '/foo/bar/text()'}}and then send a HTTP request:
<foo><bar>Long live mocking!</bar></foo>you will see a HTTP response:
Request foobar value was: Long live mocking!
You can use it to extract a list of nodes from a field using an XPath. Can be combined with loop iterators to transform a request body.
Syntax:{{xPathList sourceAttribute 'TheXPathValue'}}For example, if you use the following handlebar in a HTTP response body:
<items> {{#each (xPathList request.body '/request/items') }} <item id="{{ xPath this '/item/@id' }}" status="OK">{{ xPath this '/item/text()' }}</item> {{/each}} </items>and send a HTTP request:
<request> <items> <item id="one" ignored="any">body1</item> <item id="two" ignored="any">body2</item> </items> </request>you will see a HTTP response:
<items> <item id="one" status="OK">body1</item> <item id="two" status="OK">body2</item> </items>
You can use it to extract a value from a field using an JsonPath.
Syntax:{{jsonPath sourceAttribute 'TheJsonPathValue'}}For example, if you use the following handlebar in a HTTP response body:
Request foobar value was: {{jsonPath request.body '$.foo.bar'}}and then send a HTTP request:
{"foo":{"bar":"Long live mocking!"}}you will see a HTTP response:
Request foobar value was: Long live mocking!
You can use it to extract a list of values from a field using an JsonPath. Can be combined with loop iterators to transform a request body.
Syntax:{{jsonPathList sourceAttribute 'TheJsonPathValue'}}For example, if you use the following handlebar in a HTTP response body:
[ {{#each (jsonPathList request.body '$.items') }} { id: {{ jsonPath this '$.id' }}", status: "OK" }{{#unless @last}},{{/unless}} {{/each}} ]and send a HTTP request:
{ items: [ { id: 1234, ignored: "any" }, { id: 1235, ignored: "any" } ] }you will see a HTTP response:
[ { id: 1234, status: "OK" }, { id: 1235, status: "OK" } ]
You can use it to extract a value from a field using a single regular expression capturing group.
Syntax:{{regex sourceAttribute 'TheRegex(.*)Value'}}For example, if you use the following handlebar in a HTTP response body:
Request value was: {{regex request.body '.+? ([0-9A-Za-z]+Camel-[0-9A-Za-z]+).*'}}and then send a HTTP request:
before 001116059c5549a0tOACamel-116059c554a20tOH afteryou will see a HTTP response:
Request value was: 001116059c5549a0tOACamel-116059c554a20tOH
You can call helpers inside other helpers. You just have to wrap them in parenthesis.
For example, if you use the following handlebar in a HTTP response body:Bar was equal to xxx: {{#equal (jsonPath request.body '$.foo.bar') 'xxx'}}true{{else}}false{{/equal}}'and then send a HTTP request:
{"foo":{"bar":"xxx"}}you will see a HTTP response:
Bar was equal to xxx: true
<requestHeaders> {{#each request.headers}} <name>{{@key}}</name> <value>{{this}}</value> {{/each}} </requestHeaders>
{{#with (jsonPath request.body '$.internalCode') as |internalCode|}} "internalCode": {{ internalCode }}, "isoCountryCode": "{{select 'isoCountryCode from isoCountryCodes.csv where internalCode equals' internalCode }}" {{/with}}
{{#if request.query.password}} Password was present in the request. {{else}} Please provide a password! {{/if}}
You can use it to compare two values.
Syntax:{{#equal attributeValue1 attributeValue2}} result if true {{else}} result if false {{/equal}}For example, if you use the following handlebar:
Is user Bob? {{#equal request.query.user 'Bob'}} Yes! {{else}} No! {{/equal}}
You can use it to see if a number is even or odd.
Syntax:{{#ifEven attributeValue}} result if true {{else}} result if false {{/equal}}For example, use the following handlebar to see if the number of order items in the request body (which is json) is odd or even:
{{#ifEven (jsonPath request.body '$.orderItem')}}number is even{{else}}number is odd{{/ifEven}}
In order to create your own HTTP extensions basic Java programming language knowledge is required. We use Java to write extensions because it is a simple to use and yet very powerful language. It is also the most popular programming language in the world. If this is not something you can do, we can create the extensions for you instead.
Traffic Parrot allows for Handlebars notation in the responses. They are typically used to add dynamic content to the responses. They can be used for both HTTP and JMS.
For example, all of these existing extensions are handlebar helpers:package com.trafficparrot.sdk.example.http.template; import com.github.jknack.handlebars.Handlebars; import com.github.jknack.handlebars.Options; import com.trafficparrot.sdk.handlebars.TrafficParrotHelper; import java.io.IOException; import java.util.Random; public class RandomInteger extends TrafficParrotHelper<Object> { @Override public Handlebars.SafeString doApply(Object context, Options options) throws IOException { return new Handlebars.SafeString(String.valueOf(new Random().nextInt())); } }
Traffic Parrot allows for creating custom HTTP response transformers. They allow for altering any part of the HTTP response in any way you like, and have access to the request data.
If you would like to add a new HTTP response transformer, have a look at the following example:package com.trafficparrot.sdk.example.http.responsetransformer; import com.github.tomakehurst.wiremock.common.FileSource; import com.github.tomakehurst.wiremock.extension.Parameters; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.Response; import com.trafficparrot.sdk.http.HttpResponseTransformer; import java.util.Random; import static com.github.tomakehurst.wiremock.http.HttpHeaders.noHeaders; public class RandomServerFailure extends HttpResponseTransformer { @Override protected Response doTransform(Request request, Response response, FileSource fileSource, Parameters parameters) { if (new Random().nextBoolean()) { return Response.Builder .like(response) .but() .status(500) .headers(noHeaders()) .body("Server error!") .build(); } else { return response; } } @Override public String getName() { return getClass().getSimpleName(); } }
Traffic Parrot allows for creating custom JMS response transformers. They allow for altering any part of the JMS response message in any way you like, and have access to the request data.
If you would like to add a new JMS response transformer, have a look at the following example:package com.trafficparrot.sdk.example.jms; import com.github.tomakehurst.wiremock.common.Json; import com.trafficparrot.sdk.jms.Destination; import com.trafficparrot.sdk.jms.JmsResponse; import com.trafficparrot.sdk.jms.JmsResponseBuilder; import com.trafficparrot.sdk.jms.TextJmsResponseTransformer; import javax.jms.JMSException; import javax.jms.TextMessage; public class ChooseResponseQueueBasedOnRequestMessageBody extends TextJmsResponseTransformer { @Override protected JmsResponse doTransform(Destination requestDestination, TextMessage requestMessage, JmsResponse response) throws JMSException { Req payment = Json.read(requestMessage.getText(), Req.class); String newDestinationName; if ("foo".equals(payment.requestField)) { newDestinationName = "response_queue_1"; } else if ("bar".equals(payment.requestField)) { newDestinationName = "response_queue_2"; } else { logger.error("Destination not configured for req.requestField '" + payment.requestField + "'"); throw new UnsupportedOperationException(payment.requestField); } logger.info("Redirecting message to " + newDestinationName); return new JmsResponseBuilder() .like(response) .withDestination(new Destination(newDestinationName, response.destination.type)) .build(); } } class Req { public String requestField; }
Traffic Parrot allows for creating custom Native IBM MQ response transformers. They allow for altering any part of the Native IBM MQ response message in any way you like, and have access to the request data.
If you would like to add a new Native IBM MQ response transformer, have a look at the following example:package com.trafficparrot.sdk.example.nativeibmmq; import com.ibm.mq.MQMessage; import com.trafficparrot.sdk.ibmmq.IbmMqResponse; import com.trafficparrot.sdk.ibmmq.IbmMqResponseTransformer; import com.trafficparrot.sdk.ibmmq.SdkIbmMqUtils; import com.trafficparrot.sdk.jms.Destination; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.xml.sax.SAXException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import java.io.ByteArrayInputStream; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Optional; import java.util.Properties; import static com.trafficparrot.sdk.PropertiesHelper.readPropertiesFile; import static com.trafficparrot.sdk.ibmmq.DestinationLookup.USE_MAPPING_RESPONSE_DESTINATION; import static com.trafficparrot.sdk.ibmmq.SdkIbmMqUtils.readMessageBodySkipHeaders; import static com.trafficparrot.sdk.ibmmq.SdkIbmMqUtils.setMessageBody; public class TransformAndProxyRequestMessage extends IbmMqResponseTransformer { private static final Logger LOGGER = LoggerFactory.getLogger(TransformAndProxyRequestMessage.class); private static final long FOUR_YEARS_IN_MILLIS = 4L * 365 * 24 * 60 * 60 * 1000; public static final String DEFAULT_REAL_REQUEST_QUEUE_KEY = "default.real.request.queue"; public static final String SIT1_REAL_REQUEST_QUEUE_KEY = "sit1.real.request.queue"; public static final String SIT1_ENVIRONMENT_IDENTIFIER_KEY = "sit1.environment.identifier"; public static final String TRANSFORM_AND_PROXY_PROPERTIES_FILENAME = "transform-and-proxy.properties"; // You can put this file in the main Traffic Parrot directory where all the other properties files are private final Properties properties = readPropertiesFile(TRANSFORM_AND_PROXY_PROPERTIES_FILENAME); @Override @SuppressWarnings("UnnecessaryLocalVariable") protected IbmMqResponse doTransform(Destination requestDestination, MQMessage requestMessage, IbmMqResponse mappingResponse) throws Exception { MQMessage proxyResponseMessage = requestMessage; setReplyToQ(proxyResponseMessage); resetMqPropertiesThatAreSetByMq(proxyResponseMessage); moveDatesInBody(requestMessage, proxyResponseMessage); return new IbmMqResponse(mappingResponse.destination, proxyResponseMessage, mappingResponse.fixedDelayMilliseconds, Optional.of(USE_MAPPING_RESPONSE_DESTINATION)); } private void setReplyToQ(MQMessage proxiedMessage) { proxiedMessage.replyToQueueName = getReplyToQueue(proxiedMessage); } private String getReplyToQueue(MQMessage requestMessage) { String messageAsString = SdkIbmMqUtils.mqMessageToString(requestMessage); LOGGER.info("Transformer message converted to string: " + messageAsString); String sitEnvironmentId = properties.getProperty(SIT1_ENVIRONMENT_IDENTIFIER_KEY); if (messageAsString.contains(sitEnvironmentId)) { String sit1RealRequestQueue = properties.getProperty(SIT1_REAL_REQUEST_QUEUE_KEY); LOGGER.info("Transformer using " + sit1RealRequestQueue); return sit1RealRequestQueue; } else { String defaultRealRequestQueue = properties.getProperty(DEFAULT_REAL_REQUEST_QUEUE_KEY); LOGGER.info("Transformer using " + defaultRealRequestQueue); return defaultRealRequestQueue; } } private void moveDatesInBody(MQMessage requestMessage, MQMessage proxyResponseMessage) throws IOException, ParserConfigurationException, XPathExpressionException, SAXException, ParseException { // parse the XML request body String originalMessageBody = readMessageBodySkipHeaders(requestMessage); DocumentBuilderFactory abstractFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder factory = abstractFactory.newDocumentBuilder(); Document doc = factory.parse(new ByteArrayInputStream(originalMessageBody.getBytes())); String requestDateString = XPathFactory.newInstance().newXPath().compile("/transactions/get/startDate/text()").evaluate(doc); SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); Date requestDate = format.parse(requestDateString); // calculate the new date (in the past) Date movedDate = new Date(requestDate.getTime() - FOUR_YEARS_IN_MILLIS); String movedDateString = format.format(movedDate); // set new date in the response String newMessageBody = originalMessageBody.replace(requestDateString, movedDateString); setMessageBody(proxyResponseMessage, newMessageBody); } private void resetMqPropertiesThatAreSetByMq(MQMessage proxyResponseMessage) { proxyResponseMessage.putDateTime = null; proxyResponseMessage.putApplicationName = "Traffic Parrot transformer proxy " + getClass().getSimpleName(); } }
{ "mappingId" : "1001", "request" : { "destination" : { "name" : "PROXY.REQUEST.QUEUE", "type" : "QUEUE" }, "bodyMatcher" : { "anything" : "anything" } }, "response" : { "destination" : { "name" : "REAL.REQUEST.QUEUE", "type" : "QUEUE" }, "ibmMqResponseTransformerClassName" : "com.trafficparrot.sdk.example.nativeibmmq.TransformAndProxyRequestMessage" }, "receiveThreads" : 1, "sendThreads" : 1 }
This documentation is for an old version of Traffic Parrot. There is a more recent Traffic Parrot version available for download at trafficparrot.com