This article is participating in node.js advanced technology essay, click to see details.

Connect is an HTTP/HTTPS service that supports NodeJs middleware model architecture, extends middleware architecture for existing HTTP/HTTPS servers, and supports existing middleware plug-ins or libraries such as KOA ecology.

This article will be based on 3.7.0 version of the source code to explain the use of connect library and middleware model architecture principle.

The basic use

Connect can be used as either an HTTP/HTTPS server or extend the middleware model to existing HTTP/HTTPS services. Here are two different ways to use it:

  • Used as a standalone service that supports the middleware model
const connect = require('connect');

const app = connect();

function middlewareA(req, res, next) {
  setTimeout(() = > {
    console.log('run middlewareA');
    next();
  }, 2000);
}

function middlewareB(req, res, next) {
  setTimeout(() = > {
    console.log('run middlewareB');
    next();
  }, 1000);
}


app.use(middlewareA).use(middlewareB);

app.listen(3200.() = > {
  console.log('[connect server] running at port 3200.')});Copy the code
  • Used as a middleware model for HTTP/HTTPS services
const http = require('http');
const connect = require('connect');

const app = connect();

function middlewareA(req, res, next) {
  setTimeout(() = > {
    console.log('run middlewareA');
    next();
  }, 2000);
}

function middlewareB(req, res, next) {
  setTimeout(() = > {
    console.log('run middlewareB');
    next();
  }, 1000);
}

app.use(middlewareA).use(middlewareB);

const server = http.createServer(app);

server.listen(3200.() = > {
  console.log('[connect server] running at port 3200.')});Copy the code

Isn’t it amazing to know that you can use both? Let’s take a look at how he supports these two different uses.

The main architecture

The connect library as a whole exports a createServer function to create a server, which can be used either as a callback to the HTTP/HTTPS service or as a separate service. Look at the following code:

module.exports = createServer;

var proto = {};

function createServer() {
  function app(req, res, next){ app.handle(req, res, next); }
  merge(app, proto);
  merge(app, EventEmitter.prototype);
  app.route = '/';
  // The stack that stores the middleware collection
  app.stack = [];
  return app;
}

// Register middleware
proto.use = function use(route, fn) {}

// Call the middleware in turn
proto.handle = function handle(req, res, out) {}

// Start the listening service
proto.listen = function listen() {}
Copy the code
  • CreateServer creates an app function and returns app. The app function is basically the same as the HTTP/HTTPS. CreateServer callback. Therefore, the instance returned by createServer can be used as a callback function for the HTTP /https.createServer service. When used as a callback, the app.handle method is actually executed, which is used to call the middleware services that are added in turn.

  • merge(app, proto); Shallow copy the methods on proto to app, so that the app function object adds methods such as Listen. So app.listen(3000, () => {}) allows you to use app as a service alone.

Let’s look at how the library registers middleware:

Middleware Registration Principle

