第一个实例-----我与node.js的第一步接触javascript
由于最近有东西须要用到node.js,因此我就在linux虚拟机上安装了node.js,对于javascript,也是第一次接触。 html
刚入门,就是一个实用的案例,毕竟这些东西都是实践出真知。这个案例就是一个web应用,容许用户上传图片并在当前网页显示出来。咱们来看看,这个案例是如何从一个简简单单的代码变成具备咱们上面功能的应用。java
咱们先看看如何在网页显示咱们输出的消息,这条消息天然就是咱们每一个程序员所写下的第一句话:“Hello Word”。是的,这句话已经成为咱们不管学习什么语言或是任何操做系统,都要写的第一句话,由于这句话说明咱们已经迈出了新世界的第一步!node
先上代码:linux
var http = require("http"); http.createServer(function(request, response) { response.writeHead(200, {"Content-Type": "text/plain"}); response.write("Hello World"); response.end();}).listen(8888);
这段简短的代码就已经告诉咱们如何在网页中显示消息。首先,是第一句,注意require()函数,这个函数是表示咱们要申请的模块,这里申请的是http模块并把它的返回值赋值给咱们声明的变量,这样咱们的变量就会获得http模块所提供的全部公共方法(将该变量命名为咱们所要申请的模块是一个好习惯,固然你彻底能够自定义)。什么是模块?咱们能够将模块理解为库或包,事实上,它也差很少正是这样,由于这些模块里面,封装着咱们想要使用的函数,接下来咱们还会学到,怎样建立本身的模块。好了,如今进入正题。也许有些人会问,什么是http模块?好吧,若是真的有人由于这个问题而没法前进,那么我就在这里讲一下,可是,我真的不是什么高手,javascript我只是稍微懂点基础罢了,你们只要将它简单认为是这样的东西,当咱们要在网页显示消息的时候,必定要发出一个请求,告诉服务器,我要在一个网页前显示消息,而服务器就会响应咱们的请求显示出来,因此,你们在接下来的代码中能够看到,http模块中的createServer()的参数是一个响应处理的函数(在javascript中,函数是能够做为参数的,由于函数自己是对象),这个函数须要两个参数,request(请求)和response(响应)。对于createServer()这个函数,它的参数是一个requestListener,就是自动被添加到request事件上的函数,而后返回一个网页服务对象,就是显示咱们消息的网页(它的做用就是创建一个能够响应请求的服务器)。咱们要想输出“Hello Word"这个消息,最主要的是在响应这方面下工夫。咱们先要设置一下咱们的消息的显示格式,好比在writeHead()函数中设置状态码和内容类型,这里是txt,因此就是“text/plain",而后就是用write()设定咱们要输出的消息,最后就是以end()来结束咱们此次的响应动做。不少人必定对这个函数有个疑问,就是那两个参数究竟是什么?好吧,这里我就稍微讲一下,第一个是状态码,表示网页服务器http响应状态的三位数字,像是咱们上面的200,就表示咱们的请求已成功,请求所但愿的响应头或数据体将随此响应返回,若是是404,就表示请求失败,请求所但愿获得的资源未被服务器发现,因此404这个状态码被普遍应用于当服务器不想揭示到底为什么请求被拒绝或者没有其余适合的响应可用的状况(如今你知道为何当咱们浏览的网页出现问题时会显示404了吧)。详细的状况请看这条连接:http://baike.baidu.com/view/1790469.htm。第二个是表示MIME类型,所谓的MIME类型,就是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开,多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式,像是图片,txt等等,这里的text/plain就表示普通文档txt,若是是png图片,就是image/png,更多的状况看下面这条连接:http://baike.baidu.com/view/160611.htmandroid
相信你们注意到了,最后的listen()这个监听器。只要有作过界面的人,像是搞过android控件的人,必定对监听器很是熟悉。这里就是监听咱们的端口号为8888的网页响应动做,只要咱们运行上面的代码,而后在咱们的浏览器中输入http://localhost:8888 /就会在当前的网页中看到"Hello Word"。程序员
值得注意的是,咱们这里有一个匿名函数,这个函数就是咱们处理响应和请求的实际处理函数,咱们能够将它们提取出来,像是下面这样:web
var http = require("http"); function onRequest(request, response){ response.writeHead(200, {"Content-Type":"text/plain"}); response.write("Hellod Word"); response.end(); } http.createServer(onRequest).listen(8888);
结果都是同样的,至于使用哪一种方式,就看我的须要,不过匿名函数的使用方式比较广泛。shell
为何要对咱们的处理函数进行监听呢?那是由于node.js是基于事件驱动的。好吧,这句话实在是太泛泛而谈,就像咱们的大学课堂老师,老是用似是而非的话来忽悠咱们(由于他们本身也是似是而非)。node.js的工做原理是这样的:除了咱们的代码,它的全部东西都是并行的!几乎全部的东西都是同时运行的!为何是除了咱们的代码呢?你能够这样想象,咱们的代码就比如是整个应用的国王,它一直都在昏昏欲睡,而后,它有一大帮手下,当一个手下向他询问任务的时候,它会醒过来,指示任务而后继续昏昏欲睡,在上一个手下在执行任务的时候,会有其余手下继续询问任务而且执行。问题,为何咱们的代码要"昏昏欲睡"呢?只要稍微明白并发的同窗就会明白,若是不这样作,就会出现同时多个手下询问任务的状况。这种状况很差吗?是的,很很差,由于这个国王并非一个多么能干的国王(请别介意,事实上咱们所写的代码真的绝大部分都是不"精明"的),因此,出现这种状况,它可能会出现错误,因此,一对一是最好的,并且这样的效率更高。而后,每一个手下在完成任务后就会向咱们的国王报告状况,这也是一对一的。因此,你们明白了没?(事实上,我也不是很明白,可是接触过并发,因此多多少少都能想象出来),因此,这里就有个监听器,这个监听器就是当咱们的任务完成时向咱们的国王进行报告。npm
关于这个例子还没完,由于这里有个重要的知识点必须讲清楚,就是回调。想象这种状况,咱们的服务器是跑在一个单线程里的(是的,node.js是单线程的,由于它已经将除了代码之外的操做都设置为并行的),可是咱们的服务器是要处理http请求的,这个请求但是任什么时候候都会过来,咱们的服务器可以刚好的响应吗?因而,回调就变得很是重要,就是咱们createServer()里的函数参数。不管什么时候,什么样的请求过来,只要它做为参数传进来,那么,咱们的服务器都能根据这个参数自动调用合适的函数,恰当的处理这个请求,这就是回调(不熟悉回调的话,你只要这么想,回调的英文就是callback,咱们的函数已经写好了,就像一个选秀选手在后台等着咱们的主持人叫他出来表演,当主持人拿到它的号码(参数),就会叫他,这就是回调)。因此,咱们的服务器是异步的。 就算咱们的回调函数没有被调动,其余代码仍是能够被执行的。就像这样:
function onRequest(request, response){ console.log("server has started"); .... }
这里只是在回调函数里添加一句:console.log("server has started")(若是不清楚这一句的话,就将它当成咱们javascript中的alert()),原本应该是不会执行的,若是是咱们平时的串行代码(就算是咱们之前写的并发也是不行的,除非是特殊的方法),可是,神奇的海螺小姐又出现了!它可以被显示出来!咱们要分清楚一个问题,就是咱们的请求真正想要触发的,是我添加的语句的下面,前面并非回调函数真正想要执行的部分(我用了“真正”这个词,由于这里必须跟你们说明一下,咱们这里处理的只是response的部分,我并无对request进行任何细节上的处理,因此,理论上,当任何请求过来的时候它都能被执行,至因而否可以被响应,则是看请求的类型),因此,当有请求过来的时候,就算它不能触发咱们的回调函数,咱们的代码只要可以执行,仍是会执行的,由于这时任何请求都能使它运行。 好了,咱们继续往下讲,前面的废话实在是太多了(请原谅我,由于我也是一只初学鸡,因此有些问题很你们同样,也是一头雾水,只能将本身知道的全部东西都倒出来了)。
咱们上面讲的,只是实现一个很是基础的http服务器,它还并非一个真正意义上的node.js模块,由于它不能被其余模块调用。那么,咱们接下来怎样让它变成真正的模块呢?就是抓住我上面的关键点,可以被其余模块调用。一般,像是上面的建立服务器的代码,咱们通常命名为server.js,并且咱们都会有一个index.js做为驱使其余模块的国王。接下来咱们就是要让这个手下可以听话了:
var http = require("http"); function start(){ function onRequest(request, response){ ... } http.createServer(onRequest).listen(8888); } exports.start = start;
咱们这段代码惟一的不一样点就是咱们在结尾多了这一句:exports.start = start。这段代码就是将咱们的start()函数导出去,而后让咱们的index.js可以启动这个服务器,由于通常咱们对服务器的操做就仅是启动它而已。 咱们在index.js中只需这么写:
var server = require("./server"); server.start();
一样的道理,咱们请求server模块并将其赋给一个变量,而后调用这个模块中的start()函数。这里与咱们上面请求内置模块是同样的操做,可是,自定义的模块的请求是须要在前面加上"./"。
经过上面的例子,咱们已经能够初步掌握如何建立模块以及如何在另外一个模块中调用其余模块的方法,接下来,仍是继续探讨其余模块的建立,就是有关于处理不一样的url请求。固然,咱们彻底能够将代码放在咱们已经建好的server模块中,可是咱们通常都会新建一个模块,由于处理url请求并非服务器要作的,它是要交给路由器的(路由器这个词你们必定很是熟悉吧,路由这个名字真的是很是形象,我对于译者的崇拜之情油然而生)。咱们仍是立刻创建一个路由器模块router.js吧:
function route(pathname){ console.log("About to route:" + pathname); } exports.route = route;
而后咱们的服务器仍是要进行修改:
var http = require("http"); url = require("url"); function start(route){ function onRequest(requset, response){ var pathname = url.parse(request.url).pathname; console.log(pathname + "has received"); route(pathname); } ... } exports.start = start;
接着就是index.js:
var server = require("./server"); router = require("./router"); server.start(router.route);
相信你们必定仍是会注意到,就是导出来的函数在除了index.js以外的其余模块中,根本就不须要申请router模块就能直接使用,像是做为参数传递。我曾经将index.js的server.start()直接改成start(),结果它显示的是start()没有被定义这个错误,而后继续手贱,在server.js中添加一个route的函数而且导出,可是什么状况都没有发生!route函数并没显示任何错误!按道理来讲,像是咱们的java,若是你的两个类中的方法名是同样的,你必须指点对象,可是这里彷佛并非这样的。关于这个问题,我想放在后面再讲,由于后面的代码中会出现新的start函数。
咱们这里要先讲一下路由器的工做原理。路由器会根据请求的url和其余须要的get,post参数来执行相应的处理程序,因而咱们须要从http请求中提取出url和get,post参数。这一部分的功能究竟是路由器仍是服务器或者做为一个新的功能模块,咱们这里就不讨论,由于这也太难为我这个初学者了,这是个人第一个应用,我对web编程并无更多的经验,因此,这里就先放在server模块里。好了,那么,咱们所须要的参数都是来自于请求,就是request,那么,咱们怎样提取出来呢?解析request,咱们须要其余模块,像是我上面用到的url模块,事实上,咱们还与querystring模块,至于哪一个更好,咱们先表下不谈,就着url模块来说如何提取,下面就是这两个模块的提取原理:
url.parse(string).query | url.parse(string).pathname | | | | | ------ ------------------- http://localhost:8888/start?http=bar&hello=world --- ----- | | | | querystring(string)["http"] | | querystring(string)["hello"]
对于上面的原理,咱们只须要知道/start?http=bar&hello=world这一部分。相信你们看图就能明白这里面的组成。/start是咱们在index.js中调用的server中的start函数,根就是咱们的路径名pathname,后面就是咱们的显示消息,“Hello Word",可是这里倒是"hell0=word",为何会是这样呢?若是接触过javascript的同窗就不难明白,由于咱们的url是被处理过的,全部的字母都已经被转化为小写,而且空格对应于=。这部分我就再也不讲解了,由于我以为个人javascript知识也实在是太匮乏了,因此你们感兴趣的话,仍是本身看看相关的书吧。
咱们如今已经能够经过不一样的url路径来区别不一样的请求了,也把路由器和服务器整合起来,如今咱们来看看这里面的一些亮点吧。首先,就是咱们的服务器要想知道路由器的存在而且加以利用,就像我前面讲过的,咱们为何不能够直接将这个路由器硬编码进咱们服务器代码中呢?咱们能够这么干:
function start(router.route){ ... }
可是,咱们都知道,这样作的坏处就是咱们的代码太"硬"了!编写这样的代码,会使得咱们的复用性大大降低,因此,node.js采用的是依赖注入的方式来实现松散的注入路由模块。依赖注入,这是一个新字眼,相信我,这个字眼所表明的意思咱们在java这样的面相对象编程语言中已是接触过了,可是它的实际意义倒是很是庞大的,像我这样的新手固然是不敢有任何奢望来向你们讲解这个话题,可是我会结合咱们上面的例子稍微讲一下。依赖注入,说白一点,就是咱们的对象的建立的时候若是须要获得另外一个对象的引用(由于咱们的对象的建立可能须要另外一个对象中的方法),那么咱们固然是会将那个对象的引用做为参数传进来。这就是所谓的"依赖",传对象的引用就是所谓的"注入"。这样的行为咱们在java中是习觉得常的,可是,这种行为会形成咱们的代码的耦合性较大,由于咱们的对象的建立是须要其余代码的,这样,当这部分的代码发生变化的时候,咱们的新对象就要发生变化。哈,这样好像我是在将依赖注入是一种很差的行为,可是,我必须强调,这里的状况是指咱们传进的对象是特定的对象。好比说,像是上面的例子,咱们能够在新建一个js文件,这个js文件里也有一个导出的route函数,那么,若是是咱们之前的思惟,那么,在咱们的server.js中传进的route函数必定要制定是哪个,可是,即便你真这么作了,也不须要这么作,由于咱们的解释器可以找到正确的执行函数(我不知道这里面是怎么发生,这个问题我想,我后面会专门写篇文章来研究一下,这里就先跳过)。因此,咱们的代码就不会发生于特定的对象耦合的状况,这样之后若是要修改的话,就会相对容易,也不会有反作用。其实,我感受这点可能与javascript中解释器寻找函数的机制是同样的,由于javascript的函数的调用是能够只写函数名的!另外,稍微说点题外话,其实上面的话题是与函数式编程有点,因为这方面我根本没有接触,因此上面讲的天然是小生乱弹了。
上面的例子咱们的路由模块并无针对不一样的url执行不一样的行为,只是咱们的服务器可以与路由沟通了而已。因此,咱们的脚步仍要继续。
function start() { console.log("Request handler 'start' was called."); } function upload() { console.log("Request handler 'upload' was called."); } exports.start = start; exports.upload = upload;
这里的requestHandler模块中的start()和upload()函数也并无什么真正的处理,它们只是占位用的函数,就是咱们为咱们的start和upload这样的处理占个位置。而后咱们的问题又来了,就是咱们到底要将这段代码放在哪里?这里是将处理动做和路由联系起来,可是咱们须要将这段代码硬编码进咱们的服务器里吗?而后这样写: if(request == x){
handler y;
}
这样咱们岂不是要随着咱们所要处理的请求的增长而不断扩充if!事实证实,当你的代码中有一大串if,那说明你的代码必定有问题!因此。正确的作法就是咱们再从新写一个新的模块,这一次就是requestHandlers模块,而后再将这个对象传进来。
咱们的代码以下:
首先,是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对象,并且接下来的语句就让人很头疼:handle["/"] = requestHandlers.start.怎么回事?为何会这样写啊?其实,javascritp的对象只是键/值对的集合,咱们能够想象成是一个键位字符串类型的字典,而且值能够是函数。因此,咱们这里就彻底看到了这种特性,咱们的对象甚至能够这样写:handle["/"],"/"就是一个键,而后它的值就是requestHandlers.start。用这样的方式就能将咱们requestHandlers中的处理行为放进一个对象里。 接下来就是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;
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对象相应的值是不是处理函数,若是是,咱们就调用。 如今咱们的服务器,路由和处理程序已经联系在一块儿了。
好了,咱们如今要作的就是i,咱们不想要咱们的浏览器就只是返回"Hello Word"这么没有养分的信息,咱们想要咱们的处理程序可以返回有用的相关信息,固然咱们彻底能够这样作,就是在相关的处理程序的最后return相关信息。是的,这样作的确没有错,可是,会出现问题,就是咱们的代码会挂!是的,这里就是阻塞问题了。
上面的操做是阻塞的,咱们会想要这样子作,就是当程序还在处理一个请求的同时,咱们会想要咱们的程序继续处理新的请求。不要感到难以想象,这是web编程,想一想咱们平时使用浏览器的时候,老是喜欢同时打开多个页面是吧,若是像是上面那样作,咱们的代码就会出现阻塞,就会出现一个页面必须等待另外一个页面打开完成。那么,该怎么才能使用所谓的非阻塞操做呢?
咱们先从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); } http.createServer(onRequest).listen(8888); console.log("Server has started."); } exports.start = start;
这里的区别就是咱们将response做为参数传给route函数,而后咱们继续看route函数:
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;
接着咱们再将requestHandlers.js进行修改:
var exec = require("child_process").exec; 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(); }); } function upload(response) { console.log("Request handler 'upload' was called."); response.writeHead(200, {"Content-Type": "text/plain"}); response.write("Hello Upload"); response.end(); } exports.start = start; exports.upload = upload;
这里咱们注意一下这个新的模块"child_process”,这个模块有一个exec函数,这是一个非阻塞的函数,它真的是很可怕的东西!为何这么说呢?咱们来举个例子:
var exec = require("child_process").exec; function start(){ var content = "empty"; exec("ls-lah", function(error, stdout, stderr){ content = stdout; } return content; }
咱们这里的exec执行的是一个shell命令(若是你有过linux编程,必定知道这是什么东西),将当前目录下的文件信息输出到浏览器,可是咱们一旦真正运行,就会发现,输出是empty。为何呢?由于咱们的exec为何是非阻塞的呢?由于它回调了一个函数stdout(这个函数的做用就是返回输出的结果),而后赋给content,可是由于咱们的exec是非阻塞的,就算咱们的exec的回调还没结束,可是咱们已经执行到下面的return content了,并且这时,content依然是"empty"。因此,明白非阻塞是很重要的,可是咱们也要注意非阻塞有可能给咱们形成的麻烦。
继续回到咱们上面的例子。为何咱们要将response做为参数而且将这部分的工做移到requestHandlers里呢?由于咱们想要的是对response的直接响应。以前咱们的代码是这样的:咱们的服务器将response传给咱们的route,咱们的route进行处理好,咱们的服务器再对resopnse进行响应,如今是这样:咱们的服务器依然是将response传给route,可是如今咱们的route就已经对response进行响应,这里就少了一层,可是咱们的代码的逻辑更加清晰,由于咱们处理response的程序和响应response的程序理应放在一块。
进行到这一步,咱们发现,咱们的应用根本没有什么实际意义!咱们仍是只是显示一些信息而已!不行,咱们必须作些更有用的东西出来!!此次的目标就是容许用户上传文件,而后咱们将这个文件显示出来,好比说图片。好了,咱们先来看看如何处理POST请求。
咱们的requestHandlers.js再度修改:
function start(response) { console.log("Request handler 'start' was called."); 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="Submit text" />' + '</form>' + '</body>' + '</html>'; response.writeHead(200, {"Content-Type": "text/html"}); response.write(body); response.end(); } function upload(response) { console.log("Request handler 'upload' was called."); response.writeHead(200, {"Content-Type": "text/plain"}); response.write("Hello Upload"); response.end(); } exports.start = start; exports.upload = upload;
咱们这里的修改就是多了body,是的,咱们只是显示一个文本区供用户输入,而后经过POST请求提交给服务器,接着服务器经过处理程序将内容显示到浏览器。因此,这里须要生成带文本区的表单。
而后咱们就来处理upload。这部分固然是采用非阻塞,由于POST请求通常都是很是重的,所谓的重,就是指用户通常都会上传大量的内容,若是采用阻塞的操做,就会使用户的执行动做彻底卡死。为了使整个过程都是非阻塞的,node.js会将POST数据分红许多小的数据块,而后经过触发特定的事件,将这些小的数据块传递给回调函数,这里特定的事件有data事件(表示有新的小数据块到了),end事件(表示全部的数据块已经接受完毕)。因此,咱们天然就要告诉解释器,当这些事件触发时应该回调哪些函数。咱们能够在request上注册监听器,如:
request.addListener("data", function(chunk){ ... }); request.addListener("end", function(){ ... });
问题来了,就是这部分代码应该放在哪里?这种问题当咱们在不断扩展本身程序的功能时很是常见,由于咱们必须处理好咱们每一个模块应该要处理的功能。这里的话,应该是放在服务器里,由于服务器应该是获取POST请求的数据并进行处理而后交给应用层处理。因此咱们的server.js又要进一步改造:
var http = require("http"); var url = require("url"); function start(route, handle) { function onRequest(request, response) { var postData = ""; var pathname = url.parse(request.url).pathname; console.log("Request for " + pathname + " received."); request.setEncoding("utf8"); request.addListener("data", function(postDataChunk) { postData += postDataChunk; console.log("Received POST data chunk '" + postDataChunk + "'."); }); request.addListener("end", function() { route(handle, pathname, response, postData); }); } http.createServer(onRequest).listen(8888); console.log("Server has started."); } exports.start = start;
这里咱们设置数据的接收格式是UTF-8,而后咱们的data事件会不断收集数据,最后将全部的数据块传递给end事件,end事件中调用的是route函数,由于这是在全部的数据块接收完毕后才执行的动做。这里还有一个地方,就是每次数据到达的时候都会有输出日志,这对于最终的生产环境来讲是很差的,可是对于开发阶段来讲却能让咱们更加直观的追踪咱们收到的数据。 接着咱们要进行的是upload功能,因而咱们处理数据的router.js进行如下的修改:
function route(handle, pathname, response, postData) { console.log("About to route a request for " + pathname); if (typeof handle[pathname] === 'function') { handle[pathname](response, postData); } 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;
而后就是咱们的requestHandlers.js:
function start(response, postData) { console.log("Request handler 'start' was called."); 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="Submit text" />' + '</form>' + '</body>' + '</html>'; response.writeHead(200, {"Content-Type": "text/html"}); response.write(body); response.end(); } function upload(response, postData) { console.log("Request handler 'upload' was called."); response.writeHead(200, {"Content-Type": "text/plain"}); response.write("You've sent: " + postData); response.end(); } exports.start = start; exports.upload = upload;
这个例子到这里没有结束,由于咱们队POST请求中的数据感兴趣的只是里面的text数据,而不是全部的数据,因此咱们必须将这些感兴趣的数据提取出来传递给路由和处理程序。 因而咱们就要利用以前出现过的querystring模块:
var querystring = require("querystring"); function start(response, postData) { console.log("Request handler 'start' was called."); 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="Submit text" />' + '</form>' + '</body>' + '</html>'; response.writeHead(200, {"Content-Type": "text/html"}); response.write(body); response.end(); } function upload(response, postData) { console.log("Request handler 'upload' was called."); response.writeHead(200, {"Content-Type": "text/plain"}); response.write("You've sent: " + postData); querystring.parse((postData)text); response.end(); } exports.start = start; exports.upload = upload;
以上的例子只是说明咱们该处理文件(text数据),可是咱们最终的目标是要处理图片,因此,咱们须要用到formidable模块,可是这些模块并非咱们的node.js默认的模块,咱们须要使用外部模块,咱们须要下载并安装,因而咱们能够经过这样的命令: npm install formidable;
这样咱们的node.js就会自动下载而且安装了。
显示用户上传的图片,其实原理就是读取用户指定位置的图片,那么,咱们先来实现如何读取咱们的本地图片。
var querystring = require("querystring"), fs = require("fs"); function start(response, postData) { console.log("Request handler 'start' was called."); 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="Submit text" />' + '</form>' + '</body>' + '</html>'; response.writeHead(200, {"Content-Type": "text/html"}); response.write(body); response.end(); } function upload(response, postData) { console.log("Request handler 'upload' was called."); response.writeHead(200, {"Content-Type": "text/plain"}); response.write("You've sent the text: " + querystring.parse(postData).text); response.end(); } function show(response, postData) { console.log("Request handler 'show' was called."); 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(); } }); } exports.start = start; exports.upload = upload; exports.show = show;
这里咱们的requestHandlers.js只需作上面同样的修改就行,添加一个专门处理文件读取的fs模块就行。 接下里就是实现咱们的最终要求了。咱们要作的第一步就是在/start表单中添加一个上传元素,这个的实现很简单,就是将咱们的表单进行修改就行,添加一个文件上传组件(若是是对html的表单上的组件不熟悉的话,抱歉,这里实在是没有多少篇幅能够讲这部分的内容,还请自行百度),如:
var querystring = require("querystring"), fs = require("fs"); function start(response, postData) { console.log("Request handler 'start' was called."); var body = '<html>' + '<head>' + '<meta http-equiv="Content-Type" ' + 'content="text/html; charset=UTF-8" />' + '</head>' + '<body>' + '<form action="/upload" enctype="multipart/form-data" ' + 'method="post">' + '<input type="file" name="upload">' + '<input type="submit" value="Upload file" />' + '</form>' + '</body>' + '</html>'; response.writeHead(200, {"Content-Type": "text/html"}); response.write(body); response.end(); } function upload(response, postData) { console.log("Request handler 'upload' was called."); response.writeHead(200, {"Content-Type": "text/plain"}); response.write("You've sent the text: "+ querystring.parse(postData).text); response.end(); } function show(response, postData) { console.log("Request handler 'show' was called."); 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(); } }); } exports.start = start; exports.upload = upload; exports.show = show;
接下来咱们须要在upload中对于上传的图片进行处理,可是,这里有个问题必须知道,就是咱们须要将request传递给formidable的form.parse函数,可是咱们有的只是response和postData数组,因此咱们只能将request一路从服务器经过路由接着传递给咱们的处理程序。如今的咱们已经能够将全部有关于postData的部分从咱们的服务器和处理程序中移除了,由于它们对于咱们的上传文件已经没有什么做用了。显示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, request); } http.createServer(onRequest).listen(8888); console.log("Server has started."); } exports.start = start;
而后是router.js:
function route(handle, pathname, response, request) { console.log("About to route a request for " + pathname); if (typeof handle[pathname] === 'function') { handle[pathname](response, request); } else { console.log("No request handler found for " + pathname); response.writeHead(404, {"Content-Type": "text/html"}); response.write("404 Not found"); response.end(); } } exports.route = route;
formidable会将咱们的上传文件自动保存到/tmp目录中,咱们须要作的就是保证保存成/tmp/test.png,因此这里咱们须要使用的是fs.renameSync(path1, path2)函数,它能使一个文件从一个路径保存到另外一个路径中,而且它是同步的。requestHandlers.js以下:
var querystring = require("querystring"), fs = require("fs"), formidable = require("formidable"); function start(response) { console.log("Request handler 'start' was called."); var body = '<html>' + '<head>' + '<meta http-equiv="Content-Type" content="text/html; ' + 'charset=UTF-8" />' + '</head>' + '<body>' + '<form action="/upload" enctype="multipart/form-data" ' + 'method="post">' + '<input type="file" name="upload" multiple="multiple">' + '<input type="submit" value="Upload file" />' + '</form>' + '</body>' + '</html>'; response.writeHead(200, {"Content-Type": "text/html"}); response.write(body); response.end(); } function upload(response, request) { console.log("Request handler 'upload' was called."); var form = new formidable.IncomingForm(); console.log("about to parse"); form.parse(request, function(error, fields, files) { console.log("parsing done"); fs.renameSync(files.upload.path, "/tmp/test.png"); response.writeHead(200, {"Content-Type": "text/html"}); response.write("received image:<br/>"); response.write("<img src='/show' />"); response.end(); });} function show(response) { console.log("Request handler 'show' was called."); 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(); } }); } exports.start = start; exports.upload = upload; exports.show = show;
如今上图:
好了,到了这里,咱们所要实现的目标已经达成了。相信你们必定对于node.js有了必定的初步了解了。