Leveraging REST API to build better clients and servers

Fabio Pitino
fabio_pitino@symantec.com

REST Constraints
REpresentational State Transfer

  • Client-server
  • Stateless
  • Cacheable
  • Layered architecture
  • Code on-demand (optional)
  • Uniform interface
    • Identification of resources
    • Manipulation of resources through representation
    • Self-descriptive messages
    • HATEOAS

Resources

# Resource
/tickets/1234

# Collection of resources
/tickets

# Nested resources / Associations
/tickets/1234/files
POST /createTicket




# create a resource
POST /tickets + { "data": "value" }

# submit a search with long parameters
POST /search/tickets + { "data": "value" }

POST

GET /getAllTickets
GET /getTicket/1234



# get a collection of tickets
GET /tickets

# get a ticket by id
GET /tickets/1234

# get comments for a ticket (nested resource)
GET /tickets/1234/comments

GET

POST /updateTicket
PUT /escalateTicket




# replace comment
PUT /comments/1234 + { "text": "I changed my mind" }

# update ticket
PATCH /tickets/1234 + { "summary": "A new summary" }

PUT/PATCH

POST /deleteTicket
PUT /cancelOrder




# delete ticket
DELETE /tickets/1234

# cancel order
DELETE /order/1234

# deactivate user
DELETE /users/1234

DELETE

# Filters
GET /tickets?status=done&assignee_id=1897


# Sorting
GET /tickets?sort=priority_asc


# Text search
GET /tickets?q=Watchdog


# Pagination
GET /tickets?page=2&per_page=10


# Also possible to combine them
GET /tickets?status=done&sort=priority_asc&page=2

Collections

HTTP/1.1

...a Web Oriented
Programming Language

STATUS CODES

Extra semantics to

Client-Server communication

# Standard success response across all methods
Status: 200 OK


# When a new resource is created.
Status: 201 Created
Location: /tickets/1234


# When requesting long running jobs
Status: 202 Accepted 


# When the response has no content
# E.g DELETE /tickets/1234
Status: 204 No Content

Status Codes - Success

# Server did not understand the request. E.g: missing or 
# invalid parameters
Status: 400 Bad Request


# Credentials are required to perform this action
Status: 401 Unauthorized


# Although the user is authenticated it does not have 
# permissions to perform this action
Status: 403 Forbidden

Status Codes - Client ErrorĀ 

# The resource is not available
Status: 404 Not Found


# If the server relies on 3rd party services to perform 
# the action
Status: 408 Request Timeout


# The resource is no longer available. E.g: this version 
# of API is no longer supported or the resource is not 
# available due to retention period
Status: 410 Gone

Status Codes - Client Error

# The request format was valid but something unexpected 
# happened on the server
Status: 500 Server Error


# Server is temporary offline. Usually under maintenance
Status: 503 Service Unavailable

Status Codes - Server Error

# some of the most common ones

Accept: application/json
Accept: application/vnd.github.v3+json

Authorization: Token token=QWxhZGRpbjpvcGVuIHNlc2FtZQ

Content-Type: application/json

User-Agent: MyClientLibrary/2.3

Request Headers

# for redirection or when a resource is created
Location: https://example.com/tickets/1234

# Server name
Server: Apache/2.4.1 (Unix)

# Pagination via headers
Link: <https://api.github.com/user/repos?page=3&per_page=100>; rel="next", 
      <https://api.github.com/user/repos?page=50&per_page=100>; rel="last"

# metadata using custom headers
Total-Count: 245

# send warning messages without altering the response
Warning: 199 - "This is a warning you may want to report"

Response Headers

HATEOAS

Hypermedia As The Engine Of Application State

HATEOAS

# GET /tickets/123

{
  "type": "Ticket",
  "id": 123,
  "url": "https://example.com/tickets/123",
  "summary": "A ticket for HATEOAS",
  "reporter": {
    "type": "User",
    "id": 5678,
    "url": "https://example.com/users/5678",
    "login": "fabio_pitino",
    "email": "fabio_pitino@example.com"
  },
  "tags_url": "https://example.com/tickets/123/tags",
  "comments_url": "https://example.com/tickets/123/comments",
  "actions": [
    "close_url": "https://example.com/tickets/123/close",
    "block_url": "https://example.com/tickets/123/block",
    "assign_url": "https://example.com/tickets/123/assign"
  ]
}
GET /repos/:owner/:repo/projects

# ******* headers *******
# Status: 200 OK
# Link: <https://api.github.com/resource?page=2>; rel="next",
#       <https://api.github.com/resource?page=5>; rel="last"

