Node的核心理念是事件驱动编程,在Node构建的Server中,Http请求就是要处理的事件,http.createServer方法是将函数作为一个参数,每次有HTTP请求发送过来的时候都会调用那个函数
http.createServer(function(req, res) {
res.writeHead(200, {'content-type': 'text/plain'});
res.end();
}).listen(8888);
路由是指向客户端提供它所发出的请求的规则,对基于Web的客户端/服务器端程序而言,客户端在URL中指明他想要的内容,具体来说就是路径和查询字符串
var http = require('http'),
fs = require('fs');
function serveStaticFile(res, path, contentType, responseCode) {
if(!responseCode) {
responseCode = 200;
}
console.log('__dirname', __dirname);
// __dirname: G:\NewWebSpace\NodeJS\Node server所在的目录
fs.readFile(__dirname + path, function(err, data) {
if(err) {
res.writeHead(500, {'content-type': 'text/plain'});
res.end('500-Internal Error');
} else {
res.writeHead(responseCode, {'content-type': 'text/plain'});
res.end(data);
}
});
}
http.createServer(function(req, res) {
/*规范化url,去掉后面的查询字符串,可选的反斜杠,并将它变为小写*/
var path = req.url.replace(/\/?(?:\?.*)?$/, '').toLowerCase();
switch(path) {
case '': {
serveStaticFile(res, '/public/index.html', 'text/html');
break;
}
case '/about': {
serveStaticFile(res, '/public/about.html', 'text/html');
break;
}
case '/img/logo.jpg': {
serveStaticFile(res, '/public/img/logo.jpg', 'image/jpeg');
break;
}
default : {
res.writeHead(404, {'content-type': 'text/plain'});
res.end('Not Found');
break;
}
}
}).listen(8888);
console.log('server is started at : localhost:8888');
fs.readFile
是读取文件的异步方法,__dirname会被解析为正在执行脚本所在的目录
请求和响应对象
URL的组成部分:
协议:协议确定如何传输请求,主要的就是http和https,其他的也有file和ftp
主机名:主机名标识服务器,运行在本地计算机(localhost)和本地网络的服务器可以简单地表示,比如用一个单词,或者一个数字IP地址
端口:每一台服务器都有一系列端口号,80端口负责HTTP传输,443端口负责HTTPS传输,如果不使用80和443,就需要一个大于1023的端口号
路径:URL中影响应用程序的第一个组成部分就是路径。路径是应用中的页面或资源的唯一标识
查询字符串:查询字符串是一种键值对,是可选的,以?开头,键值对之间以&分隔,所有的名称和值都必须是URL编码的,对此,JavaScript提供了一个嵌入式的函数encodeURIComponent来处理
信息片段:信息片段(或散列)被严格定义在浏览器中使用,不会传递到服务器。用它控制单页应用应用或Ajax富应用越来越普遍(锚点)
内容渲染
渲染内容的时候用到的是res.render
,使用req.query得到查询字符串的值,使用req.session得到会话值,或使用req.cookie/req.singedCookies得到cookie的值
app.get('/about', function(req, res) {
res.render('about');
});
200以外的响应码:
app.get('/error', function(req, res) {
res.status(500).render('error');
})
将上下文传递给视图,包括查询字符串、cookie、session值
app.get('/greeting', function(req, res) {
res.render('about', {
message : 'welcome',
style: req.query.style,
userid: req.cookie.userid,
username : req.session.username
});
});
处理表单
表单信息一般在req.body中,使用中间件body-parser
app.post('/process', function(req, res) {
console.log(req.body.name);
res.redirect(303, '/thank-you');
})
get的情况下,返回JSON数据
app.get('/api/tours', function(req, res) {
res.json();
})
res.fomat根据请求报头发送不同的数据,如JSON等
res.format({
'application/json': function(){
res.json(users);
},
...
})
put方式,参数在查询字符串中传递(路由字符串中的’ :id ‘命令Express在req.params中增加一个id属性)
app.put('/api/tour/:id', function(req, res) {
......
})
模板引擎
理解模板引擎的关键在于context(上下文环境),当渲染一个模板时,便会传递给模板引擎一个对象,叫做上下文对象,叫做上下文对象,它能替换标识运行
模板引擎结合视图、布局和上下文来完成渲染,视图首先被渲染,然后再渲染布局
表单处理
无论是使用浏览器提交表单,还是使用Ajax提交,或是使用精巧的前端控件,底层机制通常仍然是HTML表单
总的说来,向服务器端发送客户端数据有两种方式,查询字符串和请求正文,如果是使用查询字符串,就发起了GET请求,如果是使用请求正文,就发起了一个POST请求
文件上传
文件上传可以使用Connect的内置插件multipart来处理,但是不建议使用,对于复合表单处理,有两个更好的选择,Busboy和Formidable,关于Formidable应用的实例,参考这里
Cookie与会话
HTTP是无状态协议,而在HTTP上建立状态,于是就有了cookie和会话
cookie
关于cookie有下面几点:
- cookie对用户来说不是加密的
- 用户可以删除或禁用cookie
- 一般的cookie都可以被篡改
- cookie可以用于攻击,比如XSS攻击
- 如果滥用cookie,会带来不好的用户体验
- 会话优于cookie
Express中的cookie,在程序开始设置和访问之前,需要先引入中间件cookie-parser
npm install --save cookie-parser
app.use(require('cookie-parser')(credentials.cookieSecret))
签名cookie
res.cookie('signed_monster', 'nom nom', {signed : true})
设置cookie可以使用下面的选项:
- domain 控制跟cookie关联的域名,这样可以将cookie分配给特定的子域名
- path 控制应用这个cookie的路径
- maxAge 客户端保存cookie的时间
- secure 指定该cookie只能通过安全HTTPS连接发送
- httpOnly 将这个选项设置为true表明,这个cookie只能由服务器进行修改,也就是说客户端JavaScript不能修改,有助于防范XSS攻击
- signed 设为true,就需要用res.signedCookies而不是res.cookies访问它。被篡改的签名cookie会被服务器拒绝,并且cookie值会重置为它的原始值
会话
要实现会话,必须要在客户端存些东西,否则服务器无法从一个请求到下一个请求中识别客户端,通常的做法是用一个包含唯一标识的cookie,然后服务器用这个标识获取相应的会话信息
中间件
从概念上来说,中间件是一种功能的封装,具体来说就是封装在程序中处理HTTP请求的功能,从实战上来说,中间件是一个只有三个参数的函数:一个请求对象,一个响应对象和一个next函数,中间件是在管道中执行的。在Express程序中,通过使用app.use向管道中插入中间件,中间件和路由处理器按它们的连入顺序调用的,顺序更清晰
重点:
- 路由处理器(app.get,app.post等,经常被称为app.VERB)可以看作只处理特定HTTP谓词(GET、POST等)的中间件
- 路由处理器的第一个参数必须是路径,如果你想要一个路径匹配所有路径,之需要/*。中间件也可以把路径作为第一个参数,但它是可选的(如果忽略了这个参数,它会匹配所有的路径,就好像是使用了\*一样)
- 路由处理器和中间件的参数中都有回掉函数,这个函数有2个,3个或者4个参数(2个或者3个的情况,头两个参数是请求和响应对象,第三个参数是next函数,如果有四个参数,就成了错误处理中间件,第一个参数就成了错误对象,然后依次是请求、响应和next对象)
- 如果不调用next(),管道就会被终止,也不会再有处理器或中间件做后续处理,如果不掉用next(),则应该发送一个响应到客户端(res.send、res.json、res.render等),如果不这么做,客户端会挂起并最终导致超时
- 如果调用了next(),一般不宜再发送请求道客户端,如果你发送了,管道中后续的中间件或路由处理器还会执行,但它们发送的任何响应都会被忽略
发送邮件
Node和Express都没有内置的邮件发送功能,可以使用Nodemailer第三方模块
发送邮件的通用语言是简单邮件传参数协议(SMTP),MSA邮件提交代理(通过可行的渠道投递邮件,降低邮件被标注成垃圾邮件的可能性),MTA邮件传输代理(提供将邮件真正送到其目的地的服务)
邮件消息分为两部分:头部和主体(和HTTP请求很像),头部包含与邮件有关的信息:谁发的、发给谁、接收日期、主题等