Resource-Oriented Web Services
Upcoming SlideShare
Loading in...5
×
 

Resource-Oriented Web Services

on

  • 7,885 views

This presentation will discuss how the Representational State Transfer (REST) architectural style can be applied to the design of your web services. ...

This presentation will discuss how the Representational State Transfer (REST) architectural style can be applied to the design of your web services.

You will learn how to use HTTP methods and status codes properly and we will discuss how to use Hypermedia As The Engine Of Application State (HATEOAS). The principles of REST and HATEOAS will be demonstrated through the Atom Publishing Protocol (AtomPub) using the Google Data APIs and other AtomPub implementations as examples.

Statistics

Views

Total Views
7,885
Views on SlideShare
7,793
Embed Views
92

Actions

Likes
7
Downloads
192
Comments
1

10 Embeds 92

http://bradley-holt.blogspot.com 47
http://www.slideshare.net 19
http://bradley-holt.com 15
http://bradley-holt.blogspot.in 5
https://duckduckgo.com 1
http://bradley-holt.blogspot.de 1
http://www.blogger.com 1
http://translate.googleusercontent.com 1
http://localhost 1
http://bradley-holt.blogspot.ch 1
More...

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

CC Attribution License

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Resource-Oriented Web Services Resource-Oriented Web Services Presentation Transcript

  • Resource-Oriented Web Services Applying the REST Architectural Style
  • This presentation will discuss how the Representational State Transfer (REST) architectural style can be applied to the design of your web services. You will learn how to use HTTP methods and status codes properly and we will discuss how to use Hypermedia As The Engine Of Application State (HATEOAS). The principles of REST and HATEOAS will be demonstrated through the Atom Publishing Protocol (AtomPub) using the Google Data APIs and other AtomPub implementations as examples.
  • HTTP "Hypertext Transfer Protocol (HTTP) is an application-level protocol for distributed, collaborative, hypermedia information systems. Its use for retrieving inter-linked resources led to the establishment of the World Wide Web."[1]
  • Methods and Resources HTTP methods are the actions that can be performed on resources.
  • Uniform Resource Identi ers (URIs) Resources are identi ed by a URI.
  • URIs Example URIs: http://www.example.org/people http://www.example.org/people/bradley-holt Examples of resources could include: • documents • people • places • things • abstract concepts (e.g. processes, transactions)
  • Limited Vocabulary There are only 8 methods: HEAD, GET, POST, PUT, DELETE, TRACE, OPTIONS, CONNECT (we're only going to talk about 4 of them); but you get to de ne your own resources.[2]
  • REST Architecture • HTTP is just one (very popular) instance of the REST architecture. • You can use HTTP correctly and not be RESTful and you can use REST without HTTP. • REST is about de ning a uniform interface. Example of a non-RESTful standard based on HTTP: • WebDAV
  • What Makes a Service RESTful? If the HTTP method doesn’t match the method information, the service isn’t RESTful. If the scoping information isn’t in the URI, the service isn’t resource-oriented. These aren’t the only requirements, but they’re good rules of thumb. From RESTful Web Services[3]
  • HTTP Methods How are you manipulating a resource? We’ll cover four of the eight methods...
  • GET • GET a representation of a resources. • Safe: Can't hold the user responsible for side-effects. • Idempotent: N > 0 identical requests are each the same as a single request. • Cacheable. Example: GET /people HTTP/1.1 Note that the HTTP request and response examples in this presentation are meant to be illustrative and are not always complete. Some HTTP headers may be missing.
  • More on Safety A hit counter is generally "safe". Yes, it changes state but the user is not held accountable for that state transition. Deleting something is not safe: you've held the user accountable. For example, Google Web Accelerator (cache pre-fetching) broke 37signals' Backpack web application because they were using GET to delete information[4].
  • POST • POST a new representation of a resource. • New resource is subordinate to the requested resource. • Not safe. • Not idempotent. • Can be cached only through the Cache-Control or Expires header elds. Example: POST /people HTTP/1.1 Content-Type: application/x-www-form-urlencoded fn=Bradley+Holt&url=http%3A%2F%2Fbradley-holt.blogspot.com%2F
  • PUT • PUT a modi ed representation of a resource. • Not safe. • Idempotent: PUTting the same thing multiple times is the same as doing it once. • Responses are not cacheable. Example: PUT /people/bradley-holt HTTP/1.1 Content-Type: application/x-www-form-urlencoded fn=Bradley+Holt&url=http%3A%2F%2Fbradley-holt.blogspot.com%2F
  • DELETE • DELETE a resource. • Not safe. • Idempotent. Deleting something multiple times is the same as doing it once. • Responses are not cacheable. Example: DELETE /people/bradley-holt HTTP/1.1
  • HTTP Status Codes What was the result of your request? A few examples...[5]
  • 200 OK Request: GET /people/bradley-holt HTTP/1.1 Response: HTTP/1.1 200 OK Content-Type: text/html <!DOCTYPE html> <html> <head> <title>Bradley Holt</title> </head> <body> <div class="vcard"> <a class="url fn" href="http://bradley-holt.blogspot.com/">Bradley Holt</a> </div> </body> </html>
  • 201 Created Request: POST /people HTTP/1.1 Content-Type: application/x-www-form-urlencoded fn=Bradley+Holt&url=http%3A%2F%2Fbradley-holt.blogspot.com%2F Response: HTTP/1.1 201 Created Location: /people/bradley-holt
  • 202 Accepted Request: POST /people HTTP/1.1 Content-Type: application/x-www-form-urlencoded fn=Bradley+Holt&url=http%3A%2F%2Fbradley-holt.blogspot.com%2F Response: HTTP/1.1 202 Accepted Location: /queue/4jn6rk
  • 301 Moved Permanently Request: GET /people/bradley-holt HTTP/1.1 Response: HTTP/1.1 301 Moved Permanently Location: /people/BradleyHolt
  • 302 Found Request: GET /people/bradley-holt HTTP/1.1 Response: HTTP/1.1 302 Found Location: /people/BradleyHolt
  • 400 Bad Request Request: POST /people HTTP/1.1 Content-Type: application/x-www-form-urlencoded fn=Bradley+Holt&url=bradley-holt.blogspot.com Response: HTTP/1.1 400 Bad Request Content-Type: text/html <!DOCTYPE html> <html> <head> <title>Bradley Holt</title> </head> <body> <p>URL is not valid.</p> </body> </html>
  • 401 Unauthorized Request: GET /people/bradley-holt HTTP/1.1 Response: HTTP/1.1 401 Unauthorized WWW-Authenticate: BASIC realm="Area 51"
  • 403 Forbidden Request: GET /people/bradley-holt HTTP/1.1 Response: HTTP/1.1 403 Forbidden
  • 404 Not Found Request: GET /people/bradley-holt HTTP/1.1 Response: HTTP/1.1 404 Not Found
  • 405 Method Not Allowed Request: POST /people/bradley-holt HTTP/1.1 Response: HTTP/1.1 405 Method Not Allowed Allow: GET, PUT, DELETE
  • 409 Con ict Request: PUT /people/bradley-holt HTTP/1.1 Content-Type: application/x-www-form-urlencoded fn=Bradley+Holt&url=http%3A%2F%www.foundline.com%2F&revision=5 Response: HTTP/1.1 409 Conflict Content-Type: text/html <!DOCTYPE html> <html> <head> <title>Conflict</title> </head> <body> <p>You are editing revision 5 and the latest revision number is 6.</p> </body> </html>
  • 418 I’m A Teapot According to the Hyper Text Coffee Pot Control Protocol (HTCPCP/1.0)[6]: Any attempt to brew coffee with a teapot should result in the error code "418 I'm a teapot". The resulting entity body MAY be short and stout. Clipped photo by revolution cycle / CC BY 2.0 http://www. ickr.com/photos/11795120@N06/3832234809/
  • 500 Internal Server Error Request: GET /people/bradley-holt HTTP/1.1 Response: HTTP/1.1 500 Internal Server Error Content-Type: text/html <!DOCTYPE html> <html> <head> <title>Internal Server Error</title> </head> <body> <p>Oops, someone broke the application.</p> </body> </html>
  • 503 Service Unavailable Request: GET /people/bradley-holt HTTP/1.1 Response: HTTP/1.1 503 Service Unavailable Retry-After: 120 Content-Type: text/html <!DOCTYPE html> <html> <head> <title>Service Unavailable</title> </head> <body> <p>Try again in two minutes.</p> </body> </html>
  • Uniform Interface Loose coupling and self-described messages versus ne-grained functionality.
  • Uniform Interface • URI identi es the resource. • HTTP method says how we're manipulating the resource. • Entity-header elds and entity-body[7] represent the resource. • Requests and responses are self-descriptive and stateless.
  • Hypermedia As The Engine Of Application State (HATEOAS) From Chapter 5 of the Fielding Dissertation[8]: In order to obtain a uniform interface, multiple architectural constraints are needed to guide the behavior of components. REST is de ned by four interface constraints: identi cation of resources; manipulation of resources through representations; self-descriptive messages; and, hypermedia as the engine of application state.
  • State of What? Wait, you just said requests and responses are "stateless" and now you're talking about application state?
  • Requests and Responses • Each request and each response is, itself stateless (forget about cookies for a minute). • All relevant state information is included in the request or response. • Not just state, but state transitions can be part of a request: POST, PUT and DELETE can change state on the server.
  • Hypermedia • XHTML • HTML 5 • microformats • RDFa • URI Templates • WADL • Atom
  • Follow the Hyperlinks • Link from current resource to another resource • Form to nd resources (i.e. a "search" form) • Form to manipulate resource's state (via POST, PUT or DELETE)
  • RESTful Implementations REST is just an architectural style. Implementations can vary in how RESTful they are.
  • AtomPub • An actual protocol that is RESTful • Uses XML document hypermedia formats to represent entities • Originally designed for publishing blogs • Used as the base for many RESTful web services including: • Google Data APIs (GData) • Amazon Simple Storage Service (Amazon S3) • Windows Azure Platform
  • GET Atom Service Document Request: GET / HTTP/1.1 Response: HTTP/1.1 200 OK Content-Type: application/atomsvc+xml <?xml version="1.0" encoding="utf-8"?> <service xmlns="http://www.w3.org/2007/app" xmlns:atom="http://www.w3.org/2005/Atom"> <workspace> <atom:title>Blog</atom:title> <collection href="http://example.org/blog"> <atom:title>Blog Entries</atom:title> </collection> <collection href="http://example.org/media"> <atom:title>Media</atom:title> <accept>image/png</accept> <accept>image/jpeg</accept> <accept>image/gif</accept> </collection> </workspace> </service> Note: Service Document tells us everything we need to know to get started (loose coupling). For example, it tells us the URIs of the collections to GET or POST to. URIs are up to the server to decide and should be opaque to the client.
  • GET Atom Collection Document Request: GET /blog HTTP/1.1 Response: HTTP/1.1 200 OK Content-Type: application/atom+xml <?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom"> <title>Blog Entries</title> <link rel="self" href="http://example.org/blog"/> <updated>2009-09-01T20:28:54Z</updated> <id>urn:uuid:17040b30-9737-11de-8a39-0800200c9a66</id> </feed>
  • POST Entry to Atom Collection Document Request: POST /blog HTTP/1.1 Slug: =?utf-8?q?blog-entry?= Content-Type: application/atom+xml <?xml version="1.0" encoding="utf-8"?> <entry xmlns="http://www.w3.org/2005/Atom"> <title>A Blog Entry</title> <updated>2009-09-01T20:32:06Z</updated> <summary>Summary of my blog entry...</summary> </entry> Response: HTTP/1.1 201 Created Location: /blog/blog-entry Content-Type: application/atom+xml <?xml version="1.0" encoding="utf-8"?> <entry xmlns="http://www.w3.org/2005/Atom"> <title>A Blog Entry</title> <link rel="edit" href="http://example.org/blog/blog-entry"/> <id>urn:uuid:337f26a0-9737-11de-8a39-0800200c9a66</id> <updated>2009-09-01T20:32:06Z</updated> <summary>Summary of my blog entry...</summary> </entry>
  • GET Atom Collection Document (again) Request: GET /blog HTTP/1.1 Response: HTTP/1.1 200 OK Content-Type: application/atom+xml <?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom"> <title>Blog Entries</title> <link rel="self" href="http://example.org/blog"/> <updated>2009-09-01T20:32:06Z</updated> <id>urn:uuid:17040b30-9737-11de-8a39-0800200c9a66</id> <entry> <title>A Blog Entry</title> <link rel="edit" href="http://example.org/blog/blog-entry"/> <id>urn:uuid:337f26a0-9737-11de-8a39-0800200c9a66</id> <updated>2009-09-01T20:32:06Z</updated> <summary>Summary of my blog entry...</summary> </entry> </feed>
  • GET Atom Entry Document Request: GET /blog/blog-entry HTTP/1.1 Response: HTTP/1.1 200 OK Content-Type: application/atom+xml <?xml version="1.0" encoding="utf-8"?> <entry xmlns="http://www.w3.org/2005/Atom"> <title>A Blog Entry</title> <link rel="edit" href="http://example.org/blog/blog-entry"/> <id>urn:uuid:337f26a0-9737-11de-8a39-0800200c9a66</id> <updated>2009-09-01T20:32:06Z</updated> <summary>Summary of my blog entry...</summary> </entry>
  • PUT Atom Entry Document Request: PUT /blog/blog-entry HTTP/1.1 Content-Type: application/atom+xml <?xml version="1.0" encoding="utf-8"?> <entry xmlns="http://www.w3.org/2005/Atom"> <title>A Blog Entry</title> <updated>2009-09-01T20:34:51Z</updated> <summary>Updated summary...</summary> </entry> Response: HTTP/1.1 200 OK Content-Type: application/atom+xml <?xml version="1.0" encoding="utf-8"?> <entry xmlns="http://www.w3.org/2005/Atom"> <title>A Blog Entry</title> <link rel="edit" href="http://example.org/blog/blog-entry"/> <id>urn:uuid:337f26a0-9737-11de-8a39-0800200c9a66</id> <updated>2009-09-01T20:34:51Z</updated> <summary>Updated summary...</summary> </entry>
  • POST Atom Media Entry Document Request: POST /media HTTP/1.1 Slug: =?utf-8?q?vacation-photo?= Content-Type: image/png ...binary data... Response: HTTP/1.1 201 Created Location: /media/vacation-photo.png Content-Type: application/atom+xml <?xml version="1.0" encoding="utf-8"?> <entry xmlns="http://www.w3.org/2005/Atom"> <title>vacation-photo</title> <link rel="edit-media" href="http://example.org/media/vacation-photo.png"/> <link rel="edit" href="http://example.org/media/vacation-photo"/> <id>urn:uuid:00518590-973f-11de-8a39-0800200c9a66</id> <updated>2009-09-01T20:37:18Z</updated> </entry> Note: • edit-media URI represents the actual media • edit URI represents the media entry (Atom Entry)
  • DELETE Atom Entry Document Request: DELETE /blog/blog-entry HTTP/1.1 Response: HTTP/1.1 200 OK
  • Google Calendar API The Google Calendar Data API allows client applications to view and update calendar events in the form of Google Data API feeds. Your client application can use the Calendar Data API to create new events, edit or delete existing events, and query for events that match particular criteria. From the Developer's Guide[9]
  • GET Calendar Feed Request: GET /calendar/feeds/default/owncalendars/full HTTP/1.1 Response (truncated): HTTP/1.1 200 OK Content-Type: application/atom+xml <feed xmlns='http://www.w3.org/2005/Atom' xmlns:gCal='http://schemas.google.com/gCal/2005'> <id>http://www.google.com/calendar/feeds/default/owncalendars/full</id> <updated>2009-09-01T22:16:48.105Z</updated> <title type='text'>bradley.holt@gmail.com's Calendar List</title> <entry> <id>http://www.google.com/calendar/feeds/default/owncalendars/full/bradley.holt %40gmail.com</id> <updated>2009-08-17T06:39:02.000Z</updated> <title type='text'>Bradley Holt (personal)</title> <link rel='http://schemas.google.com/gCal/2005#eventFeed' type='application/atom+xml' href='http://www.google.com/calendar/feeds/bradley.holt%40gmail.com/ private/full'/> <gCal:timezone value='America/New_York'/> </entry> </feed>
  • GET Event Feed Request: GET /calendar/feeds/bradley.holt%40gmail.com/private/full HTTP/1.1 Response (truncated): HTTP/1.1 200 OK Content-Type: application/atom+xml <feed xmlns='http://www.w3.org/2005/Atom' xmlns:batch='http://schemas.google.com/gdata/batch' xmlns:gCal='http://schemas.google.com/gCal/2005' xmlns:gd='http://schemas.google.com/g/2005'> <id>http://www.google.com/calendar/feeds/bradley.holt%40gmail.com/private/full</id> <updated>2009-08-17T06:39:02.000Z</updated> <title type='text'>Bradley Holt (personal)</title> <gCal:timezone value='America/New_York'/> <entry> <id>http://www.google.com/calendar/feeds/bradley.holt%40gmail.com/private/full/t8od3jn8qm65g1bjg78kokukug</id> <updated>2009-08-15T18:32:09.000Z</updated> <title type='text'>Biking</title> <link rel='self' type='application/atom+xml' href='http://www.google.com/calendar/feeds/bradley.holt%40gmail.com/private/full/t8od3jn8qm65g1bjg78kokukug'/> <gd:eventStatus value='http://schemas.google.com/g/2005#event.confirmed'/> <gd:when startTime='2009-08-15T10:00:00.000-04:00' endTime='2009-08-15T14:00:00.000-04:00'/> <gd:who rel='http://schemas.google.com/g/2005#event.attendee' valueString='bradley.holt@gmail.com' email='bradley.holt@gmail.com'/> <gd:where valueString=''/> </entry> </feed>
  • POST an Event to the Feed Request: POST /calendar/feeds/bradley.holt%40gmail.com/private/full HTTP/1.1 Content-Type: application/atom+xml <entry xmlns='http://www.w3.org/2005/Atom' xmlns:gd='http://schemas.google.com/g/2005'> <updated>2009-09-01T20:48:18.000Z</updated> <title type='text'>Haircut</title> <gd:eventStatus value='http://schemas.google.com/g/2005#event.confirmed'/> <gd:when startTime='2009-09-17T18:00:00.000-04:00' endTime='2009-09-17T18:30:00.000-04:00'/> <gd:who rel='http://schemas.google.com/g/2005#event.attendee' valueString='bradley.holt@gmail.com' email='bradley.holt@gmail.com'/> <gd:where valueString='Barber Shop'/> </entry> Response (truncated): HTTP/1.1 201 Created Content-Type: application/atom+xml <entry xmlns='http://www.w3.org/2005/Atom' xmlns:batch='http://schemas.google.com/gdata/batch' xmlns:gd='http://schemas.google.com/g/2005'> <id>http://www.google.com/calendar/feeds/bradley.holt%40gmail.com/private/full/xuid3dn5dm65gdbs78k5o3koe</id> <updated>2009-09-01T20:48:18.000Z</updated> <title type='text'>Haircut</title> <link rel='self' type='application/atom+xml' href='http://www.google.com/calendar/feeds/bradley.holt%40gmail.com/private/full/xuid3dn5dm65gdbs78k5o3koe'/> <gd:eventStatus value='http://schemas.google.com/g/2005#event.confirmed'/> <gd:eventStatus value='http://schemas.google.com/g/2005#event.confirmed'/> <gd:when startTime='2009-09-17T18:00:00.000-04:00' endTime='2009-09-17T18:30:00.000-04:00'/> <gd:who rel='http://schemas.google.com/g/2005#event.attendee' valueString='bradley.holt@gmail.com' email='bradley.holt@gmail.com'/> <gd:where valueString='Barber Shop'/> </entry>
  • Cache-Control • Allows client and server to control caching of resource. • An example of why URIs are important (cache only applies to a given URI). • Reduces latency. • Reduces network traffic. • Cached by: browser, proxy, gateway. Request: GET /people/bradley-holt HTTP/1.1 Cache-Control: max-age=1800 Response: HTTP/1.1 200 OK Content-Type: text/html Cache-Control: max-age=3600 ...HTML data...
  • Conditional GET • An entity tag (ETag) allows for a conditional GET. • An example of why URIs are important (conditional GET only applies to a given URI). • Reduces latency. • Reduces network traffic.
  • Conditional GET (continued) Request: GET /people/bradley-holt HTTP/1.1 Response: HTTP/1.1 200 OK Content-Type: text/html ETag: 6f6327696a7c8c6e7e ...HTML data... Request: GET /people/bradley-holt HTTP/1.1 If-None-Match: 6f6327696a7c8c6e7e Response: HTTP/1.1 304 Not Modified
  • Content Negotiation • Different representations of a resource served by the same URI. Request HTML: GET /people/bradley-holt HTTP/1.1 Accept: text/html Response: HTTP/1.1 200 OK Content-Type: text/html <!DOCTYPE html> <html> <head> <title>Bradley Holt</title> </head> <body> <div class="vcard"> <a class="url fn" href="http://bradley-holt.blogspot.com/">Bradley Holt</a> </div> </body> </html>
  • Content Negotiation (continued) Request Atom: GET /people/bradley-holt HTTP/1.1 Accept: application/atom+xml Response: HTTP/1.1 200 OK Content-Type: application/atom+xml <?xml version="1.0" encoding="utf-8"?> <entry xmlns="http://www.w3.org/2005/Atom"> <title>Bradley Holt</title> <link rel="edit" href="http://example.org/people/bradley-holt"/> <id>urn:uuid:e92dbee0-9d99-11de-8a39-0800200c9a66</id> <updated>2009-09-09T15:39:08Z</updated> <summary>Bradley Holt</summary> </entry>
  • Acceptance • Accept-Charset • utf-8 • iso-8859-1 • Accept-Encoding • compress • gzip • 406 Not Acceptable
  • REST is Not... • XML/JSON over HTTP • Flickr API • Twitter API • If it says, "REST API" there's a good chance it isn't RESTful.
  • Trade-Offs • No ne-grained function/method calling: course-grained representation exchange instead. • Uniform interface and loose coupling over efficiency.
  • Credits Author: Bradley Holt Technical Review: Josh Sled Layout & Design: Jason Pelletier Photo: Revolution Cycle, Solar Powered Tea Pot, http://www. ickr.com/photos/11795120@N06/3832234809/ This presentation licensed under Creative Commons -- Attribution 3.0 United States License. [1]: Hypertext Transfer Protocol. (2009, August 25). In Wikipedia, The Free Encyclopedia. Retrieved August 25, 2009, from http://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol [2]: HTTP/1.1: Method De nitions. (n.d.). Retrieved August 26, 2009, from World Wide Web Consortium - Web Standards: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html [3]: Richardson, L., & Ruby, S. (2007). RESTful Web Services. Sebastopol, CA: O’Reilly Media, Inc. [4]: Google Web Accelerator: Hey, not so fast - an alert for web app designers. (2005, May 6). Retrieved September 8, 2009, from Signal vs. Noise: http://37signals.com/svn/archives2/google_web_accelerator_hey_not_so_fast_an_alert_for_web_app_designers.php [5]: HTTP/1.1: Status Code De nitions. (n.d.). Retrieved August 27, 2009, from World Wide Web Consortium - Web Standards: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html [6]: Hyper Text Coffee Pot Control Protocol (HTCPCP/1.0) (1998, April 1). Retrieved August 31, 2009, from The Internet Engineering Task Force (IETF): http://www.ietf.org/rfc/rfc2324.txt [7]: HTTP/1.1: Entity (n.d.). Retrieved September 1, 2009, from World Wide Web Consortium - Web Standards: http://www.w3.org/ Protocols/rfc2616/rfc2616-sec7.html [8]: Fielding Dissertation: CHAPTER 5: Representational State Transfer (REST) (2000). Retrieved September 1, 2009, from Architectural Styles and the Design of Network-based Software Architectures: http://www.ics.uci.edu/~ elding/pubs/dissertation/ rest_arch_style.htm#sec_5_1_5 [9]: Developer's Guide - Google Calendar APIs and Tools (n.d.). Retrieved September 1, 2009, from Google Data APIs: http://code.google.com/apis/calendar/docs/2.0/developers_guide.html