Senior Engineer - Acquia
JSON API co-maintainer
HTTP/2 enthusiast
Specification-junkie
Loses lots of sleep over API design
IP has the task of delivering packets from the source host to the destination host solely based on the IP addresses in the packet headers.
TCP provides reliable, ordered, and error-checked delivery of a stream of octets (bytes) between applications running on hosts communicating by an IP network.
HTTP is an application protocol for distributed, collaborative, and hypermedia information systems.
An application language
Almost always a URL
Which is simply a unique identifier that can be "located"
http://example.com/api/user/1
"http:" "//" host [ ":" port ] [ abs_path [ "?" query ]]
Describe "actions" on a resource
HEAD & OPTIONS are about information discovery
GET - Show me the identified resource
POST - Add this resource
PATCH - Update the identified resource with this information
PUT - Replace the identified resource with this one
DELETE - Remove the identified resource
HEAD - Is there a resource with this identifier?
OPTIONS - What actions can I take on a resource?
Categorize communication into conversational buckets
200 - Understood
300 - Maybe you meant X?
400 - Sorry, I don't understand
500 - It's not you, it's me
Contains a start line (URI + Method/Status), headers & body
Headers describe the resource
Content-Type: text/html <html> <title>Gabe Sullice</title> <body>stout</body> </html>
Content-Type: application/json { "type": "user", "id": 1, "attributes": { "name": "gabesullice" } }
GET www.example.com/user/1 HTTP/1.1
HTTP/1.1 200 OK
Content-Type: application/json { "type": "user", "id": 1, "attributes": { "name": "gabe" } }
REST is an architectural style that defines a set of constraints and properties based on HTTP.
A separation of concerns
Client handles presentation
Server handles data storage
Foundational principle for "decoupled" sites
Every request/response is independent
A response should contain information to govern its own intermediate storage
Its purpose is to eliminate requests/responses cycles or to reduce the time it takes for that cycle to complete (latency)
The system should be indifferent to proxies, load balancers, CDNs, etc.
Optional.
Useful when a client does not have the know-how on how to process a resource. It sends a request to a remote server for the code representing that know-how, receives that code, and executes it locally.
Entity representations are decoupled from their storage.
Hypermedia is the engine of application state.
I'm a teapot, short and stout
<a href="https://httpstatus.es/218">I'm a teapot</a>
{ "type": "joke", "id": 1,
"attributes": {
"content": "I'm a teapot"
},
"links": {
"explanation": "https://httpstatus.es/218"
}
}
index.html
index.html
hero.jpg
index.html
style.css
hero.jpg
index.html
style.css
url('https://www.example.com/assets/separator.svg');
hero.jpg
index.html
style.css
url('https://www.example.com/assets/hipster-heavy.ttf');
url('https://www.example.com/assets/separator.svg');
hero.jpg
/home
/posts
/posts/2
/posts/1
/about
/posts
/posts/2
/posts/2/tags
/posts/2/author
/posts/1
/posts
/posts/2
/posts/2/tags
/posts/2/author
/posts/1
/posts
/posts/2
/posts/2/tags
/posts/2/author
/posts/1
/posts/2
/posts/2/author
/posts/2/tags
/tags/2
/tags/1
index.html
style.css
/assets/list-bullet.svg
/assets/separator.svg
hero.jpg
GET /index.html
GET /style.css
GET /assets/list-bullet.svg
GET /assets/separator.svg
GET /hero.jpg
GET /index.html
GET /style.css
GET /assets/list-bullet.svg
GET /assets/separator.svg
GET /hero.jpg
GET /index.html
GET /style.css
GET /assets/list-bullet.svg
GET /assets/separator.svg
GET /hero.jpg
GET /index.html
GET /style.css
GET /assets/list-bullet.svg
GET /assets/separator.svg
GET /hero.jpg
GET /index.html
GET /style.css
GET /assets/list-bullet.svg
GET /assets/separator.svg
GET /hero.jpg
GET /index.html
GET /style.css
GET /assets/list-bullet.svg
GET /assets/separator.svg
GET /hero.jpg
GET /index.html
GET /style.css
GET /assets/list-bullet.svg
GET /assets/separator.svg
GET /hero.jpg
GET /index.html
GET /style.css
GET /assets/list-bullet.svg
GET /assets/separator.svg
GET /hero.jpg
GET /index.html
GET /style.css
GET /assets/list-bullet.svg
GET /assets/separator.svg
GET /hero.jpg
GET /index.html
GET /style.css
GET /assets/list-bullet.svg
GET /assets/separator.svg
GET /hero.jpg
time
GET /index.html
GET /style.css
GET /assets/list-bullet.svg
GET /assets/separator.svg
GET /hero.jpg
GET /index.html
GET /style.css
GET /assets/list-bullet.svg
GET /assets/separator.svg
GET /hero.jpg
time
{
"data": {
"type": "person",
"id": 1,
"attributes": {
"title": "Black Knight",
"catchphrase": "'tis but a flesh wound.",
"body": "dismembered"
}
}
{
"data": [{
"type": "post",
"id": 1,
"attributes": {
"title": "modernism",
},
"relationships": {
"comments": {
"links": {
"related": "/api/posts/1/comments",
"create": "/api/posts/1/comments",
"schema": "/api/schemas/comments",
}
}
},
"links": {
"delete": "/api/posts/1",
"schema": "/api/schemas/post"
}
}],
"links": {
"self": "/api/content?page[offset]=1",
"next": "/api/content?page[offset]=2",
"schema": "/api/schemas/content",
"create": {
"href": "/api/content",
"meta": {
"acceptable-types": {
"post": "/api/schemas/post",
"note": "/api/schemas/note"
}
}
}
}
}
{
"data": [{
"type": "post",
"id": 2,
"attributes": {
"title": "apocalypse",
},
"relationships": {
"comments": {
"links": {
"related": "/api/posts/2/comments",
"create": "/api/posts/2/comments",
"schema": "/api/schemas/comments",
}
}
},
"links": {
"schema": "/api/schemas/post"
}
}],
"links": {
"self": "/api/content?page[offset]=2",
"previous": "/api/content?page[offset]=1",
"schema": "/api/schemas/content"
}
}
/api/content
/api/schemas/content
/api/content?page=2
/api/posts/1/comments
/api/content
/api/schemas/content
/api/content?page=2
/api/content?page=3
/api/posts/2/comments
/api/posts/1/comments
/api/content
/api/schemas/content
/api/content?page=2
/api/content?page=3
/api/content?page=4
/api/posts/2/comments
/api/posts/1/comments
/api/posts/3/comments
GET /api/content
GET /api/content
GET /api/schemas/content
GET /api/content?page=1
GET /api/posts/1/comments
GET /api/content
GET /api/schemas/content
GET /api/content?page=1
GET /api/posts/1/comments
GET /api/posts/2/comments
GET /api/content?page=2
GET /api/content
GET /api/schemas/content
GET /api/content?page=1
GET /api/posts/1/comments
GET /api/posts/2/comments
GET /api/content?page=2
GET /api/posts/3/comments
. . .
GET /api/content?page=3
time
GET /api
X-Push-Please: [{
"path": "{$.links.content.href}",
"push-please": [{
"path": "{$.links.next}",
"quota": {
"count": 100,
"counter": {$.data}
},
"push-please": [{
"path": "{$.data[*].relationships.comments.links.related}"
}]
}]
}, {
"path": "{$.links.content.schema}/content"
}]
X-Cache-Digest: abc123def456ghi789cba321fed654ihg987...jkl1ef
GET /api HTTP/2
X-Push-Please: [{
"path": "{$.links.content.href}",
"push-please": [{
"path": "{$.links.next}",
"quota": {
"count": 100,
"counter": {$.data}
},
"push-please": [{
"path": "{$.data[*].relationships.comments.links.related}"
}]
}]
}, {
"path": "{$.links.content.meta.schema}"
}]
HTTP/2 200 OK
{
"links": {
"content": {
"href": "/api/content",
"meta": {
"schema": "/api/schemas/content"
}
}
}
}
Server gets initial request, generates response:
{
"links": {
"content": {
"href": "/api/content",
"meta": {
"schema": "/api/schemas/content"
}
}
}
}
Evaluates top-level push-please:
"{$.links.content.href}" => PUSH_PROMISE /api/content
"{$.links.content.meta.schema}" => PUSH_PROMISE /api/schemas/content
Move to /api/content scope, evaluate second-level push-please:
counts {$.data} => counted = 33; count = 100 - counted;
if count > 0; "{$.links.next}" => PUSH_PROMISE /api/content?page=1
"{$.data[*].relationships.comments.links.related}" => PUSH_PROMISE /api/post/1/comments, etc.
Move to /api/content?page=1 scope:
counts "{$.data}" => counted = 47; count = 77 - counted;
if count > 0; "{$.links.next}" => PUSH_PROMISE /api/content?page=2
"{$.data[*].relationships.comments.links.related}" => PUSH_PROMISE /api/post/2/comments, etc.
Move to /api/content?page=2 scope:
counts "{$.data}" => counted = 30; count = 77 - counted;
if count > 0; "{$.links.next}" => DONE! count === 0