The mode
defines the behavior of the proxy.
proxyOnce
- ensures that the same request (defined
by the predicates) is never proxied twice. mountebank only records
one response for each request, and automatically replays that response the next
time the request predicates match.proxyAlways
- All calls will be proxied, allowing multiple responses
to be saved for the same logical request. You have to explicitly tell mountebank
to replay those responses.proxyTransparent
- All calls will be proxied, but will not be recorded.The default proxyOnce
mode is simpler; it always creates a new
stub in front of the stub with the proxy response, relying on mountebank's first-match
policy to automatically replay the saved response in the new stub the next time a request
matches the predicates. Imagine the following stubs
array, set by us when we
create the imposter:
POST /imposters HTTP/1.1
Host: localhost:<%= port %>
Content-Type: application/json
{
"port": 2001,
"protocol": "http",
"stubs": [{ "responses": [{ "is": { "body": "Downstream service response" } }] }]
}
POST /imposters HTTP/1.1
Host: localhost:<%= port %>
Content-Type: application/json
{
"port": 2000,
"protocol": "http",
"stubs": [{
"responses": [{
"proxy": {
"to": "http://origin-server.com ",
"mode": "proxyOnce",
"predicateGenerators": [{ "matches": { "path": true } }]
}
}]
}]}
When we issue an HTTP call to /test
, the stub will proxy all of the request
details to http://origin-server.com/test, and save off the response in a new stub in front of the
stub with the proxy
response:
GET /test HTTP/1.1
Host: localhost:2000
DELETE /imposters/2001 HTTP/1.1
Host: localhost:<%= port %>
Accept: application/json
DELETE /imposters/2000 HTTP/1.1
Host: localhost:<%= port %>
Accept: application/json
{
"stubs": [
{
"predicates": [{ "deepEquals": { "path": "/test" } } ],
"responses": [{
"is": {
"body": "Downstream service response",
...
}
}]
},
{
"responses": [{
"proxy": {
"to": "http://localhost:2001",
"mode": "proxyOnce",
"predicateGenerators": [{ "matches": { "path": true } }]
}
}]
}
]}
Because of mountebank's first-match policy on stubs, the next
time the imposter receives a request to /test
, the saved predicates on the
newly created stub will match, and the recorded response will be replayed. If
the imposter receives a call to /different-path
, then it will proxy again,
creating a new stub, because the path
is different.
The proxyAlways
mode saves stubs behind the proxy
stub. This allows you to record a richer set of interactions with the origin server
because it can record multiple responses for the same logical request (as defined by
the predicates). The consequence is that it requires you to save off the imposter representation
and remove or reorder the proxy
stubs to replay those interactions. The easiest
way to do that is with the mb replay
command.
Let's say you had the following stubs
array:
POST /imposters HTTP/1.1
Host: localhost:<%= port %>
Content-Type: application/json
{
"port": 4001,
"protocol": "http",
"stubs": [{
"responses": [{
"inject": "function (req, state) { state.count = state.count || 0; state.count +=1; return { body: 'Request number ' + state.count }; }"
}]
}]
}
POST /imposters HTTP/1.1
Host: localhost:<%= port %>
Content-Type: application/json
{
"port": 4000,
"protocol": "http",
"stubs": [{
"responses": [{
"proxy": {
"to": "http://origin-server.com ",
"mode": "proxyAlways",
"predicateGenerators": [{ "matches": { "path": true } }]
}
}]
}]}
Every time we send a request to /test
, it will
be proxied to http://origin-server.com/test. The first time we do that, just like the
proxyOnce
example, it will add a new stub. The difference is that the
new stub will be created at the end of the array:
GET /test HTTP/1.1
Host: localhost:4000
GET /imposters/4000 HTTP/1.1
Host: localhost:<%= port %>
Accept: application/json
{
"stubs": [
{
"responses": [
{
"proxy": {
"to": "http://origin-server.com ",
"mode": "proxyAlways",
"predicateGenerators": [{ "matches": { "path": true } }]
}
}
]
},
{
"predicates": [{ "deepEquals": { "path": "/test" } }],
"responses": [{
"is": {
"body": "Request number 1",
...
}
}]
}
]}
Because the proxy occurs before the new stub, the next request will continue to use
the proxy
response. This allow us to capture multiple responses for the
same logical request. If we once again send a request to the /test
path,
since a stub with the predicate already exists, the response will be added to the
existing stub:
GET /test HTTP/1.1
Host: localhost:4000
GET /imposters/4000 HTTP/1.1
Host: localhost:<%= port %>
Accept: application/json
{
"stubs": [
{
"responses": [
{
"proxy": {
"to": "http://origin-server.com ",
"mode": "proxyAlways",
"predicateGenerators": [{ "matches": { "path": true } }]
}
}
]
},
{
"predicates": [{ "deepEquals": { "path": "/test" } }],
"responses": [
{
"is": {
"body": "Request number 1",
...
}
},
{
"is": {
"body": "Request number 2",
...
}
}
]
}
]}
This configuration allows you to capture as rich a set of data as your downstream
system provides and play it back with the appropriate predicates. The only additional
complexity with proxyAlways
is that you have to explicitly switch to
replay mode. Conceptually, replaying is as simple as removing the proxies. The easiest
way to do that is by using the mb replay
command, passing in the port if you're using a nonstandard port for running mb
. If,
for example, we had previously started mountebank on port 3535,
we could switch to replay mode with the following call:
mb replay --port 3535
Now if we look at the imposter configuration, you'll notice that the proxy has been removed. Only the saved responses will be used.
GET /imposters/4000?replayable=true&removeProxies=true
Host: localhost: <%= port %>
Accept: application/json
HTTP/1.1 200 OK
Vary: Accept
Content-Type: application/json; charset=utf-8
Content-Length: 896
Date: Sat, 20 May 2017 14:43:12 GMT
Connection: keep-alive
{
"protocol": "http",
"port": 4000,
"recordRequests": false,
"stubs": [{
"predicates": [{ "deepEquals": { "path": "/test" } }],
"responses": [
{
"is": {
"statusCode": 200,
"headers": {
"Connection": "close",
"Date": "Sat, 20 May 2017 14:43:12 GMT ",
"Transfer-Encoding": "chunked"
},
"body": "Request number 1",
"_mode": "text"
}
},
{
"is": {
"statusCode": 200,
"headers": {
"Connection": "close",
"Date": "Sat, 20 May 2017 14:43:12 GMT ",
"Transfer-Encoding": "chunked"
},
"body": "Request number 2",
"_mode": "text"
}
}
]
}]
}
DELETE /imposters/4000 HTTP/1.1
Host: localhost:<%= port %>
Accept: application/json
POST /imposters HTTP/1.1
Host: localhost:<%= port %>
Accept: application/json
{
"protocol": "http",
"port": 4000,
"stubs": [
{
"predicates": [
{
"deepEquals": {
"path": "/test"
}
}
],
"responses": [
{
"is": {
"statusCode": 200,
"headers": {
"Connection": "close",
"Date": "Fri, 19 May 2017 01:09:42 GMT",
"Transfer-Encoding": "chunked"
},
"body": "Request number 1",
"_mode": "text",
"_proxyResponseTime": 14
}
},
{
"is": {
"statusCode": 200,
"headers": {
"Connection": "close",
"Date": "Fri, 19 May 2017 01:09:42 GMT",
"Transfer-Encoding": "chunked"
},
"body": "Request number 2",
"_mode": "text",
"_proxyResponseTime": 4
}
}
]
}
]
}
GET /imposters/4000 HTTP/1.1
Host: localhost:<%= port %>
Accept: application/json
{
"stubs": [
{
"predicates": [{ "deepEquals": { "path": "/test" } }],
"responses": [
{
"is": {
"body": "Request number 1",
...
}
},
{
"is": {
"body": "Request number 2",
...
}
}
]
}
]}
DELETE /imposters/4000 HTTP/1.1
Host: localhost:<%= port %>
Accept: application/json
DELETE /imposters/4001 HTTP/1.1
Host: localhost:<%= port %>
Accept: application/json
The proxyTransparent
mode does not save any responses and simply proxies the
requests transparently.
Let's say you had the following stubs
array:
POST /imposters HTTP/1.1
Host: localhost:<%= port %>
Content-Type: application/json
{
"port": 4002,
"protocol": "http"
}
POST /imposters HTTP/1.1
Host: localhost:<%= port %>
Content-Type: application/json
{
"port": 5050,
"protocol": "http",
"stubs": [{
"responses": [{
"proxy": {
"to": "http://origin-server.com ",
"mode": "proxyTransparent"
}
}]
}]}
Every time we send a request to /test
, it will
be proxied to http://origin-server.com/test. There will be no stub created off the back of the request.
GET /test HTTP/1.1
Host: localhost:5050
GET /imposters/5050 HTTP/1.1
Host: localhost:<%= port %>
Accept: application/json
{
"stubs": [
{
"responses": [
{
"proxy": {
"to": "http://origin-server.com ",
"mode": "proxyTransparent"
}
}
]
}
]}
DELETE /imposters/4002 HTTP/1.1
Host: localhost:<%= port %>
Accept: application/json
DELETE /imposters/5050 HTTP/1.1
Host: localhost:<%= port %>
Accept: application/json