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

Leveraging REST API to build better clients and servers

By Fabio Pitino

Leveraging REST API to build better clients and servers

Leveraging REST API to build better clients and servers

  • 523