preloader

Dispatcher & dispatching rules

🗓️ Last updated on December 26, 2022 | 9 | Improve this page

Introduction

In order to provide smart mocks, Microcks is using Dispatcher and Dispatching Rules to find the most appropriate response to return when receiving a request.

The Dispatcher is defining a routing logic for mocks, that specifies the kind of elements of an incoming request will be examined to find a match. The Dispatcher Rules refines those elements as well as the matching rule to find the correct response.

By default, Microcks looks at the variable parts between the different examples of the same operation when importing a new Service or API and infers those two elements. Then, based on those elements, it computes some fingerprint that allows unique identification for every request/response pair. That’s what we called the Dispatch Criteria.

When using this default and receiving an incoming request on a mock endpoint, Microcks will re-apply the Service or API Dispatching Rules to compute the fingerprint again and find the appropriate response matching this criteria.

However, you may need more than this inferred logic in some situations. Microcks got you covered! It allows you to configure and use advanced dispatchers and associated rules, providing some advanced dispatchers to implement your own business rules or constraints.

Inferred dispatchers

As a reminder on default, inferred dispatchers: you may find URI_PARTS, URI_PARAMS, URI_ELEMENTS, QUERY_ARGS, QUERY_MATCH or SCRIPT. The first three are usually found when using Postman or OpenAPI as a contract artifact ; they are deduced from the paths and contract elements. The last two are usually found when using SoapUI as a contract artifact.

Here are below some explanations on these dispatchers and associated dispatching rules syntax:

Dispatcher
 ExplanationsRules syntax
URI_PARTSInferred when a Service or API operation has only path parametersPath variables name separated by a &&. Example: for a /blog/post/{year}/{month} path, rule is year && month
URI_PARAMSInferred when a Service or API operation has only query parametersQuery variables name separated by a &&. Example: for a /search?status={s}&query={q} operation, rule is status && query
URI_ELEMENTSInferred when a Service or API operation has both path and query parametersPath variables name separated by a && then ?? followed by query variables name separated by a &&. Example: for a /v2/pet/{petId}?user_key={k}, rule is petId ?? user_key
QUERY_ARGSInfered when a GraphQL API operation has only primitive types argumentsVariables name separated by a &&. Example: for a GraphQL mutation mutation AddStars($filmId: String, $number: Int) {...}, rule is filmId && number
QUERY_MATCHExtracted from SoapUI project. Defines a XPath matching evaluation: extracted result from input query should match a response nameExample: for a Hello SOAP Service that extracts the sayHello element value for find a greeting rule is declare namespace ser='http://www.example.com/hello'; //ser:sayHelloResponse/sayHello.

