Original article, reproduced please specify: Reproduced from Keegan Small Steel

And indicate the original link: http://keeganlee.me/post/practice/20160812

Wechat subscription number: keeganlee_me

Written in the 2016-08-12


Road of App Project actual combat (I): Overview

Road of App Project combat (II):API chapter

Road of App project actual combat (3): Prototype

The road to App project combat (4):UI


Identify functional requirements

After the overview was published, it received a lot of support and several suggestions on the functional requirements, mainly in the following aspects:

  1. Only wechat login in the App Store is likely to pass the audit;
  2. Call wechat to obtain user profile picture and nickname interface needs enterprise wechat signal;
  3. Even when logging in to wechat, there is a need to change the profile picture and nickname.

On the first point, if you think about it, only a third-party account login is indeed approved. This is because you must provide your test account to the App Store reviewers when submitting for review. Reviewers will not use their own account for testing, whether it is their wechat, Weibo or mobile phone number. I fell into this trap before. I submitted an App to log in with a mobile phone number + SMS verification code, but did not provide a test account. The result was called back. So, still need to build their own user system, this point can not be lazy.

As for the second point, iT is because wechat has done permission control on this part of the interface, and only those who have passed the developer qualification certification can open this interface. However, wechat’s developer certification does not support individual developers. In addition, the annual audit fee of 300 yuan should be paid. In fact, the App created by the unauthenticated developer only has the permission to share, not to log in at all. So, wechat login this way is not open. So I decided to skip wechat and log on to Github instead. After all, the target audience is apps, and apps basically have a Github account. If you don’t have a Github account, you’re not a good programmer. Also do not consider joining weibo, QQ, Facebook, Twitter and other social accounts to log in. Because there are so many options, I often can’t remember which account I used last time I logged in on some platforms.

On point 3, the ability to change avatars and nicknames needs to be preserved, of course.

Therefore, the final functional requirements should be as follows:

  1. Mobile phone number + SMS verification code to register
  2. Mobile phone number + SMS verification code login
  3. Making the login
  4. To upload pictures
  5. Modify the picture
  6. Modify the nickname
  7. Sets the user technology stack TAB
  8. Gets the content list of apes in the same stack
  9. Get a list of apes of interest
  10. Get the list of users on the same stack (get if there is no ape to follow)
  11. Post questions
  12. Issued share
  13. Focus on a piece of content
  14. Unfollow content
  15. Get a list of comments on the content
  16. Add comments
  17. Reply to comment
  18. Thumb up comments
  19. Follow a User
  20. Unfollow a user
  21. Get someone’s details
  22. Get someone’s posts
  23. Someone who gets someone’s attention
  24. Get a list of someone’s followers
  25. Get my news
  26. Submit feedback
  27. Log out

The requirements are identified, and you can begin to design the API.

REST API

I won’t go into detail here on what REST is, but instead recommend a classic paper by REST authors:

  • Architectural Style and Web-based Software Architecture Design (Chinese Revised Version)

I just want to use a few examples to illustrate how several architectural styles differ in terms of API definitions.

If you want to define the interface for logging in, logging out, registering, and querying user information, you can define it as follows:

interface methods Endpoint
The login POST /user/login
Log out POST /user/logout
registered POST /user/register
Querying User Information GET /user/queryInfo

There seems to be a lot of use of this style. Others do not define interfaces in urIs, but use parameter names such as method or action to distinguish different interfaces, as shown in the following example:

interface methods parameter
The login POST method=login
Log out POST method=logout
registered POST method=register
Querying User Information GET method=queryUserInfo

Finally, look at the following interface definition:

interface methods Endpoint
The login POST /sessions
Log out DELETE /sessions/{session_id}
registered POST /users
Querying User Information GET /users/{user_id}

What’s the difference between these three definitions? In fact, the first two can be considered Remote Procedure Call (RPC) style, while the last one can be considered REST style.

What’s the difference between RPC and REST?

