Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Requirement: Proposal (2) for standardized message envelope for requests, responses and notifications #34

Open
hspaay opened this issue Dec 22, 2024 · 43 comments

Comments

@hspaay
Copy link
Collaborator

hspaay commented Dec 22, 2024

[Update 2025-01-02: changed requestID to correlationID as per @RobWin feedback]
[Update 2025-01-02: changed operation in ResponseMessage to be optional as per @unit9a feedback]

This is proposal 2 draft for standardizing the message envelopes for all protocol bindings, including the websocket binding. Adopting this approach would make the websocket protocol binding a trailblazer and would serve as an example for all future protocol bindings.

HiveOT is in the process of implementing this proposal at the application level and currently maps to existing protocol bindings as the transport.

Note, I realize this is a rather different approach to constructing messages so I don't really expect it to be adopted unless it garners a lot of support. I've documented it here in case there is an actual interest or otherwise for future prosperity.

Rational

  1. WoT protocol bindings all solve the same problem. Send requests, receive responses and send notifications. Currently protocol bindings each define their own messages and interactions in doing so. This makes life harder for the implementor as it is a lot more work to implement and test multiple different approaches than a single approach.

Protocol bindings in WoT serve two different purposes. Provide a transport and act as a (partial) application protocol for handling properties, events and actions. These are different concerns that can be separated. Separating the application protocol from the transport enables defining a single application protocol that can be used on any transport. This in turn would simplify adoption and improve interoperability.

  1. The WoT protocol bindings only describe the consumer-server interaction. They only partially address how Hubs and Gateways interact with Thing agents (servients) asynchronously.

The WoT protocol bindings does not describe how Thing agents connect as a client to a hub or gateway. Instead the assumption is that all Things run a server. This is too limited of a view. Hiveot is a Hub where Thing agents are clients to the Hub just like consumers. WoT does not describe the interaction for these agents.

This proposal describes a messaging format and behavior for protocol bindings that address the above issues.

Message Types

There are three message types: Request, Response and Notification with corresponding message envelopes. All WoT interaction that takes place between consumers, Thing agents, hubs, and gateways can be described using just these three messages.

Messages are identified by their type and operation. Operations are those defined in the WoT TD 1.1 specification and are open to extensions using @context. Response messages include the operation of the request they are a response to and thus identify the response payload.

Underlying transport protocols such as HTTP, SSE, Websocket, MQTT, etc mere act as pipes to deliver these messages in the most efficient way possible.

Request messages

The purpose of the request message is for a client to send a request for an operation on a thing. Thing agents are required to send a response when a request is received.

The following operations are considered to be requests:

  • invokeaction [WoT]
  • subscribe, unsubscribe [WoT]
  • observe, unobserve [WoT]
  • readproperty, readallproperties [WoT]
  • queryaction, queryallactions [WoT]
  • readevent, readallevents (of a Thing) [HiveOT extension]
  • readtd, readalltds (of a directory or thing) [HiveOT extension]

The request message defines the following fields:

name data type description required
type string "request". Identifies the message as a request message mandatory
operation string Describes the request to perform mandatory
thingID string ID of the thing the request applies to optional
name string Name of the affordance the request applies to if applicable. The type of affordance (event, action, property) is determined by the operation optional
input any Input data of the request as described by the operation. invokeaction refers to the action affordance while other operations define the input as part of the operation optional
correlationID string Unique identifier of the request. This must be included in the response. If no correlationID is provided then the request will still be handled by no response is returned. optional
senderID string Authenticated sender of the request. optional
messageID string Unique identification of the message. optional

Response messages

Responses serve to notify a single client of the result of a request.

Response message payload is determined by the request operation. Therefore the request operation is included in the response:

name type description required
type string "response". Identifies the message as a response message mandatory
operation string The request operation this is a response to. Not required. Intended to help with debugging optional
correlationID string identifies the request this is a response to. required
status string Status of the request processing: "pending", "running", "completed" or "failed" required
output any Result of processing the request if status is "completed" as defined in the dataschema of the action or operation. If status is "failed" then this can contain additional error information. optional
error string Error title if status is "failed". optional
received string Timestamp the request was received by the Thing (or its agent) optional
updated string Timestamp the status was updated optional
thingID string ID of the thing the request applies to optional
name string Name of the affordance the request applies to if applicable. The type of affordance (event, action, property) is determined by the operation optional
messageID string Unique identification of the message. optional

Notification messages

Notifications serve to notify subscribers of a change as identified by the operation, thingID and affordance name. Notifications are not targeted to a single receiver but intended for subscribers (or observers).

All notifications use the same message format as implemented in NotificationMessage struct (golang, JS, Python). Protocol bindings can use this envelope directly or map from their protocol equivalent to this message format.

The following operations are considered notifications:

  • property: Update of a property value, sent by a Thing agent to observers of a property.
  • event: Notification of event to subscribers.
name type description required
type string "notification". Identifies the message as a notification message mandatory
operation string Identification of the notification mandatory
data any notification data as specified by the operation optional
correlationID string optional correlation with the request, for subscriptions or streams optional
created string Timestamp the notification was created optional
thingID string ID of the thing the notification applies to optional
name string Name of the affordance the notification applies to, if applicable. The type of affordance (event, action, property) is determined by the operation optional
messageID string Unique identification of the message. optional

Behavior

Any client can publish a request. They can choose to wait for a response message or handle the response asynchronously. This is dependent on the client implementation.

The server can be an agent for a thing or a hub or gateway.

Hub or gateways will forward requests to the Thing agent. If the agent is not reachable at this point they return the error response or a pending response if there is support for queuing requests.

Thing agents will process the request. They SHOULD send a response with one of three statuses: running, completed or failed. If a request is received without a request ID then no response is sent.

If a 'running' response is send, agents MUST also send a completed or failed response once the operation has finished.

If a hub or gateway is used then the response is received by the hub/gateway, which in turn forwards it to the client that sent the request. If the client is no longer reachable then the response can be queued or discarded, depending on the capabilities of the hub or gateway.

Clients will receive a response message containing the original correlationID, the payload an any error information. Client implementations can choose to wait for a response (with timeout) or handle it as a separate callback.

Agents can sent notifications to subscribers. The notification message includes an operation that identifies the type of notification. A hub or gateway can implement their own subscription mechanism for consumers and ask agents to send all notifications.

@RobWin
Copy link
Collaborator

RobWin commented Jan 2, 2025

I suggest renaming requestID to correlationID.

This would enhance functionality, especially for subscriptions to property changes or events. Including a correlationID in the request message (subscribe, observe, unsubscribe, unobserve) and notification messages allows better correlation between notifications and their respective subscriptions.

For instance, in a WebSocket client, you might return an individual reactive stream for each (property change or event) subscription. For example a stream could be implemented with a reactive programming library like ReactiveX. And the websocket client does not return a single stream of notifications, but could separate notifications into multiple individual streams.
A correlationID (acting as a subscriptionId) can help dispatch notifications to the appropriate stream .

