Express 介绍
在前面的 node.js 基础当中介绍许多许多开设 http 的使用方法及介绍,以及许多基本的node.js 基本应用。
接下来要介绍一个套件称为 express [Express](http://expressjs.com/) ,这个套件主要帮忙解决许多 node.js http server 所需要的基本服务,让开发 http service 变得更为容易,不需要像之前需要透过层层模组(module)才有办法开始编写自己的程式。
这个套件是由TJ Holowaychuk 製作而成的套件,里面包含基本的路由处理(route),http 资料处理(GET/POST/PUT),另外还与样板套件(js html template engine)搭配,同时也可以处理许多複杂化的问题。
Express 安装
安装方式十分简单,只要透过之前介绍的 NPM 就可以使用简单的指令安装,指令如下,
这边建议需要将此套件安装成为全域模组,方便日后使用。
Express 基本操作
express 的使用也十分简单,先来建立一个基本的hello world ,
var app = require('express').createServer(), port = 1337; app.listen(port); app.get('/', function(req, res){ res.send('hello world'); }); console.log('start express servern');
可以从上面的程式码发现,基本操作与node.js http 的建立方式没有太大差异,主要差在当我们设定路由时,可以直接透过 app.get 方式设定回应与接受方式。
Express 路由处理
Express 对于 http 服务上有许多包装,让开发者使用及设定上更为方便,例如有几个路由设定,那我们就统一藉由 app.get 来处理,
// ... Create http server app.get('/', function(req, res){ res.send('hello world'); }); app.get('/test', function(req, res){ res.send('test render'); }); app.get('/user/', function(req, res){ res.send('user page'); });
如上面的程式码所表示,app.get 可以带入两个参数,第一个是路径名称设定,第二个为回应函式(call back function),回应函式里面就如同之前的 createServer 方法,里面包含 request, response 两个物件可供使用。使用者就可以透过浏览器,输入不同的url 切换到不同的页面,显示不同的结果。
路由设定上也有基本的配对方式,让使用者从浏览器输入的网址可以是一个变数,只要符合型态就可以有对应的页面产出,例如,
// ... Create http server app.get('/user/:id', function(req, res){ res.send('user: ' + req.params.id); }); app.get('/:number', function(req, res){ res.send('number: ' + req.params.number); });
里面使用到:number ,从网址输入之后就可以直接使用 req.params.number 取得所输入的资料,变成url 参数使用,当然前面也是可以加上路径的设定, /user/:id,在浏览器上路径必须符合 /user/xxx,透过 req.params.id 就可以取到 xxx这个字串值。
另外,express 参数处理也提供了路由参数配对处理,也可以透过正规表示法作为参数设定,
var app = require('express').createServer(), port = 1337; app.listen(port); app.get(/^/ip?(?:/(d{2,3})(?:.(d{2,3}))(?:.(d{2,3}))(?:.(d{2,3})))?/, function(req, res){ res.send(req.params); });
上面程式码,可以发现后面路由设定的型态是正规表示法,里面设定格式为 /ip 之后,必须要加上ip 型态才会符合资料格式,同时取得ip资料已经由正规表示法将资料做分群,因此可以取得ip的四个数字。
此程式执行之后,可以透过浏览器测试,输入网址为 localhost:3000/ip/255.255.100.10,可以从页面获得资料,
此章节全部範例程式码如下,
/** * @overview * * @author Caesar Chi * @blog clonn.blogspot.com * @version 2012/02/26 */ // create server. var app = require('express').createServer(), port = 1337; app.listen(port); // normal style app.get('/', function(req, res){ res.send('hello world'); }); app.get('/test', function(req, res){ res.send('test render'); }); // parameter style app.get('/user/:id', function(req, res){ res.send('user: ' + req.params.id); }); app.get('/:number', function(req, res){ res.send('number: ' + req.params.number); }); // REGX style app.get(/^/ip?(?:/(d{2,3})(?:.(d{2,3}))(?:.(d{2,3})))?/, function(req, res){ res.send(req.params); }); app.get('*', function(req, res){ res.send('Page not found!', 404); }); console.log('start express servern');
Express middleware
Express 里面有一个十分好用的应用概念称为middleware,可以透过 middleware 做出複杂的效果,同时上面也有介绍 next 方法参数传递,就是靠 middleware 的概念来传递参数,让开发者可以明确的控制程式逻辑。
// .. create http server app.use(express.bodyParser()); app.use(express.methodOverride()); app.use(express.session());
上面都是一种 middleware 的使用方式,透过 app.use 方式里面载入函式执行方法,回应函式会包含三个基本参数,response, request, next,其中next 表示下一个 middleware 执行函式,同时会自动将预设三个参数继续带往下个函式执行,底下有个实验,
上面的片段程式执行后,开启浏览器,连结上 localhost:1337/,会发现伺服器回应结果顺序如下,
first middle ware second middle ware execute middle ware end middleware function
从上面的结果可以得知,刚才设定的 middleware 都生效了,在 app.use 设定的 middleware 是所有url 皆会执行方法,如果有指定特定方法,就可以使用 app.get 的 middleware 设定,在 app.get 函式的第二个参数,就可以带入函式,或者是匿名函式,只要函式里面最后会接受 request, response, next 这三个参数,同时也有正确指定 next 函式的执行时机,最后都会执行到最后一个方法,当然开发者也可以评估程式逻辑要执行到哪一个阶段,让逻辑可以更为分明。
Express 路由应用
在实际开发上可能会遇到需要使用参数等方式,混和变数一起使用,express 里面提供了一个很棒的处理方法 app.all 这个方式,可以先採用基本路由配对,再将设定为每个不同的处理方式,开发者可以透过这个方式简化自己的程式逻辑,
/** * @overview * * @author * @version 2012/02/26 */ // create server. var app = require('express').createServer(), port = 1337, users = [ {name: 'Clonn'}, {name: 'Chi'} ]; app.listen(port); app.all('/user/:id/:op?', function(req, res, next){ req.user = users[req.params.id]; if (req.user) { next(); } else { next(new Error('cannot find user ' + req.params.id)); } }); app.get('/user/:id', function(req, res){ res.send('viewing ' + req.user.name); }); app.get('/user/:id/edit', function(req, res){ res.send('editing ' + req.user.name); }); app.get('/user/:id/delete', function(req, res){ res.send('deleting ' + req.user.name); }); app.get('*', function(req, res){ res.send('Page not found!', 404); }); console.log('start express servern');
内部宣告一组预设的使用者分别给予名称设定,藉由 app.all 这个方法,可以先将路由雏形建立,再接下来设定 app.get 的路径格式,只要符合格式就会分配进入对应的方法中,像上面的程式当中,如果使用者输入路径为 /user/0 ,除了执行 app.all 程式之后,执行next 方法就会对应到路径设定为 /user/:id 的这个方法当中。如果使用者输入路径为 /user/0/edit ,就会执行到 /user/:id/edit 的对应方法。
Express GET 应用範例
我们準备一个使用GET方法传送资料的表单。
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf8"> <title>Node.js菜鸟笔记(1)</title> <link rel="stylesheet" href="css/style.css" type="text/css" media="all" /> </head> <body> <h1>Node.js菜鸟笔记-注册</h1> <form id="signup" method="GET" action="http://localhost:3000/Signup"> <label>使用者名称:</label><input type="text" id="username" name="username" /><br> <label>电子邮件:</label><input type="text" id="email" name="email" /><br> <input type="submit" value="注册我的帐号" /><br> </form> </body> </html>
这个表单没有什幺特别的地方,我们只需要看第9行,form 使用的 method 是 GET,然后 action 是 http://localhost:3000/Signup,等一下我们要来撰写 /Signup 这个 URL Path 的处理程式。
处理 Signup 行为
我们知道所谓的 GET 方法,会透过 URL 来把表单的值给带过去,以上面的表单来说,到时候URL会以这样的形式传递
http://localhost:3000/Signup?username=xxx&email=xxx
所以要能处理这样的资料,必须有以下功能:
- 解析URL
- 辨别动作是Signup
- 解析出username和email
一旦能取得username和email的值,程式就能加以应用了。
处理 Signup 的程式码雏形,
// load module var url = require('url'); urlData = url.parse(req.url,true); action = urlData.pathname; res.writeHead(200, {"Content-Type":"text/html; charset=utf-8"}); if (action === "/Signup") { user = urlData.query; res.end("<h1>" + user.username + "欢迎您的加入</h1><p>我们已经将会员启用信寄至" + user.email + "</p>"); }
首先需要加载 url module,它是用来协助我们解析URL的模组,接着使用 url.parse 方法,第一个传入url 字串变数,也就是req.url。另外第二个参数的用意是,设为ture则引进 querystring模组来协助处理,预设是false。它影响到的是 urlData.query,设为true会传回物件,不然就只是一般的字串。url.parse 会将字串内容整理成一个物件,我们把它指定给urlData。
action 变数作为记录pathname,这是我们稍后要来判断目前网页的动作是什幺。接着先将 html 表头资讯 (Header)準备好,再来判断路径逻辑,如果是 /Signup 这个动作,就把urlData.query里的资料指定给user,然后输出user.username和user.email,把使用者从表单注册的资料显示于页面中。
最后进行程式测试,启动 node.js 主程式之后,开启浏览器就会看到表单,填写完毕按下送出,就可以看到结果了。
完整 node.js 程式码如下,
var http = require('http'), url = require('url'), fs = require("fs"), server; server = http.createServer(function (req,res) { var urlData, encode = "utf8", filePath = "view/express_get_example_form.html", action; urlData = url.parse(req.url,true); action = urlData.pathname; res.writeHead(200, {"Content-Type":"text/html; charset=utf-8"}); if (action === "/Signup") { user = urlData.query; res.end("<h1>" + user.username + "欢迎您的加入</h1><p>我们已经将会员启用信寄至" + user.email + "</p>"); } else { fs.readFile(filePath, encode, function(err, file) { res.write(file); res.end(); }); } }); server.listen(3000); console.log('Server跑起来了,现在时间是:' + new Date());
Express POST 应用範例
一开始準备基本的 html 表单,传送内容以 POST 方式, form 的 action 属性设定为 POST,其余 html 内容与前一个範例应用相同,
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf8"> <title>Node.js菜鸟笔记(1)</title> <link rel="stylesheet" href="css/style.css" type="text/css" media="all" /> </head> <body> <h1>Node.js菜鸟笔记-注册</h1> <form id="signup" method="POST" action="http://localhost:3000/Signup"> <label>使用者名称:</label><input type="text" id="username" name="username" /><br> <label>电子邮件:</label><input type="text" id="email" name="email" /><br> <input type="submit" value="注册我的帐号" /><br> </form> </body> </html>
node.js 的程式处理逻辑与前面 GET 範例类似,部分程式码如下,
qs = require('querystring'), if (action === "/Signup") { formData = ''; req.on("data", function (data) { formData += data; }); req.on("end", function () { user = qs.parse(formData); res.end("<h1>" + user.username + "欢迎您的加入</h1><p>我们已经将会员启用信寄至" + user.email + "</p>"); }); }
主要加入了’querystring’ 这个moduel,方便我们等一下解析由表单POST回来的资料,另外加入一个formData的变数,用来搜集待等一下表单回传的资料。前面的GET 範例,我们只从req 拿出url的资料,这次要在利用 req 身上的事件处理。
JavaScript在订阅事件时使用addEventListener,而node.js使用的则是on。这边加上了监听 data 的事件,会在浏览器传送资料到 Web Server时被执行,参数是它所接收到的资料,型态是字串。
接着再增加 end 的事件,当浏览器的请求事件结束时,它就会动作。
由于浏览器使用POST在上传资料时,会将资料一块块地上传,因为我们在监听data事件时,透过formData 变数将它累加起来< 不过由于我们上传的资料很少,一次就结束,不过如果日后需要传的是资料比较大的档案,这个累加动作就很重要。
当资料传完,就进到end事件中,会用到 qs.parse来解析formData。formData的内容是字串,内容是:
username=wordsmith&email=wordsmith%40some.where
而qs.parse可以帮我们把这个querystring转成物件的格式,也就是:
{username=wordsmith&email=wordsmith%40some.where}
一旦转成物件并指定给user之后,其他的事情就和GET方法时操作的一样,写response的表头,将内容回传,并将user.username和user.email代入到内容中。
修改完成后,接着执行 node.js 程式,启动 web server ,开启浏览器进入表单测试看看,POST 的方式能否顺利运作。
完整程式码如下,
var http = require('http'), url = require('url'), fs = require("fs"), qs = require('querystring'), server; server = http.createServer(function (req,res) { var urlData, encode = "utf8", filePath = "view/express_post_example_form.html", formData, action; urlData = url.parse(req.url,true); action = urlData.pathname; res.writeHead(200, {"Content-Type":"text/html; charset=utf-8"}); if (action === "/Signup") { formData = ''; req.on("data", function (data) { formData += data; }); req.on("end", function () { user = qs.parse(formData); res.end("<h1>" + user.username + "欢迎您的加入</h1><p>我们已经将会员启用信寄至" + user.email + "</p>"); }); } else { fs.readFile(filePath, encode, function(err, file) { res.write(file); res.end(); }); } }); server.listen(3000); console.log('Server跑起来了,现在时间是:' + new Date());
Express AJAX 应用範例
在Node.js要使用Ajax传送资料,并且与之互动,在接受资料的部份没有太大的差别,client端不是用GET就是用POST来传资料,重点在处理完后,用JSON格式回传。当然Ajax不见得只传JSON格式,有时是回传一段HTML码,不过后者对伺服器来说,基本上就和前两篇没有差别了。所以我们还是以回传JSON做为这一回的主题。
这一回其实大多数的工作都会落在前端Ajax上面,前端要负责发送与接收资料,并在接收资料后,撤掉原先发送资料的表单,并将取得的资料,改成HTML格式之后,放上页面。
首先先準备 HTML 静态页面资料,
<!DOCTYPE html> <html> <head> <title>Node.js菜鸟笔记(3)</title> <link rel="stylesheet" href="/css/style.css" type="text/css" media="all" /> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script> </head> <body> <h1>Node.js菜鸟笔记-注册(用Ajax)</h1> <form id="signup" action="/Signup" method="POST"> <label>使用者名称:</label><input type="text" id="username" name="username" /><br> <label>电子邮件:</label><input type="text" id="email" name="email" /><br> <input type="submit" value="注册我的帐号" /><br> </form> <div id="result"> </div> <script type="text/javascript"> $(function(){ $('#signup input[type="submit"]').click(function(e){ var user = {}; user.username = $("#username").val(); user.email = $("#email").val(); $.post("/Signup",user,function(data){ greet(data) }); e.preventDefault(); }); var greet = function(msg){ var resultNode = $('#result'), greeting = $('<h2>Hi,'+msg.username+',你的会员id是:'+ msg.id +'我们会将会员启动信寄至'+msg.email+'</h2>'); $('#signup').hide(); resultNode.html(""); resultNode.append(greeting); }; }); </script> </body> </html>
HTML 页面上準备了一个表单,用来传送注册资料。接着直接引用了 Google CDN 来载入 jQuery,用来帮我们处理 Ajax 的工作,这次要传送和接收的工作,很大的变动都在 HTML 页面上的 JavaScript当中。我们要做的事有(相关 jQuery 处理这边不多做赘述,指提起主要功能解说):
- 用jQUery取得submit按钮,绑定它的click动作
- 取得表单username和email的值,存放在user这个物件中
- 用jQuery的$.post方法,将user的资料传到Server
- 一旦成功取得资料后,透过greet这个function,组成回报给user的讯息
- 清空原本给使用者填资料的表单
- 将Server回传的username、email和id这3个资料,组成回应的讯息
- 将讯息放到原本表单的位置
经过以上的处理后,一个Ajax的表单的基本功能已经完成。
接着进行 node.js 主要程式的编辑,部分程式码如下,
var fs = require("fs"), qs = require('querystring'); if (action === "/Signup") { formData = ''; req.on("data", function (data) { formData += data; }); req.on("end", function () { var msg; user = qs.parse(formData); user.id = "123456"; msg = JSON.stringify(user); res.writeHead(200, {"Content-Type":"application/json; charset=utf-8","Content-Length":msg.length}); res.end(msg); }); }
这里的程式和前面 POST 範例,基本上大同小异,差别在:
- 帮user的资料加上id,随意存放一些文字进去,让Server回传的资料多于Client端传上来的,不然会觉得Server都没做事。
- 增加了msg这个变数,存放将user物件JSON文字化的结果。JSON.stringify这个转换函式是V8引擎所提供的,如果你好奇的话。
- 大重点来了,我们要告诉Client端,这次回传的资料格式是JSON,所在Content-type和Content-Length要提供给Client。
Server很轻鬆就完成任务了,最后进行程式测试,启动 node.js 主程式之后,开启浏览器就会看到表单,填写完毕按下送出,就可以看到结果了。
最后 node.js 本篇範例程式码如下,
var http = require('http'), url = require('url'), fs = require("fs"), qs = require('querystring'), server; server = http.createServer(function (req,res) { var urlData, encode = "utf8", filePath = "view/express_ajax_example_form.html", formData, action; urlData = url.parse(req.url,true); action = urlData.pathname; if (action === "/Signup") { formData = ''; req.on("data", function (data) { formData += data; }); req.on("end", function () { var msg; user = qs.parse(formData); user.id = "123456"; msg = JSON.stringify(user); res.writeHead(200, {"Content-Type":"application/json;","Content-Length":msg.length}); res.end(msg); }); } else { fs.readFile(filePath, encode, function(err, file) { res.writeHead(200, {"Content-Type":"text/html; charset=utf-8"}); res.write(file); res.end(); }); } }); server.listen(3000); console.log('Server跑起来了,现在时间是:' + new Date());