构建服务器
var http = require('http');
http.createServer(function(req,res){
req.writeHead(200,{
'Content-Type':'text/plain'
});
req.write('Hello World!');
req.end();
}).listen(8888);
注释:1.我们首先使用var http = require("http");
引入了nodejs的http模块,这个模块是nodejs官方就写好的一个模块,我们引用它的目的是为了使用 它的createServer
方法来创建一个server,这样我们可以很快地实现我们需要实现的功能。而createServer
方法返回一个对象,这个返回的对象有一个 可爱的函数叫listen,这个listen就是用来监听端口号的,它接受一个参数,也就是我们上面的代码所显示的那样,直接传入端口号8888
如果你想修改端口号, 请自行更改,但是本实验希望我们都还是坚守8888端口。2.req.writeHead()
函数发送一个HTTP状态(200)和HTTP头的内容类型(Content-Type),分别表示请求成功和需要返回的text
类型。req.write()
函数在HTTP相应主体中发送文本Hello World
,也就是我们需要显示的文本。req.end();
完成响应过程。
路由构建
路由构建的关键点:
- 获取用户输入的URL
- 服务器有多个事件处理方法,每个方法对应一个或一种URL格式
- 无法处理的URL应该抛出错误处理
var http = require('http');
var url = require('url');
function start(){
function onRequest(req,res){
var pathname = url.parse(req.url).pathname;//这里的req.url是我们传入的参数,我们只需要request的url部分
console.log('Request for' + pathneme+'received.');
}
//http.createServer(onRequest).listen(8888);
}
exports.start = start;
注释:在引入url模块后,我们使用url模块的parse方法来解析,parse方法会返回一个对象,利用该对象的pathname属性就可以找出用户输入的url。
路由事件处理
例:
//requestHandlers.js
function start(){
console.log('您访问的是/start');
}
function upload(){
console.log('您访问的是/upload');
}
exports.start = start;
exports.upload = upload;
这样我们可以访问http://localhost:8888/start
和http://localhost/upload
来执行上述两段代码。
但是这样写缺点也非常明显,随着请求处理程序的数量不断增加,我们当然不想每次有一个新的URL或请求处理程序时,都要为了在路由里完成请求而到处理程序重新写方法这样反复折腾。
于是,我们可以引用关联数组地方式来解决:我们将一系列请求处理程序通过一个对象来传递:
//index.js
var requestHanders = require('./requestHandlers');
var handle = {};
handle['/'] = requestHandlers.start;
handle['/start'] = requestHandlers.start;
handle['/upload'] = requestHandlers.upload;
server.start(router.route,handle);
这里我们就将handle作为我们的关联数组对象,我们声明以下三个来触发相应地事件处理程序:
handle['/'] = requestHandlers.start;
handle['/start'] = requestHandlers.start;
handle['/upload'] = requestHandlers.upload;
也就是分别对应触发requestHandlers中对应的方法。这样我们就可以在访问’/show’的时候:
handle[‘.show’] = requestHandlers.show;
最后我们将handle传给server的start方法,就是为了在server的start方法中使用route来处理handle,所以我们需要对server的start方法进行稍微的修改:
//server.js
function start(route,handle){
function onRequest(req,res){
route(handle,pahtname);
}
}
修改router.js
//router.js
function route(handle,pathname){
console.log('About to route a request for ' + pathname);
if(typeof handle[pathname] ==='function'){
handle[pathname]();
}else{
console.log('No request handler found for '+ pathname);
}
}
exports.route = route;
我们将handle对象作为参数传给服务器,再由路由接收,最后由路由来判断当前路径对应的请求处理程序存在否, 存在的话就调用对应的函数。
我们可以用从关联数组中获取元素一样的方式从传递的对象中获取请求处理函数, 因此就有了简洁流畅的形如handlepathname;的表达式。
这样一来,我们就可以根据不同请求作出不同的处理了。
阻塞操作
上述内容中,我们已经简单实现了一个node.js服务器。是这个服务器其实还存在一个比较大的隐患,很多时候我们 可能没有注意到由于nodejs的特殊性而造成的一些问题,接下来就来解决nodejs的阻塞操作问题。
那,什么是阻塞操作呢?我们简单举例:
在requestHandlers.js的start方法中,增加一个延迟加载内容的方法:
function start() {
function sleep(sleepTime) {
var startTime = new Date().getTime();
while (new Date().getTime() < startTime + sleepTime);
}
sleep(5000);
return "Hello Start";
}
重启服务器后,访问localhost:8888/upload,可以看到页面/upload马上就返回了。访问localhost:8888/start,可以看到我们需要等待5秒中,start页面才会返回结果给我们。
但是,当我们同时访问/start 和 /upload时,就会发现,/upload也出现了延迟5秒加载。
原因就是start()中的阻塞操作,阻塞了所有其他的处理工作。因为Node.js 是单线程的, 它可以在不新增额外线程的情况下对任务进行并行处理,所以,upload也受到了影响。
解决阻塞操作
- 使用child_process模块来处理,利用child_process的exec 来实现简单又实用的非阻塞操。
var exec = require("child_process").exec;
那么exec到底是做了什么事情呢?它其实就是从nodejs来执行一个shell命令, 然后,当/start 请求的时候将文件信息输出到浏览器中。
var content = "empty";
exec("ls -lah", function (error, stdout, stderr) {
content = stdout;
});
这段代码写在requestHandlers.js的start方法中,创建了一个新的变量content(初始值为empty),执行ls -lah命令 (我们用它来获取当前目录下所有的文件),将结果赋值给content,最后将content返回。
我们重新启动服务器,访问http://localhost:8888/start我们会发现页面输出的内容是empty。这就说明exec()发挥作用了, 有了它,我们可以执行shell操作而无需迫使我们的应用停下来等待该操作,虽然如此,但是页面输出的内容似乎不是我们想要的结果。这是因为:我们的代码是同步执行的,这就意味着在调用exec()
之后,nodejs会立即执行 return content
;
在这个时候,content
仍然是empty
,因为传递给exec()
的回调函数还未执行到――因为exec()
的操作是异步的。
此问题的解决方案:我们可以将response对象(从服务器的回调函数onRequest()获取)通过请求路由传递给请求处理程序。 随后,处理程序就可以采用该对象上的函数来对请求作出响应。所以先修改server.js中的onRequest的方法:
function onRequest(request, response) {
var pathname = url.parse(request.url).pathname;
route(handle, pathname, response);
其他代码不变,就把response作为第三个参数传给调用的route方法。并且,我们将onRequest()处理程序中所有有关 response的函数调都移除,因为我们希望这部分工作让route()函数来完成。紧接着我们修改router.js:
function route(handle, pathname, response) {
console.log("About to route a request for " + pathname);
if (typeof handle[pathname] === 'function') {
handle[pathname](response);
} else {
console.log("No request handler found for " + pathname);
response.writeHead(404, {"Content-Type": "text/plain"});
response.write("404 Not found");
response.end();
}
}
exports.route = route;
同样的模式:相对此前从请求处理程序中获取返回值,这次取而代之的是直接传递response对象。 如果没有对应的请求处理器处理,我们就直接返回“404”错误。由handlepathname得知 ,我们正常的请求处理就会在requestHandlers.js作出相应的修改:
function start(response) {
console.log("Request handler 'start' was called.");
exec("ls -lah", function (error, stdout, stderr) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.write(stdout);
response.end();
});
}
上面的start方法接收response参对象数,然后直接处理响应:
response.writeHead(200, {"Content-Type": "text/plain"});
response.write(stdout);
response.end();
之前我们都是直接将这些处理响应的代码放到server.js中的,而为了解决操作阻塞,我们将这些响应的 代码放到了exec的回调函数了,这样就不会影响到upload方法了,当然,我们也需要对upload方法进行相应的修改 ,跟start方法很类似,需要response参数,然后加上类似上面三行的响应处理代码:
function upload(response) {
console.log("Request handler 'upload' was called.");
response.writeHead(200, {"Content-Type": "text/plain"});
response.write("Hello Upload");
response.end();
}
但是这个时候,怎么证明/start处理程序中耗时的操作不会阻塞对/upload请求作出立即响应。 可以将requestHandlers.js的start方法修改为如下形式:
function start(response) {
console.log("Request handler 'start' was called.");
var content = "Hello Start";
exec("find /",
{ timeout: 10000, maxBuffer: 20000*1024 },
function (error, stdout, stderr) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.write(content);
response.end();
}
);
}
这样一来,当请求http://localhost:8888/start
的时候,会花10秒钟的时间才载入, 而当请求http://localhost:8888/upload
的时候, 会立即响应,纵然这个时候/start响应还在处理中。
上面这样写”find /”只是为了演示用,在各位的实际开发之中可以根据自己的需要来更改exec的代码逻辑。