XPath functions can also be used here for evaluation - eg. something like: concat(//ser:sayHello/title/text(),' ',//ser:sayHello/name/text())
SCRIPTExtracted from SoapUI project. Defines a Groovy script evaluation: result of type Sring should match a response nameSee below section on script dispatcher .

Dispatching rules override

Changing Dispatching Rules or even the Dispatcher can be done by different ways:

Advanced dispatchers and rules

JSON BODY dispatcher

The JSON_BODY dispatching strategy allows specifying a dispatching rule that will analyse the request payload in order to find a matching response. In order to specify such an expression you can use the help vertical right section of the page that will provide examples and copy/paste shortcuts.

The dispatching rules of JSON_BODY dispatcher are always expressed using a JSON payload with 3 properties:

  • exp is the expression to evaluate against the request body. It is indeed a JSON Pointer expression. We already use this expression language in Templating features . From the evaluation of this expression, we’ll get a value. Here /country denotes the country field of incoming request.
  • op is the operator to apply. Different operators are available like equals, range, regexp, size and presence,
  • cases are a number of cases where keys are values to compare to extracted value from incoming request.

Depending on the operator applied, the cases may have different specification formats.

Operator
Cases syntax
 Comments
equals"<value>": "<response>"A case named default is used as default option
range[<min>;<max>]: "<response>" Bracket side matter: [ means incluse, ] means exclusive for a left bracket. A case named default is used as default option
size"[<min>;<max>]": "<response>"Size of an array property. Brackets must be inclusive. A case named default is used as default option
regexp"<posix regexp>": "<response>"Regular expression applied to value. A case named default is used as default option
presence"found": "<response>"Check the presence/absence of a property. 2 mandatory cases: found and default

Say we’ve got this Beer API allowing to record a new beer in our own catalog. We have a POST method that allows to create new beer resources and we want to make the difference between 2 cases: the Accepted and the Not accepted responses. So we have to start describing the 2 examples into our API contract. You’ll notice in the capture below that:

  • Dispatcher and Dispatching Rules are empty. That means that you’ll get the first found response when invoking the mock, no matter the request.
  • We have used Templating features to make the response content more dynamic. So the {{ }} notation within response body.
image

Our business constraints here is to only accept beers coming from Belgium 🇧🇪, otherwise we have to return the Not accepted response. We may edit our dispatching rule to use the equals operator and save, and we can check this rule is applied to our operation. This override of rule will be persisted into Microcks and will survive future discoveries and refreshed of this API version.

image

💡 We recommend having an in-depth look at the exemple provided on the page to fully understand the power of different options.

Illustration

Given the templated responses and the above dispatching rule evaluating the body of incoming requests, we can now test our mock.

Let start by creating a new beer coming from Belgium:

$ curl -X POST http://microcks.example.com/rest/Beer+Catalog+API/1.0/beer \
    -H 'Content-type: application/json' \
    -d '{"name": "Abbey Brune", "country": "Belgium", "type": "Brown ale", "rating": 4.2, "references": [ { "referenceId": 1234 }, { "referenceId": 5678 } ]}'
    
{
  "name": "Abbey Brune",
  "country": "Belgium",
  "type": "Brown ale",
  "rating": 4.2,
  "references": [
    { "referenceId": 1234 },
    { "referenceId": 5678 }
  ]
}

It is a success as the country has the Belgium value and the Accepted response is returned. Templates in this response are evaluated regarding request content.

Now let’s try with a German beer… You’ll see that the Not accepted response is matched (look also at the return code) and adapted regarding incoming request:

$ curl -X POST http://microcks.example.com/rest/Beer+Catalog+API/1.0/beer \
    -H 'Content-type: application/json' \
    -d '{"name": "Spaten Oktoberfiest", "country": "Germany", "type": "Amber", "rating": 2.8, "references": []}'

< HTTP/1.1 406 
{
  "error": "Not accepted",
  "message": "Germany origin country is forbiden"
}

FALLBACK dispatcher

Another useful advanced dispatching strategy introduced in the Advanced Dispatching and Constraints for mocks blog post, is the FALLBACK strategy. As you may have guessed by its name, it behaves like a try-catch wrapping block in programming: it will try applying a first dispatcher with its own rule and if it find nothings it will default to a fallback response. This will allow you to define a default response event of the incoming requests does not match any dispatching criteria.

The dispatching rules of FALLBACK dispatcher are expressed using a JSON payload with 3 properties:

  • dispatcher is the original dispatching strategy you want to be applied at first. Valid values are all the other dispatching strategies,
  • dispatcherRules are the rules you want the original dispatcher to apply when looking for a response,
  • fallback is simply the name of the response to use as the fallback if nothing is found on first try.

Here’s below the sample that was introduced in afore mentioned blog post. In case of unknown region requested as a query parameters on a Weather Forecast API, we’ll fallback to an unknown response providing meaningful error message:

image

Illustration

Just issue a Http request with an unmanaged region like below:

$ curl 'https://microcks.apps.example.com/rest/WeatherForecast+API/1.0.0/forecast?region=center&apiKey=qwertyuiop' -k

Region is unknown. Choose in north, west, east or south.%

SCRIPT dispatcher

SCRIPT dispatchers are the most versatile and powerful to integrate custom dispatching logic in Microcks. When using such a Dispatcher, Dispatching Rule is simply a Groovy script that is evaluated and has to return the name of mock response.

Before actualy evaluating the script, Microcks builds a runtime context where elements from incoming requests are made available. Therefore, you may have access to different objects from the script.

Object
Description
mockRequestWrapper around incoming request that fullfill the contract of Soap UI mockRequest . Allows you to access body payload with requestContent, request headers with getRequestHeaders() or all others request elements with getRequest() that accesses underlying Java HttpServletRequest object
requestContextAllows you to access mock-request wide context for storing any kind of objects. Such context elements can ba later reused whne producing response content from templates
 logAccess to a logger with commons methods like debug(), info(), warn() or error(). Useful for troubleshooting. 

Common use-cases

Dispatch according a header value:

def headers = mockRequest.getRequestHeaders()
log.info("headers: " + headers)
if (headers.hasValues("testcase")) {
   def testCase = headers.get("testcase", "null")
   switch(testCase) {
      case "1":
         return "amount negativo";
      case "2":
         return "amount nullo";
      case "3":
         return "amount positivo";
      case "4":
         return "amount standard";
   }
}
return "amount standard"

Analyse XML body payload content:

import com.eviware.soapui.support.XmlHolder
def holder = new XmlHolder( mockRequest.requestContent )
def name = holder["//name"]

if (name == "Andrew"){
    return "Andrew Response"
} else if (name == "Karla"){
    return "Karla Response"
} else {
    return "World Response"
}

Analyse JSON body payload content and setting context:

log.info("request content: " + mockRequest.requestContent);
def json = new groovy.json.JsonSlurper().parseText(mockRequest.requestContent);
if (json.cars.Peugeot != null) {
   requestContext.brand = "Peugeot";
   log.info("Got Peugeot");
}
if (json.cars.Volvo != null) {
   requestContext.brand = "Volvo";
   log.info("Got Volvo");
}
return "Default"

Calling an external API (here the invocations metrics from Microcks in fact 😉) to use external information in dispatching logic:

def invJson = new URL("http://127.0.0.1:8080/api/metrics/invocations/OneApp%20Home/1.0.0").getText();
def inv = new groovy.json.JsonSlurper().parseText(invJson).dailyCount
log.info("daily invocation: " + inv)
[...]

Still Didn’t Find Your Answer?

Join our community and get the help you need. Engage with other members, ask questions, and share knowledge to resolve your queries and expand your understanding.

Join the community