伴随Web 1.0, Web 2.0一路走来,HTTP占据了网络中的大多数流量。因为Node的出现,"前端“将会被重新定义。
为了胜任Web应用的开发工作,各种语言、模式、框架层出不穷。单从框架而言,在后端数得出大名的就有Structs、CodeIgniter、Rails、Django、web.py等,在前端也有知名的BackBone、Knockout.js、AngularJS、Meteor等。
在Node中,有Connect中间件,也有Express这样的MVC框架。值得注意的是Meteor框架,它在后端是Node,在前端是JavaScript,它是一个融合了前后端JavaScript的框架。
由于前后端采用的语言都是JavaScript,在跨越HTTP进行沟通时,会有一些额外的好处。
- 无须切换语言环境
- 数据(JSON)可以很好地实现跨前后端直接使用
- 一些业务(如模板渲染)可以很自由地轻量地选择是在前端还是在后端进行,因为编程语言相同,所以切换代价小。
8.1 基础功能
http模块中服务器端的request事件发生于网络连接建立,客户端向服务器端发送报文,服务器端解析报文,发现HTTP请求的报头时。在已触发request事件前,它已准备好ServerRequest和SeverResponse对象以供对请求和响应报文的操作。
在具体的业务中,可能有如下需求:请求方法的判断、URL的路径解析、URL中查询字符串解析、Cookie的解析、Basic认证、表单数据的解析、任意格式文件的上传处理。
除此之外,可能还有Session的需求。还需要大量的工作,但都从如下函数展开:
var http = require('http');
http.createServer(function( req, res) {
res.writeHead(200, {'Content-Type':'text/plain'});
res.end('Hello World\n');
}).listen(1337, '127.0.0.1');
console.log('Sever running at http://127.0.0.1:1337/');
请求方法:req.method: GET和POST
路径解析:URL
查询字符串:路径后的?foo=bar字符串,querystring模块用于处理这部分数据
Cookie:HTTP是一个无状态的协议,需要Cookie标识和认证一个用户,其处理分为如下几步:服务器向客户端发送Cookie、浏览器将Cookie保存、之后每次浏览器都会将Cookie发向服务器端。
curl -v -H "Cookie: foo=bar; baz=val" "http://127.0.0.1:1337/path?foo=bar&foo=baz"
req.headers.cookie就是Cookie
Cookie的性能影响:减小Cookie的大小、为静态组件使用不同的域名、减少DNS查询
前端也可以修改Cookie
目前,广告和在线统计领域是最为依赖Cookie的,通过嵌入第三方的广告或统计脚本,将Cookie和当前页面绑定,这样就可以标识用户,得到用户的浏览行为,广告商就可以定向投放广告了。尽管这样的行为看起来很可怕,但是从Cookie的原理来说,它只能做到标识,而不能做任何具有破坏性的事情。如果依然担心自己站点的用户被记录下行为,那就不要挂任何等三方的脚本。
Session:Cookie并非是完美的,数据极容易被篡改和伪造。如果VIP服务在Cookie中的isVIP字段,所以Cookie对于敏感数据的保护是无效的。为此Session应运而生。Session的数据只保留在服务器端,但如何将每个客户和服务器中的数据一一对应起来,常见两种方式:
1、基于Cookie来实现用户和数据的映射:Session启用Session时,将约定一个键值作为口令,及一个短的有效期,生成session的代码如下:如果客户端禁止使用Cookie,那么就无法实现登录等操作。
var sessions = {};
var key = 'session_id';
var EXPIRES = 20 * 60 * 1000;
var generate = function() {
var session = {};
session.id = (new Date()).getTime() + Math.random();
session.cookie = {
expire: (new Date()).getTime() + EXPIRES
};
session[session.id] = session;
return session;
};
2、通过查询字符串来实现浏览器端和服务器端数据的对应:如果服务器发现查询字符串中不带session_id参数,就会将用户跳转到http://xxx/path?session_id=12344这样一个类似的地址,如果浏览器收到302状态码和Location报头,就会重新发起新的请求,但其风险远大于基于Cookie实现的风险。
Session与内存:Session数据直接存在变量session中,而Node会存在内存限制,如果用户增多,会到内存限制,另一个问题是为了利用多核CPU而启动多进程,用户请求的连接进程之间不能直接共享内存的。
为了解决性能问题和Session数据无法跨进程共享的问题,常用的方案是将Session集中化。目前常用的工具是Redis、Memcached等,通过这些高效的缓存,Node进程无须在内部维护数据对象,垃圾回收问题和内存限制问题都可以迎刃而阶,并且这些高速缓存设计的缓存过期策略更合理更高效,比在Node中自行设计缓存策略更好。
采用第三方缓存来存储Session引起的一个问题是会引起网络访问。理论上来说访问网络中的数据要比访问本地磁盘中的数据速度要慢,因为涉及到握手、传输以及网络终端自身的磁盘I/O等,尽管如此但依然会采用这些高速缓存的理由有以下几条:
- Node与缓存服务保持长连接,而非频繁的短连接,握手导致的延迟只影响初始化。
- 高速缓存直接在内存中进行数据存储和访问
- 缓存服务通常与Node进程运行在相同的机器上或者相同的机房里,网络速度受到的影响较小。
Session与安全:口令保存在客户端,自行设计的随机算法有理论机会命中有效的口令值。将口令通过私钥加密进行签名,使得伪造的成本较高。口令存在Cookie中不容易被他人获取,但是一些别的漏洞。
XSS漏洞(跨站脚本攻击):网站开发者决定哪些脚本可以执行在浏览器端,不过XSS漏洞会让别的脚本执行。主要形成原因多数是用户的输入没有被转义,而被直接执行。
缓存:为了提高性能,YSlow中也提到规则:添加Expires或Cache-Control到报头中、配置ETags(内容是否匹配)、让Ajax可缓存。报文中If-Modified-Since字段询问是否有更新,如果没有只响应304状态码,客户端就使用本地版本。
Basic认证:报文中Authorization字段的内容,由认证方式和加密值构成。Authorization: Basic dXsdiCfda。将对”username + ":" + password“进行Base64编码
8.2 数据上传
需要接收一些数据,如表单提交、文件提交、JSON上传、XML上传等。通过报头的Transfer-Encoding或Content-Length即可判断请求中是否带有内容。
表单数据:直接访问req.body就可得到表单数据。Content-Type的值为application/x-www.form-urlencoded
其他格式:req.rawBody中, Content-Type: application/json; charset=utf-8
附件上传:file类似的控件,表单属性为enctype为multipart/form-data:本次提交有多部分构成的。Content-Length: 18231,Content-Type: multipart/form-data; boundary=AaB03x(随机字符串:每部分内容的分界符)
function (req, res) {
if (hasBody(req) {
var done = function() {
handle(req, res);
};
if (mime(req) === 'application/json') {
parseJSON(req, done);
} else if (mime(req) === "application/xml') {
parseXML(req, done);
} else if (mime(req) === 'multipart/form-data') {
parseMultipart(req, done);
}
} else {
handle(req, res);
}
}
数据上传与安全:内存和CSRF相关的安全问题
内存限制:先保存用户提交的数据,然后再解析处理,最后才传递给业务逻辑。解决方案:限制大小和通过流式解析。
CSRF(跨站请求伪造):解决方案有添加随机值,在Session中赋予一个随机值,req.session.csrf = generateRandom(24);
8.3 路由解析
介绍文件路径、MVC、RESTful等路由方式。
文件路径型:静态文件(将请求的文件发送给客户端)和动态文件(执行动态脚本,如*.php去寻找php解析器执行,并传入HTTP请求的上下文)
MVC:直到开发者发现用户请求的URL路径原来可以跟具体脚本所在的路径没有任何关系
主要思想是将业务逻辑按职责分离:
- 控制器,一组行为的集合。
- 模型:数据相关的操作和封装。
- 视图:视图的渲染
它的工作模式如下:
- 路由解析,根据URL寻找到对应的控制器和行为。手工路由和自然路由
- 行为调用相关的模型,进行数据操作。
- 数据操作结束后,调用视图和相关数据进行页面渲染,输出到客户端
RESTful:MVC模式流行很多年,直到RESTful(表现层状态转化),才意识到URL也可以设计得很规范,请求方法也能作为逻辑分发的单元。它的设计哲学主要将服务器提供的内容实体看做一个资源,并表现在URL上。
通过URL设计资源、请求方法定义资源的操作,通过Accept决定资源的表现形式。
RESTful与MVC设计并不冲突,而且是更好的改进。将HTTP请求方法也加入了路由的过程,以及在URL路径上体现的更资源化。
对于多数的应用而言,只需要构建一套RESTful服务接口,就能适应移动端、PC端和各种客户端应用。
8.4 中间件
简化和隔离上面的基础功能和业务逻辑之间的细节。
异常处理:中间间出错该怎么办?
中间件和性能:编写高效的中间件和合理利用路由
8.5 页面渲染
内容响应:响应报头中的Content-*字段
Content-Encoding: gzip
Content-Length: 21179
Content-Type: text/javascript; charset=utf-8 //内容类型为JavaScript
MIME、附件下载、响应JSON和响应跳转
视图渲染:普通HTML内容的响应上
模板:P237
CGI或servlet --> ASP、PHP、JSP动态网页技术 --> MVC
Bigpipe:产生于Facebook公司的前端加载技术,为了解决重数据页面的加载速度页面。解决思路是将页面分割多个部分(pagelet),先向用户输出没有数据的布局(框架),将每个部分逐步输出到前端,再最终渲染填充框架,完成整个网页的渲染。这个过程中需要前端JavaScript的参与,它负责将后续输出的数据渲染到页面上。有几个重点:页面布局框架、后端持续性的数据输出和前端渲染。