Conditional GET

TROLIE implementations MUST support the Conditional GET pattern and clients should use it to determine when limits snapshots are available. While this pattern is well-documented by Mozilla, it can be helpful see a concrete example. In this article we will discuss this pattern in the context of obtaining a Forecast Limits Snapshot.

By employing the Conditional GET pattern, the client can efficiently determine when a new version of the resource is available without having to repeatedly download the entire resource during polling. This helps in reducing unnecessary network traffic and conserving bandwidth.

This pattern involves using the HTTP GET method along with conditional headers such as If-None-Match and If-Modified-Since to check if the resource located at /limits/forecast-snapshot has been modified since the last request.

Let’s start with this initial request:

1
2
3
4
5
GET /limits/forecast-snapshot HTTP/1.1
Host: trolie.example.com
User-Agent: TROLIE-Examples-Client
Accept: application/vnd.trolie.forecast-limits-snapshot.v1+json
Accept-Encoding: br

Suppose this is the response from the TROLIE server:

1
2
3
4
5
6
7
8
9
10
HTTP/1.1 200 OK
Content-Type: application/vnd.trolie.forecast-limits-snapshot.v1+json
Server: trolie.example.com
Date: Wed, 29 Feb 2024 12:00:00 GMT
ETag: "d41d8cd98f00b204e9800998ecf8427e"
X-Rate-Limit-Limit: 100
X-Rate-Limit-Remaining: 98
X-Rate-Limit-Reset: 3600

{... response body in Brotli compressed format ...}

When using the Conditional GET pattern, the client includes the previously received ETag and/or Last-Modified timestamp in the request headers. If the resource has not been modified since the provided ETag or Last-Modified timestamp, the server responds with a 304 Not Modified status code, indicating that the client’s cached version is still valid. If the resource has been modified, the server responds with a 200 OK status code and provides the updated resource.

The TROLIE client can then issue a Conditional GET:

1
2
3
4
5
6
GET /limits/forecast-snapshot HTTP/1.1
Host: trolie.example.com
User-Agent: TROLIE-Examples-Client
Accept: application/vnd.trolie.forecast-limits-snapshot.v1+json
Accept-Encoding: br
If-None-Match: "d41d8cd98f00b204e9800998ecf8427e"

Assuming a new snapshot hasn’t been generated, we should see a response similar to the following:

1
2
3
4
5
6
7
HTTP/1.1 304 Not Modified
ETag: "d41d8cd98f00b204e9800998ecf8427e"
Server: trolie.example.com
Date: Wed, 29 Feb 2024 12:03:20 GMT
X-Rate-Limit-Limit: 100
X-Rate-Limit-Remaining: 97
X-Rate-Limit-Reset: 3400

Note: The X-Rate-Limit-Remaining is decremented here, since rate limiting is applied in all circumstances, including 3XX and 4XX responses.

Otherwise, we would have gotten a new limit forecast:

1
2
3
4
5
6
7
8
9
10
11
HTTP/1.1 200 OK
Content-Type: application/vnd.trolie.forecast-limits-snapshot.v1+json
Server: trolie.example.com
Date: Wed, 29 Feb 2024 12:03:20 GMT
ETag: "123e4567e89b12d3a456426614174000"
X-Rate-Limit-Limit: 100
X-Rate-Limit-Remaining: 97
X-Rate-Limit-Reset: 3400
Content-Encoding: br

{... response body in Brotli compressed format ...}

Note to Implementors

The use of If-None-Match with ETag headers is strongly encouraged to implement the Conditional GET pattern. The ETag should not be a simple hash of a particular representation of a resource, i.e., do not compute a hash of the JSON document returned to the client. Instead the ETag should be unique to the logical state of the resource. One method to do that is illustrated in this pseudocode:

1
ETag: hash("{resource internal id}+{last modified timestamp}")

This has several desirable properties for an ETag:

  • Unique: Minimizing the chance of collisions for different resources.
  • Opaque: Clients cannot infer anything about the resource content or structure from the ETag.
  • Stable: Changes only when the the resource is updated.