Handwriting an http container [top] decision tree and routing table

The father of Linux said that pseudocode is the best language because it can express all logic. So all the code examples in this article are pseudo-code.


  • ALFP protocol

If I were to define the http protocol, I would give him a completely different name: ALFP (Application Layer Fetch Protocol, application layer request protocol). In 2020, I even forgot what the full name of "HTTP" is? It seems to be "Hypertext Transfer Protocol"? Then I realized that this kind of old acronym that is not friendly to newcomers is better not to be read apart. Moreover, the word "hypertext" is little known, but at least "hypertext" exists in the application layer. Things, coupled with the word "fetch" can very vividly summarize the characteristics of the http protocol: "fetch" means that there is a request and a response. So I think that if the HTTP protocol is renamed to the ALPF protocol, it will be more loving. The inspiration for the rename comes from the ALPN protocol (application layer protocol negotiation). If the rename is successful, the ALFP protocol can quickly understand the functions of this protocol and reduce their learning. Cost, while also satisfying the sand sculpture obsessive-compulsive disorder of our old players.

------------------------Serious dividing line---------------------- -


  • Handwriting an http router

The serious nonsense is over, let's talk about the topic. When we use Express, Koa and other http back-end frameworks, we can’t help but write one by ourselves. If you are lazy to learn a new framework, just to implement a small web app, hand-written back-end frameworks are often best s Choice. I will use nodejs to make the simplest http back-end routing model, so that everyone understands that the web framework is not complicated at all and everyone can write by hand.

Because it is a streamlined version of the back-end framework, there is no need to consider any load balancing and disaster recovery. Just consider the core concept of ALFP on a virtual machine server. The keywords are "application layer" and "crawling". In this way, we only need to consider what we have to do first and then what to do when a request comes in, and we will be done after the final response is done. Each step in the middle is an independent "middleware".

But in order to write a general back-end framework, we still need to examine what architectures most web apps usually have, and then combine these common requirements to create our own web framework. Regarding the requirements, the "onion diagram" proposed by the Koa framework gives us some references:

Don't worry about anything else, there are some more important back-end functions on the onion graph that we refer to, such as routing, sessions, caching, and exception handling. The architecture of most web apps is only those things taken apart.

Since the front-end architecture is basically unified after the 1920s, there is no difference between the browser, desktop, and mobile terminals. They are all called apps, but the ones that need server support are called web apps, and the ones that don’t need are called apps, and even all Most mainstream apps are written in 4 basic languages: JS, wasm, H5 and Css.

The above is the function. From the perspective of performance, in order not to block the main thread, all middleware must run on the event loop engine. In other words, each middleware is a promise.


  • Decision tree and routing table

The middleware is not only serial, but also tree-shaped: the calculation result of the previous middleware may determine the next middleware, so the entire middleware network is a decision tree, and the process of iteration on the decision tree It is called "routing", and the basis of routing is our "routing table".

There are many forms of routing tables, and different business logics can design different routing tables. Here we recommend a commonly used strategy for constructing routing tables based on Restful verbs. Restful verbs are a classification network composed of all possible operations on data. The familiar "addition, deletion, modification, and search" refers to these verbs. The following figure shows some of these verbs:

It is a good choice to build a decision tree based on data manipulation verbs. Verbs can be written in the http method header field or in the URL path. As for how the decision tree is embodied in the code, you can choose an if/else tree or a nested hash table according to the plot. Generally, the hash table can make each decision take the same time, which is more suitable for the situation where the decision tree is larger.


  • Elegant handling of URL paths

Speaking of paths, the back-end framework generally stores all the paths on the url in a list, but because the url paths are separated by forward slashes, in order to be unified with the space separator, multiple consecutive forward slashes can be regarded as one , The list only stores meaningful path names, so /path/to and /path//to and /path/to/ express the same meaning, and the corresponding routing tables are all ['path','to']. The expression for generating the routing list is as follows:

// 生成路由表的伪代码
request.paths = request.urlPath.split("/").filter(p => p.trim());

request.paths is the routing table, which stores every path from left to right on the url path. Whenever a layer of routing is passed, paths.shift() is used, and then the next middleware is selected according to request.paths[0].


  • Entrance design (index.html)

The entry design is very simple, that is, when the URL path is empty, the static file index.html needs to be returned. At this time, request.paths is an empty array.

// 网站入口的伪代码


if (request.paths.length === 0) {
  await new Promise((resolve, reject) => {
    response.setHeader("Content-Type", "text/html");


    const r = require("fs").createReadStream("path/to/index.html");
    r.on("error", err => reject(err));
    r.on("end", resolve);
    r.pipe(response);
  });
}



  • Session layer and authentication

In many places, we must consider the particularity of the entrance. In addition to index.html, what is the first thing to do after each request comes in? The first thing is usually to authenticate this request, right? Whether it is authentication by username and password or authentication based on session credentials, this is a must (even if it requests read-only resources).

According to the needs of the plot , the session information other than the session credential can be stored on the client or server . Anyway, the popular credential format JWT recommends storing other information in the client, such as some users' personal information. Anyway, the encrypted data is stored in the front end without harm, but the back end can be considered when the data volume is large. The following is an example of storing tokens on the front end:

// 会话层token认证的伪代码


module.exports = async function() {
  const req = this.request;
  const res = this.response;
  // myToken是假想的一种凭证插件,类似JWT
  const myToken = require("path/to/myToken");


  req.session = "";
  //   authorization头部用来存放加密的token
  const token = req.headers["authorization"];
  if (!token) return;


  //   secretKey是一个密钥,用于加解密token
  req.session = await myToken.verifyWithKey(token, secretKey).catch(err => {
    if (err.name === "TokenExpiredError") {
      // my-token-expire这个自定义头部提示前端应该删除这个token了
      res.setHeader("my-token-expire", 1);
    } else if (err.name === "invalidTokenError")
      throw "凭证损坏:" + err.message;
  });
};


When the request comes in, we store the decrypted data from the token it carries in the request itself for use by the middleware later, and also do error handling.


(To be continued. . . )

Guess you like

Origin blog.csdn.net/github_38885296/article/details/104132245