Request / Response
MQTT 5.0 defines a first-class request/response pattern via two
PUBLISH properties: response_topic and correlation_data. zmqtt
implements this as a single await client.request(…) call.
Basic usage
from zmqtt import create_client
async with create_client("broker", version="5.0") as client:
reply = await client.request("services/calculator", b"2+2")
print(reply.payload)
request() handles the full flow automatically:
- Acquires the response topic before publishing. When omitted, a unique topic is generated automatically.
- Publishes the request with
response_topicandcorrelation_dataset. - Waits for a message with the exact response topic and
correlation_data, then returns it. - Releases its response-topic interest on return, timeout, or cancellation.
Messages without correlation data or with a different value do not complete
the request. A matching response is claimed exclusively by its pending
request() call and is not also enqueued for a regular Subscription.
A response that matches no active request falls through to normal routing.
If a matching subscription exists, zmqtt routes the message to it; otherwise,
the message is ignored and a warning is logged.
Customising via PublishProperties
Pass a PublishProperties instance to control any field of the outgoing
PUBLISH. Two fields receive special treatment:
| Field | Behaviour |
|---|---|
response_topic |
Used as the reply topic instead of the auto-generated one. Must not contain wildcards. |
correlation_data |
Forwarded to the responder as-is. Auto-generated (16 random bytes) when absent. |
All other fields (content_type, message_expiry_interval,
user_properties, …) are forwarded unchanged.
from zmqtt import PublishProperties
reply = await client.request(
"services/translate",
b"hello",
properties=PublishProperties(
content_type="text/plain",
response_topic="my-app/replies/translate",
correlation_data=b"req-001",
),
timeout=10.0,
)
The same custom response topic can be shared by concurrent requests as long as
their correlation data differs. Responses may arrive in any order; each one is
routed to the matching request() call. Reusing the same response topic and
correlation data while a request is active raises ValueError because the two
responses would be indistinguishable.
Implementing a responder
The responder reads response_topic and correlation_data from the
incoming message and publishes the reply there:
async with client.subscribe("services/translate") as sub:
async for msg in sub:
assert msg.properties is not None
assert msg.properties.response_topic is not None
assert msg.properties.correlation_data is not None
result = translate(msg.payload)
await client.publish(
msg.properties.response_topic,
result,
properties=PublishProperties(
correlation_data=msg.properties.correlation_data,
),
)
The MQTT 5.0 request/response flow, section 4.10.1
specifies that when a Request Message contains Correlation Data, the responder
copies that property into the Response Message. request() always includes
Correlation Data, using the caller-provided value or generating 16 random
bytes. A compatible responder therefore has to return the same bytes unchanged;
it must not omit the property or generate a new value.
The broker only forwards Correlation Data; it does not add it to the response
on behalf of the responder. A response without Correlation Data, or with a
different value, cannot be associated with the active request. request()
ignores that message and continues waiting for a matching response until its
timeout. The message falls through to normal routing and may be buffered by the
one selected regular subscription.
Timeout
request() raises asyncio.TimeoutError when no matching reply arrives within
timeout seconds (default 30.0). This timeout starts after the response-topic
interest is ready and the request publish has completed. The client stops
listening for the matching response after return, timeout, or cancellation.
After a timeout, a late response is not retained by the request dispatcher and cannot complete the expired request. It falls through to normal routing and may be buffered by the one selected regular subscription; if no subscription matches, it is discarded.
import asyncio
try:
reply = await client.request("slow/service", b"ping", timeout=5.0)
except asyncio.TimeoutError:
print("Service did not respond in time")
Connection loss and reconnection
With automatic reconnection enabled (the default), a request that has already been published continues waiting for its matching response. Its response-topic interest is restored after reconnect, and the reply timeout continues across the interruption. This does not guarantee that a response published while the client was offline will be replayed: that depends on the request QoS and broker-side session state.
With reconnection disabled, or after all configured reconnect attempts are
exhausted, a request that is already waiting remains pending until its timeout
expires. Calling client.disconnect() while requests are pending ends them with
MQTTDisconnectedError.
Errors
| Exception | Raised when |
|---|---|
RuntimeError |
request() is called on an MQTT 3.1.1 connection |
MQTTInvalidTopicError |
The request topic or properties.response_topic is invalid |
MQTTDisconnectedError |
The request cannot start, or client.disconnect() is called while it is active |
ValueError |
Topic/correlation pair is already in use |
asyncio.TimeoutError |
No matching reply arrives within timeout |
Request backpressure
The request dispatcher keeps one pending result per active request and does not
buffer unmatched or late response messages. A matching response goes only to
its pending request; otherwise normal routing may enqueue the message for one
selected subscription. max_pending_requests bounds the number of active
requests (default 1000): additional calls wait for capacity before publishing.
client = create_client(
"broker",
version="5.0",
max_pending_requests=100,
)
Note
request() is only available on MQTT 5.0 connections. Prefer
create_client(…, version="5.0") for a version-typed client.
See also: MQTT 5.0 · Publishing · Error Handling