express路由子路由器
(自己动手:节点HTTP路由器)
Express是一个了不起JavaScript框架,可作为许多全栈Web应用程序的后端。 我们许多人每天都在使用它,并且精通如何 使用它,但可能对它的工作原理缺乏了解。 今天,无需深入研究Express源代码,我们将重新创建一些路由功能,以更好地了解框架的运行环境以及如何处理响应和请求。
如果您想查看最终的源代码,可以在Github上找到它。 请和我一起编写代码,以获得更好的学习体验!
入门
让我们从模拟Express的“ Hello World”应用程序开始 。 我们将略微修改它,因为我们不会引入express
,而是引入我们自己创建的模块。
首先,创建一个新的项目文件夹,并使用默认配置启动一个npm项目。
mkdir diy-node -router
cd diy- node -router
npm init -y
验证您的package.json
文件如下所示:
{"name" : "diy-node-router" ,
"version" : "1.0.0" ,
"description" : "" ,
"main" : "index.js" ,
"scripts" : {
"test" : "echo \"Error: no test specified\" && exit 1"
},
"author" : "" ,
"license" : "ISC"
}
接下来,我们将创建我们的index.js
文件。 在此文件中,我们将复制express
“ Hello World”示例,但引入我们自己的模块(我们将在短期内创建此模块)。
const router = require ( './src/diy-router' );
const app = router();
const port = 3000 ;
app.get( '/' , (req, res) => res.send( 'Hello World!' ));
app.listen(port, () => console .log( `Example app listening on port ${port} !` ));
这在本质上是一样的express
“Hello World”的例子例子。 基于此代码,我们知道router
模块应该是一个在调用时返回app
对象的函数。 这个对象应该有一个listen
方法来开始监听端口上的请求,还有一个get
方法来设置get
请求处理。 我们还将设置一个post
方法,因为我们最终希望我们的应用处理帖子。
搭建DIY路由器模块
现在,我们创建实际的路由器模块。 在新的src
目录中创建diy-router.js
文件。
mkdir src
cd src
touch diy-router.js
我们不想一次吃太多,所以让我们首先创建一个导出必要方法的模块。
module .exports = ( ( ) => {
const router = () => {
const get = ( route, handler ) => {
console .log( 'Get method called!' );
};
const listen = ( port, cb ) => {
console .log( 'Listen method called!' );
};
return {
get,
listen
};
};
return router;
})();
希望到目前为止,所有这些都有意义:我们创建了一个router
函数,该函数在被调用时返回get
和listen
方法。 此时,每个方法都将忽略其参数,而仅记录它已被调用的情况。 然后将此函数包装在立即调用函数表达式(IIFE)中。 如果您不熟悉我们为什么使用IIFE,我们这样做是为了保护数据隐私。 当我们拥有不想在模块本身之外公开的变量和函数时,接下来的步骤将更加明显。
此时,我们可以返回到根目录并使用node运行我们的应用程序。
node .
如果一切顺利,您将看到类似以下的输出:
Getmethod called !
Listen method called !
完美,一切都连在一起! 现在,让我们开始响应http请求提供内容。
处理HTTP请求
为了获得一些基本的HTTP请求处理功能,我们将node的内置http
模块引入到diy-router
。 http
模块具有createServer
方法,该方法带有带有请求和响应参数的函数。 每次将HTTP请求发送到listen
方法中指定的端口时,都会执行此函数。 下面的示例代码显示了如何使用http
模块在端口8080
上返回文本“ Hello World”。
http.createServer(( req, res ) => {
res.write( 'Hello World!' );
res.end();
}).listen( 8080 );
我们想在我们的模块中使用这种功能,但是我们需要让用户指定自己的端口。 此外,我们将要执行用户提供的回调函数。 让我们在diy-router
模块的listen
方法中使用此示例功能,并确保使用port和callback函数更加灵活。
const http = require ( 'http' );
module .exports = ( ( ) => {
const router = () => {
const get = ( route, handler ) => {
console .log( 'Get method called!' );
};
const listen = ( port, cb ) => {
http.createServer( ( req, res ) => {
res.write( 'Hello World!' );
res.end();
}).listen(port, cb);
};
return {
get,
listen
};
};
return router;
})();
让我们运行我们的应用程序,看看会发生什么。
node .
我们在控制台中看到以下内容:
Get method called!
Example app listening on port 3000!
这是一个好兆头。 让我们打开我们最喜欢的Web浏览器并导航到http:// localhost:3000 。
(简单的“ Hello World!”应用程序)
看起来不错! 现在,我们正在通过端口3000提供内容。这很好,但是我们仍然不提供与路由相关的内容。 例如,如果导航到http:// localhost:3000 / test-route,您将看到相同的“ Hello World!”。 信息。 在任何实际应用程序中,我们都希望我们为用户提供的内容取决于所提供URL中的内容。
添加和查找路线
我们需要能够向我们的应用程序添加任意数量的routes
,并在调用该路由时执行正确的路由处理程序功能。 为此,我们将一个routes数组添加到我们的模块中。 此外,我们将创建addRoute
和findRoute
函数。 名义上,代码可能看起来像这样:
let routes = [];
const addRoute = ( method, url, handler ) => {
routes.push({ method, url, handler });
};
const findRoute = ( method, url ) => {
return routes.find( route => route.method === method && route.url === url);
}
我们将使用get
和post
方法中的addRoute
方法。 findRoute
方法仅返回与提供的method
和url
匹配的routes
中的第一个元素。
在以下代码段中,我们添加了数组和两个函数。 另外,我们修改了get
方法并添加了post
方法,这两个方法都使用addRoute
函数将用户指定的路由添加到routes数组。
注意:由于只能在模块内访问routes
数组以及addRoute
和findRoute
方法,因此我们可以使用IIFE“显示模块”模式将它们暴露在模块外部。
const http = require ( 'http' );
module .exports = ( ( ) => {
let routes = [];
const addRoute = ( method, url, handler ) => {
routes.push({ method, url, handler });
};
const findRoute = ( method, url ) => {
return routes.find( route => route.method === method && route.url === url);
}
const router = () => {
const get = ( route, handler ) => addRoute( 'get' , route, handler);
const post = ( route, handler ) => addRoute( 'post' , route, handler);
const listen = ( port, cb ) => {
http.createServer( ( req, res ) => {
res.write( 'Hello World!' );
res.end();
}).listen(port, cb);
};
return {
get,
post,
listen
};
};
return router;
})();
最后,让我们在传递给createServer
方法的函数中使用findRoute
函数。 成功找到路由后,我们应调用与其关联的处理程序函数。 如果未找到该路由,则应返回404错误,指出未找到该路由。 该代码从概念上看起来将如下所示:
const method = req.method.toLowerCase();
const url = req.url.toLowerCase();
const found = findRoute(method, url);
if (found) {
return found.handler(req, res);
}
res.writeHead( 404 , { 'Content-Type' : 'text/plain' });
res.end( 'Route not found.' );
现在,将其合并到我们的模块中。 在此过程中,我们将添加一小段代码来为响应对象创建send
方法。
const http = require ( 'http' );
module .exports = ( ( ) => {
let routes = [];
const addRoute = ( method, url, handler ) => {
routes.push({ method, url, handler });
};
const findRoute = ( method, url ) => {
return routes.find( route => route.method === method && route.url === url);
}
const router = () => {
const get = ( route, handler ) => addRoute( 'get' , route, handler);
const post = ( route, handler ) => addRoute( 'post' , route, handler);
const listen = ( port, cb ) => {
http.createServer( ( req, res ) => {
const method = req.method.toLowerCase();
const url = req.url.toLowerCase();
const found = findRoute(method, url);
if (found) {
res.send = content => {
res.writeHead( 200 , { 'Content-Type' : 'text/plain' });
res.end(content);
};
return found.handler(req, res);
}
res.writeHead( 404 , { 'Content-Type' : 'text/plain' });
res.end( 'Route not found.' );
}).listen(port, cb);
};
return {
get,
post,
listen
};
};
return router;
})();
让我们看看这个动作! 同样,从根目录运行您的应用程序。
node .
您应该看到该应用程序正在端口3000上提供。在浏览器中,导航到http:// localhost:3000 。 您应该看到“ Hello World!” 但是现在,如果导航到http:// localhost:3000 / test-route ,应该会收到“找不到路由”消息。 成功!
现在我们要确认是否可以在应用程序中实际添加/test-route
作为路由。 在index.js
,设置此路由。
const router = require ( './src/diy-router' );
const app = router();
const port = 3000 ;
app.get( '/' , (req, res) => res.send( 'Hello World!' ));
app.get( '/test-route' , (req, res) => res.send( 'Testing testing' ));
app.listen(port, () => console .log( `Example app listening on port ${port} !` ));
重新启动服务器并导航到http:// localhost:3000 / test-route 。 如果看到“测试测试”,则说明您已成功设置路由!
注意:如果您有足够的乐趣,可以在这里结束! 这是路由的重要入门。 如果您想更深入一点并能够从我们的路线中提取参数,请继续阅读!
提取路线参数
在现实世界中,我们很可能在url字符串中包含参数。 例如,假设我们有一组用户,并且想要基于url字符串中的参数来获取用户。 我们的url字符串可能最终类似于/user/:username
,其中username
代表与用户关联的唯一标识。
要创建此功能,我们可以开发一些正则表达式规则以匹配任何url参数。 我不建议这样做,而是建议我们引入一个名为route-parser
的出色模块来为我们完成此任务。 route-parser
模块为具有match
方法的每条路由创建一个新对象,并match
了所有正则表达式魔术。要在我们的模块中进行所需的更改,请执行以下操作:
从命令行安装模块:
npmi route-parser
在diy-router.js
文件的顶部,需要该模块。
const Route = require( 'route-parser' );
在addRoute
函数中,而不是添加计划url字符串,而是添加Route
类的新实例。
const addRoute = ( method, url, handler ) => {
routes.push({ method, url : new Route(url), handler });
};
接下来,我们将更新findRoute
函数。 在此更新中,我们使用Route
对象的match
方法将提供的url与路由字符串进行匹配。 换句话说,导航到/user/johndoe
将匹配路由字符串/user/:username
。
如果找到匹配项,我们不仅要返回匹配项,还希望返回从URL中提取的参数。
const findRoute = ( method, url ) => {
const route = routes.find( route => {
return route.method === method && route.url.match(url);
});
if (!route) return null ;
return { handler : route.handler, params : route.url.match(url) };
};
要处理此新功能,我们需要重新访问传递给http.createServer
的函数中调用findRoute
的http.createServer
。 我们要确保路由中的所有参数都作为属性添加到请求对象上。
if (found) {
req.params = found.params;
res.send = content => {
res.writeHead( 200 , { 'Content-Type' : 'text/plain' });
res.end(content);
};
因此,我们的最终模块将如下所示:
const http = require ( 'http' );
const Route = require ( 'route-parser' );
module .exports = ( ( ) => {
let routes = [];
const addRoute = ( method, url, handler ) => {
routes.push({ method, url : new Route(url), handler });
};
const findRoute = ( method, url ) => {
const route = routes.find( route => {
return route.method === method && route.url.match(url);
});
if (!route) return null ;
return { handler : route.handler, params : route.url.match(url) };
};
const get = ( route, handler ) => addRoute( 'get' , route, handler);
const post = ( route, handler ) => addRoute( 'post' , route, handler);
const router = () => {
const listen = ( port, cb ) => {
http
.createServer( ( req, res ) => {
const method = req.method.toLowerCase();
const url = req.url.toLowerCase();
const found = findRoute(method, url);
if (found) {
req.params = found.params;
res.send = content => {
res.writeHead( 200 , { 'Content-Type' : 'text/plain' });
res.end(content);
};
return found.handler(req, res);
}
res.writeHead( 404 , { 'Content-Type' : 'text/plain' });
res.end( 'Route not found.' );
})
.listen(port, cb);
};
return {
get,
post,
listen
};
};
return router;
})();
让我们测试一下! 在我们的index.js
文件中,我们将添加一个新的用户端点,并查看是否可以通过更改url查询字符串在用户之间进行切换。 如下更改您的index.js
文件。 这将根据提供的请求的params
属性过滤user
数组。
const router = require ( './src/diy-router' );
const app = router();
const port = 3000 ;
app.get( '/' , (req, res) => res.send( 'Hello World!' ));
app.get( '/test-route' , (req, res) => res.send( 'Testing testing' ));
app.get( '/user/:username' , (req, res) => {
const users = [
{ username : 'johndoe' , name : 'John Doe' },
{ username : 'janesmith' , name : 'Jane Smith' }
];
const user = users.find( user => user.username === req.params.username);
res.send( `Hello, ${user.name} !` );
});
app.listen(port, () => console .log( `Example app listening on port ${port} !` ));
现在,重新启动您的应用程序。
node .
首先导航到http:// localhost:3000 / user / johndoe ,观察内容,然后导航到http:// localhost:3000 / user / janesmith 。 您应该分别收到以下答复:
Hello, John Doe!
Hello, Jane Smith!
最终密码
该项目的最终代码可以在Github上找到 。 感谢您的编码!
结论
在本文中,我们观察到,尽管Express是令人难以置信的工具,但我们可以通过实现自己的自定义模块来复制其路由功能。 进行这种练习确实有助于拉回“窗帘”,并使您意识到实际上并没有任何“魔术”。 话虽这么说,我绝对不建议为下一个Node项目滚动自己的框架!
像Express这样的框架之所以令人难以置信的原因之一是,它们得到了许多出色开发人员的广泛关注。 它们具有健壮的设计,并且比任何单个开发人员都可以部署的解决方案更高效,更安全。
那么您对这项练习有何看法? 这是否激发您尝试复制Express功能的其他方面? 在评论中让我知道!
express路由子路由器