Introduction
In web development, a simplified processing process is: the client initiates a request, then the server processes the request, and finally returns the relevant data. Regardless of the language or framework, the simplified model is the same except for details. To initiate a request, the client first needs an identifier, usually a URL, which sends the request to a specific processing program on the server. In this process, the request may undergo a series of global processing, such as authentication, authorization, and URL Parsing. Then, locate a processing program to process the service, and finally return the generated data to the client, the client combines the data with the view template to display the appropriate style. This process involves many modules. This article only explores the first half, that is, the process from client requests to server-side processing programs, it can also be called routing (in fact, it is how to locate the server processing program ).
For comparison, we will briefly introduce how Asp.net webform and Asp.net MVC implement routing.
Asp.net webform is special. Due to the PostBack principle, the process of locating the processing program is different from that of MVC. There is no strict requirement on the URL format, and the URL directly corresponds to the background file, the server form in aspx is sent to the corresponding aspx by default. CS file, which is located by using the two hidden domains (_ eventtarget and _ eventargument) and ipostbackeventhandler interfaces in the ASPX page, these two things can be directly located in a specific method, usually an event handler of a control. That is to say, in webform, the URL can only locate the request to the class. To locate the request to a real handler (method), other means are required.
Different from webform, Asp.net MVC does not correspond to backend files. Therefore, the URL must be resolved by some means. The background processing program in MVC is called action, which is located in the Controller class, to enable the URL to locate the action, the URL in MVC has strict format requirements. The URL must contain the controller and action, in this way, the backend can dynamically generate the Controller instance through reflection and then call the corresponding action. That is to say, in MVC, the background processing program is located based on URLs.
Through the analysis of the above two methods, we found that it doesn't matter whether the URL points to a file, but in the end, it is necessary to locate a specific processing program based on it, that is, there is a routing process between URLs and handler, but different frameworks have different processing methods. The routing process of the express framework is as follows:
Is that true?
Source code analysis
We know that when using Express, we can use the following method to register a route:
App. Get ("/", function (req, Res) {res. Send ("hello ");});
On the surface, the get method can associate the path in the URL with the background handler. To figure out this process, we can view the source code in the application. js file. The first time I glanced at it, I found that this method was not found in it. The app. Get (), app. Post () and so on were not found. I took a closer look and found the following method:
Methods. foreach (function (method) {app [Method] = function (PATH) {If ('get' = Method & 1 = arguments. length) return this. set (PATH); // get special processing. The app will be obtained when only one parameter exists. settings [path] This. lazyrouter (); var route = This. _ router. route (PATH); route [Method]. apply (route, slice. call (arguments, 1); // retrieve the second parameter, namely, the handler, and pass in the route [Method] method to return this ;};});
Originally, these methods were dynamically added. Methods is an array that stores a series of web request methods. The preceding method traverses it and adds a series of methods with the same name as the Request Method to the app, that is, app. get (), app. post (), app. put (), etc. In these methods, first instantiate a router object by calling lazyrouter, and then call this. _ router. the Route Method instantiates a route object, finally calls the route [Method] method, and passes in the corresponding handler to associate the path with the handler.
Pay attention to the following points in this method:
- The lazyrouter method will only instantiate the router object during the first call and assign it to the app. _ router field.
- Note the difference between the router and route. The router can be considered as a middleware container, which can not only store the routing middleware (route), but also store other middleware, after the router method is instantiated in the lazyrouter method, two middleware will be added: Query and init. The route is only the routing middleware and encapsulates the routing information. Both router and route maintain a stack array, which is used to store middleware and routes.
The routing container mentioned in this article represents "router/index. JS "file to the export object, routing middleware (route) represents" router/route. JS "File Export object, the app represents" application. JS.
The stack of the router and route is different. The difference is mainly reflected in the fact that the layer is used to encapsulate a Data Structure of the middleware,
Because router. the middleware stored in the stack includes but is not limited to the routing middleware. Only the execution of the routing middleware depends on the Request Method. Therefore, the router. the layer in the stack does not have the method attribute, but dynamically adds it (the layer definition does not contain the Method Field) to the route. stack layer; layer. the route field is also dynamically added. You can use this field to determine whether the middleware is a routing middleware.
You can add middleware in two ways: app. use and app [Method]. The former is used to add the non-routing middleware, and the latter is used to add the routing middleware. Both of these methods are implemented by calling the router method internally:
// Add non-routing Middleware
Proto. Use = function use (FN) {/* some code is omitted here */Callbacks. foreach (function (FN) {If (typeof FN! = 'Function') {Throw new typeerror ('router. use () requires middleware function but got a' + GetType (FN);} // Add the middleware debug ('use % S % s', path, fn. name | '<anonymous>'); // instantiate the layer object and initialize var layer = new layer (path, {sensitive: This. casesensitive, strict: false, end: false}, FN); // the value of this field is undefined layer. route = undefined; this. stack. push (layer) ;}, this); return this ;}; // Add the routing middleware Proto. route = function (PATH) {// instantiate the routing object var route = new route (PATH); // instantiate the layer object and initialize var layer = new layer (path, {sensitive: this. casesensitive, strict: This. strict, end: true}, route. dispatch. BIND (route); // point to the just-instantiated route object (very important). Use this field to associate the router and route to layer. route = route; this. stack. push (layer); Return route ;};
For routing middleware, the stack (router. stack. stack and route. the associated stack is associated. The associated schematic model is shown in:
During running, the routing container (router) has only one instance, and the routing middleware calls the app every time. route, app. use or app [Method] generates a route object. When routing middleware is added, the routing container is equivalent to a proxy, the router [Method] actually calls the route [Method] internally to add routes. The routing container has a route method, which is equivalent to creating a factory for the routing object. By adding middleware, requests are called one by one in the order they are added. In case of routing middleware, Handler stored in the stack array in the routing object is called one by one, this is the stream processing of Express. Is it a bit similar to the pipeline model in Asp.net? The Calling process is shown in:
Run the "express-e expresstest" command on the terminal (you need to install express and express-generator first), and then run the "expresstest/app. add the following code to the JS file:
// Add non-routing Middleware
App. use ('/test', function (req, res, next) {console. log ("app. use ('/test') handler1 "); next ()}, function (req, res, next) {console. log ("app. use ('/test') handler2 "); next ()}); var r = app. route ('/test'); // create a route object and add the routing middleware R through route [Method. get (function (req, res, next) {console. log ("route. get ('/test') handler1 "); next ();}). get (function (req, res, next) {console. log ("route. get ('/test') handler2 "); next ();});
/* You can directly input multiple functions in this way.
R. Get (function (req, res, next ){
Console. Log ("route. Get ('/test') handler1 ");
Next ();
}, Function (req, res, next ){
Console. Log ("route. Get ('/test') handler2 ");
Next ();
});
*/
/*
Or, you can directly input the function array, which can be a multi-dimensional array.
r.get([function(req,res,next){
console.log("route.get(‘/test‘) handler1");
next();
},[function(req,res,next){
console.log("route.get(‘/test‘) handler2");
next();
},function(req,res,next){
console.log("route.get(‘/test‘) handler3");
next();
}]]);
*/
App. get ('/test', function (req, res, next) {// Add the routing middleware console through APP [Method. log ("app. get ('/test') handler1 "); next ();}). get ('/test', function (req, Res ){
Console. Log ("app. Get ('/test') handler2 ");
Res. End ();
});
Enter "CD expresstest" and "NPM start" in the terminal to start Express, and then enter "http: // localhost: 3000/test" in the browser ", we found that the output content in the terminal is exactly the same as that in our previous analysis, as shown in:
In the example, we use the app [Method] and route [Method] Methods to add the routing middleware. From the source code, we can see that there is a big difference here, the app [Method] method has the following code: var route = This. _ router. route (PATH);, this. _ router. the route () method instantiates a route internally and returns the result. That is to say, each call to the app [Method] will recreate a new route object, the later processing program will be added to the stack of the new route object. Although routing middleware can be added through chained writing, however, each processing program is not in a stack (but this does not affect the execution of the Program). The route [Method] is different. After adding the routing middleware, the method returns itself, calling the method on a route object adds all the handlers to the stack of this object. However, you must manually instantiate a route object before using route [Method ]. . The Processing Method of the route [Method] method is different from that of the app [Method]. It can not only process multiple function parameters at the same time, but also use this code: var callbacks = utils. flatten ([]. slice. call (arguments); you can convert multiple-digit groups in arguments into one-dimensional arrays, which makes the input of parameters very flexible.
The addition of middleware mainly relies on application. JS, router/index. JS and router/route. JS, the export objects (app, router, and route) of these three files are called each other. From the require of the three files, the app depends on the router, and the router depends on the route, below is the app. use code:
App. use = function use (FN) {var offset = 0; // This variable is used to locate the start position of handler in arguments. When no path is input, handler is the first element of arguments, so it is 0 var Path = '/'; // when the path parameter is not input, the default value is "/" // default path to '/' // disambiguate app. use ([FN]) if (typeof FN! = 'Function') {var Arg = FN; while (array. isarray (ARG) & Arg. length! = 0) {// if the first parameter is an array, obtain the first element Arg = Arg [0];} // first Arg is the path if (typeof Arg! = 'Function') {// If Arg is not a function, use it as the path to process offset = 1; Path = FN ;}} var FNS = flatten (slice. call (arguments, offset); // retrieves the list of processing functions from the parameter if (FNS. length = 0) {Throw new typeerror ('app. use () requires middleware functions ');} // setup router this. lazyrouter (); // instantiate the router and assign it to this. _ router var router = This. _ router; FNS. foreach (function (FN) {// traverses the functions in the parameter and calls the router one by one. use, from this point we can see that app. use () You can input multiple functions and add them to the stack. // non-Express app if (! FN |! FN. Handle |! FN. set) {return router. use (path, FN);} debug ('. use app under % s', PATH); fn. mountpath = path; fn. parent = This; // restore. APP property on req and res router. use (path, function mounted_app (req, res, next) {var orig = req. APP; fn. handle (req, res, function (ERR) {req. _ PROTO _ = orig. request; Res. _ PROTO _ = orig. response; next (ERR) ;}); // mounted an app fn. emit ('mount', this) ;}, this); return this ;};
The Code shows that multiple functions can be input when app. Use is called. If a function is added to a specified path, the path must be the first parameter, for example:
app.use(‘/test‘,function(req,res,next){console.log("use1");next()},function(req,res,next){console.log("use2");next()});
App. Use uses this. _ router. Use to add non-routing middleware. This. _ router. the use code has been posted above, and the path judgment and app. use is the same as in the previous section. In this method, instantiate the layer and assign values, and then add this. _ router. stack.
The Code of APP [Method] has been mentioned above, so I will not talk about it here. The following is the execution process of APP. Use and app [Method]. We can see the links between the three files:
There is another note about the router. The constructor has the following code: router. _ PROTO _ = proto; the prototype of router is directed to the proto object through the _ PROTO _ attribute of the router to obtain the methods defined in Proto.
Summary
I have read so much. Let's summarize it.
- First of all, the routing diagram in the introduction is basically correct, but express must be added to various middleware, So path and handler are further encapsulated ), then save the layer in the router. stack Array.
- App. Use is used to add the non-routing middleware. app [Method] adds the routing middleware. The middleware needs to be added using the router and route. The app is equivalent to the facade and the add details are packaged.
- The router can be seen as a container that stores middleware. For the routing middleware in it, router. the layer in the stack has a route attribute pointing to the corresponding routing object, so that the router. stack and route. stack Association, You can traverse each processing program of the route object through the router.
Analysis of Its Routing Mechanism from the express source code