w3c / wot-binding-templates

Web of Things (WoT) Binding Templates
http://w3c.github.io/wot-binding-templates/
Other
22 stars 25 forks source link

[http] Differentiating sse endpoint #342

Open egekorkan opened 7 months ago

egekorkan commented 7 months ago

We are not very clear how one would differentiate different forms when there is the same HTTP endpoint but one form has readproperty and the other one has observeproperty. It is not possible to have that in longpolling but it is possible in sse (more on it later). More specifically, we can have the following property:

"properties": {
    "result": {
      "type": "number",
      "readOnly": true,
      "writeOnly": false,
      "observable": true,
      "forms": [
        {
          "href": "properties/result",
          "contentType": "application/json",
          "op": "readproperty"
          ]
        },
        {
          "href": "properties/result",
          "contentType": "text/event-stream", // should it be application/json ?
          "op": "observeproperty",
          "subprotocol": "sse"
        }
      ]
    }

This is technically possible since the Consumer can set the Accept header to "text/event-stream" and the Thing can differentiate based on the header and deliver correct value or stream. In this case, we should probably have the second form looking like the following:

        {
          "href": "properties/result",
          "contentType": "text/event-stream",
          "op": "observeproperty",
          "subprotocol": "sse",
          "htv:methodName": "GET",
          "htv:headers": [
            {
              "@type": "htv:RequestHeader", // not sure about that
              "fieldValue": "text/event-stream",
              "fieldName": "Accept"
            }
          ]
        }

I think that this header should be a default value when there is subprotocol:sse since the EventSource spec says that it is a MAY and not a MUST to send this header (see step 10 at https://html.spec.whatwg.org/multipage/server-sent-events.html#the-eventsource-interface under the green box). Also, it is not clear how this should look like in code. In node-wot, HTTP binding simply uses the event source library at https://github.com/eclipse-thingweb/node-wot/blob/master/packages/binding-http/src/subscription-protocols.ts#L112 . The library however allows headers to be passed at https://github.com/EventSource/eventsource/blob/master/lib/eventsource.js#L35 .

Maybe this is an implicit knowledge (also see https://eclipse.dev/ditto/httpapi-sse.html), but I got into the details for the first time. We should at least document this but more behavior description would be nice in this case.

lu-zero commented 5 months ago

I guess more generally we should suggest to always request the content type if more than 1 content type is available for a declared endpoint+verb. We should advise it even more strongly on the subprotocol specifications if they could be multiplexed.

relu91 commented 5 months ago

I think this question:

  {
          "href": "properties/result",
          "contentType": "text/event-stream", // should it be application/json ?
          "op": "observeproperty",
          "subprotocol": "sse"
        }

It deserves its own issue.

lu-zero commented 5 months ago

To be noted:

Otherwise, if res's status is not 200, or if res's [Content-Type](https://html.spec.whatwg.org/multipage/urls-and-fetching.html#content-type) is not [text/event-stream](https://html.spec.whatwg.org/multipage/iana.html#text/event-stream), then fail the connection.

The contentType has to be text/event-stream. So it is implicit in subprotocol:sse and it is an hard discriminant between http-subprotocols.

benfrancis commented 5 months ago

This is implemented in WebThings Gateway (Producer) and Krellian Cloud (Consumer).

WebThings Gateway always exposes readproperty, writeproperty and observeproperty operations on a given Thing using the same endpoint URL. The server differentiates between requests based on HTTP method and Accept header (i.e. standard HTTP Content Negotiation). The Thing Descriptions expose separate Forms for ["readproperty", "writeproperty"] and ["observeproperty", "unobserveproperty"] operations, but it doesn't actually include a contentType member in either Form because application/json is the default and text/event-stream is implied by the subprotocol member being set to sse.

The same approach can be used to differentiate between endpoints for readallproperties/writeallproperties and observeallproperties/unobserveallproperties. E.g. this example taken directly from the WoT Profiles specification:

  "forms": [
    {
      "op": ["readallproperties", "writemultipleproperties"],
      "href": "properties"
    },
    {
      "op": ["observeallproperties", "unobserveallproperties"],
      "href": "properties",
      "subprotocol": "sse"
    },
    {
      "op": "queryallactions",
      "href": "actions"
    },
    {
      "op": ["subscribeallevents", "unsubscribeallevents"],
      "href": "events",
      "subprotocol": "sse"
    }
  ]
benfrancis commented 5 months ago

See also: https://github.com/w3c/wot-profile/issues/398