proto.use = function use(route, fn) {
  var handle = fn;
  var path = route;

  // default route to '/'
  // Middleware scope is not explicitly specified when registering middleware,
  // The default value is '/', which takes effect for all
  if (typeofroute ! = ='string') {
    handle = route;
    path = '/';
  }

  // wrap sub-apps
  // Format the arguments when the arguments are objects
  if (typeof handle.handle === 'function') {
    var server = handle;
    server.route = path;
    handle = function (req, res, next) {
      server.handle(req, res, next);
    };
  }

  // wrap vanilla http.Servers
  if (handle instanceof http.Server) {
    handle = handle.listeners('request') [0];
  }

  // strip trailing slash
  // Remove the trailing /
  if (path[path.length - 1= = ='/') {
    path = path.slice(0, -1);
  }

  // add the middleware
  debug('use %s %s', path || '/', handle.name || 'anonymous');
  // Put the middleware on the stack
  this.stack.push({ route: path, handle: handle });

  return this;
};
Copy the code

The logic of registration is to store the middleware and the scope that the middleware should match into an array. It is a queue that stores all middleware, requiring middleware implementation formats that are consistent with middleware requirements such as KOA, so other ecologies can be used.

Middleware asynchronous queue consumption

Here’s how the middleware is called in turn, and each middleware supports asynchracy and does not rely on promise, async/await syntax, which is the core of this library:

proto.handle = function handle(req, res, out) {
  var index = 0;
  var protohost = getProtohost(req.url) || ' ';
  var removed = ' ';
  var slashAdded = false;
  var stack = this.stack;

  // final function handler
  // Final processing of the request
  var done = out || finalhandler(req, res, {
    env: env,
    onerror: logerror
  });

  // store the original URL
  req.originalUrl = req.originalUrl || req.url;

  function next(err) {
    if (slashAdded) {
      req.url = req.url.substr(1);
      slashAdded = false;
    }

    if(removed.length ! = =0) {
      req.url = protohost + removed +
          req.url.substr(protohost.length);
      removed = ' ';
    }

    // next callback
    // Middleware object to be called, layer.route Middleware route Layer. handle is a middleware function
    var layer = stack[index++];

    // all done
    // The middleware finishes execution and outputs errors if there are any
    if(! layer) { defer(done, err);return;
    }

    // route data
    // The requested pathName path
    var path = parseUrl(req).pathname || '/';
    // The path range that the current middleware matches
    var route = layer.route;

    // skip this layer if the route doesn't match
    // If the requested pathname does not match the range of the current middleware, the current middleware is skipped
    if (path.toLowerCase().substr(0, route.length) ! == route.toLowerCase()) {return next(err);
    }

    // skip if route match does not border "/", ".", or end
    For example, the middleware expects to handle a request for/A, but the request url might be /a, / ABC, /a/ BC * so this is the case where/ABC is excluded */
    var c = path.length > route.length && path[route.length];
    if(c && c ! = ='/'&& c ! = ='. ') {
      return next(err);
    }

    // trim off the part of the url that matches the route
    if(route.length ! = =0&& route ! = ='/') {
      removed = route;
      req.url = protohost +
          req.url.substr(protohost.length + removed.length);

      // ensure leading slash
      if(! protohost && req.url[0]! = ='/') {
        req.url = '/' + req.url;
        slashAdded = true; }}// call the layer handle
    call(layer.handle, route, err, req, res, next);
  }

  next();
};
Copy the code

After receiving the registered middleware queue, the logic for consumption is as follows:

  • throughindexVariable marks the middleware location to which it is currently executing
  • By calling thenextFunction executes the current middleware, andindexvariable1Is used to point to the location of the next middleware
  • nextThrough internalcallThe function actually makes the middleware call and puts the currentnextPass a reference tocall,callYou get the means to call the next middleware.
function call(handle, route, err, req, res, next) {
  // The number of parameters of the handle middleware function
  var arity = handle.length;
  var error = err;
  var hasError = Boolean(err);

  debug('%s %s : %s', handle.name || '<anonymous>', route, req.originalUrl);

  try {
    if (hasError && arity === 4) {
      // error-handling middleware
      handle(err, req, res, next);
      return;
    // If the call fails, subsequent middleware execution is not called, but directly next to the end
    } else if(! hasError && arity <4) {
      // request-handling middleware
      // Middleware call for request processing
      // Within third-party middleware, when next is called manually
      handle(req, res, next);
      return; }}catch (e) {
    // replace the error
    error = e;
  }

  // continue
  next(error);
}
Copy the code

The call function actually calls the middleware function and passes the parameters req, RES, and next to the calling middleware:

handle(req, res, next);
Copy the code

Therefore, when the middleware executes, it can process REQ and RES, and after the execution, it can manually call next to enter the next middleware:

// A middleware that simulates asynchronous processing
function middlewareA(req, res, next) {
  setTimeout(() = > {
    console.log('run middlewareA');
    next();
  }, 2000);
}
Copy the code

A similar middleware model is abstracted as follows:

const http = require('http');

class Server {
  constructor() {
    this.stack = [];
    this.server = http.createServer((req, res) = > {
      this.iterate(req, res)
    });
  }
  listen(port, cb) {
    this.server.listen(port, cb);
  }
  use(middle) {
    this.stack.push({
      middle,
    });
    return this;
  }
  iterate(req, res) {
    let index = 0;
    const stack = this.stack;

    function next(err) {
      const layer = stack[index++];
      if (index > stack.length) return;
      if (err) next(err);

      if (layer.middle) {
        layer.middle(req, res, next);
      } else {
        next(new Error('middle miss')); } } next(req, res); }}function middlewareA(req, res, next) {
  setTimeout(() = > {
    console.log('run middlewareA');
    next();
  }, 1000);
}

function middlewareB(req, res, next) {
  setTimeout(() = > {
    console.log('run middlewareB');
    next();
  }, 2000);
}

const app = new Server();
app.use(middlewareA).use(middlewareB);
app.listen(3000.() = > {
  console.log('server running at port 3000');
});
Copy the code

conclusion

The main function of the Connect library is to extend the middleware. If you are interested in middleware, you can also take a look at koA’s middleware implementation. And the middleware core is an asynchronous queue implementation, such as vue-Router library on the registration of the hook function is also different implementation of the asynchronous queue, interested can look together.

I am leng hammer, I and the front end of the story continues……

Please like 👍 and collect ❤️……