Handwriting a http container [below] the next generation of progressive web framework

To undertake the above " Handwriting a http container [top] decision tree and routing table "


  • Static file hosting: url path is mapped to FS path

One of the most commonly used functions of the web backend is the hosting of static files, that is, those read-only files stored on the server, which can be downloaded freely by the front end. The most direct way to achieve this is to map the path of the URL and the path of the file system one-to-one, so that you can download different files in the folder, including files in subdirectories, through the URL.

If you want to make a more "generous" static host, you can list all the contents of the directory when the front-end requests a directory, which can enrich the front-end applications in some cases. Based on these two functions, the static file middleware code I designed is as follows (nodejs):

// 静态文件中间件的伪代码


const path = require("path");
const fs = require("fs");
const { Readable } = require("stream");
// this代表对等体的生命周期;global是全局作用域
const req = this.request;
const res = this.response;
const cfg = global.config;


// path/to/public/是FS中被托管的文件夹路径
// req.staticPath是从url中提炼出来的文件的相对路径
const absPath = path.join(__dirname, "path/to/public/", req.staticPath);
const isFile = await new Promise((resolve, reject) => {
  fs.stat(absPath, (err, stats) => {
    if (err) reject(err);
    else if (stats.isFile()) resolve(true);
    else if (stats.isDirectory()) resolve(false);
    else reject(`${req.url}既不是文件也不是文件夹!`);
  });
});


// 根据文件后缀名判断mime类型
const suffix = isFile ? path.extname(req.url) : "directory";
res.setHeader(
  "Content-Type",
  {
    ".js": "text/javascript; charset=UTF-8",
    ".wasm": "application/wasm",
    ".html": "text/html",
    ".css": "text/css",
    ".png": "image/png",
    directory: "text/plain; charset=UTF-8"
  }[suffix] || "application/octet-stream"
);
// 利用浏览器的古典缓存机制
res.setHeader("Cache-Control", `public, max-age=${cfg.cacheInSec}`);
res.setHeader("ETag", cfg.version);


let r;
if (isFile) {
  r = fs.createReadStream(absPath);
} else {
  // 如果请求的是目录,则构建一个虚拟流来读取目录下的所有内容
  r = new Readable({
    read() {}
  });
  fs.readdirSync(absPath)
    .map(name => path.join(req.staticPath, name))
    .forEach(pa => r.push(pa + "\n"));
  r.push(null);
}
if (!res.headerSent) res.writeHead();


await new Promise((resolve, reject) => {
  r.on("error", err => reject(err));
  r.on("end", resolve);
  r.pipe(res);
});


The pseudo code is very simple, no redundant explanation, the result is: request files to get files; request directory to get a list of relative paths of all resources in the directory separated by newlines; if the requested resource does not exist, it will be thrown at the current routing point An exception occurred. Since every point on the routing tree may throw an exception, we need a unified error handling mechanism.


  • Error handling mechanism and custom http header

It's very simple. Just catch the exception at the end of the entire asynchronous decision tree, but you need to consider whether the error time point is in the response stream. If the response has not been sent, the error message can be sent to the front end as the content; if the response has already been sent, or is being sent, the back end cannot change the fact that it has been sent, and it cannot tell the front end the error message. The error message can be digested by the log system.

The http/2.0 stream is a mechanism abstracted from the socket, and the content of the stream is the http body instead of the header. The http header is used to control the life cycle of the flow. In other words, the request and response objects appear only after the header is passed.

Therefore, errors can be divided into two categories according to the moment of occurrence: before the response is sent and after the response is sent. If the response has not been sent, the error message is recommended to be written in a custom field in the http header, such as my-error; if the response has been sent, the error message is stored elsewhere.

// 决策树错误处理的伪代码


module.exports = async error => {
  const message = error.message || error || "有内鬼,终止交易";


  //   判断response是否已经发出
  if (this.response.headersSent) {
    //   将message存入假想的日志系统
    await require("path/to/logger").add(message);
  } else {
    this.response.writeHead(400, {
      "Content-Type": "text/html; charset=utf-8",
      "my-error": encodeURIComponent(message)
    });
    this.response.end(`<h1>${message}</h1>`);
  }
};


In pseudo code:

  • The reason for using the logger log system is to put error messages in a log file for the administrator to check.

  • The reason why the message is written into the http header is to allow the front end to process it in advance (before the start of the stream).

  • The reason for using URI encoding is to allow Unicode characters to be encoded into ASCII for writing into the http header.

  • The reason why the message is also written in the http body is to prevent the user from not seeing the error message when opening the error link directly.


  • Body parser and content-length header

General back-end frameworks will have some built-in body parsers such as bodyParser, and we will write one by hand.

The best way to design a progress bar is to specify the size of the entire resource in the first packet. The front-end calculates the progress according to the number of trunks transmitted; if unfortunately it is not possible to know the size of the resource at the beginning, it can only Write next to each chunk if this is the last one, of course, there will be additional space overhead.

The design of http/2.0 also considered these two situations, so we gave us the content-length field. The pseudo code will not be released, just note that the parsing of the body and other middleware are carried out concurrently, so request.body is a promise.

------------------------Irregular dividing line--------------------- ---


  • The next generation of progressive web framework: FetchMe.js

Of course, there are still many things that are not considered, but it does not prevent us from giving this framework a resounding name: if the feature of http is "fetch", it is "fetch me" for the backend, which is very vivid.

fetchme.js is a progressive web framework, based on the ALFP protocol, a next-generation scaffold that integrates a series of advanced web development concepts. Although the current money is not enough, the partner is undecided, and the code is temporarily unavailable, the theoretical basis and name of fetchme have been predetermined in this article. Based on these theories, even if it cannot be a general web framework, at least an app that implements specific applications can be built. , Such as a blog framework.

(Finish)


Guess you like

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