RESTful API 设计

来源:互联网 发布:教育收费软件 编辑:程序博客网 时间:2024/05/23 14:58

Use RESTful URLs and actions

Verbs

  • GET (SELECT): Retrieve a specific Resource from the Server, or a listing of Resources.
  • POST (CREATE): Create a new Resource on the Server.
  • PUT (UPDATE): Update a Resource on the Server, providing the entire Resource.
  • PATCH (UPDATE): Update a Resource on the Server, providing only changed attributes.
  • DELETE (DELETE): Remove a Resource from the Server.
  • HEAD – Retrieve meta data about a Resource, such as a hash of the data or when it was last updated.
  • OPTIONS – Retrieve information about what the Consumer is allowed to do with the Resource.

But what can I make a resource?

  • GET /tickets - Retrieves a list of tickets
  • GET /tickets/12 - Retrieves a specific ticket
  • POST /tickets - Creates a new ticket
  • PUT /tickets/12 - Updates ticket #12
  • PATCH /tickets/12 - Partially updates ticket #12
  • DELETE /tickets/12 - Deletes ticket #12

Use the plural form

In my opinion, it is not a really good idea to mix singular and plural forms in a single resource naming.

Use /artists instead of /artist, even for the show/delete/update actions

But how do you deal with relations?

  • GET /tickets/12/messages - Retrieves list of messages for ticket #12
  • GET /tickets/12/messages/5 - Retrieves message #5 for ticket #12
  • POST /tickets/12/messages - Creates a new message in ticket #12
  • PUT /tickets/12/messages/5 - Updates message #5 for ticket #12
  • PATCH /tickets/12/messages/5 - Partially updates message #5 for ticket #12
  • DELETE /tickets/12/messages/5 - Deletes message #5 for ticket #12

What about actions that don’t fit into the world of CRUD operations?

This is where things can get fuzzy. There are a number of approaches:

  • Restructure the action to appear like a field of a resource. This works if the action doesn’t take parameters. For example an activate action could be mapped to a boolean activated field and updated via a PATCH to the resource.
  • Treat it like a sub-resource with RESTful principles. For example, GitHub’s API lets you star a gist with PUT /gists/:id/star and unstar with DELETE /gists/:id/star.
  • Sometimes you really have no way to map the action to a sensible RESTful structure. For example, a multi-resource search doesn’t really make sense to be applied to a specific resource’s endpoint. In this case, /search would make the most sense even though it isn’t a resource. This is OK - just do what’s right from the perspective of the API consumer and make sure it’s documented clearly to avoid confusion.

Versioning

One way is to do is to pass the API version in the URL (/api/v1/...).

One other neat trick is to use the Accept HTTP header to pass the verison desired. Github does that.

API Root URL

Here are two common URL Roots:
- https://example.org/api/v1/*
- https://api.example.com/v1/*

Result filtering, sorting & searching

Filtering

Use a unique query parameter for each field that implements filtering.

  • GET /tickets?state=open

Sorting

Similar to filtering, a generic parameter sort can be used to describe sorting rules.
- GET /tickets?sort=-priority - Retrieves a list of tickets in descending order of priority
- GET /tickets?sort=-priority,created_at - Retrieves a list of tickets in descending order of priority. Within a specific priority, older tickets are ordered first

Searching

When full text search is used as a mechanism of retrieving resource instances for a specific type of resource, it can be exposed on the API as a query parameter on the resource’s endpoint. Let’s say q.

Combining these together, we can build queries like:
- GET /tickets?sort=-updated_at - Retrieve recently updated tickets
- GET /tickets?state=closed&sort=-updated_at - Retrieve recently closed tickets
- GET /tickets?q=return&state=open&sort=-priority,created_at - Retrieve the highest priority open tickets mentioning the word ‘return’

Paging

The right way to include pagination details today is using the Link header introduced by RFC 5988.

Here is an example of a Link header used properly, grabbed from GitHub’s documentation:

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"

Graph API - Paging:https://www.tonylin.idv.tw/dokuwiki/doku.php/facebook:advance:graphapi:paging

Don’t use an envelope by default, but make it possible when needed

There are 2 situations where an envelope is really needed - if the API needs to support cross domain requests(CORS) over JSONP or if the client is incapable of working with HTTP headers.

Similarly, to support limited HTTP clients, allow for a special query parameter ?envelope=true that would trigger full enveloping (without the JSONP callback function).

Auto loading related resource representations

There are many cases where an API consumer needs to load data related to (or referenced) from the resource being requested. Rather than requiring the consumer to hit the API repeatedly for this information, there would be a significant efficiency gain from allowing related data to be returned and loaded alongside the original resource on demand.

However, as this does go against some RESTful principles, we can minimize our deviation by only doing so based on an embed (or expand) query parameter.

