HTTP semantics
Trace a client request and design an HTTP contract with clear failure, retry and pagination behavior.
THE IDEA
HTTP semantics
HTTP's methods, status codes, headers and representations form a contract. Safe methods (GET, HEAD) are meant not to change server state. Idempotent methods (GET, PUT, DELETE) have the same intended effect when repeated. That is about the effect, not the response bytes: the first PUT may answer 201 Created and the second 200 OK. POST is neither, which is why a retry layer cannot blindly repeat it.
| method | safe | idempotent |
|---|---|---|
| GET | yes | yes |
| HEAD | yes | yes |
| OPTIONS | yes | yes |
| PUT | no | yes |
| DELETE | no | yes |
| POST | no | no |
| PATCH | no | no |
Safe describes the intended effect on state (reads). Idempotent describes the effect of repeating (the state ends the same). A retry layer needs these properties to decide what it may repeat.
Predict first. Must two idempotent requests always return byte-identical responses?
Chapter context
Clients communicate through name resolution, connections, transport security and application protocols. API semantics determine what a caller can safely infer or repeat when a network interruption hides an operation's outcome.
TRY IT YOURSELF
Part 1. Do it hands-on
Classify methods by hand, then write the method table, retry rule and a PUT handler.
| method | safe | idempotent |
|---|---|---|
| GET | yes | yes |
| HEAD | yes | yes |
| OPTIONS | yes | yes |
| PUT | no | yes |
| DELETE | no | yes |
| POST | no | no |
| PATCH | no | no |
Safe describes the intended effect on state (reads). Idempotent describes the effect of repeating (the state ends the same). A retry layer needs these properties to decide what it may repeat.
- To do: Is GET a safe method (not intended to change server state)? Type yes or no
- To do: Is PUT idempotent (repeating it has the same intended effect as doing it once)? Type yes or no
- To do: Is POST (create) idempotent by definition? Type yes or no
- To do: Must two idempotent requests always return byte-identical responses? Type yes or no
Part 2. Do it in code
- METHODS = {"GET": (safe, idempotent), ...} (PROVIDED for GET, HEAD, OPTIONS, PUT, DELETE, POST, PATCH).
- is_safe(method) and is_idempotent(method): from the table; case-insensitive; ValueError for an unknown method.
- status_class(code): "informational" 1xx, "success" 2xx, "redirect" 3xx, "client_error" 4xx, "server_error" 5xx; ValueError otherwise.
- may_retry(method, outcome): outcome is "timeout" or a status code. Retry only if the method is idempotent AND (the outcome is a timeout or a 5xx).
- put(store, key, value): store[key] = value; return 201 if the key was new, 200 if it replaced an existing value (the stored state is identical either way).
Stuck, or something not working? You can come back to it any time.
CHECK YOUR UNDERSTANDING
Must two idempotent requests always return byte-identical responses?
Chapter reference: math, worked example & code
This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.
Math and notation
For k sequential calls with round-trip latency r, the network component alone is approximately k*r under a simple fixed-RTT model. Independent parallel calls have an ideal max-path cost instead. This excludes connection setup, payload transfer, queueing and application computation.
Worked example
Three required sequential remote calls with 40 ms round trips consume about 120 ms of round-trip time before server work. For a separate write example, processing request key op-7 twice should create one logical result if the idempotency contract is implemented atomically. The calculation below is an offline model, not a distributed storage implementation.
Reference code
The following example uses the Python standard library and does not call a remote service.
assert 3*40 == 120
seen={}
def create(key,value):
if key in seen:
if seen[key] != value: raise ValueError('key reused with different intent')
return seen[key]
seen[key]=value
return value
assert create('op-7','order-1') == create('op-7','order-1')
assert len(seen) == 1
Python examples are provided for study and local execution; this page does not execute Python.
Common mistakes
A timeout is not proof of rollback. HTTP transport security does not authorize object access. An in-memory deduplication dictionary does not supply durability or atomicity across servers.