Background
The internet is awash with articles about restful APIs (for convenience, the "RESTful API" shorthand for "API" below), but there is no "universal" design standard: How to do authentication? What is the API format? Should your API include version information? When you start to write an app, especially when the backend model is finished, you have to design and implement the public API part of your app. Because once published, the API will be difficult to change.
When designing the API for SUPPORTEDFU, I tried to solve the problems mentioned above with a practical perspective. I want to design an API that is easy to use, easy to deploy, and flexible enough for this article.
Basic Requirements for API design
Many of the ideas on the web about API design are "college", they may be more theoretical, but sometimes derail the real world (so I'm a liberal). So my goal in this article is to start from a practical point of view, give the current Web application of API design best practices (of course, I think the best ~), if it is not appropriate, I will not comply with the standard. Of course, as the basis of design, a few of the principles must be adhered to:
- Comply with standards when the standards are reasonable.
- The API should be programmer-friendly and easy to enter in the browser's address bar.
- The API should be simple, intuitive, easy to use and elegant at the same time.
- The API should be flexible enough to support the upper UI.
- The API design weighs several of these principles.
It is important to emphasize that the API is the programmer's UI, and like any other UI, you must carefully consider its user experience!
use restful URLs and action.
Although I said before I did not have a universal API design standard. But there is one that is universally recognized and adhered to: restfu design principles. It was presented by Roy Felding (fifth chapter in his "Web-based Software Architecture" paper). The core principle of rest is to split your API into logical resources. These resources are manipulated via HTTP (GET, post,put,delete).
So how do I split these resources?
Obviously from an API user's point of view, "resources" should be a noun. Even if your internal data model and resources already have a good correspondence, API design you still do not need to expose them to one-on. The key here is to hide the internal resources and expose the necessary external resources.
In Supportfu, resources are ticket, user, group.
Once you have defined the resources you want to expose, you can define the actions that are allowed on the resource, and the corresponding relationships between those actions and your API:
- Get/tickets # Get Ticket list
- GET/TICKETS/12 # View a specific ticket
- Post/tickets # Create a new ticket
- PUT/TICKETS/12 # Update Ticket 12.
- DELETE/TICKETS/12 #删除ticekt 12
As you can see, the benefit of using rest is that you can take advantage of the power of HTTP to implement the curd functionality of your resources. And here you just need a endpoint:/tickets, no other naming rules and URL rules, cool!
The singular plural of this endpoint
One rule to follow is that although it seems awkward to use complex numbers to describe a resource instance, unifying all the endpoint and using the plural makes your URL more structured. This makes it easier for API users to understand and easier for developers to implement.
How do I handle associations? There is also a description of how to handle the management rest principle between resources:
- 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
Where, if this association and resource are independent, we can save the endpoint of the corresponding resource in the output representation of the resource. The user of the API can then find the relevant resources by clicking on the link. If the association and resources are closely linked. The output of the resource indicates that the corresponding resource information should be saved directly. (for example, if the message resource exists independently, the get/tickets/12/messages on the above will return a link to the corresponding message; If the message does not exist independently, he and ticket are attached, The above API call returns a direct return message)
Operation not conforming to curd
For this puzzling question, here are some workarounds:
- Refactor your action action. When your behavior does not require parameters, you can match the active to the activated resource, (update using patch).
- Treated with sub-resources. For example: On GitHub, on a gists add star operation: Put/gists/:id/star and cancels star operation: Delete/gists/:id/star.
- Sometimes the action is not difficult to match with a resource such as search. Then let's do it. I think the user of the API will not be too much of a/search for this URL (he is easy to understand, after all). Just note that you can write clearly in the document.
Always use SSL
SSL is always used, without exception. Your app doesn't know who to be, and what the situation is. Some are safe, some are not. Using SSL can reduce the cost of authentication: You only need a simple token to be able to be authenticated, rather than having the user sign each request every time.
It is important to note that non-SSL URL access is not redirected to the SSL URL.
Document
Documentation is as important as the API itself. Documents should be easy to find and open (hiding them in a PDF or saving them in a place where they need to be logged in is not very good). The document should have examples of display requests and outputs: either by clicking the link or by using curl (see OpenStack documentation). If there is an update (especially a public API), the document should be updated in a timely manner. The documentation should have a schedule and details about when to discard an API. It's a good way to use mailing lists or blog entries.
Version of
Adding version information to the API effectively prevents users from accessing the updated API, while also allowing for a smooth transition between different major versions. There is a controversy over whether to put the version information into the URL or the request header: API version should be included in the URL or in a header. Academia says it should be put in the header, but if you put it in the URL we can access the resources across versions. (Refer to OpenStack).
The method used by strip is good: It has the main version information in its URL, and the request of the first two sides has the child version information. This makes the URL stable during the change of the child version. Change is sometimes unavoidable, and the key is how to manage change. Complete documentation and a reasonable schedule make it easier for API users to use.
Results filter, sort, search:
URLs are best as short as possible, and results are filtered, sorted, and search-related functions should be implemented via parameters (and also easily implemented).
filtering: provides uniform parameters for all interfaces that provide filtering functionality. For example: you want to limit the return of Get/tickets: return only those open states where the Ticket–get/tickektsstate=open state is the filter parameter.
sort: As with filtering, a good sort parameter should be able to describe the collation rather than the business-related. Complex collations should be implemented by combining:
- Get/ticketssort=-priority-retrieves a list of tickets in descending order of priority
- Get/ticketssort=-priority,created_at-retrieves a list of tickets in descending order of priority. Within a specific priority, older tickets is ordered first
In the second query here, the collation has more than one rule that is combined with a comma interval.
Search: Sometimes a simple sort is not enough. We can use search technology (Elasticsearch and Lucene) to implement (still as a parameter to the URL).
- Get/ticketsq=return&state=open&sort=-priority,created_at-retrieve the highest priority open tickets Mentioning the word ' return '
For frequently used search queries, we can create aliases for them, which will make the API more elegant. For example: get/ticketsq=recently_closed-get/tickets/recently_closed.
Domain that restricts API return values
Sometimes the API consumer does not need all the results, and it should be possible to limit the vertical when the horizontal limit is placed (for example, the top ten of the value return API results). And this function can effectively improve the network bandwidth usage and speed. You can use the fields query parameter to limit the domain returned, for example: get/ticketsfields=id,subject,customer_name,updated_at&state=open&sort=-updated _at
Update and create operations should return resources
PUT, POST, PATCH operations often have side effects when working with resources: such as Created_at,updated_at timestamps. In order to prevent the user from multiple API calls (for this update operation), we should return the updated resource (updated representation.) For example: After the post operation, return the 201 created status code, and contains a URL that points to the new resource as the return header
Do you need "HATEOAS"
Online about whether to allow users to create a new URL has a big objection (note is not to create a resource-generated URL). For this rest the Hateoas is developed to describe the interaction with endpoint, and the behavior should be defined in the metadata return value of the resource.
The author here thinks that Hateoas is not mature, I do not understand this paragraph even, readers interested can go to the original text to view)
JSON is provided as return format only
Now we'll start comparing XML and JSON. XML is lengthy, difficult to read, and not suitable for various programming language parsing. XML, of course, has the advantage of extensibility, but if you just serialize it to internal resources, then his extended advantage won't work. Many applications (Youtube,twitter,box) have started to abandon XML, and I don't want to waste my breath. Give the trend map on Google:
Of course, if you use the user inside the majority of enterprise users, then you may need to support XML. If that's the case, you have another question: is the media type in your HTTP request to be synchronized with the Accept header or the URL? For convenience (browser explorability), it should be in the URL (users just have to spell the URL themselves). The best way to do this is to use the. xml or. json suffix.
How to name it?
is the Serpentine command (underline and lowercase) or hump named? If you use JSON then the best thing to do is to follow the JavaScript naming method-that is, the camel name method. If you are writing a library in multiple languages, it is best to follow the recommendations of those languages, java,c# use camels, python,ruby use snake.
Personal opinion: I always feel that the snake command better make some, of course, there is no theoretical basis for this. Some people say that snake name read faster, can reach 20%, do not know true and false http://ieeexplore.ieee.org/xpl/articleDetails.jsptp=&arnumber=5521745
Use pretty print format by default, using gzip
Just use the return results of the spaces from the browser always feel very disgusting (a big lump there?) ~). Of course you can provide the parameters on the URL to control the use of "pretty print", but it is more friendly to turn on this option by default. The loss on the extra transmission will not be too great. Conversely if you forget to use gzip then the transfer efficiency will be greatly reduced and the loss greatly increased. Imagine a user is debugging so the default output is readable--without having to copy the results to any other software in the format--it's cool to think about it, isn't it?
Here is an example:
$ Curl Https://API.github.com/users/veesahni > with-whitespace.txt$ ruby-r json-e ' puts JSON json.parse (stdin.read) ' < With-whitespace.txt > without-whitespace.txt$ gzip-c with-whitespace.txt > with-whitespace.txt.gz$ gzip-c wi Thout-whitespace.txt > without-whitespace.txt.gz
The output is as follows:
- without-whitespace.txt-1252 bytes
- with-whitespace.txt-1369 bytes
- without-whitespace.txt.gz-496 bytes
- with-whitespace.txt.gz-509 bytes
In the example above, the extra space makes the result more than 8.5% (gzip not used), instead only 2.6% more. It is said that Twitter has reduced 80% (Link:https://dev.twitter.com/blog/announcing-gzip-compression-streaming-apis) by using Gzip after its streaming API transfer.
Use "envelope" only when needed
Many APIs return results like this:
123456 |
{ "data" : { "id" : 123, "name" : "John" } } |
The reason is simple: this can easily extend the return result, you can add some pagination information, some meta-information of the data, etc.-this is useful for API users who are not easily accessible to the return header, but with the development of "standard" (Cors and http://tools.ietf.org/ Html/rfc5988#page-6 are beginning to be added to the standard, I personally recommend not doing that.
When to use envelope?
There are two cases in which the
should use envelope. If the API consumer does not have access to the return header, or the API needs to support cross-domain requests (via JSONP). The JSONP request contains a callback function parameter in the requested URL. If this parameter is given, then the API should return 200 and put the real state into the return value (wrapped in an envelope), for example:
1234567 |
callback_function({ status_code: 200, next_page: "https://.." , response: { ... actual JSON response body ... } }) |
Similarly, to support API users who cannot return headers in a method, you can allow parameters such as envelope=true.
Using JSON as input on Post,put,patch
If you agree with what I said above, then you should decide to use JSON as the output format for all the APIs, then we'll consider the input data format of the API. Many APIs use URL-encoded formats: Just like the format of URL query parameters: Simple key-value pairs. This method is simple and effective, but it has its own problem: it has no concept of data type. This allows the program to parse out the boolean and integers based on the string, and there is no hierarchy – although there are some conventions about hierarchy information that are not very useful compared to the JSON itself that supports hierarchies.
Of course, if the API itself is simple, then using the URL format input is fine. But for complex APIs, you should use JSON. Or simply use JSON uniformly. Note When using JSON transfer, the request header is required to include: Content-type:application/json., otherwise throws a 415 exception (Unsupported media Type).
Page out
Paging data can be put into "envelopes", but with the improvement of standards, I now recommend putting paging information inside the link header: http://tools.ietf.org/html/rfc5988#page-6.
The API that uses the link header should return a series of well-assembled URLs instead of having the user spell it out for themselves. This is especially important in cursor-based paging. For example, a document from GitHub
Automatic loading of related resources
Many times, it is very useful to automatically load related resources, which can greatly improve the efficiency. But this is contrary to the principle of restful. To do so, we can add parameters to the URL: embed (or expend). Embed can be a comma-separated string, for example:
1 |
GET /ticket/12embed=customer.name,assigned_user |
The corresponding API return values are as follows:
123456789101112 |
{
"id" : 12,
"subject" :
"I have a question!"
,
"summary" :
"Hi, ...."
,
"customer" : {
"name" :
"Bob"
},
assigned_user: {
"id" : 42,
"name" :
"Jim"
,
}
}
|
It is worth reminding that this feature can sometimes be complex and can lead to n+1 SELECT issues.
Overriding the HTTP method
Some clients can only issue simple get and post requests. In order to take care of them, we can rewrite the HTTP request. There is no standard here, but a common way is to accept the X-http-method-override request header.
Speed limit
To avoid flooding requests, it is important to set speed limits on the API. The HTTP status Code 429 (too many requests) was introduced for this RFC 6585. After adding the speed setting, the user should be prompted, as to how the standard is not indicated, but the popular method is to use the HTTP return header.
Here are a few of the required return headers (according to Twitter's naming conventions):
- X-rate-limit-limit: Number of concurrent requests allowed for the current time period
- X-rate-limit-remaining: The number of requests reserved for the current time period.
- X-rate-limit-reset: Number of seconds remaining in the current time period
Why use the remaining seconds of the current time period instead of the timestamp?
Timestamps save a lot of information, but also contains a lot of unnecessary information, the user only need to know that there are only a few seconds left to send the request again this also avoids the clock skew problem.
Some APIs use the UNIX format timestamp, and I recommend not doing that. Why? HTTP has been specified using the RFC 1123 time format
Authentication Authentication
The RESTful API is stateless, which means that the user's request for authentication is irrelevant to the cookie and the session, and each request should contain a verification certificate.
By using SSL we can not provide the user name and password every time: We can return a randomly generated token to the user. This makes it extremely convenient for users who use the browser to access the API. This method is applicable to the user can first pass the user name-password authentication and get token, and can copy the returned token to a later request. If this is not convenient, you can use OAuth to secure token transfer.
The API that supports JSONP requires an additional authentication method because the JSONP request cannot send a normal credential. In this case, you can add parameters to the query URL: Access_token. Note the problem with URL parameters is that most Web servers now have the query parameter saved to the server log, which can be a major security risk.
Note that there are only three ways to transfer tokens, and the actual token transfer may be the same.
Cache
HTTP provides a self-signed cache framework. What you need to do is add some return header information when you return and add input validation when you accept the input. There are two basic methods:
ETag: When generating a request, an etag is added to the HTTP header that contains the checksum and hash value of the request, which should also change as the input changes. If the input HTTP request contains a If-none-match header and an etag value, then the API should return 304 not modified status code instead of the regular output.
last-modified: like the ETag, it's just one more time stamp. Returns the last-modified: Contains the RFC 1123 timestamp, which is consistent with if-modified-since. There are three kinds of date formats in the HTTP specification, and the server should be able to handle them.
Error handling
Just as the HTML error page can display an error message, the API should also return a readable error message – It should be consistent with the general resource format. The API should always return the appropriate status code to reflect the status of the server or request. API error codes can be divided into two parts, 400 series and 500 series, and 400 series indicate client error: such as bad Request format. The 500 series represents a server error. The API should return at least all of the 400 series errors in JSON form. This should be true if the 500 series errors are possible. Errors in JSON format should contain the following information: A useful error message, a unique error code, and any possible detailed error description. As follows:
12345 |
{ "code" : 1234, "message" : "Something bad happened :-(" , "description" : "More details about the error here" } |
The checksum of the input to the Put,post,patch should also return the corresponding error message, for example:
12345678910111213141516 |
{
"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"
}
]
}
|
HTTP Status Code
123456789101112 |
200 ok - 成功返回状态,对应,GET,PUT,PATCH,DELETE.
201 created - 成功创建。
304 not modified - HTTP缓存有效。
400 bad request - 请求格式错误。
401 unauthorized - 未授权。
403 forbidden - 鉴权成功,但是该用户没有权限。
404 not found - 请求的资源不存在
405 method not allowed - 该http方法不被允许。
410 gone - 这个url对应的资源现在不可用。
415 unsupported media type - 请求类型错误。
422 unprocessable entity - 校验错误时用。
429 too many request - 请求过多。
|
http://blog.jobbole.com/41233/
RESTful API Design Best Practices