We have http-request, tcp-request, serial-request - any objections to an mqtt-request?

Cool - would be great to get a 2nd set of eyes on it. Once I push a branch, I can provide instructions for running from source (if you need them)

Will that be a node-red build? I haven't done that, but I think it is documented somewhere so can probably work it out with a small amount of guidance.

I see from the HiveMQ videos that MQTT v5 offers other new features that could be valuable to Node-red, eg shared subscriptions, enhanced authentication, user properties.

Will this new node support these features too? No idea how much coding beyond extra config options would be needed!

Yes. But not introduced here - the mqtt in and out nodes already support all of these v5 features (I added those a few years back).

Kinda.

The PR is raised here: feat: MQTT Request Node - official v5 request-response support by Steve-Mcl · Pull Request #5860 · node-red/node-red · GitHub

Minimal instructions (assumes linux)

Will install node-red source in ~/src/node-red
change as required

mkdir ~/src
cd ~/src
git clone git@github.com:node-red/node-red.git
cd node-red
git switch feat-mqtt-request
npm ci
npm run dev

If you already have a node-red instance running locally, you can start with parameters like this: npm run dev -- --userDir "~/.node-red-1881" --port 1881

Well it appears to be working :slight_smile:

Now I shall try and break it. :smiling_face_with_horns:

A bit of an anomaly:

  1. Configure the node to connect to a broker on a different machine, where one can pull the ethernet cable from the node red machine (or otherwise break the connection). Leave timeout at 5 seconds
  2. Add Catch and Complete nodes
  3. Build the response flow on the remote machine
  4. Test it works, obviously.
  5. Pull the cable
  6. Before the node shows disconnected (which is about a minute on my system) post a couple of requests
  7. The messages passed in, but not sent, do not generate timeout errors in the Catch node
  8. Wait for the node to show Disconnected and plug the cable back in
  9. When the node connects again then valid responses are emitted, even though the timeout is long gone.

Good stuff.

I suspect that is the client doing its job and delivering the initial request on reconnection.

The timeout doesnt start until a successful subscribe so that slow subscription comms doesnt eat up the timeout

I think it is an acceptable behaviour TBH.

A future option like ":white_check_mark: start timeout after subscription" which can be disabled for the behaviour you are describing is a possibility.

Happy to discuss further.

Do you mean successful publish? Is it not permanently subscribed?
I don't understand the logic, if you have slow comms then you can increase the timeout.

In my use case (guaranteed delivery of messages) I send one message at a time and wait for Complete or Timeout. If it Completes then I send the next one, if it times out then I wait a bit and try again. However, I need to impose a 'super timeout' in the flow that forces a retry if I don't see any response from the node after some time. That is to prevent lockup due to a lost message such as can occur in a partial deploy for example. So I need to know that if I wait long enough then I can be sure that the message is lost. With the current code it seems there is no way of knowing for certain that the message is not going to suddenly generate a Complete message hours later when the network recovers

I don't see what advantage there is in waiting before starting the timeout. Certainly it is not what I expected.

Apart from the timeout it is working well, for my guaranteed delivery flow, in the sender it has enabled me to reduce

To this

I do need the timeout to work from when the message is posted however. Otherwise I think I will have to put back several of the nodes in my original flow, provide my own correlation data, and check each message as it comes back to make sure it is from the most recent message.

That depends on the qos (qos 0 is fire and forget)

Yes, if it's a static response topic. No if it is passed in the msg (dynamic response topic).

If you do get an error during subscription, it will throw a catchable error.

There is a grey area tho - in a half open connection state (like you describe) - the (existing) behaviour of the mqtt client we use is that if you manage to catch it, the message gets queued by the internal store. Upon connect it's delivered. This is shared behaviour across the mqtt nodes and changing that will change existing behaviours.

I do add a v5 message expiry value to the packet but that is (strictly speaking) a broker implementation so late delivery may or may not be rejected depending on the broker. (Not sure how mqtt.js handles these, but to be clear, the behaviour is broker specific not client specific)

Another caveat is if I DO start the timeout BEFORE subscription & it is delayed/slow subscribing (such that the message reaches the client but the node times out before the response arrives) is that you won't know (or rather think it failed) so a non idempotent receiver action would be triggered again when you do a resend.


All that said, you do make some valid points and so I have updated the MQTT Request PR to do the following:

before reading the bullets, I have (hopefully) made this clear in the built-in help - the points below are in case i didnt do a good job of that :wink:

  • The timeout now runs end-to-end - it starts the moment the message arrives and covers the whole exchange (subscribe → publish → waiting for the reply). So you always get one outcome within the timeout: either a response on the output, or an error, even if the connection is half-open and the subscribe/publish stalls.

  • On timeout, the error tells you what actually happened. The (catchable) error message distinguishes "delivered but no response" from "not confirmed delivered", and the outgoing msg carries a small flag object:

    msg.mqtt = {
        timedOut: true,
        qos: <qos used>,
        delivered: <true|false>
    }
    
    

    delivered is true when publishing was confirmed before we gave up - for QoS 1/2 that means the broker acknowledged it; for QoS 0 it only means it was written to the connection.

  • If it gives up on a QoS 1/2 request that was never acknowledged, it is now purged from the client's outgoing store so that it isn't silently retransmitted on reconnect. i.e. the "queued by the internal store, delivered on connect" behaviour you flagged. It will only do this while it's still unacknowledged; once it's been acked there's nothing to purge and so leave the store alone. The v5 message-expiry value is still set as the broker-side backstop

  • The original message is passed through to the output (and to a Catch node on error) with its non-MQTT properties intact, so it slots into existing flows (http-in req/res, link-call, etc.) without losing context.

What this means to your guaranteed-delivery flows: just wait for the node to emit. Success → send the next one. Error → check msg.mqtt.delivered: false = safe to resend, true = it reached the broker so treat a resend as potentially duplicate. Either way you get a definite answer inside the timeout window, so no lock-up and no need for the external "super timeout".


Please git pull the changes, restart node-red (npm run dev) and test it out:

Great, now working perfectly.

Yes, it has always been the case that the receiver may receive repeated requests, I don't think there is any way to avoid that. If it is important to the client that repeated requests are ignored then he would have to add a serial number, or similar, to allow detection of this.

Good stuff.

Regarding that caveat, I have somewhat mitigated it for QoS 1 and QoS 2 (see the bit about " If it gives up on a QoS 1/2 request that was never acknowledged, it is now purged from the client's outgoing store" & the new flag msg.mqtt.delivered can help a user flow make informed decisions)

This is a difference in behaviour for the new node ONLY. It makes sense because this is about Request - Response specifically.


Please continue do to test and evaluate & let me know if there is any issues or subtleties to address before it becomes mainstream (assuming it is accepted of course)

Yes, I am continuing to test. Dynamic topics seem to be working ok.

Having made yourself familiar again with the MQTT node, is there a chance that you could look at this existing issue? It would be useful to me, and I suspect it would be a simple change.

Which has prompted me to consider whether the request node should allow configuration of user properties and content type.

In this first iteration I deliberately left those off. However, you can send user properties, content type, etc, in the message.

True, but is there any reason for it to be different in this respect from the MQTT Out node?

Only implementation time. If the need for all bells and whistles is raised in review as a blocker, then the PR will either sit and wait for the extra work to get done or worse, it doesnt get done and doesnt get merged. As a first implementation, this is fairly feature complete (as a request response node) and there is nothing stopping tasks being raised for later follow up.

Understood.

OK, I have given it a pretty good going over and all seems to be well.