Chris St. Pierre - @chris_st_pierre
REpresentational
State
Transfer
But why?
import sandwiches
sandwiches.make_me_a_sandwich("ham")
curl -X POST -d '{"type": "ham"}' \
https://sandwiches.example.com/sandwiches
GET http://example.com/v1/pets/ HTTP/1.0
{ "pets": [ { "id": "a8533344-6371-4982-a86b-722331839514", "links": [ { "href": "/v1/pets/a8533344-6371-4982-a86b-722331839514", "rel": "self" } ] }, { "id": "00182d56-9981-402b-821e-7b1c2906533c", "links": [ { "href": "/v1/pets/00182d56-9981-402b-821e-7b1c2906533c", "rel": "self" } ] }, { "id": "18e1588a-090e-4ace-94c2-f289617de0cb", "links": [...] }, { "id": "4dc4d7b0-a386-4919-9878-47d4eb8f49ec", "links": [...] }, { "id": "bf61616c-010e-48f2-8173-78b330804cd6", "links": [...] }, { "id": "d25dcdd0-28f4-4fd3-9e5f-da1d9d224940", "links": [...] }, { "id": "72f12e94-33ca-4537-9897-205ffe42350e", "links": [...] } ] }
$ curl http://example.com/v1/owners/
{ "owners": [ { "id": "511363b2-8693-4b30-ae4c-09964a4cebe0", "links": [ { "href": "/v1/owners/511363b2-8693-4b30-ae4c-09964a4cebe0", "rel": "self" } ] }, { "id": "ee5479d6-7070-4109-bfb2-d44e3c42782b", "links": [...] }, { "id": "35a85559-dc03-4aa2-85d2-947e17e310e5", "links": [...] }, { "id": "5e174bc4-4135-4e7b-b957-9705bacc903d", "links": [...] } ] }
$ curl http://example.com/v1/owners/511363b2-8693-4b30-ae4c-09964a4cebe0
{ "id": "511363b2-8693-4b30-ae4c-09964a4cebe0", "name": "Goran Shain", "birthday": "1981-10-27", "shoe_size": 10.5, "pets": [ "a8533344-6371-4982-a86b-722331839514", "d25dcdd0-28f4-4fd3-9e5f-da1d9d224940" ], "links": [ { "href": "/v1/owners/511363b2-8693-4b30-ae4c-09964a4cebe0", "rel": "self" }, { "href": "/v1/pets/a8533344-6371-4982-a86b-722331839514", "rel": "pet" }, { "href": "/v1/pets/d25dcdd0-28f4-4fd3-9e5f-da1d9d224940", "rel": "pet" } ] }
$ curl http://example.com/v1/owners/\?limit=1\&detail=1
{ "owners": [ { "id": "511363b2-8693-4b30-ae4c-09964a4cebe0", "name": "Goran Shain", "birthday": "1981-10-27", "shoe_size": 10.5, "pets": [ "a8533344-6371-4982-a86b-722331839514", "d25dcdd0-28f4-4fd3-9e5f-da1d9d224940" ], "links": [ { "href": "/v1/owners/511363b2-8693-4b30-ae4c-09964a4cebe0", "rel": "self" }, { "href": "/v1/pets/a8533344-6371-4982-a86b-722331839514", "rel": "pet" }, { "href": "/v1/pets/d25dcdd0-28f4-4fd3-9e5f-da1d9d224940", "rel": "pet" } ] } ] }
/v1/owners
/v1/owners/511363b2-8693-4b30-ae4c-09964a4cebe0
/v1/owners/511363b2-8693-4b30-ae4c-09964a4cebe0/pets/?limit=5
/v1/servers/b8060d42-8a2c-4f38-8ffb-b1b749e7a50a/reboot
$ curl http://example.com/v1/owners/
{ "owners": [ { "id": "511363b2-8693-4b30-ae4c-09964a4cebe0", "links": [ { "href": "/v1/owners/511363b2-8693-4b30-ae4c-09964a4cebe0", "rel": "self" } ] }, { "id": "ee5479d6-7070-4109-bfb2-d44e3c42782b", "links": [ { "href": "/v1/owners/ee5479d6-7070-4109-bfb2-d44e3c42782b", "rel": "self" } ] }, { "id": "35a85559-dc03-4aa2-85d2-947e17e310e5", "links": [ { "href": "/v1/owners/35a85559-dc03-4aa2-85d2-947e17e310e5", "rel": "self" } ] }, { "id": "5e174bc4-4135-4e7b-b957-9705bacc903d", "links": [ { "href": "/v1/owners/5e174bc4-4135-4e7b-b957-9705bacc903d", "rel": "self" } ] } ] }
$ curl http://example.com/v1/owners/
{ "owners": [ "511363b2-8693-4b30-ae4c-09964a4cebe0", "ee5479d6-7070-4109-bfb2-d44e3c42782b", "35a85559-dc03-4aa2-85d2-947e17e310e5", "5e174bc4-4135-4e7b-b957-9705bacc903d" ] }
$ curl http://example.com/v1/owners/ee5479d6-7070-4109-bfb2-d44e3c42782b
{ "id": "ee5479d6-7070-4109-bfb2-d44e3c42782b", "name": "Remus Bergfalk", "birthday": "1977-05-08", "shoe_size": 9.5, "pets": [ "00182d56-9981-402b-821e-7b1c2906533c", "72f12e94-33ca-4537-9897-205ffe42350e" ], "links": [ { "href": "/v1/owners/ee5479d6-7070-4109-bfb2-d44e3c42782b", "rel": "self" }, { "href": "/v1/pets/00182d56-9981-402b-821e-7b1c2906533c", "rel": "pet" }, { "href": "/v1/pets/72f12e94-33ca-4537-9897-205ffe42350e", "rel": "pet" } ] }
$ curl http://example.com/v2/owners/ee5479d6-7070-4109-bfb2-d44e3c42782b
{ "owner": { "id": "ee5479d6-7070-4109-bfb2-d44e3c42782b", "name": "Remus Bergfalk", "birthday": "1977-05-08", "shoe_size": 9.5, "pets": [ "00182d56-9981-402b-821e-7b1c2906533c", "72f12e94-33ca-4537-9897-205ffe42350e" ], "links": [ { "href": "/v2/owners/ee5479d6-7070-4109-bfb2-d44e3c42782b", "method": "GET", "rel": "self" }, { "href": "/v2/pets/00182d56-9981-402b-821e-7b1c2906533c", "method": "GET", "rel": "pet" }, { "href": "/v2/pets/72f12e94-33ca-4537-9897-205ffe42350e", "method": "GET", "rel": "pet" }, { "href": "/v2/owners/ee5479d6-7070-4109-bfb2-d44e3c42782b", "method": "PUT", "rel": "edit" }, { "href": "/v2/owners/ee5479d6-7070-4109-bfb2-d44e3c42782b", "method": "DELETE", "rel": "delete" } ], }, "links": [ { "href": "/v2/owners/", "method": "GET", "rel": "list" }, { "href": "/v2/owners/", "method": "POST", "rel": "create" } ] }
Hypertext
As
The
Engine
Of
Application
State
$ curl -X HEAD \ http://example.com/v1/owners/511363b2-8693-4b30-ae4c-09964a4cebe0
# HTTP code: 200
$ curl -X HEAD \ http://example.com/v1/owners/totally-bogus-owner-uuid
# HTTP code: 404
$ curl -X POST -d '{"name":"Jovka Garver","birthday":"1983-02-26"}' \ -H 'Content-Type: application/json' http://example.com/v1/owners/
{ "id": "b90a363c-d694-4e8c-be68-f53eeece2b99", "name": "Jovka Garver", "birthday": "1983-02-26", "pets": [] }
# HTTP code: 201
$ curl http://example.com/v1/owners/
{ "owners": [ "511363b2-8693-4b30-ae4c-09964a4cebe0", "ee5479d6-7070-4109-bfb2-d44e3c42782b", "35a85559-dc03-4aa2-85d2-947e17e310e5", "5e174bc4-4135-4e7b-b957-9705bacc903d", "b90a363c-d694-4e8c-be68-f53eeece2b99" ] }
$ curl http://someapp.example.com/v1/books/1
{"title": "Moby Dick", "text": "Call me Ishmael. Some years ago...."}
$ curl -H "Accept: text/plain" http://someapp.example.com/v1/books/1
Moby Dick Call me Ishmael. Some years ago....
$ curl -H "Accept-Encoding: gzip" http://someapp.example.com/v1/books/1 | \ gunzip -c
{"title": "Moby Dick", "text": "Call me Ishmael. Some years ago...."}
$ curl -X POST -d '' \ http://example.com/v1/servers/335b53b4-7b86-4a3b-976e-75f13a0a9e78/reboot
# HTTP code: 204
$ curl http://example.com/v1/owners/35a85559-dc03-4aa2-85d2-947e17e310e5
{ "id": "35a85559-dc03-4aa2-85d2-947e17e310e5", "name": "Jolanda Seaver", "birthday": "1996-08-29", "shoe_size": 8.5, "pets": [ "18e1588a-090e-4ace-94c2-f289617de0cb", "4dc4d7b0-a386-4919-9878-47d4eb8f49ec" ] }
$ curl -X PUT \ -d '{"name":"Jolanda Seaver","birthday":"1996-08-29","shoe_size":9}' \ -H 'Content-Type: application/json' \ http://example.com/v1/owners/35a85559-dc03-4aa2-85d2-947e17e310e5
{ "id": "35a85559-dc03-4aa2-85d2-947e17e310e5", "name": "Jolanda Seaver", "birthday": "1996-08-29", "shoe_size": 9.0, "pets": [ "18e1588a-090e-4ace-94c2-f289617de0cb", "4dc4d7b0-a386-4919-9878-47d4eb8f49ec" ] }
$ curl -X PUT -d '{"shoe_size":9}' -H 'Content-Type: application/json' \ http://example.com/v1/owners/35a85559-dc03-4aa2-85d2-947e17e310e5
{"message": {"name": "Missing required parameter in the JSON body or the post body or the query string"}}
# HTTP code: 400
$ curl \ http://example.com/v1/veterinarians/3cfbb699-d2f2-45cd-b932-be5171887262
{ "id": "3cfbb699-d2f2-45cd-b932-be5171887262", "name": "Seong-Min Park", "patients": [ "00182d56-9981-402b-821e-7b1c2906533c", ] }
$ curl \ http://example.com/v1/veterinarians/3cfbb699-d2f2-45cd-b932-be5171887262/patients/
{ "patients": [ "00182d56-9981-402b-821e-7b1c2906533c", ] }
$ curl -X PUT \ http://example.com/v1/veterinarians/3cfbb699-d2f2-45cd-b932-be5171887262/patients/d25dcdd0-28f4-4fd3-9e5f-da1d9d224940
{ "patient": { "id": "d25dcdd0-28f4-4fd3-9e5f-da1d9d224940" }, "veterinarian": { "id": "3cfbb699-d2f2-45cd-b932-be5171887262" } }
# HTTP code: 201
$ curl \ http://example.com/v1/veterinarians/3cfbb699-d2f2-45cd-b932-be5171887262/patients/
{ "patients": [ "00182d56-9981-402b-821e-7b1c2906533c", "d25dcdd0-28f4-4fd3-9e5f-da1d9d224940" ] }
$ curl -X PUT \ http://example.com/v1/veterinarians/3cfbb699-d2f2-45cd-b932-be5171887262/patients/d25dcdd0-28f4-4fd3-9e5f-da1d9d224940
{ "patient": { "id": "d25dcdd0-28f4-4fd3-9e5f-da1d9d224940" }, "veterinarian": { "id": "3cfbb699-d2f2-45cd-b932-be5171887262" } }
# HTTP code: 200
$ curl -X POST -d '{"pet_id":"d25dcdd0-28f4-4fd3-9e5f-da1d9d224940"}' \ -H 'Content-Type: application/json' \ http://example.com/v1/veterinarians/3cfbb699-d2f2-45cd-b932-be5171887262/patients/
{"message": "Pet d25dcdd0-28f4-4fd3-9e5f-da1d9d224940 is already assigned to veterinarian 3cfbb699-d2f2-45cd-b932-be5171887262"}
# HTTP code: 409
$ curl http://example.com/v1/pets/72f12e94-33ca-4537-9897-205ffe42350e
{ "id": "72f12e94-33ca-4537-9897-205ffe42350e", "name": "Hellbringer the Befouler", "breed": "domestic shorthair", "veterinarian": "fd87a620-466d-41ae-a6b0-1527b5126c58", "species": "cat", "owners": [ "ee5479d6-7070-4109-bfb2-d44e3c42782b" ] }
$ curl \ http://example.com/v1/pets/72f12e94-33ca-4537-9897-205ffe42350e/veterinarian/
{ "veterinarian": "fd87a620-466d-41ae-a6b0-1527b5126c58" }
$ curl \ http://example.com/v1/pets/72f12e94-33ca-4537-9897-205ffe42350e/veterinarian/\?detail=1
{ "veterinarian": { "id": "fd87a620-466d-41ae-a6b0-1527b5126c58", "name": "Colobert Bannerman", "specialty": "cats" } }
$ curl http://example.com/v1/pets/4dc4d7b0-a386-4919-9878-47d4eb8f49ec
{ "id": "4dc4d7b0-a386-4919-9878-47d4eb8f49ec", "name": "Scarface", "breed": "English Bulldog", "veterinarian": "f5df1cc0-4fa2-4605-af57-4da6479e8afa", "species": "dog", "owners": [ "35a85559-dc03-4aa2-85d2-947e17e310e5", "5e174bc4-4135-4e7b-b957-9705bacc903d" ] }
$ curl \ http://example.com/v1/pets/4dc4d7b0-a386-4919-9878-47d4eb8f49ec/owners/
{ "owners": [ "35a85559-dc03-4aa2-85d2-947e17e310e5", "5e174bc4-4135-4e7b-b957-9705bacc903d" ] }
$ curl http://example.com/v2/ownership/5f5c1ded-32e7-4fb2-9b6f-b0aa05616d67
{ "id": "5f5c1ded-32e7-4fb2-9b6f-b0aa05616d67", "owner": "511363b2-8693-4b30-ae4c-09964a4cebe0", "pet": "a8533344-6371-4982-a86b-722331839514", }
$ curl -X PATCH -d '{"shoe_size":9.5}' -H 'Content-Type: application/json' \ http://example.com/v1/owners/35a85559-dc03-4aa2-85d2-947e17e310e5
{ "id": "35a85559-dc03-4aa2-85d2-947e17e310e5", "name": "Jolanda Seaver", "birthday": "1996-08-29", "shoe_size": 9.5, "pets": [ "18e1588a-090e-4ace-94c2-f289617de0cb", "4dc4d7b0-a386-4919-9878-47d4eb8f49ec" ] }
$ curl http://example.com/v1/pets/bf61616c-010e-48f2-8173-78b330804cd6
{ "id": "bf61616c-010e-48f2-8173-78b330804cd6", "name": "Kisses", "species": "Hissing Cockroach", "veterinarian": null, "owners": [] }
$ curl -X PATCH \ -d '{"veterinarian":"3cfbb699-d2f2-45cd-b932-be5171887262","owners":["ee5479d6-7070-4109-bfb2-d44e3c42782b"]}' \ -H 'Content-Type: application/json' \ http://example.com/v1/pets/bf61616c-010e-48f2-8173-78b330804cd6
{ "id": "bf61616c-010e-48f2-8173-78b330804cd6", "name": "Kisses", "species": "Hissing Cockroach", "veterinarian": "3cfbb699-d2f2-45cd-b932-be5171887262", "owners": [ "ee5479d6-7070-4109-bfb2-d44e3c42782b" ] }
# HTTP code: 200
$ data='[ {"node": "veterinarian", "op": "change", "value": "f5df1cc0-4fa2-4605-af57-4da6479e8afa"}, {"node": "owner", "op": "delete", "value": "ee5479d6-7070-4109-bfb2-d44e3c42782b"} ]' $ curl -X PATCH -d "$data" -H 'Content-Type: application/json' \ http://example.com/v2/pets/bf61616c-010e-48f2-8173-78b330804cd6
{ "id": "bf61616c-010e-48f2-8173-78b330804cd6", "name": "Kisses", "species": "Hissing Cockroach", "veterinarian": "f5df1cc0-4fa2-4605-af57-4da6479e8afa", "owners": [] }
$ curl -X DELETE \ http://example.com/v1/owners/35a85559-dc03-4aa2-85d2-947e17e310e5
# HTTP code: 204
$ curl -X DELETE \ http://example.com/v1/owners/35a85559-dc03-4aa2-85d2-947e17e310e5
{"message": "No such owner 35a85559-dc03-4aa2-85d2-947e17e310e5"}
# HTTP code: 404
$ curl -X POST -d '{"pet_id":"a8533344-6371-4982-a86b-722331839514"}' \ -H 'Content-Type: application/json' \ http://example.com/v1/veterinarians/f5df1cc0-4fa2-4605-af57-4da6479e8afa/patients/
{"message": "Pet a8533344-6371-4982-a86b-722331839514 is already assigned to veterinarian f5df1cc0-4fa2-4605-af57-4da6479e8afa"}
# HTTP code: 409
Verb | Collection operation | Record operation |
---|---|---|
GET | List | Read |
HEAD | N/A | Check existence |
POST | Create | N/A |
PUT | N/A | Create/replace (idempotent) |
PATCH | N/A | Partial update |
DELETE | Delete (rarely) | Delete |
$ curl -X DELETE \ http://example.com/v2/owners/5e174bc4-4135-4e7b-b957-9705bacc903d
{"headers": {"WWW-Authenticate": "Basic realm=\"Login Required\""}, "message": "Request requires authentication"}
# HTTP code: 401
$ curl -X DELETE -u stpierre:hunter2 \ http://example.com/v2/owners/5e174bc4-4135-4e7b-b957-9705bacc903d
# HTTP code: 204
$ curl -u stpierre:hunter2 http://example.com/v2/token/
{ "token": "6158a4ff-8d50-47bd-8316-c0008b7cbc88" }
$ curl -X DELETE -H 'X-Token: 6158a4ff-8d50-47bd-8316-c0008b7cbc88' \ http://example.com/v2/pets/18e1588a-090e-4ace-94c2-f289617de0cb
# HTTP code: 204
$ curl -u stpierre:hunter2 -b cookies -c cookies \
http://example.com/v2/token/
$ cat cookies
localhost FALSE / FALSE 0 token 6158a4ff-8d50-47bd-8316-c0008b7cbc88
$ curl -X DELETE -b cookies -c cookies \ http://example.com/v2/veterinarians/3cfbb699-d2f2-45cd-b932-be5171887262
# HTTP code: 204
$ curl http://example.com/v2/get-nth-prime/?n=2
{"n": 2, "prime": 2}
$ curl http://example.com/v2/get-nth-prime/?n=22389047890
$ curl http://example.com/v2/get-nth-prime/?n=22389047890
{ "task": { "id": "e8a09eb7-a83e-4d4d-8d31-1208bdb7f990", "status": "QUEUED" } }
# HTTP code: 202
$ curl http://example.com/v2/tasks/e8a09eb7-a83e-4d4d-8d31-1208bdb7f990
{ "task": { "id": "e8a09eb7-a83e-4d4d-8d31-1208bdb7f990", "status": "PENDING" } }
# HTTP code: 200
$ curl http://example.com/v2/tasks/e8a09eb7-a83e-4d4d-8d31-1208bdb7f990
{ "task": { "id": "e8a09eb7-a83e-4d4d-8d31-1208bdb7f990", "status": "COMPLETE",, "result": {"n": 22389047890, "prime": 583235838259} } }
# HTTP code: 200
$ curl http://example.com/v2/tasks/e8a09eb7-a83e-4d4d-8d31-1208bdb7f990
{ "task": { "id": "e8a09eb7-a83e-4d4d-8d31-1208bdb7f990", "status": "ERROR", "error": { "message": "Ran out of numbers, contact your sysadmin to add more" } } }
# HTTP code: 200
$ curl -X POST -d '{"image": "myimage", "flavor": "m1.tiny"}' \ http://example.com/v2/servers
{ "instance": { "id": "3a7ab55d-83cd-4cc0-aca0-e8feab4fb62b", "hostname": "i-908af05e", "image": "myimage", ... "status": "CREATING" } }
# HTTP code: 202