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