nodejs入门实战教程(01)——从上传实例出发

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 。

把server.js变成NodeJs模块

(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应用的不同部分放入不同的文件中,即便于管理有提升了开发效率和程序性能。

处理不同的HTTP请求——路由选择

路由模块

(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请求,并不会产生“阻塞”。

如图:

nodejs入门实战教程(01)——从上传实例出发_第1张图片

OK,以上我们大体上把服务器、路由、请求处理程序的工作方式与思想完成了。下面,我们来做一个“图片上传并显示”的实际案例吧,切身感受以下nodejs异步编程、单线程并行处理的快感吧~~~

案例-文本提交并显示

先从显示一个简单的文本区表单入手:

处理POST请求

(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 ,你会看到表单显示在浏览器上了吧~~哈哈

如图:

nodejs入门实战教程(01)——从上传实例出发_第2张图片

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中执行表单提交操作,看看结果是否符合你的心意~
如图:

nodejs入门实战教程(01)——从上传实例出发_第3张图片

nodejs入门实战教程(01)——从上传实例出发_第4张图片

于是,我们就完成了这样一个文本表单提交的案例。爽吧~

案例-图片上传并显示

其实,图片上传本质上还是处理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对象,获取到表单中需要的数据字段。

那么,我们怎么把它应用到我们的案例中呢?

(4)formidable在项目中的应用

问题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)”:切入正题

问题(2):上传文件,然后显示

方案:

  1. 在/start表单中添加一个文件上传元素;
  2. 将node-formidable整到upload请求处理程序只能怪,用于将上传的图片保存到/tmp/目录下;
  3. 将上传的图片嵌到/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的图片。
如图:

nodejs入门实战教程(01)——从上传实例出发_第5张图片

现在我们再次执行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/

你可能感兴趣的:(前端,HTTP服务器,nodejs,上传,实战入门教程)