Hand tear koa2 source code

Time:2020-9-8

Diving in SF platform for a long time, this is also my first article, before has been mainly learning. I hope I can do some output by writing technical articles and answering questions in the future

Recently, I was asked twice about koa’s onion ring model (because I used koa2 to write several projects in the school before), but I didn’t dig into the principle of onion rings. I didn’t feel very satisfied with the answer. So this time, I have a look at the source code of KOA.

directory structure

├── lib
│   ├── application.js
│   ├── context.js
│   ├── request.js
│   └── response.js
└── package.json

At present, we downloadnode_modules/koaThis is the structure of the source file in the package. The core of KOA processing requests is the above four files, among which

  • application.jsIt’s the entrance to the entire koa2. The most importantmiddleware Logic is also processed here. This is what I learned this time.
  • context.jsResponsible for handling application context
  • request.jsProcessing HTTP requests
  • response.jsProcessing HTTP responses

Take a lookKOAWhat methods are encapsulated in the class

Constructor

constructor(options) {

super();
options = options || {};
this.proxy = options.proxy || false;
this.subdomainOffset = options.subdomainOffset || 2;
this.proxyIpHeader = options.proxyIpHeader || 'X-Forwarded-For';
this.maxIpsCount = options.maxIpsCount || 0;
this.env  = options.env  Wei process.env.NODE_ Env |'development '; // environment variable
if (options.keys) this.keys = options.keys;
this.middleware  =[]; // Middleware queue**
this.context  = Object.create (context); // Context
this.request  = Object.create (request); // request object format
this.response  = Object.create (response); // response object format
if (util.inspect.custom) {
  this[util.inspect.custom] = this.inspect;
}

}

listen

listen(...args) {
    debug('listen');
    const server = http.createServer(this.callback());
    return server.listen(...args);
}

KOAIs native tocreateServerWith simple encapsulation, the passed in parameters will also be passed directly to the nativeserver.listen。 It’s just through hereKoa classMediumcallbackMethod generates a configuration object and passes it to the server. From this point of view, the actual execution logic of KOA is actually implemented through thecallbackFunction.

callback

callback() {
  const fn = compose(this.middleware);

  if (!this.listenerCount('error')) this.on('error', this.onerror);

  const handleRequest = (req, res) => {
    const ctx = this.createContext(req, res);
    return this.handleRequest(ctx, fn);
  };

  return handleRequest;
}

First look at the first sentence

const fn = compose(this.middleware);

this.midllewareThis is obviously the middleware queue in the instance.

as everyone knows,composeIt means composition. A phrase we have learned before isbe composed of -> By Composition。 The representation in the code is probably a composite function

g() + h() => g(h())

The compose variable here comes from thekoa-composeThis bag, his role is to put all of themKoa MiddlewareMerge execution. Can be understood as beforemiddlewareArrays are just a few scattered layers of onion that are passed throughkoa-composeAfter processing, it becomes a complete onion (attached at the end of the articlekoa-composePrinciple)

Back to the above method, we get the composition function after middleware combinationfnAfter that, declare ahandleRequestFunction, in the function first throughthis.createContextInitialization message can be understood as generating a complete request message and response message, and the logic of initialization message is written inlib/request.jsandlib/response.jsin Finally, they were passedthis.handleRequestMethod to process the message.

handleRequest(ctx, fnMiddleware) {
  const res = ctx.res;
  res.statusCode = 404;
  const onerror = err => ctx.onerror(err);
  const handleResponse = () => respond(ctx);
  onFinished(res, onerror);
  return fnMiddleware(ctx).then(handleResponse).catch(onerror);
}

Here is to send the processed message to the combination function generated beforefnAfter completing the whole onion ring processing logic, do some subsequent processing (return client / error handling).

As we all know, each layer of the onion ring model is a promise object, only when the onion ring upstream (according to the official document) entersresolvedAfter the status, the usage right of the thread is passed downstream. (note that the previous onion ring is not finished at this time) when the promise of the inner layer becomesresolvedAfter the status, the usage right of JS thread will continue to bubble up to handle the outer onion ringLogic after resolve

Two parameters received by onion ring functionctxnextThe next method in is an asynchronous method, which means to force the current onion ringresolveThe current function will be blocked until all downstream logic is processed.

Hand tear koa2 source code

Seeing here, we can sort out the parts we have seen so far.

  1. The koa instance collects some required middleware(useMethods)
  2. staythis.callbackFunction through thekoa-composeThe middleware is combined to generate an onion ring processorfn(who to deal with, of course, the message object)
  3. Format method of reference message
  4. Package the packet format method and onion ring processor into a factory functionhandleRequest。 This factory function is only responsible for receiving messages and returning results.

