bosatsu dataversity rest workshop · 2013. 8. 23. · web's major goal was to be a shared...

Post on 05-Sep-2020

0 Views

Category:

Documents

0 Downloads

Preview:

Click to see full reader

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: <csupport@registerapi.com>Date: Fri Apr 11, 2003 19:31:28 US/EasternTo: timbl@w3.orgSubject: 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>bob@example.com</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>bob@example.com</email>

<contact> <email primary="true">bob@example.com</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>bob@example.com</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) brian@bosatsu.net (mailto:brian@bosatsu.net)

top related