Save 37% off PRO during our Black Friday Sale! »

HTTP and Your Angry Dog

HTTP and Your Angry Dog

As presented at Whisky Web, Confoo, PFCongres and Fronteers User Group. An overview of some intermediate level HTTP features and how they might be useful in practice.

C26bfcbd5f786591e036fa0958a11e8b?s=128

Ross Tuck

April 13, 2013
Tweet

Transcript

  1. Ross Tuck HTTP And Your Angry Dog April 13th, Whisky

    Web
  2. Who Am I?

  3. Ross Tuck

  4. Team Lead at Ibuildings Codemonkey Token Foreigner REST nut

  5. @rosstuck

  6. Today's topic:

  7. Dogs

  8. None
  9. None
  10. None
  11. None
  12. HTTP & Dogs

  13. None
  14. None
  15. None
  16. None
  17. The Agenda

  18. Basics

  19. Client Server Request Response

  20. 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
  21. 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 2 Parts
  22. 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 The body
  23. 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> ... Request The body
  24. 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 The body
  25. 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
  26. 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 The headers
  27. 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 The good stuff
  28. 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
  29. 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 GET, POST, PUT, DELETE
  30. 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 Relative URL HTTP version
  31. 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 Key/Value pairs
  32. 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
  33. None
  34. 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", ... Response Status code
  35. Status Codes

  36. None
  37. None
  38. • 2xx • 3xx • 4xx • 5xx OK! Over

    there! Client screwed up! Server screwed up!
  39. Content Negotiation

  40. GET /dogs/corgi HTTP/1.1 Host: api.example.com Request

  41. HTTP/1.1 200 OK Date: Sun, 26 Aug 2012 18:00:43 GMT

    { "cute": true, "big": false, "data_dog": true } Response
  42. HTTP/1.1 200 OK Date: Sun, 26 Aug 2012 18:00:43 GMT

    <dog breed="corgi"> <cute>true</cute> <data capacity="on" /> </dog> Response
  43. GET /dogs/corgi HTTP/1.1 Host: api.example.com Request

  44. GET /dogs/corgi.json HTTP/1.1 Host: api.example.com Request

  45. /dogs/corgi.json !== /dogs/corgi.xml

  46. Imagine the URL as your primary key.

  47. GET /dogs/corgi HTTP/1.1 Host: api.example.com Request

  48. GET /dogs/corgi?_format=json HTTP/1.1 Host: api.example.com Request

  49. POST /dogs/corgi?_format=json HTTP/1.1 Host: api.example.com Request

  50. GET /dogs/corgi HTTP/1.1 Host: api.example.com Request

  51. GET /dogs/corgi HTTP/1.1 Host: api.example.com Accept: application/json Request

  52. More POWAH

  53. GET /dogs/corgi HTTP/1.1 Host: api.example.com Accept: application/json Request

  54. GET /dogs/corgi HTTP/1.1 Host: api.example.com Accept: application/json, application/xml Request How

    do I choose?
  55. None
  56. GET /dogs/corgi HTTP/1.1 Host: api.example.com Accept: application/json, application/xml Request

  57. 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 } Response
  58. Nifty.

  59. GET /dogs/corgi HTTP/1.1 Host: api.example.com Accept: application/json, application/xml Request

  60. text/html, text/plain

  61. text/html;key=value, text/plain

  62. text/html;key=value;foo=bar, text/plain

  63. text/html, text/plain

  64. text/html, text/plain;q=0.5 Quality (Default 1.0)

  65. text/html, text/plain;q=0.5, text/*;q=0.1 Wildcards

  66. text/html, text/plain;q=0.5, */*;q=0.1 Anything at all

  67. Accept Headers Little weird...

  68. But not so scary.

  69. text/html,application/xhtml+xml, application/xml;q=0.9,*/*;q=0.8

  70. Cool...

  71. What the heck is it good for?

  72. Accept is a “Pattern”

  73. Accept-Language Accept-Encoding Accept-Charset Accept-Ranges

  74. Content-Language Content-Encoding (works differently) Content-Range

  75. Resource vs Representation /dog/corgi JSON, Dutch, Gzipped, /dog/corgi

  76. Resource vs Representation

  77. Best way to version your API. Arguably. Right now.

  78. /v1/dogs/corgi

  79. Accept: application/vnd.dogipedia-v1+json

  80. Accept: application/vnd.dogipedia-v2+json

  81. None
  82. None
  83. Vary

  84. Client Server GET /dogs/corgi HTTP/1.1 Host: api.example.com

  85. Client Server GET /dogs/corgi HTTP/1.1 Host: api.example.com

  86. Client Server Proxy GET /dogs/corgi HTTP/1.1 Host: api.example.com

  87. Client Server Proxy GET /dogs/corgi HTTP/1.1 Host: api.example.com Accept: application/json,

    text/plain User-Species: cat
  88. Same URL. Different output. WTF should I return?

  89. Client Server Proxy GET /dogs/corgi HTTP/1.1 Host: api.example.com Accept: application/json,

    text/plain User-Species: cat
  90. Client Server Proxy

  91. Here's how. Hint: Involves the Vary header!

  92. Client Server Proxy /dogs/corgi Accept: application/json, text/plain User-Species: cat

  93. HTTP/1.1 200 OK Date: Sun, 26 Aug 2012 18:00:43 GMT

    Content-Type: application/json Vary: Accept {“json”: “omgz”} Response
  94. Client Server Proxy URL and Accept? Okay, I got this.

  95. Some time later...

  96. Client Server Proxy /dogs/corgi Accept: application/json, text/plain User-Species: aardvark

  97. Client Server Proxy Valid cache. I has it.

  98. Client Server Proxy ZZ Z Z Z Z

  99. HTTP/1.1 200 OK Date: Sun, 26 Aug 2012 18:00:43 GMT

    Content-Type: application/json Vary: Accept {“json”: “omgz”} Response
  100. 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
  101. 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”} Response
  102. Request headers. Not Response!

  103. Bad Reputation? 2 Reasons

  104. 1. Accept-Encoding -Language

  105. 2. Internet Explorer

  106. Caching

  107. Expires Pragma Cache-Control

  108. Expires Pragma Cache-Control

  109. Expires Cache-Control HTTP 1.0 HTTP 1.1

  110. HTTP/1.1 200 OK Expires: Thu, 07 Feb 2013 22:00:00 GMT

    {“herp”: “derp”} Response
  111. HTTP/1.1 200 OK Cache-Control: max-age=120 {“herp”: “derp”} Response

  112. HTTP/1.1 200 OK Cache-Control: max-age=120 Response

  113. Expires Cache-Control HTTP 1.0 HTTP 1.1

  114. HTTP/1.1 200 OK Cache-Control: max-age=120 {“herp”: “derp”} Response

  115. HTTP/1.1 200 OK Cache-Control: max-age=120, s-maxage=120 {“herp”: “derp”} Response Dude,

    Where's my dash?
  116. public private no-store no-cache no-transform must-revalidate proxy-revalidate

  117. Mark Nottingham's Caching Tutorial http://www.mnot.net/cache_docs/ Much better than me.

  118. Conditional Requests

  119. Conditional Requests

  120. The Part About ETags

  121. Conditional Requests

  122. DELETE /ross/reputation HTTP/1.1 Host: api.joind.in If-Talk-Quality: Crap Request

  123. if ($talkQuality === 'Crap') { delete($rossReputation); } Not real code

    Server
  124. What kind of conditions?

  125. If-Match If-None-Match If-Modified-Since If-Unmodified-Since If-Range ETags Datetimes Either

  126. Wait a second, Ross. Audience

  127. What the heck is an ETag, anyways?

  128. A string. Any string.

  129. One rule:

  130. Represent the current state.

  131. “14” “a381bedb5d4478053eb04be35f8798dd” “winnie-the-pooh”

  132. ...for the current representation.

  133. etag(“v14-json-en”) !== etag(“v14-xml-en”) Don't cross the streams Server

  134. Last Modified Date sounds easier... Audience

  135. Wed, 15 Nov 1995 04:58:08 GMT One second of precision

  136. Caching With Conditionals

  137. Use Case

  138. GET /gists/3481910 HTTP/1.1 Host: api.github.com Accept: */* Request

  139. 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
  140. ... "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
  141. "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
  142. None
  143. 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", Response "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" }
  144. None
  145. GET /gists/3481910 HTTP/1.1 Host: api.github.com Accept: */* If-None-Match: "f4e15911542b92b44bb38186e71cc8f5" Request

  146. 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
  147. 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
  148. 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 No giant body!
  149. Caching. You has it.

  150. GET /gists/3481910 HTTP/1.1 Host: api.github.com Accept: */* If-None-Match: "a381bedb5d4478053eb04be35f8798dd" Request

  151. GET /gists/3481910 HTTP/1.1 Host: api.github.com Accept: */* If-None-Match: "ross-is-a-poo-poo-head" Request

  152. 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
  153. Recap

  154. No ETag Old ETag Matching ETag Full Body Full Body

    No Body → → →
  155. ...on supported servers.

  156. Why?

  157. Parsing Bandwidth Response time Probably ...Maybe

  158. However...

  159. “The fastest request is one you don't make.” - Jesus

  160. More Fun With ETags

  161. Optimistic Concurrency Control “Record Versioning”

  162. Request

  163. GET /gists/3481910 HTTP/1.1 Host: api.github.com Accept: */* If-None-Match: "f4e15911542b92b44bb38186e71cc8f5" Request

  164. PATCH /gists/3481910 HTTP/1.1 Host: api.github.com Accept: */* If-None-Match: "f4e15911542b92b44bb38186e71cc8f5" Request

  165. PATCH /gists/3481910 HTTP/1.1 Host: api.github.com Accept: */* If-Match: "f4e15911542b92b44bb38186e71cc8f5" Request

  166. PATCH /gists/3481910 HTTP/1.1 Host: api.github.com Accept: */* If-Match: "f4e15911542b92b44bb38186e71cc8f5" {

    "description": "cheese om nom nom" } Request
  167. Response

  168. 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", ...
  169. It works.

  170. So what?

  171. What if I send something...

  172. None
  173. PATCH /gists/3481910 HTTP/1.1 Host: api.github.com Accept: */* If-Match: "899b76047a5e68445668374c2e0faa32" {

    "description": "cheese om nom nom" } Request
  174. PATCH /gists/3481910 HTTP/1.1 Host: api.github.com Accept: */* If-Match: "stay-puft-marshmellow-dog!" {

    "description": "cheese om nom nom" } Request
  175. HTTP/1.1 412 Precondition Failed Server: nginx/1.0.13 Date: Sun, 26 Aug

    2012 18:00:43 GMT Response
  176. Response

  177. if (“stay-puft-marshmellow-dog” == “f4e1591..”) { patchTheRecord(); } Server

  178. if (“stay-puft-marshmellow-dog” == “f4e1591..”) { patchTheRecord(); } else { sendScary412Message();

    } Server
  179. Your ETag is out of date.

  180. “Two guys on the same record” problem

  181. Other Scary Precondition Errors

  182. Disclaimer New Stuff Ahead

  183. DELETE /gists/3481910 HTTP/1.1 Host: api.github.com Request

  184. HTTP/1.1 428 Precondition Required Server: nginx/1.0.13 Date: Sun, 26 Aug

    2012 18:00:43 GMT Response
  185. Am I operating on the latest version?

  186. DELETE /gists/3481910 HTTP/1.1 Host: api.github.com Request

  187. DELETE /gists/3481910 HTTP/1.1 Host: api.github.com If-Match: "f4e15911542b92b44bb38186e71cc8f5" Request

  188. HTTP/1.1 204 No Content Server: nginx/1.0.13 Date: Sun, 26 Aug

    2012 18:00:43 GMT Response
  189. Look before you leap.

  190. Tooling

  191. None
  192. None
  193. None
  194. Epilogue: HTTP & Dogs

  195. Content Negotiation Vary Caching Preconditions

  196. Treat it like your framework.

  197. None