In this case, embed would be a comma separated list of fields to be embedded. Dot-notation could be used to refer to sub-fields. For example:

GET /tickets/12?embed=customer.name,assigned_user

This would return a ticket with additional details embedded, like:

{  "id" : 12,  "subject" : "I have a question!",  "summary" : "Hi, ....",  "customer" : {    "name" : "Bob"  },  "assigned_user": {   "id" : 42,   "name" : "Jim",  }}

Of course, ability to implement something like this really depends on internal complexity. This kind of embedding can easily result in an N+1 select issue.

Rate limiting

To prevent abuse, it is standard practice to add some sort of rate limiting to an API. RFC 6585 introduced a HTTP status code 429 Too Many Requests to accommodate this.

However, it can be very useful to notify the consumer of their limits before they actually hit it. This is an area that currently lacks standards but has a number of popular conventions using HTTP response headers.

At a minimum, include the following headers (using Twitter’s naming conventions as headers typically don’t have mid-word capitalization):

  • X-Rate-Limit-Limit - The number of allowed requests in the current period
  • X-Rate-Limit-Remaining - The number of remaining requests in the current period
  • X-Rate-Limit-Reset - The number of seconds left in the current period

Caching

There are 2 approaches: ETag and Last-Modified

ETag: When generating a request, include a HTTP header ETag containing a hash or checksum of the representation. This value should change whenever the output representation changes. Now, if an inbound HTTP requests contains a If-None-Match header with a matching ETag value, the API should return a 304 Not Modified status code instead of the output representation of the resource.

Last-Modified: This basically works like to ETag, except that it uses timestamps. The response header Last-Modified contains a timestamp in RFC 1123 format which is validated against If-Modified-Since. Note that the HTTP spec has had 3 different acceptable date formats and the server should be prepared to accept any one of them.

Status Codes

Success codes

  • 201 Created should be used when creating content (INSERT),
  • 202 Accepted should be used when a request is queued for background processing (async tasks),
  • 204 No Content should be used when the request was properly executed but no content was returned (a good example would be when you delete something).
  • 304 Not Modified - Used when HTTP caching headers are in play

Client error codes

  • 400 Bad Request should be used when there was an error while processing the request payload (malformed JSON, for instance).
  • 401 Unauthorized should be used when a request is not authenticiated (wrong access token, or username or password).
  • 403 Forbidden should be used when the request is successfully authenticiated (see 401), but the action was forbidden.
  • 404 Not Found - When a non-existent resource is requested
  • 405 Method Not Allowed - When an HTTP method is being requested that isn’t allowed for the authenticated user
  • 406 Not Acceptable should be used when the requested format is not available (for instance, when requesting an XML resource from a JSON only server).
  • 410 Gone Should be returned when the requested resource is permenantely deleted and will never be available again.
  • 415 Unsupported Media Type - If incorrect content type was provided as part of the request
  • 422 Unprocessable Entity - Used for validation errors
  • 429 Too Many Requests - When a request is rejected due to rate limiting

A more complete list of status codes can be found in RFC2616.

Status Code Ranges

The 1xx range is reserved for low-level HTTP stuff, and you’ll very likely go your entire career without manually sending one of these status codes.

The 2xx range is reserved for successful messages where all goes as planned. Do your best to ensure your Server sends as many of these to the Consumer as possible.

The 3xx range is reserved for traffic redirection. Most APIs do not use these requests much (not nearly as often as the SEO folks use them ;), however, the newer Hypermedia style APIs will make more use of these.

The 4xx range is reserved for responding to errors made by the Consumer, e.g. they’re providing bad data or asking for things which don’t exist. These requests should be be idempotent, and not change the state of the server.

The 5xx range is reserved as a response when the Server makes a mistake. Often times, these errors are thrown by low-level functions even outside of the developers hands, to ensure a Consumer gets some sort of response. The Consumer can’t possibly know the state of the server when a 5xx response is received, and so these should be avoidable.

Errors

JSON output representation for something like this would look like:

{  "code" : 1234,  "message" : "Something bad happened :(",  "description" : "More details about the error here"}

Validation errors for PUT, PATCH and POST requests will need a field breakdown. This is best modeled by using a fixed top-level error code for validation failures and providing the detailed errors in an additional errors field, like so:

{  "code" : 1024,  "message" : "Validation Failed",  "errors" : [    {      "code" : 5432,      "field" : "first_name",      "message" : "First name cannot have fancy characters"    },    {       "code" : 5622,       "field" : "password",       "message" : "Password cannot be blank"    }  ]}

Authentication

Always use SSL. No exceptions. OAuth 2 uses Bearer tokens & also depends on SSL for its underlying transport encryption.

Refrernce

  • Principles of good RESTful API Design
  • REST best practices
  • Best Practices for Designing a Pragmatic RESTful API
0 0
原创粉丝点击