Skip to content

Commit 3982cc5

Browse files
authored
docs(postserveaction): document remote mode for long-lived post-serve actions (#1219)
Add sections explaining the difference between local (subprocess per request) and remote (long-lived HTTP server) execution modes, including: - When to use each mode and why local does not scale under high load - Step-by-step guide: writing the server, payload format, configuring Hoverfly via flag and hoverctl, verifying registration via admin API - Minimal Python server example with inline comments - Docker Compose example with service hostname Closes #1188
1 parent 8b110ca commit 3982cc5

1 file changed

Lines changed: 148 additions & 0 deletions

File tree

docs/pages/keyconcepts/postserveaction.rst

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,152 @@ Ways to register a Post Serve Action
3939
}
4040
4141
42+
Choosing Between Local and Remote Execution
43+
--------------------------------------------
44+
45+
Hoverfly supports two execution modes for post-serve actions. Understanding the
46+
difference is important when running under significant load.
47+
48+
**Local execution**
49+
50+
Hoverfly forks a new subprocess for every request that triggers the action. The
51+
binary or script is re-executed from scratch each time:
52+
53+
.. code::
54+
55+
hoverfly -post-serve-action "my-action python3 /path/to/script.py 0"
56+
57+
This is the easiest option to get started, but it does not scale. At high request
58+
rates (e.g. 40+ rps with a Python script), you will quickly accumulate dozens of
59+
concurrent processes — each with its own interpreter startup cost, memory footprint,
60+
and open connections. This commonly leads to OOMKilled in containerised environments.
61+
62+
**Remote execution (recommended for high throughput)**
63+
64+
Instead of forking a subprocess, Hoverfly makes an HTTP ``POST`` request to a
65+
server that you run separately. That server stays alive indefinitely — only one
66+
process, shared across all requests:
67+
68+
.. code::
69+
70+
hoverfly -post-serve-action "my-action http://localhost:8080/trigger 0"
71+
72+
This means you can use async frameworks such as ``aiohttp`` or ``FastAPI`` in their
73+
natural form: one running event loop handling many concurrent webhooks efficiently,
74+
without paying the startup cost on every request.
75+
76+
Running a Remote Post Serve Action: Step by Step
77+
--------------------------------------------------
78+
79+
**Step 1 — Write your server**
80+
81+
Your server must accept HTTP ``POST`` requests and return ``HTTP 200``. Hoverfly
82+
considers any other status code a failure and logs an error. Here is a minimal
83+
Python example:
84+
85+
.. code:: python
86+
87+
# server.py
88+
from http.server import BaseHTTPRequestHandler, HTTPServer
89+
import json
90+
91+
class Handler(BaseHTTPRequestHandler):
92+
def do_POST(self):
93+
length = int(self.headers.get("Content-Length", 0))
94+
payload = json.loads(self.rfile.read(length))
95+
96+
# payload contains two keys: "request" and "response"
97+
# use them to implement your webhook/callback logic
98+
print(f"[action] path={payload['request']['path']}")
99+
100+
self.send_response(200)
101+
self.end_headers()
102+
103+
def log_message(self, format, *args):
104+
pass # suppress default access log noise
105+
106+
if __name__ == "__main__":
107+
print("Listening on :8080")
108+
HTTPServer(("", 8080), Handler).serve_forever()
109+
110+
Start it:
111+
112+
.. code::
113+
114+
python3 server.py
115+
116+
**Step 2 — Understand the payload Hoverfly sends**
117+
118+
On every matching request, Hoverfly POSTs a JSON body to your server containing
119+
the full request-response pair:
120+
121+
.. code:: json
122+
123+
{
124+
"request": {
125+
"path": [{"matcher": "exact", "value": "/api/orders"}],
126+
"method": [{"matcher": "exact", "value": "POST"}],
127+
"destination": [{"matcher": "exact", "value": "example.com"}],
128+
"body": [{"matcher": "exact", "value": ""}],
129+
"headers": {}
130+
},
131+
"response": {
132+
"status": 200,
133+
"body": "Hello World",
134+
"encodedBody": false
135+
}
136+
}
137+
138+
Your server can read any field from this payload to drive its logic — for example,
139+
extracting an order ID from the request body to send a webhook.
140+
141+
**Step 3 — Configure Hoverfly to call your server**
142+
143+
Pass the remote URL as the second token in ``-post-serve-action``:
144+
145+
.. code::
146+
147+
hoverfly -post-serve-action "my-action http://localhost:8080 0" -import simulation.json
148+
149+
The format is: ``"<name> <url> <delay-ms>"``.
150+
151+
- ``my-action`` must match the ``postServeAction`` field in your simulation JSON.
152+
- The URL must be reachable from Hoverfly at runtime.
153+
- The delay (in milliseconds) is applied before Hoverfly calls the endpoint.
154+
155+
Alternatively, register it at runtime via hoverctl:
156+
157+
.. code::
158+
159+
hoverctl post-serve-action set --name my-action --remote http://localhost:8080 --delay 0
160+
161+
**Step 4 — Confirm the action is registered**
162+
163+
.. code::
164+
165+
curl http://localhost:8888/api/v2/hoverfly/post-serve-action
166+
167+
You should see your action listed with its remote URL.
168+
169+
**Running in Docker Compose**
170+
171+
When both Hoverfly and your action server run as containers, use the service name
172+
as the hostname. Make sure Hoverfly starts after the action server is ready:
173+
174+
.. code:: yaml
175+
176+
services:
177+
hoverfly:
178+
image: spectolabs/hoverfly
179+
command: >
180+
-post-serve-action "my-action http://postaction:8080 0"
181+
-import /app/simulation.json
182+
depends_on:
183+
- postaction
184+
185+
postaction:
186+
build: ./postaction-server
187+
ports:
188+
- "8080:8080"
189+
42190

0 commit comments

Comments
 (0)