[
  {
    "owner_url": "https://api.github.com/repos/api-playground/projects-test",
    "url": "https://api.github.com/projects/1002604",
    "html_url": "https://github.com/api-playground/projects-test/projects/12",
    "columns_url": "https://api.github.com/projects/1002604/columns",
    "id": 1002604,
    "name": "Projects Documentation",
    "body": "Developer documentation project for the developer site.",
    "number": 1,
    "state": "open",
    "creator": {
      "login": "octocat",
      "id": 1,
      "avatar_url": "https://github.com/images/error/octocat_happy.gif",
      "gravatar_id": "",
      "url": "https://api.github.com/users/octocat",
      "html_url": "https://github.com/octocat",
      "followers_url": "https://api.github.com/users/octocat/followers",
      "following_url": "https://api.github.com/users/octocat/following{/other_user}",
      "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
      "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
      "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
      "organizations_url": "https://api.github.com/users/octocat/orgs",
      "repos_url": "https://api.github.com/users/octocat/repos",
      "events_url": "https://api.github.com/users/octocat/events{/privacy}",
      "received_events_url": "https://api.github.com/users/octocat/received_events",
      "type": "User",
      "site_admin": false
    },
    "created_at": "2011-04-10T20:09:31Z",
    "updated_at": "2014-03-03T18:58:10Z"
  }
]

Response body

github.com

# Status: 400 Bad Request
{
  "errors": [
    "Ticket summary was too short. Minimum 10 characters",
    "Ticket priority must be between 1 and 4"
  ]
}


# *** VERBOSE VERSION ***
# Status: 400 Bad Request 
{
  "errors": [
    {
      "code":   "123", 
      "source": { "pointer": "/data/attributes/summary" },
      "title":  "Value is too short",
      "detail": "Ticket summary must contain at least 10 characters"
    },
    {
      "code":   "225",
      "source": { "pointer": "/data/attributes/priority" },
      "title": "Value not in range",
      "detail": "Ticket priority must be between 1 and 4"
    }
  ]
}

Error Response body

# HTTP Basic
> GET https://watchdog.symantec.com/api/tickets/1234
# Headers:
# Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l
# >> base64( <username>:<password> )

# HTTP Basic (deprecated)
GET https://Aladdin:OpenSesame@watchdog.symantec.com/api/tickets/1234

# --------------------

# Bearer (token) Authentication
> POST https://watchdog.symantec.com/api/login 
> data >> { username: 'aladdin', password: 'opensesame' }

< 200 OK
< { 
<   "token": "QWxhZGRpbjpPcGVuU2VzYW1lQWxhZGRpbjpPcGVuU2VzYW1l", 
<   "expires_at": "2019-02-23 00:00:00" 
< }

> GET https://watchdog.symantec.com/api/tickets/1234
# Headers:
# Authorization: Bearer QWxhZGRpbjpPcGVuU2VzYW1lQWxhZGRpbjpPcGVuU2VzYW1l

Authentication

# variant of Token Authentication

# Generate an application token via a frontend service
# use it in the URI
> GET https://watchdog.symantec.com/api/tickets/1234?API_TOKEN=QWxhZGRpbjpPcG...

# OAuth
# Open standard for secure delegated access to server resources on 
# behalf of the resource owner
https://oauth.net/2/

Authentication

# simple versioning in the URI
POST https://example.com/api/v1/tickets


# in the Accept request header
Accept: application/vnd.github.v3+json


# in a custom request header
api-version: 2.4


# in the URI as date
POST https://example.com/api/2017-02-28/tickets

Versioning

Scalability through CACHING

> GET /tickets/123
< 200 OK
< Date: Mon, 29 May 2017 15:30:00 GMT
< Last-Modified: Sat, 27 May 2017 12:00:00 GMT
< ... response body ...

> GET /tickets/123
> If-Modified-Since: Mon, 29 May 2017 15:30:00 GMT
< 304 Not Modified

# ************************************************

> GET /tickets/123
< 200 OK
< Date: Mon, 29 May 2017 15:30:00 GMT
< Cache-control: max-age=120
< ... response body ...

> GET /tickets/123
> If-Modified-Since: Mon, 29 May 2017 15:32:00 GMT
< 304 Not Modified

Caching - Time based

> GET /tickets/123
< 200 OK
< ETag: "xf8ab201e123a3bc"
< ... data here ...

> GET /tickets/123
> If-None-Match: "xf8ab201e123a3bc"
< 304 Not Modified

# **********************************

> PATCH /tickets/123
> If-Match: "xf8ab201e123a3bc"
< 412 Precondition Failed

Caching - Etag based

No API is usable without
DOCUMENTATION

Documentation

  • Overview and general behaviour
    • JSON schema (data, meta, links)
    • Dealing with collections (filters, sort, pagination)
    • Common errors and their structure
    • Versioning + current version
    • Rate limit (if any)
    • Timestamps and timezones
  • Authentication
  • Detailed list of available calls
    • request and response body + headers
    • possible errors
  • Available caching strategies
  • Pre-release program
    • dev / test environments
    • reviews
  • Recommendations
    • User agent
    • Warning header
    • ...the more you put in the better

Thank you

(questions?)

fabio_pitino@symantec.com

Made with Slides.com