OK,今天开始我们学习nodejs,关于node的强大和流行我在这里就不再阐述了。有人说js正在吞食整个web技术链。我们先不验证该说法是否可信,但是你要相信前端在web的世界地位将越走越宽并占据更加重要的地位!
开门见山,这就来进入NodeJs的世界:
(1)要实现一个web页面,首先需要一个http服务器;
(2)响应不同的请求,根据请求的URL,我们的服务器需要给予不同的响应——需要一个路由——用于把请求对应到请求处理程序
(3)路由——还应该能处理post数据,并且把数据封装成更友好的格式传递给请求处理程序——即,需要请求数据处理功能;
(4)当请求被服务器接收并通过路由传递之后,需要可以对其进行处理——需要最终的请求处理程序;
(5)需要把请求后的数据内容显示出来——需要一些视图逻辑供请求吹里程序使用——将内容发送给用户浏览器
(6)上传图片——需要上传处理功能
做法:
1.http服务器
注意:为保持代码的可读性,应该把不同功能的代码放入不同的模块中,保持代码分离;
方案:使用一个主文件,它可以被nodejs执行,同时建立不同功能的模块,这些模块可以被主文件和其他模块调用。
这里,我们把主文件命名为index.js,把服务器模块命名为server.js;
首先从server.js开始:
在项目的根目录下新建一个server.js,写入下面代码:
//放置http服务器模块
//建议一个HTTP服务器
var http=require('http');
http.createServer(function(request,response){
response.writeHead(200,{"Content-Type":"text/plain"});
response.write("Hello World");
response.end();
}).listen(8888);
然后再命令行中执行 node server.js
执行之后,我们在浏览器地址栏输入:localhost:8888 ,浏览器就会显示Hello World。
表示我们成功建立了一个HTTP服务器。
但是,到这里我们得先分析一下HTTP服务器:
(1)require()是nodejs自带的http模块;
(2)http模块有提供一个createServer()函数,该函数会返回一个对象,这个对象有一个叫listen的方法,该方法有一个参数——数值型,用于指定我们创建的这个HTTP服务器监听的端口号。
(3)在createServer()函数中,我们向该函数传递了一个“匿名函数”。
(4)与PHP不同,PHP对请求的处理——当有请求进入的时候,Apache服务器就为这个请求新建一个进程,并且开始从头到尾执行相应的PHP脚本。
nodejs中请求是异步的,请求可以在任何时候到达,并且服务器都只让这些请求跑在一条单进程中。
从(4)你可以看出,当请求数激增的时候,nodejs和php的性能明显区分开了。
(天猫前端Team在2015.11.11大规模应用了nodejs,性能比上一次提升了10倍)
(5)回调函数:我们向创建服务器的方法createServer()中传递了一个函数,无论什么时候,我们的服务器收到一个请求,这个函数就会被调用。——所以,我们现在能够明白,这个传递的函数是处理请求的地方!我们把这样的函数称作“回调函数”。
需要注意的是:
即使没有HTTP请求进来,我们的回调函数也没有被调用的情况下,我们的代码还会继续有效!
我们用一个例子说明:
var http=require('http');
function onRequest(request,response){
//request请求参数,response响应参数
console.log('接收到请求!');
response.writeHead(200,{"Content-Type":"text/plain"});
//当收到请求时,使用respnse.writeHead()发送一个HTTP状态,状态码为200和一个HTTP头的内容类型content-type
response.write("Hello World");
//使用response.write()在HTTP相应主体中发送文本“Hello World”
response.end();
//使用response.end()完成响应。
}
http.createServer(onRequest).listen(8888);
console.log('服务器已经启动!');
我们在命令行中执行下 node server.js,看看结果。
可以看到:
命令行输出“服务器已经启动!”;
我们再刷新一次localhost:8888,在命令行输出”接收到请求!”
如图:
注意:你可能发现,你只刷新了一次localhost:8888,但是命令行却输出了2次“接收到请求!”——原因是大多数服务器会访问localhost:8888的时候尝试读取localhost:8888/favicon.ico 。
(1)前面我们并没有把server.js变成一个nodejs模块,为了使server.js能被其他模块使用到,我们要把它做成nodejs模块。
例如:我们使用主文件index.js来调用server.js
注意:如var http=require(‘http’); 即把对模块的请求的返回值赋值给一个本地变量http,能使该变量成为一个拥有所有http这个模块所提供的公共方法的对象。
例如,我们使用http.createServer()调用了http模块的createServer()方法。
通常,我们起一个与模块同名的变量。
(2)把某段代码变成模块意味着我们需要把我们希望提供的功能部分(function) 导出 到请求这个模块的脚本中。
我们对上面server.js的代码做一次封装。如下:
var http=require('http');//使用内置nodejs内置的http模块
function start(){`
function onRequest(request,response){
console.log('接收到请求!');
response.writeHead(200,{"Content-Type":"text/plain"});
response.write("Hello World");
response.end();
}
http.createServer(onRequest).listen(8888);
console.log('服务器已经启动!');
}
exports.start=start;//将start这个功能开放出来,这样就能在index.js中使用它
在index.js中使用server.js:
var server=require("./server");//使用server模块
//调用server下的公共方法
server.start();
然后我们在命令行执行 node index.js 观察结果。这样一来,我们就可以在主文件(index.js)中启动我们的http服务啦~开森麽
提示:命令行 ctrl+c能够停止正在执行的任务。
这样一来,我们就可以结合模块化开发的思想,将我们的web应用的不同部分放入不同的文件中,即便于管理有提升了开发效率和程序性能。
(1)为路由提供请求的URL和其他需要的GET及POST参数,随后路由需要根据这些数据来执行相应的代码(实质上是处理程序)。
所以,为了要提供这些参数,我们就需要查看HTTP请求,从中提取出请求的URL以及GET/POST参数。
(2)以上我们所需要的参数&数据都包含在request对象中,该对象是onRequest()回调函数的第一个参数——request。
(3)为了解析这些数据,我们需要借助url和querystring模块。
url: 方法——url.parse(string).query和url.parse(string).pathname
querystring:方法——querystring(string)
做法:我们先来找出浏览器请求的URL路径——借助url模块
在server.js中写入如下:
var http=require('http');//使用nodejs内置的http模块
var url=require('url');//使用内置的url模块
function start(){
function onRequest(request,response){
var pathname=url.parse(request.url).pathname;
console.log('接收到'+pathname+'请求!');
response.writeHead(200,{"Content-Type":"text/plain"});
response.write("Hello NodeJS");
response.end();
}
http.createServer(onRequest).listen(8888);
console.log('服务器已经启动!');
}
exports.start=start;
这样一来,我们的应用可以通过具体请求的URL路径来区别不同的请求。
路由会将请求以URL路径为基准映射到相应的处理程序上去(代码段)。
例如,来自url:/start的请求映射到start这个处理程序上去;
来自url:upload的请求映射到upload这个处理程序上去;
如此,我们继续——使用路由来完成这样的映射。
(1)建立一个router.js
function route(pathname){
console.log("路由接收来自url:"<pathname>"的请求");
}
exports.route=route;
既然路由是接收来自服务器的请求,那么我们就需要将路由(router.js)与服务器(server.js)之间建立联系。我们使用“依赖注入”的方式添加路由模块。
(2)我们对server.js的代码做修改:(请注意差别)
var http=require('http');//使用nodejs内置的http模块
var url=require('url');//使用内置的url模块
function start(route){
function onRequest(request,response){
var pathname=url.parse(request.url).pathname;
console.log('接收到'+pathname+'请求!');
route(pathname);
response.writeHead(200,{"Content-Type":"text/plain"});
response.write("Hello NodeJS");
response.end();
}
http.createServer(onRequest).listen(8888);
console.log('服务器已经启动!');
}
exports.start=start;
然后在index.js中做修改:
var server=require("./server");//使用server模块
var router=require("./router");
//调用server下的公共方法
server.start(router.route);//将路由函数注入server.js`
OK,现在让我们再次在命令行中执行一次 node index.js。
首先,我们会看到命令行中输出了:
“服务器已经启动!”
然后,我们去刷新一下localhost:8888,会看到命令行又输出了:
“
接收到/请求!
路由接收来自url:/的请求
接收到/favicon.ico请求!
路由接收来自url:/favicon.ico的请求
”
如图:
这表明,我们的HTTP服务器已经在使用路由模块了。并且,发出的请求的URL路径会发送给路由。
现在,我们回到路由。
路由,指的是根据不同的URL,有不同的处理方式,但它并不是真正处理具体业务逻辑的模块。
所以,现在我们需要新的模块,或者说进入到处理程序中。目的就是为了将路由和处理程序联系起来,让路由“有路可寻”!
(1)新建一个requestHandlers.js
function start(){
console.log("处理'start'请求已被唤醒!");
}
function upload(){
console.log("处理'upload'请求已被唤醒!");
}
//开放API
exports.start=start;
exports.upload=upload;
(2)现在我们该想想如何在路由和处理程序之间架起“沟通的桥梁”了~
注入依赖吗?注入依赖可以让“路由”和“请求处理”程序之间的耦合更加松散,能让路由的重用性更高。
首先穿插一个关于js对象的知识点:
在C++或是C#、java 等面向对象的编程语言中,对象指的是类或是结构体的实例。对象根据他们实例化的模板,会拥有不同的属性和方法。
但,在js中,对象是一个键值对的集合。这个值可以是字符串、数字、函数等…
OK,介绍了上面的知识点,我们就需要使用对象+注入的方式建立沟通。
做法:
将一系列请求处理程序通过一个对象来传递,并且需要使用松耦合的方式将这个对象注入到route()函数中。
(1)修改index.js
var server=require("./server");//使用server模块
var router=require("./router");
var requestHandlers=require("./requestHandlers");
//建立一个处理请求的集合handle
var handle={}
//将不同的URL映射到相同的请求处理程序上,只需再对象中添加一个键为"/"的属性
//配置/和/start的请求交由requestHandlers.start来处理
handle["/"]=requestHandlers.start;
handle["/start"]=requestHandlers.start;
handle["/upload"]=requestHandlers.upload;
//调用server下的公共方法
server.start(router.route,handle);//将路由函数和处理请求对象handle注入server.js
(2)修改server.js如下:
因为我们在index.js中改变了server.start的传递参数。
//放置http服务器模块
//建议一个HTTP服务器`
var http=require('http');//使用nodejs内置的http模块
var url=require('url');//使用内置的url模块`
function start(route,handle){
function onRequest(request,response){
var pathname=url.parse(request.url).pathname;
console.log('接收到'+pathname+'请求!');
route(handle,pathname);
response.writeHead(200,{"Content-Type":"text/plain"});
response.write("Hello NodeJS");
response.end();
}
http.createServer(onRequest).listen(8888);
console.log('服务器已经启动!');
}
exports.start=start;
(3)在router.js做相应修改:
function route(handle,pathname){
console.log("路由接收来自url:"<pathname>"的请求");
//检查给定的路径对应的请求处理程序是否存在
if(typeof handle[pathname]==='function'){
//存在,直接调用相应的函数
handle[pathname];//从传递对象中获取请求处理函数,参照index.js
}else{
console.log('对于'<pathname>'没有找到相应的处理程序!');
}
}
exports.route=route;
这样,我们就将server.js,router.js , requestHandlers.js联系起来了。
在命令行中执行 node index.js 会看到输出。
并且,在浏览器测试不同的请求也会在命令行相应的输出:
例如:locahost:8888/start 和 localhost:8888/都是输出相同的;
localhost:8888/upload也会输出对应的结果。
如图:
我们也用一张图了理清服务器&路由&请求处理程序的关联:
正如前面所见,在浏览中显示的是来自server.js的响应。其实,对于浏览器显示的视图应该交由请求处理程序来做。
所以,请求处理程序要完成的是server.js中onRequest()函数的功能。
首先,我们穿插一个知识点:
Node.js可以在不新增额外线程的情况下,依然可以对任务进行并行处理;
Node.js是单线程的,它通过事件轮询来实现并行操作。
所以,在nodejs 模块中,我们应该尽量避免“阻塞”操作,因为一个模块阻塞了就会影响到下一个要执行的模块。
OK,那么我们采用“非阻塞”操作。
“非阻塞”操作,是使用回调,通过将回调函数作为参数传递给其他需要花时间做处理的函数,例如“查询数据库函数searchDB()”。
这个searchDB()函数在查询的时候,nodejs线程不会等待它处理完成,而是继续向 下处理后面的代码段。但是,searchDB()函数需要提供一个回调函数 callbackFunction(),等它查询呢完成之后,nodejs线程就会去调用这个 callbackFunction()函数。
“非阻塞”操作——exec(),来自模块child_process
方案:函数传递
将response对象(从服务器的回调桉树onRequest()获取)通过请求路由传递给请求处理程序。处理程序就可以采用该对象上的函数来对请求作出响应。
(1)修改server.js
var http=require('http');//使用nodejs内置的http模块
var url=require('url');//使用内置的url模块
function start(route,handle){
function onRequest(request,response){
var pathname=url.parse(request.url).pathname;
console.log('接收到'+pathname+'请求!');
// 将onRequest中所有关于response的操作交由route()完成
route(handle,pathname,response);
//以下关于response的操作已经交由route()来做了
// response.writeHead(200,{"Content-Type":"text/plain"});
// response.write("Hello NodeJS");
// response.end();
}
http.createServer(onRequest).listen(8888);
console.log('服务器已经启动!');
}
exports.start=start;
(2)修改route.js
function route(handle,pathname,response){
console.log("路由接收来自url:"+pathname+"的请求");
//检查给定的路径对应的请求处理程序是否存在
if(typeof handle[pathname]==='function'){
//存在,直接调用相应的函数
handle[pathname](response);//从传递对象中获取请求处理函数,参照index.js
}else{
console.log('对于'+pathname+'没有找到相应的处理程序!');
//处理response操作
response.writeHead(404,{"Content-Type":"text/plain"});
response.write("页面从地球上消失了~")
}
}
exports.route=route;
(3)修requestHandler.js
var exec=require("child_process").exec;//获取child_process模块的exec()方法
//在处理程序中,接收了response参数,对请求作出直接的响应。
function start(response){
console.log("处理'start'请求已被唤醒!");
exec("ls -lah",function(error,stdout,stderr){
response.writeHead(200,{"Content-Type":"text/plain"});
response.write(stdout);
response.end();
});
}
function upload(response){
console.log("处理'upload'请求已被唤醒!");
response.writeHead(200,{"Content-Type":"text/plain"});
response.write("Hello NodeJS~I'm upload");
}
//开放API
exports.start=start;
exports.upload=upload;
OK,让我们再次在命令行执行 node index.js
你将会看到执行localhost:/start的时候,再去执行localhost:/upload请求,并不会产生“阻塞”。
如图:
OK,以上我们大体上把服务器、路由、请求处理程序的工作方式与思想完成了。下面,我们来做一个“图片上传并显示”的实际案例吧,切身感受以下nodejs异步编程、单线程并行处理的快感吧~~~
先从显示一个简单的文本区表单入手:
(1)使用start处理请求程序来生产带文本区的表单。
修改requestHandlers.js:
//在处理程序中,接收了response参数,对请求作出直接的响应。
function start(response){`
console.log("处理'start'请求已被唤醒!"); var body='<html>'+ '<head>'+ '<meta http-equiv="Content-Type" content="text/html:'+
'charset=UTF-8" />'+ '</head>'+ '<body>'+ '<form action="/upload" method="post">'+ '<textarea name="text" rows="20" cols="60"></textarea>'+ '<input type="submit" value="提交" />'+ '</form>'+ '</body>'+ '</html>'; response.writeHead(200,{"Content-Type":"text/html"}); response.write(body); response.end(); } function upload(response){ console.log("处理'upload'请求已被唤醒!"); response.writeHead(200,{"Content-Type":"text/plain"}); response.write("Hello NodeJS~I'm upload"); response.end(); } //开放API exports.start=start; exports.upload=upload;
然后 我们再次在命令行中执行 node index.js。
然后浏览器刷新请求localhost:8888/start ,你会看到表单显示在浏览器上了吧~~哈哈
如图:
OK ,现在我们有了一个表单。但是提交这个请求还需要upload处理呢~接着往下走
(2)现在我们要处理“当用户提交表单时,触发/upload请求处理程序来处理POST请求”。
我们采用“非阻塞(异步回调)”的方式处理:
首先,你需要明白的是,Nodejs为了使真个过程非阻塞,会将POST数据拆分成很多个小的数据块,然后通过触发特定的事件,将这些小数据块传递给回调函数。
这里**特定的事件有data事件——表示新的小数据块到达了,以及end事件——表示所有的数据都接收完毕了。
通常,为了告诉Nodejs哪些事件触发的时候需要回调哪些函数,我们通过在request对象上注册监听器listener来实现。
request对象在每次接收到HTTP请求的时候,都会把该对象传递给onRequest()回调函数。**
具体方案:
将data和end事件的回调函数直接交由服务器处理,在data事件回调中收集所有的POST数据,当所有数据接收完毕之后,触发end事件,并且end事件的回调函数调用“请求路由”,并将数据传递给请求路由。然后,请求路由将数据传递给请求处理程序。
相信,经过前面的学习,你对这套流程很熟悉了。
(1)修改server.js
var http=require('http');//使用nodejs内置的http模块
var url=require('url');//使用内置的url模块
function start(route,handle){
function onRequest(request,response){
var postData="";
var pathname=url.parse(request.url).pathname;
console.log('接收到'+pathname+'请求!');
//设置接收数据的编码格式为utf-8
request.setEncoding("utf8");
//给request注册data事件
request.addListener("data",function(postDataChunk){
postData+=postDataChunk;
console.log("接收POST过来的数据块"+postDataChunk+".");
});
//给request注册end事件,end事件只会触发一次
request.addListener("end",function(){
//将POST数据传递给请求路由
route(handle,pathname,response,postData);
});
}
http.createServer(onRequest).listen(8888);
console.log('服务器已经启动!');
}
exports.start=start;
(2)修改router.js
为了能在/upload展示提交的文本内容,我们需要将postData传递给请求处理程序。
function route(handle,pathname,response,postData){
console.log("路由接收来自url:"<pathname>"的请求");
//检查给定的路径对应的请求处理程序是否存在
if(typeof handle[pathname]==='function'){
//存在,直接调用相应的函数
handle[pathname];//从传递对象中获取请求处理函数,参照index.js
}else{
console.log('对于'<pathname>'没有找到相应的处理程序!');
//处理response操作
response.writeHead(404,{"Content-Type":"text/plain"});
response.write("页面从地球上消失了~")
response.end();
}
}
exports.route=route;
(3)修改requestHandlers.js
//在处理程序中,接收了response参数,对请求作出直接的响应。
function start(response){`
console.log("处理'start'请求已被唤醒!"); var body='<html>'+ '<head>'+ '<meta http-equiv="Content-Type" content="text/html:'+
'charset=UTF-8" />'+ '</head>'+ '<body>'+ '<form action="/upload" method="post">'+ '<textarea name="text" rows="20" cols="60"></textarea>'+ '<input type="submit" value="提交" />'+ '</form>'+ '</body>'+ '</html>'; response.writeHead(200,{"Content-Type":"text/html"}); response.write(body); response.end(); } //将数据postData包含在对upload请求的响应中 function upload(response,postData){ console.log("处理'upload'请求已被唤醒!"); response.writeHead(200,{"Content-Type":"text/plain"}); // response.write("Hello NodeJS~I'm upload"); response.write("提交的文本:"+postData); response.end(); } //开放API exports.start=start; exports.upload=upload;
但是,前面我们是把请求的整个消息体传递给了请求路由和请求处理程序。
实际上,我们应该把POST数据中需要的数据传递过去,例如text字段。
解决方案:querystring模块
修改requestHandlers.js如下:
var querystring=require("querystring");//获取querystring模块
//在处理程序中,接收了response参数,对请求作出直接的响应。
function start(response){`
console.log("处理'start'请求已被唤醒!"); var body='<html>'+ '<head>'+ '<meta http-equiv="Content-Type" content="text/html:'+
'charset=UTF-8" />'+ '</head>'+ '<body>'+ '<form action="/upload" method="post">'+ '<textarea name="text" rows="20" cols="60"></textarea>'+ '<input type="submit" value="提交" />'+ '</form>'+ '</body>'+ '</html>'; response.writeHead(200,{"Content-Type":"text/html"}); response.write(body); response.end(); } function upload(response,postData){ response.setHeader('Content-Type','text/ javascript;charset=UTF-8');
console.log("处理'upload'请求已被唤醒!"); response.writeHead(200,{"Content-Type":"text/html"}); response.write('<head><meta charset="utf-8" /></head>'); //使用querystring模块的parse方法获取POST数据中得text字段 response.write("提交的文本:"+querystring.parse(postData).text); response.end(); } //开放API exports.start=start; exports.upload=upload;
OK,现在我们再次在命令行执行 node index.js
然后在浏览器localhost:8888/start中执行表单提交操作,看看结果是否符合你的心意~
如图:
于是,我们就完成了这样一个文本表单提交的案例。爽吧~
其实,图片上传本质上还是处理POST数据。
在这个案例中,我们将利用现成的node-formidable模块,因为该模块为文件上传提供了一套很不错的处理方案。大家可以先看看这篇关于node-formidable的详解https://cnodejs.org/topic/4f16442ccae1f4aa2700104d
OK,我们开始。
(1)安装node-formidable模块
命令:npm install formidable
如下,安装成功将在命令行出现下列信息:
[email protected] node_modules/formidable
(2)引入formidable模块
前面我们都是在使用node的内部模块,其实外部模块的使用方式也大相径庭。
需要用require()将该模块引入。
例如:
var formidable=require(“formidable”);
啰嗦一句:使用模块就如使用API插件一样。就如在java中一旦遇到一些不好处理的就去找API解决;这也很像我们使用某个js或是jquery插件一样。
(3)使用formidable模块
你可以先看看formidable模块的官方例子:
var formidable = require('formidable'), http = require('http'),
sys = require('sys');
http.createServer(function(req, res) {
if (req.url == '/upload' && req.method.toLowerCase() == 'post') {
// parse a file upload
var form = new formidable.IncomingForm(); form.parse(req, function(err, fields, files) {
res.writeHead(200, {'content-type': 'text/plain'}); res.write('received upload:\n\n'); res.end(sys.inspect({fields: fields, files: files}));
});
return; }
// show a file upload form
res.writeHead(200, {'content-type': 'text/html'}); res.end(
'<form action="/upload" enctype="multipart/form-data" '+ 'method="post">'+
'<input type="text" name="title"><br>'+
'<input type="file" name="upload" multiple="multiple"><br>'+ '<input type="submit" value="Upload">'+
'</form>'
);
}).listen(8888);
在这个例子中,我们需要创建一个IncomingForm,这个东西是对提交表单的抽象表示,通过它可以解析request对象,获取到表单中需要的数据字段。
那么,我们怎么把它应用到我们的案例中呢?
问题1:如何将本地文件在浏览器中显示?
方案:使用node内置的fs模块,将文件读取到我们的服务器中。
添加/showURL的请求处理程序,该处理程序直接硬编码将文件/tmp/test.png内容展示到浏览器中。
具体做法:
现在根目录下新建一个tmp/test.png的路径文件;
然后,修改requestHandler.js :
var querystring=require("querystring");//获取querystring模块
fs=require("fs");//获取node内置模块fs
//在处理程序中,接收了response参数,对请求作出直接的响应。
function start(response,postData){
console.log("处理'start'请求已被唤醒!");
var body='<html>'+
'<head>'+
'<meta http-equiv="Content-Type" content="text/html:'+
'charset=UTF-8" />'+
'</head>'+
'<body>'+
'<form action="/upload" method="post">'+
'<textarea name="text" rows="20" cols="60"></textarea>'+
'<input type="submit" value="提交" />'+
'</form>'+
'</body>'+
'</html>';
response.writeHead(200,{"Content-Type":"text/html"});
response.write(body);
response.end();
}
function upload(response,postData){`
response.setHeader('Content-Type','text/javascript;charset=UTF-8');
console.log("处理'upload'请求已被唤醒!");
response.writeHead(200,{"Content-Type":"text/html"});
response.write('<head><meta charset="utf-8" /></head>');
//使用querystring模块的parse方法获取POST数据中得text字段
response.write("提交的文本:"+querystring.parse(postData).text);
response.end();
}
//显示文件处理请求程序
function show(response,postData) {`
console.log("处理show请求程序已被唤醒!");
fs.readFile("./tmp/test.png","binary",function(error,file){
if(error){
response.writeHead(500,{"Content-Type":"text/plain"});
response.write(error+"\n");
response.end();
}else {
response.writeHead(200,{"Content-Type":"image/png"});
response.write(file,"binary");
response.end();
}
});
}
//开放API
exports.start=start;
exports.upload=upload;
exports.show=show;
修改index.js如下:将show这个请求处理程序添加到路由映射表中
var server=require("./server");//使用server模块
var router=require("./router");
var requestHandlers=require("./requestHandlers");
//建立一个处理请求的集合handle
var handle={}
//将不同的URL映射到相同的请求处理程序上,只需再对象中添加一个键为"/"的属性
//配置/和/start的请求交由requestHandlers.start来处理
handle["/"]=requestHandlers.start;
handle["/start"]=requestHandlers.start;
handle["/upload"]=requestHandlers.upload;
handle["/show"]=requestHandlers.show;
//调用server下的公共方法
server.start(router.route,handle);//将路由函数和处理请求对象handle注入server.js
OK ,现在我们在命令行执行node index.js;
然后,在浏览器地址栏输入 localhost:8888/show 看看有什么吧~
哈哈~我们看到在./tmp下的文件test.png显示在浏览器啦。
如图:
OK,我们成功解决了“问题(1)”。
问题(2):上传文件,然后显示
方案:
- 在/start表单中添加一个文件上传元素;
- 将node-formidable整到upload请求处理程序只能怪,用于将上传的图片保存到/tmp/目录下;
- 将上传的图片嵌到/uploadURL输出的HTML中;
(1)修改requestHandlers.js如下:
var querystring=require("querystring");//获取querystring模块
var fs=require("fs");//获取node内置模块fs
//在处理程序中,接收了response参数,对请求作出直接的响应。
function start(response,postData){
console.log("处理'start'请求已被唤醒!");
var body='<html>'+
'<head>'+
'<meta http-equiv="Content-Type" content="text/html:'+
'charset=UTF-8" />'+
'</head>'+
'<body>'+
'<form action="/upload" method="post" enctype="multipart/form-data">'+
'<input type="file" name="upload" multiple="multiple">'+
'<input type="submit" value="提交" />'+
'</form>'+
'</body>'+
'</html>';
response.writeHead(200,{"Content-Type":"text/html"});
response.write(body);
response.end();
}
function upload(response,postData){
response.setHeader('Content-Type','text/javascript;charset=UTF-8');
console.log("处理'upload'请求已被唤醒!");
response.writeHead(200,{"Content-Type":"text/html"});
response.write('<head><meta charset="utf-8" /></head>');
//使用querystring模块的parse方法获取POST数据中得text字段
response.write("提交的文本:"+querystring.parse(postData).text);
response.end();
}
//显示文件处理请求程序
function show(response,postData) {`
console.log("处理show请求程序已被唤醒!");
fs.readFile("./tmp/test.png","binary",function(error,file){
if(error){
response.writeHead(500,{"Content-Type":"text/plain"});
response.write(error+"\n");
response.end();
}else {
response.writeHead(200,{"Content-Type":"image/png"});
response.write(file,"binary");
response.end();
}
});
}
//开放API
exports.start=start;
exports.upload=upload;
exports.show=show;
(2)在server.js中移除对postData的处理,采用将request对象传递给请求路由的方式:
var http=require('http');//使用nodejs内置的http模块
var url=require('url');//使用内置的url模块
function start(route,handle){
function onRequest(request,response){
var pathname=url.parse(request.url).pathname;
console.log('接收到'<pathname>'请求!');
route(handle,pathname,response,request);
}
http.createServer(onRequest).listen(8888);
console.log('服务器已经启动!');
}
exports.start=start;
(3)相应的修改route.js
function route(handle,pathname,response,request){
console.log("路由接收来自url:"<pathname>"的请求");
//检查给定的路径对应的请求处理程序是否存在
if(typeof handle[pathname]==='function'){
//存在,直接调用相应的函数
handle[pathname];//从传递对象中获取请求处理函数,参照index.js
}else{
console.log('对于'<pathname>'没有找到相应的处理程序!');
//处理response操作
response.writeHead(404,{"Content-Type":"text/html"});
response.write("页面从地球上消失了~")
response.end();
}
}
exports.route=route;
(3)到这步,我们就可以上传并显示了,并且能把图片保存到/tmp目录下。
但是,我们如何将文件保存成test.png呢?
我们使用fs.renameSync(path1,path2)
的方式实现。
修改requestHandlers.js如下:
var querystring=require("querystring");//获取querystring模块
var fs=require("fs");//获取node内置模块fs
var formidable=require("formidable");//获取外部模块
//在处理程序中,接收了response参数,对请求作出直接的响应。
function start(response){
console.log("处理'start'请求已被唤醒!");
var body='<html>'+
'<head>'+
'<meta http-equiv="Content-Type" content="text/html:'+
'charset=UTF-8" />'+
'</head>'+
'<body>'+
'<form action="/upload" method="post" enctype="multipart/form-data">'+
'<input type="file" name="upload" multiple="multiple">'+
'<input type="submit" value="提交" />'+
'</form>'+
'</body>'+
'</html>';
response.writeHead(200,{"Content-Type":"text/html"});
response.write(body);
response.end();
}
function upload(response,request){
response.setHeader('Content-Type','text/javascript;charset=UTF-8');
console.log("处理'upload'请求已被唤醒!");
var form=new formidable.IncomingForm();
form.parse(request,function(error,fields,files){
console.log("解析完毕");
fs.renameSync(files.upload.path,"/tmp/test.png");//重命名
response.writeHead(200,{"Content-Type":"text/html"});
response.write("接收到得图像:");
response.write("<img src='/show'>");
response.end();
});
}
//显示文件处理请求程序
function show(response) {
console.log("处理show请求程序已被唤醒!");
fs.readFile("./tmp/test.png","binary",function(error,file){
if(error){
response.writeHead(500,{"Content-Type":"text/plain"});
response.write(error+"\n");
response.end();
}else {
response.writeHead(200,{"Content-Type":"image/png"});
response.write(file,"binary");
response.end();
}
});
}
//开放API
exports.start=start;
exports.upload=upload;
exports.show=show;
OK ,我们从本地上传一张test.png的图片。
如图:
现在我们再次执行node index.js
然后,在浏览器中输入localhost:8888/start 试试。
如果没错的话,你将在点击提交后,看到上传的图片。
但是,这里还有一个bug,很严重的bug,就是我们的文件必须与show()这个处理请求程序下要求的文件名同名同路径,这个是不符合实际应用的!!!
下一节中,我们试着来解决这个问题。这节就到这里!
https://github.com/nodejs/node/wiki
http://www.nodecloud.org/
https://cnodejs.org/
https://npm.org/