Skip to content

Misleading 'error sending request for url' when message_handler dict returns raw payload (missing 'contents' key) #1665

Description

@seanoshea

Summary

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 httpx
from pact.verifier import Verifier
import httpx

# 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 envelope

v = Verifier("test-provider")
v.message_handler(handlers)

# Direct probe: the MessageProducer server IS listening
with v._message_producer:
    url = v._message_producer.url
    try:
        r = httpx.post(url, json={"description": "some-message"}, timeout=5.0)
        print(f"HTTP {r.status_code}")
    except Exception as e:
        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:

  1. 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.

  2. 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:
        return Message(contents=val["contents"], ...)
    except KeyError as e:
        raise ValueError(
            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': ...}}}}."
        ) from e

    and have the do_POST handler catch these and return 500 <error> rather than raising.

  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions