Contents of this series

1. Global (this article)

2. Routes are matchedI went to see

3. Error handlingI went to see

4. View templateI went to see

5. Design ideaI went to see

1. The entrance

Express is simple enough to use. Generally speaking, it is to set up the route and the corresponding middleware, and the middleware flow processes the two objects reQ and RES, and finally returns the results. Take the template generated by Express scaffolding as an example:

1.1 The entry of the program is bin/www.js

conclusion

  1. Import app as a parameter to the http.createServer method
  2. Listen on port

1.2 Review of the use of the native HTTP.createserevr method

If the parameter is app, then app is options. Let’s see, what exactly is exported from the app.js file in the root directory

As you can see, the app is generated by executing the Express method. At this point, you can only look at the source code of the Express module

Obviously the exported app is a function, but http.createServer requires options to be an object, so what happens when you pass in a function? For the thrill of it all, I found the Node source code

The app method is passed as a callback to http.createServer

conclusion

  1. The Express module exports a function app
  2. The app function is passed in as a requestListener parameter to the http.createServer method to listen for ‘request’ events

Once the clue stops here, you can see that every time a request arrives, the Request event will be triggered, and the two native objects of Request and Response will be passed into the app as parameters

Extracurricular: Request event


2. Object oriented

JS, unlike us, has objects, which is the main point of most frameworks — the entire framework is built on a few important objects. However, in JS, it is important to remember that functions are also objects, and even functions with their own properties and methods are called constructors, but in essence there is not much difference. On the contrary, functional object sasa confusion + this confusion refers to various prototype mixins can cause a great barrier to read the source code, but object-oriented reading may be the right way to go

Object OR constructor in 2.1 Express

  1. App object

It can be seen that app is just a function from a poor family, but after two upgrades of attribute Mixin equipment, it has the divine power. EventEmitter. Prototype’s Minix enables app to have the ability to be related to a series of events, such as on events and emit events. Proto Mixin is express exclusive ability, in order to save time, I summarized the proto Mixin exactly what Mixin

Leaving the details aside, remember our strategy – object orientation, find objects that appear, what is the new Router object that is added? Let’s find out

2.2 the Router object

The Router object is a little noble, with a separate folder and two disciples layer and Route under it. It can be seen that the Router ontology is also a function, which is not exactly the same as app. It is also enhanced by the 1-to-1 transmission of the prototype object. We need to see what the proto prototype actually delivers to him.

The same rules, no matter the details, look at the new route, layer two disciples are holy, since they are router disciples, then look together

So far, it’s a bit too much information, and I’ve neglected a lot of details, but the basic four important objects have been introduced, so let’s give them names so we can understand what they do, and we’ll talk about their interactions in Clue three.

App –> Elizabeth Old (APP)

Router –> Boris Router, Prime Minister

Route —-> Boris Meat, son of the Prime Minister

Layer —->

Why these names? I said disorderly, I am not disorderly ah ~!

Elizabeth is mainly responsible for communicating with the outside world and exposing interfaces to the outside world. The specific interface functions are provided by the Router

Prime Minister Nittel is responsible for request routing scheduling and middleware management

The prime minister’s son Meat is responsible for scheduling specific request methods, such as get and POST routes

The worker of Rhea is responsible for the practical work. There are three layers, which appear in different places, respectively

  1. The middleware layer, located at router.stack, is responsible for layer 1 flow processing
  2. Layer, the router. Stack puppet of route, is responsible for passing information to route
  3. The layman layer, located in route.stack, is responsible for the actual processing of a particular route

More on that later

The basic relationship figure will also be analyzed later to form an impression first


3. Prepare before dawn

In a real application, we do a lot of initialization before the request arrives. For example, Express scaffolding generates a project template, as shown below. We add middleware and set things up before listening

App. use and app.all are used here, as well as app.get and app.post…. And so on, without exception, these methods are closely related to the whole program route resolution.

By analyzing the source code (source code I will not paste, directly say the result), we can find the following call flow:

  1. App. Use calls router.use, which creates the layer and pushes it onto the router’s own stack
  2. Router. route creates a layer object, a route object, associates the two, returns a route object, and finally calls route().
  3. App [method] executes point 2 on the specified method

Summarized below

For every app.use/app.all… If method is not specified, the global layer will be generated, which is managed by app._router. If method is specified, route will be generated for management, and a global layer will be pushed. The route.dispatch method calls route.stack’s layer to handle a route.dispatch method calls route.stack’s layer to handle a route.dispatch method calls route.stack’s layer to handle a route.dispatch method

Route. stack and route.stack This is one of the core middleware mechanisms of Express. Clue four goes into detail. Clue three ends.

What properties do layer objects have


4. The whole army strikes

After a long period of initial initialization and preparation, the program has the ability to handle HTTP requests, so let’s take a look at express with an HTTP request.

In the entry, we conclude that when the HTTP request is triggered, the app will be handled as a callback, receiving two parameters, a native REQ object and a native RES object.

The app calls app.handle internally, and there is no next parameter. In other words, next is undefined

Callback is undefined, so done will be wrapped as finalHandler, and this function is ignored. Finally, check whether router is set, of course, router will be created when app.use/app.all is called. If there is no router, there is no middleware function to process. If there is a router, router.handle is called. This is the most important function.

Longer, do not post photos, do not want to see can jump to the summary

proto.handle = function handle(req, res, out) {
  var self = this;
  debug('dispatching %s %s', req.method, req.url);
  var idx = 0;
  var protohost = getProtohost(req.url) || ' '
  var removed = ' ';
  var slashAdded = false;
  var paramcalled = {};
  // store options for OPTIONS request
  // only used if OPTIONS request
  var options = [];
  // middleware and routes
  var stack = self.stack;
  // manage inter-router variables
  var parentParams = req.params;
  var parentUrl = req.baseUrl || ' ';
  var done = restore(out, req, 'baseUrl'.'next'.'params');
  // setup next layer
  req.next = next;
  // for options requests, respond with a default if nothing else responds
  if (req.method === 'OPTIONS') {
    done = wrap(done, function (old, err) {
      if (err || options.length === 0) return old(err);
      sendOptionsResponse(res, options, old);
    });
  }
  // setup basic req values
  req.baseUrl = parentUrl;
  req.originalUrl = req.originalUrl || req.url;
  next();
  function next(err) {
    var layerError = err === 'route' ?
      null :
      err;

    // remove added slash
    if (slashAdded) {
      req.url = req.url.substr(1);
      slashAdded = false;
    }

    // restore altered req.url
    if(removed.length ! = =0) {
      req.baseUrl = parentUrl;
      req.url = protohost + removed + req.url.substr(protohost.length);
      removed = ' ';
    }

    // signal to exit router
    if (layerError === 'router') {
      setImmediate(done, null)
      return
    }

    // no more matching layers
    if (idx >= stack.length) {
      setImmediate(done, layerError);
      return;
    }

    // get pathname of request
    var path = getPathname(req);
    if (path == null) {
      return done(layerError);
    }

    // find next matching layer
    var layer;
    var match;
    var route;

    while(match ! = =true && idx < stack.length) {
      layer = stack[idx++];
      match = matchLayer(layer, path);
      route = layer.route;

      if (typeofmatch ! = ='boolean') {
        // hold on to layerError
        layerError = layerError || match;
      }

      if(match ! = =true) {
        continue;
      }

      if(! route) {// process non-route handlers normally
        continue;
      }

      if (layerError) {
        // routes do not match with a pending error
        match = false;
        continue;
      }

      var method = req.method;
      var has_method = route._handles_method(method);

      // build up automatic options response
      if(! has_method && method ==='OPTIONS') {
        appendMethods(options, route._options());
      }

      // don't even bother matching route
      if(! has_method && method ! = ='HEAD') {
        match = false;
        continue; }}// no match
    if(match ! = =true) {
      return done(layerError);
    }

    // store route for dispatch on change
    if (route) {
      req.route = route;
    }

    // Capture one-time layer values
    req.params = self.mergeParams ?
      mergeParams(layer.params, parentParams) :
      layer.params;
    var layerPath = layer.path;

    // this should be done for the layer
    self.process_params(layer, paramcalled, req, res, function (err) {
      if (err) {
        return next(layerError || err);
      }

      if (route) {
        returnlayer.handle_request(req, res, next); } trim_prefix(layer, layerError, layerPath, path); }); }}Copy the code

The function of this closure is to save some request-level global variables, such as params, baseUrl, etc., and then get the path of the request to the stack for layer matching. If the path is matched, the router will be able to perform the layer matching. If there is no match, execute done (finalHandle) and pass next, because next is a closure function that references variables defined in router.handle. If next is called, it is equivalent to, Giving control back to router.handle and continuing to match the next one in router.stack is how middleware works.

In general, the process is the same as above, but there will be different handle functions due to different types of layer. For example, some handle is middleware and some handle is Route. dipatch. What will happen if the latter is matched?

Route. dispatch will be executed when route.dispatch is matched with route.dispatch at the layer of route. stack. Method and path are used to match each layer in the stack. If a layer does not match, the method and path are returned to the router.stack.


conclusion

Express is mainly composed of APP, Router, Route and Layer. App is responsible for exposing interfaces and managing global attributes such as router and view template. Router has its own stack and is responsible for scheduling global middleware layer and Dispatch layer. Route manages the middleware layer that specifies the request method. Layer stands for middleware, but there are three types: global, Dispatch, and Handle, of which the Handle Layer is the one we usually write

router.get('/'.function(req,res,next) = >{})Copy the code

Of course, this can be considered middleware, but it usually returns HTTP responses here, hence the handle Layer.

That’s the end of the global overview, and the next article on route matching will delve into the details of request arrival, path matching, and dynamic params. Stay tuned!