The most direct difference is that RPC abstracts processes while REST abstracts resources. Processes are verbs at their core, while resources are nouns at their core. A simple analogy is that RPC is procedural and REST is object oriented.

As can be seen from the above examples, the first two definitions use an operational term for each interface. In the last definition, login and logout belong to /session resources, registration and query of user information belong to /user resources, and then define different operations on the same resource with methods such as POST, DELETE, and GET.

I found that there are other definitions that are rPC-REST hybrids, for example, that might look like this:

interface methods Endpoint
The login POST /users/login
Log out POST /users/logout
registered POST /users/register
Querying User Information GET /users/{user_id}

If you add an interface to modify user profiles, it might look something like this:

interface methods Endpoint
Modifying user Information POST /users/{user_id}/update

What a mess! Most of these designs were made with a superficial understanding of REST, but a lack of proper understanding. Or they don’t know how to abstract some interfaces into resources, so they define them directly in RPC mode.

In fact, I find it difficult to abstract resources when designing apis with REST style. Using RPC is much easier. At this point, someone may ask questions. Why use REST when it’s easier to abstract interfaces using RPC than REST? To answer this question, we can think in terms of process orientation and object orientation. We know that process-oriented thinking is more straightforward, so why do we use object-oriented thinking? As for the answer to that question, I won’t expand on it.

The API defined

The API for this project is intended to be defined using REST. So, first, there is the Endpoint definition of the resource. The following resources are compiled according to the previous functional requirements, and some may be missing:

Endpoint resources
/files file
/files/{file_id} A file
/sessions The session
/sessions/{session_id} A session
/users The user
/users/{user_id} A user
/users/{user_id}/posts Content posted by a user
/users/{user_id}/following The person that a user follows
/users/{user_id}/followers A fan of a user
/posts Published content
/posts/{post_id} One of the content
/posts/{post_id}/comments Comment on a piece of content
/me The current user
/me/posts What I post
/me/stars I star content
/me/following The people I care about
/me/followers I am a fan of
/me/messages My news

When defining the Endpoint of a resource, you need to know the hierarchy of different resources. A well-defined URI should be readable, meaning that the resource it represents is known from the URI itself. In addition, for some variable values in the URI, such as {file_id}, {session_id}, {user_id}, {post_id}, etc., you must ensure that the value cannot be empty when transferring the value, you can set the default value.

Next, you need to define the method of operation for each resource. I tend to use the following four methods:

methods describe The sample example
POST Creating a New resource /posts Create new content
GET Query resources /posts List of Query contents
PUT Modify the resource /posts/{post_id} Modify a content
DELETE Delete the resource /posts/{post_id} Delete an item

However, not all resources open these four methods. For example, the PUT and DELETE methods are not open for/POST. What methods need to be defined for the above resources is not listed here.

Then, add version control. After all, interfaces are not static and need to be updated constantly to accommodate changes. So, where do I put the version number? There’s a lot of discussion online about this, and some people like to add it directly to the URI, like this:

http://api.domain.com/v2.1/postsCopy the code

Some people like to add parameters like this:

http://api.domain.com/posts?version=2.1Copy the code

Some people like to add it to the Header, like this:

Accept: application/json; Version = 2.1Copy the code

Or customize the Header

API - version: 2.1Copy the code

Those who dislike the first approach mostly argue that URIs represent resources and should be version-independent. The second way is essentially the same as the first way. Most people suggest using the third method. However, many open apis are found to take the first approach. In my opinion, it doesn’t really matter where you add it. For this project, I’m going to do the first approach just like most open apis. In addition, if the version number is not provided, the interface of the latest version is used by default.

Finally, define the data protocol of the response. JSON will be used initially, and Protocol Buffers may be considered later. The data structure is as follows:

{code: 200, message: "success", data: {key1: value1, key2: value2,... }}Copy the code
  • Code: error code
  • Message: Indicates the description information. The value is “success” if the information is successful and an error message if the information is incorrect
  • Data: Data returned on success, of type object or array

