How to work with conditional requests?

Bosch IoT Things supports conditional requests based on the entity tag - ETag in short.

Once a thing entity is created, it gets the ETag - holding the revision number.

In case you create a new thing expect the revision number to be one, e.g. rev:1.
However, in case you get another revision number, this suggests that the thing either existed already, or was deleted meanwhile, and recreated by your request.

Sometimes it is convenient and more fault-tolerant to create, modify or delete Things or elements of Things only if these elements either already exist or not exist or are on a specific revision state prior to the execution of the request. Therefore, we introduced support for “conditional requests” based on RFC-7232.

Using conditional requests, you can define a condition - or a list of conditions - and pass these via headers to the Things service. Our system checks the conditions and only processes the request in case they match.

The supported conditional headers are:

  • If-Match
    Read or write the resource only
    • if the current entity tag matches at least one of the entity tags provided in this header
    • or if the header is * and the entity exists
  • If-None-Match
    Read or write the resource only
    • if the current entity tag does not match any of the entity tags provided in this header
    • or if the header is * and the entity does not exist

Find examples on using such headers at
How to work with conditional requests?

 

In this tutorial we show how you can benefit of their functionality in various use cases.

A PUT on the Things resource by default overwrites the Thing.

In some cases, e.g. provisioning of new things, you want to make sure that you do not overwrite an already existing Thing by mistake. Of course, you could just try to GET the Thing and only PUT if it does not yet exist, but there are two disadvantages with this approach:

  • You need two requests (GET and PUT) instead of just one PUT
  • Using two requests is not atomic (in theory, someone else could create the Thing between the two requests)

Using the If-None-Match header with value *, you can do that in just one atomic request

For simplicity, we just create an empty Thing:

$ curl -i -X PUT -H 'If-None-Match: *' \
$ --data-binary '{}' https://things.eu-1.bosch-iot-suite.com/api/2/things/org.example.namespace:thing-01-02-03

You will get one of the following responses:

  • 201 (Created) - in case the creation was successful,
    i.e. the Thing did not yet exist.
  • 412 (Precondition Failed) - in case the creation failed,
    i.e. a Thing with the exactly same thingId org.example.namespace:thing-01-02-03 already exists.

In some cases, you want to make sure that a thing already exists before PUTting new content.

If you could not assure this, you might accidentally create a new thing. For this purpose, you can use the If-Match header with value *:

$ curl -i -X PUT -H 'If-Match: *' \
$ --data-binary '{}' https://things.eu-1.bosch-iot-suite.com/api/2/things/org.example.namespace:thing-01-02-03

For this request, you will get one of the following responses:

  • 204 (No Content) - in case the update was successful,
    i.e. the Thing already existed and was successfully updated.
  • 412 (Precondition Failed) - in case the update failed,
    i.e. the Thing with thingId “org.example.namespace:thing-01-02-03” does not yet exist.

Most applications need to make sure that data is not overwritten by concurrent requests.

You can use the If-Match header to implement optimistic locking with following steps

First, you GET the thing.

$ curl -i https://things.eu-1.bosch-iot-suite.com/api/2/things/org.example.namespace:thing-01-02-03

In the response, you will get both: the current data and the ETag:

HTTP/1.1 200 OK
...
ETag: "rev:2"
...
{
"thingId": "org.example.namespace:thing-01-02-03",
"policyId": "org.example.namespace:thing-01-02-03",
"attributes": {
  "manufacturer": "ACME crop",
  "otherData": 4711
 }
}

Given that you have detected the typo in the manufacturer attribute (“ACME crop”) and want to fix this with a top-level thing PUT. You want to make sure, that no one else has modified the thing in the meantime, because otherwise yours would overwrite his changes.

To achieve this, PUT the thing with the changed data and the ETag from the preceding GET response (“rev:2” in this case) as If-Match header:

$ curl -i -X PUT -H 'If-Match: "rev:2"' \
$ --data-binary '{
    "attributes": {
      "manufacturer": "ACME corp",
      "otherData": 4711
     }
}' https://things.eu-1.bosch-iot-suite.com/api/2/things/org.example.namespace:thing-01-02-03

You will get one of the following responses:

  • 204 (No Content) - in case your update was successful,
    i.e. no one else has changed the thing in the meantime.
  • 412 (Precondition Failed) - in case the update was not successful,
    i.e. the thing has been changed by someone else in the meantime.

You can also apply the Optimistic Locking on DELETE requests. This way you can make sure that you do not delete a resource, which has been changed by someone else in the meantime.

To avoid unnecessary reloads of HTTP resources, you can use the If-None-Match header with a concrete ETag (the one you have currently loaded). This is basically how browsers are caching websites.

Given you have a retrieved a thing with the ETag “rev:2”. Later on, to only reload the thing if it has been changed meanwhile, you would issue the following request:

$ curl -i -H 'If-None-Match: "rev:2"' https://things.eu-1.bosch-iot-suite.com/api/2/things/org.example.namespace:thing-01-02-03

In this case, you will get one of the following responses:

  • 200 (OK) - in case the thing has been changed, with the new Thing data in the body.
    In such a case you should expect the ETag “rev:3” or bigger
  • 304 (Not Modified) - without body - in case the thing has not been changed,
    i.e. latest ETag is still “rev:2”.