That’s what it saysuseThe method is also the core of the onion ring model, literallyRegistration Middlewaremethod.

use

use(fn) {
  if (typeof fn !== 'function') throw new TypeError('middleware must be a function!');
  if (isGeneratorFunction(fn)) {
    deprecate('Support for generators will be removed in v3. ' +
              'See the documentation for examples of how to convert old middleware ' +
              'https://github.com/koajs/koa/blob/master/docs/migration.md');
    fn = convert(fn);
  }
  debug('use %s', fn._name || fn.name || '-');
  this.middleware.push(fn);
  return this;
}

useThe accepted function must be a function (accept two parameters, one is CTX context, one is next function), if this function is a generator(*generator)(the middleware in koa1 only receives iterator methods, while in koa2, it uses async wait to implement all of them)koa-convertFunction to do the conversion. (I haven’t seen this one yet

Finally, it is very simple to push this method to themiddlewareIn line

See here,applicationThe content of the file is over. It seems that there are few koa source codes. However, due to the existence of middleware, the scalability becomes very strong, which should be the reason why koa is so popular at present.

PS: koa-compose

koa-composeThe amount of code is very small, less than 50 lines (49 lines). But the design is very subtle.

function compose (middleware) {
  if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!')
  for (const fn of middleware) {
    if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!')
  }
  return function (context, next) {
    //The last invoked middleware subscript
    let index = -1
    return dispatch(0)
    function dispatch (i) {
      //I < = index indicates that a middleware has been repeatedly called and an error is thrown
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      let fn = middleware[i]
      //FN takes the middleware of the corresponding layer. If the next function is passed in, the priority of the next function is higher
      if (i === middleware.length) fn = next
      //Here FN refers to the middleware function of layer I
      //If not, resolve directly
      if (!fn) return Promise.resolve()
      try {
        //The actual execution step is here, the FN method is executed, and the execution function of the next layer middleware is also passed
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

In a nutshell, this syntax means that every middleware executesawait next()Statement, will call the next layer of middleware. You can also think of the code as

//First half processing logic
await next() 
//The latter part deals with logic


/*The equivalent of*/


//First half processing logic
Await new promise
//The latter part deals with logic

And the insertion logic isLogic of next layer MiddlewareThe execution logic of onion rings is stored in the arraykoa.middlewareOnly the first step of the onion ring is executednThe subscript will be passed only when the layer is setn+1The next layer of processing logic is removed, and a promise is generated and inserted into the function body of the upper onion ring to form an overlapping function scope.

END

Recommended Today

Using VBS to realize the function of browsing folder in Windows Script Host

‘************************************************ ‘ File:Dialog.vbs (WSH sample in VBScript)  ‘ Author:(c) G. Born ‘ ‘ Using the shell dialog box to select a folder ‘************************************************ Option Explicit ‘ Flags for the options parameter Const BIF_returnonlyfsdirs = &H0001 Const BIF_dontgobelowdomain= &H0002 Const BIF_statustext = &H0004 Const BIF_returnfsancestors= &H0008 Const BIF_editbox= &H0010 Const BIF_validate = &H0020 Const BIF_browseforcomputer= &H1000 Const BIF_browseforprinter = &H2000 Const BIF_browseincludefiles = &H4000 Dim wsh, objDlg, objF ‘ Get Application object of the Windows shell. Set objDlg = WScript.CreateObject(“Shell.Application”) ‘ Use the BrowseForFolder method. ‘ For instance: Set objF = objDlg.BrowseForFolder _ ‘ (&H0, “Select the folder to copy”, &H10, “C:\Born”) Set objF = objDlg.BrowseForFolder (&H0, _ “Select the folder to copy”, _ BIF_editbox + BIF_returnonlyfsdirs) ‘ Here we use the first method to detect the result. If IsValue(objF) Then  MsgBox “Selected folder: ” & objF.Title Else MsgBox “Canceled” End If ‘ Here we use TypeName to detect the result. If InStr(1, TypeName(objF), “Folder”) > 0 Then MsgBox “Selected folder: ” & objF.Title Else MsgBox “Canceled” End If Function IsValue(obj) ‘ Check whether the value has been returned. Dim tmp On Error Resume Next tmp = ” ” & obj If Err <> 0 Then IsValue = False Else IsValue = True End If On Error GoTo 0 End Function ‘*** End