@hspaay
Copy link
Collaborator Author

hspaay commented Jan 2, 2025

Yes, good feedback and the streaming use-case is really nice. I'll change it in the hiveot documentation and code and add it to notifications as well.

I just completed implementing this proposal in hiveot as an application layer. The http/wss/mqtt transports maps from the three message types to the specified message types for that transport. On the hub the mapping is reversed and further processing uses the three message types again (so-far only implemented http/sse). I love the simplicity of the approach!

Some observations sofar:

  1. This works well with intermediaries like a hub. Prior there was a struggle to mentally keep track of who is sending/receiving what and in reply to what. That problem has pretty much disappeared. A request goes from consumer to Thing, a response the other way and notifications are send and forget by the Thing. The only difficulties come from all the mapping to the various transport message types and back.
  2. Not having to do the mapping of messages types saves a lot of code. Agents (a Thing that connects as a client to the hub) are not supported in WoT so I didn't have to worry about interoperability. The agent just receives the RequestMessage and sends ResponseMessage and NotificationMessage envelopes. This really simplified implementation.
  3. Using 'operation' in the messages also works quite well to further process the message at the application level. There is no tension (mapping, translation) between protocol and application, and no need to define separate message types to deal with responses.
  4. Forms are also simpler. No need to have a form entry for each operation. Instead there are only 3 forms (http) with endpoint and a set of corresponding operations. I haven't run into a need to define forms with the affordances either.
  5. There is no need to define separate response messages like the current websocket protocol needs. (actionstatus(es), propertyreading(s),). Instead the ResponseMessage envelope can be used.
  6. There is no need to define response message operations. Instead the operation in the ResponseMessage is that of the request it is responding to.
  7. It works equally well for consumers without a hub that talk directly to Thing agents (servients).
  8. subscriptions, observations, and property writes are requests that can now be confirmed with a response message when applied or error if failed. (by including a correlationID)

It looks to me that the protocol bindings got overly complicated for what is essentially a transport problem. This brings it back to what it essentially is.

Next steps:

  • apply this on top of the websocket binding. Do all the mapping unfortunately, unless Ben can be convinced to adopt this.
  • same for the mqtt binding
  • better document the approach with diagrams
  • define a 'hiveot' WoT protocol proposal with http/sse, wss, mqtt as simple transports
  • implement this is a core protocol in hiveot. Initially for internal use but maybe there is an interest from adopters.

@RobWin
Copy link
Collaborator

RobWin commented Jan 2, 2025

But from a protocol level, I still miss acknowledgements.
Acknowledgements had been very useful for event subscriptions or property change subscriptions.

@unit9a
Copy link

unit9a commented Jan 2, 2025

sorry to crash in, I have been chewing on how to implement something like this for Iot via WoT with webRTC + webSocket(for any messaging that needs more reliability than webRTC). I would like to contribute here too eventually.

@RobWin, you mentioned the use of libraries like ReactiveX, would you happen to be aware on any wc3 standards for similar functionality?

reactive programming library like ReactiveX.

would you happen to be aware on any wc3 standards for similar functionality?

@hspaay, about the following response message fields:

operation | string | The request operation this is a response to. | mandatory
correlationID | string | identifies the request this is a response to. | required

shouldn't the sender of a request be able to determine the operation from the correlationID in a response only. I am under the impression the correlationID is being tracked/stored by the sender along with the operation. so why send "operation" again?

i ask because i am interested in using minimal payloads for webRTC's MediaStreamTrack & data channels.

@hspaay
Copy link
Collaborator Author

hspaay commented Jan 2, 2025

@RobWin

But from a protocol level, I still miss acknowledgements.

This is solved by making subscription,observations,and writeproperty operations a request. All requests are acknowledged using their correlation ID.
So, apart from transport constraints (such as mapping to transport messages), these protocol messages do send an acknowledgement.
Does that address your issue or am I missing something?

@hspaay
Copy link
Collaborator Author

hspaay commented Jan 2, 2025

@unit9a

shouldn't the sender of a request be able to determine the operation from the correlationID in a response only.

Yes you are correct. operation in response can be optional. The reason it was included is that I found it useful to assist in testing and debugging. You are right though, the protocol doesn't need it. I'll change it to optional in the proposal.

