Hoverfly middleware in Traffic Parrot

Part of Switching from another tool, the hub for every migration page.

« Back to documentation home

Coming from Hoverfly?

Traffic Parrot is a standalone mock and service virtualization server that is compatible with WireMock stub mappings. If you are coming from Hoverfly and rely on its Modify or Synthesize middleware to mutate or generate responses, this page maps that workflow onto Traffic Parrot.

Hoverfly's middleware modes run a language-agnostic executable: for each request/response pair, Hoverfly serialises the pair to JSON, feeds it to the middleware on stdin, and reads the mutated JSON back from stdout. Your middleware can be written in any runtime that can read stdin and write stdout: Python, Go, Node, a shell script, and so on.

Traffic Parrot matches that out-of-process middleware idea with the built-in external-process response transformer. It serialises a versioned JSON envelope (the matched request plus the response about to be sent) to your executable's stdin and applies the mutated response read back from its stdout. The External-process middleware reference in Dynamic Responses is the canonical, full description of the transformer; this page is the Hoverfly-migration view of it: the concept mapping, a copy-pasteable worked example, and the differences to expect.

Mapping Hoverfly concepts onto Traffic Parrot

The two designs share the same core: an external program is handed the HTTP exchange as JSON and returns a mutated exchange. The configuration model differs: Hoverfly enables middleware with a single global flag, while Traffic Parrot uses a two-layer model (a global opt-in, then a per-mapping opt-in). The table below translates each Hoverfly concept:

Hoverfly Traffic Parrot
Global -middleware <cmd> flag enables one middleware for the whole instance Two-layer model. Global opt-in in trafficparrot.properties: trafficparrot.http.externalprocess.enabled=true and a vetted trafficparrot.http.externalprocess.allowedDir. Per-mapping opt-in: list external-process in the response's transformers array and configure it under transformerParameters["external-process"] (executable, args, timeoutMs, onError).
Middleware reads the request/response pair as JSON on stdin and writes the mutated pair on stdout The versioned JSON envelope on stdin and stdout. meta.contractVersion is "1"; the envelope carries the matched request as read-only context and the response for the middleware to mutate.
A Modify middleware changes the response that is about to be returned The external-process response transformer runs on the matched stub's response, so the middleware mutates the response section (status, headers, body) before it is sent, the direct equivalent of Hoverfly's response-side Modify.
A Synthesize middleware constructs a response programmatically Stub a minimal response in the mapping (for example an empty body and a status), then let the middleware build the real response body, headers and status in the envelope it returns; the transformer applies whatever the middleware writes.

Enabling the feature

Because the middleware executable runs with the privileges of the Traffic Parrot process, the feature is off by default and is controlled by two properties in trafficparrot.properties. The External-process middleware reference covers them in full; in brief:

Property Default Description
trafficparrot.http.externalprocess.enabled false Set to true to activate the feature. While it is false, a mapping that lists external-process is inert: the response is returned unchanged and a warning is logged.
trafficparrot.http.externalprocess.allowedDir (empty) A single directory. Only an executable whose canonical (symlink-resolved) path is under this directory may be invoked. An empty value means nothing is allowed, even when the feature is enabled, so you must point it at a dedicated directory holding your vetted middleware.

A working example

This is the same uppercase-body middleware shown in the reference, presented as a complete copy-pasteable migration: the script, the properties, the mapping, and the round-trip.

1. The middleware script

Save this Python script as uppercase-body.py in your allowedDir. It reads the envelope from stdin, uppercases the response body when it is identity-encoded (leaving any base64 binary body untouched), stamps a marker header, and writes the whole envelope back to stdout:

#!/usr/bin/env python3
import json, sys

env = json.load(sys.stdin)
resp = env["response"]
if resp.get("bodyEncoding", "identity") == "identity":
    resp["body"] = resp.get("body", "").upper()
resp.setdefault("headers", {})["X-Transformed-By"] = ["uppercase-body.py"]
json.dump(env, sys.stdout)

Make the script executable, and make sure its canonical path resolves under allowedDir:

chmod +x /opt/tp-middleware/uppercase-body.py
2. Enable the feature

Turn on the global opt-in and point allowedDir at the directory that holds the script, in trafficparrot.properties:

trafficparrot.http.externalprocess.enabled=true
trafficparrot.http.externalprocess.allowedDir=/opt/tp-middleware
3. Configure the mapping

