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.

note 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 re-created by your request.

What are conditional requests?

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 do 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

Examples

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

Create - write only if the resource does not exist

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 it, if it does not yet exist. However, 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.

Update - write only if the resource already exists

In some cases, you want to make sure, that a thing already exists before PUTing 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.

Optimistic locking

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 your change 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.

tip 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.

Caching - avoid unnecessary resource reloads

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

Given, you have 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.
Imprint Legal info Privacy statement