Previously, I preferred to separate request status codes from business error codes. So, the code here I used to like to define as a business error code. However, if you follow the REST style of design, a uniform code is more appropriate. So, I’m going to try to change habits this time.

API security Design

In terms of security design, first of all, I’m going to use HTTPS across the board. Using HTTPS, while sacrificing performance, solves most of the security problems. Additionally, Apple announced at WWDC that HTTPS will be mandatory for all iOS apps starting January 1, 2017. This also means that from 2017, all apps will use HTTPS, not just iOS. Unless there’s something weird, you have to do HTTP and HTTPS. As for HTTPS optimization, it needs to be done slowly. As for certificates, get yourself a self-signed certificate. Later need to support the Web version of the words to find a reliable CA registration certificate.

Secondly, user authentication is intended to use Token. The user is assigned an accessToken and a refreshToken after login. The accessToken is used to initiate user requests and the refreshToken is used to update accessToken. AccessToken sets the validity period, which can be 24 hours. When the user logs out, both accessToken and refreshToken are invalid. New accessToken and refreshToken are allocated after re-login.

Then I’m going to assign AppKey and AppSecret at the App level, and one pair for Android and one for iOS. The AppKey must be carried each time a request is sent to the server. The server verifies the AppKey. AppSecret, on the other hand, needs to be kept securely on the client and cannot be transmitted over the network to prevent leakage. AppSecret is only used to encrypt some high security data and to generate signatures for urls. URL signature algorithm steps are as follows:

  1. Sort all parameters in ascending order by parameter name;
  2. Concatenate the sorted parameter names and values into a string stringParams in the format of key1Value1key2Value2… ;
  3. Concatenate the Endpoint of the URI before the string in the previous step, concatenate AppSecret after the string, i.e. StringURI + stringParams + AppSecret;
  4. AppSecret is used as the key and HMAC algorithm is used to calculate the MAC value of the result string of the previous step. The MAC value is the signature.

In addition, in order to further enhance security, a timestamp field can be added to the parameter list of participating signatures, whose value is the timestamp of sending the request. The timestamp of each request will be different, which not only increases the unpredictability of signatures, but also prevents replay attacks. After receiving the request, the server checks whether the timestamp is too long from the current time. If the timestamp is too long, the server does not process the request. However, there is also the issue of client and server time synchronization. This is difficult to keep consistent, even if you are using long connections to continuously obtain server time, there will be latency due to network reasons, and latency may be higher on mobile networks.

Another option is to use the Nonce field, which is a long random number instead of a timestamp. Each request will have a different random number to achieve the same effect. However, with this scheme, the server needs to save the nonce that was sent before. After receiving a request, check whether the Nonce exists. If the nonce exists, the request is not processed. In this way, over time, the amount of Nonce will be very large. There is also an optimization where the nonce value for each request is generated by the server and sent to the client. That is, the client needs to request the nonce value from the server before sending a formal request. In this way, the server can generate the Nonce only when the request is received and delete the Nonce after the request is processed. However, the drawback is also obvious, originally one request becomes two.

However, in my project, I only required increased unpredictability of signatures at the beginning, and the NONCE scheme had higher unpredictability. Therefore, THE solution I will adopt is that the client generates the Nonce itself, but the server does not save the Nonce and simply checks for the presence of the Nonce in the request.

The URL signature must be added to the parameters each time a request is sent. After receiving the request, the server uses the same signature algorithm to calculate the signature value. The request is considered secure only when the signature value calculated by the server is the same as the received signature value.

Write in the last

From there, the API part of the design is complete. Here’s a summary:

  1. The API is defined in the REST style, and the interface abstracts the operation of the resource.
  2. Add API version control, version number embedded in URL;
  3. JSON data formats of code, message and data are used in response.
  4. The whole site uses HTTPS;
  5. Token authentication is used to authenticate users.
  6. AppKey is used to authenticate applications.
  7. Authenticate requests using URL signatures.
  8. The nonce value is added to enhance the unpredictability of the signature.

Scan the following QR code to follow the subscription number.