Opt the mapping in by listing external-process in the response's transformers array and configuring it under transformerParameters["external-process"]. Here the executable points at the script, with no extra args, a 5-second per-invocation timeout, and fail-open so a middleware failure falls back to the original response:

{
  "request": {
    "url": "/greeting",
    "method": "GET"
  },
  "response": {
    "status": 200,
    "body": "hello parrot",
    "headers": { "Content-Type": "text/plain" },
    "transformers": ["external-process"],
    "transformerParameters": {
      "external-process": {
        "executable": "/opt/tp-middleware/uppercase-body.py",
        "args": [],
        "timeoutMs": 5000,
        "onError": "fail-open"
      }
    }
  }
}
4. The round-trip

A request to the virtual service now returns the body uppercased by the middleware, with the X-Transformed-By header it stamped:

$ curl -i http://localhost:8081/greeting
HTTP/1.1 200 OK
Content-Type: text/plain
X-Transformed-By: uppercase-body.py

HELLO PARROT

The stub's hello parrot body was passed to the middleware on stdin and came back as HELLO PARROT on stdout, and the middleware added the X-Transformed-By response header. If the body had been base64-encoded binary, the script's bodyEncoding check would have passed it through untouched.

The JSON envelope

The full envelope schema (version 1) is documented in the External-process middleware reference. If you are porting a Hoverfly middleware, three migration-relevant points matter:

  • The envelope is versioned. Your middleware should check that meta.contractVersion == "1" and refuse an envelope whose major version it does not understand, the equivalent of guarding against a Hoverfly schema change.
  • Mutate the response section only (status, headers, body). The envelope also carries the matched request and a meta block, but any changes a middleware makes to meta or request are ignored in version 1: Traffic Parrot reads back only the returned response.
  • bodyEncoding is either "identity" (UTF-8 text) or "base64" (binary). A middleware that does not decode bodies should pass a base64 body through untouched, echoing back its bodyEncoding, exactly as the example script does.

Differences from Hoverfly in this release

The external-process transformer matches the heart of Hoverfly's middleware design. A few behaviours differ in this release; knowing them up front lets you plan a migration:

  • Response-only. Hoverfly middleware can mutate the request before matching; Traffic Parrot's transformer runs on the matched stub's response, so it mutates the response only. The envelope still carries the matched request as read-only context the middleware can read, but request mutation before matching is not supported yet.
  • One process per matched request. Like Hoverfly's per-pair invocation, Traffic Parrot spawns a fresh process for every matched request on a mapping that uses the transformer. The dominant cost is interpreter/runtime startup (roughly 30 to 50 ms for a Python script), so a lightweight script keeps the per-request overhead small; there is no long-lived middleware pool in this release.
  • HTTP only. The transformer applies to HTTP responses in this release. The envelope's meta.protocol discriminator (always "http" today) is reserved so the same middleware contract can extend to messaging protocols later.
  • Per-mapping opt-in, framed as a benefit. Hoverfly's -middleware flag is global: one middleware for the whole instance. Traffic Parrot opts in per mapping, so only the mappings that list external-process pay the spawn cost, and the rest of your stubs are unaffected.

Failure handling

The Failure handling section of the reference is the full description. In brief, four conditions count as a middleware failure: a non-zero exit code, a timeout (the process is forcibly killed at timeoutMs), malformed JSON on stdout, and a missing or disallowed executable (including a path that does not resolve under allowedDir). Each is logged, and the per-mapping onError setting decides what the client receives:

  • fail-open (the default) returns the original, untransformed response, so the failure is invisible to the client.
  • fail-closed returns HTTP 500, so a broken middleware surfaces as a failed request rather than silently passing the original response through.

Security and threat model

The Security and threat model section of the reference has the full guidance. The essentials a migrating user must know:

  • The feature is off by default and must be explicitly enabled.
  • Only an executable whose canonical path resolves under the operator-configured allowedDir may be invoked, so point it at a dedicated directory of vetted scripts and keep it write-protected from untrusted users.
  • The middleware runs as a native OS process with the privileges of the Traffic Parrot user and no sandbox. Run Traffic Parrot as an unprivileged OS user and apply operating-system resource limits (for example ulimit or cgroups) to bound a runaway middleware; Traffic Parrot does not impose these itself.

Next steps