(ps: the media stream track looks very interesting. I'll experiment with support for it in hiveot once I have some more time)

@RobWin
Copy link
Collaborator

RobWin commented Jan 2, 2025

@unit9a I'm using Kotlin Flows on client and server side, not ReativeX. But the concepts are quite similar to RX or Project Reactor.

There is something like https://developer.mozilla.org/en-US/docs/Web/API/WebSocketStream
and https://rsocket.io/ for Websockets and streams.
Rsocket spec is here: https://rsocket.io/about/protocol

@unit9a
Copy link

unit9a commented Jan 2, 2025

@hspaay the media stream track API is something i am exploring for industrial Iot applications. it closely related to the draft standard for WebCodecs. it would would encourage the use of WoT and other web standards in a lot of applications. However, i don't think webCodecs will be ratified any time soon.

@RobWin I'm a excited about rRocket.io, THANKS SO MUCH!!!. Fyi, the RSocket Protocol Specification Community Group seems dead and i could not find a wc3 draft or standards page. And I hope WebSocketStream is combined with rRocket.io at some point

@unit9a
Copy link

unit9a commented Jan 3, 2025

@hspaay this reminds me of json-rpc 2.0. do you mind if I explore what a Json rpc version of this looks like via protobufs?

do you even think json rpc is relevant at all?

@hspaay
Copy link
Collaborator Author

hspaay commented Jan 3, 2025

@unit9a please go ahead and explore. Always interesting to gain more perspectives.

On the application side I found it helpful if the protocol matches the vocabulary and meaning of the application as minimalistic as possible. This is why the proposed message envelopes uses fields like operation, thingID, and (affordance) name, with the message types for request, response and notification.

On the transport layer below this, the more the merrier. Encodings like json-rpc, bson, protobuf, capn'proto with matching transports like websockets, gRPC, mqtt, and so on, are all good to support. This is at least my thinking with this proposal.

Looking forward to see what you have in mind.

(ps: lets also not lose sight of what @benfrancis has in mind for the websocket proposal. This issue is merely my own 2c's based on insights gained implementing the http, sse and proposed wss bindings)

@RobWin
Copy link
Collaborator

RobWin commented Jan 3, 2025

@unit9a Yes, seems there was no big interest into a reactive streams capable application protocol at w3c.

@hspaay

This is solved by making subscription,observations,and writeproperty operations a request. All requests are acknowledged using their correlation ID.

Means for every request (subscribe, unsubscribe, observe, unobserve, writeproperty) there is a response without output as a acknowledgement? What would be the status?

@hspaay
Copy link
Collaborator Author

hspaay commented Jan 3, 2025

Means for every request (subscribe, unsubscribe, observe, unobserve, writeproperty) there is a response without output as a acknowledgement? What would be the status?

That is correct. The status can be completed on success, or failed if the request isn't accepted for whatever reason. The error field in the response can contain the error message on failure while output optionally contains any further details.

Btw, if no acknowledgement is desired (can't think of a good reason) then the correlationID can be left empty.

The part I'm still unclear about is how to include 'alternative results' that can be specified in Forms for actions, but that is a different topic.

@unit9a
Copy link

unit9a commented Jan 3, 2025

(ps: lets also not lose sight of what @benfrancis has in mind for the websocket proposal. This issue is merely my own 2c's based on insights gained implementing the http, sse and proposed wss bindings)

@hspaay & @benfrancis, the use webRTC data channels should be the same as or similar to websockets per: https://developer.mozilla.org/en-US/docs/Web/API/RTCDataChannel#instance_properties

Values are the same as allowed on the WebSocket.binaryType

regardless, i will prioritize using webSockets.

unit9a added a commit to unit9a/web-thing-protocol that referenced this issue Jan 3, 2025
@unit9a
Copy link

unit9a commented Jan 3, 2025

@hspaay my idea so far.
#34 (comment)

transport layer concept: equivalent json rpc

assumption: this proposed web-thing-protocol (WoTP) is an application
axiomatization
of domain axiomatization:The WoT Thing Description

goals

  1. stay as close as possible to the semantics & ontology of
    The WoT Thing Description: Hypermedia Controls Vocabulary Definitions
  2. maintain compatibility or easy conversion with json-rpc
  3. expect to be used with something like protobuf to convert between json and
    binary
  4. transmitted via websocket an ArrayBuff binary payload

my other assumptions/ideas

conversion of json-rpc member name

Json-prc Request object members proposed WoTP ontology names
jsonrpc wotp
method operation

syntax

"-->" = Request
"<--" = Response

Request messages fields

Request messages fields Json-prc Request object members proposed WoTP ontology names
type indicated by member names
operation method op
thingID param.thingID thingID
name param.name affID(affordanceID)
input param.input input
correlationID param.correlationID corrID
senderID param.senderID senderID
messageID id msgID

example 1:

--> { 
        "jsonrpc": "2.0", 
        "method": "string", 
        "params": {
            "thingID": "string", 
            "name": "string", 
            "input": "any", 
            "correlationID": "string", 
            "senderID": "string"
        },
        "id": "string"
    }

======= with wotp parameter names: ========
--> { 
        "wotp": "<version>", 
        "op": "string", 
        "params": {
            "thingID": "string", 
            "affID": "string", 
            "input": <any>, 
            "corrID": "string", 
            "senderID": "string", 
            "msgId": "string"
        },
        "id": "string"
    }

Notification messages fields

same as a Request except "id" is moved to "params" as "messageID"

{
    "jsonrpc": "2.0",
    "method": "string", 
    "params": {
        "thingID": "string",
        ...
+   "msgID": "string"
    },
-   "id": "string"
}

====== becomes: ====== 
{ 
    "jsonrpc": "2.0",
    "method": "string", 
    "params": {
        "thingID": "string",
        ...
        "messageID": "string"
    }
}

======= using wotp member names: ========
{ 
        "wotp": "<version>",
        "op": "string", 
        "params": {
            "thingID": "string",
            ...
            "msgID": "string"
        }
}

Response messages fields

Response messages fields Json-prc Request object members proposed WoTP ontology names
type indicated by member names
status result.status status
thingID result.thingID thingID
name result.name affID(affordanceID)
output result.output results
error result.error errors
received result.received rxTs
updated result.updated udTs
correlationID result.correlationID corrID
messageID id msgId

example 2:

<-- { 
        "jsonrpc": "2.0", 
        "method": "string", 
        "result": {
            "correlationID": "string",
            "error": "string",  
            "name": "string", 
            "output": "any",  
            "received": "string", 
            "status": "string", 
            "thingID": "string", 
            "updated": "string"
        },
        "id": "string"
    }

======= using wotp member names: ========
<-- { 
        "wotp": "<version>", 
        "op": "string",
        "result": {
            "corrID": "string",
            "error": "string",  
            "affID": "string", 
            "output": "any",  
            "rxTs": "string", 
            "status": "string", 
            "thingID": "string", 
            "udTs": "string"
        },
        "id": "string"
    }

@unit9a
Copy link

unit9a commented Jan 4, 2025

@hspaay when using webSockets,

  1. is there a difference between an individual websocket connection ID is and the "senderID" Request messages field?
  2. can they even be the same thing?

@RobWin, i will start with WebSocketStream but will eventually support use both rsocket.io

@unit9a
Copy link

unit9a commented Jan 4, 2025

@benfrancis
I am chewing on the idea of replacing the "json-rpc" version identifier member with a json-LD style "@context" member used in a similar way "@context" is use in Wot thing description example 1

sequenceDiagram
    autonumber
    participant wot1 as thing1
    participant wot2 as thing2

    critical Negotiate "@context for rpc session"
        wot1->> wot2: { wotp:"0.1",<br>params = <@context value in example 1>}
        alt success
            wot2->> wot1: { wotp:"0.1", result = {wotpSessionID: <short uuid>, contextInfo:{<selected context>}}}

            Note right of wot1: both WoT entities map the <wotpSessionID> to @context value for the session

            par thing1 messages 
                wot1->> wot2: { @wotp: "<wotpSessionID>"... }
            and thing2 messages
                wot2->> wot1: { @wotp: "<wotpSessionID>"... }
            end

        else fail
            wot2->> wot1: { wotp:"0.1", result :{error: "no supported rpc context...", output: ...}}
        end
    end
Loading

@hspaay
Copy link
Collaborator Author

hspaay commented Jan 4, 2025

@unit9a

  • is there a difference between an individual websocket connection ID is and the "senderID" Request messages field?
  • can they even be the same thing?

Yes there is a difference between a connectionID and senderID:

  • The purpose of the senderID is for use in authorization, logging or other client specific behavior, during processing of the message. It identifies the connected client and is intented to be set by the transport protocol which has the authentication credentials for each connection.
  • A connection ID however is unique per connection. Connection-ID is used internally in hiveot as the reply-to address, (linked to by the correlationID), to ensure the response goes to the correct connection. It is intended for routing the response in a hub or gateway. It is not exposed. (no use-case?)

Hope that explains it.

@hspaay
Copy link
Collaborator Author

hspaay commented Jan 4, 2025

@unit9a I'm unclear on what WoTP is and what problem it is intended to solve.
Is it an alternative format from the initially proposed messages or is there another purpose behind it? Is it intended for interoperability with another existing format?

For example, what is the thinking behind using "corrID" instead of "correlationID".

That aside I don't see a problem with the mapping you describe other than it looks like doing the same thing with different names. Doing the same thing is good as it makes mapping easy btw.

@unit9a
Copy link

unit9a commented Jan 4, 2025

@hspaay

I'm unclear on what WoTP is and what problem it is intended to solve

"WoTP" was meant as an acronym for this"Web of Things Protocol" draft. i just wanted something short to type.



For example, what is the thinking behind using "corrID" instead of "correlationID".

Good catch, they are supposed to mean the same thing but i did not make that clear.

so i guess more clear & updated mappings are:

Request & Response messages fields Json-prc Request object members proposed WoTP ontology names
name param.name affordanceID or affID
correlationID param.correlationID correlationID or corrID


Doing the same thing is good as it makes mapping easy btw.

yeah, that is what I wanted to convey

@unit9a
Copy link

unit9a commented Jan 4, 2025

follow up, #34 (comment)

for some simplification, I realized that from the perspective of json-LD: "@context" functionally does the same thing as "type", "json-rpc" and "wotp".

Response messages fields Json-prc Request object members proposed WoTP ontology names
type indicated by member names indicated by member names

so the purpose of Negotiating "@context for rpc session" just amounts to establishing a short string identifier for the schema of response, request, and notifications to be used for subsequent messages.

so rather that deviate from the json-rpc spec, all the "WoTP" stuff i proposed.
using a response message for example

<-- { 
        "wotp": "<version>", 
        "op": "string",
        "result": {
            "corrID": "string",
            "error": "string",  
            "affID": "string", 
            "output": "any",  
            "rxTs": "string", 
            "status": "string", 
            "thingID": "string", 
            "udTs": "string"
        },
        "id": "string"
    }

could be simplified down to:

<-- { 
        "jsonrpc": "2.0", 
        "result": {
            "@context": "ID assigned to the _negotiated_ schema and extension info", 
            "op": "string - request operation this is a response to",
            "corrID": "string - identifies the request this is a response to.",
            "error": "string - Error title if status is 'failed'",  
            "affID": "string- Name of the affordance ", 
            "output": "any",  
            "rxTs": "string - received Timestamp", 
            "status": "string - Status of the request processing ['pending', 'running', 'completed' or 'failed']", 
            "thingID": "string - ID of the thing the request applies to", 
            "udTs": "string - updated Timestamp "
        },
        "id": "messageID string"
    }

@hspaay do you think this makes it even more clear the same thing is being done?

Also what do you think of "@context" being use to establish the schema of all possible WoT interactions for the remainder of the connection session between consumers, Thing agents, hubs, and gateways. kind of like a json-LD version of an openAPi spec.

@unit9a
Copy link

unit9a commented Jan 6, 2025

more simplification of my json-rpc transport layer
Request
rpc spec says that it can be extended by prefixing method (or operation) names with "rpc."

8 Extensions
Method names that begin with rpc. are reserved for system extensions, and MUST NOT be used for anything else. Each system ?> extension is defined in a related specification. All system extensions are OPTIONAL.

so using rpc extensions, the Request method/operation names gain a prefix of: "rpc."
but i think a prefix like "rpc.wot." or "rpc.wotp." is more clear.

-->{ 
        "jsonrpc": "2.0"
        "method":  "<string - operation name>", 
        "params": {
            "@context": "ID assigned to the _negotiated_ schema and extension info", 
             "thingID": "string - ID of the thing the request, 
             "affID": "string- Name of the affordance ", 
            "input": "any", 
            "correlationID": "string - Unique identifier of the request...", 
            "senderID": "string - Authenticated sender of the request."
        },
        "id": "string - messageID"
}

becomes:

--> { 
        "jsonrpc": "2.0", 
        "method": "rpc.wot." + "<string - operation name>", 
        "params": {
            "@context": "ID assigned to the _negotiated_ schema and extension info", 
             "thingID": "string - ID of the thing the request, 
             "affID": "string- Name of the affordance ", 
            "input": "any", 
            "correlationID": "string - Unique identifier of the request...", 
            "senderID": "string - Authenticated sender of the request."
        },
        "id": "string - messageID"
    }

@RobWin
Copy link
Collaborator

RobWin commented Jan 6, 2025

Somehow I have the feeling that this issue was hijacked :)
Please keep JSON RPC out of the discussions.
The web thing protocol and then proposal is first of all a protocol for Websockets.

@VigneshVSV
Copy link

VigneshVSV commented Jan 6, 2025

I like this version much better than the previous/existing strawman proposal of webthing protocol.

There must be a separation between the "type" and the "operation".

@VigneshVSV
Copy link

I would appreciate if somebody here also starts accounting pre-encoded binary payloads into the message.

In JSON, I heard its easier to encode base64 strings, but I think the use of a generic binary payload in a broader sense would be very useful.

This is similar to extracting the buffer value from InteractionOutput in node-wot, without deserializing with an existing known content type of the TD.

@unit9a
Copy link

unit9a commented Jan 6, 2025

Somehow I have the feeling that this issue was hijacked :) Please keep JSON RPC out of the discussions. The web thing protocol and then proposal is first of all a protocol for Websockets.

@RobWin how? this is just a transport concept with json-Rpc. complying with:

On the transport layer below this, the more the merrier. Encodings like json-rpc, bson, protobuf, capn'proto with matching transports like websockets, gRPC, mqtt, and so on, are all good to support. This is at least my thinking with this proposal.
also



The web thing protocol and then proposal is first of all a protocol for Websockets.

@RobWin are you talking about inserting @hspaay web thing protocol proposal's fields/datums in into the websocket message event class and life cycle? if so, then I apologize for not understanding and raising the json-rpc-stuff here.

my json-rpc idea is only meant to be a transport layer for this web thing protocol proposal's data, One that is encoded as the raw binary payload of the websocket message while still being compatible with current json-rpc use.

@RobWin & @hspaay should i move the json-rpc/json-LD transport idea to its own issue?


Also, what do you all think of a pure json-LD representation of this web thing protocol proposal's request, reponse, and notification messages?

@VigneshVSV

In JSON, I heard its easier to encode base64 strings, but I think the use of a generic binary payload in a broader sense would be very useful.

agreed! @RobWin pointed me to https://rsocket.io/ and its use of binary payloads is webSockets. this approach is was i want to pass my json-rpc/json-LD objects into. Also, I came across this in a stackoverflow comment:

however javascript environments (like v8/node.js) are heavily optimized for JSON handling (because it's a subset of javascript).

this, tracks with my experiences, so i dont think binary offers any significant advantage outside of IoT embedded systems constraints. But that still makes it a useful option to have.



There must be a separation between the "type" and the "operation".

i think @hspaay already addressed with:

name data type description required
type string "request". Identifies the message as a request message mandatory
operation string Describes the request to perform mandatory

or am I* not understanding you mean? in json-rpc the separation is done the objects structure.

@RobWin
Copy link
Collaborator

RobWin commented Jan 6, 2025

@unit9a I think that the JSON-RPC discussion should be moved out of this issue. Perhaps it could continue in the WoT Discord channel until someone expresses interest in creating a dedicated Sub-Protocol Community Group.

The scope of this Community Group is outlined in the Web Thing Protocol Charter and includes:

  • Definition of a WebSocket sub-protocol for the Web of Things.
  • Definition of an HTTP sub-protocol for the Web of Things.

Out of scope are:

  • Protocol bindings/sub-protocols for non-web or non-internet protocols.

I believe we should maintain a narrow focus within this Community Group's scope and discussions. While I also see the appeal of reusing the Web Thing Protocol as a sub-protocol for AMQP or MQTT bindings, this is beyond the current scope of this community group.

@benfrancis It might be worth clarifying in the charter document what is meant by "internet protocols" to set clearer boundaries.

As for the HTTP Profile, it is detailed in the HTTP Basic Profile. It follows a RESTful design and does not utilize JSON-RPC. In my understanding, JSON-RPC would constitute a distinct sub-protocol (or profile?) within the HTTP protocol binding. That said, we should aim to limit the number of profiles or sub-protocols for HTTP to preserve interoperability.

Looking forward to further discussions on this topic in the appropriate channels!

@hspaay
Copy link
Collaborator Author

hspaay commented Jan 6, 2025

This is a great discussion. It highlights there are concerns on messaging, transport and encoding levels. Thank you all for your insights.

However, for the sake of keeping this specific discussion on track it is probably good to split the transport and encoding from the messaging issue as @RobWin pointed out.

@unit9a. This issue was originally intended as part of feedback to the websocket strawman proposal by @benfrancis . It offers a simpler alternative to the current message formats that are in the proposal.

The idea is that it can evolve to an application level protocol with various transports and encodings, as your examples show, requires though that the message format is adopted as a stand-alone application protocol. This is definitely out of scope for the strawman proposal which is a subprotocol of the WoT http binding. I don't want to lose this discussion though as it is valuable but it should probably move to another issue, outside of the strawman proposal.

@benfrancis is an application protocol with transport protocols and encodings like discussed here in-scope for the web thing protocol discussion group? If so, should it be considered a separate proposal? What is the process for this?

@benfrancis
Copy link
Member

On the issue of generalising the message payload format across message types I can certainly see the argument. As a Software Engineer I understand the instinct to abstract away similar features into a common parent class/schema, which is something we are literally trained to do (e.g. through object oriented programming).

However, I want to state that the Web Thing Protocol is not intended as a general purpose request/response or publish/subscribe protocol. It is designed for the very specific purpose of communicating with Things on the Web of Things, using the set of WoT operations and WoT native terminology.

It was therefore a conscious design decision to directly map WoT operations onto message types and not to try to design another general purpose protocol. If that's what someone is looking for then there is a very long list of existing WebSocket sub-protocols which try to achieve just that https://www.iana.org/assignments/websocket/websocket.xhtml

I have more to say on this topic and will work my way through all of the responses because I think this is an interesting discussion with some specific points I would like to respond to, but I wanted to reply to your top level post first of all.

To some specific points in your initial post:

@hspaay wrote:

This is proposal 2 draft for standardizing the message envelopes for all protocol bindings

If that is what your proposal is about then I would suggest this is the wrong place for it, it should be proposed in https://github.com/w3c/wot-binding-templates/. However, I don't personally think this payload format can or should be adopted by all protocol bindings. Many protocols have existing fields for many of these members, which is the reason that the vocabularies in protocol binding templates exist - to map WoT concepts onto fields in messages in existing protocols.

WoT protocol bindings all solve the same problem. Send requests, receive responses and send notifications.

I think this is the wrong abstraction. The Web of Things models interactions in terms of "properties", "actions" and "events". Some protocols use a request/response pattern, some protocols use a pub/sub pattern, and some use a combination of both or something entirely different. Protocol bindings map WoT operations onto messages in a given protocol.

Separating the application protocol from the transport enables defining a single application protocol that can be used on any transport.

I think you're going to have a very hard time pushing this "application protocol" onto all the other protocol bindings. Whilst you could argue the payload format makes sense for a raw TCP socket like WebSockets, it doesn't make sense for most of the other protocols used by WoT because it duplicates a lot of the features of existing protocols. For some protocols it would add a redundant extra wrapper around messages and for others it wouldn't be possible to implement at all.

The WoT protocol bindings does not describe how Thing agents connect as a client to a hub or gateway. Instead the assumption is that all Things run a server.

This is not strictly true. The HTTP Webhook Profile assumes that Things are clients and Consumers are servers for example. The terms Consumer and Producer were used in the WoT specifications instead of client and server for this very reason. However, it is true that Thing Descriptions describe how to interact with a Thing using Forms which require URLs for endpoints. This does make it very tricky to describe Things which clients rather than servers.

This is too limited of a view. Hiveot is a Hub where Thing agents are clients to the Hub just like consumers. WoT does not describe the interaction for these agents.

This is really a wider issue for the Web of Things (the Thing Description specification in particular), rather than the Web Thing Protocol. Defining a Web Thing Protocol which can flip the client/server roles does not solve the fundamental problem of how to describe devices in a WoT Thing Description.

readevent, readallevents (of a Thing) [HiveOT extension]

See w3c/wot-thing-description#892

FYI There is some work under the new WoT Working Group charter around querying time-series data.

readtd, readalltds (of a directory or thing) [HiveOT extension]

An API for managing a collection of Web Things is explicitly out of scope for the Web Thing Protocol and is already covered by the Directory Service API in the WoT Discovery specification.

@hspaay
Copy link
Collaborator Author

hspaay commented Jan 6, 2025

Thank you for a very well thought out response @benfrancis. While I might not share the same perspective about this as you do, it is clear that you know exactly what you want to achieve. It looks like this issue has no chance of being adopted in the Strawman proposal, so I won't persue it here any further.

This issue did provide very useful and insightful feedback for which I'm grateful to all commentors. It would be nice IMHO to keep it open as a useful discussion until it is addressed elsewhere.

I will look into defining a separate proposal elsewhere for a WoT application protocol with this message format and the mentioned multiple underlying transports. Let others decide whether they find it useful to them. I'll post the link once it becomes clear where this should go instead. Maybe @egekorkan can point in the right direction.

In the meantime @benfrancis I'll continue with full support for the strawman proposal as it evolves and attempt to provide an implementation in hiveot.

@benfrancis
Copy link
Member

benfrancis commented Jan 7, 2025

@RobWin wrote:

suggest renaming requestID to correlationID.

This would enhance functionality, especially for subscriptions to property changes or events.

Just curious, why would changing the name enhance the functionality? I think we have a general consensus so far that some kind of requestID/correlationID would be a good idea, but I do have some open questions about how that should work when subscribing to events and observing properties.

See #31

For example a stream could be implemented with a reactive programming library like ReactiveX. And the websocket client does not return a single stream of notifications, but could separate notifications into multiple individual streams.
A correlationID (acting as a subscriptionId) can help dispatch notifications to the appropriate stream .

I don't have experience with ReactiveX and I'm not exactly sure what you mean by multiple individual streams. Is your intention that a Consumer should be able to have multiple subscriptions to the same event at any one time, with each subscription identified by a unique ID and individually unsubscribable? If so what benefit would that bring which justifies the added implementation complexity of managing multiple subscriptions per Consumer?

See #29

@hspaay wrote:

I just completed implementing this proposal in hiveot as an application layer. The http/wss/mqtt transports maps from the three message types to the specified message types for that transport.

What do you mean by "mapping" to a "transport"? How do you map a notification to SSE for example? Are you sending the whole message envelope defined above inside the data field of an SSE event for example? Or are you somehow using the event and id fields as well? If it's the former then why aren't you using the built-in features of the protocol? If it's the latter that isn't so much a "transport" as a "binding" and no different to the HTTP SSE Profile, it's just an internal implementation detail.

Using 'operation' in the messages also works quite well to further process the message at the application level. There is no tension (mapping, translation) between protocol and application, and no need to define separate message types to deal with responses.

I've mentioned before that I started out with the approach of using operation names to differentiate message types but found the need for other message types (like propertyReading, propertyReadings, actionStatus, event, ping and pong) which don't have a 1:1 mapping with operations. I can see that having three message types of "request", "response" and "notification" with a separate operation field is quite neat, but it does assume exactly one request, response and notification message type for each WoT operation and no other message types.

This works well with intermediaries like a hub. Prior there was a struggle to mentally keep track of who is sending/receiving what and in reply to what. That problem has pretty much disappeared. A request goes from consumer to Thing, a response the other way and notifications are send and forget by the Thing.

Would a requestID in all messages solve this problem?

Not having to do the mapping of messages types saves a lot of code.

Surely you still have to map operations onto messages and back, the format of the messages is just different...

Forms are also simpler. No need to have a form entry for each operation.

Why did you need multiple form entries for each operation before? Have you seen the example Thing Description in the strawman proposal which has one form per interaction affordance? That is actually one of the benefits of the WebSocket sub-protocol compared with a declarative HTTP binding for example.

I haven't run into a need to define forms with the affordances either.

Are you aware that in a Thing Description the forms member of an InteractionAffordance is mandatory and is not allowed to be empty?

@RobWin wrote:

But from a protocol level, I still miss acknowledgements.

See #29

@unit9a wrote:

do you even think json rpc is relevant at all?
Also, what do you all think of a pure json-LD representation of this web thing protocol proposal's request, reponse, and notification messages?

I can definitely see the similarity with @hspaay's proposal (except that in JSON-RPC notifications usually work in the opposite direction). I can certainly imagine something that looks like the WoT Scripting API implemented over JSON-RPC, but I'm not personally interested in that as a design for similar reasons to those given above. I would rather not abstract everything as a method.

@VigneshVSV wrote:

There must be a separation between the "type" and the "operation".

Why?

I would appreciate if somebody here also starts accounting pre-encoded binary payloads into the message.
In JSON, I heard its easier to encode base64 strings, but I think the use of a generic binary payload in a broader sense would be very useful.

This is one of the limitations of JSON, and yes base64 encoding values as strings is a common solution to that problem.

There are alternatives like BSON, CBOR and Protocol Buffers which don't have this problem. However:

  1. The current requirements document specifies that all messages in the WebSocket sub-protocol should be serialised in JSON
  2. Because WoT Thing Descriptions use JSON Schema for data schemas, as far as I know the only way to describe binary payloads is as a string anyway

I'm not inclined to switch away from JSON for this reason alone, since its ubiquitous support brings a lot of benefits and it being the default serialisation assumed in Thing Descriptions makes it the obvious choice.

That said, the charter does allow for the "Evaluation of other potential Web of Things sub-protocols (e.g. for CoAP)". I can imagine a future variant of the Web Thing Protocol using CoAP + CBOR for constrained devices that would struggle with WebSockets + JSON, but that is not the current focus.

@RobWin wrote:

@benfrancis It might be worth clarifying in the charter document what is meant by "internet protocols" to set clearer boundaries.

I think it's clear that an "internet protocol" is anything that works over IP. Much trickier to define is a "web protocol". A broad definition would be anything with a URI scheme registered with IANA, but I tend to have a much stricter view and haven't yet come across anything other than HTTP and CoAP that I would describe as a web protocol. Even WebSockets is stretching the definition of "web" for me, but that's another story.

JSON-RPC can be used used as a sub-protocol of both HTTP and WebSockets so arguably does fall within that scope, it's just not something I'm personally interested in. It could make an interesting protocol binding template though.

That said, we should aim to limit the number of profiles or sub-protocols for HTTP to preserve interoperability.

I strongly agree with this.

@hspaay wrote:

for the sake of keeping this specific discussion on track it is probably good to split the transport and encoding from the messaging issue as @RobWin pointed out

👍

@benfrancis
Copy link
Member

benfrancis commented Jan 7, 2025

@hspaay wrote:

@benfrancis is an application protocol with transport protocols and encodings like discussed here in-scope for the web thing protocol discussion group?

No. But let me explain why because I think it's important.

The Web of Things is basically designed to be everything-agnostic. Protocol agnostic, serialisation format agnostic and programming language agnostic. If you wanted to you could create a valid "Web Thing" which communicates entirely using animated GIFs over the IRC protocol! Depending on who you listen to this is either its biggest strength or its biggest weakness.

The benefit of this approach is that it theoretically makes it possible to describe any brownfield device using any existing IoT protocol and have it be a valid Web Thing.

The downside of this approach is that any given WoT Consumer can only communicate with a tiny subset of all the possible Web Things in the world. Imagine a 2-dimensional matrix of all the possible protocols and payload formats and the proportion of combinations supported by a given Consumer implementation will be tiny. Now expand this by n dimensions to allow for the other WoT extension points like security mechanisms, discovery mechanisms, link relation types and semantic contexts. Now you have a serious interoperability problem.

The mission of this community group is to "define a common protocol for communicating with connected devices over the web, to enable ad-hoc interoperability on the Web of Things."

The ideal outcome would be a single protocol that all greenfield WoT implementations can opt into using in order to benefit from complete out-of-the-box interoperability.

As soon as you start abstracting things into layers and talk about alternative transport protocols and encodings you have immediately fragmented that landscape again, which is counter to the mission of the group.

The charter takes a slightly more pragmatic view than this by allowing for both an HTTP sub-protocol and WebSocket sub-protocol (because HTTP has some significant limitations for IoT use cases), and for the exploration of other potential WoT sub-protocols "e.g. for CoAP" for use cases where the other two options are simply not possible to use.

Apart from that, the main thing that concerns me about your proposal in this issue is that by generalising into requests, responses and notifications you are basically re-inventing HTTP+SSE (or CoAP+Observe) over WebSockets. The Web Thing Protocol WebSocket sub-protocol is intended for the very specific purpose of communicating with connected devices over the Web [of Things], not a general purpose application protocol for the web, which is why my strawman proposal directly maps WoT concepts onto messages.

Genuine suggestion: Rather than try to re-invent HTTP+SSE over WebSockets, how about you (or we) take the underlying features we need to the IETF task force that works on the HTTP protocol and propose them for the next version of HTTP?

If the next version of HTTP supported persistent connections, multiple requests and responses over the same socket and messages pushed from the server to the client then we arguably wouldn't need a WebSocket subprotocol. We could just define a default HTTP/4 (?) protocol binding instead. I would actually prefer that to having separate HTTP and WebSocket sub-protocols.

HTTP/2 has a push feature but which doesn't really support our use cases, and HTTP/3 supports multiplexing over QUIC. But as far as I can tell not even HTTP/3 does everything that we need.

In the meantime, we have JSON and WebSockets.

@RobWin
Copy link
Collaborator

RobWin commented Jan 7, 2025

Just curious, why would changing the name enhance the functionality? I think we have a general consensus so far that some kind of requestID/correlationID would be a good idea

I think the the term correlationId is more fitting than requestId because it better captures the broader purpose of the identifier. While requestId implies a straightforward, one-to-one request-response interaction, correlationId highlights its role in linking related messages across various communication patterns.

This distinction is particularly relevant for scenarios like subscriptions and property observations, where a single request may lead to multiple or continuous responses over time. For instance, event messages in a subscription or propertyReading messages in an observation context are not one-time responses but part of an ongoing stream, making correlationId a more accurate descriptor.

Moreover, the name correlationId is versatile and can support diverse communication patterns:

  • Request/Reply: A single request corresponds to a single response.
  • Client-Streaming: Multiple request messages result in a single response. All messages share the same correlationId, including the response. (Currently not needed in WoT, but maybe in the future?)
  • Server-Streaming: A single request leads to a continuous stream of responses. All response messages in the stream share the same correlationId. (Example event subscription or property observation)

But this implies that "request" messages would also have a dedicated correlationId field, distinguishing it from the messageId, which is used to uniquely identify individual messages. The correlationId serves to group related messages together, providing a clear link across different messages, while the messageId identifies each individual message within that group.

AMQP it is called correlation-id.
In MQTT 5 request-response is called correlation data

Is your intention that a Consumer should be able to have multiple subscriptions to the same event at any one time, with each subscription identified by a unique ID and individually unsubscribable?

No, my intention is that a Consumer can have subscriptions to multiple events (or observations to multiple properties), each subscription resulting in a dedicated (reactive) event stream that can be individually canceled (unsubscribed from).
Upon receiving an event or property change message, the consumer code dispatches it to the appropriate stream. Using the property or event name alone as a unique key isn’t sufficient for managing these streams. For example, imagine a Consumer subscribes to an event or property, then unsubscribes and subscribes again rapidly. If the event or property name is the only identifier, the system can't clearly differentiate between the initial and subsequent subscriptions, leading to race conditions where an event or property message might be dispatched to the wrong (cancelled) stream. Without a correlationId in messages the code is a little more difficult to be implemented in a thread-safe manner.

@hspaay
Copy link
Collaborator Author

hspaay commented Jan 8, 2025

@benfrancis

Apart from that, the main thing that concerns me about your proposal in this issue is that by generalising into requests, responses and notifications you are basically re-inventing HTTP+SSE (or CoAP+Observe) over WebSockets.

Thank you for elaboration Ben. You've mentioned this before and unfortunately I don't agree whether this is true and whether this is even relevant.

The proposed messages are clearly application level as they are tailored for messaging related to things with properties, events and action affordances. This is not a general purpose message format.

The websocket messages defined in the original proposal are very similar to this approach. Instead of defining a message per operation (and sometimes a separate message for its response), this proposal groups messages by their intention (request, response, notification). The payload is also similar but instead of changing the member names to match the affordance (event, property, action) it uses 'name'. In both cases you have to decode the message to determine further processing.

I don't see how this even gets close to reinventing http/sse or coap/observe over websockets. The application should not be driven by the underlying protocol but the use of the protocol should be driven by the need of the application. Top down, not bottom up. It really doesn't matter what features the underlying protocol support and it whether they are all used. What matters is that a WoT application can invoke an action, write a property, get a response and receive notifications. These application level concerns are expressed in the proposed messages.

I hope this kinda helps explain my perspective. I'll continue exploring both points of view.

@benfrancis
Copy link
Member

@RobWin wrote:

But this implies that "request" messages would also have a dedicated correlationId field, distinguishing it from the messageId, which is used to uniquely identify individual messages.

Just checking, should "request" read "response" here? Is the idea that request messages only contain a messageID but response messages contain both a messageID and a correlationID? Or would request messages contain both as well?

@RobWin
Copy link
Collaborator

RobWin commented Jan 8, 2025

No, I meant that some messaging protocols allow both a unique message ID and a correlation ID even in request messages. Not only in response messages.

These fields serve distinct purposes:

  • The message ID uniquely identifies each message, ensuring traceability and de-duplication.
  • The correlation ID links messages within a specific context, such as a request-response flow or a group of related messages.

For example, in a "multiple requests, single response" pattern, the correlation ID could be used to group multiple request messages under a shared context, while each request retains its own unique message ID.

In such messaging protocols, the consumer of the request typically copies the correlation ID from the request into the correlation ID field of the response, instead of using the message ID. This ensures the responder maintains the context established by the requester, enabling the requester to match responses to the original requests.

@benfrancis
Copy link
Member

benfrancis commented Jan 8, 2025

@hspaay wrote:

I don't see how this even gets close to reinventing http/sse or coap/observe over websockets.

OK fair enough, that is probably an exaggeration and you are right that there are lots of application specific members in your proposed message format.

I think what I'm trying to get at is that what I set out to do was to create a protocol with a very direct representation of WoT operations as messages, without needing a binding to map those operations onto the terminology and concepts of an existing protocol.

The generalisation of "requests", "responses" and "notifications" proposed in this issue feels like an additional layer of abstraction above WoT concepts.

In particular, although "response" is a term used in the WoT specifications (e.g. the response and additionalResponses members of Forms), the specifications don't have such a clear concept of a "notification" as distinct from a "response".

E.g. is the actionStatus message from the current strawman proposal a "response" or a "notification"? Some actions could immediately respond with a single completed status response message, but some long running actions could result in multiple actionStatus messages being sent each time to the status of the action changes (pending -> running -> completed -> failed). (See the distinction between synchronous and asynchronous actions in the HTTP Basic Profile). If an actionStatus message is in response to a queryAction message it's a response, but if it's in response to an invokeAction message it's more like a notification.

Having said all of the above, I do like the idea of having an "operation" member which directly takes an operation name from a Thing Description, and a consistent "name" member across interaction affordances (although that one is a bit less clear). I don't like property values being referred to as "input" and "output" though, because those terms are only used in relation to actions in the WoT specifications.

I will give it some more thought.

@benfrancis
Copy link
Member

@RobWin wrote:

No, I meant that some messaging protocols allow both a unique message ID and a correlation ID in request messages. These fields serve distinct purposes

Ah OK. I think in all current WoT use cases the messageID from a request could be used as the correlationID in a response, but you're considering another use case we don't currently have where they may be multiple correlated request messages.

I see the rationale, but I am also reluctant to require three different ID members in every message if they are not needed.

@RobWin
Copy link
Collaborator

RobWin commented Jan 8, 2025

Why 3? You meant the thingId as well?

@benfrancis
Copy link
Member

Why 3? You meant the thingId as well?

Yep

@hspaay
Copy link
Collaborator Author

hspaay commented Jan 8, 2025

@benfrancis wrote:

Genuine suggestion: Rather than try to re-invent HTTP+SSE over WebSockets, how about you (or we) take the underlying features we need to the IETF task force that works on the HTTP protocol and propose them for the next version of HTTP?

I'm honored that you think I can be of any help there. My skills at the bit level have deteriorated over time unfortunately and my focus is on hiveot. @RobWin seems to be much better at this though so maybe he would be able to help 😄

@benfrancis wrote:

I see the rationale, but I am also reluctant to require three different ID members in every message if they are not needed.

Yes, I don't see a use-case to always require the messageID, so IMHO it can be optional in requests. @RobWin has done an excellent job describing use-cases in #35. We can probably continue the messageID discussion over there.

OK fair enough, that is probably an exaggeration ..

Chuckle 😄

I think what I'm trying to get at is that what I set out to do was to create a protocol with a very direct representation of WoT operations as messages, without needing a binding to map those operations onto the terminology and concepts of an existing protocol.
The generalisation of "requests", "responses" and "notifications" proposed in this issue feels like an additional layer of abstraction above WoT concepts.

Yes, and the strawman proposal does exactly that. Can't argue on that. This hiveot proposal doesn't deviate that much on the request/notification side other than a minor generalization.
I wasn't sure at first either if this 'generalization' would be a help or hindrance in using it, so I implemented both approaches. (yeah that was my December :)) I was surprised how much easier the implementation became (coding, testing, debugging) using just the 3 messages. Most likely because the post processing after unmarshalling the message has to be done by the application anyways and it didn't make much of a difference there. If anything it simplified debugging. There is just less stuff to code with the 3 messages.

In particular, although "response" is a term used in the WoT specifications (e.g. the response and additionalResponses members of Forms), the specifications don't have such a clear concept of a "notification" as distinct from a "response".
E.g. is the actionStatus message from the current strawman proposal a "response" or a "notification"?

The lack of a clear specification on action acknowledgement is true, but to me that is more of an omission than intent. The same goes for acknowledgement of subscriptions and property write request as discussed elsewhere.

Hiveot needs these to function properly so I have no choice other than support it somehow. My choice is to incorporate in the messaging in the hope that WoT 2.0 will take notice and adopt some of these ideas. This doesn't take away from the use of these messages for things that are defined in the TD, so nothing lost. It opens the door to standardization of these improvements in the future. Hence this proposal.

Bottom line is that I can't just say, sorry no ack because the spec doesn't mention it. I'm also looking at the gateway/hub use-case which is quite a bit more demanding than simply consumer->Thing.

Wrt terminology overloading. 'Response' is widely used in many protocols so I wasnt thinking it to be a problem. I'm open to renaming though if it helps make things clearer.

E.g. is the actionStatus message from the current strawman proposal a "response" or a "notification"? Some actions could immediately respond with a single completed status response message, but some long running actions could result in multiple actionStatus messages being sent each time to the status of the action changes

In this proposal the action status message is replaced with a response message, which has a status field. There is no need for an actionStatus message or propertyReading message, which can be considered unnecesary artifacts. The combination of message type 'ResponseMessage' and the original operation is all that is needed. I haven't run into a problem with this.

The hiveot http binding (golang) implementation in hiveot supports both an immediate response from the http and async responses via SSE. In golang this is easily done using channels. Direct response is posted on the result channel as are asynchronously received responses. The application waits for a response by listening on the channel and therefore doesnt care how it gets there. The 'status' field indicates if more responses are to be expected. Once status is completed the last response has been received.

Clearly this needs much better documentation that I've provided thus far. Sequence diagrams for various use-cases should clarify the simplicity of this approach. This is in the works.

Anyways, I hope this helps explaining this can actually work. If a little demo down the road would help, along with documentation, I'd be happy to present.

In the meantime, I'm giving the full support to the strawman proposal. This hiveot proposal is in my eyes an evolution, which can be included or be a separate protocol. Its all good.

Thanks for the feedback and comments Ben and Rob.

@unit9a
Copy link

unit9a commented Jan 9, 2025

@hspaay why is there no dedicated Error message type? I am not opposed to how it is now. can you give an example of how a response object with both error, output fields can be handled?

my assumption is that:

  • a response with both fields means the errors should be treated as warnings.
  • a response with only error should be treated as exceptions.

having a dedicated error response is more explicit but might not be as efficient. does it make for a better developer/debugging experience?

just curious and learning.

@RobWin
Copy link
Collaborator

RobWin commented Jan 9, 2025

I’m not referring to this specific proposal here, but just to inform you, the Web Thing Protocol Strawman Proposal adopts dedicated error messages aligned with the Problem Details for HTTP APIs specification, a widely used standard for HTTP APIs. The WoT HTTP Profile also utilizes this approach, as detailed in Error Responses in WoT HTTP Profile.

One could argue that the RFC is too complex for most use cases.

@hspaay
Copy link
Collaborator Author

hspaay commented Jan 9, 2025

@unit9a wrote:

@hspaay why is there no dedicated Error message type?

The motivation is that an error is also a response. The response has a status field with predefined values, one of them is 'failed'. If the status field holds failed then the error field holds the error title and the 'output' can hold additional detail.

I'm on the fence whether the 'error' field is even needed and whether the output field should contain the error if status is failed. The only reason that wasn't done is that the http error handling recommends a separate details field, so there are use cases where more information is useful. It is not the intent to implement RFC9457 btw.

It is of course possible to define a separate error message as the strawman proposal does here. Nothing wrong with that. For the proposed minimalistic application protocol this isn't needed though as explained.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants