This article, the second in a series, extends the functionality of the simple framework implemented in the previous article and separates routing from application for easy code maintenance and functionality expansion. In order to improve the efficiency of route matching, the routing module is further designed. Welcome to github address.
Confirm the demand
- Route and application are separated to facilitate code maintenance and function expansion
- Optimize routing modules to improve matching efficiency
The Router and Application are separated
To separate the Router from the application, we add a router. js file that encapsulates a Router.
// Routing management class
function Application() {
// An array to store routes
this.stack = [
{
path: The '*'.method: The '*'.handle: function(req, res) {
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end('404'); }}]; } Router.prototype.get =function(path, handle) {
// Push the request route onto the stack
this.stack.push({
path,
method: 'GET',
handle
});
};
Router.prototype.handle = function() {
// Loop through the request for an object to be put into the Router array. When the request method and path are consistent with the object, the callback handler method is executed
for (var i = 1, len = this.stack.length; i < len; i++) {
if (
(req.url === this.stack[i].path || this.stack[i].path === The '*') &&
(req.method === this.stack[i].method || this.stack[i].method === The '*')) {return this.stack[i].handle && this.stack[i].handle(req, res); }}return this.stack[0].handle && this.stack[0].handle(req, res);
};
Copy the code
Modify the original application.js file
var Router = require('./router');
var http = require('http');
function Application() {}
Application.prototype = {
router: new Router(),
get: function(path, fn) {
return this.stack.get(path, fn);
},
listen: function(port, cb) {
var self = this;
var server = http.createServer(function(req, res) {
if(! res.send) { res.send =function(body) {
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end(body);
};
}
return self.router.handle(req, res);
});
return server.listen.apply(server, arguments); }}; exports =module.exports = Application;
Copy the code
After the above modification, routing operations will only be related to the Router class itself, which achieves the purpose of separation from Application and makes the code structure clearer, facilitating the expansion of subsequent functions.
Optimize routing modules to improve matching efficiency
After the above implementation, the routing system can run normally. However, after in-depth analysis, we can find that our route matching implementation will have performance problems. When the number of routes increases, the this.stack array will continue to increase, and the matching efficiency will continue to decrease. In order to solve the matching efficiency problem, we need to carefully analyze the components of the route. It can be seen that a route consists of path, method and handle. The relationship between path and method is not simply one-to-one, but one-to-many. As shown in the following figure, for the same request link, functions similar to the following can be implemented according to the RestFul API specifications.
- Path: indicates the request path of the route
- Handle, representing the route’s handler (matching path only, handler if the request path is consistent)
- Route represents real routes, including Method and Handle, as shown in the following figure
--------------------------------------
| 0 | 1| -------------------------------------- | Layer | Layer | | |- path | |- path | | |- handle | |- handle | | |- route | | - the route | | | - method | | - method | | | - handle | | - method | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- inside the routerCopy the code
Create the Layer class and match the path
function Layer(path, fn) {
this.handle = fn;
this.name = fn.name || '<anonumous>';
this.path = path;
}
/** * Handle the request for the layer. * * @param {Request} req * @param {Response} res */
Layer.prototype.handle_request = function(req, res) {
var fn = this.handle;
if(fn) { fn(req, res); }};/** * Check if this route matches `path` * * @param {String} path * @return {Boolean} */
Layer.prototype.match = function(path) {
if (path === this.path || path === The '*') {
return true;
}
return false;
};
module.exports = Layer;
Copy the code
Modify the Router class to pass through the Layer wrapper
var Layer = require('./layer');
// Routing management class
function Router() {
// An array to store routes
this.stack = [
new Layer(The '*'.function(req, res) {
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end('404'); })]; } Router.prototype.get =function(path, handle) {
// Push the request route onto the stack
this.stack.push(new Layer(path, handle));
};
Router.prototype.handle = function(req, res) {
var self = this;
for (var i = 1, len = self.stack.length; i < len; i++) {
if (self.stack[i].match(req.url)) {
returnself.stack[i].handle_request(req, res); }}return self.stack[0].handle_request(req, res);
};
module.exports = Router;
Copy the code
Create a Route class that matches method
Create a Route class that matches the method in the Layer and executes the corresponding callback function. Only get requests are implemented here, which will be extended in future releases.
var Layer = require('./layer');
function Route (path) {
this.path = path;
this.stack = []; // Records routes with different methods on the same path
this.methods = {}; // Log the existence of the request mode
}
/** * Determine if the route handles a given method. * @private */
Route.prototype._handles_method = function (method) {
var name = method.toLowerCase();
return Boolean(this.methods[name]);
}
// Only the get method is implemented here
Route.prototype.get = function (fn) {
var layer = new Layer('/', fn);
layer.method = 'get';
this.methods['get'] = true;
this.stack.push(layer);
return this;
}
Route.prototype.dispatch = function(req, res) {
var self = this,
method = req.method.toLowerCase();
for(var i = 0, len = self.stack.length; i < len; i++) {
if(method === self.stack[i].method) {
returnself.stack[i].handle_request(req, res); }}}module.exports = Route;
Copy the code
Modify the Router class to integrate Route.
var Layer = require('./layer');
var Route = require('./route');
// Routing management class
function Router() {
// An array to store routes
this.stack = [
new Layer(The '*'.function(req, res) {
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end('404'); })]; } Router.prototype.get =function(path, handle) {
var route = this.route(path);
route.get(handle);
return this;
};
Router.prototype.route = function route(path) {
var route = new Route(path);
var layer = new Layer(path, function(req, res) {
route.dispatch(req, res);
});
layer.route = route;
this.stack.push(layer);
return route;
};
Router.prototype.handle = function(req, res) {
var self = this,
method = req.method;
for (var i = 1, len = self.stack.length; i < len; i++) {
if (self.stack[i].match(req.url) && self.stack[i].route && self.stack[i].route._handles_method(method)) {
returnself.stack[i].handle_request(req, res); }}return self.stack[0].handle_request(req, res);
};
module.exports = Router;
Copy the code
conclusion
Here we mainly create a complete routing system and introduce the concepts of Layer and Route based on the original code. The directory structure is as follows
Express | | - lib | | | | -- express. Js / / is responsible for the instantiation application object | | -- application. Js/app/package layer | | - the router | | | | - Index. Js / / Router class | | - layer. The js / / layer class | | - the route. The js / / the route class | | - test | | | | -- index. Js # test cases | | -- index. Js // Frame entryCopy the code
Application represents an application, and Express is responsible for instantiating the Application object. The Router represents the routing component and is responsible for the application’s entire routing system. The component is composed of a Layer array, each Layer represents a group of routing information with the same path, and the specific information is stored in the Route. Each Route is also a Layer object, but there are certain differences between the Layer inside the Route and the Layer inside the Router.
- The Layer inside the Router contains path and Route attributes
- When a request is made, each Layer of the router will be scanned first, and each Layer of the router will be compared first. If the URI is the same, each item of the Route will be scanned. If there is a match, specific information will be returned; if there is no match, the message is not found.