读Node入门有感——>【2】构建基础的HTTP服务器
如何来进行请求的“路由”
我们要为路由提供请求的 URL和其他需要的GET及POST参数,随后路由需要根据这些数据来执行相应的代码(这里“代码”对应整个应用的第三部分:一系列在接收到请求时真正工作的处理程序)。
因此,我们需要查看HTTP请求,从中提取出请求的URL以及GET/POST参数。这一功能应当属于路由还是服务器(甚至作为一个模块自身的功能)确实值得探讨,但这里暂定其为我们的HTTP服务器的功能。
我们需要的所有数据都会包含在request对象中,该对象作为onRequest()回调函数的第一个参数传递。但是为了解析这些数据,我们需要额外的Node.JS模块,它们分别是url和querystring模块。
当然我们也可以用querystring模块来解析POST请求头中的参数。
现在我们来给onRequest()函数加上一些逻辑,用来找出浏览器请求的URL路径:
var http = require("http");
var url = require("url");
function start(){
function onRequest(request,response){
var pathname = url.parse(request.url).pathname;
console.log("Request for"+pathname+"received");
response.writeHead(200,{"Content-Type":"text/plain"});
response.write("Hello World");
response.end();
}
http.createServer(onRequest).listen(8888);
console.log("Server has started");
}
exports.start = start;
我们的应用现在可以通过请求的URL路径来区分不同请求了--这使得我们得以使用路由(还未完成)来将请求以URL路径为基准映射到处理程序上。
在我们要构建的应用中,这意味着来自/start和/upload的请求可以使用不同的代码来处理。稍后我们将看到这些内容是如何整合到一起的。
现在我们可以来编写路由了,建立一个名为router.js的文件,添加以下内容:
function route(pathname){
console.log("About to route a request for" + pathname);
}
exports.route = route;
如你所见,这段代码什么也没干,不过对于现在来说这是应该的。在添加更多的逻辑之前,我们先来看看如何把路由和服务器整合起来。
我们的服务器应当知道路由的存在并加以有效利用。我们当然可以通过硬编码的方式将这一依赖项绑定到服务器上,但是其他语言的编码经验告诉我们这将会是一件非常痛苦的事,因此我们将使用依赖注入的方式较松散地添加路由模块。
首先,我们来扩展以下服务器的start()函数,以便将路由函数作为参数传递过去:
var http = require("http");
var url = require("url");
function start(route){
function onRequest(request,response){
var pathname = url.parse(request.url).pathname;
console.log("Request for"+pathname+"received");
route(pathname);
response.writeHead(200,{"Content-Type":"text/plain"});
response.write("Hello World");
response.end();
}
http.createServer(onRequest).listen(8888);
console.log("Server has started");
}
exports.start = start;
同时,我们会相应扩展index.js,使得路由函数可以被注入到服务器中:
var server = require("./server");
var router = require("./router");
server.start(router.route);
在这里,我们传递的函数依旧什么也没做。
如果现在启动应用(node index.js,始终记得这个命令行),随后请求一个URL,你将会看到应用输出相应的信息,这表明我们的HTTP服务器已经在使用路由模块了,并会将请求的路径传递给路由:
路由给真正的请求处理程序
现在我们的HTTP服务器和请求路由模块已经如我们的期望,可以相互交流了。
在现在的是线下,路由过程会在路由模块中“结束”,并且路由模块并不是真正针对请求“采取行动”的模块,否则当我们的应用程序变得更为复杂时,将无法很好地扩展。
我们暂时把作为路由目标的函数称为请求处理程序。现在我们不要急着来开发路由模块,因为如果请求处理程序没有就绪的话,再怎么完善路由模块也没有多大意义。
应用程序需要新的部件,因此加入新的模块--已经无需为此感到新奇了。
创建一个requestHandlers的模块,并对于每一个请求处理程序,添加一个占位用函数,随后将这些函数作为模块的方法导出:
function start(){
console.log("Request handler 'start' was called");
}
function upload(){
console.log("Request handler 'upload' was called")
}
exports.start = start;
exports.upload = upload;
这样我们就可以把请求处理程序和路由模块连接起来,让路由“有路可寻”。
现在我们已经确定将这一系列请求处理程序通过一个对象来传递,并且需要使用松耦合的方式将这个对象注入到route()函数中。
我们先将这个对象引入到主文件index.js中:
var server = require("./server");
var router = require("./router");
var requestHandlers = require("./requestHandlers");
var handle = {}
handle["/"] = requestHandlers.start;
handle["/start"] = requestHandlers.start;
handle["/upload"] = requestHandlers.upload;
server.start(router.route,handle);
虽然handle并不仅仅是一个“东西”(一些请求处理程序的集合),我还是建议以一个动词作为其命名,这样可以让我们在路由中使用更流畅的表达式,稍后会有说明。
正如所见,将不同的URL映射到相同的请求处理程序上是很容易:只要在对象中添加一个键为“/”的属性,对应requestHandlers.start即可,这样我们就可以干净简洁地配置/start和/的请求都交由start这一处理程序处理。
在完成了对象的定义后,我们把它作为额外的参数传递给服务器,为此将server.js修改如下:
var http = require("http");
var url = require("url");
function start(route,handle){
function onRequest(request,response){
var pathname = url.parse(request.url).pathname;
console.log("Request for"+pathname+"received");
route(handle,pathname);
response.writeHead(200,{"Content-Type":"text/plain"});
response.write("Hello World");
response.end();
}
http.createServer(onRequest).listen(8888);
console.log("Server has started");
}
exports.start = start;
这样我们就在start()函数里添加了handle参数,并且把handle对象作为第一个参数传递给了route()回调函数。
然后我们相应地在route.js文件中修改route()函数:
function route(handle,pathname){
console.log("About to route a request for"+pathname);
if(typeof handle[pathname]==='function'){
handlepathname;
}else{
console.log("No request handler found for" + pathname);
}
}
exports.route = route;
通过以上代码,我们首先检查给定的路劲对应的请求处理程序是否存在,如果存在的话直接调用相应的函数。我们可以用从关联数组中获取元素应用的方式从传递对象中获取请求处理函数,因此就有了简洁流畅的形如handlepathname;的表达式。
有了这些,我们就把服务器、路由和请求处理程序在一起了。现在我们启动应用程序并在浏览器中访问http://localhost:8888/start,以下日志可以说明系统调用了正确的请求处理程序:
并且在浏览器中打开http://localhost:8888/可以看到这个请求同样被start请求处理程序处理了: