HTTP 
& Your Angry Dog 
ZendCon 2014 
Ross Tuck
Freerange Codemonkey 
Know-It-All 
Hot-Air Balloon
@rosstuck 
rosstuck.com
Today's topic:
Dogs
HTTP & Dogs
The Agenda
Basics
Request 
Response 
Client Server
Request 
POST /gists HTTP/1.1 
Authorization: Basic xxxxxxxx 
Host: api.github.com 
Content-Length: 146 
{ 
"description": "the description for this gist", 
"public": false, 
"files": { 
...
Request 
POST /gists HTTP/1.1 
Authorization: Basic xxxxxxxx 
Host: api.github.com 
Content-Length: 146 
{ 
"description": "the description for this gist", 
"public": false, 
"files": { 
... 
2 Parts
Request 
POST /gists HTTP/1.1 
Authorization: Basic xxxxxxxx 
Host: api.github.com 
Content-Length: 146 
{ 
"description": "the description for this gist", 
"public": false, 
"files": { 
... 
The body
Request 
POST /gists HTTP/1.1 
Authorization: Basic xxxxxxxx 
Host: api.github.com 
Content-Length: 146 
<!DOCTYPE html> 
<html> 
<head> 
<meta charset='utf-8'> 
<title>My application</title> 
... 
The body
Request 
POST /gists HTTP/1.1 
Authorization: Basic xxxxxxxx 
Host: api.github.com 
Content-Length: 146 
{ 
"description": "the description for this gist", 
"public": false, 
"files": { 
... 
The body
Request 
POST /gists HTTP/1.1 
Authorization: Basic xxxxxxxx 
Host: api.github.com 
Content-Length: 146 
{ 
"description": "the description for this gist", 
"public": false, 
"files": { 
...
Request 
POST /gists HTTP/1.1 
Authorization: Basic xxxxxxxx 
Host: api.github.com 
Content-Length: 146 
{ 
"description": "the description for this gist", 
"public": false, 
"files": { 
... 
The headers
Request 
POST /gists HTTP/1.1 
Authorization: Basic xxxxxxxx 
Host: api.github.com 
Content-Length: 146 
{ 
"description": "the description for this gist", 
"public": false, 
"files": { 
... 
The good stuf
Request 
POST /gists HTTP/1.1 
Authorization: Basic xxxxxxxx 
Host: api.github.com 
Content-Length: 146 
{ 
"description": "the description for this gist", 
"public": false, 
"files": { 
...
Request 
POST /gists HTTP/1.1 
Authorization: Basic xxxxxxxx 
Host: api.github.com 
Content-Length: 146 
{ 
"description": "the description for this gist", 
"public": false, 
"files": { 
... 
GET, POST, PUT, DELETE
Request Relative URL 
POST /gists HTTP/1.1 
Authorization: Basic xxxxxxxx 
Host: api.github.com 
Content-Length: 146 
{ 
"description": "the description for this gist", 
"public": false, 
"files": { 
... 
HTTP version
Request 
POST /gists HTTP/1.1 
Authorization: Basic xxxxxxxx 
Host: api.github.com 
Content-Length: 146 
{ 
"description": "the description for this gist", 
"public": false, 
"files": { 
... 
Key/Value pairs
Request 
POST /gists HTTP/1.1 
Authorization: Basic xxxxxxxx 
Host: api.github.com 
Content-Length: 146 
{ 
"description": "the description for this gist", 
"public": false, 
"files": { 
...
Response 
HTTP/1.1 201 Created 
Date: Sun, 09 Sep 2012 11:42:41 GMT 
Content-Length: 1848 
Location: https://api.github.com/gists/a43a0cf58 
{ 
"description": "the description for this gist", 
"comments": 0, 
"created_at": "2012-09-09T11:42:40Z", 
... 
Status code
• 2xx 
• 3xx 
• 4xx 
• 5xx 
OK! 
Over there! 
Client screwed up! 
Server screwed up!
Content Negotiation
Request 
GET /dogs/corgi HTTP/1.1 
Host: api.example.com
Response 
HTTP/1.1 200 OK 
Date: Sun, 26 Aug 2012 18:00:43 GMT 
{ 
"cute": true, 
"big": false, 
"data_dog": true 
}
Response 
HTTP/1.1 200 OK 
Date: Sun, 26 Aug 2012 18:00:43 GMT 
<dog breed="corgi"> 
<cute>true</cute> 
<data capacity="on" /> 
</dog>
Request 
GET /dogs/corgi HTTP/1.1 
Host: api.example.com
Request 
GET /dogs/corgi.json HTTP/1.1 
Host: api.example.com
/dogs/corgi.json !== /dogs/corgi.xml
Imagine the URL as your primary key.
Request 
GET /dogs/corgi HTTP/1.1 
Host: api.example.com
Request 
GET /dogs/corgi?_format=json HTTP/1.1 
Host: api.example.com
Request 
POST /dogs/corgi?_format=json HTTP/1.1 
Host: api.example.com
Request 
GET /dogs/corgi HTTP/1.1 
Host: api.example.com
Request 
GET /dogs/corgi HTTP/1.1 
Host: api.example.com 
Accept: application/json
More POWAH
Request 
GET /dogs/corgi HTTP/1.1 
Host: api.example.com 
Accept: application/json
Request 
GET /dogs/corgi HTTP/1.1 
Host: api.example.com 
Accept: application/json, application/xml 
How do I choose?
Request 
GET /dogs/corgi HTTP/1.1 
Host: api.example.com 
Accept: application/json, application/xml
Response 
HTTP/1.1 200 OK 
Date: Sun, 26 Aug 2012 18:00:43 GMT 
Content-Type: application/json 
{ 
"cute": true, 
"big": false, 
"data_dog": true 
}
Nifty.
Request 
GET /dogs/corgi HTTP/1.1 
Host: api.example.com 
Accept: application/json, application/xml
text/html, text/plain
text/html;key=value, text/plain
text/html;key=value;foo=bar, text/plain
text/html, text/plain
Quality 
(Default 1.0) 
text/html, text/plain;q=0.5
text/html, text/plain;q=0.5, text/*;q=0.1 
Wildcards
Anything at all 
text/html, text/plain;q=0.5, */*;q=0.1
Accept Headers 
Little weird...
But not so scary.
text/html,application/xhtml+xml, 
application/xml;q=0.9,*/*;q=0.8
Cool...
What the heck is it good for?
Accept is a “Pattern”
Accept-Language 
Accept-Encoding 
Accept-Charset 
Accept-Ranges
Content-Language 
Content-Encoding 
(works differently) 
Content-Range
/dog/corgi 
Resource vs Representation 
JSON, 
Dutch, 
Gzipped, 
/dog/corgi
Resource vs Representation
Best way to version your API. 
Arguably. 
Right now.
/v1/dogs/corgi
Accept: application/vnd.dogipedia-v1+json
Accept: application/vnd.dogipedia-v2+json
Vary
GET /dogs/corgi HTTP/1.1 
Host: api.example.com 
Client Server
GET /dogs/corgi HTTP/1.1 
Host: api.example.com 
Client Server
GET /dogs/corgi HTTP/1.1 
Host: api.example.com 
Client Proxy Server
GET /dogs/corgi HTTP/1.1 
Host: api.example.com 
Accept: application/json, text/plain 
User-Species: cat 
Client Proxy Server
Same URL. 
Different output. 
WTF should I return?
GET /dogs/corgi HTTP/1.1 
Host: api.example.com 
Accept: application/json, text/plain 
User-Species: cat 
Client Proxy Server
Client Proxy Server
Here's how. 
Hint: 
Involves the 
Vary header!
/dogs/corgi 
Accept: application/json, text/plain 
User-Species: cat 
Client Proxy Server
Response 
HTTP/1.1 200 OK 
Date: Sun, 26 Aug 2012 18:00:43 GMT 
Content-Type: application/json 
Vary: Accept 
{“json”: “omgz”}
URL and Accept? 
Okay, I got this. 
Client Proxy Server
Some time later...
/dogs/corgi 
Accept: application/json, text/plain 
User-Species: aardvark 
Client Proxy Server
Valid cache. 
I has it. 
Client Proxy Server
ZZZ 
Z Z 
Z 
Client Proxy Server
Response 
HTTP/1.1 200 OK 
Date: Sun, 26 Aug 2012 18:00:43 GMT 
Content-Type: application/json 
Vary: Accept 
{“json”: “omgz”}
Response 
HTTP/1.1 200 OK 
Date: Sun, 26 Aug 2012 18:00:43 GMT 
Content-Type: application/json 
Vary: Accept, User-Species 
{“json”: “omgz”}
Response 
HTTP/1.1 200 OK 
Date: Sun, 26 Aug 2012 18:00:43 GMT 
Content-Type: application/json 
Vary: Accept, User-Species 
{“json”: “dogs rule, cats drool”}
Request headers. 
Not Response!
Bad Reputation? 
2 Reasons
1. Accept-Encoding 
-Language
2. Internet Explorer
Caching
Expires 
Pragma 
Cache-Control
Expires 
Pragma 
Cache-Control
Expires 
Cache-Control 
HTTP 1.0 
HTTP 1.1
Response 
HTTP/1.1 200 OK 
Expires: Wed, 29 Oct 2014 22:00:00 GMT 
{“herp”: “derp”}
Response 
HTTP/1.1 200 OK 
Cache-Control: max-age=120 
{“herp”: “derp”}
Response 
HTTP/1.1 200 OK 
Cache-Control: max-age=120
Expires 
Cache-Control 
HTTP 1.0 
HTTP 1.1
Response 
HTTP/1.1 200 OK 
Cache-Control: max-age=120 
{“herp”: “derp”}
Response 
HTTP/1.1 200 OK 
Cache-Control: max-age=120, s-maxage=120 
{“herp”: “derp”} 
Dude, Where's 
my dash?
public 
private 
no-store 
no-cache 
no-transform 
must-revalidate 
proxy-revalidate
Much better 
than me. 
Mark Nottingham's Caching Tutorial 
http://www.mnot.net/cache_docs/
Conditional Requests
Conditional Requests
The Part About ETags
Conditional Requests
Request 
DELETE /ross/reputation HTTP/1.1 
Host: api.joind.in 
If-Talk-Quality: Crap
if ($talkQuality === 'Crap') { 
real code 
delete($rossReputation); 
Not } Server
What kind of conditions?
If-Match 
If-None-Match 
If-Modified-Since 
If-Unmodified-Since 
If-Range 
ETags 
Datetimes 
Either
Wait a second, Ross. 
Audience
What the heck is an ETag?
A string. 
Any string.
One rule:
Represent the current state.
“14” 
“a381bedb5d4478053eb04be35f8798dd” 
“winnie-the-pooh”
...for the current representation.
etag(“v14-json-en”) !== etag(“v14-xml-en”) 
Don't cross the streams 
Server
Last Modified Date sounds easier... 
Audience
One second 
of precision 
Wed, 15 Nov 1995 04:58:08 GMT
Caching With Conditionals
Use Case
Request 
GET /gists/3481910 HTTP/1.1 
Host: api.github.com 
Accept: */*
Response 
HTTP/1.1 200 OK 
Server: nginx/1.0.13 
Date: Sun, 26 Aug 2012 18:00:43 GMT 
Vary: Accept 
ETag: "f4e15911542b92b44bb38186e71cc8f5" 
"history": [ 
{ 
"version": "529f6311d5518977534b6e1fd313...", 
...
Response 
... 
"user": { 
"gravatar_id": "c26bfcbd5f786591e036fa0", 
"avatar_url": "https://secure.gravatar...", 
"login": "rosstuck", 
"url": "https://api.github.com/users/rosstuck", 
"id": 146766 
}, 
"change_status": { 
"additions": 1, 
"deletions": 0, 
"total": 1 
},
Response 
"url": "https://api.github.com/gists/348...", 
"committed_at": "2012-08-26T17:40:03Z" 
} 
], 
"git_pull_url": "git://gist.github.com/34819...", 
"forks": [ 
], 
"html_url": "https://gist.github.com/3481910", 
"git_push_url": "git@gist.github.com:3481910.git", 
"comments": 0, 
"user": {
Response 
HTTP/1.1 200 OK 
Server: nginx/1.0.13 
Date: Sun, 26 Aug 2012 18:00:43 GMT 
Vary: Accept 
ETag: "f4e15911542b92b44bb38186e71cc8f5" 
{ 
"history": [ 
{ 
"version": "529f6311d5518970903cb5427534b6e1fd313aca", 
"user": { 
"gravatar_id": "c26bfcbd5f786591e036fa0958a11e8b", 
"avatar_url": "https://secure.gravatar.com/avatar/c26bfcbd5f786591e036fa0958a11e8b?d=https://a2... 
"login": "rosstuck", 
"url": "https://api.github.com/users/rosstuck", 
"id": 146766 
}, 
"change_status": { 
"additions": 1, 
"deletions": 0, 
"total": 1 
}, 
"url": "https://api.github.com/gists/3481910/529f6311d5518970903cb5427534b6e1fd313aca", 
"committed_at": "2012-08-26T17:40:03Z" 
} 
], 
"git_pull_url": "git://gist.github.com/3481910.git", 
"forks": [ 
], 
"html_url": "https://gist.github.com/3481910", 
"git_push_url": "git@gist.github.com:3481910.git", 
"comments": 0, 
"user": { 
"gravatar_id": "c26bfcbd5f786591e036fa0958a11e8b", 
"avatar_url": "https://secure.gravatar.com/avatar/c26bfcbd5f78659....",} 
"login": "rosstuck", 
"url": "https://api.github.com/users/rosstuck", 
"id": 146766 
}, 
"public": true, 
"created_at": "2012-08-26T17:40:03Z", 
"files": { 
"gistfile1.txt": { 
"type": "text/plain", 
"filename": "gistfile1.txt", 
"raw_url": "https://gist.github.com/raw/3481910/8b6946739e8098408ee3af96... 
"content": "Hello PFC!", 
"language": null, 
"size": 10 
} 
}, 
"description": "", 
"url": "https://api.github.com/gists/3481910", 
"updated_at": "2012-08-26T17:40:03Z", 
"id": "3481910" 
}
Request 
GET /gists/3481910 HTTP/1.1 
Host: api.github.com 
Accept: */* 
If-None-Match: "f4e15911542b92b44bb38186e71cc8f5"
Response 
HTTP/1.1 304 Not Modified 
Server: nginx/1.0.13 
Date: Sun, 26 Aug 2012 18:00:43 GMT 
Vary: Accept 
ETag: "f4e15911542b92b44bb38186e71cc8f5"
Response 
HTTP/1.1 304 Not Modified 
Server: nginx/1.0.13 
Date: Sun, 26 Aug 2012 18:00:43 GMT 
Vary: Accept 
ETag: "f4e15911542b92b44bb38186e71cc8f5"
Response 
HTTP/1.1 304 Not Modified 
Server: nginx/1.0.13 
Date: Sun, 26 Aug 2012 18:00:43 GMT 
Vary: Accept 
ETag: "f4e15911542b92b44bb38186e71cc8f5" 
No giant body!
Caching. 
You has it.
Request 
GET /gists/3481910 HTTP/1.1 
Host: api.github.com 
Accept: */* 
If-None-Match: "a381bedb5d4478053eb04be35f8798dd"
Request 
GET /gists/3481910 HTTP/1.1 
Host: api.github.com 
Accept: */* 
If-None-Match: "ross-is-a-poo-poo-head"
Response 
HTTP/1.1 200 OK 
Server: nginx/1.0.13 
Date: Sun, 26 Aug 2012 18:00:43 GMT 
Vary: Accept 
ETag: "f4e15911542b92b44bb38186e71cc8f5" 
"history": [ 
{ 
"version": "529f6311d5518977534b6e1fd313...",
Recap
No ETag 
Old ETag 
Matching ETag 
Full Body 
Full Body 
No Body 
→ 
→ 
→
...on supported servers.
Why?
Parsing 
Bandwidth 
Response time 
Probably 
.Maybe
However...
“The fastest request is one you don't make.” 
- Jesus
More Fun With ETags
Optimistic Concurrency Control 
“Record Versioning”
Request
Request 
GET /gists/3481910 HTTP/1.1 
Host: api.github.com 
Accept: */* 
If-None-Match: "f4e15911542b92b44bb38186e71cc8f5"
Request 
PATCH /gists/3481910 HTTP/1.1 
Host: api.github.com 
Accept: */* 
If-None-Match: "f4e15911542b92b44bb38186e71cc8f5"
Request 
PATCH /gists/3481910 HTTP/1.1 
Host: api.github.com 
Accept: */* 
If-Match: "f4e15911542b92b44bb38186e71cc8f5"
Request 
PATCH /gists/3481910 HTTP/1.1 
Host: api.github.com 
Accept: */* 
If-Match: "f4e15911542b92b44bb38186e71cc8f5" 
{ "description": "cheese om nom nom" }
Response
Response 
HTTP/1.1 200 OK 
Server: nginx/1.0.13 
Date: Sat, 01 Sep 2012 14:01:38 GMT 
ETag: "899b76047a5e68445668374c2e0faa32" 
{ 
"description": "cheese om nom nom", 
"user": { 
"login": "rosstuck", 
...
It works.
So what?
What if I send something...
Request 
PATCH /gists/3481910 HTTP/1.1 
Host: api.github.com 
Accept: */* 
If-Match: "899b76047a5e68445668374c2e0faa32" 
{ "description": "cheese om nom nom" }
Request 
PATCH /gists/3481910 HTTP/1.1 
Host: api.github.com 
Accept: */* 
If-Match: "stay-puft-marshmellow-dog!" 
{ "description": "cheese om nom nom" }
Response 
HTTP/1.1 412 Precondition Failed 
Server: nginx/1.0.13 
Date: Sun, 26 Aug 2012 18:00:43 GMT
Response
Server 
if (“stay-puft-marshmellow-dog” == “f4e1591..”) { 
patchTheRecord(); 
}
Server 
if (“stay-puft-marshmellow-dog” == “f4e1591..”) { 
patchTheRecord(); 
} else { 
sendScary412Message(); 
}
Your ETag is out of date.
“Two guys on the same record” problem
Scary Precondition Error pt. 2
Disclaimer 
New Stuff Ahead
Request 
DELETE /gists/3481910 HTTP/1.1 
Host: api.github.com
Response 
HTTP/1.1 428 Precondition Required 
Server: nginx/1.0.13 
Date: Sun, 26 Aug 2012 18:00:43 GMT
Am I operating on the latest version?
Request 
DELETE /gists/3481910 HTTP/1.1 
Host: api.github.com
Request 
DELETE /gists/3481910 HTTP/1.1 
Host: api.github.com 
If-Match: "f4e15911542b92b44bb38186e71cc8f5"
Response 
HTTP/1.1 204 No Content 
Server: nginx/1.0.13 
Date: Sun, 26 Aug 2012 18:00:43 GMT
Look before you leap.
Tooling
Epilogue: HTTP & Dogs
Content Negotiation 
Vary 
Caching 
Preconditions
Treat it like your framework.
Questions?
Resources 
• RFC 7231 
–Sections 4 - 7 
• http://redbot.org/ 
• http://mnot.net/cache_docs/ 
• http://charlesproxy.com/ 
• http://guzzlephp.org/
Image Credits 
• http://www.sxc.hu/photo/555539 
• http://www.flickr.com/photos/thedalogs/2953136078/ 
• http://www.sxc.hu/photo/678952 
• http://www.flickr.com/photos/designgate/8317884432/ 
• http://www.flickr.com/photos/barretthall/3289317664/ 
• http://www.flickr.com/photos/binaryape/3702275400/lightbox/ 
• http://www.flickr.com/photos/istolethetv/2956799679/in/photostream/ 
• http://theflashbackspodcast.blogspot.nl/2012/01/30-day-film-challenge-day-8.html 
• http://www.flickr.com/photos/jonomueller/6906420190/ 
• http://www.flickr.com/photos/cookbookman/6175752147 
• http://www.flickr.com/photos/creativedc/2913123330/ 
• http://www.flickr.com/photos/epc/44053757/ 
• http://www.flickr.com/photos/soggydan/4698849104/ 
• http://www.flickr.com/photos/owldreams/4430175427/ 
• http://www.flickr.com/photos/piddleville/2499539542/ 
• http://www.flickr.com/photos/danja/5665671907/ 
• http://www.flickr.com/photos/cradlehall/3574160744/ 
• http://www.flickr.com/photos/ironypoisoning/7114801437/ 
• http://www.flickr.com/photos/piddleville/2499539542/lightbox/ 
• http://everydayidrawadog.blogspot.nl/2009_02_01_archive.html
joind.in/12071 
Ross Tuck rosstuck.com 
@rosstuck

HTTP and Your Angry Dog