bosatsu dataversity rest workshop · 2013. 8. 23. · web's major goal was to be a shared...
TRANSCRIPT
REST WorkshopREST WorkshopBrian Sletten (@bsletten (http://twitter.com/bsletten)) 07/23/2013
Speaker's QualificationSpeaker's Qualification
20 years of experienceSpecialize in benefits and applicability of next-generation technologiesAuthor "Resource-Oriented Architectural Patterns" Morgan & Claypool 2013One of Top 100 Semantic Web People (http://www.semanticweb.com/semanticweb100/)
AgendaAgenda
IntroductionArchitectureIdentityInteractionRepresentation
Web's major goal was tobe a shared informationspace through which peopleand machines couldcommunicate.Tim Berners-Lee
What was needed was a way for peopleto store and structure their owninformation, whether permanent orephemeral in nature, such that it could beusable by themselves and others, and to beable to reference and structure theinformation stored by others so that itwould not be necessary for everyone tokeep and maintain local copies.Roy Fielding
Why the Web worksWhy the Web works
Loose couplingLocalized changesGlobal namespaceClicking resolves and requestsContent negotiation
Does your browser...Does your browser...
Have to read a website's API documentation?Break when a website rearranges its content?Break when a website changes its implementation technology?
REST vs SOAPREST vs SOAP
http://www.indeed.com/trendgraph/jobgraph.png?q=rest%2C+soap
SOAP RequestSOAP RequestPOST /quote HTTP/1.1Host: www.example.orgContent-Type: text/xml; charset=utf-8Content-Length: nnn
<?xml version="1.0"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope" SOAP-ENV:encodingStyle="http://www.w3.org/2001/12/soap-encoding"> <SOAP-ENV:Body xmlns:m="http://www.example.org/quote"> <m:GetQuote> <m:QuoteName>AAPL</m:QuoteName> </m:GetQuote> </SOAP-ENV:Body></SOAP-ENV:Envelope>
REST RequestREST RequestGET /quote/AAPL HTTP/1.1Host: www.example.org
SOAP ResponseSOAP ResponseHTTP/1.1 200 OKContent-Type: text/xml; charset=utf-8Content-Length: nnn
<?xml version="1.0"?><SOAP-ENV:Envelopexmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope"SOAP-ENV:encodingStyle="http://www.w3.org/2001/12/soap-encoding"> <SOAP-ENV:Body xmlns:m="http://www.example.org/quote"> <m:GetQuoteResponse> <m:Quote>358.16</m:Quote> </m:GetQuoteResponse> </SOAP-ENV:Body></SOAP-ENV:Envelope>
REST ResponseREST ResponseHTTP/1.1 200 OKContent-Type: text/xml; charset=utf-8Content-Length: nnn
<?xml version="1.0"?><quote name=”AAPL”> <price>358.16</price></quote>
Contextualized RequestContextualized Request
Decontextualized RequestDecontextualized Request
Decontextualized RequestDecontextualized Request
Decontextualized RequestDecontextualized Request
What REST Isn't...What REST Isn't...
RPC through URLsA direct replacement for SOAPXML over HTTPInsecureA toyWhatever you want it to be
RMM Level 0RMM Level 0
RMM Level 1RMM Level 1
RMM Level 2RMM Level 2
RMM Level 3RMM Level 3
ArchitectureArchitecture
Desired PropertiesDesired Properties
PerformanceScalabilityGeneralitySimplicityModifiabilityExtensibility
Client ServerClient Server
Stateless Client-ServerStateless Client-Server
Stateless Client-Server w/ Cache and Uniform InterfaceStateless Client-Server w/ Cache and Uniform Interface
Layered Stateless Client-Server w/ Cache and Uniform Inter-Layered Stateless Client-Server w/ Cache and Uniform Inter-faceface
REST on a SlideREST on a Slide
"Architectural Styles and the Design of Network-based Software Architectures"(http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm)
IdentityIdentity
What's in a name? Thatwhich we call a rose byany other name wouldsmell as sweet.Shakespeare, "Romeo and Juliet"
The function of a nameis to facilitate sharing.Ross J. Anderson
When systems become large, the scale-upproblems are not linear; there is often aqualitative change in complexity, and somethings that are trivial to deal with in anetwork of only a few machines andprincipals (such as naming) suddenlybecome a big deal.Ross J. Anderson
Naming SchemesNaming Schemes
First name, Last NameDatabase keysSocial Security NumbersISBNDOITelephone numbers
Naming Scheme PropertiesNaming Scheme Properties
IdentityDisambiguationScopeResolvability
URIsURIs
http://bosatsu.net/foaf/brian.rdf
ftp://ftp.funet.fi/pub/standards/RFC/rfc959.txt
urn:isbn:0913966568
Uses for URLsUses for URLs
Documents
Data
Services
Concepts
http://myserver.com/docs/reports/2009/qtr/3
http://myserver.com/gene/ade2
http://myserver.com/order?type=open
http://purl.org/net/bsletten
Information SpaceInformation Space
http://someserver.com/path1/subpath1/subsubpath1
http://someserver.com/path2/subpath2/subsubpath2
URI AliasesURI Aliases
http://bosatsu.net/time/zone/PST
http://bosatsu.net/time/state/us/hawaii
Canonical vs HistoricalCanonical vs Historical
http://bosatsu.net/talks/topic/rest
http://bosatsu.net/talks/conf/nfjs/2013/rest/minneapolis/1
Global naming leads toglobal network effects.Architecture of the World Wide Web, Volume I
Any resource anywherecan be given a URI.Axioms of Web Architecture
Any resource ofsignificance should begiven a URI.Axioms of Web Architecture
It doesn't matter towhom or where youspecify that URI, it willhave the same meaning.Axioms of Web Architecture
A URI will repeatedlyrefer to 'the same' thing.
Axioms of Web Architecture
The significance ofidentity for a given URI isdetermined by the personwho owns the URI, whofirst determined what itpoints to.Axioms of Web Architecture
URI space does not haveto be the only universalspace.Axioms of Web Architecture
What makes a cool URI?A cool URI is one whichdoes not change.
"Cool URIs Don't Change"
What sorts of URIchange?URIs don't change: peoplechange them."Cool URIs Don't Change"
Things to AvoidThings to Avoid
Author nameSubjectStatusAccessFile name extensionSoftware mechanism
A name...
is a name...
is a name...
is an attack vector...
10.0.0.1
http://example.com&gibberish=1234@167772161
In Firefox, http://coredump.cx
http://example.com\@coredump.cx
In IE, http://coredump.cx
Safari, Error.
Others, http://example.com/;.coredump.cx
http://example.com;.coredump.cx
InteractionInteraction
Application #1 Application #2 Application #3
REST API
Code
Application #1 Application #2 Application #3
REST API
Code
GETGET
POSTPOST
PUTPUT
DELETEDELETE
HEADHEAD
OPTIONSOPTIONS
PATCHPATCH
OPTIONS /example/buddies.xml HTTP/1.1Host: www.example.com
HTTP/1.1 200 OKAllow: GET, PUT, POST, OPTIONS, HEAD, DELETE, PATCHAccept-Patch: application/example, text/example
PATCH /example/buddies.xml HTTP/1.1Host: www.example.comContent-Type: application/exampleIf-Match: "e0023aa4e"Content-Length: 100
[patch changes]
HTTP/1.1 204 No ContentContent-Location: /example/buddies.xmlETag: "e0023aa4f"
IdempotencyIdempotency
GET POST PUT PATCH ~DELETE
Response Codes: 2XXResponse Codes: 2XX
Code Meaning200 Ok201 Created202 Accepted204 Success
Response Codes: 3XXResponse Codes: 3XX
Code Meaning301 Moved Permanently302 Found303 See Other304 Not Modified
Response Codes: 4XXResponse Codes: 4XX
Code Meaning400 Bad Request401 Unauthorized403 Forbidden404 Not Found405 Method Not Allowed409 Conflict411 Length Required413 Entity Too Long415 Unsupported Media Type
Response Codes: 5XXResponse Codes: 5XX
Code Meaning500 Internal Error503 Service Unavailable
The web is an information space. Whenyou explore it, you don't end up buyingstuff, agreeing to anything, or - in thiscase, losing your domain name...Tim Berners-Lee
Hall of FlameHall of FlameFrom: <[email protected]>Date: Fri Apr 11, 2003 19:31:28 US/EasternTo: [email protected]: Confirm Domain Transfer
A Transfer Request was submitted for the following domains.Click on the following link to confirm the domain transfer request for these domains.
https://secure.registerapi.com/order/trx/confirm.php?id=cesO[...]cbI
Your Transfer Request Code IS: ces[...]cbIhttps://secure.registerapi.com/order/trx/confirm.php
Domains:WWW.ORGIf you did not request the transfer of these domains then DO NOT click on the above links.By not clicking you are preventing a domain registrar transfer from taking place.
Thank you, The Automated Domain Transfer System
CSRFCSRF
<img src="http://bank.example.com/withdraw ?acct=Bob&amt=1000000&for=Fred"/>
http://example.com/admin?deleteUser=brian
<security-constraint> <web-resource-collection> <url-pattern>/admin/*</url-pattern> <http-method>GET</http-method> <http-method>POST</http-method> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> </auth-constraint></security-constraint>
apiary.ioapiary.io
ToolsTools
Firefox REST ClientFirefox REST Client
CurlCurl
brian@characterzero ~> curl --include \ http://accountexamples.apiary.io/shopping-cartHTTP/1.1 200 OKContent-Type: application/jsonDate: Sun, 14 Jul 2013 05:23:49 GMTX-Apiary-Ratelimit-Limit: 120X-Apiary-Ratelimit-Remaining: 119Content-Length: 119Connection: keep-alive
{ "items": [ { "url": "/shopping-cart/1", "product":"2ZY48XPZ", "quantity": 1, "name": "New socks", "price": 1.25 }]} brian@characterzero ~>
brian@characterzero ~> curl --include --header "Content-Type: application/json" \ --request POST \ --data-binary "{ \"product\":\"1AB23ORM\", \"quantity\": 2 }" http://accountexamples.apiary.io/shopping-cart HTTP/1.1 201 CreatedContent-Type: application/jsonDate: Sun, 14 Jul 2013 05:11:03 GMTLocation: http://accountexamples.apiary.io/shopping-cart/2X-Apiary-Ratelimit-Limit: 120X-Apiary-Ratelimit-Remaining: 119Content-Length: 50Connection: keep-alive
{ "status": "created", "url": "/shopping-cart/2" }brian@characterzero ~>
HTTP BuilderHTTP BuilderRESTClientRESTClient
import groovyx.net.http.RESTClientimport groovy.util.slurpersupport.GPathResultimport static groovyx.net.http.ContentType.URLENC twitter = new RESTClient( 'https://twitter.com/statuses/' )// twitter auth omitted try { // expect an exception from a 404 response: twitter.head path : 'public_timeline' assert false, 'Expected exception'}// The exception is used for flow control but has access // to the response as well:catch( ex ) { assert ex.response.status == 404 } assert twitter.head( path : 'public_timeline.json' ).status == 200
def resp = twitter.get( path : 'friends_timeline.json' )assert resp.status == 200assert resp.contentType == JSON.toString()assert ( resp.data instanceof net.sf.json.JSON )assert resp.data.status.size() > 0
def msg = "I'm using HTTPBuilder's RESTClient on ${new Date()}" resp = twitter.post( path : 'update.xml', body : [ status:msg, source:'httpbuilder' ], requestContentType : URLENC ) assert resp.status == 200assert ( resp.data instanceof GPathResult ) // parsed using XmlSlurperassert resp.data.text == msgassert resp.data.user.screen_name == userNamedef postID = resp.data.id.toInteger()
resp = twitter.delete( path : "destroy/${postID}.json" )assert resp.status == 200assert resp.data.id == postIDprintln "Test tweet ID ${resp.data.id} was deleted."
client = new RESTClient(url) client.parser."application/vnd.collections+json" = { resp -> def r = new InputStreamReader(resp.entity.content, ParserRegistry.getCharset(resp)) def slurper = new JsonSlurper() slurper.parse(r)}
client.parser."application/vnd.collections+xml" = { resp -> def r = new InputStreamReader(resp.entity.content, ParserRegistry.getCharset(resp)) def slurper = new XmlSlurper() slurper.parse(r)}
resp = client.get(['path' : '/api'])def rep = resp.data
assert rep.collection.version == '1.0'assert rep.collection.href == '/api'assert rep.collection.links.size() == 3
["account", "order", "product"].each { t -> assert rep.collection.links.find { it['rel'] == t && it['href'] == "/$t" }}
Error-HandlingError-Handling
Client ErrorsClient Errors
400 Bad Request403 Forbidden404 Not found...500 Internal Server Error503 Service Unavailable
Application-Level Error MessageApplication-Level Error Message
Standardize an error message responseNon-200 response codes
HTTP/1.1 400 Bad RequestContent-Type: application/xml;charset=UTF-8Link: <http://server/error/someexplanation>
<error> <message>You have made an invalid request... </message></error>
Representation DesignRepresentation Design
Simple Commerce EngineSimple Commerce Engine
User accountsItemsOrdersWorkflow
GuidelinesGuidelines
Don't reflect implementation detailsFocus on what makes sense for the domainConsider the future in designsDon't constrain too early
http://someserver/account/12345
<?xml version="1.0" encoding="utf-8"?><account> <type>3</type> <firstname>Bob</firstname> <lastname>Jones</lastname> <address> <first>12345 Main St.</first> <second>Apt. #234</second> <city>Minneapolis</city> <state>MN</state> <zipcode>55413</zipcode> </address> <email>[email protected]</email></account>
URLs as IDsURLs as IDs
Don't put sensitive information in URLs!
http://someserver/account/12345
http://someserver/account/new
http://someserver/account/best
http://someserver/account/id/12345
Identifying the ResourceIdentifying the Resource<account id="12345"> <link rel="self" href="http://someserver/account/id/12345"/> ...</account>
Keep it MeaningfulKeep it Meaningful<type>3</type>
<type>priority</type>
Clustering Related InformationClustering Related Information<firstname>Bob</firstname><lastname>Jones</lastname>
<name> <firstname>Bob</firstname> <lastname>Jones</lastname></name>
Plan AheadPlan Ahead<address> <first>12345 Main St.</first> <second>Apt. #234</second> <city>Minneapolis</city> <state>MN</state> <zipcode>55413</zipcode></address>
Plan AheadPlan Ahead<addresses> <address name="home" default="true"> <first>12345 Main St.</first> <second>Apt. #234</second> <city>Minneapolis</city> <state>MN</state> <zipcode>55413</zipcode> </address></addresses>
Functional DecompositionFunctional Decomposition<email>[email protected]</email>
<contact> <email primary="true">[email protected]</email> <phone sms="true">555-1212</phone> <twitter>bjones9999</twitter></contact>
Cleaned Up ResourceCleaned Up Resource<?xml version="1.0" encoding="utf-8"?><account id="12345"> <link rel="self" href="http://someserver/account/id/12345"/> <type>priority</type> <name> <firstname>Bob</firstname> <lastname>Jones</lastname> </name> <addresses> <address name="home" default="true"> <first>12345 Main St.</first> <second>Apt. #234</second> <city>Minneapolis</city> <state>MN</state> <zipcode>55413</zipcode> </address> </addresses> <contact> <email>[email protected]</email> </contact></account>
http://someserver/item/status/new
http://someserver/item/status/sale
http://someserver/item/id/1111
http://someserver/item/id/1111
<?xml version="1.0" encoding="utf-8"?><item id="1111"> <link rel="self" href="http://someserver/item/id/1111"/> <name>Deadwood Season 4</name> <description>Deadwood's most popular season.</description> <price>$32.00</price> <image>http://someserver/content/image/1111</image> <sku>1111</sku> <upc>1234123412431234123</upc></item>
InternationalizationInternationalization<description> Deadwood's most popular season.</description>
<description lang="en_US"> Deadwood's most popular season.</description>
InternationalizationInternationalization
<price>$32.00</price>
InternationalizationInternationalization
<price currency="USD">32.00</price>
Multiple IDsMultiple IDs<sku>1111</sku>
<upc>1234123412431234123</upc>
Multiple IDsMultiple IDs<sku>1111</sku>
<upc>1234123412431234123</upc>
http://someserver/item/id/sku/1111
http://someserver/item/id/upc/1234123412431234123
http://someserver/order/status/new
http://someserver/order/status/open
http://someserver/order/id/101-545
http://someserver/order/cust/12345
http://someserver/order/id/101-545
<?xml version="1.0" encoding="utf-8"?><order id="101-545"> <link rel="self" href="http://someserver/order/id/101-545"/> <items> <item id="1111"> <price currency="USD">32.00</price> </item> <item id="2222"> <price currency="USD">10.00</price> </item> </items> <addresses> <address shipping="true" billing="true"> <first>12345 Main St.</first> <second>Apt. #234</second> <city>Minneapolis</city> <state>MN</state> <zipcode>55413</zipcode> </address> </addresses></order>
application/vnd.acme.account+xml
application/vnd.acme.item+xml
application/vnd.acme.order+xml
<?xml version="1.0" encoding="utf-8"?><order id="101-545"> <link rel="self" href="http://someserver/order/id/101-545"/> <items> <item id="1111"> <price currency="USD">32.00</price> </item> <item id="2222"> <price currency="USD">10.00</price> </item> </items></order>
<?xml version="1.0" encoding="utf-8"?><order id="101-545"> <link rel="self" href="http://someserver/order/id/101-545"/> <link rel="submit" href="http://someserver/order/id/101-545/submit"/> <items> <item id="1111"> <price currency="USD">32.00</price> </item> <item id="2222"> <price currency="USD">10.00</price> </item> </items> <addresses> <address id="1" shipping="true" billing="true"> ... </address> </addresses> <tender> <credit-card id="1" number="XXXX1344"> </credit-card> </tender></order>
<?xml version="1.0" encoding="utf-8"?><order id="101-545"> <link rel="self" href="http://someserver/order/id/101-545"/> <link rel="submit" href="http://someserver/order/id/101-545/submit"/> <items> <link rel="self" href="http://someserver/order/id/101-545/item"/> <item id="1111"> <price currency="USD">32.00</price> </item> <item id="2222"> <price currency="USD">10.00</price> </item> </items> <addresses> <link rel="self" href="http://someserver/order/id/101-545/address"/> <address id="1" shipping="true" billing="true"> ... </address> </addresses> <tender> <link rel="self" href="http://someserver/order/id/101-545/tender"/> <credit-card id="1" number="XXXX1344"> </credit-card> </tender></order>
<?xml version="1.0" encoding="utf-8"?><order id="101-545"> <link rel="self" href="http://someserver/order/id/101-545"/> <link rel="submit" href="http://someserver/order/id/101-545/submit"/> <items> <item id="1111"> <link rel="self" href="http://someserver/order/id/101-545/item/1"/> <price currency="USD">32.00</price> </item> <item id="2222"> <link rel="self" href="http://someserver/order/id/101-545/item/2"/> <price currency="USD">10.00</price> </item> </items> <addresses> <address id="1" shipping="true" billing="true"> <link rel="self" href="http://someserver/order/id/101-545/address/1"/> ... </address> </addresses> <tender> <credit-card id="1" number="XXXX1344"> <link rel="self" href="http://someserver/order/id/101-545/tender/1"/> </credit-card> </tender></order>
Content NegotiationContent Negotiation
Logically-named ResourcesLogically-named Resourceshttp://server/report/2010/01/sales
http://server/report/2010/01/sales.html
http://server/report/2010/01/sales.xml
http://server/report/2010/01/sales.xls
AcceptAcceptAccept: application/xml; q=1.0, application/json; q=0.6, */*; q=0.0Accept-Language: en;q-1.0, jp;q=0.5Accept-Encoding: gzip
Vary Response HeaderVary Response HeaderVary: Accept-LanguageVary: Accept
Naming RulesNaming Rules
Don't define resources w/ trailing slashesPrefer hyphens to underscores in resource namesUse lowercase lettersDon't require filename extensionsDefine subdomains for your APIs
Response Header RulesResponse Header Rules
Use Content-TypeUse Content-LengthUse Last-ModifiedUse Location for Created Resources
Conditional RequestsConditional Requests
Use ETagsSupport Conditional Requests
CachingCaching
Encourage CachingUse Cache-Control, Expires and Date to Enable CachingUse Cache-Control, Expires and Pragma to Disable Caching
HypermediaHypermedia
HFactorsHFactors
Mike Amundsen (@mamund)
Embedded Links (LE)Embedded Links (LE)
LE
Dereference specified URI and display within current window.
Embedded Links (LE)Embedded Links (LE)
<img src="http://bosatsu.net/images/bosatsu-kanji.jpg"/>
<x:include href="http://bosatsu.net/snippets/bio.xml"/>
Outbound Links (LO)Outbound Links (LO)
LO
Dereference specified URI and display within new window.
Outbound Links (LO)Outbound Links (LO)
See you at <a href="http://uberconf.com">Überconf</a>.
Templated Links (LT)Templated Links (LT)
LT
Accumulate input parameters into URI definition as part of read operation.
Templated Links (LT) #1Templated Links (LT) #1
<form method="get" action="http://bosatsu.net/submit"> <input type="text" name="course" value="REST"/> <input type="submit"/></form>
http://bosatsu.net/submit?course=REST
Templated Links (LT) #2Templated Links (LT) #2
URI Templates (RFC 6570)
http://example.com/~{username}/
http://example.com/dictionary/{term:1}/{term}
http://example.com/search{?q,lang}
http://example.com/~fred/http://example.com/~mark/
http://example.com/dictionary/c/cathttp://example.com/dictionary/d/dog
http://example.com/search?q=cat&lang=enhttp://example.com/search?q=chien&lang=fr
Non-Idempotent Links (LN)Non-Idempotent Links (LN)
LN
Submit a non-idempotent request.
Non-Idempotent Links (LN)Non-Idempotent Links (LN)
<form method="post" action="http://bosatsu.net/submit/"> <textarea name="feedback">Hey Buddy, nice jacket</textarea> <input type="submit"/></form>
POST /submit/ HTTP/1.1Host: bosatsu.netContent-Type: application/x-www-form-urlencodedLength:34
feedback=Hey+Buddy%2C+nice+jacket
Idempotent Links (LI)Idempotent Links (LI)
LI
Submit an idempotent request.
Idempotent Links (LI)Idempotent Links (LI)
<link rel="edit" href="http://bosatsu.net/feed/edit/1"/>
<script type="text/javascript">function delete(url) { var xhr = new XMLHttpRequest(); xhr.open("DELETE", url);}</script>
Read Controls (CR)Read Controls (CR)
CR
Specify read control context.
Read Controls (CR)Read Controls (CR)
<x:include href="http://bosatsu.net/snippets/bio.xml" accept-language="en, en-gb; q=0.9, jp;q=0.3"/>
Update Controls (CU)Update Controls (CU)
CU
Specify update control context.
Update Controls (CU)Update Controls (CU)
<form method="post" action="http://bosatsu.net/submit/" enctype="text/plain"> <textarea name="feedback">Hey Buddy, nice jacket</textarea> <input type="submit"/></form>
POST /submit/ HTTP/1.1Host: bosatsu.netContent-Type: text/plainLength:34
Hey+Buddy%2C+nice+jacket
Method Controls (CM)Method Controls (CM)
CM
Specify the protocol method for the request.
Method Controls (CM)Method Controls (CM)
<form method="post" action="/feedback"> <input name="keywords" type="text" value="foo,bar,baz"/> <input type="submit"/></form>
<form method="get" action="/feedback"> <input name="keywords" type="text" value="foo,bar,baz"/> <input type="submit"/></form>
Link Controls (CL)Link Controls (CL)
CL
Specify link control context.
Link Controls (CL)Link Controls (CL)
<entry> <title>The Bully</title> <link rel="edit" href="http://bosatsu.net/feed/1"/> <id>tag:bosatsu.net,2013:3.2397</id> <updated>2013-03-01T12:29:29Z</updated> <published>2013-02-13T08:29:29-04:00</published> <author> <name>Brian Sletten</name> </author> <content>Hey Buddy, nice car.</content></entry>
<link rel="stylesheet" href="http://bosatsu.net/css/default.css"/>
text/uri-listtext/uri-list
CL
CR CU CM
LE LO LT LN LI
application/svg+xmlapplication/svg+xml
CL
CR CU CM
LE LO LT LN LI
application/atom+xmlapplication/atom+xml
CL
CR CU CM
LE LO LT LN LI
text/htmltext/html
CL
CR CU CM
LE LO LT LN LI
application/vnd.collections+jsonapplication/vnd.collections+json
CL
CR CU CM
LE LO LT LN LI
{ "collection" : { "version" : "1.0",
"href" : "http://example.org/friends/" } }
{ "collection" : { "version" : "1.0", "href" : "http://example.org/friends/", "links" : [], "items" : [], "queries" : [], "template" : [] } }
http://example.org/api
{ "collection": { "version": "1.0", "href": "/api", "links": [ { "rel": "account", "href": "/account" }, { "rel": "order", "href": "/order" }, { "rel": "product", "href": "/product" } ] }}
http://example.org/account
http://example.org/order
http://example.org/product
http://example.org/account
{ "collection" : { "version" : "1.0", "href" : "/account", "links" : [ { "rel": "next", "href": "/account;page=2" } ], "items" : [], "queries" : [], "template" : [] } }
http://example.org/account;page=2
{ "collection" : { "version" : "1.0", "href" : "/account;page=2", "links" : [ { "rel": "prev", "href": "/account;page=1" }, { "rel": "next", "href": "/account;page=3" } ], "items" : [], "queries" : [], "template" : [] } }
http://example.org/account
{ "collection" : { ... "items" : [], ... } }
http://example.org/account
... "items": [ { "href": "/account", "data": [ { "name": "type", "value": "account" }, { "name": "total", "value": 37 }, { "name": "page", "value": 1 }, { "name": "ipp", "value": 10 } ] }, ...
http://example.org/account
... { "href": "/account/id/9468", "data": [ { "name": "username", "value": "bob" }, { "name": "id", "value": "9468" } ], "links": [ { "name": "open", "value": "/order/account/id/9468;status=open" }, { "name": "recent", "value": "/order/account/id/9468;status=recent" } ] }, ...
http://example.org/account
{ "collection" : { ... "queries" : [ { "encoding": "uri-template", "rel": "search", "href": "/account{;status,page,ipp}", "prompt": "Search", "data": [ { "name": "status", "value": [ ] }, { "name": "page", "value": [ ] }, { "name": "ipp", "value": [ ] } ] } ], ... } }
http://example.org/account;status=open;page=2
{ "collection" : { ... "queries" : [ { "encoding": "uri-template", "rel": "search", "href": "/account{;status,page,ipp}", "prompt": "Search", "data": [ { "name": "status", "value": [ "open" ] }, { "name": "page", "value": [ "2" ] }, { "name": "ipp", "value": [ ] } ] } ], ... } }
BooksBooks
Questions?Questions?
@bsletten (http://twitter.com/bsletten) [email protected] (mailto:[email protected])