You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When Verifier.message_handler({description: <dict>}) receives dict values that lack a "contents" key (i.e., raw payload dicts instead of Message envelopes), the FFI reports every affected interaction as:
Request Failed - error sending request for url (http://localhost:<port>/_pact/message)
This error reads as a networking / loopback / transport bug — every debugger I know would first check IPv4/IPv6 resolution, port binding, ordering of add_transport, etc. The real cause is a KeyError: 'contents' inside _handler at src/pact/verifier.py:434, raised inside the MessageProducer HTTP server thread. Python's http.server catches the exception, prints a traceback to stderr, and drops the TCP connection. The Rust FFI sees the disconnected socket and surfaces "error sending request for url".
In our case (asset-compass PR #241) this cost roughly six hours of investigation — including two false-lead fixes (one merged and reverted) — before someone noticed the stderr traceback under the FFI error.
Minimal repro
# pip install pact-python==3.4.0 pytest httpxfrompact.verifierimportVerifierimporthttpx# Handler returns a raw payload dict — no 'contents' key.# pact-python 3.x expects a Message envelope: {contents, metadata, content_type}handlers= {"some-message": {"field": "value"}} # raw payload, NOT envelopev=Verifier("test-provider")
v.message_handler(handlers)
# Direct probe: the MessageProducer server IS listeningwithv._message_producer:
url=v._message_producer.urltry:
r=httpx.post(url, json={"description": "some-message"}, timeout=5.0)
print(f"HTTP {r.status_code}")
exceptExceptionase:
print(f"{type(e).__name__}: {e}")
Observed output:
Exception occurred during processing of request from ('127.0.0.1', 53137)
Traceback (most recent call last):
...
File "/.../pact/verifier.py", line 434, in _handler
contents=val["contents"],
~~~^^^^^^^^^^^^
KeyError: 'contents'
----------------------------------------
RemoteProtocolError: Server disconnected without sending a response.
The KeyError traceback goes to stderr (easy to miss under pytest capture), then the FFI reports the drop as error sending request for url ... to whoever is running Verifier.verify().
Environment
pact-python 3.4.0 (also reproduced on the 3.4.1 release-branch build, so the queued transport-ordering fix does not address this)
pact-python-ffi 0.5.4.1
Python 3.13
Ubuntu 24.04 (GitHub Actions runner) and macOS 15 (dev machine) — same behavior
Suggested fix
Anything that surfaces the handler exception distinguishably from a transport failure would be a huge quality-of-life win. Options in decreasing order of thoroughness:
Validate the handler dict shape at registration time. When message_handler({...}) is called with a plain dict, iterate entries and require each dict value to have a "contents" key (or be a callable, or bytes). Raise TypeError immediately with a clear message pointing at the offending key and the expected envelope shape. This catches the mistake before verify() even starts.
Catch exceptions inside _handler and return HTTP 500 with a diagnostic body instead of letting http.server drop the connection. The FFI then sees a real HTTP response and can surface the diagnostic. Something like:
try:
returnMessage(contents=val["contents"], ...)
exceptKeyErrorase:
raiseValueError(
f"message_handler dict value for {name!r} is missing key {e.args[0]!r}. "f"Values must be a Message envelope like "f"{{'contents': <bytes>, 'metadata': {{'contentType': ...}}}}."
) frome
and have the do_POST handler catch these and return 500 <error> rather than raising.
At minimum, document this failure mode in the message_handler docstring — a sentence like "If a dict value is missing the 'contents' key, verification fails with a misleading 'error sending request for url' from the FFI. This is a known limitation."
I'd be happy to open a PR for option 1 if that's the direction you'd want.
Related
asset-compass PR #241 — the fix on our side (wrap payloads in Message envelope + emit both contentType and content_type metadata keys).
I checked PR fix: pass http transport first to ffi #1637 / b8ee0eab — the transport-first FFI fix queued for 3.4.1 does not address this because it only applies when both HTTP and message transports are registered; a message-only setup with a bad handler shape hits the same failure signature regardless.
Summary
When
Verifier.message_handler({description: <dict>})receives dict values that lack a"contents"key (i.e., raw payload dicts instead ofMessageenvelopes), the FFI reports every affected interaction as:This error reads as a networking / loopback / transport bug — every debugger I know would first check IPv4/IPv6 resolution, port binding, ordering of
add_transport, etc. The real cause is aKeyError: 'contents'inside_handleratsrc/pact/verifier.py:434, raised inside theMessageProducerHTTP server thread. Python'shttp.servercatches the exception, prints a traceback to stderr, and drops the TCP connection. The Rust FFI sees the disconnected socket and surfaces "error sending request for url".In our case (asset-compass PR #241) this cost roughly six hours of investigation — including two false-lead fixes (one merged and reverted) — before someone noticed the stderr traceback under the FFI error.
Minimal repro
Observed output:
The
KeyErrortraceback goes to stderr (easy to miss under pytest capture), then the FFI reports the drop aserror sending request for url ...to whoever is runningVerifier.verify().Environment
Suggested fix
Anything that surfaces the handler exception distinguishably from a transport failure would be a huge quality-of-life win. Options in decreasing order of thoroughness:
Validate the handler dict shape at registration time. When
message_handler({...})is called with a plain dict, iterate entries and require eachdictvalue to have a"contents"key (or be a callable, orbytes). RaiseTypeErrorimmediately with a clear message pointing at the offending key and the expected envelope shape. This catches the mistake beforeverify()even starts.Catch exceptions inside
_handlerand return HTTP 500 with a diagnostic body instead of lettinghttp.serverdrop the connection. The FFI then sees a real HTTP response and can surface the diagnostic. Something like:and have the
do_POSThandler catch these and return500 <error>rather than raising.At minimum, document this failure mode in the
message_handlerdocstring — a sentence like "If a dict value is missing the 'contents' key, verification fails with a misleading 'error sending request for url' from the FFI. This is a known limitation."I'd be happy to open a PR for option 1 if that's the direction you'd want.
Related
contentTypeandcontent_typemetadata keys).b8ee0eab— the transport-first FFI fix queued for 3.4.1 does not address this because it only applies when both HTTP and message transports are registered; a message-only setup with a bad handler shape hits the same failure signature regardless.